hazo_config 2.0.0 → 2.0.2

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.
Files changed (47) hide show
  1. package/README.md +50 -0
  2. package/dist/components/app_config_list_editor/app_config_list_editor.d.ts +7 -0
  3. package/dist/components/app_config_list_editor/app_config_list_editor.d.ts.map +1 -0
  4. package/dist/components/app_config_list_editor/app_config_list_editor.js +128 -0
  5. package/dist/components/app_config_list_editor/components/color_swatch_picker.d.ts +9 -0
  6. package/dist/components/app_config_list_editor/components/color_swatch_picker.d.ts.map +1 -0
  7. package/dist/components/app_config_list_editor/components/color_swatch_picker.js +15 -0
  8. package/dist/components/app_config_list_editor/components/delete_dialog.d.ts +10 -0
  9. package/dist/components/app_config_list_editor/components/delete_dialog.d.ts.map +1 -0
  10. package/dist/components/app_config_list_editor/components/delete_dialog.js +9 -0
  11. package/dist/components/app_config_list_editor/components/edit_modal.d.ts +19 -0
  12. package/dist/components/app_config_list_editor/components/edit_modal.d.ts.map +1 -0
  13. package/dist/components/app_config_list_editor/components/edit_modal.js +97 -0
  14. package/dist/components/app_config_list_editor/components/empty_state.d.ts +8 -0
  15. package/dist/components/app_config_list_editor/components/empty_state.d.ts.map +1 -0
  16. package/dist/components/app_config_list_editor/components/empty_state.js +8 -0
  17. package/dist/components/app_config_list_editor/components/list_item_row.d.ts +14 -0
  18. package/dist/components/app_config_list_editor/components/list_item_row.d.ts.map +1 -0
  19. package/dist/components/app_config_list_editor/components/list_item_row.js +14 -0
  20. package/dist/components/app_config_list_editor/components/save_status_indicator.d.ts +7 -0
  21. package/dist/components/app_config_list_editor/components/save_status_indicator.d.ts.map +1 -0
  22. package/dist/components/app_config_list_editor/components/save_status_indicator.js +25 -0
  23. package/dist/components/app_config_list_editor/components/search_bar.d.ts +10 -0
  24. package/dist/components/app_config_list_editor/components/search_bar.d.ts.map +1 -0
  25. package/dist/components/app_config_list_editor/components/search_bar.js +8 -0
  26. package/dist/components/app_config_list_editor/index.d.ts +3 -0
  27. package/dist/components/app_config_list_editor/index.d.ts.map +1 -0
  28. package/dist/components/app_config_list_editor/index.js +2 -0
  29. package/dist/components/app_config_list_editor/types.d.ts +93 -0
  30. package/dist/components/app_config_list_editor/types.d.ts.map +1 -0
  31. package/dist/components/app_config_list_editor/types.js +14 -0
  32. package/dist/components/index.d.ts +2 -0
  33. package/dist/components/index.d.ts.map +1 -1
  34. package/dist/components/index.js +2 -0
  35. package/dist/components/ui/alert-dialog.d.ts +21 -0
  36. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  37. package/dist/components/ui/alert-dialog.js +26 -0
  38. package/dist/components/ui/button.d.ts +12 -0
  39. package/dist/components/ui/button.d.ts.map +1 -0
  40. package/dist/components/ui/button.js +33 -0
  41. package/dist/components/ui/dialog.d.ts +20 -0
  42. package/dist/components/ui/dialog.d.ts.map +1 -0
  43. package/dist/components/ui/dialog.js +22 -0
  44. package/dist/components/ui/input.d.ts +4 -0
  45. package/dist/components/ui/input.d.ts.map +1 -0
  46. package/dist/components/ui/input.js +8 -0
  47. package/package.json +4 -1
package/README.md CHANGED
@@ -223,6 +223,54 @@ The AppConfig component supports:
223
223
  - Section-based organization
224
224
  - Type conversion between general and JSON
225
225
 
226
+ ### Example: Generic List Editor (v2.0.2+)
227
+
228
+ ```typescript
229
+ import { AppConfigListEditor } from 'hazo_config'
230
+ import type { ColumnDef } from 'hazo_config'
231
+
232
+ interface Tag {
233
+ [key: string]: unknown
234
+ tag_id: string
235
+ tag_label: string
236
+ color: string
237
+ description: string
238
+ }
239
+
240
+ const TAG_COLUMNS: ColumnDef<Tag>[] = [
241
+ { field: 'tag_label', label: 'Label', type: 'text', required: true, list_display: 'primary' },
242
+ { field: 'tag_id', label: 'Tag ID', type: 'text', required: true, list_display: 'badge' },
243
+ { field: 'color', label: 'Color', type: 'color_swatch', color_options: ['bg-blue-100 text-blue-800', 'bg-green-100 text-green-800'] },
244
+ { field: 'description', label: 'Description', type: 'textarea', list_display: 'secondary' },
245
+ ]
246
+
247
+ export function TagManager({ tags, onTagsChange }) {
248
+ return (
249
+ <AppConfigListEditor<Tag>
250
+ items={tags}
251
+ on_items_change={onTagsChange}
252
+ columns={TAG_COLUMNS}
253
+ id_field="tag_id"
254
+ auto_id_from="tag_label"
255
+ title="Classification Tags"
256
+ description="Manage tags for document categorization."
257
+ enable_search={true}
258
+ delete_confirmation={(tag) => `Delete "${tag.tag_label}"?`}
259
+ />
260
+ )
261
+ }
262
+ ```
263
+
264
+ The `AppConfigListEditor` is a generic, reusable CRUD list editor for arrays of structured objects. It supports:
265
+ - Dynamic form fields (text, textarea, number, select, color_swatch, toggle)
266
+ - Auto-ID generation from a source field
267
+ - Search/filter for long lists
268
+ - Delete confirmation dialogs
269
+ - Color swatch picker
270
+ - Save status indicators (saving/saved/error)
271
+ - Custom item rendering (indicator, preview, full row override)
272
+ - Field validation with inline error messages
273
+
226
274
  ## Development
227
275
 
228
276
  ### Local Setup
@@ -266,6 +314,8 @@ hazo_config/
266
314
  │ │ ├── config_viewer.tsx # INI config viewer
267
315
  │ │ ├── config_editor.tsx # INI config editor
268
316
  │ │ ├── app_config.tsx # Database-backed config (v2.0+)
317
+ │ │ ├── app_config_list_editor/ # Generic CRUD list editor (v2.0.2+)
318
+ │ │ ├── ui/ # shadcn/ui primitives (Dialog, Button, etc.)
269
319
  │ │ ├── use_app_config.ts # Hook for app config
270
320
  │ │ └── *.stories.tsx
271
321
  │ ├── lib/ # Core config management
