feat(ui): build MegaMenu and rebuild Sidebar on Drawer
MegaMenu replaces a scaffold with a trigger and a wide panel of grouped link columns. One level deep on purpose: a mega menu exists to show breadth flat so everything is one click away, and nesting inside the panel buries content behind hover-within-hover. Nav is the component for cascading submenus. The panel is anchored so the runtime clamp keeps it inside the viewport. Sidebar now composes Drawer for its off-canvas presentation instead of a hand-rolled backdrop, inheriting the focus trap and scroll lock from one place. Single items, labelled groups and branches nested to three levels, with vertical roving focus. Sidebar classes move to the BEM naming the rest of the library uses, which is a breaking change; nesting via children still works, since that is what shipped in 0.8.5. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,23 +1,329 @@
|
||||
// MegaMenu -- a trigger and a wide panel of grouped links.
|
||||
//
|
||||
// <MegaMenu label="Products" columns='[{"heading":"Platform",
|
||||
// "items":[{"label":"Runtime","href":"/runtime"}]}]' />
|
||||
//
|
||||
// Deliberately one level deep. A mega menu exists to show breadth flat, so
|
||||
// everything is one click away; nesting inside the panel buries content behind
|
||||
// hover-within-hover and is close to unusable by keyboard and touch. Use Nav
|
||||
// when you actually want cascading submenus.
|
||||
//
|
||||
// The panel carries data-wrn-anchored so the runtime clamp keeps it inside the
|
||||
// viewport instead of hanging off the edge of a wide layout.
|
||||
//
|
||||
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
|
||||
// and silently swallows the rule that follows it.
|
||||
component MegaMenu {
|
||||
outputs {
|
||||
open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
close(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
select(payload: { value?: string | number | boolean | null; values?: Array<string | number | boolean | null | object>; sourceEvent?: Event; [key: string]: string | number | boolean | null | object } | string | number | boolean | null)
|
||||
open(payload: { sourceEvent: Event })
|
||||
close(payload: { reason: string })
|
||||
select(payload: { item: object; value: string })
|
||||
}
|
||||
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
label: string = "Mega Menu"
|
||||
items: unknown[] = []
|
||||
active: string = ""
|
||||
orientation: string = "horizontal"
|
||||
size: string = "default"
|
||||
label: string = "Menu"
|
||||
icon: string = ""
|
||||
columns: unknown[] = []
|
||||
footer: string = ""
|
||||
defaultOpen: boolean = false
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
state visible = defaultOpen
|
||||
|
||||
functions {
|
||||
shared function columnList() {
|
||||
return Array.isArray(columns) ? columns : []
|
||||
}
|
||||
|
||||
shared function itemsOf(column) {
|
||||
return column && Array.isArray(column.items) ? column.items : []
|
||||
}
|
||||
|
||||
client function showPanel(sourceEvent) {
|
||||
visible = true
|
||||
output.open({ sourceEvent: sourceEvent })
|
||||
}
|
||||
|
||||
client function hidePanel(reason) {
|
||||
visible = false
|
||||
output.close({ reason: reason })
|
||||
}
|
||||
|
||||
client function togglePanel(sourceEvent) {
|
||||
if (visible) {
|
||||
hidePanel("toggle")
|
||||
} else {
|
||||
showPanel(sourceEvent)
|
||||
}
|
||||
}
|
||||
|
||||
client function handleKeydown(sourceEvent) {
|
||||
if (sourceEvent.key === "Escape" && visible) {
|
||||
sourceEvent.preventDefault()
|
||||
hidePanel("escape")
|
||||
}
|
||||
}
|
||||
|
||||
client function choose(item) {
|
||||
if (item.disabled) {
|
||||
return
|
||||
}
|
||||
output.select({ item: item, value: item.value || item.label || "" })
|
||||
hidePanel("select")
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<nav class="wire-next wire-next--color-{color} wire-next--size-{size} wire-next--mega-menu wire-next--{orientation} {class}" aria-label="{label}">
|
||||
{#each items as item}<a href="{item.href}" aria-current="{item.value === active ? 'page' : ''}">{item.label}</a>{/each}
|
||||
<slot />
|
||||
</nav>
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="MegaMenu"
|
||||
class='wire-mega {class}'
|
||||
data-color='{color}'
|
||||
data-size='{size}'
|
||||
data-open='{visible}'
|
||||
@keydown='handleKeydown(event)'
|
||||
@mouseleave='hidePanel("pointer-leave")'
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="wire-mega__trigger"
|
||||
aria-haspopup="true"
|
||||
aria-expanded='{visible}'
|
||||
@click='togglePanel(event)'
|
||||
@mouseenter='showPanel(event)'
|
||||
@focus='showPanel(event)'
|
||||
>
|
||||
<span class='wire-mega__trigger-icon {icon}' data-show="icon" aria-hidden="true"></span>
|
||||
<span class="wire-mega__trigger-label">{label}</span>
|
||||
<span class="wire-mega__arrow" aria-hidden="true">›</span>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="wire-mega__panel"
|
||||
data-wrn-anchored="true"
|
||||
data-show="visible"
|
||||
data-wrn-roving="both"
|
||||
>
|
||||
<div class="wire-mega__columns">
|
||||
{#each columnList() as column}
|
||||
<section class="wire-mega__column">
|
||||
<h3 class="wire-mega__heading" data-show="column.heading">{column.heading}</h3>
|
||||
<ul class="wire-mega__list">
|
||||
{#each itemsOf(column) as item}
|
||||
<li>
|
||||
<a
|
||||
class="wire-mega__link"
|
||||
href='{item.href || "#"}'
|
||||
data-wrn-roving-item="true"
|
||||
aria-disabled='{item.disabled ? "true" : "false"}'
|
||||
@click='choose(item)'
|
||||
>
|
||||
<span
|
||||
class='wire-mega__link-icon {item.icon}'
|
||||
data-show="item.icon"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span class="wire-mega__link-body">
|
||||
<span class="wire-mega__link-label">{item.label}</span>
|
||||
<span class="wire-mega__link-description" data-show="item.description">
|
||||
{item.description}
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<p class="wire-mega__footer" data-show="footer">{footer}</p>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-mega {
|
||||
--mega-accent: var(--wire-color-primary);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.wire-mega[data-color="secondary"] {
|
||||
--mega-accent: var(--wire-color-secondary);
|
||||
}
|
||||
|
||||
.wire-mega[data-color="success"] {
|
||||
--mega-accent: var(--wire-color-success);
|
||||
}
|
||||
|
||||
.wire-mega[data-color="danger"] {
|
||||
--mega-accent: var(--wire-color-danger);
|
||||
}
|
||||
|
||||
.wire-mega[data-color="info"] {
|
||||
--mega-accent: var(--wire-color-info);
|
||||
}
|
||||
|
||||
.wire-mega[data-size="sm"] {
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.wire-mega[data-size="lg"] {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.wire-mega__trigger {
|
||||
appearance: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.45rem 0.7rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--wire-radius-sm);
|
||||
background: transparent;
|
||||
color: var(--wire-color-text-muted);
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wire-mega__trigger:hover,
|
||||
.wire-mega[data-open="true"] .wire-mega__trigger {
|
||||
background: var(--wire-color-surface-soft);
|
||||
color: var(--wire-color-text);
|
||||
}
|
||||
|
||||
.wire-mega__trigger:focus-visible {
|
||||
outline: 2px solid var(--mega-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.wire-mega__arrow {
|
||||
display: inline-block;
|
||||
transition: transform 160ms ease;
|
||||
}
|
||||
|
||||
.wire-mega[data-open="true"] .wire-mega__arrow {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.wire-mega__panel {
|
||||
position: absolute;
|
||||
z-index: 40;
|
||||
top: calc(100% + 0.4rem);
|
||||
left: 0;
|
||||
width: max-content;
|
||||
max-width: min(60rem, calc(100vw - 2rem));
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: var(--wire-radius-lg);
|
||||
background: var(--wire-color-surface);
|
||||
box-shadow: var(--wire-shadow-1);
|
||||
}
|
||||
|
||||
.wire-mega__columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.wire-mega__column {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wire-mega__heading {
|
||||
margin: 0 0 0.5rem;
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wire-mega__list {
|
||||
display: grid;
|
||||
gap: 0.15rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.wire-mega__link {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
padding: 0.45rem 0.5rem;
|
||||
border-radius: var(--wire-radius-sm);
|
||||
color: var(--wire-color-text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wire-mega__link:hover {
|
||||
background: var(--wire-color-surface-soft);
|
||||
}
|
||||
|
||||
.wire-mega__link:focus-visible {
|
||||
outline: 2px solid var(--mega-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.wire-mega__link[aria-disabled="true"] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wire-mega__link-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wire-mega__link-label {
|
||||
font-size: 0.88rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.wire-mega__link-description {
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.wire-mega__footer {
|
||||
margin: 1rem 0 0;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid var(--wire-color-border);
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* On a phone the panel stops floating and becomes part of the flow. A
|
||||
* pointer-opened overlay pinned to a trigger is unreachable on touch, and
|
||||
* a 60rem grid has nowhere to go on a 360px screen.
|
||||
*/
|
||||
@media (max-width: 767px) {
|
||||
.wire-mega {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.wire-mega__panel {
|
||||
position: static;
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.wire-mega__columns {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,335 @@
|
||||
// Sidebar -- a vertical navigation rail that becomes a drawer on small screens.
|
||||
//
|
||||
// <Sidebar items='[{"label":"Dashboard","href":"/","value":"dash"}]' active="dash" />
|
||||
//
|
||||
// An entry is one of three shapes:
|
||||
// { label, href, value } a single link
|
||||
// { heading, items: [...] } a labelled group
|
||||
// { label, items: [...] } a collapsible branch, nested to 3 levels
|
||||
//
|
||||
// The off-canvas presentation is Drawer rather than a second implementation,
|
||||
// so the focus trap and the body scroll lock come from one place.
|
||||
//
|
||||
// NOTE: the style block uses /* */ comments only -- // is not a CSS comment
|
||||
// and silently swallows the rule that follows it.
|
||||
import Drawer from "./Drawer.wrn"
|
||||
|
||||
component Sidebar {
|
||||
outputs {
|
||||
toggle(payload: { open: boolean })
|
||||
open(payload: { sourceEvent?: Event; [key: string]: string | number | boolean | null | object })
|
||||
open(payload: { sourceEvent: Event })
|
||||
close(payload: { source: string })
|
||||
select(payload: { item: string | number | boolean | null | object; value: string | number | boolean; level: string })
|
||||
select(payload: { item: object; value: string; level: number })
|
||||
}
|
||||
|
||||
props {
|
||||
size: string = "default"
|
||||
color: string = "primary"
|
||||
size: string = "default"
|
||||
label: string = "Sidebar"
|
||||
items: unknown[] = []
|
||||
active: string = ""
|
||||
orientation: string = "horizontal"
|
||||
mobileLabel: string = "Open navigation"
|
||||
drawerTitle: string = "Navigation"
|
||||
class: string = ""
|
||||
}
|
||||
|
||||
state mobileOpen: boolean = false
|
||||
state drawerOpen = false
|
||||
|
||||
functions {
|
||||
client function toggleSidebar() {
|
||||
mobileOpen = !mobileOpen
|
||||
output.toggle({ open: mobileOpen })
|
||||
output[mobileOpen ? "open" : "close"]({ source: "mobile" })
|
||||
shared function entryList() {
|
||||
return Array.isArray(items) ? items : []
|
||||
}
|
||||
|
||||
client function closeSidebar() {
|
||||
mobileOpen = false
|
||||
output.close({ source: "mobile" })
|
||||
// Accepts children as well as items: children is what shipped in 0.8.5
|
||||
// and existing callers should keep working.
|
||||
shared function childrenOf(entry) {
|
||||
if (!entry) {
|
||||
return []
|
||||
}
|
||||
if (Array.isArray(entry.items)) {
|
||||
return entry.items
|
||||
}
|
||||
return Array.isArray(entry.children) ? entry.children : []
|
||||
}
|
||||
|
||||
client function selectItem(item, level) {
|
||||
mobileOpen = false
|
||||
output.select({ item: item, value: item.value || "", level: level })
|
||||
shared function isGroup(entry) {
|
||||
return Boolean(entry.heading)
|
||||
}
|
||||
|
||||
shared function isActive(entry) {
|
||||
return Boolean(entry.value) && entry.value === active
|
||||
}
|
||||
|
||||
client function choose(entry, level) {
|
||||
if (entry.disabled) {
|
||||
return
|
||||
}
|
||||
output.select({ item: entry, value: entry.value || "", level: level })
|
||||
if (drawerOpen) {
|
||||
drawerOpen = false
|
||||
output.close({ source: "select" })
|
||||
}
|
||||
}
|
||||
|
||||
client function openDrawer(sourceEvent) {
|
||||
drawerOpen = true
|
||||
output.open({ sourceEvent: sourceEvent })
|
||||
output.toggle({ open: true })
|
||||
}
|
||||
|
||||
client function closeDrawer() {
|
||||
drawerOpen = false
|
||||
output.close({ source: "drawer" })
|
||||
output.toggle({ open: false })
|
||||
}
|
||||
}
|
||||
|
||||
view {
|
||||
<div class="wire-sidebar-shell wire-next--color-{color} wire-next--size-{size} {mobileOpen ? 'is-open' : ''} {class}">
|
||||
<button type="button" class="wire-sidebar-toggle" aria-label="{mobileLabel}" aria-expanded="{mobileOpen}" @click="toggleSidebar()">
|
||||
<span class="icon-[lucide--panel-left-open]" aria-hidden="true"></span>
|
||||
<span>{label}</span>
|
||||
<div
|
||||
{...attrs}
|
||||
data-ui-component="Sidebar"
|
||||
class='wire-sidebar {class}'
|
||||
data-color='{color}'
|
||||
data-size='{size}'
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="wire-sidebar__launcher"
|
||||
aria-label='{mobileLabel}'
|
||||
aria-expanded='{drawerOpen}'
|
||||
@click='openDrawer(event)'
|
||||
>
|
||||
<span class="wire-sidebar__launcher-bar" aria-hidden="true"></span>
|
||||
<span class="wire-sidebar__launcher-text">{mobileLabel}</span>
|
||||
</button>
|
||||
<button type="button" class="wire-sidebar-backdrop" aria-label="Close navigation" @click="closeSidebar()"></button>
|
||||
<nav class="wire-next wire-next--sidebar wire-next--{orientation} wire-sidebar-panel" aria-label="{label}">
|
||||
<div class="wire-sidebar-panel__head">
|
||||
<strong>{label}</strong>
|
||||
<button type="button" aria-label="Close navigation" @click="closeSidebar()"><span class="icon-[lucide--x]" aria-hidden="true"></span></button>
|
||||
</div>
|
||||
<div class="wire-sidebar-items">
|
||||
{#each items as item}
|
||||
{#if item.children && item.children.length}
|
||||
<details class="wire-sidebar-group" name="wire-sidebar-group">
|
||||
<summary>
|
||||
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
|
||||
<span>{item.label}</span>
|
||||
<span class="icon-[lucide--chevron-down]" aria-hidden="true"></span>
|
||||
</summary>
|
||||
<div class="wire-sidebar-group__children">
|
||||
{#each item.children as child}
|
||||
<a href="{child.href || '#'}" aria-current="{child.value === active ? 'page' : ''}" @click="selectItem(child, 2)">
|
||||
{#if child.icon}<span class="{child.icon}" aria-hidden="true"></span>{/if}
|
||||
<span>{child.label}</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</details>
|
||||
{:else}
|
||||
<a href="{item.href || '#'}" aria-current="{item.value === active ? 'page' : ''}" @click="selectItem(item, 1)">
|
||||
{#if item.icon}<span class="{item.icon}" aria-hidden="true"></span>{/if}
|
||||
<span>{item.label}</span>
|
||||
|
||||
<nav class="wire-sidebar__rail" aria-label='{label}'>
|
||||
<ul class="wire-sidebar__list" data-wrn-roving="vertical">
|
||||
{#each entryList() as entry}
|
||||
<li class="wire-sidebar__entry" data-group='{isGroup(entry)}'>
|
||||
<p class="wire-sidebar__heading" data-show="entry.heading">{entry.heading}</p>
|
||||
|
||||
<a
|
||||
class="wire-sidebar__link"
|
||||
data-show="!entry.heading"
|
||||
href='{entry.href || "#"}'
|
||||
data-wrn-roving-item="true"
|
||||
data-active='{isActive(entry)}'
|
||||
aria-current='{isActive(entry) ? "page" : "false"}'
|
||||
aria-disabled='{entry.disabled ? "true" : "false"}'
|
||||
@click='choose(entry, 1)'
|
||||
>
|
||||
<span
|
||||
class='wire-sidebar__icon {entry.icon}'
|
||||
data-show="entry.icon"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span class="wire-sidebar__label">{entry.label}</span>
|
||||
<span class="wire-sidebar__badge" data-show="entry.badge">{entry.badge}</span>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<ul class="wire-sidebar__sublist" data-show="childrenOf(entry).length > 0">
|
||||
{#each childrenOf(entry) as child}
|
||||
<li class="wire-sidebar__entry">
|
||||
<a
|
||||
class="wire-sidebar__link"
|
||||
href='{child.href || "#"}'
|
||||
data-active='{isActive(child)}'
|
||||
aria-current='{isActive(child) ? "page" : "false"}'
|
||||
aria-disabled='{child.disabled ? "true" : "false"}'
|
||||
@click='choose(child, 2)'
|
||||
>
|
||||
<span
|
||||
class='wire-sidebar__icon {child.icon}'
|
||||
data-show="child.icon"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span class="wire-sidebar__label">{child.label}</span>
|
||||
<span class="wire-sidebar__badge" data-show="child.badge">{child.badge}</span>
|
||||
</a>
|
||||
|
||||
<ul
|
||||
class="wire-sidebar__sublist wire-sidebar__sublist--level3"
|
||||
data-show="childrenOf(child).length > 0"
|
||||
>
|
||||
{#each childrenOf(child) as leaf}
|
||||
<li class="wire-sidebar__entry">
|
||||
<a
|
||||
class="wire-sidebar__link"
|
||||
href='{leaf.href || "#"}'
|
||||
data-active='{isActive(leaf)}'
|
||||
aria-current='{isActive(leaf) ? "page" : "false"}'
|
||||
@click='choose(leaf, 3)'
|
||||
>
|
||||
<span class="wire-sidebar__label">{leaf.label}</span>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</li>
|
||||
{/each}
|
||||
</div>
|
||||
</ul>
|
||||
<slot />
|
||||
</nav>
|
||||
|
||||
<Drawer
|
||||
open={drawerOpen}
|
||||
placement="left"
|
||||
title={drawerTitle}
|
||||
label={label}
|
||||
@close="closeDrawer()"
|
||||
>
|
||||
<slot name="drawer"></slot>
|
||||
</Drawer>
|
||||
</div>
|
||||
}
|
||||
|
||||
style {
|
||||
.wire-sidebar {
|
||||
--sidebar-accent: var(--wire-color-primary);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.wire-sidebar[data-color="secondary"] {
|
||||
--sidebar-accent: var(--wire-color-secondary);
|
||||
}
|
||||
|
||||
.wire-sidebar[data-color="success"] {
|
||||
--sidebar-accent: var(--wire-color-success);
|
||||
}
|
||||
|
||||
.wire-sidebar[data-color="danger"] {
|
||||
--sidebar-accent: var(--wire-color-danger);
|
||||
}
|
||||
|
||||
.wire-sidebar[data-color="info"] {
|
||||
--sidebar-accent: var(--wire-color-info);
|
||||
}
|
||||
|
||||
.wire-sidebar[data-size="sm"] {
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.wire-sidebar[data-size="lg"] {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.wire-sidebar__rail {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wire-sidebar__list,
|
||||
.wire-sidebar__sublist {
|
||||
display: grid;
|
||||
gap: 0.1rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.wire-sidebar__sublist {
|
||||
margin-left: 0.85rem;
|
||||
padding-left: 0.5rem;
|
||||
border-left: 1px solid var(--wire-color-border);
|
||||
}
|
||||
|
||||
.wire-sidebar__entry[data-group="true"] + .wire-sidebar__entry {
|
||||
margin-top: 0.35rem;
|
||||
}
|
||||
|
||||
.wire-sidebar__heading {
|
||||
margin: 0.9rem 0 0.35rem;
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.wire-sidebar__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.45rem 0.6rem;
|
||||
border-radius: var(--wire-radius-sm);
|
||||
color: var(--wire-color-text-muted);
|
||||
font-size: 0.88rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wire-sidebar__link:hover {
|
||||
background: var(--wire-color-surface-soft);
|
||||
color: var(--wire-color-text);
|
||||
}
|
||||
|
||||
.wire-sidebar__link:focus-visible {
|
||||
outline: 2px solid var(--sidebar-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.wire-sidebar__link[data-active="true"] {
|
||||
background: color-mix(in srgb, var(--sidebar-accent) 16%, transparent);
|
||||
color: var(--sidebar-accent);
|
||||
}
|
||||
|
||||
.wire-sidebar__link[aria-disabled="true"] {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.wire-sidebar__label {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.wire-sidebar__badge {
|
||||
margin-left: auto;
|
||||
padding: 0.05rem 0.4rem;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--sidebar-accent) 16%, transparent);
|
||||
color: var(--sidebar-accent);
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.wire-sidebar__launcher {
|
||||
display: none;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.45rem 0.7rem;
|
||||
border: 1px solid var(--wire-color-border);
|
||||
border-radius: var(--wire-radius-sm);
|
||||
background: var(--wire-color-surface);
|
||||
color: var(--wire-color-text);
|
||||
font: inherit;
|
||||
font-size: 0.9rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.wire-sidebar__launcher-bar {
|
||||
width: 1rem;
|
||||
height: 2px;
|
||||
background: currentColor;
|
||||
box-shadow:
|
||||
0 -5px 0 currentColor,
|
||||
0 5px 0 currentColor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Below the tablet breakpoint the rail is replaced by the drawer launcher.
|
||||
* A permanent rail eats most of a phone screen, and Drawer already handles
|
||||
* the focus trap and the scroll lock.
|
||||
*/
|
||||
@media (max-width: 767px) {
|
||||
.wire-sidebar__launcher {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.wire-sidebar__rail {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user