@vertebr/react 0.12.0 → 0.15.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 (29) hide show
  1. package/dist/components/admin/AdminSidebar.d.ts.map +1 -1
  2. package/dist/components/admin/AdminSidebar.js +3 -2
  3. package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
  4. package/dist/components/admin/VertebrAdmin.js +72 -11
  5. package/dist/components/config/DescriptionInput.d.ts +3 -1
  6. package/dist/components/config/DescriptionInput.d.ts.map +1 -1
  7. package/dist/components/config/DescriptionInput.js +2 -2
  8. package/dist/components/config/DocumentAttachmentField.d.ts +17 -0
  9. package/dist/components/config/DocumentAttachmentField.d.ts.map +1 -0
  10. package/dist/components/config/DocumentAttachmentField.js +32 -0
  11. package/dist/components/config/ImagePickerModal.d.ts +4 -1
  12. package/dist/components/config/ImagePickerModal.d.ts.map +1 -1
  13. package/dist/components/config/ImagePickerModal.js +45 -23
  14. package/dist/components/config/MarkdownDocumentModal.d.ts +19 -0
  15. package/dist/components/config/MarkdownDocumentModal.d.ts.map +1 -0
  16. package/dist/components/config/MarkdownDocumentModal.js +34 -0
  17. package/dist/components/config/form/ConfigField.d.ts.map +1 -1
  18. package/dist/components/config/form/ConfigField.js +95 -5
  19. package/dist/components/gallery/GalleryBoard.d.ts.map +1 -1
  20. package/dist/components/gallery/GalleryBoard.js +37 -5
  21. package/dist/components/ui/icons.d.ts +30 -2
  22. package/dist/components/ui/icons.d.ts.map +1 -1
  23. package/dist/components/ui/icons.js +199 -15
  24. package/dist/lib/documents.d.ts +14 -0
  25. package/dist/lib/documents.d.ts.map +1 -0
  26. package/dist/lib/documents.js +42 -0
  27. package/dist/styles/form.css +15 -0
  28. package/dist/styles/gallery.css +237 -0
  29. package/package.json +3 -1
@@ -6,10 +6,13 @@ import * as Switch from "@radix-ui/react-switch";
6
6
  import { VisibilityEyeButton } from "../VisibilityEye";
7
7
  import { isDescriptionField } from "../../../lib/description";
8
8
  import { collectRecordImages, isImageField, urlsFromValue, } from "../../../lib/images";
9
+ import { isDocumentField } from "../../../lib/documents";
9
10
  import { isCentsField, isPriceField } from "../../../lib/price";
10
11
  import { FontPickerField } from "../../fonts/FontPickerField";
11
12
  import { ImageUploadControl } from "../../resources/ImageUploadControl";
12
13
  import { DescriptionInput } from "../DescriptionInput";
14
+ import { DocumentAttachmentField } from "../DocumentAttachmentField";
15
+ import { MarkdownDocumentModal } from "../MarkdownDocumentModal";
13
16
  import { DatetimeInput } from "../DatetimeInput";
14
17
  import { EnumInput } from "../EnumInput";
15
18
  import { EnumListInput } from "../EnumListInput";
@@ -17,9 +20,39 @@ import { ImagePickerModal } from "../ImagePickerModal";
17
20
  import { PriceInput } from "../PriceInput";
18
21
  import { ReferenceInput } from "../ReferenceInput";
19
22
  import { displayLabel, firstImageUrl, isMultilineField, fieldVisibilityKey, spanFor, } from "./helpers";
23
+ function findMarkdownBodyKey(schema, currentKey) {
24
+ const preferred = ["body", "content", "policyBody", "text"];
25
+ for (const key of preferred) {
26
+ const field = schema[key];
27
+ if (!field || key === currentKey)
28
+ continue;
29
+ if (field.type === "richtext" || isDescriptionField(key, field))
30
+ return key;
31
+ }
32
+ for (const [key, field] of Object.entries(schema)) {
33
+ if (key === currentKey)
34
+ continue;
35
+ if (field.type === "richtext" || isDescriptionField(key, field))
36
+ return key;
37
+ }
38
+ return null;
39
+ }
20
40
  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
41
  const [pickerOpen, setPickerOpen] = useState(false);
42
+ const [docModalOpen, setDocModalOpen] = useState(false);
22
43
  const label = displayLabel(fieldKey, field, groupLabel);
44
+ const documentField = isDocumentField(fieldKey, field);
45
+ const markdownBodyKey = documentField
46
+ ? findMarkdownBodyKey(schema, fieldKey)
47
+ : null;
48
+ const markdownBodyField = markdownBodyKey
49
+ ? schema[markdownBodyKey]
50
+ : undefined;
51
+ const markdownBodyValue = markdownBodyKey && typeof watched?.[markdownBodyKey] === "string"
52
+ ? watched[markdownBodyKey]
53
+ : markdownBodyKey
54
+ ? ""
55
+ : undefined;
23
56
  const visibilityKey = fieldVisibilityKey(fieldKey, schema, visibilityGroup ?? groupLabel);
24
57
  const visibilityOn = !visibilityKey || watched?.[visibilityKey] !== false;
25
58
  const showInlinePreview = !hideImagePreview &&
