@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.
Files changed (112) hide show
  1. package/dist-lib/components/blocks/nav/sidebar-block.d.ts +1 -1
  2. package/dist-lib/components/blocks/scaffold/__tests__/scaffold.test.d.ts +1 -0
  3. package/dist-lib/components/blocks/scaffold/constants.d.ts +20 -0
  4. package/dist-lib/components/blocks/scaffold/index.d.ts +25 -0
  5. package/dist-lib/components/blocks/scaffold/panel-close-button.d.ts +4 -0
  6. package/dist-lib/components/blocks/scaffold/panel-menu-button.d.ts +7 -0
  7. package/dist-lib/components/blocks/scaffold/scaffold-actions.d.ts +9 -0
  8. package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +7 -0
  9. package/dist-lib/components/blocks/scaffold/scaffold-context.d.ts +5 -0
  10. package/dist-lib/components/blocks/scaffold/scaffold-inspector.d.ts +9 -0
  11. package/dist-lib/components/blocks/scaffold/scaffold-main.d.ts +8 -0
  12. package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +10 -0
  13. package/dist-lib/components/blocks/scaffold/scaffold-root.d.ts +8 -0
  14. package/dist-lib/components/blocks/scaffold/scaffold-sidebar.d.ts +7 -0
  15. package/dist-lib/components/blocks/scaffold/scaffold-sub-panel.d.ts +3 -0
  16. package/dist-lib/components/blocks/scaffold/type.d.ts +133 -0
  17. package/dist-lib/components/blocks/scaffold/use-scaffold.d.ts +8 -0
  18. package/dist-lib/components/blocks/scaffold/utils.d.ts +15 -0
  19. package/dist-lib/components/blocks/shell/animated-panel-inset.d.ts +2 -0
  20. package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +2 -0
  21. package/dist-lib/components/blocks/shell/animated-panel-trigger.d.ts +2 -0
  22. package/dist-lib/components/blocks/shell/animated-panel.d.ts +2 -0
  23. package/dist-lib/components/blocks/shell/animated-sidebar-content.d.ts +2 -0
  24. package/dist-lib/components/blocks/shell/animated-sidebar-footer.d.ts +2 -0
  25. package/dist-lib/components/blocks/shell/animated-sidebar-group-content.d.ts +2 -0
  26. package/dist-lib/components/blocks/shell/animated-sidebar-group-label.d.ts +2 -0
  27. package/dist-lib/components/blocks/shell/animated-sidebar-group.d.ts +2 -0
  28. package/dist-lib/components/blocks/shell/animated-sidebar-header.d.ts +2 -0
  29. package/dist-lib/components/blocks/shell/animated-sidebar-menu-button.d.ts +3 -0
  30. package/dist-lib/components/blocks/shell/animated-sidebar-menu-item.d.ts +2 -0
  31. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-button.d.ts +3 -0
  32. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub-item.d.ts +2 -0
  33. package/dist-lib/components/blocks/shell/animated-sidebar-menu-sub.d.ts +2 -0
  34. package/dist-lib/components/blocks/shell/animated-sidebar-menu.d.ts +2 -0
  35. package/dist-lib/components/blocks/shell/index.d.ts +16 -0
  36. package/dist-lib/components/blocks/shell/type.d.ts +90 -4
  37. package/dist-lib/components/blocks/shell/utils.d.ts +37 -0
  38. package/dist-lib/components/motion/index.d.ts +1 -0
  39. package/dist-lib/components/motion/overflow-actions.d.ts +35 -0
  40. package/dist-lib/components/ui/button-group.d.ts +11 -0
  41. package/dist-lib/components/ui/glasscn/glass-button-group.d.ts +5 -0
  42. package/dist-lib/components/ui/glasscn/glass-container.d.ts +7 -0
  43. package/dist-lib/components/ui/glasscn/glass-separator.d.ts +5 -0
  44. package/dist-lib/components/ui/group.d.ts +17 -0
  45. package/dist-lib/components/ui/separator.d.ts +2 -3
  46. package/dist-lib/components/ui/sheet.d.ts +14 -0
  47. package/dist-lib/components/ui/tooltip.d.ts +6 -0
  48. package/dist-lib/index.d.ts +2 -1
  49. package/dist-lib/index.js +1592 -1084
  50. package/dist-lib/index.js.map +1 -1
  51. package/dist-lib/lib/ease.d.ts +3 -0
  52. package/dist-lib/lib/hooks/use-hover-capable.d.ts +6 -0
  53. package/package.json +2 -1
  54. package/src/components/beste/piece/browser-frame.tsx +2 -2
  55. package/src/components/blocks/nav/sidebar-block.tsx +5 -5
  56. package/src/components/blocks/nav/sidebar-nav-row.tsx +4 -5
  57. package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +203 -0
  58. package/src/components/blocks/scaffold/constants.ts +28 -0
  59. package/src/components/blocks/scaffold/index.ts +38 -0
  60. package/src/components/blocks/scaffold/panel-close-button.tsx +36 -0
  61. package/src/components/blocks/scaffold/panel-menu-button.tsx +240 -0
  62. package/src/components/blocks/scaffold/scaffold-actions.tsx +97 -0
  63. package/src/components/blocks/scaffold/scaffold-canvas.tsx +29 -0
  64. package/src/components/blocks/scaffold/scaffold-context.ts +18 -0
  65. package/src/components/blocks/scaffold/scaffold-inspector.tsx +57 -0
  66. package/src/components/blocks/scaffold/scaffold-main.tsx +28 -0
  67. package/src/components/blocks/scaffold/scaffold-panel.tsx +98 -0
  68. package/src/components/blocks/scaffold/scaffold-root.tsx +68 -0
  69. package/src/components/blocks/scaffold/scaffold-sidebar.tsx +31 -0
  70. package/src/components/blocks/scaffold/scaffold-sub-panel.tsx +51 -0
  71. package/src/components/blocks/scaffold/type.ts +171 -0
  72. package/src/components/blocks/scaffold/use-scaffold.ts +48 -0
  73. package/src/components/blocks/scaffold/utils.ts +30 -0
  74. package/src/components/blocks/shell/animated-panel-inset.tsx +23 -0
  75. package/src/components/blocks/shell/animated-panel-provider.tsx +155 -0
  76. package/src/components/blocks/shell/animated-panel-trigger.tsx +42 -0
  77. package/src/components/blocks/shell/animated-panel.tsx +135 -0
  78. package/src/components/blocks/shell/animated-sidebar-content.tsx +19 -0
  79. package/src/components/blocks/shell/animated-sidebar-footer.tsx +19 -0
  80. package/src/components/blocks/shell/animated-sidebar-group-content.tsx +16 -0
  81. package/src/components/blocks/shell/animated-sidebar-group-label.tsx +29 -0
  82. package/src/components/blocks/shell/animated-sidebar-group.tsx +16 -0
  83. package/src/components/blocks/shell/animated-sidebar-header.tsx +16 -0
  84. package/src/components/blocks/shell/animated-sidebar-menu-button.tsx +151 -0
  85. package/src/components/blocks/shell/animated-sidebar-menu-item.tsx +20 -0
  86. package/src/components/blocks/shell/animated-sidebar-menu-sub-button.tsx +85 -0
  87. package/src/components/blocks/shell/animated-sidebar-menu-sub-item.tsx +19 -0
  88. package/src/components/blocks/shell/animated-sidebar-menu-sub.tsx +44 -0
  89. package/src/components/blocks/shell/animated-sidebar-menu.tsx +26 -0
  90. package/src/components/blocks/shell/index.ts +18 -0
  91. package/src/components/blocks/shell/shell-layout.tsx +28 -36
  92. package/src/components/blocks/shell/type.ts +112 -6
  93. package/src/components/blocks/shell/use-shell-panels.ts +1 -1
  94. package/src/components/blocks/shell/utils.ts +142 -1
  95. package/src/components/docs/demo-controls.tsx +1 -1
  96. package/src/components/motion/index.ts +0 -0
  97. package/src/components/motion/overflow-actions.tsx +376 -0
  98. package/src/components/ui/button-group.tsx +88 -0
  99. package/src/components/ui/glasscn/glass-button-group.tsx +48 -0
  100. package/src/components/ui/glasscn/glass-container.tsx +125 -0
  101. package/src/components/ui/glasscn/glass-separator.tsx +39 -0
  102. package/src/components/ui/glasscn/liquid-glass.tsx +250 -210
  103. package/src/components/ui/group.tsx +92 -0
  104. package/src/components/ui/separator.tsx +11 -11
  105. package/src/components/ui/sheet.tsx +135 -0
  106. package/src/components/ui/tooltip.tsx +66 -0
  107. package/src/index.css +1 -1
  108. package/src/index.ts +13 -1
  109. package/src/lib/ease.ts +26 -0
  110. package/src/lib/hooks/use-hover-capable.ts +23 -0
  111. package/dist-lib/components/motion/animated-sidebar.d.ts +0 -106
  112. 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
- }