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