@vertebr/react 0.6.0 → 0.8.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 (58) hide show
  1. package/dist/components/admin/AdminContent.d.ts +2 -1
  2. package/dist/components/admin/AdminContent.d.ts.map +1 -1
  3. package/dist/components/admin/AdminContent.js +10 -2
  4. package/dist/components/admin/AdminHeader.d.ts +3 -2
  5. package/dist/components/admin/AdminHeader.d.ts.map +1 -1
  6. package/dist/components/admin/AdminHeader.js +5 -3
  7. package/dist/components/admin/AdminSaveBar.d.ts +2 -0
  8. package/dist/components/admin/AdminSaveBar.d.ts.map +1 -0
  9. package/dist/components/admin/AdminSaveBar.js +10 -0
  10. package/dist/components/admin/AdminSidebar.d.ts +2 -20
  11. package/dist/components/admin/AdminSidebar.d.ts.map +1 -1
  12. package/dist/components/admin/AdminSidebar.js +8 -12
  13. package/dist/components/admin/AdminToastStack.d.ts +2 -0
  14. package/dist/components/admin/AdminToastStack.d.ts.map +1 -0
  15. package/dist/components/admin/AdminToastStack.js +8 -0
  16. package/dist/components/admin/AdminTopBar.d.ts +24 -0
  17. package/dist/components/admin/AdminTopBar.d.ts.map +1 -0
  18. package/dist/components/admin/AdminTopBar.js +7 -0
  19. package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
  20. package/dist/components/admin/VertebrAdmin.js +15 -1
  21. package/dist/components/admin/breadcrumbs.d.ts +8 -0
  22. package/dist/components/admin/breadcrumbs.d.ts.map +1 -0
  23. package/dist/components/admin/breadcrumbs.js +44 -0
  24. package/dist/components/admin/routes.d.ts.map +1 -1
  25. package/dist/components/admin/routes.js +9 -2
  26. package/dist/components/admin/types.d.ts +6 -0
  27. package/dist/components/admin/types.d.ts.map +1 -1
  28. package/dist/components/config/form/ConfigForm.d.ts.map +1 -1
  29. package/dist/components/config/form/ConfigForm.js +44 -7
  30. package/dist/components/fonts/FontPickerField.d.ts.map +1 -1
  31. package/dist/components/fonts/FontPickerField.js +12 -1
  32. package/dist/components/resources/ResourceTable.d.ts +3 -1
  33. package/dist/components/resources/ResourceTable.d.ts.map +1 -1
  34. package/dist/components/resources/ResourceTable.js +20 -5
  35. package/dist/components/ui/icons.d.ts +15 -0
  36. package/dist/components/ui/icons.d.ts.map +1 -0
  37. package/dist/components/ui/icons.js +60 -0
  38. package/dist/components/ui/primitives.d.ts +27 -0
  39. package/dist/components/ui/primitives.d.ts.map +1 -0
  40. package/dist/components/ui/primitives.js +16 -0
  41. package/dist/hooks/useSaveBar.d.ts +21 -0
  42. package/dist/hooks/useSaveBar.d.ts.map +1 -0
  43. package/dist/hooks/useSaveBar.js +30 -0
  44. package/dist/hooks/useToast.d.ts +18 -0
  45. package/dist/hooks/useToast.d.ts.map +1 -0
  46. package/dist/hooks/useToast.js +23 -0
  47. package/dist/styles/base.css +8 -14
  48. package/dist/styles/buttons.css +3 -12
  49. package/dist/styles/index.css +1 -0
  50. package/dist/styles/main.css +3 -7
  51. package/dist/styles/nav.css +3 -1
  52. package/dist/styles/shell.css +314 -0
  53. package/dist/styles/sidebar.css +26 -5
  54. package/dist/themes/dark.css +48 -46
  55. package/dist/themes/index.css +1 -0
  56. package/dist/themes/light.css +91 -85
  57. package/dist/themes/tokens.css +52 -0
  58. package/package.json +1 -1
@@ -5,6 +5,7 @@ export type AdminContentProps = {
5
5
  client: VertebrClient;
6
6
  catalog: CatalogResponse | null;
7
7
  selection: Selection;
8
+ onSelectionChange?: (next: Selection) => void;
8
9
  authRequired: boolean;
9
10
  loading: boolean;
10
11
  isPageSelection: boolean;
@@ -41,5 +42,5 @@ export type AdminContentProps = {
41
42
  onRefreshFonts?: () => Promise<void>;
42
43
  };
43
44
  };
44
- export declare function AdminContent({ client, catalog, selection, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, onRefreshResource, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }: AdminContentProps): import("react").JSX.Element;
45
+ export declare function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, onRefreshResource, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }: AdminContentProps): import("react").JSX.Element;
45
46
  //# sourceMappingURL=AdminContent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,aAAa,EACd,MAAM,iBAAiB,CAAC;AAOzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,SAAS,CAAC;IACrB,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,cAAc,CAAC;IAC7B,oBAAoB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACrD,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,eAAe,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC5C,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACvC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,aAAa,EAAE;QACb,aAAa,CAAC,EAAE;YACd,MAAM,EAAE,MAAM,EAAE,CAAC;YACjB,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;QACxD,eAAe,CAAC,EAAE,CAChB,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;KACxC,CAAC;IACF,iBAAiB,EAAE;QACjB,KAAK,CAAC,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACrC,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1D,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;KACtC,CAAC;CACH,CAAC;AA6EF,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,OAAO,EACP,SAAS,EACT,YAAY,EACZ,OAAO,EACP,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,aAAa,EACb,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,GAClB,EAAE,iBAAiB,+BAuQnB"}
