@hilum/ui 0.1.1

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 (99) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/dist/icons.d.ts +1 -0
  4. package/dist/icons.js +3 -0
  5. package/dist/icons.js.map +1 -0
  6. package/dist/index.d.ts +992 -0
  7. package/dist/index.js +4433 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/tokens.css +265 -0
  10. package/dist/tokens.d.ts +189 -0
  11. package/dist/tokens.js +193 -0
  12. package/dist/tokens.js.map +1 -0
  13. package/package.json +80 -0
  14. package/src/components/__tests__/data.test.tsx +180 -0
  15. package/src/components/__tests__/disclosure.test.tsx +198 -0
  16. package/src/components/__tests__/display.test.tsx +262 -0
  17. package/src/components/__tests__/feedback.test.tsx +138 -0
  18. package/src/components/__tests__/form.test.tsx +297 -0
  19. package/src/components/__tests__/layout.test.tsx +388 -0
  20. package/src/components/__tests__/overlay.test.tsx +90 -0
  21. package/src/components/__tests__/primitives.test.tsx +248 -0
  22. package/src/components/accordion.tsx +68 -0
  23. package/src/components/action-panel.tsx +112 -0
  24. package/src/components/activity-feed.tsx +69 -0
  25. package/src/components/alert-dialog.tsx +132 -0
  26. package/src/components/alert.tsx +44 -0
  27. package/src/components/aspect-ratio.tsx +19 -0
  28. package/src/components/avatar-stack.tsx +70 -0
  29. package/src/components/avatar.tsx +103 -0
  30. package/src/components/badge.tsx +33 -0
  31. package/src/components/breadcrumb.tsx +76 -0
  32. package/src/components/button-group.tsx +45 -0
  33. package/src/components/button.tsx +60 -0
  34. package/src/components/calendar.tsx +61 -0
  35. package/src/components/card-heading.tsx +60 -0
  36. package/src/components/card.tsx +120 -0
  37. package/src/components/carousel.tsx +222 -0
  38. package/src/components/chart.tsx +77 -0
  39. package/src/components/checkbox.tsx +35 -0
  40. package/src/components/collapsible.tsx +9 -0
  41. package/src/components/color-input.tsx +90 -0
  42. package/src/components/color-picker.tsx +111 -0
  43. package/src/components/combobox.tsx +207 -0
  44. package/src/components/command-palette.tsx +137 -0
  45. package/src/components/command.tsx +248 -0
  46. package/src/components/context-menu.tsx +201 -0
  47. package/src/components/data-table.tsx +151 -0
  48. package/src/components/date-picker.tsx +56 -0
  49. package/src/components/description-list.tsx +70 -0
  50. package/src/components/dialog.tsx +97 -0
  51. package/src/components/drawer.tsx +90 -0
  52. package/src/components/dropdown-menu.tsx +202 -0
  53. package/src/components/empty-state.tsx +43 -0
  54. package/src/components/field.tsx +45 -0
  55. package/src/components/grid-list.tsx +159 -0
  56. package/src/components/hover-card.tsx +34 -0
  57. package/src/components/input-group.tsx +134 -0
  58. package/src/components/input-number.tsx +142 -0
  59. package/src/components/input-otp.tsx +68 -0
  60. package/src/components/input.tsx +23 -0
  61. package/src/components/kbd.tsx +18 -0
  62. package/src/components/label.tsx +23 -0
  63. package/src/components/media-object.tsx +49 -0
  64. package/src/components/menubar.tsx +235 -0
  65. package/src/components/native-select.tsx +44 -0
  66. package/src/components/navigation-menu.tsx +158 -0
  67. package/src/components/notification.tsx +109 -0
  68. package/src/components/page-heading.tsx +125 -0
  69. package/src/components/pagination.tsx +89 -0
  70. package/src/components/popover.tsx +35 -0
  71. package/src/components/progress.tsx +23 -0
  72. package/src/components/property-row.tsx +56 -0
  73. package/src/components/radio-card.tsx +105 -0
  74. package/src/components/radio-group.tsx +38 -0
  75. package/src/components/resizable.tsx +74 -0
  76. package/src/components/scroll-area.tsx +42 -0
  77. package/src/components/section-heading.tsx +64 -0
  78. package/src/components/select.tsx +130 -0
  79. package/src/components/separator.tsx +25 -0
  80. package/src/components/sheet.tsx +117 -0
  81. package/src/components/sidebar.tsx +636 -0
  82. package/src/components/skeleton.tsx +14 -0
  83. package/src/components/slider.tsx +24 -0
  84. package/src/components/sonner.tsx +34 -0
  85. package/src/components/spinner.tsx +37 -0
  86. package/src/components/stacked-list.tsx +64 -0
  87. package/src/components/stat-card.tsx +54 -0
  88. package/src/components/steps.tsx +179 -0
  89. package/src/components/switch.tsx +33 -0
  90. package/src/components/table.tsx +101 -0
  91. package/src/components/tabs.tsx +55 -0
  92. package/src/components/textarea.tsx +21 -0
  93. package/src/components/toggle-group.tsx +49 -0
  94. package/src/components/toggle.tsx +66 -0
  95. package/src/components/tooltip.tsx +43 -0
  96. package/src/icons.ts +158 -0
  97. package/src/index.ts +84 -0
  98. package/src/lib/utils.ts +6 -0
  99. package/src/tokens/tokens.ts +194 -0
