Skip to content

Commit a507b17

Browse files
drankouclaude
andauthored
fix: improve sidebar hover line and add tooltip (#464)
Improves the sidebar collapse affordance. The edge rail now shows a "Toggle Sidebar" tooltip with the keyboard shortcut. Consolidates the toggle shortcut on Cmd/Ctrl+B and keeps Ctrl+S binding for users with muscle memory. | Before | After | | ------- | ------- | | <img width="896" height="1454" alt="CleanShot 2026-06-25 at 16 10 59@2x" src="https://github.com/user-attachments/assets/578c980a-47d1-43aa-8a39-937c466c1aef" /> | <img width="906" height="1484" alt="CleanShot 2026-06-25 at 16 10 44@2x" src="https://github.com/user-attachments/assets/4a951673-fc9d-4c65-b6a1-2dc05b50770d" /> | 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 989fe26 commit a507b17

4 files changed

Lines changed: 58 additions & 12 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use client'
2+
3+
import { Kbd } from '@/ui/primitives/kbd'
4+
import { SidebarRail, useSidebar } from '@/ui/primitives/sidebar'
5+
6+
export default function DashboardSidebarRail() {
7+
const { state } = useSidebar()
8+
9+
return (
10+
<SidebarRail
11+
// Remount on toggle so the hover tooltip dismisses instead of lingering
12+
// and re-anchoring when the rail moves out from under a stationary cursor.
13+
key={state}
14+
tooltip={
15+
<>
16+
<span className="text-fg-secondary text-xs">Toggle Sidebar</span>
17+
<Kbd keys={['cmd', 'b']} clientOnlyProps={{ disable: true }} />
18+
</>
19+
}
20+
/>
21+
)
22+
}
Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
1-
import {
2-
Sidebar,
3-
type SidebarProps,
4-
SidebarRail,
5-
} from '@/ui/primitives/sidebar'
1+
import { Sidebar, type SidebarProps } from '@/ui/primitives/sidebar'
62
import DashboardSidebarContent from './content'
73
import DashboardSidebarFooter from './footer'
84
import DashboardSidebarHeader from './header'
5+
import DashboardSidebarRail from './rail'
96

107
export default function DashboardSidebar(props: SidebarProps) {
118
return (
129
<Sidebar collapsible="icon" {...props}>
1310
<DashboardSidebarHeader />
1411
<DashboardSidebarContent />
1512
<DashboardSidebarFooter />
16-
<SidebarRail />
13+
<DashboardSidebarRail />
1714
</Sidebar>
1815
)
1916
}

src/features/dashboard/sidebar/toggle.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export default function DashboardSidebarToggle() {
1515

1616
const isOpen = open || openMobile
1717

18+
// Undocumented fallback so users used to the old Ctrl+S shortcut aren't confused.
1819
useKeydown((event) => {
1920
if (event.key === 's' && event.ctrlKey) {
2021
event.preventDefault()
@@ -47,7 +48,7 @@ export default function DashboardSidebarToggle() {
4748
</motion.span>
4849
)}
4950
</AnimatePresence>
50-
<ShortcutTooltip keys={['ctrl', 's']}>
51+
<ShortcutTooltip keys={['cmd', 'b']}>
5152
<IconButton onClick={toggleSidebar}>
5253
{isOpen ? <CollapseLeftIcon /> : <ExpandRightIcon />}
5354
</IconButton>

src/ui/primitives/sidebar.tsx

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ function Sidebar({
249249
side === 'left'
250250
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
251251
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
252+
// Hover line: 2px pseudo-element overlaying the border edge (no layout shift).
253+
'after:pointer-events-none after:absolute after:inset-y-0 after:z-10 after:w-0.5 after:bg-stroke after:opacity-0 after:transition-opacity after:duration-150 after:content-[""]',
254+
'group-data-[side=left]:after:-right-px group-data-[side=right]:after:-left-px',
255+
'has-[[data-sidebar=rail]:hover]:after:opacity-100',
252256
// Adjust the padding for floating and inset variants.
253257
variant === 'floating' || variant === 'inset'
254258
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
@@ -299,29 +303,51 @@ function SidebarTrigger({
299303
)
300304
}
301305

302-
function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
306+
function SidebarRail({
307+
className,
308+
tooltip,
309+
...props
310+
}: React.ComponentProps<'button'> & { tooltip?: React.ReactNode }) {
303311
const { toggleSidebar } = useSidebar()
304312

305-
return (
313+
const rail = (
306314
<button
307315
data-sidebar="rail"
308316
data-slot="sidebar-rail"
309317
aria-label="Toggle Sidebar"
310318
tabIndex={-1}
311319
onClick={toggleSidebar}
312-
title="Toggle Sidebar"
313320
className={cn(
314-
'hover:after:bg-stroke absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex',
321+
'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
322+
// Hover line is drawn on the Sidebar container (has-[rail:hover]); the rail
323+
// itself is just the hit area.
315324
'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
316325
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
317-
'hover:group-data-[collapsible=offcanvas]:bg-bg group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
326+
'hover:group-data-[collapsible=offcanvas]:bg-bg group-data-[collapsible=offcanvas]:translate-x-0',
318327
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
319328
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
320329
className
321330
)}
322331
{...props}
323332
/>
324333
)
334+
335+
if (!tooltip) {
336+
return rail
337+
}
338+
339+
return (
340+
<Tooltip delayDuration={300}>
341+
<TooltipTrigger asChild>{rail}</TooltipTrigger>
342+
<TooltipContent
343+
side="right"
344+
align="center"
345+
className="flex items-center gap-2 py-1.5 pr-2 normal-case"
346+
>
347+
{tooltip}
348+
</TooltipContent>
349+
</Tooltip>
350+
)
325351
}
326352

327353
function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {

0 commit comments

Comments
 (0)