@auto-engineer/generate-react-client 1.64.0 → 1.65.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 (144) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/starter/.storybook/main.ts +17 -22
  3. package/dist/starter/.storybook/manager-head.html +31 -31
  4. package/dist/starter/.storybook/manager.ts +133 -133
  5. package/dist/starter/.storybook/preview-head.html +12 -12
  6. package/dist/starter/.storybook/preview.tsx +79 -79
  7. package/dist/starter/biome.json +126 -0
  8. package/dist/starter/codegen.ts +11 -11
  9. package/dist/starter/components.json +27 -27
  10. package/dist/starter/package.json +86 -80
  11. package/dist/starter/public/mockServiceWorker.js +261 -261
  12. package/dist/starter/scripts/build-component-db.ts +17 -20
  13. package/dist/starter/src/App.tsx +15 -17
  14. package/dist/starter/src/components/ui/Accordion.stories.tsx +35 -35
  15. package/dist/starter/src/components/ui/Accordion.tsx +33 -33
  16. package/dist/starter/src/components/ui/Alert.stories.tsx +15 -15
  17. package/dist/starter/src/components/ui/Alert.tsx +32 -32
  18. package/dist/starter/src/components/ui/AlertDialog.stories.tsx +50 -50
  19. package/dist/starter/src/components/ui/AlertDialog.tsx +114 -115
  20. package/dist/starter/src/components/ui/AspectRatio.stories.tsx +20 -20
  21. package/dist/starter/src/components/ui/AspectRatio.tsx +1 -1
  22. package/dist/starter/src/components/ui/Avatar.stories.tsx +27 -27
  23. package/dist/starter/src/components/ui/Avatar.tsx +63 -63
  24. package/dist/starter/src/components/ui/Badge.stories.tsx +14 -14
  25. package/dist/starter/src/components/ui/Badge.tsx +27 -27
  26. package/dist/starter/src/components/ui/Breadcrumb.stories.tsx +38 -38
  27. package/dist/starter/src/components/ui/Breadcrumb.tsx +63 -62
  28. package/dist/starter/src/components/ui/Button.stories.tsx +55 -55
  29. package/dist/starter/src/components/ui/Button.tsx +49 -49
  30. package/dist/starter/src/components/ui/ButtonGroup.stories.tsx +17 -17
  31. package/dist/starter/src/components/ui/ButtonGroup.tsx +52 -53
  32. package/dist/starter/src/components/ui/Calendar.stories.tsx +20 -19
  33. package/dist/starter/src/components/ui/Calendar.tsx +142 -143
  34. package/dist/starter/src/components/ui/Card.stories.tsx +29 -29
  35. package/dist/starter/src/components/ui/Card.tsx +31 -31
  36. package/dist/starter/src/components/ui/Carousel.stories.tsx +41 -41
  37. package/dist/starter/src/components/ui/Carousel.tsx +171 -172
  38. package/dist/starter/src/components/ui/Chart.stories.tsx +21 -21
  39. package/dist/starter/src/components/ui/Chart.tsx +244 -247
  40. package/dist/starter/src/components/ui/Checkbox.stories.tsx +11 -11
  41. package/dist/starter/src/components/ui/Checkbox.tsx +18 -18
  42. package/dist/starter/src/components/ui/Collapsible.stories.tsx +40 -40
  43. package/dist/starter/src/components/ui/Collapsible.tsx +3 -3
  44. package/dist/starter/src/components/ui/Combobox.stories.tsx +48 -48
  45. package/dist/starter/src/components/ui/Combobox.tsx +204 -205
  46. package/dist/starter/src/components/ui/Command.stories.tsx +55 -55
  47. package/dist/starter/src/components/ui/Command.tsx +102 -103
  48. package/dist/starter/src/components/ui/ContextMenu.stories.tsx +52 -52
  49. package/dist/starter/src/components/ui/ContextMenu.tsx +151 -151
  50. package/dist/starter/src/components/ui/DesignSystem-Colors.stories.tsx +92 -92
  51. package/dist/starter/src/components/ui/DesignSystem-Layout.stories.tsx +139 -139
  52. package/dist/starter/src/components/ui/DesignSystem-Overview.stories.tsx +676 -657
  53. package/dist/starter/src/components/ui/DesignSystem-Typography.stories.tsx +59 -59
  54. package/dist/starter/src/components/ui/Dialog.stories.tsx +56 -56
  55. package/dist/starter/src/components/ui/Dialog.tsx +97 -98
  56. package/dist/starter/src/components/ui/Direction.stories.tsx +20 -20
  57. package/dist/starter/src/components/ui/Direction.tsx +7 -7
  58. package/dist/starter/src/components/ui/Drawer.stories.tsx +54 -54
  59. package/dist/starter/src/components/ui/Drawer.tsx +70 -70
  60. package/dist/starter/src/components/ui/DropdownMenu.stories.tsx +58 -58
  61. package/dist/starter/src/components/ui/DropdownMenu.tsx +157 -157
  62. package/dist/starter/src/components/ui/Empty.stories.tsx +22 -22
  63. package/dist/starter/src/components/ui/Empty.tsx +58 -58
  64. package/dist/starter/src/components/ui/Field.stories.tsx +31 -31
  65. package/dist/starter/src/components/ui/Field.tsx +180 -181
  66. package/dist/starter/src/components/ui/Form.stories.tsx +29 -29
  67. package/dist/starter/src/components/ui/Form.tsx +93 -96
  68. package/dist/starter/src/components/ui/HoverCard.stories.tsx +34 -34
  69. package/dist/starter/src/components/ui/HoverCard.tsx +21 -21
  70. package/dist/starter/src/components/ui/Input.stories.tsx +18 -18
  71. package/dist/starter/src/components/ui/Input.tsx +14 -14
  72. package/dist/starter/src/components/ui/InputGroup.stories.tsx +34 -34
  73. package/dist/starter/src/components/ui/InputGroup.tsx +110 -111
  74. package/dist/starter/src/components/ui/InputOTP.stories.tsx +28 -28
  75. package/dist/starter/src/components/ui/InputOTP.tsx +43 -43
  76. package/dist/starter/src/components/ui/Item.stories.tsx +45 -45
  77. package/dist/starter/src/components/ui/Item.tsx +113 -114
  78. package/dist/starter/src/components/ui/Kbd.stories.tsx +31 -31
  79. package/dist/starter/src/components/ui/Kbd.tsx +11 -11
  80. package/dist/starter/src/components/ui/Label.stories.tsx +62 -62
  81. package/dist/starter/src/components/ui/Label.tsx +26 -25
  82. package/dist/starter/src/components/ui/Menubar.stories.tsx +62 -62
  83. package/dist/starter/src/components/ui/Menubar.tsx +173 -173
  84. package/dist/starter/src/components/ui/NativeSelect.stories.tsx +26 -26
  85. package/dist/starter/src/components/ui/NativeSelect.tsx +29 -29
  86. package/dist/starter/src/components/ui/NavigationMenu.stories.tsx +64 -64
  87. package/dist/starter/src/components/ui/NavigationMenu.tsx +103 -103
  88. package/dist/starter/src/components/ui/Pagination.stories.tsx +61 -61
  89. package/dist/starter/src/components/ui/Pagination.tsx +69 -71
  90. package/dist/starter/src/components/ui/Popover.stories.tsx +38 -38
  91. package/dist/starter/src/components/ui/Popover.tsx +25 -25
  92. package/dist/starter/src/components/ui/Progress.stories.tsx +9 -9
  93. package/dist/starter/src/components/ui/Progress.tsx +14 -14
  94. package/dist/starter/src/components/ui/RadioGroup.stories.tsx +35 -35
  95. package/dist/starter/src/components/ui/RadioGroup.tsx +19 -19
  96. package/dist/starter/src/components/ui/Resizable.stories.tsx +54 -54
  97. package/dist/starter/src/components/ui/Resizable.tsx +29 -29
  98. package/dist/starter/src/components/ui/ScrollArea.stories.tsx +27 -27
  99. package/dist/starter/src/components/ui/ScrollArea.tsx +34 -34
  100. package/dist/starter/src/components/ui/Select.stories.tsx +43 -43
  101. package/dist/starter/src/components/ui/Select.tsx +120 -120
  102. package/dist/starter/src/components/ui/Separator.stories.tsx +27 -27
  103. package/dist/starter/src/components/ui/Separator.tsx +17 -17
  104. package/dist/starter/src/components/ui/Sheet.stories.tsx +53 -53
  105. package/dist/starter/src/components/ui/Sheet.tsx +69 -69
  106. package/dist/starter/src/components/ui/Sidebar.stories.tsx +77 -77
  107. package/dist/starter/src/components/ui/Sidebar.tsx +563 -564
  108. package/dist/starter/src/components/ui/Skeleton.stories.tsx +25 -25
  109. package/dist/starter/src/components/ui/Skeleton.tsx +1 -1
  110. package/dist/starter/src/components/ui/Slider.stories.tsx +5 -5
  111. package/dist/starter/src/components/ui/Slider.tsx +45 -44
  112. package/dist/starter/src/components/ui/Sonner.stories.tsx +32 -32
  113. package/dist/starter/src/components/ui/Sonner.tsx +23 -23
  114. package/dist/starter/src/components/ui/Spinner.stories.tsx +8 -8
  115. package/dist/starter/src/components/ui/Spinner.tsx +1 -1
  116. package/dist/starter/src/components/ui/Switch.stories.tsx +16 -17
  117. package/dist/starter/src/components/ui/Switch.tsx +24 -24
  118. package/dist/starter/src/components/ui/Table.stories.tsx +50 -50
  119. package/dist/starter/src/components/ui/Table.tsx +45 -45
  120. package/dist/starter/src/components/ui/Tabs.stories.tsx +39 -39
  121. package/dist/starter/src/components/ui/Tabs.tsx +47 -47
  122. package/dist/starter/src/components/ui/Textarea.stories.tsx +9 -9
  123. package/dist/starter/src/components/ui/Textarea.tsx +11 -11
  124. package/dist/starter/src/components/ui/Toast.stories.tsx +77 -77
  125. package/dist/starter/src/components/ui/Toast.tsx +75 -75
  126. package/dist/starter/src/components/ui/Toaster.tsx +17 -19
  127. package/dist/starter/src/components/ui/Toggle.stories.tsx +20 -20
  128. package/dist/starter/src/components/ui/Toggle.tsx +26 -26
  129. package/dist/starter/src/components/ui/ToggleGroup.stories.tsx +41 -41
  130. package/dist/starter/src/components/ui/ToggleGroup.tsx +61 -62
  131. package/dist/starter/src/components/ui/Tooltip.stories.tsx +26 -26
  132. package/dist/starter/src/components/ui/Tooltip.tsx +24 -24
  133. package/dist/starter/src/gql/execute.ts +1 -1
  134. package/dist/starter/src/gql/fragment-masking.ts +1 -1
  135. package/dist/starter/src/gql/graphql.ts +3 -0
  136. package/dist/starter/src/hooks/use-mobile.ts +11 -11
  137. package/dist/starter/src/hooks/use-toast.ts +135 -135
  138. package/dist/starter/src/index.css +105 -105
  139. package/dist/starter/src/lib/utils.ts +1 -1
  140. package/dist/starter/src/main.tsx +4 -1
  141. package/dist/starter/tsconfig.app.json +24 -24
  142. package/dist/starter/tsconfig.json +8 -8
  143. package/dist/starter/vite.config.ts +38 -37
  144. package/package.json +3 -3
