From 59dabf14b1d9e739ba567f63de6c01ff9ee868f6 Mon Sep 17 00:00:00 2001 From: Roland Date: Thu, 30 Jul 2026 13:15:43 -0600 Subject: [PATCH] ui: Nav.js off Semantic UI onto the token-driven component library Replaces semantic-ui-react's Menu/Dropdown/Icon/Popup with plain flexbox elements plus the Mantine-based Menu (for the mobile hamburger and desktop "More" overflow), Tooltip, and Icon from ./ui. Nav color and the warning-icon contrast swap keep their exact logic, now resolved through CSS custom properties (var(--{navColor}), var(--btn-text)) instead of Semantic color props. Adds accessible labels to previously-unlabelled warning icons while preserving their data-testid hooks and threshold/ priority logic. New nav CSS lives in App.css (wrolpi-navbar*). --- app/src/App.css | 37 ++++++++ app/src/components/Nav.js | 185 +++++++++++++++++++++++--------------- 2 files changed, 152 insertions(+), 70 deletions(-) diff --git a/app/src/App.css b/app/src/App.css index 50bbb637..9c52d444 100644 --- a/app/src/App.css +++ b/app/src/App.css @@ -1,3 +1,40 @@ +/* Nav bar: a hand-rolled flexbox bar (was a Semantic Menu). The user-chosen + background color and the `--btn-text` foreground are set inline per theme; + this stylesheet only supplies structure and hover affordances. */ +.wrolpi-navbar { + display: flex; + align-items: stretch; + flex-wrap: nowrap; + min-height: 3.4em; + padding: 0 0.25em; +} + +.wrolpi-navbar-right { + display: flex; + align-items: center; + margin-left: auto; + gap: 0.2em; +} + +.wrolpi-navbar-link, +.wrolpi-navbar-link-button { + display: flex; + align-items: center; + padding: 0 1.1em; + color: inherit; + text-decoration: none; + white-space: nowrap; + background: none; + border: none; + font: inherit; + cursor: pointer; +} + +.wrolpi-navbar-link:hover, +.wrolpi-navbar-link-button:hover { + background: rgba(0, 0, 0, 0.08); +} + .channel-edit { border-radius: 3px; padding: 0.75em; diff --git a/app/src/components/Nav.js b/app/src/components/Nav.js index c4077de0..09e32cf3 100644 --- a/app/src/components/Nav.js +++ b/app/src/components/Nav.js @@ -1,7 +1,8 @@ import React from "react"; import {Link, NavLink} from "react-router"; -import {Dropdown, Icon as SIcon, Menu} from "semantic-ui-react"; -import {Media, SettingsContext, StatusContext, ThemeContext} from "../contexts/contexts"; +import {IconMenu2, IconPlugOff, IconTemperature, IconTemperaturePlus} from "@tabler/icons-react"; +import {Icon, IconButton, Menu, Tooltip} from "./ui"; +import {Media, SettingsContext, StatusContext} from "../contexts/contexts"; import {DarkModeToggle, HotspotStatusIcon, useLocalStorage} from "./Common"; import {ShareButton} from "./Share"; import { @@ -16,7 +17,6 @@ import { } from "../hooks/customHooks"; import {useReorganizationStatus} from "../contexts/FileWorkerStatusContext"; import {SearchIconButton} from "./Search"; -import {Icon, Popup} from "./Theme"; import {HELP_VIEWER_URI, NAME, semanticUIColorMap} from "./Vars"; import {useOverflowNav} from "../hooks/useOverflowNav"; import _ from "lodash"; @@ -44,6 +44,10 @@ function updateFavicon(colorName) { if (themeColorMeta) themeColorMeta.content = hexColor; } +// A theme token name, or null to inherit the surrounding text/icon color (never +// literally "var(--null)"). +const colorVar = (color) => color ? `var(--${color})` : undefined; + const help = {to: HELP_VIEWER_URI, text: 'Help', key: 'help', target: '_blank'}; const admin = {to: '/admin', text: 'Admin', key: 'admin'}; const rightLinks = [help, admin]; @@ -61,30 +65,68 @@ const allLinks = [ {to: '/more/statistics', text: 'Statistics', key: 'statistics', end: true}, ]; -function DropdownLinks({link}) { - return - - {link.links.map(l => )} - - -} - function MenuLink({link}) { - // Wrapper around NavLink to handle Navlink/Dropdown change. + // A top-level tab rendered directly in the bar (not inside a dropdown). if (link.links) { return - } else { - const end = link.end ? {end: true} : {end: undefined}; - const target = link.target ? {target: link.target} : {}; - return - {link.text} - } + const end = link.end ? {end: true} : {}; + const target = link.target ? {target: link.target, rel: 'noopener noreferrer'} : {}; + return + {link.text} + +} + +function DropdownMenuItem({link}) { + // An item inside a Menu.Dropdown: the mobile hamburger menu, or the desktop + // "More" overflow menu. + if (link.links) { + // Mantine's Menu does not nest submenus without extra plumbing; a labelled, + // indented group stands in for Semantic's nested Dropdown. No current data + // uses this path (no allLinks entry has its own `links`). + return + {link.text} + {link.links.map(l => )} + + } + const end = link.end ? {end: true} : {}; + const target = link.target ? {target: link.target, rel: 'noopener noreferrer'} : {}; + return + {link.text} + +} + +function DropdownLinks({link}) { + // A labelled dropdown trigger: the desktop "More" overflow, or any nested + // link group handed to MenuLink. + return + + + + + {link.links.map(l => )} + + +} + +function MobileMenu({links}) { + // The mobile hamburger menu: an icon-only trigger holding every link. + return + + + + + {links.map(link => )} + + } function NavIconWrapper({children}) { @@ -123,10 +165,9 @@ export function NavBar() { const navColor = useNavColorSetting(); const wrolpiIcon = WROLPi Home Icon; const name = {NAME || wrolpiIcon}; - const topNavText = wrolModeEnabled ? <>{name}  : name; - const {i} = React.useContext(ThemeContext); + const topNavText = wrolModeEnabled ? <>{name}  : name; - const homeLink = + const homeLink = {topNavText} ; @@ -143,9 +184,10 @@ export function NavBar() { // System load is high, display a warning icon. const color = highLoad ? highWarningColor : lowWarningColor; const icon = - + ; - systemLoadIcon = + systemLoadIcon = {icon} } // RAM consumption. @@ -154,9 +196,9 @@ export function NavBar() { if (memoryPercent > 80) { const color = memoryPercent > 90 ? highWarningColor : lowWarningColor; const icon = - + ; - memoryIcon = + memoryIcon = {icon} } // Any disk is busy and processes are waiting. @@ -166,9 +208,9 @@ export function NavBar() { // Processes are waiting on disk, display a warning icon. const color = percentIOWait > 75 ? highWarningColor : lowWarningColor; const icon = - + ; - diskWaitIcon = + diskWaitIcon = {icon} } // CPU temperature. @@ -177,10 +219,11 @@ export function NavBar() { if (temperature && temperature >= highTemperature) { // CPU temperature is high, display a warning icon. const color = temperature >= criticalTemperature ? highWarningColor : lowWarningColor; - const name = temperature >= criticalTemperature ? 'thermometer' : 'thermometer half'; - const icon = + const temperatureComponent = temperature >= criticalTemperature ? IconTemperaturePlus : IconTemperature; + const icon = const link = {icon}; - temperatureIcon = ; + temperatureIcon = {link}; } // Hard-drive temperature. An overheating drive risks data loss. @@ -193,9 +236,10 @@ export function NavBar() { let driveTemperatureIcon; if (driveTemperature && driveTemperature >= driveHighTemperature) { const color = driveTemperature >= driveCriticalTemperature ? highWarningColor : lowWarningColor; - const icon = + const icon = const link = {icon}; - driveTemperatureIcon = ; + driveTemperatureIcon = {link}; } // A drive's SMART health is degraded. FAIL is an imminent failure (back @@ -206,31 +250,36 @@ export function NavBar() { const {failingDevices, failing: driveFailing, warningDevices, warning: driveWarning} = useDriveHealth(); let driveHealthIcon; if (driveFailing) { - const icon = ; + const icon = ; const link = {icon}; const message = failingDevices.length === 1 ? `Drive ${failingDevices[0]} is failing its SMART health check! Back up your data.` : `Drives failing SMART health check: ${failingDevices.join(', ')}. Back up your data.`; - driveHealthIcon = ; + driveHealthIcon = {link}; } else if (driveWarning) { - const icon = ; + const icon = ; const link = {icon}; const message = warningDevices.length === 1 ? `Drive ${warningDevices[0]} has unreadable or pending sectors — check its SMART health.` : `Drives with unreadable or pending sectors: ${warningDevices.join(', ')}. Check their SMART health.`; - driveHealthIcon = ; + driveHealthIcon = {link}; } // Power issues, this is always displayed if detected. const {underVoltage, overCurrent} = usePowerStats(); let powerIcon; if (underVoltage || overCurrent) { - const name = underVoltage ? 'power cord' : 'lightning'; - const icon = ; + const icon = underVoltage + ? + : ; const message = underVoltage ? 'Under-voltage detected! Your power supply is insufficient!' : 'Over-current detected! Your peripherals are using too much power!'; - powerIcon = ; + powerIcon = {icon}; } // Display the temperature icon first because it can cause the system to throttle. The rest are in order of effects @@ -269,15 +318,14 @@ export function NavBar() { } const processingIcon = processingLink && - + ; let apiDownIcon; if (window.apiDown) { - apiDownIcon = } - /> + apiDownIcon = + + } // Upgrade available notification - only show on native (non-Docker) installs and when WROL Mode is disabled @@ -286,12 +334,12 @@ export function NavBar() { const commitsBehind = status.commits_behind || 0; const branch = status.git_branch || 'unknown'; const icon = - + ; - upgradeIcon = ; + upgradeIcon = + {icon} + ; } const icons = @@ -308,27 +356,23 @@ export function NavBar() { return <> - + + + + - + } -function DesktopNav({i, navColor, homeLink, icons}) { +function DesktopNav({navColor, homeLink, icons}) { const containerRef = React.useRef(null); const homeRef = React.useRef(null); const rightMenuRef = React.useRef(null); @@ -343,7 +387,8 @@ function DesktopNav({i, navColor, homeLink, icons}) { return (
- + +
); }