@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,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { getFieldComponent } from './registry.js';
|
|
4
|
+
import { formatFieldName } from '../../lib/utils.js';
|
|
5
|
+
import { getUrlKey } from '@opensaas/stack-core';
|
|
6
|
+
/**
|
|
7
|
+
* Internal component that receives the resolved Component
|
|
8
|
+
*/
|
|
9
|
+
function FieldRendererInner({ Component, fieldName, fieldConfig, value, onChange, error, disabled, mode, relationshipItems, relationshipLoading, basePath,
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
}) {
|
|
12
|
+
const label = fieldConfig.label || formatFieldName(fieldName);
|
|
13
|
+
const isRequired = fieldConfig.validation &&
|
|
14
|
+
typeof fieldConfig.validation === 'object' &&
|
|
15
|
+
fieldConfig.validation !== null &&
|
|
16
|
+
'isRequired' in fieldConfig.validation
|
|
17
|
+
? fieldConfig.validation.isRequired
|
|
18
|
+
: false;
|
|
19
|
+
// Build props based on field type
|
|
20
|
+
const baseProps = {
|
|
21
|
+
name: fieldName,
|
|
22
|
+
value,
|
|
23
|
+
onChange,
|
|
24
|
+
label,
|
|
25
|
+
error,
|
|
26
|
+
disabled,
|
|
27
|
+
required: isRequired,
|
|
28
|
+
mode,
|
|
29
|
+
};
|
|
30
|
+
// Add field-type-specific props
|
|
31
|
+
const specificProps = {};
|
|
32
|
+
if (fieldConfig.type === 'select' && 'options' in fieldConfig && fieldConfig.options) {
|
|
33
|
+
specificProps.options = fieldConfig.options.map((opt) => typeof opt === 'string' ? { label: opt, value: opt } : opt);
|
|
34
|
+
}
|
|
35
|
+
if (fieldConfig.type === 'password') {
|
|
36
|
+
specificProps.showConfirm = mode === 'edit';
|
|
37
|
+
}
|
|
38
|
+
if (fieldConfig.type === 'relationship') {
|
|
39
|
+
specificProps.items = relationshipItems;
|
|
40
|
+
specificProps.isLoading = relationshipLoading;
|
|
41
|
+
specificProps.many = fieldConfig.many || false;
|
|
42
|
+
// Extract related list key from ref (format: 'ListName.fieldName')
|
|
43
|
+
if (fieldConfig.ref) {
|
|
44
|
+
const [relatedListName] = fieldConfig.ref.split('.');
|
|
45
|
+
specificProps.relatedListKey = getUrlKey(relatedListName);
|
|
46
|
+
specificProps.basePath = basePath;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Pass through any UI options from fieldConfig.ui (excluding component and fieldType)
|
|
50
|
+
if (fieldConfig.ui) {
|
|
51
|
+
const { _component, _fieldType, ...uiOptions } = fieldConfig.ui;
|
|
52
|
+
Object.assign(specificProps, uiOptions);
|
|
53
|
+
}
|
|
54
|
+
const allProps = { ...baseProps, ...specificProps };
|
|
55
|
+
return _jsx(Component, { ...allProps });
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Factory component that renders the appropriate field type
|
|
59
|
+
* based on the field configuration and component registry
|
|
60
|
+
*/
|
|
61
|
+
export function FieldRenderer(props) {
|
|
62
|
+
const { fieldName, fieldConfig, mode = 'edit' } = props;
|
|
63
|
+
const label = fieldConfig.label || formatFieldName(fieldName);
|
|
64
|
+
// Skip rendering ID and timestamp fields in forms
|
|
65
|
+
if (mode === 'edit' && ['id', 'createdAt', 'updatedAt'].includes(fieldName)) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
// Get component from:
|
|
69
|
+
// 1. Per-field component override (ui.component)
|
|
70
|
+
// 2. Custom field type override (ui.fieldType) - uses global registry
|
|
71
|
+
// 3. Default field type (fieldConfig.type) - uses global registry
|
|
72
|
+
const Component = fieldConfig.ui?.component ||
|
|
73
|
+
(fieldConfig.ui?.fieldType
|
|
74
|
+
? getFieldComponent(fieldConfig.ui.fieldType)
|
|
75
|
+
: getFieldComponent(fieldConfig.type));
|
|
76
|
+
if (!Component) {
|
|
77
|
+
console.warn(`No component registered for field type: ${fieldConfig.type}`);
|
|
78
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium text-muted-foreground", children: String(label) }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Unsupported field type: ", fieldConfig.type] })] }));
|
|
79
|
+
}
|
|
80
|
+
return _jsx(FieldRendererInner, { ...props, Component: Component });
|
|
81
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface IntegerFieldProps {
|
|
2
|
+
name: string;
|
|
3
|
+
value: number | null;
|
|
4
|
+
onChange: (value: number | null) => void;
|
|
5
|
+
label: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
mode?: 'read' | 'edit';
|
|
11
|
+
min?: number;
|
|
12
|
+
max?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function IntegerField({ name, value, onChange, label, placeholder, error, disabled, required, mode, min, max, }: IntegerFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=IntegerField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IntegerField.d.ts","sourceRoot":"","sources":["../../../src/components/fields/IntegerField.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,KAAK,EACL,WAAW,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAa,EACb,GAAG,EACH,GAAG,GACJ,EAAE,iBAAiB,2CAmCnB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Input } from '../../primitives/input.js';
|
|
4
|
+
import { Label } from '../../primitives/label.js';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
|
+
export function IntegerField({ name, value, onChange, label, placeholder, error, disabled, required, mode = 'edit', min, max, }) {
|
|
7
|
+
if (mode === 'read') {
|
|
8
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-muted-foreground", children: label }), _jsx("p", { className: "text-sm", children: value !== null ? value : '-' })] }));
|
|
9
|
+
}
|
|
10
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: name, children: [label, required && _jsx("span", { className: "text-destructive ml-1", children: "*" })] }), _jsx(Input, { id: name, name: name, type: "number", value: value ?? '', onChange: (e) => {
|
|
11
|
+
const val = e.target.value;
|
|
12
|
+
onChange(val === '' ? null : parseInt(val, 10));
|
|
13
|
+
}, placeholder: placeholder, disabled: disabled, required: required, min: min, max: max, className: cn(error && 'border-destructive') }), error && _jsx("p", { className: "text-sm text-destructive", children: error })] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface PasswordFieldProps {
|
|
2
|
+
name: string;
|
|
3
|
+
value: string | {
|
|
4
|
+
isSet: boolean;
|
|
5
|
+
};
|
|
6
|
+
onChange: (value: string | {
|
|
7
|
+
isSet: boolean;
|
|
8
|
+
} | undefined) => void;
|
|
9
|
+
label: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
mode?: 'read' | 'edit';
|
|
15
|
+
showConfirm?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function PasswordField({ name, value, onChange, label, placeholder, error, disabled, required, mode, showConfirm, }: PasswordFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=PasswordField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PasswordField.d.ts","sourceRoot":"","sources":["../../../src/components/fields/PasswordField.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,CAAA;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,SAAS,KAAK,IAAI,CAAA;IAClE,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,OAAO,CAAA;CACtB;AAED,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,KAAK,EACL,WAAW,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAa,EACb,WAAkB,GACnB,EAAE,kBAAkB,2CA8HpB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Input } from '../../primitives/input.js';
|
|
4
|
+
import { Label } from '../../primitives/label.js';
|
|
5
|
+
import { Button } from '../../primitives/button.js';
|
|
6
|
+
import { cn } from '../../lib/utils.js';
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
export function PasswordField({ name, value, onChange, label, placeholder, error, disabled, required, mode = 'edit', showConfirm = true, }) {
|
|
9
|
+
// Check if value is the isSet object
|
|
10
|
+
const isSetObject = typeof value === 'object' && value !== null && 'isSet' in value;
|
|
11
|
+
const isPasswordSet = isSetObject ? value.isSet : false;
|
|
12
|
+
const [isChangingPassword, setIsChangingPassword] = useState(false);
|
|
13
|
+
const [passwordValue, setPasswordValue] = useState('');
|
|
14
|
+
const [confirmValue, setConfirmValue] = useState('');
|
|
15
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
16
|
+
if (mode === 'read') {
|
|
17
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-muted-foreground", children: label }), _jsx("p", { className: "text-sm", children: isPasswordSet ? '••••••••' : 'Not set' })] }));
|
|
18
|
+
}
|
|
19
|
+
// If not changing password and it's set, show the button
|
|
20
|
+
if (!isChangingPassword && isSetObject) {
|
|
21
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: label }), _jsx("div", { children: _jsx(Button, { type: "button", variant: "outline", onClick: () => setIsChangingPassword(true), disabled: disabled, children: isPasswordSet ? 'Change Password' : 'Set Password' }) })] }));
|
|
22
|
+
}
|
|
23
|
+
const confirmError = showConfirm && passwordValue !== confirmValue && confirmValue !== ''
|
|
24
|
+
? 'Passwords do not match'
|
|
25
|
+
: undefined;
|
|
26
|
+
const handleCancel = () => {
|
|
27
|
+
setIsChangingPassword(false);
|
|
28
|
+
setPasswordValue('');
|
|
29
|
+
setConfirmValue('');
|
|
30
|
+
setShowPassword(false);
|
|
31
|
+
// Reset to the isSet object
|
|
32
|
+
if (isSetObject) {
|
|
33
|
+
onChange(value);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const handlePasswordChange = (newValue) => {
|
|
37
|
+
setPasswordValue(newValue);
|
|
38
|
+
// Update the parent with the actual password string
|
|
39
|
+
onChange(newValue || undefined);
|
|
40
|
+
};
|
|
41
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: name, children: [label, required && !isPasswordSet && _jsx("span", { className: "text-destructive ml-1", children: "*" })] }), _jsxs("div", { className: "relative", children: [_jsx(Input, { id: name, name: name, type: showPassword ? 'text' : 'password', value: passwordValue, onChange: (e) => handlePasswordChange(e.target.value), placeholder: placeholder, disabled: disabled, required: required && !isPasswordSet, className: cn('pr-10', error && 'border-destructive') }), _jsx("button", { type: "button", onClick: () => setShowPassword(!showPassword), className: "absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground", children: showPassword ? '👁️' : '👁️🗨️' })] }), error && _jsx("p", { className: "text-sm text-destructive", children: error })] }), showConfirm && (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: `${name}-confirm`, children: ["Confirm ", label, required && !isPasswordSet && _jsx("span", { className: "text-destructive ml-1", children: "*" })] }), _jsx(Input, { id: `${name}-confirm`, name: `${name}-confirm`, type: showPassword ? 'text' : 'password', value: confirmValue, onChange: (e) => setConfirmValue(e.target.value), placeholder: `Confirm ${placeholder || label.toLowerCase()}`, disabled: disabled, required: required && !isPasswordSet, className: cn(confirmError && 'border-destructive') }), confirmError && _jsx("p", { className: "text-sm text-destructive", children: confirmError })] })), isSetObject && (_jsx("div", { children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: handleCancel, disabled: disabled, children: "Cancel" }) }))] }));
|
|
42
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface RelationshipFieldProps {
|
|
2
|
+
name: string;
|
|
3
|
+
value: string | string[] | null;
|
|
4
|
+
onChange: (value: string | string[] | null) => void;
|
|
5
|
+
label: string;
|
|
6
|
+
items: Array<{
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}>;
|
|
10
|
+
error?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
mode?: 'read' | 'edit';
|
|
14
|
+
isLoading?: boolean;
|
|
15
|
+
many?: boolean;
|
|
16
|
+
relatedListKey?: string;
|
|
17
|
+
basePath?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function RelationshipField({ name, value, onChange, label, items, error, disabled, required, mode, isLoading, many, relatedListKey, basePath, }: RelationshipFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=RelationshipField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RelationshipField.d.ts","sourceRoot":"","sources":["../../../src/components/fields/RelationshipField.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAAA;IAC/B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,KAAK,IAAI,CAAA;IACnD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,KAAK,EACL,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAa,EACb,SAAiB,EACjB,IAAY,EACZ,cAAc,EACd,QAAQ,GACT,EAAE,sBAAsB,2CAmCxB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { ComboboxField } from './ComboboxField.js';
|
|
4
|
+
import { RelationshipManager } from './RelationshipManager.js';
|
|
5
|
+
export function RelationshipField({ name, value, onChange, label, items, error, disabled, required, mode = 'edit', isLoading = false, many = false, relatedListKey, basePath, }) {
|
|
6
|
+
// Delegate to specialized components based on cardinality
|
|
7
|
+
if (many) {
|
|
8
|
+
return (_jsx(RelationshipManager, { name: name, value: Array.isArray(value) ? value : [], onChange: onChange, label: label, items: items, error: error, disabled: disabled, required: required, mode: mode, isLoading: isLoading, relatedListKey: relatedListKey, basePath: basePath }));
|
|
9
|
+
}
|
|
10
|
+
return (_jsx(ComboboxField, { name: name, value: typeof value === 'string' ? value : null, onChange: onChange, label: label, items: items, error: error, disabled: disabled, required: required, mode: mode, isLoading: isLoading }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface RelationshipManagerProps {
|
|
2
|
+
name: string;
|
|
3
|
+
value: string[];
|
|
4
|
+
onChange: (value: string[]) => void;
|
|
5
|
+
label: string;
|
|
6
|
+
items: Array<{
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}>;
|
|
10
|
+
error?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
mode?: 'read' | 'edit';
|
|
14
|
+
isLoading?: boolean;
|
|
15
|
+
relatedListKey?: string;
|
|
16
|
+
basePath?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function RelationshipManager({ name: _name, value, onChange, label, items, error, disabled, required, mode, isLoading, relatedListKey, basePath, }: RelationshipManagerProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
//# sourceMappingURL=RelationshipManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RelationshipManager.d.ts","sourceRoot":"","sources":["../../../src/components/fields/RelationshipManager.tsx"],"names":[],"mappings":"AAwBA,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACtB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EAAE,KAAK,EACX,KAAK,EACL,QAAQ,EACR,KAAK,EACL,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAa,EACb,SAAiB,EACjB,cAAc,EACd,QAAmB,GACpB,EAAE,wBAAwB,2CAwI1B"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '../../primitives/table.js';
|
|
6
|
+
import { Combobox, ComboboxTrigger, ComboboxContent, ComboboxSearch, ComboboxList, ComboboxEmpty, ComboboxItem, } from '../../primitives/combobox.js';
|
|
7
|
+
import { Button } from '../../primitives/button.js';
|
|
8
|
+
export function RelationshipManager({ name: _name, value, onChange, label, items, error, disabled, required, mode = 'edit', isLoading = false, relatedListKey, basePath = '/admin', }) {
|
|
9
|
+
const [showConnectModal, setShowConnectModal] = useState(false);
|
|
10
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
11
|
+
const selectedIds = Array.isArray(value) ? value : [];
|
|
12
|
+
const selectedItems = items.filter((item) => selectedIds.includes(item.id));
|
|
13
|
+
const availableItems = items.filter((item) => !selectedIds.includes(item.id));
|
|
14
|
+
// Read mode
|
|
15
|
+
if (mode === 'read') {
|
|
16
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsx("label", { className: "text-sm font-medium text-muted-foreground", children: label }), _jsx("p", { className: "text-sm", children: selectedItems.length > 0 ? selectedItems.map((item) => item.label).join(', ') : '-' })] }));
|
|
17
|
+
}
|
|
18
|
+
// Filter available items based on search
|
|
19
|
+
const filteredAvailableItems = searchQuery
|
|
20
|
+
? availableItems.filter((item) => item.label.toLowerCase().includes(searchQuery.toLowerCase()))
|
|
21
|
+
: availableItems;
|
|
22
|
+
const handleRemove = (itemId) => {
|
|
23
|
+
onChange(selectedIds.filter((id) => id !== itemId));
|
|
24
|
+
};
|
|
25
|
+
const handleConnect = (itemId) => {
|
|
26
|
+
onChange([...selectedIds, itemId]);
|
|
27
|
+
setShowConnectModal(false);
|
|
28
|
+
setSearchQuery('');
|
|
29
|
+
};
|
|
30
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsxs("label", { className: "text-sm font-medium", children: [label, required && _jsx("span", { className: "text-destructive ml-1", children: "*" })] }), selectedItems.length > 0 ? (_jsx("div", { className: "rounded-md border border-input", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Name" }), _jsx(TableHead, { className: "w-[100px] text-right", children: "Actions" })] }) }), _jsx(TableBody, { children: selectedItems.map((item) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: relatedListKey ? (_jsx(Link, { href: `${basePath}/${relatedListKey}/${item.id}`, className: "text-primary hover:underline", children: item.label })) : (item.label) }), _jsx(TableCell, { className: "text-right", children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => handleRemove(item.id), disabled: disabled, children: "Remove" }) })] }, item.id))) })] }) })) : (_jsx("div", { className: "rounded-md border border-input border-dashed p-8 text-center", children: _jsx("p", { className: "text-sm text-muted-foreground", children: "No items connected. Click \"Connect Existing\" to add items." }) })), _jsx("div", { className: "flex gap-2", children: _jsxs(Combobox, { open: showConnectModal, onOpenChange: setShowConnectModal, children: [_jsx(ComboboxTrigger, { disabled: disabled || isLoading || availableItems.length === 0, className: "h-9 px-3", children: _jsx("span", { children: isLoading ? 'Loading...' : 'Connect Existing' }) }), _jsxs(ComboboxContent, { children: [_jsx(ComboboxSearch, { placeholder: "Search...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), onKeyDown: (e) => {
|
|
31
|
+
if (e.key === 'Enter') {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
}
|
|
34
|
+
} }), _jsx(ComboboxList, { children: filteredAvailableItems.length === 0 ? (_jsx(ComboboxEmpty, { children: availableItems.length === 0
|
|
35
|
+
? 'All items are already connected'
|
|
36
|
+
: 'No results found' })) : (filteredAvailableItems.map((item) => (_jsx(ComboboxItem, { onClick: () => handleConnect(item.id), children: item.label }, item.id)))) })] })] }) }), error && _jsx("p", { className: "text-sm text-destructive mt-2", children: error })] }));
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface SelectFieldProps {
|
|
2
|
+
name: string;
|
|
3
|
+
value: string | null;
|
|
4
|
+
onChange: (value: string | null) => void;
|
|
5
|
+
label: string;
|
|
6
|
+
options: Array<{
|
|
7
|
+
label: string;
|
|
8
|
+
value: string;
|
|
9
|
+
}>;
|
|
10
|
+
error?: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
mode?: 'read' | 'edit';
|
|
14
|
+
}
|
|
15
|
+
export declare function SelectField({ name, value, onChange, label, options, error, disabled, required, mode, }: SelectFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
//# sourceMappingURL=SelectField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectField.d.ts","sourceRoot":"","sources":["../../../src/components/fields/SelectField.tsx"],"names":[],"mappings":"AAWA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACxC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAChD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACvB;AAED,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,KAAK,EACL,OAAO,EACP,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAa,GACd,EAAE,gBAAgB,2CAqClB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../primitives/select.js';
|
|
4
|
+
import { Label } from '../../primitives/label.js';
|
|
5
|
+
export function SelectField({ name, value, onChange, label, options, error, disabled, required, mode = 'edit', }) {
|
|
6
|
+
if (mode === 'read') {
|
|
7
|
+
const selectedOption = options.find((opt) => opt.value === value);
|
|
8
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-muted-foreground", children: label }), _jsx("p", { className: "text-sm", children: selectedOption?.label || '-' })] }));
|
|
9
|
+
}
|
|
10
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: name, children: [label, required && _jsx("span", { className: "text-destructive ml-1", children: "*" })] }), _jsxs(Select, { value: value || undefined, onValueChange: (val) => onChange(val || null), disabled: disabled, required: required, children: [_jsx(SelectTrigger, { id: name, className: error ? 'border-destructive' : '', children: _jsx(SelectValue, { placeholder: "Select an option..." }) }), _jsx(SelectContent, { children: options.map((option) => (_jsx(SelectItem, { value: option.value, children: option.label }, option.value))) })] }), error && _jsx("p", { className: "text-sm text-destructive", children: error })] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface TextFieldProps {
|
|
2
|
+
name: string;
|
|
3
|
+
value: string;
|
|
4
|
+
onChange: (value: string) => void;
|
|
5
|
+
label: string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
mode?: 'read' | 'edit';
|
|
11
|
+
}
|
|
12
|
+
export declare function TextField({ name, value, onChange, label, placeholder, error, disabled, required, mode, }: TextFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=TextField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextField.d.ts","sourceRoot":"","sources":["../../../src/components/fields/TextField.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACvB;AAED,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,KAAK,EACL,WAAW,EACX,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAa,GACd,EAAE,cAAc,2CA8BhB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Input } from '../../primitives/input.js';
|
|
4
|
+
import { Label } from '../../primitives/label.js';
|
|
5
|
+
import { cn } from '../../lib/utils.js';
|
|
6
|
+
export function TextField({ name, value, onChange, label, placeholder, error, disabled, required, mode = 'edit', }) {
|
|
7
|
+
if (mode === 'read') {
|
|
8
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-muted-foreground", children: label }), _jsx("p", { className: "text-sm", children: value || '-' })] }));
|
|
9
|
+
}
|
|
10
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: name, children: [label, required && _jsx("span", { className: "text-destructive ml-1", children: "*" })] }), _jsx(Input, { id: name, name: name, type: "text", value: value || '', onChange: (e) => onChange(e.target.value), placeholder: placeholder, disabled: disabled, required: required, className: cn(error && 'border-destructive') }), error && _jsx("p", { className: "text-sm text-destructive", children: error })] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface TimestampFieldProps {
|
|
2
|
+
name: string;
|
|
3
|
+
value: Date | string | null;
|
|
4
|
+
onChange: (value: Date | null) => void;
|
|
5
|
+
label: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
required?: boolean;
|
|
9
|
+
mode?: 'read' | 'edit';
|
|
10
|
+
}
|
|
11
|
+
export declare function TimestampField({ name, value, onChange, label, error, disabled, required, mode, }: TimestampFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=TimestampField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TimestampField.d.ts","sourceRoot":"","sources":["../../../src/components/fields/TimestampField.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAA;IAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAA;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACvB;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,KAAK,EACL,QAAQ,EACR,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,IAAa,GACd,EAAE,mBAAmB,2CAsBrB"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Label } from '../../primitives/label.js';
|
|
4
|
+
import { DateTimePicker } from '../../primitives/datetime-picker.js';
|
|
5
|
+
import { format } from 'date-fns';
|
|
6
|
+
export function TimestampField({ name, value, onChange, label, error, disabled, required, mode = 'edit', }) {
|
|
7
|
+
const dateValue = value ? new Date(value) : null;
|
|
8
|
+
if (mode === 'read') {
|
|
9
|
+
return (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-muted-foreground", children: label }), _jsx("p", { className: "text-sm", children: dateValue ? format(dateValue, 'PPpp') : '-' })] }));
|
|
10
|
+
}
|
|
11
|
+
return (_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { htmlFor: name, children: [label, required && _jsx("span", { className: "text-destructive ml-1", children: "*" })] }), _jsx(DateTimePicker, { value: dateValue, onChange: onChange, disabled: disabled }), error && _jsx("p", { className: "text-sm text-destructive", children: error })] }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export { TextField } from './TextField.js';
|
|
2
|
+
export { IntegerField } from './IntegerField.js';
|
|
3
|
+
export { CheckboxField } from './CheckboxField.js';
|
|
4
|
+
export { SelectField } from './SelectField.js';
|
|
5
|
+
export { TimestampField } from './TimestampField.js';
|
|
6
|
+
export { PasswordField } from './PasswordField.js';
|
|
7
|
+
export { RelationshipField } from './RelationshipField.js';
|
|
8
|
+
export { ComboboxField } from './ComboboxField.js';
|
|
9
|
+
export { RelationshipManager } from './RelationshipManager.js';
|
|
10
|
+
export { FieldRenderer } from './FieldRenderer.js';
|
|
11
|
+
export { fieldComponentRegistry, registerFieldComponent, getFieldComponent } from './registry.js';
|
|
12
|
+
export type { TextFieldProps } from './TextField.js';
|
|
13
|
+
export type { IntegerFieldProps } from './IntegerField.js';
|
|
14
|
+
export type { CheckboxFieldProps } from './CheckboxField.js';
|
|
15
|
+
export type { SelectFieldProps } from './SelectField.js';
|
|
16
|
+
export type { TimestampFieldProps } from './TimestampField.js';
|
|
17
|
+
export type { PasswordFieldProps } from './PasswordField.js';
|
|
18
|
+
export type { RelationshipFieldProps } from './RelationshipField.js';
|
|
19
|
+
export type { ComboboxFieldProps } from './ComboboxField.js';
|
|
20
|
+
export type { RelationshipManagerProps } from './RelationshipManager.js';
|
|
21
|
+
export type { FieldRendererProps } from './FieldRenderer.js';
|
|
22
|
+
export type { FieldComponent, FieldComponentProps } from './registry.js';
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/fields/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAGjG,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACpD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACxD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAC9D,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAA;AACpE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AACxE,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAC5D,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Field components
|
|
2
|
+
export { TextField } from './TextField.js';
|
|
3
|
+
export { IntegerField } from './IntegerField.js';
|
|
4
|
+
export { CheckboxField } from './CheckboxField.js';
|
|
5
|
+
export { SelectField } from './SelectField.js';
|
|
6
|
+
export { TimestampField } from './TimestampField.js';
|
|
7
|
+
export { PasswordField } from './PasswordField.js';
|
|
8
|
+
export { RelationshipField } from './RelationshipField.js';
|
|
9
|
+
export { ComboboxField } from './ComboboxField.js';
|
|
10
|
+
export { RelationshipManager } from './RelationshipManager.js';
|
|
11
|
+
export { FieldRenderer } from './FieldRenderer.js';
|
|
12
|
+
// Registry for custom field types
|
|
13
|
+
export { fieldComponentRegistry, registerFieldComponent, getFieldComponent } from './registry.js';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Base props that all field components must accept
|
|
4
|
+
* Field components can extend this with additional field-specific props
|
|
5
|
+
*/
|
|
6
|
+
export type FieldComponentProps = {
|
|
7
|
+
name: string;
|
|
8
|
+
value: unknown;
|
|
9
|
+
onChange: (value: unknown) => void;
|
|
10
|
+
label: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
mode?: 'read' | 'edit';
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Type for field component
|
|
18
|
+
* Field components must accept props that extend FieldComponentProps.
|
|
19
|
+
* The registry uses ComponentType<any> because components have different
|
|
20
|
+
* specific prop types (e.g., value: string vs value: number), but all
|
|
21
|
+
* must include the base FieldComponentProps structure.
|
|
22
|
+
*/
|
|
23
|
+
export type FieldComponent = ComponentType<FieldComponentProps & Record<string, unknown>>;
|
|
24
|
+
/**
|
|
25
|
+
* Registry mapping field types to their default UI components
|
|
26
|
+
* This can be extended for custom field types
|
|
27
|
+
* Uses ComponentType<any> to allow components with more specific prop types
|
|
28
|
+
*/
|
|
29
|
+
export declare const fieldComponentRegistry: Record<string, ComponentType<any>>;
|
|
30
|
+
/**
|
|
31
|
+
* Register a custom field component for a field type
|
|
32
|
+
* Useful for adding support for custom field types
|
|
33
|
+
*
|
|
34
|
+
* @param fieldType - The field type identifier
|
|
35
|
+
* @param component - A React component that accepts FieldComponentProps (and optionally additional props)
|
|
36
|
+
*/
|
|
37
|
+
export declare function registerFieldComponent(fieldType: string, component: ComponentType<any>): void;
|
|
38
|
+
/**
|
|
39
|
+
* Get the component for a field type
|
|
40
|
+
* Returns undefined if no component is registered
|
|
41
|
+
*/
|
|
42
|
+
export declare function getFieldComponent(fieldType: string): ComponentType<any> | undefined;
|
|
43
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/components/fields/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAS1C;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACvB,CAAA;AAED;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,aAAa,CAAC,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAEzF;;;;GAIG;AAEH,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC,CAQrE,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EAEjB,SAAS,EAAE,aAAa,CAAC,GAAG,CAAC,GAC5B,IAAI,CAEN;AAED;;;GAGG;AAEH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,GAAG,SAAS,CAEnF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { TextField } from './TextField.js';
|
|
2
|
+
import { IntegerField } from './IntegerField.js';
|
|
3
|
+
import { CheckboxField } from './CheckboxField.js';
|
|
4
|
+
import { SelectField } from './SelectField.js';
|
|
5
|
+
import { TimestampField } from './TimestampField.js';
|
|
6
|
+
import { PasswordField } from './PasswordField.js';
|
|
7
|
+
import { RelationshipField } from './RelationshipField.js';
|
|
8
|
+
/**
|
|
9
|
+
* Registry mapping field types to their default UI components
|
|
10
|
+
* This can be extended for custom field types
|
|
11
|
+
* Uses ComponentType<any> to allow components with more specific prop types
|
|
12
|
+
*/
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
export const fieldComponentRegistry = {
|
|
15
|
+
text: TextField,
|
|
16
|
+
integer: IntegerField,
|
|
17
|
+
checkbox: CheckboxField,
|
|
18
|
+
select: SelectField,
|
|
19
|
+
timestamp: TimestampField,
|
|
20
|
+
password: PasswordField,
|
|
21
|
+
relationship: RelationshipField,
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Register a custom field component for a field type
|
|
25
|
+
* Useful for adding support for custom field types
|
|
26
|
+
*
|
|
27
|
+
* @param fieldType - The field type identifier
|
|
28
|
+
* @param component - A React component that accepts FieldComponentProps (and optionally additional props)
|
|
29
|
+
*/
|
|
30
|
+
export function registerFieldComponent(fieldType,
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
component) {
|
|
33
|
+
fieldComponentRegistry[fieldType] = component;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the component for a field type
|
|
37
|
+
* Returns undefined if no component is registered
|
|
38
|
+
*/
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
export function getFieldComponent(fieldType) {
|
|
41
|
+
return fieldComponentRegistry[fieldType];
|
|
42
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface DeleteButtonProps {
|
|
2
|
+
onDelete: () => Promise<{
|
|
3
|
+
success: boolean;
|
|
4
|
+
error?: string;
|
|
5
|
+
}>;
|
|
6
|
+
itemName?: string;
|
|
7
|
+
confirmTitle?: string;
|
|
8
|
+
confirmMessage?: string;
|
|
9
|
+
confirmLabel?: string;
|
|
10
|
+
cancelLabel?: string;
|
|
11
|
+
buttonLabel?: string;
|
|
12
|
+
variant?: 'danger' | 'warning';
|
|
13
|
+
buttonVariant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
|
|
14
|
+
size?: 'default' | 'sm' | 'lg' | 'icon';
|
|
15
|
+
className?: string;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Standalone delete button with confirmation dialog
|
|
20
|
+
* Can be embedded in any custom page
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <DeleteButton
|
|
25
|
+
* onDelete={async () => {
|
|
26
|
+
* await deletePost(postId);
|
|
27
|
+
* return { success: true };
|
|
28
|
+
* }}
|
|
29
|
+
* itemName="post"
|
|
30
|
+
* confirmMessage="This will permanently delete the post and all its comments."
|
|
31
|
+
* />
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function DeleteButton({ onDelete, itemName, confirmTitle, confirmMessage, confirmLabel, cancelLabel, buttonLabel, variant, buttonVariant, size, className, disabled, }: DeleteButtonProps): import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
//# sourceMappingURL=DeleteButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DeleteButton.d.ts","sourceRoot":"","sources":["../../../src/components/standalone/DeleteButton.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC9B,aAAa,CAAC,EAAE,SAAS,GAAG,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAA;IACtF,IAAI,CAAC,EAAE,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAA;IACvC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,QAAiB,EACjB,YAAY,EACZ,cAAc,EACd,YAAuB,EACvB,WAAsB,EACtB,WAAsB,EACtB,OAAkB,EAClB,aAA6B,EAC7B,IAAgB,EAChB,SAAS,EACT,QAAgB,GACjB,EAAE,iBAAiB,2CA8DnB"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Button } from '../../primitives/button.js';
|
|
5
|
+
import { ConfirmDialog } from '../ConfirmDialog.js';
|
|
6
|
+
import { LoadingSpinner } from '../LoadingSpinner.js';
|
|
7
|
+
/**
|
|
8
|
+
* Standalone delete button with confirmation dialog
|
|
9
|
+
* Can be embedded in any custom page
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* <DeleteButton
|
|
14
|
+
* onDelete={async () => {
|
|
15
|
+
* await deletePost(postId);
|
|
16
|
+
* return { success: true };
|
|
17
|
+
* }}
|
|
18
|
+
* itemName="post"
|
|
19
|
+
* confirmMessage="This will permanently delete the post and all its comments."
|
|
20
|
+
* />
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function DeleteButton({ onDelete, itemName = 'item', confirmTitle, confirmMessage, confirmLabel = 'Delete', cancelLabel = 'Cancel', buttonLabel = 'Delete', variant = 'danger', buttonVariant = 'destructive', size = 'default', className, disabled = false, }) {
|
|
24
|
+
const [showConfirm, setShowConfirm] = useState(false);
|
|
25
|
+
const [isPending, setIsPending] = useState(false);
|
|
26
|
+
const [error, setError] = useState(null);
|
|
27
|
+
const handleDelete = async () => {
|
|
28
|
+
setShowConfirm(false);
|
|
29
|
+
setIsPending(true);
|
|
30
|
+
setError(null);
|
|
31
|
+
try {
|
|
32
|
+
const result = await onDelete();
|
|
33
|
+
if (!result.success) {
|
|
34
|
+
setError(result.error || `Failed to delete ${itemName}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
setError(err.message || `Failed to delete ${itemName}`);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
setIsPending(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Button, { type: "button", variant: buttonVariant, size: size, onClick: () => setShowConfirm(true), disabled: disabled || isPending, className: className, children: [isPending && (_jsx(LoadingSpinner, { size: "sm", className: "border-primary-foreground border-t-transparent mr-2" })), buttonLabel] }), error && (_jsx("div", { className: "mt-2 bg-destructive/10 border border-destructive text-destructive rounded-lg p-3", children: _jsx("p", { className: "text-sm font-medium", children: error }) })), _jsx(ConfirmDialog, { isOpen: showConfirm, title: confirmTitle || `Delete ${itemName}`, message: confirmMessage ||
|
|
45
|
+
`Are you sure you want to delete this ${itemName}? This action cannot be undone.`, confirmLabel: confirmLabel, cancelLabel: cancelLabel, variant: variant, onConfirm: handleDelete, onCancel: () => setShowConfirm(false) })] }));
|
|
46
|
+
}
|