reusify-ui 1.0.7 → 2.0.1

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.
@@ -0,0 +1,1506 @@
1
+ "use client";
2
+ "use strict";
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
+ const jsxRuntime = require("react/jsx-runtime");
5
+ const react = require("react");
6
+ const reactDom = require("react-dom");
7
+ function cn(...classes) {
8
+ return classes.filter(Boolean).join(" ");
9
+ }
10
+ const Button = react.forwardRef(function Button2({
11
+ variant = "primary",
12
+ size = "md",
13
+ loading = false,
14
+ fullWidth = false,
15
+ leftIcon,
16
+ rightIcon,
17
+ disabled,
18
+ type = "button",
19
+ className,
20
+ children,
21
+ ...rest
22
+ }, ref) {
23
+ return /* @__PURE__ */ jsxRuntime.jsxs(
24
+ "button",
25
+ {
26
+ ref,
27
+ type,
28
+ disabled: disabled || loading,
29
+ "aria-busy": loading || void 0,
30
+ className: cn("ru-btn", `ru-btn--${variant}`, `ru-btn--${size}`, fullWidth && "ru-btn--full", className),
31
+ ...rest,
32
+ children: [
33
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-spinner", "aria-hidden": "true" }) : leftIcon,
34
+ children,
35
+ !loading && rightIcon
36
+ ]
37
+ }
38
+ );
39
+ });
40
+ const base = {
41
+ xmlns: "http://www.w3.org/2000/svg",
42
+ viewBox: "0 0 24 24",
43
+ fill: "none",
44
+ stroke: "currentColor",
45
+ strokeWidth: 2,
46
+ strokeLinecap: "round",
47
+ strokeLinejoin: "round",
48
+ "aria-hidden": true,
49
+ focusable: "false"
50
+ };
51
+ const CloseIcon = (p) => /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...p, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M18 6 6 18M6 6l12 12" }) });
52
+ const InfoIcon = (p) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...base, ...p, children: [
53
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
54
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 16v-4M12 8h.01" })
55
+ ] });
56
+ const SuccessIcon = (p) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...base, ...p, children: [
57
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
58
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m9 12 2 2 4-4" })
59
+ ] });
60
+ const WarningIcon = (p) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...base, ...p, children: [
61
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }),
62
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 9v4M12 17h.01" })
63
+ ] });
64
+ const DangerIcon = (p) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...base, ...p, children: [
65
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
66
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m15 9-6 6M9 9l6 6" })
67
+ ] });
68
+ const ChevronDownIcon = (p) => /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...p, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m6 9 6 6 6-6" }) });
69
+ const ChevronLeftIcon = (p) => /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...p, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m15 18-6-6 6-6" }) });
70
+ const ChevronRightIcon = (p) => /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...p, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m9 18 6-6-6-6" }) });
71
+ const CheckIcon = (p) => /* @__PURE__ */ jsxRuntime.jsx("svg", { ...base, ...p, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M20 6 9 17l-5-5" }) });
72
+ const UserIcon = (p) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { ...base, ...p, children: [
73
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "8", r: "4" }),
74
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M4 21a8 8 0 0 1 16 0" })
75
+ ] });
76
+ const statusIcons = {
77
+ info: InfoIcon,
78
+ success: SuccessIcon,
79
+ warning: WarningIcon,
80
+ danger: DangerIcon
81
+ };
82
+ function CloseButton({ onClick, label = "Close" }) {
83
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ru-close", "aria-label": label, onClick, children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {}) });
84
+ }
85
+ function Spinner({ size = "md", color = "primary", label = "Loading", className, ...rest }) {
86
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { role: "status", className: cn("ru-spinner-wrap", className), ...rest, children: [
87
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("ru-spinner", `ru-spinner--${size}`, `ru-c-${color}`), "aria-hidden": "true" }),
88
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-sr-only", children: label })
89
+ ] });
90
+ }
91
+ function Progress({
92
+ value,
93
+ max = 100,
94
+ size = "md",
95
+ color = "primary",
96
+ label,
97
+ showValue = false,
98
+ className,
99
+ ...rest
100
+ }) {
101
+ const indeterminate = value === void 0;
102
+ const percent = indeterminate ? 0 : Math.min(100, Math.max(0, value / max * 100));
103
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-progress", indeterminate && "ru-progress--indeterminate", className), ...rest, children: [
104
+ (label || showValue) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-progress__label", children: [
105
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
106
+ showValue && !indeterminate && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-progress__value", children: [
107
+ Math.round(percent),
108
+ "%"
109
+ ] })
110
+ ] }),
111
+ /* @__PURE__ */ jsxRuntime.jsx(
112
+ "div",
113
+ {
114
+ role: "progressbar",
115
+ "aria-valuemin": 0,
116
+ "aria-valuemax": max,
117
+ "aria-valuenow": indeterminate ? void 0 : value,
118
+ "aria-label": typeof label === "string" ? label : void 0,
119
+ className: cn("ru-progress__track", `ru-progress__track--${size}`, `ru-c-${color}`),
120
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-progress__bar", style: indeterminate ? void 0 : { width: `${percent}%` } })
121
+ }
122
+ )
123
+ ] });
124
+ }
125
+ function Skeleton({ variant = "text", width, height, lines = 1, className, style, ...rest }) {
126
+ const block = (key, lineWidth) => /* @__PURE__ */ jsxRuntime.jsx(
127
+ "span",
128
+ {
129
+ "aria-hidden": "true",
130
+ className: cn("ru-skeleton", `ru-skeleton--${variant}`, className),
131
+ style: { width: lineWidth ?? width, height: variant === "circle" ? height ?? width : height, ...style },
132
+ ...rest
133
+ },
134
+ key
135
+ );
136
+ if (variant !== "text" || lines <= 1) return block();
137
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-skeleton-group", children: Array.from({ length: lines }, (_, i) => block(i, i === lines - 1 ? "60%" : width)) });
138
+ }
139
+ function Alert({ variant = "info", title, icon, onClose, className, children, ...rest }) {
140
+ const Icon = statusIcons[variant];
141
+ return /* @__PURE__ */ jsxRuntime.jsxs(
142
+ "div",
143
+ {
144
+ role: variant === "danger" || variant === "warning" ? "alert" : "status",
145
+ className: cn("ru-alert", `ru-c-${variant}`, className),
146
+ ...rest,
147
+ children: [
148
+ icon !== false && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-alert__icon", children: icon ?? /* @__PURE__ */ jsxRuntime.jsx(Icon, {}) }),
149
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-alert__content", children: [
150
+ title && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ru-alert__title", children: title }),
151
+ children && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-alert__desc", children })
152
+ ] }),
153
+ onClose && /* @__PURE__ */ jsxRuntime.jsx(CloseButton, { onClick: onClose, label: "Dismiss" })
154
+ ]
155
+ }
156
+ );
157
+ }
158
+ function Badge({ variant = "soft", color = "primary", size = "md", dot = false, className, children, ...rest }) {
159
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("ru-badge", `ru-badge--${variant}`, `ru-badge--${size}`, `ru-c-${color}`, className), ...rest, children: [
160
+ dot && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-badge__dot", "aria-hidden": "true" }),
161
+ children
162
+ ] });
163
+ }
164
+ function getInitials(name) {
165
+ const parts = name.trim().split(/\s+/).filter(Boolean);
166
+ if (parts.length === 0) return "";
167
+ const first = parts[0][0];
168
+ const last = parts.length > 1 ? parts[parts.length - 1][0] : "";
169
+ return (first + last).toUpperCase();
170
+ }
171
+ function Avatar({ src, alt, name, size = "md", shape = "circle", className, ...rest }) {
172
+ const [failed, setFailed] = react.useState(false);
173
+ react.useEffect(() => setFailed(false), [src]);
174
+ const showImage = src && !failed;
175
+ const initials = name ? getInitials(name) : "";
176
+ return /* @__PURE__ */ jsxRuntime.jsx(
177
+ "span",
178
+ {
179
+ className: cn("ru-avatar", `ru-avatar--${size}`, shape === "square" && "ru-avatar--square", className),
180
+ role: showImage ? void 0 : "img",
181
+ "aria-label": showImage ? void 0 : alt ?? name ?? "Avatar",
182
+ ...rest,
183
+ children: showImage ? /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: alt ?? name ?? "", onError: () => setFailed(true) }) : initials ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: initials }) : /* @__PURE__ */ jsxRuntime.jsx(UserIcon, { className: "ru-avatar__icon" })
184
+ }
185
+ );
186
+ }
187
+ function AvatarGroup({ max, size = "md", className, children, ...rest }) {
188
+ const avatars = react.Children.toArray(children).filter(react.isValidElement);
189
+ const visible = max ? avatars.slice(0, max) : avatars;
190
+ const extra = avatars.length - visible.length;
191
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-avatar-group", className), ...rest, children: [
192
+ visible.map((child) => react.cloneElement(child, { size: child.props.size ?? size })),
193
+ extra > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("ru-avatar", `ru-avatar--${size}`), "aria-label": `${extra} more`, children: [
194
+ "+",
195
+ extra
196
+ ] })
197
+ ] });
198
+ }
199
+ function Card({ variant = "outline", hoverable = false, className, ...rest }) {
200
+ return /* @__PURE__ */ jsxRuntime.jsx(
201
+ "div",
202
+ {
203
+ className: cn(
204
+ "ru-card",
205
+ variant === "elevated" && "ru-card--elevated",
206
+ hoverable && "ru-card--hoverable",
207
+ className
208
+ ),
209
+ ...rest
210
+ }
211
+ );
212
+ }
213
+ function CardHeader({ title, subtitle, action, className, children, ...rest }) {
214
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-card__header", className), ...rest, children: [
215
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
216
+ title && /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "ru-card__title", children: title }),
217
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ru-card__subtitle", children: subtitle }),
218
+ children
219
+ ] }),
220
+ action
221
+ ] });
222
+ }
223
+ function CardBody({ className, ...rest }) {
224
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("ru-card__body", className), ...rest });
225
+ }
226
+ function CardFooter({ className, ...rest }) {
227
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("ru-card__footer", className), ...rest });
228
+ }
229
+ function Divider({ orientation = "horizontal", label, className, ...rest }) {
230
+ if (label && orientation === "horizontal") {
231
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { role: "separator", className: cn("ru-divider", "ru-divider--label", className), ...rest, children: label });
232
+ }
233
+ return /* @__PURE__ */ jsxRuntime.jsx(
234
+ "hr",
235
+ {
236
+ "aria-orientation": orientation,
237
+ className: cn("ru-divider", orientation === "vertical" && "ru-divider--vertical", className),
238
+ ...rest
239
+ }
240
+ );
241
+ }
242
+ function Table({
243
+ columns,
244
+ data,
245
+ rowKey,
246
+ striped = false,
247
+ hoverable = false,
248
+ size = "md",
249
+ caption,
250
+ emptyText = "No data",
251
+ onRowClick,
252
+ className
253
+ }) {
254
+ const getKey = (row, index) => {
255
+ if (typeof rowKey === "function") return rowKey(row, index);
256
+ if (rowKey !== void 0) return String(row[rowKey]);
257
+ return index;
258
+ };
259
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("ru-table-wrap", className), children: /* @__PURE__ */ jsxRuntime.jsxs(
260
+ "table",
261
+ {
262
+ className: cn(
263
+ "ru-table",
264
+ `ru-table--${size}`,
265
+ striped && "ru-table--striped",
266
+ (hoverable || onRowClick) && "ru-table--hover"
267
+ ),
268
+ children: [
269
+ caption && /* @__PURE__ */ jsxRuntime.jsx("caption", { children: caption }),
270
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("th", { scope: "col", style: { textAlign: col.align, width: col.width }, children: col.header }, col.key)) }) }),
271
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { className: "ru-table__empty", colSpan: columns.length, children: emptyText }) }) : data.map((row, i) => /* @__PURE__ */ jsxRuntime.jsx(
272
+ "tr",
273
+ {
274
+ onClick: onRowClick ? () => onRowClick(row, i) : void 0,
275
+ style: onRowClick ? { cursor: "pointer" } : void 0,
276
+ children: columns.map((col) => /* @__PURE__ */ jsxRuntime.jsx("td", { style: { textAlign: col.align }, children: col.render ? col.render(row, i) : row[col.key] }, col.key))
277
+ },
278
+ getKey(row, i)
279
+ )) })
280
+ ]
281
+ }
282
+ ) });
283
+ }
284
+ function useControllableState(value, defaultValue, onChange) {
285
+ const [internal, setInternal] = react.useState(defaultValue);
286
+ const isControlled = value !== void 0;
287
+ const onChangeRef = react.useRef(onChange);
288
+ onChangeRef.current = onChange;
289
+ const setValue = react.useCallback(
290
+ (next) => {
291
+ var _a;
292
+ if (!isControlled) setInternal(next);
293
+ (_a = onChangeRef.current) == null ? void 0 : _a.call(onChangeRef, next);
294
+ },
295
+ [isControlled]
296
+ );
297
+ return [isControlled ? value : internal, setValue];
298
+ }
299
+ function useDisclosure(initialOpen = false) {
300
+ const [isOpen, setIsOpen] = react.useState(initialOpen);
301
+ const open = react.useCallback(() => setIsOpen(true), []);
302
+ const close = react.useCallback(() => setIsOpen(false), []);
303
+ const toggle = react.useCallback(() => setIsOpen((v) => !v), []);
304
+ return { isOpen, open, close, toggle, setIsOpen };
305
+ }
306
+ function useClickOutside(ref, handler, enabled = true) {
307
+ const handlerRef = react.useRef(handler);
308
+ handlerRef.current = handler;
309
+ react.useEffect(() => {
310
+ if (!enabled) return;
311
+ const listener = (event) => {
312
+ const el = ref.current;
313
+ if (!el || el.contains(event.target)) return;
314
+ handlerRef.current();
315
+ };
316
+ document.addEventListener("pointerdown", listener);
317
+ return () => document.removeEventListener("pointerdown", listener);
318
+ }, [ref, enabled]);
319
+ }
320
+ function useMounted() {
321
+ const [mounted, setMounted] = react.useState(false);
322
+ react.useEffect(() => setMounted(true), []);
323
+ return mounted;
324
+ }
325
+ const FOCUSABLE = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
326
+ function useOverlay(open, panelRef, onClose, closeOnEsc) {
327
+ const onCloseRef = react.useRef(onClose);
328
+ onCloseRef.current = onClose;
329
+ const closeOnEscRef = react.useRef(closeOnEsc);
330
+ closeOnEscRef.current = closeOnEsc;
331
+ react.useEffect(() => {
332
+ var _a;
333
+ if (!open) return;
334
+ const panel = panelRef.current;
335
+ const previouslyFocused = document.activeElement;
336
+ const previousOverflow = document.body.style.overflow;
337
+ document.body.style.overflow = "hidden";
338
+ (_a = (panel == null ? void 0 : panel.querySelector("[data-autofocus]")) ?? panel) == null ? void 0 : _a.focus();
339
+ const onKeyDown = (event) => {
340
+ var _a2;
341
+ if (event.key === "Escape" && closeOnEscRef.current) {
342
+ (_a2 = onCloseRef.current) == null ? void 0 : _a2.call(onCloseRef);
343
+ return;
344
+ }
345
+ if (event.key !== "Tab" || !panel) return;
346
+ const items = Array.from(panel.querySelectorAll(FOCUSABLE));
347
+ if (items.length === 0) {
348
+ event.preventDefault();
349
+ return;
350
+ }
351
+ const first = items[0];
352
+ const last = items[items.length - 1];
353
+ const active = document.activeElement;
354
+ if (event.shiftKey && (active === first || active === panel)) {
355
+ event.preventDefault();
356
+ last.focus();
357
+ } else if (!event.shiftKey && active === last) {
358
+ event.preventDefault();
359
+ first.focus();
360
+ }
361
+ };
362
+ document.addEventListener("keydown", onKeyDown);
363
+ return () => {
364
+ var _a2;
365
+ document.removeEventListener("keydown", onKeyDown);
366
+ document.body.style.overflow = previousOverflow;
367
+ (_a2 = previouslyFocused == null ? void 0 : previouslyFocused.focus) == null ? void 0 : _a2.call(previouslyFocused);
368
+ };
369
+ }, [open, panelRef]);
370
+ }
371
+ function Field({
372
+ id,
373
+ helpId,
374
+ label,
375
+ helperText,
376
+ error,
377
+ size = "md",
378
+ fullWidth,
379
+ required,
380
+ disabled,
381
+ className,
382
+ wrapClassName,
383
+ children
384
+ }) {
385
+ const message = typeof error === "string" ? error : helperText;
386
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-field", fullWidth && "ru-field--full", className), children: [
387
+ label && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "ru-label", htmlFor: id, children: [
388
+ label,
389
+ required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-label__req", "aria-hidden": "true", children: "*" })
390
+ ] }),
391
+ /* @__PURE__ */ jsxRuntime.jsx(
392
+ "div",
393
+ {
394
+ className: cn(
395
+ "ru-input-wrap",
396
+ `ru-input-wrap--${size}`,
397
+ error && "ru-input-wrap--error",
398
+ disabled && "ru-input-wrap--disabled",
399
+ wrapClassName
400
+ ),
401
+ children
402
+ }
403
+ ),
404
+ message && /* @__PURE__ */ jsxRuntime.jsx("p", { id: helpId, className: cn("ru-help", error && "ru-help--error"), children: message })
405
+ ] });
406
+ }
407
+ function useFieldIds(id, error, helperText) {
408
+ const autoId = react.useId();
409
+ const fieldId = id ?? autoId;
410
+ const helpId = `${fieldId}-help`;
411
+ const hasMessage = typeof error === "string" || Boolean(helperText);
412
+ return { fieldId, helpId, describedBy: hasMessage ? helpId : void 0 };
413
+ }
414
+ const Input = react.forwardRef(function Input2({ label, helperText, error, size, fullWidth, leftIcon, rightIcon, containerClassName, className, id, ...rest }, ref) {
415
+ const { fieldId, helpId, describedBy } = useFieldIds(id, error, helperText);
416
+ return /* @__PURE__ */ jsxRuntime.jsxs(
417
+ Field,
418
+ {
419
+ id: fieldId,
420
+ helpId,
421
+ label,
422
+ helperText,
423
+ error,
424
+ size,
425
+ fullWidth,
426
+ required: rest.required,
427
+ disabled: rest.disabled,
428
+ className: containerClassName,
429
+ children: [
430
+ leftIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-input-icon", children: leftIcon }),
431
+ /* @__PURE__ */ jsxRuntime.jsx(
432
+ "input",
433
+ {
434
+ ref,
435
+ id: fieldId,
436
+ "aria-invalid": error ? true : void 0,
437
+ "aria-describedby": describedBy,
438
+ className: cn("ru-input", className),
439
+ ...rest
440
+ }
441
+ ),
442
+ rightIcon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-input-icon", children: rightIcon })
443
+ ]
444
+ }
445
+ );
446
+ });
447
+ const Textarea = react.forwardRef(function Textarea2({ label, helperText, error, fullWidth, containerClassName, className, id, rows = 4, ...rest }, ref) {
448
+ const { fieldId, helpId, describedBy } = useFieldIds(id, error, helperText);
449
+ return /* @__PURE__ */ jsxRuntime.jsx(
450
+ Field,
451
+ {
452
+ id: fieldId,
453
+ helpId,
454
+ label,
455
+ helperText,
456
+ error,
457
+ fullWidth,
458
+ required: rest.required,
459
+ disabled: rest.disabled,
460
+ className: containerClassName,
461
+ wrapClassName: "ru-input-wrap--textarea",
462
+ children: /* @__PURE__ */ jsxRuntime.jsx(
463
+ "textarea",
464
+ {
465
+ ref,
466
+ id: fieldId,
467
+ rows,
468
+ "aria-invalid": error ? true : void 0,
469
+ "aria-describedby": describedBy,
470
+ className: cn("ru-input", className),
471
+ ...rest
472
+ }
473
+ )
474
+ }
475
+ );
476
+ });
477
+ const NativeSelect = react.forwardRef(function NativeSelect2({
478
+ label,
479
+ helperText,
480
+ error,
481
+ size,
482
+ fullWidth,
483
+ options,
484
+ placeholder,
485
+ containerClassName,
486
+ className,
487
+ id,
488
+ children,
489
+ value,
490
+ defaultValue,
491
+ ...rest
492
+ }, ref) {
493
+ const { fieldId, helpId, describedBy } = useFieldIds(id, error, helperText);
494
+ const valueProps = value !== void 0 ? { value } : { defaultValue: defaultValue ?? (placeholder ? "" : void 0) };
495
+ return /* @__PURE__ */ jsxRuntime.jsxs(
496
+ Field,
497
+ {
498
+ id: fieldId,
499
+ helpId,
500
+ label,
501
+ helperText,
502
+ error,
503
+ size,
504
+ fullWidth,
505
+ required: rest.required,
506
+ disabled: rest.disabled,
507
+ className: containerClassName,
508
+ children: [
509
+ /* @__PURE__ */ jsxRuntime.jsxs(
510
+ "select",
511
+ {
512
+ ref,
513
+ id: fieldId,
514
+ "aria-invalid": error ? true : void 0,
515
+ "aria-describedby": describedBy,
516
+ className: cn("ru-input", "ru-native-select", className),
517
+ ...valueProps,
518
+ ...rest,
519
+ children: [
520
+ placeholder && /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: true, children: placeholder }),
521
+ options == null ? void 0 : options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, disabled: opt.disabled, children: opt.label }, opt.value)),
522
+ children
523
+ ]
524
+ }
525
+ ),
526
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon, { className: "ru-native-select__chevron" })
527
+ ]
528
+ }
529
+ );
530
+ });
531
+ function childrenToOptions(children) {
532
+ const result = [];
533
+ react.Children.forEach(children, (child) => {
534
+ if (!react.isValidElement(child)) return;
535
+ if (child.type === "option") {
536
+ const { value, children: label, disabled } = child.props;
537
+ result.push({ label, value: value ?? String(label), disabled });
538
+ } else if (child.type === "optgroup") {
539
+ result.push({ label: child.props.label, options: childrenToOptions(child.props.children) });
540
+ } else if (child.type === react.Fragment) {
541
+ result.push(...childrenToOptions(child.props.children));
542
+ }
543
+ });
544
+ return result;
545
+ }
546
+ function flattenOptions(options) {
547
+ const items = [];
548
+ let index = 0;
549
+ for (const opt of options) {
550
+ if (opt.options) {
551
+ items.push({ type: "group", label: opt.label, key: `group-${items.length}` });
552
+ for (const child of opt.options) items.push({ ...child, type: "option", index: index++ });
553
+ } else {
554
+ items.push({ ...opt, type: "option", index: index++ });
555
+ }
556
+ }
557
+ return items;
558
+ }
559
+ const LIST_MAX_HEIGHT = 288;
560
+ const Select = react.forwardRef(function Select2({
561
+ label,
562
+ helperText,
563
+ error,
564
+ size = "md",
565
+ fullWidth,
566
+ options = [],
567
+ placeholder = "Select…",
568
+ value,
569
+ defaultValue,
570
+ onChange,
571
+ disabled,
572
+ required,
573
+ name,
574
+ id,
575
+ emptyText = "No options",
576
+ containerClassName,
577
+ className,
578
+ children,
579
+ ...rest
580
+ }, ref) {
581
+ const { fieldId, helpId, describedBy } = useFieldIds(id, error, helperText);
582
+ const [selected, setSelected] = useControllableState(value, defaultValue ?? null, onChange);
583
+ const [open, setOpen] = react.useState(false);
584
+ const [active, setActive] = react.useState(-1);
585
+ const [position, setPosition] = react.useState(null);
586
+ const triggerRef = react.useRef(null);
587
+ const listRef = react.useRef(null);
588
+ const typeahead = react.useRef({ text: "", timer: void 0 });
589
+ const listId = react.useId();
590
+ const mounted = useMounted();
591
+ react.useImperativeHandle(ref, () => triggerRef.current);
592
+ const items = react.useMemo(() => flattenOptions([...options, ...childrenToOptions(children)]), [options, children]);
593
+ const selectable = react.useMemo(() => items.filter((item) => item.type === "option"), [items]);
594
+ const selectedItem = selectable.find((opt) => opt.value === selected);
595
+ const step = (from, dir) => {
596
+ let i = from;
597
+ for (let n = 0; n < selectable.length; n++) {
598
+ i = (i + dir + selectable.length) % selectable.length;
599
+ if (!selectable[i].disabled) return i;
600
+ }
601
+ return from;
602
+ };
603
+ const firstEnabled = () => step(-1, 1);
604
+ const lastEnabled = () => step(0, -1);
605
+ const openList = () => {
606
+ if (disabled) return;
607
+ const current = selectable.findIndex((opt) => opt.value === selected);
608
+ setActive(current >= 0 ? current : firstEnabled());
609
+ setOpen(true);
610
+ };
611
+ const choose = (opt) => {
612
+ var _a;
613
+ if (!opt || opt.disabled) return;
614
+ setSelected(opt.value);
615
+ setOpen(false);
616
+ (_a = triggerRef.current) == null ? void 0 : _a.focus();
617
+ };
618
+ const runTypeahead = (char) => {
619
+ const state = typeahead.current;
620
+ clearTimeout(state.timer);
621
+ state.text += char.toLowerCase();
622
+ state.timer = setTimeout(() => state.text = "", 500);
623
+ const start = open ? active : selectable.findIndex((opt) => opt.value === selected);
624
+ for (let n = 1; n <= selectable.length; n++) {
625
+ const i = (start + n + selectable.length) % selectable.length;
626
+ const opt = selectable[i];
627
+ if (!opt.disabled && String(opt.label).toLowerCase().startsWith(state.text)) {
628
+ setActive(i);
629
+ if (!open) setOpen(true);
630
+ return;
631
+ }
632
+ }
633
+ };
634
+ const onKeyDown = (e) => {
635
+ if (disabled) return;
636
+ const { key } = e;
637
+ if (!open) {
638
+ if (key === "ArrowDown" || key === "ArrowUp" || key === "Enter" || key === " ") {
639
+ e.preventDefault();
640
+ openList();
641
+ } else if (key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) {
642
+ runTypeahead(key);
643
+ }
644
+ return;
645
+ }
646
+ switch (key) {
647
+ case "ArrowDown":
648
+ e.preventDefault();
649
+ setActive((i) => step(i, 1));
650
+ break;
651
+ case "ArrowUp":
652
+ e.preventDefault();
653
+ setActive((i) => step(i, -1));
654
+ break;
655
+ case "Home":
656
+ e.preventDefault();
657
+ setActive(firstEnabled());
658
+ break;
659
+ case "End":
660
+ e.preventDefault();
661
+ setActive(lastEnabled());
662
+ break;
663
+ case "Enter":
664
+ case " ":
665
+ e.preventDefault();
666
+ if (key === " " && typeahead.current.text) runTypeahead(" ");
667
+ else choose(selectable[active]);
668
+ break;
669
+ case "Escape":
670
+ e.preventDefault();
671
+ e.stopPropagation();
672
+ setOpen(false);
673
+ break;
674
+ case "Tab":
675
+ setOpen(false);
676
+ break;
677
+ default:
678
+ if (key.length === 1 && !e.metaKey && !e.ctrlKey && !e.altKey) runTypeahead(key);
679
+ }
680
+ };
681
+ react.useEffect(() => {
682
+ if (!open) {
683
+ setPosition(null);
684
+ return;
685
+ }
686
+ const update = () => {
687
+ var _a;
688
+ const anchor = (_a = triggerRef.current) == null ? void 0 : _a.parentElement;
689
+ if (!anchor) return;
690
+ const rect = anchor.getBoundingClientRect();
691
+ const below = window.innerHeight - rect.bottom;
692
+ const above = rect.top;
693
+ const top = below < 240 && above > below;
694
+ setPosition({
695
+ left: rect.left,
696
+ width: rect.width,
697
+ top: top ? void 0 : rect.bottom + 6,
698
+ bottom: top ? window.innerHeight - rect.top + 6 : void 0,
699
+ maxHeight: Math.max(120, Math.min(LIST_MAX_HEIGHT, (top ? above : below) - 16)),
700
+ placement: top ? "top" : "bottom"
701
+ });
702
+ };
703
+ update();
704
+ window.addEventListener("resize", update);
705
+ window.addEventListener("scroll", update, true);
706
+ return () => {
707
+ window.removeEventListener("resize", update);
708
+ window.removeEventListener("scroll", update, true);
709
+ };
710
+ }, [open]);
711
+ react.useEffect(() => {
712
+ if (!open) return;
713
+ const onPointerDown = (e) => {
714
+ var _a, _b;
715
+ if (((_a = triggerRef.current) == null ? void 0 : _a.contains(e.target)) || ((_b = listRef.current) == null ? void 0 : _b.contains(e.target))) return;
716
+ setOpen(false);
717
+ };
718
+ document.addEventListener("pointerdown", onPointerDown);
719
+ return () => document.removeEventListener("pointerdown", onPointerDown);
720
+ }, [open]);
721
+ react.useEffect(() => {
722
+ var _a;
723
+ if (open && position && active >= 0) {
724
+ (_a = document.getElementById(`${listId}-${active}`)) == null ? void 0 : _a.scrollIntoView({ block: "nearest" });
725
+ }
726
+ }, [open, position, active, listId]);
727
+ react.useEffect(() => () => clearTimeout(typeahead.current.timer), []);
728
+ return /* @__PURE__ */ jsxRuntime.jsxs(
729
+ Field,
730
+ {
731
+ id: fieldId,
732
+ helpId,
733
+ label,
734
+ helperText,
735
+ error,
736
+ size,
737
+ fullWidth,
738
+ required,
739
+ disabled,
740
+ className: containerClassName,
741
+ wrapClassName: cn("ru-select-wrap", open && "ru-select-wrap--open"),
742
+ children: [
743
+ /* @__PURE__ */ jsxRuntime.jsxs(
744
+ "button",
745
+ {
746
+ ref: triggerRef,
747
+ id: fieldId,
748
+ type: "button",
749
+ role: "combobox",
750
+ "aria-haspopup": "listbox",
751
+ "aria-expanded": open,
752
+ "aria-controls": open ? listId : void 0,
753
+ "aria-activedescendant": open && active >= 0 ? `${listId}-${active}` : void 0,
754
+ "aria-invalid": error ? true : void 0,
755
+ "aria-required": required || void 0,
756
+ "aria-describedby": describedBy,
757
+ disabled,
758
+ className: cn("ru-select-trigger", className),
759
+ onClick: () => open ? setOpen(false) : openList(),
760
+ onKeyDown,
761
+ ...rest,
762
+ children: [
763
+ (selectedItem == null ? void 0 : selectedItem.icon) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-select__icon", children: selectedItem.icon }),
764
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("ru-select__value", !selectedItem && "ru-select__placeholder"), children: selectedItem ? selectedItem.label : placeholder }),
765
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon, { className: "ru-select__chevron" })
766
+ ]
767
+ }
768
+ ),
769
+ (name || required) && /* @__PURE__ */ jsxRuntime.jsx(
770
+ "input",
771
+ {
772
+ className: "ru-select__native",
773
+ tabIndex: -1,
774
+ "aria-hidden": "true",
775
+ name,
776
+ required,
777
+ value: selected ?? "",
778
+ onChange: () => {
779
+ },
780
+ onFocus: () => {
781
+ var _a;
782
+ return (_a = triggerRef.current) == null ? void 0 : _a.focus();
783
+ }
784
+ }
785
+ ),
786
+ open && position && mounted && reactDom.createPortal(
787
+ /* @__PURE__ */ jsxRuntime.jsxs(
788
+ "ul",
789
+ {
790
+ ref: listRef,
791
+ id: listId,
792
+ role: "listbox",
793
+ "aria-labelledby": fieldId,
794
+ className: cn("ru-select__list", position.placement === "top" && "ru-select__list--top"),
795
+ style: {
796
+ left: position.left,
797
+ width: position.width,
798
+ top: position.top,
799
+ bottom: position.bottom,
800
+ maxHeight: position.maxHeight
801
+ },
802
+ onMouseDown: (e) => e.preventDefault(),
803
+ children: [
804
+ selectable.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("li", { className: "ru-select__empty", children: emptyText }),
805
+ items.map(
806
+ (item) => item.type === "group" ? /* @__PURE__ */ jsxRuntime.jsx("li", { role: "presentation", className: "ru-select__group", children: item.label }, item.key) : /* @__PURE__ */ jsxRuntime.jsxs(
807
+ "li",
808
+ {
809
+ id: `${listId}-${item.index}`,
810
+ role: "option",
811
+ "aria-selected": item.value === selected,
812
+ "aria-disabled": item.disabled || void 0,
813
+ className: cn(
814
+ "ru-select__option",
815
+ item.index === active && "ru-select__option--active",
816
+ item.value === selected && "ru-select__option--selected",
817
+ item.disabled && "ru-select__option--disabled"
818
+ ),
819
+ onMouseMove: () => !item.disabled && item.index !== active && setActive(item.index),
820
+ onClick: () => choose(item),
821
+ children: [
822
+ item.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-select__icon", children: item.icon }),
823
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-select__text", children: [
824
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-select__label", children: item.label }),
825
+ item.description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-select__desc", children: item.description })
826
+ ] }),
827
+ item.value === selected && /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, { className: "ru-select__check" })
828
+ ]
829
+ },
830
+ item.value
831
+ )
832
+ )
833
+ ]
834
+ }
835
+ ),
836
+ document.body
837
+ )
838
+ ]
839
+ }
840
+ );
841
+ });
842
+ const Checkbox = react.forwardRef(function Checkbox2({ label, description, indeterminate = false, color = "primary", className, disabled, ...rest }, ref) {
843
+ const innerRef = react.useRef(null);
844
+ react.useImperativeHandle(ref, () => innerRef.current);
845
+ react.useEffect(() => {
846
+ if (innerRef.current) innerRef.current.indeterminate = indeterminate;
847
+ }, [indeterminate]);
848
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: cn("ru-check", `ru-c-${color}`, disabled && "ru-check--disabled", className), children: [
849
+ /* @__PURE__ */ jsxRuntime.jsx("input", { ref: innerRef, type: "checkbox", className: "ru-check__input", disabled, ...rest }),
850
+ (label || description) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-check__text", children: [
851
+ label,
852
+ description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-check__desc", children: description })
853
+ ] })
854
+ ] });
855
+ });
856
+ const Radio = react.forwardRef(function Radio2({ label, description, color = "primary", className, disabled, ...rest }, ref) {
857
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: cn("ru-check", `ru-c-${color}`, disabled && "ru-check--disabled", className), children: [
858
+ /* @__PURE__ */ jsxRuntime.jsx("input", { ref, type: "radio", className: "ru-check__input ru-check__input--radio", disabled, ...rest }),
859
+ (label || description) && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "ru-check__text", children: [
860
+ label,
861
+ description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-check__desc", children: description })
862
+ ] })
863
+ ] });
864
+ });
865
+ function RadioGroup({
866
+ options,
867
+ name,
868
+ value,
869
+ defaultValue,
870
+ onChange,
871
+ label,
872
+ orientation = "vertical",
873
+ color = "primary",
874
+ disabled,
875
+ className
876
+ }) {
877
+ const autoName = react.useId();
878
+ const [selected, setSelected] = useControllableState(value, defaultValue ?? "", onChange);
879
+ return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { className: cn("ru-radio-group", className), disabled, children: [
880
+ label && /* @__PURE__ */ jsxRuntime.jsx("legend", { className: "ru-label", children: label }),
881
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("ru-radio-group__items", orientation === "horizontal" && "ru-radio-group__items--horizontal"), children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
882
+ Radio,
883
+ {
884
+ name: name ?? autoName,
885
+ value: opt.value,
886
+ label: opt.label,
887
+ description: opt.description,
888
+ color,
889
+ disabled: disabled || opt.disabled,
890
+ checked: selected === opt.value,
891
+ onChange: () => setSelected(opt.value)
892
+ },
893
+ opt.value
894
+ )) })
895
+ ] });
896
+ }
897
+ const Switch = react.forwardRef(function Switch2({ checked, defaultChecked = false, onChange, label, size = "md", color = "primary", disabled, name, id, className }, ref) {
898
+ const [on, setOn] = useControllableState(checked, defaultChecked, onChange);
899
+ return /* @__PURE__ */ jsxRuntime.jsxs(
900
+ "label",
901
+ {
902
+ className: cn("ru-switch", `ru-switch--${size}`, `ru-c-${color}`, disabled && "ru-switch--disabled", className),
903
+ children: [
904
+ /* @__PURE__ */ jsxRuntime.jsx(
905
+ "button",
906
+ {
907
+ ref,
908
+ id,
909
+ type: "button",
910
+ role: "switch",
911
+ "aria-checked": on,
912
+ disabled,
913
+ className: "ru-switch__track",
914
+ onClick: () => setOn(!on),
915
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-switch__thumb" })
916
+ }
917
+ ),
918
+ label && /* @__PURE__ */ jsxRuntime.jsx("span", { children: label }),
919
+ name && /* @__PURE__ */ jsxRuntime.jsx("input", { type: "hidden", name, value: on ? "on" : "off" })
920
+ ]
921
+ }
922
+ );
923
+ });
924
+ function DialogContent({ titleId, descId, title, description, showCloseButton, onClose, children, footer }) {
925
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
926
+ (title || description || showCloseButton) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-modal__header", children: [
927
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
928
+ title && /* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, className: "ru-modal__title", children: title }),
929
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { id: descId, className: "ru-modal__desc", children: description })
930
+ ] }),
931
+ showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(CloseButton, { onClick: onClose })
932
+ ] }),
933
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-modal__body", children }),
934
+ footer && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-modal__footer", children: footer })
935
+ ] });
936
+ }
937
+ function Modal({
938
+ open,
939
+ onClose,
940
+ size = "md",
941
+ closeOnOverlayClick = true,
942
+ closeOnEsc = true,
943
+ showCloseButton = true,
944
+ className,
945
+ ...content
946
+ }) {
947
+ const panelRef = react.useRef(null);
948
+ const mounted = useMounted();
949
+ const titleId = react.useId();
950
+ const descId = react.useId();
951
+ useOverlay(open && mounted, panelRef, onClose, closeOnEsc);
952
+ if (!open || !mounted) return null;
953
+ const onOverlayMouseDown = (e) => {
954
+ if (closeOnOverlayClick && e.target === e.currentTarget) onClose();
955
+ };
956
+ return reactDom.createPortal(
957
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-overlay", onMouseDown: onOverlayMouseDown, children: /* @__PURE__ */ jsxRuntime.jsx(
958
+ "div",
959
+ {
960
+ ref: panelRef,
961
+ role: "dialog",
962
+ "aria-modal": "true",
963
+ "aria-labelledby": content.title ? titleId : void 0,
964
+ "aria-describedby": content.description ? descId : void 0,
965
+ tabIndex: -1,
966
+ className: cn("ru-modal", `ru-modal--${size}`, className),
967
+ children: /* @__PURE__ */ jsxRuntime.jsx(
968
+ DialogContent,
969
+ {
970
+ ...content,
971
+ titleId,
972
+ descId,
973
+ showCloseButton,
974
+ onClose
975
+ }
976
+ )
977
+ }
978
+ ) }),
979
+ document.body
980
+ );
981
+ }
982
+ const drawerSizes = { sm: "20rem", md: "24rem", lg: "32rem" };
983
+ function Drawer({
984
+ open,
985
+ onClose,
986
+ placement = "right",
987
+ size = "md",
988
+ closeOnOverlayClick = true,
989
+ closeOnEsc = true,
990
+ showCloseButton = true,
991
+ className,
992
+ ...content
993
+ }) {
994
+ const panelRef = react.useRef(null);
995
+ const mounted = useMounted();
996
+ const titleId = react.useId();
997
+ const descId = react.useId();
998
+ useOverlay(open && mounted, panelRef, onClose, closeOnEsc);
999
+ if (!open || !mounted) return null;
1000
+ const resolvedSize = typeof size === "number" ? `${size}px` : drawerSizes[size] ?? size;
1001
+ return reactDom.createPortal(
1002
+ /* @__PURE__ */ jsxRuntime.jsx(
1003
+ "div",
1004
+ {
1005
+ className: "ru-overlay ru-overlay--drawer",
1006
+ onMouseDown: (e) => {
1007
+ if (closeOnOverlayClick && e.target === e.currentTarget) onClose();
1008
+ },
1009
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1010
+ "div",
1011
+ {
1012
+ ref: panelRef,
1013
+ role: "dialog",
1014
+ "aria-modal": "true",
1015
+ "aria-labelledby": content.title ? titleId : void 0,
1016
+ "aria-describedby": content.description ? descId : void 0,
1017
+ tabIndex: -1,
1018
+ className: cn("ru-drawer", `ru-drawer--${placement}`, className),
1019
+ style: { "--ru-drawer-size": resolvedSize },
1020
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1021
+ DialogContent,
1022
+ {
1023
+ ...content,
1024
+ titleId,
1025
+ descId,
1026
+ showCloseButton,
1027
+ onClose
1028
+ }
1029
+ )
1030
+ }
1031
+ )
1032
+ }
1033
+ ),
1034
+ document.body
1035
+ );
1036
+ }
1037
+ function Tooltip({ content, children, placement = "top", delay = 100, disabled, className }) {
1038
+ const [open, setOpen] = react.useState(false);
1039
+ const timer = react.useRef(void 0);
1040
+ const id = react.useId();
1041
+ const visible = open && !disabled && content != null && content !== "";
1042
+ react.useEffect(() => () => clearTimeout(timer.current), []);
1043
+ const show = () => {
1044
+ clearTimeout(timer.current);
1045
+ timer.current = setTimeout(() => setOpen(true), delay);
1046
+ };
1047
+ const hide = () => {
1048
+ clearTimeout(timer.current);
1049
+ setOpen(false);
1050
+ };
1051
+ const trigger = react.isValidElement(children) ? react.cloneElement(children, {
1052
+ "aria-describedby": visible ? id : void 0
1053
+ }) : children;
1054
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1055
+ "span",
1056
+ {
1057
+ className: cn("ru-tooltip", className),
1058
+ onMouseEnter: show,
1059
+ onMouseLeave: hide,
1060
+ onFocus: show,
1061
+ onBlur: hide,
1062
+ onKeyDown: (e) => e.key === "Escape" && hide(),
1063
+ children: [
1064
+ trigger,
1065
+ visible && /* @__PURE__ */ jsxRuntime.jsx("span", { role: "tooltip", id, className: cn("ru-tooltip__bubble", `ru-tooltip__bubble--${placement}`), children: content })
1066
+ ]
1067
+ }
1068
+ );
1069
+ }
1070
+ function Dropdown({ trigger, items, align = "start", closeOnSelect = true, className }) {
1071
+ const [open, setOpen] = react.useState(false);
1072
+ const rootRef = react.useRef(null);
1073
+ const menuRef = react.useRef(null);
1074
+ const menuId = react.useId();
1075
+ useClickOutside(rootRef, () => setOpen(false), open);
1076
+ const getItems = () => {
1077
+ var _a;
1078
+ return Array.from(((_a = menuRef.current) == null ? void 0 : _a.querySelectorAll('[role="menuitem"]:not(:disabled)')) ?? []);
1079
+ };
1080
+ const focusItem = (index) => {
1081
+ const els = getItems();
1082
+ if (els.length) els[(index + els.length) % els.length].focus();
1083
+ };
1084
+ const closeAndFocusTrigger = () => {
1085
+ var _a, _b;
1086
+ setOpen(false);
1087
+ (_b = (_a = rootRef.current) == null ? void 0 : _a.querySelector("[aria-haspopup]")) == null ? void 0 : _b.focus();
1088
+ };
1089
+ react.useEffect(() => {
1090
+ if (open) focusItem(0);
1091
+ }, [open]);
1092
+ const onMenuKeyDown = (e) => {
1093
+ const els = getItems();
1094
+ const index = els.indexOf(document.activeElement);
1095
+ switch (e.key) {
1096
+ case "ArrowDown":
1097
+ e.preventDefault();
1098
+ focusItem(index + 1);
1099
+ break;
1100
+ case "ArrowUp":
1101
+ e.preventDefault();
1102
+ focusItem(index - 1);
1103
+ break;
1104
+ case "Home":
1105
+ e.preventDefault();
1106
+ focusItem(0);
1107
+ break;
1108
+ case "End":
1109
+ e.preventDefault();
1110
+ focusItem(-1);
1111
+ break;
1112
+ case "Escape":
1113
+ e.preventDefault();
1114
+ closeAndFocusTrigger();
1115
+ break;
1116
+ case "Tab":
1117
+ setOpen(false);
1118
+ break;
1119
+ }
1120
+ };
1121
+ const triggerProps = trigger.props;
1122
+ const triggerEl = react.cloneElement(trigger, {
1123
+ "aria-haspopup": "menu",
1124
+ "aria-expanded": open,
1125
+ "aria-controls": open ? menuId : void 0,
1126
+ onClick: (e) => {
1127
+ var _a;
1128
+ (_a = triggerProps.onClick) == null ? void 0 : _a.call(triggerProps, e);
1129
+ setOpen((v) => !v);
1130
+ },
1131
+ onKeyDown: (e) => {
1132
+ var _a;
1133
+ (_a = triggerProps.onKeyDown) == null ? void 0 : _a.call(triggerProps, e);
1134
+ if (e.key === "ArrowDown" && !open) {
1135
+ e.preventDefault();
1136
+ setOpen(true);
1137
+ }
1138
+ }
1139
+ });
1140
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: rootRef, className: cn("ru-dropdown", className), children: [
1141
+ triggerEl,
1142
+ open && /* @__PURE__ */ jsxRuntime.jsx(
1143
+ "div",
1144
+ {
1145
+ ref: menuRef,
1146
+ id: menuId,
1147
+ role: "menu",
1148
+ className: cn("ru-dropdown__menu", align === "end" && "ru-dropdown__menu--end"),
1149
+ onKeyDown: onMenuKeyDown,
1150
+ children: items.map(
1151
+ (item, i) => item.divider ? /* @__PURE__ */ jsxRuntime.jsx("div", { role: "separator", className: "ru-dropdown__divider" }, item.key ?? i) : /* @__PURE__ */ jsxRuntime.jsxs(
1152
+ "button",
1153
+ {
1154
+ type: "button",
1155
+ role: "menuitem",
1156
+ tabIndex: -1,
1157
+ disabled: item.disabled,
1158
+ className: cn("ru-dropdown__item", item.danger && "ru-dropdown__item--danger"),
1159
+ onClick: () => {
1160
+ var _a;
1161
+ (_a = item.onClick) == null ? void 0 : _a.call(item);
1162
+ if (closeOnSelect) closeAndFocusTrigger();
1163
+ },
1164
+ children: [
1165
+ item.icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-dropdown__icon", children: item.icon }),
1166
+ item.label
1167
+ ]
1168
+ },
1169
+ item.key ?? i
1170
+ )
1171
+ )
1172
+ }
1173
+ )
1174
+ ] });
1175
+ }
1176
+ function Tabs({ items, value, defaultValue, onChange, variant = "line", fullWidth = false, className }) {
1177
+ var _a;
1178
+ const baseId = react.useId();
1179
+ const firstEnabled = ((_a = items.find((item) => !item.disabled)) == null ? void 0 : _a.value) ?? "";
1180
+ const [active, setActive] = useControllableState(value, defaultValue ?? firstEnabled, onChange);
1181
+ const tabRefs = react.useRef([]);
1182
+ const onKeyDown = (e, index) => {
1183
+ var _a2;
1184
+ const enabled = items.flatMap((item, i) => item.disabled ? [] : [i]);
1185
+ const pos = enabled.indexOf(index);
1186
+ let next;
1187
+ switch (e.key) {
1188
+ case "ArrowRight":
1189
+ next = enabled[(pos + 1) % enabled.length];
1190
+ break;
1191
+ case "ArrowLeft":
1192
+ next = enabled[(pos - 1 + enabled.length) % enabled.length];
1193
+ break;
1194
+ case "Home":
1195
+ next = enabled[0];
1196
+ break;
1197
+ case "End":
1198
+ next = enabled[enabled.length - 1];
1199
+ break;
1200
+ default:
1201
+ return;
1202
+ }
1203
+ e.preventDefault();
1204
+ (_a2 = tabRefs.current[next]) == null ? void 0 : _a2.focus();
1205
+ setActive(items[next].value);
1206
+ };
1207
+ const activeIndex = items.findIndex((item) => item.value === active);
1208
+ const activeItem = items[activeIndex];
1209
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ru-tabs", `ru-tabs--${variant}`, fullWidth && "ru-tabs--full", className), children: [
1210
+ /* @__PURE__ */ jsxRuntime.jsx("div", { role: "tablist", className: "ru-tabs__list", children: items.map((item, i) => {
1211
+ const selected = i === activeIndex;
1212
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1213
+ "button",
1214
+ {
1215
+ ref: (el) => {
1216
+ tabRefs.current[i] = el;
1217
+ },
1218
+ type: "button",
1219
+ role: "tab",
1220
+ id: `${baseId}-tab-${i}`,
1221
+ "aria-selected": selected,
1222
+ "aria-controls": `${baseId}-panel-${i}`,
1223
+ tabIndex: selected ? 0 : -1,
1224
+ disabled: item.disabled,
1225
+ className: "ru-tabs__tab",
1226
+ onClick: () => setActive(item.value),
1227
+ onKeyDown: (e) => onKeyDown(e, i),
1228
+ children: [
1229
+ item.icon,
1230
+ item.label
1231
+ ]
1232
+ },
1233
+ item.value
1234
+ );
1235
+ }) }),
1236
+ activeItem && activeItem.content !== void 0 && /* @__PURE__ */ jsxRuntime.jsx(
1237
+ "div",
1238
+ {
1239
+ role: "tabpanel",
1240
+ id: `${baseId}-panel-${activeIndex}`,
1241
+ "aria-labelledby": `${baseId}-tab-${activeIndex}`,
1242
+ tabIndex: 0,
1243
+ className: "ru-tabs__panel",
1244
+ children: activeItem.content
1245
+ }
1246
+ )
1247
+ ] });
1248
+ }
1249
+ function Accordion({ items, multiple = false, value, defaultValue, onChange, className }) {
1250
+ const baseId = react.useId();
1251
+ const [openValues, setOpenValues] = useControllableState(value, defaultValue ?? [], onChange);
1252
+ const toggle = (key) => {
1253
+ const isOpen = openValues.includes(key);
1254
+ if (isOpen) setOpenValues(openValues.filter((v) => v !== key));
1255
+ else setOpenValues(multiple ? [...openValues, key] : [key]);
1256
+ };
1257
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("ru-accordion", className), children: items.map((item, i) => {
1258
+ const key = item.value ?? String(i);
1259
+ const isOpen = openValues.includes(key);
1260
+ const triggerId = `${baseId}-trigger-${i}`;
1261
+ const panelId = `${baseId}-panel-${i}`;
1262
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-accordion__item", children: [
1263
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "ru-accordion__heading", children: /* @__PURE__ */ jsxRuntime.jsxs(
1264
+ "button",
1265
+ {
1266
+ type: "button",
1267
+ id: triggerId,
1268
+ "aria-expanded": isOpen,
1269
+ "aria-controls": panelId,
1270
+ disabled: item.disabled,
1271
+ className: "ru-accordion__trigger",
1272
+ onClick: () => toggle(key),
1273
+ children: [
1274
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: item.title }),
1275
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDownIcon, { className: "ru-accordion__chevron" })
1276
+ ]
1277
+ }
1278
+ ) }),
1279
+ /* @__PURE__ */ jsxRuntime.jsx(
1280
+ "div",
1281
+ {
1282
+ id: panelId,
1283
+ role: "region",
1284
+ "aria-labelledby": triggerId,
1285
+ className: cn("ru-accordion__panel", isOpen && "ru-accordion__panel--open"),
1286
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-accordion__inner", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-accordion__content", children: item.content }) })
1287
+ }
1288
+ )
1289
+ ] }, key);
1290
+ }) });
1291
+ }
1292
+ function range(start, end) {
1293
+ return Array.from({ length: end - start + 1 }, (_, i) => start + i);
1294
+ }
1295
+ function getPageTokens(page, totalPages, siblings) {
1296
+ const slots = siblings * 2 + 5;
1297
+ if (totalPages <= slots) return range(1, totalPages);
1298
+ const left = Math.max(page - siblings, 1);
1299
+ const right = Math.min(page + siblings, totalPages);
1300
+ const showLeftEllipsis = left > 2;
1301
+ const showRightEllipsis = right < totalPages - 1;
1302
+ const edgeCount = 3 + siblings * 2;
1303
+ if (!showLeftEllipsis) return [...range(1, edgeCount), "end-ellipsis", totalPages];
1304
+ if (!showRightEllipsis) return [1, "start-ellipsis", ...range(totalPages - edgeCount + 1, totalPages)];
1305
+ return [1, "start-ellipsis", ...range(left, right), "end-ellipsis", totalPages];
1306
+ }
1307
+ function Pagination({ page, totalPages, onChange, siblingCount = 1, size = "md", className }) {
1308
+ if (totalPages < 1) return null;
1309
+ const go = (p) => {
1310
+ if (p >= 1 && p <= totalPages && p !== page) onChange(p);
1311
+ };
1312
+ return /* @__PURE__ */ jsxRuntime.jsx("nav", { "aria-label": "Pagination", className: cn("ru-pagination", `ru-pagination--${size}`, className), children: /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "ru-pagination__list", children: [
1313
+ /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
1314
+ "button",
1315
+ {
1316
+ type: "button",
1317
+ className: "ru-pagination__btn",
1318
+ "aria-label": "Previous page",
1319
+ disabled: page <= 1,
1320
+ onClick: () => go(page - 1),
1321
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeftIcon, {})
1322
+ }
1323
+ ) }),
1324
+ getPageTokens(page, totalPages, siblingCount).map(
1325
+ (token) => typeof token === "number" ? /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
1326
+ "button",
1327
+ {
1328
+ type: "button",
1329
+ className: "ru-pagination__btn",
1330
+ "aria-label": `Page ${token}`,
1331
+ "aria-current": token === page ? "page" : void 0,
1332
+ onClick: () => go(token),
1333
+ children: token
1334
+ }
1335
+ ) }, token) : /* @__PURE__ */ jsxRuntime.jsx("li", { className: "ru-pagination__ellipsis", "aria-hidden": "true", children: "…" }, token)
1336
+ ),
1337
+ /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsx(
1338
+ "button",
1339
+ {
1340
+ type: "button",
1341
+ className: "ru-pagination__btn",
1342
+ "aria-label": "Next page",
1343
+ disabled: page >= totalPages,
1344
+ onClick: () => go(page + 1),
1345
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronRightIcon, {})
1346
+ }
1347
+ ) })
1348
+ ] }) });
1349
+ }
1350
+ function Breadcrumbs({ items, separator, className }) {
1351
+ return /* @__PURE__ */ jsxRuntime.jsx("nav", { "aria-label": "Breadcrumb", className: cn("ru-breadcrumbs", className), children: /* @__PURE__ */ jsxRuntime.jsx("ol", { children: items.map((item, i) => {
1352
+ const isLast = i === items.length - 1;
1353
+ let node = item.label;
1354
+ if (isLast) {
1355
+ node = /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-breadcrumbs__current", "aria-current": "page", children: item.label });
1356
+ } else if (item.href) {
1357
+ node = /* @__PURE__ */ jsxRuntime.jsx("a", { className: "ru-breadcrumbs__link", href: item.href, onClick: item.onClick, children: item.label });
1358
+ } else if (item.onClick) {
1359
+ node = /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "ru-breadcrumbs__link", onClick: item.onClick, children: item.label });
1360
+ }
1361
+ return /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
1362
+ /* @__PURE__ */ jsxRuntime.jsx("li", { className: "ru-breadcrumbs__item", children: node }),
1363
+ !isLast && /* @__PURE__ */ jsxRuntime.jsx("li", { className: "ru-breadcrumbs__sep", "aria-hidden": "true", children: separator ?? /* @__PURE__ */ jsxRuntime.jsx(ChevronRightIcon, {}) })
1364
+ ] }, i);
1365
+ }) }) });
1366
+ }
1367
+ const ToastContext = react.createContext(null);
1368
+ function ToastProvider({ children, position = "bottom-right", duration = 4e3, max = 5 }) {
1369
+ const [toasts, setToasts] = react.useState([]);
1370
+ const mounted = useMounted();
1371
+ const counter = react.useRef(0);
1372
+ const dismiss = react.useCallback((id) => {
1373
+ setToasts((list) => id ? list.filter((t) => t.id !== id) : []);
1374
+ }, []);
1375
+ const api = react.useMemo(() => {
1376
+ const show = (options) => {
1377
+ const opts = typeof options === "string" ? { title: options } : options;
1378
+ const id = opts.id ?? `ru-toast-${++counter.current}`;
1379
+ setToasts((list) => [...list.filter((t) => t.id !== id), { ...opts, id }].slice(-max));
1380
+ return id;
1381
+ };
1382
+ const fn = show;
1383
+ fn.success = (title, o) => show({ ...o, title, variant: "success" });
1384
+ fn.error = (title, o) => show({ ...o, title, variant: "danger" });
1385
+ fn.warning = (title, o) => show({ ...o, title, variant: "warning" });
1386
+ fn.info = (title, o) => show({ ...o, title, variant: "info" });
1387
+ fn.dismiss = dismiss;
1388
+ return fn;
1389
+ }, [dismiss, max]);
1390
+ return /* @__PURE__ */ jsxRuntime.jsxs(ToastContext.Provider, { value: api, children: [
1391
+ children,
1392
+ mounted && reactDom.createPortal(
1393
+ /* @__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)) }),
1394
+ document.body
1395
+ )
1396
+ ] });
1397
+ }
1398
+ function ToastView({ toast, duration, onDismiss }) {
1399
+ const [paused, setPaused] = react.useState(false);
1400
+ const { id, variant = "default", title, description, action } = toast;
1401
+ react.useEffect(() => {
1402
+ if (paused || !Number.isFinite(duration) || duration <= 0) return;
1403
+ const timer = setTimeout(() => onDismiss(id), duration);
1404
+ return () => clearTimeout(timer);
1405
+ }, [id, duration, paused, onDismiss]);
1406
+ const Icon = variant === "default" ? null : statusIcons[variant];
1407
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1408
+ "li",
1409
+ {
1410
+ role: "status",
1411
+ className: cn("ru-toast", variant !== "default" && `ru-c-${variant}`),
1412
+ onMouseEnter: () => setPaused(true),
1413
+ onMouseLeave: () => setPaused(false),
1414
+ children: [
1415
+ Icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ru-toast__icon", children: /* @__PURE__ */ jsxRuntime.jsx(Icon, {}) }),
1416
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ru-toast__content", children: [
1417
+ title && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-toast__title", children: title }),
1418
+ description && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-toast__desc", children: description }),
1419
+ action && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ru-toast__action", children: action })
1420
+ ] }),
1421
+ /* @__PURE__ */ jsxRuntime.jsx(CloseButton, { onClick: () => onDismiss(id), label: "Dismiss notification" })
1422
+ ]
1423
+ }
1424
+ );
1425
+ }
1426
+ function useToast() {
1427
+ const ctx = react.useContext(ToastContext);
1428
+ if (!ctx) throw new Error("useToast must be used within a <ToastProvider>.");
1429
+ return ctx;
1430
+ }
1431
+ function Kbd({ className, ...rest }) {
1432
+ return /* @__PURE__ */ jsxRuntime.jsx("kbd", { className: cn("ru-kbd", className), ...rest });
1433
+ }
1434
+ function Pattern({ variant = "grid", size = 24, fade = true, className, style, ...rest }) {
1435
+ return /* @__PURE__ */ jsxRuntime.jsx(
1436
+ "div",
1437
+ {
1438
+ "aria-hidden": "true",
1439
+ className: cn("ru-pattern", `ru-pattern--${variant}`, fade && "ru-pattern--fade", className),
1440
+ style: { "--ru-pattern-size": `${size}px`, ...style },
1441
+ ...rest
1442
+ }
1443
+ );
1444
+ }
1445
+ function SpotlightCard({ color, className, style, onMouseMove, children, ...rest }) {
1446
+ const ref = react.useRef(null);
1447
+ const handleMouseMove = (e) => {
1448
+ const el = ref.current;
1449
+ if (el) {
1450
+ const rect = el.getBoundingClientRect();
1451
+ el.style.setProperty("--ru-spot-x", `${e.clientX - rect.left}px`);
1452
+ el.style.setProperty("--ru-spot-y", `${e.clientY - rect.top}px`);
1453
+ }
1454
+ onMouseMove == null ? void 0 : onMouseMove(e);
1455
+ };
1456
+ return /* @__PURE__ */ jsxRuntime.jsx(
1457
+ "div",
1458
+ {
1459
+ ref,
1460
+ className: cn("ru-card", "ru-spotlight", className),
1461
+ style: color ? { "--ru-spot-color": color, ...style } : style,
1462
+ onMouseMove: handleMouseMove,
1463
+ ...rest,
1464
+ children
1465
+ }
1466
+ );
1467
+ }
1468
+ exports.Accordion = Accordion;
1469
+ exports.Alert = Alert;
1470
+ exports.Avatar = Avatar;
1471
+ exports.AvatarGroup = AvatarGroup;
1472
+ exports.Badge = Badge;
1473
+ exports.Breadcrumbs = Breadcrumbs;
1474
+ exports.Button = Button;
1475
+ exports.Card = Card;
1476
+ exports.CardBody = CardBody;
1477
+ exports.CardFooter = CardFooter;
1478
+ exports.CardHeader = CardHeader;
1479
+ exports.Checkbox = Checkbox;
1480
+ exports.Divider = Divider;
1481
+ exports.Drawer = Drawer;
1482
+ exports.Dropdown = Dropdown;
1483
+ exports.Input = Input;
1484
+ exports.Kbd = Kbd;
1485
+ exports.Modal = Modal;
1486
+ exports.NativeSelect = NativeSelect;
1487
+ exports.Pagination = Pagination;
1488
+ exports.Pattern = Pattern;
1489
+ exports.Progress = Progress;
1490
+ exports.Radio = Radio;
1491
+ exports.RadioGroup = RadioGroup;
1492
+ exports.Select = Select;
1493
+ exports.Skeleton = Skeleton;
1494
+ exports.Spinner = Spinner;
1495
+ exports.SpotlightCard = SpotlightCard;
1496
+ exports.Switch = Switch;
1497
+ exports.Table = Table;
1498
+ exports.Tabs = Tabs;
1499
+ exports.Textarea = Textarea;
1500
+ exports.ToastProvider = ToastProvider;
1501
+ exports.Tooltip = Tooltip;
1502
+ exports.cn = cn;
1503
+ exports.useClickOutside = useClickOutside;
1504
+ exports.useControllableState = useControllableState;
1505
+ exports.useDisclosure = useDisclosure;
1506
+ exports.useToast = useToast;