hazo_config 2.1.12 → 2.3.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/CHANGE_LOG.md +15 -0
- package/dist/components/app_config_list_editor/app_config_list_editor.d.ts +1 -1
- package/dist/components/app_config_list_editor/app_config_list_editor.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/app_config_list_editor.js +140 -17
- package/dist/components/app_config_list_editor/components/bulk_edit_modal.d.ts +11 -0
- package/dist/components/app_config_list_editor/components/bulk_edit_modal.d.ts.map +1 -0
- package/dist/components/app_config_list_editor/components/bulk_edit_modal.js +83 -0
- package/dist/components/app_config_list_editor/components/edit_modal.d.ts +3 -1
- package/dist/components/app_config_list_editor/components/edit_modal.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/edit_modal.js +15 -2
- package/dist/components/app_config_list_editor/components/list_item_row.d.ts +4 -1
- package/dist/components/app_config_list_editor/components/list_item_row.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/components/list_item_row.js +8 -3
- package/dist/components/app_config_list_editor/index.d.ts +2 -0
- package/dist/components/app_config_list_editor/index.d.ts.map +1 -1
- package/dist/components/app_config_list_editor/index.js +1 -0
- package/dist/components/app_config_list_editor/types.d.ts +32 -1
- package/dist/components/app_config_list_editor/types.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/package.json +4 -4
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.3.0 — 2026-06-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `ColumnDef` gains `badge_class` for `list_display: 'badge'` columns: a Tailwind class string (or `(value, item) => string` function for per-value coloring) that overrides the default neutral `bg-gray-100 text-gray-600` pill. Lets each badge column render in a distinct color. Backward-compatible — omit it to keep the gray default.
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
- `AppConfigListEditor` delete confirmations now use `HazoUiConfirmDialog` (hazo_ui) for both single-item and bulk delete. The bulk delete previously fell back to the browser-native `window.confirm`; it now shows a styled destructive confirmation consistent with the single-item dialog. The internal Radix-based `DeleteDialog` is no longer used by the editor. (Was staged as unreleased 2.2.1; never published — folded into 2.3.0.)
|
|
10
|
+
|
|
11
|
+
## 2.2.0 — 2026-06-19
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `AppConfigListEditor` gains opt-in **row selection + bulk actions**: `enable_selection`, `bulk_edit_fields`, `on_bulk_update(ids, patch)`, `on_bulk_delete(ids)`, plus a bulk action bar and a select-all (indeterminate) header checkbox. New `BulkEditModal` lets each bulk-edit field be left unchanged.
|
|
15
|
+
- Optional **async per-item persistence adapter**: `on_item_create`, `on_item_update`, `on_item_delete`, and `on_reload`. When provided, create/edit/delete await these callbacks (with a Saving… state) instead of emitting a whole-array `on_items_change`. Fully backward-compatible — omit them to keep the callback-based behavior.
|
|
16
|
+
- `ColumnDef` gains `type: 'custom'` with `render_edit(value, item, set_value, ctx)` for bespoke edit controls, `format(value, item)` for list-cell display formatting, and `readonly` to render a field non-editable in the edit form.
|
|
17
|
+
|
|
3
18
|
## 2.1.11 — 2026-06-14
|
|
4
19
|
|
|
5
20
|
### Fixed (RSC bundling defect surfaced by hazo_admin)
|
|
@@ -3,5 +3,5 @@ import type { AppConfigListEditorProps } from './types.js';
|
|
|
3
3
|
* AppConfigListEditor - A polished CRUD list editor for config item arrays.
|
|
4
4
|
* Purely callback-based: no API calls, no database access.
|
|
5
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, edit_modal_max_width, }: AppConfigListEditorProps<T>): import("react").JSX.Element;
|
|
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, edit_modal_max_width, enable_selection, bulk_edit_fields, on_bulk_update, on_bulk_delete, on_item_create, on_item_update, on_item_delete, on_reload, }: AppConfigListEditorProps<T>): import("react").JSX.Element;
|
|
7
7
|
//# sourceMappingURL=app_config_list_editor.d.ts.map
|
|
@@ -1 +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":"AAQA,OAAO,KAAK,EAAE,wBAAwB,EAAqC,MAAM,YAAY,CAAA;AAS7F;;;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,EACpB,oBAAoB,
|
|
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":"AAQA,OAAO,KAAK,EAAE,wBAAwB,EAAqC,MAAM,YAAY,CAAA;AAS7F;;;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,EACpB,oBAAoB,EAEpB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,cAAc,EAEd,cAAc,EACd,cAAc,EACd,cAAc,EACd,SAAS,GACV,EAAE,wBAAwB,CAAC,CAAC,CAAC,+BAuc7B"}
|
|
@@ -3,23 +3,31 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
// AppConfigListEditor - Main component
|
|
4
4
|
// Generic CRUD list editor for arrays of structured objects stored as JSON config
|
|
5
5
|
import { useState, useCallback, useMemo } from 'react';
|
|
6
|
-
import { Plus } from 'lucide-react';
|
|
7
|
-
import { cn } from 'hazo_ui';
|
|
6
|
+
import { Plus, Trash2 } from 'lucide-react';
|
|
7
|
+
import { cn, HazoUiConfirmDialog } from 'hazo_ui';
|
|
8
8
|
import { ListItemRow } from './components/list_item_row.js';
|
|
9
9
|
import { EditModal } from './components/edit_modal.js';
|
|
10
10
|
import { SearchBar } from './components/search_bar.js';
|
|
11
11
|
import { EmptyState } from './components/empty_state.js';
|
|
12
|
-
import { DeleteDialog } from './components/delete_dialog.js';
|
|
13
12
|
import { SaveStatusIndicator } from './components/save_status_indicator.js';
|
|
14
13
|
import { ImportExportButtons } from './components/import_export_buttons.js';
|
|
14
|
+
import { BulkEditModal } from './components/bulk_edit_modal.js';
|
|
15
15
|
/**
|
|
16
16
|
* AppConfigListEditor - A polished CRUD list editor for config item arrays.
|
|
17
17
|
* Purely callback-based: no API calls, no database access.
|
|
18
18
|
*/
|
|
19
|
-
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', edit_modal_max_width,
|
|
19
|
+
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', edit_modal_max_width,
|
|
20
|
+
// Selection + bulk
|
|
21
|
+
enable_selection, bulk_edit_fields, on_bulk_update, on_bulk_delete,
|
|
22
|
+
// Async adapter
|
|
23
|
+
on_item_create, on_item_update, on_item_delete, on_reload, }) {
|
|
20
24
|
const [search_term, set_search_term] = useState('');
|
|
21
25
|
const [edit_state, set_edit_state] = useState(null);
|
|
22
26
|
const [delete_state, set_delete_state] = useState(null);
|
|
27
|
+
const [selected_ids, set_selected_ids] = useState(new Set());
|
|
28
|
+
const [bulk_edit_open, set_bulk_edit_open] = useState(false);
|
|
29
|
+
const [bulk_delete_open, set_bulk_delete_open] = useState(false);
|
|
30
|
+
const [is_saving, set_is_saving] = useState(false);
|
|
23
31
|
// Derive a friendly item label from the title (e.g., "Classification Tags" -> "tags")
|
|
24
32
|
const item_label = useMemo(() => {
|
|
25
33
|
if (!title)
|
|
@@ -50,6 +58,48 @@ export function AppConfigListEditor({ items, on_items_change, columns, id_field,
|
|
|
50
58
|
}, [items, search_term, columns]);
|
|
51
59
|
const show_search = enable_search && items.length > search_threshold;
|
|
52
60
|
const is_max_reached = max_items !== undefined && max_items !== null && items.length >= max_items;
|
|
61
|
+
// Selection helpers
|
|
62
|
+
const toggle_row = useCallback((id) => {
|
|
63
|
+
set_selected_ids(prev => {
|
|
64
|
+
const next = new Set(prev);
|
|
65
|
+
if (next.has(id)) {
|
|
66
|
+
next.delete(id);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
next.add(id);
|
|
70
|
+
}
|
|
71
|
+
return next;
|
|
72
|
+
});
|
|
73
|
+
}, []);
|
|
74
|
+
const toggle_select_all = useCallback(() => {
|
|
75
|
+
const filtered_ids = filtered_items.map(item => String(item[id_field]));
|
|
76
|
+
const all_selected = filtered_ids.length > 0 && filtered_ids.every(id => selected_ids.has(id));
|
|
77
|
+
if (all_selected) {
|
|
78
|
+
// Deselect all filtered
|
|
79
|
+
set_selected_ids(prev => {
|
|
80
|
+
const next = new Set(prev);
|
|
81
|
+
for (const id of filtered_ids)
|
|
82
|
+
next.delete(id);
|
|
83
|
+
return next;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Select all filtered
|
|
88
|
+
set_selected_ids(prev => {
|
|
89
|
+
const next = new Set(prev);
|
|
90
|
+
for (const id of filtered_ids)
|
|
91
|
+
next.add(id);
|
|
92
|
+
return next;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}, [filtered_items, id_field, selected_ids]);
|
|
96
|
+
const clear_selection = useCallback(() => {
|
|
97
|
+
set_selected_ids(new Set());
|
|
98
|
+
}, []);
|
|
99
|
+
// Bulk selection state
|
|
100
|
+
const filtered_ids = useMemo(() => filtered_items.map(item => String(item[id_field])), [filtered_items, id_field]);
|
|
101
|
+
const all_filtered_selected = filtered_ids.length > 0 && filtered_ids.every(id => selected_ids.has(id));
|
|
102
|
+
const some_filtered_selected = filtered_ids.some(id => selected_ids.has(id));
|
|
53
103
|
// CRUD handlers
|
|
54
104
|
const handle_add = useCallback(() => {
|
|
55
105
|
const empty_item = {};
|
|
@@ -80,34 +130,101 @@ export function AppConfigListEditor({ items, on_items_change, columns, id_field,
|
|
|
80
130
|
}, []);
|
|
81
131
|
const handle_delete_request = useCallback((item) => {
|
|
82
132
|
if (!delete_confirmation) {
|
|
83
|
-
// No confirmation needed
|
|
84
|
-
|
|
133
|
+
// No confirmation needed — use async adapter or on_items_change
|
|
134
|
+
if (on_item_delete) {
|
|
135
|
+
const id = String(item[id_field]);
|
|
136
|
+
set_is_saving(true);
|
|
137
|
+
on_item_delete(id)
|
|
138
|
+
.then(() => on_reload?.())
|
|
139
|
+
.finally(() => set_is_saving(false));
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
on_items_change(items.filter((i) => i[id_field] !== item[id_field]));
|
|
143
|
+
}
|
|
85
144
|
return;
|
|
86
145
|
}
|
|
87
146
|
set_delete_state({ item });
|
|
88
|
-
}, [delete_confirmation, items, id_field, on_items_change]);
|
|
89
|
-
const handle_delete_confirm = useCallback(() => {
|
|
147
|
+
}, [delete_confirmation, items, id_field, on_items_change, on_item_delete, on_reload]);
|
|
148
|
+
const handle_delete_confirm = useCallback(async () => {
|
|
90
149
|
if (!delete_state)
|
|
91
150
|
return;
|
|
92
|
-
|
|
151
|
+
if (on_item_delete) {
|
|
152
|
+
const id = String(delete_state.item[id_field]);
|
|
153
|
+
set_is_saving(true);
|
|
154
|
+
try {
|
|
155
|
+
await on_item_delete(id);
|
|
156
|
+
await on_reload?.();
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
set_is_saving(false);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
on_items_change(items.filter((i) => i[id_field] !== delete_state.item[id_field]));
|
|
164
|
+
}
|
|
93
165
|
set_delete_state(null);
|
|
94
|
-
}, [delete_state, items, id_field, on_items_change]);
|
|
95
|
-
const handle_save = useCallback((saved_item) => {
|
|
166
|
+
}, [delete_state, items, id_field, on_items_change, on_item_delete, on_reload]);
|
|
167
|
+
const handle_save = useCallback(async (saved_item) => {
|
|
96
168
|
if (edit_state?.mode === 'create') {
|
|
97
|
-
|
|
169
|
+
if (on_item_create) {
|
|
170
|
+
set_is_saving(true);
|
|
171
|
+
try {
|
|
172
|
+
await on_item_create(saved_item);
|
|
173
|
+
await on_reload?.();
|
|
174
|
+
}
|
|
175
|
+
finally {
|
|
176
|
+
set_is_saving(false);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
on_items_change([...items, saved_item]);
|
|
181
|
+
}
|
|
98
182
|
}
|
|
99
183
|
else if (edit_state?.mode === 'edit') {
|
|
100
|
-
const original_id = edit_state.original_item?.[id_field];
|
|
101
|
-
|
|
184
|
+
const original_id = String(edit_state.original_item?.[id_field] ?? '');
|
|
185
|
+
if (on_item_update) {
|
|
186
|
+
set_is_saving(true);
|
|
187
|
+
try {
|
|
188
|
+
await on_item_update(original_id, saved_item);
|
|
189
|
+
await on_reload?.();
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
set_is_saving(false);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
on_items_change(items.map((i) => (i[id_field] === edit_state.original_item?.[id_field] ? saved_item : i)));
|
|
197
|
+
}
|
|
102
198
|
}
|
|
103
199
|
set_edit_state(null);
|
|
104
|
-
}, [edit_state, items, id_field, on_items_change]);
|
|
200
|
+
}, [edit_state, items, id_field, on_items_change, on_item_create, on_item_update, on_reload]);
|
|
105
201
|
const handle_cancel_edit = useCallback(() => {
|
|
106
202
|
set_edit_state(null);
|
|
107
203
|
}, []);
|
|
108
204
|
const handle_cancel_delete = useCallback(() => {
|
|
109
205
|
set_delete_state(null);
|
|
110
206
|
}, []);
|
|
207
|
+
// Bulk action handlers
|
|
208
|
+
const handle_bulk_edit_confirm = useCallback(async (patch) => {
|
|
209
|
+
if (on_bulk_update) {
|
|
210
|
+
await on_bulk_update(Array.from(selected_ids), patch);
|
|
211
|
+
}
|
|
212
|
+
clear_selection();
|
|
213
|
+
set_bulk_edit_open(false);
|
|
214
|
+
}, [on_bulk_update, selected_ids, clear_selection]);
|
|
215
|
+
const handle_bulk_delete = useCallback(() => {
|
|
216
|
+
if (selected_ids.size === 0)
|
|
217
|
+
return;
|
|
218
|
+
set_bulk_delete_open(true);
|
|
219
|
+
}, [selected_ids]);
|
|
220
|
+
const handle_bulk_delete_confirm = useCallback(async () => {
|
|
221
|
+
const ids = Array.from(selected_ids);
|
|
222
|
+
if (on_bulk_delete) {
|
|
223
|
+
await on_bulk_delete(ids);
|
|
224
|
+
}
|
|
225
|
+
clear_selection();
|
|
226
|
+
set_bulk_delete_open(false);
|
|
227
|
+
}, [on_bulk_delete, selected_ids, clear_selection]);
|
|
111
228
|
// Get delete confirmation message
|
|
112
229
|
const delete_message = useMemo(() => {
|
|
113
230
|
if (!delete_state || !delete_confirmation)
|
|
@@ -127,7 +244,13 @@ export function AppConfigListEditor({ items, on_items_change, columns, id_field,
|
|
|
127
244
|
}
|
|
128
245
|
return String(delete_state.item[id_field] ?? '');
|
|
129
246
|
}, [delete_state, columns, id_field]);
|
|
130
|
-
|
|
247
|
+
const selection_count = selected_ids.size;
|
|
248
|
+
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: [_jsxs("div", { className: "flex items-center gap-2", children: [enable_selection && filtered_items.length > 0 && (_jsx("input", { type: "checkbox", checked: all_filtered_selected, ref: (el) => {
|
|
249
|
+
if (el)
|
|
250
|
+
el.indeterminate = !all_filtered_selected && some_filtered_selected;
|
|
251
|
+
}, onChange: toggle_select_all, className: "w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500", "aria-label": "Select all" })), _jsx("span", { className: "text-xs font-semibold uppercase tracking-wider text-gray-500", children: section_label })] }), _jsxs("div", { className: "flex items-center gap-1.5", children: [_jsx(ImportExportButtons, { items: items, on_items_change: on_items_change, columns: columns, id_field: id_field, title: title, max_items: max_items }), _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
|
|
131
252
|
? 'bg-gray-100 text-gray-400 cursor-not-allowed'
|
|
132
|
-
: '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, max_width_class: edit_modal_max_width })), delete_state && (_jsx(
|
|
253
|
+
: '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"] })] })] }), enable_selection && selection_count > 0 && (_jsxs("div", { className: "cls_bulk_action_bar flex items-center gap-3 mb-3 px-4 py-2.5 bg-violet-50 border border-violet-200 rounded-xl", children: [_jsxs("span", { className: "text-sm font-medium text-violet-800", children: [selection_count, " selected"] }), _jsx("button", { type: "button", onClick: clear_selection, className: "text-xs text-violet-600 hover:text-violet-800 underline", children: "Clear" }), _jsx("div", { className: "flex-1" }), bulk_edit_fields && bulk_edit_fields.length > 0 && on_bulk_update && (_jsx("button", { type: "button", onClick: () => set_bulk_edit_open(true), className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-full bg-violet-600 hover:bg-violet-700 text-white transition-colors", children: "Edit selected" })), on_bulk_delete && (_jsxs("button", { type: "button", onClick: handle_bulk_delete, className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium rounded-full bg-red-600 hover:bg-red-700 text-white transition-colors", children: [_jsx(Trash2, { className: "w-3.5 h-3.5" }), "Delete selected"] }))] })), 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), show_checkbox: enable_selection, is_selected: selected_ids.has(String(item[id_field])), on_toggle_select: () => toggle_row(String(item[id_field])) }, 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, max_width_class: edit_modal_max_width, is_saving: is_saving })), delete_state && (_jsx(HazoUiConfirmDialog, { open: true, onOpenChange: (is_open) => { if (!is_open)
|
|
254
|
+
handle_cancel_delete(); }, title: `Delete “${delete_item_name}”?`, description: delete_message, variant: "destructive", confirmLabel: "Delete", onConfirm: handle_delete_confirm, onCancel: handle_cancel_delete })), on_bulk_delete && (_jsx(HazoUiConfirmDialog, { open: bulk_delete_open, onOpenChange: (is_open) => { if (!is_open)
|
|
255
|
+
set_bulk_delete_open(false); }, title: `Delete ${selection_count} selected ${selection_count === 1 ? item_type_label.toLowerCase() : item_label}?`, description: "This action cannot be undone.", variant: "destructive", confirmLabel: `Delete ${selection_count}`, onConfirm: handle_bulk_delete_confirm, onCancel: () => set_bulk_delete_open(false) })), bulk_edit_fields && bulk_edit_fields.length > 0 && (_jsx(BulkEditModal, { open: bulk_edit_open, selected_count: selection_count, columns: columns, bulk_edit_fields: bulk_edit_fields, on_confirm: handle_bulk_edit_confirm, on_cancel: () => set_bulk_edit_open(false) }))] }));
|
|
133
256
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ColumnDef } from '../types.js';
|
|
2
|
+
export interface BulkEditModalProps<T extends Record<string, unknown>> {
|
|
3
|
+
open: boolean;
|
|
4
|
+
selected_count: number;
|
|
5
|
+
columns: ColumnDef<T>[];
|
|
6
|
+
bulk_edit_fields: (keyof T & string)[];
|
|
7
|
+
on_confirm: (patch: Partial<T>) => void;
|
|
8
|
+
on_cancel: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function BulkEditModal<T extends Record<string, unknown>>({ open, selected_count, columns, bulk_edit_fields, on_confirm, on_cancel, }: BulkEditModalProps<T>): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=bulk_edit_modal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk_edit_modal.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/bulk_edit_modal.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C,MAAM,WAAW,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACnE,IAAI,EAAE,OAAO,CAAA;IACb,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACvB,gBAAgB,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,EAAE,CAAA;IACtC,UAAU,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;IACvC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/D,IAAI,EACJ,cAAc,EACd,OAAO,EACP,gBAAgB,EAChB,UAAU,EACV,SAAS,GACV,EAAE,kBAAkB,CAAC,CAAC,CAAC,+BAuOvB"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
// Bulk edit modal component
|
|
4
|
+
// Allows editing shared fields across multiple selected items
|
|
5
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
6
|
+
import { cn } from 'hazo_ui';
|
|
7
|
+
import { Input } from '../../ui/input.js';
|
|
8
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '../../ui/dialog.js';
|
|
9
|
+
import { Button } from '../../ui/button.js';
|
|
10
|
+
export function BulkEditModal({ open, selected_count, columns, bulk_edit_fields, on_confirm, on_cancel, }) {
|
|
11
|
+
// Only the columns that are listed in bulk_edit_fields
|
|
12
|
+
const editable_cols = columns.filter(c => bulk_edit_fields.includes(c.field));
|
|
13
|
+
// form_data holds the current value for each field
|
|
14
|
+
const [form_data, set_form_data] = useState({});
|
|
15
|
+
// enabled_fields tracks which fields the user has opted into
|
|
16
|
+
const [enabled_fields, set_enabled_fields] = useState(new Set());
|
|
17
|
+
// Reset on open
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (open) {
|
|
20
|
+
const initial = {};
|
|
21
|
+
for (const col of editable_cols) {
|
|
22
|
+
if (col.type === 'toggle') {
|
|
23
|
+
initial[col.field] = false;
|
|
24
|
+
}
|
|
25
|
+
else if (col.type === 'tag_picker') {
|
|
26
|
+
initial[col.field] = [];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
initial[col.field] = '';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
set_form_data(initial);
|
|
33
|
+
set_enabled_fields(new Set());
|
|
34
|
+
}
|
|
35
|
+
}, [open]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
36
|
+
const update_field = useCallback((field, value) => {
|
|
37
|
+
set_form_data(prev => ({ ...prev, [field]: value }));
|
|
38
|
+
}, []);
|
|
39
|
+
const toggle_field_enabled = useCallback((field) => {
|
|
40
|
+
set_enabled_fields(prev => {
|
|
41
|
+
const next = new Set(prev);
|
|
42
|
+
if (next.has(field)) {
|
|
43
|
+
next.delete(field);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
next.add(field);
|
|
47
|
+
}
|
|
48
|
+
return next;
|
|
49
|
+
});
|
|
50
|
+
}, []);
|
|
51
|
+
const handle_confirm = useCallback(() => {
|
|
52
|
+
const patch = {};
|
|
53
|
+
for (const col of editable_cols) {
|
|
54
|
+
if (enabled_fields.has(col.field)) {
|
|
55
|
+
patch[col.field] = form_data[col.field];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
on_confirm(patch);
|
|
59
|
+
}, [editable_cols, enabled_fields, form_data, on_confirm]);
|
|
60
|
+
const render_field_control = (col) => {
|
|
61
|
+
const value = form_data[col.field];
|
|
62
|
+
const is_enabled = enabled_fields.has(col.field);
|
|
63
|
+
if (col.type === 'toggle') {
|
|
64
|
+
return (_jsx("button", { type: "button", role: "switch", "aria-checked": Boolean(value), disabled: !is_enabled, onClick: () => update_field(col.field, !value), className: cn('relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors', value ? 'bg-primary' : 'bg-gray-200', !is_enabled && 'opacity-40 cursor-not-allowed'), children: _jsx("span", { className: cn('pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow ring-0 transition-transform', value ? 'translate-x-4' : 'translate-x-0') }) }));
|
|
65
|
+
}
|
|
66
|
+
if (col.type === 'select' && col.options) {
|
|
67
|
+
return (_jsxs("select", { value: String(value ?? ''), disabled: !is_enabled, 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', !is_enabled && 'opacity-40 cursor-not-allowed bg-gray-50'), children: [_jsx("option", { value: "", children: col.placeholder || 'Select...' }), col.options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }));
|
|
68
|
+
}
|
|
69
|
+
if (col.type === 'textarea') {
|
|
70
|
+
return (_jsx("textarea", { value: String(value ?? ''), disabled: !is_enabled, 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', !is_enabled && 'opacity-40 bg-gray-50') }));
|
|
71
|
+
}
|
|
72
|
+
if (col.type === 'number') {
|
|
73
|
+
return (_jsx(Input, { type: "number", value: value !== undefined && value !== null ? String(value) : '', disabled: !is_enabled, 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', !is_enabled && 'opacity-40 bg-gray-50') }));
|
|
74
|
+
}
|
|
75
|
+
// Default: text
|
|
76
|
+
return (_jsx(Input, { type: "text", value: String(value ?? ''), disabled: !is_enabled, onChange: (e) => update_field(col.field, e.target.value), placeholder: col.placeholder, className: cn('rounded-lg focus-visible:ring-violet-500/20 focus-visible:ring-offset-0', !is_enabled && 'opacity-40 bg-gray-50') }));
|
|
77
|
+
};
|
|
78
|
+
return (_jsx(Dialog, { open: open, onOpenChange: (is_open) => { if (!is_open)
|
|
79
|
+
on_cancel(); }, children: _jsxs(DialogContent, { className: "cls_bulk_edit_modal w-[95vw] rounded-2xl p-0 gap-0 overflow-hidden max-h-[90vh] flex flex-col sm:max-w-lg", children: [_jsxs(DialogHeader, { className: "px-6 pt-6 pb-4 shrink-0", children: [_jsxs(DialogTitle, { children: ["Edit ", selected_count, " selected items"] }), _jsx("p", { className: "text-sm text-gray-500 mt-1", children: "Enable a field below to apply a new value to all selected items. Disabled fields are left unchanged." })] }), _jsxs("div", { className: "px-6 pb-4 space-y-3 overflow-y-auto flex-1 min-h-0", children: [editable_cols.map((col) => {
|
|
80
|
+
const is_enabled = enabled_fields.has(col.field);
|
|
81
|
+
return (_jsxs("div", { className: "cls_bulk_edit_field", children: [_jsxs("div", { className: "flex items-center gap-2 mb-1.5", children: [_jsx("input", { type: "checkbox", id: `bulk_enable_${col.field}`, checked: is_enabled, onChange: () => toggle_field_enabled(col.field), className: "w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500" }), _jsxs("label", { htmlFor: `bulk_enable_${col.field}`, className: cn('text-sm font-medium cursor-pointer select-none', is_enabled ? 'text-gray-700' : 'text-gray-400'), children: [col.label, !is_enabled && (_jsx("span", { className: "ml-2 text-xs font-normal text-gray-400", children: "Leave unchanged" }))] })] }), _jsx("div", { className: cn('transition-opacity', !is_enabled && 'opacity-50'), children: col.type === 'toggle' ? (_jsxs("div", { className: "flex items-center justify-between py-1 pl-6", children: [_jsx("span", { className: "text-sm text-gray-500", children: col.label }), render_field_control(col)] })) : (_jsx("div", { className: "pl-6", children: render_field_control(col) })) })] }, col.field));
|
|
82
|
+
}), editable_cols.length === 0 && (_jsx("p", { className: "text-sm text-gray-400 py-4 text-center", children: "No editable fields configured." }))] }), _jsxs(DialogFooter, { className: "bg-gray-50 px-6 py-4 border-t border-gray-100 sm:justify-between shrink-0", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: on_cancel, className: "rounded-full", children: "Cancel" }), _jsxs(Button, { type: "button", onClick: handle_confirm, disabled: enabled_fields.size === 0, className: "rounded-full bg-violet-600 hover:bg-violet-700 text-white disabled:opacity-50 disabled:cursor-not-allowed", children: ["Apply to ", selected_count, " items"] })] })] }) }));
|
|
83
|
+
}
|
|
@@ -15,7 +15,9 @@ interface EditModalProps<T extends Record<string, unknown>> {
|
|
|
15
15
|
on_cancel: () => void;
|
|
16
16
|
/** Tailwind max-width class(es). Default: `sm:max-w-2xl`. */
|
|
17
17
|
max_width_class?: string;
|
|
18
|
+
/** When true, disables the Save button (async adapter in progress) */
|
|
19
|
+
is_saving?: boolean;
|
|
18
20
|
}
|
|
19
|
-
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, max_width_class, }: EditModalProps<T>): React.JSX.Element;
|
|
21
|
+
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, max_width_class, is_saving, }: EditModalProps<T>): React.JSX.Element;
|
|
20
22
|
export {};
|
|
21
23
|
//# sourceMappingURL=edit_modal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"edit_modal.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/edit_modal.tsx"],"names":[],"mappings":"AAKA,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;IACrB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"edit_modal.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/edit_modal.tsx"],"names":[],"mappings":"AAKA,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;IACrB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;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,EACT,eAAgC,EAChC,SAAiB,GAClB,EAAE,cAAc,CAAC,CAAC,CAAC,qBAoTnB"}
|
|
@@ -9,7 +9,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from
|
|
|
9
9
|
import { Button } from '../../ui/button.js';
|
|
10
10
|
import { ColorSwatchPicker } from './color_swatch_picker.js';
|
|
11
11
|
import { slugify } from '../types.js';
|
|
12
|
-
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, max_width_class = 'sm:max-w-2xl', }) {
|
|
12
|
+
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, max_width_class = 'sm:max-w-2xl', is_saving = false, }) {
|
|
13
13
|
const [form_data, set_form_data] = useState({});
|
|
14
14
|
const [errors, set_errors] = useState(new Map());
|
|
15
15
|
const [user_touched_id, set_user_touched_id] = useState(false);
|
|
@@ -89,6 +89,19 @@ export function EditModal({ open, mode, item: initial_item, columns, id_field, a
|
|
|
89
89
|
const error = errors.get(col.field);
|
|
90
90
|
const is_id_field = col.field === id_field;
|
|
91
91
|
const is_disabled = is_id_field && mode === 'edit' && !id_editable_after_create;
|
|
92
|
+
// Readonly fields — show a non-editable display
|
|
93
|
+
if (col.readonly) {
|
|
94
|
+
const display_value = col.format
|
|
95
|
+
? col.format(value, form_data)
|
|
96
|
+
: String(value ?? '');
|
|
97
|
+
return (_jsxs("div", { className: "cls_edit_field space-y-1.5", children: [_jsx("label", { className: "text-sm font-medium text-gray-700", children: col.label }), _jsx("div", { className: "flex h-10 w-full items-center rounded-lg border border-input bg-gray-50 px-3 py-2 text-sm text-gray-500", children: display_value })] }, col.field));
|
|
98
|
+
}
|
|
99
|
+
// Custom render_edit or type === 'custom'
|
|
100
|
+
if (col.render_edit || col.type === 'custom') {
|
|
101
|
+
if (!col.render_edit)
|
|
102
|
+
return null;
|
|
103
|
+
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.render_edit(value, form_data, (v) => update_field(col.field, v), { mode }), error && _jsx("p", { className: "text-xs text-red-600", children: error })] }, col.field));
|
|
104
|
+
}
|
|
92
105
|
// Tag picker fields — render with dedicated component
|
|
93
106
|
if (col.type === 'tag_picker') {
|
|
94
107
|
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: "*" })] }), _jsx(TagPickerField, { value: Array.isArray(value) ? value : (typeof value === 'string' && value ? value.split(',').map(s => s.trim()).filter(Boolean) : []), options: col.tag_options ?? [], on_change: (tags) => update_field(col.field, tags) }), error && _jsx("p", { className: "text-xs text-red-600", children: error })] }, col.field));
|
|
@@ -102,7 +115,7 @@ export function EditModal({ open, mode, item: initial_item, columns, id_field, a
|
|
|
102
115
|
: 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) })), 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));
|
|
103
116
|
};
|
|
104
117
|
return (_jsx(Dialog, { open: open, onOpenChange: (is_open) => { if (!is_open)
|
|
105
|
-
on_cancel(); }, children: _jsxs(DialogContent, { className: cn('cls_edit_modal w-[95vw] rounded-2xl p-0 gap-0 overflow-hidden max-h-[90vh] flex flex-col', max_width_class), children: [_jsx(DialogHeader, { className: "px-6 pt-6 pb-4 shrink-0", children: _jsx(DialogTitle, { children: mode === 'create' ? `New ${item_type_label}` : `Edit ${item_type_label}` }) }), _jsxs("div", { className: "px-6 pb-4 space-y-4 overflow-y-auto flex-1 min-h-0", 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 shrink-0", 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' })] })] }) }));
|
|
118
|
+
on_cancel(); }, children: _jsxs(DialogContent, { className: cn('cls_edit_modal w-[95vw] rounded-2xl p-0 gap-0 overflow-hidden max-h-[90vh] flex flex-col', max_width_class), children: [_jsx(DialogHeader, { className: "px-6 pt-6 pb-4 shrink-0", children: _jsx(DialogTitle, { children: mode === 'create' ? `New ${item_type_label}` : `Edit ${item_type_label}` }) }), _jsxs("div", { className: "px-6 pb-4 space-y-4 overflow-y-auto flex-1 min-h-0", 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 shrink-0", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: on_cancel, className: "rounded-full", children: "Cancel" }), _jsx(Button, { type: "button", onClick: handle_save, disabled: is_saving, className: "rounded-full bg-violet-600 hover:bg-violet-700 text-white disabled:opacity-50 disabled:cursor-not-allowed", children: is_saving ? 'Saving…' : mode === 'create' ? `Add ${item_type_label}` : 'Save Changes' })] })] }) }));
|
|
106
119
|
}
|
|
107
120
|
/* ── Tag Picker Field ── */
|
|
108
121
|
function TagPickerField({ value, options, on_change }) {
|
|
@@ -8,7 +8,10 @@ interface ListItemRowProps<T extends Record<string, unknown>> {
|
|
|
8
8
|
render_item_indicator?: (item: T) => React.ReactNode;
|
|
9
9
|
on_edit: () => void;
|
|
10
10
|
on_delete: () => void;
|
|
11
|
+
show_checkbox?: boolean;
|
|
12
|
+
is_selected?: boolean;
|
|
13
|
+
on_toggle_select?: () => void;
|
|
11
14
|
}
|
|
12
|
-
export declare function ListItemRow<T extends Record<string, unknown>>({ item, index, columns, render_item, render_item_indicator, on_edit, on_delete, }: ListItemRowProps<T>): React.JSX.Element;
|
|
15
|
+
export declare function ListItemRow<T extends Record<string, unknown>>({ item, index, columns, render_item, render_item_indicator, on_edit, on_delete, show_checkbox, is_selected, on_toggle_select, }: ListItemRowProps<T>): React.JSX.Element;
|
|
13
16
|
export {};
|
|
14
17
|
//# sourceMappingURL=list_item_row.d.ts.map
|
|
@@ -1 +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;
|
|
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;IACrB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;CAC9B;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,EACT,aAAa,EACb,WAAW,EACX,gBAAgB,GACjB,EAAE,gBAAgB,CAAC,CAAC,CAAC,qBA0HrB"}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Pencil, Trash2 } from 'lucide-react';
|
|
3
3
|
import { cn } from 'hazo_ui';
|
|
4
|
-
export function ListItemRow({ item, index, columns, render_item, render_item_indicator, on_edit, on_delete, }) {
|
|
4
|
+
export function ListItemRow({ item, index, columns, render_item, render_item_indicator, on_edit, on_delete, show_checkbox, is_selected, on_toggle_select, }) {
|
|
5
5
|
// Custom render overrides everything
|
|
6
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" }) })] })] }));
|
|
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: [show_checkbox && (_jsx("input", { type: "checkbox", checked: is_selected ?? false, onChange: on_toggle_select, className: "shrink-0 w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500", "aria-label": "Select item" })), _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
8
|
}
|
|
9
9
|
// Default rendering based on column definitions
|
|
10
10
|
const primary_cols = columns.filter(c => c.list_display === 'primary');
|
|
11
11
|
const secondary_cols = columns.filter(c => c.list_display === 'secondary');
|
|
12
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) =>
|
|
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: [show_checkbox && (_jsx("input", { type: "checkbox", checked: is_selected ?? false, onChange: on_toggle_select, className: "shrink-0 w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500", "aria-label": "Select item" })), 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: col.format?.(item[col.field], item) ?? String(item[col.field] ?? '') }, col.field))), secondary_cols.map((col) => (_jsx("div", { className: "text-xs text-gray-500 truncate mt-0.5", children: col.format?.(item[col.field], item) ?? 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) => {
|
|
14
|
+
const badge_class = typeof col.badge_class === 'function'
|
|
15
|
+
? col.badge_class(item[col.field], item)
|
|
16
|
+
: col.badge_class;
|
|
17
|
+
return (_jsx("span", { className: cn('inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-mono', badge_class || 'bg-gray-100 text-gray-600'), children: col.format?.(item[col.field], item) ?? String(item[col.field] ?? '') }, col.field));
|
|
18
|
+
}) })), _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
19
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { AppConfigListEditor } from './app_config_list_editor.js';
|
|
2
2
|
export type { AppConfigListEditorProps, ColumnDef, ImportResult } from './types.js';
|
|
3
|
+
export { BulkEditModal } from './components/bulk_edit_modal.js';
|
|
4
|
+
export type { BulkEditModalProps } from './components/bulk_edit_modal.js';
|
|
3
5
|
export { validate_import_data, merge_items, export_items_to_json, generate_export_filename, } from './utils/json_import_export.js';
|
|
4
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +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,YAAY,EAAE,MAAM,YAAY,CAAA;AACnF,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,+BAA+B,CAAA"}
|
|
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,YAAY,EAAE,MAAM,YAAY,CAAA;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAA;AAC/D,YAAY,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AACzE,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,+BAA+B,CAAA"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
// Public exports for AppConfigListEditor
|
|
2
2
|
export { AppConfigListEditor } from './app_config_list_editor.js';
|
|
3
|
+
export { BulkEditModal } from './components/bulk_edit_modal.js';
|
|
3
4
|
export { validate_import_data, merge_items, export_items_to_json, generate_export_filename, } from './utils/json_import_export.js';
|
|
@@ -8,7 +8,7 @@ export interface ColumnDef<T> {
|
|
|
8
8
|
/** Display label for the form */
|
|
9
9
|
label: string;
|
|
10
10
|
/** Column type determines the input control */
|
|
11
|
-
type: 'text' | 'textarea' | 'color_swatch' | 'select' | 'number' | 'toggle' | 'tag_picker';
|
|
11
|
+
type: 'text' | 'textarea' | 'color_swatch' | 'select' | 'number' | 'toggle' | 'tag_picker' | 'custom';
|
|
12
12
|
/** Placeholder text */
|
|
13
13
|
placeholder?: string;
|
|
14
14
|
/** Whether this field is required */
|
|
@@ -17,6 +17,13 @@ export interface ColumnDef<T> {
|
|
|
17
17
|
show_in_list?: boolean;
|
|
18
18
|
/** How to display in list view */
|
|
19
19
|
list_display?: 'primary' | 'secondary' | 'badge' | 'hidden';
|
|
20
|
+
/**
|
|
21
|
+
* Tailwind class(es) controlling the pill color when `list_display: 'badge'`.
|
|
22
|
+
* Overrides the default neutral `bg-gray-100 text-gray-600`. May be a static
|
|
23
|
+
* string or a function of the value/item for per-value coloring. Return a
|
|
24
|
+
* falsy value to fall back to the default.
|
|
25
|
+
*/
|
|
26
|
+
badge_class?: string | ((value: unknown, item: T) => string | undefined);
|
|
20
27
|
/** Options for 'select' type */
|
|
21
28
|
options?: {
|
|
22
29
|
value: string;
|
|
@@ -31,6 +38,14 @@ export interface ColumnDef<T> {
|
|
|
31
38
|
color_options?: string[];
|
|
32
39
|
/** Validation function - returns error message or null */
|
|
33
40
|
validate?: (value: unknown, item: T) => string | null;
|
|
41
|
+
/** Custom edit control: rendered instead of built-in input when present */
|
|
42
|
+
render_edit?: (value: unknown, item: Partial<T>, set_value: (v: unknown) => void, ctx: {
|
|
43
|
+
mode: 'create' | 'edit';
|
|
44
|
+
}) => React.ReactNode;
|
|
45
|
+
/** Display formatter for list rendering (badge/secondary/primary cells) */
|
|
46
|
+
format?: (value: unknown, item: T) => string;
|
|
47
|
+
/** If true, field is shown but not editable in the edit form */
|
|
48
|
+
readonly?: boolean;
|
|
34
49
|
}
|
|
35
50
|
/**
|
|
36
51
|
* Props for the AppConfigListEditor component
|
|
@@ -77,6 +92,22 @@ export interface AppConfigListEditorProps<T extends Record<string, unknown>> {
|
|
|
77
92
|
* default `sm:max-w-2xl`. Use e.g. `sm:max-w-4xl` or `sm:max-w-[900px]`.
|
|
78
93
|
*/
|
|
79
94
|
edit_modal_max_width?: string;
|
|
95
|
+
/** Enable row checkboxes and bulk action bar */
|
|
96
|
+
enable_selection?: boolean;
|
|
97
|
+
/** Column fields to expose in the bulk edit modal */
|
|
98
|
+
bulk_edit_fields?: (keyof T & string)[];
|
|
99
|
+
/** Called with selected IDs and patch when bulk edit is confirmed */
|
|
100
|
+
on_bulk_update?: (ids: string[], patch: Partial<T>) => void | Promise<void>;
|
|
101
|
+
/** Called with selected IDs when bulk delete is confirmed */
|
|
102
|
+
on_bulk_delete?: (ids: string[]) => void | Promise<void>;
|
|
103
|
+
/** Called instead of on_items_change when creating a new item */
|
|
104
|
+
on_item_create?: (item: T) => Promise<void>;
|
|
105
|
+
/** Called instead of on_items_change when updating an item */
|
|
106
|
+
on_item_update?: (id: string, item: T) => Promise<void>;
|
|
107
|
+
/** Called instead of on_items_change when deleting an item */
|
|
108
|
+
on_item_delete?: (id: string) => Promise<void>;
|
|
109
|
+
/** Called after async create/update/delete to trigger a data refresh */
|
|
110
|
+
on_reload?: () => void | Promise<void>;
|
|
80
111
|
}
|
|
81
112
|
/**
|
|
82
113
|
* Internal state for the edit modal
|
|
@@ -1 +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,GAAG,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/app_config_list_editor/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,5 +10,7 @@ export { useAppConfig } from './use_app_config.js';
|
|
|
10
10
|
export type { UseAppConfigResult } from '../lib/app_config_types.js';
|
|
11
11
|
export { AppConfigListEditor } from './app_config_list_editor/index.js';
|
|
12
12
|
export type { AppConfigListEditorProps, ColumnDef, ImportResult } from './app_config_list_editor/index.js';
|
|
13
|
+
export { BulkEditModal } from './app_config_list_editor/index.js';
|
|
14
|
+
export type { BulkEditModalProps } from './app_config_list_editor/index.js';
|
|
13
15
|
export { validate_import_data, merge_items, export_items_to_json, generate_export_filename, } from './app_config_list_editor/index.js';
|
|
14
16
|
//# 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;AAGpE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAA;AACvE,YAAY,EAAE,wBAAwB,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAA;AAC1G,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,mCAAmC,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,YAAY,EAAE,MAAM,mCAAmC,CAAA;AAC1G,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAA;AACjE,YAAY,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAA;AAC3E,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,wBAAwB,GACzB,MAAM,mCAAmC,CAAA"}
|
package/dist/components/index.js
CHANGED
|
@@ -8,4 +8,5 @@ export { useConfigSections, is_sensitive_field, mask_value, DEFAULT_SENSITIVE_PA
|
|
|
8
8
|
export { useAppConfig } from './use_app_config.js';
|
|
9
9
|
// AppConfigListEditor exports
|
|
10
10
|
export { AppConfigListEditor } from './app_config_list_editor/index.js';
|
|
11
|
+
export { BulkEditModal } from './app_config_list_editor/index.js';
|
|
11
12
|
export { validate_import_data, merge_items, export_items_to_json, generate_export_filename, } from './app_config_list_editor/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_config",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Config wrapper with error handling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -81,12 +81,12 @@
|
|
|
81
81
|
"@storybook/react": "^8.0.0",
|
|
82
82
|
"@storybook/react-vite": "^8.0.0",
|
|
83
83
|
"@storybook/test": "^8.0.0",
|
|
84
|
+
"@tailwindcss/postcss": "^4.2.4",
|
|
84
85
|
"@testing-library/react": "^14.3.1",
|
|
85
86
|
"@types/ini": "^4.1.0",
|
|
86
87
|
"@types/node": "^20.14.10",
|
|
87
88
|
"@types/react": "^18.3.3",
|
|
88
89
|
"@types/react-dom": "^18.3.0",
|
|
89
|
-
"@tailwindcss/postcss": "^4.2.4",
|
|
90
90
|
"@vitejs/plugin-react": "^4.2.0",
|
|
91
91
|
"hazo_connect": "^3.8.0",
|
|
92
92
|
"jsdom": "^24.1.3",
|
|
@@ -97,11 +97,11 @@
|
|
|
97
97
|
"tailwindcss": "^4.2.4",
|
|
98
98
|
"typescript": "^5.7.2",
|
|
99
99
|
"vite": "^6.0.0",
|
|
100
|
-
"vitest": "^
|
|
100
|
+
"vitest": "^4.0.15"
|
|
101
101
|
},
|
|
102
102
|
"peerDependencies": {
|
|
103
103
|
"hazo_core": "^1.2.0",
|
|
104
|
-
"hazo_ui": "^4.
|
|
104
|
+
"hazo_ui": "^4.4.0",
|
|
105
105
|
"react": "^18.0.0 || ^19.0.0",
|
|
106
106
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
107
107
|
"tailwindcss": "^4.0.0"
|