@hilum/ui 1.0.1 → 2.0.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/README.md +2 -2
- package/dist/{chunk-ELL3JM3Z.js → chunk-H6JZQWX3.js} +59 -15
- package/dist/chunk-H6JZQWX3.js.map +1 -0
- package/dist/index.d.ts +350 -290
- package/dist/index.js +490 -436
- package/dist/index.js.map +1 -1
- package/dist/tokens.css +42 -16
- package/dist/tokens.d.ts +33 -8
- package/dist/tokens.js +1 -1
- package/llms.txt +9 -9
- package/package.json +1 -1
- package/src/components/accordion.tsx +5 -5
- package/src/components/action-panel.tsx +8 -6
- package/src/components/activity-feed.tsx +7 -5
- package/src/components/alert-dialog.tsx +5 -5
- package/src/components/alert.tsx +5 -5
- package/src/components/avatar-stack.tsx +3 -3
- package/src/components/avatar.tsx +4 -4
- package/src/components/badge.tsx +7 -7
- package/src/components/breadcrumb.tsx +5 -6
- package/src/components/button-group.tsx +7 -4
- package/src/components/button.tsx +7 -7
- package/src/components/calendar.tsx +10 -10
- package/src/components/card-heading.tsx +3 -3
- package/src/components/card.tsx +4 -4
- package/src/components/chart.tsx +10 -10
- package/src/components/checkbox.tsx +2 -2
- package/src/components/color-input.tsx +7 -5
- package/src/components/color-picker.tsx +8 -6
- package/src/components/combobox.tsx +66 -31
- package/src/components/command-palette.tsx +13 -11
- package/src/components/command.tsx +14 -12
- package/src/components/context-menu.tsx +16 -16
- package/src/components/data-table.tsx +18 -7
- package/src/components/date-picker.tsx +2 -2
- package/src/components/description-list.tsx +10 -8
- package/src/components/dialog.tsx +4 -4
- package/src/components/drawer.tsx +5 -5
- package/src/components/dropdown-menu.tsx +16 -16
- package/src/components/empty-state.tsx +5 -3
- package/src/components/field.tsx +4 -4
- package/src/components/grid-list.tsx +17 -13
- package/src/components/hover-card.tsx +1 -1
- package/src/components/input-group.tsx +10 -10
- package/src/components/input-number.tsx +7 -7
- package/src/components/input-otp.tsx +3 -3
- package/src/components/input.tsx +4 -4
- package/src/components/kbd.tsx +1 -1
- package/src/components/label.tsx +1 -1
- package/src/components/media-object.tsx +2 -0
- package/src/components/menubar.tsx +21 -21
- package/src/components/native-select.tsx +5 -5
- package/src/components/navigation-menu.tsx +10 -10
- package/src/components/notification.tsx +13 -11
- package/src/components/page-heading.tsx +8 -8
- package/src/components/pagination.tsx +1 -1
- package/src/components/popover.tsx +1 -1
- package/src/components/progress.tsx +1 -1
- package/src/components/property-row.tsx +3 -1
- package/src/components/radio-card.tsx +10 -8
- package/src/components/radio-group.tsx +4 -4
- package/src/components/resizable.tsx +3 -3
- package/src/components/scroll-area.tsx +1 -1
- package/src/components/section-heading.tsx +3 -3
- package/src/components/select.tsx +13 -13
- package/src/components/separator.tsx +1 -1
- package/src/components/sheet.tsx +4 -4
- package/src/components/sidebar.tsx +20 -20
- package/src/components/skeleton.tsx +1 -1
- package/src/components/slider.tsx +2 -2
- package/src/components/sonner.tsx +9 -8
- package/src/components/stacked-list.tsx +5 -5
- package/src/components/stat-card.tsx +9 -7
- package/src/components/steps.tsx +16 -14
- package/src/components/switch.tsx +2 -2
- package/src/components/table.tsx +9 -9
- package/src/components/tabs.tsx +2 -2
- package/src/components/textarea.tsx +4 -4
- package/src/components/toggle.tsx +9 -9
- package/src/components/tooltip.tsx +1 -1
- package/src/tokens/tokens.ts +36 -9
- package/dist/chunk-ELL3JM3Z.js.map +0 -1
|
@@ -16,35 +16,35 @@ function Calendar({ className, classNames, showOutsideDays = true, ...props }: C
|
|
|
16
16
|
months: "flex flex-col sm:flex-row gap-4",
|
|
17
17
|
month: "flex flex-col gap-4",
|
|
18
18
|
month_caption: "flex justify-center pt-1 relative items-center px-10",
|
|
19
|
-
caption_label: "body font-semibold text-
|
|
19
|
+
caption_label: "body font-semibold text-foreground",
|
|
20
20
|
nav: "flex items-center justify-between absolute inset-x-0 top-1",
|
|
21
21
|
button_previous: cn(
|
|
22
22
|
"absolute left-0 h-7 w-7 flex items-center justify-center rounded-md",
|
|
23
|
-
"text-
|
|
23
|
+
"text-muted-foreground hover:bg-muted hover:text-muted-foreground transition-colors",
|
|
24
24
|
),
|
|
25
25
|
button_next: cn(
|
|
26
26
|
"absolute right-0 h-7 w-7 flex items-center justify-center rounded-md",
|
|
27
|
-
"text-
|
|
27
|
+
"text-muted-foreground hover:bg-muted hover:text-muted-foreground transition-colors",
|
|
28
28
|
),
|
|
29
29
|
month_grid: "w-full border-collapse mt-1",
|
|
30
30
|
weekdays: "flex",
|
|
31
|
-
weekday: "w-9 h-9 flex items-center justify-center label text-
|
|
31
|
+
weekday: "w-9 h-9 flex items-center justify-center label text-muted-foreground font-normal",
|
|
32
32
|
week: "flex w-full mt-1",
|
|
33
33
|
day: "relative p-0 text-center",
|
|
34
34
|
day_button: cn(
|
|
35
35
|
"h-9 w-9 flex items-center justify-center rounded-full body font-normal",
|
|
36
|
-
"text-
|
|
36
|
+
"text-foreground hover:bg-muted transition-colors",
|
|
37
37
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
|
|
38
38
|
),
|
|
39
39
|
selected:
|
|
40
|
-
"[&>button]:bg-brand-primary [&>button]:text-
|
|
40
|
+
"[&>button]:bg-brand-primary [&>button]:text-background [&>button]:hover:bg-brand-primary/90",
|
|
41
41
|
today: "[&>button]:font-semibold [&>button]:text-brand-primary",
|
|
42
|
-
outside: "opacity-40 [&>button]:text-
|
|
42
|
+
outside: "opacity-40 [&>button]:text-muted-foreground",
|
|
43
43
|
disabled: "opacity-30 [&>button]:cursor-not-allowed",
|
|
44
|
-
range_start: "[&>button]:bg-brand-primary [&>button]:text-
|
|
45
|
-
range_end: "[&>button]:bg-brand-primary [&>button]:text-
|
|
44
|
+
range_start: "[&>button]:bg-brand-primary [&>button]:text-background [&>button]:rounded-full",
|
|
45
|
+
range_end: "[&>button]:bg-brand-primary [&>button]:text-background [&>button]:rounded-full",
|
|
46
46
|
range_middle:
|
|
47
|
-
"[&>button]:bg-brand-primary/15 [&>button]:text-
|
|
47
|
+
"[&>button]:bg-brand-primary/15 [&>button]:text-foreground [&>button]:rounded-none",
|
|
48
48
|
hidden: "invisible",
|
|
49
49
|
...classNames,
|
|
50
50
|
}}
|
|
@@ -21,15 +21,15 @@ function CardHeading({ title, description, actions, children, className }: CardH
|
|
|
21
21
|
return (
|
|
22
22
|
<div
|
|
23
23
|
className={cn(
|
|
24
|
-
"flex items-start justify-between gap-4 border-b border-
|
|
24
|
+
"flex items-start justify-between gap-4 border-b border-border px-5 py-4",
|
|
25
25
|
className,
|
|
26
26
|
)}
|
|
27
27
|
>
|
|
28
28
|
<div className="flex min-w-0 items-center gap-3">
|
|
29
29
|
{children}
|
|
30
30
|
<div className="min-w-0">
|
|
31
|
-
<p className="body font-semibold text-
|
|
32
|
-
{description && <p className="caption text-
|
|
31
|
+
<p className="body font-semibold text-foreground truncate">{title}</p>
|
|
32
|
+
{description && <p className="caption text-muted-foreground mt-0.5">{description}</p>}
|
|
33
33
|
</div>
|
|
34
34
|
</div>
|
|
35
35
|
|
package/src/components/card.tsx
CHANGED
|
@@ -9,8 +9,8 @@ import { cn } from "../lib/utils";
|
|
|
9
9
|
const cardVariants = cva("rounded-xl", {
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
12
|
-
default: "bg-
|
|
13
|
-
muted: "bg-
|
|
12
|
+
default: "bg-card shadow-natural",
|
|
13
|
+
muted: "bg-muted",
|
|
14
14
|
ghost: "bg-transparent",
|
|
15
15
|
},
|
|
16
16
|
},
|
|
@@ -54,7 +54,7 @@ const CardTitle = React.forwardRef<HTMLHeadingElement, React.ComponentProps<"h3"
|
|
|
54
54
|
<h3
|
|
55
55
|
ref={ref}
|
|
56
56
|
data-slot="card-title"
|
|
57
|
-
className={cn("subheading text-
|
|
57
|
+
className={cn("subheading text-foreground", className)}
|
|
58
58
|
{...props}
|
|
59
59
|
/>
|
|
60
60
|
),
|
|
@@ -66,7 +66,7 @@ const CardDescription = React.forwardRef<HTMLParagraphElement, React.ComponentPr
|
|
|
66
66
|
<p
|
|
67
67
|
ref={ref}
|
|
68
68
|
data-slot="card-description"
|
|
69
|
-
className={cn("caption text-
|
|
69
|
+
className={cn("caption text-muted-foreground", className)}
|
|
70
70
|
{...props}
|
|
71
71
|
/>
|
|
72
72
|
),
|
package/src/components/chart.tsx
CHANGED
|
@@ -6,12 +6,12 @@ import { cn } from "../lib/utils";
|
|
|
6
6
|
import { tokens } from "../tokens/tokens";
|
|
7
7
|
|
|
8
8
|
export const CHART_COLORS = {
|
|
9
|
-
primary: tokens.
|
|
10
|
-
secondary: tokens.semantic.light.success,
|
|
11
|
-
tertiary: tokens.
|
|
12
|
-
muted: tokens.
|
|
13
|
-
subtle: tokens.
|
|
14
|
-
dark: tokens.
|
|
9
|
+
primary: tokens.semantic.light.primary,
|
|
10
|
+
secondary: tokens.semantic.light.success,
|
|
11
|
+
tertiary: tokens.semantic.light.warning,
|
|
12
|
+
muted: tokens.semantic.light.mutedForeground,
|
|
13
|
+
subtle: tokens.semantic.light.border,
|
|
14
|
+
dark: tokens.semantic.light.foreground,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
interface ChartContainerProps {
|
|
@@ -39,16 +39,16 @@ interface ChartTooltipProps extends Partial<TooltipContentProps<number, string>>
|
|
|
39
39
|
function ChartTooltip({ active, payload, label, labelClassName }: ChartTooltipProps) {
|
|
40
40
|
if (!active || !payload?.length) return null;
|
|
41
41
|
return (
|
|
42
|
-
<div className="rounded-xl border border-
|
|
43
|
-
{label && <p className={cn("mb-1 label text-
|
|
42
|
+
<div className="rounded-xl border border-border bg-card px-3 py-2 shadow-elevated">
|
|
43
|
+
{label && <p className={cn("mb-1 label text-muted-foreground", labelClassName)}>{label}</p>}
|
|
44
44
|
{payload.map((entry, i) => (
|
|
45
45
|
<div key={i} className="flex items-center gap-2">
|
|
46
46
|
<span
|
|
47
47
|
className="inline-block h-2 w-2 rounded-full"
|
|
48
48
|
style={{ backgroundColor: entry.color }}
|
|
49
49
|
/>
|
|
50
|
-
<span className="caption text-
|
|
51
|
-
<span className="caption font-medium text-
|
|
50
|
+
<span className="caption text-muted-foreground">{entry.name}:</span>
|
|
51
|
+
<span className="caption font-medium text-foreground">{entry.value}</span>
|
|
52
52
|
</div>
|
|
53
53
|
))}
|
|
54
54
|
</div>
|
|
@@ -12,7 +12,7 @@ const CheckboxRoot = React.forwardRef<
|
|
|
12
12
|
<Checkbox.Root
|
|
13
13
|
ref={ref}
|
|
14
14
|
className={cn(
|
|
15
|
-
"peer size-4 shrink-0 rounded border border-
|
|
15
|
+
"peer size-4 shrink-0 rounded border border-border bg-card transition-colors",
|
|
16
16
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
|
|
17
17
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
18
18
|
"data-[state=checked]:bg-brand-primary data-[state=checked]:border-brand-primary",
|
|
@@ -21,7 +21,7 @@ const CheckboxRoot = React.forwardRef<
|
|
|
21
21
|
)}
|
|
22
22
|
{...props}
|
|
23
23
|
>
|
|
24
|
-
<Checkbox.Indicator className="flex items-center justify-center text-
|
|
24
|
+
<Checkbox.Indicator className="flex items-center justify-center text-background">
|
|
25
25
|
{props.checked === "indeterminate" ? (
|
|
26
26
|
<Minus size={11} strokeWidth={3} />
|
|
27
27
|
) : (
|
|
@@ -42,7 +42,7 @@ function ColorInput({
|
|
|
42
42
|
return (
|
|
43
43
|
<div
|
|
44
44
|
className={cn(
|
|
45
|
-
"inline-flex h-8 items-stretch gap-0 rounded-md border border-
|
|
45
|
+
"inline-flex h-8 items-stretch gap-0 rounded-md border border-border bg-card overflow-hidden",
|
|
46
46
|
"focus-within:ring-2 focus-within:ring-brand-primary/20 focus-within:border-brand-primary",
|
|
47
47
|
disabled && "opacity-50 pointer-events-none",
|
|
48
48
|
className,
|
|
@@ -65,19 +65,19 @@ function ColorInput({
|
|
|
65
65
|
commitHex("#" + (e.target as HTMLInputElement).value.replace(/^#/, ""));
|
|
66
66
|
}}
|
|
67
67
|
spellCheck={false}
|
|
68
|
-
className="w-[5.5rem] caption text-
|
|
68
|
+
className="w-[5.5rem] caption text-foreground px-2 bg-transparent border-l border-border focus:outline-none uppercase"
|
|
69
69
|
/>
|
|
70
70
|
{typeof opacity === "number" && onOpacityChange && (
|
|
71
|
-
<div className="relative flex items-center border-l border-
|
|
71
|
+
<div className="relative flex items-center border-l border-border">
|
|
72
72
|
<input
|
|
73
73
|
type="number"
|
|
74
74
|
min={0}
|
|
75
75
|
max={100}
|
|
76
76
|
value={Math.round(opacity)}
|
|
77
77
|
onChange={(e) => onOpacityChange(Number(e.target.value))}
|
|
78
|
-
className="w-12 caption text-
|
|
78
|
+
className="w-12 caption text-foreground px-2 bg-transparent focus:outline-none text-right [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
|
|
79
79
|
/>
|
|
80
|
-
<span aria-hidden className="caption-xs text-
|
|
80
|
+
<span aria-hidden className="caption-xs text-muted-foreground pr-2">
|
|
81
81
|
%
|
|
82
82
|
</span>
|
|
83
83
|
</div>
|
|
@@ -86,5 +86,7 @@ function ColorInput({
|
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
ColorInput.displayName = "ColorInput";
|
|
90
|
+
|
|
89
91
|
export { ColorInput };
|
|
90
92
|
export type { ColorInputProps };
|
|
@@ -45,8 +45,8 @@ function ColorPicker({
|
|
|
45
45
|
disabled={disabled}
|
|
46
46
|
aria-label={ariaLabel}
|
|
47
47
|
className={cn(
|
|
48
|
-
"inline-flex h-8 w-8 items-center justify-center rounded-md border border-
|
|
49
|
-
"transition-colors hover:border-
|
|
48
|
+
"inline-flex h-8 w-8 items-center justify-center rounded-md border border-border bg-card",
|
|
49
|
+
"transition-colors hover:border-border",
|
|
50
50
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/20 focus-visible:border-brand-primary",
|
|
51
51
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
52
52
|
className,
|
|
@@ -63,7 +63,7 @@ function ColorPicker({
|
|
|
63
63
|
<Popover.Content
|
|
64
64
|
align="start"
|
|
65
65
|
sideOffset={6}
|
|
66
|
-
className="z-50 w-56 rounded-xl bg-
|
|
66
|
+
className="z-50 w-56 rounded-xl bg-card p-3 shadow-natural outline-none"
|
|
67
67
|
>
|
|
68
68
|
<input
|
|
69
69
|
type="color"
|
|
@@ -72,10 +72,10 @@ function ColorPicker({
|
|
|
72
72
|
setHex(e.target.value);
|
|
73
73
|
onChange(e.target.value);
|
|
74
74
|
}}
|
|
75
|
-
className="h-10 w-full cursor-pointer rounded-md border border-
|
|
75
|
+
className="h-10 w-full cursor-pointer rounded-md border border-border bg-card p-0.5"
|
|
76
76
|
/>
|
|
77
77
|
<div className="mt-2 flex items-center gap-2">
|
|
78
|
-
<span className="caption text-
|
|
78
|
+
<span className="caption text-muted-foreground">Hex</span>
|
|
79
79
|
<input
|
|
80
80
|
type="text"
|
|
81
81
|
value={hex}
|
|
@@ -84,7 +84,7 @@ function ColorPicker({
|
|
|
84
84
|
onKeyDown={(e) => {
|
|
85
85
|
if (e.key === "Enter") commit((e.target as HTMLInputElement).value);
|
|
86
86
|
}}
|
|
87
|
-
className="flex-1 h-7 rounded-md border border-
|
|
87
|
+
className="flex-1 h-7 rounded-md border border-border bg-card px-2 caption text-foreground focus:outline-none focus:border-brand-primary"
|
|
88
88
|
/>
|
|
89
89
|
</div>
|
|
90
90
|
{presets && presets.length > 0 && (
|
|
@@ -107,5 +107,7 @@ function ColorPicker({
|
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
ColorPicker.displayName = "ColorPicker";
|
|
111
|
+
|
|
110
112
|
export { ColorPicker };
|
|
111
113
|
export type { ColorPickerProps };
|
|
@@ -33,8 +33,10 @@ function Combobox({
|
|
|
33
33
|
}: ComboboxProps) {
|
|
34
34
|
const [open, setOpen] = React.useState(false);
|
|
35
35
|
const [query, setQuery] = React.useState("");
|
|
36
|
+
const [activeIndex, setActiveIndex] = React.useState(-1);
|
|
36
37
|
const containerRef = React.useRef<HTMLDivElement>(null);
|
|
37
38
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
39
|
+
const listboxId = React.useId();
|
|
38
40
|
|
|
39
41
|
const selectedOption = options.find((o) => o.value === value);
|
|
40
42
|
|
|
@@ -47,6 +49,11 @@ function Combobox({
|
|
|
47
49
|
o.description?.toLowerCase().includes(query.toLowerCase()),
|
|
48
50
|
);
|
|
49
51
|
|
|
52
|
+
// Reset active index when the filtered list changes
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
setActiveIndex(-1);
|
|
55
|
+
}, [filtered.length, open]);
|
|
56
|
+
|
|
50
57
|
// Close on outside click
|
|
51
58
|
React.useEffect(() => {
|
|
52
59
|
function onMouseDown(e: MouseEvent) {
|
|
@@ -59,25 +66,45 @@ function Combobox({
|
|
|
59
66
|
return () => document.removeEventListener("mousedown", onMouseDown);
|
|
60
67
|
}, []);
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
function closeDropdown() {
|
|
70
|
+
setOpen(false);
|
|
71
|
+
setQuery("");
|
|
72
|
+
setActiveIndex(-1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function selectOption(opt: ComboboxOption) {
|
|
76
|
+
onValueChange?.(opt.value);
|
|
77
|
+
closeDropdown();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function handleInputKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
|
|
81
|
+
if (e.key === "Escape") {
|
|
82
|
+
closeDropdown();
|
|
83
|
+
return;
|
|
70
84
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
if (e.key === "ArrowDown") {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
if (!open) { setOpen(true); return; }
|
|
88
|
+
setActiveIndex((i) => Math.min(i + 1, filtered.length - 1));
|
|
89
|
+
} else if (e.key === "ArrowUp") {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
setActiveIndex((i) => Math.max(i - 1, 0));
|
|
92
|
+
} else if (e.key === "Enter") {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
if (!open) { setOpen(true); return; }
|
|
95
|
+
const target = activeIndex >= 0 ? filtered[activeIndex] : filtered[0];
|
|
96
|
+
if (target) selectOption(target);
|
|
97
|
+
} else if (e.key === "Tab") {
|
|
98
|
+
closeDropdown();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
74
101
|
|
|
75
102
|
return (
|
|
76
103
|
<div ref={containerRef} className={cn("relative", className)}>
|
|
77
104
|
{/* Trigger */}
|
|
78
105
|
<div className="relative flex items-center">
|
|
79
106
|
{selectedOption?.avatar && !open && (
|
|
80
|
-
<div className="pointer-events-none absolute left-2.5 flex size-5 items-center justify-center rounded-full bg-
|
|
107
|
+
<div className="pointer-events-none absolute left-2.5 flex size-5 items-center justify-center rounded-full bg-muted caption-xs font-semibold text-muted-foreground shrink-0">
|
|
81
108
|
{selectedOption.avatar}
|
|
82
109
|
</div>
|
|
83
110
|
)}
|
|
@@ -92,10 +119,14 @@ function Combobox({
|
|
|
92
119
|
type="text"
|
|
93
120
|
role="combobox"
|
|
94
121
|
aria-expanded={open}
|
|
122
|
+
aria-haspopup="listbox"
|
|
123
|
+
aria-controls={open ? listboxId : undefined}
|
|
124
|
+
aria-activedescendant={open && activeIndex >= 0 ? `${listboxId}-option-${activeIndex}` : undefined}
|
|
125
|
+
aria-autocomplete="list"
|
|
95
126
|
className={cn(
|
|
96
|
-
"flex h-9 w-full rounded-lg border border-
|
|
97
|
-
"placeholder:text-
|
|
98
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-
|
|
127
|
+
"flex h-9 w-full rounded-lg border border-border bg-card pr-8 body text-foreground",
|
|
128
|
+
"placeholder:text-muted-foreground",
|
|
129
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30 focus-visible:border-border",
|
|
99
130
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
100
131
|
selectedOption?.avatar && !open
|
|
101
132
|
? "pl-8"
|
|
@@ -113,15 +144,16 @@ function Combobox({
|
|
|
113
144
|
setOpen(true);
|
|
114
145
|
setQuery("");
|
|
115
146
|
}}
|
|
147
|
+
onKeyDown={handleInputKeyDown}
|
|
116
148
|
/>
|
|
117
149
|
<button
|
|
118
150
|
type="button"
|
|
119
151
|
tabIndex={-1}
|
|
120
|
-
|
|
152
|
+
aria-label={open ? "Close" : "Open"}
|
|
153
|
+
className="absolute inset-y-0 right-0 flex items-center px-2 text-muted-foreground hover:text-muted-foreground transition-colors"
|
|
121
154
|
onClick={() => {
|
|
122
155
|
if (open) {
|
|
123
|
-
|
|
124
|
-
setQuery("");
|
|
156
|
+
closeDropdown();
|
|
125
157
|
} else {
|
|
126
158
|
setOpen(true);
|
|
127
159
|
inputRef.current?.focus();
|
|
@@ -134,37 +166,38 @@ function Combobox({
|
|
|
134
166
|
|
|
135
167
|
{/* Dropdown */}
|
|
136
168
|
{open && (
|
|
137
|
-
<div className="absolute z-50 mt-1 w-full overflow-hidden rounded-lg border border-
|
|
138
|
-
<ul role="listbox" className="max-h-60 overflow-auto py-1">
|
|
169
|
+
<div className="absolute z-50 mt-1 w-full overflow-hidden rounded-lg border border-border bg-card shadow-elevated">
|
|
170
|
+
<ul id={listboxId} role="listbox" className="max-h-60 overflow-auto py-1">
|
|
139
171
|
{filtered.length === 0 ? (
|
|
140
|
-
<li className="px-3 py-2 body text-
|
|
172
|
+
<li className="px-3 py-2 body text-muted-foreground">{emptyText}</li>
|
|
141
173
|
) : (
|
|
142
|
-
filtered.map((option) => {
|
|
174
|
+
filtered.map((option, idx) => {
|
|
143
175
|
const isSelected = option.value === value;
|
|
176
|
+
const isActive = idx === activeIndex;
|
|
144
177
|
return (
|
|
145
178
|
<li
|
|
146
179
|
key={option.value}
|
|
180
|
+
id={`${listboxId}-option-${idx}`}
|
|
147
181
|
role="option"
|
|
148
182
|
aria-selected={isSelected}
|
|
149
183
|
className={cn(
|
|
150
184
|
"flex cursor-pointer select-none items-center gap-2.5 px-3 py-2 body transition-colors",
|
|
151
185
|
isSelected
|
|
152
|
-
? "bg-brand-primary text-
|
|
153
|
-
:
|
|
186
|
+
? "bg-brand-primary text-background"
|
|
187
|
+
: isActive
|
|
188
|
+
? "bg-muted text-foreground"
|
|
189
|
+
: "text-foreground hover:bg-muted",
|
|
154
190
|
)}
|
|
155
191
|
onMouseDown={(e) => e.preventDefault()}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
setQuery("");
|
|
159
|
-
setOpen(false);
|
|
160
|
-
}}
|
|
192
|
+
onMouseEnter={() => setActiveIndex(idx)}
|
|
193
|
+
onClick={() => selectOption(option)}
|
|
161
194
|
>
|
|
162
195
|
{/* Avatar */}
|
|
163
196
|
{option.avatar && (
|
|
164
197
|
<div
|
|
165
198
|
className={cn(
|
|
166
199
|
"flex size-6 shrink-0 items-center justify-center rounded-full caption-xs font-semibold",
|
|
167
|
-
isSelected ? "bg-
|
|
200
|
+
isSelected ? "bg-card/20 text-background" : "bg-muted text-muted-foreground",
|
|
168
201
|
)}
|
|
169
202
|
>
|
|
170
203
|
{option.avatar}
|
|
@@ -185,7 +218,7 @@ function Combobox({
|
|
|
185
218
|
<p
|
|
186
219
|
className={cn(
|
|
187
220
|
"caption truncate",
|
|
188
|
-
isSelected ? "text-
|
|
221
|
+
isSelected ? "text-background/70" : "text-muted-foreground",
|
|
189
222
|
)}
|
|
190
223
|
>
|
|
191
224
|
{option.description}
|
|
@@ -204,4 +237,6 @@ function Combobox({
|
|
|
204
237
|
);
|
|
205
238
|
}
|
|
206
239
|
|
|
240
|
+
Combobox.displayName = "Combobox";
|
|
241
|
+
|
|
207
242
|
export { Combobox };
|
|
@@ -73,12 +73,12 @@ function CommandPalette({
|
|
|
73
73
|
<Dialog open={open} onOpenChange={(v) => !v && onClose()}>
|
|
74
74
|
<DialogContent className="gap-0 overflow-hidden p-0 sm:max-w-xl">
|
|
75
75
|
{/* Search input */}
|
|
76
|
-
<div className="flex items-center gap-3 border-b border-
|
|
77
|
-
<Search size={16} className="shrink-0 text-
|
|
76
|
+
<div className="flex items-center gap-3 border-b border-border px-4">
|
|
77
|
+
<Search size={16} className="shrink-0 text-muted-foreground" />
|
|
78
78
|
<input
|
|
79
79
|
ref={inputRef}
|
|
80
80
|
type="text"
|
|
81
|
-
className="h-12 flex-1 bg-transparent body text-
|
|
81
|
+
className="h-12 flex-1 bg-transparent body text-foreground placeholder:text-muted-foreground focus:outline-none"
|
|
82
82
|
placeholder={placeholder}
|
|
83
83
|
value={query}
|
|
84
84
|
onChange={(e) => setQuery(e.target.value)}
|
|
@@ -88,7 +88,7 @@ function CommandPalette({
|
|
|
88
88
|
}}
|
|
89
89
|
/>
|
|
90
90
|
<kbd className="hidden items-center gap-0.5 sm:flex">
|
|
91
|
-
<span className="caption-xs font-medium text-
|
|
91
|
+
<span className="caption-xs font-medium text-muted-foreground rounded border border-border px-1.5 py-0.5">
|
|
92
92
|
esc
|
|
93
93
|
</span>
|
|
94
94
|
</kbd>
|
|
@@ -97,31 +97,31 @@ function CommandPalette({
|
|
|
97
97
|
{/* Results */}
|
|
98
98
|
<div className="max-h-80 overflow-y-auto">
|
|
99
99
|
{filtered.length === 0 ? (
|
|
100
|
-
<p className="px-4 py-8 text-center body text-
|
|
100
|
+
<p className="px-4 py-8 text-center body text-muted-foreground">{emptyText}</p>
|
|
101
101
|
) : (
|
|
102
102
|
<ul role="list" className="py-2">
|
|
103
103
|
{categories.map((cat) => (
|
|
104
104
|
<li key={cat}>
|
|
105
|
-
{cat && <p className="label px-4 py-2 text-
|
|
105
|
+
{cat && <p className="label px-4 py-2 text-muted-foreground">{cat}</p>}
|
|
106
106
|
{grouped[cat].map((item, i) => (
|
|
107
107
|
<button
|
|
108
108
|
key={item.id ?? i}
|
|
109
109
|
type="button"
|
|
110
|
-
className="flex w-full items-center gap-3 px-4 py-2.5 body text-left transition-colors hover:bg-
|
|
110
|
+
className="flex w-full items-center gap-3 px-4 py-2.5 body text-left transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none"
|
|
111
111
|
onClick={() => handleSelect(item)}
|
|
112
112
|
>
|
|
113
113
|
{item.icon && (
|
|
114
|
-
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg border border-
|
|
114
|
+
<span className="flex size-8 shrink-0 items-center justify-center rounded-lg border border-border bg-card text-muted-foreground">
|
|
115
115
|
{item.icon}
|
|
116
116
|
</span>
|
|
117
117
|
)}
|
|
118
118
|
<div className="min-w-0 flex-1">
|
|
119
|
-
<p className="font-medium text-
|
|
119
|
+
<p className="font-medium text-foreground truncate">{item.label}</p>
|
|
120
120
|
{item.description && (
|
|
121
|
-
<p className="caption text-
|
|
121
|
+
<p className="caption text-muted-foreground truncate">{item.description}</p>
|
|
122
122
|
)}
|
|
123
123
|
</div>
|
|
124
|
-
<ArrowRight size={13} className="shrink-0 text-
|
|
124
|
+
<ArrowRight size={13} className="shrink-0 text-muted-foreground" />
|
|
125
125
|
</button>
|
|
126
126
|
))}
|
|
127
127
|
</li>
|
|
@@ -134,4 +134,6 @@ function CommandPalette({
|
|
|
134
134
|
);
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
CommandPalette.displayName = "CommandPalette";
|
|
138
|
+
|
|
137
139
|
export { CommandPalette };
|
|
@@ -44,7 +44,7 @@ function Command({ className, children, onSelect, ...props }: CommandProps) {
|
|
|
44
44
|
<CommandVisibleCtx.Provider value={visibleCount}>
|
|
45
45
|
<div
|
|
46
46
|
className={cn(
|
|
47
|
-
"flex flex-col overflow-hidden rounded-xl border border-
|
|
47
|
+
"flex flex-col overflow-hidden rounded-xl border border-border bg-card",
|
|
48
48
|
className,
|
|
49
49
|
)}
|
|
50
50
|
{...props}
|
|
@@ -62,8 +62,8 @@ const CommandInput = React.forwardRef<
|
|
|
62
62
|
>(({ className, ...props }, ref) => {
|
|
63
63
|
const { query, setQuery } = React.useContext(CommandCtx);
|
|
64
64
|
return (
|
|
65
|
-
<div className="flex items-center gap-2 border-b border-
|
|
66
|
-
<Search size={14} className="shrink-0 text-
|
|
65
|
+
<div className="flex items-center gap-2 border-b border-border px-3">
|
|
66
|
+
<Search size={14} className="shrink-0 text-muted-foreground" />
|
|
67
67
|
<input
|
|
68
68
|
ref={ref}
|
|
69
69
|
type="text"
|
|
@@ -71,8 +71,8 @@ const CommandInput = React.forwardRef<
|
|
|
71
71
|
autoCorrect="off"
|
|
72
72
|
spellCheck={false}
|
|
73
73
|
className={cn(
|
|
74
|
-
"flex h-10 w-full bg-transparent body text-
|
|
75
|
-
"placeholder:text-
|
|
74
|
+
"flex h-10 w-full bg-transparent body text-foreground",
|
|
75
|
+
"placeholder:text-muted-foreground focus:outline-none",
|
|
76
76
|
className,
|
|
77
77
|
)}
|
|
78
78
|
value={query}
|
|
@@ -135,7 +135,7 @@ const CommandEmpty = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLD
|
|
|
135
135
|
return (
|
|
136
136
|
<div
|
|
137
137
|
ref={ref}
|
|
138
|
-
className={cn("py-6 text-center caption text-
|
|
138
|
+
className={cn("py-6 text-center caption text-muted-foreground", className)}
|
|
139
139
|
{...props}
|
|
140
140
|
>
|
|
141
141
|
{children}
|
|
@@ -152,7 +152,7 @@ interface CommandGroupProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
152
152
|
const CommandGroup = React.forwardRef<HTMLDivElement, CommandGroupProps>(
|
|
153
153
|
({ className, heading, children, ...props }, ref) => (
|
|
154
154
|
<div ref={ref} role="group" className={cn("py-1", className)} {...props}>
|
|
155
|
-
{heading && <p className="px-3 pb-1 pt-0.5 label text-
|
|
155
|
+
{heading && <p className="px-3 pb-1 pt-0.5 label text-muted-foreground">{heading}</p>}
|
|
156
156
|
{children}
|
|
157
157
|
</div>
|
|
158
158
|
),
|
|
@@ -197,9 +197,9 @@ const CommandItem = React.forwardRef<HTMLDivElement, CommandItemProps>(
|
|
|
197
197
|
tabIndex={disabled ? undefined : 0}
|
|
198
198
|
className={cn(
|
|
199
199
|
"relative flex cursor-default select-none items-center gap-2 rounded-md px-2.5 py-1.5 mx-1",
|
|
200
|
-
"body text-
|
|
201
|
-
"hover:bg-
|
|
202
|
-
"focus:bg-
|
|
200
|
+
"body text-muted-foreground outline-none transition-colors",
|
|
201
|
+
"hover:bg-muted hover:text-foreground",
|
|
202
|
+
"focus:bg-muted focus:text-foreground",
|
|
203
203
|
disabled && "pointer-events-none opacity-40",
|
|
204
204
|
className,
|
|
205
205
|
)}
|
|
@@ -221,7 +221,7 @@ CommandItem.displayName = "CommandItem";
|
|
|
221
221
|
|
|
222
222
|
const CommandSeparator = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
223
223
|
({ className, ...props }, ref) => (
|
|
224
|
-
<div ref={ref} className={cn("my-1 h-px bg-
|
|
224
|
+
<div ref={ref} className={cn("my-1 h-px bg-muted", className)} {...props} />
|
|
225
225
|
),
|
|
226
226
|
);
|
|
227
227
|
CommandSeparator.displayName = "CommandSeparator";
|
|
@@ -229,13 +229,15 @@ CommandSeparator.displayName = "CommandSeparator";
|
|
|
229
229
|
function CommandShortcut({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) {
|
|
230
230
|
return (
|
|
231
231
|
<span
|
|
232
|
-
className={cn("ml-auto caption text-
|
|
232
|
+
className={cn("ml-auto caption text-muted-foreground tracking-widest font-medium", className)}
|
|
233
233
|
{...props}
|
|
234
234
|
/>
|
|
235
235
|
);
|
|
236
236
|
}
|
|
237
237
|
CommandShortcut.displayName = "CommandShortcut";
|
|
238
238
|
|
|
239
|
+
Command.displayName = "Command";
|
|
240
|
+
|
|
239
241
|
export {
|
|
240
242
|
Command,
|
|
241
243
|
CommandInput,
|