1
+ {"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACd,mBAAmB,EACnB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,oBAAoB,EACpB,aAAa,EACd,MAAM,iBAAiB,CAAC;AAOzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;IAChC,SAAS,EAAE,SAAS,CAAC;IACrB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,IAAI,CAAC;IAC9C,YAAY,EAAE,OAAO,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,cAAc,CAAC;IAC7B,oBAAoB,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACrD,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,WAAW,EAAE,eAAe,GAAG,IAAI,CAAC;IACpC,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,eAAe,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC5C,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,UAAU,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACvC,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,YAAY,EAAE,oBAAoB,GAAG,IAAI,CAAC;IAC1C,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IAC9C,aAAa,EAAE;QACb,aAAa,CAAC,EAAE;YACd,MAAM,EAAE,MAAM,EAAE,CAAC;YACjB,QAAQ,EAAE,MAAM,CAAC;SAClB,CAAC;QACF,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;QACxD,eAAe,CAAC,EAAE,CAChB,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC;KACxC,CAAC;IACF,iBAAiB,EAAE;QACjB,KAAK,CAAC,EAAE,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACrC,YAAY,CAAC,EAAE,OAAO,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,OAAO,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1D,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;KACtC,CAAC;CACH,CAAC;AA6EF,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,OAAO,EACP,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,OAAO,EACP,eAAe,EACf,WAAW,EACX,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,aAAa,EACb,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,GAClB,EAAE,iBAAiB,+BAmRnB"}
@@ -19,7 +19,7 @@ function ConfigEditor({ client, configData, isPageSelection, previewPath, onConf
19
19
  }, ...galleryPicker, ...fontLibraryPicker }));
20
20
  return (_jsxs("div", { className: "vertebr-config-editor", children: [showPreview ? (_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: isPageSelection ? "Page content" : configData.label }), _jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })] })) : isPageSelection ? (_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Page content" }), _jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })] })) : null, showPreview && previewPath ? (_jsxs("div", { className: "vertebr-page-editor", children: [_jsx("div", { className: "vertebr-page-editor-form", children: form }), _jsx(PagePreviewPane, { previewPath: previewPath, controlId: configData.id, draft: draft ?? configData.value })] })) : (form)] }));
21
21
  }
