hazo_config 2.5.0 → 2.6.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.
Files changed (37) hide show
  1. package/CHANGE_LOG.md +47 -0
  2. package/dist/components/app_config.d.ts.map +1 -1
  3. package/dist/components/app_config.js +14 -13
  4. package/dist/components/app_config_list_editor/app_config_list_editor.d.ts.map +1 -1
  5. package/dist/components/app_config_list_editor/app_config_list_editor.js +8 -4
  6. package/dist/components/app_config_list_editor/components/bulk_edit_modal.d.ts.map +1 -1
  7. package/dist/components/app_config_list_editor/components/bulk_edit_modal.js +5 -4
  8. package/dist/components/app_config_list_editor/components/color_swatch_picker.d.ts.map +1 -1
  9. package/dist/components/app_config_list_editor/components/color_swatch_picker.js +2 -1
  10. package/dist/components/app_config_list_editor/components/delete_dialog.d.ts.map +1 -1
  11. package/dist/components/app_config_list_editor/components/delete_dialog.js +2 -1
  12. package/dist/components/app_config_list_editor/components/edit_modal.d.ts.map +1 -1
  13. package/dist/components/app_config_list_editor/components/edit_modal.js +14 -7
  14. package/dist/components/app_config_list_editor/components/empty_state.d.ts.map +1 -1
  15. package/dist/components/app_config_list_editor/components/empty_state.js +2 -1
  16. package/dist/components/app_config_list_editor/components/import_export_buttons.d.ts.map +1 -1
  17. package/dist/components/app_config_list_editor/components/import_export_buttons.js +8 -7
  18. package/dist/components/app_config_list_editor/components/list_item_row.d.ts.map +1 -1
  19. package/dist/components/app_config_list_editor/components/list_item_row.js +4 -3
  20. package/dist/components/app_config_list_editor/components/save_status_indicator.d.ts.map +1 -1
  21. package/dist/components/app_config_list_editor/components/save_status_indicator.js +2 -1
  22. package/dist/components/app_config_list_editor/components/search_bar.d.ts.map +1 -1
  23. package/dist/components/app_config_list_editor/components/search_bar.js +2 -1
  24. package/dist/components/config_editor.d.ts.map +1 -1
  25. package/dist/components/config_editor.js +5 -4
  26. package/dist/components/config_viewer.d.ts.map +1 -1
  27. package/dist/components/config_viewer.js +3 -2
  28. package/dist/components/ui/button.d.ts +1 -1
  29. package/dist/components/ui/button.d.ts.map +1 -1
  30. package/dist/lib/index.d.ts +1 -0
  31. package/dist/lib/index.d.ts.map +1 -1
  32. package/dist/lib/index.js +3 -0
  33. package/dist/lib/strings.d.ts +2 -0
  34. package/dist/lib/strings.d.ts.map +1 -0
  35. package/dist/lib/strings.js +18 -0
  36. package/dist/locales/en.json +104 -0
  37. package/package.json +12 -6
package/CHANGE_LOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.6.0 — 2026-09-21
4
+
5
+ ### Added
6
+
7
+ Full adoption of the canonical `hazo_core/i18n` bridge (`createPackageT`) for
8
+ this package's own baked-in UI strings — hazo_config previously had no i18n
9
+ bridge at all.
10
+
11
+ - `src/locales/en.json` — a new flat, literal-dotted fallback catalog (102
12
+ keys) covering every `'use client'` component's user-facing text: the
13
+ `ConfigEditor`/`ConfigViewer` screens, `AppConfig` (+ its add-key/add-section
14
+ `KeyDialog`), and the whole `AppConfigListEditor` family (`SearchBar`,
15
+ `EmptyState`, `ImportExportButtons`, `ListItemRow`, `EditModal`,
16
+ `BulkEditModal`, `DeleteDialog`, `ColorSwatchPicker`,
17
+ `SaveStatusIndicator`). Namespaced by component (`config_editor.*`,
18
+ `config_viewer.*`, `app_config.*`, `list_editor.*`, `bulk_edit.*`,
19
+ `edit_modal.*`, `search.*`, `empty_state.*`, `import_export.*`,
20
+ `list_item.*`, `save_status.*`, `color_swatch.*`), with a small `common.*`
21
+ namespace (`cancel`, `delete`, `select_placeholder`, `hide_value`,
22
+ `show_value`) for strings that were byte-identical across components.
23
+ `{{var}}` mustache interpolation used for all dynamic copy (item counts,
24
+ labels, key names).
25
+ - `src/lib/strings.ts` — `export const t = createPackageT('hazo_config', en)`,
26
+ re-exported from `hazo_config/lib`. A plain JSON import (no
27
+ `with { type: 'json' }` attribute) is used since every consuming component
28
+ is client-bundled and this package's tsconfig already has
29
+ `moduleResolution: "bundler"` + `resolveJsonModule: true`.
30
+ - Every hardcoded UI string literal across the components above now routes
31
+ through `t()` — labels, buttons, aria-labels, placeholders, validation
32
+ messages, and toast/status text. Original estimate was ~30 strings; actual
33
+ extraction came to 102 catalog keys (the `AppConfigListEditor` family alone
34
+ accounts for over half).
35
+ - `hazo_ui/test-harness` autotest group `hazo_config_i18n` (new
36
+ `test-app/app/autotest/` route + sidebar entry) — catalog-loads,
37
+ all-sampled-keys-resolve, interpolation, and coverage-threshold cases,
38
+ matching the pattern used by `hazo_ui`'s own i18n autotest scenarios.
39
+ - `hazo_i18n` added as an **optional** peer dependency (`^0.5.0`); `hazo_core`
40
+ peer range bumped `^2.3.0` → `^2.4.0` (first version shipping
41
+ `hazo_core/i18n`).
42
+
43
+ ### Fixed
44
+
45
+ `tsconfig.build.json`'s `moduleResolution` was hardcoded to the classic
46
+ `"node"` resolver, which cannot follow a `package.json` `exports` subpath
47
+ (e.g. `hazo_core/i18n`) — TS2307. Switched to `"bundler"`, matching the
48
+ package's own `tsconfig.json`.
49
+
3
50
  ## 2.5.0 — 2026-09-06
4
51
 
5
52
  ### Added
