@vertebr/react 0.1.1 → 0.2.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 (88) hide show
  1. package/dist/{VertebrAdmin.d.ts → components/admin/VertebrAdmin.d.ts} +3 -1
  2. package/dist/components/admin/VertebrAdmin.d.ts.map +1 -0
  3. package/dist/{VertebrAdmin.js → components/admin/VertebrAdmin.js} +117 -33
  4. package/dist/components/config/ConfigForm.d.ts +19 -0
  5. package/dist/components/config/ConfigForm.d.ts.map +1 -0
  6. package/dist/components/config/ConfigForm.js +281 -0
  7. package/dist/{EnvSelect.d.ts → components/env/EnvSelect.d.ts} +1 -1
  8. package/dist/components/env/EnvSelect.d.ts.map +1 -0
  9. package/dist/components/gallery/GalleryBoard.d.ts +14 -0
  10. package/dist/components/gallery/GalleryBoard.d.ts.map +1 -0
  11. package/dist/components/gallery/GalleryBoard.js +14 -0
  12. package/dist/components/index.d.ts +9 -0
  13. package/dist/components/index.d.ts.map +1 -0
  14. package/dist/components/index.js +6 -0
  15. package/dist/{MetricsBoard.d.ts → components/metrics/MetricsBoard.d.ts} +1 -1
  16. package/dist/components/metrics/MetricsBoard.d.ts.map +1 -0
  17. package/dist/components/resources/BulkImageUploadModal.d.ts +17 -0
  18. package/dist/components/resources/BulkImageUploadModal.d.ts.map +1 -0
  19. package/dist/components/resources/BulkImageUploadModal.js +47 -0
  20. package/dist/components/resources/ColumnVisibilityMenu.d.ts +11 -0
  21. package/dist/components/resources/ColumnVisibilityMenu.d.ts.map +1 -0
  22. package/dist/components/resources/ColumnVisibilityMenu.js +40 -0
  23. package/dist/components/resources/ImageUploadControl.d.ts +10 -0
  24. package/dist/components/resources/ImageUploadControl.d.ts.map +1 -0
  25. package/dist/components/resources/ImageUploadControl.js +50 -0
  26. package/dist/components/resources/ResourceCell.d.ts +10 -0
  27. package/dist/components/resources/ResourceCell.d.ts.map +1 -0
  28. package/dist/components/resources/ResourceCell.js +114 -0
  29. package/dist/{ResourceTable.d.ts → components/resources/ResourceTable.d.ts} +10 -2
  30. package/dist/components/resources/ResourceTable.d.ts.map +1 -0
  31. package/dist/components/resources/ResourceTable.js +200 -0
  32. package/dist/components/resources/ResourceViewModal.d.ts +11 -0
  33. package/dist/components/resources/ResourceViewModal.d.ts.map +1 -0
  34. package/dist/components/resources/ResourceViewModal.js +44 -0
  35. package/dist/components/theme/ThemeToggle.d.ts +7 -0
  36. package/dist/components/theme/ThemeToggle.d.ts.map +1 -0
  37. package/dist/components/theme/ThemeToggle.js +10 -0
  38. package/dist/hooks/index.d.ts +5 -0
  39. package/dist/hooks/index.d.ts.map +1 -0
  40. package/dist/hooks/index.js +2 -0
  41. package/dist/{useVertebrControls.d.ts → hooks/useVertebrControls.d.ts} +2 -2
  42. package/dist/hooks/useVertebrControls.d.ts.map +1 -0
  43. package/dist/{useVertebrControls.js → hooks/useVertebrControls.js} +7 -18
  44. package/dist/hooks/useVertebrTheme.d.ts +7 -0
  45. package/dist/hooks/useVertebrTheme.d.ts.map +1 -0
  46. package/dist/hooks/useVertebrTheme.js +38 -0
  47. package/dist/index.d.ts +15 -10
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +11 -6
  50. package/dist/styles/auth.css +39 -0
  51. package/dist/styles/base.css +70 -0
  52. package/dist/styles/buttons.css +86 -0
  53. package/dist/styles/cells.css +178 -0
  54. package/dist/styles/columns.css +103 -0
  55. package/dist/styles/form.css +498 -0
  56. package/dist/styles/gallery.css +90 -0
  57. package/dist/styles/index.css +18 -0
  58. package/dist/styles/main.css +89 -0
  59. package/dist/styles/modal.css +110 -0
  60. package/dist/styles/nav.css +92 -0
  61. package/dist/styles/panel.css +204 -0
  62. package/dist/styles/select.css +151 -0
  63. package/dist/styles/sidebar.css +42 -0
  64. package/dist/styles/states.css +169 -0
  65. package/dist/styles/table.css +92 -0
  66. package/dist/styles/theme-toggle.css +87 -0
  67. package/dist/styles.css +2 -997
  68. package/dist/themes/dark.css +55 -0
  69. package/dist/themes/index.css +2 -0
  70. package/dist/themes/light.css +102 -0
  71. package/package.json +6 -3
  72. package/dist/ConfigForm.d.ts +0 -9
  73. package/dist/ConfigForm.d.ts.map +0 -1
  74. package/dist/ConfigForm.js +0 -87
  75. package/dist/EnvSelect.d.ts.map +0 -1
  76. package/dist/MetricsBoard.d.ts.map +0 -1
  77. package/dist/ResourceTable.d.ts.map +0 -1
  78. package/dist/ResourceTable.js +0 -112
  79. package/dist/VertebrAdmin.d.ts.map +0 -1
  80. package/dist/client.d.ts +0 -32
  81. package/dist/client.d.ts.map +0 -1
  82. package/dist/client.js +0 -65
  83. package/dist/types.d.ts +0 -103
  84. package/dist/types.d.ts.map +0 -1
  85. package/dist/types.js +0 -1
  86. package/dist/useVertebrControls.d.ts.map +0 -1
  87. /package/dist/{EnvSelect.js → components/env/EnvSelect.js} +0 -0
  88. /package/dist/{MetricsBoard.js → components/metrics/MetricsBoard.js} +0 -0