@@ -28,10 +61,13 @@ export function ConfigField({ fieldKey, field, groupLabel, variant, schema, watc
28
61
  Boolean(firstImageUrl(watched?.[fieldKey]));
29
62
  const canPickFiles = !field.readOnly &&
30
63
  Boolean(onListGallery) &&
31
- (field.type === "file[]" || isImageField(fieldKey, field));
64
+ (field.type === "file[]" ||
65
+ isImageField(fieldKey, field) ||
66
+ documentField);
32
67
  const canLocalUpload = !hideUpload &&
33
68
  Boolean(uploads?.[fieldKey] && onUpload) &&
34
69
  !field.readOnly;
70
+ const canGalleryUpload = Boolean(onUploadGallery);
35
71
  const applyPickedImages = (images) => {
36
72
  const urls = images.map((image) => image.url).filter(Boolean);
37
73
  if (!urls.length)
@@ -53,9 +89,38 @@ export function ConfigField({ fieldKey, field, groupLabel, variant, schema, watc
53
89
  }
54
90
  return (_jsxs("div", { className: "vertebr-switch-row", children: [_jsxs("div", { className: "vertebr-switch-row-copy", children: [_jsx(Label.Root, { htmlFor: fieldKey, children: label }), field.description ? (_jsx("p", { className: "vertebr-switch-row-desc", children: field.description })) : null] }), _jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(Switch.Root, { className: "vertebr-switch", id: fieldKey, checked: Boolean(controllerField.value), onCheckedChange: controllerField.onChange, children: _jsx(Switch.Thumb, { className: "vertebr-switch-thumb" }) })) })] }));
55
91
  }
56
- return (_jsxs("div", { className: "vertebr-field", "data-span": spanFor(fieldKey, field, variant), "data-kind": isImageField(fieldKey, field) ? "image" : undefined, "data-storefront": visibilityKey ? (visibilityOn ? "on" : "off") : undefined, children: [_jsxs("div", { className: "vertebr-field-head", children: [_jsx(Label.Root, { htmlFor: fieldKey, children: label }), visibilityKey ? (_jsx(VisibilityEyeButton, { name: visibilityKey, label: label, form: form })) : null] }), showInlinePreview ? (_jsxs("div", { className: "vertebr-field-media", children: [_jsx("img", { src: firstImageUrl(watched[fieldKey]) ?? "", alt: "" }), _jsx("input", { id: fieldKey, type: "text", readOnly: field.readOnly, ...form.register(fieldKey) })] })) : field.type === "richtext" || isDescriptionField(fieldKey, field) ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(DescriptionInput, { id: fieldKey, name: controllerField.name, inputRef: controllerField.ref, value: typeof controllerField.value === "string"
92
+ return (_jsxs("div", { className: "vertebr-field", "data-span": spanFor(fieldKey, field, variant), "data-kind": isImageField(fieldKey, field)
93
+ ? "image"
94
+ : documentField
95
+ ? "document"
96
+ : undefined, "data-storefront": visibilityKey ? (visibilityOn ? "on" : "off") : undefined, children: [_jsxs("div", { className: "vertebr-field-head", children: [_jsx(Label.Root, { htmlFor: fieldKey, children: label }), visibilityKey ? (_jsx(VisibilityEyeButton, { name: visibilityKey, label: label, form: form })) : null] }), showInlinePreview ? (_jsxs("div", { className: "vertebr-field-media", children: [_jsx("img", { src: firstImageUrl(watched[fieldKey]) ?? "", alt: "" }), _jsx("input", { id: fieldKey, type: "text", readOnly: field.readOnly, ...form.register(fieldKey) })] })) : documentField ? (_jsx(DocumentAttachmentField, { id: fieldKey, label: label, value: typeof watched?.[fieldKey] === "string" ? watched[fieldKey] : "", readOnly: field.readOnly, description: field.description, markdownBody: markdownBodyValue, canPickFiles: canPickFiles, canUpload: canLocalUpload || canGalleryUpload, onChangeUrl: (next) => form.setValue(fieldKey, next, {
97
+ shouldDirty: true,
98
+ shouldTouch: true,
99
+ }), onOpenPicker: () => setPickerOpen(true), onUploadFiles: async (files) => {
100
+ if (canLocalUpload) {
101
+ const uploaded = await onUpload(fieldKey, files);
102
+ const url = uploaded[0]?.url;
103
+ if (url) {
104
+ form.setValue(fieldKey, url, {
105
+ shouldDirty: true,
106
+ shouldTouch: true,
107
+ });
108
+ }
109
+ return;
110
+ }
111
+ if (!onUploadGallery)
112
+ return;
113
+ const items = await onUploadGallery(files);
114
+ const url = items[0]?.url;
115
+ if (url) {
116
+ form.setValue(fieldKey, url, {
117
+ shouldDirty: true,
118
+ shouldTouch: true,
119
+ });
120
+ }
121
+ }, onEditMarkdown: markdownBodyKey ? () => setDocModalOpen(true) : undefined })) : field.type === "richtext" || isDescriptionField(fieldKey, field) ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(DescriptionInput, { id: fieldKey, name: controllerField.name, inputRef: controllerField.ref, value: typeof controllerField.value === "string"
57
122
  ? controllerField.value
58
- : "", readOnly: field.readOnly, recordImages: collectRecordImages(schema, watched), galleryUpload: galleryUpload, onListGallery: onListGallery, onUploadGallery: onUploadGallery, onBlur: controllerField.onBlur, onChange: controllerField.onChange })) })) : field.type === "file[]" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => {
123
+ : "", readOnly: field.readOnly, recordImages: collectRecordImages(schema, watched), galleryUpload: galleryUpload, onListGallery: onListGallery, onUploadGallery: onUploadGallery, onBlur: controllerField.onBlur, onChange: controllerField.onChange, onOpenDocumentView: () => setDocModalOpen(true) })) })) : field.type === "file[]" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => {
59
124
  const urls = urlsFromValue(controllerField.value);
60
125
  return (_jsx("div", { className: "vertebr-field-files", children: urls.length > 0 ? (_jsx("ul", { className: "vertebr-field-files-list", children: urls.map((url) => (_jsxs("li", { children: [_jsx("img", { src: url, alt: "" }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => controllerField.onChange(urls.filter((item) => item !== url)), children: "Remove" })] }, url))) })) : (_jsx("p", { className: "vertebr-field-files-empty", children: "No photos yet" })) }));
61
126
  } })) : field.type === "number" && isPriceField(fieldKey, field) ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx(PriceInput, { id: fieldKey, name: controllerField.name, inputRef: controllerField.ref, value: controllerField.value, asCents: isCentsField(fieldKey, field) || /^pricing$/i.test(groupLabel), readOnly: field.readOnly, onBlur: controllerField.onBlur, onChange: controllerField.onChange })) })) : field.type === "number" ? (_jsx(Controller, { control: form.control, name: fieldKey, render: ({ field: controllerField }) => (_jsx("input", { id: fieldKey, type: "number", readOnly: field.readOnly, value: typeof controllerField.value === "number" &&
@@ -64,7 +129,9 @@ export function ConfigField({ fieldKey, field, groupLabel, variant, schema, watc
64
129
  : "", onBlur: controllerField.onBlur, onChange: (event) => {
65
130
  const next = event.target.value;
66
131
  controllerField.onChange(next === "" ? undefined : Number(next));
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) => {
132
+ }, 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 &&
133
+ !documentField &&
134
+ (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[]", showOptimizeHint: true, onUpload: async (files) => {
68
135
  const uploaded = await onUpload(fieldKey, files);
69
136
  const urls = uploaded
70
137
  .map((item) => item.url)
@@ -79,5 +146,28 @@ export function ConfigField({ fieldKey, field, groupLabel, variant, schema, watc
79
146
  shouldDirty: true,
80
147
  shouldTouch: true,
81
148
  });
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] }));
149
+ } })) : null] })) : null, pickerOpen && canPickFiles ? (_jsx(ImagePickerModal, { title: documentField
150
+ ? "Choose PDF"
151
+ : field.type === "file[]"
152
+ ? "Add product photos"
153
+ : "Choose image", confirmLabel: "Use", defaultTab: "gallery", assetKind: documentField ? "document" : "image", recordImages: documentField ? [] : collectRecordImages(schema, watched), galleryUpload: galleryUpload, onListGallery: onListGallery, onUploadGallery: onUploadGallery, onClose: () => setPickerOpen(false), onInsert: applyPickedImages })) : null, docModalOpen &&
154
+ (markdownBodyKey ||
155
+ field.type === "richtext" ||
156
+ isDescriptionField(fieldKey, field)) ? (_jsx(MarkdownDocumentModal, { title: markdownBodyKey && markdownBodyField
157
+ ? displayLabel(markdownBodyKey, markdownBodyField, groupLabel)
158
+ : label, value: markdownBodyKey
159
+ ? typeof form.getValues(markdownBodyKey) === "string"
160
+ ? form.getValues(markdownBodyKey)
161
+ : ""
162
+ : typeof form.getValues(fieldKey) === "string"
163
+ ? form.getValues(fieldKey)
164
+ : "", readOnly: markdownBodyKey
165
+ ? Boolean(markdownBodyField?.readOnly)
166
+ : field.readOnly, recordImages: collectRecordImages(schema, watched), galleryUpload: galleryUpload, onListGallery: onListGallery, onUploadGallery: onUploadGallery, onChange: (next) => {
167
+ const key = markdownBodyKey ?? fieldKey;
168
+ form.setValue(key, next, {
169
+ shouldDirty: true,
170
+ shouldTouch: true,
171
+ });
172
+ }, onClose: () => setDocModalOpen(false) })) : null] }));
83
173
  }
