feat: add the header
This commit is contained in:
parent
919c35354d
commit
99e756c508
9 changed files with 324 additions and 26 deletions
31
app/(root)/layout.jsx
Normal file
31
app/(root)/layout.jsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
import Header from "@/components/Header";
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<Header />
|
||||
{/*<Header
|
||||
docLinks={[
|
||||
{
|
||||
href: "/",
|
||||
target: "_self",
|
||||
label: "Home"
|
||||
},
|
||||
{
|
||||
href: "/docs",
|
||||
target: "_self",
|
||||
label: "Docs",
|
||||
sub: true
|
||||
},
|
||||
{
|
||||
href: "/blog",
|
||||
target: "_self",
|
||||
label: "Blog",
|
||||
sub: true
|
||||
}
|
||||
]}
|
||||
/>*/}
|
||||
<div className="flex-grow flex-1 overflow-x-hidden">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
function Home() {
|
||||
return <div>Placeholder</div>;
|
||||
}
|
||||
|
||||
export default Home;
|
|
@ -1,5 +1,3 @@
|
|||
import { ThemeProvider } from "next-themes";
|
||||
|
||||
export const metadata = {
|
||||
title: "MERNMail - a MERN stack webmail application",
|
||||
description:
|
||||
|
@ -15,9 +13,9 @@ export const metadata = {
|
|||
url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png`,
|
||||
width: 2560,
|
||||
height: 1440,
|
||||
alt: "MERNMail - a MERN stack webmail application",
|
||||
},
|
||||
],
|
||||
alt: "MERNMail - a MERN stack webmail application"
|
||||
}
|
||||
]
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
|
@ -26,12 +24,14 @@ export const metadata = {
|
|||
description:
|
||||
"MERNMail: Open-source webmail app built with MERN stack. Send, receive, & manage emails easily. Explore and contribute today!",
|
||||
images: [
|
||||
`${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png`,
|
||||
`${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png`
|
||||
],
|
||||
creator: "@MERNMail",
|
||||
},
|
||||
creator: "@MERNMail"
|
||||
}
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return <>{children}</>;
|
||||
function Home() {
|
||||
return <div>Placeholder</div>;
|
||||
}
|
||||
|
||||
export default Home;
|
|
@ -4,7 +4,7 @@ import "./globals.css";
|
|||
|
||||
const inter = Inter({
|
||||
weight: ["400", "600", "700", "900"],
|
||||
subsets: ["latin"],
|
||||
subsets: ["latin"]
|
||||
});
|
||||
|
||||
export const metadata = {
|
||||
|
@ -22,9 +22,9 @@ export const metadata = {
|
|||
url: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png`,
|
||||
width: 2560,
|
||||
height: 1440,
|
||||
alt: "MERNMail - a MERN stack webmail application",
|
||||
},
|
||||
],
|
||||
alt: "MERNMail - a MERN stack webmail application"
|
||||
}
|
||||
]
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
|
@ -33,10 +33,10 @@ export const metadata = {
|
|||
description:
|
||||
"MERNMail: Open-source webmail app built with MERN stack. Send, receive, & manage emails easily. Explore and contribute today!",
|
||||
images: [
|
||||
`${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png`,
|
||||
`${process.env.NEXT_PUBLIC_WEBSITE_URL}/metadata/mernmail-cover.png`
|
||||
],
|
||||
creator: "@MERNMail",
|
||||
},
|
||||
creator: "@MERNMail"
|
||||
}
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }) {
|
189
components/Header.jsx
Normal file
189
components/Header.jsx
Normal file
|
@ -0,0 +1,189 @@
|
|||
"use client";
|
||||
import Logo from "@/components/Logo";
|
||||
import { headerLinks } from "@/constants";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { Menu, Moon, Sun, X } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { useEffect, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
function Header({ docLinks }) {
|
||||
const pathname = usePathname();
|
||||
const [menuShown, setMenuShown] = useState(false);
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
if (menuShown) {
|
||||
document.documentElement.style.overflow = "hidden";
|
||||
} else {
|
||||
document.documentElement.style.overflow = null;
|
||||
}
|
||||
}, [menuShown]);
|
||||
|
||||
return (
|
||||
<header className="w-full border-b border-b-border sticky z-40 block h-12">
|
||||
<div className="flex flex-row h-full px-2 py-2 mx-auto max-w-screen-xl">
|
||||
<Link href="/" className="mx-1.5">
|
||||
<span className="sr-only">MERNMail logo</span>
|
||||
<Logo width={160} height={35} className="inline h-8 w-auto" />
|
||||
</Link>
|
||||
<nav className="grow">
|
||||
<ul className="hidden md:inline mx-2.5 list-none">
|
||||
{headerLinks.nav.map((navLink) => (
|
||||
<li className="inline" key={navLink.label}>
|
||||
<Link
|
||||
href={navLink.href}
|
||||
target={navLink.target}
|
||||
className={`inline-block align-middle ${pathname == navLink.href ? "bg-accent" : ""} text-inherit px-2 py-1 h-8 mx-1 rounded-sm hover:bg-accent/60 transition-colors`}
|
||||
>
|
||||
{navLink.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
<span className="self-center whitespace-nowrap">
|
||||
<Link
|
||||
href={headerLinks.git.href}
|
||||
target={headerLinks.git.target}
|
||||
className="hidden md:inline-block align-middle h-8 w-8 mx-0.5 p-1 bg-inherit text-inherit hover:bg-accent/60 rounded-md transition-colors"
|
||||
>
|
||||
<span className="sr-only">Git</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={25}
|
||||
height={25}
|
||||
viewBox="0 0 123 123"
|
||||
fill="none"
|
||||
className="align-top"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M120.208 55.953 66.715 2.463a7.885 7.885 0 0 0-11.158 0l-11.109 11.11 14.088 14.088a9.373 9.373 0 0 1 11.87 11.948l13.578 13.579a9.368 9.368 0 0 1 9.704 2.23 9.386 9.386 0 0 1-6.64 16.025 9.393 9.393 0 0 1-9.21-7.547 9.384 9.384 0 0 1 .526-5.416L65.697 45.817v33.33a9.385 9.385 0 0 1 2.48 15.053 9.386 9.386 0 0 1-15.311-3.046A9.388 9.388 0 0 1 54.9 80.923a9.378 9.378 0 0 1 3.078-2.052V45.235a9.336 9.336 0 0 1-3.078-2.047A9.4 9.4 0 0 1 52.88 32.92l-13.89-13.89L2.311 55.703a7.89 7.89 0 0 0 0 11.16l53.495 53.497a7.895 7.895 0 0 0 11.157 0l53.244-53.245a7.9 7.9 0 0 0 0-11.162Z"
|
||||
/>
|
||||
</svg>
|
||||
</Link>
|
||||
<button
|
||||
className="inline-block align-middle h-8 w-8 mx-0.5 p-1 bg-inherit text-inherit hover:bg-accent/60 rounded-md transition-colors"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
if (resolvedTheme == "light") {
|
||||
setTheme("dark");
|
||||
} else {
|
||||
setTheme("light");
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Toggle theme</span>
|
||||
<Moon
|
||||
width={25}
|
||||
height={25}
|
||||
className="hidden dark:inline align-top"
|
||||
/>
|
||||
<Sun
|
||||
width={25}
|
||||
height={25}
|
||||
className="inline dark:hidden align-top"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="inline-block md:hidden align-middle h-8 w-8 mx-0.5 p-1 bg-inherit text-inherit hover:bg-accent/60 rounded-md transition-colors"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setMenuShown(true);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
<Menu width={25} height={25} />
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<nav
|
||||
className={`block md:hidden bg-background w-full h-full p-2 overflow-auto z-50 fixed top-0 ${menuShown ? "left-0" : "left-full"} transition-[left] shrink-0 duration-1000`}
|
||||
>
|
||||
<div className="flex flex-row">
|
||||
<div className="grow mx-1.5">
|
||||
<Logo
|
||||
width={160}
|
||||
height={35}
|
||||
className="inline h-8 w-auto self-center"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setMenuShown(false);
|
||||
}}
|
||||
className="inline-block md:hidden self-center"
|
||||
>
|
||||
<X className="inline-block w-8 h-8 py-1 rounded-sm bg-background text-foreground hover:bg-accent/60 hover:text-accent-foreground transition-colors" />
|
||||
</button>
|
||||
</div>
|
||||
<ul
|
||||
className={`flex ${docLinks ? "flex-row overflow-x-auto" : "flex-col"} my-3 list-none`}
|
||||
>
|
||||
{headerLinks.nav.map((navLink) => (
|
||||
<li
|
||||
className={`${docLinks ? "inline" : "block my-1 text-center"}`}
|
||||
key={navLink.label}
|
||||
>
|
||||
<Link
|
||||
href={navLink.href}
|
||||
target={navLink.target}
|
||||
className={`inline-block align-middle ${pathname == navLink.href ? "bg-accent" : ""} text-inherit px-2 py-1 h-8 mx-1 rounded-sm hover:bg-accent/60 transition-colors`}
|
||||
>
|
||||
{navLink.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
<li className={`${docLinks ? "inline" : "block my-1 text-center"}`}>
|
||||
<Link
|
||||
href={headerLinks.git.href}
|
||||
target={headerLinks.git.target}
|
||||
className="whitespace-nowrap inline-block align-middle text-inherit px-2 py-1 h-8 mx-1 rounded-sm hover:bg-accent/60 transition-colors"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={25}
|
||||
height={25}
|
||||
viewBox="0 0 123 123"
|
||||
fill="none"
|
||||
className="inline mr-2 align-top"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M120.208 55.953 66.715 2.463a7.885 7.885 0 0 0-11.158 0l-11.109 11.11 14.088 14.088a9.373 9.373 0 0 1 11.87 11.948l13.578 13.579a9.368 9.368 0 0 1 9.704 2.23 9.386 9.386 0 0 1-6.64 16.025 9.393 9.393 0 0 1-9.21-7.547 9.384 9.384 0 0 1 .526-5.416L65.697 45.817v33.33a9.385 9.385 0 0 1 2.48 15.053 9.386 9.386 0 0 1-15.311-3.046A9.388 9.388 0 0 1 54.9 80.923a9.378 9.378 0 0 1 3.078-2.052V45.235a9.336 9.336 0 0 1-3.078-2.047A9.4 9.4 0 0 1 52.88 32.92l-13.89-13.89L2.311 55.703a7.89 7.89 0 0 0 0 11.16l53.495 53.497a7.895 7.895 0 0 0 11.157 0l53.244-53.245a7.9 7.9 0 0 0 0-11.162Z"
|
||||
/>
|
||||
</svg>
|
||||
<span className="align-middle">Git</span>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
{docLinks ? (
|
||||
<ul className={`flex flex-col my-3 list-none`}>
|
||||
{docLinks.map((docLink) => (
|
||||
<li className="block my-1" key={docLink.label}>
|
||||
<Link
|
||||
href={docLink.href}
|
||||
target={docLink.target || "_self"}
|
||||
className={`${docLink.sub ? "ml-4" : ""} block align-middle ${pathname == docLink.href ? "bg-accent" : ""} text-inherit px-2 py-1 h-8 mx-1 rounded-sm hover:bg-accent/60 transition-colors`}
|
||||
>
|
||||
{docLink.label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
""
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
docLinks: PropTypes.arrayOf(PropTypes.object)
|
||||
};
|
||||
|
||||
export default Header;
|
51
components/Logo.jsx
Normal file
51
components/Logo.jsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
const Logo = (props) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={460.331}
|
||||
height={87.284}
|
||||
viewBox="0 0 121.796 23.094"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="m20 17.32-9.999 5.774-10-5.772L0 5.775 9.999 0l10 5.772Z"
|
||||
style={{
|
||||
fill: "#ff7f00",
|
||||
strokeWidth: 0.55943
|
||||
}}
|
||||
/>
|
||||
<path
|
||||
d="M5.695 6.784a1.386 1.394 0 0 0-1.382 1.341l5.601 2.477 5.622-2.486a1.386 1.394 0 0 0-1.384-1.332Zm9.843 1.977-5.624 2.6L4.31 8.77v5.741a1.386 1.394 0 0 0 1.386 1.395h8.457a1.386 1.394 0 0 0 1.386-1.395z"
|
||||
style={{
|
||||
fill: "#fff",
|
||||
strokeWidth: 4.37786
|
||||
}}
|
||||
/>
|
||||
<g
|
||||
aria-label="MERNMail"
|
||||
style={{
|
||||
fontSize: "19.7556px",
|
||||
lineHeight: 1.25,
|
||||
strokeWidth: 0.264583
|
||||
}}
|
||||
>
|
||||
<path
|
||||
d="M29.855 25.135h2.922v-6.78c0-1.081-.077-3.194-.106-4.94.512 1.93 1.09 3.946 1.428 4.94l2.421 6.78H39l2.382-6.78c.337-1.023.945-3.117 1.428-5.056-.03 1.814-.107 3.975-.107 5.055v6.781h2.962V10.762H41.13l-2.296 6.599c-.29.916-.733 2.836-1.06 4.389-.338-1.563-.782-3.483-1.072-4.39l-2.334-6.598h-4.514zM48.26 25.135h9.636v-2.44h-6.694v-3.617h6.173v-2.402h-6.173v-3.473h6.675v-2.44H48.26zM60.26 25.135h2.942v-5.131h2.315l2.74 5.131h3.26l-3.058-5.604c1.64-.704 2.547-2.122 2.547-4.09 0-2.826-1.872-4.679-5.142-4.679H60.26Zm2.942-7.514v-4.428h2.112c1.804 0 2.653.801 2.653 2.248 0 1.437-.849 2.18-2.643 2.18zM73.234 25.135h3.01v-6.607c0-.85-.087-2.393-.193-4.129.887 1.794 1.418 2.846 2.218 4.138l4.129 6.598h3.29V10.762h-3.01v6.898c0 .945.077 2.488.183 3.897a25.41 25.41 0 0 0-1.804-3.521l-4.543-7.274h-3.28z"
|
||||
style={{
|
||||
fill: "#ff7f00"
|
||||
}}
|
||||
transform="translate(-4.55 -6.367)"
|
||||
/>
|
||||
<path
|
||||
d="M88.292 25.135h2.923v-6.78c0-1.081-.078-3.194-.107-4.94.512 1.93 1.09 3.946 1.428 4.94l2.421 6.78h2.48l2.382-6.78c.338-1.023.945-3.117 1.428-5.056-.03 1.814-.106 3.975-.106 5.055v6.781h2.961V10.762h-4.534l-2.296 6.599c-.289.916-.733 2.836-1.06 4.389-.338-1.563-.782-3.483-1.071-4.39l-2.335-6.598h-4.514zM109.639 25.348c1.592 0 2.633-.705 3.154-1.708h.097v1.495h2.74v-7.263c0-2.614-2.258-3.656-4.573-3.656-2.412 0-4.148 1.07-4.698 2.942l2.653.444c.232-.695.916-1.245 2.035-1.245 1.08 0 1.689.521 1.689 1.438v.048c0 .704-.763.781-2.615.964-2.122.203-4.07.898-4.07 3.31 0 2.131 1.533 3.23 3.588 3.23zm.82-2.036c-.955 0-1.63-.434-1.63-1.273 0-.868.714-1.283 1.784-1.437.637-.087 1.785-.241 2.142-.492v1.167c0 1.138-.946 2.035-2.296 2.035zM118.099 25.135h2.894V14.351h-2.894zm1.447-12.192c.868 0 1.591-.656 1.591-1.486 0-.82-.723-1.486-1.591-1.486-.878 0-1.592.666-1.592 1.486 0 .83.714 1.486 1.592 1.486zM126.346 10.762h-2.894v14.373h2.894z"
|
||||
style={{
|
||||
fill: "currentColor",
|
||||
fontWeight: 700,
|
||||
fontFamily: "Inter",
|
||||
InkscapeFontSpecification: """
|
||||
}}
|
||||
transform="translate(-4.55 -6.367)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
export default Logo;
|
23
constants/index.jsx
Normal file
23
constants/index.jsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
export const headerLinks = {
|
||||
nav: [
|
||||
{
|
||||
href: "/",
|
||||
target: "_self",
|
||||
label: "Home"
|
||||
},
|
||||
{
|
||||
href: "/docs",
|
||||
target: "_self",
|
||||
label: "Docs"
|
||||
},
|
||||
{
|
||||
href: "/blog",
|
||||
target: "_self",
|
||||
label: "Blog"
|
||||
}
|
||||
],
|
||||
git: {
|
||||
href: "https://git.svrjs.org",
|
||||
target: "_blank"
|
||||
}
|
||||
};
|
15
package-lock.json
generated
15
package-lock.json
generated
|
@ -8,8 +8,10 @@
|
|||
"name": "mernmail-website",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "^15.0.2",
|
||||
"next-themes": "^0.4.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
|
@ -6822,6 +6824,14 @@
|
|||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "0.454.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.454.0.tgz",
|
||||
"integrity": "sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
|
||||
}
|
||||
},
|
||||
"node_modules/meow": {
|
||||
"version": "12.1.1",
|
||||
"resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz",
|
||||
|
@ -7133,7 +7143,6 @@
|
|||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
@ -7749,7 +7758,6 @@
|
|||
"version": "15.8.1",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
|
@ -7839,8 +7847,7 @@
|
|||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
"cz": "cz"
|
||||
},
|
||||
"dependencies": {
|
||||
"lucide-react": "^0.454.0",
|
||||
"next": "^15.0.2",
|
||||
"next-themes": "^0.4.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue