ソースを参照

fix(dashboard): Channel switcher not scrollable if list exceeds screen height (#4075)

Luca Killmaier 1 週間 前
コミット
9c1081a139

+ 94 - 86
packages/dashboard/src/lib/components/layout/channel-switcher.tsx

@@ -13,6 +13,7 @@ import {
     DropdownMenuTrigger,
 } from '@/vdb/components/ui/dropdown-menu.js';
 import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/vdb/components/ui/sidebar.js';
+import { ScrollArea } from '@/vdb/components/ui/scroll-area.js';
 import { DEFAULT_CHANNEL_CODE } from '@/vdb/constants.js';
 import { useChannel } from '@/vdb/hooks/use-channel.js';
 import { useLocalFormat } from '@/vdb/hooks/use-local-format.js';
@@ -61,9 +62,9 @@ export function ChannelSwitcher() {
     const availableLanguages = serverConfig?.availableLanguages || [];
     const hasMultipleLanguages = availableLanguages.length > 1;
 
-    // Reorder channels to put the currently selected one first
+    // Currently selected channel is displayed separately so filter it out of the list
     const orderedChannels = displayChannel
-        ? [displayChannel, ...channels.filter(ch => ch.id !== displayChannel.id)]
+        ? channels.filter(ch => ch.id !== displayChannel.id)
         : channels;
 
     // Sort language codes by their formatted names and map to code and label
@@ -79,6 +80,32 @@ export function ChannelSwitcher() {
         }
     }, [activeChannel, contentLanguage]);
 
