266 lines
9.2 KiB
JavaScript
266 lines
9.2 KiB
JavaScript
import { Fragment, useEffect, useRef } from 'react'
|
|
import Image from 'next/image'
|
|
import Link from 'next/link'
|
|
import { useRouter } from 'next/router'
|
|
import { Popover, Transition } from '@headlessui/react'
|
|
import clsx from 'clsx'
|
|
|
|
import { Container } from '../components/Container'
|
|
|
|
function CloseIcon(props) {
|
|
return (
|
|
<svg viewBox="0 0 24 24" aria-hidden="true" {...props}>
|
|
<path
|
|
d="m17.25 6.75-10.5 10.5M6.75 6.75l10.5 10.5"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="1.5"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function ChevronDownIcon(props) {
|
|
return (
|
|
<svg viewBox="0 0 8 6" aria-hidden="true" {...props}>
|
|
<path
|
|
d="M1.75 1.75 4 4.25l2.25-2.5"
|
|
fill="none"
|
|
strokeWidth="1.5"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function SunIcon(props) {
|
|
return (
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
strokeWidth="1.5"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
aria-hidden="true"
|
|
{...props}
|
|
>
|
|
<path d="M8 12.25A4.25 4.25 0 0 1 12.25 8v0a4.25 4.25 0 0 1 4.25 4.25v0a4.25 4.25 0 0 1-4.25 4.25v0A4.25 4.25 0 0 1 8 12.25v0Z" />
|
|
<path
|
|
d="M12.25 3v1.5M21.5 12.25H20M18.791 18.791l-1.06-1.06M18.791 5.709l-1.06 1.06M12.25 20v1.5M4.5 12.25H3M6.77 6.77 5.709 5.709M6.77 17.73l-1.061 1.061"
|
|
fill="none"
|
|
/>
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function MoonIcon(props) {
|
|
return (
|
|
<svg viewBox="0 0 24 24" aria-hidden="true" {...props}>
|
|
<path
|
|
d="M17.25 16.22a6.937 6.937 0 0 1-9.47-9.47 7.451 7.451 0 1 0 9.47 9.47ZM12.75 7C17 7 17 2.75 17 2.75S17 7 21.25 7C17 7 17 11.25 17 11.25S17 7 12.75 7Z"
|
|
strokeWidth="1.5"
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
/>
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function GithubIcon(props) {
|
|
return (
|
|
<svg
|
|
viewBox="0 0 24 24"
|
|
aria-hidden="true"
|
|
className="h-6 w-6 fill-slate-900 dark:fill-zinc-200"
|
|
>
|
|
<path
|
|
fillRule="evenodd"
|
|
clipRule="evenodd"
|
|
d="M12 2C6.477 2 2 6.463 2 11.97c0 4.404 2.865 8.14 6.839 9.458.5.092.682-.216.682-.48 0-.236-.008-.864-.013-1.695-2.782.602-3.369-1.337-3.369-1.337-.454-1.151-1.11-1.458-1.11-1.458-.908-.618.069-.606.069-.606 1.003.07 1.531 1.027 1.531 1.027.892 1.524 2.341 1.084 2.91.828.092-.643.35-1.083.636-1.332-2.22-.251-4.555-1.107-4.555-4.927 0-1.088.39-1.979 1.029-2.675-.103-.252-.446-1.266.098-2.638 0 0 .84-.268 2.75 1.022A9.607 9.607 0 0 1 12 6.82c.85.004 1.705.114 2.504.336 1.909-1.29 2.747-1.022 2.747-1.022.546 1.372.202 2.386.1 2.638.64.696 1.028 1.587 1.028 2.675 0 3.83-2.339 4.673-4.566 4.92.359.307.678.915.678 1.846 0 1.332-.012 2.407-.012 2.734 0 .267.18.577.688.48 3.97-1.32 6.833-5.054 6.833-9.458C22 6.463 17.522 2 12 2Z"
|
|
></path>
|
|
</svg>
|
|
)
|
|
}
|
|
|
|
function MobileNavItem({ href, children }) {
|
|
return (
|
|
<li>
|
|
<Popover.Button
|
|
as={Link}
|
|
href={href}
|
|
className="flex items-center gap-x-2 py-2"
|
|
>
|
|
{children}
|
|
</Popover.Button>
|
|
</li>
|
|
)
|
|
}
|
|
|
|
function MobileNavigation(props) {
|
|
return (
|
|
<Popover {...props}>
|
|
<Popover.Button className="group flex items-center rounded-full bg-white/90 px-4 py-2 text-sm font-medium text-zinc-800 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur dark:bg-zinc-800/90 dark:text-zinc-200 dark:ring-white/10 dark:hover:ring-white/20">
|
|
Menu
|
|
<ChevronDownIcon className="ml-3 h-auto w-2 stroke-zinc-500 group-hover:stroke-zinc-700 dark:group-hover:stroke-zinc-400" />
|
|
</Popover.Button>
|
|
<Transition.Root>
|
|
<Transition.Child
|
|
as={Fragment}
|
|
enter="duration-150 ease-out"
|
|
enterFrom="opacity-0"
|
|
enterTo="opacity-100"
|
|
leave="duration-150 ease-in"
|
|
leaveFrom="opacity-100"
|
|
leaveTo="opacity-0"
|
|
>
|
|
<Popover.Overlay className="fixed inset-0 z-50 bg-zinc-800/40 backdrop-blur-sm dark:bg-black/80" />
|
|
</Transition.Child>
|
|
<Transition.Child
|
|
as={Fragment}
|
|
enter="duration-150 ease-out"
|
|
enterFrom="opacity-0 scale-95"
|
|
enterTo="opacity-100 scale-100"
|
|
leave="duration-150 ease-in"
|
|
leaveFrom="opacity-100 scale-100"
|
|
leaveTo="opacity-0 scale-95"
|
|
>
|
|
<Popover.Panel
|
|
focus
|
|
className="fixed inset-x-4 top-8 z-50 origin-top rounded-3xl bg-white p-8 ring-1 ring-zinc-900/5 dark:bg-zinc-900 dark:ring-zinc-800"
|
|
>
|
|
<div className="flex flex-row-reverse items-center justify-between">
|
|
<Popover.Button aria-label="Close menu" className="-m-1 p-1">
|
|
<CloseIcon className="h-6 w-6 text-zinc-500 dark:text-zinc-400" />
|
|
</Popover.Button>
|
|
<h2 className="text-sm font-medium text-zinc-600 dark:text-zinc-400">
|
|
Navigation
|
|
</h2>
|
|
</div>
|
|
<nav className="mt-6">
|
|
<ul className="-my-2 divide-y divide-zinc-100 text-base text-zinc-800 dark:divide-zinc-100/5 dark:text-zinc-300">
|
|
<MobileNavItem href="https://github.com/leondz/hatespeechdata">
|
|
View on Github <GithubIcon />
|
|
</MobileNavItem>
|
|
</ul>
|
|
</nav>
|
|
</Popover.Panel>
|
|
</Transition.Child>
|
|
</Transition.Root>
|
|
</Popover>
|
|
)
|
|
}
|
|
|
|
function NavItem({ href, children }) {
|
|
let isActive = useRouter().pathname === href
|
|
|
|
return (
|
|
<li>
|
|
<Link
|
|
href={href}
|
|
className={clsx(
|
|
'relative flex items-center gap-x-2 px-3 py-2 transition',
|
|
isActive
|
|
? 'text-teal-500 dark:text-teal-400'
|
|
: 'hover:text-teal-500 dark:hover:text-teal-400'
|
|
)}
|
|
>
|
|
{children}
|
|
{isActive && (
|
|
<span className="absolute inset-x-1 -bottom-px h-px bg-gradient-to-r from-teal-500/0 via-teal-500/40 to-teal-500/0 dark:from-teal-400/0 dark:via-teal-400/40 dark:to-teal-400/0" />
|
|
)}
|
|
</Link>
|
|
</li>
|
|
)
|
|
}
|
|
|
|
function DesktopNavigation(props) {
|
|
return (
|
|
<nav {...props}>
|
|
<ul className="flex rounded-full bg-white/90 px-3 text-sm font-medium text-zinc-800 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur dark:bg-zinc-800/90 dark:text-zinc-200 dark:ring-white/10">
|
|
<NavItem href="https://github.com/leondz/hatespeechdata">
|
|
View on Github <GithubIcon />
|
|
</NavItem>
|
|
</ul>
|
|
</nav>
|
|
)
|
|
}
|
|
|
|
function ModeToggle() {
|
|
function disableTransitionsTemporarily() {
|
|
document.documentElement.classList.add('[&_*]:!transition-none')
|
|
window.setTimeout(() => {
|
|
document.documentElement.classList.remove('[&_*]:!transition-none')
|
|
}, 0)
|
|
}
|
|
|
|
function toggleMode() {
|
|
disableTransitionsTemporarily()
|
|
|
|
let darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
|
let isSystemDarkMode = darkModeMediaQuery.matches
|
|
let isDarkMode = document.documentElement.classList.toggle('dark')
|
|
|
|
if (isDarkMode === isSystemDarkMode) {
|
|
delete window.localStorage.isDarkMode
|
|
} else {
|
|
window.localStorage.isDarkMode = isDarkMode
|
|
}
|
|
}
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
aria-label="Toggle dark mode"
|
|
className="group rounded-full bg-white/90 px-3 py-2 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur transition dark:bg-zinc-800/90 dark:ring-white/10 dark:hover:ring-white/20"
|
|
onClick={toggleMode}
|
|
>
|
|
<SunIcon className="h-6 w-6 fill-zinc-100 stroke-zinc-500 transition group-hover:fill-zinc-200 group-hover:stroke-zinc-700 dark:hidden [@media(prefers-color-scheme:dark)]:fill-teal-50 [@media(prefers-color-scheme:dark)]:stroke-teal-500 [@media(prefers-color-scheme:dark)]:group-hover:fill-teal-50 [@media(prefers-color-scheme:dark)]:group-hover:stroke-teal-600" />
|
|
<MoonIcon className="hidden h-6 w-6 fill-zinc-700 stroke-zinc-500 transition dark:block [@media(prefers-color-scheme:dark)]:group-hover:stroke-zinc-400 [@media_not_(prefers-color-scheme:dark)]:fill-teal-400/10 [@media_not_(prefers-color-scheme:dark)]:stroke-teal-500" />
|
|
</button>
|
|
)
|
|
}
|
|
|
|
function clamp(number, a, b) {
|
|
let min = Math.min(a, b)
|
|
let max = Math.max(a, b)
|
|
return Math.min(Math.max(number, min), max)
|
|
}
|
|
|
|
export function Header() {
|
|
return (
|
|
<>
|
|
<header
|
|
className="pointer-events-none relative z-50 flex flex-col"
|
|
style={{
|
|
height: 'var(--header-height)',
|
|
marginBottom: 'var(--header-mb)',
|
|
}}
|
|
>
|
|
<div
|
|
className="top-0 z-10 h-16 pt-6"
|
|
style={{ position: 'var(--header-position)' }}
|
|
>
|
|
<Container
|
|
className="top-[var(--header-top,theme(spacing.6))] w-full"
|
|
style={{ position: 'var(--header-inner-position)' }}
|
|
>
|
|
<div className="relative flex gap-4">
|
|
<div className="flex flex-1">
|
|
<MobileNavigation className="pointer-events-auto md:hidden" />
|
|
<DesktopNavigation className="pointer-events-auto hidden md:block" />
|
|
</div>
|
|
<div className="flex justify-end md:flex-1">
|
|
<div className="pointer-events-auto">
|
|
<ModeToggle />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Container>
|
|
</div>
|
|
</header>
|
|
</>
|
|
)
|
|
}
|