@@ -1 +1 @@
1
- {"version":3,"file":"app_config.d.ts","sourceRoot":"","sources":["../../src/components/app_config.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAK7D,OAAO,KAAK,EAAE,cAAc,EAA6B,MAAM,4BAA4B,CAAA;AA0P3F;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA6a9C,CAAA"}
1
+ {"version":3,"file":"app_config.d.ts","sourceRoot":"","sources":["../../src/components/app_config.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAM7D,OAAO,KAAK,EAAE,cAAc,EAA6B,MAAM,4BAA4B,CAAA;AA0P3F;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,CA6a9C,CAAA"}
@@ -7,6 +7,7 @@ import { cn } from 'hazo_ui';
7
7
  import { RefreshCw, Save, Trash2, Eye, EyeOff, Plus, X } from 'lucide-react';
8
8
  import { useAppConfig } from './use_app_config.js';
9
9
  import { is_sensitive_field, mask_value, DEFAULT_SENSITIVE_PATTERNS } from './use_config_sections.js';
10
+ import { t } from '../lib/strings.js';
10
11
  const JsonEditor = ({ value, onChange, onValidationChange, placeholder, className }) => {
11
12
  const [text_value, set_text_value] = useState(() => JSON.stringify(value, null, 2));
12
13
  const [validation_error, set_validation_error] = useState();
@@ -20,7 +21,7 @@ const JsonEditor = ({ value, onChange, onValidationChange, placeholder, classNam
20
21
  onValidationChange?.(true);
21
22
  }
22
23
  catch (err) {
23
- const error_msg = err instanceof Error ? err.message : 'Invalid JSON format';
24
+ const error_msg = err instanceof Error ? err.message : t('app_config.invalid_json');
24
25
  set_validation_error(error_msg);
25
26
  onValidationChange?.(false, error_msg);
26
27
  }
@@ -67,7 +68,7 @@ const KeyDialog = ({ mode, section_name, on_close, on_save }) => {
67
68
  : new_key && new_value && is_json_valid;
68
69
  if (!mode)
69
70
  return null;
70
- return (_jsx("div", { className: "fixed inset-0 bg-black/50 flex items-center justify-center z-50", children: _jsxs("div", { className: "bg-white rounded-lg shadow-xl max-w-md w-full mx-4", children: [_jsxs("div", { className: "flex items-center justify-between p-6 border-b", children: [_jsx("h3", { className: "text-lg font-semibold", children: mode === 'add-section' ? 'Add New Section' : 'Add New Key' }), _jsx("button", { onClick: on_close, className: "text-muted-foreground hover:text-foreground", type: "button", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsxs("div", { className: "p-6 space-y-4", children: [mode === 'add-section' && (_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: "Section Name" }), _jsx("input", { type: "text", placeholder: "e.g., integrations, notifications", value: new_section, onChange: (e) => set_new_section(e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary", autoFocus: true })] })), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: "Key Name" }), _jsx("input", { type: "text", placeholder: "e.g., api_key, webhook_url", value: new_key, onChange: (e) => set_new_key(e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary", autoFocus: mode !== 'add-section' })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: "Type" }), _jsxs("div", { className: "flex gap-4", children: [_jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [_jsx("input", { type: "radio", name: "type", value: "general", checked: new_type === 'general', onChange: (e) => set_new_type(e.target.value), className: "w-4 h-4 text-primary" }), _jsx("span", { className: "text-sm", children: "General (Text)" })] }), _jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [_jsx("input", { type: "radio", name: "type", value: "json", checked: new_type === 'json', onChange: (e) => set_new_type(e.target.value), className: "w-4 h-4 text-primary" }), _jsx("span", { className: "text-sm", children: "JSON (Object)" })] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: "Value" }), new_type === 'json' ? (_jsx(JsonEditor, { value: new_value ? (() => {
71
+ return (_jsx("div", { className: "fixed inset-0 bg-black/50 flex items-center justify-center z-50", children: _jsxs("div", { className: "bg-white rounded-lg shadow-xl max-w-md w-full mx-4", children: [_jsxs("div", { className: "flex items-center justify-between p-6 border-b", children: [_jsx("h3", { className: "text-lg font-semibold", children: mode === 'add-section' ? t('app_config.dialog_title_add_section') : t('app_config.dialog_title_add_key') }), _jsx("button", { onClick: on_close, className: "text-muted-foreground hover:text-foreground", type: "button", children: _jsx(X, { className: "h-5 w-5" }) })] }), _jsxs("div", { className: "p-6 space-y-4", children: [mode === 'add-section' && (_jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: t('app_config.section_name_label') }), _jsx("input", { type: "text", placeholder: t('app_config.section_name_placeholder'), value: new_section, onChange: (e) => set_new_section(e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary", autoFocus: true })] })), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: t('app_config.key_name_label') }), _jsx("input", { type: "text", placeholder: t('app_config.key_name_placeholder'), value: new_key, onChange: (e) => set_new_key(e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary", autoFocus: mode !== 'add-section' })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: t('app_config.type_label') }), _jsxs("div", { className: "flex gap-4", children: [_jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [_jsx("input", { type: "radio", name: "type", value: "general", checked: new_type === 'general', onChange: (e) => set_new_type(e.target.value), className: "w-4 h-4 text-primary" }), _jsx("span", { className: "text-sm", children: t('app_config.type_general') })] }), _jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [_jsx("input", { type: "radio", name: "type", value: "json", checked: new_type === 'json', onChange: (e) => set_new_type(e.target.value), className: "w-4 h-4 text-primary" }), _jsx("span", { className: "text-sm", children: t('app_config.type_json') })] })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm font-medium", children: t('app_config.value_label') }), new_type === 'json' ? (_jsx(JsonEditor, { value: new_value ? (() => {
71
72
  try {
72
73
  return JSON.parse(new_value);
73
74
  }
@@ -76,9 +77,9 @@ const KeyDialog = ({ mode, section_name, on_close, on_save }) => {
76
77
  }
77
78
  })() : {}, onChange: (val) => set_new_value(JSON.stringify(val, null, 2)), onValidationChange: (isValid) => {
78
79
  set_is_json_valid(isValid);
79
- }, placeholder: '{"key": "value"}' })) : (_jsx("input", { type: "text", placeholder: "Enter value", value: new_value, onChange: (e) => set_new_value(e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary" }))] })] }), _jsxs("div", { className: "flex items-center justify-end gap-3 p-6 border-t bg-muted/30", children: [_jsx("button", { onClick: on_close, className: "px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground", type: "button", children: "Cancel" }), _jsx("button", { onClick: handle_save, disabled: !can_save || is_saving, className: cn('px-4 py-2 text-sm font-medium rounded-md', can_save && !is_saving
80
+ }, placeholder: '{"key": "value"}' })) : (_jsx("input", { type: "text", placeholder: t('app_config.value_placeholder'), value: new_value, onChange: (e) => set_new_value(e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary" }))] })] }), _jsxs("div", { className: "flex items-center justify-end gap-3 p-6 border-t bg-muted/30", children: [_jsx("button", { onClick: on_close, className: "px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground", type: "button", children: t('common.cancel') }), _jsx("button", { onClick: handle_save, disabled: !can_save || is_saving, className: cn('px-4 py-2 text-sm font-medium rounded-md', can_save && !is_saving
80
81
  ? 'bg-primary text-primary-foreground hover:bg-primary/90'
81
- : 'bg-muted text-muted-foreground cursor-not-allowed'), type: "button", children: is_saving ? 'Saving...' : mode === 'add-section' ? 'Create Section' : 'Add Key' })] })] }) }));
82
+ : 'bg-muted text-muted-foreground cursor-not-allowed'), type: "button", children: is_saving ? t('app_config.saving') : mode === 'add-section' ? t('app_config.create_section') : t('app_config.add_key') })] })] }) }));
82
83
  };
83
84
  /**
84
85
  * AppConfig component
@@ -87,7 +88,7 @@ const KeyDialog = ({ mode, section_name, on_close, on_save }) => {
87
88
  * @param props - Component props
88
89
  * @returns React component
89
90
  */
90
- export const AppConfig = ({ context, fetch_config, save_config, delete_config, className, on_update, on_error, sensitive_fields, sensitive_patterns = DEFAULT_SENSITIVE_PATTERNS, disable_auto_mask = false, title = 'Configuration', }) => {
91
+ export const AppConfig = ({ context, fetch_config, save_config, delete_config, className, on_update, on_error, sensitive_fields, sensitive_patterns = DEFAULT_SENSITIVE_PATTERNS, disable_auto_mask = false, title = t('app_config.title_default'), }) => {
91
92
  const { sections, is_loading, error, reload, set_value, delete_value, } = useAppConfig(context, fetch_config, save_config, delete_config);
92
93
  const [selected_section, set_selected_section] = useState(null);
93
94
  const [editing_fields, set_editing_fields] = useState(new Map());
@@ -217,7 +218,7 @@ export const AppConfig = ({ context, fetch_config, save_config, delete_config, c
217
218
  * Handle delete key
218
219
  */
219
220
  const handle_delete_key = useCallback(async (section, key) => {
220
- if (!window.confirm(`Delete key "${key}"?`))
221
+ if (!window.confirm(t('app_config.delete_key_confirm', { key })))
221
222
  return;
222
223
  try {
223
224
  await delete_value(section, key);
@@ -270,13 +271,13 @@ export const AppConfig = ({ context, fetch_config, save_config, delete_config, c
270
271
  return selected_section ? sections[selected_section] : null;
271
272
  }, [selected_section, sections]);
272
273
  if (is_loading) {
273
- return (_jsx("div", { className: cn('cls_app_config flex items-center justify-center p-8', className), children: _jsx("div", { className: "text-muted-foreground", children: "Loading configuration..." }) }));
274
+ return (_jsx("div", { className: cn('cls_app_config flex items-center justify-center p-8', className), children: _jsx("div", { className: "text-muted-foreground", children: t('app_config.loading') }) }));
274
275
  }
275
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: cn('cls_app_config space-y-6', className), children: [_jsxs("div", { className: "cls_app_config_header flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold", children: title }), has_unsaved_changes && (_jsx("p", { className: "text-sm text-amber-600 mt-1", children: "You have unsaved changes" }))] }), _jsxs("div", { className: "flex gap-3", children: [_jsxs("button", { onClick: handle_refresh, className: "px-4 py-2 border border-input rounded-md hover:bg-accent hover:text-accent-foreground flex items-center gap-2 transition-colors", type: "button", disabled: is_saving, children: [_jsx(RefreshCw, { className: "h-4 w-4" }), "Refresh"] }), _jsxs("button", { onClick: handle_save, className: cn('px-4 py-2 rounded-md flex items-center gap-2 transition-colors', has_unsaved_changes
276
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: cn('cls_app_config space-y-6', className), children: [_jsxs("div", { className: "cls_app_config_header flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold", children: title }), has_unsaved_changes && (_jsx("p", { className: "text-sm text-amber-600 mt-1", children: t('app_config.unsaved_changes') }))] }), _jsxs("div", { className: "flex gap-3", children: [_jsxs("button", { onClick: handle_refresh, className: "px-4 py-2 border border-input rounded-md hover:bg-accent hover:text-accent-foreground flex items-center gap-2 transition-colors", type: "button", disabled: is_saving, children: [_jsx(RefreshCw, { className: "h-4 w-4" }), t('app_config.refresh')] }), _jsxs("button", { onClick: handle_save, className: cn('px-4 py-2 rounded-md flex items-center gap-2 transition-colors', has_unsaved_changes
276
277
  ? 'bg-primary text-primary-foreground hover:bg-primary/90'
277
- : 'bg-muted text-muted-foreground cursor-not-allowed'), type: "button", disabled: !has_unsaved_changes || is_saving, children: [_jsx(Save, { className: "h-4 w-4" }), is_saving ? 'Saving...' : 'Save'] })] })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-4 gap-6", children: [_jsx("div", { className: "lg:col-span-1", children: _jsxs("div", { className: "bg-card border border-border rounded-lg overflow-hidden", children: [_jsx("div", { className: "p-4 border-b bg-muted/30", children: _jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h3", { className: "font-semibold", children: "Sections" }), _jsx("button", { onClick: () => set_dialog_mode('add-section'), className: "text-primary hover:text-primary/80 transition-colors", type: "button", children: _jsx(Plus, { className: "h-5 w-5" }) })] }) }), _jsx("div", { className: "p-2", children: section_list.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground text-center py-8 px-4", children: "No sections yet. Click + to add your first section." })) : (_jsx("div", { className: "space-y-1", children: section_list.map((section_name) => (_jsx("button", { onClick: () => set_selected_section(section_name), className: cn('w-full text-left px-3 py-2 rounded-md text-sm font-medium transition-colors', selected_section === section_name
278
+ : 'bg-muted text-muted-foreground cursor-not-allowed'), type: "button", disabled: !has_unsaved_changes || is_saving, children: [_jsx(Save, { className: "h-4 w-4" }), is_saving ? t('app_config.saving') : t('app_config.save')] })] })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-4 gap-6", children: [_jsx("div", { className: "lg:col-span-1", children: _jsxs("div", { className: "bg-card border border-border rounded-lg overflow-hidden", children: [_jsx("div", { className: "p-4 border-b bg-muted/30", children: _jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsx("h3", { className: "font-semibold", children: t('app_config.sections_heading') }), _jsx("button", { onClick: () => set_dialog_mode('add-section'), className: "text-primary hover:text-primary/80 transition-colors", type: "button", children: _jsx(Plus, { className: "h-5 w-5" }) })] }) }), _jsx("div", { className: "p-2", children: section_list.length === 0 ? (_jsx("div", { className: "text-sm text-muted-foreground text-center py-8 px-4", children: t('app_config.no_sections') })) : (_jsx("div", { className: "space-y-1", children: section_list.map((section_name) => (_jsx("button", { onClick: () => set_selected_section(section_name), className: cn('w-full text-left px-3 py-2 rounded-md text-sm font-medium transition-colors', selected_section === section_name
278
279
  ? 'bg-primary text-primary-foreground'
