diff options
| author | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-13 18:04:18 +1100 |
|---|---|---|
| committer | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-13 18:04:18 +1100 |
| commit | 93dfe2be64e8658839bcfe5356adf35f8cde7075 (patch) | |
| tree | c60b1e20d569b74dbde85123e1b2bf3590c66244 /src/web/components/nav_bar | |
initial commit
Diffstat (limited to 'src/web/components/nav_bar')
| -rw-r--r-- | src/web/components/nav_bar/NavBar.css | 304 | ||||
| -rw-r--r-- | src/web/components/nav_bar/NavBar.jsx | 174 |
2 files changed, 478 insertions, 0 deletions
diff --git a/src/web/components/nav_bar/NavBar.css b/src/web/components/nav_bar/NavBar.css new file mode 100644 index 0000000..d9e315e --- /dev/null +++ b/src/web/components/nav_bar/NavBar.css @@ -0,0 +1,304 @@ +:root { + --nav-bar-color: var(--black-10); + --nav-bar-font-color: var(--black-98); + --nav-bar-height: 4rem; + --nav-bar-shadow: 0 0.05em 0.16em var(--nav-bar-color); +} + +.navBar { + /* sticky footer */ + flex-shrink: 0; + + min-width: 100%; + height: var(--nav-bar-height); + + z-index: var(--nav-bar-z-index); + position: sticky; + top: 0; + display: flex; + justify-content: space-between; + align-items: center; + + background-color: var(--nav-bar-color); + box-shadow: var(--nav-bar-shadow); + font-weight: bolder; +} + +.container { + display: flex; + align-items: center; + min-height: var(--nav-bar-height); +} +.bannerContainer > a { + display: inherit; + justify-content: inherit; + align-items: inherit; + min-height: var(--nav-bar-height); + text-decoration: none; +} + +.bannerContainer { + display: flex; + align-items: center; + min-height: var(--nav-bar-height); +} +.logo { + margin-left: 0.5em; + height: calc(var(--nav-bar-height) - 0.5em); + width: calc(var(--nav-bar-height) - 0.5em); +} + +.bannerContainer h1 { + font-size: 1.5em; + margin: 0; + + color: var(--nav-bar-font-color); + text-decoration: none; + text-transform: none; + font-weight: bold; +} + +.burgerButton { + height: var(--nav-bar-height); + width: var(--nav-bar-height); + margin-left: 0.5em; + + display: flex; + justify-content: center; + align-items: center; + + border: 0; + background-color: transparent; + cursor: pointer; + background: url(assets/svg/burger.svg) center center no-repeat; + background-size: 3em; + filter: invert(95%); +} + +:root { + --menu-width: calc(100vw * 0.7); +} + +.menu { + position: fixed; + inset: 0 calc(100vw - var(--menu-width)) 0 0; + translate: calc(var(--menu-width) * -1); + z-index: var(--menu-z-index); + + display: flex; + flex-direction: column; + + background-color: var(--nav-bar-color); + + transition: translate var(--transition-time) ease-in; +} +.menuActive { + translate: initial; +} + +.darken { + z-index: calc(var(--menu-z-index) - 1); +} + +.itemListContainer { + display: inherit; + flex-direction: inherit; + overflow: scroll; + height: 100%; +} +.itemList { + position: static; + display: block; + margin: 0; + overflow: visible; + padding: 0 0.5em; +} +.itemList + .itemList { + border-top: solid hsl(0, 0%, 35%) 1px; +} +.itemList > p { + margin-bottom: 1em; + height: var(--nav-bar-height); + padding-top: 1.5em; + + text-align: center; + color: var(--nav-bar-font-color); + font-style: italic; + font-weight: 100; +} + +.titleContainer { + display: flex; + align-items: end; + margin: 1em 0 0.5em 0.5em; +} +.titleContainer h3 { + color: var(--nav-bar-font-color); + font-size: 1.25em; + text-transform: uppercase; +} + +.item { + display: flex; + align-items: center; + width: 100%; + padding: 0 1.0rem; + min-height: var(--nav-bar-height); + + background-color: transparent; + border-radius: 1.5rem; + text-decoration: none; + color: var(--nav-bar-font-color); + font-size: 1.75em; + text-transform: uppercase; +} +.item:hover { + background-color: hsl(0, 0%, 25%); +} +.itemActive { + background-color: var(--brand-80); +} +.itemActive:hover { + color: white; + background-color: var(--brand-90); +} +.itemIcon { + width: 1em; +} +.item h4 { + margin-left: 2rem; + font-size: 0.675em; + text-transform: capitalize; + font-weight: 100; +} +.itemActive h4 { + font-weight: 800; +} + +.menu span { + font-size: 0.8em; + width: 100%; + margin-top: auto; + + display: flex; + align-items: center; + justify-content: center; + + color: var(--nav-bar-font-color); +} + +.loginStatus { + font-size: 0.8em; + + padding: 0.75em 1em; + + margin: 0em 1.5em 0em 0.7em; + font-weight: 600; +} + +.accountContainer { + display: flex; + align-items: center; + justify-content: center; + margin-right: 1em; + width: 12em; + + color: var(--black-98); + background-color: hsl(0, 0%, 15%); + padding: 0.5em 1em; + border-radius: 1em; +} +.accountIcon { + height: 1.75em; + color: hsl(186, 90%, 50%); +} +.accountContainer h2 { + margin: 0 0.4em 0 0.25em; +} +.accountArrow { + height: 0.8em; + rotate: -90deg; +} +.accountInfoItemList { + display: none; + padding-top: 0.5em; + + position: absolute; + right: 1em; + top: 3em; + width: 12em; + + flex-direction: column; + background-color: inherit; + border-radius: 1em; + border-top-right-radius: 0em; + border-top-left-radius: 0em; +} +.accountInfoItem { + display: flex; + justify-content: center; + align-items: center; + + color: var(--black-98); + text-decoration: none; + padding: 1em 0; +} +.accountInfoItem + .accountInfoItem { + border-top: 1px hsl(0, 0%, 50%) solid; +} +.accountInfoItem h3 { + font-weight: 200; +} +@media (max-width: 40em) { + .accountContainerActive { + background-color: hsl(0, 0%, 25%); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + } + .accountArrowActive { + rotate: 90deg; + } + .accountInfoItemListActive { + display: flex; + } +} + +@media (min-width: 40em) { + :root { + --menu-width: 15em; + } + .darken { + display: none; + } + .burgerButton { + display: none; + } + .menu { + translate: initial; + transition: none; + + } + .itemListContainer { + font-size: 0.8em; + box-shadow: var(--nav-bar-shadow); + } + + .accountInfoListActive { + display: unset; + } + .accountContainer:hover { + background-color: hsl(0, 0%, 25%); + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + } + .accountContainer:hover .accountInfoItemList { + display: flex; + } + .accountContainer:hover .accountArrow { + rotate: 90deg; + } + .accountInfoItem:hover h3 { + color: white; + font-weight: bolder; + } +}
\ No newline at end of file diff --git a/src/web/components/nav_bar/NavBar.jsx b/src/web/components/nav_bar/NavBar.jsx new file mode 100644 index 0000000..3c22851 --- /dev/null +++ b/src/web/components/nav_bar/NavBar.jsx @@ -0,0 +1,174 @@ +import React, {useState, Fragment, useContext, useEffect} from "react"; +import {Link, useLocation} from "react-router-dom"; + +import Button from "components/button/Button.jsx"; +import Login from "components/login/Login.jsx"; +import Darken from "components/darken/Darken.jsx"; +import {UserContext} from "contexts/UserContext.jsx"; +import {StateContext} from "contexts/StateContext.jsx"; +import {refreshToken, logout} from "helpers/Auth.jsx"; +import {getSubreactFromLocation, getInfoFromSubreact} from "helpers/Location.jsx"; + +import styles from "./NavBar.css" +import logo from "assets/images/logo.png" +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faUser, faPlay} from "@fortawesome/free-solid-svg-icons"; + +function AccountInfoItem({title, onClick, path, className}) { + return ( + <Link className={styles.accountInfoItem + (className ? " " + className : "")} + onClick={onClick} + to={path}> + + <h3>{title}</h3> + </Link> + ); +} + +function AccountInfo({user, setUser}) { + // At this stage the user is logged in and we keep them logged in by periodically + // refreshing our token. + useEffect(() => { + const interval = setInterval(() => { + refreshToken(user, setUser); + }, 58 * 60 * 1000); // refresh every 58 minutes, expires every sixty. + return () => clearInterval(interval); + }, []); + const [active, setActive] = useState(false); + + return ( + <div className={styles.accountContainer + (active ? " " + styles.accountContainerActive : "")} + onClick={() => {setActive(!active);}}> + <FontAwesomeIcon icon={faUser} className={styles.accountIcon} /> + <h2>{user.username ?? "anonymous"}</h2> + <FontAwesomeIcon icon={faPlay} className={styles.accountArrow + (active ? " " + styles.accountArrowActive : "")} /> + <div className={styles.accountInfoItemList + (active ? " " + styles.accountInfoItemListActive : "")}> + <AccountInfoItem title="Profile" path="/settings" /> + <AccountInfoItem title="Settings" path="/settings" /> + <AccountInfoItem title="Logout" path="/" onClick={() => {logout(setUser);}} /> + </div> + </div> + ); +} + +function LoginStatus({loginActive, setLoginActive}) { + const [user, setUser] = useContext(UserContext); + + // Not logged in, request a login. + if (user == null) { + return ( + <Button className={styles.loginStatus} + onClick={() => {setLoginActive(!loginActive);}}> + + <h2>Not Logged In</h2> + </Button> + ); + } + + return <AccountInfo user={user} setUser={setUser} />; +} + +function Account() { + const [state, setState] = useContext(StateContext); + const {loginActive} = state; + const setLoginActive = (isActive) => {setState({...state, loginActive: isActive});}; + + return ( + <Fragment> + <Login active={loginActive} setActive={setLoginActive} /> + <LoginStatus loginActive={loginActive} setLoginActive={setLoginActive} /> + </Fragment> + ); +} + +function Banner({navBarActive, setNavBarActive, shouldToggleNavBar}) { + + return ( + <div className={styles.bannerContainer}> + <button className={styles.burgerButton} + onClick={() => setNavBarActive(!navBarActive)} /> + + <Link to="/" + onClick={shouldToggleNavBar ? () => setNavBarActive(!navBarActive) : null}> + + <img className={styles.logo} src={logo} /> + <h1>GoReact</h1> + </Link> + </div> + ); +} + +function Item({path, setNavBarActive}) { + const location = useLocation(); + const subreact = path.replace("/", ""); // "a", "m" etc + const {icon, name} = getInfoFromSubreact(subreact); + + return ( + <Link to={path} + className={styles.item + (getSubreactFromLocation(location) === subreact ? " " + styles.itemActive : "")} + onClick={() => setNavBarActive(false)}> + <FontAwesomeIcon icon={icon} className={styles.itemIcon} /> + <h4>{name}</h4> + </Link> + ); +} + +function ItemList({children, title}) { + + return ( + <div className={styles.itemList}> + <div className={styles.titleContainer}> + <h3>{title}</h3> + </div> + {children && React.Children.count(children) > 0 ? children : <p>There's nothing here...</p>} + </div> + ); +} + +function Menu({navBarActive, setNavBarActive}) { + + return ( + <div className={styles.container}> + <nav className={styles.menu + (navBarActive ? " " + styles.menuActive : "")}> + <Banner navBarActive={navBarActive} setNavBarActive={setNavBarActive} shouldToggleNavBar /> + <div className={styles.itemListContainer}> + <ItemList title="Subreacts"> + <Item path="/" setNavBarActive={setNavBarActive} /> + <Item path="/t" setNavBarActive={setNavBarActive} /> + <Item path="/g" setNavBarActive={setNavBarActive} /> + <Item path="/k" setNavBarActive={setNavBarActive} /> + <Item path="/p" setNavBarActive={setNavBarActive} /> + <Item path="/a" setNavBarActive={setNavBarActive} /> + <Item path="/pr" setNavBarActive={setNavBarActive} /> + <Item path="/m" setNavBarActive={setNavBarActive} /> + </ItemList> + + {/*TODO*/} + <ItemList title="Watched Threads"> + </ItemList> + <ItemList title="Your Threads"> + </ItemList> + <ItemList title="Recent Threads"> + </ItemList> + + <span> + <p>GoReact 2022-2023</p> + </span> + </div> + </nav> + <Account /> + </div> + ); +} + +export default function NavBar() { + const [active, setActive] = useState(false); + + return ( + <header className={styles.navBar}> + <Darken active={active} onClick={() => setActive(false)} className={styles.darken} /> + <Banner setNavBarActive={setActive} /> + <Menu navBarActive={active} setNavBarActive={setActive} /> + </header> + ); +}
\ No newline at end of file |
