@david-richard/notify-ds 1.0.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.mjs ADDED
@@ -0,0 +1,959 @@
1
+ import * as React4 from 'react';
2
+ import { Slot } from '@radix-ui/react-slot';
3
+ import { cva } from 'class-variance-authority';
4
+ import { clsx } from 'clsx';
5
+ import { twMerge } from 'tailwind-merge';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+
8
+ // components/button.tsx
9
+ function cn(...inputs) {
10
+ return twMerge(clsx(inputs));
11
+ }
12
+ var buttonVariants = cva(
13
+ [
14
+ // base
15
+ "inline-flex items-center justify-center whitespace-nowrap select-none",
16
+ "rounded-[var(--button-radius)] font-medium font-[var(--font-family-secondary)]",
17
+ // Red Hat Text Medium per spec
18
+ "transition-colors duration-[var(--p-duration-fast)] ease-[var(--p-easing-standard)]",
19
+ "outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-ring)] focus-visible:ring-offset-2",
20
+ "disabled:pointer-events-none disabled:opacity-[var(--p-opacity-disabled)]",
21
+ // icon child sizing
22
+ "[&_svg]:shrink-0"
23
+ ],
24
+ {
25
+ variants: {
26
+ variant: {
27
+ primary: [
28
+ "bg-[var(--color-primary)] text-[var(--color-primary-foreground)]",
29
+ "hover:bg-[var(--color-primary-hover)] active:bg-[var(--color-primary-pressed)]",
30
+ "data-[state=inactive]:bg-[var(--color-inactive)] data-[state=inactive]:text-[var(--color-inactive-foreground)]"
31
+ ],
32
+ secondary: [
33
+ "bg-transparent text-[var(--color-secondary-foreground)]",
34
+ "border border-[var(--color-secondary-border)]",
35
+ "hover:bg-[var(--color-secondary-hover)] active:bg-[var(--color-secondary-pressed)]",
36
+ "data-[state=inactive]:text-[var(--color-inactive-foreground)] data-[state=inactive]:border-[var(--color-inactive-border)]"
37
+ ],
38
+ tertiary: [
39
+ "bg-transparent text-[var(--color-tertiary-foreground)]",
40
+ "hover:bg-[var(--color-tertiary-hover)]",
41
+ "data-[state=inactive]:text-[var(--color-inactive-foreground)]"
42
+ ],
43
+ link: [
44
+ "bg-transparent text-[var(--color-link-foreground)] underline underline-offset-4",
45
+ "hover:decoration-2",
46
+ "data-[state=inactive]:text-[var(--color-inactive-foreground)]"
47
+ ]
48
+ },
49
+ size: {
50
+ xsm: "h-[var(--button-xsm-h)] px-[var(--button-xsm-px)] gap-[var(--button-xsm-gap)] text-[length:var(--button-xsm-fs)] leading-[var(--button-xsm-lh)] [&_svg]:size-3",
51
+ sm: "h-[var(--button-sm-h)] px-[var(--button-sm-px)] gap-[var(--button-sm-gap)] text-[length:var(--button-sm-fs)] leading-[var(--button-sm-lh)] [&_svg]:size-[14px]",
52
+ md: "h-[var(--button-md-h)] px-[var(--button-md-px)] gap-[var(--button-md-gap)] text-[length:var(--button-md-fs)] leading-[var(--button-md-lh)] [&_svg]:size-4",
53
+ lg: "h-[var(--button-lg-h)] px-[var(--button-lg-px)] gap-[var(--button-lg-gap)] text-[length:var(--button-lg-fs)] leading-[var(--button-lg-lh)] [&_svg]:size-5"
54
+ },
55
+ iconOnly: {
56
+ true: "px-0 aspect-square rounded-[var(--button-icon-radius)]",
57
+ false: ""
58
+ }
59
+ },
60
+ compoundVariants: [
61
+ // Link variant has no horizontal padding — it's text-like.
62
+ { variant: "link", iconOnly: false, class: "px-0 h-auto" },
63
+ // Tertiary should hug its text vertically.
64
+ { variant: "tertiary", iconOnly: false, class: "" }
65
+ ],
66
+ defaultVariants: {
67
+ variant: "primary",
68
+ size: "md",
69
+ iconOnly: false
70
+ }
71
+ }
72
+ );
73
+ var Button = React4.forwardRef(
74
+ ({ className, variant, size, iconOnly, state = "active", asChild = false, ...props }, ref) => {
75
+ const Comp = asChild ? Slot : "button";
76
+ return /* @__PURE__ */ jsx(
77
+ Comp,
78
+ {
79
+ ref,
80
+ "data-state": state,
81
+ className: cn(buttonVariants({ variant, size, iconOnly }), className),
82
+ ...props
83
+ }
84
+ );
85
+ }
86
+ );
87
+ Button.displayName = "Button";
88
+ var IconButton = React4.forwardRef(
89
+ (props, ref) => /* @__PURE__ */ jsx(Button, { ref, iconOnly: true, ...props })
90
+ );
91
+ IconButton.displayName = "IconButton";
92
+ var pillVariants = cva(
93
+ [
94
+ "flex items-center gap-2",
95
+ "h-12 w-full rounded-full px-4",
96
+ "border-[1.5px] bg-[var(--color-input-bg,#fff)]",
97
+ "transition-colors duration-[120ms]"
98
+ ],
99
+ {
100
+ variants: {
101
+ state: {
102
+ normal: "border-[var(--color-input-border-default,#C9C9C9)]",
103
+ active: "border-[var(--color-input-border-active,#40CCF2)] ring-0",
104
+ filled: "border-[var(--color-input-border-default,#C9C9C9)]",
105
+ error: "border-[var(--color-input-border-error,#EF2149)]",
106
+ disabled: "border-[var(--color-input-border-default,#C9C9C9)] bg-[var(--color-input-bg-disabled,#F4F4F4)] cursor-not-allowed opacity-50",
107
+ readonly: "border-[var(--color-input-border-default,#C9C9C9)] bg-[var(--color-input-bg-disabled,#F4F4F4)]"
108
+ }
109
+ },
110
+ defaultVariants: { state: "normal" }
111
+ }
112
+ );
113
+ var InputField = React4.forwardRef(
114
+ ({
115
+ type = "default",
116
+ state: stateProp,
117
+ label,
118
+ required,
119
+ helperText,
120
+ errorMessage,
121
+ rightSlot,
122
+ className,
123
+ onFocus,
124
+ onBlur,
125
+ onChange,
126
+ value,
127
+ defaultValue,
128
+ disabled,
129
+ readOnly,
130
+ placeholder,
131
+ ...props
132
+ }, ref) => {
133
+ const [focused, setFocused] = React4.useState(false);
134
+ const [hasValue, setHasValue] = React4.useState(!!defaultValue || !!value);
135
+ const [showPassword, setShowPassword] = React4.useState(false);
136
+ const state = stateProp ?? (disabled ? "disabled" : readOnly ? "readonly" : errorMessage ? "error" : focused ? "active" : hasValue ? "filled" : "normal");
137
+ const nativeType = type === "password" ? showPassword ? "text" : "password" : type === "search" ? "search" : "text";
138
+ const handleFocus = (e) => {
139
+ setFocused(true);
140
+ onFocus?.(e);
141
+ };
142
+ const handleBlur = (e) => {
143
+ setFocused(false);
144
+ onBlur?.(e);
145
+ };
146
+ const handleChange = (e) => {
147
+ setHasValue(e.target.value.length > 0);
148
+ onChange?.(e);
149
+ };
150
+ const iconColor = state === "active" ? "text-[var(--color-input-icon-active,#40CCF2)]" : state === "error" ? "text-[var(--color-input-icon-error,#EF2149)]" : "text-[var(--color-input-icon-default,#B1B1B1)]";
151
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex w-full flex-col gap-1.5", className), children: [
152
+ label && /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-0.5 font-['Red_Hat_Text'] text-[18px] font-medium leading-tight text-[var(--color-text-primary,#000)]", children: [
153
+ label,
154
+ required && /* @__PURE__ */ jsx("span", { className: "text-[var(--color-brand-red,#EF2149)]", "aria-hidden": "true", children: " *" })
155
+ ] }),
156
+ /* @__PURE__ */ jsxs("div", { className: pillVariants({ state }), children: [
157
+ type === "search" && /* @__PURE__ */ jsx("span", { className: cn("shrink-0", iconColor), "aria-hidden": "true", children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
158
+ /* @__PURE__ */ jsx(
159
+ "input",
160
+ {
161
+ ref,
162
+ type: nativeType,
163
+ disabled: disabled || state === "disabled",
164
+ readOnly: readOnly || state === "readonly",
165
+ placeholder,
166
+ value,
167
+ defaultValue,
168
+ onFocus: handleFocus,
169
+ onBlur: handleBlur,
170
+ onChange: handleChange,
171
+ className: cn(
172
+ "min-w-0 flex-1 bg-transparent outline-none",
173
+ "font-['Inter'] text-[16px] font-normal",
174
+ "text-[var(--color-input-text,#000)] placeholder:text-[var(--color-input-placeholder,#B1B1B1)]",
175
+ "disabled:cursor-not-allowed"
176
+ ),
177
+ "aria-invalid": state === "error" || void 0,
178
+ ...props
179
+ }
180
+ ),
181
+ rightSlot,
182
+ !rightSlot && type === "password" && /* @__PURE__ */ jsx(
183
+ "button",
184
+ {
185
+ type: "button",
186
+ tabIndex: -1,
187
+ onClick: () => setShowPassword((v) => !v),
188
+ className: cn("shrink-0 cursor-pointer", iconColor),
189
+ "aria-label": showPassword ? "Hide password" : "Show password",
190
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOffIcon, {}) : /* @__PURE__ */ jsx(EyeIcon, {})
191
+ }
192
+ ),
193
+ !rightSlot && type === "search" && hasValue && !disabled && /* @__PURE__ */ jsx(
194
+ "button",
195
+ {
196
+ type: "button",
197
+ tabIndex: -1,
198
+ onClick: () => setHasValue(false),
199
+ className: cn("shrink-0 cursor-pointer", iconColor),
200
+ "aria-label": "Clear search",
201
+ children: /* @__PURE__ */ jsx(XCircleIcon, {})
202
+ }
203
+ ),
204
+ !rightSlot && state === "error" && type !== "password" && /* @__PURE__ */ jsx("span", { className: cn("shrink-0", iconColor), "aria-hidden": "true", children: /* @__PURE__ */ jsx(InfoCircleIcon, {}) })
205
+ ] }),
206
+ (errorMessage || helperText) && /* @__PURE__ */ jsx(
207
+ "p",
208
+ {
209
+ className: cn(
210
+ "font-['Inter'] text-[12px] font-normal leading-tight px-1",
211
+ errorMessage ? "text-[var(--color-input-text-error,#EF2149)]" : "text-[var(--color-text-tertiary,#6B7280)]"
212
+ ),
213
+ children: errorMessage ?? helperText
214
+ }
215
+ )
216
+ ] });
217
+ }
218
+ );
219
+ InputField.displayName = "InputField";
220
+ function SearchIcon() {
221
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
222
+ /* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "4.5" }),
223
+ /* @__PURE__ */ jsx("line", { x1: "10.5", y1: "10.5", x2: "14", y2: "14" })
224
+ ] });
225
+ }
226
+ function EyeIcon() {
227
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
228
+ /* @__PURE__ */ jsx("path", { d: "M1 8s2.5-5 7-5 7 5 7 5-2.5 5-7 5-7-5-7-5Z" }),
229
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "2" })
230
+ ] });
231
+ }
232
+ function EyeOffIcon() {
233
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
234
+ /* @__PURE__ */ jsx("path", { d: "M2 2l12 12M6.5 6.5A2 2 0 0 0 9.5 9.5" }),
235
+ /* @__PURE__ */ jsx("path", { d: "M4.2 4.2C2.6 5.2 1 8 1 8s2.5 5 7 5c1.4 0 2.7-.4 3.8-1" }),
236
+ /* @__PURE__ */ jsx("path", { d: "M9.8 3.2C12.2 4.2 15 8 15 8s-.7 1.4-1.8 2.6" })
237
+ ] });
238
+ }
239
+ function XCircleIcon() {
240
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
241
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6.5" }),
242
+ /* @__PURE__ */ jsx("line", { x1: "5.5", y1: "5.5", x2: "10.5", y2: "10.5" }),
243
+ /* @__PURE__ */ jsx("line", { x1: "10.5", y1: "5.5", x2: "5.5", y2: "10.5" })
244
+ ] });
245
+ }
246
+ function InfoCircleIcon() {
247
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
248
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "8", r: "6.5" }),
249
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "7", x2: "8", y2: "11" }),
250
+ /* @__PURE__ */ jsx("circle", { cx: "8", cy: "5", r: "0.5", fill: "currentColor", stroke: "none" })
251
+ ] });
252
+ }
253
+ var Checkbox = React4.forwardRef(
254
+ ({ label, indeterminate, helperText, disabled, checked, defaultChecked, className, onChange, ...props }, ref) => {
255
+ const internalRef = React4.useRef(null);
256
+ const resolvedRef = ref ?? internalRef;
257
+ const [isChecked, setIsChecked] = React4.useState(!!defaultChecked || !!checked);
258
+ const isControlled = checked !== void 0;
259
+ const currentChecked = isControlled ? !!checked : isChecked;
260
+ React4.useEffect(() => {
261
+ if (resolvedRef.current) {
262
+ resolvedRef.current.indeterminate = !!indeterminate;
263
+ }
264
+ }, [indeterminate, resolvedRef]);
265
+ const handleChange = (e) => {
266
+ if (!isControlled) setIsChecked(e.target.checked);
267
+ onChange?.(e);
268
+ };
269
+ const showCheck = !indeterminate && currentChecked;
270
+ const showDash = !!indeterminate;
271
+ return /* @__PURE__ */ jsxs(
272
+ "label",
273
+ {
274
+ className: cn(
275
+ "inline-flex items-center gap-2 cursor-pointer select-none",
276
+ disabled && "opacity-50 cursor-not-allowed",
277
+ className
278
+ ),
279
+ children: [
280
+ /* @__PURE__ */ jsx(
281
+ "input",
282
+ {
283
+ ref: resolvedRef,
284
+ type: "checkbox",
285
+ checked: isControlled ? checked : isChecked,
286
+ disabled,
287
+ onChange: handleChange,
288
+ className: "sr-only",
289
+ ...props
290
+ }
291
+ ),
292
+ /* @__PURE__ */ jsxs(
293
+ "span",
294
+ {
295
+ "aria-hidden": "true",
296
+ className: cn(
297
+ "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-[4px]",
298
+ "border-[1.5px] transition-colors duration-[120ms]",
299
+ showCheck || showDash ? "border-transparent bg-[var(--color-primary,#40CCF2)]" : "border-[var(--color-secondary-border,#339FB8)] bg-transparent"
300
+ ),
301
+ children: [
302
+ showCheck && /* @__PURE__ */ jsx(CheckIcon, {}),
303
+ showDash && /* @__PURE__ */ jsx(DashIcon, {})
304
+ ]
305
+ }
306
+ ),
307
+ (label || helperText) && /* @__PURE__ */ jsxs("span", { className: "flex flex-col gap-0.5", children: [
308
+ label && /* @__PURE__ */ jsx("span", { className: "font-['Red_Hat_Text'] text-[14px] font-normal leading-tight text-[var(--color-text-primary,#000)]", children: label }),
309
+ helperText && /* @__PURE__ */ jsx("span", { className: "font-['Inter'] text-[12px] text-[var(--color-text-tertiary,#6B7280)]", children: helperText })
310
+ ] })
311
+ ]
312
+ }
313
+ );
314
+ }
315
+ );
316
+ Checkbox.displayName = "Checkbox";
317
+ function CheckIcon() {
318
+ return /* @__PURE__ */ jsx("svg", { width: "11", height: "8", viewBox: "0 0 11 8", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "1,4 4,7 10,1" }) });
319
+ }
320
+ function DashIcon() {
321
+ return /* @__PURE__ */ jsx("svg", { width: "10", height: "2", viewBox: "0 0 10 2", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx("line", { x1: "1", y1: "1", x2: "9", y2: "1" }) });
322
+ }
323
+ var Radio = React4.forwardRef(
324
+ ({ label, helperText, disabled, checked, defaultChecked, className, onChange, ...props }, ref) => {
325
+ const [isChecked, setIsChecked] = React4.useState(!!defaultChecked);
326
+ const isControlled = checked !== void 0;
327
+ const currentChecked = isControlled ? !!checked : isChecked;
328
+ const handleChange = (e) => {
329
+ if (!isControlled) setIsChecked(e.target.checked);
330
+ onChange?.(e);
331
+ };
332
+ return /* @__PURE__ */ jsxs(
333
+ "label",
334
+ {
335
+ className: cn(
336
+ "inline-flex items-center gap-2 cursor-pointer select-none",
337
+ disabled && "opacity-50 cursor-not-allowed",
338
+ className
339
+ ),
340
+ children: [
341
+ /* @__PURE__ */ jsx(
342
+ "input",
343
+ {
344
+ ref,
345
+ type: "radio",
346
+ checked: isControlled ? checked : isChecked,
347
+ disabled,
348
+ onChange: handleChange,
349
+ className: "sr-only",
350
+ ...props
351
+ }
352
+ ),
353
+ /* @__PURE__ */ jsx(
354
+ "span",
355
+ {
356
+ "aria-hidden": "true",
357
+ className: cn(
358
+ "flex h-[18px] w-[18px] shrink-0 items-center justify-center rounded-full",
359
+ "border-[1.5px] transition-colors duration-[120ms]",
360
+ currentChecked ? "border-transparent bg-[var(--color-primary,#40CCF2)]" : "border-[var(--color-secondary-border,#339FB8)] bg-transparent"
361
+ ),
362
+ children: currentChecked && /* @__PURE__ */ jsx("span", { className: "h-[6px] w-[6px] rounded-full bg-white" })
363
+ }
364
+ ),
365
+ (label || helperText) && /* @__PURE__ */ jsxs("span", { className: "flex flex-col gap-0.5", children: [
366
+ label && /* @__PURE__ */ jsx("span", { className: "font-['Red_Hat_Text'] text-[14px] font-normal leading-tight text-[var(--color-text-primary,#000)]", children: label }),
367
+ helperText && /* @__PURE__ */ jsx("span", { className: "font-['Inter'] text-[12px] text-[var(--color-text-tertiary,#6B7280)]", children: helperText })
368
+ ] })
369
+ ]
370
+ }
371
+ );
372
+ }
373
+ );
374
+ Radio.displayName = "Radio";
375
+ function RadioGroup({
376
+ label,
377
+ name,
378
+ value,
379
+ onChange,
380
+ children,
381
+ className,
382
+ orientation = "vertical"
383
+ }) {
384
+ const cloned = React4.Children.map(children, (child) => {
385
+ if (!React4.isValidElement(child)) return child;
386
+ const el = child;
387
+ return React4.cloneElement(el, {
388
+ name,
389
+ checked: value !== void 0 ? el.props.value === value : void 0,
390
+ onChange: (e) => {
391
+ if (e.target.checked) onChange?.(e.target.value);
392
+ el.props.onChange?.(e);
393
+ }
394
+ });
395
+ });
396
+ return /* @__PURE__ */ jsxs("fieldset", { className: cn("border-none p-0 m-0", className), children: [
397
+ label && /* @__PURE__ */ jsx("legend", { className: "mb-2 font-['Red_Hat_Text'] text-[18px] font-medium text-[var(--color-text-primary,#000)]", children: label }),
398
+ /* @__PURE__ */ jsx("div", { className: cn("flex gap-3", orientation === "vertical" ? "flex-col" : "flex-row flex-wrap"), children: cloned })
399
+ ] });
400
+ }
401
+ var Toggle = React4.forwardRef(
402
+ ({
403
+ checked,
404
+ defaultChecked = false,
405
+ disabled = false,
406
+ label,
407
+ labelPosition = "right",
408
+ helperText,
409
+ onChange,
410
+ className,
411
+ id: idProp
412
+ }, ref) => {
413
+ const generatedId = React4.useId();
414
+ const id = idProp ?? generatedId;
415
+ const [isOn, setIsOn] = React4.useState(defaultChecked);
416
+ const isControlled = checked !== void 0;
417
+ const currentOn = isControlled ? !!checked : isOn;
418
+ const handleClick = () => {
419
+ if (disabled) return;
420
+ const next = !currentOn;
421
+ if (!isControlled) setIsOn(next);
422
+ onChange?.(next);
423
+ };
424
+ const handleKeyDown = (e) => {
425
+ if (e.key === " " || e.key === "Enter") {
426
+ e.preventDefault();
427
+ handleClick();
428
+ }
429
+ };
430
+ const track = /* @__PURE__ */ jsx(
431
+ "button",
432
+ {
433
+ ref,
434
+ id,
435
+ role: "switch",
436
+ type: "button",
437
+ "aria-checked": currentOn,
438
+ disabled,
439
+ onClick: handleClick,
440
+ onKeyDown: handleKeyDown,
441
+ className: cn(
442
+ // track
443
+ "relative inline-flex h-[24px] w-[44px] shrink-0 cursor-pointer rounded-full",
444
+ "border-0 outline-none transition-colors duration-[180ms] ease-[cubic-bezier(0.2,0,0,1)]",
445
+ "focus-visible:ring-2 focus-visible:ring-[var(--color-ring,#40CCF2)] focus-visible:ring-offset-2",
446
+ "disabled:cursor-not-allowed",
447
+ currentOn ? "bg-[var(--color-primary,#40CCF2)]" : "bg-[var(--color-inactive,#DEDEDE)]"
448
+ ),
449
+ children: /* @__PURE__ */ jsx(
450
+ "span",
451
+ {
452
+ "aria-hidden": "true",
453
+ className: cn(
454
+ "absolute top-[2px] h-[20px] w-[20px] rounded-full bg-white shadow-sm",
455
+ "transition-transform duration-[180ms] ease-[cubic-bezier(0.2,0,0,1)]",
456
+ currentOn ? "translate-x-[22px]" : "translate-x-[2px]"
457
+ )
458
+ }
459
+ )
460
+ }
461
+ );
462
+ if (!label && !helperText) {
463
+ return /* @__PURE__ */ jsx("span", { className: cn(disabled && "opacity-50", className), children: track });
464
+ }
465
+ const labelContent = /* @__PURE__ */ jsxs(
466
+ "label",
467
+ {
468
+ htmlFor: id,
469
+ className: cn(
470
+ "flex flex-col gap-0.5 cursor-pointer",
471
+ disabled && "cursor-not-allowed"
472
+ ),
473
+ children: [
474
+ /* @__PURE__ */ jsx("span", { className: "font-['Red_Hat_Text'] text-[14px] font-normal leading-tight text-[var(--color-text-primary,#000)]", children: label }),
475
+ helperText && /* @__PURE__ */ jsx("span", { className: "font-['Inter'] text-[12px] text-[var(--color-text-tertiary,#6B7280)]", children: helperText })
476
+ ]
477
+ }
478
+ );
479
+ return /* @__PURE__ */ jsxs(
480
+ "div",
481
+ {
482
+ className: cn(
483
+ "inline-flex items-center gap-2.5",
484
+ disabled && "opacity-50",
485
+ className
486
+ ),
487
+ children: [
488
+ labelPosition === "left" && labelContent,
489
+ track,
490
+ labelPosition === "right" && labelContent
491
+ ]
492
+ }
493
+ );
494
+ }
495
+ );
496
+ Toggle.displayName = "Toggle";
497
+ var selectorVariants = cva(
498
+ [
499
+ "inline-flex items-center gap-1.5 rounded-full select-none cursor-pointer",
500
+ "py-2 pl-3.5 pr-2.5",
501
+ "font-['Red_Hat_Text'] text-[14px] font-medium leading-tight",
502
+ "border-[1.5px] transition-colors duration-[120ms]",
503
+ "outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-ring,#40CCF2)] focus-visible:ring-offset-1",
504
+ "disabled:pointer-events-none disabled:opacity-50"
505
+ ],
506
+ {
507
+ variants: {
508
+ variant: {
509
+ primary: "",
510
+ secondary: ""
511
+ },
512
+ state: {
513
+ active: "",
514
+ inactive: "",
515
+ disabled: ""
516
+ }
517
+ },
518
+ compoundVariants: [
519
+ // Primary
520
+ {
521
+ variant: "primary",
522
+ state: "active",
523
+ class: "bg-[var(--color-primary,#40CCF2)] border-transparent text-[var(--color-text-primary,#000)]"
524
+ },
525
+ {
526
+ variant: "primary",
527
+ state: ["inactive", "disabled"],
528
+ class: "bg-[var(--color-inactive,#DEDEDE)] border-transparent text-[var(--color-text-primary,#000)]"
529
+ },
530
+ // Secondary
531
+ {
532
+ variant: "secondary",
533
+ state: "active",
534
+ class: "bg-transparent border-[var(--color-secondary-border,#339FB8)] text-[var(--color-secondary-foreground,#339FB8)]"
535
+ },
536
+ {
537
+ variant: "secondary",
538
+ state: ["inactive", "disabled"],
539
+ class: "bg-transparent border-[var(--color-border,#C9C9C9)] text-[var(--color-text-secondary,#6B7280)]"
540
+ }
541
+ ],
542
+ defaultVariants: {
543
+ variant: "primary",
544
+ state: "active"
545
+ }
546
+ }
547
+ );
548
+ var Selector = React4.forwardRef(
549
+ ({ label, variant = "primary", state = "active", open = false, icon, className, disabled, ...props }, ref) => {
550
+ const isDisabled = disabled || state === "disabled";
551
+ return /* @__PURE__ */ jsxs(
552
+ "button",
553
+ {
554
+ ref,
555
+ type: "button",
556
+ disabled: isDisabled,
557
+ "aria-expanded": open,
558
+ "aria-haspopup": "listbox",
559
+ className: cn(selectorVariants({ variant, state: isDisabled ? "disabled" : state }), className),
560
+ ...props,
561
+ children: [
562
+ icon && /* @__PURE__ */ jsx("span", { className: "shrink-0 [&_svg]:size-4", children: icon }),
563
+ /* @__PURE__ */ jsx("span", { children: label }),
564
+ /* @__PURE__ */ jsx(
565
+ ChevronDownIcon,
566
+ {
567
+ className: cn(
568
+ "shrink-0 transition-transform duration-[120ms]",
569
+ open && "rotate-180"
570
+ )
571
+ }
572
+ )
573
+ ]
574
+ }
575
+ );
576
+ }
577
+ );
578
+ Selector.displayName = "Selector";
579
+ function SelectorGroup({ children, className }) {
580
+ return /* @__PURE__ */ jsx("div", { className: cn("flex flex-wrap items-center gap-2", className), children });
581
+ }
582
+ function ChevronDownIcon({ className }) {
583
+ return /* @__PURE__ */ jsx(
584
+ "svg",
585
+ {
586
+ width: "12",
587
+ height: "12",
588
+ viewBox: "0 0 12 12",
589
+ fill: "none",
590
+ stroke: "currentColor",
591
+ strokeWidth: "1.5",
592
+ strokeLinecap: "round",
593
+ strokeLinejoin: "round",
594
+ className,
595
+ children: /* @__PURE__ */ jsx("polyline", { points: "2,4 6,8 10,4" })
596
+ }
597
+ );
598
+ }
599
+ function TabBar({
600
+ tabs,
601
+ value,
602
+ defaultValue,
603
+ onValueChange,
604
+ className,
605
+ stretch = false
606
+ }) {
607
+ const [selected, setSelected] = React4.useState(defaultValue ?? tabs[0]);
608
+ const isControlled = value !== void 0;
609
+ const current = isControlled ? value : selected;
610
+ const handleSelect = (tab) => {
611
+ if (!isControlled) setSelected(tab);
612
+ onValueChange?.(tab);
613
+ };
614
+ return /* @__PURE__ */ jsx(
615
+ "div",
616
+ {
617
+ role: "tablist",
618
+ "aria-label": "Tabs",
619
+ className: cn(
620
+ "inline-flex items-center gap-1 rounded-full p-1",
621
+ "bg-[var(--color-inactive,#DEDEDE)]",
622
+ stretch && "w-full",
623
+ className
624
+ ),
625
+ children: tabs.map((tab) => {
626
+ const isSelected = tab === current;
627
+ return /* @__PURE__ */ jsx(
628
+ "button",
629
+ {
630
+ role: "tab",
631
+ type: "button",
632
+ "aria-selected": isSelected,
633
+ onClick: () => handleSelect(tab),
634
+ className: cn(
635
+ "rounded-full px-4 py-[7px] cursor-pointer select-none",
636
+ "font-['Inter'] text-[14px] font-medium leading-tight",
637
+ "outline-none transition-colors duration-[120ms]",
638
+ "focus-visible:ring-2 focus-visible:ring-[var(--color-ring,#40CCF2)] focus-visible:ring-offset-1",
639
+ stretch && "flex-1 text-center",
640
+ isSelected ? "bg-[var(--color-foreground,#000)] text-white" : "bg-transparent text-[var(--color-text-primary,#000)] hover:bg-black/5"
641
+ ),
642
+ children: tab
643
+ },
644
+ tab
645
+ );
646
+ })
647
+ }
648
+ );
649
+ }
650
+ function TabPanels({ value, children, className }) {
651
+ return /* @__PURE__ */ jsx("div", { className, children: React4.Children.map(children, (child) => {
652
+ if (!React4.isValidElement(child)) return null;
653
+ const el = child;
654
+ return el.props.value === value ? el : null;
655
+ }) });
656
+ }
657
+ function TabPanel({ children, className }) {
658
+ return /* @__PURE__ */ jsx("div", { role: "tabpanel", className, children });
659
+ }
660
+ function segmentValue(s) {
661
+ return typeof s === "string" ? s : s.value;
662
+ }
663
+ function segmentLabel(s) {
664
+ return typeof s === "string" ? s : s.label;
665
+ }
666
+ function segmentIcon(s) {
667
+ return typeof s === "string" ? void 0 : s.icon;
668
+ }
669
+ function Switcher({
670
+ segments,
671
+ value,
672
+ defaultValue,
673
+ onValueChange,
674
+ className,
675
+ stretch = false
676
+ }) {
677
+ const [selected, setSelected] = React4.useState(
678
+ defaultValue ?? segmentValue(segments[0])
679
+ );
680
+ const isControlled = value !== void 0;
681
+ const current = isControlled ? value : selected;
682
+ const handleSelect = (val) => {
683
+ if (!isControlled) setSelected(val);
684
+ onValueChange?.(val);
685
+ };
686
+ return /* @__PURE__ */ jsx(
687
+ "div",
688
+ {
689
+ role: "group",
690
+ className: cn(
691
+ "inline-flex items-center gap-0.5 rounded-[8px] p-[3px]",
692
+ "bg-[var(--color-inactive,#DEDEDE)]",
693
+ stretch && "w-full",
694
+ className
695
+ ),
696
+ children: segments.map((seg) => {
697
+ const val = segmentValue(seg);
698
+ const label = segmentLabel(seg);
699
+ const icon = segmentIcon(seg);
700
+ const isSelected = val === current;
701
+ return /* @__PURE__ */ jsxs(
702
+ "button",
703
+ {
704
+ type: "button",
705
+ role: "radio",
706
+ "aria-checked": isSelected,
707
+ onClick: () => handleSelect(val),
708
+ className: cn(
709
+ "inline-flex items-center justify-center gap-1.5 rounded-[6px]",
710
+ "px-4 py-[7px] cursor-pointer select-none",
711
+ "font-['Inter'] text-[14px] font-medium leading-tight",
712
+ "outline-none transition-colors duration-[120ms]",
713
+ "focus-visible:ring-2 focus-visible:ring-[var(--color-ring,#40CCF2)] focus-visible:ring-offset-1",
714
+ stretch && "flex-1",
715
+ isSelected ? "bg-[var(--color-foreground,#000)] text-white" : "bg-transparent text-[var(--color-text-secondary,#6B7280)] hover:text-[var(--color-text-primary,#000)]"
716
+ ),
717
+ children: [
718
+ icon && /* @__PURE__ */ jsx("span", { className: "shrink-0 [&_svg]:size-[14px]", children: icon }),
719
+ label
720
+ ]
721
+ },
722
+ val
723
+ );
724
+ })
725
+ }
726
+ );
727
+ }
728
+ function BottomNav({
729
+ items,
730
+ value,
731
+ defaultValue,
732
+ onValueChange,
733
+ className
734
+ }) {
735
+ const [selected, setSelected] = React4.useState(
736
+ defaultValue ?? items[0]?.value
737
+ );
738
+ const isControlled = value !== void 0;
739
+ const current = isControlled ? value : selected;
740
+ const handleSelect = (val) => {
741
+ if (!isControlled) setSelected(val);
742
+ onValueChange?.(val);
743
+ };
744
+ return /* @__PURE__ */ jsx(
745
+ "nav",
746
+ {
747
+ "aria-label": "Main navigation",
748
+ className: cn(
749
+ // Glass pill container
750
+ "flex items-center justify-between",
751
+ "w-full max-w-[360px] h-[72px] rounded-[60px]",
752
+ "px-3 py-2",
753
+ // Glass gradient
754
+ "bg-gradient-to-b from-[rgba(220,220,220,0.20)] to-[rgba(118,118,118,0.20)]",
755
+ "backdrop-blur-md",
756
+ // Shadow
757
+ "shadow-[0_4px_4px_rgba(0,0,0,0.14)]",
758
+ className
759
+ ),
760
+ children: items.map((item) => {
761
+ const isSelected = item.value === current;
762
+ return /* @__PURE__ */ jsx(
763
+ NavItemButton,
764
+ {
765
+ item,
766
+ isSelected,
767
+ onClick: () => handleSelect(item.value)
768
+ },
769
+ item.value
770
+ );
771
+ })
772
+ }
773
+ );
774
+ }
775
+ function NavItemButton({ item, isSelected, onClick }) {
776
+ return /* @__PURE__ */ jsxs(
777
+ "button",
778
+ {
779
+ type: "button",
780
+ onClick,
781
+ "aria-current": isSelected ? "page" : void 0,
782
+ className: cn(
783
+ "relative inline-flex flex-col items-center justify-center gap-[3px]",
784
+ "rounded-full py-2 px-4 cursor-pointer select-none",
785
+ "outline-none transition-colors duration-[120ms]",
786
+ "focus-visible:ring-2 focus-visible:ring-[var(--color-ring,#40CCF2)] focus-visible:ring-offset-1",
787
+ isSelected ? "bg-[var(--color-foreground,#000)] text-white" : "bg-transparent text-[var(--color-muted-foreground,#6B7280)]"
788
+ ),
789
+ children: [
790
+ item.badge && /* @__PURE__ */ jsx(
791
+ "span",
792
+ {
793
+ "aria-label": typeof item.badge === "number" ? `${item.badge} notifications` : "New notification",
794
+ className: cn(
795
+ "absolute right-2 top-2 flex items-center justify-center",
796
+ "rounded-full bg-[var(--color-destructive,#EF2149)] text-white",
797
+ typeof item.badge === "number" ? "h-4 min-w-4 px-1 font-['Inter'] text-[10px] font-semibold" : "h-2 w-2"
798
+ ),
799
+ children: typeof item.badge === "number" ? item.badge : null
800
+ }
801
+ ),
802
+ /* @__PURE__ */ jsx("span", { className: "[&_svg]:size-5", "aria-hidden": "true", children: item.icon }),
803
+ /* @__PURE__ */ jsx(
804
+ "span",
805
+ {
806
+ className: cn(
807
+ "font-['Inter'] text-[10px] leading-tight",
808
+ isSelected ? "font-semibold" : "font-normal"
809
+ ),
810
+ children: item.label
811
+ }
812
+ )
813
+ ]
814
+ }
815
+ );
816
+ }
817
+ function BottomNavContainer({ children }) {
818
+ return /* @__PURE__ */ jsx("div", { className: "fixed bottom-6 left-1/2 z-50 flex w-full max-w-[402px] -translate-x-1/2 items-center justify-center px-4", children });
819
+ }
820
+ var badgeVariants = cva(
821
+ [
822
+ "inline-flex items-center gap-1 rounded-full font-['Inter'] font-medium whitespace-nowrap",
823
+ "select-none leading-tight"
824
+ ],
825
+ {
826
+ variants: {
827
+ variant: {
828
+ default: "bg-[var(--color-inactive,#DEDEDE)] text-[var(--color-text-primary,#000)]",
829
+ success: "bg-[var(--color-success-soft,#B3F5D1)] text-[var(--color-success,#16A34A)]",
830
+ error: "bg-[var(--color-error-soft,#FFC9C9)] text-[var(--color-destructive,#EF2149)]",
831
+ warning: "bg-[var(--color-warning-soft,#FFF085)] text-[#92680E]",
832
+ alert: "bg-[var(--color-alert-soft,#EAC1C3)] text-[var(--color-alert,#FA6A0A)]",
833
+ info: "bg-[var(--color-info-soft,#BEDBFF)] text-[var(--color-info,#2F80ED)]",
834
+ neutral: "bg-transparent text-[var(--color-text-secondary,#6B7280)] border border-[var(--color-border,#C9C9C9)]",
835
+ // Brand accent fills — used for status tags in Kitchen Intelligence
836
+ brand: "bg-[var(--color-primary,#40CCF2)] text-[var(--color-text-primary,#000)]"
837
+ },
838
+ size: {
839
+ sm: "h-5 px-2 text-[11px]",
840
+ md: "h-6 px-2.5 text-[12px]"
841
+ }
842
+ },
843
+ defaultVariants: {
844
+ variant: "default",
845
+ size: "md"
846
+ }
847
+ }
848
+ );
849
+ function Badge({ variant, size, icon, children, className, ...props }) {
850
+ return /* @__PURE__ */ jsxs("span", { className: cn(badgeVariants({ variant, size }), className), ...props, children: [
851
+ icon && /* @__PURE__ */ jsx("span", { className: "shrink-0 [&_svg]:size-3", "aria-hidden": "true", children: icon }),
852
+ children
853
+ ] });
854
+ }
855
+ function TrendBadge({ value, decimals = 1, showArrow = true, ...props }) {
856
+ const variant = value > 0 ? "success" : value < 0 ? "error" : "neutral";
857
+ const formatted = `${value > 0 ? "+" : ""}${value.toFixed(decimals)}%`;
858
+ const arrow = showArrow ? value > 0 ? /* @__PURE__ */ jsx(ArrowUpIcon, {}) : value < 0 ? /* @__PURE__ */ jsx(ArrowDownIcon, {}) : null : null;
859
+ return /* @__PURE__ */ jsx(Badge, { variant, icon: arrow, ...props, children: formatted });
860
+ }
861
+ function ArrowUpIcon() {
862
+ return /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "2,7 5,3 8,7" }) });
863
+ }
864
+ function ArrowDownIcon() {
865
+ return /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "2,3 5,7 8,3" }) });
866
+ }
867
+ function MetricTile({
868
+ label,
869
+ value,
870
+ trend,
871
+ trendLabel,
872
+ icon,
873
+ valueColor,
874
+ size = "md",
875
+ loading = false,
876
+ onClick,
877
+ className,
878
+ as: Tag = "div"
879
+ }) {
880
+ const valueSize = {
881
+ sm: "text-[18px]",
882
+ md: "text-[24px]",
883
+ lg: "text-[30px]"
884
+ }[size];
885
+ const padding = {
886
+ sm: "p-3",
887
+ md: "p-4",
888
+ lg: "p-5"
889
+ }[size];
890
+ return /* @__PURE__ */ jsxs(
891
+ Tag,
892
+ {
893
+ onClick,
894
+ role: onClick ? "button" : void 0,
895
+ tabIndex: onClick ? 0 : void 0,
896
+ onKeyDown: onClick ? (e) => {
897
+ if (e.key === "Enter" || e.key === " ") onClick();
898
+ } : void 0,
899
+ className: cn(
900
+ // Card shell
901
+ "flex flex-col gap-1 rounded-[16px] bg-white",
902
+ "shadow-[0_4px_4px_rgba(0,0,0,0.06)]",
903
+ padding,
904
+ // Interactive
905
+ onClick && "cursor-pointer outline-none hover:shadow-[0_4px_12px_rgba(0,0,0,0.10)] transition-shadow duration-[120ms]",
906
+ onClick && "focus-visible:ring-2 focus-visible:ring-[var(--color-ring,#40CCF2)] focus-visible:ring-offset-2",
907
+ // Loading
908
+ loading && "animate-pulse",
909
+ className
910
+ ),
911
+ children: [
912
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
913
+ /* @__PURE__ */ jsx(
914
+ "span",
915
+ {
916
+ className: cn(
917
+ "font-['Inter'] text-[12px] font-normal leading-tight",
918
+ "text-[var(--color-text-tertiary,#6B7280)]",
919
+ loading && "h-3 w-24 rounded bg-gray-200"
920
+ ),
921
+ children: !loading && label
922
+ }
923
+ ),
924
+ icon && !loading && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[var(--color-text-tertiary,#6B7280)] [&_svg]:size-4", "aria-hidden": "true", children: icon })
925
+ ] }),
926
+ /* @__PURE__ */ jsx(
927
+ "span",
928
+ {
929
+ className: cn(
930
+ "font-['Inter'] font-semibold leading-tight tracking-tight",
931
+ valueSize,
932
+ loading && "mt-1 h-7 w-28 rounded bg-gray-200"
933
+ ),
934
+ style: valueColor && !loading ? { color: valueColor } : void 0,
935
+ children: !loading && value
936
+ }
937
+ ),
938
+ (trend !== void 0 || trendLabel) && !loading && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
939
+ trend !== void 0 && /* @__PURE__ */ jsx(TrendBadge, { value: trend, size: "sm" }),
940
+ trendLabel && /* @__PURE__ */ jsx("span", { className: "font-['Inter'] text-[11px] text-[var(--color-text-tertiary,#6B7280)]", children: trendLabel })
941
+ ] }),
942
+ loading && trend !== void 0 && /* @__PURE__ */ jsx("div", { className: "mt-1 h-4 w-16 rounded bg-gray-200" })
943
+ ]
944
+ }
945
+ );
946
+ }
947
+ function MetricTileGrid({ cols = 2, children, className }) {
948
+ const colClass = {
949
+ 1: "grid-cols-1",
950
+ 2: "grid-cols-2",
951
+ 3: "grid-cols-3",
952
+ 4: "grid-cols-4"
953
+ }[cols];
954
+ return /* @__PURE__ */ jsx("div", { className: cn("grid gap-3", colClass, className), children });
955
+ }
956
+
957
+ export { Badge, BottomNav, BottomNavContainer, Button, Checkbox, IconButton, InputField, MetricTile, MetricTileGrid, Radio, RadioGroup, Selector, SelectorGroup, Switcher, TabBar, TabPanel, TabPanels, Toggle, TrendBadge, buttonVariants };
958
+ //# sourceMappingURL=index.mjs.map
959
+ //# sourceMappingURL=index.mjs.map