@memelabui/ui 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -342,7 +342,7 @@ var sizeClass3 = {
342
342
  lg: "p-2.5 w-11 h-11"
343
343
  };
344
344
  var variantClass2 = {
345
- primary: "bg-primary text-white shadow-glow hover:brightness-[0.98]",
345
+ primary: "bg-primary text-white shadow-glow hover:shadow-glow-lg hover:scale-[1.02]",
346
346
  success: "bg-emerald-600 text-white shadow-lg shadow-emerald-600/20 hover:bg-emerald-700",
347
347
  warning: "bg-amber-600 text-white shadow-lg shadow-amber-600/20 hover:bg-amber-700",
348
348
  danger: "bg-rose-600 text-white shadow-lg shadow-rose-600/20 hover:bg-rose-700",
@@ -2045,6 +2045,7 @@ var Card = React.forwardRef(function Card2({ hoverable, variant = "surface", pad
2045
2045
  }
2046
2046
  );
2047
2047
  });
2048
+ var EXIT_DURATION = 200;
2048
2049
  function Modal({
2049
2050
  isOpen,
2050
2051
  onClose,
@@ -2060,6 +2061,23 @@ function Modal({
2060
2061
  }) {
2061
2062
  const dialogRef = React.useRef(null);
2062
2063
  const lastActiveElementRef = React.useRef(null);
2064
+ const [mounted, setMounted] = React.useState(false);
2065
+ const [closing, setClosing] = React.useState(false);
2066
+ const closingTimerRef = React.useRef();
2067
+ React.useEffect(() => {
2068
+ if (isOpen) {
2069
+ setClosing(false);
2070
+ setMounted(true);
2071
+ clearTimeout(closingTimerRef.current);
2072
+ } else if (mounted) {
2073
+ setClosing(true);
2074
+ closingTimerRef.current = setTimeout(() => {
2075
+ setMounted(false);
2076
+ setClosing(false);
2077
+ }, EXIT_DURATION);
2078
+ }
2079
+ return () => clearTimeout(closingTimerRef.current);
2080
+ }, [isOpen]);
2063
2081
  React.useEffect(() => {
2064
2082
  if (!isOpen) return;
2065
2083
  lastActiveElementRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
@@ -2076,20 +2094,28 @@ function Modal({
2076
2094
  if (lastActive?.isConnected) focusSafely(lastActive);
2077
2095
  };
2078
2096
  }, [isOpen]);
2079
- useScrollLock(isOpen);
2080
- if (!isOpen) return null;
2097
+ useScrollLock(mounted);
2098
+ const handleClose = React.useCallback(() => {
2099
+ if (closing) return;
2100
+ onClose();
2101
+ }, [closing, onClose]);
2102
+ if (!mounted) return null;
2081
2103
  return /* @__PURE__ */ jsxRuntime.jsx(
2082
2104
  "div",
2083
2105
  {
2106
+ "data-closing": closing || void 0,
2084
2107
  className: cn(
2085
- "fixed inset-0 flex items-end sm:items-center justify-center p-4 pb-safe bg-black/25 backdrop-blur-sm animate-modal-backdrop",
2108
+ "fixed inset-0 flex items-end sm:items-center justify-center p-4 pb-safe bg-black/25 backdrop-blur-sm",
2086
2109
  zIndexClassName,
2087
2110
  overlayClassName
2088
2111
  ),
2112
+ style: {
2113
+ animation: closing ? `ml-modal-backdrop-out ${EXIT_DURATION}ms ease forwards` : "ml-modal-backdrop 200ms ease forwards"
2114
+ },
2089
2115
  role: "presentation",
2090
2116
  onMouseDown: (e) => {
2091
2117
  if (!closeOnBackdrop) return;
2092
- if (e.target === e.currentTarget) onClose();
2118
+ if (e.target === e.currentTarget) handleClose();
2093
2119
  },
2094
2120
  children: /* @__PURE__ */ jsxRuntime.jsx(
2095
2121
  "div",
@@ -2101,16 +2127,19 @@ function Modal({
2101
2127
  tabIndex: -1,
2102
2128
  ref: dialogRef,
2103
2129
  className: cn(
2104
- "w-full rounded-t-3xl sm:rounded-2xl shadow-xl ring-1 ring-white/10 animate-modal-pop focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
2130
+ "w-full rounded-t-3xl sm:rounded-2xl shadow-xl ring-1 ring-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
2105
2131
  useGlass && "glass bg-surface-50/80",
2106
2132
  contentClassName
2107
2133
  ),
2134
+ style: {
2135
+ animation: closing ? `ml-modal-pop-out ${EXIT_DURATION}ms ease forwards` : "ml-modal-pop 200ms ease forwards"
2136
+ },
2108
2137
  onMouseDown: (e) => e.stopPropagation(),
2109
2138
  onKeyDownCapture: (e) => {
2110
2139
  if (closeOnEsc && e.key === "Escape") {
2111
2140
  e.preventDefault();
2112
2141
  e.stopPropagation();
2113
- onClose();
2142
+ handleClose();
2114
2143
  return;
2115
2144
  }
2116
2145
  if (e.key !== "Tab") return;
@@ -4469,6 +4498,7 @@ function DashboardLayout({
4469
4498
  brand,
4470
4499
  user,
4471
4500
  headerActions,
4501
+ serviceSwitcher,
4472
4502
  navItems,
4473
4503
  renderNavItem,
4474
4504
  navigationLabel = "Main navigation",
@@ -4479,7 +4509,7 @@ function DashboardLayout({
4479
4509
  mainClassName,
4480
4510
  maxWidth = "lg"
4481
4511
  }) {
4482
- const hasGeneratedHeader = !navbar && Boolean(brand || user || headerActions);
4512
+ const hasGeneratedHeader = !navbar && Boolean(brand || user || headerActions || serviceSwitcher);
4483
4513
  const hasGeneratedNavigation = !sidebar && Boolean(navItems?.length);
4484
4514
  const hasGeneratedShell = hasGeneratedHeader || hasGeneratedNavigation;
4485
4515
  const renderNav = renderNavItem ?? renderDefaultNavItem;
@@ -4492,7 +4522,8 @@ function DashboardLayout({
4492
4522
  ),
4493
4523
  children: [
4494
4524
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex items-center gap-3", children: brand }),
4495
- (user || headerActions) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
4525
+ (serviceSwitcher || user || headerActions) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
4526
+ serviceSwitcher,
4496
4527
  user,
4497
4528
  headerActions
4498
4529
  ] })
@@ -4538,6 +4569,272 @@ function DashboardLayout({
4538
4569
  ] })
4539
4570
  ] });
4540
4571
  }
4572
+ var GridIcon = React.forwardRef(function GridIcon2({ className }, ref) {
4573
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4574
+ "svg",
4575
+ {
4576
+ ref,
4577
+ className,
4578
+ viewBox: "0 0 16 16",
4579
+ fill: "currentColor",
4580
+ "aria-hidden": "true",
4581
+ children: [
4582
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "1", width: "6", height: "6", rx: "1.5" }),
4583
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "9", y: "1", width: "6", height: "6", rx: "1.5" }),
4584
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "9", width: "6", height: "6", rx: "1.5" }),
4585
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "9", y: "9", width: "6", height: "6", rx: "1.5" })
4586
+ ]
4587
+ }
4588
+ );
4589
+ });
4590
+ var colsClass = { 2: "grid-cols-2", 3: "grid-cols-3", 4: "grid-cols-4" };
4591
+ function ServiceSwitcher({
4592
+ services,
4593
+ trigger,
4594
+ columns = 3,
4595
+ className
4596
+ }) {
4597
+ const popoverId = React.useId();
4598
+ const triggerRef = React.useRef(null);
4599
+ const popoverRef = React.useRef(null);
4600
+ const [open, setOpen] = React.useState(false);
4601
+ const [pos, setPos] = React.useState(null);
4602
+ const updatePosition = React.useCallback(() => {
4603
+ const el = triggerRef.current;
4604
+ if (!el) return;
4605
+ const r = el.getBoundingClientRect();
4606
+ let left = r.right;
4607
+ const top = r.bottom + 8;
4608
+ const popupWidth = columns === 2 ? 240 : columns === 4 ? 400 : 320;
4609
+ if (left + 16 > window.innerWidth) {
4610
+ left = window.innerWidth - popupWidth - 16;
4611
+ }
4612
+ setPos({ left: Math.round(left), top: Math.round(top) });
4613
+ }, [columns]);
4614
+ const toggle = React.useCallback(() => {
4615
+ setOpen((prev) => {
4616
+ if (!prev) updatePosition();
4617
+ return !prev;
4618
+ });
4619
+ }, [updatePosition]);
4620
+ const close = React.useCallback(() => setOpen(false), []);
4621
+ React.useEffect(() => {
4622
+ if (!open) return;
4623
+ const handleClick = (e) => {
4624
+ const target = e.target;
4625
+ if (triggerRef.current?.contains(target) || popoverRef.current?.contains(target)) return;
4626
+ close();
4627
+ };
4628
+ document.addEventListener("mousedown", handleClick);
4629
+ return () => document.removeEventListener("mousedown", handleClick);
4630
+ }, [open, close]);
4631
+ React.useEffect(() => {
4632
+ if (!open) return;
4633
+ const handleKey = (e) => {
4634
+ if (e.key === "Escape") {
4635
+ e.preventDefault();
4636
+ close();
4637
+ triggerRef.current?.focus();
4638
+ }
4639
+ };
4640
+ document.addEventListener("keydown", handleKey);
4641
+ return () => document.removeEventListener("keydown", handleKey);
4642
+ }, [open, close]);
4643
+ React.useEffect(() => {
4644
+ if (!open) return;
4645
+ window.addEventListener("scroll", updatePosition, true);
4646
+ window.addEventListener("resize", updatePosition);
4647
+ return () => {
4648
+ window.removeEventListener("scroll", updatePosition, true);
4649
+ window.removeEventListener("resize", updatePosition);
4650
+ };
4651
+ }, [open, updatePosition]);
4652
+ const triggerButton = trigger ? /* @__PURE__ */ jsxRuntime.jsx(
4653
+ "span",
4654
+ {
4655
+ ref: triggerRef,
4656
+ onClick: toggle,
4657
+ "aria-expanded": open,
4658
+ "aria-haspopup": "dialog",
4659
+ "aria-controls": open ? popoverId : void 0,
4660
+ className: "cursor-pointer",
4661
+ children: trigger
4662
+ }
4663
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
4664
+ "button",
4665
+ {
4666
+ ref: triggerRef,
4667
+ type: "button",
4668
+ onClick: toggle,
4669
+ "aria-expanded": open,
4670
+ "aria-haspopup": "dialog",
4671
+ "aria-controls": open ? popoverId : void 0,
4672
+ "aria-label": "Switch service",
4673
+ className: cn(
4674
+ "flex items-center justify-center w-9 h-9 rounded-lg transition-colors duration-200",
4675
+ "text-white/40 hover:text-white/80 hover:bg-white/5",
4676
+ open && "text-white/80 bg-white/5",
4677
+ className
4678
+ ),
4679
+ children: /* @__PURE__ */ jsxRuntime.jsx(GridIcon, { className: "w-4 h-4" })
4680
+ }
4681
+ );
4682
+ const popup = open && typeof document !== "undefined" ? reactDom.createPortal(
4683
+ /* @__PURE__ */ jsxRuntime.jsx(
4684
+ "div",
4685
+ {
4686
+ ref: popoverRef,
4687
+ id: popoverId,
4688
+ role: "dialog",
4689
+ "aria-label": "MemeLab services",
4690
+ className: "fixed z-[9999] glass rounded-xl shadow-xl ring-1 ring-white/10 p-3",
4691
+ style: {
4692
+ left: pos?.left ?? 0,
4693
+ top: pos?.top ?? 0,
4694
+ transform: "translateX(-100%)"
4695
+ },
4696
+ children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("grid gap-1", colsClass[columns]), children: services.map((service) => /* @__PURE__ */ jsxRuntime.jsxs(
4697
+ "a",
4698
+ {
4699
+ href: service.href,
4700
+ onClick: close,
4701
+ className: cn(
4702
+ "flex flex-col items-center gap-1.5 rounded-lg px-3 py-3 transition-all duration-150",
4703
+ "hover:bg-white/5",
4704
+ service.active && "bg-white/5 ring-1 ring-accent/30"
4705
+ ),
4706
+ children: [
4707
+ /* @__PURE__ */ jsxRuntime.jsx(
4708
+ "span",
4709
+ {
4710
+ className: cn(
4711
+ "flex items-center justify-center w-10 h-10 rounded-xl transition-colors",
4712
+ service.active ? "bg-accent/15" : "bg-white/5",
4713
+ service.color
4714
+ ),
4715
+ children: service.icon
4716
+ }
4717
+ ),
4718
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(
4719
+ "text-xs font-medium truncate max-w-full",
4720
+ service.active ? "text-white" : "text-white/70"
4721
+ ), children: service.label })
4722
+ ]
4723
+ },
4724
+ service.key
4725
+ )) })
4726
+ }
4727
+ ),
4728
+ document.body
4729
+ ) : null;
4730
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4731
+ triggerButton,
4732
+ popup
4733
+ ] });
4734
+ }
4735
+ function BellIcon({ className }) {
4736
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" }) });
4737
+ }
4738
+ function ChatBubblesIcon({ className }) {
4739
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
4740
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M20 2H4a2 2 0 00-2 2v12a2 2 0 002 2h3l3 3 3-3h7a2 2 0 002-2V4a2 2 0 00-2-2z" }),
4741
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", d: "M8 9h8M8 13h4" })
4742
+ ] });
4743
+ }
4744
+ function BotIcon({ className }) {
4745
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
4746
+ /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "8", width: "18", height: "12", rx: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
4747
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 8V5m-3 8h.01M15 13h.01M9 17h6" })
4748
+ ] });
4749
+ }
4750
+ function MusicIcon({ className }) {
4751
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
4752
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 18V5l12-2v13" }),
4753
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "6", cy: "18", r: "3", strokeLinecap: "round", strokeLinejoin: "round" }),
4754
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "18", cy: "16", r: "3", strokeLinecap: "round", strokeLinejoin: "round" })
4755
+ ] });
4756
+ }
4757
+ function AlertsIcon({ className }) {
4758
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 10V3L4 14h7v7l9-11h-7z" }) });
4759
+ }
4760
+ function AuctionIcon({ className }) {
4761
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) });
4762
+ }
4763
+ function StatisticsIcon({ className }) {
4764
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) });
4765
+ }
4766
+ function StudioIcon({ className }) {
4767
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
4768
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" }),
4769
+ /* @__PURE__ */ jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z" })
4770
+ ] });
4771
+ }
4772
+ var MEMELAB_SERVICES = [
4773
+ {
4774
+ key: "studio",
4775
+ label: "Studio",
4776
+ href: "https://memelab.ru/studio",
4777
+ icon: /* @__PURE__ */ jsxRuntime.jsx(StudioIcon, { className: "w-5 h-5" }),
4778
+ color: "text-purple-400",
4779
+ description: "\u041F\u0430\u043D\u0435\u043B\u044C \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F"
4780
+ },
4781
+ {
4782
+ key: "multichat",
4783
+ label: "Multichat",
4784
+ href: "https://multichat.memelab.ru",
4785
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ChatBubblesIcon, { className: "w-5 h-5" }),
4786
+ color: "text-blue-400",
4787
+ description: "\u0410\u0433\u0440\u0435\u0433\u0430\u0442\u043E\u0440 \u0447\u0430\u0442\u043E\u0432"
4788
+ },
4789
+ {
4790
+ key: "chatbot",
4791
+ label: "Chatbot",
4792
+ href: "https://chatbot.memelab.ru",
4793
+ icon: /* @__PURE__ */ jsxRuntime.jsx(BotIcon, { className: "w-5 h-5" }),
4794
+ color: "text-emerald-400",
4795
+ description: "\u0418\u0418 \u0447\u0430\u0442-\u0431\u043E\u0442"
4796
+ },
4797
+ {
4798
+ key: "alerts",
4799
+ label: "Alerts",
4800
+ href: "https://alerts.memelab.ru",
4801
+ icon: /* @__PURE__ */ jsxRuntime.jsx(AlertsIcon, { className: "w-5 h-5" }),
4802
+ color: "text-amber-400",
4803
+ description: "\u0410\u043B\u0435\u0440\u0442\u044B \u043D\u0430 \u0441\u0442\u0440\u0438\u043C\u0435"
4804
+ },
4805
+ {
4806
+ key: "notify",
4807
+ label: "Notify",
4808
+ href: "https://notify.memelab.ru",
4809
+ icon: /* @__PURE__ */ jsxRuntime.jsx(BellIcon, { className: "w-5 h-5" }),
4810
+ color: "text-rose-400",
4811
+ description: "\u0423\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F"
4812
+ },
4813
+ {
4814
+ key: "music",
4815
+ label: "Music",
4816
+ href: "https://music.memelab.ru",
4817
+ icon: /* @__PURE__ */ jsxRuntime.jsx(MusicIcon, { className: "w-5 h-5" }),
4818
+ color: "text-violet-400",
4819
+ description: "Now Playing"
4820
+ },
4821
+ {
4822
+ key: "auction",
4823
+ label: "Auction",
4824
+ href: "https://auction.memelab.ru",
4825
+ icon: /* @__PURE__ */ jsxRuntime.jsx(AuctionIcon, { className: "w-5 h-5" }),
4826
+ color: "text-cyan-400",
4827
+ description: "\u0410\u0443\u043A\u0446\u0438\u043E\u043D \u043F\u043E\u0438\u043D\u0442\u043E\u0432"
4828
+ },
4829
+ {
4830
+ key: "statistics",
4831
+ label: "Statistics",
4832
+ href: "https://stats.memelab.ru",
4833
+ icon: /* @__PURE__ */ jsxRuntime.jsx(StatisticsIcon, { className: "w-5 h-5" }),
4834
+ color: "text-teal-400",
4835
+ description: "\u0410\u043D\u0430\u043B\u0438\u0442\u0438\u043A\u0430 \u0441\u0442\u0440\u0438\u043C\u043E\u0432"
4836
+ }
4837
+ ];
4541
4838
  function CopyIcon() {
4542
4839
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
4543
4840
  /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "5", width: "9", height: "9", rx: "1.5", stroke: "currentColor", strokeWidth: "1.5" }),
