@memelabui/ui 0.4.0 → 0.5.1

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 CHANGED
@@ -121,6 +121,7 @@ function App() {
121
121
  | `Tooltip` | Hover/focus tooltip with portal positioning |
122
122
  | `Dropdown` | Compound menu (Trigger, Menu, Item, Separator) |
123
123
  | `MutationOverlay` | Saving/saved/error status overlay for cards |
124
+ | `NotificationBell` | Notification bell button with unread count badge and ping animation |
124
125
 
125
126
  ### Layout
126
127
 
@@ -147,6 +148,9 @@ function App() {
147
148
  | `useDisclosure` | Open/close state management |
148
149
  | `useMediaQuery` | Responsive media query listener |
149
150
  | `useDebounce` | Debounced value |
151
+ | `useHotkeys` | Global keyboard shortcuts with modifier support |
152
+ | `useIntersectionObserver` | IntersectionObserver for infinite scroll and lazy loading |
153
+ | `useSharedNow` | Reactive current-time for countdowns and "X ago" labels |
150
154
 
151
155
  ## Customization
152
156
 
package/dist/index.cjs CHANGED
@@ -130,6 +130,82 @@ function useDebounce(value, delayMs = 300) {
130
130
  }, [value, delayMs]);
131
131
  return debouncedValue;
132
132
  }
133
+ function matchModifiers(e, mods) {
134
+ const ctrl = mods?.ctrl ?? false;
135
+ const shift = mods?.shift ?? false;
136
+ const alt = mods?.alt ?? false;
137
+ const meta = mods?.meta ?? false;
138
+ return e.ctrlKey === ctrl && e.shiftKey === shift && e.altKey === alt && e.metaKey === meta;
139
+ }
140
+ function useHotkeys(bindings, options = {}) {
141
+ const { enabled = true } = options;
142
+ React.useEffect(() => {
143
+ if (!enabled) return;
144
+ const onKeyDown = (e) => {
145
+ for (const binding of bindings) {
146
+ if (e.key === binding.key && matchModifiers(e, binding.modifiers)) {
147
+ binding.handler(e);
148
+ }
149
+ }
150
+ };
151
+ document.addEventListener("keydown", onKeyDown);
152
+ return () => document.removeEventListener("keydown", onKeyDown);
153
+ }, [enabled, ...bindings]);
154
+ }
155
+ function useIntersectionObserver(options = {}) {
156
+ const { root = null, rootMargin = "0px", threshold = 0, enabled = true } = options;
157
+ const [entry, setEntry] = React.useState(null);
158
+ const nodeRef = React.useRef(null);
159
+ const observerRef = React.useRef(null);
160
+ React.useEffect(() => {
161
+ if (!enabled) {
162
+ setEntry(null);
163
+ return;
164
+ }
165
+ observerRef.current = new IntersectionObserver(
166
+ ([e]) => setEntry(e),
167
+ { root, rootMargin, threshold }
168
+ );
169
+ if (nodeRef.current) {
170
+ observerRef.current.observe(nodeRef.current);
171
+ }
172
+ return () => {
173
+ observerRef.current?.disconnect();
174
+ observerRef.current = null;
175
+ };
176
+ }, [enabled, root, rootMargin, JSON.stringify(threshold)]);
177
+ const ref = (node) => {
178
+ if (nodeRef.current) {
179
+ observerRef.current?.unobserve(nodeRef.current);
180
+ }
181
+ nodeRef.current = node;
182
+ if (node) {
183
+ observerRef.current?.observe(node);
184
+ }
185
+ };
186
+ return {
187
+ ref,
188
+ entry,
189
+ isIntersecting: entry?.isIntersecting ?? false
190
+ };
191
+ }
192
+ function useSharedNow(options = {}) {
193
+ const { interval = 1e3, untilMs, enabled = true } = options;
194
+ const [now, setNow] = React.useState(Date.now);
195
+ React.useEffect(() => {
196
+ if (!enabled) return;
197
+ if (untilMs !== void 0 && Date.now() >= untilMs) return;
198
+ const id = setInterval(() => {
199
+ const current = Date.now();
200
+ setNow(current);
201
+ if (untilMs !== void 0 && current >= untilMs) {
202
+ clearInterval(id);
203
+ }
204
+ }, interval);
205
+ return () => clearInterval(id);
206
+ }, [interval, untilMs, enabled]);
207
+ return now;
208
+ }
133
209
 
134
210
  // src/tokens/colors.ts
135
211
  var colors = {
@@ -631,7 +707,7 @@ var Slider = React.forwardRef(function Slider2({
631
707
  if (numMax === numMin) return 0;
632
708
  return Math.max(0, Math.min(100, (numericValue - numMin) / (numMax - numMin) * 100));
633
709
  }, [numericValue, min, max]);
634
- const trackGradient = `linear-gradient(to right, var(--ml-primary, #8b5cf6) ${percentage}%, rgba(255,255,255,0.1) ${percentage}%)`;
710
+ const trackGradient = `linear-gradient(to right, rgb(var(--ml-primary, 139 92 246)) ${percentage}%, rgba(255,255,255,0.1) ${percentage}%)`;
635
711
  const displayValue = formatValue ? formatValue(numericValue) : String(numericValue);
636
712
  const handleChange = (e) => {
637
713
  onChange?.(Number(e.target.value));
@@ -663,7 +739,7 @@ var Slider = React.forwardRef(function Slider2({
663
739
  "[&::-webkit-slider-thumb]:rounded-full",
664
740
  "[&::-webkit-slider-thumb]:bg-white",
665
741
  "[&::-webkit-slider-thumb]:border-2",
666
- "[&::-webkit-slider-thumb]:border-[var(--ml-primary,#8b5cf6)]",
742
+ "[&::-webkit-slider-thumb]:border-[rgb(var(--ml-primary,139_92_246))]",
667
743
  "[&::-webkit-slider-thumb]:transition-shadow",
668
744
  "[&::-webkit-slider-thumb]:duration-150",
669
745
  // Moz thumb
@@ -672,7 +748,7 @@ var Slider = React.forwardRef(function Slider2({
672
748
  "[&::-moz-range-thumb]:rounded-full",
673
749
  "[&::-moz-range-thumb]:bg-white",
674
750
  "[&::-moz-range-thumb]:border-2",
675
- "[&::-moz-range-thumb]:border-[var(--ml-primary,#8b5cf6)]",
751
+ "[&::-moz-range-thumb]:border-[rgb(var(--ml-primary,139_92_246))]",
676
752
  "[&::-moz-range-thumb]:transition-shadow",
677
753
  "[&::-moz-range-thumb]:duration-150",
678
754
  // Moz track — transparent so the gradient on the element shows through
@@ -2382,7 +2458,7 @@ function StageProgress({ stages, activeStage, className }) {
2382
2458
  ) })
2383
2459
  ) : isActive ? (
2384
2460
  // Active: primary dot with ring
2385
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-3.5 w-3.5 items-center justify-center rounded-full ring-2 ring-[var(--ml-primary)] ring-offset-1 ring-offset-black/50", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-[var(--ml-primary)]" }) })
2461
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-3.5 w-3.5 items-center justify-center rounded-full ring-2 ring-[rgb(var(--ml-primary))] ring-offset-1 ring-offset-black/50", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-[rgb(var(--ml-primary))]" }) })
2386
2462
  ) : (
2387
2463
  // Upcoming: hollow dim dot
2388
2464
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "h-3.5 w-3.5 rounded-full border border-white/20 bg-transparent" })
@@ -2405,7 +2481,7 @@ function StageProgress({ stages, activeStage, className }) {
2405
2481
  children: /* @__PURE__ */ jsxRuntime.jsx(
2406
2482
  "div",
2407
2483
  {
2408
- className: "h-full rounded-full bg-gradient-to-r from-[var(--ml-accent)] to-[var(--ml-primary)] transition-[width] duration-500 ease-in-out",
2484
+ className: "h-full rounded-full bg-gradient-to-r from-[rgb(var(--ml-accent))] to-[rgb(var(--ml-primary))] transition-[width] duration-500 ease-in-out",
2409
2485
  style: { width: `${fillPercent}%` },
2410
2486
  children: isAnimating && /* @__PURE__ */ jsxRuntime.jsx(
2411
2487
  "span",
@@ -3330,6 +3406,76 @@ function MutationOverlay({
3330
3406
  }
3331
3407
  );
3332
3408
  }
3409
+ var sizeClass6 = {
3410
+ sm: "w-8 h-8",
3411
+ md: "w-10 h-10",
3412
+ lg: "w-12 h-12"
3413
+ };
3414
+ var iconSizeClass = {
3415
+ sm: "w-4 h-4",
3416
+ md: "w-5 h-5",
3417
+ lg: "w-6 h-6"
3418
+ };
3419
+ var badgeSizeClass = {
3420
+ sm: "min-w-[16px] h-4 text-[10px] px-1",
3421
+ md: "min-w-[18px] h-[18px] text-[11px] px-1",
3422
+ lg: "min-w-[20px] h-5 text-xs px-1.5"
3423
+ };
3424
+ var DefaultBellIcon = ({ className }) => /* @__PURE__ */ jsxRuntime.jsxs(
3425
+ "svg",
3426
+ {
3427
+ xmlns: "http://www.w3.org/2000/svg",
3428
+ viewBox: "0 0 24 24",
3429
+ fill: "none",
3430
+ stroke: "currentColor",
3431
+ strokeWidth: 2,
3432
+ strokeLinecap: "round",
3433
+ strokeLinejoin: "round",
3434
+ className,
3435
+ "aria-hidden": "true",
3436
+ children: [
3437
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }),
3438
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })
3439
+ ]
3440
+ }
3441
+ );
3442
+ var NotificationBell = React.forwardRef(
3443
+ function NotificationBell2({ icon, count, maxCount = 99, size = "md", ping, className, disabled, ...props }, ref) {
3444
+ const displayCount = count && count > maxCount ? `${maxCount}+` : count;
3445
+ const hasCount = count !== void 0 && count > 0;
3446
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3447
+ "button",
3448
+ {
3449
+ ref,
3450
+ type: "button",
3451
+ ...props,
3452
+ disabled,
3453
+ "aria-label": props["aria-label"] || `Notifications${hasCount ? ` (${count})` : ""}`,
3454
+ className: cn(
3455
+ "relative inline-flex items-center justify-center rounded-xl text-white/70 transition-colors hover:text-white hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent disabled:opacity-60 disabled:pointer-events-none",
3456
+ sizeClass6[size],
3457
+ className
3458
+ ),
3459
+ children: [
3460
+ icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: icon }) : /* @__PURE__ */ jsxRuntime.jsx(DefaultBellIcon, { className: iconSizeClass[size] }),
3461
+ hasCount && /* @__PURE__ */ jsxRuntime.jsxs(
3462
+ "span",
3463
+ {
3464
+ className: cn(
3465
+ "absolute top-0 right-0 flex items-center justify-center rounded-full bg-rose-500 text-white font-semibold leading-none",
3466
+ badgeSizeClass[size]
3467
+ ),
3468
+ children: [
3469
+ ping && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute inset-0 rounded-full bg-rose-500 animate-ping opacity-75" }),
3470
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative", children: displayCount })
3471
+ ]
3472
+ }
3473
+ )
3474
+ ]
3475
+ }
3476
+ );
3477
+ }
3478
+ );
3333
3479
 