@@ -1 +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"}
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;AAIvE,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;AAuBF,wBAAgB,YAAY,CAAC,EAC3B,KAAiB,EACjB,KAAK,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,GACT,EAAE,iBAAiB,+BAgHnB"}
@@ -1,14 +1,46 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { isPdfAsset, pdfFileName } from "../../lib/documents";
2
3
  import { ImageUploadControl } from "../resources/ImageUploadControl";
4
+ function acceptsDocuments(accept) {
5
+ return accept.some((entry) => /pdf/i.test(entry) ||
6
+ entry === "application/*" ||
7
+ entry === "*/*");
8
+ }
9
+ function acceptsImages(accept) {
10
+ return (accept.length === 0 ||
11
+ accept.some((entry) => entry.startsWith("image/") ||
12
+ entry === "image/*" ||
13
+ entry === "*/*"));
14
+ }
3
15
  export function GalleryBoard({ label = "Gallery", items, accept, maxBytes, canDelete, loading, onRefresh, onUpload, onDelete, }) {
4
16
  const meta = {
5
17
  field: "gallery",
6
18
  accept,
7
19
  maxBytes,
8
20
  };
9
- return (_jsxs("div", { className: "vertebr-gallery", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: label }), _jsxs("div", { className: "vertebr-gallery-head-actions", children: [_jsx("span", { className: "vertebr-count", children: items.length }), onRefresh ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: onRefresh, disabled: loading, children: loading ? "Refreshing…" : "Refresh" })) : null] })] }), _jsxs("div", { className: "vertebr-gallery-body", children: [_jsx(ImageUploadControl, { meta: meta, multiple: true, label: "Drop images here or browse to add to the gallery", onUpload: onUpload }), items.length === 0 ? (_jsx("div", { className: "vertebr-empty", children: "No images in the gallery yet." })) : (_jsx("div", { className: "vertebr-gallery-grid", children: items.map((item) => (_jsxs("article", { className: "vertebr-gallery-card", children: [_jsx("a", { className: "vertebr-gallery-thumb", href: item.url, target: "_blank", rel: "noreferrer", title: item.name ?? item.url, children: _jsx("img", { src: item.url, alt: item.name ?? "", loading: "lazy" }) }), _jsxs("div", { className: "vertebr-gallery-meta", children: [_jsx("strong", { title: item.name ?? item.id, children: item.name ?? item.id }), _jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url.replace(/^https?:\/\//i, "") })] }), canDelete && onDelete ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-danger-quiet", onClick: () => {
10
- if (window.confirm("Delete this image from the gallery?")) {
11
- void onDelete(item.id);
12
- }
13
- }, children: "Delete" })) : null] }, item.id))) }))] })] }));
21
+ const docs = acceptsDocuments(accept);
22
+ const images = acceptsImages(accept);
23
+ const dropLabel = docs && images
24
+ ? "Drop images or PDFs here or browse to add to Files"
25
+ : docs
26
+ ? "Drop PDFs here or browse to add to Files"
27
+ : "Drop images here or browse to add to the gallery";
28
+ const emptyLabel = docs && images
29
+ ? "No files yet."
30
+ : docs
31
+ ? "No documents yet."
32
+ : "No images in the gallery yet.";
33
+ return (_jsxs("div", { className: "vertebr-gallery", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: label }), _jsxs("div", { className: "vertebr-gallery-head-actions", children: [_jsx("span", { className: "vertebr-count", children: items.length }), onRefresh ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: onRefresh, disabled: loading, children: loading ? "Refreshing…" : "Refresh" })) : null] })] }), _jsxs("div", { className: "vertebr-gallery-body", children: [_jsx(ImageUploadControl, { meta: meta, multiple: true, showOptimizeHint: images, label: dropLabel, onUpload: onUpload }), items.length === 0 ? (_jsx("div", { className: "vertebr-empty", children: emptyLabel })) : (_jsx("div", { className: "vertebr-gallery-grid", children: items.map((item) => {
34
+ const pdf = isPdfAsset(item);
35
+ return (_jsxs("article", { className: "vertebr-gallery-card", children: [_jsx("a", { className: pdf
36
+ ? "vertebr-gallery-thumb vertebr-gallery-thumb-file"
37
+ : "vertebr-gallery-thumb", href: item.url, target: "_blank", rel: "noreferrer", title: item.name ?? item.url, children: pdf ? (_jsx("span", { className: "vertebr-gallery-file-badge", children: "PDF" })) : (_jsx("img", { src: item.url, alt: item.name ?? "", loading: "lazy" })) }), _jsxs("div", { className: "vertebr-gallery-meta", children: [_jsx("strong", { title: item.name ?? item.id, children: item.name ??
38
+ (pdf ? pdfFileName(item.url) : item.id) }), _jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url.replace(/^https?:\/\//i, "") })] }), canDelete && onDelete ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-danger-quiet", onClick: () => {
39
+ if (window.confirm(pdf
40
+ ? "Delete this document from Files?"
41
+ : "Delete this image from the gallery?")) {
42
+ void onDelete(item.id);
43
+ }
44
+ }, children: "Delete" })) : null] }, item.id));
45
+ }) }))] })] }));
14
46
  }
