@vertebr/react 0.4.0 → 0.5.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 (82) hide show
  1. package/dist/components/admin/AdminContent.d.ts +36 -0
  2. package/dist/components/admin/AdminContent.d.ts.map +1 -0
  3. package/dist/components/admin/AdminContent.js +107 -0
  4. package/dist/components/admin/AdminHeader.d.ts +13 -0
  5. package/dist/components/admin/AdminHeader.d.ts.map +1 -0
  6. package/dist/components/admin/AdminHeader.js +32 -0
  7. package/dist/components/admin/AdminSidebar.d.ts +25 -0
  8. package/dist/components/admin/AdminSidebar.d.ts.map +1 -0
  9. package/dist/components/admin/AdminSidebar.js +31 -0
  10. package/dist/components/admin/VertebrAdmin.d.ts +3 -18
  11. package/dist/components/admin/VertebrAdmin.d.ts.map +1 -1
  12. package/dist/components/admin/VertebrAdmin.js +130 -120
  13. package/dist/components/admin/sidebar.d.ts +3 -0
  14. package/dist/components/admin/sidebar.d.ts.map +1 -0
  15. package/dist/components/admin/sidebar.js +13 -0
  16. package/dist/components/admin/storage.d.ts +4 -0
  17. package/dist/components/admin/storage.d.ts.map +1 -0
  18. package/dist/components/admin/storage.js +24 -0
  19. package/dist/components/admin/types.d.ts +53 -0
  20. package/dist/components/admin/types.d.ts.map +1 -0
  21. package/dist/components/admin/types.js +1 -0
  22. package/dist/components/config/form/ConfigForm.d.ts +1 -1
  23. package/dist/components/config/form/ConfigForm.d.ts.map +1 -1
  24. package/dist/components/config/form/ConfigForm.js +5 -1
  25. package/dist/components/config/form/RecordConfigForm.d.ts +1 -0
  26. package/dist/components/config/form/RecordConfigForm.d.ts.map +1 -1
  27. package/dist/components/config/form/SettingsConfigForm.d.ts +2 -1
  28. package/dist/components/config/form/SettingsConfigForm.d.ts.map +1 -1
  29. package/dist/components/config/form/SettingsConfigForm.js +2 -2
  30. package/dist/components/config/form/helpers/fields.js +1 -1
  31. package/dist/components/config/form/types.d.ts +4 -0
  32. package/dist/components/config/form/types.d.ts.map +1 -1
  33. package/dist/components/emails/EmailRecipientSelect.d.ts +16 -0
  34. package/dist/components/emails/EmailRecipientSelect.d.ts.map +1 -0
  35. package/dist/components/emails/EmailRecipientSelect.js +10 -0
  36. package/dist/components/emails/EmailsActivityPanel.d.ts +11 -0
  37. package/dist/components/emails/EmailsActivityPanel.d.ts.map +1 -0
  38. package/dist/components/emails/EmailsActivityPanel.js +139 -0
  39. package/dist/components/emails/EmailsBoard.d.ts +47 -0
  40. package/dist/components/emails/EmailsBoard.d.ts.map +1 -0
  41. package/dist/components/emails/EmailsBoard.js +594 -0
  42. package/dist/components/emails/EmailsLibraryPanel.d.ts +25 -0
  43. package/dist/components/emails/EmailsLibraryPanel.d.ts.map +1 -0
  44. package/dist/components/emails/EmailsLibraryPanel.js +379 -0
  45. package/dist/components/emails/EmailsNavSelect.d.ts +18 -0
  46. package/dist/components/emails/EmailsNavSelect.d.ts.map +1 -0
  47. package/dist/components/emails/EmailsNavSelect.js +37 -0
  48. package/dist/components/emails/MultiCheckSelect.d.ts +18 -0
  49. package/dist/components/emails/MultiCheckSelect.d.ts.map +1 -0
  50. package/dist/components/emails/MultiCheckSelect.js +74 -0
  51. package/dist/components/metrics/MetricsBoard.d.ts +8 -2
  52. package/dist/components/metrics/MetricsBoard.d.ts.map +1 -1
  53. package/dist/components/metrics/MetricsBoard.js +55 -2
  54. package/dist/components/metrics/charts.d.ts +14 -0
  55. package/dist/components/metrics/charts.d.ts.map +1 -0
  56. package/dist/components/metrics/charts.js +212 -0
  57. package/dist/components/resources/ResourceActionModal.d.ts +14 -0
  58. package/dist/components/resources/ResourceActionModal.d.ts.map +1 -0
  59. package/dist/components/resources/ResourceActionModal.js +64 -0
  60. package/dist/components/resources/ResourceCell.d.ts.map +1 -1
  61. package/dist/components/resources/ResourceCell.js +25 -9
  62. package/dist/components/resources/ResourceMailModal.d.ts +12 -0
  63. package/dist/components/resources/ResourceMailModal.d.ts.map +1 -0
  64. package/dist/components/resources/ResourceMailModal.js +196 -0
  65. package/dist/components/resources/ResourceTable.d.ts +7 -3
  66. package/dist/components/resources/ResourceTable.d.ts.map +1 -1
  67. package/dist/components/resources/ResourceTable.js +204 -34
  68. package/dist/index.d.ts +7 -1
  69. package/dist/index.d.ts.map +1 -1
  70. package/dist/index.js +1 -0
  71. package/dist/lib/metricsRanges.d.ts +20 -0
  72. package/dist/lib/metricsRanges.d.ts.map +1 -0
  73. package/dist/lib/metricsRanges.js +14 -0
  74. package/dist/styles/cells.css +42 -0
  75. package/dist/styles/emails.css +632 -0
  76. package/dist/styles/index.css +3 -0
  77. package/dist/styles/mail.css +240 -0
  78. package/dist/styles/metrics.css +633 -0
  79. package/dist/styles/nav.css +103 -1
  80. package/dist/styles/panel.css +2 -68
  81. package/dist/styles/table.css +26 -3
  82. package/package.json +1 -1