3334
3480
  exports.ActiveFilterPills = ActiveFilterPills;
3335
3481
  exports.Alert = Alert;
@@ -3359,6 +3505,7 @@ exports.Input = Input;
3359
3505
  exports.Modal = Modal;
3360
3506
  exports.MutationOverlay = MutationOverlay;
3361
3507
  exports.Navbar = Navbar;
3508
+ exports.NotificationBell = NotificationBell;
3362
3509
  exports.PageShell = PageShell;
3363
3510
  exports.Pagination = Pagination;
3364
3511
  exports.Pill = Pill;
@@ -3398,5 +3545,8 @@ exports.getFocusableElements = getFocusableElements;
3398
3545
  exports.useClipboard = useClipboard;
3399
3546
  exports.useDebounce = useDebounce;
3400
3547
  exports.useDisclosure = useDisclosure;
3548
+ exports.useHotkeys = useHotkeys;
3549
+ exports.useIntersectionObserver = useIntersectionObserver;
3401
3550
  exports.useMediaQuery = useMediaQuery;
3551
+ exports.useSharedNow = useSharedNow;
3402
3552
  exports.useToast = useToast;
package/dist/index.d.cts CHANGED
@@ -31,6 +31,70 @@ declare function useMediaQuery(query: string): boolean;
31
31
 
32
32
  declare function useDebounce<T>(value: T, delayMs?: number): T;
33
33
 
34
+ type HotkeyModifiers = {
35
+ ctrl?: boolean;
36
+ shift?: boolean;
37
+ alt?: boolean;
38
+ meta?: boolean;
39
+ };
40
+ type HotkeyBinding = {
41
+ key: string;
42
+ modifiers?: HotkeyModifiers;
43
+ handler: (e: KeyboardEvent) => void;
44
+ };
45
+ type UseHotkeysOptions = {
46
+ enabled?: boolean;
47
+ };
48
+ /**
49
+ * Global keyboard shortcut hook.
50
+ *
51
+ * @example
52
+ * useHotkeys([
53
+ * { key: 'Escape', handler: () => close() },
54
+ * { key: 's', modifiers: { ctrl: true }, handler: (e) => { e.preventDefault(); save(); } },
55
+ * ]);
56
+ */
57
+ declare function useHotkeys(bindings: HotkeyBinding[], options?: UseHotkeysOptions): void;
58
+
59
+ type UseIntersectionObserverOptions = {
60
+ root?: Element | null;
61
+ rootMargin?: string;
62
+ threshold?: number | number[];
63
+ enabled?: boolean;
64
+ };
65
+ type UseIntersectionObserverReturn = {
66
+ ref: (node: Element | null) => void;
67
+ entry: IntersectionObserverEntry | null;
68
+ isIntersecting: boolean;
69
+ };
70
+ /**
71
+ * IntersectionObserver hook for infinite scroll, lazy loading, etc.
72
+ *
73
+ * @example
74
+ * const { ref, isIntersecting } = useIntersectionObserver({ rootMargin: '200px' });
75
+ * useEffect(() => { if (isIntersecting) loadMore(); }, [isIntersecting]);
76
+ * return <div ref={ref} />;
77
+ */
78
+ declare function useIntersectionObserver(options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn;
79
+
80
+ type UseSharedNowOptions = {
81
+ /** Tick interval in ms. Default: 1000 */
82
+ interval?: number;
83
+ /** Stop ticking after this timestamp (ms). Undefined = never stop. */
84
+ untilMs?: number;
85
+ /** Enable/disable. Default: true */
86
+ enabled?: boolean;
87
+ };
88
+ /**
89
+ * Reactive current-time hook that ticks on a shared interval.
90
+ * Useful for countdowns, "X minutes ago" labels, and CooldownRing.
91
+ *
92
+ * @example
93
+ * const now = useSharedNow({ interval: 1000 });
94
+ * const remaining = Math.max(0, deadline - now);
95
+ */
96
+ declare function useSharedNow(options?: UseSharedNowOptions): number;
97
+
34
98
  type Size = 'sm' | 'md' | 'lg';
35
99
 
36
100
  type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
@@ -630,4 +694,29 @@ type MutationOverlayProps = {
630
694
  };
631
695
  declare function MutationOverlay({ status, savingText, savedText, errorText, className, }: MutationOverlayProps): react_jsx_runtime.JSX.Element | null;
632
696
 
633
- export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutProps, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, type FilterPill, FormField, type FormFieldProps, IconButton, type IconButtonProps, Input, type InputProps, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, SearchInput, type SearchInputProps, SectionCard, type SectionCardProps, Select, type SelectProps, Sidebar, type SidebarProps, type Size, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, type SpinnerSize, StageProgress, type StageProgressProps, StatCard, type StatCardProps, type StatCardTrend, type Step, Stepper, type StepperProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableProps, TableRow, type TableRowProps, Tabs, type TabsProps, type TabsVariant, TagInput, type TagInputProps, Textarea, type TextareaProps, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, type UseClipboardReturn, type UseDisclosureReturn, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useMediaQuery, useToast };
697
+ type NotificationBellProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'> & {
698
+ /** Icon to display (bell SVG, etc.). If omitted, renders a default bell. */
699
+ icon?: ReactNode;
700
+ /** Unread count. 0 or undefined hides the badge. */
701
+ count?: number;
702
+ /** Max count to display before showing "N+". Default: 99 */
703
+ maxCount?: number;
704
+ /** Size variant */
705
+ size?: 'sm' | 'md' | 'lg';
706
+ /** Whether to show a ping animation on the badge */
707
+ ping?: boolean;
708
+ };
709
+ declare const NotificationBell: react.ForwardRefExoticComponent<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
710
+ /** Icon to display (bell SVG, etc.). If omitted, renders a default bell. */
711
+ icon?: ReactNode;
712
+ /** Unread count. 0 or undefined hides the badge. */
713
+ count?: number;
714
+ /** Max count to display before showing "N+". Default: 99 */
715
+ maxCount?: number;
716
+ /** Size variant */
717
+ size?: "sm" | "md" | "lg";
718
+ /** Whether to show a ping animation on the badge */
719
+ ping?: boolean;
720
+ } & react.RefAttributes<HTMLButtonElement>>;
721
+
722
+ export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutProps, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, type FilterPill, FormField, type FormFieldProps, type HotkeyBinding, type HotkeyModifiers, IconButton, type IconButtonProps, Input, type InputProps, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, NotificationBell, type NotificationBellProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, SearchInput, type SearchInputProps, SectionCard, type SectionCardProps, Select, type SelectProps, Sidebar, type SidebarProps, type Size, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, type SpinnerSize, StageProgress, type StageProgressProps, StatCard, type StatCardProps, type StatCardTrend, type Step, Stepper, type StepperProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableProps, TableRow, type TableRowProps, Tabs, type TabsProps, type TabsVariant, TagInput, type TagInputProps, Textarea, type TextareaProps, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, type UseClipboardReturn, type UseDisclosureReturn, type UseHotkeysOptions, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseSharedNowOptions, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useSharedNow, useToast };
package/dist/index.d.ts CHANGED
@@ -31,6 +31,70 @@ declare function useMediaQuery(query: string): boolean;
31
31
 
