@tuturuuu/ui 0.0.4

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 (104) hide show
  1. package/.checksum +1 -0
  2. package/README.md +46 -0
  3. package/components.json +20 -0
  4. package/eslint.config.mjs +20 -0
  5. package/jsr.json +10 -0
  6. package/package.json +120 -0
  7. package/postcss.config.mjs +8 -0
  8. package/rollup.config.js +40 -0
  9. package/src/components/ui/accordion.tsx +70 -0
  10. package/src/components/ui/alert-dialog.tsx +156 -0
  11. package/src/components/ui/alert.tsx +58 -0
  12. package/src/components/ui/aspect-ratio.tsx +11 -0
  13. package/src/components/ui/avatar.tsx +52 -0
  14. package/src/components/ui/badge.tsx +49 -0
  15. package/src/components/ui/breadcrumb.tsx +108 -0
  16. package/src/components/ui/button.tsx +61 -0
  17. package/src/components/ui/calendar.tsx +212 -0
  18. package/src/components/ui/card.tsx +74 -0
  19. package/src/components/ui/carousel.tsx +240 -0
  20. package/src/components/ui/chart.tsx +365 -0
  21. package/src/components/ui/checkbox.tsx +31 -0
  22. package/src/components/ui/codeblock.tsx +161 -0
  23. package/src/components/ui/collapsible.tsx +33 -0
  24. package/src/components/ui/color-picker.tsx +143 -0
  25. package/src/components/ui/command.tsx +176 -0
  26. package/src/components/ui/context-menu.tsx +251 -0
  27. package/src/components/ui/custom/autosize-textarea.tsx +111 -0
  28. package/src/components/ui/custom/calendar/core.tsx +61 -0
  29. package/src/components/ui/custom/calendar/day-cell.tsx +74 -0
  30. package/src/components/ui/custom/calendar/month-header.tsx +59 -0
  31. package/src/components/ui/custom/calendar/month-view.tsx +110 -0
  32. package/src/components/ui/custom/calendar/utils.ts +76 -0
  33. package/src/components/ui/custom/calendar/year-calendar.tsx +64 -0
  34. package/src/components/ui/custom/calendar/year-view.tsx +58 -0
  35. package/src/components/ui/custom/combobox.tsx +197 -0
  36. package/src/components/ui/custom/common-footer.tsx +215 -0
  37. package/src/components/ui/custom/compared-date-range-picker.tsx +561 -0
  38. package/src/components/ui/custom/date-input.tsx +279 -0
  39. package/src/components/ui/custom/empty-card.tsx +39 -0
  40. package/src/components/ui/custom/feature-summary.tsx +135 -0
  41. package/src/components/ui/custom/file-uploader.tsx +349 -0
  42. package/src/components/ui/custom/input-field.tsx +29 -0
  43. package/src/components/ui/custom/loading-indicator.tsx +28 -0
  44. package/src/components/ui/custom/modifiable-dialog-trigger.tsx +83 -0
  45. package/src/components/ui/custom/month-picker.tsx +157 -0
  46. package/src/components/ui/custom/report-preview.tsx +175 -0
  47. package/src/components/ui/custom/search-bar.tsx +56 -0
  48. package/src/components/ui/custom/select-field.tsx +78 -0
  49. package/src/components/ui/custom/tables/data-table-column-header.tsx +72 -0
  50. package/src/components/ui/custom/tables/data-table-create-button.tsx +31 -0
  51. package/src/components/ui/custom/tables/data-table-faceted-filter.tsx +142 -0
  52. package/src/components/ui/custom/tables/data-table-pagination.tsx +243 -0
  53. package/src/components/ui/custom/tables/data-table-refresh-button.tsx +45 -0
  54. package/src/components/ui/custom/tables/data-table-toolbar.tsx +133 -0
  55. package/src/components/ui/custom/tables/data-table-view-options.tsx +112 -0
  56. package/src/components/ui/custom/tables/data-table.tsx +228 -0
  57. package/src/components/ui/custom/uploaded-files-card.tsx +50 -0
  58. package/src/components/ui/dialog.tsx +137 -0
  59. package/src/components/ui/drawer.tsx +131 -0
  60. package/src/components/ui/dropdown-menu.tsx +256 -0
  61. package/src/components/ui/form.tsx +167 -0
  62. package/src/components/ui/hover-card.tsx +41 -0
  63. package/src/components/ui/icons.tsx +506 -0
  64. package/src/components/ui/input-otp.tsx +78 -0
  65. package/src/components/ui/input.tsx +18 -0
  66. package/src/components/ui/label.tsx +23 -0
  67. package/src/components/ui/markdown.tsx +7 -0
  68. package/src/components/ui/menubar.tsx +275 -0
  69. package/src/components/ui/navigation-menu.tsx +169 -0
  70. package/src/components/ui/pagination.tsx +126 -0
  71. package/src/components/ui/popover.tsx +47 -0
  72. package/src/components/ui/progress.tsx +30 -0
  73. package/src/components/ui/radio-group.tsx +44 -0
  74. package/src/components/ui/resizable.tsx +55 -0
  75. package/src/components/ui/scroll-area.tsx +57 -0
  76. package/src/components/ui/select.tsx +180 -0
  77. package/src/components/ui/separator.tsx +27 -0
  78. package/src/components/ui/sheet.tsx +138 -0
  79. package/src/components/ui/sidebar.tsx +734 -0
  80. package/src/components/ui/skeleton.tsx +13 -0
  81. package/src/components/ui/slider.tsx +62 -0
  82. package/src/components/ui/sonner.tsx +29 -0
  83. package/src/components/ui/switch.tsx +30 -0
  84. package/src/components/ui/table.tsx +112 -0
  85. package/src/components/ui/tabs.tsx +68 -0
  86. package/src/components/ui/tag-input.tsx +141 -0
  87. package/src/components/ui/textarea.tsx +17 -0
  88. package/src/components/ui/time-picker-input.tsx +117 -0
  89. package/src/components/ui/time-picker-utils.tsx +146 -0
  90. package/src/components/ui/toast.tsx +128 -0
  91. package/src/components/ui/toaster.tsx +35 -0
  92. package/src/components/ui/toggle-group.tsx +72 -0
  93. package/src/components/ui/toggle.tsx +46 -0
  94. package/src/components/ui/tooltip.tsx +60 -0
  95. package/src/globals.css +252 -0
  96. package/src/hooks/use-callback-ref.ts +28 -0
  97. package/src/hooks/use-controllable-state.ts +68 -0
  98. package/src/hooks/use-copy-to-clipboard.ts +46 -0
  99. package/src/hooks/use-form.ts +23 -0
  100. package/src/hooks/use-forwarded-ref.ts +17 -0
  101. package/src/hooks/use-mobile.tsx +21 -0
  102. package/src/hooks/use-toast.ts +191 -0
  103. package/src/resolvers.ts +3 -0
  104. package/tsconfig.json +17 -0
