aboutsummaryrefslogtreecommitdiff
path: root/src/web/components/nav_bar
diff options
context:
space:
mode:
authorNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-13 18:04:18 +1100
committerNicolas James <Eele1Ephe7uZahRie@tutanota.com>2025-02-13 18:04:18 +1100
commit93dfe2be64e8658839bcfe5356adf35f8cde7075 (patch)
treec60b1e20d569b74dbde85123e1b2bf3590c66244 /src/web/components/nav_bar
initial commit
Diffstat (limited to 'src/web/components/nav_bar')
-rw-r--r--src/web/components/nav_bar/NavBar.css304
-rw-r--r--src/web/components/nav_bar/NavBar.jsx174
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