Adding mobile support for sidebar

This commit is contained in:
Ronan Berder
2025-05-02 22:43:40 +08:00
parent 7c13727903
commit 9b5f054bb2
9 changed files with 631 additions and 554 deletions
-12
View File
@@ -9,18 +9,6 @@ export default async function(eleventyConfig) {
eleventyConfig.addPassthroughCopy({"docs/src/assets": "assets"});
eleventyConfig.addPassthroughCopy({ "src/js": "assets/js" });
eleventyConfig.addPlugin(eleventyLucideicons);
// eleventyConfig.addShortcode("fetchCode", async function(filePath) {
// return "HELLO";
// const absolutePath = path.resolve(process.cwd(), filePath);
// return absolutePath;
// try {
// console.log(absolutePath);
// return fs.readFileSync(absolutePath, 'utf8');
// } catch (error) {
// console.error(`[Eleventy FetchCode Error] Failed to read: ${absolutePath}`, error);
// return `<!-- Error fetching code for ${filePath} -->`;
// }
// });
eleventyConfig.addShortcode("fetchCode", function(filePath) {
const absolutePath = path.resolve(process.cwd(), filePath);
try {
+67 -36
View File
@@ -83,11 +83,33 @@
<body>
{% from "sidebar.njk" import sidebar %}
{% from "popover.njk" import popover %}
{% set footer %}
<a href="/logout">
<span class="icon" aria-hidden="true">🚪</span>
<span>Logout</span>
</a>
{% set trigger %}
<img src="https://github.com/hunvreus.png" class="rounded-lg shrink-0 size-8">
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">Ronan Berder</span>
<span class="truncate text-xs">@hunvreus</span>
</div>
{% lucide "chevrons-up-down" %}
{% endset %}
{% call popover(
trigger=trigger,
trigger_attrs={"class": "btn-ghost p-2 h-12 w-full flex items-center justify-start"},
content_attrs={"data-side": "right", "data-align": "end", "class": "w-80"}
) %}
<div class="grid gap-4 p-4">
<header class="grid gap-1.5">
<h2 class="font-semibold">I hope you like Basecoat...</h2>
<p class="text-muted-foreground text-sm">I made this (and <a class="underline underline-offset-4" href="https://pagescms.org" target="_target">other things</a>) because I thought it'd be helpful to others. If you find it useful, please consider sponsoring me on GitHub or following me on X.</p>
</header>
<footer class="grid gap-2">
<a href="https://github.com/sponsors/hunvreus" class="btn-sm" target="_blank">Sponsor me on GitHub</a>
<a href="https://x.com/hunvreus" class="btn-sm-outline" target="_blank">Follow me on X</a>
</footer>
</div>
{% endcall %}
{% endset %}
{% set header %}
@@ -97,7 +119,7 @@
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">Basecoat</span>
<span class="truncate text-xs">v0.1.0</span>
<span class="truncate text-xs">v{{ pkg.version }}</span>
</div>
</a>
<div class="flex flex-col gap-2 px-2 text-sm">
@@ -109,45 +131,54 @@
</div>
{% endset %}
{% set link_attrs = {
"hx-boost": "true",
"hx-select": "#content",
"hx-target": "#content",
"hx-swap": "outerHTML"
} %}
{% set icon_info %}{% lucide "info" %}{% endset %}
{% set icon_square_terminal %}{% lucide "square-terminal" %}{% endset %}
{% set icon_layout_dashboard %}{% lucide "layout-dashboard" %}{% endset %}
{% set icon_github %}{% lucide "github" %}{% endset %}
{% set menu = [
{ type: "group", label: "Getting started", items: [
{ type: "item", label: "Introduction", url: "/", icon: icon_info, current: true },
{ type: "item", label: "Installation", url: "/installation", icon: icon_square_terminal },
{ type: "item", label: "Kitchen sink", url: "/kitchen-sink", icon: icon_layout_dashboard }
{ type: "item", label: "Introduction", url: "/", icon: icon_info, current: true, attrs: link_attrs },
{ type: "item", label: "Installation", url: "/installation", icon: icon_square_terminal, attrs: link_attrs },
{ type: "item", label: "Kitchen sink", url: "/kitchen-sink", icon: icon_layout_dashboard, attrs: link_attrs },
{ type: "item", label: "GitHub", url: "https://github.com/hunvreus/basecoat", icon: icon_github, attrs: link_attrs }
]},
{ type: 'group', label: 'Components', items: [
{ type: "item", url: "/components/accordion", label: "Accordion" },
{ type: "item", url: "/components/alert", label: "Alert" },
{ type: "item", url: "/components/alert-dialog", label: "Alert Dialog" },
{ type: "item", url: "/components/avatar", label: "Avatar" },
{ type: "item", url: "/components/badge", label: "Badge" },
{ type: "item", url: "/components/breadcrumb", label: "Breadcrumb" },
{ type: "item", url: "/components/button", label: "Button" },
{ type: "item", url: "/components/card", label: "Card" },
{ type: "item", url: "/components/checkbox", label: "Checkbox" },
{ type: "item", url: "/components/combobox", label: "Combobox" },
{ type: "item", url: "/components/dialog", label: "Dialog" },
{ type: "item", url: "/components/dropdown-menu", label: "Dropdown Menu" },
{ type: "item", url: "/components/form", label: "Form" },
{ type: "item", url: "/components/input", label: "Input" },
{ type: "item", url: "/components/label", label: "Label" },
{ type: "item", url: "/components/pagination", label: "Pagination" },
{ type: "item", url: "/components/popover", label: "Popover" },
{ type: "item", url: "/components/radio-group", label: "Radio Group" },
{ type: "item", url: "/components/select", label: "Select" },
{ type: "item", url: "/components/skeleton", label: "Skeleton" },
{ type: "item", url: "/components/sidebar", label: "Sidebar" },
{ type: "item", url: "/components/slider", label: "Slider" },
{ type: "item", url: "/components/switch", label: "Switch" },
{ type: "item", url: "/components/table", label: "Table" },
{ type: "item", url: "/components/tabs", label: "Tabs" },
{ type: "item", url: "/components/textarea", label: "Textarea" },
{ type: "item", url: "/components/toast", label: "Toast" },
{ type: "item", url: "/components/tooltip", label: "Tooltip" }
{ type: "item", url: "/components/accordion", label: "Accordion", attrs: link_attrs },
{ type: "item", url: "/components/alert", label: "Alert", attrs: link_attrs },
{ type: "item", url: "/components/alert-dialog", label: "Alert Dialog", attrs: link_attrs },
{ type: "item", url: "/components/avatar", label: "Avatar", attrs: link_attrs },
{ type: "item", url: "/components/badge", label: "Badge", attrs: link_attrs },
{ type: "item", url: "/components/breadcrumb", label: "Breadcrumb", attrs: link_attrs },
{ type: "item", url: "/components/button", label: "Button", attrs: link_attrs },
{ type: "item", url: "/components/card", label: "Card", attrs: link_attrs },
{ type: "item", url: "/components/checkbox", label: "Checkbox", attrs: link_attrs },
{ type: "item", url: "/components/combobox", label: "Combobox", attrs: link_attrs },
{ type: "item", url: "/components/dialog", label: "Dialog", attrs: link_attrs },
{ type: "item", url: "/components/dropdown-menu", label: "Dropdown Menu", attrs: link_attrs },
{ type: "item", url: "/components/form", label: "Form", attrs: link_attrs },
{ type: "item", url: "/components/input", label: "Input", attrs: link_attrs },
{ type: "item", url: "/components/label", label: "Label", attrs: link_attrs },
{ type: "item", url: "/components/pagination", label: "Pagination", attrs: link_attrs },
{ type: "item", url: "/components/popover", label: "Popover", attrs: link_attrs },
{ type: "item", url: "/components/radio-group", label: "Radio Group", attrs: link_attrs },
{ type: "item", url: "/components/select", label: "Select", attrs: link_attrs },
{ type: "item", url: "/components/skeleton", label: "Skeleton", attrs: link_attrs },
{ type: "item", url: "/components/sidebar", label: "Sidebar", attrs: link_attrs },
{ type: "item", url: "/components/slider", label: "Slider", attrs: link_attrs },
{ type: "item", url: "/components/switch", label: "Switch", attrs: link_attrs },
{ type: "item", url: "/components/table", label: "Table", attrs: link_attrs },
{ type: "item", url: "/components/tabs", label: "Tabs", attrs: link_attrs },
{ type: "item", url: "/components/textarea", label: "Textarea", attrs: link_attrs },
{ type: "item", url: "/components/toast", label: "Toast", attrs: link_attrs },
{ type: "item", url: "/components/tooltip", label: "Tooltip", attrs: link_attrs }
]}
] %}
+380 -336
View File
@@ -1551,11 +1551,12 @@
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--sidebar-width: 16rem;
--sidebar-mobile-width: 18rem;
--scrollbar-track: transparent;
--scrollbar-thumb: rgba(0, 0, 0, 0.3);
--scrollbar-width: 6px;
--scrollbar-radius: 6px;
--sidebar-width: 16rem;
--chevron-down-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>');
--chevron-down-icon-50: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0 / 0.5)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>');
--check-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>');
@@ -1592,10 +1593,7 @@
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.439 0 0);
--scrollbar-track: transparent;
--scrollbar-thumb: rgba(255, 255, 255, 0.3);
--scrollbar-width: 6px;
--scrollbar-radius: 6px;
--chevron-down-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.708 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>');
--chevron-down-icon-50: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.708 0 0 / 0.5)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>');
--check-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.708 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>');
@@ -3096,6 +3094,384 @@
background-repeat: no-repeat;
}
}
.sidebar {
&:not([aria-hidden]), &[aria-hidden=false] {
@media (width < 48rem) {
position: fixed;
}
@media (width < 48rem) {
inset: calc(var(--spacing) * 0);
}
@media (width < 48rem) {
z-index: 40;
}
@media (width < 48rem) {
background-color: color-mix(in srgb, #000 50%, transparent);
@supports (color: color-mix(in lab, red, red)) {
background-color: color-mix(in oklab, var(--color-black) 50%, transparent);
}
}
}
nav {
position: fixed;
inset-block: calc(var(--spacing) * 0);
z-index: 50;
display: flex;
width: var(--sidebar-mobile-width);
flex-direction: column;
background-color: var(--color-sidebar);
color: var(--color-sidebar-foreground);
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 300ms;
transition-duration: 300ms;
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
@media (width >= 48rem) {
width: var(--sidebar-width);
}
}
&:not([data-side]), &[data-side=left] {
nav {
left: calc(var(--spacing) * 0);
border-right-style: var(--tw-border-style);
border-right-width: 1px;
}
& + * {
position: relative;
transition-property: margin;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 300ms;
transition-duration: 300ms;
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
@media (width >= 48rem) {
margin-left: var(--sidebar-width);
}
}
&[aria-hidden=true] {
nav {
--tw-translate-x: -100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
& + * {
@media (width >= 48rem) {
margin-left: calc(var(--spacing) * 0);
}
}
}
}
&[data-side=right] {
nav {
right: calc(var(--spacing) * 0);
border-left-style: var(--tw-border-style);
border-left-width: 1px;
}
& + * {
position: relative;
transition-property: margin;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 300ms;
transition-duration: 300ms;
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
@media (width >= 48rem) {
margin-right: var(--sidebar-width);
}
}
&[aria-hidden=true] {
nav {
--tw-translate-x: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
& + * {
@media (width >= 48rem) {
margin-right: calc(var(--spacing) * 0);
}
}
}
}
nav {
> header, > footer {
display: flex;
flex-direction: column;
gap: calc(var(--spacing) * 2);
padding: calc(var(--spacing) * 2);
}
[role=separator] {
margin-inline: calc(var(--spacing) * 2);
width: auto;
border-color: var(--color-sidebar-border);
}
> section {
display: flex;
min-height: calc(var(--spacing) * 0);
flex: 1;
flex-direction: column;
gap: calc(var(--spacing) * 2);
overflow-y: auto;
> [role=group] {
position: relative;
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
flex-direction: column;
padding: calc(var(--spacing) * 2);
}
h3 {
display: flex;
height: calc(var(--spacing) * 8);
flex-shrink: 0;
align-items: center;
border-radius: var(--radius-md);
padding-inline: calc(var(--spacing) * 2);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
color: var(--color-sidebar-foreground);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-sidebar-foreground) 70%, transparent);
}
--tw-ring-color: var(--color-sidebar-ring);
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
transition-property: margin,opacity;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 200ms;
transition-duration: 200ms;
--tw-ease: linear;
transition-timing-function: linear;
&:focus-visible {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&>svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
&>svg {
flex-shrink: 0;
}
}
ul {
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
flex-direction: column;
gap: calc(var(--spacing) * 1);
li {
position: relative;
> a, > details > summary {
display: flex;
width: 100%;
align-items: center;
gap: calc(var(--spacing) * 2);
overflow: hidden;
border-radius: var(--radius-md);
padding: calc(var(--spacing) * 2);
text-align: left;
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-ring-color: var(--color-sidebar-ring);
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
transition-property: width,height,padding;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
&:is(:where(.group\/menu-item):has(*[data-sidebar="menu-action"]) *) {
padding-right: calc(var(--spacing) * 8);
}
&:is(:where(.group)[data-collapsible="icon"] *) {
width: calc(var(--spacing) * 8) !important;
height: calc(var(--spacing) * 8) !important;
}
&:is(:where(.group)[data-collapsible="icon"] *) {
padding: calc(var(--spacing) * 2) !important;
}
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
&:focus-visible {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&:active {
background-color: var(--color-sidebar-accent);
}
&:active {
color: var(--color-sidebar-accent-foreground);
}
&:disabled {
pointer-events: none;
}
&:disabled {
opacity: 50%;
}
&[aria-disabled="true"] {
pointer-events: none;
}
&[aria-disabled="true"] {
opacity: 50%;
}
&[data-active="true"] {
background-color: var(--color-sidebar-accent);
}
&[data-active="true"] {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
&[data-active="true"] {
color: var(--color-sidebar-accent-foreground);
}
&[data-state="open"] {
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
}
&[data-state="open"] {
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
}
&>span:last-child {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&>svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
&>svg {
flex-shrink: 0;
}
&:not([data-variant]), &[data-variant=default] {
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
}
&[data-variant=outline] {
background-color: var(--color-background);
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, hsl(var(--sidebar-border)));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
&:hover {
@media (hover: hover) {
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, hsl(var(--sidebar-accent)));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
}
}
&:not([data-size]), &[data-size=default] {
height: calc(var(--spacing) * 8);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
&[data-size=sm] {
height: calc(var(--spacing) * 7);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
&[data-size=lg] {
height: calc(var(--spacing) * 12);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
&:is(:where(.group)[data-collapsible="icon"] *) {
padding: calc(var(--spacing) * 0) !important;
}
}
}
> details {
&:not([open]) {
> summary {
&::after {
rotate: calc(90deg * -1);
}
}
}
> summary {
&::after {
margin-left: auto;
display: block;
width: calc(var(--spacing) * 3.5);
height: calc(var(--spacing) * 3.5);
background-color: var(--color-primary);
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-ease: linear;
transition-timing-function: linear;
--tw-content: '';
content: var(--tw-content);
mask-image: var(--chevron-down-icon);
mask-size: 1rem;
mask-position: center;
mask-repeat: no-repeat;
}
}
&::details-content {
padding-inline: calc(var(--spacing) * 3.5);
}
}
}
ul {
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
--tw-translate-x: 1px;
translate: var(--tw-translate-x) var(--tw-translate-y);
flex-direction: column;
gap: calc(var(--spacing) * 1);
border-left-style: var(--tw-border-style);
border-left-width: 1px;
border-color: var(--color-sidebar-border);
padding-inline: calc(var(--spacing) * 2.5);
padding-block: calc(var(--spacing) * 0.5);
}
}
}
}
}
@layer components {
.form input[type='checkbox'][role='switch'], .input[type='checkbox'][role='switch'] {
display: inline-flex;
@@ -3730,338 +4106,6 @@
}
}
}
.sidebar {
position: fixed;
inset-block: calc(var(--spacing) * 0);
z-index: 50;
display: flex;
width: var(--sidebar-width);
flex-direction: column;
background-color: var(--color-sidebar);
color: var(--color-sidebar-foreground);
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
&:not([data-side]), &[data-side=left] {
left: calc(var(--spacing) * 0);
border-right-style: var(--tw-border-style);
border-right-width: 1px;
&[aria-hidden="true"] {
--tw-translate-x: -100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
& + * {
position: relative;
margin-left: var(--sidebar-width);
transition-property: margin;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
}
&[aria-hidden=true] + * {
margin-left: calc(var(--spacing) * 0);
}
}
&[data-side=right] {
right: calc(var(--spacing) * 0);
border-left-style: var(--tw-border-style);
border-left-width: 1px;
&[aria-hidden="true"] {
--tw-translate-x: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
& + * {
position: relative;
margin-right: var(--sidebar-width);
transition-property: margin;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-ease: var(--ease-in-out);
transition-timing-function: var(--ease-in-out);
}
&[aria-hidden=true] + * {
margin-right: calc(var(--spacing) * 0);
}
}
> header, > footer {
display: flex;
flex-direction: column;
gap: calc(var(--spacing) * 2);
padding: calc(var(--spacing) * 2);
}
[role=separator] {
margin-inline: calc(var(--spacing) * 2);
width: auto;
border-color: var(--color-sidebar-border);
}
> section {
display: flex;
min-height: calc(var(--spacing) * 0);
flex: 1;
flex-direction: column;
gap: calc(var(--spacing) * 2);
overflow-y: auto;
> [role=group] {
position: relative;
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
flex-direction: column;
padding: calc(var(--spacing) * 2);
}
h3 {
display: flex;
height: calc(var(--spacing) * 8);
flex-shrink: 0;
align-items: center;
border-radius: var(--radius-md);
padding-inline: calc(var(--spacing) * 2);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
color: var(--color-sidebar-foreground);
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, var(--color-sidebar-foreground) 70%, transparent);
}
--tw-ring-color: var(--color-sidebar-ring);
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
transition-property: margin,opacity;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-duration: 200ms;
transition-duration: 200ms;
--tw-ease: linear;
transition-timing-function: linear;
&:focus-visible {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&>svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
&>svg {
flex-shrink: 0;
}
}
ul {
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
flex-direction: column;
gap: calc(var(--spacing) * 1);
li {
position: relative;
> a, > details > summary {
display: flex;
width: 100%;
align-items: center;
gap: calc(var(--spacing) * 2);
overflow: hidden;
border-radius: var(--radius-md);
padding: calc(var(--spacing) * 2);
text-align: left;
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-ring-color: var(--color-sidebar-ring);
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
transition-property: width,height,padding;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
&:is(:where(.group\/menu-item):has(*[data-sidebar="menu-action"]) *) {
padding-right: calc(var(--spacing) * 8);
}
&:is(:where(.group)[data-collapsible="icon"] *) {
width: calc(var(--spacing) * 8) !important;
height: calc(var(--spacing) * 8) !important;
}
&:is(:where(.group)[data-collapsible="icon"] *) {
padding: calc(var(--spacing) * 2) !important;
}
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
&:focus-visible {
--tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
&:active {
background-color: var(--color-sidebar-accent);
}
&:active {
color: var(--color-sidebar-accent-foreground);
}
&:disabled {
pointer-events: none;
}
&:disabled {
opacity: 50%;
}
&[aria-disabled="true"] {
pointer-events: none;
}
&[aria-disabled="true"] {
opacity: 50%;
}
&[data-active="true"] {
background-color: var(--color-sidebar-accent);
}
&[data-active="true"] {
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
&[data-active="true"] {
color: var(--color-sidebar-accent-foreground);
}
&[data-state="open"] {
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
}
&[data-state="open"] {
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
}
&>span:last-child {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&>svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
&>svg {
flex-shrink: 0;
}
&:not([data-variant]), &[data-variant=default] {
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
}
&[data-variant=outline] {
background-color: var(--color-background);
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, hsl(var(--sidebar-border)));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
&:hover {
@media (hover: hover) {
background-color: var(--color-sidebar-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-sidebar-accent-foreground);
}
}
&:hover {
@media (hover: hover) {
--tw-shadow: 0 0 0 1px var(--tw-shadow-color, hsl(var(--sidebar-accent)));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
}
}
&:not([data-size]), &[data-size=default] {
height: calc(var(--spacing) * 8);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
&[data-size=sm] {
height: calc(var(--spacing) * 7);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
}
&[data-size=lg] {
height: calc(var(--spacing) * 12);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
&:is(:where(.group)[data-collapsible="icon"] *) {
padding: calc(var(--spacing) * 0) !important;
}
}
}
> details {
&:not([open]) {
> summary {
&::after {
rotate: calc(90deg * -1);
}
}
}
> summary {
&::after {
margin-left: auto;
display: block;
width: calc(var(--spacing) * 3.5);
height: calc(var(--spacing) * 3.5);
background-color: var(--color-primary);
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
--tw-ease: linear;
transition-timing-function: linear;
--tw-content: '';
content: var(--tw-content);
mask-image: var(--chevron-down-icon);
mask-size: 1rem;
mask-position: center;
mask-repeat: no-repeat;
}
}
&::details-content {
padding-inline: calc(var(--spacing) * 3.5);
}
}
}
ul {
display: flex;
width: 100%;
min-width: calc(var(--spacing) * 0);
--tw-translate-x: 1px;
translate: var(--tw-translate-x) var(--tw-translate-y);
flex-direction: column;
gap: calc(var(--spacing) * 1);
border-left-style: var(--tw-border-style);
border-left-width: 1px;
border-color: var(--color-sidebar-border);
padding-inline: calc(var(--spacing) * 2.5);
padding-block: calc(var(--spacing) * 0.5);
}
}
}
}
.content {
> h2 {
margin-top: calc(var(--spacing) * 16);
+116 -111
View File
@@ -33,11 +33,12 @@
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--sidebar-width: 16rem;
--sidebar-mobile-width: 18rem;
--scrollbar-track: transparent;
--scrollbar-thumb: rgba(0, 0, 0, 0.3);
--scrollbar-width: 6px;
--scrollbar-radius: 6px;
--sidebar-width: 16rem;
--chevron-down-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>'); /* --muted-foreground */
--chevron-down-icon-50: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0 / 0.5)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>'); /* --muted-foreground + 50% opacity */
--check-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.556 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>'); /* --muted-foreground */
@@ -75,10 +76,7 @@
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.439 0 0);
--scrollbar-track: transparent;
--scrollbar-thumb: rgba(255, 255, 255, 0.3);
--scrollbar-width: 6px;
--scrollbar-radius: 6px;
--chevron-down-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.708 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>'); /* --muted-foreground */
--chevron-down-icon-50: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.708 0 0 / 0.5)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>'); /* --muted-foreground + 50% opacity */
--check-icon: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="oklch(0.708 0 0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-icon lucide-check"><path d="M20 6 9 17l-5-5"/></svg>');/* --muted-foreground */
@@ -121,8 +119,6 @@
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
/* --font-sans: 'Inter var', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; */
}
@layer base {
@@ -746,6 +742,120 @@
}
}
/* Sidebar */
.sidebar {
&:not([aria-hidden]),
&[aria-hidden=false] {
@apply max-md:bg-black/50 max-md:fixed max-md:inset-0 max-md:z-40;
}
nav {
@apply bg-sidebar text-sidebar-foreground flex flex-col w-(--sidebar-mobile-width) md:w-(--sidebar-width) fixed inset-y-0 transition-transform ease-in-out duration-300 z-50;
}
&:not([data-side]),
&[data-side=left] {
nav {
@apply left-0 border-r;
}
& + * {
@apply relative md:ml-(--sidebar-width) transition-[margin] ease-in-out duration-300;
}
&[aria-hidden=true] {
nav {
@apply -translate-x-full;
}
& + * {
@apply md:ml-0;
}
}
}
&[data-side=right] {
nav {
@apply right-0 border-l;
}
& + * {
@apply relative md:mr-(--sidebar-width) transition-[margin] ease-in-out duration-300;
}
&[aria-hidden=true] {
nav {
@apply translate-x-full;
}
& + * {
@apply md:mr-0;
}
}
}
nav {
> header,
> footer {
@apply flex flex-col gap-2 p-2;
}
[role=separator] {
@apply border-sidebar-border mx-2 w-auto;
}
> section {
@apply flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto;
> [role=group] {
@apply relative flex w-full min-w-0 flex-col p-2;
}
h3 {
@apply text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0;
}
ul {
@apply flex w-full min-w-0 flex-col gap-1;
li {
@apply relative;
> a,
> details > summary {
@apply flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0;
&:not([data-variant]),
&[data-variant=default] {
@apply hover:bg-sidebar-accent hover:text-sidebar-accent-foreground;
}
&[data-variant=outline] {
@apply bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))];
}
&:not([data-size]),
&[data-size=default] {
@apply h-8 text-sm;
}
&[data-size=sm] {
@apply h-7 text-xs;
}
&[data-size=lg] {
@apply h-12 text-sm group-data-[collapsible=icon]:p-0!;
}
}
> details {
&:not([open]) {
> summary {
&::after {
@apply -rotate-90;
}
}
}
> summary {
&::after {
@apply content-[''] block size-3.5 bg-primary ml-auto transition-transform ease-linear;
@apply mask-[image:var(--chevron-down-icon)] mask-size-[1rem] mask-no-repeat mask-center;
}
}
&::details-content {
@apply px-3.5;
}
}
}
ul {
@apply border-sidebar-border flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5 w-full;
}
}
}
}
}
/* Switch */
@layer components {
.form input[type='checkbox'][role='switch'],
@@ -909,109 +1019,4 @@
}
}
}
}
/* Sidebar */
.sidebar {
@apply bg-sidebar text-sidebar-foreground flex flex-col w-(--sidebar-width) fixed inset-y-0 transition-transform ease-in-out z-50;
/* h-svh */
&:not([data-side]),
&[data-side=left] {
@apply left-0 border-r aria-[hidden=true]:-translate-x-full;
& + * {
@apply relative ml-(--sidebar-width) transition-[margin] ease-in-out;
}
&[aria-hidden=true] + * {
@apply ml-0;
}
}
&[data-side=right] {
@apply right-0 border-l aria-[hidden=true]:translate-x-full;
& + * {
@apply relative mr-(--sidebar-width) transition-[margin] ease-in-out;
}
&[aria-hidden=true] + * {
@apply mr-0;
}
}
> header,
> footer {
@apply flex flex-col gap-2 p-2;
}
[role=separator] {
@apply border-sidebar-border mx-2 w-auto;
}
> section {
@apply flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto;
> [role=group] {
@apply relative flex w-full min-w-0 flex-col p-2;
}
h3 {
@apply text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0;
}
/* GROUP CONTENT: w-full text-sm */
ul {
@apply flex w-full min-w-0 flex-col gap-1;
li {
/* group/menu-item */
@apply relative;
> a,
> details > summary {
/* peer/menu-button */
@apply flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0;
&:not([data-variant]),
&[data-variant=default] {
@apply hover:bg-sidebar-accent hover:text-sidebar-accent-foreground;
}
&[data-variant=outline] {
@apply bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))];
}
&:not([data-size]),
&[data-size=default] {
@apply h-8 text-sm;
}
&[data-size=sm] {
@apply h-7 text-xs;
}
&[data-size=lg] {
@apply h-12 text-sm group-data-[collapsible=icon]:p-0!;
}
}
> details {
&:not([open]) {
> summary {
&::after {
@apply -rotate-90;
}
}
}
> summary {
&::after {
@apply content-[''] block size-3.5 bg-primary ml-auto transition-transform ease-linear;
@apply mask-[image:var(--chevron-down-icon)] mask-size-[1rem] mask-no-repeat mask-center;
}
}
&::details-content {
@apply px-3.5;
}
}
}
ul {
@apply border-sidebar-border flex min-w-0 translate-x-px flex-col gap-1 border-l px-2.5 py-0.5 w-full;
}
}
}
}
+3 -3
View File
@@ -10,9 +10,9 @@
{% macro popover(
id=None,
trigger=None,
main_attrs=None,
trigger_attrs=None,
content_attrs=None
main_attrs={},
trigger_attrs={},
content_attrs={}
) %}
<div
class="popover {{ main_attrs.class }}"
+1 -1
View File
@@ -39,7 +39,7 @@ window.basecoat.registerDialog = function(Alpine) {
$main: {
'@dialog:open.window'(e) { if (e.detail && e.detail.id === this.id) this.show() },
'@dialog:close.window'(e) { if (e.detail && e.detail.id === this.id) this.hide() },
'@keydown.escape.window'() { this.open && this.hide(); },
'@keydown.escape.window'() { this.open && this.hide() },
},
$trigger: {
'@click'() { this.show() },
+2 -6
View File
@@ -14,12 +14,8 @@ window.basecoat.registerSidebar= function(Alpine) {
$main: {
'@sidebar:open.window'(e) { if (!this.id || (e.detail && e.detail.id === this.id)) this.open = true },
'@sidebar:close.window'(e) { if (!this.id || (e.detail && e.detail.id === this.id)) this.open = false },
'@sidebar:toggle.window'(e) {
if (!this.id || (e.detail && e.detail.id === this.id)) {
this.open = !this.open;
console.log('toggle', e);
}
},
'@sidebar:toggle.window'(e) { if (!this.id || (e.detail && e.detail.id === this.id)) this.open = !this.open },
'@click'(e) { if (e.target === this.$el) this.open = false },
':aria-hidden'() { return !this.open },
':inert'() { return !this.open },
},
+3 -3
View File
@@ -10,9 +10,9 @@
{% macro popover(
id=None,
trigger=None,
main_attrs=None,
trigger_attrs=None,
content_attrs=None
main_attrs={},
trigger_attrs={},
content_attrs={}
) %}
<div
class="popover {{ main_attrs.class }}"
+59 -46
View File
@@ -4,7 +4,13 @@
{% set item_id = parent_id ~ loop.index %}
{% if item.type == 'item' %}
<li>
<a href="{{ item.url }}" {{ 'aria-current="page"' if item.current }}>
<a
href="{{ item.url }}"
{{ 'aria-current="page"' if item.current }}
{% for key, value in item.attrs %}
{% if key != 'href' and key != 'aria-current' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% if item.icon %}{{ item.icon | safe }}{% endif %}
<span>{{ item.label }}</span>
</a>
@@ -12,7 +18,12 @@
{% elif item.type == 'submenu' %}
<li>
<details {{ 'open' if item.open }} id="submenu-{{ item_id }}">
<summary aria-controls="submenu-{{ item_id }}-content">
<summary
aria-controls="submenu-{{ item_id }}-content"
{% for key, value in item.attrs %}
{% if key != 'aria-controls' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% if item.icon %}{{ item.icon | safe }}{% endif %}
{{ item.label }}
</summary>
@@ -40,10 +51,9 @@
footer_attrs={},
content_wrapper_attrs=None
) %}
<nav
<div
{% if id %}id="{{ id }}"{% endif %}
class="sidebar peer {{ main_attrs.class }}"
aria-label="{{ label }}"
class="sidebar {{ main_attrs.class }}"
aria-hidden="{{ 'false' if open else 'true' }}"
data-side="{{ side if side else 'left' }}"
{{ 'inert' if not open }}
@@ -53,49 +63,52 @@
x-data="sidebar({{ 'true' if open else 'false' }})"
x-bind="$main"
>
{% if header %}
<header
{% for key, value in header_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
<nav
aria-label="{{ label }}"
>
{{ header | safe }}
</header>
{% endif %}
<hr role="separator"/>
<section
{% for key, value in content_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
{% if menu %}
{% for group_or_item in menu %}
{% set group_id = loop.index %}
{% if group_or_item.type == 'group' %}
<div role="group" aria-labelledby="nav-group-{{ group_id }}">
<h3 id="nav-group-{{ group_id }}">{{ group_or_item.label }}</h3>
{{ render_menu_items(group_or_item.items, 0, group_id + '-') }}
</div>
{% elif group_or_item.type == 'separator' %}
<hr role="separator"/>
{% elif group_or_item.type == 'item' or group_or_item.type == 'submenu' %}
{{ render_menu_items([group_or_item], 0, 'top-' + group_id + '-') }}
{% endif %}
{% if header %}
<header
{% for key, value in header_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% else %}
{{ caller() if caller }}
>
{{ header | safe }}
</header>
{% endif %}
</section>
{% if footer %}
<footer
{% for key, value in footer_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ footer | safe }}
</footer>
{% endif %}
</nav>
<section
{% for key, value in content_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
{% if menu %}
{% for group_or_item in menu %}
{% set group_id = loop.index %}
{% if group_or_item.type == 'group' %}
<div role="group" aria-labelledby="nav-group-{{ group_id }}">
<h3 id="nav-group-{{ group_id }}">{{ group_or_item.label }}</h3>
{{ render_menu_items(group_or_item.items, 0, group_id + '-') }}
</div>
{% elif group_or_item.type == 'separator' %}
<hr role="separator"/>
{% elif group_or_item.type == 'item' or group_or_item.type == 'submenu' %}
{{ render_menu_items([group_or_item], 0, 'top-' + group_id + '-') }}
{% endif %}
{% endfor %}
{% else %}
{{ caller() if caller }}
{% endif %}
</section>
{% if footer %}
<footer
{% for key, value in footer_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ footer | safe }}
</footer>
{% endif %}
</nav>
</div>
{% endmacro %}