reusify-ui 2.1.0 → 2.3.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.
@@ -12,7 +12,7 @@ const legacyVariants = {
12
12
  danger: ["solid", "danger"],
13
13
  success: ["solid", "success"]
14
14
  };
15
- const semanticColors$2 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
15
+ const semanticColors$5 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
16
16
  const radii = { none: "0", sm: "6px", md: "10px", lg: "14px", xl: "18px", "2xl": "24px", full: "9999px" };
17
17
  function resolveRadius(radius) {
18
18
  if (radius === void 0) return void 0;
@@ -50,7 +50,7 @@ const Button = react.forwardRef(function Button2({
50
50
  onPointerMove == null ? void 0 : onPointerMove(e);
51
51
  } : onPointerMove;
52
52
  const resolvedColor = color ?? legacyColor ?? "primary";
53
- const customColor = !semanticColors$2.has(resolvedColor);
53
+ const customColor = !semanticColors$5.has(resolvedColor);
54
54
  const classes = cn(
55
55
  "ru-btn",
56
56
  `ru-btn--${kind}`,
@@ -145,6 +145,16 @@ const UserIcon = (p) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...base, ...p,
145
145
  /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "8", r: "4" }),
146
146
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 21a8 8 0 0 1 16 0" })
147
147
  ] });
148
+ const UploadCloudIcon = (p) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...base, ...p, children: [
149
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 14.9A7 7 0 1 1 15.7 8h1.8a4.5 4.5 0 0 1 2.5 8.2" }),
150
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 12v9M16 16l-4-4-4 4" })
151
+ ] });
152
+ const FileIcon = (p) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...base, ...p, children: [
153
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
154
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
155
+ ] });
156
+ const TrendUpIcon = (p) => /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...p, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 19V5M5 12l7-7 7 7" }) });
157
+ const TrendDownIcon = (p) => /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...p, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 5v14M19 12l-7 7-7-7" }) });
148
158
  const statusIcons = {
149
159
  info: InfoIcon,
150
160
  success: SuccessIcon,
@@ -160,11 +170,17 @@ function Spinner({ size = "md", color = "primary", label = "Loading", className,
160
170
  label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-sr-only", children: label })
161
171
  ] });
162
172
  }
173
+ const progressColors = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
163
174
  function Progress({
164
175
  value,
165
176
  max = 100,
166
177
  size = "md",
167
178
  color = "primary",
179
+ variant = "bar",
180
+ ticks = 24,
181
+ glow = false,
182
+ icon,
183
+ endLabel,
168
184
  label,
169
185
  showValue = false,
170
186
  className,
@@ -172,7 +188,86 @@ function Progress({
172
188
  }) {
173
189
  const indeterminate = value === void 0;
174
190
  const percent = indeterminate ? 0 : Math.min(100, Math.max(0, value / max * 100));
175
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-progress", indeterminate && "ru-progress--indeterminate", className), ...rest, children: [
191
+ const semantic = progressColors.has(color);
192
+ const glowColor = glow === true ? semantic ? `var(--ru-${color})` : color : glow || void 0;
193
+ const meterStyle = { ...!semantic && { "--_c": color }, ...glowColor && { "--_glow": glowColor } };
194
+ if (variant === "capsule") {
195
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-progress", "ru-progress--capsule", `ru-progress--${size}`, glowColor && "ru-progress--glow", className), ...rest, children: [
196
+ label && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-progress__label", children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) }),
197
+ /* @__PURE__ */ jsxRuntime.jsxs(
198
+ "div",
199
+ {
200
+ role: "progressbar",
201
+ "aria-valuemin": 0,
202
+ "aria-valuemax": max,
203
+ "aria-valuenow": value ?? 0,
204
+ "aria-label": typeof label === "string" ? label : void 0,
205
+ className: cn("ru-progress__capsule", semantic && `ru-c-${color}`),
206
+ style: meterStyle,
207
+ children: [
208
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-progress__capsule-fill", style: { width: `max(var(--_h), ${percent}%)` }, children: icon ?? (showValue && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-progress__capsule-value", children: [
209
+ Math.round(percent),
210
+ "%"
211
+ ] })) }),
212
+ endLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-progress__capsule-end", children: endLabel })
213
+ ]
214
+ }
215
+ )
216
+ ] });
217
+ }
218
+ if (variant === "bars") {
219
+ const total = Math.max(2, ticks);
220
+ const lit = Math.round(percent / 100 * total);
221
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-progress", "ru-progress--bars", `ru-progress--${size}`, glowColor && "ru-progress--glow", className), ...rest, children: [
222
+ (label || showValue) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-progress__label", children: [
223
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
224
+ showValue && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-progress__value ru-progress__value--accent", children: [
225
+ Math.round(percent),
226
+ "%"
227
+ ] })
228
+ ] }),
229
+ /* @__PURE__ */ jsxRuntime.jsx(
230
+ "div",
231
+ {
232
+ role: "progressbar",
233
+ "aria-valuemin": 0,
234
+ "aria-valuemax": max,
235
+ "aria-valuenow": value ?? 0,
236
+ "aria-label": typeof label === "string" ? label : void 0,
237
+ className: cn("ru-progress__bars", semantic && `ru-c-${color}`),
238
+ style: meterStyle,
239
+ children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(i < lit && "is-on", i === lit - 1 && percent > 0 && percent < 100 && "is-marker") }, i))
240
+ }
241
+ )
242
+ ] });
243
+ }
244
+ if (variant === "ticks") {
245
+ const remaining = percent >= 100 ? 0 : Math.max(1, Math.round(ticks * (1 - percent / 100)));
246
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-progress", "ru-progress--ticks", `ru-progress--${size}`, glowColor && "ru-progress--glow", className), ...rest, children: [
247
+ label && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-progress__label", children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }) }),
248
+ /* @__PURE__ */ jsxRuntime.jsxs(
249
+ "div",
250
+ {
251
+ role: "progressbar",
252
+ "aria-valuemin": 0,
253
+ "aria-valuemax": max,
254
+ "aria-valuenow": value ?? 0,
255
+ "aria-label": typeof label === "string" ? label : void 0,
256
+ className: cn("ru-progress__meter", semantic && `ru-c-${color}`),
257
+ style: meterStyle,
258
+ children: [
259
+ percent > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-progress__fill", style: { width: `${percent}%` }, children: showValue && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-progress__fill-value", children: [
260
+ Math.round(percent),
261
+ "%"
262
+ ] }) }),
263
+ percent > 0 && percent < 100 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-progress__marker", "aria-hidden": "true" }),
264
+ remaining > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-progress__ticks", "aria-hidden": "true", children: Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("span", {}, i)) })
265
+ ]
266
+ }
267
+ )
268
+ ] });
269
+ }
270
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-progress", indeterminate && "ru-progress--indeterminate", glowColor && "ru-progress--glow", className), ...rest, children: [
176
271
  (label || showValue) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-progress__label", children: [
177
272
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
178
273
  showValue && !indeterminate && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-progress__value", children: [
@@ -188,7 +283,8 @@ function Progress({
188
283
  "aria-valuemax": max,
189
284
  "aria-valuenow": indeterminate ? void 0 : value,
190
285
  "aria-label": typeof label === "string" ? label : void 0,
191
- className: cn("ru-progress__track", `ru-progress__track--${size}`, `ru-c-${color}`),
286
+ className: cn("ru-progress__track", `ru-progress__track--${size}`, semantic && `ru-c-${color}`),
287
+ style: meterStyle,
192
288
  children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-progress__bar", style: indeterminate ? void 0 : { width: `${percent}%` } })
193
289
  }
194
290
  )
@@ -227,6 +323,98 @@ function Alert({ variant = "info", title, icon, onClose, className, children, ..
227
323
  }
228
324
  );
229
325
  }
