@open-mercato/ui 0.6.1 → 0.6.2-canary.3332.1.eb75d0803f

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +1 -0
  3. package/dist/ai/AiIcon.js +1 -1
  4. package/dist/ai/AiIcon.js.map +2 -2
  5. package/dist/backend/AppShell.js +211 -152
  6. package/dist/backend/AppShell.js.map +2 -2
  7. package/dist/backend/ProfileDropdown.js +101 -79
  8. package/dist/backend/ProfileDropdown.js.map +2 -2
  9. package/dist/backend/notifications/NotificationCountBadge.js +1 -1
  10. package/dist/backend/notifications/NotificationCountBadge.js.map +2 -2
  11. package/dist/backend/notifications/NotificationItem.js +67 -50
  12. package/dist/backend/notifications/NotificationItem.js.map +2 -2
  13. package/dist/backend/notifications/NotificationPanel.js +108 -85
  14. package/dist/backend/notifications/NotificationPanel.js.map +2 -2
  15. package/dist/primitives/breadcrumb.js +161 -0
  16. package/dist/primitives/breadcrumb.js.map +7 -0
  17. package/dist/primitives/sheet.js +114 -0
  18. package/dist/primitives/sheet.js.map +7 -0
  19. package/package.json +5 -4
  20. package/src/ai/AiIcon.tsx +1 -1
  21. package/src/backend/AppShell.tsx +138 -47
  22. package/src/backend/ProfileDropdown.tsx +115 -88
  23. package/src/backend/__tests__/AppShell.test.tsx +8 -0
  24. package/src/backend/notifications/NotificationCountBadge.tsx +1 -1
  25. package/src/backend/notifications/NotificationItem.tsx +97 -104
  26. package/src/backend/notifications/NotificationPanel.tsx +126 -102
  27. package/src/primitives/__tests__/breadcrumb.test.tsx +298 -0
  28. package/src/primitives/__tests__/sheet.test.tsx +140 -0
  29. package/src/primitives/breadcrumb.tsx +185 -0
  30. package/src/primitives/sheet.tsx +146 -0
@@ -1,3 +1,3 @@
1
1
  Generated lucide registry with 134 icons -> /home/runner/work/open-mercato/open-mercato/packages/ui/src/backend/icons/lucideRegistry.generated.tsx
2
- [build:ui] found 348 entry points
2
+ [build:ui] found 350 entry points
3
3
  [build:ui] built successfully
package/AGENTS.md CHANGED
@@ -42,6 +42,7 @@ When you need… use this. Details (variants, sizes, props, MUST rules) live in
42
42
  | User / entity avatar | `Avatar`, `AvatarStack` | `@open-mercato/ui/primitives/avatar` |
43
43
  | Keyboard shortcut keys | `Kbd`, `KbdShortcut` | `@open-mercato/ui/primitives/kbd` |
44
44
  | Entity tag pill | `Tag` (with `TagMap`) | `@open-mercato/ui/primitives/tag` |
45
+ | Breadcrumb navigation (DS-aligned, slash/arrow/dot divider, ARIA correct) | `Breadcrumb` (with `BreadcrumbList` / `BreadcrumbItem` / `BreadcrumbLink` / `BreadcrumbPage` / `BreadcrumbStatic` / `BreadcrumbSeparator` / `BreadcrumbEllipsis`) | `@open-mercato/ui/primitives/breadcrumb` |
45
46
  | Wrap a `<Link>` as button | `Button asChild` / `IconButton asChild` | — |
46
47
 
47
48
  ## Critical MUST rules (top of mind)
package/dist/ai/AiIcon.js CHANGED
@@ -7,7 +7,7 @@ function AiIcon({ className, ...props }) {
7
7
  {
8
8
  "aria-hidden": true,
9
9
  ...props,
10
- className: cn(className, "text-brand-violet")
10
+ className: cn(className, "text-accent-indigo")
11
11
  }
12
12
  );
