Made the app mobile friendly

This commit is contained in:
2026-04-15 14:16:08 +01:00
parent c4cf6a92fe
commit b85fd70793
3 changed files with 175 additions and 17 deletions

View File

@@ -1,4 +1,8 @@
nav ul { .navbar {
margin-bottom: 12px;
}
.nav-list {
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -7,11 +11,16 @@ nav ul {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
border-radius: 8px; border-radius: 12px;
overflow: hidden; overflow: hidden;
box-shadow: var(--shadow-soft);
} }
nav ul li a { .nav-menu-toggle {
display: none;
}
.nav-list li a {
display: block; display: block;
color: var(--nav-text); color: var(--nav-text);
padding: 10px 16px; padding: 10px 16px;
@@ -21,11 +30,11 @@ nav ul li a {
transition: background-color 0.2s ease, color 0.2s ease; transition: background-color 0.2s ease, color 0.2s ease;
} }
nav ul li a:hover { .nav-list li a:hover {
background-color: var(--nav-bg-hover); background-color: var(--nav-bg-hover);
} }
nav ul li a.active { .nav-list li a.active {
background-color: var(--nav-bg-hover); background-color: var(--nav-bg-hover);
border-radius: 8px; border-radius: 8px;
} }
@@ -101,3 +110,102 @@ nav ul li a.active {
border-top: 1px solid var(--nav-divider); border-top: 1px solid var(--nav-divider);
} }
} }
@media (max-width: 760px) {
.nav-menu-toggle {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
border: 0;
background: var(--nav-bg);
color: var(--nav-text);
border-radius: 12px;
padding: 14px 16px;
font: inherit;
font-weight: 700;
cursor: pointer;
box-shadow: var(--shadow-soft);
}
.nav-menu-toggle__label {
letter-spacing: 0.02em;
}
.nav-menu-toggle__icon {
width: 24px;
height: 18px;
display: inline-flex;
flex-direction: column;
justify-content: space-between;
flex-shrink: 0;
}
.nav-menu-toggle__icon span {
width: 100%;
height: 2px;
background: currentColor;
border-radius: 999px;
transition: transform 0.2s ease, opacity 0.2s ease;
transform-origin: center;
}
.nav-menu-toggle__icon.is-open span:nth-child(1) {
transform: translateY(8px) rotate(45deg);
}
.nav-menu-toggle__icon.is-open span:nth-child(2) {
opacity: 0;
}
.nav-menu-toggle__icon.is-open span:nth-child(3) {
transform: translateY(-8px) rotate(-45deg);
}
.nav-list {
max-height: 0;
opacity: 0;
margin-top: 10px;
flex-direction: column;
align-items: stretch;
pointer-events: none;
transition: max-height 0.25s ease, opacity 0.2s ease;
}
.nav-list--open {
max-height: 640px;
opacity: 1;
pointer-events: auto;
}
.nav-list li {
width: 100%;
}
.nav-list li + li {
border-top: 1px solid var(--nav-divider);
}
.nav-list li a {
margin: 0;
padding: 14px 18px;
border-radius: 0;
}
.nav-list li a.active {
border-radius: 0;
}
.nav-status {
padding: 16px 18px;
justify-content: flex-start;
flex-wrap: wrap;
gap: 12px;
border-top: 1px solid var(--nav-divider);
}
.nav-status__text {
width: 100%;
}
}

View File

