@dalexto/lexsys-registry 0.1.1 → 0.1.3

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 (39) hide show
  1. package/dist/index.js +12 -3
  2. package/package.json +2 -2
  3. package/templates/blocks/AuthForm/AuthForm.tsx +9 -5
  4. package/templates/blocks/AuthForm/AuthForm.types.ts +3 -3
  5. package/templates/blocks/AuthForm/AuthForm.variants.ts +8 -2
  6. package/templates/blocks/CommandPalette/CommandPalette.tsx +15 -12
  7. package/templates/blocks/CommandPalette/CommandPalette.types.ts +2 -2
  8. package/templates/blocks/CommandPalette/CommandPalette.variants.ts +6 -6
  9. package/templates/blocks/DataTable/DataTable.tsx +2 -2
  10. package/templates/blocks/DataTable/DataTable.types.ts +2 -2
  11. package/templates/blocks/DataTable/DataTable.variants.ts +5 -4
  12. package/templates/blocks/FilterToolbar/FilterToolbar.tsx +12 -5
  13. package/templates/blocks/FilterToolbar/FilterToolbar.types.ts +4 -4
  14. package/templates/blocks/FilterToolbar/FilterToolbar.variants.ts +11 -5
  15. package/templates/blocks/FormField/FormField.tsx +1 -1
  16. package/templates/blocks/FormField/FormField.types.ts +1 -1
  17. package/templates/blocks/FormField/FormField.variants.ts +1 -1
  18. package/templates/blocks/PageHeader/PageHeader.tsx +2 -2
  19. package/templates/blocks/PageHeader/PageHeader.types.ts +2 -2
  20. package/templates/blocks/PageHeader/PageHeader.variants.ts +6 -5
  21. package/templates/blocks/SettingsPanel/SettingsPanel.tsx +1 -1
  22. package/templates/blocks/SettingsPanel/SettingsPanel.types.ts +1 -1
  23. package/templates/blocks/Sidebar/Sidebar.tsx +1048 -22
  24. package/templates/blocks/Sidebar/Sidebar.types.ts +185 -1
  25. package/templates/blocks/Sidebar/Sidebar.utils.ts +34 -0
  26. package/templates/blocks/Sidebar/Sidebar.variants.ts +445 -25
  27. package/templates/blocks/StatsCard/StatsCard.tsx +1 -1
  28. package/templates/blocks/StatsCard/StatsCard.types.ts +1 -1
  29. package/templates/blocks/StatsCard/StatsCard.variants.ts +7 -6
  30. package/templates/primitives/DatePicker/DatePicker.tsx +11 -1
  31. package/templates/primitives/DatePicker/DatePicker.types.ts +4 -1
  32. package/templates/primitives/DatePicker/DatePicker.variants.ts +11 -2
  33. package/templates/primitives/Toolbar/Toolbar.variants.ts +4 -2
  34. package/templates/styles/theme.css +14 -1
  35. package/templates/styles/tokens.css +147 -29
  36. package/templates/templates/DashboardShell/DashboardShell.variants.ts +19 -4
  37. package/templates/templates/SettingsPageLayout/SettingsPageLayout.tsx +2 -2
  38. package/templates/templates/SettingsPageLayout/SettingsPageLayout.types.ts +2 -2
  39. package/templates/templates/SettingsPageLayout/SettingsPageLayout.variants.ts +16 -7
@@ -4,8 +4,21 @@
4
4
  * Reference Sidebar block — compound navigation shell with desktop and mobile drawer.
5
5
  */
6
6
 
7
- import { createContext, useContext } from "react"
8
- import { Button } from "@/components/primitives/Button"
7
+ import {
8
+ Children,
9
+ createContext,
10
+ isValidElement,
11
+ useCallback,
12
+ useContext,
13
+ useMemo,
14
+ useState,
15
+ useSyncExternalStore,
16
+ type KeyboardEvent,
17
+ type MouseEventHandler,
18
+ type ReactNode,
19
+ } from "react"
20
+ import { Badge } from "@/components/primitives/Badge/Badge"
21
+ import { Button } from "@/components/primitives/Button/Button"
9
22
  import {
10
23
  Drawer,
11
24
  DrawerBackdrop,
@@ -17,44 +30,265 @@ import {
17
30
  DrawerTitle,
18
31
  DrawerTrigger,
19
32
  DrawerViewport,
20
- } from "@/components/primitives/Drawer"
33
+ } from "@/components/primitives/Drawer/Drawer"
34
+ import { Collapsible as BaseCollapsible } from "@base-ui/react/collapsible"
35
+ import { ChevronDown } from "lucide-react"
36
+ import {
37
+ Collapsible,
38
+ CollapsiblePanel,
39
+ } from "@/components/primitives/Collapsible/Collapsible"
40
+ import { Input } from "@/components/primitives/Input/Input"
41
+ import { Separator } from "@/components/primitives/Separator/Separator"
21
42
  import {
22
43
  ScrollArea,
23
44
  ScrollAreaContent,
24
45
  ScrollAreaViewport,
25
- } from "@/components/primitives/ScrollArea"
46
+ } from "@/components/primitives/ScrollArea/ScrollArea"
47
+ import { isSidebarNavActive } from "./Sidebar.utils.js"
26
48
  import type {
49
+ SidebarCollapseTriggerProps,
27
50
  SidebarContentProps,
51
+ SidebarContextValue,
52
+ SidebarExpandableProps,
28
53
  SidebarFooterProps,
54
+ SidebarGroupCollapsiblePanelProps,
55
+ SidebarGroupCollapsibleProps,
56
+ SidebarGroupCollapsibleTriggerProps,
29
57
  SidebarGroupContentProps,
30
58
  SidebarGroupLabelProps,
31
59
  SidebarGroupProps,
32
60
  SidebarHeaderProps,
61
+ SidebarGroupActionProps,
62
+ SidebarItemActionProps,
63
+ SidebarItemAdornmentsProps,
64
+ SidebarItemTrailingProps,
65
+ SidebarItemBadgeProps,
33
66
  SidebarItemButtonProps,
67
+ SidebarItemExpandTriggerProps,
68
+ SidebarItemIconProps,
69
+ SidebarItemRowProps,
34
70
  SidebarItemLinkProps,
35
71
  SidebarItemProps,
72
+ SidebarItemShortcutProps,
73
+ SidebarInputProps,
74
+ SidebarSeparatorProps,
75
+ SidebarItemSkeletonProps,
76
+ SidebarSubItemButtonProps,
77
+ SidebarSubItemLinkProps,
78
+ SidebarSubListProps,
36
79
  SidebarListProps,
37
80
  SidebarMobileHeaderProps,
38
81
  SidebarProps,
82
+ SidebarProviderProps,
83
+ SidebarRailProps,
39
84
  SidebarTriggerProps,
40
85
  } from "./Sidebar.types"
