marclayui 0.0.0-stage → 0.3.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.
Files changed (71) hide show
  1. package/README.md +50 -3
  2. package/dist/index.cjs +1551 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.mts +394 -0
  5. package/dist/index.d.ts +394 -0
  6. package/dist/index.js +1483 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/theme-init.cjs +31 -0
  9. package/dist/theme-init.cjs.map +1 -0
  10. package/dist/theme-init.d.mts +8 -0
  11. package/dist/theme-init.d.ts +8 -0
  12. package/dist/theme-init.js +6 -0
  13. package/dist/theme-init.js.map +1 -0
  14. package/package.json +71 -4
  15. package/src/components/accordion/accordion.css +66 -0
  16. package/src/components/accordion/accordion.tsx +86 -0
  17. package/src/components/alert/alert.css +86 -0
  18. package/src/components/alert/alert.tsx +45 -0
  19. package/src/components/avatar/avatar.css +75 -0
  20. package/src/components/avatar/avatar.tsx +59 -0
  21. package/src/components/badge/badge.css +106 -0
  22. package/src/components/badge/badge.tsx +26 -0
  23. package/src/components/button/button.css +118 -0
  24. package/src/components/button/button.tsx +86 -0
  25. package/src/components/card/card.css +41 -0
  26. package/src/components/card/card.tsx +26 -0
  27. package/src/components/checks/checks.css +157 -0
  28. package/src/components/checks/checks.tsx +167 -0
  29. package/src/components/divider/divider.css +41 -0
  30. package/src/components/divider/divider.tsx +24 -0
  31. package/src/components/dropdown/dropdown.css +182 -0
  32. package/src/components/dropdown/dropdown.tsx +268 -0
  33. package/src/components/input/input.css +73 -0
  34. package/src/components/input/input.tsx +85 -0
  35. package/src/components/kbd/kbd.css +66 -0
  36. package/src/components/kbd/kbd.tsx +14 -0
  37. package/src/components/modal/modal.css +93 -0
  38. package/src/components/modal/modal.tsx +114 -0
  39. package/src/components/navbar/navbar.css +127 -0
  40. package/src/components/navbar/navbar.tsx +64 -0
  41. package/src/components/progress/progress.css +62 -0
  42. package/src/components/progress/progress.tsx +50 -0
  43. package/src/components/range/range.css +68 -0
  44. package/src/components/range/range.tsx +60 -0
  45. package/src/components/select/select.css +58 -0
  46. package/src/components/select/select.tsx +72 -0
  47. package/src/components/skeleton/skeleton.css +43 -0
  48. package/src/components/skeleton/skeleton.tsx +35 -0
  49. package/src/components/spinner/spinner.css +21 -0
  50. package/src/components/spinner/spinner.tsx +10 -0
  51. package/src/components/stack/stack.css +60 -0
  52. package/src/components/stack/stack.tsx +34 -0
  53. package/src/components/stat/stat.css +54 -0
  54. package/src/components/stat/stat.tsx +19 -0
  55. package/src/components/tabs/tabs.css +58 -0
  56. package/src/components/tabs/tabs.tsx +84 -0
  57. package/src/components/toast/toast.css +94 -0
  58. package/src/components/toast/toast.tsx +100 -0
  59. package/src/components/tooltip/tooltip.css +100 -0
  60. package/src/components/tooltip/tooltip.tsx +19 -0
  61. package/src/index.ts +115 -0
  62. package/src/styles/base.css +78 -0
  63. package/src/styles/index.css +27 -0
  64. package/src/styles/theme.css +246 -0
  65. package/src/theme-init.ts +6 -0
  66. package/src/theming/ThemeMenu.tsx +158 -0
  67. package/src/theming/ThemeProvider.tsx +234 -0
  68. package/src/theming/ThemeToggle.tsx +27 -0
  69. package/src/theming/presets.ts +33 -0
  70. package/src/theming/theming.css +155 -0
  71. package/src/utils/color.ts +58 -0
