Skip to content

Commit

Permalink
Unread badges and loadUnreadThreads button \w icon
Browse files Browse the repository at this point in the history
  • Loading branch information
arnautov-anton committed Jun 11, 2024
1 parent 386105c commit d60dd9b
Show file tree
Hide file tree
Showing 9 changed files with 184 additions and 136 deletions.
36 changes: 19 additions & 17 deletions examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
ThreadList,
ThreadProvider,
} from 'stream-chat-react';
import 'stream-chat-react/css/v2/index.css';
import '@stream-io/stream-chat-css/dist/v2/css/index.css';

const params = (new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, property) => searchParams.get(property as string),
Expand Down Expand Up @@ -65,20 +65,22 @@ const App = () => {

return (
<Chat client={chatClient}>
{!threadOnly && (
<>
<ChannelList filters={filters} options={options} sort={sort} />
<Channel>
<Window>
<ChannelHeader />
<MessageList returnAllReadData />
<MessageInput focus />
</Window>
<Thread />
</Channel>
</>
)}
{threadOnly && <Threads />}
<div className='str-chat'>
{!threadOnly && (
<>
<ChannelList filters={filters} options={options} sort={sort} />
<Channel>
<Window>
<ChannelHeader />
<MessageList returnAllReadData />
<MessageInput focus />
</Window>
<Thread virtualized />
</Channel>
</>
)}
{threadOnly && <Threads />}
</div>
</Chat>
);
};
Expand All @@ -87,8 +89,8 @@ const Threads = () => {
const [state, setState] = useState<ThreadType | undefined>(undefined);

return (
<div className='str-chat threads'>
<ThreadList onItemPointerDown={(_, thread) => setState(thread)} />
<div className='str-chat__threads'>
<ThreadList threadListItemProps={{ onPointerDown: (_, t) => setState(t) }} />
<ThreadProvider thread={state}>
<Thread virtualized />
</ThreadProvider>
Expand Down
92 changes: 17 additions & 75 deletions examples/vite/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,70 +16,10 @@ body,
display: flex;
height: 100%;

// .str-chat__thread-list {
// width: 50%;
// height: 100%;
// }

.str-chat__thread-list-item {
all: unset;
box-sizing: border-box;
padding-block: 14px;
padding-inline: 8px;
gap: 6px;
& > div.str-chat {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
cursor: pointer;
}

.str-chat__thread-list-item__channel {
font-size: 14px;
font-weight: 400;
}

.str-chat__thread-list-item__parent-message {
font-size: 12px;
font-weight: 400;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

.str-chat__thread-list-item__latest-reply-container {
display: flex;
align-items: center;
gap: 5px;
}

.str-chat__thread-list-item__latest-reply-details {
display: flex;
flex-direction: column;
flex-grow: 1;
gap: 4px;
width: 0;
}

.str-chat__thread-list-item__latest-reply-created-by {
font-weight: 500;
font-size: 16px;
}

.str-chat__thread-list-item__latest-reply-text {
display: flex;
font-size: 14px;
font-weight: 400;
justify-content: space-between;

& > div:first-child {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}

& > div:last-child {
white-space: nowrap;
}
}

.str-chat__channel-list {
Expand Down Expand Up @@ -122,18 +62,6 @@ body,
}
}

.str-chat.threads {
display: flex;
height: 100%;
width: 100%;

.vml {
display: flex;
flex-direction: column;
width: 70%;
}
}

@media screen and (min-width: 768px) {
//.str-chat__channel-list.thread-open {
// &.menu-open {
Expand Down Expand Up @@ -177,4 +105,18 @@ body,
display: none;
}
}
}
}

