@dalexto/lexsys-registry 0.1.0 → 0.1.2

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 (26) hide show
  1. package/dist/index.js +12 -3
  2. package/package.json +2 -2
  3. package/templates/blocks/AuthForm/AuthForm.tsx +2 -2
  4. package/templates/blocks/AuthForm/AuthForm.types.ts +3 -3
  5. package/templates/blocks/CommandPalette/CommandPalette.tsx +4 -4
  6. package/templates/blocks/CommandPalette/CommandPalette.types.ts +2 -2
  7. package/templates/blocks/DataTable/DataTable.tsx +2 -2
  8. package/templates/blocks/DataTable/DataTable.types.ts +2 -2
  9. package/templates/blocks/FilterToolbar/FilterToolbar.tsx +4 -4
  10. package/templates/blocks/FilterToolbar/FilterToolbar.types.ts +4 -4
  11. package/templates/blocks/FormField/FormField.tsx +1 -1
  12. package/templates/blocks/FormField/FormField.types.ts +1 -1
  13. package/templates/blocks/PageHeader/PageHeader.tsx +2 -2
  14. package/templates/blocks/PageHeader/PageHeader.types.ts +2 -2
  15. package/templates/blocks/SettingsPanel/SettingsPanel.tsx +1 -1
  16. package/templates/blocks/SettingsPanel/SettingsPanel.types.ts +1 -1
  17. package/templates/blocks/Sidebar/Sidebar.tsx +935 -22
  18. package/templates/blocks/Sidebar/Sidebar.types.ts +155 -1
  19. package/templates/blocks/Sidebar/Sidebar.utils.ts +34 -0
  20. package/templates/blocks/Sidebar/Sidebar.variants.ts +310 -17
  21. package/templates/blocks/StatsCard/StatsCard.tsx +1 -1
  22. package/templates/blocks/StatsCard/StatsCard.types.ts +1 -1
  23. package/templates/styles/theme.css +9 -1
  24. package/templates/styles/tokens.css +34 -1
  25. package/templates/templates/SettingsPageLayout/SettingsPageLayout.tsx +2 -2
  26. package/templates/templates/SettingsPageLayout/SettingsPageLayout.types.ts +2 -2
@@ -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,255 @@ 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
+ SidebarItemBadgeProps,
33
64
  SidebarItemButtonProps,
65
+ SidebarItemIconProps,
34
66
  SidebarItemLinkProps,
35
67
  SidebarItemProps,
68
+ SidebarItemShortcutProps,
69
+ SidebarInputProps,
70
+ SidebarSeparatorProps,
71
+ SidebarItemSkeletonProps,
72
+ SidebarSubItemButtonProps,
73
+ SidebarSubItemLinkProps,
74
+ SidebarSubListProps,
36
75
  SidebarListProps,
37
76
  SidebarMobileHeaderProps,
38
77
  SidebarProps,
78
+ SidebarProviderProps,
79
+ SidebarRailProps,
39
80
  SidebarTriggerProps,
40
81
  } from "./Sidebar.types"
41
82
  import {
42
83
  sidebarBrandClasses,
84
+ sidebarCollapsedItemClasses,
85
+ sidebarCollapsedGroupLabelClasses,
43
86
  sidebarDesktopClasses,
44
87
  sidebarDrawerFooterClasses,
88
+ sidebarExpandableClasses,
45
89
  sidebarFooterClasses,
90
+ sidebarGroupActionClasses,
91
+ sidebarGroupCollapsibleClasses,
92
+ sidebarGroupCollapsiblePanelClasses,
93
+ sidebarGroupCollapsibleTriggerClasses,
46
94
  sidebarGroupContentClasses,
47
95
  sidebarGroupLabelClasses,
48
96
  sidebarGroupClasses,
97
+ sidebarItemActionClasses,
98
+ sidebarItemBadgeClasses,
99
+ sidebarItemIconClasses,
100
+ sidebarItemShortcutClasses,
101
+ sidebarItemBadgeCollapsedClasses,
102
+ sidebarItemBadgeDotClasses,
103
+ sidebarItemBadgeLabelClasses,
104
+ sidebarItemClasses,
105
+ sidebarItemSkeletonClasses,
106
+ sidebarItemSkeletonIconClasses,
107
+ sidebarItemSkeletonLabelClasses,
108
+ sidebarInputClasses,
109
+ sidebarSeparatorClasses,
49
110
  sidebarMainClasses,
50
111
  sidebarMobileHeaderClasses,
51
112
  sidebarNavItemClasses,
52
113
  sidebarNavListClasses,
53
114
  sidebarNavClasses,
115
+ sidebarSubListClasses,
116
+ sidebarSubNavItemClasses,
117
+ sidebarRailClasses,
54
118
  sidebarRootClasses,
55
119
  } from "./Sidebar.variants"
56
120
  import { cn } from "@/lib/utils"
57
121
 
