Skip to content

Commit

Permalink
Adjusting existing components
Browse files Browse the repository at this point in the history
  • Loading branch information
arnautov-anton committed Jun 21, 2024
1 parent 28819eb commit d897374
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 124 deletions.
43 changes: 31 additions & 12 deletions examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import { ChannelFilters, ChannelOptions, ChannelSort } from 'stream-chat';
import {
Channel,
Expand All @@ -11,7 +11,8 @@ import {
Window,
useCreateChatClient,
ThreadList,
Views,
ChatView,
useChannelStateContext,
} from 'stream-chat-react';
import '@stream-io/stream-chat-css/dist/v2/css/index.css';

Expand All @@ -27,7 +28,7 @@ const filters: ChannelFilters = {
members: { $in: [userId] },
type: 'messaging',
};
const options: ChannelOptions = { limit: 10, presence: true, state: true };
const options: ChannelOptions = { limit: 4, presence: true, state: true };
const sort: ChannelSort = { last_message_at: -1, updated_at: -1 };

type LocalAttachmentType = Record<string, unknown>;
Expand All @@ -52,37 +53,55 @@ type StreamChatGenerics = {
userType: LocalUserType;
};

const C = () => {
const { channel } = useChannelStateContext();

return <button onPointerDown={() => channel.stopWatching()}></button>;
};

const App = () => {
const chatClient = useCreateChatClient<StreamChatGenerics>({
apiKey,
tokenOrProvider: userToken,
userData: { id: userId },
});

// const channel = useMemo(() => {
// if (!chatClient) return;

// const c = chatClient.channel('messaging', 'random-channel-2', {
// members: ['john', 'marco', 'mark'],
// name: 'Random 1',
// });
// c.updatePartial({ set: { name: 'Random 2' } });
// return c
// }, [chatClient]);

if (!chatClient) return <>Loading...</>;

return (
<Chat client={chatClient}>
<Views>
<Views.Selector />
<Views.Channel>
<ChatView>
<ChatView.Selector />
<ChatView.Channels>
<ChannelList filters={filters} options={options} sort={sort} />
<Channel>
<C />
<Window>
<ChannelHeader />
<MessageList returnAllReadData />
<MessageInput focus />
</Window>
<Thread virtualized />
</Channel>
</Views.Channel>
<Views.Threads>
</ChatView.Channels>
<ChatView.Threads>
<ThreadList />
<Views.ThreadAdapter>
<ChatView.ThreadAdapter>
<Thread virtualized />
</Views.ThreadAdapter>
</Views.Threads>
</Views>
</ChatView.ThreadAdapter>
</ChatView.Threads>
</ChatView>
</Chat>
);
};
Expand Down
13 changes: 11 additions & 2 deletions examples/vite/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ body,
}
}