@@ -1,16 +1,15 @@
1
- import * as React from 'react';
2
1
  import { cva, type VariantProps } from 'class-variance-authority';
3
2
  import { PanelLeftIcon } from 'lucide-react';
4
3
  import { Slot } from 'radix-ui';
5
-
6
- import { useIsMobile } from '@/hooks/use-mobile';
7
- import { cn } from '@/lib/utils';
4
+ import * as React from 'react';
8
5
  import { Button } from '@/components/ui/Button';
9
6
  import { Input } from '@/components/ui/Input';
10
7
  import { Separator } from '@/components/ui/Separator';
11
8
  import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from '@/components/ui/Sheet';
12
9
  import { Skeleton } from '@/components/ui/Skeleton';
13
10
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/Tooltip';
11
+ import { useIsMobile } from '@/hooks/use-mobile';
12
+ import { cn } from '@/lib/utils';
14
13
 
15
14
  const SIDEBAR_COOKIE_NAME = 'sidebar_state';
16
15
  const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
@@ -20,24 +19,24 @@ const SIDEBAR_WIDTH_ICON = '3rem';
20
19
  const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
21
20
 
22
21
  type SidebarContextProps = {
23
- state: 'expanded' | 'collapsed';
24
- open: boolean;
25
- setOpen: (open: boolean) => void;
26
- openMobile: boolean;
27
- setOpenMobile: (open: boolean) => void;
28
- isMobile: boolean;
29
- toggleSidebar: () => void;
22
+ state: 'expanded' | 'collapsed';
23
+ open: boolean;
24
+ setOpen: (open: boolean) => void;
25
+ openMobile: boolean;
26
+ setOpenMobile: (open: boolean) => void;
27
+ isMobile: boolean;
28
+ toggleSidebar: () => void;
30
29
  };
