@exegia/corpora-ui 0.17.0 → 0.18.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-lib/components/blocks/nav/sidebar-block.d.ts +1 -1
- package/dist-lib/components/blocks/scaffold/__tests__/scaffold.test.d.ts +1 -0
- package/dist-lib/components/blocks/scaffold/constants.d.ts +20 -0
- package/dist-lib/components/blocks/scaffold/index.d.ts +25 -0
- package/dist-lib/components/blocks/scaffold/panel-close-button.d.ts +4 -0
- package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +9 -0
- package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-context.d.ts +5 -0
- package/dist-lib/components/blocks/scaffold/scaffold-inspector.d.ts +9 -0
- package/dist-lib/components/blocks/scaffold/scaffold-main.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +10 -0
- package/dist-lib/components/blocks/scaffold/scaffold-root.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/scaffold-sidebar.d.ts +7 -0
- package/dist-lib/components/blocks/scaffold/scaffold-sub-panel.d.ts +3 -0
- package/dist-lib/components/blocks/scaffold/type.d.ts +133 -0
- package/dist-lib/components/blocks/scaffold/use-scaffold.d.ts +8 -0
- package/dist-lib/components/blocks/scaffold/utils.d.ts +15 -0
- package/dist-lib/components/blocks/shell/animated-panel-inset.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel-trigger.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-panel.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +3 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +3 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +2 -0
- package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +2 -0
- package/dist-lib/components/blocks/shell/index.d.ts +16 -0
- package/dist-lib/components/blocks/shell/type.d.ts +90 -4
- package/dist-lib/components/blocks/shell/utils.d.ts +37 -0
- package/dist-lib/components/motion/index.d.ts +1 -0
- package/dist-lib/components/motion/overflow-actions.d.ts +35 -0
- package/dist-lib/components/ui/button-group.d.ts +11 -0
- package/dist-lib/components/ui/glasscn/glass-button-group.d.ts +5 -0
- package/dist-lib/components/ui/glasscn/glass-container.d.ts +7 -0
- package/dist-lib/components/ui/glasscn/glass-separator.d.ts +5 -0
- package/dist-lib/components/ui/group.d.ts +17 -0
- package/dist-lib/components/ui/separator.d.ts +2 -3
- package/dist-lib/components/ui/sheet.d.ts +14 -0
- package/dist-lib/components/ui/tooltip.d.ts +6 -0
- package/dist-lib/index.d.ts +2 -1
- package/dist-lib/index.js +1592 -1084
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/lib/ease.d.ts +3 -0
- package/dist-lib/lib/hooks/use-hover-capable.d.ts +6 -0
- package/package.json +2 -1
- package/src/components/beste/piece/browser-frame.tsx +2 -2
- package/src/components/blocks/nav/sidebar-block.tsx +5 -5
- package/src/components/blocks/nav/sidebar-nav-row.tsx +4 -5
- package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +203 -0
- package/src/components/blocks/scaffold/constants.ts +28 -0
- package/src/components/blocks/scaffold/index.ts +38 -0
- package/src/components/blocks/scaffold/panel-close-button.tsx +36 -0
- package/src/components/blocks/scaffold/panel-menu-button.tsx +240 -0
- package/src/components/blocks/scaffold/scaffold-actions.tsx +97 -0
- package/src/components/blocks/scaffold/scaffold-canvas.tsx +29 -0
- package/src/components/blocks/scaffold/scaffold-context.ts +18 -0
- package/src/components/blocks/scaffold/scaffold-inspector.tsx +57 -0
- package/src/components/blocks/scaffold/scaffold-main.tsx +28 -0
- package/src/components/blocks/scaffold/scaffold-panel.tsx +98 -0
- package/src/components/blocks/scaffold/scaffold-root.tsx +68 -0
- package/src/components/blocks/scaffold/scaffold-sidebar.tsx +31 -0
- package/src/components/blocks/scaffold/scaffold-sub-panel.tsx +51 -0
- package/src/components/blocks/scaffold/type.ts +171 -0
- package/src/components/blocks/scaffold/use-scaffold.ts +48 -0
- package/src/components/blocks/scaffold/utils.ts +30 -0
- package/src/components/blocks/shell/animated-panel-inset.tsx +23 -0
- package/src/components/blocks/shell/animated-panel-provider.tsx +155 -0
- package/src/components/blocks/shell/animated-panel-trigger.tsx +42 -0
- package/src/components/blocks/shell/animated-panel.tsx +135 -0
- package/src/components/blocks/shell/animated-sidebar-content.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-footer.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-group-content.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-group-label.tsx +29 -0
- package/src/components/blocks/shell/animated-sidebar-group.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-header.tsx +16 -0
- package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +151 -0
- package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +20 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +85 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +19 -0
- package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +44 -0
- package/src/components/blocks/shell/animated-sidebar-menu.tsx +26 -0
- package/src/components/blocks/shell/index.ts +18 -0
- package/src/components/blocks/shell/shell-layout.tsx +28 -36
- package/src/components/blocks/shell/type.ts +112 -6
- package/src/components/blocks/shell/use-shell-panels.ts +1 -1
- package/src/components/blocks/shell/utils.ts +142 -1
- package/src/components/docs/demo-controls.tsx +1 -1
- package/src/components/motion/index.ts +0 -0
- package/src/components/motion/overflow-actions.tsx +376 -0
- package/src/components/ui/button-group.tsx +88 -0
- package/src/components/ui/glasscn/glass-button-group.tsx +48 -0
- package/src/components/ui/glasscn/glass-container.tsx +125 -0
- package/src/components/ui/glasscn/glass-separator.tsx +39 -0
- package/src/components/ui/glasscn/liquid-glass.tsx +250 -210
- package/src/components/ui/group.tsx +92 -0
- package/src/components/ui/separator.tsx +11 -11
- package/src/components/ui/sheet.tsx +135 -0
- package/src/components/ui/tooltip.tsx +66 -0
- package/src/index.css +1 -1
- package/src/index.ts +13 -1
- package/src/lib/ease.ts +26 -0
- package/src/lib/hooks/use-hover-capable.ts +23 -0
- package/dist-lib/components/motion/animated-sidebar.d.ts +0 -106
- package/src/components/motion/animated-sidebar.tsx +0 -1171
|
@@ -1,1171 +0,0 @@
|
|
|
1
|
-
"use client"
|
|
2
|
-
// beui.dev/components/motion/animated-sidebar
|
|
3
|
-
|
|
4
|
-
import { ChevronRight } from "lucide-react"
|
|
5
|
-
import {
|
|
6
|
-
AnimatePresence,
|
|
7
|
-
type HTMLMotionProps,
|
|
8
|
-
motion,
|
|
9
|
-
useReducedMotion,
|
|
10
|
-
type Variants,
|
|
11
|
-
} from "motion/react"
|
|
12
|
-
import {
|
|
13
|
-
type ButtonHTMLAttributes,
|
|
14
|
-
type CSSProperties,
|
|
15
|
-
createContext,
|
|
16
|
-
forwardRef,
|
|
17
|
-
type HTMLAttributes,
|
|
18
|
-
type ReactNode,
|
|
19
|
-
useCallback,
|
|
20
|
-
useContext,
|
|
21
|
-
useEffect,
|
|
22
|
-
useId,
|
|
23
|
-
useMemo,
|
|
24
|
-
useRef,
|
|
25
|
-
useState,
|
|
26
|
-
useSyncExternalStore,
|
|
27
|
-
} from "react"
|
|
28
|
-
import { createPortal } from "react-dom"
|
|
29
|
-
import { SharedLayoutBg } from "@/components/motion/shared-layout-bg"
|
|
30
|
-
import { EASE_DRAWER, EASE_OUT, SPRING_LAYOUT, SPRING_PRESS } from "@/lib/ease"
|
|
31
|
-
import { cn } from "@/lib/utils"
|
|
32
|
-
import { Button } from "@/components/ui/button";
|
|
33
|
-
|
|
34
|
-
export type SidebarSide = "left" | "right"
|
|
35
|
-
/** Open flags keyed by the side. Sides left out stay uncontrolled / at their
|
|
36
|
-
* default — there is no per-side prop, the record IS the API. */
|
|
37
|
-
export type SidebarOpenState = Partial<Record<SidebarSide, boolean>>
|
|
38
|
-
type SidebarVariant = "sidebar" | "floating" | "inset"
|
|
39
|
-
type SidebarCollapsible = "offcanvas" | "icon" | "none"
|
|
40
|
-
|
|
41
|
-
const MOBILE_QUERY = "(max-width: 767px)"
|
|
42
|
-
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|
|
43
|
-
|
|
44
|
-
const PANEL_TRANSITION = {
|
|
45
|
-
duration: 0.36,
|
|
46
|
-
ease: EASE_DRAWER,
|
|
47
|
-
} as const
|
|
48
|
-
|
|
49
|
-
// The desktop rail settles at a hard zero-width boundary. Keep the spring
|
|
50
|
-
// critically damped so it cannot overshoot, pause against that boundary, and
|
|
51
|
-
// then snap back during the final frame.
|
|
52
|
-
const SIDEBAR_MORPH_TRANSITION = {
|
|
53
|
-
type: "spring",
|
|
54
|
-
stiffness: 380,
|
|
55
|
-
damping: 35,
|
|
56
|
-
mass: 0.75,
|
|
57
|
-
} as const
|
|
58
|
-
|
|
59
|
-
const LABEL_ENTER_TRANSITION = {
|
|
60
|
-
duration: 0.2,
|
|
61
|
-
delay: 0.08,
|
|
62
|
-
ease: EASE_OUT,
|
|
63
|
-
} as const
|
|
64
|
-
|
|
65
|
-
const LABEL_EXIT_TRANSITION = {
|
|
66
|
-
duration: 0.12,
|
|
67
|
-
ease: EASE_OUT,
|
|
68
|
-
} as const
|
|
69
|
-
|
|
70
|
-
const SUBMENU_TRANSITION = {
|
|
71
|
-
duration: 0.18,
|
|
72
|
-
ease: EASE_OUT,
|
|
73
|
-
} as const
|
|
74
|
-
|
|
75
|
-
const SUBMENU_VARIANTS: Variants = {
|
|
76
|
-
closed: {
|
|
77
|
-
opacity: 0,
|
|
78
|
-
clipPath: "inset(0 0 100% 0 round 8px)",
|
|
79
|
-
transition: {
|
|
80
|
-
duration: 0.14,
|
|
81
|
-
ease: EASE_OUT,
|
|
82
|
-
staggerChildren: 0.025,
|
|
83
|
-
staggerDirection: -1,
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
open: {
|
|
87
|
-
opacity: 1,
|
|
88
|
-
clipPath: "inset(0 0 0% 0 round 8px)",
|
|
89
|
-
transition: {
|
|
90
|
-
duration: 0.2,
|
|
91
|
-
delayChildren: 0.035,
|
|
92
|
-
ease: EASE_OUT,
|
|
93
|
-
staggerChildren: 0.045,
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const SUBMENU_ITEM_VARIANTS: Variants = {
|
|
99
|
-
closed: {
|
|
100
|
-
opacity: 0,
|
|
101
|
-
y: -6,
|
|
102
|
-
filter: "blur(3px)",
|
|
103
|
-
},
|
|
104
|
-
open: {
|
|
105
|
-
opacity: 1,
|
|
106
|
-
y: 0,
|
|
107
|
-
filter: "blur(0px)",
|
|
108
|
-
transition: SUBMENU_TRANSITION,
|
|
109
|
-
},
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const REDUCED_TRANSITION = {
|
|
113
|
-
duration: 0.16,
|
|
114
|
-
ease: EASE_OUT,
|
|
115
|
-
} as const
|
|
116
|
-
|
|
117
|
-
const FOCUSABLE_SELECTOR = [
|
|
118
|
-
"a[href]",
|
|
119
|
-
"button:not([disabled])",
|
|
120
|
-
"input:not([disabled])",
|
|
121
|
-
"select:not([disabled])",
|
|
122
|
-
"textarea:not([disabled])",
|
|
123
|
-
"[tabindex]:not([tabindex='-1'])",
|
|
124
|
-
].join(",")
|
|
125
|
-
|
|
126
|
-
function subscribeToMobileQuery(callback: () => void) {
|
|
127
|
-
const query = window.matchMedia(MOBILE_QUERY)
|
|
128
|
-
query.addEventListener("change", callback)
|
|
129
|
-
return () => query.removeEventListener("change", callback)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function getMobileSnapshot() {
|
|
133
|
-
return window.matchMedia(MOBILE_QUERY).matches
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function getServerMobileSnapshot() {
|
|
137
|
-
return false
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function useIsMobile() {
|
|
141
|
-
return useSyncExternalStore(
|
|
142
|
-
subscribeToMobileQuery,
|
|
143
|
-
getMobileSnapshot,
|
|
144
|
-
getServerMobileSnapshot
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
interface AnimatedSidebarContextValue {
|
|
149
|
-
isMobile: boolean
|
|
150
|
-
layoutId: string
|
|
151
|
-
/** Desktop open state, keyed by side. */
|
|
152
|
-
open: Record<SidebarSide, boolean>
|
|
153
|
-
/** Mobile overlay open state, keyed by side. */
|
|
154
|
-
openMobile: Record<SidebarSide, boolean>
|
|
155
|
-
reduce: boolean
|
|
156
|
-
setOpen: (open: boolean, side: SidebarSide) => void
|
|
157
|
-
setOpenMobile: (open: boolean, side: SidebarSide) => void
|
|
158
|
-
/** Mobile-aware: toggles the overlay below md, the docked panel above. */
|
|
159
|
-
toggleSidebar: (side: SidebarSide) => void
|
|
160
|
-
triggerRefs: Record<SidebarSide, React.RefObject<HTMLButtonElement | null>>
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const AnimatedSidebarContext =
|
|
164
|
-
createContext<AnimatedSidebarContextValue | null>(null)
|
|
165
|
-
|
|
166
|
-
interface AnimatedSidebarPanelContextValue {
|
|
167
|
-
collapsed: boolean
|
|
168
|
-
collapsible: SidebarCollapsible
|
|
169
|
-
side: SidebarSide
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const AnimatedSidebarPanelContext =
|
|
173
|
-
createContext<AnimatedSidebarPanelContextValue | null>(null)
|
|
174
|
-
|
|
175
|
-
export function useAnimatedSidebar() {
|
|
176
|
-
const context = useContext(AnimatedSidebarContext)
|
|
177
|
-
if (!context) {
|
|
178
|
-
throw new Error(
|
|
179
|
-
"useAnimatedSidebar must be used inside AnimatedSidebarProvider."
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
return context
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function useAnimatedSidebarPanel() {
|
|
186
|
-
const context = useContext(AnimatedSidebarPanelContext)
|
|
187
|
-
if (!context) {
|
|
188
|
-
throw new Error(
|
|
189
|
-
"Animated Sidebar parts must be used inside AnimatedSidebar."
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
return context
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
type SidebarProviderStyle = CSSProperties & {
|
|
196
|
-
"--sidebar-width"?: string
|
|
197
|
-
"--sidebar-width-icon"?: string
|
|
198
|
-
"--sidebar-width-mobile"?: string
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export interface AnimatedSidebarProviderProps extends HTMLAttributes<HTMLDivElement> {
|
|
202
|
-
/** Controlled desktop open state, keyed by the side. A side left undefined
|
|
203
|
-
* stays uncontrolled. */
|
|
204
|
-
open?: SidebarOpenState
|
|
205
|
-
/** Initial desktop open state, merged over `{ left: true, right: false }`. */
|
|
206
|
-
defaultOpen?: SidebarOpenState
|
|
207
|
-
onOpenChange?: (open: boolean, side: SidebarSide) => void
|
|
208
|
-
/** Controlled mobile overlay state, keyed by side. */
|
|
209
|
-
openMobile?: SidebarOpenState
|
|
210
|
-
/** Initial mobile overlay state — every side starts closed. */
|
|
211
|
-
defaultOpenMobile?: SidebarOpenState
|
|
212
|
-
onOpenMobileChange?: (open: boolean, side: SidebarSide) => void
|
|
213
|
-
style?: SidebarProviderStyle
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
export function AnimatedSidebarProvider({
|
|
217
|
-
children,
|
|
218
|
-
open,
|
|
219
|
-
defaultOpen,
|
|
220
|
-
onOpenChange,
|
|
221
|
-
openMobile,
|
|
222
|
-
defaultOpenMobile,
|
|
223
|
-
onOpenMobileChange,
|
|
224
|
-
className,
|
|
225
|
-
style,
|
|
226
|
-
...props
|
|
227
|
-
}: AnimatedSidebarProviderProps) {
|
|
228
|
-
const [internalOpen, setInternalOpen] = useState<
|
|
229
|
-
Record<SidebarSide, boolean>
|
|
230
|
-
>(() => ({
|
|
231
|
-
left: defaultOpen?.left ?? true,
|
|
232
|
-
right: defaultOpen?.right ?? false,
|
|
233
|
-
}))
|
|
234
|
-
const [internalOpenMobile, setInternalOpenMobile] = useState<
|
|
235
|
-
Record<SidebarSide, boolean>
|
|
236
|
-
>(() => ({
|
|
237
|
-
left: defaultOpenMobile?.left ?? false,
|
|
238
|
-
right: defaultOpenMobile?.right ?? false,
|
|
239
|
-
}))
|
|
240
|
-
const isMobile = useIsMobile()
|
|
241
|
-
const reduce = useReducedMotion() ?? false
|
|
242
|
-
const generatedId = useId()
|
|
243
|
-
const leftTriggerRef = useRef<HTMLButtonElement>(null)
|
|
244
|
-
const rightTriggerRef = useRef<HTMLButtonElement>(null)
|
|
245
|
-
|
|
246
|
-
// Per-side controlled/uncontrolled merge — a side is controlled exactly
|
|
247
|
-
// when its key is present on the controlled record.
|
|
248
|
-
const controlledLeft = open?.left
|
|
249
|
-
const controlledRight = open?.right
|
|
250
|
-
const controlledMobileLeft = openMobile?.left
|
|
251
|
-
const controlledMobileRight = openMobile?.right
|
|
252
|
-
|
|
253
|
-
const openState = useMemo<Record<SidebarSide, boolean>>(
|
|
254
|
-
() => ({
|
|
255
|
-
left: controlledLeft ?? internalOpen.left,
|
|
256
|
-
right: controlledRight ?? internalOpen.right,
|
|
257
|
-
}),
|
|
258
|
-
[controlledLeft, controlledRight, internalOpen]
|
|
259
|
-
)
|
|
260
|
-
const openMobileState = useMemo<Record<SidebarSide, boolean>>(
|
|
261
|
-
() => ({
|
|
262
|
-
left: controlledMobileLeft ?? internalOpenMobile.left,
|
|
263
|
-
right: controlledMobileRight ?? internalOpenMobile.right,
|
|
264
|
-
}),
|
|
265
|
-
[controlledMobileLeft, controlledMobileRight, internalOpenMobile]
|
|
266
|
-
)
|
|
267
|
-
|
|
268
|
-
const setOpen = useCallback(
|
|
269
|
-
(nextOpen: boolean, side: SidebarSide) => {
|
|
270
|
-
const controlled = side === "left" ? controlledLeft : controlledRight
|
|
271
|
-
if (controlled === undefined) {
|
|
272
|
-
setInternalOpen((prev) =>
|
|
273
|
-
prev[side] === nextOpen ? prev : { ...prev, [side]: nextOpen }
|
|
274
|
-
)
|
|
275
|
-
}
|
|
276
|
-
onOpenChange?.(nextOpen, side)
|
|
277
|
-
},
|
|
278
|
-
[controlledLeft, controlledRight, onOpenChange]
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
const setOpenMobile = useCallback(
|
|
282
|
-
(nextOpen: boolean, side: SidebarSide) => {
|
|
283
|
-
const controlled =
|
|
284
|
-
side === "left" ? controlledMobileLeft : controlledMobileRight
|
|
285
|
-
if (controlled === undefined) {
|
|
286
|
-
setInternalOpenMobile((prev) =>
|
|
287
|
-
prev[side] === nextOpen ? prev : { ...prev, [side]: nextOpen }
|
|
288
|
-
)
|
|
289
|
-
}
|
|
290
|
-
onOpenMobileChange?.(nextOpen, side)
|
|
291
|
-
},
|
|
292
|
-
[controlledMobileLeft, controlledMobileRight, onOpenMobileChange]
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
const toggleSidebar = useCallback(
|
|
296
|
-
(side: SidebarSide) => {
|
|
297
|
-
if (isMobile) setOpenMobile(!openMobileState[side], side)
|
|
298
|
-
else setOpen(!openState[side], side)
|
|
299
|
-
},
|
|
300
|
-
[isMobile, openMobileState, openState, setOpen, setOpenMobile]
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
const triggerRefs = useMemo(
|
|
304
|
-
() => ({ left: leftTriggerRef, right: rightTriggerRef }),
|
|
305
|
-
[]
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
useEffect(() => {
|
|
309
|
-
const handleShortcut = (event: KeyboardEvent) => {
|
|
310
|
-
if (
|
|
311
|
-
event.key.toLowerCase() === SIDEBAR_KEYBOARD_SHORTCUT &&
|
|
312
|
-
(event.metaKey || event.ctrlKey)
|
|
313
|
-
) {
|
|
314
|
-
event.preventDefault()
|
|
315
|
-
toggleSidebar("left")
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
window.addEventListener("keydown", handleShortcut)
|
|
320
|
-
return () => window.removeEventListener("keydown", handleShortcut)
|
|
321
|
-
}, [toggleSidebar])
|
|
322
|
-
|
|
323
|
-
return (
|
|
324
|
-
<AnimatedSidebarContext.Provider
|
|
325
|
-
value={{
|
|
326
|
-
isMobile,
|
|
327
|
-
layoutId: `${generatedId}-active`,
|
|
328
|
-
open: openState,
|
|
329
|
-
openMobile: openMobileState,
|
|
330
|
-
reduce,
|
|
331
|
-
setOpen,
|
|
332
|
-
setOpenMobile,
|
|
333
|
-
toggleSidebar,
|
|
334
|
-
triggerRefs,
|
|
335
|
-
}}
|
|
336
|
-
>
|
|
337
|
-
<div
|
|
338
|
-
{...props}
|
|
339
|
-
data-slot="sidebar-wrapper"
|
|
340
|
-
data-state-left={openState.left ? "expanded" : "collapsed"}
|
|
341
|
-
data-state-right={openState.right ? "expanded" : "collapsed"}
|
|
342
|
-
style={{
|
|
343
|
-
"--sidebar-width": "19rem",
|
|
344
|
-
"--sidebar-width-icon": "4.25rem",
|
|
345
|
-
"--sidebar-width-mobile": "18rem",
|
|
346
|
-
...style,
|
|
347
|
-
}}
|
|
348
|
-
className={cn(
|
|
349
|
-
"group/sidebar-wrapper flex min-h-svh w-full min-w-0",
|
|
350
|
-
className
|
|
351
|
-
)}
|
|
352
|
-
>
|
|
353
|
-
{children}
|
|
354
|
-
</div>
|
|
355
|
-
</AnimatedSidebarContext.Provider>
|
|
356
|
-
)
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function MobileSidebar({
|
|
360
|
-
ariaLabel,
|
|
361
|
-
children,
|
|
362
|
-
className,
|
|
363
|
-
side,
|
|
364
|
-
}: {
|
|
365
|
-
ariaLabel: string
|
|
366
|
-
children: ReactNode
|
|
367
|
-
className?: string
|
|
368
|
-
side: SidebarSide
|
|
369
|
-
}) {
|
|
370
|
-
const context = useAnimatedSidebar()
|
|
371
|
-
const open = context.openMobile[side]
|
|
372
|
-
const setOpen = (nextOpen: boolean) => context.setOpenMobile(nextOpen, side)
|
|
373
|
-
const triggerRef = context.triggerRefs[side]
|
|
374
|
-
const panelRef = useRef<HTMLDivElement>(null)
|
|
375
|
-
const [mounted, setMounted] = useState(false)
|
|
376
|
-
|
|
377
|
-
useEffect(() => setMounted(true), [])
|
|
378
|
-
|
|
379
|
-
useEffect(() => {
|
|
380
|
-
if (!open) return
|
|
381
|
-
|
|
382
|
-
const body = document.body
|
|
383
|
-
const scrollY = window.scrollY
|
|
384
|
-
const trigger = triggerRef.current
|
|
385
|
-
const previousBodyStyles = {
|
|
386
|
-
left: body.style.left,
|
|
387
|
-
overflow: body.style.overflow,
|
|
388
|
-
position: body.style.position,
|
|
389
|
-
right: body.style.right,
|
|
390
|
-
top: body.style.top,
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
body.style.position = "fixed"
|
|
394
|
-
body.style.top = `-${scrollY}px`
|
|
395
|
-
body.style.left = "0"
|
|
396
|
-
body.style.right = "0"
|
|
397
|
-
body.style.overflow = "hidden"
|
|
398
|
-
|
|
399
|
-
const focusFrame = requestAnimationFrame(() => {
|
|
400
|
-
const firstFocusable =
|
|
401
|
-
panelRef.current?.querySelector<HTMLElement>(FOCUSABLE_SELECTOR)
|
|
402
|
-
;(firstFocusable ?? panelRef.current)?.focus({ preventScroll: true })
|
|
403
|
-
})
|
|
404
|
-
|
|
405
|
-
return () => {
|
|
406
|
-
cancelAnimationFrame(focusFrame)
|
|
407
|
-
body.style.position = previousBodyStyles.position
|
|
408
|
-
body.style.top = previousBodyStyles.top
|
|
409
|
-
body.style.left = previousBodyStyles.left
|
|
410
|
-
body.style.right = previousBodyStyles.right
|
|
411
|
-
body.style.overflow = previousBodyStyles.overflow
|
|
412
|
-
window.scrollTo(0, scrollY)
|
|
413
|
-
trigger?.focus({ preventScroll: true })
|
|
414
|
-
}
|
|
415
|
-
}, [open, triggerRef])
|
|
416
|
-
|
|
417
|
-
if (!mounted) return null
|
|
418
|
-
|
|
419
|
-
return createPortal(
|
|
420
|
-
<div
|
|
421
|
-
className={cn(
|
|
422
|
-
"pointer-events-none fixed inset-0 z-50 md:hidden",
|
|
423
|
-
open ? "visible" : "invisible"
|
|
424
|
-
)}
|
|
425
|
-
>
|
|
426
|
-
<motion.button
|
|
427
|
-
type="button"
|
|
428
|
-
aria-label="Close sidebar"
|
|
429
|
-
tabIndex={open ? 0 : -1}
|
|
430
|
-
initial={false}
|
|
431
|
-
animate={{ opacity: open ? 1 : 0 }}
|
|
432
|
-
transition={context.reduce ? REDUCED_TRANSITION : PANEL_TRANSITION}
|
|
433
|
-
onClick={() => setOpen(false)}
|
|
434
|
-
className={cn(
|
|
435
|
-
"absolute inset-0",
|
|
436
|
-
open ? "pointer-events-auto" : "pointer-events-none"
|
|
437
|
-
)}
|
|
438
|
-
/>
|
|
439
|
-
|
|
440
|
-
<motion.div
|
|
441
|
-
ref={panelRef}
|
|
442
|
-
role="dialog"
|
|
443
|
-
aria-modal="true"
|
|
444
|
-
aria-label={ariaLabel}
|
|
445
|
-
aria-hidden={!open}
|
|
446
|
-
inert={!open}
|
|
447
|
-
tabIndex={-1}
|
|
448
|
-
data-mobile="true"
|
|
449
|
-
data-state={open ? "expanded" : "collapsed"}
|
|
450
|
-
data-side={side}
|
|
451
|
-
initial={false}
|
|
452
|
-
animate={{
|
|
453
|
-
opacity: context.reduce ? (open ? 1 : 0) : 1,
|
|
454
|
-
x: context.reduce
|
|
455
|
-
? 0
|
|
456
|
-
: open
|
|
457
|
-
? "0%"
|
|
458
|
-
: side === "left"
|
|
459
|
-
? "-100%"
|
|
460
|
-
: "100%",
|
|
461
|
-
}}
|
|
462
|
-
transition={context.reduce ? REDUCED_TRANSITION : PANEL_TRANSITION}
|
|
463
|
-
onKeyDown={(event) => {
|
|
464
|
-
if (event.key === "Escape") {
|
|
465
|
-
event.preventDefault()
|
|
466
|
-
setOpen(false)
|
|
467
|
-
return
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
if (event.key !== "Tab") return
|
|
471
|
-
const focusable = panelRef.current
|
|
472
|
-
? Array.from(
|
|
473
|
-
panelRef.current.querySelectorAll<HTMLElement>(
|
|
474
|
-
FOCUSABLE_SELECTOR
|
|
475
|
-
)
|
|
476
|
-
)
|
|
477
|
-
: []
|
|
478
|
-
|
|
479
|
-
if (focusable.length === 0) {
|
|
480
|
-
event.preventDefault()
|
|
481
|
-
panelRef.current?.focus()
|
|
482
|
-
return
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const first = focusable[0]
|
|
486
|
-
const last = focusable[focusable.length - 1]
|
|
487
|
-
if (event.shiftKey && document.activeElement === first) {
|
|
488
|
-
event.preventDefault()
|
|
489
|
-
last.focus()
|
|
490
|
-
} else if (!event.shiftKey && document.activeElement === last) {
|
|
491
|
-
event.preventDefault()
|
|
492
|
-
first.focus()
|
|
493
|
-
}
|
|
494
|
-
}}
|
|
495
|
-
className={cn(
|
|
496
|
-
"pointer-events-auto absolute inset-y-0 flex h-dvh w-(--sidebar-width-mobile) max-w-[88vw] flex-col overflow-hidden",
|
|
497
|
-
"will-change-transform",
|
|
498
|
-
side === "left" ? "left-0 border-r" : "right-0",
|
|
499
|
-
!open && "pointer-events-none",
|
|
500
|
-
className
|
|
501
|
-
)}
|
|
502
|
-
>
|
|
503
|
-
<AnimatedSidebarPanelContext.Provider
|
|
504
|
-
value={{ collapsed: false, collapsible: "none", side }}
|
|
505
|
-
>
|
|
506
|
-
{children}
|
|
507
|
-
</AnimatedSidebarPanelContext.Provider>
|
|
508
|
-
</motion.div>
|
|
509
|
-
</div>,
|
|
510
|
-
document.body
|
|
511
|
-
)
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
export interface AnimatedSidebarProps extends Omit<
|
|
515
|
-
HTMLMotionProps<"aside">,
|
|
516
|
-
"children"
|
|
517
|
-
> {
|
|
518
|
-
children?: ReactNode
|
|
519
|
-
side?: SidebarSide
|
|
520
|
-
variant?: SidebarVariant
|
|
521
|
-
collapsible?: SidebarCollapsible
|
|
522
|
-
ariaLabel?: string
|
|
523
|
-
panelClassName?: string
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
export const AnimatedSidebar = forwardRef<HTMLElement, AnimatedSidebarProps>(
|
|
527
|
-
function AnimatedSidebar(
|
|
528
|
-
{
|
|
529
|
-
side = "left",
|
|
530
|
-
variant = "sidebar",
|
|
531
|
-
collapsible = "icon",
|
|
532
|
-
ariaLabel = "Sidebar",
|
|
533
|
-
children,
|
|
534
|
-
className,
|
|
535
|
-
panelClassName,
|
|
536
|
-
style,
|
|
537
|
-
...props
|
|
538
|
-
},
|
|
539
|
-
forwardedRef
|
|
540
|
-
) {
|
|
541
|
-
const context = useAnimatedSidebar()
|
|
542
|
-
const sideOpen = context.open[side]
|
|
543
|
-
const collapsed = collapsible !== "none" && !sideOpen
|
|
544
|
-
const offcanvas = collapsed && collapsible === "offcanvas"
|
|
545
|
-
const width = offcanvas
|
|
546
|
-
? "0px"
|
|
547
|
-
: collapsed
|
|
548
|
-
? "var(--sidebar-width-icon)"
|
|
549
|
-
: "var(--sidebar-width)"
|
|
550
|
-
|
|
551
|
-
if (context.isMobile) {
|
|
552
|
-
return (
|
|
553
|
-
<MobileSidebar ariaLabel={ariaLabel} className={className} side={side}>
|
|
554
|
-
{children}
|
|
555
|
-
</MobileSidebar>
|
|
556
|
-
)
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
return (
|
|
560
|
-
<motion.aside
|
|
561
|
-
{...props}
|
|
562
|
-
ref={forwardedRef}
|
|
563
|
-
initial={false}
|
|
564
|
-
aria-label={ariaLabel}
|
|
565
|
-
data-slot="sidebar"
|
|
566
|
-
data-state={collapsed ? "collapsed" : "expanded"}
|
|
567
|
-
data-collapsible={collapsible}
|
|
568
|
-
data-variant={variant}
|
|
569
|
-
data-side={side}
|
|
570
|
-
animate={{ width }}
|
|
571
|
-
transition={context.reduce ? { duration: 0 } : SIDEBAR_MORPH_TRANSITION}
|
|
572
|
-
style={style}
|
|
573
|
-
className={cn(
|
|
574
|
-
"group/sidebar relative hidden h-auto shrink-0 will-change-[width] md:block",
|
|
575
|
-
"peer flex!",
|
|
576
|
-
// The off canvas panel keeps its full width and slides out of the
|
|
577
|
-
// zero-width rail, so clip it rather than letting it bleed past the
|
|
578
|
-
// shell edge. The clip margin lets the panel's own shadow escape
|
|
579
|
-
// without letting the slid-out panel escape with it
|
|
580
|
-
collapsible === "offcanvas" &&
|
|
581
|
-
"overflow-x-visible [overflow-clip-margin:1rem]",
|
|
582
|
-
side === "right" && "order-last",
|
|
583
|
-
className
|
|
584
|
-
)}
|
|
585
|
-
>
|
|
586
|
-
{side === "right" && !collapsed && (
|
|
587
|
-
<motion.div className="flex h-full cursor-col-resize justify-center p-1 group relative">
|
|
588
|
-
<motion.div className="h-full w-full scale-y-0 animate-in bg-radial/decreasing from-amber-300/70 to-amber-500/0 to-40% transition-[colors,scale] duration-200 ease-smooth-out group-hover:scale-y-100 absolute" />
|
|
589
|
-
</motion.div>
|
|
590
|
-
)}
|
|
591
|
-
<motion.div
|
|
592
|
-
initial={false}
|
|
593
|
-
animate={{
|
|
594
|
-
opacity: offcanvas ? 0 : 1,
|
|
595
|
-
x: offcanvas ? (side === "left" ? "-100%" : "100%") : "0%",
|
|
596
|
-
}}
|
|
597
|
-
transition={context.reduce ? REDUCED_TRANSITION : PANEL_TRANSITION}
|
|
598
|
-
className={cn(
|
|
599
|
-
"sticky top-0 flex h-svh w-full flex-col overflow-hidden",
|
|
600
|
-
"bg-neutral-50 dark:bg-neutral-900",
|
|
601
|
-
"outline-offset-0.5 border-t-3 border-white outline-neutral-100 dark:border-neutral-800 dark:inset-ring-black",
|
|
602
|
-
"rounded-lg shadow-md shadow-neutral-200 dark:shadow-neutral-950",
|
|
603
|
-
collapsible === "offcanvas" && "w-(--sidebar-width)",
|
|
604
|
-
|
|
605
|
-
variant === "floating" && "m-2 h-[calc(100svh-1rem)]",
|
|
606
|
-
variant === "inset" && "my-2 h-[calc(100svh-1rem)]",
|
|
607
|
-
variant === "inset" &&
|
|
608
|
-
side === "right" &&
|
|
609
|
-
"w-[calc(var(--sidebar-width)-0.5rem)]",
|
|
610
|
-
variant === "inset" &&
|
|
611
|
-
side === "left" &&
|
|
612
|
-
"border-transparent! bg-transparent! shadow-none!",
|
|
613
|
-
panelClassName
|
|
614
|
-
)}
|
|
615
|
-
>
|
|
616
|
-
<AnimatedSidebarPanelContext.Provider
|
|
617
|
-
value={{ collapsed, collapsible, side }}
|
|
618
|
-
>
|
|
619
|
-
{children}
|
|
620
|
-
</AnimatedSidebarPanelContext.Provider>
|
|
621
|
-
</motion.div>
|
|
622
|
-
</motion.aside>
|
|
623
|
-
)
|
|
624
|
-
}
|
|
625
|
-
)
|
|
626
|
-
|
|
627
|
-
export interface AnimatedSidebarTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
628
|
-
side?: SidebarSide
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
export const AnimatedSidebarTrigger = forwardRef<
|
|
632
|
-
HTMLButtonElement,
|
|
633
|
-
AnimatedSidebarTriggerProps
|
|
634
|
-
>(function AnimatedSidebarTrigger(
|
|
635
|
-
{ onClick, side = "left", type = "button", ...props },
|
|
636
|
-
forwardedRef
|
|
637
|
-
) {
|
|
638
|
-
const context = useAnimatedSidebar()
|
|
639
|
-
const expanded = context.isMobile
|
|
640
|
-
? context.openMobile[side]
|
|
641
|
-
: context.open[side]
|
|
642
|
-
const triggerRef = context.triggerRefs[side]
|
|
643
|
-
|
|
644
|
-
return (
|
|
645
|
-
<Button
|
|
646
|
-
{...props}
|
|
647
|
-
ref={(node) => {
|
|
648
|
-
triggerRef.current = node
|
|
649
|
-
if (typeof forwardedRef === "function") forwardedRef(node)
|
|
650
|
-
else if (forwardedRef) forwardedRef.current = node
|
|
651
|
-
}}
|
|
652
|
-
type={type}
|
|
653
|
-
variant="ghost"
|
|
654
|
-
size="icon-xl"
|
|
655
|
-
aria-label={props["aria-label"] ?? "Toggle sidebar"}
|
|
656
|
-
aria-expanded={expanded}
|
|
657
|
-
data-slot="sidebar-trigger"
|
|
658
|
-
data-side={side}
|
|
659
|
-
data-state={expanded ? "expanded" : "collapsed"}
|
|
660
|
-
onClick={(event) => {
|
|
661
|
-
onClick?.(event)
|
|
662
|
-
if (event.defaultPrevented) return
|
|
663
|
-
context.toggleSidebar(side)
|
|
664
|
-
}}
|
|
665
|
-
/>
|
|
666
|
-
)
|
|
667
|
-
})
|
|
668
|
-
|
|
669
|
-
export interface AnimatedSidebarCloseProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
670
|
-
side?: SidebarSide
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
export const AnimatedSidebarClose = forwardRef<
|
|
674
|
-
HTMLButtonElement,
|
|
675
|
-
AnimatedSidebarCloseProps
|
|
676
|
-
>(function AnimatedSidebarClose(
|
|
677
|
-
{ className, onClick, side = "left", type = "button", ...props },
|
|
678
|
-
forwardedRef
|
|
679
|
-
) {
|
|
680
|
-
const context = useAnimatedSidebar()
|
|
681
|
-
|
|
682
|
-
return (
|
|
683
|
-
<button
|
|
684
|
-
{...props}
|
|
685
|
-
ref={forwardedRef}
|
|
686
|
-
type={type}
|
|
687
|
-
data-side={side}
|
|
688
|
-
aria-label={props["aria-label"] ?? "Close sidebar"}
|
|
689
|
-
onClick={(event) => {
|
|
690
|
-
onClick?.(event)
|
|
691
|
-
if (event.defaultPrevented) return
|
|
692
|
-
if (context.isMobile) context.setOpenMobile(false, side)
|
|
693
|
-
else context.setOpen(false, side)
|
|
694
|
-
}}
|
|
695
|
-
className={cn(
|
|
696
|
-
"inline-flex size-10 shrink-0 items-center justify-center rounded-xl outline-none",
|
|
697
|
-
"focus-visible:ring-2 focus-visible:ring-ring",
|
|
698
|
-
className
|
|
699
|
-
)}
|
|
700
|
-
/>
|
|
701
|
-
)
|
|
702
|
-
})
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
export interface AnimatedSidebarInsetProps extends HTMLMotionProps<"main"> {}
|
|
706
|
-
|
|
707
|
-
export const AnimatedSidebarInset = forwardRef<
|
|
708
|
-
HTMLElement,
|
|
709
|
-
AnimatedSidebarInsetProps
|
|
710
|
-
>(function AnimatedSidebarInset({ className, ...props }, forwardedRef) {
|
|
711
|
-
return (
|
|
712
|
-
<motion.main
|
|
713
|
-
{...props}
|
|
714
|
-
ref={forwardedRef}
|
|
715
|
-
data-slot="sidebar-inset"
|
|
716
|
-
className={cn(
|
|
717
|
-
"relative flex min-w-0 flex-1 flex-col bg-neutral-50 dark:bg-neutral-900",
|
|
718
|
-
"outline-offset-0.5 overflow-hidden border-t-3 border-white outline-neutral-100 dark:border-neutral-800 dark:inset-ring-black",
|
|
719
|
-
"m-2 mx-0 rounded-lg shadow-md shadow-neutral-200 dark:shadow-neutral-950",
|
|
720
|
-
className
|
|
721
|
-
)}
|
|
722
|
-
/>
|
|
723
|
-
)
|
|
724
|
-
})
|
|
725
|
-
|
|
726
|
-
export const AnimatedSidebarHeader = forwardRef<
|
|
727
|
-
HTMLDivElement,
|
|
728
|
-
HTMLAttributes<HTMLDivElement>
|
|
729
|
-
>(function AnimatedSidebarHeader({ className, ...props }, forwardedRef) {
|
|
730
|
-
return (
|
|
731
|
-
<div
|
|
732
|
-
{...props}
|
|
733
|
-
ref={forwardedRef}
|
|
734
|
-
data-slot="sidebar-header"
|
|
735
|
-
className={cn("flex shrink-0 flex-col gap-2 p-3", className)}
|
|
736
|
-
/>
|
|
737
|
-
)
|
|
738
|
-
})
|
|
739
|
-
|
|
740
|
-
export const AnimatedSidebarContent = forwardRef<
|
|
741
|
-
HTMLDivElement,
|
|
742
|
-
HTMLAttributes<HTMLDivElement>
|
|
743
|
-
>(function AnimatedSidebarContent({ className, ...props }, forwardedRef) {
|
|
744
|
-
return (
|
|
745
|
-
<div
|
|
746
|
-
{...props}
|
|
747
|
-
ref={forwardedRef}
|
|
748
|
-
data-slot="sidebar-content"
|
|
749
|
-
className={cn(
|
|
750
|
-
"flex min-h-0 flex-1 flex-col gap-2 overflow-x-hidden overflow-y-auto overscroll-contain px-2 py-2",
|
|
751
|
-
className
|
|
752
|
-
)}
|
|
753
|
-
/>
|
|
754
|
-
)
|
|
755
|
-
})
|
|
756
|
-
|
|
757
|
-
export const AnimatedSidebarFooter = forwardRef<
|
|
758
|
-
HTMLDivElement,
|
|
759
|
-
HTMLAttributes<HTMLDivElement>
|
|
760
|
-
>(function AnimatedSidebarFooter({ className, ...props }, forwardedRef) {
|
|
761
|
-
return (
|
|
762
|
-
<div
|
|
763
|
-
{...props}
|
|
764
|
-
ref={forwardedRef}
|
|
765
|
-
data-slot="sidebar-footer"
|
|
766
|
-
className={cn(
|
|
767
|
-
"flex shrink-0 flex-col gap-2 border-t border-border p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))]",
|
|
768
|
-
className
|
|
769
|
-
)}
|
|
770
|
-
/>
|
|
771
|
-
)
|
|
772
|
-
})
|
|
773
|
-
|
|
774
|
-
export const AnimatedSidebarGroup = forwardRef<
|
|
775
|
-
HTMLDivElement,
|
|
776
|
-
HTMLAttributes<HTMLDivElement>
|
|
777
|
-
>(function AnimatedSidebarGroup({ className, ...props }, forwardedRef) {
|
|
778
|
-
return (
|
|
779
|
-
<div
|
|
780
|
-
{...props}
|
|
781
|
-
ref={forwardedRef}
|
|
782
|
-
data-slot="sidebar-group"
|
|
783
|
-
className={cn("flex w-full min-w-0 flex-col px-1 py-1.5", className)}
|
|
784
|
-
/>
|
|
785
|
-
)
|
|
786
|
-
})
|
|
787
|
-
|
|
788
|
-
export const AnimatedSidebarGroupLabel = forwardRef<
|
|
789
|
-
HTMLDivElement,
|
|
790
|
-
HTMLAttributes<HTMLDivElement>
|
|
791
|
-
>(function AnimatedSidebarGroupLabel(
|
|
792
|
-
{ children, className, ...props },
|
|
793
|
-
forwardedRef
|
|
794
|
-
) {
|
|
795
|
-
const { collapsed } = useAnimatedSidebarPanel()
|
|
796
|
-
|
|
797
|
-
return (
|
|
798
|
-
<div
|
|
799
|
-
{...props}
|
|
800
|
-
ref={forwardedRef}
|
|
801
|
-
aria-hidden={collapsed}
|
|
802
|
-
data-slot="sidebar-group-label"
|
|
803
|
-
className={cn(
|
|
804
|
-
"mb-1 h-7 overflow-hidden px-2 text-[10px] font-medium tracking-[0.14em] text-muted-foreground uppercase transition-opacity",
|
|
805
|
-
collapsed ? "opacity-0" : "opacity-100",
|
|
806
|
-
className
|
|
807
|
-
)}
|
|
808
|
-
>
|
|
809
|
-
{children}
|
|
810
|
-
</div>
|
|
811
|
-
)
|
|
812
|
-
})
|
|
813
|
-
|
|
814
|
-
export const AnimatedSidebarGroupContent = forwardRef<
|
|
815
|
-
HTMLDivElement,
|
|
816
|
-
HTMLAttributes<HTMLDivElement>
|
|
817
|
-
>(function AnimatedSidebarGroupContent({ className, ...props }, forwardedRef) {
|
|
818
|
-
return (
|
|
819
|
-
<div
|
|
820
|
-
{...props}
|
|
821
|
-
ref={forwardedRef}
|
|
822
|
-
data-slot="sidebar-group-content"
|
|
823
|
-
className={cn("w-full min-w-0", className)}
|
|
824
|
-
/>
|
|
825
|
-
)
|
|
826
|
-
})
|
|
827
|
-
|
|
828
|
-
export const AnimatedSidebarMenu = forwardRef<
|
|
829
|
-
HTMLUListElement,
|
|
830
|
-
HTMLAttributes<HTMLUListElement>
|
|
831
|
-
>(function AnimatedSidebarMenu(
|
|
832
|
-
{ children, className, ...props },
|
|
833
|
-
forwardedRef
|
|
834
|
-
) {
|
|
835
|
-
return (
|
|
836
|
-
<SharedLayoutBg
|
|
837
|
-
{...props}
|
|
838
|
-
ref={forwardedRef as React.Ref<HTMLElement>}
|
|
839
|
-
as="ul"
|
|
840
|
-
inset={0}
|
|
841
|
-
pillClassName="rounded-xl bg-muted/70"
|
|
842
|
-
pillContainerClassName="inset-y-auto top-0 h-9"
|
|
843
|
-
data-slot="sidebar-menu"
|
|
844
|
-
className={cn(
|
|
845
|
-
"flex w-full min-w-0 list-none flex-col gap-0.5",
|
|
846
|
-
className
|
|
847
|
-
)}
|
|
848
|
-
>
|
|
849
|
-
{children}
|
|
850
|
-
</SharedLayoutBg>
|
|
851
|
-
)
|
|
852
|
-
})
|
|
853
|
-
|
|
854
|
-
export const AnimatedSidebarMenuItem = forwardRef<
|
|
855
|
-
HTMLLIElement,
|
|
856
|
-
HTMLMotionProps<"li">
|
|
857
|
-
>(function AnimatedSidebarMenuItem({ className, ...props }, forwardedRef) {
|
|
858
|
-
return (
|
|
859
|
-
<motion.li
|
|
860
|
-
{...props}
|
|
861
|
-
ref={forwardedRef}
|
|
862
|
-
layout="position"
|
|
863
|
-
transition={SPRING_LAYOUT}
|
|
864
|
-
data-slot="sidebar-menu-item"
|
|
865
|
-
className={cn("relative", className)}
|
|
866
|
-
/>
|
|
867
|
-
)
|
|
868
|
-
})
|
|
869
|
-
|
|
870
|
-
export interface AnimatedSidebarMenuSubProps extends Omit<
|
|
871
|
-
HTMLMotionProps<"ul">,
|
|
872
|
-
"children"
|
|
873
|
-
> {
|
|
874
|
-
open: boolean
|
|
875
|
-
children?: ReactNode
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
export const AnimatedSidebarMenuSub = forwardRef<
|
|
879
|
-
HTMLUListElement,
|
|
880
|
-
AnimatedSidebarMenuSubProps
|
|
881
|
-
>(function AnimatedSidebarMenuSub(
|
|
882
|
-
{ open, children, className, ...props },
|
|
883
|
-
forwardedRef
|
|
884
|
-
) {
|
|
885
|
-
const context = useAnimatedSidebar()
|
|
886
|
-
const panel = useAnimatedSidebarPanel()
|
|
887
|
-
|
|
888
|
-
return (
|
|
889
|
-
<AnimatePresence initial={false} mode="popLayout">
|
|
890
|
-
{open && !panel.collapsed ? (
|
|
891
|
-
<motion.ul
|
|
892
|
-
{...props}
|
|
893
|
-
ref={forwardedRef}
|
|
894
|
-
key="sidebar-submenu"
|
|
895
|
-
variants={context.reduce ? undefined : SUBMENU_VARIANTS}
|
|
896
|
-
initial={context.reduce ? false : "closed"}
|
|
897
|
-
animate={context.reduce ? { opacity: 1 } : "open"}
|
|
898
|
-
exit={context.reduce ? { opacity: 0 } : "closed"}
|
|
899
|
-
transition={context.reduce ? { duration: 0.12 } : undefined}
|
|
900
|
-
data-slot="sidebar-menu-sub"
|
|
901
|
-
className={cn(
|
|
902
|
-
"relative mt-1 ml-5 flex min-w-0 flex-col gap-0.5 border-l border-border pl-3",
|
|
903
|
-
className
|
|
904
|
-
)}
|
|
905
|
-
>
|
|
906
|
-
{children}
|
|
907
|
-
</motion.ul>
|
|
908
|
-
) : null}
|
|
909
|
-
</AnimatePresence>
|
|
910
|
-
)
|
|
911
|
-
})
|
|
912
|
-
|
|
913
|
-
export const AnimatedSidebarMenuSubItem = forwardRef<
|
|
914
|
-
HTMLLIElement,
|
|
915
|
-
HTMLMotionProps<"li">
|
|
916
|
-
>(function AnimatedSidebarMenuSubItem({ className, ...props }, forwardedRef) {
|
|
917
|
-
return (
|
|
918
|
-
<motion.li
|
|
919
|
-
{...props}
|
|
920
|
-
ref={forwardedRef}
|
|
921
|
-
variants={SUBMENU_ITEM_VARIANTS}
|
|
922
|
-
data-slot="sidebar-menu-sub-item"
|
|
923
|
-
className={cn("relative min-w-0", className)}
|
|
924
|
-
/>
|
|
925
|
-
)
|
|
926
|
-
})
|
|
927
|
-
|
|
928
|
-
export interface AnimatedSidebarMenuSubButtonProps {
|
|
929
|
-
children: ReactNode
|
|
930
|
-
icon?: ReactNode
|
|
931
|
-
href?: string
|
|
932
|
-
isActive?: boolean
|
|
933
|
-
disabled?: boolean
|
|
934
|
-
closeOnSelect?: boolean
|
|
935
|
-
target?: "_blank" | "_self" | "_parent" | "_top"
|
|
936
|
-
rel?: string
|
|
937
|
-
onSelect?: () => void
|
|
938
|
-
className?: string
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
export function AnimatedSidebarMenuSubButton({
|
|
942
|
-
children,
|
|
943
|
-
icon,
|
|
944
|
-
href,
|
|
945
|
-
isActive = false,
|
|
946
|
-
disabled = false,
|
|
947
|
-
closeOnSelect = true,
|
|
948
|
-
target,
|
|
949
|
-
rel,
|
|
950
|
-
onSelect,
|
|
951
|
-
className,
|
|
952
|
-
}: AnimatedSidebarMenuSubButtonProps) {
|
|
953
|
-
const context = useAnimatedSidebar()
|
|
954
|
-
const panel = useAnimatedSidebarPanel()
|
|
955
|
-
|
|
956
|
-
const select = (
|
|
957
|
-
event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>
|
|
958
|
-
) => {
|
|
959
|
-
if (disabled) {
|
|
960
|
-
event.preventDefault()
|
|
961
|
-
return
|
|
962
|
-
}
|
|
963
|
-
onSelect?.()
|
|
964
|
-
if (context.isMobile && closeOnSelect) {
|
|
965
|
-
context.setOpenMobile(false, panel.side)
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
const content = (
|
|
970
|
-
<>
|
|
971
|
-
<span
|
|
972
|
-
aria-hidden="true"
|
|
973
|
-
className="grid size-4 shrink-0 place-items-center"
|
|
974
|
-
>
|
|
975
|
-
{icon ?? <span className="size-1 rounded-full bg-current" />}
|
|
976
|
-
</span>
|
|
977
|
-
<span className="min-w-0 flex-1 truncate">{children}</span>
|
|
978
|
-
</>
|
|
979
|
-
)
|
|
980
|
-
|
|
981
|
-
const interactiveClassName = cn(
|
|
982
|
-
"flex min-h-8 w-full min-w-0 items-center gap-2 rounded-lg px-2 text-left text-xs outline-none",
|
|
983
|
-
"text-muted-foreground transition-colors hover:bg-muted/60 hover:text-foreground",
|
|
984
|
-
"focus-visible:bg-muted/70 focus-visible:ring-2 focus-visible:ring-ring",
|
|
985
|
-
isActive && "bg-muted/70 text-foreground",
|
|
986
|
-
disabled && "cursor-not-allowed opacity-40",
|
|
987
|
-
className
|
|
988
|
-
)
|
|
989
|
-
|
|
990
|
-
return href ? (
|
|
991
|
-
<motion.a
|
|
992
|
-
href={href}
|
|
993
|
-
target={target}
|
|
994
|
-
rel={rel ?? (target === "_blank" ? "noreferrer noopener" : undefined)}
|
|
995
|
-
aria-current={isActive ? "page" : undefined}
|
|
996
|
-
aria-disabled={disabled || undefined}
|
|
997
|
-
tabIndex={disabled ? -1 : undefined}
|
|
998
|
-
onClick={select}
|
|
999
|
-
whileTap={context.reduce || disabled ? undefined : { scale: 0.98 }}
|
|
1000
|
-
transition={SPRING_PRESS}
|
|
1001
|
-
className={interactiveClassName}
|
|
1002
|
-
>
|
|
1003
|
-
{content}
|
|
1004
|
-
</motion.a>
|
|
1005
|
-
) : (
|
|
1006
|
-
<motion.button
|
|
1007
|
-
type="button"
|
|
1008
|
-
disabled={disabled}
|
|
1009
|
-
aria-current={isActive ? "page" : undefined}
|
|
1010
|
-
onClick={select}
|
|
1011
|
-
whileTap={context.reduce || disabled ? undefined : { scale: 0.98 }}
|
|
1012
|
-
transition={SPRING_PRESS}
|
|
1013
|
-
className={interactiveClassName}
|
|
1014
|
-
>
|
|
1015
|
-
{content}
|
|
1016
|
-
</motion.button>
|
|
1017
|
-
)
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
export interface AnimatedSidebarMenuButtonProps {
|
|
1021
|
-
children: ReactNode
|
|
1022
|
-
icon?: ReactNode
|
|
1023
|
-
badge?: ReactNode
|
|
1024
|
-
href?: string
|
|
1025
|
-
isActive?: boolean
|
|
1026
|
-
ariaExpanded?: boolean
|
|
1027
|
-
disabled?: boolean
|
|
1028
|
-
closeOnSelect?: boolean
|
|
1029
|
-
target?: "_blank" | "_self" | "_parent" | "_top"
|
|
1030
|
-
rel?: string
|
|
1031
|
-
onSelect?: () => void
|
|
1032
|
-
className?: string
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
export function AnimatedSidebarMenuButton({
|
|
1036
|
-
children,
|
|
1037
|
-
icon,
|
|
1038
|
-
badge,
|
|
1039
|
-
href,
|
|
1040
|
-
isActive = false,
|
|
1041
|
-
ariaExpanded,
|
|
1042
|
-
disabled = false,
|
|
1043
|
-
closeOnSelect,
|
|
1044
|
-
target,
|
|
1045
|
-
rel,
|
|
1046
|
-
onSelect,
|
|
1047
|
-
className,
|
|
1048
|
-
}: AnimatedSidebarMenuButtonProps) {
|
|
1049
|
-
const context = useAnimatedSidebar()
|
|
1050
|
-
const panel = useAnimatedSidebarPanel()
|
|
1051
|
-
const textLabel = typeof children === "string" ? children : undefined
|
|
1052
|
-
|
|
1053
|
-
const select = (
|
|
1054
|
-
event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>
|
|
1055
|
-
) => {
|
|
1056
|
-
if (disabled) {
|
|
1057
|
-
event.preventDefault()
|
|
1058
|
-
return
|
|
1059
|
-
}
|
|
1060
|
-
onSelect?.()
|
|
1061
|
-
const shouldCloseOnSelect = closeOnSelect ?? ariaExpanded === undefined
|
|
1062
|
-
if (context.isMobile && shouldCloseOnSelect) {
|
|
1063
|
-
context.setOpenMobile(false, panel.side)
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
const content = (
|
|
1068
|
-
<>
|
|
1069
|
-
{isActive ? (
|
|
1070
|
-
<motion.span
|
|
1071
|
-
layoutId={context.layoutId}
|
|
1072
|
-
transition={context.reduce ? { duration: 0 } : SPRING_LAYOUT}
|
|
1073
|
-
className="absolute inset-0 rounded-xl bg-muted"
|
|
1074
|
-
/>
|
|
1075
|
-
) : null}
|
|
1076
|
-
{icon ? (
|
|
1077
|
-
<span
|
|
1078
|
-
aria-hidden="true"
|
|
1079
|
-
className="relative z-10 grid size-5 shrink-0 place-items-center"
|
|
1080
|
-
>
|
|
1081
|
-
{icon}
|
|
1082
|
-
</span>
|
|
1083
|
-
) : null}
|
|
1084
|
-
<motion.span
|
|
1085
|
-
initial={false}
|
|
1086
|
-
animate={{
|
|
1087
|
-
opacity: panel.collapsed ? 0 : 1,
|
|
1088
|
-
x: panel.collapsed ? -4 : 0,
|
|
1089
|
-
}}
|
|
1090
|
-
transition={
|
|
1091
|
-
context.reduce
|
|
1092
|
-
? REDUCED_TRANSITION
|
|
1093
|
-
: panel.collapsed
|
|
1094
|
-
? LABEL_EXIT_TRANSITION
|
|
1095
|
-
: LABEL_ENTER_TRANSITION
|
|
1096
|
-
}
|
|
1097
|
-
aria-hidden={panel.collapsed}
|
|
1098
|
-
className={cn(
|
|
1099
|
-
"relative z-10 min-w-0 flex-1 truncate",
|
|
1100
|
-
panel.collapsed && "pointer-events-none"
|
|
1101
|
-
)}
|
|
1102
|
-
>
|
|
1103
|
-
{children}
|
|
1104
|
-
</motion.span>
|
|
1105
|
-
{badge && !panel.collapsed ? (
|
|
1106
|
-
<span className="relative z-10 shrink-0 text-xs text-muted-foreground">
|
|
1107
|
-
{badge}
|
|
1108
|
-
</span>
|
|
1109
|
-
) : null}
|
|
1110
|
-
{ariaExpanded !== undefined ? (
|
|
1111
|
-
<motion.span
|
|
1112
|
-
aria-hidden="true"
|
|
1113
|
-
initial={false}
|
|
1114
|
-
animate={{
|
|
1115
|
-
opacity: panel.collapsed ? 0 : 1,
|
|
1116
|
-
rotate: ariaExpanded ? 90 : 0,
|
|
1117
|
-
x: panel.collapsed ? 4 : 0,
|
|
1118
|
-
}}
|
|
1119
|
-
transition={context.reduce ? { duration: 0 } : SPRING_LAYOUT}
|
|
1120
|
-
className="relative z-10 grid size-4 shrink-0 place-items-center text-muted-foreground"
|
|
1121
|
-
>
|
|
1122
|
-
<ChevronRight className="size-3.5" />
|
|
1123
|
-
</motion.span>
|
|
1124
|
-
) : null}
|
|
1125
|
-
</>
|
|
1126
|
-
)
|
|
1127
|
-
|
|
1128
|
-
const interactiveClassName = cn(
|
|
1129
|
-
"relative flex min-h-9 w-full min-w-0 items-center gap-2.5 overflow-hidden rounded-xl px-3 text-left text-sm font-medium outline-none",
|
|
1130
|
-
"text-muted-foreground transition-colors hover:text-foreground",
|
|
1131
|
-
"focus-visible:bg-muted/70 focus-visible:ring-2 focus-visible:ring-ring",
|
|
1132
|
-
isActive && "text-foreground",
|
|
1133
|
-
disabled && "cursor-not-allowed opacity-40",
|
|
1134
|
-
className
|
|
1135
|
-
)
|
|
1136
|
-
|
|
1137
|
-
return href ? (
|
|
1138
|
-
<motion.a
|
|
1139
|
-
href={href}
|
|
1140
|
-
target={target}
|
|
1141
|
-
rel={rel ?? (target === "_blank" ? "noreferrer noopener" : undefined)}
|
|
1142
|
-
aria-current={isActive ? "page" : undefined}
|
|
1143
|
-
aria-expanded={ariaExpanded}
|
|
1144
|
-
aria-disabled={disabled || undefined}
|
|
1145
|
-
aria-label={panel.collapsed ? textLabel : undefined}
|
|
1146
|
-
title={panel.collapsed ? textLabel : undefined}
|
|
1147
|
-
tabIndex={disabled ? -1 : undefined}
|
|
1148
|
-
onClick={select}
|
|
1149
|
-
whileTap={context.reduce || disabled ? undefined : { scale: 0.98 }}
|
|
1150
|
-
transition={SPRING_PRESS}
|
|
1151
|
-
className={interactiveClassName}
|
|
1152
|
-
>
|
|
1153
|
-
{content}
|
|
1154
|
-
</motion.a>
|
|
1155
|
-
) : (
|
|
1156
|
-
<motion.button
|
|
1157
|
-
type="button"
|
|
1158
|
-
disabled={disabled}
|
|
1159
|
-
aria-current={isActive ? "page" : undefined}
|
|
1160
|
-
aria-expanded={ariaExpanded}
|
|
1161
|
-
aria-label={panel.collapsed ? textLabel : undefined}
|
|
1162
|
-
title={panel.collapsed ? textLabel : undefined}
|
|
1163
|
-
onClick={select}
|
|
1164
|
-
whileTap={context.reduce || disabled ? undefined : { scale: 0.98 }}
|
|
1165
|
-
transition={SPRING_PRESS}
|
|
1166
|
-
className={interactiveClassName}
|
|
1167
|
-
>
|
|
1168
|
-
{content}
|
|
1169
|
-
</motion.button>
|
|
1170
|
-
)
|
|
1171
|
-
}
|