@memelabui/ui 0.1.0 → 0.2.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/README.md +85 -28
- package/dist/index.cjs +1237 -57
- package/dist/index.d.cts +188 -8
- package/dist/index.d.ts +188 -8
- package/dist/index.js +1217 -59
- package/dist/preset/index.cjs +13 -45
- package/dist/preset/index.js +13 -41
- package/dist/styles/index.css +351 -91
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forwardRef, useId, useRef, useEffect, useState, useMemo, cloneElement } from 'react';
|
|
1
|
+
import { forwardRef, useId, useRef, useEffect, createContext, useState, useCallback, useMemo, isValidElement, cloneElement, useReducer, useContext } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
|
|
@@ -56,6 +56,94 @@ function focusSafely(el) {
|
|
|
56
56
|
} catch {
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
|
+
function useClipboard(timeout = 2e3) {
|
|
60
|
+
const [copied, setCopied] = useState(false);
|
|
61
|
+
const timerRef = useRef(null);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
return () => {
|
|
64
|
+
if (timerRef.current !== null) {
|
|
65
|
+
clearTimeout(timerRef.current);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}, []);
|
|
69
|
+
const copy = useCallback(
|
|
70
|
+
async (text) => {
|
|
71
|
+
try {
|
|
72
|
+
await navigator.clipboard.writeText(text);
|
|
73
|
+
} catch {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (timerRef.current !== null) {
|
|
77
|
+
clearTimeout(timerRef.current);
|
|
78
|
+
}
|
|
79
|
+
setCopied(true);
|
|
80
|
+
timerRef.current = setTimeout(() => {
|
|
81
|
+
setCopied(false);
|
|
82
|
+
timerRef.current = null;
|
|
83
|
+
}, timeout);
|
|
84
|
+
},
|
|
85
|
+
[timeout]
|
|
86
|
+
);
|
|
87
|
+
return { copy, copied };
|
|
88
|
+
}
|
|
89
|
+
function useDisclosure(defaultOpen = false) {
|
|
90
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
91
|
+
const open = useCallback(() => setIsOpen(true), []);
|
|
92
|
+
const close = useCallback(() => setIsOpen(false), []);
|
|
93
|
+
const toggle = useCallback(() => setIsOpen((prev) => !prev), []);
|
|
94
|
+
return { isOpen, open, close, toggle };
|
|
95
|
+
}
|
|
96
|
+
function useMediaQuery(query) {
|
|
97
|
+
const [matches, setMatches] = useState(() => {
|
|
98
|
+
if (typeof window === "undefined") return false;
|
|
99
|
+
return window.matchMedia(query).matches;
|
|
100
|
+
});
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (typeof window === "undefined") return;
|
|
103
|
+
const mediaQueryList = window.matchMedia(query);
|
|
104
|
+
setMatches(mediaQueryList.matches);
|
|
105
|
+
const handler = (event) => {
|
|
106
|
+
setMatches(event.matches);
|
|
107
|
+
};
|
|
108
|
+
mediaQueryList.addEventListener("change", handler);
|
|
109
|
+
return () => {
|
|
110
|
+
mediaQueryList.removeEventListener("change", handler);
|
|
111
|
+
};
|
|
112
|
+
}, [query]);
|
|
113
|
+
return matches;
|
|
114
|
+
}
|
|
115
|
+
function useDebounce(value, delayMs = 300) {
|
|
116
|
+
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
const timer = setTimeout(() => {
|
|
119
|
+
setDebouncedValue(value);
|
|
120
|
+
}, delayMs);
|
|
121
|
+
return () => {
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
};
|
|
124
|
+
}, [value, delayMs]);
|
|
125
|
+
return debouncedValue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/tokens/colors.ts
|
|
129
|
+
var colors = {
|
|
130
|
+
bg: "#0a0a0f",
|
|
131
|
+
fg: "#f9fafb",
|
|
132
|
+
surface: {
|
|
133
|
+
0: "#0a0a0f",
|
|
134
|
+
50: "#0f0f18",
|
|
135
|
+
100: "#141420",
|
|
136
|
+
200: "#1a1a2e",
|
|
137
|
+
300: "#24243a",
|
|
138
|
+
400: "#2a2a4a"
|
|
139
|
+
},
|
|
140
|
+
primary: { DEFAULT: "#8b5cf6", light: "#a78bfa", dark: "#7c3aed" },
|
|
141
|
+
accent: { DEFAULT: "#667eea", light: "#8b9cf7", dark: "#4c5fd4" },
|
|
142
|
+
glow: { purple: "#764ba2", pink: "#f093fb" },
|
|
143
|
+
success: "#10b981",
|
|
144
|
+
warning: "#f59e0b",
|
|
145
|
+
danger: "#f43f5e"
|
|
146
|
+
};
|
|
59
147
|
var base = "inline-flex items-center justify-center gap-2 rounded-xl font-semibold leading-none transition-[transform,background-color,box-shadow,opacity] select-none [-webkit-tap-highlight-color:transparent] active:translate-y-[0.5px] disabled:opacity-60 disabled:pointer-events-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent";
|
|
60
148
|
var sizeClass = {
|
|
61
149
|
sm: "px-3 py-2 text-sm",
|
|
@@ -63,10 +151,10 @@ var sizeClass = {
|
|
|
63
151
|
lg: "px-4 py-3 text-base"
|
|
64
152
|
};
|
|
65
153
|
var variantClass = {
|
|
66
|
-
primary: "bg-gradient-to-r from-violet-600 to-purple-600 text-white shadow-
|
|
67
|
-
success: "bg-emerald-600 text-white shadow-
|
|
68
|
-
warning: "bg-amber-600 text-white shadow-
|
|
69
|
-
danger: "bg-rose-600 text-white shadow-
|
|
154
|
+
primary: "bg-gradient-to-r from-violet-600 to-purple-600 text-white shadow-glow hover:shadow-glow-lg hover:scale-[1.02]",
|
|
155
|
+
success: "bg-emerald-600 text-white shadow-lg shadow-emerald-600/20 hover:bg-emerald-700",
|
|
156
|
+
warning: "bg-amber-600 text-white shadow-lg shadow-amber-600/20 hover:bg-amber-700",
|
|
157
|
+
danger: "bg-rose-600 text-white shadow-lg shadow-rose-600/20 hover:bg-rose-700",
|
|
70
158
|
secondary: "text-white bg-white/5 ring-1 ring-white/10 hover:bg-white/10 hover:ring-white/20 backdrop-blur-sm",
|
|
71
159
|
ghost: "text-white/70 hover:text-white hover:bg-white/5"
|
|
72
160
|
};
|
|
@@ -78,6 +166,7 @@ var Button = forwardRef(function Button2({ variant = "secondary", size = "md", l
|
|
|
78
166
|
type: type === "submit" ? "submit" : type === "reset" ? "reset" : "button",
|
|
79
167
|
...props,
|
|
80
168
|
disabled: disabled || loading,
|
|
169
|
+
...loading ? { "aria-busy": true } : {},
|
|
81
170
|
className: cn(base, sizeClass[size], variantClass[variant], className),
|
|
82
171
|
children: loading ? /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-center gap-2", children: [
|
|
83
172
|
/* @__PURE__ */ jsx("span", { className: "w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
|
|
@@ -98,9 +187,9 @@ var sizeClass2 = {
|
|
|
98
187
|
};
|
|
99
188
|
var variantClass2 = {
|
|
100
189
|
primary: "bg-primary text-white shadow-glow hover:brightness-[0.98]",
|
|
101
|
-
success: "bg-emerald-600 text-white shadow-
|
|
102
|
-
warning: "bg-amber-600 text-white shadow-
|
|
103
|
-
danger: "bg-rose-600 text-white shadow-
|
|
190
|
+
success: "bg-emerald-600 text-white shadow-lg shadow-emerald-600/20 hover:bg-emerald-700",
|
|
191
|
+
warning: "bg-amber-600 text-white shadow-lg shadow-amber-600/20 hover:bg-amber-700",
|
|
192
|
+
danger: "bg-rose-600 text-white shadow-lg shadow-rose-600/20 hover:bg-rose-700",
|
|
104
193
|
secondary: "text-white bg-white/10 shadow-sm ring-1 ring-white/10",
|
|
105
194
|
ghost: "text-white hover:bg-white/10"
|
|
106
195
|
};
|
|
@@ -117,69 +206,155 @@ var IconButton = forwardRef(function IconButton2({ icon, variant = "ghost", size
|
|
|
117
206
|
}
|
|
118
207
|
);
|
|
119
208
|
});
|
|
120
|
-
var inputBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none placeholder-white/30 focus:ring-2 focus:ring-primary/40 transition-shadow";
|
|
209
|
+
var inputBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none placeholder-white/30 focus-visible:ring-2 focus-visible:ring-primary/40 transition-shadow";
|
|
121
210
|
var Input = forwardRef(function Input2({ hasError, label, error, helperText, className, id: externalId, ...props }, ref) {
|
|
122
211
|
const generatedId = useId();
|
|
123
212
|
const inputId = externalId || generatedId;
|
|
124
213
|
const showError = hasError || !!error;
|
|
214
|
+
const errorId = error ? `${inputId}-error` : void 0;
|
|
215
|
+
const helperId = helperText && !error ? `${inputId}-helper` : void 0;
|
|
125
216
|
const input = /* @__PURE__ */ jsx(
|
|
126
217
|
"input",
|
|
127
218
|
{
|
|
219
|
+
...props,
|
|
128
220
|
ref,
|
|
129
221
|
id: inputId,
|
|
130
|
-
|
|
222
|
+
"aria-invalid": showError || void 0,
|
|
223
|
+
"aria-describedby": errorId || helperId || void 0,
|
|
131
224
|
className: cn(inputBase, showError && "ring-2 ring-rose-500/40 focus:ring-rose-500/40", className)
|
|
132
225
|
}
|
|
133
226
|
);
|
|
134
227
|
if (!label && !error && !helperText) return input;
|
|
135
228
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
136
|
-
label && /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm text-white/
|
|
229
|
+
label && /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm text-white/70 mb-1.5", children: label }),
|
|
137
230
|
input,
|
|
138
|
-
error && /* @__PURE__ */ jsx("p", { className: "text-rose-400 text-xs mt-1", children: error }),
|
|
139
|
-
helperText && !error && /* @__PURE__ */ jsx("p", { className: "text-white/40 text-xs mt-1", children: helperText })
|
|
231
|
+
error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs mt-1", children: error }),
|
|
232
|
+
helperText && !error && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-white/40 text-xs mt-1", children: helperText })
|
|
140
233
|
] });
|
|
141
234
|
});
|
|
142
|
-
var
|
|
143
|
-
var
|
|
235
|
+
var inputBase2 = "w-full rounded-xl pl-10 pr-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none placeholder-white/30 focus-visible:ring-2 focus-visible:ring-primary/40 transition-shadow";
|
|
236
|
+
var SearchInput = forwardRef(function SearchInput2({ label, onClear, className, id: externalId, placeholder = "Search...", value, ...props }, ref) {
|
|
237
|
+
const generatedId = useId();
|
|
238
|
+
const inputId = externalId || generatedId;
|
|
239
|
+
const hasValue = value !== void 0 && value !== "";
|
|
240
|
+
const wrapper = /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
241
|
+
/* @__PURE__ */ jsx("span", { className: "absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none text-white/40", children: /* @__PURE__ */ jsxs(
|
|
242
|
+
"svg",
|
|
243
|
+
{
|
|
244
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
245
|
+
width: "16",
|
|
246
|
+
height: "16",
|
|
247
|
+
viewBox: "0 0 24 24",
|
|
248
|
+
fill: "none",
|
|
249
|
+
stroke: "currentColor",
|
|
250
|
+
strokeWidth: "2",
|
|
251
|
+
strokeLinecap: "round",
|
|
252
|
+
strokeLinejoin: "round",
|
|
253
|
+
"aria-hidden": "true",
|
|
254
|
+
children: [
|
|
255
|
+
/* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
|
|
256
|
+
/* @__PURE__ */ jsx("line", { x1: "21", y1: "21", x2: "16.65", y2: "16.65" })
|
|
257
|
+
]
|
|
258
|
+
}
|
|
259
|
+
) }),
|
|
260
|
+
/* @__PURE__ */ jsx(
|
|
261
|
+
"input",
|
|
262
|
+
{
|
|
263
|
+
ref,
|
|
264
|
+
id: inputId,
|
|
265
|
+
type: "search",
|
|
266
|
+
value,
|
|
267
|
+
placeholder,
|
|
268
|
+
...props,
|
|
269
|
+
className: cn(inputBase2, hasValue && "pr-9", className)
|
|
270
|
+
}
|
|
271
|
+
),
|
|
272
|
+
hasValue && onClear && /* @__PURE__ */ jsx(
|
|
273
|
+
"button",
|
|
274
|
+
{
|
|
275
|
+
type: "button",
|
|
276
|
+
onClick: onClear,
|
|
277
|
+
"aria-label": "Clear search",
|
|
278
|
+
className: "absolute right-3 top-1/2 -translate-y-1/2 text-white/40 hover:text-white transition-colors",
|
|
279
|
+
children: /* @__PURE__ */ jsxs(
|
|
280
|
+
"svg",
|
|
281
|
+
{
|
|
282
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
283
|
+
width: "14",
|
|
284
|
+
height: "14",
|
|
285
|
+
viewBox: "0 0 24 24",
|
|
286
|
+
fill: "none",
|
|
287
|
+
stroke: "currentColor",
|
|
288
|
+
strokeWidth: "2",
|
|
289
|
+
strokeLinecap: "round",
|
|
290
|
+
strokeLinejoin: "round",
|
|
291
|
+
"aria-hidden": "true",
|
|
292
|
+
children: [
|
|
293
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
294
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
] });
|
|
301
|
+
if (!label) return wrapper;
|
|
302
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
303
|
+
/* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm text-white/70 mb-1.5", children: label }),
|
|
304
|
+
wrapper
|
|
305
|
+
] });
|
|
306
|
+
});
|
|
307
|
+
var selectBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none focus-visible:ring-2 focus-visible:ring-primary/40 transition-shadow";
|
|
308
|
+
var Select = forwardRef(function Select2({ hasError, label, error, helperText, className, id: externalId, children, ...props }, ref) {
|
|
144
309
|
const generatedId = useId();
|
|
145
310
|
const selectId = externalId || generatedId;
|
|
146
311
|
const showError = hasError || !!error;
|
|
312
|
+
const errorId = error ? `${selectId}-error` : void 0;
|
|
313
|
+
const helperId = helperText && !error ? `${selectId}-helper` : void 0;
|
|
147
314
|
const select = /* @__PURE__ */ jsx(
|
|
148
315
|
"select",
|
|
149
316
|
{
|
|
317
|
+
...props,
|
|
150
318
|
ref,
|
|
151
319
|
id: selectId,
|
|
152
|
-
|
|
320
|
+
"aria-invalid": showError || void 0,
|
|
321
|
+
"aria-describedby": errorId || helperId || void 0,
|
|
153
322
|
className: cn(selectBase, showError && "ring-2 ring-rose-500/40 focus:ring-rose-500/40", className),
|
|
154
323
|
children
|
|
155
324
|
}
|
|
156
325
|
);
|
|
157
|
-
if (!label && !error) return select;
|
|
326
|
+
if (!label && !error && !helperText) return select;
|
|
158
327
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
159
|
-
label && /* @__PURE__ */ jsx("label", { htmlFor: selectId, className: "block text-sm text-white/
|
|
328
|
+
label && /* @__PURE__ */ jsx("label", { htmlFor: selectId, className: "block text-sm text-white/70 mb-1.5", children: label }),
|
|
160
329
|
select,
|
|
161
|
-
error && /* @__PURE__ */ jsx("p", { className: "text-rose-400 text-xs mt-1", children: error })
|
|
330
|
+
error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs mt-1", children: error }),
|
|
331
|
+
helperText && !error && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-white/40 text-xs mt-1", children: helperText })
|
|
162
332
|
] });
|
|
163
333
|
});
|
|
164
|
-
var textareaBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none placeholder-white/30 focus:ring-2 focus:ring-primary/40 transition-shadow resize-y";
|
|
165
|
-
var Textarea = forwardRef(function Textarea2({ hasError, label, error, className, id: externalId, ...props }, ref) {
|
|
334
|
+
var textareaBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none placeholder-white/30 focus-visible:ring-2 focus-visible:ring-primary/40 transition-shadow resize-y";
|
|
335
|
+
var Textarea = forwardRef(function Textarea2({ hasError, label, error, helperText, className, id: externalId, ...props }, ref) {
|
|
166
336
|
const generatedId = useId();
|
|
167
337
|
const textareaId = externalId || generatedId;
|
|
168
338
|
const showError = hasError || !!error;
|
|
339
|
+
const errorId = error ? `${textareaId}-error` : void 0;
|
|
340
|
+
const helperId = helperText && !error ? `${textareaId}-helper` : void 0;
|
|
169
341
|
const textarea = /* @__PURE__ */ jsx(
|
|
170
342
|
"textarea",
|
|
171
343
|
{
|
|
344
|
+
...props,
|
|
172
345
|
ref,
|
|
173
346
|
id: textareaId,
|
|
174
|
-
|
|
347
|
+
"aria-invalid": showError || void 0,
|
|
348
|
+
"aria-describedby": errorId || helperId || void 0,
|
|
175
349
|
className: cn(textareaBase, showError && "ring-2 ring-rose-500/40 focus:ring-rose-500/40", className)
|
|
176
350
|
}
|
|
177
351
|
);
|
|
178
|
-
if (!label && !error) return textarea;
|
|
352
|
+
if (!label && !error && !helperText) return textarea;
|
|
179
353
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
180
|
-
label && /* @__PURE__ */ jsx("label", { htmlFor: textareaId, className: "block text-sm text-white/
|
|
354
|
+
label && /* @__PURE__ */ jsx("label", { htmlFor: textareaId, className: "block text-sm text-white/70 mb-1.5", children: label }),
|
|
181
355
|
textarea,
|
|
182
|
-
error && /* @__PURE__ */ jsx("p", { className: "text-rose-400 text-xs mt-1", children: error })
|
|
356
|
+
error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs mt-1", children: error }),
|
|
357
|
+
helperText && !error && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-white/40 text-xs mt-1", children: helperText })
|
|
183
358
|
] });
|
|
184
359
|
});
|
|
185
360
|
var base3 = "inline-flex items-center justify-center rounded-full font-semibold ring-1 ring-white/10";
|
|
@@ -209,11 +384,15 @@ var thumbSize = {
|
|
|
209
384
|
sm: { base: "w-4 h-4", translate: "translate-x-4" },
|
|
210
385
|
md: { base: "w-5 h-5", translate: "translate-x-5" }
|
|
211
386
|
};
|
|
212
|
-
|
|
387
|
+
var Toggle = forwardRef(function Toggle2({ checked, onChange, disabled, label, size = "md", id: externalId, "aria-label": ariaLabel }, ref) {
|
|
388
|
+
const generatedId = useId();
|
|
389
|
+
const toggleId = externalId || generatedId;
|
|
213
390
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
214
391
|
/* @__PURE__ */ jsx(
|
|
215
392
|
"button",
|
|
216
393
|
{
|
|
394
|
+
ref,
|
|
395
|
+
id: toggleId,
|
|
217
396
|
type: "button",
|
|
218
397
|
role: "switch",
|
|
219
398
|
"aria-checked": checked,
|
|
@@ -238,20 +417,247 @@ function Toggle({ checked, onChange, disabled, label, size = "md", "aria-label":
|
|
|
238
417
|
)
|
|
239
418
|
}
|
|
240
419
|
),
|
|
241
|
-
label && /* @__PURE__ */ jsx("
|
|
420
|
+
label && /* @__PURE__ */ jsx("label", { htmlFor: toggleId, className: "text-sm text-white/60 cursor-pointer", children: label })
|
|
421
|
+
] });
|
|
422
|
+
});
|
|
423
|
+
var Checkbox = forwardRef(function Checkbox2({ label, error, indeterminate, className, id: externalId, disabled, checked, onChange, ...props }, ref) {
|
|
424
|
+
const generatedId = useId();
|
|
425
|
+
const inputId = externalId || generatedId;
|
|
426
|
+
const errorId = error ? `${inputId}-error` : void 0;
|
|
427
|
+
const internalRef = useRef(null);
|
|
428
|
+
useEffect(() => {
|
|
429
|
+
const el = internalRef.current;
|
|
430
|
+
if (el) {
|
|
431
|
+
el.indeterminate = indeterminate ?? false;
|
|
432
|
+
}
|
|
433
|
+
}, [indeterminate]);
|
|
434
|
+
const setRefs = (el) => {
|
|
435
|
+
internalRef.current = el;
|
|
436
|
+
if (typeof ref === "function") {
|
|
437
|
+
ref(el);
|
|
438
|
+
} else if (ref) {
|
|
439
|
+
ref.current = el;
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("inline-flex flex-col gap-1", className), children: [
|
|
443
|
+
/* @__PURE__ */ jsxs(
|
|
444
|
+
"label",
|
|
445
|
+
{
|
|
446
|
+
htmlFor: inputId,
|
|
447
|
+
className: cn(
|
|
448
|
+
"inline-flex items-center gap-2.5",
|
|
449
|
+
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
|
|
450
|
+
),
|
|
451
|
+
children: [
|
|
452
|
+
/* @__PURE__ */ jsx(
|
|
453
|
+
"input",
|
|
454
|
+
{
|
|
455
|
+
...props,
|
|
456
|
+
ref: setRefs,
|
|
457
|
+
id: inputId,
|
|
458
|
+
type: "checkbox",
|
|
459
|
+
checked,
|
|
460
|
+
onChange,
|
|
461
|
+
disabled,
|
|
462
|
+
"aria-invalid": !!error || void 0,
|
|
463
|
+
"aria-describedby": errorId,
|
|
464
|
+
className: "sr-only peer"
|
|
465
|
+
}
|
|
466
|
+
),
|
|
467
|
+
/* @__PURE__ */ jsxs(
|
|
468
|
+
"span",
|
|
469
|
+
{
|
|
470
|
+
className: cn(
|
|
471
|
+
"relative flex items-center justify-center w-5 h-5 rounded-md",
|
|
472
|
+
"bg-white/10 border border-white/20",
|
|
473
|
+
"transition-colors duration-150",
|
|
474
|
+
"peer-focus-visible:ring-2 peer-focus-visible:ring-primary/40 peer-focus-visible:ring-offset-1 peer-focus-visible:ring-offset-transparent",
|
|
475
|
+
checked && !indeterminate && "bg-primary border-primary",
|
|
476
|
+
indeterminate && "bg-primary border-primary"
|
|
477
|
+
),
|
|
478
|
+
"aria-hidden": "true",
|
|
479
|
+
children: [
|
|
480
|
+
checked && !indeterminate && /* @__PURE__ */ jsx(
|
|
481
|
+
"svg",
|
|
482
|
+
{
|
|
483
|
+
viewBox: "0 0 12 10",
|
|
484
|
+
fill: "none",
|
|
485
|
+
stroke: "white",
|
|
486
|
+
strokeWidth: "1.8",
|
|
487
|
+
strokeLinecap: "round",
|
|
488
|
+
strokeLinejoin: "round",
|
|
489
|
+
className: "w-3 h-2.5",
|
|
490
|
+
children: /* @__PURE__ */ jsx("polyline", { points: "1,5 4.5,8.5 11,1" })
|
|
491
|
+
}
|
|
492
|
+
),
|
|
493
|
+
indeterminate && /* @__PURE__ */ jsx(
|
|
494
|
+
"svg",
|
|
495
|
+
{
|
|
496
|
+
viewBox: "0 0 10 2",
|
|
497
|
+
fill: "none",
|
|
498
|
+
stroke: "white",
|
|
499
|
+
strokeWidth: "2",
|
|
500
|
+
strokeLinecap: "round",
|
|
501
|
+
className: "w-2.5 h-0.5",
|
|
502
|
+
children: /* @__PURE__ */ jsx("line", { x1: "0", y1: "1", x2: "10", y2: "1" })
|
|
503
|
+
}
|
|
504
|
+
)
|
|
505
|
+
]
|
|
506
|
+
}
|
|
507
|
+
),
|
|
508
|
+
label && /* @__PURE__ */ jsx("span", { className: "text-sm text-white/80 select-none", children: label })
|
|
509
|
+
]
|
|
510
|
+
}
|
|
511
|
+
),
|
|
512
|
+
error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs ml-7", children: error })
|
|
242
513
|
] });
|
|
514
|
+
});
|
|
515
|
+
var RadioGroupContext = createContext(null);
|
|
516
|
+
function useRadioGroup() {
|
|
517
|
+
const ctx = useContext(RadioGroupContext);
|
|
518
|
+
if (!ctx) {
|
|
519
|
+
throw new Error("RadioItem must be used inside a RadioGroup");
|
|
520
|
+
}
|
|
521
|
+
return ctx;
|
|
522
|
+
}
|
|
523
|
+
function RadioGroup({
|
|
524
|
+
value: controlledValue,
|
|
525
|
+
defaultValue,
|
|
526
|
+
onValueChange,
|
|
527
|
+
name: externalName,
|
|
528
|
+
disabled = false,
|
|
529
|
+
orientation = "vertical",
|
|
530
|
+
label,
|
|
531
|
+
error,
|
|
532
|
+
children,
|
|
533
|
+
className
|
|
534
|
+
}) {
|
|
535
|
+
const groupId = useId();
|
|
536
|
+
const generatedName = useId();
|
|
537
|
+
const name = externalName ?? generatedName;
|
|
538
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
539
|
+
const isControlled = controlledValue !== void 0;
|
|
540
|
+
const currentValue = isControlled ? controlledValue : uncontrolledValue;
|
|
541
|
+
const handleChange = useCallback(
|
|
542
|
+
(val) => {
|
|
543
|
+
if (!isControlled) setUncontrolledValue(val);
|
|
544
|
+
onValueChange?.(val);
|
|
545
|
+
},
|
|
546
|
+
[isControlled, onValueChange]
|
|
547
|
+
);
|
|
548
|
+
const labelId = label ? `${groupId}-label` : void 0;
|
|
549
|
+
const errorId = error ? `${groupId}-error` : void 0;
|
|
550
|
+
const ctxValue = useMemo(
|
|
551
|
+
() => ({ value: currentValue, onChange: handleChange, name, disabled, groupId }),
|
|
552
|
+
[currentValue, handleChange, name, disabled, groupId]
|
|
553
|
+
);
|
|
554
|
+
return /* @__PURE__ */ jsx(RadioGroupContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsxs(
|
|
555
|
+
"fieldset",
|
|
556
|
+
{
|
|
557
|
+
role: "radiogroup",
|
|
558
|
+
"aria-labelledby": labelId,
|
|
559
|
+
"aria-describedby": errorId,
|
|
560
|
+
"aria-disabled": disabled || void 0,
|
|
561
|
+
className: cn("border-none p-0 m-0", className),
|
|
562
|
+
children: [
|
|
563
|
+
label && /* @__PURE__ */ jsx("legend", { id: labelId, className: "text-sm text-white/50 mb-2 float-none p-0", children: label }),
|
|
564
|
+
/* @__PURE__ */ jsx(
|
|
565
|
+
"div",
|
|
566
|
+
{
|
|
567
|
+
className: cn(
|
|
568
|
+
"flex gap-3",
|
|
569
|
+
orientation === "vertical" ? "flex-col" : "flex-row flex-wrap"
|
|
570
|
+
),
|
|
571
|
+
children
|
|
572
|
+
}
|
|
573
|
+
),
|
|
574
|
+
error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs mt-1.5", children: error })
|
|
575
|
+
]
|
|
576
|
+
}
|
|
577
|
+
) });
|
|
578
|
+
}
|
|
579
|
+
function RadioItem({ value, disabled: itemDisabled, children, className }) {
|
|
580
|
+
const { value: groupValue, onChange, name, disabled: groupDisabled, groupId } = useRadioGroup();
|
|
581
|
+
const inputId = useId();
|
|
582
|
+
const inputRef = useRef(null);
|
|
583
|
+
const isDisabled = groupDisabled || itemDisabled;
|
|
584
|
+
const isSelected = groupValue === value;
|
|
585
|
+
const handleKeyDown = (e) => {
|
|
586
|
+
if (e.key !== "ArrowDown" && e.key !== "ArrowUp" && e.key !== "ArrowRight" && e.key !== "ArrowLeft") {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
e.preventDefault();
|
|
590
|
+
const fieldset = inputRef.current?.closest("fieldset");
|
|
591
|
+
if (!fieldset) return;
|
|
592
|
+
const inputs = Array.from(
|
|
593
|
+
fieldset.querySelectorAll(`input[type="radio"][name="${name}"]:not(:disabled)`)
|
|
594
|
+
);
|
|
595
|
+
const currentIndex = inputs.indexOf(inputRef.current);
|
|
596
|
+
if (currentIndex === -1) return;
|
|
597
|
+
const forward = e.key === "ArrowDown" || e.key === "ArrowRight";
|
|
598
|
+
const nextIndex = forward ? (currentIndex + 1) % inputs.length : (currentIndex - 1 + inputs.length) % inputs.length;
|
|
599
|
+
const nextInput = inputs[nextIndex];
|
|
600
|
+
nextInput.focus();
|
|
601
|
+
onChange(nextInput.value);
|
|
602
|
+
};
|
|
603
|
+
return /* @__PURE__ */ jsxs(
|
|
604
|
+
"label",
|
|
605
|
+
{
|
|
606
|
+
htmlFor: inputId,
|
|
607
|
+
className: cn(
|
|
608
|
+
"inline-flex items-center gap-2.5",
|
|
609
|
+
isDisabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer",
|
|
610
|
+
className
|
|
611
|
+
),
|
|
612
|
+
children: [
|
|
613
|
+
/* @__PURE__ */ jsx(
|
|
614
|
+
"input",
|
|
615
|
+
{
|
|
616
|
+
ref: inputRef,
|
|
617
|
+
id: inputId,
|
|
618
|
+
type: "radio",
|
|
619
|
+
name,
|
|
620
|
+
value,
|
|
621
|
+
checked: isSelected,
|
|
622
|
+
disabled: isDisabled,
|
|
623
|
+
onChange: () => onChange(value),
|
|
624
|
+
onKeyDown: handleKeyDown,
|
|
625
|
+
className: "sr-only peer",
|
|
626
|
+
"aria-describedby": void 0
|
|
627
|
+
}
|
|
628
|
+
),
|
|
629
|
+
/* @__PURE__ */ jsx(
|
|
630
|
+
"span",
|
|
631
|
+
{
|
|
632
|
+
className: cn(
|
|
633
|
+
"relative flex items-center justify-center w-5 h-5 rounded-full",
|
|
634
|
+
"bg-white/10 border border-white/20",
|
|
635
|
+
"transition-colors duration-150",
|
|
636
|
+
"peer-focus-visible:ring-2 peer-focus-visible:ring-primary/40 peer-focus-visible:ring-offset-1 peer-focus-visible:ring-offset-transparent",
|
|
637
|
+
isSelected && "border-primary"
|
|
638
|
+
),
|
|
639
|
+
"aria-hidden": "true",
|
|
640
|
+
children: isSelected && /* @__PURE__ */ jsx("span", { className: "w-2.5 h-2.5 rounded-full bg-primary" })
|
|
641
|
+
}
|
|
642
|
+
),
|
|
643
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-white/80 select-none", children })
|
|
644
|
+
]
|
|
645
|
+
}
|
|
646
|
+
);
|
|
243
647
|
}
|
|
244
648
|
var sizeClass4 = {
|
|
245
649
|
sm: "h-3 w-3 border",
|
|
246
650
|
md: "h-4 w-4 border-2",
|
|
247
651
|
lg: "h-6 w-6 border-2"
|
|
248
652
|
};
|
|
249
|
-
function Spinner({ className, size = "md" }) {
|
|
653
|
+
function Spinner({ className, size = "md", label }) {
|
|
250
654
|
return /* @__PURE__ */ jsx(
|
|
251
655
|
"span",
|
|
252
656
|
{
|
|
253
657
|
className: cn("inline-block rounded-full border-white/15 border-t-primary animate-spin", sizeClass4[size], className),
|
|
254
|
-
"
|
|
658
|
+
role: label ? "status" : void 0,
|
|
659
|
+
"aria-hidden": label ? void 0 : "true",
|
|
660
|
+
"aria-label": label || void 0
|
|
255
661
|
}
|
|
256
662
|
);
|
|
257
663
|
}
|
|
@@ -264,14 +670,170 @@ function Skeleton({ className, circle }) {
|
|
|
264
670
|
}
|
|
265
671
|
);
|
|
266
672
|
}
|
|
267
|
-
|
|
673
|
+
var TabsContext = createContext(null);
|
|
674
|
+
function useTabsContext() {
|
|
675
|
+
const ctx = useContext(TabsContext);
|
|
676
|
+
if (!ctx) throw new Error("Tabs sub-components must be used inside <Tabs>");
|
|
677
|
+
return ctx;
|
|
678
|
+
}
|
|
679
|
+
function Tabs({
|
|
680
|
+
defaultValue = "",
|
|
681
|
+
value,
|
|
682
|
+
onValueChange,
|
|
683
|
+
variant = "underline",
|
|
684
|
+
children,
|
|
685
|
+
className
|
|
686
|
+
}) {
|
|
687
|
+
const baseId = useId();
|
|
688
|
+
const isControlled = value !== void 0;
|
|
689
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
690
|
+
const activeValue = isControlled ? value ?? "" : internalValue;
|
|
691
|
+
const setActiveValue = useCallback(
|
|
692
|
+
(next) => {
|
|
693
|
+
if (!isControlled) setInternalValue(next);
|
|
694
|
+
onValueChange?.(next);
|
|
695
|
+
},
|
|
696
|
+
[isControlled, onValueChange]
|
|
697
|
+
);
|
|
698
|
+
const ctxValue = useMemo(
|
|
699
|
+
() => ({ activeValue, setActiveValue, variant, baseId }),
|
|
700
|
+
[activeValue, setActiveValue, variant, baseId]
|
|
701
|
+
);
|
|
702
|
+
return /* @__PURE__ */ jsx(TabsContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsx("div", { className: cn("flex flex-col", className), children }) });
|
|
703
|
+
}
|
|
704
|
+
function TabList({ children, className }) {
|
|
705
|
+
const { variant } = useTabsContext();
|
|
706
|
+
const listRef = useRef(null);
|
|
707
|
+
const handleKeyDown = (e) => {
|
|
708
|
+
const list = listRef.current;
|
|
709
|
+
if (!list) return;
|
|
710
|
+
const tabs = Array.from(list.querySelectorAll('[role="tab"]:not([disabled])'));
|
|
711
|
+
if (tabs.length === 0) return;
|
|
712
|
+
const focused = document.activeElement;
|
|
713
|
+
if (!focused || !list.contains(focused)) return;
|
|
714
|
+
const currentIndex = tabs.indexOf(focused);
|
|
715
|
+
let nextIndex = currentIndex;
|
|
716
|
+
switch (e.key) {
|
|
717
|
+
case "ArrowRight":
|
|
718
|
+
e.preventDefault();
|
|
719
|
+
nextIndex = currentIndex < tabs.length - 1 ? currentIndex + 1 : 0;
|
|
720
|
+
break;
|
|
721
|
+
case "ArrowLeft":
|
|
722
|
+
e.preventDefault();
|
|
723
|
+
nextIndex = currentIndex > 0 ? currentIndex - 1 : tabs.length - 1;
|
|
724
|
+
break;
|
|
725
|
+
case "Home":
|
|
726
|
+
e.preventDefault();
|
|
727
|
+
nextIndex = 0;
|
|
728
|
+
break;
|
|
729
|
+
case "End":
|
|
730
|
+
e.preventDefault();
|
|
731
|
+
nextIndex = tabs.length - 1;
|
|
732
|
+
break;
|
|
733
|
+
case "Enter":
|
|
734
|
+
case " ":
|
|
735
|
+
e.preventDefault();
|
|
736
|
+
focused?.click();
|
|
737
|
+
return;
|
|
738
|
+
default:
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
tabs[nextIndex]?.focus();
|
|
742
|
+
tabs[nextIndex]?.click();
|
|
743
|
+
};
|
|
268
744
|
return /* @__PURE__ */ jsx(
|
|
269
745
|
"div",
|
|
270
746
|
{
|
|
747
|
+
ref: listRef,
|
|
748
|
+
role: "tablist",
|
|
749
|
+
onKeyDown: handleKeyDown,
|
|
750
|
+
className: cn(
|
|
751
|
+
"flex",
|
|
752
|
+
variant === "underline" && "border-b border-white/10",
|
|
753
|
+
variant === "pill" && "gap-1 p-1 rounded-xl bg-white/5 ring-1 ring-white/10 w-fit",
|
|
754
|
+
className
|
|
755
|
+
),
|
|
756
|
+
children
|
|
757
|
+
}
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
function Tab({ value, disabled = false, children, className }) {
|
|
761
|
+
const { activeValue, setActiveValue, variant, baseId } = useTabsContext();
|
|
762
|
+
const isActive = activeValue === value;
|
|
763
|
+
return /* @__PURE__ */ jsx(
|
|
764
|
+
"button",
|
|
765
|
+
{
|
|
766
|
+
type: "button",
|
|
767
|
+
role: "tab",
|
|
768
|
+
id: `${baseId}-tab-${value}`,
|
|
769
|
+
"aria-selected": isActive,
|
|
770
|
+
"aria-controls": `${baseId}-panel-${value}`,
|
|
771
|
+
disabled,
|
|
772
|
+
tabIndex: isActive ? 0 : -1,
|
|
773
|
+
onClick: () => {
|
|
774
|
+
if (!disabled) setActiveValue(value);
|
|
775
|
+
},
|
|
776
|
+
className: cn(
|
|
777
|
+
"relative px-4 py-2.5 text-sm font-medium transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1 focus-visible:ring-offset-transparent",
|
|
778
|
+
// underline variant
|
|
779
|
+
variant === "underline" && [
|
|
780
|
+
"border-b-2 -mb-px",
|
|
781
|
+
isActive ? "border-primary text-white" : "border-transparent text-white/40 hover:text-white/70 hover:border-white/20",
|
|
782
|
+
disabled && "opacity-40 cursor-not-allowed hover:text-white/40 hover:border-transparent"
|
|
783
|
+
],
|
|
784
|
+
// pill variant
|
|
785
|
+
variant === "pill" && [
|
|
786
|
+
"rounded-lg",
|
|
787
|
+
isActive ? "bg-white/10 text-white shadow-sm" : "text-white/50 hover:text-white/80 hover:bg-white/5",
|
|
788
|
+
disabled && "opacity-40 cursor-not-allowed hover:bg-transparent hover:text-white/50"
|
|
789
|
+
],
|
|
790
|
+
className
|
|
791
|
+
),
|
|
792
|
+
children
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
function TabPanel({ value, children, className }) {
|
|
797
|
+
const { activeValue, baseId } = useTabsContext();
|
|
798
|
+
if (activeValue !== value) return null;
|
|
799
|
+
return /* @__PURE__ */ jsx(
|
|
800
|
+
"div",
|
|
801
|
+
{
|
|
802
|
+
role: "tabpanel",
|
|
803
|
+
id: `${baseId}-panel-${value}`,
|
|
804
|
+
"aria-labelledby": `${baseId}-tab-${value}`,
|
|
805
|
+
tabIndex: 0,
|
|
806
|
+
className: cn("mt-4 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary", className),
|
|
807
|
+
children
|
|
808
|
+
}
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
var Card = forwardRef(function Card2({ hoverable, variant = "surface", className, ...props }, ref) {
|
|
812
|
+
return /* @__PURE__ */ jsx(
|
|
813
|
+
"div",
|
|
814
|
+
{
|
|
815
|
+
ref,
|
|
271
816
|
...props,
|
|
272
817
|
className: cn(variant === "glass" ? "glass" : "surface", hoverable && "surface-hover", className)
|
|
273
818
|
}
|
|
274
819
|
);
|
|
820
|
+
});
|
|
821
|
+
var scrollLockCount = 0;
|
|
822
|
+
var savedOverflow = "";
|
|
823
|
+
function lockScroll() {
|
|
824
|
+
if (typeof document === "undefined") return;
|
|
825
|
+
if (scrollLockCount === 0) {
|
|
826
|
+
savedOverflow = document.body.style.overflow;
|
|
827
|
+
document.body.style.overflow = "hidden";
|
|
828
|
+
}
|
|
829
|
+
scrollLockCount++;
|
|
830
|
+
}
|
|
831
|
+
function unlockScroll() {
|
|
832
|
+
if (typeof document === "undefined") return;
|
|
833
|
+
scrollLockCount = Math.max(0, scrollLockCount - 1);
|
|
834
|
+
if (scrollLockCount === 0) {
|
|
835
|
+
document.body.style.overflow = savedOverflow;
|
|
836
|
+
}
|
|
275
837
|
}
|
|
276
838
|
function Modal({
|
|
277
839
|
isOpen,
|
|
@@ -304,6 +866,11 @@ function Modal({
|
|
|
304
866
|
if (lastActive?.isConnected) focusSafely(lastActive);
|
|
305
867
|
};
|
|
306
868
|
}, [isOpen]);
|
|
869
|
+
useEffect(() => {
|
|
870
|
+
if (!isOpen) return;
|
|
871
|
+
lockScroll();
|
|
872
|
+
return () => unlockScroll();
|
|
873
|
+
}, [isOpen]);
|
|
307
874
|
if (!isOpen) return null;
|
|
308
875
|
return /* @__PURE__ */ jsx(
|
|
309
876
|
"div",
|
|
@@ -329,7 +896,7 @@ function Modal({
|
|
|
329
896
|
ref: dialogRef,
|
|
330
897
|
className: cn(
|
|
331
898
|
"w-full rounded-t-3xl sm:rounded-2xl shadow-xl ring-1 ring-white/10 animate-modal-pop focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
|
|
332
|
-
useGlass && "glass bg-
|
|
899
|
+
useGlass && "glass bg-surface-50/80",
|
|
333
900
|
contentClassName
|
|
334
901
|
),
|
|
335
902
|
onMouseDown: (e) => e.stopPropagation(),
|
|
@@ -442,17 +1009,17 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
|
|
|
442
1009
|
const anchorRef = useRef(null);
|
|
443
1010
|
const [open, setOpen] = useState(false);
|
|
444
1011
|
const [pos, setPos] = useState(null);
|
|
445
|
-
|
|
1012
|
+
useMemo(() => {
|
|
446
1013
|
if (typeof content === "string") return content.trim();
|
|
447
1014
|
return "";
|
|
448
1015
|
}, [content]);
|
|
449
|
-
|
|
1016
|
+
const clearTimer = useCallback(() => {
|
|
450
1017
|
if (openTimerRef.current !== null) {
|
|
451
1018
|
window.clearTimeout(openTimerRef.current);
|
|
452
1019
|
openTimerRef.current = null;
|
|
453
1020
|
}
|
|
454
|
-
}
|
|
455
|
-
|
|
1021
|
+
}, []);
|
|
1022
|
+
const updatePosition = useCallback(() => {
|
|
456
1023
|
const el = anchorRef.current;
|
|
457
1024
|
if (!el) return;
|
|
458
1025
|
const r = el.getBoundingClientRect();
|
|
@@ -467,17 +1034,17 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
|
|
|
467
1034
|
top: Math.round(effPlacement === "top" ? topY : bottomY),
|
|
468
1035
|
placement: effPlacement
|
|
469
1036
|
});
|
|
470
|
-
}
|
|
471
|
-
|
|
1037
|
+
}, [placement]);
|
|
1038
|
+
const scheduleOpen = useCallback(() => {
|
|
472
1039
|
clearTimer();
|
|
473
1040
|
openTimerRef.current = window.setTimeout(() => {
|
|
474
1041
|
setOpen(true);
|
|
475
1042
|
}, Math.max(0, delayMs));
|
|
476
|
-
}
|
|
477
|
-
|
|
1043
|
+
}, [clearTimer, delayMs]);
|
|
1044
|
+
const close = useCallback(() => {
|
|
478
1045
|
clearTimer();
|
|
479
1046
|
setOpen(false);
|
|
480
|
-
}
|
|
1047
|
+
}, [clearTimer]);
|
|
481
1048
|
useEffect(() => {
|
|
482
1049
|
if (!open) return;
|
|
483
1050
|
updatePosition();
|
|
@@ -489,45 +1056,52 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
|
|
|
489
1056
|
window.removeEventListener("scroll", onScroll, true);
|
|
490
1057
|
window.removeEventListener("resize", onResize);
|
|
491
1058
|
};
|
|
492
|
-
}, [open]);
|
|
1059
|
+
}, [open, updatePosition]);
|
|
493
1060
|
useEffect(() => {
|
|
494
1061
|
return () => clearTimer();
|
|
495
|
-
}, []);
|
|
1062
|
+
}, [clearTimer]);
|
|
1063
|
+
if (!isValidElement(children)) return children;
|
|
496
1064
|
const child = cloneElement(children, {
|
|
497
1065
|
ref: (node) => {
|
|
498
1066
|
anchorRef.current = node;
|
|
499
|
-
const
|
|
1067
|
+
const childProps = children.props;
|
|
1068
|
+
const prevRef = childProps.ref;
|
|
500
1069
|
if (typeof prevRef === "function") prevRef(node);
|
|
501
1070
|
else if (prevRef && typeof prevRef === "object") prevRef.current = node;
|
|
502
1071
|
},
|
|
503
1072
|
onMouseEnter: (e) => {
|
|
504
|
-
children.props
|
|
1073
|
+
const childProps = children.props;
|
|
1074
|
+
if (typeof childProps.onMouseEnter === "function") childProps.onMouseEnter(e);
|
|
505
1075
|
scheduleOpen();
|
|
506
1076
|
},
|
|
507
1077
|
onMouseLeave: (e) => {
|
|
508
|
-
children.props
|
|
1078
|
+
const childProps = children.props;
|
|
1079
|
+
if (typeof childProps.onMouseLeave === "function") childProps.onMouseLeave(e);
|
|
509
1080
|
close();
|
|
510
1081
|
},
|
|
511
1082
|
onFocus: (e) => {
|
|
512
|
-
children.props
|
|
1083
|
+
const childProps = children.props;
|
|
1084
|
+
if (typeof childProps.onFocus === "function") childProps.onFocus(e);
|
|
513
1085
|
scheduleOpen();
|
|
514
1086
|
},
|
|
515
1087
|
onBlur: (e) => {
|
|
516
|
-
children.props
|
|
1088
|
+
const childProps = children.props;
|
|
1089
|
+
if (typeof childProps.onBlur === "function") childProps.onBlur(e);
|
|
517
1090
|
close();
|
|
518
1091
|
},
|
|
519
|
-
...
|
|
1092
|
+
...open ? { "aria-describedby": tooltipId } : {}
|
|
520
1093
|
});
|
|
521
1094
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
522
1095
|
child,
|
|
523
|
-
open && content ? createPortal(
|
|
1096
|
+
open && content && typeof document !== "undefined" ? createPortal(
|
|
524
1097
|
/* @__PURE__ */ jsx(
|
|
525
1098
|
"div",
|
|
526
1099
|
{
|
|
527
1100
|
id: tooltipId,
|
|
528
1101
|
role: "tooltip",
|
|
529
1102
|
className: cn(
|
|
530
|
-
"fixed z-[9999] max-w-[320px] rounded-lg
|
|
1103
|
+
"fixed z-[9999] max-w-[320px] rounded-lg px-3 py-2 text-xs leading-snug shadow-xl pointer-events-none",
|
|
1104
|
+
"bg-surface-100 text-white ring-1 ring-white/10",
|
|
531
1105
|
className
|
|
532
1106
|
),
|
|
533
1107
|
style: pos ? {
|
|
@@ -553,14 +1127,18 @@ function EmptyState({ icon: Icon, title, description, actionLabel, onAction, chi
|
|
|
553
1127
|
}
|
|
554
1128
|
function CollapsibleSection({ title, defaultOpen = true, children, right, className }) {
|
|
555
1129
|
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
1130
|
+
const contentId = useId();
|
|
1131
|
+
const titleId = `${contentId}-title`;
|
|
556
1132
|
return /* @__PURE__ */ jsxs("div", { className: cn("rounded-xl ring-1 ring-white/10 bg-white/5 overflow-hidden", className), children: [
|
|
557
1133
|
/* @__PURE__ */ jsxs(
|
|
558
1134
|
"button",
|
|
559
1135
|
{
|
|
560
1136
|
type: "button",
|
|
1137
|
+
id: titleId,
|
|
561
1138
|
onClick: () => setIsOpen((prev) => !prev),
|
|
562
1139
|
className: "flex w-full items-center justify-between gap-3 px-4 py-3 text-left hover:bg-white/[0.03] transition-colors",
|
|
563
1140
|
"aria-expanded": isOpen,
|
|
1141
|
+
"aria-controls": contentId,
|
|
564
1142
|
children: [
|
|
565
1143
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
566
1144
|
/* @__PURE__ */ jsx(
|
|
@@ -584,6 +1162,9 @@ function CollapsibleSection({ title, defaultOpen = true, children, right, classN
|
|
|
584
1162
|
/* @__PURE__ */ jsx(
|
|
585
1163
|
"div",
|
|
586
1164
|
{
|
|
1165
|
+
id: contentId,
|
|
1166
|
+
role: "region",
|
|
1167
|
+
"aria-labelledby": titleId,
|
|
587
1168
|
className: cn(
|
|
588
1169
|
"grid transition-[grid-template-rows] duration-200 ease-out",
|
|
589
1170
|
isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
|
|
@@ -593,6 +1174,349 @@ function CollapsibleSection({ title, defaultOpen = true, children, right, classN
|
|
|
593
1174
|
)
|
|
594
1175
|
] });
|
|
595
1176
|
}
|
|
1177
|
+
var DropdownContext = createContext(null);
|
|
1178
|
+
function useDropdownContext(component) {
|
|
1179
|
+
const ctx = useContext(DropdownContext);
|
|
1180
|
+
if (!ctx) {
|
|
1181
|
+
throw new Error(`<${component}> must be rendered inside <Dropdown>`);
|
|
1182
|
+
}
|
|
1183
|
+
return ctx;
|
|
1184
|
+
}
|
|
1185
|
+
function Dropdown({ children, className }) {
|
|
1186
|
+
const [open, setOpen] = useState(false);
|
|
1187
|
+
const triggerId = useId();
|
|
1188
|
+
const menuId = useId();
|
|
1189
|
+
const triggerRef = useRef(null);
|
|
1190
|
+
const toggleOpen = useCallback(() => setOpen((prev) => !prev), []);
|
|
1191
|
+
const ctxValue = useMemo(
|
|
1192
|
+
() => ({ open, setOpen, toggleOpen, triggerId, menuId, triggerRef }),
|
|
1193
|
+
[open, setOpen, toggleOpen, triggerId, menuId, triggerRef]
|
|
1194
|
+
);
|
|
1195
|
+
return /* @__PURE__ */ jsx(DropdownContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsx("div", { className: cn("relative inline-block", className), children }) });
|
|
1196
|
+
}
|
|
1197
|
+
function DropdownTrigger({ children, className }) {
|
|
1198
|
+
const { open, toggleOpen, triggerId, menuId, triggerRef } = useDropdownContext("DropdownTrigger");
|
|
1199
|
+
if (!isValidElement(children)) return children;
|
|
1200
|
+
return cloneElement(children, {
|
|
1201
|
+
id: triggerId,
|
|
1202
|
+
"aria-haspopup": "menu",
|
|
1203
|
+
"aria-expanded": open,
|
|
1204
|
+
"aria-controls": open ? menuId : void 0,
|
|
1205
|
+
className: cn(children.props.className, className),
|
|
1206
|
+
ref: (node) => {
|
|
1207
|
+
triggerRef.current = node;
|
|
1208
|
+
const childRef = children.ref;
|
|
1209
|
+
if (typeof childRef === "function") childRef(node);
|
|
1210
|
+
else if (childRef && typeof childRef === "object") childRef.current = node;
|
|
1211
|
+
},
|
|
1212
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1213
|
+
onClick: (e) => {
|
|
1214
|
+
const childProps = children.props;
|
|
1215
|
+
if (typeof childProps.onClick === "function") childProps.onClick(e);
|
|
1216
|
+
toggleOpen();
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
function DropdownMenu({ children, className, align = "left" }) {
|
|
1221
|
+
const { open, setOpen, menuId, triggerId, triggerRef } = useDropdownContext("DropdownMenu");
|
|
1222
|
+
const menuRef = useRef(null);
|
|
1223
|
+
const [pos, setPos] = useState(null);
|
|
1224
|
+
const [visible, setVisible] = useState(false);
|
|
1225
|
+
const updatePosition = useCallback(() => {
|
|
1226
|
+
const trigger = triggerRef.current;
|
|
1227
|
+
if (!trigger) return;
|
|
1228
|
+
const rect = trigger.getBoundingClientRect();
|
|
1229
|
+
const left = align === "right" ? rect.right : rect.left;
|
|
1230
|
+
setPos({
|
|
1231
|
+
top: Math.round(rect.bottom + 6),
|
|
1232
|
+
left: Math.round(left)
|
|
1233
|
+
});
|
|
1234
|
+
}, [align, triggerRef]);
|
|
1235
|
+
useEffect(() => {
|
|
1236
|
+
if (!open) {
|
|
1237
|
+
setVisible(false);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
updatePosition();
|
|
1241
|
+
const raf = window.requestAnimationFrame(() => {
|
|
1242
|
+
setVisible(true);
|
|
1243
|
+
const menuEl = menuRef.current;
|
|
1244
|
+
if (!menuEl) return;
|
|
1245
|
+
const items = getFocusableElements(menuEl);
|
|
1246
|
+
focusSafely(items[0] ?? menuEl);
|
|
1247
|
+
});
|
|
1248
|
+
return () => window.cancelAnimationFrame(raf);
|
|
1249
|
+
}, [open, updatePosition]);
|
|
1250
|
+
useEffect(() => {
|
|
1251
|
+
if (!open) return;
|
|
1252
|
+
function handlePointerDown(e) {
|
|
1253
|
+
const target = e.target;
|
|
1254
|
+
if (menuRef.current?.contains(target)) return;
|
|
1255
|
+
if (triggerRef.current?.contains(target)) return;
|
|
1256
|
+
setOpen(false);
|
|
1257
|
+
}
|
|
1258
|
+
document.addEventListener("pointerdown", handlePointerDown, true);
|
|
1259
|
+
return () => document.removeEventListener("pointerdown", handlePointerDown, true);
|
|
1260
|
+
}, [open, setOpen, triggerRef]);
|
|
1261
|
+
useEffect(() => {
|
|
1262
|
+
if (!open) return;
|
|
1263
|
+
const onScroll = () => updatePosition();
|
|
1264
|
+
const onResize = () => updatePosition();
|
|
1265
|
+
window.addEventListener("scroll", onScroll, true);
|
|
1266
|
+
window.addEventListener("resize", onResize);
|
|
1267
|
+
return () => {
|
|
1268
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
1269
|
+
window.removeEventListener("resize", onResize);
|
|
1270
|
+
};
|
|
1271
|
+
}, [open, updatePosition]);
|
|
1272
|
+
if (!open) return null;
|
|
1273
|
+
const panel = /* @__PURE__ */ jsx(
|
|
1274
|
+
"div",
|
|
1275
|
+
{
|
|
1276
|
+
ref: menuRef,
|
|
1277
|
+
id: menuId,
|
|
1278
|
+
role: "menu",
|
|
1279
|
+
"aria-labelledby": triggerId,
|
|
1280
|
+
tabIndex: -1,
|
|
1281
|
+
className: cn(
|
|
1282
|
+
// Base glass panel
|
|
1283
|
+
"fixed z-[9999] min-w-[10rem] py-1",
|
|
1284
|
+
"rounded-xl shadow-xl ring-1 ring-white/10",
|
|
1285
|
+
"bg-surface-50/90 backdrop-blur-md",
|
|
1286
|
+
// Animation
|
|
1287
|
+
"transition-[opacity,transform] duration-150 ease-out",
|
|
1288
|
+
visible ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-1",
|
|
1289
|
+
className
|
|
1290
|
+
),
|
|
1291
|
+
style: pos ? {
|
|
1292
|
+
top: pos.top,
|
|
1293
|
+
...align === "right" ? { right: `calc(100vw - ${pos.left}px)` } : { left: pos.left }
|
|
1294
|
+
} : { top: 0, left: 0, visibility: "hidden" },
|
|
1295
|
+
onKeyDown: (e) => {
|
|
1296
|
+
const menuEl = menuRef.current;
|
|
1297
|
+
if (!menuEl) return;
|
|
1298
|
+
if (e.key === "Escape" || e.key === "Tab") {
|
|
1299
|
+
if (e.key === "Escape") {
|
|
1300
|
+
e.preventDefault();
|
|
1301
|
+
e.stopPropagation();
|
|
1302
|
+
focusSafely(triggerRef.current);
|
|
1303
|
+
}
|
|
1304
|
+
setOpen(false);
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
const items = getFocusableElements(menuEl).filter(
|
|
1308
|
+
(el) => el.getAttribute("role") === "menuitem" && el.getAttribute("aria-disabled") !== "true"
|
|
1309
|
+
);
|
|
1310
|
+
if (items.length === 0) return;
|
|
1311
|
+
const active = document.activeElement;
|
|
1312
|
+
const currentIndex = items.findIndex((el) => el === active);
|
|
1313
|
+
if (e.key === "ArrowDown") {
|
|
1314
|
+
e.preventDefault();
|
|
1315
|
+
const next = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
1316
|
+
focusSafely(items[next]);
|
|
1317
|
+
} else if (e.key === "ArrowUp") {
|
|
1318
|
+
e.preventDefault();
|
|
1319
|
+
const prev = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
1320
|
+
focusSafely(items[prev]);
|
|
1321
|
+
} else if (e.key === "Home") {
|
|
1322
|
+
e.preventDefault();
|
|
1323
|
+
focusSafely(items[0]);
|
|
1324
|
+
} else if (e.key === "End") {
|
|
1325
|
+
e.preventDefault();
|
|
1326
|
+
focusSafely(items[items.length - 1]);
|
|
1327
|
+
}
|
|
1328
|
+
},
|
|
1329
|
+
children
|
|
1330
|
+
}
|
|
1331
|
+
);
|
|
1332
|
+
if (typeof document === "undefined") return null;
|
|
1333
|
+
return createPortal(panel, document.body);
|
|
1334
|
+
}
|
|
1335
|
+
function DropdownItem({ onSelect, disabled = false, children, className }) {
|
|
1336
|
+
const { setOpen, triggerRef } = useDropdownContext("DropdownItem");
|
|
1337
|
+
const handleSelect = useCallback(() => {
|
|
1338
|
+
if (disabled) return;
|
|
1339
|
+
setOpen(false);
|
|
1340
|
+
focusSafely(triggerRef.current);
|
|
1341
|
+
onSelect?.();
|
|
1342
|
+
}, [disabled, onSelect, setOpen, triggerRef]);
|
|
1343
|
+
return /* @__PURE__ */ jsx(
|
|
1344
|
+
"div",
|
|
1345
|
+
{
|
|
1346
|
+
role: "menuitem",
|
|
1347
|
+
tabIndex: disabled ? -1 : 0,
|
|
1348
|
+
"aria-disabled": disabled ? "true" : void 0,
|
|
1349
|
+
className: cn(
|
|
1350
|
+
"flex items-center gap-2 w-full px-3 py-2 text-sm text-left cursor-pointer select-none",
|
|
1351
|
+
"text-white/80 transition-colors duration-100",
|
|
1352
|
+
"focus:outline-none focus-visible:bg-white/10",
|
|
1353
|
+
disabled ? "opacity-40 pointer-events-none cursor-not-allowed" : "hover:bg-white/10 hover:text-white focus-visible:text-white",
|
|
1354
|
+
className
|
|
1355
|
+
),
|
|
1356
|
+
onClick: handleSelect,
|
|
1357
|
+
onKeyDown: (e) => {
|
|
1358
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1359
|
+
e.preventDefault();
|
|
1360
|
+
handleSelect();
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
children
|
|
1364
|
+
}
|
|
1365
|
+
);
|
|
1366
|
+
}
|
|
1367
|
+
function DropdownSeparator({ className }) {
|
|
1368
|
+
return /* @__PURE__ */ jsx("div", { role: "separator", className: cn("border-t border-white/10 my-1", className) });
|
|
1369
|
+
}
|
|
1370
|
+
function matchesAccept(file, accept) {
|
|
1371
|
+
const types = accept.split(",").map((t) => t.trim().toLowerCase());
|
|
1372
|
+
const fileName = file.name.toLowerCase();
|
|
1373
|
+
const mimeType = file.type.toLowerCase();
|
|
1374
|
+
return types.some((t) => {
|
|
1375
|
+
if (t.startsWith(".")) return fileName.endsWith(t);
|
|
1376
|
+
if (t.endsWith("/*")) return mimeType.startsWith(t.slice(0, -1));
|
|
1377
|
+
return mimeType === t;
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
function DropZone({
|
|
1381
|
+
onFilesDropped,
|
|
1382
|
+
accept,
|
|
1383
|
+
maxFiles,
|
|
1384
|
+
maxSize,
|
|
1385
|
+
disabled = false,
|
|
1386
|
+
children,
|
|
1387
|
+
className,
|
|
1388
|
+
"aria-label": ariaLabel = "File drop zone"
|
|
1389
|
+
}) {
|
|
1390
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
1391
|
+
const inputRef = useRef(null);
|
|
1392
|
+
const dragCounterRef = useRef(0);
|
|
1393
|
+
const processFiles = useCallback(
|
|
1394
|
+
(fileList) => {
|
|
1395
|
+
if (!fileList || disabled) return;
|
|
1396
|
+
let files = Array.from(fileList);
|
|
1397
|
+
if (accept) {
|
|
1398
|
+
files = files.filter((f) => matchesAccept(f, accept));
|
|
1399
|
+
}
|
|
1400
|
+
if (maxSize !== void 0) {
|
|
1401
|
+
files = files.filter((f) => f.size <= maxSize);
|
|
1402
|
+
}
|
|
1403
|
+
if (maxFiles !== void 0) {
|
|
1404
|
+
files = files.slice(0, maxFiles);
|
|
1405
|
+
}
|
|
1406
|
+
onFilesDropped(files);
|
|
1407
|
+
},
|
|
1408
|
+
[accept, disabled, maxFiles, maxSize, onFilesDropped]
|
|
1409
|
+
);
|
|
1410
|
+
const handleDragEnter = useCallback(
|
|
1411
|
+
(e) => {
|
|
1412
|
+
e.preventDefault();
|
|
1413
|
+
dragCounterRef.current++;
|
|
1414
|
+
if (!disabled) setIsDragging(true);
|
|
1415
|
+
},
|
|
1416
|
+
[disabled]
|
|
1417
|
+
);
|
|
1418
|
+
const handleDragOver = useCallback(
|
|
1419
|
+
(e) => {
|
|
1420
|
+
e.preventDefault();
|
|
1421
|
+
},
|
|
1422
|
+
[]
|
|
1423
|
+
);
|
|
1424
|
+
const handleDragLeave = useCallback((e) => {
|
|
1425
|
+
e.preventDefault();
|
|
1426
|
+
dragCounterRef.current--;
|
|
1427
|
+
if (dragCounterRef.current === 0) setIsDragging(false);
|
|
1428
|
+
}, []);
|
|
1429
|
+
const handleDrop = useCallback(
|
|
1430
|
+
(e) => {
|
|
1431
|
+
e.preventDefault();
|
|
1432
|
+
dragCounterRef.current = 0;
|
|
1433
|
+
setIsDragging(false);
|
|
1434
|
+
if (disabled) return;
|
|
1435
|
+
processFiles(e.dataTransfer.files);
|
|
1436
|
+
},
|
|
1437
|
+
[disabled, processFiles]
|
|
1438
|
+
);
|
|
1439
|
+
const handleClick = useCallback(() => {
|
|
1440
|
+
if (!disabled) inputRef.current?.click();
|
|
1441
|
+
}, [disabled]);
|
|
1442
|
+
const handleKeyDown = useCallback(
|
|
1443
|
+
(e) => {
|
|
1444
|
+
if (disabled) return;
|
|
1445
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1446
|
+
e.preventDefault();
|
|
1447
|
+
inputRef.current?.click();
|
|
1448
|
+
}
|
|
1449
|
+
},
|
|
1450
|
+
[disabled]
|
|
1451
|
+
);
|
|
1452
|
+
const handleInputChange = useCallback(() => {
|
|
1453
|
+
processFiles(inputRef.current?.files ?? null);
|
|
1454
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
1455
|
+
}, [processFiles]);
|
|
1456
|
+
return /* @__PURE__ */ jsxs(
|
|
1457
|
+
"div",
|
|
1458
|
+
{
|
|
1459
|
+
role: "button",
|
|
1460
|
+
tabIndex: disabled ? -1 : 0,
|
|
1461
|
+
"aria-label": ariaLabel,
|
|
1462
|
+
"aria-disabled": disabled,
|
|
1463
|
+
onClick: handleClick,
|
|
1464
|
+
onKeyDown: handleKeyDown,
|
|
1465
|
+
onDragEnter: handleDragEnter,
|
|
1466
|
+
onDragOver: handleDragOver,
|
|
1467
|
+
onDragLeave: handleDragLeave,
|
|
1468
|
+
onDrop: handleDrop,
|
|
1469
|
+
className: cn(
|
|
1470
|
+
"flex flex-col items-center justify-center gap-3 rounded-2xl border-2 border-dashed px-6 py-10 text-center transition-colors cursor-pointer select-none outline-none",
|
|
1471
|
+
"border-white/20 bg-white/5 backdrop-blur-sm",
|
|
1472
|
+
"hover:border-primary/50 hover:bg-white/[0.08]",
|
|
1473
|
+
"focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent",
|
|
1474
|
+
isDragging && "border-primary bg-primary/10",
|
|
1475
|
+
disabled && "pointer-events-none opacity-50",
|
|
1476
|
+
className
|
|
1477
|
+
),
|
|
1478
|
+
children: [
|
|
1479
|
+
/* @__PURE__ */ jsx(
|
|
1480
|
+
"input",
|
|
1481
|
+
{
|
|
1482
|
+
ref: inputRef,
|
|
1483
|
+
type: "file",
|
|
1484
|
+
tabIndex: -1,
|
|
1485
|
+
className: "sr-only",
|
|
1486
|
+
accept,
|
|
1487
|
+
multiple: maxFiles !== 1,
|
|
1488
|
+
onChange: handleInputChange,
|
|
1489
|
+
onClick: (e) => e.stopPropagation(),
|
|
1490
|
+
"aria-hidden": "true"
|
|
1491
|
+
}
|
|
1492
|
+
),
|
|
1493
|
+
children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1494
|
+
/* @__PURE__ */ jsx(
|
|
1495
|
+
"svg",
|
|
1496
|
+
{
|
|
1497
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1498
|
+
className: "h-10 w-10 text-white/40",
|
|
1499
|
+
fill: "none",
|
|
1500
|
+
viewBox: "0 0 24 24",
|
|
1501
|
+
stroke: "currentColor",
|
|
1502
|
+
strokeWidth: 1.5,
|
|
1503
|
+
"aria-hidden": "true",
|
|
1504
|
+
children: /* @__PURE__ */ jsx(
|
|
1505
|
+
"path",
|
|
1506
|
+
{
|
|
1507
|
+
strokeLinecap: "round",
|
|
1508
|
+
strokeLinejoin: "round",
|
|
1509
|
+
d: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5"
|
|
1510
|
+
}
|
|
1511
|
+
)
|
|
1512
|
+
}
|
|
1513
|
+
),
|
|
1514
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-white/50", children: "Drop files here or click to browse" })
|
|
1515
|
+
] })
|
|
1516
|
+
]
|
|
1517
|
+
}
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
596
1520
|
var maxWidthClass = {
|
|
597
1521
|
sm: "max-w-2xl",
|
|
598
1522
|
md: "max-w-4xl",
|
|
@@ -605,7 +1529,7 @@ function defaultBackground() {
|
|
|
605
1529
|
return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 overflow-hidden", children: [
|
|
606
1530
|
/* @__PURE__ */ jsx("div", { className: "absolute -top-32 -left-32 h-[600px] w-[600px] rounded-full bg-violet-600/20 blur-[140px]" }),
|
|
607
1531
|
/* @__PURE__ */ jsx("div", { className: "absolute top-20 -right-32 h-[500px] w-[500px] rounded-full bg-purple-500/[0.12] blur-[120px]" }),
|
|
608
|
-
/* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-1/2 h-[400px] w-[500px] -translate-x-1/2 rounded-full bg-
|
|
1532
|
+
/* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-1/2 h-[400px] w-[500px] -translate-x-1/2 rounded-full bg-accent/[0.10] blur-[120px]" })
|
|
609
1533
|
] });
|
|
610
1534
|
}
|
|
611
1535
|
function PageShell({
|
|
@@ -639,11 +1563,8 @@ function PageShell({
|
|
|
639
1563
|
}
|
|
640
1564
|
function Navbar({ logo, children, className, glass = true }) {
|
|
641
1565
|
return /* @__PURE__ */ jsx("header", { className: cn("fixed top-0 w-full z-50", glass && "glass", className), children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto px-6 h-16 flex items-center justify-between", children: [
|
|
642
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: logo
|
|
643
|
-
|
|
644
|
-
/* @__PURE__ */ jsx("span", { className: "text-lg font-bold tracking-tight", children: "MemeLab" })
|
|
645
|
-
] }) }),
|
|
646
|
-
children && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children })
|
|
1566
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-3", children: logo ?? /* @__PURE__ */ jsx("div", { className: "w-9 h-9 rounded-xl animated-gradient" }) }),
|
|
1567
|
+
children && /* @__PURE__ */ jsx("nav", { "aria-label": "Main navigation", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-4", children }) })
|
|
647
1568
|
] }) });
|
|
648
1569
|
}
|
|
649
1570
|
function Sidebar({ children, collapsed = false, onToggle, className }) {
|
|
@@ -681,7 +1602,15 @@ function Sidebar({ children, collapsed = false, onToggle, className }) {
|
|
|
681
1602
|
}
|
|
682
1603
|
);
|
|
683
1604
|
}
|
|
684
|
-
|
|
1605
|
+
var maxWidthClass2 = {
|
|
1606
|
+
sm: "max-w-2xl",
|
|
1607
|
+
md: "max-w-4xl",
|
|
1608
|
+
lg: "max-w-5xl",
|
|
1609
|
+
xl: "max-w-7xl",
|
|
1610
|
+
"2xl": "max-w-[92rem]",
|
|
1611
|
+
full: "max-w-full"
|
|
1612
|
+
};
|
|
1613
|
+
function DashboardLayout({ children, navbar, sidebar, className, mainClassName, maxWidth = "lg" }) {
|
|
685
1614
|
return /* @__PURE__ */ jsxs("div", { className: cn("min-h-screen bg-surface relative overflow-hidden", className), children: [
|
|
686
1615
|
/* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "pointer-events-none fixed inset-0", children: [
|
|
687
1616
|
/* @__PURE__ */ jsx("div", { className: "orb orb-purple w-[400px] h-[400px] -top-[150px] -left-[150px] opacity-15" }),
|
|
@@ -690,9 +1619,238 @@ function DashboardLayout({ children, navbar, sidebar, className }) {
|
|
|
690
1619
|
navbar,
|
|
691
1620
|
/* @__PURE__ */ jsxs("div", { className: cn("flex", navbar && "pt-16"), children: [
|
|
692
1621
|
sidebar,
|
|
693
|
-
/* @__PURE__ */ jsx("main", { className: "flex-1 min-w-0 py-8 px-6", children: /* @__PURE__ */ jsx("div", { className: "
|
|
1622
|
+
/* @__PURE__ */ jsx("main", { className: cn("flex-1 min-w-0 py-8 px-6", mainClassName), children: /* @__PURE__ */ jsx("div", { className: cn("mx-auto", maxWidthClass2[maxWidth]), children }) })
|
|
694
1623
|
] })
|
|
695
1624
|
] });
|
|
696
1625
|
}
|
|
1626
|
+
var ProgressButton = forwardRef(
|
|
1627
|
+
function ProgressButton2({ isLoading, loadingText, children, disabled, className, ...props }, ref) {
|
|
1628
|
+
return /* @__PURE__ */ jsx(
|
|
1629
|
+
Button,
|
|
1630
|
+
{
|
|
1631
|
+
ref,
|
|
1632
|
+
...props,
|
|
1633
|
+
disabled: disabled || isLoading,
|
|
1634
|
+
"aria-busy": isLoading || void 0,
|
|
1635
|
+
className: cn("relative overflow-hidden", className),
|
|
1636
|
+
children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1637
|
+
/* @__PURE__ */ jsx(
|
|
1638
|
+
"span",
|
|
1639
|
+
{
|
|
1640
|
+
"aria-hidden": "true",
|
|
1641
|
+
className: "absolute inset-0 bg-white/20 animate-[ml-shimmer_2s_ease-in-out_infinite] skew-x-[-20deg] pointer-events-none"
|
|
1642
|
+
}
|
|
1643
|
+
),
|
|
1644
|
+
/* @__PURE__ */ jsx(Spinner, { size: "sm" }),
|
|
1645
|
+
loadingText ?? children
|
|
1646
|
+
] }) : children
|
|
1647
|
+
}
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
);
|
|
1651
|
+
function reducer(state, action) {
|
|
1652
|
+
switch (action.type) {
|
|
1653
|
+
case "ADD": {
|
|
1654
|
+
const next = [...state, action.toast];
|
|
1655
|
+
return next.length > action.maxToasts ? next.slice(-action.maxToasts) : next;
|
|
1656
|
+
}
|
|
1657
|
+
case "REMOVE":
|
|
1658
|
+
return state.filter((t) => t.id !== action.id);
|
|
1659
|
+
case "REMOVE_ALL":
|
|
1660
|
+
return [];
|
|
1661
|
+
default:
|
|
1662
|
+
return state;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
var ToastContext = createContext(null);
|
|
1666
|
+
var variantBarColor = {
|
|
1667
|
+
success: "bg-emerald-500",
|
|
1668
|
+
error: "bg-rose-500",
|
|
1669
|
+
warning: "bg-amber-500",
|
|
1670
|
+
info: "bg-primary"
|
|
1671
|
+
};
|
|
1672
|
+
var variantProgressColor = {
|
|
1673
|
+
success: "bg-emerald-500",
|
|
1674
|
+
error: "bg-rose-500",
|
|
1675
|
+
warning: "bg-amber-500",
|
|
1676
|
+
info: "bg-primary"
|
|
1677
|
+
};
|
|
1678
|
+
var variantIconLabel = {
|
|
1679
|
+
success: "\u2713",
|
|
1680
|
+
error: "\u2715",
|
|
1681
|
+
warning: "\u26A0",
|
|
1682
|
+
info: "i"
|
|
1683
|
+
};
|
|
1684
|
+
var variantIconColor = {
|
|
1685
|
+
success: "text-emerald-400",
|
|
1686
|
+
error: "text-rose-400",
|
|
1687
|
+
warning: "text-amber-400",
|
|
1688
|
+
info: "text-primary"
|
|
1689
|
+
};
|
|
1690
|
+
var positionClass = {
|
|
1691
|
+
"top-right": "top-4 right-4 items-end",
|
|
1692
|
+
"top-center": "top-4 left-1/2 -translate-x-1/2 items-center",
|
|
1693
|
+
"bottom-right": "bottom-4 right-4 items-end",
|
|
1694
|
+
"bottom-center": "bottom-4 left-1/2 -translate-x-1/2 items-center"
|
|
1695
|
+
};
|
|
1696
|
+
function ToastCard({ toast, onDismiss }) {
|
|
1697
|
+
const duration = toast.duration ?? 5e3;
|
|
1698
|
+
const [visible, setVisible] = useState(false);
|
|
1699
|
+
const [exiting, setExiting] = useState(false);
|
|
1700
|
+
const [started, setStarted] = useState(false);
|
|
1701
|
+
const dismissedRef = useRef(false);
|
|
1702
|
+
const timerRef = useRef(null);
|
|
1703
|
+
const exitTimerRef = useRef(null);
|
|
1704
|
+
useEffect(() => {
|
|
1705
|
+
let innerFrame;
|
|
1706
|
+
const frame = requestAnimationFrame(() => {
|
|
1707
|
+
setVisible(true);
|
|
1708
|
+
innerFrame = requestAnimationFrame(() => setStarted(true));
|
|
1709
|
+
});
|
|
1710
|
+
return () => {
|
|
1711
|
+
cancelAnimationFrame(frame);
|
|
1712
|
+
cancelAnimationFrame(innerFrame);
|
|
1713
|
+
};
|
|
1714
|
+
}, []);
|
|
1715
|
+
useEffect(() => {
|
|
1716
|
+
return () => {
|
|
1717
|
+
if (exitTimerRef.current !== null) clearTimeout(exitTimerRef.current);
|
|
1718
|
+
};
|
|
1719
|
+
}, []);
|
|
1720
|
+
const triggerDismiss = useCallback(() => {
|
|
1721
|
+
if (dismissedRef.current) return;
|
|
1722
|
+
dismissedRef.current = true;
|
|
1723
|
+
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
|
1724
|
+
setExiting(true);
|
|
1725
|
+
exitTimerRef.current = setTimeout(() => onDismiss(toast.id), 280);
|
|
1726
|
+
}, [onDismiss, toast.id]);
|
|
1727
|
+
useEffect(() => {
|
|
1728
|
+
if (duration <= 0) return;
|
|
1729
|
+
timerRef.current = setTimeout(triggerDismiss, duration);
|
|
1730
|
+
return () => {
|
|
1731
|
+
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
|
1732
|
+
};
|
|
1733
|
+
}, [duration, triggerDismiss]);
|
|
1734
|
+
return /* @__PURE__ */ jsxs(
|
|
1735
|
+
"div",
|
|
1736
|
+
{
|
|
1737
|
+
role: "alert",
|
|
1738
|
+
"aria-live": "assertive",
|
|
1739
|
+
"aria-atomic": "true",
|
|
1740
|
+
"data-variant": toast.variant,
|
|
1741
|
+
className: cn(
|
|
1742
|
+
// Base layout
|
|
1743
|
+
"relative flex w-80 max-w-[calc(100vw-2rem)] overflow-hidden rounded-xl shadow-xl",
|
|
1744
|
+
// Glass background
|
|
1745
|
+
"bg-surface-100/95 backdrop-blur-md ring-1 ring-white/10",
|
|
1746
|
+
// Enter / exit transitions
|
|
1747
|
+
"transition-all duration-300",
|
|
1748
|
+
visible && !exiting ? "opacity-100 translate-x-0" : "opacity-0 translate-x-8",
|
|
1749
|
+
exiting && "scale-95"
|
|
1750
|
+
),
|
|
1751
|
+
children: [
|
|
1752
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("w-[3px] shrink-0 self-stretch", variantBarColor[toast.variant]) }),
|
|
1753
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-start gap-3 px-4 py-3 pr-9", children: [
|
|
1754
|
+
/* @__PURE__ */ jsx(
|
|
1755
|
+
"span",
|
|
1756
|
+
{
|
|
1757
|
+
"aria-hidden": "true",
|
|
1758
|
+
className: cn(
|
|
1759
|
+
"mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[11px] font-bold ring-1",
|
|
1760
|
+
variantIconColor[toast.variant],
|
|
1761
|
+
"ring-current/30"
|
|
1762
|
+
),
|
|
1763
|
+
children: variantIconLabel[toast.variant]
|
|
1764
|
+
}
|
|
1765
|
+
),
|
|
1766
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1767
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-semibold leading-snug text-white", children: toast.title }),
|
|
1768
|
+
toast.description ? /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-xs leading-relaxed text-white/60", children: toast.description }) : null
|
|
1769
|
+
] })
|
|
1770
|
+
] }),
|
|
1771
|
+
/* @__PURE__ */ jsx(
|
|
1772
|
+
"button",
|
|
1773
|
+
{
|
|
1774
|
+
type: "button",
|
|
1775
|
+
"aria-label": "Dismiss notification",
|
|
1776
|
+
onClick: triggerDismiss,
|
|
1777
|
+
className: cn(
|
|
1778
|
+
"absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-lg",
|
|
1779
|
+
"text-white/40 transition-colors hover:bg-white/10 hover:text-white/80",
|
|
1780
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40"
|
|
1781
|
+
),
|
|
1782
|
+
children: /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M1 1l8 8M9 1L1 9", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) })
|
|
1783
|
+
}
|
|
1784
|
+
),
|
|
1785
|
+
duration > 0 && /* @__PURE__ */ jsx(
|
|
1786
|
+
"span",
|
|
1787
|
+
{
|
|
1788
|
+
"aria-hidden": "true",
|
|
1789
|
+
className: cn(
|
|
1790
|
+
"absolute bottom-0 left-0 h-[2px] ease-linear",
|
|
1791
|
+
variantProgressColor[toast.variant],
|
|
1792
|
+
"opacity-60"
|
|
1793
|
+
),
|
|
1794
|
+
style: {
|
|
1795
|
+
width: started ? "0%" : "100%",
|
|
1796
|
+
transitionProperty: "width",
|
|
1797
|
+
transitionDuration: started ? `${duration}ms` : "0ms"
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
)
|
|
1801
|
+
]
|
|
1802
|
+
}
|
|
1803
|
+
);
|
|
1804
|
+
}
|
|
1805
|
+
function ToastProvider({ children, position = "top-right", maxToasts = 5 }) {
|
|
1806
|
+
const [toasts, dispatch] = useReducer(reducer, []);
|
|
1807
|
+
const counterRef = useRef(0);
|
|
1808
|
+
const maxToastsRef = useRef(maxToasts);
|
|
1809
|
+
maxToastsRef.current = maxToasts;
|
|
1810
|
+
const toast = useCallback(
|
|
1811
|
+
(options) => {
|
|
1812
|
+
const id = `toast-${++counterRef.current}`;
|
|
1813
|
+
const newToast = {
|
|
1814
|
+
id,
|
|
1815
|
+
variant: options.variant,
|
|
1816
|
+
title: options.title,
|
|
1817
|
+
description: options.description,
|
|
1818
|
+
duration: options.duration ?? 5e3
|
|
1819
|
+
};
|
|
1820
|
+
dispatch({ type: "ADD", toast: newToast, maxToasts: maxToastsRef.current });
|
|
1821
|
+
return id;
|
|
1822
|
+
},
|
|
1823
|
+
[]
|
|
1824
|
+
);
|
|
1825
|
+
const dismiss = useCallback((id) => {
|
|
1826
|
+
dispatch({ type: "REMOVE", id });
|
|
1827
|
+
}, []);
|
|
1828
|
+
const dismissAll = useCallback(() => {
|
|
1829
|
+
dispatch({ type: "REMOVE_ALL" });
|
|
1830
|
+
}, []);
|
|
1831
|
+
const value = useMemo(() => ({ toast, dismiss, dismissAll }), [toast, dismiss, dismissAll]);
|
|
1832
|
+
const container = typeof document !== "undefined" ? createPortal(
|
|
1833
|
+
/* @__PURE__ */ jsx(
|
|
1834
|
+
"div",
|
|
1835
|
+
{
|
|
1836
|
+
"aria-label": "Notifications",
|
|
1837
|
+
className: cn("fixed z-[9999] flex flex-col gap-3 pointer-events-none", positionClass[position]),
|
|
1838
|
+
children: toasts.map((t) => /* @__PURE__ */ jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx(ToastCard, { toast: t, onDismiss: dismiss }) }, t.id))
|
|
1839
|
+
}
|
|
1840
|
+
),
|
|
1841
|
+
document.body
|
|
1842
|
+
) : null;
|
|
1843
|
+
return /* @__PURE__ */ jsxs(ToastContext.Provider, { value, children: [
|
|
1844
|
+
children,
|
|
1845
|
+
container
|
|
1846
|
+
] });
|
|
1847
|
+
}
|
|
1848
|
+
function useToast() {
|
|
1849
|
+
const ctx = useContext(ToastContext);
|
|
1850
|
+
if (!ctx) {
|
|
1851
|
+
throw new Error("useToast must be used inside <ToastProvider>");
|
|
1852
|
+
}
|
|
1853
|
+
return ctx;
|
|
1854
|
+
}
|
|
697
1855
|
|
|
698
|
-
export { Badge, Button, Card, CollapsibleSection, ConfirmDialog, DashboardLayout, EmptyState, IconButton, Input, Modal, Navbar, PageShell, Pill, Select, Sidebar, Skeleton, Spinner, Textarea, Toggle, Tooltip, cn, focusSafely, getFocusableElements };
|
|
1856
|
+
export { Badge, Button, Card, Checkbox, CollapsibleSection, ConfirmDialog, DashboardLayout, DropZone, Dropdown, DropdownItem, DropdownMenu, DropdownSeparator, DropdownTrigger, EmptyState, IconButton, Input, Modal, Navbar, PageShell, Pill, ProgressButton, RadioGroup, RadioItem, SearchInput, Select, Sidebar, Skeleton, Spinner, Tab, TabList, TabPanel, Tabs, Textarea, ToastProvider, Toggle, Tooltip, cn, colors, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useMediaQuery, useToast };
|