31
30
 
32
31
  const SidebarContext = React.createContext<SidebarContextProps | null>(null);
33
32
 
34
33
  function useSidebar() {
35
- const context = React.useContext(SidebarContext);
36
- if (!context) {
37
- throw new Error('useSidebar must be used within a SidebarProvider.');
38
- }
34
+ const context = React.useContext(SidebarContext);
35
+ if (!context) {
36
+ throw new Error('useSidebar must be used within a SidebarProvider.');
37
+ }
39
38
 
40
- return context;
39
+ return context;
41
40
  }
42
41
 
43
42
  /**
@@ -45,95 +44,95 @@ function useSidebar() {
45
44
  * Wrap your layout with this to enable sidebar state management for all child Sidebar components.
46
45
  */
47
46
  function SidebarProvider({
48
- defaultOpen = true,
49
- open: openProp,
50
- onOpenChange: setOpenProp,
51
- className,
52
- style,
53
- children,
54
- ...props
47
+ defaultOpen = true,
48
+ open: openProp,
49
+ onOpenChange: setOpenProp,
50
+ className,
51
+ style,
52
+ children,
53
+ ...props
55
54
  }: React.ComponentProps<'div'> & {
56
- defaultOpen?: boolean;
57
- open?: boolean;
58
- onOpenChange?: (open: boolean) => void;
55
+ defaultOpen?: boolean;
56
+ open?: boolean;
57
+ onOpenChange?: (open: boolean) => void;
59
58
  }) {
60
- const isMobile = useIsMobile();
61
- const [openMobile, setOpenMobile] = React.useState(false);
62
-
63
- // This is the internal state of the sidebar.
64
- // We use openProp and setOpenProp for control from outside the component.
65
- const [_open, _setOpen] = React.useState(defaultOpen);
66
- const open = openProp ?? _open;
67
- const setOpen = React.useCallback(
68
- (value: boolean | ((value: boolean) => boolean)) => {
69
- const openState = typeof value === 'function' ? value(open) : value;
70
- if (setOpenProp) {
71
- setOpenProp(openState);
72
- } else {
73
- _setOpen(openState);
74
- }
75
-
76
- // This sets the cookie to keep the sidebar state.
77
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
78
- },
79
- [setOpenProp, open],
80
- );
81
-
82
- // Helper to toggle the sidebar.
83
- const toggleSidebar = React.useCallback(() => {
84
- return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
85
- }, [isMobile, setOpen, setOpenMobile]);
86
-
87
- // Adds a keyboard shortcut to toggle the sidebar.
88
- React.useEffect(() => {
89
- const handleKeyDown = (event: KeyboardEvent) => {
90
- if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
91
- event.preventDefault();
92
- toggleSidebar();
93
- }
94
- };
95
-
96
- window.addEventListener('keydown', handleKeyDown);
97
- return () => window.removeEventListener('keydown', handleKeyDown);
98
- }, [toggleSidebar]);
99
-
100
- // We add a state so that we can do data-state="expanded" or "collapsed".
101
- // This makes it easier to style the sidebar with Tailwind classes.
102
- const state = open ? 'expanded' : 'collapsed';
103
-
104
- const contextValue = React.useMemo<SidebarContextProps>(
105
- () => ({
106
- state,
107
- open,
108
- setOpen,
109
- isMobile,
110
- openMobile,
111
- setOpenMobile,
112
- toggleSidebar,
113
- }),
114
- [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
115
- );
116
-
117
- return (
118
- <SidebarContext.Provider value={contextValue}>
119
- <TooltipProvider delayDuration={0}>
120
- <div
121
- data-slot="sidebar-wrapper"
122
- style={
123
- {
124
- '--sidebar-width': SIDEBAR_WIDTH,
125
- '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
126
- ...style,
127
- } as React.CSSProperties
128
- }
129
- className={cn('group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full', className)}
130
- {...props}
131
- >
132
- {children}
133
- </div>
134
- </TooltipProvider>
135
- </SidebarContext.Provider>
136
- );
59
+ const isMobile = useIsMobile();
60
+ const [openMobile, setOpenMobile] = React.useState(false);
61
+
62
+ // This is the internal state of the sidebar.
63
+ // We use openProp and setOpenProp for control from outside the component.
64
+ const [_open, _setOpen] = React.useState(defaultOpen);
65
+ const open = openProp ?? _open;
66
+ const setOpen = React.useCallback(
67
+ (value: boolean | ((value: boolean) => boolean)) => {
68
+ const openState = typeof value === 'function' ? value(open) : value;
69
+ if (setOpenProp) {
70
+ setOpenProp(openState);
71
+ } else {
72
+ _setOpen(openState);
73
+ }
74
+
75
+ // biome-ignore lint/suspicious/noDocumentCookie: Persisting sidebar state via cookie is intentional for SSR hydration
76
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
77
+ },
78
+ [setOpenProp, open],
79
+ );
80
+
81
+ // Helper to toggle the sidebar.
82
+ const toggleSidebar = React.useCallback(() => {
83
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
84
+ }, [isMobile, setOpen]);
85
+
86
+ // Adds a keyboard shortcut to toggle the sidebar.
87
+ React.useEffect(() => {
88
+ const handleKeyDown = (event: KeyboardEvent) => {
89
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
90
+ event.preventDefault();
91
+ toggleSidebar();
92
+ }
93
+ };
94
+
95
+ window.addEventListener('keydown', handleKeyDown);
96
+ return () => window.removeEventListener('keydown', handleKeyDown);
97
+ }, [toggleSidebar]);
98
+
99
+ // We add a state so that we can do data-state="expanded" or "collapsed".
100
+ // This makes it easier to style the sidebar with Tailwind classes.
101
+ const state = open ? 'expanded' : 'collapsed';
102
+
103
+ const contextValue = React.useMemo<SidebarContextProps>(
104
+ () => ({
105
+ state,
106
+ open,
107
+ setOpen,
108
+ isMobile,
109
+ openMobile,
110
+ setOpenMobile,
111
+ toggleSidebar,
112
+ }),
113
+ [state, open, setOpen, isMobile, openMobile, toggleSidebar],
114
+ );
115
+
116
+ return (
117
+ <SidebarContext.Provider value={contextValue}>
118
+ <TooltipProvider delayDuration={0}>
119
+ <div
120
+ data-slot="sidebar-wrapper"
121
+ style={
122
+ {
123
+ '--sidebar-width': SIDEBAR_WIDTH,
124
+ '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
125
+ ...style,
126
+ } as React.CSSProperties
127
+ }
128
+ className={cn('group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full', className)}
129
+ {...props}
130
+ >
131
+ {children}
132
+ </div>
133
+ </TooltipProvider>
134
+ </SidebarContext.Provider>
135
+ );
137
136
  }