279
- : 'hover:bg-muted'), type: "button", children: section_name }, section_name))) })) })] }) }), _jsx("div", { className: "lg:col-span-3", children: selected_section && current_section_data ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-xl font-semibold", children: selected_section }), _jsxs("button", { onClick: () => set_dialog_mode('add'), className: "px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 flex items-center gap-2 transition-colors", type: "button", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Key"] })] }), _jsxs("div", { className: "space-y-3", children: [Object.entries(current_section_data).map(([key, item]) => {
280
+ : 'hover:bg-muted'), type: "button", children: section_name }, section_name))) })) })] }) }), _jsx("div", { className: "lg:col-span-3", children: selected_section && current_section_data ? (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("h3", { className: "text-xl font-semibold", children: selected_section }), _jsxs("button", { onClick: () => set_dialog_mode('add'), className: "px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 flex items-center gap-2 transition-colors", type: "button", children: [_jsx(Plus, { className: "h-4 w-4" }), t('app_config.add_key')] })] }), _jsxs("div", { className: "space-y-3", children: [Object.entries(current_section_data).map(([key, item]) => {
280
281
  const is_sensitive = should_mask_field(key);
281
282
  const show_value = !is_sensitive || is_revealed(selected_section, key);
282
283
  const display_value = get_field_value(selected_section, key, item);
@@ -285,7 +286,7 @@ export const AppConfig = ({ context, fetch_config, save_config, delete_config, c
285
286
  : 'bg-gray-100 text-gray-700'), children: item.config_type })] }) }), _jsxs("div", { className: "flex items-center gap-2", children: [is_sensitive && (_jsx("button", { onClick: () => toggle_reveal(selected_section, key), className: "text-muted-foreground hover:text-foreground transition-colors", type: "button", children: show_value ? _jsx(EyeOff, { className: "h-4 w-4" }) : _jsx(Eye, { className: "h-4 w-4" }) })), _jsx("button", { onClick: () => handle_delete_key(selected_section, key), className: "text-red-500 hover:text-red-700 transition-colors", type: "button", children: _jsx(Trash2, { className: "h-4 w-4" }) })] })] }), item.config_type === 'json' ? (_jsx(JsonEditor, { value: typeof display_value === 'object' ? display_value : {}, onChange: (val) => handle_field_change(selected_section, key, val), className: "bg-muted/30" })) : (_jsx("input", { type: is_sensitive && !show_value ? 'password' : 'text', value: is_sensitive && !show_value
286
287
  ? mask_value(String(display_value))
287
288
  : String(display_value), onChange: (e) => handle_field_change(selected_section, key, e.target.value), className: "w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-primary", readOnly: is_sensitive && !show_value }))] }, key));
288
- }), Object.keys(current_section_data).length === 0 && (_jsx("div", { className: "text-center py-12 text-muted-foreground", children: "No keys in this section yet. Click \"Add Key\" to get started." }))] })] })) : (_jsx("div", { className: "flex items-center justify-center h-64 bg-muted/30 border border-dashed border-border rounded-lg", children: _jsx("div", { className: "text-center text-muted-foreground", children: section_list.length > 0
289
- ? 'Select a section to view and edit keys'
290
- : 'Click + to create your first configuration section' }) })) })] })] }), _jsx(KeyDialog, { mode: dialog_mode, section_name: selected_section || undefined, on_close: () => set_dialog_mode(null), on_save: handle_dialog_save })] }));
289
+ }), Object.keys(current_section_data).length === 0 && (_jsx("div", { className: "text-center py-12 text-muted-foreground", children: t('app_config.no_keys') }))] })] })) : (_jsx("div", { className: "flex items-center justify-center h-64 bg-muted/30 border border-dashed border-border rounded-lg", children: _jsx("div", { className: "text-center text-muted-foreground", children: section_list.length > 0
290
+ ? t('app_config.select_section_prompt')
291
+ : t('app_config.create_first_section_prompt') }) })) })] })] }), _jsx(KeyDialog, { mode: dialog_mode, section_name: selected_section || undefined, on_close: () => set_dialog_mode(null), on_save: handle_dialog_save })] }));
291
292
  };
@@ -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,EAEpB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,cAAc,EAEd,cAAc,EACd,cAAc,EACd,cAAc,EACd,SAAS,GACV,EAAE,wBAAwB,CAAC,CAAC,CAAC,+BAuc7B"}
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":"AASA,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,+BA0c7B"}
@@ -5,6 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  import { useState, useCallback, useMemo } from 'react';
6
6
  import { Plus, Trash2 } from 'lucide-react';
7
7
  import { cn, HazoUiConfirmDialog } from 'hazo_ui';
8
+ import { t } from '../../lib/strings.js';
8
9
  import { ListItemRow } from './components/list_item_row.js';
9
10
  import { EditModal } from './components/edit_modal.js';
10
11
  import { SearchBar } from './components/search_bar.js';
@@ -248,9 +249,12 @@ on_item_create, on_item_update, on_item_delete, on_reload, }) {
248
249
  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
250
  if (el)
250
251
  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
252
+ }, onChange: toggle_select_all, className: "w-4 h-4 rounded border-gray-300 text-violet-600 focus:ring-violet-500", "aria-label": t('list_editor.select_all_aria') })), _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
252
253
  ? 'bg-gray-100 text-gray-400 cursor-not-allowed'
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) }))] }));
254
+ : 'bg-violet-600 hover:bg-violet-700 text-white'), title: is_max_reached ? t('list_editor.max_items_title', { max: max_items ?? 0 }) : undefined, children: [_jsx(Plus, { className: "w-4 h-4" }), t('list_editor.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: [_jsx("span", { className: "text-sm font-medium text-violet-800", children: t('list_editor.selected_count', { count: selection_count }) }), _jsx("button", { type: "button", onClick: clear_selection, className: "text-xs text-violet-600 hover:text-violet-800 underline", children: t('list_editor.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: t('list_editor.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" }), t('list_editor.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 ? (_jsx("div", { className: "py-8 text-center text-sm text-gray-500", children: t('list_editor.no_items_matching', { item_label, term: search_term }) })) : (_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 && (_jsx("p", { className: "text-xs text-gray-400 mt-2", children: t('list_editor.max_reached_note', { max: max_items ?? 0, item_label }) })), 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)
255
+ handle_cancel_delete(); }, title: t('list_editor.delete_confirm_title', { name: delete_item_name }), description: delete_message, variant: "destructive", confirmLabel: t('common.delete'), onConfirm: handle_delete_confirm, onCancel: handle_cancel_delete })), on_bulk_delete && (_jsx(HazoUiConfirmDialog, { open: bulk_delete_open, onOpenChange: (is_open) => { if (!is_open)
256
+ set_bulk_delete_open(false); }, title: t('list_editor.bulk_delete_title', {
257
+ count: selection_count,
258
+ label: selection_count === 1 ? item_type_label.toLowerCase() : item_label,
259
+ }), description: t('list_editor.bulk_delete_description'), variant: "destructive", confirmLabel: t('list_editor.bulk_delete_confirm_label', { count: 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) }))] }));
256
260
  }
@@ -1 +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"}
1
+ {"version":3,"file":"bulk_edit_modal.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/bulk_edit_modal.tsx"],"names":[],"mappings":"AAiBA,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"}
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  // Allows editing shared fields across multiple selected items
5
5
  import { useState, useCallback, useEffect } from 'react';
6
6
  import { cn } from 'hazo_ui';
7
+ import { t } from '../../../lib/strings.js';
7
8
  import { Input } from '../../ui/input.js';
8
9
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '../../ui/dialog.js';
9
10
  import { Button } from '../../ui/button.js';
@@ -64,7 +65,7 @@ export function BulkEditModal({ open, selected_count, columns, bulk_edit_fields,
64
65
  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
  }
66
67
  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
+ 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 || t('common.select_placeholder') }), col.options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] }));
68
69
  }
69
70
  if (col.type === 'textarea') {
70
71
  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') }));
@@ -76,8 +77,8 @@ export function BulkEditModal({ open, selected_count, columns, bulk_edit_fields,
76
77
  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
  };
78
79
  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
+ 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: [_jsx(DialogTitle, { children: t('bulk_edit.title', { count: selected_count }) }), _jsx("p", { className: "text-sm text-gray-500 mt-1", children: t('bulk_edit.description') })] }), _jsxs("div", { className: "px-6 pb-4 space-y-3 overflow-y-auto flex-1 min-h-0", children: [editable_cols.map((col) => {
80
81
  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"] })] })] }) }));
82
+ 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: t('bulk_edit.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));
83
+ }), editable_cols.length === 0 && (_jsx("p", { className: "text-sm text-gray-400 py-4 text-center", children: t('bulk_edit.no_fields') }))] }), _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: t('common.cancel') }), _jsx(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: t('bulk_edit.apply', { count: selected_count }) })] })] }) }));
83
84
  }