@@ -0,0 +1,196 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState } from "react";
3
+ import { EnumInput } from "../config/EnumInput";
4
+ import { actionInputDefaults } from "./ResourceActionModal";
5
+ /** Presets that still need subject + body from the operator. */
6
+ const FREEFORM_PRESETS = new Set(["custom", "promo", "custom_discount"]);
7
+ const DISCOUNT_PRESETS = new Set([
8
+ "discount",
9
+ "welcome_offer",
10
+ "flash_sale",
11
+ "custom_discount",
12
+ ]);
13
+ const DISCOUNT_FIELD_KEYS = new Set([
14
+ "discountId",
15
+ "discountCode",
16
+ "discountOffer",
17
+ "discountExpires",
18
+ ]);
19
+ function formatRecipients(to) {
20
+ return Array.isArray(to) ? to.join(", ") : to;
21
+ }
22
+ function fieldValue(value) {
23
+ return typeof value === "string" ? value : String(value ?? "");
24
+ }
25
+ function isMultiline(key, field) {
26
+ const hay = `${key} ${field.label ?? ""}`;
27
+ return field.type === "richtext" || /(body|message|html|notes)/i.test(hay);
28
+ }
29
+ function needsFreeformCopy(schema, value) {
30
+ if (!schema.preset)
31
+ return true;
32
+ const preset = fieldValue(value.preset);
33
+ return !preset || FREEFORM_PRESETS.has(preset);
34
+ }
35
+ function isDiscountPreset(preset) {
36
+ return DISCOUNT_PRESETS.has(preset);
37
+ }
38
+ function fieldVisible(key, schema, value) {
39
+ if (key === "subject" || key === "body") {
40
+ return needsFreeformCopy(schema, value);
41
+ }
42
+ if (DISCOUNT_FIELD_KEYS.has(key)) {
43
+ const preset = fieldValue(value.preset);
44
+ if (!isDiscountPreset(preset))
45
+ return false;
46
+ if (key === "discountId" && !(schema.discountId?.options?.length)) {
47
+ return false;
48
+ }
49
+ return true;
50
+ }
51
+ return true;
52
+ }
53
+ function requiredFilled(schema, value) {
54
+ return Object.entries(schema).every(([key, field]) => {
55
+ if (!fieldVisible(key, schema, value))
56
+ return true;
57
+ const freeformCopy = (key === "subject" || key === "body") &&
58
+ needsFreeformCopy(schema, value);
59
+ const discountRequired = (key === "discountCode" || key === "discountOffer") &&
60
+ isDiscountPreset(fieldValue(value.preset));
61
+ if (field.required === false && !freeformCopy && !discountRequired) {
62
+ return true;
63
+ }
64
+ if (field.type !== "string" &&
65
+ field.type !== "richtext" &&
66
+ field.type !== "enum") {
67
+ return true;
68
+ }
69
+ return Boolean(fieldValue(value[key]).trim());
70
+ });
71
+ }
72
+ function errorMessage(error) {
73
+ if (!(error instanceof Error))
74
+ return "Request failed";
75
+ const raw = error.message.trim();
76
+ if (!raw.startsWith("{"))
77
+ return raw;
78
+ try {
79
+ const parsed = JSON.parse(raw);
80
+ if (typeof parsed.error === "string" && parsed.error.trim()) {
81
+ return parsed.error;
82
+ }
83
+ if (parsed.error &&
84
+ typeof parsed.error === "object" &&
85
+ typeof parsed.error.message === "string" &&
86
+ parsed.error.message.trim()) {
87
+ return parsed.error.message;
88
+ }
89
+ if (typeof parsed.message === "string" && parsed.message.trim()) {
90
+ return parsed.message;
91
+ }
92
+ }
93
+ catch {
94
+ /* keep raw */
95
+ }
96
+ return raw;
97
+ }
98
+ export function ResourceMailModal({ title, count, schema, submitLabel, onClose, onPreview, onSubmit, }) {
99
+ const defaults = useMemo(() => actionInputDefaults(schema), [schema]);
100
+ const fields = useMemo(() => Object.entries(schema), [schema]);
101
+ const hasFields = fields.length > 0;
102
+ const [draft, setDraft] = useState(defaults);
103
+ const [preview, setPreview] = useState(null);
104
+ const [previewError, setPreviewError] = useState(null);
105
+ const [previewing, setPreviewing] = useState(false);
106
+ const [sending, setSending] = useState(false);
107
+ const canSend = !sending && (!hasFields || requiredFilled(schema, draft));
108
+ const visibleFields = fields.filter(([key]) => fieldVisible(key, schema, draft));
109
+ const loadPreview = useCallback(async (value) => {
110
+ if (hasFields && !requiredFilled(schema, value)) {
111
+ setPreview(null);
112
+ setPreviewError(null);
113
+ return;
114
+ }
115
+ setPreviewing(true);
116
+ setPreviewError(null);
117
+ try {
118
+ const next = await onPreview(value);
119
+ setPreview(next);
120
+ }
121
+ catch (error) {
122
+ setPreview(null);
123
+ setPreviewError(errorMessage(error));
124
+ }
125
+ finally {
126
+ setPreviewing(false);
127
+ }
128
+ }, [hasFields, onPreview, schema]);
129
+ useEffect(() => {
130
+ const onKeyDown = (event) => {
131
+ if (event.key === "Escape")
132
+ onClose();
133
+ };
134
+ window.addEventListener("keydown", onKeyDown);
135
+ const previous = document.body.style.overflow;
136
+ document.body.style.overflow = "hidden";
137
+ return () => {
138
+ window.removeEventListener("keydown", onKeyDown);
139
+ document.body.style.overflow = previous;
140
+ };
141
+ }, [onClose]);
142
+ useEffect(() => {
143
+ if (!hasFields) {
144
+ void loadPreview({});
145
+ return;
146
+ }
147
+ const timer = window.setTimeout(() => {
148
+ void loadPreview(draft);
149
+ }, 280);
150
+ return () => window.clearTimeout(timer);
151
+ }, [draft, hasFields, loadPreview]);
152
+ const send = async () => {
153
+ if (!canSend)
154
+ return;
155
+ setSending(true);
156
+ try {
157
+ await onSubmit(hasFields ? draft : {});
158
+ onClose();
159
+ }
160
+ catch (error) {
161
+ setPreviewError(errorMessage(error));
162
+ }
163
+ finally {
164
+ setSending(false);
165
+ }
166
+ };
167
+ return (_jsx("div", { className: "vertebr-modal-backdrop", role: "presentation", onClick: onClose, children: _jsxs("div", { className: "vertebr-modal vertebr-modal-form vertebr-mail-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: count === 1 ? "1 person" : `${count} people` }), _jsx("h2", { children: title })] }), _jsx("div", { className: "vertebr-modal-actions", children: _jsx("button", { type: "button", className: "vertebr-btn", onClick: onClose, "aria-label": "Close", children: "Close" }) })] }), _jsxs("div", { className: "vertebr-mail-layout", children: [_jsxs("form", { className: "vertebr-mail-compose", onSubmit: (event) => {
168
+ event.preventDefault();
169
+ void send();
170
+ }, children: [hasFields ? (_jsxs("div", { className: "vertebr-mail-fields", children: [visibleFields.map(([key, field]) => {
171
+ const multiline = isMultiline(key, field);
172
+ return (_jsxs("label", { className: "vertebr-mail-field", children: [_jsx("span", { children: field.label ?? key }), field.type === "enum" ? (_jsx(EnumInput, { id: `mail-${key}`, value: draft[key], field: field, onChange: (next) => setDraft((current) => ({
173
+ ...current,
174
+ [key]: next,
175
+ })) })) : multiline ? (_jsx("textarea", { rows: 7, value: fieldValue(draft[key]), placeholder: "Plain text \u2014 the host template styles it.", onChange: (event) => setDraft((current) => ({
176
+ ...current,
177
+ [key]: event.target.value,
178
+ })) })) : (_jsx("input", { type: "text", value: fieldValue(draft[key]), onChange: (event) => setDraft((current) => ({
179
+ ...current,
180
+ [key]: event.target.value,
181
+ })) }))] }, key));
182
+ }), schema.preset && !needsFreeformCopy(schema, draft) ? (_jsx("p", { className: "vertebr-mail-compose-hint", children: "Preview updates from the selected preset. Switch to Custom to write your own copy." })) : null] })) : (_jsx("p", { className: "vertebr-mail-compose-note", children: "Host template \u2014 Vertebr only delivers it." })), previewError ? (_jsx("p", { className: "vertebr-mail-compose-error", children: previewError })) : null, _jsx("div", { className: "vertebr-mail-compose-foot", children: _jsx("button", { type: "submit", className: "vertebr-btn vertebr-btn-primary", disabled: !canSend, children: sending ? "Sending…" : submitLabel }) })] }), _jsxs("div", { className: "vertebr-mail-preview", children: [_jsxs("div", { className: "vertebr-mail-preview-meta", children: [_jsxs("p", { children: [_jsx("span", { children: "To" }), _jsx("strong", { children: preview
183
+ ? formatRecipients(preview.to)
184
+ : count === 1
185
+ ? "—"
186
+ : `${count} recipients` })] }), _jsxs("p", { children: [_jsx("span", { children: "Subject" }), _jsx("strong", { children: preview?.subject ?? (previewing ? "Updating…" : "—") })] })] }), _jsx("div", { className: "vertebr-mail-preview-stage", children: _jsx("div", { className: "vertebr-mail-preview-frame", children: preview?.html ? (_jsx("iframe", { title: "Email preview", sandbox: "", srcDoc: preview.html })) : preview?.text ? (_jsx("pre", { children: preview.text })) : (_jsx("div", { className: "vertebr-mail-preview-empty", children: previewError ??
187
+ (previewing
188
+ ? "Rendering host template…"
189
+ : hasFields
190
+ ? schema.preset && needsFreeformCopy(schema, draft)
191
+ ? "Write a subject and body to preview."
192
+ : schema.preset
193
+ ? "Pick a preset to preview."
194
+ : "Write a subject and body to preview."
195
+ : "Loading preview…") })) }) })] })] })] }) }));
196
+ }
@@ -1,4 +1,4 @@
1
- import type { FieldSchema, GalleryItem, ResourceActionMeta, ResourceUploadMeta } from "@vertebr/client";
1
+ import type { FieldSchema, GalleryItem, MailPreviewResponse, ResourceActionMeta, ResourceUploadMeta } from "@vertebr/client";
2
2
  type ResourcePanelProps = {
3
3
  schema: Record<string, FieldSchema>;
4
4
  items: Record<string, unknown>[];
@@ -17,7 +17,11 @@ type ResourcePanelProps = {
17
17
  onUpdate?: (rowId: string, value: Record<string, unknown>) => Promise<void>;
18
18
  onCreate?: (value: Record<string, unknown>) => Promise<void>;
19
19
  onDelete?: (rowId: string) => Promise<void>;
20
- onAction?: (rowId: string, actionId: string) => Promise<void>;
20
+ onAction?: (rowId: string, actionId: string, input?: Record<string, unknown>) => Promise<void>;
21
+ onActionDone?: () => Promise<void> | void;
22
+ onPreviewMail?: (rowId: string, actionId: string, input?: Record<string, unknown>) => Promise<MailPreviewResponse>;
23
+ collectionActions?: Record<string, ResourceActionMeta>;
24
+ onCollectionAction?: (actionId: string, input?: Record<string, unknown>) => Promise<void>;
21
25
  onUpload?: (field: string, files: File[]) => Promise<Array<{
22
26
  url: string;
23
27
  name?: string;
@@ -26,7 +30,7 @@ type ResourcePanelProps = {
26
30
  onListGallery?: () => Promise<GalleryItem[]>;
27
31
  onUploadGallery?: (files: File[]) => Promise<GalleryItem[]>;
28
32
  };
29
- export declare function ResourcePanel({ schema, items, permissions, actions, label, uploads, rowIdKeys, storageKey, onUpdate, onCreate, onDelete, onAction, onUpload, galleryUpload, onListGallery, onUploadGallery, }: ResourcePanelProps): import("react").JSX.Element;
33
+ export declare function ResourcePanel({ schema, items, permissions, actions, label, uploads, rowIdKeys, storageKey, onUpdate, onCreate, onDelete, onAction, onActionDone, onPreviewMail, collectionActions, onCollectionAction, onUpload, galleryUpload, onListGallery, onUploadGallery, }: ResourcePanelProps): import("react").JSX.Element;
30
34
  /** @deprecated Prefer ResourcePanel */
31
35
  export declare function ResourceTable(props: {
32
36
  schema: Record<string, FieldSchema>;
@@ -1 +1 @@
1
- {"version":3,"file":"ResourceTable.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceTable.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AAOzB,KAAK,kBAAkB,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACjC,WAAW,CAAC,EAAE;QACZ,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,CACT,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,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,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;CAC7D,CAAC;AA0EF,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAY,EACZ,KAAK,EACL,OAAO,EACP,SAA0C,EAC1C,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,aAAa,EACb,eAAe,GAChB,EAAE,kBAAkB,+BAqdpB;AAED,uCAAuC;AACvC,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CAClC,+BAEA"}
1
+ {"version":3,"file":"ResourceTable.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceTable.tsx"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table";
4
4
  import { ConfigForm } from "../config/ConfigForm";
@@ -6,7 +6,23 @@ import { BulkImageUploadModal } from "./BulkImageUploadModal";
6
6
  import { ColumnVisibilityMenu } from "./ColumnVisibilityMenu";
7
7
  import { ResourceCell } from "./ResourceCell";
8
8
  import { ResourceCreateModal } from "./ResourceCreateModal";
9
+ import { actionInputSchema, ResourceActionModal, } from "./ResourceActionModal";
10
+ import { ResourceMailModal } from "./ResourceMailModal";
9
11
  import { ResourceViewModal } from "./ResourceViewModal";
12
+ function actionHasInput(meta) {
13
+ return Boolean(meta?.input && Object.keys(meta.input).length > 0);
14
+ }
15
+ function actionIsMail(meta) {
16
+ return Boolean(meta?.mail);
17
+ }
18
+ function toggleId(current, id) {
19
+ const next = new Set(current);
20
+ if (next.has(id))
21
+ next.delete(id);
22
+ else
23
+ next.add(id);
24
+ return next;
25
+ }
10
26
  function resolveRowId(row, keys) {
11
27
  for (const key of keys) {
12
28
  const value = row[key];
@@ -32,30 +48,56 @@ function rowTitle(row, keys) {
32
48
  }
33
49
  return "Record";
34
50
  }
35
- function readVisibility(storageKey) {
51
+ function writeVisibility(storageKey, value) {
36
52
  if (!storageKey || typeof localStorage === "undefined")
37
- return {};
53
+ return;
38
54
  try {
39
- const raw = localStorage.getItem(`vertebr:columns:${storageKey}`);
40
- if (!raw)
41
- return {};
42
- const parsed = JSON.parse(raw);
43
- if (!parsed || typeof parsed !== "object")
44
- return {};
45
- return parsed;
55
+ localStorage.setItem(`vertebr:columns:v2:${storageKey}`, JSON.stringify(value));
46
56
  }
47
57
  catch {
48
- return {};
58
+ /* ignore quota / private mode */
49
59
  }
50
60
  }
51
- function writeVisibility(storageKey, value) {
61
+ function isDefaultHiddenColumn(key, field, schema) {
62
+ if (/^show[A-Z]/.test(key))
63
+ return true;
64
+ if (field?.type === "richtext")
65
+ return true;
66
+ if (key === "updatedAt" || key === "createdAt")
67
+ return true;
68
+ if ((key === "imageSrc" || key === "imageAlt") && "images" in schema) {
69
+ return true;
70
+ }
71
+ if (key === "slug" && "name" in schema)
72
+ return true;
73
+ if (field?.type === "string" && /^(details|description)$/i.test(key)) {
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+ function defaultVisibility(schema) {
79
+ const next = {};
80
+ for (const [key, field] of Object.entries(schema)) {
81
+ if (isDefaultHiddenColumn(key, field, schema))
82
+ next[key] = false;
83
+ }
84
+ return next;
85
+ }
86
+ function readVisibility(storageKey, schema) {
87
+ const defaults = defaultVisibility(schema);
52
88
  if (!storageKey || typeof localStorage === "undefined")
53
- return;
89
+ return defaults;
54
90
  try {
55
- localStorage.setItem(`vertebr:columns:${storageKey}`, JSON.stringify(value));
91
+ const raw = localStorage.getItem(`vertebr:columns:v2:${storageKey}`);
92
+ if (!raw)
93
+ return defaults;
94
+ const parsed = JSON.parse(raw);
95
+ if (!parsed || typeof parsed !== "object")
96
+ return defaults;
97
+ return { ...defaults, ...parsed };
56
98
  }
57
99
  catch {
58
- /* ignore quota / private mode */
100
+ return defaults;
59
101
  }
60
102
  }
61
103
  function addRecordLabel(resourceLabel) {
@@ -76,28 +118,106 @@ function titleFromFilename(name) {
76
118
  .replace(/\s+/g, " ")
77
119
  .trim();
78
120
  }
79
- export function ResourcePanel({ schema, items, permissions, actions = {}, label, uploads, rowIdKeys = ["id", "slug", "uid", "email"], storageKey, onUpdate, onCreate, onDelete, onAction, onUpload, galleryUpload, onListGallery, onUploadGallery, }) {
121
+ export function ResourcePanel({ schema, items, permissions, actions = {}, label, uploads, rowIdKeys = ["id", "slug", "uid", "email"], storageKey, onUpdate, onCreate, onDelete, onAction, onActionDone, onPreviewMail, collectionActions = {}, onCollectionAction, onUpload, galleryUpload, onListGallery, onUploadGallery, }) {
80
122
  const [editing, setEditing] = useState(null);
81
123
  const [creating, setCreating] = useState(false);
82
124
  const [viewing, setViewing] = useState(null);
83
125
  const [bulkOpen, setBulkOpen] = useState(false);
84
126
  const [busyAction, setBusyAction] = useState(null);
127
+ const [selected, setSelected] = useState(new Set());
128
+ const [pendingAction, setPendingAction] = useState(null);
129
+ const [pendingCollectionAction, setPendingCollectionAction] = useState(null);
85
130
  const tableWrapRef = useRef(null);
86
131
  const [tableOverflow, setTableOverflow] = useState({
87
132
  overflow: false,
88
133
  canLeft: false,
89
134
  canRight: false,
90
135
  });
91
- const [columnVisibility, setColumnVisibility] = useState(() => readVisibility(storageKey));
136
+ const [columnVisibility, setColumnVisibility] = useState(() => readVisibility(storageKey, schema));
92
137
  const canUpdate = Boolean(permissions?.update && onUpdate);
93
138
  const canCreate = Boolean(permissions?.create && onCreate);
94
139
  const canDelete = Boolean(permissions?.delete && onDelete);
95
140
  const createLabel = addRecordLabel(label);
96
141
  const canUpload = Boolean(uploads && onUpload && Object.keys(uploads).length);
97
142
  const actionEntries = Object.entries(actions);
143
+ const collectionActionEntries = Object.entries(collectionActions);
98
144
  const fieldKeys = Object.keys(schema);
145
+ const selectable = Boolean(onAction && actionEntries.length > 0);
99
146
  useEffect(() => {
100
- setColumnVisibility(readVisibility(storageKey));
147
+ const valid = new Set(items
148
+ .map((row) => resolveRowId(row, rowIdKeys))
149
+ .filter((id) => Boolean(id)));
150
+ setSelected((current) => {
151
+ const next = new Set([...current].filter((id) => valid.has(id)));
152
+ return next.size === current.size ? current : next;
153
+ });
154
+ }, [items, rowIdKeys]);
155
+ const runActions = useCallback(async (actionId, rowIds, input) => {
156
+ if (!onAction || rowIds.length === 0)
157
+ return;
158
+ setBusyAction(actionId);
159
+ const errors = [];
160
+ try {
161
+ for (const rowId of rowIds) {
162
+ try {
163
+ await onAction(rowId, actionId, input);
164
+ }
165
+ catch (error) {
166
+ errors.push(`${rowId}: ${error instanceof Error ? error.message : "Failed"}`);
167
+ }
168
+ }
169
+ await onActionDone?.();
170
+ setSelected(new Set());
171
+ if (errors.length > 0) {
172
+ window.alert(`Finished with ${errors.length} error(s).\n\n${errors.slice(0, 8).join("\n")}`);
173
+ }
174
+ }
175
+ finally {
176
+ setBusyAction(null);
177
+ }
178
+ }, [onAction, onActionDone]);
179
+ const queueAction = useCallback((actionId, rowIds) => {
180
+ const meta = actions[actionId];
181
+ if (!meta || rowIds.length === 0 || !onAction)
182
+ return;
183
+ if (actionIsMail(meta) || actionHasInput(meta)) {
184
+ setPendingAction({ actionId, rowIds });
185
+ return;
186
+ }
187
+ const who = rowIds.length === 1 ? "this person" : `${rowIds.length} people`;
188
+ if (!window.confirm(`${meta.label} for ${who}?`))
189
+ return;
190
+ void runActions(actionId, rowIds);
191
+ }, [actions, onAction, runActions]);
192
+ const runCollectionAction = useCallback(async (actionId, input) => {
193
+ if (!onCollectionAction)
194
+ return;
195
+ setBusyAction(actionId);
196
+ try {
197
+ await onCollectionAction(actionId, input);
198
+ await onActionDone?.();
199
+ }
200
+ catch (error) {
201
+ window.alert(error instanceof Error ? error.message : "Action failed");
202
+ }
203
+ finally {
204
+ setBusyAction(null);
205
+ }
206
+ }, [onCollectionAction, onActionDone]);
207
+ const queueCollectionAction = useCallback((actionId) => {
208
+ const meta = collectionActions[actionId];
209
+ if (!meta || !onCollectionAction)
210
+ return;
211
+ if (actionHasInput(meta)) {
212
+ setPendingCollectionAction(actionId);
213
+ return;
214
+ }
215
+ if (!window.confirm(`${meta.label}?`))
216
+ return;
217
+ void runCollectionAction(actionId);
218
+ }, [collectionActions, onCollectionAction, runCollectionAction]);
219
+ useEffect(() => {
220
+ setColumnVisibility(readVisibility(storageKey, schema));
101
221
  }, [storageKey]);
102
222
  useEffect(() => {
103
223
  writeVisibility(storageKey, columnVisibility);
@@ -124,6 +244,9 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
124
244
  el.addEventListener("scroll", onScroll, { passive: true });
125
245
  const observer = new ResizeObserver(updateTableOverflow);
126
246
  observer.observe(el);
247
+ const table = el.querySelector("table");
248
+ if (table)
249
+ observer.observe(table);
127
250
  return () => {
128
251
  el.removeEventListener("scroll", onScroll);
129
252
  observer.disconnect();
@@ -144,7 +267,33 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
144
267
  label: schema[key]?.label ?? humanizeKey(key),
145
268
  })), [fieldKeys, schema]);
146
269
  const columns = useMemo(() => {
147
- const defs = fieldKeys.length === 0
270
+ const defs = [];
271
+ if (selectable) {
272
+ const visibleIds = items
273
+ .map((row) => resolveRowId(row, rowIdKeys))
274
+ .filter((id) => Boolean(id));
275
+ const allSelected = visibleIds.length > 0 && visibleIds.every((id) => selected.has(id));
276
+ defs.push({
277
+ id: "_select",
278
+ enableHiding: false,
279
+ header: () => (_jsx("input", { type: "checkbox", className: "vertebr-row-check", checked: allSelected, "aria-label": "Select all", onChange: () => {
280
+ setSelected((current) => {
281
+ if (visibleIds.every((id) => current.has(id))) {
282
+ return new Set();
283
+ }
284
+ return new Set(visibleIds);
285
+ });
286
+ } })),
287
+ cell: ({ row }) => {
288
+ const rowId = resolveRowId(row.original, rowIdKeys);
289
+ if (!rowId)
290
+ return null;
291
+ return (_jsx("input", { type: "checkbox", className: "vertebr-row-check", checked: selected.has(rowId), "aria-label": "Select row", onClick: (event) => event.stopPropagation(), onChange: () => setSelected((current) => toggleId(current, rowId)) }));
292
+ },
293
+ size: 44,
294
+ });
295
+ }
296
+ defs.push(...(fieldKeys.length === 0
148
297
  ? [
149
298
  {
150
299
  id: "raw",
@@ -161,10 +310,10 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
161
310
  accessorKey: key,
162
311
  header: field?.label ?? humanizeKey(key),
163
312
  meta: { fieldKey: key, field },
164
- cell: ({ getValue }) => (_jsx(ResourceCell, { fieldKey: key, field: field, value: getValue() })),
313
+ cell: ({ getValue, }) => (_jsx(ResourceCell, { fieldKey: key, field: field, value: getValue() })),
165
314
  size: isImage ? 220 : undefined,
166
315
  };
167
- });
316
+ })));
168
317
  defs.push({
169
318
  id: "_actions",
170
319
  header: () => _jsx("span", { className: "vertebr-sr-only", children: "Actions" }),
@@ -172,12 +321,7 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
172
321
  cell: ({ row }) => {
173
322
  const rowId = resolveRowId(row.original, rowIdKeys);
174
323
  return (_jsxs("div", { className: "vertebr-row-actions", onClick: (event) => event.stopPropagation(), onKeyDown: (event) => event.stopPropagation(), children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => setViewing(row.original), children: "View" }), canUpdate ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => setEditing(row.original), children: "Edit" })) : null, rowId
175
- ? actionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyAction === `${rowId}:${actionId}`, onClick: () => {
176
- if (!onAction)
177
- return;
178
- setBusyAction(`${rowId}:${actionId}`);
179
- void onAction(rowId, actionId).finally(() => setBusyAction(null));
180
- }, children: meta.label }, actionId)))
324
+ ? actionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyAction != null, onClick: () => queueAction(actionId, [rowId]), children: meta.label }, actionId)))
181
325
  : null, canDelete && rowId ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-danger-quiet", onClick: () => {
182
326
  if (window.confirm("Delete this row? This cannot be undone.")) {
183
327
  void onDelete?.(rowId);
@@ -189,11 +333,14 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
189
333
  }, [
190
334
  schema,
191
335
  fieldKeys,
336
+ items,
337
+ selected,
338
+ selectable,
192
339
  canUpdate,
193
340
  canDelete,
194
341
  actionEntries,
195
342
  busyAction,
196
- onAction,
343
+ queueAction,
197
344
  onDelete,
198
345
  rowIdKeys,
199
346
  ]);
@@ -230,7 +377,7 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
230
377
  setEditing(null);
231
378
  } })] }));
