@opensaas/stack-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +8 -0
- package/README.md +286 -0
- package/dist/components/AdminUI.d.ts +24 -0
- package/dist/components/AdminUI.d.ts.map +1 -0
- package/dist/components/AdminUI.js +48 -0
- package/dist/components/ConfirmDialog.d.ts +16 -0
- package/dist/components/ConfirmDialog.d.ts.map +1 -0
- package/dist/components/ConfirmDialog.js +11 -0
- package/dist/components/Dashboard.d.ts +12 -0
- package/dist/components/Dashboard.d.ts.map +1 -0
- package/dist/components/Dashboard.js +30 -0
- package/dist/components/ItemForm.d.ts +17 -0
- package/dist/components/ItemForm.d.ts.map +1 -0
- package/dist/components/ItemForm.js +97 -0
- package/dist/components/ItemFormClient.d.ts +22 -0
- package/dist/components/ItemFormClient.d.ts.map +1 -0
- package/dist/components/ItemFormClient.js +127 -0
- package/dist/components/ListView.d.ts +17 -0
- package/dist/components/ListView.d.ts.map +1 -0
- package/dist/components/ListView.js +76 -0
- package/dist/components/ListViewClient.d.ts +19 -0
- package/dist/components/ListViewClient.d.ts.map +1 -0
- package/dist/components/ListViewClient.js +108 -0
- package/dist/components/LoadingSpinner.d.ts +10 -0
- package/dist/components/LoadingSpinner.d.ts.map +1 -0
- package/dist/components/LoadingSpinner.js +14 -0
- package/dist/components/Navigation.d.ts +13 -0
- package/dist/components/Navigation.d.ts.map +1 -0
- package/dist/components/Navigation.js +20 -0
- package/dist/components/SkeletonLoader.d.ts +22 -0
- package/dist/components/SkeletonLoader.d.ts.map +1 -0
- package/dist/components/SkeletonLoader.js +25 -0
- package/dist/components/fields/CheckboxField.d.ts +11 -0
- package/dist/components/fields/CheckboxField.d.ts.map +1 -0
- package/dist/components/fields/CheckboxField.js +10 -0
- package/dist/components/fields/ComboboxField.d.ts +18 -0
- package/dist/components/fields/ComboboxField.d.ts.map +1 -0
- package/dist/components/fields/ComboboxField.js +32 -0
- package/dist/components/fields/FieldRenderer.d.ts +22 -0
- package/dist/components/fields/FieldRenderer.d.ts.map +1 -0
- package/dist/components/fields/FieldRenderer.js +81 -0
- package/dist/components/fields/IntegerField.d.ts +15 -0
- package/dist/components/fields/IntegerField.d.ts.map +1 -0
- package/dist/components/fields/IntegerField.js +14 -0
- package/dist/components/fields/PasswordField.d.ts +18 -0
- package/dist/components/fields/PasswordField.d.ts.map +1 -0
- package/dist/components/fields/PasswordField.js +42 -0
- package/dist/components/fields/RelationshipField.d.ts +20 -0
- package/dist/components/fields/RelationshipField.d.ts.map +1 -0
- package/dist/components/fields/RelationshipField.js +11 -0
- package/dist/components/fields/RelationshipManager.d.ts +19 -0
- package/dist/components/fields/RelationshipManager.d.ts.map +1 -0
- package/dist/components/fields/RelationshipManager.js +37 -0
- package/dist/components/fields/SelectField.d.ts +16 -0
- package/dist/components/fields/SelectField.d.ts.map +1 -0
- package/dist/components/fields/SelectField.js +11 -0
- package/dist/components/fields/TextField.d.ts +13 -0
- package/dist/components/fields/TextField.d.ts.map +1 -0
- package/dist/components/fields/TextField.js +11 -0
- package/dist/components/fields/TimestampField.d.ts +12 -0
- package/dist/components/fields/TimestampField.d.ts.map +1 -0
- package/dist/components/fields/TimestampField.js +12 -0
- package/dist/components/fields/index.d.ts +23 -0
- package/dist/components/fields/index.d.ts.map +1 -0
- package/dist/components/fields/index.js +13 -0
- package/dist/components/fields/registry.d.ts +43 -0
- package/dist/components/fields/registry.d.ts.map +1 -0
- package/dist/components/fields/registry.js +42 -0
- package/dist/components/standalone/DeleteButton.d.ts +35 -0
- package/dist/components/standalone/DeleteButton.d.ts.map +1 -0
- package/dist/components/standalone/DeleteButton.js +46 -0
- package/dist/components/standalone/ItemCreateForm.d.ts +34 -0
- package/dist/components/standalone/ItemCreateForm.d.ts.map +1 -0
- package/dist/components/standalone/ItemCreateForm.js +91 -0
- package/dist/components/standalone/ItemEditForm.d.ts +37 -0
- package/dist/components/standalone/ItemEditForm.d.ts.map +1 -0
- package/dist/components/standalone/ItemEditForm.js +112 -0
- package/dist/components/standalone/ListTable.d.ts +33 -0
- package/dist/components/standalone/ListTable.d.ts.map +1 -0
- package/dist/components/standalone/ListTable.js +94 -0
- package/dist/components/standalone/SearchBar.d.ts +29 -0
- package/dist/components/standalone/SearchBar.d.ts.map +1 -0
- package/dist/components/standalone/SearchBar.js +43 -0
- package/dist/components/standalone/index.d.ts +11 -0
- package/dist/components/standalone/index.d.ts.map +1 -0
- package/dist/components/standalone/index.js +6 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/lib/serializeFieldConfig.d.ts +43 -0
- package/dist/lib/serializeFieldConfig.d.ts.map +1 -0
- package/dist/lib/serializeFieldConfig.js +48 -0
- package/dist/lib/theme.d.ts +17 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +192 -0
- package/dist/lib/utils.d.ts +18 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +76 -0
- package/dist/primitives/button.d.ts +12 -0
- package/dist/primitives/button.d.ts.map +1 -0
- package/dist/primitives/button.js +33 -0
- package/dist/primitives/calendar.d.ts +9 -0
- package/dist/primitives/calendar.d.ts.map +1 -0
- package/dist/primitives/calendar.js +48 -0
- package/dist/primitives/card.d.ts +9 -0
- package/dist/primitives/card.d.ts.map +1 -0
- package/dist/primitives/card.js +16 -0
- package/dist/primitives/checkbox.d.ts +5 -0
- package/dist/primitives/checkbox.d.ts.map +1 -0
- package/dist/primitives/checkbox.js +7 -0
- package/dist/primitives/combobox.d.ts +14 -0
- package/dist/primitives/combobox.d.ts.map +1 -0
- package/dist/primitives/combobox.js +20 -0
- package/dist/primitives/datetime-picker.d.ts +9 -0
- package/dist/primitives/datetime-picker.d.ts.map +1 -0
- package/dist/primitives/datetime-picker.js +42 -0
- package/dist/primitives/dialog.d.ts +20 -0
- package/dist/primitives/dialog.d.ts.map +1 -0
- package/dist/primitives/dialog.js +21 -0
- package/dist/primitives/index.d.ts +14 -0
- package/dist/primitives/index.d.ts.map +1 -0
- package/dist/primitives/index.js +14 -0
- package/dist/primitives/input.d.ts +5 -0
- package/dist/primitives/input.d.ts.map +1 -0
- package/dist/primitives/input.js +8 -0
- package/dist/primitives/label.d.ts +6 -0
- package/dist/primitives/label.d.ts.map +1 -0
- package/dist/primitives/label.js +9 -0
- package/dist/primitives/popover.d.ts +7 -0
- package/dist/primitives/popover.d.ts.map +1 -0
- package/dist/primitives/popover.js +10 -0
- package/dist/primitives/select.d.ts +14 -0
- package/dist/primitives/select.d.ts.map +1 -0
- package/dist/primitives/select.js +24 -0
- package/dist/primitives/table.d.ts +11 -0
- package/dist/primitives/table.d.ts.map +1 -0
- package/dist/primitives/table.js +20 -0
- package/dist/primitives/time-picker.d.ts +8 -0
- package/dist/primitives/time-picker.d.ts.map +1 -0
- package/dist/primitives/time-picker.js +27 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +2 -0
- package/dist/server/types.d.ts +15 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +1 -0
- package/dist/styles/globals.css +1896 -0
- package/package.json +91 -0
- package/postcss.config.cjs +5 -0
- package/src/components/AdminUI.tsx +112 -0
- package/src/components/ConfirmDialog.tsx +56 -0
- package/src/components/Dashboard.tsx +134 -0
- package/src/components/ItemForm.tsx +195 -0
- package/src/components/ItemFormClient.tsx +237 -0
- package/src/components/ListView.tsx +153 -0
- package/src/components/ListViewClient.tsx +282 -0
- package/src/components/LoadingSpinner.tsx +32 -0
- package/src/components/Navigation.tsx +117 -0
- package/src/components/SkeletonLoader.tsx +82 -0
- package/src/components/fields/CheckboxField.tsx +54 -0
- package/src/components/fields/ComboboxField.tsx +127 -0
- package/src/components/fields/FieldRenderer.tsx +132 -0
- package/src/components/fields/IntegerField.tsx +68 -0
- package/src/components/fields/PasswordField.tsx +159 -0
- package/src/components/fields/RelationshipField.tsx +71 -0
- package/src/components/fields/RelationshipManager.tsx +189 -0
- package/src/components/fields/SelectField.tsx +71 -0
- package/src/components/fields/TextField.tsx +59 -0
- package/src/components/fields/TimestampField.tsx +49 -0
- package/src/components/fields/index.ts +27 -0
- package/src/components/fields/registry.ts +72 -0
- package/src/components/standalone/DeleteButton.tsx +114 -0
- package/src/components/standalone/ItemCreateForm.tsx +161 -0
- package/src/components/standalone/ItemEditForm.tsx +193 -0
- package/src/components/standalone/ListTable.tsx +211 -0
- package/src/components/standalone/SearchBar.tsx +86 -0
- package/src/components/standalone/index.ts +13 -0
- package/src/index.ts +74 -0
- package/src/lib/serializeFieldConfig.ts +88 -0
- package/src/lib/theme.ts +202 -0
- package/src/lib/utils.ts +81 -0
- package/src/primitives/button.tsx +49 -0
- package/src/primitives/calendar.tsx +160 -0
- package/src/primitives/card.tsx +58 -0
- package/src/primitives/checkbox.tsx +27 -0
- package/src/primitives/combobox.tsx +159 -0
- package/src/primitives/datetime-picker.tsx +130 -0
- package/src/primitives/dialog.tsx +108 -0
- package/src/primitives/index.ts +54 -0
- package/src/primitives/input.tsx +24 -0
- package/src/primitives/label.tsx +19 -0
- package/src/primitives/popover.tsx +31 -0
- package/src/primitives/select.tsx +158 -0
- package/src/primitives/table.tsx +91 -0
- package/src/primitives/time-picker.tsx +65 -0
- package/src/server/index.ts +3 -0
- package/src/server/types.ts +15 -0
- package/src/styles/globals.css +123 -0
- package/tailwind.config.ts +3 -0
- package/tests/components/TextField.test.tsx +94 -0
- package/tests/setup.ts +11 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { FieldConfig } from '@opensaas/stack-core';
|
|
2
|
+
export interface ItemCreateFormProps<TData = Record<string, unknown>> {
|
|
3
|
+
fields: Record<string, FieldConfig>;
|
|
4
|
+
onSubmit: (data: TData) => Promise<{
|
|
5
|
+
success: boolean;
|
|
6
|
+
error?: string;
|
|
7
|
+
}>;
|
|
8
|
+
onCancel?: () => void;
|
|
9
|
+
relationshipData?: Record<string, Array<{
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
}>>;
|
|
13
|
+
submitLabel?: string;
|
|
14
|
+
cancelLabel?: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Standalone form component for creating items
|
|
19
|
+
* Can be embedded in any custom page
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* <ItemCreateForm
|
|
24
|
+
* fields={config.lists.Post.fields}
|
|
25
|
+
* onSubmit={async (data) => {
|
|
26
|
+
* const result = await createPost(data);
|
|
27
|
+
* return { success: !!result };
|
|
28
|
+
* }}
|
|
29
|
+
* onCancel={() => router.back()}
|
|
30
|
+
* />
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function ItemCreateForm<TData = Record<string, unknown>>({ fields, onSubmit, onCancel, relationshipData, submitLabel, cancelLabel, className, }: ItemCreateFormProps<TData>): import("react/jsx-runtime").JSX.Element;
|
|
34
|
+
//# sourceMappingURL=ItemCreateForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ItemCreateForm.d.ts","sourceRoot":"","sources":["../../../src/components/standalone/ItemCreateForm.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGvD,MAAM,WAAW,mBAAmB,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAClE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACnC,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACxE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC9D,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,gBAAqB,EACrB,WAAsB,EACtB,WAAsB,EACtB,SAAS,GACV,EAAE,mBAAmB,CAAC,KAAK,CAAC,2CAoH5B"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useMemo } from 'react';
|
|
4
|
+
import { FieldRenderer } from '../fields/FieldRenderer.js';
|
|
5
|
+
import { LoadingSpinner } from '../LoadingSpinner.js';
|
|
6
|
+
import { Button } from '../../primitives/button.js';
|
|
7
|
+
import { serializeFieldConfigs } from '../../lib/serializeFieldConfig.js';
|
|
8
|
+
/**
|
|
9
|
+
* Standalone form component for creating items
|
|
10
|
+
* Can be embedded in any custom page
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <ItemCreateForm
|
|
15
|
+
* fields={config.lists.Post.fields}
|
|
16
|
+
* onSubmit={async (data) => {
|
|
17
|
+
* const result = await createPost(data);
|
|
18
|
+
* return { success: !!result };
|
|
19
|
+
* }}
|
|
20
|
+
* onCancel={() => router.back()}
|
|
21
|
+
* />
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function ItemCreateForm({ fields, onSubmit, onCancel, relationshipData = {}, submitLabel = 'Create', cancelLabel = 'Cancel', className, }) {
|
|
25
|
+
// Serialize field configs to remove non-serializable properties
|
|
26
|
+
const serializedFields = useMemo(() => serializeFieldConfigs(fields), [fields]);
|
|
27
|
+
const [isPending, setIsPending] = useState(false);
|
|
28
|
+
const [formData, setFormData] = useState({});
|
|
29
|
+
const [errors, setErrors] = useState({});
|
|
30
|
+
const [generalError, setGeneralError] = useState(null);
|
|
31
|
+
const handleFieldChange = (fieldName, value) => {
|
|
32
|
+
setFormData((prev) => ({ ...prev, [fieldName]: value }));
|
|
33
|
+
// Clear error for this field when user starts typing
|
|
34
|
+
if (errors[fieldName]) {
|
|
35
|
+
setErrors((prev) => {
|
|
36
|
+
const newErrors = { ...prev };
|
|
37
|
+
delete newErrors[fieldName];
|
|
38
|
+
return newErrors;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const handleSubmit = async (e) => {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
setErrors({});
|
|
45
|
+
setGeneralError(null);
|
|
46
|
+
setIsPending(true);
|
|
47
|
+
try {
|
|
48
|
+
// Transform relationship fields to Prisma format
|
|
49
|
+
const transformedData = {};
|
|
50
|
+
for (const [fieldName, value] of Object.entries(formData)) {
|
|
51
|
+
const fieldConfig = serializedFields[fieldName];
|
|
52
|
+
// Transform relationship fields
|
|
53
|
+
if (fieldConfig?.type === 'relationship') {
|
|
54
|
+
if (fieldConfig.many) {
|
|
55
|
+
// Many relationship: use connect format
|
|
56
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
57
|
+
transformedData[fieldName] = {
|
|
58
|
+
connect: value.map((id) => ({ id })),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
// Single relationship: use connect format
|
|
64
|
+
if (value) {
|
|
65
|
+
transformedData[fieldName] = {
|
|
66
|
+
connect: { id: value },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Non-relationship field: pass through
|
|
73
|
+
transformedData[fieldName] = value;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const result = await onSubmit(transformedData);
|
|
77
|
+
if (!result.success) {
|
|
78
|
+
setGeneralError(result.error || 'Failed to create item');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
setGeneralError(error.message || 'Failed to create item');
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
setIsPending(false);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
// Filter out system fields
|
|
89
|
+
const editableFields = Object.entries(serializedFields).filter(([key]) => !['id', 'createdAt', 'updatedAt'].includes(key));
|
|
90
|
+
return (_jsxs("form", { onSubmit: handleSubmit, className: className, children: [generalError && (_jsx("div", { className: "bg-destructive/10 border border-destructive text-destructive rounded-lg p-4 mb-6", children: _jsx("p", { className: "text-sm font-medium", children: generalError }) })), _jsx("div", { className: "space-y-6", children: editableFields.map(([fieldName, fieldConfig]) => (_jsx(FieldRenderer, { fieldName: fieldName, fieldConfig: fieldConfig, value: formData[fieldName], onChange: (value) => handleFieldChange(fieldName, value), error: errors[fieldName], disabled: isPending, mode: "edit", relationshipItems: relationshipData[fieldName] || [], relationshipLoading: false }, fieldName))) }), _jsxs("div", { className: "flex gap-3 pt-6 mt-6 border-t border-border", children: [_jsxs(Button, { type: "submit", disabled: isPending, className: "gap-2", children: [isPending && (_jsx(LoadingSpinner, { size: "sm", className: "border-primary-foreground border-t-transparent" })), isPending ? 'Creating...' : submitLabel] }), onCancel && (_jsx(Button, { type: "button", variant: "secondary", onClick: onCancel, disabled: isPending, children: cancelLabel }))] })] }));
|
|
91
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FieldConfig } from '@opensaas/stack-core';
|
|
2
|
+
export interface ItemEditFormProps<TData = Record<string, unknown>> {
|
|
3
|
+
fields: Record<string, FieldConfig>;
|
|
4
|
+
initialData: TData;
|
|
5
|
+
onSubmit: (data: TData) => Promise<{
|
|
6
|
+
success: boolean;
|
|
7
|
+
error?: string;
|
|
8
|
+
}>;
|
|
9
|
+
onCancel?: () => void;
|
|
10
|
+
relationshipData?: Record<string, Array<{
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
}>>;
|
|
14
|
+
submitLabel?: string;
|
|
15
|
+
cancelLabel?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
basePath?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Standalone form component for editing items
|
|
21
|
+
* Can be embedded in any custom page
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```tsx
|
|
25
|
+
* <ItemEditForm
|
|
26
|
+
* fields={config.lists.Post.fields}
|
|
27
|
+
* initialData={post}
|
|
28
|
+
* onSubmit={async (data) => {
|
|
29
|
+
* const result = await updatePost(postId, data);
|
|
30
|
+
* return { success: !!result };
|
|
31
|
+
* }}
|
|
32
|
+
* onCancel={() => router.back()}
|
|
33
|
+
* />
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare function ItemEditForm<TData = Record<string, unknown>>({ fields, initialData, onSubmit, onCancel, relationshipData, submitLabel, cancelLabel, className, basePath, }: ItemEditFormProps<TData>): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
//# sourceMappingURL=ItemEditForm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ItemEditForm.d.ts","sourceRoot":"","sources":["../../../src/components/standalone/ItemEditForm.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAGvD,MAAM,WAAW,iBAAiB,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAChE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACnC,WAAW,EAAE,KAAK,CAAA;IAClB,QAAQ,EAAE,CAAC,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACxE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;IACvE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC5D,MAAM,EACN,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,gBAAqB,EACrB,WAAoB,EACpB,WAAsB,EACtB,SAAS,EACT,QAAmB,GACpB,EAAE,iBAAiB,CAAC,KAAK,CAAC,2CA+I1B"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useMemo } from 'react';
|
|
4
|
+
import { FieldRenderer } from '../fields/FieldRenderer.js';
|
|
5
|
+
import { LoadingSpinner } from '../LoadingSpinner.js';
|
|
6
|
+
import { Button } from '../../primitives/button.js';
|
|
7
|
+
import { serializeFieldConfigs } from '../../lib/serializeFieldConfig.js';
|
|
8
|
+
/**
|
|
9
|
+
* Standalone form component for editing items
|
|
10
|
+
* Can be embedded in any custom page
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <ItemEditForm
|
|
15
|
+
* fields={config.lists.Post.fields}
|
|
16
|
+
* initialData={post}
|
|
17
|
+
* onSubmit={async (data) => {
|
|
18
|
+
* const result = await updatePost(postId, data);
|
|
19
|
+
* return { success: !!result };
|
|
20
|
+
* }}
|
|
21
|
+
* onCancel={() => router.back()}
|
|
22
|
+
* />
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function ItemEditForm({ fields, initialData, onSubmit, onCancel, relationshipData = {}, submitLabel = 'Save', cancelLabel = 'Cancel', className, basePath = '/admin', }) {
|
|
26
|
+
// Serialize field configs to remove non-serializable properties
|
|
27
|
+
const serializedFields = useMemo(() => serializeFieldConfigs(fields), [fields]);
|
|
28
|
+
// Apply valueForClientSerialization transformations to initial data
|
|
29
|
+
const transformedInitialData = useMemo(() => {
|
|
30
|
+
const transformed = { ...initialData };
|
|
31
|
+
for (const [fieldName, fieldConfig] of Object.entries(fields)) {
|
|
32
|
+
const fieldConfigAny = fieldConfig;
|
|
33
|
+
if (fieldConfigAny.ui?.valueForClientSerialization &&
|
|
34
|
+
typeof fieldConfigAny.ui.valueForClientSerialization === 'function') {
|
|
35
|
+
const transformer = fieldConfigAny.ui.valueForClientSerialization;
|
|
36
|
+
transformed[fieldName] = transformer({
|
|
37
|
+
value: transformed[fieldName],
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return transformed;
|
|
42
|
+
}, [initialData, fields]);
|
|
43
|
+
const [isPending, setIsPending] = useState(false);
|
|
44
|
+
const [formData, setFormData] = useState(transformedInitialData);
|
|
45
|
+
const [errors, setErrors] = useState({});
|
|
46
|
+
const [generalError, setGeneralError] = useState(null);
|
|
47
|
+
const handleFieldChange = (fieldName, value) => {
|
|
48
|
+
setFormData((prev) => ({ ...prev, [fieldName]: value }));
|
|
49
|
+
// Clear error for this field when user starts typing
|
|
50
|
+
if (errors[fieldName]) {
|
|
51
|
+
setErrors((prev) => {
|
|
52
|
+
const newErrors = { ...prev };
|
|
53
|
+
delete newErrors[fieldName];
|
|
54
|
+
return newErrors;
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const handleSubmit = async (e) => {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
setErrors({});
|
|
61
|
+
setGeneralError(null);
|
|
62
|
+
setIsPending(true);
|
|
63
|
+
try {
|
|
64
|
+
// Transform relationship fields to Prisma format
|
|
65
|
+
// Filter out password fields with isSet objects (unchanged passwords)
|
|
66
|
+
const transformedData = {};
|
|
67
|
+
for (const [fieldName, value] of Object.entries(formData)) {
|
|
68
|
+
const fieldConfig = serializedFields[fieldName];
|
|
69
|
+
// Skip password fields that have { isSet: boolean } value (not being changed)
|
|
70
|
+
if (typeof value === 'object' && value !== null && 'isSet' in value) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
// Transform relationship fields
|
|
74
|
+
if (fieldConfig?.type === 'relationship') {
|
|
75
|
+
if (fieldConfig.many) {
|
|
76
|
+
// Many relationship: use connect format
|
|
77
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
78
|
+
transformedData[fieldName] = {
|
|
79
|
+
connect: value.map((id) => ({ id })),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Single relationship: use connect format
|
|
85
|
+
if (value) {
|
|
86
|
+
transformedData[fieldName] = {
|
|
87
|
+
connect: { id: value },
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
// Non-relationship field: pass through
|
|
94
|
+
transformedData[fieldName] = value;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const result = await onSubmit(transformedData);
|
|
98
|
+
if (!result.success) {
|
|
99
|
+
setGeneralError(result.error || 'Failed to update item');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
setGeneralError(error.message || 'Failed to update item');
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
setIsPending(false);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
// Filter out system fields
|
|
110
|
+
const editableFields = Object.entries(serializedFields).filter(([key]) => !['id', 'createdAt', 'updatedAt'].includes(key));
|
|
111
|
+
return (_jsxs("form", { onSubmit: handleSubmit, className: className, children: [generalError && (_jsx("div", { className: "bg-destructive/10 border border-destructive text-destructive rounded-lg p-4 mb-6", children: _jsx("p", { className: "text-sm font-medium", children: generalError }) })), _jsx("div", { className: "space-y-6", children: editableFields.map(([fieldName, fieldConfig]) => (_jsx(FieldRenderer, { fieldName: fieldName, fieldConfig: fieldConfig, value: formData[fieldName], onChange: (value) => handleFieldChange(fieldName, value), error: errors[fieldName], disabled: isPending, mode: "edit", relationshipItems: relationshipData[fieldName] || [], relationshipLoading: false, basePath: basePath }, fieldName))) }), _jsxs("div", { className: "flex gap-3 pt-6 mt-6 border-t border-border", children: [_jsxs(Button, { type: "submit", disabled: isPending, className: "gap-2", children: [isPending && (_jsx(LoadingSpinner, { size: "sm", className: "border-primary-foreground border-t-transparent" })), isPending ? 'Saving...' : submitLabel] }), onCancel && (_jsx(Button, { type: "button", variant: "secondary", onClick: onCancel, disabled: isPending, children: cancelLabel }))] })] }));
|
|
112
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface ListTableProps {
|
|
3
|
+
items: Array<Record<string, unknown>>;
|
|
4
|
+
fieldTypes: Record<string, string>;
|
|
5
|
+
relationshipRefs?: Record<string, string>;
|
|
6
|
+
basePath?: string;
|
|
7
|
+
columns?: string[];
|
|
8
|
+
onRowClick?: (item: Record<string, unknown>) => void;
|
|
9
|
+
sortable?: boolean;
|
|
10
|
+
emptyMessage?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
renderActions?: (item: Record<string, unknown>) => React.ReactNode;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Standalone table component for displaying list data
|
|
16
|
+
* Can be embedded in any custom page
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* <ListTable
|
|
21
|
+
* items={posts}
|
|
22
|
+
* fieldTypes={{ title: 'text', status: 'select', publishedAt: 'timestamp', author: 'relationship' }}
|
|
23
|
+
* relationshipRefs={{ author: 'User.posts' }}
|
|
24
|
+
* columns={['title', 'status', 'publishedAt', 'author']}
|
|
25
|
+
* onRowClick={(post) => router.push(`/posts/${post.id}`)}
|
|
26
|
+
* renderActions={(post) => (
|
|
27
|
+
* <Button onClick={() => deletePost(post.id)}>Delete</Button>
|
|
28
|
+
* )}
|
|
29
|
+
* />
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function ListTable({ items, fieldTypes, relationshipRefs, basePath, columns, onRowClick, sortable, emptyMessage, className, renderActions, }: ListTableProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
//# sourceMappingURL=ListTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListTable.d.ts","sourceRoot":"","sources":["../../../src/components/standalone/ListTable.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAc9B,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACrC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;IAClB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;CACnE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,UAAU,EACV,gBAAgB,EAChB,QAAmB,EACnB,OAAO,EACP,UAAU,EACV,QAAe,EACf,YAA+B,EAC/B,SAAS,EACT,aAAa,GACd,EAAE,cAAc,2CAyJhB"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import Link from 'next/link';
|
|
6
|
+
import { formatFieldName, getFieldDisplayValue } from '../../lib/utils.js';
|
|
7
|
+
import { getUrlKey } from '@opensaas/stack-core';
|
|
8
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '../../primitives/table.js';
|
|
9
|
+
/**
|
|
10
|
+
* Standalone table component for displaying list data
|
|
11
|
+
* Can be embedded in any custom page
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <ListTable
|
|
16
|
+
* items={posts}
|
|
17
|
+
* fieldTypes={{ title: 'text', status: 'select', publishedAt: 'timestamp', author: 'relationship' }}
|
|
18
|
+
* relationshipRefs={{ author: 'User.posts' }}
|
|
19
|
+
* columns={['title', 'status', 'publishedAt', 'author']}
|
|
20
|
+
* onRowClick={(post) => router.push(`/posts/${post.id}`)}
|
|
21
|
+
* renderActions={(post) => (
|
|
22
|
+
* <Button onClick={() => deletePost(post.id)}>Delete</Button>
|
|
23
|
+
* )}
|
|
24
|
+
* />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function ListTable({ items, fieldTypes, relationshipRefs, basePath = '/admin', columns, onRowClick, sortable = true, emptyMessage = 'No items found', className, renderActions, }) {
|
|
28
|
+
const [sortBy, setSortBy] = useState(null);
|
|
29
|
+
const [sortOrder, setSortOrder] = useState('asc');
|
|
30
|
+
/**
|
|
31
|
+
* Render a relationship field as a clickable link or links
|
|
32
|
+
*/
|
|
33
|
+
const renderRelationshipCell = (value, fieldName) => {
|
|
34
|
+
if (!relationshipRefs) {
|
|
35
|
+
return getFieldDisplayValue(value, 'relationship');
|
|
36
|
+
}
|
|
37
|
+
const ref = relationshipRefs[fieldName];
|
|
38
|
+
if (!ref) {
|
|
39
|
+
return getFieldDisplayValue(value, 'relationship');
|
|
40
|
+
}
|
|
41
|
+
// Parse ref to get related list name
|
|
42
|
+
const [relatedListKey] = ref.split('.');
|
|
43
|
+
const relatedUrlKey = getUrlKey(relatedListKey);
|
|
44
|
+
if (!value || typeof value !== 'object') {
|
|
45
|
+
return _jsx("span", { className: "text-muted-foreground", children: "-" });
|
|
46
|
+
}
|
|
47
|
+
// Handle array of relationships (many: true)
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
if (value.length === 0)
|
|
50
|
+
return _jsx("span", { className: "text-muted-foreground", children: "-" });
|
|
51
|
+
return (_jsx("span", { className: "flex flex-wrap gap-1", children: value.map((item, idx) => {
|
|
52
|
+
if (!item || typeof item !== 'object')
|
|
53
|
+
return null;
|
|
54
|
+
const displayValue = getFieldDisplayValue(item, 'relationship');
|
|
55
|
+
const itemId = 'id' in item ? item.id : null;
|
|
56
|
+
const key = itemId || idx;
|
|
57
|
+
return (_jsxs(React.Fragment, { children: [idx > 0 && _jsx("span", { className: "text-muted-foreground", children: ", " }), _jsx(Link, { href: `${basePath}/${relatedUrlKey}/${itemId}`, className: "text-primary hover:underline", onClick: (e) => e.stopPropagation(), children: displayValue })] }, key));
|
|
58
|
+
}) }));
|
|
59
|
+
}
|
|
60
|
+
// Handle single relationship
|
|
61
|
+
const itemId = 'id' in value ? value.id : null;
|
|
62
|
+
const displayValue = getFieldDisplayValue(value, 'relationship');
|
|
63
|
+
return (_jsx(Link, { href: `${basePath}/${relatedUrlKey}/${itemId}`, className: "text-primary hover:underline", onClick: (e) => e.stopPropagation(), children: displayValue }));
|
|
64
|
+
};
|
|
65
|
+
// Determine which columns to show
|
|
66
|
+
const displayColumns = columns ||
|
|
67
|
+
Object.keys(fieldTypes).filter((key) => !['password', 'createdAt', 'updatedAt'].includes(key));
|
|
68
|
+
// Sort items if needed
|
|
69
|
+
const sortedItems = [...items];
|
|
70
|
+
if (sortBy && sortable) {
|
|
71
|
+
sortedItems.sort((a, b) => {
|
|
72
|
+
const aVal = a[sortBy];
|
|
73
|
+
const bVal = b[sortBy];
|
|
74
|
+
if (aVal === bVal)
|
|
75
|
+
return 0;
|
|
76
|
+
const comparison = String(aVal) > String(bVal) ? 1 : -1;
|
|
77
|
+
return sortOrder === 'asc' ? comparison : -comparison;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const handleSort = (column) => {
|
|
81
|
+
if (!sortable)
|
|
82
|
+
return;
|
|
83
|
+
if (sortBy === column) {
|
|
84
|
+
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
setSortBy(column);
|
|
88
|
+
setSortOrder('asc');
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
return (_jsx("div", { className: className, children: _jsx("div", { className: "rounded-lg border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [displayColumns.map((column) => (_jsx(TableHead, { className: sortable ? 'cursor-pointer hover:bg-muted/70 transition-colors' : '', onClick: () => handleSort(column), children: _jsxs("div", { className: "flex items-center space-x-1", children: [_jsx("span", { children: formatFieldName(column) }), sortable && sortBy === column && (_jsx("span", { className: "text-primary", children: sortOrder === 'asc' ? '↑' : '↓' }))] }) }, column))), renderActions && _jsx(TableHead, { className: "text-right", children: "Actions" })] }) }), _jsx(TableBody, { children: sortedItems.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: displayColumns.length + (renderActions ? 1 : 0), className: "h-24 text-center", children: emptyMessage }) })) : (sortedItems.map((item) => (_jsxs(TableRow, { className: onRowClick ? 'cursor-pointer' : '', onClick: () => onRowClick?.(item), children: [displayColumns.map((column) => (_jsx(TableCell, { children: fieldTypes[column] === 'relationship'
|
|
92
|
+
? renderRelationshipCell(item[column], column)
|
|
93
|
+
: getFieldDisplayValue(item[column], fieldTypes[column]) }, column))), renderActions && (_jsx(TableCell, { className: "text-right", onClick: (e) => e.stopPropagation(), children: renderActions(item) }))] }, String(item.id))))) })] }) }) }));
|
|
94
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface SearchBarProps {
|
|
2
|
+
onSearch?: (query: string) => void;
|
|
3
|
+
onClear?: () => void;
|
|
4
|
+
placeholder?: string;
|
|
5
|
+
defaultValue?: string;
|
|
6
|
+
searchLabel?: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Standalone search bar component
|
|
11
|
+
* Can be embedded in any custom page
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <SearchBar
|
|
16
|
+
* onSearch={(query) => {
|
|
17
|
+
* setSearchQuery(query);
|
|
18
|
+
* fetchPosts({ search: query });
|
|
19
|
+
* }}
|
|
20
|
+
* onClear={() => {
|
|
21
|
+
* setSearchQuery('');
|
|
22
|
+
* fetchPosts({});
|
|
23
|
+
* }}
|
|
24
|
+
* placeholder="Search posts..."
|
|
25
|
+
* />
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function SearchBar({ onSearch, onClear, placeholder, defaultValue, searchLabel, className, }: SearchBarProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
//# sourceMappingURL=SearchBar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SearchBar.d.ts","sourceRoot":"","sources":["../../../src/components/standalone/SearchBar.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,OAAO,EACP,WAAyB,EACzB,YAAiB,EACjB,WAAsB,EACtB,SAAS,GACV,EAAE,cAAc,2CAyChB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Input } from '../../primitives/input.js';
|
|
5
|
+
import { Button } from '../../primitives/button.js';
|
|
6
|
+
import { Card } from '../../primitives/card.js';
|
|
7
|
+
import { usePathname, useRouter } from 'next/navigation';
|
|
8
|
+
/**
|
|
9
|
+
* Standalone search bar component
|
|
10
|
+
* Can be embedded in any custom page
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <SearchBar
|
|
15
|
+
* onSearch={(query) => {
|
|
16
|
+
* setSearchQuery(query);
|
|
17
|
+
* fetchPosts({ search: query });
|
|
18
|
+
* }}
|
|
19
|
+
* onClear={() => {
|
|
20
|
+
* setSearchQuery('');
|
|
21
|
+
* fetchPosts({});
|
|
22
|
+
* }}
|
|
23
|
+
* placeholder="Search posts..."
|
|
24
|
+
* />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function SearchBar({ onSearch, onClear, placeholder = 'Search...', defaultValue = '', searchLabel = 'Search', className, }) {
|
|
28
|
+
const router = useRouter();
|
|
29
|
+
const pathname = usePathname();
|
|
30
|
+
const [searchInput, setSearchInput] = useState(defaultValue);
|
|
31
|
+
const handleSubmit = (e) => {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
if (onSearch)
|
|
34
|
+
onSearch(searchInput.trim());
|
|
35
|
+
else
|
|
36
|
+
router.push(`${pathname}?search=${searchInput.trim()}`);
|
|
37
|
+
};
|
|
38
|
+
const handleClear = () => {
|
|
39
|
+
setSearchInput('');
|
|
40
|
+
onClear?.();
|
|
41
|
+
};
|
|
42
|
+
return (_jsx(Card, { className: `p-4 ${className || ''}`, children: _jsxs("form", { onSubmit: handleSubmit, className: "flex gap-2", children: [_jsxs("div", { className: "flex-1 relative", children: [_jsx(Input, { type: "text", value: searchInput, onChange: (e) => setSearchInput(e.target.value), placeholder: placeholder, className: "pr-10" }), searchInput && (_jsx("button", { type: "button", onClick: handleClear, className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground", children: "\u2715" }))] }), _jsx(Button, { type: "submit", children: searchLabel })] }) }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { ItemCreateForm } from './ItemCreateForm.js';
|
|
2
|
+
export { ItemEditForm } from './ItemEditForm.js';
|
|
3
|
+
export { ListTable } from './ListTable.js';
|
|
4
|
+
export { SearchBar } from './SearchBar.js';
|
|
5
|
+
export { DeleteButton } from './DeleteButton.js';
|
|
6
|
+
export type { ItemCreateFormProps } from './ItemCreateForm.js';
|
|
7
|
+
export type { ItemEditFormProps } from './ItemEditForm.js';
|
|
8
|
+
export type { ListTableProps } from './ListTable.js';
|
|
9
|
+
export type { SearchBarProps } from './SearchBar.js';
|
|
10
|
+
export type { DeleteButtonProps } from './DeleteButton.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/standalone/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Standalone composable components
|
|
2
|
+
export { ItemCreateForm } from './ItemCreateForm.js';
|
|
3
|
+
export { ItemEditForm } from './ItemEditForm.js';
|
|
4
|
+
export { ListTable } from './ListTable.js';
|
|
5
|
+
export { SearchBar } from './SearchBar.js';
|
|
6
|
+
export { DeleteButton } from './DeleteButton.js';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export { AdminUI } from './components/AdminUI.js';
|
|
2
|
+
export { Dashboard } from './components/Dashboard.js';
|
|
3
|
+
export { Navigation } from './components/Navigation.js';
|
|
4
|
+
export { ListView } from './components/ListView.js';
|
|
5
|
+
export { ListViewClient } from './components/ListViewClient.js';
|
|
6
|
+
export { ItemForm } from './components/ItemForm.js';
|
|
7
|
+
export { ItemFormClient } from './components/ItemFormClient.js';
|
|
8
|
+
export { ConfirmDialog } from './components/ConfirmDialog.js';
|
|
9
|
+
export { LoadingSpinner } from './components/LoadingSpinner.js';
|
|
10
|
+
export { SkeletonLoader, TableSkeleton, FormSkeleton } from './components/SkeletonLoader.js';
|
|
11
|
+
export { TextField, IntegerField, CheckboxField, SelectField, TimestampField, PasswordField, RelationshipField, FieldRenderer, fieldComponentRegistry, registerFieldComponent, getFieldComponent, } from './components/fields/index.js';
|
|
12
|
+
export type { AdminUIProps } from './components/AdminUI.js';
|
|
13
|
+
export type { DashboardProps } from './components/Dashboard.js';
|
|
14
|
+
export type { NavigationProps } from './components/Navigation.js';
|
|
15
|
+
export type { ListViewProps } from './components/ListView.js';
|
|
16
|
+
export type { ListViewClientProps } from './components/ListViewClient.js';
|
|
17
|
+
export type { ItemFormProps } from './components/ItemForm.js';
|
|
18
|
+
export type { ItemFormClientProps } from './components/ItemFormClient.js';
|
|
19
|
+
export type { ConfirmDialogProps } from './components/ConfirmDialog.js';
|
|
20
|
+
export type { LoadingSpinnerProps } from './components/LoadingSpinner.js';
|
|
21
|
+
export type { SkeletonLoaderProps } from './components/SkeletonLoader.js';
|
|
22
|
+
export type { TextFieldProps, IntegerFieldProps, CheckboxFieldProps, SelectFieldProps, TimestampFieldProps, PasswordFieldProps, RelationshipFieldProps, FieldRendererProps, FieldComponent, FieldComponentProps, } from './components/fields/index.js';
|
|
23
|
+
export { ItemCreateForm, ItemEditForm, ListTable, SearchBar, DeleteButton, } from './components/standalone/index.js';
|
|
24
|
+
export type { ItemCreateFormProps, ItemEditFormProps, ListTableProps, SearchBarProps, DeleteButtonProps, } from './components/standalone/index.js';
|
|
25
|
+
export { cn, formatListName, formatFieldName, getFieldDisplayValue } from './lib/utils.js';
|
|
26
|
+
export { generateThemeCSS, getThemeStyleTag, presetThemes } from './lib/theme.js';
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAA;AAC/D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAG5F,OAAO,EACL,SAAS,EACT,YAAY,EACZ,aAAa,EACb,WAAW,EACX,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,sBAAsB,EACtB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,8BAA8B,CAAA;AAGrC,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAA;AAC3D,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC/D,YAAY,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AACjE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA;AACzE,YAAY,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA;AACzE,YAAY,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAA;AACvE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA;AACzE,YAAY,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAA;AAEzE,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,cAAc,EACd,mBAAmB,GACpB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EACL,cAAc,EACd,YAAY,EACZ,SAAS,EACT,SAAS,EACT,YAAY,GACb,MAAM,kCAAkC,CAAA;AAEzC,YAAY,EACV,mBAAmB,EACnB,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,iBAAiB,GAClB,MAAM,kCAAkC,CAAA;AAGzC,OAAO,EAAE,EAAE,EAAE,cAAc,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAG1F,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Main components
|
|
2
|
+
export { AdminUI } from './components/AdminUI.js';
|
|
3
|
+
export { Dashboard } from './components/Dashboard.js';
|
|
4
|
+
export { Navigation } from './components/Navigation.js';
|
|
5
|
+
export { ListView } from './components/ListView.js';
|
|
6
|
+
export { ListViewClient } from './components/ListViewClient.js';
|
|
7
|
+
export { ItemForm } from './components/ItemForm.js';
|
|
8
|
+
export { ItemFormClient } from './components/ItemFormClient.js';
|
|
9
|
+
export { ConfirmDialog } from './components/ConfirmDialog.js';
|
|
10
|
+
export { LoadingSpinner } from './components/LoadingSpinner.js';
|
|
11
|
+
export { SkeletonLoader, TableSkeleton, FormSkeleton } from './components/SkeletonLoader.js';
|
|
12
|
+
// Field components
|
|
13
|
+
export { TextField, IntegerField, CheckboxField, SelectField, TimestampField, PasswordField, RelationshipField, FieldRenderer, fieldComponentRegistry, registerFieldComponent, getFieldComponent, } from './components/fields/index.js';
|
|
14
|
+
// Standalone composable components
|
|
15
|
+
export { ItemCreateForm, ItemEditForm, ListTable, SearchBar, DeleteButton, } from './components/standalone/index.js';
|
|
16
|
+
// Utility functions
|
|
17
|
+
export { cn, formatListName, formatFieldName, getFieldDisplayValue } from './lib/utils.js';
|
|
18
|
+
// Theme utilities
|
|
19
|
+
export { generateThemeCSS, getThemeStyleTag, presetThemes } from './lib/theme.js';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { FieldConfig } from '@opensaas/stack-core';
|
|
2
|
+
import type { ComponentType } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Serializable field config for client components
|
|
5
|
+
* Strips out functions and non-serializable properties
|
|
6
|
+
*/
|
|
7
|
+
export type SerializableFieldConfig = {
|
|
8
|
+
type: string;
|
|
9
|
+
label?: string;
|
|
10
|
+
validation?: {
|
|
11
|
+
isRequired?: boolean;
|
|
12
|
+
length?: {
|
|
13
|
+
min?: number;
|
|
14
|
+
max?: number;
|
|
15
|
+
};
|
|
16
|
+
min?: number;
|
|
17
|
+
max?: number;
|
|
18
|
+
};
|
|
19
|
+
options?: Array<{
|
|
20
|
+
label: string;
|
|
21
|
+
value: string;
|
|
22
|
+
}>;
|
|
23
|
+
many?: boolean;
|
|
24
|
+
ref?: string;
|
|
25
|
+
ui?: {
|
|
26
|
+
component?: ComponentType<any>;
|
|
27
|
+
fieldType?: string;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Extract only serializable properties from a single field config
|
|
33
|
+
* Removes functions (getZodSchema, getPrismaType, getTypeScriptType)
|
|
34
|
+
* and non-serializable properties (access, hooks, typePatch, valueForClientSerialization)
|
|
35
|
+
*/
|
|
36
|
+
export declare function serializeFieldConfig(fieldConfig: FieldConfig): SerializableFieldConfig;
|
|
37
|
+
/**
|
|
38
|
+
* Extract only serializable properties from field configs
|
|
39
|
+
* Removes functions (getZodSchema, getPrismaType, getTypeScriptType)
|
|
40
|
+
* and non-serializable properties (access, hooks, typePatch)
|
|
41
|
+
*/
|
|
42
|
+
export declare function serializeFieldConfigs(fields: Record<string, FieldConfig>): Record<string, SerializableFieldConfig>;
|
|
43
|
+
//# sourceMappingURL=serializeFieldConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializeFieldConfig.d.ts","sourceRoot":"","sources":["../../src/lib/serializeFieldConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE;QACX,UAAU,CAAC,EAAE,OAAO,CAAA;QACpB,MAAM,CAAC,EAAE;YAAE,GAAG,CAAC,EAAE,MAAM,CAAC;YAAC,GAAG,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;QACvC,GAAG,CAAC,EAAE,MAAM,CAAA;QACZ,GAAG,CAAC,EAAE,MAAM,CAAA;KACb,CAAA;IACD,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjD,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,EAAE,CAAC,EAAE;QAEH,SAAS,CAAC,EAAE,aAAa,CAAC,GAAG,CAAC,CAAA;QAC9B,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KACvB,CAAA;CACF,CAAA;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,WAAW,GAAG,uBAAuB,CAsCtF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAClC,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAQzC"}
|