diff --git a/desktop/src/features/sidebar/ui/AppSidebar.tsx b/desktop/src/features/sidebar/ui/AppSidebar.tsx index a4f3b4d6a5..ba148c606f 100644 --- a/desktop/src/features/sidebar/ui/AppSidebar.tsx +++ b/desktop/src/features/sidebar/ui/AppSidebar.tsx @@ -1,6 +1,4 @@ // biome-ignore format: keep compact to stay within file size limit -import { MessageCirclePlus } from "lucide-react"; - import * as React from "react"; import { FeatureGate } from "@/shared/features"; import { SidebarDndContext } from "@/features/sidebar/ui/SidebarDnd"; @@ -36,8 +34,9 @@ import { SidebarSection } from "@/features/sidebar/ui/SidebarSection"; import { ChannelGroupSection, CustomChannelSection, + SectionActionsMenu, + SectionQuickAction, } from "@/features/sidebar/ui/CustomChannelSection"; -import { ChannelSortDropdown } from "@/features/sidebar/ui/ChannelSortDropdown"; import { CreateChannelDialog } from "@/features/sidebar/ui/CreateChannelDialog"; import { NewDirectMessageDialog } from "@/features/sidebar/ui/NewDirectMessageDialog"; import { SidebarProfileCard } from "@/features/sidebar/ui/SidebarProfileCard"; @@ -47,10 +46,6 @@ import { SidebarLoadingContent, useSidebarLoadingShape, } from "@/features/sidebar/ui/sidebarLoadingSkeleton"; -import { - SECTION_ACTION_VISIBILITY_CLASS, - SECTION_ICON_BUTTON_CLASS, -} from "@/features/sidebar/ui/sidebarSectionStyles"; import { useDeferredModalOpen } from "@/shared/ui/deferredModalOpen"; import { SidebarUpdateCard } from "@/features/settings/SidebarUpdateCard"; import { useUpdaterContext } from "@/features/settings/hooks/UpdaterProvider"; @@ -592,7 +587,6 @@ export function AppSidebar({ <> {starredChannels.length > 0 ? ( unreadChannelIds.has(c.id), )} @@ -600,16 +594,9 @@ export function AppSidebar({ isActiveChannel={selectedView === "channel"} activeWorkingByChannelId={activeWorkingByChannelId} items={starredChannels} - leadingHeaderAction={ - - setSortModeFor("starred", mode) - } - sortMode={sortModeFor("starred")} - testId="channel-sort-trigger-starred" - /> - } + sortMode={sortModeFor("starred")} + onSortModeChange={(mode) => setSortModeFor("starred", mode)} + actionsTestId="section-actions-starred" listTestId="starred-list" onMarkAllRead={() => { for (const channel of starredChannels) { @@ -661,21 +648,9 @@ export function AppSidebar({ assignments={channelAssignments} isFirst={idx === 0} isLast={idx === channelSections.length - 1} - leadingHeaderAction={ - - setSortModeFor( - sectionSortGroupKey(section.id), - mode, - ) - } - sortMode={sortModeFor( - sectionSortGroupKey(section.id), - )} - testId={`channel-sort-trigger-section-${section.id}`} - visibilityClassName="" - /> + sortMode={sortModeFor(sectionSortGroupKey(section.id))} + onSortModeChange={(mode) => + setSortModeFor(sectionSortGroupKey(section.id), mode) } onToggleCollapsed={() => toggleCollapsedSection(section.id) @@ -707,27 +682,23 @@ export function AppSidebar({ /> ))} 0} isCollapsed={collapsedGroups.channels} isActiveChannel={selectedView === "channel"} activeWorkingByChannelId={activeWorkingByChannelId} items={sectionBuckets.unassigned} - leadingHeaderAction={ - - setSortModeFor("channels", mode) - } - sortMode={sortModeFor("channels")} - testId="channel-sort-trigger-channels" - /> + sortMode={sortModeFor("channels")} + onSortModeChange={(mode) => + setSortModeFor("channels", mode) } + actionsTestId="section-actions-channels" listTestId="stream-list" onBrowseClick={onBrowseChannels} onCreateClick={() => openCreateDialog("stream")} + showQuickCreate onMarkAllRead={onMarkAllChannelsRead} onMarkChannelRead={onMarkChannelRead} onMarkChannelUnread={onMarkChannelUnread} @@ -753,22 +724,15 @@ export function AppSidebar({ 0} isCollapsed={collapsedGroups.forums} isActiveChannel={selectedView === "channel"} activeWorkingByChannelId={activeWorkingByChannelId} items={forumChannels} - leadingHeaderAction={ - - setSortModeFor("forums", mode) - } - sortMode={sortModeFor("forums")} - testId="channel-sort-trigger-forums" - /> - } + sortMode={sortModeFor("forums")} + onSortModeChange={(mode) => setSortModeFor("forums", mode)} + actionsTestId="section-actions-forums" listTestId="forum-list" onCreateClick={() => openCreateDialog("forum")} onMarkAllRead={onMarkAllChannelsRead} @@ -788,25 +752,18 @@ export function AppSidebar({ - setSortModeFor("dms", mode)} + setIsNewDmOpen(true)} + testId="section-actions-dms-quick-create" + /> + setIsNewDmOpen(true)} sortMode={sortModeFor("dms")} - testId="channel-sort-trigger-dms" + onSortModeChange={(mode) => setSortModeFor("dms", mode)} /> - } dmParticipantsByChannelId={dmParticipantsByChannelId} diff --git a/desktop/src/features/sidebar/ui/ChannelContextMenu.tsx b/desktop/src/features/sidebar/ui/ChannelContextMenu.tsx new file mode 100644 index 0000000000..b965aed086 --- /dev/null +++ b/desktop/src/features/sidebar/ui/ChannelContextMenu.tsx @@ -0,0 +1,284 @@ +import { + Bell, + BellOff, + Check, + CheckCircle2, + CircleDot, + Copy, + LogOut, + Plus, + Star, + StarOff, +} from "lucide-react"; + +import type { ChannelSection } from "@/features/sidebar/lib/useChannelSections"; +import { + ContextMenuIconSlot, + deferMenuAction, +} from "@/features/sidebar/ui/sidebarMenuHelpers"; +import { StatusEmoji } from "@/features/user-status/ui/StatusEmoji"; +import type { Channel } from "@/shared/api/types"; +import { copyTextToClipboard } from "@/shared/lib/clipboard"; +import { + ContextMenuItem, + ContextMenuSeparator, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, +} from "@/shared/ui/context-menu"; + +function MoveToSectionSubmenu({ + channelId, + sections, + assignments, + onAssignChannel, + onUnassignChannel, + onCreateSectionForChannel, +}: { + channelId: string; + sections: ChannelSection[]; + assignments: Record; + onAssignChannel: (channelId: string, sectionId: string) => void; + onUnassignChannel: (channelId: string) => void; + onCreateSectionForChannel: (channelId: string) => void; +}) { + const currentSectionId = assignments[channelId]; + + return ( + + + + Move to section + + + {sections.map((section) => ( + + deferMenuAction(() => onAssignChannel(channelId, section.id)) + } + > + + {currentSectionId === section.id ? ( + + ) : section.icon ? ( + + ) : null} + + {section.name} + + ))} + {sections.length > 0 ? : null} + + deferMenuAction(() => onCreateSectionForChannel(channelId)) + } + > + + + + New section... + + {currentSectionId ? ( + deferMenuAction(() => onUnassignChannel(channelId))} + > + + Remove from section + + ) : null} + + + ); +} + +/** + * The channel/DM context menu's Copy actions, grouped under a single + * "Copy" submenu (channel name / channel ID). + */ +function CopyChannelSubmenu({ channel }: { channel: Channel }) { + return ( + + + + + + Copy + + + + copyTextToClipboard( + channel.name, + "Channel name copied to clipboard", + ) + } + > + Copy channel name + + + copyTextToClipboard(channel.id, "Channel ID copied to clipboard") + } + > + Copy channel ID + + + + ); +} + +export function ChannelContextMenuItems({ + channel, + hasUnread, + isMuted, + isStarred, + sections, + assignments, + onMarkChannelRead, + onMarkChannelUnread, + onMuteChannel, + onUnmuteChannel, + onStarChannel, + onUnstarChannel, + onAssignChannel, + onUnassignChannel, + onCreateSectionForChannel, + onLeaveChannel, +}: { + channel: Channel; + hasUnread: boolean; + isMuted?: boolean; + isStarred?: boolean; + sections?: ChannelSection[]; + assignments?: Record; + onMarkChannelRead?: ( + channelId: string, + lastMessageAt: string | null | undefined, + ) => void; + onMarkChannelUnread?: (channelId: string) => void; + onMuteChannel?: (channelId: string) => void; + onUnmuteChannel?: (channelId: string) => void; + onStarChannel?: (channelId: string) => void; + onUnstarChannel?: (channelId: string) => void; + onAssignChannel?: (channelId: string, sectionId: string) => void; + onUnassignChannel?: (channelId: string) => void; + onCreateSectionForChannel?: (channelId: string) => void; + onLeaveChannel?: (channel: Channel) => void; +}) { + const showStar = Boolean(onStarChannel && onUnstarChannel); + const showReadToggle = hasUnread + ? Boolean(onMarkChannelRead) + : Boolean(onMarkChannelUnread); + const showMuteToggle = Boolean(onMuteChannel && onUnmuteChannel); + const showMove = Boolean( + sections && + assignments && + onAssignChannel && + onUnassignChannel && + onCreateSectionForChannel, + ); + + return ( + <> + + {showMove ? ( + {})} + onUnassignChannel={onUnassignChannel ?? (() => {})} + onCreateSectionForChannel={onCreateSectionForChannel ?? (() => {})} + /> + ) : null} + {showReadToggle ? : null} + {hasUnread && onMarkChannelRead ? ( + + deferMenuAction(() => + onMarkChannelRead(channel.id, channel.lastMessageAt), + ) + } + > + + + + Mark as read + + ) : !hasUnread && onMarkChannelUnread ? ( + + deferMenuAction(() => onMarkChannelUnread(channel.id)) + } + > + + + + Mark unread + + ) : null} + {showMuteToggle || showStar ? : null} + {showMuteToggle ? ( + isMuted ? ( + + deferMenuAction(() => onUnmuteChannel?.(channel.id)) + } + > + + + + Unmute channel + + ) : ( + deferMenuAction(() => onMuteChannel?.(channel.id))} + > + + + + Mute channel + + ) + ) : null} + {showStar ? ( + isStarred ? ( + + deferMenuAction(() => onUnstarChannel?.(channel.id)) + } + > + + + + Unstar channel + + ) : ( + deferMenuAction(() => onStarChannel?.(channel.id))} + > + + + + Star channel + + ) + ) : null} + {onLeaveChannel ? ( + <> + + deferMenuAction(() => onLeaveChannel(channel))} + > + + + + Leave channel + + + ) : null} + + ); +} diff --git a/desktop/src/features/sidebar/ui/ChannelSortDropdown.tsx b/desktop/src/features/sidebar/ui/ChannelSortDropdown.tsx deleted file mode 100644 index 7a76d22d0a..0000000000 --- a/desktop/src/features/sidebar/ui/ChannelSortDropdown.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { ArrowUpDown } from "lucide-react"; - -import type { ChannelSortMode } from "@/features/sidebar/lib/channelSortPreference"; -import { - SECTION_ACTION_VISIBILITY_CLASS, - SECTION_ICON_BUTTON_CLASS, -} from "@/features/sidebar/ui/sidebarSectionStyles"; -import { cn } from "@/shared/lib/cn"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuTrigger, -} from "@/shared/ui/dropdown-menu"; - -const SORT_OPTIONS: { value: ChannelSortMode; label: string }[] = [ - { value: "recent", label: "Recent" }, - { value: "alpha", label: "A–Z" }, -]; - -/** - * Section-header dropdown for a single sidebar grouping's sort preference. - * Every grouping (Starred, each custom section, Channels, Forums, DMs) - * carries its own control and saved mode; grouping boundaries are untouched. - */ -export function ChannelSortDropdown({ - groupLabel, - sortMode, - onSortModeChange, - testId, - visibilityClassName = SECTION_ACTION_VISIBILITY_CLASS, -}: { - groupLabel: string; - sortMode: ChannelSortMode; - onSortModeChange: (mode: ChannelSortMode) => void; - testId?: string; - visibilityClassName?: string; -}) { - const activeLabel = - SORT_OPTIONS.find((option) => option.value === sortMode)?.label ?? "A–Z"; - const ariaLabel = `Sort ${groupLabel}: ${activeLabel}`; - - return ( - - - - - - onSortModeChange(value as ChannelSortMode)} - value={sortMode} - > - {SORT_OPTIONS.map((option) => ( - - {option.label} - - ))} - - - - ); -} diff --git a/desktop/src/features/sidebar/ui/CustomChannelSection.tsx b/desktop/src/features/sidebar/ui/CustomChannelSection.tsx index d0e4874055..2021f7eca1 100644 --- a/desktop/src/features/sidebar/ui/CustomChannelSection.tsx +++ b/desktop/src/features/sidebar/ui/CustomChannelSection.tsx @@ -1,37 +1,38 @@ import { ArrowDown, ArrowUp, - Bell, - BellOff, - Check, + ArrowUpDown, CheckCheck, - CheckCircle2, ChevronDown, - CircleDot, - Clipboard, - Copy, - GripVertical, - LogOut, + EllipsisVertical, Pencil, Plus, - Star, - StarOff, Trash2, } from "lucide-react"; +import { useRef } from "react"; import type * as React from "react"; -import { copyTextToClipboard } from "@/shared/lib/clipboard"; +import type { ChannelSortMode } from "@/features/sidebar/lib/channelSortPreference"; import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuSeparator, - ContextMenuSub, - ContextMenuSubContent, - ContextMenuSubTrigger, ContextMenuTrigger, } from "@/shared/ui/context-menu"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuTrigger, +} from "@/shared/ui/dropdown-menu"; import { SidebarGroup, SidebarGroupContent, @@ -40,6 +41,8 @@ import { SidebarMenuItem, } from "@/shared/ui/sidebar"; import { ChannelMenuButton } from "@/features/sidebar/ui/SidebarSection"; +import { ChannelContextMenuItems } from "@/features/sidebar/ui/ChannelContextMenu"; +import { deferMenuAction } from "@/features/sidebar/ui/sidebarMenuHelpers"; import { DraggableChannelRow, DroppableSectionBody, @@ -64,262 +67,264 @@ const SECTION_LABEL_CHEVRON_CLASS = const SECTION_LABEL_CHEVRON_ICON_CLASS = "absolute left-1/2 top-1/2 size-2.5 -translate-x-1/2 -translate-y-1/2"; -function MoveToSectionSubmenu({ - channelId, - sections, - assignments, - onAssignChannel, - onUnassignChannel, - onCreateSectionForChannel, -}: { - channelId: string; - sections: ChannelSection[]; - assignments: Record; - onAssignChannel: (channelId: string, sectionId: string) => void; - onUnassignChannel: (channelId: string) => void; - onCreateSectionForChannel: (channelId: string) => void; -}) { - const currentSectionId = assignments[channelId]; +const SORT_OPTIONS: { value: ChannelSortMode; label: string }[] = [ + { value: "recent", label: "Recent" }, + { value: "alpha", label: "A–Z" }, +]; - return ( - - Move to section - - {sections.map((section) => ( - onAssignChannel(channelId, section.id)} - > - {currentSectionId === section.id ? ( - - ) : section.icon ? ( - - ) : ( - - )} - {section.name} - - ))} - {sections.length > 0 ? : null} - onCreateSectionForChannel(channelId)}> - - New section... - - {currentSectionId ? ( - onUnassignChannel(channelId)}> - Remove from section - - ) : null} - - - ); -} - -export function ChannelContextMenuItems({ - channel, - hasUnread, - isMuted, - isStarred, - sections, - assignments, - onMarkChannelRead, - onMarkChannelUnread, - onMuteChannel, - onUnmuteChannel, - onStarChannel, - onUnstarChannel, - onAssignChannel, - onUnassignChannel, - onCreateSectionForChannel, - onLeaveChannel, +/** + * A single always-visible "+" quick action shown at the right edge of a + * section header, to the left of the ⋮ menu. Used for the most common + * per-section create action (New channel, New message) while every other + * action stays folded into {@link SectionActionsMenu}. + */ +export function SectionQuickAction({ + label, + onClick, + testId, + icon: Icon = Plus, + visibilityClassName = SECTION_ACTION_VISIBILITY_CLASS, }: { - channel: Channel; - hasUnread: boolean; - isMuted?: boolean; - isStarred?: boolean; - sections?: ChannelSection[]; - assignments?: Record; - onMarkChannelRead?: ( - channelId: string, - lastMessageAt: string | null | undefined, - ) => void; - onMarkChannelUnread?: (channelId: string) => void; - onMuteChannel?: (channelId: string) => void; - onUnmuteChannel?: (channelId: string) => void; - onStarChannel?: (channelId: string) => void; - onUnstarChannel?: (channelId: string) => void; - onAssignChannel?: (channelId: string, sectionId: string) => void; - onUnassignChannel?: (channelId: string) => void; - onCreateSectionForChannel?: (channelId: string) => void; - onLeaveChannel?: (channel: Channel) => void; + label: string; + onClick: () => void; + testId?: string; + icon?: typeof Plus; + visibilityClassName?: string; }) { - const showStar = Boolean(onStarChannel && onUnstarChannel); - const showReadToggle = hasUnread - ? Boolean(onMarkChannelRead) - : Boolean(onMarkChannelUnread); return ( - <> - {showStar ? ( - isStarred ? ( - onUnstarChannel?.(channel.id)}> - - Unstar channel - - ) : ( - onStarChannel?.(channel.id)}> - - Star channel - - ) - ) : null} - {showStar && showReadToggle ? : null} - {hasUnread && onMarkChannelRead ? ( - onMarkChannelRead(channel.id, channel.lastMessageAt)} - > - - Mark as read - - ) : !hasUnread && onMarkChannelUnread ? ( - onMarkChannelUnread(channel.id)}> - - Mark unread - - ) : null} - {onMuteChannel && onUnmuteChannel ? ( - <> - - {isMuted ? ( - onUnmuteChannel(channel.id)}> - - Unmute channel - - ) : ( - onMuteChannel(channel.id)}> - - Mute channel - - )} - - ) : null} - {sections && - assignments && - onAssignChannel && - onUnassignChannel && - onCreateSectionForChannel ? ( - <> - - - - ) : null} - - - copyTextToClipboard(channel.name, "Channel name copied to clipboard") - } - > - - Copy channel name - - - copyTextToClipboard(channel.id, "Channel ID copied to clipboard") - } - > - - Copy channel ID - - {onLeaveChannel ? ( - <> - - onLeaveChannel(channel)} - > - - Leave channel - - - ) : null} - + ); } -function SectionHeaderActions({ - browseAriaLabel, - createAriaLabel, +/** + * The single "more actions" menu shown at the right edge of every sidebar + * section header (Starred, Channels, Forums, Direct messages, and each custom + * section). Items render only when their handler is provided; a Sort radio + * group is appended whenever a sort preference is supplied. + */ +export function SectionActionsMenu({ + sectionLabel, + testId, + visibilityClassName = SECTION_ACTION_VISIBILITY_CLASS, hasUnread, - leadingAction, - onBrowseClick, - onCreateClick, onMarkAllRead, + onBrowse, + browseLabel, + onCreate, + createLabel, + onNewMessage, + newMessageLabel, + onRenameSection, + onMoveSectionUp, + onMoveSectionDown, + onDeleteSection, + isFirstSection, + isLastSection, + sortMode, + onSortModeChange, }: { - browseAriaLabel?: string; - createAriaLabel: string; + sectionLabel: string; + testId?: string; + visibilityClassName?: string; hasUnread?: boolean; - leadingAction?: React.ReactNode; - onBrowseClick?: () => void; - onCreateClick?: () => void; onMarkAllRead?: () => void; + onBrowse?: () => void; + browseLabel?: string; + onCreate?: () => void; + createLabel?: string; + onNewMessage?: () => void; + newMessageLabel?: string; + onRenameSection?: () => void; + onMoveSectionUp?: () => void; + onMoveSectionDown?: () => void; + onDeleteSection?: () => void; + isFirstSection?: boolean; + isLastSection?: boolean; + sortMode?: ChannelSortMode; + onSortModeChange?: (mode: ChannelSortMode) => void; }) { + const triggerRef = useRef(null); + const showSectionManagement = Boolean(onRenameSection || onDeleteSection); + const showSort = Boolean(sortMode && onSortModeChange); + return ( -
- {leadingAction} - {hasUnread && onMarkAllRead ? ( + + - ) : null} - {onBrowseClick ? ( - - ) : null} - {onCreateClick ? ( + + { + event.preventDefault(); + triggerRef.current?.blur(); + }} + > + {hasUnread && onMarkAllRead ? ( + deferMenuAction(onMarkAllRead)}> + + Mark all as read + + ) : null} + {onNewMessage ? ( + deferMenuAction(onNewMessage)}> + + {newMessageLabel ?? "New message"} + + ) : null} + {onBrowse ? ( + deferMenuAction(onBrowse)}> + + {browseLabel ?? "Browse channels"} + + ) : null} + {onCreate ? ( + deferMenuAction(onCreate)}> + + {createLabel ?? "Create channel"} + + ) : null} + {showSectionManagement ? ( + <> + {onRenameSection ? ( + deferMenuAction(onRenameSection)} + > + + Rename section + + ) : null} + {onMoveSectionUp ? ( + deferMenuAction(onMoveSectionUp)} + > + + Move up + + ) : null} + {onMoveSectionDown ? ( + deferMenuAction(onMoveSectionDown)} + > + + Move down + + ) : null} + + ) : null} + {showSort ? ( + <> + + + + + Sort + + + + onSortModeChange?.(value as ChannelSortMode) + } + value={sortMode} + > + {SORT_OPTIONS.map((option) => ( + + {option.label} + + ))} + + + + + ) : null} + {onDeleteSection ? ( + <> + + deferMenuAction(onDeleteSection)} + > + + Delete section + + + ) : null} + + + ); +} + +function ChannelSectionHeader({ + contentId, + isCollapsed, + onToggleCollapsed, + title, + actions, +}: { + contentId: string; + isCollapsed: boolean; + onToggleCollapsed: () => void; + title: string; + actions: React.ReactNode; +}) { + return ( +
+ - ) : null} + +
+ {actions} +
); } export function ChannelGroupSection({ - browseAriaLabel, - createAriaLabel, + browseLabel, + createLabel, draggable, groupClassName, hasUnread, @@ -327,16 +332,19 @@ export function ChannelGroupSection({ isActiveChannel, activeWorkingByChannelId, items, - leadingHeaderAction, listTestId, onBrowseClick, onCreateClick, + showQuickCreate, onMarkAllRead, onMarkChannelRead, onMarkChannelUnread, onSelectChannel, onToggleCollapsed, selectedChannelId, + sortMode, + onSortModeChange, + actionsTestId, title, unreadChannelCounts, unreadChannelIds, @@ -353,18 +361,18 @@ export function ChannelGroupSection({ onUnstarChannel, onLeaveChannel, }: { - browseAriaLabel?: string; - createAriaLabel: string; + browseLabel?: string; + createLabel?: string; draggable?: boolean; groupClassName?: string; isCollapsed: boolean; isActiveChannel: boolean; activeWorkingByChannelId?: ReadonlyMap; items: Channel[]; - leadingHeaderAction?: React.ReactNode; listTestId: string; onBrowseClick?: () => void; onCreateClick?: () => void; + showQuickCreate?: boolean; onMarkChannelRead: ( channelId: string, lastMessageAt: string | null | undefined, @@ -373,6 +381,9 @@ export function ChannelGroupSection({ onSelectChannel: (channelId: string) => void; onToggleCollapsed: () => void; selectedChannelId: string | null; + sortMode?: ChannelSortMode; + onSortModeChange?: (mode: ChannelSortMode) => void; + actionsTestId?: string; title: string; unreadChannelCounts: ReadonlyMap; unreadChannelIds: ReadonlySet; @@ -462,36 +473,37 @@ export function ChannelGroupSection({ -
- - - - -
+ ) : null} + + + } + /> {!isCollapsed ? ( {channelList} ) : null} @@ -519,7 +531,8 @@ export function CustomChannelSection({ assignments, isFirst, isLast, - leadingHeaderAction, + sortMode, + onSortModeChange, onToggleCollapsed, onSelectChannel, onMarkChannelRead, @@ -553,7 +566,8 @@ export function CustomChannelSection({ assignments: Record; isFirst: boolean; isLast: boolean; - leadingHeaderAction?: React.ReactNode; + sortMode?: ChannelSortMode; + onSortModeChange?: (mode: ChannelSortMode) => void; onToggleCollapsed: () => void; onSelectChannel: (channelId: string) => void; onMarkChannelRead: ( @@ -603,13 +617,6 @@ export function CustomChannelSection({ onClick={onToggleCollapsed} type="button" > -