13
13
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/ai/AiIcon.tsx"],
4
- "sourcesContent": ["import { Sparkles, type LucideProps } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport function AiIcon({ className, ...props }: LucideProps) {\n return (\n <Sparkles\n aria-hidden\n {...props}\n className={cn(className, 'text-brand-violet')}\n />\n )\n}\n"],
5
- "mappings": "AAKI;AALJ,SAAS,gBAAkC;AAC3C,SAAS,UAAU;AAEZ,SAAS,OAAO,EAAE,WAAW,GAAG,MAAM,GAAgB;AAC3D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW;AAAA,MACV,GAAG;AAAA,MACJ,WAAW,GAAG,WAAW,mBAAmB;AAAA;AAAA,EAC9C;AAEJ;",
4
+ "sourcesContent": ["import { Sparkles, type LucideProps } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\n\nexport function AiIcon({ className, ...props }: LucideProps) {\n return (\n <Sparkles\n aria-hidden\n {...props}\n className={cn(className, 'text-accent-indigo')}\n />\n )\n}\n"],
5
+ "mappings": "AAKI;AALJ,SAAS,gBAAkC;AAC3C,SAAS,UAAU;AAEZ,SAAS,OAAO,EAAE,WAAW,GAAG,MAAM,GAAgB;AAC3D,SACE;AAAA,IAAC;AAAA;AAAA,MACC,eAAW;AAAA,MACV,GAAG;AAAA,MACJ,WAAW,GAAG,WAAW,oBAAoB;AAAA;AAAA,EAC/C;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -4,8 +4,17 @@ import * as React from "react";
4
4
  import { createContext, useContext } from "react";
5
5
  import Link from "next/link";
6
6
  import Image from "next/image";
7
- import { ChevronDown, ChevronLeft } from "lucide-react";
7
+ import { ChevronDown, ChevronLeft, Home, PanelLeftClose, PanelLeftOpen, X } from "lucide-react";
8
8
  import { Button } from "../primitives/button.js";
9
+ import {
10
+ Breadcrumb as BreadcrumbNav,
11
+ BreadcrumbEllipsis,
12
+ BreadcrumbItem,
13
+ BreadcrumbLink,
14
+ BreadcrumbList,
15
+ BreadcrumbPage,
16
+ BreadcrumbSeparator
17
+ } from "../primitives/breadcrumb.js";
9
18
  import { IconButton } from "../primitives/icon-button.js";
10
19
  import { SearchInput } from "../primitives/search-input.js";
11
20
  import { FlashMessages } from "./FlashMessages.js";
@@ -283,6 +292,10 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
283
292
  useEventBridge();
284
293
  const resolvedProductName = productName ?? t("appShell.productName");
285
294
  const [mobileOpen, setMobileOpen] = React.useState(false);
295
+ const [mobileDrawerView, setMobileDrawerView] = React.useState("auto");
296
+ React.useEffect(() => {
297
+ if (!mobileOpen) setMobileDrawerView("auto");
298
+ }, [mobileOpen]);
286
299
  const [collapsed, setCollapsed] = React.useState(sidebarCollapsedDefault);
287
300
  const [navGroups, setNavGroups] = React.useState(resolvedGroups);