@@ -0,0 +1,7 @@
1
+ import type { AppConfigListEditorProps } from './types.js';
2
+ /**
3
+ * AppConfigListEditor - A polished CRUD list editor for config item arrays.
4
+ * Purely callback-based: no API calls, no database access.
5
+ */
6
+ export declare function AppConfigListEditor<T extends Record<string, unknown>>({ items, on_items_change, columns, id_field, auto_id_from, title, description, enable_search, search_threshold, render_item, render_item_indicator, render_preview, delete_confirmation, max_items, id_editable_after_create, className, save_status, }: AppConfigListEditorProps<T>): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=app_config_list_editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app_config_list_editor.d.ts","sourceRoot":"","sources":["../../../src/components/app_config_list_editor/app_config_list_editor.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,wBAAwB,EAAqC,MAAM,YAAY,CAAA;AAQ7F;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACrE,KAAK,EACL,eAAe,EACf,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,KAAK,EACL,WAAW,EACX,aAAqB,EACrB,gBAAoB,EACpB,WAAW,EACX,qBAAqB,EACrB,cAAc,EACd,mBAAmB,EACnB,SAAS,EACT,wBAAgC,EAChC,SAAS,EACT,WAAoB,GACrB,EAAE,wBAAwB,CAAC,CAAC,CAAC,2CAsP7B"}
@@ -0,0 +1,128 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // AppConfigListEditor - Main component
3
+ // Generic CRUD list editor for arrays of structured objects stored as JSON config
4
+ import { useState, useCallback, useMemo } from 'react';
5
+ import { Plus } from 'lucide-react';
6
+ import { cn } from '../../lib/utils.js';
7
+ import { ListItemRow } from './components/list_item_row.js';
8
+ import { EditModal } from './components/edit_modal.js';
9
+ import { SearchBar } from './components/search_bar.js';
10
+ import { EmptyState } from './components/empty_state.js';
11
+ import { DeleteDialog } from './components/delete_dialog.js';
12
+ import { SaveStatusIndicator } from './components/save_status_indicator.js';
13
+ /**
14
+ * AppConfigListEditor - A polished CRUD list editor for config item arrays.
15
+ * Purely callback-based: no API calls, no database access.
16
+ */
17
+ export function AppConfigListEditor({ items, on_items_change, columns, id_field, auto_id_from, title, description, enable_search = false, search_threshold = 8, render_item, render_item_indicator, render_preview, delete_confirmation, max_items, id_editable_after_create = false, className, save_status = 'idle', }) {
18
+ const [search_term, set_search_term] = useState('');
19
+ const [edit_state, set_edit_state] = useState(null);
20
+ const [delete_state, set_delete_state] = useState(null);
21
+ // Derive a friendly item label from the title (e.g., "Classification Tags" -> "tags")
22
+ const item_label = useMemo(() => {
23
+ if (!title)
24
+ return 'items';
25
+ const words = title.toLowerCase().split(/\s+/);
26
+ return words[words.length - 1] || 'items';
27
+ }, [title]);
28
+ // Derive singular item type label (e.g., "tags" -> "Tag")
29
+ const item_type_label = useMemo(() => {
30
+ const singular = item_label.replace(/s$/, '');
31
+ return singular.charAt(0).toUpperCase() + singular.slice(1);
32
+ }, [item_label]);
33
+ // Derive section header label (uppercase, e.g., "TAGS")
34
+ const section_label = useMemo(() => item_label.toUpperCase(), [item_label]);
35
+ // Get all existing IDs for duplicate checking
36
+ const existing_ids = useMemo(() => items.map((item) => String(item[id_field])), [items, id_field]);
37
+ // Filter items by search term
38
+ const filtered_items = useMemo(() => {
39
+ if (!search_term.trim())
40
+ return items;
41
+ const term = search_term.toLowerCase();
42
+ return items.filter((item) => columns.some((col) => {
43
+ if (col.list_display === 'hidden' && col.show_in_list === false)
44
+ return false;
45
+ const val = item[col.field];
46
+ return val !== undefined && val !== null && String(val).toLowerCase().includes(term);
47
+ }));
48
+ }, [items, search_term, columns]);
49
+ const show_search = enable_search && items.length > search_threshold;
50
+ const is_max_reached = max_items !== undefined && max_items !== null && items.length >= max_items;
51
+ // CRUD handlers
52
+ const handle_add = useCallback(() => {
53
+ const empty_item = {};
54
+ // Initialize with defaults for toggle fields
55
+ for (const col of columns) {
56
+ if (col.type === 'toggle') {
57
+ empty_item[col.field] = false;
58
+ }
59
+ }
60
+ set_edit_state({
61
+ mode: 'create',
62
+ item: empty_item,
63
+ errors: new Map(),
64
+ user_touched_id: false,
65
+ });
66
+ }, [columns]);
67
+ const handle_edit = useCallback((item) => {
68
+ set_edit_state({
69
+ mode: 'edit',
70
+ item: { ...item },
71
+ original_item: item,
72
+ errors: new Map(),
73
+ user_touched_id: true,
74
+ });
75
+ }, []);
76
+ const handle_delete_request = useCallback((item) => {
77
+ if (!delete_confirmation) {
78
+ // No confirmation needed, delete immediately
79
+ on_items_change(items.filter((i) => i[id_field] !== item[id_field]));
80
+ return;
81
+ }
82
+ set_delete_state({ item });
83
+ }, [delete_confirmation, items, id_field, on_items_change]);
84
+ const handle_delete_confirm = useCallback(() => {
85
+ if (!delete_state)
86
+ return;
87
+ on_items_change(items.filter((i) => i[id_field] !== delete_state.item[id_field]));
88
+ set_delete_state(null);
89
+ }, [delete_state, items, id_field, on_items_change]);
90
+ const handle_save = useCallback((saved_item) => {
91
+ if (edit_state?.mode === 'create') {
92
+ on_items_change([...items, saved_item]);
93
+ }
94
+ else if (edit_state?.mode === 'edit') {
95
+ const original_id = edit_state.original_item?.[id_field];
96
+ on_items_change(items.map((i) => (i[id_field] === original_id ? saved_item : i)));
97
+ }
98
+ set_edit_state(null);
99
+ }, [edit_state, items, id_field, on_items_change]);
100
+ const handle_cancel_edit = useCallback(() => {
101
+ set_edit_state(null);
102
+ }, []);
103
+ const handle_cancel_delete = useCallback(() => {
104
+ set_delete_state(null);
105
+ }, []);
106
+ // Get delete confirmation message
107
+ const delete_message = useMemo(() => {
108
+ if (!delete_state || !delete_confirmation)
109
+ return '';
110
+ if (typeof delete_confirmation === 'function') {
111
+ return delete_confirmation(delete_state.item);
112
+ }
113
+ return delete_confirmation;
114
+ }, [delete_state, delete_confirmation]);
115
+ // Get delete item name for dialog title
116
+ const delete_item_name = useMemo(() => {
117
+ if (!delete_state)
118
+ return '';
119
+ const primary_col = columns.find((c) => c.list_display === 'primary');
120
+ if (primary_col) {
121
+ return String(delete_state.item[primary_col.field] ?? '');
122
+ }
123
+ return String(delete_state.item[id_field] ?? '');
124
+ }, [delete_state, columns, id_field]);
125
+ return (_jsxs("div", { className: cn('cls_list_editor', className), children: [(title || description) && (_jsxs("div", { className: "cls_list_editor_header mb-6", children: [title && (_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("h2", { className: "text-2xl font-bold text-gray-900", children: title }), _jsx(SaveStatusIndicator, { status: save_status })] })), description && (_jsx("p", { className: "text-sm text-gray-500 mt-1", children: description }))] })), _jsxs("div", { className: "cls_list_editor_section_bar flex items-center justify-between mb-3", children: [_jsx("div", { className: "flex items-center gap-2", children: _jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-gray-500", children: section_label }) }), _jsxs("button", { type: "button", onClick: handle_add, disabled: is_max_reached, className: cn('inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-full transition-colors', is_max_reached
126
+ ? 'bg-gray-100 text-gray-400 cursor-not-allowed'
127
+ : 'bg-violet-600 hover:bg-violet-700 text-white'), title: is_max_reached ? `Maximum of ${max_items} items reached` : undefined, children: [_jsx(Plus, { className: "w-4 h-4" }), "Add"] })] }), show_search && (_jsx("div", { className: "mb-3", children: _jsx(SearchBar, { value: search_term, on_change: set_search_term, item_count: filtered_items.length, item_label: item_label }) })), _jsx("div", { className: "cls_list_editor_card rounded-xl border border-gray-200 bg-white overflow-hidden", children: items.length === 0 ? (_jsx(EmptyState, { item_label: item_label, on_add: handle_add })) : filtered_items.length === 0 ? (_jsxs("div", { className: "py-8 text-center text-sm text-gray-500", children: ["No ", item_label, " matching \u201C", search_term, "\u201D"] })) : (_jsx("div", { className: "divide-y divide-gray-100", children: filtered_items.map((item, index) => (_jsx(ListItemRow, { item: item, index: index, columns: columns, render_item: render_item, render_item_indicator: render_item_indicator, on_edit: () => handle_edit(item), on_delete: () => handle_delete_request(item) }, String(item[id_field])))) })) }), is_max_reached && (_jsxs("p", { className: "text-xs text-gray-400 mt-2", children: ["Maximum of ", max_items, " ", item_label, " reached."] })), edit_state && (_jsx(EditModal, { open: true, mode: edit_state.mode, item: edit_state.item, columns: columns, id_field: id_field, auto_id_from: auto_id_from, id_editable_after_create: id_editable_after_create, existing_ids: existing_ids, item_type_label: item_type_label, render_preview: render_preview, on_save: handle_save, on_cancel: handle_cancel_edit })), delete_state && (_jsx(DeleteDialog, { open: true, item_name: delete_item_name, message: delete_message, on_confirm: handle_delete_confirm, on_cancel: handle_cancel_delete }))] }));
128
+ }
@@ -0,0 +1,9 @@
1
+ interface ColorSwatchPickerProps {
2
+ value: string;
3
+ options: string[];
4
+ on_change: (color: string) => void;
5
+ className?: string;
6
+ }
7
+ export declare function ColorSwatchPicker({ value, options, on_change, className, }: ColorSwatchPickerProps): import("react/jsx-runtime").JSX.Element;
8
+ export {};
9
+ //# sourceMappingURL=color_swatch_picker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color_swatch_picker.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/color_swatch_picker.tsx"],"names":[],"mappings":"AAMA,UAAU,sBAAsB;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,OAAO,EACP,SAAS,EACT,SAAS,GACV,EAAE,sBAAsB,2CA+BxB"}
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Color swatch picker component
3
+ // Displays a row of circular color swatches for selection
4
+ import { Check } from 'lucide-react';
5
+ import { cn } from '../../../lib/utils.js';
6
+ export function ColorSwatchPicker({ value, options, on_change, className, }) {
7
+ return (_jsx("div", { className: cn('cls_color_swatch_picker flex flex-wrap gap-2', className), children: options.map((color) => {
8
+ const is_selected = value === color;
9
+ // Extract the bg class for the swatch display
10
+ const bg_class = color.split(' ').find(c => c.startsWith('bg-')) || 'bg-gray-200';
11
+ return (_jsx("button", { type: "button", onClick: () => on_change(color), className: cn('cls_color_swatch w-7 h-7 rounded-full flex items-center justify-center transition-transform hover:scale-110', bg_class, is_selected
12
+ ? 'ring-2 ring-violet-600 ring-offset-2'
13
+ : 'ring-1 ring-gray-200'), "aria-label": `Select color ${color}`, "aria-pressed": is_selected, children: is_selected && (_jsx(Check, { className: "w-3.5 h-3.5 text-current opacity-80" })) }, color));
14
+ }) }));
15
+ }
@@ -0,0 +1,10 @@
1
+ interface DeleteDialogProps {
2
+ open: boolean;
3
+ item_name: string;
4
+ message: string;
5
+ on_confirm: () => void;
6
+ on_cancel: () => void;
7
+ }
8
+ export declare function DeleteDialog({ open, item_name, message, on_confirm, on_cancel, }: DeleteDialogProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=delete_dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete_dialog.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/delete_dialog.tsx"],"names":[],"mappings":"AAeA,UAAU,iBAAiB;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,SAAS,EACT,OAAO,EACP,UAAU,EACV,SAAS,GACV,EAAE,iBAAiB,2CAgCnB"}
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Delete confirmation dialog component
3
+ // Uses shadcn AlertDialog with red warning styling
4
+ import { AlertTriangle } from 'lucide-react';
5
+ import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, } from '../../ui/alert-dialog.js';
6
+ export function DeleteDialog({ open, item_name, message, on_confirm, on_cancel, }) {
7
+ return (_jsx(AlertDialog, { open: open, onOpenChange: (is_open) => { if (!is_open)
8
+ on_cancel(); }, children: _jsxs(AlertDialogContent, { className: "cls_delete_dialog max-w-sm rounded-2xl", children: [_jsxs(AlertDialogHeader, { className: "items-center sm:items-center", children: [_jsx("div", { className: "w-12 h-12 rounded-full bg-red-100 flex items-center justify-center mb-2", children: _jsx(AlertTriangle, { className: "w-6 h-6 text-red-600" }) }), _jsxs(AlertDialogTitle, { className: "text-center", children: ["Delete \u201C", item_name, "\u201D?"] }), _jsx(AlertDialogDescription, { className: "text-center", children: message })] }), _jsxs(AlertDialogFooter, { className: "sm:justify-center gap-2", children: [_jsx(AlertDialogCancel, { onClick: on_cancel, className: "rounded-full", children: "Cancel" }), _jsx(AlertDialogAction, { onClick: on_confirm, className: "rounded-full bg-red-600 text-white hover:bg-red-700", children: "Delete" })] })] }) }));
9
+ }
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import type { ColumnDef } from '../types.js';
3
+ interface EditModalProps<T extends Record<string, unknown>> {
4
+ open: boolean;
5
+ mode: 'create' | 'edit';
6
+ item: Partial<T>;
7
+ columns: ColumnDef<T>[];
8
+ id_field: keyof T & string;
9
+ auto_id_from?: keyof T & string;
10
+ id_editable_after_create?: boolean;
11
+ existing_ids: string[];
12
+ item_type_label: string;
13
+ render_preview?: (item: T) => React.ReactNode;
14
+ on_save: (item: T) => void;
15
+ on_cancel: () => void;
16
+ }
17
+ export declare function EditModal<T extends Record<string, unknown>>({ open, mode, item: initial_item, columns, id_field, auto_id_from, id_editable_after_create, existing_ids, item_type_label, render_preview, on_save, on_cancel, }: EditModalProps<T>): import("react/jsx-runtime").JSX.Element;
18
+ export {};
19
+ //# sourceMappingURL=edit_modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edit_modal.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/edit_modal.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA2C,MAAM,OAAO,CAAA;AAY/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAG5C,UAAU,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACxD,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAA;IACvB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IAChB,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IAC/B,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;IAC7C,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,CAAA;IAC1B,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,SAAS,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC3D,IAAI,EACJ,IAAI,EACJ,IAAI,EAAE,YAAY,EAClB,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,wBAAwB,EACxB,YAAY,EACZ,eAAe,EACf,cAAc,EACd,OAAO,EACP,SAAS,GACV,EAAE,cAAc,CAAC,CAAC,CAAC,2CA2PnB"}
@@ -0,0 +1,97 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Edit modal component
3
+ // Centered dialog for creating/editing items with dynamic form fields
4
+ import { useState, useCallback, useEffect } from 'react';
5
+ import { cn } from '../../../lib/utils.js';
6
+ import { Input } from '../../ui/input.js';
7
+ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '../../ui/dialog.js';
8
+ import { Button } from '../../ui/button.js';
9
+ import { ColorSwatchPicker } from './color_swatch_picker.js';
10
+ import { slugify } from '../types.js';
11
+ export function EditModal({ open, mode, item: initial_item, columns, id_field, auto_id_from, id_editable_after_create, existing_ids, item_type_label, render_preview, on_save, on_cancel, }) {
12
+ const [form_data, set_form_data] = useState({});
13
+ const [errors, set_errors] = useState(new Map());
14
+ const [user_touched_id, set_user_touched_id] = useState(false);
15
+ // Reset form when modal opens
16
+ useEffect(() => {
17
+ if (open) {
18
+ set_form_data({ ...initial_item });
19
+ set_errors(new Map());
20
+ set_user_touched_id(mode === 'edit');
21
+ }
22
+ }, [open, initial_item, mode]);
23
+ const update_field = useCallback((field, value) => {
24
+ set_form_data((prev) => {
25
+ const updated = { ...prev, [field]: value };
26
+ // Auto-generate ID from source field when creating
27
+ if (auto_id_from &&
28
+ field === auto_id_from &&
29
+ !user_touched_id &&
30
+ mode === 'create') {
31
+ updated[id_field] = slugify(String(value ?? ''));
32
+ }
33
+ return updated;
34
+ });
35
+ // Clear error for this field on change
36
+ set_errors((prev) => {
37
+ const next = new Map(prev);
38
+ next.delete(field);
39
+ return next;
40
+ });
41
+ }, [auto_id_from, id_field, mode, user_touched_id]);
42
+ const handle_id_change = useCallback((value) => {
43
+ set_user_touched_id(true);
44
+ update_field(id_field, value);
45
+ }, [id_field, update_field]);
46
+ const validate_form = useCallback(() => {
47
+ const new_errors = new Map();
48
+ for (const col of columns) {
49
+ const value = form_data[col.field];
50
+ // Required check
51
+ if (col.required && (value === undefined || value === null || value === '')) {
52
+ new_errors.set(col.field, `${col.label} is required`);
53
+ continue;
54
+ }
55
+ // Custom validation
56
+ if (col.validate && value !== undefined && value !== null && value !== '') {
57
+ const error = col.validate(value, form_data);
58
+ if (error) {
59
+ new_errors.set(col.field, error);
60
+ }
61
+ }
62
+ }
63
+ // Check ID field is filled
64
+ const id_value = form_data[id_field];
65
+ if (!id_value || String(id_value).trim() === '') {
66
+ new_errors.set(id_field, 'ID is required');
67
+ }
68
+ // Check for duplicate ID
69
+ if (id_value) {
70
+ const id_str = String(id_value);
71
+ const is_duplicate = mode === 'create'
72
+ ? existing_ids.includes(id_str)
73
+ : existing_ids.filter(id => id !== String(initial_item[id_field])).includes(id_str);
74
+ if (is_duplicate) {
75
+ new_errors.set(id_field, 'This ID already exists');
76
+ }
77
+ }
78
+ set_errors(new_errors);
79
+ return new_errors.size === 0;
80
+ }, [columns, form_data, id_field, existing_ids, mode, initial_item]);
81
+ const handle_save = useCallback(() => {
82
+ if (validate_form()) {
83
+ on_save(form_data);
84
+ }
85
+ }, [validate_form, form_data, on_save]);
86
+ const render_field = (col) => {
87
+ const value = form_data[col.field];
88
+ const error = errors.get(col.field);
89
+ const is_id_field = col.field === id_field;
90
+ const is_disabled = is_id_field && mode === 'edit' && !id_editable_after_create;
91
+ return (_jsxs("div", { className: "cls_edit_field space-y-1.5", children: [_jsxs("label", { className: "text-sm font-medium text-gray-700", children: [col.label, col.required && _jsx("span", { className: "text-red-500 ml-0.5", children: "*" })] }), col.type === 'text' && (_jsx(Input, { type: "text", value: String(value ?? ''), onChange: (e) => is_id_field
92
+ ? handle_id_change(e.target.value)
93
+ : update_field(col.field, e.target.value), placeholder: col.placeholder, disabled: is_disabled, className: cn('rounded-lg focus-visible:ring-violet-500/20 focus-visible:ring-offset-0', is_disabled && 'bg-gray-50 text-gray-500', error && 'border-red-400 focus-visible:ring-red-500/20') })), col.type === 'number' && (_jsx(Input, { type: "number", value: value !== undefined && value !== null ? String(value) : '', onChange: (e) => update_field(col.field, e.target.value === '' ? '' : Number(e.target.value)), placeholder: col.placeholder, className: cn('rounded-lg focus-visible:ring-violet-500/20 focus-visible:ring-offset-0', error && 'border-red-400 focus-visible:ring-red-500/20') })), col.type === 'textarea' && (_jsx("textarea", { value: String(value ?? ''), onChange: (e) => update_field(col.field, e.target.value), placeholder: col.placeholder, rows: 3, className: cn('flex w-full rounded-lg border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500/20 focus-visible:border-violet-300 disabled:cursor-not-allowed disabled:opacity-50', error && 'border-red-400 focus-visible:ring-red-500/20') })), col.type === 'select' && col.options && (_jsxs("select", { value: String(value ?? ''), onChange: (e) => update_field(col.field, e.target.value), className: cn('flex h-10 w-full rounded-lg border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-violet-500/20 focus-visible:border-violet-300', error && 'border-red-400 focus-visible:ring-red-500/20'), children: [_jsx("option", { value: "", children: col.placeholder || 'Select...' }), col.options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] })), col.type === 'color_swatch' && col.color_options && (_jsx(ColorSwatchPicker, { value: String(value ?? ''), options: col.color_options, on_change: (color) => update_field(col.field, color) })), col.type === 'toggle' && (_jsx("button", { type: "button", role: "switch", "aria-checked": Boolean(value), onClick: () => update_field(col.field, !value), className: cn('relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors', value ? 'bg-violet-600' : 'bg-gray-200'), children: _jsx("span", { className: cn('pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow-lg ring-0 transition-transform', value ? 'translate-x-5' : 'translate-x-0') }) })), is_id_field && auto_id_from && mode === 'create' && !user_touched_id && (_jsxs("p", { className: "text-xs text-gray-400", children: ["Auto-generated from ", columns.find(c => c.field === auto_id_from)?.label?.toLowerCase() || auto_id_from] })), error && (_jsx("p", { className: "text-xs text-red-600", children: error }))] }, col.field));
94
+ };
95
+ return (_jsx(Dialog, { open: open, onOpenChange: (is_open) => { if (!is_open)
96
+ on_cancel(); }, children: _jsxs(DialogContent, { className: "cls_edit_modal max-w-[460px] rounded-2xl p-0 gap-0 overflow-hidden", children: [_jsx(DialogHeader, { className: "px-6 pt-6 pb-4", children: _jsx(DialogTitle, { children: mode === 'create' ? `New ${item_type_label}` : `Edit ${item_type_label}` }) }), _jsxs("div", { className: "px-6 pb-4 space-y-4", children: [render_preview && Object.keys(form_data).length > 0 && (_jsx("div", { className: "p-3 bg-gray-50 rounded-lg border border-gray-100", children: render_preview(form_data) })), columns.map((col) => render_field(col))] }), _jsxs(DialogFooter, { className: "bg-gray-50 px-6 py-4 border-t border-gray-100 sm:justify-between", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: on_cancel, className: "rounded-full", children: "Cancel" }), _jsx(Button, { type: "button", onClick: handle_save, className: "rounded-full bg-violet-600 hover:bg-violet-700 text-white", children: mode === 'create' ? `Add ${item_type_label}` : 'Save Changes' })] })] }) }));
97
+ }
@@ -0,0 +1,8 @@
1
+ interface EmptyStateProps {
2
+ item_label: string;
3
+ on_add: () => void;
4
+ className?: string;
5
+ }
6
+ export declare function EmptyState({ item_label, on_add, className, }: EmptyStateProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=empty_state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty_state.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/empty_state.tsx"],"names":[],"mappings":"AAMA,UAAU,eAAe;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,UAAU,CAAC,EACzB,UAAU,EACV,MAAM,EACN,SAAS,GACV,EAAE,eAAe,2CAsBjB"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Empty state component
3
+ // Displayed when the list has no items
4
+ import { Inbox, Plus } from 'lucide-react';
5
+ import { cn } from '../../../lib/utils.js';
6
+ export function EmptyState({ item_label, on_add, className, }) {
7
+ return (_jsxs("div", { className: cn('cls_empty_state flex flex-col items-center justify-center py-12 px-4', className), children: [_jsx("div", { className: "w-12 h-12 rounded-full bg-gray-100 flex items-center justify-center mb-3", children: _jsx(Inbox, { className: "w-6 h-6 text-gray-400" }) }), _jsxs("p", { className: "text-sm font-medium text-gray-900 mb-1", children: ["No ", item_label, " yet"] }), _jsxs("p", { className: "text-sm text-gray-500 mb-4", children: ["Get started by adding your first ", item_label.replace(/s$/, ''), "."] }), _jsxs("button", { type: "button", onClick: on_add, className: "inline-flex items-center gap-1.5 px-4 py-2 text-sm font-medium text-white bg-violet-600 hover:bg-violet-700 rounded-full transition-colors", children: [_jsx(Plus, { className: "w-4 h-4" }), "Add your first ", item_label.replace(/s$/, '')] })] }));
8
+ }
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import type { ColumnDef } from '../types.js';
3
+ interface ListItemRowProps<T extends Record<string, unknown>> {
4
+ item: T;
5
+ index: number;
6
+ columns: ColumnDef<T>[];
7
+ render_item?: (item: T, index: number) => React.ReactNode;
8
+ render_item_indicator?: (item: T) => React.ReactNode;
9
+ on_edit: () => void;
10
+ on_delete: () => void;
11
+ }
12
+ export declare function ListItemRow<T extends Record<string, unknown>>({ item, index, columns, render_item, render_item_indicator, on_edit, on_delete, }: ListItemRowProps<T>): import("react/jsx-runtime").JSX.Element;
13
+ export {};
14
+ //# sourceMappingURL=list_item_row.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list_item_row.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/list_item_row.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,UAAU,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC1D,IAAI,EAAE,CAAC,CAAA;IACP,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACvB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IACzD,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;IACpD,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC7D,IAAI,EACJ,KAAK,EACL,OAAO,EACP,WAAW,EACX,qBAAqB,EACrB,OAAO,EACP,SAAS,GACV,EAAE,gBAAgB,CAAC,CAAC,CAAC,2CAgGrB"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Pencil, Trash2 } from 'lucide-react';
3
+ import { cn } from '../../../lib/utils.js';
4
+ export function ListItemRow({ item, index, columns, render_item, render_item_indicator, on_edit, on_delete, }) {
5
+ // Custom render overrides everything
6
+ if (render_item) {
7
+ return (_jsxs("div", { className: "cls_list_editor_row flex items-center gap-3 px-4 py-3 hover:bg-gray-50 transition-colors", children: [_jsx("div", { className: "flex-1 min-w-0", children: render_item(item, index) }), _jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [_jsx("button", { type: "button", onClick: on_edit, className: "w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors", "aria-label": "Edit item", children: _jsx(Pencil, { className: "w-4 h-4" }) }), _jsx("button", { type: "button", onClick: on_delete, className: "w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-red-600 hover:bg-red-50 transition-colors", "aria-label": "Delete item", children: _jsx(Trash2, { className: "w-4 h-4" }) })] })] }));
8
+ }
9
+ // Default rendering based on column definitions
10
+ const primary_cols = columns.filter(c => c.list_display === 'primary');
11
+ const secondary_cols = columns.filter(c => c.list_display === 'secondary');
12
+ const badge_cols = columns.filter(c => c.list_display === 'badge');
13
+ return (_jsxs("div", { className: "cls_list_editor_row flex items-center gap-3 px-4 py-3 hover:bg-gray-50 transition-colors", children: [render_item_indicator && (_jsx("div", { className: "shrink-0", children: render_item_indicator(item) })), _jsxs("div", { className: "flex-1 min-w-0", children: [primary_cols.map((col) => (_jsx("div", { className: "text-sm font-medium text-gray-900 truncate", children: String(item[col.field] ?? '') }, col.field))), secondary_cols.map((col) => (_jsx("div", { className: "text-xs text-gray-500 truncate mt-0.5", children: String(item[col.field] ?? '') }, col.field)))] }), badge_cols.length > 0 && (_jsx("div", { className: "flex items-center gap-2 shrink-0", children: badge_cols.map((col) => (_jsx("span", { className: cn('inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-mono', 'bg-gray-100 text-gray-600'), children: String(item[col.field] ?? '') }, col.field))) })), _jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [_jsx("button", { type: "button", onClick: on_edit, className: "w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-gray-700 hover:bg-gray-100 transition-colors", "aria-label": "Edit item", children: _jsx(Pencil, { className: "w-4 h-4" }) }), _jsx("button", { type: "button", onClick: on_delete, className: "w-8 h-8 rounded-full flex items-center justify-center text-gray-400 hover:text-red-600 hover:bg-red-50 transition-colors", "aria-label": "Delete item", children: _jsx(Trash2, { className: "w-4 h-4" }) })] })] }));
14
+ }
@@ -0,0 +1,7 @@
1
+ interface SaveStatusIndicatorProps {
2
+ status: 'idle' | 'saving' | 'saved' | 'error';
3
+ className?: string;
4
+ }
5
+ export declare function SaveStatusIndicator({ status, className, }: SaveStatusIndicatorProps): import("react/jsx-runtime").JSX.Element | null;
6
+ export {};
7
+ //# sourceMappingURL=save_status_indicator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"save_status_indicator.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/save_status_indicator.tsx"],"names":[],"mappings":"AAOA,UAAU,wBAAwB;IAChC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAA;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,SAAS,GACV,EAAE,wBAAwB,kDA+C1B"}
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Save status indicator component
3
+ // Shows saving/saved/error states inline
4
+ import { useEffect, useState } from 'react';
5
+ import { Loader2, CheckCircle2, AlertCircle } from 'lucide-react';
6
+ import { cn } from '../../../lib/utils.js';
7
+ export function SaveStatusIndicator({ status, className, }) {
8
+ const [visible, set_visible] = useState(false);
9
+ useEffect(() => {
10
+ if (status === 'saved') {
11
+ set_visible(true);
12
+ const timer = setTimeout(() => set_visible(false), 2000);
13
+ return () => clearTimeout(timer);
14
+ }
15
+ if (status === 'saving' || status === 'error') {
16
+ set_visible(true);
17
+ }
18
+ else {
19
+ set_visible(false);
20
+ }
21
+ }, [status]);
22
+ if (!visible && status === 'idle')
23
+ return null;
24
+ return (_jsxs("div", { className: cn('cls_save_status flex items-center gap-1.5 text-xs transition-opacity duration-300', !visible && 'opacity-0', visible && 'opacity-100', className), children: [status === 'saving' && (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "w-3.5 h-3.5 text-gray-500 animate-spin" }), _jsx("span", { className: "text-gray-500", children: "Saving..." })] })), status === 'saved' && (_jsxs(_Fragment, { children: [_jsx(CheckCircle2, { className: "w-3.5 h-3.5 text-green-600" }), _jsx("span", { className: "text-green-600", children: "Saved" })] })), status === 'error' && (_jsxs(_Fragment, { children: [_jsx(AlertCircle, { className: "w-3.5 h-3.5 text-red-600" }), _jsx("span", { className: "text-red-600", children: "Error saving" })] }))] }));
25
+ }
@@ -0,0 +1,10 @@
1
+ interface SearchBarProps {
2
+ value: string;
3
+ on_change: (value: string) => void;
4
+ item_count: number;
5
+ item_label: string;
6
+ className?: string;
7
+ }
8
+ export declare function SearchBar({ value, on_change, item_count, item_label, className, }: SearchBarProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=search_bar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search_bar.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/search_bar.tsx"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAClC,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,GACV,EAAE,cAAc,2CA6BhB"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Search bar component
3
+ // Pill-shaped search input with icon, clear button, and item count
4
+ import { Search, X } from 'lucide-react';
5
+ import { cn } from '../../../lib/utils.js';
6
+ export function SearchBar({ value, on_change, item_count, item_label, className, }) {
7
+ return (_jsxs("div", { className: cn('cls_search_bar flex items-center gap-3', className), children: [_jsxs("div", { className: "relative flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" }), _jsx("input", { type: "text", value: value, onChange: (e) => on_change(e.target.value), placeholder: `Search ${item_label}...`, className: "w-full pl-9 pr-8 py-2 text-sm rounded-full border border-gray-200 bg-white focus:outline-none focus:ring-2 focus:ring-violet-500/20 focus:border-violet-300 transition-colors", "aria-label": `Search ${item_label}` }), value && (_jsx("button", { type: "button", onClick: () => on_change(''), className: "absolute right-2.5 top-1/2 -translate-y-1/2 w-5 h-5 flex items-center justify-center rounded-full text-gray-400 hover:text-gray-600 hover:bg-gray-100", "aria-label": "Clear search", children: _jsx(X, { className: "w-3.5 h-3.5" }) }))] }), _jsxs("span", { className: "text-sm text-gray-500 whitespace-nowrap", children: [item_count, " ", item_label] })] }));
8
+ }
@@ -0,0 +1,3 @@
1
+ export { AppConfigListEditor } from './app_config_list_editor.js';
2
+ export type { AppConfigListEditorProps, ColumnDef } from './types.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/app_config_list_editor/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,YAAY,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,2 @@
1
+ // Public exports for AppConfigListEditor
2
+ export { AppConfigListEditor } from './app_config_list_editor.js';
@@ -0,0 +1,93 @@
1
+ import type React from 'react';
2
+ /**
3
+ * Column/field definition for list display and edit form
4
+ */
5
+ export interface ColumnDef<T> {
6
+ /** The field key in the item */
7
+ field: keyof T & string;
8
+ /** Display label for the form */
9
+ label: string;
10
+ /** Column type determines the input control */
11
+ type: 'text' | 'textarea' | 'color_swatch' | 'select' | 'number' | 'toggle';
12
+ /** Placeholder text */
13
+ placeholder?: string;
14
+ /** Whether this field is required */
15
+ required?: boolean;
16
+ /** Show in the list view (default: true for text, false for textarea) */
17
+ show_in_list?: boolean;
18
+ /** How to display in list view */
19
+ list_display?: 'primary' | 'secondary' | 'badge' | 'hidden';
20
+ /** Options for 'select' type */
21
+ options?: {
22
+ value: string;
23
+ label: string;
24
+ }[];
25
+ /** Options for 'color_swatch' type */
26
+ color_options?: string[];
27
+ /** Validation function - returns error message or null */
28
+ validate?: (value: unknown, item: T) => string | null;
29
+ }
30
+ /**
31
+ * Props for the AppConfigListEditor component
32
+ */
33
+ export interface AppConfigListEditorProps<T extends Record<string, unknown>> {
34
+ /** The array of items to display and edit */
35
+ items: T[];
36
+ /** Called when items change (add, edit, delete, reorder) */
37
+ on_items_change: (items: T[]) => void;
38
+ /** Column/field definitions for the list and edit form */
39
+ columns: ColumnDef<T>[];
40
+ /** Unique key field in each item (e.g., 'tag_id', 'type_id') */
41
+ id_field: keyof T & string;
42
+ /** Optional: field to auto-generate ID from (e.g., 'tag_label' -> auto-generates tag_id) */
43
+ auto_id_from?: keyof T & string;
44
+ /** Title shown above the list */
45
+ title?: string;
46
+ /** Subtitle/description */
47
+ description?: string;
48
+ /** Whether to allow drag-and-drop reordering (v2 feature) */
49
+ enable_reorder?: boolean;
50
+ /** Whether to show a search/filter input for long lists */
51
+ enable_search?: boolean;
52
+ /** Search threshold - only show search if item count exceeds this (default: 8) */
53
+ search_threshold?: number;
54
+ /** Custom render for each list item (overrides default rendering) */
55
+ render_item?: (item: T, index: number) => React.ReactNode;
56
+ /** Custom render for the item's leading visual (icon, color dot, avatar, etc.) */
57
+ render_item_indicator?: (item: T) => React.ReactNode;
58
+ /** Custom render for item preview/badge (shown in edit form) */
59
+ render_preview?: (item: T) => React.ReactNode;
60
+ /** Confirmation message for delete (null = no confirmation) */
61
+ delete_confirmation?: string | ((item: T) => string);
62
+ /** Max items allowed (null = unlimited) */
63
+ max_items?: number;
64
+ /** Whether the ID field is editable after creation (default: false) */
65
+ id_editable_after_create?: boolean;
66
+ /** Additional className */
67
+ className?: string;
68
+ /** Save status indicator */
69
+ save_status?: 'idle' | 'saving' | 'saved' | 'error';
70
+ }
71
+ /**
72
+ * Internal state for the edit modal
73
+ */
74
+ export interface EditModalState<T> {
75
+ mode: 'create' | 'edit';
76
+ item: Partial<T>;
77
+ original_item?: T;
78
+ errors: Map<string, string>;
79
+ user_touched_id: boolean;
80
+ }
81
+ /**
82
+ * Internal state for the delete confirmation dialog
83
+ */
84
+ export interface DeleteDialogState<T> {
85
+ item: T;
86
+ }
87
+ /**
88
+ * Slugify a string for auto-ID generation.
89
+ * Converts to lowercase, replaces non-alphanumeric chars with underscores,
90
+ * collapses consecutive underscores, and trims leading/trailing underscores.
91
+ */
92
+ export declare function slugify(value: string): string;
93
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/app_config_list_editor/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B;;GAEG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IACvB,iCAAiC;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,+CAA+C;IAC/C,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAC3E,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,yEAAyE;IACzE,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,kCAAkC;IAClC,YAAY,CAAC,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,QAAQ,CAAA;IAC3D,gCAAgC;IAChC,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IAC5C,sCAAsC;IACtC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,IAAI,CAAA;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACzE,6CAA6C;IAC7C,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,4DAA4D;IAC5D,eAAe,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,CAAA;IACrC,0DAA0D;IAC1D,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACvB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IAC1B,4FAA4F;IAC5F,YAAY,CAAC,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IAC/B,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,qEAAqE;IACrE,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAA;IACzD,kFAAkF;IAClF,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;IACpD,gEAAgE;IAChE,cAAc,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,KAAK,CAAC,SAAS,CAAA;IAC7C,+DAA+D;IAC/D,mBAAmB,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC,CAAA;IACpD,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAA;CACpD;AAED;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAA;IACvB,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAA;IAChB,aAAa,CAAC,EAAE,CAAC,CAAA;IACjB,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3B,eAAe,EAAE,OAAO,CAAA;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,IAAI,EAAE,CAAC,CAAA;CACR;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAM7C"}
@@ -0,0 +1,14 @@
1
+ // Type definitions for AppConfigListEditor component
2
+ // Defines column configuration, component props, and internal state types
3
+ /**
4
+ * Slugify a string for auto-ID generation.
5
+ * Converts to lowercase, replaces non-alphanumeric chars with underscores,
6
+ * collapses consecutive underscores, and trims leading/trailing underscores.
7
+ */
8
+ export function slugify(value) {
9
+ return value
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9]+/g, '_')
12
+ .replace(/^_+|_+$/g, '')
13
+ .replace(/_+/g, '_');
14
+ }
@@ -8,4 +8,6 @@ export { useConfigSections, is_sensitive_field, mask_value, DEFAULT_SENSITIVE_PA
8
8
  export type { UseConfigSectionsResult } from './use_config_sections.js';
9
9
  export { useAppConfig } from './use_app_config.js';
10
10
  export type { UseAppConfigResult } from '../lib/app_config_types.js';
11
+ export { AppConfigListEditor } from './app_config_list_editor/index.js';
12
+ export type { AppConfigListEditorProps, ColumnDef } from './app_config_list_editor/index.js';
11
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,YAAY,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAGhE,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,0BAA0B,EAC3B,MAAM,0BAA0B,CAAA;AACjC,YAAY,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,YAAY,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAA;AAGhE,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,UAAU,EACV,0BAA0B,EAC3B,MAAM,0BAA0B,CAAA;AACjC,YAAY,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAA;AAEvE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAGpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;AACvE,YAAY,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAA"}
@@ -6,3 +6,5 @@ export { AppConfig } from './app_config.js';
6
6
  // Hook exports
7
7
  export { useConfigSections, is_sensitive_field, mask_value, DEFAULT_SENSITIVE_PATTERNS } from './use_config_sections.js';
8
8
  export { useAppConfig } from './use_app_config.js';
9
+ // AppConfigListEditor exports
10
+ export { AppConfigListEditor } from './app_config_list_editor/index.js';
@@ -0,0 +1,21 @@
1
+ import * as React from "react";
2
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
3
+ declare const AlertDialog: React.FC<AlertDialogPrimitive.AlertDialogProps>;
4
+ declare const AlertDialogTrigger: React.ForwardRefExoticComponent<AlertDialogPrimitive.AlertDialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
5
+ declare const AlertDialogPortal: React.FC<AlertDialogPrimitive.AlertDialogPortalProps>;
6
+ declare const AlertDialogOverlay: React.ForwardRefExoticComponent<Omit<AlertDialogPrimitive.AlertDialogOverlayProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
7
+ declare const AlertDialogContent: React.ForwardRefExoticComponent<Omit<AlertDialogPrimitive.AlertDialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
8
+ declare const AlertDialogHeader: {
9
+ ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
10
+ displayName: string;
11
+ };
12
+ declare const AlertDialogFooter: {
13
+ ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
14
+ displayName: string;
15
+ };
16
+ declare const AlertDialogTitle: React.ForwardRefExoticComponent<Omit<AlertDialogPrimitive.AlertDialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
17
+ declare const AlertDialogDescription: React.ForwardRefExoticComponent<Omit<AlertDialogPrimitive.AlertDialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
18
+ declare const AlertDialogAction: React.ForwardRefExoticComponent<Omit<AlertDialogPrimitive.AlertDialogActionProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
19
+ declare const AlertDialogCancel: React.ForwardRefExoticComponent<Omit<AlertDialogPrimitive.AlertDialogCancelProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
20
+ export { AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel, };
21
+ //# sourceMappingURL=alert-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alert-dialog.d.ts","sourceRoot":"","sources":["../../../src/components/ui/alert-dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,oBAAoB,MAAM,8BAA8B,CAAA;AAKpE,QAAA,MAAM,WAAW,iDAA4B,CAAA;AAE7C,QAAA,MAAM,kBAAkB,wHAA+B,CAAA;AAEvD,QAAA,MAAM,iBAAiB,uDAA8B,CAAA;AAErD,QAAA,MAAM,kBAAkB,wKAYtB,CAAA;AAGF,QAAA,MAAM,kBAAkB,wKAetB,CAAA;AAGF,QAAA,MAAM,iBAAiB;8BAGpB,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;;CAQtC,CAAA;AAGD,QAAA,MAAM,iBAAiB;8BAGpB,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;;CAQtC,CAAA;AAGD,QAAA,MAAM,gBAAgB,8KASpB,CAAA;AAGF,QAAA,MAAM,sBAAsB,wLAS1B,CAAA;AAIF,QAAA,MAAM,iBAAiB,6KASrB,CAAA;AAGF,QAAA,MAAM,iBAAiB,6KAarB,CAAA;AAGF,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,GAClB,CAAA"}
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
4
+ import { cn } from "../../lib/utils.js";
5
+ import { buttonVariants } from "./button.js";
6
+ const AlertDialog = AlertDialogPrimitive.Root;
7
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
8
+ const AlertDialogPortal = AlertDialogPrimitive.Portal;
9
+ const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => (_jsx(AlertDialogPrimitive.Overlay, { className: cn("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className), ...props, ref: ref })));
10
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
11
+ const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (_jsxs(AlertDialogPortal, { children: [_jsx(AlertDialogOverlay, {}), _jsx(AlertDialogPrimitive.Content, { ref: ref, className: cn("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", className), ...props })] })));
12
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
13
+ const AlertDialogHeader = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col space-y-2 text-center sm:text-left", className), ...props }));
14
+ AlertDialogHeader.displayName = "AlertDialogHeader";
15
+ const AlertDialogFooter = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props }));
16
+ AlertDialogFooter.displayName = "AlertDialogFooter";
17
+ const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (_jsx(AlertDialogPrimitive.Title, { ref: ref, className: cn("text-lg font-semibold", className), ...props })));
18
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
19
+ const AlertDialogDescription = React.forwardRef(({ className, ...props }, ref) => (_jsx(AlertDialogPrimitive.Description, { ref: ref, className: cn("text-sm text-muted-foreground", className), ...props })));
20
+ AlertDialogDescription.displayName =
21
+ AlertDialogPrimitive.Description.displayName;
22
+ const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (_jsx(AlertDialogPrimitive.Action, { ref: ref, className: cn(buttonVariants(), className), ...props })));
23
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
24
+ const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (_jsx(AlertDialogPrimitive.Cancel, { ref: ref, className: cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className), ...props })));
25
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
26
+ export { AlertDialog, AlertDialogPortal, AlertDialogOverlay, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogAction, AlertDialogCancel, };
@@ -0,0 +1,12 @@
1
+ import * as React from "react";
2
+ import { type VariantProps } from "class-variance-authority";
3
+ declare const buttonVariants: (props?: ({
4
+ variant?: "default" | "link" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
5
+ size?: "default" | "sm" | "lg" | "icon" | null | undefined;
6
+ } & import("class-variance-authority/dist/types.js").ClassProp) | undefined) => string;
7
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
8
+ asChild?: boolean;
9
+ }
10
+ declare const Button: React.ForwardRefExoticComponent<ButtonProps & React.RefAttributes<HTMLButtonElement>>;
11
+ export { Button, buttonVariants };
12
+ //# sourceMappingURL=button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../../src/components/ui/button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AAIjE,QAAA,MAAM,cAAc;;;sFA2BnB,CAAA;AAED,MAAM,WAAW,WACf,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EACnD,YAAY,CAAC,OAAO,cAAc,CAAC;IACrC,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,QAAA,MAAM,MAAM,uFAWX,CAAA;AAGD,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAA"}
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { Slot } from "@radix-ui/react-slot";
4
+ import { cva } from "class-variance-authority";
5
+ import { cn } from "../../lib/utils.js";
6
+ const buttonVariants = cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", {
7
+ variants: {
8
+ variant: {
9
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
10
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
11
+ outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
12
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
13
+ ghost: "hover:bg-accent hover:text-accent-foreground",
14
+ link: "text-primary underline-offset-4 hover:underline",
15
+ },
16
+ size: {
17
+ default: "h-10 px-4 py-2",
18
+ sm: "h-9 rounded-md px-3",
19
+ lg: "h-11 rounded-md px-8",
20
+ icon: "h-10 w-10",
21
+ },
22
+ },
23
+ defaultVariants: {
24
+ variant: "default",
25
+ size: "default",
26
+ },
27
+ });
28
+ const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
29
+ const Comp = asChild ? Slot : "button";
30
+ return (_jsx(Comp, { className: cn(buttonVariants({ variant, size, className })), ref: ref, ...props }));
31
+ });
32
+ Button.displayName = "Button";
33
+ export { Button, buttonVariants };
@@ -0,0 +1,20 @@
1
+ import * as React from "react";
2
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
3
+ declare const Dialog: React.FC<DialogPrimitive.DialogProps>;
4
+ declare const DialogTrigger: React.ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & React.RefAttributes<HTMLButtonElement>>;
5
+ declare const DialogPortal: React.FC<DialogPrimitive.DialogPortalProps>;
6
+ declare const DialogClose: React.ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
7
+ declare const DialogOverlay: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogOverlayProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
8
+ declare const DialogContent: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
9
+ declare const DialogHeader: {
10
+ ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
11
+ displayName: string;
12
+ };
13
+ declare const DialogFooter: {
14
+ ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
15
+ displayName: string;
16
+ };
17
+ declare const DialogTitle: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
18
+ declare const DialogDescription: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
19
+ export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, };
20
+ //# sourceMappingURL=dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialog.d.ts","sourceRoot":"","sources":["../../../src/components/ui/dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,KAAK,eAAe,MAAM,wBAAwB,CAAA;AAKzD,QAAA,MAAM,MAAM,uCAAuB,CAAA;AAEnC,QAAA,MAAM,aAAa,8GAA0B,CAAA;AAE7C,QAAA,MAAM,YAAY,6CAAyB,CAAA;AAE3C,QAAA,MAAM,WAAW,4GAAwB,CAAA;AAEzC,QAAA,MAAM,aAAa,8JAYjB,CAAA;AAGF,QAAA,MAAM,aAAa,8JAqBjB,CAAA;AAGF,QAAA,MAAM,YAAY;8BAGf,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;;CAQtC,CAAA;AAGD,QAAA,MAAM,YAAY;8BAGf,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;;CAQtC,CAAA;AAGD,QAAA,MAAM,WAAW,oKAYf,CAAA;AAGF,QAAA,MAAM,iBAAiB,8KASrB,CAAA;AAGF,OAAO,EACL,MAAM,EACN,YAAY,EACZ,aAAa,EACb,WAAW,EACX,aAAa,EACb,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,iBAAiB,GAClB,CAAA"}
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
4
+ import { X } from "lucide-react";
5
+ import { cn } from "../../lib/utils.js";
6
+ const Dialog = DialogPrimitive.Root;
7
+ const DialogTrigger = DialogPrimitive.Trigger;
8
+ const DialogPortal = DialogPrimitive.Portal;
9
+ const DialogClose = DialogPrimitive.Close;
10
+ const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Overlay, { ref: ref, className: cn("fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", className), ...props })));
11
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
12
+ const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => (_jsxs(DialogPortal, { children: [_jsx(DialogOverlay, {}), _jsxs(DialogPrimitive.Content, { ref: ref, className: cn("fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg", className), ...props, children: [children, _jsxs(DialogPrimitive.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground", children: [_jsx(X, { className: "h-4 w-4" }), _jsx("span", { className: "sr-only", children: "Close" })] })] })] })));
13
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
14
+ const DialogHeader = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col space-y-1.5 text-center sm:text-left", className), ...props }));
15
+ DialogHeader.displayName = "DialogHeader";
16
+ const DialogFooter = ({ className, ...props }) => (_jsx("div", { className: cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className), ...props }));
17
+ DialogFooter.displayName = "DialogFooter";
18
+ const DialogTitle = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Title, { ref: ref, className: cn("text-lg font-semibold leading-none tracking-tight", className), ...props })));
19
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
20
+ const DialogDescription = React.forwardRef(({ className, ...props }, ref) => (_jsx(DialogPrimitive.Description, { ref: ref, className: cn("text-sm text-muted-foreground", className), ...props })));
21
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
22
+ export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, };
@@ -0,0 +1,4 @@
1
+ import * as React from "react";
2
+ declare const Input: React.ForwardRefExoticComponent<Omit<React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "ref"> & React.RefAttributes<HTMLInputElement>>;
3
+ export { Input };
4
+ //# sourceMappingURL=input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/ui/input.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,QAAA,MAAM,KAAK,8KAcV,CAAA;AAGD,OAAO,EAAE,KAAK,EAAE,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { cn } from "../../lib/utils.js";
4
+ const Input = React.forwardRef(({ className, type, ...props }, ref) => {
5
+ return (_jsx("input", { type: type, className: cn("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className), ref: ref, ...props }));
6
+ });
7
+ Input.displayName = "Input";
8
+ export { Input };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_config",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Config wrapper with error handling",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -52,6 +52,9 @@
52
52
  },
53
53
  "homepage": "https://github.com/pub12/hazo_config#readme",
54
54
  "dependencies": {
55
+ "@radix-ui/react-alert-dialog": "^1.1.15",
56
+ "@radix-ui/react-dialog": "^1.1.15",
57
+ "@radix-ui/react-slot": "^1.2.4",
55
58
  "class-variance-authority": "^0.7.0",
56
59
  "clsx": "^2.1.0",
57
60
  "ini": "^4.1.0",