@@ -6,6 +6,8 @@ export type VertebrAdminProps = {
6
6
  envStorageKey?: string;
7
7
  /** localStorage key for the access token */
8
8
  tokenStorageKey?: string;
9
+ /** localStorage key for light/dark theme preference */
10
+ themeStorageKey?: string;
9
11
  /** Optional host-provided token getter (Firebase ID token, etc.) */
10
12
  getAccessToken?: () => string | null | undefined | Promise<string | null | undefined>;
11
13
  /** Signed-in identity shown in the dashboard. */
@@ -13,5 +15,5 @@ export type VertebrAdminProps = {
13
15
  /** Host-provided sign-out action. */
14
16
  onSignOut?: () => void | Promise<void>;
15
17
  };
16
- export declare function VertebrAdmin({ apiBase, title, envStorageKey, tokenStorageKey, getAccessToken, authUserLabel, onSignOut, }: VertebrAdminProps): import("react").JSX.Element;
18
+ export declare function VertebrAdmin({ apiBase, title, envStorageKey, tokenStorageKey, themeStorageKey, getAccessToken, authUserLabel, onSignOut, }: VertebrAdminProps): import("react").JSX.Element;
17
19
  //# sourceMappingURL=VertebrAdmin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"AAkBA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,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,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtF,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,CAAC;AAiCF,wBAAgB,YAAY,CAAC,EAC3B,OAAsB,EACtB,KAAuB,EACvB,aAA6B,EAC7B,eAAiC,EACjC,eAAiC,EACjC,cAAc,EACd,aAAa,EACb,SAAS,GACV,EAAE,iBAAiB,+BAolBnB"}
@@ -1,10 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useState } from "react";
3
- import { createVertebrClient } from "./client";
4
- import { ConfigForm } from "./ConfigForm";
5
- import { EnvSelect } from "./EnvSelect";
6
- import { MetricsBoard } from "./MetricsBoard";
7
- import { ResourcePanel } from "./ResourceTable";
3
+ import { createVertebrClient, } from "@vertebr/client";
4
+ import { ConfigForm } from "../config/ConfigForm";
5
+ import { EnvSelect } from "../env/EnvSelect";
6
+ import { GalleryBoard } from "../gallery/GalleryBoard";
7
+ import { MetricsBoard } from "../metrics/MetricsBoard";
8
+ import { ResourcePanel } from "../resources/ResourceTable";
9
+ import { ThemeToggle } from "../theme/ThemeToggle";
10
+ import { useVertebrTheme } from "../../hooks/useVertebrTheme";
8
11
  function readStored(key) {
9
12
  try {
10
13
  return localStorage.getItem(key);
@@ -29,7 +32,8 @@ function clearStored(key) {
29
32
  // ignore
30
33
  }
31
34
  }
32
- export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane", envStorageKey = "vertebr:env", tokenStorageKey = "vertebr:token", getAccessToken, authUserLabel, onSignOut, }) {
35
+ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane", envStorageKey = "vertebr:env", tokenStorageKey = "vertebr:token", themeStorageKey = "vertebr:theme", getAccessToken, authUserLabel, onSignOut, }) {
36
+ const { theme, setTheme } = useVertebrTheme(themeStorageKey);
33
37
  const [environment, setEnvironment] = useState(() => readStored(envStorageKey) ?? "");
34
38
  const [token, setToken] = useState(() => readStored(tokenStorageKey) ?? "");
35
39
  const [tokenDraft, setTokenDraft] = useState(token);
@@ -46,6 +50,8 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
46
50
  const [configData, setConfigData] = useState(null);
47
51
  const [metricsData, setMetricsData] = useState(null);
48
52
  const [metricsLoading, setMetricsLoading] = useState(false);
53
+ const [galleryData, setGalleryData] = useState(null);
54
+ const [galleryLoading, setGalleryLoading] = useState(false);
49
55
  const [error, setError] = useState(null);
50
56
  const [loading, setLoading] = useState(true);
51
57
  useEffect(() => {
@@ -89,6 +95,8 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
89
95
  setSelection((current) => {
90
96
  if (current?.kind === "overview")
91
97
  return current;
98
+ if (current?.kind === "gallery" && next.gallery)
99
+ return current;
92
100
  if (current?.kind === "resource") {
93
101
  if (next.resources.some((r) => r.id === current.id))
94
102
  return current;
@@ -102,6 +110,8 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
102
110
  const firstResource = next.resources[0];
103
111
  if (firstResource)
104
112
  return { kind: "resource", id: firstResource.id };
113
+ if (next.gallery)
114
+ return { kind: "gallery" };
105
115
  const firstConfig = next.configs[0];
106
116
  if (firstConfig)
107
117
  return { kind: "config", id: firstConfig.id };
@@ -137,6 +147,17 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
137
147
  setMetricsData(data);
138
148
  setResourceData(null);
139
149
  setConfigData(null);
150
+ setGalleryData(null);
151
+ }
152
+ }
153
+ else if (selection.kind === "gallery") {
154
+ setGalleryLoading(true);
155
+ const data = await client.getGallery();
156
+ if (!cancelled) {
157
+ setGalleryData(data);
158
+ setResourceData(null);
159
+ setConfigData(null);
160
+ setMetricsData(null);
140
161
  }
141
162
  }
142
163
  else if (selection.kind === "resource") {
@@ -145,6 +166,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
145
166
  setResourceData(data);
146
167
  setConfigData(null);
147
168
  setMetricsData(null);
169
+ setGalleryData(null);
148
170
  }
149
171
  }
150
172
  else {
@@ -153,6 +175,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
153
175
  setConfigData(data);
154
176
  setResourceData(null);
155
177
  setMetricsData(null);
178
+ setGalleryData(null);
156
179
  }
157
180
  }
158
181
  }
@@ -162,8 +185,10 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
162
185
  }
