(FEAT): Navbar implemented!

This commit is contained in:
Hayden Hargreaves 2025-10-29 21:19:53 -07:00
parent a0d4f29527
commit 8655df8f6b
12 changed files with 803 additions and 74 deletions

View File

@ -24,6 +24,7 @@
templ
tailwindcss_4
tailwindcss-language-server
tailwindcss-language-server:
watchman
docker-language-server
dockerfile-language-server-nodejs

685
web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,13 @@
"preview": "vite preview"
},
"dependencies": {
"@tailwindcss/vite": "^4.1.16",
"eslint-plugin-react-dom": "^2.2.4",
"eslint-plugin-react-x": "^2.2.4",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-router-dom": "^7.9.5"
"react-router-dom": "^7.9.5",
"tailwindcss": "^4.1.16"
},
"devDependencies": {
"@eslint/js": "^9.36.0",

View File

@ -3,19 +3,24 @@ import Home from './pages/Home';
import WebLayout from "./layouts/WebLayout";
import NotFound from './pages/NotFound';
import ROUTE_CONSTANTS from './types/routes';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Navigate to="/web" replace />} />
<Route path="/web" element={<WebLayout />}>
<Route index element={<Home />} />
<Route path="/" element={<Navigate to={ROUTE_CONSTANTS.Home} replace />} />
<Route path="/v2/web" element={<WebLayout />}>
<Route index element={<Navigate to={ROUTE_CONSTANTS.Home} replace />} />
<Route path="home" element={<Home />} />
{/* <Route path="recipe/:id" element={<Home />} /> */}
</Route>
{/* 404: Not Found */}
<Route path="*" element={<NotFound />} />
<Route path="*" element={<WebLayout />}>
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</BrowserRouter>
);

View File

