hypnagaga/src/components/SiteHeader/NavBar/index.svelte
2025-03-05 14:05:34 -08:00

266 lines
6.4 KiB
Svelte

<script lang="ts">
import DownArrow from './DownArrow.svelte';
import SectionDropdown from './NavDropdown/SectionDropdown.svelte';
import MoreDropdown from './NavDropdown/MoreDropdown.svelte';
import { normalizeUrl } from './utils/index';
import { getContext } from 'svelte';
let { sections = [] } = $props();
const activeSection = getContext('nav-active-section');
let windowWidth = $state(1200);
let getDisplayCount = $derived(() => {
if (windowWidth >= 1300) return 7;
return 5;
});
let navTimeout = $state();
const timeout = 250;
let displayCount = $derived(getDisplayCount());
let displaySections = $derived(sections.slice(0, displayCount));
let hiddenSections = $derived(sections.slice(displayCount));
</script>
<svelte:window bind:innerWidth="{windowWidth}" />
<div class="nav-bar">
<nav aria-label="Main navigation">
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<ul class="nav-list">
{#each displaySections as section}
{#if section.children}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<li
class="nav-item category link"
onmouseenter="{() => {
navTimeout = setTimeout(
() => activeSection.set(section.id),
timeout
);
}}"
onfocus="{() => activeSection.set(section.id)}"
onmouseleave="{() => {
clearTimeout(navTimeout);
activeSection.set(null);
}}"
onblur="{() => {
clearTimeout(navTimeout);
activeSection.set(null);
}}"
onclick="{() => {
if ($activeSection === section.id) {
clearTimeout(navTimeout);
activeSection.set(null);
}
}}"
>
<div
class="nav-button link"
class:open="{section.id === $activeSection}"
>
<a href="{normalizeUrl(section.url)}">
{section.name}
</a>
<button class="button">
<DownArrow rotate="{section.id === $activeSection}" />
</button>
</div>
{#if $activeSection === section.id}
<SectionDropdown
{section}
headingText="{`Latest in ${section.name}`}"
/>
{/if}
</li>
{:else}
<li class="nav-item category link">
<div class="nav-button link">
<a href="{normalizeUrl(section.url)}">
{section.name}
</a>
</div>
</li>
{/if}
{/each}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<li
class="nav-item"
onmouseenter="{() => {
navTimeout = setTimeout(() => activeSection.set('more'), timeout);
}}"
onfocus="{() => activeSection.set('more')}"
onmouseleave="{() => {
clearTimeout(navTimeout);
activeSection.set(null);
}}"
onblur="{() => {
clearTimeout(navTimeout);
activeSection.set(null);
}}"
onclick="{() => {
if ($activeSection === 'more') {
clearTimeout(navTimeout);
activeSection.set(null);
}
}}"
>
<div
class="nav-button more link"
class:open="{$activeSection === 'more'}"
>
<button class="button">
<span>More <DownArrow rotate="{$activeSection === 'more'}" /></span>
</button>
</div>
{#if $activeSection === 'more'}
<MoreDropdown sections="{hiddenSections}" />
{/if}
</li>
</ul>
</nav>
</div>
<style lang="scss">
@import './../scss/_colors.scss';
@import './../scss/_breakpoints.scss';
@import './../scss/_z-indexes.scss';
@import '../../../scss/mixins';
$nav-height: 64px;
$mobile-nav-height: 56px;
.nav-bar {
margin-left: auto;
@include for-mobile {
display: none;
}
}
.nav-list {
display: block;
list-style: none;
margin: 0;
padding: 0;
font-family: var(--theme-font-family-sans-serif);
font-weight: 400;
font-size: 16px;
line-height: 1.5;
}
.nav-item {
display: inline-flex;
margin: 0;
padding: 0 10px;
@include font-sans;
font-weight: 500;
font-size: 16px;
.nav-button {
position: relative;
height: $nav-height;
display: flex;
align-items: center;
cursor: pointer;
a,
span {
color: var(--nav-primary, var(--tr-dark-grey));
text-decoration: none;
&:hover,
&:active {
text-decoration: none;
// &:after {
// content: '';
// position: absolute;
// left: 0;
// right: 0;
// bottom: 0;
// display: block;
// height: 4px;
// background: var(--nav-accent, var(--tr-orange));
// opacity: 0.5;
// }
}
}
@include for-mobile {
height: $mobile-nav-height;
}
&.open:after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
display: block;
height: 4px;
background: var(--nav-accent, var(--tr-orange));
opacity: 1 !important;
}
}
.link {
position: relative;
line-height: 64px;
&:hover:after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: 0;
display: block;
height: 4px;
background: var(--nav-accent, var(--tr-orange));
}
}
}
.button {
margin: 0;
padding: 0;
border: none;
background-color: unset;
appearance: none;
cursor: pointer;
@include font-sans;
font-weight: 500;
font-size: 16px;
color: var(--nav-primary, var(--tr-dark-grey));
&:not(.focused) {
outline: none;
}
}
.category {
display: none;
// Hide all but first 4
@include for-tablet-down {
&:nth-child(-n + 4) {
display: inline-flex;
}
}
// Hide all but first 5
@include for-desktop {
&:nth-child(-n + 5) {
display: inline-flex;
}
}
// Hide all but first 7
@include for-wide-desktop {
&:nth-child(-n + 7) {
display: inline-flex;
}
}
}
</style>