326
+ const useBrowserLayoutEffect = typeof document === "undefined" ? react.useEffect : react.useLayoutEffect;
327
+ const TOOLTIP_EDGE = 8;
328
+ const TOOLTIP_COMFORT = 16;
329
+ const ARROW_INSET = 14;
330
+ const oppositeSide = { top: "bottom", bottom: "top", left: "right", right: "left" };
331
+ const AUTO_ORDER = ["top", "bottom", "right", "left"];
332
+ const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
333
+ function visibleArea(el) {
334
+ const area = {
335
+ top: TOOLTIP_EDGE,
336
+ left: TOOLTIP_EDGE,
337
+ right: document.documentElement.clientWidth - TOOLTIP_EDGE,
338
+ bottom: window.innerHeight - TOOLTIP_EDGE
339
+ };
340
+ for (let node = el; node && node !== document.body; node = node.parentElement) {
341
+ const { overflowX, overflowY, position } = getComputedStyle(node);
342
+ if (node !== el) {
343
+ const rect = node.getBoundingClientRect();
344
+ if (overflowX !== "visible") {
345
+ area.left = Math.max(area.left, rect.left + TOOLTIP_EDGE);
346
+ area.right = Math.min(area.right, rect.right - TOOLTIP_EDGE);
347
+ }
348
+ if (overflowY !== "visible") {
349
+ area.top = Math.max(area.top, rect.top + TOOLTIP_EDGE);
350
+ area.bottom = Math.min(area.bottom, rect.bottom - TOOLTIP_EDGE);
351
+ }
352
+ }
353
+ if (position === "fixed") break;
354
+ }
355
+ return area;
356
+ }
357
+ function slideAlong(start, size, low, high, triggerLow, triggerHigh) {
358
+ const nearLow = start < low + TOOLTIP_COMFORT;
359
+ const nearHigh = start + size > high - TOOLTIP_COMFORT;
360
+ if (!nearLow && !nearHigh) return 0;
361
+ const clamped = clamp(start, low, high - size);
362
+ if (nearLow && nearHigh) return clamped - start;
363
+ const snapped = nearHigh ? triggerHigh - size : triggerLow;
364
+ const canSnap = snapped >= low - TOOLTIP_EDGE && snapped + size <= high + TOOLTIP_EDGE;
365
+ return (canSnap ? snapped : clamped) - start;
366
+ }
367
+ const samePosition = (a, b) => a && Object.keys(b).every((key) => a[key] === b[key]);
368
+ function placeTooltip(trigger, bubble, placement) {
369
+ const t = trigger.getBoundingClientRect();
370
+ const w = bubble.offsetWidth;
371
+ const h = bubble.offsetHeight;
372
+ const gap = parseFloat(getComputedStyle(bubble).getPropertyValue("--_gap")) || 10;
373
+ const area = visibleArea(trigger);
374
+ const room = { top: t.top - area.top, bottom: area.bottom - t.bottom, left: t.left - area.left, right: area.right - t.right };
375
+ const needs = (side2) => (side2 === "top" || side2 === "bottom" ? h : w) + gap;
376
+ const fits = (s) => room[s] >= needs(s);
377
+ const aligned = (s) => s === "top" || s === "bottom" ? slideAlong(t.left + t.width / 2 - w / 2, w, area.left, area.right, t.left, t.right) === 0 : slideAlong(t.top + t.height / 2 - h / 2, h, area.top, area.bottom, t.top, t.bottom) === 0;
378
+ const roomiest = AUTO_ORDER.reduce((best, s) => room[s] > room[best] ? s : best);
379
+ let side = placement === "auto" ? AUTO_ORDER.find((s) => fits(s) && aligned(s)) ?? AUTO_ORDER.find(fits) ?? roomiest : placement;
380
+ if (room[side] < needs(side) && room[oppositeSide[side]] > room[side]) side = oppositeSide[side];
381
+ if (room[side] < needs(side) && (side === "left" || side === "right")) {
382
+ const vertical = room.bottom >= room.top ? "bottom" : "top";
383
+ if (room[vertical] >= needs(vertical)) side = vertical;
384
+ }
385
+ if (side === "top" || side === "bottom") {
386
+ const start2 = t.left + t.width / 2 - w / 2;
387
+ const shift2 = slideAlong(start2, w, area.left, area.right, t.left, t.right);
388
+ return { side, shiftX: shift2, shiftY: 0, arrowX: clamp(-shift2, ARROW_INSET - w / 2, w / 2 - ARROW_INSET), arrowY: 0 };
389
+ }
390
+ const start = t.top + t.height / 2 - h / 2;
391
+ const shift = slideAlong(start, h, area.top, area.bottom, t.top, t.bottom);
392
+ return { side, shiftX: 0, shiftY: shift, arrowX: 0, arrowY: clamp(-shift, ARROW_INSET - h / 2, h / 2 - ARROW_INSET) };
393
+ }
394
+ function usePlacement(open, triggerRef, bubbleRef, placement = "top") {
395
+ const [position, setPosition] = react.useState(null);
396
+ const update = () => {
397
+ if (!triggerRef.current || !bubbleRef.current) return;
398
+ const next = placeTooltip(triggerRef.current, bubbleRef.current, placement);
399
+ setPosition((prev) => samePosition(prev, next) ? prev : next);
400
+ };
401
+ const updateRef = react.useRef(update);
402
+ updateRef.current = update;
403
+ useBrowserLayoutEffect(() => {
404
+ if (open) updateRef.current();
405
+ });
406
+ react.useEffect(() => {
407
+ if (!open) return void 0;
408
+ const onChange = () => updateRef.current();
409
+ window.addEventListener("scroll", onChange, true);
410
+ window.addEventListener("resize", onChange);
411
+ return () => {
412
+ window.removeEventListener("scroll", onChange, true);
413
+ window.removeEventListener("resize", onChange);
414
+ };
415
+ }, [open]);
416
+ return position;
417
+ }
230
418
  function useControllableState(value, defaultValue, onChange) {
231
419
  const [internal, setInternal] = react.useState(defaultValue);
232
420
  const isControlled = value !== void 0;
@@ -1086,18 +1274,22 @@ function resolveAvatarColor(color, seed) {
1086
1274
  }
1087
1275
  function AvatarTooltip({ content, description, accent, children }) {
1088
1276
  const [open, setOpen] = react.useState(false);
1277
+ const rootRef = react.useRef(null);
1089
1278
  const bubbleRef = react.useRef(null);
1279
+ const position = usePlacement(open, rootRef, bubbleRef, "top");
1280
+ const sliding = Boolean(position == null ? void 0 : position.shiftX);
1090
1281
  const tiltToward = (e) => {
1091
1282
  const bubble = bubbleRef.current;
1092
1283
  if (!bubble) return;
1093
1284
  const rect = e.currentTarget.getBoundingClientRect();
1094
1285
  const ratio = Math.max(-1, Math.min(1, (e.clientX - (rect.left + rect.width / 2)) / (rect.width / 2)));
1095
- bubble.style.setProperty("--_shift", `${ratio * 10}px`);
1286
+ bubble.style.setProperty("--_shift", sliding ? "0px" : `${ratio * 10}px`);
1096
1287
  bubble.style.setProperty("--_tilt", `${ratio * 12}deg`);
1097
1288
  };
1098
1289
  return /* @__PURE__ */ jsxRuntime.jsxs(
1099
1290
  "span",
1100
1291
  {
1292
+ ref: rootRef,
1101
1293
  className: "ru-avatar-tip",
1102
1294
  onMouseEnter: () => setOpen(true),
1103
1295
  onMouseLeave: () => setOpen(false),
@@ -1107,10 +1299,23 @@ function AvatarTooltip({ content, description, accent, children }) {
1107
1299
  children: [
1108
1300
  children,
1109
1301
  open && // The avatar's accessible name already includes this text, so the bubble is visual only.
1110
- /* @__PURE__ */ jsxRuntime.jsx("span", { ref: bubbleRef, className: "ru-avatar-tip__bubble", "aria-hidden": "true", style: accent ? { "--_accent": accent } : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-avatar-tip__inner", children: [
1111
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-avatar-tip__name", children: content }),
1112
- description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-avatar-tip__desc", children: description })
1113
- ] }) })
1302
+ /* @__PURE__ */ jsxRuntime.jsx(
1303
+ "span",
1304
+ {
1305
+ ref: bubbleRef,
1306
+ className: cn("ru-avatar-tip__bubble", (position == null ? void 0 : position.side) === "bottom" && "ru-avatar-tip__bubble--bottom"),
1307
+ "aria-hidden": "true",
1308
+ style: {
1309
+ ...accent && { "--_accent": accent },
1310
+ ...position && { "--_sx": `${position.shiftX}px`, "--_ax": `${position.arrowX}px` }
1311
+ },
1312
+ children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-avatar-tip__inner", children: [
1313
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-avatar-tip__dots" }),
1314
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-avatar-tip__name", children: content }),
1315
+ description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-avatar-tip__desc", children: description })
1316
+ ] })
1317
+ }
1318
+ )
1114
1319
  ]
