@vertebr/react 0.3.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 (126) 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/EnumInput.d.ts +10 -0
  23. package/dist/components/config/EnumInput.d.ts.map +1 -0
  24. package/dist/components/config/EnumInput.js +19 -0
  25. package/dist/components/config/EnumListInput.d.ts +10 -0
  26. package/dist/components/config/EnumListInput.d.ts.map +1 -0
  27. package/dist/components/config/EnumListInput.js +46 -0
  28. package/dist/components/config/VisibilityEye.d.ts +8 -0
  29. package/dist/components/config/VisibilityEye.d.ts.map +1 -0
  30. package/dist/components/config/VisibilityEye.js +18 -0
  31. package/dist/components/config/form/ConfigField.d.ts +2 -1
  32. package/dist/components/config/form/ConfigField.d.ts.map +1 -1
  33. package/dist/components/config/form/ConfigField.js +9 -4
  34. package/dist/components/config/form/ConfigForm.d.ts +1 -1
  35. package/dist/components/config/form/ConfigForm.d.ts.map +1 -1
  36. package/dist/components/config/form/ConfigForm.js +18 -9
  37. package/dist/components/config/form/ConfigFormActions.d.ts +2 -1
  38. package/dist/components/config/form/ConfigFormActions.d.ts.map +1 -1
  39. package/dist/components/config/form/ConfigFormActions.js +2 -2
  40. package/dist/components/config/form/RecordConfigForm.d.ts +3 -1
  41. package/dist/components/config/form/RecordConfigForm.d.ts.map +1 -1
  42. package/dist/components/config/form/RecordConfigForm.js +16 -8
  43. package/dist/components/config/form/SettingsConfigForm.d.ts +3 -1
  44. package/dist/components/config/form/SettingsConfigForm.d.ts.map +1 -1
  45. package/dist/components/config/form/SettingsConfigForm.js +2 -2
  46. package/dist/components/config/form/helpers/fields.d.ts +7 -0
  47. package/dist/components/config/form/helpers/fields.d.ts.map +1 -0
  48. package/dist/components/config/form/helpers/fields.js +31 -0
  49. package/dist/components/config/form/helpers/groups.d.ts +4 -0
  50. package/dist/components/config/form/helpers/groups.d.ts.map +1 -0
  51. package/dist/components/config/form/helpers/groups.js +107 -0
  52. package/dist/components/config/form/helpers/images.d.ts +4 -0
  53. package/dist/components/config/form/helpers/images.d.ts.map +1 -0
  54. package/dist/components/config/form/helpers/images.js +27 -0
  55. package/dist/components/config/form/helpers/index.d.ts +7 -0
  56. package/dist/components/config/form/helpers/index.d.ts.map +1 -0
  57. package/dist/components/config/form/helpers/index.js +6 -0
  58. package/dist/components/config/form/helpers/labels.d.ts +5 -0
  59. package/dist/components/config/form/helpers/labels.d.ts.map +1 -0
  60. package/dist/components/config/form/helpers/labels.js +28 -0
  61. package/dist/components/config/form/helpers/schema.d.ts +8 -0
  62. package/dist/components/config/form/helpers/schema.d.ts.map +1 -0
  63. package/dist/components/config/form/helpers/schema.js +30 -0
  64. package/dist/components/config/form/helpers/visibility.d.ts +13 -0
  65. package/dist/components/config/form/helpers/visibility.d.ts.map +1 -0
  66. package/dist/components/config/form/helpers/visibility.js +86 -0
  67. package/dist/components/config/form/types.d.ts +6 -0
  68. package/dist/components/config/form/types.d.ts.map +1 -1
  69. package/dist/components/emails/EmailRecipientSelect.d.ts +16 -0
  70. package/dist/components/emails/EmailRecipientSelect.d.ts.map +1 -0
  71. package/dist/components/emails/EmailRecipientSelect.js +10 -0
  72. package/dist/components/emails/EmailsActivityPanel.d.ts +11 -0
  73. package/dist/components/emails/EmailsActivityPanel.d.ts.map +1 -0
  74. package/dist/components/emails/EmailsActivityPanel.js +139 -0
  75. package/dist/components/emails/EmailsBoard.d.ts +47 -0
  76. package/dist/components/emails/EmailsBoard.d.ts.map +1 -0
  77. package/dist/components/emails/EmailsBoard.js +594 -0
  78. package/dist/components/emails/EmailsLibraryPanel.d.ts +25 -0
  79. package/dist/components/emails/EmailsLibraryPanel.d.ts.map +1 -0
  80. package/dist/components/emails/EmailsLibraryPanel.js +379 -0
  81. package/dist/components/emails/EmailsNavSelect.d.ts +18 -0
  82. package/dist/components/emails/EmailsNavSelect.d.ts.map +1 -0
  83. package/dist/components/emails/EmailsNavSelect.js +37 -0
  84. package/dist/components/emails/MultiCheckSelect.d.ts +18 -0
  85. package/dist/components/emails/MultiCheckSelect.d.ts.map +1 -0
  86. package/dist/components/emails/MultiCheckSelect.js +74 -0
  87. package/dist/components/metrics/MetricsBoard.d.ts +8 -2
  88. package/dist/components/metrics/MetricsBoard.d.ts.map +1 -1
  89. package/dist/components/metrics/MetricsBoard.js +55 -2
  90. package/dist/components/metrics/charts.d.ts +14 -0
  91. package/dist/components/metrics/charts.d.ts.map +1 -0
  92. package/dist/components/metrics/charts.js +212 -0
  93. package/dist/components/resources/ResourceActionModal.d.ts +14 -0
  94. package/dist/components/resources/ResourceActionModal.d.ts.map +1 -0
  95. package/dist/components/resources/ResourceActionModal.js +64 -0
  96. package/dist/components/resources/ResourceCell.d.ts.map +1 -1
  97. package/dist/components/resources/ResourceCell.js +25 -9
  98. package/dist/components/resources/ResourceCreateModal.d.ts +18 -0
  99. package/dist/components/resources/ResourceCreateModal.d.ts.map +1 -0
  100. package/dist/components/resources/ResourceCreateModal.js +22 -0
  101. package/dist/components/resources/ResourceMailModal.d.ts +12 -0
  102. package/dist/components/resources/ResourceMailModal.d.ts.map +1 -0
  103. package/dist/components/resources/ResourceMailModal.js +196 -0
  104. package/dist/components/resources/ResourceTable.d.ts +9 -3
  105. package/dist/components/resources/ResourceTable.d.ts.map +1 -1
  106. package/dist/components/resources/ResourceTable.js +241 -35
  107. package/dist/index.d.ts +11 -1
  108. package/dist/index.d.ts.map +1 -1
  109. package/dist/index.js +3 -0
  110. package/dist/lib/metricsRanges.d.ts +20 -0
  111. package/dist/lib/metricsRanges.d.ts.map +1 -0
  112. package/dist/lib/metricsRanges.js +14 -0
  113. package/dist/styles/cells.css +42 -0
  114. package/dist/styles/emails.css +632 -0
  115. package/dist/styles/form.css +145 -0
  116. package/dist/styles/index.css +3 -0
  117. package/dist/styles/mail.css +240 -0
  118. package/dist/styles/metrics.css +633 -0
  119. package/dist/styles/modal.css +18 -0
  120. package/dist/styles/nav.css +103 -1
  121. package/dist/styles/panel.css +8 -68
  122. package/dist/styles/table.css +26 -3
  123. package/package.json +2 -2
  124. package/dist/components/config/form/helpers.d.ts +0 -16
  125. package/dist/components/config/form/helpers.d.ts.map +0 -1
  126. package/dist/components/config/form/helpers.js +0 -186
