mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 00:11:37 -06:00
fix(client): keep the sidebar dock inside a narrow sidebar
The dock's six 32px controls need 13.25rem, but the sidebar can be dragged down to 224px, so the language and settings buttons spilled past its edge. Below that width the buttons now shrink to 28px with a 2px gap. The spacer that added a stray gap is replaced by a right-aligned group; the rail and phone sheet are unchanged. Closes #1873
This commit is contained in:
@@ -42,11 +42,16 @@ const themeStore = useThemeStore()
|
||||
|
||||
const iconRadiusClass = computed(() => (themeStore.radius === 'sharp' ? 'rounded-none' : 'rounded-full'))
|
||||
const controlClass = computed(() => [
|
||||
'h-8 w-8 border border-(--shell-accent-line) text-foreground transition-colors duration-150 hover:bg-(--shell-accent-wash)',
|
||||
'size-(--dock-control) border border-(--shell-accent-line) text-foreground transition-colors duration-150 hover:bg-(--shell-accent-wash)',
|
||||
iconRadiusClass.value,
|
||||
])
|
||||
/** The full set of controls needs 13.25rem, and the sidebar can be dragged narrower than that, so the dock tightens its buttons instead of spilling out. */
|
||||
const rowClass = computed(() => [
|
||||
'flex items-center gap-(--dock-gap) [--dock-control:2rem] [--dock-gap:0.25rem] group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:gap-1.5',
|
||||
!props.isRail && '@max-[13.25rem]:[--dock-control:1.75rem] @max-[13.25rem]:[--dock-gap:0.125rem]',
|
||||
])
|
||||
const avatarClass = computed(() => [
|
||||
'flex h-8 w-8 shrink-0 items-center justify-center overflow-hidden border border-(--shell-accent-line) bg-(--shell-accent-tint) transition-colors duration-150 hover:bg-(--shell-accent-wash) focus:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
||||
'flex size-(--dock-control) shrink-0 items-center justify-center overflow-hidden border border-(--shell-accent-line) bg-(--shell-accent-tint) transition-colors duration-150 hover:bg-(--shell-accent-wash) focus:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
||||
iconRadiusClass.value,
|
||||
])
|
||||
|
||||
@@ -94,96 +99,98 @@ function handleLogout() {
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex shrink-0 items-center gap-1 border-t border-sidebar-border px-2 py-[7px] group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:gap-1.5 group-data-[collapsible=icon]:py-2"
|
||||
class="@container shrink-0 border-t border-sidebar-border px-2 py-[7px] group-data-[collapsible=icon]:py-2"
|
||||
ref="dockRef"
|
||||
data-testid="sidebar-dock"
|
||||
>
|
||||
<DropdownMenu v-if="user">
|
||||
<DropdownMenuTrigger as-child>
|
||||
<button type="button" :class="avatarClass" :aria-label="t('components.appHeader.account')">
|
||||
<UserAvatar :name="user.name" :avatar-url="user.avatarUrl ?? null" size-class="h-full w-full" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent :side="menuSide" align="end" :side-offset="12" :reference="menuReference" class="w-52">
|
||||
<DropdownMenuLabel class="font-normal">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span class="text-[13px] font-medium text-foreground">{{ user.name }}</span>
|
||||
<span class="text-[11px] text-muted-foreground">{{ user.username }}</span>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem @click="navigateToAccount">
|
||||
<User :size="13" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.appHeader.account') }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem v-if="canChangePassword" @click="handleChangePassword">
|
||||
<KeyRound :size="13" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.appHeader.changePassword') }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem class="text-destructive focus:text-destructive" @click="handleLogout">
|
||||
<LogOut :size="13" class="mr-2" />
|
||||
{{ t('components.appHeader.signOut') }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
<span class="min-w-0 flex-1 group-data-[collapsible=icon]:hidden" aria-hidden="true" />
|
||||
|
||||
<NotificationSheet v-if="canAccessNotifications" :icon-radius-class="iconRadiusClass" />
|
||||
|
||||
<Tooltip>
|
||||
<DropdownMenu>
|
||||
<TooltipTrigger as-child>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button
|
||||
data-tour="documentation-link"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
:class="['relative', controlClass]"
|
||||
:aria-label="t('components.appHeader.help')"
|
||||
>
|
||||
<BadgeQuestionMark :size="15" aria-hidden="true" />
|
||||
<span
|
||||
v-if="hasUnseenWhatsNew"
|
||||
class="absolute -right-0.5 -top-0.5 h-2 w-2 rounded-full bg-primary ring-2 ring-sidebar"
|
||||
:aria-label="t('components.appHeader.newReleaseNotes')"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<DropdownMenuContent :side="menuSide" align="end" :side-offset="12" :reference="menuReference" class="w-48">
|
||||
<DropdownMenuItem as-child>
|
||||
<a :href="DOCUMENTATION_URL" target="_blank" rel="noopener noreferrer">
|
||||
<BadgeQuestionMark :size="14" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.appHeader.documentation') }}
|
||||
<ExternalLink :size="12" class="ml-auto text-muted-foreground" />
|
||||
</a>
|
||||
<div :class="rowClass">
|
||||
<DropdownMenu v-if="user">
|
||||
<DropdownMenuTrigger as-child>
|
||||
<button type="button" :class="avatarClass" :aria-label="t('components.appHeader.account')">
|
||||
<UserAvatar :name="user.name" :avatar-url="user.avatarUrl ?? null" size-class="h-full w-full" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent :side="menuSide" align="end" :side-offset="12" :reference="menuReference" class="w-52">
|
||||
<DropdownMenuLabel class="font-normal">
|
||||
<div class="flex flex-col gap-0.5">
|
||||
<span class="text-[13px] font-medium text-foreground">{{ user.name }}</span>
|
||||
<span class="text-[11px] text-muted-foreground">{{ user.username }}</span>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem @click="navigateToAccount">
|
||||
<User :size="13" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.appHeader.account') }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="navigateToWhatsNew">
|
||||
<Sparkles :size="14" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.appHeader.whatsNew') }}
|
||||
<span v-if="hasUnseenWhatsNew" class="ml-auto h-1.5 w-1.5 rounded-full bg-primary" :aria-label="t('components.appHeader.new')" />
|
||||
<DropdownMenuItem v-if="canChangePassword" @click="handleChangePassword">
|
||||
<KeyRound :size="13" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.appHeader.changePassword') }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="handleAbout">
|
||||
<Info :size="14" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.legalNotices.about') }}
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem class="text-destructive focus:text-destructive" @click="handleLogout">
|
||||
<LogOut :size="13" class="mr-2" />
|
||||
{{ t('components.appHeader.signOut') }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<TooltipContent :side="tooltipSide">{{ t('components.appHeader.help') }}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<SidebarAppearanceMenu :control-class="controlClass" :is-mobile="isMobile" :is-rail="isRail" :anchor="dockRef" />
|
||||
<SidebarLanguageMenu :control-class="controlClass" :is-mobile="isMobile" :is-rail="isRail" :anchor="dockRef" />
|
||||
<div class="ms-auto flex items-center gap-(--dock-gap) group-data-[collapsible=icon]:contents">
|
||||
<NotificationSheet v-if="canAccessNotifications" :icon-radius-class="iconRadiusClass" />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" :class="controlClass" :aria-label="t('components.appHeader.settings')" @click="navigateToSettings">
|
||||
<Settings :size="15" aria-hidden="true" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent :side="tooltipSide">{{ t('components.appHeader.settings') }}</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<DropdownMenu>
|
||||
<TooltipTrigger as-child>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button
|
||||
data-tour="documentation-link"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
:class="['relative', controlClass]"
|
||||
:aria-label="t('components.appHeader.help')"
|
||||
>
|
||||
<BadgeQuestionMark :size="15" aria-hidden="true" />
|
||||
<span
|
||||
v-if="hasUnseenWhatsNew"
|
||||
class="absolute -right-0.5 -top-0.5 h-2 w-2 rounded-full bg-primary ring-2 ring-sidebar"
|
||||
:aria-label="t('components.appHeader.newReleaseNotes')"
|
||||
/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
</TooltipTrigger>
|
||||
<DropdownMenuContent :side="menuSide" align="end" :side-offset="12" :reference="menuReference" class="w-48">
|
||||
<DropdownMenuItem as-child>
|
||||
<a :href="DOCUMENTATION_URL" target="_blank" rel="noopener noreferrer">
|
||||
<BadgeQuestionMark :size="14" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.appHeader.documentation') }}
|
||||
<ExternalLink :size="12" class="ml-auto text-muted-foreground" />
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="navigateToWhatsNew">
|
||||
<Sparkles :size="14" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.appHeader.whatsNew') }}
|
||||
<span v-if="hasUnseenWhatsNew" class="ml-auto h-1.5 w-1.5 rounded-full bg-primary" :aria-label="t('components.appHeader.new')" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem @click="handleAbout">
|
||||
<Info :size="14" class="mr-2 text-muted-foreground" />
|
||||
{{ t('components.legalNotices.about') }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<TooltipContent :side="tooltipSide">{{ t('components.appHeader.help') }}</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<SidebarAppearanceMenu :control-class="controlClass" :is-mobile="isMobile" :is-rail="isRail" :anchor="dockRef" />
|
||||
<SidebarLanguageMenu :control-class="controlClass" :is-mobile="isMobile" :is-rail="isRail" :anchor="dockRef" />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" :class="controlClass" :aria-label="t('components.appHeader.settings')" @click="navigateToSettings">
|
||||
<Settings :size="15" aria-hidden="true" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent :side="tooltipSide">{{ t('components.appHeader.settings') }}</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -51,7 +51,7 @@ function handleLoadMore() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="relative h-8 w-8 border border-(--shell-accent-line) text-foreground transition-colors duration-150 hover:bg-(--shell-accent-wash)"
|
||||
class="relative size-(--dock-control,2rem) border border-(--shell-accent-line) text-foreground transition-colors duration-150 hover:bg-(--shell-accent-wash)"
|
||||
:class="iconRadiusClass"
|
||||
:aria-label="t('notifications.title')"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user