@hilum/ui 3.5.3 → 3.6.0
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/dist/index.d.ts +77 -18
- package/dist/index.js +672 -554
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/button.tsx +1 -1
- package/src/components/color-input.tsx +5 -2
- package/src/components/combobox.tsx +5 -2
- package/src/components/input-group.tsx +3 -1
- package/src/components/input-number.tsx +16 -9
- package/src/components/input.tsx +7 -1
- package/src/components/native-select.tsx +30 -22
- package/src/components/stat-card.tsx +7 -1
- package/src/components/textarea.tsx +7 -1
- package/src/index.ts +3 -0
- package/src/lib/interaction.ts +1 -2
- package/src/lib/shape-context.tsx +2 -2
package/package.json
CHANGED
|
@@ -210,7 +210,7 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
210
210
|
className="transition-[stroke-width] duration-80 group-hover:stroke-[2]"
|
|
211
211
|
/>
|
|
212
212
|
)}
|
|
213
|
-
<span>{children}</span>
|
|
213
|
+
{LeadingIcon || TrailingIcon ? <span>{children}</span> : children}
|
|
214
214
|
{TrailingIcon && (
|
|
215
215
|
<TrailingIcon
|
|
216
216
|
size={iconSize}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../lib/utils";
|
|
5
5
|
import { motionClasses } from "../lib/interaction";
|
|
6
|
+
import { useShape } from "../lib/shape-context";
|
|
6
7
|
import { ColorPicker } from "./color-picker";
|
|
7
8
|
|
|
8
9
|
interface ColorInputProps {
|
|
@@ -29,6 +30,7 @@ function ColorInput({
|
|
|
29
30
|
disabled,
|
|
30
31
|
presets,
|
|
31
32
|
}: ColorInputProps) {
|
|
33
|
+
const shape = useShape();
|
|
32
34
|
const [hex, setHex] = React.useState(value);
|
|
33
35
|
|
|
34
36
|
React.useEffect(() => {
|
|
@@ -43,8 +45,9 @@ function ColorInput({
|
|
|
43
45
|
return (
|
|
44
46
|
<div
|
|
45
47
|
className={cn(
|
|
46
|
-
"inline-flex h-8 items-stretch gap-0
|
|
47
|
-
|
|
48
|
+
"inline-flex h-8 items-stretch gap-0 overflow-hidden border border-border bg-background",
|
|
49
|
+
shape.input,
|
|
50
|
+
"focus-within:border-brand-primary focus-within:ring-2 focus-within:ring-brand-primary/20",
|
|
48
51
|
motionClasses,
|
|
49
52
|
disabled && "opacity-50 pointer-events-none",
|
|
50
53
|
className,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Check, ChevronsUpDown } from "lucide-react";
|
|
5
5
|
import { cn } from "../lib/utils";
|
|
6
|
+
import { useShape } from "../lib/shape-context";
|
|
6
7
|
|
|
7
8
|
export interface ComboboxOption {
|
|
8
9
|
value: string;
|
|
@@ -31,6 +32,7 @@ function Combobox({
|
|
|
31
32
|
emptyText = "No results found.",
|
|
32
33
|
className,
|
|
33
34
|
}: ComboboxProps) {
|
|
35
|
+
const shape = useShape();
|
|
34
36
|
const [open, setOpen] = React.useState(false);
|
|
35
37
|
const [query, setQuery] = React.useState("");
|
|
36
38
|
const [activeIndex, setActiveIndex] = React.useState(-1);
|
|
@@ -132,9 +134,10 @@ function Combobox({
|
|
|
132
134
|
}
|
|
133
135
|
aria-autocomplete="list"
|
|
134
136
|
className={cn(
|
|
135
|
-
"flex h-10 w-full
|
|
137
|
+
"flex h-10 w-full border border-border bg-background pr-10 body text-foreground",
|
|
138
|
+
shape.input,
|
|
136
139
|
"placeholder:text-muted-foreground",
|
|
137
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30
|
|
140
|
+
"focus-visible:border-border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/30",
|
|
138
141
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
139
142
|
selectedOption?.avatar && !open
|
|
140
143
|
? "pl-8"
|
|
@@ -78,6 +78,7 @@ const InputGroup = forwardRef<HTMLDivElement, InputGroupProps>(
|
|
|
78
78
|
) => {
|
|
79
79
|
const itemsRef = useRef(new Map<number, HTMLLabelElement>());
|
|
80
80
|
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
|
81
|
+
const shape = useShape();
|
|
81
82
|
|
|
82
83
|
const registerItem = useCallback((index: number, element: HTMLLabelElement | null) => {
|
|
83
84
|
if (element) {
|
|
@@ -128,7 +129,8 @@ const InputGroup = forwardRef<HTMLDivElement, InputGroupProps>(
|
|
|
128
129
|
<div
|
|
129
130
|
ref={ref}
|
|
130
131
|
className={cn(
|
|
131
|
-
"relative flex items-center gap-2
|
|
132
|
+
"relative flex items-center gap-2 border bg-background px-3 py-2 text-[13px] transition-colors",
|
|
133
|
+
shape.input,
|
|
132
134
|
error ? "border-destructive/50" : "border-border",
|
|
133
135
|
disabled && "opacity-50",
|
|
134
136
|
pill && "rounded-full",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { cn } from "../lib/utils";
|
|
5
|
+
import { useShape } from "../lib/shape-context";
|
|
5
6
|
|
|
6
7
|
interface InputNumberProps extends Omit<
|
|
7
8
|
React.ComponentProps<"input">,
|
|
@@ -50,6 +51,8 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
50
51
|
},
|
|
51
52
|
ref,
|
|
52
53
|
) => {
|
|
54
|
+
const shape = useShape();
|
|
55
|
+
|
|
53
56
|
const formatValue = React.useCallback(
|
|
54
57
|
(next: number | null) => (next === null ? mixedLabel : next.toFixed(precision)),
|
|
55
58
|
[mixedLabel, precision],
|
|
@@ -63,13 +66,16 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
63
66
|
|
|
64
67
|
const clamp = React.useCallback((n: number) => Math.min(max, Math.max(min, n)), [min, max]);
|
|
65
68
|
|
|
66
|
-
const parseAndClamp = React.useCallback(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
const parseAndClamp = React.useCallback(
|
|
70
|
+
(raw: string) => {
|
|
71
|
+
const n = parseFloat(raw);
|
|
72
|
+
if (Number.isFinite(n)) {
|
|
73
|
+
return clamp(n);
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
},
|
|
77
|
+
[clamp],
|
|
78
|
+
);
|
|
73
79
|
|
|
74
80
|
const commit = (raw: string) => {
|
|
75
81
|
const next = parseAndClamp(raw);
|
|
@@ -105,8 +111,9 @@ const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>(
|
|
|
105
111
|
return (
|
|
106
112
|
<div
|
|
107
113
|
className={cn(
|
|
108
|
-
"inline-flex h-8 items-stretch
|
|
109
|
-
|
|
114
|
+
"inline-flex h-8 items-stretch overflow-hidden border border-border bg-background",
|
|
115
|
+
shape.input,
|
|
116
|
+
"focus-within:border-brand-primary focus-within:ring-2 focus-within:ring-brand-primary/20",
|
|
110
117
|
disabled && "opacity-50 pointer-events-none",
|
|
111
118
|
className,
|
|
112
119
|
)}
|
package/src/components/input.tsx
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import * as React from "react";
|
|
2
4
|
import { cn } from "../lib/utils";
|
|
3
5
|
import { controlSurfaceClasses, focusRingClasses, motionClasses } from "../lib/interaction";
|
|
6
|
+
import { useShape } from "../lib/shape-context";
|
|
4
7
|
|
|
5
8
|
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
9
|
+
const shape = useShape();
|
|
10
|
+
|
|
6
11
|
return (
|
|
7
12
|
<input
|
|
8
13
|
type={type}
|
|
9
14
|
data-slot="input"
|
|
10
15
|
className={cn(
|
|
11
|
-
"flex h-10 w-full
|
|
16
|
+
"flex h-10 w-full px-3 py-1",
|
|
17
|
+
shape.input,
|
|
12
18
|
"body text-foreground placeholder:text-muted-foreground",
|
|
13
19
|
controlSurfaceClasses,
|
|
14
20
|
motionClasses,
|
|
@@ -1,32 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import * as React from "react";
|
|
2
4
|
import { ChevronDown } from "lucide-react";
|
|
3
5
|
import { cn } from "../lib/utils";
|
|
6
|
+
import { useShape } from "../lib/shape-context";
|
|
4
7
|
|
|
5
8
|
const NativeSelect = React.forwardRef<
|
|
6
9
|
HTMLSelectElement,
|
|
7
10
|
React.SelectHTMLAttributes<HTMLSelectElement>
|
|
8
|
-
>(({ className, children, ...props }, ref) =>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
11
|
+
>(({ className, children, ...props }, ref) => {
|
|
12
|
+
const shape = useShape();
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="relative">
|
|
16
|
+
<select
|
|
17
|
+
ref={ref}
|
|
18
|
+
className={cn(
|
|
19
|
+
"peer h-10 w-full appearance-none border border-border bg-background pl-3 pr-8",
|
|
20
|
+
shape.input,
|
|
21
|
+
"body text-foreground",
|
|
22
|
+
"focus:border-border focus:outline-none focus:ring-2 focus:ring-ring/30",
|
|
23
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
24
|
+
"aria-[invalid=true]:border-destructive aria-[invalid=true]:focus:ring-destructive/20",
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</select>
|
|
31
|
+
<ChevronDown
|
|
32
|
+
size={14}
|
|
33
|
+
className="pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground peer-disabled:opacity-50"
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
});
|
|
30
38
|
NativeSelect.displayName = "NativeSelect";
|
|
31
39
|
|
|
32
40
|
const NativeSelectOption = React.forwardRef<
|
|
@@ -6,6 +6,7 @@ interface StatCardProps {
|
|
|
6
6
|
label?: string;
|
|
7
7
|
title?: string;
|
|
8
8
|
value?: string | number;
|
|
9
|
+
variant?: "default" | "responsive";
|
|
9
10
|
trend?: {
|
|
10
11
|
value: string;
|
|
11
12
|
direction: "up" | "down" | "neutral";
|
|
@@ -37,6 +38,7 @@ function StatCard({
|
|
|
37
38
|
label,
|
|
38
39
|
title,
|
|
39
40
|
value,
|
|
41
|
+
variant = "default",
|
|
40
42
|
trend,
|
|
41
43
|
icon,
|
|
42
44
|
children,
|
|
@@ -58,11 +60,15 @@ function StatCard({
|
|
|
58
60
|
down: TrendingDown,
|
|
59
61
|
neutral: Minus,
|
|
60
62
|
};
|
|
63
|
+
const surfaceClass =
|
|
64
|
+
variant === "responsive"
|
|
65
|
+
? "min-w-0 border-border bg-transparent p-4 sm:rounded-xl sm:border sm:bg-card sm:p-5 sm:shadow-natural"
|
|
66
|
+
: "min-w-0 rounded-xl border border-border bg-card p-4 shadow-natural sm:p-5";
|
|
61
67
|
|
|
62
68
|
return (
|
|
63
69
|
<div
|
|
64
70
|
className={cn(
|
|
65
|
-
|
|
71
|
+
surfaceClass,
|
|
66
72
|
containerClassName,
|
|
67
73
|
className,
|
|
68
74
|
)}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import * as React from "react";
|
|
2
4
|
import { cn } from "../lib/utils";
|
|
3
5
|
import { controlSurfaceClasses, focusRingClasses, motionClasses } from "../lib/interaction";
|
|
6
|
+
import { useShape } from "../lib/shape-context";
|
|
4
7
|
|
|
5
8
|
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
9
|
+
const shape = useShape();
|
|
10
|
+
|
|
6
11
|
return (
|
|
7
12
|
<textarea
|
|
8
13
|
data-slot="textarea"
|
|
9
14
|
className={cn(
|
|
10
|
-
"flex min-h-[80px] w-full
|
|
15
|
+
"flex min-h-[80px] w-full px-3 py-2",
|
|
16
|
+
shape.input,
|
|
11
17
|
"body text-foreground placeholder:text-muted-foreground",
|
|
12
18
|
"resize-none",
|
|
13
19
|
controlSurfaceClasses,
|
package/src/index.ts
CHANGED
package/src/lib/interaction.ts
CHANGED
|
@@ -14,8 +14,7 @@ export const focusRingClasses =
|
|
|
14
14
|
export const iconStrokeClasses =
|
|
15
15
|
"[&_svg]:transition-[stroke-width,transform,color] [&_svg]:duration-150 [&_svg]:ease-out group-hover:[&_svg]:stroke-[2]";
|
|
16
16
|
|
|
17
|
-
export const controlSurfaceClasses =
|
|
18
|
-
"border border-border bg-card shadow-natural hover:border-ground-200 hover:shadow-elevated";
|
|
17
|
+
export const controlSurfaceClasses = "border border-border bg-background hover:border-ground-200";
|
|
19
18
|
|
|
20
19
|
export const surfaceElevationClasses = {
|
|
21
20
|
flat: "border border-border bg-card",
|
|
@@ -59,7 +59,7 @@ const ShapeContext = createContext<ShapeContextValue | null>(null);
|
|
|
59
59
|
|
|
60
60
|
function useShape(): ShapeClasses {
|
|
61
61
|
const ctx = useContext(ShapeContext);
|
|
62
|
-
if (!ctx) return shapeMap.
|
|
62
|
+
if (!ctx) return shapeMap.rounded;
|
|
63
63
|
return ctx.classes;
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -79,7 +79,7 @@ function transitionShape(callback: () => void) {
|
|
|
79
79
|
|
|
80
80
|
function ShapeProvider({
|
|
81
81
|
children,
|
|
82
|
-
defaultShape = "
|
|
82
|
+
defaultShape = "rounded",
|
|
83
83
|
}: {
|
|
84
84
|
children: ReactNode;
|
|
85
85
|
defaultShape?: ShapeVariant;
|