@@ -1,7 +1,10 @@
import { NavLink } from 'react-router-dom' import { useEffect, useState } from 'react'
import { NavLink, useLocation } from 'react-router-dom'
import { useAuth } from '../../context/AuthContext.jsx' import { useAuth } from '../../context/AuthContext.jsx'
import './Navbar.css' import './Navbar.css'
const MOBILE_NAV_BREAKPOINT = 760
function SunIcon() { function SunIcon() {
return ( return (
<svg viewBox="0 0 24 24" aria-hidden="true"> <svg viewBox="0 0 24 24" aria-hidden="true">
@@ -21,22 +24,64 @@ function MoonIcon() {
function Navbar({ theme, onToggleTheme }) { function Navbar({ theme, onToggleTheme }) {
const { isAuthenticated, isSiteAdmin, logout, user } = useAuth() const { isAuthenticated, isSiteAdmin, logout, user } = useAuth()
const location = useLocation()
const [isMenuOpen, setIsMenuOpen] = useState(false)
const nextThemeLabel = theme === 'dark' ? 'Switch to light' : 'Switch to dark' const nextThemeLabel = theme === 'dark' ? 'Switch to light' : 'Switch to dark'
useEffect(() => {
setIsMenuOpen(false)
}, [location.pathname, isAuthenticated])
useEffect(() => {
if (typeof window === 'undefined') {
return undefined
}
function handleResize() {
if (window.innerWidth > MOBILE_NAV_BREAKPOINT) {
setIsMenuOpen(false)
}
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
function closeMenu() {
setIsMenuOpen(false)
}
return ( return (
<nav> <nav className="navbar" aria-label="Primary navigation">
<ul> <button
type="button"
className="nav-menu-toggle"
aria-expanded={isMenuOpen}
aria-controls="primary-navigation"
onClick={() => setIsMenuOpen(currentValue => !currentValue)}
>
<span className="nav-menu-toggle__label">Menu</span>
<span className={`nav-menu-toggle__icon ${isMenuOpen ? 'is-open' : ''}`} aria-hidden="true">
<span />
<span />
<span />
</span>
</button>
<ul id="primary-navigation" className={`nav-list ${isMenuOpen ? 'nav-list--open' : ''}`}>
{isAuthenticated ? ( {isAuthenticated ? (
<> <>
<li><NavLink to="/">Homepage</NavLink></li> <li><NavLink to="/" onClick={closeMenu}>Homepage</NavLink></li>
<li><NavLink to="/admin">Admin</NavLink></li> <li><NavLink to="/admin" onClick={closeMenu}>Admin</NavLink></li>
<li><NavLink to="/inventory">Inventory</NavLink></li> <li><NavLink to="/inventory" onClick={closeMenu}>Inventory</NavLink></li>
<li><NavLink to="/search">Search</NavLink></li> <li><NavLink to="/search" onClick={closeMenu}>Search</NavLink></li>
<li><NavLink to="/barcode">Barcode Scanner</NavLink></li> <li><NavLink to="/barcode" onClick={closeMenu}>Barcode Scanner</NavLink></li>
{isSiteAdmin && <li><NavLink to="/users">Users</NavLink></li>} {isSiteAdmin && <li><NavLink to="/users" onClick={closeMenu}>Users</NavLink></li>}
</> </>
) : ( ) : (
<li><NavLink to="/">Login</NavLink></li> <li><NavLink to="/" onClick={closeMenu}>Login</NavLink></li>
)} )}
<li className="nav-status"> <li className="nav-status">
<button <button
@@ -56,7 +101,12 @@ function Navbar({ theme, onToggleTheme }) {
</button> </button>
<span className="nav-status__text">{isAuthenticated ? user?.email ?? 'Signed in' : 'Signed out'}</span> <span className="nav-status__text">{isAuthenticated ? user?.email ?? 'Signed in' : 'Signed out'}</span>
{isAuthenticated && ( {isAuthenticated && (
<button type="button" className="nav-button" onClick={logout}>Sign out</button> <button type="button" className="nav-button" onClick={() => {
closeMenu()
logout()
}}>
Sign out
</button>
)} )}
</li> </li>
</ul> </ul>

View File

@@ -192,7 +192,7 @@ function SearchPage() {
<div className="search-container panel"> <div className="search-container panel">
<div className="search-form"> <div className="search-form">
<div className="form-group"> <div className="form-group">
<label htmlFor="search-name">Item / location query</label> <label htmlFor="search-name">Item / Location / Barcode Search</label>
<input <input
type="text" type="text"
id="search-name" id="search-name"