@skiddph/prui 0.2.1 → 0.4.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.
package/dist/core.js CHANGED
@@ -1,64 +1,67 @@
1
1
  import { cn as e } from "./core/cn.js";
2
- import { Button as a, buttonPropsMeta as p } from "./core/button.js";
3
- import { Input as l, Textarea as i, inputPropsMeta as d, textareaPropsMeta as n } from "./core/input.js";
4
- import { Label as c, Separator as m, labelPropsMeta as P, separatorPropsMeta as f } from "./core/label.js";
5
- import { Select as g, SelectContent as C, SelectItem as b, SelectTrigger as D, SelectValue as T, selectPropsMeta as S } from "./core/select.js";
6
- import { Switch as w, switchPropsMeta as A } from "./core/switch.js";
7
- import { Tabs as v, TabsContent as B, TabsList as F, TabsTrigger as H, tabsPropsMeta as I } from "./core/tabs.js";
8
- import { Avatar as V, Badge as j, Card as k, CardContent as q, CardDescription as y, CardFooter as z, CardHeader as E, CardTitle as G, avatarPropsMeta as J, badgePropsMeta as K, cardPropsMeta as N } from "./core/card.js";
9
- import { Dialog as Q, DialogContent as R, DialogDescription as U, DialogFooter as W, DialogHeader as X, DialogTitle as Y, dialogPropsMeta as Z } from "./core/dialog.js";
10
- import { Dropdown as $, dropdownPropsMeta as oo } from "./core/dropdown.js";
11
- import { ScrollArea as eo, scrollAreaPropsMeta as to } from "./core/scroll-area.js";
12
- import { Modal as po, confirmModal as so, confirmModalPropsMeta as lo, modalPropsMeta as io } from "./core/modal.js";
2
+ import { resolveSurface as a, withSurface as p } from "./core/surface.js";
3
+ import { Button as l, buttonPropsMeta as i } from "./core/button.js";
4
+ import { Input as n, Textarea as M, inputPropsMeta as c, textareaPropsMeta as f } from "./core/input.js";
5
+ import { Label as P, Separator as x, labelPropsMeta as g, separatorPropsMeta as C } from "./core/label.js";
6
+ import { Select as b, SelectContent as D, SelectItem as T, SelectTrigger as u, SelectValue as w, selectPropsMeta as h } from "./core/select.js";
7
+ import { Switch as A, switchPropsMeta as B } from "./core/switch.js";
8
+ import { Tabs as H, TabsContent as I, TabsList as L, TabsTrigger as V, tabsPropsMeta as j } from "./core/tabs.js";
9
+ import { Avatar as q, Badge as y, Card as z, CardContent as E, CardDescription as G, CardFooter as J, CardHeader as K, CardTitle as N, avatarPropsMeta as O, badgePropsMeta as Q, cardPropsMeta as R } from "./core/card.js";
10
+ import { Dialog as W, DialogContent as X, DialogDescription as Y, DialogFooter as Z, DialogHeader as _, DialogTitle as $, dialogPropsMeta as rr } from "./core/dialog.js";
11
+ import { Dropdown as er, dropdownPropsMeta as tr } from "./core/dropdown.js";
12
+ import { ScrollArea as pr, scrollAreaPropsMeta as sr } from "./core/scroll-area.js";
13
+ import { Modal as ir, confirmModal as dr, confirmModalPropsMeta as nr, modalPropsMeta as Mr } from "./core/modal.js";
13
14
  export {
14
- V as Avatar,
15
- j as Badge,
16
- a as Button,
17
- k as Card,
18
- q as CardContent,
19
- y as CardDescription,
20
- z as CardFooter,
21
- E as CardHeader,
22
- G as CardTitle,
23
- Q as Dialog,
24
- R as DialogContent,
25
- U as DialogDescription,
26
- W as DialogFooter,
27
- X as DialogHeader,
28
- Y as DialogTitle,
29
- $ as Dropdown,
30
- l as Input,
31
- c as Label,
32
- po as Modal,
33
- eo as ScrollArea,
34
- g as Select,
35
- C as SelectContent,
36
- b as SelectItem,
37
- D as SelectTrigger,
38
- T as SelectValue,
39
- m as Separator,
40
- w as Switch,
41
- v as Tabs,
42
- B as TabsContent,
43
- F as TabsList,
44
- H as TabsTrigger,
45
- i as Textarea,
46
- J as avatarPropsMeta,
47
- K as badgePropsMeta,
48
- p as buttonPropsMeta,
49
- N as cardPropsMeta,
15
+ q as Avatar,
16
+ y as Badge,
17
+ l as Button,
18
+ z as Card,
19
+ E as CardContent,
20
+ G as CardDescription,
21
+ J as CardFooter,
22
+ K as CardHeader,
23
+ N as CardTitle,
24
+ W as Dialog,
25
+ X as DialogContent,
26
+ Y as DialogDescription,
27
+ Z as DialogFooter,
28
+ _ as DialogHeader,
29
+ $ as DialogTitle,
30
+ er as Dropdown,
31
+ n as Input,
32
+ P as Label,
33
+ ir as Modal,
34
+ pr as ScrollArea,
35
+ b as Select,
36
+ D as SelectContent,
37
+ T as SelectItem,
38
+ u as SelectTrigger,
39
+ w as SelectValue,
40
+ x as Separator,
41
+ A as Switch,
42
+ H as Tabs,
43
+ I as TabsContent,
44
+ L as TabsList,
45
+ V as TabsTrigger,
46
+ M as Textarea,
47
+ O as avatarPropsMeta,
48
+ Q as badgePropsMeta,
49
+ i as buttonPropsMeta,
50
+ R as cardPropsMeta,
50
51
  e as cn,
51
- so as confirmModal,
52
- lo as confirmModalPropsMeta,
53
- Z as dialogPropsMeta,
54
- oo as dropdownPropsMeta,
55
- d as inputPropsMeta,
56
- P as labelPropsMeta,
57
- io as modalPropsMeta,
58
- to as scrollAreaPropsMeta,
59
- S as selectPropsMeta,
60
- f as separatorPropsMeta,
61
- A as switchPropsMeta,
62
- I as tabsPropsMeta,
63
- n as textareaPropsMeta
52
+ dr as confirmModal,
53
+ nr as confirmModalPropsMeta,
54
+ rr as dialogPropsMeta,
55
+ tr as dropdownPropsMeta,
56
+ c as inputPropsMeta,
57
+ g as labelPropsMeta,
58
+ Mr as modalPropsMeta,
59
+ a as resolveSurface,
60
+ sr as scrollAreaPropsMeta,
61
+ h as selectPropsMeta,
62
+ C as separatorPropsMeta,
63
+ B as switchPropsMeta,
64
+ j as tabsPropsMeta,
65
+ f as textareaPropsMeta,
66
+ p as withSurface
64
67
  };