32
32
  declare function useDebounce<T>(value: T, delayMs?: number): T;
33
33
 
34
+ type HotkeyModifiers = {
35
+ ctrl?: boolean;
36
+ shift?: boolean;
37
+ alt?: boolean;
38
+ meta?: boolean;
39
+ };
40
+ type HotkeyBinding = {
41
+ key: string;
42
+ modifiers?: HotkeyModifiers;
43
+ handler: (e: KeyboardEvent) => void;
44
+ };
45
+ type UseHotkeysOptions = {
46
+ enabled?: boolean;
47
+ };
48
+ /**
49
+ * Global keyboard shortcut hook.
50
+ *
51
+ * @example
52
+ * useHotkeys([
53
+ * { key: 'Escape', handler: () => close() },
54
+ * { key: 's', modifiers: { ctrl: true }, handler: (e) => { e.preventDefault(); save(); } },
55
+ * ]);
56
+ */
57
+ declare function useHotkeys(bindings: HotkeyBinding[], options?: UseHotkeysOptions): void;
58
+
59
+ type UseIntersectionObserverOptions = {
60
+ root?: Element | null;
61
+ rootMargin?: string;
62
+ threshold?: number | number[];
63
+ enabled?: boolean;
64
+ };
65
+ type UseIntersectionObserverReturn = {
66
+ ref: (node: Element | null) => void;
67
+ entry: IntersectionObserverEntry | null;
68
+ isIntersecting: boolean;
69
+ };
70
+ /**
71
+ * IntersectionObserver hook for infinite scroll, lazy loading, etc.
72
+ *
73
+ * @example
74
+ * const { ref, isIntersecting } = useIntersectionObserver({ rootMargin: '200px' });
75
+ * useEffect(() => { if (isIntersecting) loadMore(); }, [isIntersecting]);
76
+ * return <div ref={ref} />;
77
+ */
78
+ declare function useIntersectionObserver(options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn;
79
+
80
+ type UseSharedNowOptions = {
81
+ /** Tick interval in ms. Default: 1000 */
82
+ interval?: number;
83
+ /** Stop ticking after this timestamp (ms). Undefined = never stop. */
84
+ untilMs?: number;
85
+ /** Enable/disable. Default: true */
86
+ enabled?: boolean;
87
+ };
88
+ /**
89
+ * Reactive current-time hook that ticks on a shared interval.
90
+ * Useful for countdowns, "X minutes ago" labels, and CooldownRing.
91
+ *
92
+ * @example
93
+ * const now = useSharedNow({ interval: 1000 });
94
+ * const remaining = Math.max(0, deadline - now);
95
+ */
96
+ declare function useSharedNow(options?: UseSharedNowOptions): number;
97
+
34
98
  type Size = 'sm' | 'md' | 'lg';
35
99
 
36
100
  type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
@@ -630,4 +694,29 @@ type MutationOverlayProps = {
630
694
  };
631
695
  declare function MutationOverlay({ status, savingText, savedText, errorText, className, }: MutationOverlayProps): react_jsx_runtime.JSX.Element | null;
632
696
 
633
- export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutProps, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, type FilterPill, FormField, type FormFieldProps, IconButton, type IconButtonProps, Input, type InputProps, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, SearchInput, type SearchInputProps, SectionCard, type SectionCardProps, Select, type SelectProps, Sidebar, type SidebarProps, type Size, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, type SpinnerSize, StageProgress, type StageProgressProps, StatCard, type StatCardProps, type StatCardTrend, type Step, Stepper, type StepperProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableProps, TableRow, type TableRowProps, Tabs, type TabsProps, type TabsVariant, TagInput, type TagInputProps, Textarea, type TextareaProps, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, type UseClipboardReturn, type UseDisclosureReturn, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useMediaQuery, useToast };
697
+ type NotificationBellProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children'> & {
698
+ /** Icon to display (bell SVG, etc.). If omitted, renders a default bell. */
699
+ icon?: ReactNode;
700
+ /** Unread count. 0 or undefined hides the badge. */
701
+ count?: number;
702
+ /** Max count to display before showing "N+". Default: 99 */
703
+ maxCount?: number;
704
+ /** Size variant */
705
+ size?: 'sm' | 'md' | 'lg';
706
+ /** Whether to show a ping animation on the badge */
707
+ ping?: boolean;
708
+ };
709
+ declare const NotificationBell: react.ForwardRefExoticComponent<Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
710
+ /** Icon to display (bell SVG, etc.). If omitted, renders a default bell. */
711
+ icon?: ReactNode;
712
+ /** Unread count. 0 or undefined hides the badge. */
713
+ count?: number;
714
+ /** Max count to display before showing "N+". Default: 99 */
715
+ maxCount?: number;
716
+ /** Size variant */
717
+ size?: "sm" | "md" | "lg";
718
+ /** Whether to show a ping animation on the badge */
719
+ ping?: boolean;
720
+ } & react.RefAttributes<HTMLButtonElement>>;
721
+
722
+ export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutProps, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, type FilterPill, FormField, type FormFieldProps, type HotkeyBinding, type HotkeyModifiers, IconButton, type IconButtonProps, Input, type InputProps, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, NotificationBell, type NotificationBellProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, SearchInput, type SearchInputProps, SectionCard, type SectionCardProps, Select, type SelectProps, Sidebar, type SidebarProps, type Size, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, type SpinnerSize, StageProgress, type StageProgressProps, StatCard, type StatCardProps, type StatCardTrend, type Step, Stepper, type StepperProps, Tab, TabList, type TabListProps, TabPanel, type TabPanelProps, type TabProps, Table, TableBody, type TableBodyProps, TableCell, type TableCellProps, TableHead, type TableHeadProps, TableHeader, type TableHeaderProps, type TableProps, TableRow, type TableRowProps, Tabs, type TabsProps, type TabsVariant, TagInput, type TagInputProps, Textarea, type TextareaProps, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, type UseClipboardReturn, type UseDisclosureReturn, type UseHotkeysOptions, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseSharedNowOptions, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useSharedNow, useToast };
package/dist/index.js CHANGED
@@ -124,6 +124,82 @@ function useDebounce(value, delayMs = 300) {
124
124
  }, [value, delayMs]);