@@ -1 +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,+BA+BxB"}
1
+ {"version":3,"file":"color_swatch_picker.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/color_swatch_picker.tsx"],"names":[],"mappings":"AAOA,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,+BA+BxB"}
@@ -3,6 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  // Displays a row of circular color swatches for selection
4
4
  import { Check } from 'lucide-react';
5
5
  import { cn } from 'hazo_ui';
6
+ import { t } from '../../../lib/strings.js';
6
7
  export function ColorSwatchPicker({ value, options, on_change, className, }) {
7
8
  return (_jsx("div", { className: cn('cls_color_swatch_picker flex flex-wrap gap-2', className), children: options.map((color) => {
8
9
  const is_selected = value === color;
@@ -10,6 +11,6 @@ export function ColorSwatchPicker({ value, options, on_change, className, }) {
10
11
  const bg_class = color.split(' ').find(c => c.startsWith('bg-')) || 'bg-gray-200';
11
12
  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
13
  ? '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
+ : 'ring-1 ring-gray-200'), "aria-label": t('color_swatch.select_aria', { color }), "aria-pressed": is_selected, children: is_selected && (_jsx(Check, { className: "w-3.5 h-3.5 text-current opacity-80" })) }, color));
14
15
  }) }));
15
16
  }
@@ -1 +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,+BAgCnB"}
1
+ {"version":3,"file":"delete_dialog.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/delete_dialog.tsx"],"names":[],"mappings":"AAgBA,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,+BAgCnB"}
@@ -2,8 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // Delete confirmation dialog component
3
3
  // Uses shadcn AlertDialog with red warning styling
4
4
  import { AlertTriangle } from 'lucide-react';
5
+ import { t } from '../../../lib/strings.js';
5
6
  import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, } from '../../ui/alert-dialog.js';
6
7
  export function DeleteDialog({ open, item_name, message, on_confirm, on_cancel, }) {
7
8
  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
+ 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: t('common.cancel') }), _jsx(AlertDialogAction, { onClick: on_confirm, className: "rounded-full bg-red-600 text-white hover:bg-red-700", children: t('common.delete') })] })] }) }));
9
10
  }
@@ -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;AAc/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAQ5C,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,qBAqUnB"}
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;AAe/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAQ5C,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,qBA2UnB"}
@@ -4,6 +4,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  // Centered dialog for creating/editing items with dynamic form fields
5
5
  import React, { useState, useCallback, useEffect } from 'react';
6
6
  import { cn } from 'hazo_ui';
7
+ import { t } from '../../../lib/strings.js';
7
8
  import { Input } from '../../ui/input.js';
8
9
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '../../ui/dialog.js';
9
10
  import { Button } from '../../ui/button.js';
