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