reusify-ui 2.1.0 → 2.2.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.
@@ -10,7 +10,7 @@ const legacyVariants = {
10
10
  danger: ["solid", "danger"],
11
11
  success: ["solid", "success"]
12
12
  };
13
- const semanticColors$2 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
13
+ const semanticColors$5 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
14
14
  const radii = { none: "0", sm: "6px", md: "10px", lg: "14px", xl: "18px", "2xl": "24px", full: "9999px" };
15
15
  function resolveRadius(radius) {
16
16
  if (radius === void 0) return void 0;
@@ -48,7 +48,7 @@ const Button = forwardRef(function Button2({
48
48
  onPointerMove == null ? void 0 : onPointerMove(e);
49
49
  } : onPointerMove;
50
50
  const resolvedColor = color ?? legacyColor ?? "primary";
51
- const customColor = !semanticColors$2.has(resolvedColor);
51
+ const customColor = !semanticColors$5.has(resolvedColor);
52
52
  const classes = cn(
53
53
  "ru-btn",
54
54
  `ru-btn--${kind}`,
@@ -143,6 +143,16 @@ const UserIcon = (p) => /* @__PURE__ */ jsxs("svg", { ...base, ...p, children: [
143
143
  /* @__PURE__ */ jsx("circle", { cx: "12", cy: "8", r: "4" }),
144
144
  /* @__PURE__ */ jsx("path", { d: "M4 21a8 8 0 0 1 16 0" })
145
145
  ] });
146
+ const UploadCloudIcon = (p) => /* @__PURE__ */ jsxs("svg", { ...base, ...p, children: [
147
+ /* @__PURE__ */ jsx("path", { d: "M4 14.9A7 7 0 1 1 15.7 8h1.8a4.5 4.5 0 0 1 2.5 8.2" }),
148
+ /* @__PURE__ */ jsx("path", { d: "M12 12v9M16 16l-4-4-4 4" })
149
+ ] });
150
+ const FileIcon = (p) => /* @__PURE__ */ jsxs("svg", { ...base, ...p, children: [
151
+ /* @__PURE__ */ jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
152
+ /* @__PURE__ */ jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" })
153
+ ] });
154
+ const TrendUpIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base, ...p, children: /* @__PURE__ */ jsx("path", { d: "M12 19V5M5 12l7-7 7 7" }) });
155
+ const TrendDownIcon = (p) => /* @__PURE__ */ jsx("svg", { ...base, ...p, children: /* @__PURE__ */ jsx("path", { d: "M12 5v14M19 12l-7 7-7-7" }) });
146
156
  const statusIcons = {
147
157
  info: InfoIcon,
148
158
  success: SuccessIcon,
@@ -158,11 +168,17 @@ function Spinner({ size = "md", color = "primary", label = "Loading", className,
158
168
  label && /* @__PURE__ */ jsx("span", { className: "ru-sr-only", children: label })
159
169
  ] });
160
170
  }
171
+ const progressColors = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
161
172
  function Progress({
162
173
  value,
163
174
  max = 100,
164
175
  size = "md",
165
176
  color = "primary",
177
+ variant = "bar",
178
+ ticks = 24,
179
+ glow = false,
180
+ icon,
181
+ endLabel,
166
182
  label,
167
183
  showValue = false,
168
184
  className,
@@ -170,7 +186,86 @@ function Progress({
170
186
  }) {
171
187
  const indeterminate = value === void 0;
172
188
  const percent = indeterminate ? 0 : Math.min(100, Math.max(0, value / max * 100));
173
- return /* @__PURE__ */ jsxs("div", { className: cn("ru-progress", indeterminate && "ru-progress--indeterminate", className), ...rest, children: [
189
+ const semantic = progressColors.has(color);
190
+ const glowColor = glow === true ? semantic ? `var(--ru-${color})` : color : glow || void 0;
191
+ const meterStyle = { ...!semantic && { "--_c": color }, ...glowColor && { "--_glow": glowColor } };
192
+ if (variant === "capsule") {
193
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-progress", "ru-progress--capsule", `ru-progress--${size}`, glowColor && "ru-progress--glow", className), ...rest, children: [
194
+ label && /* @__PURE__ */ jsx("div", { className: "ru-progress__label", children: /* @__PURE__ */ jsx("span", { children: label }) }),
195
+ /* @__PURE__ */ jsxs(
196
+ "div",
197
+ {
198
+ role: "progressbar",
199
+ "aria-valuemin": 0,
200
+ "aria-valuemax": max,
201
+ "aria-valuenow": value ?? 0,
202
+ "aria-label": typeof label === "string" ? label : void 0,
203
+ className: cn("ru-progress__capsule", semantic && `ru-c-${color}`),
204
+ style: meterStyle,
205
+ children: [
206
+ /* @__PURE__ */ jsx("div", { className: "ru-progress__capsule-fill", style: { width: `max(var(--_h), ${percent}%)` }, children: icon ?? (showValue && /* @__PURE__ */ jsxs("span", { className: "ru-progress__capsule-value", children: [
207
+ Math.round(percent),
208
+ "%"
209
+ ] })) }),
210
+ endLabel && /* @__PURE__ */ jsx("span", { className: "ru-progress__capsule-end", children: endLabel })
211
+ ]
212
+ }
213
+ )
214
+ ] });
215
+ }
216
+ if (variant === "bars") {
217
+ const total = Math.max(2, ticks);
218
+ const lit = Math.round(percent / 100 * total);
219
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-progress", "ru-progress--bars", `ru-progress--${size}`, glowColor && "ru-progress--glow", className), ...rest, children: [
220
+ (label || showValue) && /* @__PURE__ */ jsxs("div", { className: "ru-progress__label", children: [
221
+ /* @__PURE__ */ jsx("span", { children: label }),
222
+ showValue && /* @__PURE__ */ jsxs("span", { className: "ru-progress__value ru-progress__value--accent", children: [
223
+ Math.round(percent),
224
+ "%"
225
+ ] })
226
+ ] }),
227
+ /* @__PURE__ */ jsx(
228
+ "div",
229
+ {
230
+ role: "progressbar",
231
+ "aria-valuemin": 0,
232
+ "aria-valuemax": max,
233
+ "aria-valuenow": value ?? 0,
234
+ "aria-label": typeof label === "string" ? label : void 0,
235
+ className: cn("ru-progress__bars", semantic && `ru-c-${color}`),
236
+ style: meterStyle,
237
+ children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsx("span", { className: cn(i < lit && "is-on", i === lit - 1 && percent > 0 && percent < 100 && "is-marker") }, i))
238
+ }
239
+ )
240
+ ] });
241
+ }
242
+ if (variant === "ticks") {
243
+ const remaining = percent >= 100 ? 0 : Math.max(1, Math.round(ticks * (1 - percent / 100)));
244
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-progress", "ru-progress--ticks", `ru-progress--${size}`, glowColor && "ru-progress--glow", className), ...rest, children: [
245
+ label && /* @__PURE__ */ jsx("div", { className: "ru-progress__label", children: /* @__PURE__ */ jsx("span", { children: label }) }),
246
+ /* @__PURE__ */ jsxs(
247
+ "div",
248
+ {
249
+ role: "progressbar",
250
+ "aria-valuemin": 0,
251
+ "aria-valuemax": max,
252
+ "aria-valuenow": value ?? 0,
253
+ "aria-label": typeof label === "string" ? label : void 0,
254
+ className: cn("ru-progress__meter", semantic && `ru-c-${color}`),
255
+ style: meterStyle,
256
+ children: [
257
+ percent > 0 && /* @__PURE__ */ jsx("div", { className: "ru-progress__fill", style: { width: `${percent}%` }, children: showValue && /* @__PURE__ */ jsxs("span", { className: "ru-progress__fill-value", children: [
258
+ Math.round(percent),
259
+ "%"
260
+ ] }) }),
261
+ percent > 0 && percent < 100 && /* @__PURE__ */ jsx("span", { className: "ru-progress__marker", "aria-hidden": "true" }),
262
+ remaining > 0 && /* @__PURE__ */ jsx("div", { className: "ru-progress__ticks", "aria-hidden": "true", children: Array.from({ length: remaining }, (_, i) => /* @__PURE__ */ jsx("span", {}, i)) })
263
+ ]
264
+ }
265
+ )
266
+ ] });
267
+ }
268
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-progress", indeterminate && "ru-progress--indeterminate", glowColor && "ru-progress--glow", className), ...rest, children: [
174
269
  (label || showValue) && /* @__PURE__ */ jsxs("div", { className: "ru-progress__label", children: [
175
270
  /* @__PURE__ */ jsx("span", { children: label }),
176
271
  showValue && !indeterminate && /* @__PURE__ */ jsxs("span", { className: "ru-progress__value", children: [
@@ -186,7 +281,8 @@ function Progress({
186
281
  "aria-valuemax": max,
187
282
  "aria-valuenow": indeterminate ? void 0 : value,
188
283
  "aria-label": typeof label === "string" ? label : void 0,
189
- className: cn("ru-progress__track", `ru-progress__track--${size}`, `ru-c-${color}`),
284
+ className: cn("ru-progress__track", `ru-progress__track--${size}`, semantic && `ru-c-${color}`),
285
+ style: meterStyle,
190
286
  children: /* @__PURE__ */ jsx("div", { className: "ru-progress__bar", style: indeterminate ? void 0 : { width: `${percent}%` } })
191
287
  }
192
288
  )
@@ -1860,7 +1956,18 @@ function SpotlightCard({ color, className, style, onMouseMove, children, ...rest
1860
1956
  }
1861
1957
  );
1862
1958
  }
1863
- const semanticColors$1 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
1959
+ function DotText({ as: Tag = "span", dot = 4, color, glow = false, className, style, children, ...rest }) {
1960
+ return /* @__PURE__ */ jsx(
1961
+ Tag,
1962
+ {
1963
+ className: cn("ru-dot-text", glow && "ru-dot-text--glow", className),
1964
+ style: { "--_d": `${dot}px`, ...color && { color }, ...style },
1965
+ ...rest,
1966
+ children
1967
+ }
1968
+ );
1969
+ }
1970
+ const semanticColors$4 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
1864
1971
  function Pager({ onPrev, onNext, prevLabel, nextLabel }) {
1865
1972
  if (!onPrev && !onNext) return null;
1866
1973
  return /* @__PURE__ */ jsxs("div", { className: "ru-frame-card__pager", children: [
@@ -1898,7 +2005,7 @@ function FrameCard({
1898
2005
  {
1899
2006
  className: cn("ru-frame-card", onFrame && "ru-frame-card--title-frame", className),
1900
2007
  style: {
1901
- "--_fc": semanticColors$1.has(color) ? `var(--ru-${color})` : color,
2008
+ "--_fc": semanticColors$4.has(color) ? `var(--ru-${color})` : color,
1902
2009
  ...textColor && { "--_fc-on": textColor, "--_fc-ink": textColor },
1903
2010
  ...style
1904
2011
  },
@@ -1937,7 +2044,7 @@ function FrameCard({
1937
2044
  }
1938
2045
  const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1939
2046
  const WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
1940
- const semanticColors = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
2047
+ const semanticColors$3 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
1941
2048
  const dayKey = (d) => d.getFullYear() * 1e4 + d.getMonth() * 100 + d.getDate();
1942
2049
  const sameDay = (a, b) => Boolean(a && b) && dayKey(a) === dayKey(b);
1943
2050
  function Calendar({
@@ -1997,7 +2104,7 @@ function Calendar({
1997
2104
  next.focus();
1998
2105
  }
1999
2106
  };
2000
- const ink = color ? semanticColors.has(color) ? `var(--ru-${color})` : color : void 0;
2107
+ const ink = color ? semanticColors$3.has(color) ? `var(--ru-${color})` : color : void 0;
2001
2108
  return /* @__PURE__ */ jsxs("div", { className: cn("ru-calendar", className), style: ink ? { "--_cc": ink, ...style } : style, ...rest, children: [
2002
2109
  /* @__PURE__ */ jsxs("div", { className: "ru-calendar__head", children: [
2003
2110
  /* @__PURE__ */ jsx("button", { type: "button", className: "ru-calendar__nav", "aria-label": "Previous month", onClick: () => shift(-1), children: /* @__PURE__ */ jsx(ChevronLeftIcon, {}) }),
@@ -2052,6 +2159,638 @@ function Calendar({
2052
2159
  }) })
2053
2160
  ] });
2054
2161
  }
2162
+ const normalize = (options) => options.map((o) => typeof o === "object" ? o : { label: String(o), value: o });
2163
+ function SegmentedControl({
2164
+ options,
2165
+ value,
2166
+ defaultValue,
2167
+ onChange,
2168
+ size = "md",
2169
+ fullWidth = false,
2170
+ pill = false,
2171
+ disabled = false,
2172
+ className,
2173
+ style,
2174
+ ...rest
2175
+ }) {
2176
+ var _a;
2177
+ const items = normalize(options);
2178
+ const firstEnabled = (_a = items.find((i) => !i.disabled)) == null ? void 0 : _a.value;
2179
+ const [active, setActive] = useControllableState(value, defaultValue ?? firstEnabled, onChange);
2180
+ const rootRef = useRef(null);
2181
+ const [thumb, setThumb] = useState(null);
2182
+ const activeIndex = items.findIndex((i) => i.value === active);
2183
+ useEffect(() => {
2184
+ const root = rootRef.current;
2185
+ if (!root) return void 0;
2186
+ const measure = () => {
2187
+ const el = root.querySelectorAll('[role="radio"]')[activeIndex];
2188
+ setThumb(el ? { x: el.offsetLeft, w: el.offsetWidth } : null);
2189
+ };
2190
+ measure();
2191
+ if (typeof ResizeObserver === "undefined") return void 0;
2192
+ const ro = new ResizeObserver(measure);
2193
+ ro.observe(root);
2194
+ return () => ro.disconnect();
2195
+ }, [activeIndex, items.length]);
2196
+ const move = (from, step) => {
2197
+ var _a2;
2198
+ const buttons = rootRef.current.querySelectorAll('[role="radio"]');
2199
+ for (let n = 1; n <= items.length; n++) {
2200
+ const i = (from + step * n + items.length) % items.length;
2201
+ if (!items[i].disabled) {
2202
+ setActive(items[i].value);
2203
+ (_a2 = buttons[i]) == null ? void 0 : _a2.focus();
2204
+ return;
2205
+ }
2206
+ }
2207
+ };
2208
+ const onKeyDown = (e, index) => {
2209
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") move(index, 1);
2210
+ else if (e.key === "ArrowLeft" || e.key === "ArrowUp") move(index, -1);
2211
+ else return;
2212
+ e.preventDefault();
2213
+ };
2214
+ return /* @__PURE__ */ jsxs(
2215
+ "div",
2216
+ {
2217
+ ref: rootRef,
2218
+ role: "radiogroup",
2219
+ "aria-disabled": disabled || void 0,
2220
+ className: cn(
2221
+ "ru-segmented",
2222
+ `ru-segmented--${size}`,
2223
+ fullWidth && "ru-segmented--full",
2224
+ pill && "ru-segmented--pill",
2225
+ thumb && "ru-segmented--ready",
2226
+ className
2227
+ ),
2228
+ style: thumb ? { "--_x": `${thumb.x}px`, "--_w": `${thumb.w}px`, ...style } : style,
2229
+ ...rest,
2230
+ children: [
2231
+ thumb && activeIndex >= 0 && /* @__PURE__ */ jsx("span", { className: "ru-segmented__thumb", "aria-hidden": "true" }),
2232
+ items.map((item, i) => {
2233
+ const selected = i === activeIndex;
2234
+ return /* @__PURE__ */ jsxs(
2235
+ "button",
2236
+ {
2237
+ type: "button",
2238
+ role: "radio",
2239
+ "aria-checked": selected,
2240
+ tabIndex: selected || activeIndex < 0 && i === 0 ? 0 : -1,
2241
+ disabled: disabled || item.disabled,
2242
+ className: "ru-segmented__item",
2243
+ onClick: () => setActive(item.value),
2244
+ onKeyDown: (e) => onKeyDown(e, i),
2245
+ children: [
2246
+ item.icon,
2247
+ item.label
2248
+ ]
2249
+ },
2250
+ String(item.value)
2251
+ );
2252
+ })
2253
+ ]
2254
+ }
2255
+ );
2256
+ }
2257
+ const semanticColors$2 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
2258
+ function ProgressRing({
2259
+ value = 0,
2260
+ max = 100,
2261
+ size = 48,
2262
+ thickness = 4,
2263
+ color = "primary",
2264
+ trackColor,
2265
+ showValue = false,
2266
+ label,
2267
+ className,
2268
+ style,
2269
+ children,
2270
+ ...rest
2271
+ }) {
2272
+ const percent = Math.min(100, Math.max(0, value / max * 100));
2273
+ const r = (size - thickness) / 2;
2274
+ const circumference = 2 * Math.PI * r;
2275
+ const ink = semanticColors$2.has(color) ? `var(--ru-${color})` : color;
2276
+ return /* @__PURE__ */ jsxs(
2277
+ "div",
2278
+ {
2279
+ role: "progressbar",
2280
+ "aria-valuemin": 0,
2281
+ "aria-valuemax": max,
2282
+ "aria-valuenow": value,
2283
+ "aria-label": label,
2284
+ className: cn("ru-ring", className),
2285
+ style: { width: size, height: size, "--_c": ink, ...trackColor && { "--_track": trackColor }, ...style },
2286
+ ...rest,
2287
+ children: [
2288
+ /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, "aria-hidden": "true", children: [
2289
+ /* @__PURE__ */ jsx("circle", { className: "ru-ring__track", cx: size / 2, cy: size / 2, r, strokeWidth: thickness }),
2290
+ /* @__PURE__ */ jsx(
2291
+ "circle",
2292
+ {
2293
+ className: "ru-ring__bar",
2294
+ cx: size / 2,
2295
+ cy: size / 2,
2296
+ r,
2297
+ strokeWidth: thickness,
2298
+ strokeDasharray: circumference,
2299
+ strokeDashoffset: circumference * (1 - percent / 100)
2300
+ }
2301
+ )
2302
+ ] }),
2303
+ (children !== void 0 || showValue) && /* @__PURE__ */ jsx("span", { className: "ru-ring__label", style: { fontSize: Math.max(10, Math.round(size * 0.24)) }, children: children ?? `${Math.round(percent)}%` })
2304
+ ]
2305
+ }
2306
+ );
2307
+ }
2308
+ const semanticColors$1 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
2309
+ function Stat({
2310
+ label,
2311
+ value,
2312
+ trend,
2313
+ trendLabel,
2314
+ invertTrend = false,
2315
+ helpText,
2316
+ size = "md",
2317
+ align = "start",
2318
+ className,
2319
+ ...rest
2320
+ }) {
2321
+ const hasTrend = typeof trend === "number";
2322
+ const up = hasTrend && trend >= 0;
2323
+ const good = up !== invertTrend;
2324
+ const TrendIcon = up ? TrendUpIcon : TrendDownIcon;
2325
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-stat", `ru-stat--${size}`, align === "end" && "ru-stat--end", className), ...rest, children: [
2326
+ /* @__PURE__ */ jsxs("div", { className: "ru-stat__top", children: [
2327
+ label && /* @__PURE__ */ jsx("span", { className: "ru-stat__label", children: label }),
2328
+ hasTrend && /* @__PURE__ */ jsxs("span", { className: cn("ru-stat__trend", good ? "ru-stat__trend--good" : "ru-stat__trend--bad"), children: [
2329
+ /* @__PURE__ */ jsx("span", { className: "ru-stat__trend-icon", children: /* @__PURE__ */ jsx(TrendIcon, {}) }),
2330
+ trendLabel ?? `${Math.abs(trend).toLocaleString()}%`,
2331
+ /* @__PURE__ */ jsx("span", { className: "ru-sr-only", children: up ? " increase" : " decrease" })
2332
+ ] })
2333
+ ] }),
2334
+ /* @__PURE__ */ jsx("div", { className: "ru-stat__value", children: value }),
2335
+ helpText && /* @__PURE__ */ jsx("div", { className: "ru-stat__help", children: helpText })
2336
+ ] });
2337
+ }
2338
+ function DateTile({ date, color = "primary", size = "md", className, style, ...rest }) {
2339
+ const d = date instanceof Date ? date : new Date(date);
2340
+ const ink = semanticColors$1.has(color) ? `var(--ru-${color})` : color;
2341
+ return /* @__PURE__ */ jsxs(
2342
+ "time",
2343
+ {
2344
+ dateTime: d.toISOString().slice(0, 10),
2345
+ "aria-label": d.toLocaleDateString("en-US", { weekday: "long", month: "long", day: "numeric", year: "numeric" }),
2346
+ className: cn("ru-date-tile", `ru-date-tile--${size}`, className),
2347
+ style: { "--_c": ink, ...style },
2348
+ ...rest,
2349
+ children: [
2350
+ /* @__PURE__ */ jsx("span", { className: "ru-date-tile__month", "aria-hidden": "true", children: d.toLocaleDateString("en-US", { month: "short" }) }),
2351
+ /* @__PURE__ */ jsx("span", { className: "ru-date-tile__day", "aria-hidden": "true", children: d.getDate() })
2352
+ ]
2353
+ }
2354
+ );
2355
+ }
2356
+ function matchesAccept(file, accept) {
2357
+ if (!accept) return true;
2358
+ return accept.split(",").some((rule) => {
2359
+ const r = rule.trim().toLowerCase();
2360
+ if (!r) return false;
2361
+ if (r.startsWith(".")) return file.name.toLowerCase().endsWith(r);
2362
+ if (r.endsWith("/*")) return file.type.toLowerCase().startsWith(r.slice(0, -1));
2363
+ return file.type.toLowerCase() === r;
2364
+ });
2365
+ }
2366
+ function formatBytes(bytes) {
2367
+ if (typeof bytes !== "number") return bytes;
2368
+ if (bytes < 1024) return `${bytes} B`;
2369
+ const units = ["KB", "MB", "GB"];
2370
+ let n = bytes / 1024;
2371
+ let i = 0;
2372
+ while (n >= 1024 && i < units.length - 1) {
2373
+ n /= 1024;
2374
+ i++;
2375
+ }
2376
+ return `${Number(n.toFixed(1))}${units[i]}`;
2377
+ }
2378
+ function Dropzone({
2379
+ onFiles,
2380
+ onReject,
2381
+ accept,
2382
+ multiple = true,
2383
+ maxSize,
2384
+ disabled = false,
2385
+ icon,
2386
+ title = "Drag & drop files here",
2387
+ description,
2388
+ className,
2389
+ children,
2390
+ ...rest
2391
+ }) {
2392
+ const inputRef = useRef(null);
2393
+ const [dragging, setDragging] = useState(false);
2394
+ const handle = (list) => {
2395
+ const files = Array.from(list ?? []);
2396
+ const accepted = [];
2397
+ const rejected = [];
2398
+ for (const file of multiple ? files : files.slice(0, 1)) {
2399
+ if (!matchesAccept(file, accept)) rejected.push({ file, reason: "type" });
2400
+ else if (maxSize && file.size > maxSize) rejected.push({ file, reason: "size" });
2401
+ else accepted.push(file);
2402
+ }
2403
+ if (accepted.length) onFiles == null ? void 0 : onFiles(accepted);
2404
+ if (rejected.length) onReject == null ? void 0 : onReject(rejected);
2405
+ };
2406
+ const open = () => {
2407
+ var _a;
2408
+ return !disabled && ((_a = inputRef.current) == null ? void 0 : _a.click());
2409
+ };
2410
+ return /* @__PURE__ */ jsxs(
2411
+ "div",
2412
+ {
2413
+ role: "button",
2414
+ tabIndex: disabled ? -1 : 0,
2415
+ "aria-disabled": disabled || void 0,
2416
+ className: cn("ru-dropzone", dragging && "ru-dropzone--active", disabled && "ru-dropzone--disabled", className),
2417
+ onClick: open,
2418
+ onKeyDown: (e) => {
2419
+ if (e.key === "Enter" || e.key === " ") {
2420
+ e.preventDefault();
2421
+ open();
2422
+ }
2423
+ },
2424
+ onDragEnter: (e) => {
2425
+ e.preventDefault();
2426
+ if (!disabled) setDragging(true);
2427
+ },
2428
+ onDragOver: (e) => e.preventDefault(),
2429
+ onDragLeave: (e) => {
2430
+ if (!e.currentTarget.contains(e.relatedTarget)) setDragging(false);
2431
+ },
2432
+ onDrop: (e) => {
2433
+ e.preventDefault();
2434
+ setDragging(false);
2435
+ if (!disabled) handle(e.dataTransfer.files);
2436
+ },
2437
+ ...rest,
2438
+ children: [
2439
+ /* @__PURE__ */ jsx(
2440
+ "input",
2441
+ {
2442
+ ref: inputRef,
2443
+ type: "file",
2444
+ hidden: true,
2445
+ accept,
2446
+ multiple,
2447
+ disabled,
2448
+ onChange: (e) => {
2449
+ handle(e.target.files);
2450
+ e.target.value = "";
2451
+ }
2452
+ }
2453
+ ),
2454
+ children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
2455
+ /* @__PURE__ */ jsx("span", { className: "ru-dropzone__icon", children: icon ?? /* @__PURE__ */ jsx(UploadCloudIcon, {}) }),
2456
+ /* @__PURE__ */ jsx("span", { className: "ru-dropzone__title", children: title }),
2457
+ /* @__PURE__ */ jsx("span", { className: "ru-dropzone__desc", children: description ?? /* @__PURE__ */ jsxs(Fragment, { children: [
2458
+ "or ",
2459
+ /* @__PURE__ */ jsx("span", { className: "ru-dropzone__browse", children: "browse" }),
2460
+ " to choose files",
2461
+ maxSize ? ` (max ${formatBytes(maxSize)})` : ""
2462
+ ] }) })
2463
+ ] })
2464
+ ]
2465
+ }
2466
+ );
2467
+ }
2468
+ const statusText = { uploading: "Uploading…", complete: "Completed", error: "Failed" };
2469
+ function FileItem({
2470
+ name,
2471
+ size,
2472
+ status = "complete",
2473
+ progress = 0,
2474
+ icon,
2475
+ error,
2476
+ onRemove,
2477
+ action,
2478
+ removeLabel,
2479
+ className,
2480
+ ...rest
2481
+ }) {
2482
+ const uploading = status === "uploading";
2483
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-file-item", `ru-file-item--${status}`, className), ...rest, children: [
2484
+ /* @__PURE__ */ jsx("span", { className: "ru-file-item__icon", children: icon ?? /* @__PURE__ */ jsx(FileIcon, {}) }),
2485
+ /* @__PURE__ */ jsxs("div", { className: "ru-file-item__body", children: [
2486
+ /* @__PURE__ */ jsx("div", { className: "ru-file-item__name", title: name, children: name }),
2487
+ /* @__PURE__ */ jsxs("div", { className: "ru-file-item__meta", children: [
2488
+ size !== void 0 && /* @__PURE__ */ jsx("span", { children: formatBytes(size) }),
2489
+ size !== void 0 && /* @__PURE__ */ jsx("span", { className: "ru-file-item__dot", "aria-hidden": "true" }),
2490
+ /* @__PURE__ */ jsx("span", { className: "ru-file-item__status", children: status === "error" && error ? error : statusText[status] })
2491
+ ] }),
2492
+ uploading && /* @__PURE__ */ jsx(
2493
+ "div",
2494
+ {
2495
+ className: "ru-file-item__bar",
2496
+ role: "progressbar",
2497
+ "aria-valuemin": 0,
2498
+ "aria-valuemax": 100,
2499
+ "aria-valuenow": progress,
2500
+ "aria-label": `Uploading ${name}`,
2501
+ children: /* @__PURE__ */ jsx("span", { style: { width: `${progress}%` } })
2502
+ }
2503
+ )
2504
+ ] }),
2505
+ uploading && /* @__PURE__ */ jsxs("span", { className: "ru-file-item__percent", children: [
2506
+ Math.round(progress),
2507
+ "%"
2508
+ ] }),
2509
+ action,
2510
+ onRemove && /* @__PURE__ */ jsx("button", { type: "button", className: "ru-file-item__remove", "aria-label": removeLabel ?? `Remove ${name}`, onClick: onRemove, children: /* @__PURE__ */ jsx(CloseIcon, {}) })
2511
+ ] });
2512
+ }
2513
+ const UndoIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
2514
+ /* @__PURE__ */ jsx("path", { d: "M9 14 4 9l5-5" }),
2515
+ /* @__PURE__ */ jsx("path", { d: "M4 9h10.5a5.5 5.5 0 0 1 0 11H11" })
2516
+ ] });
2517
+ const EraserIcon = () => /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
2518
+ /* @__PURE__ */ 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" }),
2519
+ /* @__PURE__ */ jsx("path", { d: "M22 21H7M5 11l9 9" })
2520
+ ] });
2521
+ const SignaturePad = forwardRef(function SignaturePad2({
2522
+ onChange,
2523
+ strokeColor,
2524
+ strokeWidth = 2.5,
2525
+ height = 180,
2526
+ placeholder = "Sign here",
2527
+ showControls = true,
2528
+ undoLabel = "Undo",
2529
+ clearLabel = "Clear",
2530
+ actions,
2531
+ disabled = false,
2532
+ className,
2533
+ style,
2534
+ ...rest
2535
+ }, ref) {
2536
+ const canvasRef = useRef(null);
2537
+ const strokes = useRef([]);
2538
+ const drawing = useRef(null);
2539
+ const [count, setCount] = useState(0);
2540
+ const ink = useCallback(() => strokeColor ?? getComputedStyle(canvasRef.current).color, [strokeColor]);
2541
+ const drawStroke = useCallback(
2542
+ (ctx, points) => {
2543
+ ctx.strokeStyle = ink();
2544
+ ctx.fillStyle = ctx.strokeStyle;
2545
+ if (points.length === 1) {
2546
+ ctx.beginPath();
2547
+ ctx.arc(points[0].x, points[0].y, strokeWidth / 2, 0, Math.PI * 2);
2548
+ ctx.fill();
2549
+ return;
2550
+ }
2551
+ for (let i = 1; i < points.length; i++) {
2552
+ const a = points[i - 1];
2553
+ const b = points[i];
2554
+ const mid = { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
2555
+ ctx.lineWidth = b.w;
2556
+ ctx.beginPath();
2557
+ ctx.moveTo(a.mx ?? a.x, a.my ?? a.y);
2558
+ ctx.quadraticCurveTo(a.x, a.y, mid.x, mid.y);
2559
+ ctx.stroke();
2560
+ b.mx = mid.x;
2561
+ b.my = mid.y;
2562
+ }
2563
+ },
2564
+ [ink, strokeWidth]
2565
+ );
2566
+ const redraw = useCallback(() => {
2567
+ const canvas = canvasRef.current;
2568
+ if (!canvas) return;
2569
+ const ctx = canvas.getContext("2d");
2570
+ if (!ctx) return;
2571
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
2572
+ ctx.lineCap = "round";
2573
+ ctx.lineJoin = "round";
2574
+ strokes.current.forEach((s) => drawStroke(ctx, s.map(({ x, y, w }) => ({ x, y, w }))));
2575
+ }, [drawStroke]);
2576
+ useEffect(() => {
2577
+ const canvas = canvasRef.current;
2578
+ if (!canvas) return void 0;
2579
+ const fit = () => {
2580
+ var _a;
2581
+ const dpr = window.devicePixelRatio || 1;
2582
+ const { width, height: h } = canvas.getBoundingClientRect();
2583
+ canvas.width = Math.max(1, Math.round(width * dpr));
2584
+ canvas.height = Math.max(1, Math.round(h * dpr));
2585
+ (_a = canvas.getContext("2d")) == null ? void 0 : _a.setTransform(dpr, 0, 0, dpr, 0, 0);
2586
+ redraw();
2587
+ };
2588
+ fit();
2589
+ if (typeof ResizeObserver === "undefined") return void 0;
2590
+ const ro = new ResizeObserver(fit);
2591
+ ro.observe(canvas);
2592
+ return () => ro.disconnect();
2593
+ }, [redraw]);
2594
+ const emit = () => onChange == null ? void 0 : onChange(strokes.current.length ? canvasRef.current.toDataURL("image/png") : null);
2595
+ const point = (e) => {
2596
+ const rect = canvasRef.current.getBoundingClientRect();
2597
+ return { x: e.clientX - rect.left, y: e.clientY - rect.top, t: e.timeStamp };
2598
+ };
2599
+ const onPointerDown = (e) => {
2600
+ if (disabled || e.pointerType === "mouse" && e.button !== 0) return;
2601
+ e.currentTarget.setPointerCapture(e.pointerId);
2602
+ const p = { ...point(e), w: strokeWidth };
2603
+ drawing.current = [p];
2604
+ const ctx = canvasRef.current.getContext("2d");
2605
+ ctx.lineCap = "round";
2606
+ ctx.lineJoin = "round";
2607
+ drawStroke(ctx, [p]);
2608
+ };
2609
+ const onPointerMove = (e) => {
2610
+ const stroke = drawing.current;
2611
+ if (!stroke) return;
2612
+ const prev = stroke[stroke.length - 1];
2613
+ const p = point(e);
2614
+ const dist = Math.hypot(p.x - prev.x, p.y - prev.y);
2615
+ if (dist < 1.5) return;
2616
+ const speed = dist / Math.max(1, p.t - prev.t);
2617
+ const target = strokeWidth * Math.max(0.45, Math.min(1.35, 1.4 - speed * 0.45));
2618
+ p.w = prev.w * 0.6 + target * 0.4;
2619
+ stroke.push(p);
2620
+ drawStroke(canvasRef.current.getContext("2d"), stroke.slice(-3));
2621
+ };
2622
+ const onPointerUp = () => {
2623
+ if (!drawing.current) return;
2624
+ strokes.current = [...strokes.current, drawing.current];
2625
+ drawing.current = null;
2626
+ setCount(strokes.current.length);
2627
+ emit();
2628
+ };
2629
+ const undo = () => {
2630
+ strokes.current = strokes.current.slice(0, -1);
2631
+ setCount(strokes.current.length);
2632
+ redraw();
2633
+ emit();
2634
+ };
2635
+ const clear = () => {
2636
+ strokes.current = [];
2637
+ setCount(0);
2638
+ redraw();
2639
+ emit();
2640
+ };
2641
+ useImperativeHandle(ref, () => ({
2642
+ clear,
2643
+ undo,
2644
+ isEmpty: () => strokes.current.length === 0,
2645
+ toDataURL: (type = "image/png") => canvasRef.current.toDataURL(type)
2646
+ }));
2647
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-signature", disabled && "ru-signature--disabled", className), style, ...rest, children: [
2648
+ /* @__PURE__ */ jsxs("div", { className: "ru-signature__canvas-wrap", style: { height }, children: [
2649
+ count === 0 && placeholder && /* @__PURE__ */ jsx("span", { className: "ru-signature__placeholder", "aria-hidden": "true", children: placeholder }),
2650
+ /* @__PURE__ */ jsx(
2651
+ "canvas",
2652
+ {
2653
+ ref: canvasRef,
2654
+ className: "ru-signature__canvas",
2655
+ role: "img",
2656
+ "aria-label": count ? "Signature drawn" : "Signature pad, empty",
2657
+ onPointerDown,
2658
+ onPointerMove,
2659
+ onPointerUp,
2660
+ onPointerCancel: onPointerUp
2661
+ }
2662
+ ),
2663
+ /* @__PURE__ */ jsx("span", { className: "ru-signature__baseline", "aria-hidden": "true" })
2664
+ ] }),
2665
+ (showControls || actions) && /* @__PURE__ */ jsxs("div", { className: "ru-signature__bar", children: [
2666
+ showControls && /* @__PURE__ */ jsxs(Fragment, { children: [
2667
+ /* @__PURE__ */ jsxs("button", { type: "button", className: "ru-signature__btn", onClick: undo, disabled: disabled || count === 0, children: [
2668
+ /* @__PURE__ */ jsx(UndoIcon, {}),
2669
+ undoLabel
2670
+ ] }),
2671
+ /* @__PURE__ */ jsx("span", { className: "ru-signature__sep", "aria-hidden": "true" }),
2672
+ /* @__PURE__ */ jsxs("button", { type: "button", className: "ru-signature__btn", onClick: clear, disabled: disabled || count === 0, children: [
2673
+ /* @__PURE__ */ jsx(EraserIcon, {}),
2674
+ clearLabel
2675
+ ] })
2676
+ ] }),
2677
+ actions && /* @__PURE__ */ jsx("div", { className: "ru-signature__actions", children: actions })
2678
+ ] })
2679
+ ] });
2680
+ });
2681
+ const statusLabels = {
2682
+ running: "In progress",
2683
+ queued: "Queued",
2684
+ pending: "Not started",
2685
+ partial: "Partly done",
2686
+ complete: "Complete",
2687
+ error: "Failed"
2688
+ };
2689
+ function StatusIcon({ status = "pending", progress = 50, size = 20, label, className, style, ...rest }) {
2690
+ const r = 8;
2691
+ const c = 2 * Math.PI * r;
2692
+ return /* @__PURE__ */ jsx(
2693
+ "span",
2694
+ {
2695
+ role: "img",
2696
+ "aria-label": label ?? statusLabels[status] ?? status,
2697
+ className: cn("ru-status-icon", `ru-status-icon--${status}`, className),
2698
+ style: { width: size, height: size, ...style },
2699
+ ...rest,
2700
+ children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 20 20", width: size, height: size, "aria-hidden": "true", children: status === "complete" || status === "error" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2701
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "10", className: "ru-status-icon__fill" }),
2702
+ status === "complete" ? /* @__PURE__ */ jsx("path", { d: "m6 10.2 2.6 2.6L14 7.4", className: "ru-status-icon__glyph" }) : /* @__PURE__ */ jsx("path", { d: "m7 7 6 6M13 7l-6 6", className: "ru-status-icon__glyph" })
2703
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2704
+ /* @__PURE__ */ jsx(
2705
+ "circle",
2706
+ {
2707
+ cx: "10",
2708
+ cy: "10",
2709
+ r,
2710
+ className: "ru-status-icon__ring",
2711
+ strokeDasharray: status === "running" || status === "queued" ? "2.6 2.4" : void 0
2712
+ }
2713
+ ),
2714
+ status === "partial" && /* @__PURE__ */ jsx(
2715
+ "circle",
2716
+ {
2717
+ cx: "10",
2718
+ cy: "10",
2719
+ r,
2720
+ className: "ru-status-icon__arc",
2721
+ strokeDasharray: c,
2722
+ strokeDashoffset: c * (1 - Math.min(100, Math.max(0, progress)) / 100)
2723
+ }
2724
+ )
2725
+ ] }) })
2726
+ }
2727
+ );
2728
+ }
2729
+ function StepList({ items, connector = true, dividers = true, className, ...rest }) {
2730
+ return /* @__PURE__ */ jsx("ol", { className: cn("ru-step-list", !dividers && "ru-step-list--plain", className), ...rest, children: items.map((item, i) => /* @__PURE__ */ jsxs(
2731
+ "li",
2732
+ {
2733
+ className: cn("ru-step-list__item", `ru-step-list__item--${item.status ?? "pending"}`),
2734
+ "aria-current": item.status === "running" ? "step" : void 0,
2735
+ children: [
2736
+ /* @__PURE__ */ jsxs("span", { className: "ru-step-list__rail", children: [
2737
+ /* @__PURE__ */ jsx("span", { className: "ru-step-list__icon", children: /* @__PURE__ */ jsx(StatusIcon, { status: item.status, progress: item.progress, size: 22 }) }),
2738
+ connector && i < items.length - 1 && /* @__PURE__ */ jsx("span", { className: "ru-step-list__line", "aria-hidden": "true" })
2739
+ ] }),
2740
+ /* @__PURE__ */ jsxs("div", { className: "ru-step-list__body", children: [
2741
+ /* @__PURE__ */ jsx("div", { className: "ru-step-list__title", children: item.title }),
2742
+ item.description && /* @__PURE__ */ jsx("div", { className: "ru-step-list__desc", children: item.description })
2743
+ ] }),
2744
+ item.meta && /* @__PURE__ */ jsx("div", { className: "ru-step-list__meta", children: item.meta })
2745
+ ]
2746
+ },
2747
+ item.key ?? i
2748
+ )) });
2749
+ }
2750
+ function StackedProgress({ segments, max, size = "md", showLegend = true, gap = 4, label, className, ...rest }) {
2751
+ const total = max ?? segments.reduce((sum, s) => sum + s.value, 0);
2752
+ const summary = segments.map((s) => `${s.label} ${Math.round(s.value / total * 100)}%`).join(", ");
2753
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-stacked", `ru-stacked--${size}`, className), ...rest, children: [
2754
+ (label || showLegend) && /* @__PURE__ */ jsxs("div", { className: "ru-stacked__head", children: [
2755
+ label && /* @__PURE__ */ jsx("span", { className: "ru-stacked__label", children: label }),
2756
+ showLegend && /* @__PURE__ */ jsx("ul", { className: "ru-stacked__legend", children: segments.map((s) => /* @__PURE__ */ jsxs("li", { children: [
2757
+ /* @__PURE__ */ jsx("span", { className: "ru-stacked__dot", style: { background: s.color } }),
2758
+ s.label
2759
+ ] }, s.label)) })
2760
+ ] }),
2761
+ /* @__PURE__ */ jsxs("div", { className: "ru-stacked__bar", role: "img", "aria-label": summary, style: { gap }, children: [
2762
+ segments.map((s) => /* @__PURE__ */ jsx("span", { className: "ru-stacked__seg", title: `${s.label}: ${s.value}`, style: { flexGrow: s.value, background: s.color } }, s.label)),
2763
+ max !== void 0 && total > segments.reduce((sum, s) => sum + s.value, 0) && /* @__PURE__ */ jsx("span", { className: "ru-stacked__rest", style: { flexGrow: total - segments.reduce((sum, s) => sum + s.value, 0) } })
2764
+ ] })
2765
+ ] });
2766
+ }
2767
+ const semanticColors = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
2768
+ function IconTile({
2769
+ color = "primary",
2770
+ size = 48,
2771
+ radius,
2772
+ badge,
2773
+ glow = true,
2774
+ label,
2775
+ className,
2776
+ style,
2777
+ children,
2778
+ ...rest
2779
+ }) {
2780
+ const ink = semanticColors.has(color) ? `var(--ru-${color})` : color;
2781
+ return /* @__PURE__ */ jsxs(
2782
+ "span",
2783
+ {
2784
+ className: cn("ru-icon-tile", glow && "ru-icon-tile--glow", className),
2785
+ style: { "--_c": ink, "--_s": `${size}px`, ...radius !== void 0 && { "--_r": typeof radius === "number" ? `${radius}px` : radius }, ...style },
2786
+ ...rest,
2787
+ children: [
2788
+ /* @__PURE__ */ jsx("span", { className: "ru-icon-tile__face", role: label ? "img" : void 0, "aria-label": label, "aria-hidden": label ? void 0 : true, children }),
2789
+ badge && /* @__PURE__ */ jsx("span", { className: "ru-icon-tile__badge", "aria-hidden": "true", children: badge })
2790
+ ]
2791
+ }
2792
+ );
2793
+ }
2055
2794
  export {
2056
2795
  Accordion,
2057
2796
  Alert,
@@ -2067,10 +2806,15 @@ export {
2067
2806
  CardFooter,
2068
2807
  CardHeader,
2069
2808
  Checkbox,
2809
+ DateTile,
2070
2810
  Divider,
2811
+ DotText,
2071
2812
  Drawer,
2072
2813
  Dropdown,
2814
+ Dropzone,
2815
+ FileItem,
2073
2816
  FrameCard,
2817
+ IconTile,
2074
2818
  Input,
2075
2819
  Kbd,
2076
2820
  Modal,
@@ -2078,12 +2822,19 @@ export {
2078
2822
  Pagination,
2079
2823
  Pattern,
2080
2824
  Progress,
2825
+ ProgressRing,
2081
2826
  Radio,
2082
2827
  RadioGroup,
2828
+ SegmentedControl,
2083
2829
  Select,
2830
+ SignaturePad,
2084
2831
  Skeleton,
2085
2832
  Spinner,
2086
2833
  SpotlightCard,
2834
+ StackedProgress,
2835
+ Stat,
2836
+ StatusIcon,
2837
+ StepList,
2087
2838
  Switch,
2088
2839
  Table,
2089
2840
  Tabs,
@@ -2091,6 +2842,7 @@ export {
2091
2842
  ToastProvider,
2092
2843
  Tooltip,
2093
2844
  cn,
2845
+ formatBytes,
2094
2846
  useClickOutside,
2095
2847
  useControllableState,
2096
2848
  useDisclosure,