@pablozaiden/webapp 0.3.5 → 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/src/web/styles.css +43 -19
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>
|
package/src/web/styles.css
CHANGED
|
@@ -1072,9 +1072,10 @@ textarea::placeholder {
|
|
|
1072
1072
|
}
|
|
1073
1073
|
|
|
1074
1074
|
.wapp-badge {
|
|
1075
|
+
--wapp-badge-fg: var(--wapp-muted);
|
|
1075
1076
|
border-radius: 999px;
|
|
1076
1077
|
background: color-mix(in srgb, var(--wapp-muted), transparent 86%);
|
|
1077
|
-
color: var(--wapp-
|
|
1078
|
+
color: var(--wapp-badge-fg);
|
|
1078
1079
|
font-size: 0.6875rem;
|
|
1079
1080
|
font-weight: 700;
|
|
1080
1081
|
padding: 0.125rem 0.5rem;
|
|
@@ -1093,61 +1094,73 @@ textarea::placeholder {
|
|
|
1093
1094
|
|
|
1094
1095
|
.wapp-badge-success,
|
|
1095
1096
|
.wapp-badge-completed {
|
|
1097
|
+
--wapp-badge-fg: rgb(22 101 52);
|
|
1096
1098
|
background: rgb(220 252 231);
|
|
1097
|
-
color:
|
|
1099
|
+
color: var(--wapp-badge-fg);
|
|
1098
1100
|
}
|
|
1099
1101
|
.wapp-badge-warning,
|
|
1100
1102
|
.wapp-badge-plan_ready {
|
|
1103
|
+
--wapp-badge-fg: rgb(146 64 14);
|
|
1101
1104
|
background: rgb(254 243 199);
|
|
1102
|
-
color:
|
|
1105
|
+
color: var(--wapp-badge-fg);
|
|
1103
1106
|
}
|
|
1104
1107
|
.wapp-badge-error,
|
|
1105
1108
|
.wapp-badge-failed {
|
|
1109
|
+
--wapp-badge-fg: rgb(153 27 27);
|
|
1106
1110
|
background: rgb(254 226 226);
|
|
1107
|
-
color:
|
|
1111
|
+
color: var(--wapp-badge-fg);
|
|
1108
1112
|
}
|
|
1109
1113
|
.wapp-badge-info,
|
|
1110
1114
|
.wapp-badge-running {
|
|
1115
|
+
--wapp-badge-fg: rgb(30 64 175);
|
|
1111
1116
|
background: rgb(219 234 254);
|
|
1112
|
-
color:
|
|
1117
|
+
color: var(--wapp-badge-fg);
|
|
1113
1118
|
}
|
|
1114
1119
|
.wapp-badge-disabled,
|
|
1115
1120
|
.wapp-badge-stopped,
|
|
1116
1121
|
.wapp-badge-idle {
|
|
1122
|
+
--wapp-badge-fg: rgb(31 41 55);
|
|
1117
1123
|
background: rgb(243 244 246);
|
|
1118
|
-
color:
|
|
1124
|
+
color: var(--wapp-badge-fg);
|
|
1119
1125
|
}
|
|
1120
1126
|
.wapp-badge-planning {
|
|
1127
|
+
--wapp-badge-fg: rgb(21 94 117);
|
|
1121
1128
|
background: rgb(207 250 254);
|
|
1122
|
-
color:
|
|
1129
|
+
color: var(--wapp-badge-fg);
|
|
1123
1130
|
}
|
|
1124
1131
|
.wapp-badge-merged {
|
|
1132
|
+
--wapp-badge-fg: rgb(107 33 168);
|
|
1125
1133
|
background: rgb(243 232 255);
|
|
1126
|
-
color:
|
|
1134
|
+
color: var(--wapp-badge-fg);
|
|
1127
1135
|
}
|
|
1128
1136
|
.wapp-badge-pushed {
|
|
1137
|
+
--wapp-badge-fg: rgb(55 48 163);
|
|
1129
1138
|
background: rgb(224 231 255);
|
|
1130
|
-
color:
|
|
1139
|
+
color: var(--wapp-badge-fg);
|
|
1131
1140
|
}
|
|
1132
1141
|
.wapp-badge-deleted {
|
|
1142
|
+
--wapp-badge-fg: rgb(107 114 128);
|
|
1133
1143
|
background: rgb(243 244 246);
|
|
1134
|
-
color:
|
|
1144
|
+
color: var(--wapp-badge-fg);
|
|
1135
1145
|
}
|
|
1136
1146
|
|
|
1137
1147
|
:root.dark .wapp-badge-success,
|
|
1138
1148
|
:root.dark .wapp-badge-completed {
|
|
1149
|
+
--wapp-badge-fg: rgb(134 239 172);
|
|
1139
1150
|
background: rgb(20 83 45);
|
|
1140
|
-
color:
|
|
1151
|
+
color: var(--wapp-badge-fg);
|
|
1141
1152
|
}
|
|
1142
1153
|
:root.dark .wapp-badge-warning,
|
|
1143
1154
|
:root.dark .wapp-badge-plan_ready {
|
|
1155
|
+
--wapp-badge-fg: rgb(252 211 77);
|
|
1144
1156
|
background: rgb(120 53 15);
|
|
1145
|
-
color:
|
|
1157
|
+
color: var(--wapp-badge-fg);
|
|
1146
1158
|
}
|
|
1147
1159
|
:root.dark .wapp-badge-error,
|
|
1148
1160
|
:root.dark .wapp-badge-failed {
|
|
1161
|
+
--wapp-badge-fg: rgb(252 165 165);
|
|
1149
1162
|
background: rgb(127 29 29);
|
|
1150
|
-
color:
|
|
1163
|
+
color: var(--wapp-badge-fg);
|
|
1151
1164
|
}
|
|
1152
1165
|
:root.dark .wapp-badge-info,
|
|
1153
1166
|
:root.dark .wapp-badge-running,
|
|
@@ -1159,25 +1172,36 @@ textarea::placeholder {
|
|
|
1159
1172
|
}
|
|
1160
1173
|
:root.dark .wapp-badge-info,
|
|
1161
1174
|
:root.dark .wapp-badge-running {
|
|
1162
|
-
|
|
1175
|
+
--wapp-badge-fg: rgb(147 197 253);
|
|
1176
|
+
color: var(--wapp-badge-fg);
|
|
1163
1177
|
}
|
|
1164
1178
|
:root.dark .wapp-badge-planning {
|
|
1165
|
-
|
|
1179
|
+
--wapp-badge-fg: rgb(103 232 249);
|
|
1180
|
+
color: var(--wapp-badge-fg);
|
|
1166
1181
|
}
|
|
1167
1182
|
:root.dark .wapp-badge-merged {
|
|
1168
|
-
|
|
1183
|
+
--wapp-badge-fg: rgb(216 180 254);
|
|
1184
|
+
color: var(--wapp-badge-fg);
|
|
1169
1185
|
}
|
|
1170
1186
|
:root.dark .wapp-badge-pushed {
|
|
1171
|
-
|
|
1187
|
+
--wapp-badge-fg: rgb(165 180 252);
|
|
1188
|
+
color: var(--wapp-badge-fg);
|
|
1172
1189
|
}
|
|
1173
1190
|
:root.dark .wapp-badge-deleted {
|
|
1174
|
-
|
|
1191
|
+
--wapp-badge-fg: rgb(107 114 128);
|
|
1192
|
+
color: var(--wapp-badge-fg);
|
|
1175
1193
|
}
|
|
1176
1194
|
:root.dark .wapp-badge-disabled,
|
|
1177
1195
|
:root.dark .wapp-badge-stopped,
|
|
1178
1196
|
:root.dark .wapp-badge-idle {
|
|
1197
|
+
--wapp-badge-fg: rgb(209 213 219);
|
|
1179
1198
|
background: rgb(64 64 64);
|
|
1180
|
-
color:
|
|
1199
|
+
color: var(--wapp-badge-fg);
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
.wapp-badge.wapp-sidebar-badge,
|
|
1203
|
+
:root.dark .wapp-badge.wapp-sidebar-badge {
|
|
1204
|
+
background: var(--wapp-badge-fg);
|
|
1181
1205
|
}
|
|
1182
1206
|
|
|
1183
1207
|
.wapp-settings {
|