288
301
  const [openGroups, setOpenGroups] = React.useState(
@@ -474,7 +487,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
474
487
  "aria-label": t("appShell.goToDashboard"),
475
488
  children: [
476
489
  /* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 40, height: 40, className: "rounded-full shrink-0" }),
477
- !compact && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: resolvedProductName })
490
+ !compact && /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: resolvedProductName })
478
491
  ]
479
492
  }
480
493
  ) }),
@@ -577,7 +590,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
577
590
  "aria-label": t("appShell.goToDashboard"),
578
591
  children: [
579
592
  /* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 40, height: 40, className: "rounded-full shrink-0" }),
580
- !compact && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: resolvedProductName })
593
+ !compact && /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: resolvedProductName })
581
594
  ]
582
595
  }
583
596
  ) }) : null,
@@ -637,7 +650,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
637
650
  "aria-label": t("appShell.goToDashboard"),
638
651
  children: [
639
652
  /* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 40, height: 40, className: "rounded-full shrink-0" }),
640
- !compact && /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: resolvedProductName })
653
+ !compact && /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: resolvedProductName })
641
654
  ]
642
655
  }
643
656
  ) }),
@@ -877,165 +890,211 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
877
890
  [t, topbarInjectedMenuItems]
878
891
  );
879
892
  return /* @__PURE__ */ jsxs(HeaderContext.Provider, { value: headerCtxValue, children: [
880
- /* @__PURE__ */ jsxs("div", { className: `min-h-svh lg:grid transition-[grid-template-columns] duration-200 ease-out ${gridColsClass}`, children: [
881
- /* @__PURE__ */ jsxs("aside", { ref: sidebarAsideRef, className: `${asideClassesBase} ${effectiveCollapsed ? "px-2" : "px-3"} hidden lg:block lg:sticky lg:top-0 lg:h-svh lg:self-start lg:overflow-hidden lg:relative transition-[width,padding] duration-200 ease-out`, style: { width: asideWidth }, children: [
882
- renderSidebar(effectiveCollapsed, false, isSectionView),
883
- sidebarScrollState !== "none" ? /* @__PURE__ */ jsx(
884
- "div",
885
- {
886
- className: "pointer-events-none absolute inset-x-0 bottom-0 flex h-10 items-end justify-center bg-gradient-to-t from-background via-background/80 to-transparent pb-1.5",
887
- children: /* @__PURE__ */ jsx(
888
- IconButton,
889
- {
890
- type: "button",
891
- variant: "ghost",
892
- size: "sm",
893
- "data-testid": "sidebar-scroll-chevron",
894
- "data-sidebar-scroll-chevron": sidebarScrollState,
895
- "aria-label": sidebarScrollState === "up" ? t("ui.sidebar.chevron.scrollTop", "Scroll to top") : t("ui.sidebar.chevron.scrollBottom", "Scroll to bottom"),
896
- className: "pointer-events-auto text-muted-foreground/70 hover:text-foreground",
897
- onClick: () => handleSidebarChevronScroll(sidebarScrollState === "up" ? "top" : "bottom"),
898
- children: /* @__PURE__ */ jsx(
899
- "span",
900
- {
901
- className: `inline-flex transition-transform duration-300 ${sidebarScrollState === "up" ? "rotate-180" : ""}`,
902
- children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 animate-bounce" })
903
- }
904
- )
905
- }
906
- )
907
- }
908
- ) : null
909
- ] }),
910
- isSectionView ? /* @__PURE__ */ jsxs(
911
- "aside",
912
- {
913
- className: `${asideClassesBase} px-3 hidden lg:block lg:sticky lg:top-0 lg:h-svh lg:self-start lg:overflow-hidden lg:relative`,
914
- style: { width: "240px" },
915
- "data-testid": "appshell-section-sidebar",
916
- children: [
917
- renderSectionAside(),
918
- /* @__PURE__ */ jsx(
919
- "div",
920
- {
921
- "aria-hidden": true,
922
- className: "pointer-events-none absolute inset-x-0 bottom-0 h-10 bg-gradient-to-t from-background via-background/80 to-transparent"
923
- }
924
- )
925
- ]
926
- }
927
- ) : null,
928
- /* @__PURE__ */ jsxs("div", { className: "flex min-h-svh flex-col min-w-0", children: [
929
- /* @__PURE__ */ jsxs("header", { className: "border-b bg-background/80 px-3 lg:px-4 py-2 lg:py-3 flex items-center justify-between gap-2", children: [
893
+ /* @__PURE__ */ jsxs(
894
+ "div",
895
+ {
896
+ className: `relative min-h-svh lg:grid transition-[grid-template-columns] duration-200 ease-out ${gridColsClass}`,
897
+ style: { "--topbar-height": "61px" },
898
+ children: [
930
899
  /* @__PURE__ */ jsx(
931
- "div",
900
+ "button",
932
901
  {
933
- "data-testid": "backend-chrome-ready",
934
- "data-ready": isChromeReady ? "true" : "false",
935
- className: "hidden"
902
+ type: "button",
903
+ onClick: () => setCollapsed((c) => !c),
904
+ "aria-label": t("appShell.toggleSidebar"),
905
+ className: "hidden lg:flex fixed top-4 z-[var(--z-index-dropdown)] size-7 items-center justify-center rounded-md border bg-background text-muted-foreground shadow-sm transition-all hover:text-foreground hover:bg-muted focus:outline-none focus-visible:shadow-focus",
906
+ style: { left: `calc(${asideWidth} - 14px)` },
907
+ children: effectiveCollapsed ? /* @__PURE__ */ jsx(PanelLeftOpen, { className: "size-4" }) : /* @__PURE__ */ jsx(PanelLeftClose, { className: "size-4" })
936
908
  }
937
909
  ),
938
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
939
- /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "sm", className: "lg:hidden", "aria-label": t("appShell.openMenu"), onClick: () => setMobileOpen(true), children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M3 6h18M3 12h18M3 18h18" }) }) }),
940
- /* @__PURE__ */ jsx(
941
- IconButton,
910
+ /* @__PURE__ */ jsxs("aside", { ref: sidebarAsideRef, className: `${asideClassesBase} ${effectiveCollapsed ? "px-2" : "px-3"} hidden lg:block lg:sticky lg:top-0 lg:h-svh lg:self-start lg:overflow-hidden lg:relative transition-[width,padding] duration-200 ease-out`, style: { width: asideWidth }, children: [
911
+ renderSidebar(effectiveCollapsed, false, isSectionView),
912
+ sidebarScrollState !== "none" ? /* @__PURE__ */ jsx(
913
+ "div",
942
914
  {
943
- variant: "outline",
944
- size: "sm",
945
- className: "hidden lg:inline-flex",
946
- "aria-label": t("appShell.toggleSidebar"),
947
- onClick: () => setCollapsed((c) => !c),
948
- children: /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
949
- /* @__PURE__ */ jsx("rect", { x: "3", y: "4", width: "18", height: "16", rx: "2" }),
950
- /* @__PURE__ */ jsx("path", { d: "M9 4v16" })
951
- ] })
952
- }
953
- ),
954
- (() => {
955
- const dashboardLabel = t("dashboard.title");
956
- const root = [{ label: dashboardLabel, href: "/backend" }];
957
- let rest = [];
958
- if (headerBreadcrumb && headerBreadcrumb.length) {
959
- const first = headerBreadcrumb[0];
960
- const dup = first && (first.href === "/backend" || first.label === dashboardLabel || first.label?.toLowerCase() === "dashboard");
961
- rest = dup ? headerBreadcrumb.slice(1) : headerBreadcrumb;
962
- } else if (headerTitle) {
963
- rest = [{ label: headerTitle }];
915
+ className: "pointer-events-none absolute inset-x-0 bottom-0 flex h-10 items-end justify-center bg-gradient-to-t from-background via-background/80 to-transparent pb-1.5",
916
+ children: /* @__PURE__ */ jsx(
917
+ IconButton,
918
+ {
919
+ type: "button",
920
+ variant: "ghost",
921
+ size: "sm",
922
+ "data-testid": "sidebar-scroll-chevron",
923
+ "data-sidebar-scroll-chevron": sidebarScrollState,
924
+ "aria-label": sidebarScrollState === "up" ? t("ui.sidebar.chevron.scrollTop", "Scroll to top") : t("ui.sidebar.chevron.scrollBottom", "Scroll to bottom"),
925
+ className: "pointer-events-auto text-muted-foreground/70 hover:text-foreground",
926
+ onClick: () => handleSidebarChevronScroll(sidebarScrollState === "up" ? "top" : "bottom"),
927
+ children: /* @__PURE__ */ jsx(
928
+ "span",
929
+ {
930
+ className: `inline-flex transition-transform duration-300 ${sidebarScrollState === "up" ? "rotate-180" : ""}`,
931
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 animate-bounce" })
932
+ }
933
+ )
934
+ }
935
+ )
964
936
  }
965
- const items = [...root, ...rest];
966
- const lastIndex = items.length - 1;
967
- return /* @__PURE__ */ jsx("nav", { className: "flex items-center gap-2 text-sm min-w-0", children: items.map((b, i) => {
968
- const isLast = i === lastIndex;
969
- const hiddenOnMobile = !isLast ? "hidden md:inline" : "";
970
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
971
- i > 0 && /* @__PURE__ */ jsx("span", { className: `text-muted-foreground hidden md:inline`, children: "/" }),
972
- b.href ? /* @__PURE__ */ jsx(Link, { href: b.href, className: `text-muted-foreground hover:text-foreground ${hiddenOnMobile}`, children: b.label }) : /* @__PURE__ */ jsx("span", { className: `font-medium truncate max-w-[45vw] md:max-w-[60vw]`, children: b.label })
973
- ] }, i);
974
- }) });
975
- })()
937
+ ) : null
976
938
  ] }),
977
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 md:gap-2 text-sm shrink-0", children: [
978
- /* @__PURE__ */ jsx(
979
- StatusBadgeInjectionSpot,
980
- {
981
- spotId: GLOBAL_HEADER_STATUS_INDICATORS_INJECTION_SPOT_ID,
982
- context: injectionContext
983
- }
984
- ),
985
- /* @__PURE__ */ jsx(
986
- InjectionSpot,
987
- {
988
- spotId: BACKEND_TOPBAR_ACTIONS_INJECTION_SPOT_ID,
989
- context: injectionContext
990
- }
991
- ),
992
- renderedTopbarInjectedActions,
993
- /* @__PURE__ */ jsx(AiAssistantLauncher, { variant: "topbar" }),
994
- rightHeaderSlot ? rightHeaderSlot : /* @__PURE__ */ jsx("span", { className: "opacity-80", children: email || t("appShell.userFallback") })
995
- ] })
996
- ] }),
997
- /* @__PURE__ */ jsx(ProgressTopBar, { t, className: "sticky top-0 z-sticky" }),
998
- /* @__PURE__ */ jsxs("main", { className: "flex-1 p-4 lg:p-6 mx-auto w-full max-w-screen-2xl", children: [
999
- /* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID, context: injectionContext }),
1000
- /* @__PURE__ */ jsx(FlashMessages, {}),
1001
- /* @__PURE__ */ jsx(PartialIndexBanner, {}),
1002
- /* @__PURE__ */ jsx(UpgradeActionBanner, {}),
1003
- /* @__PURE__ */ jsx(LastOperationBanner, {}),
1004
- /* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID, context: injectionContext }),
1005
- /* @__PURE__ */ jsx(
1006
- InjectionSpot,
939
+ isSectionView ? /* @__PURE__ */ jsxs(
940
+ "aside",
1007
941
  {
1008
- spotId: LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID,
1009
- context: injectionContext
942
+ className: `${asideClassesBase} px-3 hidden lg:block lg:sticky lg:top-0 lg:h-svh lg:self-start lg:overflow-hidden lg:relative`,
943
+ style: { width: "240px" },
944
+ "data-testid": "appshell-section-sidebar",
945
+ children: [
946
+ renderSectionAside(),
947
+ /* @__PURE__ */ jsx(
948
+ "div",
949
+ {
950
+ "aria-hidden": true,
951
+ className: "pointer-events-none absolute inset-x-0 bottom-0 h-10 bg-gradient-to-t from-background via-background/80 to-transparent"
952
+ }
953
+ )
954
+ ]
1010
955
  }
1011
- ),
1012
- /* @__PURE__ */ jsx("div", { id: "om-top-banners", className: "mb-3 space-y-2" }),
1013
- children,
1014
- /* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID, context: injectionContext })
1015
- ] }),
1016
- /* @__PURE__ */ jsxs("footer", { className: "border-t bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/80 px-4 py-3 flex flex-wrap items-center justify-end gap-4", children: [
1017
- version ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("appShell.version", { version }) }) : null,
1018
- /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
1019
- /* @__PURE__ */ jsx(Link, { href: "/terms", className: "transition hover:text-foreground", children: t("common.terms") }),
1020
- /* @__PURE__ */ jsx(Link, { href: "/privacy", className: "transition hover:text-foreground", children: t("common.privacy") })
1021
- ] })
1022
- ] })
1023
- ] }),
1024
- mobileOpen && /* @__PURE__ */ jsxs("div", { className: "lg:hidden fixed inset-0 z-modal", children: [
1025
- /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/20", onClick: () => setMobileOpen(false), "aria-hidden": "true" }),
1026
- /* @__PURE__ */ jsxs("aside", { className: "absolute left-0 top-0 flex h-full w-[260px] flex-col bg-background border-r overflow-hidden", children: [
1027
- /* @__PURE__ */ jsxs("div", { className: "shrink-0 p-3 pb-2 flex items-center justify-between border-b", children: [
1028
- /* @__PURE__ */ jsxs(Link, { href: "/backend", className: "flex items-center gap-2 text-sm font-semibold", onClick: () => setMobileOpen(false), "aria-label": t("appShell.goToDashboard"), children: [
1029
- /* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 28, height: 28, className: "rounded" }),
1030
- resolvedProductName
956
+ ) : null,
957
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-svh flex-col min-w-0", children: [
958
+ /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-[var(--z-index-sticky)] border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 px-3 sm:px-4 lg:px-6 py-3 flex items-center justify-between gap-2 sm:gap-3", children: [
959
+ /* @__PURE__ */ jsx(
960
+ "div",
961
+ {
962
+ "data-testid": "backend-chrome-ready",
963
+ "data-ready": isChromeReady ? "true" : "false",
964
+ className: "hidden"
965
+ }
966
+ ),
967
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
968
+ /* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "sm", className: "lg:hidden", "aria-label": t("appShell.openMenu"), onClick: () => setMobileOpen(true), children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("path", { d: "M3 6h18M3 12h18M3 18h18" }) }) }),
969
+ (() => {
970
+ const dashboardLabel = t("dashboard.title");
971
+ const root = [{ label: dashboardLabel, href: "/backend" }];
972
+ let rest = [];
973
+ if (headerBreadcrumb && headerBreadcrumb.length) {
974
+ const first = headerBreadcrumb[0];
975
+ const dup = first && (first.href === "/backend" || first.label === dashboardLabel || first.label?.toLowerCase() === "dashboard");
976
+ rest = dup ? headerBreadcrumb.slice(1) : headerBreadcrumb;
977
+ } else if (headerTitle) {
978
+ rest = [{ label: headerTitle }];
979
+ }
980
+ const items = [...root, ...rest];
981
+ if (items.length === 0) return null;
982
+ const home = items[0];
983
+ const current = items.length > 1 ? items[items.length - 1] : null;
984
+ const mid = items.slice(1, -1);
985
+ const hasMid = mid.length > 0;
986
+ return /* @__PURE__ */ jsx(BreadcrumbNav, { divider: "arrow", className: "ml-2 lg:ml-3 text-sm", children: /* @__PURE__ */ jsxs(BreadcrumbList, { className: "[&_[data-slot=breadcrumb-separator]_svg]:size-4", children: [
987
+ /* @__PURE__ */ jsx(BreadcrumbItem, { children: home.href && current ? /* @__PURE__ */ jsx(BreadcrumbLink, { asChild: true, "aria-label": home.label, children: /* @__PURE__ */ jsx(Link, { href: home.href, children: /* @__PURE__ */ jsx(Home, { className: "size-4", "aria-hidden": "true" }) }) }) : /* @__PURE__ */ jsx(BreadcrumbPage, { "aria-label": home.label, children: /* @__PURE__ */ jsx(Home, { className: "size-4", "aria-hidden": "true" }) }) }),
988
+ current ? /* @__PURE__ */ jsxs(Fragment, { children: [
989
+ hasMid ? /* @__PURE__ */ jsxs(Fragment, { children: [
990
+ /* @__PURE__ */ jsx(BreadcrumbSeparator, { className: "md:hidden" }),
991
+ /* @__PURE__ */ jsx(BreadcrumbItem, { className: "md:hidden", children: /* @__PURE__ */ jsx(BreadcrumbEllipsis, { "aria-label": t("appShell.breadcrumb.collapsed", { count: mid.length }) }) }),
992
+ mid.map((b, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
993
+ /* @__PURE__ */ jsx(BreadcrumbSeparator, { className: "hidden md:inline-flex" }),
994
+ /* @__PURE__ */ jsx(BreadcrumbItem, { className: "hidden md:inline-flex", children: b.href ? /* @__PURE__ */ jsx(BreadcrumbLink, { asChild: true, title: b.label, children: /* @__PURE__ */ jsx(Link, { href: b.href, children: b.label }) }) : /* @__PURE__ */ jsx(BreadcrumbLink, { title: b.label, "aria-disabled": "true", tabIndex: -1, children: b.label }) })
995
+ ] }, `mid-${i}`))
996
+ ] }) : null,
997
+ /* @__PURE__ */ jsx(BreadcrumbSeparator, {}),
998
+ /* @__PURE__ */ jsx(BreadcrumbItem, { children: /* @__PURE__ */ jsx(BreadcrumbPage, { title: current.label, children: current.label }) })
999
+ ] }) : null
1000
+ ] }) });
1001
+ })()
1002
+ ] }),
1003
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2 md:gap-3 text-sm shrink-0", children: [
1004
+ /* @__PURE__ */ jsx(
1005
+ StatusBadgeInjectionSpot,
1006
+ {
1007
+ spotId: GLOBAL_HEADER_STATUS_INDICATORS_INJECTION_SPOT_ID,
1008
+ context: injectionContext
1009
+ }
1010
+ ),
1011
+ /* @__PURE__ */ jsx(
1012
+ InjectionSpot,
1013
+ {
1014
+ spotId: BACKEND_TOPBAR_ACTIONS_INJECTION_SPOT_ID,
1015
+ context: injectionContext
1016
+ }
1017
+ ),
1018
+ renderedTopbarInjectedActions,
1019
+ /* @__PURE__ */ jsx(AiAssistantLauncher, { variant: "topbar" }),
1020
+ rightHeaderSlot ? rightHeaderSlot : /* @__PURE__ */ jsx("span", { className: "opacity-80", children: email || t("appShell.userFallback") })
1021
+ ] })
1031
1022
  ] }),
