@open-mercato/ui 0.6.2-develop.3372.1.de9a169973 → 0.6.2-develop.3373.1.be847b1058

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 +223 -153
  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 +3 -3
  20. package/src/ai/AiIcon.tsx +1 -1
  21. package/src/backend/AppShell.tsx +151 -49
  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 +152 -0
  29. package/src/primitives/breadcrumb.tsx +185 -0
  30. package/src/primitives/sheet.tsx +165 -0
@@ -1,3 +1,3 @@
1
1
  Generated lucide registry with 135 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
  ) }),
@@ -866,7 +879,6 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
866
879
  type: "button",
867
880
  variant: "outline",
868
881
  size: "sm",
869
- className: "h-7 text-xs",
870
882
  "data-menu-item-id": item.id,
871
883
  onClick: () => item.onClick?.(),
872
884
  children: label
@@ -877,165 +889,223 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
877
889
  [t, topbarInjectedMenuItems]
878
890
  );
879
891
  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: [
892
+ /* @__PURE__ */ jsxs(
893
+ "div",
894
+ {
895
+ className: `relative min-h-svh lg:grid transition-[grid-template-columns] duration-200 ease-out ${gridColsClass}`,
896
+ style: { "--topbar-height": "61px" },
897
+ children: [
930
898
  /* @__PURE__ */ jsx(
931
- "div",
899
+ "button",
932
900
  {
933
- "data-testid": "backend-chrome-ready",
934
- "data-ready": isChromeReady ? "true" : "false",
935
- className: "hidden"
901
+ type: "button",
902
+ onClick: () => setCollapsed((c) => !c),
903
+ "aria-label": t("appShell.toggleSidebar"),
904
+ className: "hidden lg:flex fixed top-4 z-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",
905
+ style: { left: `calc(${asideWidth} - 14px)` },
906
+ children: effectiveCollapsed ? /* @__PURE__ */ jsx(PanelLeftOpen, { className: "size-4" }) : /* @__PURE__ */ jsx(PanelLeftClose, { className: "size-4" })
936
907
  }
937
908
  ),
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,
909
+ /* @__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: [
910
+ renderSidebar(effectiveCollapsed, false, isSectionView),
911
+ sidebarScrollState !== "none" ? /* @__PURE__ */ jsx(
912
+ "div",
942
913
  {
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 }];
914
+ 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",
915
+ children: /* @__PURE__ */ jsx(
916
+ IconButton,
917
+ {
918
+ type: "button",
919
+ variant: "ghost",
920
+ size: "sm",
921
+ "data-testid": "sidebar-scroll-chevron",
922
+ "data-sidebar-scroll-chevron": sidebarScrollState,
923
+ "aria-label": sidebarScrollState === "up" ? t("ui.sidebar.chevron.scrollTop", "Scroll to top") : t("ui.sidebar.chevron.scrollBottom", "Scroll to bottom"),
924
+ className: "pointer-events-auto text-muted-foreground/70 hover:text-foreground",
925
+ onClick: () => handleSidebarChevronScroll(sidebarScrollState === "up" ? "top" : "bottom"),
926
+ children: /* @__PURE__ */ jsx(
927
+ "span",
928
+ {
929
+ className: `inline-flex transition-transform duration-300 ${sidebarScrollState === "up" ? "rotate-180" : ""}`,
930
+ children: /* @__PURE__ */ jsx(ChevronDown, { className: "size-4 animate-bounce" })
931
+ }
932
+ )
933
+ }
934
+ )
964
935
  }
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
- })()
936
+ ) : null
976
937
  ] }),
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,
938
+ isSectionView ? /* @__PURE__ */ jsxs(
939
+ "aside",
1007
940
  {
1008
- spotId: LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID,
1009
- context: injectionContext
941
+ className: `${asideClassesBase} px-3 hidden lg:block lg:sticky lg:top-0 lg:h-svh lg:self-start lg:overflow-hidden lg:relative`,
942
+ style: { width: "240px" },
943
+ "data-testid": "appshell-section-sidebar",
944
+ children: [
945
+ renderSectionAside(),
946
+ /* @__PURE__ */ jsx(
947
+ "div",
948
+ {
949
+ "aria-hidden": true,
950
+ className: "pointer-events-none absolute inset-x-0 bottom-0 h-10 bg-gradient-to-t from-background via-background/80 to-transparent"
951
+ }
952
+ )
953
+ ]
1010
954
  }
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
955
+ ) : null,
956
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-svh flex-col min-w-0", children: [
957
+ /* @__PURE__ */ jsxs("header", { className: "sticky top-0 z-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: [
958
+ /* @__PURE__ */ jsx(
959
+ "div",
960
+ {
961
+ "data-testid": "backend-chrome-ready",
962
+ "data-ready": isChromeReady ? "true" : "false",
963
+ className: "hidden"
964
+ }
965
+ ),
966
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
967
+ /* @__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" }) }) }),
968
+ (() => {
969
+ const dashboardLabel = t("dashboard.title");
970
+ const root = [{ label: dashboardLabel, href: "/backend" }];
971
+ let rest = [];
972
+ if (headerBreadcrumb && headerBreadcrumb.length) {
973
+ const first = headerBreadcrumb[0];
974
+ const dup = first && (first.href === "/backend" || first.label === dashboardLabel || first.label?.toLowerCase() === "dashboard");
975
+ rest = dup ? headerBreadcrumb.slice(1) : headerBreadcrumb;
976
+ } else if (headerTitle) {
977
+ rest = [{ label: headerTitle }];
978
+ }
979
+ const items = [...root, ...rest];
980
+ if (items.length === 0) return null;
981
+ const home = items[0];
982
+ const current = items.length > 1 ? items[items.length - 1] : null;
983
+ const mid = items.slice(1, -1);
984
+ const hasMid = mid.length > 0;
985
+ 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: [
986
+ /* @__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" }) }) }),
987
+ current ? /* @__PURE__ */ jsxs(Fragment, { children: [
988
+ hasMid ? /* @__PURE__ */ jsxs(Fragment, { children: [
989
+ /* @__PURE__ */ jsx(BreadcrumbSeparator, { className: "md:hidden" }),
990
+ /* @__PURE__ */ jsx(BreadcrumbItem, { className: "md:hidden", children: /* @__PURE__ */ jsx(BreadcrumbEllipsis, { "aria-label": t("appShell.breadcrumb.collapsed", { count: mid.length }) }) }),
991
+ mid.map((b, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
992
+ /* @__PURE__ */ jsx(BreadcrumbSeparator, { className: "hidden md:inline-flex" }),
993
+ /* @__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 }) })
994
+ ] }, `mid-${i}`))
995
+ ] }) : null,
996
+ /* @__PURE__ */ jsx(BreadcrumbSeparator, {}),
997
+ /* @__PURE__ */ jsx(BreadcrumbItem, { children: /* @__PURE__ */ jsx(BreadcrumbPage, { title: current.label, children: current.label }) })
998
+ ] }) : null
999
+ ] }) });
1000
+ })()
1001
+ ] }),
1002
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2 md:gap-3 text-sm shrink-0", children: [
1003
+ /* @__PURE__ */ jsx(
1004
+ StatusBadgeInjectionSpot,
1005
+ {
1006
+ spotId: GLOBAL_HEADER_STATUS_INDICATORS_INJECTION_SPOT_ID,
1007
+ context: injectionContext
1008
+ }
1009
+ ),
1010
+ /* @__PURE__ */ jsx(
1011
+ InjectionSpot,
1012
+ {
1013
+ spotId: BACKEND_TOPBAR_ACTIONS_INJECTION_SPOT_ID,
1014
+ context: injectionContext
1015
+ }
1016
+ ),
1017
+ renderedTopbarInjectedActions,
1018
+ /* @__PURE__ */ jsx(AiAssistantLauncher, { variant: "topbar" }),
1019
+ rightHeaderSlot ? rightHeaderSlot : /* @__PURE__ */ jsx("span", { className: "opacity-80", children: email || t("appShell.userFallback") })
1020
+ ] })
1031
1021
  ] }),
