diff --git a/examples/vite/src/App.tsx b/examples/vite/src/App.tsx index 215d7d0a1..175ede371 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) => { @@ -67,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; @@ -140,6 +142,7 @@ const App = () => { }, { type: 'public' }, ], + archived: false, }), [userId], ); @@ -204,12 +207,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/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/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/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/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); 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..6dd140fd2 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, IconCircleX, IconMagnifyingGlassSearch } from '../../../components'; import type { SearchControllerState } from 'stream-chat'; @@ -43,13 +44,13 @@ export const SearchBar = () => { return (
-
+ { @@ -69,33 +70,39 @@ export const SearchBar = () => { value={searchQuery} /> {searchQuery && ( - + + )}
- {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 (