649 lines
19 KiB
Plaintext
649 lines
19 KiB
Plaintext
component Navbar {
|
|
outputs {
|
|
toggle(payload: { open: boolean })
|
|
open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
|
close(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
|
select(payload: { item: string | number | boolean | null | object; value: string | number | boolean; level: string })
|
|
action(payload: { item: string | number | boolean | null | object; value: string | number | boolean })
|
|
}
|
|
|
|
props {
|
|
size: string = "default"
|
|
color: string = "primary"
|
|
label: string = "Primary navigation"
|
|
topbarLabel: string = "Utility navigation"
|
|
brand: Record<string, unknown> = {}
|
|
items: unknown[] = []
|
|
actions: unknown[] = []
|
|
active: string = ""
|
|
sticky: boolean = false
|
|
openOnHover: boolean = false
|
|
maxWidth: string = "full"
|
|
mobileLabel: string = "Toggle navigation"
|
|
class: string = ""
|
|
}
|
|
|
|
state mobileOpen: boolean = false
|
|
|
|
functions {
|
|
client function toggleNavigation() {
|
|
mobileOpen = !mobileOpen
|
|
output.toggle({ open: mobileOpen })
|
|
output[mobileOpen ? "open" : "close"]({ source: "mobile" })
|
|
}
|
|
|
|
client function selectItem(item, level) {
|
|
mobileOpen = false
|
|
output.select({ item: item, value: item.value || "", level: level })
|
|
}
|
|
|
|
client function selectAction(item) {
|
|
mobileOpen = false
|
|
output.action({ item: item, value: item.value || "" })
|
|
}
|
|
|
|
shared function isItemActive(item) {
|
|
if (!item) return false
|
|
if (item.value && item.value === active) return true
|
|
if (!item.children || !item.children.length) return false
|
|
return item.children.some(function (child) {
|
|
return isItemActive(child)
|
|
})
|
|
}
|
|
|
|
client function toggleDropdown(event, item) {
|
|
output[event.currentTarget.open ? "open" : "close"]({
|
|
source: "menu",
|
|
item: item
|
|
})
|
|
}
|
|
}
|
|
|
|
view {
|
|
<header class="wrn-navbar wrn-navbar--width-{maxWidth} wrn-component--color-{color} wrn-component--size-{size} {sticky ? 'wrn-navbar--sticky' : ''} {class}" data-wrn-navbar data-open-on-hover="{openOnHover}">
|
|
<div class="wrn-navbar__topbar" aria-label="{topbarLabel}">
|
|
<slot name="topbar" />
|
|
</div>
|
|
|
|
<div class="wrn-navbar__main">
|
|
<a class="wrn-navbar__brand" href="{brand.href || '/'}" aria-label="{brand.ariaLabel || brand.label || label}">
|
|
{#if brand.logo}
|
|
<img class="wrn-navbar__brand-logo" src="{brand.logo}" alt="{brand.logoAlt || brand.label || ''}" width="{brand.logoWidth || ''}" height="{brand.logoHeight || ''}" />
|
|
{/if}
|
|
{#if brand.icon}
|
|
<span class="wrn-navbar__brand-icon {brand.icon}" aria-hidden="true"></span>
|
|
{/if}
|
|
{#if brand.label || brand.description}
|
|
<span class="wrn-navbar__brand-copy">
|
|
{#if brand.label}<strong>{brand.label}</strong>{/if}
|
|
{#if brand.description}<small>{brand.description}</small>{/if}
|
|
</span>
|
|
{/if}
|
|
</a>
|
|
|
|
<button type="button" class="wrn-navbar__toggle" aria-label="{mobileLabel}" aria-expanded="{mobileOpen}" @click="toggleNavigation()">
|
|
<span aria-hidden="true"></span><span aria-hidden="true"></span><span aria-hidden="true"></span>
|
|
</button>
|
|
|
|
<div class="wrn-navbar__collapse {mobileOpen ? 'is-open' : ''}">
|
|
<nav class="wrn-navbar__menus" aria-label="{label}" data-wrn-roving="horizontal">
|
|
{#each items as item}
|
|
{#if item.children && item.children.length}
|
|
<details class="wrn-navbar__dropdown wrn-navbar__dropdown--{item.type || 'dropdown'}" name="wrn-navbar-menu" @toggle="toggleDropdown(event, item)">
|
|
<summary data-wrn-roving-item="true" aria-current="{isItemActive(item) ? 'page' : 'false'}">
|
|
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
|
|
<span>{item.label}</span>
|
|
<span class="wrn-navbar__chevron" aria-hidden="true"></span>
|
|
</summary>
|
|
<div class="wrn-navbar__panel wrn-navbar__panel--columns-{item.columns || 1}">
|
|
{#if item.description}<p class="wrn-navbar__panel-intro">{item.description}</p>{/if}
|
|
{#each item.children as child}
|
|
<div class="wrn-navbar__group">
|
|
{#if child.children && child.children.length}
|
|
{#if child.label}<strong class="wrn-navbar__group-title">{child.label}</strong>{/if}
|
|
{#if child.description}<small>{child.description}</small>{/if}
|
|
{#each child.children as nested}
|
|
<a href="{nested.href || '#'}" target="{nested.target || ''}" rel="{nested.rel || ''}" aria-current="{nested.value === active ? 'page' : 'false'}" @click="selectItem(nested, 3)">
|
|
{#if nested.icon}<span class="{nested.icon}" aria-hidden="true"></span>{/if}
|
|
<span><strong>{nested.label}</strong>{#if nested.description}<small>{nested.description}</small>{/if}</span>
|
|
</a>
|
|
{/each}
|
|
{:else}
|
|
<a href="{child.href || '#'}" target="{child.target || ''}" rel="{child.rel || ''}" aria-current="{child.value === active ? 'page' : 'false'}" @click="selectItem(child, 2)">
|
|
{#if child.icon}<span class="{child.icon}" aria-hidden="true"></span>{/if}
|
|
<span><strong>{child.label}</strong>{#if child.description}<small>{child.description}</small>{/if}</span>
|
|
</a>
|
|
{/if}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</details>
|
|
{:else}
|
|
<a class="wrn-navbar__menu-link" data-wrn-roving-item="true" href="{item.href || '#'}" target="{item.target || ''}" rel="{item.rel || ''}" aria-current="{item.value === active ? 'page' : 'false'}" @click="selectItem(item, 1)">
|
|
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
|
|
<span>{item.label}</span>
|
|
</a>
|
|
{/if}
|
|
{/each}
|
|
</nav>
|
|
|
|
<div class="wrn-navbar__actions">
|
|
{#each actions as item}
|
|
<a class="wrn-navbar__action wrn-navbar__action--{item.variant || 'link'}" href="{item.href || '#'}" target="{item.target || ''}" rel="{item.rel || ''}" @click="selectAction(item)">
|
|
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
|
|
<span>{item.label}</span>
|
|
</a>
|
|
{/each}
|
|
<slot name="actions" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
}
|
|
|
|
style {
|
|
/* Full application navigation */
|
|
.wrn-navbar {
|
|
position: relative;
|
|
z-index: 40;
|
|
width: 100%;
|
|
color: var(--wrn-color-text);
|
|
background: color-mix(in srgb, var(--wrn-color-surface) 96%, transparent);
|
|
border-bottom: 1px solid var(--wrn-color-border);
|
|
}
|
|
|
|
.wrn-navbar--sticky {
|
|
position: sticky;
|
|
top: 0;
|
|
backdrop-filter: blur(16px);
|
|
}
|
|
|
|
.wrn-navbar__topbar:empty,
|
|
.wrn-navbar__topbar:not(:has(*)) {
|
|
display: none;
|
|
}
|
|
|
|
.wrn-navbar__topbar:not(:empty) {
|
|
display: flex;
|
|
min-height: 2.25rem;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 1rem;
|
|
padding: 0.35rem clamp(1rem, 4vw, 3rem);
|
|
color: var(--wrn-color-muted);
|
|
background: var(--wrn-color-surface-2);
|
|
border-bottom: 1px solid var(--wrn-color-border);
|
|
font-size: 0.75rem;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.wrn-navbar__main {
|
|
display: flex;
|
|
min-height: 4.25rem;
|
|
width: min(100%, 90rem);
|
|
margin-inline: auto;
|
|
padding: 0.75rem clamp(1rem, 4vw, 3rem);
|
|
align-items: center;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.wrn-navbar--width-full .wrn-navbar__main {
|
|
width: 100%;
|
|
max-width: none;
|
|
}
|
|
|
|
.wrn-navbar--width-compact .wrn-navbar__main {
|
|
width: min(86%, 80rem);
|
|
}
|
|
|
|
.wrn-navbar__brand,
|
|
.wrn-navbar__menu-link,
|
|
.wrn-navbar__dropdown summary,
|
|
.wrn-navbar__action,
|
|
.wrn-navbar__panel a {
|
|
color: inherit;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.wrn-navbar__brand {
|
|
display: inline-flex;
|
|
min-width: 0;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.wrn-navbar__brand-logo {
|
|
display: block;
|
|
width: auto;
|
|
max-width: 11rem;
|
|
height: 2.5rem;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.wrn-navbar__brand-icon {
|
|
width: 2.5rem;
|
|
height: 2.5rem;
|
|
color: var(--wrn-component-color, var(--wrn-color-primary));
|
|
}
|
|
|
|
.wrn-navbar__brand-copy {
|
|
display: grid;
|
|
min-width: 0;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.wrn-navbar__brand-copy strong {
|
|
font-size: 0.9rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.wrn-navbar__brand-copy small {
|
|
margin-top: 0.2rem;
|
|
color: var(--wrn-color-muted);
|
|
font-size: 0.6875rem;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.wrn-navbar__collapse,
|
|
.wrn-navbar__menus,
|
|
.wrn-navbar__actions {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.wrn-navbar__collapse {
|
|
min-width: 0;
|
|
flex: 1;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.wrn-navbar__menus {
|
|
justify-content: center;
|
|
flex: 1;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.wrn-navbar__actions {
|
|
justify-content: flex-end;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.wrn-navbar__menu-link,
|
|
.wrn-navbar__dropdown > summary,
|
|
.wrn-navbar__action {
|
|
display: inline-flex;
|
|
min-height: 2.75rem;
|
|
padding: 0.65rem 0.8rem;
|
|
align-items: center;
|
|
gap: 0.45rem;
|
|
border-radius: 0.65rem;
|
|
cursor: pointer;
|
|
font-size: 0.8125rem;
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.wrn-navbar__menu-link:hover,
|
|
.wrn-navbar__dropdown > summary:hover {
|
|
color: var(--wrn-color-text);
|
|
background: var(--wrn-color-surface-2);
|
|
}
|
|
|
|
.wrn-navbar__menu-link[aria-current="page"],
|
|
.wrn-navbar__dropdown > summary[aria-current="page"] {
|
|
color: var(--wrn-component-color, var(--wrn-color-primary));
|
|
font-weight: 600;
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--wrn-component-color, var(--wrn-color-primary)) 12%,
|
|
transparent
|
|
);
|
|
box-shadow: inset 0 -2px 0 var(--wrn-component-color, var(--wrn-color-primary));
|
|
}
|
|
|
|
.wrn-navbar__menu-link[aria-current="page"]:hover,
|
|
.wrn-navbar__dropdown > summary[aria-current="page"]:hover {
|
|
color: var(--wrn-component-color, var(--wrn-color-primary));
|
|
background: color-mix(
|
|
in srgb,
|
|
var(--wrn-component-color, var(--wrn-color-primary)) 16%,
|
|
transparent
|
|
);
|
|
}
|
|
|
|
.wrn-navbar__dropdown {
|
|
position: relative;
|
|
}
|
|
|
|
.wrn-navbar__dropdown > summary {
|
|
list-style: none;
|
|
}
|
|
|
|
.wrn-navbar__dropdown > summary::-webkit-details-marker {
|
|
display: none;
|
|
}
|
|
|
|
.wrn-navbar__chevron {
|
|
width: 0.45rem;
|
|
height: 0.45rem;
|
|
border-right: 1.5px solid currentColor;
|
|
border-bottom: 1.5px solid currentColor;
|
|
transform: rotate(45deg) translateY(-0.15rem);
|
|
transition: transform var(--wrn-motion-fast, 150ms) ease;
|
|
}
|
|
|
|
.wrn-navbar__dropdown[open] .wrn-navbar__chevron {
|
|
transform: rotate(225deg) translate(-0.1rem, -0.1rem);
|
|
}
|
|
|
|
.wrn-navbar__panel {
|
|
position: absolute;
|
|
top: calc(100% + 0.55rem);
|
|
left: 50%;
|
|
display: grid;
|
|
width: max-content;
|
|
min-width: 15rem;
|
|
max-width: min(90vw, 64rem);
|
|
padding: 0.65rem;
|
|
gap: 0.45rem;
|
|
border: 1px solid var(--wrn-color-border);
|
|
border-radius: 0.9rem;
|
|
color: var(--wrn-color-text);
|
|
background: var(--wrn-color-surface);
|
|
box-shadow: 0 18px 48px rgb(15 23 42 / 0.16);
|
|
opacity: 1;
|
|
transform: translateX(-50%) translateY(0) scale(1);
|
|
transform-origin: top center;
|
|
transition:
|
|
opacity 180ms ease,
|
|
transform 180ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
display 180ms allow-discrete;
|
|
}
|
|
|
|
.wrn-navbar__panel::before {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: 100%;
|
|
left: 0;
|
|
height: 0.65rem;
|
|
content: "";
|
|
}
|
|
|
|
.wrn-navbar__dropdown:not([open]) > .wrn-navbar__panel {
|
|
display: none;
|
|
opacity: 0;
|
|
transform: translateX(-50%) translateY(-0.5rem) scale(0.98);
|
|
}
|
|
|
|
@starting-style {
|
|
.wrn-navbar__dropdown[open] > .wrn-navbar__panel {
|
|
opacity: 0;
|
|
transform: translateX(-50%) translateY(-0.5rem) scale(0.98);
|
|
}
|
|
}
|
|
|
|
.wrn-navbar__dropdown--mega {
|
|
position: static;
|
|
}
|
|
|
|
.wrn-navbar__dropdown--mega .wrn-navbar__panel {
|
|
right: auto;
|
|
left: 50%;
|
|
width: min(calc(100vw - 2rem), 64rem);
|
|
}
|
|
|
|
.wrn-navbar__panel--columns-2 {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
|
|
.wrn-navbar__panel--columns-3 {
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
}
|
|
|
|
.wrn-navbar__panel--columns-4 {
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
}
|
|
|
|
.wrn-navbar__panel-intro {
|
|
grid-column: 1 / -1;
|
|
margin: 0;
|
|
padding: 0.5rem 0.65rem;
|
|
color: var(--wrn-color-muted);
|
|
font-size: 0.8125rem;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.wrn-navbar__group {
|
|
display: grid;
|
|
align-content: start;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.wrn-navbar__group-title {
|
|
padding: 0.55rem 0.65rem 0.25rem;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.wrn-navbar__group > small {
|
|
padding: 0 0.65rem 0.4rem;
|
|
color: var(--wrn-color-muted);
|
|
font-size: 0.7rem;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.wrn-navbar__panel a {
|
|
display: flex;
|
|
min-height: 2.75rem;
|
|
padding: 0.65rem;
|
|
align-items: flex-start;
|
|
gap: 0.65rem;
|
|
border-radius: 0.6rem;
|
|
font-size: 0.8rem;
|
|
font-weight: 450;
|
|
}
|
|
|
|
.wrn-navbar__panel a:hover {
|
|
background: var(--wrn-color-surface-2);
|
|
}
|
|
|
|
.wrn-navbar__panel a span:last-child {
|
|
display: grid;
|
|
gap: 0.15rem;
|
|
}
|
|
|
|
.wrn-navbar__panel a strong {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.wrn-navbar__panel a small {
|
|
color: var(--wrn-color-muted);
|
|
font-size: 0.7rem;
|
|
font-weight: 400;
|
|
}
|
|
|
|
.wrn-navbar__action {
|
|
border: 1px solid transparent;
|
|
}
|
|
|
|
.wrn-navbar__action--primary {
|
|
color: var(--wrn-color-on-primary, #fff);
|
|
background: var(--wrn-component-color, var(--wrn-color-primary));
|
|
font-weight: 600;
|
|
}
|
|
|
|
.wrn-navbar__action--outline {
|
|
border-color: var(--wrn-color-border);
|
|
}
|
|
|
|
.wrn-navbar__toggle {
|
|
display: none;
|
|
width: 2.75rem;
|
|
height: 2.75rem;
|
|
margin-left: auto;
|
|
padding: 0.65rem;
|
|
border: 1px solid var(--wrn-color-border);
|
|
border-radius: 0.65rem;
|
|
color: inherit;
|
|
background: var(--wrn-color-surface);
|
|
}
|
|
|
|
.wrn-navbar__toggle span {
|
|
display: block;
|
|
height: 2px;
|
|
margin: 0.25rem 0;
|
|
background: currentColor;
|
|
}
|
|
|
|
@media (max-width: 900px) {
|
|
.wrn-navbar--width-compact .wrn-navbar__main {
|
|
width: 100%;
|
|
}
|
|
.wrn-navbar__toggle {
|
|
display: block;
|
|
}
|
|
.wrn-navbar__collapse {
|
|
position: absolute;
|
|
top: 100%;
|
|
right: 0;
|
|
left: 0;
|
|
display: none;
|
|
padding: 0.75rem 1rem 1rem;
|
|
align-items: stretch;
|
|
flex-direction: column;
|
|
background: var(--wrn-color-surface);
|
|
border-bottom: 1px solid var(--wrn-color-border);
|
|
box-shadow: 0 16px 32px rgb(15 23 42 / 0.12);
|
|
max-height: calc(100dvh - 4rem);
|
|
overflow-x: hidden;
|
|
overflow-y: auto;
|
|
overscroll-behavior: contain;
|
|
}
|
|
.wrn-navbar__collapse.is-open {
|
|
display: flex;
|
|
}
|
|
.wrn-navbar__menus,
|
|
.wrn-navbar__actions {
|
|
width: 100%;
|
|
align-items: stretch;
|
|
flex-direction: column;
|
|
}
|
|
.wrn-navbar__menu-link,
|
|
.wrn-navbar__dropdown > summary,
|
|
.wrn-navbar__action {
|
|
width: 100%;
|
|
}
|
|
.wrn-navbar__panel,
|
|
.wrn-navbar__dropdown--mega .wrn-navbar__panel {
|
|
position: static;
|
|
width: 100%;
|
|
max-width: none;
|
|
margin-top: 0.25rem;
|
|
box-shadow: none;
|
|
transform: none;
|
|
overflow: hidden;
|
|
animation: wrn-navbar-mobile-panel 180ms ease both;
|
|
}
|
|
.wrn-navbar__dropdown:not([open]) > .wrn-navbar__panel,
|
|
.wrn-navbar__dropdown[open] > .wrn-navbar__panel {
|
|
transform: none;
|
|
}
|
|
.wrn-navbar__panel[class*="wrn-navbar__panel--columns-"] {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
.wrn-navbar__panel::before {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.wrn-navbar__brand-copy small {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
@keyframes wrn-navbar-mobile-panel {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-0.35rem);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* Shared component foundation. */
|
|
|
|
.wrn-component {
|
|
--wrn-component-color: var(--wrn-color-primary);
|
|
--wrn-component-scale: 1;
|
|
margin: 0;
|
|
color: var(--wrn-color-text);
|
|
font-family: var(--wrn-font-sans, inherit);
|
|
}
|
|
|
|
.wrn-component--color-primary {
|
|
--wrn-component-color: var(--wrn-color-primary);
|
|
}
|
|
|
|
.wrn-component--color-secondary {
|
|
--wrn-component-color: var(--wrn-color-secondary);
|
|
}
|
|
|
|
.wrn-component--color-success {
|
|
--wrn-component-color: var(--wrn-color-success);
|
|
}
|
|
|
|
.wrn-component--color-warning {
|
|
--wrn-component-color: var(--wrn-color-warning);
|
|
}
|
|
|
|
.wrn-component--color-danger {
|
|
--wrn-component-color: var(--wrn-color-danger);
|
|
}
|
|
|
|
.wrn-component--color-info {
|
|
--wrn-component-color: var(--wrn-color-info);
|
|
}
|
|
|
|
.wrn-component--size-xs {
|
|
--wrn-component-scale: 0.78;
|
|
}
|
|
|
|
.wrn-component--size-sm {
|
|
--wrn-component-scale: 0.88;
|
|
}
|
|
|
|
.wrn-component--size-default,
|
|
.wrn-component--size-md {
|
|
--wrn-component-scale: 1;
|
|
}
|
|
|
|
.wrn-component--size-lg {
|
|
--wrn-component-scale: 1.14;
|
|
}
|
|
|
|
.wrn-component--size-xl {
|
|
--wrn-component-scale: 1.28;
|
|
}
|
|
|
|
.wrn-component:not(.wrn-btn) {
|
|
font-size: calc(1em * var(--wrn-component-scale));
|
|
}
|
|
|
|
.wrn-component :is(a, button, input, select, textarea):focus-visible {
|
|
outline-color: var(--wrn-component-color);
|
|
}
|
|
|
|
.wrn-component p {
|
|
margin: 0;
|
|
color: var(--wrn-color-muted);
|
|
line-height: 1.6;
|
|
}
|
|
}
|
|
}
|