@zovaris/sephiro 0.6.1 → 0.7.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.js CHANGED
@@ -1,11 +1,60 @@
1
- // src/atoms/Badge/index.tsx
1
+ // src/atoms/Avatar/index.tsx
2
2
  import { cn } from "cn";
3
+ import { useState } from "react";
3
4
 
4
5
  // src/lib/node.ts
5
6
  var node = (value) => value;
6
7
 
8
+ // src/atoms/Avatar/index.tsx
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ function initialsOf(name) {
11
+ if (!name) return "?";
12
+ const words = name.trim().split(/\s+/).slice(0, 2);
13
+ return words.map((word) => word[0]?.toUpperCase() ?? "").join("") || "?";
14
+ }
15
+ function Avatar({
16
+ src,
17
+ name,
18
+ size = "md",
19
+ status,
20
+ className,
21
+ ...props
22
+ }) {
23
+ const [failed, setFailed] = useState(false);
24
+ const showImage = Boolean(src) && !failed;
25
+ return /* @__PURE__ */ jsxs(
26
+ "span",
27
+ {
28
+ ...node(props),
29
+ className: cn("sph-avatar", className),
30
+ "data-size": size,
31
+ title: name,
32
+ children: [
33
+ showImage ? /* @__PURE__ */ jsx(
34
+ "img",
35
+ {
36
+ className: "sph-avatar__image",
37
+ src,
38
+ alt: name ?? "",
39
+ onError: () => setFailed(true)
40
+ }
41
+ ) : /* @__PURE__ */ jsx("span", { className: "sph-avatar__initials", "aria-hidden": "true", children: initialsOf(name) }),
42
+ status !== void 0 && /* @__PURE__ */ jsx(
43
+ "span",
44
+ {
45
+ className: "sph-avatar__status",
46
+ "data-status": status,
47
+ "aria-hidden": "true"
48
+ }
49
+ )
50
+ ]
51
+ }
52
+ );
53
+ }
54
+
7
55
  // src/atoms/Badge/index.tsx