package/dist/index.js ADDED
@@ -0,0 +1,1483 @@
1
+ "use client";
2
+
3
+ // src/theming/ThemeProvider.tsx
4
+ import {
5
+ createContext,
6
+ useCallback,
7
+ useContext,
8
+ useEffect,
9
+ useMemo,
10
+ useRef,
11
+ useState
12
+ } from "react";
13
+
14
+ // src/utils/color.ts
15
+ var WHITE = { r: 255, g: 255, b: 255 };
16
+ var BLACK = { r: 0, g: 0, b: 0 };
17
+ function clamp(n, min = 0, max = 255) {
18
+ return Math.min(max, Math.max(min, n));
19
+ }
20
+ function parseHex(hex) {
21
+ let h = hex.trim().replace("#", "");
22
+ if (h.length === 3) h = h.split("").map((c) => c + c).join("");
23
+ if (h.length !== 6 || /[^0-9a-f]/i.test(h)) return { ...BLACK };
24
+ const n = parseInt(h, 16);
25
+ return { r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255 };
26
+ }
27
+ function toHex(c) {
28
+ const part = (v) => clamp(Math.round(v)).toString(16).padStart(2, "0");
29
+ return `#${part(c.r)}${part(c.g)}${part(c.b)}`;
30
+ }
31
+ function rgba(c, a) {
32
+ return `rgba(${clamp(Math.round(c.r))}, ${clamp(Math.round(c.g))}, ${clamp(Math.round(c.b))}, ${a})`;
33
+ }
34
+ function mix(a, b, t) {
35
+ return {
36
+ r: a.r + (b.r - a.r) * t,
37
+ g: a.g + (b.g - a.g) * t,
38
+ b: a.b + (b.b - a.b) * t
39
+ };
40
+ }
41
+ function lighten(hex, t) {
42
+ return toHex(mix(parseHex(hex), WHITE, t));
43
+ }
44
+ function darken(hex, t) {
45
+ return toHex(mix(parseHex(hex), BLACK, t));
46
+ }
47
+ function alpha(hex, a) {
48
+ return rgba(parseHex(hex), a);
49
+ }
50
+ function readableInk(base) {
51
+ const { r, g, b } = mix(parseHex(base), WHITE, 0.12);
52
+ const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
53
+ return lum > 0.62 ? "#3a2d20" : "#fff8f2";
54
+ }
55
+
56
+ // src/theming/presets.ts
57
+ var PRESETS = [
58
+ {
59
+ id: "clay",
60
+ label: "Claymorphism",
61
+ description: "Warm terracotta clay, squircles, Quicksand \u2014 the signature MarclayUI look."
62
+ },
63
+ {
64
+ id: "neo",
65
+ label: "Neumorphism",
66
+ description: "Soft grey-blue extrusions, violet (light) / gold (dark) accent, circles, Poppins & Manrope."
67
+ }
68
+ ];
69
+ var PRESET_STORAGE_KEY = "mcl-preset";
70
+ var COLOR_SWATCHES = [
71
+ { name: "Terra", color: "#e0785a" },
72
+ { name: "Violet", color: "#7c5cff" },
73
+ { name: "Ocean", color: "#4a90d9" },
74
+ { name: "Forest", color: "#5a8a46" },
75
+ { name: "Magenta", color: "#c74f9d" }
76
+ ];
77
+
78
+ // src/theming/ThemeProvider.tsx
79
+ import { jsx } from "react/jsx-runtime";
80
+ var ThemeContext = createContext(null);
81
+ var STORAGE_KEY = "mcl-theme";
82
+ var TONES = [
83
+ ["primary", "primary", "p"],
84
+ ["secondary", "secondary", "s"],
85
+ ["accent", "accent", "a"],
86
+ ["success", "success", "c"],
87
+ ["danger", "danger", "d"],
88
+ ["warning", "warning", "w"],
89
+ ["info", "info", "i"]
90
+ ];
91
+ function systemMode() {
92
+ if (typeof window === "undefined" || !window.matchMedia) return "light";
93
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
94
+ }
95
+ function toneVars(base, dark) {
96
+ return {
97
+ base,
98
+ light: lighten(base, dark ? 0.24 : 0.16),
99
+ deep: darken(base, 0.32),
100
+ ink: readableInk(base),
101
+ clay: `inset -5px -6px 10px ${alpha(darken(base, 0.68), dark ? 0.5 : 0.3)}, inset 5px 6px 10px ${alpha(
102
+ lighten(base, 0.45),
103
+ dark ? 0.38 : 0.5
104
+ )}`
105
+ };
106
+ }
107
+ function colorVars(c, mode) {
108
+ const vars = {};
109
+ const dark = mode === "dark";
110
+ for (const [key, name, short] of TONES) {
111
+ const base = c[key];
112
+ if (!base) continue;
113
+ const t = toneVars(base, dark);
114
+ vars[`--mcl-${name}`] = t.base;
115
+ vars[`--mcl-${name}-2`] = t.light;
116
+ vars[`--mcl-${name}-deep`] = t.deep;
117
+ vars[`--mcl-${name}-ink`] = t.ink;
118
+ vars[`--mcl-clay-${short}`] = t.clay;
119
+ if (key === "primary") vars["--mcl-ring"] = alpha(base, dark ? 0.45 : 0.4);
120
+ }
121
+ if (c.bg) {
122
+ vars["--mcl-bg"] = c.bg;
123
+ vars["--mcl-sd"] = alpha(darken(c.bg, dark ? 0.78 : 0.4), dark ? 0.72 : 0.6);
124
+ vars["--mcl-sl"] = alpha(lighten(c.bg, dark ? 0.3 : 0.9), dark ? 0.07 : 0.95);
125
+ vars["--mcl-sheen"] = alpha(lighten(c.bg, 0.9), dark ? 0.08 : 0.5);
126
+ }
127
+ if (c.ink) vars["--mcl-ink"] = c.ink;
128
+ if (c.muted) vars["--mcl-muted"] = c.muted;
129
+ return vars;
130
+ }
131
+ function MarclayProvider({
132
+ children,
133
+ defaultMode = "system",
134
+ preset: initialPreset = "clay",
135
+ colors: initialColors
136
+ }) {
137
+ const [preference, setPreferenceState] = useState(defaultMode);
138
+ const [mode, setMode] = useState("light");
139
+ const [preset, setPresetState] = useState(initialPreset);
140
+ const [colors, setColorsState] = useState(initialColors ?? {});
141
+ const appliedKeys = useRef(/* @__PURE__ */ new Set());
142
+ useEffect(() => {
143
+ let stored = null;
144
+ let storedPreset = null;
145
+ try {
146
+ stored = localStorage.getItem(STORAGE_KEY);
147
+ storedPreset = localStorage.getItem(PRESET_STORAGE_KEY);
148
+ } catch {
149
+ stored = null;
150
+ }
151
+ setPreferenceState(stored ?? defaultMode);
152
+ if (storedPreset === "clay" || storedPreset === "neo") setPresetState(storedPreset);
153
+ }, []);
154
+ useEffect(() => {
155
+ const apply = () => {
156
+ const m = preference === "system" ? systemMode() : preference;
157
+ setMode(m);
158
+ document.documentElement.setAttribute("data-theme", m);
159
+ };
160
+ apply();
161
+ if (preference !== "system" || !window.matchMedia) return;
162
+ const mq = window.matchMedia("(prefers-color-scheme: dark)");
163
+ const onChange = () => apply();
164
+ mq.addEventListener?.("change", onChange);
165
+ return () => mq.removeEventListener?.("change", onChange);
166
+ }, [preference]);
167
+ useEffect(() => {
168
+ if (preset === "clay") document.documentElement.removeAttribute("data-preset");
169
+ else document.documentElement.setAttribute("data-preset", preset);
170
+ }, [preset]);
171
+ useEffect(() => {
172
+ const root = document.documentElement;
173
+ appliedKeys.current.forEach((k) => root.style.removeProperty(k));
174
+ appliedKeys.current.clear();
175
+ if (Object.keys(colors).length === 0) return;
176
+ const vars = colorVars(colors, mode);
177
+ for (const [k, v] of Object.entries(vars)) {
178
+ root.style.setProperty(k, v);
179
+ appliedKeys.current.add(k);
180
+ }
181
+ }, [colors, mode]);
182
+ const setPreference = useCallback((p) => {
183
+ setPreferenceState(p);
184
+ try {
185
+ localStorage.setItem(STORAGE_KEY, p);
186
+ } catch {
187
+ }
188
+ }, []);
189
+ const toggle = useCallback(() => {
190
+ setPreference(mode === "dark" ? "light" : "dark");
191
+ }, [mode, setPreference]);
192
+ const setPreset = useCallback((p) => {
193
+ setPresetState(p);
194
+ try {
195
+ localStorage.setItem(PRESET_STORAGE_KEY, p);
196
+ } catch {
197
+ }
198
+ setColorsState({});
199
+ }, []);
200
+ const setColors = useCallback((c) => {
201
+ setColorsState((prev) => ({ ...prev, ...c }));
202
+ }, []);
203
+ const resetColors = useCallback(() => setColorsState({}), []);
204
+ const value = useMemo(
205
+ () => ({
206
+ mode,
207
+ preference,
208
+ setPreference,
209
+ toggle,
210
+ preset,
211
+ setPreset,
212
+ colors,
213
+ setColors,
214
+ resetColors
215
+ }),
216
+ [mode, preference, setPreference, toggle, preset, setPreset, colors, setColors, resetColors]
217
+ );
218
+ return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children });
219
+ }
220
+ function useTheme() {
221
+ const ctx = useContext(ThemeContext);
222
+ if (!ctx) throw new Error("useTheme must be used inside <MarclayProvider>.");
223
+ return ctx;
224
+ }
225
+ var ThemeProvider = MarclayProvider;
226
+
227
+ // src/theming/ThemeToggle.tsx
228
+ import { jsx as jsx2 } from "react/jsx-runtime";
229
+ function ThemeToggle({ className = "" }) {
230
+ const { mode, toggle } = useTheme();
231
+ const next = mode === "dark" ? "light" : "dark";
232
+ return /* @__PURE__ */ jsx2(
233
+ "button",
234
+ {
235
+ type: "button",
236
+ className: `mcl-theme-toggle ${className}`.trim(),
237
+ onClick: toggle,
238
+ "aria-label": `Switch to ${next} mode`,
239
+ title: `Switch to ${next} mode`,
240
+ children: /* @__PURE__ */ jsx2("span", { className: "mcl-theme-toggle-icon", "aria-hidden": "true", children: mode === "dark" ? "\u2600\uFE0F" : "\u{1F319}" })
241
+ }
242
+ );
243
+ }
244
+
245
+ // src/theming/ThemeMenu.tsx
246
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
247
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
248
+ var MODE_OPTIONS = [
249
+ { key: "light", label: "Light", icon: "\u2600\uFE0F" },
250
+ { key: "system", label: "System", icon: "\u{1F5A5}\uFE0F" },
251
+ { key: "dark", label: "Dark", icon: "\u{1F319}" }
252
+ ];
253
+ function ThemeMenu({ className = "" }) {
254
+ const {
255
+ mode,
256
+ preference,
257
+ setPreference,
258
+ preset,
259
+ setPreset,
260
+ colors,
261
+ setColors,
262
+ resetColors
263
+ } = useTheme();
264
+ const [open, setOpen] = useState2(false);
265
+ const [flipUp, setFlipUp] = useState2(false);
266
+ const shellRef = useRef2(null);
267
+ const panelRef = useRef2(null);
268
+ const triggerRef = useRef2(null);
269
+ useEffect2(() => {
270
+ if (!open) return;
271
+ const onDown = (e) => {
272
+ if (shellRef.current && !shellRef.current.contains(e.target)) {
273
+ setOpen(false);
274
+ triggerRef.current?.focus();
275
+ }
276
+ };
277
+ const onKey = (e) => {
278
+ if (e.key === "Escape") {
279
+ setOpen(false);
280
+ triggerRef.current?.focus();
281
+ }
282
+ };
283
+ document.addEventListener("mousedown", onDown);
284
+ document.addEventListener("keydown", onKey);
285
+ return () => {
286
+ document.removeEventListener("mousedown", onDown);
287
+ document.removeEventListener("keydown", onKey);
288
+ };
289
+ }, [open]);
290
+ useEffect2(() => {
291
+ if (open) {
292
+ panelRef.current?.focus();
293
+ const t = window.setTimeout(() => {
294
+ const trig = triggerRef.current;
295
+ const panel = panelRef.current;
296
+ if (!trig || !panel) return;
297
+ const r = trig.getBoundingClientRect();
298
+ setFlipUp(window.innerHeight - r.bottom < panel.offsetHeight + 12);
299
+ }, 0);
300
+ return () => window.clearTimeout(t);
301
+ }
302
+ setFlipUp(false);
303
+ }, [open]);
304
+ const activeSwatch = colors.primary?.toLowerCase();
305
+ return /* @__PURE__ */ jsxs("div", { ref: shellRef, className: `mcl-theme-menu-shell ${className}`.trim(), children: [
306
+ /* @__PURE__ */ jsx3(
307
+ "button",
308
+ {
309
+ ref: triggerRef,
310
+ type: "button",
311
+ className: "mcl-theme-toggle",
312
+ "aria-haspopup": "dialog",
313
+ "aria-expanded": open,
314
+ "aria-label": "Customize theme",
315
+ title: "Customize theme",
316
+ onClick: () => setOpen((o) => !o),
317
+ children: /* @__PURE__ */ jsx3("span", { className: "mcl-theme-toggle-icon", "aria-hidden": "true", children: preference === "system" ? "\u{1F5A5}\uFE0F" : mode === "dark" ? "\u{1F319}" : "\u2600\uFE0F" })
318
+ }
319
+ ),
320
+ open && /* @__PURE__ */ jsxs(
321
+ "div",
322
+ {
323
+ ref: panelRef,
324
+ tabIndex: -1,
325
+ role: "dialog",
326
+ "aria-label": "Customize theme",
327
+ className: `mcl-theme-menu${flipUp ? " mcl-theme-menu-up" : ""}`,
328
+ children: [
329
+ /* @__PURE__ */ jsxs("div", { className: "mcl-theme-menu-section", children: [
330
+ /* @__PURE__ */ jsxs("span", { className: "mcl-theme-menu-label", children: [
331
+ "Mode \xB7 ",
332
+ mode
333
+ ] }),
334
+ /* @__PURE__ */ jsx3("div", { className: "mcl-theme-menu-chips", children: MODE_OPTIONS.map((m) => /* @__PURE__ */ jsxs(
335
+ "button",
336
+ {
337
+ type: "button",
338
+ className: `mcl-theme-menu-chip${preference === m.key ? " mcl-theme-menu-chip-on" : ""}`,
339
+ "aria-pressed": preference === m.key,
340
+ onClick: () => setPreference(m.key),
341
+ children: [
342
+ /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", children: m.icon }),
343
+ " ",
344
+ m.label
345
+ ]
346
+ },
347
+ m.key
348
+ )) })
349
+ ] }),
350
+ /* @__PURE__ */ jsxs("div", { className: "mcl-theme-menu-section", children: [
351
+ /* @__PURE__ */ jsx3("span", { className: "mcl-theme-menu-label", children: "Style preset" }),
352
+ /* @__PURE__ */ jsx3("div", { className: "mcl-theme-menu-chips", children: PRESETS.map((p) => /* @__PURE__ */ jsx3(
353
+ "button",
354
+ {
355
+ type: "button",
356
+ className: `mcl-theme-menu-chip${preset === p.id ? " mcl-theme-menu-chip-on" : ""}`,
357
+ "aria-pressed": preset === p.id,
358
+ onClick: () => setPreset(p.id),
359
+ children: p.label
360
+ },
361
+ p.id
362
+ )) })
363
+ ] }),
364
+ /* @__PURE__ */ jsxs("div", { className: "mcl-theme-menu-section", children: [
365
+ /* @__PURE__ */ jsx3("span", { className: "mcl-theme-menu-label", children: "Primary color" }),
366
+ /* @__PURE__ */ jsx3("div", { className: "mcl-theme-menu-swatches", children: COLOR_SWATCHES.map((s) => /* @__PURE__ */ jsx3(
367
+ "button",
368
+ {
369
+ type: "button",
370
+ className: `mcl-theme-menu-swatch${activeSwatch === s.color.toLowerCase() ? " mcl-theme-menu-swatch-on" : ""}`,
371
+ style: { background: s.color },
372
+ title: s.name,
373
+ "aria-label": `Primary ${s.name}`,
374
+ "aria-pressed": activeSwatch === s.color.toLowerCase(),
375
+ onClick: () => setColors({ primary: s.color })
376
+ },
377
+ s.color
378
+ )) }),
379
+ /* @__PURE__ */ jsx3("button", { type: "button", className: "mcl-theme-menu-reset", onClick: resetColors, children: "Reset colors" })
380
+ ] })
381
+ ]
382
+ }
383
+ )
384
+ ] });
385
+ }
386
+
387
+ // src/components/button/button.tsx
388
+ import React3 from "react";
389
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
390
+ var Button = React3.forwardRef(
391
+ function Button2({
392
+ variant = "soft",
393
+ size = "md",
394
+ loading = false,
395
+ block = false,
396
+ className = "",
397
+ disabled,
398
+ children,
399
+ type = "button",
400
+ href,
401
+ ...rest
402
+ }, ref) {
403
+ const cls = [
404
+ "mcl-btn",
405
+ `mcl-btn-${variant}`,
406
+ `mcl-btn-${size}`,
407
+ block ? "mcl-btn-block" : "",
408
+ loading ? "mcl-btn-loading" : "",
409
+ className
410
+ ].filter(Boolean).join(" ");
411
+ if (href) {
412
+ return /* @__PURE__ */ jsxs2(
413
+ "a",
414
+ {
415
+ ref,
416
+ href,
417
+ className: cls,
418
+ "aria-disabled": disabled || void 0,
419
+ ...rest,
420
+ children: [
421
+ loading && /* @__PURE__ */ jsx4("span", { className: "mcl-btn-spinner", "aria-hidden": "true" }),
422
+ /* @__PURE__ */ jsx4("span", { className: "mcl-btn-label", children })
423
+ ]
424
+ }
425
+ );
426
+ }
427
+ return /* @__PURE__ */ jsxs2(
428
+ "button",
429
+ {
430
+ ref,
431
+ type,
432
+ className: cls,
433
+ disabled: disabled || loading,
434
+ ...rest,
435
+ children: [
436
+ loading && /* @__PURE__ */ jsx4("span", { className: "mcl-btn-spinner", "aria-hidden": "true" }),
437
+ /* @__PURE__ */ jsx4("span", { className: "mcl-btn-label", children })
438
+ ]
439
+ }
440
+ );
441
+ }
442
+ );
443
+ function ButtonGroup({ children, className = "", ...rest }) {
444
+ return /* @__PURE__ */ jsx4("div", { className: `mcl-btn-group ${className}`.trim(), role: "group", ...rest, children });
445
+ }
446
+
447
+ // src/components/kbd/kbd.tsx
448
+ import { jsx as jsx5 } from "react/jsx-runtime";
449
+ function Kbd({ children, className = "", ...rest }) {
450
+ return /* @__PURE__ */ jsx5("kbd", { className: `mcl-kbd ${className}`.trim(), ...rest, children });
451
+ }
452
+
453
+ // src/components/card/card.tsx
454
+ import { jsx as jsx6 } from "react/jsx-runtime";
455
+ function Card({ hover = false, className = "", ...rest }) {
456
+ return /* @__PURE__ */ jsx6("div", { className: `mcl-card${hover ? " mcl-card-hover" : ""} ${className}`.trim(), ...rest });
457
+ }
458
+ function CardHeader({ className = "", ...rest }) {
459
+ return /* @__PURE__ */ jsx6("div", { className: `mcl-card-header ${className}`.trim(), ...rest });
460
+ }
461
+ function CardTitle({ className = "", ...rest }) {
462
+ return /* @__PURE__ */ jsx6("h3", { className: `mcl-card-title ${className}`.trim(), ...rest });
463
+ }
464
+ function CardBody({ className = "", ...rest }) {
465
+ return /* @__PURE__ */ jsx6("div", { className: `mcl-card-body ${className}`.trim(), ...rest });
466
+ }
467
+ function CardFooter({ className = "", ...rest }) {
468
+ return /* @__PURE__ */ jsx6("div", { className: `mcl-card-footer ${className}`.trim(), ...rest });
469
+ }
470
+
471
+ // src/components/input/input.tsx
472
+ import React4 from "react";
473
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
474
+ function fieldClasses(base, size, invalid) {
475
+ return [base, `${base}-${size}`, invalid ? `${base}-invalid` : ""].filter(Boolean).join(" ");
476
+ }
477
+ function Chrome({
478
+ id,
479
+ label,
480
+ hint,
481
+ error,
482
+ block,
483
+ children
484
+ }) {
485
+ return /* @__PURE__ */ jsxs3("div", { className: `mcl-field${block ? " mcl-field-block" : ""}`, children: [
486
+ label && /* @__PURE__ */ jsx7("label", { className: "mcl-label", htmlFor: id, children: label }),
487
+ children,
488
+ error ? /* @__PURE__ */ jsx7("div", { className: "mcl-hint mcl-hint-error", role: "alert", children: error }) : hint ? /* @__PURE__ */ jsx7("div", { className: "mcl-hint", children: hint }) : null
489
+ ] });
490
+ }
491
+ function Input({ label, hint, error, size = "md", block = false, className = "", id, ...rest }) {
492
+ const autoId = React4.useId();
493
+ const inputId = id ?? autoId;
494
+ const input = /* @__PURE__ */ jsx7(
495
+ "input",
496
+ {
497
+ id: inputId,
498
+ className: `${fieldClasses("mcl-input", size, !!error)}${block ? " mcl-input-block" : ""} ${className}`.trim(),
499
+ "aria-invalid": error ? true : void 0,
500
+ ...rest
501
+ }
502
+ );
503
+ if (!label && !hint && !error) return input;
504
+ return /* @__PURE__ */ jsx7(Chrome, { id: inputId, label, hint, error, block, children: input });
505
+ }
506
+ function Textarea({ label, hint, error, size = "md", block = true, className = "", id, rows = 4, ...rest }) {
507
+ const autoId = React4.useId();
508
+ const inputId = id ?? autoId;
509
+ const el = /* @__PURE__ */ jsx7(
510
+ "textarea",
511
+ {
512
+ id: inputId,
513
+ rows,
514
+ className: `${fieldClasses("mcl-textarea", size, !!error)} ${className}`.trim(),
515
+ "aria-invalid": error ? true : void 0,
516
+ ...rest
517
+ }
518
+ );
519
+ if (!label && !hint && !error) return el;
520
+ return /* @__PURE__ */ jsx7(Chrome, { id: inputId, label, hint, error, block: true, children: el });
521
+ }
522
+
523
+ // src/components/select/select.tsx
524
+ import React5 from "react";
525
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
526
+ function Select({
527
+ label,
528
+ hint,
529
+ error,
530
+ size = "md",
531
+ options,
532
+ placeholder,
533
+ className = "",
534
+ id,
535
+ children,
536
+ ...rest
537
+ }) {
538
+ const autoId = React5.useId();
539
+ const selectId = id ?? autoId;
540
+ const select = /* @__PURE__ */ jsx8("span", { className: `mcl-select-shell ${className}`.trim(), children: /* @__PURE__ */ jsxs4(
541
+ "select",
542
+ {
543
+ id: selectId,
544
+ className: `mcl-select mcl-select-${size}${error ? " mcl-select-invalid" : ""}`,
545
+ "aria-invalid": error ? true : void 0,
546
+ ...rest,
547
+ children: [
548
+ placeholder && /* @__PURE__ */ jsx8("option", { value: "", disabled: true, children: placeholder }),
549
+ options?.map((o) => /* @__PURE__ */ jsx8("option", { value: o.value, children: o.label }, o.value)),
550
+ children
551
+ ]
552
+ }
553
+ ) });
554
+ if (!label && !hint && !error) return select;
555
+ return /* @__PURE__ */ jsxs4("div", { className: "mcl-field", children: [
556
+ label && /* @__PURE__ */ jsx8("label", { className: "mcl-label", htmlFor: selectId, children: label }),
557
+ select,
558
+ error ? /* @__PURE__ */ jsx8("div", { className: "mcl-hint mcl-hint-error", role: "alert", children: error }) : hint ? /* @__PURE__ */ jsx8("div", { className: "mcl-hint", children: hint }) : null
559
+ ] });
560
+ }
561
+
562
+ // src/components/checks/checks.tsx
563
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
564
+ import { Fragment, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
565
+ function withLabel(input, label) {
566
+ if (label === void 0 || label === null) return input;
567
+ return /* @__PURE__ */ jsxs5("label", { className: "mcl-check", children: [
568
+ input,
569
+ /* @__PURE__ */ jsx9("span", { className: "mcl-check-label", children: label })
570
+ ] });
571
+ }
572
+ function Checkbox({ label, indeterminate = false, className = "", ...rest }) {
573
+ const inputRef = useRef3(null);
574
+ useEffect3(() => {
575
+ if (inputRef.current) inputRef.current.indeterminate = indeterminate;
576
+ }, [indeterminate]);
577
+ return withLabel(
578
+ /* @__PURE__ */ jsxs5(Fragment, { children: [
579
+ /* @__PURE__ */ jsx9("input", { ref: inputRef, type: "checkbox", className: `mcl-check-input ${className}`.trim(), ...rest }),
580
+ /* @__PURE__ */ jsx9("span", { className: "mcl-check-box", "aria-hidden": "true" })
581
+ ] }),
582
+ label
583
+ );
584
+ }
585
+ function CheckboxGroup({
586
+ options,
587
+ value,
588
+ defaultValue = [],
589
+ onChange,
590
+ selectAll = false,
591
+ selectAllLabel = "Select all",
592
+ label,
593
+ disabled = false,
594
+ className = ""
595
+ }) {
596
+ const [inner, setInner] = useState3(defaultValue);
597
+ const allRef = useRef3(null);
598
+ const selected = value ?? inner;
599
+ const enabled = options.filter((o) => !o.disabled).map((o) => o.value);
600
+ const allOn = enabled.length > 0 && enabled.every((v) => selected.includes(v));
601
+ const someOn = enabled.some((v) => selected.includes(v));
602
+ useEffect3(() => {
603
+ if (allRef.current) allRef.current.indeterminate = someOn && !allOn;
604
+ }, [someOn, allOn]);
605
+ const commit = (next) => {
606
+ setInner(next);
607
+ onChange?.(next);
608
+ };
609
+ const toggle = (v) => {
610
+ commit(selected.includes(v) ? selected.filter((x) => x !== v) : [...selected, v]);
611
+ };
612
+ const toggleAll = () => {
613
+ commit(allOn ? selected.filter((v) => !enabled.includes(v)) : Array.from(/* @__PURE__ */ new Set([...selected, ...enabled])));
614
+ };
615
+ return /* @__PURE__ */ jsxs5(
616
+ "div",
617
+ {
618
+ className: `mcl-check-group ${className}`.trim(),
619
+ role: "group",
620
+ "aria-label": typeof label === "string" ? label : void 0,
621
+ children: [
622
+ label && /* @__PURE__ */ jsx9("span", { className: "mcl-label", children: label }),
623
+ selectAll && /* @__PURE__ */ jsxs5("label", { className: `mcl-check mcl-check-group-all${disabled ? " mcl-check-disabled" : ""}`, children: [
624
+ /* @__PURE__ */ jsx9(
625
+ "input",
626
+ {
627
+ ref: allRef,
628
+ type: "checkbox",
629
+ className: "mcl-check-input",
630
+ checked: allOn,
631
+ disabled,
632
+ onChange: toggleAll
633
+ }
634
+ ),
635
+ /* @__PURE__ */ jsx9("span", { className: "mcl-check-box", "aria-hidden": "true" }),
636
+ /* @__PURE__ */ jsx9("span", { className: "mcl-check-label", children: selectAllLabel })
637
+ ] }),
638
+ /* @__PURE__ */ jsx9("div", { className: "mcl-check-group-items", children: options.map((o) => /* @__PURE__ */ jsxs5("label", { className: `mcl-check${o.disabled || disabled ? " mcl-check-disabled" : ""}`, children: [
639
+ /* @__PURE__ */ jsx9(
640
+ "input",
641
+ {
642
+ type: "checkbox",
643
+ className: "mcl-check-input",
644
+ checked: selected.includes(o.value),
645
+ disabled: o.disabled || disabled,
646
+ onChange: () => toggle(o.value)
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsx9("span", { className: "mcl-check-box", "aria-hidden": "true" }),
650
+ /* @__PURE__ */ jsx9("span", { className: "mcl-check-label", children: o.label })
651
+ ] }, o.value)) })
652
+ ]
653
+ }
654
+ );
655
+ }
656
+ function Radio({ label, className = "", ...rest }) {
657
+ return withLabel(
658
+ /* @__PURE__ */ jsxs5(Fragment, { children: [
659
+ /* @__PURE__ */ jsx9("input", { type: "radio", className: `mcl-check-input ${className}`.trim(), ...rest }),
660
+ /* @__PURE__ */ jsx9("span", { className: "mcl-check-box mcl-radio-box", "aria-hidden": "true" })
661
+ ] }),
662
+ label
663
+ );
664
+ }
665
+ function Switch({ label, className = "", ...rest }) {
666
+ return withLabel(
667
+ /* @__PURE__ */ jsxs5(Fragment, { children: [
668
+ /* @__PURE__ */ jsx9(
669
+ "input",
670
+ {
671
+ type: "checkbox",
672
+ role: "switch",
673
+ className: `mcl-switch-input ${className}`.trim(),
674
+ ...rest
675
+ }
676
+ ),
677
+ /* @__PURE__ */ jsx9("span", { className: "mcl-switch-track", "aria-hidden": "true", children: /* @__PURE__ */ jsx9("span", { className: "mcl-switch-knob" }) })
678
+ ] }),
679
+ label
680
+ );
681
+ }
682
+
683
+ // src/components/dropdown/dropdown.tsx
684
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
685
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
686
+ function Dropdown({
687
+ items,
688
+ value,
689
+ defaultValue,
690
+ onChange,
691
+ multiple = false,
692
+ placeholder = "Select\u2026",
693
+ label,
694
+ hint,
695
+ size = "md",
696
+ block = false,
697
+ disabled = false,
698
+ className = ""
699
+ }) {
700
+ const [open, setOpen] = useState4(false);
701
+ const [inner, setInner] = useState4(defaultValue);
702
+ const [active, setActive] = useState4(0);
703
+ const [flip, setFlip] = useState4({ up: false, left: false });
704
+ const shellRef = useRef4(null);
705
+ const menuRef = useRef4(null);
706
+ const triggerRef = useRef4(null);
707
+ const current = value ?? inner;
708
+ const selectedValues = multiple ? Array.isArray(current) ? current : [] : [];
709
+ const singleValue = !multiple && !Array.isArray(current) ? current : void 0;
710
+ const labelFor = (v) => items.find((i) => i.value === v)?.label;
711
+ let triggerText = placeholder;
712
+ if (multiple) {
713
+ if (selectedValues.length > 0) {
714
+ const labels = items.filter((i) => selectedValues.includes(i.value)).map((i) => i.label);
715
+ triggerText = labels.length <= 2 ? labels.join(", ") : `${labels.length} selected`;
716
+ }
717
+ } else if (singleValue !== void 0) {
718
+ triggerText = labelFor(singleValue) ?? singleValue;
719
+ }
720
+ const hasSelection = multiple ? selectedValues.length > 0 : singleValue !== void 0 && singleValue !== "";
721
+ useEffect4(() => {
722
+ if (!open) return;
723
+ const onDown = (e) => {
724
+ if (shellRef.current && !shellRef.current.contains(e.target)) {
725
+ setOpen(false);
726
+ triggerRef.current?.focus();
727
+ }
728
+ };
729
+ document.addEventListener("mousedown", onDown);
730
+ return () => document.removeEventListener("mousedown", onDown);
731
+ }, [open]);
732
+ useEffect4(() => {
733
+ if (!open) return;
734
+ const firstSelected = multiple ? items.findIndex((i) => selectedValues.includes(i.value)) : items.findIndex((i) => i.value === singleValue);
735
+ const firstEnabled = items.findIndex((i) => !i.disabled);
736
+ const idx = firstSelected >= 0 ? firstSelected : Math.max(firstEnabled, 0);
737
+ setActive(idx);
738
+ menuRef.current?.focus();
739
+ const t = window.setTimeout(() => {
740
+ const el = menuRef.current?.children[idx];
741
+ if (el && typeof el.scrollIntoView === "function") el.scrollIntoView({ block: "nearest" });
742
+ }, 0);
743
+ return () => window.clearTimeout(t);
744
+ }, [open]);
745
+ useEffect4(() => {
746
+ if (!open) return;
747
+ const t = window.setTimeout(() => {
748
+ const trig = triggerRef.current;
749
+ const menu = menuRef.current;
750
+ if (!trig || !menu) return;
751
+ const r = trig.getBoundingClientRect();
752
+ const m = menu.getBoundingClientRect();
753
+ const below = window.innerHeight - r.bottom;
754
+ const above = r.top;
755
+ setFlip({
756
+ up: below < m.height + 12 && above > below,
757
+ left: r.left + Math.max(m.width, r.width) > window.innerWidth - 12
758
+ });
759
+ }, 0);
760
+ return () => window.clearTimeout(t);
761
+ }, [open]);
762
+ const close = (refocus = true) => {
763
+ setOpen(false);
764
+ if (refocus) triggerRef.current?.focus();
765
+ };
766
+ const commit = (v) => {
767
+ if (multiple) {
768
+ const next = selectedValues.includes(v) ? selectedValues.filter((x) => x !== v) : [...selectedValues, v];
769
+ setInner(next);
770
+ onChange?.(next);
771
+ } else {
772
+ setInner(v);
773
+ onChange?.(v);
774
+ close();
775
+ }
776
+ };
777
+ const move = (dir) => {
778
+ const enabled = items.map((it, i) => it.disabled ? -1 : i).filter((i) => i >= 0);
779
+ if (enabled.length === 0) return;
780
+ const pos = enabled.indexOf(active);
781
+ const next = enabled[(pos + dir + enabled.length) % enabled.length];
782
+ setActive(next);
783
+ const el = menuRef.current?.children[next];
784
+ if (el && typeof el.scrollIntoView === "function") el.scrollIntoView({ block: "nearest" });
785
+ };
786
+ const onMenuKeyDown = (e) => {
787
+ if (e.key === "Escape") {
788
+ e.preventDefault();
789
+ close();
790
+ } else if (e.key === "ArrowDown") {
791
+ e.preventDefault();
792
+ move(1);
793
+ } else if (e.key === "ArrowUp") {
794
+ e.preventDefault();
795
+ move(-1);
796
+ } else if (e.key === "Home") {
797
+ e.preventDefault();
798
+ const first = items.findIndex((i) => !i.disabled);
799
+ if (first >= 0) setActive(first);
800
+ } else if (e.key === "End") {
801
+ e.preventDefault();
802
+ const last = items.map((i, idx) => i.disabled ? -1 : idx).filter((i) => i >= 0).pop();
803
+ if (last !== void 0) setActive(last);
804
+ } else if (e.key === "Enter" || e.key === " ") {
805
+ e.preventDefault();
806
+ const it = items[active];
807
+ if (it && !it.disabled) commit(it.value);
808
+ } else if (e.key === "Tab") {
809
+ setOpen(false);
810
+ }
811
+ };
812
+ const onTriggerKeyDown = (e) => {
813
+ if (!open && e.key === "ArrowDown") {
814
+ e.preventDefault();
815
+ setOpen(true);
816
+ }
817
+ };
818
+ return /* @__PURE__ */ jsxs6("div", { className: `mcl-field${block ? " mcl-field-block" : ""} ${className}`.trim(), children: [
819
+ label && /* @__PURE__ */ jsx10("span", { className: "mcl-label", children: label }),
820
+ /* @__PURE__ */ jsxs6(
821
+ "div",
822
+ {
823
+ ref: shellRef,
824
+ className: `mcl-dropdown-shell${block ? " mcl-dropdown-block" : ""}`,
825
+ children: [
826
+ /* @__PURE__ */ jsxs6(
827
+ "button",
828
+ {
829
+ ref: triggerRef,
830
+ type: "button",
831
+ className: `mcl-dropdown-trigger mcl-dropdown-${size}`,
832
+ "aria-haspopup": "listbox",
833
+ "aria-expanded": open,
834
+ disabled,
835
+ onClick: () => setOpen((o) => !o),
836
+ onKeyDown: onTriggerKeyDown,
837
+ children: [
838
+ /* @__PURE__ */ jsx10("span", { className: `mcl-dropdown-value${hasSelection ? "" : " mcl-dropdown-placeholder"}`, children: triggerText }),
839
+ /* @__PURE__ */ jsx10("span", { className: "mcl-dropdown-chevron", "aria-hidden": "true" })
840
+ ]
841
+ }
842
+ ),
843
+ open && /* @__PURE__ */ jsx10(
844
+ "div",
845
+ {
846
+ ref: menuRef,
847
+ tabIndex: -1,
848
+ role: "listbox",
849
+ "aria-multiselectable": multiple || void 0,
850
+ className: [
851
+ "mcl-dropdown-menu",
852
+ "mcl-scroll",
853
+ `mcl-dropdown-${size}`,
854
+ flip.up ? "mcl-dropdown-menu-up" : "",
855
+ flip.left ? "mcl-dropdown-menu-left" : ""
856
+ ].filter(Boolean).join(" "),
857
+ onKeyDown: onMenuKeyDown,
858
+ children: items.map((it, i) => {
859
+ const selected = multiple ? selectedValues.includes(it.value) : singleValue === it.value;
860
+ return /* @__PURE__ */ jsxs6(
861
+ "div",
862
+ {
863
+ role: "option",
864
+ "aria-selected": selected,
865
+ "aria-disabled": it.disabled || void 0,
866
+ className: [
867
+ "mcl-dropdown-item",
868
+ i === active ? "mcl-dropdown-item-active" : "",
869
+ selected ? "mcl-dropdown-item-selected" : "",
870
+ it.disabled ? "mcl-dropdown-item-disabled" : ""
871
+ ].filter(Boolean).join(" "),
872
+ onMouseEnter: () => setActive(i),
873
+ onClick: () => {
874
+ if (!it.disabled) commit(it.value);
875
+ },
876
+ children: [
877
+ multiple && /* @__PURE__ */ jsx10("span", { className: "mcl-dropdown-check", "aria-hidden": "true" }),
878
+ /* @__PURE__ */ jsx10("span", { className: "mcl-dropdown-item-label", children: it.label }),
879
+ !multiple && selected && /* @__PURE__ */ jsx10("span", { className: "mcl-dropdown-tick", "aria-hidden": "true", children: "\u2713" })
880
+ ]
881
+ },
882
+ it.value
883
+ );
884
+ })
885
+ }
886
+ )
887
+ ]
888
+ }
889
+ ),
890
+ hint && /* @__PURE__ */ jsx10("div", { className: "mcl-hint", children: hint })
891
+ ] });
892
+ }
893
+
894
+ // src/components/range/range.tsx
895
+ import React8, { useState as useState5 } from "react";
896
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
897
+ function Range({
898
+ label,
899
+ hint,
900
+ showValue = false,
901
+ value,
902
+ defaultValue,
903
+ onChange,
904
+ className = "",
905
+ id,
906
+ ...rest
907
+ }) {
908
+ const autoId = React8.useId();
909
+ const inputId = id ?? autoId;
910
+ const isControlled = value !== void 0;
911
+ const [inner, setInner] = useState5(
912
+ () => value !== void 0 ? String(value) : defaultValue !== void 0 ? String(defaultValue) : "0"
913
+ );
914
+ const current = isControlled ? String(value) : inner;
915
+ const input = /* @__PURE__ */ jsx11(
916
+ "input",
917
+ {
918
+ id: inputId,
919
+ type: "range",
920
+ className: `mcl-range ${className}`.trim(),
921
+ value: isControlled ? value : inner,
922
+ onChange: (e) => {
923
+ if (!isControlled) setInner(e.target.value);
924
+ onChange?.(e);
925
+ },
926
+ ...rest
927
+ }
928
+ );
929
+ const field = /* @__PURE__ */ jsxs7("div", { className: "mcl-field", children: [
930
+ label && /* @__PURE__ */ jsxs7("label", { className: "mcl-range-head", htmlFor: inputId, children: [
931
+ /* @__PURE__ */ jsx11("span", { className: "mcl-label", children: label }),
932
+ showValue && /* @__PURE__ */ jsx11("span", { className: "mcl-range-value", children: current })
933
+ ] }),
934
+ input,
935
+ hint && /* @__PURE__ */ jsx11("div", { className: "mcl-hint", children: hint })
936
+ ] });
937
+ if (!label && !hint) return input;
938
+ return field;
939
+ }
940
+
941
+ // src/components/badge/badge.tsx
942
+ import { jsx as jsx12 } from "react/jsx-runtime";
943
+ function Badge({ variant = "neutral", solid = false, className = "", ...rest }) {
944
+ return /* @__PURE__ */ jsx12(
945
+ "span",
946
+ {
947
+ className: `mcl-badge mcl-badge-${variant}${solid ? " mcl-badge-solid" : ""} ${className}`.trim(),
948
+ ...rest
949
+ }
950
+ );
951
+ }
952
+
953
+ // src/components/alert/alert.tsx
954
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
955
+ var DEFAULT_ICONS = {
956
+ success: "\u2713",
957
+ danger: "\u2715",
958
+ warning: "!",
959
+ info: "i"
960
+ };
961
+ function Alert({
962
+ variant = "info",
963
+ title,
964
+ icon,
965
+ onClose,
966
+ className = "",
967
+ children,
968
+ ...rest
969
+ }) {
970
+ return /* @__PURE__ */ jsxs8("div", { className: `mcl-alert mcl-alert-${variant} ${className}`.trim(), role: "alert", ...rest, children: [
971
+ /* @__PURE__ */ jsx13("span", { className: "mcl-alert-icon", "aria-hidden": "true", children: icon ?? DEFAULT_ICONS[variant] }),
972
+ /* @__PURE__ */ jsxs8("div", { className: "mcl-alert-content", children: [
973
+ title && /* @__PURE__ */ jsx13("p", { className: "mcl-alert-title", children: title }),
974
+ children
975
+ ] }),
976
+ onClose && /* @__PURE__ */ jsx13("button", { type: "button", className: "mcl-alert-close", onClick: onClose, "aria-label": "Dismiss", children: "\u2715" })
977
+ ] });
978
+ }
979
+
980
+ // src/components/avatar/avatar.tsx
981
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
982
+ function initialsOf(name) {
983
+ const parts = name.trim().split(/\s+/).slice(0, 2);
984
+ return parts.map((p) => p[0]?.toUpperCase() ?? "").join("");
985
+ }
986
+ function Avatar({
987
+ src,
988
+ alt,
989
+ name,
990
+ size = "md",
991
+ status,
992
+ gradient = false,
993
+ className = "",
994
+ children,
995
+ ...rest
996
+ }) {
997
+ const label = alt ?? (name ? `${name}'s avatar` : "avatar");
998
+ return /* @__PURE__ */ jsxs9(
999
+ "span",
1000
+ {
1001
+ className: `mcl-avatar mcl-avatar-${size}${gradient ? " mcl-avatar-gradient" : ""} ${className}`.trim(),
1002
+ role: "img",
1003
+ "aria-label": label,
1004
+ ...rest,
1005
+ children: [
1006
+ src ? /* @__PURE__ */ jsx14("img", { src, alt: "", className: "mcl-avatar-img", loading: "lazy", decoding: "async" }) : children ?? initialsOf(name ?? "??") ?? "",
1007
+ status && /* @__PURE__ */ jsx14("span", { className: `mcl-avatar-status mcl-avatar-status-${status}`, "aria-hidden": "true" })
1008
+ ]
1009
+ }
1010
+ );
1011
+ }
1012
+ function AvatarGroup({ className = "", children, ...rest }) {
1013
+ return /* @__PURE__ */ jsx14("span", { className: `mcl-avatar-group ${className}`.trim(), ...rest, children });
1014
+ }
1015
+
1016
+ // src/components/progress/progress.tsx
1017
+ import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
1018
+ function Progress({
1019
+ value = 0,
1020
+ max = 100,
1021
+ tone = "primary",
1022
+ size = "md",
1023
+ label,
1024
+ showValue = false,
1025
+ indeterminate = false,
1026
+ className = ""
1027
+ }) {
1028
+ const pct = Math.max(0, Math.min(100, value / max * 100));
1029
+ return /* @__PURE__ */ jsxs10(
1030
+ "div",
1031
+ {
1032
+ className: `mcl-progress mcl-progress-${size} ${className}`.trim(),
1033
+ role: "progressbar",
1034
+ "aria-valuemin": 0,
1035
+ "aria-valuemax": max,
1036
+ "aria-valuenow": indeterminate ? void 0 : Math.round(pct),
1037
+ children: [
1038
+ (label || showValue) && /* @__PURE__ */ jsxs10("div", { className: "mcl-progress-head", children: [
1039
+ label && /* @__PURE__ */ jsx15("span", { children: label }),
1040
+ showValue && /* @__PURE__ */ jsx15("span", { className: "mcl-progress-value", children: indeterminate ? "\u2026" : `${Math.round(pct)}%` })
1041
+ ] }),
1042
+ /* @__PURE__ */ jsx15("div", { className: "mcl-progress-track", children: /* @__PURE__ */ jsx15(
1043
+ "span",
1044
+ {
1045
+ className: `mcl-progress-fill mcl-progress-fill-${tone}${indeterminate ? " mcl-progress-fill-indeterminate" : ""}`,
1046
+ style: indeterminate ? void 0 : { width: `${pct}%` }
1047
+ }
1048
+ ) })
1049
+ ]
1050
+ }
1051
+ );
1052
+ }
1053
+
1054
+ // src/components/spinner/spinner.tsx
1055
+ import { jsx as jsx16 } from "react/jsx-runtime";
1056
+ function Spinner({ size = "md", className = "" }) {
1057
+ return /* @__PURE__ */ jsx16("span", { className: `mcl-spinner mcl-spinner-${size} ${className}`.trim(), role: "status", "aria-label": "Loading" });
1058
+ }
1059
+
1060
+ // src/components/skeleton/skeleton.tsx
1061
+ import { jsx as jsx17 } from "react/jsx-runtime";
1062
+ function Skeleton({ variant = "text", width, height, count = 1, className = "" }) {
1063
+ const lines = variant === "text" ? Math.max(1, count) : 1;
1064
+ const style = (last) => ({
1065
+ width: width ?? (variant === "circle" ? 48 : "100%"),
1066
+ height: height ?? (variant === "text" ? 14 : variant === "circle" ? 48 : 96),
1067
+ ...variant === "text" && count > 1 && last ? { width: "62%" } : null
1068
+ });
1069
+ if (lines === 1) {
1070
+ return /* @__PURE__ */ jsx17(
1071
+ "span",
1072
+ {
1073
+ className: `mcl-skeleton mcl-skeleton-${variant} ${className}`.trim(),
1074
+ style: style(true),
1075
+ "aria-hidden": "true"
1076
+ }
1077
+ );
1078
+ }
1079
+ return /* @__PURE__ */ jsx17("span", { className: `mcl-skeleton-lines ${className}`.trim(), "aria-hidden": "true", children: Array.from({ length: lines }, (_, i) => /* @__PURE__ */ jsx17("span", { className: "mcl-skeleton mcl-skeleton-text", style: style(i === lines - 1) }, i)) });
1080
+ }
1081
+
1082
+ // src/components/modal/modal.tsx
1083
+ import { useEffect as useEffect5, useId, useRef as useRef5, useState as useState6 } from "react";
1084
+ import { createPortal } from "react-dom";
1085
+ import { jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
1086
+ var FOCUSABLE = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
1087
+ function Modal({
1088
+ open,
1089
+ onClose,
1090
+ title,
1091
+ footer,
1092
+ size = "md",
1093
+ closeOnBackdrop = true,
1094
+ closeOnEsc = true,
1095
+ children,
1096
+ className = ""
1097
+ }) {
1098
+ const [mounted, setMounted] = useState6(false);
1099
+ const panelRef = useRef5(null);
1100
+ const lastFocusedRef = useRef5(null);
1101
+ const titleId = useId();
1102
+ useEffect5(() => setMounted(true), []);
1103
+ useEffect5(() => {
1104
+ if (!open) return;
1105
+ lastFocusedRef.current = document.activeElement;
1106
+ const onKey = (e) => {
1107
+ if (e.key === "Escape" && closeOnEsc) onClose();
1108
+ };
1109
+ document.addEventListener("keydown", onKey);
1110
+ const prevOverflow = document.body.style.overflow;
1111
+ document.body.style.overflow = "hidden";
1112
+ panelRef.current?.focus();
1113
+ return () => {
1114
+ document.removeEventListener("keydown", onKey);
1115
+ document.body.style.overflow = prevOverflow;
1116
+ lastFocusedRef.current?.focus?.();
1117
+ };
1118
+ }, [open, onClose, closeOnEsc]);
1119
+ const trapFocus = (e) => {
1120
+ if (e.key !== "Tab" || !panelRef.current) return;
1121
+ const focusable = Array.from(panelRef.current.querySelectorAll(FOCUSABLE)).filter(
1122
+ (el) => el.offsetParent !== null
1123
+ );
1124
+ if (focusable.length === 0) {
1125
+ e.preventDefault();
1126
+ return;
1127
+ }
1128
+ const first = focusable[0];
1129
+ const last = focusable[focusable.length - 1];
1130
+ const active = document.activeElement;
1131
+ if (e.shiftKey && (active === first || !panelRef.current.contains(active))) {
1132
+ e.preventDefault();
1133
+ last.focus();
1134
+ } else if (!e.shiftKey && active === last) {
1135
+ e.preventDefault();
1136
+ first.focus();
1137
+ }
1138
+ };
1139
+ if (!open || !mounted) return null;
1140
+ return createPortal(
1141
+ /* @__PURE__ */ jsx18(
1142
+ "div",
1143
+ {
1144
+ className: "mcl-modal-overlay",
1145
+ onMouseDown: (e) => {
1146
+ if (e.target === e.currentTarget && closeOnBackdrop) onClose();
1147
+ },
1148
+ children: /* @__PURE__ */ jsxs11(
1149
+ "div",
1150
+ {
1151
+ ref: panelRef,
1152
+ className: `mcl-modal mcl-modal-${size} ${className}`.trim(),
1153
+ role: "dialog",
1154
+ "aria-modal": "true",
1155
+ "aria-labelledby": title ? titleId : void 0,
1156
+ tabIndex: -1,
1157
+ onKeyDown: trapFocus,
1158
+ children: [
1159
+ /* @__PURE__ */ jsxs11("div", { className: "mcl-modal-header", children: [
1160
+ title ? /* @__PURE__ */ jsx18("h3", { id: titleId, className: "mcl-modal-title", children: title }) : /* @__PURE__ */ jsx18("span", {}),
1161
+ /* @__PURE__ */ jsx18("button", { type: "button", className: "mcl-modal-close", onClick: onClose, "aria-label": "Close dialog", children: "\u2715" })
1162
+ ] }),
1163
+ /* @__PURE__ */ jsx18("div", { className: "mcl-modal-body mcl-scroll", children }),
1164
+ footer && /* @__PURE__ */ jsx18("div", { className: "mcl-modal-footer", children: footer })
1165
+ ]
1166
+ }
1167
+ )
1168
+ }
1169
+ ),
1170
+ document.body
1171
+ );
1172
+ }
1173
+
1174
+ // src/components/tooltip/tooltip.tsx
1175
+ import { jsx as jsx19 } from "react/jsx-runtime";
1176
+ function Tooltip({ label, placement = "top", className = "", children }) {
1177
+ return /* @__PURE__ */ jsx19("span", { className: `mcl-tooltip mcl-tooltip-${placement} ${className}`.trim(), "data-mcl-tooltip": label, children });
1178
+ }
1179
+
1180
+ // src/components/tabs/tabs.tsx
1181
+ import { useId as useId2, useRef as useRef6, useState as useState7 } from "react";
1182
+ import { jsx as jsx20, jsxs as jsxs12 } from "react/jsx-runtime";
1183
+ function Tabs({ items, value, onChange, className = "" }) {
1184
+ const uid = useId2();
1185
+ const [inner, setInner] = useState7(0);
1186
+ const active = value ?? inner;
1187
+ const tabRefs = useRef6([]);
1188
+ const select = (i) => {
1189
+ if (items[i]?.disabled) return;
1190
+ setInner(i);
1191
+ onChange?.(i);
1192
+ };
1193
+ const onKeyDown = (e) => {
1194
+ const focusable = items.map((it, i) => it.disabled ? -1 : i).filter((i) => i >= 0);
1195
+ let next = -1;
1196
+ if (e.key === "ArrowRight") next = focusable[(focusable.indexOf(active) + 1) % focusable.length];
1197
+ if (e.key === "ArrowLeft") next = focusable[(focusable.indexOf(active) - 1 + focusable.length) % focusable.length];
1198
+ if (e.key === "Home") next = focusable[0];
1199
+ if (e.key === "End") next = focusable[focusable.length - 1];
1200
+ if (next >= 0 && next !== active) {
1201
+ e.preventDefault();
1202
+ select(next);
1203
+ tabRefs.current[next]?.focus();
1204
+ }
1205
+ };
1206
+ const current = items[active];
1207
+ return /* @__PURE__ */ jsxs12("div", { className: `mcl-tabs ${className}`.trim(), children: [
1208
+ /* @__PURE__ */ jsx20("div", { className: "mcl-tablist", role: "tablist", "aria-label": "Tabs", onKeyDown, children: items.map((it, i) => /* @__PURE__ */ jsx20(
1209
+ "button",
1210
+ {
1211
+ ref: (el) => {
1212
+ tabRefs.current[i] = el;
1213
+ },
1214
+ type: "button",
1215
+ role: "tab",
1216
+ id: `mcl-tab-${uid}-${it.key}`,
1217
+ "aria-selected": i === active,
1218
+ "aria-controls": `mcl-panel-${uid}-${it.key}`,
1219
+ tabIndex: i === active ? 0 : -1,
1220
+ className: `mcl-tab${i === active ? " mcl-tab-active" : ""}`,
1221
+ disabled: it.disabled,
1222
+ onClick: () => select(i),
1223
+ children: it.label
1224
+ },
1225
+ it.key
1226
+ )) }),
1227
+ current && /* @__PURE__ */ jsx20(
1228
+ "div",
1229
+ {
1230
+ role: "tabpanel",
1231
+ id: `mcl-panel-${uid}-${current.key}`,
1232
+ "aria-labelledby": `mcl-tab-${uid}-${current.key}`,
1233
+ className: "mcl-tabpanel",
1234
+ children: current.content
1235
+ },
1236
+ current.key
1237
+ )
1238
+ ] });
1239
+ }
1240
+
1241
+ // src/components/accordion/accordion.tsx
1242
+ import { useEffect as useEffect6, useId as useId3, useRef as useRef7, useState as useState8 } from "react";
1243
+ import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
1244
+ function Accordion({ items, defaultOpen = -1, exclusive = true, className = "" }) {
1245
+ const uid = useId3();
1246
+ const [openSet, setOpenSet] = useState8(
1247
+ () => new Set(defaultOpen >= 0 ? [defaultOpen] : [])
1248
+ );
1249
+ const panelRefs = useRef7([]);
1250
+ const [heights, setHeights] = useState8([]);
1251
+ const measure = () => {
1252
+ setHeights(items.map((_, i) => panelRefs.current[i]?.scrollHeight ?? 0));
1253
+ };
1254
+ useEffect6(() => {
1255
+ measure();
1256
+ window.addEventListener("resize", measure);
1257
+ return () => window.removeEventListener("resize", measure);
1258
+ }, [items, openSet]);
1259
+ const toggle = (i) => {
1260
+ if (!openSet.has(i)) {
1261
+ const h = panelRefs.current[i]?.scrollHeight ?? 0;
1262
+ setHeights((prev) => prev[i] === h ? prev : prev.map((x, j) => j === i ? h : x));
1263
+ }
1264
+ setOpenSet((prev) => {
1265
+ const next = new Set(exclusive ? [] : prev);
1266
+ if (prev.has(i)) next.delete(i);
1267
+ else next.add(i);
1268
+ return next;
1269
+ });
1270
+ };
1271
+ return /* @__PURE__ */ jsx21("div", { className: `mcl-accordion ${className}`.trim(), children: items.map((item, i) => {
1272
+ const open = openSet.has(i);
1273
+ return /* @__PURE__ */ jsxs13("div", { className: `mcl-accordion-item${open ? " mcl-accordion-item-open" : ""}`, children: [
1274
+ /* @__PURE__ */ jsxs13(
1275
+ "button",
1276
+ {
1277
+ type: "button",
1278
+ className: "mcl-accordion-header",
1279
+ "aria-expanded": open,
1280
+ "aria-controls": `mcl-acc-${uid}-${i}`,
1281
+ onClick: () => toggle(i),
1282
+ children: [
1283
+ /* @__PURE__ */ jsx21("span", { children: item.title }),
1284
+ /* @__PURE__ */ jsx21("span", { className: "mcl-accordion-chevron", "aria-hidden": "true", children: "\u25BE" })
1285
+ ]
1286
+ }
1287
+ ),
1288
+ /* @__PURE__ */ jsx21(
1289
+ "div",
1290
+ {
1291
+ id: `mcl-acc-${uid}-${i}`,
1292
+ ref: (el) => {
1293
+ panelRefs.current[i] = el;
1294
+ },
1295
+ className: "mcl-accordion-panel",
1296
+ style: { maxHeight: open ? heights[i] || 999 : 0 },
1297
+ role: "region",
1298
+ children: /* @__PURE__ */ jsx21("div", { className: "mcl-accordion-panel-inner", children: item.content })
1299
+ }
1300
+ )
1301
+ ] }, i);
1302
+ }) });
1303
+ }
1304
+
1305
+ // src/components/toast/toast.tsx
1306
+ import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useEffect as useEffect7, useRef as useRef8, useState as useState9 } from "react";
1307
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
1308
+ var ToastContext = createContext2(null);
1309
+ function ToastProvider({ children, placement = "bottom-right" }) {
1310
+ const [toasts, setToasts] = useState9([]);
1311
+ const idRef = useRef8(0);
1312
+ const timersRef = useRef8(/* @__PURE__ */ new Map());
1313
+ const dismiss = useCallback2((id) => {
1314
+ const timer = timersRef.current.get(id);
1315
+ if (timer !== void 0) {
1316
+ window.clearTimeout(timer);
1317
+ timersRef.current.delete(id);
1318
+ }
1319
+ setToasts((list) => list.filter((t) => t.id !== id));
1320
+ }, []);
1321
+ const toast = useCallback2(
1322
+ (o) => {
1323
+ const id = ++idRef.current;
1324
+ setToasts((list) => [...list, { ...o, id }].slice(-4));
1325
+ const d = o.duration ?? 4200;
1326
+ if (d > 0) {
1327
+ const timer = window.setTimeout(() => dismiss(id), d);
1328
+ timersRef.current.set(id, timer);
1329
+ }
1330
+ },
1331
+ [dismiss]
1332
+ );
1333
+ useEffect7(
1334
+ () => () => {
1335
+ timersRef.current.forEach((timer) => window.clearTimeout(timer));
1336
+ timersRef.current.clear();
1337
+ },
1338
+ []
1339
+ );
1340
+ return /* @__PURE__ */ jsxs14(ToastContext.Provider, { value: { toast, dismiss }, children: [
1341
+ children,
1342
+ /* @__PURE__ */ jsx22("div", { className: `mcl-toasts mcl-toasts-${placement}`, "aria-live": "polite", children: toasts.map((t) => /* @__PURE__ */ jsx22(ToastCard, { item: t, onClose: () => dismiss(t.id) }, t.id)) })
1343
+ ] });
1344
+ }
1345
+ function ToastCard({ item, onClose }) {
1346
+ return /* @__PURE__ */ jsxs14("div", { className: `mcl-toast mcl-toast-${item.variant ?? "info"}`, role: "status", children: [
1347
+ /* @__PURE__ */ jsx22("span", { className: "mcl-toast-dot", "aria-hidden": "true" }),
1348
+ /* @__PURE__ */ jsxs14("div", { className: "mcl-toast-content", children: [
1349
+ item.title && /* @__PURE__ */ jsx22("div", { className: "mcl-toast-title", children: item.title }),
1350
+ item.message && /* @__PURE__ */ jsx22("div", { className: "mcl-toast-message", children: item.message })
1351
+ ] }),
1352
+ /* @__PURE__ */ jsx22("button", { type: "button", className: "mcl-toast-close", onClick: onClose, "aria-label": "Dismiss notification", children: "\u2715" })
1353
+ ] });
1354
+ }
1355
+ function useToast() {
1356
+ const ctx = useContext2(ToastContext);
1357
+ if (!ctx) throw new Error("useToast must be used inside <ToastProvider>.");
1358
+ return ctx;
1359
+ }
1360
+
1361
+ // src/components/navbar/navbar.tsx
1362
+ import { useState as useState10 } from "react";
1363
+ import { jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
1364
+ function Navbar({ brand, links = [], activeHref, right, sticky = true, className = "" }) {
1365
+ const [open, setOpen] = useState10(false);
1366
+ return /* @__PURE__ */ jsx23("nav", { className: `mcl-navbar${sticky ? " mcl-navbar-sticky" : ""} ${className}`.trim(), children: /* @__PURE__ */ jsxs15("div", { className: "mcl-navbar-inner", children: [
1367
+ brand && /* @__PURE__ */ jsx23("a", { className: "mcl-navbar-brand", href: "/", children: brand }),
1368
+ links.length > 0 && /* @__PURE__ */ jsx23(
1369
+ "button",
1370
+ {
1371
+ type: "button",
1372
+ className: "mcl-navbar-toggle",
1373
+ "aria-expanded": open,
1374
+ "aria-label": "Toggle menu",
1375
+ onClick: () => setOpen((o) => !o),
1376
+ children: /* @__PURE__ */ jsx23("span", { className: "mcl-navbar-burger", "aria-hidden": "true" })
1377
+ }
1378
+ ),
1379
+ links.length > 0 && /* @__PURE__ */ jsx23("ul", { className: `mcl-navbar-links${open ? " mcl-navbar-open" : ""}`, children: links.map((l) => /* @__PURE__ */ jsx23("li", { children: /* @__PURE__ */ jsx23(
1380
+ "a",
1381
+ {
1382
+ href: l.href,
1383
+ className: `mcl-navbar-link${activeHref === l.href ? " mcl-navbar-link-active" : ""}`,
1384
+ onClick: () => setOpen(false),
1385
+ "aria-current": activeHref === l.href ? "page" : void 0,
1386
+ children: l.label
1387
+ }
1388
+ ) }, l.href)) }),
1389
+ right && /* @__PURE__ */ jsx23("div", { className: "mcl-navbar-right", children: right })
1390
+ ] }) });
1391
+ }
1392
+
1393
+ // src/components/stat/stat.tsx
1394
+ import { jsx as jsx24, jsxs as jsxs16 } from "react/jsx-runtime";
1395
+ function Stat({ value, label, tone = "primary", className = "" }) {
1396
+ return /* @__PURE__ */ jsxs16("div", { className: `mcl-stat mcl-stat-${tone} ${className}`.trim(), children: [
1397
+ /* @__PURE__ */ jsx24("b", { className: "mcl-stat-value", children: value }),
1398
+ label && /* @__PURE__ */ jsx24("span", { className: "mcl-stat-label", children: label })
1399
+ ] });
1400
+ }
1401
+
1402
+ // src/components/divider/divider.tsx
1403
+ import { jsx as jsx25 } from "react/jsx-runtime";
1404
+ function Divider({ orientation = "horizontal", label, className = "", ...rest }) {
1405
+ if (label !== void 0 && label !== null) {
1406
+ return /* @__PURE__ */ jsx25("div", { className: `mcl-divider-label ${className}`.trim(), role: "separator", ...rest, children: /* @__PURE__ */ jsx25("span", { className: "mcl-divider-label-text", children: label }) });
1407
+ }
1408
+ return /* @__PURE__ */ jsx25(
1409
+ "div",
1410
+ {
1411
+ className: `mcl-divider mcl-divider-${orientation} ${className}`.trim(),
1412
+ role: "separator",
1413
+ ...rest
1414
+ }
1415
+ );
1416
+ }
1417
+
1418
+ // src/components/stack/stack.tsx
1419
+ import { jsx as jsx26 } from "react/jsx-runtime";
1420
+ function Stack({
1421
+ direction = "column",
1422
+ gap = "md",
1423
+ wrap = false,
1424
+ align,
1425
+ justify,
1426
+ className = "",
1427
+ ...rest
1428
+ }) {
1429
+ const cls = [
1430
+ "mcl-stack",
1431
+ direction === "row" ? "mcl-stack-row" : "",
1432
+ `mcl-stack-gap-${gap}`,
1433
+ wrap ? "mcl-stack-wrap" : "",
1434
+ align ? `mcl-stack-align-${align}` : "",
1435
+ justify ? `mcl-stack-justify-${justify}` : "",
1436
+ className
1437
+ ].filter(Boolean).join(" ");
1438
+ return /* @__PURE__ */ jsx26("div", { className: cls, ...rest });
1439
+ }
1440
+ export {
1441
+ Accordion,
1442
+ Alert,
1443
+ Avatar,
1444
+ AvatarGroup,
1445
+ Badge,
1446
+ Button,
1447
+ ButtonGroup,
1448
+ COLOR_SWATCHES,
1449
+ Card,
1450
+ CardBody,
1451
+ CardFooter,
1452
+ CardHeader,
1453
+ CardTitle,
1454
+ Checkbox,
1455
+ CheckboxGroup,
1456
+ Divider,
1457
+ Dropdown,
1458
+ Input,
1459
+ Kbd,
1460
+ MarclayProvider,
1461
+ Modal,
1462
+ Navbar,
1463
+ PRESETS,
1464
+ Progress,
1465
+ Radio,
1466
+ Range,
1467
+ Select,
1468
+ Skeleton,
1469
+ Spinner,
1470
+ Stack,
1471
+ Stat,
1472
+ Switch,
1473
+ Tabs,
1474
+ Textarea,
1475
+ ThemeMenu,
1476
+ ThemeProvider,
1477
+ ThemeToggle,
1478
+ ToastProvider,
1479
+ Tooltip,
1480
+ useTheme,
1481
+ useToast
1482
+ };
1483
+ //# sourceMappingURL=index.js.map