From 9ffd481110b116341b6a7687972d7fd041bab7f3 Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Fri, 6 Mar 2026 17:06:26 +0100 Subject: [PATCH 1/3] Initial commit --- examples/vite/src/App.tsx | 4 +- examples/vite/src/stream-imports-layout.scss | 4 +- examples/vite/src/stream-imports-theme.scss | 4 +- src/components/Chat/Chat.tsx | 4 +- .../Search/SearchBar/SearchBar.tsx | 22 +++--- .../SearchResults/SearchResultsHeader.tsx | 4 ++ src/experimental/Search/styling/Search.scss | 70 +++++++++++++++++++ src/experimental/Search/styling/index.scss | 1 + src/styling/index.scss | 1 + 9 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 src/experimental/Search/styling/Search.scss create mode 100644 src/experimental/Search/styling/index.scss diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx index 215d7d0a1..6777b442f 100644 --- a/examples/vite/src/App.tsx +++ b/examples/vite/src/App.tsx @@ -40,6 +40,8 @@ import { humanId } from 'human-id'; import { chatViewSelectorItemSet } from './Sidebar/ChatViewSelectorItemSet.tsx'; import { useAppSettingsState } from './AppSettings'; +import { Search } from 'stream-chat-react/experimental'; + init({ data }); const parseUserIdFromToken = (token: string) => { @@ -204,12 +206,12 @@ const App = () => { /> diff --git a/examples/vite/src/stream-imports-layout.scss b/examples/vite/src/stream-imports-layout.scss index 3daf1ab47..be04cc6ac 100644 --- a/examples/vite/src/stream-imports-layout.scss +++ b/examples/vite/src/stream-imports-layout.scss @@ -12,7 +12,7 @@ //@use 'stream-chat-react/dist/scss/v2/ChannelHeader/ChannelHeader-layout'; //@use 'stream-chat-react/dist/scss/v2/ChannelList/ChannelList-layout'; //@use 'stream-chat-react/dist/scss/v2/ChannelPreview/ChannelPreview-layout'; -@use 'stream-chat-react/dist/scss/v2/ChannelSearch/ChannelSearch-layout'; +// @use 'stream-chat-react/dist/scss/v2/ChannelSearch/ChannelSearch-layout'; @use 'stream-chat-react/dist/scss/v2/common/CTAButton/CTAButton-layout'; @use 'stream-chat-react/dist/scss/v2/common/CircleFAButton/CircleFAButton-layout'; //@use 'stream-chat-react/dist/scss/v2/Dialog/Dialog-layout'; @@ -39,8 +39,8 @@ @use 'stream-chat-react/dist/scss/v2/Notification/NotificationList-layout'; @use 'stream-chat-react/dist/scss/v2/Notification/Notification-layout'; //@use 'stream-chat-react/dist/scss/v2/Poll/Poll-layout'; -@use 'stream-chat-react/dist/scss/v2/Search/Search-layout'; //@use 'stream-chat-react/dist/scss/v2/Thread/Thread-layout'; +//@use 'stream-chat-react/dist/scss/v2/Search/Search-layout'; @use 'stream-chat-react/dist/scss/v2/Tooltip/Tooltip-layout'; @use 'stream-chat-react/dist/scss/v2/TypingIndicator/TypingIndicator-layout'; // @use 'stream-chat-react/dist/scss/v2/ThreadList/ThreadList-layout'; diff --git a/examples/vite/src/stream-imports-theme.scss b/examples/vite/src/stream-imports-theme.scss index e8874fe27..e65b35a73 100644 --- a/examples/vite/src/stream-imports-theme.scss +++ b/examples/vite/src/stream-imports-theme.scss @@ -12,7 +12,7 @@ //@use 'stream-chat-react/dist/scss/v2/ChannelHeader/ChannelHeader-theme'; //@use 'stream-chat-react/dist/scss/v2/ChannelList/ChannelList-theme'; //@use 'stream-chat-react/dist/scss/v2/ChannelPreview/ChannelPreview-theme'; -@use 'stream-chat-react/dist/scss/v2/ChannelSearch/ChannelSearch-theme'; +// @use 'stream-chat-react/dist/scss/v2/ChannelSearch/ChannelSearch-theme'; //@use 'stream-chat-react/dist/scss/v2/Dialog/Dialog-theme'; //@use 'stream-chat-react/dist/scss/v2/DragAndDropContainer/DragAndDropContainer-theme'; //@use 'stream-chat-react/dist/scss/v2/DropzoneContainer/DropzoneContainer-theme'; @@ -33,8 +33,8 @@ @use 'stream-chat-react/dist/scss/v2/Notification/NotificationList-theme'; @use 'stream-chat-react/dist/scss/v2/Notification/Notification-theme'; //@use 'stream-chat-react/dist/scss/v2/Poll/Poll-theme'; -@use 'stream-chat-react/dist/scss/v2/Search/Search-theme'; //@use 'stream-chat-react/dist/scss/v2/Thread/Thread-theme'; +//@use 'stream-chat-react/dist/scss/v2/Search/Search-theme'; @use 'stream-chat-react/dist/scss/v2/Tooltip/Tooltip-theme'; @use 'stream-chat-react/dist/scss/v2/TypingIndicator/TypingIndicator-theme'; // @use 'stream-chat-react/dist/scss/v2/ThreadList/ThreadList-theme'; diff --git a/src/components/Chat/Chat.tsx b/src/components/Chat/Chat.tsx index b452b6486..3258835c8 100644 --- a/src/components/Chat/Chat.tsx +++ b/src/components/Chat/Chat.tsx @@ -90,8 +90,8 @@ export const Chat = (props: PropsWithChildren) => { new SearchController({ sources: [ new ChannelSearchSource(client), - new UserSearchSource(client), - new MessageSearchSource(client), + // new UserSearchSource(client), + // new MessageSearchSource(client), ], }), [client, customChannelSearchController], diff --git a/src/experimental/Search/SearchBar/SearchBar.tsx b/src/experimental/Search/SearchBar/SearchBar.tsx index 474427e16..fdc0bb306 100644 --- a/src/experimental/Search/SearchBar/SearchBar.tsx +++ b/src/experimental/Search/SearchBar/SearchBar.tsx @@ -5,6 +5,7 @@ import { useSearchContext } from '../SearchContext'; import { useSearchQueriesInProgress } from '../hooks'; import { useTranslationContext } from '../../../context'; import { useStateStore } from '../../../store'; +import { Button, IconCrossSmall, IconMagnifyingGlassSearch } from '../../../components'; import type { SearchControllerState } from 'stream-chat'; @@ -47,7 +48,7 @@ export const SearchBar = () => { 'str-chat__search-input--wrapper-active': isActive, })} > -
+ { value={searchQuery} /> {searchQuery && ( - + + )}
- {isActive ? ( + {/* TODO: return button once designs are in */} + {/* {isActive && ( - ) : null} + )} */} ); }; diff --git a/src/experimental/Search/SearchResults/SearchResultsHeader.tsx b/src/experimental/Search/SearchResults/SearchResultsHeader.tsx index c2ea49fc2..5e13d7195 100644 --- a/src/experimental/Search/SearchResults/SearchResultsHeader.tsx +++ b/src/experimental/Search/SearchResults/SearchResultsHeader.tsx @@ -59,6 +59,10 @@ const SearchSourceFilterButton = ({ source }: SearchSourceFilterButtonProps) => export const SearchResultsHeader = () => { const { searchController } = useSearchContext(); + + // render nothing if there's only one source (can't change filters) + if (searchController.sources.length < 2) return null; + return (
Date: Fri, 6 Mar 2026 18:43:56 +0100 Subject: [PATCH 2/3] Add context menu to ChannelPreviewActionButtons --- examples/vite/src/App.tsx | 3 +- src/components/ChannelList/ChannelList.tsx | 106 +++++++++--------- .../ChannelPreviewActionButtons.tsx | 87 ++++++++++---- .../styling/ChannelPreview.scss | 5 + 4 files changed, 126 insertions(+), 75 deletions(-) diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx index 6777b442f..175ede371 100644 --- a/examples/vite/src/App.tsx +++ b/examples/vite/src/App.tsx @@ -69,7 +69,7 @@ const options: ChannelOptions = { state: true, }; // pinned_at param leads to BE not returning (empty) channels -const sort: ChannelSort = { last_message_at: -1, updated_at: -1 }; +const sort: ChannelSort = { pinned_at: -1, last_message_at: -1, updated_at: -1 }; // @ts-ignore const isMessageAIGenerated = (message: LocalMessage) => !!message?.ai_generated; @@ -142,6 +142,7 @@ const App = () => { }, { type: 'public' }, ], + archived: false, }), [userId], ); diff --git a/src/components/ChannelList/ChannelList.tsx b/src/components/ChannelList/ChannelList.tsx index 3c355c492..230cb221a 100644 --- a/src/components/ChannelList/ChannelList.tsx +++ b/src/components/ChannelList/ChannelList.tsx @@ -27,6 +27,7 @@ import { LoadingChannels } from '../Loading/LoadingChannels'; import { LoadMorePaginator } from '../LoadMore/LoadMorePaginator'; import { ChannelListContextProvider, + DialogManagerProvider, useChatContext, useComponentContext, } from '../../context'; @@ -44,6 +45,7 @@ import type { TranslationContextValue } from '../../context/TranslationContext'; import type { PaginatorProps } from '../../types/types'; import type { LoadingErrorIndicatorProps } from '../Loading'; import { ChannelListHeader } from './ChannelListHeader'; +import { useStableId } from '../UtilityComponents/useStableId'; const DEFAULT_FILTERS = {}; const DEFAULT_OPTIONS = {}; @@ -205,6 +207,8 @@ const UnMemoizedChannelList = (props: ChannelListProps) => { watchers = {}, } = props; + const stableId = useStableId(); + const { channel, channelsQueryState, @@ -379,58 +383,60 @@ const UnMemoizedChannelList = (props: ChannelListProps) => { const showChannelList = (!searchActive && !searchIsActive) || additionalChannelSearchProps?.popupResults; return ( - -
- - {showChannelSearch && - (Search ? ( - + +
+ + {showChannelSearch && + (Search ? ( + + ) : ( + + ))} + {showChannelList && ( + - ) : ( - - ))} - {showChannelList && ( - - {!loadedChannels?.length ? ( - - ) : ( - - {renderChannels - ? renderChannels(loadedChannels, renderChannel) - : loadedChannels.map((channel) => renderChannel(channel))} - - )} - - )} -
-
+ > + {!loadedChannels?.length ? ( + + ) : ( + + {renderChannels + ? renderChannels(loadedChannels, renderChannel) + : loadedChannels.map((channel) => renderChannel(channel))} + + )} + + )} +
+
+ ); }; diff --git a/src/components/ChannelPreview/ChannelPreviewActionButtons.tsx b/src/components/ChannelPreview/ChannelPreviewActionButtons.tsx index 170ce9f49..3eb341c58 100644 --- a/src/components/ChannelPreview/ChannelPreviewActionButtons.tsx +++ b/src/components/ChannelPreview/ChannelPreviewActionButtons.tsx @@ -4,10 +4,16 @@ import type { Channel } from 'stream-chat'; import { useChannelMembershipState } from '../ChannelList'; import { useTranslationContext } from '../../context'; import { Button } from '../Button'; -import { IconArchive, IconMute, IconPin } from '../Icons'; +import { IconArchive, IconDotGrid1x3Horizontal, IconMute, IconPin } from '../Icons'; import clsx from 'clsx'; import { useIsChannelMuted } from './hooks/useIsChannelMuted'; +import { + ContextMenu, + ContextMenuButton, + useDialogIsOpen, + useDialogOnNearestManager, +} from '../Dialog'; export type ChannelPreviewActionButtonsProps = { channel: Channel; @@ -25,40 +31,33 @@ export function ChannelPreviewActionButtons({ channel.data?.member_count === 2 && channel.id?.startsWith('!members-'); - // const buttonRef = useRef>(null); - // const dialogId = `channel-action-buttons-${channel.id}`; - // const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId }); - // const dialogIsOpen = useDialogIsOpen(dialogId, dialogManager?.id); + const [referenceElement, setReferenceElement] = + React.useState(null); + const dialogId = `channel-action-buttons-${channel.id}`; + const { dialog, dialogManager } = useDialogOnNearestManager({ id: dialogId }); + const dialogIsOpen = useDialogIsOpen(dialogId, dialogManager?.id); return (
{isDirectMessageChannel ? (
); } diff --git a/src/components/ChannelPreview/styling/ChannelPreview.scss b/src/components/ChannelPreview/styling/ChannelPreview.scss index d8d23f1e3..7aef7abb4 100644 --- a/src/components/ChannelPreview/styling/ChannelPreview.scss +++ b/src/components/ChannelPreview/styling/ChannelPreview.scss @@ -3,6 +3,7 @@ padding: var(--spacing-xxs); position: relative; + &:has(.str-chat__channel-preview__action-buttons--active), &:hover { .str-chat__channel-preview__action-buttons { display: flex; @@ -35,6 +36,10 @@ border-bottom: 1px solid var(--border-core-subtle); } +.str-chat__channel-preview__action-buttons-context-menu { + min-width: 150px; +} + .str-chat__channel-preview { display: flex; gap: var(--spacing-md); From c305c7f1aad7fe821b8b7186acd1667a14da3ea4 Mon Sep 17 00:00:00 2001 From: Anton Arnautov Date: Mon, 9 Mar 2026 15:15:26 +0100 Subject: [PATCH 3/3] Post-review adjustments --- src/components/Button/Button.tsx | 3 +- src/components/Button/styling/Button.scss | 13 ++++++++- .../styling/ChannelListHeader.scss | 2 +- .../Search/SearchBar/SearchBar.tsx | 29 ++++++++++--------- src/experimental/Search/styling/Search.scss | 13 ++++----- src/styling/_global-theme-variables.scss | 25 +++++++++++----- 6 files changed, 54 insertions(+), 31 deletions(-) diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 2055ce3e3..13d101131 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -3,7 +3,7 @@ import clsx from 'clsx'; export type ButtonVariant = 'primary' | 'secondary' | 'danger'; export type ButtonAppearance = 'solid' | 'outline' | 'ghost'; -export type ButtonSize = 'lg' | 'md' | 'sm'; +export type ButtonSize = 'lg' | 'md' | 'sm' | 'xs'; export type ButtonProps = ComponentProps<'button'> & { /** Semantic variant: primary, secondary, or danger (maps to destructive in styles). */ @@ -32,6 +32,7 @@ const sizeToClass: Record = { lg: 'str-chat__button--size-lg', md: 'str-chat__button--size-md', sm: 'str-chat__button--size-sm', + xs: 'str-chat__button--size-xs', }; export const Button = forwardRef(function Button( diff --git a/src/components/Button/styling/Button.scss b/src/components/Button/styling/Button.scss index 7b986efc6..35f03b9ff 100644 --- a/src/components/Button/styling/Button.scss +++ b/src/components/Button/styling/Button.scss @@ -12,7 +12,7 @@ align-items: center; justify-content: center; - font: var(--str-chat__body-emphasis-text); + font: var(--str-chat__heading-xs-text); text-transform: capitalize; &.str-chat__button--solid { @@ -144,7 +144,18 @@ } } + &.str-chat__button--size-xs { + padding-block: var(--button-padding-y-xs); + padding-inline: var(--button-padding-x-with-label-xs); + border-radius: var(--button-radius-md); + + &.str-chat__button--circular { + padding-inline: var(--button-padding-x-icon-only-xs); + } + } + &.str-chat__button--circular { + aspect-ratio: 1/1; display: flex; align-items: center; justify-content: center; diff --git a/src/components/ChannelList/styling/ChannelListHeader.scss b/src/components/ChannelList/styling/ChannelListHeader.scss index 34084e3eb..4e099e6a0 100644 --- a/src/components/ChannelList/styling/ChannelListHeader.scss +++ b/src/components/ChannelList/styling/ChannelListHeader.scss @@ -2,7 +2,7 @@ display: flex; align-items: center; padding: var(--spacing-md); - height: var(--str-chat__channel-header-height); + // height: var(--str-chat__channel-header-height); width: 100%; .str-chat__channel-list__header__title { diff --git a/src/experimental/Search/SearchBar/SearchBar.tsx b/src/experimental/Search/SearchBar/SearchBar.tsx index fdc0bb306..6dd140fd2 100644 --- a/src/experimental/Search/SearchBar/SearchBar.tsx +++ b/src/experimental/Search/SearchBar/SearchBar.tsx @@ -5,7 +5,7 @@ import { useSearchContext } from '../SearchContext'; import { useSearchQueriesInProgress } from '../hooks'; import { useTranslationContext } from '../../../context'; import { useStateStore } from '../../../store'; -import { Button, IconCrossSmall, IconMagnifyingGlassSearch } from '../../../components'; +import { Button, IconCircleX, IconMagnifyingGlassSearch } from '../../../components'; import type { SearchControllerState } from 'stream-chat'; @@ -44,13 +44,13 @@ export const SearchBar = () => { return (
{ @@ -71,35 +71,38 @@ export const SearchBar = () => { /> {searchQuery && ( )}
{/* TODO: return button once designs are in */} - {/* {isActive && ( - - )} */} + + )}
); }; diff --git a/src/experimental/Search/styling/Search.scss b/src/experimental/Search/styling/Search.scss index 65cdc15b2..295f4ae39 100644 --- a/src/experimental/Search/styling/Search.scss +++ b/src/experimental/Search/styling/Search.scss @@ -11,7 +11,7 @@ display: flex; gap: var(--spacing-xs); - .str-chat__search-input--wrapper { + .str-chat__search-bar__input-wrapper { display: flex; min-height: 40px; align-items: center; @@ -31,7 +31,7 @@ // outline-offset: 2px; // } - .str-chat__search-input { + .str-chat__search-bar__input { border: none; background: none; width: 100%; @@ -43,11 +43,6 @@ } } - .str-chat__button.str-chat__search-input--clear-button { - aspect-ratio: 1/1; - padding: var(--spacing-xxs); - } - padding-block: var(--spacing-xs); padding-inline: var(--spacing-sm); @@ -56,6 +51,10 @@ height: var(--icon-size-sm); } } + + .str-chat__search-bar__exit-search-button { + flex-shrink: 0; + } } .str-chat__search-source-result-list__footer, diff --git a/src/styling/_global-theme-variables.scss b/src/styling/_global-theme-variables.scss index 3c176cb1b..bda7f9971 100644 --- a/src/styling/_global-theme-variables.scss +++ b/src/styling/_global-theme-variables.scss @@ -37,19 +37,19 @@ var(--typography-font-family-sans), system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica Neue, sans-serif; - --str-chat__metadata-default-text: normal var(--typography-font-weight-regular) + --str-chat__metadata-emphasis-text: normal var(--typography-font-weight-semi-bold) var(--typography-font-size-xs) / var(--typography-line-height-tight) var(--str-chat__font-family); - - --str-chat__metadata-emphasis-text: normal var(--typography-font-weight-semi-bold) + + --str-chat__metadata-default-text: normal var(--typography-font-weight-regular) var(--typography-font-size-xs) / var(--typography-line-height-tight) var(--str-chat__font-family); - --str-chat__caption-default-text: normal var(--typography-font-weight-regular) - var(--typography-font-size-sm) / var(--typography-line-height-normal) + --str-chat__caption-emphasis-text: normal var(--typography-font-weight-semi-bold) + var(--typography-font-size-sm) / var(--typography-line-height-tight) var(--str-chat__font-family); - --str-chat__caption-emphasis-text: normal var(--typography-font-weight-semi-bold) + --str-chat__caption-default-text: normal var(--typography-font-weight-regular) var(--typography-font-size-sm) / var(--typography-line-height-tight) var(--str-chat__font-family); @@ -57,17 +57,26 @@ var(--typography-font-size-md) / var(--typography-line-height-normal) var(--str-chat__font-family); - --str-chat__heading-xs-text: normal var(--typography-font-weight-medium) - var(--typography-font-size-sm) / var(--typography-line-height-tight) + --str-chat__body-default-text: normal var(--typography-font-weight-regular) + var(--typography-font-size-md) / var(--typography-line-height-normal) + var(--str-chat__font-family); + + --str-chat__heading-xs-text: normal var(--typography-font-weight-semi-bold) + var(--typography-font-size-sm) / var(--typography-line-height-normal) var(--str-chat__font-family); --str-chat__heading-sm-text: normal var(--typography-font-weight-semi-bold) var(--typography-font-size-md) / var(--typography-line-height-normal) var(--str-chat__font-family); + --str-chat__heading-md-text: normal var(--typography-font-weight-semi-bold) + var(--typography-font-size-lg) / var(--typography-line-height-relaxed) + var(--str-chat__font-family); + --str-chat__heading-lg-text: normal var(--typography-font-weight-semi-bold) var(--typography-font-size-xl) / var(--typography-line-height-relaxed) var(--str-chat__font-family); + color: var(--text-primary, #1a1b25); // todo: adapt the old text variables to so that they use the new semantic text variables