@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.
- package/dist/backend/AppShell.js +84 -14
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/CrudForm.js +6 -0
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/primitives/popover.js +1 -1
- package/dist/primitives/popover.js.map +1 -1
- package/package.json +3 -3
- package/src/backend/AppShell.tsx +98 -17
- package/src/backend/CrudForm.tsx +6 -0
- package/src/backend/__tests__/AppShell.test.tsx +153 -1
- package/src/primitives/popover.tsx +1 -1
package/dist/backend/AppShell.js
CHANGED
|
@@ -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 (!
|
|
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 =
|
|
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 (!
|
|
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 ||
|
|
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
|
-
|
|
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(
|