@@ -1,11 +1,24 @@
1
- import type { SVGProps } from "react";
1
+ import type { ReactElement, SVGProps } from "react";
2
+ import type { CatalogAddonCategory, NavIconName } from "@vertebr/client";
2
3
  type IconProps = SVGProps<SVGSVGElement>;
3
4
  export declare function IconHome(props: IconProps): import("react").JSX.Element;
4
5
  export declare function IconProducts(props: IconProps): import("react").JSX.Element;
6
+ export declare function IconInventory(props: IconProps): import("react").JSX.Element;
7
+ export declare function IconUsers(props: IconProps): import("react").JSX.Element;
8
+ export declare function IconCard(props: IconProps): import("react").JSX.Element;
9
+ export declare function IconTag(props: IconProps): import("react").JSX.Element;
10
+ export declare function IconBolt(props: IconProps): import("react").JSX.Element;
5
11
  export declare function IconGallery(props: IconProps): import("react").JSX.Element;
6
12
  export declare function IconFonts(props: IconProps): import("react").JSX.Element;
7
13
  export declare function IconMail(props: IconProps): import("react").JSX.Element;
8
14
  export declare function IconPages(props: IconProps): import("react").JSX.Element;
15
+ export declare function IconPalette(props: IconProps): import("react").JSX.Element;
16
+ export declare function IconTruck(props: IconProps): import("react").JSX.Element;
17
+ export declare function IconClock(props: IconProps): import("react").JSX.Element;
18
+ export declare function IconStar(props: IconProps): import("react").JSX.Element;
19
+ export declare function IconMegaphone(props: IconProps): import("react").JSX.Element;
20
+ export declare function IconLayout(props: IconProps): import("react").JSX.Element;
21
+ export declare function IconList(props: IconProps): import("react").JSX.Element;
9
22
  export declare function IconSettings(props: IconProps): import("react").JSX.Element;
10
23
  export declare function IconChart(props: IconProps): import("react").JSX.Element;
11
24
  export declare function IconChevronLeft(props: IconProps): import("react").JSX.Element;