@@ -4891,7 +5188,10 @@ function ToastProvider({ children, position = "top-right", maxToasts = 5 }) {
4891
5188
  /* @__PURE__ */ jsxRuntime.jsx(
4892
5189
  "div",
4893
5190
  {
5191
+ role: "region",
4894
5192
  "aria-label": "Notifications",
5193
+ "aria-live": "polite",
5194
+ "aria-relevant": "additions text",
4895
5195
  className: cn("fixed z-[9999] flex flex-col gap-3 pointer-events-none", positionClass2[position]),
4896
5196
  children: toasts.map((t) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsxRuntime.jsx(ToastCard, { toast: t, onDismiss: dismiss }) }, t.id))
4897
5197
  }
@@ -5823,6 +6123,7 @@ exports.Heading = Heading;
5823
6123
  exports.IconButton = IconButton;
5824
6124
  exports.Input = Input;
5825
6125
  exports.LoadingScreen = LoadingScreen;
6126
+ exports.MEMELAB_SERVICES = MEMELAB_SERVICES;
5826
6127
  exports.Modal = Modal;
5827
6128
  exports.MutationOverlay = MutationOverlay;
5828
6129
  exports.Navbar = Navbar;
@@ -5839,6 +6140,7 @@ exports.ScrollArea = ScrollArea;
5839
6140
  exports.SearchInput = SearchInput;
