@memelabui/ui 0.10.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
@@ -4498,6 +4498,7 @@ function DashboardLayout({
4498
4498
  brand,
4499
4499
  user,
4500
4500
  headerActions,
4501
+ serviceSwitcher,
4501
4502
  navItems,
4502
4503
  renderNavItem,
4503
4504
  navigationLabel = "Main navigation",
@@ -4508,7 +4509,7 @@ function DashboardLayout({
4508
4509
  mainClassName,
4509
4510
  maxWidth = "lg"
4510
4511
  }) {
4511
- const hasGeneratedHeader = !navbar && Boolean(brand || user || headerActions);
4512
+ const hasGeneratedHeader = !navbar && Boolean(brand || user || headerActions || serviceSwitcher);
4512
4513
  const hasGeneratedNavigation = !sidebar && Boolean(navItems?.length);
4513
4514
  const hasGeneratedShell = hasGeneratedHeader || hasGeneratedNavigation;
4514
4515
  const renderNav = renderNavItem ?? renderDefaultNavItem;
@@ -4521,7 +4522,8 @@ function DashboardLayout({
4521
4522
  ),
4522
4523
  children: [
4523
4524
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex items-center gap-3", children: brand }),
4524
- (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,
4525
4527
  user,
4526
4528
  headerActions
4527
4529
  ] })
@@ -4567,6 +4569,272 @@ function DashboardLayout({
4567
4569
  ] })
4568
4570
  ] });
4569
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
+ ];
4570
4838
  function CopyIcon() {
4571
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: [
4572
4840
  /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "5", y: "5", width: "9", height: "9", rx: "1.5", stroke: "currentColor", strokeWidth: "1.5" }),
@@ -5855,6 +6123,7 @@ exports.Heading = Heading;
5855
6123
  exports.IconButton = IconButton;
5856
6124
  exports.Input = Input;
5857
6125
  exports.LoadingScreen = LoadingScreen;
6126
+ exports.MEMELAB_SERVICES = MEMELAB_SERVICES;
5858
6127
  exports.Modal = Modal;
5859
6128
  exports.MutationOverlay = MutationOverlay;
5860
6129
  exports.Navbar = Navbar;
@@ -5871,6 +6140,7 @@ exports.ScrollArea = ScrollArea;
5871
6140
  exports.SearchInput = SearchInput;
5872
6141
  exports.SectionCard = SectionCard;
5873
6142
  exports.Select = Select;
6143
+ exports.ServiceSwitcher = ServiceSwitcher;
5874
6144
  exports.Sidebar = Sidebar;
5875
6145
  exports.Skeleton = Skeleton;
5876
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
@@ -4492,6 +4492,7 @@ function DashboardLayout({
4492
4492
  brand,
4493
4493
  user,
4494
4494
  headerActions,
4495
+ serviceSwitcher,
4495
4496
  navItems,
4496
4497
  renderNavItem,
4497
4498
  navigationLabel = "Main navigation",
@@ -4502,7 +4503,7 @@ function DashboardLayout({
4502
4503
  mainClassName,
4503
4504
  maxWidth = "lg"
4504
4505
  }) {
4505
- const hasGeneratedHeader = !navbar && Boolean(brand || user || headerActions);
4506
+ const hasGeneratedHeader = !navbar && Boolean(brand || user || headerActions || serviceSwitcher);
4506
4507
  const hasGeneratedNavigation = !sidebar && Boolean(navItems?.length);
4507
4508
  const hasGeneratedShell = hasGeneratedHeader || hasGeneratedNavigation;
4508
4509
  const renderNav = renderNavItem ?? renderDefaultNavItem;
@@ -4515,7 +4516,8 @@ function DashboardLayout({
4515
4516
  ),
4516
4517
  children: [
4517
4518
  /* @__PURE__ */ jsx("div", { className: "min-w-0 flex items-center gap-3", children: brand }),
4518
- (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,
4519
4521
  user,
4520
4522
  headerActions
4521
4523
  ] })
@@ -4561,6 +4563,272 @@ function DashboardLayout({
4561
4563
  ] })
4562
4564
  ] });
4563
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
+ ];
4564
4832
  function CopyIcon() {
4565
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: [
4566
4834
  /* @__PURE__ */ jsx("rect", { x: "5", y: "5", width: "9", height: "9", rx: "1.5", stroke: "currentColor", strokeWidth: "1.5" }),
@@ -5816,4 +6084,4 @@ function LoadingScreen({ message, size = "lg", className }) {
5816
6084
  );
5817
6085
  }
5818
6086
 
5819
- 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 };
@@ -1514,6 +1514,15 @@ a {
1514
1514
  .grid-cols-1 {
1515
1515
  grid-template-columns: repeat(1, minmax(0, 1fr));
1516
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
+ }
1517
1526
  .grid-cols-7 {
1518
1527
  grid-template-columns: repeat(7, minmax(0, 1fr));
1519
1528
  }
@@ -2274,6 +2283,10 @@ a {
2274
2283
  --tw-text-opacity: 1;
2275
2284
  color: rgb(96 165 250 / var(--tw-text-opacity, 1));
2276
2285
  }
2286
+ .text-cyan-400 {
2287
+ --tw-text-opacity: 1;
2288
+ color: rgb(34 211 238 / var(--tw-text-opacity, 1));
2289
+ }
2277
2290
  .text-emerald-400 {
2278
2291
  --tw-text-opacity: 1;
2279
2292
  color: rgb(52 211 153 / var(--tw-text-opacity, 1));
@@ -2290,6 +2303,10 @@ a {
2290
2303
  --tw-text-opacity: 1;
2291
2304
  color: rgb(var(--ml-primary-light) / var(--tw-text-opacity, 1));
2292
2305
  }
2306
+ .text-purple-400 {
2307
+ --tw-text-opacity: 1;
2308
+ color: rgb(192 132 252 / var(--tw-text-opacity, 1));
2309
+ }
2293
2310
  .text-red-400 {
2294
2311
  --tw-text-opacity: 1;
2295
2312
  color: rgb(248 113 113 / var(--tw-text-opacity, 1));
@@ -2302,6 +2319,10 @@ a {
2302
2319
  --tw-text-opacity: 1;
2303
2320
  color: rgb(244 63 94 / var(--tw-text-opacity, 1));
2304
2321
  }
2322
+ .text-teal-400 {
2323
+ --tw-text-opacity: 1;
2324
+ color: rgb(45 212 191 / var(--tw-text-opacity, 1));
2325
+ }
2305
2326
  .text-transparent {
2306
2327
  color: transparent;
2307
2328
  }
@@ -2478,6 +2499,9 @@ a {
2478
2499
  .ring-accent\/20 {
2479
2500
  --tw-ring-color: rgb(var(--ml-accent) / 0.2);
2480
2501
  }
2502
+ .ring-accent\/30 {
2503
+ --tw-ring-color: rgb(var(--ml-accent) / 0.3);
2504
+ }
2481
2505
  .ring-amber-500\/20 {
2482
2506
  --tw-ring-color: rgb(245 158 11 / 0.2);
2483
2507
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memelabui/ui",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "MemeLab shared UI component library — React + Tailwind + Glassmorphism",
5
5
  "type": "module",
6
6
  "sideEffects": [