Working on charts and sidebar

This commit is contained in:
Ronan Berder
2025-05-04 15:58:10 +08:00
parent c5417901db
commit 7749810bb1
10 changed files with 717 additions and 855 deletions
+1 -1
View File
@@ -16,4 +16,4 @@
}
@import "./themes/claude.css";
/* @import "./themes/claude.css"; */
+5 -148
View File
@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html class="dark"><head>
<html>
<head>
<!-- Theme -->
<script>
function isDark() {
@@ -13,10 +14,6 @@
document.addEventListener('alpine:init', () => {
Alpine.store('theme', {
isDark: isDark(),
set(value) {
this.isDark = value;
localStorage.setItem('theme', value ? 'dark' : 'light');
},
toggle() {
this.isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('theme', this.isDark ? 'dark' : 'light');
@@ -82,154 +79,14 @@
</head>
<body>
{% from "sidebar.njk" import sidebar %}
{% from "popover.njk" import popover %}
{% set footer %}
{% 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 %}
<a href="/" class="btn-ghost p-2 h-12 w-full justify-start">
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-4 w-4"><rect width="256" height="256" fill="none"></rect><line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line><line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line></svg>
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">Basecoat</span>
<span class="truncate text-xs">v{{ pkg.version }}</span>
</div>
</a>
<div class="flex flex-col gap-2 px-2 text-sm">
<form class="form relative">
<label for="search" class="sr-only">Search</label>
<input type="text" placeholder="Search the docs..." class="h-8 w-full shadow-none pl-8">
{% lucide "search", { "class": "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" } %}
</form>
</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, 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", 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 }
]}
] %}
{{ sidebar(
label="Sidebar navigation",
header=header,
footer=footer,
menu=menu
) }}
{% include "partials/sidebar.njk" %}
<main id="content">
<header class="bg-background sticky inset-x-0 top-0 isolate z-20 flex shrink-0 items-center gap-2 border-b z-10">
<div class="flex h-14 w-full items-center gap-2 px-4">
<button
type="button"
aria-label="Hide sidebar"
data-tooltip="Hide sidebar"
:data-tooltip="sidebarOpen ? 'Hide sidebar' : 'Show sidebar'"
data-side="bottom"
data-align="start"
class="btn-sm-icon-ghost mr-auto"
x-init
x-data="{ sidebarOpen: true }"
@click="
sidebarOpen = !sidebarOpen;
$dispatch('sidebar:toggle');
"
>
{% lucide "panel-left" %}
</button>
<select class="select h-8 leading-none">
<option value="light">Default</option>
<option value="light">Other</option>
</select>
<button
type="button"
aria-label="Toggle theme"
data-tooltip="Toggle theme"
data-side="bottom"
data-align="end"
class="btn-icon-outline size-8"
x-init
@click="$store.theme.toggle()"
>
<span x-show="$store.theme.isDark">{% lucide "sun" %}</span>
<span x-show="!$store.theme.isDark">{% lucide "moon" %}</span>
</button>
</div>
{% include "partials/header.njk" %}
</header>
<div class="grid gap-4 p-4">
<div class="p-4">
{{ content | safe }}
</div>
</main>
+1 -1
View File
@@ -5,7 +5,7 @@ layout: layouts/base.njk
<main class="mx-auto relative py-6 lg:gap-10 lg:py-8 flex w-full max-w-screen-lg">
<div class="mx-auto w-full max-w-screen-md">
<header class="space-y-2">
<h1 class="text-3xl font-medium tracking-tight">{{ title }}</h1>
<h1 class="text-3xl font-semibold tracking-tight">{{ title }}</h1>
<p class="text-muted-foreground">{{ description }}</p>
</header>
<article class="pb-12 mt-8 content">
+126 -147
View File
@@ -1,149 +1,128 @@
{% set components = [
{ "id": "accordion", "name": "Accordion" },
{ "id": "alert", "name": "Alert" },
{ "id": "alert-dialog", "name": "Alert Dialog" },
{ "id": "avatar", "name": "Avatar" },
{ "id": "badge", "name": "Badge" },
{ "id": "breadcrumb", "name": "Breadcrumb" },
{ "id": "button", "name": "Button" },
{ "id": "card", "name": "Card" },
{ "id": "checkbox", "name": "Checkbox" },
{ "id": "combobox", "name": "Combobox" },
{ "id": "dialog", "name": "Dialog" },
{ "id": "dropdown-menu", "name": "Dropdown Menu" },
{ "id": "form", "name": "Form" },
{ "id": "input", "name": "Input" },
{ "id": "label", "name": "Label" },
{ "id": "pagination", "name": "Pagination" },
{ "id": "popover", "name": "Popover" },
{ "id": "radio-group", "name": "Radio Group" },
{ "id": "select", "name": "Select" },
{ "id": "skeleton", "name": "Skeleton" },
{ "id": "sidebar", "name": "Sidebar" },
{ "id": "slider", "name": "Slider" },
{ "id": "switch", "name": "Switch" },
{ "id": "table", "name": "Table" },
{ "id": "tabs", "name": "Tabs" },
{ "id": "textarea", "name": "Textarea" },
{ "id": "toast", "name": "Toast" },
{ "id": "tooltip", "name": "Tooltip" }
{% from "sidebar.njk" import sidebar %}
{% from "popover.njk" import popover %}
{% set footer %}
{% 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 %}
<a href="/" class="btn-ghost p-2 h-12 w-full justify-start">
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-4 w-4"><rect width="256" height="256" fill="none"></rect><line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line><line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line></svg>
</div>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">Basecoat</span>
<span class="truncate text-xs">v{{ pkg.version }}</span>
</div>
</a>
<div class="flex flex-col gap-2 px-2 text-sm">
<form class="form relative">
<label for="search" class="sr-only">Search</label>
<input type="text" placeholder="Search the docs..." class="h-8 w-full shadow-none pl-8">
{% lucide "search", { "class": "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" } %}
</form>
</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: [
{ label: "Introduction", url: "/", icon: icon_info, current: true, attrs: link_attrs },
{ label: "Installation", url: "/installation", icon: icon_square_terminal, attrs: link_attrs },
{ label: "Kitchen sink", url: "/kitchen-sink", icon: icon_layout_dashboard, attrs: link_attrs },
{ label: "GitHub", url: "https://github.com/hunvreus/basecoat", icon: icon_github, attrs: link_attrs }
]},
{ type: 'group', label: 'Components', items: [
{ label: "Accordion", url: "/components/accordion", attrs: link_attrs },
{ label: "Alert", url: "/components/alert", attrs: link_attrs },
{ label: "Alert Dialog", url: "/components/alert-dialog", attrs: link_attrs },
{ label: "Avatar", url: "/components/avatar", attrs: link_attrs },
{ label: "Badge", url: "/components/badge", attrs: link_attrs },
{ label: "Breadcrumb", url: "/components/breadcrumb", attrs: link_attrs },
{ label: "Button", url: "/components/button", attrs: link_attrs },
{ label: "Card", url: "/components/card", attrs: link_attrs },
{ label: "Checkbox", url: "/components/checkbox", attrs: link_attrs },
{ label: "Combobox", url: "/components/combobox", attrs: link_attrs },
{ label: "Dialog", url: "/components/dialog", attrs: link_attrs },
{ label: "Dropdown Menu", url: "/components/dropdown-menu", attrs: link_attrs },
{ label: "Form", url: "/components/form", attrs: link_attrs },
{ label: "Input", url: "/components/input", attrs: link_attrs },
{ label: "Label", url: "/components/label", attrs: link_attrs },
{ label: "Pagination", url: "/components/pagination", attrs: link_attrs },
{ label: "Popover", url: "/components/popover", attrs: link_attrs },
{ label: "Radio Group", url: "/components/radio-group", attrs: link_attrs },
{ label: "Select", url: "/components/select", attrs: link_attrs },
{ label: "Skeleton", url: "/components/skeleton", attrs: link_attrs },
{ label: "Sidebar", url: "/components/sidebar", attrs: link_attrs },
{ label: "Slider", url: "/components/slider", attrs: link_attrs },
{ label: "Switch", url: "/components/switch", attrs: link_attrs },
{ label: "Table", url: "/components/table", attrs: link_attrs },
{ label: "Tabs", url: "/components/tabs", attrs: link_attrs },
{ label: "Textarea", url: "/components/textarea", attrs: link_attrs },
{ label: "Toast", url: "/components/toast", attrs: link_attrs },
{ label: "Tooltip", url: "/components/tooltip", attrs: link_attrs }
]}
] %}
<header class="flex flex-col gap-2 p-2">
<a href="/" class="btn-ghost p-2 h-12 w-full justify-start">
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="h-4 w-4"><rect width="256" height="256" fill="none"></rect><line x1="208" y1="128" x2="128" y2="208" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line><line x1="192" y1="40" x2="40" y2="192" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="32"></line></svg>
</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>
</div>
</a>
<div class="flex flex-col gap-2 px-2 text-sm">
<form class="form relative">
<label for="search" class="sr-only">Search</label>
<input type="text" placeholder="Search the docs..." class="h-8 w-full shadow-none pl-8">
{% lucide "search", { "class": "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" } %}
</form>
</div>
</header>
<section class="flex flex-1 flex-col gap-2 overflow-y-auto scrollbar">
<nav aria-label="Pages navigation" class="flex flex-col p-2">
<h2 class="text-sidebar-foreground/70 flex h-8 items-center px-2 text-xs font-medium">Getting Started</h2>
<ul class="flex flex-col gap-1">
<li>
<a
href="/"
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
>
{% lucide "info" %}
Introduction
</a>
</li>
<li>
<a
href="/installation"
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
>
{% lucide "terminal" %}
Installation
</a>
</li>
<li>
<a
href="/kitchen-sink"
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
>
{% lucide "layout-dashboard" %}
Kitchen sink
</a>
</li>
<li>
<a
href="https://github.com/hunvreus/basecoat"
target="_blank"
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
>
{% lucide "github" %}
GitHub
{% lucide "arrow-up-right", { "class": "text-muted-foreground size-3.5 ml-auto" } %}
</a>
</li>
</ul>
</nav>
<nav aria-label="Components navigation" class="flex flex-col p-2">
<h2 class="text-sidebar-foreground/70 flex h-8 items-center px-2 text-xs font-medium">Components</h2>
<ul class="flex flex-col gap-1">
{% for component in components %}
<li>
<a
href="/components/{{ component.id }}"
class="h-8 flex items-center gap-2 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 [&>svg]:size-4 [&>svg]:shrink-0"
hx-boost="true"
hx-select="#content"
hx-target="#content"
hx-swap="outerHTML"
>
{{ component.name }}
</a>
</li>
{% endfor %}
</ul>
</nav>
</section>
<footer class="flex flex-col gap-2 p-2">
{% set profile_menu %}
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<img src="https://avatar.vercel.sh/ronan_berder" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">hunvreus</span>
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
</div>
</div>
<hr role="separator">
<nav role="menu">
<button type="button" role="menuitem">
{% lucide "star" %}
Upgrade to Pro
</button>
<hr role="separator">
<button type="button" role="menuitem">
{% lucide "user" %}
Account
</button>
<button type="button" role="menuitem">
{% lucide "credit-card" %}
Billing
</button>
<button type="button" role="menuitem">
{% lucide "bell" %}
Notifications
</button>
<hr role="separator">
<button type="button" role="menuitem">
{% lucide "log-out" %}
Signout
</button>
</nav>
{% endset %}
</footer>
{{ sidebar(
label="Sidebar navigation",
header=header,
footer=footer,
content_attrs={
"x-data": "{
updateCurrentPage() {
$el.querySelectorAll('a').forEach(link => {
normalizedCurrentPathname = window.location.pathname.endsWith('/') ?
window.location.pathname.slice(0, -1) :
window.location.pathname;
normalizedLinkPathname = link.pathname.endsWith('/') ?
link.pathname.slice(0, -1) :
link.pathname;
if (normalizedCurrentPathname === normalizedLinkPathname) {
link.setAttribute('aria-current', 'page');
} else {
link.setAttribute('aria-current', 'false');
}
});
}
}",
"x-init": "updateCurrentPage()",
"@htmx:pushed-into-history.window": "updateCurrentPage()"
},
menu=menu
) }}
+388 -490
View File
File diff suppressed because it is too large Load Diff
+4 -1
View File
@@ -744,6 +744,9 @@
/* Sidebar */
.sidebar {
&[data-uninitialized] {
@apply max-md:hidden;
}
&:not([aria-hidden]),
&[aria-hidden=false] {
@apply max-md:bg-black/50 max-md:fixed max-md:inset-0 max-md:z-40;
@@ -809,7 +812,7 @@
> 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;
@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 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&[aria-current=page]]:bg-sidebar-accent [&[aria-current=page]]:font-medium [&[aria-current=page]]:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0;
&:not([data-variant]),
&[data-variant=default] {
+11 -9
View File
@@ -2,22 +2,24 @@ window.basecoat = window.basecoat || {};
window.basecoat.registerSidebar= function(Alpine) {
if (Alpine.components && Alpine.components.sidebar) return;
Alpine.data('sidebar', (initialOpen = true) => ({
id: null,
open: true,
Alpine.data('sidebar', (initialOpen = true, initialMobileOpen = false) => ({
open: window.innerWidth >= 768
? initialOpen
: initialMobileOpen,
init() {
this.id = this.$el.id;
this.open = initialOpen;
this.$nextTick(() => {
this.$el.removeAttribute('data-uninitialized');
});
},
$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 },
'@sidebar:open.window'(e) { this.open = true },
'@sidebar:close.window'(e) { this.open = false },
'@sidebar:toggle.window'(e) { this.open = !this.open },
'@click'(e) { if (e.target === this.$el) this.open = false },
':aria-hidden'() { return !this.open },
':inert'() { return !this.open },
':inert'() { return !this.open }
},
}));
};
+58 -2
View File
@@ -3,6 +3,7 @@
@param id {string} [optional] - Unique identifier for the dropdown component.
@param trigger {string} [optional] - HTML content for the button that triggers the dropdown.
@param menu {array} [optional] - Array of menu items for the dropdown.
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
@param content_attrs {object} [optional] - Additional HTML attributes for the dropdown content div.
@@ -10,6 +11,7 @@
{% macro dropdown_menu(
id=None,
trigger=None,
menu=None,
main_attrs={},
trigger_attrs={},
content_attrs={}
@@ -20,7 +22,7 @@
@click.away="open = false"
{% if id %}id="{{ id }}"{% endif %}
{% for key, value in main_attrs %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% if trigger %}
@@ -50,8 +52,62 @@
{% endfor %}
>
<nav role="menu">
{{ caller() if caller }}
{% if menu %}
{{ render_dropdown_items(menu, id ~ "-items" if id else "items") }}
{% else %}
{{ caller() if caller }}
{% endif %}
</nav>
</div>
</div>
{% endmacro %}
{#
Renders dropdown menu items recursively.
@param items {array} - The array of items to render.
@param parent_id_prefix {string} [optional] - Prefix for generating element IDs.
#}
{% macro render_dropdown_items(items, parent_id_prefix="items") %}
{% for item in items %}
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
{% if item.type == "group" %}
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
<div
role="group"
aria-labelledby="{{ group_label_id }}"
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
{{ render_dropdown_items(item.items, item_id) if item.items }}
</div>
{% elif item.type == "separator" %}
<hr role="separator" />
{% elif item.type == "item" or not item.type %}
{% if item.url %}
<a
role="menuitem"
href="{{ item.url }}"
{% for key, value in item.attrs %}
{% if key != "url" %} {{ key }}="{{ value }}" {% endif %}
{% endfor %}
>
{{ item.label | safe }}
</a>
{% else %}
<button
role="menuitem"
type="button"
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ item.label | safe }}
</button>
{% endif %}
{% endif %}
{% endfor %}
{% endmacro %}
+44 -1
View File
@@ -16,6 +16,7 @@
id=None,
selected=None,
name=None,
items=None,
main_attrs={},
trigger_attrs={},
content_attrs={},
@@ -91,7 +92,11 @@
{{ key }}="{{ value }}"
{% endfor %}
>
{{ caller() if caller }}
{% if items %}
{{ render_select_items(items, id ~ "-items" if id else "items") }}
{% else %}
{{ caller() if caller }}
{% endif %}
</div>
</div>
{% if name is defined %}
@@ -105,4 +110,42 @@
>
{% endif %}
</div>
{% endmacro %}
{#
Renders a list of items for the select component.
@param items {array} - The array of items to render.
@param parent_id_prefix {string} [optional] - The prefix for the item id.
#}
{% macro render_select_items(items, parent_id_prefix="items") %}
{% for item in items %}
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
{% if item.type == "group" %}
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
<div
role="group"
aria-labelledby="{{ group_label_id }}"
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
{{ render_select_items(item.items, item_id) if item.items }}
</div>
{% elif item.type == "separator" %}
<hr role="separator" />
{% elif item.type == "item" or not item.type %}
<div
role="option"
data-value="{{ item.value }}"
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ item.label | safe }}
</div>
{% endif %}
{% endfor %}
{% endmacro %}
+79 -55
View File
@@ -1,42 +1,14 @@
{% macro render_menu_items(items, level=0, parent_id="") %}
<ul>
{% for item in items %}
{% set item_id = parent_id ~ loop.index %}
{% if item.type == 'item' %}
<li>
<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>
</li>
{% elif item.type == 'submenu' %}
<li>
<details {{ 'open' if item.open }} id="submenu-{{ item_id }}">
<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>
<div id="submenu-{{ item_id }}-content">
{{ render_menu_items(item.items, level + 1, item_id + '-') }}
</div>
</details>
</li>
{% endif %}
{% endfor %}
</ul>
{% endmacro %}
{#
Renders a sidebar component.
@param id {string} [optional] - Unique identifier for the sidebar component.
@param label {string} [optional] - Label for the sidebar navigation.
@param open {boolean} [optional] - Whether the sidebar is open.
@param side {string} [optional] - Side of the sidebar to display.
@param header {string} [optional] - Header content for the sidebar.
@param footer {string} [optional] - Footer content for the sidebar.
@param menu {array} [optional] - Array of menu items for the sidebar.
#}
{% macro sidebar(
id=None,
label="Sidebar navigation",
@@ -54,13 +26,14 @@
<div
{% if id %}id="{{ id }}"{% endif %}
class="sidebar {{ main_attrs.class }}"
aria-hidden="{{ 'false' if open else 'true' }}"
data-side="{{ side if side else 'left' }}"
{{ 'inert' if not open }}
data-uninitialized
data-side="{{ side if side else "left" }}"
aria-hidden="{{ "true" if not open else "false" }}"
{{ "inert" if not open }}
{% for key, value in main_attrs %}
{% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
x-data="sidebar({{ 'true' if open else 'false' }})"
x-data="sidebar({{ "true" if open else "false" }})"
x-bind="$main"
>
<nav
@@ -82,19 +55,7 @@
{% 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 %}
{{ render_sidebar_content(menu, id ~ "-content" if id else "content") }}
{% else %}
{{ caller() if caller }}
{% endif %}
@@ -111,4 +72,67 @@
{% endif %}
</nav>
</div>
{% endmacro %}
{#
Renders sidebar content recursively (groups, items, submenus, separators).
@param items {array} - The array of items to render.
@param parent_id_prefix {string} [optional] - Prefix for generating element IDs.
#}
{% macro render_sidebar_content(items, parent_id_prefix="content") %}
{% for item in items %}
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
{% if item.type == "group" %}
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
<div
role="group"
{% if item.label %}aria-labelledby="{{ group_label_id }}"{% endif %}
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
{% if item.label %}
<h3 id="{{ group_label_id }}">{{ item.label }}</h3>
{% endif %}
<ul>
{{ render_sidebar_content(item.items, item_id) if item.items }}
</ul>
</div>
{% elif item.type == "separator" %}
<hr role="separator"/>
{% elif item.type == "submenu" %}
<li>
<details
id="submenu-{{ item_id }}"
{{ "open" if item.open }}
{% for key, value in item.attrs %}
{% if key != "open" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
<summary aria-controls="submenu-{{ item_id }}-content">
{% if item.icon %}{{ item.icon | safe }}{% endif %}
{{ item.label }}
</summary>
<ul id="submenu-{{ item_id }}-content">
{{ render_sidebar_content(item.items, item_id) if item.items }}
</ul>
</details>
</li>
{% elif item.type == "item" or not item.type %}
<li>
<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>
</li>
{% endif %}
{% endfor %}
{% endmacro %}