5840
6141
  exports.SectionCard = SectionCard;
5841
6142
  exports.Select = Select;
6143
+ exports.ServiceSwitcher = ServiceSwitcher;
5842
6144
  exports.Sidebar = Sidebar;
5843
6145
  exports.Skeleton = Skeleton;
5844
6146
  exports.Slider = Slider;
package/dist/index.d.cts CHANGED
@@ -842,6 +842,8 @@ type DashboardLayoutProps = {
842
842
  brand?: ReactNode;
843
843
  user?: ReactNode;
844
844
  headerActions?: ReactNode;
845
+ /** Service switcher rendered in the header between brand and user */
846
+ serviceSwitcher?: ReactNode;
845
847
  navItems?: DashboardLayoutNavItem[];
846
848
  renderNavItem?: (item: DashboardLayoutNavItem, context: DashboardLayoutNavRenderContext) => ReactNode;
847
849
  navigationLabel?: string;
@@ -852,7 +854,42 @@ type DashboardLayoutProps = {
852
854
  mainClassName?: string;
853
855
  maxWidth?: DashboardLayoutWidth;
854
856
  };
855
- declare function DashboardLayout({ children, navbar, sidebar, brand, user, headerActions, navItems, renderNavItem, navigationLabel, mobileNavigationLabel, shellClassName, shellWidth, className, mainClassName, maxWidth, }: DashboardLayoutProps): react_jsx_runtime.JSX.Element;
857
+ declare function DashboardLayout({ children, navbar, sidebar, brand, user, headerActions, serviceSwitcher, navItems, renderNavItem, navigationLabel, mobileNavigationLabel, shellClassName, shellWidth, className, mainClassName, maxWidth, }: DashboardLayoutProps): react_jsx_runtime.JSX.Element;
858
+
859
+ interface ServiceSwitcherItem {
860
+ /** Unique service identifier */
861
+ key: string;
862
+ /** Display name */
863
+ label: string;
864
+ /** Full URL including protocol (e.g. https://chatbot.memelab.ru) */
865
+ href: string;
866
+ /** Icon element */
867
+ icon: ReactNode;
868
+ /** Tailwind color class for the icon (e.g. 'text-rose-400') */
869
+ color?: string;
870
+ /** Short description shown below the label */
871
+ description?: string;
872
+ /** Whether this is the currently active service */
873
+ active?: boolean;
874
+ }
875
+ /**
876
+ * All MemeLab platform services.
877
+ * Used by ServiceSwitcher across all service frontends.
878
+ * Each service marks itself as `active` when rendering.
879
+ */
880
+ declare const MEMELAB_SERVICES: ServiceSwitcherItem[];
881
+
882
+ type ServiceSwitcherProps = {
883
+ /** List of services to display in the grid */
884
+ services: ServiceSwitcherItem[];
885
+ /** Custom trigger element. Default: grid icon button */
886
+ trigger?: ReactElement;
887
+ /** Number of grid columns. Default: 3 */
888
+ columns?: 2 | 3 | 4;
889
+ /** Extra class on the root wrapper */
890
+ className?: string;
891
+ };
892
+ declare function ServiceSwitcher({ services, trigger, columns, className, }: ServiceSwitcherProps): react_jsx_runtime.JSX.Element;
856
893
 
857
894
  type AlertVariant = 'info' | 'success' | 'warning' | 'error';
858
895
  type AlertProps = {
@@ -1192,4 +1229,4 @@ type LoadingScreenProps = {
1192
1229
  };
1193
1230
  declare function LoadingScreen({ message, size, className }: LoadingScreenProps): react_jsx_runtime.JSX.Element;
1194
1231
 
1195
- export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, type BreadcrumbItem, type BreadcrumbRenderLinkProps, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardPadding, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, Combobox, type ComboboxOption, type ComboboxProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutNavItem, type DashboardLayoutNavRenderContext, type DashboardLayoutProps, type DashboardLayoutWidth, DataTable, type DataTableCellContext, type DataTableColumnDef, type DataTableColumnFilterType, type DataTableFilterContext, type DataTableFilterOption, type DataTableHeaderContext, type DataTableProps, type DataTableSort, DateRangePicker, type DateRangePickerPlaceholder, type DateRangePickerProps, type DateRangePreset, type DateRangeValue, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, Drawer, type DrawerProps, type DrawerSide, type DrawerSize, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, ErrorBoundary, type ErrorBoundaryActionContext, type ErrorBoundaryProps, type FilterPill, FormField, type FormFieldProps, Heading, type HeadingLevel, type HeadingProps, type HotkeyBinding, type HotkeyModifiers, IconButton, type IconButtonProps, Input, type InputProps, LoadingScreen, type LoadingScreenProps, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, NotificationBell, type NotificationBellProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, Popover, type PopoverPlacement, type PopoverProps, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, ScrollArea, type ScrollAreaProps, 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, Stack, type StackProps, StageProgress, type StageProgressProps, StatCard, type StatCardMetric, type StatCardProps, type StatCardSize, type StatCardTrend, type StatCardValueColor, 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, Text, type TextColor, type TextProps, type TextSize, Textarea, type TextareaProps, Timeline, type TimelineEvent, type TimelineGroupBy, type TimelineIconTone, type TimelineLineStyle, type TimelineOrder, type TimelineProps, type TimelineTimestampPosition, type TimelineVariant, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, Transition, type TransitionPreset, type TransitionProps, type UseClipboardReturn, type UseDisclosureReturn, type UseHotkeysOptions, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseSharedNowOptions, VisuallyHidden, type VisuallyHiddenProps, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useScrollLock, useSharedNow, useToast };
1232
+ export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, type BreadcrumbItem, type BreadcrumbRenderLinkProps, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardPadding, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, Combobox, type ComboboxOption, type ComboboxProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutNavItem, type DashboardLayoutNavRenderContext, type DashboardLayoutProps, type DashboardLayoutWidth, DataTable, type DataTableCellContext, type DataTableColumnDef, type DataTableColumnFilterType, type DataTableFilterContext, type DataTableFilterOption, type DataTableHeaderContext, type DataTableProps, type DataTableSort, DateRangePicker, type DateRangePickerPlaceholder, type DateRangePickerProps, type DateRangePreset, type DateRangeValue, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, Drawer, type DrawerProps, type DrawerSide, type DrawerSize, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, ErrorBoundary, type ErrorBoundaryActionContext, type ErrorBoundaryProps, type FilterPill, FormField, type FormFieldProps, Heading, type HeadingLevel, type HeadingProps, type HotkeyBinding, type HotkeyModifiers, IconButton, type IconButtonProps, Input, type InputProps, LoadingScreen, type LoadingScreenProps, MEMELAB_SERVICES, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, NotificationBell, type NotificationBellProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, Popover, type PopoverPlacement, type PopoverProps, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, ScrollArea, type ScrollAreaProps, SearchInput, type SearchInputProps, SectionCard, type SectionCardProps, Select, type SelectProps, ServiceSwitcher, type ServiceSwitcherItem, type ServiceSwitcherProps, Sidebar, type SidebarProps, type Size, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, type SpinnerSize, Stack, type StackProps, StageProgress, type StageProgressProps, StatCard, type StatCardMetric, type StatCardProps, type StatCardSize, type StatCardTrend, type StatCardValueColor, 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, Text, type TextColor, type TextProps, type TextSize, Textarea, type TextareaProps, Timeline, type TimelineEvent, type TimelineGroupBy, type TimelineIconTone, type TimelineLineStyle, type TimelineOrder, type TimelineProps, type TimelineTimestampPosition, type TimelineVariant, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, Transition, type TransitionPreset, type TransitionProps, type UseClipboardReturn, type UseDisclosureReturn, type UseHotkeysOptions, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseSharedNowOptions, VisuallyHidden, type VisuallyHiddenProps, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useScrollLock, useSharedNow, useToast };
package/dist/index.d.ts CHANGED
@@ -842,6 +842,8 @@ type DashboardLayoutProps = {
842
842
  brand?: ReactNode;
843
843
  user?: ReactNode;
844
844
  headerActions?: ReactNode;
845
+ /** Service switcher rendered in the header between brand and user */
846
+ serviceSwitcher?: ReactNode;
845
847
  navItems?: DashboardLayoutNavItem[];
846
848
  renderNavItem?: (item: DashboardLayoutNavItem, context: DashboardLayoutNavRenderContext) => ReactNode;
847
849
  navigationLabel?: string;
@@ -852,7 +854,42 @@ type DashboardLayoutProps = {
852
854
  mainClassName?: string;
853
855
  maxWidth?: DashboardLayoutWidth;
854
856
  };
855
- declare function DashboardLayout({ children, navbar, sidebar, brand, user, headerActions, navItems, renderNavItem, navigationLabel, mobileNavigationLabel, shellClassName, shellWidth, className, mainClassName, maxWidth, }: DashboardLayoutProps): react_jsx_runtime.JSX.Element;
857
+ declare function DashboardLayout({ children, navbar, sidebar, brand, user, headerActions, serviceSwitcher, navItems, renderNavItem, navigationLabel, mobileNavigationLabel, shellClassName, shellWidth, className, mainClassName, maxWidth, }: DashboardLayoutProps): react_jsx_runtime.JSX.Element;
858
+
859
+ interface ServiceSwitcherItem {
860
+ /** Unique service identifier */
861
+ key: string;
862
+ /** Display name */
863
+ label: string;
864
+ /** Full URL including protocol (e.g. https://chatbot.memelab.ru) */
865
+ href: string;
866
+ /** Icon element */
867
+ icon: ReactNode;
868
+ /** Tailwind color class for the icon (e.g. 'text-rose-400') */
869
+ color?: string;
870
+ /** Short description shown below the label */
871
+ description?: string;
872
+ /** Whether this is the currently active service */
873
+ active?: boolean;
874
+ }
875
+ /**
876
+ * All MemeLab platform services.
877
+ * Used by ServiceSwitcher across all service frontends.
878
+ * Each service marks itself as `active` when rendering.
879
+ */
880
+ declare const MEMELAB_SERVICES: ServiceSwitcherItem[];
881
+
882
+ type ServiceSwitcherProps = {
883
+ /** List of services to display in the grid */
884
+ services: ServiceSwitcherItem[];
885
+ /** Custom trigger element. Default: grid icon button */
886
+ trigger?: ReactElement;
887
+ /** Number of grid columns. Default: 3 */
888
+ columns?: 2 | 3 | 4;
889
+ /** Extra class on the root wrapper */
890
+ className?: string;
891
+ };
892
+ declare function ServiceSwitcher({ services, trigger, columns, className, }: ServiceSwitcherProps): react_jsx_runtime.JSX.Element;
856
893
 
857
894
  type AlertVariant = 'info' | 'success' | 'warning' | 'error';
858
895
  type AlertProps = {
@@ -1192,4 +1229,4 @@ type LoadingScreenProps = {
1192
1229
  };
1193
1230
  declare function LoadingScreen({ message, size, className }: LoadingScreenProps): react_jsx_runtime.JSX.Element;
1194
1231
 
1195
- export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, type BreadcrumbItem, type BreadcrumbRenderLinkProps, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardPadding, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, Combobox, type ComboboxOption, type ComboboxProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutNavItem, type DashboardLayoutNavRenderContext, type DashboardLayoutProps, type DashboardLayoutWidth, DataTable, type DataTableCellContext, type DataTableColumnDef, type DataTableColumnFilterType, type DataTableFilterContext, type DataTableFilterOption, type DataTableHeaderContext, type DataTableProps, type DataTableSort, DateRangePicker, type DateRangePickerPlaceholder, type DateRangePickerProps, type DateRangePreset, type DateRangeValue, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, Drawer, type DrawerProps, type DrawerSide, type DrawerSize, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, ErrorBoundary, type ErrorBoundaryActionContext, type ErrorBoundaryProps, type FilterPill, FormField, type FormFieldProps, Heading, type HeadingLevel, type HeadingProps, type HotkeyBinding, type HotkeyModifiers, IconButton, type IconButtonProps, Input, type InputProps, LoadingScreen, type LoadingScreenProps, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, NotificationBell, type NotificationBellProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, Popover, type PopoverPlacement, type PopoverProps, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, ScrollArea, type ScrollAreaProps, 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, Stack, type StackProps, StageProgress, type StageProgressProps, StatCard, type StatCardMetric, type StatCardProps, type StatCardSize, type StatCardTrend, type StatCardValueColor, 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, Text, type TextColor, type TextProps, type TextSize, Textarea, type TextareaProps, Timeline, type TimelineEvent, type TimelineGroupBy, type TimelineIconTone, type TimelineLineStyle, type TimelineOrder, type TimelineProps, type TimelineTimestampPosition, type TimelineVariant, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, Transition, type TransitionPreset, type TransitionProps, type UseClipboardReturn, type UseDisclosureReturn, type UseHotkeysOptions, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseSharedNowOptions, VisuallyHidden, type VisuallyHiddenProps, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useScrollLock, useSharedNow, useToast };
1232
+ export { ActiveFilterPills, type ActiveFilterPillsProps, Alert, type AlertProps, type AlertVariant, Avatar, type AvatarProps, type AvatarSize, Badge, type BadgeProps, type BadgeSize, type BadgeVariant, type BreadcrumbItem, type BreadcrumbRenderLinkProps, Breadcrumbs, type BreadcrumbsProps, Button, type ButtonProps, type ButtonSize, type ButtonVariant, Card, type CardPadding, type CardProps, type CardVariant, Checkbox, type CheckboxProps, CollapsibleSection, type CollapsibleSectionProps, ColorInput, type ColorInputProps, Combobox, type ComboboxOption, type ComboboxProps, ConfirmDialog, type ConfirmDialogProps, type ConfirmDialogVariant, CooldownRing, type CooldownRingProps, type CooldownRingSize, CopyField, type CopyFieldProps, DashboardLayout, type DashboardLayoutNavItem, type DashboardLayoutNavRenderContext, type DashboardLayoutProps, type DashboardLayoutWidth, DataTable, type DataTableCellContext, type DataTableColumnDef, type DataTableColumnFilterType, type DataTableFilterContext, type DataTableFilterOption, type DataTableHeaderContext, type DataTableProps, type DataTableSort, DateRangePicker, type DateRangePickerPlaceholder, type DateRangePickerProps, type DateRangePreset, type DateRangeValue, Divider, type DividerProps, DotIndicator, type DotIndicatorProps, Drawer, type DrawerProps, type DrawerSide, type DrawerSize, DropZone, type DropZoneProps, Dropdown, DropdownItem, type DropdownItemProps, DropdownMenu, type DropdownMenuProps, type DropdownProps, DropdownSeparator, type DropdownSeparatorProps, DropdownTrigger, type DropdownTriggerProps, EmptyState, type EmptyStateProps, ErrorBoundary, type ErrorBoundaryActionContext, type ErrorBoundaryProps, type FilterPill, FormField, type FormFieldProps, Heading, type HeadingLevel, type HeadingProps, type HotkeyBinding, type HotkeyModifiers, IconButton, type IconButtonProps, Input, type InputProps, LoadingScreen, type LoadingScreenProps, MEMELAB_SERVICES, Modal, type ModalProps, MutationOverlay, type MutationOverlayProps, type MutationOverlayStatus, Navbar, type NavbarProps, NotificationBell, type NotificationBellProps, PageShell, type PageShellProps, type PageShellVariant, Pagination, type PaginationProps, Pill, Popover, type PopoverPlacement, type PopoverProps, ProgressBar, type ProgressBarProps, type ProgressBarVariant, ProgressButton, type ProgressButtonProps, RadioGroup, type RadioGroupProps, RadioItem, type RadioItemProps, ScrollArea, type ScrollAreaProps, SearchInput, type SearchInputProps, SectionCard, type SectionCardProps, Select, type SelectProps, ServiceSwitcher, type ServiceSwitcherItem, type ServiceSwitcherProps, Sidebar, type SidebarProps, type Size, Skeleton, type SkeletonProps, Slider, type SliderProps, Spinner, type SpinnerProps, type SpinnerSize, Stack, type StackProps, StageProgress, type StageProgressProps, StatCard, type StatCardMetric, type StatCardProps, type StatCardSize, type StatCardTrend, type StatCardValueColor, 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, Text, type TextColor, type TextProps, type TextSize, Textarea, type TextareaProps, Timeline, type TimelineEvent, type TimelineGroupBy, type TimelineIconTone, type TimelineLineStyle, type TimelineOrder, type TimelineProps, type TimelineTimestampPosition, type TimelineVariant, type ToastData, type ToastPosition, ToastProvider, type ToastProviderProps, type ToastVariant, Toggle, type ToggleProps, type ToggleSize, Tooltip, type TooltipPlacement, type TooltipProps, Transition, type TransitionPreset, type TransitionProps, type UseClipboardReturn, type UseDisclosureReturn, type UseHotkeysOptions, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseSharedNowOptions, VisuallyHidden, type VisuallyHiddenProps, cn, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useScrollLock, useSharedNow, useToast };
package/dist/index.js CHANGED
@@ -336,7 +336,7 @@ var sizeClass3 = {
336
336
  lg: "p-2.5 w-11 h-11"
337
337
  };
338
338
  var variantClass2 = {
339
- primary: "bg-primary text-white shadow-glow hover:brightness-[0.98]",
339
+ primary: "bg-primary text-white shadow-glow hover:shadow-glow-lg hover:scale-[1.02]",
340
340
  success: "bg-emerald-600 text-white shadow-lg shadow-emerald-600/20 hover:bg-emerald-700",
341
341
  warning: "bg-amber-600 text-white shadow-lg shadow-amber-600/20 hover:bg-amber-700",
342
342
  danger: "bg-rose-600 text-white shadow-lg shadow-rose-600/20 hover:bg-rose-700",
@@ -2039,6 +2039,7 @@ var Card = forwardRef(function Card2({ hoverable, variant = "surface", padding =
2039
2039
  }
2040
2040
  );
2041
2041
  });
2042
+ var EXIT_DURATION = 200;
2042
2043
  function Modal({
2043
2044
  isOpen,
2044
2045
  onClose,
@@ -2054,6 +2055,23 @@ function Modal({
2054
2055
  }) {
2055
2056
  const dialogRef = useRef(null);
2056
2057
  const lastActiveElementRef = useRef(null);
2058
+ const [mounted, setMounted] = useState(false);
2059
+ const [closing, setClosing] = useState(false);
2060
+ const closingTimerRef = useRef();
2061
+ useEffect(() => {
2062
+ if (isOpen) {
2063
+ setClosing(false);
2064
+ setMounted(true);
2065
+ clearTimeout(closingTimerRef.current);
2066
+ } else if (mounted) {
2067
+ setClosing(true);
2068
+ closingTimerRef.current = setTimeout(() => {
2069
+ setMounted(false);
2070
+ setClosing(false);
2071
+ }, EXIT_DURATION);
2072
+ }
2073
+ return () => clearTimeout(closingTimerRef.current);
2074
+ }, [isOpen]);
2057
2075
  useEffect(() => {
2058
2076
  if (!isOpen) return;
2059
2077
  lastActiveElementRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
@@ -2070,20 +2088,28 @@ function Modal({
2070
2088
  if (lastActive?.isConnected) focusSafely(lastActive);
2071
2089
  };
2072
2090
  }, [isOpen]);
2073
- useScrollLock(isOpen);
2074
- if (!isOpen) return null;
2091
+ useScrollLock(mounted);
2092
+ const handleClose = useCallback(() => {
2093
+ if (closing) return;
2094
+ onClose();
2095
+ }, [closing, onClose]);
2096
+ if (!mounted) return null;
2075
2097
  return /* @__PURE__ */ jsx(
2076
2098
  "div",
2077
2099
  {
2100
+ "data-closing": closing || void 0,
2078
2101
  className: cn(
2079
- "fixed inset-0 flex items-end sm:items-center justify-center p-4 pb-safe bg-black/25 backdrop-blur-sm animate-modal-backdrop",
2102
+ "fixed inset-0 flex items-end sm:items-center justify-center p-4 pb-safe bg-black/25 backdrop-blur-sm",
2080
2103
  zIndexClassName,
2081
2104
  overlayClassName
2082
2105
  ),
2106
+ style: {
2107
+ animation: closing ? `ml-modal-backdrop-out ${EXIT_DURATION}ms ease forwards` : "ml-modal-backdrop 200ms ease forwards"
2108
+ },
2083
2109
  role: "presentation",
2084
2110
  onMouseDown: (e) => {
2085
2111
  if (!closeOnBackdrop) return;
2086
- if (e.target === e.currentTarget) onClose();
2112
+ if (e.target === e.currentTarget) handleClose();
2087
2113
  },
2088
2114
  children: /* @__PURE__ */ jsx(
2089
2115
  "div",
@@ -2095,16 +2121,19 @@ function Modal({
2095
2121
  tabIndex: -1,
2096
2122
  ref: dialogRef,
2097
2123
  className: cn(
2098
- "w-full rounded-t-3xl sm:rounded-2xl shadow-xl ring-1 ring-white/10 animate-modal-pop focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
2124
+ "w-full rounded-t-3xl sm:rounded-2xl shadow-xl ring-1 ring-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-primary",
2099
2125
  useGlass && "glass bg-surface-50/80",
2100
2126
  contentClassName
2101
2127
  ),
2128
+ style: {
2129
+ animation: closing ? `ml-modal-pop-out ${EXIT_DURATION}ms ease forwards` : "ml-modal-pop 200ms ease forwards"
2130
+ },
2102
2131
  onMouseDown: (e) => e.stopPropagation(),
2103
2132
  onKeyDownCapture: (e) => {
2104
2133
  if (closeOnEsc && e.key === "Escape") {
2105
2134
  e.preventDefault();
2106
2135
  e.stopPropagation();
2107
- onClose();
2136
+ handleClose();
2108
2137
  return;
2109
2138
  }
2110
2139
  if (e.key !== "Tab") return;
@@ -4463,6 +4492,7 @@ function DashboardLayout({
4463
4492
  brand,
4464
4493
  user,
4465
4494
  headerActions,
4495
+ serviceSwitcher,
4466
4496
  navItems,
4467
4497
  renderNavItem,
4468
4498
  navigationLabel = "Main navigation",
@@ -4473,7 +4503,7 @@ function DashboardLayout({
4473
4503
  mainClassName,
4474
4504
  maxWidth = "lg"
4475
4505
  }) {
4476
- const hasGeneratedHeader = !navbar && Boolean(brand || user || headerActions);
4506
+ const hasGeneratedHeader = !navbar && Boolean(brand || user || headerActions || serviceSwitcher);
4477
4507
  const hasGeneratedNavigation = !sidebar && Boolean(navItems?.length);
4478
4508
  const hasGeneratedShell = hasGeneratedHeader || hasGeneratedNavigation;
4479
4509
  const renderNav = renderNavItem ?? renderDefaultNavItem;
@@ -4486,7 +4516,8 @@ function DashboardLayout({
4486
4516
  ),
4487
4517
  children: [
4488
4518
  /* @__PURE__ */ jsx("div", { className: "min-w-0 flex items-center gap-3", children: brand }),
4489
- (user || headerActions) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
4519
+ (serviceSwitcher || user || headerActions) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4520
+ serviceSwitcher,
4490
4521
  user,
4491
4522
  headerActions
4492
4523
  ] })
@@ -4532,6 +4563,272 @@ function DashboardLayout({
4532
4563
  ] })
4533
4564
  ] });
4534
4565
  }
4566
+ var GridIcon = forwardRef(function GridIcon2({ className }, ref) {
4567
+ return /* @__PURE__ */ jsxs(
4568
+ "svg",
4569
+ {
4570
+ ref,
4571
+ className,
4572
+ viewBox: "0 0 16 16",
4573
+ fill: "currentColor",
4574
+ "aria-hidden": "true",
4575
+ children: [
4576
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "1", width: "6", height: "6", rx: "1.5" }),
4577
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "1", width: "6", height: "6", rx: "1.5" }),
4578
+ /* @__PURE__ */ jsx("rect", { x: "1", y: "9", width: "6", height: "6", rx: "1.5" }),
4579
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "6", height: "6", rx: "1.5" })
4580
+ ]
4581
+ }
4582
+ );
4583
+ });
4584
+ var colsClass = { 2: "grid-cols-2", 3: "grid-cols-3", 4: "grid-cols-4" };
4585
+ function ServiceSwitcher({
4586
+ services,
4587
+ trigger,
4588
+ columns = 3,
4589
+ className
4590
+ }) {
4591
+ const popoverId = useId();
4592
+ const triggerRef = useRef(null);
4593
+ const popoverRef = useRef(null);
4594
+ const [open, setOpen] = useState(false);
4595
+ const [pos, setPos] = useState(null);
4596
+ const updatePosition = useCallback(() => {
4597
+ const el = triggerRef.current;
4598
+ if (!el) return;
4599
+ const r = el.getBoundingClientRect();
4600
+ let left = r.right;
4601
+ const top = r.bottom + 8;
4602
+ const popupWidth = columns === 2 ? 240 : columns === 4 ? 400 : 320;
4603
+ if (left + 16 > window.innerWidth) {
4604
+ left = window.innerWidth - popupWidth - 16;
4605
+ }
4606
+ setPos({ left: Math.round(left), top: Math.round(top) });
4607
+ }, [columns]);
4608
+ const toggle = useCallback(() => {
4609
+ setOpen((prev) => {
4610
+ if (!prev) updatePosition();
4611
+ return !prev;
4612
+ });
4613
+ }, [updatePosition]);
4614
+ const close = useCallback(() => setOpen(false), []);
4615
+ useEffect(() => {
4616
+ if (!open) return;
4617
+ const handleClick = (e) => {
4618
+ const target = e.target;
4619
+ if (triggerRef.current?.contains(target) || popoverRef.current?.contains(target)) return;
4620
+ close();
4621
+ };
4622
+ document.addEventListener("mousedown", handleClick);
4623
+ return () => document.removeEventListener("mousedown", handleClick);
4624
+ }, [open, close]);
4625
+ useEffect(() => {
4626
+ if (!open) return;
4627
+ const handleKey = (e) => {
4628
+ if (e.key === "Escape") {
4629
+ e.preventDefault();
4630
+ close();
4631
+ triggerRef.current?.focus();
4632
+ }
4633
+ };
4634
+ document.addEventListener("keydown", handleKey);
4635
+ return () => document.removeEventListener("keydown", handleKey);
4636
+ }, [open, close]);
4637
+ useEffect(() => {
4638
+ if (!open) return;
4639
+ window.addEventListener("scroll", updatePosition, true);
4640
+ window.addEventListener("resize", updatePosition);
4641
+ return () => {
4642
+ window.removeEventListener("scroll", updatePosition, true);
4643
+ window.removeEventListener("resize", updatePosition);
4644
+ };
4645
+ }, [open, updatePosition]);
4646
+ const triggerButton = trigger ? /* @__PURE__ */ jsx(
4647
+ "span",
4648
+ {
4649
+ ref: triggerRef,
4650
+ onClick: toggle,
4651
+ "aria-expanded": open,
4652
+ "aria-haspopup": "dialog",
4653
+ "aria-controls": open ? popoverId : void 0,
4654
+ className: "cursor-pointer",
4655
+ children: trigger
4656
+ }
4657
+ ) : /* @__PURE__ */ jsx(
4658
+ "button",
4659
+ {
4660
+ ref: triggerRef,
4661
+ type: "button",
4662
+ onClick: toggle,
4663
+ "aria-expanded": open,
4664
+ "aria-haspopup": "dialog",
4665
+ "aria-controls": open ? popoverId : void 0,
4666
+ "aria-label": "Switch service",
4667
+ className: cn(
4668
+ "flex items-center justify-center w-9 h-9 rounded-lg transition-colors duration-200",
4669
+ "text-white/40 hover:text-white/80 hover:bg-white/5",
4670
+ open && "text-white/80 bg-white/5",
4671
+ className
4672
+ ),
4673
+ children: /* @__PURE__ */ jsx(GridIcon, { className: "w-4 h-4" })
4674
+ }
4675
+ );
4676
+ const popup = open && typeof document !== "undefined" ? createPortal(
4677
+ /* @__PURE__ */ jsx(
4678
+ "div",
4679
+ {
4680
+ ref: popoverRef,
4681
+ id: popoverId,
4682
+ role: "dialog",
4683
+ "aria-label": "MemeLab services",
4684
+ className: "fixed z-[9999] glass rounded-xl shadow-xl ring-1 ring-white/10 p-3",
4685
+ style: {
4686
+ left: pos?.left ?? 0,
4687
+ top: pos?.top ?? 0,
4688
+ transform: "translateX(-100%)"
4689
+ },
4690
+ children: /* @__PURE__ */ jsx("div", { className: cn("grid gap-1", colsClass[columns]), children: services.map((service) => /* @__PURE__ */ jsxs(
4691
+ "a",
4692
+ {
4693
+ href: service.href,
4694
+ onClick: close,
4695
+ className: cn(
4696
+ "flex flex-col items-center gap-1.5 rounded-lg px-3 py-3 transition-all duration-150",
4697
+ "hover:bg-white/5",
4698
+ service.active && "bg-white/5 ring-1 ring-accent/30"
4699
+ ),
4700
+ children: [
4701
+ /* @__PURE__ */ jsx(
4702
+ "span",
4703
+ {
4704
+ className: cn(
4705
+ "flex items-center justify-center w-10 h-10 rounded-xl transition-colors",
4706
+ service.active ? "bg-accent/15" : "bg-white/5",
4707
+ service.color
4708
+ ),
4709
+ children: service.icon
4710
+ }
4711
+ ),
4712
+ /* @__PURE__ */ jsx("span", { className: cn(
4713
+ "text-xs font-medium truncate max-w-full",
4714
+ service.active ? "text-white" : "text-white/70"
4715
+ ), children: service.label })
4716
+ ]
4717
+ },
4718
+ service.key
4719
+ )) })
4720
+ }
4721
+ ),
4722
+ document.body
4723
+ ) : null;
4724
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4725
+ triggerButton,
4726
+ popup
4727
+ ] });
4728
+ }
4729
+ function BellIcon({ className }) {
4730
+ return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" }) });
4731
+ }
4732
+ function ChatBubblesIcon({ className }) {
4733
+ return /* @__PURE__ */ jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
4734
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M20 2H4a2 2 0 00-2 2v12a2 2 0 002 2h3l3 3 3-3h7a2 2 0 002-2V4a2 2 0 00-2-2z" }),
4735
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", d: "M8 9h8M8 13h4" })
4736
+ ] });
4737
+ }
4738
+ function BotIcon({ className }) {
4739
+ return /* @__PURE__ */ jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
4740
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "8", width: "18", height: "12", rx: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
4741
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 8V5m-3 8h.01M15 13h.01M9 17h6" })
4742
+ ] });
4743
+ }
4744
+ function MusicIcon({ className }) {
4745
+ return /* @__PURE__ */ jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
4746
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 18V5l12-2v13" }),
4747
+ /* @__PURE__ */ jsx("circle", { cx: "6", cy: "18", r: "3", strokeLinecap: "round", strokeLinejoin: "round" }),
4748
+ /* @__PURE__ */ jsx("circle", { cx: "18", cy: "16", r: "3", strokeLinecap: "round", strokeLinejoin: "round" })
4749
+ ] });
4750
+ }
4751
+ function AlertsIcon({ className }) {
4752
+ return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M13 10V3L4 14h7v7l9-11h-7z" }) });
4753
+ }
4754
+ function AuctionIcon({ className }) {
4755
+ return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) });
4756
+ }
4757
+ function StatisticsIcon({ className }) {
4758
+ return /* @__PURE__ */ jsx("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }) });
4759
+ }
4760
+ function StudioIcon({ className }) {
4761
+ return /* @__PURE__ */ jsxs("svg", { className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
4762
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" }),
4763
+ /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 12a9 9 0 11-18 0 9 9 0 0118 0z" })
4764
+ ] });
4765
+ }
4766
+ var MEMELAB_SERVICES = [
4767
+ {
4768
+ key: "studio",
4769
+ label: "Studio",
4770
+ href: "https://memelab.ru/studio",
4771
+ icon: /* @__PURE__ */ jsx(StudioIcon, { className: "w-5 h-5" }),
4772
+ color: "text-purple-400",
4773
+ description: "\u041F\u0430\u043D\u0435\u043B\u044C \u0443\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u0438\u044F"
4774
+ },
4775
+ {
4776
+ key: "multichat",
4777
+ label: "Multichat",
4778
+ href: "https://multichat.memelab.ru",
4779
+ icon: /* @__PURE__ */ jsx(ChatBubblesIcon, { className: "w-5 h-5" }),
4780
+ color: "text-blue-400",
4781
+ description: "\u0410\u0433\u0440\u0435\u0433\u0430\u0442\u043E\u0440 \u0447\u0430\u0442\u043E\u0432"
4782
+ },
4783
+ {
4784
+ key: "chatbot",
4785
+ label: "Chatbot",
4786
+ href: "https://chatbot.memelab.ru",
4787
+ icon: /* @__PURE__ */ jsx(BotIcon, { className: "w-5 h-5" }),
4788
+ color: "text-emerald-400",
4789
+ description: "\u0418\u0418 \u0447\u0430\u0442-\u0431\u043E\u0442"
4790
+ },
4791
+ {
4792
+ key: "alerts",
4793
+ label: "Alerts",
4794
+ href: "https://alerts.memelab.ru",
4795
+ icon: /* @__PURE__ */ jsx(AlertsIcon, { className: "w-5 h-5" }),
4796
+ color: "text-amber-400",
4797
+ description: "\u0410\u043B\u0435\u0440\u0442\u044B \u043D\u0430 \u0441\u0442\u0440\u0438\u043C\u0435"
4798
+ },
4799
+ {
4800
+ key: "notify",
4801
+ label: "Notify",
4802
+ href: "https://notify.memelab.ru",
4803
+ icon: /* @__PURE__ */ jsx(BellIcon, { className: "w-5 h-5" }),
4804
+ color: "text-rose-400",
4805
+ description: "\u0423\u0432\u0435\u0434\u043E\u043C\u043B\u0435\u043D\u0438\u044F"
4806
+ },
4807
+ {
4808
+ key: "music",
4809
+ label: "Music",
4810
+ href: "https://music.memelab.ru",
4811
+ icon: /* @__PURE__ */ jsx(MusicIcon, { className: "w-5 h-5" }),
4812
+ color: "text-violet-400",
4813
+ description: "Now Playing"
4814
+ },
4815
+ {
4816
+ key: "auction",
4817
+ label: "Auction",
4818
+ href: "https://auction.memelab.ru",
4819
+ icon: /* @__PURE__ */ jsx(AuctionIcon, { className: "w-5 h-5" }),
4820
+ color: "text-cyan-400",
4821
+ description: "\u0410\u0443\u043A\u0446\u0438\u043E\u043D \u043F\u043E\u0438\u043D\u0442\u043E\u0432"
4822
+ },
4823
+ {
4824
+ key: "statistics",
4825
+ label: "Statistics",
4826
+ href: "https://stats.memelab.ru",
4827
+ icon: /* @__PURE__ */ jsx(StatisticsIcon, { className: "w-5 h-5" }),
4828
+ color: "text-teal-400",
4829
+ description: "\u0410\u043D\u0430\u043B\u0438\u0442\u0438\u043A\u0430 \u0441\u0442\u0440\u0438\u043C\u043E\u0432"
4830
+ }
4831
+ ];
4535
4832
  function CopyIcon() {
4536
4833
  return /* @__PURE__ */ jsxs("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
4537
4834
  /* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "9", height: "9", rx: "1.5", stroke: "currentColor", strokeWidth: "1.5" }),
@@ -4885,7 +5182,10 @@ function ToastProvider({ children, position = "top-right", maxToasts = 5 }) {
4885
5182
  /* @__PURE__ */ jsx(
4886
5183
  "div",
4887
5184
  {
5185
+ role: "region",
4888
5186
  "aria-label": "Notifications",
5187
+ "aria-live": "polite",
5188
+ "aria-relevant": "additions text",
4889
5189
  className: cn("fixed z-[9999] flex flex-col gap-3 pointer-events-none", positionClass2[position]),
4890
5190
  children: toasts.map((t) => /* @__PURE__ */ jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx(ToastCard, { toast: t, onDismiss: dismiss }) }, t.id))
4891
5191
  }
@@ -5784,4 +6084,4 @@ function LoadingScreen({ message, size = "lg", className }) {
5784
6084
  );
5785
6085
  }
