Made the app mobile friendly
This commit is contained in:
@@ -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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user