1115
1320
  }
1116
1321
  );
@@ -1270,6 +1475,7 @@ function Divider({
1270
1475
  "ru-divider",
1271
1476
  variant !== "solid" && `ru-divider--${variant}`,
1272
1477
  vertical && "ru-divider--vertical",
1478
+ vertical && length !== void 0 && "ru-divider--sized",
1273
1479
  labelled && "ru-divider--label",
1274
1480
  labelled && labelPosition !== "center" && `ru-divider--label-${labelPosition}`,
1275
1481
  !vertical && align !== "center" && `ru-divider--align-${align}`,
@@ -1400,7 +1606,7 @@ function Table({
1400
1606
  ] })
1401
1607
  ] });
1402
1608
  }
1403
- const EXIT_MS = 200;
1609
+ const EXIT_MS$1 = 200;
1404
1610
  function usePresence(open) {
1405
1611
  const [render, setRender] = react.useState(open);
1406
1612
  const [closing, setClosing] = react.useState(false);
@@ -1415,7 +1621,7 @@ function usePresence(open) {
1415
1621
  const timer = setTimeout(() => {
1416
1622
  setRender(false);
1417
1623
  setClosing(false);
1418
- }, EXIT_MS);
1624
+ }, EXIT_MS$1);
1419
1625
  return () => clearTimeout(timer);
1420
1626
  }, [open]);
1421
1627
  return { render, closing };
