feat: navbar, homepage header
All checks were successful
Deploy Website / build (push) Successful in 1m28s
All checks were successful
Deploy Website / build (push) Successful in 1m28s
This commit is contained in:
parent
fb79913b35
commit
6fdc97b98b
28
components/Button.tsx
Normal file
28
components/Button.tsx
Normal 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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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}>
|
||||
|
|
|
@ -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}>
|
||||
<Mountain />
|
||||
<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>
|
||||
|
|
23
components/ThemeChanger.tsx
Normal file
23
components/ThemeChanger.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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 • Git Hosting That Just Works.</title>
|
||||
|
||||
<h1>Flint</h1>
|
||||
<meta name="description" content="A free and open-source, self hostable federated git platform." />
|
||||
</Head>
|
||||
|
||||
<p>
|
||||
A free and open-source, self hostable federated git platform.
|
||||
</p>
|
||||
<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 />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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
59
styles/Button.module.scss
Normal 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
90
styles/Home.module.scss
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in a new issue