@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.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +727 -0
  3. package/dist/chunk-755IRYDN.js +941 -0
  4. package/dist/chunk-7SNK37GF.js +418 -0
  5. package/dist/chunk-ASMYTWTR.js +356 -0
  6. package/dist/chunk-B2XXH5V4.js +66 -0
  7. package/dist/chunk-BQMXYYEV.js +348 -0
  8. package/dist/chunk-DTKBXCTU.js +211 -0
  9. package/dist/chunk-EYIBATYR.js +33 -0
  10. package/dist/chunk-FPZAQ2YQ.js +474 -0
  11. package/dist/chunk-G2OU4BYC.js +205 -0
  12. package/dist/chunk-JEGEIQ3R.js +925 -0
  13. package/dist/chunk-JUNLQJ6H.js +1013 -0
  14. package/dist/chunk-NRGMW3JA.js +906 -0
  15. package/dist/chunk-Q6FPL2OJ.js +1086 -0
  16. package/dist/chunk-QHWAGKNW.js +456 -0
  17. package/dist/chunk-QZT62CGJ.js +924 -0
  18. package/dist/chunk-RDTUEOLK.js +486 -0
  19. package/dist/chunk-RESL4IJJ.js +112 -0
  20. package/dist/chunk-UDCWQUTR.js +221 -0
  21. package/dist/chunk-UE7PZOIJ.js +11 -0
  22. package/dist/chunk-UTZTHGNE.js +84 -0
  23. package/dist/chunk-UVRXMOX5.js +439 -0
  24. package/dist/chunk-XFD3N2D4.js +161 -0
  25. package/dist/client-CXIHCQtA.d.ts +274 -0
  26. package/dist/client.d.ts +617 -0
  27. package/dist/client.js +54 -0
  28. package/dist/index.d.ts +415 -0
  29. package/dist/index.js +296 -0
  30. package/dist/joy.d.ts +199 -0
  31. package/dist/joy.js +1150 -0
  32. package/dist/permission-gate-DVmY42oz.d.ts +1269 -0
  33. package/dist/permission-gate-apt9T9Mu.d.ts +1256 -0
  34. package/dist/types-1bAiH2uK.d.ts +392 -0
  35. package/dist/types-BX6u5sAd.d.ts +403 -0
  36. package/dist/types-BpdY7w5l.d.ts +403 -0
  37. package/dist/types-BrepeVp8.d.ts +403 -0
  38. package/dist/types-BvAqMZhn.d.ts +403 -0
  39. package/dist/types-C74nSscq.d.ts +403 -0
  40. package/dist/types-DD1Cpx8F.d.ts +403 -0
  41. package/dist/types-DHUhQwJn.d.ts +403 -0
  42. package/dist/types-DZSJNt_M.d.ts +392 -0
  43. package/dist/types-DaaJiIjW.d.ts +391 -0
  44. package/dist/types-LUpWJwps.d.ts +403 -0
  45. package/dist/types-a7zVU6WE.d.ts +394 -0
  46. package/dist/types-biJTHMcH.d.ts +403 -0
  47. package/dist/types-ow_qSEYJ.d.ts +392 -0
  48. package/dist/types-wnLasZaB.d.ts +1234 -0
  49. package/package.json +88 -0
