@cfast/ui 0.0.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/LICENSE +21 -0
- package/README.md +727 -0
- package/dist/chunk-755IRYDN.js +941 -0
- package/dist/chunk-7SNK37GF.js +418 -0
- package/dist/chunk-ASMYTWTR.js +356 -0
- package/dist/chunk-B2XXH5V4.js +66 -0
- package/dist/chunk-BQMXYYEV.js +348 -0
- package/dist/chunk-DTKBXCTU.js +211 -0
- package/dist/chunk-EYIBATYR.js +33 -0
- package/dist/chunk-FPZAQ2YQ.js +474 -0
- package/dist/chunk-G2OU4BYC.js +205 -0
- package/dist/chunk-JEGEIQ3R.js +925 -0
- package/dist/chunk-JUNLQJ6H.js +1013 -0
- package/dist/chunk-NRGMW3JA.js +906 -0
- package/dist/chunk-Q6FPL2OJ.js +1086 -0
- package/dist/chunk-QHWAGKNW.js +456 -0
- package/dist/chunk-QZT62CGJ.js +924 -0
- package/dist/chunk-RDTUEOLK.js +486 -0
- package/dist/chunk-RESL4IJJ.js +112 -0
- package/dist/chunk-UDCWQUTR.js +221 -0
- package/dist/chunk-UE7PZOIJ.js +11 -0
- package/dist/chunk-UTZTHGNE.js +84 -0
- package/dist/chunk-UVRXMOX5.js +439 -0
- package/dist/chunk-XFD3N2D4.js +161 -0
- package/dist/client-CXIHCQtA.d.ts +274 -0
- package/dist/client.d.ts +617 -0
- package/dist/client.js +54 -0
- package/dist/index.d.ts +415 -0
- package/dist/index.js +296 -0
- package/dist/joy.d.ts +199 -0
- package/dist/joy.js +1150 -0
- package/dist/permission-gate-DVmY42oz.d.ts +1269 -0
- package/dist/permission-gate-apt9T9Mu.d.ts +1256 -0
- package/dist/types-1bAiH2uK.d.ts +392 -0
- package/dist/types-BX6u5sAd.d.ts +403 -0
- package/dist/types-BpdY7w5l.d.ts +403 -0
- package/dist/types-BrepeVp8.d.ts +403 -0
- package/dist/types-BvAqMZhn.d.ts +403 -0
- package/dist/types-C74nSscq.d.ts +403 -0
- package/dist/types-DD1Cpx8F.d.ts +403 -0
- package/dist/types-DHUhQwJn.d.ts +403 -0
- package/dist/types-DZSJNt_M.d.ts +392 -0
- package/dist/types-DaaJiIjW.d.ts +391 -0
- package/dist/types-LUpWJwps.d.ts +403 -0
- package/dist/types-a7zVU6WE.d.ts +394 -0
- package/dist/types-biJTHMcH.d.ts +403 -0
- package/dist/types-ow_qSEYJ.d.ts +392 -0
- package/dist/types-wnLasZaB.d.ts +1234 -0
- package/package.json +88 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
// src/plugin.tsx
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
|
|
4
|
+
// src/headless-defaults.tsx
|
|
5
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
var headlessDefaults = {
|
|
7
|
+
// Actions
|
|
8
|
+
button: ({ children, onClick, disabled, loading, type }) => /* @__PURE__ */ jsx("button", { onClick, disabled: disabled || loading, type: type ?? "button", children: loading ? "Loading..." : children }),
|
|
9
|
+
tooltip: ({ children, title }) => /* @__PURE__ */ jsx("span", { title, children }),
|
|
10
|
+
confirmDialog: ({ open, onClose, onConfirm, title, description, confirmLabel, cancelLabel }) => open ? /* @__PURE__ */ jsxs("dialog", { open: true, children: [
|
|
11
|
+
/* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("strong", { children: title }) }),
|
|
12
|
+
description ? /* @__PURE__ */ jsx("p", { children: description }) : null,
|
|
13
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
14
|
+
/* @__PURE__ */ jsx("button", { onClick: onClose, children: cancelLabel ?? "Cancel" }),
|
|
15
|
+
/* @__PURE__ */ jsx("button", { onClick: onConfirm, children: confirmLabel ?? "Confirm" })
|
|
16
|
+
] })
|
|
17
|
+
] }) : null,
|
|
18
|
+
// Data display
|
|
19
|
+
table: ({ children }) => /* @__PURE__ */ jsx("table", { children }),
|
|
20
|
+
tableHead: ({ children }) => /* @__PURE__ */ jsx("thead", { children }),
|
|
21
|
+
tableBody: ({ children }) => /* @__PURE__ */ jsx("tbody", { children }),
|
|
22
|
+
tableRow: ({ children, onClick }) => /* @__PURE__ */ jsx("tr", { onClick, children }),
|
|
23
|
+
tableCell: ({ children, header, sortable, sortDirection, onSort }) => {
|
|
24
|
+
const Tag = header ? "th" : "td";
|
|
25
|
+
return /* @__PURE__ */ jsxs(
|
|
26
|
+
Tag,
|
|
27
|
+
{
|
|
28
|
+
onClick: sortable ? onSort : void 0,
|
|
29
|
+
style: sortable ? { cursor: "pointer" } : void 0,
|
|
30
|
+
children: [
|
|
31
|
+
children,
|
|
32
|
+
sortable && sortDirection ? sortDirection === "asc" ? " \u2191" : " \u2193" : null
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
},
|
|
37
|
+
chip: ({ children, size }) => /* @__PURE__ */ jsx(
|
|
38
|
+
"span",
|
|
39
|
+
{
|
|
40
|
+
style: {
|
|
41
|
+
display: "inline-block",
|
|
42
|
+
padding: size === "sm" ? "1px 6px" : "2px 8px",
|
|
43
|
+
borderRadius: "12px",
|
|
44
|
+
fontSize: size === "sm" ? "12px" : "14px",
|
|
45
|
+
backgroundColor: "#eee"
|
|
46
|
+
},
|
|
47
|
+
children
|
|
48
|
+
}
|
|
49
|
+
),
|
|
50
|
+
// Layout
|
|
51
|
+
appShell: ({ children, sidebar, header }) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", minHeight: "100vh" }, children: [
|
|
52
|
+
sidebar ? /* @__PURE__ */ jsx("nav", { children: sidebar }) : null,
|
|
53
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
|
|
54
|
+
header ?? null,
|
|
55
|
+
/* @__PURE__ */ jsx("main", { children })
|
|
56
|
+
] })
|
|
57
|
+
] }),
|
|
58
|
+
sidebar: ({ children }) => /* @__PURE__ */ jsx("aside", { style: { width: "240px", borderRight: "1px solid #ddd" }, children }),
|
|
59
|
+
pageContainer: ({ children, title, actions }) => /* @__PURE__ */ jsxs("div", { children: [
|
|
60
|
+
title || actions ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
|
|
61
|
+
title ? /* @__PURE__ */ jsx("h1", { children: title }) : null,
|
|
62
|
+
actions ?? null
|
|
63
|
+
] }) : null,
|
|
64
|
+
children
|
|
65
|
+
] }),
|
|
66
|
+
breadcrumb: ({ items }) => /* @__PURE__ */ jsx("nav", { "aria-label": "breadcrumb", children: items.map((item, i) => /* @__PURE__ */ jsxs("span", { children: [
|
|
67
|
+
i > 0 ? " / " : null,
|
|
68
|
+
item.to ? /* @__PURE__ */ jsx("a", { href: item.to, children: item.label }) : item.label
|
|
69
|
+
] }, i)) }),
|
|
70
|
+
// Feedback
|
|
71
|
+
toast: ({ children }) => /* @__PURE__ */ jsx("div", { children }),
|
|
72
|
+
alert: ({ children, color }) => /* @__PURE__ */ jsx(
|
|
73
|
+
"div",
|
|
74
|
+
{
|
|
75
|
+
role: "alert",
|
|
76
|
+
style: {
|
|
77
|
+
padding: "8px 12px",
|
|
78
|
+
borderRadius: "4px",
|
|
79
|
+
backgroundColor: color === "danger" ? "#fee" : color === "success" ? "#efe" : color === "warning" ? "#ffe" : "#f5f5f5"
|
|
80
|
+
},
|
|
81
|
+
children
|
|
82
|
+
}
|
|
83
|
+
),
|
|
84
|
+
// File
|
|
85
|
+
dropZone: ({ children, isDragOver, onClick, onDrop, onDragOver, onDragLeave }) => /* @__PURE__ */ jsx(
|
|
86
|
+
"div",
|
|
87
|
+
{
|
|
88
|
+
onClick,
|
|
89
|
+
onDrop: (e) => {
|
|
90
|
+
e.preventDefault();
|
|
91
|
+
onDrop(e.dataTransfer.files);
|
|
92
|
+
},
|
|
93
|
+
onDragOver: (e) => {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
onDragOver(e);
|
|
96
|
+
},
|
|
97
|
+
onDragLeave,
|
|
98
|
+
style: {
|
|
99
|
+
border: `2px dashed ${isDragOver ? "#4caf50" : "#ccc"}`,
|
|
100
|
+
borderRadius: "8px",
|
|
101
|
+
padding: "32px",
|
|
102
|
+
textAlign: "center",
|
|
103
|
+
cursor: "pointer"
|
|
104
|
+
},
|
|
105
|
+
children
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// src/plugin.tsx
|
|
111
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
112
|
+
var UIPluginContext = createContext(null);
|
|
113
|
+
function createUIPlugin(config) {
|
|
114
|
+
return { components: config.components };
|
|
115
|
+
}
|
|
116
|
+
function UIPluginProvider({
|
|
117
|
+
plugin,
|
|
118
|
+
children
|
|
119
|
+
}) {
|
|
120
|
+
return /* @__PURE__ */ jsx2(UIPluginContext.Provider, { value: plugin, children });
|
|
121
|
+
}
|
|
122
|
+
function useUIPlugin() {
|
|
123
|
+
return useContext(UIPluginContext);
|
|
124
|
+
}
|
|
125
|
+
function useComponent(slot) {
|
|
126
|
+
const plugin = useUIPlugin();
|
|
127
|
+
const merged = { ...headlessDefaults, ...plugin?.components };
|
|
128
|
+
return merged[slot];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/hooks/use-toast.ts
|
|
132
|
+
import { createContext as createContext2, useContext as useContext2, useCallback } from "react";
|
|
133
|
+
var ToastContext = createContext2(null);
|
|
134
|
+
function useToast() {
|
|
135
|
+
const ctx = useContext2(ToastContext);
|
|
136
|
+
if (!ctx) {
|
|
137
|
+
throw new Error("useToast must be used within a <ToastProvider>");
|
|
138
|
+
}
|
|
139
|
+
const show = useCallback(
|
|
140
|
+
(options) => ctx.show(options),
|
|
141
|
+
[ctx]
|
|
142
|
+
);
|
|
143
|
+
const success = useCallback(
|
|
144
|
+
(message, description) => ctx.show({ message, type: "success", description }),
|
|
145
|
+
[ctx]
|
|
146
|
+
);
|
|
147
|
+
const error = useCallback(
|
|
148
|
+
(message, description) => ctx.show({ message, type: "error", description }),
|
|
149
|
+
[ctx]
|
|
150
|
+
);
|
|
151
|
+
const info = useCallback(
|
|
152
|
+
(message, description) => ctx.show({ message, type: "info", description }),
|
|
153
|
+
[ctx]
|
|
154
|
+
);
|
|
155
|
+
const warning = useCallback(
|
|
156
|
+
(message, description) => ctx.show({ message, type: "warning", description }),
|
|
157
|
+
[ctx]
|
|
158
|
+
);
|
|
159
|
+
return { show, success, error, info, warning };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/components/permission-gate.tsx
|
|
163
|
+
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
164
|
+
function PermissionGate({
|
|
165
|
+
action,
|
|
166
|
+
children,
|
|
167
|
+
fallback
|
|
168
|
+
}) {
|
|
169
|
+
if (action.invisible) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
if (!action.permitted) {
|
|
173
|
+
return fallback ? /* @__PURE__ */ jsx3(Fragment, { children: fallback }) : null;
|
|
174
|
+
}
|
|
175
|
+
return /* @__PURE__ */ jsx3(Fragment, { children });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/components/avatar-with-initials.tsx
|
|
179
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
180
|
+
function getInitials(name) {
|
|
181
|
+
return name.split(" ").map((part) => part[0]).join("").toUpperCase().slice(0, 2);
|
|
182
|
+
}
|
|
183
|
+
var sizeMap = { sm: 32, md: 40, lg: 56 };
|
|
184
|
+
function AvatarWithInitials({
|
|
185
|
+
src,
|
|
186
|
+
name,
|
|
187
|
+
size = "md"
|
|
188
|
+
}) {
|
|
189
|
+
const px = sizeMap[size];
|
|
190
|
+
if (src) {
|
|
191
|
+
return /* @__PURE__ */ jsx4(
|
|
192
|
+
"img",
|
|
193
|
+
{
|
|
194
|
+
src,
|
|
195
|
+
alt: name,
|
|
196
|
+
style: {
|
|
197
|
+
width: px,
|
|
198
|
+
height: px,
|
|
199
|
+
borderRadius: "50%",
|
|
200
|
+
objectFit: "cover"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return /* @__PURE__ */ jsx4(
|
|
206
|
+
"span",
|
|
207
|
+
{
|
|
208
|
+
"aria-label": name,
|
|
209
|
+
style: {
|
|
210
|
+
display: "inline-flex",
|
|
211
|
+
alignItems: "center",
|
|
212
|
+
justifyContent: "center",
|
|
213
|
+
width: px,
|
|
214
|
+
height: px,
|
|
215
|
+
borderRadius: "50%",
|
|
216
|
+
backgroundColor: "#ddd",
|
|
217
|
+
fontSize: px * 0.4,
|
|
218
|
+
fontWeight: "bold"
|
|
219
|
+
},
|
|
220
|
+
children: getInitials(name)
|
|
221
|
+
}
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/fields/date-field.tsx
|
|
226
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
227
|
+
var rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
|
228
|
+
function getRelativeTime(date) {
|
|
229
|
+
const now = Date.now();
|
|
230
|
+
const diffMs = date.getTime() - now;
|
|
231
|
+
const diffSec = Math.round(diffMs / 1e3);
|
|
232
|
+
const diffMin = Math.round(diffSec / 60);
|
|
233
|
+
const diffHour = Math.round(diffMin / 60);
|
|
234
|
+
const diffDay = Math.round(diffHour / 24);
|
|
235
|
+
if (Math.abs(diffSec) < 60) return rtf.format(diffSec, "second");
|
|
236
|
+
if (Math.abs(diffMin) < 60) return rtf.format(diffMin, "minute");
|
|
237
|
+
if (Math.abs(diffHour) < 24) return rtf.format(diffHour, "hour");
|
|
238
|
+
if (Math.abs(diffDay) < 30) return rtf.format(diffDay, "day");
|
|
239
|
+
const diffMonth = Math.round(diffDay / 30);
|
|
240
|
+
if (Math.abs(diffMonth) < 12) return rtf.format(diffMonth, "month");
|
|
241
|
+
return rtf.format(Math.round(diffDay / 365), "year");
|
|
242
|
+
}
|
|
243
|
+
function formatDate(date, format, locale) {
|
|
244
|
+
switch (format) {
|
|
245
|
+
case "relative":
|
|
246
|
+
return getRelativeTime(date);
|
|
247
|
+
case "long":
|
|
248
|
+
return new Intl.DateTimeFormat(locale, {
|
|
249
|
+
dateStyle: "long"
|
|
250
|
+
}).format(date);
|
|
251
|
+
case "datetime":
|
|
252
|
+
return new Intl.DateTimeFormat(locale, {
|
|
253
|
+
dateStyle: "medium",
|
|
254
|
+
timeStyle: "short"
|
|
255
|
+
}).format(date);
|
|
256
|
+
case "short":
|
|
257
|
+
default:
|
|
258
|
+
return new Intl.DateTimeFormat(locale, {
|
|
259
|
+
dateStyle: "medium"
|
|
260
|
+
}).format(date);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function DateField({
|
|
264
|
+
value,
|
|
265
|
+
format = "short",
|
|
266
|
+
locale = "en"
|
|
267
|
+
}) {
|
|
268
|
+
if (value == null) {
|
|
269
|
+
return /* @__PURE__ */ jsx5("span", { children: "\u2014" });
|
|
270
|
+
}
|
|
271
|
+
const date = value instanceof Date ? value : typeof value === "string" || typeof value === "number" ? new Date(value) : new Date(String(value));
|
|
272
|
+
if (Number.isNaN(date.getTime())) {
|
|
273
|
+
return /* @__PURE__ */ jsx5("span", { children: "Invalid date" });
|
|
274
|
+
}
|
|
275
|
+
return /* @__PURE__ */ jsx5("time", { dateTime: date.toISOString(), children: formatDate(date, format, locale) });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/fields/boolean-field.tsx
|
|
279
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
280
|
+
function BooleanField({
|
|
281
|
+
value,
|
|
282
|
+
trueLabel = "Yes",
|
|
283
|
+
falseLabel = "No",
|
|
284
|
+
trueColor = "success",
|
|
285
|
+
falseColor = "neutral"
|
|
286
|
+
}) {
|
|
287
|
+
const Chip = useComponent("chip");
|
|
288
|
+
if (value == null) {
|
|
289
|
+
return /* @__PURE__ */ jsx6("span", { children: "\u2014" });
|
|
290
|
+
}
|
|
291
|
+
const boolValue = Boolean(value);
|
|
292
|
+
const color = boolValue ? trueColor : falseColor;
|
|
293
|
+
const chipColor = isChipColor(color) ? color : "neutral";
|
|
294
|
+
return /* @__PURE__ */ jsx6(
|
|
295
|
+
Chip,
|
|
296
|
+
{
|
|
297
|
+
color: chipColor,
|
|
298
|
+
variant: "soft",
|
|
299
|
+
size: "sm",
|
|
300
|
+
children: boolValue ? trueLabel : falseLabel
|
|
301
|
+
}
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
var CHIP_COLORS = /* @__PURE__ */ new Set(["success", "neutral", "danger", "primary", "warning"]);
|
|
305
|
+
function isChipColor(value) {
|
|
306
|
+
return CHIP_COLORS.has(value);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/fields/number-field.tsx
|
|
310
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
311
|
+
function NumberField({
|
|
312
|
+
value,
|
|
313
|
+
locale = "en",
|
|
314
|
+
currency,
|
|
315
|
+
decimals
|
|
316
|
+
}) {
|
|
317
|
+
if (value == null) {
|
|
318
|
+
return /* @__PURE__ */ jsx7("span", { children: "\u2014" });
|
|
319
|
+
}
|
|
320
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
321
|
+
if (Number.isNaN(num)) {
|
|
322
|
+
return /* @__PURE__ */ jsx7("span", { children: "\u2014" });
|
|
323
|
+
}
|
|
324
|
+
const options = {};
|
|
325
|
+
if (currency) {
|
|
326
|
+
options.style = "currency";
|
|
327
|
+
options.currency = currency;
|
|
328
|
+
}
|
|
329
|
+
if (decimals !== void 0) {
|
|
330
|
+
options.minimumFractionDigits = decimals;
|
|
331
|
+
options.maximumFractionDigits = decimals;
|
|
332
|
+
}
|
|
333
|
+
const formatted = new Intl.NumberFormat(locale, options).format(num);
|
|
334
|
+
return /* @__PURE__ */ jsx7("span", { children: formatted });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/fields/text-field.tsx
|
|
338
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
339
|
+
function TextField({
|
|
340
|
+
value,
|
|
341
|
+
maxLength
|
|
342
|
+
}) {
|
|
343
|
+
if (value == null) {
|
|
344
|
+
return /* @__PURE__ */ jsx8("span", { children: "\u2014" });
|
|
345
|
+
}
|
|
346
|
+
const text = String(value);
|
|
347
|
+
const display = maxLength && text.length > maxLength ? `${text.slice(0, maxLength)}\u2026` : text;
|
|
348
|
+
if (maxLength && text.length > maxLength) {
|
|
349
|
+
return /* @__PURE__ */ jsx8("span", { title: text, children: display });
|
|
350
|
+
}
|
|
351
|
+
return /* @__PURE__ */ jsx8("span", { children: display });
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/fields/email-field.tsx
|
|
355
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
356
|
+
function EmailField({ value }) {
|
|
357
|
+
if (value == null) {
|
|
358
|
+
return /* @__PURE__ */ jsx9("span", { children: "\u2014" });
|
|
359
|
+
}
|
|
360
|
+
const email = String(value);
|
|
361
|
+
return /* @__PURE__ */ jsx9("a", { href: `mailto:${email}`, children: email });
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/fields/json-field.tsx
|
|
365
|
+
import { useState } from "react";
|
|
366
|
+
import { jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
367
|
+
function JsonField({
|
|
368
|
+
value,
|
|
369
|
+
collapsed = false
|
|
370
|
+
}) {
|
|
371
|
+
const [isCollapsed, setIsCollapsed] = useState(collapsed);
|
|
372
|
+
if (value == null) {
|
|
373
|
+
return /* @__PURE__ */ jsx10("span", { children: "\u2014" });
|
|
374
|
+
}
|
|
375
|
+
const formatted = JSON.stringify(value, null, 2);
|
|
376
|
+
if (isCollapsed) {
|
|
377
|
+
const preview = JSON.stringify(value);
|
|
378
|
+
const short = preview.length > 60 ? `${preview.slice(0, 60)}\u2026` : preview;
|
|
379
|
+
return /* @__PURE__ */ jsxs2("span", { children: [
|
|
380
|
+
/* @__PURE__ */ jsx10("code", { children: short }),
|
|
381
|
+
" ",
|
|
382
|
+
/* @__PURE__ */ jsx10(
|
|
383
|
+
"button",
|
|
384
|
+
{
|
|
385
|
+
onClick: () => setIsCollapsed(false),
|
|
386
|
+
style: { border: "none", background: "none", cursor: "pointer", color: "#666", fontSize: "12px" },
|
|
387
|
+
children: "expand"
|
|
388
|
+
}
|
|
389
|
+
)
|
|
390
|
+
] });
|
|
391
|
+
}
|
|
392
|
+
return /* @__PURE__ */ jsx10(
|
|
393
|
+
"pre",
|
|
394
|
+
{
|
|
395
|
+
style: {
|
|
396
|
+
margin: 0,
|
|
397
|
+
fontSize: "13px",
|
|
398
|
+
backgroundColor: "#f5f5f5",
|
|
399
|
+
padding: "8px",
|
|
400
|
+
borderRadius: "4px",
|
|
401
|
+
overflow: "auto"
|
|
402
|
+
},
|
|
403
|
+
children: /* @__PURE__ */ jsx10("code", { children: formatted })
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// src/fields/field-for-column.ts
|
|
409
|
+
function fieldForColumn(column) {
|
|
410
|
+
const { dataType, name } = column;
|
|
411
|
+
const dt = dataType.toLowerCase();
|
|
412
|
+
if (dt === "boolean" || dt === "integer" && name.startsWith("is")) {
|
|
413
|
+
return BooleanField;
|
|
414
|
+
}
|
|
415
|
+
if (dt.includes("timestamp") || dt.includes("date") || dt.includes("datetime")) {
|
|
416
|
+
return DateField;
|
|
417
|
+
}
|
|
418
|
+
if (dt === "integer" || dt === "real" || dt === "numeric" || dt.includes("int") || dt.includes("float") || dt.includes("double") || dt.includes("decimal")) {
|
|
419
|
+
return NumberField;
|
|
420
|
+
}
|
|
421
|
+
if (name === "email" || name.endsWith("Email")) {
|
|
422
|
+
return EmailField;
|
|
423
|
+
}
|
|
424
|
+
if (dt === "json" || dt === "jsonb" || dt === "blob") {
|
|
425
|
+
return JsonField;
|
|
426
|
+
}
|
|
427
|
+
return TextField;
|
|
428
|
+
}
|
|
429
|
+
function fieldsForTable(table) {
|
|
430
|
+
const result = {};
|
|
431
|
+
for (const [key, col] of Object.entries(table)) {
|
|
432
|
+
if (isColumnMeta(col)) {
|
|
433
|
+
result[key] = fieldForColumn(col);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
function isColumnMeta(value) {
|
|
439
|
+
return typeof value === "object" && value !== null && "dataType" in value && typeof value.dataType === "string" && "name" in value && typeof value.name === "string";
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/record-access.ts
|
|
443
|
+
function getField(obj, key) {
|
|
444
|
+
if (typeof obj !== "object" || obj === null) {
|
|
445
|
+
return void 0;
|
|
446
|
+
}
|
|
447
|
+
return obj[key];
|
|
448
|
+
}
|
|
449
|
+
function getRecordId(obj) {
|
|
450
|
+
const id = getField(obj, "id");
|
|
451
|
+
if (typeof id === "string" || typeof id === "number") {
|
|
452
|
+
return id;
|
|
453
|
+
}
|
|
454
|
+
return 0;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// src/hooks/use-action-status.ts
|
|
458
|
+
import { useActions } from "@cfast/actions/client";
|
|
459
|
+
function useActionStatus(descriptor) {
|
|
460
|
+
const actions = useActions(descriptor);
|
|
461
|
+
const name = descriptor.actionNames[0];
|
|
462
|
+
return actions[name]();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export {
|
|
466
|
+
createUIPlugin,
|
|
467
|
+
UIPluginProvider,
|
|
468
|
+
useUIPlugin,
|
|
469
|
+
useComponent,
|
|
470
|
+
ToastContext,
|
|
471
|
+
useToast,
|
|
472
|
+
PermissionGate,
|
|
473
|
+
getInitials,
|
|
474
|
+
AvatarWithInitials,
|
|
475
|
+
getField,
|
|
476
|
+
getRecordId,
|
|
477
|
+
DateField,
|
|
478
|
+
BooleanField,
|
|
479
|
+
NumberField,
|
|
480
|
+
TextField,
|
|
481
|
+
EmailField,
|
|
482
|
+
JsonField,
|
|
483
|
+
fieldForColumn,
|
|
484
|
+
fieldsForTable,
|
|
485
|
+
useActionStatus
|
|
486
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// src/hooks/use-action-status.ts
|
|
2
|
+
import { useActions } from "@cfast/actions/client";
|
|
3
|
+
function useActionStatus(descriptor, actionName, input) {
|
|
4
|
+
const actions = useActions(descriptor);
|
|
5
|
+
const name = actionName ?? descriptor.actionNames[0];
|
|
6
|
+
const actionFn = actions[name];
|
|
7
|
+
if (!actionFn) {
|
|
8
|
+
return {
|
|
9
|
+
permitted: false,
|
|
10
|
+
invisible: true,
|
|
11
|
+
reason: `Action "${name}" not found in descriptor`,
|
|
12
|
+
submit: () => {
|
|
13
|
+
},
|
|
14
|
+
pending: false,
|
|
15
|
+
data: void 0,
|
|
16
|
+
error: void 0
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const result = actionFn(input);
|
|
20
|
+
return {
|
|
21
|
+
permitted: result.permitted,
|
|
22
|
+
invisible: result.invisible,
|
|
23
|
+
reason: result.reason,
|
|
24
|
+
submit: result.submit,
|
|
25
|
+
pending: result.pending,
|
|
26
|
+
data: result.data,
|
|
27
|
+
error: result.error
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// src/hooks/use-toast.ts
|
|
32
|
+
import { createContext, useContext, useCallback } from "react";
|
|
33
|
+
var ToastContext = createContext(null);
|
|
34
|
+
function useToast() {
|
|
35
|
+
const ctx = useContext(ToastContext);
|
|
36
|
+
if (!ctx) {
|
|
37
|
+
throw new Error("useToast must be used within a <ToastProvider>");
|
|
38
|
+
}
|
|
39
|
+
const show = useCallback(
|
|
40
|
+
(options) => ctx.show(options),
|
|
41
|
+
[ctx]
|
|
42
|
+
);
|
|
43
|
+
const success = useCallback(
|
|
44
|
+
(message, description) => ctx.show({ message, type: "success", description }),
|
|
45
|
+
[ctx]
|
|
46
|
+
);
|
|
47
|
+
const error = useCallback(
|
|
48
|
+
(message, description) => ctx.show({ message, type: "error", description }),
|
|
49
|
+
[ctx]
|
|
50
|
+
);
|
|
51
|
+
const info = useCallback(
|
|
52
|
+
(message, description) => ctx.show({ message, type: "info", description }),
|
|
53
|
+
[ctx]
|
|
54
|
+
);
|
|
55
|
+
const warning = useCallback(
|
|
56
|
+
(message, description) => ctx.show({ message, type: "warning", description }),
|
|
57
|
+
[ctx]
|
|
58
|
+
);
|
|
59
|
+
return { show, success, error, info, warning };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/components/avatar-with-initials.tsx
|
|
63
|
+
import { createElement } from "react";
|
|
64
|
+
function getInitials(name) {
|
|
65
|
+
return name.split(" ").map((part) => part[0]).join("").toUpperCase().slice(0, 2);
|
|
66
|
+
}
|
|
67
|
+
var sizeMap = { sm: 32, md: 40, lg: 56 };
|
|
68
|
+
function AvatarWithInitials({
|
|
69
|
+
src,
|
|
70
|
+
name,
|
|
71
|
+
size = "md"
|
|
72
|
+
}) {
|
|
73
|
+
const px = sizeMap[size];
|
|
74
|
+
if (src) {
|
|
75
|
+
return createElement("img", {
|
|
76
|
+
src,
|
|
77
|
+
alt: name,
|
|
78
|
+
style: {
|
|
79
|
+
width: px,
|
|
80
|
+
height: px,
|
|
81
|
+
borderRadius: "50%",
|
|
82
|
+
objectFit: "cover"
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return createElement(
|
|
87
|
+
"span",
|
|
88
|
+
{
|
|
89
|
+
"aria-label": name,
|
|
90
|
+
style: {
|
|
91
|
+
display: "inline-flex",
|
|
92
|
+
alignItems: "center",
|
|
93
|
+
justifyContent: "center",
|
|
94
|
+
width: px,
|
|
95
|
+
height: px,
|
|
96
|
+
borderRadius: "50%",
|
|
97
|
+
backgroundColor: "#ddd",
|
|
98
|
+
fontSize: px * 0.4,
|
|
99
|
+
fontWeight: "bold"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
getInitials(name)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export {
|
|
107
|
+
useActionStatus,
|
|
108
|
+
ToastContext,
|
|
109
|
+
useToast,
|
|
110
|
+
getInitials,
|
|
111
|
+
AvatarWithInitials
|
|
112
|
+
};
|