@@ -56,7 +57,7 @@ export function EditModal({ open, mode, item: initial_item, columns, id_field, a
56
57
  const value = form_data[col.field];
57
58
  // Required check
58
59
  if (col.required && (value === undefined || value === null || value === '')) {
59
- new_errors.set(col.field, `${col.label} is required`);
60
+ new_errors.set(col.field, t('edit_modal.field_required', { label: col.label }));
60
61
  continue;
61
62
  }
62
63
  // Custom validation
@@ -70,7 +71,7 @@ export function EditModal({ open, mode, item: initial_item, columns, id_field, a
70
71
  // Check ID field is filled
71
72
  const id_value = form_data[id_field];
72
73
  if (!id_value || String(id_value).trim() === '') {
73
- new_errors.set(id_field, 'ID is required');
74
+ new_errors.set(id_field, t('edit_modal.id_required'));
74
75
  }
75
76
  // Check for duplicate ID
76
77
  if (id_value) {
@@ -79,7 +80,7 @@ export function EditModal({ open, mode, item: initial_item, columns, id_field, a
79
80
  ? existing_ids.includes(id_str)
80
81
  : existing_ids.filter(id => id !== String(initial_item[id_field])).includes(id_str);
81
82
  if (is_duplicate) {
82
- new_errors.set(id_field, 'This ID already exists');
83
+ new_errors.set(id_field, t('edit_modal.id_duplicate'));
83
84
  }
84
85
  }
85
86
  set_errors(new_errors);
@@ -119,11 +120,17 @@ export function EditModal({ open, mode, item: initial_item, columns, id_field, a
119
120
  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
120
121
  ? handle_id_change(e.target.value)
121
122
  : 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 && ((col.searchable || col.options.length > 10)
122
- ? (_jsx(Combobox, { value: String(value ?? ''), onChange: (v) => update_field(col.field, v), options: [{ value: '', label: col.placeholder || 'Select...' }, ...col.options], placeholder: col.placeholder || 'Select...', error: !!error }))
123
- : (_jsxs(Select, { value: String(value ?? ''), onValueChange: (v) => update_field(col.field, v === SELECT_CLEAR_VALUE ? '' : v), children: [_jsx(SelectTrigger, { className: cn(error && 'border-red-400'), children: _jsx(SelectValue, { placeholder: col.placeholder || 'Select...' }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: SELECT_CLEAR_VALUE, children: col.placeholder || 'Select...' }), col.options.map((opt) => (_jsx(SelectItem, { 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));
123
+ ? (_jsx(Combobox, { value: String(value ?? ''), onChange: (v) => update_field(col.field, v), options: [{ value: '', label: col.placeholder || t('common.select_placeholder') }, ...col.options], placeholder: col.placeholder || t('common.select_placeholder'), error: !!error }))
124
+ : (_jsxs(Select, { value: String(value ?? ''), onValueChange: (v) => update_field(col.field, v === SELECT_CLEAR_VALUE ? '' : v), children: [_jsx(SelectTrigger, { className: cn(error && 'border-red-400'), children: _jsx(SelectValue, { placeholder: col.placeholder || t('common.select_placeholder') }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: SELECT_CLEAR_VALUE, children: col.placeholder || t('common.select_placeholder') }), col.options.map((opt) => (_jsx(SelectItem, { 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 && (_jsx("p", { className: "text-xs text-gray-400", children: t('edit_modal.auto_generated_hint', { source: 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));
124
125
  };
125
126
  return (_jsx(Dialog, { open: open, onOpenChange: (is_open) => { if (!is_open)
126
- 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' })] })] }) }));
127
+ 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'
128
+ ? t('edit_modal.title_new', { item_type: item_type_label })
129
+ : t('edit_modal.title_edit', { item_type: 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: t('common.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
130
+ ? t('edit_modal.saving')
131
+ : mode === 'create'
132
+ ? t('edit_modal.add_button', { item_type: item_type_label })
133
+ : t('edit_modal.save_changes') })] })] }) }));
127
134
  }
128
135
  /* ── Tag Picker Field ── */
129
136
  function TagPickerField({ value, options, on_change }) {
@@ -151,5 +158,5 @@ function TagPickerField({ value, options, on_change }) {
151
158
  on_change(value.filter(v => v !== tag_value));
152
159
  };
153
160
  const label_for = (v) => options.find(o => o.value === v)?.label ?? v;
154
- return (_jsxs("div", { ref: ref, className: "relative", children: [_jsxs("div", { className: cn('flex flex-wrap gap-1.5 min-h-[38px] p-1.5 border rounded-lg bg-white cursor-text', open && 'ring-2 ring-violet-500/20 border-violet-400'), onClick: () => set_open(true), children: [value.map(v => (_jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-violet-100 text-violet-800 text-xs font-medium", children: [label_for(v), _jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); remove_tag(v); }, className: "text-violet-500 hover:text-violet-800 ml-0.5", children: "\u00D7" })] }, v))), _jsx("input", { type: "text", value: search, onChange: e => { set_search(e.target.value); set_open(true); }, onFocus: () => set_open(true), placeholder: value.length === 0 ? 'Type to search...' : '', className: "flex-1 min-w-[80px] text-sm outline-none bg-transparent px-1 py-0.5" })] }), open && filtered.length > 0 && (_jsx("div", { className: "absolute z-50 mt-1 w-full max-h-48 overflow-y-auto rounded-lg border bg-white shadow-lg", children: filtered.map(opt => (_jsxs("button", { type: "button", onClick: () => add_tag(opt.value), className: "w-full text-left px-3 py-2 text-sm hover:bg-violet-50 transition-colors flex items-center justify-between", children: [_jsx("span", { children: opt.label }), _jsx("span", { className: "text-xs text-gray-400 font-mono", children: opt.value })] }, opt.value))) })), open && filtered.length === 0 && search && (_jsx("div", { className: "absolute z-50 mt-1 w-full rounded-lg border bg-white shadow-lg px-3 py-2 text-sm text-gray-400", children: "No matching options" }))] }));
161
+ return (_jsxs("div", { ref: ref, className: "relative", children: [_jsxs("div", { className: cn('flex flex-wrap gap-1.5 min-h-[38px] p-1.5 border rounded-lg bg-white cursor-text', open && 'ring-2 ring-violet-500/20 border-violet-400'), onClick: () => set_open(true), children: [value.map(v => (_jsxs("span", { className: "inline-flex items-center gap-1 px-2 py-0.5 rounded-md bg-violet-100 text-violet-800 text-xs font-medium", children: [label_for(v), _jsx("button", { type: "button", onClick: (e) => { e.stopPropagation(); remove_tag(v); }, className: "text-violet-500 hover:text-violet-800 ml-0.5", children: "\u00D7" })] }, v))), _jsx("input", { type: "text", value: search, onChange: e => { set_search(e.target.value); set_open(true); }, onFocus: () => set_open(true), placeholder: value.length === 0 ? t('edit_modal.tag_search_placeholder') : '', className: "flex-1 min-w-[80px] text-sm outline-none bg-transparent px-1 py-0.5" })] }), open && filtered.length > 0 && (_jsx("div", { className: "absolute z-50 mt-1 w-full max-h-48 overflow-y-auto rounded-lg border bg-white shadow-lg", children: filtered.map(opt => (_jsxs("button", { type: "button", onClick: () => add_tag(opt.value), className: "w-full text-left px-3 py-2 text-sm hover:bg-violet-50 transition-colors flex items-center justify-between", children: [_jsx("span", { children: opt.label }), _jsx("span", { className: "text-xs text-gray-400 font-mono", children: opt.value })] }, opt.value))) })), open && filtered.length === 0 && search && (_jsx("div", { className: "absolute z-50 mt-1 w-full rounded-lg border bg-white shadow-lg px-3 py-2 text-sm text-gray-400", children: t('edit_modal.no_matching_options') }))] }));
155
162
  }
@@ -1 +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,+BAsBjB"}
1
+ {"version":3,"file":"empty_state.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/empty_state.tsx"],"names":[],"mappings":"AAOA,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,+BAsBjB"}
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // Displayed when the list has no items
4
4
  import { Inbox, Plus } from 'lucide-react';
5
5
  import { cn } from 'hazo_ui';
6
+ import { t } from '../../../lib/strings.js';
6
7
  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
+ 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" }) }), _jsx("p", { className: "text-sm font-medium text-gray-900 mb-1", children: t('empty_state.title', { item_label }) }), _jsx("p", { className: "text-sm text-gray-500 mb-4", children: t('empty_state.description', { item: 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" }), t('empty_state.cta', { item: item_label.replace(/s$/, '') })] })] }));
8
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"import_export_buttons.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/import_export_buttons.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAQ5C,UAAU,wBAAwB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAClE,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,eAAe,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,CAAA;IACrC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACrE,KAAK,EACL,eAAe,EACf,OAAO,EACP,QAAQ,EACR,KAAK,EACL,SAAS,GACV,EAAE,wBAAwB,CAAC,CAAC,CAAC,+BA0H7B"}
1
+ {"version":3,"file":"import_export_buttons.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/import_export_buttons.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAQ5C,UAAU,wBAAwB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAClE,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,eAAe,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,IAAI,CAAA;IACrC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACrE,KAAK,EACL,eAAe,EACf,OAAO,EACP,QAAQ,EACR,KAAK,EACL,SAAS,GACV,EAAE,wBAAwB,CAAC,CAAC,CAAC,+BA0H7B"}
@@ -5,6 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  import { useRef, useState, useCallback, useEffect } from 'react';
6
6
  import { Download, Upload } from 'lucide-react';
7
7
  import { cn } from 'hazo_ui';
8
+ import { t } from '../../../lib/strings.js';
8
9
  import { validate_import_data, merge_items, export_items_to_json, generate_export_filename, } from '../utils/json_import_export.js';
9
10
  export function ImportExportButtons({ items, on_items_change, columns, id_field, title, max_items, }) {
10
11
  const file_input_ref = useRef(null);
@@ -35,7 +36,7 @@ export function ImportExportButtons({ items, on_items_change, columns, id_field,
35
36
  reader.onload = (e) => {
36
37
  const text = e.target?.result;
37
38
  if (typeof text !== 'string') {
38
- set_status_message({ text: 'Failed to read file', is_error: true });
39
+ set_status_message({ text: t('import_export.error_read_file'), is_error: true });
39
40
  return;
40
41
  }
41
42
  let data;
@@ -43,12 +44,12 @@ export function ImportExportButtons({ items, on_items_change, columns, id_field,
43
44
  data = JSON.parse(text);
44
45
  }
45
46
  catch {
46
- set_status_message({ text: 'Invalid JSON file', is_error: true });
47
+ set_status_message({ text: t('import_export.error_invalid_json'), is_error: true });
47
48
  return;
48
49
  }
49
50
  const { valid_items, errors } = validate_import_data(data, columns, id_field);
50
51
  if (valid_items.length === 0) {
51
- const msg = errors.length > 0 ? errors[0] : 'No valid items found';
52
+ const msg = errors.length > 0 ? errors[0] : t('import_export.error_no_valid_items');
52
53
  set_status_message({ text: msg, is_error: true });
53
54
  return;
54
55
  }
@@ -56,13 +57,13 @@ export function ImportExportButtons({ items, on_items_change, columns, id_field,
56
57
  on_items_change(result.merged_items);
57
58
  const parts = [];
58
59
  if (result.added_count > 0) {
59
- parts.push(`Added ${result.added_count}`);
60
+ parts.push(t('import_export.added', { count: result.added_count }));
60
61
  }
61
62
  if (result.skipped_count > 0) {
62
- parts.push(`${result.skipped_count} skipped`);
63
+ parts.push(t('import_export.skipped', { count: result.skipped_count }));
63
64
  }
64
65
  if (errors.length > 0) {
65
- parts.push(`${errors.length} invalid`);
66
+ parts.push(t('import_export.invalid', { count: errors.length }));
66
67
  }
67
68
  set_status_message({
68
69
  text: parts.join(', '),
@@ -71,5 +72,5 @@ export function ImportExportButtons({ items, on_items_change, columns, id_field,
71
72
  };
72
73
  reader.readAsText(file);
73
74
  }, [items, columns, id_field, max_items, on_items_change]);
74
- return (_jsxs("div", { className: "cls_import_export flex items-center gap-1.5", children: [status_message && (_jsx("span", { className: cn('cls_import_status text-xs transition-opacity duration-300', status_message.is_error ? 'text-red-600' : 'text-green-600'), children: status_message.text })), _jsxs("button", { type: "button", onClick: handle_export, className: "inline-flex items-center gap-1 px-2 py-1.5 text-xs font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors", title: "Export as JSON", children: [_jsx(Download, { className: "w-3.5 h-3.5" }), "Export"] }), _jsxs("button", { type: "button", onClick: handle_import_click, className: "inline-flex items-center gap-1 px-2 py-1.5 text-xs font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors", title: "Import from JSON", children: [_jsx(Upload, { className: "w-3.5 h-3.5" }), "Import"] }), _jsx("input", { ref: file_input_ref, type: "file", accept: ".json", onChange: handle_file_selected, className: "hidden" })] }));
75
+ return (_jsxs("div", { className: "cls_import_export flex items-center gap-1.5", children: [status_message && (_jsx("span", { className: cn('cls_import_status text-xs transition-opacity duration-300', status_message.is_error ? 'text-red-600' : 'text-green-600'), children: status_message.text })), _jsxs("button", { type: "button", onClick: handle_export, className: "inline-flex items-center gap-1 px-2 py-1.5 text-xs font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors", title: t('import_export.export_title'), children: [_jsx(Download, { className: "w-3.5 h-3.5" }), t('import_export.export')] }), _jsxs("button", { type: "button", onClick: handle_import_click, className: "inline-flex items-center gap-1 px-2 py-1.5 text-xs font-medium text-gray-500 hover:text-gray-700 hover:bg-gray-100 rounded-full transition-colors", title: t('import_export.import_title'), children: [_jsx(Upload, { className: "w-3.5 h-3.5" }), t('import_export.import')] }), _jsx("input", { ref: file_input_ref, type: "file", accept: ".json", onChange: handle_file_selected, className: "hidden" })] }));
75
76
  }
@@ -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;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
+ {"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;AAIzB,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,19 +1,20 @@
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
+ import { t } from '../../../lib/strings.js';
4
5
  export function ListItemRow({ item, index, columns, render_item, render_item_indicator, on_edit, on_delete, show_checkbox, is_selected, on_toggle_select, }) {
5
6
  // Custom render overrides everything
6
7
  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: [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
+ 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": t('list_item.select_item_aria') })), _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": t('list_item.edit_item_aria'), 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": t('list_item.delete_item_aria'), children: _jsx(Trash2, { className: "w-4 h-4" }) })] })] }));
8
9
  }
9
10
  // Default rendering based on column definitions
10
11
  const primary_cols = columns.filter(c => c.list_display === 'primary');
11
12
  const secondary_cols = columns.filter(c => c.list_display === 'secondary');
12
13
  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: [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
+ 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": t('list_item.select_item_aria') })), 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
15
  const badge_class = typeof col.badge_class === 'function'
15
16
  ? col.badge_class(item[col.field], item)
16
17
  : col.badge_class;
17
18
  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" }) })] })] }));
19
+ }) })), _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": t('list_item.edit_item_aria'), 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": t('list_item.delete_item_aria'), children: _jsx(Trash2, { className: "w-4 h-4" }) })] })] }));
19
20
  }
@@ -1 +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":"AASA,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,sCA+C1B"}
1
+ {"version":3,"file":"save_status_indicator.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/save_status_indicator.tsx"],"names":[],"mappings":"AAUA,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,sCA+C1B"}
@@ -5,6 +5,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
5
5
  import { useEffect, useState } from 'react';
6
6
  import { Loader2, CheckCircle2, AlertCircle } from 'lucide-react';
7
7
  import { cn } from 'hazo_ui';
8
+ import { t } from '../../../lib/strings.js';
8
9
  export function SaveStatusIndicator({ status, className, }) {
9
10
  const [visible, set_visible] = useState(false);
10
11
  useEffect(() => {
@@ -22,5 +23,5 @@ export function SaveStatusIndicator({ status, className, }) {
22
23
  }, [status]);
23
24
  if (!visible && status === 'idle')
24
25
  return null;
25
- 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" })] }))] }));
26
+ 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: t('save_status.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: t('save_status.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: t('save_status.error') })] }))] }));
26
27
  }
@@ -1 +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,+BA6BhB"}
1
+ {"version":3,"file":"search_bar.d.ts","sourceRoot":"","sources":["../../../../src/components/app_config_list_editor/components/search_bar.tsx"],"names":[],"mappings":"AAOA,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,+BA6BhB"}
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  // Pill-shaped search input with icon, clear button, and item count
4
4
  import { Search, X } from 'lucide-react';
5
5
  import { cn } from 'hazo_ui';
6
+ import { t } from '../../../lib/strings.js';
6
7
  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
+ 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: t('search.placeholder', { 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": t('search.aria_label', { 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": t('search.clear_aria_label'), 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
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config_editor.d.ts","sourceRoot":"","sources":["../../src/components/config_editor.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIrD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,eAAe,EAAE,cAAc,CAAA;IAC/B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAWD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgYpD,CAAA"}
1
+ {"version":3,"file":"config_editor.d.ts","sourceRoot":"","sources":["../../src/components/config_editor.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAKrD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,eAAe,EAAE,cAAc,CAAA;IAC/B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAWD;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAgYpD,CAAA"}
@@ -6,6 +6,7 @@ import React, { useState, useCallback, useMemo } from 'react';
6
6
  import { cn } from 'hazo_ui';
7
7
  import { RefreshCw, Save, Trash2, Eye, EyeOff } from 'lucide-react';
8
8
  import { useConfigSections, is_sensitive_field, mask_value, DEFAULT_SENSITIVE_PATTERNS } from './use_config_sections.js';
9
+ import { t } from '../lib/strings.js';
9
10
  /**
10
11
  * Config editor component
11
12
  * Provides interface for viewing and editing configuration with refresh and save capabilities
@@ -208,9 +209,9 @@ export const ConfigEditor = ({ config_provider, className, on_update, on_error,
208
209
  const current_section_data = useMemo(() => {
209
210
  return selected_section ? sections[selected_section] : null;
210
211
  }, [selected_section, sections]);
211
- return (_jsxs("div", { className: cn('cls_config_editor space-y-4', className), children: [_jsxs("div", { className: "cls_config_editor_header flex items-center justify-between border-b pb-2", children: [_jsxs("h2", { className: "text-xl font-semibold", children: ["Configuration Editor", has_unsaved_changes && (_jsx("span", { className: "ml-2 text-sm text-amber-600", children: "(unsaved changes)" }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs("button", { onClick: handle_refresh, className: "px-3 py-1 border rounded hover:bg-muted flex items-center gap-2", "aria-label": "Refresh configuration", type: "button", children: [_jsx(RefreshCw, { size: 16 }), "Refresh"] }), _jsxs("button", { onClick: handle_save, className: cn('px-3 py-1 rounded flex items-center gap-2', has_unsaved_changes
212
+ return (_jsxs("div", { className: cn('cls_config_editor space-y-4', className), children: [_jsxs("div", { className: "cls_config_editor_header flex items-center justify-between border-b pb-2", children: [_jsxs("h2", { className: "text-xl font-semibold", children: [t('config_editor.title'), has_unsaved_changes && (_jsx("span", { className: "ml-2 text-sm text-amber-600", children: t('config_editor.unsaved_suffix') }))] }), _jsxs("div", { className: "flex gap-2", children: [_jsxs("button", { onClick: handle_refresh, className: "px-3 py-1 border rounded hover:bg-muted flex items-center gap-2", "aria-label": t('config_editor.refresh_aria'), type: "button", children: [_jsx(RefreshCw, { size: 16 }), t('config_editor.refresh')] }), _jsxs("button", { onClick: handle_save, className: cn('px-3 py-1 rounded flex items-center gap-2', has_unsaved_changes
212
213
  ? 'bg-primary text-primary-foreground hover:bg-primary/90'
213
- : 'bg-muted text-muted-foreground'), "aria-label": "Save configuration", type: "button", disabled: !has_unsaved_changes, children: [_jsx(Save, { size: 16 }), "Save"] })] })] }), _jsxs("div", { className: "cls_config_editor_content grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { className: "cls_config_sections_list border rounded p-4", children: [_jsx("h3", { className: "font-semibold mb-2", children: "Sections" }), _jsx("div", { className: "space-y-1", role: "listbox", "aria-label": "Configuration sections", children: section_list.map((section_name) => (_jsx("button", { onClick: () => handle_section_select(section_name), className: cn('w-full text-left px-2 py-1 rounded text-sm', selected_section === section_name
214
+ : 'bg-muted text-muted-foreground'), "aria-label": t('config_editor.save_aria'), type: "button", disabled: !has_unsaved_changes, children: [_jsx(Save, { size: 16 }), t('config_editor.save')] })] })] }), _jsxs("div", { className: "cls_config_editor_content grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { className: "cls_config_sections_list border rounded p-4", children: [_jsx("h3", { className: "font-semibold mb-2", children: t('config_editor.sections_heading') }), _jsx("div", { className: "space-y-1", role: "listbox", "aria-label": t('config_editor.sections_aria'), children: section_list.map((section_name) => (_jsx("button", { onClick: () => handle_section_select(section_name), className: cn('w-full text-left px-2 py-1 rounded text-sm', selected_section === section_name
214
215
  ? 'bg-primary text-primary-foreground'
215
216
  : 'hover:bg-muted'), role: "option", "aria-selected": selected_section === section_name, type: "button", children: section_name }, section_name))) })] }), _jsx("div", { className: "cls_config_section_content md:col-span-2 border rounded p-4", children: selected_section ? (_jsxs(_Fragment, { children: [_jsx("h3", { className: "font-semibold mb-4", children: selected_section }), _jsxs("div", { className: "space-y-3", children: [current_section_data &&
216
217
  Object.entries(current_section_data).map(([key, value]) => {
@@ -218,6 +219,6 @@ export const ConfigEditor = ({ config_provider, className, on_update, on_error,
218
219
  const field_id = `config-${selected_section}-${key}`;
219
220
  const show_value = !is_sensitive || is_revealed(selected_section, key);
220
221
  const display_value = get_field_value(selected_section, key, value);
221
- return (_jsxs("div", { className: "cls_config_item", children: [_jsx("label", { htmlFor: field_id, className: "block text-sm font-medium mb-1", children: key }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { id: field_id, type: is_sensitive && !show_value ? 'password' : 'text', value: is_sensitive && !show_value ? mask_value(display_value) : display_value, onChange: (e) => handle_field_change(selected_section, key, e.target.value), className: "flex-1 px-2 py-1 border rounded", readOnly: is_sensitive && !show_value }), is_sensitive && (_jsx("button", { onClick: () => toggle_reveal(selected_section, key), className: "text-gray-500 hover:text-gray-700", "aria-label": show_value ? 'Hide value' : 'Show value', type: "button", children: show_value ? _jsx(EyeOff, { size: 18 }) : _jsx(Eye, { size: 18 }) })), _jsx("button", { onClick: () => handle_delete_key(selected_section, key), className: "text-red-500 hover:text-red-700", "aria-label": `Delete ${key}`, type: "button", children: _jsx(Trash2, { size: 18 }) })] })] }, key));
222
- }), _jsxs("div", { className: "cls_add_new_key border-t pt-3 mt-3", children: [_jsx("h4", { className: "text-sm font-medium mb-2", children: "Add New Key" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "new-key-name", className: "sr-only", children: "Key name" }), _jsx("input", { id: "new-key-name", type: "text", placeholder: "Key name", value: new_key, onChange: handle_new_key_change, className: "w-full px-2 py-1 border rounded text-sm" })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "new-key-value", className: "sr-only", children: "Value" }), _jsx("input", { id: "new-key-value", type: "text", placeholder: "Value", value: new_value, onChange: handle_new_value_change, className: "w-full px-2 py-1 border rounded text-sm" })] }), _jsx("button", { onClick: handle_add_key, className: "px-3 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90 text-sm", type: "button", disabled: !new_key || !new_value, children: "Add Key" })] })] })] })] })) : (_jsx("div", { className: "text-center text-muted-foreground py-8", children: "Select a section to view/edit" })) })] })] }));
222
+ return (_jsxs("div", { className: "cls_config_item", children: [_jsx("label", { htmlFor: field_id, className: "block text-sm font-medium mb-1", children: key }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { id: field_id, type: is_sensitive && !show_value ? 'password' : 'text', value: is_sensitive && !show_value ? mask_value(display_value) : display_value, onChange: (e) => handle_field_change(selected_section, key, e.target.value), className: "flex-1 px-2 py-1 border rounded", readOnly: is_sensitive && !show_value }), is_sensitive && (_jsx("button", { onClick: () => toggle_reveal(selected_section, key), className: "text-gray-500 hover:text-gray-700", "aria-label": show_value ? t('common.hide_value') : t('common.show_value'), type: "button", children: show_value ? _jsx(EyeOff, { size: 18 }) : _jsx(Eye, { size: 18 }) })), _jsx("button", { onClick: () => handle_delete_key(selected_section, key), className: "text-red-500 hover:text-red-700", "aria-label": t('config_editor.delete_key_aria', { key }), type: "button", children: _jsx(Trash2, { size: 18 }) })] })] }, key));
223
+ }), _jsxs("div", { className: "cls_add_new_key border-t pt-3 mt-3", children: [_jsx("h4", { className: "text-sm font-medium mb-2", children: t('config_editor.add_new_key_heading') }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { children: [_jsx("label", { htmlFor: "new-key-name", className: "sr-only", children: t('config_editor.key_name_label') }), _jsx("input", { id: "new-key-name", type: "text", placeholder: t('config_editor.key_name_label'), value: new_key, onChange: handle_new_key_change, className: "w-full px-2 py-1 border rounded text-sm" })] }), _jsxs("div", { children: [_jsx("label", { htmlFor: "new-key-value", className: "sr-only", children: t('config_editor.value_label') }), _jsx("input", { id: "new-key-value", type: "text", placeholder: t('config_editor.value_label'), value: new_value, onChange: handle_new_value_change, className: "w-full px-2 py-1 border rounded text-sm" })] }), _jsx("button", { onClick: handle_add_key, className: "px-3 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90 text-sm", type: "button", disabled: !new_key || !new_value, children: t('config_editor.add_key') })] })] })] })] })) : (_jsx("div", { className: "text-center text-muted-foreground py-8", children: t('config_editor.select_section_prompt') })) })] })] }));
223
224
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config_viewer.d.ts","sourceRoot":"","sources":["../../src/components/config_viewer.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIrD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,eAAe,EAAE,cAAc,CAAA;IAC/B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA2MpD,CAAA"}
1
+ {"version":3,"file":"config_viewer.d.ts","sourceRoot":"","sources":["../../src/components/config_viewer.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAyC,MAAM,OAAO,CAAA;AAE7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAKrD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,eAAe,EAAE,cAAc,CAAA;IAC/B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAA;IACjC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA2MpD,CAAA"}
@@ -6,6 +6,7 @@ import React, { useState, useCallback, useMemo } from 'react';
6
6
  import { cn } from 'hazo_ui';
7
7
  import { Pencil, CheckCircle2, XCircle, Eye, EyeOff } from 'lucide-react';
8
8
  import { useConfigSections, is_sensitive_field, mask_value, DEFAULT_SENSITIVE_PATTERNS } from './use_config_sections.js';
9
+ import { t } from '../lib/strings.js';
9
10
  /**
10
11
  * Config viewer component
11
12
  * Displays configuration sections and allows viewing/editing values
@@ -108,11 +109,11 @@ export const ConfigViewer = ({ config_provider, className, on_update, on_error,
108
109
  * Memoized section entries for rendering
109
110
  */
110
111
  const section_entries = useMemo(() => Object.entries(sections), [sections]);
111
- return (_jsx("div", { className: cn('cls_config_viewer space-y-4', className), children: section_entries.length === 0 ? (_jsx("div", { className: "p-4 text-center text-muted-foreground", children: "No configuration sections found" })) : (section_entries.map(([section_name, section_data]) => (_jsxs("div", { className: "cls_config_section border rounded-lg p-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-3", children: section_name }), _jsx("div", { className: "space-y-2", children: Object.entries(section_data).map(([key, value]) => {
112
+ return (_jsx("div", { className: cn('cls_config_viewer space-y-4', className), children: section_entries.length === 0 ? (_jsx("div", { className: "p-4 text-center text-muted-foreground", children: t('config_viewer.empty') })) : (section_entries.map(([section_name, section_data]) => (_jsxs("div", { className: "cls_config_section border rounded-lg p-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-3", children: section_name }), _jsx("div", { className: "space-y-2", children: Object.entries(section_data).map(([key, value]) => {
112
113
  const is_editing = editing?.section === section_name && editing?.key === key;
113
114
  const is_sensitive = should_mask_field(key);
114
115
  const field_id = `config-${section_name}-${key}`;
115
116
  const show_value = !is_sensitive || is_revealed(section_name, key);
116
- return (_jsx("div", { className: "cls_config_item flex items-center gap-2", children: _jsxs("div", { className: "flex-1", children: [_jsx("label", { htmlFor: field_id, className: "text-sm font-medium text-muted-foreground", children: key }), is_editing ? (_jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("input", { id: field_id, type: is_sensitive ? 'password' : 'text', value: edit_value, onChange: handle_edit_change, className: "flex-1 px-2 py-1 border rounded text-sm", autoFocus: true }), _jsx("button", { onClick: save_edit, className: "text-green-600 hover:text-green-700", "aria-label": "Save changes", type: "button", children: _jsx(CheckCircle2, { size: 20 }) }), _jsx("button", { onClick: cancel_edit, className: "text-red-600 hover:text-red-700", "aria-label": "Cancel editing", type: "button", children: _jsx(XCircle, { size: 20 }) })] })) : (_jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("div", { id: field_id, className: "flex-1 px-2 py-1 bg-muted rounded text-sm font-mono", children: show_value ? (value || '(empty)') : mask_value(value) }), is_sensitive && (_jsx("button", { onClick: () => toggle_reveal(section_name, key), className: "text-gray-500 hover:text-gray-700", "aria-label": show_value ? 'Hide value' : 'Show value', type: "button", children: show_value ? _jsx(EyeOff, { size: 18 }) : _jsx(Eye, { size: 18 }) })), _jsx("button", { onClick: () => start_edit(section_name, key), className: "text-blue-600 hover:text-blue-700", "aria-label": `Edit ${key}`, type: "button", children: _jsx(Pencil, { size: 18 }) })] }))] }) }, key));
117
+ return (_jsx("div", { className: "cls_config_item flex items-center gap-2", children: _jsxs("div", { className: "flex-1", children: [_jsx("label", { htmlFor: field_id, className: "text-sm font-medium text-muted-foreground", children: key }), is_editing ? (_jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("input", { id: field_id, type: is_sensitive ? 'password' : 'text', value: edit_value, onChange: handle_edit_change, className: "flex-1 px-2 py-1 border rounded text-sm", autoFocus: true }), _jsx("button", { onClick: save_edit, className: "text-green-600 hover:text-green-700", "aria-label": t('config_viewer.save_changes_aria'), type: "button", children: _jsx(CheckCircle2, { size: 20 }) }), _jsx("button", { onClick: cancel_edit, className: "text-red-600 hover:text-red-700", "aria-label": t('config_viewer.cancel_editing_aria'), type: "button", children: _jsx(XCircle, { size: 20 }) })] })) : (_jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("div", { id: field_id, className: "flex-1 px-2 py-1 bg-muted rounded text-sm font-mono", children: show_value ? (value || t('config_viewer.empty_value')) : mask_value(value) }), is_sensitive && (_jsx("button", { onClick: () => toggle_reveal(section_name, key), className: "text-gray-500 hover:text-gray-700", "aria-label": show_value ? t('common.hide_value') : t('common.show_value'), type: "button", children: show_value ? _jsx(EyeOff, { size: 18 }) : _jsx(Eye, { size: 18 }) })), _jsx("button", { onClick: () => start_edit(section_name, key), className: "text-blue-600 hover:text-blue-700", "aria-label": t('config_viewer.edit_key_aria', { key }), type: "button", children: _jsx(Pencil, { size: 18 }) })] }))] }) }, key));
117
118
  }) })] }, section_name)))) }));
118
119
  };
@@ -3,7 +3,7 @@ import { type VariantProps } from "class-variance-authority";
3
3
  declare const buttonVariants: (props?: ({
4
4
  variant?: "default" | "link" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
5
5
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
6
- } & import("class-variance-authority/dist/types").ClassProp) | undefined) => string;
6
+ } & import("class-variance-authority/types").ClassProp) | undefined) => string;
7
7
  export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> {
8
8
  asChild?: boolean;
9
9
  }