138
137
 
139
138
  /**
@@ -141,555 +140,555 @@ function SidebarProvider({
141
140
  * Supports left/right placement, multiple visual variants (sidebar, floating, inset), and collapsible modes (offcanvas, icon, none).
142
141
  */
143
142
  function Sidebar({
144
- /** Which edge of the viewport the sidebar is placed on. */
145
- side = 'left',
146
- /** Visual style: "sidebar" (bordered), "floating" (rounded with shadow), or "inset" (embedded). */
147
- variant = 'sidebar',
148
- /** Collapse behavior: "offcanvas" (slides off-screen), "icon" (collapses to icon width), or "none" (always visible). */
149
- collapsible = 'offcanvas',
150
- className,
151
- children,
152
- ...props
143
+ /** Which edge of the viewport the sidebar is placed on. */
144
+ side = 'left',
145
+ /** Visual style: "sidebar" (bordered), "floating" (rounded with shadow), or "inset" (embedded). */
146
+ variant = 'sidebar',
147
+ /** Collapse behavior: "offcanvas" (slides off-screen), "icon" (collapses to icon width), or "none" (always visible). */
148
+ collapsible = 'offcanvas',
149
+ className,
150
+ children,
151
+ ...props
153
152
  }: React.ComponentProps<'div'> & {
154
- side?: 'left' | 'right';
155
- variant?: 'sidebar' | 'floating' | 'inset';
156
- collapsible?: 'offcanvas' | 'icon' | 'none';
153
+ side?: 'left' | 'right';
154
+ variant?: 'sidebar' | 'floating' | 'inset';
155
+ collapsible?: 'offcanvas' | 'icon' | 'none';
157
156
  }) {
158
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
159
-
160
- if (collapsible === 'none') {
161
- return (
162
- <div
163
- data-slot="sidebar"
164
- className={cn('bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col', className)}
165
- {...props}
166
- >
167
- {children}
168
- </div>
169
- );
170
- }
171
-
172
- if (isMobile) {
173
- return (
174
- <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
175
- <SheetContent
176
- data-sidebar="sidebar"
177
- data-slot="sidebar"
178
- data-mobile="true"
179
- className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
180
- style={
181
- {
182
- '--sidebar-width': SIDEBAR_WIDTH_MOBILE,
183
- } as React.CSSProperties
184
- }
185
- side={side}
186
- >
187
- <SheetHeader className="sr-only">
188
- <SheetTitle>Sidebar</SheetTitle>
189
- <SheetDescription>Displays the mobile sidebar.</SheetDescription>
190
- </SheetHeader>
191
- <div className="flex h-full w-full flex-col">{children}</div>
192
- </SheetContent>
193
- </Sheet>
194
- );
195
- }
196
-
197
- return (
198
- <div
199
- className="group peer text-sidebar-foreground hidden md:block"
200
- data-state={state}
201
- data-collapsible={state === 'collapsed' ? collapsible : ''}
202
- data-variant={variant}
203
- data-side={side}
204
- data-slot="sidebar"
205
- >
206
- {/* This is what handles the sidebar gap on desktop */}
207
- <div
208
- data-slot="sidebar-gap"
209
- className={cn(
210
- 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
211
- 'group-data-[collapsible=offcanvas]:w-0',
212
- 'group-data-[side=right]:rotate-180',
213
- variant === 'floating' || variant === 'inset'
214
- ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
215
- : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
216
- )}
217
- />
218
- <div
219
- data-slot="sidebar-container"
220
- className={cn(
221
- 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',
222
- side === 'left'
223
- ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
224
- : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
225
- // Adjust the padding for floating and inset variants.
226
- variant === 'floating' || variant === 'inset'
227
- ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
228
- : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',
229
- className,
230
- )}
231
- {...props}
232
- >
233
- <div
234
- data-sidebar="sidebar"
235
- data-slot="sidebar-inner"
236
- className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
237
- >
238
- {children}
239
- </div>
240
- </div>
241
- </div>
242
- );
157
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
158
+
159
+ if (collapsible === 'none') {
160
+ return (
161
+ <div
162
+ data-slot="sidebar"
163
+ className={cn('bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col', className)}
164
+ {...props}
165
+ >
166
+ {children}
167
+ </div>
168
+ );
169
+ }
170
+
171
+ if (isMobile) {
172
+ return (
173
+ <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
174
+ <SheetContent
175
+ data-sidebar="sidebar"
176
+ data-slot="sidebar"
177
+ data-mobile="true"
178
+ className="bg-sidebar text-sidebar-foreground w-(--sidebar-width) p-0 [&>button]:hidden"
179
+ style={
180
+ {
181
+ '--sidebar-width': SIDEBAR_WIDTH_MOBILE,
182
+ } as React.CSSProperties
183
+ }
184
+ side={side}
185
+ >
186
+ <SheetHeader className="sr-only">
187
+ <SheetTitle>Sidebar</SheetTitle>
188
+ <SheetDescription>Displays the mobile sidebar.</SheetDescription>
189
+ </SheetHeader>
190
+ <div className="flex h-full w-full flex-col">{children}</div>
191
+ </SheetContent>
192
+ </Sheet>
193
+ );
194
+ }
195
+
196
+ return (
197
+ <div
198
+ className="group peer text-sidebar-foreground hidden md:block"
199
+ data-state={state}
200
+ data-collapsible={state === 'collapsed' ? collapsible : ''}
201
+ data-variant={variant}
202
+ data-side={side}
203
+ data-slot="sidebar"
204
+ >
205
+ {/* This is what handles the sidebar gap on desktop */}
206
+ <div
207
+ data-slot="sidebar-gap"
208
+ className={cn(
209
+ 'relative w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
210
+ 'group-data-[collapsible=offcanvas]:w-0',
211
+ 'group-data-[side=right]:rotate-180',
212
+ variant === 'floating' || variant === 'inset'
213
+ ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
214
+ : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)',
215
+ )}
216
+ />
217
+ <div
218
+ data-slot="sidebar-container"
219
+ className={cn(
220
+ 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',
221
+ side === 'left'
222
+ ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
223
+ : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
224
+ // Adjust the padding for floating and inset variants.
225
+ variant === 'floating' || variant === 'inset'
226
+ ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
227
+ : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',
228
+ className,
229
+ )}
230
+ {...props}
231
+ >
232
+ <div
233
+ data-sidebar="sidebar"
234
+ data-slot="sidebar-inner"
235
+ className="bg-sidebar group-data-[variant=floating]:border-sidebar-border flex h-full w-full flex-col group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:shadow-sm"
236
+ >
237
+ {children}
238
+ </div>
239
+ </div>
240
+ </div>
241
+ );
243
242
  }