@@ -13,6 +26,21 @@ export declare function IconSearch(props: IconProps): import("react").JSX.Elemen
13
26
  export declare function IconSync(props: IconProps): import("react").JSX.Element;
14
27
  export declare function IconMenu(props: IconProps): import("react").JSX.Element;
15
28
  export declare function IconAddon(props: IconProps): import("react").JSX.Element;
16
- export declare function iconForNavKey(key: string): typeof IconHome;
29
+ export type NavIconComponent = (props: IconProps) => ReactElement;
30
+ export declare const NAV_ICON_COMPONENTS: Record<NavIconName, NavIconComponent>;
31
+ export type ResolveNavIconInput = {
32
+ key: string;
33
+ id?: string;
34
+ label?: string;
35
+ group?: string;
36
+ category?: CatalogAddonCategory;
37
+ section?: "config" | "page" | "addon";
38
+ icon?: NavIconName | null;
39
+ };
40
+ /** Resolve a stable icon name for a sidebar entry. */
41
+ export declare function resolveNavIconName(input: ResolveNavIconInput): NavIconName;
42
+ export declare function resolveNavIcon(input: ResolveNavIconInput): NavIconComponent;
43
+ /** @deprecated Prefer `resolveNavIcon`. */
44
+ export declare function iconForNavKey(key: string): NavIconComponent;
17
45
  export {};
