@open-mercato/ui 0.5.1-develop.2996.ce62fd491c → 0.5.1-develop.3032.01699048cb

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.
@@ -4,7 +4,7 @@ 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, Search, X } from "lucide-react";
7
+ import { ChevronDown, ChevronLeft, Search, X } from "lucide-react";
8
8
  import { Button } from "../primitives/button.js";
9
9
  import { IconButton } from "../primitives/icon-button.js";
10
10
  import { Input } from "../primitives/input.js";
@@ -383,6 +383,20 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
383
383
  } catch {
384
384
  }
385
385
  }, [collapsed]);
386
+ const collapsedBeforeSectionRef = React.useRef(null);
387
+ const previousSidebarModeRef = React.useRef("main");
388
+ React.useEffect(() => {
389
+ const previous = previousSidebarModeRef.current;
390
+ if (previous === "main" && sidebarMode !== "main") {
391
+ collapsedBeforeSectionRef.current = collapsed;
392
+ if (!collapsed) setCollapsed(true);
393
+ } else if (previous !== "main" && sidebarMode === "main" && collapsedBeforeSectionRef.current !== null) {
394
+ const restoreTo = collapsedBeforeSectionRef.current;
395
+ collapsedBeforeSectionRef.current = null;
396
+ if (collapsed !== restoreTo) setCollapsed(restoreTo);
397
+ }
398
+ previousSidebarModeRef.current = sidebarMode;
399
+ }, [sidebarMode, collapsed]);
386
400
  React.useEffect(() => {
387
401
  try {
388
402
  localStorage.setItem("om:sidebarOpenGroups", JSON.stringify(openGroups));
@@ -410,7 +424,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
410
424
  React.useEffect(() => {
411
425
  setNavGroups(cloneSidebarGroups(resolvedGroups));
412
426
  }, [resolvedGroups]);
413
- function renderSectionSidebar(sections, title, compact, hideHeader) {
427
+ function renderSectionSidebar(sections, title, compact, hideHeader, hideSearch) {
414
428
  const sortedSections = [...sections].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
415
429
  const lastVisibleIndex = sortedSections.length - 1;
416
430
  return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col gap-3", children: [
@@ -426,7 +440,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
426
440
  ]
427
441
  }
428
442
  ) }),
429
- !compact && /* @__PURE__ */ jsx(
443
+ !compact && !hideSearch && /* @__PURE__ */ jsx(
430
444
  Input,
431
445
  {
432
446
  type: "text",
@@ -450,13 +464,14 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
450
464
  }
451
465
  ),
452
466
  /* @__PURE__ */ jsx("div", { "data-sidebar-scroll": "true", className: `flex flex-1 flex-col gap-3 overflow-y-auto scrollbar-hide pr-1 ${compact ? "-ml-2 pl-2" : "-ml-3 pl-3"}`, children: /* @__PURE__ */ jsx("nav", { className: "flex flex-col gap-2", children: sortedSections.map((section, sectionIndex) => {
467
+ const sectionNavQueryActive = hideSearch ? false : navQueryActive;
453
468
  const matchesItemQuery = (item) => {
454
- if (!navQueryActive) return true;
469
+ if (!sectionNavQueryActive) return true;
455
470
  const label = item.labelKey ? t(item.labelKey, item.label) : item.label;
456
471
  if (matchesQuery(label)) return true;
457
472
  return Array.isArray(item.children) && item.children.some(matchesItemQuery);
458
473
  };
459
- const visibleItems = navQueryActive ? section.items.filter(matchesItemQuery) : section.items;
474
+ const visibleItems = sectionNavQueryActive ? section.items.filter(matchesItemQuery) : section.items;
460
475
  if (visibleItems.length === 0) return null;
461
476
  const sortedItems = [...visibleItems].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
462
477
  const sectionLabel = section.labelKey ? t(section.labelKey, section.label) : section.label;
@@ -465,7 +480,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
465
480
  const sortSectionItems = (items = []) => [...items].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
466
481
  const filterChildren = (children2) => {
467
482
  if (!children2) return [];
468
- if (!navQueryActive) return [...children2];
483
+ if (!sectionNavQueryActive) return [...children2];
469
484
  return children2.filter(matchesItemQuery);
470
485
  };
471
486
  const renderSectionItem = (item, depth = 0) => {
@@ -473,7 +488,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
473
488
  const childItems = sortSectionItems(filterChildren(item.children));
474
489
  const isOnItemBranch = !!pathname && (pathname === item.href || pathname.startsWith(`${item.href}/`));
475
490
  const hasActiveChild = !!(pathname && childItems.some((child) => pathname === child.href || pathname.startsWith(`${child.href}/`)));
476
- const showChildren = childItems.length > 0 && (isOnItemBranch || navQueryActive);
491
+ const showChildren = childItems.length > 0 && (isOnItemBranch || sectionNavQueryActive);
477
492
  const isActive = isOnItemBranch || hasActiveChild;
478
493
  const base = compact ? "w-10 h-10 justify-center" : "w-full py-2 gap-2";
479
494
  const spacingStyle = !compact ? {
@@ -525,7 +540,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
525
540
  }) }) })
526
541
  ] });
527
542
  }
528
- function renderSidebar(compact, hideHeader) {
543
+ function renderSidebar(compact, hideHeader, forceMainOnly) {
529
544
  if (!isChromeReady && isChromeLoading && resolvedGroups.length === 0) {
530
545
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-full gap-3", "data-testid": "backend-chrome-loading", children: [
531
546
  !hideHeader ? /* @__PURE__ */ jsx("div", { className: "mb-2", children: /* @__PURE__ */ jsxs(
@@ -559,7 +574,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
559
574
  ] })
560
575
  ] });