163
186
  }
164
187
  finally {
165
- if (!cancelled)
188
+ if (!cancelled) {
166
189
  setMetricsLoading(false);
190
+ setGalleryLoading(false);
191
+ }
167
192
  }
168
193
  })();
169
194
  return () => {
@@ -186,9 +211,23 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
186
211
  setMetricsLoading(false);
187
212
  }
188
213
  }, [client]);
214
+ const refreshGallery = useCallback(async () => {
215
+ setGalleryLoading(true);
216
+ try {
217
+ const data = await client.getGallery();
218
+ setGalleryData(data);
219
+ }
220
+ finally {
221
+ setGalleryLoading(false);
222
+ }
223
+ }, [client]);
189
224
  const envList = catalog?.environments ?? bootstrap?.environments ?? [];
190
225
  const activeEnv = envList.find((env) => env.id === environment);
191
226
  const authRequired = Boolean(activeEnv?.auth?.required);
227
+ const activeConfig = selection?.kind === "config"
228
+ ? catalog?.configs.find((c) => c.id === selection.id)
229
+ : undefined;
230
+ const isPageSelection = activeConfig?.section === "page";
192
231
  const switchEnvironment = (nextEnv) => {
193
232
  if (nextEnv === environment)
194
233
  return;
@@ -202,6 +241,7 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
202
241
  setEnvironment(nextEnv);
203
242
  setResourceData(null);
204
243
  setConfigData(null);
244
+ setGalleryData(null);
205
245
  };
206
246
  const saveToken = () => {
207
247
  const next = tokenDraft.trim();
@@ -224,38 +264,82 @@ export function VertebrAdmin({ apiBase = "/admin/api", title = "Control plane",
224
264
  }
225
265
  const activeLabel = selection?.kind === "overview"
226
266
  ? (catalog?.metrics?.label ?? "Overview")
227
- : selection?.kind === "resource"
228
- ? catalog?.resources.find((r) => r.id === selection.id)?.label
229
- : selection?.kind === "config"
230
- ? catalog?.configs.find((c) => c.id === selection.id)?.label
231
- : null;
232
- return (_jsxs("div", { className: "vertebr-root", "data-env": environment, children: [_jsxs("aside", { className: "vertebr-sidebar", children: [_jsxs("div", { className: "vertebr-sidebar-top", children: [_jsxs("div", { className: "vertebr-brand", children: [_jsx("strong", { children: bootstrap?.brand ?? catalog?.brand ?? "Vertebr" }), _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: switchEnvironment })] })) : null, _jsxs("nav", { className: "vertebr-nav", children: [catalog?.metrics ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Analytics" }), _jsx("button", { type: "button", "data-active": selection?.kind === "overview", onClick: () => setSelection({ kind: "overview" }), children: catalog.metrics.label })] })) : null, catalog && catalog.resources.length > 0 ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Resources" }), catalog.resources.map((resource) => (_jsx("button", { type: "button", "data-active": selection?.kind === "resource" &&
233
- selection.id === resource.id, onClick: () => setSelection({ kind: "resource", id: resource.id }), children: resource.label }, resource.id)))] })) : null, catalog && catalog.configs.length > 0 ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Config" }), catalog.configs.map((config) => (_jsx("button", { type: "button", "data-active": selection?.kind === "config" && selection.id === config.id, onClick: () => setSelection({ kind: "config", id: config.id }), children: config.label }, config.id)))] })) : null] })] }), _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
267
+ : selection?.kind === "gallery"
268
+ ? (catalog?.gallery?.label ?? "Gallery")
269
+ : selection?.kind === "resource"
270
+ ? catalog?.resources.find((r) => r.id === selection.id)?.label
271
+ : selection?.kind === "config"
272
+ ? catalog?.configs.find((c) => c.id === selection.id)?.label
273
+ : null;
274
+ return (_jsxs("div", { className: "vertebr-root", "data-env": environment, children: [_jsxs("aside", { className: "vertebr-sidebar", children: [_jsxs("div", { className: "vertebr-sidebar-top", children: [_jsxs("div", { className: "vertebr-brand", children: [_jsx("strong", { children: bootstrap?.brand ?? catalog?.brand ?? "Vertebr" }), _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: switchEnvironment })] })) : null, _jsxs("nav", { className: "vertebr-nav", children: [catalog?.metrics ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Analytics" }), _jsx("button", { type: "button", "data-active": selection?.kind === "overview", onClick: () => setSelection({ kind: "overview" }), children: catalog.metrics.label })] })) : null, catalog &&
275
+ (catalog.resources.length > 0 || catalog.gallery) ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Resources" }), catalog.resources.map((resource) => (_jsx("button", { type: "button", "data-active": selection?.kind === "resource" &&
276
+ selection.id === resource.id, onClick: () => setSelection({ kind: "resource", id: resource.id }), children: resource.label }, resource.id))), catalog.gallery ? (_jsx("button", { type: "button", "data-active": selection?.kind === "gallery", onClick: () => setSelection({ kind: "gallery" }), children: catalog.gallery.label })) : null] })) : null, catalog &&
277
+ catalog.configs.some((c) => (c.section ?? "config") !== "page") ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Config" }), catalog.configs
278
+ .filter((c) => (c.section ?? "config") !== "page")
279
+ .map((config) => (_jsx("button", { type: "button", "data-active": selection?.kind === "config" &&
280
+ selection.id === config.id, onClick: () => setSelection({ kind: "config", id: config.id }), children: config.label }, config.id)))] })) : null, catalog &&
281
+ catalog.configs.some((c) => c.section === "page") ? (_jsxs("div", { className: "vertebr-nav-group", children: [_jsx("div", { className: "vertebr-nav-label", children: "Pages" }), catalog.configs
282
+ .filter((c) => c.section === "page")
283
+ .map((config) => (_jsx("button", { type: "button", "data-active": selection?.kind === "config" &&
284
+ selection.id === config.id, onClick: () => setSelection({ kind: "config", id: config.id }), children: config.label }, config.id)))] })) : null] })] }), _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
234
285
  ? `This environment requires auth${activeEnv?.auth?.claimKey
235
286
  ? ` (${activeEnv.auth.claimKey})`
236
287
  : ""}.`
