@inshorts/glint-ui 0.5.4 → 0.6.1
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.
- package/README.md +3 -1
- package/dist/alert.cjs +96 -0
- package/dist/alert.cjs.map +1 -0
- package/dist/alert.d.cts +29 -0
- package/dist/alert.d.ts +29 -0
- package/dist/alert.js +10 -0
- package/dist/alert.js.map +1 -0
- package/dist/autocomplete.cjs +578 -0
- package/dist/autocomplete.cjs.map +1 -0
- package/dist/autocomplete.d.cts +56 -0
- package/dist/autocomplete.d.ts +56 -0
- package/dist/autocomplete.js +11 -0
- package/dist/autocomplete.js.map +1 -0
- package/dist/badge.cjs +89 -0
- package/dist/badge.cjs.map +1 -0
- package/dist/badge.d.cts +25 -0
- package/dist/badge.d.ts +25 -0
- package/dist/badge.js +10 -0
- package/dist/badge.js.map +1 -0
- package/dist/breadcrumb.cjs +88 -0
- package/dist/breadcrumb.cjs.map +1 -0
- package/dist/breadcrumb.d.cts +14 -0
- package/dist/breadcrumb.d.ts +14 -0
- package/dist/breadcrumb.js +8 -0
- package/dist/breadcrumb.js.map +1 -0
- package/dist/button.cjs +143 -0
- package/dist/button.cjs.map +1 -0
- package/dist/button.d.cts +34 -0
- package/dist/button.d.ts +34 -0
- package/dist/button.js +9 -0
- package/dist/button.js.map +1 -0
- package/dist/card.cjs +111 -0
- package/dist/card.cjs.map +1 -0
- package/dist/card.d.cts +15 -0
- package/dist/card.d.ts +15 -0
- package/dist/card.js +18 -0
- package/dist/card.js.map +1 -0
- package/dist/checkbox.cjs +66 -0
- package/dist/checkbox.cjs.map +1 -0
- package/{src/checkbox.tsx → dist/checkbox.d.cts} +5 -22
- package/dist/checkbox.d.ts +25 -0
- package/dist/checkbox.js +10 -0
- package/dist/checkbox.js.map +1 -0
- package/dist/chunk-5MEOZHJA.js +513 -0
- package/dist/chunk-5MEOZHJA.js.map +1 -0
- package/dist/chunk-67ACNS42.js +29 -0
- package/dist/chunk-67ACNS42.js.map +1 -0
- package/dist/chunk-A6GRLKOO.js +85 -0
- package/dist/chunk-A6GRLKOO.js.map +1 -0
- package/dist/chunk-DS3QYIKL.js +90 -0
- package/dist/chunk-DS3QYIKL.js.map +1 -0
- package/dist/chunk-EWSBFU54.js +53 -0
- package/dist/chunk-EWSBFU54.js.map +1 -0
- package/dist/chunk-G4ZQUSAJ.js +38 -0
- package/dist/chunk-G4ZQUSAJ.js.map +1 -0
- package/dist/chunk-GMCUXEFI.js +67 -0
- package/dist/chunk-GMCUXEFI.js.map +1 -0
- package/dist/chunk-HBBOR43A.js +85 -0
- package/dist/chunk-HBBOR43A.js.map +1 -0
- package/dist/chunk-HCA5ODCD.js +11 -0
- package/dist/chunk-HCA5ODCD.js.map +1 -0
- package/dist/chunk-HYRJYUPI.js +26 -0
- package/dist/chunk-HYRJYUPI.js.map +1 -0
- package/dist/chunk-IRCTGJQB.js +26 -0
- package/dist/chunk-IRCTGJQB.js.map +1 -0
- package/dist/chunk-ISPICB4W.js +70 -0
- package/dist/chunk-ISPICB4W.js.map +1 -0
- package/dist/chunk-JDE5XYPT.js +30 -0
- package/dist/chunk-JDE5XYPT.js.map +1 -0
- package/dist/chunk-JSJEEKR3.js +59 -0
- package/dist/chunk-JSJEEKR3.js.map +1 -0
- package/dist/chunk-KR3BCFP7.js +85 -0
- package/dist/chunk-KR3BCFP7.js.map +1 -0
- package/dist/chunk-MLRSOTFI.js +81 -0
- package/dist/chunk-MLRSOTFI.js.map +1 -0
- package/dist/chunk-MQT5RAQV.js +202 -0
- package/dist/chunk-MQT5RAQV.js.map +1 -0
- package/dist/chunk-MUPW2TFJ.js +102 -0
- package/dist/chunk-MUPW2TFJ.js.map +1 -0
- package/dist/chunk-OUYT6SPU.js +76 -0
- package/dist/chunk-OUYT6SPU.js.map +1 -0
- package/dist/chunk-PM2VE7DJ.js +100 -0
- package/dist/chunk-PM2VE7DJ.js.map +1 -0
- package/dist/chunk-PPYR3O3L.js +44 -0
- package/dist/chunk-PPYR3O3L.js.map +1 -0
- package/dist/chunk-PUTC52SI.js +98 -0
- package/dist/chunk-PUTC52SI.js.map +1 -0
- package/dist/chunk-PWAKTEXF.js +18 -0
- package/dist/chunk-PWAKTEXF.js.map +1 -0
- package/dist/chunk-QTFWORA7.js +26 -0
- package/dist/chunk-QTFWORA7.js.map +1 -0
- package/dist/chunk-RUPL2H6V.js +49 -0
- package/dist/chunk-RUPL2H6V.js.map +1 -0
- package/dist/chunk-S7PVZAPL.js +28 -0
- package/dist/chunk-S7PVZAPL.js.map +1 -0
- package/dist/chunk-SQDV2AKJ.js +49 -0
- package/dist/chunk-SQDV2AKJ.js.map +1 -0
- package/dist/chunk-T2KXVH3O.js +297 -0
- package/dist/chunk-T2KXVH3O.js.map +1 -0
- package/dist/chunk-T4WCCHJQ.js +29 -0
- package/dist/chunk-T4WCCHJQ.js.map +1 -0
- package/dist/chunk-UC3ZIQPE.js +93 -0
- package/dist/chunk-UC3ZIQPE.js.map +1 -0
- package/dist/chunk-UE7JY7VR.js +28 -0
- package/dist/chunk-UE7JY7VR.js.map +1 -0
- package/dist/chunk-Y5WCT6XN.js +93 -0
- package/dist/chunk-Y5WCT6XN.js.map +1 -0
- package/dist/chunk-YDICVETX.js +151 -0
- package/dist/chunk-YDICVETX.js.map +1 -0
- package/dist/chunk-ZFSQVMLJ.js +126 -0
- package/dist/chunk-ZFSQVMLJ.js.map +1 -0
- package/dist/chunk-ZHATD73Y.js +46 -0
- package/dist/chunk-ZHATD73Y.js.map +1 -0
- package/dist/chunk-ZLAA4DGG.js +111 -0
- package/dist/chunk-ZLAA4DGG.js.map +1 -0
- package/dist/chunk-ZP252OJZ.js +56 -0
- package/dist/chunk-ZP252OJZ.js.map +1 -0
- package/dist/clipboard.cjs +125 -0
- package/dist/clipboard.cjs.map +1 -0
- package/dist/clipboard.d.cts +52 -0
- package/dist/clipboard.d.ts +52 -0
- package/dist/clipboard.js +23 -0
- package/dist/clipboard.js.map +1 -0
- package/dist/collapsible.cjs +94 -0
- package/dist/collapsible.cjs.map +1 -0
- package/dist/collapsible.d.cts +21 -0
- package/dist/collapsible.d.ts +21 -0
- package/dist/collapsible.js +9 -0
- package/dist/collapsible.js.map +1 -0
- package/dist/confirm-dialog.cjs +369 -0
- package/dist/confirm-dialog.cjs.map +1 -0
- package/dist/confirm-dialog.d.cts +16 -0
- package/dist/confirm-dialog.d.ts +16 -0
- package/dist/confirm-dialog.js +13 -0
- package/dist/confirm-dialog.js.map +1 -0
- package/dist/copy-button.cjs +102 -0
- package/dist/copy-button.cjs.map +1 -0
- package/dist/copy-button.d.cts +18 -0
- package/dist/copy-button.d.ts +18 -0
- package/dist/copy-button.js +10 -0
- package/dist/copy-button.js.map +1 -0
- package/dist/data-table.cjs +140 -0
- package/dist/data-table.cjs.map +1 -0
- package/dist/data-table.d.cts +81 -0
- package/dist/data-table.d.ts +81 -0
- package/dist/data-table.js +8 -0
- package/dist/data-table.js.map +1 -0
- package/dist/date-range-picker/dateRange.cjs +138 -0
- package/dist/date-range-picker/dateRange.cjs.map +1 -0
- package/dist/date-range-picker/dateRange.d.cts +57 -0
- package/dist/date-range-picker/dateRange.d.ts +57 -0
- package/dist/date-range-picker/dateRange.js +31 -0
- package/dist/date-range-picker/dateRange.js.map +1 -0
- package/dist/date-range-picker/index.cjs +375 -0
- package/dist/date-range-picker/index.cjs.map +1 -0
- package/dist/date-range-picker/index.d.cts +18 -0
- package/dist/date-range-picker/index.d.ts +18 -0
- package/dist/date-range-picker/index.js +10 -0
- package/dist/date-range-picker/index.js.map +1 -0
- package/dist/dialogLayer.cjs +121 -0
- package/dist/dialogLayer.cjs.map +1 -0
- package/dist/dialogLayer.d.cts +11 -0
- package/dist/dialogLayer.d.ts +11 -0
- package/dist/dialogLayer.js +8 -0
- package/dist/dialogLayer.js.map +1 -0
- package/dist/feature-access-gate.cjs +160 -0
- package/dist/feature-access-gate.cjs.map +1 -0
- package/dist/feature-access-gate.d.cts +14 -0
- package/dist/feature-access-gate.d.ts +14 -0
- package/dist/feature-access-gate.js +11 -0
- package/dist/feature-access-gate.js.map +1 -0
- package/dist/field.cjs +58 -0
- package/dist/field.cjs.map +1 -0
- package/dist/field.d.cts +23 -0
- package/dist/field.d.ts +23 -0
- package/dist/field.js +8 -0
- package/dist/field.js.map +1 -0
- package/dist/image-uploader.cjs +293 -0
- package/dist/image-uploader.cjs.map +1 -0
- package/dist/image-uploader.d.cts +34 -0
- package/dist/image-uploader.d.ts +34 -0
- package/dist/image-uploader.js +12 -0
- package/dist/image-uploader.js.map +1 -0
- package/dist/index.cjs +3008 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +41 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +232 -0
- package/dist/index.js.map +1 -0
- package/dist/input.cjs +58 -0
- package/dist/input.cjs.map +1 -0
- package/dist/input.d.cts +7 -0
- package/dist/input.d.ts +7 -0
- package/dist/input.js +10 -0
- package/dist/input.js.map +1 -0
- package/dist/label.cjs +67 -0
- package/dist/label.cjs.map +1 -0
- package/dist/label.d.cts +7 -0
- package/dist/label.d.ts +7 -0
- package/dist/label.js +9 -0
- package/dist/label.js.map +1 -0
- package/dist/modal.cjs +212 -0
- package/dist/modal.cjs.map +1 -0
- package/dist/modal.d.cts +39 -0
- package/dist/modal.d.ts +39 -0
- package/dist/modal.js +12 -0
- package/dist/modal.js.map +1 -0
- package/dist/page-header.cjs +78 -0
- package/dist/page-header.cjs.map +1 -0
- package/dist/page-header.d.cts +35 -0
- package/dist/page-header.d.ts +35 -0
- package/dist/page-header.js +8 -0
- package/dist/page-header.js.map +1 -0
- package/dist/pagination.cjs +237 -0
- package/dist/pagination.cjs.map +1 -0
- package/dist/pagination.d.cts +15 -0
- package/dist/pagination.d.ts +15 -0
- package/dist/pagination.js +11 -0
- package/dist/pagination.js.map +1 -0
- package/dist/panel-brand-mark.cjs +59 -0
- package/dist/panel-brand-mark.cjs.map +1 -0
- package/dist/panel-brand-mark.d.cts +13 -0
- package/dist/panel-brand-mark.d.ts +13 -0
- package/dist/panel-brand-mark.js +8 -0
- package/dist/panel-brand-mark.js.map +1 -0
- package/dist/panel-sign-in.cjs +256 -0
- package/dist/panel-sign-in.cjs.map +1 -0
- package/dist/panel-sign-in.d.cts +31 -0
- package/dist/panel-sign-in.d.ts +31 -0
- package/dist/panel-sign-in.js +14 -0
- package/dist/panel-sign-in.js.map +1 -0
- package/dist/segmented-control.cjs +122 -0
- package/dist/segmented-control.cjs.map +1 -0
- package/dist/segmented-control.d.cts +58 -0
- package/dist/segmented-control.d.ts +58 -0
- package/dist/segmented-control.js +9 -0
- package/dist/segmented-control.js.map +1 -0
- package/dist/select.cjs +648 -0
- package/dist/select.cjs.map +1 -0
- package/dist/select.d.cts +30 -0
- package/dist/select.d.ts +30 -0
- package/dist/select.js +12 -0
- package/dist/select.js.map +1 -0
- package/dist/separator.cjs +70 -0
- package/dist/separator.cjs.map +1 -0
- package/dist/separator.d.cts +13 -0
- package/dist/separator.d.ts +13 -0
- package/dist/separator.js +9 -0
- package/dist/separator.js.map +1 -0
- package/dist/side-drawer.cjs +321 -0
- package/dist/side-drawer.cjs.map +1 -0
- package/dist/side-drawer.d.cts +21 -0
- package/dist/side-drawer.d.ts +21 -0
- package/dist/side-drawer.js +13 -0
- package/dist/side-drawer.js.map +1 -0
- package/dist/spinner.cjs +57 -0
- package/dist/spinner.cjs.map +1 -0
- package/{src/spinner.tsx → dist/spinner.d.cts} +11 -22
- package/dist/spinner.d.ts +27 -0
- package/dist/spinner.js +8 -0
- package/dist/spinner.js.map +1 -0
- package/dist/spreadsheet-grid.cjs +238 -0
- package/dist/spreadsheet-grid.cjs.map +1 -0
- package/dist/spreadsheet-grid.d.cts +48 -0
- package/dist/spreadsheet-grid.d.ts +48 -0
- package/dist/spreadsheet-grid.js +8 -0
- package/dist/spreadsheet-grid.js.map +1 -0
- package/dist/state-panel.cjs +86 -0
- package/dist/state-panel.cjs.map +1 -0
- package/dist/state-panel.d.cts +23 -0
- package/dist/state-panel.d.ts +23 -0
- package/dist/state-panel.js +9 -0
- package/dist/state-panel.js.map +1 -0
- package/dist/switch.cjs +87 -0
- package/dist/switch.cjs.map +1 -0
- package/dist/switch.d.cts +13 -0
- package/dist/switch.d.ts +13 -0
- package/dist/switch.js +9 -0
- package/dist/switch.js.map +1 -0
- package/dist/table.cjs +139 -0
- package/dist/table.cjs.map +1 -0
- package/dist/table.d.cts +16 -0
- package/dist/table.d.ts +16 -0
- package/dist/table.js +22 -0
- package/dist/table.js.map +1 -0
- package/dist/tabs.cjs +168 -0
- package/dist/tabs.cjs.map +1 -0
- package/dist/tabs.d.cts +59 -0
- package/dist/tabs.d.ts +59 -0
- package/dist/tabs.js +11 -0
- package/dist/tabs.js.map +1 -0
- package/dist/textarea.cjs +66 -0
- package/dist/textarea.cjs.map +1 -0
- package/dist/textarea.d.cts +18 -0
- package/dist/textarea.d.ts +18 -0
- package/dist/textarea.js +10 -0
- package/dist/textarea.js.map +1 -0
- package/dist/utils.cjs +35 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +5 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +7 -0
- package/dist/utils.js.map +1 -0
- package/package.json +391 -14
- package/src/alert.tsx +0 -74
- package/src/autocomplete.tsx +0 -666
- package/src/badge.tsx +0 -60
- package/src/breadcrumb.tsx +0 -71
- package/src/button.tsx +0 -113
- package/src/card.tsx +0 -79
- package/src/clipboard.ts +0 -144
- package/src/collapsible.tsx +0 -63
- package/src/confirm-dialog.tsx +0 -84
- package/src/copy-button.tsx +0 -58
- package/src/data-table.tsx +0 -190
- package/src/date-range-picker/dateRange.ts +0 -142
- package/src/date-range-picker/index.tsx +0 -366
- package/src/dialogLayer.ts +0 -142
- package/src/feature-access-gate.tsx +0 -38
- package/src/field.tsx +0 -56
- package/src/image-uploader.tsx +0 -181
- package/src/index.ts +0 -37
- package/src/input.tsx +0 -16
- package/src/label.tsx +0 -23
- package/src/modal.tsx +0 -117
- package/src/page-header.tsx +0 -75
- package/src/pagination.tsx +0 -116
- package/src/panel-brand-mark.tsx +0 -32
- package/src/panel-sign-in.tsx +0 -134
- package/src/segmented-control.tsx +0 -155
- package/src/select.tsx +0 -105
- package/src/separator.tsx +0 -32
- package/src/side-drawer.tsx +0 -87
- package/src/spreadsheet-grid.tsx +0 -264
- package/src/state-panel.tsx +0 -46
- package/src/switch.tsx +0 -47
- package/src/table.tsx +0 -110
- package/src/tabs.tsx +0 -201
- package/src/textarea.tsx +0 -32
- package/src/utils.ts +0 -6
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Input
|
|
3
|
+
} from "./chunk-PWAKTEXF.js";
|
|
4
|
+
import {
|
|
5
|
+
Button
|
|
6
|
+
} from "./chunk-KR3BCFP7.js";
|
|
7
|
+
import {
|
|
8
|
+
cn
|
|
9
|
+
} from "./chunk-HCA5ODCD.js";
|
|
10
|
+
|
|
11
|
+
// src/image-uploader.tsx
|
|
12
|
+
import { ImagePlus, Loader2, X } from "lucide-react";
|
|
13
|
+
import * as React from "react";
|
|
14
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
+
function ImageUploader({
|
|
16
|
+
value = "",
|
|
17
|
+
onChange,
|
|
18
|
+
disabled,
|
|
19
|
+
label,
|
|
20
|
+
className,
|
|
21
|
+
allowUrlPaste = true,
|
|
22
|
+
accept = "image/*,.gif",
|
|
23
|
+
uploadFile,
|
|
24
|
+
uploadFromUrl
|
|
25
|
+
}) {
|
|
26
|
+
const inputRef = React.useRef(null);
|
|
27
|
+
const [uploading, setUploading] = React.useState(false);
|
|
28
|
+
const [error, setError] = React.useState(null);
|
|
29
|
+
const busy = Boolean(disabled || uploading);
|
|
30
|
+
const handleFile = async (file) => {
|
|
31
|
+
if (!uploadFile) {
|
|
32
|
+
setError("File upload is not configured for this panel.");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
setUploading(true);
|
|
36
|
+
setError(null);
|
|
37
|
+
try {
|
|
38
|
+
onChange(await uploadFile(file));
|
|
39
|
+
} catch (err) {
|
|
40
|
+
setError(err instanceof Error ? err.message : "Upload failed");
|
|
41
|
+
} finally {
|
|
42
|
+
setUploading(false);
|
|
43
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const handleUrl = async (imageUrl) => {
|
|
47
|
+
const trimmed = imageUrl.trim();
|
|
48
|
+
if (!trimmed) {
|
|
49
|
+
onChange("");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (trimmed === value) return;
|
|
53
|
+
if (!uploadFromUrl || /^https?:\/\//i.test(trimmed)) {
|
|
54
|
+
setError(null);
|
|
55
|
+
onChange(trimmed);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
setUploading(true);
|
|
59
|
+
setError(null);
|
|
60
|
+
try {
|
|
61
|
+
onChange(await uploadFromUrl(trimmed));
|
|
62
|
+
} catch (err) {
|
|
63
|
+
setError(err instanceof Error ? err.message : "Upload failed");
|
|
64
|
+
} finally {
|
|
65
|
+
setUploading(false);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2", className), children: [
|
|
69
|
+
label ? /* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-xs font-medium", children: label }) : null,
|
|
70
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
71
|
+
/* @__PURE__ */ jsx(
|
|
72
|
+
"input",
|
|
73
|
+
{
|
|
74
|
+
ref: inputRef,
|
|
75
|
+
type: "file",
|
|
76
|
+
accept,
|
|
77
|
+
className: "hidden",
|
|
78
|
+
disabled: busy,
|
|
79
|
+
onChange: (e) => {
|
|
80
|
+
const file = e.target.files?.[0];
|
|
81
|
+
if (file) void handleFile(file);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
/* @__PURE__ */ jsxs(
|
|
86
|
+
Button,
|
|
87
|
+
{
|
|
88
|
+
type: "button",
|
|
89
|
+
variant: "outline",
|
|
90
|
+
size: "sm",
|
|
91
|
+
disabled: busy,
|
|
92
|
+
onClick: () => inputRef.current?.click(),
|
|
93
|
+
children: [
|
|
94
|
+
uploading ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(ImagePlus, { className: "h-4 w-4" }),
|
|
95
|
+
uploading ? "Uploading\u2026" : "Upload"
|
|
96
|
+
]
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
value ? /* @__PURE__ */ jsxs(
|
|
100
|
+
Button,
|
|
101
|
+
{
|
|
102
|
+
type: "button",
|
|
103
|
+
variant: "ghost",
|
|
104
|
+
size: "sm",
|
|
105
|
+
disabled: busy,
|
|
106
|
+
onClick: () => onChange(""),
|
|
107
|
+
"aria-label": "Clear image",
|
|
108
|
+
children: [
|
|
109
|
+
/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
|
|
110
|
+
"Clear"
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
) : null
|
|
114
|
+
] }),
|
|
115
|
+
allowUrlPaste ? /* @__PURE__ */ jsx(
|
|
116
|
+
Input,
|
|
117
|
+
{
|
|
118
|
+
defaultValue: value,
|
|
119
|
+
disabled: busy,
|
|
120
|
+
placeholder: "Or paste image URL\u2026",
|
|
121
|
+
onBlur: (e) => {
|
|
122
|
+
void handleUrl(e.target.value);
|
|
123
|
+
},
|
|
124
|
+
onKeyDown: (e) => {
|
|
125
|
+
if (e.key === "Enter") {
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
void handleUrl(e.target.value);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
value || "empty"
|
|
132
|
+
) : null,
|
|
133
|
+
value ? (
|
|
134
|
+
// eslint-disable-next-line @next/next/no-img-element -- CDN URLs vary
|
|
135
|
+
/* @__PURE__ */ jsx(
|
|
136
|
+
"img",
|
|
137
|
+
{
|
|
138
|
+
src: value,
|
|
139
|
+
alt: "",
|
|
140
|
+
className: "border-border max-h-24 rounded border bg-white object-contain p-1"
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
) : null,
|
|
144
|
+
error ? /* @__PURE__ */ jsx("p", { className: "text-destructive text-xs", role: "alert", children: error }) : null
|
|
145
|
+
] });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export {
|
|
149
|
+
ImageUploader
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=chunk-YDICVETX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/image-uploader.tsx"],"sourcesContent":["\"use client\";\n\nimport { ImagePlus, Loader2, X } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { Button } from \"./button\";\nimport { Input } from \"./input\";\nimport { cn } from \"./utils\";\n\nexport type ImageUploaderProps = {\n value?: string;\n onChange: (url: string) => void;\n disabled?: boolean;\n label?: string;\n className?: string;\n /** Allow pasting a remote URL in addition to file upload. Default true. */\n allowUrlPaste?: boolean;\n accept?: string;\n /**\n * Upload a picked file and return its hosted URL.\n * Injected by the panel — Upload stays enabled either way; without a\n * handler, picking a file surfaces a configuration error.\n */\n uploadFile?: (file: File) => Promise<string>;\n /**\n * Optional transform for pasted values (e.g. host a relative path).\n * When omitted, any pasted non-empty string is applied as-is. Absolute\n * `http(s)` URLs never call this.\n */\n uploadFromUrl?: (imageUrl: string) => Promise<string>;\n};\n\n/**\n * Reusable image uploader.\n *\n * Upload and URL paste are both enabled by default. Hosting is injected:\n * each panel passes `uploadFile` / `uploadFromUrl` so the kit stays free of\n * panel-specific API routes.\n */\nexport function ImageUploader({\n value = \"\",\n onChange,\n disabled,\n label,\n className,\n allowUrlPaste = true,\n accept = \"image/*,.gif\",\n uploadFile,\n uploadFromUrl,\n}: ImageUploaderProps) {\n const inputRef = React.useRef<HTMLInputElement>(null);\n const [uploading, setUploading] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n const busy = Boolean(disabled || uploading);\n\n const handleFile = async (file: File) => {\n if (!uploadFile) {\n setError(\"File upload is not configured for this panel.\");\n return;\n }\n setUploading(true);\n setError(null);\n try {\n onChange(await uploadFile(file));\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Upload failed\");\n } finally {\n setUploading(false);\n if (inputRef.current) inputRef.current.value = \"\";\n }\n };\n\n const handleUrl = async (imageUrl: string) => {\n const trimmed = imageUrl.trim();\n if (!trimmed) {\n onChange(\"\");\n return;\n }\n if (trimmed === value) return;\n\n // Absolute URLs — and any paste when no transform is configured — apply\n // as-is. Paste is never gated behind a disabled input.\n if (!uploadFromUrl || /^https?:\\/\\//i.test(trimmed)) {\n setError(null);\n onChange(trimmed);\n return;\n }\n\n setUploading(true);\n setError(null);\n try {\n onChange(await uploadFromUrl(trimmed));\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Upload failed\");\n } finally {\n setUploading(false);\n }\n };\n\n return (\n <div className={cn(\"space-y-2\", className)}>\n {label ? (\n <div className=\"text-muted-foreground text-xs font-medium\">{label}</div>\n ) : null}\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n className=\"hidden\"\n disabled={busy}\n onChange={(e) => {\n const file = e.target.files?.[0];\n if (file) void handleFile(file);\n }}\n />\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={busy}\n onClick={() => inputRef.current?.click()}\n >\n {uploading ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <ImagePlus className=\"h-4 w-4\" />\n )}\n {uploading ? \"Uploading…\" : \"Upload\"}\n </Button>\n {value ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={busy}\n onClick={() => onChange(\"\")}\n aria-label=\"Clear image\"\n >\n <X className=\"h-4 w-4\" />\n Clear\n </Button>\n ) : null}\n </div>\n\n {allowUrlPaste ? (\n <Input\n key={value || \"empty\"}\n defaultValue={value}\n disabled={busy}\n placeholder=\"Or paste image URL…\"\n onBlur={(e) => {\n void handleUrl(e.target.value);\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n void handleUrl((e.target as HTMLInputElement).value);\n }\n }}\n />\n ) : null}\n\n {value ? (\n // eslint-disable-next-line @next/next/no-img-element -- CDN URLs vary\n <img\n src={value}\n alt=\"\"\n className=\"border-border max-h-24 rounded border bg-white object-contain p-1\"\n />\n ) : null}\n\n {error ? (\n <p className=\"text-destructive text-xs\" role=\"alert\">\n {error}\n </p>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-HCA5ODCD.js";
|
|
4
|
+
|
|
5
|
+
// src/tabs.tsx
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
function TabPanel({
|
|
9
|
+
id,
|
|
10
|
+
labelledBy,
|
|
11
|
+
active,
|
|
12
|
+
children,
|
|
13
|
+
className
|
|
14
|
+
}) {
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
"div",
|
|
17
|
+
{
|
|
18
|
+
role: "tabpanel",
|
|
19
|
+
id,
|
|
20
|
+
"aria-labelledby": labelledBy,
|
|
21
|
+
hidden: !active,
|
|
22
|
+
className,
|
|
23
|
+
children: active ? children : null
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
function Tabs({
|
|
28
|
+
tabs,
|
|
29
|
+
value,
|
|
30
|
+
onChange,
|
|
31
|
+
ariaLabel,
|
|
32
|
+
className
|
|
33
|
+
}) {
|
|
34
|
+
const refs = React.useRef(/* @__PURE__ */ new Map());
|
|
35
|
+
const reactId = React.useId();
|
|
36
|
+
const hasInlinePanels = tabs.some((tab) => tab.panel != null);
|
|
37
|
+
function tabDomId(key) {
|
|
38
|
+
return `${reactId}-tab-${key}`;
|
|
39
|
+
}
|
|
40
|
+
function panelDomId(tab) {
|
|
41
|
+
return tab.panelId ?? `${reactId}-panel-${tab.key}`;
|
|
42
|
+
}
|
|
43
|
+
function focusTab(key) {
|
|
44
|
+
onChange(key);
|
|
45
|
+
refs.current.get(key)?.focus();
|
|
46
|
+
}
|
|
47
|
+
function onKeyDown(event) {
|
|
48
|
+
const step = event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
|
|
49
|
+
if (step === 0) return;
|
|
50
|
+
const selectable = tabs.filter((tab) => !tab.disabled);
|
|
51
|
+
if (selectable.length < 2) return;
|
|
52
|
+
const current = selectable.findIndex((tab) => tab.key === value);
|
|
53
|
+
const next = selectable[(current + step + selectable.length) % selectable.length];
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
focusTab(next.key);
|
|
56
|
+
}
|
|
57
|
+
const tablist = /* @__PURE__ */ jsx(
|
|
58
|
+
"div",
|
|
59
|
+
{
|
|
60
|
+
role: "tablist",
|
|
61
|
+
"aria-label": ariaLabel,
|
|
62
|
+
onKeyDown,
|
|
63
|
+
className: cn(
|
|
64
|
+
"border-border flex flex-wrap gap-1 border-b",
|
|
65
|
+
!hasInlinePanels ? className : void 0
|
|
66
|
+
),
|
|
67
|
+
children: tabs.map((tab) => {
|
|
68
|
+
const active = tab.key === value;
|
|
69
|
+
const id = tabDomId(tab.key);
|
|
70
|
+
const controls = tab.panel != null || tab.panelId ? panelDomId(tab) : void 0;
|
|
71
|
+
return /* @__PURE__ */ jsxs(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
id,
|
|
75
|
+
ref: (node) => {
|
|
76
|
+
refs.current.set(tab.key, node);
|
|
77
|
+
},
|
|
78
|
+
type: "button",
|
|
79
|
+
role: "tab",
|
|
80
|
+
"aria-selected": active,
|
|
81
|
+
"aria-controls": controls,
|
|
82
|
+
disabled: tab.disabled,
|
|
83
|
+
tabIndex: active ? 0 : -1,
|
|
84
|
+
onClick: () => onChange(tab.key),
|
|
85
|
+
className: cn(
|
|
86
|
+
"cursor-pointer rounded-t-md px-3 py-1.5 text-sm font-medium transition",
|
|
87
|
+
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
|
|
88
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
89
|
+
// The filled-pill active state the five hand-rolled strips
|
|
90
|
+
// already used. Adopting it keeps this a deduplication rather
|
|
91
|
+
// than a restyle of every panel that has tabs.
|
|
92
|
+
active ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted"
|
|
93
|
+
),
|
|
94
|
+
children: [
|
|
95
|
+
tab.label,
|
|
96
|
+
tab.badge != null ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground ml-1.5 text-xs", children: tab.badge }) : null
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
tab.key
|
|
100
|
+
);
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
if (!hasInlinePanels) return tablist;
|
|
105
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
|
|
106
|
+
tablist,
|
|
107
|
+
tabs.map(
|
|
108
|
+
(tab) => tab.panel != null ? /* @__PURE__ */ jsx(
|
|
109
|
+
TabPanel,
|
|
110
|
+
{
|
|
111
|
+
id: panelDomId(tab),
|
|
112
|
+
labelledBy: tabDomId(tab.key),
|
|
113
|
+
active: tab.key === value,
|
|
114
|
+
children: tab.panel
|
|
115
|
+
},
|
|
116
|
+
tab.key
|
|
117
|
+
) : null
|
|
118
|
+
)
|
|
119
|
+
] });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
TabPanel,
|
|
124
|
+
Tabs
|
|
125
|
+
};
|
|
126
|
+
//# sourceMappingURL=chunk-ZFSQVMLJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tabs.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"./utils\";\n\n/**\n * The tab strip five Managers were each drawing by hand, with the same\n * `rounded-t-md px-3 py-1.5 text-sm font-medium` and their own idea of what an\n * active tab looks like.\n *\n * It is a controlled component with no state of its own: every caller already\n * held the active key, usually because a URL or a query depends on it.\n *\n * Roles are the reason to use this rather than styling buttons again. A hand\n * -rolled strip is a row of buttons to a screen reader, which says nothing\n * about how many tabs there are or which is selected; `tablist` / `tab` /\n * `aria-selected` do. Arrow-key navigation comes with that expectation, so it\n * is implemented here too.\n *\n * Optional `panel` on a tab (or a sibling `TabPanel`) wires APG `aria-controls`\n * / `role=\"tabpanel\"`. Callers that already render content from `value` can\n * keep doing so and pass `panelId` for association, or switch to `panel`.\n */\nexport interface TabItem<Key extends string = string> {\n key: Key;\n label: React.ReactNode;\n /** Rendered after the label — a count, usually. */\n badge?: React.ReactNode;\n disabled?: boolean;\n /**\n * Optional panel body. When any tab provides `panel`, panels render under\n * the strip with `role=\"tabpanel\"`.\n */\n panel?: React.ReactNode;\n /**\n * External panel element id for `aria-controls` when panels live outside\n * this component.\n */\n panelId?: string;\n}\n\nexport interface TabsProps<Key extends string = string> {\n tabs: ReadonlyArray<TabItem<Key>>;\n value: Key;\n onChange: (key: Key) => void;\n /** Names the strip for screen readers. */\n ariaLabel?: string;\n className?: string;\n}\n\nexport interface TabPanelProps {\n /** Must match the controlling tab's generated or explicit id. */\n id: string;\n /** Id of the tab that labels this panel. */\n labelledBy: string;\n active: boolean;\n children: React.ReactNode;\n className?: string;\n}\n\n/** Companion panel for callers that keep content outside `Tabs`. */\nexport function TabPanel({\n id,\n labelledBy,\n active,\n children,\n className,\n}: TabPanelProps) {\n return (\n <div\n role=\"tabpanel\"\n id={id}\n aria-labelledby={labelledBy}\n hidden={!active}\n className={className}\n >\n {active ? children : null}\n </div>\n );\n}\n\nexport function Tabs<Key extends string = string>({\n tabs,\n value,\n onChange,\n ariaLabel,\n className,\n}: TabsProps<Key>) {\n const refs = React.useRef(new Map<Key, HTMLButtonElement | null>());\n const reactId = React.useId();\n const hasInlinePanels = tabs.some((tab) => tab.panel != null);\n\n function tabDomId(key: Key) {\n return `${reactId}-tab-${key}`;\n }\n\n function panelDomId(tab: TabItem<Key>) {\n return tab.panelId ?? `${reactId}-panel-${tab.key}`;\n }\n\n function focusTab(key: Key) {\n onChange(key);\n // The newly selected tab has to take focus, or the next arrow press is\n // still measured from the old one.\n refs.current.get(key)?.focus();\n }\n\n function onKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {\n const step =\n event.key === \"ArrowRight\" ? 1 : event.key === \"ArrowLeft\" ? -1 : 0;\n if (step === 0) return;\n\n const selectable = tabs.filter((tab) => !tab.disabled);\n if (selectable.length < 2) return;\n\n const current = selectable.findIndex((tab) => tab.key === value);\n // Wraps, which is what a tablist is expected to do.\n const next =\n selectable[(current + step + selectable.length) % selectable.length];\n\n event.preventDefault();\n focusTab(next.key);\n }\n\n const tablist = (\n <div\n role=\"tablist\"\n aria-label={ariaLabel}\n onKeyDown={onKeyDown}\n className={cn(\n \"border-border flex flex-wrap gap-1 border-b\",\n !hasInlinePanels ? className : undefined,\n )}\n >\n {tabs.map((tab) => {\n const active = tab.key === value;\n const id = tabDomId(tab.key);\n const controls =\n tab.panel != null || tab.panelId ? panelDomId(tab) : undefined;\n return (\n <button\n key={tab.key}\n id={id}\n ref={(node) => {\n refs.current.set(tab.key, node);\n }}\n type=\"button\"\n role=\"tab\"\n aria-selected={active}\n aria-controls={controls}\n disabled={tab.disabled}\n // Only the active tab is reachable by Tab; arrows move within the\n // strip. Without this a keyboard user pages through every tab to\n // get past the strip.\n tabIndex={active ? 0 : -1}\n onClick={() => onChange(tab.key)}\n className={cn(\n \"cursor-pointer rounded-t-md px-3 py-1.5 text-sm font-medium transition\",\n \"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n // The filled-pill active state the five hand-rolled strips\n // already used. Adopting it keeps this a deduplication rather\n // than a restyle of every panel that has tabs.\n active\n ? \"bg-primary text-primary-foreground\"\n : \"text-muted-foreground hover:bg-muted\",\n )}\n >\n {tab.label}\n {tab.badge != null ? (\n <span className=\"text-muted-foreground ml-1.5 text-xs\">\n {tab.badge}\n </span>\n ) : null}\n </button>\n );\n })}\n </div>\n );\n\n if (!hasInlinePanels) return tablist;\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n {tablist}\n {tabs.map((tab) =>\n tab.panel != null ? (\n <TabPanel\n key={tab.key}\n id={panelDomId(tab)}\n labelledBy={tabDomId(tab.key)}\n active={tab.key === value}\n >\n {tab.panel}\n </TabPanel>\n ) : null,\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-HCA5ODCD.js";
|
|
4
|
+
|
|
5
|
+
// src/switch.tsx
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
var Switch = React.forwardRef(
|
|
9
|
+
({ checked, onCheckedChange, className, disabled, id, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
10
|
+
"button",
|
|
11
|
+
{
|
|
12
|
+
ref,
|
|
13
|
+
id,
|
|
14
|
+
type: "button",
|
|
15
|
+
role: "switch",
|
|
16
|
+
"aria-checked": checked,
|
|
17
|
+
disabled,
|
|
18
|
+
onClick: () => onCheckedChange(!checked),
|
|
19
|
+
className: cn(
|
|
20
|
+
// 24×44px track meets DESIGN.md 24px minimum target size.
|
|
21
|
+
"relative inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border transition-colors",
|
|
22
|
+
"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
|
|
23
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
24
|
+
checked ? "border-primary bg-primary" : "border-border bg-muted",
|
|
25
|
+
className
|
|
26
|
+
),
|
|
27
|
+
...props,
|
|
28
|
+
children: /* @__PURE__ */ jsx(
|
|
29
|
+
"span",
|
|
30
|
+
{
|
|
31
|
+
"aria-hidden": true,
|
|
32
|
+
className: cn(
|
|
33
|
+
"bg-background pointer-events-none block size-5 rounded-full shadow-sm transition-transform",
|
|
34
|
+
checked ? "translate-x-5" : "translate-x-0.5"
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
Switch.displayName = "Switch";
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
Switch
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=chunk-ZHATD73Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/switch.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"./utils\";\n\nexport type SwitchProps = Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n \"onChange\"\n> & {\n checked: boolean;\n onCheckedChange: (checked: boolean) => void;\n};\n\n/** Compact accessible toggle — use instead of full-width checkbox inputs. */\nexport const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(\n ({ checked, onCheckedChange, className, disabled, id, ...props }, ref) => (\n <button\n ref={ref}\n id={id}\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n disabled={disabled}\n onClick={() => onCheckedChange(!checked)}\n className={cn(\n // 24×44px track meets DESIGN.md 24px minimum target size.\n \"relative inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border transition-colors\",\n \"focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none\",\n \"disabled:cursor-not-allowed disabled:opacity-50\",\n checked ? \"border-primary bg-primary\" : \"border-border bg-muted\",\n className,\n )}\n {...props}\n >\n <span\n aria-hidden\n className={cn(\n \"bg-background pointer-events-none block size-5 rounded-full shadow-sm transition-transform\",\n checked ? \"translate-x-5\" : \"translate-x-0.5\",\n )}\n />\n </button>\n ),\n);\n\nSwitch.displayName = \"Switch\";\n"],"mappings":";;;;;AAEA,YAAY,WAAW;AAiCjB;AApBC,IAAM,SAAe;AAAA,EAC1B,CAAC,EAAE,SAAS,iBAAiB,WAAW,UAAU,IAAI,GAAG,MAAM,GAAG,QAChE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,MAAK;AAAA,MACL,MAAK;AAAA,MACL,gBAAc;AAAA,MACd;AAAA,MACA,SAAS,MAAM,gBAAgB,CAAC,OAAO;AAAA,MACvC,WAAW;AAAA;AAAA,QAET;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,8BAA8B;AAAA,QACxC;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA,QAAC;AAAA;AAAA,UACC,eAAW;AAAA,UACX,WAAW;AAAA,YACT;AAAA,YACA,UAAU,kBAAkB;AAAA,UAC9B;AAAA;AAAA,MACF;AAAA;AAAA,EACF;AAEJ;AAEA,OAAO,cAAc;","names":[]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-HCA5ODCD.js";
|
|
4
|
+
|
|
5
|
+
// src/data-table.tsx
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
var HEADER_VARIANTS = {
|
|
8
|
+
uppercase: "border-border bg-muted/40 text-muted-foreground border-b text-xs tracking-wide uppercase",
|
|
9
|
+
plain: "text-muted-foreground border-border border-b text-left"
|
|
10
|
+
};
|
|
11
|
+
var DENSITY = {
|
|
12
|
+
comfortable: "px-4 py-3",
|
|
13
|
+
compact: "px-3 py-2"
|
|
14
|
+
};
|
|
15
|
+
var ALIGN = {
|
|
16
|
+
left: "text-left",
|
|
17
|
+
right: "text-right",
|
|
18
|
+
center: "text-center"
|
|
19
|
+
};
|
|
20
|
+
function DataTable({
|
|
21
|
+
columns,
|
|
22
|
+
rows,
|
|
23
|
+
rowKey,
|
|
24
|
+
variant = "uppercase",
|
|
25
|
+
density = "comfortable",
|
|
26
|
+
stickyHeader = false,
|
|
27
|
+
rowClassName,
|
|
28
|
+
emptyMessage,
|
|
29
|
+
footer,
|
|
30
|
+
ariaLabel,
|
|
31
|
+
className,
|
|
32
|
+
tableClassName
|
|
33
|
+
}) {
|
|
34
|
+
return /* @__PURE__ */ jsx(
|
|
35
|
+
"div",
|
|
36
|
+
{
|
|
37
|
+
className: cn(
|
|
38
|
+
"border-border bg-background/80 overflow-x-auto rounded-lg border",
|
|
39
|
+
className
|
|
40
|
+
),
|
|
41
|
+
children: /* @__PURE__ */ jsxs(
|
|
42
|
+
"table",
|
|
43
|
+
{
|
|
44
|
+
"aria-label": ariaLabel,
|
|
45
|
+
className: cn("min-w-full text-left text-sm", tableClassName),
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ jsx(
|
|
48
|
+
"thead",
|
|
49
|
+
{
|
|
50
|
+
className: cn(
|
|
51
|
+
HEADER_VARIANTS[variant],
|
|
52
|
+
stickyHeader && "bg-muted sticky top-0 z-10"
|
|
53
|
+
),
|
|
54
|
+
children: /* @__PURE__ */ jsx("tr", { children: columns.map((column) => /* @__PURE__ */ jsx(
|
|
55
|
+
"th",
|
|
56
|
+
{
|
|
57
|
+
scope: "col",
|
|
58
|
+
"aria-sort": column.ariaSort,
|
|
59
|
+
className: cn(
|
|
60
|
+
DENSITY[density],
|
|
61
|
+
"font-medium",
|
|
62
|
+
ALIGN[column.align ?? "left"],
|
|
63
|
+
column.headerClassName
|
|
64
|
+
),
|
|
65
|
+
children: column.title
|
|
66
|
+
},
|
|
67
|
+
column.key
|
|
68
|
+
)) })
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.length === 0 && emptyMessage != null ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
|
|
72
|
+
"td",
|
|
73
|
+
{
|
|
74
|
+
colSpan: columns.length,
|
|
75
|
+
className: "text-muted-foreground px-4 py-6 text-center text-sm",
|
|
76
|
+
children: emptyMessage
|
|
77
|
+
}
|
|
78
|
+
) }) : rows.map((row, index) => /* @__PURE__ */ jsx(
|
|
79
|
+
"tr",
|
|
80
|
+
{
|
|
81
|
+
className: cn(
|
|
82
|
+
"border-border/70 border-b last:border-0",
|
|
83
|
+
rowClassName?.(row, index)
|
|
84
|
+
),
|
|
85
|
+
children: columns.map((column) => /* @__PURE__ */ jsx(
|
|
86
|
+
"td",
|
|
87
|
+
{
|
|
88
|
+
className: cn(
|
|
89
|
+
DENSITY[density],
|
|
90
|
+
ALIGN[column.align ?? "left"],
|
|
91
|
+
column.className
|
|
92
|
+
),
|
|
93
|
+
children: column.cell(row, index)
|
|
94
|
+
},
|
|
95
|
+
column.key
|
|
96
|
+
))
|
|
97
|
+
},
|
|
98
|
+
rowKey(row, index)
|
|
99
|
+
)) }),
|
|
100
|
+
footer != null ? /* @__PURE__ */ jsx("tfoot", { children: footer }) : null
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
DataTable
|
|
110
|
+
};
|
|
111
|
+
//# sourceMappingURL=chunk-ZLAA4DGG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/data-table.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { cn } from \"./utils\";\n\n/**\n * The list table every feature panel was hand-rolling.\n *\n * Seventeen files repeated the same wrapper string\n * (`border-border bg-background/80 overflow-x-auto rounded-lg border`) and the\n * header had already drifted into two incompatible looks, so Campaigns and\n * Targets did not resemble each other. Both are kept as `variant` rather than\n * unified here: collapsing them is a visual change to ten panels and belongs in\n * its own reviewable diff, not smuggled in with a refactor.\n *\n * The column contract deliberately echoes `GridColumn` in\n * `spreadsheet-grid.tsx` — `key`, `title`, a renderer, an optional class — so\n * the repo has one shape to learn. It drops that type's `value: (row) => string`\n * because there is no clipboard or CSV here; nothing would keep it honest, and\n * a required field nobody reads gets filled in with lies.\n *\n * **Not for every table.** `GroupTable` renders parent and child rows with\n * different column counts, and `BusinessTable`'s rows are dnd-kit sortables that\n * own their own refs and transforms. `PublishModal`, `BulkCreateModal` and the\n * clone queue in `MetaAdSetsPanel` are grids of inputs rather than lists of\n * values — every cell is an editable control, and they size their header and\n * body rows differently on purpose. All five are legitimately tables of a\n * different kind; bending this component around them would cost more than it\n * saves.\n */\nexport type TableColumn<Row> = {\n key: string;\n /** Node, not string: a header can carry a select-all box or a sort control. */\n title: React.ReactNode;\n cell: (row: Row, index: number) => React.ReactNode;\n align?: \"left\" | \"right\" | \"center\";\n /**\n * Set on a sortable column so a screen reader announces the current sort.\n * Only the sorted column says `ascending`/`descending`; the rest say `none`,\n * which is what marks them as sortable at all.\n */\n ariaSort?: \"ascending\" | \"descending\" | \"none\";\n /** Extra classes for this column's `<td>`. */\n className?: string;\n /** Extra classes for this column's `<th>`. */\n headerClassName?: string;\n};\n\nexport interface DataTableProps<Row> {\n columns: TableColumn<Row>[];\n rows: Row[];\n /** Stable React key per row. */\n rowKey: (row: Row, index: number) => string;\n /**\n * `uppercase` — the tinted small-caps header (campaigns, showcase, filters).\n * `plain` — the quieter left-aligned header (ads, targets, creatives).\n */\n variant?: \"uppercase\" | \"plain\";\n /**\n * `comfortable` — the roomy default, for tables of a handful of columns.\n * `compact` — what every table of eight columns or more had already chosen by\n * hand, because the roomy padding pushes them into a horizontal scroll.\n */\n density?: \"comfortable\" | \"compact\";\n /** Pins the header for a table inside its own scroll container. */\n stickyHeader?: boolean;\n rowClassName?: (row: Row, index: number) => string | undefined;\n /** Rendered in place of `<tbody>` rows when there are none. */\n emptyMessage?: React.ReactNode;\n /**\n * Rows for a `<tfoot>` — a totals line, typically. The caller supplies the\n * `<tr>` itself because a summary row rarely lines up with the columns above\n * it; designer-counts spans its first two.\n */\n footer?: React.ReactNode;\n /** Names the table for screen readers when no visible caption fits. */\n ariaLabel?: string;\n /** Classes for the scroll wrapper. */\n className?: string;\n /** Classes for the `<table>`. */\n tableClassName?: string;\n}\n\nconst HEADER_VARIANTS = {\n uppercase:\n \"border-border bg-muted/40 text-muted-foreground border-b text-xs tracking-wide uppercase\",\n plain: \"text-muted-foreground border-border border-b text-left\",\n} as const;\n\nconst DENSITY = {\n comfortable: \"px-4 py-3\",\n compact: \"px-3 py-2\",\n} as const;\n\nconst ALIGN = {\n left: \"text-left\",\n right: \"text-right\",\n center: \"text-center\",\n} as const;\n\nexport function DataTable<Row>({\n columns,\n rows,\n rowKey,\n variant = \"uppercase\",\n density = \"comfortable\",\n stickyHeader = false,\n rowClassName,\n emptyMessage,\n footer,\n ariaLabel,\n className,\n tableClassName,\n}: DataTableProps<Row>) {\n return (\n <div\n className={cn(\n \"border-border bg-background/80 overflow-x-auto rounded-lg border\",\n className,\n )}\n >\n <table\n aria-label={ariaLabel}\n className={cn(\"min-w-full text-left text-sm\", tableClassName)}\n >\n <thead\n className={cn(\n HEADER_VARIANTS[variant],\n stickyHeader && \"bg-muted sticky top-0 z-10\",\n )}\n >\n <tr>\n {columns.map((column) => (\n <th\n key={column.key}\n scope=\"col\"\n aria-sort={column.ariaSort}\n className={cn(\n DENSITY[density],\n \"font-medium\",\n ALIGN[column.align ?? \"left\"],\n column.headerClassName,\n )}\n >\n {column.title}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {rows.length === 0 && emptyMessage != null ? (\n <tr>\n {/* One cell across the whole width — a short row would otherwise\n leave the message sitting under the first column. */}\n <td\n colSpan={columns.length}\n className=\"text-muted-foreground px-4 py-6 text-center text-sm\"\n >\n {emptyMessage}\n </td>\n </tr>\n ) : (\n rows.map((row, index) => (\n <tr\n key={rowKey(row, index)}\n className={cn(\n \"border-border/70 border-b last:border-0\",\n rowClassName?.(row, index),\n )}\n >\n {columns.map((column) => (\n <td\n key={column.key}\n className={cn(\n DENSITY[density],\n ALIGN[column.align ?? \"left\"],\n column.className,\n )}\n >\n {column.cell(row, index)}\n </td>\n ))}\n </tr>\n ))\n )}\n </tbody>\n {footer != null ? <tfoot>{footer}</tfoot> : null}\n </table>\n </div>\n );\n}\n"],"mappings":";;;;;AAwHM,SAYQ,KAZR;AAtCN,IAAM,kBAAkB;AAAA,EACtB,WACE;AAAA,EACF,OAAO;AACT;AAEA,IAAM,UAAU;AAAA,EACd,aAAa;AAAA,EACb,SAAS;AACX;AAEA,IAAM,QAAQ;AAAA,EACZ,MAAM;AAAA,EACN,OAAO;AAAA,EACP,QAAQ;AACV;AAEO,SAAS,UAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV,UAAU;AAAA,EACV,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,cAAY;AAAA,UACZ,WAAW,GAAG,gCAAgC,cAAc;AAAA,UAE5D;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAW;AAAA,kBACT,gBAAgB,OAAO;AAAA,kBACvB,gBAAgB;AAAA,gBAClB;AAAA,gBAEA,8BAAC,QACE,kBAAQ,IAAI,CAAC,WACZ;AAAA,kBAAC;AAAA;AAAA,oBAEC,OAAM;AAAA,oBACN,aAAW,OAAO;AAAA,oBAClB,WAAW;AAAA,sBACT,QAAQ,OAAO;AAAA,sBACf;AAAA,sBACA,MAAM,OAAO,SAAS,MAAM;AAAA,sBAC5B,OAAO;AAAA,oBACT;AAAA,oBAEC,iBAAO;AAAA;AAAA,kBAVH,OAAO;AAAA,gBAWd,CACD,GACH;AAAA;AAAA,YACF;AAAA,YACA,oBAAC,WACE,eAAK,WAAW,KAAK,gBAAgB,OACpC,oBAAC,QAGC;AAAA,cAAC;AAAA;AAAA,gBACC,SAAS,QAAQ;AAAA,gBACjB,WAAU;AAAA,gBAET;AAAA;AAAA,YACH,GACF,IAEA,KAAK,IAAI,CAAC,KAAK,UACb;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAW;AAAA,kBACT;AAAA,kBACA,eAAe,KAAK,KAAK;AAAA,gBAC3B;AAAA,gBAEC,kBAAQ,IAAI,CAAC,WACZ;AAAA,kBAAC;AAAA;AAAA,oBAEC,WAAW;AAAA,sBACT,QAAQ,OAAO;AAAA,sBACf,MAAM,OAAO,SAAS,MAAM;AAAA,sBAC5B,OAAO;AAAA,oBACT;AAAA,oBAEC,iBAAO,KAAK,KAAK,KAAK;AAAA;AAAA,kBAPlB,OAAO;AAAA,gBAQd,CACD;AAAA;AAAA,cAjBI,OAAO,KAAK,KAAK;AAAA,YAkBxB,CACD,GAEL;AAAA,YACC,UAAU,OAAO,oBAAC,WAAO,kBAAO,IAAW;AAAA;AAAA;AAAA,MAC9C;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-HCA5ODCD.js";
|
|
4
|
+
|
|
5
|
+
// src/alert.tsx
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { cva } from "class-variance-authority";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var alertVariants = cva("rounded-lg border px-3 py-2 text-sm", {
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
danger: "border-destructive/30 bg-destructive/5 text-destructive",
|
|
13
|
+
warning: "border-warning/30 bg-warning/5 text-warning",
|
|
14
|
+
success: "border-success/30 bg-success/5 text-success",
|
|
15
|
+
info: "border-info/30 bg-info/5 text-info",
|
|
16
|
+
/**
|
|
17
|
+
* The quiet state of a banner that also has loud ones — "no default
|
|
18
|
+
* campaign set" next to "this is the default". Without it those callers
|
|
19
|
+
* keep a ternary of raw class strings and the shared component only
|
|
20
|
+
* covers half of what they draw.
|
|
21
|
+
*/
|
|
22
|
+
neutral: "border-border bg-muted/40 text-muted-foreground"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
// Failure is what a banner almost always reports here, and defaulting to
|
|
27
|
+
// it means the noisy case cannot be forgotten.
|
|
28
|
+
variant: "danger"
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
var Alert = React.forwardRef(
|
|
32
|
+
({ className, variant, action, children, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
33
|
+
"div",
|
|
34
|
+
{
|
|
35
|
+
ref,
|
|
36
|
+
role: variant === "danger" || variant == null ? "alert" : "status",
|
|
37
|
+
className: cn(
|
|
38
|
+
alertVariants({ variant }),
|
|
39
|
+
action && "flex flex-wrap items-center justify-between gap-3",
|
|
40
|
+
className
|
|
41
|
+
),
|
|
42
|
+
...props,
|
|
43
|
+
children: [
|
|
44
|
+
action ? /* @__PURE__ */ jsx("span", { children }) : children,
|
|
45
|
+
action
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
Alert.displayName = "Alert";
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
alertVariants,
|
|
54
|
+
Alert
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=chunk-ZP252OJZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/alert.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"./utils\";\n\n/**\n * The inline message banner — a failed load, a warning above a form, a note.\n *\n * This was the most duplicated markup in the panel: thirty blocks across\n * twenty-eight files, spelled seven different ways. `px-3 py-2` in most places\n * and `px-4 py-3` in others, `text-rose-700` here and `text-destructive`\n * there — all meaning \"something went wrong\".\n *\n * `role` is derived from the variant rather than left to callers. An error a\n * user needs to act on should interrupt a screen reader; a note should not.\n * Almost none of the hand-rolled copies set either.\n */\nconst alertVariants = cva(\"rounded-lg border px-3 py-2 text-sm\", {\n variants: {\n variant: {\n danger: \"border-destructive/30 bg-destructive/5 text-destructive\",\n warning: \"border-warning/30 bg-warning/5 text-warning\",\n success: \"border-success/30 bg-success/5 text-success\",\n info: \"border-info/30 bg-info/5 text-info\",\n /**\n * The quiet state of a banner that also has loud ones — \"no default\n * campaign set\" next to \"this is the default\". Without it those callers\n * keep a ternary of raw class strings and the shared component only\n * covers half of what they draw.\n */\n neutral: \"border-border bg-muted/40 text-muted-foreground\",\n },\n },\n defaultVariants: {\n // Failure is what a banner almost always reports here, and defaulting to\n // it means the noisy case cannot be forgotten.\n variant: \"danger\",\n },\n});\n\nexport interface AlertProps\n extends\n React.HTMLAttributes<HTMLDivElement>,\n VariantProps<typeof alertVariants> {\n /**\n * A control belonging to the message, such as Retry. Given one, the banner\n * lays out as a row and keeps the action at its end.\n */\n action?: React.ReactNode;\n}\n\nexport const Alert = React.forwardRef<HTMLDivElement, AlertProps>(\n ({ className, variant, action, children, ...props }, ref) => (\n <div\n ref={ref}\n role={variant === \"danger\" || variant == null ? \"alert\" : \"status\"}\n className={cn(\n alertVariants({ variant }),\n action && \"flex flex-wrap items-center justify-between gap-3\",\n className,\n )}\n {...props}\n >\n {/* Wrapped so the action sits opposite the text rather than being\n pushed around by whatever the caller passed as children. */}\n {action ? <span>{children}</span> : children}\n {action}\n </div>\n ),\n);\n\nAlert.displayName = \"Alert\";\n\nexport { alertVariants };\n"],"mappings":";;;;;AAAA,YAAY,WAAW;AACvB,SAAS,WAA8B;AAoDnC,SAYY,KAZZ;AApCJ,IAAM,gBAAgB,IAAI,uCAAuC;AAAA,EAC/D,UAAU;AAAA,IACR,SAAS;AAAA,MACP,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,SAAS;AAAA,MACT,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAON,SAAS;AAAA,IACX;AAAA,EACF;AAAA,EACA,iBAAiB;AAAA;AAAA;AAAA,IAGf,SAAS;AAAA,EACX;AACF,CAAC;AAaM,IAAM,QAAc;AAAA,EACzB,CAAC,EAAE,WAAW,SAAS,QAAQ,UAAU,GAAG,MAAM,GAAG,QACnD;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAM,YAAY,YAAY,WAAW,OAAO,UAAU;AAAA,MAC1D,WAAW;AAAA,QACT,cAAc,EAAE,QAAQ,CAAC;AAAA,QACzB,UAAU;AAAA,QACV;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAIH;AAAA,iBAAS,oBAAC,UAAM,UAAS,IAAU;AAAA,QACnC;AAAA;AAAA;AAAA,EACH;AAEJ;AAEA,MAAM,cAAc;","names":[]}
|