122
+ const MD_MEDIA_QUERY = "(min-width: 768px)"
123
+
124
+ const SIDEBAR_NAV_ITEM_SELECTOR =
125
+ "a.lex-sidebar__item, button.lex-sidebar__item"
126
+
127
+ const getSidebarNavItems = (nav: HTMLElement): HTMLElement[] => {
128
+ return Array.from(
129
+ nav.querySelectorAll<HTMLElement>(SIDEBAR_NAV_ITEM_SELECTOR),
130
+ ).filter((item) => {
131
+ if (item.hasAttribute("disabled")) {
132
+ return false
133
+ }
134
+
135
+ if (item.getAttribute("aria-disabled") === "true") {
136
+ return false
137
+ }
138
+
139
+ if (item.closest("[hidden], [aria-hidden='true']")) {
140
+ return false
141
+ }
142
+
143
+ return true
144
+ })
145
+ }
146
+
147
+ const handleSidebarNavKeyDown = (event: KeyboardEvent<HTMLElement>): void => {
148
+ const { key, currentTarget } = event
149
+
150
+ if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(key)) {
151
+ return
152
+ }
153
+
154
+ const items = getSidebarNavItems(currentTarget)
155
+
156
+ if (items.length === 0) {
157
+ return
158
+ }
159
+
160
+ const activeIndex = items.indexOf(document.activeElement as HTMLElement)
161
+ let nextIndex = activeIndex
162
+
163
+ if (key === "ArrowDown") {
164
+ nextIndex = activeIndex === -1 ? 0 : (activeIndex + 1) % items.length
165
+ } else if (key === "ArrowUp") {
166
+ nextIndex =
167
+ activeIndex === -1
168
+ ? items.length - 1
169
+ : (activeIndex - 1 + items.length) % items.length
170
+ } else if (key === "Home") {
171
+ nextIndex = 0
172
+ } else if (key === "End") {
173
+ nextIndex = items.length - 1
174
+ }
175
+
176
+ if (nextIndex !== activeIndex || activeIndex === -1) {
177
+ event.preventDefault()
178
+ items[nextIndex]?.focus()
179
+ }
180
+ }
181
+
182
+ const getSidebarActiveLinkProps = (active?: boolean, disabled?: boolean) => {
183
+ if (disabled) {
184
+ return undefined
185
+ }
186
+
187
+ return active ? ({ "aria-current": "page" } as const) : undefined
188
+ }
189
+
190
+ const SidebarItemDisabledContext = createContext(false)
191
+
192
+ const useSidebarItemDisabled = () => useContext(SidebarItemDisabledContext)
193
+
194
+ const resolveSidebarNavItemDisabled = (
195
+ explicit?: boolean,
196
+ inherited?: boolean,
197
+ ) => explicit ?? inherited ?? false
198
+
199
+ const getSidebarDisabledAnchorProps = (disabled: boolean) => {
200
+ if (!disabled) {
201
+ return {}
202
+ }
203
+
204
+ return {
205
+ "aria-disabled": true as const,
206
+ "data-disabled": "",
207
+ tabIndex: -1,
208
+ }
209
+ }
210
+
211
+ const getSidebarDisabledAnchorClickHandler = (
212
+ disabled: boolean,
213
+ onClick?: MouseEventHandler<HTMLAnchorElement>,
214
+ ): MouseEventHandler<HTMLAnchorElement> | undefined => {
215
+ if (!disabled) {
216
+ return onClick
217
+ }
218
+
219
+ return (event) => {
220
+ event.preventDefault()
221
+ event.stopPropagation()
222
+ }
223
+ }
224
+
225
+ const getDesktopMediaQuery = () => {
226
+ if (
227
+ typeof window === "undefined" ||
228
+ typeof window.matchMedia !== "function"
229
+ ) {
230
+ return null
231
+ }
232
+
233
+ return window.matchMedia(MD_MEDIA_QUERY)
234
+ }
235
+
236
+ const subscribeDesktopMedia = (onStoreChange: () => void) => {
237
+ const mediaQuery = getDesktopMediaQuery()
238
+
239
+ if (!mediaQuery) {
240
+ return () => undefined
241
+ }
242
+
243
+ mediaQuery.addEventListener("change", onStoreChange)
244
+ return () => mediaQuery.removeEventListener("change", onStoreChange)
245
+ }
246
+
247
+ const getIsDesktopSnapshot = () => getDesktopMediaQuery()?.matches ?? true
248
+
249
+ const getIsDesktopServerSnapshot = () => true
250
+
251
+ const readPersistedCollapsed = (persistKey: string): boolean => {
252
+ if (typeof window === "undefined") return false
253
+ return localStorage.getItem(persistKey) === "true"
254
+ }
255
+
256
+ const defaultSidebarContext: SidebarContextValue = {
257
+ open: false,
258
+ setOpen: () => undefined,
259
+ collapsed: false,
260
+ setCollapsed: () => undefined,
261
+ toggleSidebar: () => undefined,
262
+ isMobile: false,
263
+ collapsible: "none",
264
+ side: "left",
265
+ }
266
+
267
+ const SidebarContext = createContext<SidebarContextValue | null>(null)
268
+
269
+ const useSidebarContext = () =>
270
+ useContext(SidebarContext) ?? defaultSidebarContext
271
+
272
+ const useSidebar = () => {
273
+ const context = useContext(SidebarContext)
274
+
275
+ if (!context) {
276
+ throw new Error("useSidebar must be used within SidebarProvider")
277
+ }
278
+
279
+ return context
280
+ }
281
+
58
282
  interface SidebarMobileContextValue {
59
283
  closeOnSelect: boolean
60
284
  }
