@neuronection/assistant-ui 0.7.1 → 0.8.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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @neuronection/assistant-ui
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`8f92588`](https://github.com/neuronection/assistant-ui/commit/8f92588f6b294c8661fd56d57991c394a1ac9dc1) Thanks [@constLiakos](https://github.com/constLiakos)! - New `TimePicker` (+ standalone `TimePickerContent`): clock-face time picker
8
+ with a 12h AM/PM UI over a 24-hour `HH:MM` value, editable HH:MM fields
9
+ (typing 13–23 or 0 normalizes the period), keyboard-navigable dial, and
10
+ default/unstyled trigger variants. New `TimeList`: chip-based editor for
11
+ `HH:MM` lists with per-chip pickers, remove-on-hover and a max cap.
12
+ Ported from health-assistant (i18n → label props, Radix popover, tokens).
13
+
14
+ ### Patch Changes
15
+
16
+ - [`077def0`](https://github.com/neuronection/assistant-ui/commit/077def0835ab7d962b7dec04fd4e2cd26ff08bc3) Thanks [@constLiakos](https://github.com/constLiakos)! - `AiButton`: new `showLabel` (icon-only trigger), `closeOnSubmit`
17
+ (auto-close after submit) and controlled `open`/`onOpenChange` props
18
+ (close-on-success form-fill flows).
19
+
3
20
  ## 0.7.1
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -82,6 +82,7 @@ files in the package's `dist/`. Visual reference:
82
82
  | `wizard` | `Wizard` (steps config + `renderStep`, per-step validation gates, modal/drawer variants), `Stepper` (dots/labels) |
83
83
  | `input` / `search-input` / `expandable-search` | `Input` (label/hint/error wiring) · `SearchInput` · `ExpandableSearch` |
84
84
  | `chip-input` / `chip-list` | `ChipInput` (Enter/comma commit, paste-splits, Backspace-removes) · `ChipList` (variant pills, clickable, removable) |
85
+ | `time-picker` / `time-list` | `TimePicker` (clock face, 24h value, 12h UI) · `TimeList` (time chips) |
85
86
  | `date-picker` | `DatePicker` (popover calendar: days/months/years views, min/max, `allowClear`, arrow-key day grid) |
86
87
  | `range-bar` / `scale-slider` | `RangeBar` (low–high band + value dot) · `ScaleSlider` (+ `scaleColorForValue`) |
87
88
  | `table` | `Table` (headers/rows, built-in empty state) |
@@ -12,6 +12,14 @@ interface AiButtonProps {
12
12
  loadingLabel?: string;
13
13
  placeholder?: string;
14
14
  disabled?: boolean;
15
+ /** Controlled open state (defaults to internal). */
16
+ open?: boolean;
17
+ defaultOpen?: boolean;
18
+ onOpenChange?: (open: boolean) => void;
19
+ /** Show the label text next to the icon (default true; icon-only when false). */
20
+ showLabel?: boolean;
21
+ /** Close the panel after a submit (form-fill flows; Q&A keeps it open). */
22
+ closeOnSubmit?: boolean;
15
23
  side?: 'top' | 'bottom' | 'left' | 'right';
16
24
  align?: 'start' | 'center' | 'end';
17
25
  className?: string;
package/dist/ai-button.js CHANGED
@@ -1,3 +1,3 @@
1
- export { AiButton } from './chunk-L56KGPGS.js';
1
+ export { AiButton } from './chunk-3VCFRIIK.js';
2
2
  //# sourceMappingURL=ai-button.js.map
3
3
  //# sourceMappingURL=ai-button.js.map
@@ -17,18 +17,30 @@ var AiButton = React.forwardRef(
17
17
  loadingLabel = "Thinking\u2026",
18
18
  placeholder,
19
19
  disabled = false,
20
+ open: openProp,
21
+ defaultOpen = false,
22
+ onOpenChange,
23
+ showLabel = true,
24
+ closeOnSubmit = false,
20
25
  side = "bottom",
21
26
  align = "end",
22
27
  className,
23
28
  panelClassName
24
29
  }, ref) {
25
- const [open, setOpen] = React.useState(false);
30
+ const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen);
31
+ const isControlled = openProp !== void 0;
32
+ const open = isControlled ? openProp : uncontrolledOpen;
26
33
  const [prompt, setPrompt] = React.useState("");
34
+ const setOpen = (next) => {
35
+ if (!isControlled) setUncontrolledOpen(next);
36
+ onOpenChange?.(next);
37
+ };
27
38
  const send = (value) => {
28
39
  const trimmed = value.trim();
29
40
  if (!trimmed || loading) return;
30
41
  onSubmit(trimmed);
31
42
  setPrompt("");
43
+ if (closeOnSubmit) setOpen(false);
32
44
  };
33
45
  return /* @__PURE__ */ jsx(Popover, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs("span", { className: "relative inline-flex", children: [
34
46
  /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxs(
@@ -44,7 +56,7 @@ var AiButton = React.forwardRef(
44
56
  ),
45
57
  children: [
46
58
  /* @__PURE__ */ jsx(Sparkles, { className: "size-3.5", "aria-hidden": true }),
47
- label
59
+ showLabel ? label : null
48
60
  ]
49
61
  }
50
62
  ) }),
@@ -111,5 +123,5 @@ var AiButton = React.forwardRef(
111
123
  );
112
124
 
113
125
  export { AiButton };
114
- //# sourceMappingURL=chunk-L56KGPGS.js.map
115
- //# sourceMappingURL=chunk-L56KGPGS.js.map
126
+ //# sourceMappingURL=chunk-3VCFRIIK.js.map
127
+ //# sourceMappingURL=chunk-3VCFRIIK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/ai-button/AiButton.tsx"],"names":["AiButton"],"mappings":"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","file":"chunk-3VCFRIIK.js","sourcesContent":["import * as React from 'react'\nimport { Send, Sparkles } from 'lucide-react'\nimport { cn } from '../../lib/utils'\nimport { Popover, PopoverContent, PopoverTrigger } from '../popover'\n\nexport interface AiButtonProps {\n onSubmit: (prompt: string) => void\n suggestions?: string[]\n onResponse?: React.ReactNode\n loading?: boolean\n error?: string | null\n label?: string\n promptLabel?: string\n submitLabel?: string\n loadingLabel?: string\n placeholder?: string\n disabled?: boolean\n /** Controlled open state (defaults to internal). */\n open?: boolean\n defaultOpen?: boolean\n onOpenChange?: (open: boolean) => void\n /** Show the label text next to the icon (default true; icon-only when false). */\n showLabel?: boolean\n /** Close the panel after a submit (form-fill flows; Q&A keeps it open). */\n closeOnSubmit?: boolean\n side?: 'top' | 'bottom' | 'left' | 'right'\n align?: 'start' | 'center' | 'end'\n className?: string\n panelClassName?: string\n}\n\nexport const AiButton = React.forwardRef<HTMLButtonElement, AiButtonProps>(\n function AiButton(\n {\n onSubmit,\n suggestions = [],\n onResponse,\n loading = false,\n error,\n label = 'Ask AI',\n promptLabel = 'Ask a question',\n submitLabel = 'Send',\n loadingLabel = 'Thinking…',\n placeholder,\n disabled = false,\n open: openProp,\n defaultOpen = false,\n onOpenChange,\n showLabel = true,\n closeOnSubmit = false,\n side = 'bottom',\n align = 'end',\n className,\n panelClassName,\n },\n ref,\n ) {\n const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)\n const isControlled = openProp !== undefined\n const open = isControlled ? openProp : uncontrolledOpen\n const [prompt, setPrompt] = React.useState('')\n\n const setOpen = (next: boolean) => {\n if (!isControlled) setUncontrolledOpen(next)\n onOpenChange?.(next)\n }\n\n const send = (value: string) => {\n const trimmed = value.trim()\n if (!trimmed || loading) return\n onSubmit(trimmed)\n setPrompt('')\n if (closeOnSubmit) setOpen(false)\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <span className=\"relative inline-flex\">\n <PopoverTrigger asChild disabled={disabled}>\n <button\n ref={ref}\n type=\"button\"\n data-as=\"ai-button\"\n aria-label={label}\n className={cn(\n 'inline-flex cursor-pointer items-center gap-1 rounded-full bg-[color-mix(in_srgb,var(--as-ai)_12%,transparent)] px-2.5 py-1 text-xs font-medium text-[var(--as-ai)] transition-[filter] hover:brightness-95 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)] disabled:pointer-events-none disabled:opacity-50',\n className,\n )}\n >\n <Sparkles className=\"size-3.5\" aria-hidden />\n {showLabel ? label : null}\n </button>\n </PopoverTrigger>\n <PopoverContent\n side={side}\n align={align}\n aria-label={label}\n className={cn('w-72 p-3', panelClassName)}\n >\n <form\n data-as=\"ai-button-panel\"\n onSubmit={(event) => {\n event.preventDefault()\n send(prompt)\n }}\n >\n {suggestions.length > 0 ? (\n <div className=\"mb-2 flex flex-wrap gap-1\">\n {suggestions.map((suggestion) => (\n <button\n key={suggestion}\n type=\"button\"\n disabled={loading}\n onClick={() => send(suggestion)}\n className=\"cursor-pointer rounded-[var(--as-radius-sm)] bg-[var(--as-secondary)] px-2 py-1 text-xs transition-colors hover:bg-[color-mix(in_srgb,var(--as-ai)_14%,var(--as-secondary))] disabled:pointer-events-none disabled:opacity-50\"\n >\n {suggestion}\n </button>\n ))}\n </div>\n ) : null}\n <div className=\"mb-2 flex items-center gap-2\">\n <input\n type=\"text\"\n aria-label={promptLabel}\n placeholder={placeholder ?? promptLabel}\n value={prompt}\n onChange={(event) => setPrompt(event.target.value)}\n className=\"h-8 w-full rounded-[var(--as-radius-sm)] border border-[var(--as-border)] bg-[var(--as-surface)] px-2 text-sm outline-none placeholder:text-[var(--as-muted-fg)] focus-visible:border-[var(--as-focus-ring)]\"\n />\n <button\n type=\"submit\"\n aria-label={submitLabel}\n disabled={loading || !prompt.trim()}\n className=\"shrink-0 cursor-pointer rounded-[var(--as-radius-sm)] bg-[var(--as-ai)] p-1.5 text-[var(--as-ai-fg)] transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-40\"\n >\n <Send className=\"size-3.5\" aria-hidden />\n </button>\n </div>\n {loading ? (\n <p className=\"text-sm text-[var(--as-muted-fg)]\" role=\"status\">\n {loadingLabel}\n </p>\n ) : null}\n {error ? (\n <p role=\"alert\" className=\"text-sm text-[var(--as-danger)]\">\n {error}\n </p>\n ) : null}\n {onResponse}\n </form>\n </PopoverContent>\n </span>\n </Popover>\n )\n },\n)\n"]}
@@ -0,0 +1,463 @@
1
+ import { Popover, PopoverTrigger, PopoverContent } from './chunk-SIIY2EGI.js';
2
+ import { cn } from './chunk-BLJWR4ZV.js';
3
+ import * as React from 'react';
4
+ import { Clock, Check } from 'lucide-react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ function parse24(value) {
8
+ if (!value) return null;
9
+ const m = /^(\d{1,2}):(\d{1,2})$/.exec(value.trim());
10
+ if (!m) return null;
11
+ const h = parseInt(m[1], 10);
12
+ const min = parseInt(m[2], 10);
13
+ if (Number.isNaN(h) || Number.isNaN(min) || h < 0 || h > 23 || min < 0 || min > 59) return null;
14
+ return { hour24: h, minute: min };
15
+ }
16
+ function to24(hour12, period) {
17
+ if (period === "AM") return hour12 === 12 ? 0 : hour12;
18
+ return hour12 === 12 ? 12 : hour12 + 12;
19
+ }
20
+ function from24(hour24) {
21
+ if (hour24 === 0) return { hour12: 12, period: "AM" };
22
+ if (hour24 === 12) return { hour12: 12, period: "PM" };
23
+ if (hour24 < 12) return { hour12: hour24, period: "AM" };
24
+ return { hour12: hour24 - 12, period: "PM" };
25
+ }
26
+ function format12(hour24, minute) {
27
+ const { hour12, period } = from24(hour24);
28
+ const mm = minute.toString().padStart(2, "0");
29
+ return `${hour12}:${mm} ${period}`;
30
+ }
31
+ function format24(hour24, minute) {
32
+ return `${hour24.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
33
+ }
34
+ var ClockFace = ({
35
+ mode,
36
+ hour12,
37
+ minute,
38
+ size,
39
+ onPickHour,
40
+ onPickMinute
41
+ }) => {
42
+ const cx = size / 2;
43
+ const cy = size / 2;
44
+ const radius = size / 2 - 22;
45
+ const ticks = mode === "hours" ? Array.from({ length: 12 }, (_, i) => i + 1) : Array.from({ length: 12 }, (_, i) => i * 5);
46
+ const selectedValue = mode === "hours" ? hour12 : minute;
47
+ const angleFor = React.useCallback(
48
+ (val) => {
49
+ if (mode === "hours") {
50
+ return val / 12 * 360 - 90;
51
+ }
52
+ return val / 60 * 360 - 90;
53
+ },
54
+ [mode]
55
+ );
56
+ const pointFor = React.useCallback(
57
+ (val, r) => {
58
+ const rad = angleFor(val) * Math.PI / 180;
59
+ return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) };
60
+ },
61
+ [angleFor, cx, cy]
62
+ );
63
+ const handTarget = pointFor(selectedValue, radius - 22);
64
+ const labelPoint = (val) => pointFor(val, radius);
65
+ const handleClick = (e) => {
66
+ const svg = e.currentTarget;
67
+ const rect = svg.getBoundingClientRect();
68
+ const x = e.clientX - rect.left - cx;
69
+ const y = e.clientY - rect.top - cy;
70
+ let deg = Math.atan2(y, x) * 180 / Math.PI + 90;
71
+ if (deg < 0) deg += 360;
72
+ if (mode === "hours") {
73
+ const h = Math.round(deg / 30) || 12;
74
+ onPickHour(h);
75
+ } else {
76
+ const m = (Math.round(deg / 30) || 12) * 5;
77
+ onPickMinute(m === 60 ? 0 : m);
78
+ }
79
+ };
80
+ const handleKeyDown = (e) => {
81
+ if (mode === "hours") {
82
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
83
+ e.preventDefault();
84
+ onPickHour(hour12 >= 12 ? 1 : hour12 + 1);
85
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
86
+ e.preventDefault();
87
+ onPickHour(hour12 <= 1 ? 12 : hour12 - 1);
88
+ }
89
+ } else {
90
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
91
+ e.preventDefault();
92
+ onPickMinute(minute >= 55 ? 0 : minute + 5);
93
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
94
+ e.preventDefault();
95
+ onPickMinute(minute <= 0 ? 55 : minute - 5);
96
+ }
97
+ }
98
+ };
99
+ return /* @__PURE__ */ jsxs(
100
+ "svg",
101
+ {
102
+ width: size,
103
+ height: size,
104
+ viewBox: `0 0 ${size} ${size}`,
105
+ onClick: handleClick,
106
+ onKeyDown: handleKeyDown,
107
+ tabIndex: 0,
108
+ role: "slider",
109
+ "aria-label": mode === "hours" ? "Hour" : "Minute",
110
+ "aria-valuemin": mode === "hours" ? 1 : 0,
111
+ "aria-valuemax": mode === "hours" ? 12 : 59,
112
+ "aria-valuenow": selectedValue,
113
+ className: "cursor-pointer rounded-full outline-none focus-visible:ring-2 focus-visible:ring-[var(--as-focus-ring)]",
114
+ children: [
115
+ /* @__PURE__ */ jsx("circle", { cx, cy, r: radius + 4, className: "fill-[var(--as-muted)] stroke-[var(--as-border)]", strokeWidth: 1 }),
116
+ ticks.map((val) => {
117
+ const p = labelPoint(val);
118
+ const isSelected = val === selectedValue;
119
+ return /* @__PURE__ */ jsxs("g", { children: [
120
+ /* @__PURE__ */ jsx(
121
+ "circle",
122
+ {
123
+ cx: p.x,
124
+ cy: p.y,
125
+ r: isSelected ? 17 : 15,
126
+ className: cn(
127
+ "transition-all",
128
+ isSelected ? "fill-[var(--as-primary)]" : "fill-[var(--as-surface-raised)] stroke-[var(--as-border)]"
129
+ ),
130
+ strokeWidth: 1
131
+ }
132
+ ),
133
+ /* @__PURE__ */ jsx(
134
+ "text",
135
+ {
136
+ x: p.x,
137
+ y: p.y,
138
+ textAnchor: "middle",
139
+ dominantBaseline: "central",
140
+ className: cn(
141
+ "font-medium",
142
+ isSelected ? "fill-[var(--as-primary-fg)] font-bold" : "fill-[var(--as-fg)]"
143
+ ),
144
+ style: { fontSize: 13, userSelect: "none" },
145
+ children: val.toString().padStart(2, "0")
146
+ }
147
+ )
148
+ ] }, val);
149
+ }),
150
+ /* @__PURE__ */ jsx(
151
+ "line",
152
+ {
153
+ x1: cx,
154
+ y1: cy,
155
+ x2: handTarget.x,
156
+ y2: handTarget.y,
157
+ className: "stroke-[var(--as-primary)]",
158
+ strokeWidth: 2,
159
+ strokeLinecap: "round"
160
+ }
161
+ ),
162
+ /* @__PURE__ */ jsx("circle", { cx, cy, r: 5, className: "fill-[var(--as-primary)]" })
163
+ ]
164
+ }
165
+ );
166
+ };
167
+ var TimeInput = ({
168
+ value,
169
+ min,
170
+ max,
171
+ active,
172
+ onChange,
173
+ onFocus,
174
+ onConvertFrom24,
175
+ ariaLabel
176
+ }) => {
177
+ const pad = (n) => n.toString().padStart(2, "0");
178
+ const [text, setText] = React.useState(pad(value));
179
+ const [focused, setFocused] = React.useState(false);
180
+ React.useEffect(() => {
181
+ if (!focused) setText(pad(value));
182
+ }, [value, focused]);
183
+ const commit = (raw) => {
184
+ const digits = raw.replace(/\D/g, "");
185
+ if (digits === "") {
186
+ setText(pad(value));
187
+ return;
188
+ }
189
+ const n = parseInt(digits, 10);
190
+ if (Number.isNaN(n)) {
191
+ setText(pad(value));
192
+ return;
193
+ }
194
+ if (onConvertFrom24 && (n > max || n === 0)) {
195
+ onConvertFrom24(n);
196
+ return;
197
+ }
198
+ const clamped = Math.max(min, Math.min(max, n));
199
+ onChange(clamped);
200
+ setText(pad(clamped));
201
+ };
202
+ return /* @__PURE__ */ jsx(
203
+ "input",
204
+ {
205
+ type: "text",
206
+ inputMode: "numeric",
207
+ pattern: "[0-9]*",
208
+ maxLength: 2,
209
+ value: text,
210
+ "aria-label": ariaLabel,
211
+ onFocus: () => {
212
+ setFocused(true);
213
+ onFocus();
214
+ requestAnimationFrame(() => {
215
+ const el = document.activeElement;
216
+ if (el) el.select();
217
+ });
218
+ },
219
+ onBlur: () => {
220
+ setFocused(false);
221
+ commit(text);
222
+ },
223
+ onChange: (e) => setText(e.target.value.replace(/\D/g, "").slice(0, 2)),
224
+ onKeyDown: (e) => {
225
+ if (e.key === "Enter") {
226
+ e.preventDefault();
227
+ e.target.blur();
228
+ } else if (e.key === "ArrowUp") {
229
+ e.preventDefault();
230
+ const next = value >= max ? min : value + 1;
231
+ onChange(next);
232
+ } else if (e.key === "ArrowDown") {
233
+ e.preventDefault();
234
+ const next = value <= min ? max : value - 1;
235
+ onChange(next);
236
+ }
237
+ },
238
+ className: cn(
239
+ "h-10 w-12 rounded-[var(--as-radius-sm)] bg-transparent text-center text-2xl font-bold tabular-nums outline-none transition-colors",
240
+ active ? "bg-[color-mix(in_srgb,var(--as-primary)_12%,transparent)] text-[var(--as-primary)]" : "text-[var(--as-fg)] hover:bg-[var(--as-muted)]"
241
+ )
242
+ }
243
+ );
244
+ };
245
+ var TimePickerContent = ({
246
+ value,
247
+ onChange,
248
+ onDone,
249
+ size = 240,
250
+ hourLabel = "Hour",
251
+ minuteLabel = "Minute",
252
+ doneLabel = "Done",
253
+ className
254
+ }) => {
255
+ const seedRef = React.useRef(null);
256
+ if (seedRef.current === null) {
257
+ const p = parse24(value);
258
+ if (p) {
259
+ seedRef.current = { h24: p.hour24, min: p.minute };
260
+ } else {
261
+ const now = /* @__PURE__ */ new Date();
262
+ seedRef.current = { h24: now.getHours(), min: now.getMinutes() };
263
+ }
264
+ }
265
+ const seed = seedRef.current;
266
+ const [hour12, setHour12] = React.useState(() => from24(seed.h24).hour12);
267
+ const [minute, setMinute] = React.useState(seed.min);
268
+ const [period, setPeriod] = React.useState(() => from24(seed.h24).period);
269
+ const [mode, setMode] = React.useState("hours");
270
+ const commit = React.useCallback(
271
+ (h12, min, p) => {
272
+ onChange(format24(to24(h12, p), min));
273
+ },
274
+ [onChange]
275
+ );
276
+ const pickHour = (h) => {
277
+ setHour12(h);
278
+ commit(h, minute, period);
279
+ };
280
+ const pickMinute = (m) => {
281
+ setMinute(m);
282
+ commit(hour12, m, period);
283
+ };
284
+ const togglePeriod = (p) => {
285
+ if (p === period) return;
286
+ setPeriod(p);
287
+ commit(hour12, minute, p);
288
+ };
289
+ return /* @__PURE__ */ jsxs(
290
+ "div",
291
+ {
292
+ "data-as": "time-picker-content",
293
+ className: cn("flex flex-col gap-2 p-4", className),
294
+ children: [
295
+ /* @__PURE__ */ jsxs("div", { className: "mb-1 flex select-none items-center justify-center gap-2", children: [
296
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 rounded-[var(--as-radius)] bg-[var(--as-muted)] px-1 py-0.5", children: [
297
+ /* @__PURE__ */ jsx(
298
+ TimeInput,
299
+ {
300
+ value: hour12,
301
+ min: 1,
302
+ max: 12,
303
+ active: mode === "hours",
304
+ ariaLabel: hourLabel,
305
+ onChange: (h) => {
306
+ setHour12(h);
307
+ commit(h, minute, period);
308
+ },
309
+ onConvertFrom24: (n24) => {
310
+ const f = from24(n24);
311
+ setHour12(f.hour12);
312
+ setPeriod(f.period);
313
+ commit(f.hour12, minute, f.period);
314
+ },
315
+ onFocus: () => setMode("hours")
316
+ }
317
+ ),
318
+ /* @__PURE__ */ jsx("span", { className: "px-0.5 text-2xl font-bold text-[var(--as-muted-fg)]", children: ":" }),
319
+ /* @__PURE__ */ jsx(
320
+ TimeInput,
321
+ {
322
+ value: minute,
323
+ min: 0,
324
+ max: 59,
325
+ active: mode === "minutes",
326
+ ariaLabel: minuteLabel,
327
+ onChange: (m) => {
328
+ setMinute(m);
329
+ commit(hour12, m, period);
330
+ },
331
+ onFocus: () => setMode("minutes")
332
+ }
333
+ )
334
+ ] }),
335
+ /* @__PURE__ */ jsx("div", { className: "flex overflow-hidden rounded-[var(--as-radius)] border border-[var(--as-border)]", children: ["AM", "PM"].map((p) => /* @__PURE__ */ jsx(
336
+ "button",
337
+ {
338
+ type: "button",
339
+ onClick: () => togglePeriod(p),
340
+ "aria-pressed": period === p,
341
+ className: cn(
342
+ "cursor-pointer px-3 py-1.5 text-xs font-bold uppercase tracking-widest transition-colors",
343
+ period === p ? "bg-[var(--as-primary)] text-[var(--as-primary-fg)]" : "bg-[var(--as-surface)] text-[var(--as-muted-fg)] hover:bg-[var(--as-muted)]"
344
+ ),
345
+ children: p
346
+ },
347
+ p
348
+ )) })
349
+ ] }),
350
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-center text-[10px] font-bold uppercase tracking-widest text-[var(--as-muted-fg)]", children: mode === "hours" ? hourLabel : minuteLabel }),
351
+ /* @__PURE__ */ jsx("div", { className: "mb-2 flex justify-center", children: /* @__PURE__ */ jsx(
352
+ ClockFace,
353
+ {
354
+ mode,
355
+ hour12,
356
+ minute,
357
+ size,
358
+ onPickHour: pickHour,
359
+ onPickMinute: pickMinute
360
+ }
361
+ ) }),
362
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end border-t border-[var(--as-border)] pt-2", children: /* @__PURE__ */ jsxs(
363
+ "button",
364
+ {
365
+ type: "button",
366
+ onClick: onDone,
367
+ className: "flex cursor-pointer items-center gap-1.5 rounded-[var(--as-radius)] bg-[var(--as-primary)] px-4 py-2 text-xs font-bold text-[var(--as-primary-fg)] transition-[filter] hover:brightness-110",
368
+ children: [
369
+ /* @__PURE__ */ jsx(Check, { className: "size-3.5", "aria-hidden": true }),
370
+ doneLabel
371
+ ]
372
+ }
373
+ ) })
374
+ ]
375
+ }
376
+ );
377
+ };
378
+ var TimePicker = React.forwardRef(
379
+ function TimePicker2({
380
+ value,
381
+ onChange,
382
+ className,
383
+ placeholder = "Select time",
384
+ disabled = false,
385
+ id,
386
+ size = 240,
387
+ label,
388
+ hourLabel,
389
+ minuteLabel,
390
+ doneLabel,
391
+ variant = "default"
392
+ }, ref) {
393
+ const [open, setOpen] = React.useState(false);
394
+ const parsed = parse24(value);
395
+ const display = parsed ? format12(parsed.hour24, parsed.minute) : null;
396
+ return /* @__PURE__ */ jsxs(
397
+ "div",
398
+ {
399
+ ref,
400
+ "data-as": "time-picker",
401
+ className: cn(!className?.includes("w-") && "w-full", "flex flex-col", className),
402
+ children: [
403
+ label ? /* @__PURE__ */ jsx("label", { className: "mb-1.5 ml-1 block text-[10px] font-bold uppercase tracking-widest text-[var(--as-muted-fg)]", children: label }) : null,
404
+ /* @__PURE__ */ jsxs(Popover, { open: open && !disabled, onOpenChange: setOpen, children: [
405
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, disabled, children: /* @__PURE__ */ jsxs(
406
+ "button",
407
+ {
408
+ type: "button",
409
+ "aria-label": label ?? "Choose time",
410
+ className: cn(
411
+ "flex items-center gap-2 text-left text-sm outline-none transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50",
412
+ !className?.includes("w-") && "w-full",
413
+ variant === "default" && "rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-2.5 hover:border-[var(--as-primary)]"
414
+ ),
415
+ children: [
416
+ variant === "default" ? /* @__PURE__ */ jsx(Clock, { className: "size-4 shrink-0 text-[var(--as-muted-fg)]", "aria-hidden": true }) : null,
417
+ /* @__PURE__ */ jsx(
418
+ "span",
419
+ {
420
+ className: cn(
421
+ "flex-1 truncate",
422
+ !parsed && (variant === "default" ? "text-[var(--as-muted-fg)]" : "opacity-70"),
423
+ parsed && variant === "default" && "font-medium text-[var(--as-fg)]"
424
+ ),
425
+ children: display ?? placeholder
426
+ }
427
+ )
428
+ ]
429
+ }
430
+ ) }),
431
+ /* @__PURE__ */ jsx(
432
+ PopoverContent,
433
+ {
434
+ align: "start",
435
+ sideOffset: 4,
436
+ collisionPadding: 8,
437
+ "aria-label": label ?? "Choose time",
438
+ className: "w-[300px] p-0",
439
+ children: /* @__PURE__ */ jsx(
440
+ TimePickerContent,
441
+ {
442
+ value,
443
+ onChange,
444
+ onDone: () => setOpen(false),
445
+ size,
446
+ hourLabel,
447
+ minuteLabel,
448
+ doneLabel
449
+ }
450
+ )
451
+ }
452
+ )
453
+ ] }),
454
+ /* @__PURE__ */ jsx("input", { type: "hidden", value: value ?? "", id, name: id })
455
+ ]
456
+ }
457
+ );
458
+ }
459
+ );
460
+
461
+ export { TimePicker, TimePickerContent };
462
+ //# sourceMappingURL=chunk-ERK53RFJ.js.map
463
+ //# sourceMappingURL=chunk-ERK53RFJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/time-picker/TimePicker.tsx"],"names":["TimePicker","ClockIcon"],"mappings":"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","file":"chunk-ERK53RFJ.js","sourcesContent":["import * as React from 'react'\nimport { Check, Clock as ClockIcon } from 'lucide-react'\nimport { cn } from '../../lib/utils'\nimport { Popover, PopoverContent, PopoverTrigger } from '../popover'\n\ntype ViewMode = 'hours' | 'minutes'\n\nexport interface TimePickerProps {\n /** 24-hour `HH:MM` string, or null/undefined when empty. */\n value: string | null | undefined\n /** Fired with a 24-hour `HH:MM` string. */\n onChange: (value: string) => void\n className?: string\n placeholder?: string\n disabled?: boolean\n id?: string\n /** Diameter of the clock face in px (default 240). */\n size?: number\n /** Accessible name for the trigger (also an optional visible label). */\n label?: string\n hourLabel?: string\n minuteLabel?: string\n doneLabel?: string\n variant?: 'default' | 'unstyled'\n}\n\ninterface Hms {\n hour24: number\n minute: number\n}\n\nfunction parse24(value: string | null | undefined): Hms | null {\n if (!value) return null\n const m = /^(\\d{1,2}):(\\d{1,2})$/.exec(value.trim())\n if (!m) return null\n const h = parseInt(m[1]!, 10)\n const min = parseInt(m[2]!, 10)\n if (Number.isNaN(h) || Number.isNaN(min) || h < 0 || h > 23 || min < 0 || min > 59) return null\n return { hour24: h, minute: min }\n}\n\nfunction to24(hour12: number, period: 'AM' | 'PM'): number {\n if (period === 'AM') return hour12 === 12 ? 0 : hour12\n return hour12 === 12 ? 12 : hour12 + 12\n}\n\nfunction from24(hour24: number): { hour12: number; period: 'AM' | 'PM' } {\n if (hour24 === 0) return { hour12: 12, period: 'AM' }\n if (hour24 === 12) return { hour12: 12, period: 'PM' }\n if (hour24 < 12) return { hour12: hour24, period: 'AM' }\n return { hour12: hour24 - 12, period: 'PM' }\n}\n\nfunction format12(hour24: number, minute: number): string {\n const { hour12, period } = from24(hour24)\n const mm = minute.toString().padStart(2, '0')\n return `${hour12}:${mm} ${period}`\n}\n\nfunction format24(hour24: number, minute: number): string {\n return `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`\n}\n\ninterface ClockProps {\n mode: ViewMode\n hour12: number\n minute: number\n size: number\n onPickHour: (h: number) => void\n onPickMinute: (m: number) => void\n}\n\n/**\n * SVG clock face. Hours (1–12) and minutes (0,5,…,55) sit on a ring; the\n * selected tick is highlighted and a hand points to it. Click anywhere on the\n * face to snap to the nearest tick of the active mode.\n */\nconst ClockFace: React.FC<ClockProps> = ({\n mode,\n hour12,\n minute,\n size,\n onPickHour,\n onPickMinute,\n}) => {\n const cx = size / 2\n const cy = size / 2\n const radius = size / 2 - 22\n\n const ticks =\n mode === 'hours'\n ? Array.from({ length: 12 }, (_, i) => i + 1)\n : Array.from({ length: 12 }, (_, i) => i * 5)\n\n const selectedValue = mode === 'hours' ? hour12 : minute\n\n const angleFor = React.useCallback(\n (val: number) => {\n if (mode === 'hours') {\n return (val / 12) * 360 - 90\n }\n return (val / 60) * 360 - 90\n },\n [mode],\n )\n\n const pointFor = React.useCallback(\n (val: number, r: number) => {\n const rad = (angleFor(val) * Math.PI) / 180\n return { x: cx + r * Math.cos(rad), y: cy + r * Math.sin(rad) }\n },\n [angleFor, cx, cy],\n )\n\n const handTarget = pointFor(selectedValue, radius - 22)\n const labelPoint = (val: number) => pointFor(val, radius)\n\n const handleClick = (e: React.MouseEvent<SVGSVGElement>) => {\n const svg = e.currentTarget\n const rect = svg.getBoundingClientRect()\n const x = e.clientX - rect.left - cx\n const y = e.clientY - rect.top - cy\n let deg = (Math.atan2(y, x) * 180) / Math.PI + 90\n if (deg < 0) deg += 360\n if (mode === 'hours') {\n const h = Math.round(deg / 30) || 12\n onPickHour(h)\n } else {\n const m = (Math.round(deg / 30) || 12) * 5\n onPickMinute(m === 60 ? 0 : m)\n }\n }\n\n const handleKeyDown = (e: React.KeyboardEvent<SVGSVGElement>) => {\n if (mode === 'hours') {\n if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {\n e.preventDefault()\n onPickHour(hour12 >= 12 ? 1 : hour12 + 1)\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {\n e.preventDefault()\n onPickHour(hour12 <= 1 ? 12 : hour12 - 1)\n }\n } else {\n if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {\n e.preventDefault()\n onPickMinute(minute >= 55 ? 0 : minute + 5)\n } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {\n e.preventDefault()\n onPickMinute(minute <= 0 ? 55 : minute - 5)\n }\n }\n }\n\n return (\n <svg\n width={size}\n height={size}\n viewBox={`0 0 ${size} ${size}`}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n tabIndex={0}\n role=\"slider\"\n aria-label={mode === 'hours' ? 'Hour' : 'Minute'}\n aria-valuemin={mode === 'hours' ? 1 : 0}\n aria-valuemax={mode === 'hours' ? 12 : 59}\n aria-valuenow={selectedValue}\n className=\"cursor-pointer rounded-full outline-none focus-visible:ring-2 focus-visible:ring-[var(--as-focus-ring)]\"\n >\n <circle cx={cx} cy={cy} r={radius + 4} className=\"fill-[var(--as-muted)] stroke-[var(--as-border)]\" strokeWidth={1} />\n\n {ticks.map((val) => {\n const p = labelPoint(val)\n const isSelected = val === selectedValue\n return (\n <g key={val}>\n <circle\n cx={p.x}\n cy={p.y}\n r={isSelected ? 17 : 15}\n className={cn(\n 'transition-all',\n isSelected\n ? 'fill-[var(--as-primary)]'\n : 'fill-[var(--as-surface-raised)] stroke-[var(--as-border)]',\n )}\n strokeWidth={1}\n />\n <text\n x={p.x}\n y={p.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className={cn(\n 'font-medium',\n isSelected ? 'fill-[var(--as-primary-fg)] font-bold' : 'fill-[var(--as-fg)]',\n )}\n style={{ fontSize: 13, userSelect: 'none' }}\n >\n {val.toString().padStart(2, '0')}\n </text>\n </g>\n )\n })}\n\n <line\n x1={cx}\n y1={cy}\n x2={handTarget.x}\n y2={handTarget.y}\n className=\"stroke-[var(--as-primary)]\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n />\n <circle cx={cx} cy={cy} r={5} className=\"fill-[var(--as-primary)]\" />\n </svg>\n )\n}\n\ninterface TimeInputProps {\n value: number\n min: number\n max: number\n active?: boolean\n onChange: (n: number) => void\n onFocus: () => void\n onConvertFrom24?: (n24: number) => void\n ariaLabel?: string\n}\n\n/**\n * A 2-digit, keyboard-editable numeric field. Accepts only digits, clamps on\n * blur/Enter, and supports Arrow Up/Down to step. While focused it holds the\n * raw text; it re-syncs from `value` when not focused.\n */\nconst TimeInput: React.FC<TimeInputProps> = ({\n value,\n min,\n max,\n active,\n onChange,\n onFocus,\n onConvertFrom24,\n ariaLabel,\n}) => {\n const pad = (n: number) => n.toString().padStart(2, '0')\n const [text, setText] = React.useState<string>(pad(value))\n const [focused, setFocused] = React.useState(false)\n\n React.useEffect(() => {\n if (!focused) setText(pad(value))\n }, [value, focused])\n\n const commit = (raw: string) => {\n const digits = raw.replace(/\\D/g, '')\n if (digits === '') {\n setText(pad(value))\n return\n }\n const n = parseInt(digits, 10)\n if (Number.isNaN(n)) {\n setText(pad(value))\n return\n }\n if (onConvertFrom24 && (n > max || n === 0)) {\n onConvertFrom24(n)\n return\n }\n const clamped = Math.max(min, Math.min(max, n))\n onChange(clamped)\n setText(pad(clamped))\n }\n\n return (\n <input\n type=\"text\"\n inputMode=\"numeric\"\n pattern=\"[0-9]*\"\n maxLength={2}\n value={text}\n aria-label={ariaLabel}\n onFocus={() => {\n setFocused(true)\n onFocus()\n requestAnimationFrame(() => {\n const el = document.activeElement as HTMLInputElement | null\n if (el) el.select()\n })\n }}\n onBlur={() => {\n setFocused(false)\n commit(text)\n }}\n onChange={(e) => setText(e.target.value.replace(/\\D/g, '').slice(0, 2))}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n e.preventDefault()\n ;(e.target as HTMLInputElement).blur()\n } else if (e.key === 'ArrowUp') {\n e.preventDefault()\n const next = value >= max ? min : value + 1\n onChange(next)\n } else if (e.key === 'ArrowDown') {\n e.preventDefault()\n const next = value <= min ? max : value - 1\n onChange(next)\n }\n }}\n className={cn(\n 'h-10 w-12 rounded-[var(--as-radius-sm)] bg-transparent text-center text-2xl font-bold tabular-nums outline-none transition-colors',\n active\n ? 'bg-[color-mix(in_srgb,var(--as-primary)_12%,transparent)] text-[var(--as-primary)]'\n : 'text-[var(--as-fg)] hover:bg-[var(--as-muted)]',\n )}\n />\n )\n}\n\nexport interface TimePickerContentProps {\n value: string | null | undefined\n onChange: (value: string) => void\n onDone?: () => void\n size?: number\n hourLabel?: string\n minuteLabel?: string\n doneLabel?: string\n className?: string\n}\n\n/**\n * The popover body: editable HH:MM inputs + AM/PM segmented control + clock\n * face + Done button. Owns a working copy of the time, seeded from `value`\n * once on mount (the parent unmounts it when the popover closes).\n */\nexport const TimePickerContent: React.FC<TimePickerContentProps> = ({\n value,\n onChange,\n onDone,\n size = 240,\n hourLabel = 'Hour',\n minuteLabel = 'Minute',\n doneLabel = 'Done',\n className,\n}) => {\n const seedRef = React.useRef<{ h24: number; min: number } | null>(null)\n if (seedRef.current === null) {\n const p = parse24(value)\n if (p) {\n seedRef.current = { h24: p.hour24, min: p.minute }\n } else {\n const now = new Date()\n seedRef.current = { h24: now.getHours(), min: now.getMinutes() }\n }\n }\n const seed = seedRef.current\n\n const [hour12, setHour12] = React.useState<number>(() => from24(seed.h24).hour12)\n const [minute, setMinute] = React.useState<number>(seed.min)\n const [period, setPeriod] = React.useState<'AM' | 'PM'>(() => from24(seed.h24).period)\n const [mode, setMode] = React.useState<ViewMode>('hours')\n\n const commit = React.useCallback(\n (h12: number, min: number, p: 'AM' | 'PM') => {\n onChange(format24(to24(h12, p), min))\n },\n [onChange],\n )\n\n const pickHour = (h: number) => {\n setHour12(h)\n commit(h, minute, period)\n }\n const pickMinute = (m: number) => {\n setMinute(m)\n commit(hour12, m, period)\n }\n const togglePeriod = (p: 'AM' | 'PM') => {\n if (p === period) return\n setPeriod(p)\n commit(hour12, minute, p)\n }\n\n return (\n <div\n data-as=\"time-picker-content\"\n className={cn('flex flex-col gap-2 p-4', className)}\n >\n <div className=\"mb-1 flex select-none items-center justify-center gap-2\">\n <div className=\"flex items-center gap-0.5 rounded-[var(--as-radius)] bg-[var(--as-muted)] px-1 py-0.5\">\n <TimeInput\n value={hour12}\n min={1}\n max={12}\n active={mode === 'hours'}\n ariaLabel={hourLabel}\n onChange={(h) => {\n setHour12(h)\n commit(h, minute, period)\n }}\n onConvertFrom24={(n24) => {\n const f = from24(n24)\n setHour12(f.hour12)\n setPeriod(f.period)\n commit(f.hour12, minute, f.period)\n }}\n onFocus={() => setMode('hours')}\n />\n <span className=\"px-0.5 text-2xl font-bold text-[var(--as-muted-fg)]\">:</span>\n <TimeInput\n value={minute}\n min={0}\n max={59}\n active={mode === 'minutes'}\n ariaLabel={minuteLabel}\n onChange={(m) => {\n setMinute(m)\n commit(hour12, m, period)\n }}\n onFocus={() => setMode('minutes')}\n />\n </div>\n\n <div className=\"flex overflow-hidden rounded-[var(--as-radius)] border border-[var(--as-border)]\">\n {(['AM', 'PM'] as const).map((p) => (\n <button\n key={p}\n type=\"button\"\n onClick={() => togglePeriod(p)}\n aria-pressed={period === p}\n className={cn(\n 'cursor-pointer px-3 py-1.5 text-xs font-bold uppercase tracking-widest transition-colors',\n period === p\n ? 'bg-[var(--as-primary)] text-[var(--as-primary-fg)]'\n : 'bg-[var(--as-surface)] text-[var(--as-muted-fg)] hover:bg-[var(--as-muted)]',\n )}\n >\n {p}\n </button>\n ))}\n </div>\n </div>\n\n <p className=\"mb-1 text-center text-[10px] font-bold uppercase tracking-widest text-[var(--as-muted-fg)]\">\n {mode === 'hours' ? hourLabel : minuteLabel}\n </p>\n\n <div className=\"mb-2 flex justify-center\">\n <ClockFace\n mode={mode}\n hour12={hour12}\n minute={minute}\n size={size}\n onPickHour={pickHour}\n onPickMinute={pickMinute}\n />\n </div>\n\n <div className=\"flex justify-end border-t border-[var(--as-border)] pt-2\">\n <button\n type=\"button\"\n onClick={onDone}\n className=\"flex cursor-pointer items-center gap-1.5 rounded-[var(--as-radius)] bg-[var(--as-primary)] px-4 py-2 text-xs font-bold text-[var(--as-primary-fg)] transition-[filter] hover:brightness-110\"\n >\n <Check className=\"size-3.5\" aria-hidden />\n {doneLabel}\n </button>\n </div>\n </div>\n )\n}\n\nexport const TimePicker = React.forwardRef<HTMLDivElement, TimePickerProps>(\n function TimePicker(\n {\n value,\n onChange,\n className,\n placeholder = 'Select time',\n disabled = false,\n id,\n size = 240,\n label,\n hourLabel,\n minuteLabel,\n doneLabel,\n variant = 'default',\n },\n ref,\n ) {\n const [open, setOpen] = React.useState(false)\n\n const parsed = parse24(value)\n const display = parsed ? format12(parsed.hour24, parsed.minute) : null\n\n return (\n <div\n ref={ref}\n data-as=\"time-picker\"\n className={cn(!className?.includes('w-') && 'w-full', 'flex flex-col', className)}\n >\n {label ? (\n <label className=\"mb-1.5 ml-1 block text-[10px] font-bold uppercase tracking-widest text-[var(--as-muted-fg)]\">\n {label}\n </label>\n ) : null}\n <Popover open={open && !disabled} onOpenChange={setOpen}>\n <PopoverTrigger asChild disabled={disabled}>\n <button\n type=\"button\"\n aria-label={label ?? 'Choose time'}\n className={cn(\n 'flex items-center gap-2 text-left text-sm outline-none transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)] disabled:cursor-not-allowed disabled:opacity-50',\n !className?.includes('w-') && 'w-full',\n variant === 'default' &&\n 'rounded-[var(--as-radius)] border border-[var(--as-border)] bg-[var(--as-surface)] px-3 py-2.5 hover:border-[var(--as-primary)]',\n )}\n >\n {variant === 'default' ? (\n <ClockIcon className=\"size-4 shrink-0 text-[var(--as-muted-fg)]\" aria-hidden />\n ) : null}\n <span\n className={cn(\n 'flex-1 truncate',\n !parsed && (variant === 'default' ? 'text-[var(--as-muted-fg)]' : 'opacity-70'),\n parsed && variant === 'default' && 'font-medium text-[var(--as-fg)]',\n )}\n >\n {display ?? placeholder}\n </span>\n </button>\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n sideOffset={4}\n collisionPadding={8}\n aria-label={label ?? 'Choose time'}\n className=\"w-[300px] p-0\"\n >\n <TimePickerContent\n value={value}\n onChange={onChange}\n onDone={() => setOpen(false)}\n size={size}\n hourLabel={hourLabel}\n minuteLabel={minuteLabel}\n doneLabel={doneLabel}\n />\n </PopoverContent>\n </Popover>\n\n <input type=\"hidden\" value={value ?? ''} id={id} name={id} />\n </div>\n )\n },\n)\n"]}