reusify-ui 2.0.1 → 2.1.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.
@@ -1,40 +1,112 @@
1
1
  "use client";
2
- import { jsxs, jsx, Fragment as Fragment$1 } from "react/jsx-runtime";
3
- import { forwardRef, useState, useEffect, Children, isValidElement, cloneElement, useRef, useCallback, useImperativeHandle, useId, useMemo, Fragment, useContext, createContext } from "react";
2
+ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
+ import { forwardRef, useRef, useEffect, useState, useCallback, useImperativeHandle, useId, useMemo, Children, isValidElement, Fragment as Fragment$1, cloneElement, useContext, createContext } from "react";
4
4
  import { createPortal } from "react-dom";
5
5
  function cn(...classes) {
6
6
  return classes.filter(Boolean).join(" ");
7
7
  }
8
+ const legacyVariants = {
9
+ primary: ["solid", void 0],
10
+ danger: ["solid", "danger"],
11
+ success: ["solid", "success"]
12
+ };
13
+ const semanticColors$2 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
14
+ const radii = { none: "0", sm: "6px", md: "10px", lg: "14px", xl: "18px", "2xl": "24px", full: "9999px" };
15
+ function resolveRadius(radius) {
16
+ if (radius === void 0) return void 0;
17
+ if (typeof radius === "number") return `${radius}px`;
18
+ return radii[radius] ?? radius;
19
+ }
8
20
  const Button = forwardRef(function Button2({
9
- variant = "primary",
21
+ variant = "solid",
22
+ color,
23
+ textColor,
24
+ radius,
25
+ justify = "center",
10
26
  size = "md",
11
27
  loading = false,
12
28
  fullWidth = false,
29
+ pill = false,
30
+ iconOnly = false,
13
31
  leftIcon,
14
32
  rightIcon,
15
33
  disabled,
34
+ href,
16
35
  type = "button",
17
36
  className,
37
+ style,
38
+ onPointerMove,
18
39
  children,
19
40
  ...rest
20
41
  }, ref) {
21
- return /* @__PURE__ */ jsxs(
42
+ const [kind, legacyColor] = legacyVariants[variant] ?? [variant, void 0];
43
+ const handlePointerMove = kind === "pixel" ? (e) => {
44
+ const el = e.currentTarget;
45
+ const rect = el.getBoundingClientRect();
46
+ el.style.setProperty("--ru-px", `${e.clientX - rect.left}px`);
47
+ el.style.setProperty("--ru-py", `${e.clientY - rect.top}px`);
48
+ onPointerMove == null ? void 0 : onPointerMove(e);
49
+ } : onPointerMove;
50
+ const resolvedColor = color ?? legacyColor ?? "primary";
51
+ const customColor = !semanticColors$2.has(resolvedColor);
52
+ const classes = cn(
53
+ "ru-btn",
54
+ `ru-btn--${kind}`,
55
+ `ru-btn--${size}`,
56
+ !customColor && `ru-c-${resolvedColor}`,
57
+ fullWidth && "ru-btn--full",
58
+ pill && "ru-btn--pill",
59
+ iconOnly && "ru-btn--icon",
60
+ justify !== "center" && `ru-btn--justify-${justify}`,
61
+ className
62
+ );
63
+ const inlineStyle = {
64
+ ...customColor && { "--_c": resolvedColor },
65
+ ...textColor && { "--_fg": textColor },
66
+ ...radius !== void 0 && { borderRadius: resolveRadius(radius) },
67
+ ...style
68
+ };
69
+ const styleProp = Object.keys(inlineStyle).length ? inlineStyle : void 0;
70
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
71
+ loading ? /* @__PURE__ */ jsx("span", { className: "ru-spinner", "aria-hidden": "true" }) : leftIcon,
72
+ children,
73
+ !loading && rightIcon && /* @__PURE__ */ jsx("span", { className: "ru-btn__end", children: rightIcon })
74
+ ] });
75
+ if (href !== void 0) {
76
+ const inactive = disabled || loading;
77
+ return /* @__PURE__ */ jsx(
78
+ "a",
79
+ {
80
+ ref,
81
+ href: inactive ? void 0 : href,
82
+ "aria-disabled": inactive || void 0,
83
+ "aria-busy": loading || void 0,
84
+ className: classes,
85
+ style: styleProp,
86
+ onPointerMove: handlePointerMove,
87
+ ...rest,
88
+ children: content
89
+ }
90
+ );
91
+ }
92
+ return /* @__PURE__ */ jsx(
22
93
  "button",
23
94
  {
24
95
  ref,
25
96
  type,
26
97
  disabled: disabled || loading,
27
98
  "aria-busy": loading || void 0,
28
- className: cn("ru-btn", `ru-btn--${variant}`, `ru-btn--${size}`, fullWidth && "ru-btn--full", className),
99
+ className: classes,
100
+ style: styleProp,
101
+ onPointerMove: handlePointerMove,
29
102
  ...rest,
30
- children: [
31
- loading ? /* @__PURE__ */ jsx("span", { className: "ru-spinner", "aria-hidden": "true" }) : leftIcon,
32
- children,
33
- !loading && rightIcon
34
- ]
103
+ children: content
35
104
  }
36
105
  );
37
106
  });
107
+ function ButtonGroup({ attached = false, className, ...rest }) {
108
+ return /* @__PURE__ */ jsx("div", { role: "group", className: cn("ru-btn-group", attached && "ru-btn-group--attached", className), ...rest });
109
+ }
38
110
  const base = {
39
111
  xmlns: "http://www.w3.org/2000/svg",
40
112
  viewBox: "0 0 24 24",
@@ -153,132 +225,6 @@ function Alert({ variant = "info", title, icon, onClose, className, children, ..
153
225
  }
154
226
  );
155
227
  }
156
- function Badge({ variant = "soft", color = "primary", size = "md", dot = false, className, children, ...rest }) {
157
- return /* @__PURE__ */ jsxs("span", { className: cn("ru-badge", `ru-badge--${variant}`, `ru-badge--${size}`, `ru-c-${color}`, className), ...rest, children: [
158
- dot && /* @__PURE__ */ jsx("span", { className: "ru-badge__dot", "aria-hidden": "true" }),
159
- children
160
- ] });
161
- }
162
- function getInitials(name) {
163
- const parts = name.trim().split(/\s+/).filter(Boolean);
164
- if (parts.length === 0) return "";
165
- const first = parts[0][0];
166
- const last = parts.length > 1 ? parts[parts.length - 1][0] : "";
167
- return (first + last).toUpperCase();
168
- }
169
- function Avatar({ src, alt, name, size = "md", shape = "circle", className, ...rest }) {
170
- const [failed, setFailed] = useState(false);
171
- useEffect(() => setFailed(false), [src]);
172
- const showImage = src && !failed;
173
- const initials = name ? getInitials(name) : "";
174
- return /* @__PURE__ */ jsx(
175
- "span",
176
- {
177
- className: cn("ru-avatar", `ru-avatar--${size}`, shape === "square" && "ru-avatar--square", className),
178
- role: showImage ? void 0 : "img",
179
- "aria-label": showImage ? void 0 : alt ?? name ?? "Avatar",
180
- ...rest,
181
- children: showImage ? /* @__PURE__ */ jsx("img", { src, alt: alt ?? name ?? "", onError: () => setFailed(true) }) : initials ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: initials }) : /* @__PURE__ */ jsx(UserIcon, { className: "ru-avatar__icon" })
182
- }
183
- );
184
- }
185
- function AvatarGroup({ max, size = "md", className, children, ...rest }) {
186
- const avatars = Children.toArray(children).filter(isValidElement);
187
- const visible = max ? avatars.slice(0, max) : avatars;
188
- const extra = avatars.length - visible.length;
189
- return /* @__PURE__ */ jsxs("div", { className: cn("ru-avatar-group", className), ...rest, children: [
190
- visible.map((child) => cloneElement(child, { size: child.props.size ?? size })),
191
- extra > 0 && /* @__PURE__ */ jsxs("span", { className: cn("ru-avatar", `ru-avatar--${size}`), "aria-label": `${extra} more`, children: [
192
- "+",
193
- extra
194
- ] })
195
- ] });
196
- }
197
- function Card({ variant = "outline", hoverable = false, className, ...rest }) {
198
- return /* @__PURE__ */ jsx(
199
- "div",
200
- {
201
- className: cn(
202
- "ru-card",
203
- variant === "elevated" && "ru-card--elevated",
204
- hoverable && "ru-card--hoverable",
205
- className
206
- ),
207
- ...rest
208
- }
209
- );
210
- }
211
- function CardHeader({ title, subtitle, action, className, children, ...rest }) {
212
- return /* @__PURE__ */ jsxs("div", { className: cn("ru-card__header", className), ...rest, children: [
213
- /* @__PURE__ */ jsxs("div", { children: [
214
- title && /* @__PURE__ */ jsx("h3", { className: "ru-card__title", children: title }),
215
- subtitle && /* @__PURE__ */ jsx("p", { className: "ru-card__subtitle", children: subtitle }),
216
- children
217
- ] }),
218
- action
219
- ] });
220
- }
221
- function CardBody({ className, ...rest }) {
222
- return /* @__PURE__ */ jsx("div", { className: cn("ru-card__body", className), ...rest });
223
- }
224
- function CardFooter({ className, ...rest }) {
225
- return /* @__PURE__ */ jsx("div", { className: cn("ru-card__footer", className), ...rest });
226
- }
227
- function Divider({ orientation = "horizontal", label, className, ...rest }) {
228
- if (label && orientation === "horizontal") {
229
- return /* @__PURE__ */ jsx("div", { role: "separator", className: cn("ru-divider", "ru-divider--label", className), ...rest, children: label });
230
- }
231
- return /* @__PURE__ */ jsx(
232
- "hr",
233
- {
234
- "aria-orientation": orientation,
235
- className: cn("ru-divider", orientation === "vertical" && "ru-divider--vertical", className),
236
- ...rest
237
- }
238
- );
239
- }
240
- function Table({
241
- columns,
242
- data,
243
- rowKey,
244
- striped = false,
245
- hoverable = false,
246
- size = "md",
247
- caption,
248
- emptyText = "No data",
249
- onRowClick,
250
- className
251
- }) {
252
- const getKey = (row, index) => {
253
- if (typeof rowKey === "function") return rowKey(row, index);
254
- if (rowKey !== void 0) return String(row[rowKey]);
255
- return index;
256
- };
257
- return /* @__PURE__ */ jsx("div", { className: cn("ru-table-wrap", className), children: /* @__PURE__ */ jsxs(
258
- "table",
259
- {
260
- className: cn(
261
- "ru-table",
262
- `ru-table--${size}`,
263
- striped && "ru-table--striped",
264
- (hoverable || onRowClick) && "ru-table--hover"
265
- ),
266
- children: [
267
- caption && /* @__PURE__ */ jsx("caption", { children: caption }),
268
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsx("th", { scope: "col", style: { textAlign: col.align, width: col.width }, children: col.header }, col.key)) }) }),
269
- /* @__PURE__ */ jsx("tbody", { children: data.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { className: "ru-table__empty", colSpan: columns.length, children: emptyText }) }) : data.map((row, i) => /* @__PURE__ */ jsx(
270
- "tr",
271
- {
272
- onClick: onRowClick ? () => onRowClick(row, i) : void 0,
273
- style: onRowClick ? { cursor: "pointer" } : void 0,
274
- children: columns.map((col) => /* @__PURE__ */ jsx("td", { style: { textAlign: col.align }, children: col.render ? col.render(row, i) : row[col.key] }, col.key))
275
- },
276
- getKey(row, i)
277
- )) })
278
- ]
279
- }
280
- ) });
281
- }
282
228
  function useControllableState(value, defaultValue, onChange) {
283
229
  const [internal, setInternal] = useState(defaultValue);
284
230
  const isControlled = value !== void 0;
@@ -535,7 +481,7 @@ function childrenToOptions(children) {
535
481
  result.push({ label, value: value ?? String(label), disabled });
536
482
  } else if (child.type === "optgroup") {
537
483
  result.push({ label: child.props.label, options: childrenToOptions(child.props.children) });
538
- } else if (child.type === Fragment) {
484
+ } else if (child.type === Fragment$1) {
539
485
  result.push(...childrenToOptions(child.props.children));
540
486
  }
541
487
  });
