Skip to content
Open
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
109 changes: 73 additions & 36 deletions examples/vite/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ import {
import {
Attachment,
type AttachmentProps,
Button,
Chat,
ChatView,
createIcon,
MessageReactions,
type NotificationListProps,
NotificationList,
Expand All @@ -27,6 +29,7 @@ import {
type ReactionOptions,
mapEmojiMartData,
useCreateChatClient,
useTranslationContext,
Search,
} from 'stream-chat-react';
import { createTextComposerEmojiMiddleware, EmojiPicker } from 'stream-chat-react/emojis';
Expand All @@ -37,6 +40,7 @@ import { humanId } from 'human-id';
import { appSettingsStore, useAppSettingsSelector } from './AppSettings';
import { DESKTOP_LAYOUT_BREAKPOINT } from './ChatLayout/constants.ts';
import { ChannelsPanels, ThreadsPanels } from './ChatLayout/Panels.tsx';
import { SidebarProvider, useSidebar } from './ChatLayout/SidebarContext.tsx';
import {
ChatViewSelectorWidthSync,
PanelLayoutStyleSync,
Expand Down Expand Up @@ -162,6 +166,36 @@ const ConfigurableNotificationList = (props: NotificationListProps) => {
return <NotificationList {...props} verticalAlignment={verticalAlignment} />;
};

const IconSidebar = createIcon(
'IconSidebar',
<path
d='M6.875 3.75V16.25M3.125 3.75H16.875C17.2202 3.75 17.5 4.02982 17.5 4.375V15.625C17.5 15.9702 17.2202 16.25 16.875 16.25H3.125C2.77982 16.25 2.5 15.9702 2.5 15.625V4.375C2.5 4.02982 2.77982 3.75 3.125 3.75Z'
fill='none'
stroke='currentColor'
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth='1.5'
/>,
);

const SidebarToggle = () => {
const { closeSidebar, openSidebar, sidebarOpen } = useSidebar();
const { t } = useTranslationContext();
return (
<Button
appearance='ghost'
aria-label={sidebarOpen ? t('aria/Collapse sidebar') : t('aria/Expand sidebar')}
circular
className='str-chat__header-sidebar-toggle'
onClick={sidebarOpen ? closeSidebar : openSidebar}
size='md'
variant='secondary'
>
<IconSidebar />
</Button>
);
};

const language = new URLSearchParams(window.location.search).get('language');
const i18nInstance = language ? new Streami18n({ language: language as any }) : undefined;

Expand Down Expand Up @@ -195,7 +229,7 @@ const App = () => {
() => appSettingsStore.getLatestValue().panelLayout,
[],
);
const initialNavOpen = useMemo(() => {
const initialSidebarOpen = useMemo(() => {
if (typeof window === 'undefined') return !initialPanelLayout.leftPanel.collapsed;

const isMobile = window.innerWidth < DESKTOP_LAYOUT_BREAKPOINT;
Expand Down Expand Up @@ -324,7 +358,7 @@ const App = () => {
<LoadingScreen
initialAppLayoutStyle={initialAppLayoutStyle}
initialChannelSelected={Boolean(initialChannelId)}
initialNavOpen={initialNavOpen}
initialSidebarOpen={initialSidebarOpen}
/>
);
}
Expand Down Expand Up @@ -358,46 +392,49 @@ const App = () => {
MessageReactions: CustomMessageReactions,
reactionOptions: newReactionOptions,
Search: CustomChannelSearch,
HeaderEndContent: SidebarToggle,
HeaderStartContent: SidebarToggle,
...messageUiOverrides,
}}
>
<Chat
searchController={searchController}
client={chatClient}
i18nInstance={i18nInstance}
initialNavOpen={initialNavOpen}
isMessageAIGenerated={isMessageAIGenerated}
theme={chatTheme}
>
<div
className='app-chat-layout'
ref={appLayoutRef}
style={initialAppLayoutStyle}
data-variant={messageUiVariant ?? undefined}
<SidebarProvider initialOpen={initialSidebarOpen}>
<Chat
searchController={searchController}
client={chatClient}
i18nInstance={i18nInstance}
isMessageAIGenerated={isMessageAIGenerated}
theme={chatTheme}
>
<PanelLayoutStyleSync layoutRef={appLayoutRef} />
<ChatViewSelectorWidthSync
iconOnly={chatView.iconOnly}
layoutRef={appLayoutRef}
/>
<ChatView>
<ChatStateSync initialChatView={initialChatView} />
<SidebarLayoutSync />
<ChannelsPanels
filters={filters}
iconOnly={chatView.iconOnly}
initialChannelId={initialChannelId ?? undefined}
itemSet={chatViewSelectorItemSet}
options={options}
sort={sort}
/>
<ThreadsPanels
<div
className='app-chat-layout'
ref={appLayoutRef}
style={initialAppLayoutStyle}
data-variant={messageUiVariant ?? undefined}
>
<PanelLayoutStyleSync layoutRef={appLayoutRef} />
<ChatViewSelectorWidthSync
iconOnly={chatView.iconOnly}
itemSet={chatViewSelectorItemSet}
layoutRef={appLayoutRef}
/>
</ChatView>
</div>
</Chat>
<ChatView>
<ChatStateSync initialChatView={initialChatView} />
<SidebarLayoutSync />
<ChannelsPanels
filters={filters}
iconOnly={chatView.iconOnly}
initialChannelId={initialChannelId ?? undefined}
itemSet={chatViewSelectorItemSet}
options={options}
sort={sort}
/>
<ThreadsPanels
iconOnly={chatView.iconOnly}
itemSet={chatViewSelectorItemSet}
/>
</ChatView>
</div>
</Chat>
</SidebarProvider>
</WithComponents>
);
};
Expand Down
10 changes: 6 additions & 4 deletions examples/vite/src/ChatLayout/Panels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from 'stream-chat-react';

import { SidebarResizeHandle, ThreadResizeHandle } from './Resize.tsx';
import { useSidebar } from './SidebarContext.tsx';
import { ThreadStateSync } from './Sync.tsx';

const ChannelThreadPanel = () => {
Expand Down Expand Up @@ -92,15 +93,16 @@ export const ChannelsPanels = ({
options: ChannelOptions;
sort: ChannelSort;
}) => {
const { channel, navOpen = true } = useChatContext('ChannelsPanels');
const { channel } = useChatContext('ChannelsPanels');
const { sidebarOpen } = useSidebar();
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,
'app-chat-view__channels-layout--sidebar-collapsed': !sidebarOpen,
})}
ref={channelsLayoutRef}
>
Expand Down Expand Up @@ -138,7 +140,7 @@ export const ThreadsPanels = ({
iconOnly?: boolean;
itemSet?: ChatViewSelectorEntry[];
}) => {
const { navOpen = true } = useChatContext('ThreadsPanels');
const { sidebarOpen } = useSidebar();
const { activeThread } = useThreadsViewContext();
const threadsLayoutRef = useRef<HTMLDivElement | null>(null);

Expand All @@ -148,7 +150,7 @@ export const ThreadsPanels = ({
<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,
'app-chat-view__threads-layout--sidebar-collapsed': !sidebarOpen,
})}
ref={threadsLayoutRef}
>
Expand Down
16 changes: 8 additions & 8 deletions examples/vite/src/ChatLayout/Resize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
useEffect,
useRef,
} from 'react';
import { useChatContext } from 'stream-chat-react';
import { useSidebar } from './SidebarContext.tsx';

import {
type LeftPanelLayoutSettingsState,
Expand Down Expand Up @@ -197,7 +197,7 @@ const PanelResizeHandle = ({
);

export const SidebarLayoutSync = () => {
const { navOpen = true } = useChatContext();
const { sidebarOpen } = useSidebar();
const { collapsed: leftPanelCollapsed } = useAppSettingsSelector(
(state) => state.panelLayout.leftPanel,
);
Expand All @@ -209,7 +209,7 @@ export const SidebarLayoutSync = () => {

if (document.body.classList.contains('app-chat-resizing-sidebar')) return;

const shouldBeCollapsed = !navOpen;
const shouldBeCollapsed = !sidebarOpen;

if (shouldBeCollapsed === leftPanelCollapsed) return;

Expand All @@ -220,7 +220,7 @@ export const SidebarLayoutSync = () => {
collapsed: shouldBeCollapsed,
},
}));
}, [leftPanelCollapsed, navOpen]);
}, [leftPanelCollapsed, sidebarOpen]);