@@ -1448,7 +1654,7 @@ function Modal({
1448
1654
  const titleId = react.useId();
1449
1655
  const descId = react.useId();
1450
1656
  const { render, closing } = usePresence(open);
1451
- useOverlay(open && mounted, panelRef, onClose, closeOnEsc);
1657
+ useOverlay(open && mounted && render, panelRef, onClose, closeOnEsc);
1452
1658
  if (!render || !mounted) return null;
1453
1659
  const onOverlayMouseDown = (e) => {
1454
1660
  if (!closing && closeOnOverlayClick && e.target === e.currentTarget) onClose();
@@ -1497,7 +1703,7 @@ function Drawer({
1497
1703
  const titleId = react.useId();
1498
1704
  const descId = react.useId();
1499
1705
  const { render, closing } = usePresence(open);
1500
- useOverlay(open && mounted, panelRef, onClose, closeOnEsc);
1706
+ useOverlay(open && mounted && render, panelRef, onClose, closeOnEsc);
1501
1707
  if (!render || !mounted) return null;
1502
1708
  const resolvedSize = typeof size === "number" ? `${size}px` : drawerSizes[size] ?? size;
1503
1709
  return reactDom.createPortal(
@@ -1553,13 +1759,24 @@ function Tooltip({
1553
1759
  textColor,
1554
1760
  open: openProp,
1555
1761
  disabled,
1762
+ onPlacementChange,
1556
1763
  className
1557
1764
  }) {
1558
1765
  const [open, setOpen] = react.useState(false);
1559
1766
  const timer = react.useRef(void 0);
1767
+ const rootRef = react.useRef(null);
1768
+ const bubbleRef = react.useRef(null);
1560
1769
  const id = react.useId();
1561
1770
  const visible = (openProp ?? open) && !disabled && content != null && content !== "";
1562
1771
  react.useEffect(() => () => clearTimeout(timer.current), []);
1772
+ const position = usePlacement(visible, rootRef, bubbleRef, placement);
1773
+ const side = position == null ? void 0 : position.side;
1774
+ const onPlacementChangeRef = react.useRef(onPlacementChange);
1775
+ onPlacementChangeRef.current = onPlacementChange;
1776
+ react.useEffect(() => {
1777
+ var _a;
1778
+ if (side) (_a = onPlacementChangeRef.current) == null ? void 0 : _a.call(onPlacementChangeRef, side);
1779
+ }, [side]);
1563
1780
  const show = () => {
1564
1781
  clearTimeout(timer.current);
1565
1782
  timer.current = setTimeout(() => setOpen(true), delay);
@@ -1574,6 +1791,7 @@ function Tooltip({
1574
1791
  return /* @__PURE__ */ jsxRuntime.jsxs(
1575
1792
  "span",
1576
1793
  {
1794
+ ref: rootRef,
1577
1795
  className: cn("ru-tooltip", className),
1578
1796
  onMouseEnter: show,
1579
1797
  onMouseLeave: hide,
@@ -1585,15 +1803,25 @@ function Tooltip({
1585
1803
  visible && /* @__PURE__ */ jsxRuntime.jsx(
1586
1804
  "span",
1587
1805
  {
1806
+ ref: bubbleRef,
1588
1807
  role: "tooltip",
1589
1808
  id,
1590
1809
  className: cn(
1591
1810
  "ru-tooltip__bubble",
1592
- `ru-tooltip__bubble--${placement}`,
1811
+ `ru-tooltip__bubble--${side ?? (placement === "auto" ? "top" : placement)}`,
1593
1812
  size !== "md" && `ru-tooltip__bubble--${size}`,
1594
1813
  !arrow && "ru-tooltip__bubble--no-arrow"
1595
1814
  ),
1596
- style: color || textColor ? { "--ru-tooltip-bg": color, "--ru-tooltip-text": textColor } : void 0,
1815
+ style: {
1816
+ ...color && { "--ru-tooltip-bg": color },
1817
+ ...textColor && { "--ru-tooltip-text": textColor },
1818
+ ...position && {
1819
+ "--_sx": `${position.shiftX}px`,
1820
+ "--_sy": `${position.shiftY}px`,
1821
+ "--_ax": `${position.arrowX}px`,
1822
+ "--_ay": `${position.arrowY}px`
1823
+ }
1824
+ },
1597
1825
  children: content
1598
1826
  }
1599
1827
  )
@@ -1708,12 +1936,23 @@ function Dropdown({ trigger, items, align = "start", closeOnSelect = true, class
1708
1936
  ] });
1709
1937
  }
1710
1938
  const ToastContext = react.createContext(null);
1711
- function ToastProvider({ children, position = "bottom-right", duration = 4e3, max = 5 }) {
1939
+ const EXIT_MS = 200;
1940
+ function ToastProvider({
1941
+ children,
1942
+ position = "bottom-right",
1943
+ duration = 4e3,
1944
+ max = 5,
1945
+ appearance = "default",
1946
+ animation = "slide",
1947
+ progress = false
1948
+ }) {
1712
1949
  const [toasts, setToasts] = react.useState([]);
1713
1950
  const mounted = useMounted();
1714
1951
  const counter = react.useRef(0);
1715
1952
  const dismiss = react.useCallback((id) => {
1716
- setToasts((list) => id ? list.filter((t) => t.id !== id) : []);
1953
+ const matches = (t) => !id || t.id === id;
1954
+ setToasts((list) => list.map((t) => matches(t) ? { ...t, closing: true } : t));
1955
+ setTimeout(() => setToasts((list) => list.filter((t) => !(matches(t) && t.closing))), EXIT_MS);
1717
1956
  }, []);
1718
1957
  const api = react.useMemo(() => {
1719
1958
  const show = (options) => {
@@ -1733,25 +1972,61 @@ function ToastProvider({ children, position = "bottom-right", duration = 4e3, ma
1733
1972
  return /* @__PURE__ */ jsxRuntime.jsxs(ToastContext.Provider, { value: api, children: [
1734
1973
  children,
1735
1974
  mounted && reactDom.createPortal(
1736
- /* @__PURE__ */ jsxRuntime.jsx("ol", { className: cn("ru-toaster", `ru-toaster--${position}`), "aria-live": "polite", "aria-label": "Notifications", children: toasts.map((t) => /* @__PURE__ */ jsxRuntime.jsx(ToastView, { toast: t, duration: t.duration ?? duration, onDismiss: dismiss }, t.id)) }),
1975
+ // One stack per position in use, so toasts can override the provider's position.
1976
+ [.../* @__PURE__ */ new Set([position, ...toasts.map((t) => t.position ?? position)])].map((spot) => /* @__PURE__ */ jsxRuntime.jsx("ol", { className: cn("ru-toaster", `ru-toaster--${spot}`), "aria-live": "polite", "aria-label": "Notifications", children: toasts.filter((t) => (t.position ?? position) === spot).map((t) => /* @__PURE__ */ jsxRuntime.jsx(
1977
+ ToastView,
1978
+ {
1979
+ toast: t,
1980
+ duration: t.duration ?? duration,
1981
+ appearance: t.appearance ?? appearance,
1982
+ animation: t.animation ?? animation,
1983
+ progress: t.progress ?? progress,
1984
+ onDismiss: dismiss
1985
+ },
1986
+ t.id
1987
+ )) }, spot)),
1737
1988
  document.body
1738
1989
  )
1739
1990
  ] });
1740
1991
  }
1741
- function ToastView({ toast, duration, onDismiss }) {
1992
+ function ToastView({ toast, duration, appearance, animation, progress, onDismiss }) {
1742
1993
  const [paused, setPaused] = react.useState(false);
1743
- const { id, variant = "default", title, description, action } = toast;
1994
+ const { id, variant = "default", title, description, action, closing } = toast;
1995
+ const ref = react.useRef(null);
1996
+ const remaining = react.useRef(duration);
1997
+ const timed = Number.isFinite(duration) && duration > 0;
1744
1998
  react.useEffect(() => {
1745
- if (paused || !Number.isFinite(duration) || duration <= 0) return;
1746
- const timer = setTimeout(() => onDismiss(id), duration);
1747
- return () => clearTimeout(timer);
1748
- }, [id, duration, paused, onDismiss]);
1999
+ remaining.current = duration;
2000
+ }, [id, duration]);
2001
+ react.useEffect(() => {
2002
+ if (paused || closing || !timed) return void 0;
2003
+ const startedAt = Date.now();
2004
+ const timer = setTimeout(() => onDismiss(id), remaining.current);
2005
+ return () => {
2006
+ clearTimeout(timer);
2007
+ remaining.current -= Date.now() - startedAt;
2008
+ };
2009
+ }, [id, duration, paused, closing, timed, onDismiss]);
2010
+ react.useLayoutEffect(() => {
2011
+ if (closing && ref.current) ref.current.style.setProperty("--_h", `${ref.current.offsetHeight}px`);
2012
+ }, [closing]);
1749
2013
  const Icon = variant === "default" ? null : statusIcons[variant];
2014
+ const countdown = progress && timed;
2015
+ const traced = countdown && appearance === "accent";
2016
+ const countdownStyle = { animationDuration: `${duration}ms`, animationPlayState: paused ? "paused" : "running" };
1750
2017
  return /* @__PURE__ */ jsxRuntime.jsxs(
1751
2018
  "li",
1752
2019
  {
2020
+ ref,
1753
2021
  role: "status",
1754
- className: cn("ru-toast", variant !== "default" && `ru-c-${variant}`),
2022
+ className: cn(
2023
+ "ru-toast",
2024
+ `ru-toast--${appearance}`,
2025
+ `ru-toast--in-${animation}`,
2026
+ traced && "ru-toast--traced",
2027
+ closing && "ru-toast--closing",
2028
+ variant !== "default" && `ru-c-${variant}`
2029
+ ),
1755
2030
  onMouseEnter: () => setPaused(true),
1756
2031
  onMouseLeave: () => setPaused(false),
1757
2032
  children: [
@@ -1761,7 +2036,9 @@ function ToastView({ toast, duration, onDismiss }) {
1761
2036
  description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-toast__desc", children: description }),
1762
2037
  action && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-toast__action", children: action })
1763
2038
  ] }),
1764
- /* @__PURE__ */ jsxRuntime.jsx(CloseButton, { onClick: () => onDismiss(id), label: "Dismiss notification" })
2039
+ /* @__PURE__ */ jsxRuntime.jsx(CloseButton, { onClick: () => onDismiss(id), label: "Dismiss notification" }),
2040
+ traced && /* @__PURE__ */ jsxRuntime.jsx("svg", { className: "ru-toast__trace", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("rect", { width: "100%", height: "100%", rx: "13", pathLength: "1", style: countdownStyle }) }),
2041
+ countdown && !traced && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-toast__progress", "aria-hidden": "true", style: countdownStyle })
1765
2042
  ]
1766
2043
  }
1767
2044
  );
@@ -1862,7 +2139,18 @@ function SpotlightCard({ color, className, style, onMouseMove, children, ...rest
1862
2139
  }
1863
2140
  );
1864
2141
  }
1865
- const semanticColors$1 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
2142
+ function DotText({ as: Tag = "span", dot = 4, color, glow = false, className, style, children, ...rest }) {
2143
+ return /* @__PURE__ */ jsxRuntime.jsx(
2144
+ Tag,
2145
+ {
2146
+ className: cn("ru-dot-text", glow && "ru-dot-text--glow", className),
2147
+ style: { "--_d": `${dot}px`, ...color && { color }, ...style },
2148
+ ...rest,
2149
+ children
2150
+ }
2151
+ );
2152
+ }
2153
+ const semanticColors$4 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
1866
2154
  function Pager({ onPrev, onNext, prevLabel, nextLabel }) {
1867
2155
  if (!onPrev && !onNext) return null;
1868
2156
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-frame-card__pager", children: [
@@ -1900,7 +2188,7 @@ function FrameCard({
1900
2188
  {
1901
2189
  className: cn("ru-frame-card", onFrame && "ru-frame-card--title-frame", className),
1902
2190
  style: {
1903
- "--_fc": semanticColors$1.has(color) ? `var(--ru-${color})` : color,
2191
+ "--_fc": semanticColors$4.has(color) ? `var(--ru-${color})` : color,
1904
2192
  ...textColor && { "--_fc-on": textColor, "--_fc-ink": textColor },
1905
2193
  ...style
1906
2194
  },
@@ -1939,7 +2227,7 @@ function FrameCard({
1939
2227
  }
1940
2228
  const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1941
2229
  const WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
1942
- const semanticColors = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
2230
+ const semanticColors$3 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
1943
2231
  const dayKey = (d) => d.getFullYear() * 1e4 + d.getMonth() * 100 + d.getDate();
1944
2232
  const sameDay = (a, b) => Boolean(a && b) && dayKey(a) === dayKey(b);
1945
2233
  function Calendar({
@@ -1999,7 +2287,7 @@ function Calendar({
1999
2287
  next.focus();
2000
2288
  }
2001
2289
  };
2002
- const ink = color ? semanticColors.has(color) ? `var(--ru-${color})` : color : void 0;
2290
+ const ink = color ? semanticColors$3.has(color) ? `var(--ru-${color})` : color : void 0;
2003
2291
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-calendar", className), style: ink ? { "--_cc": ink, ...style } : style, ...rest, children: [
2004
2292
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-calendar__head", children: [
2005
2293
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ru-calendar__nav", "aria-label": "Previous month", onClick: () => shift(-1), children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeftIcon, {}) }),
@@ -2054,6 +2342,638 @@ function Calendar({
2054
2342
  }) })
2055
2343
  ] });
2056
2344
  }
2345
+ const normalize = (options) => options.map((o) => typeof o === "object" ? o : { label: String(o), value: o });
2346
+ function SegmentedControl({
2347
+ options,
2348
+ value,
2349
+ defaultValue,
2350
+ onChange,
2351
+ size = "md",
2352
+ fullWidth = false,
2353
+ pill = false,
2354
+ disabled = false,
2355
+ className,
2356
+ style,
2357
+ ...rest
2358
+ }) {
2359
+ var _a;
2360
+ const items = normalize(options);
2361
+ const firstEnabled = (_a = items.find((i) => !i.disabled)) == null ? void 0 : _a.value;
2362
+ const [active, setActive] = useControllableState(value, defaultValue ?? firstEnabled, onChange);
2363
+ const rootRef = react.useRef(null);
2364
+ const [thumb, setThumb] = react.useState(null);
2365
+ const activeIndex = items.findIndex((i) => i.value === active);
2366
+ react.useEffect(() => {
2367
+ const root = rootRef.current;
2368
+ if (!root) return void 0;
2369
+ const measure = () => {
2370
+ const el = root.querySelectorAll('[role="radio"]')[activeIndex];
2371
+ setThumb(el ? { x: el.offsetLeft, w: el.offsetWidth } : null);
2372
+ };
2373
+ measure();
2374
+ if (typeof ResizeObserver === "undefined") return void 0;
2375
+ const ro = new ResizeObserver(measure);
2376
+ ro.observe(root);
2377
+ return () => ro.disconnect();
2378
+ }, [activeIndex, items.length]);
2379
+ const move = (from, step) => {
2380
+ var _a2;
2381
+ const buttons = rootRef.current.querySelectorAll('[role="radio"]');
2382
+ for (let n = 1; n <= items.length; n++) {
2383
+ const i = (from + step * n + items.length) % items.length;
2384
+ if (!items[i].disabled) {
2385
+ setActive(items[i].value);
2386
+ (_a2 = buttons[i]) == null ? void 0 : _a2.focus();
2387
+ return;
2388
+ }
2389
+ }
2390
+ };
2391
+ const onKeyDown = (e, index) => {
2392
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") move(index, 1);
2393
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp") move(index, -1);
2394
+ else return;
2395
+ e.preventDefault();
2396
+ };
2397
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2398
+ "div",
2399
+ {
2400
+ ref: rootRef,
2401
+ role: "radiogroup",
2402
+ "aria-disabled": disabled || void 0,
2403
+ className: cn(
2404
+ "ru-segmented",
2405
+ `ru-segmented--${size}`,
2406
+ fullWidth && "ru-segmented--full",
2407
+ pill && "ru-segmented--pill",
2408
+ thumb && "ru-segmented--ready",
2409
+ className
2410
+ ),
2411
+ style: thumb ? { "--_x": `${thumb.x}px`, "--_w": `${thumb.w}px`, ...style } : style,
2412
+ ...rest,
2413
+ children: [
2414
+ thumb && activeIndex >= 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-segmented__thumb", "aria-hidden": "true" }),
2415
+ items.map((item, i) => {
2416
+ const selected = i === activeIndex;
2417
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2418
+ "button",
2419
+ {
2420
+ type: "button",
2421
+ role: "radio",
2422
+ "aria-checked": selected,
2423
+ tabIndex: selected || activeIndex < 0 && i === 0 ? 0 : -1,
2424
+ disabled: disabled || item.disabled,
2425
+ className: "ru-segmented__item",
2426
+ onClick: () => setActive(item.value),
2427
+ onKeyDown: (e) => onKeyDown(e, i),
2428
+ children: [
2429
+ item.icon,
2430
+ item.label
2431
+ ]
2432
+ },
2433
+ String(item.value)
2434
+ );
2435
+ })
2436
+ ]
2437
+ }
2438
+ );
2439
+ }
2440
+ const semanticColors$2 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
2441
+ function ProgressRing({
2442
+ value = 0,
2443
+ max = 100,
2444
+ size = 48,
2445
+ thickness = 4,
2446
+ color = "primary",
2447
+ trackColor,
2448
+ showValue = false,
2449
+ label,
2450
+ className,
2451
+ style,
2452
+ children,
2453
+ ...rest
2454
+ }) {
2455
+ const percent = Math.min(100, Math.max(0, value / max * 100));
2456
+ const r = (size - thickness) / 2;
2457
+ const circumference = 2 * Math.PI * r;
2458
+ const ink = semanticColors$2.has(color) ? `var(--ru-${color})` : color;
2459
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2460
+ "div",
2461
+ {
2462
+ role: "progressbar",
2463
+ "aria-valuemin": 0,
2464
+ "aria-valuemax": max,
2465
+ "aria-valuenow": value,
2466
+ "aria-label": label,
2467
+ className: cn("ru-ring", className),
2468
+ style: { width: size, height: size, "--_c": ink, ...trackColor && { "--_track": trackColor }, ...style },
2469
+ ...rest,
2470
+ children: [
2471
+ /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, "aria-hidden": "true", children: [
2472
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { className: "ru-ring__track", cx: size / 2, cy: size / 2, r, strokeWidth: thickness }),
2473
+ /* @__PURE__ */ jsxRuntime.jsx(
2474
+ "circle",
2475
+ {
2476
+ className: "ru-ring__bar",
2477
+ cx: size / 2,
2478
+ cy: size / 2,
2479
+ r,
2480
+ strokeWidth: thickness,
2481
+ strokeDasharray: circumference,
2482
+ strokeDashoffset: circumference * (1 - percent / 100)
2483
+ }
2484
+ )
2485
+ ] }),
2486
+ (children !== void 0 || showValue) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-ring__label", style: { fontSize: Math.max(10, Math.round(size * 0.24)) }, children: children ?? `${Math.round(percent)}%` })
2487
+ ]
2488
+ }
2489
+ );
2490
+ }
2491
+ const semanticColors$1 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
2492
+ function Stat({
2493
+ label,
2494
+ value,
2495
+ trend,
2496
+ trendLabel,
2497
+ invertTrend = false,
2498
+ helpText,
2499
+ size = "md",
2500
+ align = "start",
2501
+ className,
2502
+ ...rest
2503
+ }) {
2504
+ const hasTrend = typeof trend === "number";
2505
+ const up = hasTrend && trend >= 0;
2506
+ const good = up !== invertTrend;
2507
+ const TrendIcon = up ? TrendUpIcon : TrendDownIcon;
2508
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-stat", `ru-stat--${size}`, align === "end" && "ru-stat--end", className), ...rest, children: [
2509
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-stat__top", children: [
2510
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-stat__label", children: label }),
2511
+ hasTrend && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("ru-stat__trend", good ? "ru-stat__trend--good" : "ru-stat__trend--bad"), children: [
2512
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-stat__trend-icon", children: /* @__PURE__ */ jsxRuntime.jsx(TrendIcon, {}) }),
2513
+ trendLabel ?? `${Math.abs(trend).toLocaleString()}%`,
2514
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-sr-only", children: up ? " increase" : " decrease" })
2515
+ ] })
2516
+ ] }),
2517
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-stat__value", children: value }),
2518
+ helpText && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-stat__help", children: helpText })
2519
+ ] });
2520
+ }
2521
+ function DateTile({ date, color = "primary", size = "md", className, style, ...rest }) {
2522
+ const d = date instanceof Date ? date : new Date(date);
2523
+ const ink = semanticColors$1.has(color) ? `var(--ru-${color})` : color;
2524
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2525
+ "time",
2526
+ {
2527
+ dateTime: d.toISOString().slice(0, 10),
2528
+ "aria-label": d.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric" }),
2529
+ className: cn("ru-date-tile", `ru-date-tile--${size}`, className),
2530
+ style: { "--_c": ink, ...style },
2531
+ ...rest,
2532
+ children: [
2533
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-date-tile__month", "aria-hidden": "true", children: d.toLocaleDateString("en-US", { month: "short" }) }),
2534
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-date-tile__day", "aria-hidden": "true", children: d.getDate() })
2535
+ ]
2536
+ }
2537
+ );
2538
+ }
2539
+ function matchesAccept(file, accept) {
2540
+ if (!accept) return true;
2541
+ return accept.split(",").some((rule) => {
2542
+ const r = rule.trim().toLowerCase();
2543
+ if (!r) return false;
2544
+ if (r.startsWith(".")) return file.name.toLowerCase().endsWith(r);
2545
+ if (r.endsWith("/*")) return file.type.toLowerCase().startsWith(r.slice(0, -1));
2546
+ return file.type.toLowerCase() === r;
2547
+ });
2548
+ }
2549
+ function formatBytes(bytes) {
2550
+ if (typeof bytes !== "number") return bytes;
2551
+ if (bytes < 1024) return `${bytes} B`;
2552
+ const units = ["KB", "MB", "GB"];
2553
+ let n = bytes / 1024;
2554
+ let i = 0;
2555
+ while (n >= 1024 && i < units.length - 1) {
2556
+ n /= 1024;
2557
+ i++;
2558
+ }
2559
+ return `${Number(n.toFixed(1))}${units[i]}`;
2560
+ }
2561
+ function Dropzone({
2562
+ onFiles,
2563
+ onReject,
2564
+ accept,
2565
+ multiple = true,
2566
+ maxSize,
2567
+ disabled = false,
2568
+ icon,
2569
+ title = "Drag & drop files here",
2570
+ description,
2571
+ className,
2572
+ children,
2573
+ ...rest
2574
+ }) {
2575
+ const inputRef = react.useRef(null);
2576
+ const [dragging, setDragging] = react.useState(false);
2577
+ const handle = (list) => {
2578
+ const files = Array.from(list ?? []);
2579
+ const accepted = [];
2580
+ const rejected = [];
2581
+ for (const file of multiple ? files : files.slice(0, 1)) {
2582
+ if (!matchesAccept(file, accept)) rejected.push({ file, reason: "type" });
2583
+ else if (maxSize && file.size > maxSize) rejected.push({ file, reason: "size" });
2584
+ else accepted.push(file);
2585
+ }
2586
+ if (accepted.length) onFiles == null ? void 0 : onFiles(accepted);
2587
+ if (rejected.length) onReject == null ? void 0 : onReject(rejected);
2588
+ };
2589
+ const open = () => {
2590
+ var _a;
2591
+ return !disabled && ((_a = inputRef.current) == null ? void 0 : _a.click());
2592
+ };
2593
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2594
+ "div",
2595
+ {
2596
+ role: "button",
2597
+ tabIndex: disabled ? -1 : 0,
2598
+ "aria-disabled": disabled || void 0,
2599
+ className: cn("ru-dropzone", dragging && "ru-dropzone--active", disabled && "ru-dropzone--disabled", className),
2600
+ onClick: open,
2601
+ onKeyDown: (e) => {
2602
+ if (e.key === "Enter" || e.key === " ") {
2603
+ e.preventDefault();
2604
+ open();
2605
+ }
2606
+ },
2607
+ onDragEnter: (e) => {
2608
+ e.preventDefault();
2609
+ if (!disabled) setDragging(true);
2610
+ },
2611
+ onDragOver: (e) => e.preventDefault(),
2612
+ onDragLeave: (e) => {
2613
+ if (!e.currentTarget.contains(e.relatedTarget)) setDragging(false);
2614
+ },
2615
+ onDrop: (e) => {
2616
+ e.preventDefault();
2617
+ setDragging(false);
2618
+ if (!disabled) handle(e.dataTransfer.files);
2619
+ },
2620
+ ...rest,
2621
+ children: [
2622
+ /* @__PURE__ */ jsxRuntime.jsx(
2623
+ "input",
2624
+ {
2625
+ ref: inputRef,
2626
+ type: "file",
2627
+ hidden: true,
2628
+ accept,
2629
+ multiple,
2630
+ disabled,
2631
+ onChange: (e) => {
2632
+ handle(e.target.files);
2633
+ e.target.value = "";
2634
+ }
2635
+ }
2636
+ ),
2637
+ children ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2638
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-dropzone__icon", children: icon ?? /* @__PURE__ */ jsxRuntime.jsx(UploadCloudIcon, {}) }),
2639
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-dropzone__title", children: title }),
2640
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-dropzone__desc", children: description ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2641
+ "or ",
2642
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-dropzone__browse", children: "browse" }),
2643
+ " to choose files",
2644
+ maxSize ? ` (max ${formatBytes(maxSize)})` : ""
2645
+ ] }) })
2646
+ ] })
2647
+ ]
2648
+ }
2649
+ );
2650
+ }
2651
+ const statusText = { uploading: "Uploading…", complete: "Completed", error: "Failed" };
2652
+ function FileItem({
2653
+ name,
2654
+ size,
2655
+ status = "complete",
2656
+ progress = 0,
2657
+ icon,
2658
+ error,
2659
+ onRemove,
2660
+ action,
2661
+ removeLabel,
2662
+ className,
2663
+ ...rest
2664
+ }) {
2665
+ const uploading = status === "uploading";
2666
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-file-item", `ru-file-item--${status}`, className), ...rest, children: [
2667
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-file-item__icon", children: icon ?? /* @__PURE__ */ jsxRuntime.jsx(FileIcon, {}) }),
2668
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-file-item__body", children: [
2669
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-file-item__name", title: name, children: name }),
2670
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-file-item__meta", children: [
2671
+ size !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatBytes(size) }),
2672
+ size !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-file-item__dot", "aria-hidden": "true" }),
2673
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-file-item__status", children: status === "error" && error ? error : statusText[status] })
2674
+ ] }),
2675
+ uploading && /* @__PURE__ */ jsxRuntime.jsx(
2676
+ "div",
2677
+ {
2678
+ className: "ru-file-item__bar",
2679
+ role: "progressbar",
2680
+ "aria-valuemin": 0,
2681
+ "aria-valuemax": 100,
2682
+ "aria-valuenow": progress,
2683
+ "aria-label": `Uploading ${name}`,
2684
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { style: { width: `${progress}%` } })
2685
+ }
2686
+ )
2687
+ ] }),
2688
+ uploading && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-file-item__percent", children: [
2689
+ Math.round(progress),
2690
+ "%"
2691
+ ] }),
2692
+ action,
2693
+ onRemove && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ru-file-item__remove", "aria-label": removeLabel ?? `Remove ${name}`, onClick: onRemove, children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {}) })
2694
+ ] });
2695
+ }
2696
+ const UndoIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
2697
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 14 4 9l5-5" }),
2698
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 0 11H11" })
2699
+ ] });
2700
+ const EraserIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
2701
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m7 21-4.3-4.3a1 1 0 0 1 0-1.4l10-10a1 1 0 0 1 1.4 0l5.6 5.6a1 1 0 0 1 0 1.4L11 21" }),
2702
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M22 21H7M5 11l9 9" })
2703
+ ] });
2704
+ const SignaturePad = react.forwardRef(function SignaturePad2({
2705
+ onChange,
2706
+ strokeColor,
2707
+ strokeWidth = 2.5,
2708
+ height = 180,
2709
+ placeholder = "Sign here",
2710
+ showControls = true,
2711
+ undoLabel = "Undo",
2712
+ clearLabel = "Clear",
2713
+ actions,
2714
+ disabled = false,
2715
+ className,
2716
+ style,
2717
+ ...rest
2718
+ }, ref) {
2719
+ const canvasRef = react.useRef(null);
2720
+ const strokes = react.useRef([]);
2721
+ const drawing = react.useRef(null);
2722
+ const [count, setCount] = react.useState(0);
2723
+ const ink = react.useCallback(() => strokeColor ?? getComputedStyle(canvasRef.current).color, [strokeColor]);
2724
+ const drawStroke = react.useCallback(
2725
+ (ctx, points) => {
2726
+ ctx.strokeStyle = ink();
2727
+ ctx.fillStyle = ctx.strokeStyle;
2728
+ if (points.length === 1) {
2729
+ ctx.beginPath();
2730
+ ctx.arc(points[0].x, points[0].y, strokeWidth / 2, 0, Math.PI * 2);
2731
+ ctx.fill();
2732
+ return;
2733
+ }
2734
+ for (let i = 1; i < points.length; i++) {
2735
+ const a = points[i - 1];
2736
+ const b = points[i];
2737
+ const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
2738
+ ctx.lineWidth = b.w;
2739
+ ctx.beginPath();
2740
+ ctx.moveTo(a.mx ?? a.x, a.my ?? a.y);
2741
+ ctx.quadraticCurveTo(a.x, a.y, mid.x, mid.y);
2742
+ ctx.stroke();
2743
+ b.mx = mid.x;
2744
+ b.my = mid.y;
2745
+ }
2746
+ },
2747
+ [ink, strokeWidth]
2748
+ );
2749
+ const redraw = react.useCallback(() => {
2750
+ const canvas = canvasRef.current;
2751
+ if (!canvas) return;
2752
+ const ctx = canvas.getContext("2d");
2753
+ if (!ctx) return;
2754
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
2755
+ ctx.lineCap = "round";
2756
+ ctx.lineJoin = "round";
2757
+ strokes.current.forEach((s) => drawStroke(ctx, s.map(({ x, y, w }) => ({ x, y, w }))));
2758
+ }, [drawStroke]);
2759
+ react.useEffect(() => {
2760
+ const canvas = canvasRef.current;
2761
+ if (!canvas) return void 0;
2762
+ const fit = () => {
2763
+ var _a;
2764
+ const dpr = window.devicePixelRatio || 1;
2765
+ const { width, height: h } = canvas.getBoundingClientRect();
2766
+ canvas.width = Math.max(1, Math.round(width * dpr));
2767
+ canvas.height = Math.max(1, Math.round(h * dpr));
2768
+ (_a = canvas.getContext("2d")) == null ? void 0 : _a.setTransform(dpr, 0, 0, dpr, 0, 0);
2769
+ redraw();
2770
+ };
2771
+ fit();
2772
+ if (typeof ResizeObserver === "undefined") return void 0;
2773
+ const ro = new ResizeObserver(fit);
2774
+ ro.observe(canvas);
2775
+ return () => ro.disconnect();
2776
+ }, [redraw]);
2777
+ const emit = () => onChange == null ? void 0 : onChange(strokes.current.length ? canvasRef.current.toDataURL("image/png") : null);
2778
+ const point = (e) => {
2779
+ const rect = canvasRef.current.getBoundingClientRect();
2780
+ return { x: e.clientX - rect.left, y: e.clientY - rect.top, t: e.timeStamp };
2781
+ };
2782
+ const onPointerDown = (e) => {
2783
+ if (disabled || e.pointerType === "mouse" && e.button !== 0) return;
2784
+ e.currentTarget.setPointerCapture(e.pointerId);
2785
+ const p = { ...point(e), w: strokeWidth };
2786
+ drawing.current = [p];
2787
+ const ctx = canvasRef.current.getContext("2d");
2788
+ ctx.lineCap = "round";
2789
+ ctx.lineJoin = "round";
2790
+ drawStroke(ctx, [p]);
2791
+ };
2792
+ const onPointerMove = (e) => {
2793
+ const stroke = drawing.current;
2794
+ if (!stroke) return;
2795
+ const prev = stroke[stroke.length - 1];
2796
+ const p = point(e);
2797
+ const dist = Math.hypot(p.x - prev.x, p.y - prev.y);
2798
+ if (dist < 1.5) return;
2799
+ const speed = dist / Math.max(1, p.t - prev.t);
2800
+ const target = strokeWidth * Math.max(0.45, Math.min(1.35, 1.4 - speed * 0.45));
2801
+ p.w = prev.w * 0.6 + target * 0.4;
2802
+ stroke.push(p);
2803
+ drawStroke(canvasRef.current.getContext("2d"), stroke.slice(-3));
2804
+ };
2805
+ const onPointerUp = () => {
2806
+ if (!drawing.current) return;
2807
+ strokes.current = [...strokes.current, drawing.current];
2808
+ drawing.current = null;
2809
+ setCount(strokes.current.length);
2810
+ emit();
2811
+ };
2812
+ const undo = () => {
2813
+ strokes.current = strokes.current.slice(0, -1);
2814
+ setCount(strokes.current.length);
2815
+ redraw();
2816
+ emit();
2817
+ };
2818
+ const clear = () => {
2819
+ strokes.current = [];
2820
+ setCount(0);
2821
+ redraw();
2822
+ emit();
2823
+ };
2824
+ react.useImperativeHandle(ref, () => ({
2825
+ clear,
2826
+ undo,
2827
+ isEmpty: () => strokes.current.length === 0,
2828
+ toDataURL: (type = "image/png") => canvasRef.current.toDataURL(type)
2829
+ }));
2830
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-signature", disabled && "ru-signature--disabled", className), style, ...rest, children: [
2831
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-signature__canvas-wrap", style: { height }, children: [
2832
+ count === 0 && placeholder && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-signature__placeholder", "aria-hidden": "true", children: placeholder }),
2833
+ /* @__PURE__ */ jsxRuntime.jsx(
2834
+ "canvas",
2835
+ {
2836
+ ref: canvasRef,
2837
+ className: "ru-signature__canvas",
2838
+ role: "img",
2839
+ "aria-label": count ? "Signature drawn" : "Signature pad, empty",
2840
+ onPointerDown,
2841
+ onPointerMove,
2842
+ onPointerUp,
2843
+ onPointerCancel: onPointerUp
2844
+ }
2845
+ ),
2846
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-signature__baseline", "aria-hidden": "true" })
2847
+ ] }),
2848
+ (showControls || actions) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-signature__bar", children: [
2849
+ showControls && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2850
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "ru-signature__btn", onClick: undo, disabled: disabled || count === 0, children: [
2851
+ /* @__PURE__ */ jsxRuntime.jsx(UndoIcon, {}),
2852
+ undoLabel
2853
+ ] }),
2854
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-signature__sep", "aria-hidden": "true" }),
2855
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "ru-signature__btn", onClick: clear, disabled: disabled || count === 0, children: [
2856
+ /* @__PURE__ */ jsxRuntime.jsx(EraserIcon, {}),
2857
+ clearLabel
2858
+ ] })
2859
+ ] }),
2860
+ actions && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-signature__actions", children: actions })
2861
+ ] })
2862
+ ] });
2863
+ });
2864
+ const statusLabels = {
2865
+ running: "In progress",
2866
+ queued: "Queued",
2867
+ pending: "Not started",
2868
+ partial: "Partly done",
2869
+ complete: "Complete",
2870
+ error: "Failed"
2871
+ };
2872
+ function StatusIcon({ status = "pending", progress = 50, size = 20, label, className, style, ...rest }) {
2873
+ const r = 8;
2874
+ const c = 2 * Math.PI * r;
2875
+ return /* @__PURE__ */ jsxRuntime.jsx(
2876
+ "span",
2877
+ {
2878
+ role: "img",
2879
+ "aria-label": label ?? statusLabels[status] ?? status,
2880
+ className: cn("ru-status-icon", `ru-status-icon--${status}`, className),
2881
+ style: { width: size, height: size, ...style },
2882
+ ...rest,
2883
+ children: /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 20 20", width: size, height: size, "aria-hidden": "true", children: status === "complete" || status === "error" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2884
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "10", cy: "10", r: "10", className: "ru-status-icon__fill" }),
2885
+ status === "complete" ? /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 10.2 2.6 2.6L14 7.4", className: "ru-status-icon__glyph" }) : /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m7 7 6 6M13 7l-6 6", className: "ru-status-icon__glyph" })
2886
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2887
+ /* @__PURE__ */ jsxRuntime.jsx(
2888
+ "circle",
2889
+ {
2890
+ cx: "10",
2891
+ cy: "10",
2892
+ r,
2893
+ className: "ru-status-icon__ring",
2894
+ strokeDasharray: status === "running" || status === "queued" ? "2.6 2.4" : void 0
2895
+ }
2896
+ ),
2897
+ status === "partial" && /* @__PURE__ */ jsxRuntime.jsx(
2898
+ "circle",
2899
+ {
2900
+ cx: "10",
2901
+ cy: "10",
2902
+ r,
2903
+ className: "ru-status-icon__arc",
2904
+ strokeDasharray: c,
2905
+ strokeDashoffset: c * (1 - Math.min(100, Math.max(0, progress)) / 100)
2906
+ }
2907
+ )
2908
+ ] }) })
2909
+ }
2910
+ );
2911
+ }
2912
+ function StepList({ items, connector = true, dividers = true, className, ...rest }) {
2913
+ return /* @__PURE__ */ jsxRuntime.jsx("ol", { className: cn("ru-step-list", !dividers && "ru-step-list--plain", className), ...rest, children: items.map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs(
2914
+ "li",
2915
+ {
2916
+ className: cn("ru-step-list__item", `ru-step-list__item--${item.status ?? "pending"}`),
2917
+ "aria-current": item.status === "running" ? "step" : void 0,
2918
+ children: [
2919
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-step-list__rail", children: [
2920
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-step-list__icon", children: /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { status: item.status, progress: item.progress, size: 22 }) }),
2921
+ connector && i < items.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-step-list__line", "aria-hidden": "true" })
2922
+ ] }),
2923
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-step-list__body", children: [
2924
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-step-list__title", children: item.title }),
2925
+ item.description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-step-list__desc", children: item.description })
2926
+ ] }),
2927
+ item.meta && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-step-list__meta", children: item.meta })
2928
+ ]
2929
+ },
2930
+ item.key ?? i
2931
+ )) });
2932
+ }
2933
+ function StackedProgress({ segments, max, size = "md", showLegend = true, gap = 4, label, className, ...rest }) {
2934
+ const total = max ?? segments.reduce((sum, s) => sum + s.value, 0);
2935
+ const summary = segments.map((s) => `${s.label} ${Math.round(s.value / total * 100)}%`).join(", ");
2936
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-stacked", `ru-stacked--${size}`, className), ...rest, children: [
2937
+ (label || showLegend) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-stacked__head", children: [
2938
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-stacked__label", children: label }),
2939
+ showLegend && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "ru-stacked__legend", children: segments.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
2940
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-stacked__dot", style: { background: s.color } }),
2941
+ s.label
2942
+ ] }, s.label)) })
2943
+ ] }),
2944
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-stacked__bar", role: "img", "aria-label": summary, style: { gap }, children: [
2945
+ segments.map((s) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-stacked__seg", title: `${s.label}: ${s.value}`, style: { flexGrow: s.value, background: s.color } }, s.label)),
2946
+ max !== void 0 && total > segments.reduce((sum, s) => sum + s.value, 0) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-stacked__rest", style: { flexGrow: total - segments.reduce((sum, s) => sum + s.value, 0) } })
2947
+ ] })
2948
+ ] });
2949
+ }
2950
+ const semanticColors = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
2951
+ function IconTile({
2952
+ color = "primary",
2953
+ size = 48,
2954
+ radius,
2955
+ badge,
2956
+ glow = true,
2957
+ label,
2958
+ className,
2959
+ style,
2960
+ children,
2961
+ ...rest
2962
+ }) {
2963
+ const ink = semanticColors.has(color) ? `var(--ru-${color})` : color;
2964
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2965
+ "span",
2966
+ {
2967
+ className: cn("ru-icon-tile", glow && "ru-icon-tile--glow", className),
2968
+ style: { "--_c": ink, "--_s": `${size}px`, ...radius !== void 0 && { "--_r": typeof radius === "number" ? `${radius}px` : radius }, ...style },
2969
+ ...rest,
2970
+ children: [
2971
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-icon-tile__face", role: label ? "img" : void 0, "aria-label": label, "aria-hidden": label ? void 0 : true, children }),
2972
+ badge && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-icon-tile__badge", "aria-hidden": "true", children: badge })
2973
+ ]
2974
+ }
2975
+ );
2976
+ }
2057
2977
  exports.Accordion = Accordion;
