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