@fnd-platform/cms 1.0.0-alpha.1
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/LICENSE +21 -0
- package/README.md +283 -0
- package/lib/cms-project.d.ts +127 -0
- package/lib/cms-project.d.ts.map +1 -0
- package/lib/cms-project.js +343 -0
- package/lib/cms-project.js.map +1 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +20 -0
- package/lib/index.js.map +1 -0
- package/lib/options.d.ts +59 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/options.js +3 -0
- package/lib/options.js.map +1 -0
- package/lib/templates/admin-breadcrumbs.d.ts +13 -0
- package/lib/templates/admin-breadcrumbs.d.ts.map +1 -0
- package/lib/templates/admin-breadcrumbs.js +80 -0
- package/lib/templates/admin-breadcrumbs.js.map +1 -0
- package/lib/templates/admin-content-route.d.ts +18 -0
- package/lib/templates/admin-content-route.d.ts.map +1 -0
- package/lib/templates/admin-content-route.js +100 -0
- package/lib/templates/admin-content-route.js.map +1 -0
- package/lib/templates/admin-content-type-route.d.ts +9 -0
- package/lib/templates/admin-content-type-route.d.ts.map +1 -0
- package/lib/templates/admin-content-type-route.js +96 -0
- package/lib/templates/admin-content-type-route.js.map +1 -0
- package/lib/templates/admin-header.d.ts +13 -0
- package/lib/templates/admin-header.d.ts.map +1 -0
- package/lib/templates/admin-header.js +123 -0
- package/lib/templates/admin-header.js.map +1 -0
- package/lib/templates/admin-index.d.ts +9 -0
- package/lib/templates/admin-index.d.ts.map +1 -0
- package/lib/templates/admin-index.js +60 -0
- package/lib/templates/admin-index.js.map +1 -0
- package/lib/templates/admin-layout.d.ts +10 -0
- package/lib/templates/admin-layout.d.ts.map +1 -0
- package/lib/templates/admin-layout.js +46 -0
- package/lib/templates/admin-layout.js.map +1 -0
- package/lib/templates/admin-sidebar.d.ts +13 -0
- package/lib/templates/admin-sidebar.d.ts.map +1 -0
- package/lib/templates/admin-sidebar.js +149 -0
- package/lib/templates/admin-sidebar.js.map +1 -0
- package/lib/templates/content-editor.d.ts +10 -0
- package/lib/templates/content-editor.d.ts.map +1 -0
- package/lib/templates/content-editor.js +354 -0
- package/lib/templates/content-editor.js.map +1 -0
- package/lib/templates/content-schema.d.ts +10 -0
- package/lib/templates/content-schema.d.ts.map +1 -0
- package/lib/templates/content-schema.js +274 -0
- package/lib/templates/content-schema.js.map +1 -0
- package/lib/templates/content-table.d.ts +13 -0
- package/lib/templates/content-table.d.ts.map +1 -0
- package/lib/templates/content-table.js +177 -0
- package/lib/templates/content-table.js.map +1 -0
- package/lib/templates/content-types-examples.d.ts +19 -0
- package/lib/templates/content-types-examples.d.ts.map +1 -0
- package/lib/templates/content-types-examples.js +275 -0
- package/lib/templates/content-types-examples.js.map +1 -0
- package/lib/templates/content-types-registry.d.ts +10 -0
- package/lib/templates/content-types-registry.d.ts.map +1 -0
- package/lib/templates/content-types-registry.js +87 -0
- package/lib/templates/content-types-registry.js.map +1 -0
- package/lib/templates/content-types.d.ts +10 -0
- package/lib/templates/content-types.d.ts.map +1 -0
- package/lib/templates/content-types.js +384 -0
- package/lib/templates/content-types.js.map +1 -0
- package/lib/templates/dashboard-stats.d.ts +13 -0
- package/lib/templates/dashboard-stats.d.ts.map +1 -0
- package/lib/templates/dashboard-stats.js +117 -0
- package/lib/templates/dashboard-stats.js.map +1 -0
- package/lib/templates/editor/index.d.ts +6 -0
- package/lib/templates/editor/index.d.ts.map +1 -0
- package/lib/templates/editor/index.js +21 -0
- package/lib/templates/editor/index.js.map +1 -0
- package/lib/templates/editor/rich-text-editor.d.ts +7 -0
- package/lib/templates/editor/rich-text-editor.d.ts.map +1 -0
- package/lib/templates/editor/rich-text-editor.js +115 -0
- package/lib/templates/editor/rich-text-editor.js.map +1 -0
- package/lib/templates/editor/toolbar.d.ts +7 -0
- package/lib/templates/editor/toolbar.d.ts.map +1 -0
- package/lib/templates/editor/toolbar.js +272 -0
- package/lib/templates/editor/toolbar.js.map +1 -0
- package/lib/templates/form-fields/boolean-field.d.ts +7 -0
- package/lib/templates/form-fields/boolean-field.d.ts.map +1 -0
- package/lib/templates/form-fields/boolean-field.js +76 -0
- package/lib/templates/form-fields/boolean-field.js.map +1 -0
- package/lib/templates/form-fields/date-field.d.ts +7 -0
- package/lib/templates/form-fields/date-field.d.ts.map +1 -0
- package/lib/templates/form-fields/date-field.js +61 -0
- package/lib/templates/form-fields/date-field.js.map +1 -0
- package/lib/templates/form-fields/datetime-field.d.ts +7 -0
- package/lib/templates/form-fields/datetime-field.d.ts.map +1 -0
- package/lib/templates/form-fields/datetime-field.js +87 -0
- package/lib/templates/form-fields/datetime-field.js.map +1 -0
- package/lib/templates/form-fields/index.d.ts +23 -0
- package/lib/templates/form-fields/index.d.ts.map +1 -0
- package/lib/templates/form-fields/index.js +275 -0
- package/lib/templates/form-fields/index.js.map +1 -0
- package/lib/templates/form-fields/media-field.d.ts +10 -0
- package/lib/templates/form-fields/media-field.d.ts.map +1 -0
- package/lib/templates/form-fields/media-field.js +225 -0
- package/lib/templates/form-fields/media-field.js.map +1 -0
- package/lib/templates/form-fields/multiselect-field.d.ts +7 -0
- package/lib/templates/form-fields/multiselect-field.d.ts.map +1 -0
- package/lib/templates/form-fields/multiselect-field.js +121 -0
- package/lib/templates/form-fields/multiselect-field.js.map +1 -0
- package/lib/templates/form-fields/number-field.d.ts +7 -0
- package/lib/templates/form-fields/number-field.d.ts.map +1 -0
- package/lib/templates/form-fields/number-field.js +87 -0
- package/lib/templates/form-fields/number-field.js.map +1 -0
- package/lib/templates/form-fields/reference-field.d.ts +9 -0
- package/lib/templates/form-fields/reference-field.d.ts.map +1 -0
- package/lib/templates/form-fields/reference-field.js +145 -0
- package/lib/templates/form-fields/reference-field.js.map +1 -0
- package/lib/templates/form-fields/richtext-field.d.ts +9 -0
- package/lib/templates/form-fields/richtext-field.d.ts.map +1 -0
- package/lib/templates/form-fields/richtext-field.js +60 -0
- package/lib/templates/form-fields/richtext-field.js.map +1 -0
- package/lib/templates/form-fields/select-field.d.ts +7 -0
- package/lib/templates/form-fields/select-field.d.ts.map +1 -0
- package/lib/templates/form-fields/select-field.js +70 -0
- package/lib/templates/form-fields/select-field.js.map +1 -0
- package/lib/templates/form-fields/slug-field.d.ts +7 -0
- package/lib/templates/form-fields/slug-field.d.ts.map +1 -0
- package/lib/templates/form-fields/slug-field.js +143 -0
- package/lib/templates/form-fields/slug-field.js.map +1 -0
- package/lib/templates/form-fields/tags-field.d.ts +7 -0
- package/lib/templates/form-fields/tags-field.d.ts.map +1 -0
- package/lib/templates/form-fields/tags-field.js +172 -0
- package/lib/templates/form-fields/tags-field.js.map +1 -0
- package/lib/templates/form-fields/text-field.d.ts +7 -0
- package/lib/templates/form-fields/text-field.d.ts.map +1 -0
- package/lib/templates/form-fields/text-field.js +63 -0
- package/lib/templates/form-fields/text-field.js.map +1 -0
- package/lib/templates/form-fields/textarea-field.d.ts +7 -0
- package/lib/templates/form-fields/textarea-field.d.ts.map +1 -0
- package/lib/templates/form-fields/textarea-field.js +64 -0
- package/lib/templates/form-fields/textarea-field.js.map +1 -0
- package/lib/templates/index.d.ts +34 -0
- package/lib/templates/index.d.ts.map +1 -0
- package/lib/templates/index.js +92 -0
- package/lib/templates/index.js.map +1 -0
- package/lib/templates/media/index.d.ts +12 -0
- package/lib/templates/media/index.d.ts.map +1 -0
- package/lib/templates/media/index.js +50 -0
- package/lib/templates/media/index.js.map +1 -0
- package/lib/templates/media/media-api.d.ts +13 -0
- package/lib/templates/media/media-api.d.ts.map +1 -0
- package/lib/templates/media/media-api.js +274 -0
- package/lib/templates/media/media-api.js.map +1 -0
- package/lib/templates/media/media-grid.d.ts +14 -0
- package/lib/templates/media/media-grid.d.ts.map +1 -0
- package/lib/templates/media/media-grid.js +314 -0
- package/lib/templates/media/media-grid.js.map +1 -0
- package/lib/templates/media/media-library-route.d.ts +13 -0
- package/lib/templates/media/media-library-route.d.ts.map +1 -0
- package/lib/templates/media/media-library-route.js +105 -0
- package/lib/templates/media/media-library-route.js.map +1 -0
- package/lib/templates/media/media-picker.d.ts +13 -0
- package/lib/templates/media/media-picker.d.ts.map +1 -0
- package/lib/templates/media/media-picker.js +152 -0
- package/lib/templates/media/media-picker.js.map +1 -0
- package/lib/templates/media/media-uploader.d.ts +14 -0
- package/lib/templates/media/media-uploader.d.ts.map +1 -0
- package/lib/templates/media/media-uploader.js +318 -0
- package/lib/templates/media/media-uploader.js.map +1 -0
- package/lib/templates/recent-content.d.ts +13 -0
- package/lib/templates/recent-content.d.ts.map +1 -0
- package/lib/templates/recent-content.js +138 -0
- package/lib/templates/recent-content.js.map +1 -0
- package/lib/templates/slug-utils.d.ts +10 -0
- package/lib/templates/slug-utils.d.ts.map +1 -0
- package/lib/templates/slug-utils.js +194 -0
- package/lib/templates/slug-utils.js.map +1 -0
- package/lib/templates/ui-avatar.d.ts +8 -0
- package/lib/templates/ui-avatar.d.ts.map +1 -0
- package/lib/templates/ui-avatar.js +60 -0
- package/lib/templates/ui-avatar.js.map +1 -0
- package/lib/templates/ui-badge.d.ts +8 -0
- package/lib/templates/ui-badge.d.ts.map +1 -0
- package/lib/templates/ui-badge.js +52 -0
- package/lib/templates/ui-badge.js.map +1 -0
- package/lib/templates/ui-dialog.d.ts +10 -0
- package/lib/templates/ui-dialog.d.ts.map +1 -0
- package/lib/templates/ui-dialog.js +134 -0
- package/lib/templates/ui-dialog.js.map +1 -0
- package/lib/templates/ui-dropdown-menu.d.ts +8 -0
- package/lib/templates/ui-dropdown-menu.d.ts.map +1 -0
- package/lib/templates/ui-dropdown-menu.js +210 -0
- package/lib/templates/ui-dropdown-menu.js.map +1 -0
- package/lib/templates/ui-popover.d.ts +8 -0
- package/lib/templates/ui-popover.d.ts.map +1 -0
- package/lib/templates/ui-popover.js +43 -0
- package/lib/templates/ui-popover.js.map +1 -0
- package/lib/templates/ui-progress.d.ts +10 -0
- package/lib/templates/ui-progress.d.ts.map +1 -0
- package/lib/templates/ui-progress.js +40 -0
- package/lib/templates/ui-progress.js.map +1 -0
- package/lib/templates/ui-table.d.ts +8 -0
- package/lib/templates/ui-table.d.ts.map +1 -0
- package/lib/templates/ui-table.js +129 -0
- package/lib/templates/ui-table.js.map +1 -0
- package/lib/templates/ui-tabs.d.ts +10 -0
- package/lib/templates/ui-tabs.d.ts.map +1 -0
- package/lib/templates/ui-tabs.js +67 -0
- package/lib/templates/ui-tabs.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getNumberFieldTemplate = getNumberFieldTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the number field component template.
|
|
6
|
+
*
|
|
7
|
+
* @returns Template string for app/components/form-fields/number-field.tsx
|
|
8
|
+
*/
|
|
9
|
+
function getNumberFieldTemplate() {
|
|
10
|
+
return `import type { NumberFieldDefinition } from '~/lib/content-types';
|
|
11
|
+
import { Input } from '~/components/ui/input';
|
|
12
|
+
import { Label } from '~/components/ui/label';
|
|
13
|
+
import { cn } from '~/lib/utils';
|
|
14
|
+
import type { FieldProps } from './index';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Numeric input field.
|
|
18
|
+
*/
|
|
19
|
+
export function NumberField({
|
|
20
|
+
field,
|
|
21
|
+
value,
|
|
22
|
+
onChange,
|
|
23
|
+
error,
|
|
24
|
+
disabled,
|
|
25
|
+
}: FieldProps<NumberFieldDefinition>) {
|
|
26
|
+
const id = \`field-\${field.name}\`;
|
|
27
|
+
|
|
28
|
+
const handleChange = (inputValue: string) => {
|
|
29
|
+
if (inputValue === '') {
|
|
30
|
+
onChange(undefined);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const numValue = field.decimal
|
|
35
|
+
? parseFloat(inputValue)
|
|
36
|
+
: parseInt(inputValue, 10);
|
|
37
|
+
|
|
38
|
+
if (!isNaN(numValue)) {
|
|
39
|
+
onChange(numValue);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="space-y-2">
|
|
45
|
+
<Label htmlFor={id} className={cn(field.required && "after:content-['*'] after:ml-0.5 after:text-destructive")}>
|
|
46
|
+
{field.label}
|
|
47
|
+
</Label>
|
|
48
|
+
<Input
|
|
49
|
+
id={id}
|
|
50
|
+
name={field.name}
|
|
51
|
+
type="number"
|
|
52
|
+
value={value !== undefined && value !== null ? String(value) : ''}
|
|
53
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
54
|
+
placeholder={field.placeholder}
|
|
55
|
+
disabled={disabled}
|
|
56
|
+
min={field.min}
|
|
57
|
+
max={field.max}
|
|
58
|
+
step={field.step ?? (field.decimal ? 'any' : 1)}
|
|
59
|
+
className={cn(error && 'border-destructive')}
|
|
60
|
+
aria-invalid={!!error}
|
|
61
|
+
aria-describedby={error ? \`\${id}-error\` : field.description ? \`\${id}-description\` : undefined}
|
|
62
|
+
/>
|
|
63
|
+
{field.description && !error && (
|
|
64
|
+
<p id={\`\${id}-description\`} className="text-sm text-muted-foreground">
|
|
65
|
+
{field.description}
|
|
66
|
+
</p>
|
|
67
|
+
)}
|
|
68
|
+
{(field.min !== undefined || field.max !== undefined) && !error && !field.description && (
|
|
69
|
+
<p className="text-sm text-muted-foreground">
|
|
70
|
+
{field.min !== undefined && field.max !== undefined
|
|
71
|
+
? \`Value must be between \${field.min} and \${field.max}\`
|
|
72
|
+
: field.min !== undefined
|
|
73
|
+
? \`Minimum value: \${field.min}\`
|
|
74
|
+
: \`Maximum value: \${field.max}\`}
|
|
75
|
+
</p>
|
|
76
|
+
)}
|
|
77
|
+
{error && (
|
|
78
|
+
<p id={\`\${id}-error\`} className="text-sm text-destructive">
|
|
79
|
+
{error}
|
|
80
|
+
</p>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=number-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number-field.js","sourceRoot":"","sources":["../../../src/templates/form-fields/number-field.ts"],"names":[],"mappings":";;AAKA,wDA6EC;AAlFD;;;;GAIG;AACH,SAAgB,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2ER,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the reference field component template.
|
|
3
|
+
*
|
|
4
|
+
* This is a placeholder that will be fully implemented in a later sprint.
|
|
5
|
+
*
|
|
6
|
+
* @returns Template string for app/components/form-fields/reference-field.tsx
|
|
7
|
+
*/
|
|
8
|
+
export declare function getReferenceFieldTemplate(): string;
|
|
9
|
+
//# sourceMappingURL=reference-field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference-field.d.ts","sourceRoot":"","sources":["../../../src/templates/form-fields/reference-field.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAqIlD"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getReferenceFieldTemplate = getReferenceFieldTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the reference field component template.
|
|
6
|
+
*
|
|
7
|
+
* This is a placeholder that will be fully implemented in a later sprint.
|
|
8
|
+
*
|
|
9
|
+
* @returns Template string for app/components/form-fields/reference-field.tsx
|
|
10
|
+
*/
|
|
11
|
+
function getReferenceFieldTemplate() {
|
|
12
|
+
return `import { Link2, X, Search } from 'lucide-react';
|
|
13
|
+
import type { ReferenceFieldDefinition } from '~/lib/content-types';
|
|
14
|
+
import { Label } from '~/components/ui/label';
|
|
15
|
+
import { Button } from '~/components/ui/button';
|
|
16
|
+
import { Badge } from '~/components/ui/badge';
|
|
17
|
+
import { cn } from '~/lib/utils';
|
|
18
|
+
import type { FieldProps } from './index';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Content reference field.
|
|
22
|
+
*
|
|
23
|
+
* Note: This is a placeholder component. Full reference picking
|
|
24
|
+
* functionality will be implemented in a later sprint with a
|
|
25
|
+
* content browser modal.
|
|
26
|
+
*/
|
|
27
|
+
export function ReferenceField({
|
|
28
|
+
field,
|
|
29
|
+
value,
|
|
30
|
+
onChange,
|
|
31
|
+
error,
|
|
32
|
+
disabled,
|
|
33
|
+
}: FieldProps<ReferenceFieldDefinition>) {
|
|
34
|
+
const id = \`field-\${field.name}\`;
|
|
35
|
+
const references = field.multiple
|
|
36
|
+
? (Array.isArray(value) ? value : [])
|
|
37
|
+
: (value ? [value as string] : []);
|
|
38
|
+
|
|
39
|
+
const handleRemove = (refId: string) => {
|
|
40
|
+
if (disabled) return;
|
|
41
|
+
if (field.multiple) {
|
|
42
|
+
onChange((references as string[]).filter((r) => r !== refId));
|
|
43
|
+
} else {
|
|
44
|
+
onChange('');
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="space-y-2">
|
|
50
|
+
<Label className={cn(field.required && "after:content-['*'] after:ml-0.5 after:text-destructive")}>
|
|
51
|
+
{field.label}
|
|
52
|
+
</Label>
|
|
53
|
+
<div
|
|
54
|
+
className={cn(
|
|
55
|
+
'border rounded-md p-4',
|
|
56
|
+
error && 'border-destructive'
|
|
57
|
+
)}
|
|
58
|
+
>
|
|
59
|
+
{references.length > 0 ? (
|
|
60
|
+
<div className="space-y-2">
|
|
61
|
+
{references.map((refId) => (
|
|
62
|
+
<div
|
|
63
|
+
key={refId}
|
|
64
|
+
className="flex items-center justify-between gap-2 p-2 bg-muted rounded-md"
|
|
65
|
+
>
|
|
66
|
+
<div className="flex items-center gap-2">
|
|
67
|
+
<Link2 className="h-4 w-4 text-muted-foreground" />
|
|
68
|
+
<span className="text-sm font-mono">{refId}</span>
|
|
69
|
+
</div>
|
|
70
|
+
<Button
|
|
71
|
+
type="button"
|
|
72
|
+
variant="ghost"
|
|
73
|
+
size="icon"
|
|
74
|
+
className="h-6 w-6"
|
|
75
|
+
onClick={() => handleRemove(refId)}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
>
|
|
78
|
+
<X className="h-3 w-3" />
|
|
79
|
+
</Button>
|
|
80
|
+
</div>
|
|
81
|
+
))}
|
|
82
|
+
{field.multiple && (
|
|
83
|
+
<Button
|
|
84
|
+
type="button"
|
|
85
|
+
variant="outline"
|
|
86
|
+
size="sm"
|
|
87
|
+
className="w-full"
|
|
88
|
+
disabled
|
|
89
|
+
>
|
|
90
|
+
<Search className="h-4 w-4 mr-2" />
|
|
91
|
+
Add Reference
|
|
92
|
+
</Button>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
) : (
|
|
96
|
+
<div className="text-center py-6">
|
|
97
|
+
<div className="mx-auto w-12 h-12 rounded-full bg-muted flex items-center justify-center mb-3">
|
|
98
|
+
<Link2 className="h-6 w-6 text-muted-foreground" />
|
|
99
|
+
</div>
|
|
100
|
+
<p className="text-sm font-medium mb-1">Content Reference</p>
|
|
101
|
+
<p className="text-xs text-muted-foreground mb-3">
|
|
102
|
+
Link to {field.contentTypes.join(', ')}
|
|
103
|
+
</p>
|
|
104
|
+
<Button type="button" variant="outline" size="sm" disabled>
|
|
105
|
+
<Search className="h-4 w-4 mr-2" />
|
|
106
|
+
Browse Content
|
|
107
|
+
</Button>
|
|
108
|
+
<p className="text-xs text-muted-foreground mt-2">
|
|
109
|
+
Reference browser coming soon
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
{/* Hidden inputs for form submission */}
|
|
115
|
+
{field.multiple ? (
|
|
116
|
+
references.map((refId) => (
|
|
117
|
+
<input key={refId} type="hidden" name={\`\${field.name}[]\`} value={refId} />
|
|
118
|
+
))
|
|
119
|
+
) : references.length > 0 ? (
|
|
120
|
+
<input type="hidden" name={field.name} value={references[0]} />
|
|
121
|
+
) : null}
|
|
122
|
+
<div className="flex gap-2 flex-wrap">
|
|
123
|
+
<span className="text-sm text-muted-foreground">Accepts:</span>
|
|
124
|
+
{field.contentTypes.map((type) => (
|
|
125
|
+
<Badge key={type} variant="outline" className="text-xs">
|
|
126
|
+
{type}
|
|
127
|
+
</Badge>
|
|
128
|
+
))}
|
|
129
|
+
</div>
|
|
130
|
+
{field.description && !error && (
|
|
131
|
+
<p id={\`\${id}-description\`} className="text-sm text-muted-foreground">
|
|
132
|
+
{field.description}
|
|
133
|
+
</p>
|
|
134
|
+
)}
|
|
135
|
+
{error && (
|
|
136
|
+
<p id={\`\${id}-error\`} className="text-sm text-destructive">
|
|
137
|
+
{error}
|
|
138
|
+
</p>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=reference-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference-field.js","sourceRoot":"","sources":["../../../src/templates/form-fields/reference-field.ts"],"names":[],"mappings":";;AAOA,8DAqIC;AA5ID;;;;;;GAMG;AACH,SAAgB,yBAAyB;IACvC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmIR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates the rich text field component template.
|
|
3
|
+
*
|
|
4
|
+
* Uses Tiptap-based RichTextEditor for WYSIWYG content editing.
|
|
5
|
+
*
|
|
6
|
+
* @returns Template string for app/components/form-fields/richtext-field.tsx
|
|
7
|
+
*/
|
|
8
|
+
export declare function getRichtextFieldTemplate(): string;
|
|
9
|
+
//# sourceMappingURL=richtext-field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"richtext-field.d.ts","sourceRoot":"","sources":["../../../src/templates/form-fields/richtext-field.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAgDjD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getRichtextFieldTemplate = getRichtextFieldTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the rich text field component template.
|
|
6
|
+
*
|
|
7
|
+
* Uses Tiptap-based RichTextEditor for WYSIWYG content editing.
|
|
8
|
+
*
|
|
9
|
+
* @returns Template string for app/components/form-fields/richtext-field.tsx
|
|
10
|
+
*/
|
|
11
|
+
function getRichtextFieldTemplate() {
|
|
12
|
+
return `import type { RichtextFieldDefinition } from '~/lib/content-types';
|
|
13
|
+
import { Label } from '~/components/ui/label';
|
|
14
|
+
import { RichTextEditor } from '~/components/editor/rich-text-editor';
|
|
15
|
+
import { cn } from '~/lib/utils';
|
|
16
|
+
import type { FieldProps } from './index';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Rich text editor field.
|
|
20
|
+
*
|
|
21
|
+
* Provides a full WYSIWYG editing experience using Tiptap.
|
|
22
|
+
* Supports bold, italic, headings, lists, code blocks, links, and images.
|
|
23
|
+
*/
|
|
24
|
+
export function RichtextField({
|
|
25
|
+
field,
|
|
26
|
+
value,
|
|
27
|
+
onChange,
|
|
28
|
+
error,
|
|
29
|
+
disabled,
|
|
30
|
+
}: FieldProps<RichtextFieldDefinition>) {
|
|
31
|
+
const id = \`field-\${field.name}\`;
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="space-y-2">
|
|
35
|
+
<Label htmlFor={id} className={cn(field.required && "after:content-['*'] after:ml-0.5 after:text-destructive")}>
|
|
36
|
+
{field.label}
|
|
37
|
+
</Label>
|
|
38
|
+
<RichTextEditor
|
|
39
|
+
value={(value as string) ?? ''}
|
|
40
|
+
onChange={onChange}
|
|
41
|
+
placeholder={field.placeholder ?? 'Start writing...'}
|
|
42
|
+
disabled={disabled}
|
|
43
|
+
error={!!error}
|
|
44
|
+
/>
|
|
45
|
+
{field.description && !error && (
|
|
46
|
+
<p id={\`\${id}-description\`} className="text-sm text-muted-foreground">
|
|
47
|
+
{field.description}
|
|
48
|
+
</p>
|
|
49
|
+
)}
|
|
50
|
+
{error && (
|
|
51
|
+
<p id={\`\${id}-error\`} className="text-sm text-destructive">
|
|
52
|
+
{error}
|
|
53
|
+
</p>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=richtext-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"richtext-field.js","sourceRoot":"","sources":["../../../src/templates/form-fields/richtext-field.ts"],"names":[],"mappings":";;AAOA,4DAgDC;AAvDD;;;;;;GAMG;AACH,SAAgB,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8CR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-field.d.ts","sourceRoot":"","sources":["../../../src/templates/form-fields/select-field.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CA4D/C"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getSelectFieldTemplate = getSelectFieldTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the select field component template.
|
|
6
|
+
*
|
|
7
|
+
* @returns Template string for app/components/form-fields/select-field.tsx
|
|
8
|
+
*/
|
|
9
|
+
function getSelectFieldTemplate() {
|
|
10
|
+
return `import type { SelectFieldDefinition } from '~/lib/content-types';
|
|
11
|
+
import { Label } from '~/components/ui/label';
|
|
12
|
+
import { cn } from '~/lib/utils';
|
|
13
|
+
import type { FieldProps } from './index';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Single select dropdown field.
|
|
17
|
+
*/
|
|
18
|
+
export function SelectField({
|
|
19
|
+
field,
|
|
20
|
+
value,
|
|
21
|
+
onChange,
|
|
22
|
+
error,
|
|
23
|
+
disabled,
|
|
24
|
+
}: FieldProps<SelectFieldDefinition>) {
|
|
25
|
+
const id = \`field-\${field.name}\`;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="space-y-2">
|
|
29
|
+
<Label htmlFor={id} className={cn(field.required && "after:content-['*'] after:ml-0.5 after:text-destructive")}>
|
|
30
|
+
{field.label}
|
|
31
|
+
</Label>
|
|
32
|
+
<select
|
|
33
|
+
id={id}
|
|
34
|
+
name={field.name}
|
|
35
|
+
value={(value as string) ?? ''}
|
|
36
|
+
onChange={(e) => onChange(e.target.value)}
|
|
37
|
+
disabled={disabled}
|
|
38
|
+
className={cn(
|
|
39
|
+
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
40
|
+
error && 'border-destructive',
|
|
41
|
+
!value && 'text-muted-foreground'
|
|
42
|
+
)}
|
|
43
|
+
aria-invalid={!!error}
|
|
44
|
+
aria-describedby={error ? \`\${id}-error\` : field.description ? \`\${id}-description\` : undefined}
|
|
45
|
+
>
|
|
46
|
+
<option value="" disabled>
|
|
47
|
+
{field.placeholder ?? 'Select an option...'}
|
|
48
|
+
</option>
|
|
49
|
+
{field.options.map((option) => (
|
|
50
|
+
<option key={option.value} value={option.value}>
|
|
51
|
+
{option.label}
|
|
52
|
+
</option>
|
|
53
|
+
))}
|
|
54
|
+
</select>
|
|
55
|
+
{field.description && !error && (
|
|
56
|
+
<p id={\`\${id}-description\`} className="text-sm text-muted-foreground">
|
|
57
|
+
{field.description}
|
|
58
|
+
</p>
|
|
59
|
+
)}
|
|
60
|
+
{error && (
|
|
61
|
+
<p id={\`\${id}-error\`} className="text-sm text-destructive">
|
|
62
|
+
{error}
|
|
63
|
+
</p>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=select-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select-field.js","sourceRoot":"","sources":["../../../src/templates/form-fields/select-field.ts"],"names":[],"mappings":";;AAKA,wDA4DC;AAjED;;;;GAIG;AACH,SAAgB,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0DR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug-field.d.ts","sourceRoot":"","sources":["../../../src/templates/form-fields/slug-field.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAqI7C"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
3
|
+
exports.getSlugFieldTemplate = getSlugFieldTemplate;
|
|
4
|
+
/**
|
|
5
|
+
* Generates the slug field component template.
|
|
6
|
+
*
|
|
7
|
+
* @returns Template string for app/components/form-fields/slug-field.tsx
|
|
8
|
+
*/
|
|
9
|
+
function getSlugFieldTemplate() {
|
|
10
|
+
return `import { useState, useEffect, useCallback } from 'react';
|
|
11
|
+
import { Lock, Unlock, RefreshCw } from 'lucide-react';
|
|
12
|
+
import type { SlugFieldDefinition } from '~/lib/content-types';
|
|
13
|
+
import { Input } from '~/components/ui/input';
|
|
14
|
+
import { Label } from '~/components/ui/label';
|
|
15
|
+
import { Button } from '~/components/ui/button';
|
|
16
|
+
import { cn } from '~/lib/utils';
|
|
17
|
+
import { generateSlug } from '~/lib/slug-utils';
|
|
18
|
+
import type { FieldProps } from './index';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* URL slug field with auto-generation from source field.
|
|
22
|
+
*/
|
|
23
|
+
export function SlugField({
|
|
24
|
+
field,
|
|
25
|
+
value,
|
|
26
|
+
onChange,
|
|
27
|
+
error,
|
|
28
|
+
disabled,
|
|
29
|
+
formData,
|
|
30
|
+
}: FieldProps<SlugFieldDefinition>) {
|
|
31
|
+
const id = \`field-\${field.name}\`;
|
|
32
|
+
const [isLocked, setIsLocked] = useState(true);
|
|
33
|
+
const [hasBeenEdited, setHasBeenEdited] = useState(false);
|
|
34
|
+
|
|
35
|
+
// Get the source field value for auto-generation
|
|
36
|
+
const sourceValue = formData?.[field.sourceField] as string | undefined;
|
|
37
|
+
|
|
38
|
+
// Auto-generate slug from source field when locked
|
|
39
|
+
const regenerateSlug = useCallback(() => {
|
|
40
|
+
if (sourceValue) {
|
|
41
|
+
const newSlug = generateSlug(sourceValue);
|
|
42
|
+
onChange(newSlug);
|
|
43
|
+
}
|
|
44
|
+
}, [sourceValue, onChange]);
|
|
45
|
+
|
|
46
|
+
// Auto-update slug when source changes and slug is locked
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (isLocked && !hasBeenEdited && sourceValue) {
|
|
49
|
+
regenerateSlug();
|
|
50
|
+
}
|
|
51
|
+
}, [isLocked, hasBeenEdited, sourceValue, regenerateSlug]);
|
|
52
|
+
|
|
53
|
+
const handleChange = (newValue: string) => {
|
|
54
|
+
// Normalize to valid slug characters
|
|
55
|
+
const normalized = newValue.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
|
|
56
|
+
onChange(normalized);
|
|
57
|
+
setHasBeenEdited(true);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const toggleLock = () => {
|
|
61
|
+
if (isLocked) {
|
|
62
|
+
setIsLocked(false);
|
|
63
|
+
} else {
|
|
64
|
+
setIsLocked(true);
|
|
65
|
+
setHasBeenEdited(false);
|
|
66
|
+
regenerateSlug();
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className="space-y-2">
|
|
72
|
+
<Label htmlFor={id} className={cn(field.required && "after:content-['*'] after:ml-0.5 after:text-destructive")}>
|
|
73
|
+
{field.label}
|
|
74
|
+
</Label>
|
|
75
|
+
<div className="flex gap-2">
|
|
76
|
+
{field.prefix && (
|
|
77
|
+
<span className="flex items-center px-3 text-sm text-muted-foreground bg-muted rounded-l-md border border-r-0">
|
|
78
|
+
{field.prefix}
|
|
79
|
+
</span>
|
|
80
|
+
)}
|
|
81
|
+
<div className="relative flex-1">
|
|
82
|
+
<Input
|
|
83
|
+
id={id}
|
|
84
|
+
name={field.name}
|
|
85
|
+
type="text"
|
|
86
|
+
value={(value as string) ?? ''}
|
|
87
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
88
|
+
disabled={disabled || isLocked}
|
|
89
|
+
className={cn(
|
|
90
|
+
field.prefix && 'rounded-l-none',
|
|
91
|
+
'pr-20',
|
|
92
|
+
error && 'border-destructive'
|
|
93
|
+
)}
|
|
94
|
+
aria-invalid={!!error}
|
|
95
|
+
aria-describedby={error ? \`\${id}-error\` : field.description ? \`\${id}-description\` : undefined}
|
|
96
|
+
/>
|
|
97
|
+
<div className="absolute right-1 top-1/2 -translate-y-1/2 flex gap-1">
|
|
98
|
+
<Button
|
|
99
|
+
type="button"
|
|
100
|
+
variant="ghost"
|
|
101
|
+
size="icon"
|
|
102
|
+
className="h-7 w-7"
|
|
103
|
+
onClick={regenerateSlug}
|
|
104
|
+
disabled={disabled || !sourceValue}
|
|
105
|
+
title="Regenerate from title"
|
|
106
|
+
>
|
|
107
|
+
<RefreshCw className="h-4 w-4" />
|
|
108
|
+
</Button>
|
|
109
|
+
<Button
|
|
110
|
+
type="button"
|
|
111
|
+
variant="ghost"
|
|
112
|
+
size="icon"
|
|
113
|
+
className="h-7 w-7"
|
|
114
|
+
onClick={toggleLock}
|
|
115
|
+
disabled={disabled}
|
|
116
|
+
title={isLocked ? 'Unlock for manual editing' : 'Lock to auto-generate'}
|
|
117
|
+
>
|
|
118
|
+
{isLocked ? <Lock className="h-4 w-4" /> : <Unlock className="h-4 w-4" />}
|
|
119
|
+
</Button>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
{field.description && !error && (
|
|
124
|
+
<p id={\`\${id}-description\`} className="text-sm text-muted-foreground">
|
|
125
|
+
{field.description}
|
|
126
|
+
</p>
|
|
127
|
+
)}
|
|
128
|
+
{!field.description && !error && (
|
|
129
|
+
<p className="text-sm text-muted-foreground">
|
|
130
|
+
{isLocked ? 'Auto-generated from ' + field.sourceField + '. Click unlock to edit manually.' : 'Manual editing enabled. Click lock to auto-generate.'}
|
|
131
|
+
</p>
|
|
132
|
+
)}
|
|
133
|
+
{error && (
|
|
134
|
+
<p id={\`\${id}-error\`} className="text-sm text-destructive">
|
|
135
|
+
{error}
|
|
136
|
+
</p>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
`;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=slug-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slug-field.js","sourceRoot":"","sources":["../../../src/templates/form-fields/slug-field.ts"],"names":[],"mappings":";;AAKA,oDAqIC;AA1ID;;;;GAIG;AACH,SAAgB,oBAAoB;IAClC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmIR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tags-field.d.ts","sourceRoot":"","sources":["../../../src/templates/form-fields/tags-field.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAkK7C"}
|