@@ -0,0 +1,636 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { PanelLeft } from "lucide-react";
5
+ import { Slot } from "radix-ui";
6
+ import { cn } from "../lib/utils";
7
+ import { Button } from "./button";
8
+ import { Separator } from "./separator";
9
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
10
+
11
+ /* ------------------------------------------------------------------ */
12
+ /* Constants */
13
+ /* ------------------------------------------------------------------ */
14
+
15
+ const SIDEBAR_COOKIE_NAME = "sidebar:state";
16
+ const SIDEBAR_WIDTH = "16rem";
17
+ const SIDEBAR_WIDTH_ICON = "3.5rem";
18
+
19
+ /* ------------------------------------------------------------------ */
20
+ /* Context */
21
+ /* ------------------------------------------------------------------ */
22
+
23
+ interface SidebarContextValue {
24
+ state: "expanded" | "collapsed";
25
+ open: boolean;
26
+ setOpen: (open: boolean) => void;
27
+ toggleSidebar: () => void;
28
+ }
29
+
30
+ const SidebarContext = React.createContext<SidebarContextValue | null>(null);
31
+
32
+ function useSidebar() {
33
+ const ctx = React.useContext(SidebarContext);
34
+ if (!ctx) throw new Error("useSidebar must be used inside SidebarProvider");
35
+ return ctx;
36
+ }
37
+
38
+ /* ------------------------------------------------------------------ */
39
+ /* SidebarProvider */
40
+ /* ------------------------------------------------------------------ */
41
+
42
+ interface SidebarProviderProps extends React.ComponentProps<"div"> {
43
+ defaultOpen?: boolean;
44
+ open?: boolean;
45
+ onOpenChange?: (open: boolean) => void;
46
+ }
47
+
48
+ function SidebarProvider({
49
+ defaultOpen = true,
50
+ open: openProp,
51
+ onOpenChange,
52
+ className,
53
+ style,
54
+ children,
55
+ ...props
56
+ }: SidebarProviderProps) {
57
+ const [_open, _setOpen] = React.useState(defaultOpen);
58
+
59
+ const open = openProp !== undefined ? openProp : _open;
60
+
61
+ const setOpen = React.useCallback(
62
+ (value: boolean) => {
63
+ if (onOpenChange) {
64
+ onOpenChange(value);
65
+ } else {
66
+ _setOpen(value);
67
+ }
68
+ // Persist to cookie
69
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${value}; path=/; max-age=${60 * 60 * 24 * 7}`;
70
+ },
71
+ [onOpenChange],
72
+ );
73
+
74
+ const toggleSidebar = React.useCallback(() => {
75
+ setOpen(!open);
76
+ }, [open, setOpen]);
77
+
78
+ const state = open ? "expanded" : "collapsed";
79
+
80
+ const contextValue = React.useMemo<SidebarContextValue>(
81
+ () => ({ state, open, setOpen, toggleSidebar }),
82
+ [state, open, setOpen, toggleSidebar],
83
+ );
84
+
85
+ return (
86
+ <SidebarContext.Provider value={contextValue}>
87
+ <TooltipProvider delayDuration={0}>
88
+ <div
89
+ data-sidebar="provider"
90
+ data-state={state}
91
+ className={cn("group/sidebar-wrapper flex min-h-svh w-full", className)}
92
+ style={
93
+ {
94
+ "--sidebar-width": SIDEBAR_WIDTH,
95
+ "--sidebar-width-icon": SIDEBAR_WIDTH_ICON,
96
+ ...style,
97
+ } as React.CSSProperties
98
+ }
99
+ {...props}
100
+ >
101
+ {children}
102
+ </div>
103
+ </TooltipProvider>
104
+ </SidebarContext.Provider>
105
+ );
106
+ }
107
+
108
+ /* ------------------------------------------------------------------ */
109
+ /* Sidebar */
110
+ /* ------------------------------------------------------------------ */
111
+
112
+ interface SidebarProps extends React.ComponentProps<"aside"> {
113
+ side?: "left" | "right";
114
+ variant?: "sidebar" | "floating" | "inset";
115
+ collapsible?: "offcanvas" | "icon" | "none";
116
+ }
117
+
118
+ function Sidebar({
119
+ side = "left",
120
+ variant = "sidebar",
121
+ collapsible = "icon",
122
+ className,
123
+ children,
124
+ ...props
125
+ }: SidebarProps) {
126
+ const { state } = useSidebar();
127
+
128
+ if (collapsible === "none") {
129
+ return (
130
+ <aside
131
+ data-side={side}
132
+ data-variant={variant}
133
+ data-collapsible="none"
134
+ className={cn(
135
+ "flex flex-col h-svh bg-white border-r border-taupe-100 w-[--sidebar-width]",
136
+ side === "right" && "border-r-0 border-l",
137
+ className,
138
+ )}
139
+ {...props}
140
+ >
141
+ {children}
142
+ </aside>
143
+ );
144
+ }
145
+
146
+ if (collapsible === "offcanvas") {
147
+ return (
148
+ <aside
149
+ data-state={state}
150
+ data-side={side}
151
+ data-variant={variant}
152
+ data-collapsible="offcanvas"
153
+ className={cn(
154
+ "group peer flex flex-col h-svh bg-white border-r border-taupe-100",
155
+ "transition-[width,transform] duration-200 ease-linear",
156
+ "data-[state=expanded]:w-[--sidebar-width] data-[state=collapsed]:w-0 data-[state=collapsed]:-translate-x-full",
157
+ side === "right" && "border-r-0 border-l data-[state=collapsed]:translate-x-full",
158
+ className,
159
+ )}
160
+ {...props}
161
+ >
162
+ {children}
163
+ </aside>
164
+ );
165
+ }
166
+
167
+ // collapsible === "icon" (default)
168
+ return (
169
+ <aside
170
+ data-state={state}
171
+ data-side={side}
172
+ data-variant={variant}
173
+ data-collapsible="icon"
174
+ className={cn(
175
+ "group peer hidden md:flex flex-col h-svh bg-white border-r border-taupe-100",
176
+ "transition-[width] duration-200 ease-linear overflow-hidden",
177
+ "data-[state=expanded]:w-[--sidebar-width] data-[state=collapsed]:w-[--sidebar-width-icon]",
178
+ side === "right" && "border-r-0 border-l",
179
+ variant === "floating" && "m-2 h-[calc(100svh-1rem)] rounded-xl border shadow-natural",
180
+ variant === "inset" && "border-r-0",
181
+ className,
182
+ )}
183
+ {...props}
184
+ >
185
+ {children}
186
+ </aside>
187
+ );
188
+ }
189
+
190
+ /* ------------------------------------------------------------------ */
191
+ /* SidebarTrigger */
192
+ /* ------------------------------------------------------------------ */
193
+
194
+ function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<typeof Button>) {
195
+ const { toggleSidebar } = useSidebar();
196
+
197
+ return (
198
+ <Button
199
+ data-sidebar="trigger"
200
+ variant="ghost"
201
+ size="sm"
202
+ className={cn("h-7 w-7", className)}
203
+ onClick={(e) => {
204
+ onClick?.(e);
205
+ toggleSidebar();
206
+ }}
207
+ {...props}
208
+ >
209
+ <PanelLeft size={16} />
210
+ <span className="sr-only">Toggle sidebar</span>
211
+ </Button>
212
+ );
213
+ }
214
+
215
+ /* ------------------------------------------------------------------ */
216
+ /* SidebarRail */
217
+ /* ------------------------------------------------------------------ */
218
+
219
+ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
220
+ const { toggleSidebar } = useSidebar();
221
+
222
+ return (
223
+ <button
224
+ data-sidebar="rail"
225
+ aria-label="Toggle sidebar"
226
+ tabIndex={-1}
227
+ onClick={toggleSidebar}
228
+ title="Toggle sidebar"
229
+ className={cn(
230
+ "absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 cursor-col-resize transition-all ease-linear",
231
+ "after:absolute after:inset-y-0 after:left-1/2 after:w-[2px]",
232
+ "hover:after:bg-brand-primary/30",
233
+ "group-data-[side=left]:-right-4 group-data-[side=right]:left-0",
234
+ "sm:flex",
235
+ className,
236
+ )}
237
+ {...props}
238
+ />
239
+ );
240
+ }
241
+
242
+ /* ------------------------------------------------------------------ */
243
+ /* SidebarInset */
244
+ /* ------------------------------------------------------------------ */
245
+
246
+ function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
247
+ return (
248
+ <main
249
+ data-sidebar="inset"
250
+ className={cn("relative flex min-h-svh flex-1 flex-col bg-white overflow-hidden", className)}
251
+ {...props}
252
+ />
253
+ );
254
+ }
255
+
256
+ /* ------------------------------------------------------------------ */
257
+ /* Structure components */
258
+ /* ------------------------------------------------------------------ */
259
+
260
+ function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
261
+ return (
262
+ <div
263
+ data-sidebar="header"
264
+ className={cn("flex flex-col gap-2 p-3 border-b border-taupe-100", className)}
265
+ {...props}
266
+ />
267
+ );
268
+ }
269
+
270
+ function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
271
+ return (
272
+ <div
273
+ data-sidebar="content"
274
+ className={cn("flex min-h-0 flex-1 flex-col gap-2 overflow-auto p-3", className)}
275
+ {...props}
276
+ />
277
+ );
278
+ }
279
+
280
+ function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
281
+ return (
282
+ <div
283
+ data-sidebar="footer"
284
+ className={cn("flex flex-col gap-2 p-3 border-t border-taupe-100", className)}
285
+ {...props}
286
+ />
287
+ );
288
+ }
289
+
290
+ function SidebarSeparator({ className, ...props }: React.ComponentProps<typeof Separator>) {
291
+ return (
292
+ <Separator
293
+ data-sidebar="separator"
294
+ className={cn("mx-2 my-1 bg-taupe-100", className)}
295
+ {...props}
296
+ />
297
+ );
298
+ }
299
+
300
+ /* ------------------------------------------------------------------ */
301
+ /* Group components */
302
+ /* ------------------------------------------------------------------ */
303
+
304
+ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
305
+ return (
306
+ <div
307
+ data-sidebar="group"
308
+ className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
309
+ {...props}
310
+ />
311
+ );
312
+ }
313
+
314
+ function SidebarGroupLabel({
315
+ className,
316
+ asChild = false,
317
+ ...props
318
+ }: React.ComponentProps<"div"> & { asChild?: boolean }) {
319
+ const Comp = asChild ? Slot.Root : "div";
320
+
321
+ return (
322
+ <Comp
323
+ data-sidebar="group-label"
324
+ className={cn(
325
+ "flex h-8 shrink-0 items-center rounded-md px-2 label text-taupe-400",
326
+ "outline-none ring-taupe-900 transition-[margin,opacity] duration-200 ease-linear",
327
+ "group-data-[state=collapsed]/sidebar-wrapper:opacity-0",
328
+ "group-data-[state=collapsed]/sidebar-wrapper:h-0",
329
+ "group-data-[state=collapsed]/sidebar-wrapper:m-0",
330
+ "group-data-[state=collapsed]/sidebar-wrapper:p-0",
331
+ "group-data-[state=collapsed]/sidebar-wrapper:overflow-hidden",
332
+ className,
333
+ )}
334
+ {...props}
335
+ />
336
+ );
337
+ }
338
+
339
+ function SidebarGroupContent({ className, ...props }: React.ComponentProps<"div">) {
340
+ return (
341
+ <div data-sidebar="group-content" className={cn("w-full text-sm", className)} {...props} />
342
+ );
343
+ }
344
+
345
+ function SidebarGroupAction({
346
+ className,
347
+ asChild = false,
348
+ ...props
349
+ }: React.ComponentProps<"button"> & { asChild?: boolean }) {
350
+ const Comp = asChild ? Slot.Root : "button";
351
+
352
+ return (
353
+ <Comp
354
+ data-sidebar="group-action"
355
+ className={cn(
356
+ "absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center",
357
+ "rounded-md text-taupe-400 hover:bg-taupe-100 hover:text-taupe-900",
358
+ "outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
359
+ className,
360
+ )}
361
+ {...props}
362
+ />
363
+ );
364
+ }
365
+
366
+ /* ------------------------------------------------------------------ */
367
+ /* Menu components */
368
+ /* ------------------------------------------------------------------ */
369
+
370
+ function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
371
+ return (
372
+ <ul
373
+ data-sidebar="menu"
374
+ className={cn("flex w-full min-w-0 flex-col gap-1", className)}
375
+ {...props}
376
+ />
377
+ );
378
+ }
379
+
380
+ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
381
+ return (
382
+ <li data-sidebar="menu-item" className={cn("group/menu-item relative", className)} {...props} />
383
+ );
384
+ }
385
+
386
+ /* ------------------------------------------------------------------ */
387
+ /* SidebarMenuButton */
388
+ /* ------------------------------------------------------------------ */
389
+
390
+ interface SidebarMenuButtonProps extends React.ComponentProps<"button"> {
391
+ asChild?: boolean;
392
+ isActive?: boolean;
393
+ tooltip?: string;
394
+ size?: "default" | "sm" | "lg";
395
+ }
396
+
397
+ function SidebarMenuButton({
398
+ asChild = false,
399
+ isActive = false,
400
+ tooltip,
401
+ size = "default",
402
+ className,
403
+ children,
404
+ ...props
405
+ }: SidebarMenuButtonProps) {
406
+ const Comp = asChild ? (Slot.Root as React.ElementType) : "button";
407
+
408
+ const button = (
409
+ <Comp
410
+ data-sidebar="menu-button"
411
+ data-active={isActive}
412
+ data-size={size}
413
+ className={cn(
414
+ // Base
415
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 body text-left",
416
+ "outline-none ring-taupe-900",
417
+ "transition-[width,height,padding] duration-150",
418
+ "hover:bg-taupe-100 hover:text-taupe-900",
419
+ "focus-visible:ring-2 focus-visible:ring-brand-primary/30",
420
+ "active:bg-taupe-100",
421
+ "disabled:pointer-events-none disabled:opacity-50",
422
+ "[&>svg]:size-4 [&>svg]:shrink-0",
423
+ // Size variants
424
+ size === "sm" && "h-7 text-xs",
425
+ size === "lg" && "h-10",
426
+ // Active state
427
+ isActive && [
428
+ "bg-brand-primary/10 text-brand-primary",
429
+ "hover:bg-brand-primary/15 hover:text-brand-primary",
430
+ ],
431
+ // Collapsed state
432
+ "group-data-[state=collapsed]/sidebar-wrapper:justify-center",
433
+ "group-data-[state=collapsed]/sidebar-wrapper:size-9",
434
+ "group-data-[state=collapsed]/sidebar-wrapper:p-0",
435
+ className,
436
+ )}
437
+ {...props}
438
+ >
439
+ {children}
440
+ </Comp>
441
+ );
442
+
443
+ if (!tooltip) {
444
+ return button;
445
+ }
446
+
447
+ return (
448
+ <Tooltip>
449
+ <TooltipTrigger asChild>{button}</TooltipTrigger>
450
+ <TooltipContent
451
+ side="right"
452
+ className={cn(
453
+ // Only visible when collapsed
454
+ "hidden group-data-[state=collapsed]/sidebar-wrapper:block",
455
+ )}
456
+ >
457
+ {tooltip}
458
+ </TooltipContent>
459
+ </Tooltip>
460
+ );
461
+ }
462
+
463
+ /* ------------------------------------------------------------------ */
464
+ /* SidebarMenuAction */
465
+ /* ------------------------------------------------------------------ */
466
+
467
+ interface SidebarMenuActionProps extends React.ComponentProps<"button"> {
468
+ asChild?: boolean;
469
+ showOnHover?: boolean;
470
+ }
471
+
472
+ function SidebarMenuAction({
473
+ className,
474
+ asChild = false,
475
+ showOnHover = false,
476
+ ...props
477
+ }: SidebarMenuActionProps) {
478
+ const Comp = asChild ? (Slot.Root as React.ElementType) : "button";
479
+
480
+ return (
481
+ <Comp
482
+ data-sidebar="menu-action"
483
+ className={cn(
484
+ "absolute right-1 top-1/2 -translate-y-1/2 flex aspect-square w-5 items-center justify-center",
485
+ "rounded-md text-taupe-400 hover:bg-taupe-100 hover:text-taupe-900",
486
+ "outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
487
+ showOnHover && [
488
+ "opacity-0 transition-opacity",
489
+ "group-hover/menu-item:opacity-100",
490
+ "peer-data-[active=true]/menu-button:opacity-100",
491
+ ],
492
+ "group-data-[state=collapsed]/sidebar-wrapper:hidden",
493
+ className,
494
+ )}
495
+ {...props}
496
+ />
497
+ );
498
+ }
499
+
500
+ /* ------------------------------------------------------------------ */
501
+ /* SidebarMenuBadge */
502
+ /* ------------------------------------------------------------------ */
503
+
504
+ function SidebarMenuBadge({ className, ...props }: React.ComponentProps<"div">) {
505
+ return (
506
+ <div
507
+ data-sidebar="menu-badge"
508
+ className={cn(
509
+ "absolute right-1 flex h-5 min-w-5 items-center justify-center rounded-full",
510
+ "caption font-medium bg-taupe-100 text-taupe-700 px-1",
511
+ "group-data-[state=collapsed]/sidebar-wrapper:hidden",
512
+ className,
513
+ )}
514
+ {...props}
515
+ />
516
+ );
517
+ }
518
+
519
+ /* ------------------------------------------------------------------ */
520
+ /* SidebarMenuSkeleton */
521
+ /* ------------------------------------------------------------------ */
522
+
523
+ function SidebarMenuSkeleton({
524
+ className,
525
+ showIcon = false,
526
+ ...props
527
+ }: React.ComponentProps<"div"> & { showIcon?: boolean }) {
528
+ const width = React.useMemo(() => {
529
+ return `${Math.floor(Math.random() * 40) + 50}%`;
530
+ }, []);
531
+
532
+ return (
533
+ <div
534
+ data-sidebar="menu-skeleton"
535
+ className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
536
+ {...props}
537
+ >
538
+ {showIcon && <div className="size-4 shrink-0 rounded-md bg-taupe-200 animate-pulse" />}
539
+ <div
540
+ className="h-4 max-w-[--skeleton-width] flex-1 rounded-md bg-taupe-200 animate-pulse"
541
+ style={{ "--skeleton-width": width } as React.CSSProperties}
542
+ />
543
+ </div>
544
+ );
545
+ }
546
+
547
+ /* ------------------------------------------------------------------ */
548
+ /* Sub menu */
549
+ /* ------------------------------------------------------------------ */
550
+
551
+ function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
552
+ return (
553
+ <ul
554
+ data-sidebar="menu-sub"
555
+ className={cn(
556
+ "mx-3.5 flex min-w-0 flex-col gap-1 border-l border-taupe-100 pl-3",
557
+ "group-data-[state=collapsed]/sidebar-wrapper:hidden",
558
+ className,
559
+ )}
560
+ {...props}
561
+ />
562
+ );
563
+ }
564
+
565
+ function SidebarMenuSubItem({ className, ...props }: React.ComponentProps<"li">) {
566
+ return (
567
+ <li
568
+ data-sidebar="menu-sub-item"
569
+ className={cn("group/menu-sub-item relative", className)}
570
+ {...props}
571
+ />
572
+ );
573
+ }
574
+
575
+ interface SidebarMenuSubButtonProps extends React.ComponentProps<"a"> {
576
+ asChild?: boolean;
577
+ isActive?: boolean;
578
+ }
579
+
580
+ function SidebarMenuSubButton({
581
+ asChild = false,
582
+ isActive = false,
583
+ className,
584
+ ...props
585
+ }: SidebarMenuSubButtonProps) {
586
+ const Comp = asChild ? (Slot.Root as React.ElementType) : "a";
587
+
588
+ return (
589
+ <Comp
590
+ data-sidebar="menu-sub-button"
591
+ data-active={isActive}
592
+ className={cn(
593
+ "flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2",
594
+ "body text-taupe-500 outline-none",
595
+ "transition-colors duration-150",
596
+ "hover:text-taupe-900 hover:bg-taupe-100",
597
+ "focus-visible:ring-2 focus-visible:ring-brand-primary/30",
598
+ "disabled:pointer-events-none disabled:opacity-50",
599
+ "[&>svg]:size-3.5 [&>svg]:shrink-0",
600
+ isActive && "text-brand-primary hover:text-brand-primary",
601
+ className,
602
+ )}
603
+ {...props}
604
+ />
605
+ );
606
+ }
607
+
608
+ /* ------------------------------------------------------------------ */
609
+ /* Exports */
610
+ /* ------------------------------------------------------------------ */
611
+
612
+ export {
613
+ SidebarProvider,
614
+ Sidebar,
615
+ SidebarContent,
616
+ SidebarFooter,
617
+ SidebarGroup,
618
+ SidebarGroupAction,
619
+ SidebarGroupContent,
620
+ SidebarGroupLabel,
621
+ SidebarHeader,
622
+ SidebarInset,
623
+ SidebarMenu,
624
+ SidebarMenuAction,
625
+ SidebarMenuBadge,
626
+ SidebarMenuButton,
627
+ SidebarMenuItem,
628
+ SidebarMenuSkeleton,
629
+ SidebarMenuSub,
630
+ SidebarMenuSubButton,
631
+ SidebarMenuSubItem,
632
+ SidebarRail,
633
+ SidebarSeparator,
634
+ SidebarTrigger,
635
+ useSidebar,
636
+ };
@@ -0,0 +1,14 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
5
+ return (
6
+ <div
7
+ data-slot="skeleton"
8
+ className={cn("animate-pulse rounded-md bg-taupe-100", className)}
9
+ {...props}
10
+ />
11
+ );
12
+ }
13
+
14
+ export { Skeleton };
@@ -0,0 +1,24 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Slider } from "radix-ui";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const SliderRoot = React.forwardRef<
8
+ React.ComponentRef<typeof Slider.Root>,
9
+ React.ComponentPropsWithoutRef<typeof Slider.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <Slider.Root
12
+ ref={ref}
13
+ className={cn("relative flex w-full touch-none select-none items-center", className)}
14
+ {...props}
15
+ >
16
+ <Slider.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-taupe-100">
17
+ <Slider.Range className="absolute h-full bg-brand-primary" />
18
+ </Slider.Track>
19
+ <Slider.Thumb className="block size-4 rounded-full border-2 border-brand-primary bg-white shadow-natural transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30 disabled:pointer-events-none disabled:opacity-50" />
20
+ </Slider.Root>
21
+ ));
22
+ SliderRoot.displayName = "Slider";
23
+
24
+ export { SliderRoot as Slider };
@@ -0,0 +1,34 @@
1
+ "use client";
2
+
3
+ import { Toaster as SonnerToaster } from "sonner";
4
+
5
+ type ToasterProps = React.ComponentProps<typeof SonnerToaster>;
6
+
7
+ function Toaster({ ...props }: ToasterProps) {
8
+ return (
9
+ <SonnerToaster
10
+ theme="light"
11
+ className="toaster group"
12
+ toastOptions={{
13
+ classNames: {
14
+ toast:
15
+ "group toast font-sans text-sm group-[.toaster]:bg-white group-[.toaster]:text-taupe-900 group-[.toaster]:border group-[.toaster]:border-taupe-100 group-[.toaster]:shadow-elevated group-[.toaster]:rounded-xl",
16
+ description: "group-[.toast]:text-taupe-500 group-[.toast]:text-xs",
17
+ actionButton:
18
+ "group-[.toast]:bg-brand-primary group-[.toast]:text-white group-[.toast]:rounded-md group-[.toast]:text-xs group-[.toast]:font-medium",
19
+ cancelButton:
20
+ "group-[.toast]:bg-taupe-100 group-[.toast]:text-taupe-600 group-[.toast]:rounded-md group-[.toast]:text-xs group-[.toast]:font-medium",
21
+ success: "group-[.toaster]:border-brand-secondary/40 group-[.toaster]:text-taupe-900",
22
+ error:
23
+ "group-[.toaster]:bg-red-50 group-[.toaster]:text-red-700 group-[.toaster]:border-red-200",
24
+ warning:
25
+ "group-[.toaster]:bg-yellow-50 group-[.toaster]:text-yellow-800 group-[.toaster]:border-yellow-200",
26
+ info: "group-[.toaster]:border-taupe-200",
27
+ },
28
+ }}
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ export { Toaster };