@memelabui/ui 0.7.0 → 0.8.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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, useState, useId, useMemo, useRef, useEffect, createContext, useCallback, isValidElement, cloneElement, useReducer, useContext } from 'react';
1
+ import React, { forwardRef, useState, useId, useRef, useMemo, useEffect, createContext, useCallback, isValidElement, cloneElement, useReducer, useContext, Component } from 'react';
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { createPortal } from 'react-dom';
4
4
 
@@ -133,10 +133,12 @@ function matchModifiers(e, mods) {
133
133
  }
134
134
  function useHotkeys(bindings, options = {}) {
135
135
  const { enabled = true } = options;
136
+ const bindingsRef = useRef(bindings);
137
+ bindingsRef.current = bindings;
136
138
  useEffect(() => {
137
139
  if (!enabled) return;
138
140
  const onKeyDown = (e) => {
139
- for (const binding of bindings) {
141
+ for (const binding of bindingsRef.current) {
140
142
  if (e.key === binding.key && matchModifiers(e, binding.modifiers)) {
141
143
  binding.handler(e);
142
144
  }
@@ -144,7 +146,7 @@ function useHotkeys(bindings, options = {}) {
144
146
  };
145
147
  document.addEventListener("keydown", onKeyDown);
146
148
  return () => document.removeEventListener("keydown", onKeyDown);
147
- }, [enabled, ...bindings]);
149
+ }, [enabled]);
148
150
  }
149
151
  function useIntersectionObserver(options = {}) {
150
152
  const { root = null, rootMargin = "0px", threshold = 0, enabled = true } = options;
@@ -200,6 +202,30 @@ function useSharedNow(options = {}) {
200
202
  }, [interval, untilMs, enabled]);
201
203
  return now;
202
204
  }
205
+ var lockCount = 0;
206
+ var savedOverflow = "";
207
+ function lockScroll() {
208
+ if (typeof document === "undefined") return;
209
+ if (lockCount === 0) {
210
+ savedOverflow = document.body.style.overflow;
211
+ document.body.style.overflow = "hidden";
212
+ }
213
+ lockCount++;
214
+ }
215
+ function unlockScroll() {
216
+ if (typeof document === "undefined") return;
217
+ lockCount = Math.max(0, lockCount - 1);
218
+ if (lockCount === 0) {
219
+ document.body.style.overflow = savedOverflow;
220
+ }
221
+ }
222
+ function useScrollLock(active) {
223
+ useEffect(() => {
224
+ if (!active) return;
225
+ lockScroll();
226
+ return () => unlockScroll();
227
+ }, [active]);
228
+ }
203
229
 
204
230
  // src/tokens/colors.ts
205
231
  var colors = {
@@ -481,126 +507,129 @@ var Textarea = forwardRef(function Textarea2({ hasError, label, error, helperTex
481
507
  helperText && !error && /* @__PURE__ */ jsx("p", { id: helperId, className: "text-white/40 text-xs mt-1", children: helperText })
482
508
  ] });
483
509
  });