return null;
};
Expand All @@ -230,7 +230,7 @@ export const SidebarResizeHandle = ({
}: {
layoutRef: RefObject<HTMLDivElement | null>;
}) => {
const { closeMobileNav, openMobileNav } = useChatContext('SidebarResizeHandle');
const { closeSidebar, openSidebar } = useSidebar();
const leftPanel = useAppSettingsSelector((state) => state.panelLayout.leftPanel);
const isSidebarCollapsedRef = useRef(leftPanel.collapsed);
const leftPanelStateRef = useRef(leftPanel);
Expand Down Expand Up @@ -301,9 +301,9 @@ export const SidebarResizeHandle = ({
isSidebarCollapsedRef.current = shouldCollapse;

if (shouldCollapse) {
closeMobileNav();
closeSidebar();
} else {
openMobileNav();
openSidebar();
}
}
},
Expand All @@ -330,7 +330,7 @@ export const SidebarResizeHandle = ({
pointerId: event.pointerId,
});
},
[closeMobileNav, layoutRef, openMobileNav],
[closeSidebar, layoutRef, openSidebar],
);

return (
Expand Down
32 changes: 32 additions & 0 deletions examples/vite/src/ChatLayout/SidebarContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createContext, useCallback, useContext, useState } from 'react';
import type { PropsWithChildren } from 'react';

