commit 0466f5d4344d41f92f4bb8bb471c60bc306c6a2c
parent 211ceccb9f5bbaea5ef2f4083bfc48f02245f75b
Author: MTRNord <mtrnord1@gmail.com>
Date: Fri, 21 Apr 2023 22:43:08 +0200
feat: Add roomlist item component
Diffstat:
5 files changed, 232 insertions(+), 17 deletions(-)
diff --git a/src/app/hooks.ts b/src/app/hooks.ts
@@ -1,6 +1,27 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
+import { RefObject, useEffect, useMemo, useState } from 'react';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
+
+export default function useOnScreen(ref: RefObject<HTMLElement>) {
+ const [isIntersecting, setIntersecting] = useState(false)
+
+ const observer = useMemo(() => new IntersectionObserver(
+ ([entry]) => setIntersecting(entry.isIntersecting),
+ {
+ // TODO: This is probably too much or too less
+ rootMargin: "70px"
+ }
+ ), [ref])
+
+
+ useEffect(() => {
+ observer.observe(ref.current as Element)
+ return () => observer.disconnect()
+ }, [])
+
+ return isIntersecting
+}
diff --git a/src/components/avatar/avatar.stories.tsx b/src/components/avatar/avatar.stories.tsx
@@ -15,7 +15,7 @@ const meta: Meta<typeof Avatar> = {
name: "Avatar URL",
description: "The URL of the avatar image",
},
- userID: {
+ displayname: {
required: true,
name: "User ID",
description: "The DISPLAY name of the User",
@@ -42,7 +42,7 @@ type Story = StoryObj<typeof Avatar>;
export const Fallback: Story = {
args: {
- userID: "test",
+ displayname: "test",
dm: false,
online: false,
avatarUrl: ""
@@ -51,7 +51,7 @@ export const Fallback: Story = {
export const Image: Story = {
args: {
- userID: "test",
+ displayname: "test",
avatarUrl: "https://randomuser.me/api/portraits/men/62.jpg",
dm: false,
online: false,
@@ -60,7 +60,7 @@ export const Image: Story = {
export const DMOffline: Story = {
args: {
- userID: "test",
+ displayname: "test",
avatarUrl: "https://randomuser.me/api/portraits/men/62.jpg",
dm: true,
online: false,
@@ -69,7 +69,7 @@ export const DMOffline: Story = {
export const DMOnline: Story = {
args: {
- userID: "test",
+ displayname: "test",
avatarUrl: "https://randomuser.me/api/portraits/men/62.jpg",
dm: true,
online: true,
@@ -78,7 +78,7 @@ export const DMOnline: Story = {
export const DMNoAvatarOffline: Story = {
args: {
- userID: "test",
+ displayname: "test",
avatarUrl: "",
dm: true,
online: false,
@@ -87,7 +87,7 @@ export const DMNoAvatarOffline: Story = {
export const DMNoAvatarOnline: Story = {
args: {
- userID: "test",
+ displayname: "test",
avatarUrl: "",
dm: true,
online: true,
diff --git a/src/components/avatar/avatar.tsx b/src/components/avatar/avatar.tsx
@@ -6,7 +6,7 @@ type AvatarProps = {
/**
* The displayname of the avatar user
*/
- userID: string
+ displayname: string
/**
* Wether it is a DM or not
*/
@@ -17,17 +17,17 @@ type AvatarProps = {
online: boolean
};
-export default function Avatar({ avatarUrl, userID, dm = false, online = false }: AvatarProps) {
+export default function Avatar({ avatarUrl, displayname, dm = false, online = false }: AvatarProps) {
if (avatarUrl) {
return (
- <div className="flex relative w-12 h-12 justify-center items-center m-1 mr-2 text-xl rounded-full text-white">
- <img className="rounded-full" alt={userID} src={avatarUrl} />
+ <div className="flex relative w-8 h-8 justify-center items-center m-1 mr-2 text-xl rounded-full text-white">
+ <img className="rounded-full" alt={displayname} src={avatarUrl} />
{
dm ?
(
online ?
- <div className="bg-green-500 rounded-full w-3 h-3 absolute bottom-[2px] right-[2px]"></div> :
- <div className="bg-red-500 rounded-full w-3 h-3 absolute bottom-[2px] right-[2px]"></div>
+ <div className="bg-green-500 rounded-full w-3 h-3 absolute bottom-0 right-0"></div> :
+ <div className="bg-red-500 rounded-full w-3 h-3 absolute bottom-0 right-0"></div>
) :
<></>
}
@@ -35,14 +35,14 @@ export default function Avatar({ avatarUrl, userID, dm = false, online = false }
);
}
return (
- <div className="flex relative w-12 h-12 bg-orange-500 justify-center items-center m-1 mr-2 text-xl rounded-full text-white">
- {userID[0].toUpperCase()}
+ <div className="flex relative w-8 h-8 bg-orange-500 justify-center items-center m-1 mr-2 text-xl rounded-full text-white">
+ {displayname[0].toUpperCase()}
{
dm ?
(
online ?
- <div className="bg-green-500 rounded-full w-3 h-3 absolute bottom-[2px] right-[2px]"></div> :
- <div className="bg-red-500 rounded-full w-3 h-3 absolute bottom-[2px] right-[2px]"></div>
+ <div className="bg-green-500 rounded-full w-3 h-3 absolute bottom-0 right-0"></div> :
+ <div className="bg-red-500 rounded-full w-3 h-3 absolute bottom-0 right-0"></div>
) :
<></>
}
diff --git a/src/components/roomList/roomListItem/roomListItem.stories.tsx b/src/components/roomList/roomListItem/roomListItem.stories.tsx
@@ -0,0 +1,150 @@
+import { Meta, StoryObj } from '@storybook/react';
+import RoomListItem from './roomListItem';
+import { BADGE } from '@geometricpanda/storybook-addon-badges';
+
+const meta: Meta<typeof RoomListItem> = {
+ title: 'Chat/RoomList/Item',
+ tags: ['autodocs'],
+ component: RoomListItem,
+ parameters: {
+ badges: [BADGE.EXPERIMENTAL]
+ },
+ argTypes: {
+ avatarUrl: {
+ required: false,
+ name: "Avatar URL",
+ description: "The URL of the avatar image",
+ },
+ displayname: {
+ required: true,
+ name: "Displayname",
+ description: "The displayname of the Room",
+ },
+ dm: {
+ required: false,
+ defaultValue: false,
+ control: "boolean",
+ name: "DM",
+ description: "Wether the room is a DM",
+ },
+ online: {
+ required: false,
+ defaultValue: false,
+ control: "boolean",
+ name: "Online",
+ description: "Wether the user is online. Only used if dm is true",
+ },
+ active: {
+ required: false,
+ defaultValue: false,
+ control: "boolean",
+ name: "Active",
+ description: "Wether the room is currently selected",
+ },
+ }
+};
+
+export default meta;
+type Story = StoryObj<typeof RoomListItem>;
+
+export const Fallback: Story = {
+ args: {
+ displayname: "test",
+ dm: false,
+ online: false,
+ avatarUrl: "",
+ active: false,
+ }
+};
+
+export const FallbackActive: Story = {
+ args: {
+ displayname: "test",
+ dm: false,
+ online: false,
+ avatarUrl: "",
+ active: true,
+ }
+};
+
+
+export const Image: Story = {
+ args: {
+ displayname: "test",
+ avatarUrl: "https://randomuser.me/api/portraits/men/62.jpg",
+ dm: false,
+ online: false,
+ active: false,
+ }
+};
+
+
+export const ImageActive: Story = {
+ args: {
+ displayname: "test",
+ avatarUrl: "https://randomuser.me/api/portraits/men/62.jpg",
+ dm: false,
+ online: false,
+ active: true,
+ }
+};
+
+export const DMOffline: Story = {
+ args: {
+ displayname: "test",
+ avatarUrl: "https://randomuser.me/api/portraits/men/62.jpg",
+ dm: true,
+ online: false,
+ active: false,
+ }
+};
+
+export const DMOfflineActive: Story = {
+ args: {
+ displayname: "test",
+ avatarUrl: "https://randomuser.me/api/portraits/men/62.jpg",
+ dm: true,
+ online: false,
+ active: true,
+ }
+};
+
+export const DMOnline: Story = {
+ args: {
+ displayname: "test",
+ avatarUrl: "https://randomuser.me/api/portraits/men/62.jpg",
+ dm: true,
+ online: true,
+ active: false,
+ }
+};
+
+export const DMOnlineActive: Story = {
+ args: {
+ displayname: "test",
+ avatarUrl: "https://randomuser.me/api/portraits/men/62.jpg",
+ dm: true,
+ online: true,
+ active: true,
+ }
+};
+
+export const DMNoAvatarOffline: Story = {
+ args: {
+ displayname: "test",
+ avatarUrl: "",
+ dm: true,
+ online: false,
+ active: false,
+ }
+};
+
+export const DMNoAvatarOnline: Story = {
+ args: {
+ displayname: "test",
+ avatarUrl: "",
+ dm: true,
+ online: true,
+ active: false,
+ }
+};
+\ No newline at end of file
diff --git a/src/components/roomList/roomListItem/roomListItem.tsx b/src/components/roomList/roomListItem/roomListItem.tsx
@@ -0,0 +1,42 @@
+import Avatar from "../../avatar/avatar";
+
+type RoomListItemProps = {
+ /**
+ * The URL of the Avatar image
+ */
+ avatarUrl?: string
+ /**
+ * The displayname of the room list item
+ */
+ displayname: string
+ /**
+ * Wether it is a DM or not
+ */
+ dm: boolean
+ /**
+ * Wether the user is online. Only used if dm is true.
+ */
+ online: boolean
+ /**
+ * Wether the current room is selected
+ */
+ active: boolean
+};
+
+export default function RoomListItem({ avatarUrl, displayname, dm = false, online = false, active = false }: RoomListItemProps) {
+ if (!active) {
+ return (
+ <div className="flex flex-row gap-2 p-1 hover:bg-gray-300 hover:rounded duration-150 ease-in items-center">
+ <Avatar avatarUrl={avatarUrl} displayname={displayname} dm={dm} online={online} />
+ <span className='text-black font-normal text-xl capitalize'>{displayname}</span>
+ </div>
+ );
+ } else {
+ return (
+ <div className="flex flex-row gap-2 p-1 bg-gray-300 rounded duration-150 ease-in items-center">
+ <Avatar avatarUrl={avatarUrl} displayname={displayname} dm={dm} online={online} />
+ <span className='text-black font-normal text-xl capitalize'>{displayname}</span>
+ </div>
+ );
+ }
+}
+\ No newline at end of file