@rsc-kit/press 0.0.0-stage → 0.30.42
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/README.md +117 -2
- package/dist/admin/actions.d.ts +30 -0
- package/dist/admin/actions.js +171 -0
- package/dist/admin/actions.js.map +1 -0
- package/dist/admin/index.d.ts +5 -0
- package/dist/admin/index.js +2 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/panel.d.ts +96 -0
- package/dist/admin/panel.js +354 -0
- package/dist/admin/panel.js.map +1 -0
- package/dist/admin/registry.d.ts +58 -0
- package/dist/admin/registry.js +90 -0
- package/dist/admin/registry.js.map +1 -0
- package/dist/admin/theme.d.ts +251 -0
- package/dist/admin/theme.js +2 -0
- package/dist/admin/theme.js.map +1 -0
- package/dist/admin/url.d.ts +24 -0
- package/dist/admin/url.js +45 -0
- package/dist/admin/url.js.map +1 -0
- package/dist/blocks.d.ts +46 -0
- package/dist/blocks.js +33 -0
- package/dist/blocks.js.map +1 -0
- package/dist/forms/actions.d.ts +7 -0
- package/dist/forms/actions.js +26 -0
- package/dist/forms/actions.js.map +1 -0
- package/dist/forms/form.d.ts +42 -0
- package/dist/forms/form.js +44 -0
- package/dist/forms/form.js.map +1 -0
- package/dist/forms/honeypot.d.ts +2 -0
- package/dist/forms/honeypot.js +3 -0
- package/dist/forms/honeypot.js.map +1 -0
- package/dist/forms/index.d.ts +3 -0
- package/dist/forms/index.js +2 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/forms/registry.d.ts +10 -0
- package/dist/forms/registry.js +17 -0
- package/dist/forms/registry.js.map +1 -0
- package/dist/forms/theme.d.ts +29 -0
- package/dist/forms/theme.js +2 -0
- package/dist/forms/theme.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/installable.d.ts +173 -0
- package/dist/installable.js +490 -0
- package/dist/installable.js.map +1 -0
- package/dist/memory.d.ts +7 -0
- package/dist/memory.js +50 -0
- package/dist/memory.js.map +1 -0
- package/dist/pages/index.d.ts +2 -0
- package/dist/pages/index.js +2 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/pages/pages.d.ts +78 -0
- package/dist/pages/pages.js +183 -0
- package/dist/pages/pages.js.map +1 -0
- package/dist/plugin.d.ts +51 -0
- package/dist/plugin.js +20 -0
- package/dist/plugin.js.map +1 -0
- package/dist/resource.d.ts +320 -0
- package/dist/resource.js +415 -0
- package/dist/resource.js.map +1 -0
- package/dist/shadcn/breadcrumbs.d.ts +6 -0
- package/dist/shadcn/breadcrumbs.js +21 -0
- package/dist/shadcn/breadcrumbs.js.map +1 -0
- package/dist/shadcn/components/ui/alert-dialog.d.ts +18 -0
- package/dist/shadcn/components/ui/alert-dialog.js +44 -0
- package/dist/shadcn/components/ui/alert-dialog.js.map +1 -0
- package/dist/shadcn/components/ui/avatar.d.ts +11 -0
- package/dist/shadcn/components/ui/avatar.js +25 -0
- package/dist/shadcn/components/ui/avatar.js.map +1 -0
- package/dist/shadcn/components/ui/badge.d.ts +7 -0
- package/dist/shadcn/components/ui/badge.js +34 -0
- package/dist/shadcn/components/ui/badge.js.map +1 -0
- package/dist/shadcn/components/ui/breadcrumb.d.ts +10 -0
- package/dist/shadcn/components/ui/breadcrumb.js +38 -0
- package/dist/shadcn/components/ui/breadcrumb.js.map +1 -0
- package/dist/shadcn/components/ui/button.d.ts +8 -0
- package/dist/shadcn/components/ui/button.js +35 -0
- package/dist/shadcn/components/ui/button.js.map +1 -0
- package/dist/shadcn/components/ui/card.d.ts +11 -0
- package/dist/shadcn/components/ui/card.js +26 -0
- package/dist/shadcn/components/ui/card.js.map +1 -0
- package/dist/shadcn/components/ui/checkbox.d.ts +3 -0
- package/dist/shadcn/components/ui/checkbox.js +10 -0
- package/dist/shadcn/components/ui/checkbox.js.map +1 -0
- package/dist/shadcn/components/ui/dropdown-menu.d.ts +29 -0
- package/dist/shadcn/components/ui/dropdown-menu.js +53 -0
- package/dist/shadcn/components/ui/dropdown-menu.js.map +1 -0
- package/dist/shadcn/components/ui/field.d.ts +24 -0
- package/dist/shadcn/components/ui/field.js +69 -0
- package/dist/shadcn/components/ui/field.js.map +1 -0
- package/dist/shadcn/components/ui/input.d.ts +3 -0
- package/dist/shadcn/components/ui/input.js +9 -0
- package/dist/shadcn/components/ui/input.js.map +1 -0
- package/dist/shadcn/components/ui/label.d.ts +3 -0
- package/dist/shadcn/components/ui/label.js +9 -0
- package/dist/shadcn/components/ui/label.js.map +1 -0
- package/dist/shadcn/components/ui/select.d.ts +15 -0
- package/dist/shadcn/components/ui/select.js +36 -0
- package/dist/shadcn/components/ui/select.js.map +1 -0
- package/dist/shadcn/components/ui/separator.d.ts +3 -0
- package/dist/shadcn/components/ui/separator.js +9 -0
- package/dist/shadcn/components/ui/separator.js.map +1 -0
- package/dist/shadcn/components/ui/sheet.d.ts +14 -0
- package/dist/shadcn/components/ui/sheet.js +39 -0
- package/dist/shadcn/components/ui/sheet.js.map +1 -0
- package/dist/shadcn/components/ui/sidebar.d.ts +63 -0
- package/dist/shadcn/components/ui/sidebar.js +257 -0
- package/dist/shadcn/components/ui/sidebar.js.map +1 -0
- package/dist/shadcn/components/ui/skeleton.d.ts +2 -0
- package/dist/shadcn/components/ui/skeleton.js +7 -0
- package/dist/shadcn/components/ui/skeleton.js.map +1 -0
- package/dist/shadcn/components/ui/sonner.d.ts +3 -0
- package/dist/shadcn/components/ui/sonner.js +38 -0
- package/dist/shadcn/components/ui/sonner.js.map +1 -0
- package/dist/shadcn/components/ui/switch.d.ts +5 -0
- package/dist/shadcn/components/ui/switch.js +9 -0
- package/dist/shadcn/components/ui/switch.js.map +1 -0
- package/dist/shadcn/components/ui/table.d.ts +10 -0
- package/dist/shadcn/components/ui/table.js +30 -0
- package/dist/shadcn/components/ui/table.js.map +1 -0
- package/dist/shadcn/components/ui/textarea.d.ts +3 -0
- package/dist/shadcn/components/ui/textarea.js +8 -0
- package/dist/shadcn/components/ui/textarea.js.map +1 -0
- package/dist/shadcn/components/ui/tooltip.d.ts +6 -0
- package/dist/shadcn/components/ui/tooltip.js +18 -0
- package/dist/shadcn/components/ui/tooltip.js.map +1 -0
- package/dist/shadcn/data-table.d.ts +12 -0
- package/dist/shadcn/data-table.js +195 -0
- package/dist/shadcn/data-table.js.map +1 -0
- package/dist/shadcn/fields.d.ts +20 -0
- package/dist/shadcn/fields.js +179 -0
- package/dist/shadcn/fields.js.map +1 -0
- package/dist/shadcn/flash-toasts.d.ts +2 -0
- package/dist/shadcn/flash-toasts.js +11 -0
- package/dist/shadcn/flash-toasts.js.map +1 -0
- package/dist/shadcn/hooks/use-mobile.d.ts +1 -0
- package/dist/shadcn/hooks/use-mobile.js +16 -0
- package/dist/shadcn/hooks/use-mobile.js.map +1 -0
- package/dist/shadcn/index.d.ts +10 -0
- package/dist/shadcn/index.js +31 -0
- package/dist/shadcn/index.js.map +1 -0
- package/dist/shadcn/mode-toggle.d.ts +7 -0
- package/dist/shadcn/mode-toggle.js +52 -0
- package/dist/shadcn/mode-toggle.js.map +1 -0
- package/dist/shadcn/nav.d.ts +5 -0
- package/dist/shadcn/nav.js +19 -0
- package/dist/shadcn/nav.js.map +1 -0
- package/dist/shadcn/pages.d.ts +15 -0
- package/dist/shadcn/pages.js +59 -0
- package/dist/shadcn/pages.js.map +1 -0
- package/dist/shadcn/plugin-forms.d.ts +16 -0
- package/dist/shadcn/plugin-forms.js +25 -0
- package/dist/shadcn/plugin-forms.js.map +1 -0
- package/dist/shadcn/public-form.d.ts +8 -0
- package/dist/shadcn/public-form.js +26 -0
- package/dist/shadcn/public-form.js.map +1 -0
- package/dist/shadcn/resource-form.d.ts +19 -0
- package/dist/shadcn/resource-form.js +30 -0
- package/dist/shadcn/resource-form.js.map +1 -0
- package/dist/shadcn/styles.css +12 -0
- package/dist/shadcn/tenant-switcher.d.ts +3 -0
- package/dist/shadcn/tenant-switcher.js +17 -0
- package/dist/shadcn/tenant-switcher.js.map +1 -0
- package/dist/shadcn/theme-script.d.ts +7 -0
- package/dist/shadcn/theme-script.js +10 -0
- package/dist/shadcn/theme-script.js.map +1 -0
- package/dist/shadcn/user-menu.d.ts +3 -0
- package/dist/shadcn/user-menu.js +23 -0
- package/dist/shadcn/user-menu.js.map +1 -0
- package/dist/signing.d.ts +43 -0
- package/dist/signing.js +82 -0
- package/dist/signing.js.map +1 -0
- package/dist/tenant.d.ts +12 -0
- package/dist/tenant.js +29 -0
- package/dist/tenant.js.map +1 -0
- package/dist/uploads.d.ts +32 -0
- package/dist/uploads.js +28 -0
- package/dist/uploads.js.map +1 -0
- package/package.json +74 -4
package/dist/resource.js
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { issuesToErrors } from '@rsc-kit/core/standardSchema';
|
|
2
|
+
import { accepts, IMAGE_TYPES, sizeOf } from './uploads.js';
|
|
3
|
+
/** A column, chained: text('name').searchable().sortable(). */
|
|
4
|
+
export class ColumnBuilder {
|
|
5
|
+
#column;
|
|
6
|
+
constructor(column) {
|
|
7
|
+
this.#column = column;
|
|
8
|
+
}
|
|
9
|
+
sortable() {
|
|
10
|
+
this.#column.sortable = true;
|
|
11
|
+
return this;
|
|
12
|
+
}
|
|
13
|
+
searchable() {
|
|
14
|
+
this.#column.searchable = true;
|
|
15
|
+
return this;
|
|
16
|
+
}
|
|
17
|
+
label(label) {
|
|
18
|
+
this.#column.label = label;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
/** In the Columns menu, unticked until the visitor shows it. */
|
|
22
|
+
hidden() {
|
|
23
|
+
this.#column.visible = false;
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
/** Draw the cell yourself: .cell((value, row) => <Avatar src={row.photo} />). */
|
|
27
|
+
cell(render) {
|
|
28
|
+
this.#column.cell = render;
|
|
29
|
+
return this;
|
|
30
|
+
}
|
|
31
|
+
build() {
|
|
32
|
+
return { ...this.#column };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function columns() {
|
|
36
|
+
const make = (format) => (key) => new ColumnBuilder({ key, label: humanize(key), format, sortable: false, searchable: false, visible: true });
|
|
37
|
+
return {
|
|
38
|
+
text: make('text'),
|
|
39
|
+
money: make('money'),
|
|
40
|
+
badge: make('badge'),
|
|
41
|
+
boolean: make('boolean'),
|
|
42
|
+
date: make('date'),
|
|
43
|
+
relation: (key, resource) => new ColumnBuilder({ key, label: humanize(key.replace(/_?[iI]d$/, '')), format: 'relation', relation: resource, sortable: false, searchable: false, visible: true }),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Two calls so the row is named and the schema is inferred:
|
|
48
|
+
* defineResource<Product>()({ ... }).
|
|
49
|
+
*/
|
|
50
|
+
export function defineResource() {
|
|
51
|
+
return (definition) => {
|
|
52
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(definition.name)) {
|
|
53
|
+
throw new Error(`[admin] a resource's name is its url segment, lowercase letters, digits and dashes: ${JSON.stringify(definition.name)}`);
|
|
54
|
+
}
|
|
55
|
+
const fields = fieldsOf(definition.form, definition.fields ?? {});
|
|
56
|
+
const bulkActions = definition.bulkActions ?? [];
|
|
57
|
+
const bulkNames = bulkActions.map((action) => action.name);
|
|
58
|
+
if (bulkActions.length > 0 && definition.readOnly) {
|
|
59
|
+
throw new Error(`[admin] ${definition.name} is read-only, so it has no bulk actions`);
|
|
60
|
+
}
|
|
61
|
+
for (const [index, name] of bulkNames.entries()) {
|
|
62
|
+
if (name === 'delete')
|
|
63
|
+
throw new Error(`[admin] ${definition.name}: 'delete' is the panel's own bulk action`);
|
|
64
|
+
if (bulkNames.indexOf(name) !== index)
|
|
65
|
+
throw new Error(`[admin] ${definition.name}: two bulk actions are named ${name}`);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
name: definition.name,
|
|
69
|
+
label: definition.label ?? humanize(definition.name),
|
|
70
|
+
singular: definition.singular ?? humanize(definition.name).replace(/s$/, ''),
|
|
71
|
+
key: definition.key,
|
|
72
|
+
title: definition.title,
|
|
73
|
+
source: definition.source,
|
|
74
|
+
columns: definition.table(columns()).map((column) => column.build()),
|
|
75
|
+
filters: (definition.filters ?? []).map((key) => {
|
|
76
|
+
const field = fields.find((candidate) => candidate.name === key);
|
|
77
|
+
if (!field?.options)
|
|
78
|
+
throw new Error(`[admin] ${definition.name}: the filter ${key} needs an enum field in the form`);
|
|
79
|
+
return { key, label: field.label, options: field.options };
|
|
80
|
+
}),
|
|
81
|
+
schema: definition.form,
|
|
82
|
+
fields,
|
|
83
|
+
perPage: definition.perPage ?? 10,
|
|
84
|
+
mode: definition.mode ?? 'server',
|
|
85
|
+
readOnly: definition.readOnly ?? false,
|
|
86
|
+
bulkActions,
|
|
87
|
+
...(definition.icon ? { icon: definition.icon } : {}),
|
|
88
|
+
...(definition.description ? { description: definition.description } : {}),
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A blocks field's schemas, by field. Kept beside the field rather than on
|
|
94
|
+
* it, because a field crosses to the browser and a schema cannot.
|
|
95
|
+
*/
|
|
96
|
+
const schemasOf = new WeakMap();
|
|
97
|
+
/** A file field's storage, kept beside it for the same reason. */
|
|
98
|
+
const storageOf = new WeakMap();
|
|
99
|
+
/** A bare option labelled for reading, or one the app labelled itself. */
|
|
100
|
+
function optionOf(option) {
|
|
101
|
+
return typeof option === 'string' ? { value: option, label: humanize(option) } : option;
|
|
102
|
+
}
|
|
103
|
+
/** An override as a field: options made whole, and a relation drawn as a select. */
|
|
104
|
+
function applied(override) {
|
|
105
|
+
if (!override)
|
|
106
|
+
return {};
|
|
107
|
+
// storage never goes on the field: a field crosses to the browser.
|
|
108
|
+
const { options, blocks, storage: _, ...rest } = override;
|
|
109
|
+
return {
|
|
110
|
+
...(override.relation ? { type: 'select' } : {}),
|
|
111
|
+
...rest,
|
|
112
|
+
...(options ? { options: options.map(optionOf) } : {}),
|
|
113
|
+
...(blocks ? { type: 'blocks', blocks: blocks.map(({ name, label, fields }) => ({ name, label, fields })) } : {}),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Keeps a blocks field's schemas, and a file field's storage, where parse() finds them. */
|
|
117
|
+
function withSchemas(field, override) {
|
|
118
|
+
if (field.type === 'file' || field.type === 'image') {
|
|
119
|
+
if (!override?.storage)
|
|
120
|
+
throw new Error(`[press] the ${field.type} field ${field.name} needs storage: where an upload is kept`);
|
|
121
|
+
storageOf.set(field, override.storage);
|
|
122
|
+
if (field.type === 'image' && field.accept === undefined)
|
|
123
|
+
field.accept = IMAGE_TYPES;
|
|
124
|
+
return field;
|
|
125
|
+
}
|
|
126
|
+
if (field.type !== 'blocks')
|
|
127
|
+
return field;
|
|
128
|
+
if (!override?.blocks?.length)
|
|
129
|
+
throw new Error(`[press] the blocks field ${field.name} needs blocks: [...] - the kinds it offers`);
|
|
130
|
+
schemasOf.set(field, new Map(override.blocks.map((block) => [block.name, block.schema])));
|
|
131
|
+
return field;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* A JSON Schema pattern as the pattern attribute, or nothing. The attribute
|
|
135
|
+
* matches the whole value and compiles with the v flag; a JSON Schema pattern
|
|
136
|
+
* matches anywhere. So only an anchored one carries over, and only one the v
|
|
137
|
+
* flag accepts - an attribute the browser cannot compile is ignored, and one
|
|
138
|
+
* that meant "contains" would refuse what the server accepts.
|
|
139
|
+
*/
|
|
140
|
+
function patternOf(pattern) {
|
|
141
|
+
if (!pattern?.startsWith('^') || !pattern.endsWith('$') || pattern.endsWith('\\$'))
|
|
142
|
+
return undefined;
|
|
143
|
+
try {
|
|
144
|
+
new RegExp(pattern, 'v');
|
|
145
|
+
return pattern;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** The constraints the browser can check for a field of this type. */
|
|
152
|
+
function constraintsOf(type, json, integer) {
|
|
153
|
+
const out = {};
|
|
154
|
+
if (type === 'number') {
|
|
155
|
+
// zod's int() writes the safe-integer range as bounds: not a constraint anyone wrote.
|
|
156
|
+
if (json.minimum !== undefined && json.minimum > Number.MIN_SAFE_INTEGER)
|
|
157
|
+
out.min = json.minimum;
|
|
158
|
+
if (json.maximum !== undefined && json.maximum < Number.MAX_SAFE_INTEGER)
|
|
159
|
+
out.max = json.maximum;
|
|
160
|
+
// Without step a number input refuses 12.5: 'any' unless the schema says integer.
|
|
161
|
+
out.step = integer ? 1 : 'any';
|
|
162
|
+
}
|
|
163
|
+
if (type === 'text' || type === 'textarea' || type === 'email') {
|
|
164
|
+
if (json.minLength !== undefined)
|
|
165
|
+
out.minLength = json.minLength;
|
|
166
|
+
if (json.maxLength !== undefined)
|
|
167
|
+
out.maxLength = json.maxLength;
|
|
168
|
+
}
|
|
169
|
+
// Only plain text: an email's or a date's pattern is the library's own
|
|
170
|
+
// spelling of the format, which type="email" and type="date" already check.
|
|
171
|
+
if (type === 'text') {
|
|
172
|
+
const pattern = patternOf(json.pattern);
|
|
173
|
+
if (pattern)
|
|
174
|
+
out.pattern = pattern;
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* The schema as JSON Schema, if it can say: the Standard JSON Schema
|
|
180
|
+
* interface, which zod (4.2+) implements and other libraries are adding. Of
|
|
181
|
+
* the input - what the form is filled with - not the output it parses to.
|
|
182
|
+
*/
|
|
183
|
+
function describe(schema) {
|
|
184
|
+
const converter = schema['~standard'].jsonSchema;
|
|
185
|
+
if (!converter?.input)
|
|
186
|
+
return null;
|
|
187
|
+
try {
|
|
188
|
+
return converter.input({ target: 'draft-2020-12' });
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/** The one type a property is, looking through a nullable union. */
|
|
195
|
+
function typeOf(property) {
|
|
196
|
+
const options = property.anyOf ?? property.oneOf;
|
|
197
|
+
if (options)
|
|
198
|
+
return options.find((option) => option.type !== 'null') ?? property;
|
|
199
|
+
return property;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The fields a schema describes: the type of each property, whether it is
|
|
203
|
+
* required, an enum's options. From its JSON Schema when it has one; from
|
|
204
|
+
* `overrides` alone when it does not, which must then list every field.
|
|
205
|
+
*/
|
|
206
|
+
export function fieldsOf(schema, overrides = {}) {
|
|
207
|
+
const json = describe(schema);
|
|
208
|
+
if (!json?.properties) {
|
|
209
|
+
if (Object.keys(overrides).length === 0) {
|
|
210
|
+
throw new Error(`[admin] this ${schema['~standard'].vendor} schema does not describe its fields as JSON Schema; list them in fields: { name: { type: 'text' }, ... }`);
|
|
211
|
+
}
|
|
212
|
+
return Object.entries(overrides).map(([name, override]) => withSchemas({ name, label: humanize(name), type: 'text', required: true, ...applied(override) }, override));
|
|
213
|
+
}
|
|
214
|
+
const required = new Set(json.required ?? []);
|
|
215
|
+
return Object.entries(json.properties).map(([name, property]) => {
|
|
216
|
+
const inner = typeOf(property);
|
|
217
|
+
const types = [].concat(inner.type ?? []);
|
|
218
|
+
const options = inner.enum?.filter((option) => option !== null).map((option) => optionOf(String(option)));
|
|
219
|
+
// zod writes a nullable string as type: ['string', 'null'], and a nullable url as anyOf: [{ format: 'uri' }, { type: 'null' }].
|
|
220
|
+
const nullable = [].concat(property.type ?? []).includes('null') || ((property.anyOf ?? property.oneOf)?.some((option) => option.type === 'null') ?? false);
|
|
221
|
+
const type = options ? 'select'
|
|
222
|
+
: types.includes('boolean') ? 'toggle'
|
|
223
|
+
: types.includes('number') || types.includes('integer') ? 'number'
|
|
224
|
+
: inner.format === 'email' ? 'email'
|
|
225
|
+
: inner.format === 'date' || inner.format === 'date-time' ? 'date'
|
|
226
|
+
: 'text';
|
|
227
|
+
const override = overrides[name];
|
|
228
|
+
const numeric = types.includes('number') || types.includes('integer');
|
|
229
|
+
const final = override?.relation ? 'select' : (override?.type ?? type);
|
|
230
|
+
return withSchemas({
|
|
231
|
+
name,
|
|
232
|
+
// categoryId is a Category to whoever picks it.
|
|
233
|
+
label: humanize(override?.relation ? name.replace(/_?[iI]d$/, '') : name),
|
|
234
|
+
type,
|
|
235
|
+
required: required.has(name) && !nullable,
|
|
236
|
+
...(nullable ? { nullable: true } : {}),
|
|
237
|
+
...(options ? { options } : {}),
|
|
238
|
+
...(numeric && final === 'select' ? { numeric: true } : {}),
|
|
239
|
+
// Checked for the type the field ends up as: a string made a textarea keeps its lengths, loses its pattern.
|
|
240
|
+
...constraintsOf(final, inner, types.includes('integer')),
|
|
241
|
+
...applied(override),
|
|
242
|
+
}, override);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* A submitted form's values as the schema expects them, by its fields: a
|
|
247
|
+
* resource's, a public form's, a block's. A checkbox that is off is not in
|
|
248
|
+
* FormData at all, so a toggle is whether its name arrived; an empty optional
|
|
249
|
+
* field is left out rather than sent as "".
|
|
250
|
+
*/
|
|
251
|
+
export function valuesFrom(target, form) {
|
|
252
|
+
return Object.fromEntries(target.fields.flatMap((field) => {
|
|
253
|
+
if (field.type === 'toggle')
|
|
254
|
+
return [[field.name, form.has(field.name)]];
|
|
255
|
+
if (field.type === 'blocks')
|
|
256
|
+
return [[field.name, blocksFrom(field, form)]];
|
|
257
|
+
if (field.type === 'file' || field.type === 'image')
|
|
258
|
+
return fileFrom(field, form);
|
|
259
|
+
const value = form.get(field.name);
|
|
260
|
+
if (value === null || (value === '' && !field.required))
|
|
261
|
+
return [];
|
|
262
|
+
// A number field arrives as text. Converted here, so the schema says
|
|
263
|
+
// z.number() and not z.coerce.number() - the same schema an API would
|
|
264
|
+
// use - and a field that is not a number stays text for it to refuse.
|
|
265
|
+
if ((field.type === 'number' || field.numeric) && typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {
|
|
266
|
+
return [[field.name, Number(value)]];
|
|
267
|
+
}
|
|
268
|
+
return [[field.name, value]];
|
|
269
|
+
}));
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* A file field's value: the file chosen, else the value it already had
|
|
273
|
+
* (sent back by the form beside the file input), else nothing - or null
|
|
274
|
+
* when it was emptied and may be. A kept value is the browser's word, so
|
|
275
|
+
* parse() takes it only if the record already had it.
|
|
276
|
+
*/
|
|
277
|
+
function fileFrom(field, form) {
|
|
278
|
+
const sent = form.getAll(field.name);
|
|
279
|
+
const chosen = sent.find((entry) => typeof entry !== 'string' && entry.size > 0);
|
|
280
|
+
if (chosen)
|
|
281
|
+
return [[field.name, chosen]];
|
|
282
|
+
const kept = sent.find((entry) => typeof entry === 'string' && entry !== '');
|
|
283
|
+
if (kept !== undefined)
|
|
284
|
+
return [[field.name, kept]];
|
|
285
|
+
return field.nullable && sent.length > 0 ? [[field.name, null]] : [];
|
|
286
|
+
}
|
|
287
|
+
/** A blocks field's list, read from `name.0.type`, `name.0.heading`, ... until an index is missing. */
|
|
288
|
+
function blocksFrom(field, form) {
|
|
289
|
+
const out = [];
|
|
290
|
+
for (let index = 0; form.has(`${field.name}.${index}.type`); index++) {
|
|
291
|
+
const prefix = `${field.name}.${index}.`;
|
|
292
|
+
const type = String(form.get(`${prefix}type`));
|
|
293
|
+
const spec = field.blocks?.find((candidate) => candidate.name === type);
|
|
294
|
+
if (!spec) {
|
|
295
|
+
out.push({ type, data: {} });
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
const data = valuesFrom({ fields: spec.fields.map((inner) => ({ ...inner, name: prefix + inner.name })) }, form);
|
|
299
|
+
out.push({ type, data: Object.fromEntries(Object.entries(data).map(([name, value]) => [name.slice(prefix.length), value])) });
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Each block of each blocks field checked by its own schema, its data
|
|
305
|
+
* replaced by what the schema parsed it to. A refusal is keyed
|
|
306
|
+
* `name.index.field`, where the form shows it.
|
|
307
|
+
*/
|
|
308
|
+
async function checkBlocks(fields, values, apply) {
|
|
309
|
+
const errors = {};
|
|
310
|
+
for (const field of fields) {
|
|
311
|
+
const schemas = schemasOf.get(field);
|
|
312
|
+
const list = values[field.name];
|
|
313
|
+
if (!schemas || !Array.isArray(list))
|
|
314
|
+
continue;
|
|
315
|
+
for (const [index, item] of list.entries()) {
|
|
316
|
+
const schema = schemas.get(item.type);
|
|
317
|
+
if (!schema) {
|
|
318
|
+
errors[`${field.name}.${index}`] = [`There is no ${item.type} block.`];
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
const result = await schema['~standard'].validate(item.data);
|
|
322
|
+
if (!result.issues) {
|
|
323
|
+
if (apply)
|
|
324
|
+
item.data = result.value;
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
for (const [path, messages] of Object.entries(issuesToErrors(result.issues))) {
|
|
328
|
+
errors[`${field.name}.${index}${path ? `.${path}` : ''}`] = messages;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return errors;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Finds each file field's upload, here and inside blocks. A file the field
|
|
336
|
+
* does not accept, or too large, is refused under it. A kept value the
|
|
337
|
+
* record did not have is dropped: anyone can post a string, and it would
|
|
338
|
+
* otherwise save a url of their choosing as though it had been uploaded.
|
|
339
|
+
*/
|
|
340
|
+
function uploadsIn(fields, values, had, at, uploads, refused) {
|
|
341
|
+
for (const field of fields) {
|
|
342
|
+
const path = at + field.name;
|
|
343
|
+
const value = values[field.name];
|
|
344
|
+
if (field.type === 'blocks') {
|
|
345
|
+
const before = Array.isArray(had[0]?.[field.name]) ? had[0][field.name] : [];
|
|
346
|
+
for (const [index, item] of (Array.isArray(value) ? value : []).entries()) {
|
|
347
|
+
const spec = field.blocks?.find((candidate) => candidate.name === item.type);
|
|
348
|
+
const same = before.filter((block) => block?.type === item.type && block.data).map((block) => block.data);
|
|
349
|
+
if (spec)
|
|
350
|
+
uploadsIn(spec.fields, item.data, same, `${path}.${index}.`, uploads, refused);
|
|
351
|
+
}
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (field.type !== 'file' && field.type !== 'image')
|
|
355
|
+
continue;
|
|
356
|
+
if (typeof value === 'string' && !had.some((record) => record[field.name] === value)) {
|
|
357
|
+
if (field.nullable)
|
|
358
|
+
values[field.name] = null;
|
|
359
|
+
else
|
|
360
|
+
delete values[field.name];
|
|
361
|
+
}
|
|
362
|
+
if (value instanceof Blob) {
|
|
363
|
+
const file = value;
|
|
364
|
+
if (!accepts(field.accept, file))
|
|
365
|
+
refused[path] = [field.type === 'image' ? 'Choose a PNG, JPEG, WebP, GIF or AVIF image.' : 'That type of file is not accepted here.'];
|
|
366
|
+
else if (field.maxSize !== undefined && file.size > field.maxSize)
|
|
367
|
+
refused[path] = [`Choose a file of ${sizeOf(field.maxSize)} or less.`];
|
|
368
|
+
else
|
|
369
|
+
uploads.push({ path, file, storage: storageOf.get(field), keep: (kept) => (values[field.name] = kept) });
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
if (field.required && (values[field.name] === undefined || values[field.name] === null))
|
|
373
|
+
refused[path] = ['Choose a file.'];
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/** Blocks, then the whole form, each against its schema. */
|
|
377
|
+
async function check(target, values, apply) {
|
|
378
|
+
const blocks = await checkBlocks(target.fields, values, apply);
|
|
379
|
+
const result = await target.schema['~standard'].validate(values);
|
|
380
|
+
if (!result.issues && Object.keys(blocks).length === 0)
|
|
381
|
+
return { ok: true, values: result.value };
|
|
382
|
+
return { ok: false, errors: { ...(result.issues ? issuesToErrors(result.issues) : {}), ...blocks } };
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* A submitted form checked against its schema, with each refusal under its
|
|
386
|
+
* field. Uploads are kept last: the form is checked with them still files,
|
|
387
|
+
* and only when nothing else is wrong is each one stored and the form
|
|
388
|
+
* checked again with what storage answered - so a refused save leaves no
|
|
389
|
+
* file behind.
|
|
390
|
+
*/
|
|
391
|
+
export async function parse(target, form, options = {}) {
|
|
392
|
+
const values = valuesFrom(target, form);
|
|
393
|
+
const uploads = [];
|
|
394
|
+
const refused = {};
|
|
395
|
+
uploadsIn(target.fields, values, options.current ? [options.current] : [], '', uploads, refused);
|
|
396
|
+
if (uploads.length === 0 && Object.keys(refused).length === 0)
|
|
397
|
+
return check(target, values, true);
|
|
398
|
+
const waiting = new Set(uploads.map((upload) => upload.path));
|
|
399
|
+
const first = await check(target, values, false);
|
|
400
|
+
const others = first.ok ? {} : Object.fromEntries(Object.entries(first.errors).filter(([path]) => !waiting.has(path)));
|
|
401
|
+
if (Object.keys(others).length > 0 || Object.keys(refused).length > 0)
|
|
402
|
+
return { ok: false, errors: { ...others, ...refused } };
|
|
403
|
+
for (const upload of uploads)
|
|
404
|
+
upload.keep(await upload.storage.put(upload.file, { field: upload.path, tenant: options.tenant ?? null }));
|
|
405
|
+
return check(target, values, true);
|
|
406
|
+
}
|
|
407
|
+
export function humanize(key) {
|
|
408
|
+
const words = key.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ');
|
|
409
|
+
return words.charAt(0).toUpperCase() + words.slice(1).toLowerCase();
|
|
410
|
+
}
|
|
411
|
+
/** What a bulk action's toast says: "3 products published." with `done`, else "Publish: 3 products.". */
|
|
412
|
+
export function bulkToast(bulk, count, noun) {
|
|
413
|
+
return bulk.done ? `${count} ${noun} ${bulk.done}.` : `${bulk.label}: ${count} ${noun}.`;
|
|
414
|
+
}
|
|
415
|
+
//# sourceMappingURL=resource.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A resource, described once: what its table shows, what its form takes, and\n * where its rows come from. The panel renders every page from this - nothing\n * per resource is written as a page.\n *\n * The form is a schema, not a list of widgets: the same object validates the\n * save on the server and is read for the fields it has, so a field cannot be\n * on the form and missing from the check, or the other way round. Any\n * Standard Schema validates; one that also describes itself as JSON Schema\n * (Standard JSON Schema - zod 4.2+, and others as they add it) gives its\n * fields too, and any other says them in `fields`.\n */\nimport type { ComponentType, ReactNode } from 'react'\nimport { issuesToErrors } from '@rsc-kit/core/standardSchema'\nimport type { StandardSchemaV1 } from '@rsc-kit/core/standardSchema'\nimport { accepts, IMAGE_TYPES, sizeOf } from './uploads.js'\nimport type { Storage } from './uploads.js'\n\n/** What the form validates with: any Standard Schema - zod, valibot, arktype, effect. */\nexport type FormSchema = StandardSchemaV1<any, Record<string, unknown>>\n\n/** What a schema's save hands the data source. */\nexport type ValuesOf<Schema extends FormSchema> = NonNullable<Schema['~standard']['types']>['output']\n\nexport type Format = 'text' | 'money' | 'badge' | 'boolean' | 'date' | 'relation'\n\nexport interface Column {\n key: string\n label: string\n format: Format\n sortable: boolean\n searchable: boolean\n /** Shown until the visitor hides it from the Columns menu. */\n visible: boolean\n /** Draws the cell instead of the format. Runs on the server, so it may be async and read anything. */\n cell?: (value: any, row: any) => ReactNode | Promise<ReactNode>\n /** For a relation column: the resource whose row the value names, shown by its title. */\n relation?: string\n}\n\n/** A column, chained: text('name').searchable().sortable(). */\nexport class ColumnBuilder {\n readonly #column: Column\n\n constructor(column: Column) {\n this.#column = column\n }\n\n sortable(): this {\n this.#column.sortable = true\n return this\n }\n\n searchable(): this {\n this.#column.searchable = true\n return this\n }\n\n label(label: string): this {\n this.#column.label = label\n return this\n }\n\n /** In the Columns menu, unticked until the visitor shows it. */\n hidden(): this {\n this.#column.visible = false\n return this\n }\n\n /** Draw the cell yourself: .cell((value, row) => <Avatar src={row.photo} />). */\n cell(render: NonNullable<Column['cell']>): this {\n this.#column.cell = render\n return this\n }\n\n build(): Column {\n return { ...this.#column }\n }\n}\n\n/** The column helpers, typed to the row so a misspelt key fails the typecheck. */\nexport interface Columns<Row> {\n text(key: keyof Row & string): ColumnBuilder\n money(key: keyof Row & string): ColumnBuilder\n badge(key: keyof Row & string): ColumnBuilder\n boolean(key: keyof Row & string): ColumnBuilder\n date(key: keyof Row & string): ColumnBuilder\n /**\n * A belongsTo: the value is another resource's key, shown as that row's\n * title - col.relation('categoryId', 'categories') shows \"Lighting\", not 3.\n */\n relation(key: keyof Row & string, resource: string): ColumnBuilder\n}\n\nfunction columns<Row>(): Columns<Row> {\n const make = (format: Format) => (key: string) =>\n new ColumnBuilder({ key, label: humanize(key), format, sortable: false, searchable: false, visible: true })\n\n return {\n text: make('text'),\n money: make('money'),\n badge: make('badge'),\n boolean: make('boolean'),\n date: make('date'),\n relation: (key, resource) =>\n new ColumnBuilder({ key, label: humanize(key.replace(/_?[iI]d$/, '')), format: 'relation', relation: resource, sortable: false, searchable: false, visible: true }),\n }\n}\n\nexport type FieldType = 'text' | 'textarea' | 'number' | 'select' | 'toggle' | 'email' | 'date' | 'blocks' | 'file' | 'image'\n\n/** A kind of block a blocks field offers, as the form needs it: plain data. */\nexport interface BlockSpec {\n name: string\n label: string\n fields: Field[]\n}\n\n/** One choice of a select: what is saved, and what the visitor reads. */\nexport interface Option {\n value: string\n label: string\n}\n\n/** What the form renders. Plain data: it crosses to the client component. */\nexport interface Field {\n name: string\n label: string\n type: FieldType\n required: boolean\n options?: Option[]\n /**\n * A belongsTo: the options are another resource's rows, its key saved and\n * its title shown, read when the form is drawn and scoped to the tenant.\n */\n relation?: string\n /** A select whose schema wants a number: its value is converted, as a number field's is. */\n numeric?: boolean\n /** Help under the control. */\n description?: string\n /**\n * What the browser checks before a submit, from the schema: the native\n * constraints, so a mistake is caught without a round trip and without the\n * schema in the browser. A courtesy only - the server validates every save.\n */\n min?: number\n max?: number\n minLength?: number\n maxLength?: number\n /** Whole-value, as the pattern attribute matches. */\n pattern?: string\n step?: number | 'any'\n /**\n * A blocks field's kinds of block. Its value is a list of { type, data },\n * each block's data checked by its own schema; in a form, block i's field\n * is named `name.i.field`, which is also where its errors are.\n */\n blocks?: BlockSpec[]\n /**\n * A file or image field's types, as the accept attribute says them:\n * `image/*`, `application/pdf`, `.csv`. Checked by the browser when the\n * file is chosen and by the server when it arrives.\n */\n accept?: string\n /** A file or image field's largest file, in bytes. */\n maxSize?: number\n /** The schema allows null: a file field that is emptied saves null, so the source clears it. */\n nullable?: boolean\n}\n\nexport interface Filter {\n key: string\n label: string\n options: Option[]\n}\n\nexport interface ListQuery {\n q?: string\n sort?: string\n dir: 'asc' | 'desc'\n page: number\n /**\n * Null asks for every row: a resource whose table works in the browser\n * (mode: 'client') is loaded whole, and searched, sorted and paged there.\n */\n perPage: number | null\n /** Only the resource's declared filters, each with a value. */\n filters: Record<string, string>\n /** The columns marked searchable: what q is matched against. */\n searchable: string[]\n}\n\n/** Whoever's records these are, in a panel with tenants. */\nexport interface Tenant {\n id: string\n name: string\n /** A second line under the name in the switcher: a plan, a role. */\n description?: string\n /** An image url for the switcher. Without one the first letter is shown. */\n avatar?: string\n}\n\n/** What every call to a data source is told besides its arguments. */\nexport interface SourceContext {\n /** The tenant the visitor is working in, or null in a panel without tenants. */\n tenant: Tenant | null\n}\n\n/**\n * Where a resource's rows live: five functions, and nothing about how. A\n * database (Drizzle, Prisma, SQL), a REST or GraphQL API, a Laravel app through\n * rpc(), a file, memory - the panel calls these and never anything else.\n *\n * In a panel with tenants, a source answers only for context.tenant. find()\n * returning null for another tenant's record is what makes its url a 404.\n */\nexport interface DataSource<Row, Values> {\n list(query: ListQuery, context: SourceContext): Promise<{ rows: Row[]; total: number }>\n find(id: string, context: SourceContext): Promise<Row | null>\n create(values: Values, context: SourceContext): Promise<Row>\n update(id: string, values: Values, context: SourceContext): Promise<Row>\n delete(id: string, context: SourceContext): Promise<void>\n}\n\n/**\n * Where a table's search, filters, sort and paging happen.\n *\n * 'server' each change is a request; the source is asked for one page.\n * For a table that grows: orders, users, an API with millions.\n * 'client' the source is asked once for every row (perPage: null) and the\n * browser does the rest, with no request per keystroke. For a\n * table that stays small: categories, settings, team members.\n */\nexport type TableMode = 'server' | 'client'\n\n/**\n * Something done to the rows ticked in the table: publish, archive, export.\n * `run` is given the rows themselves, each found by the source as the\n * visitor - an id the browser sent that is not theirs never reaches it.\n */\nexport interface BulkAction<Row = any> {\n /** Identifies it in the call; 'delete' is the panel's own. */\n name: string\n label: string\n /** Asked about first, as a delete is. */\n destructive?: boolean\n /** What happened to the rows, for the toast: 'published' says \"3 products published.\", as Delete says \"deleted\". */\n done?: string\n /** Returns its own words for the toast, or leaves them to `done`. */\n run: (rows: Row[], context: SourceContext) => void | string | Promise<void | string>\n}\n\nexport interface Resource<Row = any, Schema extends FormSchema = FormSchema> {\n name: string\n label: string\n singular: string\n key: keyof Row & string\n title: keyof Row & string\n source: DataSource<Row, ValuesOf<Schema>>\n columns: Column[]\n filters: Filter[]\n schema: Schema\n fields: Field[]\n perPage: number\n mode: TableMode\n /** Listed and searched, never created, edited or deleted from the panel. */\n readOnly: boolean\n bulkActions: BulkAction<Row>[]\n /** The sidebar's icon: a lucide icon, or any component. */\n icon?: ComponentType<{ className?: string }>\n description?: string\n}\n\nexport interface ResourceDefinition<Row, Schema extends FormSchema> {\n /** The url segment: /admin/products. */\n name: string\n label?: string\n singular?: string\n /** The field that identifies a row in the url. */\n key: keyof Row & string\n /** The field an edit page's heading shows. */\n title: keyof Row & string\n source: DataSource<Row, ValuesOf<Schema>>\n table: (col: Columns<Row>) => ColumnBuilder[]\n /** Enum fields of the form, offered as a select above the table. */\n filters?: (keyof Row & string)[]\n /** Any Standard Schema of an object: z.object(), v.object(), type({...}). */\n form: Schema\n /**\n * What the schema cannot say: a long string is a textarea, a label, help.\n * The whole list, for a schema that does not describe itself as JSON\n * Schema - a field missing here is then missing from the form.\n */\n fields?: Partial<Record<keyof ValuesOf<Schema> & string, FieldOverride>>\n perPage?: number\n /** Where search, filters, sort and paging happen. 'server' unless said. */\n mode?: TableMode\n /** Listed and searched, never created, edited or deleted from the panel: a log, a feed, a plugin's own records. */\n readOnly?: boolean\n /** Offered when rows are ticked, beside the panel's own Delete. */\n bulkActions?: BulkAction<Row>[]\n /** The sidebar's icon: import { Package } from 'lucide-react'. */\n icon?: ComponentType<{ className?: string }>\n /** Under the page's heading. */\n description?: string\n}\n\n/**\n * Two calls so the row is named and the schema is inferred:\n * defineResource<Product>()({ ... }).\n */\nexport function defineResource<Row>() {\n return <Schema extends FormSchema>(definition: ResourceDefinition<Row, Schema>): Resource<Row, Schema> => {\n if (!/^[a-z0-9][a-z0-9-]*$/.test(definition.name)) {\n throw new Error(`[admin] a resource's name is its url segment, lowercase letters, digits and dashes: ${JSON.stringify(definition.name)}`)\n }\n\n const fields = fieldsOf(definition.form, definition.fields ?? {})\n const bulkActions = definition.bulkActions ?? []\n const bulkNames = bulkActions.map((action) => action.name)\n\n if (bulkActions.length > 0 && definition.readOnly) {\n throw new Error(`[admin] ${definition.name} is read-only, so it has no bulk actions`)\n }\n\n for (const [index, name] of bulkNames.entries()) {\n if (name === 'delete') throw new Error(`[admin] ${definition.name}: 'delete' is the panel's own bulk action`)\n if (bulkNames.indexOf(name) !== index) throw new Error(`[admin] ${definition.name}: two bulk actions are named ${name}`)\n }\n\n return {\n name: definition.name,\n label: definition.label ?? humanize(definition.name),\n singular: definition.singular ?? humanize(definition.name).replace(/s$/, ''),\n key: definition.key,\n title: definition.title,\n source: definition.source,\n columns: definition.table(columns<Row>()).map((column) => column.build()),\n filters: (definition.filters ?? []).map((key) => {\n const field = fields.find((candidate) => candidate.name === key)\n\n if (!field?.options) throw new Error(`[admin] ${definition.name}: the filter ${key} needs an enum field in the form`)\n\n return { key, label: field.label, options: field.options }\n }),\n schema: definition.form,\n fields,\n perPage: definition.perPage ?? 10,\n mode: definition.mode ?? 'server',\n readOnly: definition.readOnly ?? false,\n bulkActions,\n ...(definition.icon ? { icon: definition.icon } : {}),\n ...(definition.description ? { description: definition.description } : {}),\n }\n }\n}\n\n/** A field as the app says it, over what the schema said. */\nexport type FieldOverride = Partial<Pick<Field, 'type' | 'label' | 'description' | 'required' | 'relation'>> & {\n /** A bare value is labelled by humanizing it: 'in_stock' reads \"In stock\". */\n options?: (string | Option)[]\n /** The kinds a blocks field offers: { type: 'blocks', blocks: [hero, features] }. */\n blocks?: BlockLike[]\n /** Where a file or image field keeps what is uploaded. Required for one. */\n storage?: Storage\n /** A file or image field's types. An image field takes PNG, JPEG, WebP, GIF and AVIF unless it says. */\n accept?: string\n /** A file or image field's largest file, in bytes. */\n maxSize?: number\n}\n\n/** What a blocks field needs of a block: its name, label, fields and schema. */\ninterface BlockLike {\n name: string\n label: string\n fields: Field[]\n schema: FormSchema\n}\n\n/**\n * A blocks field's schemas, by field. Kept beside the field rather than on\n * it, because a field crosses to the browser and a schema cannot.\n */\nconst schemasOf = new WeakMap<Field, Map<string, FormSchema>>()\n\n/** A file field's storage, kept beside it for the same reason. */\nconst storageOf = new WeakMap<Field, Storage>()\n\n/** A bare option labelled for reading, or one the app labelled itself. */\nfunction optionOf(option: string | Option): Option {\n return typeof option === 'string' ? { value: option, label: humanize(option) } : option\n}\n\n/** An override as a field: options made whole, and a relation drawn as a select. */\nfunction applied(override: FieldOverride | undefined): Partial<Field> {\n if (!override) return {}\n\n // storage never goes on the field: a field crosses to the browser.\n const { options, blocks, storage: _, ...rest } = override\n\n return {\n ...(override.relation ? { type: 'select' as FieldType } : {}),\n ...rest,\n ...(options ? { options: options.map(optionOf) } : {}),\n ...(blocks ? { type: 'blocks' as FieldType, blocks: blocks.map(({ name, label, fields }) => ({ name, label, fields })) } : {}),\n }\n}\n\n/** Keeps a blocks field's schemas, and a file field's storage, where parse() finds them. */\nfunction withSchemas(field: Field, override: FieldOverride | undefined): Field {\n if (field.type === 'file' || field.type === 'image') {\n if (!override?.storage) throw new Error(`[press] the ${field.type} field ${field.name} needs storage: where an upload is kept`)\n\n storageOf.set(field, override.storage)\n if (field.type === 'image' && field.accept === undefined) field.accept = IMAGE_TYPES\n\n return field\n }\n\n if (field.type !== 'blocks') return field\n\n if (!override?.blocks?.length) throw new Error(`[press] the blocks field ${field.name} needs blocks: [...] - the kinds it offers`)\n\n schemasOf.set(field, new Map(override.blocks.map((block) => [block.name, block.schema])))\n\n return field\n}\n\ntype Overrides = Record<string, FieldOverride | undefined>\n\ntype JsonSchema = {\n type?: string | string[]\n format?: string\n enum?: unknown[]\n const?: unknown\n anyOf?: JsonSchema[]\n oneOf?: JsonSchema[]\n properties?: Record<string, JsonSchema>\n required?: string[]\n minimum?: number\n maximum?: number\n minLength?: number\n maxLength?: number\n pattern?: string\n}\n\n/**\n * A JSON Schema pattern as the pattern attribute, or nothing. The attribute\n * matches the whole value and compiles with the v flag; a JSON Schema pattern\n * matches anywhere. So only an anchored one carries over, and only one the v\n * flag accepts - an attribute the browser cannot compile is ignored, and one\n * that meant \"contains\" would refuse what the server accepts.\n */\nfunction patternOf(pattern: string | undefined): string | undefined {\n if (!pattern?.startsWith('^') || !pattern.endsWith('$') || pattern.endsWith('\\\\$')) return undefined\n\n try {\n new RegExp(pattern, 'v')\n\n return pattern\n } catch {\n return undefined\n }\n}\n\n/** The constraints the browser can check for a field of this type. */\nfunction constraintsOf(type: FieldType, json: JsonSchema, integer: boolean): Partial<Field> {\n const out: Partial<Field> = {}\n\n if (type === 'number') {\n // zod's int() writes the safe-integer range as bounds: not a constraint anyone wrote.\n if (json.minimum !== undefined && json.minimum > Number.MIN_SAFE_INTEGER) out.min = json.minimum\n if (json.maximum !== undefined && json.maximum < Number.MAX_SAFE_INTEGER) out.max = json.maximum\n // Without step a number input refuses 12.5: 'any' unless the schema says integer.\n out.step = integer ? 1 : 'any'\n }\n\n if (type === 'text' || type === 'textarea' || type === 'email') {\n if (json.minLength !== undefined) out.minLength = json.minLength\n if (json.maxLength !== undefined) out.maxLength = json.maxLength\n }\n\n // Only plain text: an email's or a date's pattern is the library's own\n // spelling of the format, which type=\"email\" and type=\"date\" already check.\n if (type === 'text') {\n const pattern = patternOf(json.pattern)\n\n if (pattern) out.pattern = pattern\n }\n\n return out\n}\n\n/**\n * The schema as JSON Schema, if it can say: the Standard JSON Schema\n * interface, which zod (4.2+) implements and other libraries are adding. Of\n * the input - what the form is filled with - not the output it parses to.\n */\nfunction describe(schema: FormSchema): JsonSchema | null {\n const converter = (schema['~standard'] as { jsonSchema?: { input?: (options: { target: string }) => unknown } }).jsonSchema\n\n if (!converter?.input) return null\n\n try {\n return converter.input({ target: 'draft-2020-12' }) as JsonSchema\n } catch {\n return null\n }\n}\n\n/** The one type a property is, looking through a nullable union. */\nfunction typeOf(property: JsonSchema): JsonSchema {\n const options = property.anyOf ?? property.oneOf\n\n if (options) return options.find((option) => option.type !== 'null') ?? property\n\n return property\n}\n\n/**\n * The fields a schema describes: the type of each property, whether it is\n * required, an enum's options. From its JSON Schema when it has one; from\n * `overrides` alone when it does not, which must then list every field.\n */\nexport function fieldsOf(schema: FormSchema, overrides: Overrides = {}): Field[] {\n const json = describe(schema)\n\n if (!json?.properties) {\n if (Object.keys(overrides).length === 0) {\n throw new Error(\n `[admin] this ${schema['~standard'].vendor} schema does not describe its fields as JSON Schema; list them in fields: { name: { type: 'text' }, ... }`,\n )\n }\n\n return Object.entries(overrides).map(([name, override]) =>\n withSchemas({ name, label: humanize(name), type: 'text' as FieldType, required: true, ...applied(override) }, override),\n )\n }\n\n const required = new Set(json.required ?? [])\n\n return Object.entries(json.properties).map(([name, property]) => {\n const inner = typeOf(property)\n const types = ([] as string[]).concat(inner.type ?? [])\n const options = inner.enum?.filter((option) => option !== null).map((option) => optionOf(String(option)))\n // zod writes a nullable string as type: ['string', 'null'], and a nullable url as anyOf: [{ format: 'uri' }, { type: 'null' }].\n const nullable = ([] as string[]).concat(property.type ?? []).includes('null') || ((property.anyOf ?? property.oneOf)?.some((option) => option.type === 'null') ?? false)\n const type: FieldType =\n options ? 'select'\n : types.includes('boolean') ? 'toggle'\n : types.includes('number') || types.includes('integer') ? 'number'\n : inner.format === 'email' ? 'email'\n : inner.format === 'date' || inner.format === 'date-time' ? 'date'\n : 'text'\n\n const override = overrides[name]\n const numeric = types.includes('number') || types.includes('integer')\n const final = override?.relation ? 'select' : (override?.type ?? type)\n\n return withSchemas({\n name,\n // categoryId is a Category to whoever picks it.\n label: humanize(override?.relation ? name.replace(/_?[iI]d$/, '') : name),\n type,\n required: required.has(name) && !nullable,\n ...(nullable ? { nullable: true } : {}),\n ...(options ? { options } : {}),\n ...(numeric && final === 'select' ? { numeric: true } : {}),\n // Checked for the type the field ends up as: a string made a textarea keeps its lengths, loses its pattern.\n ...constraintsOf(final, inner, types.includes('integer')),\n ...applied(override),\n }, override)\n })\n}\n\n/**\n * A submitted form's values as the schema expects them, by its fields: a\n * resource's, a public form's, a block's. A checkbox that is off is not in\n * FormData at all, so a toggle is whether its name arrived; an empty optional\n * field is left out rather than sent as \"\".\n */\nexport function valuesFrom(target: { fields: Field[] }, form: FormData): Record<string, unknown> {\n return Object.fromEntries(\n target.fields.flatMap((field): [string, unknown][] => {\n if (field.type === 'toggle') return [[field.name, form.has(field.name)]]\n if (field.type === 'blocks') return [[field.name, blocksFrom(field, form)]]\n if (field.type === 'file' || field.type === 'image') return fileFrom(field, form)\n\n const value = form.get(field.name)\n\n if (value === null || (value === '' && !field.required)) return []\n\n // A number field arrives as text. Converted here, so the schema says\n // z.number() and not z.coerce.number() - the same schema an API would\n // use - and a field that is not a number stays text for it to refuse.\n if ((field.type === 'number' || field.numeric) && typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {\n return [[field.name, Number(value)]]\n }\n\n return [[field.name, value]]\n }),\n )\n}\n\n/**\n * A file field's value: the file chosen, else the value it already had\n * (sent back by the form beside the file input), else nothing - or null\n * when it was emptied and may be. A kept value is the browser's word, so\n * parse() takes it only if the record already had it.\n */\nfunction fileFrom(field: Field, form: FormData): [string, unknown][] {\n const sent = form.getAll(field.name)\n const chosen = sent.find((entry): entry is File => typeof entry !== 'string' && entry.size > 0)\n\n if (chosen) return [[field.name, chosen]]\n\n const kept = sent.find((entry) => typeof entry === 'string' && entry !== '')\n\n if (kept !== undefined) return [[field.name, kept]]\n\n return field.nullable && sent.length > 0 ? [[field.name, null]] : []\n}\n\n/** A blocks field's list, read from `name.0.type`, `name.0.heading`, ... until an index is missing. */\nfunction blocksFrom(field: Field, form: FormData): { type: string; data: Record<string, unknown> }[] {\n const out: { type: string; data: Record<string, unknown> }[] = []\n\n for (let index = 0; form.has(`${field.name}.${index}.type`); index++) {\n const prefix = `${field.name}.${index}.`\n const type = String(form.get(`${prefix}type`))\n const spec = field.blocks?.find((candidate) => candidate.name === type)\n\n if (!spec) {\n out.push({ type, data: {} })\n continue\n }\n\n const data = valuesFrom({ fields: spec.fields.map((inner) => ({ ...inner, name: prefix + inner.name })) }, form)\n\n out.push({ type, data: Object.fromEntries(Object.entries(data).map(([name, value]) => [name.slice(prefix.length), value])) })\n }\n\n return out\n}\n\n/**\n * Each block of each blocks field checked by its own schema, its data\n * replaced by what the schema parsed it to. A refusal is keyed\n * `name.index.field`, where the form shows it.\n */\nasync function checkBlocks(fields: Field[], values: Record<string, unknown>, apply: boolean): Promise<Record<string, string[]>> {\n const errors: Record<string, string[]> = {}\n\n for (const field of fields) {\n const schemas = schemasOf.get(field)\n const list = values[field.name]\n\n if (!schemas || !Array.isArray(list)) continue\n\n for (const [index, item] of (list as { type: string; data: Record<string, unknown> }[]).entries()) {\n const schema = schemas.get(item.type)\n\n if (!schema) {\n errors[`${field.name}.${index}`] = [`There is no ${item.type} block.`]\n continue\n }\n\n const result = await schema['~standard'].validate(item.data)\n\n if (!result.issues) {\n if (apply) item.data = result.value as Record<string, unknown>\n continue\n }\n\n for (const [path, messages] of Object.entries(issuesToErrors(result.issues))) {\n errors[`${field.name}.${index}${path ? `.${path}` : ''}`] = messages\n }\n }\n }\n\n return errors\n}\n\nexport type Parsed<Values> = { ok: true; values: Values } | { ok: false; errors: Record<string, string[]> }\n\n/** An upload waiting for the rest of the form to pass. */\ninterface Upload {\n path: string\n file: File\n storage: Storage\n keep: (value: string) => void\n}\n\n/**\n * Finds each file field's upload, here and inside blocks. A file the field\n * does not accept, or too large, is refused under it. A kept value the\n * record did not have is dropped: anyone can post a string, and it would\n * otherwise save a url of their choosing as though it had been uploaded.\n */\nfunction uploadsIn(fields: Field[], values: Record<string, unknown>, had: Record<string, unknown>[], at: string, uploads: Upload[], refused: Record<string, string[]>) {\n for (const field of fields) {\n const path = at + field.name\n const value = values[field.name]\n\n if (field.type === 'blocks') {\n const before = Array.isArray(had[0]?.[field.name]) ? (had[0]![field.name] as { type?: string; data?: Record<string, unknown> }[]) : []\n\n for (const [index, item] of (Array.isArray(value) ? (value as { type: string; data: Record<string, unknown> }[]) : []).entries()) {\n const spec = field.blocks?.find((candidate) => candidate.name === item.type)\n const same = before.filter((block) => block?.type === item.type && block.data).map((block) => block.data!)\n\n if (spec) uploadsIn(spec.fields, item.data, same, `${path}.${index}.`, uploads, refused)\n }\n\n continue\n }\n\n if (field.type !== 'file' && field.type !== 'image') continue\n\n if (typeof value === 'string' && !had.some((record) => record[field.name] === value)) {\n if (field.nullable) values[field.name] = null\n else delete values[field.name]\n }\n\n if (value instanceof Blob) {\n const file = value as File\n\n if (!accepts(field.accept, file)) refused[path] = [field.type === 'image' ? 'Choose a PNG, JPEG, WebP, GIF or AVIF image.' : 'That type of file is not accepted here.']\n else if (field.maxSize !== undefined && file.size > field.maxSize) refused[path] = [`Choose a file of ${sizeOf(field.maxSize)} or less.`]\n else uploads.push({ path, file, storage: storageOf.get(field)!, keep: (kept) => (values[field.name] = kept) })\n\n continue\n }\n\n if (field.required && (values[field.name] === undefined || values[field.name] === null)) refused[path] = ['Choose a file.']\n }\n}\n\n/** Blocks, then the whole form, each against its schema. */\nasync function check<Schema extends FormSchema>(target: { schema: Schema; fields: Field[] }, values: Record<string, unknown>, apply: boolean): Promise<Parsed<ValuesOf<Schema>>> {\n const blocks = await checkBlocks(target.fields, values, apply)\n const result = await target.schema['~standard'].validate(values)\n\n if (!result.issues && Object.keys(blocks).length === 0) return { ok: true, values: result.value as ValuesOf<Schema> }\n\n return { ok: false, errors: { ...(result.issues ? issuesToErrors(result.issues) : {}), ...blocks } }\n}\n\nexport interface ParseOptions {\n /** The record being edited, as it was: the values a file field may keep without uploading again. */\n current?: Record<string, unknown> | null\n /** Handed to storage with each upload. */\n tenant?: Tenant | null\n}\n\n/**\n * A submitted form checked against its schema, with each refusal under its\n * field. Uploads are kept last: the form is checked with them still files,\n * and only when nothing else is wrong is each one stored and the form\n * checked again with what storage answered - so a refused save leaves no\n * file behind.\n */\nexport async function parse<Schema extends FormSchema>(\n target: { schema: Schema; fields: Field[] },\n form: FormData,\n options: ParseOptions = {},\n): Promise<Parsed<ValuesOf<Schema>>> {\n const values = valuesFrom(target, form)\n const uploads: Upload[] = []\n const refused: Record<string, string[]> = {}\n\n uploadsIn(target.fields, values, options.current ? [options.current] : [], '', uploads, refused)\n\n if (uploads.length === 0 && Object.keys(refused).length === 0) return check(target, values, true)\n\n const waiting = new Set(uploads.map((upload) => upload.path))\n const first = await check(target, values, false)\n const others = first.ok ? {} : Object.fromEntries(Object.entries(first.errors).filter(([path]) => !waiting.has(path)))\n\n if (Object.keys(others).length > 0 || Object.keys(refused).length > 0) return { ok: false, errors: { ...others, ...refused } }\n\n for (const upload of uploads) upload.keep(await upload.storage.put(upload.file, { field: upload.path, tenant: options.tenant ?? null }))\n\n return check(target, values, true)\n}\n\nexport function humanize(key: string): string {\n const words = key.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/[_-]+/g, ' ')\n\n return words.charAt(0).toUpperCase() + words.slice(1).toLowerCase()\n}\n\n/** What a bulk action's toast says: \"3 products published.\" with `done`, else \"Publish: 3 products.\". */\nexport function bulkToast(bulk: Pick<BulkAction, 'label' | 'done'>, count: number, noun: string): string {\n return bulk.done ? `${count} ${noun} ${bulk.done}.` : `${bulk.label}: ${count} ${noun}.`\n}\n"]}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { BreadcrumbsProps } from '../admin/theme.js';
|
|
2
|
+
/**
|
|
3
|
+
* Where you are, from the url: Admin / Products / Edit. In the layout, which
|
|
4
|
+
* stays mounted across pages, so it reads the path rather than being told.
|
|
5
|
+
*/
|
|
6
|
+
export declare function PanelBreadcrumbs({ root, title, labels }: BreadcrumbsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment } from 'react';
|
|
4
|
+
import Link from '@rsc-kit/core/Link';
|
|
5
|
+
import { usePathname } from '@rsc-kit/core/usePathname';
|
|
6
|
+
import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from './components/ui/breadcrumb.js';
|
|
7
|
+
/**
|
|
8
|
+
* Where you are, from the url: Admin / Products / Edit. In the layout, which
|
|
9
|
+
* stays mounted across pages, so it reads the path rather than being told.
|
|
10
|
+
*/
|
|
11
|
+
export function PanelBreadcrumbs({ root, title, labels }) {
|
|
12
|
+
const pathname = usePathname();
|
|
13
|
+
const parts = pathname.startsWith(root) ? pathname.slice(root.length).split('/').filter(Boolean) : [];
|
|
14
|
+
const crumbs = [{ href: root, label: title }];
|
|
15
|
+
if (parts[0])
|
|
16
|
+
crumbs.push({ href: `${root}/${parts[0]}`, label: labels[parts[0]] ?? parts[0] });
|
|
17
|
+
if (parts[1])
|
|
18
|
+
crumbs.push({ href: pathname, label: parts[1] === 'new' ? 'New' : 'Edit' });
|
|
19
|
+
return (_jsx(Breadcrumb, { children: _jsx(BreadcrumbList, { children: crumbs.map((crumb, index) => (_jsxs(Fragment, { children: [index > 0 && _jsx(BreadcrumbSeparator, { className: "hidden md:block" }), _jsx(BreadcrumbItem, { className: index < crumbs.length - 1 ? 'hidden md:block' : undefined, children: index === crumbs.length - 1 ? (_jsx(BreadcrumbPage, { children: crumb.label })) : (_jsx(BreadcrumbLink, { render: _jsx(Link, { href: crumb.href }), children: crumb.label })) })] }, crumb.href))) }) }));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=breadcrumbs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../../src/shadcn/breadcrumbs.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAChC,OAAO,IAAI,MAAM,oBAAoB,CAAA;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAEvD,OAAO,EACL,UAAU,EACV,cAAc,EACd,cAAc,EACd,cAAc,EACd,cAAc,EACd,mBAAmB,GACpB,MAAM,+BAA+B,CAAA;AAEtC;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAoB;IACxE,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;IAC9B,MAAM,KAAK,GAAG,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACrG,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;IAE7C,IAAI,KAAK,CAAC,CAAC,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IAC/F,IAAI,KAAK,CAAC,CAAC,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;IAEzF,OAAO,CACL,KAAC,UAAU,cACT,KAAC,cAAc,cACZ,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAC5B,MAAC,QAAQ,eACN,KAAK,GAAG,CAAC,IAAI,KAAC,mBAAmB,IAAC,SAAS,EAAC,iBAAiB,GAAG,EACjE,KAAC,cAAc,IAAC,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,YACjF,KAAK,KAAK,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAC7B,KAAC,cAAc,cAAE,KAAK,CAAC,KAAK,GAAkB,CAC/C,CAAC,CAAC,CAAC,CACF,KAAC,cAAc,IAAC,MAAM,EAAE,KAAC,IAAI,IAAC,IAAI,EAAE,KAAK,CAAC,IAAa,GAAI,YAAG,KAAK,CAAC,KAAK,GAAkB,CAC5F,GACc,KARJ,KAAK,CAAC,IAAI,CASd,CACZ,CAAC,GACa,GACN,CACd,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\nimport { Fragment } from 'react'\nimport Link from '@rsc-kit/core/Link'\nimport { usePathname } from '@rsc-kit/core/usePathname'\nimport type { BreadcrumbsProps } from '../admin/theme.js'\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from './components/ui/breadcrumb.js'\n\n/**\n * Where you are, from the url: Admin / Products / Edit. In the layout, which\n * stays mounted across pages, so it reads the path rather than being told.\n */\nexport function PanelBreadcrumbs({ root, title, labels }: BreadcrumbsProps) {\n const pathname = usePathname()\n const parts = pathname.startsWith(root) ? pathname.slice(root.length).split('/').filter(Boolean) : []\n const crumbs = [{ href: root, label: title }]\n\n if (parts[0]) crumbs.push({ href: `${root}/${parts[0]}`, label: labels[parts[0]] ?? parts[0] })\n if (parts[1]) crumbs.push({ href: pathname, label: parts[1] === 'new' ? 'New' : 'Edit' })\n\n return (\n <Breadcrumb>\n <BreadcrumbList>\n {crumbs.map((crumb, index) => (\n <Fragment key={crumb.href}>\n {index > 0 && <BreadcrumbSeparator className=\"hidden md:block\" />}\n <BreadcrumbItem className={index < crumbs.length - 1 ? 'hidden md:block' : undefined}>\n {index === crumbs.length - 1 ? (\n <BreadcrumbPage>{crumb.label}</BreadcrumbPage>\n ) : (\n <BreadcrumbLink render={<Link href={crumb.href as never} />}>{crumb.label}</BreadcrumbLink>\n )}\n </BreadcrumbItem>\n </Fragment>\n ))}\n </BreadcrumbList>\n </Breadcrumb>\n )\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
|
|
3
|
+
import { Button } from "./button.js";
|
|
4
|
+
declare function AlertDialog({ ...props }: AlertDialogPrimitive.Root.Props): React.JSX.Element;
|
|
5
|
+
declare function AlertDialogTrigger({ ...props }: AlertDialogPrimitive.Trigger.Props): React.JSX.Element;
|
|
6
|
+
declare function AlertDialogPortal({ ...props }: AlertDialogPrimitive.Portal.Props): React.JSX.Element;
|
|
7
|
+
declare function AlertDialogOverlay({ className, ...props }: AlertDialogPrimitive.Backdrop.Props): React.JSX.Element;
|
|
8
|
+
declare function AlertDialogContent({ className, size, ...props }: AlertDialogPrimitive.Popup.Props & {
|
|
9
|
+
size?: "default" | "sm";
|
|
10
|
+
}): React.JSX.Element;
|
|
11
|
+
declare function AlertDialogHeader({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
12
|
+
declare function AlertDialogFooter({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
13
|
+
declare function AlertDialogMedia({ className, ...props }: React.ComponentProps<"div">): React.JSX.Element;
|
|
14
|
+
declare function AlertDialogTitle({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Title>): React.JSX.Element;
|
|
15
|
+
declare function AlertDialogDescription({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Description>): React.JSX.Element;
|
|
16
|
+
declare function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof Button>): React.JSX.Element;
|
|
17
|
+
declare function AlertDialogCancel({ className, variant, size, ...props }: AlertDialogPrimitive.Close.Props & Pick<React.ComponentProps<typeof Button>, "variant" | "size">): React.JSX.Element;
|
|
18
|
+
export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { AlertDialog as AlertDialogPrimitive } from "@base-ui/react/alert-dialog";
|
|
5
|
+
import { cn } from "cn";
|
|
6
|
+
import { Button } from "./button.js";
|
|
7
|
+
function AlertDialog({ ...props }) {
|
|
8
|
+
return _jsx(AlertDialogPrimitive.Root, { "data-slot": "alert-dialog", ...props });
|
|
9
|
+
}
|
|
10
|
+
function AlertDialogTrigger({ ...props }) {
|
|
11
|
+
return (_jsx(AlertDialogPrimitive.Trigger, { "data-slot": "alert-dialog-trigger", ...props }));
|
|
12
|
+
}
|
|
13
|
+
function AlertDialogPortal({ ...props }) {
|
|
14
|
+
return (_jsx(AlertDialogPrimitive.Portal, { "data-slot": "alert-dialog-portal", ...props }));
|
|
15
|
+
}
|
|
16
|
+
function AlertDialogOverlay({ className, ...props }) {
|
|
17
|
+
return (_jsx(AlertDialogPrimitive.Backdrop, { "data-slot": "alert-dialog-overlay", className: cn("fixed inset-0 isolate z-50 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0", className), ...props }));
|
|
18
|
+
}
|
|
19
|
+
function AlertDialogContent({ className, size = "default", ...props }) {
|
|
20
|
+
return (_jsxs(AlertDialogPortal, { children: [_jsx(AlertDialogOverlay, {}), _jsx(AlertDialogPrimitive.Popup, { "data-slot": "alert-dialog-content", "data-size": size, className: cn("group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className), ...props })] }));
|
|
21
|
+
}
|
|
22
|
+
function AlertDialogHeader({ className, ...props }) {
|
|
23
|
+
return (_jsx("div", { "data-slot": "alert-dialog-header", className: cn("grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]", className), ...props }));
|
|
24
|
+
}
|
|
25
|
+
function AlertDialogFooter({ className, ...props }) {
|
|
26
|
+
return (_jsx("div", { "data-slot": "alert-dialog-footer", className: cn("-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end", className), ...props }));
|
|
27
|
+
}
|
|
28
|
+
function AlertDialogMedia({ className, ...props }) {
|
|
29
|
+
return (_jsx("div", { "data-slot": "alert-dialog-media", className: cn("mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6", className), ...props }));
|
|
30
|
+
}
|
|
31
|
+
function AlertDialogTitle({ className, ...props }) {
|
|
32
|
+
return (_jsx(AlertDialogPrimitive.Title, { "data-slot": "alert-dialog-title", className: cn("text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2", className), ...props }));
|
|
33
|
+
}
|
|
34
|
+
function AlertDialogDescription({ className, ...props }) {
|
|
35
|
+
return (_jsx(AlertDialogPrimitive.Description, { "data-slot": "alert-dialog-description", className: cn("text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground", className), ...props }));
|
|
36
|
+
}
|
|
37
|
+
function AlertDialogAction({ className, ...props }) {
|
|
38
|
+
return (_jsx(Button, { "data-slot": "alert-dialog-action", className: cn(className), ...props }));
|
|
39
|
+
}
|
|
40
|
+
function AlertDialogCancel({ className, variant = "outline", size = "default", ...props }) {
|
|
41
|
+
return (_jsx(AlertDialogPrimitive.Close, { "data-slot": "alert-dialog-cancel", className: cn(className), render: _jsx(Button, { variant: variant, size: size }), ...props }));
|
|
42
|
+
}
|
|
43
|
+
export { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogMedia, AlertDialogOverlay, AlertDialogPortal, AlertDialogTitle, AlertDialogTrigger, };
|
|
44
|
+
//# sourceMappingURL=alert-dialog.js.map
|