@skiddph/prui 0.7.0 → 0.9.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.
Files changed (79) hide show
  1. package/dist/app/resource.d.ts +44 -1
  2. package/dist/app.js +1 -1
  3. package/dist/chunks/auth-shell-DQJ4UmUv.js +1208 -0
  4. package/dist/chunks/data-table-toolbar-B-5J2Ozl.js +736 -0
  5. package/dist/core/accordion.d.ts +16 -0
  6. package/dist/core/accordion.js +108 -0
  7. package/dist/core/alert.d.ts +20 -0
  8. package/dist/core/alert.js +63 -0
  9. package/dist/core/anchor.d.ts +35 -0
  10. package/dist/core/anchor.js +39 -0
  11. package/dist/core/breadcrumb.d.ts +30 -0
  12. package/dist/core/breadcrumb.js +67 -0
  13. package/dist/core/button.d.ts +8 -1
  14. package/dist/core/button.js +23 -18
  15. package/dist/core/calendar.d.ts +29 -0
  16. package/dist/core/calendar.js +180 -0
  17. package/dist/core/card.d.ts +5 -1
  18. package/dist/core/card.js +33 -29
  19. package/dist/core/checkbox.d.ts +19 -0
  20. package/dist/core/checkbox.js +55 -0
  21. package/dist/core/combobox.d.ts +32 -0
  22. package/dist/core/combobox.js +193 -0
  23. package/dist/core/date-picker.d.ts +62 -0
  24. package/dist/core/date-picker.js +314 -0
  25. package/dist/core/dialog.d.ts +15 -0
  26. package/dist/core/dialog.js +77 -60
  27. package/dist/core/drawer.d.ts +34 -0
  28. package/dist/core/drawer.js +129 -0
  29. package/dist/core/dropdown.d.ts +8 -4
  30. package/dist/core/dropdown.js +136 -51
  31. package/dist/core/file-upload.d.ts +28 -0
  32. package/dist/core/file-upload.js +111 -0
  33. package/dist/core/index.d.ts +23 -1
  34. package/dist/core/list-nav.d.ts +27 -0
  35. package/dist/core/list-nav.js +51 -0
  36. package/dist/core/modal.d.ts +14 -5
  37. package/dist/core/modal.js +163 -156
  38. package/dist/core/overlay.d.ts +93 -0
  39. package/dist/core/overlay.js +189 -0
  40. package/dist/core/popover.d.ts +29 -0
  41. package/dist/core/popover.js +124 -0
  42. package/dist/core/progress.d.ts +21 -0
  43. package/dist/core/progress.js +59 -0
  44. package/dist/core/radio.d.ts +20 -0
  45. package/dist/core/radio.js +115 -0
  46. package/dist/core/select.d.ts +6 -0
  47. package/dist/core/select.js +187 -98
  48. package/dist/core/skeleton.d.ts +20 -0
  49. package/dist/core/skeleton.js +41 -0
  50. package/dist/core/spinner.d.ts +15 -0
  51. package/dist/core/spinner.js +41 -0
  52. package/dist/core/tabs.js +82 -53
  53. package/dist/core/time-picker.d.ts +68 -0
  54. package/dist/core/time-picker.js +305 -0
  55. package/dist/core/timeline.d.ts +22 -0
  56. package/dist/core/timeline.js +57 -0
  57. package/dist/core/toast.d.ts +36 -0
  58. package/dist/core/toast.js +123 -0
  59. package/dist/core/tooltip.d.ts +23 -0
  60. package/dist/core/tooltip.js +96 -0
  61. package/dist/core/tree-view.d.ts +34 -0
  62. package/dist/core/tree-view.js +133 -0
  63. package/dist/core.js +160 -62
  64. package/dist/data-table/data-table.d.ts +22 -1
  65. package/dist/data-table.js +1 -1
  66. package/dist/forms/form.d.ts +8 -0
  67. package/dist/forms.js +213 -200
  68. package/dist/i18n/index.d.ts +89 -0
  69. package/dist/i18n.js +105 -0
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.js +263 -155
  72. package/dist/prui-utilities.css +1 -1
  73. package/dist/prui.css +111 -4
  74. package/dist/theme/base.css +110 -3
  75. package/dist/theme/index.d.ts +27 -0
  76. package/dist/theme.js +86 -58
  77. package/package.json +9 -2
  78. package/dist/chunks/auth-shell-BG5vx9eG.js +0 -1054
  79. package/dist/chunks/data-table-toolbar-BUy2EWPy.js +0 -581
