@memelabui/ui 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -0
- package/dist/index.cjs +722 -0
- package/dist/index.d.cts +232 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.js +698 -0
- package/dist/preset/index.cjs +139 -0
- package/dist/preset/index.d.cts +5 -0
- package/dist/preset/index.d.ts +5 -0
- package/dist/preset/index.js +133 -0
- package/dist/styles/index.css +1766 -0
- package/dist/tokens/index.cjs +23 -0
- package/dist/tokens/index.d.cts +32 -0
- package/dist/tokens/index.d.ts +32 -0
- package/dist/tokens/index.js +21 -0
- package/package.json +120 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import { forwardRef, useId, useRef, useEffect, useState, useMemo, cloneElement } from 'react';
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
|
|
5
|
+
// src/utils/cn.ts
|
|
6
|
+
function toClassName(out, value) {
|
|
7
|
+
if (!value) return;
|
|
8
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
9
|
+
out.push(String(value));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
for (const v of value) toClassName(out, v);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === "object") {
|
|
17
|
+
for (const [k, v] of Object.entries(value)) {
|
|
18
|
+
if (v) out.push(k);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function cn(...values) {
|
|
23
|
+
const out = [];
|
|
24
|
+
for (const v of values) toClassName(out, v);
|
|
25
|
+
return out.join(" ");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/utils/focus.ts
|
|
29
|
+
var FOCUSABLE_SELECTOR = [
|
|
30
|
+
"a[href]",
|
|
31
|
+
"area[href]",
|
|
32
|
+
"button:not([disabled])",
|
|
33
|
+
'input:not([disabled]):not([type="hidden"])',
|
|
34
|
+
"select:not([disabled])",
|
|
35
|
+
"textarea:not([disabled])",
|
|
36
|
+
"iframe",
|
|
37
|
+
"object",
|
|
38
|
+
"embed",
|
|
39
|
+
'[contenteditable="true"]',
|
|
40
|
+
'[tabindex]:not([tabindex="-1"])'
|
|
41
|
+
].join(",");
|
|
42
|
+
function isVisible(el) {
|
|
43
|
+
if (el.getClientRects().length === 0) return false;
|
|
44
|
+
const style = window.getComputedStyle(el);
|
|
45
|
+
if (style.visibility === "hidden" || style.display === "none") return false;
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
function getFocusableElements(container) {
|
|
49
|
+
const nodes = Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR));
|
|
50
|
+
return nodes.filter((el) => isVisible(el) && !el.hasAttribute("disabled") && el.getAttribute("aria-hidden") !== "true");
|
|
51
|
+
}
|
|
52
|
+
function focusSafely(el) {
|
|
53
|
+
if (!el) return;
|
|
54
|
+
try {
|
|
55
|
+
el.focus({ preventScroll: true });
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
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
|
+
var sizeClass = {
|
|
61
|
+
sm: "px-3 py-2 text-sm",
|
|
62
|
+
md: "px-3.5 py-2.5 text-sm",
|
|
63
|
+
lg: "px-4 py-3 text-base"
|
|
64
|
+
};
|
|
65
|
+
var variantClass = {
|
|
66
|
+
primary: "bg-gradient-to-r from-violet-600 to-purple-600 text-white shadow-[0_0_25px_rgba(139,92,246,0.35)] hover:shadow-[0_0_35px_rgba(139,92,246,0.5)] hover:scale-[1.02]",
|
|
67
|
+
success: "bg-emerald-600 text-white shadow-[0_10px_18px_rgba(16,185,129,0.22)] hover:bg-emerald-700",
|
|
68
|
+
warning: "bg-amber-600 text-white shadow-[0_10px_18px_rgba(245,158,11,0.22)] hover:bg-amber-700",
|
|
69
|
+
danger: "bg-rose-600 text-white shadow-[0_10px_18px_rgba(244,63,94,0.22)] hover:bg-rose-700",
|
|
70
|
+
secondary: "text-white bg-white/5 ring-1 ring-white/10 hover:bg-white/10 hover:ring-white/20 backdrop-blur-sm",
|
|
71
|
+
ghost: "text-white/70 hover:text-white hover:bg-white/5"
|
|
72
|
+
};
|
|
73
|
+
var Button = forwardRef(function Button2({ variant = "secondary", size = "md", leftIcon, rightIcon, loading, className, disabled, children, type, ...props }, ref) {
|
|
74
|
+
return /* @__PURE__ */ jsx(
|
|
75
|
+
"button",
|
|
76
|
+
{
|
|
77
|
+
ref,
|
|
78
|
+
type: type === "submit" ? "submit" : type === "reset" ? "reset" : "button",
|
|
79
|
+
...props,
|
|
80
|
+
disabled: disabled || loading,
|
|
81
|
+
className: cn(base, sizeClass[size], variantClass[variant], className),
|
|
82
|
+
children: loading ? /* @__PURE__ */ jsxs("span", { className: "flex items-center justify-center gap-2", children: [
|
|
83
|
+
/* @__PURE__ */ jsx("span", { className: "w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" }),
|
|
84
|
+
children
|
|
85
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
86
|
+
leftIcon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: leftIcon }) : null,
|
|
87
|
+
children,
|
|
88
|
+
rightIcon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: rightIcon }) : null
|
|
89
|
+
] })
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
var base2 = "inline-flex items-center justify-center 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";
|
|
94
|
+
var sizeClass2 = {
|
|
95
|
+
sm: "p-1.5 w-7 h-7",
|
|
96
|
+
md: "p-2 w-9 h-9",
|
|
97
|
+
lg: "p-2.5 w-11 h-11"
|
|
98
|
+
};
|
|
99
|
+
var variantClass2 = {
|
|
100
|
+
primary: "bg-primary text-white shadow-glow hover:brightness-[0.98]",
|
|
101
|
+
success: "bg-emerald-600 text-white shadow-[0_10px_18px_rgba(16,185,129,0.22)] hover:bg-emerald-700",
|
|
102
|
+
warning: "bg-amber-600 text-white shadow-[0_10px_18px_rgba(245,158,11,0.22)] hover:bg-amber-700",
|
|
103
|
+
danger: "bg-rose-600 text-white shadow-[0_10px_18px_rgba(244,63,94,0.22)] hover:bg-rose-700",
|
|
104
|
+
secondary: "text-white bg-white/10 shadow-sm ring-1 ring-white/10",
|
|
105
|
+
ghost: "text-white hover:bg-white/10"
|
|
106
|
+
};
|
|
107
|
+
var IconButton = forwardRef(function IconButton2({ icon, variant = "ghost", size = "md", className, disabled, type, ...props }, ref) {
|
|
108
|
+
return /* @__PURE__ */ jsx(
|
|
109
|
+
"button",
|
|
110
|
+
{
|
|
111
|
+
ref,
|
|
112
|
+
type: type === "submit" ? "submit" : type === "reset" ? "reset" : "button",
|
|
113
|
+
...props,
|
|
114
|
+
disabled,
|
|
115
|
+
className: cn(base2, sizeClass2[size], variantClass2[variant], className),
|
|
116
|
+
children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: icon })
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
});
|
|
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";
|
|
121
|
+
var Input = forwardRef(function Input2({ hasError, label, error, helperText, className, id: externalId, ...props }, ref) {
|
|
122
|
+
const generatedId = useId();
|
|
123
|
+
const inputId = externalId || generatedId;
|
|
124
|
+
const showError = hasError || !!error;
|
|
125
|
+
const input = /* @__PURE__ */ jsx(
|
|
126
|
+
"input",
|
|
127
|
+
{
|
|
128
|
+
ref,
|
|
129
|
+
id: inputId,
|
|
130
|
+
...props,
|
|
131
|
+
className: cn(inputBase, showError && "ring-2 ring-rose-500/40 focus:ring-rose-500/40", className)
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
if (!label && !error && !helperText) return input;
|
|
135
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
136
|
+
label && /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm text-white/50 mb-1.5", children: label }),
|
|
137
|
+
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 })
|
|
140
|
+
] });
|
|
141
|
+
});
|
|
142
|
+
var selectBase = "w-full rounded-xl px-3 py-2.5 text-sm bg-white/10 text-white shadow-sm outline-none focus:ring-2 focus:ring-primary/40 transition-shadow";
|
|
143
|
+
var Select = forwardRef(function Select2({ hasError, label, error, className, id: externalId, children, ...props }, ref) {
|
|
144
|
+
const generatedId = useId();
|
|
145
|
+
const selectId = externalId || generatedId;
|
|
146
|
+
const showError = hasError || !!error;
|
|
147
|
+
const select = /* @__PURE__ */ jsx(
|
|
148
|
+
"select",
|
|
149
|
+
{
|
|
150
|
+
ref,
|
|
151
|
+
id: selectId,
|
|
152
|
+
...props,
|
|
153
|
+
className: cn(selectBase, showError && "ring-2 ring-rose-500/40 focus:ring-rose-500/40", className),
|
|
154
|
+
children
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
if (!label && !error) return select;
|
|
158
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
159
|
+
label && /* @__PURE__ */ jsx("label", { htmlFor: selectId, className: "block text-sm text-white/50 mb-1.5", children: label }),
|
|
160
|
+
select,
|
|
161
|
+
error && /* @__PURE__ */ jsx("p", { className: "text-rose-400 text-xs mt-1", children: error })
|
|
162
|
+
] });
|
|
163
|
+
});
|
|
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) {
|
|
166
|
+
const generatedId = useId();
|
|
167
|
+
const textareaId = externalId || generatedId;
|
|
168
|
+
const showError = hasError || !!error;
|
|
169
|
+
const textarea = /* @__PURE__ */ jsx(
|
|
170
|
+
"textarea",
|
|
171
|
+
{
|
|
172
|
+
ref,
|
|
173
|
+
id: textareaId,
|
|
174
|
+
...props,
|
|
175
|
+
className: cn(textareaBase, showError && "ring-2 ring-rose-500/40 focus:ring-rose-500/40", className)
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
if (!label && !error) return textarea;
|
|
179
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
180
|
+
label && /* @__PURE__ */ jsx("label", { htmlFor: textareaId, className: "block text-sm text-white/50 mb-1.5", children: label }),
|
|
181
|
+
textarea,
|
|
182
|
+
error && /* @__PURE__ */ jsx("p", { className: "text-rose-400 text-xs mt-1", children: error })
|
|
183
|
+
] });
|
|
184
|
+
});
|
|
185
|
+
var base3 = "inline-flex items-center justify-center rounded-full font-semibold ring-1 ring-white/10";
|
|
186
|
+
var sizeClass3 = {
|
|
187
|
+
sm: "text-xs px-2.5 py-1",
|
|
188
|
+
md: "text-sm px-3 py-1.5"
|
|
189
|
+
};
|
|
190
|
+
var variantClass3 = {
|
|
191
|
+
neutral: "bg-white/10 text-white/90",
|
|
192
|
+
primary: "bg-primary/15 text-primary ring-primary/20",
|
|
193
|
+
success: "bg-emerald-500/15 text-emerald-400 ring-emerald-500/20",
|
|
194
|
+
successSolid: "bg-emerald-600 text-white ring-0",
|
|
195
|
+
warning: "bg-amber-500/15 text-amber-400 ring-amber-500/20",
|
|
196
|
+
danger: "bg-rose-500/15 text-rose-400 ring-rose-500/20",
|
|
197
|
+
dangerSolid: "bg-rose-600 text-white ring-0",
|
|
198
|
+
accent: "bg-accent/15 text-accent-light ring-accent/20"
|
|
199
|
+
};
|
|
200
|
+
var Badge = forwardRef(function Badge2({ children, variant = "neutral", size = "sm", className, ...props }, ref) {
|
|
201
|
+
return /* @__PURE__ */ jsx("span", { ref, ...props, className: cn(base3, sizeClass3[size], variantClass3[variant], className), children });
|
|
202
|
+
});
|
|
203
|
+
var Pill = Badge;
|
|
204
|
+
var trackSize = {
|
|
205
|
+
sm: "w-9 h-5",
|
|
206
|
+
md: "w-11 h-6"
|
|
207
|
+
};
|
|
208
|
+
var thumbSize = {
|
|
209
|
+
sm: { base: "w-4 h-4", translate: "translate-x-4" },
|
|
210
|
+
md: { base: "w-5 h-5", translate: "translate-x-5" }
|
|
211
|
+
};
|
|
212
|
+
function Toggle({ checked, onChange, disabled, label, size = "md", "aria-label": ariaLabel }) {
|
|
213
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
214
|
+
/* @__PURE__ */ jsx(
|
|
215
|
+
"button",
|
|
216
|
+
{
|
|
217
|
+
type: "button",
|
|
218
|
+
role: "switch",
|
|
219
|
+
"aria-checked": checked,
|
|
220
|
+
"aria-label": ariaLabel || label,
|
|
221
|
+
disabled,
|
|
222
|
+
onClick: () => onChange(!checked),
|
|
223
|
+
className: cn(
|
|
224
|
+
"relative rounded-full transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-surface",
|
|
225
|
+
trackSize[size],
|
|
226
|
+
checked ? "bg-primary" : "bg-surface-300",
|
|
227
|
+
disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer"
|
|
228
|
+
),
|
|
229
|
+
children: /* @__PURE__ */ jsx(
|
|
230
|
+
"span",
|
|
231
|
+
{
|
|
232
|
+
className: cn(
|
|
233
|
+
"absolute top-0.5 left-0.5 rounded-full bg-white transition-transform duration-200",
|
|
234
|
+
thumbSize[size].base,
|
|
235
|
+
checked ? thumbSize[size].translate : "translate-x-0"
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
),
|
|
241
|
+
label && /* @__PURE__ */ jsx("span", { className: "text-sm text-white/60", children: label })
|
|
242
|
+
] });
|
|
243
|
+
}
|
|
244
|
+
var sizeClass4 = {
|
|
245
|
+
sm: "h-3 w-3 border",
|
|
246
|
+
md: "h-4 w-4 border-2",
|
|
247
|
+
lg: "h-6 w-6 border-2"
|
|
248
|
+
};
|
|
249
|
+
function Spinner({ className, size = "md" }) {
|
|
250
|
+
return /* @__PURE__ */ jsx(
|
|
251
|
+
"span",
|
|
252
|
+
{
|
|
253
|
+
className: cn("inline-block rounded-full border-white/15 border-t-primary animate-spin", sizeClass4[size], className),
|
|
254
|
+
"aria-hidden": "true"
|
|
255
|
+
}
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
function Skeleton({ className, circle }) {
|
|
259
|
+
return /* @__PURE__ */ jsx(
|
|
260
|
+
"div",
|
|
261
|
+
{
|
|
262
|
+
"aria-hidden": "true",
|
|
263
|
+
className: cn("bg-white/10 animate-pulse", circle ? "rounded-full" : "rounded-lg", className)
|
|
264
|
+
}
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
function Card({ hoverable, variant = "surface", className, ...props }) {
|
|
268
|
+
return /* @__PURE__ */ jsx(
|
|
269
|
+
"div",
|
|
270
|
+
{
|
|
271
|
+
...props,
|
|
272
|
+
className: cn(variant === "glass" ? "glass" : "surface", hoverable && "surface-hover", className)
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
function Modal({
|
|
277
|
+
isOpen,
|
|
278
|
+
onClose,
|
|
279
|
+
children,
|
|
280
|
+
ariaLabel,
|
|
281
|
+
ariaLabelledBy,
|
|
282
|
+
closeOnBackdrop = true,
|
|
283
|
+
closeOnEsc = true,
|
|
284
|
+
useGlass = true,
|
|
285
|
+
overlayClassName,
|
|
286
|
+
contentClassName,
|
|
287
|
+
zIndexClassName = "z-50"
|
|
288
|
+
}) {
|
|
289
|
+
const dialogRef = useRef(null);
|
|
290
|
+
const lastActiveElementRef = useRef(null);
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
if (!isOpen) return;
|
|
293
|
+
lastActiveElementRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
294
|
+
const raf = window.requestAnimationFrame(() => {
|
|
295
|
+
const dialogEl = dialogRef.current;
|
|
296
|
+
if (!dialogEl) return;
|
|
297
|
+
const focusables = getFocusableElements(dialogEl);
|
|
298
|
+
focusSafely(focusables[0] ?? dialogEl);
|
|
299
|
+
});
|
|
300
|
+
return () => {
|
|
301
|
+
window.cancelAnimationFrame(raf);
|
|
302
|
+
const lastActive = lastActiveElementRef.current;
|
|
303
|
+
lastActiveElementRef.current = null;
|
|
304
|
+
if (lastActive?.isConnected) focusSafely(lastActive);
|
|
305
|
+
};
|
|
306
|
+
}, [isOpen]);
|
|
307
|
+
if (!isOpen) return null;
|
|
308
|
+
return /* @__PURE__ */ jsx(
|
|
309
|
+
"div",
|
|
310
|
+
{
|
|
311
|
+
className: cn(
|
|
312
|
+
"fixed inset-0 flex items-end sm:items-center justify-center p-4 pb-safe bg-black/25 backdrop-blur-sm animate-modal-backdrop",
|
|
313
|
+
zIndexClassName,
|
|
314
|
+
overlayClassName
|
|
315
|
+
),
|
|
316
|
+
role: "presentation",
|
|
317
|
+
onMouseDown: (e) => {
|
|
318
|
+
if (!closeOnBackdrop) return;
|
|
319
|
+
if (e.target === e.currentTarget) onClose();
|
|
320
|
+
},
|
|
321
|
+
children: /* @__PURE__ */ jsx(
|
|
322
|
+
"div",
|
|
323
|
+
{
|
|
324
|
+
role: "dialog",
|
|
325
|
+
"aria-modal": "true",
|
|
326
|
+
"aria-label": ariaLabelledBy ? void 0 : ariaLabel,
|
|
327
|
+
"aria-labelledby": ariaLabelledBy,
|
|
328
|
+
tabIndex: -1,
|
|
329
|
+
ref: dialogRef,
|
|
330
|
+
className: cn(
|
|
331
|
+
"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-[#0f0f18]/80",
|
|
333
|
+
contentClassName
|
|
334
|
+
),
|
|
335
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
336
|
+
onKeyDownCapture: (e) => {
|
|
337
|
+
if (closeOnEsc && e.key === "Escape") {
|
|
338
|
+
e.preventDefault();
|
|
339
|
+
e.stopPropagation();
|
|
340
|
+
onClose();
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (e.key !== "Tab") return;
|
|
344
|
+
const dialogEl = dialogRef.current;
|
|
345
|
+
if (!dialogEl) return;
|
|
346
|
+
const focusables = getFocusableElements(dialogEl);
|
|
347
|
+
if (focusables.length === 0) {
|
|
348
|
+
e.preventDefault();
|
|
349
|
+
focusSafely(dialogEl);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const active = document.activeElement;
|
|
353
|
+
const first = focusables[0];
|
|
354
|
+
const last = focusables[focusables.length - 1];
|
|
355
|
+
if (!(active instanceof HTMLElement) || !dialogEl.contains(active)) {
|
|
356
|
+
e.preventDefault();
|
|
357
|
+
focusSafely(first);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (e.shiftKey) {
|
|
361
|
+
if (active === first) {
|
|
362
|
+
e.preventDefault();
|
|
363
|
+
focusSafely(last);
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
if (active === last) {
|
|
367
|
+
e.preventDefault();
|
|
368
|
+
focusSafely(first);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
children
|
|
373
|
+
}
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
var confirmVariantClass = {
|
|
379
|
+
danger: "bg-rose-600 hover:bg-rose-700",
|
|
380
|
+
warning: "bg-amber-600 hover:bg-amber-700",
|
|
381
|
+
primary: ""
|
|
382
|
+
};
|
|
383
|
+
function ConfirmDialog({
|
|
384
|
+
isOpen,
|
|
385
|
+
onClose,
|
|
386
|
+
onConfirm,
|
|
387
|
+
title,
|
|
388
|
+
message,
|
|
389
|
+
confirmText = "Confirm",
|
|
390
|
+
cancelText = "Cancel",
|
|
391
|
+
loadingText = "Processing...",
|
|
392
|
+
variant = "danger",
|
|
393
|
+
isLoading = false
|
|
394
|
+
}) {
|
|
395
|
+
const titleId = useId();
|
|
396
|
+
return /* @__PURE__ */ jsxs(
|
|
397
|
+
Modal,
|
|
398
|
+
{
|
|
399
|
+
isOpen,
|
|
400
|
+
onClose,
|
|
401
|
+
ariaLabelledBy: titleId,
|
|
402
|
+
closeOnEsc: !isLoading,
|
|
403
|
+
closeOnBackdrop: !isLoading,
|
|
404
|
+
contentClassName: "max-w-md p-4 sm:p-6",
|
|
405
|
+
children: [
|
|
406
|
+
/* @__PURE__ */ jsx("h2", { id: titleId, className: "text-xl sm:text-2xl font-bold text-white mb-3 sm:mb-4", children: title }),
|
|
407
|
+
/* @__PURE__ */ jsx("div", { className: "text-white/70 mb-4 sm:mb-6", children: message }),
|
|
408
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col-reverse sm:flex-row gap-3 sm:justify-end", children: [
|
|
409
|
+
/* @__PURE__ */ jsx(
|
|
410
|
+
Button,
|
|
411
|
+
{
|
|
412
|
+
type: "button",
|
|
413
|
+
variant: "ghost",
|
|
414
|
+
onClick: onClose,
|
|
415
|
+
disabled: isLoading,
|
|
416
|
+
className: "w-full sm:w-auto px-4 py-3 sm:py-2.5 bg-white/10 hover:bg-white/15 text-white/90 transition-colors",
|
|
417
|
+
children: cancelText
|
|
418
|
+
}
|
|
419
|
+
),
|
|
420
|
+
/* @__PURE__ */ jsx(
|
|
421
|
+
Button,
|
|
422
|
+
{
|
|
423
|
+
type: "button",
|
|
424
|
+
variant: variant === "primary" ? "primary" : "ghost",
|
|
425
|
+
onClick: onConfirm,
|
|
426
|
+
disabled: isLoading,
|
|
427
|
+
className: cn(
|
|
428
|
+
"w-full sm:w-auto px-4 py-3 sm:py-2.5 text-white transition-colors",
|
|
429
|
+
variant !== "primary" && confirmVariantClass[variant]
|
|
430
|
+
),
|
|
431
|
+
children: isLoading ? loadingText : confirmText
|
|
432
|
+
}
|
|
433
|
+
)
|
|
434
|
+
] })
|
|
435
|
+
]
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
function Tooltip({ content, delayMs = 500, placement = "top", className, children }) {
|
|
440
|
+
const tooltipId = useId();
|
|
441
|
+
const openTimerRef = useRef(null);
|
|
442
|
+
const anchorRef = useRef(null);
|
|
443
|
+
const [open, setOpen] = useState(false);
|
|
444
|
+
const [pos, setPos] = useState(null);
|
|
445
|
+
const contentStr = useMemo(() => {
|
|
446
|
+
if (typeof content === "string") return content.trim();
|
|
447
|
+
return "";
|
|
448
|
+
}, [content]);
|
|
449
|
+
function clearTimer() {
|
|
450
|
+
if (openTimerRef.current !== null) {
|
|
451
|
+
window.clearTimeout(openTimerRef.current);
|
|
452
|
+
openTimerRef.current = null;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function updatePosition() {
|
|
456
|
+
const el = anchorRef.current;
|
|
457
|
+
if (!el) return;
|
|
458
|
+
const r = el.getBoundingClientRect();
|
|
459
|
+
const centerX = r.left + r.width / 2;
|
|
460
|
+
const preferTop = placement === "top";
|
|
461
|
+
const topY = r.top - 10;
|
|
462
|
+
const bottomY = r.bottom + 10;
|
|
463
|
+
const canTop = topY > 8;
|
|
464
|
+
const effPlacement = preferTop ? canTop ? "top" : "bottom" : "bottom";
|
|
465
|
+
setPos({
|
|
466
|
+
left: Math.round(centerX),
|
|
467
|
+
top: Math.round(effPlacement === "top" ? topY : bottomY),
|
|
468
|
+
placement: effPlacement
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
function scheduleOpen() {
|
|
472
|
+
clearTimer();
|
|
473
|
+
openTimerRef.current = window.setTimeout(() => {
|
|
474
|
+
setOpen(true);
|
|
475
|
+
}, Math.max(0, delayMs));
|
|
476
|
+
}
|
|
477
|
+
function close() {
|
|
478
|
+
clearTimer();
|
|
479
|
+
setOpen(false);
|
|
480
|
+
}
|
|
481
|
+
useEffect(() => {
|
|
482
|
+
if (!open) return;
|
|
483
|
+
updatePosition();
|
|
484
|
+
const onScroll = () => updatePosition();
|
|
485
|
+
const onResize = () => updatePosition();
|
|
486
|
+
window.addEventListener("scroll", onScroll, true);
|
|
487
|
+
window.addEventListener("resize", onResize);
|
|
488
|
+
return () => {
|
|
489
|
+
window.removeEventListener("scroll", onScroll, true);
|
|
490
|
+
window.removeEventListener("resize", onResize);
|
|
491
|
+
};
|
|
492
|
+
}, [open]);
|
|
493
|
+
useEffect(() => {
|
|
494
|
+
return () => clearTimer();
|
|
495
|
+
}, []);
|
|
496
|
+
const child = cloneElement(children, {
|
|
497
|
+
ref: (node) => {
|
|
498
|
+
anchorRef.current = node;
|
|
499
|
+
const prevRef = children.ref;
|
|
500
|
+
if (typeof prevRef === "function") prevRef(node);
|
|
501
|
+
else if (prevRef && typeof prevRef === "object") prevRef.current = node;
|
|
502
|
+
},
|
|
503
|
+
onMouseEnter: (e) => {
|
|
504
|
+
children.props.onMouseEnter?.(e);
|
|
505
|
+
scheduleOpen();
|
|
506
|
+
},
|
|
507
|
+
onMouseLeave: (e) => {
|
|
508
|
+
children.props.onMouseLeave?.(e);
|
|
509
|
+
close();
|
|
510
|
+
},
|
|
511
|
+
onFocus: (e) => {
|
|
512
|
+
children.props.onFocus?.(e);
|
|
513
|
+
scheduleOpen();
|
|
514
|
+
},
|
|
515
|
+
onBlur: (e) => {
|
|
516
|
+
children.props.onBlur?.(e);
|
|
517
|
+
close();
|
|
518
|
+
},
|
|
519
|
+
...contentStr ? { "aria-describedby": tooltipId } : {}
|
|
520
|
+
});
|
|
521
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
522
|
+
child,
|
|
523
|
+
open && content ? createPortal(
|
|
524
|
+
/* @__PURE__ */ jsx(
|
|
525
|
+
"div",
|
|
526
|
+
{
|
|
527
|
+
id: tooltipId,
|
|
528
|
+
role: "tooltip",
|
|
529
|
+
className: cn(
|
|
530
|
+
"fixed z-[9999] max-w-[320px] rounded-lg bg-gray-900 px-3 py-2 text-xs leading-snug text-white shadow-xl pointer-events-none",
|
|
531
|
+
className
|
|
532
|
+
),
|
|
533
|
+
style: pos ? {
|
|
534
|
+
left: pos.left,
|
|
535
|
+
top: pos.top,
|
|
536
|
+
transform: pos.placement === "top" ? "translate(-50%, -100%)" : "translate(-50%, 0%)"
|
|
537
|
+
} : { left: 0, top: 0, transform: "translate(-9999px, -9999px)" },
|
|
538
|
+
children: content
|
|
539
|
+
}
|
|
540
|
+
),
|
|
541
|
+
document.body
|
|
542
|
+
) : null
|
|
543
|
+
] });
|
|
544
|
+
}
|
|
545
|
+
function EmptyState({ icon: Icon, title, description, actionLabel, onAction, children, className }) {
|
|
546
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center justify-center py-12 px-4 text-center", className), children: [
|
|
547
|
+
Icon ? /* @__PURE__ */ jsx("div", { className: "mb-4 rounded-full bg-white/10 p-4", children: /* @__PURE__ */ jsx(Icon, { className: "h-8 w-8 text-white/50" }) }) : null,
|
|
548
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold text-white mb-2", children: title }),
|
|
549
|
+
description ? /* @__PURE__ */ jsx("p", { className: "text-white/60 max-w-md mb-6", children: description }) : null,
|
|
550
|
+
actionLabel && onAction ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "primary", onClick: onAction, children: actionLabel }) : null,
|
|
551
|
+
children
|
|
552
|
+
] });
|
|
553
|
+
}
|
|
554
|
+
function CollapsibleSection({ title, defaultOpen = true, children, right, className }) {
|
|
555
|
+
const [isOpen, setIsOpen] = useState(defaultOpen);
|
|
556
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("rounded-xl ring-1 ring-white/10 bg-white/5 overflow-hidden", className), children: [
|
|
557
|
+
/* @__PURE__ */ jsxs(
|
|
558
|
+
"button",
|
|
559
|
+
{
|
|
560
|
+
type: "button",
|
|
561
|
+
onClick: () => setIsOpen((prev) => !prev),
|
|
562
|
+
className: "flex w-full items-center justify-between gap-3 px-4 py-3 text-left hover:bg-white/[0.03] transition-colors",
|
|
563
|
+
"aria-expanded": isOpen,
|
|
564
|
+
children: [
|
|
565
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
566
|
+
/* @__PURE__ */ jsx(
|
|
567
|
+
"svg",
|
|
568
|
+
{
|
|
569
|
+
className: cn("h-4 w-4 text-white/40 transition-transform duration-200", isOpen && "rotate-90"),
|
|
570
|
+
fill: "none",
|
|
571
|
+
viewBox: "0 0 24 24",
|
|
572
|
+
stroke: "currentColor",
|
|
573
|
+
strokeWidth: 2,
|
|
574
|
+
"aria-hidden": "true",
|
|
575
|
+
children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" })
|
|
576
|
+
}
|
|
577
|
+
),
|
|
578
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-white", children: title })
|
|
579
|
+
] }),
|
|
580
|
+
right
|
|
581
|
+
]
|
|
582
|
+
}
|
|
583
|
+
),
|
|
584
|
+
/* @__PURE__ */ jsx(
|
|
585
|
+
"div",
|
|
586
|
+
{
|
|
587
|
+
className: cn(
|
|
588
|
+
"grid transition-[grid-template-rows] duration-200 ease-out",
|
|
589
|
+
isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
|
|
590
|
+
),
|
|
591
|
+
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-3", children }) })
|
|
592
|
+
}
|
|
593
|
+
)
|
|
594
|
+
] });
|
|
595
|
+
}
|
|
596
|
+
var maxWidthClass = {
|
|
597
|
+
sm: "max-w-2xl",
|
|
598
|
+
md: "max-w-4xl",
|
|
599
|
+
lg: "max-w-5xl",
|
|
600
|
+
xl: "max-w-7xl",
|
|
601
|
+
"2xl": "max-w-[92rem]",
|
|
602
|
+
full: "max-w-full"
|
|
603
|
+
};
|
|
604
|
+
function defaultBackground() {
|
|
605
|
+
return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-0 overflow-hidden", children: [
|
|
606
|
+
/* @__PURE__ */ jsx("div", { className: "absolute -top-32 -left-32 h-[600px] w-[600px] rounded-full bg-violet-600/20 blur-[140px]" }),
|
|
607
|
+
/* @__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-indigo-500/[0.10] blur-[120px]" })
|
|
609
|
+
] });
|
|
610
|
+
}
|
|
611
|
+
function PageShell({
|
|
612
|
+
header,
|
|
613
|
+
background,
|
|
614
|
+
variant = "plain",
|
|
615
|
+
className,
|
|
616
|
+
mainClassName,
|
|
617
|
+
containerClassName,
|
|
618
|
+
maxWidth = "xl",
|
|
619
|
+
children
|
|
620
|
+
}) {
|
|
621
|
+
const showDefaultBackground = variant !== "minimal" && !background;
|
|
622
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative overflow-hidden flex-1 min-h-full flex flex-col", className), children: [
|
|
623
|
+
background,
|
|
624
|
+
showDefaultBackground ? defaultBackground() : null,
|
|
625
|
+
header,
|
|
626
|
+
/* @__PURE__ */ jsx(
|
|
627
|
+
"main",
|
|
628
|
+
{
|
|
629
|
+
className: cn(
|
|
630
|
+
"relative py-8 flex-1 w-full mx-auto px-4 sm:px-6 lg:px-8",
|
|
631
|
+
maxWidthClass[maxWidth],
|
|
632
|
+
containerClassName,
|
|
633
|
+
mainClassName
|
|
634
|
+
),
|
|
635
|
+
children
|
|
636
|
+
}
|
|
637
|
+
)
|
|
638
|
+
] });
|
|
639
|
+
}
|
|
640
|
+
function Navbar({ logo, children, className, glass = true }) {
|
|
641
|
+
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 || /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
643
|
+
/* @__PURE__ */ jsx("div", { className: "w-9 h-9 rounded-xl animated-gradient" }),
|
|
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 })
|
|
647
|
+
] }) });
|
|
648
|
+
}
|
|
649
|
+
function Sidebar({ children, collapsed = false, onToggle, className }) {
|
|
650
|
+
return /* @__PURE__ */ jsxs(
|
|
651
|
+
"aside",
|
|
652
|
+
{
|
|
653
|
+
className: cn(
|
|
654
|
+
"glass h-full flex flex-col transition-[width] duration-200 overflow-hidden",
|
|
655
|
+
collapsed ? "w-16" : "w-64",
|
|
656
|
+
className
|
|
657
|
+
),
|
|
658
|
+
children: [
|
|
659
|
+
onToggle && /* @__PURE__ */ jsx(
|
|
660
|
+
"button",
|
|
661
|
+
{
|
|
662
|
+
type: "button",
|
|
663
|
+
onClick: onToggle,
|
|
664
|
+
className: "flex items-center justify-center w-full h-12 text-white/40 hover:text-white hover:bg-white/5 transition-colors",
|
|
665
|
+
"aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
|
|
666
|
+
children: /* @__PURE__ */ jsx(
|
|
667
|
+
"svg",
|
|
668
|
+
{
|
|
669
|
+
className: cn("h-5 w-5 transition-transform duration-200", collapsed && "rotate-180"),
|
|
670
|
+
fill: "none",
|
|
671
|
+
viewBox: "0 0 24 24",
|
|
672
|
+
stroke: "currentColor",
|
|
673
|
+
strokeWidth: 2,
|
|
674
|
+
children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11 19l-7-7 7-7m8 14l-7-7 7-7" })
|
|
675
|
+
}
|
|
676
|
+
)
|
|
677
|
+
}
|
|
678
|
+
),
|
|
679
|
+
/* @__PURE__ */ jsx("nav", { className: "flex-1 overflow-y-auto py-2", children })
|
|
680
|
+
]
|
|
681
|
+
}
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
function DashboardLayout({ children, navbar, sidebar, className }) {
|
|
685
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("min-h-screen bg-surface relative overflow-hidden", className), children: [
|
|
686
|
+
/* @__PURE__ */ jsxs("div", { "aria-hidden": "true", className: "pointer-events-none fixed inset-0", children: [
|
|
687
|
+
/* @__PURE__ */ jsx("div", { className: "orb orb-purple w-[400px] h-[400px] -top-[150px] -left-[150px] opacity-15" }),
|
|
688
|
+
/* @__PURE__ */ jsx("div", { className: "orb orb-blue w-[300px] h-[300px] top-[60%] -right-[100px] opacity-10" })
|
|
689
|
+
] }),
|
|
690
|
+
navbar,
|
|
691
|
+
/* @__PURE__ */ jsxs("div", { className: cn("flex", navbar && "pt-16"), children: [
|
|
692
|
+
sidebar,
|
|
693
|
+
/* @__PURE__ */ jsx("main", { className: "flex-1 min-w-0 py-8 px-6", children: /* @__PURE__ */ jsx("div", { className: "max-w-5xl mx-auto", children }) })
|
|
694
|
+
] })
|
|
695
|
+
] });
|
|
696
|
+
}
|
|
697
|
+
|
|
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 };
|