244
243
 
245
244
  /** A button that toggles the sidebar open/closed state. */
246
245
  function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<typeof Button>) {
247
- const { toggleSidebar } = useSidebar();
248
-
249
- return (
250
- <Button
251
- data-sidebar="trigger"
252
- data-slot="sidebar-trigger"
253
- variant="ghost"
254
- size="icon"
255
- className={cn('size-7', className)}
256
- onClick={(event) => {
257
- onClick?.(event);
258
- toggleSidebar();
259
- }}
260
- {...props}
261
- >
262
- <PanelLeftIcon />
263
- <span className="sr-only">Toggle Sidebar</span>
264
- </Button>
265
- );
246
+ const { toggleSidebar } = useSidebar();
247
+
248
+ return (
249
+ <Button
250
+ data-sidebar="trigger"
251
+ data-slot="sidebar-trigger"
252
+ variant="ghost"
253
+ size="icon"
254
+ className={cn('size-7', className)}
255
+ onClick={(event) => {
256
+ onClick?.(event);
257
+ toggleSidebar();
258
+ }}
259
+ {...props}
260
+ >
261
+ <PanelLeftIcon />
262
+ <span className="sr-only">Toggle Sidebar</span>
263
+ </Button>
264
+ );
266
265
  }
267
266
 
268
267
  /** A thin hover-activated rail along the sidebar edge that toggles collapse on click. */
269
268
  function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
270
- const { toggleSidebar } = useSidebar();
271
-
272
- return (
273
- <button
274
- data-sidebar="rail"
275
- data-slot="sidebar-rail"
276
- aria-label="Toggle Sidebar"
277
- tabIndex={-1}
278
- onClick={toggleSidebar}
279
- title="Toggle Sidebar"
280
- className={cn(
281
- 'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex',
282
- 'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
283
- '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
284
- 'hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
285
- '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
286
- '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
287
- className,
288
- )}
289
- {...props}
290
- />
291
- );
269
+ const { toggleSidebar } = useSidebar();
270
+
271
+ return (
272
+ <button
273
+ data-sidebar="rail"
274
+ data-slot="sidebar-rail"
275
+ aria-label="Toggle Sidebar"
276
+ tabIndex={-1}
277
+ onClick={toggleSidebar}
278
+ title="Toggle Sidebar"
279
+ className={cn(
280
+ 'hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex',
281
+ 'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
282
+ '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
283
+ 'hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full',
284
+ '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
285
+ '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
286
+ className,
287
+ )}
288
+ {...props}
289
+ />
290
+ );
292
291
  }
293
292
 
294
293
  /** The main content area that sits alongside the sidebar, adjusting its margin based on sidebar state. */
295
294
  function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
296
- return (
297
- <main
298
- data-slot="sidebar-inset"
299
- className={cn(
300
- 'bg-background relative flex w-full flex-1 flex-col',
301
- 'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',
302
- className,
303
- )}
304
- {...props}
305
- />
306
- );
295
+ return (
296
+ <main
297
+ data-slot="sidebar-inset"
298
+ className={cn(
299
+ 'bg-background relative flex w-full flex-1 flex-col',
300
+ 'md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2',
301
+ className,
302
+ )}
303
+ {...props}
304
+ />
305
+ );
307
306
  }
308
307
 
309
308
  function SidebarInput({ className, ...props }: React.ComponentProps<typeof Input>) {
310
- return (
311
- <Input
312
- data-slot="sidebar-input"
313
- data-sidebar="input"
314
- className={cn('bg-background h-8 w-full shadow-none', className)}
315
- {...props}
316
- />
317
- );
309
+ return (
310
+ <Input
311
+ data-slot="sidebar-input"
312
+ data-sidebar="input"
313
+ className={cn('bg-background h-8 w-full shadow-none', className)}
314
+ {...props}
315
+ />
316
+ );
318
317
  }
319
318
 
320
319
  function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
321
- return (
322
- <div
323
- data-slot="sidebar-header"
324
- data-sidebar="header"
325
- className={cn('flex flex-col gap-2 p-2', className)}
326
- {...props}
327
- />
328
- );
320
+ return (
321
+ <div
322
+ data-slot="sidebar-header"
323
+ data-sidebar="header"
324
+ className={cn('flex flex-col gap-2 p-2', className)}
325
+ {...props}
326
+ />
327
+ );
329
328
  }
330
329
 
331
330
  function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
332
- return (
333
- <div
334
- data-slot="sidebar-footer"
335
- data-sidebar="footer"
336
- className={cn('flex flex-col gap-2 p-2', className)}
337
- {...props}
338
- />
339
- );
331
+ return (
332
+ <div
333
+ data-slot="sidebar-footer"
334
+ data-sidebar="footer"
335
+ className={cn('flex flex-col gap-2 p-2', className)}
336
+ {...props}
337
+ />
338
+ );
340
339
  }
341
340
 