@@ -892,7 +838,7 @@ function RadioGroup({
892
838
  )) })
893
839
  ] });
894
840
  }
895
- const Switch = forwardRef(function Switch2({ checked, defaultChecked = false, onChange, label, size = "md", color = "primary", disabled, name, id, className }, ref) {
841
+ const Switch = forwardRef(function Switch2({ checked, defaultChecked = false, onChange, label, size = "md", color = "primary", disabled, name, id, className, ...rest }, ref) {
896
842
  const [on, setOn] = useControllableState(checked, defaultChecked, onChange);
897
843
  return /* @__PURE__ */ jsxs(
898
844
  "label",
@@ -910,6 +856,7 @@ const Switch = forwardRef(function Switch2({ checked, defaultChecked = false, on
910
856
  disabled,
911
857
  className: "ru-switch__track",
912
858
  onClick: () => setOn(!on),
859
+ ...rest,
913
860
  children: /* @__PURE__ */ jsx("span", { className: "ru-switch__thumb" })
914
861
  }
915
862
  ),
@@ -919,330 +866,78 @@ const Switch = forwardRef(function Switch2({ checked, defaultChecked = false, on
919
866
  }
920
867
  );
921
868
  });
922
- function DialogContent({ titleId, descId, title, description, showCloseButton, onClose, children, footer }) {
923
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
924
- (title || description || showCloseButton) && /* @__PURE__ */ jsxs("div", { className: "ru-modal__header", children: [
925
- /* @__PURE__ */ jsxs("div", { children: [
926
- title && /* @__PURE__ */ jsx("h2", { id: titleId, className: "ru-modal__title", children: title }),
927
- description && /* @__PURE__ */ jsx("p", { id: descId, className: "ru-modal__desc", children: description })
928
- ] }),
929
- showCloseButton && /* @__PURE__ */ jsx(CloseButton, { onClick: onClose })
930
- ] }),
931
- /* @__PURE__ */ jsx("div", { className: "ru-modal__body", children }),
932
- footer && /* @__PURE__ */ jsx("div", { className: "ru-modal__footer", children: footer })
933
- ] });
934
- }
935
- function Modal({
936
- open,
937
- onClose,
938
- size = "md",
939
- closeOnOverlayClick = true,
940
- closeOnEsc = true,
941
- showCloseButton = true,
942
- className,
943
- ...content
944
- }) {
945
- const panelRef = useRef(null);
946
- const mounted = useMounted();
947
- const titleId = useId();
948
- const descId = useId();
949
- useOverlay(open && mounted, panelRef, onClose, closeOnEsc);
950
- if (!open || !mounted) return null;
951
- const onOverlayMouseDown = (e) => {
952
- if (closeOnOverlayClick && e.target === e.currentTarget) onClose();
869
+ function Tabs({ items, value, defaultValue, onChange, variant = "line", fullWidth = false, className }) {
870
+ var _a;
871
+ const baseId = useId();
872
+ const firstEnabled = ((_a = items.find((item) => !item.disabled)) == null ? void 0 : _a.value) ?? "";
873
+ const [active, setActive] = useControllableState(value, defaultValue ?? firstEnabled, onChange);
874
+ const tabRefs = useRef([]);
875
+ const onKeyDown = (e, index) => {
876
+ var _a2;
877
+ const enabled = items.flatMap((item, i) => item.disabled ? [] : [i]);
878
+ const pos = enabled.indexOf(index);
879
+ let next;
880
+ switch (e.key) {
881
+ case "ArrowRight":
882
+ next = enabled[(pos + 1) % enabled.length];
883
+ break;
884
+ case "ArrowLeft":
885
+ next = enabled[(pos - 1 + enabled.length) % enabled.length];
886
+ break;
887
+ case "Home":
888
+ next = enabled[0];
889
+ break;
890
+ case "End":
891
+ next = enabled[enabled.length - 1];
892
+ break;
893
+ default:
894
+ return;
895
+ }
896
+ e.preventDefault();
897
+ (_a2 = tabRefs.current[next]) == null ? void 0 : _a2.focus();
898
+ setActive(items[next].value);
953
899
  };
954
- return createPortal(
955
- /* @__PURE__ */ jsx("div", { className: "ru-overlay", onMouseDown: onOverlayMouseDown, children: /* @__PURE__ */ jsx(
900
+ const activeIndex = items.findIndex((item) => item.value === active);
901
+ const activeItem = items[activeIndex];
902
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-tabs", `ru-tabs--${variant}`, fullWidth && "ru-tabs--full", className), children: [
903
+ /* @__PURE__ */ jsx("div", { role: "tablist", className: "ru-tabs__list", children: items.map((item, i) => {
904
+ const selected = i === activeIndex;
905
+ return /* @__PURE__ */ jsxs(
906
+ "button",
907
+ {
908
+ ref: (el) => {
909
+ tabRefs.current[i] = el;
910
+ },
911
+ type: "button",
912
+ role: "tab",
913
+ id: `${baseId}-tab-${i}`,
914
+ "aria-selected": selected,
915
+ "aria-controls": `${baseId}-panel-${i}`,
916
+ tabIndex: selected ? 0 : -1,
917
+ disabled: item.disabled,
918
+ className: "ru-tabs__tab",
919
+ onClick: () => setActive(item.value),
920
+ onKeyDown: (e) => onKeyDown(e, i),
921
+ children: [
922
+ item.icon,
923
+ item.label
924
+ ]
925
+ },
926
+ item.value
927
+ );
928
+ }) }),
929
+ activeItem && activeItem.content !== void 0 && /* @__PURE__ */ jsx(
956
930
  "div",
957
931
  {
958
- ref: panelRef,
959
- role: "dialog",
960
- "aria-modal": "true",
961
- "aria-labelledby": content.title ? titleId : void 0,
962
- "aria-describedby": content.description ? descId : void 0,
963
- tabIndex: -1,
964
- className: cn("ru-modal", `ru-modal--${size}`, className),
965
- children: /* @__PURE__ */ jsx(
966
- DialogContent,
967
- {
968
- ...content,
969
- titleId,
970
- descId,
971
- showCloseButton,
972
- onClose
973
- }
974
- )
932
+ role: "tabpanel",
933
+ id: `${baseId}-panel-${activeIndex}`,
934
+ "aria-labelledby": `${baseId}-tab-${activeIndex}`,
935
+ tabIndex: 0,
936
+ className: "ru-tabs__panel",
937
+ children: activeItem.content
975
938
  }
976
- ) }),
977
- document.body
978
- );
979
- }
980
- const drawerSizes = { sm: "20rem", md: "24rem", lg: "32rem" };
981
- function Drawer({
982
- open,
983
- onClose,
984
- placement = "right",
985
- size = "md",
986
- closeOnOverlayClick = true,
987
- closeOnEsc = true,
988
- showCloseButton = true,
989
- className,
990
- ...content
991
- }) {
992
- const panelRef = useRef(null);
993
- const mounted = useMounted();
994
- const titleId = useId();
995
- const descId = useId();
996
- useOverlay(open && mounted, panelRef, onClose, closeOnEsc);
997
- if (!open || !mounted) return null;
998
- const resolvedSize = typeof size === "number" ? `${size}px` : drawerSizes[size] ?? size;
999
- return createPortal(
1000
- /* @__PURE__ */ jsx(
1001
- "div",
1002
- {
1003
- className: "ru-overlay ru-overlay--drawer",
1004
- onMouseDown: (e) => {
1005
- if (closeOnOverlayClick && e.target === e.currentTarget) onClose();
1006
- },
1007
- children: /* @__PURE__ */ jsx(
1008
- "div",
1009
- {
1010
- ref: panelRef,
1011
- role: "dialog",
1012
- "aria-modal": "true",
1013
- "aria-labelledby": content.title ? titleId : void 0,
1014
- "aria-describedby": content.description ? descId : void 0,
1015
- tabIndex: -1,
1016
- className: cn("ru-drawer", `ru-drawer--${placement}`, className),
1017
- style: { "--ru-drawer-size": resolvedSize },
1018
- children: /* @__PURE__ */ jsx(
1019
- DialogContent,
1020
- {
1021
- ...content,
1022
- titleId,
1023
- descId,
1024
- showCloseButton,
1025
- onClose
1026
- }
1027
- )
1028
- }
1029
- )
1030
- }
1031
- ),
1032
- document.body
1033
- );
1034
- }
1035
- function Tooltip({ content, children, placement = "top", delay = 100, disabled, className }) {
1036
- const [open, setOpen] = useState(false);
1037
- const timer = useRef(void 0);
1038
- const id = useId();
1039
- const visible = open && !disabled && content != null && content !== "";
1040
- useEffect(() => () => clearTimeout(timer.current), []);
1041
- const show = () => {
1042
- clearTimeout(timer.current);
1043
- timer.current = setTimeout(() => setOpen(true), delay);
1044
- };
1045
- const hide = () => {
1046
- clearTimeout(timer.current);
1047
- setOpen(false);
1048
- };
1049
- const trigger = isValidElement(children) ? cloneElement(children, {
1050
- "aria-describedby": visible ? id : void 0
1051
- }) : children;
1052
- return /* @__PURE__ */ jsxs(
1053
- "span",
1054
- {
1055
- className: cn("ru-tooltip", className),
1056
- onMouseEnter: show,
1057
- onMouseLeave: hide,
1058
- onFocus: show,
1059
- onBlur: hide,
1060
- onKeyDown: (e) => e.key === "Escape" && hide(),
1061
- children: [
1062
- trigger,
1063
- visible && /* @__PURE__ */ jsx("span", { role: "tooltip", id, className: cn("ru-tooltip__bubble", `ru-tooltip__bubble--${placement}`), children: content })
1064
- ]
1065
- }
1066
- );
1067
- }
1068
- function Dropdown({ trigger, items, align = "start", closeOnSelect = true, className }) {
1069
- const [open, setOpen] = useState(false);
1070
- const rootRef = useRef(null);
1071
- const menuRef = useRef(null);
1072
- const menuId = useId();
1073
- useClickOutside(rootRef, () => setOpen(false), open);
1074
- const getItems = () => {
1075
- var _a;
1076
- return Array.from(((_a = menuRef.current) == null ? void 0 : _a.querySelectorAll('[role="menuitem"]:not(:disabled)')) ?? []);
1077
- };
1078
- const focusItem = (index) => {
1079
- const els = getItems();
1080
- if (els.length) els[(index + els.length) % els.length].focus();
1081
- };
1082
- const closeAndFocusTrigger = () => {
1083
- var _a, _b;
1084
- setOpen(false);
1085
- (_b = (_a = rootRef.current) == null ? void 0 : _a.querySelector("[aria-haspopup]")) == null ? void 0 : _b.focus();
1086
- };
1087
- useEffect(() => {
1088
- if (open) focusItem(0);
1089
- }, [open]);
1090
- const onMenuKeyDown = (e) => {
1091
- const els = getItems();
1092
- const index = els.indexOf(document.activeElement);
1093
- switch (e.key) {
1094
- case "ArrowDown":
1095
- e.preventDefault();
1096
- focusItem(index + 1);
1097
- break;
1098
- case "ArrowUp":
1099
- e.preventDefault();
1100
- focusItem(index - 1);
1101
- break;
1102
- case "Home":
1103
- e.preventDefault();
1104
- focusItem(0);
1105
- break;
1106
- case "End":
1107
- e.preventDefault();
1108
- focusItem(-1);
1109
- break;
1110
- case "Escape":
1111
- e.preventDefault();
1112
- closeAndFocusTrigger();
1113
- break;
1114
- case "Tab":
1115
- setOpen(false);
1116
- break;
1117
- }
1118
- };
1119
- const triggerProps = trigger.props;
1120
- const triggerEl = cloneElement(trigger, {
1121
- "aria-haspopup": "menu",
1122
- "aria-expanded": open,
1123
- "aria-controls": open ? menuId : void 0,
1124
- onClick: (e) => {
1125
- var _a;
1126
- (_a = triggerProps.onClick) == null ? void 0 : _a.call(triggerProps, e);
1127
- setOpen((v) => !v);
1128
- },
1129
- onKeyDown: (e) => {
1130
- var _a;
1131
- (_a = triggerProps.onKeyDown) == null ? void 0 : _a.call(triggerProps, e);
1132
- if (e.key === "ArrowDown" && !open) {
1133
- e.preventDefault();
1134
- setOpen(true);
1135
- }
1136
- }
1137
- });
1138
- return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("ru-dropdown", className), children: [
1139
- triggerEl,
1140
- open && /* @__PURE__ */ jsx(
1141
- "div",
1142
- {
1143
- ref: menuRef,
1144
- id: menuId,
1145
- role: "menu",
1146
- className: cn("ru-dropdown__menu", align === "end" && "ru-dropdown__menu--end"),
1147
- onKeyDown: onMenuKeyDown,
1148
- children: items.map(
1149
- (item, i) => item.divider ? /* @__PURE__ */ jsx("div", { role: "separator", className: "ru-dropdown__divider" }, item.key ?? i) : /* @__PURE__ */ jsxs(
1150
- "button",
1151
- {
1152
- type: "button",
1153
- role: "menuitem",
1154
- tabIndex: -1,
1155
- disabled: item.disabled,
1156
- className: cn("ru-dropdown__item", item.danger && "ru-dropdown__item--danger"),
1157
- onClick: () => {
1158
- var _a;
1159
- (_a = item.onClick) == null ? void 0 : _a.call(item);
1160
- if (closeOnSelect) closeAndFocusTrigger();
1161
- },
1162
- children: [
1163
- item.icon && /* @__PURE__ */ jsx("span", { className: "ru-dropdown__icon", children: item.icon }),
1164
- item.label
1165
- ]
1166
- },
1167
- item.key ?? i
1168
- )
1169
- )
1170
- }
1171
- )
1172
- ] });
1173
- }
1174
- function Tabs({ items, value, defaultValue, onChange, variant = "line", fullWidth = false, className }) {
1175
- var _a;
1176
- const baseId = useId();
1177
- const firstEnabled = ((_a = items.find((item) => !item.disabled)) == null ? void 0 : _a.value) ?? "";
1178
- const [active, setActive] = useControllableState(value, defaultValue ?? firstEnabled, onChange);
1179
- const tabRefs = useRef([]);
1180
- const onKeyDown = (e, index) => {
1181
- var _a2;
1182
- const enabled = items.flatMap((item, i) => item.disabled ? [] : [i]);
1183
- const pos = enabled.indexOf(index);
1184
- let next;
1185
- switch (e.key) {
1186
- case "ArrowRight":
1187
- next = enabled[(pos + 1) % enabled.length];
1188
- break;
1189
- case "ArrowLeft":
1190
- next = enabled[(pos - 1 + enabled.length) % enabled.length];
1191
- break;
1192
- case "Home":
1193
- next = enabled[0];
1194
- break;
1195
- case "End":
1196
- next = enabled[enabled.length - 1];
1197
- break;
1198
- default:
1199
- return;
1200
- }
1201
- e.preventDefault();
1202
- (_a2 = tabRefs.current[next]) == null ? void 0 : _a2.focus();
1203
- setActive(items[next].value);
1204
- };
1205
- const activeIndex = items.findIndex((item) => item.value === active);
1206
- const activeItem = items[activeIndex];
1207
- return /* @__PURE__ */ jsxs("div", { className: cn("ru-tabs", `ru-tabs--${variant}`, fullWidth && "ru-tabs--full", className), children: [
1208
- /* @__PURE__ */ jsx("div", { role: "tablist", className: "ru-tabs__list", children: items.map((item, i) => {
1209
- const selected = i === activeIndex;
1210
- return /* @__PURE__ */ jsxs(
1211
- "button",
1212
- {
1213
- ref: (el) => {
1214
- tabRefs.current[i] = el;
1215
- },
1216
- type: "button",
1217
- role: "tab",
1218
- id: `${baseId}-tab-${i}`,
1219
- "aria-selected": selected,
1220
- "aria-controls": `${baseId}-panel-${i}`,
1221
- tabIndex: selected ? 0 : -1,
1222
- disabled: item.disabled,
1223
- className: "ru-tabs__tab",
1224
- onClick: () => setActive(item.value),
1225
- onKeyDown: (e) => onKeyDown(e, i),
1226
- children: [
1227
- item.icon,
1228
- item.label
1229
- ]
1230
- },
1231
- item.value
1232
- );
1233
- }) }),
1234
- activeItem && activeItem.content !== void 0 && /* @__PURE__ */ jsx(
1235
- "div",
1236
- {
1237
- role: "tabpanel",
1238
- id: `${baseId}-panel-${activeIndex}`,
1239
- "aria-labelledby": `${baseId}-tab-${activeIndex}`,
1240
- tabIndex: 0,
1241
- className: "ru-tabs__panel",
1242
- children: activeItem.content
1243
- }
1244
- )
1245
- ] });
939
+ )
940
+ ] });
1246
941
  }