@@ -0,0 +1,189 @@
1
+ import * as s from "react";
2
+ import { createPortal as E } from "react-dom";
3
+ function P({
4
+ children: t,
5
+ container: e
6
+ }) {
7
+ const [r, c] = s.useState(null);
8
+ return s.useEffect(() => {
9
+ c(e ?? (typeof document < "u" ? document.body : null));
10
+ }, [e]), r == null ? null : E(t, r);
11
+ }
12
+ function x() {
13
+ const [t, e] = s.useState(() => typeof window > "u" || !window.matchMedia ? !1 : window.matchMedia("(prefers-reduced-motion: reduce)").matches);
14
+ return s.useEffect(() => {
15
+ if (typeof window > "u" || !window.matchMedia) return;
16
+ const r = window.matchMedia("(prefers-reduced-motion: reduce)"), c = () => e(r.matches);
17
+ return r.addEventListener("change", c), () => r.removeEventListener("change", c);
18
+ }, []), t;
19
+ }
20
+ const v = [
21
+ "a[href]",
22
+ "button:not([disabled])",
23
+ "input:not([disabled])",
24
+ "select:not([disabled])",
25
+ "textarea:not([disabled])",
26
+ "iframe",
27
+ "object",
28
+ "embed",
29
+ "[tabindex]:not([tabindex='-1'])",
30
+ "[contenteditable]:not([contenteditable='false'])"
31
+ ].join(",");
32
+ function y(t) {
33
+ return t ? Array.from(t.querySelectorAll(v)).filter(
34
+ (e) => !e.closest("[inert], [aria-hidden='true']") && !e.hasAttribute("hidden")
35
+ ) : [];
36
+ }
37
+ const f = [];
38
+ function g(t) {
39
+ const e = Symbol("prui-overlay");
40
+ return f.push({ id: e, element: t }), e;
41
+ }
42
+ function T(t) {
43
+ const e = f.findIndex((r) => r.id === t);
44
+ e >= 0 && f.splice(e, 1);
45
+ }
46
+ function k(t) {
47
+ const e = f[f.length - 1];
48
+ return e != null && e.id === t;
49
+ }
50
+ function I() {
51
+ return f.length;
52
+ }
53
+ function w(t) {
54
+ const e = s.useRef(null), r = s.useRef(null), [c, o] = s.useReducer((i) => i + 1, 0);
55
+ s.useEffect(() => {
56
+ if (!t) return;
57
+ const i = g(null);
58
+ return e.current = i, r.current = f[f.length - 1] ?? null, o(), () => {
59
+ T(i), e.current === i && (e.current = null, r.current = null);
60
+ };
61
+ }, [t]);
62
+ const n = s.useCallback((i) => {
63
+ r.current && (r.current.element = i);
64
+ }, []), u = s.useCallback(() => e.current != null ? k(e.current) : !1, []);
65
+ return { setElement: n, isTop: u };
66
+ }
67
+ function R(t, e, r) {
68
+ const c = s.useRef(r);
69
+ c.current = r, s.useEffect(() => {
70
+ if (!t) return;
71
+ const o = (n) => {
72
+ if (n.key !== "Escape" || !e()) return;
73
+ c.current() === !1 && n.stopPropagation();
74
+ };
75
+ return document.addEventListener("keydown", o, !0), () => document.removeEventListener("keydown", o, !0);
76
+ }, [t, e]);
77
+ }
78
+ let a = 0, h = "", p = "";
79
+ function L() {
80
+ typeof document > "u" || (a++, !(a > 1) && (h = document.body.style.overflow, p = document.body.style.paddingRight, document.body.style.overflow = "hidden", document.body.classList.add("modal-open")));
81
+ }
82
+ function A() {
83
+ typeof document > "u" || (a = Math.max(0, a - 1), !(a > 0) && (document.body.style.overflow = h, document.body.style.paddingRight = p, document.body.classList.remove("modal-open")));
84
+ }
85
+ function S(t) {
86
+ s.useEffect(() => {
87
+ if (t)
88
+ return L(), A;
89
+ }, [t]);
90
+ }
91
+ const b = typeof HTMLElement < "u" && "inert" in HTMLElement.prototype;
92
+ function C(t, e) {
93
+ const r = t.ownerDocument, c = [];
94
+ for (const o of Array.from(r.body.childNodes)) {
95
+ if (!(o instanceof HTMLElement)) continue;
96
+ const n = o.tagName;
97
+ n === "SCRIPT" || n === "STYLE" || n === "LINK" || n === "NOSCRIPT" || n === "META" || n === "TITLE" || n === "TEMPLATE" || o === t || o.contains(t) || t.contains(o) || e.has(o) || o.getAttribute("aria-hidden") !== "true" && (c.push({ el: o, ariaHidden: o.getAttribute("aria-hidden") }), o.setAttribute("aria-hidden", "true"), b && o.setAttribute("inert", ""));
98
+ }
99
+ return () => {
100
+ for (const { el: o, ariaHidden: n } of c)
101
+ n === null ? o.removeAttribute("aria-hidden") : o.setAttribute("aria-hidden", n), b && o.removeAttribute("inert");
102
+ };
103
+ }
104
+ function M(t, e) {
105
+ s.useEffect(() => {
106
+ if (!t || !e || typeof document > "u") return;
107
+ const r = /* @__PURE__ */ new Set();
108
+ for (const c of f)
109
+ c.element && r.add(c.element);
110
+ return C(e, r);
111
+ }, [t, e]);
112
+ }
113
+ function O({ active: t, container: e, initialFocus: r = "first", restoreFocus: c = !0 }) {
114
+ const o = s.useRef(null);
115
+ s.useEffect(() => {
116
+ if (!t) return;
117
+ o.current = document.activeElement ?? null;
118
+ const u = (r === !1 ? null : r === "container" ? e : r === "first" ? y(e)[0] ?? e : r.current ?? null) ?? e;
119
+ u && u.isConnected && !u.contains(document.activeElement) && (!u.hasAttribute("tabindex") && u.tagName !== "INPUT" && u.tagName !== "BUTTON" && u.tagName !== "TEXTAREA" && u.tagName !== "SELECT" && u.tagName !== "A" && !u.isContentEditable && u.setAttribute("tabindex", "-1"), u.focus());
120
+ }, [t, e, r]), s.useEffect(() => {
121
+ if (t) return;
122
+ const n = o.current;
123
+ !n || !c || (o.current = null, n.isConnected && n.focus());
124
+ }, [t, c]), s.useEffect(
125
+ () => () => {
126
+ const n = o.current;
127
+ n && c && n.isConnected && n.focus();
128
+ },
129
+ [c]
130
+ ), s.useEffect(() => {
131
+ if (!t) return;
132
+ const n = (u) => {
133
+ if (u.key !== "Tab") return;
134
+ const i = y(e);
135
+ if (i.length === 0) {
136
+ e && !e.contains(document.activeElement) && (u.preventDefault(), e.focus());
137
+ return;
138
+ }
139
+ const l = i[0], m = i[i.length - 1], d = document.activeElement;
140
+ if (!d || !(e != null && e.contains(d))) {
141
+ u.preventDefault(), l.focus();
142
+ return;
143
+ }
144
+ u.shiftKey && d === l ? (u.preventDefault(), m.focus()) : !u.shiftKey && d === m && (u.preventDefault(), l.focus());
145
+ };
146
+ return document.addEventListener("keydown", n, !0), () => document.removeEventListener("keydown", n, !0);
147
+ }, [t, e]);
148
+ }
149
+ function B({
150
+ open: t,
151
+ onEscape: e,
152
+ modal: r = !0,
153
+ initialFocus: c = "first",
154
+ restoreFocus: o = !0
155
+ }) {
156
+ const [n, u] = s.useState(null), { setElement: i, isTop: l } = w(t);
157
+ return R(t, l, () => e == null ? void 0 : e()), S(t && r), M(t && r && n != null, n), O({ active: t && r, container: n, initialFocus: c, restoreFocus: o }), { ref: s.useCallback(
158
+ (d) => {
159
+ u(d), i(d);
160
+ },
161
+ [i]
162
+ ), element: n, top: l };
163
+ }
164
+ const D = {
165
+ name: "useOverlay",
166
+ props: [
167
+ { name: "open", type: "boolean", default: null, control: "boolean" },
168
+ { name: "onEscape", type: "() => boolean | void", default: null, control: "none", description: "Return false to block close (Modal shakes)." },
169
+ { name: "modal", type: "boolean", default: "true", control: "boolean", description: "Trap focus, lock scroll, inert the background." },
170
+ { name: "initialFocus", type: 'ref | "first" | "container" | false', default: '"first"', control: "none" },
171
+ { name: "restoreFocus", type: "boolean", default: "true", control: "boolean" }
172
+ ]
173
+ };
174
+ export {
175
+ P as Portal,
176
+ y as getFocusable,
177
+ k as isTopOverlay,
178
+ I as overlayCount,
179
+ D as overlayPropsMeta,
180
+ g as pushOverlay,
181
+ T as removeOverlay,
182
+ R as useEscapeKey,
183
+ O as useFocusTrap,
184
+ M as useInertBackground,
185
+ B as useOverlay,
186
+ w as useOverlayStack,
187
+ x as useReducedMotion,
188
+ S as useScrollLock
189
+ };
@@ -0,0 +1,29 @@
1
+ import { AnchorSide, AnchorAlign } from './anchor';
2
+ import { PropsMeta } from './props-meta';
3
+ import * as React from "react";
4
+ /**
5
+ * Popover: a click-triggered anchored surface for rich content (filters,
6
+ * pickers, little forms). Non-modal by default: Escape (topmost only) and
7
+ * outside-pointer close it, focus starts inside the panel, and it returns
8
+ * to the trigger on close. `modal` opts into the full trap + inert
9
+ * background.
10
+ */
11
+ export interface PopoverProps {
12
+ /** The element that toggles the popover (cloned and wired up). */
13
+ trigger?: React.ReactNode;
14
+ children?: React.ReactNode;
15
+ open?: boolean;
16
+ defaultOpen?: boolean;
17
+ onOpenChange?: (open: boolean) => void;
18
+ side?: AnchorSide;
19
+ align?: AnchorAlign;
20
+ /** Focus-trap + inert background instead of a dismissible surface. */
21
+ modal?: boolean;
22
+ /** Accessible label for the popover panel. */
23
+ ariaLabel?: string;
24
+ /** Min panel width in px. Default 200. */
25
+ minWidth?: number;
26
+ className?: string;
27
+ }
28
+ export declare const Popover: React.ForwardRefExoticComponent<PopoverProps & React.RefAttributes<HTMLDivElement>>;
29
+ export declare const popoverPropsMeta: PropsMeta;
@@ -0,0 +1,124 @@
1
+ import { jsxs as T, Fragment as q, jsx as m } from "react/jsx-runtime";
2
+ import * as l from "react";
3
+ import { cn as C } from "./cn.js";
4
+ import { useOverlayStack as D, useEscapeKey as F, Portal as M } from "./overlay.js";
5
+ import { useAnchoredPosition as U } from "./anchor.js";
6
+ import { usePruiI18n as V } from "../i18n.js";
7
+ const B = l.forwardRef(function({
8
+ trigger: u,
9
+ children: E,
10
+ open: b,
11
+ defaultOpen: P = !1,
12
+ onOpenChange: f,
13
+ side: v = "bottom",
14
+ align: R = "start",
15
+ modal: y = !1,
16
+ ariaLabel: w,
17
+ minWidth: k = 200,
18
+ className: A
19
+ }, c) {
20
+ const [N, S] = l.useState(P), x = b !== void 0, n = x ? b : N, s = l.useRef(null), d = l.useRef(null), [g, j] = l.useState(null), { t: L } = V(), i = (e) => {
21
+ var o, t;
22
+ if (x || S(e), f == null || f(e), !e) {
23
+ const r = document.activeElement;
24
+ r && ((o = d.current) != null && o.contains(r)) && ((t = s.current) == null || t.focus());
25
+ }
26
+ }, { setElement: z, isTop: I } = D(n);
27
+ F(n, I, () => i(!1));
28
+ const { ref: K, position: a } = U({ active: n, anchorRef: s, side: v, align: R });
29
+ l.useEffect(() => {
30
+ if (!n || y) return;
31
+ const e = (o) => {
32
+ var r, h;
33
+ const t = o.target;
34
+ (r = s.current) != null && r.contains(t) || (h = d.current) != null && h.contains(t) || i(!1);
35
+ };
36
+ return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
37
+ }), l.useEffect(() => {
38
+ if (!n) return;
39
+ const e = g;
40
+ if (!e) return;
41
+ const o = e.querySelectorAll(
42
+ "a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])"
43
+ );
44
+ (Array.from(o).find((r) => !r.hasAttribute("hidden")) ?? e).focus();
45
+ }, [n, g]);
46
+ let p;
47
+ if (l.isValidElement(u)) {
48
+ const e = u;
49
+ p = l.cloneElement(e, {
50
+ ref: (o) => {
51
+ s.current = o;
52
+ const t = e.props.ref;
53
+ typeof t == "function" ? t(o) : t && typeof t == "object" && (t.current = o);
54
+ },
55
+ "aria-haspopup": "dialog",
56
+ "aria-expanded": n,
57
+ onClick: (o) => {
58
+ var t, r;
59
+ (r = (t = e.props).onClick) == null || r.call(t, o), o.defaultPrevented || i(!n);
60
+ }
61
+ });
62
+ } else
63
+ p = /* @__PURE__ */ m(
64
+ "button",
65
+ {
66
+ type: "button",
67
+ ref: (e) => {
68
+ s.current = e;
69
+ },
70
+ "aria-haspopup": "dialog",
71
+ "aria-expanded": n,
72
+ onClick: () => i(!n),
73
+ children: u
74
+ }
75
+ );
76
+ return /* @__PURE__ */ T(q, { children: [
77
+ p,
78
+ n ? /* @__PURE__ */ m(M, { children: /* @__PURE__ */ m(
79
+ "div",
80
+ {
81
+ ref: (e) => {
82
+ d.current = e, j(e), K(e), z(e), typeof c == "function" ? c(e) : c && (c.current = e);
83
+ },
84
+ role: "dialog",
85
+ "aria-label": w ?? L.dismiss,
86
+ tabIndex: -1,
87
+ "data-state": n ? "open" : "closed",
88
+ "data-side": (a == null ? void 0 : a.side) ?? v,
89
+ className: C(
90
+ "prui-popover fixed z-[var(--prui-z-overlay)] rounded-[var(--prui-radius)] border border-[var(--prui-line)]",
91
+ "bg-[var(--prui-surface)] p-4 text-sm text-[var(--prui-fg)] shadow-[var(--prui-shadow-lg)] outline-none",
92
+ A
93
+ ),
94
+ style: {
95
+ top: (a == null ? void 0 : a.top) ?? -9999,
96
+ left: (a == null ? void 0 : a.left) ?? -9999,
97
+ minWidth: k
98
+ },
99
+ onKeyDown: (e) => {
100
+ !y && e.key === "Tab" && i(!1);
101
+ },
102
+ children: E
103
+ }
104
+ ) }) : null
105
+ ] });
106
+ });
107
+ B.displayName = "Popover";
108
+ const X = {
109
+ name: "Popover",
110
+ props: [
111
+ { name: "trigger", type: "ReactNode", default: null, control: "none" },
112
+ { name: "open", type: "boolean", default: "undefined", control: "boolean" },
113
+ { name: "defaultOpen", type: "boolean", default: "false", control: "boolean" },
114
+ { name: "onOpenChange", type: "(open: boolean) => void", default: null, control: "none" },
115
+ { name: "side", type: "'top' | 'bottom' | 'left' | 'right'", default: "'bottom'", control: "select", options: ["top", "bottom", "left", "right"] },
116
+ { name: "align", type: "'start' | 'center' | 'end'", default: "'start'", control: "select", options: ["start", "center", "end"] },
117
+ { name: "modal", type: "boolean", default: "false", control: "boolean" },
118
+ { name: "ariaLabel", type: "string", default: "undefined", control: "text" }
119
+ ]
120
+ };
121
+ export {
122
+ B as Popover,
123
+ X as popoverPropsMeta
124
+ };
@@ -0,0 +1,21 @@
1
+ import { PropsMeta } from './props-meta';
2
+ import * as React from "react";
3
+ /**
4
+ * Progress: a linear progress indicator. role="progressbar" with the full
5
+ * aria value triple; an indeterminate mode communicates activity without a
6
+ * known percentage.
7
+ */
8
+ export interface ProgressProps extends React.HTMLAttributes<HTMLDivElement> {
9
+ /** Current value; omit for indeterminate. */
10
+ value?: number;
11
+ /** Minimum (default 0). */
12
+ min?: number;
13
+ /** Maximum (default 100). */
14
+ max?: number;
15
+ /** Render the numeric percentage label. */
16
+ showValue?: boolean;
17
+ /** Color variant. */
18
+ variant?: "brand" | "success" | "warning" | "danger";
19
+ }
20
+ export declare const Progress: React.ForwardRefExoticComponent<ProgressProps & React.RefAttributes<HTMLDivElement>>;
21
+ export declare const progressPropsMeta: PropsMeta;
@@ -0,0 +1,59 @@
1
+ import { jsxs as o, jsx as d } from "react/jsx-runtime";
2
+ import * as v from "react";
3
+ import { cn as l } from "./cn.js";
4
+ const b = {
5
+ brand: "bg-[var(--prui-brand)]",
6
+ success: "bg-[var(--prui-ok)]",
7
+ warning: "bg-[var(--prui-warn)]",
8
+ danger: "bg-[var(--prui-danger)]"
9
+ }, g = v.forwardRef(
10
+ ({ className: u, value: s, min: a = 0, max: e = 100, showValue: m = !1, variant: p = "brand", ...i }, c) => {
11
+ const r = s === void 0, n = r ? void 0 : Math.min(Math.max(s, a), e), t = n === void 0 ? void 0 : (n - a) / (e - a) * 100;
12
+ return /* @__PURE__ */ o("div", { className: l("flex items-center gap-2", u), children: [
13
+ /* @__PURE__ */ d(
14
+ "div",
15
+ {
16
+ ref: c,
17
+ role: "progressbar",
18
+ "aria-valuenow": r ? void 0 : n,
19
+ "aria-valuemin": a,
20
+ "aria-valuemax": e,
21
+ "aria-label": i["aria-label"] ?? "Progress",
22
+ "data-state": r ? "indeterminate" : "determinate",
23
+ className: "prui-progress h-2 w-full overflow-hidden rounded-[var(--prui-radius-full)] bg-[var(--prui-raise)]",
24
+ ...i,
25
+ children: /* @__PURE__ */ d(
26
+ "div",
27
+ {
28
+ className: l(
29
+ "h-full rounded-[var(--prui-radius-full)] transition-[width] duration-[var(--prui-duration-base)] ease-[var(--prui-ease-out)]",
30
+ b[p],
31
+ r && "prui-progress-indeterminate w-1/3"
32
+ ),
33
+ style: r ? void 0 : { width: `${t}%` }
34
+ }
35
+ )
36
+ }
37
+ ),
38
+ m && t !== void 0 ? /* @__PURE__ */ o("span", { className: "text-xs tabular-nums text-[var(--prui-dim)]", children: [
39
+ Math.round(t),
40
+ "%"
41
+ ] }) : null
42
+ ] });
43
+ }
44
+ );
45
+ g.displayName = "Progress";
46
+ const w = {
47
+ name: "Progress",
48
+ props: [
49
+ { name: "value", type: "number", default: "undefined (indeterminate)", control: "number" },
50
+ { name: "min", type: "number", default: "0", control: "number" },
51
+ { name: "max", type: "number", default: "100", control: "number" },
52
+ { name: "showValue", type: "boolean", default: "false", control: "boolean" },
53
+ { name: "variant", type: "'brand' | 'success' | 'warning' | 'danger'", default: "'brand'", control: "select", options: ["brand", "success", "warning", "danger"] }
54
+ ]
55
+ };
56
+ export {
57
+ g as Progress,
58
+ w as progressPropsMeta
59
+ };
@@ -0,0 +1,20 @@
1
+ import { PropsMeta } from './props-meta';
2
+ import * as React from "react";
3
+ export interface RadioGroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
4
+ value?: string;
5
+ defaultValue?: string;
6
+ onChange?: (value: string) => void;
7
+ /** Group label exposed to assistive tech. */
8
+ label?: string;
9
+ name?: string;
10
+ disabled?: boolean;
11
+ orientation?: "vertical" | "horizontal";
12
+ }
13
+ export declare const RadioGroup: React.ForwardRefExoticComponent<RadioGroupProps & React.RefAttributes<HTMLDivElement>>;
14
+ export interface RadioProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "value" | "onChange" | "checked"> {
15
+ value: string;
16
+ disabled?: boolean;
17
+ label?: React.ReactNode;
18
+ }
19
+ export declare const Radio: React.ForwardRefExoticComponent<RadioProps & React.RefAttributes<HTMLButtonElement>>;
20
+ export declare const radioGroupPropsMeta: PropsMeta;
@@ -0,0 +1,115 @@
1
+ import { jsx as v, jsxs as M } from "react/jsx-runtime";
2
+ import * as n from "react";
3
+ import { Circle as P } from "lucide-react";
4
+ import { cn as G } from "./cn.js";
5
+ const I = n.createContext(null);
6
+ function S() {
7
+ const m = n.useContext(I);
8
+ if (!m) throw new Error("Radio must be used inside <RadioGroup>");
9
+ return m;
10
+ }
11
+ const U = n.forwardRef(function({ value: d, defaultValue: a, onChange: i, label: R, name: h, disabled: f, orientation: b = "vertical", className: s, onKeyDown: c, children: y, ...p }, k) {
12
+ const l = n.useId(), x = h ?? `prui-radio-${l}`, [N, o] = n.useState(a ?? ""), j = d !== void 0 ? d : N, [V, A] = n.useState([]), w = n.useRef(/* @__PURE__ */ new Map()), E = (e) => {
13
+ d === void 0 && o(e), i == null || i(e);
14
+ }, z = n.useCallback((e, t) => {
15
+ t ? (w.current.set(e, t), A((u) => u.includes(e) ? u : [...u, e])) : (w.current.delete(e), A((u) => u.filter((g) => g !== e)));
16
+ }, []), D = (e) => {
17
+ if (c == null || c(e), e.defaultPrevented) return;
18
+ const t = V.map((r) => w.current.get(r)).filter((r) => !!r && !r.disabled);
19
+ if (t.length === 0) return;
20
+ const u = t.findIndex((r) => r.tabIndex === 0), g = e.key === "ArrowDown" || e.key === "ArrowRight" ? 1 : e.key === "ArrowUp" || e.key === "ArrowLeft" ? -1 : 0;
21
+ if (g !== 0) {
22
+ e.preventDefault();
23
+ const r = t[(u + g + t.length) % t.length];
24
+ r.focus(), r.click();
25
+ return;
26
+ }
27
+ if (e.key === "Home") {
28
+ e.preventDefault(), t[0].focus(), t[0].click();
29
+ return;
30
+ }
31
+ if (e.key === "End") {
32
+ e.preventDefault();
33
+ const r = t[t.length - 1];
34
+ r.focus(), r.click();
35
+ }
36
+ };
37
+ return /* @__PURE__ */ v(I.Provider, { value: { value: j, name: x, setValue: E, register: z, firstValue: V[0] ?? null }, children: /* @__PURE__ */ v(
38
+ "div",
39
+ {
40
+ ref: k,
41
+ role: "radiogroup",
42
+ "aria-label": R,
43
+ "aria-disabled": f,
44
+ "data-orientation": b,
45
+ onKeyDown: D,
46
+ className: G(
47
+ "prui-radio-group flex gap-2",
48
+ b === "vertical" ? "flex-col" : "flex-row flex-wrap items-center",
49
+ s
50
+ ),
51
+ ...p,
52
+ children: y
53
+ }
54
+ ) });
55
+ });
56
+ U.displayName = "RadioGroup";
57
+ const H = n.forwardRef(function({ className: d, value: a, disabled: i, label: R, children: h, onClick: f, ...b }, s) {
58
+ const { value: c, setValue: y, register: p, firstValue: k } = S(), l = c === a, x = n.useRef(null);
59
+ return n.useEffect(() => (p(a, x.current), () => p(a, null)), [a, p]), /* @__PURE__ */ M(
60
+ "button",
61
+ {
62
+ ref: (o) => {
63
+ x.current = o, typeof s == "function" ? s(o) : s && (s.current = o);
64
+ },
65
+ type: "button",
66
+ role: "radio",
67
+ "aria-checked": l,
68
+ "aria-disabled": i,
69
+ "data-state": l ? "checked" : "unchecked",
70
+ tabIndex: l || !c && a === k ? 0 : -1,
71
+ disabled: i,
72
+ onClick: (o) => {
73
+ f == null || f(o), !o.defaultPrevented && !i && y(a);
74
+ },
75
+ className: G(
76
+ "prui-radio inline-flex items-center gap-2 text-sm cursor-pointer outline-none",
77
+ "focus-visible:ring-2 focus-visible:ring-[var(--prui-brand)] focus-visible:ring-offset-1 rounded-[var(--prui-radius-1)]",
78
+ i ? "opacity-50 cursor-not-allowed" : "text-[var(--prui-fg)]",
79
+ d
80
+ ),
81
+ ...b,
82
+ children: [
83
+ /* @__PURE__ */ v(
84
+ "span",
85
+ {
86
+ "aria-hidden": !0,
87
+ className: G(
88
+ "inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-[var(--prui-radius-full)] border",
89
+ "transition-colors duration-[var(--prui-duration-fast)]",
90
+ l ? "border-[var(--prui-brand)]" : "border-[var(--prui-line)] bg-[var(--prui-background)]"
91
+ ),
92
+ children: l ? /* @__PURE__ */ v(P, { className: "h-2 w-2 fill-[var(--prui-brand)] text-[var(--prui-brand)]" }) : null
93
+ }
94
+ ),
95
+ R ?? h
96
+ ]
97
+ }
98
+ );
99
+ });
100
+ H.displayName = "Radio";
101
+ const B = {
102
+ name: "RadioGroup",
103
+ props: [
104
+ { name: "value", type: "string", default: "undefined", control: "text" },
105
+ { name: "defaultValue", type: "string", default: "undefined", control: "text" },
106
+ { name: "onChange", type: "(value: string) => void", default: null, control: "none" },
107
+ { name: "label", type: "string (group label)", default: null, control: "text" },
108
+ { name: "orientation", type: "'vertical' | 'horizontal'", default: "'vertical'", control: "select", options: ["vertical", "horizontal"] }
109
+ ]
110
+ };
111
+ export {
112
+ H as Radio,
113
+ U as RadioGroup,
114
+ B as radioGroupPropsMeta
115
+ };
@@ -1,5 +1,11 @@
1
1
  import { PropsMeta } from './props-meta';
2
2
  import * as React from "react";
3
+ /**
4
+ * Select: a native-feeling single-select listbox. Full keyboard support:
5
+ * Enter/Space/ArrowDown open, ArrowUp/Down/Home/End move, Enter/Space pick,
6
+ * Escape (topmost overlay only) closes and restores focus, Tab closes,
7
+ * printable-character typeahead jumps. Non-modal: no scroll lock.
8
+ */
3
9
  export interface SelectOption {
4
10
  label: string;
5
11
  value: string;