@@ -1,11 +1,28 @@
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";
5
5
  import { BulkImageUploadModal } from "./BulkImageUploadModal";
6
6
  import { ColumnVisibilityMenu } from "./ColumnVisibilityMenu";
7
7
  import { ResourceCell } from "./ResourceCell";
8
+ import { ResourceCreateModal } from "./ResourceCreateModal";
9
+ import { actionInputSchema, ResourceActionModal, } from "./ResourceActionModal";
10
+ import { ResourceMailModal } from "./ResourceMailModal";
8
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
+ }
9
26
  function resolveRowId(row, keys) {
10
27
  for (const key of keys) {
11
28
  const value = row[key];
@@ -31,32 +48,69 @@ function rowTitle(row, keys) {
31
48
  }
32
49
  return "Record";
33
50
  }
34
- function readVisibility(storageKey) {
51
+ function writeVisibility(storageKey, value) {
35
52
  if (!storageKey || typeof localStorage === "undefined")
36
- return {};
53
+ return;
37
54
  try {
38
- const raw = localStorage.getItem(`vertebr:columns:${storageKey}`);
39
- if (!raw)
40
- return {};
41
- const parsed = JSON.parse(raw);
42
- if (!parsed || typeof parsed !== "object")
43
- return {};
44
- return parsed;
55
+ localStorage.setItem(`vertebr:columns:v2:${storageKey}`, JSON.stringify(value));
45
56
  }
46
57
  catch {
47
- return {};
58
+ /* ignore quota / private mode */
48
59
  }
49
60
  }
50
- 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);
51
88
  if (!storageKey || typeof localStorage === "undefined")
52
- return;
89
+ return defaults;
53
90
  try {
54
- 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 };
55
98
  }
