@vertebr/react 0.11.0 → 0.12.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.
@@ -1 +1 @@
1
- {"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AdminContent.d.ts","sourceRoot":"","sources":["../../../src/components/admin/AdminContent.tsx"],"names":[],"mappings":"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"}
@@ -85,11 +85,8 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
85
85
  : "Unable to load catalog." }) }) }));
86
86
  }
87
87
  return (_jsx("div", { className: "vertebr-content", children: _jsxs("section", { className: "vertebr-panel", children: [selection?.kind === "overview" ? (_jsx(MetricsBoard, { label: metricsData?.label ?? catalog.metrics?.label, windowLabel: metricsData?.windowLabel, compareLabel: metricsData?.compareLabel, metrics: metricsData?.metrics ?? [], charts: metricsData?.charts ?? [], range: metricsRange, onRangeChange: onMetricsRangeChange, loading: metricsLoading, onRefresh: () => void onRefreshMetrics() })) : null, selection?.kind === "audit" ? (_jsx(AuditBoard, { client: client, label: catalog.audit?.label })) : null, selection?.kind === "sync" ? (_jsx(AdminSyncPage, { client: client, catalog: catalog })) : 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 onRefreshGallery(), onUpload: async (files) => {
88
- if (files.length === 1 && files[0]) {
89
- await client.uploadGalleryFile(files[0]);
90
- }
91
- else {
92
- await client.uploadGalleryFiles(files);
88
+ for (const file of files) {
89
+ await client.uploadGalleryFile(file);
93
90
  }
94
91
  await onRefreshGallery();
95
92
  }, onDelete: galleryData.canDelete
@@ -192,12 +189,12 @@ export function AdminContent({ client, catalog, selection, onSelectionChange, au
192
189
  }
193
190
  : undefined, onUpload: resourceData.uploads
194
191
  ? async (field, files) => {
195
- if (files.length === 1 && files[0]) {
196
- const result = await client.uploadResourceFile(resourceData.id, field, files[0]);
197
- return [{ url: result.url, name: result.name }];
192
+ const uploaded = [];
193
+ for (const file of files) {
194
+ const result = await client.uploadResourceFile(resourceData.id, field, file);
195
+ uploaded.push({ url: result.url, name: result.name });
198
196
  }
199
- const result = await client.uploadResourceFiles(resourceData.id, field, files);
200
- return result.files;
197
+ return uploaded;
201
198
  }
202
199
  : undefined, ...galleryPicker })) : null, selection?.kind === "addons" ? (_jsx(AddonOverview, { client: client, catalog: catalog, onSelectAddon: (id) => onSelectionChange?.({ kind: "addon", id }) })) : null, selection?.kind === "config" && configData ? (_jsx(ConfigEditor, { client: client, catalog: catalog, configData: configData, isPageSelection: isPageSelection, previewPath: catalog.configs.find((item) => item.id === configData.id)
203
200
  ?.previewPath, relatedAddons: catalog.configs.find((item) => item.id === configData.id)
@@ -1 +1 @@
1
- {"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"AAwCA,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,+BAguBnB"}
1
+ {"version":3,"file":"VertebrAdmin.d.ts","sourceRoot":"","sources":["../../../src/components/admin/VertebrAdmin.tsx"],"names":[],"mappings":"AAwCA,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,+BAkuBnB"}
@@ -380,11 +380,13 @@ export function VertebrAdmin({ apiBase = "/admin/api", basePath: basePathProp, t
380
380
  return data.items;
381
381
  }, [client]);
382
382
  const uploadGallery = useCallback(async (files) => {
383
- if (files.length === 1 && files[0]) {
384
- const { item } = await client.uploadGalleryFile(files[0]);
385
- return [item];
383
+ // Upload one file per request — base64 bulk payloads easily exceed the
384
+ // host HTTP body limit when several product photos are selected.
385
+ const items = [];
386
+ for (const file of files) {
387
+ const { item } = await client.uploadGalleryFile(file);
388
+ items.push(item);
386
389
  }
387
- const { items } = await client.uploadGalleryFiles(files);
388
390
  return items;
389
391
  }, [client]);
390
392
  const galleryPicker = catalog?.gallery
@@ -6,6 +6,7 @@ export type PickerImage = {
6
6
  name: string;
7
7
  source: "record" | "gallery" | "url";
8
8
  };
9
+ type Tab = "record" | "gallery";
9
10
  export type ImagePickerModalProps = {
10
11
  recordImages?: RecordImage[];
11
12
  galleryUpload?: Pick<ResourceUploadMeta, "accept" | "maxBytes">;
@@ -13,6 +14,13 @@ export type ImagePickerModalProps = {
13
14
  onUploadGallery?: (files: File[]) => Promise<GalleryItem[]>;
14
15
  onClose: () => void;
15
16
  onInsert: (images: PickerImage[]) => void;
17
+ /** Dialog heading. @default "Choose images" */
18
+ title?: string;
19
+ /** Primary action label prefix. @default "Use" */
20
+ confirmLabel?: string;
21
+ /** Prefer Files tab when both product photos and gallery exist. */
22
+ defaultTab?: Tab;
16
23
  };
17
- export declare function ImagePickerModal({ recordImages, galleryUpload, onListGallery, onUploadGallery, onClose, onInsert, }: ImagePickerModalProps): import("react").JSX.Element;
24
+ export declare function ImagePickerModal({ recordImages, galleryUpload, onListGallery, onUploadGallery, onClose, onInsert, title, confirmLabel, defaultTab, }: ImagePickerModalProps): import("react").ReactPortal;
25
+ export {};
18
26
  //# sourceMappingURL=ImagePickerModal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ImagePickerModal.d.ts","sourceRoot":"","sources":["../../../src/components/config/ImagePickerModal.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5D,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;CAC3C,CAAC;AAIF,wBAAgB,gBAAgB,CAAC,EAC/B,YAAiB,EACjB,aAAa,EACb,aAAa,EACb,eAAe,EACf,OAAO,EACP,QAAQ,GACT,EAAE,qBAAqB,+BA4PvB"}
1
+ {"version":3,"file":"ImagePickerModal.d.ts","sourceRoot":"","sources":["../../../src/components/config/ImagePickerModal.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;CACtC,CAAC;AAEF,KAAK,GAAG,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEhC,MAAM,MAAM,qBAAqB,GAAG;IAClC,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;IAC7B,aAAa,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,QAAQ,GAAG,UAAU,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7C,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5D,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,IAAI,CAAC;IAC1C,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAC/B,YAAiB,EACjB,aAAa,EACb,aAAa,EACb,eAAe,EACf,OAAO,EACP,QAAQ,EACR,KAAuB,EACvB,YAAoB,EACpB,UAAU,GACX,EAAE,qBAAqB,+BAiQvB"}
@@ -1,9 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from "react";
3
+ import { createPortal } from "react-dom";
3
4
  import { ImageUploadControl } from "../resources/ImageUploadControl";
4
- export function ImagePickerModal({ recordImages = [], galleryUpload, onListGallery, onUploadGallery, onClose, onInsert, }) {
5
+ export function ImagePickerModal({ recordImages = [], galleryUpload, onListGallery, onUploadGallery, onClose, onInsert, title = "Choose images", confirmLabel = "Use", defaultTab, }) {
5
6
  const hasGallery = Boolean(onListGallery);
6
- const [tab, setTab] = useState(recordImages.length ? "record" : "gallery");
7
+ const [tab, setTab] = useState(() => {
8
+ if (defaultTab)
9
+ return defaultTab;
10
+ if (recordImages.length)
11
+ return "record";
12
+ return "gallery";
13
+ });
7
14
  const [gallery, setGallery] = useState([]);
8
15
  const [loading, setLoading] = useState(false);
9
16
  const [error, setError] = useState(null);
@@ -84,11 +91,11 @@ export function ImagePickerModal({ recordImages = [], galleryUpload, onListGalle
84
91
  setSelected((current) => ({ ...current, [image.id]: image }));
85
92
  setUrl("");
86
93
  };
87
- return (_jsx("div", { className: "vertebr-modal-backdrop", role: "presentation", onClick: onClose, children: _jsxs("div", { className: "vertebr-modal", role: "dialog", "aria-modal": "true", "aria-label": "Insert images", onClick: (event) => event.stopPropagation(), children: [_jsxs("header", { className: "vertebr-modal-head", children: [_jsxs("div", { className: "vertebr-modal-heading", children: [_jsx("p", { className: "vertebr-modal-eyebrow", children: "Media" }), _jsx("h2", { children: "Insert images" })] }), _jsx("button", { type: "button", className: "vertebr-btn", onClick: onClose, children: "Close" })] }), _jsxs("div", { className: "vertebr-modal-body vertebr-picker-body", children: [recordImages.length > 0 && hasGallery ? (_jsxs("div", { className: "vertebr-picker-tabs", role: "tablist", children: [_jsxs("button", { type: "button", role: "tab", "aria-selected": tab === "record", "data-active": tab === "record" || undefined, onClick: () => setTab("record"), children: ["Product photos", _jsx("span", { children: recordTiles.length })] }), _jsxs("button", { type: "button", role: "tab", "aria-selected": tab === "gallery", "data-active": tab === "gallery" || undefined, onClick: () => setTab("gallery"), children: ["Gallery", _jsx("span", { children: loading ? "…" : galleryTiles.length })] })] })) : null, tab === "gallery" && canGalleryUpload ? (_jsx(ImageUploadControl, { meta: {
94
+ return createPortal(_jsx("div", { className: "vertebr-modal-backdrop", role: "presentation", onClick: onClose, children: _jsxs("div", { className: "vertebr-modal", role: "dialog", "aria-modal": "true", "aria-label": title, onClick: (event) => event.stopPropagation(), children: [_jsxs("header", { className: "vertebr-modal-head", children: [_jsxs("div", { className: "vertebr-modal-heading", children: [_jsx("p", { className: "vertebr-modal-eyebrow", children: "Media" }), _jsx("h2", { children: title })] }), _jsx("button", { type: "button", className: "vertebr-btn", onClick: onClose, children: "Close" })] }), _jsxs("div", { className: "vertebr-modal-body vertebr-picker-body", children: [recordImages.length > 0 && hasGallery ? (_jsxs("div", { className: "vertebr-picker-tabs", role: "tablist", children: [_jsxs("button", { type: "button", role: "tab", "aria-selected": tab === "record", "data-active": tab === "record" || undefined, onClick: () => setTab("record"), children: ["Product photos", _jsx("span", { children: recordTiles.length })] }), _jsxs("button", { type: "button", role: "tab", "aria-selected": tab === "gallery", "data-active": tab === "gallery" || undefined, onClick: () => setTab("gallery"), children: ["Files", _jsx("span", { children: loading ? "…" : galleryTiles.length })] })] })) : null, tab === "gallery" && canGalleryUpload ? (_jsx(ImageUploadControl, { meta: {
88
95
  field: "gallery",
89
96
  accept: galleryUpload.accept,
90
97
  maxBytes: galleryUpload.maxBytes,
91
- }, multiple: true, label: "Drop images to add them to the gallery", onUpload: async (files) => {
98
+ }, multiple: true, label: "Drop images to add them to Files", onUpload: async (files) => {
92
99
  const items = await onUploadGallery(files);
93
100
  setGallery((current) => [...items, ...current]);
94
101
  setSelected((current) => {
@@ -112,9 +119,9 @@ export function ImagePickerModal({ recordImages = [], galleryUpload, onListGalle
112
119
  } }), _jsx("button", { type: "button", className: "vertebr-btn", disabled: !url.trim(), onClick: addUrl, children: "Add" })] })] }), error ? (_jsx("p", { className: "vertebr-status", "data-tone": "danger", children: error })) : null, loading && tab === "gallery" ? (_jsx("div", { className: "vertebr-empty", children: "Loading gallery\u2026" })) : tiles.length === 0 ? (_jsx("div", { className: "vertebr-empty", children: tab === "record"
113
120
  ? "No product photos on this record yet."
114
121
  : hasGallery
115
- ? "No images in the gallery yet."
116
- : "No gallery is configured. Paste a URL or add product photos." })) : (_jsx("div", { className: "vertebr-picker-grid", children: tiles.map((image) => {
122
+ ? "No images in Files yet."
123
+ : "No Files library is configured. Paste a URL or add product photos." })) : (_jsx("div", { className: "vertebr-picker-grid", children: tiles.map((image) => {
117
124
  const active = Boolean(selected[image.id]);
118
125
  return (_jsxs("button", { type: "button", className: "vertebr-picker-tile", "data-selected": active || undefined, onClick: () => toggle(image), children: [_jsx("span", { className: "vertebr-picker-thumb", children: _jsx("img", { src: image.url, alt: "" }) }), _jsx("span", { className: "vertebr-picker-name", children: image.name })] }, image.id));
119
- }) }))] }), _jsxs("footer", { className: "vertebr-picker-foot", children: [_jsxs("span", { className: "vertebr-picker-count", children: [picked.length, " selected"] }), _jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", disabled: picked.length === 0, onClick: () => onInsert(picked), children: ["Insert ", picked.length || "", picked.length === 1 ? " image" : " images"] })] })] }) }));
126
+ }) }))] }), _jsxs("footer", { className: "vertebr-picker-foot", children: [_jsxs("span", { className: "vertebr-picker-count", children: [picked.length, " selected"] }), _jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-primary", disabled: picked.length === 0, onClick: () => onInsert(picked), children: [confirmLabel, " ", picked.length || "", picked.length === 1 ? " image" : " images"] })] })] }) }), document.body);
120
127
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigField.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/ConfigField.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIjE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAuBtF,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEvF,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAC5C,mBAAmB,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,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;IACpD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,KAAK,EACL,UAAU,EACV,OAAO,EACP,MAAM,EACN,OAAO,EACP,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,WAAW,EACX,MAAM,GACP,EAAE,gBAAgB,+BAkUlB"}
1
+ {"version":3,"file":"ConfigField.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/ConfigField.tsx"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAIjE,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAwBtF,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAEvF,MAAM,MAAM,gBAAgB,GAAG,eAAe,GAC5C,mBAAmB,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,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;IACpD,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,CAAC;AAEF,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,KAAK,EACL,UAAU,EACV,OAAO,EACP,MAAM,EACN,OAAO,EACP,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,WAAW,EACX,MAAM,GACP,EAAE,gBAAgB,+BAyXlB"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
2
3
  import { Controller } from "react-hook-form";
3
4
  import * as Label from "@radix-ui/react-label";
4
5
  import * as Switch from "@radix-ui/react-switch";
@@ -12,10 +13,12 @@ import { DescriptionInput } from "../DescriptionInput";
12
13
  import { DatetimeInput } from "../DatetimeInput";
13
14
  import { EnumInput } from "../EnumInput";
14
15
  import { EnumListInput } from "../EnumListInput";
16
+ import { ImagePickerModal } from "../ImagePickerModal";
15
17
  import { PriceInput } from "../PriceInput";
16
18
  import { ReferenceInput } from "../ReferenceInput";
17
19
  import { displayLabel, firstImageUrl, isMultilineField, fieldVisibilityKey, spanFor, } from "./helpers";
18
20
  export function ConfigField({ fieldKey, field, groupLabel, variant, schema, watched, form, uploads, onUpload, galleryUpload, onListGallery, onUploadGallery, fonts, fontsLoading, onListFonts, onRefreshFonts, hideImagePreview, hideUpload, visibilityGroup, settingsRow, client, }) {
21
+ const [pickerOpen, setPickerOpen] = useState(false);
19
22
  const label = displayLabel(fieldKey, field, groupLabel);
20
23
  const visibilityKey = fieldVisibilityKey(fieldKey, schema, visibilityGroup ?? groupLabel);
21
24
  const visibilityOn = !visibilityKey || watched?.[visibilityKey] !== false;
@@ -23,6 +26,27 @@ export function ConfigField({ fieldKey, field, groupLabel, variant, schema, watc
23
26
  field.type !== "file[]" &&
24
27
  isImageField(fieldKey, field) &&
25
28
  Boolean(firstImageUrl(watched?.[fieldKey]));
29
+ const canPickFiles = !field.readOnly &&
30
+ Boolean(onListGallery) &&
31
+ (field.type === "file[]" || isImageField(fieldKey, field));
32
+ const canLocalUpload = !hideUpload &&
33
+ Boolean(uploads?.[fieldKey] && onUpload) &&
34
+ !field.readOnly;
35
+ const applyPickedImages = (images) => {
36
+ const urls = images.map((image) => image.url).filter(Boolean);
37
+ if (!urls.length)
38
+ return;
39
+ if (field.type === "file[]") {
40
+ form.setValue(fieldKey, [...urlsFromValue(form.getValues(fieldKey)), ...urls], { shouldDirty: true, shouldTouch: true });
41
+ }
42
+ else {
43
+ form.setValue(fieldKey, urls[0], {
44
+ shouldDirty: true,
45
+ shouldTouch: true,
46
+ });
47
+ }
48
+ setPickerOpen(false);
49
+ };
26
50
  if (field.type === "boolean") {
27
51
  if (settingsRow) {
28
52
  return (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsxs("li", { className: "vertebr-settings-list-row", "data-on": Boolean(controllerField.value) ? "true" : "false", children: [_jsxs("div", { className: "vertebr-settings-list-copy", children: [_jsx(Label.Root, { htmlFor: fieldKey, children: label }), field.description ? (_jsx("p", { className: "vertebr-settings-list-desc", children: field.description })) : null] }), _jsx(Switch.Root, { className: "vertebr-switch", id: fieldKey, checked: Boolean(controllerField.value), onCheckedChange: controllerField.onChange, children: _jsx(Switch.Thumb, { className: "vertebr-switch-thumb" }) })] })) }));
@@ -40,20 +64,20 @@ export function ConfigField({ fieldKey, field, groupLabel, variant, schema, watc
40
64
  : "", onBlur: controllerField.onBlur, onChange: (event) => {
41
65
  const next = event.target.value;
42
66
  controllerField.onChange(next === "" ? undefined : Number(next));
43
- }, name: controllerField.name, ref: controllerField.ref })) })) : field.type === "datetime" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(DatetimeInput, { id: fieldKey, name: controllerField.name, inputRef: controllerField.ref, value: controllerField.value, readOnly: field.readOnly, onBlur: controllerField.onBlur, onChange: controllerField.onChange })) })) : field.type === "enum" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(EnumInput, { id: fieldKey, value: controllerField.value, field: field, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : field.type === "enum[]" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(EnumListInput, { id: fieldKey, value: controllerField.value, field: field, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : field.type === "font" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(FontPickerField, { id: fieldKey, value: controllerField.value, label: field.label, description: field.description, readOnly: field.readOnly, fonts: fonts ?? [], loading: fontsLoading, onChange: controllerField.onChange, onRefreshFonts: onRefreshFonts })) })) : field.type === "reference" && client ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(ReferenceInput, { id: fieldKey, value: controllerField.value, field: field, client: client, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : isMultilineField(fieldKey, field) ? (_jsx("textarea", { id: fieldKey, rows: 5, readOnly: field.readOnly, ...form.register(fieldKey) })) : (_jsx("input", { id: fieldKey, type: "text", readOnly: field.readOnly, ...form.register(fieldKey) })), !hideUpload && uploads?.[fieldKey] && onUpload && !field.readOnly ? (_jsx(ImageUploadControl, { meta: uploads[fieldKey], multiple: field.type === "file[]", onUpload: async (files) => {
44
- const uploaded = await onUpload(fieldKey, files);
45
- const urls = uploaded
46
- .map((item) => item.url)
47
- .filter(Boolean);
48
- if (!urls.length)
49
- return;
50
- if (field.type === "file[]") {
51
- form.setValue(fieldKey, [...urlsFromValue(form.getValues(fieldKey)), ...urls], { shouldDirty: true, shouldTouch: true });
52
- return;
53
- }
54
- form.setValue(fieldKey, urls[0], {
55
- shouldDirty: true,
56
- shouldTouch: true,
57
- });
58
- } })) : null] }));
67
+ }, name: controllerField.name, ref: controllerField.ref })) })) : field.type === "datetime" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(DatetimeInput, { id: fieldKey, name: controllerField.name, inputRef: controllerField.ref, value: controllerField.value, readOnly: field.readOnly, onBlur: controllerField.onBlur, onChange: controllerField.onChange })) })) : field.type === "enum" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(EnumInput, { id: fieldKey, value: controllerField.value, field: field, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : field.type === "enum[]" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(EnumListInput, { id: fieldKey, value: controllerField.value, field: field, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : field.type === "font" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(FontPickerField, { id: fieldKey, value: controllerField.value, label: field.label, description: field.description, readOnly: field.readOnly, fonts: fonts ?? [], loading: fontsLoading, onChange: controllerField.onChange, onRefreshFonts: onRefreshFonts })) })) : field.type === "reference" && client ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(ReferenceInput, { id: fieldKey, value: controllerField.value, field: field, client: client, readOnly: field.readOnly, onChange: controllerField.onChange })) })) : isMultilineField(fieldKey, field) ? (_jsx("textarea", { id: fieldKey, rows: 5, readOnly: field.readOnly, ...form.register(fieldKey) })) : (_jsx("input", { id: fieldKey, type: "text", readOnly: field.readOnly, ...form.register(fieldKey) })), !hideUpload && (canLocalUpload || canPickFiles) ? (_jsxs("div", { className: "vertebr-field-media-actions", children: [canPickFiles ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => setPickerOpen(true), children: "Choose from Files" })) : null, canLocalUpload ? (_jsx(ImageUploadControl, { meta: uploads[fieldKey], multiple: field.type === "file[]", onUpload: async (files) => {
68
+ const uploaded = await onUpload(fieldKey, files);
69
+ const urls = uploaded
70
+ .map((item) => item.url)
71
+ .filter(Boolean);
72
+ if (!urls.length)
73
+ return;
74
+ if (field.type === "file[]") {
75
+ form.setValue(fieldKey, [...urlsFromValue(form.getValues(fieldKey)), ...urls], { shouldDirty: true, shouldTouch: true });
76
+ return;
77
+ }
78
+ form.setValue(fieldKey, urls[0], {
79
+ shouldDirty: true,
80
+ shouldTouch: true,
81
+ });
82
+ } })) : null] })) : null, pickerOpen && canPickFiles ? (_jsx(ImagePickerModal, { title: field.type === "file[]" ? "Add product photos" : "Choose image", confirmLabel: "Use", defaultTab: "gallery", recordImages: collectRecordImages(schema, watched), galleryUpload: galleryUpload, onListGallery: onListGallery, onUploadGallery: onUploadGallery, onClose: () => setPickerOpen(false), onInsert: applyPickedImages })) : null] }));
59
83
  }
@@ -1 +1 @@
1
- {"version":3,"file":"RecordConfigForm.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/RecordConfigForm.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAYtF,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG;IACpD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,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;IACpD,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC;IAC5D,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAC;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,MAAM,EACN,QAAQ,EACR,OAAO,EACP,MAAM,EACN,IAAI,EACJ,SAAS,EACT,WAAW,EACX,WAAW,GACZ,EAAE,qBAAqB,+BAuJvB"}
1
+ {"version":3,"file":"RecordConfigForm.d.ts","sourceRoot":"","sources":["../../../../src/components/config/form/RecordConfigForm.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAatF,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,MAAM,qBAAqB,GAAG,eAAe,GAAG;IACpD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,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;IACpD,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC;IAC5D,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,IAAI,GAAG,QAAQ,CAAC;IACvB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,EACf,MAAM,EACN,QAAQ,EACR,OAAO,EACP,MAAM,EACN,IAAI,EACJ,SAAS,EACT,WAAW,EACX,WAAW,GACZ,EAAE,qBAAqB,+BA4LvB"}
@@ -1,15 +1,29 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { looksLikeUrl } from "../../../lib/images";
2
+ import { useState } from "react";
3
+ import { collectRecordImages, looksLikeUrl } from "../../../lib/images";
3
4
  import { ImageUploadControl } from "../../resources/ImageUploadControl";
5
+ import { ImagePickerModal } from "../ImagePickerModal";
4
6
  import { ConfigField } from "./ConfigField";
5
7
  import { ConfigFormActions } from "./ConfigFormActions";
6
8
  import { VisibilityEyeButton } from "../VisibilityEye";
7
9
  import { findPrimaryImageKey, firstImageUrl, groupVisibilityKey, isStockField, } from "./helpers";
8
10
  export function RecordConfigForm({ schema, groups, watched, form, uploads, onUpload, galleryUpload, onListGallery, onUploadGallery, client, onSubmit, isDirty, status, tone, onDiscard, submitLabel, hideActions, }) {
11
+ const [pickerOpen, setPickerOpen] = useState(false);
9
12
  const imageKey = findPrimaryImageKey(schema, watched);
10
13
  const imageValue = imageKey ? firstImageUrl(watched?.[imageKey]) ?? "" : "";
11
14
  const asideGroups = groups.filter((group) => /^(Media|Display|Status)$/i.test(group.id));
12
15
  const mainGroups = groups.filter((group) => !/^(Media|Display|Status)$/i.test(group.id));
16
+ const canPickPrimary = Boolean(imageKey && onListGallery);
17
+ const canUploadPrimary = Boolean(imageKey && uploads?.[imageKey] && onUpload);
18
+ const applyPrimaryImage = (images) => {
19
+ if (!imageKey)
20
+ return;
21
+ const url = images[0]?.url;
22
+ if (!url)
23
+ return;
24
+ form.setValue(imageKey, url, { shouldDirty: true, shouldTouch: true });
25
+ setPickerOpen(false);
26
+ };
13
27
  const fieldProps = {
14
28
  variant: "record",
15
29
  schema,
@@ -22,15 +36,15 @@ export function RecordConfigForm({ schema, groups, watched, form, uploads, onUpl
22
36
  onUploadGallery,
23
37
  client,
24
38
  };
25
- 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) => {
26
- const [first] = await onUpload(imageKey, files);
27
- if (first?.url) {
28
- form.setValue(imageKey, first.url, {
29
- shouldDirty: true,
30
- shouldTouch: true,
31
- });
32
- }
33
- } })) : null, asideGroups.map((group) => (_jsxs("div", { className: "vertebr-record-block", "data-layout": /^inventory$/i.test(group.id) ? "chips" : undefined, children: [_jsx(RecordBlockLabel, { groupId: group.id, label: group.label, schema: schema, form: form }), _jsx("div", { className: "vertebr-form-fields", "data-layout": isStockField(group.fields[0]?.[0] ?? "", group.fields[0]?.[1])
39
+ 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" })) }), canPickPrimary || canUploadPrimary ? (_jsxs("div", { className: "vertebr-field-media-actions", children: [canPickPrimary ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => setPickerOpen(true), children: "Choose from Files" })) : null, canUploadPrimary ? (_jsx(ImageUploadControl, { meta: uploads[imageKey], label: "Or upload a new image", onUpload: async (files) => {
40
+ const [first] = await onUpload(imageKey, files);
41
+ if (first?.url) {
42
+ form.setValue(imageKey, first.url, {
43
+ shouldDirty: true,
44
+ shouldTouch: true,
45
+ });
46
+ }
47
+ } })) : null] })) : null, pickerOpen && canPickPrimary ? (_jsx(ImagePickerModal, { title: "Choose product image", confirmLabel: "Use", defaultTab: "gallery", recordImages: collectRecordImages(schema, watched), galleryUpload: galleryUpload, onListGallery: onListGallery, onUploadGallery: onUploadGallery, onClose: () => setPickerOpen(false), onInsert: applyPrimaryImage })) : null, asideGroups.map((group) => (_jsxs("div", { className: "vertebr-record-block", "data-layout": /^inventory$/i.test(group.id) ? "chips" : undefined, children: [_jsx(RecordBlockLabel, { groupId: group.id, label: group.label, schema: schema, form: form }), _jsx("div", { className: "vertebr-form-fields", "data-layout": isStockField(group.fields[0]?.[0] ?? "", group.fields[0]?.[1])
34
48
  ? "chips"
35
49
  : undefined, children: group.fields.map(([key, field]) => (_jsx(ConfigField, { fieldKey: key, field: field, groupLabel: group.label, visibilityGroup: group.id, hideImagePreview: true, hideUpload: key === imageKey, ...fieldProps }, key))) })] }, group.id)))] }), _jsx("div", { className: "vertebr-record-main", children: mainGroups.map((group) => {
36
50
  const inventory = /^inventory$/i.test(group.id);
@@ -1 +1 @@
1
- {"version":3,"file":"FontLibraryBoard.d.ts","sourceRoot":"","sources":["../../../src/components/fonts/FontLibraryBoard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,mBAAmB,EACnB,uBAAuB,EAExB,MAAM,iBAAiB,CAAC;AAMzB,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,CACR,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KACxC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,CAAC;AAUF,wBAAgB,gBAAgB,CAAC,EAC/B,KAAsB,EACtB,IAAI,EACJ,OAAO,EACP,SAAS,EACT,QAAQ,EACR,cAAc,EACd,QAAQ,GACT,EAAE,qBAAqB,+BAqIvB"}
1
+ {"version":3,"file":"FontLibraryBoard.d.ts","sourceRoot":"","sources":["../../../src/components/fonts/FontLibraryBoard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAEV,mBAAmB,EACnB,uBAAuB,EAExB,MAAM,iBAAiB,CAAC;AAMzB,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,mBAAmB,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,CACR,KAAK,EAAE,IAAI,EAAE,EACb,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KACxC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,CAAC;AAUF,wBAAgB,gBAAgB,CAAC,EAC/B,KAAsB,EACtB,IAAI,EACJ,OAAO,EACP,SAAS,EACT,QAAQ,EACR,cAAc,EACd,QAAQ,GACT,EAAE,qBAAqB,+BAsIvB"}
@@ -18,7 +18,7 @@ export function FontLibraryBoard({ label = "Font Library", data, loading, onRefr
18
18
  accept: data.accept,
19
19
  maxBytes: data.maxBytes,
20
20
  };
21
- return (_jsxs("div", { className: "vertebr-font-library", children: [_jsx(FontFaceInjector, { items: data.items }), _jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: label }), _jsxs("div", { className: "vertebr-font-library-head-actions", children: [_jsx("span", { className: "vertebr-count", children: data.items.length }), onRefresh ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: onRefresh, disabled: loading, children: loading ? "Refreshing…" : "Refresh" })) : null] })] }), _jsxs("div", { className: "vertebr-font-library-body", children: [data.canImportGoogle && onImportGoogle ? (_jsx(GoogleFontsBrowser, { disabled: loading, onImport: onImportGoogle, existingFamilies: data.items.map((item) => item.family.toLowerCase()) })) : null, _jsx(ImageUploadControl, { meta: meta, multiple: true, label: "Drop .woff2 / .woff / .ttf files here or browse to upload", onUpload: async (files) => {
21
+ return (_jsxs("div", { className: "vertebr-font-library", children: [_jsx(FontFaceInjector, { items: data.items }), _jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: label }), _jsxs("div", { className: "vertebr-font-library-head-actions", children: [_jsx("span", { className: "vertebr-count", children: data.items.length }), onRefresh ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: onRefresh, disabled: loading, children: loading ? "Refreshing…" : "Refresh" })) : null] })] }), _jsxs("div", { className: "vertebr-font-library-body", children: [data.canImportGoogle && onImportGoogle ? (_jsx(GoogleFontsBrowser, { disabled: loading, onImport: onImportGoogle, existingFamilies: data.items.map((item) => item.family.toLowerCase()) })) : null, _jsx(ImageUploadControl, { meta: meta, multiple: true, showOptimizeHint: false, label: "Drop .woff2 / .woff / .ttf files here or browse to upload", onUpload: async (files) => {
22
22
  for (const file of files) {
23
23
  const base = file.name.replace(/\.[^.]+$/, "");
24
24
  const weightMatch = /(?:^|[\s_-])(100|200|300|400|500|600|700|800|900)(?:[\s_-]|$)/i.exec(base);
@@ -4,7 +4,9 @@ export type ImageUploadControlProps = {
4
4
  disabled?: boolean;
5
5
  multiple?: boolean;
6
6
  label?: string;
7
+ /** Show storefront optimize hint (image uploads). @default true */
8
+ showOptimizeHint?: boolean;
7
9
  onUpload: (files: File[]) => Promise<void>;
8
10
  };
9
- export declare function ImageUploadControl({ meta, disabled, multiple, label, onUpload, }: ImageUploadControlProps): import("react").JSX.Element;
11
+ export declare function ImageUploadControl({ meta, disabled, multiple, label, showOptimizeHint, onUpload, }: ImageUploadControlProps): import("react").JSX.Element;
10
12
  //# sourceMappingURL=ImageUploadControl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ImageUploadControl.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ImageUploadControl.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,kBAAkB,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C,CAAC;AAMF,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,QAAQ,EACR,QAAgB,EAChB,KAAK,EACL,QAAQ,GACT,EAAE,uBAAuB,+BA8EzB"}
1
+ {"version":3,"file":"ImageUploadControl.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ImageUploadControl.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,kBAAkB,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C,CAAC;AAMF,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,QAAQ,EACR,QAAgB,EAChB,KAAK,EACL,gBAAuB,EACvB,QAAQ,GACT,EAAE,uBAAuB,+BAqFzB"}
@@ -3,7 +3,7 @@ import { useRef, useState } from "react";
3
3
  function acceptAttr(accept) {
4
4
  return accept.join(",");
5
5
  }
6
- export function ImageUploadControl({ meta, disabled, multiple = false, label, onUpload, }) {
6
+ export function ImageUploadControl({ meta, disabled, multiple = false, label, showOptimizeHint = true, onUpload, }) {
7
7
  const inputRef = useRef(null);
8
8
  const [busy, setBusy] = useState(false);
9
9
  const [error, setError] = useState(null);
@@ -42,9 +42,11 @@ export function ImageUploadControl({ meta, disabled, multiple = false, label, on
42
42
  setDragOver(false);
43
43
  void run(event.dataTransfer.files);
44
44
  }, children: [_jsx("p", { className: "vertebr-upload-copy", children: busy
45
- ? "Uploading…"
45
+ ? showOptimizeHint
46
+ ? "Optimizing…"
47
+ : "Uploading…"
46
48
  : label ??
47
49
  (multiple
48
50
  ? "Drop images here or browse"
49
- : "Drop an image here or browse") }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: disabled || busy, onClick: () => inputRef.current?.click(), children: multiple ? "Choose images" : "Choose image" }), _jsx("input", { ref: inputRef, type: "file", accept: acceptAttr(meta.accept), multiple: multiple, hidden: true, onChange: (event) => void run(event.target.files) })] }), error ? (_jsx("p", { className: "vertebr-upload-error", role: "alert", children: error })) : null] }));
51
+ : "Drop an image here or browse") }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: disabled || busy, onClick: () => inputRef.current?.click(), children: multiple ? "Choose images" : "Choose image" }), _jsx("input", { ref: inputRef, type: "file", accept: acceptAttr(meta.accept), multiple: multiple, hidden: true, onChange: (event) => void run(event.target.files) })] }), showOptimizeHint ? (_jsx("p", { className: "vertebr-upload-hint", children: "Images are optimized for the storefront" })) : null, error ? (_jsx("p", { className: "vertebr-upload-error", role: "alert", children: error })) : null] }));
50
52
  }