.str-chat__views {
.str-chat__chat-view {
display: flex;
width: 100%;
height: 100%;
Expand All @@ -115,9 +115,18 @@ body,
display: flex;
flex-direction: column;
list-style: none;
margin: unset;
padding-inline: 8px;
padding-block: 16px;
gap: 20px;

& button {
display: flex;
padding-inline: 19px;
}
}

&__channel {
&__channels {
display: flex;
flex-grow: 1;
}
Expand Down
140 changes: 140 additions & 0 deletions src/components/ChatView/ChatView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { ThreadProvider } from '../Threads';

import type { PropsWithChildren } from 'react';
import type { Thread } from 'stream-chat';

const availableChatViews = ['channels', 'threads'] as const;

type ChatViewContextValue = {
activeChatView: typeof availableChatViews[number];
setActiveChatView: (cv: ChatViewContextValue['activeChatView']) => void;
};

const ChatViewContext = createContext<ChatViewContextValue>({
activeChatView: 'channels',
setActiveChatView: () => undefined,
});

export const ChatView = ({ children }: PropsWithChildren) => {
const [activeChatView, setActiveChatView] = useState<ChatViewContextValue['activeChatView']>(
'channels',
);

const value = useMemo(() => ({ activeChatView, setActiveChatView }), [activeChatView]);

return (
<ChatViewContext.Provider value={value}>
<div className='str-chat str-chat__chat-view'>{children}</div>
</ChatViewContext.Provider>
);
};

const ChannelsView = ({ children }: PropsWithChildren) => {
const { activeChatView } = useContext(ChatViewContext);

if (activeChatView !== 'channels') return null;

return <div className='str-chat__chat-view__channels'>{children}</div>;
};

export type ThreadsViewContextValue = {
activeThread: Thread | undefined;
setActiveThread: (cv: ThreadsViewContextValue['activeThread']) => void;
};

const ThreadsViewContext = createContext<ThreadsViewContextValue>({
activeThread: undefined,
setActiveThread: () => undefined,
});

export const useThreadsViewContext = () => useContext(ThreadsViewContext);

const ThreadsView = ({ children }: PropsWithChildren) => {
const { activeChatView } = useContext(ChatViewContext);
const [activeThread, setActiveThread] = useState<ThreadsViewContextValue['activeThread']>(
undefined,
);

const value = useMemo(() => ({ activeThread, setActiveThread }), [activeThread]);

if (activeChatView !== 'threads') return null;

return (
<ThreadsViewContext.Provider value={value}>
<div className='str-chat__chat-view__threads'>{children}</div>
</ThreadsViewContext.Provider>
);
};

// thread business logic that's impossible to keep within client but encapsulated for ease of use
const useThreadBl = ({ thread }: { thread?: Thread }) => {
useEffect(() => {
if (!thread) return;

const handleVisibilityChange = () => {
if (document.visibilityState === 'visible' && document.hasFocus()) {
thread.activate();
thread.markAsRead();
}
if (document.visibilityState === 'hidden' || !document.hasFocus()) {
thread.deactivate();
}
};

handleVisibilityChange();

window.addEventListener('focus', handleVisibilityChange);
window.addEventListener('blur', handleVisibilityChange);
return () => {
thread.deactivate();
window.addEventListener('blur', handleVisibilityChange);
window.removeEventListener('focus', handleVisibilityChange);
};
}, [thread]);
};
// ThreadList under View.Threads context, will access setting function and on item click will set activeThread
// which can be accessed for the ease of use by ThreadAdapter which forwards it to required ThreadProvider
// ThreadList can easily live without this context and click handler can be overriden, ThreadAdapter is then no longer needed
/**
* // this setup still works
* const MyCustomComponent = () => {
* const [activeThread, setActiveThread] = useState();
*
* return <>
* // simplified
* <ThreadList onItemPointerDown={setActiveThread} />
* <ThreadProvider thread={activeThread}>
* <Thread />
* </ThreadProvider>
* </>
* }
*
*/
const ThreadAdapter = ({ children }: PropsWithChildren) => {
const { activeThread } = useThreadsViewContext();

useThreadBl({ thread: activeThread });

return <ThreadProvider thread={activeThread}>{children}</ThreadProvider>;
};

const ChatViewSelector = () => {
const { setActiveChatView } = useContext(ChatViewContext);

return (
<ul className='str-chat__chat-view__selector'>
<li>
<button onPointerDown={() => setActiveChatView('channels')}>Channels</button>
</li>
<li>
<button onPointerDown={() => setActiveChatView('threads')}>Threads</button>
</li>
</ul>
);
};

ChatView.Channels = ChannelsView;
ChatView.Threads = ThreadsView;
ChatView.ThreadAdapter = ThreadAdapter;
ChatView.Selector = ChatViewSelector;
1 change: 1 addition & 0 deletions src/components/ChatView/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ChatView';
50 changes: 39 additions & 11 deletions src/components/Thread/Thread.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ export const Thread = <
};

const selector = (nextValue: InferStoreValueType<ThreadType>) =>
[nextValue.latestReplies, nextValue.loadingPreviousPage, nextValue.parentMessage] as const;
[
nextValue.latestReplies,
nextValue.loadingPreviousPage,
nextValue.parentMessage,
nextValue.read,
] as const;

const ThreadInner = <
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics,
Expand Down Expand Up @@ -133,9 +138,33 @@ const ThreadInner = <
loadMoreThread();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

if (!thread && !parentMessage) return null;
}, [thread, loadMoreThread]);

const threadProps: Pick<
VirtualizedMessageListProps<StreamChatGenerics>,
| 'hasMoreNewer'
| 'loadMoreNewer'
| 'loadingMoreNewer'
| 'hasMore'
| 'loadMore'
| 'loadingMore'
| 'messages'
> = threadInstance.channel
? {
loadingMore: loadingPreviousPage,
loadMore: threadInstance.loadPreviousPage,
messages: latestReplies,
}
: {
hasMore: threadHasMore,
loadingMore: threadLoadingMore,
loadMore: loadMoreThread,
messages: threadMessages,
};

const messageAsThread = thread ?? parentMessage;

if (!messageAsThread) return null;

const threadClass =
customClasses?.thread ||
Expand All @@ -146,27 +175,26 @@ const ThreadInner = <

const head = (
<ThreadHead
key={thread?.id ?? threadInstance.id}
message={thread ?? parentMessage!}
key={messageAsThread.id}
message={messageAsThread}
Message={MessageUIComponent}
{...additionalParentMessageProps}
/>
);

return (
<div className={threadClass}>
<ThreadHeader closeThread={closeThread} thread={thread ?? parentMessage!} />
<ThreadHeader closeThread={closeThread} thread={messageAsThread} />
<ThreadMessageList
disableDateSeparator={!enableDateSeparator}
hasMore={threadHasMore}
head={head}
loadingMore={parentMessage ? loadingPreviousPage : threadLoadingMore}
loadMore={parentMessage ? threadInstance.loadPreviousPage : loadMoreThread}
Message={MessageUIComponent}
messageActions={messageActions}
messages={parentMessage ? latestReplies : threadMessages}
// TODO: allow passing read object?
// read={read}
suppressAutoscroll={threadSuppressAutoscroll}
threadList
{...threadProps}
{...(virtualized ? additionalVirtualizedMessageListProps : additionalMessageListProps)}
/>
<MessageInput
Expand Down
2 changes: 1 addition & 1 deletion src/components/Threads/ThreadList/ThreadListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { useSimpleStateStore } from '../hooks/useSimpleStateStore';
import { Avatar } from '../../Avatar';
import { Icon } from '../icons';
import { useChatContext } from '../../../context';
import { useThreadsViewContext } from '../../Views';
import { useThreadsViewContext } from '../../ChatView';
import clsx from 'clsx';

export type ThreadListItemProps = {
Expand Down
Loading

0 comments on commit d897374

Please sign in to comment.