342
341
  function SidebarSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {
343
- return (
344
- <Separator
345
- data-slot="sidebar-separator"
346
- data-sidebar="separator"
347
- className={cn('bg-sidebar-border mx-2 w-auto', className)}
348
- {...props}
349
- />
350
- );
342
+ return (
343
+ <Separator
344
+ data-slot="sidebar-separator"
345
+ data-sidebar="separator"
346
+ className={cn('bg-sidebar-border mx-2 w-auto', className)}
347
+ {...props}
348
+ />
349
+ );
351
350
  }
352
351
 
353
352
  /** The scrollable main body area of the sidebar that holds SidebarGroup sections. */
354
353
  function SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {
355
- return (
356
- <div
357
- data-slot="sidebar-content"
358
- data-sidebar="content"
359
- className={cn(
360
- 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
361
- className,
362
- )}
363
- {...props}
364
- />
365
- );
354
+ return (
355
+ <div
356
+ data-slot="sidebar-content"
357
+ data-sidebar="content"
358
+ className={cn(
359
+ 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
360
+ className,
361
+ )}
362
+ {...props}
363
+ />
364
+ );
366
365
  }
367
366
 
368
367
  /** A logical grouping of sidebar menu items, optionally with a label and action button. */
369
368
  function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
370
- return (
371
- <div
372
- data-slot="sidebar-group"
373
- data-sidebar="group"
374
- className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
375
- {...props}
376
- />
377
- );
369
+ return (
370
+ <div
371
+ data-slot="sidebar-group"
372
+ data-sidebar="group"
373
+ className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
374
+ {...props}
375
+ />
376
+ );
378
377
  }
379
378
 
380
379
  /** A small heading label for a SidebarGroup. Hidden when the sidebar is collapsed to icon mode. */
381
380
  function SidebarGroupLabel({
382
- className,
383
- asChild = false,
384
- ...props
381
+ className,
382
+ asChild = false,
383
+ ...props
385
384
  }: React.ComponentProps<'div'> & { asChild?: boolean }) {
386
- const Comp = asChild ? Slot.Root : 'div';
387
-
388
- return (
389
- <Comp
390
- data-slot="sidebar-group-label"
391
- data-sidebar="group-label"
392
- className={cn(
393
- 'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
394
- 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
395
- className,
396
- )}
397
- {...props}
398
- />
399
- );
385
+ const Comp = asChild ? Slot.Root : 'div';
386
+
387
+ return (
388
+ <Comp
389
+ data-slot="sidebar-group-label"
390
+ data-sidebar="group-label"
391
+ className={cn(
392
+ 'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
393
+ 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
394
+ className,
395
+ )}
396
+ {...props}
397
+ />
398
+ );
400
399
  }
401
400
 
402
401
  function SidebarGroupAction({
403
- className,
404
- asChild = false,
405
- ...props
402
+ className,
403
+ asChild = false,
404
+ ...props
406
405
  }: React.ComponentProps<'button'> & { asChild?: boolean }) {
407
- const Comp = asChild ? Slot.Root : 'button';
408
-
409
- return (
410
- <Comp
411
- data-slot="sidebar-group-action"
412
- data-sidebar="group-action"
413
- className={cn(
414
- 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
415
- // Increases the hit area of the button on mobile.
416
- 'after:absolute after:-inset-2 md:after:hidden',
417
- 'group-data-[collapsible=icon]:hidden',
418
- className,
419
- )}
420
- {...props}
421
- />
422
- );
406
+ const Comp = asChild ? Slot.Root : 'button';
407
+
408
+ return (
409
+ <Comp
410
+ data-slot="sidebar-group-action"
411
+ data-sidebar="group-action"
412
+ className={cn(
413
+ 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
414
+ // Increases the hit area of the button on mobile.
415
+ 'after:absolute after:-inset-2 md:after:hidden',
416
+ 'group-data-[collapsible=icon]:hidden',
417
+ className,
418
+ )}
419
+ {...props}
420
+ />
421
+ );
423
422
  }
424
423
 
425
424
  function SidebarGroupContent({ className, ...props }: React.ComponentProps<'div'>) {
426
- return (
427
- <div
428
- data-slot="sidebar-group-content"
429
- data-sidebar="group-content"
430
- className={cn('w-full text-sm', className)}
431
- {...props}
432
- />
433
- );
425
+ return (
426
+ <div
427
+ data-slot="sidebar-group-content"
428
+ data-sidebar="group-content"
429
+ className={cn('w-full text-sm', className)}
430
+ {...props}
431
+ />
432
+ );
434
433
  }
435
434
 
436
435
  function SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {
437
- return (
438
- <ul
439
- data-slot="sidebar-menu"
440
- data-sidebar="menu"
441
- className={cn('flex w-full min-w-0 flex-col gap-1', className)}
442
- {...props}
443
- />
444
- );
436
+ return (
437
+ <ul
438
+ data-slot="sidebar-menu"
439
+ data-sidebar="menu"
440
+ className={cn('flex w-full min-w-0 flex-col gap-1', className)}
441
+ {...props}
442
+ />
443
+ );
445
444
  }
446
445
 
447
446
  function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
448
- return (
449
- <li
450
- data-slot="sidebar-menu-item"
451
- data-sidebar="menu-item"
452
- className={cn('group/menu-item relative', className)}
453
- {...props}
454
- />
455
- );
447
+ return (
448
+ <li
449
+ data-slot="sidebar-menu-item"
450
+ data-sidebar="menu-item"
451
+ className={cn('group/menu-item relative', className)}
452
+ {...props}
453
+ />
454
+ );
456
455
  }
457
456
 
458
457
  const sidebarMenuButtonVariants = cva(
459
- 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
460
- {
461
- variants: {
462
- variant: {
463
- default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
464
- outline:
465
- 'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
466
- },
467
- size: {
468
- default: 'h-8 text-sm',
469
- sm: 'h-7 text-xs',
470
- lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
471
- },
472
- },
473
- defaultVariants: {
474
- variant: 'default',
475
- size: 'default',
476
- },
477
- },
458
+ 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
459
+ {
460
+ variants: {
461
+ variant: {
462
+ default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
463
+ outline:
464
+ 'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
465
+ },
466
+ size: {
467
+ default: 'h-8 text-sm',
468
+ sm: 'h-7 text-xs',
469
+ lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
470
+ },
471
+ },
472
+ defaultVariants: {
473
+ variant: 'default',
474
+ size: 'default',
475
+ },
476
+ },
478
477
  );