232
379
  }
233
- const toolbar = (_jsxs("div", { className: "vertebr-table-toolbar", children: [_jsxs("div", { className: "vertebr-table-toolbar-start", children: [columnOptions.length > 0 ? (_jsx(ColumnVisibilityMenu, { columns: columnOptions, visibility: columnVisibility, onChange: setColumnVisibility })) : null, canUpload ? (_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon", onClick: () => setBulkOpen(true), children: [_jsx(ImageUploadIcon, {}), "Bulk upload"] })) : null] }), tableOverflow.overflow ? (_jsxs("div", { className: "vertebr-table-scroll-controls", role: "group", "aria-label": "Scroll columns", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !tableOverflow.canLeft, onClick: () => scrollTable("left"), "aria-label": "Scroll columns left", children: _jsx(ChevronLeftIcon, {}) }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !tableOverflow.canRight, onClick: () => scrollTable("right"), "aria-label": "Scroll columns right", children: _jsx(ChevronRightIcon, {}) })] })) : null] }));
380
+ const toolbar = (_jsxs("div", { className: "vertebr-table-toolbar", children: [_jsxs("div", { className: "vertebr-table-toolbar-start", children: [columnOptions.length > 0 ? (_jsx(ColumnVisibilityMenu, { columns: columnOptions, visibility: columnVisibility, onChange: setColumnVisibility })) : null, canUpload ? (_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon", onClick: () => setBulkOpen(true), children: [_jsx(ImageUploadIcon, {}), "Bulk upload"] })) : null, collectionActionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyAction != null, onClick: () => queueCollectionAction(actionId), children: meta.label }, actionId))), selectable && selected.size > 0 ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "vertebr-count", children: [selected.size, " selected"] }), actionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyAction != null, onClick: () => queueAction(actionId, [...selected]), children: meta.label }, actionId))), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", onClick: () => setSelected(new Set()), children: "Clear" })] })) : null] }), tableOverflow.overflow ? (_jsxs("div", { className: "vertebr-table-scroll-controls", role: "group", "aria-label": "Scroll columns", children: [_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !tableOverflow.canLeft, onClick: () => scrollTable("left"), "aria-label": "Scroll columns left", children: _jsx(ChevronLeftIcon, {}) }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon vertebr-table-scroll-btn", disabled: !tableOverflow.canRight, onClick: () => scrollTable("right"), "aria-label": "Scroll columns right", children: _jsx(ChevronRightIcon, {}) })] })) : null] }));
234
381
  const bulkModal = bulkOpen && uploads && onUpload ? (_jsx(BulkImageUploadModal, { uploads: uploads, canCreate: canCreate, fieldLabels: Object.fromEntries(Object.keys(uploads).map((key) => [
235
382
  key,
236
383
  schema[key]?.label ?? humanizeKey(key),
@@ -247,25 +394,48 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, label,
247
394
  }
248
395
  : undefined })) : null;
249
396
  const createModal = creating && onCreate ? (_jsx(ResourceCreateModal, { title: createLabel, schema: createSchema, value: createDefaults, uploads: uploads, onUpload: onUpload, galleryUpload: galleryUpload, onListGallery: onListGallery, onUploadGallery: onUploadGallery, onClose: () => setCreating(false), onCreate: onCreate })) : null;
397
+ const pendingActionModal = pendingAction ? (actionIsMail(actions[pendingAction.actionId]) && onPreviewMail ? (_jsx(ResourceMailModal, { title: actions[pendingAction.actionId]?.label ?? "Send email", count: pendingAction.rowIds.length, schema: actionInputSchema(actions[pendingAction.actionId]?.input ?? {}), submitLabel: busyAction
398
+ ? "Sending…"
399
+ : pendingAction.rowIds.length === 1
400
+ ? "Send"
401
+ : `Send to ${pendingAction.rowIds.length}`, onClose: () => setPendingAction(null), onPreview: (value) => onPreviewMail(pendingAction.rowIds[0], pendingAction.actionId, value), onSubmit: (value) => runActions(pendingAction.actionId, pendingAction.rowIds, value) })) : actions[pendingAction.actionId]?.input ? (_jsx(ResourceActionModal, { title: actions[pendingAction.actionId]?.label ?? "Run action", count: pendingAction.rowIds.length, schema: actionInputSchema(actions[pendingAction.actionId].input), submitLabel: busyAction
402
+ ? "Sending…"
403
+ : pendingAction.rowIds.length === 1
404
+ ? actions[pendingAction.actionId]?.label ?? "Send"
405
+ : `Send to ${pendingAction.rowIds.length}`, onClose: () => setPendingAction(null), onSubmit: (value) => runActions(pendingAction.actionId, pendingAction.rowIds, value) })) : null) : null;
406
+ const pendingCollectionModal = pendingCollectionAction &&
407
+ collectionActions[pendingCollectionAction]?.input ? (_jsx(ResourceActionModal, { title: collectionActions[pendingCollectionAction]?.label ?? "Run action", eyebrow: "This resource", schema: actionInputSchema(collectionActions[pendingCollectionAction].input), submitLabel: busyAction ? "Working…" : "Run", onClose: () => setPendingCollectionAction(null), onSubmit: (value) => runCollectionAction(pendingCollectionAction, value) })) : null;
250
408
  const recordsHead = (_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Records" }), _jsxs("div", { className: "vertebr-panel-head-actions", children: [canCreate ? (_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-icon", onClick: () => setCreating(true), children: [_jsx(PlusIcon, {}), createLabel] })) : null, _jsx("span", { className: "vertebr-count", children: items.length })] })] }));
251
409
  if (items.length === 0) {
252
- return (_jsxs("div", { className: "vertebr-resource-body", children: [recordsHead, canUpload || columnOptions.length > 0 ? toolbar : null, _jsx("div", { className: "vertebr-empty", children: "No rows yet." }), bulkModal, createModal] }));
410
+ return (_jsxs("div", { className: "vertebr-resource-body", children: [recordsHead, canUpload ||
411
+ columnOptions.length > 0 ||
412
+ collectionActionEntries.length > 0
413
+ ? toolbar
414
+ : null, _jsx("div", { className: "vertebr-empty", children: "No rows yet." }), bulkModal, createModal, pendingActionModal, pendingCollectionModal] }));
253
415
  }
254
416
  return (_jsxs("div", { className: "vertebr-resource-body", children: [recordsHead, toolbar, _jsx("div", { className: "vertebr-table-wrap", ref: tableWrapRef, "data-overflow": tableOverflow.overflow ? "true" : undefined, "data-overflow-left": tableOverflow.canLeft ? "true" : undefined, "data-overflow-right": tableOverflow.canRight ? "true" : undefined, children: _jsxs("table", { className: "vertebr-table", children: [_jsx("thead", { children: table.getHeaderGroups().map((headerGroup) => (_jsx("tr", { children: headerGroup.headers.map((header) => (_jsx("th", { className: header.column.id === "_actions"
255
417
  ? "vertebr-col-actions"
256
- : undefined, children: header.isPlaceholder
418
+ : header.column.id === "_select"
419
+ ? "vertebr-col-select"
420
+ : undefined, children: header.isPlaceholder
257
421
  ? null
258
- : flexRender(header.column.columnDef.header, header.getContext()) }, header.id))) }, headerGroup.id))) }), _jsx("tbody", { children: table.getRowModel().rows.map((row) => (_jsx("tr", { className: "vertebr-table-row-clickable", tabIndex: 0, onClick: () => setViewing(row.original), onKeyDown: (event) => {
422
+ : flexRender(header.column.columnDef.header, header.getContext()) }, header.id))) }, headerGroup.id))) }), _jsx("tbody", { children: table.getRowModel().rows.map((row) => (_jsx("tr", { className: "vertebr-table-row-clickable", "data-selected": selectable &&
423
+ resolveRowId(row.original, rowIdKeys) &&
424
+ selected.has(resolveRowId(row.original, rowIdKeys))
425
+ ? "true"
426
+ : undefined, tabIndex: 0, onClick: () => setViewing(row.original), onKeyDown: (event) => {
259
427
  if (event.key === "Enter" || event.key === " ") {
260
428
  event.preventDefault();
261
429
  setViewing(row.original);
262
430
  }
263
431
  }, children: row.getVisibleCells().map((cell) => (_jsx("td", { className: cell.column.id === "_actions"
264
432
  ? "vertebr-col-actions"
265
- : undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id))) })] }) }), viewing ? (_jsx(ResourceViewModal, { schema: schema, row: viewing, title: rowTitle(viewing, rowIdKeys), canEdit: canUpdate, onClose: () => setViewing(null), onEdit: () => {
433
+ : cell.column.id === "_select"
434
+ ? "vertebr-col-select"
435
+ : undefined, children: flexRender(cell.column.columnDef.cell, cell.getContext()) }, cell.id))) }, row.id))) })] }) }), viewing ? (_jsx(ResourceViewModal, { schema: schema, row: viewing, title: rowTitle(viewing, rowIdKeys), canEdit: canUpdate, onClose: () => setViewing(null), onEdit: () => {
266
436
  setEditing(viewing);
267
437
  setViewing(null);
268
- } })) : null, bulkModal, createModal] }));
438
+ } })) : null, pendingActionModal, pendingCollectionModal, bulkModal, createModal] }));
269
439
  }