5786
6086
 
5787
- export { ActiveFilterPills, Alert, Avatar, Badge, Breadcrumbs, Button, Card, Checkbox, CollapsibleSection, ColorInput, Combobox, ConfirmDialog, CooldownRing, CopyField, DashboardLayout, DataTable, DateRangePicker, 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, Timeline, ToastProvider, Toggle, Tooltip, Transition, VisuallyHidden, cn, colors, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useScrollLock, useSharedNow, useToast };
6087
+ export { ActiveFilterPills, Alert, Avatar, Badge, Breadcrumbs, Button, Card, Checkbox, CollapsibleSection, ColorInput, Combobox, ConfirmDialog, CooldownRing, CopyField, DashboardLayout, DataTable, DateRangePicker, Divider, DotIndicator, Drawer, DropZone, Dropdown, DropdownItem, DropdownMenu, DropdownSeparator, DropdownTrigger, EmptyState, ErrorBoundary, FormField, Heading, IconButton, Input, LoadingScreen, MEMELAB_SERVICES, Modal, MutationOverlay, Navbar, NotificationBell, PageShell, Pagination, Pill, Popover, ProgressBar, ProgressButton, RadioGroup, RadioItem, ScrollArea, SearchInput, SectionCard, Select, ServiceSwitcher, Sidebar, Skeleton, Slider, Spinner, Stack, StageProgress, StatCard, Stepper, Tab, TabList, TabPanel, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TagInput, Text, Textarea, Timeline, ToastProvider, Toggle, Tooltip, Transition, VisuallyHidden, cn, colors, focusSafely, getFocusableElements, useClipboard, useDebounce, useDisclosure, useHotkeys, useIntersectionObserver, useMediaQuery, useScrollLock, useSharedNow, useToast };
@@ -62,6 +62,14 @@
62
62
  --ml-transition-fast: 150ms ease;
