@camstack/ui-library 0.1.25
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/assets/brand/stacked-lens-current.svg +14 -0
- package/assets/brand/stacked-lens-light.svg +13 -0
- package/assets/brand/stacked-lens-mono.svg +13 -0
- package/assets/brand/stacked-lens-primary.svg +13 -0
- package/dist/index.cjs +2086 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +415 -0
- package/dist/index.d.ts +415 -0
- package/dist/index.js +2043 -0
- package/dist/index.js.map +1 -0
- package/dist/theme/index.cjs +306 -0
- package/dist/theme/index.cjs.map +1 -0
- package/dist/theme/index.d.cts +128 -0
- package/dist/theme/index.d.ts +128 -0
- package/dist/theme/index.js +273 -0
- package/dist/theme/index.js.map +1 -0
- package/package.json +58 -0
- package/src/tailwind/camstack-theme.css +96 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2043 @@
|
|
|
1
|
+
// src/theme/defaults.ts
|
|
2
|
+
var providerColors = {
|
|
3
|
+
frigate: "#3b82f6",
|
|
4
|
+
scrypted: "#a855f7",
|
|
5
|
+
reolink: "#06b6d4",
|
|
6
|
+
homeAssistant: "#22d3ee",
|
|
7
|
+
rtsp: "#78716c"
|
|
8
|
+
};
|
|
9
|
+
var darkColors = {
|
|
10
|
+
primary: "#f59e42",
|
|
11
|
+
primaryForeground: "#0c0a09",
|
|
12
|
+
background: "#0c0a09",
|
|
13
|
+
backgroundElevated: "#1c1917",
|
|
14
|
+
surface: "#1c1917",
|
|
15
|
+
surfaceHover: "#292524",
|
|
16
|
+
border: "#292524",
|
|
17
|
+
borderSubtle: "#1c1917",
|
|
18
|
+
foreground: "#fafaf9",
|
|
19
|
+
foregroundMuted: "#a8a29e",
|
|
20
|
+
foregroundSubtle: "#78716c",
|
|
21
|
+
foregroundDisabled: "#57534e",
|
|
22
|
+
success: "#4ade80",
|
|
23
|
+
warning: "#fbbf24",
|
|
24
|
+
danger: "#f87171",
|
|
25
|
+
info: "#60a5fa",
|
|
26
|
+
provider: providerColors
|
|
27
|
+
};
|
|
28
|
+
var lightColors = {
|
|
29
|
+
primary: "#e67e22",
|
|
30
|
+
primaryForeground: "#ffffff",
|
|
31
|
+
background: "#fafaf9",
|
|
32
|
+
backgroundElevated: "#ffffff",
|
|
33
|
+
surface: "#f5f5f4",
|
|
34
|
+
surfaceHover: "#e7e5e4",
|
|
35
|
+
border: "#d6d3d1",
|
|
36
|
+
borderSubtle: "#e7e5e4",
|
|
37
|
+
foreground: "#1c1917",
|
|
38
|
+
foregroundMuted: "#57534e",
|
|
39
|
+
foregroundSubtle: "#78716c",
|
|
40
|
+
foregroundDisabled: "#a8a29e",
|
|
41
|
+
success: "#16a34a",
|
|
42
|
+
warning: "#d97706",
|
|
43
|
+
danger: "#dc2626",
|
|
44
|
+
info: "#2563eb",
|
|
45
|
+
provider: providerColors
|
|
46
|
+
};
|
|
47
|
+
var defaultTheme = {
|
|
48
|
+
colors: {
|
|
49
|
+
dark: darkColors,
|
|
50
|
+
light: lightColors
|
|
51
|
+
},
|
|
52
|
+
spacing: {
|
|
53
|
+
xs: 2,
|
|
54
|
+
sm: 4,
|
|
55
|
+
md: 8,
|
|
56
|
+
lg: 12,
|
|
57
|
+
xl: 16,
|
|
58
|
+
"2xl": 24,
|
|
59
|
+
"3xl": 32
|
|
60
|
+
},
|
|
61
|
+
radius: {
|
|
62
|
+
sm: 4,
|
|
63
|
+
md: 6,
|
|
64
|
+
lg: 8,
|
|
65
|
+
xl: 12
|
|
66
|
+
},
|
|
67
|
+
typography: {
|
|
68
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
69
|
+
sizes: {
|
|
70
|
+
xs: { fontSize: 10, lineHeight: 14 },
|
|
71
|
+
sm: { fontSize: 11, lineHeight: 16 },
|
|
72
|
+
base: { fontSize: 12, lineHeight: 18 },
|
|
73
|
+
lg: { fontSize: 13, lineHeight: 18 },
|
|
74
|
+
xl: { fontSize: 14, lineHeight: 20 },
|
|
75
|
+
"2xl": { fontSize: 16, lineHeight: 22 },
|
|
76
|
+
"3xl": { fontSize: 20, lineHeight: 28 }
|
|
77
|
+
},
|
|
78
|
+
weights: {
|
|
79
|
+
regular: 400,
|
|
80
|
+
medium: 500,
|
|
81
|
+
semibold: 600,
|
|
82
|
+
bold: 700
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
table: {
|
|
86
|
+
rowHeight: 28,
|
|
87
|
+
headerHeight: 24,
|
|
88
|
+
cellPaddingX: 8,
|
|
89
|
+
cellPaddingY: 6
|
|
90
|
+
},
|
|
91
|
+
sidebar: {
|
|
92
|
+
width: 176,
|
|
93
|
+
itemHeight: 28,
|
|
94
|
+
iconSize: 14
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/theme/create-theme.ts
|
|
99
|
+
function deepMerge(target, source) {
|
|
100
|
+
const result = { ...target };
|
|
101
|
+
for (const key in source) {
|
|
102
|
+
const sourceVal = source[key];
|
|
103
|
+
const targetVal = target[key];
|
|
104
|
+
if (sourceVal !== void 0 && typeof sourceVal === "object" && sourceVal !== null && !Array.isArray(sourceVal) && typeof targetVal === "object" && targetVal !== null) {
|
|
105
|
+
result[key] = deepMerge(
|
|
106
|
+
targetVal,
|
|
107
|
+
sourceVal
|
|
108
|
+
);
|
|
109
|
+
} else if (sourceVal !== void 0) {
|
|
110
|
+
result[key] = sourceVal;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
function createTheme(overrides) {
|
|
116
|
+
if (!overrides) return structuredClone(defaultTheme);
|
|
117
|
+
return deepMerge(structuredClone(defaultTheme), overrides);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/theme/theme-to-css.ts
|
|
121
|
+
function camelToKebab(str) {
|
|
122
|
+
return str.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
123
|
+
}
|
|
124
|
+
function colorTokenToCssVar(key) {
|
|
125
|
+
return `--color-${camelToKebab(key)}`;
|
|
126
|
+
}
|
|
127
|
+
function generateColorBlock(colors) {
|
|
128
|
+
const lines = [];
|
|
129
|
+
for (const [key, value] of Object.entries(colors)) {
|
|
130
|
+
if (key === "provider") continue;
|
|
131
|
+
lines.push(` ${colorTokenToCssVar(key)}: ${value};`);
|
|
132
|
+
}
|
|
133
|
+
return lines.join("\n");
|
|
134
|
+
}
|
|
135
|
+
function generateProviderColors(provider) {
|
|
136
|
+
const lines = [];
|
|
137
|
+
for (const [key, value] of Object.entries(provider)) {
|
|
138
|
+
lines.push(` --color-provider-${camelToKebab(key)}: ${value};`);
|
|
139
|
+
}
|
|
140
|
+
return lines.join("\n");
|
|
141
|
+
}
|
|
142
|
+
function generateSpacingTokens(spacing) {
|
|
143
|
+
const lines = [];
|
|
144
|
+
for (const [key, value] of Object.entries(spacing)) {
|
|
145
|
+
lines.push(` --spacing-${key}: ${value}px;`);
|
|
146
|
+
}
|
|
147
|
+
return lines.join("\n");
|
|
148
|
+
}
|
|
149
|
+
function generateRadiusTokens(radius) {
|
|
150
|
+
const lines = [];
|
|
151
|
+
for (const [key, value] of Object.entries(radius)) {
|
|
152
|
+
lines.push(` --radius-${key}: ${value}px;`);
|
|
153
|
+
}
|
|
154
|
+
return lines.join("\n");
|
|
155
|
+
}
|
|
156
|
+
function themeToCss(theme) {
|
|
157
|
+
const darkColorBlock = generateColorBlock(theme.colors.dark);
|
|
158
|
+
const lightColorBlock = generateColorBlock(theme.colors.light);
|
|
159
|
+
const providerBlock = generateProviderColors(theme.colors.dark.provider);
|
|
160
|
+
const spacingBlock = generateSpacingTokens(theme.spacing);
|
|
161
|
+
const radiusBlock = generateRadiusTokens(theme.radius);
|
|
162
|
+
return `@theme {
|
|
163
|
+
${providerBlock}
|
|
164
|
+
${spacingBlock}
|
|
165
|
+
${radiusBlock}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.dark {
|
|
169
|
+
${darkColorBlock}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.light {
|
|
173
|
+
${lightColorBlock}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@media (prefers-color-scheme: dark) {
|
|
177
|
+
:root {
|
|
178
|
+
${darkColorBlock.replace(/^ /gm, " ")}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@media (prefers-color-scheme: light) {
|
|
183
|
+
:root {
|
|
184
|
+
${lightColorBlock.replace(/^ /gm, " ")}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/theme/theme-provider.tsx
|
|
191
|
+
import { createContext, useCallback, useEffect, useMemo, useState } from "react";
|
|
192
|
+
import { jsx } from "react/jsx-runtime";
|
|
193
|
+
var ThemeContext = createContext(null);
|
|
194
|
+
var TOGGLE_ORDER = ["dark", "light", "system"];
|
|
195
|
+
function getSystemPreference() {
|
|
196
|
+
if (typeof window === "undefined") return "dark";
|
|
197
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
198
|
+
}
|
|
199
|
+
function getInitialMode(storageKey, defaultMode) {
|
|
200
|
+
if (typeof window === "undefined") return defaultMode;
|
|
201
|
+
const stored = localStorage.getItem(storageKey);
|
|
202
|
+
if (stored === "dark" || stored === "light" || stored === "system") {
|
|
203
|
+
return stored;
|
|
204
|
+
}
|
|
205
|
+
return defaultMode;
|
|
206
|
+
}
|
|
207
|
+
function resolveMode(mode) {
|
|
208
|
+
if (mode === "system") return getSystemPreference();
|
|
209
|
+
return mode;
|
|
210
|
+
}
|
|
211
|
+
function ThemeProvider({
|
|
212
|
+
children,
|
|
213
|
+
defaultMode = "system",
|
|
214
|
+
storageKey = "camstack-theme-mode"
|
|
215
|
+
}) {
|
|
216
|
+
const [mode, setModeState] = useState(() => getInitialMode(storageKey, defaultMode));
|
|
217
|
+
const [resolvedMode, setResolvedMode] = useState(() => resolveMode(mode));
|
|
218
|
+
const setMode = useCallback(
|
|
219
|
+
(newMode) => {
|
|
220
|
+
setModeState(newMode);
|
|
221
|
+
setResolvedMode(resolveMode(newMode));
|
|
222
|
+
if (typeof window !== "undefined") {
|
|
223
|
+
localStorage.setItem(storageKey, newMode);
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
[storageKey]
|
|
227
|
+
);
|
|
228
|
+
const toggleMode = useCallback(() => {
|
|
229
|
+
const currentIndex = TOGGLE_ORDER.indexOf(mode);
|
|
230
|
+
const nextIndex = (currentIndex + 1) % TOGGLE_ORDER.length;
|
|
231
|
+
setMode(TOGGLE_ORDER[nextIndex] ?? "dark");
|
|
232
|
+
}, [mode, setMode]);
|
|
233
|
+
useEffect(() => {
|
|
234
|
+
if (typeof document === "undefined") return;
|
|
235
|
+
const root = document.documentElement;
|
|
236
|
+
root.classList.remove("dark", "light");
|
|
237
|
+
root.classList.add(resolvedMode);
|
|
238
|
+
}, [mode, resolvedMode]);
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
if (typeof window === "undefined" || mode !== "system") return;
|
|
241
|
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
242
|
+
const handleChange = () => {
|
|
243
|
+
setResolvedMode(getSystemPreference());
|
|
244
|
+
};
|
|
245
|
+
mediaQuery.addEventListener("change", handleChange);
|
|
246
|
+
return () => mediaQuery.removeEventListener("change", handleChange);
|
|
247
|
+
}, [mode]);
|
|
248
|
+
const value = useMemo(
|
|
249
|
+
() => ({ mode, resolvedMode, setMode, toggleMode }),
|
|
250
|
+
[mode, resolvedMode, setMode, toggleMode]
|
|
251
|
+
);
|
|
252
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// src/theme/use-theme-mode.ts
|
|
256
|
+
import { useContext } from "react";
|
|
257
|
+
function useThemeMode() {
|
|
258
|
+
const context = useContext(ThemeContext);
|
|
259
|
+
if (!context) {
|
|
260
|
+
throw new Error("useThemeMode must be used within a ThemeProvider");
|
|
261
|
+
}
|
|
262
|
+
return context;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/lib/cn.ts
|
|
266
|
+
import { clsx } from "clsx";
|
|
267
|
+
import { twMerge } from "tailwind-merge";
|
|
268
|
+
function cn(...inputs) {
|
|
269
|
+
return twMerge(clsx(inputs));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/icons/provider-icons.ts
|
|
273
|
+
import { Ship, Shield, Radio, Home, Cast } from "lucide-react";
|
|
274
|
+
var providerIcons = {
|
|
275
|
+
frigate: Ship,
|
|
276
|
+
scrypted: Shield,
|
|
277
|
+
reolink: Radio,
|
|
278
|
+
homeAssistant: Home,
|
|
279
|
+
rtsp: Cast
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/icons/status-icons.ts
|
|
283
|
+
import { CircleCheck, CircleX, CircleAlert, CircleHelp } from "lucide-react";
|
|
284
|
+
var statusIcons = {
|
|
285
|
+
online: CircleCheck,
|
|
286
|
+
offline: CircleX,
|
|
287
|
+
degraded: CircleAlert,
|
|
288
|
+
unknown: CircleHelp
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/primitives/button.tsx
|
|
292
|
+
import { forwardRef } from "react";
|
|
293
|
+
import { cva } from "class-variance-authority";
|
|
294
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
295
|
+
var buttonVariants = cva(
|
|
296
|
+
"inline-flex items-center justify-center rounded-md font-medium transition-colors disabled:opacity-50 disabled:pointer-events-none",
|
|
297
|
+
{
|
|
298
|
+
variants: {
|
|
299
|
+
variant: {
|
|
300
|
+
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
301
|
+
secondary: "bg-surface text-foreground hover:bg-surface-hover",
|
|
302
|
+
ghost: "hover:bg-surface-hover text-foreground-muted",
|
|
303
|
+
danger: "bg-danger text-white hover:bg-danger/90",
|
|
304
|
+
outline: "border border-border bg-transparent hover:bg-surface-hover"
|
|
305
|
+
},
|
|
306
|
+
size: {
|
|
307
|
+
sm: "h-7 px-2.5 text-xs gap-1.5",
|
|
308
|
+
md: "h-8 px-3 text-sm gap-2",
|
|
309
|
+
lg: "h-9 px-4 text-sm gap-2"
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
defaultVariants: {
|
|
313
|
+
variant: "primary",
|
|
314
|
+
size: "sm"
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
);
|
|
318
|
+
var Button = forwardRef(
|
|
319
|
+
({ className, variant, size, ...props }, ref) => {
|
|
320
|
+
return /* @__PURE__ */ jsx2(
|
|
321
|
+
"button",
|
|
322
|
+
{
|
|
323
|
+
ref,
|
|
324
|
+
className: cn(buttonVariants({ variant, size }), className),
|
|
325
|
+
...props
|
|
326
|
+
}
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
);
|
|
330
|
+
Button.displayName = "Button";
|
|
331
|
+
|
|
332
|
+
// src/primitives/icon-button.tsx
|
|
333
|
+
import { forwardRef as forwardRef2 } from "react";
|
|
334
|
+
import { cva as cva2 } from "class-variance-authority";
|
|
335
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
336
|
+
var iconButtonVariants = cva2(
|
|
337
|
+
"inline-flex items-center justify-center rounded-md font-medium transition-colors disabled:opacity-50 disabled:pointer-events-none",
|
|
338
|
+
{
|
|
339
|
+
variants: {
|
|
340
|
+
variant: {
|
|
341
|
+
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
342
|
+
secondary: "bg-surface text-foreground hover:bg-surface-hover",
|
|
343
|
+
ghost: "hover:bg-surface-hover text-foreground-muted",
|
|
344
|
+
danger: "bg-danger text-white hover:bg-danger/90",
|
|
345
|
+
outline: "border border-border bg-transparent hover:bg-surface-hover"
|
|
346
|
+
},
|
|
347
|
+
size: {
|
|
348
|
+
sm: "h-7 w-7",
|
|
349
|
+
md: "h-8 w-8",
|
|
350
|
+
lg: "h-9 w-9"
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
defaultVariants: {
|
|
354
|
+
variant: "primary",
|
|
355
|
+
size: "sm"
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
var IconButton = forwardRef2(
|
|
360
|
+
({ className, variant, size, icon: Icon, ...props }, ref) => {
|
|
361
|
+
return /* @__PURE__ */ jsx3(
|
|
362
|
+
"button",
|
|
363
|
+
{
|
|
364
|
+
ref,
|
|
365
|
+
className: cn(iconButtonVariants({ variant, size }), className),
|
|
366
|
+
...props,
|
|
367
|
+
children: /* @__PURE__ */ jsx3(Icon, { className: "h-4 w-4" })
|
|
368
|
+
}
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
);
|
|
372
|
+
IconButton.displayName = "IconButton";
|
|
373
|
+
|
|
374
|
+
// src/primitives/badge.tsx
|
|
375
|
+
import { forwardRef as forwardRef3 } from "react";
|
|
376
|
+
import { cva as cva3 } from "class-variance-authority";
|
|
377
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
378
|
+
var badgeVariants = cva3(
|
|
379
|
+
"inline-flex items-center rounded-full text-xs font-medium px-2 py-0.5",
|
|
380
|
+
{
|
|
381
|
+
variants: {
|
|
382
|
+
variant: {
|
|
383
|
+
default: "bg-surface-hover text-foreground",
|
|
384
|
+
success: "bg-success/15 text-success",
|
|
385
|
+
warning: "bg-warning/15 text-warning",
|
|
386
|
+
danger: "bg-danger/15 text-danger",
|
|
387
|
+
info: "bg-info/15 text-info"
|
|
388
|
+
},
|
|
389
|
+
styleVariant: {
|
|
390
|
+
solid: "",
|
|
391
|
+
outline: "border bg-transparent"
|
|
392
|
+
}
|
|
393
|
+
},
|
|
394
|
+
defaultVariants: {
|
|
395
|
+
variant: "default",
|
|
396
|
+
styleVariant: "solid"
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
var Badge = forwardRef3(
|
|
401
|
+
({ className, variant, style, ...props }, ref) => {
|
|
402
|
+
return /* @__PURE__ */ jsx4(
|
|
403
|
+
"span",
|
|
404
|
+
{
|
|
405
|
+
ref,
|
|
406
|
+
className: cn(badgeVariants({ variant, styleVariant: style }), className),
|
|
407
|
+
...props
|
|
408
|
+
}
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
);
|
|
412
|
+
Badge.displayName = "Badge";
|
|
413
|
+
|
|
414
|
+
// src/primitives/card.tsx
|
|
415
|
+
import { forwardRef as forwardRef4 } from "react";
|
|
416
|
+
import { cva as cva4 } from "class-variance-authority";
|
|
417
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
418
|
+
var cardVariants = cva4("rounded-lg p-3", {
|
|
419
|
+
variants: {
|
|
420
|
+
variant: {
|
|
421
|
+
flat: "bg-surface",
|
|
422
|
+
bordered: "bg-surface border border-border"
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
defaultVariants: {
|
|
426
|
+
variant: "bordered"
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
var Card = forwardRef4(
|
|
430
|
+
({ className, variant, ...props }, ref) => {
|
|
431
|
+
return /* @__PURE__ */ jsx5(
|
|
432
|
+
"div",
|
|
433
|
+
{
|
|
434
|
+
ref,
|
|
435
|
+
className: cn(cardVariants({ variant }), className),
|
|
436
|
+
...props
|
|
437
|
+
}
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
);
|
|
441
|
+
Card.displayName = "Card";
|
|
442
|
+
|
|
443
|
+
// src/primitives/label.tsx
|
|
444
|
+
import { forwardRef as forwardRef5 } from "react";
|
|
445
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
446
|
+
var Label = forwardRef5(
|
|
447
|
+
({ className, ...props }, ref) => {
|
|
448
|
+
return /* @__PURE__ */ jsx6(
|
|
449
|
+
"label",
|
|
450
|
+
{
|
|
451
|
+
ref,
|
|
452
|
+
className: cn(
|
|
453
|
+
"text-[10px] uppercase tracking-wider text-foreground-muted font-medium",
|
|
454
|
+
className
|
|
455
|
+
),
|
|
456
|
+
...props
|
|
457
|
+
}
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
);
|
|
461
|
+
Label.displayName = "Label";
|
|
462
|
+
|
|
463
|
+
// src/primitives/separator.tsx
|
|
464
|
+
import { forwardRef as forwardRef6 } from "react";
|
|
465
|
+
import { cva as cva5 } from "class-variance-authority";
|
|
466
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
467
|
+
var separatorVariants = cva5("", {
|
|
468
|
+
variants: {
|
|
469
|
+
orientation: {
|
|
470
|
+
horizontal: "h-px w-full bg-border-subtle",
|
|
471
|
+
vertical: "w-px h-full bg-border-subtle"
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
defaultVariants: {
|
|
475
|
+
orientation: "horizontal"
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
var Separator = forwardRef6(
|
|
479
|
+
({ className, orientation, ...props }, ref) => {
|
|
480
|
+
return /* @__PURE__ */ jsx7(
|
|
481
|
+
"div",
|
|
482
|
+
{
|
|
483
|
+
ref,
|
|
484
|
+
role: "separator",
|
|
485
|
+
className: cn(separatorVariants({ orientation }), className),
|
|
486
|
+
...props
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
);
|
|
491
|
+
Separator.displayName = "Separator";
|
|
492
|
+
|
|
493
|
+
// src/primitives/skeleton.tsx
|
|
494
|
+
import { forwardRef as forwardRef7 } from "react";
|
|
495
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
496
|
+
var Skeleton = forwardRef7(
|
|
497
|
+
({ className, ...props }, ref) => {
|
|
498
|
+
return /* @__PURE__ */ jsx8(
|
|
499
|
+
"div",
|
|
500
|
+
{
|
|
501
|
+
ref,
|
|
502
|
+
className: cn("animate-pulse bg-surface-hover rounded-md h-4 w-full", className),
|
|
503
|
+
...props
|
|
504
|
+
}
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
);
|
|
508
|
+
Skeleton.displayName = "Skeleton";
|
|
509
|
+
|
|
510
|
+
// src/primitives/input.tsx
|
|
511
|
+
import { forwardRef as forwardRef8 } from "react";
|
|
512
|
+
import { cva as cva6 } from "class-variance-authority";
|
|
513
|
+
import { jsx as jsx9, jsxs } from "react/jsx-runtime";
|
|
514
|
+
var inputVariants = cva6(
|
|
515
|
+
"h-7 w-full px-2.5 text-xs bg-surface border rounded-md text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-1 focus:ring-primary transition-colors",
|
|
516
|
+
{
|
|
517
|
+
variants: {
|
|
518
|
+
state: {
|
|
519
|
+
default: "border-border",
|
|
520
|
+
error: "border-danger"
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
defaultVariants: {
|
|
524
|
+
state: "default"
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
var Input = forwardRef8(
|
|
529
|
+
({ className, state, leftSlot, rightSlot, ...props }, ref) => {
|
|
530
|
+
if (leftSlot || rightSlot) {
|
|
531
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative flex items-center w-full", children: [
|
|
532
|
+
leftSlot && /* @__PURE__ */ jsx9("div", { className: "absolute left-2.5 flex items-center pointer-events-none", children: leftSlot }),
|
|
533
|
+
/* @__PURE__ */ jsx9(
|
|
534
|
+
"input",
|
|
535
|
+
{
|
|
536
|
+
ref,
|
|
537
|
+
className: cn(
|
|
538
|
+
inputVariants({ state }),
|
|
539
|
+
leftSlot ? "pl-7" : "",
|
|
540
|
+
rightSlot ? "pr-7" : "",
|
|
541
|
+
className
|
|
542
|
+
),
|
|
543
|
+
...props
|
|
544
|
+
}
|
|
545
|
+
),
|
|
546
|
+
rightSlot && /* @__PURE__ */ jsx9("div", { className: "absolute right-2.5 flex items-center pointer-events-none", children: rightSlot })
|
|
547
|
+
] });
|
|
548
|
+
}
|
|
549
|
+
return /* @__PURE__ */ jsx9(
|
|
550
|
+
"input",
|
|
551
|
+
{
|
|
552
|
+
ref,
|
|
553
|
+
className: cn(inputVariants({ state }), className),
|
|
554
|
+
...props
|
|
555
|
+
}
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
);
|
|
559
|
+
Input.displayName = "Input";
|
|
560
|
+
|
|
561
|
+
// src/primitives/select.tsx
|
|
562
|
+
import { forwardRef as forwardRef9 } from "react";
|
|
563
|
+
import { ChevronDown } from "lucide-react";
|
|
564
|
+
import { jsx as jsx10, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
565
|
+
var Select = forwardRef9(
|
|
566
|
+
({ className, options, ...props }, ref) => {
|
|
567
|
+
return /* @__PURE__ */ jsxs2("div", { className: "relative w-full", children: [
|
|
568
|
+
/* @__PURE__ */ jsx10(
|
|
569
|
+
"select",
|
|
570
|
+
{
|
|
571
|
+
ref,
|
|
572
|
+
className: cn(
|
|
573
|
+
"h-7 w-full px-2.5 text-xs bg-surface border border-border rounded-md text-foreground appearance-none pr-7 focus:outline-none focus:ring-1 focus:ring-primary",
|
|
574
|
+
className
|
|
575
|
+
),
|
|
576
|
+
...props,
|
|
577
|
+
children: options.map((option) => /* @__PURE__ */ jsx10("option", { value: option.value, children: option.label }, option.value))
|
|
578
|
+
}
|
|
579
|
+
),
|
|
580
|
+
/* @__PURE__ */ jsx10("div", { className: "absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none text-foreground-muted", children: /* @__PURE__ */ jsx10(ChevronDown, { className: "h-3.5 w-3.5" }) })
|
|
581
|
+
] });
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
Select.displayName = "Select";
|
|
585
|
+
|
|
586
|
+
// src/primitives/checkbox.tsx
|
|
587
|
+
import { forwardRef as forwardRef10 } from "react";
|
|
588
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
589
|
+
var Checkbox = forwardRef10(
|
|
590
|
+
({ className, ...props }, ref) => {
|
|
591
|
+
return /* @__PURE__ */ jsx11(
|
|
592
|
+
"input",
|
|
593
|
+
{
|
|
594
|
+
ref,
|
|
595
|
+
type: "checkbox",
|
|
596
|
+
className: cn(
|
|
597
|
+
"w-3.5 h-3.5 rounded-sm border border-border accent-primary",
|
|
598
|
+
className
|
|
599
|
+
),
|
|
600
|
+
...props
|
|
601
|
+
}
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
Checkbox.displayName = "Checkbox";
|
|
606
|
+
|
|
607
|
+
// src/primitives/switch.tsx
|
|
608
|
+
import { forwardRef as forwardRef11 } from "react";
|
|
609
|
+
import { jsx as jsx12, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
610
|
+
var Switch = forwardRef11(
|
|
611
|
+
({ className, checked, onCheckedChange, label, ...props }, ref) => {
|
|
612
|
+
return /* @__PURE__ */ jsxs3(
|
|
613
|
+
"button",
|
|
614
|
+
{
|
|
615
|
+
ref,
|
|
616
|
+
role: "switch",
|
|
617
|
+
"aria-checked": checked,
|
|
618
|
+
type: "button",
|
|
619
|
+
onClick: () => onCheckedChange(!checked),
|
|
620
|
+
className: cn("inline-flex items-center gap-2", className),
|
|
621
|
+
...props,
|
|
622
|
+
children: [
|
|
623
|
+
/* @__PURE__ */ jsx12(
|
|
624
|
+
"span",
|
|
625
|
+
{
|
|
626
|
+
className: cn(
|
|
627
|
+
"w-8 h-4 rounded-full transition-colors",
|
|
628
|
+
checked ? "bg-primary" : "bg-surface-hover"
|
|
629
|
+
),
|
|
630
|
+
children: /* @__PURE__ */ jsx12(
|
|
631
|
+
"span",
|
|
632
|
+
{
|
|
633
|
+
className: cn(
|
|
634
|
+
"block w-3.5 h-3.5 rounded-full bg-white transition-transform",
|
|
635
|
+
checked ? "translate-x-4" : "translate-x-0.5"
|
|
636
|
+
)
|
|
637
|
+
}
|
|
638
|
+
)
|
|
639
|
+
}
|
|
640
|
+
),
|
|
641
|
+
label && /* @__PURE__ */ jsx12("span", { className: "text-xs text-foreground", children: label })
|
|
642
|
+
]
|
|
643
|
+
}
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
);
|
|
647
|
+
Switch.displayName = "Switch";
|
|
648
|
+
|
|
649
|
+
// src/primitives/dialog.tsx
|
|
650
|
+
import {
|
|
651
|
+
createContext as createContext2,
|
|
652
|
+
forwardRef as forwardRef12,
|
|
653
|
+
useCallback as useCallback2,
|
|
654
|
+
useContext as useContext2,
|
|
655
|
+
useEffect as useEffect2,
|
|
656
|
+
useId,
|
|
657
|
+
useRef,
|
|
658
|
+
useState as useState2
|
|
659
|
+
} from "react";
|
|
660
|
+
import { cva as cva7 } from "class-variance-authority";
|
|
661
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
662
|
+
var DialogContext = createContext2(null);
|
|
663
|
+
function useDialogContext() {
|
|
664
|
+
const ctx = useContext2(DialogContext);
|
|
665
|
+
if (!ctx) throw new Error("Dialog compound components must be used within <Dialog>");
|
|
666
|
+
return ctx;
|
|
667
|
+
}
|
|
668
|
+
function Dialog({ children, open: controlledOpen, onOpenChange }) {
|
|
669
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState2(false);
|
|
670
|
+
const open = controlledOpen ?? uncontrolledOpen;
|
|
671
|
+
const contentId = useId();
|
|
672
|
+
const setOpen = useCallback2(
|
|
673
|
+
(next) => {
|
|
674
|
+
onOpenChange?.(next);
|
|
675
|
+
if (controlledOpen === void 0) setUncontrolledOpen(next);
|
|
676
|
+
},
|
|
677
|
+
[controlledOpen, onOpenChange]
|
|
678
|
+
);
|
|
679
|
+
return /* @__PURE__ */ jsx13(DialogContext.Provider, { value: { open, setOpen, contentId }, children });
|
|
680
|
+
}
|
|
681
|
+
function DialogTrigger({ children, ...props }) {
|
|
682
|
+
const { setOpen } = useDialogContext();
|
|
683
|
+
return /* @__PURE__ */ jsx13("button", { type: "button", onClick: () => setOpen(true), ...props, children });
|
|
684
|
+
}
|
|
685
|
+
var contentVariants = cva7(
|
|
686
|
+
"bg-background-elevated border border-border rounded-lg p-4 backdrop:bg-black/50 backdrop:backdrop-blur-sm",
|
|
687
|
+
{
|
|
688
|
+
variants: {
|
|
689
|
+
width: {
|
|
690
|
+
sm: "max-w-sm",
|
|
691
|
+
md: "max-w-md",
|
|
692
|
+
lg: "max-w-lg"
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
defaultVariants: { width: "md" }
|
|
696
|
+
}
|
|
697
|
+
);
|
|
698
|
+
var DialogContent = forwardRef12(
|
|
699
|
+
({ className, width, children, ...props }, ref) => {
|
|
700
|
+
const { open, setOpen, contentId } = useDialogContext();
|
|
701
|
+
const innerRef = useRef(null);
|
|
702
|
+
const dialogRef = ref ?? innerRef;
|
|
703
|
+
useEffect2(() => {
|
|
704
|
+
const el = dialogRef.current;
|
|
705
|
+
if (!el) return;
|
|
706
|
+
if (open && !el.open) el.showModal();
|
|
707
|
+
if (!open && el.open) el.close();
|
|
708
|
+
}, [open, dialogRef]);
|
|
709
|
+
const handleClick = (e) => {
|
|
710
|
+
if (e.target === e.currentTarget) setOpen(false);
|
|
711
|
+
};
|
|
712
|
+
return /* @__PURE__ */ jsx13(
|
|
713
|
+
"dialog",
|
|
714
|
+
{
|
|
715
|
+
ref: dialogRef,
|
|
716
|
+
id: contentId,
|
|
717
|
+
className: cn(contentVariants({ width }), "w-full", className),
|
|
718
|
+
onClick: handleClick,
|
|
719
|
+
onClose: () => setOpen(false),
|
|
720
|
+
...props,
|
|
721
|
+
children
|
|
722
|
+
}
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
);
|
|
726
|
+
DialogContent.displayName = "DialogContent";
|
|
727
|
+
function DialogHeader({ className, ...props }) {
|
|
728
|
+
return /* @__PURE__ */ jsx13("div", { className: cn("flex flex-col gap-1 mb-3", className), ...props });
|
|
729
|
+
}
|
|
730
|
+
function DialogFooter({ className, ...props }) {
|
|
731
|
+
return /* @__PURE__ */ jsx13("div", { className: cn("flex justify-end gap-2 mt-4", className), ...props });
|
|
732
|
+
}
|
|
733
|
+
function DialogTitle({ className, ...props }) {
|
|
734
|
+
return /* @__PURE__ */ jsx13("h2", { className: cn("text-sm font-semibold text-foreground", className), ...props });
|
|
735
|
+
}
|
|
736
|
+
function DialogDescription({ className, ...props }) {
|
|
737
|
+
return /* @__PURE__ */ jsx13("p", { className: cn("text-xs text-foreground-muted", className), ...props });
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// src/primitives/dropdown.tsx
|
|
741
|
+
import {
|
|
742
|
+
createContext as createContext3,
|
|
743
|
+
useCallback as useCallback3,
|
|
744
|
+
useContext as useContext3,
|
|
745
|
+
useEffect as useEffect3,
|
|
746
|
+
useId as useId2,
|
|
747
|
+
useRef as useRef2,
|
|
748
|
+
useState as useState3
|
|
749
|
+
} from "react";
|
|
750
|
+
import { jsx as jsx14, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
751
|
+
var DropdownContext = createContext3(null);
|
|
752
|
+
function useDropdownContext() {
|
|
753
|
+
const ctx = useContext3(DropdownContext);
|
|
754
|
+
if (!ctx) throw new Error("Dropdown compound components must be used within <Dropdown>");
|
|
755
|
+
return ctx;
|
|
756
|
+
}
|
|
757
|
+
function Dropdown({ children }) {
|
|
758
|
+
const [open, setOpen] = useState3(false);
|
|
759
|
+
const triggerId = useId2();
|
|
760
|
+
const contentId = useId2();
|
|
761
|
+
return /* @__PURE__ */ jsx14(DropdownContext.Provider, { value: { open, setOpen, triggerId, contentId }, children: /* @__PURE__ */ jsx14("div", { className: "relative inline-block", children }) });
|
|
762
|
+
}
|
|
763
|
+
function DropdownTrigger({ children, ...props }) {
|
|
764
|
+
const { open, setOpen, triggerId, contentId } = useDropdownContext();
|
|
765
|
+
return /* @__PURE__ */ jsx14(
|
|
766
|
+
"button",
|
|
767
|
+
{
|
|
768
|
+
type: "button",
|
|
769
|
+
id: triggerId,
|
|
770
|
+
"aria-haspopup": "menu",
|
|
771
|
+
"aria-expanded": open,
|
|
772
|
+
"aria-controls": open ? contentId : void 0,
|
|
773
|
+
onClick: () => setOpen(!open),
|
|
774
|
+
...props,
|
|
775
|
+
children
|
|
776
|
+
}
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
function DropdownContent({ className, children, ...props }) {
|
|
780
|
+
const { open, setOpen, contentId, triggerId } = useDropdownContext();
|
|
781
|
+
const ref = useRef2(null);
|
|
782
|
+
useEffect3(() => {
|
|
783
|
+
if (!open) return;
|
|
784
|
+
const handler = (e) => {
|
|
785
|
+
const el = ref.current;
|
|
786
|
+
const trigger = document.getElementById(triggerId);
|
|
787
|
+
if (el && !el.contains(e.target) && !trigger?.contains(e.target)) {
|
|
788
|
+
setOpen(false);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
const escHandler = (e) => {
|
|
792
|
+
if (e.key === "Escape") setOpen(false);
|
|
793
|
+
};
|
|
794
|
+
document.addEventListener("mousedown", handler);
|
|
795
|
+
document.addEventListener("keydown", escHandler);
|
|
796
|
+
return () => {
|
|
797
|
+
document.removeEventListener("mousedown", handler);
|
|
798
|
+
document.removeEventListener("keydown", escHandler);
|
|
799
|
+
};
|
|
800
|
+
}, [open, setOpen, triggerId]);
|
|
801
|
+
if (!open) return null;
|
|
802
|
+
return /* @__PURE__ */ jsx14(
|
|
803
|
+
"div",
|
|
804
|
+
{
|
|
805
|
+
ref,
|
|
806
|
+
id: contentId,
|
|
807
|
+
role: "menu",
|
|
808
|
+
"aria-labelledby": triggerId,
|
|
809
|
+
className: cn(
|
|
810
|
+
"absolute left-0 top-full z-50 mt-1 bg-background-elevated border border-border rounded-md shadow-lg py-1 min-w-[160px]",
|
|
811
|
+
className
|
|
812
|
+
),
|
|
813
|
+
...props,
|
|
814
|
+
children
|
|
815
|
+
}
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
function DropdownItem({
|
|
819
|
+
className,
|
|
820
|
+
icon: Icon,
|
|
821
|
+
variant = "default",
|
|
822
|
+
children,
|
|
823
|
+
onClick,
|
|
824
|
+
...props
|
|
825
|
+
}) {
|
|
826
|
+
const { setOpen } = useDropdownContext();
|
|
827
|
+
const handleClick = useCallback3(
|
|
828
|
+
(e) => {
|
|
829
|
+
onClick?.(e);
|
|
830
|
+
setOpen(false);
|
|
831
|
+
},
|
|
832
|
+
[onClick, setOpen]
|
|
833
|
+
);
|
|
834
|
+
return /* @__PURE__ */ jsxs4(
|
|
835
|
+
"button",
|
|
836
|
+
{
|
|
837
|
+
type: "button",
|
|
838
|
+
role: "menuitem",
|
|
839
|
+
className: cn(
|
|
840
|
+
"h-7 text-xs px-2 w-full text-left flex items-center gap-2",
|
|
841
|
+
variant === "danger" ? "text-danger hover:bg-danger/10" : "text-foreground hover:bg-surface-hover",
|
|
842
|
+
className
|
|
843
|
+
),
|
|
844
|
+
onClick: handleClick,
|
|
845
|
+
...props,
|
|
846
|
+
children: [
|
|
847
|
+
Icon && /* @__PURE__ */ jsx14(Icon, { size: 14 }),
|
|
848
|
+
children
|
|
849
|
+
]
|
|
850
|
+
}
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/primitives/tooltip.tsx
|
|
855
|
+
import {
|
|
856
|
+
createContext as createContext4,
|
|
857
|
+
useContext as useContext4,
|
|
858
|
+
useId as useId3,
|
|
859
|
+
useRef as useRef3,
|
|
860
|
+
useState as useState4
|
|
861
|
+
} from "react";
|
|
862
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
863
|
+
var TooltipContext = createContext4(null);
|
|
864
|
+
function useTooltipContext() {
|
|
865
|
+
const ctx = useContext4(TooltipContext);
|
|
866
|
+
if (!ctx) throw new Error("Tooltip compound components must be used within <Tooltip>");
|
|
867
|
+
return ctx;
|
|
868
|
+
}
|
|
869
|
+
function Tooltip({ children }) {
|
|
870
|
+
const [open, setOpen] = useState4(false);
|
|
871
|
+
const timerRef = useRef3(null);
|
|
872
|
+
const tooltipId = useId3();
|
|
873
|
+
const show = () => {
|
|
874
|
+
timerRef.current = setTimeout(() => setOpen(true), 300);
|
|
875
|
+
};
|
|
876
|
+
const hide = () => {
|
|
877
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
878
|
+
setOpen(false);
|
|
879
|
+
};
|
|
880
|
+
return /* @__PURE__ */ jsx15(TooltipContext.Provider, { value: { open, show, hide, tooltipId }, children: /* @__PURE__ */ jsx15("div", { className: "relative inline-block", children }) });
|
|
881
|
+
}
|
|
882
|
+
function TooltipTrigger({ children, ...props }) {
|
|
883
|
+
const { show, hide, tooltipId, open } = useTooltipContext();
|
|
884
|
+
return /* @__PURE__ */ jsx15(
|
|
885
|
+
"div",
|
|
886
|
+
{
|
|
887
|
+
onMouseEnter: show,
|
|
888
|
+
onMouseLeave: hide,
|
|
889
|
+
onFocus: show,
|
|
890
|
+
onBlur: hide,
|
|
891
|
+
"aria-describedby": open ? tooltipId : void 0,
|
|
892
|
+
...props,
|
|
893
|
+
children
|
|
894
|
+
}
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
function TooltipContent({ className, children, ...props }) {
|
|
898
|
+
const { open, tooltipId } = useTooltipContext();
|
|
899
|
+
return /* @__PURE__ */ jsx15(
|
|
900
|
+
"div",
|
|
901
|
+
{
|
|
902
|
+
id: tooltipId,
|
|
903
|
+
role: "tooltip",
|
|
904
|
+
className: cn(
|
|
905
|
+
"absolute bottom-full left-1/2 -translate-x-1/2 mb-2 bg-foreground text-background text-xs px-2 py-1 rounded-md shadow-md whitespace-nowrap pointer-events-none transition-opacity duration-150",
|
|
906
|
+
open ? "opacity-100" : "opacity-0",
|
|
907
|
+
className
|
|
908
|
+
),
|
|
909
|
+
...props,
|
|
910
|
+
children
|
|
911
|
+
}
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// src/primitives/popover.tsx
|
|
916
|
+
import {
|
|
917
|
+
createContext as createContext5,
|
|
918
|
+
useCallback as useCallback4,
|
|
919
|
+
useContext as useContext5,
|
|
920
|
+
useEffect as useEffect4,
|
|
921
|
+
useId as useId4,
|
|
922
|
+
useRef as useRef4,
|
|
923
|
+
useState as useState5
|
|
924
|
+
} from "react";
|
|
925
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
926
|
+
var PopoverContext = createContext5(null);
|
|
927
|
+
function usePopoverContext() {
|
|
928
|
+
const ctx = useContext5(PopoverContext);
|
|
929
|
+
if (!ctx) throw new Error("Popover compound components must be used within <Popover>");
|
|
930
|
+
return ctx;
|
|
931
|
+
}
|
|
932
|
+
function Popover({ children, open: controlledOpen, onOpenChange }) {
|
|
933
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState5(false);
|
|
934
|
+
const open = controlledOpen ?? uncontrolledOpen;
|
|
935
|
+
const triggerId = useId4();
|
|
936
|
+
const contentId = useId4();
|
|
937
|
+
const setOpen = useCallback4(
|
|
938
|
+
(next) => {
|
|
939
|
+
onOpenChange?.(next);
|
|
940
|
+
if (controlledOpen === void 0) setUncontrolledOpen(next);
|
|
941
|
+
},
|
|
942
|
+
[controlledOpen, onOpenChange]
|
|
943
|
+
);
|
|
944
|
+
return /* @__PURE__ */ jsx16(PopoverContext.Provider, { value: { open, setOpen, triggerId, contentId }, children: /* @__PURE__ */ jsx16("div", { className: "relative inline-block", children }) });
|
|
945
|
+
}
|
|
946
|
+
function PopoverTrigger({ children, ...props }) {
|
|
947
|
+
const { open, setOpen, triggerId, contentId } = usePopoverContext();
|
|
948
|
+
return /* @__PURE__ */ jsx16(
|
|
949
|
+
"button",
|
|
950
|
+
{
|
|
951
|
+
type: "button",
|
|
952
|
+
id: triggerId,
|
|
953
|
+
"aria-haspopup": "dialog",
|
|
954
|
+
"aria-expanded": open,
|
|
955
|
+
"aria-controls": open ? contentId : void 0,
|
|
956
|
+
onClick: () => setOpen(!open),
|
|
957
|
+
...props,
|
|
958
|
+
children
|
|
959
|
+
}
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
function PopoverContent({ className, children, ...props }) {
|
|
963
|
+
const { open, setOpen, contentId, triggerId } = usePopoverContext();
|
|
964
|
+
const ref = useRef4(null);
|
|
965
|
+
useEffect4(() => {
|
|
966
|
+
if (!open) return;
|
|
967
|
+
const handler = (e) => {
|
|
968
|
+
const el = ref.current;
|
|
969
|
+
const trigger = document.getElementById(triggerId);
|
|
970
|
+
if (el && !el.contains(e.target) && !trigger?.contains(e.target)) {
|
|
971
|
+
setOpen(false);
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
const escHandler = (e) => {
|
|
975
|
+
if (e.key === "Escape") setOpen(false);
|
|
976
|
+
};
|
|
977
|
+
document.addEventListener("mousedown", handler);
|
|
978
|
+
document.addEventListener("keydown", escHandler);
|
|
979
|
+
return () => {
|
|
980
|
+
document.removeEventListener("mousedown", handler);
|
|
981
|
+
document.removeEventListener("keydown", escHandler);
|
|
982
|
+
};
|
|
983
|
+
}, [open, setOpen, triggerId]);
|
|
984
|
+
if (!open) return null;
|
|
985
|
+
return /* @__PURE__ */ jsx16(
|
|
986
|
+
"div",
|
|
987
|
+
{
|
|
988
|
+
ref,
|
|
989
|
+
id: contentId,
|
|
990
|
+
role: "dialog",
|
|
991
|
+
"aria-labelledby": triggerId,
|
|
992
|
+
className: cn(
|
|
993
|
+
"absolute left-0 top-full z-50 mt-1 bg-background-elevated border border-border rounded-lg shadow-lg p-3",
|
|
994
|
+
className
|
|
995
|
+
),
|
|
996
|
+
...props,
|
|
997
|
+
children
|
|
998
|
+
}
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/primitives/tabs.tsx
|
|
1003
|
+
import {
|
|
1004
|
+
createContext as createContext6,
|
|
1005
|
+
useCallback as useCallback5,
|
|
1006
|
+
useContext as useContext6,
|
|
1007
|
+
useState as useState6
|
|
1008
|
+
} from "react";
|
|
1009
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1010
|
+
var TabsContext = createContext6(null);
|
|
1011
|
+
function useTabsContext() {
|
|
1012
|
+
const ctx = useContext6(TabsContext);
|
|
1013
|
+
if (!ctx) throw new Error("Tabs compound components must be used within <Tabs>");
|
|
1014
|
+
return ctx;
|
|
1015
|
+
}
|
|
1016
|
+
function Tabs({
|
|
1017
|
+
value: controlledValue,
|
|
1018
|
+
onValueChange,
|
|
1019
|
+
defaultValue = "",
|
|
1020
|
+
className,
|
|
1021
|
+
...props
|
|
1022
|
+
}) {
|
|
1023
|
+
const [uncontrolledValue, setUncontrolledValue] = useState6(defaultValue);
|
|
1024
|
+
const value = controlledValue ?? uncontrolledValue;
|
|
1025
|
+
const setValue = useCallback5(
|
|
1026
|
+
(next) => {
|
|
1027
|
+
onValueChange?.(next);
|
|
1028
|
+
if (controlledValue === void 0) setUncontrolledValue(next);
|
|
1029
|
+
},
|
|
1030
|
+
[controlledValue, onValueChange]
|
|
1031
|
+
);
|
|
1032
|
+
return /* @__PURE__ */ jsx17(TabsContext.Provider, { value: { value, setValue }, children: /* @__PURE__ */ jsx17("div", { className, ...props }) });
|
|
1033
|
+
}
|
|
1034
|
+
function TabsList({ className, ...props }) {
|
|
1035
|
+
return /* @__PURE__ */ jsx17(
|
|
1036
|
+
"div",
|
|
1037
|
+
{
|
|
1038
|
+
role: "tablist",
|
|
1039
|
+
className: cn("flex flex-row border-b border-border-subtle", className),
|
|
1040
|
+
...props
|
|
1041
|
+
}
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
1044
|
+
function TabsTrigger({ value, className, ...props }) {
|
|
1045
|
+
const { value: activeValue, setValue } = useTabsContext();
|
|
1046
|
+
const isActive = value === activeValue;
|
|
1047
|
+
const panelId = `tabpanel-${value}`;
|
|
1048
|
+
return /* @__PURE__ */ jsx17(
|
|
1049
|
+
"button",
|
|
1050
|
+
{
|
|
1051
|
+
type: "button",
|
|
1052
|
+
role: "tab",
|
|
1053
|
+
"aria-selected": isActive,
|
|
1054
|
+
"aria-controls": panelId,
|
|
1055
|
+
tabIndex: isActive ? 0 : -1,
|
|
1056
|
+
className: cn(
|
|
1057
|
+
"h-7 text-xs px-3 transition-colors",
|
|
1058
|
+
isActive ? "border-b-2 border-primary text-foreground font-medium" : "text-foreground-muted hover:text-foreground",
|
|
1059
|
+
className
|
|
1060
|
+
),
|
|
1061
|
+
onClick: () => setValue(value),
|
|
1062
|
+
...props
|
|
1063
|
+
}
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
function TabsContent({ value, className, ...props }) {
|
|
1067
|
+
const { value: activeValue } = useTabsContext();
|
|
1068
|
+
if (value !== activeValue) return null;
|
|
1069
|
+
return /* @__PURE__ */ jsx17(
|
|
1070
|
+
"div",
|
|
1071
|
+
{
|
|
1072
|
+
role: "tabpanel",
|
|
1073
|
+
id: `tabpanel-${value}`,
|
|
1074
|
+
className: cn("pt-3", className),
|
|
1075
|
+
...props
|
|
1076
|
+
}
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/primitives/scroll-area.tsx
|
|
1081
|
+
import { forwardRef as forwardRef13 } from "react";
|
|
1082
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
1083
|
+
var ScrollArea = forwardRef13(
|
|
1084
|
+
({ className, ...props }, ref) => {
|
|
1085
|
+
return /* @__PURE__ */ jsx18(
|
|
1086
|
+
"div",
|
|
1087
|
+
{
|
|
1088
|
+
ref,
|
|
1089
|
+
className: cn(
|
|
1090
|
+
"overflow-auto [&::-webkit-scrollbar]:w-1 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-surface-hover [&::-webkit-scrollbar-thumb]:rounded-full",
|
|
1091
|
+
className
|
|
1092
|
+
),
|
|
1093
|
+
...props
|
|
1094
|
+
}
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
);
|
|
1098
|
+
ScrollArea.displayName = "ScrollArea";
|
|
1099
|
+
|
|
1100
|
+
// src/primitives/floating-panel.tsx
|
|
1101
|
+
import { useRef as useRef5, useState as useState7, useCallback as useCallback6, useEffect as useEffect5 } from "react";
|
|
1102
|
+
import { X, Minimize2, Maximize2, GripHorizontal } from "lucide-react";
|
|
1103
|
+
import { jsx as jsx19, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1104
|
+
function FloatingPanel({
|
|
1105
|
+
title,
|
|
1106
|
+
onClose,
|
|
1107
|
+
children,
|
|
1108
|
+
defaultWidth = 360,
|
|
1109
|
+
defaultHeight = 280,
|
|
1110
|
+
minWidth = 280,
|
|
1111
|
+
minHeight = 160,
|
|
1112
|
+
offsetIndex = 0,
|
|
1113
|
+
className
|
|
1114
|
+
}) {
|
|
1115
|
+
const [pos, setPos] = useState7({ x: 80 + offsetIndex * 30, y: 80 + offsetIndex * 30 });
|
|
1116
|
+
const [size, setSize] = useState7({ w: defaultWidth, h: defaultHeight });
|
|
1117
|
+
const [minimized, setMinimized] = useState7(false);
|
|
1118
|
+
const dragging = useRef5(false);
|
|
1119
|
+
const resizing = useRef5(false);
|
|
1120
|
+
const offset = useRef5({ x: 0, y: 0 });
|
|
1121
|
+
const onDragStart = useCallback6((e) => {
|
|
1122
|
+
e.preventDefault();
|
|
1123
|
+
dragging.current = true;
|
|
1124
|
+
offset.current = { x: e.clientX - pos.x, y: e.clientY - pos.y };
|
|
1125
|
+
}, [pos]);
|
|
1126
|
+
const onResizeStart = useCallback6((e) => {
|
|
1127
|
+
e.preventDefault();
|
|
1128
|
+
e.stopPropagation();
|
|
1129
|
+
resizing.current = true;
|
|
1130
|
+
offset.current = { x: e.clientX, y: e.clientY };
|
|
1131
|
+
}, []);
|
|
1132
|
+
useEffect5(() => {
|
|
1133
|
+
const onMouseMove = (e) => {
|
|
1134
|
+
if (dragging.current) setPos({ x: e.clientX - offset.current.x, y: e.clientY - offset.current.y });
|
|
1135
|
+
if (resizing.current) {
|
|
1136
|
+
const dx = e.clientX - offset.current.x;
|
|
1137
|
+
const dy = e.clientY - offset.current.y;
|
|
1138
|
+
offset.current = { x: e.clientX, y: e.clientY };
|
|
1139
|
+
setSize((prev) => ({ w: Math.max(minWidth, prev.w + dx), h: Math.max(minHeight, prev.h + dy) }));
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
const onMouseUp = () => {
|
|
1143
|
+
dragging.current = false;
|
|
1144
|
+
resizing.current = false;
|
|
1145
|
+
};
|
|
1146
|
+
window.addEventListener("mousemove", onMouseMove);
|
|
1147
|
+
window.addEventListener("mouseup", onMouseUp);
|
|
1148
|
+
return () => {
|
|
1149
|
+
window.removeEventListener("mousemove", onMouseMove);
|
|
1150
|
+
window.removeEventListener("mouseup", onMouseUp);
|
|
1151
|
+
};
|
|
1152
|
+
}, [minWidth, minHeight]);
|
|
1153
|
+
return /* @__PURE__ */ jsxs5(
|
|
1154
|
+
"div",
|
|
1155
|
+
{
|
|
1156
|
+
className: cn(
|
|
1157
|
+
"fixed z-50 rounded-lg border border-border bg-background-elevated shadow-2xl flex flex-col overflow-hidden",
|
|
1158
|
+
className
|
|
1159
|
+
),
|
|
1160
|
+
style: { left: pos.x, top: pos.y, width: minimized ? 280 : size.w, height: minimized ? "auto" : size.h },
|
|
1161
|
+
children: [
|
|
1162
|
+
/* @__PURE__ */ jsxs5(
|
|
1163
|
+
"div",
|
|
1164
|
+
{
|
|
1165
|
+
onMouseDown: onDragStart,
|
|
1166
|
+
className: "flex items-center justify-between gap-2 px-3 py-2 border-b border-border cursor-move select-none shrink-0 bg-surface",
|
|
1167
|
+
children: [
|
|
1168
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
1169
|
+
/* @__PURE__ */ jsx19(GripHorizontal, { size: 12, className: "text-foreground-subtle shrink-0" }),
|
|
1170
|
+
/* @__PURE__ */ jsx19("span", { className: "text-[11px] font-medium truncate", children: title })
|
|
1171
|
+
] }),
|
|
1172
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1 shrink-0", children: [
|
|
1173
|
+
/* @__PURE__ */ jsx19(
|
|
1174
|
+
"button",
|
|
1175
|
+
{
|
|
1176
|
+
onClick: () => setMinimized(!minimized),
|
|
1177
|
+
className: "p-0.5 rounded hover:bg-surface-hover text-foreground-muted transition-colors",
|
|
1178
|
+
title: minimized ? "Restore" : "Minimize",
|
|
1179
|
+
children: minimized ? /* @__PURE__ */ jsx19(Maximize2, { size: 12 }) : /* @__PURE__ */ jsx19(Minimize2, { size: 12 })
|
|
1180
|
+
}
|
|
1181
|
+
),
|
|
1182
|
+
/* @__PURE__ */ jsx19(
|
|
1183
|
+
"button",
|
|
1184
|
+
{
|
|
1185
|
+
onClick: onClose,
|
|
1186
|
+
className: "p-0.5 rounded hover:bg-danger/20 text-foreground-muted hover:text-danger transition-colors",
|
|
1187
|
+
title: "Close",
|
|
1188
|
+
children: /* @__PURE__ */ jsx19(X, { size: 12 })
|
|
1189
|
+
}
|
|
1190
|
+
)
|
|
1191
|
+
] })
|
|
1192
|
+
]
|
|
1193
|
+
}
|
|
1194
|
+
),
|
|
1195
|
+
!minimized && /* @__PURE__ */ jsxs5("div", { className: "flex-1 min-h-0 overflow-y-auto relative", children: [
|
|
1196
|
+
children,
|
|
1197
|
+
/* @__PURE__ */ jsx19(
|
|
1198
|
+
"div",
|
|
1199
|
+
{
|
|
1200
|
+
onMouseDown: onResizeStart,
|
|
1201
|
+
className: "absolute bottom-0 right-0 w-4 h-4 cursor-nwse-resize",
|
|
1202
|
+
style: { background: "linear-gradient(135deg, transparent 50%, var(--color-foreground-subtle) 50%)", opacity: 0.4 }
|
|
1203
|
+
}
|
|
1204
|
+
)
|
|
1205
|
+
] })
|
|
1206
|
+
]
|
|
1207
|
+
}
|
|
1208
|
+
);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// src/composites/status-badge.tsx
|
|
1212
|
+
import { jsx as jsx20, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1213
|
+
var statusConfig = {
|
|
1214
|
+
online: { colorClass: "bg-success", label: "Online" },
|
|
1215
|
+
offline: { colorClass: "bg-danger", label: "Offline" },
|
|
1216
|
+
degraded: { colorClass: "bg-warning", label: "Degraded" },
|
|
1217
|
+
unknown: { colorClass: "bg-foreground-subtle", label: "Unknown" }
|
|
1218
|
+
};
|
|
1219
|
+
function StatusBadge({
|
|
1220
|
+
status,
|
|
1221
|
+
showDot = true,
|
|
1222
|
+
showLabel = true,
|
|
1223
|
+
size = "sm",
|
|
1224
|
+
className
|
|
1225
|
+
}) {
|
|
1226
|
+
const config = statusConfig[status];
|
|
1227
|
+
return /* @__PURE__ */ jsxs6(
|
|
1228
|
+
"span",
|
|
1229
|
+
{
|
|
1230
|
+
className: cn(
|
|
1231
|
+
"inline-flex items-center gap-1.5",
|
|
1232
|
+
size === "sm" ? "text-xs" : "text-sm",
|
|
1233
|
+
className
|
|
1234
|
+
),
|
|
1235
|
+
children: [
|
|
1236
|
+
showDot && /* @__PURE__ */ jsx20(
|
|
1237
|
+
"span",
|
|
1238
|
+
{
|
|
1239
|
+
className: cn("h-1.5 w-1.5 shrink-0 rounded-full", config.colorClass),
|
|
1240
|
+
"aria-hidden": "true"
|
|
1241
|
+
}
|
|
1242
|
+
),
|
|
1243
|
+
showLabel && /* @__PURE__ */ jsx20("span", { className: "text-foreground", children: config.label })
|
|
1244
|
+
]
|
|
1245
|
+
}
|
|
1246
|
+
);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// src/composites/provider-badge.tsx
|
|
1250
|
+
import { jsx as jsx21, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1251
|
+
var providerConfig = {
|
|
1252
|
+
frigate: { colorClass: "bg-provider-frigate", label: "Frigate" },
|
|
1253
|
+
scrypted: { colorClass: "bg-provider-scrypted", label: "Scrypted" },
|
|
1254
|
+
reolink: { colorClass: "bg-provider-reolink", label: "Reolink" },
|
|
1255
|
+
homeAssistant: { colorClass: "bg-provider-homeAssistant", label: "Home Assistant" },
|
|
1256
|
+
rtsp: { colorClass: "bg-provider-rtsp", label: "RTSP" }
|
|
1257
|
+
};
|
|
1258
|
+
function ProviderBadge({
|
|
1259
|
+
provider,
|
|
1260
|
+
showLabel = true,
|
|
1261
|
+
className
|
|
1262
|
+
}) {
|
|
1263
|
+
const config = providerConfig[provider];
|
|
1264
|
+
return /* @__PURE__ */ jsxs7("span", { className: cn("inline-flex items-center gap-1.5 text-xs", className), children: [
|
|
1265
|
+
/* @__PURE__ */ jsx21(
|
|
1266
|
+
"span",
|
|
1267
|
+
{
|
|
1268
|
+
className: cn("h-1.5 w-1.5 shrink-0 rounded-sm", config.colorClass),
|
|
1269
|
+
"aria-hidden": "true"
|
|
1270
|
+
}
|
|
1271
|
+
),
|
|
1272
|
+
showLabel && /* @__PURE__ */ jsx21("span", { className: "text-foreground", children: config.label })
|
|
1273
|
+
] });
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// src/composites/form-field.tsx
|
|
1277
|
+
import { jsx as jsx22, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1278
|
+
function FormField({
|
|
1279
|
+
label,
|
|
1280
|
+
description,
|
|
1281
|
+
error,
|
|
1282
|
+
required,
|
|
1283
|
+
children,
|
|
1284
|
+
orientation = "vertical",
|
|
1285
|
+
className
|
|
1286
|
+
}) {
|
|
1287
|
+
const isHorizontal = orientation === "horizontal";
|
|
1288
|
+
return /* @__PURE__ */ jsxs8(
|
|
1289
|
+
"div",
|
|
1290
|
+
{
|
|
1291
|
+
className: cn(
|
|
1292
|
+
"flex gap-2",
|
|
1293
|
+
isHorizontal ? "flex-row items-center justify-between" : "flex-col",
|
|
1294
|
+
className
|
|
1295
|
+
),
|
|
1296
|
+
children: [
|
|
1297
|
+
/* @__PURE__ */ jsxs8("div", { className: cn(isHorizontal ? "flex-1" : ""), children: [
|
|
1298
|
+
/* @__PURE__ */ jsxs8(Label, { children: [
|
|
1299
|
+
label,
|
|
1300
|
+
required && /* @__PURE__ */ jsx22("span", { className: "text-danger ml-0.5", children: "*" })
|
|
1301
|
+
] }),
|
|
1302
|
+
description && /* @__PURE__ */ jsx22("p", { className: "text-foreground-subtle text-xs mt-0.5", children: description })
|
|
1303
|
+
] }),
|
|
1304
|
+
/* @__PURE__ */ jsx22("div", { className: cn(isHorizontal ? "shrink-0" : ""), children }),
|
|
1305
|
+
error && /* @__PURE__ */ jsx22("p", { className: "text-danger text-xs", children: error })
|
|
1306
|
+
]
|
|
1307
|
+
}
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/composites/page-header.tsx
|
|
1312
|
+
import { jsx as jsx23, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
1313
|
+
function PageHeader({ title, subtitle, actions, className }) {
|
|
1314
|
+
return /* @__PURE__ */ jsxs9("div", { className: cn("flex items-center justify-between mb-3", className), children: [
|
|
1315
|
+
/* @__PURE__ */ jsxs9("div", { children: [
|
|
1316
|
+
/* @__PURE__ */ jsx23("h1", { className: "text-sm font-semibold text-foreground", children: title }),
|
|
1317
|
+
subtitle && /* @__PURE__ */ jsx23("p", { className: "text-foreground-subtle text-xs", children: subtitle })
|
|
1318
|
+
] }),
|
|
1319
|
+
actions && /* @__PURE__ */ jsx23("div", { className: "flex items-center gap-2", children: actions })
|
|
1320
|
+
] });
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// src/composites/empty-state.tsx
|
|
1324
|
+
import { jsx as jsx24, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1325
|
+
function EmptyState({
|
|
1326
|
+
icon: Icon,
|
|
1327
|
+
title,
|
|
1328
|
+
description,
|
|
1329
|
+
action,
|
|
1330
|
+
className
|
|
1331
|
+
}) {
|
|
1332
|
+
return /* @__PURE__ */ jsxs10("div", { className: cn("flex flex-col items-center justify-center gap-3 py-12", className), children: [
|
|
1333
|
+
Icon && /* @__PURE__ */ jsx24(Icon, { className: "h-12 w-12 text-foreground-subtle", "aria-hidden": "true" }),
|
|
1334
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-center gap-1 text-center", children: [
|
|
1335
|
+
/* @__PURE__ */ jsx24("p", { className: "text-foreground-muted text-sm font-medium", children: title }),
|
|
1336
|
+
description && /* @__PURE__ */ jsx24("p", { className: "text-foreground-subtle text-xs max-w-xs", children: description })
|
|
1337
|
+
] }),
|
|
1338
|
+
action && /* @__PURE__ */ jsx24("div", { className: "mt-1", children: action })
|
|
1339
|
+
] });
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// src/composites/confirm-dialog.tsx
|
|
1343
|
+
import { jsx as jsx25, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1344
|
+
function ConfirmDialog({
|
|
1345
|
+
title,
|
|
1346
|
+
message,
|
|
1347
|
+
confirmLabel = "Confirm",
|
|
1348
|
+
cancelLabel = "Cancel",
|
|
1349
|
+
onConfirm,
|
|
1350
|
+
onCancel,
|
|
1351
|
+
variant = "default",
|
|
1352
|
+
open,
|
|
1353
|
+
onOpenChange
|
|
1354
|
+
}) {
|
|
1355
|
+
return /* @__PURE__ */ jsx25(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs11(DialogContent, { children: [
|
|
1356
|
+
/* @__PURE__ */ jsxs11(DialogHeader, { children: [
|
|
1357
|
+
/* @__PURE__ */ jsx25(DialogTitle, { children: title }),
|
|
1358
|
+
/* @__PURE__ */ jsx25(DialogDescription, { children: message })
|
|
1359
|
+
] }),
|
|
1360
|
+
/* @__PURE__ */ jsxs11(DialogFooter, { children: [
|
|
1361
|
+
/* @__PURE__ */ jsx25(Button, { variant: "ghost", onClick: onCancel, children: cancelLabel }),
|
|
1362
|
+
/* @__PURE__ */ jsx25(
|
|
1363
|
+
Button,
|
|
1364
|
+
{
|
|
1365
|
+
variant: variant === "danger" ? "danger" : "primary",
|
|
1366
|
+
onClick: onConfirm,
|
|
1367
|
+
children: confirmLabel
|
|
1368
|
+
}
|
|
1369
|
+
)
|
|
1370
|
+
] })
|
|
1371
|
+
] }) });
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// src/composites/stat-card.tsx
|
|
1375
|
+
import { TrendingUp, TrendingDown } from "lucide-react";
|
|
1376
|
+
import { jsx as jsx26, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1377
|
+
function StatCard({ value, label, trend, className }) {
|
|
1378
|
+
return /* @__PURE__ */ jsxs12(Card, { className: cn("flex flex-col gap-1", className), children: [
|
|
1379
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-baseline gap-2", children: [
|
|
1380
|
+
/* @__PURE__ */ jsx26("span", { className: "text-2xl font-semibold text-foreground", children: value }),
|
|
1381
|
+
trend && /* @__PURE__ */ jsxs12(
|
|
1382
|
+
"span",
|
|
1383
|
+
{
|
|
1384
|
+
className: cn(
|
|
1385
|
+
"inline-flex items-center gap-0.5 text-xs font-medium",
|
|
1386
|
+
trend.direction === "up" ? "text-success" : "text-danger"
|
|
1387
|
+
),
|
|
1388
|
+
children: [
|
|
1389
|
+
trend.direction === "up" ? /* @__PURE__ */ jsx26(TrendingUp, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx26(TrendingDown, { className: "h-3 w-3" }),
|
|
1390
|
+
trend.value,
|
|
1391
|
+
"%"
|
|
1392
|
+
]
|
|
1393
|
+
}
|
|
1394
|
+
)
|
|
1395
|
+
] }),
|
|
1396
|
+
/* @__PURE__ */ jsx26("span", { className: "text-xs text-foreground-muted", children: label })
|
|
1397
|
+
] });
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// src/composites/key-value-list.tsx
|
|
1401
|
+
import { jsx as jsx27, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1402
|
+
function KeyValueList({ items, className }) {
|
|
1403
|
+
return /* @__PURE__ */ jsx27("dl", { className: cn("flex flex-col", className), children: items.map((item) => /* @__PURE__ */ jsxs13(
|
|
1404
|
+
"div",
|
|
1405
|
+
{
|
|
1406
|
+
className: "flex items-center h-7",
|
|
1407
|
+
children: [
|
|
1408
|
+
/* @__PURE__ */ jsx27("dt", { className: "text-foreground-subtle text-xs w-1/3 shrink-0", children: item.key }),
|
|
1409
|
+
/* @__PURE__ */ jsx27("dd", { className: "text-foreground text-xs", children: item.value })
|
|
1410
|
+
]
|
|
1411
|
+
},
|
|
1412
|
+
item.key
|
|
1413
|
+
)) });
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// src/composites/code-block.tsx
|
|
1417
|
+
import { useCallback as useCallback7, useState as useState8 } from "react";
|
|
1418
|
+
import { Copy, Check } from "lucide-react";
|
|
1419
|
+
import { jsx as jsx28, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1420
|
+
function CodeBlock({ children, maxHeight = 300, className }) {
|
|
1421
|
+
const [copied, setCopied] = useState8(false);
|
|
1422
|
+
const handleCopy = useCallback7(() => {
|
|
1423
|
+
navigator.clipboard.writeText(children).then(() => {
|
|
1424
|
+
setCopied(true);
|
|
1425
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
1426
|
+
});
|
|
1427
|
+
}, [children]);
|
|
1428
|
+
return /* @__PURE__ */ jsxs14("div", { className: cn("relative group", className), children: [
|
|
1429
|
+
/* @__PURE__ */ jsx28(ScrollArea, { style: { maxHeight }, children: /* @__PURE__ */ jsx28("pre", { className: "font-mono text-xs bg-surface p-3 rounded-md border border-border-subtle", children: /* @__PURE__ */ jsx28("code", { children }) }) }),
|
|
1430
|
+
/* @__PURE__ */ jsx28("div", { className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity", children: /* @__PURE__ */ jsx28(
|
|
1431
|
+
IconButton,
|
|
1432
|
+
{
|
|
1433
|
+
icon: copied ? Check : Copy,
|
|
1434
|
+
"aria-label": "Copy code",
|
|
1435
|
+
variant: "ghost",
|
|
1436
|
+
size: "sm",
|
|
1437
|
+
onClick: handleCopy
|
|
1438
|
+
}
|
|
1439
|
+
) })
|
|
1440
|
+
] });
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// src/composites/filter-bar.tsx
|
|
1444
|
+
import { Search } from "lucide-react";
|
|
1445
|
+
import { jsx as jsx29 } from "react/jsx-runtime";
|
|
1446
|
+
function FilterBar({ filters, values, onChange, className }) {
|
|
1447
|
+
const handleChange = (key, value) => {
|
|
1448
|
+
onChange({ ...values, [key]: value });
|
|
1449
|
+
};
|
|
1450
|
+
return /* @__PURE__ */ jsx29("div", { className: cn("flex items-center gap-2 flex-wrap", className), children: filters.map((filter) => {
|
|
1451
|
+
switch (filter.type) {
|
|
1452
|
+
case "search":
|
|
1453
|
+
return /* @__PURE__ */ jsx29(
|
|
1454
|
+
Input,
|
|
1455
|
+
{
|
|
1456
|
+
placeholder: filter.placeholder ?? "Search...",
|
|
1457
|
+
value: values[filter.key] ?? "",
|
|
1458
|
+
onChange: (e) => handleChange(filter.key, e.target.value),
|
|
1459
|
+
leftSlot: /* @__PURE__ */ jsx29(Search, { className: "h-3 w-3 text-foreground-subtle" }),
|
|
1460
|
+
className: "w-48"
|
|
1461
|
+
},
|
|
1462
|
+
filter.key
|
|
1463
|
+
);
|
|
1464
|
+
case "select":
|
|
1465
|
+
return /* @__PURE__ */ jsx29(
|
|
1466
|
+
Select,
|
|
1467
|
+
{
|
|
1468
|
+
options: filter.options,
|
|
1469
|
+
value: values[filter.key] ?? "",
|
|
1470
|
+
onChange: (e) => handleChange(filter.key, e.target.value),
|
|
1471
|
+
className: "w-36"
|
|
1472
|
+
},
|
|
1473
|
+
filter.key
|
|
1474
|
+
);
|
|
1475
|
+
case "badge-toggle":
|
|
1476
|
+
return /* @__PURE__ */ jsx29("div", { className: "flex items-center gap-1", children: filter.options.map((option) => {
|
|
1477
|
+
const currentValue = values[filter.key];
|
|
1478
|
+
const isActive = currentValue === option.value;
|
|
1479
|
+
return /* @__PURE__ */ jsx29(
|
|
1480
|
+
"button",
|
|
1481
|
+
{
|
|
1482
|
+
type: "button",
|
|
1483
|
+
onClick: () => handleChange(
|
|
1484
|
+
filter.key,
|
|
1485
|
+
isActive ? void 0 : option.value
|
|
1486
|
+
),
|
|
1487
|
+
children: /* @__PURE__ */ jsx29(
|
|
1488
|
+
Badge,
|
|
1489
|
+
{
|
|
1490
|
+
variant: isActive ? "info" : "default",
|
|
1491
|
+
className: "cursor-pointer",
|
|
1492
|
+
children: option.label
|
|
1493
|
+
}
|
|
1494
|
+
)
|
|
1495
|
+
},
|
|
1496
|
+
option.value
|
|
1497
|
+
);
|
|
1498
|
+
}) }, filter.key);
|
|
1499
|
+
default:
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
}) });
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// src/composites/app-shell/sidebar-item.tsx
|
|
1506
|
+
import { jsx as jsx30, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1507
|
+
function SidebarItem({
|
|
1508
|
+
label,
|
|
1509
|
+
icon: Icon,
|
|
1510
|
+
href,
|
|
1511
|
+
badge,
|
|
1512
|
+
active = false,
|
|
1513
|
+
className
|
|
1514
|
+
}) {
|
|
1515
|
+
return /* @__PURE__ */ jsxs15(
|
|
1516
|
+
"a",
|
|
1517
|
+
{
|
|
1518
|
+
href,
|
|
1519
|
+
className: cn(
|
|
1520
|
+
"flex items-center gap-2 h-7 px-2 text-[11px] transition-colors",
|
|
1521
|
+
active ? "border-l-2 border-primary bg-primary/[0.08] text-foreground rounded-r-md" : "text-foreground-subtle hover:bg-surface-hover rounded-md",
|
|
1522
|
+
className
|
|
1523
|
+
),
|
|
1524
|
+
children: [
|
|
1525
|
+
/* @__PURE__ */ jsx30(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
1526
|
+
/* @__PURE__ */ jsx30("span", { className: "truncate flex-1", children: label }),
|
|
1527
|
+
badge !== void 0 && /* @__PURE__ */ jsx30(Badge, { className: "ml-auto text-[10px] px-1.5 py-0", children: badge })
|
|
1528
|
+
]
|
|
1529
|
+
}
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
// src/composites/app-shell/sidebar.tsx
|
|
1534
|
+
import { jsx as jsx31, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1535
|
+
function Sidebar({ logo, sections, footer, className }) {
|
|
1536
|
+
return /* @__PURE__ */ jsxs16(
|
|
1537
|
+
"nav",
|
|
1538
|
+
{
|
|
1539
|
+
className: cn(
|
|
1540
|
+
"w-44 bg-surface border-r border-border h-full flex flex-col",
|
|
1541
|
+
className
|
|
1542
|
+
),
|
|
1543
|
+
children: [
|
|
1544
|
+
logo && /* @__PURE__ */ jsx31("div", { className: "px-3 py-2 shrink-0", children: logo }),
|
|
1545
|
+
/* @__PURE__ */ jsx31("div", { className: "flex-1 overflow-auto px-1 py-1", children: sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs16("div", { className: cn(sectionIndex > 0 ? "mt-3" : ""), children: [
|
|
1546
|
+
section.label && /* @__PURE__ */ jsx31("span", { className: "text-[10px] text-foreground-disabled uppercase tracking-wider px-2 mb-1 block", children: section.label }),
|
|
1547
|
+
/* @__PURE__ */ jsx31("div", { className: "flex flex-col gap-0.5", children: section.items.map((item) => /* @__PURE__ */ jsx31(SidebarItem, { ...item }, item.href)) })
|
|
1548
|
+
] }, sectionIndex)) }),
|
|
1549
|
+
footer && footer.length > 0 && /* @__PURE__ */ jsxs16("div", { className: "shrink-0 px-1 pb-1", children: [
|
|
1550
|
+
/* @__PURE__ */ jsx31(Separator, { className: "mb-1" }),
|
|
1551
|
+
/* @__PURE__ */ jsx31("div", { className: "flex flex-col gap-0.5", children: footer.map((item) => /* @__PURE__ */ jsx31(SidebarItem, { ...item }, item.href)) })
|
|
1552
|
+
] })
|
|
1553
|
+
]
|
|
1554
|
+
}
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// src/composites/app-shell/app-shell.tsx
|
|
1559
|
+
import { ChevronRight } from "lucide-react";
|
|
1560
|
+
import { jsx as jsx32, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1561
|
+
function AppShell({ sidebar, header, children, className }) {
|
|
1562
|
+
return /* @__PURE__ */ jsxs17("div", { className: cn("flex h-screen", className), children: [
|
|
1563
|
+
/* @__PURE__ */ jsx32(Sidebar, { ...sidebar }),
|
|
1564
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex flex-1 flex-col min-w-0", children: [
|
|
1565
|
+
header && /* @__PURE__ */ jsxs17("header", { className: "flex items-center h-10 border-b border-border px-4 shrink-0", children: [
|
|
1566
|
+
header.breadcrumbs && header.breadcrumbs.length > 0 && /* @__PURE__ */ jsx32("nav", { className: "flex items-center gap-1 text-xs flex-1 min-w-0", children: header.breadcrumbs.map((crumb, index) => {
|
|
1567
|
+
const isLast = index === header.breadcrumbs.length - 1;
|
|
1568
|
+
return /* @__PURE__ */ jsxs17("span", { className: "flex items-center gap-1", children: [
|
|
1569
|
+
index > 0 && /* @__PURE__ */ jsx32(ChevronRight, { className: "h-3 w-3 text-foreground-subtle shrink-0" }),
|
|
1570
|
+
crumb.href && !isLast ? /* @__PURE__ */ jsx32(
|
|
1571
|
+
"a",
|
|
1572
|
+
{
|
|
1573
|
+
href: crumb.href,
|
|
1574
|
+
className: "text-foreground-subtle hover:text-foreground transition-colors truncate",
|
|
1575
|
+
children: crumb.label
|
|
1576
|
+
}
|
|
1577
|
+
) : /* @__PURE__ */ jsx32("span", { className: "text-foreground truncate", children: crumb.label })
|
|
1578
|
+
] }, index);
|
|
1579
|
+
}) }),
|
|
1580
|
+
header.actions && /* @__PURE__ */ jsx32("div", { className: "flex items-center gap-2 ml-auto shrink-0", children: header.actions })
|
|
1581
|
+
] }),
|
|
1582
|
+
/* @__PURE__ */ jsx32("main", { className: "flex-1 overflow-auto p-4", children })
|
|
1583
|
+
] })
|
|
1584
|
+
] });
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// src/composites/data-table/data-table.tsx
|
|
1588
|
+
import { useMemo as useMemo2 } from "react";
|
|
1589
|
+
import {
|
|
1590
|
+
useReactTable,
|
|
1591
|
+
getCoreRowModel,
|
|
1592
|
+
getSortedRowModel,
|
|
1593
|
+
getFilteredRowModel,
|
|
1594
|
+
getPaginationRowModel,
|
|
1595
|
+
flexRender
|
|
1596
|
+
} from "@tanstack/react-table";
|
|
1597
|
+
|
|
1598
|
+
// src/composites/data-table/data-table-header.tsx
|
|
1599
|
+
import { ArrowUpDown, ArrowUp, ArrowDown } from "lucide-react";
|
|
1600
|
+
import { jsx as jsx33, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1601
|
+
function DataTableHeader({
|
|
1602
|
+
headerGroups,
|
|
1603
|
+
onSortingChange,
|
|
1604
|
+
stickyHeader,
|
|
1605
|
+
flexRender: render
|
|
1606
|
+
}) {
|
|
1607
|
+
return /* @__PURE__ */ jsx33(
|
|
1608
|
+
"thead",
|
|
1609
|
+
{
|
|
1610
|
+
className: cn(
|
|
1611
|
+
stickyHeader && "sticky top-0 z-10 bg-background"
|
|
1612
|
+
),
|
|
1613
|
+
children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx33("tr", { className: "h-6", children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx33(
|
|
1614
|
+
HeaderCell,
|
|
1615
|
+
{
|
|
1616
|
+
header,
|
|
1617
|
+
sortable: header.column.getCanSort() && !!onSortingChange,
|
|
1618
|
+
flexRender: render
|
|
1619
|
+
},
|
|
1620
|
+
header.id
|
|
1621
|
+
)) }, headerGroup.id))
|
|
1622
|
+
}
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
function HeaderCell({ header, sortable, flexRender: render }) {
|
|
1626
|
+
const sorted = header.column.getIsSorted();
|
|
1627
|
+
const SortIcon = sorted === "asc" ? ArrowUp : sorted === "desc" ? ArrowDown : ArrowUpDown;
|
|
1628
|
+
return /* @__PURE__ */ jsx33(
|
|
1629
|
+
"th",
|
|
1630
|
+
{
|
|
1631
|
+
className: cn(
|
|
1632
|
+
"px-2 py-1 text-left text-[10px] text-foreground-subtle uppercase tracking-wider font-medium",
|
|
1633
|
+
sortable && "cursor-pointer select-none"
|
|
1634
|
+
),
|
|
1635
|
+
onClick: sortable ? header.column.getToggleSortingHandler() : void 0,
|
|
1636
|
+
children: /* @__PURE__ */ jsxs18("span", { className: "inline-flex items-center gap-1", children: [
|
|
1637
|
+
header.isPlaceholder ? null : render(header.column.columnDef.header, header.getContext()),
|
|
1638
|
+
sortable && /* @__PURE__ */ jsx33(SortIcon, { className: "h-3 w-3" })
|
|
1639
|
+
] })
|
|
1640
|
+
}
|
|
1641
|
+
);
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// src/composites/data-table/data-table-row.tsx
|
|
1645
|
+
import { MoreHorizontal } from "lucide-react";
|
|
1646
|
+
import { jsx as jsx34, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1647
|
+
function DataTableRow({
|
|
1648
|
+
row,
|
|
1649
|
+
onRowClick,
|
|
1650
|
+
rowActions,
|
|
1651
|
+
flexRender: render
|
|
1652
|
+
}) {
|
|
1653
|
+
const actions = rowActions ? rowActions(row.original) : [];
|
|
1654
|
+
return /* @__PURE__ */ jsxs19(
|
|
1655
|
+
"tr",
|
|
1656
|
+
{
|
|
1657
|
+
className: cn(
|
|
1658
|
+
"h-7 border-b border-border/50",
|
|
1659
|
+
onRowClick && "cursor-pointer",
|
|
1660
|
+
"hover:bg-surface-hover"
|
|
1661
|
+
),
|
|
1662
|
+
onClick: onRowClick ? () => onRowClick(row.original) : void 0,
|
|
1663
|
+
children: [
|
|
1664
|
+
row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx34(DataTableCell, { cell, flexRender: render }, cell.id)),
|
|
1665
|
+
actions.length > 0 && /* @__PURE__ */ jsx34("td", { className: "px-2 py-1.5 w-8", children: /* @__PURE__ */ jsxs19(Dropdown, { children: [
|
|
1666
|
+
/* @__PURE__ */ jsx34(
|
|
1667
|
+
DropdownTrigger,
|
|
1668
|
+
{
|
|
1669
|
+
className: "p-0.5 rounded hover:bg-surface-hover",
|
|
1670
|
+
onClick: (e) => e.stopPropagation(),
|
|
1671
|
+
children: /* @__PURE__ */ jsx34(MoreHorizontal, { className: "h-3.5 w-3.5 text-foreground-muted" })
|
|
1672
|
+
}
|
|
1673
|
+
),
|
|
1674
|
+
/* @__PURE__ */ jsx34(DropdownContent, { className: "right-0 left-auto", children: actions.map((action) => /* @__PURE__ */ jsx34(
|
|
1675
|
+
DropdownItem,
|
|
1676
|
+
{
|
|
1677
|
+
icon: action.icon,
|
|
1678
|
+
variant: action.variant,
|
|
1679
|
+
onClick: (e) => {
|
|
1680
|
+
e.stopPropagation();
|
|
1681
|
+
action.onClick();
|
|
1682
|
+
},
|
|
1683
|
+
children: action.label
|
|
1684
|
+
},
|
|
1685
|
+
action.label
|
|
1686
|
+
)) })
|
|
1687
|
+
] }) })
|
|
1688
|
+
]
|
|
1689
|
+
}
|
|
1690
|
+
);
|
|
1691
|
+
}
|
|
1692
|
+
function DataTableCell({ cell, flexRender: render }) {
|
|
1693
|
+
return /* @__PURE__ */ jsx34("td", { className: "px-2 py-1.5 text-xs text-foreground", children: render(cell.column.columnDef.cell, cell.getContext()) });
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/composites/data-table/data-table-pagination.tsx
|
|
1697
|
+
import { ChevronLeft, ChevronRight as ChevronRight2 } from "lucide-react";
|
|
1698
|
+
import { jsx as jsx35, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1699
|
+
var PAGE_SIZE_OPTIONS = [
|
|
1700
|
+
{ value: "10", label: "10" },
|
|
1701
|
+
{ value: "25", label: "25" },
|
|
1702
|
+
{ value: "50", label: "50" },
|
|
1703
|
+
{ value: "100", label: "100" }
|
|
1704
|
+
];
|
|
1705
|
+
function DataTablePagination({
|
|
1706
|
+
page,
|
|
1707
|
+
pageSize,
|
|
1708
|
+
total,
|
|
1709
|
+
onPaginationChange
|
|
1710
|
+
}) {
|
|
1711
|
+
const totalPages = Math.max(1, Math.ceil(total / pageSize));
|
|
1712
|
+
const currentPage = page + 1;
|
|
1713
|
+
return /* @__PURE__ */ jsxs20("div", { className: "flex items-center justify-between px-2 py-2 text-xs text-foreground-muted", children: [
|
|
1714
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
|
|
1715
|
+
/* @__PURE__ */ jsx35("span", { children: "Rows per page" }),
|
|
1716
|
+
/* @__PURE__ */ jsx35("div", { className: "w-16", children: /* @__PURE__ */ jsx35(
|
|
1717
|
+
Select,
|
|
1718
|
+
{
|
|
1719
|
+
options: PAGE_SIZE_OPTIONS,
|
|
1720
|
+
value: String(pageSize),
|
|
1721
|
+
onChange: (e) => onPaginationChange?.({
|
|
1722
|
+
pageIndex: 0,
|
|
1723
|
+
pageSize: Number(e.target.value)
|
|
1724
|
+
})
|
|
1725
|
+
}
|
|
1726
|
+
) })
|
|
1727
|
+
] }),
|
|
1728
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
|
|
1729
|
+
/* @__PURE__ */ jsxs20("span", { children: [
|
|
1730
|
+
"Page ",
|
|
1731
|
+
currentPage,
|
|
1732
|
+
" of ",
|
|
1733
|
+
totalPages
|
|
1734
|
+
] }),
|
|
1735
|
+
/* @__PURE__ */ jsx35(
|
|
1736
|
+
IconButton,
|
|
1737
|
+
{
|
|
1738
|
+
icon: ChevronLeft,
|
|
1739
|
+
"aria-label": "Previous page",
|
|
1740
|
+
variant: "ghost",
|
|
1741
|
+
size: "sm",
|
|
1742
|
+
disabled: page <= 0,
|
|
1743
|
+
onClick: () => onPaginationChange?.({ pageIndex: page - 1, pageSize })
|
|
1744
|
+
}
|
|
1745
|
+
),
|
|
1746
|
+
/* @__PURE__ */ jsx35(
|
|
1747
|
+
IconButton,
|
|
1748
|
+
{
|
|
1749
|
+
icon: ChevronRight2,
|
|
1750
|
+
"aria-label": "Next page",
|
|
1751
|
+
variant: "ghost",
|
|
1752
|
+
size: "sm",
|
|
1753
|
+
disabled: currentPage >= totalPages,
|
|
1754
|
+
onClick: () => onPaginationChange?.({ pageIndex: page + 1, pageSize })
|
|
1755
|
+
}
|
|
1756
|
+
)
|
|
1757
|
+
] })
|
|
1758
|
+
] });
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// src/composites/data-table/data-table.tsx
|
|
1762
|
+
import { Fragment, jsx as jsx36, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1763
|
+
function DataTable({
|
|
1764
|
+
data,
|
|
1765
|
+
columns: userColumns,
|
|
1766
|
+
sorting,
|
|
1767
|
+
onSortingChange,
|
|
1768
|
+
filtering,
|
|
1769
|
+
onFilteringChange,
|
|
1770
|
+
pagination,
|
|
1771
|
+
onPaginationChange,
|
|
1772
|
+
loading = false,
|
|
1773
|
+
emptyState,
|
|
1774
|
+
rowActions,
|
|
1775
|
+
onRowClick,
|
|
1776
|
+
selectable = false,
|
|
1777
|
+
compact = true,
|
|
1778
|
+
stickyHeader = false,
|
|
1779
|
+
className
|
|
1780
|
+
}) {
|
|
1781
|
+
const columns = useMemo2(() => {
|
|
1782
|
+
if (!selectable) return userColumns;
|
|
1783
|
+
const selectColumn = {
|
|
1784
|
+
id: "__select",
|
|
1785
|
+
header: ({ table: table2 }) => /* @__PURE__ */ jsx36(
|
|
1786
|
+
Checkbox,
|
|
1787
|
+
{
|
|
1788
|
+
checked: table2.getIsAllPageRowsSelected(),
|
|
1789
|
+
onChange: table2.getToggleAllPageRowsSelectedHandler(),
|
|
1790
|
+
"aria-label": "Select all"
|
|
1791
|
+
}
|
|
1792
|
+
),
|
|
1793
|
+
cell: ({ row }) => /* @__PURE__ */ jsx36(
|
|
1794
|
+
Checkbox,
|
|
1795
|
+
{
|
|
1796
|
+
checked: row.getIsSelected(),
|
|
1797
|
+
onChange: row.getToggleSelectedHandler(),
|
|
1798
|
+
"aria-label": "Select row"
|
|
1799
|
+
}
|
|
1800
|
+
),
|
|
1801
|
+
enableSorting: false
|
|
1802
|
+
};
|
|
1803
|
+
return [selectColumn, ...userColumns];
|
|
1804
|
+
}, [userColumns, selectable]);
|
|
1805
|
+
const table = useReactTable({
|
|
1806
|
+
data,
|
|
1807
|
+
columns,
|
|
1808
|
+
state: {
|
|
1809
|
+
...sorting !== void 0 && { sorting },
|
|
1810
|
+
...filtering !== void 0 && { columnFilters: filtering },
|
|
1811
|
+
...pagination !== void 0 && {
|
|
1812
|
+
pagination: { pageIndex: pagination.page, pageSize: pagination.pageSize }
|
|
1813
|
+
}
|
|
1814
|
+
},
|
|
1815
|
+
onSortingChange: onSortingChange ? (updater) => {
|
|
1816
|
+
const next = typeof updater === "function" ? updater(sorting ?? []) : updater;
|
|
1817
|
+
onSortingChange(next);
|
|
1818
|
+
} : void 0,
|
|
1819
|
+
onColumnFiltersChange: onFilteringChange ? (updater) => {
|
|
1820
|
+
const next = typeof updater === "function" ? updater(filtering ?? []) : updater;
|
|
1821
|
+
onFilteringChange(next);
|
|
1822
|
+
} : void 0,
|
|
1823
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1824
|
+
getSortedRowModel: getSortedRowModel(),
|
|
1825
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
1826
|
+
getPaginationRowModel: pagination ? getPaginationRowModel() : void 0,
|
|
1827
|
+
manualPagination: pagination !== void 0,
|
|
1828
|
+
pageCount: pagination ? Math.ceil(pagination.total / pagination.pageSize) : void 0
|
|
1829
|
+
});
|
|
1830
|
+
const hasActions = !!rowActions;
|
|
1831
|
+
return /* @__PURE__ */ jsxs21("div", { className: cn("overflow-auto", className), children: [
|
|
1832
|
+
/* @__PURE__ */ jsxs21("table", { className: "w-full border-collapse", children: [
|
|
1833
|
+
/* @__PURE__ */ jsx36(
|
|
1834
|
+
DataTableHeader,
|
|
1835
|
+
{
|
|
1836
|
+
headerGroups: table.getHeaderGroups(),
|
|
1837
|
+
onSortingChange,
|
|
1838
|
+
stickyHeader,
|
|
1839
|
+
flexRender
|
|
1840
|
+
}
|
|
1841
|
+
),
|
|
1842
|
+
/* @__PURE__ */ jsx36("tbody", { children: loading ? /* @__PURE__ */ jsx36(LoadingRows, { colSpan: columns.length + (hasActions ? 1 : 0), compact }) : table.getRowModel().rows.length === 0 ? /* @__PURE__ */ jsx36("tr", { children: /* @__PURE__ */ jsx36(
|
|
1843
|
+
"td",
|
|
1844
|
+
{
|
|
1845
|
+
colSpan: columns.length + (hasActions ? 1 : 0),
|
|
1846
|
+
className: "text-center py-8 text-xs text-foreground-muted",
|
|
1847
|
+
children: emptyState ?? "No data"
|
|
1848
|
+
}
|
|
1849
|
+
) }) : table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx36(
|
|
1850
|
+
DataTableRow,
|
|
1851
|
+
{
|
|
1852
|
+
row,
|
|
1853
|
+
onRowClick,
|
|
1854
|
+
rowActions,
|
|
1855
|
+
flexRender
|
|
1856
|
+
},
|
|
1857
|
+
row.id
|
|
1858
|
+
)) })
|
|
1859
|
+
] }),
|
|
1860
|
+
pagination && /* @__PURE__ */ jsx36(
|
|
1861
|
+
DataTablePagination,
|
|
1862
|
+
{
|
|
1863
|
+
page: pagination.page,
|
|
1864
|
+
pageSize: pagination.pageSize,
|
|
1865
|
+
total: pagination.total,
|
|
1866
|
+
onPaginationChange
|
|
1867
|
+
}
|
|
1868
|
+
)
|
|
1869
|
+
] });
|
|
1870
|
+
}
|
|
1871
|
+
function LoadingRows({ colSpan, compact }) {
|
|
1872
|
+
return /* @__PURE__ */ jsx36(Fragment, { children: Array.from({ length: 5 }).map((_, rowIdx) => /* @__PURE__ */ jsx36("tr", { className: compact ? "h-7" : "h-9", children: Array.from({ length: colSpan }).map((_2, colIdx) => /* @__PURE__ */ jsx36("td", { className: "px-2 py-1.5", children: /* @__PURE__ */ jsx36(Skeleton, { className: "h-3 w-full" }) }, colIdx)) }, rowIdx)) });
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// src/composites/device-card.tsx
|
|
1876
|
+
import { jsx as jsx37, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
1877
|
+
var STATUS_COLORS = {
|
|
1878
|
+
online: "bg-success",
|
|
1879
|
+
offline: "bg-danger",
|
|
1880
|
+
warning: "bg-warning",
|
|
1881
|
+
unknown: "bg-foreground-subtle"
|
|
1882
|
+
};
|
|
1883
|
+
function DeviceCard({
|
|
1884
|
+
title,
|
|
1885
|
+
subtitle,
|
|
1886
|
+
status,
|
|
1887
|
+
selected,
|
|
1888
|
+
onClick,
|
|
1889
|
+
badges,
|
|
1890
|
+
actions,
|
|
1891
|
+
offlineAction,
|
|
1892
|
+
className
|
|
1893
|
+
}) {
|
|
1894
|
+
const isOffline = status === "offline";
|
|
1895
|
+
return /* @__PURE__ */ jsxs22(
|
|
1896
|
+
"div",
|
|
1897
|
+
{
|
|
1898
|
+
onClick,
|
|
1899
|
+
className: cn(
|
|
1900
|
+
"w-full rounded-lg border p-3 text-left transition-colors",
|
|
1901
|
+
onClick && "cursor-pointer",
|
|
1902
|
+
selected ? "border-primary bg-primary/10" : "border-border bg-surface hover:bg-surface-hover",
|
|
1903
|
+
isOffline && !selected && "opacity-50",
|
|
1904
|
+
className
|
|
1905
|
+
),
|
|
1906
|
+
children: [
|
|
1907
|
+
/* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between mb-2", children: [
|
|
1908
|
+
/* @__PURE__ */ jsx37("span", { className: "text-sm font-medium truncate", children: title }),
|
|
1909
|
+
status && /* @__PURE__ */ jsx37("span", { className: cn("h-2 w-2 rounded-full shrink-0", STATUS_COLORS[status]) })
|
|
1910
|
+
] }),
|
|
1911
|
+
subtitle && /* @__PURE__ */ jsx37("div", { className: "text-[11px] text-foreground-muted", children: subtitle }),
|
|
1912
|
+
badges && badges.length > 0 && /* @__PURE__ */ jsx37("div", { className: "flex flex-wrap gap-1 mt-2", children: badges.map((badge, i) => {
|
|
1913
|
+
const cls = cn(
|
|
1914
|
+
"rounded px-1.5 py-0.5 text-[10px] flex items-center gap-0.5",
|
|
1915
|
+
selected ? "bg-primary/20" : "bg-surface-hover",
|
|
1916
|
+
badge.onClick && "hover:opacity-80 transition-opacity cursor-pointer"
|
|
1917
|
+
);
|
|
1918
|
+
return badge.onClick ? /* @__PURE__ */ jsxs22(
|
|
1919
|
+
"button",
|
|
1920
|
+
{
|
|
1921
|
+
onClick: (e) => {
|
|
1922
|
+
e.stopPropagation();
|
|
1923
|
+
badge.onClick();
|
|
1924
|
+
},
|
|
1925
|
+
className: cls,
|
|
1926
|
+
children: [
|
|
1927
|
+
badge.icon,
|
|
1928
|
+
badge.label
|
|
1929
|
+
]
|
|
1930
|
+
},
|
|
1931
|
+
i
|
|
1932
|
+
) : /* @__PURE__ */ jsxs22("span", { className: cls, children: [
|
|
1933
|
+
badge.icon,
|
|
1934
|
+
badge.label
|
|
1935
|
+
] }, i);
|
|
1936
|
+
}) }),
|
|
1937
|
+
!isOffline && actions && actions.length > 0 && /* @__PURE__ */ jsx37("div", { className: "flex items-center gap-0.5 mt-2 -mb-1", children: actions.map((action, i) => /* @__PURE__ */ jsx37(
|
|
1938
|
+
"button",
|
|
1939
|
+
{
|
|
1940
|
+
onClick: (e) => {
|
|
1941
|
+
e.stopPropagation();
|
|
1942
|
+
action.onClick();
|
|
1943
|
+
},
|
|
1944
|
+
className: "p-1 rounded hover:bg-surface-hover text-foreground-subtle hover:text-foreground transition-colors",
|
|
1945
|
+
title: action.label,
|
|
1946
|
+
"aria-label": action.label,
|
|
1947
|
+
children: action.icon
|
|
1948
|
+
},
|
|
1949
|
+
i
|
|
1950
|
+
)) }),
|
|
1951
|
+
isOffline && offlineAction && /* @__PURE__ */ jsx37("div", { className: "mt-2", onClick: (e) => e.stopPropagation(), children: offlineAction })
|
|
1952
|
+
]
|
|
1953
|
+
}
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// src/composites/device-grid.tsx
|
|
1958
|
+
import { jsx as jsx38 } from "react/jsx-runtime";
|
|
1959
|
+
function DeviceGrid({
|
|
1960
|
+
children,
|
|
1961
|
+
minCardWidth = 220,
|
|
1962
|
+
gap = 3,
|
|
1963
|
+
className
|
|
1964
|
+
}) {
|
|
1965
|
+
return /* @__PURE__ */ jsx38(
|
|
1966
|
+
"div",
|
|
1967
|
+
{
|
|
1968
|
+
className: cn(
|
|
1969
|
+
"p-4 overflow-y-auto flex-1 content-start",
|
|
1970
|
+
className
|
|
1971
|
+
),
|
|
1972
|
+
style: {
|
|
1973
|
+
display: "grid",
|
|
1974
|
+
gridTemplateColumns: `repeat(auto-fill, minmax(${minCardWidth}px, 1fr))`,
|
|
1975
|
+
gap: `${gap * 4}px`
|
|
1976
|
+
},
|
|
1977
|
+
children
|
|
1978
|
+
}
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
export {
|
|
1982
|
+
AppShell,
|
|
1983
|
+
Badge,
|
|
1984
|
+
Button,
|
|
1985
|
+
Card,
|
|
1986
|
+
Checkbox,
|
|
1987
|
+
CodeBlock,
|
|
1988
|
+
ConfirmDialog,
|
|
1989
|
+
DataTable,
|
|
1990
|
+
DeviceCard,
|
|
1991
|
+
DeviceGrid,
|
|
1992
|
+
Dialog,
|
|
1993
|
+
DialogContent,
|
|
1994
|
+
DialogDescription,
|
|
1995
|
+
DialogFooter,
|
|
1996
|
+
DialogHeader,
|
|
1997
|
+
DialogTitle,
|
|
1998
|
+
DialogTrigger,
|
|
1999
|
+
Dropdown,
|
|
2000
|
+
DropdownContent,
|
|
2001
|
+
DropdownItem,
|
|
2002
|
+
DropdownTrigger,
|
|
2003
|
+
EmptyState,
|
|
2004
|
+
FilterBar,
|
|
2005
|
+
FloatingPanel,
|
|
2006
|
+
FormField,
|
|
2007
|
+
IconButton,
|
|
2008
|
+
Input,
|
|
2009
|
+
KeyValueList,
|
|
2010
|
+
Label,
|
|
2011
|
+
PageHeader,
|
|
2012
|
+
Popover,
|
|
2013
|
+
PopoverContent,
|
|
2014
|
+
PopoverTrigger,
|
|
2015
|
+
ProviderBadge,
|
|
2016
|
+
ScrollArea,
|
|
2017
|
+
Select,
|
|
2018
|
+
Separator,
|
|
2019
|
+
Sidebar,
|
|
2020
|
+
SidebarItem,
|
|
2021
|
+
Skeleton,
|
|
2022
|
+
StatCard,
|
|
2023
|
+
StatusBadge,
|
|
2024
|
+
Switch,
|
|
2025
|
+
Tabs,
|
|
2026
|
+
TabsContent,
|
|
2027
|
+
TabsList,
|
|
2028
|
+
TabsTrigger,
|
|
2029
|
+
ThemeProvider,
|
|
2030
|
+
Tooltip,
|
|
2031
|
+
TooltipContent,
|
|
2032
|
+
TooltipTrigger,
|
|
2033
|
+
cn,
|
|
2034
|
+
createTheme,
|
|
2035
|
+
darkColors,
|
|
2036
|
+
defaultTheme,
|
|
2037
|
+
lightColors,
|
|
2038
|
+
providerIcons,
|
|
2039
|
+
statusIcons,
|
|
2040
|
+
themeToCss,
|
|
2041
|
+
useThemeMode
|
|
2042
|
+
};
|
|
2043
|
+
//# sourceMappingURL=index.js.map
|