1032
- /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "sm", onClick: () => setMobileOpen(false), "aria-label": t("appShell.closeMenu"), children: "\u2715" })
1023
+ /* @__PURE__ */ jsx(ProgressTopBar, { t, className: "sticky top-0 z-sticky" }),
1024
+ /* @__PURE__ */ jsxs("main", { className: "flex-1 p-4 lg:p-6 mx-auto w-full max-w-screen-2xl", children: [
1025
+ /* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID, context: injectionContext }),
1026
+ /* @__PURE__ */ jsx(FlashMessages, {}),
1027
+ /* @__PURE__ */ jsx(PartialIndexBanner, {}),
1028
+ /* @__PURE__ */ jsx(UpgradeActionBanner, {}),
1029
+ /* @__PURE__ */ jsx(LastOperationBanner, {}),
1030
+ /* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID, context: injectionContext }),
1031
+ /* @__PURE__ */ jsx(
1032
+ InjectionSpot,
1033
+ {
1034
+ spotId: LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID,
1035
+ context: injectionContext
1036
+ }
1037
+ ),
1038
+ /* @__PURE__ */ jsx("div", { id: "om-top-banners", className: "mb-3 space-y-2" }),
1039
+ children,
1040
+ /* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID, context: injectionContext })
1041
+ ] }),
1042
+ /* @__PURE__ */ jsxs("footer", { className: "border-t bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/80 px-4 py-3 flex flex-wrap items-center justify-end gap-4", children: [
1043
+ version ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("appShell.version", { version }) }) : null,
1044
+ /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
1045
+ /* @__PURE__ */ jsx(Link, { href: "/terms", className: "transition hover:text-foreground", children: t("common.terms") }),
1046
+ /* @__PURE__ */ jsx(Link, { href: "/privacy", className: "transition hover:text-foreground", children: t("common.privacy") })
1047
+ ] })
1048
+ ] })
1033
1049
  ] }),
