@jdcpuwiz/homelab-ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +228 -0
- package/dist/index.cjs +602 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +302 -0
- package/dist/index.d.ts +302 -0
- package/dist/index.js +565 -0
- package/dist/index.js.map +1 -0
- package/globals.css +56 -0
- package/package.json +86 -0
- package/tailwind-preset.cjs +135 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var src_exports = {};
|
|
23
|
+
__export(src_exports, {
|
|
24
|
+
Button: () => Button,
|
|
25
|
+
ConfirmDialog: () => ConfirmDialog,
|
|
26
|
+
EmptyState: () => EmptyState,
|
|
27
|
+
FONT_CSS_VARIABLES: () => FONT_CSS_VARIABLES,
|
|
28
|
+
Modal: () => Modal,
|
|
29
|
+
PageHeader: () => PageHeader,
|
|
30
|
+
Sidebar: () => Sidebar,
|
|
31
|
+
SidebarNavItem: () => SidebarNavItem,
|
|
32
|
+
Spinner: () => Spinner,
|
|
33
|
+
TagChips: () => TagChips,
|
|
34
|
+
cn: () => cn,
|
|
35
|
+
tagTextColor: () => tagTextColor
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(src_exports);
|
|
38
|
+
|
|
39
|
+
// src/components/Button.tsx
|
|
40
|
+
var import_react = require("react");
|
|
41
|
+
|
|
42
|
+
// src/lib/utils.ts
|
|
43
|
+
var import_clsx = require("clsx");
|
|
44
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
45
|
+
function cn(...inputs) {
|
|
46
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/components/Button.tsx
|
|
50
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
51
|
+
var BASE = "inline-flex items-center justify-center gap-1.5 rounded-[0.5rem] font-semibold transition active:scale-95 disabled:cursor-not-allowed disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ffb84d]";
|
|
52
|
+
var VARIANT = {
|
|
53
|
+
primary: "bg-[#ff9900] text-[#0a0a0a] hover:bg-[#cc6a1a]",
|
|
54
|
+
secondary: "bg-[#1e1e1e] text-white hover:bg-[#252525]",
|
|
55
|
+
tertiary: "text-white/60 hover:bg-[#252525] hover:text-white",
|
|
56
|
+
danger: "bg-[#b91c1c] text-white hover:brightness-110"
|
|
57
|
+
};
|
|
58
|
+
var SIZE = {
|
|
59
|
+
sm: "px-3 py-1 text-xs",
|
|
60
|
+
md: "px-4 py-1.5 text-sm"
|
|
61
|
+
};
|
|
62
|
+
var Button = (0, import_react.forwardRef)(
|
|
63
|
+
function Button2({ variant = "secondary", size = "md", className, ...rest }, ref) {
|
|
64
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
65
|
+
"button",
|
|
66
|
+
{
|
|
67
|
+
ref,
|
|
68
|
+
...rest,
|
|
69
|
+
className: cn(BASE, VARIANT[variant], SIZE[size], className)
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// src/components/ConfirmDialog.tsx
|
|
76
|
+
var import_react3 = require("react");
|
|
77
|
+
|
|
78
|
+
// src/components/Modal.tsx
|
|
79
|
+
var import_react2 = require("react");
|
|
80
|
+
var import_lucide_react = require("lucide-react");
|
|
81
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
82
|
+
function Modal({
|
|
83
|
+
open,
|
|
84
|
+
onClose,
|
|
85
|
+
title,
|
|
86
|
+
showClose,
|
|
87
|
+
density = "form",
|
|
88
|
+
maxWidth = "max-w-md",
|
|
89
|
+
footer,
|
|
90
|
+
children
|
|
91
|
+
}) {
|
|
92
|
+
const titleIdRef = (0, import_react2.useRef)(
|
|
93
|
+
`hl-modal-${Math.random().toString(36).slice(2, 8)}`
|
|
94
|
+
);
|
|
95
|
+
(0, import_react2.useEffect)(() => {
|
|
96
|
+
if (!open) return;
|
|
97
|
+
const onKey = (e) => {
|
|
98
|
+
if (e.key === "Escape") onClose();
|
|
99
|
+
};
|
|
100
|
+
window.addEventListener("keydown", onKey);
|
|
101
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
102
|
+
}, [open, onClose]);
|
|
103
|
+
if (!open) return null;
|
|
104
|
+
const showCloseBtn = showClose ?? Boolean(title);
|
|
105
|
+
const overlayClass = density === "media" ? "bg-black/90" : "bg-black/70";
|
|
106
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
107
|
+
"div",
|
|
108
|
+
{
|
|
109
|
+
className: cn(
|
|
110
|
+
"fixed inset-0 z-50 flex items-center justify-center p-4",
|
|
111
|
+
overlayClass
|
|
112
|
+
),
|
|
113
|
+
onClick: onClose,
|
|
114
|
+
role: "presentation",
|
|
115
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
116
|
+
"div",
|
|
117
|
+
{
|
|
118
|
+
role: "dialog",
|
|
119
|
+
"aria-modal": "true",
|
|
120
|
+
"aria-labelledby": title ? titleIdRef.current : void 0,
|
|
121
|
+
className: cn(
|
|
122
|
+
"w-full rounded-[1rem] border-2 border-[#1e1e1e] bg-[#111111] p-5",
|
|
123
|
+
maxWidth
|
|
124
|
+
),
|
|
125
|
+
onClick: (e) => e.stopPropagation(),
|
|
126
|
+
children: [
|
|
127
|
+
(title || showCloseBtn) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
|
|
128
|
+
title && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
129
|
+
"h2",
|
|
130
|
+
{
|
|
131
|
+
id: titleIdRef.current,
|
|
132
|
+
className: "text-sm font-semibold uppercase tracking-widest text-white",
|
|
133
|
+
children: title
|
|
134
|
+
}
|
|
135
|
+
),
|
|
136
|
+
showCloseBtn && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
137
|
+
"button",
|
|
138
|
+
{
|
|
139
|
+
type: "button",
|
|
140
|
+
onClick: onClose,
|
|
141
|
+
className: "ml-auto rounded-[0.25rem] p-1 text-white/40 transition hover:bg-[#252525] hover:text-white active:scale-90",
|
|
142
|
+
"aria-label": "Close",
|
|
143
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.X, { className: "h-4 w-4" })
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
] }),
|
|
147
|
+
children,
|
|
148
|
+
footer && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-5 flex flex-wrap items-center justify-end gap-2", children: footer })
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// src/components/ConfirmDialog.tsx
|
|
157
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
158
|
+
function ConfirmDialog({
|
|
159
|
+
open,
|
|
160
|
+
title,
|
|
161
|
+
message,
|
|
162
|
+
confirmLabel = "Confirm",
|
|
163
|
+
cancelLabel = "Cancel",
|
|
164
|
+
tone = "danger",
|
|
165
|
+
onConfirm,
|
|
166
|
+
onClose
|
|
167
|
+
}) {
|
|
168
|
+
(0, import_react3.useEffect)(() => {
|
|
169
|
+
if (!open) return;
|
|
170
|
+
const onKey = (e) => {
|
|
171
|
+
if (e.key === "Enter") onConfirm();
|
|
172
|
+
};
|
|
173
|
+
window.addEventListener("keydown", onKey);
|
|
174
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
175
|
+
}, [open, onConfirm]);
|
|
176
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
177
|
+
Modal,
|
|
178
|
+
{
|
|
179
|
+
open,
|
|
180
|
+
onClose,
|
|
181
|
+
title,
|
|
182
|
+
footer: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
183
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Button, { variant: "tertiary", size: "md", onClick: onClose, children: cancelLabel }),
|
|
184
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
185
|
+
Button,
|
|
186
|
+
{
|
|
187
|
+
variant: tone === "danger" ? "danger" : "primary",
|
|
188
|
+
size: "md",
|
|
189
|
+
onClick: onConfirm,
|
|
190
|
+
children: confirmLabel
|
|
191
|
+
}
|
|
192
|
+
)
|
|
193
|
+
] }),
|
|
194
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "whitespace-pre-line text-sm text-white/70", children: message })
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/components/EmptyState.tsx
|
|
200
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
201
|
+
function EmptyState({
|
|
202
|
+
icon,
|
|
203
|
+
title,
|
|
204
|
+
message,
|
|
205
|
+
action,
|
|
206
|
+
variant = "panel",
|
|
207
|
+
className
|
|
208
|
+
}) {
|
|
209
|
+
if (variant === "inline") {
|
|
210
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: cn("px-3 py-2 text-xs text-white/40", className), children: message });
|
|
211
|
+
}
|
|
212
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
213
|
+
"div",
|
|
214
|
+
{
|
|
215
|
+
className: cn(
|
|
216
|
+
"flex flex-col items-center justify-center gap-3 rounded-[1rem] bg-[#2a2620] p-12 text-center",
|
|
217
|
+
className
|
|
218
|
+
),
|
|
219
|
+
children: [
|
|
220
|
+
icon && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "text-white/30", children: icon }),
|
|
221
|
+
title && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { className: "text-sm font-semibold text-white", children: title }),
|
|
222
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-sm text-white/60", children: message }),
|
|
223
|
+
action
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/components/PageHeader.tsx
|
|
230
|
+
var import_react4 = require("react");
|
|
231
|
+
var import_lucide_react2 = require("lucide-react");
|
|
232
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
233
|
+
function PageHeader({
|
|
234
|
+
title,
|
|
235
|
+
description,
|
|
236
|
+
back,
|
|
237
|
+
actions,
|
|
238
|
+
imageSrc,
|
|
239
|
+
imageAlt,
|
|
240
|
+
breadcrumb,
|
|
241
|
+
className
|
|
242
|
+
}) {
|
|
243
|
+
const [imageErrored, setImageErrored] = (0, import_react4.useState)(false);
|
|
244
|
+
(0, import_react4.useEffect)(() => {
|
|
245
|
+
setImageErrored(false);
|
|
246
|
+
}, [imageSrc]);
|
|
247
|
+
const hasImageSlot = !!imageSrc;
|
|
248
|
+
const imageRenders = hasImageSlot && !imageErrored;
|
|
249
|
+
const hasBreadcrumb = Array.isArray(breadcrumb) && breadcrumb.length > 0;
|
|
250
|
+
const textIsSrOnly = hasImageSlot;
|
|
251
|
+
const backInner = back ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
|
|
252
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.ArrowLeft, { className: "h-3 w-3" }),
|
|
253
|
+
back.label
|
|
254
|
+
] }) : null;
|
|
255
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
256
|
+
"header",
|
|
257
|
+
{
|
|
258
|
+
className: cn(
|
|
259
|
+
"flex flex-wrap items-end justify-between gap-3",
|
|
260
|
+
className
|
|
261
|
+
),
|
|
262
|
+
children: [
|
|
263
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex min-w-0 items-end gap-4", children: [
|
|
264
|
+
hasImageSlot && (imageRenders ? (
|
|
265
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
266
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
267
|
+
"img",
|
|
268
|
+
{
|
|
269
|
+
src: imageSrc,
|
|
270
|
+
alt: imageAlt ?? title,
|
|
271
|
+
onError: () => setImageErrored(true),
|
|
272
|
+
className: "h-28 w-28 shrink-0 rounded-[1rem] object-contain md:h-32 md:w-32"
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(PlaceholderHeader, { title, imageSrc })),
|
|
276
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "min-w-0", children: [
|
|
277
|
+
back && (back.render ? back.render(
|
|
278
|
+
back.href,
|
|
279
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "inline-flex items-center gap-1 text-xs text-white/40 transition hover:text-white", children: backInner })
|
|
280
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
281
|
+
"a",
|
|
282
|
+
{
|
|
283
|
+
href: back.href,
|
|
284
|
+
className: "inline-flex items-center gap-1 text-xs text-white/40 transition hover:text-white",
|
|
285
|
+
children: backInner
|
|
286
|
+
}
|
|
287
|
+
)),
|
|
288
|
+
hasBreadcrumb ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
289
|
+
Breadcrumb,
|
|
290
|
+
{
|
|
291
|
+
crumbs: breadcrumb,
|
|
292
|
+
srTitle: title,
|
|
293
|
+
className: back ? "mt-1" : ""
|
|
294
|
+
}
|
|
295
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
296
|
+
"h1",
|
|
297
|
+
{
|
|
298
|
+
className: cn(
|
|
299
|
+
"text-xl font-semibold text-[#aa89b7]",
|
|
300
|
+
back && "mt-1",
|
|
301
|
+
textIsSrOnly && "sr-only"
|
|
302
|
+
),
|
|
303
|
+
children: title
|
|
304
|
+
}
|
|
305
|
+
),
|
|
306
|
+
description && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-1 text-xs text-white/40", children: description })
|
|
307
|
+
] })
|
|
308
|
+
] }),
|
|
309
|
+
actions && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "flex items-center gap-2", children: actions })
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
function PlaceholderHeader({
|
|
315
|
+
title,
|
|
316
|
+
imageSrc
|
|
317
|
+
}) {
|
|
318
|
+
const file = imageSrc.replace(/^\//, "");
|
|
319
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
320
|
+
"div",
|
|
321
|
+
{
|
|
322
|
+
title: `Drop ${file} in /public to brand this header`,
|
|
323
|
+
className: "flex h-28 w-28 shrink-0 flex-col items-center justify-center gap-1 rounded-[1rem] border-2 border-dashed border-[#1e1e1e] bg-[#2a2620] text-white/30 md:h-32 md:w-32",
|
|
324
|
+
children: [
|
|
325
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.ImageOff, { className: "h-6 w-6" }),
|
|
326
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "px-2 text-center text-[10px] font-semibold uppercase tracking-widest", children: title })
|
|
327
|
+
]
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
function Breadcrumb({
|
|
332
|
+
crumbs,
|
|
333
|
+
srTitle,
|
|
334
|
+
className
|
|
335
|
+
}) {
|
|
336
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
337
|
+
"nav",
|
|
338
|
+
{
|
|
339
|
+
"aria-label": "Breadcrumb",
|
|
340
|
+
className: cn(
|
|
341
|
+
"flex flex-wrap items-center gap-1.5 text-xl font-semibold",
|
|
342
|
+
className
|
|
343
|
+
),
|
|
344
|
+
children: [
|
|
345
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h1", { className: "sr-only", children: srTitle }),
|
|
346
|
+
crumbs.map((crumb, idx) => {
|
|
347
|
+
const isLast = idx === crumbs.length - 1;
|
|
348
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
349
|
+
"span",
|
|
350
|
+
{
|
|
351
|
+
className: "flex items-center gap-1.5",
|
|
352
|
+
children: [
|
|
353
|
+
idx > 0 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
354
|
+
import_lucide_react2.ChevronRight,
|
|
355
|
+
{
|
|
356
|
+
className: "h-4 w-4 shrink-0 text-white/30",
|
|
357
|
+
"aria-hidden": "true"
|
|
358
|
+
}
|
|
359
|
+
),
|
|
360
|
+
crumb.onClick && !isLast ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
361
|
+
"button",
|
|
362
|
+
{
|
|
363
|
+
type: "button",
|
|
364
|
+
onClick: crumb.onClick,
|
|
365
|
+
className: "rounded px-1 text-white/45 transition hover:text-white",
|
|
366
|
+
children: crumb.label
|
|
367
|
+
}
|
|
368
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
369
|
+
"span",
|
|
370
|
+
{
|
|
371
|
+
className: cn(
|
|
372
|
+
isLast ? "text-white" : "text-white/45",
|
|
373
|
+
isLast && "px-1"
|
|
374
|
+
),
|
|
375
|
+
"aria-current": isLast ? "page" : void 0,
|
|
376
|
+
children: crumb.label
|
|
377
|
+
}
|
|
378
|
+
)
|
|
379
|
+
]
|
|
380
|
+
},
|
|
381
|
+
`${crumb.label}-${idx}`
|
|
382
|
+
);
|
|
383
|
+
})
|
|
384
|
+
]
|
|
385
|
+
}
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/components/Sidebar.tsx
|
|
390
|
+
var import_react5 = require("react");
|
|
391
|
+
var import_lucide_react3 = require("lucide-react");
|
|
392
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
393
|
+
function Sidebar({
|
|
394
|
+
logoSrc,
|
|
395
|
+
logoAlt,
|
|
396
|
+
widgets,
|
|
397
|
+
nav,
|
|
398
|
+
middle,
|
|
399
|
+
lower,
|
|
400
|
+
admin,
|
|
401
|
+
footer,
|
|
402
|
+
mobileOpen: controlledOpen,
|
|
403
|
+
onMobileOpenChange,
|
|
404
|
+
className,
|
|
405
|
+
showMobileTopBar = true
|
|
406
|
+
}) {
|
|
407
|
+
const [internalOpen, setInternalOpen] = (0, import_react5.useState)(false);
|
|
408
|
+
const isControlled = controlledOpen !== void 0;
|
|
409
|
+
const open = isControlled ? controlledOpen : internalOpen;
|
|
410
|
+
const setOpen = (next) => {
|
|
411
|
+
if (!isControlled) setInternalOpen(next);
|
|
412
|
+
onMobileOpenChange?.(next);
|
|
413
|
+
};
|
|
414
|
+
(0, import_react5.useEffect)(() => {
|
|
415
|
+
if (typeof document === "undefined") return;
|
|
416
|
+
if (!open) return;
|
|
417
|
+
const prev = document.body.style.overflow;
|
|
418
|
+
document.body.style.overflow = "hidden";
|
|
419
|
+
return () => {
|
|
420
|
+
document.body.style.overflow = prev;
|
|
421
|
+
};
|
|
422
|
+
}, [open]);
|
|
423
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
|
|
424
|
+
showMobileTopBar && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "fixed inset-x-0 top-0 z-30 flex items-center gap-2 border-b-2 border-[#1e1e1e] bg-[#111111] px-3 py-2 md:hidden", children: [
|
|
425
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
426
|
+
"button",
|
|
427
|
+
{
|
|
428
|
+
type: "button",
|
|
429
|
+
onClick: () => setOpen(true),
|
|
430
|
+
className: "rounded-[0.25rem] p-1.5 text-white transition hover:bg-[#252525] active:scale-90",
|
|
431
|
+
"aria-label": "Open menu",
|
|
432
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Menu, { className: "h-5 w-5" })
|
|
433
|
+
}
|
|
434
|
+
),
|
|
435
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: logoSrc, alt: "", width: 24, height: 24 }),
|
|
436
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm font-semibold text-white", children: logoAlt })
|
|
437
|
+
] }),
|
|
438
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
439
|
+
"div",
|
|
440
|
+
{
|
|
441
|
+
className: "fixed inset-0 z-40 bg-black/70 md:hidden",
|
|
442
|
+
onClick: () => setOpen(false),
|
|
443
|
+
role: "presentation"
|
|
444
|
+
}
|
|
445
|
+
),
|
|
446
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
447
|
+
"aside",
|
|
448
|
+
{
|
|
449
|
+
className: cn(
|
|
450
|
+
"fixed inset-y-0 left-0 z-50 flex w-60 shrink-0 flex-col border-r-2 border-[#1e1e1e] bg-[#111111] transition-transform md:static md:translate-x-0 md:h-screen",
|
|
451
|
+
open ? "translate-x-0" : "-translate-x-full md:translate-x-0",
|
|
452
|
+
className
|
|
453
|
+
),
|
|
454
|
+
children: [
|
|
455
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "relative flex flex-col items-center border-b-2 border-[#1e1e1e] px-4 pt-6 pb-5", children: [
|
|
456
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
457
|
+
"img",
|
|
458
|
+
{
|
|
459
|
+
src: logoSrc,
|
|
460
|
+
alt: logoAlt,
|
|
461
|
+
className: "h-36 w-36 rounded-[1rem] object-contain"
|
|
462
|
+
}
|
|
463
|
+
),
|
|
464
|
+
showMobileTopBar && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
465
|
+
"button",
|
|
466
|
+
{
|
|
467
|
+
type: "button",
|
|
468
|
+
onClick: () => setOpen(false),
|
|
469
|
+
className: "absolute right-3 top-3 rounded-[0.25rem] p-1 text-white/40 transition hover:bg-[#252525] hover:text-white md:hidden",
|
|
470
|
+
"aria-label": "Close menu",
|
|
471
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.X, { className: "h-4 w-4" })
|
|
472
|
+
}
|
|
473
|
+
)
|
|
474
|
+
] }),
|
|
475
|
+
widgets && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-3 mt-3", children: widgets }),
|
|
476
|
+
nav && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("nav", { className: "mx-3 mt-3 space-y-0.5 rounded-[0.75rem] bg-[#1e1e1e] p-2", children: nav }),
|
|
477
|
+
middle && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mx-3 mt-3 flex-1 overflow-y-auto rounded-[0.75rem] bg-[#1e1e1e] p-2", children: middle }),
|
|
478
|
+
lower && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("nav", { className: "mx-3 mt-3 space-y-0.5 rounded-[0.75rem] bg-[#1e1e1e] p-2", children: lower }),
|
|
479
|
+
!middle && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-1" }),
|
|
480
|
+
admin && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: admin }),
|
|
481
|
+
footer && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "border-t-2 border-[#1e1e1e] px-4 py-3 text-center text-xs text-white/20", children: footer })
|
|
482
|
+
]
|
|
483
|
+
}
|
|
484
|
+
)
|
|
485
|
+
] });
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// src/components/SidebarNavItem.tsx
|
|
489
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
490
|
+
var ITEM_CLASS_BASE = "flex items-center gap-2 rounded-[0.5rem] px-3 py-2.5 text-base font-medium transition-all";
|
|
491
|
+
var itemClass = (active) => cn(
|
|
492
|
+
ITEM_CLASS_BASE,
|
|
493
|
+
active ? "bg-[#ff9900] text-white" : "text-white/45 hover:bg-white/5 hover:text-white"
|
|
494
|
+
);
|
|
495
|
+
function SidebarNavItem({
|
|
496
|
+
label,
|
|
497
|
+
icon,
|
|
498
|
+
active = false,
|
|
499
|
+
onClick,
|
|
500
|
+
href,
|
|
501
|
+
render
|
|
502
|
+
}) {
|
|
503
|
+
const className = itemClass(active);
|
|
504
|
+
const inner = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
|
|
505
|
+
icon,
|
|
506
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: label })
|
|
507
|
+
] });
|
|
508
|
+
if (href && render) return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: render({ href, className }, inner) });
|
|
509
|
+
if (href) {
|
|
510
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { href, className, children: inner });
|
|
511
|
+
}
|
|
512
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
513
|
+
"button",
|
|
514
|
+
{
|
|
515
|
+
type: "button",
|
|
516
|
+
onClick,
|
|
517
|
+
"aria-pressed": active,
|
|
518
|
+
className: `${className} w-full`,
|
|
519
|
+
children: inner
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// src/components/Spinner.tsx
|
|
525
|
+
var import_lucide_react4 = require("lucide-react");
|
|
526
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
527
|
+
function Spinner({ label = "Loading\u2026", className }) {
|
|
528
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
529
|
+
"p",
|
|
530
|
+
{
|
|
531
|
+
className: cn(
|
|
532
|
+
"flex items-center gap-2 px-3 py-2 text-xs text-white/40",
|
|
533
|
+
className
|
|
534
|
+
),
|
|
535
|
+
children: [
|
|
536
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_lucide_react4.Loader2, { className: "h-3 w-3 animate-spin" }),
|
|
537
|
+
label
|
|
538
|
+
]
|
|
539
|
+
}
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/lib/colors.ts
|
|
544
|
+
function tagTextColor(hex) {
|
|
545
|
+
const normalized = hex.trim().replace(/^#/, "");
|
|
546
|
+
if (normalized.length !== 3 && normalized.length !== 6) return "#ffffff";
|
|
547
|
+
const expand = normalized.length === 3 ? normalized.split("").map((c) => c + c).join("") : normalized;
|
|
548
|
+
const r = parseInt(expand.slice(0, 2), 16);
|
|
549
|
+
const g = parseInt(expand.slice(2, 4), 16);
|
|
550
|
+
const b = parseInt(expand.slice(4, 6), 16);
|
|
551
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b)) return "#ffffff";
|
|
552
|
+
const luma = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
553
|
+
return luma > 0.65 ? "#000000" : "#ffffff";
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/components/TagChips.tsx
|
|
557
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
558
|
+
function TagChips({ tags, size = "xs", onClick }) {
|
|
559
|
+
if (tags.length === 0) return null;
|
|
560
|
+
const sizeClasses = size === "xs" ? "px-1.5 py-0.5 text-[10px]" : "px-2 py-0.5 text-xs";
|
|
561
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex flex-wrap gap-1", children: tags.map((t) => {
|
|
562
|
+
const style = {
|
|
563
|
+
backgroundColor: t.color,
|
|
564
|
+
color: tagTextColor(t.color)
|
|
565
|
+
};
|
|
566
|
+
const className = `inline-block rounded-[0.25rem] font-semibold ${sizeClasses}`;
|
|
567
|
+
return onClick ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
568
|
+
"button",
|
|
569
|
+
{
|
|
570
|
+
type: "button",
|
|
571
|
+
onClick: () => onClick(t),
|
|
572
|
+
className: `${className} transition active:scale-95`,
|
|
573
|
+
style,
|
|
574
|
+
children: t.name
|
|
575
|
+
},
|
|
576
|
+
t.id
|
|
577
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className, style, children: t.name }, t.id);
|
|
578
|
+
}) });
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/lib/fonts.ts
|
|
582
|
+
var FONT_CSS_VARIABLES = {
|
|
583
|
+
sans: "--font-geist-sans",
|
|
584
|
+
mono: "--font-geist-mono",
|
|
585
|
+
display: "--font-orbitron"
|
|
586
|
+
};
|
|
587
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
588
|
+
0 && (module.exports = {
|
|
589
|
+
Button,
|
|
590
|
+
ConfirmDialog,
|
|
591
|
+
EmptyState,
|
|
592
|
+
FONT_CSS_VARIABLES,
|
|
593
|
+
Modal,
|
|
594
|
+
PageHeader,
|
|
595
|
+
Sidebar,
|
|
596
|
+
SidebarNavItem,
|
|
597
|
+
Spinner,
|
|
598
|
+
TagChips,
|
|
599
|
+
cn,
|
|
600
|
+
tagTextColor
|
|
601
|
+
});
|
|
602
|
+
//# sourceMappingURL=index.cjs.map
|