63
63
  --ml-transition-normal: 200ms ease;
64
64
  --ml-transition-slow: 300ms ease;
65
+
66
+ /* ---- Animation durations ---- */
67
+ --ml-anim-fast: 200ms;
68
+ --ml-anim-normal: 300ms;
69
+ --ml-anim-slow: 500ms;
70
+
71
+ /* ---- Layout ---- */
72
+ --ml-header-height: 4rem;
65
73
  }
66
74
  @keyframes ml-float {
67
75
  0%,
@@ -120,6 +128,24 @@
120
128
  transform: translateY(0) scale(1);
121
129
  }
122
130
  }
131
+ @keyframes ml-modal-backdrop-out {
132
+ from {
133
+ opacity: 1;
134
+ }
135
+ to {
136
+ opacity: 0;
137
+ }
138
+ }
139
+ @keyframes ml-modal-pop-out {
140
+ from {
141
+ opacity: 1;
142
+ transform: translateY(0) scale(1);
143
+ }
144
+ to {
145
+ opacity: 0;
146
+ transform: translateY(6px) scale(0.98);
147
+ }
148
+ }
123
149
  @keyframes ml-shimmer {
124
150
  0% {
125
151
  transform: translateX(-200%);
@@ -149,7 +175,8 @@
149
175
  [class*='animate-modal'],
150
176
  [class*='animate-shimmer'],
151
177
  [class*='animate-spin'],
152
- [class*='animate-pulse'] {
178
+ [class*='animate-pulse'],
179
+ [data-closing] {
153
180
  animation-duration: 0.01ms;
154
181
  animation-iteration-count: 1;
155
182
  transition-duration: 0.01ms;
@@ -664,6 +691,12 @@ a {
664
691
  0 1px 3px rgba(0, 0, 0, 0.35),
665
692
  inset 0 0 0 1px var(--ml-glass-border, rgba(255, 255, 255, 0.1));
666
693
  }
694
+ .glass:hover {
695
+ background: var(--ml-glass-bg-hover, rgba(255, 255, 255, 0.07));
696
+ box-shadow:
697
+ 0 1px 3px rgba(0, 0, 0, 0.35),
698
+ inset 0 0 0 1px var(--ml-glass-border-hover, rgba(102, 126, 234, 0.25));
699
+ }
667
700
  /* Surface card */
668
701
  .surface {
669
702
  border-radius: var(--ml-radius-md, 0.75rem);
@@ -677,9 +710,12 @@ a {
677
710
  );
678
711
  }
679
712
  .surface-hover {
680
- transition: box-shadow var(--ml-transition-fast, 150ms ease);
713
+ transition:
714
+ box-shadow var(--ml-transition-fast, 150ms ease),
715
+ transform var(--ml-transition-fast, 150ms ease);
681
716
  }
682
717
  .surface-hover:hover {
718
+ transform: translateY(-1px);
683
719
  box-shadow: var(
684
720
  --ml-shadow-surface-hover,
685
721
  0 10px 25px rgba(0, 0, 0, 0.45),
@@ -1420,30 +1456,6 @@ a {
1420
1456
  .animate-\[ml-shimmer_2s_ease-in-out_infinite\] {
1421
1457
  animation: ml-shimmer 2s ease-in-out infinite;
1422
1458
  }
1423
- @keyframes ml-modal-backdrop {
1424
- from {
1425
- opacity: 0;
1426
- }
1427
- to {
1428
- opacity: 1;
1429
- }
1430
- }
1431
- .animate-modal-backdrop {
1432
- animation: ml-modal-backdrop 140ms ease-out both;
1433
- }
1434
- @keyframes ml-modal-pop {
1435
- from {
1436
- opacity: 0;
1437
- transform: translateY(6px) scale(0.98);
1438
- }
1439
- to {
1440
- opacity: 1;
1441
- transform: translateY(0) scale(1);
1442
- }
1443
- }
1444
- .animate-modal-pop {
1445
- animation: ml-modal-pop 160ms cubic-bezier(0.22,1,0.36,1) both;
1446
- }
1447
1459
  @keyframes ping {
1448
1460
  75%, 100% {
1449
1461
  transform: scale(2);
@@ -1502,6 +1514,15 @@ a {
1502
1514
  .grid-cols-1 {
1503
1515
  grid-template-columns: repeat(1, minmax(0, 1fr));
1504
1516
  }
1517
+ .grid-cols-2 {
1518
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1519
+ }
1520
+ .grid-cols-3 {
1521
+ grid-template-columns: repeat(3, minmax(0, 1fr));
1522
+ }
1523
+ .grid-cols-4 {
1524
+ grid-template-columns: repeat(4, minmax(0, 1fr));
1525
+ }
1505
1526
  .grid-cols-7 {
1506
1527
  grid-template-columns: repeat(7, minmax(0, 1fr));
1507
1528
  }
@@ -2262,6 +2283,10 @@ a {
2262
2283
  --tw-text-opacity: 1;
2263
2284
  color: rgb(96 165 250 / var(--tw-text-opacity, 1));
2264
2285
  }
2286
+ .text-cyan-400 {
2287
+ --tw-text-opacity: 1;
2288
+ color: rgb(34 211 238 / var(--tw-text-opacity, 1));
2289
+ }
2265
2290
  .text-emerald-400 {
2266
2291
  --tw-text-opacity: 1;
2267
2292
  color: rgb(52 211 153 / var(--tw-text-opacity, 1));
@@ -2278,6 +2303,10 @@ a {
2278
2303
  --tw-text-opacity: 1;
2279
2304
  color: rgb(var(--ml-primary-light) / var(--tw-text-opacity, 1));
2280
2305
  }
2306
+ .text-purple-400 {
2307
+ --tw-text-opacity: 1;
2308
+ color: rgb(192 132 252 / var(--tw-text-opacity, 1));
2309
+ }
2281
2310
  .text-red-400 {
2282
2311
  --tw-text-opacity: 1;
2283
2312
  color: rgb(248 113 113 / var(--tw-text-opacity, 1));
@@ -2290,6 +2319,10 @@ a {
2290
2319
  --tw-text-opacity: 1;
2291
2320
  color: rgb(244 63 94 / var(--tw-text-opacity, 1));
2292
2321
  }
2322
+ .text-teal-400 {
2323
+ --tw-text-opacity: 1;
2324
+ color: rgb(45 212 191 / var(--tw-text-opacity, 1));
2325
+ }
2293
2326
  .text-transparent {
2294
2327
  color: transparent;
2295
2328
  }
@@ -2466,6 +2499,9 @@ a {
2466
2499
  .ring-accent\/20 {
2467
2500
  --tw-ring-color: rgb(var(--ml-accent) / 0.2);
2468
2501
  }
2502
+ .ring-accent\/30 {
2503
+ --tw-ring-color: rgb(var(--ml-accent) / 0.3);
2504
+ }
2469
2505
  .ring-amber-500\/20 {
2470
2506
  --tw-ring-color: rgb(245 158 11 / 0.2);
2471
2507
  }
@@ -2734,10 +2770,6 @@ a {
2734
2770
  .hover\:ring-white\/20:hover {
2735
2771
  --tw-ring-color: rgb(255 255 255 / 0.2);
2736
2772
  }
2737
- .hover\:brightness-\[0\.98\]:hover {
2738
- --tw-brightness: brightness(0.98);
2739
- filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
2740
- }
2741
2773
  .focus\:outline-none:focus {
2742
2774
  outline: 2px solid transparent;
2743
2775
  outline-offset: 2px;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memelabui/ui",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "MemeLab shared UI component library — React + Tailwind + Glassmorphism",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -46,6 +46,20 @@
46
46
  "dist",
47
47
  "README.md"
48
48
  ],
49
+ "scripts": {
50
+ "dev": "storybook dev -p 6006",
51
+ "build": "tsup && pnpm build:css",
52
+ "build:css": "postcss src/styles/index.css -o dist/styles/index.css",
53
+ "build:storybook": "storybook build -o storybook-static",
54
+ "test": "vitest",
55
+ "test:ci": "vitest --run",
56
+ "typecheck": "tsc --noEmit",
57
+ "lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
58
+ "lint:fix": "eslint src --ext ts,tsx --fix",
59
+ "format": "prettier --check \"src/**/*.{ts,tsx,css}\"",
60
+ "format:fix": "prettier --write \"src/**/*.{ts,tsx,css}\"",
61
+ "prepublishOnly": "pnpm build"
62
+ },
49
63
  "peerDependencies": {
50
64
  "react": "^18.0.0 || ^19.0.0",
51
65
  "react-dom": "^18.0.0 || ^19.0.0",
@@ -67,7 +81,7 @@
67
81
  "@typescript-eslint/parser": "^8.22.0",
68
82
  "@vitejs/plugin-react": "^4.3.4",
69
83
  "autoprefixer": "^10.4.20",
70
- "eslint": "^9.19.0",
84
+ "eslint": "^10.0.2",
71
85
  "eslint-plugin-react": "^7.37.4",
72
86
  "eslint-plugin-react-hooks": "^5.1.0",
73
87
  "jsdom": "^25.0.1",
@@ -88,6 +102,10 @@
88
102
  "access": "public",
89
103
  "registry": "https://registry.npmjs.org/"
90
104
  },
105
+ "packageManager": "pnpm@9.15.0",
106
+ "engines": {
107
+ "node": ">=22"
108
+ },
91
109
  "license": "MIT",
92
110
  "repository": {
93
111
  "type": "git",
@@ -101,18 +119,5 @@
101
119
  "glassmorphism",
102
120
  "dark-theme",
103
121
  "component-library"
104
- ],
105
- "scripts": {
106
- "dev": "storybook dev -p 6006",
107
- "build": "tsup && pnpm build:css",
108
- "build:css": "postcss src/styles/index.css -o dist/styles/index.css",
109
- "build:storybook": "storybook build -o storybook-static",
110
- "test": "vitest",
111
- "test:ci": "vitest --run",
112
- "typecheck": "tsc --noEmit",
113
- "lint": "eslint src --ext ts,tsx --report-unused-disable-directives",
114
- "lint:fix": "eslint src --ext ts,tsx --fix",
115
- "format": "prettier --check \"src/**/*.{ts,tsx,css}\"",
116
- "format:fix": "prettier --write \"src/**/*.{ts,tsx,css}\""
117
- }
118
- }
122
+ ]
123
+ }