@@ -1 +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;;;mFA2BnB,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"}
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;;;8EA2BnB,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"}
@@ -10,6 +10,7 @@
10
10
  */
11
11
  export { MockConfigProvider } from './mock_config_provider.js';
12
12
  export { ConfigErrorCode } from './types.js';
13
+ export { t } from './strings.js';
13
14
  export type { ConfigProvider, HazoConfigOptions, Logger, HazoConfigError, LogData } from './types.js';
14
15
  export type { ConfigType, AppConfigItem, AppConfigContext, AppConfigProps, UseAppConfigResult } from './app_config_types.js';
15
16
  export { mergeTwoTier } from './two_tier.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAC5C,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,MAAM,EACN,eAAe,EACf,OAAO,EACR,MAAM,YAAY,CAAA;AAGnB,YAAY,EACV,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EACnB,MAAM,uBAAuB,CAAA;AAK9B,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAI5C,OAAO,EAAE,CAAC,EAAE,MAAM,cAAc,CAAA;AAChC,YAAY,EACV,cAAc,EACd,iBAAiB,EACjB,MAAM,EACN,eAAe,EACf,OAAO,EACR,MAAM,YAAY,CAAA;AAGnB,YAAY,EACV,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EACnB,MAAM,uBAAuB,CAAA;AAK9B,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA"}