56
99
  catch {
57
- /* ignore quota / private mode */
100
+ return defaults;
58
101
  }
59
102
  }
103
+ function addRecordLabel(resourceLabel) {
104
+ const name = resourceLabel?.trim();
105
+ if (!name)
106
+ return "Add record";
107
+ const singular = /ies$/i.test(name)
108
+ ? name.replace(/ies$/i, "y")
109
+ : /s$/i.test(name)
110
+ ? name.slice(0, -1)
111
+ : name;
112
+ return `Add ${singular.toLowerCase()}`;
113
+ }
60
114
  function titleFromFilename(name) {
61
115
  return name
62
116
  .replace(/\.[^.]+$/, "")
@@ -64,26 +118,106 @@ function titleFromFilename(name) {
64
118
  .replace(/\s+/g, " ")
65
119
  .trim();
66
120
  }
67
- export function ResourcePanel({ schema, items, permissions, actions = {}, 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, }) {
68
122
  const [editing, setEditing] = useState(null);
123
+ const [creating, setCreating] = useState(false);
69
124
  const [viewing, setViewing] = useState(null);
70
125
  const [bulkOpen, setBulkOpen] = useState(false);
71
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);
72
130
  const tableWrapRef = useRef(null);
73
131
  const [tableOverflow, setTableOverflow] = useState({
74
132
  overflow: false,
75
133
  canLeft: false,
76
134
  canRight: false,
77
135
  });
78
- const [columnVisibility, setColumnVisibility] = useState(() => readVisibility(storageKey));
136
+ const [columnVisibility, setColumnVisibility] = useState(() => readVisibility(storageKey, schema));
79
137
  const canUpdate = Boolean(permissions?.update && onUpdate);
80
138
  const canCreate = Boolean(permissions?.create && onCreate);
81
139
  const canDelete = Boolean(permissions?.delete && onDelete);
140
+ const createLabel = addRecordLabel(label);
82
141
  const canUpload = Boolean(uploads && onUpload && Object.keys(uploads).length);
83
142
  const actionEntries = Object.entries(actions);
143
+ const collectionActionEntries = Object.entries(collectionActions);
84
144
  const fieldKeys = Object.keys(schema);
145
+ const selectable = Boolean(onAction && actionEntries.length > 0);
85
146
  useEffect(() => {
86
- 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));
87
221
  }, [storageKey]);