@@ -0,0 +1,78 @@
1
+ import { ReactNode } from 'react';
2
+ import { PropsMeta } from '../core/props-meta';
3
+ import { FormMessage, FormGroupColumns, SafeParseSchema } from './types';
4
+ import * as React from "react";
5
+ export interface FormProps {
6
+ /** Registry id referenced by <FormButton form={...}>. */
7
+ id: string;
8
+ children?: ReactNode;
9
+ /** Optional zod-shaped schema. Any object with safeParse works. */
10
+ schema?: SafeParseSchema;
11
+ initialValues?: Record<string, unknown>;
12
+ onSubmit?: (values: Record<string, unknown>) => void | Promise<void>;
13
+ onReset?: () => void;
14
+ /** Validate as the user types; off means validate on blur and submit. */
15
+ validateOnChange?: boolean;
16
+ className?: string;
17
+ }
18
+ export declare function Form({ id, children, schema, initialValues, onSubmit, onReset, validateOnChange, className }: FormProps): React.JSX.Element;
19
+ export interface FormGroupProps {
20
+ label?: string;
21
+ description?: string;
22
+ /** Columns per breakpoint; every entry is 1 to 4. */
23
+ columns?: number | FormGroupColumns;
24
+ gap?: "sm" | "md" | "lg";
25
+ children?: ReactNode;
26
+ className?: string;
27
+ }
28
+ export declare function FormGroup({ label, description, columns, gap, children, className }: FormGroupProps): React.JSX.Element;
29
+ export interface FormInputProps {
30
+ name: string;
31
+ label?: string;
32
+ type?: "text" | "email" | "password" | "number" | "tel" | "url" | "search" | "date" | "time" | "datetime-local" | "textarea" | "select" | "switch" | "checkbox";
33
+ placeholder?: string;
34
+ required?: boolean;
35
+ disabled?: boolean;
36
+ readOnly?: boolean;
37
+ autoFocus?: boolean;
38
+ minLength?: number;
39
+ maxLength?: number;
40
+ min?: number;
41
+ max?: number;
42
+ step?: number;
43
+ pattern?: RegExp;
44
+ patternMessage?: string;
45
+ /** Select/switch/checkbox options. */
46
+ options?: {
47
+ label: string;
48
+ value: string;
49
+ disabled?: boolean;
50
+ }[];
51
+ rows?: number;
52
+ /** Static helper line under the control. */
53
+ help?: ReactNode;
54
+ /** Caller message under the control; validation errors take precedence. */
55
+ message?: FormMessage;
56
+ validate?: (value: unknown, values: Record<string, unknown>) => string | null;
57
+ onChange?: (value: unknown) => void;
58
+ className?: string;
59
+ }
60
+ export declare function FormInput({ name, label, type, placeholder, required, disabled, readOnly, autoFocus, minLength, maxLength, min, max, step, pattern, patternMessage, options, rows, help, message, validate, onChange, className, }: FormInputProps): React.JSX.Element;
61
+ export interface FormButtonProps {
62
+ /** The id of the <Form> this button drives. */
63
+ form: string;
64
+ action?: "submit" | "reset" | "clear";
65
+ /** Disable rule: while the referenced form is invalid or untouched. */
66
+ disableWhen?: "invalid" | "pristine" | "never";
67
+ variant?: "primary" | "default" | "ghost" | "danger" | "outline" | "soft" | "link";
68
+ size?: "sm" | "md" | "lg";
69
+ disabled?: boolean;
70
+ children?: ReactNode;
71
+ onClick?: () => void;
72
+ className?: string;
73
+ }
74
+ export declare function FormButton({ form, action, disableWhen, variant, size, disabled, children, onClick, className }: FormButtonProps): React.JSX.Element;
75
+ export declare const formPropsMeta: PropsMeta;
76
+ export declare const formGroupPropsMeta: PropsMeta;
77
+ export declare const formInputPropsMeta: PropsMeta;
78
+ export declare const formButtonPropsMeta: PropsMeta;
@@ -0,0 +1,8 @@
1
+ import { FormApi } from './types';
2
+ /** Higher-order forms: config-driven inputs, responsive groups, zod-optional
3
+ * validation, and buttons that drive a form from outside its scope. */
4
+ export { Form, FormGroup, FormInput, FormButton, formPropsMeta, formGroupPropsMeta, formInputPropsMeta, formButtonPropsMeta, type FormProps, type FormGroupProps, type FormInputProps, type FormButtonProps, } from './form';
5
+ export { registerForm, getFormApi, formIds, } from './registry';
6
+ /** Resolve a mounted form's API by id: submit, reset, clear, read state. */
7
+ export declare function useFormApi(id: string): FormApi | undefined;
8
+ export type { SafeParseSchema, FormFieldRule, FormSnapshot, FormApi, FormGroupColumns, FormMessage, } from './types';
@@ -0,0 +1,4 @@
1
+ import { FormApi } from './types';
2
+ export declare function registerForm(api: FormApi): () => void;
3
+ export declare function getFormApi(id: string): FormApi | undefined;
4
+ export declare function formIds(): string[];
@@ -0,0 +1,55 @@
1
+ import { ReactNode } from 'react';
2
+ /**
3
+ * A validator shaped like zod's schema. Structurally typed so any version
4
+ * of zod (or anything with the same safeParse shape) works without this
5
+ * package depending on it.
6
+ */
7
+ export interface SafeParseSchema<T = Record<string, unknown>> {
8
+ safeParse(data: unknown): {
9
+ success: boolean;
10
+ data?: T | unknown;
11
+ error?: {
12
+ issues: ReadonlyArray<{
13
+ path: ReadonlyArray<string | number | symbol>;
14
+ message: string;
15
+ }>;
16
+ };
17
+ };
18
+ }
19
+ export interface FormFieldRule {
20
+ label: string;
21
+ required?: boolean;
22
+ validate?: (value: unknown, values: Record<string, unknown>) => string | null;
23
+ min?: number;
24
+ max?: number;
25
+ minLength?: number;
26
+ maxLength?: number;
27
+ pattern?: RegExp;
28
+ patternMessage?: string;
29
+ }
30
+ export interface FormSnapshot {
31
+ isValid: boolean;
32
+ isDirty: boolean;
33
+ errorCount: number;
34
+ values: Record<string, unknown>;
35
+ }
36
+ export interface FormApi {
37
+ readonly id: string;
38
+ submit(): Promise<boolean>;
39
+ reset(): void;
40
+ clear(): void;
41
+ setField(name: string, value: unknown): void;
42
+ getValue(name: string): unknown;
43
+ getState(): FormSnapshot;
44
+ subscribe(listener: (snapshot: FormSnapshot) => void): () => void;
45
+ }
46
+ export interface FormGroupColumns {
47
+ base?: 1 | 2 | 3 | 4;
48
+ sm?: 1 | 2 | 3 | 4;
49
+ md?: 1 | 2 | 3 | 4;
50
+ lg?: 1 | 2 | 3 | 4;
51
+ }
52
+ export interface FormMessage {
53
+ message: ReactNode;
54
+ type?: "info" | "warning" | "success";
55
+ }
package/dist/forms.js ADDED
@@ -0,0 +1,364 @@
1
+ import { jsx as v, jsxs as O } from "react/jsx-runtime";
2
+ import * as m from "react";
3
+ import { Button as L } from "./core/button.js";
4
+ import { Textarea as q, Input as z } from "./core/input.js";
5
+ import { Label as H } from "./core/label.js";
6
+ import { Select as J } from "./core/select.js";
7
+ import { Switch as K } from "./core/switch.js";
8
+ import { cn as k } from "./core/cn.js";
9
+ const M = /* @__PURE__ */ new Map();
10
+ function Q(t) {
11
+ return M.set(t.id, t), () => {
12
+ M.get(t.id) === t && M.delete(t.id);
13
+ };
14
+ }
15
+ function R(t) {
16
+ return M.get(t);
17
+ }
18
+ function at() {
19
+ return [...M.keys()];
20
+ }
21
+ const G = m.createContext(null);
22
+ function U() {
23
+ const t = m.useContext(G);
24
+ if (!t) throw new Error("Form components must be rendered inside <Form>.");
25
+ return t;
26
+ }
27
+ function _(t) {
28
+ return t == null || t === "" || Array.isArray(t) && t.length === 0;
29
+ }
30
+ function D(t, r, o) {
31
+ var i;
32
+ return r.required && _(t) ? `${r.label} is required.` : _(t) ? null : r.minLength !== void 0 && String(t).length < r.minLength ? `${r.label} must be at least ${r.minLength} characters.` : r.maxLength !== void 0 && String(t).length > r.maxLength ? `${r.label} must be at most ${r.maxLength} characters.` : r.min !== void 0 && typeof t == "number" && t < r.min ? `${r.label} must be ${r.min} or more.` : r.max !== void 0 && typeof t == "number" && t > r.max ? `${r.label} must be ${r.max} or less.` : r.pattern && !r.pattern.test(String(t)) ? r.patternMessage ?? `${r.label} has an invalid format.` : ((i = r.validate) == null ? void 0 : i.call(r, t, o)) ?? null;
33
+ }
34
+ function ut({ id: t, children: r, schema: o, initialValues: i = {}, onSubmit: p, onReset: g, validateOnChange: a = !0, className: h }) {
35
+ const [s, d] = m.useState({ ...i }), [w, b] = m.useState({}), [T, y] = m.useState(null), S = m.useRef(/* @__PURE__ */ new Map()), P = m.useRef(/* @__PURE__ */ new Set()), C = m.useRef({ ...i }), I = () => {
36
+ let e = !0;
37
+ for (const [l, u] of S.current)
38
+ if (D(s[l], u, s) !== null) {
39
+ e = !1;
40
+ break;
41
+ }
42
+ e && o && (o.safeParse(s).success || (e = !1));
43
+ const n = Object.keys(s).some((l) => s[l] !== C.current[l]) || Object.keys(C.current).length !== Object.keys(s).length;
44
+ return { isValid: e, isDirty: n, errorCount: Object.keys(w).length, values: { ...s } };
45
+ }, V = (e) => {
46
+ var l;
47
+ const n = {};
48
+ for (const [u, f] of S.current) {
49
+ const x = D(e[u], f, e);
50
+ x && (n[u] = x);
51
+ }
52
+ if (o) {
53
+ const u = o.safeParse(e);
54
+ if (!u.success)
55
+ for (const f of ((l = u.error) == null ? void 0 : l.issues) ?? []) {
56
+ const x = String(f.path[0] ?? "");
57
+ x && !n[x] ? n[x] = f.message : x || (n.__form__ = f.message);
58
+ }
59
+ }
60
+ return n;
61
+ }, A = async () => {
62
+ const e = V(s);
63
+ if (b(e), Object.keys(e).length > 0)
64
+ return y(null), !1;
65
+ y(null);
66
+ try {
67
+ return await (p == null ? void 0 : p({ ...s })), !0;
68
+ } catch (n) {
69
+ return y(n instanceof Error ? n.message : String(n)), !1;
70
+ }
71
+ }, j = () => {
72
+ d({ ...C.current }), b({}), y(null), g == null || g();
73
+ }, B = () => {
74
+ d({}), b({}), y(null);
75
+ }, F = (e, n) => d((l) => ({ ...l, [e]: n })), E = () => {
76
+ const e = I();
77
+ for (const n of P.current) n(e);
78
+ }, $ = m.useMemo(
79
+ () => ({
80
+ id: t,
81
+ submit: async () => A(),
82
+ reset: j,
83
+ clear: B,
84
+ setField: F,
85
+ getValue: (e) => s[e],
86
+ getState: () => I(),
87
+ subscribe: (e) => (P.current.add(e), () => P.current.delete(e))
88
+ }),
89
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- the api closes over the latest render state
90
+ [t, s, w, o, p, g]
91
+ );
92
+ m.useEffect(() => Q($), [$]), m.useEffect(() => {
93
+ E();
94
+ });
95
+ const N = {
96
+ id: t,
97
+ getValue: (e) => s[e],
98
+ setValue: (e, n) => {
99
+ if (d((l) => ({ ...l, [e]: n })), a) {
100
+ const l = S.current.get(e);
101
+ if (l) {
102
+ const u = D(n, l, { ...s, [e]: n });
103
+ b((f) => {
104
+ if (u) return { ...f, [e]: u };
105
+ if (!(e in f)) return f;
106
+ const x = { ...f };
107
+ return delete x[e], x;
108
+ });
109
+ }
110
+ }
111
+ y(null);
112
+ },
113
+ registerField: (e, n) => S.current.set(e, n),
114
+ unregisterField: (e) => S.current.delete(e),
115
+ errorFor: (e) => w[e],
116
+ markTouched: (e) => {
117
+ const n = S.current.get(e);
118
+ if (n && a) {
119
+ const l = D(s[e], n, s);
120
+ b((u) => {
121
+ if (l) return { ...u, [e]: l };
122
+ if (!(e in u)) return u;
123
+ const f = { ...u };
124
+ return delete f[e], f;
125
+ });
126
+ }
127
+ },
128
+ formError: T
129
+ };
130
+ return /* @__PURE__ */ v(G.Provider, { value: N, children: /* @__PURE__ */ v(
131
+ "form",
132
+ {
133
+ id: t,
134
+ className: k("prui-form flex flex-col gap-4", h),
135
+ noValidate: !0,
136
+ onSubmit: (e) => {
137
+ e.preventDefault(), A();
138
+ },
139
+ onReset: (e) => {
140
+ e.preventDefault(), j();
141
+ },
142
+ children: r
143
+ }
144
+ ) });
145
+ }
146
+ const W = { 1: "grid-cols-1", 2: "grid-cols-2", 3: "grid-cols-3", 4: "grid-cols-4" }, X = { 2: "sm:grid-cols-2", 3: "sm:grid-cols-3", 4: "sm:grid-cols-4" }, Y = { 2: "md:grid-cols-2", 3: "md:grid-cols-3", 4: "md:grid-cols-4" }, Z = { 2: "lg:grid-cols-2", 3: "lg:grid-cols-3", 4: "lg:grid-cols-4" };
147
+ function ct({ label: t, description: r, columns: o = 1, gap: i = "md", children: p, className: g }) {
148
+ const a = typeof o == "number" ? { base: o } : o, h = a.base ?? 1, s = [
149
+ "grid",
150
+ W[h],
151
+ a.sm && a.sm > 1 ? X[a.sm] : null,
152
+ a.md && a.md > 1 ? Y[a.md] : null,
153
+ a.lg && a.lg > 1 ? Z[a.lg] : null
154
+ ].filter(Boolean), d = i === "sm" ? "gap-x-2 gap-y-3" : i === "lg" ? "gap-x-6 gap-y-4" : "gap-x-4 gap-y-3";
155
+ return /* @__PURE__ */ O(
156
+ "div",
157
+ {
158
+ role: "group",
159
+ className: k("prui-form-group rounded-[var(--prui-radius)] border border-[var(--prui-line)] bg-[var(--prui-surface)] p-4", g),
160
+ children: [
161
+ t ? /* @__PURE__ */ v("div", { className: "text-sm font-semibold text-[var(--prui-fg)]", children: t }) : null,
162
+ r ? /* @__PURE__ */ v("div", { className: "mb-1 text-xs text-[var(--prui-dim)]", children: r }) : null,
163
+ /* @__PURE__ */ v("div", { className: k(...s, d), children: p })
164
+ ]
165
+ }
166
+ );
167
+ }
168
+ function dt({
169
+ name: t,
170
+ label: r = t,
171
+ type: o = "text",
172
+ placeholder: i,
173
+ required: p,
174
+ disabled: g,
175
+ readOnly: a,
176
+ autoFocus: h,
177
+ minLength: s,
178
+ maxLength: d,
179
+ min: w,
180
+ max: b,
181
+ step: T,
182
+ pattern: y,
183
+ patternMessage: S,
184
+ options: P,
185
+ rows: C = 3,
186
+ help: I,
187
+ message: V,
188
+ validate: A,
189
+ onChange: j,
190
+ className: B
191
+ }) {
192
+ const F = U();
193
+ m.useEffect(() => (F.registerField(t, { label: r, required: p, validate: A, minLength: s, maxLength: d, min: w, max: b, pattern: y, patternMessage: S }), () => F.unregisterField(t)), [t]);
194
+ const E = F.getValue(t), $ = F.errorFor(t), N = (c) => {
195
+ F.setValue(t, c), j == null || j(c);
196
+ }, e = `prui-field-${F.id}-${t}`, n = `${e}-msg`, l = $ ?? (V == null ? void 0 : V.message) ?? I, u = $ ? "error" : V ? V.type ?? "info" : "help", f = {
197
+ error: "text-[var(--prui-danger)]",
198
+ warning: "text-[var(--prui-warn)]",
199
+ info: "text-[var(--prui-dim)]",
200
+ success: "text-[var(--prui-ok)]",
201
+ help: "text-[var(--prui-dim)]"
202
+ }[u], x = o === "textarea" ? /* @__PURE__ */ v(
203
+ q,
204
+ {
205
+ id: e,
206
+ rows: C,
207
+ placeholder: i,
208
+ disabled: g,
209
+ readOnly: a,
210
+ autoFocus: h,
211
+ minLength: s,
212
+ maxLength: d,
213
+ className: k("w-full", B),
214
+ value: String(E ?? ""),
215
+ onChange: (c) => N(c.target.value),
216
+ onBlur: () => F.markTouched(t),
217
+ "aria-invalid": $ ? !0 : void 0,
218
+ "aria-describedby": l ? n : void 0
219
+ }
220
+ ) : o === "select" ? /* @__PURE__ */ v(
221
+ J,
222
+ {
223
+ id: e,
224
+ className: k("w-full", B),
225
+ options: (P ?? []).map((c) => ({ label: c.label, value: c.value, disabled: c.disabled })),
226
+ placeholder: i,
227
+ disabled: g,
228
+ value: String(E ?? ""),
229
+ onChange: (c) => N(c),
230
+ "aria-label": r
231
+ }
232
+ ) : o === "switch" || o === "checkbox" ? /* @__PURE__ */ v(
233
+ K,
234
+ {
235
+ id: e,
236
+ checked: !!E,
237
+ disabled: g,
238
+ onChange: (c) => N(c),
239
+ "aria-label": r
240
+ }
241
+ ) : /* @__PURE__ */ v(
242
+ z,
243
+ {
244
+ id: e,
245
+ type: o,
246
+ placeholder: i,
247
+ disabled: g,
248
+ readOnly: a,
249
+ autoFocus: h,
250
+ minLength: s,
251
+ maxLength: d,
252
+ min: w,
253
+ max: b,
254
+ step: T,
255
+ pattern: y == null ? void 0 : y.source,
256
+ className: k("w-full", B),
257
+ value: String(E ?? ""),
258
+ onChange: (c) => N(o === "number" ? c.target.value === "" ? "" : Number(c.target.value) : c.target.value),
259
+ onBlur: () => F.markTouched(t),
260
+ "aria-invalid": $ ? !0 : void 0,
261
+ "aria-describedby": l ? n : void 0
262
+ }
263
+ );
264
+ return /* @__PURE__ */ O("div", { className: k("prui-form-input flex flex-col gap-1.5", B), "data-testid": `form-input-${t}`, children: [
265
+ r ? /* @__PURE__ */ O(H, { htmlFor: e, children: [
266
+ r,
267
+ p ? /* @__PURE__ */ v("span", { className: "ml-0.5 text-[var(--prui-danger)]", children: "*" }) : null
268
+ ] }) : null,
269
+ x,
270
+ l ? /* @__PURE__ */ v("p", { id: n, className: k("text-xs", f), role: $ ? "alert" : void 0, children: l }) : null
271
+ ] });
272
+ }
273
+ function tt(t) {
274
+ const [r, o] = m.useState(() => {
275
+ var i;
276
+ return ((i = R(t)) == null ? void 0 : i.getState()) ?? null;
277
+ });
278
+ return m.useEffect(() => {
279
+ const i = R(t);
280
+ if (!i) {
281
+ o(null);
282
+ return;
283
+ }
284
+ return o(i.getState()), i.subscribe((p) => o(p));
285
+ }, [t]), r;
286
+ }
287
+ function ft({ form: t, action: r = "submit", disableWhen: o = "never", variant: i, size: p, disabled: g, children: a, onClick: h, className: s }) {
288
+ const d = tt(t), w = g || (o === "invalid" && d ? !d.isValid : !1) || (o === "pristine" && d ? !d.isDirty : !1);
289
+ return /* @__PURE__ */ v(
290
+ L,
291
+ {
292
+ type: "button",
293
+ variant: i ?? "default",
294
+ size: p,
295
+ disabled: w,
296
+ className: s,
297
+ "data-testid": `form-button-${t}-${r}`,
298
+ onClick: () => {
299
+ const b = R(t);
300
+ if (!b) {
301
+ console.warn(`[prui] FormButton: no <Form id="${t}"> is mounted.`);
302
+ return;
303
+ }
304
+ r === "submit" ? b.submit() : r === "reset" ? b.reset() : r === "clear" && b.clear(), h == null || h();
305
+ },
306
+ children: a
307
+ }
308
+ );
309
+ }
310
+ const mt = {
311
+ name: "Form",
312
+ props: [
313
+ { name: "id", type: "string", default: null, control: "text" },
314
+ { name: "schema", type: "SafeParseSchema (zod-compatible)", default: "undefined", control: "none" },
315
+ { name: "initialValues", type: "Record<string, unknown>", default: "{}", control: "object" },
316
+ { name: "onSubmit", type: "(values) => void | Promise<void>", default: null, control: "none" },
317
+ { name: "validateOnChange", type: "boolean", default: "true", control: "boolean" }
318
+ ]
319
+ }, pt = {
320
+ name: "FormGroup",
321
+ props: [
322
+ { name: "label", type: "string", default: null, control: "text" },
323
+ { name: "description", type: "string", default: null, control: "text" },
324
+ { name: "columns", type: "number | { base?, sm?, md?, lg? }", default: "1", control: "number" },
325
+ { name: "gap", type: "'sm' | 'md' | 'lg'", default: "'md'", control: "select", options: ["sm", "md", "lg"] }
326
+ ]
327
+ }, gt = {
328
+ name: "FormInput",
329
+ props: [
330
+ { name: "name", type: "string", default: null, control: "text" },
331
+ { name: "label", type: "string", default: "name", control: "text" },
332
+ { name: "type", type: "text | email | password | number | tel | url | search | date | time | datetime-local | textarea | select | switch | checkbox", default: "'text'", control: "select", options: ["text", "email", "password", "number", "date", "textarea", "select", "switch"] },
333
+ { name: "required", type: "boolean", default: "false", control: "boolean" },
334
+ { name: "help", type: "ReactNode", default: null, control: "text" },
335
+ { name: "message", type: "{ message, type: 'info' | 'warning' | 'success' }", default: null, control: "object" },
336
+ { name: "options", type: "{ label, value }[]", default: "select/switch only", control: "object" },
337
+ { name: "validate", type: "(value, values) => string | null", default: null, control: "none" }
338
+ ]
339
+ }, bt = {
340
+ name: "FormButton",
341
+ props: [
342
+ { name: "form", type: "string (id of the target <Form>)", default: null, control: "text" },
343
+ { name: "action", type: "'submit' | 'reset' | 'clear'", default: "'submit'", control: "select", options: ["submit", "reset", "clear"] },
344
+ { name: "disableWhen", type: "'invalid' | 'pristine' | 'never'", default: "'never'", control: "select", options: ["invalid", "pristine", "never"] },
345
+ { name: "variant", type: "ButtonVariant", default: "'default'", control: "select", options: ["primary", "default", "ghost", "danger", "outline", "soft", "link"] }
346
+ ]
347
+ };
348
+ function xt(t) {
349
+ return R(t);
350
+ }
351
+ export {
352
+ ut as Form,
353
+ ft as FormButton,
354
+ ct as FormGroup,
355
+ dt as FormInput,
356
+ bt as formButtonPropsMeta,
357
+ pt as formGroupPropsMeta,
358
+ at as formIds,
359
+ gt as formInputPropsMeta,
360
+ mt as formPropsMeta,
361
+ R as getFormApi,
362
+ Q as registerForm,
363
+ xt as useFormApi
364
+ };
package/dist/index.d.ts CHANGED
@@ -3,3 +3,4 @@ export * from './data-table';
3
3
  export * from './app';
4
4
  export * from './pages';
5
5
  export * from './theme';
6
+ export { FormGroup, FormInput, FormButton, useFormApi, registerForm, getFormApi, formGroupPropsMeta, formInputPropsMeta, formButtonPropsMeta, type FormGroupProps, type FormInputProps, type FormButtonProps, type SafeParseSchema, } from './forms';