+    const renderChannel = (channel: typeof channels[number]) => (
+        <div key={channel.code}>
+            <DropdownMenuItem
+                onClick={() => setActiveChannel(channel.id)}
+                className="gap-2 p-2"
+            >
+                <div
+                    className={cn(
+                        'flex size-8 items-center justify-center rounded border',
+                        channel.code === DEFAULT_CHANNEL_CODE ? 'bg-primary' : '',
+                    )}
+                >
+                    <span className="truncate font-semibold text-xs uppercase">
+                        {getChannelInitialsFromCode(channel.code)}
+                    </span>
+                </div>
+                <ChannelCodeLabel code={channel.code} />
+                {channel.id === displayChannel?.id && (
+                    <span className="ms-auto text-xs text-muted-foreground">
+                        <Trans context="current channel">Current</Trans>
+                    </span>
+                )}
+            </DropdownMenuItem>
+        </div>
+    );
+
     return (
         <>
             <SidebarMenu>
@@ -112,99 +139,80 @@ export function ChannelSwitcher() {
                             </SidebarMenuButton>
                         </DropdownMenuTrigger>
                         <DropdownMenuContent
-                            className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
+                            className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg pt-0 pr-0"
                             align="start"
                             side={isMobile ? 'bottom' : 'right'}
                             sideOffset={4}
                         >
-                            <DropdownMenuLabel className="text-muted-foreground text-xs">
-                                <Trans>Channels</Trans>
-                            </DropdownMenuLabel>
-                            {orderedChannels.map((channel, index) => (
-                                <div key={channel.code}>
-                                    <DropdownMenuItem
-                                        onClick={() => setActiveChannel(channel.id)}
-                                        className="gap-2 p-2"
-                                    >
-                                        <div
-                                            className={cn(
-                                                'flex size-8 items-center justify-center rounded border',
-                                                channel.code === DEFAULT_CHANNEL_CODE ? 'bg-primary' : '',
-                                            )}
-                                        >
-                                            <span className="truncate font-semibold text-xs uppercase">
-                                                {getChannelInitialsFromCode(channel.code)}
-                                            </span>
-                                        </div>
-                                        <ChannelCodeLabel code={channel.code} />
-                                        {channel.id === displayChannel?.id && (
-                                            <span className="ms-auto text-xs text-muted-foreground">
-                                                <Trans context="current channel">Current</Trans>
-                                            </span>
-                                        )}
-                                    </DropdownMenuItem>
-                                    {/* Show language sub-menu for the current channel */}
-                                    {channel.id === displayChannel?.id && (
-                                        <DropdownMenuSub>
-                                            <DropdownMenuSubTrigger className="gap-2 p-2 ps-4">
-                                                <Languages className="w-4 h-4" />
-                                                <div className="flex gap-1 ms-2">
-                                                    <span className="text-muted-foreground">Content: </span>
-                                                    {formatLanguageName(contentLanguage)}
-                                                </div>
-                                            </DropdownMenuSubTrigger>
-                                            <DropdownMenuSubContent>
-                                                {sortedLanguages?.map(({ code: languageCode, label }) => (
+                            <ScrollArea className="max-h-[calc(100vh_-_24px)] overflow-y-auto pr-1">
+                                <div className="sticky top-0 pt-1 bg-popover z-10">
+                                    <DropdownMenuLabel className="text-muted-foreground text-xs">
+                                        <Trans>Channels</Trans>
+                                    </DropdownMenuLabel>
+                                    {!!displayChannel && (
+                                        <>
+                                            {renderChannel(displayChannel)}
+                                            {/* Show language sub-menu for the current channel */}
+                                            <DropdownMenuSub>
+                                                <DropdownMenuSubTrigger className="gap-2 p-2 ps-4">
+                                                    <Languages className="w-4 h-4" />
+                                                    <div className="flex gap-1 ms-2">
+                                                        <span className="text-muted-foreground">Content: </span>
+                                                        {formatLanguageName(contentLanguage)}
+                                                    </div>
+                                                </DropdownMenuSubTrigger>
+                                                <DropdownMenuSubContent>
+                                                    {sortedLanguages?.map(({ code: languageCode, label }) => (
+                                                        <DropdownMenuItem
+                                                            key={`${displayChannel.code}-${languageCode}`}
+                                                            onClick={() => setContentLanguage(languageCode)}
+                                                            className={`gap-2 p-2 ${contentLanguage === languageCode ? 'bg-accent' : ''}`}
+                                                        >
+                                                            <div className="flex w-6 h-5 items-center justify-center rounded border">
+                                                                <span className="truncate font-medium text-xs">
+                                                                    {languageCode.toUpperCase()}
+                                                                </span>
+                                                            </div>
+                                                            <span>{label}</span>
+                                                            {contentLanguage === languageCode && (
+                                                                <span className="ms-auto text-xs text-muted-foreground">
+                                                                    <Trans context="active language">
+                                                                        Active
+                                                                    </Trans>
+                                                                </span>
+                                                            )}
+                                                        </DropdownMenuItem>
+                                                    ))}
+                                                    <DropdownMenuSeparator />
                                                     <DropdownMenuItem
-                                                        key={`${channel.code}-${languageCode}`}
-                                                        onClick={() => setContentLanguage(languageCode)}
-                                                        className={`gap-2 p-2 ${contentLanguage === languageCode ? 'bg-accent' : ''}`}
+                                                        onClick={() => setShowManageLanguagesDialog(true)}
+                                                        className="gap-2 p-2"
                                                     >
-                                                        <div className="flex w-6 h-5 items-center justify-center rounded border">
-                                                            <span className="truncate font-medium text-xs">
-                                                                {languageCode.toUpperCase()}
-                                                            </span>
-                                                        </div>
-                                                        <span>{label}</span>
-                                                        {contentLanguage === languageCode && (
-                                                            <span className="ms-auto text-xs text-muted-foreground">
-                                                                <Trans context="active language">
-                                                                    Active
-                                                                </Trans>
-                                                            </span>
-                                                        )}
+                                                        <Languages className="w-4 h-4" />
+                                                        <span>
+                                                            <Trans>Manage Languages</Trans>
+                                                        </span>
                                                     </DropdownMenuItem>
-                                                ))}
-                                                <DropdownMenuSeparator />
-                                                <DropdownMenuItem
-                                                    onClick={() => setShowManageLanguagesDialog(true)}
-                                                    className="gap-2 p-2"
-                                                >
-                                                    <Languages className="w-4 h-4" />
-                                                    <span>
-                                                        <Trans>Manage Languages</Trans>
-                                                    </span>
-                                                </DropdownMenuItem>
-                                            </DropdownMenuSubContent>
-                                        </DropdownMenuSub>
+                                                </DropdownMenuSubContent>
+                                            </DropdownMenuSub>
+                                            {/* Add separator after the current channel group */}
+                                            {orderedChannels.length > 0 && <DropdownMenuSeparator />}
+                                        </>
                                     )}
-                                    {/* Add separator after the current channel group */}
-                                    {channel.id === displayChannel?.id &&
-                                        index === 0 &&
-                                        orderedChannels.length > 1 && <DropdownMenuSeparator />}
                                 </div>
-                            ))}
-                            <DropdownMenuSeparator />
-                            <DropdownMenuItem className="gap-2 p-2 cursor-pointer" asChild>
-                                <Link to={'/channels/new'}>
-                                    <div className="bg-background flex size-6 items-center justify-center rounded-md border">
-                                        <Plus className="size-4" />
-                                    </div>
-                                    <div className="text-muted-foreground font-medium">
-                                        <Trans>Add channel</Trans>
-                                    </div>
-                                </Link>
-                            </DropdownMenuItem>
+                                {orderedChannels.map(renderChannel)}
+                                <DropdownMenuSeparator />
+                                <DropdownMenuItem className="gap-2 p-2 cursor-pointer" asChild>
+                                    <Link to={'/channels/new'}>
+                                        <div className="bg-background flex size-6 items-center justify-center rounded-md border">
+                                            <Plus className="size-4" />
+                                        </div>
+                                        <div className="text-muted-foreground font-medium">
+                                            <Trans>Add channel</Trans>
+                                        </div>
+                                    </Link>
+                                </DropdownMenuItem>
+                            </ScrollArea>
                         </DropdownMenuContent>
                     </DropdownMenu>
                 </SidebarMenuItem>