125
125
  return debouncedValue;
126
126
  }
127
+ function matchModifiers(e, mods) {
128
+ const ctrl = mods?.ctrl ?? false;
129
+ const shift = mods?.shift ?? false;
130
+ const alt = mods?.alt ?? false;
131
+ const meta = mods?.meta ?? false;
132
+ return e.ctrlKey === ctrl && e.shiftKey === shift && e.altKey === alt && e.metaKey === meta;
133
+ }
134
+ function useHotkeys(bindings, options = {}) {
135
+ const { enabled = true } = options;
136
+ useEffect(() => {
137
+ if (!enabled) return;
138
+ const onKeyDown = (e) => {
139
+ for (const binding of bindings) {
140
+ if (e.key === binding.key && matchModifiers(e, binding.modifiers)) {
141
+ binding.handler(e);
142
+ }
143
+ }
144
+ };
145
+ document.addEventListener("keydown", onKeyDown);
146
+ return () => document.removeEventListener("keydown", onKeyDown);
147
+ }, [enabled, ...bindings]);
148
+ }
149
+ function useIntersectionObserver(options = {}) {
150
+ const { root = null, rootMargin = "0px", threshold = 0, enabled = true } = options;
151
+ const [entry, setEntry] = useState(null);
152
+ const nodeRef = useRef(null);
153
+ const observerRef = useRef(null);
154
+ useEffect(() => {
155
+ if (!enabled) {
156
+ setEntry(null);
157
+ return;
158
+ }
159
+ observerRef.current = new IntersectionObserver(
160
+ ([e]) => setEntry(e),
161
+ { root, rootMargin, threshold }
162
+ );
163
+ if (nodeRef.current) {
164
+ observerRef.current.observe(nodeRef.current);
165
+ }
166
+ return () => {
167
+ observerRef.current?.disconnect();
168
+ observerRef.current = null;
169
+ };
170
+ }, [enabled, root, rootMargin, JSON.stringify(threshold)]);
171
+ const ref = (node) => {
172
+ if (nodeRef.current) {
173
+ observerRef.current?.unobserve(nodeRef.current);
174
+ }
175
+ nodeRef.current = node;
176
+ if (node) {
177
+ observerRef.current?.observe(node);
178
+ }
179
+ };
180
+ return {
181
+ ref,
182
+ entry,
183
+ isIntersecting: entry?.isIntersecting ?? false
184
+ };
185
+ }
186
+ function useSharedNow(options = {}) {
187
+ const { interval = 1e3, untilMs, enabled = true } = options;
188
+ const [now, setNow] = useState(Date.now);
189
+ useEffect(() => {
190
+ if (!enabled) return;
191
+ if (untilMs !== void 0 && Date.now() >= untilMs) return;
192
+ const id = setInterval(() => {
193
+ const current = Date.now();
194
+ setNow(current);
195
+ if (untilMs !== void 0 && current >= untilMs) {
196
+ clearInterval(id);
197
+ }
198
+ }, interval);
199
+ return () => clearInterval(id);
200
+ }, [interval, untilMs, enabled]);
201
+ return now;
202
+ }
127
203
 
128
204
  // src/tokens/colors.ts
