@modern-admin/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action-guard.d.ts +13 -0
- package/dist/action-guard.d.ts.map +1 -0
- package/dist/action-guard.js +15 -0
- package/dist/action-guard.js.map +1 -0
- package/dist/action-menu.d.ts +17 -0
- package/dist/action-menu.d.ts.map +1 -0
- package/dist/action-menu.jsx +80 -0
- package/dist/action-menu.jsx.map +1 -0
- package/dist/admin-app.d.ts +23 -0
- package/dist/admin-app.d.ts.map +1 -0
- package/dist/admin-app.jsx +407 -0
- package/dist/admin-app.jsx.map +1 -0
- package/dist/admin-router.d.ts +29 -0
- package/dist/admin-router.d.ts.map +1 -0
- package/dist/admin-router.jsx +215 -0
- package/dist/admin-router.jsx.map +1 -0
- package/dist/breadcrumbs.d.ts +17 -0
- package/dist/breadcrumbs.d.ts.map +1 -0
- package/dist/breadcrumbs.jsx +40 -0
- package/dist/breadcrumbs.jsx.map +1 -0
- package/dist/client.d.ts +526 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +582 -0
- package/dist/client.js.map +1 -0
- package/dist/component-loader.d.ts +10 -0
- package/dist/component-loader.d.ts.map +1 -0
- package/dist/component-loader.js +23 -0
- package/dist/component-loader.js.map +1 -0
- package/dist/components/ai-assistant-widget.d.ts +3 -0
- package/dist/components/ai-assistant-widget.d.ts.map +1 -0
- package/dist/components/ai-assistant-widget.jsx +390 -0
- package/dist/components/ai-assistant-widget.jsx.map +1 -0
- package/dist/components/ai-fill-dialog.d.ts +9 -0
- package/dist/components/ai-fill-dialog.d.ts.map +1 -0
- package/dist/components/ai-fill-dialog.jsx +105 -0
- package/dist/components/ai-fill-dialog.jsx.map +1 -0
- package/dist/components/chart-builder-dialog.d.ts +10 -0
- package/dist/components/chart-builder-dialog.d.ts.map +1 -0
- package/dist/components/chart-builder-dialog.jsx +433 -0
- package/dist/components/chart-builder-dialog.jsx.map +1 -0
- package/dist/components/chart-widget.d.ts +12 -0
- package/dist/components/chart-widget.d.ts.map +1 -0
- package/dist/components/chart-widget.jsx +365 -0
- package/dist/components/chart-widget.jsx.map +1 -0
- package/dist/components/global-search-dialog.d.ts +7 -0
- package/dist/components/global-search-dialog.d.ts.map +1 -0
- package/dist/components/global-search-dialog.jsx +187 -0
- package/dist/components/global-search-dialog.jsx.map +1 -0
- package/dist/components/group-settings-dialog.d.ts +13 -0
- package/dist/components/group-settings-dialog.d.ts.map +1 -0
- package/dist/components/group-settings-dialog.jsx +53 -0
- package/dist/components/group-settings-dialog.jsx.map +1 -0
- package/dist/components/move-chart-dialog.d.ts +18 -0
- package/dist/components/move-chart-dialog.d.ts.map +1 -0
- package/dist/components/move-chart-dialog.jsx +68 -0
- package/dist/components/move-chart-dialog.jsx.map +1 -0
- package/dist/components/reference-multi-table-dialog.d.ts +12 -0
- package/dist/components/reference-multi-table-dialog.d.ts.map +1 -0
- package/dist/components/reference-multi-table-dialog.jsx +126 -0
- package/dist/components/reference-multi-table-dialog.jsx.map +1 -0
- package/dist/components/related-records-tabs.d.ts +8 -0
- package/dist/components/related-records-tabs.d.ts.map +1 -0
- package/dist/components/related-records-tabs.jsx +75 -0
- package/dist/components/related-records-tabs.jsx.map +1 -0
- package/dist/components/revisions-button.d.ts +7 -0
- package/dist/components/revisions-button.d.ts.map +1 -0
- package/dist/components/revisions-button.jsx +152 -0
- package/dist/components/revisions-button.jsx.map +1 -0
- package/dist/components/wizard-form.d.ts +43 -0
- package/dist/components/wizard-form.d.ts.map +1 -0
- package/dist/components/wizard-form.jsx +136 -0
- package/dist/components/wizard-form.jsx.map +1 -0
- package/dist/dashboard/time-series.d.ts +20 -0
- package/dist/dashboard/time-series.d.ts.map +1 -0
- package/dist/dashboard/time-series.js +108 -0
- package/dist/dashboard/time-series.js.map +1 -0
- package/dist/dialogs.d.ts +35 -0
- package/dist/dialogs.d.ts.map +1 -0
- package/dist/dialogs.jsx +152 -0
- package/dist/dialogs.jsx.map +1 -0
- package/dist/export.d.ts +39 -0
- package/dist/export.d.ts.map +1 -0
- package/dist/export.js +114 -0
- package/dist/export.js.map +1 -0
- package/dist/extension-registry.d.ts +122 -0
- package/dist/extension-registry.d.ts.map +1 -0
- package/dist/extension-registry.js +93 -0
- package/dist/extension-registry.js.map +1 -0
- package/dist/header-controls.d.ts +4 -0
- package/dist/header-controls.d.ts.map +1 -0
- package/dist/header-controls.jsx +70 -0
- package/dist/header-controls.jsx.map +1 -0
- package/dist/hooks.d.ts +104 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +374 -0
- package/dist/hooks.js.map +1 -0
- package/dist/hotkey-help.d.ts +3 -0
- package/dist/hotkey-help.d.ts.map +1 -0
- package/dist/hotkey-help.jsx +32 -0
- package/dist/hotkey-help.jsx.map +1 -0
- package/dist/hotkey-registry.d.ts +18 -0
- package/dist/hotkey-registry.d.ts.map +1 -0
- package/dist/hotkey-registry.jsx +34 -0
- package/dist/hotkey-registry.jsx.map +1 -0
- package/dist/i18n.d.ts +74 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.jsx +127 -0
- package/dist/i18n.jsx.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/notify.d.ts +41 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.jsx +58 -0
- package/dist/notify.jsx.map +1 -0
- package/dist/pages/ai-assistant-settings-section.d.ts +3 -0
- package/dist/pages/ai-assistant-settings-section.d.ts.map +1 -0
- package/dist/pages/ai-assistant-settings-section.jsx +126 -0
- package/dist/pages/ai-assistant-settings-section.jsx.map +1 -0
- package/dist/pages/audit-log-page.d.ts +3 -0
- package/dist/pages/audit-log-page.d.ts.map +1 -0
- package/dist/pages/audit-log-page.jsx +354 -0
- package/dist/pages/audit-log-page.jsx.map +1 -0
- package/dist/pages/edit-page.d.ts +7 -0
- package/dist/pages/edit-page.d.ts.map +1 -0
- package/dist/pages/edit-page.jsx +614 -0
- package/dist/pages/edit-page.jsx.map +1 -0
- package/dist/pages/export-dialog.d.ts +11 -0
- package/dist/pages/export-dialog.d.ts.map +1 -0
- package/dist/pages/export-dialog.jsx +102 -0
- package/dist/pages/export-dialog.jsx.map +1 -0
- package/dist/pages/home-page.d.ts +3 -0
- package/dist/pages/home-page.d.ts.map +1 -0
- package/dist/pages/home-page.jsx +211 -0
- package/dist/pages/home-page.jsx.map +1 -0
- package/dist/pages/list-page.d.ts +42 -0
- package/dist/pages/list-page.d.ts.map +1 -0
- package/dist/pages/list-page.jsx +1596 -0
- package/dist/pages/list-page.jsx.map +1 -0
- package/dist/pages/login-page.d.ts +11 -0
- package/dist/pages/login-page.d.ts.map +1 -0
- package/dist/pages/login-page.jsx +157 -0
- package/dist/pages/login-page.jsx.map +1 -0
- package/dist/pages/settings-page.d.ts +5 -0
- package/dist/pages/settings-page.d.ts.map +1 -0
- package/dist/pages/settings-page.jsx +787 -0
- package/dist/pages/settings-page.jsx.map +1 -0
- package/dist/pages/settings-shared.d.ts +51 -0
- package/dist/pages/settings-shared.d.ts.map +1 -0
- package/dist/pages/settings-shared.jsx +66 -0
- package/dist/pages/settings-shared.jsx.map +1 -0
- package/dist/pages/show-page.d.ts +7 -0
- package/dist/pages/show-page.d.ts.map +1 -0
- package/dist/pages/show-page.jsx +147 -0
- package/dist/pages/show-page.jsx.map +1 -0
- package/dist/pages/wizard-create-page.d.ts +14 -0
- package/dist/pages/wizard-create-page.d.ts.map +1 -0
- package/dist/pages/wizard-create-page.jsx +106 -0
- package/dist/pages/wizard-create-page.jsx.map +1 -0
- package/dist/property-renderer.d.ts +8 -0
- package/dist/property-renderer.d.ts.map +1 -0
- package/dist/property-renderer.jsx +690 -0
- package/dist/property-renderer.jsx.map +1 -0
- package/dist/provider.d.ts +20 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.jsx +32 -0
- package/dist/provider.jsx.map +1 -0
- package/dist/realtime.d.ts +22 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +38 -0
- package/dist/realtime.js.map +1 -0
- package/dist/reference.d.ts +52 -0
- package/dist/reference.d.ts.map +1 -0
- package/dist/reference.jsx +224 -0
- package/dist/reference.jsx.map +1 -0
- package/dist/relations.d.ts +11 -0
- package/dist/relations.d.ts.map +1 -0
- package/dist/relations.js +36 -0
- package/dist/relations.js.map +1 -0
- package/dist/router.d.ts +82 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.jsx +187 -0
- package/dist/router.jsx.map +1 -0
- package/dist/show-when.d.ts +7 -0
- package/dist/show-when.d.ts.map +1 -0
- package/dist/show-when.js +77 -0
- package/dist/show-when.js.map +1 -0
- package/dist/types.d.ts +194 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/use-dashboard-charts.d.ts +93 -0
- package/dist/use-dashboard-charts.d.ts.map +1 -0
- package/dist/use-dashboard-charts.js +263 -0
- package/dist/use-dashboard-charts.js.map +1 -0
- package/dist/use-hotkey.d.ts +17 -0
- package/dist/use-hotkey.d.ts.map +1 -0
- package/dist/use-hotkey.js +103 -0
- package/dist/use-hotkey.js.map +1 -0
- package/dist/user-directory.d.ts +18 -0
- package/dist/user-directory.d.ts.map +1 -0
- package/dist/user-directory.js +51 -0
- package/dist/user-directory.js.map +1 -0
- package/dist/validation.d.ts +22 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +338 -0
- package/dist/validation.js.map +1 -0
- package/package.json +59 -0
- package/src/action-guard.ts +20 -0
- package/src/action-menu.tsx +161 -0
- package/src/admin-app.tsx +630 -0
- package/src/admin-router.tsx +273 -0
- package/src/breadcrumbs.tsx +75 -0
- package/src/client.ts +1093 -0
- package/src/component-loader.ts +33 -0
- package/src/components/ai-assistant-widget.tsx +565 -0
- package/src/components/ai-fill-dialog.tsx +143 -0
- package/src/components/chart-builder-dialog.tsx +618 -0
- package/src/components/chart-widget.tsx +654 -0
- package/src/components/global-search-dialog.tsx +272 -0
- package/src/components/group-settings-dialog.tsx +93 -0
- package/src/components/move-chart-dialog.tsx +130 -0
- package/src/components/reference-multi-table-dialog.tsx +196 -0
- package/src/components/related-records-tabs.tsx +130 -0
- package/src/components/revisions-button.tsx +237 -0
- package/src/components/wizard-form.tsx +302 -0
- package/src/dashboard/time-series.ts +125 -0
- package/src/dialogs.tsx +265 -0
- package/src/export.ts +140 -0
- package/src/extension-registry.ts +195 -0
- package/src/header-controls.tsx +125 -0
- package/src/hooks.ts +509 -0
- package/src/hotkey-help.tsx +56 -0
- package/src/hotkey-registry.tsx +60 -0
- package/src/i18n.tsx +267 -0
- package/src/index.ts +192 -0
- package/src/notify.tsx +94 -0
- package/src/pages/ai-assistant-settings-section.tsx +167 -0
- package/src/pages/audit-log-page.tsx +580 -0
- package/src/pages/edit-page.tsx +743 -0
- package/src/pages/export-dialog.tsx +154 -0
- package/src/pages/home-page.tsx +318 -0
- package/src/pages/list-page.tsx +2645 -0
- package/src/pages/login-page.tsx +242 -0
- package/src/pages/settings-page.tsx +1143 -0
- package/src/pages/settings-shared.tsx +134 -0
- package/src/pages/show-page.tsx +223 -0
- package/src/pages/wizard-create-page.tsx +164 -0
- package/src/property-renderer.tsx +1143 -0
- package/src/provider.tsx +70 -0
- package/src/realtime.ts +55 -0
- package/src/reference.tsx +386 -0
- package/src/relations.ts +55 -0
- package/src/router.tsx +211 -0
- package/src/show-when.ts +76 -0
- package/src/types.ts +198 -0
- package/src/use-dashboard-charts.ts +362 -0
- package/src/use-hotkey.ts +128 -0
- package/src/user-directory.ts +56 -0
- package/src/validation.ts +361 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
// Per-property Zod schema builder with localized error messages.
|
|
2
|
+
//
|
|
3
|
+
// Each PropertyJSON.type maps to a typed validator: numerics get coerced from
|
|
4
|
+
// string inputs, dates parsed via Date, references checked for non-empty,
|
|
5
|
+
// arrays validated as collections of FK-shaped values, and enums (anything
|
|
6
|
+
// with `availableValues`) restricted to the declared set. Required vs
|
|
7
|
+
// optional is honoured uniformly. All error messages route through the
|
|
8
|
+
// passed translator so the active locale wins.
|
|
9
|
+
//
|
|
10
|
+
// The builder returns plain Zod schemas — RHF + zodResolver consume them
|
|
11
|
+
// directly. Keep the builder pure (no React/i18n imports) so it stays
|
|
12
|
+
// testable and reusable from non-React contexts.
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { evaluateShowWhen } from './show-when.js';
|
|
15
|
+
/** Email pattern: deliberately loose, matches Better Auth / common usage. */
|
|
16
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
17
|
+
const URL_RE = /^https?:\/\/.+/i;
|
|
18
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
19
|
+
const isBlank = (v) => v === undefined || v === null || (typeof v === 'string' && v.trim() === '');
|
|
20
|
+
/** Build a string validator with optional/required + format checks. */
|
|
21
|
+
function stringSchema(p, t, format) {
|
|
22
|
+
const label = p.label;
|
|
23
|
+
const checkFormat = (v) => {
|
|
24
|
+
if (format === 'email')
|
|
25
|
+
return EMAIL_RE.test(v);
|
|
26
|
+
if (format === 'url')
|
|
27
|
+
return URL_RE.test(v);
|
|
28
|
+
if (format === 'uuid')
|
|
29
|
+
return UUID_RE.test(v);
|
|
30
|
+
return true;
|
|
31
|
+
};
|
|
32
|
+
const formatKey = format === 'email'
|
|
33
|
+
? 'validation:invalidEmail'
|
|
34
|
+
: format === 'url'
|
|
35
|
+
? 'validation:invalidUrl'
|
|
36
|
+
: format === 'uuid'
|
|
37
|
+
? 'validation:invalidUuid'
|
|
38
|
+
: null;
|
|
39
|
+
return z
|
|
40
|
+
.union([z.string(), z.null(), z.undefined()])
|
|
41
|
+
.transform((v) => (typeof v === 'string' ? v : v == null ? '' : String(v)))
|
|
42
|
+
.superRefine((value, ctx) => {
|
|
43
|
+
const blank = value.trim() === '';
|
|
44
|
+
if (blank) {
|
|
45
|
+
if (p.isRequired) {
|
|
46
|
+
ctx.addIssue({ code: 'custom', message: t('validation:required', { label }) });
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (formatKey && !checkFormat(value)) {
|
|
51
|
+
ctx.addIssue({ code: 'custom', message: t(formatKey, { label }) });
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
.transform((v) => (v.trim() === '' ? null : v));
|
|
55
|
+
}
|
|
56
|
+
/** Number validator with coercion from string inputs (HTML inputs ship strings). */
|
|
57
|
+
function numberSchema(p, t, integer = false) {
|
|
58
|
+
const label = p.label;
|
|
59
|
+
// Validate at the unknown layer because Zod 4's z.number() rejects NaN
|
|
60
|
+
// before our refinement can produce a localized message. We coerce here,
|
|
61
|
+
// then assert the type ourselves so all error paths flow through superRefine.
|
|
62
|
+
return z.unknown().superRefine((raw, ctx) => {
|
|
63
|
+
if (isBlank(raw)) {
|
|
64
|
+
if (p.isRequired) {
|
|
65
|
+
ctx.addIssue({ code: 'custom', message: t('validation:required', { label }) });
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const value = typeof raw === 'number' ? raw : Number(raw);
|
|
70
|
+
if (!Number.isFinite(value)) {
|
|
71
|
+
ctx.addIssue({ code: 'custom', message: t('validation:invalidNumber', { label }) });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (integer && !Number.isInteger(value)) {
|
|
75
|
+
ctx.addIssue({ code: 'custom', message: t('validation:invalidInteger', { label }) });
|
|
76
|
+
}
|
|
77
|
+
}).transform((raw) => {
|
|
78
|
+
if (isBlank(raw))
|
|
79
|
+
return null;
|
|
80
|
+
const n = typeof raw === 'number' ? raw : Number(raw);
|
|
81
|
+
return Number.isFinite(n) ? n : null;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** Boolean validator; missing value → false (HTML default for unchecked). */
|
|
85
|
+
function booleanSchema(_p, _t) {
|
|
86
|
+
return z.preprocess((v) => (typeof v === 'boolean' ? v : Boolean(v)), z.boolean());
|
|
87
|
+
}
|
|
88
|
+
/** Date validator: accepts ISO strings or `Date`; rejects unparseable input. */
|
|
89
|
+
function dateSchema(p, t) {
|
|
90
|
+
const label = p.label;
|
|
91
|
+
return z
|
|
92
|
+
.union([z.string(), z.date(), z.null(), z.undefined()])
|
|
93
|
+
.superRefine((value, ctx) => {
|
|
94
|
+
if (isBlank(value)) {
|
|
95
|
+
if (p.isRequired) {
|
|
96
|
+
ctx.addIssue({ code: 'custom', message: t('validation:required', { label }) });
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const parsed = value instanceof Date ? value : new Date(String(value));
|
|
101
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
102
|
+
ctx.addIssue({ code: 'custom', message: t('validation:invalidDate', { label }) });
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
.transform((v) => (isBlank(v) ? null : v));
|
|
106
|
+
}
|
|
107
|
+
/** Single reference validator: requires a non-empty FK when isRequired. */
|
|
108
|
+
function referenceSchema(p, t) {
|
|
109
|
+
const label = p.label;
|
|
110
|
+
return z
|
|
111
|
+
.union([z.string(), z.number(), z.null(), z.undefined()])
|
|
112
|
+
.superRefine((value, ctx) => {
|
|
113
|
+
if (isBlank(value) && p.isRequired) {
|
|
114
|
+
ctx.addIssue({ code: 'custom', message: t('validation:required', { label }) });
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
.transform((v) => (isBlank(v) ? null : v));
|
|
118
|
+
}
|
|
119
|
+
/** Multi-reference validator: array of FKs, must be non-empty when required.
|
|
120
|
+
*
|
|
121
|
+
* Pre-processes the input to flatten any accidentally-nested arrays and drop
|
|
122
|
+
* blank items, so a stale form state like `[["3","4"]]` still validates as
|
|
123
|
+
* the expected `["3","4"]` rather than tripping the inner string check. */
|
|
124
|
+
function multiReferenceSchema(p, t) {
|
|
125
|
+
const label = p.label;
|
|
126
|
+
const normalize = (raw) => {
|
|
127
|
+
if (raw == null)
|
|
128
|
+
return [];
|
|
129
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
130
|
+
const out = [];
|
|
131
|
+
for (const item of items) {
|
|
132
|
+
if (Array.isArray(item)) {
|
|
133
|
+
for (const sub of item) {
|
|
134
|
+
if (sub != null && sub !== '')
|
|
135
|
+
out.push(sub);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
else if (item != null && item !== '') {
|
|
139
|
+
out.push(item);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
};
|
|
144
|
+
return z
|
|
145
|
+
.preprocess(normalize, z.array(z.union([z.string(), z.number()])))
|
|
146
|
+
.superRefine((value, ctx) => {
|
|
147
|
+
if (p.isRequired && value.length === 0) {
|
|
148
|
+
ctx.addIssue({ code: 'custom', message: t('validation:emptySelection', { label }) });
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/** File validator: single-file values are storage keys (string/null), while
|
|
153
|
+
* multi-file values are arrays of storage keys. */
|
|
154
|
+
function fileSchema(p, t) {
|
|
155
|
+
if (p.isArray) {
|
|
156
|
+
return multiReferenceSchema(p, t);
|
|
157
|
+
}
|
|
158
|
+
return stringSchema(p, t);
|
|
159
|
+
}
|
|
160
|
+
/** Enum validator from `availableValues`. Unmatched value → notInList. */
|
|
161
|
+
function enumSchema(p, t) {
|
|
162
|
+
const label = p.label;
|
|
163
|
+
const allowed = new Set((p.availableValues ?? []).map((v) => v.value));
|
|
164
|
+
return z
|
|
165
|
+
.union([z.string(), z.null(), z.undefined()])
|
|
166
|
+
.transform((v) => (v == null ? '' : String(v)))
|
|
167
|
+
.superRefine((value, ctx) => {
|
|
168
|
+
if (value === '') {
|
|
169
|
+
if (p.isRequired) {
|
|
170
|
+
ctx.addIssue({ code: 'custom', message: t('validation:required', { label }) });
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (!allowed.has(value)) {
|
|
175
|
+
ctx.addIssue({ code: 'custom', message: t('validation:notInList', { label }) });
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
.transform((v) => (v === '' ? null : v));
|
|
179
|
+
}
|
|
180
|
+
/** JSON validator: accepts any JSON-serializable value (object/array/null).
|
|
181
|
+
*
|
|
182
|
+
* Unlike string fields, json fields hold a parsed JavaScript value — the
|
|
183
|
+
* JsonEditor emits objects/arrays directly. The schema passes them through
|
|
184
|
+
* as-is; only the required check (null/undefined → error) is applied. */
|
|
185
|
+
function jsonSchema(p, t) {
|
|
186
|
+
const label = p.label;
|
|
187
|
+
return z
|
|
188
|
+
.unknown()
|
|
189
|
+
.superRefine((v, ctx) => {
|
|
190
|
+
if ((v == null || v === '') && p.isRequired) {
|
|
191
|
+
ctx.addIssue({ code: 'custom', message: t('validation:required', { label }) });
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
.transform((v) => (v === '' ? null : v));
|
|
195
|
+
}
|
|
196
|
+
/** Many-to-many validator: array of `{ id, ...extras }` items.
|
|
197
|
+
*
|
|
198
|
+
* The M2M editor emits an array of objects (id of the referenced record plus
|
|
199
|
+
* arbitrary junction extra fields, e.g. `addedAt`, `position`). Bare ids are
|
|
200
|
+
* also accepted and normalized into `{ id }` objects so legacy form state
|
|
201
|
+
* doesn't trip the schema. Extra fields are passed through untouched —
|
|
202
|
+
* the backend's m2m feature persists whatever it recognises and ignores
|
|
203
|
+
* the rest. */
|
|
204
|
+
function m2mSchema(p, t) {
|
|
205
|
+
const label = p.label;
|
|
206
|
+
const normalize = (raw) => {
|
|
207
|
+
if (raw == null)
|
|
208
|
+
return [];
|
|
209
|
+
const items = Array.isArray(raw) ? raw : [raw];
|
|
210
|
+
const out = [];
|
|
211
|
+
for (const item of items) {
|
|
212
|
+
if (item == null || item === '')
|
|
213
|
+
continue;
|
|
214
|
+
if (typeof item === 'string' || typeof item === 'number') {
|
|
215
|
+
out.push({ id: String(item) });
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (typeof item === 'object') {
|
|
219
|
+
const obj = item;
|
|
220
|
+
const id = obj.id ?? obj.value;
|
|
221
|
+
if (id == null || id === '')
|
|
222
|
+
continue;
|
|
223
|
+
out.push({ ...obj, id: String(id) });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
};
|
|
228
|
+
return z
|
|
229
|
+
.preprocess(normalize, z.array(z.record(z.string(), z.unknown())))
|
|
230
|
+
.superRefine((value, ctx) => {
|
|
231
|
+
if (p.isRequired && value.length === 0) {
|
|
232
|
+
ctx.addIssue({ code: 'custom', message: t('validation:emptySelection', { label }) });
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/** Build the Zod schema for a property without considering `showWhen`. */
|
|
237
|
+
function buildPropertySchemaInner(p, t) {
|
|
238
|
+
// M2M is structurally different from a multi-reference — its values are
|
|
239
|
+
// `{ id, ...extras }` objects, not bare FKs. Branch first so the
|
|
240
|
+
// `p.reference` check below doesn't capture it.
|
|
241
|
+
if (p.type === 'm2m') {
|
|
242
|
+
return m2mSchema(p, t);
|
|
243
|
+
}
|
|
244
|
+
// Enum-like properties always go through the availableValues path —
|
|
245
|
+
// overrides the raw type (e.g. a string with a fixed set of options).
|
|
246
|
+
if (p.availableValues && p.availableValues.length > 0) {
|
|
247
|
+
return enumSchema(p, t);
|
|
248
|
+
}
|
|
249
|
+
if (p.reference) {
|
|
250
|
+
return p.isArray ? multiReferenceSchema(p, t) : referenceSchema(p, t);
|
|
251
|
+
}
|
|
252
|
+
switch (p.type) {
|
|
253
|
+
case 'boolean':
|
|
254
|
+
return booleanSchema(p, t);
|
|
255
|
+
case 'number':
|
|
256
|
+
case 'float':
|
|
257
|
+
case 'currency':
|
|
258
|
+
case 'money':
|
|
259
|
+
return numberSchema(p, t, false);
|
|
260
|
+
case 'integer':
|
|
261
|
+
return numberSchema(p, t, true);
|
|
262
|
+
case 'date':
|
|
263
|
+
case 'datetime':
|
|
264
|
+
case 'datetime-local':
|
|
265
|
+
return dateSchema(p, t);
|
|
266
|
+
case 'email':
|
|
267
|
+
return stringSchema(p, t, 'email');
|
|
268
|
+
case 'url':
|
|
269
|
+
return stringSchema(p, t, 'url');
|
|
270
|
+
case 'uuid':
|
|
271
|
+
return stringSchema(p, t, 'uuid');
|
|
272
|
+
case 'json':
|
|
273
|
+
return jsonSchema(p, t);
|
|
274
|
+
case 'string':
|
|
275
|
+
case 'text':
|
|
276
|
+
case 'textarea':
|
|
277
|
+
case 'password':
|
|
278
|
+
case 'richtext':
|
|
279
|
+
case 'color':
|
|
280
|
+
case 'file':
|
|
281
|
+
return fileSchema(p, t);
|
|
282
|
+
default:
|
|
283
|
+
return stringSchema(p, t);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Map a PropertyJSON to its Zod schema. When the property has a `showWhen`
|
|
288
|
+
* rule and a `getValues` getter is supplied, the schema short-circuits to
|
|
289
|
+
* a no-op while the rule does not match — letting hidden branches pass
|
|
290
|
+
* validation without their required/format checks tripping submission.
|
|
291
|
+
*/
|
|
292
|
+
export function buildPropertySchema(p, t, getValues) {
|
|
293
|
+
const inner = buildPropertySchemaInner(p, t);
|
|
294
|
+
if (!p.showWhen || !getValues)
|
|
295
|
+
return inner;
|
|
296
|
+
// Wrap: only forward to `inner` when visible. Hidden → accept anything,
|
|
297
|
+
// pass it through unchanged. We re-emit issues from `inner` so error
|
|
298
|
+
// messages and paths stay identical to the non-conditional case.
|
|
299
|
+
return z
|
|
300
|
+
.any()
|
|
301
|
+
.superRefine((value, ctx) => {
|
|
302
|
+
if (!evaluateShowWhen(p.showWhen, getValues()))
|
|
303
|
+
return;
|
|
304
|
+
const result = inner.safeParse(value);
|
|
305
|
+
if (!result.success) {
|
|
306
|
+
// Zod 4's `RefinementCtx.addIssue` accepts a structurally looser
|
|
307
|
+
// shape than `$ZodIssue`; spread into a plain object to satisfy
|
|
308
|
+
// the inferred parameter type.
|
|
309
|
+
for (const issue of result.error.issues) {
|
|
310
|
+
ctx.addIssue({ ...issue });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
})
|
|
314
|
+
.transform((value) => {
|
|
315
|
+
if (!evaluateShowWhen(p.showWhen, getValues()))
|
|
316
|
+
return value;
|
|
317
|
+
const result = inner.safeParse(value);
|
|
318
|
+
return result.success ? result.data : value;
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/** Build a Zod object schema covering every editable property in a resource. */
|
|
322
|
+
export function buildValidationSchema(properties, t, getValues) {
|
|
323
|
+
const shape = {};
|
|
324
|
+
for (const p of properties)
|
|
325
|
+
shape[p.path] = buildPropertySchema(p, t, getValues);
|
|
326
|
+
return z.object(shape);
|
|
327
|
+
}
|
|
328
|
+
/** Sensible empty default per type so RHF stays controlled from first render. */
|
|
329
|
+
export function defaultValueFor(p) {
|
|
330
|
+
if (p.type === 'boolean')
|
|
331
|
+
return false;
|
|
332
|
+
if (p.type === 'json')
|
|
333
|
+
return null;
|
|
334
|
+
if (p.isArray)
|
|
335
|
+
return [];
|
|
336
|
+
return '';
|
|
337
|
+
}
|
|
338
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@modern-admin/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React 19 frontend layer for Modern Admin — translation boundary, routing, hooks.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/modern-admin/modern-admin.git",
|
|
10
|
+
"directory": "packages/react"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"registry": "https://registry.npmjs.org",
|
|
26
|
+
"access": "public",
|
|
27
|
+
"main": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@hookform/resolvers": "^5.2.2",
|
|
38
|
+
"@modern-admin/core": "0.1.0",
|
|
39
|
+
"@modern-admin/i18n": "0.1.0",
|
|
40
|
+
"@modern-admin/ui": "0.1.0",
|
|
41
|
+
"@tanstack/react-query": "^5.100.9",
|
|
42
|
+
"@tanstack/react-router": "^1.169.2",
|
|
43
|
+
"@tanstack/react-table": "^8.21.3",
|
|
44
|
+
"lucide-react": "^1.14.0",
|
|
45
|
+
"react-hook-form": "^7.75.0",
|
|
46
|
+
"zod": "^4.4.3"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@modern-admin/tsconfig": "0.1.0",
|
|
50
|
+
"@types/bun": "^1.3.13",
|
|
51
|
+
"@types/react": "^19.2.14",
|
|
52
|
+
"@types/react-dom": "^19.2.3",
|
|
53
|
+
"typescript": "^6.0.3"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"react": "^19.2.6",
|
|
57
|
+
"react-dom": "^19.2.6"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ActionDescriptor } from './types.js'
|
|
2
|
+
import type { ConfirmOptions, DialogsApi } from './dialogs.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* If `action.guard` is set, shows a confirm dialog with that text as the
|
|
6
|
+
* description before proceeding. Returns `true` if the action should run
|
|
7
|
+
* (no guard configured, or user clicked Confirm), `false` if cancelled.
|
|
8
|
+
*
|
|
9
|
+
* Usage at an invoke call-site:
|
|
10
|
+
* if (!await confirmGuard(action, dialogs)) return
|
|
11
|
+
* invoke.mutate(...)
|
|
12
|
+
*/
|
|
13
|
+
export async function confirmGuard(
|
|
14
|
+
action: Pick<ActionDescriptor, 'guard'>,
|
|
15
|
+
dialogs: Pick<DialogsApi, 'confirm'>,
|
|
16
|
+
extra?: Omit<ConfirmOptions, 'description'>,
|
|
17
|
+
): Promise<boolean> {
|
|
18
|
+
if (!action.guard) return true
|
|
19
|
+
return dialogs.confirm({ description: action.guard, ...extra })
|
|
20
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuLabel,
|
|
8
|
+
DropdownMenuPortal,
|
|
9
|
+
DropdownMenuSeparator,
|
|
10
|
+
DropdownMenuSub,
|
|
11
|
+
DropdownMenuSubContent,
|
|
12
|
+
DropdownMenuSubTrigger,
|
|
13
|
+
DropdownMenuTrigger,
|
|
14
|
+
} from '@modern-admin/ui'
|
|
15
|
+
import { MoreHorizontal, Zap } from 'lucide-react'
|
|
16
|
+
import type { ActionDescriptor, ActionGroup } from './types.js'
|
|
17
|
+
|
|
18
|
+
interface ActionMenuGroupNode {
|
|
19
|
+
key: string
|
|
20
|
+
group: ActionGroup
|
|
21
|
+
items: ActionMenuNode[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type ActionMenuNode =
|
|
25
|
+
| { kind: 'action'; action: ActionDescriptor }
|
|
26
|
+
| { kind: 'group'; group: ActionMenuGroupNode }
|
|
27
|
+
|
|
28
|
+
export interface ActionMenuProps {
|
|
29
|
+
actions: ActionDescriptor[]
|
|
30
|
+
onAction(action: ActionDescriptor): void
|
|
31
|
+
t: (key: string, params?: Record<string, string | number>) => string
|
|
32
|
+
trigger?: React.ReactElement
|
|
33
|
+
menuLabel?: string
|
|
34
|
+
align?: 'start' | 'center' | 'end'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const getActionLabel = (action: ActionDescriptor): string =>
|
|
38
|
+
typeof action.custom?.label === 'string' ? action.custom.label : action.name
|
|
39
|
+
|
|
40
|
+
const buildActionMenuTree = (actions: ActionDescriptor[]): ActionMenuNode[] => {
|
|
41
|
+
const root: ActionMenuNode[] = []
|
|
42
|
+
const groups = new Map<string, ActionMenuGroupNode>()
|
|
43
|
+
|
|
44
|
+
const ensureGroup = (target: ActionMenuNode[], path: ActionGroup[], depth: number): ActionMenuNode[] => {
|
|
45
|
+
if (depth >= path.length) return target
|
|
46
|
+
const segment = path[depth]!
|
|
47
|
+
const key = path
|
|
48
|
+
.slice(0, depth + 1)
|
|
49
|
+
.map((item) => item.name)
|
|
50
|
+
.join('>')
|
|
51
|
+
let group = groups.get(key)
|
|
52
|
+
if (!group) {
|
|
53
|
+
group = { key, group: segment, items: [] }
|
|
54
|
+
groups.set(key, group)
|
|
55
|
+
target.push({ kind: 'group', group })
|
|
56
|
+
}
|
|
57
|
+
return ensureGroup(group.items, path, depth + 1)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
for (const action of actions) {
|
|
61
|
+
const nesting = action.nesting ?? []
|
|
62
|
+
const target = nesting.length > 0 ? ensureGroup(root, nesting, 0) : root
|
|
63
|
+
target.push({ kind: 'action', action })
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return root
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const renderNodes = (
|
|
70
|
+
nodes: ActionMenuNode[],
|
|
71
|
+
onAction: (action: ActionDescriptor) => void,
|
|
72
|
+
): React.ReactNode =>
|
|
73
|
+
nodes.map((node) => {
|
|
74
|
+
if (node.kind === 'action') {
|
|
75
|
+
return (
|
|
76
|
+
<DropdownMenuItem
|
|
77
|
+
key={node.action.name}
|
|
78
|
+
onSelect={() => onAction(node.action)}
|
|
79
|
+
>
|
|
80
|
+
<Zap className="size-4" /> {getActionLabel(node.action)}
|
|
81
|
+
</DropdownMenuItem>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
return (
|
|
85
|
+
<DropdownMenuSub key={node.group.key}>
|
|
86
|
+
<DropdownMenuSubTrigger>
|
|
87
|
+
{node.group.group.name}
|
|
88
|
+
</DropdownMenuSubTrigger>
|
|
89
|
+
<DropdownMenuPortal>
|
|
90
|
+
<DropdownMenuSubContent>
|
|
91
|
+
{renderNodes(node.group.items, onAction)}
|
|
92
|
+
</DropdownMenuSubContent>
|
|
93
|
+
</DropdownMenuPortal>
|
|
94
|
+
</DropdownMenuSub>
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
export function ActionMenuItems({
|
|
99
|
+
actions,
|
|
100
|
+
onAction,
|
|
101
|
+
}: {
|
|
102
|
+
actions: ActionDescriptor[]
|
|
103
|
+
onAction(action: ActionDescriptor): void
|
|
104
|
+
}): React.ReactElement | null {
|
|
105
|
+
const nodes = React.useMemo(() => buildActionMenuTree(actions), [actions])
|
|
106
|
+
if (nodes.length === 0) return null
|
|
107
|
+
return <>{renderNodes(nodes, onAction)}</>
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function ActionMenu({
|
|
111
|
+
actions,
|
|
112
|
+
onAction,
|
|
113
|
+
t,
|
|
114
|
+
trigger,
|
|
115
|
+
menuLabel,
|
|
116
|
+
align = 'end',
|
|
117
|
+
}: ActionMenuProps): React.ReactElement | null {
|
|
118
|
+
const nodes = React.useMemo(() => buildActionMenuTree(actions), [actions])
|
|
119
|
+
if (nodes.length === 0) return null
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<DropdownMenu>
|
|
123
|
+
<DropdownMenuTrigger asChild>
|
|
124
|
+
{trigger ?? (
|
|
125
|
+
<Button variant="outline" size="sm">
|
|
126
|
+
<Zap className="size-4" />
|
|
127
|
+
{t('common:actions')}
|
|
128
|
+
</Button>
|
|
129
|
+
)}
|
|
130
|
+
</DropdownMenuTrigger>
|
|
131
|
+
<DropdownMenuContent align={align}>
|
|
132
|
+
<DropdownMenuLabel>{menuLabel ?? t('common:actions')}</DropdownMenuLabel>
|
|
133
|
+
<DropdownMenuSeparator />
|
|
134
|
+
<ActionMenuItems actions={actions} onAction={onAction} />
|
|
135
|
+
</DropdownMenuContent>
|
|
136
|
+
</DropdownMenu>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function MoreActionsMenu({
|
|
141
|
+
actions,
|
|
142
|
+
onAction,
|
|
143
|
+
t,
|
|
144
|
+
menuLabel,
|
|
145
|
+
}: Omit<ActionMenuProps, 'trigger' | 'align'>): React.ReactElement | null {
|
|
146
|
+
if (actions.length === 0) return null
|
|
147
|
+
return (
|
|
148
|
+
<ActionMenu
|
|
149
|
+
actions={actions}
|
|
150
|
+
onAction={onAction}
|
|
151
|
+
t={t}
|
|
152
|
+
menuLabel={menuLabel}
|
|
153
|
+
trigger={(
|
|
154
|
+
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
155
|
+
<MoreHorizontal className="size-4" />
|
|
156
|
+
<span className="sr-only">{t('common:openMenu')}</span>
|
|
157
|
+
</Button>
|
|
158
|
+
)}
|
|
159
|
+
/>
|
|
160
|
+
)
|
|
161
|
+
}
|