41
86
  import {
42
87
  sidebarBrandClasses,
88
+ sidebarCollapsedItemClasses,
89
+ sidebarCollapsedGroupLabelClasses,
43
90
  sidebarDesktopClasses,
44
91
  sidebarDrawerFooterClasses,
92
+ sidebarExpandableClasses,
45
93
  sidebarFooterClasses,
94
+ sidebarGroupActionClasses,
95
+ sidebarGroupCollapsibleClasses,
96
+ sidebarGroupCollapsiblePanelClasses,
97
+ sidebarGroupCollapsibleTriggerClasses,
46
98
  sidebarGroupContentClasses,
47
99
  sidebarGroupLabelClasses,
48
100
  sidebarGroupClasses,
101
+ sidebarItemActionClasses,
102
+ sidebarItemBadgeClasses,
103
+ sidebarItemDisclosureRowClasses,
104
+ sidebarItemIconClasses,
105
+ sidebarItemRowClasses,
106
+ sidebarItemTrailingClasses,
107
+ sidebarItemShortcutClasses,
108
+ sidebarNavItemExpandTriggerClasses,
109
+ sidebarItemBadgeCollapsedClasses,
110
+ sidebarItemBadgeDotClasses,
111
+ sidebarItemBadgeLabelClasses,
112
+ sidebarItemClasses,
113
+ sidebarItemSkeletonClasses,
114
+ sidebarItemSkeletonIconClasses,
115
+ sidebarItemSkeletonLabelClasses,
116
+ sidebarInputClasses,
117
+ sidebarSeparatorClasses,
49
118
  sidebarMainClasses,
119
+ sidebarMobileBarClasses,
50
120
  sidebarMobileHeaderClasses,
51
121
  sidebarNavItemClasses,
122
+ sidebarNavItemDisclosureLeadClasses,
52
123
  sidebarNavListClasses,
53
124
  sidebarNavClasses,
125
+ sidebarSubListClasses,
126
+ sidebarSubNavItemClasses,
127
+ sidebarRailClasses,
54
128
  sidebarRootClasses,
55
129
  } from "./Sidebar.variants"
56
130
  import { cn } from "@/lib/utils"
57
131
 
132
+ const MD_MEDIA_QUERY = "(min-width: 768px)"
133
+
134
+ const SIDEBAR_NAV_ITEM_SELECTOR =
135
+ "a.lex-sidebar__item, button.lex-sidebar__item"
136
+
137
+ const getSidebarNavItems = (nav: HTMLElement): HTMLElement[] => {
138
+ return Array.from(
139
+ nav.querySelectorAll<HTMLElement>(SIDEBAR_NAV_ITEM_SELECTOR),
140
+ ).filter((item) => {
141
+ if (item.hasAttribute("disabled")) {
142
+ return false
143
+ }
144
+
145
+ if (item.getAttribute("aria-disabled") === "true") {
146
+ return false
147
+ }
148
+
149
+ if (item.closest("[hidden], [aria-hidden='true']")) {
150
+ return false
151
+ }
152
+
153
+ return true
154
+ })
155
+ }
156
+
157
+ const handleSidebarNavKeyDown = (event: KeyboardEvent<HTMLElement>): void => {
158
+ const { key, currentTarget } = event
159
+
160
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(key)) {
161
+ return
162
+ }
163
+
164
+ const items = getSidebarNavItems(currentTarget)
165
+
166
+ if (items.length === 0) {
167
+ return
168
+ }
169
+
170
+ const activeIndex = items.indexOf(document.activeElement as HTMLElement)
171
+ let nextIndex = activeIndex
172
+
173
+ if (key === "ArrowDown") {
174
+ nextIndex = activeIndex === -1 ? 0 : (activeIndex + 1) % items.length
175
+ } else if (key === "ArrowUp") {
176
+ nextIndex =
177
+ activeIndex === -1
178
+ ? items.length - 1
179
+ : (activeIndex - 1 + items.length) % items.length
180
+ } else if (key === "Home") {
181
+ nextIndex = 0
182
+ } else if (key === "End") {
183
+ nextIndex = items.length - 1
184
+ }
185
+
186
+ if (nextIndex !== activeIndex || activeIndex === -1) {
187
+ event.preventDefault()
188
+ items[nextIndex]?.focus()
189
+ }
190
+ }
191
+
192
+ const getSidebarActiveLinkProps = (active?: boolean, disabled?: boolean) => {
193
+ if (disabled) {
194
+ return undefined
195
+ }
196
+
197
+ return active ? ({ "aria-current": "page" } as const) : undefined
198
+ }
199
+
200
+ const SidebarItemDisabledContext = createContext(false)
201
+
202
+ const useSidebarItemDisabled = () => useContext(SidebarItemDisabledContext)
203
+
204
+ const resolveSidebarNavItemDisabled = (
205
+ explicit?: boolean,
206
+ inherited?: boolean,
207
+ ) => explicit ?? inherited ?? false
208
+
209
+ const getSidebarDisabledAnchorProps = (disabled: boolean) => {
210
+ if (!disabled) {
211
+ return {}
212
+ }
213
+
214
+ return {
215
+ "aria-disabled": true as const,
216
+ "data-disabled": "",
217
+ tabIndex: -1,
218
+ }
219
+ }
220
+
221
+ const getSidebarDisabledAnchorClickHandler = (
222
+ disabled: boolean,
223
+ onClick?: MouseEventHandler<HTMLAnchorElement>,
224
+ ): MouseEventHandler<HTMLAnchorElement> | undefined => {
225
+ if (!disabled) {
226
+ return onClick
227
+ }
228
+
229
+ return (event) => {
230
+ event.preventDefault()
231
+ event.stopPropagation()
232
+ }
233
+ }
234
+
235
+ const getDesktopMediaQuery = () => {
236
+ if (
237
+ typeof window === "undefined" ||
238
+ typeof window.matchMedia !== "function"
239
+ ) {
240
+ return null
241
+ }
242
+
243
+ return window.matchMedia(MD_MEDIA_QUERY)
244
+ }
245
+
246
+ const subscribeDesktopMedia = (onStoreChange: () => void) => {
247
+ const mediaQuery = getDesktopMediaQuery()
248
+
249
+ if (!mediaQuery) {
250
+ return () => undefined
251
+ }
252
+
253
+ mediaQuery.addEventListener("change", onStoreChange)
254
+ return () => mediaQuery.removeEventListener("change", onStoreChange)
255
+ }
256
+
257
+ const getIsDesktopSnapshot = () => getDesktopMediaQuery()?.matches ?? true
258
+
259
+ const getIsDesktopServerSnapshot = () => true
260
+
261
+ const readPersistedCollapsed = (persistKey: string): boolean => {
262
+ if (typeof window === "undefined") return false
263
+ return localStorage.getItem(persistKey) === "true"
264
+ }
265
+
266
+ const defaultSidebarContext: SidebarContextValue = {
267
+ open: false,
268
+ setOpen: () => undefined,
269
+ collapsed: false,
270
+ setCollapsed: () => undefined,
271
+ toggleSidebar: () => undefined,
272
+ isMobile: false,
273
+ collapsible: "none",
274
+ side: "left",
275
+ }
276
+
277
+ const SidebarContext = createContext<SidebarContextValue | null>(null)
278
+
279
+ const useSidebarContext = () =>
280
+ useContext(SidebarContext) ?? defaultSidebarContext
281
+
282
+ const useSidebar = () => {
283
+ const context = useContext(SidebarContext)
284
+
285
+ if (!context) {
286
+ throw new Error("useSidebar must be used within SidebarProvider")
287
+ }
288
+
289
+ return context
290
+ }
291
+
58
292
  interface SidebarMobileContextValue {
59
293
  closeOnSelect: boolean
60
294
  }