@@ -0,0 +1,734 @@
1
+ 'use client';
2
+
3
+ import { useIsMobile } from '../../hooks/use-mobile';
4
+ import { Button } from './button';
5
+ import { Input } from './input';
6
+ import { Separator } from './separator';
7
+ import {
8
+ Sheet,
9
+ SheetContent,
10
+ SheetDescription,
11
+ SheetHeader,
12
+ SheetTitle,
13
+ } from './sheet';
14
+ import { Skeleton } from './skeleton';
15
+ import {
16
+ Tooltip,
17
+ TooltipContent,
18
+ TooltipProvider,
19
+ TooltipTrigger,
20
+ } from './tooltip';
21
+ import { Slot } from '@radix-ui/react-slot';
22
+ import { cn } from '@tuturuuu/utils/format';
23
+ import { VariantProps, cva } from 'class-variance-authority';
24
+ import { PanelLeftIcon } from 'lucide-react';
25
+ import * as React from 'react';
26
+
27
+ const SIDEBAR_COOKIE_NAME = 'sidebar_state';
28
+ const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
29
+ const SIDEBAR_WIDTH = '16rem';
30
+ const SIDEBAR_WIDTH_MOBILE = '18rem';
31
+ const SIDEBAR_WIDTH_ICON = '3rem';
32
+ const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
33
+
34
+ type SidebarContext = {
35
+ state: 'expanded' | 'collapsed';
36
+ open: boolean;
37
+ setOpen: (open: boolean) => void;
38
+ openMobile: boolean;
39
+ setOpenMobile: (open: boolean) => void;
40
+ isMobile: boolean;
41
+ toggleSidebar: () => void;
42
+ };
43
+
44
+ const SidebarContext = React.createContext<SidebarContext | null>(null);
45
+
46
+ function useSidebar() {
47
+ const context = React.useContext(SidebarContext);
48
+ if (!context) {
49
+ throw new Error('useSidebar must be used within a SidebarProvider.');
50
+ }
51
+
52
+ return context;
53
+ }
54
+
55
+ const SidebarProvider = React.forwardRef<
56
+ HTMLDivElement,
57
+ React.ComponentProps<'div'> & {
58
+ defaultOpen?: boolean;
59
+ open?: boolean;
60
+ onOpenChange?: (open: boolean) => void;
61
+ }
62
+ >(
63
+ (
64
+ {
65
+ defaultOpen = true,
66
+ open: openProp,
67
+ onOpenChange: setOpenProp,
68
+ className,
69
+ style,
70
+ children,
71
+ ...props
72
+ },
73
+ ref
74
+ ) => {
75
+ const isMobile = useIsMobile();
76
+ const [openMobile, setOpenMobile] = React.useState(false);
77
+
78
+ // This is the internal state of the sidebar.
79
+ // We use openProp and setOpenProp for control from outside the component.
80
+ const [_open, _setOpen] = React.useState(defaultOpen);
81
+ const open = openProp ?? _open;
82
+ const setOpen = React.useCallback(
83
+ (value: boolean | ((value: boolean) => boolean)) => {
84
+ const openState = typeof value === 'function' ? value(open) : value;
85
+ if (setOpenProp) {
86
+ setOpenProp(openState);
87
+ } else {
88
+ _setOpen(openState);
89
+ }
90
+
91
+ // This sets the cookie to keep the sidebar state.
92
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
93
+ },
94
+ [setOpenProp, open]
95
+ );
96
+
97
+ // Helper to toggle the sidebar.
98
+ const toggleSidebar = React.useCallback(() => {
99
+ return isMobile
100
+ ? setOpenMobile((open) => !open)
101
+ : setOpen((open) => !open);
102
+ }, [isMobile, setOpen, setOpenMobile]);
103
+
104
+ // Adds a keyboard shortcut to toggle the sidebar.
105
+ React.useEffect(() => {
106
+ const handleKeyDown = (event: KeyboardEvent) => {
107
+ if (
108
+ event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
109
+ (event.metaKey || event.ctrlKey)
110
+ ) {
111
+ event.preventDefault();
112
+ toggleSidebar();
113
+ }
114
+ };
115
+
116
+ window.addEventListener('keydown', handleKeyDown);
117
+ return () => window.removeEventListener('keydown', handleKeyDown);
118
+ }, [toggleSidebar]);
119
+
120
+ // We add a state so that we can do data-state="expanded" or "collapsed".
121
+ // This makes it easier to style the sidebar with Tailwind classes.
122
+ const state = open ? 'expanded' : 'collapsed';
123
+
124
+ const contextValue = React.useMemo<SidebarContext>(
125
+ () => ({
126
+ state,
127
+ open,
128
+ setOpen,
129
+ isMobile,
130
+ openMobile,
131
+ setOpenMobile,
132
+ toggleSidebar,
133
+ }),
134
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
135
+ );
136
+
137
+ return (
138
+ <SidebarContext.Provider value={contextValue}>
139
+ <TooltipProvider delayDuration={0}>
140
+ <div
141
+ data-slot="sidebar-wrapper"
142
+ style={
143
+ {
144
+ '--sidebar-width': SIDEBAR_WIDTH,
145
+ '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
146
+ ...style,
147
+ } as React.CSSProperties
148
+ }
149
+ className={cn(
150
+ 'group/sidebar-wrapper flex min-h-svh w-full has-data-[variant=inset]:bg-sidebar',
151
+ className
152
+ )}
153
+ ref={ref}
154
+ {...props}
155
+ >
156
+ {children}
157
+ </div>
158
+ </TooltipProvider>
159
+ </SidebarContext.Provider>
160
+ );
161
+ }
162
+ );
163
+ SidebarProvider.displayName = 'SidebarProvider';
164
+
165
+ function Sidebar({
166
+ side = 'left',
167
+ variant = 'sidebar',
168
+ collapsible = 'offcanvas',
169
+ className,
170
+ children,
171
+ ...props
172
+ }: React.ComponentProps<'div'> & {
173
+ side?: 'left' | 'right';
174
+ variant?: 'sidebar' | 'floating' | 'inset';
175
+ collapsible?: 'offcanvas' | 'icon' | 'none';
176
+ }) {
177
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
178
+
179
+ if (collapsible === 'none') {
180
+ return (
181
+ <div
182
+ data-slot="sidebar"
183
+ className={cn(
184
+ 'flex h-full w-(--sidebar-width) flex-col bg-sidebar text-sidebar-foreground',
185
+ className
186
+ )}
187
+ {...props}
188
+ >
189
+ {children}
190
+ </div>
191
+ );
192
+ }
193
+
194
+ if (isMobile) {
195
+ return (
196
+ <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
197
+ <SheetHeader className="sr-only">
198
+ <SheetTitle>Sidebar</SheetTitle>
199
+ <SheetDescription>Displays the mobile sidebar.</SheetDescription>
200
+ </SheetHeader>
201
+ <SheetContent
202
+ data-sidebar="sidebar"
203
+ data-slot="sidebar"
204
+ data-mobile="true"
205
+ className="w-(--sidebar-width) bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
206
+ style={
207
+ {
208
+ '--sidebar-width': SIDEBAR_WIDTH_MOBILE,
209
+ } as React.CSSProperties
210
+ }
211
+ side={side}
212
+ >
213
+ <div className="flex h-full w-full flex-col">{children}</div>
214
+ </SheetContent>
215
+ </Sheet>
216
+ );
217
+ }
218
+
219
+ return (
220
+ <div
221
+ className="group peer hidden text-sidebar-foreground md:block"
222
+ data-state={state}
223
+ data-collapsible={state === 'collapsed' ? collapsible : ''}
224
+ data-variant={variant}
225
+ data-side={side}
226
+ data-slot="sidebar"
227
+ >
228
+ {/* This is what handles the sidebar gap on desktop */}
229
+ <div
230
+ className={cn(
231
+ 'relative h-svh w-(--sidebar-width) bg-transparent transition-[width] duration-200 ease-linear',
232
+ 'group-data-[collapsible=offcanvas]:w-0',
233
+ 'group-data-[side=right]:rotate-180',
234
+ variant === 'floating' || variant === 'inset'
235
+ ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4)))]'
236
+ : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon)'
237
+ )}
238
+ />
239
+ <div
240
+ className={cn(
241
+ 'fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] duration-200 ease-linear md:flex',
242
+ side === 'left'
243
+ ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
244
+ : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
245
+ // Adjust the padding for floating and inset variants.
246
+ variant === 'floating' || variant === 'inset'
247
+ ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]'
248
+ : 'group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l',
249
+ className
250
+ )}
251
+ {...props}
252
+ >
253
+ <div
254
+ data-sidebar="sidebar"
255
+ className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow-sm"
256
+ >
257
+ {children}
258
+ </div>
259
+ </div>
260
+ </div>
261
+ );
262
+ }
263
+
264
+ function SidebarTrigger({
265
+ className,
266
+ onClick,
267
+ ...props
268
+ }: React.ComponentProps<typeof Button>) {
269
+ const { toggleSidebar } = useSidebar();
270
+
271
+ return (
272
+ <Button
273
+ data-sidebar="trigger"
274
+ data-slot="sidebar-trigger"
275
+ variant="ghost"
276
+ size="icon"
277
+ className={cn('h-7 w-7', className)}
278
+ onClick={(event) => {
279
+ onClick?.(event);
280
+ toggleSidebar();
281
+ }}
282
+ {...props}
283
+ >
284
+ <PanelLeftIcon />
285
+ <span className="sr-only">Toggle Sidebar</span>
286
+ </Button>
287
+ );
288
+ }
289
+
290
+ function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
291
+ const { toggleSidebar } = useSidebar();
292
+
293
+ return (
294
+ <button
295
+ data-sidebar="rail"
296
+ data-slot="sidebar-rail"
297
+ aria-label="Toggle Sidebar"
298
+ tabIndex={-1}
299
+ onClick={toggleSidebar}
300
+ title="Toggle Sidebar"
301
+ className={cn(
302
+ '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] hover:after:bg-sidebar-border sm:flex',
303
+ 'in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize',
304
+ '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
305
+ 'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full hover:group-data-[collapsible=offcanvas]:bg-sidebar',
306
+ '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
307
+ '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
308
+ className
309
+ )}
310
+ {...props}
311
+ />
312
+ );
313
+ }
314
+
315
+ function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
316
+ return (
317
+ <main
318
+ data-slot="sidebar-inset"
319
+ className={cn(
320
+ 'relative flex min-h-svh flex-1 flex-col bg-background',
321
+ 'peer-data-[variant=inset]:min-h-[calc(100svh-(--spacing(4)))] 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',
322
+ className
323
+ )}
324
+ {...props}
325
+ />
326
+ );
327
+ }
328
+
329
+ function SidebarInput({
330
+ className,
331
+ ...props
332
+ }: React.ComponentProps<typeof Input>) {
333
+ return (
334
+ <Input
335
+ data-slot="sidebar-input"
336
+ data-sidebar="input"
337
+ className={cn('h-8 w-full bg-background shadow-none', className)}
338
+ {...props}
339
+ />
340
+ );
341
+ }
342
+
343
+ function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
344
+ return (
345
+ <div
346
+ data-slot="sidebar-header"
347
+ data-sidebar="header"
348
+ className={cn('flex flex-col gap-2 p-2', className)}
349
+ {...props}
350
+ />
351
+ );
352
+ }
353
+
354
+ function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
355
+ return (
356
+ <div
357
+ data-slot="sidebar-footer"
358
+ data-sidebar="footer"
359
+ className={cn('flex flex-col gap-2 p-2', className)}
360
+ {...props}
361
+ />
362
+ );
363
+ }
364
+
365
+ function SidebarSeparator({
366
+ className,
367
+ ...props
368
+ }: React.ComponentProps<typeof Separator>) {
369
+ return (
370
+ <Separator
371
+ data-slot="sidebar-separator"
372
+ data-sidebar="separator"
373
+ className={cn('mx-2 w-auto bg-sidebar-border', className)}
374
+ {...props}
375
+ />
376
+ );
377
+ }
378
+
379
+ function SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {
380
+ return (
381
+ <div
382
+ data-slot="sidebar-content"
383
+ data-sidebar="content"
384
+ className={cn(
385
+ 'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
386
+ className
387
+ )}
388
+ {...props}
389
+ />
390
+ );
391
+ }
392
+
393
+ function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
394
+ return (
395
+ <div
396
+ data-slot="sidebar-group"
397
+ data-sidebar="group"
398
+ className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
399
+ {...props}
400
+ />
401
+ );
402
+ }
403
+
404
+ function SidebarGroupLabel({
405
+ className,
406
+ asChild = false,
407
+ ...props
408
+ }: React.ComponentProps<'div'> & { asChild?: boolean }) {
409
+ const Comp = asChild ? Slot : 'div';
410
+
411
+ return (
412
+ <Comp
413
+ data-slot="sidebar-group-label"
414
+ data-sidebar="group-label"
415
+ className={cn(
416
+ 'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 ring-sidebar-ring outline-hidden transition-[margin,opa] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
417
+ 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
418
+ className
419
+ )}
420
+ {...props}
421
+ />
422
+ );
423
+ }
424
+
425
+ function SidebarGroupAction({
426
+ className,
427
+ asChild = false,
428
+ ...props
429
+ }: React.ComponentProps<'button'> & { asChild?: boolean }) {
430
+ const Comp = asChild ? Slot : 'button';
431
+
432
+ return (
433
+ <Comp
434
+ data-slot="sidebar-group-action"
435
+ data-sidebar="group-action"
436
+ className={cn(
437
+ 'absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
438
+ // Increases the hit area of the button on mobile.
439
+ 'after:absolute after:-inset-2 md:after:hidden',
440
+ 'group-data-[collapsible=icon]:hidden',
441
+ className
442
+ )}
443
+ {...props}
444
+ />
445
+ );
446
+ }
447
+
448
+ function SidebarGroupContent({
449
+ className,
450
+ ...props
451
+ }: React.ComponentProps<'div'>) {
452
+ return (
453
+ <div
454
+ data-slot="sidebar-group-content"
455
+ data-sidebar="group-content"
456
+ className={cn('w-full text-sm', className)}
457
+ {...props}
458
+ />
459
+ );
460
+ }
461
+
462
+ function SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {
463
+ return (
464
+ <ul
465
+ data-slot="sidebar-menu"
466
+ data-sidebar="menu"
467
+ className={cn('flex w-full min-w-0 flex-col gap-1', className)}
468
+ {...props}
469
+ />
470
+ );
471
+ }
472
+
473
+ function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
474
+ return (
475
+ <li
476
+ data-slot="sidebar-menu-item"
477
+ data-sidebar="menu-item"
478
+ className={cn('group/menu-item relative', className)}
479
+ {...props}
480
+ />
481
+ );
482
+ }
483
+
484
+ const sidebarMenuButtonVariants = cva(
485
+ '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',
486
+ {
487
+ variants: {
488
+ variant: {
489
+ default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
490
+ outline:
491
+ '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))]',
492
+ },
493
+ size: {
494
+ default: 'h-8 text-sm',
495
+ sm: 'h-7 text-xs',
496
+ lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!',
497
+ },
498
+ },
499
+ defaultVariants: {
500
+ variant: 'default',
501
+ size: 'default',
502
+ },
503
+ }
504
+ );
505
+
506
+ function SidebarMenuButton({
507
+ asChild = false,
508
+ isActive = false,
509
+ variant = 'default',
510
+ size = 'default',
511
+ tooltip,
512
+ className,
513
+ ...props
514
+ }: React.ComponentProps<'button'> & {
515
+ asChild?: boolean;
516
+ isActive?: boolean;
517
+ tooltip?: string | React.ComponentProps<typeof TooltipContent>;
518
+ } & VariantProps<typeof sidebarMenuButtonVariants>) {
519
+ const Comp = asChild ? Slot : 'button';
520
+ const { isMobile, state } = useSidebar();
521
+
522
+ const button = (
523
+ <Comp
524
+ data-slot="sidebar-menu-button"
525
+ data-sidebar="menu-button"
526
+ data-size={size}
527
+ data-active={isActive}
528
+ className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
529
+ {...props}
530
+ />
531
+ );
532
+
533
+ if (!tooltip) {
534
+ return button;
535
+ }
536
+
537
+ if (typeof tooltip === 'string') {
538
+ tooltip = {
539
+ children: tooltip,
540
+ };
541
+ }
542
+
543
+ return (
544
+ <Tooltip>
545
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
546
+ <TooltipContent
547
+ side="right"
548
+ align="center"
549
+ hidden={state !== 'collapsed' || isMobile}
550
+ {...tooltip}
551
+ />
552
+ </Tooltip>
553
+ );
554
+ }
555
+
556
+ function SidebarMenuAction({
557
+ className,
558
+ asChild = false,
559
+ showOnHover = false,
560
+ ...props
561
+ }: React.ComponentProps<'button'> & {
562
+ asChild?: boolean;
563
+ showOnHover?: boolean;
564
+ }) {
565
+ const Comp = asChild ? Slot : 'button';
566
+
567
+ return (
568
+ <Comp
569
+ data-slot="sidebar-menu-action"
570
+ data-sidebar="menu-action"
571
+ className={cn(
572
+ 'absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground ring-sidebar-ring outline-hidden transition-transform peer-hover/menu-button:text-sidebar-accent-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
573
+ // Increases the hit area of the button on mobile.
574
+ 'after:absolute after:-inset-2 md:after:hidden',
575
+ 'peer-data-[size=sm]/menu-button:top-1',
576
+ 'peer-data-[size=default]/menu-button:top-1.5',
577
+ 'peer-data-[size=lg]/menu-button:top-2.5',
578
+ 'group-data-[collapsible=icon]:hidden',
579
+ showOnHover &&
580
+ 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground data-[state=open]:opacity-100 md:opacity-0',
581
+ className
582
+ )}
583
+ {...props}
584
+ />
585
+ );
586
+ }
587
+
588
+ function SidebarMenuBadge({
589
+ className,
590
+ ...props
591
+ }: React.ComponentProps<'div'>) {
592
+ return (
593
+ <div
594
+ data-slot="sidebar-menu-badge"
595
+ data-sidebar="menu-badge"
596
+ className={cn(
597
+ 'pointer-events-none absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-md px-1 text-xs font-medium text-sidebar-foreground tabular-nums select-none',
598
+ 'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
599
+ 'peer-data-[size=sm]/menu-button:top-1',
600
+ 'peer-data-[size=default]/menu-button:top-1.5',
601
+ 'peer-data-[size=lg]/menu-button:top-2.5',
602
+ 'group-data-[collapsible=icon]:hidden',
603
+ className
604
+ )}
605
+ {...props}
606
+ />
607
+ );
608
+ }
609
+
610
+ function SidebarMenuSkeleton({
611
+ className,
612
+ showIcon = false,
613
+ ...props
614
+ }: React.ComponentProps<'div'> & {
615
+ showIcon?: boolean;
616
+ }) {
617
+ // Random width between 50 to 90%.
618
+ const width = React.useMemo(() => {
619
+ return `${Math.floor(Math.random() * 40) + 50}%`;
620
+ }, []);
621
+
622
+ return (
623
+ <div
624
+ data-slot="sidebar-menu-skeleton"
625
+ data-sidebar="menu-skeleton"
626
+ className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
627
+ {...props}
628
+ >
629
+ {showIcon && (
630
+ <Skeleton
631
+ className="size-4 rounded-md"
632
+ data-sidebar="menu-skeleton-icon"
633
+ />
634
+ )}
635
+ <Skeleton
636
+ className="h-4 max-w-(--skeleton-width) flex-1"
637
+ data-sidebar="menu-skeleton-text"
638
+ style={
639
+ {
640
+ '--skeleton-width': width,
641
+ } as React.CSSProperties
642
+ }
643
+ />
644
+ </div>
645
+ );
646
+ }
647
+
648
+ function SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {
649
+ return (
650
+ <ul
651
+ data-slot="sidebar-menu-sub"
652
+ data-sidebar="menu-sub"
653
+ className={cn(
654
+ 'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5',
655
+ 'group-data-[collapsible=icon]:hidden',
656
+ className
657
+ )}
658
+ {...props}
659
+ />
660
+ );
661
+ }
662
+
663
+ function SidebarMenuSubItem({
664
+ className,
665
+ ...props
666
+ }: React.ComponentProps<'li'>) {
667
+ return (
668
+ <li
669
+ data-slot="sidebar-menu-sub-item"
670
+ data-sidebar="menu-sub-item"
671
+ className={cn('group/menu-sub-item relative', className)}
672
+ {...props}
673
+ />
674
+ );
675
+ }
676
+
677
+ function SidebarMenuSubButton({
678
+ asChild = false,
679
+ size = 'md',
680
+ isActive = false,
681
+ className,
682
+ ...props
683
+ }: React.ComponentProps<'a'> & {
684
+ asChild?: boolean;
685
+ size?: 'sm' | 'md';
686
+ isActive?: boolean;
687
+ }) {
688
+ const Comp = asChild ? Slot : 'a';
689
+
690
+ return (
691
+ <Comp
692
+ data-slot="sidebar-menu-sub-button"
693
+ data-sidebar="menu-sub-button"
694
+ data-size={size}
695
+ data-active={isActive}
696
+ className={cn(
697
+ 'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground ring-sidebar-ring outline-hidden 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 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
698
+ 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
699
+ size === 'sm' && 'text-xs',
700
+ size === 'md' && 'text-sm',
701
+ 'group-data-[collapsible=icon]:hidden',
702
+ className
703
+ )}
704
+ {...props}
705
+ />
706
+ );
707
+ }
708
+
709
+ export {
710
+ Sidebar,
711
+ SidebarContent,
712
+ SidebarFooter,
713
+ SidebarGroup,
714
+ SidebarGroupAction,
715
+ SidebarGroupContent,
716
+ SidebarGroupLabel,
717
+ SidebarHeader,
718
+ SidebarInput,
719
+ SidebarInset,
720
+ SidebarMenu,
721
+ SidebarMenuAction,
722
+ SidebarMenuBadge,
723
+ SidebarMenuButton,
724
+ SidebarMenuItem,
725
+ SidebarMenuSkeleton,
726
+ SidebarMenuSub,
727
+ SidebarMenuSubButton,
728
+ SidebarMenuSubItem,
729
+ SidebarProvider,
730
+ SidebarRail,
731
+ SidebarSeparator,
732
+ SidebarTrigger,
733
+ useSidebar,
734
+ };