feat(FN-1848): simplify mobile node selection to read-only label

- Replace interactive dropdown with static label in mobile header
- Add CSS styling for the read-only node indicator
- Improve mobile UX by removing unnecessary dropdown interaction
This commit is contained in:
Fusion
2026-04-15 21:01:07 -07:00
committed by gsxdsm
parent be8add9ef4
commit 3b66fa4fe4
2 changed files with 77 additions and 51 deletions

View File

@@ -525,63 +525,73 @@ export function Header({
{/* Node selector and status indicator */}
{showNodeSelector && (
<div className="header-node-selector" ref={nodeSelectorRef}>
<div
className={`header-node-selector${isMobile ? " header-node-selector--mobile" : ""}`}
ref={isMobile ? undefined : nodeSelectorRef}
>
{/* Node status indicator - always visible */}
<NodeStatusIndicator node={currentNode ?? null} showDetails />
{/* Node selector dropdown */}
<button
className={`btn-icon node-selector-trigger${isNodeSelectorOpen ? " node-selector-trigger--open" : ""}`}
onClick={() => setIsNodeSelectorOpen((prev) => !prev)}
title="Switch node"
aria-label="Switch node"
aria-expanded={isNodeSelectorOpen}
aria-haspopup="listbox"
data-testid="node-selector-trigger"
>
<ChevronRight
size={12}
className={`node-selector-chevron${isNodeSelectorOpen ? " node-selector-chevron--open" : ""}`}
/>
</button>
<NodeStatusIndicator
node={currentNode ?? null}
showDetails={!isMobile}
/>
{/* Node selector dropdown menu */}
{isNodeSelectorOpen && (
<div className="node-selector-dropdown" role="listbox" aria-label="Select node">
{/* Local option */}
{/* Node selector dropdown - desktop/tablet only */}
{!isMobile && (
<>
<button
className={`node-selector-option${!isRemote ? " node-selector-option--active" : ""}`}
onClick={() => {
onSelectNode?.(null);
setIsNodeSelectorOpen(false);
}}
role="option"
aria-selected={!isRemote}
data-testid="node-option-local"
className={`btn-icon node-selector-trigger${isNodeSelectorOpen ? " node-selector-trigger--open" : ""}`}
onClick={() => setIsNodeSelectorOpen((prev) => !prev)}
title="Switch node"
aria-label="Switch node"
aria-expanded={isNodeSelectorOpen}
aria-haspopup="listbox"
data-testid="node-selector-trigger"
>
<span className="node-selector-option-dot node-selector-option-dot--local" />
<span className="node-selector-option-label">Local</span>
<ChevronRight
size={12}
className={`node-selector-chevron${isNodeSelectorOpen ? " node-selector-chevron--open" : ""}`}
/>
</button>
{/* Remote nodes */}
{remoteNodes.map((node) => (
<button
key={node.id}
className={`node-selector-option${currentNode?.id === node.id ? " node-selector-option--active" : ""}`}
onClick={() => {
onSelectNode?.(node);
setIsNodeSelectorOpen(false);
}}
role="option"
aria-selected={currentNode?.id === node.id}
data-testid={`node-option-${node.id}`}
>
<span className={`node-selector-option-dot node-selector-option-dot--${node.status}`} />
<span className="node-selector-option-label">{node.name}</span>
<span className="node-selector-option-status">{node.status}</span>
</button>
))}
</div>
{/* Node selector dropdown menu */}
{isNodeSelectorOpen && (
<div className="node-selector-dropdown" role="listbox" aria-label="Select node">
{/* Local option */}
<button
className={`node-selector-option${!isRemote ? " node-selector-option--active" : ""}`}
onClick={() => {
onSelectNode?.(null);
setIsNodeSelectorOpen(false);
}}
role="option"
aria-selected={!isRemote}
data-testid="node-option-local"
>
<span className="node-selector-option-dot node-selector-option-dot--local" />
<span className="node-selector-option-label">Local</span>
</button>
{/* Remote nodes */}
{remoteNodes.map((node) => (
<button
key={node.id}
className={`node-selector-option${currentNode?.id === node.id ? " node-selector-option--active" : ""}`}
onClick={() => {
onSelectNode?.(node);
setIsNodeSelectorOpen(false);
}}
role="option"
aria-selected={currentNode?.id === node.id}
data-testid={`node-option-${node.id}`}
>
<span className={`node-selector-option-dot node-selector-option-dot--${node.status}`} />
<span className="node-selector-option-label">{node.name}</span>
<span className="node-selector-option-status">{node.status}</span>
</button>
))}
</div>
)}
</>
)}
</div>
)}

View File

@@ -6693,6 +6693,22 @@ body {
padding-right: max(var(--space-md), env(safe-area-inset-right, 0px));
}
/* Mobile: compact node label, no dropdown */
.header-node-selector--mobile {
margin-right: var(--space-xs);
cursor: default;
}
.header-node-selector--mobile .node-status-indicator {
font-size: 11px;
padding: 2px var(--space-xs);
}
.header-node-selector--mobile .node-status-indicator__name {
max-width: 80px;
font-weight: 400;
}
/* Mobile view toggle in header — compact 2-button board/list switcher */
.header-actions > .view-toggle {
height: 32px;