@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
@@ -7,6 +7,12 @@ import * as React from "react";
7
7
  * false = shake feedback, and an imperative confirmModal() that renders a
8
8
  * typed confirmation dialog imperatively and resolves a Promise<boolean>.
9
9
  * Restyled onto PRUI tokens instead of hardcoded hex.
10
+ *
11
+ * Built on the shared overlay infrastructure (core/overlay): portaled to the
12
+ * body, focus-trapped with initial focus and focus restoration, Escape is
13
+ * answered only by the topmost overlay, and the background is aria-hidden +
14
+ * inert where supported while open. prefers-reduced-motion disables the
15
+ * shake/scale/fade choreography.
10
16
  */
11
17
  export type ModalSize = "xs" | "sm" | "md" | "lg" | "xl";
12
18
  export interface ModalProps {
@@ -32,11 +38,12 @@ export interface ModalProps {
32
38
  /** Accessible label. */
33
39
  ariaLabel?: string;
34
40
  className?: string;
41
+ /** Inline style applied to the modal content element. */
42
+ style?: React.CSSProperties;
43
+ /** Initial focus target: the first focusable, the dialog itself, a specific element, or none. */
44
+ initialFocus?: React.RefObject<HTMLElement | null> | "first" | "container" | false;
35
45
  }
36
- export declare function Modal({ open, onClose, children, disableDefaultClose, closeOnOverlayClick, size, xs, sm, md, lg, xl, maxWidth, padding, noPadding, showCloseButton, ariaLabel, className, }: ModalProps): React.JSX.Element | null;
37
- export declare namespace Modal {
38
- var displayName: string;
39
- }
46
+ export declare const Modal: React.ForwardRefExoticComponent<ModalProps & React.RefAttributes<HTMLDivElement>>;
40
47
  export type ConfirmModalType = "success" | "warning" | "danger" | "error" | "info";
41
48
  export interface ConfirmModalOptions {
42
49
  title: string;
@@ -49,7 +56,9 @@ export interface ConfirmModalOptions {
49
56
  /**
50
57
  * confirmModal({ title, message, type }) => Promise<boolean>
51
58
  * Renders the confirmation modal imperatively; resolves true on confirm,
52
- * false on cancel/Escape/overlay-shake close. Focus-trapped like the original.
59
+ * false on cancel/Escape/overlay-shake close. Focus-trapped via the shared
60
+ * overlay infrastructure: initial focus lands on the confirm action and
61
+ * focus returns to the invoker on close.
53
62
  */
54
63
  export declare function confirmModal(options: ConfirmModalOptions): Promise<boolean>;
55
64
  export declare const modalPropsMeta: PropsMeta;
@@ -1,155 +1,170 @@
1
- import { jsx as e, jsxs as d } from "react/jsx-runtime";
2
- import * as r from "react";
3
- import { createRoot as I } from "react-dom/client";
4
- import { X as K } from "lucide-react";
5
- import { cn as Y } from "./cn.js";
6
- const P = {
1
+ import { jsx as e, jsxs as a } from "react/jsx-runtime";
2
+ import * as o from "react";
3
+ import { createRoot as X } from "react-dom/client";
4
+ import { X as q } from "lucide-react";
5
+ import { cn as G } from "./cn.js";
6
+ import { useReducedMotion as B, useOverlay as F, Portal as H } from "./overlay.js";
7
+ import { getPruiDictionary as J } from "../i18n.js";
8
+ const O = {
7
9
  xs: { width: "320px", minWidth: "280px" },
8
10
  sm: { width: "448px", minWidth: "320px" },
9
11
  md: { width: "640px", minWidth: "384px" },
10
12
  lg: { width: "896px", minWidth: "512px" },
11
13
  xl: { width: "1152px", minWidth: "640px" }
12
- }, D = {
14
+ }, K = {
13
15
  xs: "24px 32px",
14
16
  sm: "24px 32px",
15
17
  md: "32px 40px",
16
18
  lg: "32px 40px",
17
19
  xl: "40px 48px"
18
- };
19
- function O({
20
- open: n,
21
- onClose: o,
20
+ }, Q = o.forwardRef(function({
21
+ open: r,
22
+ onClose: n,
22
23
  children: W,
23
- disableDefaultClose: u = !1,
24
- closeOnOverlayClick: L = !0,
24
+ disableDefaultClose: p = !1,
25
+ closeOnOverlayClick: M = !0,
25
26
  size: y,
26
- xs: T,
27
- sm: S,
28
- md: z,
29
- lg: f,
30
- xl: v,
31
- maxWidth: E,
32
- padding: N,
33
- noPadding: h = !1,
34
- showCloseButton: M = !0,
35
- ariaLabel: x,
36
- className: k
37
- }) {
38
- const l = y ?? (T ? "xs" : S ? "sm" : f ? "lg" : v ? "xl" : "md"), [R, g] = r.useState(n), [c, m] = r.useState(!1), [w, i] = r.useState(!1), C = r.useRef(null);
39
- if (r.useEffect(() => {
40
- if (n) {
41
- g(!0);
27
+ xs: S,
28
+ sm: T,
29
+ md: N,
30
+ lg: m,
31
+ xl: b,
32
+ maxWidth: R,
33
+ padding: z,
34
+ noPadding: w = !1,
35
+ showCloseButton: k = !0,
36
+ ariaLabel: f,
37
+ className: h,
38
+ style: s,
39
+ initialFocus: P = "first"
40
+ }, l) {
41
+ const v = y ?? (S ? "xs" : T ? "sm" : m ? "lg" : b ? "xl" : "md"), [x, i] = o.useState(r), [d, u] = o.useState(!1), [g, L] = o.useState(!1), A = o.useRef(null), C = B(), E = () => {
42
+ C || (L(!0), setTimeout(() => L(!1), 500));
43
+ };
44
+ o.useEffect(() => {
45
+ if (r) {
46
+ if (i(!0), C) {
47
+ u(!0);
48
+ return;
49
+ }
42
50
  const t = setTimeout(() => {
43
- m(!0), document.body.classList.add("modal-open");
51
+ u(!0);
44
52
  }, 10);
45
53
  return () => clearTimeout(t);
46
54
  }
47
- m(!1), document.body.classList.remove("modal-open");
48
- }, [n]), r.useEffect(() => () => document.body.classList.remove("modal-open"), []), r.useEffect(() => {
49
- if (!n) return;
50
- const t = (p) => {
51
- p.key === "Escape" && (u ? (i(!0), setTimeout(() => i(!1), 500)) : o());
52
- };
53
- return document.addEventListener("keydown", t), () => document.removeEventListener("keydown", t);
54
- }, [n, u, o]), !R && !c) return null;
55
- const a = (t) => {
55
+ u(!1), C && i(!1);
56
+ }, [r, C]);
57
+ const { ref: V } = F({
58
+ open: r,
59
+ onEscape: () => {
60
+ if (p)
61
+ return E(), !1;
62
+ n();
63
+ },
64
+ initialFocus: P
65
+ });
66
+ if (!x && !d) return null;
67
+ const I = (t) => {
56
68
  if (t.target === t.currentTarget) {
57
- if (u || !L) {
58
- i(!0), setTimeout(() => i(!1), 500);
69
+ if (p || !M) {
70
+ E();
59
71
  return;
60
72
  }
61
- o();
73
+ n();
62
74
  }
63
- }, s = P[l], b = h ? "0" : N ?? D[l];
64
- return /* @__PURE__ */ e(
75
+ }, j = O[v], D = w ? "0" : z ?? K[v];
76
+ return /* @__PURE__ */ e(H, { children: /* @__PURE__ */ e("div", { ref: V, children: /* @__PURE__ */ e(
65
77
  "div",
66
78
  {
67
- className: "prui-modal-overlay fixed inset-0 z-[10000] flex justify-center overflow-y-auto p-4",
79
+ className: "prui-modal-overlay fixed inset-0 z-[var(--prui-z-modal)] flex justify-center overflow-y-auto p-4",
68
80
  style: {
69
81
  alignItems: "flex-start",
70
82
  paddingTop: "10vh",
71
- backgroundColor: "rgba(0, 0, 0, 0.55)",
83
+ backgroundColor: "var(--prui-scrim)",
72
84
  backdropFilter: "blur(4px)",
73
- opacity: c ? 1 : 0,
74
- transition: "opacity 0.3s cubic-bezier(0.25, 0.1, 0.25, 1)"
85
+ opacity: d ? 1 : 0,
86
+ transition: "opacity var(--prui-duration-slow) var(--prui-ease-out)"
75
87
  },
76
- onClick: a,
88
+ onClick: I,
77
89
  onMouseDown: (t) => {
78
- t.target === t.currentTarget && a(t);
90
+ t.target === t.currentTarget && I(t);
79
91
  },
80
- children: /* @__PURE__ */ d(
92
+ children: /* @__PURE__ */ a(
81
93
  "div",
82
94
  {
83
- ref: C,
95
+ ref: (t) => {
96
+ A.current = t, typeof l == "function" ? l(t) : l && (l.current = t);
97
+ },
84
98
  role: "dialog",
85
99
  "aria-modal": "true",
86
- "aria-label": x,
87
- className: Y(
100
+ "aria-label": f,
101
+ className: G(
88
102
  "prui-modal-content relative h-fit m-auto w-full",
89
- w && "prui-modal-shake",
90
- k
103
+ g && "prui-modal-shake",
104
+ h
91
105
  ),
92
106
  style: {
93
- width: s.width,
94
- minWidth: s.minWidth,
95
- maxWidth: E ?? s.width,
96
- padding: b,
107
+ width: j.width,
108
+ minWidth: j.minWidth,
109
+ maxWidth: R ?? j.width,
110
+ padding: D,
97
111
  backgroundColor: "var(--prui-surface)",
98
112
  border: "1px solid var(--prui-line)",
99
113
  borderRadius: "var(--prui-radius-3)",
100
- boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.2)",
114
+ boxShadow: "var(--prui-shadow-modal)",
101
115
  color: "var(--prui-fg)",
102
- transform: c ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)",
103
- opacity: c ? 1 : 0,
104
- transition: "transform 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), opacity 0.3s cubic-bezier(0.25, 0.1, 0.25, 1)"
116
+ transform: d ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)",
117
+ opacity: d ? 1 : 0,
118
+ transition: "transform var(--prui-duration-slow) var(--prui-ease-out), opacity var(--prui-duration-slow) var(--prui-ease-out)",
119
+ ...s
105
120
  },
106
121
  onTransitionEnd: () => {
107
- n || g(!1);
122
+ r || i(!1);
108
123
  },
109
124
  children: [
110
- M && !u ? /* @__PURE__ */ e(
125
+ W,
126
+ k && !p ? /* @__PURE__ */ e(
111
127
  "button",
112
128
  {
113
129
  type: "button",
114
- onClick: o,
130
+ onClick: n,
115
131
  "aria-label": "Close modal",
116
132
  className: "absolute right-4 top-4 flex h-10 w-10 cursor-pointer items-center justify-center rounded-[var(--prui-radius)] text-[var(--prui-dim)] transition-all hover:bg-[var(--prui-raise)] hover:text-[var(--prui-fg)]",
117
- children: /* @__PURE__ */ e(K, { className: "h-4 w-4", "aria-hidden": !0 })
133
+ children: /* @__PURE__ */ e(q, { className: "h-4 w-4", "aria-hidden": !0 })
118
134
  }
119
- ) : null,
120
- W
135
+ ) : null
121
136
  ]
122
137
  }
123
138
  )
124
139
  }
125
- );
126
- }
127
- O.displayName = "Modal";
128
- const B = {
140
+ ) }) });
141
+ });
142
+ Q.displayName = "Modal";
143
+ const Y = {
129
144
  success: "var(--prui-ok)",
130
145
  warning: "var(--prui-warn)",
131
146
  danger: "var(--prui-danger)",
132
147
  error: "var(--prui-danger)",
133
148
  info: "var(--prui-brand)",
134
149
  default: "var(--prui-brand)"
135
- }, A = ({ type: n }) => {
136
- const o = B[n ?? "default"];
137
- switch (n) {
150
+ }, U = ({ type: c }) => {
151
+ const r = Y[c ?? "default"];
152
+ switch (c) {
138
153
  case "success":
139
- return /* @__PURE__ */ d("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", style: { color: o }, children: [
154
+ return /* @__PURE__ */ a("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", style: { color: r }, "aria-hidden": !0, children: [
140
155
  /* @__PURE__ */ e("path", { d: "M9 12l2 2 4-4", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
141
156
  /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" })
142
157
  ] });
143
158
  case "warning":
144
- return /* @__PURE__ */ e("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", style: { color: o }, children: /* @__PURE__ */ e("path", { d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.694-.833-2.464 0L4.34 16.5c-.77.833.192 2.5 1.732 2.5z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) });
159
+ return /* @__PURE__ */ e("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", style: { color: r }, "aria-hidden": !0, children: /* @__PURE__ */ e("path", { d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.694-.833-2.464 0L4.34 16.5c-.77.833.192 2.5 1.732 2.5z", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) });
145
160
  case "danger":
146
161
  case "error":
147
- return /* @__PURE__ */ d("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", style: { color: o }, children: [
162
+ return /* @__PURE__ */ a("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", style: { color: r }, "aria-hidden": !0, children: [
148
163
  /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" }),
149
- /* @__PURE__ */ e("path", { d: "M15 9l-6 6m0-6l6 6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" })
164
+ /* @__PURE__ */ e("path", { d: "M15 9l-6 6m0-6l6 6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
150
165
  ] });
151
166
  case "info":
152
- return /* @__PURE__ */ d("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", style: { color: o }, children: [
167
+ return /* @__PURE__ */ a("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", style: { color: r }, "aria-hidden": !0, children: [
153
168
  /* @__PURE__ */ e("circle", { cx: "12", cy: "12", r: "9", stroke: "currentColor", strokeWidth: "2" }),
154
169
  /* @__PURE__ */ e("path", { d: "M12 8v4m0 4h.01", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" })
155
170
  ] });
@@ -157,109 +172,100 @@ const B = {
157
172
  return null;
158
173
  }
159
174
  };
160
- function G(n) {
161
- const { title: o, message: W, confirmText: u = "Confirm", cancelText: L = "Cancel", type: y, size: T = "sm" } = n, S = B[y ?? "default"];
162
- return new Promise((z) => {
163
- const f = document.createElement("div");
164
- document.body.appendChild(f);
165
- const v = I(f), E = () => {
175
+ function oe(c) {
176
+ const r = J(), { title: n, message: W, confirmText: p = r.confirm, cancelText: M = r.cancel, type: y, size: S = "sm" } = c, T = Y[y ?? "default"];
177
+ return new Promise((N) => {
178
+ const m = document.createElement("div");
179
+ document.body.appendChild(m);
180
+ const b = X(m), R = () => {
166
181
  setTimeout(() => {
167
- v.unmount(), f.remove();
182
+ b.unmount(), m.remove();
168
183
  }, 320);
169
- }, N = () => {
170
- const [h, M] = r.useState(!1), [x, k] = r.useState(!1), [l, R] = r.useState(!1), g = r.useRef(null), c = r.useRef(null);
171
- r.useEffect(() => {
172
- const i = setTimeout(() => {
173
- M(!0), k(!0), document.body.classList.add("modal-open");
184
+ }, z = () => {
185
+ const [w, k] = o.useState(!1), [f, h] = o.useState(!1), [s, P] = o.useState(!1), l = o.useRef(null), v = o.useRef(null), x = B();
186
+ o.useEffect(() => {
187
+ if (x) {
188
+ k(!0), h(!0);
189
+ return;
190
+ }
191
+ const g = setTimeout(() => {
192
+ k(!0), h(!0);
174
193
  }, 10);
175
- return () => clearTimeout(i);
176
- }, []);
177
- const m = (i) => {
178
- R(!0), k(!1), document.body.classList.remove("modal-open"), setTimeout(() => {
179
- E(), z(i);
180
- }, 300);
181
- };
182
- if (r.useEffect(() => {
183
- var C;
184
- if (!h || l) return;
185
- (C = c.current) == null || C.focus();
186
- const i = (a) => {
187
- if (l) return;
188
- if (a.key === "Escape") {
189
- a.preventDefault(), m(!1);
190
- return;
191
- }
192
- if (a.key !== "Tab") return;
193
- const s = [g.current, c.current].filter((j) => !!j);
194
- if (!s.length) return;
195
- const b = s[0], t = s[s.length - 1], p = document.activeElement;
196
- !p || !s.some((j) => j === p) ? (a.preventDefault(), b.focus()) : a.shiftKey && p === b ? (a.preventDefault(), t.focus()) : !a.shiftKey && p === t && (a.preventDefault(), b.focus());
197
- };
198
- return document.addEventListener("keydown", i), () => document.removeEventListener("keydown", i);
199
- }, [h, l]), !h) return null;
200
- const w = P[T];
201
- return /* @__PURE__ */ e(
194
+ return () => clearTimeout(g);
195
+ }, [x]);
196
+ const i = (g) => {
197
+ P(!0), h(!1), setTimeout(() => {
198
+ R(), N(g);
199
+ }, x ? 0 : 300);
200
+ }, { ref: d } = F({
201
+ open: w && !s,
202
+ onEscape: () => (s || i(!1), !1),
203
+ initialFocus: v
204
+ });
205
+ if (!w) return null;
206
+ const u = O[S];
207
+ return /* @__PURE__ */ e("div", { ref: d, children: /* @__PURE__ */ e(
202
208
  "div",
203
209
  {
204
- className: "prui-modal-overlay fixed inset-0 z-[10001] flex justify-center overflow-y-auto p-4",
210
+ className: "prui-modal-overlay fixed inset-0 z-[var(--prui-z-confirm)] flex justify-center overflow-y-auto p-4",
205
211
  style: {
206
212
  alignItems: "flex-start",
207
213
  paddingTop: "12vh",
208
- backgroundColor: "rgba(0, 0, 0, 0.55)",
214
+ backgroundColor: "var(--prui-scrim)",
209
215
  backdropFilter: "blur(4px)",
210
- opacity: x ? 1 : 0,
211
- transition: "opacity 0.3s cubic-bezier(0.25, 0.1, 0.25, 1)"
216
+ opacity: f ? 1 : 0,
217
+ transition: "opacity var(--prui-duration-slow) var(--prui-ease-out)"
212
218
  },
213
- children: /* @__PURE__ */ d(
219
+ children: /* @__PURE__ */ a(
214
220
  "div",
215
221
  {
216
222
  role: "alertdialog",
217
223
  "aria-modal": "true",
218
- "aria-label": o,
224
+ "aria-label": n,
219
225
  className: "prui-modal-content relative m-auto w-full",
220
226
  style: {
221
- width: w.width,
222
- minWidth: w.minWidth,
227
+ width: u.width,
228
+ minWidth: u.minWidth,
223
229
  backgroundColor: "var(--prui-surface)",
224
230
  border: "1px solid var(--prui-line)",
225
231
  borderRadius: "var(--prui-radius-3)",
226
- boxShadow: "0 20px 25px -5px rgba(0,0,0,0.4), 0 10px 10px -5px rgba(0,0,0,0.2)",
232
+ boxShadow: "var(--prui-shadow-modal)",
227
233
  color: "var(--prui-fg)",
228
- transform: x ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)",
229
- opacity: x ? 1 : 0,
230
- transition: "transform 0.3s cubic-bezier(0.25,0.1,0.25,1), opacity 0.3s cubic-bezier(0.25,0.1,0.25,1)"
234
+ transform: f ? "scale(1) translateY(0)" : "scale(0.95) translateY(8px)",
235
+ opacity: f ? 1 : 0,
236
+ transition: "transform var(--prui-duration-slow) var(--prui-ease-out), opacity var(--prui-duration-slow) var(--prui-ease-out)"
231
237
  },
232
238
  children: [
233
- /* @__PURE__ */ d("div", { className: "flex items-start gap-3 p-4 pb-2", children: [
234
- /* @__PURE__ */ e(A, { type: y }),
235
- /* @__PURE__ */ d("div", { className: "min-w-0", children: [
236
- /* @__PURE__ */ e("h2", { className: "m-0 text-lg font-semibold leading-normal", style: { color: "var(--prui-fg)" }, children: o }),
239
+ /* @__PURE__ */ a("div", { className: "flex items-start gap-3 p-4 pb-2", children: [
240
+ /* @__PURE__ */ e(U, { type: y }),
241
+ /* @__PURE__ */ a("div", { className: "min-w-0", children: [
242
+ /* @__PURE__ */ e("h2", { className: "m-0 text-lg font-semibold leading-normal", style: { color: "var(--prui-fg)" }, children: n }),
237
243
  /* @__PURE__ */ e("p", { className: "mt-2 text-sm leading-normal", style: { color: "var(--prui-dim)" }, children: W })
238
244
  ] })
239
245
  ] }),
240
- /* @__PURE__ */ d("div", { className: "flex items-center justify-end gap-3 px-4 pb-4", children: [
246
+ /* @__PURE__ */ a("div", { className: "flex items-center justify-end gap-3 px-4 pb-4", children: [
241
247
  /* @__PURE__ */ e(
242
248
  "button",
243
249
  {
244
250
  type: "button",
245
- ref: g,
246
- disabled: l,
247
- onClick: () => m(!1),
251
+ ref: l,
252
+ disabled: s,
253
+ onClick: () => i(!1),
248
254
  className: "cursor-pointer rounded-[var(--prui-radius)] border px-4 py-2 text-sm font-medium transition-all disabled:opacity-60",
249
255
  style: { background: "transparent", borderColor: "var(--prui-line)", color: "var(--prui-fg)" },
250
- children: L
256
+ children: M
251
257
  }
252
258
  ),
253
259
  /* @__PURE__ */ e(
254
260
  "button",
255
261
  {
256
262
  type: "button",
257
- ref: c,
258
- disabled: l,
259
- onClick: () => m(!0),
263
+ ref: v,
264
+ disabled: s,
265
+ onClick: () => i(!0),
260
266
  className: "cursor-pointer rounded-[var(--prui-radius)] border border-transparent px-4 py-2 text-sm font-medium text-white transition-all disabled:opacity-60",
261
- style: { backgroundColor: S },
262
- children: u
267
+ style: { backgroundColor: T },
268
+ children: p
263
269
  }
264
270
  )
265
271
  ] })
@@ -267,12 +273,12 @@ function G(n) {
267
273
  }
268
274
  )
269
275
  }
270
- );
276
+ ) });
271
277
  };
272
- v.render(/* @__PURE__ */ e(N, {}));
278
+ b.render(/* @__PURE__ */ e(z, {}));
273
279
  });
274
280
  }
275
- const H = {
281
+ const ae = {
276
282
  name: "Modal",
277
283
  props: [
278
284
  { name: "open", type: "boolean", default: null, control: "boolean" },
@@ -283,9 +289,10 @@ const H = {
283
289
  { name: "noPadding", type: "boolean", default: "false", control: "boolean" },
284
290
  { name: "showCloseButton", type: "boolean", default: "true", control: "boolean" },
285
291
  { name: "padding", type: "string (CSS)", default: "per-size", control: "text" },
286
- { name: "maxWidth", type: "string (CSS)", default: "per-size", control: "text" }
292
+ { name: "maxWidth", type: "string (CSS)", default: "per-size", control: "text" },
293
+ { name: "initialFocus", type: 'ref | "first" | "container" | false', default: '"first"', control: "none" }
287
294
  ]
288
- }, J = {
295
+ }, ie = {
289
296
  name: "confirmModal",
290
297
  props: [
291
298
  { name: "title", type: "string", default: null, control: "text" },
@@ -296,8 +303,8 @@ const H = {
296
303
  ]
297
304
  };
298
305
  export {
299
- O as Modal,
300
- G as confirmModal,
301
- J as confirmModalPropsMeta,
302
- H as modalPropsMeta
306
+ Q as Modal,
307
+ oe as confirmModal,
308
+ ie as confirmModalPropsMeta,
309
+ ae as modalPropsMeta
303
310
  };
@@ -0,0 +1,93 @@
1
+ import { PropsMeta } from './props-meta';
2
+ import * as React from "react";
3
+ /**
4
+ * Shared overlay infrastructure.
5
+ *
6
+ * Every floating surface in PRUI (Modal, Dialog, Drawer, Sheet, Command
7
+ * Palette, Dropdown, Tooltip, Popover, Toast, confirmModal) builds on the
8
+ * primitives in this file instead of duplicating focus/escape/scroll logic:
9
+ *
10
+ * - <Portal> renders into document.body (or a custom container).
11
+ * - useReducedMotion() mirrors prefers-reduced-motion.
12
+ * - useOverlayStack() registers the overlay so only the TOPMOST one answers
13
+ * Escape, and nested dialogs stack correctly.
14
+ * - useFocusTrap() cycles Tab/Shift+Tab inside the overlay, moves initial
15
+ * focus in, and restores focus to the invoker on close.
16
+ * - useScrollLock() is counter-based so nested overlays cannot unlock each
17
+ * other's body lock.
18
+ * - useInertBackground() hides the rest of the page from assistive tech
19
+ * (aria-hidden + the native inert attribute where supported).
20
+ * - useOverlay() composes all of the above for modal surfaces.
21
+ */
22
+ export declare function Portal({ children, container, }: {
23
+ children: React.ReactNode;
24
+ container?: HTMLElement | null;
25
+ }): React.ReactPortal | null;
26
+ /** True when the user asked the OS for reduced motion. */
27
+ export declare function useReducedMotion(): boolean;
28
+ /** All focusable elements inside root, in DOM order. */
29
+ export declare function getFocusable(root: HTMLElement | null): HTMLElement[];
30
+ /** Register an overlay; returns its stack id. */
31
+ export declare function pushOverlay(element: HTMLElement | null): symbol;
32
+ /** Unregister an overlay by id. */
33
+ export declare function removeOverlay(id: symbol): void;
34
+ /** True when `id` is the topmost registered overlay. */
35
+ export declare function isTopOverlay(id: symbol): boolean;
36
+ /** Number of currently open overlays (for nesting decisions). */
37
+ export declare function overlayCount(): number;
38
+ /**
39
+ * Registers/unregisters an overlay in the stack while `active`.
40
+ * Registration happens in effect order (= mount order = nesting order), so
41
+ * the last-registered open overlay is the topmost. Returns
42
+ * [setElement, isTop] — setElement keeps the stack entry's DOM node current
43
+ * for inert-background exemption.
44
+ */
45
+ export declare function useOverlayStack(active: boolean): {
46
+ setElement: (el: HTMLElement | null) => void;
47
+ isTop: () => boolean;
48
+ };
49
+ /**
50
+ * Calls onEscape for Escape keydowns while active AND topmost in the overlay
51
+ * stack. Returning false from onEscape marks the key handled-but-blocked
52
+ * (e.g. the Modal shake) and stops the event from leaking to lower overlays.
53
+ */
54
+ export declare function useEscapeKey(active: boolean, isTop: () => boolean, onEscape: () => boolean | void): void;
55
+ /** Locks body scroll while active. Nest-safe: the last overlay to unlock wins. */
56
+ export declare function useScrollLock(active: boolean): void;
57
+ /**
58
+ * Hides everything outside the overlay element from assistive technology
59
+ * while active. Elements registered as open overlay portals (this overlay
60
+ * and any nested ones) stay reachable. SSR-safe no-op.
61
+ */
62
+ export declare function useInertBackground(active: boolean, element: HTMLElement | null): void;
63
+ export interface FocusTrapOptions {
64
+ active: boolean;
65
+ /** Container to trap inside. */
66
+ container: HTMLElement | null;
67
+ /** Focus target on activation: a ref, "first" focusable, or the container. */
68
+ initialFocus?: React.RefObject<HTMLElement | null> | "first" | "container" | false;
69
+ /** Return focus to the pre-open active element on deactivate. */
70
+ restoreFocus?: boolean;
71
+ }
72
+ export declare function useFocusTrap({ active, container, initialFocus, restoreFocus }: FocusTrapOptions): void;
73
+ export interface UseOverlayOptions {
74
+ open: boolean;
75
+ /** Escape behavior; return false to block (e.g. shake). Default closes via onEscape. */
76
+ onEscape?: () => boolean | void;
77
+ /** Modal: trap focus, lock scroll, inert the background. Default true. */
78
+ modal?: boolean;
79
+ initialFocus?: React.RefObject<HTMLElement | null> | "first" | "container" | false;
80
+ restoreFocus?: boolean;
81
+ }
82
+ /**
83
+ * The composite hook for modal overlays: stack registration, topmost-only
84
+ * Escape, scroll locking, background inertness, focus trapping, initial
85
+ * focus and focus restoration. Returns props to spread on the portaled
86
+ * wrapper element.
87
+ */
88
+ export declare function useOverlay({ open, onEscape, modal, initialFocus, restoreFocus, }: UseOverlayOptions): {
89
+ ref: (el: HTMLElement | null) => void;
90
+ element: HTMLElement | null;
91
+ top: () => boolean;
92
+ };
93
+ export declare const overlayPropsMeta: PropsMeta;