@@ -0,0 +1,356 @@
1
+ import {
2
+ useToast
3
+ } from "./chunk-UTZTHGNE.js";
4
+
5
+ // src/plugin.tsx
6
+ import { createContext, useContext } from "react";
7
+
8
+ // src/headless-defaults.tsx
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ var headlessDefaults = {
11
+ // Actions
12
+ button: ({ children, onClick, disabled, loading, type }) => /* @__PURE__ */ jsx("button", { onClick, disabled: disabled || loading, type: type ?? "button", children: loading ? "Loading..." : children }),
13
+ tooltip: ({ children, title }) => /* @__PURE__ */ jsx("span", { title, children }),
14
+ confirmDialog: ({ open, onClose, onConfirm, title, description, confirmLabel, cancelLabel }) => open ? /* @__PURE__ */ jsxs("dialog", { open: true, children: [
15
+ /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("strong", { children: title }) }),
16
+ description ? /* @__PURE__ */ jsx("p", { children: description }) : null,
17
+ /* @__PURE__ */ jsxs("div", { children: [
18
+ /* @__PURE__ */ jsx("button", { onClick: onClose, children: cancelLabel ?? "Cancel" }),
19
+ /* @__PURE__ */ jsx("button", { onClick: onConfirm, children: confirmLabel ?? "Confirm" })
20
+ ] })
21
+ ] }) : null,
22
+ // Data display
23
+ table: ({ children }) => /* @__PURE__ */ jsx("table", { children }),
24
+ tableHead: ({ children }) => /* @__PURE__ */ jsx("thead", { children }),
25
+ tableBody: ({ children }) => /* @__PURE__ */ jsx("tbody", { children }),
26
+ tableRow: ({ children, onClick }) => /* @__PURE__ */ jsx("tr", { onClick, children }),
27
+ tableCell: ({ children, header, sortable, sortDirection, onSort }) => {
28
+ const Tag = header ? "th" : "td";
29
+ return /* @__PURE__ */ jsxs(
30
+ Tag,
31
+ {
32
+ onClick: sortable ? onSort : void 0,
33
+ style: sortable ? { cursor: "pointer" } : void 0,
34
+ children: [
35
+ children,
36
+ sortable && sortDirection ? sortDirection === "asc" ? " \u2191" : " \u2193" : null
37
+ ]
38
+ }
39
+ );
40
+ },
41
+ chip: ({ children, size }) => /* @__PURE__ */ jsx(
42
+ "span",
43
+ {
44
+ style: {
45
+ display: "inline-block",
46
+ padding: size === "sm" ? "1px 6px" : "2px 8px",
47
+ borderRadius: "12px",
48
+ fontSize: size === "sm" ? "12px" : "14px",
49
+ backgroundColor: "#eee"
50
+ },
51
+ children
52
+ }
53
+ ),
54
+ // Layout
55
+ appShell: ({ children, sidebar, header }) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", minHeight: "100vh" }, children: [
56
+ sidebar ? /* @__PURE__ */ jsx("nav", { children: sidebar }) : null,
57
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
58
+ header ?? null,
59
+ /* @__PURE__ */ jsx("main", { children })
60
+ ] })
61
+ ] }),
62
+ sidebar: ({ children }) => /* @__PURE__ */ jsx("aside", { style: { width: "240px", borderRight: "1px solid #ddd" }, children }),
63
+ pageContainer: ({ children, title, actions }) => /* @__PURE__ */ jsxs("div", { children: [
64
+ title || actions ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
65
+ title ? /* @__PURE__ */ jsx("h1", { children: title }) : null,
66
+ actions ?? null
67
+ ] }) : null,
68
+ children
69
+ ] }),
70
+ breadcrumb: ({ items }) => /* @__PURE__ */ jsx("nav", { "aria-label": "breadcrumb", children: items.map((item, i) => /* @__PURE__ */ jsxs("span", { children: [
71
+ i > 0 ? " / " : null,
72
+ item.to ? /* @__PURE__ */ jsx("a", { href: item.to, children: item.label }) : item.label
73
+ ] }, i)) }),
74
+ // Feedback
75
+ toast: ({ children }) => /* @__PURE__ */ jsx("div", { children }),
76
+ alert: ({ children, color }) => /* @__PURE__ */ jsx(
77
+ "div",
78
+ {
79
+ role: "alert",
80
+ style: {
81
+ padding: "8px 12px",
82
+ borderRadius: "4px",
83
+ backgroundColor: color === "danger" ? "#fee" : color === "success" ? "#efe" : color === "warning" ? "#ffe" : "#f5f5f5"
84
+ },
85
+ children
86
+ }
87
+ ),
88
+ // File
89
+ dropZone: ({ children, isDragOver, onClick, onDrop, onDragOver, onDragLeave }) => /* @__PURE__ */ jsx(
90
+ "div",
91
+ {
92
+ onClick,
93
+ onDrop: (e) => {
94
+ e.preventDefault();
95
+ onDrop(e.dataTransfer.files);
96
+ },
97
+ onDragOver: (e) => {
98
+ e.preventDefault();
99
+ onDragOver(e);
100
+ },
101
+ onDragLeave,
102
+ style: {
103
+ border: `2px dashed ${isDragOver ? "#4caf50" : "#ccc"}`,
104
+ borderRadius: "8px",
105
+ padding: "32px",
106
+ textAlign: "center",
107
+ cursor: "pointer"
108
+ },
109
+ children
110
+ }
111
+ )
112
+ };
113
+
114
+ // src/plugin.tsx
115
+ import { jsx as jsx2 } from "react/jsx-runtime";
116
+ var UIPluginContext = createContext(null);
117
+ function createUIPlugin(config) {
118
+ return { components: config.components };
119
+ }
120
+ function UIPluginProvider({
121
+ plugin,
122
+ children
123
+ }) {
124
+ return /* @__PURE__ */ jsx2(UIPluginContext.Provider, { value: plugin, children });
125
+ }
126
+ function useUIPlugin() {
127
+ return useContext(UIPluginContext);
128
+ }
129
+ function useComponent(slot) {
130
+ const plugin = useUIPlugin();
131
+ const component = plugin?.components[slot];
132
+ if (component) {
133
+ return component;
134
+ }
135
+ return headlessDefaults[slot];
136
+ }
137
+
138
+ // src/hooks/use-confirm.ts
139
+ import { createContext as createContext2, useContext as useContext2, useCallback } from "react";
140
+ var ConfirmContext = createContext2(null);
141
+ function useConfirm() {
142
+ const ctx = useContext2(ConfirmContext);
143
+ if (!ctx) {
144
+ throw new Error("useConfirm must be used within a <ConfirmProvider>");
145
+ }
146
+ return useCallback(
147
+ (options) => ctx.confirm(options),
148
+ [ctx]
149
+ );
150
+ }
151
+
152
+ // src/hooks/use-action-toast.ts
153
+ import { useEffect, useRef } from "react";
154
+ import { useActions } from "@cfast/actions/client";
155
+ function useActionToast(descriptor, config) {
156
+ const actions = useActions(descriptor);
157
+ const toast = useToast();
158
+ const prevDataRef = useRef({});
159
+ useEffect(() => {
160
+ for (const [name, cfg] of Object.entries(config)) {
161
+ const actionFn = actions[name];
162
+ if (!actionFn) continue;
163
+ const result = actionFn();
164
+ const prevData = prevDataRef.current[name];
165
+ if (result.data !== void 0 && result.data !== prevData) {
166
+ prevDataRef.current[name] = result.data;
167
+ if (cfg.success) {
168
+ toast.success(cfg.success);
169
+ }
170
+ }
171
+ if (result.error !== void 0 && result.error !== prevData) {
172
+ prevDataRef.current[name] = result.error;
173
+ if (cfg.error) {
174
+ toast.error(cfg.error);
175
+ }
176
+ }
177
+ }
178
+ });
179
+ }
180
+
181
+ // src/components/permission-gate.tsx
182
+ import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
183
+ function PermissionGate({
184
+ action,
185
+ children,
186
+ fallback
187
+ }) {
188
+ if (action.invisible) {
189
+ return null;
190
+ }
191
+ if (!action.permitted) {
192
+ return fallback ? /* @__PURE__ */ jsx3(Fragment, { children: fallback }) : null;
193
+ }
194
+ return /* @__PURE__ */ jsx3(Fragment, { children });
195
+ }
196
+
197
+ // src/components/action-button.tsx
198
+ import { jsx as jsx4 } from "react/jsx-runtime";
199
+ function ActionButton({
200
+ action,
201
+ children,
202
+ whenForbidden = "disable",
203
+ confirmation: _confirmation,
204
+ ...buttonProps
205
+ }) {
206
+ const Button = useComponent("button");
207
+ if (action.invisible) {
208
+ return null;
209
+ }
210
+ if (!action.permitted && whenForbidden === "hide") {
211
+ return null;
212
+ }
213
+ const disabled = !action.permitted && whenForbidden === "disable";
214
+ return /* @__PURE__ */ jsx4(
215
+ Button,
216
+ {
217
+ ...buttonProps,
218
+ onClick: () => action.submit(),
219
+ disabled,
220
+ loading: action.pending,
221
+ children
222
+ }
223
+ );
224
+ }
225
+
226
+ // src/components/confirm-provider.tsx
227
+ import { useState, useCallback as useCallback2, useRef as useRef2 } from "react";
228
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
229
+ function ConfirmProvider({ children }) {
230
+ const [state, setState] = useState(null);
231
+ const ConfirmDialog = useComponent("confirmDialog");
232
+ const resolveRef = useRef2(null);
233
+ const confirm = useCallback2((options) => {
234
+ return new Promise((resolve) => {
235
+ resolveRef.current = resolve;
236
+ setState({ ...options, resolve });
237
+ });
238
+ }, []);
239
+ const handleClose = useCallback2(() => {
240
+ resolveRef.current?.(false);
241
+ resolveRef.current = null;
242
+ setState(null);
243
+ }, []);
244
+ const handleConfirm = useCallback2(() => {
245
+ resolveRef.current?.(true);
246
+ resolveRef.current = null;
247
+ setState(null);
248
+ }, []);
249
+ return /* @__PURE__ */ jsxs2(ConfirmContext.Provider, { value: { confirm }, children: [
250
+ children,
251
+ state ? /* @__PURE__ */ jsx5(
252
+ ConfirmDialog,
253
+ {
254
+ open: true,
255
+ onClose: handleClose,
256
+ onConfirm: handleConfirm,
257
+ title: state.title,
258
+ description: state.description,
259
+ confirmLabel: state.confirmLabel,
260
+ cancelLabel: state.cancelLabel,
261
+ variant: state.variant
262
+ }
263
+ ) : null
264
+ ] });
265
+ }
266
+
267
+ // src/components/form-status.tsx
268
+ import { jsx as jsx6 } from "react/jsx-runtime";
269
+ function FormStatus({ data }) {
270
+ const Alert = useComponent("alert");
271
+ if (!data) return null;
272
+ const elements = [];
273
+ if (data.success) {
274
+ elements.push(
275
+ /* @__PURE__ */ jsx6(Alert, { color: "success", children: data.success }, "success")
276
+ );
277
+ }
278
+ if (data.error) {
279
+ elements.push(
280
+ /* @__PURE__ */ jsx6(Alert, { color: "danger", children: data.error }, "error")
281
+ );
282
+ }
283
+ if (data.fieldErrors) {
284
+ const errorMessages = Object.entries(data.fieldErrors).flatMap(
285
+ ([field, errors]) => errors.map((err) => `${field}: ${err}`)
286
+ );
287
+ if (errorMessages.length > 0) {
288
+ elements.push(
289
+ /* @__PURE__ */ jsx6(Alert, { color: "danger", children: /* @__PURE__ */ jsx6("ul", { style: { margin: 0, paddingLeft: "16px" }, children: errorMessages.map((msg, i) => /* @__PURE__ */ jsx6("li", { children: msg }, i)) }) }, "field-errors")
290
+ );
291
+ }
292
+ }
293
+ if (elements.length === 0) return null;
294
+ return /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: elements });
295
+ }
296
+
297
+ // src/components/role-badge.tsx
298
+ import { jsx as jsx7 } from "react/jsx-runtime";
299
+ var defaultColors = {
300
+ admin: "danger",
301
+ editor: "primary",
302
+ author: "success",
303
+ reader: "neutral"
304
+ };
305
+ function RoleBadge({ role, colors }) {
306
+ const Chip = useComponent("chip");
307
+ const colorMap = colors ? { ...defaultColors, ...Object.fromEntries(
308
+ Object.entries(colors).map(([k, v]) => [k, v])
309
+ ) } : defaultColors;
310
+ const chipColor = colorMap[role] ?? "neutral";
311
+ return /* @__PURE__ */ jsx7(Chip, { color: chipColor, variant: "soft", size: "sm", children: role });
312
+ }
313
+
314
+ // src/components/impersonation-banner.tsx
315
+ import { useCurrentUser } from "@cfast/auth/client";
316
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
317
+ function ImpersonationBanner({
318
+ stopAction = "/admin/stop-impersonation"
319
+ }) {
320
+ const user = useCurrentUser();
321
+ const Alert = useComponent("alert");
322
+ const Button = useComponent("button");
323
+ if (!user?.isImpersonating) {
324
+ return null;
325
+ }
326
+ return /* @__PURE__ */ jsx8(Alert, { color: "warning", children: /* @__PURE__ */ jsxs3(
327
+ "div",
328
+ {
329
+ style: {
330
+ display: "flex",
331
+ alignItems: "center",
332
+ justifyContent: "center",
333
+ gap: "12px"
334
+ },
335
+ children: [
336
+ /* @__PURE__ */ jsx8("strong", { children: `Viewing as ${user.name} (${user.email})` }),
337
+ /* @__PURE__ */ jsx8("form", { method: "post", action: stopAction, children: /* @__PURE__ */ jsx8(Button, { type: "submit", variant: "outlined", size: "sm", children: "Stop Impersonating" }) })
338
+ ]
339
+ }
340
+ ) });
341
+ }
342
+
343
+ export {
344
+ createUIPlugin,
345
+ UIPluginProvider,
346
+ useUIPlugin,
347
+ useComponent,
348
+ useConfirm,
349
+ useActionToast,
350
+ PermissionGate,
351
+ ActionButton,
352
+ ConfirmProvider,
353
+ FormStatus,
354
+ RoleBadge,
355
+ ImpersonationBanner
356
+ };
@@ -0,0 +1,66 @@
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
+ export {
63
+ useActionStatus,
64
+ ToastContext,
65
+ useToast
66
+ };