88
222
  useEffect(() => {
89
223
  writeVisibility(storageKey, columnVisibility);
@@ -110,6 +244,9 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
110
244
  el.addEventListener("scroll", onScroll, { passive: true });
111
245
  const observer = new ResizeObserver(updateTableOverflow);
112
246
  observer.observe(el);
247
+ const table = el.querySelector("table");
248
+ if (table)
249
+ observer.observe(table);
113
250
  return () => {
114
251
  el.removeEventListener("scroll", onScroll);
115
252
  observer.disconnect();
@@ -130,7 +267,33 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
130
267
  label: schema[key]?.label ?? humanizeKey(key),
131
268
  })), [fieldKeys, schema]);
132
269
  const columns = useMemo(() => {
133
- 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
134
297
  ? [
135
298
  {
136
299
  id: "raw",
@@ -147,10 +310,10 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
147
310
  accessorKey: key,
148
311
  header: field?.label ?? humanizeKey(key),
149
312
  meta: { fieldKey: key, field },
150
- cell: ({ getValue }) => (_jsx(ResourceCell, { fieldKey: key, field: field, value: getValue() })),
313
+ cell: ({ getValue, }) => (_jsx(ResourceCell, { fieldKey: key, field: field, value: getValue() })),
151
314
  size: isImage ? 220 : undefined,
152
315
  };
153
- });
316
+ })));
154
317
  defs.push({
155
318
  id: "_actions",
156
319
  header: () => _jsx("span", { className: "vertebr-sr-only", children: "Actions" }),
@@ -158,12 +321,7 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
158
321
  cell: ({ row }) => {
159
322
  const rowId = resolveRowId(row.original, rowIdKeys);
160
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
161
- ? actionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyAction === `${rowId}:${actionId}`, onClick: () => {
162
- if (!onAction)
163
- return;
164
- setBusyAction(`${rowId}:${actionId}`);
165
- void onAction(rowId, actionId).finally(() => setBusyAction(null));
166
- }, 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)))
167
325
  : null, canDelete && rowId ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-danger-quiet", onClick: () => {
168
326
  if (window.confirm("Delete this row? This cannot be undone.")) {
169
327
  void onDelete?.(rowId);
@@ -175,11 +333,14 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
175
333
  }, [
176
334
  schema,
177
335
  fieldKeys,
336
+ items,
337
+ selected,
338
+ selectable,
178
339
  canUpdate,
179
340
  canDelete,
180
341
  actionEntries,
181
342
  busyAction,
182
- onAction,
343
+ queueAction,
183
344
  onDelete,
184
345
  rowIdKeys,
185
346
  ]);
@@ -191,6 +352,23 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
191
352
  getCoreRowModel: getCoreRowModel(),
192
353
  });
193
354
  const editingId = editing ? resolveRowId(editing, rowIdKeys) : null;
355
+ const createSchema = Object.fromEntries(Object.entries(schema)
356
+ .filter(([, field]) => !field.readOnly || field.required)
357
+ .map(([key, field]) => [
358
+ key,
359
+ field.readOnly ? { ...field, readOnly: false } : field,
360
+ ]));
361
+ const createDefaults = Object.fromEntries(Object.entries(createSchema).map(([key, field]) => [
362
+ key,
363
+ field.default ??
364
+ (field.type === "boolean"
365
+ ? false
366
+ : field.type === "number"
367
+ ? undefined
368
+ : field.type === "file[]"
369
+ ? []
370
+ : ""),
371
+ ]));
194
372
  if (editing && editingId && onUpdate) {
195
373
  const editableSchema = Object.fromEntries(Object.entries(schema).filter(([, field]) => !field.readOnly));
196
374
  const title = rowTitle(editing, rowIdKeys);
@@ -199,7 +377,7 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
199
377
  setEditing(null);
200
378
  } })] }));
201
379
  }
202
- 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] }));
203
381
  const bulkModal = bulkOpen && uploads && onUpload ? (_jsx(BulkImageUploadModal, { uploads: uploads, canCreate: canCreate, fieldLabels: Object.fromEntries(Object.keys(uploads).map((key) => [
204
382
  key,
205
383
  schema[key]?.label ?? humanizeKey(key),
@@ -215,29 +393,57 @@ export function ResourcePanel({ schema, items, permissions, actions = {}, upload
215
393
  }
216
394
  }
217
395
  : undefined })) : null;
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;
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 })] })] }));
218
409
  if (items.length === 0) {
219
- return (_jsxs("div", { className: "vertebr-resource-body", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Records" }), _jsx("span", { className: "vertebr-count", children: "0" })] }), canUpload || columnOptions.length > 0 ? toolbar : null, _jsx("div", { className: "vertebr-empty", children: "No rows yet." }), bulkModal] }));
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] }));
220
415
  }
