@cfast/ui 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/llms.txt +159 -0
  2. package/package.json +26 -18
  3. package/LICENSE +0 -21
  4. package/dist/chunk-755IRYDN.js +0 -941
  5. package/dist/chunk-7SNK37GF.js +0 -418
  6. package/dist/chunk-ASMYTWTR.js +0 -356
  7. package/dist/chunk-B2XXH5V4.js +0 -66
  8. package/dist/chunk-BQMXYYEV.js +0 -348
  9. package/dist/chunk-DTKBXCTU.js +0 -211
  10. package/dist/chunk-EYIBATYR.js +0 -33
  11. package/dist/chunk-FPZAQ2YQ.js +0 -474
  12. package/dist/chunk-G2OU4BYC.js +0 -205
  13. package/dist/chunk-JUNLQJ6H.js +0 -1013
  14. package/dist/chunk-NRGMW3JA.js +0 -906
  15. package/dist/chunk-Q6FPL2OJ.js +0 -1086
  16. package/dist/chunk-QHWAGKNW.js +0 -456
  17. package/dist/chunk-QZT62CGJ.js +0 -924
  18. package/dist/chunk-RESL4IJJ.js +0 -112
  19. package/dist/chunk-UDCWQUTR.js +0 -221
  20. package/dist/chunk-UE7PZOIJ.js +0 -11
  21. package/dist/chunk-UTZTHGNE.js +0 -84
  22. package/dist/chunk-UVRXMOX5.js +0 -439
  23. package/dist/chunk-XFD3N2D4.js +0 -161
  24. package/dist/client-CXIHCQtA.d.ts +0 -274
  25. package/dist/permission-gate-apt9T9Mu.d.ts +0 -1256
  26. package/dist/types-1bAiH2uK.d.ts +0 -392
  27. package/dist/types-BX6u5sAd.d.ts +0 -403
  28. package/dist/types-BpdY7w5l.d.ts +0 -403
  29. package/dist/types-BrepeVp8.d.ts +0 -403
  30. package/dist/types-BvAqMZhn.d.ts +0 -403
  31. package/dist/types-C74nSscq.d.ts +0 -403
  32. package/dist/types-DD1Cpx8F.d.ts +0 -403
  33. package/dist/types-DHUhQwJn.d.ts +0 -403
  34. package/dist/types-DZSJNt_M.d.ts +0 -392
  35. package/dist/types-DaaJiIjW.d.ts +0 -391
  36. package/dist/types-LUpWJwps.d.ts +0 -403
  37. package/dist/types-a7zVU6WE.d.ts +0 -394
  38. package/dist/types-biJTHMcH.d.ts +0 -403
  39. package/dist/types-ow_qSEYJ.d.ts +0 -392
  40. package/dist/types-wnLasZaB.d.ts +0 -1234