1034
- mobileSidebarSlot && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-3 py-2", children: mobileSidebarSlot }),
1035
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto overflow-x-hidden p-3", children: renderSidebar(false, true) })
1036
- ] })
1037
- ] })
1038
- ] }),
1050
+ mobileOpen && /* @__PURE__ */ jsxs("div", { className: "lg:hidden fixed inset-0 z-modal", children: [
1051
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: () => setMobileOpen(false), "aria-hidden": "true" }),
1052
+ /* @__PURE__ */ jsxs("aside", { className: "absolute left-0 top-0 flex h-full w-[280px] max-w-[85vw] flex-col bg-background border-r shadow-lg overflow-hidden", children: [
1053
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center justify-between gap-2 border-b px-4 py-3", children: [
1054
+ /* @__PURE__ */ jsxs(Link, { href: "/backend", className: "flex items-center gap-2 min-w-0 text-sm font-semibold", onClick: () => setMobileOpen(false), "aria-label": t("appShell.goToDashboard"), children: [
1055
+ /* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 28, height: 28, className: "rounded shrink-0" }),
1056
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: resolvedProductName })
1057
+ ] }),
1058
+ /* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "sm", onClick: () => setMobileOpen(false), "aria-label": t("appShell.closeMenu"), children: /* @__PURE__ */ jsx(X, { className: "size-4" }) })
1059
+ ] }),
1060
+ mobileSidebarSlot && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-3 py-2", children: mobileSidebarSlot }),
1061
+ sidebarMode !== "main" ? /* @__PURE__ */ jsx("div", { className: "shrink-0 flex items-center gap-5 border-b px-4 pt-3 pb-0", role: "tablist", children: [
1062
+ { id: "main", label: t("backend.nav.main", "Main") },
1063
+ {
1064
+ id: "section",
1065
+ label: sidebarMode === "settings" ? settingsSectionTitle ?? t("backend.nav.settings", "Settings") : profileSectionTitle ?? t("backend.nav.profile", "Profile")
1066
+ }
1067
+ ].map((tab) => {
1068
+ const isActive = tab.id === "main" ? mobileDrawerView === "main" : mobileDrawerView === "auto";
1069
+ return /* @__PURE__ */ jsxs(
1070
+ "button",
1071
+ {
1072
+ type: "button",
1073
+ role: "tab",
1074
+ "aria-selected": isActive,
1075
+ onClick: () => setMobileDrawerView(tab.id === "main" ? "main" : "auto"),
1076
+ className: "relative inline-flex items-center pb-2 text-sm font-medium leading-5 tracking-tight transition-colors focus:outline-none data-[active=true]:text-foreground data-[active=false]:text-muted-foreground hover:text-foreground",
1077
+ "data-active": isActive,
1078
+ children: [
1079
+ /* @__PURE__ */ jsx("span", { children: tab.label }),
1080
+ isActive ? /* @__PURE__ */ jsx(
1081
+ "span",
1082
+ {
1083
+ className: "absolute -bottom-px left-0 right-0 h-0.5 bg-foreground",
1084
+ "aria-hidden": "true"
1085
+ }
1086
+ ) : null
1087
+ ]
1088
+ },
1089
+ tab.id
1090
+ );
1091
+ }) }) : null,
1092
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto overflow-x-hidden p-3", children: renderSidebar(false, true, mobileDrawerView === "main") })
1093
+ ] })
1094
+ ] })
1095
+ ]
1096
+ }
1097
+ ),
1039
1098
  /* @__PURE__ */ jsx(UmesDevToolsPanel, {})
1040
1099
  ] });
1041
1100
  }