221
- return (_jsxs("div", { className: "vertebr-resource-body", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: "Records" }), _jsx("span", { className: "vertebr-count", children: items.length })] }), 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"
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"
222
417
  ? "vertebr-col-actions"
223
- : undefined, children: header.isPlaceholder
418
+ : header.column.id === "_select"
419
+ ? "vertebr-col-select"
420
+ : undefined, children: header.isPlaceholder
224
421
  ? null
225
- : 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) => {
226
427
  if (event.key === "Enter" || event.key === " ") {
227
428
  event.preventDefault();
228
429
  setViewing(row.original);
229
430
  }
230
431
  }, children: row.getVisibleCells().map((cell) => (_jsx("td", { className: cell.column.id === "_actions"
231
432
  ? "vertebr-col-actions"
232
- : 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: () => {
233
436
  setEditing(viewing);
234
437
  setViewing(null);
235
- } })) : null, bulkModal] }));
438
+ } })) : null, pendingActionModal, pendingCollectionModal, bulkModal, createModal] }));
236
439
  }
237
440
  /** @deprecated Prefer ResourcePanel */
238
441
  export function ResourceTable(props) {
239
442
  return _jsx(ResourcePanel, { ...props });
240
443
  }
444
+ function PlusIcon() {
445
+ return (_jsxs("svg", { className: "vertebr-btn-glyph", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [_jsx("path", { d: "M12 5v14" }), _jsx("path", { d: "M5 12h14" })] }));
446
+ }
241
447
  function ChevronLeftIcon() {
242
448
  return (_jsx("svg", { className: "vertebr-btn-glyph", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "m15 18-6-6 6-6" }) }));
243
449
  }
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";
@@ -17,5 +23,9 @@ export type { VertebrControlsProviderProps } from "./hooks/useVertebrControls";
17
23
  export { createVertebrClient, VERTEBR_ENV_HEADER, renderMarkdown, } from "@vertebr/client";
18
24
  export { VertebrMarkdown } from "@vertebr/client/react";
19
25
  export type { VertebrMarkdownProps } from "@vertebr/client/react";
20
- 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";
26
+ export { createVertebrCatalog, catalogHref, catalogMatch, } from "@vertebr/client";
27
+ export type { VertebrCatalog, VertebrCatalogOptions, PublicResourceResponse, } from "@vertebr/client";
28
+ export { VertebrCatalogProvider, useVertebrCatalog, useVertebrCatalogItem, } from "@vertebr/client/react";
29
+ export type { VertebrCatalogProviderProps, VertebrCatalogState, } from "@vertebr/client/react";
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";
21
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,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"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { VertebrAdmin } from "./components/admin/VertebrAdmin";
2
2
  export { MetricsBoard } from "./components/metrics/MetricsBoard";
3
3
  export { GalleryBoard } from "./components/gallery/GalleryBoard";
4
+ export { EmailsBoard } from "./components/emails/EmailsBoard";
4
5
  export { ResourcePanel, ResourceTable } from "./components/resources/ResourceTable";
5
6
  export { ThemeToggle } from "./components/theme/ThemeToggle";
6
7
  export { ConfigForm } from "./components/config/ConfigForm";
@@ -12,3 +13,5 @@ export { VertebrControlsProvider, useVertebrConfig, useVertebrControls, } from "
12
13
  // Re-export the framework-agnostic client for convenience.
13
14
  export { createVertebrClient, VERTEBR_ENV_HEADER, renderMarkdown, } from "@vertebr/client";
14
15
  export { VertebrMarkdown } from "@vertebr/client/react";