479
478
 
480
479
  /** An interactive menu item button with optional tooltip support when collapsed. Supports active state highlighting and size variants. */
481
480
  function SidebarMenuButton({
482
- asChild = false,
483
- isActive = false,
484
- variant = 'default',
485
- size = 'default',
486
- tooltip,
487
- className,
488
- ...props
481
+ asChild = false,
482
+ isActive = false,
483
+ variant = 'default',
484
+ size = 'default',
485
+ tooltip,
486
+ className,
487
+ ...props
489
488
  }: React.ComponentProps<'button'> & {
490
- asChild?: boolean;
491
- isActive?: boolean;
492
- tooltip?: string | React.ComponentProps<typeof TooltipContent>;
489
+ asChild?: boolean;
490
+ isActive?: boolean;
491
+ tooltip?: string | React.ComponentProps<typeof TooltipContent>;
493
492
  } & VariantProps<typeof sidebarMenuButtonVariants>) {
494
- const Comp = asChild ? Slot.Root : 'button';
495
- const { isMobile, state } = useSidebar();
496
-
497
- const button = (
498
- <Comp
499
- data-slot="sidebar-menu-button"
500
- data-sidebar="menu-button"
501
- data-size={size}
502
- data-active={isActive}
503
- className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
504
- {...props}
505
- />
506
- );
507
-
508
- if (!tooltip) {
509
- return button;
510
- }
511
-
512
- if (typeof tooltip === 'string') {
513
- tooltip = {
514
- children: tooltip,
515
- };
516
- }
517
-
518
- return (
519
- <Tooltip>
520
- <TooltipTrigger asChild>{button}</TooltipTrigger>
521
- <TooltipContent side="right" align="center" hidden={state !== 'collapsed' || isMobile} {...tooltip} />
522
- </Tooltip>
523
- );
493
+ const Comp = asChild ? Slot.Root : 'button';
494
+ const { isMobile, state } = useSidebar();
495
+
496
+ const button = (
497
+ <Comp
498
+ data-slot="sidebar-menu-button"
499
+ data-sidebar="menu-button"
500
+ data-size={size}
501
+ data-active={isActive}
502
+ className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
503
+ {...props}
504
+ />
505
+ );
506
+
507
+ if (!tooltip) {
508
+ return button;
509
+ }
510
+
511
+ if (typeof tooltip === 'string') {
512
+ tooltip = {
513
+ children: tooltip,
514
+ };
515
+ }
516
+
517
+ return (
518
+ <Tooltip>
519
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
520
+ <TooltipContent side="right" align="center" hidden={state !== 'collapsed' || isMobile} {...tooltip} />
521
+ </Tooltip>
522
+ );
524
523
  }
525
524
 
526
525
  /** An action button (e.g., delete, settings) positioned to the right of a SidebarMenuButton. */
527
526
  function SidebarMenuAction({
528
- className,
529
- asChild = false,
530
- showOnHover = false,
531
- ...props
527
+ className,
528
+ asChild = false,
529
+ showOnHover = false,
530
+ ...props
532
531
  }: React.ComponentProps<'button'> & {
533
- asChild?: boolean;
534
- showOnHover?: boolean;
532
+ asChild?: boolean;
533
+ showOnHover?: boolean;
535
534
  }) {
536
- const Comp = asChild ? Slot.Root : 'button';
537
-
538
- return (
539
- <Comp
540
- data-slot="sidebar-menu-action"
541
- data-sidebar="menu-action"
542
- className={cn(
543
- 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
544
- // Increases the hit area of the button on mobile.
545
- 'after:absolute after:-inset-2 md:after:hidden',
546
- 'peer-data-[size=sm]/menu-button:top-1',
547
- 'peer-data-[size=default]/menu-button:top-1.5',
548
- 'peer-data-[size=lg]/menu-button:top-2.5',
549
- 'group-data-[collapsible=icon]:hidden',
550
- showOnHover &&
551
- 'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
552
- className,
553
- )}
554
- {...props}
555
- />
556
- );
535
+ const Comp = asChild ? Slot.Root : 'button';
536
+
537
+ return (
538
+ <Comp
539
+ data-slot="sidebar-menu-action"
540
+ data-sidebar="menu-action"
541
+ className={cn(
542
+ 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
543
+ // Increases the hit area of the button on mobile.
544
+ 'after:absolute after:-inset-2 md:after:hidden',
545
+ 'peer-data-[size=sm]/menu-button:top-1',
546
+ 'peer-data-[size=default]/menu-button:top-1.5',
547
+ 'peer-data-[size=lg]/menu-button:top-2.5',
548
+ 'group-data-[collapsible=icon]:hidden',
549
+ showOnHover &&
550
+ 'peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0',
551
+ className,
552
+ )}
553
+ {...props}
554
+ />
555
+ );
557
556
  }
558
557
 
559
558
  function SidebarMenuBadge({ className, ...props }: React.ComponentProps<'div'>) {
560
- return (
561
- <div
562
- data-slot="sidebar-menu-badge"
563
- data-sidebar="menu-badge"
564
- className={cn(
565
- 'text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none',
566
- 'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
567
- 'peer-data-[size=sm]/menu-button:top-1',
568
- 'peer-data-[size=default]/menu-button:top-1.5',
569
- 'peer-data-[size=lg]/menu-button:top-2.5',
570
- 'group-data-[collapsible=icon]:hidden',
571
- className,
572
- )}
573
- {...props}
574
- />
575
- );
559
+ return (
560
+ <div
561
+ data-slot="sidebar-menu-badge"
562
+ data-sidebar="menu-badge"
563
+ className={cn(
564
+ 'text-sidebar-foreground pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums select-none',
565
+ 'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
566
+ 'peer-data-[size=sm]/menu-button:top-1',
567
+ 'peer-data-[size=default]/menu-button:top-1.5',
568
+ 'peer-data-[size=lg]/menu-button:top-2.5',
569
+ 'group-data-[collapsible=icon]:hidden',
570
+ className,
571
+ )}
572
+ {...props}
573
+ />
574
+ );
576
575
  }
577
576
 