237
- : "Optional access token for protected environments." }), _jsx("input", { className: "vertebr-auth-input", type: "password", placeholder: "Bearer token", value: tokenDraft, onChange: (event) => setTokenDraft(event.target.value), autoComplete: "off" }), _jsxs("div", { className: "vertebr-auth-actions", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", onClick: saveToken, children: "Use token" }), token ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: clearToken, children: "Clear" })) : null] })] })) : null] })] }), _jsxs("main", { className: "vertebr-main", children: [_jsxs("header", { className: "vertebr-header", children: [_jsxs("div", { children: [_jsxs("div", { className: "vertebr-heading-row", children: [_jsx("h1", { children: activeLabel ?? (catalog ? "Dashboard" : "Sign in") }), activeEnv ? (_jsx("span", { className: "vertebr-env-badge", "data-tone": activeEnv.tone ?? "neutral", children: activeEnv.label })) : null] }), _jsx("p", { children: !catalog && authRequired
288
+ : "Optional access token for protected environments." }), _jsx("input", { className: "vertebr-auth-input", type: "password", placeholder: "Bearer token", value: tokenDraft, onChange: (event) => setTokenDraft(event.target.value), autoComplete: "off" }), _jsxs("div", { className: "vertebr-auth-actions", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", onClick: saveToken, children: "Use token" }), token ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: clearToken, children: "Clear" })) : null] })] })) : null, _jsx(ThemeToggle, { theme: theme, onChange: setTheme })] })] }), _jsxs("main", { className: "vertebr-main", children: [_jsxs("header", { className: "vertebr-header", children: [_jsxs("div", { children: [_jsxs("div", { className: "vertebr-heading-row", children: [_jsx("h1", { children: activeLabel ?? (catalog ? "Dashboard" : "Sign in") }), activeEnv ? (_jsx("span", { className: "vertebr-env-badge", "data-tone": activeEnv.tone ?? "neutral", children: activeEnv.label })) : null] }), _jsx("p", { children: !catalog && authRequired
238
289
  ? "Provide an access token to load this environment."
239
290
  : selection?.kind === "overview"
240
291
  ? "Live metrics for the selected environment."
241
- : selection?.kind === "resource"
242
- ? "Manage records scoped to the selected environment."
243
- : selection?.kind === "config"
244
- ? "Owner-managed config for this environment."
245
- : "Select a panel." })] }), error ? (_jsx("span", { className: "vertebr-status", "data-tone": "danger", children: error })) : null] }), _jsx("div", { className: "vertebr-content", children: catalog ? (_jsxs("section", { className: "vertebr-panel", children: [selection?.kind === "overview" ? (_jsx(MetricsBoard, { label: metricsData?.label ?? catalog.metrics?.label, metrics: metricsData?.metrics ?? [], loading: metricsLoading, onRefresh: () => void refreshMetrics() })) : null, selection?.kind === "resource" && resourceData ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Records" }), _jsx("span", { className: "vertebr-count", children: resourceData.items.length })] }), _jsx(ResourcePanel, { schema: resourceData.schema, items: resourceData.items, permissions: resourceData.permissions, actions: resourceData.actions, onUpdate: resourceData.permissions.update
246
- ? async (rowId, value) => {
247
- await client.updateResource(resourceData.id, rowId, value);
248
- await refreshResource();
249
- }
250
- : undefined, onDelete: resourceData.permissions.delete
251
- ? async (rowId) => {
252
- await client.deleteResource(resourceData.id, rowId);
253
- await refreshResource();
254
- }
255
- : undefined, onAction: async (rowId, actionId) => {
256
- await client.runResourceAction(resourceData.id, rowId, actionId);
257
- await refreshResource();
258
- } })] })) : null, selection?.kind === "config" && configData ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Settings" }), _jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })] }), _jsx(ConfigForm, { schema: configData.schema, value: configData.value, onSave: async (next) => {
292
+ : selection?.kind === "gallery"
293
+ ? "Browse and upload shared media assets."
294
+ : selection?.kind === "resource"
295
+ ? "Manage records scoped to the selected environment."
296
+ : selection?.kind === "config"
297
+ ? isPageSelection
298
+ ? "Edit content for this page."
299
+ : "Owner-managed config for this environment."
300
+ : "Select a panel." })] }), error ? (_jsx("span", { className: "vertebr-status", "data-tone": "danger", children: error })) : null] }), _jsx("div", { className: "vertebr-content", children: catalog ? (_jsxs("section", { className: "vertebr-panel", children: [selection?.kind === "overview" ? (_jsx(MetricsBoard, { label: metricsData?.label ?? catalog.metrics?.label, metrics: metricsData?.metrics ?? [], loading: metricsLoading, onRefresh: () => void refreshMetrics() })) : null, selection?.kind === "gallery" && galleryData ? (_jsx(GalleryBoard, { label: galleryData.label, items: galleryData.items, accept: galleryData.accept, maxBytes: galleryData.maxBytes, canDelete: galleryData.canDelete, loading: galleryLoading, onRefresh: () => void refreshGallery(), onUpload: async (files) => {
301
+ if (files.length === 1 && files[0]) {
302
+ await client.uploadGalleryFile(files[0]);
303
+ }
304
+ else {
305
+ await client.uploadGalleryFiles(files);
306
+ }
307
+ await refreshGallery();
308
+ }, onDelete: galleryData.canDelete
309
+ ? async (id) => {
310
+ await client.deleteGalleryItem(id);
311
+ await refreshGallery();
312
+ }
313
+ : undefined })) : null, selection?.kind === "resource" && resourceData ? (_jsx(ResourcePanel, { schema: resourceData.schema, items: resourceData.items, permissions: resourceData.permissions, actions: resourceData.actions, uploads: resourceData.uploads, storageKey: resourceData.id, onUpdate: resourceData.permissions.update
314
+ ? async (rowId, value) => {
315
+ await client.updateResource(resourceData.id, rowId, value);
316
+ await refreshResource();
317
+ }
318
+ : undefined, onCreate: resourceData.permissions.create
319
+ ? async (value) => {
320
+ await client.createResource(resourceData.id, value);
321
+ await refreshResource();
322
+ }
323
+ : undefined, onDelete: resourceData.permissions.delete
324
+ ? async (rowId) => {
325
+ await client.deleteResource(resourceData.id, rowId);
326
+ await refreshResource();
327
+ }
328
+ : undefined, onAction: async (rowId, actionId) => {
329
+ await client.runResourceAction(resourceData.id, rowId, actionId);
330
+ await refreshResource();
331
+ }, onUpload: resourceData.uploads
332
+ ? async (field, files) => {
333
+ if (files.length === 1 && files[0]) {
334
+ const result = await client.uploadResourceFile(resourceData.id, field, files[0]);
335
+ return [
336
+ { url: result.url, name: result.name },
337
+ ];
338
+ }
339
+ const result = await client.uploadResourceFiles(resourceData.id, field, files);
340
+ return result.files;
341
+ }
342
+ : undefined })) : null, selection?.kind === "config" && configData ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: isPageSelection ? "Page content" : "Settings" }), _jsxs("span", { className: "vertebr-count", children: [Object.keys(configData.schema).length, " fields"] })] }), _jsx(ConfigForm, { schema: configData.schema, value: configData.value, onSave: async (next) => {
259
343
  const saved = await client.setConfig(configData.id, next);
260
344
  setConfigData({
261
345
  ...configData,
@@ -0,0 +1,19 @@
1
+ import type { FieldSchema, ResourceUploadMeta } from "@vertebr/client";
2
+ export type ConfigFormVariant = "config" | "record";
3
+ type ConfigFormProps = {
4
+ schema: Record<string, FieldSchema>;
5
+ value: Record<string, unknown>;
6
+ onSave: (value: Record<string, unknown>) => Promise<void>;
7
+ /** `record` uses a dense product-style editor. */
8
+ variant?: ConfigFormVariant;
9
+ /** Field-keyed upload capabilities from the host resource. */
10
+ uploads?: Record<string, ResourceUploadMeta>;
11
+ /** Upload files for a field; return public URL(s). */
12
+ onUpload?: (field: string, files: File[]) => Promise<Array<{
13
+ url: string;
14
+ name?: string;
15
+ }>>;
16
+ };
17
+ export declare function ConfigForm({ schema, value, onSave, variant, uploads, onUpload, }: ConfigFormProps): import("react").JSX.Element;
18
+ export {};
19
+ //# sourceMappingURL=ConfigForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfigForm.d.ts","sourceRoot":"","sources":["../../../src/components/config/ConfigForm.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAGvE,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEpD,KAAK,eAAe,GAAG;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,kDAAkD;IAClD,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,sDAAsD;IACtD,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,EAAE,KACV,OAAO,CAAC,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACrD,CAAC;AAqNF,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,KAAK,EACL,MAAM,EACN,OAAkB,EAClB,OAAO,EACP,QAAQ,GACT,EAAE,eAAe,+BAyUjB"}
@@ -0,0 +1,281 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { useForm, Controller, useWatch } from "react-hook-form";
4
+ import { z } from "zod";
5
+ import { zodResolver } from "@hookform/resolvers/zod";
6
+ import * as Label from "@radix-ui/react-label";
7
+ import * as Switch from "@radix-ui/react-switch";
8
+ import { ImageUploadControl } from "../resources/ImageUploadControl";
9
+ const RECORD_GROUP_ORDER = [
10
+ "Details",
11
+ "Content",
12
+ "Pricing",
13
+ "Inventory",
14
+ "Media",
15
+ "Status",
16
+ ];
17
+ function buildZodSchema(schema) {
18
+ const shape = {};
19
+ for (const [key, field] of Object.entries(schema)) {
20
+ let zodField;
21
+ switch (field.type) {
22
+ case "number":
23
+ zodField = z.number();
24
+ if (field.min != null)
25
+ zodField = zodField.min(field.min);
26
+ if (field.max != null)
27
+ zodField = zodField.max(field.max);
28
+ break;
29
+ case "boolean":
30
+ zodField = z.boolean();
31
+ break;
32
+ default:
33
+ zodField = z.string();
34
+ }
35
+ if (!field.required) {
36
+ zodField = zodField.optional();
37
+ }
38
+ shape[key] = zodField;
39
+ }
40
+ return z.object(shape);
41
+ }
42
+ function humanizeKey(key) {
43
+ return key
44
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
45
+ .replace(/[_-]+/g, " ")
46
+ .trim();
47
+ }
48
+ function escapeRegExp(value) {
49
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
50
+ }
51
+ function looksLikeUrl(value) {
52
+ return /^https?:\/\//i.test(value) || value.startsWith("//");
53
+ }
54
+ function isImageField(key, field) {
55
+ const hay = `${key} ${field?.label ?? ""}`;
56
+ return /(image|img|photo|avatar|thumbnail|thumb|cover)/i.test(hay);
57
+ }
58
+ function isStockField(key, field) {
59
+ const hay = `${key} ${field?.label ?? ""}`;
60
+ return /(stock|inventory|qty|quantity)/i.test(hay);
61
+ }
62
+ function spanFor(key, field, variant) {
63
+ if (field.type === "boolean")
64
+ return "full";
65
+ if (field.type === "richtext")
66
+ return "full";
67
+ if (variant === "record" && isImageField(key, field))
68
+ return "full";
69
+ return undefined;
70
+ }
71
+ function inferConfigGroup(key, field) {
72
+ if (field.group?.trim())
73
+ return field.group.trim();
74
+ const source = field.label?.trim() || humanizeKey(key);
75
+ const words = source.split(/\s+/).filter(Boolean);
76
+ if (words.length >= 2)
77
+ return words[0];
78
+ return "General";
79
+ }
80
+ function inferRecordGroup(key, field) {
81
+ if (field.group?.trim())
82
+ return field.group.trim();
83
+ const k = key.toLowerCase();
84
+ const label = (field.label ?? "").toLowerCase();
85
+ const hay = `${k} ${label}`;
86
+ if (/(image|img|photo|avatar|thumbnail|thumb|cover|media_url)/i.test(hay)) {
87
+ return "Media";
88
+ }
89
+ if (/(price|cost|cents|amount|msrp)/i.test(hay)) {
90
+ return "Pricing";
91
+ }
92
+ if (/(stock|inventory|qty|quantity)/i.test(hay)) {
93
+ return "Inventory";
94
+ }
95
+ if (field.type === "boolean" ||
96
+ /(^|_)(active|enabled|published|status|visible)(_|$)/i.test(k) ||
97
+ /^(active|enabled|published|status|visible)$/i.test(label)) {
98
+ return "Status";
99
+ }
100
+ if (field.type === "richtext" ||
101
+ /(description|body|content|summary|bio)/i.test(hay)) {
102
+ return "Content";
103
+ }
104
+ return "Details";
105
+ }
106
+ function displayLabel(key, field, group) {
107
+ const label = field.label?.trim() || humanizeKey(key);
108
+ const stripped = label.replace(new RegExp(`^${escapeRegExp(group)}\\s+`, "i"), "");
109
+ if (/^inventory$/i.test(group)) {
110
+ const stock = label.replace(/^stock\s+/i, "").trim();
111
+ if (stock && stock !== label)
112
+ return stock;
113
+ }
114
+ if (/^pricing$/i.test(group) && /cents/i.test(label)) {
115
+ return label.replace(/\s*\(cents\)/i, "").trim() || label;
116
+ }
117
+ return stripped || label;
118
+ }
119
+ function sortGroups(groups, variant) {
120
+ if (variant !== "record")
121
+ return groups;
122
+ const rank = (id) => {
123
+ const index = RECORD_GROUP_ORDER.indexOf(id);
124
+ return index === -1 ? RECORD_GROUP_ORDER.length + 1 : index;
125
+ };
126
+ return [...groups].sort((a, b) => rank(a.id) - rank(b.id));
127
+ }
128
+ function groupFields(schema, variant) {
129
+ const order = [];
130
+ const map = new Map();
131
+ const explicit = new Set();
132
+ for (const entry of Object.entries(schema)) {
133
+ const [key, field] = entry;
134
+ const group = variant === "record"
135
+ ? inferRecordGroup(key, field)
136
+ : inferConfigGroup(key, field);
137
+ if (field.group?.trim())
138
+ explicit.add(group);
139
+ if (!map.has(group)) {
140
+ map.set(group, []);
141
+ order.push(group);
142
+ }
143
+ map.get(group).push(entry);
144
+ }
145
+ if (variant === "config") {
146
+ for (const [group, fields] of [...map.entries()]) {
147
+ if (fields.length > 1 || explicit.has(group) || group === "General") {
148
+ continue;
149
+ }
150
+ if (!map.has("General")) {
151
+ map.set("General", []);
152
+ order.unshift("General");
153
+ }
154
+ map.get("General").push(...fields);
155
+ map.delete(group);
156
+ const idx = order.indexOf(group);
157
+ if (idx >= 0)
158
+ order.splice(idx, 1);
159
+ }
160
+ }
161
+ const groups = order
162
+ .filter((id) => map.has(id))
163
+ .map((id) => ({
164
+ id,
165
+ label: id,
166
+ fields: map.get(id),
167
+ }));
168
+ return sortGroups(groups, variant);
169
+ }
170
+ function findPrimaryImageKey(schema, values) {
171
+ for (const [key, field] of Object.entries(schema)) {
172
+ if (!isImageField(key, field))
173
+ continue;
174
+ const value = values?.[key];
175
+ if (typeof value === "string" && looksLikeUrl(value))
176
+ return key;
177
+ }
178
+ for (const [key, field] of Object.entries(schema)) {
179
+ if (isImageField(key, field))
180
+ return key;
181
+ }
182
+ return null;
183
+ }
184
+ export function ConfigForm({ schema, value, onSave, variant = "config", uploads, onUpload, }) {
185
+ const zodSchema = useMemo(() => buildZodSchema(schema), [schema]);
186
+ const groups = useMemo(() => groupFields(schema, variant), [schema, variant]);
187
+ const [status, setStatus] = useState(null);
188
+ const [tone, setTone] = useState();
189
+ const form = useForm({
190
+ resolver: zodResolver(zodSchema),
191
+ defaultValues: value,
192
+ });
193
+ const watched = useWatch({ control: form.control });
194
+ useEffect(() => {
195
+ form.reset(value);
196
+ }, [value, form]);
197
+ const onSubmit = form.handleSubmit(async (data) => {
198
+ setStatus("Saving…");
199
+ setTone(undefined);
200
+ try {
201
+ await onSave(data);
202
+ setStatus("Saved");
203
+ setTone("ok");
204
+ }
205
+ catch (error) {
206
+ setStatus(error instanceof Error ? error.message : "Save failed");
207
+ setTone("danger");
208
+ }
209
+ });
210
+ const isDirty = form.formState.isDirty;
211
+ useEffect(() => {
212
+ const onKeyDown = (event) => {
213
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") {
214
+ event.preventDefault();
215
+ void onSubmit();
216
+ }
217
+ };
218
+ window.addEventListener("keydown", onKeyDown);
219
+ return () => window.removeEventListener("keydown", onKeyDown);
220
+ }, [onSubmit]);
221
+ const renderField = (key, field, groupLabel, options) => {
222
+ const label = displayLabel(key, field, groupLabel);
223
+ const showInlinePreview = !options?.hideImagePreview &&
224
+ isImageField(key, field) &&
225
+ typeof watched?.[key] === "string" &&
226
+ looksLikeUrl(String(watched[key]));
227
+ if (field.type === "boolean") {
228
+ return (_jsxs("div", { className: "vertebr-switch-row", children: [_jsx(Label.Root, { htmlFor: key, children: label }), _jsx(Controller, { control: form.control, name: key, render: ({ field: controllerField }) => (_jsx(Switch.Root, { className: "vertebr-switch", id: key, checked: Boolean(controllerField.value), onCheckedChange: controllerField.onChange, children: _jsx(Switch.Thumb, { className: "vertebr-switch-thumb" }) })) })] }, key));
229
+ }
230
+ return (_jsxs("div", { className: "vertebr-field", "data-span": spanFor(key, field, variant), "data-kind": isImageField(key, field) ? "image" : undefined, children: [_jsx(Label.Root, { htmlFor: key, children: label }), showInlinePreview ? (_jsxs("div", { className: "vertebr-field-media", children: [_jsx("img", { src: String(watched[key]), alt: "" }), _jsx("input", { id: key, type: "text", readOnly: field.readOnly, ...form.register(key) })] })) : field.type === "richtext" ? (_jsx("textarea", { id: key, rows: 3, readOnly: field.readOnly, ...form.register(key) })) : field.type === "number" ? (_jsx(Controller, { control: form.control, name: key, render: ({ field: controllerField }) => (_jsx("input", { id: key, type: "number", readOnly: field.readOnly, value: typeof controllerField.value === "number" &&
231
+ Number.isFinite(controllerField.value)
232
+ ? controllerField.value
233
+ : "", onBlur: controllerField.onBlur, onChange: (event) => {
234
+ const next = event.target.value;
235
+ controllerField.onChange(next === "" ? undefined : Number(next));
236
+ }, name: controllerField.name, ref: controllerField.ref })) })) : (_jsx("input", { id: key, type: "text", readOnly: field.readOnly, ...form.register(key) })), !options?.hideUpload &&
237
+ uploads?.[key] &&
238
+ onUpload &&
239
+ !field.readOnly ? (_jsx(ImageUploadControl, { meta: uploads[key], onUpload: async (files) => {
240
+ const [first] = await onUpload(key, files);
241
+ if (first?.url) {
242
+ form.setValue(key, first.url, {
243
+ shouldDirty: true,
244
+ shouldTouch: true,
245
+ });
246
+ }
247
+ } })) : null] }, key));
248
+ };
249
+ const renderActions = () => (_jsxs("div", { className: "vertebr-actions", children: [_jsx("button", { className: "vertebr-btn vertebr-btn-primary", type: "submit", disabled: form.formState.isSubmitting || !isDirty, children: form.formState.isSubmitting ? "Saving…" : "Save changes" }), isDirty ? (_jsx("button", { className: "vertebr-btn", type: "button", onClick: () => {
250
+ form.reset(value);
251
+ setStatus(null);
252
+ setTone(undefined);
253
+ }, children: "Discard" })) : null, isDirty ? (_jsx("span", { className: "vertebr-dirty", children: "Unsaved" })) : status ? (_jsx("span", { className: "vertebr-status", "data-tone": tone, children: status })) : null, _jsx("span", { className: "vertebr-actions-spacer" }), _jsx("span", { className: "vertebr-kbd", children: "\u2318S" })] }));
254
+ if (variant === "record") {
255
+ const imageKey = findPrimaryImageKey(schema, watched);
256
+ const imageValue = imageKey && typeof watched?.[imageKey] === "string"
257
+ ? String(watched[imageKey])
258
+ : "";
259
+ const asideGroups = groups.filter((g) => /^(Media|Status)$/i.test(g.id));
260
+ const mainGroups = groups.filter((g) => !/^(Media|Status)$/i.test(g.id));
261
+ return (_jsxs("form", { className: "vertebr-form", "data-variant": "record", onSubmit: onSubmit, children: [_jsx("div", { className: "vertebr-form-scroll", children: _jsxs("div", { className: "vertebr-record-layout", children: [_jsxs("aside", { className: "vertebr-record-aside", children: [_jsx("div", { className: "vertebr-record-preview", children: imageValue && looksLikeUrl(imageValue) ? (_jsx("img", { src: imageValue, alt: "" })) : (_jsx("div", { className: "vertebr-record-preview-empty", children: "No image" })) }), imageKey && uploads?.[imageKey] && onUpload ? (_jsx(ImageUploadControl, { meta: uploads[imageKey], label: "Drop a new image or browse", onUpload: async (files) => {
262
+ const [first] = await onUpload(imageKey, files);
263
+ if (first?.url) {
264
+ form.setValue(imageKey, first.url, {
265
+ shouldDirty: true,
266
+ shouldTouch: true,
267
+ });
268
+ }
269
+ } })) : null, asideGroups.map((group) => (_jsxs("div", { className: "vertebr-record-block", "data-layout": /^inventory$/i.test(group.id) ? "chips" : undefined, children: [_jsx("div", { className: "vertebr-record-block-label", children: group.label }), _jsx("div", { className: "vertebr-form-fields", "data-layout": isStockField(group.fields[0]?.[0] ?? "", group.fields[0]?.[1])
270
+ ? "chips"
271
+ : undefined, children: group.fields.map(([key, field]) => renderField(key, field, group.label, {
272
+ hideImagePreview: true,
273
+ hideUpload: key === imageKey,
274
+ })) })] }, group.id)))] }), _jsx("div", { className: "vertebr-record-main", children: mainGroups.map((group) => {
275
+ const inventory = /^inventory$/i.test(group.id);
276
+ return (_jsxs("div", { className: "vertebr-record-block", "data-layout": inventory ? "chips" : undefined, children: [_jsx("div", { className: "vertebr-record-block-label", children: group.label }), _jsx("div", { className: "vertebr-form-fields", "data-layout": inventory ? "chips" : undefined, children: group.fields.map(([key, field]) => renderField(key, field, group.label)) })] }, group.id));
277
+ }) })] }) }), renderActions()] }));
278
+ }
279
+ const singleGroup = groups.length === 1;
280
+ return (_jsxs("form", { className: "vertebr-form", "data-variant": "config", onSubmit: onSubmit, children: [_jsx("div", { className: "vertebr-form-scroll", children: groups.map((group) => (_jsxs("div", { className: "vertebr-form-section", "data-plain": singleGroup ? "true" : undefined, children: [!singleGroup ? (_jsxs("div", { className: "vertebr-form-section-head", children: [_jsx("h3", { children: group.label }), _jsx("span", { className: "vertebr-form-section-count", children: group.fields.length })] })) : null, _jsx("div", { className: "vertebr-form-fields", children: group.fields.map(([key, field]) => renderField(key, field, group.label)) })] }, group.id))) }), renderActions()] }));
281
+ }
@@ -1,4 +1,4 @@
1
- import type { AdminEnvironment } from "./types";
1
+ import type { AdminEnvironment } from "@vertebr/client";
2
2
  type EnvSelectProps = {
3
3
  environments: AdminEnvironment[];
4
4
  value: string;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnvSelect.d.ts","sourceRoot":"","sources":["../../../src/components/env/EnvSelect.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,KAAK,cAAc,GAAG;IACpB,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC,CAAC;AAEF,wBAAgB,SAAS,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,cAAc,+BAqD1E"}
@@ -0,0 +1,14 @@
1
+ import type { GalleryItem } from "@vertebr/client";
2
+ export type GalleryBoardProps = {
3
+ label?: string;
4
+ items: GalleryItem[];
5
+ accept: string[];
6
+ maxBytes: number;
7
+ canDelete?: boolean;
8
+ loading?: boolean;
9
+ onRefresh?: () => void;
10
+ onUpload: (files: File[]) => Promise<void>;
11
+ onDelete?: (id: string) => Promise<void>;
12
+ };
13
+ export declare function GalleryBoard({ label, items, accept, maxBytes, canDelete, loading, onRefresh, onUpload, onDelete, }: GalleryBoardProps): import("react").JSX.Element;
14
+ //# sourceMappingURL=GalleryBoard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GalleryBoard.d.ts","sourceRoot":"","sources":["../../../src/components/gallery/GalleryBoard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAsB,MAAM,iBAAiB,CAAC;AAGvE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,KAAiB,EACjB,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,GACT,EAAE,iBAAiB,+BAiFnB"}