feat: navbar, homepage header
All checks were successful
Deploy Website / build (push) Successful in 1m28s

This commit is contained in:
TheClashFruit 2024-04-21 16:48:51 +02:00
parent fb79913b35
commit 6fdc97b98b
Signed by: TheClashFruit
GPG key ID: 09BB24C34C2F3204
12 changed files with 480 additions and 19 deletions

28
components/Button.tsx Normal file
View file

@ -0,0 +1,28 @@
import styles from '@/styles/Button.module.scss';
import Link from 'next/link';
type Props = {
href?: string;
children: React.ReactNode;
className?: string;
disabled?: boolean;
type: 'primary' | 'outlined';
[key: string]: any;
};
export default function Button({ href, children, className, disabled, type, ...props }: Props) {
return (
<>
{href ? (
<Link href={href} className={className ? `${className} ${styles.button}` : styles.button} data-type={type} data-disabled={disabled} {...props}>
{children}
</Link>
) : (
<button className={className ? `${className} ${styles.button}` : styles.button} disabled={disabled} data-type={type} data-disabled={disabled} {...props}>
{children}
</button>
)}
</>
);
}

View file

@ -21,7 +21,7 @@ export default function MarkdownPage({ metadata, children }: Props) {
<meta name="description" content={metadata?.description} />
</Head>
<Navbar />
<Navbar currentPage="docs" />
<div className={`${styles.pageContent} ${styles.container}`}>
<aside className={styles.sideBar}>

View file

@ -3,28 +3,77 @@ import styles from '@/styles/Navbar.module.scss';
import Link from 'next/link';
import {
Mountain
Menu,
Mountain,
X
} from 'lucide-react';
export default function Navbar() {
import {
useEffect,
useRef,
useState
} from 'react';
type Props = {
currentPage: string;
}
export default function Navbar({ currentPage }: Props) {
const [ navOpen, setNavOpen ] = useState(false);
const [ scrolled, setScrolled ] = useState(false);
const toggleNav = () => {
setNavOpen(!navOpen);
};
useEffect(() => {
if (navOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'auto';
}
}, [ navOpen ]);
useEffect(() => {
if(window.scrollY <= 10)
setScrolled(false);
else
setScrolled(true);
document.addEventListener('scroll', () => {
if(window.scrollY <= 10)
setScrolled(false);
else
setScrolled(true);
console.log(window.scrollY);
});
}, [ ]);
return (
<nav className={styles.navBar}>
<nav className={styles.navBar} data-open={navOpen} data-scrolled={scrolled}>
<div className={styles.container}>
<div className={styles.navMain}>
<Mountain />
<button onClick={toggleNav}>
{!navOpen ? <Menu /> : <X />}
</button>
</div>
<div className={styles.navCollapse}>
<ul className={styles.navLinks}>
<li>
<Link href="/">Home</Link>
<Link href="/" className={currentPage === 'home' ? styles.active : ''}>Home</Link>
</li>
<li>
<Link href="/docs">Documentation</Link>
<Link href="/docs" className={currentPage === 'docs' ? styles.active : ''}>Documentation</Link>
</li>
<li>
<Link href="/servers">Servers</Link>
<Link href="/servers" className={currentPage === 'servers' ? styles.active : ''}>Servers</Link>
</li>
<li>
<Link href="/blog">Blog</Link>
<Link href="/blog" className={currentPage === 'blog' ? styles.active : ''}>Blog</Link>
</li>
</ul>
</div>

View file

@ -0,0 +1,23 @@
import { useState, useEffect } from 'react';
import { useTheme } from 'next-themes';
export default function ThemeChanger() {
const [mounted, setMounted] = useState(false);
const { theme, setTheme } = useTheme();
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
<select value={theme} onChange={e => setTheme(e.target.value)}>
<option value="system">System</option>
<option value="dark">Dark</option>
<option value="light">Light</option>
</select>
);
}

View file

@ -17,6 +17,7 @@
"highlight.js": "^11.9.0",
"lucide-react": "^0.372.0",
"next": "14.2.2",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"rehype-highlight": "^7.0.0"

View file

@ -1,7 +1,12 @@
import '@/styles/globals.scss';
import { ThemeProvider } from 'next-themes';
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
);
}

View file

@ -1,15 +1,66 @@
import styles from '@/styles/Home.module.scss';
import Navbar from '@/components/Navbar';
import ThemeChanger from '@/components/ThemeChanger';
import {
Mountain
} from 'lucide-react';
import Button from '@/components/Button';
import Head from 'next/head';
export default function Home() {
return (
<>
<Navbar />
<Head>
<title>Flint &bull; Git Hosting That Just Works.</title>
<h1>Flint</h1>
<meta name="description" content="A free and open-source, self hostable federated git platform." />
</Head>
<Navbar currentPage="home" />
<header className={styles.headLine}>
<h3>Flint</h3>
<h1>
Git Hosting That Just Works.
</h1>
<p>
A free and open-source, self hostable federated git platform.
</p>
<ul>
<li>
<Button href="/servers" type={'primary'}>
Find a Server
</Button>
</li>
<li>
<Button href="/docs" type={'outlined'}>
Get Started
</Button>
</li>
</ul>
<span className={styles.gradient}></span>
</header>
<main className={styles.container}>
<section>
<h2>Features</h2>
<ul>
<li>Self-Hostable</li>
<li>Federated</li>
<li>Open-Source</li>
<li>Easy to Use</li>
</ul>
</section>
</main>
<ThemeChanger />
</>
);
}

View file

@ -29,6 +29,9 @@ dependencies:
next:
specifier: 14.2.2
version: 14.2.2(react-dom@18.2.0)(react@18.2.0)(sass@1.75.0)
next-themes:
specifier: ^0.3.0
version: 0.3.0(react-dom@18.2.0)(react@18.2.0)
react:
specifier: ^18
version: 18.2.0
@ -2864,6 +2867,16 @@ packages:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
dev: false
/next-themes@0.3.0(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==}
peerDependencies:
react: ^16.8 || ^17 || ^18
react-dom: ^16.8 || ^17 || ^18
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/next@14.2.2(react-dom@18.2.0)(react@18.2.0)(sass@1.75.0):
resolution: {integrity: sha512-oGwUaa2bCs47FbuxWMpOoXtBMPYpvTPgdZr3UAo+pu7Ns00z9otmYpoeV1HEiYL06AlRQQIA/ypK526KjJfaxg==}
engines: {node: '>=18.17.0'}

59
styles/Button.module.scss Normal file
View file

@ -0,0 +1,59 @@
.button {
cursor: pointer;
padding: 1rem 1.5rem;
border-radius: 0.75rem;
font-size: 1rem;
text-decoration: none;
transition: background 0.3s, color 0.3s;
&[data-type='primary'] {
border: 2px solid #3B0764;
background: #3B0764;
color: #f8fafc;
[data-theme='dark'] & {
border: 2px solid #f8fafc;
background: #f8fafc;
color: #0f172a;
}
&:hover {
background: #2e1065;
[data-theme='dark'] & {
background: #f8fafc;
}
}
}
&[data-type='outlined'] {
border: 2px solid #3B0764;
color: #3B0764;
[data-theme='dark'] & {
border: 2px solid #f8fafc;
color: #f8fafc;
}
&:hover {
background: #3B0764;
color: #f8fafc;
[data-theme='dark'] & {
background: #f8fafc;
color: #0f172a;
}
}
}
}

90
styles/Home.module.scss Normal file
View file

@ -0,0 +1,90 @@
@import 'variables.module';
.headLine {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 1rem;
min-height: 100vh;
text-align: center;
overflow: hidden;
h3 {
font-size: 2rem;
max-width: 50rem;
margin-bottom: 2rem;
}
h1 {
font-size: 5rem;
max-width: 50rem;
background: linear-gradient(180deg, #e5e7eb, #f9fafb), #e5e7eb;
background-clip: text;
[data-theme='dark'] & {
background: linear-gradient(180deg, #f9fafb, #e5e7eb), #f9fafb;
background-clip: text;
}
margin-bottom: 1rem;
@media (max-width: 768px) {
font-size: 3.5rem;
}
}
p {
font-size: 1.25rem;
line-height: 1.75rem;
max-width: 50rem;
margin-bottom: 8rem;
}
ul {
list-style: none;
display: flex;
gap: 1rem;
}
.gradient {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: -1;
width: 1000px;
height: 1000px;
max-width: 100%;
background: radial-gradient(50% 50% at 50% 50%, #ddd6fe 0%, #f8fafc 100%);
[data-theme='dark'] & {
background: radial-gradient(50% 50% at 50% 50%, #2e1065 0%, #0f172a 100%);
}
filter: blur(128px);
mix-blend-mode: normal;
will-change: filter;
}
}

View file

@ -1,23 +1,149 @@
@import 'variables.module';
.navBar {
position: fixed;
top: 0;
left: 0;
right: 0;
> .container {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
padding-bottom: 1rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
> .navMain {
display: flex;
justify-content: space-between;
align-items: center;
> button {
display: none;
justify-content: center;
align-items: center;
padding: 0.15rem;
background: none;
border: none;
cursor: pointer;
> svg {
width: 1.5rem;
height: 1.5rem;
fill: rgba(#0f172a, .75);
[data-theme='dark'] & {
fill: rgba(#f8fafc, .75);
}
}
@media (max-width: 768px) {
display: flex;
}
}
}
> .navCollapse {
> .navLinks {
list-style: none;
display: flex;
gap: 0.5rem;
> li {
padding: 1rem;
> a {
text-decoration: none;
color: rgba(#0f172a, .75);
[data-theme='dark'] & {
color: rgba(#f8fafc, .75);
}
&:hover, &.active {
color: #3B0764;
[data-theme='dark'] & {
color: #f8fafc;
}
}
}
}
}
}
}
&[data-scrolled='true'] {
background: rgba(#f8fafc, .95);
[data-theme='dark'] & {
background: rgba(#0f172a, .95);
}
}
@media (max-width: 768px) {
> .container {
flex-direction: column;
justify-content: flex-start;
padding: 1rem !important;
height: 100%;
> .navMain {
width: 100%;
}
}
.navCollapse {
display: none;
flex: 1;
width: 100%;
margin-top: 1rem;
> .navLinks {
flex-direction: column;
justify-content: center;
align-items: flex-end;
height: 100%;
> li {
padding: 0.75rem 0 !important;
}
}
}
&[data-open='true'] {
height: 100dvh;
background: rgba(#f8fafc, .95);
.navCollapse {
display: block;
}
[data-theme='dark'] & {
background: rgba(#0f172a, .95);
}
}
}

View file

@ -5,12 +5,28 @@
margin: 0;
box-sizing: border-box;
font-size: 1rem;
}
body {
font-family: 'Noto Sans', sans;
color: #0f172a;
background: #f8fafc;
[data-theme='dark'] & {
color: #f8fafc;
background: #0f172a;
}
}
pre, code {
font-family: 'Noto Sans Mono', monospace;
}
::selection {
background: rgba(#3B0764, 0.7);
color: #fff;
}