484
- function TagInput({
485
- value,
486
- onChange,
487
- placeholder,
488
- disabled = false,
489
- label,
490
- error,
491
- maxTags,
492
- className,
493
- id: externalId
494
- }) {
495
- const generatedId = useId();
496
- const inputId = externalId ?? generatedId;
497
- const errorId = error ? `${inputId}-error` : void 0;
498
- const inputRef = useRef(null);
499
- const atMax = maxTags !== void 0 && value.length >= maxTags;
500
- function addTag(raw) {
501
- const tag = raw.trim();
502
- if (!tag || value.includes(tag) || atMax) return;
503
- onChange([...value, tag]);
504
- }
505
- function removeTag(index) {
506
- onChange(value.filter((_, i) => i !== index));
507
- }
508
- function handleKeyDown(e) {
509
- const input = inputRef.current;
510
- if (!input) return;
511
- if (e.key === "Enter" || e.key === "," || e.key === "Tab") {
512
- if (input.value) {
513
- e.preventDefault();
514
- addTag(input.value);
515
- input.value = "";
516
- }
517
- return;
510
+ var TagInput = forwardRef(
511
+ function TagInput2({
512
+ value,
513
+ onChange,
514
+ placeholder,
515
+ disabled = false,
516
+ label,
517
+ error,
518
+ maxTags,
519
+ className,
520
+ id: externalId
521
+ }, ref) {
522
+ const generatedId = useId();
523
+ const inputId = externalId ?? generatedId;
524
+ const errorId = error ? `${inputId}-error` : void 0;
525
+ const internalRef = useRef(null);
526
+ const inputRef = ref ?? internalRef;
527
+ const atMax = maxTags !== void 0 && value.length >= maxTags;
528
+ function addTag(raw) {
529
+ const tag = raw.trim();
530
+ if (!tag || value.includes(tag) || atMax) return;
531
+ onChange([...value, tag]);
518
532
  }
519
- if (e.key === "Backspace" && !input.value && value.length > 0) {
520
- removeTag(value.length - 1);
533
+ function removeTag(index) {
534
+ onChange(value.filter((_, i) => i !== index));
521
535
  }
522
- }
523
- function handlePaste(e) {
524
- const pasted = e.clipboardData.getData("text");
525
- if (!pasted.includes(",")) return;
526
- e.preventDefault();
527
- const parts = pasted.split(",");
528
- const next = [...value];
529
- for (const part of parts) {
530
- const tag = part.trim();
531
- if (tag && !next.includes(tag)) {
532
- if (maxTags !== void 0 && next.length >= maxTags) break;
533
- next.push(tag);
536
+ function handleKeyDown(e) {
537
+ const input = inputRef.current;
538
+ if (!input) return;
539
+ if (e.key === "Enter" || e.key === "," || e.key === "Tab") {
540
+ if (input.value) {
541
+ e.preventDefault();
542
+ addTag(input.value);
543
+ input.value = "";
544
+ }
545
+ return;
546
+ }
547
+ if (e.key === "Backspace" && !input.value && value.length > 0) {
548
+ removeTag(value.length - 1);
534
549
  }
535
550
  }
536
- onChange(next);
537
- if (inputRef.current) inputRef.current.value = "";
538
- }
539
- const wrapper = /* @__PURE__ */ jsxs(
540
- "div",
541
- {
542
- className: cn(
543
- "flex flex-wrap items-center gap-1.5 min-h-[42px] w-full rounded-xl px-3 py-2 bg-white/10 ring-1 ring-white/10 transition-shadow focus-within:ring-2 focus-within:ring-primary/40",
544
- error && "ring-2 ring-rose-500/40 focus-within:ring-rose-500/40",
545
- disabled && "opacity-50 cursor-not-allowed",
546
- className
547
- ),
548
- onClick: () => inputRef.current?.focus(),
549
- children: [
550
- value.map((tag, i) => /* @__PURE__ */ jsxs(
551
- "span",
552
- {
553
- className: "inline-flex items-center gap-1 bg-white/10 text-white/90 rounded-full text-xs px-2.5 py-1 ring-1 ring-white/10",
554
- children: [
555
- tag,
556
- !disabled && /* @__PURE__ */ jsx(
557
- "button",
558
- {
559
- type: "button",
560
- "aria-label": `Remove ${tag}`,
561
- onClick: (e) => {
562
- e.stopPropagation();
563
- removeTag(i);
564
- },
565
- className: "text-white/50 hover:text-white/90 transition-colors leading-none",
566
- children: "\u2715"
567
- }
568
- )
569
- ]
570
- },
571
- `${tag}-${i}`
572
- )),
573
- /* @__PURE__ */ jsx(
574
- "input",
575
- {
576
- ref: inputRef,
577
- id: inputId,
578
- type: "text",
579
- disabled: disabled || atMax,
580
- placeholder: value.length === 0 ? placeholder : void 0,
581
- "aria-invalid": !!error || void 0,
582
- "aria-describedby": errorId,
583
- onKeyDown: handleKeyDown,
584
- onPaste: handlePaste,
585
- onBlur: (e) => {
586
- if (e.target.value) {
587
- addTag(e.target.value);
588
- e.target.value = "";
589
- }
590
- },
591
- className: "flex-1 min-w-[120px] bg-transparent text-sm text-white outline-none placeholder-white/30 disabled:cursor-not-allowed"
592
- }
593
- )
594
- ]
551
+ function handlePaste(e) {
552
+ const pasted = e.clipboardData.getData("text");
553
+ if (!pasted.includes(",")) return;
554
+ e.preventDefault();
555
+ const parts = pasted.split(",");
556
+ const next = [...value];
557
+ for (const part of parts) {
558
+ const tag = part.trim();
559
+ if (tag && !next.includes(tag)) {
560
+ if (maxTags !== void 0 && next.length >= maxTags) break;
561
+ next.push(tag);
562
+ }
563
+ }
564
+ onChange(next);
565
+ if (inputRef.current) inputRef.current.value = "";
595
566
  }
596
- );
597
- if (!label && !error) return wrapper;
598
- return /* @__PURE__ */ jsxs("div", { children: [
599
- label && /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm text-white/70 mb-1.5", children: label }),
600
- wrapper,
601
- error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs mt-1", children: error })
602
- ] });
603
- }
567
+ const wrapper = /* @__PURE__ */ jsxs(
568
+ "div",
569
+ {
570
+ className: cn(
571
+ "flex flex-wrap items-center gap-1.5 min-h-[42px] w-full rounded-xl px-3 py-2 bg-white/10 ring-1 ring-white/10 transition-shadow focus-within:ring-2 focus-within:ring-primary/40",
572
+ error && "ring-2 ring-rose-500/40 focus-within:ring-rose-500/40",
573
+ disabled && "opacity-50 cursor-not-allowed",
574
+ className
575
+ ),
576
+ onClick: () => inputRef.current?.focus(),
577
+ children: [
578
+ value.map((tag, i) => /* @__PURE__ */ jsxs(
579
+ "span",
580
+ {
581
+ className: "inline-flex items-center gap-1 bg-white/10 text-white/90 rounded-full text-xs px-2.5 py-1 ring-1 ring-white/10",
582
+ children: [
583
+ tag,
584
+ !disabled && /* @__PURE__ */ jsx(
585
+ "button",
586
+ {
587
+ type: "button",
588
+ "aria-label": `Remove ${tag}`,
589
+ onClick: (e) => {
590
+ e.stopPropagation();
591
+ removeTag(i);
592
+ },
593
+ className: "text-white/50 hover:text-white/90 transition-colors leading-none",
594
+ children: "\u2715"
595
+ }
596
+ )
597
+ ]
598
+ },
599
+ `${tag}-${i}`
600
+ )),
601
+ /* @__PURE__ */ jsx(
602
+ "input",
603
+ {
604
+ ref: inputRef,
605
+ id: inputId,
606
+ type: "text",
607
+ disabled: disabled || atMax,
608
+ placeholder: value.length === 0 ? placeholder : void 0,
609
+ "aria-invalid": !!error || void 0,
610
+ "aria-describedby": errorId,
611
+ onKeyDown: handleKeyDown,
612
+ onPaste: handlePaste,
613
+ onBlur: (e) => {
614
+ if (e.target.value) {
615
+ addTag(e.target.value);
616
+ e.target.value = "";
617
+ }
618
+ },
619
+ className: "flex-1 min-w-[120px] bg-transparent text-sm text-white outline-none placeholder-white/30 disabled:cursor-not-allowed"
620
+ }
621
+ )
622
+ ]
623
+ }
624
+ );
625
+ if (!label && !error) return wrapper;
626
+ return /* @__PURE__ */ jsxs("div", { children: [
627
+ label && /* @__PURE__ */ jsx("label", { htmlFor: inputId, className: "block text-sm text-white/70 mb-1.5", children: label }),
628
+ wrapper,
629
+ error && /* @__PURE__ */ jsx("p", { id: errorId, className: "text-rose-400 text-xs mt-1", children: error })
630
+ ] });
631
+ }
632
+ );
604
633
  var base3 = "inline-flex items-center justify-center rounded-full font-semibold ring-1 ring-white/10";
605
634
  var sizeClass4 = {
606
635
  sm: "text-xs px-2.5 py-1",
@@ -1251,23 +1280,6 @@ var Card = forwardRef(function Card2({ hoverable, variant = "surface", padding =
1251
1280
  }
1252
1281
  );
1253
1282
  });
1254
- var scrollLockCount = 0;
1255
- var savedOverflow = "";
1256
- function lockScroll() {
1257
- if (typeof document === "undefined") return;
1258
- if (scrollLockCount === 0) {
1259
- savedOverflow = document.body.style.overflow;
1260
- document.body.style.overflow = "hidden";
1261
- }
1262
- scrollLockCount++;
1263
- }
1264
- function unlockScroll() {
1265
- if (typeof document === "undefined") return;
1266
- scrollLockCount = Math.max(0, scrollLockCount - 1);
1267
- if (scrollLockCount === 0) {
1268
- document.body.style.overflow = savedOverflow;
1269
- }
1270
- }
1271
1283
  function Modal({
1272
1284
  isOpen,
1273
1285
  onClose,
@@ -1299,11 +1311,7 @@ function Modal({
1299
1311
  if (lastActive?.isConnected) focusSafely(lastActive);
1300
1312
  };
1301
1313
  }, [isOpen]);
1302
- useEffect(() => {
1303
- if (!isOpen) return;
1304
- lockScroll();
1305
- return () => unlockScroll();
1306
- }, [isOpen]);
1314
+ useScrollLock(isOpen);
1307
1315
  if (!isOpen) return null;
1308
1316
  return /* @__PURE__ */ jsx(
1309
1317
  "div",
@@ -1452,17 +1460,32 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
1452
1460
  const el = anchorRef.current;
1453
1461
  if (!el) return;
1454
1462
  const r = el.getBoundingClientRect();
1463
+ const vw = window.innerWidth;
1455
1464
  const centerX = r.left + r.width / 2;
1456
- const preferTop = placement === "top";
1465
+ const centerY = r.top + r.height / 2;
1457
1466
  const topY = r.top - 10;
1458
1467
  const bottomY = r.bottom + 10;
1459
- const canTop = topY > 8;
1460
- const effPlacement = preferTop ? canTop ? "top" : "bottom" : "bottom";
1461
- setPos({
1462
- left: Math.round(centerX),
1463
- top: Math.round(effPlacement === "top" ? topY : bottomY),
1464
- placement: effPlacement
1465
- });
1468
+ const leftX = r.left - 10;
1469
+ const rightX = r.right + 10;
1470
+ let effPlacement = placement;
1471
+ if (placement === "top" || placement === "bottom") {
1472
+ const canTop = topY > 8;
1473
+ effPlacement = placement === "top" ? canTop ? "top" : "bottom" : "bottom";
1474
+ setPos({
1475
+ left: Math.round(centerX),
1476
+ top: Math.round(effPlacement === "top" ? topY : bottomY),
1477
+ placement: effPlacement
1478
+ });
1479
+ } else {
1480
+ const canLeft = leftX > 8;
1481
+ const canRight = rightX < vw - 8;
1482
+ effPlacement = placement === "left" ? canLeft ? "left" : "right" : canRight ? "right" : "left";
1483
+ setPos({
1484
+ left: Math.round(effPlacement === "left" ? leftX : rightX),
1485
+ top: Math.round(centerY),
1486
+ placement: effPlacement
1487
+ });
1488
+ }
1466
1489
  }, [placement]);
1467
1490
  const scheduleOpen = useCallback(() => {
1468
1491
  clearTimer();
@@ -1536,7 +1559,7 @@ function Tooltip({ content, delayMs = 500, placement = "top", className, childre
1536
1559
  style: pos ? {
1537
1560
  left: pos.left,
1538
1561
  top: pos.top,
1539
- transform: pos.placement === "top" ? "translate(-50%, -100%)" : "translate(-50%, 0%)"
1562
+ transform: pos.placement === "top" ? "translate(-50%, -100%)" : pos.placement === "bottom" ? "translate(-50%, 0%)" : pos.placement === "left" ? "translate(-100%, -50%)" : "translate(0%, -50%)"
1540
1563
  } : { left: 0, top: 0, transform: "translate(-9999px, -9999px)" },
1541
1564
  children: content
1542
1565
  }
@@ -2079,7 +2102,7 @@ function StatCard({ value, label, icon, trend, className }) {
2079
2102
  "div",
2080
2103
  {
2081
2104
  className: cn(
2082
- "bg-white/5 ring-1 ring-white/10 rounded-xl p-4 flex items-start gap-3",
2105
+ "glass rounded-xl p-4 flex items-start gap-3",
2083
2106
  className
2084
2107
  ),
2085
2108
  children: [
@@ -3812,23 +3835,6 @@ function Popover({
3812
3835
  ) : null
3813
3836
  ] });
3814
3837
  }
3815
- var scrollLockCount2 = 0;
3816
- var savedOverflow2 = "";
3817
- function lockScroll2() {
3818
- if (typeof document === "undefined") return;
3819
- if (scrollLockCount2 === 0) {
3820
- savedOverflow2 = document.body.style.overflow;
3821
- document.body.style.overflow = "hidden";
3822
- }
3823
- scrollLockCount2++;
3824
- }
3825
- function unlockScroll2() {
3826
- if (typeof document === "undefined") return;
3827
- scrollLockCount2 = Math.max(0, scrollLockCount2 - 1);
3828
- if (scrollLockCount2 === 0) {
3829
- document.body.style.overflow = savedOverflow2;
3830
- }
3831
- }
3832
3838
  var sizeClass8 = {
3833
3839
  left: { sm: "w-64", md: "w-80", lg: "w-96", full: "w-screen" },
3834
3840
  right: { sm: "w-64", md: "w-80", lg: "w-96", full: "w-screen" },
@@ -3878,11 +3884,7 @@ function Drawer({
3878
3884
  if (lastActive?.isConnected) focusSafely(lastActive);
3879
3885
  };
3880
3886
  }, [isOpen]);
3881
- useEffect(() => {
3882
- if (!isOpen) return;
3883
- lockScroll2();
3884
- return () => unlockScroll2();
3885
- }, [isOpen]);
3887
+ useScrollLock(isOpen);
3886
3888
  if (!isOpen) return null;
3887
3889
  return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50", role: "presentation", children: [
3888
3890
  /* @__PURE__ */ jsx(
@@ -4265,5 +4267,60 @@ function VisuallyHidden({ children, as: Tag = "span", style, ...props }) {
4265
4267
  }
4266
4268
  );
4267
4269
  }
4270
+ var ErrorBoundary = class extends Component {
4271
+ constructor() {
4272
+ super(...arguments);
4273
+ this.state = { error: null };
4274
+ this.reset = () => {
4275
+ this.setState({ error: null });
4276
+ };
4277
+ }
4278
+ static getDerivedStateFromError(error) {
4279
+ return { error };
4280
+ }
4281
+ componentDidCatch(error, errorInfo) {
4282
+ this.props.onError?.(error, errorInfo);
4283
+ }
4284
+ render() {
4285
+ const { error } = this.state;
4286
+ if (!error) return this.props.children;
4287
+ const { fallback } = this.props;
4288
+ if (typeof fallback === "function") {
4289
+ return fallback(error, this.reset);
4290
+ }
4291
+ if (fallback !== void 0) {
4292
+ return fallback;
4293
+ }
4294
+ return /* @__PURE__ */ jsxs("div", { role: "alert", className: cn("glass rounded-xl p-6 text-center"), children: [
4295
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-white mb-2", children: "Something went wrong" }),
4296
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-white/60 mb-4", children: error.message }),
4297
+ /* @__PURE__ */ jsx(
4298
+ "button",
4299
+ {
4300
+ type: "button",
4301
+ onClick: this.reset,
4302
+ className: "inline-flex items-center justify-center gap-2 rounded-xl font-semibold leading-none px-3.5 py-2.5 text-sm bg-gradient-to-r from-violet-600 to-purple-600 text-white shadow-glow hover:shadow-glow-lg hover:scale-[1.02] transition-[transform,background-color,box-shadow,opacity] active:translate-y-[0.5px]",
4303
+ children: "Try again"
4304
+ }
4305
+ )
4306
+ ] });
4307
+ }
4308
+ };
4309
+ function LoadingScreen({ message, size = "lg", className }) {
4310
+ return /* @__PURE__ */ jsxs(
4311
+ "div",
4312
+ {
4313
+ role: "status",
4314
+ className: cn(
4315
+ "flex flex-col items-center justify-center min-h-screen bg-ml-bg gap-4",
4316
+ className
4317
+ ),
4318
+ children: [
4319
+ /* @__PURE__ */ jsx(Spinner, { size }),
4320
+ message && /* @__PURE__ */ jsx("p", { className: "text-sm text-white/60", children: message })
4321
+ ]
4322
+ }
4323
+ );
4324
+ }
4268
4325
 
4269
- export { ActiveFilterPills, Alert, Avatar, Badge, Breadcrumbs, Button, Card, Checkbox, CollapsibleSection, ColorInput, Combobox, ConfirmDialog, CooldownRing, CopyField, DashboardLayout, Divider, DotIndicator, Drawer, DropZone, Dropdown, DropdownItem, DropdownMenu, DropdownSeparator, DropdownTrigger, EmptyState, FormField, Heading, IconButton, Input, Modal, MutationOverlay, Navbar, NotificationBell, PageShell, Pagination, Pill, Popover, ProgressBar, ProgressButton, RadioGroup, RadioItem, ScrollArea, SearchInput, SectionCard, Select, Sidebar, Skeleton, Slider, Spinner, Stack, StageProgress, StatCard, Stepper, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TagInput, Text, Textarea, ToastProvider, Toggle, Tooltip, Transition, VisuallyHidden, cn, colors, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useSharedNow, useToast };
4326
+ export { ActiveFilterPills, Alert, Avatar, Badge, Breadcrumbs, Button, Card, Checkbox, CollapsibleSection, ColorInput, Combobox, ConfirmDialog, CooldownRing, CopyField, DashboardLayout, Divider, DotIndicator, Drawer, DropZone, Dropdown, DropdownItem, DropdownMenu, DropdownSeparator, DropdownTrigger, EmptyState, ErrorBoundary, FormField, Heading, IconButton, Input, LoadingScreen, Modal, MutationOverlay, Navbar, NotificationBell, PageShell, Pagination, Pill, Popover, ProgressBar, ProgressButton, RadioGroup, RadioItem, ScrollArea, SearchInput, SectionCard, Select, Sidebar, Skeleton, Slider, Spinner, Stack, StageProgress, StatCard, Stepper, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TagInput, Text, Textarea, ToastProvider, Toggle, Tooltip, Transition, VisuallyHidden, cn, colors, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useScrollLock, useSharedNow, useToast };