Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 43 additions & 16 deletions examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,23 +181,49 @@ const App = () => {
const { tokenProvider, userId, userImage, userName } = useUser();
const chatView = useAppSettingsSelector((state) => state.chatView);
const { mode: themeMode } = useAppSettingsSelector((state) => state.theme);
const initialSearchParams = useMemo(
() => new URLSearchParams(window.location.search),
[],
);
const initialChannelId = useMemo(() => getSelectedChannelIdFromUrl(), []);
const initialChatView = useMemo(() => getSelectedChatViewFromUrl(), []);
const initialThreadOpen = useMemo(
() => Boolean(new URLSearchParams(window.location.search).get('thread')),
[],
const initialThreadId = useMemo(
() => initialSearchParams.get('thread'),
[initialSearchParams],
);
const initialThreadOpen = useMemo(() => Boolean(initialThreadId), [initialThreadId]);
const initialPanelLayout = useMemo(
() => appSettingsStore.getLatestValue().panelLayout,
[],
);
const initialNavOpen = useMemo(
() =>
typeof window !== 'undefined' && window.innerWidth < DESKTOP_LAYOUT_BREAKPOINT
? true
: !initialPanelLayout.leftPanel.collapsed,
[initialPanelLayout.leftPanel.collapsed],
);
const initialNavOpen = useMemo(() => {
if (typeof window === 'undefined') return !initialPanelLayout.leftPanel.collapsed;

const isMobile = window.innerWidth < DESKTOP_LAYOUT_BREAKPOINT;

if (!isMobile) return !initialPanelLayout.leftPanel.collapsed;

const hasSelectedChannel = Boolean(initialChannelId);
const hasSelectedThread = Boolean(initialThreadId);
const channelsView = initialChatView !== 'threads';

// Keep sidebar open on mobile when a channel is preselected via URL.
// It will auto-collapse later once the selected channel is actually resolved.
if (channelsView && hasSelectedChannel) {
return true;
}

if ((!channelsView && hasSelectedThread) || hasSelectedThread) {
return false;
}

return true;
}, [
initialChannelId,
initialChatView,
initialPanelLayout.leftPanel.collapsed,
initialThreadId,
]);
const appLayoutRef = useRef<HTMLDivElement | null>(null);

const chatClient = useCreateChatClient({
Expand Down Expand Up @@ -298,8 +324,8 @@ const App = () => {
return (
<LoadingScreen
initialAppLayoutStyle={initialAppLayoutStyle}
initialChannelSelected={Boolean(initialChannelId)}
initialNavOpen={initialNavOpen}
initialThreadOpen={initialThreadOpen}
/>
);
}
Expand Down Expand Up @@ -358,17 +384,18 @@ const App = () => {
<ChatView>
<ChatStateSync initialChatView={initialChatView} />
<SidebarLayoutSync />
<ChatView.Selector
itemSet={chatViewSelectorItemSet}
iconOnly={chatView.iconOnly}
/>
<ChannelsPanels
filters={filters}
iconOnly={chatView.iconOnly}
initialChannelId={initialChannelId ?? undefined}
itemSet={chatViewSelectorItemSet}
options={options}
sort={sort}
/>
<ThreadsPanels />
<ThreadsPanels
iconOnly={chatView.iconOnly}
itemSet={chatViewSelectorItemSet}
/>
</ChatView>
</div>
</Chat>
Expand Down
4 changes: 2 additions & 2 deletions examples/vite/src/AppSettings/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export type NotificationsSettingsState = {
verticalAlignment: 'bottom' | 'top';
};

export const LEFT_PANEL_MIN_WIDTH = 360;
export const THREAD_PANEL_MIN_WIDTH = 360;
export const LEFT_PANEL_MIN_WIDTH = 260;
export const THREAD_PANEL_MIN_WIDTH = 260;

export type LeftPanelLayoutSettingsState = {
collapsed: boolean;
Expand Down
98 changes: 67 additions & 31 deletions examples/vite/src/ChatLayout/Panels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
WithDragAndDropUpload,
useChannelStateContext,
useChatContext,
type ChatViewSelectorEntry,
useThreadsViewContext,
} from 'stream-chat-react';

import { SidebarResizeHandle, ThreadResizeHandle } from './Resize.tsx';
Expand All @@ -41,80 +43,114 @@ const ChannelThreadPanel = () => {
);
};

const ResponsiveChannelPanels = () => {
const { thread } = useChannelStateContext('ResponsiveChannelPanels');
const isThreadOpen = !!thread;

return (
<div
className={clsx('app-chat-view__channel-content', {
'app-chat-view__channel-content--thread-open': isThreadOpen,
})}
>
<WithDragAndDropUpload className='app-chat-view__channel-main'>
<Window>
<ChannelHeader Avatar={ChannelAvatar} />
<MessageList returnAllReadData />
<AIStateIndicator />
<MessageComposer
focus
audioRecordingEnabled
maxRows={10}
asyncMessagesMultiSendEnabled
/>
</Window>
</WithDragAndDropUpload>
<ChannelThreadPanel />
</div>
);
};

export const ChannelsPanels = ({
filters,
iconOnly,
initialChannelId,
itemSet,
options,
sort,
}: {
filters: ChannelFilters;
iconOnly?: boolean;
initialChannelId?: string;
itemSet?: ChatViewSelectorEntry[];
options: ChannelOptions;
sort: ChannelSort;
}) => {
const { navOpen = true } = useChatContext('ChannelsPanels');
const { channel, navOpen = true } = useChatContext('ChannelsPanels');
const channelsLayoutRef = useRef<HTMLDivElement | null>(null);

return (
<ChatView.Channels>
<div
className={clsx('app-chat-view__channels-layout', {
'app-chat-view__channels-layout--channel-selected': !!channel?.id,
'app-chat-view__channels-layout--sidebar-collapsed': navOpen === false,
})}
ref={channelsLayoutRef}
>
<WithComponents
overrides={{
// @ts-expect-error TODO: adjust the sizing
Avatar: ChannelAvatar,
}}
>
<ChannelList
customActiveChannel={initialChannelId}
filters={filters}
options={options}
sort={sort}
showChannelSearch
/>
</WithComponents>
<div className='app-chat-sidebar-overlay'>
<ChatView.Selector iconOnly={iconOnly} itemSet={itemSet} />
<WithComponents
overrides={{
// @ts-expect-error TODO: adjust the sizing
Avatar: ChannelAvatar,
}}
>
<ChannelList
customActiveChannel={initialChannelId}
filters={filters}
options={options}
sort={sort}
showChannelSearch
/>
</WithComponents>
</div>
<SidebarResizeHandle layoutRef={channelsLayoutRef} />
<WithComponents overrides={{ TypingIndicator }}>
<Channel>
<WithDragAndDropUpload>
<Window>
<ChannelHeader Avatar={ChannelAvatar} />
<MessageList returnAllReadData />
<AIStateIndicator />
<MessageComposer
focus
audioRecordingEnabled
maxRows={10}
asyncMessagesMultiSendEnabled
/>
</Window>
</WithDragAndDropUpload>
<ChannelThreadPanel />
<ResponsiveChannelPanels />
</Channel>
</WithComponents>
</div>
</ChatView.Channels>
);
};

export const ThreadsPanels = () => {
export const ThreadsPanels = ({
iconOnly,
itemSet,
}: {
iconOnly?: boolean;
itemSet?: ChatViewSelectorEntry[];
}) => {
const { navOpen = true } = useChatContext('ThreadsPanels');
const { activeThread } = useThreadsViewContext();
const threadsLayoutRef = useRef<HTMLDivElement | null>(null);

return (
<ChatView.Threads>
<ThreadStateSync />
<div
className={clsx('app-chat-view__threads-layout', {
'app-chat-view__threads-layout--thread-selected': !!activeThread?.id,
'app-chat-view__threads-layout--sidebar-collapsed': navOpen === false,
})}
ref={threadsLayoutRef}
>
<ThreadList />
<div className='app-chat-sidebar-overlay'>
<ChatView.Selector iconOnly={iconOnly} itemSet={itemSet} />
<ThreadList />
</div>
<SidebarResizeHandle layoutRef={threadsLayoutRef} />
<div className='app-chat-view__threads-main'>
<ChatView.ThreadAdapter>
Expand Down
80 changes: 32 additions & 48 deletions examples/vite/src/LoadingScreen/LoadingScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,52 @@ import { LoadingChannel, LoadingChannels } from 'stream-chat-react';
// Update this layout every time layout in App.tsx is updated.
type LoadingScreenProps = {
initialAppLayoutStyle: CSSProperties;
initialChannelSelected: boolean;
initialNavOpen: boolean;
initialThreadOpen: boolean;
};

const selectorButtonCount = 4;

export const LoadingScreen = ({
initialAppLayoutStyle,
initialChannelSelected,
initialNavOpen,
initialThreadOpen,
}: LoadingScreenProps) => (
<div className='app-chat-layout' style={initialAppLayoutStyle}>
<div className='str-chat'>
<div className='str-chat__chat-view'>
<div
className={clsx('str-chat__chat-view__selector', {
'str-chat__chat-view__selector--nav-closed': !initialNavOpen,
'str-chat__chat-view__selector--nav-open': initialNavOpen,
})}
>
{Array.from({ length: selectorButtonCount }).map((_, index) => (
<div className='str-chat__chat-view__selector-button-container' key={index}>
<div className='str-chat__chat-view__selector-button'>
<span className='str-chat__loading-channels-avatar' />
</div>
</div>
))}
</div>
<div className='str-chat__chat-view__channels'>
<div
className={clsx('app-chat-view__channels-layout', {
'app-chat-view__channels-layout--channel-selected': initialChannelSelected,
'app-chat-view__channels-layout--sidebar-collapsed': !initialNavOpen,
})}
>
<div className='str-chat__channel-list'>
<LoadingChannels />
<div className='app-chat-sidebar-overlay'>
<div
className={clsx('str-chat__chat-view__selector', {
'str-chat__chat-view__selector--nav-closed': !initialNavOpen,
'str-chat__chat-view__selector--nav-open': initialNavOpen,
})}
>
{Array.from({ length: selectorButtonCount }).map((_, index) => (
<div
className='str-chat__chat-view__selector-button-container'
key={index}
>
<div className='str-chat__chat-view__selector-button'>
<span className='str-chat__loading-channels-avatar' />
</div>
</div>
))}
</div>
<div
className={clsx('str-chat__channel-list', {
'str-chat__channel-list--open': initialNavOpen,
})}
>
<LoadingChannels />
</div>
</div>
<div
aria-orientation='vertical'
Expand All @@ -56,38 +66,12 @@ export const LoadingScreen = ({
</div>
</div>
<div className='str-chat__channel'>
<div className='str-chat__container'>
<div className='str-chat__main-panel'>
<div className='str-chat__main-panel-inner'>
<div className='str-chat__window'>
<LoadingChannel />
</div>
<div className='str-chat__main-panel'>
<div className='str-chat__main-panel-inner'>
<div className='str-chat__window app-loading-screen__window'>
<LoadingChannel />
</div>
</div>
<div
aria-orientation='vertical'
className={clsx(
'app-chat-resize-handle app-chat-resize-handle--thread',
{
'app-chat-resize-handle--thread-hidden': !initialThreadOpen,
},
)}
role='separator'
>
<div className='app-chat-resize-handle__hitbox'>
<div className='app-chat-resize-handle__line' />
</div>
</div>
<div
className={clsx(
'str-chat__dropzone-root--thread app-chat-thread-panel',
{
'app-chat-thread-panel--open': initialThreadOpen,
},
)}
>
<LoadingChannel />
</div>
</div>
</div>
</div>
Expand Down
Loading
Loading