diff --git a/src/app/layout.js b/src/app/layout.js
index d8c275d..6ae71e0 100755
--- a/src/app/layout.js
+++ b/src/app/layout.js
@@ -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 (
-
+
{children}
diff --git a/src/components/Dropdown/Dropdown.js b/src/components/Dropdown/Dropdown.js
new file mode 100644
index 0000000..8b252a6
--- /dev/null
+++ b/src/components/Dropdown/Dropdown.js
@@ -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: , tr: tr1 }]
+ * where 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
+ * , tr: tr1 },
+ * { el: , 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 (
+ <>
+ setOpen(open)}
+ onPointerEnter={() => setOpen(open)}
+ onMouseLeave={() => setOpen(0)}
+ onPointerLeave={() => setOpen(0)}
+ ref={dropdown}
+ style={{
+ border: open ? '' : 'none',
+ }}
+ >
+
+ {dropdownElements[open]}
+
+
+
+ >
+ );
+}
diff --git a/src/components/Dropdown/Dropdown.module.css b/src/components/Dropdown/Dropdown.module.css
new file mode 100644
index 0000000..362a26d
--- /dev/null
+++ b/src/components/Dropdown/Dropdown.module.css
@@ -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;
+}
diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js
index 0c3b557..1cdfdda 100644
--- a/src/components/Header/Header.js
+++ b/src/components/Header/Header.js
@@ -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 (
+
+ );
+ };
return (
-
+
+
+
+
+
+
+
+
+
+
+
+
+
, tr: trigger1 },
+ { el:
, tr: trigger2 },
+ { el:
, tr: trigger3 },
+ { el:
, tr: trigger4 },
+ ]}
+ openState={[open, setOpen]}
+ boundaryRef={headerInnerSection}
+ boundaryTolerance={24}
+ />
+
+
);
}
diff --git a/src/components/Header/Header.module.css b/src/components/Header/Header.module.css
index 2664317..2617935 100644
--- a/src/components/Header/Header.module.css
+++ b/src/components/Header/Header.module.css
@@ -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;
}
diff --git a/src/components/Header/HeaderDropdowns/HeaderDropdown.module.css b/src/components/Header/HeaderDropdowns/HeaderDropdown.module.css
new file mode 100644
index 0000000..4f47816
--- /dev/null
+++ b/src/components/Header/HeaderDropdowns/HeaderDropdown.module.css
@@ -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;
+}
diff --git a/src/components/Header/HeaderDropdowns/HeaderDropdown1.js b/src/components/Header/HeaderDropdowns/HeaderDropdown1.js
new file mode 100644
index 0000000..5caafdb
--- /dev/null
+++ b/src/components/Header/HeaderDropdowns/HeaderDropdown1.js
@@ -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 (
+
+
+
+
+
+
+
{locale.nav.credits}
+
{locale.nav.dropdown.captions.credits}
+
+
+
+
+
+
+
+
+
+
{locale.nav.about}
+
{locale.nav.dropdown.captions.about}
+
+
+
+
+
+
+
+
+
+
{locale.nav.faq}
+
{locale.nav.dropdown.captions.faq}
+
+
+
+
+
+
+
+
+
+
{locale.nav.blog}
+
{locale.nav.dropdown.captions.blog}
+
+
+
+
+ );
+}
diff --git a/src/components/Header/HeaderDropdowns/HeaderDropdown2.js b/src/components/Header/HeaderDropdowns/HeaderDropdown2.js
new file mode 100644
index 0000000..602f477
--- /dev/null
+++ b/src/components/Header/HeaderDropdowns/HeaderDropdown2.js
@@ -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 (
+
+
+
+
+
+
+
{locale.docs.quickLinks.links[0].header}
+
{locale.docs.quickLinks.links[0].caption}
+
+
+
+
+
+
+
+
+
{locale.docs.quickLinks.links[1].header}
+
{locale.docs.quickLinks.links[1].caption}
+
+
+
+
+ );
+}
diff --git a/src/components/Header/HeaderDropdowns/HeaderDropdown3.js b/src/components/Header/HeaderDropdowns/HeaderDropdown3.js
new file mode 100644
index 0000000..04fec99
--- /dev/null
+++ b/src/components/Header/HeaderDropdowns/HeaderDropdown3.js
@@ -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 (
+
+
+
+
+
+
+
TESTTTTT
+
{locale.docs.quickLinks.links[0].caption}
+
+
+
+
+ );
+}
diff --git a/src/components/Header/HeaderDropdowns/HeaderDropdown4.js b/src/components/Header/HeaderDropdowns/HeaderDropdown4.js
new file mode 100644
index 0000000..7a5b8f1
--- /dev/null
+++ b/src/components/Header/HeaderDropdowns/HeaderDropdown4.js
@@ -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 (
+
+
+
+
+
+
+
Bees
+
{locale.docs.quickLinks.links[0].caption}
+
+
+
+
+ );
+}
diff --git a/src/components/Header/HeaderWrapper.js b/src/components/Header/HeaderWrapper.js
new file mode 100644
index 0000000..9a2e229
--- /dev/null
+++ b/src/components/Header/HeaderWrapper.js
@@ -0,0 +1,13 @@
+import Header from './Header';
+
+import { getLocale } from '@/utils/locale';
+
+export default function HeaderWrapper() {
+ const { locale } = getLocale('TODO');
+
+ return (
+
+ );
+}
diff --git a/src/components/Header/Nav.js b/src/components/Header/Nav.js
deleted file mode 100644
index 910216d..0000000
--- a/src/components/Header/Nav.js
+++ /dev/null
@@ -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 (
-
- );
- };
-
- const Dropdown1 = () => {
- return (
- <>
-
-
-
-
-
-
{locale.nav.credits}
-
{locale.nav.dropdown.captions.credits}
-
-
-
-
-
-
-
-
-
-
{locale.nav.about}
-
{locale.nav.dropdown.captions.about}
-
-
-
-
-
-
-
-
-
-
{locale.nav.faq}
-
{locale.nav.dropdown.captions.faq}
-
-
-
-
-
-
-
-
-
-
{locale.nav.blog}
-
{locale.nav.dropdown.captions.blog}
-
-
-
- >
- );
- };
-
- const Dropdown2 = () => {
- return (
- <>
-
-
-
-
-
-
{locale.docs.quickLinks.links[0].header}
-
{locale.docs.quickLinks.links[0].caption}
-
-
-
-
-
-
-
-
-
{locale.docs.quickLinks.links[1].header}
-
{locale.docs.quickLinks.links[1].caption}
-
-
-
- >
- );
- };
-
- return (
-
- );
-}
diff --git a/src/components/Section/Section.js b/src/components/Section/Section.js
index 059a3cf..b9223e7 100644
--- a/src/components/Section/Section.js
+++ b/src/components/Section/Section.js
@@ -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 (
- {children}
+
+ {children}
+
);
}