129
205
  var colors = {
@@ -625,7 +701,7 @@ var Slider = forwardRef(function Slider2({
625
701
  if (numMax === numMin) return 0;
626
702
  return Math.max(0, Math.min(100, (numericValue - numMin) / (numMax - numMin) * 100));
627
703
  }, [numericValue, min, max]);
628
- const trackGradient = `linear-gradient(to right, var(--ml-primary, #8b5cf6) ${percentage}%, rgba(255,255,255,0.1) ${percentage}%)`;
704
+ const trackGradient = `linear-gradient(to right, rgb(var(--ml-primary, 139 92 246)) ${percentage}%, rgba(255,255,255,0.1) ${percentage}%)`;
629
705
  const displayValue = formatValue ? formatValue(numericValue) : String(numericValue);
630
706
  const handleChange = (e) => {
631
707
  onChange?.(Number(e.target.value));
@@ -657,7 +733,7 @@ var Slider = forwardRef(function Slider2({
657
733
  "[&::-webkit-slider-thumb]:rounded-full",
658
734
  "[&::-webkit-slider-thumb]:bg-white",
659
735
  "[&::-webkit-slider-thumb]:border-2",
660
- "[&::-webkit-slider-thumb]:border-[var(--ml-primary,#8b5cf6)]",
736
+ "[&::-webkit-slider-thumb]:border-[rgb(var(--ml-primary,139_92_246))]",
661
737
  "[&::-webkit-slider-thumb]:transition-shadow",
662
738
  "[&::-webkit-slider-thumb]:duration-150",
663
739
  // Moz thumb
@@ -666,7 +742,7 @@ var Slider = forwardRef(function Slider2({
666
742
  "[&::-moz-range-thumb]:rounded-full",
667
743
  "[&::-moz-range-thumb]:bg-white",
668
744
  "[&::-moz-range-thumb]:border-2",
669
- "[&::-moz-range-thumb]:border-[var(--ml-primary,#8b5cf6)]",
745
+ "[&::-moz-range-thumb]:border-[rgb(var(--ml-primary,139_92_246))]",
670
746
  "[&::-moz-range-thumb]:transition-shadow",
671
747
  "[&::-moz-range-thumb]:duration-150",
672
748
  // Moz track — transparent so the gradient on the element shows through
@@ -2376,7 +2452,7 @@ function StageProgress({ stages, activeStage, className }) {
2376
2452
  ) })
2377
2453
  ) : isActive ? (
2378
2454
  // Active: primary dot with ring
2379
- /* @__PURE__ */ jsx("span", { className: "flex h-3.5 w-3.5 items-center justify-center rounded-full ring-2 ring-[var(--ml-primary)] ring-offset-1 ring-offset-black/50", children: /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-[var(--ml-primary)]" }) })
2455
+ /* @__PURE__ */ jsx("span", { className: "flex h-3.5 w-3.5 items-center justify-center rounded-full ring-2 ring-[rgb(var(--ml-primary))] ring-offset-1 ring-offset-black/50", children: /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-[rgb(var(--ml-primary))]" }) })
2380
2456
  ) : (
2381
2457
  // Upcoming: hollow dim dot
2382
2458
  /* @__PURE__ */ jsx("span", { className: "h-3.5 w-3.5 rounded-full border border-white/20 bg-transparent" })
@@ -2399,7 +2475,7 @@ function StageProgress({ stages, activeStage, className }) {
2399
2475
  children: /* @__PURE__ */ jsx(
2400
2476
  "div",
2401
2477
  {
2402
- className: "h-full rounded-full bg-gradient-to-r from-[var(--ml-accent)] to-[var(--ml-primary)] transition-[width] duration-500 ease-in-out",
2478
+ className: "h-full rounded-full bg-gradient-to-r from-[rgb(var(--ml-accent))] to-[rgb(var(--ml-primary))] transition-[width] duration-500 ease-in-out",
2403
2479
  style: { width: `${fillPercent}%` },
2404
2480
  children: isAnimating && /* @__PURE__ */ jsx(
2405
2481
  "span",
@@ -3324,5 +3400,75 @@ function MutationOverlay({
3324
3400
  }
3325
3401
  );
3326
3402
  }
3403
+ var sizeClass6 = {
3404
+ sm: "w-8 h-8",
3405
+ md: "w-10 h-10",
3406
+ lg: "w-12 h-12"
3407
+ };
3408
+ var iconSizeClass = {
3409
+ sm: "w-4 h-4",
3410
+ md: "w-5 h-5",
3411
+ lg: "w-6 h-6"
3412
+ };
3413
+ var badgeSizeClass = {
3414
+ sm: "min-w-[16px] h-4 text-[10px] px-1",
3415
+ md: "min-w-[18px] h-[18px] text-[11px] px-1",
3416
+ lg: "min-w-[20px] h-5 text-xs px-1.5"
3417
+ };
3418
+ var DefaultBellIcon = ({ className }) => /* @__PURE__ */ jsxs(
3419
+ "svg",
3420
+ {
3421
+ xmlns: "http://www.w3.org/2000/svg",
3422
+ viewBox: "0 0 24 24",
3423
+ fill: "none",
3424
+ stroke: "currentColor",
3425
+ strokeWidth: 2,
3426
+ strokeLinecap: "round",
3427
+ strokeLinejoin: "round",
3428
+ className,
3429
+ "aria-hidden": "true",
3430
+ children: [
3431
+ /* @__PURE__ */ jsx("path", { d: "M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" }),
3432
+ /* @__PURE__ */ jsx("path", { d: "M10.3 21a1.94 1.94 0 0 0 3.4 0" })
3433
+ ]
3434
+ }
3435
+ );
3436
+ var NotificationBell = forwardRef(
3437
+ function NotificationBell2({ icon, count, maxCount = 99, size = "md", ping, className, disabled, ...props }, ref) {
3438
+ const displayCount = count && count > maxCount ? `${maxCount}+` : count;
3439
+ const hasCount = count !== void 0 && count > 0;
3440
+ return /* @__PURE__ */ jsxs(
3441
+ "button",
3442
+ {
3443
+ ref,
3444
+ type: "button",
3445
+ ...props,
3446
+ disabled,
3447
+ "aria-label": props["aria-label"] || `Notifications${hasCount ? ` (${count})` : ""}`,
3448
+ className: cn(
3449
+ "relative inline-flex items-center justify-center rounded-xl text-white/70 transition-colors hover:text-white hover:bg-white/10 focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-transparent disabled:opacity-60 disabled:pointer-events-none",
3450
+ sizeClass6[size],
3451
+ className
3452
+ ),
3453
+ children: [
3454
+ icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: icon }) : /* @__PURE__ */ jsx(DefaultBellIcon, { className: iconSizeClass[size] }),
3455
+ hasCount && /* @__PURE__ */ jsxs(
3456
+ "span",
3457
+ {
3458
+ className: cn(
3459
+ "absolute top-0 right-0 flex items-center justify-center rounded-full bg-rose-500 text-white font-semibold leading-none",
3460
+ badgeSizeClass[size]
3461
+ ),
3462
+ children: [
3463
+ ping && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 rounded-full bg-rose-500 animate-ping opacity-75" }),
3464
+ /* @__PURE__ */ jsx("span", { className: "relative", children: displayCount })
3465
+ ]
3466
+ }
3467
+ )
3468
+ ]
3469
+ }
3470
+ );
3471
+ }
3472
+ );
3327
3473
 
3328
- export { ActiveFilterPills, Alert, Avatar, Badge, Button, Card, Checkbox, CollapsibleSection, ColorInput, ConfirmDialog, CooldownRing, CopyField, DashboardLayout, Divider, DotIndicator, DropZone, Dropdown, DropdownItem, DropdownMenu, DropdownSeparator, DropdownTrigger, EmptyState, FormField, IconButton, Input, Modal, MutationOverlay, Navbar, PageShell, Pagination, Pill, ProgressBar, ProgressButton, RadioGroup, RadioItem, SearchInput, SectionCard, Select, Sidebar, Skeleton, Slider, Spinner, StageProgress, StatCard, Stepper, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TagInput, Textarea, ToastProvider, Toggle, Tooltip, cn, colors, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useMediaQuery, useToast };
3474
+ export { ActiveFilterPills, Alert, Avatar, Badge, Button, Card, Checkbox, CollapsibleSection, ColorInput, ConfirmDialog, CooldownRing, CopyField, DashboardLayout, Divider, DotIndicator, DropZone, Dropdown, DropdownItem, DropdownMenu, DropdownSeparator, DropdownTrigger, EmptyState, FormField, IconButton, Input, Modal, MutationOverlay, Navbar, NotificationBell, PageShell, Pagination, Pill, ProgressBar, ProgressButton, RadioGroup, RadioItem, SearchInput, SectionCard, Select, Sidebar, Skeleton, Slider, Spinner, StageProgress, StatCard, Stepper, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TagInput, Textarea, ToastProvider, Toggle, Tooltip, cn, colors, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useSharedNow, useToast };
@@ -1,37 +1,38 @@
1
1
  'use strict';
2
2
 
3
3
  // src/preset/index.ts
4
+ var rgb = (cssVar) => `rgb(var(${cssVar}) / <alpha-value>)`;
4
5
  var memelabPreset = {
5
6
  darkMode: "class",
6
7
  theme: {
7
8
  extend: {
8
9
  colors: {
9
10
  surface: {
10
- DEFAULT: "var(--ml-bg)",
11
- 0: "var(--ml-surface-0)",
12
- 50: "var(--ml-surface-50)",
13
- 100: "var(--ml-surface-100)",
14
- 200: "var(--ml-surface-200)",
15
- 300: "var(--ml-surface-300)",
16
- 400: "var(--ml-surface-400)"
11
+ DEFAULT: rgb("--ml-bg"),
12
+ 0: rgb("--ml-surface-0"),
13
+ 50: rgb("--ml-surface-50"),
14
+ 100: rgb("--ml-surface-100"),
15
+ 200: rgb("--ml-surface-200"),
16
+ 300: rgb("--ml-surface-300"),
17
+ 400: rgb("--ml-surface-400")
17
18
  },
18
19
  primary: {
19
- DEFAULT: "var(--ml-primary)",
20
- light: "var(--ml-primary-light)",
21
- dark: "var(--ml-primary-dark)"
20
+ DEFAULT: rgb("--ml-primary"),
21
+ light: rgb("--ml-primary-light"),
22
+ dark: rgb("--ml-primary-dark")
22
23
  },
23
24
  accent: {
24
- DEFAULT: "var(--ml-accent)",
25
- light: "var(--ml-accent-light)",
26
- dark: "var(--ml-accent-dark)"
25
+ DEFAULT: rgb("--ml-accent"),
26
+ light: rgb("--ml-accent-light"),
27
+ dark: rgb("--ml-accent-dark")
27
28
  },
28
29
  glow: {
29
- purple: "var(--ml-glow-purple)",
30
- pink: "var(--ml-glow-pink)"
30
+ purple: rgb("--ml-glow-purple"),
31
+ pink: rgb("--ml-glow-pink")
31
32
  },
32
- success: "var(--ml-success)",
33
- warning: "var(--ml-warning)",
34
- danger: "var(--ml-danger)"
33
+ success: rgb("--ml-success"),
34
+ warning: rgb("--ml-warning"),
35
+ danger: rgb("--ml-danger")
35
36
  },
36
37
  fontFamily: {
37
38
  sans: ["var(--ml-font-sans)"]
@@ -1,35 +1,36 @@
1
1
  // src/preset/index.ts
2
+ var rgb = (cssVar) => `rgb(var(${cssVar}) / <alpha-value>)`;
2
3
  var memelabPreset = {
3
4
  darkMode: "class",
4
5
  theme: {
5
6
  extend: {
6
7
  colors: {
7
8
  surface: {
8
- DEFAULT: "var(--ml-bg)",
9
- 0: "var(--ml-surface-0)",
10
- 50: "var(--ml-surface-50)",
11
- 100: "var(--ml-surface-100)",
12
- 200: "var(--ml-surface-200)",
13
- 300: "var(--ml-surface-300)",
14
- 400: "var(--ml-surface-400)"
9
+ DEFAULT: rgb("--ml-bg"),
10
+ 0: rgb("--ml-surface-0"),
11
+ 50: rgb("--ml-surface-50"),
12
+ 100: rgb("--ml-surface-100"),
13
+ 200: rgb("--ml-surface-200"),
14
+ 300: rgb("--ml-surface-300"),
15
+ 400: rgb("--ml-surface-400")
15
16
  },
16
17
  primary: {
17
- DEFAULT: "var(--ml-primary)",
18
- light: "var(--ml-primary-light)",
19
- dark: "var(--ml-primary-dark)"
18
+ DEFAULT: rgb("--ml-primary"),
19
+ light: rgb("--ml-primary-light"),
20
+ dark: rgb("--ml-primary-dark")
20
21
  },
21
22
  accent: {
22
- DEFAULT: "var(--ml-accent)",
23
- light: "var(--ml-accent-light)",
24
- dark: "var(--ml-accent-dark)"
23
+ DEFAULT: rgb("--ml-accent"),
24
+ light: rgb("--ml-accent-light"),
25
+ dark: rgb("--ml-accent-dark")
25
26
  },
26
27
  glow: {
27
- purple: "var(--ml-glow-purple)",
28
- pink: "var(--ml-glow-pink)"
28
+ purple: rgb("--ml-glow-purple"),
29
+ pink: rgb("--ml-glow-pink")
29
30
  },
30
- success: "var(--ml-success)",
31
- warning: "var(--ml-warning)",
32
- danger: "var(--ml-danger)"
31
+ success: rgb("--ml-success"),
32
+ warning: rgb("--ml-warning"),
33
+ danger: rgb("--ml-danger")
33
34
  },
34
35
  fontFamily: {
35
36
  sans: ["var(--ml-font-sans)"]
@@ -1,15 +1,15 @@
1
1
  :root {
2
2
  /* ---- Background ---- */
3
- --ml-bg: #0a0a0f;
4
- --ml-fg: #f9fafb;
3
+ --ml-bg: 10 10 15;
4
+ --ml-fg: 249 250 251;
5
5
 
6
6
  /* ---- Surface scale ---- */
7
- --ml-surface-0: #0a0a0f;
8
- --ml-surface-50: #0f0f18;
9
- --ml-surface-100: #141420;
10
- --ml-surface-200: #1a1a2e;
11
- --ml-surface-300: #24243a;
12
- --ml-surface-400: #2a2a4a;
7
+ --ml-surface-0: 10 10 15;
8
+ --ml-surface-50: 15 15 24;
9
+ --ml-surface-100: 20 20 32;
10
+ --ml-surface-200: 26 26 46;
11
+ --ml-surface-300: 36 36 58;
12
+ --ml-surface-400: 42 42 74;
13
13
 
14
14
  /* ---- Glass ---- */
15
15
  --ml-glass-bg: rgba(255, 255, 255, 0.05);
@@ -19,23 +19,23 @@
19
19
  --ml-glass-blur: 16px;
20
20
 
21
21
  /* ---- Primary palette ---- */
22
- --ml-primary: #8b5cf6;
23
- --ml-primary-light: #a78bfa;
24
- --ml-primary-dark: #7c3aed;
22
+ --ml-primary: 139 92 246;
23
+ --ml-primary-light: 167 139 250;
24
+ --ml-primary-dark: 124 58 237;
25
25
 
26
26
  /* ---- Accent (gradient source) ---- */
27
- --ml-accent: #667eea;
28
- --ml-accent-light: #8b9cf7;
29
- --ml-accent-dark: #4c5fd4;
27
+ --ml-accent: 102 126 234;
28
+ --ml-accent-light: 139 156 247;
29
+ --ml-accent-dark: 76 95 212;
30
30
 
31
31
  /* ---- Glow ---- */
32
- --ml-glow-purple: #764ba2;
33
- --ml-glow-pink: #f093fb;
32
+ --ml-glow-purple: 118 75 162;
33
+ --ml-glow-pink: 240 147 251;
34
34
 
35
35
  /* ---- Semantic ---- */
36
- --ml-success: #10b981;
37
- --ml-warning: #f59e0b;
38
- --ml-danger: #f43f5e;
36
+ --ml-success: 16 185 129;
37
+ --ml-warning: 245 158 11;
38
+ --ml-danger: 244 63 94;
39
39
 
40
40
  /* ---- Typography ---- */
41
41
  --ml-font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
@@ -589,8 +589,8 @@ video {
589
589
  display: none;
590
590
  }
591
591
  body {
592
- background-color: var(--ml-bg, #0a0a0f);
593
- color: var(--ml-fg, #f9fafb);
592
+ background-color: rgb(var(--ml-bg, 10 10 15));
593
+ color: rgb(var(--ml-fg, 249 250 251));
594
594
  font-family: var(--ml-font-sans);
595
595
  -webkit-font-smoothing: antialiased;
596
596
  -moz-osx-font-smoothing: grayscale;
@@ -600,17 +600,17 @@ body {
600
600
  width: 6px;
601
601
  }
602
602
  ::-webkit-scrollbar-track {
603
- background: var(--ml-surface-50, #0f0f18);
603
+ background: rgb(var(--ml-surface-50, 15 15 24));
604
604
  }
605
605
  ::-webkit-scrollbar-thumb {
606
- background: var(--ml-surface-300, #24243a);
606
+ background: rgb(var(--ml-surface-300, 36 36 58));
607
607
  border-radius: 9999px;
608
608
  }
609
609
  /* Global focus-visible */
610
610
  :where(a, button, input, select, textarea, [role='button'], [role='menuitem'], [role='tab']):focus-visible {
611
611
  outline: none;
612
612
  box-shadow:
613
- 0 0 0 2px var(--ml-primary, #8b5cf6),
613
+ 0 0 0 2px rgb(var(--ml-primary, 139 92 246)),
614
614
  0 0 0 4px transparent;
615
615
  }
616
616
  /* Dark select */
@@ -619,8 +619,8 @@ select {
619
619
  }
620
620
  select option,
621
621
  select optgroup {
622
- background-color: var(--ml-surface-50, #0f0f18);
623
- color: var(--ml-fg, #f9fafb);
622
+ background-color: rgb(var(--ml-surface-50, 15 15 24));
623
+ color: rgb(var(--ml-fg, 249 250 251));
624
624
  }
625
625
  /* Accessibility: link underline offset */
626
626
  a {
@@ -691,9 +691,9 @@ a {
691
691
  .animated-gradient {
692
692
  background: linear-gradient(
693
693
  270deg,
694
- var(--ml-accent, #667eea),
695
- var(--ml-glow-purple, #764ba2),
696
- var(--ml-glow-pink, #f093fb)
694
+ rgb(var(--ml-accent, 102 126 234)),
695
+ rgb(var(--ml-glow-purple, 118 75 162)),
696
+ rgb(var(--ml-glow-pink, 240 147 251))
697
697
  );
698
698
  background-size: 400% 400%;
699
699
  animation: ml-gradient 15s ease infinite;
@@ -708,10 +708,10 @@ a {
708
708
  pointer-events: none;
709
709
  }
710
710
  .orb-purple {
711
- background: var(--ml-glow-purple, #764ba2);
711
+ background: rgb(var(--ml-glow-purple, 118 75 162));
712
712
  }
713
713
  .orb-blue {
714
- background: var(--ml-accent, #667eea);
714
+ background: rgb(var(--ml-accent, 102 126 234));
715
715
  }
716
716
  /* iOS safe-area */
717
717
  .pb-safe {
@@ -793,6 +793,9 @@ a {
793
793
  .left-3 {
794
794
  left: 0.75rem;
795
795
  }
796
+ .right-0 {
797
+ right: 0px;
798
+ }
796
799
  .right-2 {
797
800
  right: 0.5rem;
798
801
  }
@@ -993,6 +996,9 @@ a {
993
996
  .h-9 {
994
997
  height: 2.25rem;
995
998
  }
999
+ .h-\[18px\] {
1000
+ height: 18px;
1001
+ }
996
1002
  .h-\[2px\] {
997
1003
  height: 2px;
998
1004
  }
@@ -1123,6 +1129,15 @@ a {
1123
1129
  .min-w-\[120px\] {
1124
1130
  min-width: 120px;
1125
1131
  }
1132
+ .min-w-\[16px\] {
1133
+ min-width: 16px;
1134
+ }
1135
+ .min-w-\[18px\] {
1136
+ min-width: 18px;
1137
+ }
1138
+ .min-w-\[20px\] {
1139
+ min-width: 20px;
1140
+ }
1126
1141
  .max-w-2xl {
1127
1142
  max-width: 42rem;
1128
1143
  }
@@ -1257,6 +1272,15 @@ a {
1257
1272
  .animate-modal-pop {
1258
1273
  animation: ml-modal-pop 160ms cubic-bezier(0.22,1,0.36,1) both;
1259
1274
  }
1275
+ @keyframes ping {
1276
+ 75%, 100% {
1277
+ transform: scale(2);
1278
+ opacity: 0;
1279
+ }
1280
+ }
1281
+ .animate-ping {
1282
+ animation: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
1283
+ }
1260
1284
  @keyframes pulse {
1261
1285
  50% {
1262
1286
  opacity: .5;
@@ -1477,7 +1501,11 @@ a {
1477
1501
  border-color: rgb(16 185 129 / var(--tw-border-opacity, 1));
1478
1502
  }
1479
1503
  .border-primary {
1480
- border-color: var(--ml-primary);
1504
+ --tw-border-opacity: 1;
1505
+ border-color: rgb(var(--ml-primary) / var(--tw-border-opacity, 1));
1506
+ }
1507
+ .border-primary\/30 {
1508
+ border-color: rgb(var(--ml-primary) / 0.3);
1481
1509
  }
1482
1510
  .border-rose-500 {
1483
1511
  --tw-border-opacity: 1;
@@ -1502,14 +1530,21 @@ a {
1502
1530
  border-color: rgb(255 255 255 / 0.06);
1503
1531
  }
1504
1532
  .border-t-primary {
1505
- border-top-color: var(--ml-primary);
1533
+ --tw-border-opacity: 1;
1534
+ border-top-color: rgb(var(--ml-primary) / var(--tw-border-opacity, 1));
1506
1535
  }
1507
1536
  .border-t-white {
1508
1537
  --tw-border-opacity: 1;
1509
1538
  border-top-color: rgb(255 255 255 / var(--tw-border-opacity, 1));
1510
1539
  }
1511
- .bg-\[var\(--ml-primary\)\] {
1512
- background-color: var(--ml-primary);
1540
+ .bg-\[rgb\(var\(--ml-primary\)\)\] {
1541
+ background-color: rgb(var(--ml-primary));
1542
+ }
1543
+ .bg-accent\/15 {
1544
+ background-color: rgb(var(--ml-accent) / 0.15);
1545
+ }
1546
+ .bg-accent\/\[0\.10\] {
1547
+ background-color: rgb(var(--ml-accent) / 0.10);
1513
1548
  }
1514
1549
  .bg-amber-400 {
1515
1550
  --tw-bg-opacity: 1;
@@ -1554,7 +1589,17 @@ a {
1554
1589
  background-color: rgb(5 150 105 / var(--tw-bg-opacity, 1));
1555
1590
  }
1556
1591
  .bg-primary {
1557
- background-color: var(--ml-primary);
1592
+ --tw-bg-opacity: 1;
1593
+ background-color: rgb(var(--ml-primary) / var(--tw-bg-opacity, 1));
1594
+ }
1595
+ .bg-primary\/10 {
1596
+ background-color: rgb(var(--ml-primary) / 0.1);
1597
+ }
1598
+ .bg-primary\/15 {
1599
+ background-color: rgb(var(--ml-primary) / 0.15);
1600
+ }
1601
+ .bg-primary\/20 {
1602
+ background-color: rgb(var(--ml-primary) / 0.2);
1558
1603
  }
1559
1604
  .bg-purple-500\/\[0\.12\] {
1560
1605
  background-color: rgb(168 85 247 / 0.12);
@@ -1578,13 +1623,25 @@ a {
1578
1623
  background-color: rgb(225 29 72 / var(--tw-bg-opacity, 1));
1579
1624
  }
1580
1625
  .bg-surface {
1581
- background-color: var(--ml-bg);
1626
+ --tw-bg-opacity: 1;
1627
+ background-color: rgb(var(--ml-bg) / var(--tw-bg-opacity, 1));
1582
1628
  }
1583
1629
  .bg-surface-100 {
1584
- background-color: var(--ml-surface-100);
1630
+ --tw-bg-opacity: 1;
1631
+ background-color: rgb(var(--ml-surface-100) / var(--tw-bg-opacity, 1));
1632
+ }
1633
+ .bg-surface-100\/95 {
1634
+ background-color: rgb(var(--ml-surface-100) / 0.95);
1585
1635
  }
1586
1636
  .bg-surface-300 {
1587
- background-color: var(--ml-surface-300);
1637
+ --tw-bg-opacity: 1;
1638
+ background-color: rgb(var(--ml-surface-300) / var(--tw-bg-opacity, 1));
1639
+ }
1640
+ .bg-surface-50\/80 {
1641
+ background-color: rgb(var(--ml-surface-50) / 0.8);
1642
+ }
1643
+ .bg-surface-50\/90 {
1644
+ background-color: rgb(var(--ml-surface-50) / 0.9);
1588
1645
  }
1589
1646
  .bg-transparent {
1590
1647
  background-color: transparent;
@@ -1615,14 +1672,14 @@ a {
1615
1672
  .bg-gradient-to-r {
1616
1673
  background-image: linear-gradient(to right, var(--tw-gradient-stops));
1617
1674
  }
1618
- .from-\[var\(--ml-accent\)\] {
1619
- --tw-gradient-from: var(--ml-accent) var(--tw-gradient-from-position);
1620
- --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
1675
+ .from-\[rgb\(var\(--ml-accent\)\)\] {
1676
+ --tw-gradient-from: rgb(var(--ml-accent)) var(--tw-gradient-from-position);
1677
+ --tw-gradient-to: rgb(var(--ml-accent) / 0) var(--tw-gradient-to-position);
1621
1678
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
1622
1679
  }
1623
1680
  .from-primary {
1624
- --tw-gradient-from: var(--ml-primary) var(--tw-gradient-from-position);
1625
- --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
1681
+ --tw-gradient-from: rgb(var(--ml-primary) / 1) var(--tw-gradient-from-position);
1682
+ --tw-gradient-to: rgb(var(--ml-primary) / 0) var(--tw-gradient-to-position);
1626
1683
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
1627
1684
  }
1628
1685
  .from-transparent {
@@ -1639,11 +1696,11 @@ a {
1639
1696
  --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
1640
1697
  --tw-gradient-stops: var(--tw-gradient-from), rgb(255 255 255 / 0.3) var(--tw-gradient-via-position), var(--tw-gradient-to);
1641
1698
  }
1642
- .to-\[var\(--ml-primary\)\] {
1643
- --tw-gradient-to: var(--ml-primary) var(--tw-gradient-to-position);
1699
+ .to-\[rgb\(var\(--ml-primary\)\)\] {
1700
+ --tw-gradient-to: rgb(var(--ml-primary)) var(--tw-gradient-to-position);
1644
1701
  }
1645
1702
  .to-accent {
1646
- --tw-gradient-to: var(--ml-accent) var(--tw-gradient-to-position);
1703
+ --tw-gradient-to: rgb(var(--ml-accent) / 1) var(--tw-gradient-to-position);
1647
1704
  }
1648
1705
  .to-purple-600 {
1649
1706
  --tw-gradient-to: #9333ea var(--tw-gradient-to-position);
@@ -1676,6 +1733,14 @@ a {
1676
1733
  .p-6 {
1677
1734
  padding: 1.5rem;
1678
1735
  }
1736
+ .px-1 {
1737
+ padding-left: 0.25rem;
1738
+ padding-right: 0.25rem;
1739
+ }
1740
+ .px-1\.5 {
1741
+ padding-left: 0.375rem;
1742
+ padding-right: 0.375rem;
1743
+ }
1679
1744
  .px-2 {
1680
1745
  padding-left: 0.5rem;
1681
1746
  padding-right: 0.5rem;
@@ -1782,6 +1847,9 @@ a {
1782
1847
  font-size: 1.875rem;
1783
1848
  line-height: 2.25rem;
1784
1849
  }
1850
+ .text-\[10px\] {
1851
+ font-size: 10px;
1852
+ }
1785
1853
  .text-\[11px\] {
1786
1854
  font-size: 11px;
1787
1855
  }
@@ -1834,7 +1902,8 @@ a {
1834
1902
  letter-spacing: 0.05em;
1835
1903
  }
1836
1904
  .text-accent-light {
1837
- color: var(--ml-accent-light);
1905
+ --tw-text-opacity: 1;
1906
+ color: rgb(var(--ml-accent-light) / var(--tw-text-opacity, 1));
1838
1907
  }
1839
1908
  .text-amber-400 {
1840
1909
  --tw-text-opacity: 1;
@@ -1857,7 +1926,8 @@ a {
1857
1926
  color: rgb(74 222 128 / var(--tw-text-opacity, 1));
1858
1927
  }
1859
1928
  .text-primary {
1860
- color: var(--ml-primary);
1929
+ --tw-text-opacity: 1;
1930
+ color: rgb(var(--ml-primary) / var(--tw-text-opacity, 1));
1861
1931
  }
1862
1932
  .text-red-400 {
1863
1933
  --tw-text-opacity: 1;
@@ -2003,8 +2073,11 @@ a {
2003
2073
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2004
2074
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2005
2075
  }
2006
- .ring-\[var\(--ml-primary\)\] {
2007
- --tw-ring-color: var(--ml-primary);
2076
+ .ring-\[rgb\(var\(--ml-primary\)\)\] {
2077
+ --tw-ring-color: rgb(var(--ml-primary));
2078
+ }
2079
+ .ring-accent\/20 {
2080
+ --tw-ring-color: rgb(var(--ml-accent) / 0.2);
2008
2081
  }
2009
2082
  .ring-amber-500\/20 {
2010
2083
  --tw-ring-color: rgb(245 158 11 / 0.2);
@@ -2012,6 +2085,12 @@ a {
2012
2085
  .ring-emerald-500\/20 {
2013
2086
  --tw-ring-color: rgb(16 185 129 / 0.2);
2014
2087
  }
2088
+ .ring-primary\/20 {
2089
+ --tw-ring-color: rgb(var(--ml-primary) / 0.2);
2090
+ }
2091
+ .ring-primary\/40 {
2092
+ --tw-ring-color: rgb(var(--ml-primary) / 0.4);
2093
+ }
2015
2094
  .ring-rose-500\/20 {
2016
2095
  --tw-ring-color: rgb(244 63 94 / 0.2);
2017
2096
  }
@@ -2143,6 +2222,9 @@ a {
2143
2222
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2144
2223
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2145
2224
  }
2225
+ .focus-within\:ring-primary\/40:focus-within {
2226
+ --tw-ring-color: rgb(var(--ml-primary) / 0.4);
2227
+ }
2146
2228
  .focus-within\:ring-rose-500\/40:focus-within {
2147
2229
  --tw-ring-color: rgb(244 63 94 / 0.4);
2148
2230
  }
@@ -2156,6 +2238,9 @@ a {
2156
2238
  --tw-scale-y: 1.02;
2157
2239
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
2158
2240
  }
2241
+ .hover\:border-primary\/50:hover {
2242
+ border-color: rgb(var(--ml-primary) / 0.5);
2243
+ }
2159
2244
  .hover\:border-transparent:hover {
2160
2245
  border-color: transparent;
2161
2246
  }
@@ -2254,7 +2339,14 @@ a {
2254
2339
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2255
2340
  }
2256
2341
  .focus-visible\:ring-primary:focus-visible {
2257
- --tw-ring-color: var(--ml-primary);
2342
+ --tw-ring-opacity: 1;
2343
+ --tw-ring-color: rgb(var(--ml-primary) / var(--tw-ring-opacity, 1));
2344
+ }
2345
+ .focus-visible\:ring-primary\/40:focus-visible {
2346
+ --tw-ring-color: rgb(var(--ml-primary) / 0.4);
2347
+ }
2348
+ .focus-visible\:ring-primary\/60:focus-visible {
2349
+ --tw-ring-color: rgb(var(--ml-primary) / 0.6);
2258
2350
  }
2259
2351
  .focus-visible\:ring-offset-1:focus-visible {
2260
2352
  --tw-ring-offset-width: 1px;
@@ -2263,7 +2355,7 @@ a {
2263
2355
  --tw-ring-offset-width: 2px;
2264
2356
  }
2265
2357
  .focus-visible\:ring-offset-surface:focus-visible {
2266
- --tw-ring-offset-color: var(--ml-bg);
2358
+ --tw-ring-offset-color: rgb(var(--ml-bg) / 1);
2267
2359
  }
2268
2360
  .focus-visible\:ring-offset-transparent:focus-visible {
2269
2361
  --tw-ring-offset-color: transparent;
@@ -2291,6 +2383,9 @@ a {
2291
2383
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2292
2384
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2293
2385
  }
2386
+ .peer:focus-visible ~ .peer-focus-visible\:ring-primary\/40 {
2387
+ --tw-ring-color: rgb(var(--ml-primary) / 0.4);
2388
+ }
2294
2389
  .peer:focus-visible ~ .peer-focus-visible\:ring-offset-1 {
2295
2390
  --tw-ring-offset-width: 1px;
2296
2391
  }
@@ -2358,8 +2453,8 @@ a {
2358
2453
  .\[\&\:\:-moz-range-thumb\]\:border-2::-moz-range-thumb {
2359
2454
  border-width: 2px;
2360
2455
  }
2361
- .\[\&\:\:-moz-range-thumb\]\:border-\[var\(--ml-primary\2c \#8b5cf6\)\]::-moz-range-thumb {
2362
- border-color: var(--ml-primary,#8b5cf6);
2456
+ .\[\&\:\:-moz-range-thumb\]\:border-\[rgb\(var\(--ml-primary\2c 139_92_246\)\)\]::-moz-range-thumb {
2457
+ border-color: rgb(var(--ml-primary,139 92 246));
2363
2458
  }
2364
2459
  .\[\&\:\:-moz-range-thumb\]\:bg-white::-moz-range-thumb {
2365
2460
  --tw-bg-opacity: 1;
@@ -2379,6 +2474,9 @@ a {
2379
2474
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2380
2475
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2381
2476
  }
2477
+ .focus-visible\:\[\&\:\:-moz-range-thumb\]\:ring-primary\/40::-moz-range-thumb:focus-visible {
2478
+ --tw-ring-color: rgb(var(--ml-primary) / 0.4);
2479
+ }
2382
2480
  .\[\&\:\:-moz-range-track\]\:rounded-full::-moz-range-track {
2383
2481
  border-radius: 9999px;
2384
2482
  }
@@ -2401,8 +2499,8 @@ a {
2401
2499
  .\[\&\:\:-webkit-slider-thumb\]\:border-2::-webkit-slider-thumb {
2402
2500
  border-width: 2px;
2403
2501
  }
2404
- .\[\&\:\:-webkit-slider-thumb\]\:border-\[var\(--ml-primary\2c \#8b5cf6\)\]::-webkit-slider-thumb {
2405
- border-color: var(--ml-primary,#8b5cf6);
2502
+ .\[\&\:\:-webkit-slider-thumb\]\:border-\[rgb\(var\(--ml-primary\2c 139_92_246\)\)\]::-webkit-slider-thumb {
2503
+ border-color: rgb(var(--ml-primary,139 92 246));
2406
2504
  }
2407
2505
  .\[\&\:\:-webkit-slider-thumb\]\:bg-white::-webkit-slider-thumb {
2408
2506
  --tw-bg-opacity: 1;
@@ -2422,9 +2520,12 @@ a {
2422
2520
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
2423
2521
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
2424
2522
  }
2523
+ .focus-visible\:\[\&\:\:-webkit-slider-thumb\]\:ring-primary\/40::-webkit-slider-thumb:focus-visible {
2524
+ --tw-ring-color: rgb(var(--ml-primary) / 0.4);
2525
+ }
2425
2526
  .focus-visible\:\[\&\:\:-webkit-slider-thumb\]\:ring-offset-2::-webkit-slider-thumb:focus-visible {
2426
2527
  --tw-ring-offset-width: 2px;
2427
2528
  }
2428
2529
  .focus-visible\:\[\&\:\:-webkit-slider-thumb\]\:ring-offset-surface::-webkit-slider-thumb:focus-visible {
2429
- --tw-ring-offset-color: var(--ml-bg);
2530
+ --tw-ring-offset-color: rgb(var(--ml-bg) / 1);
2430
2531
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memelabui/ui",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "MemeLab shared UI component library — React + Tailwind + Glassmorphism",
5
5
  "type": "module",
6
6
  "sideEffects": [