270
440
  /** @deprecated Prefer ResourcePanel */
271
441
  export function ResourceTable(props) {
package/dist/index.d.ts CHANGED
@@ -2,6 +2,12 @@ export { VertebrAdmin } from "./components/admin/VertebrAdmin";
2
2
  export type { VertebrAdminProps } from "./components/admin/VertebrAdmin";
3
3
  export { MetricsBoard } from "./components/metrics/MetricsBoard";
4
4
  export { GalleryBoard } from "./components/gallery/GalleryBoard";
5
+ export { EmailsBoard } from "./components/emails/EmailsBoard";
6
+ export type { EmailsBoardProps } from "./components/emails/EmailsBoard";
7
+ export type { EmailRecipientOption } from "./components/emails/EmailRecipientSelect";
8
+ export type { MultiCheckOption } from "./components/emails/MultiCheckSelect";
9
+ export type { EmailsLibraryPanelProps } from "./components/emails/EmailsLibraryPanel";
10
+ export type { EmailsActivityPanelProps } from "./components/emails/EmailsActivityPanel";
5
11
  export { ResourcePanel, ResourceTable } from "./components/resources/ResourceTable";
6
12
  export { ThemeToggle } from "./components/theme/ThemeToggle";
7
13
  export type { ThemeToggleProps } from "./components/theme/ThemeToggle";
@@ -21,5 +27,5 @@ export { createVertebrCatalog, catalogHref, catalogMatch, } from "@vertebr/clien
21
27
  export type { VertebrCatalog, VertebrCatalogOptions, PublicResourceResponse, } from "@vertebr/client";
22
28
  export { VertebrCatalogProvider, useVertebrCatalog, useVertebrCatalogItem, } from "@vertebr/client/react";
23
29
  export type { VertebrCatalogProviderProps, VertebrCatalogState, } from "@vertebr/client/react";
24
- export type { VertebrClient, VertebrClientOptions, VertebrReadOptions, AdminEnvironment, BootstrapResponse, CatalogResponse, CatalogResource, CatalogConfig, CatalogConfigSection, EnvironmentAuthPublic, FieldSchema, FieldType, GalleryItem, GalleryMeta, GalleryResponse, MetricCard, MetricsResponse, ResourceListResponse, ResourceActionMeta, ResourceUploadMeta, ResourceUploadResponse, ResourceBulkUploadResponse, ConfigResponse, VertebrControlsMap, } from "@vertebr/client";
30
+ export type { VertebrClient, VertebrClientOptions, VertebrMetricsOptions, VertebrReadOptions, AdminEnvironment, BootstrapResponse, CatalogResponse, CatalogResource, CatalogConfig, CatalogConfigSection, EnvironmentAuthPublic, FieldSchema, FieldType, GalleryItem, GalleryMeta, GalleryResponse, MailPreviewResponse, MailLibraryTemplate, MailHistoryEntry, MailProductOption, MailDiscountOption, EmailsBoardResponse, MetricCard, MetricChart, MetricsResponse, ResourceListResponse, ResourceActionMeta, ResourceUploadMeta, ResourceUploadResponse, ResourceBulkUploadResponse, ConfigResponse, VertebrControlsMap, } from "@vertebr/client";
25
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,EACf,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAC9D,YAAY,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,YAAY,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AACrF,YAAY,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,YAAY,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACtF,YAAY,EAAE,wBAAwB,EAAE,MAAM,yCAAyC,CAAC;AACxF,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sCAAsC,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,EACL,WAAW,EACX,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GACd,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,YAAY,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,YAAY,EAAE,4BAA4B,EAAE,MAAM,4BAA4B,CAAC;AAG/E,OAAO,EACL,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,2BAA2B,EAC3B,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,eAAe,EACf,aAAa,EACb,oBAAoB,EACpB,qBAAqB,EACrB,WAAW,EACX,SAAS,EACT,WAAW,EACX,WAAW,EACX,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,UAAU,EACV,WAAW,EACX,eAAe,EACf,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,cAAc,EACd,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}