@hilum/ui 3.4.0 → 3.5.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 +4 -0
- package/dist/{chunk-MAHPAP65.js → chunk-RI6QJR2L.js} +6 -3
- package/dist/chunk-RI6QJR2L.js.map +1 -0
- package/dist/index.d.ts +789 -115
- package/dist/index.js +10404 -1099
- package/dist/index.js.map +1 -1
- package/dist/tokens.css +17 -0
- package/dist/tokens.d.ts +3 -0
- package/dist/tokens.js +1 -1
- package/llms.txt +18 -4
- package/package.json +19 -1
- package/src/components/accordion.tsx +746 -58
- package/src/components/alert-dialog.tsx +11 -5
- package/src/components/ask-user-questions.tsx +1366 -0
- package/src/components/badge.tsx +105 -24
- package/src/components/button.tsx +207 -38
- package/src/components/card.tsx +3 -2
- package/src/components/chat-message.tsx +116 -0
- package/src/components/checkbox-group.tsx +403 -0
- package/src/components/checkbox.tsx +6 -3
- package/src/components/color-input.tsx +3 -1
- package/src/components/color-picker.tsx +1992 -80
- package/src/components/dialog.tsx +141 -81
- package/src/components/dropdown-menu.tsx +21 -13
- package/src/components/dropdown.tsx +245 -0
- package/src/components/file-thumbnail.tsx +156 -0
- package/src/components/hover-card.tsx +2 -0
- package/src/components/input-copy.tsx +307 -0
- package/src/components/input-group.tsx +303 -108
- package/src/components/input-message.tsx +883 -0
- package/src/components/input.tsx +6 -3
- package/src/components/menu-item.tsx +156 -0
- package/src/components/mobile-drawer.tsx +50 -0
- package/src/components/nav-item.tsx +43 -0
- package/src/components/nav-menu.tsx +47 -0
- package/src/components/popover.tsx +3 -1
- package/src/components/radio-group.tsx +381 -35
- package/src/components/scroll-area.tsx +9 -2
- package/src/components/select.tsx +708 -121
- package/src/components/sheet.tsx +15 -6
- package/src/components/slider.tsx +1583 -20
- package/src/components/status-badge.tsx +2 -0
- package/src/components/switch.tsx +245 -28
- package/src/components/table.tsx +191 -77
- package/src/components/tabs-subtle.tsx +411 -0
- package/src/components/tabs.tsx +459 -49
- package/src/components/textarea.tsx +7 -3
- package/src/components/thinking-indicator.tsx +106 -0
- package/src/components/thinking-steps.tsx +382 -0
- package/src/components/tooltip.tsx +158 -33
- package/src/hooks/use-merge-split.tsx +359 -0
- package/src/hooks/use-proximity-hover.ts +208 -0
- package/src/index.ts +14 -0
- package/src/lib/elevated.tsx +52 -0
- package/src/lib/font-weight.ts +18 -0
- package/src/lib/icon-context.tsx +98 -0
- package/src/lib/icon-map.tsx +669 -0
- package/src/lib/interaction.ts +102 -0
- package/src/lib/scroll-fade.tsx +241 -0
- package/src/lib/shape-context.tsx +121 -0
- package/src/lib/springs.ts +30 -0
- package/src/lib/surface-classes.ts +43 -0
- package/src/lib/surface-context.tsx +17 -0
- package/src/tokens/tokens.ts +3 -0
- package/dist/chunk-MAHPAP65.js.map +0 -1
|
@@ -1,151 +1,346 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useRef,
|
|
5
|
+
useState,
|
|
6
|
+
useCallback,
|
|
7
|
+
useEffect,
|
|
8
|
+
createContext,
|
|
9
|
+
useContext,
|
|
10
|
+
forwardRef,
|
|
11
|
+
type ReactNode,
|
|
12
|
+
type HTMLAttributes,
|
|
13
|
+
type InputHTMLAttributes,
|
|
14
|
+
type ChangeEventHandler,
|
|
15
|
+
} from "react";
|
|
16
|
+
import type { IconComponent } from "../lib/icon-context";
|
|
3
17
|
import { cn } from "../lib/utils";
|
|
18
|
+
import { fontWeights } from "../lib/font-weight";
|
|
19
|
+
import { useShape } from "../lib/shape-context";
|
|
4
20
|
|
|
5
|
-
interface
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
21
|
+
interface InputGroupContextValue {
|
|
22
|
+
registerItem: (index: number, element: HTMLLabelElement | null) => void;
|
|
23
|
+
activeIndex: number | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const InputGroupContext = createContext<InputGroupContextValue | null>(null);
|
|
27
|
+
|
|
28
|
+
function useInputGroup() {
|
|
29
|
+
const ctx = useContext(InputGroupContext);
|
|
30
|
+
if (!ctx) throw new Error("useInputGroup must be used within an InputGroup");
|
|
31
|
+
return ctx;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface InputGroupProps extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
35
|
+
children?: ReactNode;
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
value?: string;
|
|
38
|
+
onChange?: ChangeEventHandler<HTMLInputElement>;
|
|
39
|
+
leadingAddon?: ReactNode;
|
|
40
|
+
trailingAddon?: ReactNode;
|
|
41
|
+
trailingAction?: ReactNode;
|
|
42
|
+
leadingIcon?: ReactNode;
|
|
43
|
+
trailingIcon?: ReactNode;
|
|
44
|
+
trailingButton?: ReactNode;
|
|
45
|
+
error?: boolean | string;
|
|
46
|
+
disabled?: boolean;
|
|
15
47
|
wrapperClassName?: string;
|
|
48
|
+
pill?: boolean;
|
|
49
|
+
type?: InputHTMLAttributes<HTMLInputElement>["type"];
|
|
50
|
+
defaultValue?: InputHTMLAttributes<HTMLInputElement>["defaultValue"];
|
|
51
|
+
id?: string;
|
|
16
52
|
}
|
|
17
53
|
|
|
18
|
-
const InputGroup =
|
|
54
|
+
const InputGroup = forwardRef<HTMLDivElement, InputGroupProps>(
|
|
19
55
|
(
|
|
20
56
|
{
|
|
57
|
+
children,
|
|
58
|
+
className,
|
|
59
|
+
placeholder,
|
|
60
|
+
value,
|
|
61
|
+
onChange,
|
|
21
62
|
leadingAddon,
|
|
22
63
|
trailingAddon,
|
|
64
|
+
trailingAction,
|
|
23
65
|
leadingIcon,
|
|
24
66
|
trailingIcon,
|
|
25
|
-
trailingAction,
|
|
26
|
-
trailingActionClassName,
|
|
27
67
|
trailingButton,
|
|
28
68
|
error,
|
|
29
|
-
|
|
30
|
-
className,
|
|
69
|
+
disabled,
|
|
31
70
|
wrapperClassName,
|
|
71
|
+
pill,
|
|
72
|
+
type,
|
|
73
|
+
defaultValue,
|
|
74
|
+
id,
|
|
32
75
|
...props
|
|
33
76
|
},
|
|
34
77
|
ref,
|
|
35
78
|
) => {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
79
|
+
const itemsRef = useRef(new Map<number, HTMLLabelElement>());
|
|
80
|
+
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
|
81
|
+
|
|
82
|
+
const registerItem = useCallback((index: number, element: HTMLLabelElement | null) => {
|
|
83
|
+
if (element) {
|
|
84
|
+
itemsRef.current.set(index, element);
|
|
85
|
+
} else {
|
|
86
|
+
itemsRef.current.delete(index);
|
|
87
|
+
}
|
|
88
|
+
}, []);
|
|
89
|
+
|
|
90
|
+
const handleMouseMove = useCallback((e: React.MouseEvent) => {
|
|
91
|
+
const mouseY = e.clientY;
|
|
92
|
+
|
|
93
|
+
let closestIndex: number | null = null;
|
|
94
|
+
let closestDistance = Infinity;
|
|
95
|
+
|
|
96
|
+
itemsRef.current.forEach((element, index) => {
|
|
97
|
+
const rect = element.getBoundingClientRect();
|
|
98
|
+
const itemCenterY = rect.top + rect.height / 2;
|
|
99
|
+
const distance = Math.abs(mouseY - itemCenterY);
|
|
100
|
+
|
|
101
|
+
if (distance < closestDistance) {
|
|
102
|
+
closestDistance = distance;
|
|
103
|
+
closestIndex = index;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
setActiveIndex(closestIndex);
|
|
108
|
+
}, []);
|
|
109
|
+
|
|
110
|
+
const handleMouseLeave = useCallback(() => {
|
|
111
|
+
setActiveIndex(null);
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
const rendersInput =
|
|
115
|
+
placeholder !== undefined ||
|
|
116
|
+
value !== undefined ||
|
|
117
|
+
onChange !== undefined ||
|
|
118
|
+
leadingAddon !== undefined ||
|
|
119
|
+
trailingAddon !== undefined ||
|
|
120
|
+
trailingAction !== undefined ||
|
|
121
|
+
leadingIcon !== undefined ||
|
|
122
|
+
trailingIcon !== undefined ||
|
|
123
|
+
trailingButton !== undefined ||
|
|
124
|
+
error !== undefined;
|
|
125
|
+
|
|
126
|
+
if (rendersInput) {
|
|
127
|
+
return (
|
|
128
|
+
<div
|
|
129
|
+
ref={ref}
|
|
130
|
+
className={cn(
|
|
131
|
+
"relative flex items-center gap-2 rounded-[var(--radius-input)] border bg-background px-3 py-2 text-[13px] transition-colors",
|
|
132
|
+
error ? "border-destructive/50" : "border-border",
|
|
133
|
+
disabled && "opacity-50",
|
|
134
|
+
pill && "rounded-full",
|
|
135
|
+
wrapperClassName,
|
|
136
|
+
)}
|
|
137
|
+
{...props}
|
|
138
|
+
>
|
|
139
|
+
{leadingAddon && <span className="shrink-0 text-muted-foreground">{leadingAddon}</span>}
|
|
140
|
+
{leadingIcon && <span className="shrink-0 text-muted-foreground">{leadingIcon}</span>}
|
|
141
|
+
<input
|
|
142
|
+
id={id}
|
|
143
|
+
type={type}
|
|
144
|
+
value={value}
|
|
145
|
+
defaultValue={defaultValue}
|
|
146
|
+
onChange={onChange}
|
|
147
|
+
placeholder={placeholder}
|
|
148
|
+
disabled={disabled}
|
|
149
|
+
aria-invalid={!!error || undefined}
|
|
150
|
+
className={cn(
|
|
151
|
+
"min-w-0 flex-1 bg-transparent outline-none placeholder:text-muted-foreground",
|
|
152
|
+
(trailingAction || trailingIcon) && "pr-16",
|
|
153
|
+
className,
|
|
154
|
+
)}
|
|
155
|
+
/>
|
|
156
|
+
{trailingAddon && <span className="shrink-0 text-muted-foreground">{trailingAddon}</span>}
|
|
157
|
+
{trailingIcon && <span className="shrink-0 text-muted-foreground">{trailingIcon}</span>}
|
|
158
|
+
{trailingButton}
|
|
159
|
+
{trailingAction && (
|
|
160
|
+
<span className="absolute right-2 top-1/2 -translate-y-1/2">{trailingAction}</span>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<InputGroupContext.Provider value={{ registerItem, activeIndex }}>
|
|
168
|
+
<div
|
|
169
|
+
ref={ref}
|
|
170
|
+
onMouseMove={handleMouseMove}
|
|
171
|
+
onMouseLeave={handleMouseLeave}
|
|
172
|
+
className={cn("flex flex-col gap-3 w-72 max-w-full", className)}
|
|
173
|
+
{...props}
|
|
174
|
+
>
|
|
175
|
+
{children}
|
|
176
|
+
</div>
|
|
177
|
+
</InputGroupContext.Provider>
|
|
47
178
|
);
|
|
179
|
+
},
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
InputGroup.displayName = "InputGroup";
|
|
183
|
+
|
|
184
|
+
interface InputFieldProps extends Omit<
|
|
185
|
+
InputHTMLAttributes<HTMLInputElement>,
|
|
186
|
+
"onChange" | "index"
|
|
187
|
+
> {
|
|
188
|
+
label: string;
|
|
189
|
+
placeholder?: string;
|
|
190
|
+
icon?: IconComponent;
|
|
191
|
+
index: number;
|
|
192
|
+
value: string;
|
|
193
|
+
onChange: (value: string) => void;
|
|
194
|
+
error?: string;
|
|
195
|
+
disabled?: boolean;
|
|
196
|
+
className?: string;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const InputField = forwardRef<HTMLLabelElement, InputFieldProps>(
|
|
200
|
+
(
|
|
201
|
+
{
|
|
202
|
+
label,
|
|
203
|
+
placeholder,
|
|
204
|
+
icon: Icon,
|
|
205
|
+
index,
|
|
206
|
+
value,
|
|
207
|
+
onChange,
|
|
208
|
+
error,
|
|
209
|
+
disabled,
|
|
210
|
+
className,
|
|
211
|
+
...props
|
|
212
|
+
},
|
|
213
|
+
ref,
|
|
214
|
+
) => {
|
|
215
|
+
const internalRef = useRef<HTMLLabelElement>(null);
|
|
216
|
+
const { registerItem, activeIndex } = useInputGroup();
|
|
217
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
218
|
+
const shape = useShape();
|
|
219
|
+
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
registerItem(index, internalRef.current);
|
|
222
|
+
return () => registerItem(index, null);
|
|
223
|
+
}, [index, registerItem]);
|
|
48
224
|
|
|
49
|
-
|
|
50
|
-
|
|
225
|
+
const isActive = activeIndex === index;
|
|
226
|
+
const labelActive = isActive || isFocused;
|
|
227
|
+
|
|
228
|
+
const errorId = error ? `input-error-${index}` : undefined;
|
|
229
|
+
|
|
230
|
+
const handleFocus = () => {
|
|
231
|
+
setIsFocused(true);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const handleBlur = () => {
|
|
235
|
+
setIsFocused(false);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Input container classes
|
|
239
|
+
let bgClass: string;
|
|
240
|
+
let ringClass: string;
|
|
241
|
+
|
|
242
|
+
if (disabled) {
|
|
243
|
+
bgClass = "bg-transparent";
|
|
244
|
+
ringClass = "ring-border";
|
|
245
|
+
} else if (error) {
|
|
246
|
+
bgClass = isFocused ? "bg-card" : isActive ? "bg-destructive-light/60" : "bg-transparent";
|
|
247
|
+
ringClass = isFocused || isActive ? "ring-destructive/50" : "ring-transparent";
|
|
248
|
+
} else if (isFocused) {
|
|
249
|
+
bgClass = "bg-card";
|
|
250
|
+
ringClass = "ring-border";
|
|
251
|
+
} else if (isActive) {
|
|
252
|
+
bgClass = "bg-muted/50";
|
|
253
|
+
ringClass = "ring-border";
|
|
254
|
+
} else {
|
|
255
|
+
bgClass = "bg-transparent";
|
|
256
|
+
ringClass = "ring-transparent";
|
|
51
257
|
}
|
|
52
258
|
|
|
53
259
|
return (
|
|
54
|
-
<
|
|
55
|
-
{
|
|
56
|
-
|
|
260
|
+
<label
|
|
261
|
+
ref={(node) => {
|
|
262
|
+
(internalRef as React.MutableRefObject<HTMLLabelElement | null>).current = node;
|
|
263
|
+
if (typeof ref === "function") ref(node);
|
|
264
|
+
else if (ref) (ref as React.MutableRefObject<HTMLLabelElement | null>).current = node;
|
|
265
|
+
}}
|
|
266
|
+
className={cn(
|
|
267
|
+
"flex flex-col gap-1 cursor-text",
|
|
268
|
+
disabled && "opacity-50 pointer-events-none",
|
|
269
|
+
className,
|
|
270
|
+
)}
|
|
271
|
+
>
|
|
272
|
+
{/* Label */}
|
|
273
|
+
<span className="inline-grid text-[13px] pl-3">
|
|
57
274
|
<span
|
|
58
|
-
className=
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
pill ? "rounded-l-full" : "rounded-l-lg",
|
|
62
|
-
)}
|
|
275
|
+
className="col-start-1 row-start-1 invisible"
|
|
276
|
+
style={{ fontVariationSettings: fontWeights.semibold }}
|
|
277
|
+
aria-hidden="true"
|
|
63
278
|
>
|
|
64
|
-
{
|
|
279
|
+
{label}
|
|
65
280
|
</span>
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
{/* Relative wrapper for icon-only inputs */}
|
|
69
|
-
<div className="relative flex-1">
|
|
70
|
-
{leadingIcon && (
|
|
71
|
-
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3 text-muted-foreground">
|
|
72
|
-
{leadingIcon}
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
75
|
-
|
|
76
|
-
<input
|
|
77
|
-
ref={ref}
|
|
281
|
+
<span
|
|
78
282
|
className={cn(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
leadingAddon
|
|
82
|
-
? trailingAddon || trailingButton
|
|
83
|
-
? "rounded-none px-3"
|
|
84
|
-
: pill
|
|
85
|
-
? "rounded-none rounded-r-full px-3"
|
|
86
|
-
: "rounded-none rounded-r-lg px-3"
|
|
87
|
-
: leadingIcon
|
|
88
|
-
? trailingAddon
|
|
89
|
-
? pill
|
|
90
|
-
? "rounded-l-full rounded-r-none pl-9 pr-3"
|
|
91
|
-
: "rounded-l-lg rounded-r-none pl-9 pr-3"
|
|
92
|
-
: hasInsetTrailing
|
|
93
|
-
? cn(radius, "pl-9 pr-9")
|
|
94
|
-
: cn(radius, "pl-9 pr-3")
|
|
95
|
-
: trailingAddon
|
|
96
|
-
? pill
|
|
97
|
-
? "rounded-l-full rounded-r-none px-3"
|
|
98
|
-
: "rounded-l-lg rounded-r-none px-3"
|
|
99
|
-
: hasInsetTrailing
|
|
100
|
-
? cn(radius, "pl-3 pr-9")
|
|
101
|
-
: cn(radius, "px-3"),
|
|
102
|
-
trailingAction && "pr-16",
|
|
103
|
-
className,
|
|
283
|
+
"col-start-1 row-start-1",
|
|
284
|
+
error ? "text-destructive" : "text-muted-foreground",
|
|
104
285
|
)}
|
|
105
|
-
{
|
|
106
|
-
|
|
286
|
+
style={{
|
|
287
|
+
fontVariationSettings: fontWeights.normal,
|
|
288
|
+
}}
|
|
289
|
+
>
|
|
290
|
+
{label}
|
|
291
|
+
</span>
|
|
292
|
+
</span>
|
|
107
293
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
)}
|
|
115
|
-
</div>
|
|
294
|
+
{/* Input container */}
|
|
295
|
+
<div
|
|
296
|
+
className={cn(
|
|
297
|
+
`flex items-center gap-2 ${shape.input} px-3 py-2 ring-1 transition-all duration-80`,
|
|
298
|
+
bgClass,
|
|
299
|
+
ringClass,
|
|
116
300
|
)}
|
|
117
|
-
|
|
118
|
-
{
|
|
119
|
-
<
|
|
301
|
+
>
|
|
302
|
+
{Icon && (
|
|
303
|
+
<Icon
|
|
304
|
+
size={16}
|
|
305
|
+
strokeWidth={labelActive ? 2 : 1.5}
|
|
120
306
|
className={cn(
|
|
121
|
-
"
|
|
122
|
-
|
|
307
|
+
"shrink-0 transition-[color,stroke-width] duration-80",
|
|
308
|
+
labelActive ? "text-foreground" : "text-muted-foreground",
|
|
123
309
|
)}
|
|
124
|
-
|
|
125
|
-
{trailingAction}
|
|
126
|
-
</div>
|
|
310
|
+
/>
|
|
127
311
|
)}
|
|
312
|
+
<input
|
|
313
|
+
type="text"
|
|
314
|
+
value={value}
|
|
315
|
+
onChange={(e) => onChange(e.target.value)}
|
|
316
|
+
onFocus={handleFocus}
|
|
317
|
+
onBlur={handleBlur}
|
|
318
|
+
placeholder={placeholder}
|
|
319
|
+
disabled={disabled}
|
|
320
|
+
aria-invalid={!!error || undefined}
|
|
321
|
+
aria-describedby={errorId}
|
|
322
|
+
className="w-full bg-transparent text-[13px] text-foreground placeholder:text-muted-foreground outline-none font-[inherit]"
|
|
323
|
+
style={{ fontVariationSettings: fontWeights.normal }}
|
|
324
|
+
{...props}
|
|
325
|
+
/>
|
|
128
326
|
</div>
|
|
129
327
|
|
|
130
|
-
{/*
|
|
131
|
-
{
|
|
328
|
+
{/* Error message */}
|
|
329
|
+
{error && (
|
|
132
330
|
<span
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
pill ? "rounded-r-full" : "rounded-r-lg",
|
|
137
|
-
)}
|
|
331
|
+
id={errorId}
|
|
332
|
+
className="text-[12px] text-destructive pl-3"
|
|
333
|
+
style={{ fontVariationSettings: fontWeights.medium }}
|
|
138
334
|
>
|
|
139
|
-
{
|
|
335
|
+
{error}
|
|
140
336
|
</span>
|
|
141
337
|
)}
|
|
142
|
-
|
|
143
|
-
{/* Trailing button */}
|
|
144
|
-
{trailingButton && <div className="flex">{trailingButton}</div>}
|
|
145
|
-
</div>
|
|
338
|
+
</label>
|
|
146
339
|
);
|
|
147
340
|
},
|
|
148
341
|
);
|
|
149
|
-
InputGroup.displayName = "InputGroup";
|
|
150
342
|
|
|
151
|
-
|
|
343
|
+
InputField.displayName = "InputField";
|
|
344
|
+
|
|
345
|
+
export { InputGroup, InputField };
|
|
346
|
+
export default InputGroup;
|