18
46
  //# sourceMappingURL=icons.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../../src/components/ui/icons.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEtC,KAAK,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;AAiBzC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAMxC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,+BAO5C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,+BAQ3C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BAOzC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAOxC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BAOzC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,+BAO5C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BAMzC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,+BAM/C;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,+BAO1C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BASxC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAQxC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BASzC;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,mBAYxC"}
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../../src/components/ui/icons.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACpD,OAAO,KAAK,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEzE,KAAK,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;AAiBzC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAMxC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,+BAO5C;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,+BAS7C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BASzC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAQxC;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,+BAOvC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAMxC;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,+BAQ3C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BAOzC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAOxC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BAOzC;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,+BAS3C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BASzC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BAOzC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAMxC;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,+BAQ7C;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,+BAO1C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAOxC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,SAAS,+BAO5C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BAMzC;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,SAAS,+BAM/C;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,+BAO1C;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BASxC;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,+BAQxC;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,+BASzC;AAED,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,SAAS,KAAK,YAAY,CAAC;AAElE,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,WAAW,EAAE,gBAAgB,CAuBrE,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,oBAAoB,CAAC;IAChC,OAAO,CAAC,EAAE,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IACtC,IAAI,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;CAC3B,CAAC;AA8FF,sDAAsD;AACtD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,mBAAmB,GAAG,WAAW,CAY1E;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,gBAAgB,CAE3E;AAED,2CAA2C;AAC3C,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAE3D"}
@@ -19,6 +19,21 @@ export function IconHome(props) {
19
19
  export function IconProducts(props) {
20
20
  return (_jsxs("svg", { ...base(props), children: [_jsx("path", { d: "M3 6.5 10 2.5l7 4v9H3v-9z" }), _jsx("path", { d: "M7 11.5h6M7 14h4" })] }));
21
21
  }
22
+ export function IconInventory(props) {
23
+ return (_jsxs("svg", { ...base(props), children: [_jsx("path", { d: "M3.5 7h13v9.5H3.5z" }), _jsx("path", { d: "M3.5 10.5h13" }), _jsx("path", { d: "M7 7V5.5h6V7" }), _jsx("path", { d: "M7 13h2M11 13h2" })] }));
24
+ }
25
+ export function IconUsers(props) {
26
+ return (_jsxs("svg", { ...base(props), children: [_jsx("circle", { cx: "7.5", cy: "7", r: "2.25" }), _jsx("path", { d: "M3.5 15.5c.4-2.4 2-3.75 4-3.75s3.6 1.35 4 3.75" }), _jsx("circle", { cx: "13.5", cy: "7.25", r: "1.75" }), _jsx("path", { d: "M12.2 11.9c1.35.2 2.55 1.1 2.9 3.1" })] }));
27
+ }
28
+ export function IconCard(props) {
29
+ return (_jsxs("svg", { ...base(props), children: [_jsx("rect", { x: "2.5", y: "5", width: "15", height: "10.5", rx: "1.5" }), _jsx("path", { d: "M2.5 8.5h15" }), _jsx("path", { d: "M5.5 12.5h4" })] }));
30
+ }
31
+ export function IconTag(props) {
32
+ return (_jsxs("svg", { ...base(props), children: [_jsx("path", { d: "M3.5 10.5V4.5H9.5l7 7-5 5-8-6z" }), _jsx("circle", { cx: "7", cy: "7", r: "1", fill: "currentColor", stroke: "none" })] }));
33
+ }
34
+ export function IconBolt(props) {
35
+ return (_jsx("svg", { ...base(props), children: _jsx("path", { d: "M11 2.5 5.5 11h4L9 17.5 14.5 9h-4L11 2.5z" }) }));
36
+ }
22
37
  export function IconGallery(props) {
23
38
  return (_jsxs("svg", { ...base(props), children: [_jsx("rect", { x: "2.5", y: "4.5", width: "15", height: "11", rx: "1.5" }), _jsx("path", { d: "m3.5 13 3.5-3.5 2.5 2.5L12.5 9l4 4.5" }), _jsx("circle", { cx: "7", cy: "7.5", r: "1", fill: "currentColor", stroke: "none" })] }));
24
39
  }
@@ -26,11 +41,32 @@ export function IconFonts(props) {
26
41
  return (_jsxs("svg", { ...base(props), children: [_jsx("path", { d: "M5 16.5V5.5h10M7.5 16.5V9h5v7.5" }), _jsx("path", { d: "M7 5.5h6" })] }));
27
42
  }
28
43
  export function IconMail(props) {
29
- return (_jsxs("svg", { ...base(props), children: [_jsx("rect", { x: "2.5", y: "4.5", width: "15", height: "11", rx: "1.5" }), _jsx("path", { d: "m3.5 6.5 6.5 4.5 3.5 2.5 6.5-2.5 6.5 4.5" })] }));
44
+ return (_jsxs("svg", { ...base(props), children: [_jsx("rect", { x: "2.5", y: "4.5", width: "15", height: "11", rx: "1.5" }), _jsx("path", { d: "m3.5 6.5 6.5 4.5 6.5-4.5" })] }));
30
45
  }
31
46
  export function IconPages(props) {
32
47
  return (_jsxs("svg", { ...base(props), children: [_jsx("path", { d: "M6 3.5h5.5L16 8v8.5a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1v-12a1 1 0 0 1 1-1z" }), _jsx("path", { d: "M11.5 3.5V8H16" })] }));
33
48
  }
49
+ export function IconPalette(props) {
50
+ return (_jsxs("svg", { ...base(props), children: [_jsx("path", { d: "M10 3a7 7 0 1 0 0 14h1.2a1.8 1.8 0 0 0 0-3.6H10a2 2 0 0 1 0-4h3.5" }), _jsx("circle", { cx: "7.2", cy: "8", r: "0.9", fill: "currentColor", stroke: "none" }), _jsx("circle", { cx: "8.4", cy: "11.2", r: "0.9", fill: "currentColor", stroke: "none" }), _jsx("circle", { cx: "12.2", cy: "7.2", r: "0.9", fill: "currentColor", stroke: "none" })] }));
51
+ }
52
+ export function IconTruck(props) {
53
+ return (_jsxs("svg", { ...base(props), children: [_jsx("path", { d: "M2.5 13.5V6.5h9v7" }), _jsx("path", { d: "M11.5 9.5h4l2 2.5v1.5h-6" }), _jsx("circle", { cx: "6", cy: "14.5", r: "1.5" }), _jsx("circle", { cx: "14.5", cy: "14.5", r: "1.5" })] }));
54
+ }
55
+ export function IconClock(props) {
56
+ return (_jsxs("svg", { ...base(props), children: [_jsx("circle", { cx: "10", cy: "10", r: "6.5" }), _jsx("path", { d: "M10 6.5V10l3 2" })] }));
57
+ }
58
+ export function IconStar(props) {
59
+ return (_jsx("svg", { ...base(props), children: _jsx("path", { d: "m10 3.5 1.8 3.7 4.1.6-3 2.9.7 4.1L10 13.2 6.4 14.8l.7-4.1-3-2.9 4.1-.6L10 3.5z" }) }));
60
+ }
61
+ export function IconMegaphone(props) {
62
+ return (_jsxs("svg", { ...base(props), children: [_jsx("path", { d: "M4.5 11.5v-3l10-3.5v10L4.5 11.5z" }), _jsx("path", { d: "M4.5 8.5v5.5a2 2 0 0 0 2 0V11" }), _jsx("path", { d: "M15.5 7.5v5" })] }));
63
+ }
64
+ export function IconLayout(props) {
65
+ return (_jsxs("svg", { ...base(props), children: [_jsx("rect", { x: "3", y: "3.5", width: "14", height: "13", rx: "1.5" }), _jsx("path", { d: "M3 8h14M9 8v8.5" })] }));
66
+ }
67
+ export function IconList(props) {
68
+ return (_jsxs("svg", { ...base(props), children: [_jsx("path", { d: "M7 5.5h9.5M7 10h9.5M7 14.5h9.5" }), _jsx("path", { d: "M4 5.5h.01M4 10h.01M4 14.5h.01" })] }));
69
+ }
34
70
  export function IconSettings(props) {
35
71
  return (_jsxs("svg", { ...base(props), children: [_jsx("circle", { cx: "10", cy: "10", r: "2.25" }), _jsx("path", { d: "M10 2.5v2M10 15.5v2M3.5 10h2M14.5 10h2M5.4 5.4l1.4 1.4M13.2 13.2l1.4 1.4M5.4 14.6l1.4-1.4M13.2 6.8l1.4-1.4" })] }));
36
72
  }
@@ -52,26 +88,174 @@ export function IconMenu(props) {
52
88
  export function IconAddon(props) {
53
89
  return (_jsxs("svg", { ...base(props), children: [_jsx("path", { d: "M8 3.5h4v4H8z" }), _jsx("path", { d: "M3.5 8h4v4h-4z" }), _jsx("path", { d: "M12.5 8h4v4h-4z" }), _jsx("path", { d: "M8 12.5h4v4H8z" })] }));
54
90
  }
55
- export function iconForNavKey(key) {
91
+ export const NAV_ICON_COMPONENTS = {
92
+ home: IconHome,
93
+ products: IconProducts,
94
+ inventory: IconInventory,
95
+ users: IconUsers,
96
+ card: IconCard,
97
+ tag: IconTag,
98
+ bolt: IconBolt,
99
+ gallery: IconGallery,
100
+ fonts: IconFonts,
101
+ mail: IconMail,
102
+ pages: IconPages,
103
+ palette: IconPalette,
104
+ truck: IconTruck,
105
+ clock: IconClock,
106
+ star: IconStar,
107
+ megaphone: IconMegaphone,
108
+ layout: IconLayout,
109
+ list: IconList,
110
+ chart: IconChart,
111
+ sync: IconSync,
112
+ addon: IconAddon,
113
+ settings: IconSettings,
114
+ };
115
+ function normalize(value) {
116
+ return (value ?? "").trim().toLowerCase().replace(/[_-]+/g, " ");
117
+ }
118
+ function haystack(input) {
119
+ return `${normalize(input.id)} ${normalize(input.label)}`.trim();
120
+ }
121
+ function matchHeuristic(text) {
122
+ if (!text)
123
+ return null;
124
+ if (/\binventory\b|\bstock\b|\bsku\b/.test(text))
125
+ return "inventory";
126
+ if (/\bwaitlist\b|\bsignups?\b|\bcustomers?\b|\bsubscribers?\b/.test(text))
127
+ return "users";
128
+ if (/\bwebhooks?\b|\bhooks?\b/.test(text))
129
+ return "bolt";
130
+ if (/\bdiscounts?\b|\bcoupons?\b|\bpromos?\b|\boffers?\b/.test(text))
131
+ return "tag";
132
+ if (/\bsessions?\b|\bpayments?\b|\bcheckout\b/.test(text))
133
+ return "card";
134
+ if (/\bstripe\b/.test(text))
135
+ return "card";
136
+ if (/\btheme\b|\bbrand(?:ing)?\b|\bappearance\b|\bcolors?\b/.test(text))
137
+ return "palette";
138
+ if (/\bshipping\b|\bfulfill\b|\bdelivery\b|\btruck\b/.test(text))
139
+ return "truck";
140
+ if (/\brefunds?\b|\breturns?\b/.test(text))
141
+ return "sync";
142
+ if (/\bterms\b|\bpolic(?:y|ies)\b|\blegal\b|\bprivacy\b/.test(text))
143
+ return "pages";
144
+ if (/\bproduct page\b|\blayout\b|\bblocks?\b/.test(text))
145
+ return "layout";
146
+ if (/\bpre[- ]?order\b|\bcountdown\b|\bschedule\b|\brelease\b/.test(text))
147
+ return "clock";
148
+ if (/\breviews?\b|\bratings?\b|\btestimonials?\b/.test(text))
149
+ return "star";
150
+ if (/\bannouncements?\b|\bbanners?\b|\bmegaphone\b|\balerts?\b/.test(text))
151
+ return "megaphone";
152
+ if (/\blookbook\b|\bpress\b|\blogos?\b|\bsize guide\b|\bguide\b/.test(text))
153
+ return "list";
154
+ if (/\bback in stock\b|\bfree shipping\b|\bthreshold\b/.test(text))
155
+ return "tag";
156
+ if (/\bemails?\b|\bmail\b|\bcampaigns?\b/.test(text))
157
+ return "mail";
158
+ if (/\bfonts?\b|\btypography\b/.test(text))
159
+ return "fonts";
160
+ if (/\bgallery\b|\bfiles?\b|\bmedia\b|\bimages?\b/.test(text))
161
+ return "gallery";
162
+ if (/\baudit\b|\bmetrics?\b|\banalytics\b/.test(text))
163
+ return "chart";
164
+ if (/\bsync\b|\benvironments?\b/.test(text))
165
+ return "sync";
166
+ if (/\bgeneral\b|\bsettings?\b/.test(text))
167
+ return "settings";
168
+ if (/\bproducts?\b|\bcatalog\b/.test(text))
169
+ return "products";
170
+ if (/\bhome\b|\boverview\b/.test(text))
171
+ return "home";
172
+ if (/\bnav(?:igation)?\b|\bmenu\b/.test(text))
173
+ return "list";
174
+ if (/\babout\b|\bcoming soon\b|\bpages?\b/.test(text))
175
+ return "pages";
176
+ return null;
177
+ }
178
+ function matchGroup(group) {
179
+ const g = normalize(group);
180
+ if (!g)
181
+ return null;
182
+ if (g.includes("payment"))
183
+ return "card";
184
+ if (g.includes("marketing") || g.includes("growth") || g.includes("customer"))
185
+ return "users";
186
+ if (g.includes("product") || g.includes("catalog"))
187
+ return "products";
188
+ if (g.includes("online store") ||
189
+ g.includes("pages") ||
190
+ g.includes("storefront"))
191
+ return "pages";
192
+ if (g.includes("setting") || g.includes("general"))
193
+ return "settings";
194
+ if (g.includes("content") || g.includes("media"))
195
+ return "gallery";
196
+ if (g.includes("analytic") || g.includes("home"))
197
+ return "chart";
198
+ if (g.includes("add-on") || g.includes("addon"))
199
+ return "addon";
200
+ return null;
201
+ }
202
+ function matchCategory(category) {
203
+ if (category === "product")
204
+ return "products";
205
+ if (category === "engagement")
206
+ return "megaphone";
207
+ if (category === "sales")
208
+ return "tag";
209
+ return null;
210
+ }
211
+ function kindDefault(key, section) {
56
212
  if (key === "overview")
57
- return IconHome;
213
+ return "home";
58
214
  if (key === "audit")
59
- return IconChart;
60
- if (key.startsWith("resource:"))
61
- return IconProducts;
215
+ return "chart";
62
216
  if (key === "gallery")
63
- return IconGallery;
217
+ return "gallery";
64
218
  if (key === "fonts")
65
- return IconFonts;
219
+ return "fonts";
66
220
  if (key === "emails")
67
- return IconMail;
221
+ return "mail";
68
222
  if (key === "sync")
69
- return IconSync;
223
+ return "sync";
70
224
  if (key === "addons:overview")
71
- return IconAddon;
225
+ return "addon";
72
226
  if (key.startsWith("addon:"))
73
- return IconAddon;
74
- if (key.startsWith("config:"))
75
- return IconPages;
76
- return IconSettings;
227
+ return "addon";
228
+ if (key.startsWith("resource:"))
229
+ return "products";
230
+ if (key.startsWith("config:")) {
231
+ if (section === "page")
232
+ return "pages";
233
+ return "settings";
234
+ }
235
+ return "settings";
236
+ }
237
+ /** Resolve a stable icon name for a sidebar entry. */
238
+ export function resolveNavIconName(input) {
239
+ if (input.icon && input.icon in NAV_ICON_COMPONENTS)
240
+ return input.icon;
241
+ const text = haystack(input);
242
+ const fromText = matchHeuristic(text);
243
+ if (fromText)
244
+ return fromText;
245
+ if (input.key.startsWith("addon:") || input.section === "addon") {
246
+ const fromCategory = matchCategory(input.category);
247
+ if (fromCategory)
248
+ return fromCategory;
249
+ }
250
+ const fromGroup = matchGroup(input.group);
251
+ if (fromGroup)
252
+ return fromGroup;
253
+ return kindDefault(input.key, input.section);
254
+ }
255
+ export function resolveNavIcon(input) {
256
+ return NAV_ICON_COMPONENTS[resolveNavIconName(input)];
257
+ }
258
+ /** @deprecated Prefer `resolveNavIcon`. */
259
+ export function iconForNavKey(key) {
260
+ return resolveNavIcon({ key });
77
261
  }
@@ -0,0 +1,14 @@
1
+ import type { FieldSchema, GalleryItem } from "@vertebr/client";
2
+ import { looksLikeUrl } from "./images";
3
+ /** True when a gallery/file item is a PDF (by type, name, or URL). */
4
+ export declare function isPdfAsset(item: {
5
+ url?: string;
6
+ name?: string | null;
7
+ contentType?: string | null;
8
+ }): boolean;
9
+ /** Config string fields meant for a policy/document PDF URL. */
10
+ export declare function isDocumentField(key: string, field?: Pick<FieldSchema, "label" | "type">): boolean;
11
+ export declare function pdfFileName(url: string, fallback?: string): string;
12
+ export declare function filterGalleryByKind(items: GalleryItem[], kind: "image" | "document" | "any"): GalleryItem[];
13
+ export { looksLikeUrl };
14
+ //# sourceMappingURL=documents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents.d.ts","sourceRoot":"","sources":["../../src/lib/documents.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,sEAAsE;AACtE,wBAAgB,UAAU,CAAC,IAAI,EAAE;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,GAAG,OAAO,CAMV;AAED,gEAAgE;AAChE,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,GAAG,MAAM,CAAC,GAC1C,OAAO,CAUT;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAiB,GAAG,MAAM,CAQ1E;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,WAAW,EAAE,EACpB,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,KAAK,GACjC,WAAW,EAAE,CAIf;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { looksLikeUrl } from "./images";
2
+ /** True when a gallery/file item is a PDF (by type, name, or URL). */
3
+ export function isPdfAsset(item) {
4
+ if (item.contentType && /pdf/i.test(item.contentType))
5
+ return true;
6
+ const name = item.name ?? "";
7
+ if (/\.pdf$/i.test(name))
8
+ return true;
9
+ const url = item.url ?? "";
10
+ return /\.pdf(?:$|\?)/i.test(url);
11
+ }
12
+ /** Config string fields meant for a policy/document PDF URL. */
13
+ export function isDocumentField(key, field) {
14
+ if (field?.type === "file[]")
15
+ return false;
16
+ if (field?.type && field.type !== "string")
17
+ return false;
18
+ const hay = `${key} ${field?.label ?? ""}`;
19
+ if (/\bpdf\b|pdfUrl|pdf_url|documentUrl|document_url/i.test(hay))
20
+ return true;
21
+ // Policy-ish labels only when they also look like a file/URL attachment.
22
+ return (/(policy|legal|terms|privacy|refund|shipping)/i.test(hay) &&
23
+ /(pdf|url|file|attach|document)/i.test(hay));
24
+ }
25
+ export function pdfFileName(url, fallback = "document.pdf") {
26
+ try {
27
+ const path = new URL(url).pathname;
28
+ const base = path.split("/").pop() || fallback;
29
+ return decodeURIComponent(base);
30
+ }
31
+ catch {
32
+ return fallback;
33
+ }
34
+ }
35
+ export function filterGalleryByKind(items, kind) {
36
+ if (kind === "any")
37
+ return items;
38
+ if (kind === "document")
39
+ return items.filter(isPdfAsset);
40
+ return items.filter((item) => !isPdfAsset(item));
41
+ }
42
+ export { looksLikeUrl };
@@ -1047,6 +1047,21 @@
1047
1047
  background: var(--vb-surface-tint);
1048
1048
  }
1049
1049
 
1050
+ .vertebr-md-doc-link {
1051
+ appearance: none;
1052
+ border: 0;
1053
+ padding: 0;
1054
+ background: transparent;
1055
+ color: var(--vb-accent);
1056
+ font: inherit;
1057
+ letter-spacing: inherit;
1058
+ cursor: pointer;
1059
+ }
1060
+
1061
+ .vertebr-md-doc-link:hover {
1062
+ text-decoration: underline;
1063
+ }
1064
+
1050
1065
  .vertebr-field input:hover,
1051
1066
  .vertebr-field textarea:hover {
1052
1067
  border-color: var(--vb-border-strong);