@pablozaiden/webapp 0.3.6 → 0.3.7
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/package.json +1 -1
- package/src/web/WebAppRoot.tsx +31 -11
package/package.json
CHANGED
package/src/web/WebAppRoot.tsx
CHANGED
|
@@ -531,6 +531,17 @@ function DeviceVerificationScreen() {
|
|
|
531
531
|
|
|
532
532
|
type SidebarTreeParentKind = "root" | "section" | "item";
|
|
533
533
|
|
|
534
|
+
type SidebarTreeProps = {
|
|
535
|
+
nodes: SidebarNode[];
|
|
536
|
+
route: WebAppRoute;
|
|
537
|
+
navigate: (route: WebAppRoute) => void;
|
|
538
|
+
collapsed: SidebarCollapsedState;
|
|
539
|
+
toggleCollapsed: (id: string, isCollapsed: boolean) => void;
|
|
540
|
+
searchActive: boolean;
|
|
541
|
+
level?: number;
|
|
542
|
+
parentKind?: SidebarTreeParentKind;
|
|
543
|
+
};
|
|
544
|
+
|
|
534
545
|
function sidebarIndentStyle(level: number, parentKind: SidebarTreeParentKind): { marginLeft?: string } | undefined {
|
|
535
546
|
if (level <= 0) {
|
|
536
547
|
return undefined;
|
|
@@ -540,19 +551,26 @@ function sidebarIndentStyle(level: number, parentKind: SidebarTreeParentKind): {
|
|
|
540
551
|
return { marginLeft: `${baseIndentRem + nestedSectionIndentRem}rem` };
|
|
541
552
|
}
|
|
542
553
|
|
|
543
|
-
function SidebarTree({ nodes, route, navigate, collapsed, toggleCollapsed, level = 0, parentKind = "root" }:
|
|
554
|
+
function SidebarTree({ nodes, route, navigate, collapsed, toggleCollapsed, searchActive, level = 0, parentKind = "root" }: SidebarTreeProps) {
|
|
544
555
|
const [contextMenu, setContextMenu] = useState<{ position: ContextMenuPosition; items: ActionMenuItem[]; title: string } | null>(null);
|
|
545
556
|
return (
|
|
546
557
|
<>
|
|
547
558
|
{nodes.map((node) => {
|
|
548
559
|
const hasChildren = Boolean(node.children?.length);
|
|
549
|
-
const
|
|
560
|
+
const storedIsCollapsed = collapsed[node.id] ?? node.defaultCollapsed ?? false;
|
|
561
|
+
const isCollapsed = searchActive && hasChildren ? false : storedIsCollapsed;
|
|
562
|
+
const toggleAriaLabel = searchActive ? `Toggling unavailable during search for ${node.title}` : `${isCollapsed ? "Expand" : "Collapse"} ${node.title}`;
|
|
563
|
+
const toggleNodeCollapsed = () => {
|
|
564
|
+
if (!searchActive) {
|
|
565
|
+
toggleCollapsed(node.id, storedIsCollapsed);
|
|
566
|
+
}
|
|
567
|
+
};
|
|
550
568
|
if (node.type === "section") {
|
|
551
569
|
return (
|
|
552
570
|
<section className={`wapp-sidebar-section ${level === 0 ? "top" : "nested"}`} key={node.id}>
|
|
553
571
|
<div className="wapp-sidebar-section-title" style={sidebarIndentStyle(level, parentKind)}>
|
|
554
572
|
{hasChildren ? (
|
|
555
|
-
<button type="button" aria-expanded={!isCollapsed} aria-label={
|
|
573
|
+
<button type="button" aria-expanded={!isCollapsed} aria-label={toggleAriaLabel} disabled={searchActive} onClick={toggleNodeCollapsed}>
|
|
556
574
|
<span>{isCollapsed ? "▶" : "▼"}</span>{node.title}
|
|
557
575
|
</button>
|
|
558
576
|
) : (
|
|
@@ -560,7 +578,7 @@ function SidebarTree({ nodes, route, navigate, collapsed, toggleCollapsed, level
|
|
|
560
578
|
)}
|
|
561
579
|
{node.action ? <button type="button" className="wapp-sidebar-action" title={node.action.title} aria-label={node.action.title} onClick={node.action.onAction ?? (() => node.action?.route && navigate(node.action.route))}>{node.action.label ?? "New"}</button> : null}
|
|
562
580
|
</div>
|
|
563
|
-
{!isCollapsed && hasChildren ? <SidebarTree nodes={node.children ?? []} route={route} navigate={navigate} collapsed={collapsed} toggleCollapsed={toggleCollapsed} level={level + 1} parentKind="section" /> : null}
|
|
581
|
+
{!isCollapsed && hasChildren ? <SidebarTree nodes={node.children ?? []} route={route} navigate={navigate} collapsed={collapsed} toggleCollapsed={toggleCollapsed} searchActive={searchActive} level={level + 1} parentKind="section" /> : null}
|
|
564
582
|
{!isCollapsed && !hasChildren && level === 0 ? <div className="wapp-sidebar-empty">No items.</div> : null}
|
|
565
583
|
</section>
|
|
566
584
|
);
|
|
@@ -568,7 +586,7 @@ function SidebarTree({ nodes, route, navigate, collapsed, toggleCollapsed, level
|
|
|
568
586
|
const active = node.route?.view === route.view && Object.entries(node.route).every(([key, value]) => key === "view" || route[key] === value);
|
|
569
587
|
return (
|
|
570
588
|
<div className={`wapp-sidebar-item-wrap ${hasChildren ? "has-toggle" : ""}`} key={node.id} style={sidebarIndentStyle(level, parentKind)}>
|
|
571
|
-
{hasChildren ? <button type="button" className="wapp-tree-toggle" aria-expanded={!isCollapsed} aria-label={
|
|
589
|
+
{hasChildren ? <button type="button" className="wapp-tree-toggle" aria-expanded={!isCollapsed} aria-label={toggleAriaLabel} disabled={searchActive} onClick={toggleNodeCollapsed}>{isCollapsed ? "▶" : "▼"}</button> : null}
|
|
572
590
|
<button
|
|
573
591
|
type="button"
|
|
574
592
|
className={`wapp-sidebar-item ${active ? "active" : ""}`}
|
|
@@ -585,7 +603,7 @@ function SidebarTree({ nodes, route, navigate, collapsed, toggleCollapsed, level
|
|
|
585
603
|
</span>
|
|
586
604
|
{node.badge ? <Badge variant={node.badgeVariant} className="wapp-sidebar-badge" title={node.badge} aria-label={node.badge}> </Badge> : null}
|
|
587
605
|
</button>
|
|
588
|
-
{!isCollapsed && node.children ? <div className="wapp-sidebar-children"><SidebarTree nodes={node.children} route={route} navigate={navigate} collapsed={collapsed} toggleCollapsed={toggleCollapsed} level={level + 1} parentKind="item" /></div> : null}
|
|
606
|
+
{!isCollapsed && node.children ? <div className="wapp-sidebar-children"><SidebarTree nodes={node.children} route={route} navigate={navigate} collapsed={collapsed} toggleCollapsed={toggleCollapsed} searchActive={searchActive} level={level + 1} parentKind="item" /></div> : null}
|
|
589
607
|
</div>
|
|
590
608
|
);
|
|
591
609
|
})}
|
|
@@ -1040,10 +1058,12 @@ export function WebAppRoot({ appName, homeRoute, sidebar, routes, header, onRout
|
|
|
1040
1058
|
});
|
|
1041
1059
|
}, []);
|
|
1042
1060
|
const sidebarSearchEnabled = sidebar.search !== false;
|
|
1061
|
+
const normalizedSidebarSearch = sidebarSearchEnabled ? search.trim() : "";
|
|
1062
|
+
const sidebarSearchActive = normalizedSidebarSearch.length > 0;
|
|
1043
1063
|
const pinningEnabled = sidebar.pinning !== false;
|
|
1044
1064
|
const sidebarPins = useSidebarPins(appName, sidebar.pinning ? sidebar.pinning.storageKey : undefined);
|
|
1045
1065
|
const baseNodes = useMemo(() => sidebar.getNodes({ search: "" }), [sidebar]);
|
|
1046
|
-
const filteredNodes = useMemo(() => sidebar.getNodes({ search:
|
|
1066
|
+
const filteredNodes = useMemo(() => sidebar.getNodes({ search: normalizedSidebarSearch }), [sidebar, normalizedSidebarSearch]);
|
|
1047
1067
|
const allPinnableItems = useMemo(() => flattenSidebarItems(baseNodes).filter((node) => node.pinnable && node.route), [baseNodes]);
|
|
1048
1068
|
const currentPins = useMemo(() => {
|
|
1049
1069
|
const byId = new Map(allPinnableItems.map((node) => [node.pinId ?? node.id, node]));
|
|
@@ -1071,7 +1091,7 @@ export function WebAppRoot({ appName, homeRoute, sidebar, routes, header, onRout
|
|
|
1071
1091
|
}), [pinningActionFor]);
|
|
1072
1092
|
const nodes = useMemo(() => {
|
|
1073
1093
|
const augmented = augmentPinningActions(filteredNodes);
|
|
1074
|
-
if (!pinningEnabled ||
|
|
1094
|
+
if (!pinningEnabled || sidebarSearchActive || currentPins.length === 0) return augmented;
|
|
1075
1095
|
const augmentedByPinId = new Map(flattenSidebarItems(augmentPinningActions(baseNodes)).map((node) => [node.pinId ?? node.id, node]));
|
|
1076
1096
|
const pinnedChildren = currentPins.map((pin) => ({
|
|
1077
1097
|
...(augmentedByPinId.get(pin.id) ?? {
|
|
@@ -1091,7 +1111,7 @@ export function WebAppRoot({ appName, homeRoute, sidebar, routes, header, onRout
|
|
|
1091
1111
|
{ type: "section" as const, id: "framework:pinned", title: sidebar.pinning ? sidebar.pinning.sectionTitle ?? "Pinned" : "Pinned", children: pinnedChildren },
|
|
1092
1112
|
...augmented,
|
|
1093
1113
|
];
|
|
1094
|
-
}, [augmentPinningActions, baseNodes, currentPins, filteredNodes, pinningEnabled,
|
|
1114
|
+
}, [augmentPinningActions, baseNodes, currentPins, filteredNodes, pinningEnabled, sidebar.pinning, sidebarSearchActive]);
|
|
1095
1115
|
|
|
1096
1116
|
useEffect(() => {
|
|
1097
1117
|
if (!config?.currentUser) return;
|
|
@@ -1192,8 +1212,8 @@ export function WebAppRoot({ appName, homeRoute, sidebar, routes, header, onRout
|
|
|
1192
1212
|
</div>
|
|
1193
1213
|
</div>
|
|
1194
1214
|
<div className="wapp-sidebar-scroll">
|
|
1195
|
-
{sidebarSearchEnabled ? <label className="wapp-search"><span className="sr-only">Search</span><input value={search}
|
|
1196
|
-
<SidebarTree nodes={nodes} route={route} navigate={(next) => { navigate(next); setSidebarOpen(false); }} collapsed={sidebarTreeState.collapsed} toggleCollapsed={sidebarTreeState.toggleCollapsed} />
|
|
1215
|
+
{sidebarSearchEnabled ? <label className="wapp-search"><span className="sr-only">Search</span><input value={search} onInput={(event) => setSearch(event.currentTarget.value)} placeholder="Search" /></label> : null}
|
|
1216
|
+
<SidebarTree nodes={nodes} route={route} navigate={(next) => { navigate(next); setSidebarOpen(false); }} collapsed={sidebarTreeState.collapsed} toggleCollapsed={sidebarTreeState.toggleCollapsed} searchActive={sidebarSearchActive} />
|
|
1197
1217
|
<div className="wapp-sidebar-footer">v{effectiveVersion}<button type="button" aria-label="Reload" onClick={() => window.location.reload()}><Icon name="refresh" /></button></div>
|
|
1198
1218
|
</div>
|
|
1199
1219
|
</aside>
|