@skiddph/prui 0.3.0 → 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/app.js +1 -1
- package/dist/chunks/{auth-shell-DsAwGqf8.js → auth-shell-DWnZhpVf.js} +123 -122
- package/dist/core/button.d.ts +7 -2
- package/dist/core/button.js +45 -32
- package/dist/core/card.d.ts +9 -2
- package/dist/core/card.js +73 -58
- package/dist/core/index.d.ts +1 -0
- package/dist/core/input.d.ts +14 -1
- package/dist/core/input.js +32 -17
- package/dist/core/surface.d.ts +40 -0
- package/dist/core/surface.js +37 -0
- package/dist/core.js +63 -60
- package/dist/forms/form.d.ts +78 -0
- package/dist/forms/index.d.ts +8 -0
- package/dist/forms/registry.d.ts +4 -0
- package/dist/forms/types.d.ts +55 -0
- package/dist/forms.js +364 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +150 -131
- package/dist/prui-utilities.css +1 -1
- package/dist/prui.css +37 -1
- package/dist/theme/base.css +36 -0
- package/dist/theme/index.d.ts +51 -0
- package/dist/theme.js +110 -35
- package/package.json +5 -1
package/dist/core.js
CHANGED
|
@@ -1,64 +1,67 @@
|
|
|
1
1
|
import { cn as e } from "./core/cn.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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,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';
|