22
- export function AdminContent({ client, catalog, selection, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, onRefreshResource, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
22
+ export function AdminContent({ client, catalog, selection, onSelectionChange, authRequired, loading, isPageSelection, metricsData, metricsLoading, metricsRange, onMetricsRangeChange, onRefreshMetrics, galleryData, galleryLoading, onRefreshGallery, fontLibraryData, fontLibraryLoading, onRefreshFonts, emailsData, emailsLoading, onRefreshEmails, resourceData, onRefreshResource, configData, onConfigSaved, galleryPicker, fontLibraryPicker, }) {
23
23
  if (!catalog) {
24
24
  return (_jsx("div", { className: "vertebr-content", children: _jsx("section", { className: "vertebr-panel", children: _jsx("div", { className: "vertebr-empty", children: authRequired
25
25
  ? "Authenticated access required for this environment."
@@ -97,7 +97,15 @@ export function AdminContent({ client, catalog, selection, authRequired, loading
97
97
  ? async (id) => {
98
98
  await client.deleteEmailLibraryTemplate(id);
99
99
  }
100
- : undefined })) : null, selection?.kind === "resource" && resourceData ? (_jsx(ResourcePanel, { schema: resourceData.schema, items: resourceData.items, label: resourceData.label, permissions: resourceData.permissions, actions: resourceData.actions, collectionActions: resourceData.collectionActions, uploads: resourceData.uploads, storageKey: resourceData.id, onUpdate: resourceData.permissions.update
100
+ : undefined })) : null, selection?.kind === "resource" && resourceData ? (_jsx(ResourcePanel, { schema: resourceData.schema, items: resourceData.items, label: resourceData.label, initialEditRowId: selection.rowId && selection.id === resourceData.id
101
+ ? selection.rowId
102
+ : undefined, onEditRowIdChange: (rowId) => {
103
+ onSelectionChange?.({
104
+ kind: "resource",
105
+ id: resourceData.id,
106
+ rowId: rowId ?? undefined,
107
+ });
108
+ }, permissions: resourceData.permissions, actions: resourceData.actions, collectionActions: resourceData.collectionActions, uploads: resourceData.uploads, storageKey: resourceData.id, onUpdate: resourceData.permissions.update
101
109
  ? async (rowId, value) => {
102
110
  await client.updateResource(resourceData.id, rowId, value);
103
111
  await onRefreshResource();
@@ -1,4 +1,5 @@
1
1
  import type { AdminEnvironment } from "@vertebr/client";
2
+ import type { Breadcrumb } from "./breadcrumbs";
2
3
  import type { Selection } from "./types";
3
4
  export type AdminHeaderProps = {
4
5
  activeLabel: string;
@@ -7,9 +8,9 @@ export type AdminHeaderProps = {
7
8
  authRequired: boolean;
8
9
  selection: Selection;
9
10
  isPageSelection: boolean;
10
- /** True when the active page/layout has a host previewPath. */
11
11
  hasPagePreview?: boolean;
12
12
  error: string | null;
13
+ breadcrumbs: Breadcrumb[];
13
14
  };
14
- export declare function AdminHeader({ activeLabel, activeEnv, catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, error, }: AdminHeaderProps): import("react").JSX.Element;
15
+ export declare function AdminHeader({ activeLabel, activeEnv, catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, error, breadcrumbs, }: AdminHeaderProps): import("react").JSX.Element;
15
16
  //# sourceMappingURL=AdminHeader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AdminHeader.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,+DAA+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AA+CF,wBAAgB,WAAW,CAAC,EAC1B,WAAW,EACX,SAAS,EACT,aAAa,EACb,YAAY,EACZ,SAAS,EACT,eAAe,EACf,cAAc,EACd,KAAK,GACN,EAAE,gBAAgB,+BAgClB"}
1
+ {"version":3,"file":"AdminHeader.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminHeader.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,EAAE,OAAO,CAAC;IACzB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B,CAAC;AAiDF,wBAAgB,WAAW,CAAC,EAC1B,WAAW,EACX,SAAS,EACT,aAAa,EACb,YAAY,EACZ,SAAS,EACT,eAAe,EACf,cAAc,EACd,KAAK,EACL,WAAW,GACZ,EAAE,gBAAgB,+BA6ClB"}
@@ -16,7 +16,9 @@ function subtitle({ catalogLoaded, authRequired, selection, isPageSelection, has
16
16
  return "Send or schedule branded email to a list or specific people.";
17
17
  }
18
18
  if (selection?.kind === "resource") {
19
- return "Manage records scoped to the selected environment.";
19
+ return selection.rowId
20
+ ? "Edit this record — changes apply to the selected environment."
21
+ : "Manage records scoped to the selected environment.";
20
22
  }
21
23
  if (selection?.kind === "config") {
22
24
  if (hasPagePreview) {
@@ -30,8 +32,8 @@ function subtitle({ catalogLoaded, authRequired, selection, isPageSelection, has
30
32
  }
31
33
  return "Select a panel.";
32
34
  }
33
- export function AdminHeader({ activeLabel, activeEnv, catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, error, }) {
34
- return (_jsxs("header", { className: "vertebr-header", children: [_jsxs("div", { children: [_jsxs("div", { className: "vertebr-heading-row", children: [_jsx("h1", { children: activeLabel }), activeEnv ? (_jsx("span", { className: "vertebr-env-badge", "data-tone": activeEnv.tone ?? "neutral", children: activeEnv.label })) : null] }), _jsx("p", { children: subtitle({
35
+ export function AdminHeader({ activeLabel, activeEnv, catalogLoaded, authRequired, selection, isPageSelection, hasPagePreview, error, breadcrumbs, }) {
36
+ return (_jsxs("header", { className: "vertebr-header", children: [_jsxs("div", { children: [breadcrumbs.length > 1 ? (_jsx("ol", { className: "vertebr-breadcrumbs", children: breadcrumbs.map((crumb, index) => (_jsx("li", { children: index === breadcrumbs.length - 1 ? (_jsx("strong", { children: crumb.label })) : (crumb.label) }, `${crumb.label}-${index}`))) })) : null, _jsxs("div", { className: "vertebr-heading-row", children: [_jsx("h1", { children: activeLabel }), activeEnv ? (_jsx("span", { className: "vertebr-env-badge", "data-tone": activeEnv.tone ?? "neutral", children: activeEnv.label })) : null] }), _jsx("p", { children: subtitle({
35
37
  catalogLoaded,
36
38
  authRequired,
37
39
  selection,
@@ -0,0 +1,2 @@
1
+ export declare function AdminSaveBar(): import("react").JSX.Element | null;
2
+ //# sourceMappingURL=AdminSaveBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdminSaveBar.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSaveBar.tsx"],"names":[],"mappings":"AAEA,wBAAgB,YAAY,uCAmC3B"}
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useSaveBar } from "../../hooks/useSaveBar";
3
+ export function AdminSaveBar() {
4
+ const { state } = useSaveBar();
5
+ if (!state.visible)
6
+ return null;
7
+ return (_jsxs("div", { className: "vertebr-savebar", "data-tone": state.tone, children: [_jsx("div", { className: "vertebr-savebar-message", children: state.saving
8
+ ? "Saving…"
9
+ : state.message ?? (state.isDirty ? "Unsaved changes" : "Saved") }), _jsxs("div", { className: "vertebr-savebar-actions", children: [state.onDiscard && state.isDirty ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: state.saving, onClick: state.onDiscard, children: "Discard" })) : null, state.onSave ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", disabled: state.saving || !state.isDirty, onClick: () => void state.onSave?.(), children: "Save" })) : null] })] }));
10
+ }
@@ -1,25 +1,7 @@
1
- import type { AdminEnvironment } from "@vertebr/client";
2
- import type { VbTheme } from "../../hooks/useVertebrTheme";
3
1
  import type { SidebarNavGroup } from "./types";
4
2
  export type AdminSidebarProps = {
5
- brand: string;
6
- title: string;
7
- envList: AdminEnvironment[];
8
- environment: string;
9
- onEnvironmentChange: (nextEnv: string) => void;
10
3
  navGroups: SidebarNavGroup[];
11
- getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;
12
- authUserLabel?: string;
13
- onSignOut?: () => void | Promise<void>;
14
- authRequired: boolean;
15
- activeEnv?: AdminEnvironment;
16
- token: string;
17
- tokenDraft: string;
18
- onTokenDraftChange: (value: string) => void;
19
- onSaveToken: () => void;
20
- onClearToken: () => void;
21
- theme: VbTheme;
22
- onThemeChange: (theme: VbTheme) => void;
4
+ collapsed: boolean;
23
5
  };
24
- export declare function AdminSidebar({ brand, title, envList, environment, onEnvironmentChange, navGroups, getAccessToken, authUserLabel, onSignOut, authRequired, activeEnv, token, tokenDraft, onTokenDraftChange, onSaveToken, onClearToken, theme, onThemeChange, }: AdminSidebarProps): import("react").JSX.Element;
6
+ export declare function AdminSidebar({ navGroups, collapsed }: AdminSidebarProps): import("react").JSX.Element;
25
7
  //# sourceMappingURL=AdminSidebar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AdminSidebar.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSidebar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,SAAS,CAAC;AAE7D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,cAAc,CAAC,EAAE,MACb,MAAM,GACN,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,YAAY,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACzC,CAAC;AA2EF,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,KAAK,EACL,OAAO,EACP,WAAW,EACX,mBAAmB,EACnB,SAAS,EACT,cAAc,EACd,aAAa,EACb,SAAS,EACT,YAAY,EACZ,SAAS,EACT,KAAK,EACL,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,KAAK,EACL,aAAa,GACd,EAAE,iBAAiB,+BA8FnB"}
1
+ {"version":3,"file":"AdminSidebar.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminSidebar.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAgB,eAAe,EAAE,MAAM,SAAS,CAAC;AAG7D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAwFF,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,iBAAiB,+BAuBvE"}
@@ -1,31 +1,27 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
- import { EnvSelect } from "../env/EnvSelect";
4
- import { ThemeToggle } from "../theme/ThemeToggle";
5
- function SidebarNavItem({ item }) {
3
+ import { iconForNavKey } from "../ui/icons";
4
+ function SidebarNavItem({ item, collapsed, }) {
6
5
  const hasChildren = Boolean(item.children?.length);
7
6
  const childActive = item.children?.some((child) => child.active) ?? false;
8
7
  const sectionActive = item.active || childActive;
9
8
  const [open, setOpen] = useState(sectionActive);
9
+ const Icon = item.icon ?? iconForNavKey(item.key);
10
10
  useEffect(() => {
11
11
  if (sectionActive)
12
12
  setOpen(true);
13
13
  }, [sectionActive]);
14
14
  if (!hasChildren) {
15
- return (_jsx("button", { type: "button", "data-active": item.active, onClick: item.onSelect, children: item.label }));
15
+ return (_jsxs("button", { type: "button", "data-active": item.active, title: collapsed ? item.label : undefined, onClick: item.onSelect, children: [_jsx(Icon, { className: "vertebr-nav-icon" }), _jsx("span", { className: "vertebr-nav-label-text", children: item.label })] }));
16
16
  }
17
- return (_jsxs("div", { className: "vertebr-nav-item", "data-has-children": "true", "data-open": open || undefined, children: [_jsxs("div", { className: "vertebr-nav-parent", children: [_jsx("button", { type: "button", className: "vertebr-nav-parent-label", "data-active": sectionActive && !childActive, "data-section": sectionActive || undefined, onClick: () => {
17
+ return (_jsxs("div", { className: "vertebr-nav-item", "data-has-children": "true", "data-open": open || undefined, children: [_jsxs("div", { className: "vertebr-nav-parent", children: [_jsxs("button", { type: "button", className: "vertebr-nav-parent-label", "data-active": sectionActive && !childActive, "data-section": sectionActive || undefined, title: collapsed ? item.label : undefined, onClick: () => {
18
18
  setOpen(true);
19
19
  item.onSelect();
20
- }, children: item.label }), _jsx("button", { type: "button", className: "vertebr-nav-parent-toggle", "aria-label": open ? `Collapse ${item.label}` : `Expand ${item.label}`, "aria-expanded": open, onClick: () => setOpen((prev) => !prev), children: _jsx("span", { "aria-hidden": "true", children: open ? "▾" : "▸" }) })] }), open ? (_jsx("div", { className: "vertebr-nav-sub", role: "group", children: item.children.map((child) => (_jsx("button", { type: "button", className: "vertebr-nav-subitem", "data-active": child.active, onClick: () => {
20
+ }, children: [_jsx(Icon, { className: "vertebr-nav-icon" }), _jsx("span", { className: "vertebr-nav-label-text", children: item.label })] }), !collapsed ? (_jsx("button", { type: "button", className: "vertebr-nav-parent-toggle", "aria-label": open ? `Collapse ${item.label}` : `Expand ${item.label}`, "aria-expanded": open, onClick: () => setOpen((prev) => !prev), children: _jsx("span", { "aria-hidden": "true", children: open ? "▾" : "▸" }) })) : null] }), open && !collapsed ? (_jsx("div", { className: "vertebr-nav-sub", role: "group", children: item.children.map((child) => (_jsx("button", { type: "button", className: "vertebr-nav-subitem", "data-active": child.active, onClick: () => {
21
21
  setOpen(true);
22
22
  child.onSelect();
23
23
  }, children: child.label }, child.key))) })) : null] }));
24
24
  }
25
- export function AdminSidebar({ brand, title, envList, environment, onEnvironmentChange, navGroups, getAccessToken, authUserLabel, onSignOut, authRequired, activeEnv, token, tokenDraft, onTokenDraftChange, onSaveToken, onClearToken, theme, onThemeChange, }) {
26
- return (_jsxs("aside", { className: "vertebr-sidebar", children: [_jsxs("div", { className: "vertebr-sidebar-top", children: [_jsxs("div", { className: "vertebr-brand", children: [_jsx("strong", { children: brand }), _jsx("span", { children: title })] }), envList.length > 0 ? (_jsxs("div", { className: "vertebr-env", children: [_jsx("div", { className: "vertebr-nav-label", children: "Environment" }), _jsx(EnvSelect, { environments: envList, value: environment, onChange: onEnvironmentChange })] })) : null, _jsx("nav", { className: "vertebr-nav", children: navGroups.map((group) => (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: group.label }), group.items.map((item) => (_jsx(SidebarNavItem, { item: item }, item.key)))] }, group.label))) })] }), _jsxs("div", { className: "vertebr-sidebar-bottom", children: [getAccessToken && authUserLabel ? (_jsxs("div", { className: "vertebr-auth", children: [_jsx("div", { className: "vertebr-nav-label", children: "Signed in" }), _jsx("p", { className: "vertebr-auth-hint", children: authUserLabel }), onSignOut ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: () => void onSignOut(), children: "Sign out" })) : null] })) : null, !getAccessToken && (authRequired || token) ? (_jsxs("div", { className: "vertebr-auth", children: [_jsx("div", { className: "vertebr-nav-label", children: "Access" }), _jsx("p", { className: "vertebr-auth-hint", children: authRequired
27
- ? `This environment requires auth${activeEnv?.auth?.claimKey
28
- ? ` (${activeEnv.auth.claimKey})`
29
- : ""}.`
30
- : "Optional access token for protected environments." }), _jsx("input", { className: "vertebr-auth-input", type: "password", placeholder: "Bearer token", value: tokenDraft, onChange: (event) => onTokenDraftChange(event.target.value), autoComplete: "off" }), _jsxs("div", { className: "vertebr-auth-actions", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", onClick: onSaveToken, children: "Use token" }), token ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: onClearToken, children: "Clear" })) : null] })] })) : null, _jsx(ThemeToggle, { theme: theme, onChange: onThemeChange })] })] }));
25
+ export function AdminSidebar({ navGroups, collapsed }) {
26
+ return (_jsx("aside", { className: "vertebr-sidebar", "data-collapsed": collapsed || undefined, children: _jsx("div", { className: "vertebr-sidebar-top", children: _jsx("nav", { className: "vertebr-nav", children: navGroups.map((group) => (_jsxs("div", { className: "vertebr-nav-group", children: [!collapsed ? (_jsx("div", { className: "vertebr-nav-label", children: group.label })) : null, group.items.map((item) => (_jsx(SidebarNavItem, { item: item, collapsed: collapsed }, item.key)))] }, group.label))) }) }) }));
31
27
  }
@@ -0,0 +1,2 @@
1
+ export declare function AdminToastStack(): import("react").JSX.Element | null;
2
+ //# sourceMappingURL=AdminToastStack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdminToastStack.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminToastStack.tsx"],"names":[],"mappings":"AAEA,wBAAgB,eAAe,uCA0B9B"}
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useToast } from "../../hooks/useToast";
3
+ export function AdminToastStack() {
4
+ const { toasts, dismiss } = useToast();
5
+ if (!toasts.length)
6
+ return null;
7
+ return (_jsx("div", { className: "vertebr-toast-stack", "aria-live": "polite", children: toasts.map((toast) => (_jsxs("div", { className: "vertebr-toast", "data-tone": toast.tone, role: "status", children: [_jsx("span", { children: toast.message }), _jsx("button", { type: "button", className: "vertebr-toast-dismiss", "aria-label": "Dismiss", onClick: () => dismiss(toast.id), children: "\u00D7" })] }, toast.id))) }));
8
+ }
@@ -0,0 +1,24 @@
1
+ import type { AdminEnvironment } from "@vertebr/client";
2
+ import type { VbTheme } from "../../hooks/useVertebrTheme";
3
+ export type AdminTopBarProps = {
4
+ brand: string;
5
+ envList: AdminEnvironment[];
6
+ environment: string;
7
+ onEnvironmentChange: (nextEnv: string) => void;
8
+ activeEnv?: AdminEnvironment;
9
+ authUserLabel?: string;
10
+ onSignOut?: () => void | Promise<void>;
11
+ getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;
12
+ authRequired: boolean;
13
+ token: string;
14
+ tokenDraft: string;
15
+ onTokenDraftChange: (value: string) => void;
16
+ onSaveToken: () => void;
17
+ onClearToken: () => void;
18
+ theme: VbTheme;
19
+ onThemeChange: (theme: VbTheme) => void;
20
+ sidebarCollapsed: boolean;
21
+ onToggleSidebar: () => void;
22
+ };
23
+ export declare function AdminTopBar({ brand, envList, environment, onEnvironmentChange, activeEnv, authUserLabel, onSignOut, getAccessToken, authRequired, token, tokenDraft, onTokenDraftChange, onSaveToken, onClearToken, theme, onThemeChange, sidebarCollapsed, onToggleSidebar, }: AdminTopBarProps): import("react").JSX.Element;
24
+ //# sourceMappingURL=AdminTopBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdminTopBar.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminTopBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAG3D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,mBAAmB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,cAAc,CAAC,EAAE,MACb,MAAM,GACN,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACvC,YAAY,EAAE,OAAO,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,OAAO,EACP,WAAW,EACX,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,SAAS,EACT,cAAc,EACd,YAAY,EACZ,KAAK,EACL,UAAU,EACV,kBAAkB,EAClB,WAAW,EACX,YAAY,EACZ,KAAK,EACL,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,EAAE,gBAAgB,+BAsFlB"}
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { EnvSelect } from "../env/EnvSelect";
3
+ import { ThemeToggle } from "../theme/ThemeToggle";
4
+ import { IconSearch } from "../ui/icons";
5
+ export function AdminTopBar({ brand, envList, environment, onEnvironmentChange, activeEnv, authUserLabel, onSignOut, getAccessToken, authRequired, token, tokenDraft, onTokenDraftChange, onSaveToken, onClearToken, theme, onThemeChange, sidebarCollapsed, onToggleSidebar, }) {
6
+ return (_jsxs("header", { className: "vertebr-topbar", children: [_jsxs("div", { className: "vertebr-topbar-start", children: [_jsx("button", { type: "button", className: "vertebr-topbar-toggle", "aria-label": sidebarCollapsed ? "Expand sidebar" : "Collapse sidebar", onClick: onToggleSidebar, children: "\u2630" }), _jsx("strong", { className: "vertebr-topbar-brand", children: brand }), envList.length > 0 ? (_jsxs("div", { className: "vertebr-topbar-env", children: [_jsx(EnvSelect, { environments: envList, value: environment, onChange: onEnvironmentChange }), activeEnv ? (_jsx("span", { className: "vertebr-env-badge", "data-tone": activeEnv.tone ?? "neutral", children: activeEnv.label })) : null] })) : null] }), _jsxs("div", { className: "vertebr-topbar-search", "aria-hidden": "true", children: [_jsx(IconSearch, {}), _jsx("span", { children: "Search admin" }), _jsx("kbd", { children: "\u2318K" })] }), _jsxs("div", { className: "vertebr-topbar-end", children: [!getAccessToken && (authRequired || token) ? (_jsxs("div", { className: "vertebr-topbar-token", children: [_jsx("input", { className: "vertebr-auth-input", type: "password", placeholder: "Access token", value: tokenDraft, onChange: (event) => onTokenDraftChange(event.target.value), autoComplete: "off" }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: onSaveToken, children: "Use" }), token ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: onClearToken, children: "Clear" })) : null] })) : null, getAccessToken && authUserLabel ? (_jsx("span", { className: "vertebr-topbar-user", children: authUserLabel })) : null, onSignOut ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => void onSignOut(), children: "Sign out" })) : null, _jsx(ThemeToggle, { theme: theme, onChange: onThemeChange })] })] }));
7
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,wBAAgB,YAAY,CAAC,EAC3B,OAAsB,EACtB,QAAQ,EAAE,YAAY,EACtB,KAAuB,EACvB,aAA6B,EAC7B,eAAiC,EACjC,eAAiC,EACjC,sBAAgD,EAChD,cAAc,EACd,aAAa,EACb,SAAS,GACV,EAAE,iBAAiB,+BA+iBnB"}
1
+ {"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"AAqCA,OAAO,KAAK,EAGV,iBAAiB,EAClB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD,wBAAgB,YAAY,CAAC,EAC3B,OAAsB,EACtB,QAAQ,EAAE,YAAY,EACtB,KAAuB,EACvB,aAA6B,EAC7B,eAAiC,EACjC,eAAiC,EACjC,sBAAgD,EAChD,cAAc,EACd,aAAa,EACb,SAAS,GACV,EAAE,iBAAiB,+BAskBnB"}
@@ -5,10 +5,17 @@ import { useVertebrTheme } from "../../hooks/useVertebrTheme";
5
5
  import { parseMetricsRangeId, } from "../../lib/metricsRanges";
6
6
  import { AdminContent } from "./AdminContent";
7
7
  import { AdminHeader } from "./AdminHeader";
8
+ import { AdminSaveBar } from "./AdminSaveBar";
8
9
  import { AdminSidebar } from "./AdminSidebar";
10
+ import { AdminToastStack } from "./AdminToastStack";
11
+ import { AdminTopBar } from "./AdminTopBar";
12
+ import { breadcrumbsForSelection } from "./breadcrumbs";
9
13
  import { adminBaseFromApiBase, coerceSelection, defaultSelection, hrefForSelection, selectionFromPath, selectionsEqual, } from "./routes";
10
14
  import { sidebarGroups } from "./sidebar";
11
15
  import { clearStored, readStored, writeStored } from "./storage";
16
+ import { SaveBarProvider } from "../../hooks/useSaveBar";
17
+ import { ToastProvider } from "../../hooks/useToast";
18
+ import { iconForNavKey } from "../ui/icons";
12
19
  export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, title = "Control plane", envStorageKey = "vertebr:env", tokenStorageKey = "vertebr:token", themeStorageKey = "vertebr:theme", metricsRangeStorageKey = "vertebr:metrics-range", getAccessToken, authUserLabel, onSignOut, }) {
13
20
  const basePath = basePathProp ?? adminBaseFromApiBase(apiBase);
14
21
  const { theme, setTheme } = useVertebrTheme(themeStorageKey);
@@ -43,6 +50,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
43
50
  const [emailsLoading, setEmailsLoading] = useState(false);
44
51
  const [error, setError] = useState(null);
45
52
  const [loading, setLoading] = useState(true);
53
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
46
54
  /** Skip pushState when selection was applied from the URL (catalog/popstate). */
47
55
  const skipNextUrlPush = useRef(true);
48
56
  const syncUrl = useCallback((next, catalogSnapshot, replace = false) => {
@@ -371,6 +379,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
371
379
  key: "overview",
372
380
  group: catalog.metrics.group ?? "Analytics",
373
381
  label: catalog.metrics.label,
382
+ icon: iconForNavKey("overview"),
374
383
  active: selection?.kind === "overview",
375
384
  onSelect: () => setSelection({ kind: "overview" }),
376
385
  });
@@ -380,6 +389,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
380
389
  key: `resource:${resource.id}`,
381
390
  group: resource.group ?? "Resources",
382
391
  label: resource.label,
392
+ icon: iconForNavKey(`resource:${resource.id}`),
383
393
  active: selection?.kind === "resource" && selection.id === resource.id,
384
394
  onSelect: () => setSelection({ kind: "resource", id: resource.id }),
385
395
  });
@@ -389,6 +399,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
389
399
  key: "gallery",
390
400
  group: catalog.gallery.group ?? "Resources",
391
401
  label: catalog.gallery.label,
402
+ icon: iconForNavKey("gallery"),
392
403
  active: selection?.kind === "gallery",
393
404
  onSelect: () => setSelection({ kind: "gallery" }),
394
405
  });
@@ -398,6 +409,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
398
409
  key: "fonts",
399
410
  group: catalog.fontLibrary.group ?? "Resources",
400
411
  label: catalog.fontLibrary.label,
412
+ icon: iconForNavKey("fonts"),
401
413
  active: selection?.kind === "fontLibrary",
402
414
  onSelect: () => setSelection({ kind: "fontLibrary" }),
403
415
  });
@@ -408,6 +420,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
408
420
  key: "emails",
409
421
  group: catalog.emails.group ?? "Growth",
410
422
  label: catalog.emails.label,
423
+ icon: iconForNavKey("emails"),
411
424
  active: selection?.kind === "emails",
412
425
  onSelect: () => setSelection({ kind: "emails", tab: "compose" }),
413
426
  children: [
@@ -438,6 +451,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
438
451
  key: `config:${config.id}`,
439
452
  group: config.group ?? fallback,
440
453
  label: config.label,
454
+ icon: iconForNavKey(`config:${config.id}`),
441
455
  active: selection?.kind === "config" && selection.id === config.id,
442
456
  onSelect: () => setSelection({ kind: "config", id: config.id }),
443
457
  });
@@ -463,5 +477,5 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
463
477
  : selection?.kind === "config"
464
478
  ? catalog?.configs.find((c) => c.id === selection.id)?.label
465
479
  : null;
466
- return (_jsxs("div", { className: "vertebr-root", "data-env": environment, children: [_jsx(AdminSidebar, { brand: bootstrap?.brand ?? catalog?.brand ?? "Vertebr", title: title, envList: envList, environment: environment, onEnvironmentChange: switchEnvironment, navGroups: navGroups, getAccessToken: getAccessToken, authUserLabel: authUserLabel, onSignOut: onSignOut, authRequired: authRequired, activeEnv: activeEnv, token: token, tokenDraft: tokenDraft, onTokenDraftChange: setTokenDraft, onSaveToken: saveToken, onClearToken: clearToken, theme: theme, onThemeChange: setTheme }), _jsxs("main", { className: "vertebr-main", children: [_jsx(AdminHeader, { activeLabel: activeLabel ?? (catalog ? "Dashboard" : "Sign in"), activeEnv: activeEnv, catalogLoaded: Boolean(catalog), authRequired: authRequired, selection: selection, isPageSelection: isPageSelection, hasPagePreview: hasPagePreview, error: error }), _jsx(AdminContent, { client: client, catalog: catalog, selection: selection, authRequired: authRequired, loading: loading, isPageSelection: isPageSelection, metricsData: metricsData, metricsLoading: metricsLoading, metricsRange: metricsRange, onMetricsRangeChange: changeMetricsRange, onRefreshMetrics: refreshMetrics, galleryData: galleryData, galleryLoading: galleryLoading, onRefreshGallery: refreshGallery, fontLibraryData: fontLibraryData, fontLibraryLoading: fontLibraryLoading, onRefreshFonts: refreshFonts, emailsData: emailsData, emailsLoading: emailsLoading, onRefreshEmails: refreshEmails, resourceData: resourceData, onRefreshResource: refreshResource, configData: configData, onConfigSaved: setConfigData, galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker })] })] }));
480
+ return (_jsx(SaveBarProvider, { children: _jsx(ToastProvider, { children: _jsxs("div", { className: "vertebr-root", "data-env": environment, "data-sidebar-collapsed": sidebarCollapsed || undefined, children: [_jsx(AdminTopBar, { brand: bootstrap?.brand ?? catalog?.brand ?? "Vertebr", envList: envList, environment: environment, onEnvironmentChange: switchEnvironment, activeEnv: activeEnv, authUserLabel: authUserLabel, onSignOut: onSignOut, getAccessToken: getAccessToken, authRequired: authRequired, token: token, tokenDraft: tokenDraft, onTokenDraftChange: setTokenDraft, onSaveToken: saveToken, onClearToken: clearToken, theme: theme, onThemeChange: setTheme, sidebarCollapsed: sidebarCollapsed, onToggleSidebar: () => setSidebarCollapsed((prev) => !prev) }), _jsx(AdminSidebar, { navGroups: navGroups, collapsed: sidebarCollapsed }), _jsxs("main", { className: "vertebr-main", children: [_jsx(AdminHeader, { activeLabel: activeLabel ?? (catalog ? "Dashboard" : "Sign in"), activeEnv: activeEnv, catalogLoaded: Boolean(catalog), authRequired: authRequired, selection: selection, isPageSelection: isPageSelection, hasPagePreview: hasPagePreview, error: error, breadcrumbs: breadcrumbsForSelection(selection, catalog) }), _jsx(AdminContent, { client: client, catalog: catalog, selection: selection, onSelectionChange: setSelection, authRequired: authRequired, loading: loading, isPageSelection: isPageSelection, metricsData: metricsData, metricsLoading: metricsLoading, metricsRange: metricsRange, onMetricsRangeChange: changeMetricsRange, onRefreshMetrics: refreshMetrics, galleryData: galleryData, galleryLoading: galleryLoading, onRefreshGallery: refreshGallery, fontLibraryData: fontLibraryData, fontLibraryLoading: fontLibraryLoading, onRefreshFonts: refreshFonts, emailsData: emailsData, emailsLoading: emailsLoading, onRefreshEmails: refreshEmails, resourceData: resourceData, onRefreshResource: refreshResource, configData: configData, onConfigSaved: setConfigData, galleryPicker: galleryPicker, fontLibraryPicker: fontLibraryPicker }), _jsx(AdminSaveBar, {})] }), _jsx(AdminToastStack, {})] }) }) }));
467
481
  }
@@ -0,0 +1,8 @@
1
+ import type { CatalogResponse } from "@vertebr/client";
2
+ import type { Selection } from "./types";
3
+ export type Breadcrumb = {
4
+ label: string;
5
+ href?: string;
6
+ };
7
+ export declare function breadcrumbsForSelection(selection: Selection, catalog: CatalogResponse | null): Breadcrumb[];
8
+ //# sourceMappingURL=breadcrumbs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"breadcrumbs.d.ts","sourceRoot":"","sources":["../../../src/components/admin/breadcrumbs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GAAG,IAAI,GAC9B,UAAU,EAAE,CA6Cd"}
@@ -0,0 +1,44 @@
1
+ export function breadcrumbsForSelection(selection, catalog) {
2
+ const crumbs = [{ label: "Home", href: "/overview" }];
3
+ if (!selection || !catalog)
4
+ return crumbs;
5
+ switch (selection.kind) {
6
+ case "overview":
7
+ return [...crumbs, { label: catalog.metrics?.label ?? "Overview" }];
8
+ case "gallery":
9
+ return [...crumbs, { label: catalog.gallery?.label ?? "Gallery" }];
10
+ case "fontLibrary":
11
+ return [...crumbs, { label: catalog.fontLibrary?.label ?? "Fonts" }];
12
+ case "emails":
13
+ return [
14
+ ...crumbs,
15
+ { label: catalog.emails?.label ?? "Email" },
16
+ {
17
+ label: selection.tab === "templates"
18
+ ? "Templates"
19
+ : selection.tab === "activity"
20
+ ? "Activity"
21
+ : "Compose",
22
+ },
23
+ ];
24
+ case "resource": {
25
+ const resource = catalog.resources.find((item) => item.id === selection.id);
26
+ const items = [
27
+ ...crumbs,
28
+ { label: resource?.group ?? "Products", href: undefined },
29
+ { label: resource?.label ?? selection.id },
30
+ ];
31
+ if (selection.rowId) {
32
+ items.push({ label: selection.rowId });
33
+ }
34
+ return items;
35
+ }
36
+ case "config": {
37
+ const config = catalog.configs.find((item) => item.id === selection.id);
38
+ const group = config?.group ?? (config?.section === "page" ? "Pages" : "Settings");
39
+ return [...crumbs, { label: group }, { label: config?.label ?? selection.id }];
40
+ }
41
+ default:
42
+ return crumbs;
43
+ }
44
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/components/admin/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,SAAS,CAAC;AAIpD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAO5D;AAOD,iEAAiE;AACjE,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,MAAM,CAuBR;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,MAAM,CAIR;AASD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,GAAG,IAAI,GAC9B,SAAS,GAAG,IAAI,CAoClB;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,CAUpE;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,SAAS,CAyBX;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAanE"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/components/admin/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,SAAS,CAAC;AAIpD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAO5D;AAOD,iEAAiE;AACjE,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,MAAM,CA0BR;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,MAAM,CAIR;AASD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,GAAG,IAAI,GAC9B,SAAS,GAAG,IAAI,CAuClB;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,CAUpE;AAED,+DAA+D;AAC/D,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,eAAe,GACvB,SAAS,CAyBX;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,GAAG,OAAO,CAenE"}
@@ -32,6 +32,9 @@ export function pathForSelection(selection, catalog) {
32
32
  return `/emails/${tab}`;
33
33
  }
34
34
  case "resource":
35
+ if (selection.rowId) {
36
+ return `/resources/${encodeURIComponent(selection.id)}/${encodeURIComponent(selection.rowId)}`;
37
+ }
35
38
  return `/resources/${encodeURIComponent(selection.id)}`;
36
39
  case "config": {
37
40
  const config = catalog.configs.find((item) => item.id === selection.id);
@@ -86,6 +89,9 @@ export function selectionFromPath(pathname, basePath, catalog) {
86
89
  if (catalog && !catalog.resources.some((item) => item.id === id)) {
87
90
  return null;
88
91
  }
92
+ if (tail[1]) {
93
+ return { kind: "resource", id, rowId: decodeURIComponent(tail[1]) };
94
+ }
89
95
  return { kind: "resource", id };
90
96
  }
91
97
  if ((head === "pages" || head === "config") && tail[0]) {
@@ -162,8 +168,9 @@ export function selectionsEqual(a, b) {
162
168
  if (a.kind === "emails" && b.kind === "emails") {
163
169
  return (a.tab ?? "compose") === (b.tab ?? "compose");
164
170
  }
165
- if (a.kind === "resource" && b.kind === "resource")
166
- return a.id === b.id;
171
+ if (a.kind === "resource" && b.kind === "resource") {
172
+ return a.id === b.id && (a.rowId ?? "") === (b.rowId ?? "");
173
+ }
167
174
  if (a.kind === "config" && b.kind === "config")
168
175
  return a.id === b.id;
169
176
  return false;
@@ -22,12 +22,17 @@ export type VertebrAdminProps = {
22
22
  /** Host-provided sign-out action. */
23
23
  onSignOut?: () => void | Promise<void>;
24
24
  };
25
+ import type { ReactElement } from "react";
25
26
  export type EmailsTab = "compose" | "templates" | "activity";
27
+ export type NavIcon = (props: {
28
+ className?: string;
29
+ }) => ReactElement;
26
30
  export type Selection = {
27
31
  kind: "overview";
28
32
  } | {
29
33
  kind: "resource";
30
34
  id: string;
35
+ rowId?: string;
31
36
  } | {
32
37
  kind: "gallery";
33
38
  } | {
@@ -52,6 +57,7 @@ export type SidebarEntry = {
52
57
  active: boolean;
53
58
  onSelect: () => void;
54
59
  }>;
60
+ icon?: NavIcon;
55
61
  };
56
62
  export type SidebarNavGroup = {
57
63
  label: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/admin/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oEAAoE;IACpE,cAAc,CAAC,EAAE,MACb,MAAM,GACN,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACvC,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAE7D,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,CAAC,EAAE,SAAS,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9B,IAAI,CAAC;AAET,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,OAAO,CAAC;QAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;KACtB,CAAC,CAAC;CACJ,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/admin/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sDAAsD;IACtD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,oEAAoE;IACpE,cAAc,CAAC,EAAE,MACb,MAAM,GACN,IAAI,GACJ,SAAS,GACT,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACvC,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAEF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAE1C,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAE7D,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,YAAY,CAAC;AAEtE,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,CAAC,EAAE,SAAS,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC9B,IAAI,CAAC;AAET,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,KAAK,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,OAAO,CAAC;QAChB,QAAQ,EAAE,MAAM,IAAI,CAAC;KACtB,CAAC,CAAC;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigForm.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/ConfigForm.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAElE,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,KAAK,EACL,MAAM,EACN,WAAW,EACX,OAAkB,EAClB,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACR,WAAW,GACZ,EAAE,eAAe,+BAkGjB"}
1
+ {"version":3,"file":"ConfigForm.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/ConfigForm.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAElE,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,KAAK,EACL,MAAM,EACN,WAAW,EACX,OAAkB,EAClB,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAAc,EACd,QAAQ,EACR,WAAW,GACZ,EAAE,eAAe,+BAqIjB"}
@@ -2,10 +2,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from "react";
3
3
  import { useForm, useWatch } from "react-hook-form";
4
4
  import { zodResolver } from "@hookform/resolvers/zod";
5
+ import { useSaveBar } from "../../../hooks/useSaveBar";
6
+ import { useToast } from "../../../hooks/useToast";
5
7
  import { buildZodSchema, groupFields, withSpecVisibility, withSpecVisibilityValues, } from "./helpers";
6
8
  import { RecordConfigForm } from "./RecordConfigForm";
7
9
  import { SettingsConfigForm } from "./SettingsConfigForm";
8
10
  export function ConfigForm({ schema, value, onSave, submitLabel, variant = "config", uploads, onUpload, galleryUpload, onListGallery, onUploadGallery, fonts, fontsLoading, onListFonts, onRefreshFonts, onChange, hideActions, }) {
11
+ const { register, clear } = useSaveBar();
12
+ const { push: pushToast } = useToast();
9
13
  const editorSchema = useMemo(() => (variant === "record" ? withSpecVisibility(schema) : schema), [schema, variant]);
10
14
  const editorValue = useMemo(() => variant === "record"
11
15
  ? withSpecVisibilityValues(editorSchema, value)
@@ -25,20 +29,58 @@ export function ConfigForm({ schema, value, onSave, submitLabel, variant = "conf
25
29
  useEffect(() => {
26
30
  form.reset(editorValue);
27
31
  }, [editorValue, form]);
32
+ const onDiscard = () => {
33
+ form.reset(editorValue);
34
+ setStatus(null);
35
+ setTone(undefined);
36
+ };
28
37
  const onSubmit = form.handleSubmit(async (data) => {
29
38
  setStatus("Saving…");
30
39
  setTone(undefined);
40
+ register({ saving: true, isDirty: true, visible: true });
31
41
  try {
32
42
  await onSave(data);
33
43
  setStatus("Saved");
34
44
  setTone("ok");
45
+ form.reset(data);
46
+ pushToast("Changes saved", "ok");
35
47
  }
36
48
  catch (error) {
37
- setStatus(error instanceof Error ? error.message : "Save failed");
49
+ const message = error instanceof Error ? error.message : "Save failed";
50
+ setStatus(message);
38
51
  setTone("danger");
52
+ pushToast(message, "danger");
53
+ }
54
+ finally {
55
+ register({ saving: false });
39
56
  }
40
57
  });
41
58
  const isDirty = form.formState.isDirty;
59
+ useEffect(() => {
60
+ if (hideActions) {
61
+ clear();
62
+ return;
63
+ }
64
+ register({
65
+ visible: isDirty,
66
+ isDirty,
67
+ saving: status === "Saving…",
68
+ message: status ?? undefined,
69
+ tone,
70
+ onSave: () => void onSubmit(),
71
+ onDiscard,
72
+ });
73
+ return () => clear();
74
+ }, [
75
+ hideActions,
76
+ isDirty,
77
+ status,
78
+ tone,
79
+ onSubmit,
80
+ onDiscard,
81
+ register,
82
+ clear,
83
+ ]);
42
84
  useEffect(() => {
43
85
  const onKeyDown = (event) => {
44
86
  if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") {
@@ -49,11 +91,6 @@ export function ConfigForm({ schema, value, onSave, submitLabel, variant = "conf
49
91
  window.addEventListener("keydown", onKeyDown);
50
92
  return () => window.removeEventListener("keydown", onKeyDown);
51
93
  }, [onSubmit]);
52
- const onDiscard = () => {
53
- form.reset(editorValue);
54
- setStatus(null);
55
- setTone(undefined);
56
- };
57
94
  const shared = {
58
95
  schema: editorSchema,
59
96
  groups,
@@ -74,7 +111,7 @@ export function ConfigForm({ schema, value, onSave, submitLabel, variant = "conf
74
111
  tone,
75
112
  onDiscard,
76
113
  submitLabel,
77
- hideActions,
114
+ hideActions: true,
78
115
  };
79
116
  if (variant === "record") {
80
117
  return _jsx(RecordConfigForm, { ...shared });