@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
package/dist/joy.js ADDED
@@ -0,0 +1,1150 @@
1
+ import {
2
+ PermissionGate,
3
+ ToastContext,
4
+ fieldForColumn,
5
+ getField,
6
+ getInitials,
7
+ getRecordId,
8
+ useActionStatus
9
+ } from "./chunk-RDTUEOLK.js";
10
+
11
+ // src/joy/action-button.tsx
12
+ import "react";
13
+ import JoyButton from "@mui/joy/Button";
14
+ import JoyTooltip from "@mui/joy/Tooltip";
15
+ import { jsx } from "react/jsx-runtime";
16
+ function ActionButton({
17
+ action,
18
+ children,
19
+ whenForbidden = "disable",
20
+ confirmation: _confirmation,
21
+ variant = "solid",
22
+ color = "primary",
23
+ size = "md",
24
+ ...buttonProps
25
+ }) {
26
+ if (action.invisible) {
27
+ return null;
28
+ }
29
+ if (!action.permitted && whenForbidden === "hide") {
30
+ return null;
31
+ }
32
+ const disabled = !action.permitted && whenForbidden === "disable";
33
+ const button = /* @__PURE__ */ jsx(
34
+ JoyButton,
35
+ {
36
+ ...buttonProps,
37
+ onClick: () => action.submit(),
38
+ disabled,
39
+ loading: action.pending,
40
+ variant,
41
+ color,
42
+ size,
43
+ children
44
+ }
45
+ );
46
+ if (disabled && action.reason) {
47
+ const wrapper = /* @__PURE__ */ jsx("span", { children: button });
48
+ return /* @__PURE__ */ jsx(JoyTooltip, { title: action.reason, children: wrapper });
49
+ }
50
+ return button;
51
+ }
52
+
53
+ // src/joy/confirm-dialog.tsx
54
+ import "react";
55
+ import Modal from "@mui/joy/Modal";
56
+ import ModalDialog from "@mui/joy/ModalDialog";
57
+ import Typography from "@mui/joy/Typography";
58
+ import Button from "@mui/joy/Button";
59
+ import Stack from "@mui/joy/Stack";
60
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
61
+ function ConfirmDialog({
62
+ open,
63
+ onClose,
64
+ onConfirm,
65
+ title,
66
+ description,
67
+ confirmLabel = "Confirm",
68
+ cancelLabel = "Cancel",
69
+ variant = "default"
70
+ }) {
71
+ const dialog = /* @__PURE__ */ jsxs(ModalDialog, { variant: "outlined", role: "alertdialog", sx: { maxWidth: 400 }, children: [
72
+ /* @__PURE__ */ jsx2(Typography, { level: "h4", children: title }),
73
+ description ? /* @__PURE__ */ jsx2(Typography, { level: "body-md", sx: { mt: 1 }, children: description }) : null,
74
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, justifyContent: "flex-end", sx: { mt: 2 }, children: [
75
+ /* @__PURE__ */ jsx2(Button, { variant: "plain", color: "neutral", onClick: onClose, children: cancelLabel }),
76
+ /* @__PURE__ */ jsx2(
77
+ Button,
78
+ {
79
+ variant: "solid",
80
+ color: variant === "danger" ? "danger" : "primary",
81
+ onClick: onConfirm,
82
+ children: confirmLabel
83
+ }
84
+ )
85
+ ] })
86
+ ] });
87
+ return /* @__PURE__ */ jsx2(Modal, { open, onClose, children: dialog });
88
+ }
89
+
90
+ // src/joy/toast-provider.tsx
91
+ import { useCallback } from "react";
92
+ import { toast, Toaster } from "sonner";
93
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
94
+ function ToastProvider({ children }) {
95
+ const show = useCallback((options) => {
96
+ const method = options.type ?? "info";
97
+ switch (method) {
98
+ case "success":
99
+ toast.success(options.message, { description: options.description, duration: options.duration });
100
+ break;
101
+ case "error":
102
+ toast.error(options.message, { description: options.description, duration: options.duration });
103
+ break;
104
+ case "warning":
105
+ toast.warning(options.message, { description: options.description, duration: options.duration });
106
+ break;
107
+ default:
108
+ toast.info(options.message, { description: options.description, duration: options.duration });
109
+ break;
110
+ }
111
+ }, []);
112
+ return /* @__PURE__ */ jsxs2(ToastContext.Provider, { value: { show }, children: [
113
+ children,
114
+ /* @__PURE__ */ jsx3(Toaster, { richColors: true, position: "bottom-right" })
115
+ ] });
116
+ }
117
+
118
+ // src/joy/form-status.tsx
119
+ import "react";
120
+ import Alert from "@mui/joy/Alert";
121
+ import Stack2 from "@mui/joy/Stack";
122
+ import { jsx as jsx4 } from "react/jsx-runtime";
123
+ function FormStatus({ data }) {
124
+ if (!data) return null;
125
+ const elements = [];
126
+ if (data.success) {
127
+ elements.push(
128
+ /* @__PURE__ */ jsx4(Alert, { color: "success", variant: "soft", children: data.success }, "success")
129
+ );
130
+ }
131
+ if (data.error) {
132
+ elements.push(
133
+ /* @__PURE__ */ jsx4(Alert, { color: "danger", variant: "soft", children: data.error }, "error")
134
+ );
135
+ }
136
+ if (data.fieldErrors) {
137
+ const errorMessages = Object.entries(data.fieldErrors).flatMap(
138
+ ([field, errors]) => errors.map((err) => `${field}: ${err}`)
139
+ );
140
+ if (errorMessages.length > 0) {
141
+ elements.push(
142
+ /* @__PURE__ */ jsx4(Alert, { color: "danger", variant: "soft", children: /* @__PURE__ */ jsx4("ul", { style: { margin: 0, paddingLeft: "16px" }, children: errorMessages.map((msg, i) => /* @__PURE__ */ jsx4("li", { children: msg }, i)) }) }, "field-errors")
143
+ );
144
+ }
145
+ }
146
+ if (elements.length === 0) return null;
147
+ return /* @__PURE__ */ jsx4(Stack2, { spacing: 1, children: elements });
148
+ }
149
+
150
+ // src/joy/empty-state.tsx
151
+ import "react";
152
+ import Typography2 from "@mui/joy/Typography";
153
+ import Button2 from "@mui/joy/Button";
154
+ import Stack3 from "@mui/joy/Stack";
155
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
156
+ function EmptyState({
157
+ title,
158
+ description,
159
+ createAction,
160
+ createLabel = "Create",
161
+ icon: Icon
162
+ }) {
163
+ if (!createAction) {
164
+ return /* @__PURE__ */ jsxs3(Stack3, { alignItems: "center", spacing: 2, sx: { py: 6, px: 2 }, children: [
165
+ Icon ? /* @__PURE__ */ jsx5(Icon, { className: "empty-state-icon" }) : null,
166
+ /* @__PURE__ */ jsx5(Typography2, { level: "h3", children: title }),
167
+ description ? /* @__PURE__ */ jsx5(Typography2, { level: "body-md", color: "neutral", children: description }) : null
168
+ ] });
169
+ }
170
+ return /* @__PURE__ */ jsx5(
171
+ EmptyStateWithAction,
172
+ {
173
+ title,
174
+ description,
175
+ createAction,
176
+ createLabel,
177
+ icon: Icon
178
+ }
179
+ );
180
+ }
181
+ function EmptyStateWithAction({
182
+ title,
183
+ description,
184
+ createAction,
185
+ createLabel,
186
+ icon: Icon
187
+ }) {
188
+ const status = useActionStatus(createAction);
189
+ if (status.invisible) {
190
+ return /* @__PURE__ */ jsx5(Stack3, { alignItems: "center", spacing: 2, sx: { py: 6, px: 2 }, children: /* @__PURE__ */ jsx5(Typography2, { level: "h3", children: "Nothing here yet" }) });
191
+ }
192
+ return /* @__PURE__ */ jsxs3(Stack3, { alignItems: "center", spacing: 2, sx: { py: 6, px: 2 }, children: [
193
+ Icon ? /* @__PURE__ */ jsx5(Icon, { className: "empty-state-icon" }) : null,
194
+ /* @__PURE__ */ jsx5(Typography2, { level: "h3", children: title }),
195
+ description ? /* @__PURE__ */ jsx5(Typography2, { level: "body-md", color: "neutral", children: description }) : null,
196
+ status.permitted ? /* @__PURE__ */ jsx5(Button2, { onClick: () => status.submit(), loading: status.pending, children: createLabel }) : null
197
+ ] });
198
+ }
199
+
200
+ // src/joy/navigation-progress.tsx
201
+ import "react";
202
+ import LinearProgress from "@mui/joy/LinearProgress";
203
+ import { useNavigation } from "react-router";
204
+ import { jsx as jsx6 } from "react/jsx-runtime";
205
+ function NavigationProgress() {
206
+ const navigation = useNavigation();
207
+ const isNavigating = navigation.state === "loading";
208
+ if (!isNavigating) {
209
+ return null;
210
+ }
211
+ return /* @__PURE__ */ jsx6(
212
+ LinearProgress,
213
+ {
214
+ sx: {
215
+ position: "fixed",
216
+ top: 0,
217
+ left: 0,
218
+ right: 0,
219
+ zIndex: 9999,
220
+ "--LinearProgress-radius": "0px",
221
+ "--LinearProgress-thickness": "3px"
222
+ }
223
+ }
224
+ );
225
+ }
226
+
227
+ // src/joy/page-container.tsx
228
+ import "react";
229
+ import Typography3 from "@mui/joy/Typography";
230
+ import Breadcrumbs from "@mui/joy/Breadcrumbs";
231
+ import { Link } from "react-router";
232
+ import Stack4 from "@mui/joy/Stack";
233
+ import { Fragment, jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
234
+ function PageContainer({
235
+ title,
236
+ breadcrumb,
237
+ actions,
238
+ tabs: _tabs,
239
+ children
240
+ }) {
241
+ return /* @__PURE__ */ jsxs4("div", { style: { padding: "16px 24px" }, children: [
242
+ breadcrumb && breadcrumb.length > 0 ? /* @__PURE__ */ jsx7(Breadcrumbs, { size: "sm", sx: { mb: 1, p: 0 }, children: breadcrumb.map(
243
+ (item, i) => item.to ? /* @__PURE__ */ jsx7(Link, { to: item.to, style: { textDecoration: "none", color: "inherit" }, children: item.label }, i) : /* @__PURE__ */ jsx7(Typography3, { fontSize: "sm", children: item.label }, i)
244
+ ) }) : null,
245
+ title || actions ? /* @__PURE__ */ jsxs4(Stack4, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { mb: 2 }, children: [
246
+ title ? /* @__PURE__ */ jsx7(Typography3, { level: "h2", children: title }) : null,
247
+ actions ? /* @__PURE__ */ jsx7(Fragment, { children: actions }) : null
248
+ ] }) : null,
249
+ children
250
+ ] });
251
+ }
252
+
253
+ // src/joy/app-shell.tsx
254
+ import "react";
255
+ import Sheet from "@mui/joy/Sheet";
256
+ import List from "@mui/joy/List";
257
+ import ListItem from "@mui/joy/ListItem";
258
+ import ListItemButton from "@mui/joy/ListItemButton";
259
+ import { Link as Link2 } from "react-router";
260
+ import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
261
+ var AnyListItemButton = ListItemButton;
262
+ var AnySheet = Sheet;
263
+ function AppShell({
264
+ children,
265
+ sidebar,
266
+ header
267
+ }) {
268
+ return /* @__PURE__ */ jsxs5("div", { style: { display: "flex", minHeight: "100vh" }, children: [
269
+ sidebar ?? null,
270
+ /* @__PURE__ */ jsxs5("div", { style: { flex: 1, display: "flex", flexDirection: "column" }, children: [
271
+ header ?? null,
272
+ /* @__PURE__ */ jsx8("main", { style: { flex: 1 }, children })
273
+ ] })
274
+ ] });
275
+ }
276
+ function AppShellSidebar({ items }) {
277
+ return /* @__PURE__ */ jsx8(
278
+ Sheet,
279
+ {
280
+ sx: {
281
+ width: 240,
282
+ borderRight: "1px solid",
283
+ borderColor: "divider",
284
+ p: 2
285
+ },
286
+ children: /* @__PURE__ */ jsx8(List, { size: "sm", children: items.map((item) => /* @__PURE__ */ jsx8(SidebarItem, { item }, item.to)) })
287
+ }
288
+ );
289
+ }
290
+ function SidebarItem({ item }) {
291
+ if (item.action) {
292
+ return /* @__PURE__ */ jsx8(PermissionFilteredItem, { item });
293
+ }
294
+ return /* @__PURE__ */ jsx8(ListItem, { children: /* @__PURE__ */ jsx8(AnyListItemButton, { component: Link2, to: item.to, children: item.label }) });
295
+ }
296
+ function PermissionFilteredItem({ item }) {
297
+ const status = useActionStatus(item.action);
298
+ if (status.invisible || !status.permitted) {
299
+ return null;
300
+ }
301
+ return /* @__PURE__ */ jsx8(ListItem, { children: /* @__PURE__ */ jsx8(AnyListItemButton, { component: Link2, to: item.to, children: item.label }) });
302
+ }
303
+ function AppShellHeader({
304
+ children,
305
+ userMenu
306
+ }) {
307
+ return /* @__PURE__ */ jsxs5(
308
+ AnySheet,
309
+ {
310
+ component: "header",
311
+ sx: {
312
+ display: "flex",
313
+ alignItems: "center",
314
+ justifyContent: "space-between",
315
+ px: 3,
316
+ py: 1.5,
317
+ borderBottom: "1px solid",
318
+ borderColor: "divider"
319
+ },
320
+ children: [
321
+ children ?? /* @__PURE__ */ jsx8(Fragment2, {}),
322
+ userMenu ?? null
323
+ ]
324
+ }
325
+ );
326
+ }
327
+ AppShell.Sidebar = AppShellSidebar;
328
+ AppShell.Header = AppShellHeader;
329
+
330
+ // src/joy/user-menu.tsx
331
+ import "react";
332
+ import Avatar from "@mui/joy/Avatar";
333
+ import Dropdown from "@mui/joy/Dropdown";
334
+ import Menu from "@mui/joy/Menu";
335
+ import MenuItem from "@mui/joy/MenuItem";
336
+ import MenuButton from "@mui/joy/MenuButton";
337
+ import IconButton from "@mui/joy/IconButton";
338
+ import { Link as Link3 } from "react-router";
339
+ import { useCurrentUser } from "@cfast/auth/client";
340
+
341
+ // src/joy/role-badge.tsx
342
+ import "react";
343
+ import Chip from "@mui/joy/Chip";
344
+ import { jsx as jsx9 } from "react/jsx-runtime";
345
+ var VALID_COLORS = /* @__PURE__ */ new Set(["primary", "neutral", "danger", "success", "warning"]);
346
+ var defaultColors = {
347
+ admin: "danger",
348
+ editor: "primary",
349
+ author: "success",
350
+ reader: "neutral"
351
+ };
352
+ function isColorPaletteProp(value) {
353
+ return VALID_COLORS.has(value);
354
+ }
355
+ function RoleBadge({ role, colors }) {
356
+ const rawColor = colors?.[role] ?? defaultColors[role] ?? "neutral";
357
+ const chipColor = isColorPaletteProp(rawColor) ? rawColor : "neutral";
358
+ return /* @__PURE__ */ jsx9(Chip, { size: "sm", variant: "soft", color: chipColor, children: role });
359
+ }
360
+
361
+ // src/joy/user-menu.tsx
362
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
363
+ var AnyMenuItem = MenuItem;
364
+ function UserMenu({
365
+ links = [],
366
+ onSignOut
367
+ }) {
368
+ const user = useCurrentUser();
369
+ if (!user) {
370
+ return null;
371
+ }
372
+ return /* @__PURE__ */ jsxs6(Dropdown, { children: [
373
+ /* @__PURE__ */ jsx10(MenuButton, { slots: { root: IconButton }, slotProps: { root: { variant: "plain", size: "sm" } }, children: /* @__PURE__ */ jsx10(Avatar, { src: user.avatarUrl ?? void 0, size: "sm", children: getInitials(user.name) }) }),
374
+ /* @__PURE__ */ jsxs6(Menu, { placement: "bottom-end", size: "sm", children: [
375
+ /* @__PURE__ */ jsx10(MenuItem, { disabled: true, children: /* @__PURE__ */ jsxs6("div", { children: [
376
+ /* @__PURE__ */ jsx10("strong", { children: user.name }),
377
+ /* @__PURE__ */ jsx10("br", {}),
378
+ /* @__PURE__ */ jsx10("small", { children: user.email })
379
+ ] }) }),
380
+ user.roles.length > 0 ? /* @__PURE__ */ jsx10(MenuItem, { disabled: true, children: /* @__PURE__ */ jsx10("div", { style: { display: "flex", gap: "4px" }, children: user.roles.map((role) => /* @__PURE__ */ jsx10(RoleBadge, { role }, role)) }) }) : null,
381
+ links.map(
382
+ (link) => link.action ? /* @__PURE__ */ jsx10(PermissionFilteredLink, { link }, link.to) : /* @__PURE__ */ jsx10(AnyMenuItem, { component: Link3, to: link.to, children: link.label }, link.to)
383
+ ),
384
+ onSignOut ? /* @__PURE__ */ jsx10(MenuItem, { onClick: onSignOut, children: "Sign out" }) : null
385
+ ] })
386
+ ] });
387
+ }
388
+ function PermissionFilteredLink({ link }) {
389
+ const status = useActionStatus(link.action);
390
+ if (status.invisible || !status.permitted) {
391
+ return null;
392
+ }
393
+ return /* @__PURE__ */ jsx10(AnyMenuItem, { component: Link3, to: link.to, children: link.label });
394
+ }
395
+
396
+ // src/joy/data-table.tsx
397
+ import { useState, useCallback as useCallback2 } from "react";
398
+ import JoyTable from "@mui/joy/Table";
399
+ import JoySheet from "@mui/joy/Sheet";
400
+ import JoyCheckbox from "@mui/joy/Checkbox";
401
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
402
+ function normalizeColumns(columns) {
403
+ if (!columns) return [];
404
+ return columns.map((col) => {
405
+ if (typeof col === "string") {
406
+ return {
407
+ key: col,
408
+ label: col.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim(),
409
+ sortable: true
410
+ };
411
+ }
412
+ return col;
413
+ });
414
+ }
415
+ function DataTable({
416
+ data,
417
+ columns: columnsProp,
418
+ selectable = false,
419
+ selectedRows: externalSelectedRows,
420
+ onSelectionChange,
421
+ onRowClick,
422
+ getRowId,
423
+ emptyMessage = "No data"
424
+ }) {
425
+ const columns = normalizeColumns(columnsProp);
426
+ const [sortKey, setSortKey] = useState(null);
427
+ const [sortDir, setSortDir] = useState("asc");
428
+ const [internalSelected, setInternalSelected] = useState(/* @__PURE__ */ new Set());
429
+ const getId = getRowId ?? defaultGetId;
430
+ const selectedSet = externalSelectedRows ? new Set(externalSelectedRows.map((r) => getId(r))) : internalSelected;
431
+ const handleSort = useCallback2((key) => {
432
+ if (sortKey === key) {
433
+ setSortDir((d) => d === "asc" ? "desc" : "asc");
434
+ } else {
435
+ setSortKey(key);
436
+ setSortDir("asc");
437
+ }
438
+ }, [sortKey]);
439
+ const toggleRow = useCallback2((id) => {
440
+ if (onSelectionChange) {
441
+ const row = data.items.find((r) => getId(r) === id);
442
+ if (!row) return;
443
+ const current = externalSelectedRows ?? [];
444
+ const isSelected = current.some((r) => getId(r) === id);
445
+ onSelectionChange(isSelected ? current.filter((r) => getId(r) !== id) : [...current, row]);
446
+ } else {
447
+ setInternalSelected((prev) => {
448
+ const next = new Set(prev);
449
+ if (next.has(id)) next.delete(id);
450
+ else next.add(id);
451
+ return next;
452
+ });
453
+ }
454
+ }, [data.items, externalSelectedRows, onSelectionChange, getId]);
455
+ if (data.items.length === 0 && !data.isLoading) {
456
+ return /* @__PURE__ */ jsx11("div", { style: { textAlign: "center", padding: "32px", color: "#666" }, children: emptyMessage });
457
+ }
458
+ return /* @__PURE__ */ jsx11(JoySheet, { variant: "outlined", sx: { borderRadius: "sm", overflow: "auto" }, children: /* @__PURE__ */ jsxs7(JoyTable, { hoverRow: true, sx: { "& th": { fontWeight: "lg" } }, children: [
459
+ /* @__PURE__ */ jsx11("thead", { children: /* @__PURE__ */ jsxs7("tr", { children: [
460
+ selectable ? /* @__PURE__ */ jsx11("th", { style: { width: 40 } }) : null,
461
+ columns.map((col) => /* @__PURE__ */ jsx11("th", { children: col.sortable !== false ? /* @__PURE__ */ jsxs7(
462
+ "button",
463
+ {
464
+ onClick: () => handleSort(col.key),
465
+ style: {
466
+ background: "none",
467
+ border: "none",
468
+ cursor: "pointer",
469
+ fontWeight: "bold",
470
+ font: "inherit",
471
+ padding: 0,
472
+ color: "inherit"
473
+ },
474
+ children: [
475
+ col.label ?? col.key,
476
+ sortKey === col.key ? sortDir === "asc" ? " \u2191" : " \u2193" : null
477
+ ]
478
+ }
479
+ ) : col.label ?? col.key }, col.key))
480
+ ] }) }),
481
+ /* @__PURE__ */ jsx11("tbody", { children: data.items.map((row) => {
482
+ const id = getId(row);
483
+ const isSelected = selectedSet.has(id);
484
+ return /* @__PURE__ */ jsxs7(
485
+ "tr",
486
+ {
487
+ onClick: onRowClick ? () => onRowClick(row) : void 0,
488
+ style: onRowClick ? { cursor: "pointer" } : void 0,
489
+ children: [
490
+ selectable ? /* @__PURE__ */ jsx11("td", { children: /* @__PURE__ */ jsx11(
491
+ JoyCheckbox,
492
+ {
493
+ checked: isSelected,
494
+ onChange: () => toggleRow(id),
495
+ size: "sm"
496
+ }
497
+ ) }) : null,
498
+ columns.map((col) => {
499
+ const value = getField(row, col.key);
500
+ return /* @__PURE__ */ jsx11("td", { children: col.render ? col.render(value, row) : String(value ?? "") }, col.key);
501
+ })
502
+ ]
503
+ },
504
+ String(id)
505
+ );
506
+ }) })
507
+ ] }) });
508
+ }
509
+ function defaultGetId(row) {
510
+ return getRecordId(row);
511
+ }
512
+
513
+ // src/joy/filter-bar.tsx
514
+ import { useCallback as useCallback3 } from "react";
515
+ import { useSearchParams, useNavigate, useLocation } from "react-router";
516
+ import JoyInput from "@mui/joy/Input";
517
+ import JoySelect from "@mui/joy/Select";
518
+ import JoyOption from "@mui/joy/Option";
519
+ import JoyStack from "@mui/joy/Stack";
520
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
521
+ function FilterBar({
522
+ filters,
523
+ searchable
524
+ }) {
525
+ const [searchParams] = useSearchParams();
526
+ const navigate = useNavigate();
527
+ const location = useLocation();
528
+ const updateParam = useCallback3(
529
+ (key, value) => {
530
+ const params = new URLSearchParams(searchParams);
531
+ if (value === null || value === "") {
532
+ params.delete(key);
533
+ } else {
534
+ params.set(key, value);
535
+ }
536
+ params.delete("page");
537
+ params.delete("cursor");
538
+ navigate(`${location.pathname}?${params.toString()}`);
539
+ },
540
+ [searchParams, navigate, location.pathname]
541
+ );
542
+ return /* @__PURE__ */ jsxs8(JoyStack, { direction: "row", spacing: 1, flexWrap: "wrap", alignItems: "center", sx: { mb: 2 }, children: [
543
+ searchable && searchable.length > 0 ? /* @__PURE__ */ jsx12(
544
+ JoyInput,
545
+ {
546
+ type: "search",
547
+ placeholder: `Search ${searchable.join(", ")}...`,
548
+ value: searchParams.get("q") ?? "",
549
+ onChange: (e) => updateParam("q", e.target.value || null),
550
+ size: "sm",
551
+ sx: { minWidth: 200 }
552
+ }
553
+ ) : null,
554
+ filters.map((filter) => /* @__PURE__ */ jsx12(
555
+ JoyFilterInput,
556
+ {
557
+ filter,
558
+ value: searchParams.get(filter.column) ?? "",
559
+ onChange: (value) => updateParam(filter.column, value || null)
560
+ },
561
+ filter.column
562
+ ))
563
+ ] });
564
+ }
565
+ function JoyFilterInput({
566
+ filter,
567
+ value,
568
+ onChange
569
+ }) {
570
+ const label = filter.label ?? filter.column;
571
+ switch (filter.type) {
572
+ case "select":
573
+ case "boolean":
574
+ return /* @__PURE__ */ jsx12(
575
+ JoySelect,
576
+ {
577
+ value: value || null,
578
+ onChange: (_e, newValue) => onChange(newValue ? String(newValue) : ""),
579
+ placeholder: `All ${label}`,
580
+ size: "sm",
581
+ sx: { minWidth: 140 },
582
+ slotProps: { button: { "aria-label": label } },
583
+ children: (filter.options ?? []).map((opt) => /* @__PURE__ */ jsx12(JoyOption, { value: String(opt.value), children: opt.label }, String(opt.value)))
584
+ }
585
+ );
586
+ case "text":
587
+ default:
588
+ return /* @__PURE__ */ jsx12(
589
+ JoyInput,
590
+ {
591
+ placeholder: filter.placeholder ?? label,
592
+ value,
593
+ onChange: (e) => onChange(e.target.value),
594
+ size: "sm",
595
+ slotProps: { input: { "aria-label": label } }
596
+ }
597
+ );
598
+ }
599
+ }
600
+
601
+ // src/joy/bulk-action-bar.tsx
602
+ import "react";
603
+ import JoySheet2 from "@mui/joy/Sheet";
604
+ import JoyStack2 from "@mui/joy/Stack";
605
+ import JoyButton2 from "@mui/joy/Button";
606
+ import JoyTypography from "@mui/joy/Typography";
607
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
608
+ function BulkActionBar({
609
+ selectedCount,
610
+ actions,
611
+ onAction,
612
+ onClearSelection
613
+ }) {
614
+ if (selectedCount === 0) return null;
615
+ return /* @__PURE__ */ jsxs9(
616
+ JoySheet2,
617
+ {
618
+ variant: "soft",
619
+ color: "primary",
620
+ sx: {
621
+ p: 1,
622
+ px: 2,
623
+ borderRadius: "sm",
624
+ mb: 1,
625
+ display: "flex",
626
+ alignItems: "center",
627
+ gap: 1
628
+ },
629
+ children: [
630
+ /* @__PURE__ */ jsx13(JoyTypography, { level: "body-sm", fontWeight: "lg", children: `${selectedCount} selected` }),
631
+ /* @__PURE__ */ jsxs9(JoyStack2, { direction: "row", spacing: 1, sx: { ml: "auto" }, children: [
632
+ actions.map((action) => {
633
+ const Icon = action.icon;
634
+ return /* @__PURE__ */ jsx13(
635
+ JoyButton2,
636
+ {
637
+ size: "sm",
638
+ variant: "soft",
639
+ onClick: () => onAction(action),
640
+ startDecorator: Icon ? /* @__PURE__ */ jsx13(Icon, { className: "bulk-action-icon" }) : void 0,
641
+ children: action.label
642
+ },
643
+ action.label
644
+ );
645
+ }),
646
+ /* @__PURE__ */ jsx13(
647
+ JoyButton2,
648
+ {
649
+ size: "sm",
650
+ variant: "plain",
651
+ color: "neutral",
652
+ onClick: onClearSelection,
653
+ children: "Clear selection"
654
+ }
655
+ )
656
+ ] })
657
+ ]
658
+ }
659
+ );
660
+ }
661
+
662
+ // src/joy/drop-zone.tsx
663
+ import { useState as useState2, useCallback as useCallback4, useRef } from "react";
664
+ import JoySheet3 from "@mui/joy/Sheet";
665
+ import JoyTypography2 from "@mui/joy/Typography";
666
+ import JoyLinearProgress from "@mui/joy/LinearProgress";
667
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
668
+ function DropZone({
669
+ upload,
670
+ multiple = false,
671
+ children
672
+ }) {
673
+ const [isDragOver, setIsDragOver] = useState2(false);
674
+ const inputRef = useRef(null);
675
+ const handleFiles = useCallback4(
676
+ (files) => {
677
+ if (!files || files.length === 0) return;
678
+ if (multiple) {
679
+ for (let i = 0; i < files.length; i++) {
680
+ upload.start(files[i]);
681
+ }
682
+ } else {
683
+ upload.start(files[0]);
684
+ }
685
+ },
686
+ [upload, multiple]
687
+ );
688
+ const handleDrop = useCallback4(
689
+ (e) => {
690
+ e.preventDefault();
691
+ setIsDragOver(false);
692
+ handleFiles(e.dataTransfer.files);
693
+ },
694
+ [handleFiles]
695
+ );
696
+ const handleDragOver = useCallback4((e) => {
697
+ e.preventDefault();
698
+ setIsDragOver(true);
699
+ }, []);
700
+ const handleDragLeave = useCallback4(() => {
701
+ setIsDragOver(false);
702
+ }, []);
703
+ const handleClick = useCallback4(() => {
704
+ inputRef.current?.click();
705
+ }, []);
706
+ const hasError = !!(upload.error || upload.validationError);
707
+ return /* @__PURE__ */ jsxs10("div", { children: [
708
+ /* @__PURE__ */ jsx14(
709
+ JoySheet3,
710
+ {
711
+ variant: "outlined",
712
+ sx: {
713
+ borderStyle: "dashed",
714
+ borderWidth: 2,
715
+ borderColor: hasError ? "danger.400" : isDragOver ? "primary.400" : "neutral.outlinedBorder",
716
+ borderRadius: "md",
717
+ p: 4,
718
+ textAlign: "center",
719
+ cursor: "pointer",
720
+ transition: "border-color 0.2s",
721
+ "&:hover": { borderColor: "primary.300" }
722
+ },
723
+ onClick: handleClick,
724
+ onDrop: handleDrop,
725
+ onDragOver: handleDragOver,
726
+ onDragLeave: handleDragLeave,
727
+ children: children ?? /* @__PURE__ */ jsx14("div", { children: upload.isUploading ? /* @__PURE__ */ jsxs10("div", { children: [
728
+ /* @__PURE__ */ jsx14(JoyTypography2, { level: "body-sm", children: `Uploading... ${upload.progress}%` }),
729
+ /* @__PURE__ */ jsx14(
730
+ JoyLinearProgress,
731
+ {
732
+ determinate: true,
733
+ value: upload.progress,
734
+ sx: { mt: 1 }
735
+ }
736
+ )
737
+ ] }) : hasError ? /* @__PURE__ */ jsx14(JoyTypography2, { color: "danger", level: "body-sm", children: upload.error ?? upload.validationError }) : /* @__PURE__ */ jsx14(JoyTypography2, { level: "body-sm", color: "neutral", children: "Drop files here or click to browse" }) })
738
+ }
739
+ ),
740
+ /* @__PURE__ */ jsx14(
741
+ "input",
742
+ {
743
+ ref: inputRef,
744
+ type: "file",
745
+ accept: upload.accept,
746
+ multiple,
747
+ style: { display: "none" },
748
+ onChange: (e) => handleFiles(e.target.files)
749
+ }
750
+ )
751
+ ] });
752
+ }
753
+
754
+ // src/joy/image-preview.tsx
755
+ import "react";
756
+ import JoyAspectRatio from "@mui/joy/AspectRatio";
757
+ import JoyTypography3 from "@mui/joy/Typography";
758
+ import { jsx as jsx15 } from "react/jsx-runtime";
759
+ function ImagePreview({
760
+ fileKey,
761
+ src,
762
+ getUrl,
763
+ width = 200,
764
+ height = 200,
765
+ fallback,
766
+ alt = "Image preview"
767
+ }) {
768
+ const resolvedSrc = src ?? (fileKey && getUrl ? getUrl(fileKey) : null);
769
+ if (!resolvedSrc) {
770
+ return fallback ? /* @__PURE__ */ jsx15("div", { children: fallback }) : /* @__PURE__ */ jsx15(
771
+ JoyAspectRatio,
772
+ {
773
+ ratio: width / height,
774
+ sx: { width, borderRadius: "sm", bgcolor: "neutral.softBg" },
775
+ children: /* @__PURE__ */ jsx15(JoyTypography3, { level: "body-sm", color: "neutral", children: "No image" })
776
+ }
777
+ );
778
+ }
779
+ return /* @__PURE__ */ jsx15(
780
+ JoyAspectRatio,
781
+ {
782
+ ratio: width / height,
783
+ sx: { width, borderRadius: "sm", overflow: "hidden" },
784
+ children: /* @__PURE__ */ jsx15(
785
+ "img",
786
+ {
787
+ src: resolvedSrc,
788
+ alt,
789
+ style: { objectFit: "cover", width: "100%", height: "100%" }
790
+ }
791
+ )
792
+ }
793
+ );
794
+ }
795
+
796
+ // src/joy/file-list.tsx
797
+ import "react";
798
+ import JoyList from "@mui/joy/List";
799
+ import JoyListItem from "@mui/joy/ListItem";
800
+ import JoyListItemContent from "@mui/joy/ListItemContent";
801
+ import JoyTypography4 from "@mui/joy/Typography";
802
+ import JoyButton3 from "@mui/joy/Button";
803
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
804
+ function formatBytes(bytes) {
805
+ if (bytes < 1024) return `${bytes} B`;
806
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
807
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
808
+ }
809
+ function FileList({
810
+ files,
811
+ onDownload
812
+ }) {
813
+ if (files.length === 0) {
814
+ return /* @__PURE__ */ jsx16(JoyTypography4, { level: "body-sm", color: "neutral", children: "No files" });
815
+ }
816
+ return /* @__PURE__ */ jsx16(JoyList, { size: "sm", children: files.map((file) => /* @__PURE__ */ jsxs11(JoyListItem, { children: [
817
+ /* @__PURE__ */ jsxs11(JoyListItemContent, { children: [
818
+ /* @__PURE__ */ jsx16(JoyTypography4, { level: "body-sm", children: file.name }),
819
+ file.size != null ? /* @__PURE__ */ jsx16(JoyTypography4, { level: "body-xs", color: "neutral", children: formatBytes(file.size) }) : null
820
+ ] }),
821
+ onDownload || file.url ? /* @__PURE__ */ jsx16(
822
+ JoyButton3,
823
+ {
824
+ size: "sm",
825
+ variant: "plain",
826
+ onClick: onDownload ? () => onDownload(file) : void 0,
827
+ children: "\u2193"
828
+ }
829
+ ) : null
830
+ ] }, file.key)) });
831
+ }
832
+
833
+ // src/joy/list-view.tsx
834
+ import { useState as useState3, useCallback as useCallback5 } from "react";
835
+ import JoyButton4 from "@mui/joy/Button";
836
+ import JoyStack3 from "@mui/joy/Stack";
837
+ import JoyTypography5 from "@mui/joy/Typography";
838
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
839
+ function ListView({
840
+ title,
841
+ data,
842
+ table: _table,
843
+ columns,
844
+ actions: _actions,
845
+ filters,
846
+ searchable,
847
+ createAction,
848
+ createLabel = "Create",
849
+ selectable = false,
850
+ bulkActions,
851
+ breadcrumb
852
+ }) {
853
+ const [selectedRows, setSelectedRows] = useState3([]);
854
+ const handleBulkAction = useCallback5(
855
+ (action) => {
856
+ if (action.handler) {
857
+ action.handler(selectedRows);
858
+ }
859
+ },
860
+ [selectedRows]
861
+ );
862
+ const clearSelection = useCallback5(() => {
863
+ setSelectedRows([]);
864
+ }, []);
865
+ const createButton = createAction ? /* @__PURE__ */ jsx17(CreateButton, { action: createAction, label: createLabel }) : null;
866
+ return /* @__PURE__ */ jsx17(PageContainer, { title, breadcrumb, actions: createButton, children: /* @__PURE__ */ jsxs12(JoyStack3, { spacing: 2, children: [
867
+ filters && filters.length > 0 ? /* @__PURE__ */ jsx17(FilterBar, { filters, searchable }) : null,
868
+ selectable && bulkActions && bulkActions.length > 0 ? /* @__PURE__ */ jsx17(
869
+ BulkActionBar,
870
+ {
871
+ selectedCount: selectedRows.length,
872
+ actions: bulkActions,
873
+ onAction: handleBulkAction,
874
+ onClearSelection: clearSelection
875
+ }
876
+ ) : null,
877
+ data.items.length === 0 && !data.isLoading ? /* @__PURE__ */ jsx17(
878
+ EmptyState,
879
+ {
880
+ title: `No ${title.toLowerCase()} found`,
881
+ description: filters ? "Try adjusting your filters" : void 0,
882
+ createAction,
883
+ createLabel
884
+ }
885
+ ) : /* @__PURE__ */ jsx17(
886
+ DataTable,
887
+ {
888
+ data,
889
+ columns,
890
+ selectable,
891
+ selectedRows: selectable ? selectedRows : void 0,
892
+ onSelectionChange: selectable ? (rows) => setSelectedRows(rows) : void 0
893
+ }
894
+ ),
895
+ data.totalPages && data.totalPages > 1 && data.goToPage ? /* @__PURE__ */ jsxs12(JoyStack3, { direction: "row", justifyContent: "center", alignItems: "center", spacing: 2, children: [
896
+ /* @__PURE__ */ jsx17(
897
+ JoyButton4,
898
+ {
899
+ size: "sm",
900
+ variant: "outlined",
901
+ disabled: data.currentPage === 1,
902
+ onClick: () => data.goToPage?.(Math.max(1, (data.currentPage ?? 1) - 1)),
903
+ children: "Previous"
904
+ }
905
+ ),
906
+ /* @__PURE__ */ jsx17(JoyTypography5, { level: "body-sm", children: `Page ${data.currentPage ?? 1} of ${data.totalPages}` }),
907
+ /* @__PURE__ */ jsx17(
908
+ JoyButton4,
909
+ {
910
+ size: "sm",
911
+ variant: "outlined",
912
+ disabled: data.currentPage === data.totalPages,
913
+ onClick: () => data.goToPage?.(Math.min(data.totalPages ?? 1, (data.currentPage ?? 1) + 1)),
914
+ children: "Next"
915
+ }
916
+ )
917
+ ] }) : null,
918
+ data.hasMore && data.loadMore ? /* @__PURE__ */ jsx17(JoyStack3, { alignItems: "center", children: /* @__PURE__ */ jsx17(JoyButton4, { variant: "soft", onClick: data.loadMore, children: "Load more" }) }) : null
919
+ ] }) });
920
+ }
921
+ function CreateButton({ action, label }) {
922
+ const status = useActionStatus(action);
923
+ return /* @__PURE__ */ jsx17(ActionButton, { action: status, variant: "solid", color: "primary", children: label });
924
+ }
925
+
926
+ // src/joy/detail-view.tsx
927
+ import JoyGrid from "@mui/joy/Grid";
928
+ import JoyTypography6 from "@mui/joy/Typography";
929
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
930
+ function normalizeFields(fields) {
931
+ if (!fields) return [];
932
+ return fields.map((col) => {
933
+ if (typeof col === "string") {
934
+ return {
935
+ key: col,
936
+ label: col.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim()
937
+ };
938
+ }
939
+ return col;
940
+ });
941
+ }
942
+ function DetailView({
943
+ title,
944
+ table,
945
+ record,
946
+ fields: fieldsProp,
947
+ exclude,
948
+ breadcrumb
949
+ }) {
950
+ const fields = normalizeFields(fieldsProp);
951
+ const displayFields = fields.length > 0 ? fields : inferFieldsFromRecord(record, exclude);
952
+ return /* @__PURE__ */ jsx18(PageContainer, { title, breadcrumb, children: /* @__PURE__ */ jsx18(JoyGrid, { container: true, spacing: 2, children: displayFields.map((field) => {
953
+ const value = getField(record, field.key);
954
+ const FieldComponent = field.render ? null : resolveFieldComponent(field.key, table);
955
+ return /* @__PURE__ */ jsxs13(JoyGrid, { xs: 12, md: 6, children: [
956
+ /* @__PURE__ */ jsx18(
957
+ JoyTypography6,
958
+ {
959
+ level: "body-xs",
960
+ textTransform: "uppercase",
961
+ fontWeight: "lg",
962
+ mb: 0.5,
963
+ children: field.label ?? field.key
964
+ }
965
+ ),
966
+ /* @__PURE__ */ jsx18("div", { children: field.render ? field.render(value, record) : FieldComponent ? /* @__PURE__ */ jsx18(FieldComponent, { value }) : String(value ?? "\u2014") })
967
+ ] }, field.key);
968
+ }) }) });
969
+ }
970
+ function inferFieldsFromRecord(record, exclude) {
971
+ if (!record || typeof record !== "object") return [];
972
+ return Object.keys(record).filter((key) => !exclude || !exclude.includes(key)).map((key) => ({
973
+ key,
974
+ label: key.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim()
975
+ }));
976
+ }
977
+ function resolveFieldComponent(_key, table) {
978
+ if (!table || typeof table !== "object") return null;
979
+ const col = getField(table, _key);
980
+ if (!col || typeof col !== "object" || !("dataType" in col) || typeof col.dataType !== "string" || !("name" in col) || typeof col.name !== "string") {
981
+ return null;
982
+ }
983
+ return fieldForColumn({ dataType: col.dataType, name: col.name });
984
+ }
985
+
986
+ // src/joy/avatar-with-initials.tsx
987
+ import "react";
988
+ import Avatar2 from "@mui/joy/Avatar";
989
+ import { jsx as jsx19 } from "react/jsx-runtime";
990
+ function AvatarWithInitials({
991
+ src,
992
+ name,
993
+ size = "md"
994
+ }) {
995
+ return /* @__PURE__ */ jsx19(Avatar2, { src: src ?? void 0, alt: name, size, children: getInitials(name) });
996
+ }
997
+
998
+ // src/joy/impersonation-banner.tsx
999
+ import "react";
1000
+ import Sheet2 from "@mui/joy/Sheet";
1001
+ import Typography4 from "@mui/joy/Typography";
1002
+ import Button3 from "@mui/joy/Button";
1003
+ import Stack5 from "@mui/joy/Stack";
1004
+ import { useCurrentUser as useCurrentUser2 } from "@cfast/auth/client";
1005
+ import { jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
1006
+ function ImpersonationBanner({
1007
+ stopAction = "/admin/stop-impersonation"
1008
+ }) {
1009
+ const user = useCurrentUser2();
1010
+ if (!user?.isImpersonating) {
1011
+ return null;
1012
+ }
1013
+ return /* @__PURE__ */ jsx20(
1014
+ Sheet2,
1015
+ {
1016
+ color: "warning",
1017
+ variant: "solid",
1018
+ sx: {
1019
+ position: "sticky",
1020
+ top: 0,
1021
+ zIndex: 1100,
1022
+ py: 1,
1023
+ px: 3
1024
+ },
1025
+ children: /* @__PURE__ */ jsxs14(Stack5, { direction: "row", spacing: 2, alignItems: "center", justifyContent: "center", children: [
1026
+ /* @__PURE__ */ jsx20(Typography4, { level: "body-sm", sx: { fontWeight: "bold" }, children: `Viewing as ${user.name} (${user.email})` }),
1027
+ /* @__PURE__ */ jsx20("form", { method: "post", action: stopAction, children: /* @__PURE__ */ jsx20(Button3, { size: "sm", variant: "outlined", color: "warning", type: "submit", children: "Stop Impersonating" }) })
1028
+ ] })
1029
+ }
1030
+ );
1031
+ }
1032
+
1033
+ // src/joy/login-components.tsx
1034
+ import Box from "@mui/joy/Box";
1035
+ import Card from "@mui/joy/Card";
1036
+ import Input from "@mui/joy/Input";
1037
+ import Button4 from "@mui/joy/Button";
1038
+ import Alert2 from "@mui/joy/Alert";
1039
+ import Divider from "@mui/joy/Divider";
1040
+ import { Fragment as Fragment3, jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
1041
+ function Layout({ children }) {
1042
+ return /* @__PURE__ */ jsx21(
1043
+ Box,
1044
+ {
1045
+ sx: {
1046
+ display: "flex",
1047
+ justifyContent: "center",
1048
+ alignItems: "center",
1049
+ minHeight: "100vh",
1050
+ bgcolor: "background.surface"
1051
+ },
1052
+ children: /* @__PURE__ */ jsx21(Card, { variant: "outlined", sx: { maxWidth: 400, width: "100%", p: 4 }, children })
1053
+ }
1054
+ );
1055
+ }
1056
+ function EmailInput({
1057
+ value,
1058
+ onChange
1059
+ }) {
1060
+ return /* @__PURE__ */ jsx21(
1061
+ Input,
1062
+ {
1063
+ type: "email",
1064
+ placeholder: "you@example.com",
1065
+ value,
1066
+ onChange: (e) => onChange(e.target.value),
1067
+ required: true,
1068
+ size: "lg"
1069
+ }
1070
+ );
1071
+ }
1072
+ function MagicLinkButton({
1073
+ onClick,
1074
+ loading
1075
+ }) {
1076
+ return /* @__PURE__ */ jsx21(
1077
+ Button4,
1078
+ {
1079
+ onClick,
1080
+ loading,
1081
+ size: "lg",
1082
+ fullWidth: true,
1083
+ children: "Send Magic Link"
1084
+ }
1085
+ );
1086
+ }
1087
+ function PasskeyButton({
1088
+ onClick,
1089
+ loading
1090
+ }) {
1091
+ return /* @__PURE__ */ jsxs15(Fragment3, { children: [
1092
+ /* @__PURE__ */ jsx21(Divider, { children: "or" }),
1093
+ /* @__PURE__ */ jsx21(
1094
+ Button4,
1095
+ {
1096
+ variant: "outlined",
1097
+ color: "neutral",
1098
+ onClick,
1099
+ loading,
1100
+ size: "lg",
1101
+ fullWidth: true,
1102
+ children: "Sign in with Passkey"
1103
+ }
1104
+ )
1105
+ ] });
1106
+ }
1107
+ function SuccessMessage({ email }) {
1108
+ return /* @__PURE__ */ jsxs15(Alert2, { color: "success", children: [
1109
+ "Check your email (",
1110
+ email,
1111
+ ") for a magic link. Click the link to sign in."
1112
+ ] });
1113
+ }
1114
+ function ErrorMessage({ error }) {
1115
+ return /* @__PURE__ */ jsx21(Alert2, { color: "danger", sx: { mb: 2 }, children: error });
1116
+ }
1117
+ var joyLoginComponents = {
1118
+ Layout,
1119
+ EmailInput,
1120
+ MagicLinkButton,
1121
+ PasskeyButton,
1122
+ SuccessMessage,
1123
+ ErrorMessage
1124
+ };
1125
+ export {
1126
+ ActionButton,
1127
+ AppShell,
1128
+ AppShellHeader,
1129
+ AppShellSidebar,
1130
+ AvatarWithInitials,
1131
+ BulkActionBar,
1132
+ ConfirmDialog,
1133
+ DataTable,
1134
+ DetailView,
1135
+ DropZone,
1136
+ EmptyState,
1137
+ FileList,
1138
+ FilterBar,
1139
+ FormStatus,
1140
+ ImagePreview,
1141
+ ImpersonationBanner,
1142
+ ListView,
1143
+ NavigationProgress,
1144
+ PageContainer,
1145
+ PermissionGate,
1146
+ RoleBadge,
1147
+ ToastProvider,
1148
+ UserMenu,
1149
+ joyLoginComponents
1150
+ };