@ -0,0 +1,116 @@
import { useState } from "react";
import ROUTE_CONSTANTS from "../types/routes.ts";
import { useLocation } from "react-router-dom";
import ShoppingListIcon from "./icons/ShoppingListIcon.tsx";
export default function Navigation() {
const [displayHamburgerMenu, setDisplayHamburgerMenu] = useState<boolean>(false);
const location = useLocation();
return (
<>
<nav className="block md:fixed w-full z-10">
<div
className="relative w-full px-8 md:px-44 p-4 border-b border-gray-300 shadow-sm shadow-gray-300 bg-white flex justify-between items-center"
>
<div>
<a href={ROUTE_CONSTANTS.Home}>
<p className="select-none">Potion</p>
</a>
</div>
<div className="hidden md:flex lg:flex items-center gap-8 select-none">
<NavigationLink name="Home" url={ROUTE_CONSTANTS.Home} current={location.pathname === ROUTE_CONSTANTS.Home} />
<NavigationLink name="Favorites" url={ROUTE_CONSTANTS.Favorites} current={location.pathname === ROUTE_CONSTANTS.Favorites} />
<NavigationLink name="Create" url={ROUTE_CONSTANTS.Create} current={location.pathname === ROUTE_CONSTANTS.Create} />
<NavigationLink name="Profile" url={ROUTE_CONSTANTS.Profile} current={location.pathname === ROUTE_CONSTANTS.Profile} />
<IconNavigationLink icon={<ShoppingListIcon current={location.pathname === ROUTE_CONSTANTS.ShoppingList} />} url={ROUTE_CONSTANTS.ShoppingList} />
</div>
<div className="md:hidden grid place-content-center">
<button onClick={() => setDisplayHamburgerMenu(!displayHamburgerMenu)} className="p-2">
<svg
className={`${displayHamburgerMenu ? "flex" : "hidden"} size-5`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 320 512"
>
<path
d="M182.6 137.4c-12.5-12.5-32.8-12.5-45.3 0l-128 128c-9.2 9.2-11.9 22.9-6.9 34.9s16.6 19.8 29.6 19.8l256 0c12.9 0 24.6-7.8 29.6-19.8s2.2-25.7-6.9-34.9l-128-128z"
></path>
</svg>
<svg
className={`${displayHamburgerMenu ? "hidden" : "flex"} size-5`}
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
<path
fill="currentColor"
d="M0 96C0 78.3 14.3 64 32 64l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 128C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32l384 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 288c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32L32 448c-17.7 0-32-14.3-32-32s14.3-32 32-32l384 0c17.7 0 32 14.3 32 32z"
></path>
</svg>
</button>
</div>
<HamburgerMenu show={displayHamburgerMenu} />
</div>
</nav>
</>
);
}
interface HamburgerMenuProps {
show: boolean;
};
function HamburgerMenu({ show }: HamburgerMenuProps) {
return (
<div className={`${show ? "flex" : "hidden"} w-full flex-col items-center absolute top-[100%] left-0 py-2 bg-white border-b border-gray-300 shadow-sm shadow-gray-300 z-20`}>
<DropdownLink name="Home" url={ROUTE_CONSTANTS.Home} />
<DropdownLink name="Favorites" url={ROUTE_CONSTANTS.Favorites} />
<DropdownLink name="Create" url={ROUTE_CONSTANTS.Create} />
<DropdownLink name="Profile" url={ROUTE_CONSTANTS.Profile} />
<DropdownLink name="Shopping List" url={ROUTE_CONSTANTS.ShoppingList} />
</div>
);
}
interface DropdownLinkProps {
name: string;
url: string;
}
function DropdownLink({ name, url }: DropdownLinkProps) {
return (
<a className="py-2" href={url}>
{name}
</a>
);
};
interface NavigationLinkProps {
name: string;
url: string;
current: boolean;
}
function NavigationLink({ name, url, current }: NavigationLinkProps) {
return (
<a href={url} className={`${current ? "border-blue-500" : "hover:border-blue-400 border-white"} duration-150 text-gray-700 border-b-2 px-1 cursor-pointer`}>
{name}
</a>
);
}
interface IconNavigationLinkProps {
icon: React.ReactElement;
url: string;
}
function IconNavigationLink({ icon, url }: IconNavigationLinkProps) {
return (
<a href={url} className="px-1 cursor-pointer">
{icon}
</a>
);
}

View File

@ -0,0 +1,19 @@
interface ShoppingListIconProps {
current: boolean;
};
export default function ShoppingListIcon({ current }: ShoppingListIconProps) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 576 512"
className={`${current ? "text-blue-500" : "text-gray-700 hover:text-blue-400"} duration-150 h-4`}
>
<path
fill="currentColor"
d="M0 24C0 10.7 10.7 0 24 0L69.5 0c22 0 41.5 12.8 50.6 32l411 0c26.3 0 45.5 25 38.6 50.4l-41 152.3c-8.5 31.4-37 53.3-69.5 53.3l-288.5 0 5.4 28.5c2.2 11.3 12.1 19.5 23.6 19.5L488 336c13.3 0 24 10.7 24 24s-10.7 24-24 24l-288.3 0c-34.6 0-64.3-24.6-70.7-58.5L77.4 54.5c-.7-3.8-4-6.5-7.9-6.5L24 48C10.7 48 0 37.3 0 24zM128 464a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm336-48a48 48 0 1 1 0 96 48 48 0 1 1 0-96z"
/>
</svg>
);
}

View File

@ -0,0 +1 @@
@import "tailwindcss";

View File

@ -1,11 +1,12 @@
import { Outlet } from "react-router-dom";
import Navigation from "../components/Navigation";
export default function WebLayout() {
return (
<>
<p> LAYOUT </p>
<Navigation />
<Outlet />
</>

View File

@ -3,7 +3,7 @@
export default function Home() {
return (
<>
<p>Hello world </p>
<p className="text-red-500">Hello world </p>
</>
);
}

18
web/src/types/routes.ts Normal file
View File

@ -0,0 +1,18 @@
const VERSION_FLAG = "/v2";
const ROUTE_CONSTANTS: {
Home: string;
Favorites: string;
Create: string;
Profile: string;
ShoppingList: string;
} = {
Home: `${VERSION_FLAG}/web/home`,
Favorites: `${VERSION_FLAG}/web/favorites`,
Create: `${VERSION_FLAG}/web/create`,
Profile: `${VERSION_FLAG}/web/profile`,
ShoppingList: `${VERSION_FLAG}/web/list`,
};
export default ROUTE_CONSTANTS;

8
web/tailwind.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}"
],
theme: { extend: {}, },
plugins: [],
}

View File

@ -1,7 +1,8 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import tailwindcss from '@tailwindcss/vite';
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [react(), tailwindcss()],
})