@@ -1,924 +0,0 @@
1
- import {
2
- fieldForColumn,
3
- useActionStatus,
4
- useComponent,
5
- useToast
6
- } from "./chunk-QHWAGKNW.js";
7
-
8
- // src/hooks/use-confirm.ts
9
- import { createContext, useContext, useCallback } from "react";
10
- var ConfirmContext = createContext(null);
11
- function useConfirm() {
12
- const ctx = useContext(ConfirmContext);
13
- if (!ctx) {
14
- throw new Error("useConfirm must be used within a <ConfirmProvider>");
15
- }
16
- return useCallback(
17
- (options) => ctx.confirm(options),
18
- [ctx]
19
- );
20
- }
21
-
22
- // src/hooks/use-action-toast.ts
23
- import { useEffect, useRef } from "react";
24
- import { useActions } from "@cfast/actions/client";
25
- function useActionToast(descriptor, config) {
26
- const actions = useActions(descriptor);
27
- const toast = useToast();
28
- const prevDataRef = useRef({});
29
- useEffect(() => {
30
- for (const [name, cfg] of Object.entries(config)) {
31
- const actionFn = actions[name];
32
- if (!actionFn) continue;
33
- const result = actionFn();
34
- const prevData = prevDataRef.current[name];
35
- if (result.data !== void 0 && result.data !== prevData) {
36
- prevDataRef.current[name] = result.data;
37
- if (cfg.success) {
38
- toast.success(cfg.success);
39
- }
40
- }
41
- if (result.error !== void 0 && result.error !== prevData) {
42
- prevDataRef.current[name] = result.error;
43
- if (cfg.error) {
44
- toast.error(cfg.error);
45
- }
46
- }
47
- }
48
- });
49
- }
50
-
51
- // src/components/action-button.tsx
52
- import { jsx } from "react/jsx-runtime";
53
- function ActionButton({
54
- action,
55
- children,
56
- whenForbidden = "disable",
57
- confirmation: _confirmation,
58
- ...buttonProps
59
- }) {
60
- const Button = useComponent("button");
61
- if (action.invisible) {
62
- return null;
63
- }
64
- if (!action.permitted && whenForbidden === "hide") {
65
- return null;
66
- }
67
- const disabled = !action.permitted && whenForbidden === "disable";
68
- return /* @__PURE__ */ jsx(
69
- Button,
70
- {
71
- ...buttonProps,
72
- onClick: () => action.submit(),
73
- disabled,
74
- loading: action.pending,
75
- children
76
- }
77
- );
78
- }
79
-
80
- // src/components/confirm-provider.tsx
81
- import { useState, useCallback as useCallback2, useRef as useRef2 } from "react";
82
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
83
- function ConfirmProvider({ children }) {
84
- const [state, setState] = useState(null);
85
- const ConfirmDialog = useComponent("confirmDialog");
86
- const resolveRef = useRef2(null);
87
- const confirm = useCallback2((options) => {
88
- return new Promise((resolve) => {
89
- resolveRef.current = resolve;
90
- setState({ ...options, resolve });
91
- });
92
- }, []);
93
- const handleClose = useCallback2(() => {
94
- resolveRef.current?.(false);
95
- resolveRef.current = null;
96
- setState(null);
97
- }, []);
98
- const handleConfirm = useCallback2(() => {
99
- resolveRef.current?.(true);
100
- resolveRef.current = null;
101
- setState(null);
102
- }, []);
103
- return /* @__PURE__ */ jsxs(ConfirmContext.Provider, { value: { confirm }, children: [
104
- children,
105
- state ? /* @__PURE__ */ jsx2(
106
- ConfirmDialog,
107
- {
108
- open: true,
109
- onClose: handleClose,
110
- onConfirm: handleConfirm,
111
- title: state.title,
112
- description: state.description,
113
- confirmLabel: state.confirmLabel,
114
- cancelLabel: state.cancelLabel,
115
- variant: state.variant
116
- }
117
- ) : null
118
- ] });
119
- }
120
-
121
- // src/components/form-status.tsx
122
- import { jsx as jsx3 } from "react/jsx-runtime";
123
- function FormStatus({ data }) {
124
- const Alert = useComponent("alert");
125
- if (!data) return null;
126
- const elements = [];
127
- if (data.success) {
128
- elements.push(
129
- /* @__PURE__ */ jsx3(Alert, { color: "success", children: data.success }, "success")
130
- );
131
- }
132
- if (data.error) {
133
- elements.push(
134
- /* @__PURE__ */ jsx3(Alert, { color: "danger", children: data.error }, "error")
135
- );
136
- }
137
- if (data.fieldErrors) {
138
- const errorMessages = Object.entries(data.fieldErrors).flatMap(
139
- ([field, errors]) => errors.map((err) => `${field}: ${err}`)
140
- );
141
- if (errorMessages.length > 0) {
142
- elements.push(
143
- /* @__PURE__ */ jsx3(Alert, { color: "danger", children: /* @__PURE__ */ jsx3("ul", { style: { margin: 0, paddingLeft: "16px" }, children: errorMessages.map((msg, i) => /* @__PURE__ */ jsx3("li", { children: msg }, i)) }) }, "field-errors")
144
- );
145
- }
146
- }
147
- if (elements.length === 0) return null;
148
- return /* @__PURE__ */ jsx3("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: elements });
149
- }
150
-
151
- // src/components/role-badge.tsx
152
- import { jsx as jsx4 } from "react/jsx-runtime";
153
- var defaultColors = {
154
- admin: "danger",
155
- editor: "primary",
156
- author: "success",
157
- reader: "neutral"
158
- };
159
- function RoleBadge({ role, colors }) {
160
- const Chip = useComponent("chip");
161
- const colorMap = colors ? { ...defaultColors, ...Object.fromEntries(
162
- Object.entries(colors).map(([k, v]) => [k, v])
163
- ) } : defaultColors;
164
- const chipColor = colorMap[role] ?? "neutral";
165
- return /* @__PURE__ */ jsx4(Chip, { color: chipColor, variant: "soft", size: "sm", children: role });
166
- }
167
-
168
- // src/components/impersonation-banner.tsx
169
- import { useCurrentUser } from "@cfast/auth/client";
170
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
171
- function ImpersonationBanner({
172
- stopAction = "/admin/stop-impersonation"
173
- }) {
174
- const user = useCurrentUser();
175
- const Alert = useComponent("alert");
176
- const Button = useComponent("button");
177
- if (!user?.isImpersonating) {
178
- return null;
179
- }
180
- return /* @__PURE__ */ jsx5(Alert, { color: "warning", children: /* @__PURE__ */ jsxs2(
181
- "div",
182
- {
183
- style: {
184
- display: "flex",
185
- alignItems: "center",
186
- justifyContent: "center",
187
- gap: "12px"
188
- },
189
- children: [
190
- /* @__PURE__ */ jsx5("strong", { children: `Viewing as ${user.name} (${user.email})` }),
191
- /* @__PURE__ */ jsx5("form", { method: "post", action: stopAction, children: /* @__PURE__ */ jsx5(Button, { type: "submit", variant: "outlined", size: "sm", children: "Stop Impersonating" }) })
192
- ]
193
- }
194
- ) });
195
- }
196
-
197
- // src/components/data-table.tsx
198
- import { useState as useState2, useCallback as useCallback3 } from "react";
199
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
200
- function normalizeColumns(columns) {
201
- if (!columns) return [];
202
- return columns.map((col) => {
203
- if (typeof col === "string") {
204
- return {
205
- key: col,
206
- label: col.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim(),
207
- sortable: true
208
- };
209
- }
210
- return col;
211
- });
212
- }
213
- function DataTable({
214
- data,
215
- columns: columnsProp,
216
- selectable = false,
217
- selectedRows: externalSelectedRows,
218
- onSelectionChange,
219
- onRowClick,
220
- getRowId,
221
- emptyMessage = "No data"
222
- }) {
223
- const Table = useComponent("table");
224
- const TableHead = useComponent("tableHead");
225
- const TableBody = useComponent("tableBody");
226
- const TableRow = useComponent("tableRow");
227
- const TableCell = useComponent("tableCell");
228
- const columns = normalizeColumns(columnsProp);
229
- const [sortKey, setSortKey] = useState2(null);
230
- const [sortDir, setSortDir] = useState2("asc");
231
- const [internalSelected, setInternalSelected] = useState2(/* @__PURE__ */ new Set());
232
- const selectedSet = externalSelectedRows ? new Set(externalSelectedRows.map((r) => (getRowId ?? defaultGetId)(r))) : internalSelected;
233
- const handleSort = useCallback3((key) => {
234
- if (sortKey === key) {
235
- setSortDir((d) => d === "asc" ? "desc" : "asc");
236
- } else {
237
- setSortKey(key);
238
- setSortDir("asc");
239
- }
240
- }, [sortKey]);
241
- const toggleRow = useCallback3((id) => {
242
- if (onSelectionChange) {
243
- const row = data.items.find((r) => (getRowId ?? defaultGetId)(r) === id);
244
- if (!row) return;
245
- const current = externalSelectedRows ?? [];
246
- const isSelected = current.some((r) => (getRowId ?? defaultGetId)(r) === id);
247
- onSelectionChange(isSelected ? current.filter((r) => (getRowId ?? defaultGetId)(r) !== id) : [...current, row]);
248
- } else {
249
- setInternalSelected((prev) => {
250
- const next = new Set(prev);
251
- if (next.has(id)) next.delete(id);
252
- else next.add(id);
253
- return next;
254
- });
255
- }
256
- }, [data.items, externalSelectedRows, onSelectionChange, getRowId]);
257
- if (data.items.length === 0 && !data.isLoading) {
258
- return /* @__PURE__ */ jsx6("div", { style: { textAlign: "center", padding: "32px", color: "#666" }, children: emptyMessage });
259
- }
260
- return /* @__PURE__ */ jsxs3(Table, { hoverRow: true, children: [
261
- /* @__PURE__ */ jsx6(TableHead, { children: /* @__PURE__ */ jsxs3(TableRow, { children: [
262
- selectable ? /* @__PURE__ */ jsx6(TableCell, { header: true, children: "" }) : null,
263
- columns.map((col) => /* @__PURE__ */ jsx6(
264
- TableCell,
265
- {
266
- header: true,
267
- sortable: col.sortable !== false,
268
- sortDirection: sortKey === col.key ? sortDir : null,
269
- onSort: () => handleSort(col.key),
270
- children: col.label ?? col.key
271
- },
272
- col.key
273
- ))
274
- ] }) }),
275
- /* @__PURE__ */ jsx6(TableBody, { children: data.items.map((row) => {
276
- const id = (getRowId ?? defaultGetId)(row);
277
- const isSelected = selectedSet.has(id);
278
- return /* @__PURE__ */ jsxs3(
279
- TableRow,
280
- {
281
- selected: isSelected,
282
- onClick: onRowClick ? () => onRowClick(row) : void 0,
283
- children: [
284
- selectable ? /* @__PURE__ */ jsx6(TableCell, { children: /* @__PURE__ */ jsx6(
285
- "input",
286
- {
287
- type: "checkbox",
288
- checked: isSelected,
289
- onChange: () => toggleRow(id)
290
- }
291
- ) }) : null,
292
- columns.map((col) => {
293
- const value = row[col.key];
294
- return /* @__PURE__ */ jsx6(TableCell, { children: col.render ? col.render(value, row) : String(value ?? "") }, col.key);
295
- })
296
- ]
297
- },
298
- String(id)
299
- );
300
- }) })
301
- ] });
302
- }
303
- function defaultGetId(row) {
304
- return row.id;
305
- }
306
-
307
- // src/components/filter-bar.tsx
308
- import { useCallback as useCallback4 } from "react";
309
- import { useSearchParams, useNavigate, useLocation } from "react-router";
310
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
311
- function FilterBar({
312
- filters,
313
- searchable
314
- }) {
315
- const [searchParams] = useSearchParams();
316
- const navigate = useNavigate();
317
- const location = useLocation();
318
- const updateParam = useCallback4(
319
- (key, value) => {
320
- const params = new URLSearchParams(searchParams);
321
- if (value === null || value === "") {
322
- params.delete(key);
323
- } else {
324
- params.set(key, value);
325
- }
326
- params.delete("page");
327
- params.delete("cursor");
328
- navigate(`${location.pathname}?${params.toString()}`);
329
- },
330
- [searchParams, navigate, location.pathname]
331
- );
332
- return /* @__PURE__ */ jsxs4(
333
- "div",
334
- {
335
- style: {
336
- display: "flex",
337
- gap: "8px",
338
- flexWrap: "wrap",
339
- alignItems: "center",
340
- marginBottom: "16px"
341
- },
342
- children: [
343
- searchable && searchable.length > 0 ? /* @__PURE__ */ jsx7(
344
- "input",
345
- {
346
- type: "search",
347
- placeholder: `Search ${searchable.join(", ")}...`,
348
- value: searchParams.get("q") ?? "",
349
- onChange: (e) => updateParam("q", e.target.value || null),
350
- style: { padding: "6px 10px", border: "1px solid #ccc", borderRadius: "4px" }
351
- }
352
- ) : null,
353
- filters.map((filter) => /* @__PURE__ */ jsx7(
354
- FilterInput,
355
- {
356
- filter,
357
- value: searchParams.get(filter.column) ?? "",
358
- onChange: (value) => updateParam(filter.column, value || null)
359
- },
360
- filter.column
361
- ))
362
- ]
363
- }
364
- );
365
- }
366
- function FilterInput({
367
- filter,
368
- value,
369
- onChange
370
- }) {
371
- const label = filter.label ?? filter.column;
372
- switch (filter.type) {
373
- case "select":
374
- case "boolean":
375
- return /* @__PURE__ */ jsxs4(
376
- "select",
377
- {
378
- value,
379
- onChange: (e) => onChange(e.target.value),
380
- "aria-label": label,
381
- children: [
382
- /* @__PURE__ */ jsx7("option", { value: "", children: `All ${label}` }),
383
- (filter.options ?? []).map((opt) => /* @__PURE__ */ jsx7("option", { value: String(opt.value), children: opt.label }, String(opt.value)))
384
- ]
385
- }
386
- );
387
- case "text":
388
- default:
389
- return /* @__PURE__ */ jsx7(
390
- "input",
391
- {
392
- type: "text",
393
- placeholder: filter.placeholder ?? label,
394
- value,
395
- onChange: (e) => onChange(e.target.value),
396
- "aria-label": label,
397
- style: { padding: "6px 10px", border: "1px solid #ccc", borderRadius: "4px" }
398
- }
399
- );
400
- }
401
- }
402
-
403
- // src/components/bulk-action-bar.tsx
404
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
405
- function BulkActionBar({
406
- selectedCount,
407
- actions,
408
- onAction,
409
- onClearSelection
410
- }) {
411
- const Button = useComponent("button");
412
- if (selectedCount === 0) return null;
413
- return /* @__PURE__ */ jsxs5(
414
- "div",
415
- {
416
- style: {
417
- display: "flex",
418
- alignItems: "center",
419
- gap: "8px",
420
- padding: "8px 16px",
421
- backgroundColor: "#f0f4ff",
422
- borderRadius: "4px",
423
- marginBottom: "8px"
424
- },
425
- children: [
426
- /* @__PURE__ */ jsx8("span", { children: `${selectedCount} selected` }),
427
- actions.map((action) => {
428
- const Icon = action.icon;
429
- return /* @__PURE__ */ jsx8(
430
- Button,
431
- {
432
- onClick: () => onAction(action),
433
- variant: "soft",
434
- size: "sm",
435
- startDecorator: Icon ? /* @__PURE__ */ jsx8(Icon, { className: "bulk-action-icon" }) : void 0,
436
- children: action.label
437
- },
438
- action.label
439
- );
440
- }),
441
- /* @__PURE__ */ jsx8(
442
- Button,
443
- {
444
- onClick: onClearSelection,
445
- variant: "plain",
446
- size: "sm",
447
- children: "Clear"
448
- }
449
- )
450
- ]
451
- }
452
- );
453
- }
454
-
455
- // src/hooks/use-column-inference.ts
456
- import { useMemo } from "react";
457
- function useColumnInference(table, columns) {
458
- return useMemo(() => {
459
- if (!table) return [];
460
- const result = [];
461
- for (const [key, col] of Object.entries(table)) {
462
- if (columns && !columns.includes(key)) continue;
463
- if (!col || typeof col !== "object" || !("dataType" in col) || !("name" in col)) continue;
464
- const meta = col;
465
- const field = fieldForColumn(meta);
466
- const label = key.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
467
- result.push({
468
- key,
469
- label,
470
- sortable: true,
471
- field
472
- });
473
- }
474
- if (columns) {
475
- result.sort((a, b) => columns.indexOf(a.key) - columns.indexOf(b.key));
476
- }
477
- return result;
478
- }, [table, columns]);
479
- }
480
-
481
- // src/components/drop-zone.tsx
482
- import { useState as useState3, useCallback as useCallback5, useRef as useRef3 } from "react";
483
- import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
484
- function DropZone({
485
- upload,
486
- multiple = false,
487
- children
488
- }) {
489
- const DropZoneSlot = useComponent("dropZone");
490
- const [isDragOver, setIsDragOver] = useState3(false);
491
- const inputRef = useRef3(null);
492
- const handleFiles = useCallback5(
493
- (files) => {
494
- if (!files || files.length === 0) return;
495
- if (multiple) {
496
- for (let i = 0; i < files.length; i++) {
497
- upload.start(files[i]);
498
- }
499
- } else {
500
- upload.start(files[0]);
501
- }
502
- },
503
- [upload, multiple]
504
- );
505
- const handleDrop = useCallback5(
506
- (files) => {
507
- setIsDragOver(false);
508
- handleFiles(files);
509
- },
510
- [handleFiles]
511
- );
512
- const handleClick = useCallback5(() => {
513
- inputRef.current?.click();
514
- }, []);
515
- const handleDragOver = useCallback5((_e) => {
516
- setIsDragOver(true);
517
- }, []);
518
- const handleDragLeave = useCallback5(() => {
519
- setIsDragOver(false);
520
- }, []);
521
- const defaultContent = upload.isUploading ? `Uploading... ${upload.progress}%` : upload.error ?? upload.validationError ?? "Drop files here or click to browse";
522
- return /* @__PURE__ */ jsxs6("div", { children: [
523
- /* @__PURE__ */ jsx9(
524
- DropZoneSlot,
525
- {
526
- isDragOver,
527
- isInvalid: !!(upload.error || upload.validationError),
528
- onClick: handleClick,
529
- onDrop: handleDrop,
530
- onDragOver: handleDragOver,
531
- onDragLeave: handleDragLeave,
532
- accept: upload.accept,
533
- children: children ?? defaultContent
534
- }
535
- ),
536
- /* @__PURE__ */ jsx9(
537
- "input",
538
- {
539
- ref: inputRef,
540
- type: "file",
541
- accept: upload.accept,
542
- multiple,
543
- style: { display: "none" },
544
- onChange: (e) => handleFiles(e.target.files)
545
- }
546
- )
547
- ] });
548
- }
549
-
550
- // src/components/image-preview.tsx
551
- import { jsx as jsx10 } from "react/jsx-runtime";
552
- function ImagePreview({
553
- fileKey,
554
- src,
555
- getUrl,
556
- width = 200,
557
- height = 200,
558
- fallback,
559
- alt = "Image preview"
560
- }) {
561
- const resolvedSrc = src ?? (fileKey && getUrl ? getUrl(fileKey) : null);
562
- if (!resolvedSrc) {
563
- return fallback ? /* @__PURE__ */ jsx10("div", { children: fallback }) : /* @__PURE__ */ jsx10(
564
- "div",
565
- {
566
- style: {
567
- width,
568
- height,
569
- backgroundColor: "#f5f5f5",
570
- display: "flex",
571
- alignItems: "center",
572
- justifyContent: "center",
573
- borderRadius: "4px",
574
- color: "#999"
575
- },
576
- children: "No image"
577
- }
578
- );
579
- }
580
- return /* @__PURE__ */ jsx10(
581
- "img",
582
- {
583
- src: resolvedSrc,
584
- alt,
585
- style: {
586
- width,
587
- height,
588
- objectFit: "cover",
589
- borderRadius: "4px"
590
- }
591
- }
592
- );
593
- }
594
-
595
- // src/components/file-list.tsx
596
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
597
- function formatBytes(bytes) {
598
- if (bytes < 1024) return `${bytes} B`;
599
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
600
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
601
- }
602
- function FileList({
603
- files,
604
- onDownload
605
- }) {
606
- if (files.length === 0) {
607
- return /* @__PURE__ */ jsx11("div", { style: { color: "#999" }, children: "No files" });
608
- }
609
- return /* @__PURE__ */ jsx11(
610
- "ul",
611
- {
612
- style: { listStyle: "none", padding: 0, margin: 0 },
613
- "data-testid": "file-list",
614
- children: files.map((file) => /* @__PURE__ */ jsx11(
615
- FileListItem,
616
- {
617
- file,
618
- onDownload
619
- },
620
- file.key
621
- ))
622
- }
623
- );
624
- }
625
- function FileListItem({
626
- file,
627
- onDownload
628
- }) {
629
- return /* @__PURE__ */ jsxs7(
630
- "li",
631
- {
632
- style: {
633
- display: "flex",
634
- alignItems: "center",
635
- gap: "8px",
636
- padding: "8px 0",
637
- borderBottom: "1px solid #eee"
638
- },
639
- children: [
640
- /* @__PURE__ */ jsx11("span", { style: { flex: 1 }, children: file.name }),
641
- file.size != null ? /* @__PURE__ */ jsx11("span", { style: { color: "#666", fontSize: "0.85em" }, children: formatBytes(file.size) }) : null,
642
- onDownload ? /* @__PURE__ */ jsx11(
643
- "button",
644
- {
645
- onClick: () => onDownload(file),
646
- style: {
647
- background: "none",
648
- border: "none",
649
- cursor: "pointer",
650
- color: "#1976d2",
651
- textDecoration: "underline"
652
- },
653
- children: "Download"
654
- }
655
- ) : file.url ? /* @__PURE__ */ jsx11(
656
- "a",
657
- {
658
- href: file.url,
659
- download: file.name,
660
- style: { color: "#1976d2", textDecoration: "underline" },
661
- children: "Download"
662
- }
663
- ) : null
664
- ]
665
- }
666
- );
667
- }
668
-
669
- // src/components/page-container.tsx
670
- import { Fragment, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
671
- function PageContainer({
672
- title,
673
- breadcrumb,
674
- actions,
675
- tabs: _tabs,
676
- children
677
- }) {
678
- const PageContainerSlot = useComponent("pageContainer");
679
- const Breadcrumb = useComponent("breadcrumb");
680
- return /* @__PURE__ */ jsxs8(Fragment, { children: [
681
- breadcrumb && breadcrumb.length > 0 ? /* @__PURE__ */ jsx12(Breadcrumb, { items: breadcrumb }) : null,
682
- /* @__PURE__ */ jsx12(PageContainerSlot, { title, actions, children })
683
- ] });
684
- }
685
-
686
- // src/components/empty-state.tsx
687
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
688
- function EmptyState({
689
- title,
690
- description,
691
- createAction,
692
- createLabel = "Create",
693
- icon: Icon
694
- }) {
695
- const Button = useComponent("button");
696
- if (!createAction) {
697
- return /* @__PURE__ */ jsxs9("div", { style: { textAlign: "center", padding: "48px 16px" }, children: [
698
- Icon ? /* @__PURE__ */ jsx13(Icon, { className: "empty-state-icon" }) : null,
699
- /* @__PURE__ */ jsx13("h3", { style: { margin: "16px 0 8px" }, children: title }),
700
- description ? /* @__PURE__ */ jsx13("p", { style: { color: "#666" }, children: description }) : null
701
- ] });
702
- }
703
- return /* @__PURE__ */ jsx13(
704
- EmptyStateWithAction,
705
- {
706
- title,
707
- description,
708
- createAction,
709
- createLabel,
710
- icon: Icon,
711
- Button
712
- }
713
- );
714
- }
715
- function EmptyStateWithAction({
716
- title,
717
- description,
718
- createAction,
719
- createLabel,
720
- icon: Icon,
721
- Button
722
- }) {
723
- const status = useActionStatus(createAction);
724
- if (status.invisible) {
725
- return /* @__PURE__ */ jsx13("div", { style: { textAlign: "center", padding: "48px 16px" }, children: /* @__PURE__ */ jsx13("h3", { style: { margin: "16px 0 8px" }, children: "Nothing here yet" }) });
726
- }
727
- return /* @__PURE__ */ jsxs9("div", { style: { textAlign: "center", padding: "48px 16px" }, children: [
728
- Icon ? /* @__PURE__ */ jsx13(Icon, { className: "empty-state-icon" }) : null,
729
- /* @__PURE__ */ jsx13("h3", { style: { margin: "16px 0 8px" }, children: title }),
730
- description ? /* @__PURE__ */ jsx13("p", { style: { color: "#666" }, children: description }) : null,
731
- status.permitted ? /* @__PURE__ */ jsx13("div", { style: { marginTop: "16px" }, children: /* @__PURE__ */ jsx13(Button, { onClick: () => status.submit(), loading: status.pending, children: createLabel }) }) : null
732
- ] });
733
- }
734
-
735
- // src/components/list-view.tsx
736
- import { useState as useState4, useCallback as useCallback6 } from "react";
737
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
738
- function ListView({
739
- title,
740
- data,
741
- table: _table,
742
- columns,
743
- actions: _actions,
744
- filters,
745
- searchable,
746
- createAction,
747
- createLabel = "Create",
748
- selectable = false,
749
- bulkActions,
750
- breadcrumb
751
- }) {
752
- const [selectedRows, setSelectedRows] = useState4([]);
753
- const handleBulkAction = useCallback6(
754
- (action) => {
755
- if (action.handler) {
756
- action.handler(selectedRows);
757
- }
758
- },
759
- [selectedRows]
760
- );
761
- const clearSelection = useCallback6(() => {
762
- setSelectedRows([]);
763
- }, []);
764
- const createButton = createAction ? /* @__PURE__ */ jsx14(CreateButton, { action: createAction, label: createLabel }) : null;
765
- return /* @__PURE__ */ jsx14(PageContainer, { title, breadcrumb, actions: createButton, children: /* @__PURE__ */ jsxs10("div", { children: [
766
- filters && filters.length > 0 ? /* @__PURE__ */ jsx14(FilterBar, { filters, searchable }) : null,
767
- selectable && bulkActions && bulkActions.length > 0 ? /* @__PURE__ */ jsx14(
768
- BulkActionBar,
769
- {
770
- selectedCount: selectedRows.length,
771
- actions: bulkActions,
772
- onAction: handleBulkAction,
773
- onClearSelection: clearSelection
774
- }
775
- ) : null,
776
- data.items.length === 0 && !data.isLoading ? /* @__PURE__ */ jsx14(
777
- EmptyState,
778
- {
779
- title: `No ${title.toLowerCase()} found`,
780
- description: filters ? "Try adjusting your filters" : void 0,
781
- createAction,
782
- createLabel
783
- }
784
- ) : /* @__PURE__ */ jsx14(
785
- DataTable,
786
- {
787
- data,
788
- columns,
789
- selectable,
790
- selectedRows: selectable ? selectedRows : void 0,
791
- onSelectionChange: selectable ? (rows) => setSelectedRows(rows) : void 0
792
- }
793
- ),
794
- data.totalPages && data.totalPages > 1 && data.goToPage ? /* @__PURE__ */ jsxs10(
795
- "div",
796
- {
797
- style: {
798
- display: "flex",
799
- justifyContent: "center",
800
- gap: "8px",
801
- marginTop: "16px"
802
- },
803
- children: [
804
- /* @__PURE__ */ jsx14(
805
- "button",
806
- {
807
- disabled: data.currentPage === 1,
808
- onClick: () => data.goToPage?.(Math.max(1, (data.currentPage ?? 1) - 1)),
809
- children: "Previous"
810
- }
811
- ),
812
- /* @__PURE__ */ jsx14("span", { children: `Page ${data.currentPage ?? 1} of ${data.totalPages}` }),
813
- /* @__PURE__ */ jsx14(
814
- "button",
815
- {
816
- disabled: data.currentPage === data.totalPages,
817
- onClick: () => data.goToPage?.(
818
- Math.min(data.totalPages ?? 1, (data.currentPage ?? 1) + 1)
819
- ),
820
- children: "Next"
821
- }
822
- )
823
- ]
824
- }
825
- ) : null,
826
- data.hasMore && data.loadMore ? /* @__PURE__ */ jsx14("div", { style: { textAlign: "center", marginTop: "16px" }, children: /* @__PURE__ */ jsx14("button", { onClick: data.loadMore, children: "Load more" }) }) : null
827
- ] }) });
828
- }
829
- function CreateButton({ action, label }) {
830
- const status = useActionStatus(action);
831
- return /* @__PURE__ */ jsx14(ActionButton, { action: status, variant: "solid", color: "primary", children: label });
832
- }
833
-
834
- // src/components/detail-view.tsx
835
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
836
- function normalizeFields(fields) {
837
- if (!fields) return [];
838
- return fields.map((col) => {
839
- if (typeof col === "string") {
840
- return {
841
- key: col,
842
- label: col.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim()
843
- };
844
- }
845
- return col;
846
- });
847
- }
848
- function DetailView({
849
- title,
850
- table,
851
- record,
852
- fields: fieldsProp,
853
- exclude,
854
- breadcrumb
855
- }) {
856
- const fields = normalizeFields(fieldsProp);
857
- const displayFields = fields.length > 0 ? fields : inferFieldsFromRecord(record, exclude);
858
- return /* @__PURE__ */ jsx15(PageContainer, { title, breadcrumb, children: /* @__PURE__ */ jsx15(
859
- "div",
860
- {
861
- style: {
862
- display: "grid",
863
- gridTemplateColumns: "1fr 1fr",
864
- gap: "16px"
865
- },
866
- children: displayFields.map((field) => {
867
- const value = record[field.key];
868
- const FieldComponent = field.render ? null : resolveFieldComponent(field.key, table);
869
- return /* @__PURE__ */ jsxs11("div", { children: [
870
- /* @__PURE__ */ jsx15(
871
- "div",
872
- {
873
- style: {
874
- fontSize: "0.85em",
875
- color: "#666",
876
- marginBottom: "4px",
877
- fontWeight: 600
878
- },
879
- children: field.label ?? field.key
880
- }
881
- ),
882
- /* @__PURE__ */ jsx15("div", { children: field.render ? field.render(value, record) : FieldComponent ? /* @__PURE__ */ jsx15(FieldComponent, { value }) : String(value ?? "\u2014") })
883
- ] }, field.key);
884
- })
885
- }
886
- ) });
887
- }
888
- function inferFieldsFromRecord(record, exclude) {
889
- if (!record || typeof record !== "object") return [];
890
- return Object.keys(record).filter((key) => !exclude || !exclude.includes(key)).map((key) => ({
891
- key,
892
- label: key.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim()
893
- }));
894
- }
895
- function resolveFieldComponent(_key, table) {
896
- if (!table || typeof table !== "object") return null;
897
- const col = table[_key];
898
- if (!col || typeof col !== "object" || !("dataType" in col) || !("name" in col)) {
899
- return null;
900
- }
901
- const meta = col;
902
- return fieldForColumn(meta);
903
- }
904
-
905
- export {
906
- useConfirm,
907
- useActionToast,
908
- ActionButton,
909
- ConfirmProvider,
910
- FormStatus,
911
- RoleBadge,
912
- ImpersonationBanner,
913
- DataTable,
914
- FilterBar,
915
- BulkActionBar,
916
- useColumnInference,
917
- DropZone,
918
- ImagePreview,
919
- FileList,
920
- PageContainer,
921
- EmptyState,
922
- ListView,
923
- DetailView
924
- };