@vertebr/react 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/{VertebrAdmin.d.ts → components/admin/VertebrAdmin.d.ts} +3 -1
  2. package/dist/components/admin/VertebrAdmin.d.ts.map +1 -0
  3. package/dist/{VertebrAdmin.js → components/admin/VertebrAdmin.js} +117 -33
  4. package/dist/components/config/ConfigForm.d.ts +19 -0
  5. package/dist/components/config/ConfigForm.d.ts.map +1 -0
  6. package/dist/components/config/ConfigForm.js +281 -0
  7. package/dist/{EnvSelect.d.ts → components/env/EnvSelect.d.ts} +1 -1
  8. package/dist/components/env/EnvSelect.d.ts.map +1 -0
  9. package/dist/components/gallery/GalleryBoard.d.ts +14 -0
  10. package/dist/components/gallery/GalleryBoard.d.ts.map +1 -0
  11. package/dist/components/gallery/GalleryBoard.js +14 -0
  12. package/dist/components/index.d.ts +9 -0
  13. package/dist/components/index.d.ts.map +1 -0
  14. package/dist/components/index.js +6 -0
  15. package/dist/{MetricsBoard.d.ts → components/metrics/MetricsBoard.d.ts} +1 -1
  16. package/dist/components/metrics/MetricsBoard.d.ts.map +1 -0
  17. package/dist/components/resources/BulkImageUploadModal.d.ts +17 -0
  18. package/dist/components/resources/BulkImageUploadModal.d.ts.map +1 -0
  19. package/dist/components/resources/BulkImageUploadModal.js +47 -0
  20. package/dist/components/resources/ColumnVisibilityMenu.d.ts +11 -0
  21. package/dist/components/resources/ColumnVisibilityMenu.d.ts.map +1 -0
  22. package/dist/components/resources/ColumnVisibilityMenu.js +40 -0
  23. package/dist/components/resources/ImageUploadControl.d.ts +10 -0
  24. package/dist/components/resources/ImageUploadControl.d.ts.map +1 -0
  25. package/dist/components/resources/ImageUploadControl.js +50 -0
  26. package/dist/components/resources/ResourceCell.d.ts +10 -0
  27. package/dist/components/resources/ResourceCell.d.ts.map +1 -0
  28. package/dist/components/resources/ResourceCell.js +114 -0
  29. package/dist/{ResourceTable.d.ts → components/resources/ResourceTable.d.ts} +10 -2
  30. package/dist/components/resources/ResourceTable.d.ts.map +1 -0
  31. package/dist/components/resources/ResourceTable.js +200 -0
  32. package/dist/components/resources/ResourceViewModal.d.ts +11 -0
  33. package/dist/components/resources/ResourceViewModal.d.ts.map +1 -0
  34. package/dist/components/resources/ResourceViewModal.js +44 -0
  35. package/dist/components/theme/ThemeToggle.d.ts +7 -0
  36. package/dist/components/theme/ThemeToggle.d.ts.map +1 -0
  37. package/dist/components/theme/ThemeToggle.js +10 -0
  38. package/dist/hooks/index.d.ts +5 -0
  39. package/dist/hooks/index.d.ts.map +1 -0
  40. package/dist/hooks/index.js +2 -0
  41. package/dist/{useVertebrControls.d.ts → hooks/useVertebrControls.d.ts} +2 -2
  42. package/dist/hooks/useVertebrControls.d.ts.map +1 -0
  43. package/dist/{useVertebrControls.js → hooks/useVertebrControls.js} +7 -18
  44. package/dist/hooks/useVertebrTheme.d.ts +7 -0
  45. package/dist/hooks/useVertebrTheme.d.ts.map +1 -0
  46. package/dist/hooks/useVertebrTheme.js +38 -0
  47. package/dist/index.d.ts +15 -10
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +11 -6
  50. package/dist/styles/auth.css +39 -0
  51. package/dist/styles/base.css +70 -0
  52. package/dist/styles/buttons.css +86 -0
  53. package/dist/styles/cells.css +178 -0
  54. package/dist/styles/columns.css +103 -0
  55. package/dist/styles/form.css +498 -0
  56. package/dist/styles/gallery.css +90 -0
  57. package/dist/styles/index.css +18 -0
  58. package/dist/styles/main.css +89 -0
  59. package/dist/styles/modal.css +110 -0
  60. package/dist/styles/nav.css +92 -0
  61. package/dist/styles/panel.css +204 -0
  62. package/dist/styles/select.css +151 -0
  63. package/dist/styles/sidebar.css +42 -0
  64. package/dist/styles/states.css +169 -0
  65. package/dist/styles/table.css +92 -0
  66. package/dist/styles/theme-toggle.css +87 -0
  67. package/dist/styles.css +2 -997
  68. package/dist/themes/dark.css +55 -0
  69. package/dist/themes/index.css +2 -0
  70. package/dist/themes/light.css +102 -0
  71. package/package.json +6 -3
  72. package/dist/ConfigForm.d.ts +0 -9
  73. package/dist/ConfigForm.d.ts.map +0 -1
  74. package/dist/ConfigForm.js +0 -87
  75. package/dist/EnvSelect.d.ts.map +0 -1
  76. package/dist/MetricsBoard.d.ts.map +0 -1
  77. package/dist/ResourceTable.d.ts.map +0 -1
  78. package/dist/ResourceTable.js +0 -112
  79. package/dist/VertebrAdmin.d.ts.map +0 -1
  80. package/dist/client.d.ts +0 -32
  81. package/dist/client.d.ts.map +0 -1
  82. package/dist/client.js +0 -65
  83. package/dist/types.d.ts +0 -103
  84. package/dist/types.d.ts.map +0 -1
  85. package/dist/types.js +0 -1
  86. package/dist/useVertebrControls.d.ts.map +0 -1
  87. /package/dist/{EnvSelect.js → components/env/EnvSelect.js} +0 -0
  88. /package/dist/{MetricsBoard.js → components/metrics/MetricsBoard.js} +0 -0
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ImageUploadControl } from "../resources/ImageUploadControl";
3
+ export function GalleryBoard({ label = "Gallery", items, accept, maxBytes, canDelete, loading, onRefresh, onUpload, onDelete, }) {
4
+ const meta = {
5
+ field: "gallery",
6
+ accept,
7
+ maxBytes,
8
+ };
9
+ return (_jsxs("div", { className: "vertebr-gallery", children: [_jsxs("div", { className: "vertebr-panel-head", children: [_jsx("h2", { children: label }), _jsxs("div", { className: "vertebr-gallery-head-actions", children: [_jsx("span", { className: "vertebr-count", children: items.length }), onRefresh ? (_jsx("button", { type: "button", className: "vertebr-btn", onClick: onRefresh, disabled: loading, children: loading ? "Refreshing…" : "Refresh" })) : null] })] }), _jsxs("div", { className: "vertebr-gallery-body", children: [_jsx(ImageUploadControl, { meta: meta, multiple: true, label: "Drop images here or browse to add to the gallery", onUpload: onUpload }), items.length === 0 ? (_jsx("div", { className: "vertebr-empty", children: "No images in the gallery yet." })) : (_jsx("div", { className: "vertebr-gallery-grid", children: items.map((item) => (_jsxs("article", { className: "vertebr-gallery-card", children: [_jsx("a", { className: "vertebr-gallery-thumb", href: item.url, target: "_blank", rel: "noreferrer", title: item.name ?? item.url, children: _jsx("img", { src: item.url, alt: item.name ?? "", loading: "lazy" }) }), _jsxs("div", { className: "vertebr-gallery-meta", children: [_jsx("strong", { title: item.name ?? item.id, children: item.name ?? item.id }), _jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url.replace(/^https?:\/\//i, "") })] }), canDelete && onDelete ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-danger-quiet", onClick: () => {
10
+ if (window.confirm("Delete this image from the gallery?")) {
11
+ void onDelete(item.id);
12
+ }
13
+ }, children: "Delete" })) : null] }, item.id))) }))] })] }));
14
+ }
@@ -0,0 +1,9 @@
1
+ export { VertebrAdmin } from "./admin/VertebrAdmin";
2
+ export type { VertebrAdminProps } from "./admin/VertebrAdmin";
3
+ export { ConfigForm } from "./config/ConfigForm";
4
+ export { EnvSelect } from "./env/EnvSelect";
5
+ export { MetricsBoard } from "./metrics/MetricsBoard";
6
+ export { ResourcePanel, ResourceTable } from "./resources/ResourceTable";
7
+ export { ThemeToggle } from "./theme/ThemeToggle";
8
+ export type { ThemeToggleProps } from "./theme/ThemeToggle";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { VertebrAdmin } from "./admin/VertebrAdmin";
2
+ export { ConfigForm } from "./config/ConfigForm";
3
+ export { EnvSelect } from "./env/EnvSelect";
4
+ export { MetricsBoard } from "./metrics/MetricsBoard";
5
+ export { ResourcePanel, ResourceTable } from "./resources/ResourceTable";
6
+ export { ThemeToggle } from "./theme/ThemeToggle";
@@ -1,4 +1,4 @@
1
- import type { MetricCard } from "./types";
1
+ import type { MetricCard } from "@vertebr/client";
2
2
  type MetricsBoardProps = {
3
3
  label?: string;
4
4
  metrics: MetricCard[];
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricsBoard.d.ts","sourceRoot":"","sources":["../../../src/components/metrics/MetricsBoard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,KAAK,iBAAiB,GAAG;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,KAAkB,EAClB,OAAO,EACP,SAAS,EACT,OAAO,GACR,EAAE,iBAAiB,+BAqCnB"}
@@ -0,0 +1,17 @@
1
+ import type { ResourceUploadMeta } from "@vertebr/client";
2
+ export type BulkImageUploadModalProps = {
3
+ uploads: Record<string, ResourceUploadMeta>;
4
+ canCreate?: boolean;
5
+ fieldLabels: Record<string, string>;
6
+ onClose: () => void;
7
+ onUpload: (field: string, files: File[]) => Promise<Array<{
8
+ url: string;
9
+ name?: string;
10
+ }>>;
11
+ onCreateFromUploads?: (field: string, files: Array<{
12
+ url: string;
13
+ name?: string;
14
+ }>) => Promise<void>;
15
+ };
16
+ export declare function BulkImageUploadModal({ uploads, canCreate, fieldLabels, onClose, onUpload, onCreateFromUploads, }: BulkImageUploadModalProps): import("react").JSX.Element | null;
17
+ //# sourceMappingURL=BulkImageUploadModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BulkImageUploadModal.d.ts","sourceRoot":"","sources":["../../../src/components/resources/BulkImageUploadModal.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,CACR,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,mBAAmB,CAAC,EAAE,CACpB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,KACzC,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,SAAS,EACT,WAAW,EACX,OAAO,EACP,QAAQ,EACR,mBAAmB,GACpB,EAAE,yBAAyB,sCA6H3B"}
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { ImageUploadControl } from "./ImageUploadControl";
4
+ export function BulkImageUploadModal({ uploads, canCreate, fieldLabels, onClose, onUpload, onCreateFromUploads, }) {
5
+ const fields = useMemo(() => Object.keys(uploads), [uploads]);
6
+ const [field, setField] = useState(fields[0] ?? "");
7
+ const [createRows, setCreateRows] = useState(Boolean(canCreate));
8
+ const [results, setResults] = useState([]);
9
+ const [status, setStatus] = useState(null);
10
+ const meta = uploads[field];
11
+ useEffect(() => {
12
+ if (!fields.includes(field) && fields[0])
13
+ setField(fields[0]);
14
+ }, [field, fields]);
15
+ useEffect(() => {
16
+ const onKeyDown = (event) => {
17
+ if (event.key === "Escape")
18
+ onClose();
19
+ };
20
+ window.addEventListener("keydown", onKeyDown);
21
+ const previous = document.body.style.overflow;
22
+ document.body.style.overflow = "hidden";
23
+ return () => {
24
+ window.removeEventListener("keydown", onKeyDown);
25
+ document.body.style.overflow = previous;
26
+ };
27
+ }, [onClose]);
28
+ if (!meta)
29
+ return null;
30
+ return (_jsx("div", { className: "vertebr-modal-backdrop", role: "presentation", onClick: onClose, children: _jsxs("div", { className: "vertebr-modal vertebr-modal-sm", role: "dialog", "aria-modal": "true", "aria-label": "Bulk image upload", onClick: (event) => event.stopPropagation(), children: [_jsxs("header", { className: "vertebr-modal-head", children: [_jsxs("div", { className: "vertebr-modal-heading", children: [_jsx("p", { className: "vertebr-modal-eyebrow", children: "Media" }), _jsx("h2", { children: "Bulk image upload" })] }), _jsx("button", { type: "button", className: "vertebr-btn", onClick: onClose, children: "Close" })] }), _jsxs("div", { className: "vertebr-modal-body", children: [fields.length > 1 ? (_jsxs("label", { className: "vertebr-field", children: [_jsx("span", { children: "Target field" }), _jsx("select", { className: "vertebr-native-select", value: field, onChange: (event) => {
31
+ setField(event.target.value);
32
+ setResults([]);
33
+ setStatus(null);
34
+ }, children: fields.map((key) => (_jsx("option", { value: key, children: fieldLabels[key] ?? key }, key))) })] })) : null, canCreate && onCreateFromUploads ? (_jsxs("label", { className: "vertebr-upload-check", children: [_jsx("input", { type: "checkbox", checked: createRows, onChange: (event) => setCreateRows(event.target.checked) }), _jsx("span", { children: "Create a record for each uploaded image" })] })) : null, _jsx(ImageUploadControl, { meta: meta, multiple: true, onUpload: async (files) => {
35
+ setStatus(`Uploading ${files.length} image${files.length === 1 ? "" : "s"}…`);
36
+ const uploaded = await onUpload(field, files);
37
+ setResults(uploaded);
38
+ if (createRows && onCreateFromUploads) {
39
+ setStatus("Creating records…");
40
+ await onCreateFromUploads(field, uploaded);
41
+ setStatus(`Uploaded ${uploaded.length} and created records.`);
42
+ }
43
+ else {
44
+ setStatus(`Uploaded ${uploaded.length} image${uploaded.length === 1 ? "" : "s"}.`);
45
+ }
46
+ } }), status ? _jsx("p", { className: "vertebr-upload-status", children: status }) : null, results.length > 0 ? (_jsx("ul", { className: "vertebr-upload-results", children: results.map((item) => (_jsxs("li", { children: [_jsx("img", { src: item.url, alt: "" }), _jsxs("div", { children: [_jsx("strong", { children: item.name ?? "Image" }), _jsx("a", { href: item.url, target: "_blank", rel: "noreferrer", children: item.url })] })] }, item.url))) })) : null] })] }) }));
47
+ }
@@ -0,0 +1,11 @@
1
+ export type ColumnOption = {
2
+ id: string;
3
+ label: string;
4
+ };
5
+ export type ColumnVisibilityMenuProps = {
6
+ columns: ColumnOption[];
7
+ visibility: Record<string, boolean>;
8
+ onChange: (next: Record<string, boolean>) => void;
9
+ };
10
+ export declare function ColumnVisibilityMenu({ columns, visibility, onChange, }: ColumnVisibilityMenuProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=ColumnVisibilityMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ColumnVisibilityMenu.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ColumnVisibilityMenu.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CACnD,CAAC;AAEF,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,UAAU,EACV,QAAQ,GACT,EAAE,yBAAyB,+BAkF3B"}
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef, useState } from "react";
3
+ export function ColumnVisibilityMenu({ columns, visibility, onChange, }) {
4
+ const [open, setOpen] = useState(false);
5
+ const rootRef = useRef(null);
6
+ const menuId = useId();
7
+ const hiddenCount = columns.filter((col) => visibility[col.id] === false).length;
8
+ useEffect(() => {
9
+ if (!open)
10
+ return;
11
+ const onPointerDown = (event) => {
12
+ if (!rootRef.current?.contains(event.target)) {
13
+ setOpen(false);
14
+ }
15
+ };
16
+ const onKeyDown = (event) => {
17
+ if (event.key === "Escape")
18
+ setOpen(false);
19
+ };
20
+ window.addEventListener("mousedown", onPointerDown);
21
+ window.addEventListener("keydown", onKeyDown);
22
+ return () => {
23
+ window.removeEventListener("mousedown", onPointerDown);
24
+ window.removeEventListener("keydown", onKeyDown);
25
+ };
26
+ }, [open]);
27
+ const visibleCount = columns.length - hiddenCount;
28
+ return (_jsxs("div", { className: "vertebr-col-menu", ref: rootRef, children: [_jsxs("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", "aria-expanded": open, "aria-controls": menuId, onClick: () => setOpen((value) => !value), children: ["Columns", hiddenCount > 0 ? (_jsx("span", { className: "vertebr-col-menu-count", children: visibleCount })) : null] }), open ? (_jsxs("div", { className: "vertebr-col-menu-panel", id: menuId, role: "menu", children: [_jsxs("div", { className: "vertebr-col-menu-head", children: [_jsx("span", { children: "Visible fields" }), _jsx("button", { type: "button", className: "vertebr-link-btn", onClick: () => {
29
+ const next = {};
30
+ for (const col of columns)
31
+ next[col.id] = true;
32
+ onChange(next);
33
+ }, children: "Show all" })] }), _jsx("ul", { className: "vertebr-col-menu-list", children: columns.map((col) => {
34
+ const checked = visibility[col.id] !== false;
35
+ return (_jsx("li", { children: _jsxs("label", { className: "vertebr-col-menu-item", children: [_jsx("input", { type: "checkbox", checked: checked, disabled: checked && visibleCount <= 1, onChange: () => {
36
+ const next = { ...visibility, [col.id]: !checked };
37
+ onChange(next);
38
+ } }), _jsx("span", { children: col.label })] }) }, col.id));
39
+ }) })] })) : null] }));
40
+ }
@@ -0,0 +1,10 @@
1
+ import type { ResourceUploadMeta } from "@vertebr/client";
2
+ export type ImageUploadControlProps = {
3
+ meta: ResourceUploadMeta;
4
+ disabled?: boolean;
5
+ multiple?: boolean;
6
+ label?: string;
7
+ onUpload: (files: File[]) => Promise<void>;
8
+ };
9
+ export declare function ImageUploadControl({ meta, disabled, multiple, label, onUpload, }: ImageUploadControlProps): import("react").JSX.Element;
10
+ //# sourceMappingURL=ImageUploadControl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageUploadControl.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ImageUploadControl.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D,MAAM,MAAM,uBAAuB,GAAG;IACpC,IAAI,EAAE,kBAAkB,CAAC;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C,CAAC;AAMF,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,QAAQ,EACR,QAAgB,EAChB,KAAK,EACL,QAAQ,GACT,EAAE,uBAAuB,+BA8EzB"}
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState } from "react";
3
+ function acceptAttr(accept) {
4
+ return accept.join(",");
5
+ }
6
+ export function ImageUploadControl({ meta, disabled, multiple = false, label, onUpload, }) {
7
+ const inputRef = useRef(null);
8
+ const [busy, setBusy] = useState(false);
9
+ const [error, setError] = useState(null);
10
+ const [dragOver, setDragOver] = useState(false);
11
+ const run = async (fileList) => {
12
+ if (!fileList || disabled || busy)
13
+ return;
14
+ const files = Array.from(fileList);
15
+ if (!files.length)
16
+ return;
17
+ setBusy(true);
18
+ setError(null);
19
+ try {
20
+ await onUpload(multiple ? files : files.slice(0, 1));
21
+ }
22
+ catch (err) {
23
+ setError(err instanceof Error ? err.message : "Upload failed");
24
+ }
25
+ finally {
26
+ setBusy(false);
27
+ if (inputRef.current)
28
+ inputRef.current.value = "";
29
+ }
30
+ };
31
+ return (_jsxs("div", { className: "vertebr-upload", children: [_jsxs("div", { className: "vertebr-upload-drop", "data-busy": busy ? "true" : undefined, "data-drag": dragOver ? "true" : undefined, onDragEnter: (event) => {
32
+ event.preventDefault();
33
+ setDragOver(true);
34
+ }, onDragOver: (event) => {
35
+ event.preventDefault();
36
+ setDragOver(true);
37
+ }, onDragLeave: (event) => {
38
+ event.preventDefault();
39
+ setDragOver(false);
40
+ }, onDrop: (event) => {
41
+ event.preventDefault();
42
+ setDragOver(false);
43
+ void run(event.dataTransfer.files);
44
+ }, children: [_jsx("p", { className: "vertebr-upload-copy", children: busy
45
+ ? "Uploading…"
46
+ : label ??
47
+ (multiple
48
+ ? "Drop images here or browse"
49
+ : "Drop an image here or browse") }), _jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: disabled || busy, onClick: () => inputRef.current?.click(), children: multiple ? "Choose images" : "Choose image" }), _jsx("input", { ref: inputRef, type: "file", accept: acceptAttr(meta.accept), multiple: multiple, hidden: true, onChange: (event) => void run(event.target.files) })] }), error ? (_jsx("p", { className: "vertebr-upload-error", role: "alert", children: error })) : null] }));
50
+ }
@@ -0,0 +1,10 @@
1
+ import type { FieldSchema } from "@vertebr/client";
2
+ export type ResourceCellProps = {
3
+ fieldKey: string;
4
+ field?: FieldSchema;
5
+ value: unknown;
6
+ /** `detail` expands images/text for modals. */
7
+ variant?: "table" | "detail";
8
+ };
9
+ export declare function ResourceCell({ fieldKey, field, value, variant, }: ResourceCellProps): import("react").JSX.Element;
10
+ //# sourceMappingURL=ResourceCell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResourceCell.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceCell.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAuDnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC9B,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,KAAK,EACL,KAAK,EACL,OAAiB,GAClB,EAAE,iBAAiB,+BAsInB"}
@@ -0,0 +1,114 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ const IMAGE_KEY = /(image|img|photo|avatar|thumbnail|thumb|cover)/i;
3
+ const URL_KEY = /(url|href|link|src)/i;
4
+ const CENTS_KEY = /(cents|priceCents|amountCents)/i;
5
+ const PRICE_KEY = /(price|amount|cost|total)/i;
6
+ const IMAGE_EXT = /\.(png|jpe?g|gif|webp|svg|avif)(\?|#|$)/i;
7
+ function looksLikeUrl(value) {
8
+ return /^https?:\/\//i.test(value) || value.startsWith("//");
9
+ }
10
+ function looksLikeImageUrl(value) {
11
+ if (!looksLikeUrl(value))
12
+ return false;
13
+ if (IMAGE_EXT.test(value))
14
+ return true;
15
+ // CDN URLs often omit extensions but include image query params / paths.
16
+ return /(image|img|media|cdn|photo)/i.test(value);
17
+ }
18
+ function formatDate(value) {
19
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
20
+ return value.toLocaleString();
21
+ }
22
+ if (value &&
23
+ typeof value === "object" &&
24
+ "toDate" in value &&
25
+ typeof value.toDate === "function") {
26
+ try {
27
+ return value.toDate().toLocaleString();
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ if (typeof value === "string" || typeof value === "number") {
34
+ const date = new Date(value);
35
+ if (!Number.isNaN(date.getTime()))
36
+ return date.toLocaleString();
37
+ }
38
+ return null;
39
+ }
40
+ function formatMoney(value, asCents) {
41
+ const amount = asCents ? value / 100 : value;
42
+ try {
43
+ return new Intl.NumberFormat(undefined, {
44
+ style: "currency",
45
+ currency: "USD",
46
+ maximumFractionDigits: asCents || !Number.isInteger(amount) ? 2 : 0,
47
+ }).format(amount);
48
+ }
49
+ catch {
50
+ return asCents ? `$${(value / 100).toFixed(2)}` : String(value);
51
+ }
52
+ }
53
+ export function ResourceCell({ fieldKey, field, value, variant = "table", }) {
54
+ const detail = variant === "detail";
55
+ if (value == null || value === "") {
56
+ return _jsx("span", { className: "vertebr-cell-empty", children: "\u2014" });
57
+ }
58
+ const type = field?.type;
59
+ if (type === "boolean" || typeof value === "boolean") {
60
+ const on = Boolean(value);
61
+ return (_jsx("span", { className: "vertebr-badge", "data-tone": on ? "ok" : "neutral", children: on ? "Active" : "Off" }));
62
+ }
63
+ if (type === "datetime") {
64
+ const formatted = formatDate(value);
65
+ if (formatted) {
66
+ return _jsx("span", { className: "vertebr-cell-datetime", children: formatted });
67
+ }
68
+ }
69
+ if (type === "number" || typeof value === "number") {
70
+ if (typeof value === "number" && Number.isFinite(value)) {
71
+ if (CENTS_KEY.test(fieldKey) || /cents/i.test(field?.label ?? "")) {
72
+ return (_jsx("span", { className: "vertebr-cell-money", children: formatMoney(value, true) }));
73
+ }
74
+ if (PRICE_KEY.test(fieldKey)) {
75
+ return (_jsx("span", { className: "vertebr-cell-money", children: formatMoney(value, false) }));
76
+ }
77
+ return (_jsx("span", { className: "vertebr-cell-number", children: new Intl.NumberFormat().format(value) }));
78
+ }
79
+ }
80
+ if (Array.isArray(value)) {
81
+ if (value.length === 0) {
82
+ return _jsx("span", { className: "vertebr-cell-empty", children: "\u2014" });
83
+ }
84
+ if (value.every((entry) => typeof entry === "string")) {
85
+ return (_jsx("div", { className: "vertebr-chip-row", children: value.map((entry) => (_jsx("span", { className: "vertebr-chip", children: entry }, entry))) }));
86
+ }
87
+ }
88
+ if (typeof value === "string") {
89
+ const showImage = IMAGE_KEY.test(fieldKey) ||
90
+ (URL_KEY.test(fieldKey) && looksLikeImageUrl(value)) ||
91
+ looksLikeImageUrl(value);
92
+ if (showImage && looksLikeUrl(value)) {
93
+ return (_jsxs("a", { className: detail ? "vertebr-cell-media vertebr-cell-media-detail" : "vertebr-cell-media", href: value, target: "_blank", rel: "noreferrer", title: value, onClick: (event) => event.stopPropagation(), children: [_jsx("img", { src: value, alt: "", loading: "lazy" }), detail ? (_jsx("span", { className: "vertebr-cell-link", children: value })) : (_jsx("span", { className: "vertebr-cell-truncate", children: value }))] }));
94
+ }
95
+ if (URL_KEY.test(fieldKey) || looksLikeUrl(value)) {
96
+ return (_jsx("a", { className: "vertebr-cell-link", href: value.startsWith("//") ? `https:${value}` : value, target: "_blank", rel: "noreferrer", title: value, onClick: (event) => event.stopPropagation(), children: detail ? value : value.replace(/^https?:\/\//i, "") }));
97
+ }
98
+ if (!detail && (type === "richtext" || value.length > 80)) {
99
+ return (_jsx("span", { className: "vertebr-cell-clamp", title: value, children: value }));
100
+ }
101
+ if (detail && (type === "richtext" || value.length > 80)) {
102
+ return _jsx("p", { className: "vertebr-cell-prose", children: value });
103
+ }
104
+ return _jsx("span", { className: "vertebr-cell-text", children: value });
105
+ }
106
+ const date = formatDate(value);
107
+ if (date) {
108
+ return _jsx("span", { className: "vertebr-cell-datetime", children: date });
109
+ }
110
+ if (typeof value === "object") {
111
+ return (_jsx("code", { className: "vertebr-cell-json", title: JSON.stringify(value), children: JSON.stringify(value) }));
112
+ }
113
+ return _jsx("span", { className: "vertebr-cell-text", children: String(value) });
114
+ }
@@ -1,4 +1,4 @@
1
- import type { FieldSchema, ResourceActionMeta } from "./types";
1
+ import type { FieldSchema, ResourceActionMeta, ResourceUploadMeta } from "@vertebr/client";
2
2
  type ResourcePanelProps = {
3
3
  schema: Record<string, FieldSchema>;
4
4
  items: Record<string, unknown>[];
@@ -8,12 +8,20 @@ type ResourcePanelProps = {
8
8
  delete?: boolean;
9
9
  };
10
10
  actions?: Record<string, ResourceActionMeta>;
11
+ uploads?: Record<string, ResourceUploadMeta>;
11
12
  rowIdKeys?: string[];
13
+ /** Persist column visibility under this key (e.g. resource id). */
14
+ storageKey?: string;
12
15
  onUpdate?: (rowId: string, value: Record<string, unknown>) => Promise<void>;
16
+ onCreate?: (value: Record<string, unknown>) => Promise<void>;
13
17
  onDelete?: (rowId: string) => Promise<void>;
14
18
  onAction?: (rowId: string, actionId: string) => Promise<void>;
19
+ onUpload?: (field: string, files: File[]) => Promise<Array<{
20
+ url: string;
21
+ name?: string;
22
+ }>>;
15
23
  };
16
- export declare function ResourcePanel({ schema, items, permissions, actions, rowIdKeys, onUpdate, onDelete, onAction, }: ResourcePanelProps): import("react").JSX.Element;
24
+ export declare function ResourcePanel({ schema, items, permissions, actions, uploads, rowIdKeys, storageKey, onUpdate, onCreate, onDelete, onAction, onUpload, }: ResourcePanelProps): import("react").JSX.Element;
17
25
  /** @deprecated Prefer ResourcePanel */
18
26
  export declare function ResourceTable(props: {
19
27
  schema: Record<string, FieldSchema>;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResourceTable.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceTable.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AAMzB,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,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;CACrD,CAAC;AA+DF,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,KAAK,EACL,WAAW,EACX,OAAY,EACZ,OAAO,EACP,SAA0C,EAC1C,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,QAAQ,GACT,EAAE,kBAAkB,+BAoVpB;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"}
@@ -0,0 +1,200 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ import { flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table";
4
+ import { ConfigForm } from "../config/ConfigForm";
5
+ import { BulkImageUploadModal } from "./BulkImageUploadModal";
6
+ import { ColumnVisibilityMenu } from "./ColumnVisibilityMenu";
7
+ import { ResourceCell } from "./ResourceCell";
8
+ import { ResourceViewModal } from "./ResourceViewModal";
9
+ function resolveRowId(row, keys) {
10
+ for (const key of keys) {
11
+ const value = row[key];
12
+ if (typeof value === "string" && value.trim())
13
+ return value;
14
+ if (typeof value === "number")
15
+ return String(value);
16
+ }
17
+ return null;
18
+ }
19
+ function humanizeKey(key) {
20
+ return key
21
+ .replace(/([a-z])([A-Z])/g, "$1 $2")
22
+ .replace(/[_-]+/g, " ")
23
+ .replace(/\bcents\b/i, "")
24
+ .trim();
25
+ }
26
+ function rowTitle(row, keys) {
27
+ for (const key of ["name", "title", "label", ...keys]) {
28
+ const value = row[key];
29
+ if (typeof value === "string" && value.trim())
30
+ return value;
31
+ }
32
+ return "Record";
33
+ }
34
+ function readVisibility(storageKey) {
35
+ if (!storageKey || typeof localStorage === "undefined")
36
+ return {};
37
+ 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;
45
+ }
46
+ catch {
47
+ return {};
48
+ }
49
+ }
50
+ function writeVisibility(storageKey, value) {
51
+ if (!storageKey || typeof localStorage === "undefined")
52
+ return;
53
+ try {
54
+ localStorage.setItem(`vertebr:columns:${storageKey}`, JSON.stringify(value));
55
+ }
56
+ catch {
57
+ /* ignore quota / private mode */
58
+ }
59
+ }
60
+ function titleFromFilename(name) {
61
+ return name
62
+ .replace(/\.[^.]+$/, "")
63
+ .replace(/[_-]+/g, " ")
64
+ .replace(/\s+/g, " ")
65
+ .trim();
66
+ }
67
+ export function ResourcePanel({ schema, items, permissions, actions = {}, uploads, rowIdKeys = ["id", "slug", "uid", "email"], storageKey, onUpdate, onCreate, onDelete, onAction, onUpload, }) {
68
+ const [editing, setEditing] = useState(null);
69
+ const [viewing, setViewing] = useState(null);
70
+ const [bulkOpen, setBulkOpen] = useState(false);
71
+ const [busyAction, setBusyAction] = useState(null);
72
+ const [columnVisibility, setColumnVisibility] = useState(() => readVisibility(storageKey));
73
+ const canUpdate = Boolean(permissions?.update && onUpdate);
74
+ const canCreate = Boolean(permissions?.create && onCreate);
75
+ const canDelete = Boolean(permissions?.delete && onDelete);
76
+ const canUpload = Boolean(uploads && onUpload && Object.keys(uploads).length);
77
+ const actionEntries = Object.entries(actions);
78
+ const fieldKeys = Object.keys(schema);
79
+ useEffect(() => {
80
+ setColumnVisibility(readVisibility(storageKey));
81
+ }, [storageKey]);
82
+ useEffect(() => {
83
+ writeVisibility(storageKey, columnVisibility);
84
+ }, [storageKey, columnVisibility]);
85
+ const columnOptions = useMemo(() => fieldKeys.map((key) => ({
86
+ id: key,
87
+ label: schema[key]?.label ?? humanizeKey(key),
88
+ })), [fieldKeys, schema]);
89
+ const columns = useMemo(() => {
90
+ const defs = fieldKeys.length === 0
91
+ ? [
92
+ {
93
+ id: "raw",
94
+ header: "Item",
95
+ cell: ({ row }) => (_jsx(ResourceCell, { fieldKey: "raw", value: row.original })),
96
+ },
97
+ ]
98
+ : fieldKeys.map((key) => {
99
+ const field = schema[key];
100
+ const isImage = /(image|img|photo|avatar|thumbnail|thumb|cover)/i.test(key) ||
101
+ /(image|img|photo)/i.test(field?.label ?? "");
102
+ return {
103
+ id: key,
104
+ accessorKey: key,
105
+ header: field?.label ?? humanizeKey(key),
106
+ meta: { fieldKey: key, field },
107
+ cell: ({ getValue }) => (_jsx(ResourceCell, { fieldKey: key, field: field, value: getValue() })),
108
+ size: isImage ? 220 : undefined,
109
+ };
110
+ });
111
+ defs.push({
112
+ id: "_actions",
113
+ header: () => _jsx("span", { className: "vertebr-sr-only", children: "Actions" }),
114
+ enableHiding: false,
115
+ cell: ({ row }) => {
116
+ const rowId = resolveRowId(row.original, rowIdKeys);
117
+ 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
118
+ ? actionEntries.map(([actionId, meta]) => (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet", disabled: busyAction === `${rowId}:${actionId}`, onClick: () => {
119
+ if (!onAction)
120
+ return;
121
+ setBusyAction(`${rowId}:${actionId}`);
122
+ void onAction(rowId, actionId).finally(() => setBusyAction(null));
123
+ }, children: meta.label }, actionId)))
124
+ : null, canDelete && rowId ? (_jsx("button", { type: "button", className: "vertebr-btn vertebr-btn-quiet vertebr-btn-danger-quiet", onClick: () => {
125
+ if (window.confirm("Delete this row? This cannot be undone.")) {
126
+ void onDelete?.(rowId);
127
+ }
128
+ }, children: "Delete" })) : null] }));
129
+ },
130
+ });
131
+ return defs;
132
+ }, [
133
+ schema,
134
+ fieldKeys,
135
+ canUpdate,
136
+ canDelete,
137
+ actionEntries,
138
+ busyAction,
139
+ onAction,
140
+ onDelete,
141
+ rowIdKeys,
142
+ ]);
143
+ const table = useReactTable({
144
+ data: items,
145
+ columns,
146
+ state: { columnVisibility },
147
+ onColumnVisibilityChange: setColumnVisibility,
148
+ getCoreRowModel: getCoreRowModel(),
149
+ });
150
+ const editingId = editing ? resolveRowId(editing, rowIdKeys) : null;
151
+ if (editing && editingId && onUpdate) {
152
+ const editableSchema = Object.fromEntries(Object.entries(schema).filter(([, field]) => !field.readOnly));
153
+ const title = rowTitle(editing, rowIdKeys);
154
+ return (_jsxs("div", { className: "vertebr-resource-edit", children: [_jsxs("div", { className: "vertebr-resource-edit-head", children: [_jsxs("div", { className: "vertebr-resource-edit-heading", children: [_jsx("p", { className: "vertebr-resource-edit-eyebrow", children: "Editing record" }), _jsx("h2", { children: title }), _jsx("p", { className: "vertebr-resource-edit-id", children: editingId })] }), _jsx("button", { type: "button", className: "vertebr-btn", onClick: () => setEditing(null), children: "Back" })] }), _jsx(ConfigForm, { variant: "record", schema: editableSchema, value: editing, uploads: uploads, onUpload: onUpload, onSave: async (next) => {
155
+ await onUpdate(editingId, next);
156
+ setEditing(null);
157
+ } })] }));
158
+ }
159
+ const toolbar = (_jsx("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] }) }));
160
+ const bulkModal = bulkOpen && uploads && onUpload ? (_jsx(BulkImageUploadModal, { uploads: uploads, canCreate: canCreate, fieldLabels: Object.fromEntries(Object.keys(uploads).map((key) => [
161
+ key,
162
+ schema[key]?.label ?? humanizeKey(key),
163
+ ])), onClose: () => setBulkOpen(false), onUpload: onUpload, onCreateFromUploads: canCreate && onCreate
164
+ ? async (field, files) => {
165
+ for (const file of files) {
166
+ const name = titleFromFilename(file.name ?? "Image");
167
+ await onCreate({
168
+ [field]: file.url,
169
+ name,
170
+ title: name,
171
+ });
172
+ }
173
+ }
174
+ : undefined })) : null;
175
+ if (items.length === 0) {
176
+ 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] }));
177
+ }
178
+ 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 })] }), canUpload || columnOptions.length > 0 ? toolbar : null, _jsx("div", { className: "vertebr-table-wrap", 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"
179
+ ? "vertebr-col-actions"
180
+ : undefined, children: header.isPlaceholder
181
+ ? null
182
+ : 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) => {
183
+ if (event.key === "Enter" || event.key === " ") {
184
+ event.preventDefault();
185
+ setViewing(row.original);
186
+ }
187
+ }, children: row.getVisibleCells().map((cell) => (_jsx("td", { className: cell.column.id === "_actions"
188
+ ? "vertebr-col-actions"
189
+ : 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: () => {
190
+ setEditing(viewing);
191
+ setViewing(null);
192
+ } })) : null, bulkModal] }));
193
+ }
194
+ /** @deprecated Prefer ResourcePanel */
195
+ export function ResourceTable(props) {
196
+ return _jsx(ResourcePanel, { ...props });
197
+ }
198
+ function ImageUploadIcon() {
199
+ 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: "M16 5h6" }), _jsx("path", { d: "M19 2v6" }), _jsx("rect", { x: "3", y: "7", width: "14", height: "14", rx: "2" }), _jsx("circle", { cx: "8.5", cy: "12.5", r: "1.5" }), _jsx("path", { d: "m17 21-4.5-4.5L7 21" })] }));
200
+ }
@@ -0,0 +1,11 @@
1
+ import type { FieldSchema } from "@vertebr/client";
2
+ export type ResourceViewModalProps = {
3
+ schema: Record<string, FieldSchema>;
4
+ row: Record<string, unknown>;
5
+ title: string;
6
+ canEdit?: boolean;
7
+ onClose: () => void;
8
+ onEdit?: () => void;
9
+ };
10
+ export declare function ResourceViewModal({ schema, row, title, canEdit, onClose, onEdit, }: ResourceViewModalProps): import("react").JSX.Element;
11
+ //# sourceMappingURL=ResourceViewModal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResourceViewModal.d.ts","sourceRoot":"","sources":["../../../src/components/resources/ResourceViewModal.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAGnD,MAAM,MAAM,sBAAsB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AA4BF,wBAAgB,iBAAiB,CAAC,EAChC,MAAM,EACN,GAAG,EACH,KAAK,EACL,OAAO,EACP,OAAO,EACP,MAAM,GACP,EAAE,sBAAsB,+BAsFxB"}