16
+ export { createVertebrCatalog, catalogHref, catalogMatch, } from "@vertebr/client";
17
+ export { VertebrCatalogProvider, useVertebrCatalog, useVertebrCatalogItem, } from "@vertebr/client/react";
@@ -0,0 +1,20 @@
1
+ export declare const METRICS_RANGES: readonly [{
2
+ readonly id: "7d";
3
+ readonly label: "Last 7 days";
4
+ }, {
5
+ readonly id: "14d";
6
+ readonly label: "Last 14 days";
7
+ }, {
8
+ readonly id: "30d";
9
+ readonly label: "Last 30 days";
10
+ }, {
11
+ readonly id: "this_month";
12
+ readonly label: "This month";
13
+ }, {
14
+ readonly id: "last_month";
15
+ readonly label: "Last month";
16
+ }];
17
+ export type MetricsRangeId = (typeof METRICS_RANGES)[number]["id"];
18
+ export declare const DEFAULT_METRICS_RANGE: MetricsRangeId;
19
+ export declare function parseMetricsRangeId(raw: string | null | undefined): MetricsRangeId;
20
+ //# sourceMappingURL=metricsRanges.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metricsRanges.d.ts","sourceRoot":"","sources":["../../src/lib/metricsRanges.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;EAMjB,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC;AAEnE,eAAO,MAAM,qBAAqB,EAAE,cAAsB,CAAC;AAI3D,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC7B,cAAc,CAGhB"}
@@ -0,0 +1,14 @@
1
+ export const METRICS_RANGES = [
2
+ { id: "7d", label: "Last 7 days" },
3
+ { id: "14d", label: "Last 14 days" },
4
+ { id: "30d", label: "Last 30 days" },
5
+ { id: "this_month", label: "This month" },
6
+ { id: "last_month", label: "Last month" },
7
+ ];
8
+ export const DEFAULT_METRICS_RANGE = "14d";
9
+ const RANGE_IDS = new Set(METRICS_RANGES.map((item) => item.id));
10
+ export function parseMetricsRangeId(raw) {
11
+ if (raw && RANGE_IDS.has(raw))
12
+ return raw;
13
+ return DEFAULT_METRICS_RANGE;
14
+ }
@@ -137,6 +137,11 @@
137
137
  }
138
138
 
139
139
  .vertebr-chip {
140
+ display: inline-block;
141
+ max-width: 12rem;
142
+ overflow: hidden;
143
+ text-overflow: ellipsis;
144
+ white-space: nowrap;
140
145
  border: 1px solid var(--vb-border);
141
146
  border-radius: 999px;
142
147
  padding: 0.1rem 0.45rem;
@@ -145,6 +150,43 @@
145
150
  background: var(--vb-surface-tint);
146
151
  }
147
152
 
153
+ .vertebr-cell-thumbs {
154
+ display: flex;
155
+ align-items: center;
156
+ }
157
+
158
+ .vertebr-cell-thumbs img {
159
+ width: 36px;
160
+ height: 36px;
161
+ object-fit: cover;
162
+ border-radius: var(--vb-radius-sm);
163
+ border: 1px solid var(--vb-border);
164
+ background: var(--vb-bg-muted);
165
+ flex: none;
166
+ }
167
+
168
+ .vertebr-cell-thumbs img + img {
169
+ margin-left: -0.55rem;
170
+ box-shadow: -2px 0 0 var(--vb-bg-elevated);
171
+ }
172
+
173
+ .vertebr-cell-thumbs-more {
174
+ margin-left: 0.4rem;
175
+ font-size: 0.72rem;
176
+ font-weight: 600;
177
+ color: var(--vb-text-muted);
178
+ }
179
+
180
+ .vertebr-cell-thumbs-detail {
181
+ flex-wrap: wrap;
182
+ gap: 0.75rem;
183
+ }
184
+
185
+ .vertebr-cell-thumbs-detail img + img {
186
+ margin-left: 0;
187
+ box-shadow: none;
188
+ }
189
+
148
190
  .vertebr-empty {
149
191
  flex: 1 1 auto;
150
192
  display: grid;