@@ -1111,6 +1111,12 @@
1111
1111
  color: var(--vb-text-subtle);
1112
1112
  }
1113
1113
 
1114
+ .vertebr-field-media-actions {
1115
+ display: flex;
1116
+ flex-direction: column;
1117
+ gap: 0.55rem;
1118
+ }
1119
+
1114
1120
  .vertebr-field[data-kind="image"] {
1115
1121
  grid-column: 1 / -1;
1116
1122
  }
@@ -1294,6 +1300,13 @@
1294
1300
  line-height: 1.35;
1295
1301
  }
1296
1302
 
1303
+ .vertebr-upload-hint {
1304
+ margin: 0;
1305
+ font-size: 0.72rem;
1306
+ color: var(--vb-text-faint, var(--vb-text-muted));
1307
+ line-height: 1.35;
1308
+ }
1309
+
1297
1310
  .vertebr-upload-error {
1298
1311
  margin: 0;
1299
1312
  font-size: 0.78rem;
@@ -3,7 +3,8 @@
3
3
  .vertebr-modal-backdrop {
4
4
  position: fixed;
5
5
  inset: 0;
6
- z-index: 80;
6
+ /* Above shell chrome, toasts (100), and command palette (90). */
7
+ z-index: 200;
7
8
  display: grid;
8
9
  place-items: center;
9
10
  padding: 1.25rem;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertebr/react",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "description": "Vertebr admin dashboard UI",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",