2058
2978
  exports.Alert = Alert;
2059
2979
  exports.Avatar = Avatar;
@@ -2068,10 +2988,15 @@ exports.CardBody = CardBody;
2068
2988
  exports.CardFooter = CardFooter;
2069
2989
  exports.CardHeader = CardHeader;
2070
2990
  exports.Checkbox = Checkbox;
2991
+ exports.DateTile = DateTile;
2071
2992
  exports.Divider = Divider;
2993
+ exports.DotText = DotText;
2072
2994
  exports.Drawer = Drawer;
2073
2995
  exports.Dropdown = Dropdown;
2996
+ exports.Dropzone = Dropzone;
2997
+ exports.FileItem = FileItem;
2074
2998
  exports.FrameCard = FrameCard;
2999
+ exports.IconTile = IconTile;
2075
3000
  exports.Input = Input;
2076
3001
  exports.Kbd = Kbd;
2077
3002
  exports.Modal = Modal;
@@ -2079,12 +3004,19 @@ exports.NativeSelect = NativeSelect;
2079
3004
  exports.Pagination = Pagination;
2080
3005
  exports.Pattern = Pattern;
2081
3006
  exports.Progress = Progress;
3007
+ exports.ProgressRing = ProgressRing;
2082
3008
  exports.Radio = Radio;
2083
3009
  exports.RadioGroup = RadioGroup;
3010
+ exports.SegmentedControl = SegmentedControl;
2084
3011
  exports.Select = Select;
3012
+ exports.SignaturePad = SignaturePad;
2085
3013
  exports.Skeleton = Skeleton;
2086
3014
  exports.Spinner = Spinner;
2087
3015
  exports.SpotlightCard = SpotlightCard;
3016
+ exports.StackedProgress = StackedProgress;
3017
+ exports.Stat = Stat;
3018
+ exports.StatusIcon = StatusIcon;
3019
+ exports.StepList = StepList;
2088
3020
  exports.Switch = Switch;
2089
3021
  exports.Table = Table;
2090
3022
  exports.Tabs = Tabs;
@@ -2092,6 +3024,7 @@ exports.Textarea = Textarea;
2092
3024
  exports.ToastProvider = ToastProvider;
2093
3025
  exports.Tooltip = Tooltip;
2094
3026
  exports.cn = cn;
3027
+ exports.formatBytes = formatBytes;
2095
3028
  exports.useClickOutside = useClickOutside;
2096
3029
  exports.useControllableState = useControllableState;
2097
3030
  exports.useDisclosure = useDisclosure;