type SidebarContextValue = {
closeSidebar: () => void;
openSidebar: () => void;
sidebarOpen: boolean;
};

const SidebarContext = createContext<SidebarContextValue | undefined>(undefined);

export const useSidebar = () => {
const value = useContext(SidebarContext);
if (!value) throw new Error('useSidebar must be used within a SidebarProvider');
return value;
};

export const SidebarProvider = ({
children,
initialOpen = true,
}: PropsWithChildren<{ initialOpen?: boolean }>) => {
const [sidebarOpen, setSidebarOpen] = useState(initialOpen);

const closeSidebar = useCallback(() => setSidebarOpen(false), []);
const openSidebar = useCallback(() => setSidebarOpen(true), []);

return (
<SidebarContext.Provider value={{ closeSidebar, openSidebar, sidebarOpen }}>
{children}
</SidebarContext.Provider>
);
};
19 changes: 5 additions & 14 deletions examples/vite/src/LoadingScreen/LoadingScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import { LoadingChannel, LoadingChannels } from 'stream-chat-react';
type LoadingScreenProps = {
initialAppLayoutStyle: CSSProperties;
initialChannelSelected: boolean;
initialNavOpen: boolean;
initialSidebarOpen: boolean;
};

const selectorButtonCount = 4;

export const LoadingScreen = ({
initialAppLayoutStyle,
initialChannelSelected,
initialNavOpen,
initialSidebarOpen,
}: LoadingScreenProps) => (
<div className='app-chat-layout' style={initialAppLayoutStyle}>
<div className='str-chat'>
Expand All @@ -27,16 +27,11 @@ export const LoadingScreen = ({
<div
className={clsx('app-chat-view__channels-layout', {
'app-chat-view__channels-layout--channel-selected': initialChannelSelected,
'app-chat-view__channels-layout--sidebar-collapsed': !initialNavOpen,
'app-chat-view__channels-layout--sidebar-collapsed': !initialSidebarOpen,
})}
>
<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,
})}
>
<div className='str-chat__chat-view__selector'>
{Array.from({ length: selectorButtonCount }).map((_, index) => (
<div
className='str-chat__chat-view__selector-button-container'
Expand All @@ -48,11 +43,7 @@ export const LoadingScreen = ({
</div>
))}
</div>
<div
className={clsx('str-chat__channel-list', {
'str-chat__channel-list--open': initialNavOpen,
})}
>
<div className='str-chat__channel-list'>
<LoadingChannels />
</div>
</div>
Expand Down
21 changes: 16 additions & 5 deletions examples/vite/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,22 @@ body {
0s, 0s, 0s, 0s, 0s, 0s, var(--str-chat__channel-list-transition-duration, 180ms);
}

.app-chat-sidebar-overlay > .str-chat__chat-view__selector,
.app-chat-sidebar-overlay
> .str-chat__chat-view__selector.str-chat__chat-view__selector--nav-closed,
.app-chat-sidebar-overlay
> .str-chat__chat-view__selector.str-chat__chat-view__selector--nav-open {
/* Hide expand toggle in content headers when sidebar is visible */
.app-chat-view__channels-layout:not(
.app-chat-view__channels-layout--sidebar-collapsed
)
.str-chat__channel-header
.str-chat__header-sidebar-toggle {
display: none;
}

.app-chat-view__threads-layout:not(.app-chat-view__threads-layout--sidebar-collapsed)
.str-chat__thread-header
.str-chat__header-sidebar-toggle {
display: none;
}

.app-chat-sidebar-overlay > .str-chat__chat-view__selector {
position: static;
inset: auto;
width: var(--str-chat__chat-view-selector-mobile-width);
Expand Down
Loading
Loading