@@ -65,16 +289,157 @@ const SidebarMobileContext = createContext<SidebarMobileContextValue>({
65
289
 
66
290
  const useSidebarMobileContext = () => useContext(SidebarMobileContext)
67
291
 
68
- const Sidebar = ({ ref, className, children, ...props }: SidebarProps) => {
292
+ const SidebarProvider = ({
293
+ children,
294
+ defaultOpen = false,
295
+ open: openProp,
296
+ onOpenChange,
297
+ defaultCollapsed = false,
298
+ collapsed: collapsedProp,
299
+ onCollapsedChange,
300
+ collapsible = "none",
301
+ side = "left",
302
+ persistKey,
303
+ }: SidebarProviderProps) => {
304
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen)
305
+ const [uncontrolledCollapsed, setUncontrolledCollapsed] = useState(() => {
306
+ if (persistKey) {
307
+ return readPersistedCollapsed(persistKey)
308
+ }
309
+
310
+ return defaultCollapsed
311
+ })
312
+
313
+ const open = openProp ?? uncontrolledOpen
314
+ const collapsed = collapsedProp ?? uncontrolledCollapsed
315
+ const isDesktop = useSyncExternalStore(
316
+ subscribeDesktopMedia,
317
+ getIsDesktopSnapshot,
318
+ getIsDesktopServerSnapshot,
319
+ )
320
+ const isMobile = !isDesktop
321
+
322
+ const setOpen = useCallback(
323
+ (nextOpen: boolean) => {
324
+ if (openProp === undefined) {
325
+ setUncontrolledOpen(nextOpen)
326
+ }
327
+
328
+ onOpenChange?.(nextOpen)
329
+ },
330
+ [onOpenChange, openProp],
331
+ )
332
+
333
+ const setCollapsed = useCallback(
334
+ (nextCollapsed: boolean) => {
335
+ if (collapsedProp === undefined) {
336
+ setUncontrolledCollapsed(nextCollapsed)
337
+ }
338
+
339
+ if (persistKey && typeof window !== "undefined") {
340
+ localStorage.setItem(persistKey, String(nextCollapsed))
341
+ }
342
+
343
+ onCollapsedChange?.(nextCollapsed)
344
+ },
345
+ [collapsedProp, onCollapsedChange, persistKey],
346
+ )
347
+
348
+ const toggleSidebar = useCallback(() => {
349
+ if (isMobile) {
350
+ setOpen(!open)
351
+ return
352
+ }
353
+
354
+ if (collapsible !== "none") {
355
+ setCollapsed(!collapsed)
356
+ }
357
+ }, [collapsed, collapsible, isMobile, open, setCollapsed, setOpen])
358
+
359
+ const value = useMemo<SidebarContextValue>(
360
+ () => ({
361
+ open,
362
+ setOpen,
363
+ collapsed,
364
+ setCollapsed,
365
+ toggleSidebar,
366
+ isMobile,
367
+ collapsible,
368
+ side,
369
+ }),
370
+ [
371
+ collapsed,
372
+ collapsible,
373
+ isMobile,
374
+ open,
375
+ setCollapsed,
376
+ setOpen,
377
+ side,
378
+ toggleSidebar,
379
+ ],
380
+ )
381
+
382
+ return (
383
+ <SidebarContext.Provider value={value}>{children}</SidebarContext.Provider>
384
+ )
385
+ }
386
+
387
+ SidebarProvider.displayName = "SidebarProvider"
388
+
389
+ const isSidebarMobileHeaderChild = (child: ReactNode): boolean => {
390
+ return (
391
+ isValidElement(child) &&
392
+ (child.type as { displayName?: string }).displayName ===
393
+ "SidebarMobileHeader"
394
+ )
395
+ }
396
+
397
+ const partitionSidebarChildren = (children: ReactNode) => {
398
+ const mobileHeader: ReactNode[] = []
399
+ const rest: ReactNode[] = []
400
+
401
+ Children.forEach(children, (child) => {
402
+ if (isSidebarMobileHeaderChild(child)) {
403
+ mobileHeader.push(child)
404
+ return
405
+ }
406
+
407
+ rest.push(child)
408
+ })
409
+
410
+ return { mobileHeader, rest }
411
+ }
412
+
413
+ const Sidebar = ({
414
+ ref,
415
+ className,
416
+ children,
417
+ collapsible: collapsibleProp,
418
+ side: sideProp,
419
+ ...props
420
+ }: SidebarProps) => {
421
+ const {
422
+ open,
423
+ setOpen,
424
+ collapsed,
425
+ collapsible: contextCollapsible,
426
+ side: contextSide,
427
+ } = useSidebarContext()
428
+
429
+ const collapsible = collapsibleProp ?? contextCollapsible
430
+ const side = sideProp ?? contextSide
431
+ const shellOptions = { collapsed, collapsible, side }
432
+ const { mobileHeader, rest } = partitionSidebarChildren(children)
433
+
69
434
  const sidebarBody = (
70
435
  <SidebarMobileContext.Provider value={{ closeOnSelect: false }}>
71
- {children}
436
+ {rest}
72
437
  </SidebarMobileContext.Provider>
73
438
  )
74
439
 
75
440
  const drawerBody = (
76
441
  <SidebarMobileContext.Provider value={{ closeOnSelect: true }}>
77
- {children}
442
+ {rest}
78
443
  <div className={sidebarDrawerFooterClasses()}>
79
444
  <DrawerClose render={<Button variant="secondary" size="sm" />}>
80
445
  Close
@@ -84,13 +449,32 @@ const Sidebar = ({ ref, className, children, ...props }: SidebarProps) => {
84
449
  )
85
450
 
86
451
  return (
87
- <aside ref={ref} className={cn(sidebarRootClasses(), className)} {...props}>
88
- <Drawer swipeDirection="left">
89
- <div className={sidebarDesktopClasses()}>{sidebarBody}</div>
452
+ <aside
453
+ ref={ref}
454
+ className={cn(sidebarRootClasses(shellOptions), className)}
455
+ data-collapsed={collapsed ? "true" : "false"}
456
+ data-collapsible={collapsible}
457
+ data-side={side}
458
+ {...props}
459
+ >
460
+ <Drawer
461
+ open={open}
462
+ onOpenChange={setOpen}
463
+ swipeDirection={side === "right" ? "right" : "left"}
464
+ >
465
+ {mobileHeader.length > 0 ? (
466
+ <div className="flex items-center gap-3 border-b border-[var(--lex-border-default)] bg-[var(--lex-color-background-base)] px-[var(--lex-space-4)] py-[var(--lex-space-3)] md:hidden">
467
+ {mobileHeader}
468
+ </div>
469
+ ) : null}
470
+ <div className={cn("relative", sidebarDesktopClasses(shellOptions))}>
471
+ {sidebarBody}
472
+ {collapsible !== "none" ? <SidebarRail /> : null}
473
+ </div>
90
474
  <DrawerPortal>
91
475
  <DrawerBackdrop />
92
- <DrawerViewport side="left">
93
- <DrawerPopup side="left" size="sm">
476
+ <DrawerViewport side={side}>
477
+ <DrawerPopup side={side} size="sm">
94
478
  <DrawerClose aria-label="Close navigation" />
95
479
  <DrawerContent className={sidebarMainClasses()}>
96
480
  <DrawerTitle className="sr-only">Navigation</DrawerTitle>
@@ -124,10 +508,51 @@ const SidebarHeader = ({
124
508
 
125
509
  SidebarHeader.displayName = "SidebarHeader"
126
510
 
511
+ const SidebarInput = ({
512
+ ref,
513
+ className,
514
+ size = "sm",
515
+ variant = "ghost",
516
+ type = "search",
517
+ ...props
518
+ }: SidebarInputProps) => {
519
+ return (
520
+ <Input
521
+ ref={ref}
522
+ type={type}
523
+ size={size}
524
+ variant={variant}
525
+ className={cn(sidebarInputClasses(), className)}
526
+ {...props}
527
+ />
528
+ )
529
+ }
530
+
531
+ SidebarInput.displayName = "SidebarInput"
532
+
533
+ const SidebarSeparator = ({
534
+ ref,
535
+ className,
536
+ orientation = "horizontal",
537
+ ...props
538
+ }: SidebarSeparatorProps) => {
539
+ return (
540
+ <Separator
541
+ ref={ref}
542
+ orientation={orientation}
543
+ className={cn(sidebarSeparatorClasses(), className)}
544
+ {...props}
545
+ />
546
+ )
547
+ }
548
+
549
+ SidebarSeparator.displayName = "SidebarSeparator"
550
+
127
551
  const SidebarContent = ({
128
552
  ref,
129
553
  className,
130
554
  children,
555
+ onKeyDown,
131
556
  ...props
132
557
  }: SidebarContentProps) => {
133
558
  return (
@@ -138,6 +563,10 @@ const SidebarContent = ({
138
563
  ref={ref}
139
564
  aria-label="Application navigation"
140
565
  className={className}
566
+ onKeyDown={(event) => {
567
+ handleSidebarNavKeyDown(event)
568
+ onKeyDown?.(event)
569
+ }}
141
570
  {...props}
142
571
  >
143
572
  {children}
@@ -173,6 +602,24 @@ const SidebarTrigger = ({
173
602
  className,
174
603
  ...props
175
604
  }: SidebarTriggerProps) => {
605
+ const { isMobile, collapsible, toggleSidebar } = useSidebarContext()
606
+
607
+ if (!isMobile && collapsible !== "none") {
608
+ return (
609
+ <Button
610
+ ref={ref}
611
+ type="button"
612
+ variant={variant}
613
+ size={size}
614
+ className={className}
615
+ onClick={toggleSidebar}
616
+ {...props}
617
+ >
618
+ {children}
619
+ </Button>
620
+ )
621
+ }
622
+
176
623
  return (
177
624
  <DrawerTrigger
178
625
  render={
@@ -192,6 +639,59 @@ const SidebarTrigger = ({
192
639
 
193
640
  SidebarTrigger.displayName = "SidebarTrigger"
194
641
 
642
+ const SidebarCollapseTrigger = ({
643
+ ref,
644
+ children = "Toggle sidebar",
645
+ variant = "ghost",
646
+ size = "sm",
647
+ className,
648
+ ...props
649
+ }: SidebarCollapseTriggerProps) => {
650
+ const { collapsed, setCollapsed, isMobile, collapsible } = useSidebarContext()
651
+
652
+ if (isMobile || collapsible === "none") {
653
+ return null
654
+ }
655
+
656
+ return (
657
+ <Button
658
+ ref={ref}
659
+ type="button"
660
+ variant={variant}
661
+ size={size}
662
+ className={cn("hidden shrink-0 md:inline-flex", className)}
663
+ aria-label={collapsed ? "Expand sidebar" : "Collapse sidebar"}
664
+ onClick={() => setCollapsed(!collapsed)}
665
+ {...props}
666
+ >
667
+ {children}
668
+ </Button>
669
+ )
670
+ }
671
+
672
+ SidebarCollapseTrigger.displayName = "SidebarCollapseTrigger"
673
+
674
+ const SidebarRail = ({ ref, className, ...props }: SidebarRailProps) => {
675
+ const { collapsible, toggleSidebar, isMobile, side } = useSidebarContext()
676
+
677
+ if (isMobile || collapsible === "none") {
678
+ return null
679
+ }
680
+
681
+ return (
682
+ <button
683
+ ref={ref}
684
+ type="button"
685
+ aria-label="Toggle sidebar rail"
686
+ className={cn(sidebarRailClasses({ side }), className)}
687
+ onClick={toggleSidebar}
688
+ {...props}
689
+ />
690
+ )
691
+ }
692
+
693
+ SidebarRail.displayName = "SidebarRail"
694
+
195
695
  const SidebarMobileHeader = ({
196
696
  ref,
197
697
  className,
@@ -211,6 +711,25 @@ const SidebarMobileHeader = ({
211
711
 
212
712
  SidebarMobileHeader.displayName = "SidebarMobileHeader"
213
713
 
714
+ const SidebarExpandable = ({
715
+ ref,
716
+ className,
717
+ children,
718
+ ...props
719
+ }: SidebarExpandableProps) => {
720
+ return (
721
+ <span
722
+ ref={ref}
723
+ className={cn(sidebarExpandableClasses(), className)}
724
+ {...props}
725
+ >
726
+ {children}
727
+ </span>
728
+ )
729
+ }
730
+
731
+ SidebarExpandable.displayName = "SidebarExpandable"
732
+
214
733
  const SidebarGroup = ({
215
734
  ref,
216
735
  className,
@@ -235,7 +754,11 @@ const SidebarGroupLabel = ({
235
754
  return (
236
755
  <div
237
756
  ref={ref}
238
- className={cn(sidebarGroupLabelClasses(), className)}
757
+ className={cn(
758
+ sidebarGroupLabelClasses(),
759
+ sidebarCollapsedGroupLabelClasses(),
760
+ className,
761
+ )}
239
762
  {...props}
240
763
  >
241
764
  {children}
@@ -264,6 +787,59 @@ const SidebarGroupContent = ({
264
787
 
265
788
  SidebarGroupContent.displayName = "SidebarGroupContent"
266
789
 
790
+ const SidebarGroupCollapsible = ({
791
+ ref,
792
+ className,
793
+ ...props
794
+ }: SidebarGroupCollapsibleProps) => {
795
+ return (
796
+ <Collapsible
797
+ ref={ref}
798
+ variant="plain"
799
+ className={cn(sidebarGroupCollapsibleClasses(), className)}
800
+ {...props}
801
+ />
802
+ )
803
+ }
804
+
805
+ SidebarGroupCollapsible.displayName = "SidebarGroupCollapsible"
806
+
807
+ const SidebarGroupCollapsibleTrigger = ({
808
+ ref,
809
+ className,
810
+ children,
811
+ ...props
812
+ }: SidebarGroupCollapsibleTriggerProps) => {
813
+ return (
814
+ <BaseCollapsible.Trigger
815
+ ref={ref}
816
+ className={cn(sidebarGroupCollapsibleTriggerClasses(), className)}
817
+ {...props}
818
+ >
819
+ {children}
820
+ <ChevronDown aria-hidden="true" />
821
+ </BaseCollapsible.Trigger>
822
+ )
823
+ }
824
+
825
+ SidebarGroupCollapsibleTrigger.displayName = "SidebarGroupCollapsibleTrigger"
826
+
827
+ const SidebarGroupCollapsiblePanel = ({
828
+ ref,
829
+ className,
830
+ ...props
831
+ }: SidebarGroupCollapsiblePanelProps) => {
832
+ return (
833
+ <CollapsiblePanel
834
+ ref={ref}
835
+ className={cn(sidebarGroupCollapsiblePanelClasses(), className)}
836
+ {...props}
837
+ />
838
+ )
839
+ }
840
+
841
+ SidebarGroupCollapsiblePanel.displayName = "SidebarGroupCollapsiblePanel"
842
+
267
843
  const SidebarList = ({
268
844
  ref,
269
845
  className,
@@ -283,12 +859,21 @@ const SidebarItem = ({
283
859
  ref,
284
860
  className,
285
861
  children,
862
+ disabled = false,
286
863
  ...props
287
864
  }: SidebarItemProps) => {
288
865
  return (
289
- <li ref={ref} className={className} {...props}>
290
- {children}
291
- </li>
866
+ <SidebarItemDisabledContext.Provider value={disabled}>
867
+ <li
868
+ ref={ref}
869
+ className={cn(sidebarItemClasses(), className)}
870
+ data-disabled={disabled ? "" : undefined}
871
+ aria-disabled={disabled || undefined}
872
+ {...props}
873
+ >
874
+ {children}
875
+ </li>
876
+ </SidebarItemDisabledContext.Provider>
292
877
  )
293
878
  }
294
879
 
@@ -297,16 +882,31 @@ SidebarItem.displayName = "SidebarItem"
297
882
  const SidebarItemLink = ({
298
883
  ref,
299
884
  active,
885
+ disabled,
300
886
  className,
301
887
  children,
888
+ onClick,
302
889
  ...props
303
890
  }: SidebarItemLinkProps) => {
304
891
  const { closeOnSelect } = useSidebarMobileContext()
305
- const linkClassName = cn(sidebarNavItemClasses(active), className)
892
+ const inheritedDisabled = useSidebarItemDisabled()
893
+ const isDisabled = resolveSidebarNavItemDisabled(disabled, inheritedDisabled)
894
+ const linkClassName = cn(
895
+ sidebarNavItemClasses(active, isDisabled),
896
+ sidebarCollapsedItemClasses(),
897
+ className,
898
+ )
899
+
900
+ const linkProps = {
901
+ ...props,
902
+ ...getSidebarDisabledAnchorProps(isDisabled),
903
+ ...getSidebarActiveLinkProps(active, isDisabled),
904
+ onClick: getSidebarDisabledAnchorClickHandler(isDisabled, onClick),
905
+ }
306
906
 
307
907
  if (!closeOnSelect) {
308
908
  return (
309
- <a ref={ref} className={linkClassName} {...props}>
909
+ <a ref={ref} className={linkClassName} {...linkProps}>
310
910
  {children}
311
911
  </a>
312
912
  )
@@ -315,7 +915,7 @@ const SidebarItemLink = ({
315
915
  return (
316
916
  <DrawerClose
317
917
  appearance="inline"
318
- render={<a ref={ref} className={linkClassName} {...props} />}
918
+ render={<a ref={ref} className={linkClassName} {...linkProps} />}
319
919
  >
320
920
  {children}
321
921
  </DrawerClose>
@@ -327,17 +927,35 @@ SidebarItemLink.displayName = "SidebarItemLink"
327
927
  const SidebarItemButton = ({
328
928
  ref,
329
929
  active,
930
+ disabled,
330
931
  className,
331
932
  children,
332
933
  type = "button",
333
934
  ...props
334
935
  }: SidebarItemButtonProps) => {
335
936
  const { closeOnSelect } = useSidebarMobileContext()
336
- const buttonClassName = cn(sidebarNavItemClasses(active), className)
937
+ const inheritedDisabled = useSidebarItemDisabled()
938
+ const isDisabled = resolveSidebarNavItemDisabled(disabled, inheritedDisabled)
939
+ const buttonClassName = cn(
940
+ sidebarNavItemClasses(active, isDisabled),
941
+ sidebarCollapsedItemClasses(),
942
+ className,
943
+ )
944
+ const buttonProps = {
945
+ ...props,
946
+ disabled: isDisabled,
947
+ "data-disabled": isDisabled ? "" : undefined,
948
+ "aria-disabled": isDisabled || undefined,
949
+ }
337
950
 
338
951
  if (!closeOnSelect) {
339
952
  return (
340
- <button ref={ref} type={type} className={buttonClassName} {...props}>
953
+ <button
954
+ ref={ref}
955
+ type={type}
956
+ className={buttonClassName}
957
+ {...buttonProps}
958
+ >
341
959
  {children}
342
960
  </button>
343
961
  )
@@ -347,7 +965,12 @@ const SidebarItemButton = ({
347
965
  <DrawerClose
348
966
  appearance="inline"
349
967
  render={
350
- <button ref={ref} type={type} className={buttonClassName} {...props} />
968
+ <button
969
+ ref={ref}
970
+ type={type}
971
+ className={buttonClassName}
972
+ {...buttonProps}
973
+ />
351
974
  }
352
975
  >
353
976
  {children}
@@ -357,18 +980,308 @@ const SidebarItemButton = ({
357
980
 
358
981
  SidebarItemButton.displayName = "SidebarItemButton"
359
982
 
983
+ const SidebarItemSkeleton = ({
984
+ ref,
985
+ className,
986
+ showIcon = true,
987
+ indent = false,
988
+ ...props
989
+ }: SidebarItemSkeletonProps) => {
990
+ return (
991
+ <div
992
+ ref={ref}
993
+ aria-hidden
994
+ className={cn(sidebarItemSkeletonClasses(indent), className)}
995
+ {...props}
996
+ >
997
+ {showIcon ? <span className={sidebarItemSkeletonIconClasses()} /> : null}
998
+ <span className={sidebarItemSkeletonLabelClasses()} />
999
+ </div>
1000
+ )
1001
+ }
1002
+
1003
+ SidebarItemSkeleton.displayName = "SidebarItemSkeleton"
1004
+
1005
+ const getSidebarItemBadgeLabel = (children: ReactNode): string | undefined => {
1006
+ if (typeof children === "string" || typeof children === "number") {
1007
+ return String(children)
1008
+ }
1009
+
1010
+ return undefined
1011
+ }
1012
+
1013
+ const SidebarItemBadge = ({
1014
+ ref,
1015
+ variant = "neutral",
1016
+ appearance,
1017
+ size = "sm",
1018
+ dot,
1019
+ className,
1020
+ children,
1021
+ ...props
1022
+ }: SidebarItemBadgeProps) => {
1023
+ const badgeLabel = getSidebarItemBadgeLabel(children)
1024
+
1025
+ if (dot) {
1026
+ return (
1027
+ <span
1028
+ ref={ref}
1029
+ role="status"
1030
+ aria-label={badgeLabel}
1031
+ className={cn(
1032
+ sidebarItemBadgeClasses(),
1033
+ sidebarItemBadgeDotClasses(variant),
1034
+ className,
1035
+ )}
1036
+ {...props}
1037
+ />
1038
+ )
1039
+ }
1040
+
1041
+ return (
1042
+ <Badge
1043
+ ref={ref}
1044
+ variant={variant}
1045
+ appearance={appearance}
1046
+ size={size}
1047
+ className={cn(
1048
+ sidebarItemBadgeClasses(),
1049
+ sidebarItemBadgeCollapsedClasses(),
1050
+ className,
1051
+ )}
1052
+ {...props}
1053
+ >
1054
+ <span className={sidebarItemBadgeLabelClasses()}>{children}</span>
1055
+ </Badge>
1056
+ )
1057
+ }
1058
+
1059
+ SidebarItemBadge.displayName = "SidebarItemBadge"
1060
+
1061
+ const SidebarItemIcon = ({
1062
+ ref,
1063
+ className,
1064
+ children,
1065
+ ...props
1066
+ }: SidebarItemIconProps) => {
1067
+ return (
1068
+ <span
1069
+ ref={ref}
1070
+ className={cn(sidebarItemIconClasses(), className)}
1071
+ {...props}
1072
+ >
1073
+ {children}
1074
+ </span>
1075
+ )
1076
+ }
1077
+
1078
+ SidebarItemIcon.displayName = "SidebarItemIcon"
1079
+
1080
+ const SidebarItemAction = ({
1081
+ ref,
1082
+ showOnHover = true,
1083
+ className,
1084
+ ...props
1085
+ }: SidebarItemActionProps) => {
1086
+ return (
1087
+ <Button
1088
+ ref={ref}
1089
+ type="button"
1090
+ variant="ghost"
1091
+ size="xs"
1092
+ className={cn(sidebarItemActionClasses(showOnHover), className)}
1093
+ {...props}
1094
+ />
1095
+ )
1096
+ }
1097
+
1098
+ SidebarItemAction.displayName = "SidebarItemAction"
1099
+
1100
+ const SidebarItemShortcut = ({
1101
+ ref,
1102
+ className,
1103
+ children,
1104
+ ...props
1105
+ }: SidebarItemShortcutProps) => {
1106
+ return (
1107
+ <kbd
1108
+ ref={ref}
1109
+ className={cn(sidebarItemShortcutClasses(), className)}
1110
+ {...props}
1111
+ >
1112
+ {children}
1113
+ </kbd>
1114
+ )
1115
+ }
1116
+
1117
+ SidebarItemShortcut.displayName = "SidebarItemShortcut"
1118
+
1119
+ const SidebarGroupAction = ({
1120
+ ref,
1121
+ className,
1122
+ ...props
1123
+ }: SidebarGroupActionProps) => {
1124
+ return (
1125
+ <Button
1126
+ ref={ref}
1127
+ type="button"
1128
+ variant="ghost"
1129
+ size="xs"
1130
+ className={cn(sidebarGroupActionClasses(), className)}
1131
+ {...props}
1132
+ />
1133
+ )
1134
+ }
1135
+
1136
+ SidebarGroupAction.displayName = "SidebarGroupAction"
1137
+
1138
+ const SidebarSubList = ({
1139
+ ref,
1140
+ className,
1141
+ children,
1142
+ ...props
1143
+ }: SidebarSubListProps) => {
1144
+ return (
1145
+ <ul ref={ref} className={cn(sidebarSubListClasses(), className)} {...props}>
1146
+ {children}
1147
+ </ul>
1148
+ )
1149
+ }
1150
+
1151
+ SidebarSubList.displayName = "SidebarSubList"
1152
+
1153
+ const SidebarSubItemLink = ({
1154
+ ref,
1155
+ active,
1156
+ disabled,
1157
+ className,
1158
+ children,
1159
+ onClick,
1160
+ ...props
1161
+ }: SidebarSubItemLinkProps) => {
1162
+ const { closeOnSelect } = useSidebarMobileContext()
1163
+ const inheritedDisabled = useSidebarItemDisabled()
1164
+ const isDisabled = resolveSidebarNavItemDisabled(disabled, inheritedDisabled)
1165
+ const linkClassName = cn(
1166
+ sidebarSubNavItemClasses(active, isDisabled),
1167
+ className,
1168
+ )
1169
+ const linkProps = {
1170
+ ...props,
1171
+ ...getSidebarDisabledAnchorProps(isDisabled),
1172
+ ...getSidebarActiveLinkProps(active, isDisabled),
1173
+ onClick: getSidebarDisabledAnchorClickHandler(isDisabled, onClick),
1174
+ }
1175
+
1176
+ if (!closeOnSelect) {
1177
+ return (
1178
+ <a ref={ref} className={linkClassName} {...linkProps}>
1179
+ {children}
1180
+ </a>
1181
+ )
1182
+ }
1183
+
1184
+ return (
1185
+ <DrawerClose
1186
+ appearance="inline"
1187
+ render={<a ref={ref} className={linkClassName} {...linkProps} />}
1188
+ >
1189
+ {children}
1190
+ </DrawerClose>
1191
+ )
1192
+ }
1193
+
1194
+ SidebarSubItemLink.displayName = "SidebarSubItemLink"
1195
+
1196
+ const SidebarSubItemButton = ({
1197
+ ref,
1198
+ active,
1199
+ disabled,
1200
+ className,
1201
+ children,
1202
+ type = "button",
1203
+ ...props
1204
+ }: SidebarSubItemButtonProps) => {
1205
+ const { closeOnSelect } = useSidebarMobileContext()
1206
+ const inheritedDisabled = useSidebarItemDisabled()
1207
+ const isDisabled = resolveSidebarNavItemDisabled(disabled, inheritedDisabled)
1208
+ const buttonClassName = cn(
1209
+ sidebarSubNavItemClasses(active, isDisabled),
1210
+ className,
1211
+ )
1212
+ const buttonProps = {
1213
+ ...props,
1214
+ disabled: isDisabled,
1215
+ "data-disabled": isDisabled ? "" : undefined,
1216
+ "aria-disabled": isDisabled || undefined,
1217
+ }
1218
+
1219
+ if (!closeOnSelect) {
1220
+ return (
1221
+ <button
1222
+ ref={ref}
1223
+ type={type}
1224
+ className={buttonClassName}
1225
+ {...buttonProps}
1226
+ >
1227
+ {children}
1228
+ </button>
1229
+ )
1230
+ }
1231
+
1232
+ return (
1233
+ <DrawerClose
1234
+ appearance="inline"
1235
+ render={
1236
+ <button
1237
+ ref={ref}
1238
+ type={type}
1239
+ className={buttonClassName}
1240
+ {...buttonProps}
1241
+ />
1242
+ }
1243
+ >
1244
+ {children}
1245
+ </DrawerClose>
1246
+ )
1247
+ }
1248
+
1249
+ SidebarSubItemButton.displayName = "SidebarSubItemButton"
1250
+
360
1251
  export {
361
1252
  Sidebar,
1253
+ SidebarProvider,
1254
+ useSidebar,
362
1255
  SidebarHeader,
1256
+ SidebarInput,
1257
+ SidebarSeparator,
363
1258
  SidebarContent,
364
1259
  SidebarFooter,
365
1260
  SidebarGroup,
366
1261
  SidebarGroupLabel,
367
1262
  SidebarGroupContent,
1263
+ SidebarGroupCollapsible,
1264
+ SidebarGroupCollapsibleTrigger,
1265
+ SidebarGroupCollapsiblePanel,
368
1266
  SidebarList,
369
1267
  SidebarItem,
370
1268
  SidebarItemLink,
371
1269
  SidebarItemButton,
1270
+ SidebarItemSkeleton,
1271
+ SidebarItemBadge,
1272
+ SidebarItemIcon,
1273
+ SidebarItemAction,
1274
+ SidebarItemShortcut,
1275
+ SidebarGroupAction,
1276
+ SidebarSubList,
1277
+ SidebarSubItemLink,
1278
+ SidebarSubItemButton,
372
1279
  SidebarTrigger,
1280
+ SidebarCollapseTrigger,
1281
+ SidebarRail,
373
1282
  SidebarMobileHeader,
1283
+ SidebarExpandable,
1284
+ isSidebarNavActive,
374
1285
  }
1286
+
1287
+ export type { SidebarNavActiveOptions } from "./Sidebar.types.js"