mirror of
https://github.com/PretendoNetwork/website.git
synced 2026-08-27 19:17:27 -05:00
feat: spun off dropdown into reusable component, other tweaks and changes
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import '../styles/globals.css';
|
||||
import Header from '@/components/Header/Header';
|
||||
import Header from '@/components/Header/HeaderWrapper';
|
||||
import Footer from '@/components/Footer/Footer';
|
||||
import Script from 'next/script';
|
||||
|
||||
@@ -14,7 +14,7 @@ const poppins = Poppins({
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html className={`${poppins.variable}`}>
|
||||
<body style={{ paddingTop: '36px' }}>
|
||||
<body style={{ paddingTop: '48px' }}>
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
|
||||
142
src/components/Dropdown/Dropdown.js
Normal file
142
src/components/Dropdown/Dropdown.js
Normal file
@@ -0,0 +1,142 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useRef, useMemo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './Dropdown.module.css';
|
||||
|
||||
/**
|
||||
*
|
||||
* A reusable object that renders a dropdown.
|
||||
*
|
||||
* @param {React.useState} openState: a state object [state, setState],
|
||||
* where state is an int which refers to the index of the dropdownContent to render + 1
|
||||
* (having it be +1 lets us use 0 as a "do not render" magic number)
|
||||
*
|
||||
* @param {Array} dropdownContents: an array of objects, structured as
|
||||
* follows:
|
||||
* [{ el: <El1 />, tr: tr1 }]
|
||||
* where <El1 /> is the content to be rendered when the state is set to
|
||||
* the corresponding state, and tr1 is the reference to the element that triggered
|
||||
* the dropdown in the first place
|
||||
*
|
||||
* @param {React.useRef} boundaryRef: a ref to the object to use as a boundary (the
|
||||
* dropdown will always be rendered to be [horizontally] inside of the element)
|
||||
*
|
||||
* @param {Number} boundaryTolerance: the amount of pixels by which the dropdown can poke out of the boundary.
|
||||
*
|
||||
* @example
|
||||
* <Dropdown
|
||||
* dropdownContents={[
|
||||
* { el: <El1 />, tr: tr1 },
|
||||
* { el: <El2 />, tr: tr2 },
|
||||
* ]}
|
||||
* openState={[open, setOpen]}
|
||||
* boundaryRef={navbarRef}
|
||||
* boundaryTolerance={20}
|
||||
* />
|
||||
*/
|
||||
|
||||
export default function Dropdown(ctx) {
|
||||
const [open, setOpen] = ctx.openState;
|
||||
const { boundaryRef, boundaryTolerance } = ctx;
|
||||
|
||||
const dropdownElements = useMemo(() => {
|
||||
return [null];
|
||||
}, []);
|
||||
const dropdownTriggers = useMemo(() => {
|
||||
return [null];
|
||||
}, []);
|
||||
|
||||
ctx.dropdownContents.forEach((c) => {
|
||||
dropdownElements.push(c.el);
|
||||
dropdownTriggers.push(c.tr);
|
||||
});
|
||||
|
||||
const dropdownContent = useRef(null);
|
||||
const dropdownArrow = useRef(null);
|
||||
const dropdown = useRef(null);
|
||||
|
||||
// animation business. Don't even bother.
|
||||
useEffect(() => {
|
||||
const dropdownContentRect = dropdownContent?.current?.getBoundingClientRect();
|
||||
const dropdownRect = dropdownContent?.current?.getBoundingClientRect();
|
||||
|
||||
const pxToInt = (px) => parseInt(px.replace('px', ''));
|
||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||
|
||||
if (dropdownContentRect && open) {
|
||||
const sectionWidth = pxToInt(getComputedStyle(boundaryRef.current).width);
|
||||
const dropdownWidth = dropdownRect.width;
|
||||
|
||||
function getRealCenter(trRef) {
|
||||
const style = getComputedStyle(trRef.current);
|
||||
const rightPad = pxToInt(style.paddingRight);
|
||||
const leftPad = pxToInt(style.paddingLeft);
|
||||
|
||||
const elWidth = pxToInt(style.width);
|
||||
return (elWidth - rightPad - leftPad) / 2;
|
||||
}
|
||||
|
||||
function getTriggerOffset(trRef) {
|
||||
const style = getComputedStyle(trRef.current);
|
||||
const leftPad = pxToInt(style.paddingLeft);
|
||||
return (
|
||||
trRef.current.getBoundingClientRect().left -
|
||||
boundaryRef.current.getBoundingClientRect().left +
|
||||
leftPad
|
||||
);
|
||||
}
|
||||
|
||||
function getLeftOffset(trRef) {
|
||||
return getTriggerOffset(trRef) + getRealCenter(trRef);
|
||||
}
|
||||
|
||||
const arrowOffset = getLeftOffset(dropdownTriggers[open]);
|
||||
const leftOffset = getLeftOffset(dropdownTriggers[open]) - dropdownWidth / 2;
|
||||
|
||||
const leftBoundary = 0 - boundaryTolerance;
|
||||
const rightBoundary = sectionWidth - dropdownWidth + boundaryTolerance;
|
||||
|
||||
const maxLeftOffset = clamp(leftOffset, leftBoundary, rightBoundary);
|
||||
|
||||
dropdown.current.style.opacity = 1;
|
||||
dropdown.current.style.width = `${dropdownContentRect.width}px`;
|
||||
dropdown.current.style.height = `${dropdownContentRect.height}px`;
|
||||
|
||||
dropdown.current.style.left = `${maxLeftOffset}px`;
|
||||
dropdownArrow.current.style.left = `${arrowOffset}px`;
|
||||
} else {
|
||||
dropdown.current.style.opacity = 0;
|
||||
//dropdown.current.style.width = 0;
|
||||
dropdown.current.style.height = 0;
|
||||
}
|
||||
}, [open, dropdownTriggers, boundaryTolerance, boundaryRef]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={styles.dropdown}
|
||||
onMouseEnter={() => setOpen(open)}
|
||||
onPointerEnter={() => setOpen(open)}
|
||||
onMouseLeave={() => setOpen(0)}
|
||||
onPointerLeave={() => setOpen(0)}
|
||||
ref={dropdown}
|
||||
style={{
|
||||
border: open ? '' : 'none',
|
||||
}}
|
||||
>
|
||||
<div ref={dropdownContent} className={styles.dropdownContent}>
|
||||
{dropdownElements[open]}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(styles.dropdownArrow, {
|
||||
[styles.arrowAppear]: Boolean(open),
|
||||
[styles.arrowDisappear]: !Boolean(open),
|
||||
})}
|
||||
ref={dropdownArrow}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
115
src/components/Dropdown/Dropdown.module.css
Normal file
115
src/components/Dropdown/Dropdown.module.css
Normal file
@@ -0,0 +1,115 @@
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
background: var(--bg-shade-2);
|
||||
bottom: -0.75rem;
|
||||
left: 0;
|
||||
transform: translate(0, 100%);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--bg-shade-3);
|
||||
width: 0;
|
||||
height: 0;
|
||||
transition: width 200ms ease-out, height 100ms ease-out, opacity 500ms ease-out 100ms, left 150ms ease-out;
|
||||
}
|
||||
|
||||
.dropdownArrow {
|
||||
position: absolute;
|
||||
bottom: calc(-0.75rem - 6px);
|
||||
left: 0;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: var(--bg-shade-2);
|
||||
border: 1px solid var(--bg-shade-3);
|
||||
border-bottom: 0;
|
||||
border-right: 0;
|
||||
transform: translateX(-50%) rotate(45deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dropdownArrow.arrowAppear {
|
||||
transform: scale(1) translateX(-50%) rotate(45deg);
|
||||
transition: transform 250ms ease-out 200ms;
|
||||
}
|
||||
|
||||
.dropdownArrow.arrowDisappear {
|
||||
transform: scale(0) translateX(-50%) rotate(45deg);
|
||||
transition: transform 50ms ease-in;
|
||||
}
|
||||
|
||||
.dropdown::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 1.4rem;
|
||||
top: -1.4rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown .dropdownContent {
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.dropdownContent .link {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1.25rem;
|
||||
padding-right: 5rem;
|
||||
border-radius: 6px;
|
||||
max-width: 90vw;
|
||||
min-width: 24rem;
|
||||
}
|
||||
|
||||
.dropdownContent .link .iconWrapper {
|
||||
background: var(--bg-shade-0);
|
||||
color: var(--accent-shade-2);
|
||||
display: flex;
|
||||
padding: 12px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.dropdownContent .link p {
|
||||
margin: 0;
|
||||
margin-top: 4px;
|
||||
font-weight: 300;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.dropdownContent .link .arrow {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 1.5rem;
|
||||
color: var(--accent-shade-1);
|
||||
}
|
||||
|
||||
.dropdownContent,
|
||||
.dropdownContent .link {
|
||||
animation: appear 0.75s;
|
||||
}
|
||||
|
||||
@keyframes appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownContent .link:hover {
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
|
||||
.dropdownContent .link:hover .iconWrapper {
|
||||
color: var(--accent-shade-1);
|
||||
}
|
||||
|
||||
.dropdownContent .link:hover .arrow {
|
||||
display: block;
|
||||
}
|
||||
@@ -1,25 +1,116 @@
|
||||
import Logo from '@/components/Logo/Logo';
|
||||
import Section from '@/components/Section/Section';
|
||||
import Nav from './Nav';
|
||||
'use client';
|
||||
|
||||
import { getLocale } from '@/utils/locale';
|
||||
/*
|
||||
* This is a separate component because it needs to be rendered
|
||||
* clientside, while the main header needs to be rendered on
|
||||
* the server to be able to fetch the locale. Very cool.
|
||||
*/
|
||||
|
||||
import Link from 'next/link';
|
||||
import { Heart, Translate, UserCircle } from '@phosphor-icons/react';
|
||||
import { useState, useRef } from 'react';
|
||||
|
||||
import styles from './Header.module.css';
|
||||
|
||||
export default function Header(ctx) {
|
||||
const { locale } = getLocale('TODO');
|
||||
import Section from '../Section/Section';
|
||||
import Logo from '../Logo/Logo';
|
||||
import Dropdown from '../Dropdown/Dropdown';
|
||||
|
||||
import HeaderDropdown1 from './HeaderDropdowns/HeaderDropdown1';
|
||||
import HeaderDropdown2 from './HeaderDropdowns/HeaderDropdown2';
|
||||
import HeaderDropdown3 from './HeaderDropdowns/HeaderDropdown3';
|
||||
import HeaderDropdown4 from './HeaderDropdowns/HeaderDropdown4';
|
||||
|
||||
export default function Header({ locale }) {
|
||||
const [open, setOpen] = useState(0);
|
||||
|
||||
const headerInnerSection = useRef(null);
|
||||
|
||||
const trigger1 = useRef(null);
|
||||
const trigger2 = useRef(null);
|
||||
const trigger3 = useRef(null);
|
||||
const trigger4 = useRef(null);
|
||||
|
||||
const DropdownTrigger = ({ n, refForEl, children }) => {
|
||||
return (
|
||||
<button
|
||||
ref={refForEl}
|
||||
onMouseEnter={() => setOpen(n)}
|
||||
onPointerEnter={() => setOpen(n)}
|
||||
onMouseLeave={() => setOpen(0)}
|
||||
onPointerLeave={() => setOpen(0)}
|
||||
style={{
|
||||
color: open === n && 'var(--text-shade-3)',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<header id="header">
|
||||
<Section compact className={styles.headerWrapper} contentClassName={styles.header}>
|
||||
<Link href="/" aria-label="Homepage" title="Homepage">
|
||||
<Logo text size={42} className={styles.logo} />
|
||||
</Link>
|
||||
<Section
|
||||
compact
|
||||
className={styles.headerWrapper}
|
||||
contentClassName={styles.header}
|
||||
contentRef={headerInnerSection}
|
||||
>
|
||||
<Link href="/" aria-label="Homepage" title="Homepage">
|
||||
<Logo text size={42} className={styles.logo} />
|
||||
</Link>
|
||||
|
||||
<Nav locale={locale} />
|
||||
</Section>
|
||||
</header>
|
||||
<div className={styles.navWrapper}>
|
||||
<nav className={styles.nav}>
|
||||
<ul className={styles.dropdownTriggers}>
|
||||
<li>
|
||||
<DropdownTrigger n={1} refForEl={trigger1}>
|
||||
{locale.nav.about}
|
||||
</DropdownTrigger>
|
||||
</li>
|
||||
<li>
|
||||
<DropdownTrigger n={2} refForEl={trigger2}>
|
||||
{locale.nav.docs}
|
||||
</DropdownTrigger>
|
||||
</li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/progress">
|
||||
<button>{locale.nav.progress}</button>
|
||||
</Link>
|
||||
</li>
|
||||
<li className={styles.upgrade}>
|
||||
<Link href="/upgrade">
|
||||
<button>
|
||||
<Heart size={16} weight="fill" alt="" />
|
||||
<span>{locale.nav.donate}</span>
|
||||
</button>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div className={styles.rightIconsWrapper}>
|
||||
<DropdownTrigger n={3} refForEl={trigger3}>
|
||||
<Translate size={24} />
|
||||
</DropdownTrigger>
|
||||
|
||||
<DropdownTrigger n={4} refForEl={trigger4}>
|
||||
<UserCircle size={32} />
|
||||
</DropdownTrigger>
|
||||
</div>
|
||||
<Dropdown
|
||||
dropdownContents={[
|
||||
{ el: <HeaderDropdown1 key={1} locale={locale} />, tr: trigger1 },
|
||||
{ el: <HeaderDropdown2 key={2} locale={locale} />, tr: trigger2 },
|
||||
{ el: <HeaderDropdown3 key={3} locale={locale} />, tr: trigger3 },
|
||||
{ el: <HeaderDropdown4 key={4} locale={locale} />, tr: trigger4 },
|
||||
]}
|
||||
openState={[open, setOpen]}
|
||||
boundaryRef={headerInnerSection}
|
||||
boundaryTolerance={24}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
padding: 1rem 0 !important;
|
||||
background: rgba(19, 23, 51, 0.9);
|
||||
position: fixed !important;
|
||||
width: 100%;
|
||||
width: 100vw;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 99;
|
||||
backdrop-filter: blur(2rem);
|
||||
border-bottom: 1px solid var(--bg-shade-2);
|
||||
@@ -21,6 +22,14 @@
|
||||
margin-left: -8px;
|
||||
}
|
||||
|
||||
.navWrapper {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -51,7 +60,8 @@
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.nav li button {
|
||||
.nav li button,
|
||||
.rightIconsWrapper button {
|
||||
appearance: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
@@ -83,96 +93,9 @@
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
|
||||
background: var(--bg-shade-2);
|
||||
|
||||
top: 3rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--bg-shade-3);
|
||||
width: fit-content;
|
||||
height: fit-content;
|
||||
transition: width 150ms ease-out, height 150ms ease-out;
|
||||
}
|
||||
|
||||
.dropdown::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
display: block;
|
||||
height: 1.6rem;
|
||||
top: -1.6rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown > div {
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.dropdown > div,
|
||||
.dropdown .link {
|
||||
animation: appear 0.75s;
|
||||
}
|
||||
|
||||
@keyframes appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown .link {
|
||||
position: relative;
|
||||
.rightIconsWrapper {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1.25rem;
|
||||
padding-right: 5rem;
|
||||
border-radius: 6px;
|
||||
max-width: 90vw;
|
||||
min-width: 24rem;
|
||||
}
|
||||
|
||||
.dropdown .link .iconWrapper {
|
||||
background: var(--bg-shade-0);
|
||||
color: var(--accent-shade-2);
|
||||
display: flex;
|
||||
padding: 12px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.dropdown .link p {
|
||||
margin: 0;
|
||||
margin-top: 4px;
|
||||
font-weight: 300;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.dropdown .link .arrow {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 1.5rem;
|
||||
color: var(--accent-shade-1);
|
||||
}
|
||||
|
||||
.dropdown .link:hover {
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
|
||||
.dropdown .link:hover .iconWrapper {
|
||||
color: var(--accent-shade-1);
|
||||
}
|
||||
|
||||
.dropdown .link:hover .arrow {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
.dropdownContent .link {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1.25rem;
|
||||
padding-right: 5rem;
|
||||
border-radius: 6px;
|
||||
max-width: 90vw;
|
||||
min-width: 24rem;
|
||||
}
|
||||
|
||||
.dropdownContent .link .iconWrapper {
|
||||
background: var(--bg-shade-0);
|
||||
color: var(--accent-shade-2);
|
||||
display: flex;
|
||||
padding: 12px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.dropdownContent .link p {
|
||||
margin: 0;
|
||||
margin-top: 4px;
|
||||
font-weight: 300;
|
||||
color: var(--text-shade-3);
|
||||
}
|
||||
|
||||
.dropdownContent .link .arrow {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 1.5rem;
|
||||
color: var(--accent-shade-1);
|
||||
}
|
||||
|
||||
.dropdownContent,
|
||||
.dropdownContent .link {
|
||||
animation: appear 0.75s;
|
||||
}
|
||||
|
||||
@keyframes appear {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownContent .link:hover {
|
||||
background: var(--bg-shade-3);
|
||||
}
|
||||
|
||||
.dropdownContent .link:hover .iconWrapper {
|
||||
color: var(--accent-shade-1);
|
||||
}
|
||||
|
||||
.dropdownContent .link:hover .arrow {
|
||||
display: block;
|
||||
}
|
||||
56
src/components/Header/HeaderDropdowns/HeaderDropdown1.js
Normal file
56
src/components/Header/HeaderDropdowns/HeaderDropdown1.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { ArrowRight, Users, Info, SealQuestion, NewspaperClipping } from '@phosphor-icons/react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import Title from '@/components/Title/Title';
|
||||
|
||||
import styles from './HeaderDropdown.module.css';
|
||||
|
||||
export default function HeaderDropdown1({ locale }) {
|
||||
return (
|
||||
<div className={styles.dropdownContent}>
|
||||
<Link className={styles.link} href="/#credits">
|
||||
<div className={styles.iconWrapper}>
|
||||
<Users weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.nav.credits}</Title>
|
||||
<p>{locale.nav.dropdown.captions.credits}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
|
||||
<Link className={styles.link} href="/#about">
|
||||
<div className={styles.iconWrapper}>
|
||||
<Info weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.nav.about}</Title>
|
||||
<p>{locale.nav.dropdown.captions.about}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
|
||||
<Link className={styles.link} href="/#faq">
|
||||
<div className={styles.iconWrapper}>
|
||||
<SealQuestion weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.nav.faq}</Title>
|
||||
<p>{locale.nav.dropdown.captions.faq}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
|
||||
<Link className={styles.link} href="/blog">
|
||||
<div className={styles.iconWrapper}>
|
||||
<NewspaperClipping weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.nav.blog}</Title>
|
||||
<p>{locale.nav.dropdown.captions.blog}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
33
src/components/Header/HeaderDropdowns/HeaderDropdown2.js
Normal file
33
src/components/Header/HeaderDropdowns/HeaderDropdown2.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ArrowRight, Wrench, SealWarning } from '@phosphor-icons/react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import Title from '@/components/Title/Title';
|
||||
|
||||
import styles from './HeaderDropdown.module.css';
|
||||
|
||||
export default function HeaderDropdown2({ locale }) {
|
||||
return (
|
||||
<div className={styles.dropdownContent}>
|
||||
<Link className={styles.link} href="/docs">
|
||||
<div className={styles.iconWrapper}>
|
||||
<Wrench weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.docs.quickLinks.links[0].header}</Title>
|
||||
<p>{locale.docs.quickLinks.links[0].caption}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
<Link className={styles.link} href="/docs/search">
|
||||
<div className={styles.iconWrapper}>
|
||||
<SealWarning weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.docs.quickLinks.links[1].header}</Title>
|
||||
<p>{locale.docs.quickLinks.links[1].caption}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
src/components/Header/HeaderDropdowns/HeaderDropdown3.js
Normal file
23
src/components/Header/HeaderDropdowns/HeaderDropdown3.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ArrowRight, Wrench, SealWarning } from '@phosphor-icons/react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import Title from '@/components/Title/Title';
|
||||
|
||||
import styles from './HeaderDropdown.module.css';
|
||||
|
||||
export default function HeaderDropdown3({ locale }) {
|
||||
return (
|
||||
<div className={styles.dropdownContent}>
|
||||
<Link className={styles.link} href="/docs">
|
||||
<div className={styles.iconWrapper}>
|
||||
<Wrench weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">TESTTTTT</Title>
|
||||
<p>{locale.docs.quickLinks.links[0].caption}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
23
src/components/Header/HeaderDropdowns/HeaderDropdown4.js
Normal file
23
src/components/Header/HeaderDropdowns/HeaderDropdown4.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { ArrowRight, Wrench, SealWarning } from '@phosphor-icons/react';
|
||||
import Link from 'next/link';
|
||||
|
||||
import Title from '@/components/Title/Title';
|
||||
|
||||
import styles from './HeaderDropdown.module.css';
|
||||
|
||||
export default function HeaderDropdown4({ locale }) {
|
||||
return (
|
||||
<div className={styles.dropdownContent}>
|
||||
<Link className={styles.link} href="/docs">
|
||||
<div className={styles.iconWrapper}>
|
||||
<Wrench weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">Bees</Title>
|
||||
<p>{locale.docs.quickLinks.links[0].caption}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
src/components/Header/HeaderWrapper.js
Normal file
13
src/components/Header/HeaderWrapper.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import Header from './Header';
|
||||
|
||||
import { getLocale } from '@/utils/locale';
|
||||
|
||||
export default function HeaderWrapper() {
|
||||
const { locale } = getLocale('TODO');
|
||||
|
||||
return (
|
||||
<header id="header">
|
||||
<Header locale={locale} />
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
'use client';
|
||||
|
||||
/*
|
||||
* This is a separate component because it needs to be rendered
|
||||
* clientside, while the main header needs to be rendered on
|
||||
* the server to be able to fetch the locale. Very cool.
|
||||
*/
|
||||
|
||||
import Link from 'next/link';
|
||||
import {
|
||||
ArrowRight,
|
||||
Heart,
|
||||
Users,
|
||||
Info,
|
||||
SealQuestion,
|
||||
NewspaperClipping,
|
||||
Wrench,
|
||||
SealWarning,
|
||||
} from '@phosphor-icons/react';
|
||||
|
||||
import styles from './Header.module.css';
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import Title from '../Title/Title';
|
||||
|
||||
export default function Nav({ locale }) {
|
||||
const [open, setOpen] = useState(0);
|
||||
|
||||
const dropdown = useRef(null);
|
||||
const dropdownWrap = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
let w = 0;
|
||||
let h = 0;
|
||||
|
||||
const clR = dropdown?.current?.getBoundingClientRect();
|
||||
if (clR && open) {
|
||||
w = clR.width;
|
||||
h = clR.height;
|
||||
}
|
||||
dropdownWrap.current.style.width = `${w}px`;
|
||||
dropdownWrap.current.style.height = `${h}px`;
|
||||
}, [open]);
|
||||
|
||||
const DropdownTrigger = ({ n, children }) => {
|
||||
return (
|
||||
<button
|
||||
onMouseEnter={() => setOpen(n)}
|
||||
onMouseLeave={() => setOpen(0)}
|
||||
style={{
|
||||
color: open === n && 'var(--text-shade-3)',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const Dropdown1 = () => {
|
||||
return (
|
||||
<>
|
||||
<Link className={styles.link} href="/#credits">
|
||||
<div className={styles.iconWrapper}>
|
||||
<Users weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.nav.credits}</Title>
|
||||
<p>{locale.nav.dropdown.captions.credits}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
|
||||
<Link className={styles.link} href="/#about">
|
||||
<div className={styles.iconWrapper}>
|
||||
<Info weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.nav.about}</Title>
|
||||
<p>{locale.nav.dropdown.captions.about}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
|
||||
<Link className={styles.link} href="/#faq">
|
||||
<div className={styles.iconWrapper}>
|
||||
<SealQuestion weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.nav.faq}</Title>
|
||||
<p>{locale.nav.dropdown.captions.faq}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
|
||||
<Link className={styles.link} href="/blog">
|
||||
<div className={styles.iconWrapper}>
|
||||
<NewspaperClipping weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.nav.blog}</Title>
|
||||
<p>{locale.nav.dropdown.captions.blog}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Dropdown2 = () => {
|
||||
return (
|
||||
<>
|
||||
<Link className={styles.link} href="/docs">
|
||||
<div className={styles.iconWrapper}>
|
||||
<Wrench weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.docs.quickLinks.links[0].header}</Title>
|
||||
<p>{locale.docs.quickLinks.links[0].caption}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
<Link className={styles.link} href="/docs/search">
|
||||
<div className={styles.iconWrapper}>
|
||||
<SealWarning weight="fill" size={32} />
|
||||
</div>
|
||||
<div>
|
||||
<Title element="h4">{locale.docs.quickLinks.links[1].header}</Title>
|
||||
<p>{locale.docs.quickLinks.links[1].caption}</p>
|
||||
</div>
|
||||
<ArrowRight size={28} className={styles.arrow} />
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className={styles.nav}>
|
||||
<ul className={styles.dropdownTriggers}>
|
||||
<li>
|
||||
<DropdownTrigger n={1}>{locale.nav.about}</DropdownTrigger>
|
||||
</li>
|
||||
<li>
|
||||
<DropdownTrigger n={2}>{locale.nav.docs}</DropdownTrigger>
|
||||
</li>
|
||||
<div
|
||||
className={styles.dropdown}
|
||||
onMouseEnter={() => setOpen(open)}
|
||||
onMouseLeave={() => setOpen(0)}
|
||||
ref={dropdownWrap}
|
||||
style={{
|
||||
border: open ? '' : 'none',
|
||||
}}
|
||||
>
|
||||
<div ref={dropdown}>{open === 1 ? <Dropdown1 /> : open === 2 ? <Dropdown2 /> : null}</div>
|
||||
</div>
|
||||
</ul>
|
||||
<ul>
|
||||
<li>
|
||||
<Link href="/progress">
|
||||
<button>{locale.nav.progress}</button>
|
||||
</Link>
|
||||
</li>
|
||||
<li className={styles.upgrade}>
|
||||
<Link href="/upgrade">
|
||||
<button>
|
||||
<Heart size={16} weight="fill" alt="" />
|
||||
<span>{locale.nav.donate}</span>
|
||||
</button>
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import styles from './Section.module.css';
|
||||
*/
|
||||
|
||||
export default function Section(ctx) {
|
||||
const { id, style, children, className, compact, contentClassName } = ctx;
|
||||
const { id, style, children, className, compact, contentClassName, contentRef } = ctx;
|
||||
|
||||
return (
|
||||
<section
|
||||
@@ -27,7 +27,9 @@ export default function Section(ctx) {
|
||||
id={id}
|
||||
style={style}
|
||||
>
|
||||
<div className={classNames(styles.content, contentClassName)}>{children}</div>
|
||||
<div className={classNames(styles.content, contentClassName)} ref={contentRef}>
|
||||
{children}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user