578
577
  function SidebarMenuSkeleton({
579
- className,
580
- showIcon = false,
581
- ...props
578
+ className,
579
+ showIcon = false,
580
+ ...props
582
581
  }: React.ComponentProps<'div'> & {
583
- showIcon?: boolean;
582
+ showIcon?: boolean;
584
583
  }) {
585
- // Random width between 50 to 90%.
586
- const width = React.useMemo(() => {
587
- return `${Math.floor(Math.random() * 40) + 50}%`;
588
- }, []);
589
-
590
- return (
591
- <div
592
- data-slot="sidebar-menu-skeleton"
593
- data-sidebar="menu-skeleton"
594
- className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
595
- {...props}
596
- >
597
- {showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
598
- <Skeleton
599
- className="h-4 max-w-(--skeleton-width) flex-1"
600
- data-sidebar="menu-skeleton-text"
601
- style={
602
- {
603
- '--skeleton-width': width,
604
- } as React.CSSProperties
605
- }
606
- />
607
- </div>
608
- );
584
+ // Random width between 50 to 90%.
585
+ const width = React.useMemo(() => {
586
+ return `${Math.floor(Math.random() * 40) + 50}%`;
587
+ }, []);
588
+
589
+ return (
590
+ <div
591
+ data-slot="sidebar-menu-skeleton"
592
+ data-sidebar="menu-skeleton"
593
+ className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
594
+ {...props}
595
+ >
596
+ {showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
597
+ <Skeleton
598
+ className="h-4 max-w-(--skeleton-width) flex-1"
599
+ data-sidebar="menu-skeleton-text"
600
+ style={
601
+ {
602
+ '--skeleton-width': width,
603
+ } as React.CSSProperties
604
+ }
605
+ />
606
+ </div>
607
+ );
609
608
  }
610
609
 
611
610
  /** A nested sub-menu list indented under a parent menu item with a left border. */
612
611
  function SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {
613
- return (
614
- <ul
615
- data-slot="sidebar-menu-sub"
616
- data-sidebar="menu-sub"
617
- className={cn(
618
- 'border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5',
619
- 'group-data-[collapsible=icon]:hidden',
620
- className,
621
- )}
622
- {...props}
623
- />
624
- );
612
+ return (
613
+ <ul
614
+ data-slot="sidebar-menu-sub"
615
+ data-sidebar="menu-sub"
616
+ className={cn(
617
+ 'border-sidebar-border mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5',
618
+ 'group-data-[collapsible=icon]:hidden',
619
+ className,
620
+ )}
621
+ {...props}
622
+ />
623
+ );
625
624
  }
626
625
 
627
626
  function SidebarMenuSubItem({ className, ...props }: React.ComponentProps<'li'>) {
628
- return (
629
- <li
630
- data-slot="sidebar-menu-sub-item"
631
- data-sidebar="menu-sub-item"
632
- className={cn('group/menu-sub-item relative', className)}
633
- {...props}
634
- />
635
- );
627
+ return (
628
+ <li
629
+ data-slot="sidebar-menu-sub-item"
630
+ data-sidebar="menu-sub-item"
631
+ className={cn('group/menu-sub-item relative', className)}
632
+ {...props}
633
+ />
634
+ );
636
635
  }
637
636
 
638
637
  function SidebarMenuSubButton({
639
- asChild = false,
640
- size = 'md',
641
- isActive = false,
642
- className,
643
- ...props
638
+ asChild = false,
639
+ size = 'md',
640
+ isActive = false,
641
+ className,
642
+ ...props
644
643
  }: React.ComponentProps<'a'> & {
645
- asChild?: boolean;
646
- size?: 'sm' | 'md';
647
- isActive?: boolean;
644
+ asChild?: boolean;
645
+ size?: 'sm' | 'md';
646
+ isActive?: boolean;
648
647
  }) {
649
- const Comp = asChild ? Slot.Root : 'a';
650
-
651
- return (
652
- <Comp
653
- data-slot="sidebar-menu-sub-button"
654
- data-sidebar="menu-sub-button"
655
- data-size={size}
656
- data-active={isActive}
657
- className={cn(
658
- 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
659
- 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
660
- size === 'sm' && 'text-xs',
661
- size === 'md' && 'text-sm',
662
- 'group-data-[collapsible=icon]:hidden',
663
- className,
664
- )}
665
- {...props}
666
- />
667
- );
648
+ const Comp = asChild ? Slot.Root : 'a';
649
+
650
+ return (
651
+ <Comp
652
+ data-slot="sidebar-menu-sub-button"
653
+ data-sidebar="menu-sub-button"
654
+ data-size={size}
655
+ data-active={isActive}
656
+ className={cn(
657
+ 'text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
658
+ 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
659
+ size === 'sm' && 'text-xs',
660
+ size === 'md' && 'text-sm',
661
+ 'group-data-[collapsible=icon]:hidden',
662
+ className,
663
+ )}
664
+ {...props}
665
+ />
666
+ );
668
667
  }
669
668
 
670
669
  export {
671
- Sidebar,
672
- SidebarContent,
673
- SidebarFooter,
674
- SidebarGroup,
675
- SidebarGroupAction,
676
- SidebarGroupContent,
677
- SidebarGroupLabel,
678
- SidebarHeader,
679
- SidebarInput,
680
- SidebarInset,
681
- SidebarMenu,
682
- SidebarMenuAction,
683
- SidebarMenuBadge,
684
- SidebarMenuButton,
685
- SidebarMenuItem,
686
- SidebarMenuSkeleton,
687
- SidebarMenuSub,
688
- SidebarMenuSubButton,
689
- SidebarMenuSubItem,
690
- SidebarProvider,
691
- SidebarRail,
692
- SidebarSeparator,
693
- SidebarTrigger,
694
- useSidebar,
670
+ Sidebar,
671
+ SidebarContent,
672
+ SidebarFooter,
673
+ SidebarGroup,
674
+ SidebarGroupAction,
675
+ SidebarGroupContent,
676
+ SidebarGroupLabel,
677
+ SidebarHeader,
678
+ SidebarInput,
679
+ SidebarInset,
680
+ SidebarMenu,
681
+ SidebarMenuAction,
682
+ SidebarMenuBadge,
683
+ SidebarMenuButton,
684
+ SidebarMenuItem,
685
+ SidebarMenuSkeleton,
686
+ SidebarMenuSub,
687
+ SidebarMenuSubButton,
688
+ SidebarMenuSubItem,
689
+ SidebarProvider,
690
+ SidebarRail,
691
+ SidebarSeparator,
692
+ SidebarTrigger,
693
+ useSidebar,
695
694
  };