package/dist/lib/index.js CHANGED
@@ -11,6 +11,9 @@
11
11
  // Client-safe exports (no Node.js dependencies)
12
12
  export { MockConfigProvider } from './mock_config_provider.js';
13
13
  export { ConfigErrorCode } from './types.js';
14
+ // i18n — namespaced translator for this package's own baked-in UI strings
15
+ // (config editor/viewer, AppConfig, AppConfigListEditor). See strings.ts.
16
+ export { t } from './strings.js';
14
17
  // Two-tier (global default + scope-level override) merge -- pure, no
15
18
  // database dependency. See 'hazo_config/server-data' for the hazo_connect-
16
19
  // backed clone/override-count helpers that operate on the same shape.
@@ -0,0 +1,2 @@
1
+ export declare const t: import("hazo_core/i18n").TFunc;
2
+ //# sourceMappingURL=strings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/lib/strings.ts"],"names":[],"mappings":"AAkBA,eAAO,MAAM,CAAC,gCAA8D,CAAA"}
@@ -0,0 +1,18 @@
1
+ // hazo_config/src/lib/strings.ts — i18n plumbing for hazo_config's own
2
+ // baked-in UI strings (config editor/viewer, AppConfig, and the
3
+ // AppConfigListEditor family), via hazo_core's canonical bridge
4
+ // (`hazo_core/i18n`).
5
+ //
6
+ // hazo_config's tsconfig.json uses `moduleResolution: "bundler"` (not
7
+ // `Node16`), and `resolveJsonModule: true` is set — so a plain JSON import
8
+ // (no `with { type: 'json' }` attribute) resolves cleanly. tsconfig.build.json
9
+ // previously overrode `moduleResolution` to the classic `"node"` resolver,
10
+ // which cannot follow a package.json `exports` subpath like `hazo_core/i18n`
11
+ // (TS2307) — it was switched to `"bundler"` (matching tsconfig.json) as part
12
+ // of this package's i18n adoption. All of this package's components that use
13
+ // `t()` are `'use client'` components, so a plain import (browser-safe, no
14
+ // `createRequire`) is used rather than the Node-only `createRequire`
15
+ // fallback documented for server-only consumers of this pattern.
16
+ import en from '../locales/en.json';
17
+ import { createPackageT } from 'hazo_core/i18n';
18
+ export const t = createPackageT('hazo_config', en);
@@ -0,0 +1,104 @@
1
+ {
2
+ "common.cancel": "Cancel",
3
+ "common.delete": "Delete",
4
+ "common.select_placeholder": "Select...",
5
+ "common.hide_value": "Hide value",
6
+ "common.show_value": "Show value",
7
+ "save_status.saving": "Saving...",
8
+ "save_status.saved": "Saved",
9
+ "save_status.error": "Error saving",
10
+ "search.placeholder": "Search {{item_label}}...",
11
+ "search.aria_label": "Search {{item_label}}",
12
+ "search.clear_aria_label": "Clear search",
13
+ "empty_state.title": "No {{item_label}} yet",
14
+ "empty_state.description": "Get started by adding your first {{item}}.",
15
+ "empty_state.cta": "Add your first {{item}}",
16
+ "import_export.export": "Export",
17
+ "import_export.export_title": "Export as JSON",
18
+ "import_export.import": "Import",
19
+ "import_export.import_title": "Import from JSON",
20
+ "import_export.error_read_file": "Failed to read file",
21
+ "import_export.error_invalid_json": "Invalid JSON file",
22
+ "import_export.error_no_valid_items": "No valid items found",
23
+ "import_export.added": "Added {{count}}",
24
+ "import_export.skipped": "{{count}} skipped",
25
+ "import_export.invalid": "{{count}} invalid",
26
+ "list_item.select_item_aria": "Select item",
27
+ "list_item.edit_item_aria": "Edit item",
28
+ "list_item.delete_item_aria": "Delete item",
29
+ "list_editor.select_all_aria": "Select all",
30
+ "list_editor.add": "Add",
31
+ "list_editor.max_items_title": "Maximum of {{max}} items reached",
32
+ "list_editor.selected_count": "{{count}} selected",
33
+ "list_editor.clear": "Clear",
34
+ "list_editor.edit_selected": "Edit selected",
35
+ "list_editor.delete_selected": "Delete selected",
36
+ "list_editor.no_items_matching": "No {{item_label}} matching “{{term}}”",
37
+ "list_editor.max_reached_note": "Maximum of {{max}} {{item_label}} reached.",
38
+ "list_editor.delete_confirm_title": "Delete “{{name}}”?",
39
+ "list_editor.bulk_delete_title": "Delete {{count}} selected {{label}}?",
40
+ "list_editor.bulk_delete_description": "This action cannot be undone.",
41
+ "list_editor.bulk_delete_confirm_label": "Delete {{count}}",
42
+ "bulk_edit.title": "Edit {{count}} selected items",
43
+ "bulk_edit.description": "Enable a field below to apply a new value to all selected items. Disabled fields are left unchanged.",
44
+ "bulk_edit.leave_unchanged": "Leave unchanged",
45
+ "bulk_edit.no_fields": "No editable fields configured.",
46
+ "bulk_edit.apply": "Apply to {{count}} items",
47
+ "edit_modal.field_required": "{{label}} is required",
48
+ "edit_modal.id_required": "ID is required",
49
+ "edit_modal.id_duplicate": "This ID already exists",
50
+ "edit_modal.auto_generated_hint": "Auto-generated from {{source}}",
51
+ "edit_modal.title_new": "New {{item_type}}",
52
+ "edit_modal.title_edit": "Edit {{item_type}}",
53
+ "edit_modal.saving": "Saving…",
54
+ "edit_modal.add_button": "Add {{item_type}}",
55
+ "edit_modal.save_changes": "Save Changes",
56
+ "edit_modal.tag_search_placeholder": "Type to search...",
57
+ "edit_modal.no_matching_options": "No matching options",
58
+ "color_swatch.select_aria": "Select color {{color}}",
59
+ "config_editor.title": "Configuration Editor",
60
+ "config_editor.unsaved_suffix": "(unsaved changes)",
61
+ "config_editor.refresh_aria": "Refresh configuration",
62
+ "config_editor.refresh": "Refresh",
63
+ "config_editor.save_aria": "Save configuration",
64
+ "config_editor.save": "Save",
65
+ "config_editor.sections_heading": "Sections",
66
+ "config_editor.sections_aria": "Configuration sections",
67
+ "config_editor.add_new_key_heading": "Add New Key",
68
+ "config_editor.key_name_label": "Key name",
69
+ "config_editor.value_label": "Value",
70
+ "config_editor.add_key": "Add Key",
71
+ "config_editor.select_section_prompt": "Select a section to view/edit",
72
+ "config_editor.delete_key_aria": "Delete {{key}}",
73
+ "config_viewer.empty": "No configuration sections found",
74
+ "config_viewer.save_changes_aria": "Save changes",
75
+ "config_viewer.cancel_editing_aria": "Cancel editing",
76
+ "config_viewer.edit_key_aria": "Edit {{key}}",
77
+ "config_viewer.empty_value": "(empty)",
78
+ "app_config.title_default": "Configuration",
79
+ "app_config.loading": "Loading configuration...",
80
+ "app_config.unsaved_changes": "You have unsaved changes",
81
+ "app_config.refresh": "Refresh",
82
+ "app_config.save": "Save",
83
+ "app_config.saving": "Saving...",
84
+ "app_config.sections_heading": "Sections",
85
+ "app_config.no_sections": "No sections yet. Click + to add your first section.",
86
+ "app_config.add_key": "Add Key",
87
+ "app_config.no_keys": "No keys in this section yet. Click “Add Key” to get started.",
88
+ "app_config.select_section_prompt": "Select a section to view and edit keys",
89
+ "app_config.create_first_section_prompt": "Click + to create your first configuration section",
90
+ "app_config.delete_key_confirm": "Delete key “{{key}}”?",
91
+ "app_config.invalid_json": "Invalid JSON format",
92
+ "app_config.dialog_title_add_section": "Add New Section",
93
+ "app_config.dialog_title_add_key": "Add New Key",
94
+ "app_config.section_name_label": "Section Name",
95
+ "app_config.section_name_placeholder": "e.g., integrations, notifications",
96
+ "app_config.key_name_label": "Key Name",
97
+ "app_config.key_name_placeholder": "e.g., api_key, webhook_url",
98
+ "app_config.type_label": "Type",
99
+ "app_config.type_general": "General (Text)",
100
+ "app_config.type_json": "JSON (Object)",
101
+ "app_config.value_label": "Value",
102
+ "app_config.value_placeholder": "Enter value",
103
+ "app_config.create_section": "Create Section"
104
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_config",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Config wrapper with error handling",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -38,6 +38,8 @@
38
38
  }