@@ -65,16 +299,157 @@ const SidebarMobileContext = createContext<SidebarMobileContextValue>({
65
299
 
66
300
  const useSidebarMobileContext = () => useContext(SidebarMobileContext)
67
301
 
68
- const Sidebar = ({ ref, className, children, ...props }: SidebarProps) => {
302
+ const SidebarProvider = ({
303
+ children,
304
+ defaultOpen = false,
305
+ open: openProp,
306
+ onOpenChange,
307
+ defaultCollapsed = false,
308
+ collapsed: collapsedProp,
309
+ onCollapsedChange,
310
+ collapsible = "none",
311
+ side = "left",
312
+ persistKey,
313
+ }: SidebarProviderProps) => {
314
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen)
315
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState(() => {
316
+ if (persistKey) {
317
+ return readPersistedCollapsed(persistKey)
318
+ }
319
+
320
+ return defaultCollapsed
321
+ })
322
+
323
+ const open = openProp ?? uncontrolledOpen
324
+ const collapsed = collapsedProp ?? uncontrolledCollapsed
325
+ const isDesktop = useSyncExternalStore(
326
+ subscribeDesktopMedia,
327
+ getIsDesktopSnapshot,
328
+ getIsDesktopServerSnapshot,
329
+ )
330
+ const isMobile = !isDesktop
331
+
332
+ const setOpen = useCallback(
333
+ (nextOpen: boolean) => {
334
+ if (openProp === undefined) {
335
+ setUncontrolledOpen(nextOpen)
336
+ }
337
+
338
+ onOpenChange?.(nextOpen)
339
+ },
340
+ [onOpenChange, openProp],
341
+ )
342
+
343
+ const setCollapsed = useCallback(
344
+ (nextCollapsed: boolean) => {
345
+ if (collapsedProp === undefined) {
346
+ setUncontrolledCollapsed(nextCollapsed)
347
+ }
348
+
349
+ if (persistKey && typeof window !== "undefined") {
350
+ localStorage.setItem(persistKey, String(nextCollapsed))
351
+ }
352
+
353
+ onCollapsedChange?.(nextCollapsed)
354
+ },
355
+ [collapsedProp, onCollapsedChange, persistKey],
356
+ )
357
+
358
+ const toggleSidebar = useCallback(() => {
359
+ if (isMobile) {
360
+ setOpen(!open)
361
+ return
362
+ }
363
+
364
+ if (collapsible !== "none") {
365
+ setCollapsed(!collapsed)
366
+ }
367
+ }, [collapsed, collapsible, isMobile, open, setCollapsed, setOpen])
368
+
369
+ const value = useMemo<SidebarContextValue>(
370
+ () => ({
371
+ open,
372
+ setOpen,
373
+ collapsed,
374
+ setCollapsed,
375
+ toggleSidebar,
376
+ isMobile,
377
+ collapsible,
378
+ side,
379
+ }),
380
+ [
381
+ collapsed,
382
+ collapsible,
383
+ isMobile,
384
+ open,
385
+ setCollapsed,
386
+ setOpen,
387
+ side,
388
+ toggleSidebar,
389
+ ],
390
+ )
391
+
392
+ return (
393
+ <SidebarContext.Provider value={value}>{children}</SidebarContext.Provider>
394
+ )
395
+ }
396
+
397
+ SidebarProvider.displayName = "SidebarProvider"
398
+
399
+ const isSidebarMobileHeaderChild = (child: ReactNode): boolean => {
400
+ return (
401
+ isValidElement(child) &&
402
+ (child.type as { displayName?: string }).displayName ===
403
+ "SidebarMobileHeader"
404
+ )
405
+ }
406
+
407
+ const partitionSidebarChildren = (children: ReactNode) => {
408
+ const mobileHeader: ReactNode[] = []
409
+ const rest: ReactNode[] = []
410
+
411
+ Children.forEach(children, (child) => {
412
+ if (isSidebarMobileHeaderChild(child)) {
413
+ mobileHeader.push(child)
414
+ return
415
+ }
416
+
417
+ rest.push(child)
418
+ })
419
+
420
+ return { mobileHeader, rest }
421
+ }
422
+
423
+ const Sidebar = ({
424
+ ref,
425
+ className,
426
+ children,
427
+ collapsible: collapsibleProp,
428
+ side: sideProp,
429
+ ...props
430
+ }: SidebarProps) => {
431
+ const {
432
+ open,
433
+ setOpen,
434
+ collapsed,
435
+ collapsible: contextCollapsible,
436
+ side: contextSide,
437
+ } = useSidebarContext()
438
+
439
+ const collapsible = collapsibleProp ?? contextCollapsible
440
+ const side = sideProp ?? contextSide
441
+ const shellOptions = { collapsed, collapsible, side }
442
+ const { mobileHeader, rest } = partitionSidebarChildren(children)
443
+
69
444
  const sidebarBody = (
70
445
  <SidebarMobileContext.Provider value={{ closeOnSelect: false }}>
71
- {children}
446
+ {rest}
72
447
  </SidebarMobileContext.Provider>
73
448
  )
74
449
 
75
450
  const drawerBody = (
76
451
  <SidebarMobileContext.Provider value={{ closeOnSelect: true }}>
77
- {children}
452
+ {rest}
78
453
  <div className={sidebarDrawerFooterClasses()}>
79
454
  <DrawerClose render={<Button variant="secondary" size="sm" />}>
80
455
  Close
@@ -84,13 +459,32 @@ const Sidebar = ({ ref, className, children, ...props }: SidebarProps) => {
84
459
  )
85
460
 
86
461
  return (
87
- <aside ref={ref} className={cn(sidebarRootClasses(), className)} {...props}>
88
- <Drawer swipeDirection="left">
89
- <div className={sidebarDesktopClasses()}>{sidebarBody}</div>
462
+ <aside
463
+ ref={ref}
464
+ className={cn(sidebarRootClasses(shellOptions), className)}
465
+ data-collapsed={collapsed ? "true" : "false"}
466
+ data-collapsible={collapsible}
467
+ data-side={side}
468
+ {...props}
469
+ >
470
+ <Drawer
471
+ open={open}
472
+ onOpenChange={setOpen}
473
+ swipeDirection={side === "right" ? "right" : "left"}
474
+ >
475
+ {mobileHeader.length > 0 ? (
476
+ <div className={cn(sidebarMobileBarClasses(), "md:hidden")}>
477
+ {mobileHeader}
478
+ </div>
479
+ ) : null}
480
+ <div className={cn("relative", sidebarDesktopClasses(shellOptions))}>
481
+ {sidebarBody}
482
+ {collapsible !== "none" ? <SidebarRail /> : null}
483
+ </div>
90
484
  <DrawerPortal>
91
485
  <DrawerBackdrop />
92
- <DrawerViewport side="left">
93
- <DrawerPopup side="left" size="sm">
486
+ <DrawerViewport side={side}>
487
+ <DrawerPopup side={side} size="sm">
94
488
  <DrawerClose aria-label="Close navigation" />
95
489
  <DrawerContent className={sidebarMainClasses()}>
96
490
  <DrawerTitle className="sr-only">Navigation</DrawerTitle>
@@ -124,10 +518,51 @@ const SidebarHeader = ({
124
518
 
125
519
  SidebarHeader.displayName = "SidebarHeader"
126
520
 
521
+ const SidebarInput = ({
522
+ ref,
523
+ className,
524
+ size = "sm",
525
+ variant = "ghost",
526
+ type = "search",
527
+ ...props
528
+ }: SidebarInputProps) => {
529
+ return (
530
+ <Input
531
+ ref={ref}
532
+ type={type}
533
+ size={size}
534
+ variant={variant}
535
+ className={cn(sidebarInputClasses(), className)}
536
+ {...props}
537
+ />
538
+ )
539
+ }
540
+
541
+ SidebarInput.displayName = "SidebarInput"
542
+
543
+ const SidebarSeparator = ({
544
+ ref,
545
+ className,
546
+ orientation = "horizontal",
547
+ ...props
548
+ }: SidebarSeparatorProps) => {
549
+ return (
550
+ <Separator
551
+ ref={ref}
552
+ orientation={orientation}
553
+ className={cn(sidebarSeparatorClasses(), className)}
554
+ {...props}
555
+ />
556
+ )
557
+ }
558
+
559
+ SidebarSeparator.displayName = "SidebarSeparator"
560
+
127
561
  const SidebarContent = ({
128
562
  ref,
129
563
  className,
130
564
  children,
565
+ onKeyDown,
131
566
  ...props
132
567
  }: SidebarContentProps) => {
133
568
  return (
@@ -138,6 +573,10 @@ const SidebarContent = ({
138
573
  ref={ref}
139
574
  aria-label="Application navigation"
140
575
  className={className}
576
+ onKeyDown={(event) => {
577
+ handleSidebarNavKeyDown(event)
578
+ onKeyDown?.(event)
579
+ }}
141
580
  {...props}
142
581
  >
143
582
  {children}
@@ -173,6 +612,24 @@ const SidebarTrigger = ({
173
612
  className,
174
613
  ...props
175
614
  }: SidebarTriggerProps) => {
615
+ const { isMobile, collapsible, toggleSidebar } = useSidebarContext()
616
+
617
+ if (!isMobile && collapsible !== "none") {
618
+ return (
619
+ <Button
620
+ ref={ref}
621
+ type="button"
622
+ variant={variant}
623
+ size={size}
624
+ className={className}
625
+ onClick={toggleSidebar}
626
+ {...props}
627
+ >
628
+ {children}
629
+ </Button>
630
+ )
631
+ }
632
+
176
633
  return (
177
634
  <DrawerTrigger
178
635
  render={
@@ -192,6 +649,59 @@ const SidebarTrigger = ({
192
649
 
193
650
  SidebarTrigger.displayName = "SidebarTrigger"
194
651
 
652
+ const SidebarCollapseTrigger = ({
653
+ ref,
654
+ children = "Toggle sidebar",
655
+ variant = "ghost",
656
+ size = "sm",
657
+ className,
658
+ ...props
659
+ }: SidebarCollapseTriggerProps) => {
660
+ const { collapsed, setCollapsed, isMobile, collapsible } = useSidebarContext()
661
+
662
+ if (isMobile || collapsible === "none") {
663
+ return null
664
+ }
665
+
666
+ return (
667
+ <Button
668
+ ref={ref}
669
+ type="button"
670
+ variant={variant}
671
+ size={size}
672
+ className={cn("hidden shrink-0 md:inline-flex", className)}
673
+ aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
674
+ onClick={() => setCollapsed(!collapsed)}
675
+ {...props}
676
+ >
677
+ {children}
678
+ </Button>
679
+ )
680
+ }
681
+
682
+ SidebarCollapseTrigger.displayName = "SidebarCollapseTrigger"
683
+
684
+ const SidebarRail = ({ ref, className, ...props }: SidebarRailProps) => {
685
+ const { collapsible, toggleSidebar, isMobile, side } = useSidebarContext()
686
+
687
+ if (isMobile || collapsible === "none") {
688
+ return null
689
+ }
690
+
691
+ return (
692
+ <button
693
+ ref={ref}
694
+ type="button"
695
+ aria-label="Toggle sidebar rail"
696
+ className={cn(sidebarRailClasses({ side }), className)}
697
+ onClick={toggleSidebar}
698
+ {...props}
699
+ />
700
+ )
701
+ }
702
+
703
+ SidebarRail.displayName = "SidebarRail"
704
+
195
705
  const SidebarMobileHeader = ({
196
706
  ref,
197
707
  className,
@@ -211,6 +721,25 @@ const SidebarMobileHeader = ({
211
721
 
212
722
  SidebarMobileHeader.displayName = "SidebarMobileHeader"
213
723
 
724
+ const SidebarExpandable = ({
725
+ ref,
726
+ className,
727
+ children,
728
+ ...props
729
+ }: SidebarExpandableProps) => {
730
+ return (
731
+ <span
732
+ ref={ref}
733
+ className={cn(sidebarExpandableClasses(), className)}
734
+ {...props}
735
+ >
736
+ {children}
737
+ </span>
738
+ )
739
+ }
740
+
741
+ SidebarExpandable.displayName = "SidebarExpandable"
742
+
214
743
  const SidebarGroup = ({
215
744
  ref,
216
745
  className,
@@ -235,7 +764,11 @@ const SidebarGroupLabel = ({
235
764
  return (
236
765
  <div
237
766
  ref={ref}
238
- className={cn(sidebarGroupLabelClasses(), className)}
767
+ className={cn(
768
+ sidebarGroupLabelClasses(),
769
+ sidebarCollapsedGroupLabelClasses(),
770
+ className,
771
+ )}
239
772
  {...props}
240
773
  >
241
774
  {children}
@@ -264,6 +797,59 @@ const SidebarGroupContent = ({
264
797
 
265
798
  SidebarGroupContent.displayName = "SidebarGroupContent"
266
799
 
800
+ const SidebarGroupCollapsible = ({
801
+ ref,
802
+ className,
803
+ ...props
804
+ }: SidebarGroupCollapsibleProps) => {
805
+ return (
806
+ <Collapsible
807
+ ref={ref}
808
+ variant="plain"
809
+ className={cn(sidebarGroupCollapsibleClasses(), className)}
810
+ {...props}
811
+ />
812
+ )
813
+ }
814
+
815
+ SidebarGroupCollapsible.displayName = "SidebarGroupCollapsible"
816
+
817
+ const SidebarGroupCollapsibleTrigger = ({
818
+ ref,
819
+ className,
820
+ children,
821
+ ...props
822
+ }: SidebarGroupCollapsibleTriggerProps) => {
823
+ return (
824
+ <BaseCollapsible.Trigger
825
+ ref={ref}
826
+ className={cn(sidebarGroupCollapsibleTriggerClasses(), className)}
827
+ {...props}
828
+ >
829
+ {children}
830
+ <ChevronDown aria-hidden="true" />
831
+ </BaseCollapsible.Trigger>
832
+ )
833
+ }
834
+
835
+ SidebarGroupCollapsibleTrigger.displayName = "SidebarGroupCollapsibleTrigger"
836
+
837
+ const SidebarGroupCollapsiblePanel = ({
838
+ ref,
839
+ className,
840
+ ...props
841
+ }: SidebarGroupCollapsiblePanelProps) => {
842
+ return (
843
+ <CollapsiblePanel
844
+ ref={ref}
845
+ className={cn(sidebarGroupCollapsiblePanelClasses(), className)}
846
+ {...props}
847
+ />
848
+ )
849
+ }
850
+
851
+ SidebarGroupCollapsiblePanel.displayName = "SidebarGroupCollapsiblePanel"
852
+
267
853
  const SidebarList = ({
268
854
  ref,
269
855
  className,
@@ -283,12 +869,21 @@ const SidebarItem = ({
283
869
  ref,
284
870
  className,
285
871
  children,
872
+ disabled = false,
286
873
  ...props
287
874
  }: SidebarItemProps) => {
288
875
  return (
289
- <li ref={ref} className={className} {...props}>
290
- {children}
291
- </li>
876
+ <SidebarItemDisabledContext.Provider value={disabled}>
877
+ <li
878
+ ref={ref}
879
+ className={cn(sidebarItemClasses(), className)}
880
+ data-disabled={disabled ? "" : undefined}
881
+ aria-disabled={disabled || undefined}
882
+ {...props}
883
+ >
884
+ {children}
885
+ </li>
886
+ </SidebarItemDisabledContext.Provider>
292
887
  )
293
888
  }
294
889
 
@@ -297,16 +892,34 @@ SidebarItem.displayName = "SidebarItem"
297
892
  const SidebarItemLink = ({
298
893
  ref,
299
894
  active,
895
+ disabled,
896
+ chrome = "default",
300
897
  className,
301
898
  children,
899
+ onClick,
302
900
  ...props
303
901
  }: SidebarItemLinkProps) => {
304
902
  const { closeOnSelect } = useSidebarMobileContext()
305
- const linkClassName = cn(sidebarNavItemClasses(active), className)
903
+ const inheritedDisabled = useSidebarItemDisabled()
904
+ const isDisabled = resolveSidebarNavItemDisabled(disabled, inheritedDisabled)
905
+ const linkClassName = cn(
906
+ chrome === "disclosureLead"
907
+ ? sidebarNavItemDisclosureLeadClasses(active, isDisabled)
908
+ : sidebarNavItemClasses(active, isDisabled),
909
+ sidebarCollapsedItemClasses(),
910
+ className,
911
+ )
912
+
913
+ const linkProps = {
914
+ ...props,
915
+ ...getSidebarDisabledAnchorProps(isDisabled),
916
+ ...getSidebarActiveLinkProps(active, isDisabled),
917
+ onClick: getSidebarDisabledAnchorClickHandler(isDisabled, onClick),
918
+ }
306
919
 
307
920
  if (!closeOnSelect) {
308
921
  return (
309
- <a ref={ref} className={linkClassName} {...props}>
922
+ <a ref={ref} className={linkClassName} {...linkProps}>
310
923
  {children}
311
924
  </a>
312
925
  )
@@ -315,7 +928,7 @@ const SidebarItemLink = ({
315
928
  return (
316
929
  <DrawerClose
317
930
  appearance="inline"
318
- render={<a ref={ref} className={linkClassName} {...props} />}
931
+ render={<a ref={ref} className={linkClassName} {...linkProps} />}
319
932
  >
320
933
  {children}
321
934
  </DrawerClose>
@@ -327,17 +940,35 @@ SidebarItemLink.displayName = "SidebarItemLink"
327
940
  const SidebarItemButton = ({
328
941
  ref,
329
942
  active,
943
+ disabled,
330
944
  className,
331
945
  children,
332
946
  type = "button",
333
947
  ...props
334
948
  }: SidebarItemButtonProps) => {
335
949
  const { closeOnSelect } = useSidebarMobileContext()
336
- const buttonClassName = cn(sidebarNavItemClasses(active), className)
950
+ const inheritedDisabled = useSidebarItemDisabled()
951
+ const isDisabled = resolveSidebarNavItemDisabled(disabled, inheritedDisabled)
952
+ const buttonClassName = cn(
953
+ sidebarNavItemClasses(active, isDisabled),
954
+ sidebarCollapsedItemClasses(),
955
+ className,
956
+ )
957
+ const buttonProps = {
958
+ ...props,
959
+ disabled: isDisabled,
960
+ "data-disabled": isDisabled ? "" : undefined,
961
+ "aria-disabled": isDisabled || undefined,
962
+ }
337
963
 
338
964
  if (!closeOnSelect) {
339
965
  return (
340
- <button ref={ref} type={type} className={buttonClassName} {...props}>
966
+ <button
967
+ ref={ref}
968
+ type={type}
969
+ className={buttonClassName}
970
+ {...buttonProps}
971
+ >
341
972
  {children}
342
973
  </button>
343
974
  )
@@ -347,7 +978,12 @@ const SidebarItemButton = ({
347
978
  <DrawerClose
348
979
  appearance="inline"
349
980
  render={
350
- <button ref={ref} type={type} className={buttonClassName} {...props} />
981
+ <button
982
+ ref={ref}
983
+ type={type}
984
+ className={buttonClassName}
985
+ {...buttonProps}
986
+ />
351
987
  }
352
988
  >
353
989
  {children}
@@ -357,18 +993,408 @@ const SidebarItemButton = ({
357
993
 
358
994
  SidebarItemButton.displayName = "SidebarItemButton"
359
995
 
996
+ const SidebarItemSkeleton = ({
997
+ ref,
998
+ className,
999
+ showIcon = true,
1000
+ indent = false,
1001
+ ...props
1002
+ }: SidebarItemSkeletonProps) => {
1003
+ return (
1004
+ <div
1005
+ ref={ref}
1006
+ aria-hidden
1007
+ className={cn(sidebarItemSkeletonClasses(indent), className)}
1008
+ {...props}
1009
+ >
1010
+ {showIcon ? <span className={sidebarItemSkeletonIconClasses()} /> : null}
1011
+ <span className={sidebarItemSkeletonLabelClasses()} />
1012
+ </div>
1013
+ )
1014
+ }
1015
+
1016
+ SidebarItemSkeleton.displayName = "SidebarItemSkeleton"
1017
+
1018
+ const getSidebarItemBadgeLabel = (children: ReactNode): string | undefined => {
1019
+ if (typeof children === "string" || typeof children === "number") {
1020
+ return String(children)
1021
+ }
1022
+
1023
+ return undefined
1024
+ }
1025
+
1026
+ const SidebarItemBadge = ({
1027
+ ref,
1028
+ variant = "neutral",
1029
+ appearance,
1030
+ size = "sm",
1031
+ dot,
1032
+ className,
1033
+ children,
1034
+ ...props
1035
+ }: SidebarItemBadgeProps) => {
1036
+ const badgeLabel = getSidebarItemBadgeLabel(children)
1037
+
1038
+ if (dot) {
1039
+ return (
1040
+ <span
1041
+ ref={ref}
1042
+ role="status"
1043
+ aria-label={badgeLabel}
1044
+ className={cn(
1045
+ sidebarItemBadgeClasses(),
1046
+ sidebarItemBadgeDotClasses(variant),
1047
+ className,
1048
+ )}
1049
+ {...props}
1050
+ />
1051
+ )
1052
+ }
1053
+
1054
+ return (
1055
+ <Badge
1056
+ ref={ref}
1057
+ variant={variant}
1058
+ appearance={appearance}
1059
+ size={size}
1060
+ className={cn(
1061
+ sidebarItemBadgeClasses(),
1062
+ sidebarItemBadgeCollapsedClasses(),
1063
+ className,
1064
+ )}
1065
+ {...props}
1066
+ >
1067
+ <span className={sidebarItemBadgeLabelClasses()}>{children}</span>
1068
+ </Badge>
1069
+ )
1070
+ }
1071
+
1072
+ SidebarItemBadge.displayName = "SidebarItemBadge"
1073
+
1074
+ const SidebarItemIcon = ({
1075
+ ref,
1076
+ className,
1077
+ children,
1078
+ ...props
1079
+ }: SidebarItemIconProps) => {
1080
+ return (
1081
+ <span
1082
+ ref={ref}
1083
+ className={cn(sidebarItemIconClasses(), className)}
1084
+ {...props}
1085
+ >
1086
+ {children}
1087
+ </span>
1088
+ )
1089
+ }
1090
+
1091
+ SidebarItemIcon.displayName = "SidebarItemIcon"
1092
+
1093
+ const SidebarItemRow = ({
1094
+ ref,
1095
+ variant = "default",
1096
+ className,
1097
+ children,
1098
+ ...props
1099
+ }: SidebarItemRowProps) => {
1100
+ return (
1101
+ <div
1102
+ ref={ref}
1103
+ className={cn(
1104
+ variant === "disclosure"
1105
+ ? sidebarItemDisclosureRowClasses()
1106
+ : sidebarItemRowClasses(),
1107
+ className,
1108
+ )}
1109
+ {...props}
1110
+ >
1111
+ {children}
1112
+ </div>
1113
+ )
1114
+ }
1115
+
1116
+ SidebarItemRow.displayName = "SidebarItemRow"
1117
+
1118
+ const SidebarItemTrailing = ({
1119
+ ref,
1120
+ className,
1121
+ children,
1122
+ ...props
1123
+ }: SidebarItemTrailingProps) => {
1124
+ return (
1125
+ <div
1126
+ ref={ref}
1127
+ className={cn(sidebarItemTrailingClasses(), className)}
1128
+ {...props}
1129
+ >
1130
+ {children}
1131
+ </div>
1132
+ )
1133
+ }
1134
+
1135
+ SidebarItemTrailing.displayName = "SidebarItemTrailing"
1136
+
1137
+ /** @deprecated Use `SidebarItemTrailing` inside the item shell. */
1138
+ const SidebarItemAdornments = ({
1139
+ ref,
1140
+ className,
1141
+ children,
1142
+ ...props
1143
+ }: SidebarItemAdornmentsProps) => {
1144
+ return (
1145
+ <SidebarItemTrailing ref={ref} className={className} {...props}>
1146
+ {children}
1147
+ </SidebarItemTrailing>
1148
+ )
1149
+ }
1150
+
1151
+ SidebarItemAdornments.displayName = "SidebarItemAdornments"
1152
+
1153
+ const SidebarItemExpandTrigger = ({
1154
+ ref,
1155
+ variant = "default",
1156
+ className,
1157
+ open = false,
1158
+ children,
1159
+ type = "button",
1160
+ ...props
1161
+ }: SidebarItemExpandTriggerProps) => {
1162
+ return (
1163
+ <button
1164
+ ref={ref}
1165
+ type={type}
1166
+ className={cn(
1167
+ sidebarNavItemExpandTriggerClasses(open, variant),
1168
+ className,
1169
+ )}
1170
+ {...props}
1171
+ >
1172
+ {children ?? <ChevronDown aria-hidden />}
1173
+ </button>
1174
+ )
1175
+ }
1176
+
1177
+ SidebarItemExpandTrigger.displayName = "SidebarItemExpandTrigger"
1178
+
1179
+ const SidebarItemAction = ({
1180
+ ref,
1181
+ showOnHover = true,
1182
+ className,
1183
+ ...props
1184
+ }: SidebarItemActionProps) => {
1185
+ return (
1186
+ <Button
1187
+ ref={ref}
1188
+ type="button"
1189
+ variant="ghost"
1190
+ size="xs"
1191
+ className={cn(sidebarItemActionClasses(showOnHover), className)}
1192
+ {...props}
1193
+ />
1194
+ )
1195
+ }
1196
+
1197
+ SidebarItemAction.displayName = "SidebarItemAction"
1198
+
1199
+ const SidebarItemShortcut = ({
1200
+ ref,
1201
+ className,
1202
+ children,
1203
+ ...props
1204
+ }: SidebarItemShortcutProps) => {
1205
+ return (
1206
+ <kbd
1207
+ ref={ref}
1208
+ className={cn(sidebarItemShortcutClasses(), className)}
1209
+ {...props}
1210
+ >
1211
+ {children}
1212
+ </kbd>
1213
+ )
1214
+ }
1215
+
1216
+ SidebarItemShortcut.displayName = "SidebarItemShortcut"
1217
+
1218
+ const SidebarGroupAction = ({
1219
+ ref,
1220
+ className,
1221
+ ...props
1222
+ }: SidebarGroupActionProps) => {
1223
+ return (
1224
+ <Button
1225
+ ref={ref}
1226
+ type="button"
1227
+ variant="ghost"
1228
+ size="xs"
1229
+ className={cn(sidebarGroupActionClasses(), className)}
1230
+ {...props}
1231
+ />
1232
+ )
1233
+ }
1234
+
1235
+ SidebarGroupAction.displayName = "SidebarGroupAction"
1236
+
1237
+ const SidebarSubList = ({
1238
+ ref,
1239
+ className,
1240
+ children,
1241
+ ...props
1242
+ }: SidebarSubListProps) => {
1243
+ return (
1244
+ <ul ref={ref} className={cn(sidebarSubListClasses(), className)} {...props}>
1245
+ {children}
1246
+ </ul>
1247
+ )
1248
+ }
1249
+
1250
+ SidebarSubList.displayName = "SidebarSubList"
1251
+
1252
+ const SidebarSubItemLink = ({
1253
+ ref,
1254
+ active,
1255
+ disabled,
1256
+ className,
1257
+ children,
1258
+ onClick,
1259
+ ...props
1260
+ }: SidebarSubItemLinkProps) => {
1261
+ const { closeOnSelect } = useSidebarMobileContext()
1262
+ const inheritedDisabled = useSidebarItemDisabled()
1263
+ const isDisabled = resolveSidebarNavItemDisabled(disabled, inheritedDisabled)
1264
+ const linkClassName = cn(
1265
+ sidebarSubNavItemClasses(active, isDisabled),
1266
+ className,
1267
+ )
1268
+ const linkProps = {
1269
+ ...props,
1270
+ ...getSidebarDisabledAnchorProps(isDisabled),
1271
+ ...getSidebarActiveLinkProps(active, isDisabled),
1272
+ onClick: getSidebarDisabledAnchorClickHandler(isDisabled, onClick),
1273
+ }
1274
+
1275
+ if (!closeOnSelect) {
1276
+ return (
1277
+ <a ref={ref} className={linkClassName} {...linkProps}>
1278
+ {children}
1279
+ </a>
1280
+ )
1281
+ }
1282
+
1283
+ return (
1284
+ <DrawerClose
1285
+ appearance="inline"
1286
+ render={<a ref={ref} className={linkClassName} {...linkProps} />}
1287
+ >
1288
+ {children}
1289
+ </DrawerClose>
1290
+ )
1291
+ }
1292
+
1293
+ SidebarSubItemLink.displayName = "SidebarSubItemLink"
1294
+
1295
+ const SidebarSubItemButton = ({
1296
+ ref,
1297
+ active,
1298
+ disabled,
1299
+ className,
1300
+ children,
1301
+ type = "button",
1302
+ ...props
1303
+ }: SidebarSubItemButtonProps) => {
1304
+ const { closeOnSelect } = useSidebarMobileContext()
1305
+ const inheritedDisabled = useSidebarItemDisabled()
1306
+ const isDisabled = resolveSidebarNavItemDisabled(disabled, inheritedDisabled)
1307
+ const buttonClassName = cn(
1308
+ sidebarSubNavItemClasses(active, isDisabled),
1309
+ className,
1310
+ )
1311
+ const buttonProps = {
1312
+ ...props,
1313
+ disabled: isDisabled,
1314
+ "data-disabled": isDisabled ? "" : undefined,
1315
+ "aria-disabled": isDisabled || undefined,
1316
+ }
1317
+
1318
+ if (!closeOnSelect) {
1319
+ return (
1320
+ <button
1321
+ ref={ref}
1322
+ type={type}
1323
+ className={buttonClassName}
1324
+ {...buttonProps}
1325
+ >
1326
+ {children}
1327
+ </button>
1328
+ )
1329
+ }
1330
+
1331
+ return (
1332
+ <DrawerClose
1333
+ appearance="inline"
1334
+ render={
1335
+ <button
1336
+ ref={ref}
1337
+ type={type}
1338
+ className={buttonClassName}
1339
+ {...buttonProps}
1340
+ />
1341
+ }
1342
+ >
1343
+ {children}
1344
+ </DrawerClose>
1345
+ )
1346
+ }
1347
+
1348
+ SidebarSubItemButton.displayName = "SidebarSubItemButton"
1349
+
360
1350
  export {
361
1351
  Sidebar,
1352
+ SidebarProvider,
1353
+ useSidebar,
362
1354
  SidebarHeader,
1355
+ SidebarInput,
1356
+ SidebarSeparator,
363
1357
  SidebarContent,
364
1358
  SidebarFooter,
365
1359
  SidebarGroup,
366
1360
  SidebarGroupLabel,
367
1361
  SidebarGroupContent,
1362
+ SidebarGroupCollapsible,
1363
+ SidebarGroupCollapsibleTrigger,
1364
+ SidebarGroupCollapsiblePanel,
368
1365
  SidebarList,
369
1366
  SidebarItem,
370
1367
  SidebarItemLink,
371
1368
  SidebarItemButton,
1369
+ SidebarItemSkeleton,
1370
+ SidebarItemBadge,
1371
+ SidebarItemIcon,
1372
+ SidebarItemRow,
1373
+ SidebarItemTrailing,
1374
+ SidebarItemAdornments,
1375
+ SidebarItemExpandTrigger,
1376
+ SidebarItemAction,
1377
+ SidebarItemShortcut,
1378
+ SidebarGroupAction,
1379
+ SidebarSubList,
1380
+ SidebarSubItemLink,
1381
+ SidebarSubItemButton,
372
1382
  SidebarTrigger,
1383
+ SidebarCollapseTrigger,
1384
+ SidebarRail,
373
1385
  SidebarMobileHeader,
1386
+ SidebarExpandable,
1387
+ isSidebarNavActive,
374
1388
  }
1389
+
1390
+ export type { SidebarNavActiveOptions } from "./Sidebar.types.js"
1391
+ export {
1392
+ sidebarItemRowClasses,
1393
+ sidebarItemDisclosureRowClasses,
1394
+ sidebarItemTrailingClasses,
1395
+ sidebarItemAdornmentsClasses,
1396
+ sidebarMobileBarClasses,
1397
+ sidebarNavItemRowLeadClasses,
1398
+ sidebarNavItemDisclosureLeadClasses,
1399
+ sidebarNavItemExpandTriggerClasses,
1400
+ } from "./Sidebar.variants.js"