@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,105 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Check } from "lucide-react";
5
+ import { cn } from "../lib/utils";
6
+
7
+ export interface RadioCardOption {
8
+ value: string;
9
+ label: string;
10
+ description?: string;
11
+ meta?: string;
12
+ icon?: React.ReactNode;
13
+ disabled?: boolean;
14
+ }
15
+
16
+ interface RadioCardsProps {
17
+ options: RadioCardOption[];
18
+ value?: string;
19
+ onValueChange?: (value: string) => void;
20
+ columns?: 1 | 2 | 3 | 4;
21
+ className?: string;
22
+ }
23
+
24
+ function RadioCards({ options, value, onValueChange, columns = 3, className }: RadioCardsProps) {
25
+ const colMap = {
26
+ 1: "grid-cols-1",
27
+ 2: "grid-cols-1 sm:grid-cols-2",
28
+ 3: "grid-cols-1 sm:grid-cols-3",
29
+ 4: "grid-cols-2 sm:grid-cols-4",
30
+ };
31
+
32
+ return (
33
+ <div className={cn("grid gap-3", colMap[columns], className)}>
34
+ {options.map((option) => {
35
+ const isSelected = option.value === value;
36
+ return (
37
+ <button
38
+ key={option.value}
39
+ type="button"
40
+ role="radio"
41
+ aria-checked={isSelected}
42
+ disabled={option.disabled}
43
+ onClick={() => !option.disabled && onValueChange?.(option.value)}
44
+ className={cn(
45
+ "relative flex cursor-pointer flex-col gap-1 rounded-xl border p-4 text-left transition-all",
46
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
47
+ "disabled:cursor-not-allowed disabled:opacity-50",
48
+ isSelected
49
+ ? "border-brand-primary bg-white shadow-natural"
50
+ : "border-taupe-200 bg-white hover:border-taupe-400",
51
+ )}
52
+ >
53
+ {/* Selected ring overlay */}
54
+ {isSelected && (
55
+ <span
56
+ className="pointer-events-none absolute inset-0 rounded-xl ring-2 ring-brand-primary"
57
+ aria-hidden="true"
58
+ />
59
+ )}
60
+
61
+ <div className="flex items-start justify-between gap-2">
62
+ <div className="min-w-0 flex-1">
63
+ {option.icon && <div className="mb-2 text-taupe-500">{option.icon}</div>}
64
+ <p
65
+ className={cn(
66
+ "body font-semibold",
67
+ isSelected ? "text-taupe-900" : "text-taupe-800",
68
+ )}
69
+ >
70
+ {option.label}
71
+ </p>
72
+ {option.description && (
73
+ <p className="mt-0.5 caption text-taupe-400">{option.description}</p>
74
+ )}
75
+ </div>
76
+ <div
77
+ className={cn(
78
+ "flex size-5 shrink-0 items-center justify-center rounded-full border-2 transition-colors",
79
+ isSelected
80
+ ? "border-brand-primary bg-brand-primary"
81
+ : "border-taupe-300 bg-white",
82
+ )}
83
+ >
84
+ {isSelected && <Check size={10} className="text-white" strokeWidth={3} />}
85
+ </div>
86
+ </div>
87
+
88
+ {option.meta && (
89
+ <p
90
+ className={cn(
91
+ "caption font-semibold mt-2",
92
+ isSelected ? "text-taupe-900" : "text-taupe-400",
93
+ )}
94
+ >
95
+ {option.meta}
96
+ </p>
97
+ )}
98
+ </button>
99
+ );
100
+ })}
101
+ </div>
102
+ );
103
+ }
104
+
105
+ export { RadioCards };
@@ -0,0 +1,38 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { RadioGroup } from "radix-ui";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const RadioGroupRoot = React.forwardRef<
8
+ React.ComponentRef<typeof RadioGroup.Root>,
9
+ React.ComponentPropsWithoutRef<typeof RadioGroup.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <RadioGroup.Root ref={ref} className={cn("grid gap-2", className)} {...props} />
12
+ ));
13
+ RadioGroupRoot.displayName = "RadioGroup";
14
+
15
+ const RadioGroupItem = React.forwardRef<
16
+ React.ComponentRef<typeof RadioGroup.Item>,
17
+ React.ComponentPropsWithoutRef<typeof RadioGroup.Item>
18
+ >(({ className, ...props }, ref) => (
19
+ <RadioGroup.Item
20
+ ref={ref}
21
+ className={cn(
22
+ "aspect-square size-4 rounded-full border border-taupe-300 text-taupe-900",
23
+ "shadow-none transition-colors",
24
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-taupe-900/20",
25
+ "disabled:cursor-not-allowed disabled:opacity-50",
26
+ "data-[state=checked]:bg-taupe-900 data-[state=checked]:border-taupe-900",
27
+ className,
28
+ )}
29
+ {...props}
30
+ >
31
+ <RadioGroup.Indicator className="flex items-center justify-center">
32
+ <div className="size-2 rounded-full bg-white" />
33
+ </RadioGroup.Indicator>
34
+ </RadioGroup.Item>
35
+ ));
36
+ RadioGroupItem.displayName = "RadioGroupItem";
37
+
38
+ export { RadioGroupRoot as RadioGroup, RadioGroupItem };
@@ -0,0 +1,74 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Group, Panel, Separator } from "react-resizable-panels";
5
+ import type { GroupProps, SeparatorProps } from "react-resizable-panels";
6
+ import { GripVertical } from "lucide-react";
7
+ import { cn } from "../lib/utils";
8
+
9
+ // react-resizable-panels v4 uses `orientation` and `elementRef` instead of
10
+ // the older `direction` prop + React.forwardRef pattern.
11
+
12
+ type ResizablePanelGroupProps = Omit<GroupProps, "orientation"> & {
13
+ /** Alias for `orientation` — matches the common shadcn/ui API. */
14
+ direction?: "horizontal" | "vertical";
15
+ orientation?: "horizontal" | "vertical";
16
+ };
17
+
18
+ function ResizablePanelGroup({
19
+ className,
20
+ direction,
21
+ orientation,
22
+ ...props
23
+ }: ResizablePanelGroupProps) {
24
+ const resolvedOrientation = orientation ?? direction ?? "horizontal";
25
+ return (
26
+ <Group
27
+ orientation={resolvedOrientation}
28
+ className={cn(
29
+ "flex h-full w-full",
30
+ resolvedOrientation === "vertical" && "flex-col",
31
+ className,
32
+ )}
33
+ {...props}
34
+ />
35
+ );
36
+ }
37
+ ResizablePanelGroup.displayName = "ResizablePanelGroup";
38
+
39
+ const ResizablePanel = Panel;
40
+
41
+ type ResizableHandleProps = SeparatorProps & {
42
+ withHandle?: boolean;
43
+ };
44
+
45
+ function ResizableHandle({ className, withHandle = true, ...props }: ResizableHandleProps) {
46
+ return (
47
+ <Separator
48
+ className={cn(
49
+ "relative flex shrink-0 items-center justify-center bg-taupe-100",
50
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-brand-primary/50",
51
+ // Horizontal (default): vertical bar
52
+ "[&:not([aria-orientation=vertical])]:h-full [&:not([aria-orientation=vertical])]:w-px",
53
+ // Vertical: horizontal bar
54
+ "[aria-orientation=vertical]:h-px [aria-orientation=vertical]:w-full",
55
+ // Wider hit target via pseudo-element
56
+ "after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2",
57
+ "[aria-orientation=vertical]:after:left-0 [aria-orientation=vertical]:after:h-1 [aria-orientation=vertical]:after:w-full [aria-orientation=vertical]:after:translate-x-0 [aria-orientation=vertical]:after:-translate-y-1/2",
58
+ // Active drag state
59
+ "active:bg-brand-primary/30",
60
+ className,
61
+ )}
62
+ {...props}
63
+ >
64
+ {withHandle && (
65
+ <div className="z-10 flex h-5 w-3 items-center justify-center rounded-sm border border-taupe-100 bg-white shadow-natural">
66
+ <GripVertical size={10} className="text-taupe-400" />
67
+ </div>
68
+ )}
69
+ </Separator>
70
+ );
71
+ }
72
+ ResizableHandle.displayName = "ResizableHandle";
73
+
74
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle };
@@ -0,0 +1,42 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ScrollArea } from "radix-ui";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const ScrollAreaRoot = React.forwardRef<
8
+ React.ComponentRef<typeof ScrollArea.Root>,
9
+ React.ComponentPropsWithoutRef<typeof ScrollArea.Root>
10
+ >(({ className, children, ...props }, ref) => (
11
+ <ScrollArea.Root ref={ref} className={cn("relative overflow-hidden", className)} {...props}>
12
+ <ScrollArea.Viewport className="h-full w-full rounded-[inherit]">
13
+ {children}
14
+ </ScrollArea.Viewport>
15
+ <ScrollAreaScrollbar orientation="vertical" />
16
+ <ScrollAreaScrollbar orientation="horizontal" />
17
+ <ScrollArea.Corner />
18
+ </ScrollArea.Root>
19
+ ));
20
+ ScrollAreaRoot.displayName = "ScrollArea";
21
+
22
+ const ScrollAreaScrollbar = React.forwardRef<
23
+ React.ComponentRef<typeof ScrollArea.Scrollbar>,
24
+ React.ComponentPropsWithoutRef<typeof ScrollArea.Scrollbar>
25
+ >(({ className, orientation = "vertical", ...props }, ref) => (
26
+ <ScrollArea.Scrollbar
27
+ ref={ref}
28
+ orientation={orientation}
29
+ className={cn(
30
+ "flex touch-none select-none transition-colors",
31
+ orientation === "vertical" && "h-full w-2 border-l border-l-transparent p-px",
32
+ orientation === "horizontal" && "h-2 flex-col border-t border-t-transparent p-px",
33
+ className,
34
+ )}
35
+ {...props}
36
+ >
37
+ <ScrollArea.Thumb className="relative flex-1 rounded-full bg-taupe-200 hover:bg-taupe-300 transition-colors" />
38
+ </ScrollArea.Scrollbar>
39
+ ));
40
+ ScrollAreaScrollbar.displayName = "ScrollAreaScrollbar";
41
+
42
+ export { ScrollAreaRoot as ScrollArea };
@@ -0,0 +1,64 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+ import { Button } from "./button";
4
+
5
+ interface SectionHeadingAction {
6
+ label: string;
7
+ href?: string;
8
+ onClick?: () => void;
9
+ variant?: "default" | "outline" | "ghost";
10
+ }
11
+
12
+ interface SectionHeadingProps {
13
+ title: string;
14
+ description?: string;
15
+ actions?: SectionHeadingAction[];
16
+ border?: boolean;
17
+ className?: string;
18
+ }
19
+
20
+ function SectionHeading({
21
+ title,
22
+ description,
23
+ actions,
24
+ border = true,
25
+ className,
26
+ }: SectionHeadingProps) {
27
+ return (
28
+ <div
29
+ className={cn(
30
+ "flex flex-col gap-1 pb-4 sm:flex-row sm:items-start sm:justify-between",
31
+ border && "border-b border-taupe-100",
32
+ className,
33
+ )}
34
+ >
35
+ <div className="min-w-0 flex-1">
36
+ <h2 className="heading text-taupe-900 truncate">{title}</h2>
37
+ {description && <p className="mt-1 body text-taupe-400">{description}</p>}
38
+ </div>
39
+
40
+ {actions && actions.length > 0 && (
41
+ <div className="flex shrink-0 items-center gap-2 pt-1">
42
+ {actions.map((action, i) =>
43
+ action.href ? (
44
+ <Button key={i} size="sm" variant={action.variant ?? "default"} asChild>
45
+ <a href={action.href}>{action.label}</a>
46
+ </Button>
47
+ ) : (
48
+ <Button
49
+ key={i}
50
+ size="sm"
51
+ variant={action.variant ?? "default"}
52
+ onClick={action.onClick}
53
+ >
54
+ {action.label}
55
+ </Button>
56
+ ),
57
+ )}
58
+ </div>
59
+ )}
60
+ </div>
61
+ );
62
+ }
63
+
64
+ export { SectionHeading };
@@ -0,0 +1,130 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Select } from "radix-ui";
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react";
6
+ import { cn } from "../lib/utils";
7
+
8
+ const SelectRoot = Select.Root;
9
+ const SelectGroup = Select.Group;
10
+ const SelectValue = Select.Value;
11
+
12
+ const SelectTrigger = React.forwardRef<
13
+ React.ComponentRef<typeof Select.Trigger>,
14
+ React.ComponentPropsWithoutRef<typeof Select.Trigger>
15
+ >(({ className, children, ...props }, ref) => (
16
+ <Select.Trigger
17
+ ref={ref}
18
+ className={cn(
19
+ "flex h-9 w-full items-center justify-between rounded-xl bg-white px-3 py-2",
20
+ "body text-taupe-900 shadow-natural",
21
+ "focus:outline-none focus:ring-2 focus:ring-taupe-900/15",
22
+ "disabled:opacity-50 disabled:cursor-not-allowed",
23
+ "data-[placeholder]:text-taupe-400",
24
+ "[&>span]:line-clamp-1",
25
+ className,
26
+ )}
27
+ {...props}
28
+ >
29
+ {children}
30
+ <Select.Icon asChild>
31
+ <ChevronDown size={15} className="text-taupe-400 shrink-0" />
32
+ </Select.Icon>
33
+ </Select.Trigger>
34
+ ));
35
+ SelectTrigger.displayName = "SelectTrigger";
36
+
37
+ const SelectContent = React.forwardRef<
38
+ React.ComponentRef<typeof Select.Content>,
39
+ React.ComponentPropsWithoutRef<typeof Select.Content>
40
+ >(({ className, children, position = "popper", ...props }, ref) => (
41
+ <Select.Portal>
42
+ <Select.Content
43
+ ref={ref}
44
+ className={cn(
45
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden",
46
+ "bg-white rounded-xl shadow-natural p-1",
47
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
48
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
49
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
50
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
51
+ position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1",
52
+ className,
53
+ )}
54
+ position={position}
55
+ {...props}
56
+ >
57
+ <Select.ScrollUpButton className="flex cursor-default items-center justify-center py-1">
58
+ <ChevronUp size={14} className="text-taupe-400" />
59
+ </Select.ScrollUpButton>
60
+ <Select.Viewport
61
+ className={cn(
62
+ position === "popper" &&
63
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
64
+ )}
65
+ >
66
+ {children}
67
+ </Select.Viewport>
68
+ <Select.ScrollDownButton className="flex cursor-default items-center justify-center py-1">
69
+ <ChevronDown size={14} className="text-taupe-400" />
70
+ </Select.ScrollDownButton>
71
+ </Select.Content>
72
+ </Select.Portal>
73
+ ));
74
+ SelectContent.displayName = "SelectContent";
75
+
76
+ const SelectItem = React.forwardRef<
77
+ React.ComponentRef<typeof Select.Item>,
78
+ React.ComponentPropsWithoutRef<typeof Select.Item>
79
+ >(({ className, children, ...props }, ref) => (
80
+ <Select.Item
81
+ ref={ref}
82
+ className={cn(
83
+ "relative flex w-full cursor-default select-none items-center rounded-md px-2.5 py-1.5",
84
+ "body text-taupe-700 outline-none",
85
+ "focus:bg-taupe-50 focus:text-taupe-900",
86
+ "data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
87
+ className,
88
+ )}
89
+ {...props}
90
+ >
91
+ <span className="absolute right-2 flex size-3.5 items-center justify-center">
92
+ <Select.ItemIndicator>
93
+ <Check size={13} className="text-taupe-900" />
94
+ </Select.ItemIndicator>
95
+ </span>
96
+ <Select.ItemText>{children}</Select.ItemText>
97
+ </Select.Item>
98
+ ));
99
+ SelectItem.displayName = "SelectItem";
100
+
101
+ const SelectLabel = React.forwardRef<
102
+ React.ComponentRef<typeof Select.Label>,
103
+ React.ComponentPropsWithoutRef<typeof Select.Label>
104
+ >(({ className, ...props }, ref) => (
105
+ <Select.Label
106
+ ref={ref}
107
+ className={cn("px-2.5 py-1 label text-taupe-400", className)}
108
+ {...props}
109
+ />
110
+ ));
111
+ SelectLabel.displayName = "SelectLabel";
112
+
113
+ const SelectSeparator = React.forwardRef<
114
+ React.ComponentRef<typeof Select.Separator>,
115
+ React.ComponentPropsWithoutRef<typeof Select.Separator>
116
+ >(({ className, ...props }, ref) => (
117
+ <Select.Separator ref={ref} className={cn("mx-1 my-1 h-px bg-taupe-100", className)} {...props} />
118
+ ));
119
+ SelectSeparator.displayName = "SelectSeparator";
120
+
121
+ export {
122
+ SelectRoot as Select,
123
+ SelectGroup,
124
+ SelectValue,
125
+ SelectTrigger,
126
+ SelectContent,
127
+ SelectItem,
128
+ SelectLabel,
129
+ SelectSeparator,
130
+ };
@@ -0,0 +1,25 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Separator as SeparatorPrimitive } from "radix-ui";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const Separator = React.forwardRef<
8
+ React.ElementRef<typeof SeparatorPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
10
+ >(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
11
+ <SeparatorPrimitive.Root
12
+ ref={ref}
13
+ decorative={decorative}
14
+ orientation={orientation}
15
+ className={cn(
16
+ "shrink-0 bg-taupe-100",
17
+ orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
18
+ className,
19
+ )}
20
+ {...props}
21
+ />
22
+ ));
23
+ Separator.displayName = "Separator";
24
+
25
+ export { Separator };
@@ -0,0 +1,117 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Dialog } from "radix-ui";
5
+ import { X } from "lucide-react";
6
+ import { cva, type VariantProps } from "class-variance-authority";
7
+ import { cn } from "../lib/utils";
8
+
9
+ const SheetRoot = Dialog.Root;
10
+ const SheetTrigger = Dialog.Trigger;
11
+ const SheetClose = Dialog.Close;
12
+
13
+ const SheetOverlay = React.forwardRef<
14
+ React.ComponentRef<typeof Dialog.Overlay>,
15
+ React.ComponentPropsWithoutRef<typeof Dialog.Overlay>
16
+ >(({ className, ...props }, ref) => (
17
+ <Dialog.Overlay
18
+ ref={ref}
19
+ className={cn(
20
+ "fixed inset-0 z-50 bg-black/30 backdrop-blur-sm",
21
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
22
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
23
+ className,
24
+ )}
25
+ {...props}
26
+ />
27
+ ));
28
+ SheetOverlay.displayName = "SheetOverlay";
29
+
30
+ const sheetContentVariants = cva(
31
+ [
32
+ "fixed z-50 bg-white shadow-elevated p-6",
33
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
34
+ "transition ease-in-out duration-300",
35
+ ],
36
+ {
37
+ variants: {
38
+ side: {
39
+ right:
40
+ "right-0 top-0 h-full w-full max-w-sm data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right",
41
+ left: "left-0 top-0 h-full w-full max-w-sm data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left",
42
+ top: "top-0 inset-x-0 h-auto data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
43
+ bottom:
44
+ "bottom-0 inset-x-0 h-auto data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
45
+ },
46
+ },
47
+ defaultVariants: {
48
+ side: "right",
49
+ },
50
+ },
51
+ );
52
+
53
+ interface SheetContentProps
54
+ extends
55
+ React.ComponentPropsWithoutRef<typeof Dialog.Content>,
56
+ VariantProps<typeof sheetContentVariants> {}
57
+
58
+ const SheetContent = React.forwardRef<React.ComponentRef<typeof Dialog.Content>, SheetContentProps>(
59
+ ({ className, children, side = "right", ...props }, ref) => (
60
+ <Dialog.Portal>
61
+ <SheetOverlay />
62
+ <Dialog.Content
63
+ ref={ref}
64
+ className={cn(sheetContentVariants({ side }), className)}
65
+ {...props}
66
+ >
67
+ {children}
68
+ <Dialog.Close className="absolute right-4 top-4 rounded-md p-1 text-taupe-400 opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-taupe-900/20">
69
+ <X size={16} />
70
+ <span className="sr-only">Close</span>
71
+ </Dialog.Close>
72
+ </Dialog.Content>
73
+ </Dialog.Portal>
74
+ ),
75
+ );
76
+ SheetContent.displayName = "SheetContent";
77
+
78
+ function SheetHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
79
+ return <div className={cn("flex flex-col gap-1.5 mb-4", className)} {...props} />;
80
+ }
81
+ SheetHeader.displayName = "SheetHeader";
82
+
83
+ function SheetFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
84
+ return <div className={cn("flex items-center justify-end gap-2 mt-6", className)} {...props} />;
85
+ }
86
+ SheetFooter.displayName = "SheetFooter";
87
+
88
+ const SheetTitle = React.forwardRef<
89
+ React.ComponentRef<typeof Dialog.Title>,
90
+ React.ComponentPropsWithoutRef<typeof Dialog.Title>
91
+ >(({ className, ...props }, ref) => (
92
+ <Dialog.Title
93
+ ref={ref}
94
+ className={cn("body-lg font-semibold text-taupe-900", className)}
95
+ {...props}
96
+ />
97
+ ));
98
+ SheetTitle.displayName = "SheetTitle";
99
+
100
+ const SheetDescription = React.forwardRef<
101
+ React.ComponentRef<typeof Dialog.Description>,
102
+ React.ComponentPropsWithoutRef<typeof Dialog.Description>
103
+ >(({ className, ...props }, ref) => (
104
+ <Dialog.Description ref={ref} className={cn("body text-taupe-500", className)} {...props} />
105
+ ));
106
+ SheetDescription.displayName = "SheetDescription";
107
+
108
+ export {
109
+ SheetRoot as Sheet,
110
+ SheetTrigger,
111
+ SheetContent,
112
+ SheetHeader,
113
+ SheetFooter,
114
+ SheetTitle,
115
+ SheetDescription,
116
+ SheetClose,
117
+ };