561
576
  }
562
- if (sidebarMode === "settings" && resolvedSettingsSections && resolvedSettingsSections.length > 0) {
577
+ if (!forceMainOnly && sidebarMode === "settings" && resolvedSettingsSections && resolvedSettingsSections.length > 0) {
563
578
  const mergedSettingsSections = mergeSectionGroupsWithInjected(
564
579
  resolvedSettingsSections,
565
580
  settingsSidebarInjectedMenuItems,
@@ -572,7 +587,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
572
587
  hideHeader
573
588
  );
574
589
  }
575
- if (sidebarMode === "profile" && resolvedProfileSections && resolvedProfileSections.length > 0) {
590
+ if (!forceMainOnly && sidebarMode === "profile" && resolvedProfileSections && resolvedProfileSections.length > 0) {
576
591
  const mergedProfileSections = mergeSectionGroupsWithInjected(
577
592
  resolvedProfileSections,
578
593
  profileSidebarInjectedMenuItems,
@@ -774,7 +789,44 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
774
789
  ] })
775
790
  ] });
776
791
  }
777
- const gridColsClass = effectiveCollapsed ? "lg:grid-cols-[80px_1fr]" : "lg:grid-cols-[240px_1fr]";
792
+ function renderSectionAside() {
793
+ let sections = null;
794
+ let title = "";
795
+ if (sidebarMode === "settings" && resolvedSettingsSections && resolvedSettingsSections.length > 0) {
796
+ sections = mergeSectionGroupsWithInjected(
797
+ resolvedSettingsSections,
798
+ settingsSidebarInjectedMenuItems,
799
+ t
800
+ );
801
+ title = settingsSectionTitle ?? t("backend.nav.settings", "Settings");
802
+ } else if (sidebarMode === "profile" && resolvedProfileSections && resolvedProfileSections.length > 0) {
803
+ sections = mergeSectionGroupsWithInjected(
804
+ resolvedProfileSections,
805
+ profileSidebarInjectedMenuItems,
806
+ t
807
+ );
808
+ title = profileSectionTitle ?? t("backend.nav.profile", "Profile");
809
+ }
810
+ if (!sections) return null;
811
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col gap-2", children: [
812
+ /* @__PURE__ */ jsxs(
813
+ Link,
814
+ {
815
+ href: "/backend",
816
+ className: "inline-flex items-center gap-2 rounded-lg px-2 py-2 text-sm font-semibold text-foreground transition-colors hover:bg-muted",
817
+ "data-testid": "appshell-section-back-to-main",
818
+ "aria-label": t("backend.nav.backToMain", "Back to Main"),
819
+ children: [
820
+ /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4 shrink-0", "aria-hidden": true }),
821
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: title })
822
+ ]
823
+ }
824
+ ),
825
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: renderSectionSidebar(sections, title, false, true, true) })
826
+ ] });
827
+ }
828
+ const isSectionView = sidebarMode === "settings" && !!resolvedSettingsSections && resolvedSettingsSections.length > 0 || sidebarMode === "profile" && !!resolvedProfileSections && resolvedProfileSections.length > 0;
829
+ const gridColsClass = isSectionView ? effectiveCollapsed ? "lg:grid-cols-[80px_240px_1fr]" : "lg:grid-cols-[240px_240px_1fr]" : effectiveCollapsed ? "lg:grid-cols-[80px_1fr]" : "lg:grid-cols-[240px_1fr]";
778
830
  const headerCtxValue = React.useMemo(() => ({
779
831
  setBreadcrumb: setHeaderBreadcrumb,
780
832
  setTitle: setHeaderTitle
@@ -811,9 +863,9 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
811
863
  [t, topbarInjectedMenuItems]
812
864
  );
813
865
  return /* @__PURE__ */ jsxs(HeaderContext.Provider, { value: headerCtxValue, children: [
814
- /* @__PURE__ */ jsxs("div", { className: `min-h-svh lg:grid ${gridColsClass}`, children: [
815
- /* @__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`, style: { width: asideWidth }, children: [
816
- renderSidebar(effectiveCollapsed),
866
+ /* @__PURE__ */ jsxs("div", { className: `min-h-svh lg:grid transition-[grid-template-columns] duration-200 ease-out ${gridColsClass}`, children: [
867
+ /* @__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: [
868
+ renderSidebar(effectiveCollapsed, false, isSectionView),
817
869
  sidebarScrollState !== "none" ? /* @__PURE__ */ jsx(
818
870
  "div",
819
871
  {
@@ -829,6 +881,24 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
829
881
  }
830
882
  ) : null
831
883
  ] }),
884
+ isSectionView ? /* @__PURE__ */ jsxs(
885
+ "aside",
886
+ {
887
+ className: `${asideClassesBase} px-3 hidden lg:block lg:sticky lg:top-0 lg:h-svh lg:self-start lg:overflow-hidden lg:relative`,
888
+ style: { width: "240px" },
889
+ "data-testid": "appshell-section-sidebar",
890
+ children: [
891
+ renderSectionAside(),
892
+ /* @__PURE__ */ jsx(
893
+ "div",
894
+ {
895
+ "aria-hidden": true,
896
+ className: "pointer-events-none absolute inset-x-0 bottom-0 h-10 bg-gradient-to-t from-background via-background/80 to-transparent"
897
+ }
898
+ )
899
+ ]
900
+ }
901
+ ) : null,
832
902
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-svh flex-col min-w-0", children: [
833
903
  /* @__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: [
834
904
  /* @__PURE__ */ jsx(