8
- import { jsx } from "react/jsx-runtime";
56
+ import { cn as cn2 } from "cn";
57
+ import { jsx as jsx2 } from "react/jsx-runtime";
9
58
  function Badge({
10
59
  variant = "neutral",
11
60
  size = "sm",
@@ -13,11 +62,11 @@ function Badge({
13
62
  children,
14
63
  ...props
15
64
  }) {
16
- return /* @__PURE__ */ jsx(
65
+ return /* @__PURE__ */ jsx2(
17
66
  "span",
18
67
  {
19
68
  ...node(props),
20
- className: cn("sph-badge", className),
69
+ className: cn2("sph-badge", className),
21
70
  "data-variant": variant,
22
71
  "data-size": size,
23
72
  children: node(children)
@@ -26,22 +75,22 @@ function Badge({
26
75
  }
27
76
 
28
77
  // src/atoms/Button/index.tsx
29
- import { cn as cn3 } from "cn";
78
+ import { cn as cn4 } from "cn";
30
79
 
31
80
  // src/atoms/Spinner/index.tsx
32
- import { cn as cn2 } from "cn";
33
- import { jsx as jsx2 } from "react/jsx-runtime";
81
+ import { cn as cn3 } from "cn";
82
+ import { jsx as jsx3 } from "react/jsx-runtime";
34
83
  function Spinner({
35
84
  size = "md",
36
85
  label,
37
86
  className,
38
87
  ...props
39
88
  }) {
40
- return /* @__PURE__ */ jsx2(
89
+ return /* @__PURE__ */ jsx3(
41
90
  "span",
42
91
  {
43
92
  ...node(props),
44
- className: cn2("sph-spinner", `sph-spinner--${size}`, className),
93
+ className: cn3("sph-spinner", `sph-spinner--${size}`, className),
45
94
  role: label ? "status" : void 0,
46
95
  "aria-label": label,
47
96
  "aria-hidden": label ? void 0 : true
@@ -50,7 +99,7 @@ function Spinner({
50
99
  }
51
100
 
52
101
  // src/atoms/Button/index.tsx
53
- import { jsx as jsx3, jsxs } from "react/jsx-runtime";
102
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
54
103
  function Button({
55
104
  variant = "secondary",
56
105
  size = "md",
@@ -62,11 +111,11 @@ function Button({
62
111
  "aria-busy": ariaBusy,
63
112
  ...props
64
113
  }) {
65
- return /* @__PURE__ */ jsxs(
114
+ return /* @__PURE__ */ jsxs2(
66
115
  "button",
67
116
  {
68
117
  type,
69
- className: cn3("sph-button", className),
118
+ className: cn4("sph-button", className),
70
119
  "data-variant": variant,
71
120
  "data-size": size,
72
121
  "data-loading": loading || void 0,
@@ -74,8 +123,8 @@ function Button({
74
123
  ...node(props),
75
124
  "aria-busy": ariaBusy ?? (loading || void 0),
76
125
  children: [
77
- loading && /* @__PURE__ */ jsx3(Spinner, { size: "sm" }),
78
- /* @__PURE__ */ jsx3("span", { className: "sph-button__label", children: node(children) })
126
+ loading && /* @__PURE__ */ jsx4(Spinner, { size: "sm" }),
127
+ /* @__PURE__ */ jsx4("span", { className: "sph-button__label", children: node(children) })
79
128
  ]
80
129
  }
81
130
  );
@@ -83,8 +132,8 @@ function Button({
83
132
 
84
133
  // src/atoms/Checkbox/index.tsx
85
134
  import { CheckIcon } from "@phosphor-icons/react";
86
- import { cn as cn4 } from "cn";
87
- import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
135
+ import { cn as cn5 } from "cn";
136
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
88
137
  function Checkbox({
89
138
  label,
90
139
  size = "md",
@@ -92,7 +141,7 @@ function Checkbox({
92
141
  className,
93
142
  ...props
94
143
  }) {
95
- const control = /* @__PURE__ */ jsx4(
144
+ const control = /* @__PURE__ */ jsx5(
96
145
  "input",
97
146
  {
98
147
  ...node(props),
@@ -103,16 +152,16 @@ function Checkbox({
103
152
  }
104
153
  );
105
154
  if (label === void 0) return control;
106
- return /* @__PURE__ */ jsxs2("label", { className: cn4("sph-checkbox", className), "data-size": size, children: [
155
+ return /* @__PURE__ */ jsxs3("label", { className: cn5("sph-checkbox", className), "data-size": size, children: [
107
156
  control,
108
- /* @__PURE__ */ jsx4("span", { className: "sph-checkbox__box", "aria-hidden": "true", children: /* @__PURE__ */ jsx4(CheckIcon, { className: "sph-checkbox__check" }) }),
109
- /* @__PURE__ */ jsx4("span", { className: "sph-checkbox__label", children: node(label) })
157
+ /* @__PURE__ */ jsx5("span", { className: "sph-checkbox__box", "aria-hidden": "true", children: /* @__PURE__ */ jsx5(CheckIcon, { className: "sph-checkbox__check" }) }),
158
+ /* @__PURE__ */ jsx5("span", { className: "sph-checkbox__label", children: node(label) })
110
159
  ] });
111
160
  }
112
161
 
113
162
  // src/atoms/IconButton/index.tsx
114
- import { cn as cn5 } from "cn";
115
- import { jsx as jsx5 } from "react/jsx-runtime";
163
+ import { cn as cn6 } from "cn";
164
+ import { jsx as jsx6 } from "react/jsx-runtime";
116
165
  function IconButton({
117
166
  icon,
118
167
  label,
@@ -124,26 +173,26 @@ function IconButton({
124
173
  type = "button",
125
174
  ...props
126
175
  }) {
127
- return /* @__PURE__ */ jsx5(
176
+ return /* @__PURE__ */ jsx6(
128
177
  "button",
129
178
  {
130
179
  ...node(props),
131
180
  type,
132
- className: cn5("sph-icon-button", className),
181
+ className: cn6("sph-icon-button", className),
133
182
  "data-size": size,
134
183
  "data-variant": variant,
135
184
  "data-loading": loading || void 0,
136
185
  "aria-label": label,
137
186
  "aria-busy": loading ? true : props["aria-busy"],
138
187
  disabled: disabled || loading,
139
- children: loading ? /* @__PURE__ */ jsx5(Spinner, { size: size === "lg" ? "md" : "sm" }) : node(icon)
188
+ children: loading ? /* @__PURE__ */ jsx6(Spinner, { size: size === "lg" ? "md" : "sm" }) : node(icon)
140
189
  }
141
190
  );
142
191
  }
143
192
 
144
193
  // src/atoms/Input/index.tsx
145
- import { cn as cn6 } from "cn";
146
- import { jsx as jsx6 } from "react/jsx-runtime";
194
+ import { cn as cn7 } from "cn";
195
+ import { jsx as jsx7 } from "react/jsx-runtime";
147
196
  function Input({
148
197
  invalid = false,
149
198
  size = "md",
@@ -151,31 +200,95 @@ function Input({
151
200
  "aria-invalid": ariaInvalid,
152
201
  ...props
153
202
  }) {
154
- return /* @__PURE__ */ jsx6(
203
+ return /* @__PURE__ */ jsx7(
155
204
  "input",
156
205
  {
157
206
  ...node(props),
158
- className: cn6("sph-input", className),
207
+ className: cn7("sph-input", className),
159
208
  "data-size": size,
160
209
  "aria-invalid": ariaInvalid ?? (invalid || void 0)
161
210
  }
162
211
  );
163
212
  }
164
213
 
214
+ // src/atoms/Kbd/index.tsx
215
+ import { cn as cn8 } from "cn";
216
+ import { jsx as jsx8 } from "react/jsx-runtime";
217
+ function Kbd({ keys, className }) {
218
+ const caps = Array.isArray(keys) ? keys : [keys];
219
+ return /* @__PURE__ */ jsx8("kbd", { className: cn8("sph-kbd", className), children: caps.map((cap, index) => /* @__PURE__ */ jsx8("kbd", { className: "sph-kbd__key", children: node(cap) }, index)) });
220
+ }
221
+
222
+ // src/atoms/Progress/index.tsx
223
+ import { cn as cn9 } from "cn";
224
+ import { jsx as jsx9 } from "react/jsx-runtime";
225
+ function Progress({
226
+ value,
227
+ label,
228
+ size = "md",
229
+ className,
230
+ ...props
231
+ }) {
232
+ const determinate = typeof value === "number" && Number.isFinite(value);
233
+ const clamped = determinate ? Math.min(Math.max(value, 0), 100) : void 0;
234
+ return /* @__PURE__ */ jsx9(
235
+ "div",
236
+ {
237
+ ...node(props),
238
+ className: cn9("sph-progress", className),
239
+ "data-size": size,
240
+ role: "progressbar",
241
+ "aria-label": label,
242
+ "aria-valuemin": determinate ? 0 : void 0,
243
+ "aria-valuemax": determinate ? 100 : void 0,
244
+ "aria-valuenow": clamped,
245
+ "aria-valuetext": clamped !== void 0 ? `${Math.round(clamped)}%` : void 0,
246
+ children: /* @__PURE__ */ jsx9("span", { className: "sph-progress__track", children: /* @__PURE__ */ jsx9(
247
+ "span",
248
+ {
249
+ className: "sph-progress__bar",
250
+ "data-indeterminate": determinate ? void 0 : "true",
251
+ style: determinate ? { width: `${clamped}%` } : void 0
252
+ }
253
+ ) })
254
+ }
255
+ );
256
+ }
257
+
258
+ // src/atoms/ScrollArea/index.tsx
259
+ import { cn as cn10 } from "cn";
260
+ import { jsx as jsx10 } from "react/jsx-runtime";
261
+ function ScrollArea({
262
+ orientation = "vertical",
263
+ className,
264
+ children,
265
+ ...props
266
+ }) {
267
+ return /* @__PURE__ */ jsx10(
268
+ "div",
269
+ {
270
+ ...node(props),
271
+ className: cn10("sph-scroll-area", className),
272
+ "data-orientation": orientation,
273
+ children: node(children)
274
+ }
275
+ );
276
+ }
277
+
165
278
  // src/atoms/Separator/index.tsx
166
- import { cn as cn7 } from "cn";
167
- import { jsx as jsx7 } from "react/jsx-runtime";
279
+ import { cn as cn11 } from "cn";
280
+ import { jsx as jsx11 } from "react/jsx-runtime";
168
281
  function Separator({
169
282
  orientation = "horizontal",
170
283
  decorative = true,
171
284
  className,
172
285
  ...props
173
286
  }) {
174
- return /* @__PURE__ */ jsx7(
287
+ return /* @__PURE__ */ jsx11(
175
288
  "hr",
176
289
  {
177
290
  ...node(props),
178
- className: cn7("sph-separator", className),
291
+ className: cn11("sph-separator", className),
179
292
  "data-orientation": orientation,
180
293
  role: decorative ? "presentation" : "separator",
181
294
  "aria-orientation": decorative ? void 0 : orientation
@@ -184,8 +297,8 @@ function Separator({
184
297
  }
185
298
 
186
299
  // src/atoms/Skeleton/index.tsx
187
- import { cn as cn8 } from "cn";
188
- import { jsx as jsx8 } from "react/jsx-runtime";
300
+ import { cn as cn12 } from "cn";
301
+ import { jsx as jsx12 } from "react/jsx-runtime";
189
302
  function Skeleton({
190
303
  width,
191
304
  height,
@@ -194,11 +307,11 @@ function Skeleton({
194
307
  "aria-label": ariaLabel,
195
308
  ...props
196
309
  }) {
197
- return /* @__PURE__ */ jsx8(
310
+ return /* @__PURE__ */ jsx12(
198
311
  "span",
199
312
  {
200
313
  ...node(props),
201
- className: cn8("sph-skeleton", className),
314
+ className: cn12("sph-skeleton", className),
202
315
  style: { ...style, width, height },
203
316
  "aria-hidden": ariaLabel ? void 0 : true
204
317
  }
@@ -206,9 +319,9 @@ function Skeleton({
206
319
  }
207
320
 
208
321
  // src/atoms/Slider/index.tsx
209
- import { cn as cn9 } from "cn";
322
+ import { cn as cn13 } from "cn";
210
323
  import { useId } from "react";
211
- import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
324
+ import { jsx as jsx13, jsxs as jsxs4 } from "react/jsx-runtime";
212
325
  function Slider({
213
326
  value,
214
327
  onValueChange,
@@ -236,17 +349,17 @@ function Slider({
236
349
  ...style,
237
350
  "--sph-slider-progress": `${progress}%`
238
351
  };
239
- return /* @__PURE__ */ jsxs3(
352
+ return /* @__PURE__ */ jsxs4(
240
353
  "div",
241
354
  {
242
- className: cn9("sph-slider", className),
355
+ className: cn13("sph-slider", className),
243
356
  "data-size": size,
244
357
  "data-disabled": disabled || void 0,
245
358
  "data-invalid": invalid || void 0,
246
359
  children: [
247
- /* @__PURE__ */ jsxs3("div", { className: "sph-slider__head", children: [
248
- /* @__PURE__ */ jsx9("label", { className: "sph-slider__label", htmlFor: inputId, children: node(label) }),
249
- valueLabel !== void 0 && /* @__PURE__ */ jsx9(
360
+ /* @__PURE__ */ jsxs4("div", { className: "sph-slider__head", children: [
361
+ /* @__PURE__ */ jsx13("label", { className: "sph-slider__label", htmlFor: inputId, children: node(label) }),
362
+ valueLabel !== void 0 && /* @__PURE__ */ jsx13(
250
363
  "output",
251
364
  {
252
365
  className: "sph-slider__value",
@@ -256,7 +369,7 @@ function Slider({
256
369
  }
257
370
  )
258
371
  ] }),
259
- /* @__PURE__ */ jsx9(
372
+ /* @__PURE__ */ jsx13(
260
373
  "input",
261
374
  {
262
375
  ...node(props),
@@ -281,8 +394,8 @@ function Slider({
281
394
  }
282
395
 
283
396
  // src/atoms/Surface/index.tsx
284
- import { cn as cn10 } from "cn";
285
- import { jsx as jsx10 } from "react/jsx-runtime";
397
+ import { cn as cn14 } from "cn";
398
+ import { jsx as jsx14 } from "react/jsx-runtime";
286
399
  function Surface({
287
400
  tone = "base",
288
401
  padding = "md",
@@ -290,11 +403,11 @@ function Surface({
290
403
  children,
291
404
  ...props
292
405
  }) {
293
- return /* @__PURE__ */ jsx10(
406
+ return /* @__PURE__ */ jsx14(
294
407
  "div",
295
408
  {
296
409
  ...node(props),
297
- className: cn10("sph-surface", className),
410
+ className: cn14("sph-surface", className),
298
411
  "data-tone": tone,
299
412
  "data-padding": padding,
300
413
  children: node(children)
@@ -303,8 +416,8 @@ function Surface({
303
416
  }
304
417
 
305
418
  // src/atoms/Textarea/index.tsx
306
- import { cn as cn11 } from "cn";
307
- import { jsx as jsx11 } from "react/jsx-runtime";
419
+ import { cn as cn15 } from "cn";
420
+ import { jsx as jsx15 } from "react/jsx-runtime";
308
421
  function Textarea({
309
422
  invalid = false,
310
423
  size = "md",
@@ -312,11 +425,11 @@ function Textarea({
312
425
  "aria-invalid": ariaInvalid,
313
426
  ...props
314
427
  }) {
315
- return /* @__PURE__ */ jsx11(
428
+ return /* @__PURE__ */ jsx15(
316
429
  "textarea",
317
430
  {
318
431
  ...node(props),
319
- className: cn11("sph-textarea", className),
432
+ className: cn15("sph-textarea", className),
320
433
  "data-size": size,
321
434
  "aria-invalid": ariaInvalid ?? (invalid || void 0)
322
435
  }
@@ -324,8 +437,8 @@ function Textarea({
324
437
  }
325
438
 
326
439
  // src/atoms/Toggle/index.tsx
327
- import { cn as cn12 } from "cn";
328
- import { jsx as jsx12 } from "react/jsx-runtime";
440
+ import { cn as cn16 } from "cn";
441
+ import { jsx as jsx16, jsxs as jsxs5 } from "react/jsx-runtime";
329
442
  function Toggle({
330
443
  checked,
331
444
  onCheckedChange,
@@ -338,7 +451,7 @@ function Toggle({
338
451
  "aria-label": ariaLabel = label,
339
452
  ...props
340
453
  }) {
341
- return /* @__PURE__ */ jsx12(
454
+ return /* @__PURE__ */ jsxs5(
342
455
  "button",
343
456
  {
344
457
  type: "button",
@@ -346,7 +459,7 @@ function Toggle({
346
459
  "aria-checked": ariaChecked ?? checked,
347
460
  "aria-label": ariaLabel ?? label,
348
461
  disabled,
349
- className: cn12("sph-toggle", className),
462
+ className: cn16("sph-toggle", className),
350
463
  "data-state": checked ? "checked" : "unchecked",
351
464
  "data-size": size,
352
465
  onClick: (event) => {
@@ -354,20 +467,77 @@ function Toggle({
354
467
  if (!event.defaultPrevented) onCheckedChange(!checked);
355
468
  },
356
469
  ...node(props),
357
- children: /* @__PURE__ */ jsx12("span", { className: "sph-toggle__track", children: /* @__PURE__ */ jsx12("span", { className: "sph-toggle__thumb" }) })
470
+ children: [
471
+ " ",
472
+ /* @__PURE__ */ jsx16("span", { className: "sph-toggle__track", children: /* @__PURE__ */ jsx16("span", { className: "sph-toggle__thumb" }) })
473
+ ]
358
474
  }
359
475
  );
360
476
  }
361
477
 
478
+ // src/lib/presence.ts
479
+ import { useEffect, useRef, useState as useState2 } from "react";
480
+ var SPEED_TOKEN = {
481
+ fast: "--sph-motion-fast",
482
+ normal: "--sph-motion-normal",
483
+ slow: "--sph-motion-slow"
484
+ };
485
+ var EXIT_FRAME_BUDGET = 40;
486
+ function parseDurationMs(value) {
487
+ const match = value.trim().match(/^(\d+(?:\.\d+)?)(ms|s)?$/);
488
+ if (!match) return 0;
489
+ const amount = Number.parseFloat(match[1]);
490
+ if (!Number.isFinite(amount) || amount <= 0) return 0;
491
+ return match[2] === "s" ? amount * 1e3 : amount;
492
+ }
493
+ function motionDurationMs(element, speed) {
494
+ if (typeof window === "undefined" || typeof getComputedStyle !== "function")
495
+ return 0;
496
+ const source = element ?? document.documentElement;
497
+ const raw = getComputedStyle(source).getPropertyValue(SPEED_TOKEN[speed]);
498
+ return Math.min(parseDurationMs(raw || "0ms"), 1200);
499
+ }
500
+ function usePresence(open, speed = "normal") {
501
+ const ref = useRef(null);
502
+ const [present, setPresent] = useState2(open);
503
+ const [leaving, setLeaving] = useState2(false);
504
+ useEffect(() => {
505
+ if (open) {
506
+ setLeaving(false);
507
+ setPresent(true);
508
+ return;
509
+ }
510
+ if (!present) return;
511
+ const duration = motionDurationMs(ref.current, speed);
512
+ if (duration <= 0) {
513
+ setPresent(false);
514
+ return;
515
+ }
516
+ setLeaving(true);
517
+ const timer = setTimeout(() => {
518
+ setPresent(false);
519
+ setLeaving(false);
520
+ }, duration + EXIT_FRAME_BUDGET);
521
+ return () => clearTimeout(timer);
522
+ }, [open, present, speed]);
523
+ return {
524
+ present,
525
+ leaving,
526
+ state: leaving ? "closed" : "open",
527
+ ref
528
+ };
529
+ }
530
+
362
531
  // src/molecules/Alert/index.tsx
363
532
  import { CheckIcon as CheckIcon2, InfoIcon, WarningIcon, XIcon } from "@phosphor-icons/react";
364
- import { cn as cn13 } from "cn";
365
- import { jsx as jsx13, jsxs as jsxs4 } from "react/jsx-runtime";
533
+ import { cn as cn17 } from "cn";
534
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
535
+ import { jsx as jsx17, jsxs as jsxs6 } from "react/jsx-runtime";
366
536
  var alertIcons = {
367
- info: /* @__PURE__ */ jsx13(InfoIcon, { size: 16 }),
368
- success: /* @__PURE__ */ jsx13(CheckIcon2, { size: 16 }),
369
- warning: /* @__PURE__ */ jsx13(WarningIcon, { size: 16 }),
370
- danger: /* @__PURE__ */ jsx13(XIcon, { size: 16 })
537
+ info: /* @__PURE__ */ jsx17(InfoIcon, { size: 16 }),
538
+ success: /* @__PURE__ */ jsx17(CheckIcon2, { size: 16 }),
539
+ warning: /* @__PURE__ */ jsx17(WarningIcon, { size: 16 }),
540
+ danger: /* @__PURE__ */ jsx17(XIcon, { size: 16 })
371
541
  };
372
542
  function Alert({
373
543
  variant = "info",
@@ -379,29 +549,53 @@ function Alert({
379
549
  role,
380
550
  ...props
381
551
  }) {
552
+ const rootRef = useRef2(null);
553
+ const timerRef = useRef2(void 0);
554
+ const [dismissing, setDismissing] = useState3(false);
555
+ useEffect2(
556
+ () => () => {
557
+ if (timerRef.current) clearTimeout(timerRef.current);
558
+ },
559
+ []
560
+ );
561
+ const requestDismiss = () => {
562
+ if (onDismiss === void 0 || dismissing) return;
563
+ const duration = motionDurationMs(rootRef.current, "fast");
564
+ if (duration <= 0) {
565
+ onDismiss();
566
+ return;
567
+ }
568
+ setDismissing(true);
569
+ timerRef.current = setTimeout(() => {
570
+ setDismissing(false);
571
+ onDismiss();
572
+ }, duration + 40);
573
+ };
382
574
  if (title === void 0 && children === void 0) return null;
383
- return /* @__PURE__ */ jsxs4(
575
+ return /* @__PURE__ */ jsxs6(
384
576
  "div",
385
577
  {
578
+ ref: rootRef,
386
579
  ...node(props),
387
- className: cn13("sph-alert", className),
580
+ className: cn17("sph-alert", className),
388
581
  "data-variant": variant,
582
+ "data-state": dismissing ? "closed" : "open",
389
583
  role: role ?? (variant === "danger" ? "alert" : "status"),
390
584
  "aria-live": role ? void 0 : variant === "danger" ? "assertive" : "polite",
391
585
  children: [
392
- /* @__PURE__ */ jsx13("span", { className: "sph-alert__icon", "aria-hidden": "true", children: alertIcons[variant] }),
393
- /* @__PURE__ */ jsxs4("div", { className: "sph-alert__body", children: [
394
- title !== void 0 && /* @__PURE__ */ jsx13("p", { className: "sph-alert__title", children: node(title) }),
395
- children !== void 0 && /* @__PURE__ */ jsx13("div", { className: "sph-alert__content", children: node(children) })
586
+ /* @__PURE__ */ jsx17("span", { className: "sph-alert__icon", "aria-hidden": "true", children: alertIcons[variant] }),
587
+ /* @__PURE__ */ jsxs6("div", { className: "sph-alert__body", children: [
588
+ title !== void 0 && /* @__PURE__ */ jsx17("p", { className: "sph-alert__title", children: node(title) }),
589
+ children !== void 0 && /* @__PURE__ */ jsx17("div", { className: "sph-alert__content", children: node(children) })
396
590
  ] }),
397
- dismissible && /* @__PURE__ */ jsx13(
591
+ dismissible && /* @__PURE__ */ jsx17(
398
592
  "button",
399
593
  {
400
594
  type: "button",
401
595
  className: "sph-alert__dismiss",
402
596
  "aria-label": "Dismiss notification",
403
- onClick: onDismiss,
404
- children: /* @__PURE__ */ jsx13(XIcon, { size: 12, "aria-hidden": "true" })
597
+ onClick: requestDismiss,
598
+ children: /* @__PURE__ */ jsx17(XIcon, { size: 12, "aria-hidden": "true" })
405
599
  }
406
600
  )
407
601
  ]
@@ -410,9 +604,39 @@ function Alert({
410
604
  }
411
605
  var Notice = Alert;
412
606
 
607
+ // src/molecules/AvatarGroup/index.tsx
608
+ import { cn as cn18 } from "cn";
609
+ import { jsx as jsx18, jsxs as jsxs7 } from "react/jsx-runtime";
610
+ function AvatarGroup({
611
+ avatars,
612
+ max = 4,
613
+ size = "md",
614
+ className,
615
+ ...props
616
+ }) {
617
+ const shown = avatars.slice(0, Math.max(max, 1));
618
+ const overflow = avatars.length - shown.length;
619
+ return /* @__PURE__ */ jsxs7("span", { ...node(props), className: cn18("sph-avatar-group", className), children: [
620
+ shown.map((avatar, index) => /* @__PURE__ */ jsx18(
621
+ Avatar,
622
+ {
623
+ src: avatar.src,
624
+ name: avatar.name,
625
+ status: avatar.status,
626
+ size
627
+ },
628
+ `${avatar.src ?? avatar.name ?? "avatar"}-${index}`
629
+ )),
630
+ overflow > 0 && /* @__PURE__ */ jsxs7("span", { className: "sph-avatar sph-avatar--overflow", "data-size": size, children: [
631
+ "+",
632
+ overflow
633
+ ] })
634
+ ] });
635
+ }
636
+
413
637
  // src/molecules/Card/index.tsx
414
- import { cn as cn14 } from "cn";
415
- import { jsx as jsx14, jsxs as jsxs5 } from "react/jsx-runtime";
638
+ import { cn as cn19 } from "cn";
639
+ import { jsx as jsx19, jsxs as jsxs8 } from "react/jsx-runtime";
416
640
  function Card({
417
641
  title,
418
642
  description,
@@ -425,62 +649,707 @@ function Card({
425
649
  ...props
426
650
  }) {
427
651
  const hasHeader = header !== void 0 || title !== void 0 || description !== void 0;
428
- return /* @__PURE__ */ jsxs5(
652
+ return /* @__PURE__ */ jsxs8(
429
653
  "article",
430
654
  {
431
655
  ...node(props),
432
- className: cn14("sph-card", className),
656
+ className: cn19("sph-card", className),
433
657
  "data-interactive": interactive || void 0,
434
658
  "data-selected": selected || void 0,
435
659
  children: [
436
- hasHeader && /* @__PURE__ */ jsxs5("header", { className: "sph-card__header", children: [
660
+ hasHeader && /* @__PURE__ */ jsxs8("header", { className: "sph-card__header", children: [
437
661
  node(header),
438
- title !== void 0 && /* @__PURE__ */ jsx14("h3", { className: "sph-card__title", children: node(title) }),
439
- description !== void 0 && /* @__PURE__ */ jsx14("p", { className: "sph-card__description", children: node(description) })
662
+ title !== void 0 && /* @__PURE__ */ jsx19("h3", { className: "sph-card__title", children: node(title) }),
663
+ description !== void 0 && /* @__PURE__ */ jsx19("p", { className: "sph-card__description", children: node(description) })
440
664
  ] }),
441
- children !== void 0 && /* @__PURE__ */ jsx14("div", { className: "sph-card__content", children: node(children) }),
442
- footer !== void 0 && /* @__PURE__ */ jsx14("footer", { className: "sph-card__footer", children: node(footer) })
665
+ children !== void 0 && /* @__PURE__ */ jsx19("div", { className: "sph-card__content", children: node(children) }),
666
+ footer !== void 0 && /* @__PURE__ */ jsx19("footer", { className: "sph-card__footer", children: node(footer) })
443
667
  ]
444
668
  }
445
669
  );
446
670
  }
447
671
 
448
- // src/molecules/Dialog/index.tsx
449
- import { XIcon as XIcon2 } from "@phosphor-icons/react";
450
- import { cn as cn15 } from "cn";
451
- import { useEffect, useId as useId2, useRef } from "react";
452
- import { jsx as jsx15, jsxs as jsxs6 } from "react/jsx-runtime";
453
- function Dialog({
454
- open,
455
- onClose,
456
- title,
457
- description,
672
+ // src/molecules/Collapsible/index.tsx
673
+ import { CaretRightIcon } from "@phosphor-icons/react";
674
+ import { cn as cn20 } from "cn";
675
+ import { useId as useId2, useState as useState4 } from "react";
676
+ import { jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
677
+ function Collapsible({
678
+ trigger,
458
679
  children,
459
- footer,
460
- closeLabel = "Close dialog",
461
- closeOnOverlayClick = true,
680
+ open,
681
+ defaultOpen = false,
682
+ onOpenChange,
683
+ disabled = false,
684
+ className,
685
+ ...props
686
+ }) {
687
+ const [uncontrolled, setUncontrolled] = useState4(defaultOpen);
688
+ const isOpen = open ?? uncontrolled;
689
+ const contentId = `${useId2()}-content`;
690
+ const toggle = () => {
691
+ if (disabled) return;
692
+ const next = !isOpen;
693
+ setUncontrolled(next);
694
+ onOpenChange?.(next);
695
+ };
696
+ return /* @__PURE__ */ jsxs9(
697
+ "div",
698
+ {
699
+ ...node(props),
700
+ className: cn20("sph-collapsible", className),
701
+ "data-state": isOpen ? "open" : "closed",
702
+ children: [
703
+ /* @__PURE__ */ jsxs9(
704
+ "button",
705
+ {
706
+ type: "button",
707
+ className: "sph-collapsible__trigger",
708
+ "aria-expanded": isOpen,
709
+ "aria-controls": contentId,
710
+ disabled,
711
+ onClick: toggle,
712
+ children: [
713
+ /* @__PURE__ */ jsx20("span", { className: "sph-collapsible__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx20(CaretRightIcon, { size: 13, weight: "bold" }) }),
714
+ node(trigger)
715
+ ]
716
+ }
717
+ ),
718
+ /* @__PURE__ */ jsx20("div", { className: "sph-collapsible__body", id: contentId, children: /* @__PURE__ */ jsx20("div", { className: "sph-collapsible__inner", children: node(children) }) })
719
+ ]
720
+ }
721
+ );
722
+ }
723
+
724
+ // src/molecules/Combobox/index.tsx
725
+ import {
726
+ CaretDownIcon,
727
+ CheckIcon as CheckIcon3,
728
+ MagnifyingGlassIcon,
729
+ XIcon as XIcon2
730
+ } from "@phosphor-icons/react";
731
+ import { cn as cn21 } from "cn";
732
+ import { useEffect as useEffect4, useId as useId3, useMemo, useRef as useRef4, useState as useState6 } from "react";
733
+ import { createPortal } from "react-dom";
734
+
735
+ // src/lib/filter.ts
736
+ function matchText(text, query) {
737
+ const needle = query.trim().toLowerCase();
738
+ if (needle.length === 0) return { score: 0, indexes: [] };
739
+ const haystack = text.toLowerCase();
740
+ const indexes = [];
741
+ let score = 0;
742
+ let cursor = 0;
743
+ for (const char of needle) {
744
+ if (char === " ") continue;
745
+ const found = haystack.indexOf(char, cursor);
746
+ if (found < 0) return null;
747
+ indexes.push(found);
748
+ if (found === 0) score += 6;
749
+ const previous = found > 0 ? haystack[found - 1] : "";
750
+ const previousIsBoundary = previous === "" || !/[a-z0-9]/.test(previous);
751
+ if (previousIsBoundary) score += 4;
752
+ if (found === cursor && indexes.length > 1) score += 2;
753
+ score += 1;
754
+ cursor = found + 1;
755
+ }
756
+ score -= Math.min(haystack.length - needle.length, 10) * 0.1;
757
+ return { score, indexes };
758
+ }
759
+ function filterItems(items, query, read) {
760
+ const trimmed = query.trim();
761
+ if (trimmed.length === 0) return items.map((item) => ({ item, indexes: [] }));
762
+ const results = [];
763
+ for (const item of items) {
764
+ const { label, keywords } = read(item);
765
+ const onLabel = matchText(label, trimmed);
766
+ if (onLabel) {
767
+ results.push({ item, indexes: onLabel.indexes });
768
+ continue;
769
+ }
770
+ if (keywords && matchText(keywords, trimmed)) {
771
+ results.push({ item, indexes: [] });
772
+ }
773
+ }
774
+ return results;
775
+ }
776
+
777
+ // src/lib/highlight.tsx
778
+ import { jsx as jsx21 } from "react/jsx-runtime";
779
+ function Highlight({
780
+ text,
781
+ indexes
782
+ }) {
783
+ if (indexes.length === 0) return node(text);
784
+ const parts = [];
785
+ let cursor = 0;
786
+ for (const position of indexes) {
787
+ if (position > cursor) parts.push(text.slice(cursor, position));
788
+ parts.push(
789
+ /* @__PURE__ */ jsx21("mark", { className: "sph-match", children: text.slice(position, position + 1) }, position)
790
+ );
791
+ cursor = position + 1;
792
+ }
793
+ if (cursor < text.length) parts.push(text.slice(cursor));
794
+ return node(parts);
795
+ }
796
+
797
+ // src/lib/overlay.ts
798
+ import { useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState5 } from "react";
799
+ var useIsoLayoutEffect = typeof document === "undefined" ? useEffect3 : useLayoutEffect;
800
+ function clamp(value, min, max) {
801
+ const low = Math.min(min, max);
802
+ const high = Math.max(min, max);
803
+ return Math.min(Math.max(value, low), high);
804
+ }
805
+ function resolvePlacement(trigger, content, viewport, placement) {
806
+ const { side: wanted, align, offset, padding } = placement;
807
+ let side = wanted;
808
+ if (side === "left" || side === "right") {
809
+ const need = content.width + offset + padding;
810
+ const roomLeft = trigger.left - padding;
811
+ const roomRight = viewport.width - trigger.right - padding;
812
+ if (side === "left" && roomLeft < need && roomRight > roomLeft)
813
+ side = "right";
814
+ else if (side === "right" && roomRight < need && roomLeft > roomRight)
815
+ side = "left";
816
+ } else {
817
+ const need = content.height + offset + padding;
818
+ const roomBelow = viewport.height - trigger.bottom - padding;
819
+ const roomAbove = trigger.top - padding;
820
+ if (side === "bottom" && roomBelow < need && roomAbove > roomBelow)
821
+ side = "top";
822
+ else if (side === "top" && roomAbove < need && roomBelow > roomAbove)
823
+ side = "bottom";
824
+ }
825
+ let left;
826
+ let top;
827
+ if (side === "bottom" || side === "top") {
828
+ top = side === "bottom" ? trigger.bottom + offset : trigger.top - content.height - offset;
829
+ left = align === "start" ? trigger.left : align === "end" ? trigger.right - content.width : trigger.left + (trigger.width - content.width) / 2;
830
+ } else {
831
+ left = side === "right" ? trigger.right + offset : trigger.left - content.width - offset;
832
+ top = trigger.top + (trigger.height - content.height) / 2;
833
+ }
834
+ left = clamp(left, padding, viewport.width - content.width - padding);
835
+ top = clamp(top, padding, viewport.height - content.height - padding);
836
+ return { left: Math.round(left), top: Math.round(top), side };
837
+ }
838
+ function overlayOrigin(side, align) {
839
+ if (side === "left") return "right center";
840
+ if (side === "right") return "left center";
841
+ const edge = side === "bottom" ? "top" : "bottom";
842
+ if (align === "center") return `${edge} center`;
843
+ return `${edge} ${align === "start" ? "left" : "right"}`;
844
+ }
845
+ function usePortaled() {
846
+ const [portaled, setPortaled] = useState5(false);
847
+ useIsoLayoutEffect(() => {
848
+ setPortaled(true);
849
+ }, []);
850
+ return portaled;
851
+ }
852
+ function useOverlayPosition(triggerRef, contentRef, active, placement = {}) {
853
+ const {
854
+ side = "bottom",
855
+ align = "start",
856
+ offset = 4,
857
+ padding = 8,
858
+ matchWidth = false
859
+ } = placement;
860
+ const frameRef = useRef3(null);
861
+ useIsoLayoutEffect(() => {
862
+ if (!active) return;
863
+ const update = () => {
864
+ frameRef.current = null;
865
+ const trigger = triggerRef.current;
866
+ const content = contentRef.current;
867
+ if (!trigger || !content) return;
868
+ const triggerRect = trigger.getBoundingClientRect();
869
+ if (matchWidth)
870
+ content.style.width = `${Math.round(triggerRect.width)}px`;
871
+ content.style.position = "fixed";
872
+ const contentSize = {
873
+ width: content.offsetWidth,
874
+ height: content.offsetHeight
875
+ };
876
+ const resolved = resolvePlacement(
877
+ triggerRect,
878
+ contentSize,
879
+ { width: window.innerWidth, height: window.innerHeight },
880
+ { side, align, offset, padding }
881
+ );
882
+ const style = content.style;
883
+ style.left = `${resolved.left}px`;
884
+ style.top = `${resolved.top}px`;
885
+ style.right = "auto";
886
+ style.bottom = "auto";
887
+ style.translate = "none";
888
+ style.transformOrigin = overlayOrigin(resolved.side, align);
889
+ content.dataset.side = resolved.side;
890
+ };
891
+ const schedule = () => {
892
+ if (frameRef.current === null)
893
+ frameRef.current = requestAnimationFrame(update);
894
+ };
895
+ update();
896
+ window.addEventListener("resize", schedule);
897
+ document.addEventListener("scroll", schedule, {
898
+ capture: true,
899
+ passive: true
900
+ });
901
+ return () => {
902
+ window.removeEventListener("resize", schedule);
903
+ document.removeEventListener("scroll", schedule, { capture: true });
904
+ if (frameRef.current !== null) cancelAnimationFrame(frameRef.current);
905
+ };
906
+ }, [
907
+ active,
908
+ align,
909
+ matchWidth,
910
+ offset,
911
+ padding,
912
+ side,
913
+ contentRef,
914
+ triggerRef
915
+ ]);
916
+ }
917
+
918
+ // src/molecules/Combobox/index.tsx
919
+ import { jsx as jsx22, jsxs as jsxs10 } from "react/jsx-runtime";
920
+ function readableLabel(option) {
921
+ return typeof option.label === "string" ? option.label : "";
922
+ }
923
+ function resolveActive(options, wanted) {
924
+ if (options.length === 0) return -1;
925
+ const start = Math.min(Math.max(wanted, 0), options.length - 1);
926
+ const enabled = (index) => !options[index]?.option.disabled;
927
+ if (enabled(start)) return start;
928
+ for (let index = start + 1; index < options.length; index += 1)
929
+ if (enabled(index)) return index;
930
+ for (let index = 0; index < start; index += 1)
931
+ if (enabled(index)) return index;
932
+ return start;
933
+ }
934
+ function Combobox({
935
+ options,
936
+ value,
937
+ values,
938
+ onValueChange,
939
+ onValuesChange,
940
+ placeholder = "Select\u2026",
941
+ searchPlaceholder = "Search\u2026",
942
+ emptyMessage = "No matches.",
943
+ disabled = false,
944
+ size = "md",
945
+ id,
946
+ ariaLabel,
462
947
  className
463
948
  }) {
464
- const id = useId2();
465
- const titleId = `${id}-title`;
466
- const descriptionId = description ? `${id}-description` : void 0;
467
- const dialogRef = useRef(null);
468
- useEffect(() => {
469
- if (!open) return;
470
- const dialog = dialogRef.current;
471
- const focusable = () => Array.from(
472
- dialog?.querySelectorAll(
473
- "button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])"
474
- ) ?? []
475
- ).filter((element) => !element.hasAttribute("disabled"));
476
- requestAnimationFrame(() => focusable()[0]?.focus());
949
+ const generatedId = useId3();
950
+ const controlId = id ?? `sph-combobox-${generatedId}`;
951
+ const listId = `${controlId}-list`;
952
+ const rootRef = useRef4(null);
953
+ const triggerRef = useRef4(null);
954
+ const listRef = useRef4(null);
955
+ const inputRef = useRef4(null);
956
+ const [open, setOpen] = useState6(false);
957
+ const [query, setQuery] = useState6("");
958
+ const [activeState, setActive] = useState6(0);
959
+ const multiple = values !== void 0;
960
+ const selectedValues = multiple ? values ?? [] : value ? [value] : [];
961
+ const {
962
+ present,
963
+ state,
964
+ ref: presenceRef
965
+ } = usePresence(open, "fast");
966
+ const portaled = usePortaled();
967
+ const visible = present && portaled;
968
+ useOverlayPosition(triggerRef, presenceRef, visible, {
969
+ side: "bottom",
970
+ align: "start",
971
+ offset: 4,
972
+ matchWidth: true
973
+ });
974
+ const sections = useMemo(() => {
975
+ const read = (option) => ({
976
+ label: readableLabel(option),
977
+ keywords: option.keywords
978
+ });
979
+ const filtered = filterItems(options, query, read);
980
+ const ungrouped = filtered.filter(
981
+ (entry) => entry.item.group === void 0
982
+ );
983
+ const groupNames = [
984
+ ...new Set(
985
+ filtered.map((entry) => entry.item.group).filter((name) => name !== void 0)
986
+ )
987
+ ];
988
+ const raw = [
989
+ ...ungrouped.length > 0 ? [{ heading: void 0, entries: ungrouped }] : [],
990
+ ...groupNames.map((name) => ({
991
+ heading: name,
992
+ entries: filtered.filter((entry) => entry.item.group === name)
993
+ }))
994
+ ];
995
+ let order = 0;
996
+ const built = raw.map((section) => ({
997
+ heading: section.heading,
998
+ entries: section.entries.map((entry) => ({
999
+ option: entry.item,
1000
+ indexes: entry.indexes,
1001
+ activeId: `${listId}-option-${order}`,
1002
+ index: order++
1003
+ }))
1004
+ }));
1005
+ return built;
1006
+ }, [listId, options, query]);
1007
+ const flat = sections.flatMap((section) => section.entries);
1008
+ const active = resolveActive(flat, activeState);
1009
+ const close = () => {
1010
+ setOpen(false);
1011
+ setQuery("");
1012
+ };
1013
+ const toggleValue = (option) => {
1014
+ if (option.disabled) return;
1015
+ if (multiple) {
1016
+ const next = selectedValues.includes(option.value) ? selectedValues.filter((entry) => entry !== option.value) : [...selectedValues, option.value];
1017
+ onValuesChange?.(next);
1018
+ setQuery("");
1019
+ setActive(0);
1020
+ return;
1021
+ }
1022
+ onValueChange?.(option.value);
1023
+ close();
1024
+ };
1025
+ const removeValue = (entry) => {
1026
+ onValuesChange?.(selectedValues.filter((item) => item !== entry));
1027
+ };
1028
+ const activate = (index) => {
1029
+ const option = flat[index]?.option;
1030
+ if (!option) return;
1031
+ toggleValue(option);
1032
+ };
1033
+ const move = (direction) => {
1034
+ if (flat.length === 0) return;
1035
+ let index = active < 0 ? 0 : active;
1036
+ for (let step = 0; step < flat.length; step += 1) {
1037
+ index = (index + direction + flat.length) % flat.length;
1038
+ if (!flat[index]?.option.disabled) {
1039
+ setActive(index);
1040
+ return;
1041
+ }
1042
+ }
1043
+ };
1044
+ useEffect4(() => {
1045
+ if (!open) setQuery("");
1046
+ }, [open]);
1047
+ useEffect4(() => {
1048
+ setActive(0);
1049
+ }, [query]);
1050
+ useEffect4(() => {
1051
+ if (!visible) return;
1052
+ inputRef.current?.setAttribute("spellcheck", "false");
1053
+ requestAnimationFrame(() => inputRef.current?.focus());
1054
+ }, [visible]);
1055
+ useEffect4(() => {
1056
+ if (!visible) return;
477
1057
  const handleKeyDown = (event) => {
478
1058
  if (event.key === "Escape") {
479
- onClose();
480
- return;
1059
+ event.preventDefault();
1060
+ close();
481
1061
  }
1062
+ };
1063
+ const handlePointerDown = (event) => {
1064
+ const target = event.target;
1065
+ if (!rootRef.current?.contains(target) && !presenceRef.current?.contains(target))
1066
+ close();
1067
+ };
1068
+ document.addEventListener("keydown", handleKeyDown);
1069
+ document.addEventListener("pointerdown", handlePointerDown);
1070
+ return () => {
1071
+ document.removeEventListener("keydown", handleKeyDown);
1072
+ document.removeEventListener("pointerdown", handlePointerDown);
1073
+ };
1074
+ });
1075
+ useEffect4(() => {
1076
+ if (!visible || active < 0) return;
1077
+ listRef.current?.querySelector('[data-active="true"]')?.scrollIntoView({ block: "nearest" });
1078
+ }, [active, visible]);
1079
+ const handleInputKeyDown = (event) => {
1080
+ if (event.key === "ArrowDown") {
1081
+ event.preventDefault();
1082
+ move(1);
1083
+ } else if (event.key === "ArrowUp") {
1084
+ event.preventDefault();
1085
+ move(-1);
1086
+ } else if (event.key === "Home") {
1087
+ event.preventDefault();
1088
+ setActive(0);
1089
+ } else if (event.key === "End") {
1090
+ event.preventDefault();
1091
+ setActive(flat.length - 1);
1092
+ } else if (event.key === "Enter") {
1093
+ event.preventDefault();
1094
+ if (active >= 0) activate(active);
1095
+ }
1096
+ };
1097
+ const handleTriggerKeyDown = (event) => {
1098
+ if (event.key === "ArrowDown" || event.key === "Enter" || event.key === " ") {
1099
+ event.preventDefault();
1100
+ if (!disabled) setOpen(true);
1101
+ }
1102
+ };
1103
+ const labelOf = (entry) => readableLabel(
1104
+ options.find((option) => option.value === entry) ?? {
1105
+ value: entry,
1106
+ label: entry
1107
+ }
1108
+ );
1109
+ return /* @__PURE__ */ jsxs10(
1110
+ "div",
1111
+ {
1112
+ ref: rootRef,
1113
+ className: cn21("sph-combobox", className),
1114
+ "data-size": size,
1115
+ "data-open": open || void 0,
1116
+ children: [
1117
+ multiple ? /* @__PURE__ */ jsxs10(
1118
+ "div",
1119
+ {
1120
+ className: "sph-combobox__control",
1121
+ onClick: () => {
1122
+ if (!disabled) setOpen((current) => !current);
1123
+ },
1124
+ children: [
1125
+ /* @__PURE__ */ jsxs10("div", { className: "sph-combobox__chips", children: [
1126
+ selectedValues.map((entry) => /* @__PURE__ */ jsxs10("span", { className: "sph-combobox__chip", children: [
1127
+ /* @__PURE__ */ jsx22("span", { className: "sph-combobox__chip-label", children: labelOf(entry) }),
1128
+ /* @__PURE__ */ jsx22(
1129
+ "button",
1130
+ {
1131
+ type: "button",
1132
+ className: "sph-combobox__chip-remove",
1133
+ "aria-label": `Remove ${labelOf(entry)}`,
1134
+ disabled,
1135
+ onClick: (event) => {
1136
+ event.stopPropagation();
1137
+ removeValue(entry);
1138
+ },
1139
+ children: /* @__PURE__ */ jsx22(XIcon2, { size: 10, "aria-hidden": "true" })
1140
+ }
1141
+ )
1142
+ ] }, entry)),
1143
+ selectedValues.length === 0 && /* @__PURE__ */ jsx22("span", { className: "sph-combobox__placeholder", children: node(placeholder) })
1144
+ ] }),
1145
+ /* @__PURE__ */ jsx22(
1146
+ "button",
1147
+ {
1148
+ type: "button",
1149
+ ref: triggerRef,
1150
+ className: "sph-combobox__toggle",
1151
+ "aria-label": ariaLabel ?? "Open suggestions",
1152
+ "aria-haspopup": "listbox",
1153
+ "aria-expanded": open,
1154
+ "aria-controls": open ? listId : void 0,
1155
+ disabled,
1156
+ onClick: (event) => {
1157
+ event.stopPropagation();
1158
+ if (!disabled) setOpen((current) => !current);
1159
+ },
1160
+ onKeyDown: handleTriggerKeyDown,
1161
+ children: /* @__PURE__ */ jsx22(
1162
+ CaretDownIcon,
1163
+ {
1164
+ className: "sph-combobox__caret",
1165
+ size: 13,
1166
+ "aria-hidden": "true"
1167
+ }
1168
+ )
1169
+ }
1170
+ )
1171
+ ]
1172
+ }
1173
+ ) : /* @__PURE__ */ jsxs10(
1174
+ "button",
1175
+ {
1176
+ type: "button",
1177
+ ref: triggerRef,
1178
+ className: "sph-combobox__control sph-combobox__control--button",
1179
+ "aria-label": ariaLabel,
1180
+ "aria-haspopup": "listbox",
1181
+ "aria-expanded": open,
1182
+ "aria-controls": open ? listId : void 0,
1183
+ disabled,
1184
+ onClick: () => {
1185
+ if (!disabled) setOpen((current) => !current);
1186
+ },
1187
+ onKeyDown: handleTriggerKeyDown,
1188
+ children: [
1189
+ /* @__PURE__ */ jsx22(
1190
+ "span",
1191
+ {
1192
+ className: selectedValues.length > 0 ? "sph-combobox__value" : "sph-combobox__placeholder",
1193
+ children: selectedValues.length > 0 ? node(
1194
+ options.find((option) => option.value === value)?.label ?? value
1195
+ ) : node(placeholder)
1196
+ }
1197
+ ),
1198
+ /* @__PURE__ */ jsx22(
1199
+ CaretDownIcon,
1200
+ {
1201
+ className: "sph-combobox__caret",
1202
+ size: 13,
1203
+ "aria-hidden": "true"
1204
+ }
1205
+ )
1206
+ ]
1207
+ }
1208
+ ),
1209
+ visible && createPortal(
1210
+ /* @__PURE__ */ jsxs10(
1211
+ "div",
1212
+ {
1213
+ ref: presenceRef,
1214
+ className: "sph-combobox__panel",
1215
+ "data-state": state,
1216
+ children: [
1217
+ /* @__PURE__ */ jsxs10("div", { className: "sph-combobox__search", children: [
1218
+ /* @__PURE__ */ jsx22(
1219
+ MagnifyingGlassIcon,
1220
+ {
1221
+ className: "sph-combobox__search-icon",
1222
+ size: 14,
1223
+ "aria-hidden": "true"
1224
+ }
1225
+ ),
1226
+ /* @__PURE__ */ jsx22(
1227
+ "input",
1228
+ {
1229
+ ref: inputRef,
1230
+ className: "sph-combobox__input",
1231
+ type: "text",
1232
+ role: "combobox",
1233
+ "aria-expanded": true,
1234
+ "aria-controls": listId,
1235
+ "aria-activedescendant": active >= 0 ? flat[active]?.activeId : void 0,
1236
+ "aria-autocomplete": "list",
1237
+ "aria-label": searchPlaceholder,
1238
+ placeholder: searchPlaceholder,
1239
+ value: query,
1240
+ autoComplete: "off",
1241
+ onChange: (event) => setQuery(event.currentTarget.value),
1242
+ onKeyDown: handleInputKeyDown
1243
+ }
1244
+ )
1245
+ ] }),
1246
+ /* @__PURE__ */ jsxs10(
1247
+ "div",
1248
+ {
1249
+ ref: listRef,
1250
+ className: "sph-combobox__list",
1251
+ id: listId,
1252
+ role: "listbox",
1253
+ "aria-label": ariaLabel ?? searchPlaceholder,
1254
+ "aria-multiselectable": multiple || void 0,
1255
+ children: [
1256
+ flat.length === 0 && /* @__PURE__ */ jsx22("div", { className: "sph-combobox__empty", role: "status", children: node(emptyMessage) }),
1257
+ sections.map((section) => /* @__PURE__ */ jsxs10(
1258
+ "div",
1259
+ {
1260
+ className: "sph-combobox__group",
1261
+ role: "group",
1262
+ "aria-labelledby": section.heading !== void 0 ? `${listId}-group-${section.heading}` : void 0,
1263
+ children: [
1264
+ section.heading !== void 0 && /* @__PURE__ */ jsx22(
1265
+ "div",
1266
+ {
1267
+ className: "sph-combobox__heading",
1268
+ id: `${listId}-group-${section.heading}`,
1269
+ children: section.heading
1270
+ }
1271
+ ),
1272
+ section.entries.map((entry) => {
1273
+ const option = entry.option;
1274
+ const isSelected = selectedValues.includes(option.value);
1275
+ return /* @__PURE__ */ jsxs10(
1276
+ "div",
1277
+ {
1278
+ id: entry.activeId,
1279
+ className: "sph-combobox__option",
1280
+ role: "option",
1281
+ "data-index": entry.index,
1282
+ "data-active": entry.index === active ? "true" : void 0,
1283
+ "data-disabled": option.disabled || void 0,
1284
+ "aria-selected": isSelected,
1285
+ "aria-disabled": option.disabled || void 0,
1286
+ onPointerEnter: () => {
1287
+ if (!option.disabled) setActive(entry.index);
1288
+ },
1289
+ onPointerDown: (event) => event.preventDefault(),
1290
+ onClick: () => activate(entry.index),
1291
+ children: [
1292
+ /* @__PURE__ */ jsx22(
1293
+ "span",
1294
+ {
1295
+ className: "sph-combobox__check",
1296
+ "data-checked": isSelected || void 0,
1297
+ "aria-hidden": "true",
1298
+ children: isSelected && /* @__PURE__ */ jsx22(CheckIcon3, { size: 13, weight: "bold" })
1299
+ }
1300
+ ),
1301
+ /* @__PURE__ */ jsx22("span", { className: "sph-combobox__label", children: typeof option.label === "string" ? /* @__PURE__ */ jsx22(
1302
+ Highlight,
1303
+ {
1304
+ text: option.label,
1305
+ indexes: entry.indexes
1306
+ }
1307
+ ) : node(option.label) })
1308
+ ]
1309
+ },
1310
+ option.value
1311
+ );
1312
+ })
1313
+ ]
1314
+ },
1315
+ section.heading ?? "section-root"
1316
+ ))
1317
+ ]
1318
+ }
1319
+ )
1320
+ ]
1321
+ }
1322
+ ),
1323
+ document.body
1324
+ )
1325
+ ]
1326
+ }
1327
+ );
1328
+ }
1329
+
1330
+ // src/molecules/Dialog/index.tsx
1331
+ import { XIcon as XIcon3 } from "@phosphor-icons/react";
1332
+ import { cn as cn22 } from "cn";
1333
+ import { useEffect as useEffect6, useId as useId4 } from "react";
1334
+
1335
+ // src/lib/focus.ts
1336
+ import { useEffect as useEffect5 } from "react";
1337
+ var FOCUSABLE_SELECTOR = "button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])";
1338
+ function focusable(container) {
1339
+ return Array.from(
1340
+ container?.querySelectorAll(FOCUSABLE_SELECTOR) ?? []
1341
+ ).filter((element) => !element.hasAttribute("disabled"));
1342
+ }
1343
+ function useFocusTrap(ref, active, initial) {
1344
+ useEffect5(() => {
1345
+ if (!active) return;
1346
+ requestAnimationFrame(() => {
1347
+ const target = initial?.current ?? focusable(ref.current)[0];
1348
+ target?.focus();
1349
+ });
1350
+ const handleKeyDown = (event) => {
482
1351
  if (event.key !== "Tab") return;
483
- const elements = focusable();
1352
+ const elements = focusable(ref.current);
484
1353
  if (elements.length === 0) {
485
1354
  event.preventDefault();
486
1355
  return;
@@ -497,45 +1366,154 @@ function Dialog({
497
1366
  };
498
1367
  document.addEventListener("keydown", handleKeyDown);
499
1368
  return () => document.removeEventListener("keydown", handleKeyDown);
1369
+ }, [active, initial, ref]);
1370
+ }
1371
+
1372
+ // src/molecules/Dialog/index.tsx
1373
+ import { jsx as jsx23, jsxs as jsxs11 } from "react/jsx-runtime";
1374
+ function Dialog({
1375
+ open,
1376
+ onClose,
1377
+ title,
1378
+ description,
1379
+ children,
1380
+ footer,
1381
+ closeLabel = "Close dialog",
1382
+ closeOnOverlayClick = true,
1383
+ className
1384
+ }) {
1385
+ const id = useId4();
1386
+ const titleId = `${id}-title`;
1387
+ const descriptionId = description ? `${id}-description` : void 0;
1388
+ const { present, state, ref: dialogRef } = usePresence(open);
1389
+ useFocusTrap(dialogRef, open);
1390
+ useEffect6(() => {
1391
+ if (!open) return;
1392
+ const handleKeyDown = (event) => {
1393
+ if (event.key === "Escape") onClose();
1394
+ };
1395
+ document.addEventListener("keydown", handleKeyDown);
1396
+ return () => document.removeEventListener("keydown", handleKeyDown);
500
1397
  }, [onClose, open]);
501
- if (!open) return null;
502
- return /* @__PURE__ */ jsx15(
1398
+ if (!present) return null;
1399
+ return /* @__PURE__ */ jsx23(
503
1400
  "div",
504
1401
  {
505
1402
  className: "sph-dialog__backdrop",
1403
+ "data-state": state,
506
1404
  role: "presentation",
507
1405
  onMouseDown: (event) => {
508
1406
  if (closeOnOverlayClick && event.target === event.currentTarget)
509
1407
  onClose();
510
1408
  },
511
- children: /* @__PURE__ */ jsxs6(
1409
+ children: /* @__PURE__ */ jsxs11(
512
1410
  "section",
513
1411
  {
514
1412
  ref: dialogRef,
515
- className: cn15("sph-dialog", className),
1413
+ className: cn22("sph-dialog", className),
516
1414
  role: "dialog",
517
1415
  "aria-modal": "true",
518
1416
  "aria-labelledby": titleId,
519
1417
  "aria-describedby": descriptionId,
520
1418
  children: [
521
- /* @__PURE__ */ jsxs6("header", { className: "sph-dialog__header", children: [
522
- /* @__PURE__ */ jsxs6("div", { className: "sph-dialog__heading", children: [
523
- /* @__PURE__ */ jsx15("h2", { id: titleId, className: "sph-dialog__title", children: node(title) }),
524
- description !== void 0 && /* @__PURE__ */ jsx15("p", { id: descriptionId, className: "sph-dialog__description", children: node(description) })
1419
+ /* @__PURE__ */ jsxs11("header", { className: "sph-dialog__header", children: [
1420
+ /* @__PURE__ */ jsxs11("div", { className: "sph-dialog__heading", children: [
1421
+ /* @__PURE__ */ jsx23("h2", { id: titleId, className: "sph-dialog__title", children: node(title) }),
1422
+ description !== void 0 && /* @__PURE__ */ jsx23("p", { id: descriptionId, className: "sph-dialog__description", children: node(description) })
525
1423
  ] }),
526
- /* @__PURE__ */ jsx15(
1424
+ /* @__PURE__ */ jsx23(
527
1425
  "button",
528
1426
  {
529
1427
  type: "button",
530
1428
  className: "sph-dialog__close",
531
1429
  "aria-label": closeLabel,
532
1430
  onClick: onClose,
533
- children: /* @__PURE__ */ jsx15(XIcon2, { size: 14, "aria-hidden": "true" })
1431
+ children: /* @__PURE__ */ jsx23(XIcon3, { size: 14, "aria-hidden": "true" })
534
1432
  }
535
1433
  )
536
1434
  ] }),
537
- children !== void 0 && /* @__PURE__ */ jsx15("div", { className: "sph-dialog__content", children: node(children) }),
538
- footer !== void 0 && /* @__PURE__ */ jsx15("footer", { className: "sph-dialog__footer", children: node(footer) })
1435
+ children !== void 0 && /* @__PURE__ */ jsx23("div", { className: "sph-dialog__content", children: node(children) }),
1436
+ footer !== void 0 && /* @__PURE__ */ jsx23("footer", { className: "sph-dialog__footer", children: node(footer) })
1437
+ ]
1438
+ }
1439
+ )
1440
+ }
1441
+ );
1442
+ }
1443
+
1444
+ // src/molecules/Drawer/index.tsx
1445
+ import { XIcon as XIcon4 } from "@phosphor-icons/react";
1446
+ import { cn as cn23 } from "cn";
1447
+ import { useEffect as useEffect7, useId as useId5 } from "react";
1448
+ import { jsx as jsx24, jsxs as jsxs12 } from "react/jsx-runtime";
1449
+ function Drawer({
1450
+ open,
1451
+ onClose,
1452
+ title,
1453
+ description,
1454
+ children,
1455
+ footer,
1456
+ side = "right",
1457
+ size = "md",
1458
+ closeLabel = "Close panel",
1459
+ closeOnOverlayClick = true,
1460
+ className
1461
+ }) {
1462
+ const id = useId5();
1463
+ const titleId = `${id}-title`;
1464
+ const descriptionId = description ? `${id}-description` : void 0;
1465
+ const { present, state, ref: drawerRef } = usePresence(open);
1466
+ useFocusTrap(drawerRef, open);
1467
+ useEffect7(() => {
1468
+ if (!open) return;
1469
+ const handleKeyDown = (event) => {
1470
+ if (event.key === "Escape") onClose();
1471
+ };
1472
+ document.addEventListener("keydown", handleKeyDown);
1473
+ return () => document.removeEventListener("keydown", handleKeyDown);
1474
+ }, [onClose, open]);
1475
+ if (!present) return null;
1476
+ return /* @__PURE__ */ jsx24(
1477
+ "div",
1478
+ {
1479
+ className: "sph-drawer__backdrop",
1480
+ "data-state": state,
1481
+ "data-side": side,
1482
+ role: "presentation",
1483
+ onMouseDown: (event) => {
1484
+ if (closeOnOverlayClick && event.target === event.currentTarget)
1485
+ onClose();
1486
+ },
1487
+ children: /* @__PURE__ */ jsxs12(
1488
+ "section",
1489
+ {
1490
+ ref: drawerRef,
1491
+ className: cn23("sph-drawer", className),
1492
+ "data-side": side,
1493
+ "data-size": size,
1494
+ role: "dialog",
1495
+ "aria-modal": "true",
1496
+ "aria-labelledby": titleId,
1497
+ "aria-describedby": descriptionId,
1498
+ children: [
1499
+ /* @__PURE__ */ jsxs12("header", { className: "sph-drawer__header", children: [
1500
+ /* @__PURE__ */ jsxs12("div", { className: "sph-drawer__heading", children: [
1501
+ /* @__PURE__ */ jsx24("h2", { id: titleId, className: "sph-drawer__title", children: node(title) }),
1502
+ description !== void 0 && /* @__PURE__ */ jsx24("p", { id: descriptionId, className: "sph-drawer__description", children: node(description) })
1503
+ ] }),
1504
+ /* @__PURE__ */ jsx24(
1505
+ "button",
1506
+ {
1507
+ type: "button",
1508
+ className: "sph-drawer__close",
1509
+ "aria-label": closeLabel,
1510
+ onClick: onClose,
1511
+ children: /* @__PURE__ */ jsx24(XIcon4, { size: 14, "aria-hidden": "true" })
1512
+ }
1513
+ )
1514
+ ] }),
1515
+ children !== void 0 && /* @__PURE__ */ jsx24("div", { className: "sph-drawer__content", children: node(children) }),
1516
+ footer !== void 0 && /* @__PURE__ */ jsx24("footer", { className: "sph-drawer__footer", children: node(footer) })
539
1517
  ]
540
1518
  }
541
1519
  )
@@ -544,12 +1522,12 @@ function Dialog({
544
1522
  }
545
1523
 
546
1524
  // src/molecules/Field/index.tsx
547
- import { cn as cn17 } from "cn";
548
- import { useId as useId3 } from "react";
1525
+ import { cn as cn25 } from "cn";
1526
+ import { useId as useId6 } from "react";
549
1527
 
550
1528
  // src/molecules/FieldMessage/index.tsx
551
- import { cn as cn16 } from "cn";
552
- import { jsx as jsx16 } from "react/jsx-runtime";
1529
+ import { cn as cn24 } from "cn";
1530
+ import { jsx as jsx25 } from "react/jsx-runtime";
553
1531
  function FieldMessage({
554
1532
  variant = "hint",
555
1533
  className,
@@ -557,11 +1535,11 @@ function FieldMessage({
557
1535
  ...props
558
1536
  }) {
559
1537
  if (children === void 0 || children === null) return null;
560
- return /* @__PURE__ */ jsx16(
1538
+ return /* @__PURE__ */ jsx25(
561
1539
  "p",
562
1540
  {
563
1541
  ...node(props),
564
- className: cn16("sph-field-message", className),
1542
+ className: cn24("sph-field-message", className),
565
1543
  "data-variant": variant,
566
1544
  role: variant === "error" ? "alert" : void 0,
567
1545
  children: node(children)
@@ -570,7 +1548,7 @@ function FieldMessage({
570
1548
  }
571
1549
 
572
1550
  // src/molecules/Field/index.tsx
573
- import { jsx as jsx17, jsxs as jsxs7 } from "react/jsx-runtime";
1551
+ import { jsx as jsx26, jsxs as jsxs13 } from "react/jsx-runtime";
574
1552
  function Field({
575
1553
  label,
576
1554
  htmlFor,
@@ -581,24 +1559,145 @@ function Field({
581
1559
  className,
582
1560
  children
583
1561
  }) {
584
- const generatedId = useId3();
1562
+ const generatedId = useId6();
585
1563
  const descriptionId = description ? `${generatedId}-description` : void 0;
586
1564
  const messageId = message ? `${generatedId}-message` : void 0;
587
- return /* @__PURE__ */ jsxs7("div", { className: cn17("sph-field", className), children: [
588
- label !== void 0 && /* @__PURE__ */ jsxs7("label", { className: "sph-field__label", htmlFor, children: [
589
- /* @__PURE__ */ jsx17("span", { children: node(label) }),
590
- required && /* @__PURE__ */ jsx17("span", { className: "sph-field__required", children: "Required" })
1565
+ return /* @__PURE__ */ jsxs13("div", { className: cn25("sph-field", className), children: [
1566
+ label !== void 0 && /* @__PURE__ */ jsxs13("label", { className: "sph-field__label", htmlFor, children: [
1567
+ /* @__PURE__ */ jsx26("span", { children: node(label) }),
1568
+ required && /* @__PURE__ */ jsx26("span", { className: "sph-field__required", children: "Required" })
591
1569
  ] }),
592
- description !== void 0 && description !== null && /* @__PURE__ */ jsx17("p", { id: descriptionId, className: "sph-field__description", children: node(description) }),
593
- /* @__PURE__ */ jsx17("div", { className: "sph-field__control", children: node(children) }),
594
- message !== void 0 && message !== null && /* @__PURE__ */ jsx17(FieldMessage, { id: messageId, variant: messageType, children: node(message) })
1570
+ description !== void 0 && description !== null && /* @__PURE__ */ jsx26("p", { id: descriptionId, className: "sph-field__description", children: node(description) }),
1571
+ /* @__PURE__ */ jsx26("div", { className: "sph-field__control", children: node(children) }),
1572
+ message !== void 0 && message !== null && /* @__PURE__ */ jsx26(FieldMessage, { id: messageId, variant: messageType, children: node(message) })
595
1573
  ] });
596
1574
  }
597
1575
 
1576
+ // src/molecules/HoverCard/index.tsx
1577
+ import { cn as cn26 } from "cn";
1578
+ import { useEffect as useEffect8, useId as useId7, useRef as useRef5, useState as useState7 } from "react";
1579
+ import { createPortal as createPortal2 } from "react-dom";
1580
+ import { jsx as jsx27, jsxs as jsxs14 } from "react/jsx-runtime";
1581
+ function HoverCard({
1582
+ trigger,
1583
+ children,
1584
+ openDelay = 180,
1585
+ closeDelay = 140,
1586
+ placement = "bottom",
1587
+ ariaLabel = "Hover card",
1588
+ className
1589
+ }) {
1590
+ const id = useId7();
1591
+ const rootRef = useRef5(null);
1592
+ const triggerRef = useRef5(null);
1593
+ const timers = useRef5({});
1594
+ const [open, setOpen] = useState7(false);
1595
+ const {
1596
+ present,
1597
+ state,
1598
+ ref: contentRef
1599
+ } = usePresence(open, "fast");
1600
+ const portaled = usePortaled();
1601
+ useOverlayPosition(triggerRef, contentRef, present && portaled, {
1602
+ side: placement,
1603
+ align: "start",
1604
+ offset: 6
1605
+ });
1606
+ const clearTimers = () => {
1607
+ if (timers.current.open !== void 0)
1608
+ window.clearTimeout(timers.current.open);
1609
+ if (timers.current.close !== void 0)
1610
+ window.clearTimeout(timers.current.close);
1611
+ timers.current = {};
1612
+ };
1613
+ const requestOpen = () => {
1614
+ clearTimers();
1615
+ timers.current.open = window.setTimeout(() => setOpen(true), openDelay);
1616
+ };
1617
+ const requestClose = () => {
1618
+ clearTimers();
1619
+ timers.current.close = window.setTimeout(() => setOpen(false), closeDelay);
1620
+ };
1621
+ useEffect8(() => clearTimers, []);
1622
+ useEffect8(() => {
1623
+ if (!present || !portaled) return;
1624
+ const closeNow = () => {
1625
+ clearTimers();
1626
+ setOpen(false);
1627
+ };
1628
+ const handleKeyDown = (event) => {
1629
+ if (event.key === "Escape") closeNow();
1630
+ };
1631
+ const handlePointerDown = (event) => {
1632
+ const target = event.target;
1633
+ if (!rootRef.current?.contains(target) && !contentRef.current?.contains(target))
1634
+ closeNow();
1635
+ };
1636
+ document.addEventListener("keydown", handleKeyDown);
1637
+ document.addEventListener("pointerdown", handlePointerDown);
1638
+ return () => {
1639
+ document.removeEventListener("keydown", handleKeyDown);
1640
+ document.removeEventListener("pointerdown", handlePointerDown);
1641
+ };
1642
+ }, [contentRef, present, portaled]);
1643
+ const handleBlur = (event) => {
1644
+ const next = event.relatedTarget;
1645
+ if (next && (rootRef.current?.contains(next) || contentRef.current?.contains(next)))
1646
+ return;
1647
+ clearTimers();
1648
+ setOpen(false);
1649
+ };
1650
+ return /* @__PURE__ */ jsxs14(
1651
+ "div",
1652
+ {
1653
+ ref: rootRef,
1654
+ className: cn26("sph-hover-card", className),
1655
+ "data-open": open || void 0,
1656
+ onPointerEnter: requestOpen,
1657
+ onPointerLeave: requestClose,
1658
+ onBlur: handleBlur,
1659
+ children: [
1660
+ /* @__PURE__ */ jsx27(
1661
+ "button",
1662
+ {
1663
+ type: "button",
1664
+ ref: triggerRef,
1665
+ className: "sph-hover-card__trigger",
1666
+ "aria-haspopup": "dialog",
1667
+ "aria-expanded": open,
1668
+ "aria-controls": open ? id : void 0,
1669
+ onFocus: requestOpen,
1670
+ children: node(trigger)
1671
+ }
1672
+ ),
1673
+ present && portaled && createPortal2(
1674
+ /* @__PURE__ */ jsx27(
1675
+ "div",
1676
+ {
1677
+ id,
1678
+ ref: contentRef,
1679
+ className: "sph-hover-card__content",
1680
+ "data-state": state,
1681
+ role: "dialog",
1682
+ "aria-label": ariaLabel,
1683
+ onPointerEnter: clearTimers,
1684
+ onPointerLeave: requestClose,
1685
+ onBlur: handleBlur,
1686
+ children: node(children)
1687
+ }
1688
+ ),
1689
+ document.body
1690
+ )
1691
+ ]
1692
+ }
1693
+ );
1694
+ }
1695
+
598
1696
  // src/molecules/Menu/index.tsx
599
- import { cn as cn18 } from "cn";
600
- import { useEffect as useEffect2, useId as useId4, useRef as useRef2, useState } from "react";
601
- import { jsx as jsx18, jsxs as jsxs8 } from "react/jsx-runtime";
1697
+ import { cn as cn27 } from "cn";
1698
+ import { useEffect as useEffect9, useId as useId8, useRef as useRef6, useState as useState8 } from "react";
1699
+ import { createPortal as createPortal3 } from "react-dom";
1700
+ import { jsx as jsx28, jsxs as jsxs15 } from "react/jsx-runtime";
602
1701
  function Menu({
603
1702
  trigger,
604
1703
  items,
@@ -608,18 +1707,32 @@ function Menu({
608
1707
  disabled = false,
609
1708
  className
610
1709
  }) {
611
- const rootRef = useRef2(null);
612
- const menuId = useId4();
613
- const [open, setOpen] = useState(false);
1710
+ const rootRef = useRef6(null);
1711
+ const triggerRef = useRef6(null);
1712
+ const menuId = useId8();
1713
+ const [open, setOpen] = useState8(false);
1714
+ const {
1715
+ present,
1716
+ state,
1717
+ ref: contentRef
1718
+ } = usePresence(open, "fast");
1719
+ const portaled = usePortaled();
1720
+ useOverlayPosition(triggerRef, contentRef, present && portaled, {
1721
+ side: "bottom",
1722
+ align,
1723
+ offset: 4
1724
+ });
614
1725
  const enabledIndexes = items.flatMap(
615
1726
  (item, index) => !item.separator && !item.disabled ? [index] : []
616
1727
  );
617
1728
  const close = () => setOpen(false);
618
1729
  const focusItem = (index) => document.getElementById(`${menuId}-${index}`)?.focus();
619
- useEffect2(() => {
1730
+ useEffect9(() => {
620
1731
  if (!open) return;
621
1732
  const handlePointerDown = (event) => {
622
- if (!rootRef.current?.contains(event.target)) close();
1733
+ const target = event.target;
1734
+ if (!rootRef.current?.contains(target) && !contentRef.current?.contains(target))
1735
+ close();
623
1736
  };
624
1737
  document.addEventListener("pointerdown", handlePointerDown);
625
1738
  return () => document.removeEventListener("pointerdown", handlePointerDown);
@@ -661,58 +1774,75 @@ function Menu({
661
1774
  const nextIndex = enabledIndexes[nextPosition];
662
1775
  if (nextIndex !== void 0) focusItem(nextIndex);
663
1776
  };
664
- return /* @__PURE__ */ jsxs8("div", { id: menuId, ref: rootRef, className: cn18("sph-menu", className), children: [
665
- /* @__PURE__ */ jsx18(
1777
+ return /* @__PURE__ */ jsxs15("div", { id: menuId, ref: rootRef, className: cn27("sph-menu", className), children: [
1778
+ /* @__PURE__ */ jsx28(
666
1779
  "button",
667
1780
  {
668
1781
  type: "button",
1782
+ ref: triggerRef,
669
1783
  className: "sph-menu__trigger",
670
1784
  "aria-label": label,
671
1785
  "aria-haspopup": "menu",
672
1786
  "aria-expanded": open,
1787
+ "aria-controls": open ? `${menuId}-menu` : void 0,
673
1788
  disabled,
674
1789
  onClick: () => setOpen((current) => !current),
675
1790
  onKeyDown: handleKeyDown,
676
1791
  children: node(trigger)
677
1792
  }
678
- ),
679
- open && /* @__PURE__ */ jsx18("div", { className: "sph-menu__content", "data-align": align, role: "menu", children: items.map(
680
- (item, index) => item.separator ? /* @__PURE__ */ jsx18(
681
- "div",
682
- {
683
- className: "sph-menu__separator",
684
- role: "separator"
685
- },
686
- `separator-${index}`
687
- ) : /* @__PURE__ */ jsxs8(
688
- "button",
689
- {
690
- id: `${menuId}-${index}`,
691
- type: "button",
692
- role: "menuitem",
693
- className: "sph-menu__item",
694
- "data-danger": item.danger || void 0,
695
- disabled: item.disabled,
696
- onClick: () => choose(item),
697
- onKeyDown: (event) => handleItemKeyDown(event, index),
698
- children: [
699
- /* @__PURE__ */ jsxs8("span", { className: "sph-menu__item-label", children: [
700
- node(item.icon),
701
- /* @__PURE__ */ jsx18("span", { children: node(item.label) })
702
- ] }),
703
- item.shortcut !== void 0 && /* @__PURE__ */ jsx18("span", { className: "sph-menu__shortcut", children: node(item.shortcut) })
704
- ]
705
- },
706
- item.value ?? `item-${index}`
707
- )
708
- ) })
1793
+ ),
1794
+ present && portaled && createPortal3(
1795
+ /* @__PURE__ */ jsx28(
1796
+ "div",
1797
+ {
1798
+ id: `${menuId}-menu`,
1799
+ ref: contentRef,
1800
+ className: "sph-menu__content",
1801
+ "data-align": align,
1802
+ "data-state": state,
1803
+ role: "menu",
1804
+ children: items.map(
1805
+ (item, index) => item.separator ? /* @__PURE__ */ jsx28(
1806
+ "div",
1807
+ {
1808
+ className: "sph-menu__separator",
1809
+ role: "separator"
1810
+ },
1811
+ `separator-${index}`
1812
+ ) : /* @__PURE__ */ jsxs15(
1813
+ "button",
1814
+ {
1815
+ id: `${menuId}-${index}`,
1816
+ type: "button",
1817
+ role: "menuitem",
1818
+ className: "sph-menu__item",
1819
+ "data-danger": item.danger || void 0,
1820
+ disabled: item.disabled,
1821
+ onClick: () => choose(item),
1822
+ onKeyDown: (event) => handleItemKeyDown(event, index),
1823
+ children: [
1824
+ /* @__PURE__ */ jsxs15("span", { className: "sph-menu__item-label", children: [
1825
+ node(item.icon),
1826
+ /* @__PURE__ */ jsx28("span", { children: node(item.label) })
1827
+ ] }),
1828
+ item.shortcut !== void 0 && /* @__PURE__ */ jsx28("span", { className: "sph-menu__shortcut", children: node(item.shortcut) })
1829
+ ]
1830
+ },
1831
+ item.value ?? `item-${index}`
1832
+ )
1833
+ )
1834
+ }
1835
+ ),
1836
+ document.body
1837
+ )
709
1838
  ] });
710
1839
  }
711
1840
 
712
1841
  // src/molecules/Popover/index.tsx
713
- import { cn as cn19 } from "cn";
714
- import { useEffect as useEffect3, useId as useId5, useRef as useRef3, useState as useState2 } from "react";
715
- import { jsx as jsx19, jsxs as jsxs9 } from "react/jsx-runtime";
1842
+ import { cn as cn28 } from "cn";
1843
+ import { useEffect as useEffect10, useId as useId9, useRef as useRef7, useState as useState9 } from "react";
1844
+ import { createPortal as createPortal4 } from "react-dom";
1845
+ import { jsx as jsx29, jsxs as jsxs16 } from "react/jsx-runtime";
716
1846
  function Popover({
717
1847
  trigger,
718
1848
  children,
@@ -724,18 +1854,32 @@ function Popover({
724
1854
  disabled = false,
725
1855
  className
726
1856
  }) {
727
- const id = useId5();
728
- const rootRef = useRef3(null);
729
- const [uncontrolledOpen, setUncontrolledOpen] = useState2(defaultOpen);
1857
+ const id = useId9();
1858
+ const rootRef = useRef7(null);
1859
+ const triggerRef = useRef7(null);
1860
+ const [uncontrolledOpen, setUncontrolledOpen] = useState9(defaultOpen);
730
1861
  const isOpen = open ?? uncontrolledOpen;
1862
+ const {
1863
+ present,
1864
+ state,
1865
+ ref: contentRef
1866
+ } = usePresence(isOpen, "fast");
1867
+ const portaled = usePortaled();
1868
+ useOverlayPosition(triggerRef, contentRef, present && portaled, {
1869
+ side: "bottom",
1870
+ align: placement,
1871
+ offset: 6
1872
+ });
731
1873
  const setOpen = (next) => {
732
1874
  setUncontrolledOpen(next);
733
1875
  onOpenChange?.(next);
734
1876
  };
735
- useEffect3(() => {
1877
+ useEffect10(() => {
736
1878
  if (!isOpen) return;
737
1879
  const handlePointerDown = (event) => {
738
- if (!rootRef.current?.contains(event.target)) setOpen(false);
1880
+ const target = event.target;
1881
+ if (!rootRef.current?.contains(target) && !contentRef.current?.contains(target))
1882
+ setOpen(false);
739
1883
  };
740
1884
  const handleKeyDown = (event) => {
741
1885
  if (event.key === "Escape") setOpen(false);
@@ -753,17 +1897,18 @@ function Popover({
753
1897
  setOpen(true);
754
1898
  }
755
1899
  };
756
- return /* @__PURE__ */ jsxs9(
1900
+ return /* @__PURE__ */ jsxs16(
757
1901
  "div",
758
1902
  {
759
1903
  ref: rootRef,
760
- className: cn19("sph-popover", className),
1904
+ className: cn28("sph-popover", className),
761
1905
  "data-open": isOpen || void 0,
762
1906
  children: [
763
- /* @__PURE__ */ jsx19(
1907
+ /* @__PURE__ */ jsx29(
764
1908
  "button",
765
1909
  {
766
1910
  type: "button",
1911
+ ref: triggerRef,
767
1912
  className: "sph-popover__trigger",
768
1913
  "aria-haspopup": "dialog",
769
1914
  "aria-expanded": isOpen,
@@ -774,16 +1919,21 @@ function Popover({
774
1919
  children: node(trigger)
775
1920
  }
776
1921
  ),
777
- isOpen && /* @__PURE__ */ jsx19(
778
- "div",
779
- {
780
- id,
781
- className: "sph-popover__content",
782
- "data-placement": placement,
783
- role: "dialog",
784
- "aria-label": ariaLabel,
785
- children: node(children)
786
- }
1922
+ present && portaled && createPortal4(
1923
+ /* @__PURE__ */ jsx29(
1924
+ "div",
1925
+ {
1926
+ id,
1927
+ ref: contentRef,
1928
+ className: "sph-popover__content",
1929
+ "data-placement": placement,
1930
+ "data-state": state,
1931
+ role: "dialog",
1932
+ "aria-label": ariaLabel,
1933
+ children: node(children)
1934
+ }
1935
+ ),
1936
+ document.body
787
1937
  )
788
1938
  ]
789
1939
  }
@@ -791,9 +1941,9 @@ function Popover({
791
1941
  }
792
1942
 
793
1943
  // src/molecules/RadioGroup/index.tsx
794
- import { cn as cn20 } from "cn";
795
- import { useId as useId6 } from "react";
796
- import { jsx as jsx20, jsxs as jsxs10 } from "react/jsx-runtime";
1944
+ import { cn as cn29 } from "cn";
1945
+ import { useId as useId10 } from "react";
1946
+ import { jsx as jsx30, jsxs as jsxs17 } from "react/jsx-runtime";
797
1947
  function RadioGroup({
798
1948
  value,
799
1949
  options,
@@ -806,29 +1956,29 @@ function RadioGroup({
806
1956
  disabled = false,
807
1957
  className
808
1958
  }) {
809
- const groupId = useId6();
1959
+ const groupId = useId10();
810
1960
  const descriptionId = description ? `${groupId}-description` : void 0;
811
- return /* @__PURE__ */ jsxs10(
1961
+ return /* @__PURE__ */ jsxs17(
812
1962
  "fieldset",
813
1963
  {
814
- className: cn20("sph-radio-group", className),
1964
+ className: cn29("sph-radio-group", className),
815
1965
  "data-size": size,
816
1966
  "data-orientation": orientation,
817
1967
  disabled,
818
1968
  "aria-describedby": descriptionId,
819
1969
  children: [
820
- label !== void 0 && /* @__PURE__ */ jsx20("legend", { className: "sph-radio-group__legend", children: node(label) }),
821
- description !== void 0 && /* @__PURE__ */ jsx20("p", { id: descriptionId, className: "sph-radio-group__description", children: node(description) }),
822
- /* @__PURE__ */ jsx20("div", { className: "sph-radio-group__options", children: options.map((option, index) => {
1970
+ label !== void 0 && /* @__PURE__ */ jsx30("legend", { className: "sph-radio-group__legend", children: node(label) }),
1971
+ description !== void 0 && /* @__PURE__ */ jsx30("p", { id: descriptionId, className: "sph-radio-group__description", children: node(description) }),
1972
+ /* @__PURE__ */ jsx30("div", { className: "sph-radio-group__options", children: options.map((option, index) => {
823
1973
  const optionId = `${groupId}-${index}`;
824
1974
  const optionDescriptionId = option.description ? `${optionId}-description` : void 0;
825
- return /* @__PURE__ */ jsxs10(
1975
+ return /* @__PURE__ */ jsxs17(
826
1976
  "label",
827
1977
  {
828
1978
  className: "sph-radio-group__option",
829
1979
  "data-disabled": option.disabled || void 0,
830
1980
  children: [
831
- /* @__PURE__ */ jsx20(
1981
+ /* @__PURE__ */ jsx30(
832
1982
  "input",
833
1983
  {
834
1984
  id: optionId,
@@ -841,10 +1991,10 @@ function RadioGroup({
841
1991
  onChange: () => onValueChange(option.value)
842
1992
  }
843
1993
  ),
844
- /* @__PURE__ */ jsx20("span", { className: "sph-radio-group__control", "aria-hidden": "true" }),
845
- /* @__PURE__ */ jsxs10("span", { className: "sph-radio-group__content", children: [
846
- /* @__PURE__ */ jsx20("span", { className: "sph-radio-group__label", children: node(option.label) }),
847
- option.description !== void 0 && /* @__PURE__ */ jsx20(
1994
+ /* @__PURE__ */ jsx30("span", { className: "sph-radio-group__control", "aria-hidden": "true" }),
1995
+ /* @__PURE__ */ jsxs17("span", { className: "sph-radio-group__content", children: [
1996
+ /* @__PURE__ */ jsx30("span", { className: "sph-radio-group__label", children: node(option.label) }),
1997
+ option.description !== void 0 && /* @__PURE__ */ jsx30(
848
1998
  "span",
849
1999
  {
850
2000
  id: optionDescriptionId,
@@ -864,10 +2014,11 @@ function RadioGroup({
864
2014
  }
865
2015
 
866
2016
  // src/molecules/Select/index.tsx
867
- import { CaretDownIcon, CheckIcon as CheckIcon3 } from "@phosphor-icons/react";
868
- import { cn as cn21 } from "cn";
869
- import { useEffect as useEffect4, useId as useId7, useRef as useRef4, useState as useState3 } from "react";
870
- import { jsx as jsx21, jsxs as jsxs11 } from "react/jsx-runtime";
2017
+ import { CaretDownIcon as CaretDownIcon2, CheckIcon as CheckIcon4 } from "@phosphor-icons/react";
2018
+ import { cn as cn30 } from "cn";
2019
+ import { useEffect as useEffect11, useId as useId11, useRef as useRef8, useState as useState10 } from "react";
2020
+ import { createPortal as createPortal5 } from "react-dom";
2021
+ import { jsx as jsx31, jsxs as jsxs18 } from "react/jsx-runtime";
871
2022
  function nextEnabled(options, start, direction) {
872
2023
  if (options.length === 0) return -1;
873
2024
  for (let step = 1; step <= options.length; step += 1) {
@@ -888,25 +2039,40 @@ function Select({
888
2039
  disabled = false,
889
2040
  size = "md"
890
2041
  }) {
891
- const generatedId = useId7();
2042
+ const generatedId = useId11();
892
2043
  const triggerId = id ?? `sph-select-${generatedId}`;
893
2044
  const listboxId = `${triggerId}-listbox`;
894
- const rootRef = useRef4(null);
2045
+ const rootRef = useRef8(null);
2046
+ const triggerRef = useRef8(null);
895
2047
  const selectedIndex = options.findIndex((option) => option.value === value);
896
2048
  const firstEnabled2 = options.findIndex((option) => !option.disabled);
897
- const [open, setOpen] = useState3(false);
898
- const [activeIndex, setActiveIndex] = useState3(
2049
+ const [open, setOpen] = useState10(false);
2050
+ const {
2051
+ present,
2052
+ state,
2053
+ ref: listboxRef
2054
+ } = usePresence(open, "fast");
2055
+ const portaled = usePortaled();
2056
+ useOverlayPosition(triggerRef, listboxRef, present && portaled, {
2057
+ side: "bottom",
2058
+ align: "start",
2059
+ offset: 4,
2060
+ matchWidth: true
2061
+ });
2062
+ const [activeIndex, setActiveIndex] = useState10(
899
2063
  selectedIndex >= 0 ? selectedIndex : firstEnabled2
900
2064
  );
901
- useEffect4(() => {
2065
+ useEffect11(() => {
902
2066
  if (!open) return;
903
2067
  const close = (event) => {
904
- if (!rootRef.current?.contains(event.target)) setOpen(false);
2068
+ const target = event.target;
2069
+ if (!rootRef.current?.contains(target) && !listboxRef.current?.contains(target))
2070
+ setOpen(false);
905
2071
  };
906
2072
  document.addEventListener("pointerdown", close);
907
2073
  return () => document.removeEventListener("pointerdown", close);
908
2074
  }, [open]);
909
- useEffect4(() => {
2075
+ useEffect11(() => {
910
2076
  if (!open)
911
2077
  setActiveIndex(selectedIndex >= 0 ? selectedIndex : firstEnabled2);
912
2078
  }, [firstEnabled2, open, selectedIndex]);
@@ -946,12 +2112,13 @@ function Select({
946
2112
  }
947
2113
  }
948
2114
  const selected = selectedIndex >= 0 ? options[selectedIndex] : void 0;
949
- return /* @__PURE__ */ jsxs11("div", { ref: rootRef, className: cn21("sph-select", className), "data-size": size, children: [
950
- /* @__PURE__ */ jsxs11(
2115
+ return /* @__PURE__ */ jsxs18("div", { ref: rootRef, className: cn30("sph-select", className), "data-size": size, children: [
2116
+ /* @__PURE__ */ jsxs18(
951
2117
  "button",
952
2118
  {
953
2119
  id: triggerId,
954
2120
  type: "button",
2121
+ ref: triggerRef,
955
2122
  className: "sph-select__trigger",
956
2123
  "aria-label": ariaLabel,
957
2124
  "aria-labelledby": ariaLabelledBy,
@@ -963,9 +2130,9 @@ function Select({
963
2130
  onClick: () => setOpen((current) => !current),
964
2131
  onKeyDown: handleKeyDown,
965
2132
  children: [
966
- /* @__PURE__ */ jsx21("span", { className: "sph-select__value", children: node(selected?.label ?? placeholder) }),
967
- /* @__PURE__ */ jsx21(
968
- CaretDownIcon,
2133
+ /* @__PURE__ */ jsx31("span", { className: "sph-select__value", children: node(selected?.label ?? placeholder) }),
2134
+ /* @__PURE__ */ jsx31(
2135
+ CaretDownIcon2,
969
2136
  {
970
2137
  className: "sph-select__caret",
971
2138
  style: { transform: open ? "rotate(180deg)" : void 0 },
@@ -975,32 +2142,45 @@ function Select({
975
2142
  ]
976
2143
  }
977
2144
  ),
978
- open && /* @__PURE__ */ jsx21("div", { id: listboxId, role: "listbox", className: "sph-select__listbox", children: options.map((option, index) => /* @__PURE__ */ jsxs11(
979
- "button",
980
- {
981
- id: `${listboxId}-${index}`,
982
- type: "button",
983
- role: "option",
984
- "aria-selected": index === selectedIndex,
985
- disabled: option.disabled,
986
- className: "sph-select__option",
987
- "data-active": index === activeIndex || void 0,
988
- onPointerMove: () => !option.disabled && setActiveIndex(index),
989
- onClick: () => choose(index),
990
- children: [
991
- /* @__PURE__ */ jsx21("span", { children: node(option.label) }),
992
- index === selectedIndex && /* @__PURE__ */ jsx21(CheckIcon3, { className: "sph-select__check", "aria-hidden": "true" })
993
- ]
994
- },
995
- option.value
996
- )) })
2145
+ present && portaled && createPortal5(
2146
+ /* @__PURE__ */ jsx31(
2147
+ "div",
2148
+ {
2149
+ id: listboxId,
2150
+ ref: listboxRef,
2151
+ role: "listbox",
2152
+ className: "sph-select__listbox",
2153
+ "data-state": state,
2154
+ children: options.map((option, index) => /* @__PURE__ */ jsxs18(
2155
+ "button",
2156
+ {
2157
+ id: `${listboxId}-${index}`,
2158
+ type: "button",
2159
+ role: "option",
2160
+ "aria-selected": index === selectedIndex,
2161
+ disabled: option.disabled,
2162
+ className: "sph-select__option",
2163
+ "data-active": index === activeIndex || void 0,
2164
+ onPointerMove: () => !option.disabled && setActiveIndex(index),
2165
+ onClick: () => choose(index),
2166
+ children: [
2167
+ /* @__PURE__ */ jsx31("span", { children: node(option.label) }),
2168
+ index === selectedIndex && /* @__PURE__ */ jsx31(CheckIcon4, { className: "sph-select__check", "aria-hidden": "true" })
2169
+ ]
2170
+ },
2171
+ option.value
2172
+ ))
2173
+ }
2174
+ ),
2175
+ document.body
2176
+ )
997
2177
  ] });
998
2178
  }
999
2179
 
1000
2180
  // src/molecules/Tabs/index.tsx
1001
- import { cn as cn22 } from "cn";
1002
- import { useId as useId8, useState as useState4 } from "react";
1003
- import { jsx as jsx22, jsxs as jsxs12 } from "react/jsx-runtime";
2181
+ import { cn as cn31 } from "cn";
2182
+ import { useId as useId12, useState as useState11 } from "react";
2183
+ import { jsx as jsx32, jsxs as jsxs19 } from "react/jsx-runtime";
1004
2184
  function firstEnabled(items) {
1005
2185
  return items.find((item) => !item.disabled)?.value;
1006
2186
  }
@@ -1014,8 +2194,8 @@ function Tabs({
1014
2194
  orientation = "horizontal",
1015
2195
  className
1016
2196
  }) {
1017
- const generatedId = useId8();
1018
- const [uncontrolledValue, setUncontrolledValue] = useState4(
2197
+ const generatedId = useId12();
2198
+ const [uncontrolledValue, setUncontrolledValue] = useState11(
1019
2199
  defaultValue ?? firstEnabled(items) ?? ""
1020
2200
  );
1021
2201
  const activeValue = value ?? uncontrolledValue;
@@ -1043,8 +2223,8 @@ function Tabs({
1043
2223
  }
1044
2224
  };
1045
2225
  const panelId = `${generatedId}-panel`;
1046
- return /* @__PURE__ */ jsxs12("div", { className: cn22("sph-tabs", className), "data-orientation": orientation, children: [
1047
- /* @__PURE__ */ jsx22(
2226
+ return /* @__PURE__ */ jsxs19("div", { className: cn31("sph-tabs", className), "data-orientation": orientation, children: [
2227
+ /* @__PURE__ */ jsx32(
1048
2228
  "div",
1049
2229
  {
1050
2230
  className: "sph-tabs__list",
@@ -1053,7 +2233,7 @@ function Tabs({
1053
2233
  "aria-orientation": orientation,
1054
2234
  children: items.map((item, index) => {
1055
2235
  const selected = item.value === activeValue;
1056
- return /* @__PURE__ */ jsx22(
2236
+ return /* @__PURE__ */ jsx32(
1057
2237
  "button",
1058
2238
  {
1059
2239
  id: `${generatedId}-${index}`,
@@ -1074,7 +2254,7 @@ function Tabs({
1074
2254
  })
1075
2255
  }
1076
2256
  ),
1077
- activeItem && (activeItem.content !== void 0 || children !== void 0) && /* @__PURE__ */ jsx22(
2257
+ activeItem && (activeItem.content !== void 0 || children !== void 0) && /* @__PURE__ */ jsx32(
1078
2258
  "div",
1079
2259
  {
1080
2260
  id: panelId,
@@ -1082,25 +2262,26 @@ function Tabs({
1082
2262
  role: "tabpanel",
1083
2263
  "aria-labelledby": `${generatedId}-${activeIndex}`,
1084
2264
  children: node(activeItem.content ?? children)
1085
- }
2265
+ },
2266
+ activeItem.content !== void 0 ? activeValue : "static"
1086
2267
  )
1087
2268
  ] });
1088
2269
  }
1089
2270
 
1090
2271
  // src/molecules/Toast/index.tsx
1091
- import { CheckIcon as CheckIcon4, InfoIcon as InfoIcon2, WarningIcon as WarningIcon2, XIcon as XIcon3 } from "@phosphor-icons/react";
1092
- import { cn as cn23 } from "cn";
1093
- import { useEffect as useEffect5 } from "react";
2272
+ import { CheckIcon as CheckIcon5, InfoIcon as InfoIcon2, WarningIcon as WarningIcon2, XIcon as XIcon5 } from "@phosphor-icons/react";
2273
+ import { cn as cn32 } from "cn";
2274
+ import { useEffect as useEffect12 } from "react";
1094
2275
  import {
1095
2276
  Toaster as SonnerToaster,
1096
2277
  toast
1097
2278
  } from "sonner";
1098
- import { jsx as jsx23 } from "react/jsx-runtime";
2279
+ import { jsx as jsx33 } from "react/jsx-runtime";
1099
2280
  var toastIcons = {
1100
- success: /* @__PURE__ */ jsx23("span", { className: "sph-toast__status-icon", children: /* @__PURE__ */ jsx23(CheckIcon4, { size: 20 }) }),
1101
- info: /* @__PURE__ */ jsx23("span", { className: "sph-toast__status-icon", children: /* @__PURE__ */ jsx23(InfoIcon2, { size: 20 }) }),
1102
- warning: /* @__PURE__ */ jsx23("span", { className: "sph-toast__status-icon", children: /* @__PURE__ */ jsx23(WarningIcon2, { size: 20 }) }),
1103
- error: /* @__PURE__ */ jsx23("span", { className: "sph-toast__status-icon", children: /* @__PURE__ */ jsx23(XIcon3, { size: 20 }) })
2281
+ success: /* @__PURE__ */ jsx33("span", { className: "sph-toast__status-icon", children: /* @__PURE__ */ jsx33(CheckIcon5, { size: 20 }) }),
2282
+ info: /* @__PURE__ */ jsx33("span", { className: "sph-toast__status-icon", children: /* @__PURE__ */ jsx33(InfoIcon2, { size: 20 }) }),
2283
+ warning: /* @__PURE__ */ jsx33("span", { className: "sph-toast__status-icon", children: /* @__PURE__ */ jsx33(WarningIcon2, { size: 20 }) }),
2284
+ error: /* @__PURE__ */ jsx33("span", { className: "sph-toast__status-icon", children: /* @__PURE__ */ jsx33(XIcon5, { size: 20 }) })
1104
2285
  };
1105
2286
  function Toast({
1106
2287
  open = true,
@@ -1113,7 +2294,7 @@ function Toast({
1113
2294
  closeButton = true,
1114
2295
  className
1115
2296
  }) {
1116
- useEffect5(() => {
2297
+ useEffect12(() => {
1117
2298
  if (!open) return;
1118
2299
  const options = {
1119
2300
  id,
@@ -1121,7 +2302,7 @@ function Toast({
1121
2302
  closeButton,
1122
2303
  description: node(description),
1123
2304
  duration,
1124
- className: cn23("sph-toast", className),
2305
+ className: cn32("sph-toast", className),
1125
2306
  unstyled: true
1126
2307
  };
1127
2308
  const toastId = variant === "success" ? toast.success(node(title), options) : variant === "warning" ? toast.warning(node(title), options) : variant === "danger" ? toast.error(node(title), options) : toast(node(title), options);
@@ -1141,7 +2322,7 @@ function ToastViewport({
1141
2322
  expand = false,
1142
2323
  ...props
1143
2324
  }) {
1144
- return /* @__PURE__ */ jsx23(
2325
+ return /* @__PURE__ */ jsx33(
1145
2326
  SonnerToaster,
1146
2327
  {
1147
2328
  ...node(props),
@@ -1151,12 +2332,12 @@ function ToastViewport({
1151
2332
  icons: toastIcons,
1152
2333
  containerAriaLabel: label,
1153
2334
  expand,
1154
- className: cn23("sph-toast-viewport", className),
2335
+ className: cn32("sph-toast-viewport", className),
1155
2336
  toastOptions: {
1156
2337
  ...toastOptions,
1157
2338
  closeButton: toastOptions?.closeButton ?? closeButton,
1158
2339
  closeButtonAriaLabel: toastOptions?.closeButtonAriaLabel ?? "Dismiss notification",
1159
- className: cn23("sph-toast", toastOptions?.className),
2340
+ className: cn32("sph-toast", toastOptions?.className),
1160
2341
  unstyled: true
1161
2342
  }
1162
2343
  }
@@ -1164,8 +2345,8 @@ function ToastViewport({
1164
2345
  }
1165
2346
 
1166
2347
  // src/molecules/Toolbar/index.tsx
1167
- import { cn as cn24 } from "cn";
1168
- import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
2348
+ import { cn as cn33 } from "cn";
2349
+ import { jsx as jsx34, jsxs as jsxs20 } from "react/jsx-runtime";
1169
2350
  function Toolbar({
1170
2351
  label = "Toolbar",
1171
2352
  orientation = "horizontal",
@@ -1176,29 +2357,30 @@ function Toolbar({
1176
2357
  className,
1177
2358
  ...props
1178
2359
  }) {
1179
- return /* @__PURE__ */ jsxs13(
2360
+ return /* @__PURE__ */ jsxs20(
1180
2361
  "div",
1181
2362
  {
1182
2363
  ...node(props),
1183
- className: cn24("sph-toolbar", className),
2364
+ className: cn33("sph-toolbar", className),
1184
2365
  "data-orientation": orientation,
1185
2366
  "data-size": size,
1186
2367
  role: "toolbar",
1187
2368
  "aria-label": label,
1188
2369
  "aria-orientation": orientation,
1189
2370
  children: [
1190
- start !== void 0 && /* @__PURE__ */ jsx24("div", { className: "sph-toolbar__start", children: node(start) }),
1191
- children !== void 0 && /* @__PURE__ */ jsx24("div", { className: "sph-toolbar__content", children: node(children) }),
1192
- end !== void 0 && /* @__PURE__ */ jsx24("div", { className: "sph-toolbar__end", children: node(end) })
2371
+ start !== void 0 && /* @__PURE__ */ jsx34("div", { className: "sph-toolbar__start", children: node(start) }),
2372
+ children !== void 0 && /* @__PURE__ */ jsx34("div", { className: "sph-toolbar__content", children: node(children) }),
2373
+ end !== void 0 && /* @__PURE__ */ jsx34("div", { className: "sph-toolbar__end", children: node(end) })
1193
2374
  ]
1194
2375
  }
1195
2376
  );
1196
2377
  }
1197
2378
 
1198
2379
  // src/molecules/Tooltip/index.tsx
1199
- import { cn as cn25 } from "cn";
1200
- import { useId as useId9, useRef as useRef5, useState as useState5 } from "react";
1201
- import { jsx as jsx25, jsxs as jsxs14 } from "react/jsx-runtime";
2380
+ import { cn as cn34 } from "cn";
2381
+ import { useId as useId13, useRef as useRef9, useState as useState12 } from "react";
2382
+ import { createPortal as createPortal6 } from "react-dom";
2383
+ import { jsx as jsx35, jsxs as jsxs21 } from "react/jsx-runtime";
1202
2384
  function Tooltip({
1203
2385
  content,
1204
2386
  children,
@@ -1207,9 +2389,21 @@ function Tooltip({
1207
2389
  disabled = false,
1208
2390
  className
1209
2391
  }) {
1210
- const id = useId9();
1211
- const timerRef = useRef5(void 0);
1212
- const [visible, setVisible] = useState5(false);
2392
+ const id = useId13();
2393
+ const timerRef = useRef9(void 0);
2394
+ const triggerRef = useRef9(null);
2395
+ const [visible, setVisible] = useState12(false);
2396
+ const {
2397
+ present,
2398
+ state,
2399
+ ref: contentRef
2400
+ } = usePresence(visible, "fast");
2401
+ const portaled = usePortaled();
2402
+ useOverlayPosition(triggerRef, contentRef, present && portaled, {
2403
+ side,
2404
+ align: "center",
2405
+ offset: 6
2406
+ });
1213
2407
  const show = () => {
1214
2408
  if (disabled) return;
1215
2409
  timerRef.current = setTimeout(() => setVisible(true), delay);
@@ -1218,45 +2412,522 @@ function Tooltip({
1218
2412
  if (timerRef.current) clearTimeout(timerRef.current);
1219
2413
  setVisible(false);
1220
2414
  };
1221
- return /* @__PURE__ */ jsxs14(
2415
+ return /* @__PURE__ */ jsxs21(
1222
2416
  "span",
1223
2417
  {
1224
- className: cn25("sph-tooltip", className),
2418
+ className: cn34("sph-tooltip", className),
1225
2419
  onMouseEnter: show,
1226
2420
  onMouseLeave: hide,
1227
2421
  onFocus: show,
1228
2422
  onBlur: hide,
1229
2423
  children: [
1230
- /* @__PURE__ */ jsx25(
2424
+ /* @__PURE__ */ jsx35(
1231
2425
  "span",
1232
2426
  {
2427
+ ref: triggerRef,
1233
2428
  className: "sph-tooltip__trigger",
1234
2429
  "aria-describedby": visible ? id : void 0,
1235
2430
  children: node(children)
1236
2431
  }
1237
2432
  ),
1238
- /* @__PURE__ */ jsx25(
1239
- "span",
1240
- {
1241
- id,
1242
- className: "sph-tooltip__content",
1243
- "data-side": side,
1244
- "data-visible": visible || void 0,
1245
- role: "tooltip",
1246
- hidden: !visible,
1247
- children: node(content)
1248
- }
2433
+ present && portaled && createPortal6(
2434
+ /* @__PURE__ */ jsx35(
2435
+ "span",
2436
+ {
2437
+ id,
2438
+ ref: contentRef,
2439
+ className: "sph-tooltip__content",
2440
+ "data-side": side,
2441
+ "data-state": state,
2442
+ role: "tooltip",
2443
+ children: node(content)
2444
+ }
2445
+ ),
2446
+ document.body
1249
2447
  )
1250
2448
  ]
1251
2449
  }
1252
2450
  );
1253
2451
  }
1254
2452
 
2453
+ // src/organisms/CommandPalette/index.tsx
2454
+ import { CaretRightIcon as CaretRightIcon2, MagnifyingGlassIcon as MagnifyingGlassIcon2 } from "@phosphor-icons/react";
2455
+ import { cn as cn35 } from "cn";
2456
+ import { useEffect as useEffect13, useId as useId14, useMemo as useMemo2, useRef as useRef10, useState as useState13 } from "react";
2457
+ import { createPortal as createPortal7 } from "react-dom";
2458
+ import { jsx as jsx36, jsxs as jsxs22 } from "react/jsx-runtime";
2459
+ function replay(element, className) {
2460
+ if (!element) return;
2461
+ element.classList.remove(className);
2462
+ void element.offsetWidth;
2463
+ element.classList.add(className);
2464
+ }
2465
+ function readableLabel2(item) {
2466
+ return typeof item.label === "string" ? item.label : "";
2467
+ }
2468
+ function crumbLabel(item, fallback) {
2469
+ const label = readableLabel2(item);
2470
+ if (label) return label;
2471
+ if (typeof item.value === "string" && item.value) return item.value;
2472
+ return fallback;
2473
+ }
2474
+ function resolveActive2(options, wanted) {
2475
+ if (options.length === 0) return -1;
2476
+ const start = Math.min(Math.max(wanted, 0), options.length - 1);
2477
+ const enabled = (index) => !options[index]?.item.disabled;
2478
+ if (enabled(start)) return start;
2479
+ for (let index = start + 1; index < options.length; index += 1)
2480
+ if (enabled(index)) return index;
2481
+ for (let index = 0; index < start; index += 1)
2482
+ if (enabled(index)) return index;
2483
+ return start;
2484
+ }
2485
+ function CommandPalette({
2486
+ open,
2487
+ onOpenChange,
2488
+ items,
2489
+ onSelect,
2490
+ placeholder = "Type a command or search\u2026",
2491
+ emptyMessage = "No results.",
2492
+ homeLabel = "Home",
2493
+ ariaLabel = "Command palette",
2494
+ hotkey = false,
2495
+ className
2496
+ }) {
2497
+ const listId = useId14();
2498
+ const rootRef = useRef10(null);
2499
+ const panelRef = useRef10(null);
2500
+ const bodyRef = useRef10(null);
2501
+ const innerRef = useRef10(null);
2502
+ const trackRef = useRef10(null);
2503
+ const indicatorRef = useRef10(null);
2504
+ const inputRef = useRef10(null);
2505
+ const previousPathLength = useRef10(null);
2506
+ const heightCleanup = useRef10(null);
2507
+ const [path, setPath] = useState13([]);
2508
+ const [query, setQuery] = useState13("");
2509
+ const [activeState, setActive] = useState13(0);
2510
+ const {
2511
+ present,
2512
+ state,
2513
+ ref: backdropRef
2514
+ } = usePresence(open, "normal");
2515
+ const portaled = usePortaled();
2516
+ const visible = present && portaled;
2517
+ useFocusTrap(rootRef, visible, inputRef);
2518
+ const levelItems = path.length > 0 ? path[path.length - 1]?.children ?? [] : items;
2519
+ const { sections, options } = useMemo2(() => {
2520
+ const read = (item) => ({
2521
+ label: readableLabel2(item),
2522
+ keywords: [
2523
+ item.keywords ?? "",
2524
+ ...(item.children ?? []).map((child) => readableLabel2(child))
2525
+ ].filter(Boolean).join(" ")
2526
+ });
2527
+ const filtered = filterItems(levelItems, query, read);
2528
+ const ungrouped = filtered.filter(
2529
+ (entry) => entry.item.group === void 0
2530
+ );
2531
+ const groupNames = [
2532
+ ...new Set(
2533
+ filtered.map((entry) => entry.item.group).filter((name) => name !== void 0)
2534
+ )
2535
+ ];
2536
+ const grouped = groupNames.map((name) => ({
2537
+ heading: name,
2538
+ entries: filtered.filter((entry) => entry.item.group === name)
2539
+ }));
2540
+ const raw = [
2541
+ ...ungrouped.length > 0 ? [{ heading: void 0, entries: ungrouped }] : [],
2542
+ ...grouped
2543
+ ];
2544
+ let order = 0;
2545
+ const built = raw.map((section) => ({
2546
+ heading: section.heading,
2547
+ entries: section.entries.map((entry) => ({
2548
+ item: entry.item,
2549
+ indexes: entry.indexes,
2550
+ activeId: `${listId}-option-${order}`,
2551
+ index: order++
2552
+ }))
2553
+ }));
2554
+ return { sections: built, options: built.flatMap((s) => s.entries) };
2555
+ }, [levelItems, listId, query]);
2556
+ const active = resolveActive2(options, activeState);
2557
+ const close = () => onOpenChange(false);
2558
+ const back = () => {
2559
+ if (query) setQuery("");
2560
+ else if (path.length > 0) setPath(path.slice(0, -1));
2561
+ else close();
2562
+ };
2563
+ const activate = (index) => {
2564
+ const item = options[index]?.item;
2565
+ if (!item || item.disabled) return;
2566
+ if (item.children && item.children.length > 0) {
2567
+ setPath([...path, item]);
2568
+ setQuery("");
2569
+ return;
2570
+ }
2571
+ onSelect?.(item);
2572
+ close();
2573
+ };
2574
+ const move = (direction) => {
2575
+ if (options.length === 0) return;
2576
+ let index = active < 0 ? 0 : active;
2577
+ for (let step = 0; step < options.length; step += 1) {
2578
+ index = (index + direction + options.length) % options.length;
2579
+ if (!options[index]?.item.disabled) {
2580
+ setActive(index);
2581
+ return;
2582
+ }
2583
+ }
2584
+ };
2585
+ const goToLevel = (length) => {
2586
+ setPath(path.slice(0, length));
2587
+ setQuery("");
2588
+ };
2589
+ const handleInputKeyDown = (event) => {
2590
+ if (event.key === "ArrowDown") {
2591
+ event.preventDefault();
2592
+ move(1);
2593
+ } else if (event.key === "ArrowUp") {
2594
+ event.preventDefault();
2595
+ move(-1);
2596
+ } else if (event.key === "Home") {
2597
+ event.preventDefault();
2598
+ setActive(0);
2599
+ } else if (event.key === "End") {
2600
+ event.preventDefault();
2601
+ setActive(options.length - 1);
2602
+ } else if (event.key === "Enter") {
2603
+ event.preventDefault();
2604
+ if (active >= 0) activate(active);
2605
+ }
2606
+ };
2607
+ useEffect13(() => {
2608
+ if (!open) {
2609
+ setPath([]);
2610
+ setQuery("");
2611
+ setActive(0);
2612
+ }
2613
+ }, [open]);
2614
+ useEffect13(() => {
2615
+ inputRef.current?.setAttribute("spellcheck", "false");
2616
+ }, [visible]);
2617
+ useEffect13(() => {
2618
+ setActive(0);
2619
+ }, [path, query]);
2620
+ useEffect13(() => {
2621
+ if (!hotkey) return;
2622
+ const handleHotkey = (event) => {
2623
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
2624
+ event.preventDefault();
2625
+ onOpenChange(!open);
2626
+ }
2627
+ };
2628
+ document.addEventListener("keydown", handleHotkey);
2629
+ return () => document.removeEventListener("keydown", handleHotkey);
2630
+ }, [hotkey, onOpenChange, open]);
2631
+ useEffect13(() => {
2632
+ if (!visible) return;
2633
+ const handleKeyDown = (event) => {
2634
+ if (event.key !== "Escape") return;
2635
+ event.preventDefault();
2636
+ if (query) setQuery("");
2637
+ else if (path.length > 0) setPath(path.slice(0, -1));
2638
+ else close();
2639
+ };
2640
+ const handlePointerDown = (event) => {
2641
+ const target = event.target;
2642
+ if (!rootRef.current?.contains(target)) close();
2643
+ };
2644
+ document.addEventListener("keydown", handleKeyDown);
2645
+ document.addEventListener("pointerdown", handlePointerDown);
2646
+ return () => {
2647
+ document.removeEventListener("keydown", handleKeyDown);
2648
+ document.removeEventListener("pointerdown", handlePointerDown);
2649
+ };
2650
+ }, [onOpenChange, path, query, visible]);
2651
+ useEffect13(() => {
2652
+ if (!visible || active < 0) return;
2653
+ const option = trackRef.current?.querySelector(
2654
+ '[data-active="true"]'
2655
+ );
2656
+ option?.scrollIntoView({ block: "nearest" });
2657
+ }, [active, visible]);
2658
+ useIsoLayoutEffect(() => {
2659
+ const indicator = indicatorRef.current;
2660
+ const track = trackRef.current;
2661
+ if (!visible || !indicator || !track || active < 0) return;
2662
+ const option = track.querySelector(`[data-index="${active}"]`);
2663
+ if (!option) return;
2664
+ const trackRect = track.getBoundingClientRect();
2665
+ const optionRect = option.getBoundingClientRect();
2666
+ const first = !indicator.style.width;
2667
+ if (first) indicator.style.transition = "none";
2668
+ indicator.style.opacity = "1";
2669
+ indicator.style.width = `${optionRect.width}px`;
2670
+ indicator.style.height = `${optionRect.height}px`;
2671
+ indicator.style.transform = `translate(${optionRect.left - trackRect.left}px, ${optionRect.top - trackRect.top}px)`;
2672
+ if (first)
2673
+ requestAnimationFrame(() => {
2674
+ indicator.style.transition = "";
2675
+ });
2676
+ }, [active, options, present, portaled]);
2677
+ useIsoLayoutEffect(() => {
2678
+ const body = bodyRef.current;
2679
+ const inner = innerRef.current;
2680
+ if (!visible || !body || !inner) return;
2681
+ heightCleanup.current?.();
2682
+ heightCleanup.current = null;
2683
+ const duration = motionDurationMs(body, "fast");
2684
+ if (duration <= 0) {
2685
+ body.style.height = "";
2686
+ return;
2687
+ }
2688
+ const current = body.style.height ? Number.parseFloat(body.style.height) : body.getBoundingClientRect().height;
2689
+ if (!body.style.height) {
2690
+ body.style.height = `${current}px`;
2691
+ return;
2692
+ }
2693
+ const target = inner.getBoundingClientRect().height;
2694
+ if (Math.abs(current - target) < 1) {
2695
+ body.style.height = `${target}px`;
2696
+ return;
2697
+ }
2698
+ body.style.transition = "none";
2699
+ body.style.height = `${current}px`;
2700
+ void body.offsetHeight;
2701
+ body.style.transition = `height ${duration}ms var(--sph-ease-standard)`;
2702
+ body.style.height = `${target}px`;
2703
+ const settle = () => {
2704
+ body.style.transition = "";
2705
+ heightCleanup.current = null;
2706
+ };
2707
+ body.addEventListener("transitionend", settle, { once: true });
2708
+ heightCleanup.current = () => body.removeEventListener("transitionend", settle);
2709
+ }, [path, present, portaled, query, visible]);
2710
+ useIsoLayoutEffect(() => {
2711
+ if (!visible) {
2712
+ previousPathLength.current = null;
2713
+ return;
2714
+ }
2715
+ const previous = previousPathLength.current;
2716
+ previousPathLength.current = path.length;
2717
+ if (previous === null || previous === path.length) return;
2718
+ replay(panelRef.current, "sph-level-pulse");
2719
+ replay(trackRef.current, "sph-level-fade");
2720
+ }, [path, visible]);
2721
+ if (!visible) return null;
2722
+ return createPortal7(
2723
+ /* @__PURE__ */ jsx36(
2724
+ "div",
2725
+ {
2726
+ ref: backdropRef,
2727
+ className: "sph-palette__backdrop",
2728
+ "data-state": state,
2729
+ role: "presentation",
2730
+ children: /* @__PURE__ */ jsx36(
2731
+ "section",
2732
+ {
2733
+ ref: rootRef,
2734
+ className: cn35("sph-palette", className),
2735
+ role: "dialog",
2736
+ "aria-modal": "true",
2737
+ "aria-label": ariaLabel,
2738
+ children: /* @__PURE__ */ jsxs22("div", { ref: panelRef, className: "sph-palette__panel", children: [
2739
+ /* @__PURE__ */ jsxs22("nav", { className: "sph-palette__crumbs", "aria-label": "Breadcrumb", children: [
2740
+ " ",
2741
+ /* @__PURE__ */ jsx36(
2742
+ "button",
2743
+ {
2744
+ type: "button",
2745
+ className: "sph-palette__crumb sph-motion-in sph-motion-in--blur",
2746
+ "data-current": path.length === 0 || void 0,
2747
+ onClick: () => goToLevel(0),
2748
+ children: homeLabel
2749
+ }
2750
+ ),
2751
+ path.flatMap((entry, index) => [
2752
+ /* @__PURE__ */ jsx36(
2753
+ "span",
2754
+ {
2755
+ className: "sph-palette__crumb-sep",
2756
+ "aria-hidden": "true",
2757
+ children: /* @__PURE__ */ jsx36(CaretRightIcon2, { size: 11, weight: "bold" })
2758
+ },
2759
+ `sep-${index}`
2760
+ ),
2761
+ /* @__PURE__ */ jsx36(
2762
+ "button",
2763
+ {
2764
+ type: "button",
2765
+ className: "sph-palette__crumb sph-motion-in sph-motion-in--blur",
2766
+ "data-current": index === path.length - 1 || void 0,
2767
+ onClick: () => goToLevel(index + 1),
2768
+ children: crumbLabel(entry, homeLabel)
2769
+ },
2770
+ `crumb-${index}`
2771
+ )
2772
+ ])
2773
+ ] }),
2774
+ /* @__PURE__ */ jsxs22("div", { className: "sph-palette__search", children: [
2775
+ /* @__PURE__ */ jsx36(
2776
+ MagnifyingGlassIcon2,
2777
+ {
2778
+ className: "sph-palette__search-icon",
2779
+ size: 15,
2780
+ "aria-hidden": "true"
2781
+ }
2782
+ ),
2783
+ /* @__PURE__ */ jsx36(
2784
+ "input",
2785
+ {
2786
+ ref: inputRef,
2787
+ className: "sph-palette__input",
2788
+ type: "text",
2789
+ role: "combobox",
2790
+ "aria-expanded": true,
2791
+ "aria-controls": listId,
2792
+ "aria-activedescendant": active >= 0 ? options[active]?.activeId : void 0,
2793
+ "aria-autocomplete": "list",
2794
+ "aria-label": ariaLabel,
2795
+ placeholder,
2796
+ value: query,
2797
+ autoComplete: "off",
2798
+ onChange: (event) => setQuery(event.currentTarget.value),
2799
+ onKeyDown: handleInputKeyDown
2800
+ }
2801
+ ),
2802
+ /* @__PURE__ */ jsx36(
2803
+ "button",
2804
+ {
2805
+ type: "button",
2806
+ className: "sph-kbd sph-palette__escape",
2807
+ "aria-label": "Close command palette",
2808
+ onClick: close,
2809
+ children: /* @__PURE__ */ jsx36("span", { className: "sph-kbd__key", children: "Esc" })
2810
+ }
2811
+ )
2812
+ ] }),
2813
+ /* @__PURE__ */ jsx36("div", { className: "sph-palette__body", ref: bodyRef, children: /* @__PURE__ */ jsx36(
2814
+ "div",
2815
+ {
2816
+ className: "sph-palette__list",
2817
+ ref: innerRef,
2818
+ id: listId,
2819
+ role: "listbox",
2820
+ "aria-label": ariaLabel,
2821
+ children: /* @__PURE__ */ jsxs22("div", { className: "sph-palette__track", ref: trackRef, children: [
2822
+ /* @__PURE__ */ jsx36(
2823
+ "span",
2824
+ {
2825
+ className: "sph-palette__indicator",
2826
+ ref: indicatorRef,
2827
+ role: "presentation"
2828
+ }
2829
+ ),
2830
+ options.length === 0 && /* @__PURE__ */ jsx36("div", { className: "sph-palette__empty", role: "status", children: node(emptyMessage) }),
2831
+ sections.map((section) => /* @__PURE__ */ jsxs22(
2832
+ "div",
2833
+ {
2834
+ className: "sph-palette__group",
2835
+ role: "group",
2836
+ "aria-labelledby": section.heading !== void 0 ? `${listId}-group-${section.heading}` : void 0,
2837
+ children: [
2838
+ section.heading !== void 0 && /* @__PURE__ */ jsx36(
2839
+ "div",
2840
+ {
2841
+ className: "sph-palette__heading",
2842
+ id: `${listId}-group-${section.heading}`,
2843
+ children: section.heading
2844
+ }
2845
+ ),
2846
+ section.entries.map((option) => {
2847
+ const item = option.item;
2848
+ const index = option.index;
2849
+ return /* @__PURE__ */ jsxs22(
2850
+ "div",
2851
+ {
2852
+ id: option.activeId,
2853
+ className: "sph-palette__option",
2854
+ role: "option",
2855
+ "data-index": index,
2856
+ "data-active": index === active ? "true" : void 0,
2857
+ "data-disabled": item.disabled || void 0,
2858
+ "aria-selected": index === active,
2859
+ "aria-disabled": item.disabled || void 0,
2860
+ onPointerEnter: () => {
2861
+ if (!item.disabled) setActive(index);
2862
+ },
2863
+ onPointerDown: (event) => event.preventDefault(),
2864
+ onClick: () => activate(index),
2865
+ children: [
2866
+ item.icon !== void 0 && /* @__PURE__ */ jsx36(
2867
+ "span",
2868
+ {
2869
+ className: "sph-palette__icon",
2870
+ "aria-hidden": "true",
2871
+ children: node(item.icon)
2872
+ }
2873
+ ),
2874
+ /* @__PURE__ */ jsx36("span", { className: "sph-palette__label", children: typeof item.label === "string" ? /* @__PURE__ */ jsx36(
2875
+ Highlight,
2876
+ {
2877
+ text: item.label,
2878
+ indexes: option.indexes
2879
+ }
2880
+ ) : node(item.label) }),
2881
+ item.badge !== void 0 && /* @__PURE__ */ jsx36("span", { className: "sph-palette__badge", children: node(item.badge) }),
2882
+ item.children && item.children.length > 0 ? /* @__PURE__ */ jsx36(
2883
+ "span",
2884
+ {
2885
+ className: "sph-palette__more",
2886
+ "aria-hidden": "true",
2887
+ children: /* @__PURE__ */ jsx36(CaretRightIcon2, { size: 13, weight: "bold" })
2888
+ }
2889
+ ) : item.hint !== void 0 ? /* @__PURE__ */ jsx36("span", { className: "sph-palette__hint", children: node(item.hint) }) : item.shortcut !== void 0 && /* @__PURE__ */ jsx36(Kbd, { keys: item.shortcut })
2890
+ ]
2891
+ },
2892
+ item.id ?? `${readableLabel2(item)}-${index}`
2893
+ );
2894
+ })
2895
+ ]
2896
+ },
2897
+ section.heading ?? "section-root"
2898
+ ))
2899
+ ] })
2900
+ }
2901
+ ) }),
2902
+ /* @__PURE__ */ jsxs22("footer", { className: "sph-palette__footer", "aria-hidden": "true", children: [
2903
+ /* @__PURE__ */ jsxs22("span", { className: "sph-palette__hintrow", children: [
2904
+ /* @__PURE__ */ jsx36(Kbd, { keys: "\u2191" }),
2905
+ /* @__PURE__ */ jsx36(Kbd, { keys: "\u2193" }),
2906
+ "navigate"
2907
+ ] }),
2908
+ /* @__PURE__ */ jsxs22("span", { className: "sph-palette__hintrow", children: [
2909
+ /* @__PURE__ */ jsx36(Kbd, { keys: "\u21B5" }),
2910
+ "select"
2911
+ ] }),
2912
+ /* @__PURE__ */ jsxs22("span", { className: "sph-palette__hintrow", children: [
2913
+ /* @__PURE__ */ jsx36(Kbd, { keys: "esc" }),
2914
+ path.length > 0 || query ? "back" : "close"
2915
+ ] })
2916
+ ] })
2917
+ ] })
2918
+ }
2919
+ )
2920
+ }
2921
+ ),
2922
+ document.body
2923
+ );
2924
+ }
2925
+
1255
2926
  // src/organisms/EmptyState/index.tsx
1256
2927
  import { TrayIcon } from "@phosphor-icons/react";
1257
- import { cn as cn26 } from "cn";
1258
- import { jsx as jsx26, jsxs as jsxs15 } from "react/jsx-runtime";
1259
- var emptyStateDefaultIcon = /* @__PURE__ */ jsx26(TrayIcon, { size: 20 });
2928
+ import { cn as cn36 } from "cn";
2929
+ import { jsx as jsx37, jsxs as jsxs23 } from "react/jsx-runtime";
2930
+ var emptyStateDefaultIcon = /* @__PURE__ */ jsx37(TrayIcon, { size: 20 });
1260
2931
  function EmptyState({
1261
2932
  title,
1262
2933
  description,
@@ -1266,25 +2937,429 @@ function EmptyState({
1266
2937
  className,
1267
2938
  ...props
1268
2939
  }) {
1269
- return /* @__PURE__ */ jsxs15(
2940
+ return /* @__PURE__ */ jsxs23(
1270
2941
  "section",
1271
2942
  {
1272
2943
  ...node(props),
1273
- className: cn26("sph-empty-state", className),
2944
+ className: cn36("sph-empty-state", className),
1274
2945
  "data-compact": compact || void 0,
1275
2946
  children: [
1276
- /* @__PURE__ */ jsx26("div", { className: "sph-empty-state__icon", "aria-hidden": "true", children: node(icon) }),
1277
- /* @__PURE__ */ jsx26("h2", { className: "sph-empty-state__title", children: node(title) }),
1278
- description !== void 0 && /* @__PURE__ */ jsx26("p", { className: "sph-empty-state__description", children: node(description) }),
1279
- action !== void 0 && /* @__PURE__ */ jsx26("div", { className: "sph-empty-state__action", children: node(action) })
2947
+ /* @__PURE__ */ jsx37("div", { className: "sph-empty-state__icon", "aria-hidden": "true", children: node(icon) }),
2948
+ /* @__PURE__ */ jsx37("h2", { className: "sph-empty-state__title", children: node(title) }),
2949
+ description !== void 0 && /* @__PURE__ */ jsx37("p", { className: "sph-empty-state__description", children: node(description) }),
2950
+ action !== void 0 && /* @__PURE__ */ jsx37("div", { className: "sph-empty-state__action", children: node(action) })
2951
+ ]
2952
+ }
2953
+ );
2954
+ }
2955
+
2956
+ // src/organisms/MagneticDropzone/index.tsx
2957
+ import { cn as cn37 } from "cn";
2958
+ import { useRef as useRef11, useState as useState14 } from "react";
2959
+ import { jsx as jsx38, jsxs as jsxs24 } from "react/jsx-runtime";
2960
+ function MagneticDropzone({
2961
+ children,
2962
+ activeContent,
2963
+ onFiles,
2964
+ accept,
2965
+ multiple = true,
2966
+ disabled = false,
2967
+ className,
2968
+ ...props
2969
+ }) {
2970
+ const zoneRef = useRef11(null);
2971
+ const inputRef = useRef11(null);
2972
+ const [dragging, setDragging] = useState14(false);
2973
+ const pull = (event) => {
2974
+ const zone = zoneRef.current;
2975
+ if (!zone) return;
2976
+ const rect = zone.getBoundingClientRect();
2977
+ const x = ((event.clientX - rect.left) / rect.width - 0.5) * 2;
2978
+ const y = ((event.clientY - rect.top) / rect.height - 0.5) * 2;
2979
+ zone.style.setProperty("--sph-pull-x", `${(x * 10).toFixed(1)}px`);
2980
+ zone.style.setProperty("--sph-pull-y", `${(y * 8).toFixed(1)}px`);
2981
+ };
2982
+ const reset = () => {
2983
+ const zone = zoneRef.current;
2984
+ if (!zone) return;
2985
+ zone.style.setProperty("--sph-pull-x", "0px");
2986
+ zone.style.setProperty("--sph-pull-y", "0px");
2987
+ };
2988
+ const handleDragOver = (event) => {
2989
+ if (disabled) return;
2990
+ event.preventDefault();
2991
+ if (!dragging) setDragging(true);
2992
+ pull(event);
2993
+ };
2994
+ const handleDragLeave = (event) => {
2995
+ const next = event.relatedTarget;
2996
+ if (next && zoneRef.current?.contains(next)) return;
2997
+ setDragging(false);
2998
+ reset();
2999
+ };
3000
+ const handleDrop = (event) => {
3001
+ if (disabled) return;
3002
+ event.preventDefault();
3003
+ setDragging(false);
3004
+ reset();
3005
+ const files = Array.from(event.dataTransfer?.files ?? []);
3006
+ if (files.length > 0) onFiles?.(multiple ? files : files.slice(0, 1));
3007
+ };
3008
+ const openPicker = () => {
3009
+ if (!disabled) inputRef.current?.click();
3010
+ };
3011
+ return /* @__PURE__ */ jsxs24(
3012
+ "div",
3013
+ {
3014
+ ...node(props),
3015
+ ref: zoneRef,
3016
+ className: cn37("sph-dropzone", className),
3017
+ "data-active": dragging || void 0,
3018
+ "data-disabled": disabled || void 0,
3019
+ onDragOver: handleDragOver,
3020
+ onDragLeave: handleDragLeave,
3021
+ onDrop: handleDrop,
3022
+ onClick: openPicker,
3023
+ children: [
3024
+ /* @__PURE__ */ jsx38("div", { className: "sph-dropzone__content", children: node(
3025
+ dragging && activeContent !== void 0 ? activeContent : children
3026
+ ) }),
3027
+ /* @__PURE__ */ jsx38(
3028
+ "input",
3029
+ {
3030
+ ref: inputRef,
3031
+ className: "sph-dropzone__input",
3032
+ type: "file",
3033
+ accept,
3034
+ multiple,
3035
+ "aria-hidden": "true",
3036
+ tabIndex: -1,
3037
+ disabled,
3038
+ onChange: (event) => {
3039
+ const files = Array.from(event.currentTarget.files ?? []);
3040
+ if (files.length > 0) onFiles?.(multiple ? files : files.slice(0, 1));
3041
+ event.currentTarget.value = "";
3042
+ }
3043
+ }
3044
+ )
3045
+ ]
3046
+ }
3047
+ );
3048
+ }
3049
+
3050
+ // src/organisms/ReasoningSteps/index.tsx
3051
+ import { cn as cn38 } from "cn";
3052
+ import { jsx as jsx39, jsxs as jsxs25 } from "react/jsx-runtime";
3053
+ function ReasoningSteps({
3054
+ steps,
3055
+ label = "Reasoning",
3056
+ open,
3057
+ defaultOpen = true,
3058
+ onOpenChange,
3059
+ className
3060
+ }) {
3061
+ const activeCount = steps.filter((step) => step.status === "active").length;
3062
+ return /* @__PURE__ */ jsx39("div", { className: cn38("sph-reasoning", className), children: /* @__PURE__ */ jsx39(
3063
+ Collapsible,
3064
+ {
3065
+ open,
3066
+ defaultOpen,
3067
+ onOpenChange,
3068
+ trigger: /* @__PURE__ */ jsxs25("span", { className: "sph-reasoning__trigger", children: [
3069
+ /* @__PURE__ */ jsx39("span", { className: "sph-reasoning__label", children: node(label) }),
3070
+ /* @__PURE__ */ jsxs25("span", { className: "sph-reasoning__meta", children: [
3071
+ activeCount > 0 && /* @__PURE__ */ jsxs25("span", { className: "sph-typing", "aria-hidden": "true", children: [
3072
+ /* @__PURE__ */ jsx39("i", {}),
3073
+ /* @__PURE__ */ jsx39("i", {}),
3074
+ /* @__PURE__ */ jsx39("i", {})
3075
+ ] }),
3076
+ /* @__PURE__ */ jsxs25("span", { className: "sph-reasoning__count", children: [
3077
+ steps.length,
3078
+ " ",
3079
+ steps.length === 1 ? "step" : "steps"
3080
+ ] })
3081
+ ] })
3082
+ ] }),
3083
+ children: /* @__PURE__ */ jsx39("ol", { className: "sph-reasoning__list", children: steps.map((step, index) => /* @__PURE__ */ jsxs25(
3084
+ "li",
3085
+ {
3086
+ className: "sph-reasoning__step",
3087
+ "data-status": step.status ?? "done",
3088
+ style: { animationDelay: `${index * 26}ms` },
3089
+ children: [
3090
+ /* @__PURE__ */ jsx39("span", { className: "sph-reasoning__marker", "aria-hidden": "true", children: /* @__PURE__ */ jsx39("span", { className: "sph-reasoning__dot" }) }),
3091
+ /* @__PURE__ */ jsxs25("span", { className: "sph-reasoning__body", children: [
3092
+ /* @__PURE__ */ jsx39("span", { className: "sph-reasoning__title", children: node(step.title) }),
3093
+ step.detail !== void 0 && /* @__PURE__ */ jsx39("span", { className: "sph-reasoning__detail", children: node(step.detail) })
3094
+ ] }),
3095
+ /* @__PURE__ */ jsx39("span", { className: "sph-reasoning__status", children: step.status === "active" ? "thinking" : step.status === "pending" ? "queued" : "done" })
3096
+ ]
3097
+ },
3098
+ step.id ?? index
3099
+ )) })
3100
+ }
3101
+ ) });
3102
+ }
3103
+
3104
+ // src/organisms/Resizable/index.tsx
3105
+ import { cn as cn39 } from "cn";
3106
+ import { useMemo as useMemo3, useRef as useRef12, useState as useState15 } from "react";
3107
+ import { jsx as jsx40 } from "react/jsx-runtime";
3108
+ function clamp2(value, low, high) {
3109
+ return Math.min(Math.max(value, low), high);
3110
+ }
3111
+ function Resizable({
3112
+ panes,
3113
+ direction = "horizontal",
3114
+ sizes,
3115
+ defaultSizes,
3116
+ onSizesChange,
3117
+ min = 10,
3118
+ handleLabel = "Resize panels",
3119
+ className,
3120
+ ...props
3121
+ }) {
3122
+ const rootRef = useRef12(null);
3123
+ const [uncontrolled, setUncontrolled] = useState15(
3124
+ () => defaultSizes ?? []
3125
+ );
3126
+ const [dragIndex, setDragIndex] = useState15(null);
3127
+ const drag = useRef12(null);
3128
+ const even = useMemo3(
3129
+ () => panes.map(() => 100 / Math.max(panes.length, 1)),
3130
+ [panes.length]
3131
+ );
3132
+ const candidate = sizes ?? uncontrolled;
3133
+ const active = candidate.length === panes.length && panes.length > 0 ? candidate : even;
3134
+ const commit = (next) => {
3135
+ setUncontrolled(next);
3136
+ onSizesChange?.(next);
3137
+ };
3138
+ const startDrag = (event, index) => {
3139
+ if (event.button !== 0) return;
3140
+ const rect = rootRef.current?.getBoundingClientRect();
3141
+ if (!rect) return;
3142
+ event.currentTarget.setPointerCapture(event.pointerId);
3143
+ drag.current = {
3144
+ index,
3145
+ pointer: { x: event.clientX, y: event.clientY },
3146
+ sizes: [...active],
3147
+ extent: direction === "horizontal" ? rect.width : rect.height
3148
+ };
3149
+ setDragIndex(index);
3150
+ };
3151
+ const moveDrag = (event) => {
3152
+ const state = drag.current;
3153
+ if (!state || state.extent <= 0) return;
3154
+ const position = direction === "horizontal" ? event.clientX : event.clientY;
3155
+ const origin = direction === "horizontal" ? state.pointer.x : state.pointer.y;
3156
+ const delta = (position - origin) / state.extent * 100;
3157
+ const next = [...state.sizes];
3158
+ const before = next[state.index] ?? 0;
3159
+ const after = next[state.index + 1] ?? 0;
3160
+ const span = before + after;
3161
+ const moved = clamp2(before + delta, min, span - min);
3162
+ next[state.index] = moved;
3163
+ next[state.index + 1] = span - moved;
3164
+ commit(next);
3165
+ };
3166
+ const endDrag = (event) => {
3167
+ if (drag.current === null) return;
3168
+ drag.current = null;
3169
+ setDragIndex(null);
3170
+ if (event.pointerId !== void 0) {
3171
+ try {
3172
+ event.currentTarget.releasePointerCapture(event.pointerId);
3173
+ } catch {
3174
+ }
3175
+ }
3176
+ };
3177
+ const handleKeyDown = (event, index) => {
3178
+ const step = (event.shiftKey ? 10 : 2) * (direction === "horizontal" ? event.key === "ArrowLeft" ? -1 : event.key === "ArrowRight" ? 1 : 0 : event.key === "ArrowUp" ? -1 : event.key === "ArrowDown" ? 1 : 0);
3179
+ if (step === 0) return;
3180
+ event.preventDefault();
3181
+ const next = [...active];
3182
+ const before = next[index] ?? 0;
3183
+ const after = next[index + 1] ?? 0;
3184
+ const span = before + after;
3185
+ const moved = clamp2(before + step, min, span - min);
3186
+ next[index] = moved;
3187
+ next[index + 1] = span - moved;
3188
+ commit(next);
3189
+ };
3190
+ return /* @__PURE__ */ jsx40(
3191
+ "div",
3192
+ {
3193
+ ...node(props),
3194
+ ref: rootRef,
3195
+ className: cn39("sph-resizable", className),
3196
+ "data-direction": direction,
3197
+ children: panes.flatMap((pane, index) => {
3198
+ const items = [
3199
+ /* @__PURE__ */ jsx40(
3200
+ "div",
3201
+ {
3202
+ className: "sph-resizable__pane",
3203
+ style: {
3204
+ flexGrow: active[index] ?? even[index] ?? 1,
3205
+ flexBasis: 0,
3206
+ minWidth: 0,
3207
+ minHeight: 0
3208
+ },
3209
+ children: node(pane)
3210
+ },
3211
+ `pane-${index}`
3212
+ )
3213
+ ];
3214
+ if (index < panes.length - 1) {
3215
+ items.push(
3216
+ /* @__PURE__ */ jsx40(
3217
+ "div",
3218
+ {
3219
+ role: "separator",
3220
+ tabIndex: 0,
3221
+ "aria-orientation": direction === "horizontal" ? "vertical" : "horizontal",
3222
+ "aria-valuenow": Math.round(active[index] ?? 0),
3223
+ "aria-label": handleLabel,
3224
+ className: "sph-resizable__handle",
3225
+ "data-dragging": dragIndex === index || void 0,
3226
+ onPointerDown: (event) => startDrag(event, index),
3227
+ onPointerMove: moveDrag,
3228
+ onPointerUp: endDrag,
3229
+ onPointerCancel: endDrag,
3230
+ onKeyDown: (event) => handleKeyDown(event, index)
3231
+ },
3232
+ `handle-${index}`
3233
+ )
3234
+ );
3235
+ }
3236
+ return items;
3237
+ })
3238
+ }
3239
+ );
3240
+ }
3241
+
3242
+ // src/organisms/Sidebar/index.tsx
3243
+ import { cn as cn40 } from "cn";
3244
+ import { useId as useId15, useState as useState16 } from "react";
3245
+ import { jsx as jsx41, jsxs as jsxs26 } from "react/jsx-runtime";
3246
+ function Sidebar({
3247
+ sections,
3248
+ value,
3249
+ defaultValue,
3250
+ onSelect,
3251
+ header,
3252
+ footer,
3253
+ collapsible = false,
3254
+ collapsed,
3255
+ defaultCollapsed = false,
3256
+ onCollapsedChange,
3257
+ ariaLabel = "Primary",
3258
+ className,
3259
+ ...props
3260
+ }) {
3261
+ const listId = useId15();
3262
+ const [uncontrolled, setUncontrolled] = useState16(defaultCollapsed);
3263
+ const isCollapsed = collapsed ?? uncontrolled;
3264
+ const active = value ?? defaultValue;
3265
+ const toggleCollapsed = () => {
3266
+ const next = !isCollapsed;
3267
+ setUncontrolled(next);
3268
+ onCollapsedChange?.(next);
3269
+ };
3270
+ return /* @__PURE__ */ jsxs26(
3271
+ "aside",
3272
+ {
3273
+ ...node(props),
3274
+ className: cn40("sph-sidebar", className),
3275
+ "data-collapsed": isCollapsed || void 0,
3276
+ children: [
3277
+ header !== void 0 && /* @__PURE__ */ jsx41("div", { className: "sph-sidebar__header", children: node(header) }),
3278
+ /* @__PURE__ */ jsxs26("nav", { className: "sph-sidebar__nav", "aria-label": ariaLabel, children: [
3279
+ sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs26(
3280
+ "div",
3281
+ {
3282
+ className: "sph-sidebar__section",
3283
+ children: [
3284
+ section.title !== void 0 && /* @__PURE__ */ jsx41("div", { className: "sph-sidebar__section-title", "aria-hidden": "true", children: section.title }),
3285
+ section.items.map((item) => {
3286
+ const isActive = item.value === active;
3287
+ return /* @__PURE__ */ jsxs26(
3288
+ "button",
3289
+ {
3290
+ type: "button",
3291
+ className: "sph-sidebar__item",
3292
+ "data-active": isActive || void 0,
3293
+ "aria-current": isActive ? "page" : void 0,
3294
+ disabled: item.disabled,
3295
+ title: isCollapsed && typeof item.label === "string" ? item.label : void 0,
3296
+ onClick: () => onSelect?.(item.value),
3297
+ children: [
3298
+ item.icon !== void 0 && /* @__PURE__ */ jsx41("span", { className: "sph-sidebar__icon", "aria-hidden": "true", children: node(item.icon) }),
3299
+ /* @__PURE__ */ jsx41("span", { className: "sph-sidebar__label", children: node(item.label) }),
3300
+ item.badge !== void 0 && /* @__PURE__ */ jsx41("span", { className: "sph-sidebar__badge", children: node(item.badge) })
3301
+ ]
3302
+ },
3303
+ item.value
3304
+ );
3305
+ })
3306
+ ]
3307
+ },
3308
+ section.title ?? `section-${sectionIndex}`
3309
+ )),
3310
+ /* @__PURE__ */ jsx41("span", { id: `${listId}-hint`, className: "sph-visually-hidden", children: isCollapsed ? "Sidebar collapsed" : "Sidebar expanded" })
3311
+ ] }),
3312
+ (footer !== void 0 || collapsible) && /* @__PURE__ */ jsxs26("div", { className: "sph-sidebar__footer", children: [
3313
+ footer !== void 0 && /* @__PURE__ */ jsx41("div", { className: "sph-sidebar__footer-content", children: node(footer) }),
3314
+ collapsible && /* @__PURE__ */ jsx41(
3315
+ "button",
3316
+ {
3317
+ type: "button",
3318
+ className: "sph-sidebar__toggle",
3319
+ "aria-expanded": !isCollapsed,
3320
+ "aria-label": isCollapsed ? "Expand sidebar" : "Collapse sidebar",
3321
+ onClick: toggleCollapsed,
3322
+ children: /* @__PURE__ */ jsx41("span", { "aria-hidden": "true", children: isCollapsed ? "\xBB" : "\xAB" })
3323
+ }
3324
+ )
3325
+ ] })
1280
3326
  ]
1281
3327
  }
1282
3328
  );
1283
3329
  }
1284
3330
 
3331
+ // src/organisms/StreamingText/index.tsx
3332
+ import { cn as cn41 } from "cn";
3333
+ import { jsx as jsx42 } from "react/jsx-runtime";
3334
+ function StreamingText({
3335
+ text,
3336
+ speed = 30,
3337
+ animated = true,
3338
+ className,
3339
+ ...props
3340
+ }) {
3341
+ const tokens = text.split(/(\s+)/);
3342
+ let wordIndex = -1;
3343
+ return /* @__PURE__ */ jsx42("div", { ...node(props), className: cn41("sph-stream", className), children: tokens.map((token, index) => {
3344
+ if (token.trim().length === 0) return token;
3345
+ wordIndex += 1;
3346
+ const delay = animated ? wordIndex * speed : 0;
3347
+ return /* @__PURE__ */ jsx42(
3348
+ "span",
3349
+ {
3350
+ className: "sph-stream__word",
3351
+ "data-animated": animated || void 0,
3352
+ style: { animationDelay: `${delay}ms` },
3353
+ children: token
3354
+ },
3355
+ index
3356
+ );
3357
+ }) });
3358
+ }
3359
+
1285
3360
  // src/organisms/Table/index.tsx
1286
- import { cn as cn27 } from "cn";
1287
- import { jsx as jsx27, jsxs as jsxs16 } from "react/jsx-runtime";
3361
+ import { cn as cn42 } from "cn";
3362
+ import { jsx as jsx43, jsxs as jsxs27 } from "react/jsx-runtime";
1288
3363
  function Table({
1289
3364
  columns,
1290
3365
  rows,
@@ -1297,7 +3372,7 @@ function Table({
1297
3372
  className,
1298
3373
  ...props
1299
3374
  }) {
1300
- return /* @__PURE__ */ jsx27("div", { className: cn27("sph-table-wrap", className), children: /* @__PURE__ */ jsxs16(
3375
+ return /* @__PURE__ */ jsx43("div", { className: cn42("sph-table-wrap", className), children: /* @__PURE__ */ jsxs27(
1301
3376
  "table",
1302
3377
  {
1303
3378
  ...node(props),
@@ -1306,8 +3381,8 @@ function Table({
1306
3381
  "data-striped": striped || void 0,
1307
3382
  "data-sticky-header": stickyHeader || void 0,
1308
3383
  children: [
1309
- caption !== void 0 && /* @__PURE__ */ jsx27("caption", { children: node(caption) }),
1310
- /* @__PURE__ */ jsx27("thead", { children: /* @__PURE__ */ jsx27("tr", { children: columns.map((column) => /* @__PURE__ */ jsx27(
3384
+ caption !== void 0 && /* @__PURE__ */ jsx43("caption", { children: node(caption) }),
3385
+ /* @__PURE__ */ jsx43("thead", { children: /* @__PURE__ */ jsx43("tr", { children: columns.map((column) => /* @__PURE__ */ jsx43(
1311
3386
  "th",
1312
3387
  {
1313
3388
  scope: "col",
@@ -1317,7 +3392,7 @@ function Table({
1317
3392
  },
1318
3393
  column.key
1319
3394
  )) }) }),
1320
- /* @__PURE__ */ jsx27("tbody", { children: rows.length === 0 ? /* @__PURE__ */ jsx27("tr", { children: /* @__PURE__ */ jsx27("td", { className: "sph-table__empty", colSpan: columns.length, children: node(emptyMessage) }) }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx27("tr", { children: columns.map((column) => /* @__PURE__ */ jsx27("td", { "data-align": column.align ?? "start", children: node(
3395
+ /* @__PURE__ */ jsx43("tbody", { children: rows.length === 0 ? /* @__PURE__ */ jsx43("tr", { children: /* @__PURE__ */ jsx43("td", { className: "sph-table__empty", colSpan: columns.length, children: node(emptyMessage) }) }) : rows.map((row, rowIndex) => /* @__PURE__ */ jsx43("tr", { children: columns.map((column) => /* @__PURE__ */ jsx43("td", { "data-align": column.align ?? "start", children: node(
1321
3396
  column.render ? column.render(row, rowIndex) : row[column.key]
1322
3397
  ) }, column.key)) }, rowKey ? String(row[rowKey]) : rowIndex)) })
1323
3398
  ]
@@ -1326,25 +3401,40 @@ function Table({
1326
3401
  }
1327
3402
  export {
1328
3403
  Alert,
3404
+ Avatar,
3405
+ AvatarGroup,
1329
3406
  Badge,
1330
3407
  Button,
1331
3408
  Card,
1332
3409
  Checkbox,
3410
+ Collapsible,
3411
+ Combobox,
3412
+ CommandPalette,
1333
3413
  Dialog,
3414
+ Drawer,
1334
3415
  EmptyState,
1335
3416
  Field,
1336
3417
  FieldMessage,
3418
+ HoverCard,
1337
3419
  IconButton,
1338
3420
  Input,
3421
+ Kbd,
3422
+ MagneticDropzone,
1339
3423
  Menu,
1340
3424
  Notice,
1341
3425
  Popover,
3426
+ Progress,
1342
3427
  RadioGroup,
3428
+ ReasoningSteps,
3429
+ Resizable,
3430
+ ScrollArea,
1343
3431
  Select,
1344
3432
  Separator,
3433
+ Sidebar,
1345
3434
  Skeleton,
1346
3435
  Slider,
1347
3436
  Spinner,
3437
+ StreamingText,
1348
3438
  Surface,
1349
3439
  Table,
1350
3440
  Tabs,
@@ -1354,6 +3444,9 @@ export {
1354
3444
  Toggle,
1355
3445
  Toolbar,
1356
3446
  Tooltip,
1357
- toast
3447
+ motionDurationMs,
3448
+ parseDurationMs,
3449
+ toast,
3450
+ usePresence
1358
3451
  };
1359
3452
  //# sourceMappingURL=index.js.map