39
39
  },
40
40
  "scripts": {
41
+ "clean": "rm -rf dist",
42
+ "prebuild": "npm run clean",
41
43
  "build": "tsc -p tsconfig.build.json",
42
44
  "prepublishOnly": "npm run build",
43
45
  "storybook": "npx storybook dev -p 6006",
@@ -96,7 +98,7 @@
96
98
  "@types/react": "^18.3.3",
97
99
  "@types/react-dom": "^18.3.0",
98
100
  "@vitejs/plugin-react": "^4.2.0",
99
- "hazo_connect": "^4.1.0",
101
+ "hazo_connect": "^4.3.2",
100
102
  "jsdom": "^24.1.3",
101
103
  "postcss": "^8.4.49",
102
104
  "react": "^18.2.0",
@@ -108,10 +110,11 @@
108
110
  "vitest": "^4.0.15"
109
111
  },
110
112
  "peerDependencies": {
111
- "hazo_connect": "^4.1.0",
112
- "hazo_core": "^2.2.0",
113
- "hazo_theme": "^1.3.1",
114
- "hazo_ui": "^6.11.2",
113
+ "hazo_connect": "^4.3.2",
114
+ "hazo_core": "^2.4.0",
115
+ "hazo_i18n": "^0.9.0",
116
+ "hazo_theme": "^1.4.0",
117
+ "hazo_ui": "^6.14.0",
115
118
  "react": "^18.0.0 || ^19.0.0",
116
119
  "react-dom": "^18.0.0 || ^19.0.0",
117
120
  "tailwindcss": "^4.0.0"
@@ -123,6 +126,9 @@
123
126
  "hazo_core": {
124
127
  "optional": true
125
128
  },
129
+ "hazo_i18n": {
130
+ "optional": true
131
+ },
126
132
  "tailwindcss": {
127
133
  "optional": true
128
134
  },