1247
942
  function Accordion({ items, multiple = false, value, defaultValue, onChange, className }) {
1248
943
  const baseId = useId();
@@ -1356,38 +1051,686 @@ function Breadcrumbs({ items, separator, className }) {
1356
1051
  } else if (item.onClick) {
1357
1052
  node = /* @__PURE__ */ jsx("button", { type: "button", className: "ru-breadcrumbs__link", onClick: item.onClick, children: item.label });
1358
1053
  }
1359
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1054
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
1360
1055
  /* @__PURE__ */ jsx("li", { className: "ru-breadcrumbs__item", children: node }),
1361
1056
  !isLast && /* @__PURE__ */ jsx("li", { className: "ru-breadcrumbs__sep", "aria-hidden": "true", children: separator ?? /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
1362
1057
  ] }, i);
1363
1058
  }) }) });
1364
1059
  }
1365
- const ToastContext = createContext(null);
1366
- function ToastProvider({ children, position = "bottom-right", duration = 4e3, max = 5 }) {
1367
- const [toasts, setToasts] = useState([]);
1368
- const mounted = useMounted();
1369
- const counter = useRef(0);
1370
- const dismiss = useCallback((id) => {
1371
- setToasts((list) => id ? list.filter((t) => t.id !== id) : []);
1372
- }, []);
1373
- const api = useMemo(() => {
1374
- const show = (options) => {
1375
- const opts = typeof options === "string" ? { title: options } : options;
1376
- const id = opts.id ?? `ru-toast-${++counter.current}`;
1377
- setToasts((list) => [...list.filter((t) => t.id !== id), { ...opts, id }].slice(-max));
1378
- return id;
1379
- };
1380
- const fn = show;
1381
- fn.success = (title, o) => show({ ...o, title, variant: "success" });
1382
- fn.error = (title, o) => show({ ...o, title, variant: "danger" });
1383
- fn.warning = (title, o) => show({ ...o, title, variant: "warning" });
1384
- fn.info = (title, o) => show({ ...o, title, variant: "info" });
1385
- fn.dismiss = dismiss;
1386
- return fn;
1387
- }, [dismiss, max]);
1388
- return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: api, children: [
1389
- children,
1390
- mounted && createPortal(
1060
+ function Badge({ variant = "soft", color = "primary", size = "md", dot = false, className, children, ...rest }) {
1061
+ return /* @__PURE__ */ jsxs("span", { className: cn("ru-badge", `ru-badge--${variant}`, `ru-badge--${size}`, `ru-c-${color}`, className), ...rest, children: [
1062
+ dot && /* @__PURE__ */ jsx("span", { className: "ru-badge__dot", "aria-hidden": "true" }),
1063
+ children
1064
+ ] });
1065
+ }
1066
+ function getInitials(name) {
1067
+ const parts = name.trim().split(/\s+/).filter(Boolean);
1068
+ if (parts.length === 0) return "";
1069
+ const first = parts[0][0];
1070
+ const last = parts.length > 1 ? parts[parts.length - 1][0] : "";
1071
+ return (first + last).toUpperCase();
1072
+ }
1073
+ const avatarSemanticColors = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
1074
+ const avatarStatusLabels = { online: "Online", away: "Away", busy: "Busy", offline: "Offline" };
1075
+ function hueFromString(text) {
1076
+ let hash = 0;
1077
+ for (const ch of text) hash = (hash * 31 + ch.charCodeAt(0)) % 360;
1078
+ return hash;
1079
+ }
1080
+ function resolveAvatarColor(color, seed) {
1081
+ if (!color) return void 0;
1082
+ if (color === "auto") return `hsl(${hueFromString(seed)} 70% 52%)`;
1083
+ return avatarSemanticColors.has(color) ? `var(--ru-${color})` : color;
1084
+ }
1085
+ function AvatarTooltip({ content, description, accent, children }) {
1086
+ const [open, setOpen] = useState(false);
1087
+ const bubbleRef = useRef(null);
1088
+ const tiltToward = (e) => {
1089
+ const bubble = bubbleRef.current;
1090
+ if (!bubble) return;
1091
+ const rect = e.currentTarget.getBoundingClientRect();
1092
+ const ratio = Math.max(-1, Math.min(1, (e.clientX - (rect.left + rect.width / 2)) / (rect.width / 2)));
1093
+ bubble.style.setProperty("--_shift", `${ratio * 10}px`);
1094
+ bubble.style.setProperty("--_tilt", `${ratio * 12}deg`);
1095
+ };
1096
+ return /* @__PURE__ */ jsxs(
1097
+ "span",
1098
+ {
1099
+ className: "ru-avatar-tip",
1100
+ onMouseEnter: () => setOpen(true),
1101
+ onMouseLeave: () => setOpen(false),
1102
+ onMouseMove: tiltToward,
1103
+ onFocus: () => setOpen(true),
1104
+ onBlur: () => setOpen(false),
1105
+ children: [
1106
+ children,
1107
+ open && // The avatar's accessible name already includes this text, so the bubble is visual only.
1108
+ /* @__PURE__ */ jsx("span", { ref: bubbleRef, className: "ru-avatar-tip__bubble", "aria-hidden": "true", style: accent ? { "--_accent": accent } : void 0, children: /* @__PURE__ */ jsxs("span", { className: "ru-avatar-tip__inner", children: [
1109
+ /* @__PURE__ */ jsx("span", { className: "ru-avatar-tip__name", children: content }),
1110
+ description && /* @__PURE__ */ jsx("span", { className: "ru-avatar-tip__desc", children: description })
1111
+ ] }) })
1112
+ ]
1113
+ }
1114
+ );
1115
+ }
1116
+ function Avatar({
1117
+ src,
1118
+ alt,
1119
+ name,
1120
+ size = "md",
1121
+ shape = "circle",
1122
+ color,
1123
+ ring,
1124
+ status,
1125
+ statusPulse = false,
1126
+ badge,
1127
+ tooltip,
1128
+ description,
1129
+ tooltipAccent,
1130
+ className,
1131
+ style,
1132
+ ...rest
1133
+ }) {
1134
+ const [failed, setFailed] = useState(false);
1135
+ useEffect(() => setFailed(false), [src]);
1136
+ const showImage = src && !failed;
1137
+ const initials = name ? getInitials(name) : "";
1138
+ const numericSize = typeof size === "number";
1139
+ const tint = resolveAvatarColor(color, name ?? alt ?? "");
1140
+ const ringColor = typeof ring === "string" ? resolveAvatarColor(ring, name ?? "") : void 0;
1141
+ const vars = {
1142
+ ...numericSize && { width: size, height: size, fontSize: Math.round(size * 0.38) },
1143
+ ...tint && { "--_avc": tint },
1144
+ ...ringColor && { "--_ring": ringColor },
1145
+ ...style
1146
+ };
1147
+ const baseLabel = alt ?? name ?? "Avatar";
1148
+ const label = [baseLabel, description && `, ${description}`, status && ` (${avatarStatusLabels[status] ?? status})`, badge !== void 0 && badge !== null && `, ${badge} new`].filter(Boolean).join("");
1149
+ const avatar = /* @__PURE__ */ jsxs(
1150
+ "span",
1151
+ {
1152
+ className: cn(
1153
+ "ru-avatar",
1154
+ !numericSize && `ru-avatar--${size}`,
1155
+ shape !== "circle" && `ru-avatar--${shape}`,
1156
+ tint && "ru-avatar--tinted",
1157
+ ring && "ru-avatar--ring",
1158
+ className
1159
+ ),
1160
+ role: showImage ? void 0 : "img",
1161
+ "aria-label": showImage ? void 0 : label,
1162
+ style: Object.keys(vars).length ? vars : void 0,
1163
+ ...rest,
1164
+ children: [
1165
+ showImage ? /* @__PURE__ */ jsx("img", { src, alt: label === "Avatar" ? "" : label, onError: () => setFailed(true) }) : initials ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: initials }) : /* @__PURE__ */ jsx(UserIcon, { className: "ru-avatar__icon" }),
1166
+ badge !== void 0 && badge !== null && /* @__PURE__ */ jsx("span", { className: "ru-avatar__badge", "aria-hidden": "true", children: badge }),
1167
+ status && /* @__PURE__ */ jsx(
1168
+ "span",
1169
+ {
1170
+ className: cn("ru-avatar__status", `ru-avatar__status--${status}`, statusPulse && "ru-avatar__status--pulse"),
1171
+ "aria-hidden": "true"
1172
+ }
1173
+ )
1174
+ ]
1175
+ }
1176
+ );
1177
+ if (!tooltip) return avatar;
1178
+ return /* @__PURE__ */ jsx(AvatarTooltip, { content: tooltip === true ? baseLabel : tooltip, description, accent: tooltipAccent, children: avatar });
1179
+ }
1180
+ const avatarOverlap = { tight: "-0.75rem", normal: "-0.5rem", loose: "-0.25rem", none: "0.25rem" };
1181
+ function AvatarGroup({ max, size = "md", spacing, tooltips = false, className, style, children, ...rest }) {
1182
+ const avatars = Children.toArray(children).filter(isValidElement);
1183
+ const visible = max ? avatars.slice(0, max) : avatars;
1184
+ const extra = avatars.length - visible.length;
1185
+ const overlap = spacing === void 0 ? void 0 : avatarOverlap[spacing] ?? `-${typeof spacing === "number" ? `${spacing}px` : spacing}`;
1186
+ const vars = overlap ? { "--_overlap": overlap, ...style } : style;
1187
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-avatar-group", className), style: vars, ...rest, children: [
1188
+ visible.map(
1189
+ (child) => cloneElement(child, {
1190
+ size: child.props.size ?? size,
1191
+ ...tooltips && child.props.tooltip === void 0 && { tooltip: true }
1192
+ })
1193
+ ),
1194
+ extra > 0 && /* @__PURE__ */ jsxs(
1195
+ "span",
1196
+ {
1197
+ className: cn("ru-avatar", typeof size === "number" ? void 0 : `ru-avatar--${size}`),
1198
+ style: typeof size === "number" ? { width: size, height: size, fontSize: Math.round(size * 0.34) } : void 0,
1199
+ role: "img",
1200
+ "aria-label": `${extra} more`,
1201
+ children: [
1202
+ "+",
1203
+ extra
1204
+ ]
1205
+ }
1206
+ )
1207
+ ] });
1208
+ }
1209
+ function Card({ variant = "outline", hoverable = false, className, ...rest }) {
1210
+ return /* @__PURE__ */ jsx(
1211
+ "div",
1212
+ {
1213
+ className: cn(
1214
+ "ru-card",
1215
+ variant === "elevated" && "ru-card--elevated",
1216
+ hoverable && "ru-card--hoverable",
1217
+ className
1218
+ ),
1219
+ ...rest
1220
+ }
1221
+ );
1222
+ }
1223
+ function CardHeader({ title, subtitle, action, className, children, ...rest }) {
1224
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-card__header", className), ...rest, children: [
1225
+ /* @__PURE__ */ jsxs("div", { children: [
1226
+ title && /* @__PURE__ */ jsx("h3", { className: "ru-card__title", children: title }),
1227
+ subtitle && /* @__PURE__ */ jsx("p", { className: "ru-card__subtitle", children: subtitle }),
1228
+ children
1229
+ ] }),
1230
+ action
1231
+ ] });
1232
+ }
1233
+ function CardBody({ className, ...rest }) {
1234
+ return /* @__PURE__ */ jsx("div", { className: cn("ru-card__body", className), ...rest });
1235
+ }
1236
+ function CardFooter({ className, ...rest }) {
1237
+ return /* @__PURE__ */ jsx("div", { className: cn("ru-card__footer", className), ...rest });
1238
+ }
1239
+ const dividerSemanticColors = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
1240
+ const dividerThickness = { sm: "1px", md: "2px", lg: "4px" };
1241
+ const dividerSpacing = { none: "0", sm: "0.5rem", md: "1rem", lg: "2rem", xl: "3rem" };
1242
+ const toLength = (v) => typeof v === "number" ? `${v}px` : v;
1243
+ function Divider({
1244
+ orientation = "horizontal",
1245
+ variant = "solid",
1246
+ size,
1247
+ color,
1248
+ length,
1249
+ align = "center",
1250
+ spacing,
1251
+ label,
1252
+ labelPosition = "center",
1253
+ className,
1254
+ style,
1255
+ ...rest
1256
+ }) {
1257
+ const vertical = orientation === "vertical";
1258
+ const labelled = Boolean(label) && !vertical;
1259
+ const vars = {
1260
+ ...size !== void 0 && { "--_t": dividerThickness[size] ?? toLength(size) },
1261
+ ...color && { "--_line": dividerSemanticColors.has(color) ? `var(--ru-${color})` : color },
1262
+ ...length !== void 0 && { "--_len": toLength(length) },
1263
+ ...spacing !== void 0 && { "--_gap": dividerSpacing[spacing] ?? toLength(spacing) },
1264
+ ...style
1265
+ };
1266
+ const styleProp = Object.keys(vars).length ? vars : void 0;
1267
+ const classes = cn(
1268
+ "ru-divider",
1269
+ variant !== "solid" && `ru-divider--${variant}`,
1270
+ vertical && "ru-divider--vertical",
1271
+ labelled && "ru-divider--label",
1272
+ labelled && labelPosition !== "center" && `ru-divider--label-${labelPosition}`,
1273
+ !vertical && align !== "center" && `ru-divider--align-${align}`,
1274
+ className
1275
+ );
1276
+ if (labelled) {
1277
+ return /* @__PURE__ */ jsx("div", { role: "separator", "aria-orientation": "horizontal", className: classes, style: styleProp, ...rest, children: /* @__PURE__ */ jsx("span", { className: "ru-divider__label", children: label }) });
1278
+ }
1279
+ return /* @__PURE__ */ jsx("hr", { "aria-orientation": orientation, className: classes, style: styleProp, ...rest });
1280
+ }
1281
+ function usePagination(pagination, dataLength) {
1282
+ const opts = pagination === true ? {} : pagination || {};
1283
+ const enabled = Boolean(pagination);
1284
+ const [pageSize, setPageSize] = useControllableState(
1285
+ opts.onPageSizeChange ? opts.pageSize : void 0,
1286
+ opts.pageSize ?? opts.defaultPageSize ?? 10,
1287
+ opts.onPageSizeChange
1288
+ );
1289
+ const [page, setPage] = useControllableState(opts.page, opts.defaultPage ?? 1, opts.onPageChange);
1290
+ const serverSide = opts.totalRows !== void 0;
1291
+ const totalRows = serverSide ? opts.totalRows : dataLength;
1292
+ const totalPages = Math.max(1, Math.ceil(totalRows / pageSize));
1293
+ const current = Math.min(Math.max(1, page), totalPages);
1294
+ const start = enabled ? (current - 1) * pageSize : 0;
1295
+ return {
1296
+ enabled,
1297
+ opts,
1298
+ serverSide,
1299
+ page: current,
1300
+ pageSize,
1301
+ totalRows,
1302
+ totalPages,
1303
+ start,
1304
+ setPage,
1305
+ changePageSize: (size) => {
1306
+ setPageSize(size);
1307
+ setPage(1);
1308
+ }
1309
+ };
1310
+ }
1311
+ function Table({
1312
+ columns,
1313
+ data,
1314
+ rowKey,
1315
+ striped = false,
1316
+ hoverable = false,
1317
+ size = "md",
1318
+ caption,
1319
+ emptyText = "No data",
1320
+ onRowClick,
1321
+ pagination,
1322
+ loading = false,
1323
+ className
1324
+ }) {
1325
+ const pager = usePagination(pagination, data.length);
1326
+ const rows = pager.enabled && !pager.serverSide ? data.slice(pager.start, pager.start + pager.pageSize) : data;
1327
+ const getKey = (row, index) => {
1328
+ if (typeof rowKey === "function") return rowKey(row, index);
1329
+ if (rowKey !== void 0) return String(row[rowKey]);
1330
+ return index;
1331
+ };
1332
+ const firstRow = pager.totalRows === 0 ? 0 : pager.start + 1;
1333
+ const lastRow = Math.min(pager.start + (pager.serverSide ? rows.length : pager.pageSize), pager.totalRows);
1334
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-table-wrap", className), children: [
1335
+ /* @__PURE__ */ jsx("div", { className: "ru-table-scroll", children: /* @__PURE__ */ jsxs(
1336
+ "table",
1337
+ {
1338
+ className: cn(
1339
+ "ru-table",
1340
+ `ru-table--${size}`,
1341
+ striped && "ru-table--striped",
1342
+ (hoverable || onRowClick) && "ru-table--hover",
1343
+ loading && "ru-table--loading"
1344
+ ),
1345
+ "aria-busy": loading || void 0,
1346
+ children: [
1347
+ caption && /* @__PURE__ */ jsx("caption", { children: caption }),
1348
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: columns.map((col) => /* @__PURE__ */ jsx("th", { scope: "col", style: { textAlign: col.align, width: col.width }, children: col.header }, col.key)) }) }),
1349
+ /* @__PURE__ */ jsx("tbody", { children: rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { className: "ru-table__empty", colSpan: columns.length, children: loading ? "Loading…" : emptyText }) }) : rows.map((row, i) => {
1350
+ const index = pager.start + i;
1351
+ return /* @__PURE__ */ jsx(
1352
+ "tr",
1353
+ {
1354
+ onClick: onRowClick ? () => onRowClick(row, index) : void 0,
1355
+ style: onRowClick ? { cursor: "pointer" } : void 0,
1356
+ children: columns.map((col) => /* @__PURE__ */ jsx("td", { style: { textAlign: col.align }, children: col.render ? col.render(row, index) : row[col.key] }, col.key))
1357
+ },
1358
+ getKey(row, index)
1359
+ );
1360
+ }) })
1361
+ ]
1362
+ }
1363
+ ) }),
1364
+ pager.enabled && /* @__PURE__ */ jsxs("div", { className: "ru-table__footer", children: [
1365
+ /* @__PURE__ */ jsx("p", { className: "ru-table__info", "aria-live": "polite", children: pager.totalRows === 0 ? "No results" : /* @__PURE__ */ jsxs(Fragment, { children: [
1366
+ "Showing ",
1367
+ /* @__PURE__ */ jsx("strong", { children: firstRow }),
1368
+ "–",
1369
+ /* @__PURE__ */ jsx("strong", { children: lastRow }),
1370
+ " of ",
1371
+ /* @__PURE__ */ jsx("strong", { children: pager.totalRows })
1372
+ ] }) }),
1373
+ pager.opts.pageSizeOptions && /* @__PURE__ */ jsxs("div", { className: "ru-table__size", children: [
1374
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "Rows per page" }),
1375
+ /* @__PURE__ */ jsx(
1376
+ Select,
1377
+ {
1378
+ size: "sm",
1379
+ "aria-label": "Rows per page",
1380
+ value: pager.pageSize,
1381
+ onChange: pager.changePageSize,
1382
+ options: pager.opts.pageSizeOptions.map((n) => ({ label: String(n), value: n })),
1383
+ containerClassName: "ru-table__size-select"
1384
+ }
1385
+ )
1386
+ ] }),
1387
+ pager.totalPages > 1 && /* @__PURE__ */ jsx(
1388
+ Pagination,
1389
+ {
1390
+ size: "sm",
1391
+ page: pager.page,
1392
+ totalPages: pager.totalPages,
1393
+ onChange: pager.setPage,
1394
+ siblingCount: pager.opts.siblingCount,
1395
+ className: "ru-table__pagination"
1396
+ }
1397
+ )
1398
+ ] })
1399
+ ] });
1400
+ }
1401
+ const EXIT_MS = 200;
1402
+ function usePresence(open) {
1403
+ const [render, setRender] = useState(open);
1404
+ const [closing, setClosing] = useState(false);
1405
+ useEffect(() => {
1406
+ if (open) {
1407
+ setRender(true);
1408
+ setClosing(false);
1409
+ return void 0;
1410
+ }
1411
+ if (!render) return void 0;
1412
+ setClosing(true);
1413
+ const timer = setTimeout(() => {
1414
+ setRender(false);
1415
+ setClosing(false);
1416
+ }, EXIT_MS);
1417
+ return () => clearTimeout(timer);
1418
+ }, [open]);
1419
+ return { render, closing };
1420
+ }
1421
+ function DialogContent({ titleId, descId, title, description, showCloseButton, onClose, children, footer }) {
1422
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1423
+ (title || description || showCloseButton) && /* @__PURE__ */ jsxs("div", { className: "ru-modal__header", children: [
1424
+ /* @__PURE__ */ jsxs("div", { children: [
1425
+ title && /* @__PURE__ */ jsx("h2", { id: titleId, className: "ru-modal__title", children: title }),
1426
+ description && /* @__PURE__ */ jsx("p", { id: descId, className: "ru-modal__desc", children: description })
1427
+ ] }),
1428
+ showCloseButton && /* @__PURE__ */ jsx(CloseButton, { onClick: onClose })
1429
+ ] }),
1430
+ /* @__PURE__ */ jsx("div", { className: "ru-modal__body", children }),
1431
+ footer && /* @__PURE__ */ jsx("div", { className: "ru-modal__footer", children: footer })
1432
+ ] });
1433
+ }
1434
+ function Modal({
1435
+ open,
1436
+ onClose,
1437
+ size = "md",
1438
+ closeOnOverlayClick = true,
1439
+ closeOnEsc = true,
1440
+ showCloseButton = true,
1441
+ className,
1442
+ ...content
1443
+ }) {
1444
+ const panelRef = useRef(null);
1445
+ const mounted = useMounted();
1446
+ const titleId = useId();
1447
+ const descId = useId();
1448
+ const { render, closing } = usePresence(open);
1449
+ useOverlay(open && mounted, panelRef, onClose, closeOnEsc);
1450
+ if (!render || !mounted) return null;
1451
+ const onOverlayMouseDown = (e) => {
1452
+ if (!closing && closeOnOverlayClick && e.target === e.currentTarget) onClose();
1453
+ };
1454
+ return createPortal(
1455
+ /* @__PURE__ */ jsx("div", { className: cn("ru-overlay", closing && "ru-overlay--closing"), onMouseDown: onOverlayMouseDown, children: /* @__PURE__ */ jsx(
1456
+ "div",
1457
+ {
1458
+ ref: panelRef,
1459
+ role: "dialog",
1460
+ "aria-modal": "true",
1461
+ "aria-labelledby": content.title ? titleId : void 0,
1462
+ "aria-describedby": content.description ? descId : void 0,
1463
+ tabIndex: -1,
1464
+ className: cn("ru-modal", `ru-modal--${size}`, closing && "ru-modal--closing", className),
1465
+ children: /* @__PURE__ */ jsx(
1466
+ DialogContent,
1467
+ {
1468
+ ...content,
1469
+ titleId,
1470
+ descId,
1471
+ showCloseButton,
1472
+ onClose
1473
+ }
1474
+ )
1475
+ }
1476
+ ) }),
1477
+ document.body
1478
+ );
1479
+ }
1480
+ const drawerSizes = { sm: "20rem", md: "24rem", lg: "32rem" };
1481
+ function Drawer({
1482
+ open,
1483
+ onClose,
1484
+ placement = "right",
1485
+ size = "md",
1486
+ floating = false,
1487
+ closeOnOverlayClick = true,
1488
+ closeOnEsc = true,
1489
+ showCloseButton = true,
1490
+ className,
1491
+ ...content
1492
+ }) {
1493
+ const panelRef = useRef(null);
1494
+ const mounted = useMounted();
1495
+ const titleId = useId();
1496
+ const descId = useId();
1497
+ const { render, closing } = usePresence(open);
1498
+ useOverlay(open && mounted, panelRef, onClose, closeOnEsc);
1499
+ if (!render || !mounted) return null;
1500
+ const resolvedSize = typeof size === "number" ? `${size}px` : drawerSizes[size] ?? size;
1501
+ return createPortal(
1502
+ /* @__PURE__ */ jsx(
1503
+ "div",
1504
+ {
1505
+ className: cn("ru-overlay", "ru-overlay--drawer", closing && "ru-overlay--closing"),
1506
+ onMouseDown: (e) => {
1507
+ if (!closing && closeOnOverlayClick && e.target === e.currentTarget) onClose();
1508
+ },
1509
+ children: /* @__PURE__ */ jsx(
1510
+ "div",
1511
+ {
1512
+ ref: panelRef,
1513
+ role: "dialog",
1514
+ "aria-modal": "true",
1515
+ "aria-labelledby": content.title ? titleId : void 0,
1516
+ "aria-describedby": content.description ? descId : void 0,
1517
+ tabIndex: -1,
1518
+ className: cn(
1519
+ "ru-drawer",
1520
+ `ru-drawer--${placement}`,
1521
+ floating && "ru-drawer--floating",
1522
+ closing && "ru-drawer--closing",
1523
+ className
1524
+ ),
1525
+ style: { "--ru-drawer-size": resolvedSize },
1526
+ children: /* @__PURE__ */ jsx(
1527
+ DialogContent,
1528
+ {
1529
+ ...content,
1530
+ titleId,
1531
+ descId,
1532
+ showCloseButton,
1533
+ onClose
1534
+ }
1535
+ )
1536
+ }
1537
+ )
1538
+ }
1539
+ ),
1540
+ document.body
1541
+ );
1542
+ }
1543
+ function Tooltip({
1544
+ content,
1545
+ children,
1546
+ placement = "top",
1547
+ delay = 100,
1548
+ size = "md",
1549
+ arrow = true,
1550
+ color,
1551
+ textColor,
1552
+ open: openProp,
1553
+ disabled,
1554
+ className
1555
+ }) {
1556
+ const [open, setOpen] = useState(false);
1557
+ const timer = useRef(void 0);
1558
+ const id = useId();
1559
+ const visible = (openProp ?? open) && !disabled && content != null && content !== "";
1560
+ useEffect(() => () => clearTimeout(timer.current), []);
1561
+ const show = () => {
1562
+ clearTimeout(timer.current);
1563
+ timer.current = setTimeout(() => setOpen(true), delay);
1564
+ };
1565
+ const hide = () => {
1566
+ clearTimeout(timer.current);
1567
+ setOpen(false);
1568
+ };
1569
+ const trigger = isValidElement(children) ? cloneElement(children, {
1570
+ "aria-describedby": visible ? id : void 0
1571
+ }) : children;
1572
+ return /* @__PURE__ */ jsxs(
1573
+ "span",
1574
+ {
1575
+ className: cn("ru-tooltip", className),
1576
+ onMouseEnter: show,
1577
+ onMouseLeave: hide,
1578
+ onFocus: show,
1579
+ onBlur: hide,
1580
+ onKeyDown: (e) => e.key === "Escape" && hide(),
1581
+ children: [
1582
+ trigger,
1583
+ visible && /* @__PURE__ */ jsx(
1584
+ "span",
1585
+ {
1586
+ role: "tooltip",
1587
+ id,
1588
+ className: cn(
1589
+ "ru-tooltip__bubble",
1590
+ `ru-tooltip__bubble--${placement}`,
1591
+ size !== "md" && `ru-tooltip__bubble--${size}`,
1592
+ !arrow && "ru-tooltip__bubble--no-arrow"
1593
+ ),
1594
+ style: color || textColor ? { "--ru-tooltip-bg": color, "--ru-tooltip-text": textColor } : void 0,
1595
+ children: content
1596
+ }
1597
+ )
1598
+ ]
1599
+ }
1600
+ );
1601
+ }
1602
+ function Dropdown({ trigger, items, align = "start", closeOnSelect = true, className }) {
1603
+ const [open, setOpen] = useState(false);
1604
+ const rootRef = useRef(null);
1605
+ const menuRef = useRef(null);
1606
+ const menuId = useId();
1607
+ useClickOutside(rootRef, () => setOpen(false), open);
1608
+ const getItems = () => {
1609
+ var _a;
1610
+ return Array.from(((_a = menuRef.current) == null ? void 0 : _a.querySelectorAll('[role="menuitem"]:not(:disabled)')) ?? []);
1611
+ };
1612
+ const focusItem = (index) => {
1613
+ const els = getItems();
1614
+ if (els.length) els[(index + els.length) % els.length].focus();
1615
+ };
1616
+ const closeAndFocusTrigger = () => {
1617
+ var _a, _b;
1618
+ setOpen(false);
1619
+ (_b = (_a = rootRef.current) == null ? void 0 : _a.querySelector("[aria-haspopup]")) == null ? void 0 : _b.focus();
1620
+ };
1621
+ useEffect(() => {
1622
+ if (open) focusItem(0);
1623
+ }, [open]);
1624
+ const onMenuKeyDown = (e) => {
1625
+ const els = getItems();
1626
+ const index = els.indexOf(document.activeElement);
1627
+ switch (e.key) {
1628
+ case "ArrowDown":
1629
+ e.preventDefault();
1630
+ focusItem(index + 1);
1631
+ break;
1632
+ case "ArrowUp":
1633
+ e.preventDefault();
1634
+ focusItem(index - 1);
1635
+ break;
1636
+ case "Home":
1637
+ e.preventDefault();
1638
+ focusItem(0);
1639
+ break;
1640
+ case "End":
1641
+ e.preventDefault();
1642
+ focusItem(-1);
1643
+ break;
1644
+ case "Escape":
1645
+ e.preventDefault();
1646
+ closeAndFocusTrigger();
1647
+ break;
1648
+ case "Tab":
1649
+ setOpen(false);
1650
+ break;
1651
+ }
1652
+ };
1653
+ const triggerProps = trigger.props;
1654
+ const triggerEl = cloneElement(trigger, {
1655
+ "aria-haspopup": "menu",
1656
+ "aria-expanded": open,
1657
+ "aria-controls": open ? menuId : void 0,
1658
+ onClick: (e) => {
1659
+ var _a;
1660
+ (_a = triggerProps.onClick) == null ? void 0 : _a.call(triggerProps, e);
1661
+ setOpen((v) => !v);
1662
+ },
1663
+ onKeyDown: (e) => {
1664
+ var _a;
1665
+ (_a = triggerProps.onKeyDown) == null ? void 0 : _a.call(triggerProps, e);
1666
+ if (e.key === "ArrowDown" && !open) {
1667
+ e.preventDefault();
1668
+ setOpen(true);
1669
+ }
1670
+ }
1671
+ });
1672
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cn("ru-dropdown", className), children: [
1673
+ triggerEl,
1674
+ open && /* @__PURE__ */ jsx(
1675
+ "div",
1676
+ {
1677
+ ref: menuRef,
1678
+ id: menuId,
1679
+ role: "menu",
1680
+ className: cn("ru-dropdown__menu", align === "end" && "ru-dropdown__menu--end"),
1681
+ onKeyDown: onMenuKeyDown,
1682
+ children: items.map(
1683
+ (item, i) => item.divider ? /* @__PURE__ */ jsx("div", { role: "separator", className: "ru-dropdown__divider" }, item.key ?? i) : /* @__PURE__ */ jsxs(
1684
+ "button",
1685
+ {
1686
+ type: "button",
1687
+ role: "menuitem",
1688
+ tabIndex: -1,
1689
+ disabled: item.disabled,
1690
+ className: cn("ru-dropdown__item", item.danger && "ru-dropdown__item--danger"),
1691
+ onClick: () => {
1692
+ var _a;
1693
+ (_a = item.onClick) == null ? void 0 : _a.call(item);
1694
+ if (closeOnSelect) closeAndFocusTrigger();
1695
+ },
1696
+ children: [
1697
+ item.icon && /* @__PURE__ */ jsx("span", { className: "ru-dropdown__icon", children: item.icon }),
1698
+ item.label
1699
+ ]
1700
+ },
1701
+ item.key ?? i
1702
+ )
1703
+ )
1704
+ }
1705
+ )
1706
+ ] });
1707
+ }
1708
+ const ToastContext = createContext(null);
1709
+ function ToastProvider({ children, position = "bottom-right", duration = 4e3, max = 5 }) {
1710
+ const [toasts, setToasts] = useState([]);
1711
+ const mounted = useMounted();
1712
+ const counter = useRef(0);
1713
+ const dismiss = useCallback((id) => {
1714
+ setToasts((list) => id ? list.filter((t) => t.id !== id) : []);
1715
+ }, []);
1716
+ const api = useMemo(() => {
1717
+ const show = (options) => {
1718
+ const opts = typeof options === "string" ? { title: options } : options;
1719
+ const id = opts.id ?? `ru-toast-${++counter.current}`;
1720
+ setToasts((list) => [...list.filter((t) => t.id !== id), { ...opts, id }].slice(-max));
1721
+ return id;
1722
+ };
1723
+ const fn = show;
1724
+ fn.success = (title, o) => show({ ...o, title, variant: "success" });
1725
+ fn.error = (title, o) => show({ ...o, title, variant: "danger" });
1726
+ fn.warning = (title, o) => show({ ...o, title, variant: "warning" });
1727
+ fn.info = (title, o) => show({ ...o, title, variant: "info" });
1728
+ fn.dismiss = dismiss;
1729
+ return fn;
1730
+ }, [dismiss, max]);
1731
+ return /* @__PURE__ */ jsxs(ToastContext.Provider, { value: api, children: [
1732
+ children,
1733
+ mounted && createPortal(
1391
1734
  /* @__PURE__ */ jsx("ol", { className: cn("ru-toaster", `ru-toaster--${position}`), "aria-live": "polite", "aria-label": "Notifications", children: toasts.map((t) => /* @__PURE__ */ jsx(ToastView, { toast: t, duration: t.duration ?? duration, onDismiss: dismiss }, t.id)) }),
1392
1735
  document.body
1393
1736
  )
@@ -1429,14 +1772,68 @@ function useToast() {
1429
1772
  function Kbd({ className, ...rest }) {
1430
1773
  return /* @__PURE__ */ jsx("kbd", { className: cn("ru-kbd", className), ...rest });
1431
1774
  }
1432
- function Pattern({ variant = "grid", size = 24, fade = true, className, style, ...rest }) {
1433
- return /* @__PURE__ */ jsx(
1775
+ const patternSemanticColors = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
1776
+ const toCssColor = (c) => patternSemanticColors.has(c) ? `var(--ru-${c})` : c;
1777
+ const toPx = (v) => typeof v === "number" ? `${v}px` : v;
1778
+ function Pattern({
1779
+ variant = "grid",
1780
+ size = 24,
1781
+ thickness,
1782
+ color,
1783
+ opacity,
1784
+ fade = true,
1785
+ animate,
1786
+ speed,
1787
+ spotlight = false,
1788
+ spotlightColor,
1789
+ spotlightSize,
1790
+ className,
1791
+ style,
1792
+ ...rest
1793
+ }) {
1794
+ const ref = useRef(null);
1795
+ useEffect(() => {
1796
+ const el = ref.current;
1797
+ const parent = el == null ? void 0 : el.parentElement;
1798
+ if (!spotlight || !parent) return void 0;
1799
+ const move = (e) => {
1800
+ const rect = el.getBoundingClientRect();
1801
+ el.style.setProperty("--ru-mx", `${e.clientX - rect.left}px`);
1802
+ el.style.setProperty("--ru-my", `${e.clientY - rect.top}px`);
1803
+ el.classList.add("ru-pattern--spot-on");
1804
+ };
1805
+ const leave = () => el.classList.remove("ru-pattern--spot-on");
1806
+ parent.addEventListener("pointermove", move);
1807
+ parent.addEventListener("pointerleave", leave);
1808
+ return () => {
1809
+ parent.removeEventListener("pointermove", move);
1810
+ parent.removeEventListener("pointerleave", leave);
1811
+ };
1812
+ }, [spotlight]);
1813
+ const fadeKind = fade === true ? "radial" : fade || null;
1814
+ const vars = {
1815
+ "--_ps": toPx(size),
1816
+ ...thickness !== void 0 && { "--_pt": toPx(thickness) },
1817
+ ...color && { "--_pc": toCssColor(color) },
1818
+ ...spotlightColor && { "--_spc": toCssColor(spotlightColor) },
1819
+ ...spotlightSize !== void 0 && { "--_sr": toPx(spotlightSize) },
1820
+ ...speed !== void 0 && { "--_speed": typeof speed === "number" ? `${speed}s` : speed },
1821
+ ...opacity !== void 0 && { opacity },
1822
+ ...style
1823
+ };
1824
+ const layerClass = `ru-pattern__layer ru-pattern__layer--${variant}`;
1825
+ return /* @__PURE__ */ jsxs(
1434
1826
  "div",
1435
1827
  {
1828
+ ref,
1436
1829
  "aria-hidden": "true",
1437
- className: cn("ru-pattern", `ru-pattern--${variant}`, fade && "ru-pattern--fade", className),
1438
- style: { "--ru-pattern-size": `${size}px`, ...style },
1439
- ...rest
1830
+ className: cn("ru-pattern", fadeKind && `ru-pattern--fade-${fadeKind}`, animate && `ru-pattern--${animate}`, className),
1831
+ style: vars,
1832
+ ...rest,
1833
+ children: [
1834
+ /* @__PURE__ */ jsx("div", { className: layerClass }),
1835
+ spotlight && /* @__PURE__ */ jsx("div", { className: "ru-pattern__spot", children: /* @__PURE__ */ jsx("div", { className: layerClass }) })
1836
+ ]
1440
1837
  }
1441
1838
  );
1442
1839
  }
@@ -1463,6 +1860,198 @@ function SpotlightCard({ color, className, style, onMouseMove, children, ...rest
1463
1860
  }
1464
1861
  );
1465
1862
  }
1863
+ const semanticColors$1 = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
1864
+ function Pager({ onPrev, onNext, prevLabel, nextLabel }) {
1865
+ if (!onPrev && !onNext) return null;
1866
+ return /* @__PURE__ */ jsxs("div", { className: "ru-frame-card__pager", children: [
1867
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": prevLabel, onClick: onPrev, disabled: !onPrev, children: /* @__PURE__ */ jsx(ChevronLeftIcon, {}) }),
1868
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": nextLabel, onClick: onNext, disabled: !onNext, children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
1869
+ ] });
1870
+ }
1871
+ function FrameCard({
1872
+ title,
1873
+ color = "warning",
1874
+ textColor,
1875
+ titlePlacement = "panel",
1876
+ collapsible = false,
1877
+ open,
1878
+ defaultOpen = true,
1879
+ onOpenChange,
1880
+ headerAction,
1881
+ footer,
1882
+ onPrev,
1883
+ onNext,
1884
+ prevLabel = "Previous",
1885
+ nextLabel = "Next",
1886
+ className,
1887
+ style,
1888
+ children,
1889
+ ...rest
1890
+ }) {
1891
+ const [isOpen, setOpen] = useControllableState(open, defaultOpen, onOpenChange);
1892
+ const bodyId = useId();
1893
+ const onFrame = titlePlacement === "frame";
1894
+ const pager = /* @__PURE__ */ jsx(Pager, { onPrev, onNext, prevLabel, nextLabel });
1895
+ const hasPager = Boolean(onPrev || onNext);
1896
+ return /* @__PURE__ */ jsxs(
1897
+ "section",
1898
+ {
1899
+ className: cn("ru-frame-card", onFrame && "ru-frame-card--title-frame", className),
1900
+ style: {
1901
+ "--_fc": semanticColors$1.has(color) ? `var(--ru-${color})` : color,
1902
+ ...textColor && { "--_fc-on": textColor, "--_fc-ink": textColor },
1903
+ ...style
1904
+ },
1905
+ ...rest,
1906
+ children: [
1907
+ onFrame && /* @__PURE__ */ jsxs("div", { className: "ru-frame-card__frame-head", children: [
1908
+ /* @__PURE__ */ jsx("h3", { className: "ru-frame-card__title", children: title }),
1909
+ headerAction ?? pager
1910
+ ] }),
1911
+ /* @__PURE__ */ jsxs("div", { className: "ru-frame-card__panel", children: [
1912
+ !onFrame && (title || headerAction || collapsible) && /* @__PURE__ */ jsxs("div", { className: "ru-frame-card__head", children: [
1913
+ /* @__PURE__ */ jsx("h3", { className: "ru-frame-card__title", children: title }),
1914
+ headerAction,
1915
+ collapsible && /* @__PURE__ */ jsx(
1916
+ "button",
1917
+ {
1918
+ type: "button",
1919
+ className: "ru-frame-card__toggle",
1920
+ "aria-expanded": isOpen,
1921
+ "aria-controls": bodyId,
1922
+ "aria-label": isOpen ? "Collapse" : "Expand",
1923
+ onClick: () => setOpen(!isOpen),
1924
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, {})
1925
+ }
1926
+ )
1927
+ ] }),
1928
+ /* @__PURE__ */ jsx("div", { id: bodyId, className: cn("ru-frame-card__collapse", !isOpen && "ru-frame-card__collapse--closed"), children: /* @__PURE__ */ jsx("div", { className: "ru-frame-card__inner", children: /* @__PURE__ */ jsx("div", { className: "ru-frame-card__body", children }) }) })
1929
+ ] }),
1930
+ (footer || !onFrame && hasPager) && /* @__PURE__ */ jsxs("div", { className: "ru-frame-card__foot", children: [
1931
+ /* @__PURE__ */ jsx("div", { className: "ru-frame-card__foot-content", children: footer }),
1932
+ !onFrame && pager
1933
+ ] })
1934
+ ]
1935
+ }
1936
+ );
1937
+ }
1938
+ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1939
+ const WEEKDAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
1940
+ const semanticColors = /* @__PURE__ */ new Set(["primary", "secondary", "success", "danger", "warning", "info"]);
1941
+ const dayKey = (d) => d.getFullYear() * 1e4 + d.getMonth() * 100 + d.getDate();
1942
+ const sameDay = (a, b) => Boolean(a && b) && dayKey(a) === dayKey(b);
1943
+ function Calendar({
1944
+ mode = "single",
1945
+ value,
1946
+ defaultValue,
1947
+ onChange,
1948
+ defaultMonth,
1949
+ weekStartsOn = 1,
1950
+ minYear,
1951
+ maxYear,
1952
+ color,
1953
+ today = /* @__PURE__ */ new Date(),
1954
+ className,
1955
+ style,
1956
+ ...rest
1957
+ }) {
1958
+ const emptyValue = mode === "range" ? { start: null, end: null } : null;
1959
+ const [selected, setSelected] = useControllableState(value, defaultValue ?? emptyValue, onChange);
1960
+ const anchor = defaultMonth ?? (mode === "range" ? selected == null ? void 0 : selected.start : selected) ?? today;
1961
+ const [view, setView] = useState({ y: anchor.getFullYear(), m: anchor.getMonth() });
1962
+ const gridRef = useRef(null);
1963
+ const shift = (delta) => setView((v) => {
1964
+ const d = new Date(v.y, v.m + delta, 1);
1965
+ return { y: d.getFullYear(), m: d.getMonth() };
1966
+ });
1967
+ const days = useMemo(() => {
1968
+ const offset = (new Date(view.y, view.m, 1).getDay() - weekStartsOn + 7) % 7;
1969
+ const inMonth = new Date(view.y, view.m + 1, 0).getDate();
1970
+ const cells = Math.ceil((offset + inMonth) / 7) * 7;
1971
+ return Array.from({ length: cells }, (_, i) => new Date(view.y, view.m, 1 - offset + i));
1972
+ }, [view, weekStartsOn]);
1973
+ const weekdays = Array.from({ length: 7 }, (_, i) => WEEKDAYS[(i + weekStartsOn) % 7]);
1974
+ const firstYear = minYear ?? view.y - 10;
1975
+ const lastYear = maxYear ?? view.y + 10;
1976
+ const years = Array.from({ length: lastYear - firstYear + 1 }, (_, i) => firstYear + i);
1977
+ const isSelected = (d) => {
1978
+ if (mode !== "range") return sameDay(d, selected);
1979
+ const { start, end } = selected ?? {};
1980
+ if (start && end) return dayKey(d) >= dayKey(start) && dayKey(d) <= dayKey(end);
1981
+ return sameDay(d, start);
1982
+ };
1983
+ const pick = (d) => {
1984
+ if (d.getMonth() !== view.m) setView({ y: d.getFullYear(), m: d.getMonth() });
1985
+ if (mode !== "range") return setSelected(d);
1986
+ const { start, end } = selected ?? {};
1987
+ if (!start || end) return setSelected({ start: d, end: null });
1988
+ return setSelected(dayKey(d) < dayKey(start) ? { start: d, end: start } : { start, end: d });
1989
+ };
1990
+ const onGridKeyDown = (e) => {
1991
+ const step = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[e.key];
1992
+ if (!step) return;
1993
+ const buttons = Array.from(gridRef.current.querySelectorAll("button"));
1994
+ const next = buttons[buttons.indexOf(document.activeElement) + step];
1995
+ if (next) {
1996
+ e.preventDefault();
1997
+ next.focus();
1998
+ }
1999
+ };
2000
+ const ink = color ? semanticColors.has(color) ? `var(--ru-${color})` : color : void 0;
2001
+ return /* @__PURE__ */ jsxs("div", { className: cn("ru-calendar", className), style: ink ? { "--_cc": ink, ...style } : style, ...rest, children: [
2002
+ /* @__PURE__ */ jsxs("div", { className: "ru-calendar__head", children: [
2003
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ru-calendar__nav", "aria-label": "Previous month", onClick: () => shift(-1), children: /* @__PURE__ */ jsx(ChevronLeftIcon, {}) }),
2004
+ /* @__PURE__ */ jsxs("div", { className: "ru-calendar__pickers", children: [
2005
+ /* @__PURE__ */ jsx(
2006
+ Select,
2007
+ {
2008
+ size: "sm",
2009
+ "aria-label": "Month",
2010
+ value: view.m,
2011
+ onChange: (m) => setView((v) => ({ ...v, m })),
2012
+ options: MONTHS.map((label, i) => ({ label, value: i })),
2013
+ containerClassName: "ru-calendar__picker"
2014
+ }
2015
+ ),
2016
+ /* @__PURE__ */ jsx(
2017
+ Select,
2018
+ {
2019
+ size: "sm",
2020
+ "aria-label": "Year",
2021
+ value: view.y,
2022
+ onChange: (y) => setView((v) => ({ ...v, y })),
2023
+ options: years.map((y) => ({ label: String(y), value: y })),
2024
+ containerClassName: "ru-calendar__picker ru-calendar__picker--year"
2025
+ }
2026
+ )
2027
+ ] }),
2028
+ /* @__PURE__ */ jsx("button", { type: "button", className: "ru-calendar__nav", "aria-label": "Next month", onClick: () => shift(1), children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
2029
+ ] }),
2030
+ /* @__PURE__ */ jsx("div", { className: "ru-calendar__weekdays", "aria-hidden": "true", children: weekdays.map((w) => /* @__PURE__ */ jsx("span", { children: w }, w)) }),
2031
+ /* @__PURE__ */ jsx("div", { ref: gridRef, className: "ru-calendar__grid", onKeyDown: onGridKeyDown, children: days.map((d) => {
2032
+ const outside = d.getMonth() !== view.m;
2033
+ const selectedDay = isSelected(d);
2034
+ return /* @__PURE__ */ jsx(
2035
+ "button",
2036
+ {
2037
+ type: "button",
2038
+ className: cn(
2039
+ "ru-calendar__cell",
2040
+ outside && "ru-calendar__cell--outside",
2041
+ selectedDay && "ru-calendar__cell--selected",
2042
+ sameDay(d, today) && "ru-calendar__cell--today"
2043
+ ),
2044
+ "aria-label": d.toDateString(),
2045
+ "aria-pressed": selectedDay,
2046
+ "aria-current": sameDay(d, today) ? "date" : void 0,
2047
+ onClick: () => pick(d),
2048
+ children: /* @__PURE__ */ jsx("span", { className: "ru-calendar__day", children: d.getDate() })
2049
+ },
2050
+ dayKey(d)
2051
+ );
2052
+ }) })
2053
+ ] });
2054
+ }
1466
2055
  export {
1467
2056
  Accordion,
1468
2057
  Alert,
@@ -1471,6 +2060,8 @@ export {
1471
2060
  Badge,
1472
2061
  Breadcrumbs,
1473
2062
  Button,
2063
+ ButtonGroup,
2064
+ Calendar,
1474
2065
  Card,
1475
2066
  CardBody,
1476
2067
  CardFooter,
@@ -1479,6 +2070,7 @@ export {
1479
2070
  Divider,
1480
2071
  Drawer,
1481
2072
  Dropdown,
2073
+ FrameCard,
1482
2074
  Input,
1483
2075
  Kbd,
1484
2076
  Modal,