@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,37 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "../lib/utils";
4
+
5
+ const spinnerVariants = cva(
6
+ "inline-block animate-spin rounded-full border-2 border-current border-t-transparent",
7
+ {
8
+ variants: {
9
+ size: {
10
+ xs: "size-3",
11
+ sm: "size-4",
12
+ default: "size-5",
13
+ lg: "size-6",
14
+ xl: "size-8",
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ size: "default",
19
+ },
20
+ },
21
+ );
22
+
23
+ interface SpinnerProps
24
+ extends React.HTMLAttributes<HTMLSpanElement>, VariantProps<typeof spinnerVariants> {}
25
+
26
+ function Spinner({ className, size, ...props }: SpinnerProps) {
27
+ return (
28
+ <span
29
+ role="status"
30
+ aria-label="Loading"
31
+ className={cn(spinnerVariants({ size }), className)}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ export { Spinner, spinnerVariants };
@@ -0,0 +1,64 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ interface StackedListProps {
5
+ children: React.ReactNode;
6
+ divided?: boolean;
7
+ bordered?: boolean;
8
+ className?: string;
9
+ }
10
+
11
+ function StackedList({ children, divided = true, bordered = true, className }: StackedListProps) {
12
+ return (
13
+ <ul
14
+ role="list"
15
+ className={cn(
16
+ "overflow-hidden rounded-xl bg-white",
17
+ bordered && "border border-taupe-100 shadow-natural",
18
+ divided && "divide-y divide-taupe-100",
19
+ className,
20
+ )}
21
+ >
22
+ {children}
23
+ </ul>
24
+ );
25
+ }
26
+
27
+ interface StackedListItemProps {
28
+ children: React.ReactNode;
29
+ href?: string;
30
+ onClick?: () => void;
31
+ className?: string;
32
+ }
33
+
34
+ function StackedListItem({ children, href, onClick, className }: StackedListItemProps) {
35
+ const inner = <div className={cn("px-4 py-3.5", className)}>{children}</div>;
36
+
37
+ if (href) {
38
+ return (
39
+ <li>
40
+ <a href={href} className="block hover:bg-taupe-50 transition-colors">
41
+ {inner}
42
+ </a>
43
+ </li>
44
+ );
45
+ }
46
+
47
+ if (onClick) {
48
+ return (
49
+ <li>
50
+ <button
51
+ type="button"
52
+ className="w-full text-left hover:bg-taupe-50 transition-colors"
53
+ onClick={onClick}
54
+ >
55
+ {inner}
56
+ </button>
57
+ </li>
58
+ );
59
+ }
60
+
61
+ return <li>{inner}</li>;
62
+ }
63
+
64
+ export { StackedList, StackedListItem };
@@ -0,0 +1,54 @@
1
+ import * as React from "react";
2
+ import { TrendingUp, TrendingDown, Minus } from "lucide-react";
3
+ import { cn } from "../lib/utils";
4
+
5
+ interface StatCardProps {
6
+ label: string;
7
+ value: string | number;
8
+ trend?: {
9
+ value: string;
10
+ direction: "up" | "down" | "neutral";
11
+ };
12
+ icon?: React.ReactNode;
13
+ className?: string;
14
+ }
15
+
16
+ function StatCard({ label, value, trend, icon, className }: StatCardProps) {
17
+ const trendColor = {
18
+ up: "text-taupe-800 bg-brand-secondary/30 rounded-full px-2 py-0.5 w-fit",
19
+ down: "text-red-600",
20
+ neutral: "text-taupe-400",
21
+ };
22
+
23
+ const TrendIcon = {
24
+ up: TrendingUp,
25
+ down: TrendingDown,
26
+ neutral: Minus,
27
+ };
28
+
29
+ return (
30
+ <div className={cn("rounded-xl border border-taupe-100 bg-white p-5", className)}>
31
+ <div className="flex items-start justify-between gap-3">
32
+ <p className="label text-taupe-400">{label}</p>
33
+ {icon && (
34
+ <div className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-taupe-50 text-taupe-400">
35
+ {icon}
36
+ </div>
37
+ )}
38
+ </div>
39
+ <p className="heading-xl mt-2 text-taupe-900">{value}</p>
40
+ {trend &&
41
+ (() => {
42
+ const Icon = TrendIcon[trend.direction];
43
+ return (
44
+ <div className={cn("mt-2 flex items-center gap-1", trendColor[trend.direction])}>
45
+ <Icon size={12} />
46
+ <span className="caption font-medium">{trend.value}</span>
47
+ </div>
48
+ );
49
+ })()}
50
+ </div>
51
+ );
52
+ }
53
+
54
+ export { StatCard };
@@ -0,0 +1,179 @@
1
+ import * as React from "react";
2
+ import { Check } from "lucide-react";
3
+ import { cn } from "../lib/utils";
4
+
5
+ export type StepStatus = "complete" | "current" | "upcoming";
6
+
7
+ export interface Step {
8
+ id?: string | number;
9
+ name: string;
10
+ description?: string;
11
+ status: StepStatus;
12
+ href?: string;
13
+ }
14
+
15
+ interface StepsProps {
16
+ steps: Step[];
17
+ variant?: "circles" | "bullets" | "progress";
18
+ className?: string;
19
+ }
20
+
21
+ /* ---------- Circles variant ---------- */
22
+ function CirclesSteps({ steps, className }: { steps: Step[]; className?: string }) {
23
+ return (
24
+ <nav aria-label="Progress" className={className}>
25
+ <ol role="list" className="flex items-center">
26
+ {steps.map((step, i) => {
27
+ const isLast = i === steps.length - 1;
28
+ return (
29
+ <li key={step.id ?? i} className={cn("relative", !isLast && "flex-1")}>
30
+ {/* Connector line */}
31
+ {!isLast && (
32
+ <div className="absolute top-4 left-8 -right-px h-0.5" aria-hidden="true">
33
+ <div
34
+ className={cn(
35
+ "h-full",
36
+ step.status === "complete" ? "bg-brand-primary" : "bg-taupe-200",
37
+ )}
38
+ />
39
+ </div>
40
+ )}
41
+
42
+ <a
43
+ href={step.href ?? "#"}
44
+ onClick={!step.href ? (e) => e.preventDefault() : undefined}
45
+ className="group relative flex flex-col items-center gap-2"
46
+ >
47
+ {/* Circle */}
48
+ <span
49
+ className={cn(
50
+ "relative z-10 flex size-8 items-center justify-center rounded-full transition-colors",
51
+ step.status === "complete" &&
52
+ "bg-brand-primary group-hover:bg-brand-primary/80",
53
+ step.status === "current" && "border-2 border-brand-primary bg-white",
54
+ step.status === "upcoming" &&
55
+ "border-2 border-taupe-300 bg-white group-hover:border-taupe-400",
56
+ )}
57
+ >
58
+ {step.status === "complete" ? (
59
+ <Check size={14} className="text-white" strokeWidth={2.5} />
60
+ ) : step.status === "current" ? (
61
+ <span className="size-2.5 rounded-full bg-brand-primary" />
62
+ ) : (
63
+ <span className="size-2.5 rounded-full bg-transparent group-hover:bg-taupe-300" />
64
+ )}
65
+ </span>
66
+
67
+ {/* Labels */}
68
+ <span className="flex flex-col items-center">
69
+ <span
70
+ className={cn(
71
+ "caption font-semibold",
72
+ step.status === "upcoming" ? "text-taupe-400" : "text-taupe-900",
73
+ )}
74
+ >
75
+ {step.name}
76
+ </span>
77
+ {step.description && (
78
+ <span className="caption text-taupe-400">{step.description}</span>
79
+ )}
80
+ </span>
81
+ </a>
82
+ </li>
83
+ );
84
+ })}
85
+ </ol>
86
+ </nav>
87
+ );
88
+ }
89
+
90
+ /* ---------- Bullets variant ---------- */
91
+ function BulletsSteps({ steps, className }: { steps: Step[]; className?: string }) {
92
+ const currentIdx = steps.findIndex((s) => s.status === "current");
93
+
94
+ return (
95
+ <nav className={cn("flex items-center gap-4", className)} aria-label="Progress">
96
+ <p className="body font-medium text-taupe-700">
97
+ Step {currentIdx + 1} of {steps.length}
98
+ </p>
99
+ <ol role="list" className="flex items-center gap-2">
100
+ {steps.map((step, i) => (
101
+ <li key={step.id ?? i}>
102
+ <a
103
+ href={step.href ?? "#"}
104
+ onClick={!step.href ? (e) => e.preventDefault() : undefined}
105
+ className="block"
106
+ >
107
+ {step.status === "complete" ? (
108
+ <span
109
+ className="block size-2.5 rounded-full bg-brand-primary hover:bg-brand-primary/80 transition-colors"
110
+ title={step.name}
111
+ />
112
+ ) : step.status === "current" ? (
113
+ <span
114
+ className="relative flex size-4 items-center justify-center"
115
+ aria-current="step"
116
+ >
117
+ <span className="absolute size-4 rounded-full bg-brand-primary/20" />
118
+ <span className="relative size-2.5 rounded-full bg-brand-primary" />
119
+ <span className="sr-only">{step.name}</span>
120
+ </span>
121
+ ) : (
122
+ <span
123
+ className="block size-2.5 rounded-full bg-taupe-200 hover:bg-taupe-300 transition-colors"
124
+ title={step.name}
125
+ />
126
+ )}
127
+ </a>
128
+ </li>
129
+ ))}
130
+ </ol>
131
+ </nav>
132
+ );
133
+ }
134
+
135
+ /* ---------- Progress bar variant ---------- */
136
+ function ProgressSteps({ steps, className }: { steps: Step[]; className?: string }) {
137
+ const completeCount = steps.filter((s) => s.status === "complete").length;
138
+ const currentIdx = steps.findIndex((s) => s.status === "current");
139
+ const progress = ((completeCount + (currentIdx >= 0 ? 0.5 : 0)) / steps.length) * 100;
140
+
141
+ return (
142
+ <div className={cn("w-full", className)}>
143
+ {/* Bar */}
144
+ <div className="h-1.5 w-full overflow-hidden rounded-full bg-taupe-100">
145
+ <div
146
+ className="h-full rounded-full bg-brand-primary transition-all duration-500"
147
+ style={{ width: `${progress}%` }}
148
+ />
149
+ </div>
150
+ {/* Labels */}
151
+ <div className="mt-4 grid" style={{ gridTemplateColumns: `repeat(${steps.length}, 1fr)` }}>
152
+ {steps.map((step, i) => {
153
+ const isLast = i === steps.length - 1;
154
+ return (
155
+ <div
156
+ key={step.id ?? i}
157
+ className={cn(
158
+ "caption font-medium",
159
+ i === 0 ? "text-left" : isLast ? "text-right" : "text-center",
160
+ step.status === "upcoming" ? "text-taupe-400" : "text-taupe-900",
161
+ )}
162
+ >
163
+ {step.name}
164
+ </div>
165
+ );
166
+ })}
167
+ </div>
168
+ </div>
169
+ );
170
+ }
171
+
172
+ /* ---------- Main component ---------- */
173
+ function Steps({ steps, variant = "circles", className }: StepsProps) {
174
+ if (variant === "bullets") return <BulletsSteps steps={steps} className={className} />;
175
+ if (variant === "progress") return <ProgressSteps steps={steps} className={className} />;
176
+ return <CirclesSteps steps={steps} className={className} />;
177
+ }
178
+
179
+ export { Steps };
@@ -0,0 +1,33 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Switch } from "radix-ui";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const SwitchRoot = React.forwardRef<
8
+ React.ComponentRef<typeof Switch.Root>,
9
+ React.ComponentPropsWithoutRef<typeof Switch.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <Switch.Root
12
+ ref={ref}
13
+ className={cn(
14
+ "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full",
15
+ "border-2 border-transparent transition-colors",
16
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
17
+ "disabled:cursor-not-allowed disabled:opacity-50",
18
+ "data-[state=unchecked]:bg-taupe-200 data-[state=checked]:bg-brand-primary",
19
+ className,
20
+ )}
21
+ {...props}
22
+ >
23
+ <Switch.Thumb
24
+ className={cn(
25
+ "pointer-events-none block size-4 rounded-full bg-white shadow-natural",
26
+ "transition-transform data-[state=unchecked]:translate-x-0 data-[state=checked]:translate-x-4",
27
+ )}
28
+ />
29
+ </Switch.Root>
30
+ ));
31
+ SwitchRoot.displayName = "Switch";
32
+
33
+ export { SwitchRoot as Switch };
@@ -0,0 +1,101 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ const Table = React.forwardRef<
5
+ HTMLTableElement,
6
+ React.HTMLAttributes<HTMLTableElement> & { containerClassName?: string }
7
+ >(({ className, containerClassName, ...props }, ref) => (
8
+ <div
9
+ className={cn(
10
+ "w-full overflow-hidden rounded-xl border border-taupe-100 bg-white",
11
+ containerClassName,
12
+ )}
13
+ >
14
+ <table
15
+ ref={ref}
16
+ className={cn("min-w-full caption-bottom divide-y divide-taupe-200", className)}
17
+ {...props}
18
+ />
19
+ </div>
20
+ ));
21
+ Table.displayName = "Table";
22
+
23
+ const TableHeader = React.forwardRef<
24
+ HTMLTableSectionElement,
25
+ React.HTMLAttributes<HTMLTableSectionElement>
26
+ >(({ className, ...props }, ref) => (
27
+ <thead ref={ref} className={cn("bg-taupe-50", className)} {...props} />
28
+ ));
29
+ TableHeader.displayName = "TableHeader";
30
+
31
+ const TableBody = React.forwardRef<
32
+ HTMLTableSectionElement,
33
+ React.HTMLAttributes<HTMLTableSectionElement>
34
+ >(({ className, ...props }, ref) => (
35
+ <tbody ref={ref} className={cn("divide-y divide-taupe-100 bg-white", className)} {...props} />
36
+ ));
37
+ TableBody.displayName = "TableBody";
38
+
39
+ const TableFooter = React.forwardRef<
40
+ HTMLTableSectionElement,
41
+ React.HTMLAttributes<HTMLTableSectionElement>
42
+ >(({ className, ...props }, ref) => (
43
+ <tfoot ref={ref} className={cn("bg-taupe-50 font-medium", className)} {...props} />
44
+ ));
45
+ TableFooter.displayName = "TableFooter";
46
+
47
+ const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
48
+ ({ className, ...props }, ref) => (
49
+ <tr
50
+ ref={ref}
51
+ className={cn(
52
+ "transition-colors hover:bg-taupe-50/50 data-[state=selected]:bg-taupe-50",
53
+ className,
54
+ )}
55
+ {...props}
56
+ />
57
+ ),
58
+ );
59
+ TableRow.displayName = "TableRow";
60
+
61
+ const TableHead = React.forwardRef<
62
+ HTMLTableCellElement,
63
+ React.ThHTMLAttributes<HTMLTableCellElement>
64
+ >(({ className, ...props }, ref) => (
65
+ <th
66
+ ref={ref}
67
+ className={cn(
68
+ "px-6 py-3 text-left align-middle label text-taupe-400",
69
+ "[&:has([role=checkbox])]:pr-0",
70
+ className,
71
+ )}
72
+ {...props}
73
+ />
74
+ ));
75
+ TableHead.displayName = "TableHead";
76
+
77
+ const TableCell = React.forwardRef<
78
+ HTMLTableCellElement,
79
+ React.TdHTMLAttributes<HTMLTableCellElement>
80
+ >(({ className, ...props }, ref) => (
81
+ <td
82
+ ref={ref}
83
+ className={cn(
84
+ "px-6 py-4 align-middle body text-taupe-500",
85
+ "[&:has([role=checkbox])]:pr-0",
86
+ className,
87
+ )}
88
+ {...props}
89
+ />
90
+ ));
91
+ TableCell.displayName = "TableCell";
92
+
93
+ const TableCaption = React.forwardRef<
94
+ HTMLTableCaptionElement,
95
+ React.HTMLAttributes<HTMLTableCaptionElement>
96
+ >(({ className, ...props }, ref) => (
97
+ <caption ref={ref} className={cn("mt-4 caption text-taupe-400", className)} {...props} />
98
+ ));
99
+ TableCaption.displayName = "TableCaption";
100
+
101
+ export { Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption };
@@ -0,0 +1,55 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Tabs } from "radix-ui";
5
+ import { cn } from "../lib/utils";
6
+
7
+ const TabsRoot = React.forwardRef<
8
+ React.ComponentRef<typeof Tabs.Root>,
9
+ React.ComponentPropsWithoutRef<typeof Tabs.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <Tabs.Root ref={ref} className={cn("w-full", className)} {...props} />
12
+ ));
13
+ TabsRoot.displayName = "Tabs";
14
+
15
+ const TabsList = React.forwardRef<
16
+ React.ComponentRef<typeof Tabs.List>,
17
+ React.ComponentPropsWithoutRef<typeof Tabs.List>
18
+ >(({ className, ...props }, ref) => (
19
+ <Tabs.List
20
+ ref={ref}
21
+ className={cn("inline-flex items-center gap-1 border-b border-taupe-100 w-full", className)}
22
+ {...props}
23
+ />
24
+ ));
25
+ TabsList.displayName = "TabsList";
26
+
27
+ const TabsTrigger = React.forwardRef<
28
+ React.ComponentRef<typeof Tabs.Trigger>,
29
+ React.ComponentPropsWithoutRef<typeof Tabs.Trigger>
30
+ >(({ className, ...props }, ref) => (
31
+ <Tabs.Trigger
32
+ ref={ref}
33
+ className={cn(
34
+ "px-0 pb-2.5 body font-medium text-taupe-400",
35
+ "border-b-2 border-transparent -mb-px",
36
+ "transition-colors cursor-pointer",
37
+ "data-[state=active]:text-brand-primary data-[state=active]:border-brand-primary",
38
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/20",
39
+ "disabled:pointer-events-none disabled:opacity-50",
40
+ className,
41
+ )}
42
+ {...props}
43
+ />
44
+ ));
45
+ TabsTrigger.displayName = "TabsTrigger";
46
+
47
+ const TabsContent = React.forwardRef<
48
+ React.ComponentRef<typeof Tabs.Content>,
49
+ React.ComponentPropsWithoutRef<typeof Tabs.Content>
50
+ >(({ className, ...props }, ref) => (
51
+ <Tabs.Content ref={ref} className={cn("mt-4", className)} {...props} />
52
+ ));
53
+ TabsContent.displayName = "TabsContent";
54
+
55
+ export { TabsRoot as Tabs, TabsList, TabsTrigger, TabsContent };
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
5
+ return (
6
+ <textarea
7
+ data-slot="textarea"
8
+ className={cn(
9
+ "flex min-h-[80px] w-full rounded-md border border-taupe-200 bg-white px-3 py-2",
10
+ "body text-taupe-900 placeholder:text-taupe-400",
11
+ "resize-none transition-colors duration-150",
12
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-taupe-900/15 focus-visible:border-taupe-400",
13
+ "disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-taupe-50",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { Textarea };
@@ -0,0 +1,49 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ToggleGroup } from "radix-ui";
5
+ import { type VariantProps } from "class-variance-authority";
6
+ import { cn } from "../lib/utils";
7
+ import { toggleVariants } from "./toggle";
8
+
9
+ const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariants>>({
10
+ size: "default",
11
+ variant: "default",
12
+ });
13
+
14
+ const ToggleGroupRoot = React.forwardRef<
15
+ React.ComponentRef<typeof ToggleGroup.Root>,
16
+ React.ComponentPropsWithoutRef<typeof ToggleGroup.Root> & VariantProps<typeof toggleVariants>
17
+ >(({ className, variant, size, children, ...props }, ref) => (
18
+ <ToggleGroup.Root
19
+ ref={ref}
20
+ className={cn("inline-flex items-center gap-1", className)}
21
+ {...props}
22
+ >
23
+ <ToggleGroupContext.Provider value={{ variant, size }}>{children}</ToggleGroupContext.Provider>
24
+ </ToggleGroup.Root>
25
+ ));
26
+ ToggleGroupRoot.displayName = "ToggleGroup";
27
+
28
+ const ToggleGroupItem = React.forwardRef<
29
+ React.ComponentRef<typeof ToggleGroup.Item>,
30
+ React.ComponentPropsWithoutRef<typeof ToggleGroup.Item> & VariantProps<typeof toggleVariants>
31
+ >(({ className, variant, size, ...props }, ref) => {
32
+ const context = React.useContext(ToggleGroupContext);
33
+ return (
34
+ <ToggleGroup.Item
35
+ ref={ref}
36
+ className={cn(
37
+ toggleVariants({
38
+ variant: variant ?? context.variant,
39
+ size: size ?? context.size,
40
+ }),
41
+ className,
42
+ )}
43
+ {...props}
44
+ />
45
+ );
46
+ });
47
+ ToggleGroupItem.displayName = "ToggleGroupItem";
48
+
49
+ export { ToggleGroupRoot as ToggleGroup, ToggleGroupItem };
@@ -0,0 +1,66 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { Toggle } from "radix-ui";
5
+ import { cva, type VariantProps } from "class-variance-authority";
6
+ import { cn } from "../lib/utils";
7
+
8
+ const toggleVariants = cva(
9
+ [
10
+ "inline-flex shrink-0 items-center justify-center gap-2",
11
+ "body font-medium whitespace-nowrap rounded-md",
12
+ "transition-all duration-150 outline-none",
13
+ "focus-visible:ring-2 focus-visible:ring-brand-primary/30 focus-visible:ring-offset-1",
14
+ "disabled:pointer-events-none disabled:opacity-50",
15
+ "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
16
+ ],
17
+ {
18
+ variants: {
19
+ variant: {
20
+ default: [
21
+ "bg-transparent text-taupe-600",
22
+ "hover:bg-taupe-100 hover:text-taupe-900",
23
+ "data-[state=on]:bg-taupe-100 data-[state=on]:text-taupe-900",
24
+ ],
25
+ outline: [
26
+ "border border-taupe-200 bg-white text-taupe-600 shadow-natural",
27
+ "hover:bg-taupe-50 hover:text-taupe-900",
28
+ "data-[state=on]:bg-taupe-100 data-[state=on]:text-taupe-900 data-[state=on]:border-taupe-300",
29
+ ],
30
+ brand: [
31
+ "bg-transparent text-taupe-600",
32
+ "hover:bg-taupe-100 hover:text-taupe-900",
33
+ "data-[state=on]:bg-brand-primary data-[state=on]:text-white",
34
+ ],
35
+ },
36
+ size: {
37
+ sm: "h-8 gap-1.5 px-3",
38
+ default: "h-9 px-3",
39
+ lg: "h-10 px-4",
40
+ icon: "size-9",
41
+ "icon-sm": "size-8",
42
+ "icon-lg": "size-10",
43
+ },
44
+ },
45
+ defaultVariants: {
46
+ variant: "default",
47
+ size: "default",
48
+ },
49
+ },
50
+ );
51
+
52
+ interface ToggleProps
53
+ extends React.ComponentPropsWithoutRef<typeof Toggle.Root>, VariantProps<typeof toggleVariants> {}
54
+
55
+ const ToggleRoot = React.forwardRef<React.ComponentRef<typeof Toggle.Root>, ToggleProps>(
56
+ ({ className, variant, size, ...props }, ref) => (
57
+ <Toggle.Root
58
+ ref={ref}
59
+ className={cn(toggleVariants({ variant, size }), className)}
60
+ {...props}
61
+ />
62
+ ),
63
+ );
64
+ ToggleRoot.displayName = "Toggle";
65
+
66
+ export { ToggleRoot as Toggle, toggleVariants };