@modern-admin/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/accordion.d.ts +7 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.jsx +19 -0
- package/dist/components/accordion.jsx.map +1 -0
- package/dist/components/alert-dialog.d.ts +22 -0
- package/dist/components/alert-dialog.d.ts.map +1 -0
- package/dist/components/alert-dialog.jsx +27 -0
- package/dist/components/alert-dialog.jsx.map +1 -0
- package/dist/components/audit-timeline.d.ts +24 -0
- package/dist/components/audit-timeline.d.ts.map +1 -0
- package/dist/components/audit-timeline.jsx +60 -0
- package/dist/components/audit-timeline.jsx.map +1 -0
- package/dist/components/avatar.d.ts +6 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.jsx +10 -0
- package/dist/components/avatar.jsx.map +1 -0
- package/dist/components/badge.d.ts +10 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.jsx +19 -0
- package/dist/components/badge.jsx.map +1 -0
- package/dist/components/breadcrumb.d.ts +17 -0
- package/dist/components/breadcrumb.d.ts.map +1 -0
- package/dist/components/breadcrumb.jsx +27 -0
- package/dist/components/breadcrumb.jsx.map +1 -0
- package/dist/components/button.d.ts +12 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.jsx +37 -0
- package/dist/components/button.jsx.map +1 -0
- package/dist/components/calendar.d.ts +9 -0
- package/dist/components/calendar.d.ts.map +1 -0
- package/dist/components/calendar.jsx +102 -0
- package/dist/components/calendar.jsx.map +1 -0
- package/dist/components/card.d.ts +8 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.jsx +18 -0
- package/dist/components/card.jsx.map +1 -0
- package/dist/components/chart.d.ts +97 -0
- package/dist/components/chart.d.ts.map +1 -0
- package/dist/components/chart.jsx +233 -0
- package/dist/components/chart.jsx.map +1 -0
- package/dist/components/checkbox.d.ts +4 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.jsx +11 -0
- package/dist/components/checkbox.jsx.map +1 -0
- package/dist/components/combobox.d.ts +46 -0
- package/dist/components/combobox.d.ts.map +1 -0
- package/dist/components/combobox.jsx +145 -0
- package/dist/components/combobox.jsx.map +1 -0
- package/dist/components/command.d.ts +80 -0
- package/dist/components/command.d.ts.map +1 -0
- package/dist/components/command.jsx +32 -0
- package/dist/components/command.jsx.map +1 -0
- package/dist/components/date-picker.d.ts +24 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.jsx +149 -0
- package/dist/components/date-picker.jsx.map +1 -0
- package/dist/components/date-range-input.d.ts +22 -0
- package/dist/components/date-range-input.d.ts.map +1 -0
- package/dist/components/date-range-input.jsx +202 -0
- package/dist/components/date-range-input.jsx.map +1 -0
- package/dist/components/dialog.d.ts +19 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.jsx +30 -0
- package/dist/components/dialog.jsx.map +1 -0
- package/dist/components/diff-view.d.ts +24 -0
- package/dist/components/diff-view.d.ts.map +1 -0
- package/dist/components/diff-view.jsx +69 -0
- package/dist/components/diff-view.jsx.map +1 -0
- package/dist/components/dropdown-menu.d.ts +27 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu.jsx +48 -0
- package/dist/components/dropdown-menu.jsx.map +1 -0
- package/dist/components/empty.d.ts +15 -0
- package/dist/components/empty.d.ts.map +1 -0
- package/dist/components/empty.jsx +27 -0
- package/dist/components/empty.jsx.map +1 -0
- package/dist/components/field.d.ts +23 -0
- package/dist/components/field.d.ts.map +1 -0
- package/dist/components/field.jsx +60 -0
- package/dist/components/field.jsx.map +1 -0
- package/dist/components/file-input.d.ts +50 -0
- package/dist/components/file-input.d.ts.map +1 -0
- package/dist/components/file-input.jsx +104 -0
- package/dist/components/file-input.jsx.map +1 -0
- package/dist/components/form.d.ts +20 -0
- package/dist/components/form.d.ts.map +1 -0
- package/dist/components/form.jsx +66 -0
- package/dist/components/form.jsx.map +1 -0
- package/dist/components/info-tooltip.d.ts +11 -0
- package/dist/components/info-tooltip.d.ts.map +1 -0
- package/dist/components/info-tooltip.jsx +17 -0
- package/dist/components/info-tooltip.jsx.map +1 -0
- package/dist/components/input.d.ts +13 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.jsx +19 -0
- package/dist/components/input.jsx.map +1 -0
- package/dist/components/json-editor.d.ts +23 -0
- package/dist/components/json-editor.d.ts.map +1 -0
- package/dist/components/json-editor.jsx +143 -0
- package/dist/components/json-editor.jsx.map +1 -0
- package/dist/components/kbd.d.ts +15 -0
- package/dist/components/kbd.d.ts.map +1 -0
- package/dist/components/kbd.jsx +23 -0
- package/dist/components/kbd.jsx.map +1 -0
- package/dist/components/key-value-editor.d.ts +92 -0
- package/dist/components/key-value-editor.d.ts.map +1 -0
- package/dist/components/key-value-editor.jsx +187 -0
- package/dist/components/key-value-editor.jsx.map +1 -0
- package/dist/components/keyboard-shortcuts-help.d.ts +17 -0
- package/dist/components/keyboard-shortcuts-help.d.ts.map +1 -0
- package/dist/components/keyboard-shortcuts-help.jsx +97 -0
- package/dist/components/keyboard-shortcuts-help.jsx.map +1 -0
- package/dist/components/label.d.ts +5 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.jsx +8 -0
- package/dist/components/label.jsx.map +1 -0
- package/dist/components/media-preview.d.ts +30 -0
- package/dist/components/media-preview.d.ts.map +1 -0
- package/dist/components/media-preview.jsx +189 -0
- package/dist/components/media-preview.jsx.map +1 -0
- package/dist/components/multi-file-input.d.ts +76 -0
- package/dist/components/multi-file-input.d.ts.map +1 -0
- package/dist/components/multi-file-input.jsx +131 -0
- package/dist/components/multi-file-input.jsx.map +1 -0
- package/dist/components/password-input.d.ts +10 -0
- package/dist/components/password-input.d.ts.map +1 -0
- package/dist/components/password-input.jsx +18 -0
- package/dist/components/password-input.jsx.map +1 -0
- package/dist/components/popover.d.ts +7 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.jsx +11 -0
- package/dist/components/popover.jsx.map +1 -0
- package/dist/components/revision-timeline.d.ts +30 -0
- package/dist/components/revision-timeline.d.ts.map +1 -0
- package/dist/components/revision-timeline.jsx +42 -0
- package/dist/components/revision-timeline.jsx.map +1 -0
- package/dist/components/richtext-editor.d.ts +43 -0
- package/dist/components/richtext-editor.d.ts.map +1 -0
- package/dist/components/richtext-editor.jsx +319 -0
- package/dist/components/richtext-editor.jsx.map +1 -0
- package/dist/components/richtext-mode.d.ts +23 -0
- package/dist/components/richtext-mode.d.ts.map +1 -0
- package/dist/components/richtext-mode.js +36 -0
- package/dist/components/richtext-mode.js.map +1 -0
- package/dist/components/richtext-render.d.ts +8 -0
- package/dist/components/richtext-render.d.ts.map +1 -0
- package/dist/components/richtext-render.jsx +33 -0
- package/dist/components/richtext-render.jsx.map +1 -0
- package/dist/components/richtext-sync.d.ts +37 -0
- package/dist/components/richtext-sync.d.ts.map +1 -0
- package/dist/components/richtext-sync.js +46 -0
- package/dist/components/richtext-sync.js.map +1 -0
- package/dist/components/scroll-area.d.ts +5 -0
- package/dist/components/scroll-area.d.ts.map +1 -0
- package/dist/components/scroll-area.jsx +16 -0
- package/dist/components/scroll-area.jsx.map +1 -0
- package/dist/components/select.d.ts +36 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.jsx +87 -0
- package/dist/components/select.jsx.map +1 -0
- package/dist/components/separator.d.ts +4 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/separator.jsx +6 -0
- package/dist/components/separator.jsx.map +1 -0
- package/dist/components/sheet.d.ts +29 -0
- package/dist/components/sheet.d.ts.map +1 -0
- package/dist/components/sheet.jsx +44 -0
- package/dist/components/sheet.jsx.map +1 -0
- package/dist/components/sidebar.d.ts +70 -0
- package/dist/components/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar.jsx +245 -0
- package/dist/components/sidebar.jsx.map +1 -0
- package/dist/components/skeleton.d.ts +3 -0
- package/dist/components/skeleton.d.ts.map +1 -0
- package/dist/components/skeleton.jsx +6 -0
- package/dist/components/skeleton.jsx.map +1 -0
- package/dist/components/sonner.d.ts +6 -0
- package/dist/components/sonner.d.ts.map +1 -0
- package/dist/components/sonner.jsx +29 -0
- package/dist/components/sonner.jsx.map +1 -0
- package/dist/components/switch.d.ts +4 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.jsx +8 -0
- package/dist/components/switch.jsx.map +1 -0
- package/dist/components/table.d.ts +10 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/table.jsx +21 -0
- package/dist/components/table.jsx.map +1 -0
- package/dist/components/tabs.d.ts +7 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.jsx +14 -0
- package/dist/components/tabs.jsx.map +1 -0
- package/dist/components/textarea.d.ts +4 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.jsx +5 -0
- package/dist/components/textarea.jsx.map +1 -0
- package/dist/components/tooltip.d.ts +7 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.jsx +11 -0
- package/dist/components/tooltip.jsx.map +1 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +72 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/theme.d.ts +11 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +44 -0
- package/dist/lib/theme.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/styles.css +242 -0
- package/package.json +85 -0
- package/src/components/accordion.tsx +48 -0
- package/src/components/alert-dialog.tsx +113 -0
- package/src/components/audit-timeline.tsx +102 -0
- package/src/components/avatar.tsx +42 -0
- package/src/components/badge.tsx +34 -0
- package/src/components/breadcrumb.tsx +99 -0
- package/src/components/button.tsx +58 -0
- package/src/components/calendar.tsx +176 -0
- package/src/components/card.tsx +60 -0
- package/src/components/chart.tsx +558 -0
- package/src/components/checkbox.tsx +23 -0
- package/src/components/combobox.tsx +264 -0
- package/src/components/command.tsx +120 -0
- package/src/components/date-picker.tsx +221 -0
- package/src/components/date-range-input.tsx +295 -0
- package/src/components/dialog.tsx +94 -0
- package/src/components/diff-view.tsx +182 -0
- package/src/components/dropdown-menu.tsx +165 -0
- package/src/components/empty.tsx +100 -0
- package/src/components/field.tsx +168 -0
- package/src/components/file-input.tsx +233 -0
- package/src/components/form.tsx +152 -0
- package/src/components/info-tooltip.tsx +40 -0
- package/src/components/input.tsx +55 -0
- package/src/components/json-editor.tsx +210 -0
- package/src/components/kbd.tsx +35 -0
- package/src/components/key-value-editor.tsx +423 -0
- package/src/components/keyboard-shortcuts-help.tsx +136 -0
- package/src/components/label.tsx +16 -0
- package/src/components/media-preview.tsx +278 -0
- package/src/components/multi-file-input.tsx +315 -0
- package/src/components/password-input.tsx +50 -0
- package/src/components/popover.tsx +26 -0
- package/src/components/revision-timeline.tsx +93 -0
- package/src/components/richtext-editor.tsx +624 -0
- package/src/components/richtext-mode.ts +39 -0
- package/src/components/richtext-render.tsx +51 -0
- package/src/components/richtext-sync.ts +57 -0
- package/src/components/scroll-area.tsx +41 -0
- package/src/components/select.tsx +200 -0
- package/src/components/separator.tsx +21 -0
- package/src/components/sheet.tsx +109 -0
- package/src/components/sidebar.tsx +660 -0
- package/src/components/skeleton.tsx +9 -0
- package/src/components/sonner.tsx +45 -0
- package/src/components/switch.tsx +24 -0
- package/src/components/table.tsx +93 -0
- package/src/components/tabs.tsx +57 -0
- package/src/components/textarea.tsx +18 -0
- package/src/components/tooltip.tsx +25 -0
- package/src/index.ts +342 -0
- package/src/lib/theme.ts +45 -0
- package/src/lib/utils.ts +6 -0
- package/src/styles.css +242 -0
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
// shadcn-style Sidebar — collapsible side navigation built on Sheet (mobile)
|
|
2
|
+
// + a CSS-variable-driven layout (desktop). Composed of Provider/Sidebar/
|
|
3
|
+
// Inset + Group/Menu primitives. Faithful port of the canonical recipe at
|
|
4
|
+
// https://ui.shadcn.com/docs/components/sidebar with two project-specific
|
|
5
|
+
// tweaks: state is persisted in localStorage instead of cookies (so the
|
|
6
|
+
// component works in non-SSR Vite apps without server plumbing), and the
|
|
7
|
+
// `width-mobile`/`width-icon` CSS variables are exposed via the @theme
|
|
8
|
+
// block in styles.css. All other class names match the upstream component.
|
|
9
|
+
|
|
10
|
+
import * as React from 'react'
|
|
11
|
+
import { Slot } from '@radix-ui/react-slot'
|
|
12
|
+
import { cva, type VariantProps } from 'class-variance-authority'
|
|
13
|
+
import { PanelLeft } from 'lucide-react'
|
|
14
|
+
import { cn } from '../lib/utils.js'
|
|
15
|
+
import { Button } from './button.js'
|
|
16
|
+
import { Input } from './input.js'
|
|
17
|
+
import { Separator } from './separator.js'
|
|
18
|
+
import { Sheet, SheetContent, SheetDescription, SheetTitle } from './sheet.js'
|
|
19
|
+
import { Skeleton } from './skeleton.js'
|
|
20
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip.js'
|
|
21
|
+
|
|
22
|
+
const SIDEBAR_STATE_KEY = 'sidebar:state'
|
|
23
|
+
const SIDEBAR_KEYBOARD_SHORTCUT = 'b'
|
|
24
|
+
const SIDEBAR_WIDTH = '16rem'
|
|
25
|
+
const SIDEBAR_WIDTH_MOBILE = '18rem'
|
|
26
|
+
const SIDEBAR_WIDTH_ICON = '3rem'
|
|
27
|
+
|
|
28
|
+
interface SidebarContextValue {
|
|
29
|
+
state: 'expanded' | 'collapsed'
|
|
30
|
+
open: boolean
|
|
31
|
+
setOpen(open: boolean): void
|
|
32
|
+
openMobile: boolean
|
|
33
|
+
setOpenMobile(open: boolean): void
|
|
34
|
+
isMobile: boolean
|
|
35
|
+
toggleSidebar(): void
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const SidebarContext = React.createContext<SidebarContextValue | null>(null)
|
|
39
|
+
|
|
40
|
+
export function useSidebar(): SidebarContextValue {
|
|
41
|
+
const ctx = React.useContext(SidebarContext)
|
|
42
|
+
if (!ctx) throw new Error('useSidebar must be used within <SidebarProvider>')
|
|
43
|
+
return ctx
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function useIsMobile(breakpoint = 768): boolean {
|
|
47
|
+
const [isMobile, setIsMobile] = React.useState(false)
|
|
48
|
+
React.useEffect(() => {
|
|
49
|
+
const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`)
|
|
50
|
+
const update = (): void => setIsMobile(mql.matches)
|
|
51
|
+
update()
|
|
52
|
+
mql.addEventListener('change', update)
|
|
53
|
+
return () => mql.removeEventListener('change', update)
|
|
54
|
+
}, [breakpoint])
|
|
55
|
+
return isMobile
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SidebarProviderProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
59
|
+
defaultOpen?: boolean
|
|
60
|
+
open?: boolean
|
|
61
|
+
onOpenChange?(open: boolean): void
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const SidebarProvider = React.forwardRef<HTMLDivElement, SidebarProviderProps>(
|
|
65
|
+
(
|
|
66
|
+
{
|
|
67
|
+
defaultOpen = true,
|
|
68
|
+
open: openProp,
|
|
69
|
+
onOpenChange: setOpenProp,
|
|
70
|
+
className,
|
|
71
|
+
style,
|
|
72
|
+
children,
|
|
73
|
+
...props
|
|
74
|
+
},
|
|
75
|
+
ref,
|
|
76
|
+
) => {
|
|
77
|
+
const isMobile = useIsMobile()
|
|
78
|
+
const [openMobile, setOpenMobile] = React.useState(false)
|
|
79
|
+
|
|
80
|
+
const [_open, _setOpen] = React.useState(() => {
|
|
81
|
+
if (typeof localStorage === 'undefined') return defaultOpen
|
|
82
|
+
const v = localStorage.getItem(SIDEBAR_STATE_KEY)
|
|
83
|
+
return v == null ? defaultOpen : v === 'true'
|
|
84
|
+
})
|
|
85
|
+
const open = openProp ?? _open
|
|
86
|
+
const setOpen = React.useCallback(
|
|
87
|
+
(value: boolean | ((v: boolean) => boolean)) => {
|
|
88
|
+
const next = typeof value === 'function' ? value(open) : value
|
|
89
|
+
if (setOpenProp) setOpenProp(next)
|
|
90
|
+
else _setOpen(next)
|
|
91
|
+
if (typeof localStorage !== 'undefined') {
|
|
92
|
+
localStorage.setItem(SIDEBAR_STATE_KEY, String(next))
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
[setOpenProp, open],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
const toggleSidebar = React.useCallback(() => {
|
|
99
|
+
if (isMobile) setOpenMobile((v) => !v)
|
|
100
|
+
else setOpen((v) => !v)
|
|
101
|
+
}, [isMobile, setOpen])
|
|
102
|
+
|
|
103
|
+
React.useEffect(() => {
|
|
104
|
+
const onKey = (e: KeyboardEvent): void => {
|
|
105
|
+
if (
|
|
106
|
+
e.key === SIDEBAR_KEYBOARD_SHORTCUT &&
|
|
107
|
+
(e.metaKey || e.ctrlKey) &&
|
|
108
|
+
!e.shiftKey &&
|
|
109
|
+
!e.altKey
|
|
110
|
+
) {
|
|
111
|
+
e.preventDefault()
|
|
112
|
+
toggleSidebar()
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
window.addEventListener('keydown', onKey)
|
|
116
|
+
return () => window.removeEventListener('keydown', onKey)
|
|
117
|
+
}, [toggleSidebar])
|
|
118
|
+
|
|
119
|
+
const state: 'expanded' | 'collapsed' = open ? 'expanded' : 'collapsed'
|
|
120
|
+
|
|
121
|
+
const value = React.useMemo<SidebarContextValue>(
|
|
122
|
+
() => ({
|
|
123
|
+
state,
|
|
124
|
+
open,
|
|
125
|
+
setOpen,
|
|
126
|
+
openMobile,
|
|
127
|
+
setOpenMobile,
|
|
128
|
+
isMobile,
|
|
129
|
+
toggleSidebar,
|
|
130
|
+
}),
|
|
131
|
+
[state, open, setOpen, openMobile, isMobile, toggleSidebar],
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<SidebarContext.Provider value={value}>
|
|
136
|
+
<TooltipProvider delayDuration={0}>
|
|
137
|
+
<div
|
|
138
|
+
ref={ref}
|
|
139
|
+
data-slot="sidebar-wrapper"
|
|
140
|
+
style={
|
|
141
|
+
{
|
|
142
|
+
'--sidebar-width': SIDEBAR_WIDTH,
|
|
143
|
+
'--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
|
|
144
|
+
...style,
|
|
145
|
+
} as React.CSSProperties
|
|
146
|
+
}
|
|
147
|
+
className={cn(
|
|
148
|
+
'group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-muted',
|
|
149
|
+
className,
|
|
150
|
+
)}
|
|
151
|
+
{...props}
|
|
152
|
+
>
|
|
153
|
+
{children}
|
|
154
|
+
</div>
|
|
155
|
+
</TooltipProvider>
|
|
156
|
+
</SidebarContext.Provider>
|
|
157
|
+
)
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
SidebarProvider.displayName = 'SidebarProvider'
|
|
161
|
+
|
|
162
|
+
export interface SidebarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
163
|
+
side?: 'left' | 'right'
|
|
164
|
+
variant?: 'sidebar' | 'floating' | 'inset'
|
|
165
|
+
collapsible?: 'offcanvas' | 'icon' | 'none'
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const Sidebar = React.forwardRef<HTMLDivElement, SidebarProps>(
|
|
169
|
+
(
|
|
170
|
+
{ side = 'left', variant = 'sidebar', collapsible = 'offcanvas', className, children, ...props },
|
|
171
|
+
ref,
|
|
172
|
+
) => {
|
|
173
|
+
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
|
|
174
|
+
|
|
175
|
+
if (collapsible === 'none') {
|
|
176
|
+
return (
|
|
177
|
+
<div
|
|
178
|
+
ref={ref}
|
|
179
|
+
className={cn(
|
|
180
|
+
'flex h-full w-[var(--sidebar-width)] flex-col bg-card text-foreground',
|
|
181
|
+
className,
|
|
182
|
+
)}
|
|
183
|
+
{...props}
|
|
184
|
+
>
|
|
185
|
+
{children}
|
|
186
|
+
</div>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (isMobile) {
|
|
191
|
+
return (
|
|
192
|
+
<Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
|
|
193
|
+
<SheetContent
|
|
194
|
+
data-sidebar="sidebar"
|
|
195
|
+
data-mobile="true"
|
|
196
|
+
side={side}
|
|
197
|
+
className="w-[var(--sidebar-width)] bg-card p-0 text-foreground [&>button]:hidden"
|
|
198
|
+
style={{ '--sidebar-width': SIDEBAR_WIDTH_MOBILE } as React.CSSProperties}
|
|
199
|
+
>
|
|
200
|
+
<SheetTitle className="sr-only">Sidebar</SheetTitle>
|
|
201
|
+
<SheetDescription className="sr-only">Navigation menu</SheetDescription>
|
|
202
|
+
<div className="flex h-full w-full flex-col">{children}</div>
|
|
203
|
+
</SheetContent>
|
|
204
|
+
</Sheet>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div
|
|
210
|
+
ref={ref}
|
|
211
|
+
className="group peer hidden text-foreground md:block"
|
|
212
|
+
data-state={state}
|
|
213
|
+
data-collapsible={state === 'collapsed' ? collapsible : ''}
|
|
214
|
+
data-variant={variant}
|
|
215
|
+
data-side={side}
|
|
216
|
+
>
|
|
217
|
+
{/* Spacer that takes up width in flow */}
|
|
218
|
+
<div
|
|
219
|
+
className={cn(
|
|
220
|
+
'relative h-svh w-[var(--sidebar-width)] bg-transparent transition-[width] duration-200 ease-linear',
|
|
221
|
+
'group-data-[collapsible=offcanvas]:w-0',
|
|
222
|
+
'group-data-[side=right]:rotate-180',
|
|
223
|
+
variant === 'floating' || variant === 'inset'
|
|
224
|
+
? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
|
|
225
|
+
: 'group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)]',
|
|
226
|
+
)}
|
|
227
|
+
/>
|
|
228
|
+
{/* Actual sidebar — fixed-positioned, width animated */}
|
|
229
|
+
<div
|
|
230
|
+
className={cn(
|
|
231
|
+
'fixed inset-y-0 z-40 hidden h-svh w-[var(--sidebar-width)] transition-[left,right,width] duration-200 ease-linear md:flex',
|
|
232
|
+
side === 'left'
|
|
233
|
+
? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
|
|
234
|
+
: 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
|
235
|
+
variant === 'floating' || variant === 'inset'
|
|
236
|
+
? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
|
|
237
|
+
: 'group-data-[collapsible=icon]:w-[var(--sidebar-width-icon)] group-data-[side=left]:border-r group-data-[side=right]:border-l border-border',
|
|
238
|
+
className,
|
|
239
|
+
)}
|
|
240
|
+
{...props}
|
|
241
|
+
>
|
|
242
|
+
<div
|
|
243
|
+
data-sidebar="sidebar"
|
|
244
|
+
className={cn(
|
|
245
|
+
'flex h-full w-full flex-col bg-card group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-border group-data-[variant=floating]:shadow',
|
|
246
|
+
)}
|
|
247
|
+
>
|
|
248
|
+
{children}
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
)
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
Sidebar.displayName = 'Sidebar'
|
|
256
|
+
|
|
257
|
+
export const SidebarTrigger = React.forwardRef<
|
|
258
|
+
React.ElementRef<typeof Button>,
|
|
259
|
+
React.ComponentPropsWithoutRef<typeof Button>
|
|
260
|
+
>(({ className, onClick, ...props }, ref) => {
|
|
261
|
+
const { toggleSidebar } = useSidebar()
|
|
262
|
+
return (
|
|
263
|
+
<Button
|
|
264
|
+
ref={ref}
|
|
265
|
+
data-sidebar="trigger"
|
|
266
|
+
variant="ghost"
|
|
267
|
+
size="icon"
|
|
268
|
+
className={cn('size-7', className)}
|
|
269
|
+
onClick={(e) => {
|
|
270
|
+
onClick?.(e)
|
|
271
|
+
toggleSidebar()
|
|
272
|
+
}}
|
|
273
|
+
{...props}
|
|
274
|
+
>
|
|
275
|
+
<PanelLeft className="size-4" />
|
|
276
|
+
<span className="sr-only">Toggle sidebar</span>
|
|
277
|
+
</Button>
|
|
278
|
+
)
|
|
279
|
+
})
|
|
280
|
+
SidebarTrigger.displayName = 'SidebarTrigger'
|
|
281
|
+
|
|
282
|
+
export const SidebarRail = React.forwardRef<
|
|
283
|
+
HTMLButtonElement,
|
|
284
|
+
React.ButtonHTMLAttributes<HTMLButtonElement>
|
|
285
|
+
>(({ className, ...props }, ref) => {
|
|
286
|
+
const { toggleSidebar } = useSidebar()
|
|
287
|
+
return (
|
|
288
|
+
<button
|
|
289
|
+
ref={ref}
|
|
290
|
+
type="button"
|
|
291
|
+
data-sidebar="rail"
|
|
292
|
+
aria-label="Toggle sidebar"
|
|
293
|
+
tabIndex={-1}
|
|
294
|
+
onClick={toggleSidebar}
|
|
295
|
+
title="Toggle sidebar"
|
|
296
|
+
className={cn(
|
|
297
|
+
'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
|
|
298
|
+
'[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
|
|
299
|
+
'[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
|
|
300
|
+
'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-card',
|
|
301
|
+
'[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
|
|
302
|
+
'[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
|
|
303
|
+
className,
|
|
304
|
+
)}
|
|
305
|
+
{...props}
|
|
306
|
+
/>
|
|
307
|
+
)
|
|
308
|
+
})
|
|
309
|
+
SidebarRail.displayName = 'SidebarRail'
|
|
310
|
+
|
|
311
|
+
export const SidebarInset = React.forwardRef<
|
|
312
|
+
HTMLDivElement,
|
|
313
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
314
|
+
>(({ className, ...props }, ref) => (
|
|
315
|
+
<main
|
|
316
|
+
ref={ref}
|
|
317
|
+
className={cn(
|
|
318
|
+
'relative flex min-h-svh flex-1 flex-col bg-background',
|
|
319
|
+
'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
|
|
320
|
+
className,
|
|
321
|
+
)}
|
|
322
|
+
{...props}
|
|
323
|
+
/>
|
|
324
|
+
))
|
|
325
|
+
SidebarInset.displayName = 'SidebarInset'
|
|
326
|
+
|
|
327
|
+
export const SidebarInput = React.forwardRef<
|
|
328
|
+
React.ElementRef<typeof Input>,
|
|
329
|
+
React.ComponentPropsWithoutRef<typeof Input>
|
|
330
|
+
>(({ className, ...props }, ref) => (
|
|
331
|
+
<Input
|
|
332
|
+
ref={ref}
|
|
333
|
+
data-sidebar="input"
|
|
334
|
+
className={cn('h-8 w-full bg-background shadow-none focus-visible:ring-2', className)}
|
|
335
|
+
{...props}
|
|
336
|
+
/>
|
|
337
|
+
))
|
|
338
|
+
SidebarInput.displayName = 'SidebarInput'
|
|
339
|
+
|
|
340
|
+
export const SidebarHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
341
|
+
({ className, ...props }, ref) => (
|
|
342
|
+
<div
|
|
343
|
+
ref={ref}
|
|
344
|
+
data-sidebar="header"
|
|
345
|
+
className={cn('flex flex-col gap-2 p-2', className)}
|
|
346
|
+
{...props}
|
|
347
|
+
/>
|
|
348
|
+
),
|
|
349
|
+
)
|
|
350
|
+
SidebarHeader.displayName = 'SidebarHeader'
|
|
351
|
+
|
|
352
|
+
export const SidebarFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
353
|
+
({ className, ...props }, ref) => (
|
|
354
|
+
<div
|
|
355
|
+
ref={ref}
|
|
356
|
+
data-sidebar="footer"
|
|
357
|
+
className={cn('flex flex-col gap-2 p-2', className)}
|
|
358
|
+
{...props}
|
|
359
|
+
/>
|
|
360
|
+
),
|
|
361
|
+
)
|
|
362
|
+
SidebarFooter.displayName = 'SidebarFooter'
|
|
363
|
+
|
|
364
|
+
export const SidebarSeparator = React.forwardRef<
|
|
365
|
+
React.ElementRef<typeof Separator>,
|
|
366
|
+
React.ComponentPropsWithoutRef<typeof Separator>
|
|
367
|
+
>(({ className, ...props }, ref) => (
|
|
368
|
+
<Separator
|
|
369
|
+
ref={ref}
|
|
370
|
+
data-sidebar="separator"
|
|
371
|
+
className={cn('mx-2 w-auto bg-border', className)}
|
|
372
|
+
{...props}
|
|
373
|
+
/>
|
|
374
|
+
))
|
|
375
|
+
SidebarSeparator.displayName = 'SidebarSeparator'
|
|
376
|
+
|
|
377
|
+
export const SidebarContent = React.forwardRef<
|
|
378
|
+
HTMLDivElement,
|
|
379
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
380
|
+
>(({ className, ...props }, ref) => (
|
|
381
|
+
<div
|
|
382
|
+
ref={ref}
|
|
383
|
+
data-sidebar="content"
|
|
384
|
+
className={cn(
|
|
385
|
+
'flex py-2 min-h-0 flex-1 flex-col overflow-auto group-data-[collapsible=icon]:overflow-hidden',
|
|
386
|
+
className,
|
|
387
|
+
)}
|
|
388
|
+
{...props}
|
|
389
|
+
/>
|
|
390
|
+
))
|
|
391
|
+
SidebarContent.displayName = 'SidebarContent'
|
|
392
|
+
|
|
393
|
+
export const SidebarGroup = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
394
|
+
({ className, ...props }, ref) => (
|
|
395
|
+
<div
|
|
396
|
+
ref={ref}
|
|
397
|
+
data-sidebar="group"
|
|
398
|
+
className={cn('relative flex w-full min-w-0 flex-col px-2 py-1', className)}
|
|
399
|
+
{...props}
|
|
400
|
+
/>
|
|
401
|
+
),
|
|
402
|
+
)
|
|
403
|
+
SidebarGroup.displayName = 'SidebarGroup'
|
|
404
|
+
|
|
405
|
+
export const SidebarGroupLabel = React.forwardRef<
|
|
406
|
+
HTMLDivElement,
|
|
407
|
+
React.HTMLAttributes<HTMLDivElement> & { asChild?: boolean }
|
|
408
|
+
>(({ className, asChild, ...props }, ref) => {
|
|
409
|
+
const Comp = asChild ? Slot : 'div'
|
|
410
|
+
return (
|
|
411
|
+
<Comp
|
|
412
|
+
ref={ref}
|
|
413
|
+
data-sidebar="group-label"
|
|
414
|
+
className={cn(
|
|
415
|
+
'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-muted-foreground outline-none ring-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
416
|
+
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
|
417
|
+
className,
|
|
418
|
+
)}
|
|
419
|
+
{...props}
|
|
420
|
+
/>
|
|
421
|
+
)
|
|
422
|
+
})
|
|
423
|
+
SidebarGroupLabel.displayName = 'SidebarGroupLabel'
|
|
424
|
+
|
|
425
|
+
export const SidebarGroupAction = React.forwardRef<
|
|
426
|
+
HTMLButtonElement,
|
|
427
|
+
React.ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean }
|
|
428
|
+
>(({ className, asChild, ...props }, ref) => {
|
|
429
|
+
const Comp = asChild ? Slot : 'button'
|
|
430
|
+
return (
|
|
431
|
+
<Comp
|
|
432
|
+
ref={ref}
|
|
433
|
+
data-sidebar="group-action"
|
|
434
|
+
className={cn(
|
|
435
|
+
'absolute right-3 top-3.5 flex aspect-square w-5 cursor-pointer items-center justify-center rounded-md p-0 text-muted-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
|
436
|
+
'after:absolute after:-inset-2 md:after:hidden',
|
|
437
|
+
'group-data-[collapsible=icon]:hidden',
|
|
438
|
+
className,
|
|
439
|
+
)}
|
|
440
|
+
{...props}
|
|
441
|
+
/>
|
|
442
|
+
)
|
|
443
|
+
})
|
|
444
|
+
SidebarGroupAction.displayName = 'SidebarGroupAction'
|
|
445
|
+
|
|
446
|
+
export const SidebarGroupContent = React.forwardRef<
|
|
447
|
+
HTMLDivElement,
|
|
448
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
449
|
+
>(({ className, ...props }, ref) => (
|
|
450
|
+
<div
|
|
451
|
+
ref={ref}
|
|
452
|
+
data-sidebar="group-content"
|
|
453
|
+
className={cn('w-full text-sm', className)}
|
|
454
|
+
{...props}
|
|
455
|
+
/>
|
|
456
|
+
))
|
|
457
|
+
SidebarGroupContent.displayName = 'SidebarGroupContent'
|
|
458
|
+
|
|
459
|
+
export const SidebarMenu = React.forwardRef<HTMLUListElement, React.HTMLAttributes<HTMLUListElement>>(
|
|
460
|
+
({ className, ...props }, ref) => (
|
|
461
|
+
<ul
|
|
462
|
+
ref={ref}
|
|
463
|
+
data-sidebar="menu"
|
|
464
|
+
className={cn('flex w-full min-w-0 flex-col gap-1', className)}
|
|
465
|
+
{...props}
|
|
466
|
+
/>
|
|
467
|
+
),
|
|
468
|
+
)
|
|
469
|
+
SidebarMenu.displayName = 'SidebarMenu'
|
|
470
|
+
|
|
471
|
+
export const SidebarMenuItem = React.forwardRef<HTMLLIElement, React.HTMLAttributes<HTMLLIElement>>(
|
|
472
|
+
({ className, ...props }, ref) => (
|
|
473
|
+
<li
|
|
474
|
+
ref={ref}
|
|
475
|
+
data-sidebar="menu-item"
|
|
476
|
+
className={cn('group/menu-item relative', className)}
|
|
477
|
+
{...props}
|
|
478
|
+
/>
|
|
479
|
+
),
|
|
480
|
+
)
|
|
481
|
+
SidebarMenuItem.displayName = 'SidebarMenuItem'
|
|
482
|
+
|
|
483
|
+
const sidebarMenuButtonVariants = cva(
|
|
484
|
+
'peer/menu-button flex w-full cursor-pointer items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-ring transition-[width,height,padding] hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 active:bg-accent active:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-accent data-[active=true]:font-medium data-[active=true]:text-accent-foreground data-[state=open]:hover:bg-accent data-[state=open]:hover:text-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
|
|
485
|
+
{
|
|
486
|
+
variants: {
|
|
487
|
+
variant: {
|
|
488
|
+
default: 'hover:bg-accent hover:text-accent-foreground',
|
|
489
|
+
outline:
|
|
490
|
+
'bg-background shadow-[0_0_0_1px_hsl(var(--border))] hover:bg-accent hover:text-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--accent))]',
|
|
491
|
+
},
|
|
492
|
+
size: {
|
|
493
|
+
default: 'h-8 text-sm',
|
|
494
|
+
sm: 'h-7 text-xs',
|
|
495
|
+
lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
defaultVariants: { variant: 'default', size: 'default' },
|
|
499
|
+
},
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
export interface SidebarMenuButtonProps
|
|
503
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
504
|
+
VariantProps<typeof sidebarMenuButtonVariants> {
|
|
505
|
+
asChild?: boolean
|
|
506
|
+
isActive?: boolean
|
|
507
|
+
tooltip?: string | React.ComponentProps<typeof TooltipContent>
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export const SidebarMenuButton = React.forwardRef<HTMLButtonElement, SidebarMenuButtonProps>(
|
|
511
|
+
({ asChild, isActive, variant, size, tooltip, className, ...props }, ref) => {
|
|
512
|
+
const Comp = asChild ? Slot : 'button'
|
|
513
|
+
const { isMobile, state } = useSidebar()
|
|
514
|
+
const button = (
|
|
515
|
+
<Comp
|
|
516
|
+
ref={ref}
|
|
517
|
+
data-sidebar="menu-button"
|
|
518
|
+
data-size={size ?? 'default'}
|
|
519
|
+
data-active={isActive}
|
|
520
|
+
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
|
|
521
|
+
{...props}
|
|
522
|
+
/>
|
|
523
|
+
)
|
|
524
|
+
if (!tooltip) return button
|
|
525
|
+
const tooltipProps = typeof tooltip === 'string' ? { children: tooltip } : tooltip
|
|
526
|
+
return (
|
|
527
|
+
<Tooltip>
|
|
528
|
+
<TooltipTrigger asChild>{button}</TooltipTrigger>
|
|
529
|
+
<TooltipContent side="right" align="center" hidden={state !== 'collapsed' || isMobile} {...tooltipProps} />
|
|
530
|
+
</Tooltip>
|
|
531
|
+
)
|
|
532
|
+
},
|
|
533
|
+
)
|
|
534
|
+
SidebarMenuButton.displayName = 'SidebarMenuButton'
|
|
535
|
+
|
|
536
|
+
export const SidebarMenuAction = React.forwardRef<
|
|
537
|
+
HTMLButtonElement,
|
|
538
|
+
React.ButtonHTMLAttributes<HTMLButtonElement> & { asChild?: boolean; showOnHover?: boolean }
|
|
539
|
+
>(({ className, asChild, showOnHover, ...props }, ref) => {
|
|
540
|
+
const Comp = asChild ? Slot : 'button'
|
|
541
|
+
return (
|
|
542
|
+
<Comp
|
|
543
|
+
ref={ref}
|
|
544
|
+
data-sidebar="menu-action"
|
|
545
|
+
className={cn(
|
|
546
|
+
'absolute right-1 top-1.5 flex aspect-square w-5 cursor-pointer items-center justify-center rounded-md p-0 text-muted-foreground outline-none ring-ring transition-transform hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
|
|
547
|
+
'after:absolute after:-inset-2 md:after:hidden',
|
|
548
|
+
'peer-data-[size=sm]/menu-button:top-1',
|
|
549
|
+
'peer-data-[size=default]/menu-button:top-1.5',
|
|
550
|
+
'peer-data-[size=lg]/menu-button:top-2.5',
|
|
551
|
+
'group-data-[collapsible=icon]:hidden',
|
|
552
|
+
showOnHover &&
|
|
553
|
+
'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-accent-foreground md:opacity-0',
|
|
554
|
+
className,
|
|
555
|
+
)}
|
|
556
|
+
{...props}
|
|
557
|
+
/>
|
|
558
|
+
)
|
|
559
|
+
})
|
|
560
|
+
SidebarMenuAction.displayName = 'SidebarMenuAction'
|
|
561
|
+
|
|
562
|
+
export const SidebarMenuBadge = React.forwardRef<
|
|
563
|
+
HTMLDivElement,
|
|
564
|
+
React.HTMLAttributes<HTMLDivElement>
|
|
565
|
+
>(({ className, ...props }, ref) => (
|
|
566
|
+
<div
|
|
567
|
+
ref={ref}
|
|
568
|
+
data-sidebar="menu-badge"
|
|
569
|
+
className={cn(
|
|
570
|
+
'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-muted-foreground',
|
|
571
|
+
'peer-hover/menu-button:text-accent-foreground peer-data-[active=true]/menu-button:text-accent-foreground',
|
|
572
|
+
'peer-data-[size=sm]/menu-button:top-1',
|
|
573
|
+
'peer-data-[size=default]/menu-button:top-1.5',
|
|
574
|
+
'peer-data-[size=lg]/menu-button:top-2.5',
|
|
575
|
+
'group-data-[collapsible=icon]:hidden',
|
|
576
|
+
className,
|
|
577
|
+
)}
|
|
578
|
+
{...props}
|
|
579
|
+
/>
|
|
580
|
+
))
|
|
581
|
+
SidebarMenuBadge.displayName = 'SidebarMenuBadge'
|
|
582
|
+
|
|
583
|
+
export const SidebarMenuSkeleton = React.forwardRef<
|
|
584
|
+
HTMLDivElement,
|
|
585
|
+
React.HTMLAttributes<HTMLDivElement> & { showIcon?: boolean }
|
|
586
|
+
>(({ className, showIcon = false, ...props }, ref) => {
|
|
587
|
+
const id = React.useId()
|
|
588
|
+
// Derive a stable per-instance pseudo-random width (50%..90%) from useId so
|
|
589
|
+
// skeleton rows visually vary without violating component purity.
|
|
590
|
+
const width = React.useMemo(() => {
|
|
591
|
+
let h = 0
|
|
592
|
+
for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) | 0
|
|
593
|
+
return `${50 + (Math.abs(h) % 40)}%`
|
|
594
|
+
}, [id])
|
|
595
|
+
return (
|
|
596
|
+
<div
|
|
597
|
+
ref={ref}
|
|
598
|
+
data-sidebar="menu-skeleton"
|
|
599
|
+
className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
|
|
600
|
+
{...props}
|
|
601
|
+
>
|
|
602
|
+
{showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
|
|
603
|
+
<Skeleton
|
|
604
|
+
className="h-4 max-w-[var(--skeleton-width)] flex-1"
|
|
605
|
+
data-sidebar="menu-skeleton-text"
|
|
606
|
+
style={{ '--skeleton-width': width } as React.CSSProperties}
|
|
607
|
+
/>
|
|
608
|
+
</div>
|
|
609
|
+
)
|
|
610
|
+
})
|
|
611
|
+
SidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton'
|
|
612
|
+
|
|
613
|
+
export const SidebarMenuSub = React.forwardRef<HTMLUListElement, React.HTMLAttributes<HTMLUListElement>>(
|
|
614
|
+
({ className, ...props }, ref) => (
|
|
615
|
+
<ul
|
|
616
|
+
ref={ref}
|
|
617
|
+
data-sidebar="menu-sub"
|
|
618
|
+
className={cn(
|
|
619
|
+
'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-border px-2.5 py-0.5',
|
|
620
|
+
'group-data-[collapsible=icon]:hidden',
|
|
621
|
+
className,
|
|
622
|
+
)}
|
|
623
|
+
{...props}
|
|
624
|
+
/>
|
|
625
|
+
),
|
|
626
|
+
)
|
|
627
|
+
SidebarMenuSub.displayName = 'SidebarMenuSub'
|
|
628
|
+
|
|
629
|
+
export const SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.HTMLAttributes<HTMLLIElement>>(
|
|
630
|
+
({ ...props }, ref) => <li ref={ref} {...props} />,
|
|
631
|
+
)
|
|
632
|
+
SidebarMenuSubItem.displayName = 'SidebarMenuSubItem'
|
|
633
|
+
|
|
634
|
+
export const SidebarMenuSubButton = React.forwardRef<
|
|
635
|
+
HTMLAnchorElement,
|
|
636
|
+
React.AnchorHTMLAttributes<HTMLAnchorElement> & {
|
|
637
|
+
asChild?: boolean
|
|
638
|
+
size?: 'sm' | 'md'
|
|
639
|
+
isActive?: boolean
|
|
640
|
+
}
|
|
641
|
+
>(({ asChild, size = 'md', isActive, className, ...props }, ref) => {
|
|
642
|
+
const Comp = asChild ? Slot : 'a'
|
|
643
|
+
return (
|
|
644
|
+
<Comp
|
|
645
|
+
ref={ref}
|
|
646
|
+
data-sidebar="menu-sub-button"
|
|
647
|
+
data-size={size}
|
|
648
|
+
data-active={isActive}
|
|
649
|
+
className={cn(
|
|
650
|
+
'flex h-7 min-w-0 -translate-x-px cursor-pointer items-center gap-2 overflow-hidden rounded-md px-2 text-sm text-foreground outline-none ring-ring hover:bg-accent hover:text-accent-foreground focus-visible:ring-2 active:bg-accent active:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-muted-foreground',
|
|
651
|
+
'data-[active=true]:bg-accent data-[active=true]:text-accent-foreground',
|
|
652
|
+
size === 'sm' && 'text-xs',
|
|
653
|
+
'group-data-[collapsible=icon]:hidden',
|
|
654
|
+
className,
|
|
655
|
+
)}
|
|
656
|
+
{...props}
|
|
657
|
+
/>
|
|
658
|
+
)
|
|
659
|
+
})
|
|
660
|
+
SidebarMenuSubButton.displayName = 'SidebarMenuSubButton'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { cn } from '../lib/utils.js'
|
|
3
|
+
|
|
4
|
+
export function Skeleton({
|
|
5
|
+
className,
|
|
6
|
+
...props
|
|
7
|
+
}: React.HTMLAttributes<HTMLDivElement>): React.ReactElement {
|
|
8
|
+
return <div className={cn('animate-pulse rounded-md bg-muted', className)} {...props} />
|
|
9
|
+
}
|