.str-chat__threads {
display: flex;
width: 100%;

.str-chat__thread {
width: 100%;
}

.str-chat__thread-list {
height: 100%;
max-width: 420px;
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
"@semantic-release/changelog": "^6.0.2",
"@semantic-release/git": "^10.0.1",
"@stream-io/rollup-plugin-node-builtins": "^2.1.5",
"@stream-io/stream-chat-css": "^4.16.1",
"@stream-io/stream-chat-css": "link:../stream-chat-css/",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^13.1.1",
"@testing-library/react-hooks": "^8.0.0",
Expand Down
9 changes: 2 additions & 7 deletions src/components/MessageList/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -225,18 +225,13 @@ const MessageListWithContext = <
<UnreadMessagesNotification unreadCount={channelUnreadUiState?.unread_messages} />
)}
<div
className={clsx(messageListClass, {
[customClasses?.threadList || 'str-chat__thread-list']: threadList,
})}
className={clsx(messageListClass, customClasses?.threadList)}
onScroll={onScroll}
ref={setListElement}
tabIndex={0}
>
{showEmptyStateIndicator ? (
<EmptyStateIndicator
key={'empty-state-indicator'}
listType={threadList ? 'thread' : 'message'}
/>
<EmptyStateIndicator listType={threadList ? 'thread' : 'message'} />
) : (
<InfiniteScroll
className='str-chat__message-list-scroll'
Expand Down
2 changes: 1 addition & 1 deletion src/components/Threads/ThreadContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const useThreadContext = () => {
const thread = useContext(ThreadContext);

const placeholder = useMemo(
() => new Thread({ client, registerEventHandlers: false, threadData: {} }),
() => new Thread({ client, registerSubscriptions: false, threadData: {} }),
[client],
);

Expand Down
60 changes: 42 additions & 18 deletions src/components/Threads/ThreadList/ThreadList.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import React, { useEffect } from 'react';
import { ComputeItemKey, Virtuoso } from 'react-virtuoso';
import { ComputeItemKey, Virtuoso, VirtuosoProps } from 'react-virtuoso';

import type { ComponentType, PointerEvent } from 'react';
import type { InferStoreValueType, Thread, ThreadManager } from 'stream-chat';

import { ThreadListItem } from './ThreadListItem';
import { Icon } from '../icons';
import { useChatContext } from '../../../context';
import { useSimpleStateStore } from '../hooks/useSimpleStateStore';

Expand All @@ -23,40 +24,63 @@ import type { ThreadListItemProps } from './ThreadListItem';
* - probably good idea to move component context up to a Chat component
*/

const selector = (nextValue: InferStoreValueType<ThreadManager>) => [nextValue.threads] as const;
const selector = (nextValue: InferStoreValueType<ThreadManager>) =>
[nextValue.unreadThreads.newIds, nextValue.threads] as const;

const computeItemKey: ComputeItemKey<Thread, unknown> = (_, item) => item.id;

type ThreadListProps = {
onItemPointerDown?: (event: PointerEvent<HTMLButtonElement>, thread: Thread) => void;
ThreadListItem?: ComponentType<ThreadListItemProps>;
// threads?: Thread[]
threadListItemProps?: Omit<ThreadListItemProps, 'thread' | 'onPointerDown'> & {
onPointerDown?: (event: PointerEvent<HTMLButtonElement>, thread: Thread) => void;
};
virtuosoProps?: VirtuosoProps<Thread, unknown>;
};

export const ThreadList = ({
ThreadListItem: PropsThreadListItem = ThreadListItem,
onItemPointerDown,
virtuosoProps,
threadListItemProps: { onPointerDown, ...restThreadListItemProps } = {},
}: ThreadListProps) => {
const { client } = useChatContext();
const [threads] = useSimpleStateStore(client.threads.state, selector);
const [unreadThreadIds, threads] = useSimpleStateStore(client.threads.state, selector);

useEffect(() => {
client.threads.loadNextPage();
}, [client]);

return (
<Virtuoso
atBottomStateChange={(atBottom) => atBottom && client.threads.loadNextPage()}
className='str-chat str-chat__thread-list'
computeItemKey={computeItemKey}
data={threads}
itemContent={(_, thread) => (
<PropsThreadListItem
onPointerDown={(e) => onItemPointerDown?.(e, thread)}
thread={thread}
/>
<div className='str-chat__thread-list-container'>
{/* TODO: create a replaceable banner component, wait for BE to support "in" keyword for query threads */}
{/* TODO: use query threads with limit (unreadThreadsId.length) - should be top of the list, and prepend
- this does not work when we reply to an non-loaded thread and then reply to a loaded thread
- querying afterwards will return only the latest, which was already in the list but not the one we need
*/}
{unreadThreadIds.length > 0 && (
<div className='str-chat__unread-threads-banner'>
{unreadThreadIds.length} unread threads
<button
className='str-chat__unread-threads-banner__button'
onClick={client.threads.loadUnreadThreads}
>
<Icon.Reload />
</button>
</div>
)}
style={{ height: '100%', width: '50%' }}
/>
<Virtuoso
atBottomStateChange={(atBottom) => atBottom && client.threads.loadNextPage()}
className='str-chat__thread-list'
computeItemKey={computeItemKey}
data={threads}
itemContent={(_, thread) => (
<PropsThreadListItem
onPointerDown={(e) => onPointerDown?.(e, thread)}
thread={thread}
{...restThreadListItemProps}
/>
)}
{...virtuosoProps}
/>
</div>
);
};
Loading

0 comments on commit d60dd9b

Please sign in to comment.