@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.
- package/LICENSE +21 -0
- package/README.md +42 -0
- package/dist/icons.d.ts +1 -0
- package/dist/icons.js +3 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +992 -0
- package/dist/index.js +4433 -0
- package/dist/index.js.map +1 -0
- package/dist/tokens.css +265 -0
- package/dist/tokens.d.ts +189 -0
- package/dist/tokens.js +193 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +80 -0
- package/src/components/__tests__/data.test.tsx +180 -0
- package/src/components/__tests__/disclosure.test.tsx +198 -0
- package/src/components/__tests__/display.test.tsx +262 -0
- package/src/components/__tests__/feedback.test.tsx +138 -0
- package/src/components/__tests__/form.test.tsx +297 -0
- package/src/components/__tests__/layout.test.tsx +388 -0
- package/src/components/__tests__/overlay.test.tsx +90 -0
- package/src/components/__tests__/primitives.test.tsx +248 -0
- package/src/components/accordion.tsx +68 -0
- package/src/components/action-panel.tsx +112 -0
- package/src/components/activity-feed.tsx +69 -0
- package/src/components/alert-dialog.tsx +132 -0
- package/src/components/alert.tsx +44 -0
- package/src/components/aspect-ratio.tsx +19 -0
- package/src/components/avatar-stack.tsx +70 -0
- package/src/components/avatar.tsx +103 -0
- package/src/components/badge.tsx +33 -0
- package/src/components/breadcrumb.tsx +76 -0
- package/src/components/button-group.tsx +45 -0
- package/src/components/button.tsx +60 -0
- package/src/components/calendar.tsx +61 -0
- package/src/components/card-heading.tsx +60 -0
- package/src/components/card.tsx +120 -0
- package/src/components/carousel.tsx +222 -0
- package/src/components/chart.tsx +77 -0
- package/src/components/checkbox.tsx +35 -0
- package/src/components/collapsible.tsx +9 -0
- package/src/components/color-input.tsx +90 -0
- package/src/components/color-picker.tsx +111 -0
- package/src/components/combobox.tsx +207 -0
- package/src/components/command-palette.tsx +137 -0
- package/src/components/command.tsx +248 -0
- package/src/components/context-menu.tsx +201 -0
- package/src/components/data-table.tsx +151 -0
- package/src/components/date-picker.tsx +56 -0
- package/src/components/description-list.tsx +70 -0
- package/src/components/dialog.tsx +97 -0
- package/src/components/drawer.tsx +90 -0
- package/src/components/dropdown-menu.tsx +202 -0
- package/src/components/empty-state.tsx +43 -0
- package/src/components/field.tsx +45 -0
- package/src/components/grid-list.tsx +159 -0
- package/src/components/hover-card.tsx +34 -0
- package/src/components/input-group.tsx +134 -0
- package/src/components/input-number.tsx +142 -0
- package/src/components/input-otp.tsx +68 -0
- package/src/components/input.tsx +23 -0
- package/src/components/kbd.tsx +18 -0
- package/src/components/label.tsx +23 -0
- package/src/components/media-object.tsx +49 -0
- package/src/components/menubar.tsx +235 -0
- package/src/components/native-select.tsx +44 -0
- package/src/components/navigation-menu.tsx +158 -0
- package/src/components/notification.tsx +109 -0
- package/src/components/page-heading.tsx +125 -0
- package/src/components/pagination.tsx +89 -0
- package/src/components/popover.tsx +35 -0
- package/src/components/progress.tsx +23 -0
- package/src/components/property-row.tsx +56 -0
- package/src/components/radio-card.tsx +105 -0
- package/src/components/radio-group.tsx +38 -0
- package/src/components/resizable.tsx +74 -0
- package/src/components/scroll-area.tsx +42 -0
- package/src/components/section-heading.tsx +64 -0
- package/src/components/select.tsx +130 -0
- package/src/components/separator.tsx +25 -0
- package/src/components/sheet.tsx +117 -0
- package/src/components/sidebar.tsx +636 -0
- package/src/components/skeleton.tsx +14 -0
- package/src/components/slider.tsx +24 -0
- package/src/components/sonner.tsx +34 -0
- package/src/components/spinner.tsx +37 -0
- package/src/components/stacked-list.tsx +64 -0
- package/src/components/stat-card.tsx +54 -0
- package/src/components/steps.tsx +179 -0
- package/src/components/switch.tsx +33 -0
- package/src/components/table.tsx +101 -0
- package/src/components/tabs.tsx +55 -0
- package/src/components/textarea.tsx +21 -0
- package/src/components/toggle-group.tsx +49 -0
- package/src/components/toggle.tsx +66 -0
- package/src/components/tooltip.tsx +43 -0
- package/src/icons.ts +158 -0
- package/src/index.ts +84 -0
- package/src/lib/utils.ts +6 -0
- 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
|
+
};
|