@web-my-money/blocks 1.0.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.
- package/dist/app.d.mts +454 -0
- package/dist/app.d.ts +454 -0
- package/dist/app.js +1293 -0
- package/dist/app.mjs +53 -0
- package/dist/chunk-L6OIX4DG.mjs +70 -0
- package/dist/chunk-PJO7WJ4G.mjs +10 -0
- package/dist/chunk-UM2RDUPI.mjs +1183 -0
- package/dist/chunk-X6SF4TT2.mjs +2205 -0
- package/dist/funnel.d.mts +80 -0
- package/dist/funnel.d.ts +80 -0
- package/dist/funnel.js +281 -0
- package/dist/funnel.mjs +235 -0
- package/dist/fx.d.mts +314 -0
- package/dist/fx.d.ts +314 -0
- package/dist/fx.js +1142 -0
- package/dist/fx.mjs +1084 -0
- package/dist/index-D5sr_PKq.d.mts +29 -0
- package/dist/index-D5sr_PKq.d.ts +29 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3525 -0
- package/dist/index.mjs +121 -0
- package/dist/next.d.mts +32 -0
- package/dist/next.d.ts +32 -0
- package/dist/next.js +65 -0
- package/dist/next.mjs +28 -0
- package/dist/site.d.mts +532 -0
- package/dist/site.d.ts +532 -0
- package/dist/site.js +2338 -0
- package/dist/site.mjs +77 -0
- package/package.json +93 -0
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cn
|
|
3
|
+
} from "./chunk-PJO7WJ4G.mjs";
|
|
4
|
+
|
|
5
|
+
// src/notification-banner/index.tsx
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { motion, AnimatePresence } from "framer-motion";
|
|
8
|
+
import { X, Info, AlertTriangle, CheckCircle2, AlertCircle } from "lucide-react";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
var variantConfig = {
|
|
11
|
+
info: { icon: Info, classes: "bg-primary/10 border-primary/20", iconClass: "text-primary" },
|
|
12
|
+
success: { icon: CheckCircle2, classes: "bg-emerald-500/10 border-emerald-500/20", iconClass: "text-emerald-500" },
|
|
13
|
+
warning: { icon: AlertTriangle, classes: "bg-amber-500/10 border-amber-500/20", iconClass: "text-amber-500" },
|
|
14
|
+
danger: { icon: AlertCircle, classes: "bg-destructive/10 border-destructive/20", iconClass: "text-destructive" },
|
|
15
|
+
brand: { icon: Info, classes: "border-0", iconClass: "text-white" }
|
|
16
|
+
};
|
|
17
|
+
function NotificationBanner({
|
|
18
|
+
variant = "info",
|
|
19
|
+
message,
|
|
20
|
+
action,
|
|
21
|
+
dismissible = true,
|
|
22
|
+
onDismiss,
|
|
23
|
+
visible = true,
|
|
24
|
+
className
|
|
25
|
+
}) {
|
|
26
|
+
const [open, setOpen] = React.useState(true);
|
|
27
|
+
const show = visible && open;
|
|
28
|
+
const dismiss = () => {
|
|
29
|
+
setOpen(false);
|
|
30
|
+
onDismiss?.();
|
|
31
|
+
};
|
|
32
|
+
const cfg = variantConfig[variant];
|
|
33
|
+
const Icon = cfg.icon;
|
|
34
|
+
return /* @__PURE__ */ jsx(AnimatePresence, { children: show && /* @__PURE__ */ jsx(
|
|
35
|
+
motion.div,
|
|
36
|
+
{
|
|
37
|
+
initial: { height: 0, opacity: 0 },
|
|
38
|
+
animate: { height: "auto", opacity: 1 },
|
|
39
|
+
exit: { height: 0, opacity: 0 },
|
|
40
|
+
transition: { duration: 0.2, ease: "easeInOut" },
|
|
41
|
+
className: "overflow-hidden",
|
|
42
|
+
children: /* @__PURE__ */ jsxs(
|
|
43
|
+
"div",
|
|
44
|
+
{
|
|
45
|
+
className: cn(
|
|
46
|
+
"relative flex items-center justify-center gap-3 px-4 py-2.5 text-sm border-b",
|
|
47
|
+
variant === "brand" ? "text-white" : "text-foreground",
|
|
48
|
+
cfg.classes,
|
|
49
|
+
className
|
|
50
|
+
),
|
|
51
|
+
style: variant === "brand" ? { background: "var(--gradient-primary, var(--primary))" } : void 0,
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ jsx(Icon, { size: 15, className: cn("shrink-0", cfg.iconClass) }),
|
|
54
|
+
/* @__PURE__ */ jsxs("p", { className: cn("text-center", variant === "brand" ? "text-white" : "text-foreground/90"), children: [
|
|
55
|
+
message,
|
|
56
|
+
action && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
57
|
+
" ",
|
|
58
|
+
action.href ? /* @__PURE__ */ jsx(
|
|
59
|
+
"a",
|
|
60
|
+
{
|
|
61
|
+
href: action.href,
|
|
62
|
+
className: cn(
|
|
63
|
+
"underline underline-offset-2 font-medium ml-1",
|
|
64
|
+
variant === "brand" ? "text-white/90 hover:text-white" : "text-primary hover:text-primary/80"
|
|
65
|
+
),
|
|
66
|
+
children: action.label
|
|
67
|
+
}
|
|
68
|
+
) : /* @__PURE__ */ jsx(
|
|
69
|
+
"button",
|
|
70
|
+
{
|
|
71
|
+
onClick: action.onClick,
|
|
72
|
+
className: cn(
|
|
73
|
+
"underline underline-offset-2 font-medium ml-1",
|
|
74
|
+
variant === "brand" ? "text-white/90 hover:text-white" : "text-primary hover:text-primary/80"
|
|
75
|
+
),
|
|
76
|
+
children: action.label
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
] })
|
|
80
|
+
] }),
|
|
81
|
+
dismissible && /* @__PURE__ */ jsx(
|
|
82
|
+
"button",
|
|
83
|
+
{
|
|
84
|
+
onClick: dismiss,
|
|
85
|
+
"aria-label": "Dismiss",
|
|
86
|
+
className: cn(
|
|
87
|
+
"absolute right-3 rounded p-1 transition-colors",
|
|
88
|
+
variant === "brand" ? "text-white/70 hover:text-white hover:bg-white/10" : "text-muted-foreground hover:text-foreground hover:bg-accent"
|
|
89
|
+
),
|
|
90
|
+
children: /* @__PURE__ */ jsx(X, { size: 14 })
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
},
|
|
97
|
+
"banner"
|
|
98
|
+
) });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// src/stat-card/index.tsx
|
|
102
|
+
import { TrendingUp, TrendingDown, Minus } from "lucide-react";
|
|
103
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
104
|
+
function inferStatus(delta) {
|
|
105
|
+
if (!delta) return "flat";
|
|
106
|
+
if (delta.startsWith("+") || !delta.startsWith("-") && parseFloat(delta) > 0) return "up";
|
|
107
|
+
if (delta.startsWith("-") || parseFloat(delta) < 0) return "down";
|
|
108
|
+
return "flat";
|
|
109
|
+
}
|
|
110
|
+
var statusColors = {
|
|
111
|
+
success: "text-emerald-500",
|
|
112
|
+
warning: "text-amber-500",
|
|
113
|
+
danger: "text-red-500",
|
|
114
|
+
info: "text-primary"
|
|
115
|
+
};
|
|
116
|
+
function StatCard({
|
|
117
|
+
title,
|
|
118
|
+
value,
|
|
119
|
+
delta,
|
|
120
|
+
deltaLabel,
|
|
121
|
+
icon,
|
|
122
|
+
chart,
|
|
123
|
+
status,
|
|
124
|
+
className
|
|
125
|
+
}) {
|
|
126
|
+
const trend = inferStatus(delta);
|
|
127
|
+
const deltaColor = status ? statusColors[status] : trend === "up" ? "text-emerald-500" : trend === "down" ? "text-red-500" : "text-muted-foreground";
|
|
128
|
+
const TrendIcon = trend === "up" ? TrendingUp : trend === "down" ? TrendingDown : Minus;
|
|
129
|
+
return /* @__PURE__ */ jsxs2(
|
|
130
|
+
"div",
|
|
131
|
+
{
|
|
132
|
+
className: cn(
|
|
133
|
+
"relative flex flex-col gap-3 rounded-xl border bg-card p-5 overflow-hidden",
|
|
134
|
+
"border-border",
|
|
135
|
+
className
|
|
136
|
+
),
|
|
137
|
+
style: { boxShadow: "var(--shadow-card)" },
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ jsx2(
|
|
140
|
+
"div",
|
|
141
|
+
{
|
|
142
|
+
"aria-hidden": true,
|
|
143
|
+
className: "absolute inset-x-0 top-0 h-px opacity-60",
|
|
144
|
+
style: { background: "var(--gradient-primary, var(--primary))" }
|
|
145
|
+
}
|
|
146
|
+
),
|
|
147
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between gap-2", children: [
|
|
148
|
+
/* @__PURE__ */ jsx2("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider truncate", children: title }),
|
|
149
|
+
icon && /* @__PURE__ */ jsx2("span", { className: "shrink-0 text-muted-foreground/60", children: icon })
|
|
150
|
+
] }),
|
|
151
|
+
/* @__PURE__ */ jsx2("p", { className: "text-3xl font-bold text-foreground tracking-tight tabular-nums", children: typeof value === "number" ? value.toLocaleString() : value }),
|
|
152
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-end justify-between gap-2 mt-auto", children: [
|
|
153
|
+
delta ? /* @__PURE__ */ jsxs2("div", { className: cn("flex items-center gap-1 text-xs font-medium", deltaColor), children: [
|
|
154
|
+
/* @__PURE__ */ jsx2(TrendIcon, { size: 13, strokeWidth: 2.5 }),
|
|
155
|
+
/* @__PURE__ */ jsx2("span", { children: delta }),
|
|
156
|
+
deltaLabel && /* @__PURE__ */ jsx2("span", { className: "text-muted-foreground font-normal", children: deltaLabel })
|
|
157
|
+
] }) : /* @__PURE__ */ jsx2("div", {}),
|
|
158
|
+
chart && /* @__PURE__ */ jsx2("div", { className: "shrink-0", children: chart })
|
|
159
|
+
] })
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/page-header/index.tsx
|
|
166
|
+
import { ChevronRight } from "lucide-react";
|
|
167
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
168
|
+
function PageHeader({
|
|
169
|
+
title,
|
|
170
|
+
description,
|
|
171
|
+
breadcrumb,
|
|
172
|
+
actions,
|
|
173
|
+
below,
|
|
174
|
+
className
|
|
175
|
+
}) {
|
|
176
|
+
return /* @__PURE__ */ jsxs3(
|
|
177
|
+
"header",
|
|
178
|
+
{
|
|
179
|
+
className: cn(
|
|
180
|
+
"flex flex-col gap-4 pb-6 border-b border-border",
|
|
181
|
+
className
|
|
182
|
+
),
|
|
183
|
+
children: [
|
|
184
|
+
breadcrumb && breadcrumb.length > 0 && /* @__PURE__ */ jsx3("nav", { "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx3("ol", { className: "flex items-center gap-1 flex-wrap", children: breadcrumb.map((item, i) => /* @__PURE__ */ jsxs3("li", { className: "flex items-center gap-1", children: [
|
|
185
|
+
i > 0 && /* @__PURE__ */ jsx3(ChevronRight, { size: 12, className: "text-muted-foreground/50" }),
|
|
186
|
+
item.href ? /* @__PURE__ */ jsx3(
|
|
187
|
+
"a",
|
|
188
|
+
{
|
|
189
|
+
href: item.href,
|
|
190
|
+
className: "text-xs text-muted-foreground hover:text-foreground transition-colors",
|
|
191
|
+
children: item.label
|
|
192
|
+
}
|
|
193
|
+
) : /* @__PURE__ */ jsx3("span", { className: "text-xs text-muted-foreground", children: item.label })
|
|
194
|
+
] }, i)) }) }),
|
|
195
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [
|
|
196
|
+
/* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
|
|
197
|
+
/* @__PURE__ */ jsx3("h1", { className: "text-2xl font-bold tracking-tight text-foreground", children: title }),
|
|
198
|
+
description && /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground max-w-xl", children: description })
|
|
199
|
+
] }),
|
|
200
|
+
actions && /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-2 flex-wrap", children: actions })
|
|
201
|
+
] }),
|
|
202
|
+
below && /* @__PURE__ */ jsx3("div", { children: below })
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/empty-state/index.tsx
|
|
209
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
210
|
+
function EmptyState({
|
|
211
|
+
icon,
|
|
212
|
+
title,
|
|
213
|
+
description,
|
|
214
|
+
action,
|
|
215
|
+
secondaryAction,
|
|
216
|
+
variant = "page",
|
|
217
|
+
className
|
|
218
|
+
}) {
|
|
219
|
+
return /* @__PURE__ */ jsxs4(
|
|
220
|
+
"div",
|
|
221
|
+
{
|
|
222
|
+
className: cn(
|
|
223
|
+
"flex flex-col items-center text-center",
|
|
224
|
+
variant === "page" ? "min-h-[360px] justify-center px-6 py-20" : "rounded-xl border border-border border-dashed bg-card/50 px-6 py-12",
|
|
225
|
+
className
|
|
226
|
+
),
|
|
227
|
+
children: [
|
|
228
|
+
icon && /* @__PURE__ */ jsx4(
|
|
229
|
+
"div",
|
|
230
|
+
{
|
|
231
|
+
className: "mb-5 flex h-14 w-14 items-center justify-center rounded-xl border border-border bg-muted/40 text-muted-foreground",
|
|
232
|
+
children: icon
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
/* @__PURE__ */ jsx4("p", { className: "text-base font-semibold text-foreground mb-2", children: title }),
|
|
236
|
+
description && /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground max-w-xs mb-6", children: description }),
|
|
237
|
+
(action || secondaryAction) && /* @__PURE__ */ jsxs4("div", { className: "flex flex-col sm:flex-row items-center gap-3", children: [
|
|
238
|
+
action,
|
|
239
|
+
secondaryAction
|
|
240
|
+
] })
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/dashboard-header/index.tsx
|
|
247
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
248
|
+
function DashboardHeader({
|
|
249
|
+
pageTitle,
|
|
250
|
+
notificationsCount = 0,
|
|
251
|
+
actions,
|
|
252
|
+
className
|
|
253
|
+
}) {
|
|
254
|
+
return /* @__PURE__ */ jsxs5(
|
|
255
|
+
"header",
|
|
256
|
+
{
|
|
257
|
+
className: cn(
|
|
258
|
+
"flex h-14 lg:h-[60px] items-center gap-4 border-b bg-muted/20 px-6 justify-between",
|
|
259
|
+
className
|
|
260
|
+
),
|
|
261
|
+
children: [
|
|
262
|
+
/* @__PURE__ */ jsx5("div", { className: "font-semibold text-lg", children: pageTitle }),
|
|
263
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-4", children: [
|
|
264
|
+
notificationsCount > 0 && /* @__PURE__ */ jsx5("div", { className: "relative", children: /* @__PURE__ */ jsx5("span", { className: "h-5 w-5 rounded-full bg-primary/20 aspect-square text-[10px] font-bold flex items-center justify-center", children: notificationsCount }) }),
|
|
265
|
+
actions
|
|
266
|
+
] })
|
|
267
|
+
]
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/data-summary-cards/index.tsx
|
|
273
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
274
|
+
function DataSummaryCards({
|
|
275
|
+
metrics,
|
|
276
|
+
columns = 4,
|
|
277
|
+
className
|
|
278
|
+
}) {
|
|
279
|
+
const gridCols = {
|
|
280
|
+
2: "md:grid-cols-2",
|
|
281
|
+
3: "md:grid-cols-3",
|
|
282
|
+
4: "md:grid-cols-2 lg:grid-cols-4"
|
|
283
|
+
};
|
|
284
|
+
return /* @__PURE__ */ jsx6("div", { className: cn("grid gap-4", gridCols[columns], className), children: metrics.map((item, i) => /* @__PURE__ */ jsxs6(
|
|
285
|
+
"div",
|
|
286
|
+
{
|
|
287
|
+
className: "rounded-xl bg-card p-4 text-card-foreground ring-1 ring-foreground/10",
|
|
288
|
+
children: [
|
|
289
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between mb-2", children: [
|
|
290
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm font-medium text-muted-foreground", children: item.title }),
|
|
291
|
+
item.icon ?? /* @__PURE__ */ jsx6("div", { className: "h-4 w-4 rounded bg-muted-foreground/20" })
|
|
292
|
+
] }),
|
|
293
|
+
/* @__PURE__ */ jsx6("div", { className: "text-2xl font-bold", children: item.value }),
|
|
294
|
+
item.description && /* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground mt-1", children: item.description })
|
|
295
|
+
]
|
|
296
|
+
},
|
|
297
|
+
i
|
|
298
|
+
)) });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/data-table/index.tsx
|
|
302
|
+
import * as React2 from "react";
|
|
303
|
+
import { PaginationBar } from "@web-my-money/primitives/pagination";
|
|
304
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
305
|
+
function DataTable({
|
|
306
|
+
title,
|
|
307
|
+
columns,
|
|
308
|
+
rows,
|
|
309
|
+
activeBadgeLabel = "Active",
|
|
310
|
+
className,
|
|
311
|
+
pageSize,
|
|
312
|
+
page: controlledPage,
|
|
313
|
+
onPageChange
|
|
314
|
+
}) {
|
|
315
|
+
const [uncontrolledPage, setUncontrolledPage] = React2.useState(1);
|
|
316
|
+
const page = controlledPage ?? uncontrolledPage;
|
|
317
|
+
const pageCount = pageSize ? Math.max(1, Math.ceil(rows.length / pageSize)) : 1;
|
|
318
|
+
const visibleRows = pageSize ? rows.slice((page - 1) * pageSize, page * pageSize) : rows;
|
|
319
|
+
const setPage = (next) => {
|
|
320
|
+
setUncontrolledPage(next);
|
|
321
|
+
onPageChange?.(next);
|
|
322
|
+
};
|
|
323
|
+
return /* @__PURE__ */ jsxs7("div", { className: cn("rounded-md border", className), children: [
|
|
324
|
+
/* @__PURE__ */ jsx7("div", { className: "p-4 border-b bg-muted/10 font-medium text-lg", children: title }),
|
|
325
|
+
/* @__PURE__ */ jsx7("div", { className: "p-0", children: /* @__PURE__ */ jsxs7("table", { className: "w-full text-sm", children: [
|
|
326
|
+
/* @__PURE__ */ jsx7("thead", { className: "border-b bg-muted/20", children: /* @__PURE__ */ jsx7("tr", { children: columns.map((col, i) => /* @__PURE__ */ jsx7(
|
|
327
|
+
"th",
|
|
328
|
+
{
|
|
329
|
+
className: cn(
|
|
330
|
+
"font-medium p-4",
|
|
331
|
+
i === 0 ? "text-left" : i === columns.length - 1 ? "text-right" : "text-left"
|
|
332
|
+
),
|
|
333
|
+
children: col
|
|
334
|
+
},
|
|
335
|
+
col
|
|
336
|
+
)) }) }),
|
|
337
|
+
/* @__PURE__ */ jsx7("tbody", { children: visibleRows.map((row) => /* @__PURE__ */ jsx7(
|
|
338
|
+
"tr",
|
|
339
|
+
{
|
|
340
|
+
className: "border-b last:border-0 hover:bg-muted/10 transition-colors",
|
|
341
|
+
children: row.cells.map((cell, i) => /* @__PURE__ */ jsx7(
|
|
342
|
+
"td",
|
|
343
|
+
{
|
|
344
|
+
className: cn(
|
|
345
|
+
"p-4",
|
|
346
|
+
i === 0 ? "font-medium" : i === columns.length - 1 ? "text-right font-medium" : "text-muted-foreground"
|
|
347
|
+
),
|
|
348
|
+
children: i === 1 && row.isActive ? /* @__PURE__ */ jsx7("span", { className: "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold bg-primary/10 text-primary", children: activeBadgeLabel }) : cell
|
|
349
|
+
},
|
|
350
|
+
i
|
|
351
|
+
))
|
|
352
|
+
},
|
|
353
|
+
row.id
|
|
354
|
+
)) })
|
|
355
|
+
] }) }),
|
|
356
|
+
pageSize && pageCount > 1 && /* @__PURE__ */ jsx7("div", { className: "flex justify-center border-t p-3", children: /* @__PURE__ */ jsx7(PaginationBar, { page, pageCount, onPageChange: setPage }) })
|
|
357
|
+
] });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// src/sidebar-navigation/index.tsx
|
|
361
|
+
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
362
|
+
function SidebarNavigation({
|
|
363
|
+
appName,
|
|
364
|
+
userName,
|
|
365
|
+
menuItems,
|
|
366
|
+
footer,
|
|
367
|
+
className
|
|
368
|
+
}) {
|
|
369
|
+
return /* @__PURE__ */ jsxs8(
|
|
370
|
+
"aside",
|
|
371
|
+
{
|
|
372
|
+
className: cn(
|
|
373
|
+
"w-64 border-r bg-muted/20 h-screen hidden md:flex flex-col",
|
|
374
|
+
className
|
|
375
|
+
),
|
|
376
|
+
children: [
|
|
377
|
+
/* @__PURE__ */ jsx8("div", { className: "h-14 border-b flex items-center px-4 font-bold text-lg", children: appName }),
|
|
378
|
+
/* @__PURE__ */ jsx8("nav", { className: "flex-1 overflow-auto py-4 px-2 space-y-2", children: menuItems.map((item, i) => /* @__PURE__ */ jsxs8(
|
|
379
|
+
"a",
|
|
380
|
+
{
|
|
381
|
+
href: item.href ?? "#",
|
|
382
|
+
className: cn(
|
|
383
|
+
"flex items-center px-3 py-2 text-sm font-medium rounded-md cursor-pointer transition-colors",
|
|
384
|
+
item.active ? "bg-primary/10 text-primary" : "hover:bg-muted text-muted-foreground"
|
|
385
|
+
),
|
|
386
|
+
children: [
|
|
387
|
+
item.icon ?? /* @__PURE__ */ jsx8(
|
|
388
|
+
"span",
|
|
389
|
+
{
|
|
390
|
+
className: cn(
|
|
391
|
+
"h-4 w-4 mr-3 rounded",
|
|
392
|
+
item.active ? "bg-primary/50" : "bg-muted-foreground/30"
|
|
393
|
+
)
|
|
394
|
+
}
|
|
395
|
+
),
|
|
396
|
+
item.label
|
|
397
|
+
]
|
|
398
|
+
},
|
|
399
|
+
i
|
|
400
|
+
)) }),
|
|
401
|
+
(userName || footer) && /* @__PURE__ */ jsx8("div", { className: "p-4 border-t text-sm font-medium", children: footer ?? userName })
|
|
402
|
+
]
|
|
403
|
+
}
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// src/kpi-row/index.tsx
|
|
408
|
+
import { TrendingUp as TrendingUp2, TrendingDown as TrendingDown2, Minus as Minus2 } from "lucide-react";
|
|
409
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
410
|
+
function inferTrend(delta) {
|
|
411
|
+
if (!delta) return "flat";
|
|
412
|
+
if (delta.startsWith("+") || parseFloat(delta) > 0) return "up";
|
|
413
|
+
if (delta.startsWith("-") || parseFloat(delta) < 0) return "down";
|
|
414
|
+
return "flat";
|
|
415
|
+
}
|
|
416
|
+
var deltaColorMap = {
|
|
417
|
+
up: "text-emerald-500",
|
|
418
|
+
down: "text-red-500",
|
|
419
|
+
flat: "text-muted-foreground"
|
|
420
|
+
};
|
|
421
|
+
var statusColorMap = {
|
|
422
|
+
success: "text-emerald-500",
|
|
423
|
+
warning: "text-amber-500",
|
|
424
|
+
danger: "text-red-500",
|
|
425
|
+
neutral: "text-muted-foreground"
|
|
426
|
+
};
|
|
427
|
+
function KPIRow({ items, dividers = true, className }) {
|
|
428
|
+
return /* @__PURE__ */ jsx9(
|
|
429
|
+
"div",
|
|
430
|
+
{
|
|
431
|
+
className: cn(
|
|
432
|
+
"grid w-full rounded-xl border border-border bg-card",
|
|
433
|
+
`grid-cols-${Math.min(items.length, 4)} sm:grid-cols-${items.length}`,
|
|
434
|
+
className
|
|
435
|
+
),
|
|
436
|
+
style: { gridTemplateColumns: `repeat(${items.length}, minmax(0, 1fr))` },
|
|
437
|
+
children: items.map((item, i) => {
|
|
438
|
+
const trend = inferTrend(item.delta);
|
|
439
|
+
const deltaColor = item.status ? statusColorMap[item.status] : deltaColorMap[trend];
|
|
440
|
+
const TrendIcon = trend === "up" ? TrendingUp2 : trend === "down" ? TrendingDown2 : Minus2;
|
|
441
|
+
return /* @__PURE__ */ jsxs9(
|
|
442
|
+
"div",
|
|
443
|
+
{
|
|
444
|
+
className: cn(
|
|
445
|
+
"flex flex-col gap-1 px-5 py-4",
|
|
446
|
+
dividers && i < items.length - 1 && "border-r border-border"
|
|
447
|
+
),
|
|
448
|
+
children: [
|
|
449
|
+
/* @__PURE__ */ jsx9("p", { className: "text-xs font-medium uppercase tracking-wider text-muted-foreground truncate", children: item.label }),
|
|
450
|
+
/* @__PURE__ */ jsxs9("p", { className: "text-2xl font-bold tabular-nums text-foreground", children: [
|
|
451
|
+
item.prefix && /* @__PURE__ */ jsx9("span", { className: "text-lg font-medium text-muted-foreground", children: item.prefix }),
|
|
452
|
+
typeof item.value === "number" ? item.value.toLocaleString() : item.value,
|
|
453
|
+
item.suffix && /* @__PURE__ */ jsx9("span", { className: "text-lg font-medium text-muted-foreground ml-0.5", children: item.suffix })
|
|
454
|
+
] }),
|
|
455
|
+
item.delta && /* @__PURE__ */ jsxs9("div", { className: cn("flex items-center gap-1 text-xs font-medium", deltaColor), children: [
|
|
456
|
+
/* @__PURE__ */ jsx9(TrendIcon, { size: 11, strokeWidth: 2.5, "aria-hidden": true }),
|
|
457
|
+
/* @__PURE__ */ jsx9("span", { children: item.delta }),
|
|
458
|
+
item.deltaLabel && /* @__PURE__ */ jsx9("span", { className: "font-normal text-muted-foreground", children: item.deltaLabel })
|
|
459
|
+
] })
|
|
460
|
+
]
|
|
461
|
+
},
|
|
462
|
+
i
|
|
463
|
+
);
|
|
464
|
+
})
|
|
465
|
+
}
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/activity-feed/index.tsx
|
|
470
|
+
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
471
|
+
var typeDot = {
|
|
472
|
+
task: "bg-primary",
|
|
473
|
+
comment: "bg-sky-500",
|
|
474
|
+
alert: "bg-amber-500",
|
|
475
|
+
success: "bg-emerald-500",
|
|
476
|
+
info: "bg-muted-foreground",
|
|
477
|
+
user: "bg-violet-500"
|
|
478
|
+
};
|
|
479
|
+
function getInitials(text) {
|
|
480
|
+
if (!text) return "?";
|
|
481
|
+
const w = text.trim().split(/\s+/);
|
|
482
|
+
return w.length === 1 ? w[0].slice(0, 2).toUpperCase() : (w[0][0] + w[w.length - 1][0]).toUpperCase();
|
|
483
|
+
}
|
|
484
|
+
function ActivityFeed({
|
|
485
|
+
items,
|
|
486
|
+
timeline = true,
|
|
487
|
+
compact = false,
|
|
488
|
+
className,
|
|
489
|
+
emptyMessage = "No activity yet."
|
|
490
|
+
}) {
|
|
491
|
+
if (!items.length) {
|
|
492
|
+
return /* @__PURE__ */ jsx10("div", { className: cn("py-10 text-center text-sm text-muted-foreground", className), children: emptyMessage });
|
|
493
|
+
}
|
|
494
|
+
return /* @__PURE__ */ jsx10("ul", { className: cn("flex flex-col", compact ? "gap-1" : "gap-0", className), children: items.map((item, i) => {
|
|
495
|
+
const dotColor = item.dotColor ?? typeDot[item.type ?? "info"];
|
|
496
|
+
const isLast = i === items.length - 1;
|
|
497
|
+
return /* @__PURE__ */ jsxs10("li", { className: cn("relative flex gap-4", compact ? "py-2 px-1" : "py-3 px-1"), children: [
|
|
498
|
+
timeline && /* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-center shrink-0", style: { width: 28 }, children: [
|
|
499
|
+
/* @__PURE__ */ jsx10("span", { className: cn("mt-0.5 size-2.5 rounded-full ring-2 ring-background shrink-0", dotColor) }),
|
|
500
|
+
!isLast && /* @__PURE__ */ jsx10("span", { className: "mt-1 w-px flex-1 bg-border", style: { minHeight: compact ? 16 : 24 } })
|
|
501
|
+
] }),
|
|
502
|
+
!timeline && /* @__PURE__ */ jsx10("span", { className: "mt-0.5 shrink-0 inline-flex size-8 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground overflow-hidden", children: item.icon ?? (item.avatar ? /* @__PURE__ */ jsx10("img", { src: item.avatar, alt: item.initials ?? "", className: "size-full object-cover" }) : getInitials(item.initials ?? item.title)) }),
|
|
503
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex-1 min-w-0", children: [
|
|
504
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-start justify-between gap-2", children: [
|
|
505
|
+
/* @__PURE__ */ jsx10("p", { className: cn("text-sm font-medium text-foreground leading-snug", compact && "text-xs"), children: item.title }),
|
|
506
|
+
/* @__PURE__ */ jsx10("span", { className: "shrink-0 text-xs text-muted-foreground whitespace-nowrap", children: item.timestamp })
|
|
507
|
+
] }),
|
|
508
|
+
item.description && /* @__PURE__ */ jsx10("p", { className: "mt-0.5 text-xs text-muted-foreground line-clamp-2", children: item.description }),
|
|
509
|
+
item.meta && /* @__PURE__ */ jsx10("p", { className: "mt-1 text-[11px] text-muted-foreground/70", children: item.meta })
|
|
510
|
+
] })
|
|
511
|
+
] }, item.id);
|
|
512
|
+
}) });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/app-shell/index.tsx
|
|
516
|
+
import * as React3 from "react";
|
|
517
|
+
import {
|
|
518
|
+
Menu,
|
|
519
|
+
X as X2,
|
|
520
|
+
PanelLeftClose,
|
|
521
|
+
PanelLeft,
|
|
522
|
+
ChevronDown,
|
|
523
|
+
ChevronRight as ChevronRight2,
|
|
524
|
+
PanelTop
|
|
525
|
+
} from "lucide-react";
|
|
526
|
+
import {
|
|
527
|
+
DropdownMenu,
|
|
528
|
+
DropdownMenuTrigger,
|
|
529
|
+
DropdownMenuContent,
|
|
530
|
+
DropdownMenuItem
|
|
531
|
+
} from "@web-my-money/primitives/dropdown-menu";
|
|
532
|
+
|
|
533
|
+
// src/app-brand/index.tsx
|
|
534
|
+
import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
535
|
+
var MARK_SIZE = { sm: "size-6 text-xs", md: "size-7 text-sm" };
|
|
536
|
+
var TITLE_SIZE = { sm: "text-[13px]", md: "text-sm" };
|
|
537
|
+
function AppBrand({
|
|
538
|
+
appName,
|
|
539
|
+
prefix = "WMM",
|
|
540
|
+
logo,
|
|
541
|
+
subtitle,
|
|
542
|
+
badge,
|
|
543
|
+
href,
|
|
544
|
+
collapsed = false,
|
|
545
|
+
size = "md",
|
|
546
|
+
renderLink,
|
|
547
|
+
className
|
|
548
|
+
}) {
|
|
549
|
+
const mark = logo ?? // Mini-logo placeholder — first letter on a primary chip.
|
|
550
|
+
/* @__PURE__ */ jsx11(
|
|
551
|
+
"span",
|
|
552
|
+
{
|
|
553
|
+
className: cn(
|
|
554
|
+
"grid shrink-0 place-items-center rounded-lg bg-primary font-bold text-primary-foreground",
|
|
555
|
+
MARK_SIZE[size]
|
|
556
|
+
),
|
|
557
|
+
"aria-hidden": true,
|
|
558
|
+
children: appName.slice(0, 1)
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
const inner = /* @__PURE__ */ jsxs11("span", { className: "flex items-center gap-2.5", children: [
|
|
562
|
+
/* @__PURE__ */ jsx11("span", { className: "grid shrink-0 place-items-center [&_svg]:h-auto", children: mark }),
|
|
563
|
+
!collapsed && /* @__PURE__ */ jsxs11("span", { className: "flex min-w-0 items-center gap-2", children: [
|
|
564
|
+
/* @__PURE__ */ jsxs11("span", { className: "flex min-w-0 flex-col leading-tight", children: [
|
|
565
|
+
/* @__PURE__ */ jsxs11("span", { className: cn("truncate font-semibold tracking-tight", TITLE_SIZE[size]), children: [
|
|
566
|
+
prefix && /* @__PURE__ */ jsxs11("span", { className: "text-muted-foreground", children: [
|
|
567
|
+
prefix,
|
|
568
|
+
" "
|
|
569
|
+
] }),
|
|
570
|
+
/* @__PURE__ */ jsx11("span", { className: "text-primary", children: appName })
|
|
571
|
+
] }),
|
|
572
|
+
subtitle && /* @__PURE__ */ jsx11("span", { className: "truncate text-[11px] text-muted-foreground", children: subtitle })
|
|
573
|
+
] }),
|
|
574
|
+
badge != null && /* @__PURE__ */ jsx11("span", { className: "border-l border-border pl-2 text-sm font-semibold text-muted-foreground", children: badge })
|
|
575
|
+
] })
|
|
576
|
+
] });
|
|
577
|
+
const cls = cn("inline-flex items-center", className);
|
|
578
|
+
if (href && renderLink) return /* @__PURE__ */ jsx11(Fragment2, { children: renderLink({ href, className: cls, children: inner }) });
|
|
579
|
+
if (href)
|
|
580
|
+
return /* @__PURE__ */ jsx11("a", { href, className: cls, children: inner });
|
|
581
|
+
return /* @__PURE__ */ jsx11("span", { className: cls, children: inner });
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/app-shell/index.tsx
|
|
585
|
+
import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
586
|
+
var railW = "w-14";
|
|
587
|
+
var openW = "w-56";
|
|
588
|
+
var LAYOUT_KEY = "wmm-app-layout";
|
|
589
|
+
function isGroup(n) {
|
|
590
|
+
return Array.isArray(n.items) && !("href" in n) && !("onClick" in n) && !("icon" in n) && !("badge" in n) && !("shortLabel" in n) && !("external" in n);
|
|
591
|
+
}
|
|
592
|
+
function toGroups(nav) {
|
|
593
|
+
if (!nav.length) return [];
|
|
594
|
+
return isGroup(nav[0]) ? nav : [{ items: nav }];
|
|
595
|
+
}
|
|
596
|
+
function allItems(groups) {
|
|
597
|
+
return groups.flatMap((g) => g.items);
|
|
598
|
+
}
|
|
599
|
+
function withActive(item, pathname) {
|
|
600
|
+
if (item.active !== void 0 || !pathname || !item.href) return item;
|
|
601
|
+
const active = item.href === "/" ? pathname === "/" : pathname.startsWith(item.href);
|
|
602
|
+
return active ? { ...item, active } : item;
|
|
603
|
+
}
|
|
604
|
+
var LayoutContext = React3.createContext(null);
|
|
605
|
+
function useLayoutPreference(defaultLayout = "sidebar") {
|
|
606
|
+
const [layout, setLayoutState] = React3.useState(defaultLayout);
|
|
607
|
+
React3.useEffect(() => {
|
|
608
|
+
try {
|
|
609
|
+
const saved = window.localStorage.getItem(LAYOUT_KEY);
|
|
610
|
+
if (saved === "sidebar" || saved === "top") setLayoutState(saved);
|
|
611
|
+
} catch {
|
|
612
|
+
}
|
|
613
|
+
}, []);
|
|
614
|
+
const setLayout = React3.useCallback((l) => {
|
|
615
|
+
setLayoutState(l);
|
|
616
|
+
try {
|
|
617
|
+
window.localStorage.setItem(LAYOUT_KEY, l);
|
|
618
|
+
} catch {
|
|
619
|
+
}
|
|
620
|
+
}, []);
|
|
621
|
+
return [layout, setLayout];
|
|
622
|
+
}
|
|
623
|
+
function LayoutToggle({ className }) {
|
|
624
|
+
const ctx = React3.useContext(LayoutContext);
|
|
625
|
+
if (!ctx) return null;
|
|
626
|
+
const next = ctx.layout === "sidebar" ? "top" : "sidebar";
|
|
627
|
+
return /* @__PURE__ */ jsx12(
|
|
628
|
+
"button",
|
|
629
|
+
{
|
|
630
|
+
type: "button",
|
|
631
|
+
onClick: () => ctx.setLayout(next),
|
|
632
|
+
className: cn(
|
|
633
|
+
"inline-flex items-center justify-center rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
634
|
+
className
|
|
635
|
+
),
|
|
636
|
+
"aria-label": `Switch to ${next} layout`,
|
|
637
|
+
title: `Switch to ${next} layout`,
|
|
638
|
+
children: ctx.layout === "sidebar" ? /* @__PURE__ */ jsx12(PanelTop, { className: "size-4" }) : /* @__PURE__ */ jsx12(PanelLeft, { className: "size-4" })
|
|
639
|
+
}
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
function SidebarNavRow({
|
|
643
|
+
item,
|
|
644
|
+
collapsed,
|
|
645
|
+
renderLink,
|
|
646
|
+
onNavigate,
|
|
647
|
+
nested
|
|
648
|
+
}) {
|
|
649
|
+
const inner = /* @__PURE__ */ jsxs12(
|
|
650
|
+
"span",
|
|
651
|
+
{
|
|
652
|
+
className: cn(
|
|
653
|
+
"flex items-center gap-2.5 rounded-md px-2.5 py-2 text-[13px] font-medium transition-colors",
|
|
654
|
+
item.active ? "bg-primary/15 text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground",
|
|
655
|
+
collapsed && "justify-center px-0",
|
|
656
|
+
nested && !collapsed && "ml-3.5 py-1.5"
|
|
657
|
+
),
|
|
658
|
+
children: [
|
|
659
|
+
/* @__PURE__ */ jsx12("span", { className: "grid size-[15px] shrink-0 place-items-center [&_svg]:size-[15px]", children: item.icon }),
|
|
660
|
+
!collapsed && /* @__PURE__ */ jsxs12("span", { className: "flex-1 truncate", children: [
|
|
661
|
+
item.label,
|
|
662
|
+
item.external && " \u2197"
|
|
663
|
+
] }),
|
|
664
|
+
!collapsed && item.badge != null && /* @__PURE__ */ jsx12("span", { className: "rounded bg-danger/20 px-1.5 py-px text-[10px] font-semibold text-danger", children: item.badge })
|
|
665
|
+
]
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
const cls = "block";
|
|
669
|
+
if (item.href && item.external)
|
|
670
|
+
return /* @__PURE__ */ jsx12("a", { href: item.href, target: "_blank", rel: "noopener noreferrer", className: cls, onClick: onNavigate, children: inner });
|
|
671
|
+
if (item.href && renderLink) return /* @__PURE__ */ jsx12(Fragment4, { children: renderLink({ href: item.href, className: cls, children: inner }) });
|
|
672
|
+
if (item.href)
|
|
673
|
+
return /* @__PURE__ */ jsx12("a", { href: item.href, className: cls, onClick: onNavigate, children: inner });
|
|
674
|
+
return /* @__PURE__ */ jsx12("button", { type: "button", className: cn(cls, "w-full text-left"), onClick: () => {
|
|
675
|
+
item.onClick?.();
|
|
676
|
+
onNavigate?.();
|
|
677
|
+
}, children: inner });
|
|
678
|
+
}
|
|
679
|
+
function SectionHeader({
|
|
680
|
+
group,
|
|
681
|
+
open,
|
|
682
|
+
onToggle
|
|
683
|
+
}) {
|
|
684
|
+
if (!group.collapsible) {
|
|
685
|
+
return /* @__PURE__ */ jsx12("div", { className: "px-2.5 pb-1 pt-3 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground", children: group.label });
|
|
686
|
+
}
|
|
687
|
+
return /* @__PURE__ */ jsxs12(
|
|
688
|
+
"button",
|
|
689
|
+
{
|
|
690
|
+
type: "button",
|
|
691
|
+
onClick: onToggle,
|
|
692
|
+
className: "flex w-full items-center gap-1 px-2.5 pb-1 pt-3 text-[10px] font-semibold uppercase tracking-widest text-muted-foreground transition-colors hover:text-foreground",
|
|
693
|
+
children: [
|
|
694
|
+
open ? /* @__PURE__ */ jsx12(ChevronDown, { className: "size-3" }) : /* @__PURE__ */ jsx12(ChevronRight2, { className: "size-3" }),
|
|
695
|
+
/* @__PURE__ */ jsx12("span", { className: "flex-1 text-left", children: group.label })
|
|
696
|
+
]
|
|
697
|
+
}
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
function TopLink({
|
|
701
|
+
item,
|
|
702
|
+
renderLink
|
|
703
|
+
}) {
|
|
704
|
+
const inner = /* @__PURE__ */ jsxs12(
|
|
705
|
+
"span",
|
|
706
|
+
{
|
|
707
|
+
className: cn(
|
|
708
|
+
"inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-[13px] font-medium transition-colors [&_svg]:size-4",
|
|
709
|
+
item.active ? "bg-primary/10 text-primary" : "text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
710
|
+
),
|
|
711
|
+
children: [
|
|
712
|
+
item.icon,
|
|
713
|
+
item.label,
|
|
714
|
+
item.external && " \u2197",
|
|
715
|
+
item.badge != null && /* @__PURE__ */ jsx12("span", { className: "rounded bg-danger/20 px-1.5 text-[10px] font-semibold text-danger", children: item.badge })
|
|
716
|
+
]
|
|
717
|
+
}
|
|
718
|
+
);
|
|
719
|
+
if (item.href && item.external)
|
|
720
|
+
return /* @__PURE__ */ jsx12("a", { href: item.href, target: "_blank", rel: "noopener noreferrer", children: inner });
|
|
721
|
+
if (item.href && renderLink) return /* @__PURE__ */ jsx12(Fragment4, { children: renderLink({ href: item.href, children: inner }) });
|
|
722
|
+
if (item.href) return /* @__PURE__ */ jsx12("a", { href: item.href, children: inner });
|
|
723
|
+
return /* @__PURE__ */ jsx12("button", { type: "button", onClick: item.onClick, children: inner });
|
|
724
|
+
}
|
|
725
|
+
function TopDropdown({
|
|
726
|
+
label,
|
|
727
|
+
items,
|
|
728
|
+
renderLink
|
|
729
|
+
}) {
|
|
730
|
+
const hasBadge = items.some((i) => i.badge != null && i.badge !== 0);
|
|
731
|
+
const anyActive = items.some((i) => i.active);
|
|
732
|
+
return /* @__PURE__ */ jsxs12(DropdownMenu, { children: [
|
|
733
|
+
/* @__PURE__ */ jsxs12(
|
|
734
|
+
DropdownMenuTrigger,
|
|
735
|
+
{
|
|
736
|
+
className: cn(
|
|
737
|
+
"inline-flex items-center gap-1 rounded-md px-2.5 py-1.5 text-[13px] font-medium transition-colors hover:bg-muted hover:text-foreground",
|
|
738
|
+
anyActive ? "text-foreground" : "text-muted-foreground"
|
|
739
|
+
),
|
|
740
|
+
children: [
|
|
741
|
+
label,
|
|
742
|
+
hasBadge && /* @__PURE__ */ jsx12("span", { className: "size-1.5 rounded-full bg-primary", "aria-hidden": true }),
|
|
743
|
+
/* @__PURE__ */ jsx12(ChevronDown, { className: "size-3.5 opacity-50" })
|
|
744
|
+
]
|
|
745
|
+
}
|
|
746
|
+
),
|
|
747
|
+
/* @__PURE__ */ jsx12(DropdownMenuContent, { align: "start", children: items.map((item, i) => /* @__PURE__ */ jsxs12(
|
|
748
|
+
DropdownMenuItem,
|
|
749
|
+
{
|
|
750
|
+
className: cn(item.active && "text-primary"),
|
|
751
|
+
onClick: item.onClick,
|
|
752
|
+
render: item.href ? item.external ? /* @__PURE__ */ jsx12("a", { href: item.href, target: "_blank", rel: "noopener noreferrer" }) : /* @__PURE__ */ jsx12("a", { href: item.href }) : void 0,
|
|
753
|
+
children: [
|
|
754
|
+
item.icon,
|
|
755
|
+
/* @__PURE__ */ jsxs12("span", { className: "flex-1", children: [
|
|
756
|
+
item.label,
|
|
757
|
+
item.external && " \u2197"
|
|
758
|
+
] }),
|
|
759
|
+
item.badge != null && /* @__PURE__ */ jsx12("span", { className: "rounded bg-danger/20 px-1.5 text-[10px] font-semibold text-danger", children: item.badge })
|
|
760
|
+
]
|
|
761
|
+
},
|
|
762
|
+
i
|
|
763
|
+
)) })
|
|
764
|
+
] });
|
|
765
|
+
}
|
|
766
|
+
function AppShell({
|
|
767
|
+
appName,
|
|
768
|
+
brandPrefix,
|
|
769
|
+
logo,
|
|
770
|
+
brandSubtitle,
|
|
771
|
+
brandBadge,
|
|
772
|
+
nav,
|
|
773
|
+
mobileTabs,
|
|
774
|
+
user,
|
|
775
|
+
headerActions,
|
|
776
|
+
sidebarFooter,
|
|
777
|
+
layout: layoutProp,
|
|
778
|
+
defaultLayout = "sidebar",
|
|
779
|
+
onLayoutChange,
|
|
780
|
+
pathname,
|
|
781
|
+
contained = false,
|
|
782
|
+
defaultCollapsed = false,
|
|
783
|
+
renderLink,
|
|
784
|
+
children,
|
|
785
|
+
className
|
|
786
|
+
}) {
|
|
787
|
+
const [uncontrolled, setUncontrolled] = useLayoutPreference(defaultLayout);
|
|
788
|
+
const layout = layoutProp ?? uncontrolled;
|
|
789
|
+
const setLayout = React3.useCallback(
|
|
790
|
+
(l) => {
|
|
791
|
+
onLayoutChange?.(l);
|
|
792
|
+
if (layoutProp === void 0) setUncontrolled(l);
|
|
793
|
+
},
|
|
794
|
+
[onLayoutChange, layoutProp, setUncontrolled]
|
|
795
|
+
);
|
|
796
|
+
const layoutCtx = React3.useMemo(() => ({ layout, setLayout }), [layout, setLayout]);
|
|
797
|
+
const [collapsed, setCollapsed] = React3.useState(defaultCollapsed);
|
|
798
|
+
const [drawer, setDrawer] = React3.useState(false);
|
|
799
|
+
const [open, setOpen] = React3.useState({});
|
|
800
|
+
const groups = React3.useMemo(() => {
|
|
801
|
+
const gs = toGroups(nav);
|
|
802
|
+
return gs.map((g) => ({ ...g, items: g.items.map((it) => withActive(it, pathname)) }));
|
|
803
|
+
}, [nav, pathname]);
|
|
804
|
+
const flat = React3.useMemo(() => allItems(groups), [groups]);
|
|
805
|
+
const tabs = (mobileTabs ?? flat).slice(0, 5);
|
|
806
|
+
const brand = /* @__PURE__ */ jsx12(
|
|
807
|
+
AppBrand,
|
|
808
|
+
{
|
|
809
|
+
appName,
|
|
810
|
+
prefix: brandPrefix,
|
|
811
|
+
logo,
|
|
812
|
+
subtitle: brandSubtitle,
|
|
813
|
+
badge: brandBadge,
|
|
814
|
+
renderLink
|
|
815
|
+
}
|
|
816
|
+
);
|
|
817
|
+
const isOpen = (g, i) => !g.collapsible || (open[g.label ?? String(i)] ?? g.defaultOpen ?? true);
|
|
818
|
+
const SidebarBody = ({ isCollapsed, onNavigate }) => /* @__PURE__ */ jsxs12("div", { className: "flex h-full flex-col", children: [
|
|
819
|
+
/* @__PURE__ */ jsxs12("div", { className: cn("flex h-12 items-center gap-2 border-b border-border px-3", isCollapsed && "justify-center px-0"), children: [
|
|
820
|
+
/* @__PURE__ */ jsx12(AppBrand, { appName, prefix: brandPrefix, logo, subtitle: brandSubtitle, collapsed: isCollapsed, renderLink, href: "/" }),
|
|
821
|
+
/* @__PURE__ */ jsx12(
|
|
822
|
+
"button",
|
|
823
|
+
{
|
|
824
|
+
type: "button",
|
|
825
|
+
onClick: () => setCollapsed((c) => !c),
|
|
826
|
+
className: "ml-auto hidden rounded-md p-1 text-muted-foreground hover:bg-muted hover:text-foreground md:inline-flex",
|
|
827
|
+
"aria-label": "Toggle sidebar",
|
|
828
|
+
children: isCollapsed ? /* @__PURE__ */ jsx12(PanelLeft, { className: "size-4" }) : /* @__PURE__ */ jsx12(PanelLeftClose, { className: "size-4" })
|
|
829
|
+
}
|
|
830
|
+
)
|
|
831
|
+
] }),
|
|
832
|
+
/* @__PURE__ */ jsx12("nav", { className: "flex-1 space-y-0.5 overflow-y-auto p-2", children: groups.map((g, gi) => /* @__PURE__ */ jsxs12("div", { className: cn(gi > 0 && !g.label && "mt-1"), children: [
|
|
833
|
+
g.label && !isCollapsed && /* @__PURE__ */ jsx12(
|
|
834
|
+
SectionHeader,
|
|
835
|
+
{
|
|
836
|
+
group: g,
|
|
837
|
+
open: isOpen(g, gi),
|
|
838
|
+
onToggle: () => setOpen((o) => ({ ...o, [g.label ?? String(gi)]: !isOpen(g, gi) }))
|
|
839
|
+
}
|
|
840
|
+
),
|
|
841
|
+
isOpen(g, gi) && g.items.map((item, i) => /* @__PURE__ */ jsxs12(React3.Fragment, { children: [
|
|
842
|
+
/* @__PURE__ */ jsx12(SidebarNavRow, { item, collapsed: isCollapsed, renderLink, onNavigate }),
|
|
843
|
+
!isCollapsed && item.items?.map((sub, si) => /* @__PURE__ */ jsx12(SidebarNavRow, { item: withActive(sub, pathname), renderLink, onNavigate, nested: true }, si))
|
|
844
|
+
] }, i))
|
|
845
|
+
] }, g.label ?? gi)) }),
|
|
846
|
+
(sidebarFooter || user) && /* @__PURE__ */ jsxs12("div", { className: "border-t border-border p-2", children: [
|
|
847
|
+
sidebarFooter,
|
|
848
|
+
user && /* @__PURE__ */ jsx12("div", { className: cn("mt-1", isCollapsed && "flex justify-center"), children: user })
|
|
849
|
+
] })
|
|
850
|
+
] });
|
|
851
|
+
const MobileDrawer = () => drawer ? /* @__PURE__ */ jsxs12("div", { className: "fixed inset-0 z-50 md:hidden", role: "dialog", "aria-modal": true, children: [
|
|
852
|
+
/* @__PURE__ */ jsx12("div", { className: "absolute inset-0 bg-black/50", onClick: () => setDrawer(false) }),
|
|
853
|
+
/* @__PURE__ */ jsxs12("div", { className: "absolute inset-y-0 left-0 w-[80vw] max-w-xs border-r border-border bg-card", children: [
|
|
854
|
+
/* @__PURE__ */ jsx12(
|
|
855
|
+
"button",
|
|
856
|
+
{
|
|
857
|
+
type: "button",
|
|
858
|
+
onClick: () => setDrawer(false),
|
|
859
|
+
className: "absolute right-2 top-2 rounded-md p-1.5 text-muted-foreground hover:bg-muted",
|
|
860
|
+
"aria-label": "Close menu",
|
|
861
|
+
children: /* @__PURE__ */ jsx12(X2, { className: "size-4" })
|
|
862
|
+
}
|
|
863
|
+
),
|
|
864
|
+
/* @__PURE__ */ jsx12(SidebarBody, { isCollapsed: false, onNavigate: () => setDrawer(false) })
|
|
865
|
+
] })
|
|
866
|
+
] }) : null;
|
|
867
|
+
const BottomTabs = () => /* @__PURE__ */ jsx12(
|
|
868
|
+
"nav",
|
|
869
|
+
{
|
|
870
|
+
className: "fixed inset-x-0 bottom-0 z-40 flex h-14 items-stretch border-t border-border bg-card md:hidden",
|
|
871
|
+
style: { paddingBottom: "env(safe-area-inset-bottom)" },
|
|
872
|
+
children: tabs.map((item, i) => {
|
|
873
|
+
const content = /* @__PURE__ */ jsxs12("span", { className: cn("flex h-full flex-1 flex-col items-center justify-center gap-0.5", item.active ? "text-primary" : "text-muted-foreground"), children: [
|
|
874
|
+
/* @__PURE__ */ jsx12("span", { className: "grid size-5 place-items-center [&_svg]:size-5", children: item.icon }),
|
|
875
|
+
/* @__PURE__ */ jsx12("span", { className: "text-[10px] font-medium leading-none", children: item.shortLabel ?? item.label })
|
|
876
|
+
] });
|
|
877
|
+
return item.href && item.external ? /* @__PURE__ */ jsx12("a", { href: item.href, target: "_blank", rel: "noopener noreferrer", className: "flex-1", children: content }, i) : item.href && renderLink ? /* @__PURE__ */ jsx12(React3.Fragment, { children: renderLink({ href: item.href, className: "flex-1", children: content }) }, i) : item.href ? /* @__PURE__ */ jsx12("a", { href: item.href, className: "flex-1", children: content }, i) : /* @__PURE__ */ jsx12("button", { type: "button", className: "flex-1", onClick: item.onClick, children: content }, i);
|
|
878
|
+
})
|
|
879
|
+
}
|
|
880
|
+
);
|
|
881
|
+
const MobileTopBar = () => /* @__PURE__ */ jsxs12("header", { className: "flex h-12 shrink-0 items-center gap-2 border-b border-border px-3 md:hidden", children: [
|
|
882
|
+
/* @__PURE__ */ jsx12("button", { type: "button", onClick: () => setDrawer(true), className: "rounded-md p-1.5 text-muted-foreground hover:bg-muted", "aria-label": "Open menu", children: /* @__PURE__ */ jsx12(Menu, { className: "size-5" }) }),
|
|
883
|
+
brand,
|
|
884
|
+
/* @__PURE__ */ jsx12("div", { className: "ml-auto flex items-center gap-1", children: headerActions })
|
|
885
|
+
] });
|
|
886
|
+
if (layout === "top") {
|
|
887
|
+
return /* @__PURE__ */ jsx12(LayoutContext.Provider, { value: layoutCtx, children: /* @__PURE__ */ jsxs12("div", { className: cn("flex min-h-screen flex-col bg-background text-foreground", className), children: [
|
|
888
|
+
/* @__PURE__ */ jsxs12("header", { className: "sticky top-0 z-40 hidden h-14 shrink-0 items-center gap-1 border-b border-border bg-card/95 px-4 backdrop-blur md:flex", children: [
|
|
889
|
+
/* @__PURE__ */ jsx12("div", { className: "shrink-0", children: brand }),
|
|
890
|
+
/* @__PURE__ */ jsx12("nav", { className: "ml-4 flex items-center gap-0.5", children: groups.map(
|
|
891
|
+
(g, gi) => g.label ? /* @__PURE__ */ jsx12(TopDropdown, { label: g.label, items: g.items, renderLink }, gi) : g.items.map(
|
|
892
|
+
(item, i) => item.items ? /* @__PURE__ */ jsx12(TopDropdown, { label: item.label, items: item.items, renderLink }, `${gi}-${i}`) : /* @__PURE__ */ jsx12(TopLink, { item, renderLink }, `${gi}-${i}`)
|
|
893
|
+
)
|
|
894
|
+
) }),
|
|
895
|
+
/* @__PURE__ */ jsxs12("div", { className: "ml-auto flex items-center gap-2", children: [
|
|
896
|
+
headerActions,
|
|
897
|
+
user
|
|
898
|
+
] })
|
|
899
|
+
] }),
|
|
900
|
+
/* @__PURE__ */ jsx12(MobileTopBar, {}),
|
|
901
|
+
/* @__PURE__ */ jsx12(MobileDrawer, {}),
|
|
902
|
+
/* @__PURE__ */ jsx12("main", { className: "flex-1 overflow-y-auto pb-20 md:pb-0", children: /* @__PURE__ */ jsx12("div", { className: cn(contained ? "mx-auto max-w-7xl px-4 py-6 sm:px-6 sm:py-8" : "p-4 sm:p-6"), children }) }),
|
|
903
|
+
/* @__PURE__ */ jsx12(BottomTabs, {})
|
|
904
|
+
] }) });
|
|
905
|
+
}
|
|
906
|
+
return /* @__PURE__ */ jsx12(LayoutContext.Provider, { value: layoutCtx, children: /* @__PURE__ */ jsxs12("div", { className: cn("flex h-screen overflow-hidden bg-background text-foreground", className), children: [
|
|
907
|
+
/* @__PURE__ */ jsx12("aside", { className: cn("hidden shrink-0 border-r border-border transition-[width] duration-200 md:block", collapsed ? railW : openW), children: /* @__PURE__ */ jsx12(SidebarBody, { isCollapsed: collapsed }) }),
|
|
908
|
+
/* @__PURE__ */ jsx12(MobileDrawer, {}),
|
|
909
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
910
|
+
/* @__PURE__ */ jsx12(MobileTopBar, {}),
|
|
911
|
+
headerActions && /* @__PURE__ */ jsx12("header", { className: "hidden h-12 shrink-0 items-center border-b border-border px-4 md:flex", children: /* @__PURE__ */ jsx12("div", { className: "ml-auto flex items-center gap-2", children: headerActions }) }),
|
|
912
|
+
/* @__PURE__ */ jsx12("main", { className: "flex-1 overflow-y-auto pb-20 md:pb-0", children: /* @__PURE__ */ jsx12("div", { className: cn(contained ? "mx-auto max-w-7xl px-4 py-6 sm:px-6 sm:py-8" : "p-4 sm:p-6"), children }) }),
|
|
913
|
+
/* @__PURE__ */ jsx12(BottomTabs, {})
|
|
914
|
+
] })
|
|
915
|
+
] }) });
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// src/sub-tabs/index.tsx
|
|
919
|
+
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
920
|
+
function SubTabs({
|
|
921
|
+
items,
|
|
922
|
+
value,
|
|
923
|
+
onChange,
|
|
924
|
+
className,
|
|
925
|
+
"aria-label": ariaLabel
|
|
926
|
+
}) {
|
|
927
|
+
return /* @__PURE__ */ jsx13(
|
|
928
|
+
"div",
|
|
929
|
+
{
|
|
930
|
+
role: "tablist",
|
|
931
|
+
"aria-label": ariaLabel,
|
|
932
|
+
className: cn("inline-flex rounded-lg border border-border bg-card p-0.5", className),
|
|
933
|
+
children: items.map((it) => {
|
|
934
|
+
const selected = value === it.id;
|
|
935
|
+
return /* @__PURE__ */ jsxs13(
|
|
936
|
+
"button",
|
|
937
|
+
{
|
|
938
|
+
type: "button",
|
|
939
|
+
role: "tab",
|
|
940
|
+
"aria-selected": selected,
|
|
941
|
+
onClick: () => onChange(it.id),
|
|
942
|
+
className: cn(
|
|
943
|
+
"inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm transition-colors [&_svg]:size-4",
|
|
944
|
+
selected ? "bg-primary/10 text-primary" : "text-muted-foreground hover:text-foreground"
|
|
945
|
+
),
|
|
946
|
+
children: [
|
|
947
|
+
it.icon,
|
|
948
|
+
it.label
|
|
949
|
+
]
|
|
950
|
+
},
|
|
951
|
+
it.id
|
|
952
|
+
);
|
|
953
|
+
})
|
|
954
|
+
}
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// src/page-header-banner/index.tsx
|
|
959
|
+
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
960
|
+
function PageHeaderBanner({
|
|
961
|
+
title,
|
|
962
|
+
subtitle,
|
|
963
|
+
actions,
|
|
964
|
+
gradient = true,
|
|
965
|
+
icon,
|
|
966
|
+
className
|
|
967
|
+
}) {
|
|
968
|
+
return /* @__PURE__ */ jsxs14(
|
|
969
|
+
"div",
|
|
970
|
+
{
|
|
971
|
+
className: cn(
|
|
972
|
+
"mb-6 flex flex-col gap-4 rounded-xl border border-border p-6 sm:mb-8 sm:flex-row sm:items-center sm:p-8",
|
|
973
|
+
className
|
|
974
|
+
),
|
|
975
|
+
style: gradient ? {
|
|
976
|
+
background: "linear-gradient(135deg, color-mix(in srgb, var(--primary) 10%, transparent), color-mix(in srgb, var(--accent) 5%, transparent))"
|
|
977
|
+
} : { background: "var(--card)" },
|
|
978
|
+
children: [
|
|
979
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 items-center gap-4", children: [
|
|
980
|
+
icon && /* @__PURE__ */ jsx14("span", { className: "grid size-12 shrink-0 place-items-center rounded-xl bg-primary/15 text-primary [&_svg]:size-6", children: icon }),
|
|
981
|
+
/* @__PURE__ */ jsxs14("div", { className: "min-w-0", children: [
|
|
982
|
+
/* @__PURE__ */ jsx14("h1", { className: "font-heading text-2xl font-bold tracking-tight text-foreground sm:text-3xl", children: title }),
|
|
983
|
+
subtitle && /* @__PURE__ */ jsx14("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle })
|
|
984
|
+
] })
|
|
985
|
+
] }),
|
|
986
|
+
actions && /* @__PURE__ */ jsx14("div", { className: "flex shrink-0 items-center gap-2 sm:ml-auto", children: actions })
|
|
987
|
+
]
|
|
988
|
+
}
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
// src/glass-panel/index.tsx
|
|
993
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
994
|
+
function GlassPanel({ strong, glow, className, style, ...props }) {
|
|
995
|
+
return /* @__PURE__ */ jsx15(
|
|
996
|
+
"div",
|
|
997
|
+
{
|
|
998
|
+
className: cn("rounded-2xl border", className),
|
|
999
|
+
style: {
|
|
1000
|
+
background: strong ? "var(--glass-bg-strong, rgba(255,255,255,0.08))" : "var(--glass-bg, rgba(255,255,255,0.04))",
|
|
1001
|
+
borderColor: "var(--glass-border, var(--border))",
|
|
1002
|
+
backdropFilter: "var(--glass-blur, blur(20px))",
|
|
1003
|
+
WebkitBackdropFilter: "var(--glass-blur, blur(20px))",
|
|
1004
|
+
boxShadow: glow ? "var(--shadow-glow)" : "var(--shadow-elevated, 0 8px 32px rgba(0,0,0,0.2))",
|
|
1005
|
+
...style
|
|
1006
|
+
},
|
|
1007
|
+
...props
|
|
1008
|
+
}
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/brand-frame/index.tsx
|
|
1013
|
+
import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1014
|
+
var POS = {
|
|
1015
|
+
"top-right": "top-4 right-4",
|
|
1016
|
+
"bottom-right": "bottom-4 right-4",
|
|
1017
|
+
center: "inset-0 flex items-center justify-center"
|
|
1018
|
+
};
|
|
1019
|
+
function BrandFrame({
|
|
1020
|
+
watermark,
|
|
1021
|
+
watermarkPosition = "top-right",
|
|
1022
|
+
watermarkOpacity = 0.06,
|
|
1023
|
+
glow = false,
|
|
1024
|
+
glass = false,
|
|
1025
|
+
animated = false,
|
|
1026
|
+
header,
|
|
1027
|
+
backdrop,
|
|
1028
|
+
className,
|
|
1029
|
+
children,
|
|
1030
|
+
style,
|
|
1031
|
+
...props
|
|
1032
|
+
}) {
|
|
1033
|
+
return /* @__PURE__ */ jsxs15(
|
|
1034
|
+
"div",
|
|
1035
|
+
{
|
|
1036
|
+
className: cn(
|
|
1037
|
+
"relative overflow-hidden rounded-2xl border border-border",
|
|
1038
|
+
glass ? "backdrop-blur-xl" : "bg-card",
|
|
1039
|
+
className
|
|
1040
|
+
),
|
|
1041
|
+
style: {
|
|
1042
|
+
...glass ? { background: "var(--glass-bg, color-mix(in srgb, var(--card) 70%, transparent))" } : null,
|
|
1043
|
+
...glow ? { boxShadow: "var(--shadow-glow, 0 4px 24px color-mix(in srgb, var(--primary) 28%, transparent))" } : null,
|
|
1044
|
+
...style
|
|
1045
|
+
},
|
|
1046
|
+
...props,
|
|
1047
|
+
children: [
|
|
1048
|
+
animated && /* @__PURE__ */ jsx16(
|
|
1049
|
+
"span",
|
|
1050
|
+
{
|
|
1051
|
+
"aria-hidden": true,
|
|
1052
|
+
"data-wmm-fx": true,
|
|
1053
|
+
className: "pointer-events-none absolute inset-x-0 top-0 h-px",
|
|
1054
|
+
style: {
|
|
1055
|
+
background: "var(--gradient-primary, linear-gradient(90deg, var(--primary), var(--accent)))",
|
|
1056
|
+
backgroundSize: "200% 100%",
|
|
1057
|
+
animation: "wmm-shimmer 3s linear infinite"
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
),
|
|
1061
|
+
backdrop && /* @__PURE__ */ jsx16("div", { className: "pointer-events-none absolute inset-0", children: backdrop }),
|
|
1062
|
+
watermark && /* @__PURE__ */ jsx16(
|
|
1063
|
+
"div",
|
|
1064
|
+
{
|
|
1065
|
+
"aria-hidden": true,
|
|
1066
|
+
className: cn("pointer-events-none absolute", POS[watermarkPosition]),
|
|
1067
|
+
style: { opacity: watermarkOpacity },
|
|
1068
|
+
children: watermark
|
|
1069
|
+
}
|
|
1070
|
+
),
|
|
1071
|
+
/* @__PURE__ */ jsxs15("div", { className: "relative", children: [
|
|
1072
|
+
header && /* @__PURE__ */ jsx16("div", { className: "flex items-center justify-between gap-3 border-b border-border px-5 py-3", children: header }),
|
|
1073
|
+
/* @__PURE__ */ jsx16("div", { className: "p-5", children })
|
|
1074
|
+
] })
|
|
1075
|
+
]
|
|
1076
|
+
}
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/use-action-runner.ts
|
|
1081
|
+
import * as React4 from "react";
|
|
1082
|
+
import { useToast } from "@web-my-money/primitives/toast";
|
|
1083
|
+
function useActionRunner() {
|
|
1084
|
+
const toast = useToast();
|
|
1085
|
+
const [pending, setPending] = React4.useState(false);
|
|
1086
|
+
const run = React4.useCallback(
|
|
1087
|
+
async (action, options = {}) => {
|
|
1088
|
+
setPending(true);
|
|
1089
|
+
try {
|
|
1090
|
+
const result = await action();
|
|
1091
|
+
if (result.ok) {
|
|
1092
|
+
if (options.successMessage) {
|
|
1093
|
+
const title = typeof options.successMessage === "function" ? options.successMessage(result.data) : options.successMessage;
|
|
1094
|
+
toast.add({
|
|
1095
|
+
title,
|
|
1096
|
+
actionProps: options.undo ? {
|
|
1097
|
+
children: options.undo.label ?? "Undo",
|
|
1098
|
+
onClick: () => {
|
|
1099
|
+
void options.undo.action();
|
|
1100
|
+
}
|
|
1101
|
+
} : void 0
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
options.onSuccess?.(result.data);
|
|
1105
|
+
} else {
|
|
1106
|
+
const title = typeof options.errorMessage === "function" ? options.errorMessage(result.error) : options.errorMessage ?? result.error;
|
|
1107
|
+
toast.add({ title, type: "error" });
|
|
1108
|
+
options.onError?.(result.error);
|
|
1109
|
+
}
|
|
1110
|
+
return result;
|
|
1111
|
+
} finally {
|
|
1112
|
+
setPending(false);
|
|
1113
|
+
options.onSettled?.();
|
|
1114
|
+
}
|
|
1115
|
+
},
|
|
1116
|
+
[toast]
|
|
1117
|
+
);
|
|
1118
|
+
return { run, pending };
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// src/use-optimistic-action.ts
|
|
1122
|
+
import * as React5 from "react";
|
|
1123
|
+
function useOptimisticAction(state, reducer) {
|
|
1124
|
+
const [optimisticState, setOptimistic] = React5.useOptimistic(state, reducer);
|
|
1125
|
+
const [isPending, startTransition] = React5.useTransition();
|
|
1126
|
+
const apply = React5.useCallback(
|
|
1127
|
+
(optimisticValue, action, onResult) => {
|
|
1128
|
+
startTransition(async () => {
|
|
1129
|
+
setOptimistic(optimisticValue);
|
|
1130
|
+
const result = await action();
|
|
1131
|
+
onResult?.(result);
|
|
1132
|
+
});
|
|
1133
|
+
},
|
|
1134
|
+
[setOptimistic]
|
|
1135
|
+
);
|
|
1136
|
+
return [optimisticState, apply, isPending];
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/appearance-settings/index.tsx
|
|
1140
|
+
import { ThemeToggle, FontSizeToggle } from "@web-my-money/tokens/react";
|
|
1141
|
+
import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1142
|
+
function AppearanceSettings({ className }) {
|
|
1143
|
+
return /* @__PURE__ */ jsxs16("div", { className: cn("space-y-6", className), "data-slot": "appearance-settings", children: [
|
|
1144
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center justify-between gap-4", "data-slot": "appearance-settings-row", children: [
|
|
1145
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1146
|
+
/* @__PURE__ */ jsx17("div", { className: "text-sm font-medium text-foreground", children: "Theme" }),
|
|
1147
|
+
/* @__PURE__ */ jsx17("div", { className: "text-xs text-muted-foreground", children: "Light, dark, or the WMM brand theme." })
|
|
1148
|
+
] }),
|
|
1149
|
+
/* @__PURE__ */ jsx17(ThemeToggle, {})
|
|
1150
|
+
] }),
|
|
1151
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center justify-between gap-4", "data-slot": "appearance-settings-row", children: [
|
|
1152
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1153
|
+
/* @__PURE__ */ jsx17("div", { className: "text-sm font-medium text-foreground", children: "Text size" }),
|
|
1154
|
+
/* @__PURE__ */ jsx17("div", { className: "text-xs text-muted-foreground", children: "Scales the whole interface." })
|
|
1155
|
+
] }),
|
|
1156
|
+
/* @__PURE__ */ jsx17(FontSizeToggle, {})
|
|
1157
|
+
] })
|
|
1158
|
+
] });
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
export {
|
|
1162
|
+
NotificationBanner,
|
|
1163
|
+
StatCard,
|
|
1164
|
+
PageHeader,
|
|
1165
|
+
EmptyState,
|
|
1166
|
+
DashboardHeader,
|
|
1167
|
+
DataSummaryCards,
|
|
1168
|
+
DataTable,
|
|
1169
|
+
SidebarNavigation,
|
|
1170
|
+
KPIRow,
|
|
1171
|
+
ActivityFeed,
|
|
1172
|
+
AppBrand,
|
|
1173
|
+
useLayoutPreference,
|
|
1174
|
+
LayoutToggle,
|
|
1175
|
+
AppShell,
|
|
1176
|
+
SubTabs,
|
|
1177
|
+
PageHeaderBanner,
|
|
1178
|
+
GlassPanel,
|
|
1179
|
+
BrandFrame,
|
|
1180
|
+
useActionRunner,
|
|
1181
|
+
useOptimisticAction,
|
|
1182
|
+
AppearanceSettings
|
|
1183
|
+
};
|