1032
- /* @__PURE__ */ jsx(IconButton, { variant: "outline", size: "sm", onClick: () => setMobileOpen(false), "aria-label": t("appShell.closeMenu"), children: "\u2715" })
1022
+ /* @__PURE__ */ jsx(ProgressTopBar, { t, className: "sticky top-0 z-sticky" }),
1023
+ /* @__PURE__ */ jsxs("main", { className: "flex-1 p-4 lg:p-6 mx-auto w-full max-w-screen-2xl", children: [
1024
+ /* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID, context: injectionContext }),
1025
+ /* @__PURE__ */ jsx(FlashMessages, {}),
1026
+ /* @__PURE__ */ jsx(PartialIndexBanner, {}),
1027
+ /* @__PURE__ */ jsx(UpgradeActionBanner, {}),
1028
+ /* @__PURE__ */ jsx(LastOperationBanner, {}),
1029
+ /* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_RECORD_CURRENT_INJECTION_SPOT_ID, context: injectionContext }),
1030
+ /* @__PURE__ */ jsx(
1031
+ InjectionSpot,
1032
+ {
1033
+ spotId: LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID,
1034
+ context: injectionContext
1035
+ }
1036
+ ),
1037
+ /* @__PURE__ */ jsx("div", { id: "om-top-banners", className: "mb-3 space-y-2" }),
1038
+ children,
1039
+ /* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID, context: injectionContext })
1040
+ ] }),
1041
+ /* @__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: [
1042
+ version ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("appShell.version", { version }) }) : null,
1043
+ /* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
1044
+ /* @__PURE__ */ jsx(Link, { href: "/terms", className: "transition hover:text-foreground", children: t("common.terms") }),
1045
+ /* @__PURE__ */ jsx(Link, { href: "/privacy", className: "transition hover:text-foreground", children: t("common.privacy") })
1046
+ ] })
1047
+ ] })
1033
1048
  ] }),
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
- ] }),
1049
+ mobileOpen && /* @__PURE__ */ jsxs("div", { className: "lg:hidden fixed inset-0 z-modal", children: [
1050
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50 backdrop-blur-sm", onClick: () => setMobileOpen(false), "aria-hidden": "true" }),
1051
+ /* @__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: [
1052
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 flex items-center justify-between gap-2 border-b px-4 py-3", children: [
1053
+ /* @__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: [
1054
+ /* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 28, height: 28, className: "rounded shrink-0" }),
1055
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: resolvedProductName })
1056
+ ] }),
1057
+ /* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "sm", onClick: () => setMobileOpen(false), "aria-label": t("appShell.closeMenu"), children: /* @__PURE__ */ jsx(X, { className: "size-4" }) })
1058
+ ] }),
1059
+ mobileSidebarSlot && /* @__PURE__ */ jsx("div", { className: "shrink-0 border-b px-3 py-2", children: mobileSidebarSlot }),
1060
+ sidebarMode !== "main" ? /* @__PURE__ */ jsx("div", { className: "shrink-0 flex items-center gap-5 border-b px-4 pt-3 pb-0", role: "tablist", children: [
1061
+ { id: "main", label: t("backend.nav.main", "Main") },
1062
+ {
1063
+ id: "section",
1064
+ label: sidebarMode === "settings" ? settingsSectionTitle ?? t("backend.nav.settings", "Settings") : profileSectionTitle ?? t("backend.nav.profile", "Profile")
1065
+ }
1066
+ ].map((tab) => {
1067
+ const isActive = tab.id === "main" ? mobileDrawerView === "main" : mobileDrawerView === "auto";
1068
+ const tabId = `mobile-drawer-tab-${tab.id}`;
1069
+ return /* @__PURE__ */ jsxs(
1070
+ "button",
1071
+ {
1072
+ id: tabId,
1073
+ type: "button",
1074
+ role: "tab",
1075
+ "aria-selected": isActive,
1076
+ "aria-controls": "mobile-drawer-tabpanel",
1077
+ onClick: () => setMobileDrawerView(tab.id === "main" ? "main" : "auto"),
1078
+ 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",
1079
+ "data-active": isActive,
1080
+ children: [
1081
+ /* @__PURE__ */ jsx("span", { children: tab.label }),
1082
+ isActive ? /* @__PURE__ */ jsx(
1083
+ "span",
1084
+ {
1085
+ className: "absolute -bottom-px left-0 right-0 h-0.5 bg-foreground",
1086
+ "aria-hidden": "true"
1087
+ }
1088
+ ) : null
1089
+ ]
1090
+ },
1091
+ tab.id
1092
+ );
1093
+ }) }) : null,
1094
+ /* @__PURE__ */ jsx(
1095
+ "div",
1096
+ {
1097
+ id: "mobile-drawer-tabpanel",
1098
+ role: sidebarMode !== "main" ? "tabpanel" : void 0,
1099
+ "aria-labelledby": sidebarMode !== "main" ? `mobile-drawer-tab-${mobileDrawerView === "main" ? "main" : "section"}` : void 0,
1100
+ className: "min-h-0 flex-1 overflow-y-auto overflow-x-hidden p-3",
1101
+ children: renderSidebar(false, true, mobileDrawerView === "main")
1102
+ }
1103
+ )
1104
+ ] })
1105
+ ] })
1106
+ ]
1107
+ }
1108
+ ),
1039
1109
  /* @__PURE__ */ jsx(UmesDevToolsPanel, {})
1040
1110
  ] });
1041
1111
  }