Working on docs

This commit is contained in:
Ronan Berder
2025-06-12 18:44:17 +08:00
parent 3b0b38a4cf
commit ebf5b6e33f
20 changed files with 808 additions and 1218 deletions
+23 -35
View File
@@ -1,44 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<!-- Theme -->
<!-- Dark Mode -->
<script>
function isDark() {
return localStorage.getItem('darkMode') === 'dark' || (!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches);
}
if (isDark()) {
document.documentElement.classList.add('dark');
}
if (localStorage.getItem('themeVariant')) {
document.documentElement.classList.add(`theme-${localStorage.getItem('themeVariant')}`);
}
document.addEventListener('alpine:init', () => {
Alpine.store('darkMode', {
isDark: isDark(),
toggle() {
this.isDark = document.documentElement.classList.toggle('dark');
localStorage.setItem('darkMode', this.isDark ? 'dark' : 'light');
(function() {
try {
const storedMode = localStorage.getItem('darkMode');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (storedMode === 'dark' || (storedMode === null && prefersDark)) document.documentElement.classList.add('dark');
} catch (e) {
console.error('Could not apply dark mode from localStorage', e);
}
});
Alpine.store('themeVariant', {
name: localStorage.getItem('themeVariant'),
setThemeVariant(name) {
this.name = name;
localStorage.setItem('themeVariant', name);
document.documentElement.classList.forEach(className => {
if (className.startsWith('theme-')) {
document.documentElement.classList.remove(className);
}
});
document.documentElement.classList.add(`theme-${name}`);
}
});
});
})();
</script>
<script src="/assets/js/dark-mode.js" defer></script>
<!-- Theme variant -->
<script>
(function() {
try {
const storedTheme = localStorage.getItem('themeVariant');
if (storedTheme) document.documentElement.classList.add(`theme-${storedTheme}`);
} catch (e) {
console.error('Could not apply theme variant from localStorage', e);
}
})();
</script>
<script src="/assets/js/theme.js" defer></script>
<meta charset="utf-8">
<meta http-equiv="Content-Language" content="en">
@@ -81,6 +68,7 @@
<script src="/assets/js/toast.js" defer></script>
<script src="/assets/js/select.js" defer></script>
<script src="/assets/js/dropdown-menu.js" defer></script>
<script src="/assets/js/sidebar.js" defer></script>
<script src="/assets/js/tabs.js" defer></script>
+2 -1
View File
@@ -15,7 +15,8 @@
}
}"
>
<pre x-ref="code" class="text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-neutral-900 p-0">{{- code | escape -}}</code></pre>
<pre x-ref="code" class="text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar whitespace-pre-wrap
"><code class="language-{{ language }} !bg-neutral-900 p-0">{{- code | escape -}}</code></pre>
<button
@click="copy()"
+26 -9
View File
@@ -12,26 +12,43 @@
</button>
<select
class="select h-8 leading-none"
x-init
@change="$store.themeVariant.setThemeVariant($event.target.value)"
:value="$store.themeVariant.name"
id="theme-select"
>
<option value="">Default</option>
<option value="claude">Claude</option>
<option value="doom-64">Doom 64</option>
<option value="supabase">Supabase</option>
</select>
<script>
document.addEventListener('DOMContentLoaded', () => {
const themeSelect = document.getElementById('theme-select');
const storedTheme = localStorage.getItem('themeVariant');
if (themeSelect && storedTheme) themeSelect.value = storedTheme;
themeSelect.addEventListener('change', () => {
const newTheme = themeSelect.value;
document.documentElement.classList.forEach(c => {
if (c.startsWith('theme-')) document.documentElement.classList.remove(c);
});
if (newTheme) {
document.documentElement.classList.add(`theme-${newTheme}`);
localStorage.setItem('themeVariant', newTheme);
} else {
localStorage.removeItem('themeVariant');
}
});
});
</script>
<button
type="button"
:aria-label="$store.darkMode.isDark ? 'Switch to light mode' : 'Switch to dark mode'"
:data-tooltip="$store.darkMode.isDark ? 'Light mode' : 'Dark mode'"
aria-label="Toggle dark mode"
data-tooltip="Toggle dark mode"
data-side="bottom"
data-action="toggle-dark-mode"
class="btn-icon-outline size-8"
x-init
@click="$store.darkMode.toggle()"
>
<span x-show="$store.darkMode.isDark">{% lucide "sun" %}</span>
<span x-show="!$store.darkMode.isDark">{% lucide "moon" %}</span>
<span class="hidden dark:block">{% lucide "sun" %}</span>
<span class="block dark:hidden">{% lucide "moon" %}</span>
</button>
<a
href="https://github.com/hunvreus/basecoat"
+5 -8
View File
@@ -62,26 +62,23 @@
"hx-boost": "true",
"hx-select": "#content",
"hx-target": "#content",
"hx-swap": "outerHTML",
"data-sidebar-close-on-mobile": ""
"hx-swap": "outerHTML"
} %}
{% set icon_home %}{% lucide "house" %}{% endset %}
{% 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 icon_github %}<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>{% endset %}
{% set icon_discord %}<svg fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Discord</title><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>{% endset %}
{% set menu = [
{ type: "group", label: "Getting started", items: [
{ label: "Introduction", url: "/introduction", icon: icon_info, 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: {
"target": "_blank",
"data-sidebar-close-on-mobile": ""
}
}
{ label: "GitHub", url: "https://github.com/hunvreus/basecoat", icon: icon_github, attrs: { "target": "_blank" } },
{ label: "Discord", url: "https://basecoatui.com/chat", icon: icon_discord, attrs: { "target": "_blank" } }
]},
{ type: 'group', label: 'Components', items: [
{ label: "Accordion", url: "/components/accordion", attrs: link_attrs },
+351 -236
View File
@@ -268,6 +268,9 @@
.absolute {
position: absolute;
}
.fixed {
position: fixed;
}
.relative {
position: relative;
}
@@ -280,6 +283,9 @@
.inset-x-0 {
inset-inline: calc(var(--spacing) * 0);
}
.inset-y-0 {
inset-block: calc(var(--spacing) * 0);
}
.top-0 {
top: calc(var(--spacing) * 0);
}
@@ -289,9 +295,15 @@
.top-3 {
top: calc(var(--spacing) * 3);
}
.top-4 {
top: calc(var(--spacing) * 4);
}
.top-22 {
top: calc(var(--spacing) * 22);
}
.top-\[50\%\] {
top: 50%;
}
.right-3 {
right: calc(var(--spacing) * 3);
}
@@ -301,12 +313,18 @@
.left-2 {
left: calc(var(--spacing) * 2);
}
.left-\[50\%\] {
left: 50%;
}
.isolate {
isolation: isolate;
}
.z-10 {
z-index: 10;
}
.z-50 {
z-index: 50;
}
.col-span-2 {
grid-column: span 2 / span 2;
}
@@ -328,6 +346,9 @@
max-width: 96rem;
}
}
.-mx-6 {
margin-inline: calc(var(--spacing) * -6);
}
.mx-auto {
margin-inline: auto;
}
@@ -520,6 +541,9 @@
.max-w-\[300px\] {
max-width: 300px;
}
.max-w-\[calc\(100\%-2rem\)\] {
max-width: calc(100% - 2rem);
}
.max-w-lg {
max-width: var(--container-lg);
}
@@ -601,6 +625,9 @@
.flex-col {
flex-direction: column;
}
.flex-col-reverse {
flex-direction: column-reverse;
}
.flex-row {
flex-direction: row;
}
@@ -747,6 +774,9 @@
.rounded-xl {
border-radius: var(--radius-xl);
}
.rounded-xs {
border-radius: var(--radius-xs);
}
.border {
border-style: var(--tw-border-style);
border-width: 1px;
@@ -787,6 +817,12 @@
.bg-background {
background-color: var(--color-background);
}
.bg-black\/50 {
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);
}
}
.bg-card {
background-color: var(--color-card);
}
@@ -820,6 +856,9 @@
.p-4 {
padding: calc(var(--spacing) * 4);
}
.p-6 {
padding: calc(var(--spacing) * 6);
}
.p-10 {
padding: calc(var(--spacing) * 10);
}
@@ -1002,9 +1041,16 @@
.opacity-60 {
opacity: 60%;
}
.opacity-70 {
opacity: 70%;
}
.opacity-100 {
opacity: 100%;
}
.shadow-lg {
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-none {
--tw-shadow: 0 0 #0000;
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@@ -1037,6 +1083,11 @@
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.transition-opacity {
transition-property: opacity;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
transition-duration: var(--tw-duration, var(--default-transition-duration));
}
.transition-transform {
transition-property: transform, translate, scale, rotate;
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
@@ -1186,6 +1237,13 @@
}
}
}
.hover\:opacity-100 {
&:hover {
@media (hover: hover) {
opacity: 100%;
}
}
}
.focus\:bg-destructive\/10 {
&:focus {
background-color: var(--color-destructive);
@@ -1199,6 +1257,33 @@
color: var(--color-destructive);
}
}
.focus\:ring-2 {
&:focus {
--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);
}
}
.focus\:ring-ring {
&:focus {
--tw-ring-color: var(--color-ring);
}
}
.focus\:ring-offset-2 {
&:focus {
--tw-ring-offset-width: 2px;
--tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
}
}
.focus\:outline-hidden {
&:focus {
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
}
}
.focus-visible\:border-ring {
&:focus-visible {
border-color: var(--color-ring);
@@ -1277,6 +1362,16 @@
background-color: var(--color-green-50);
}
}
.data-\[state\=open\]\:bg-accent {
&[data-state="open"] {
background-color: var(--color-accent);
}
}
.data-\[state\=open\]\:text-muted-foreground {
&[data-state="open"] {
color: var(--color-muted-foreground);
}
}
.max-sm\:flex-col {
@media (width < 40rem) {
flex-direction: column;
@@ -1287,6 +1382,16 @@
max-width: 425px;
}
}
.sm\:max-w-lg {
@media (width >= 40rem) {
max-width: var(--container-lg);
}
}
.sm\:flex-row {
@media (width >= 40rem) {
flex-direction: row;
}
}
.sm\:gap-2\.5 {
@media (width >= 40rem) {
gap: calc(var(--spacing) * 2.5);
@@ -1366,6 +1471,16 @@
min-width: var(--container-sm);
}
}
.dark\:block {
&:is(.dark *) {
display: block;
}
}
.dark\:hidden {
&:is(.dark *) {
display: none;
}
}
.dark\:border-amber-950 {
&:is(.dark *) {
border-color: var(--color-amber-950);
@@ -2572,6 +2687,97 @@
}
}
}
@layer components {
.dropdown-menu {
[popover].popover {
padding: calc(var(--spacing) * 1);
min-width: anchor-size(width);
[role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio'] {
position: relative;
display: flex;
width: 100%;
cursor: default;
align-items: center;
gap: calc(var(--spacing) * 2);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-radius: var(--radius-sm);
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
-webkit-user-select: none;
user-select: none;
&:disabled {
pointer-events: none;
}
&:disabled {
opacity: 50%;
}
&[aria-disabled="true"] {
pointer-events: none;
}
&[aria-disabled="true"] {
opacity: 50%;
}
&[aria-hidden="true"] {
display: none;
}
& svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
& svg {
flex-shrink: 0;
}
& svg {
color: var(--color-muted-foreground);
}
&:hover {
@media (hover: hover) {
background-color: var(--color-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-accent-foreground);
}
}
&:focus-visible {
background-color: var(--color-accent);
}
&:focus-visible {
color: var(--color-accent-foreground);
}
&.active {
background-color: var(--color-accent);
color: var(--color-accent-foreground);
}
}
[role='menu'] [role='heading'] {
display: flex;
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
[role='separator'] {
margin-inline: calc(var(--spacing) * -1);
margin-block: calc(var(--spacing) * 1);
border-color: var(--color-border);
}
}
}
}
@layer components {
.form input[type='text'], .form input[type='email'], .form input[type='password'], .form input[type='number'], .form input[type='file'], .form input[type='tel'], .form input[type='url'], .form input[type='search'], .form input[type='date'], .form input[type='datetime-local'], .form input[type='month'], .form input[type='week'], .form input[type='time'], .input[type='text'], .input[type='email'], .input[type='password'], .input[type='number'], .input[type='file'], .input[type='tel'], .input[type='url'], .input[type='search'], .input[type='date'], .input[type='datetime-local'], .input[type='month'], .input[type='week'], .input[type='time'] {
display: flex;
@@ -2710,242 +2916,6 @@
}
}
}
@layer components {
div.select {
[popover].popover {
min-width: anchor-size(width);
padding: calc(var(--spacing) * 1);
[role='option'] {
position: relative;
display: flex;
width: 100%;
cursor: default;
align-items: center;
gap: calc(var(--spacing) * 2);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-radius: var(--radius-sm);
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
-webkit-user-select: none;
user-select: none;
&:disabled {
pointer-events: none;
}
&:disabled {
opacity: 50%;
}
&[aria-disabled="true"] {
pointer-events: none;
}
&[aria-disabled="true"] {
opacity: 50%;
}
&[aria-hidden="true"] {
display: none;
}
& svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
& svg {
flex-shrink: 0;
}
& svg {
color: var(--color-muted-foreground);
}
&[aria-selected='true'] {
background-image: var(--check-icon);
background-size: 0.875rem;
background-position: center right 0.5rem;
background-repeat: no-repeat;
}
}
[role='listbox'] [role='heading'] {
display: flex;
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
color: var(--color-muted-foreground);
}
[role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
display: none;
}
[role='separator'] {
margin-inline: calc(var(--spacing) * -1);
margin-block: calc(var(--spacing) * 1);
border-color: var(--color-border);
}
> header {
margin-inline: calc(var(--spacing) * -1);
margin-top: calc(var(--spacing) * -1);
margin-bottom: calc(var(--spacing) * 1);
display: flex;
height: calc(var(--spacing) * 9);
align-items: center;
gap: calc(var(--spacing) * 2);
border-bottom-style: var(--tw-border-style);
border-bottom-width: 1px;
padding-inline: calc(var(--spacing) * 3);
svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
flex-shrink: 0;
opacity: 50%;
}
input[role='combobox'] {
display: flex;
height: calc(var(--spacing) * 10);
width: 100%;
min-width: calc(var(--spacing) * 0);
flex: 1;
border-radius: var(--radius-md);
background-color: transparent;
padding-block: calc(var(--spacing) * 3);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
&::placeholder {
color: var(--color-muted-foreground);
}
&:disabled {
cursor: not-allowed;
}
&:disabled {
opacity: 50%;
}
}
}
[role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: calc(var(--spacing) * 6);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
[role='listbox'][data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before {
--tw-content: attr(data-empty);
content: var(--tw-content);
}
[role='listbox']:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before {
--tw-content: 'No results found';
content: var(--tw-content);
}
}
&:not([data-select-initialized]) [role='option']:hover, &[data-select-initialized] [role='option'].active, &[data-select-initialized] [role='option']:focus-visible {
background-color: var(--color-accent);
color: var(--color-accent-foreground);
}
}
.dropdown-menu {
[popover].popover {
padding: calc(var(--spacing) * 1);
min-width: anchor-size(width);
[role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio'] {
position: relative;
display: flex;
width: 100%;
cursor: default;
align-items: center;
gap: calc(var(--spacing) * 2);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-radius: var(--radius-sm);
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
-webkit-user-select: none;
user-select: none;
&:disabled {
pointer-events: none;
}
&:disabled {
opacity: 50%;
}
&[aria-disabled="true"] {
pointer-events: none;
}
&[aria-disabled="true"] {
opacity: 50%;
}
&[aria-hidden="true"] {
display: none;
}
& svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
& svg {
flex-shrink: 0;
}
& svg {
color: var(--color-muted-foreground);
}
&:hover {
@media (hover: hover) {
background-color: var(--color-accent);
}
}
&:hover {
@media (hover: hover) {
color: var(--color-accent-foreground);
}
}
&:focus-visible {
background-color: var(--color-accent);
}
&:focus-visible {
color: var(--color-accent-foreground);
}
&.active {
background-color: var(--color-accent);
color: var(--color-accent-foreground);
}
}
[role='menu'] [role='heading'] {
display: flex;
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-font-weight: var(--font-weight-medium);
font-weight: var(--font-weight-medium);
}
[role='separator'] {
margin-inline: calc(var(--spacing) * -1);
margin-block: calc(var(--spacing) * 1);
border-color: var(--color-border);
}
}
}
}
@layer components {
[popover].popover {
visibility: visible;
@@ -3343,6 +3313,151 @@
color: var(--color-popover-foreground);
}
}
div.select {
[popover].popover {
min-width: anchor-size(width);
padding: calc(var(--spacing) * 1);
[role='option'] {
position: relative;
display: flex;
width: 100%;
cursor: default;
align-items: center;
gap: calc(var(--spacing) * 2);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
border-radius: var(--radius-sm);
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
-webkit-user-select: none;
user-select: none;
&:disabled {
pointer-events: none;
}
&:disabled {
opacity: 50%;
}
&[aria-disabled="true"] {
pointer-events: none;
}
&[aria-disabled="true"] {
opacity: 50%;
}
&[aria-hidden="true"] {
display: none;
}
& svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
}
& svg {
flex-shrink: 0;
}
& svg {
color: var(--color-muted-foreground);
}
&[aria-selected='true'] {
background-image: var(--check-icon);
background-size: 0.875rem;
background-position: center right 0.5rem;
background-repeat: no-repeat;
}
}
[role='listbox'] [role='heading'] {
display: flex;
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1.5);
font-size: var(--text-xs);
line-height: var(--tw-leading, var(--text-xs--line-height));
color: var(--color-muted-foreground);
}
[role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
display: none;
}
[role='separator'] {
margin-inline: calc(var(--spacing) * -1);
margin-block: calc(var(--spacing) * 1);
border-color: var(--color-border);
}
> header {
margin-inline: calc(var(--spacing) * -1);
margin-top: calc(var(--spacing) * -1);
margin-bottom: calc(var(--spacing) * 1);
display: flex;
height: calc(var(--spacing) * 9);
align-items: center;
gap: calc(var(--spacing) * 2);
border-bottom-style: var(--tw-border-style);
border-bottom-width: 1px;
padding-inline: calc(var(--spacing) * 3);
svg {
width: calc(var(--spacing) * 4);
height: calc(var(--spacing) * 4);
flex-shrink: 0;
opacity: 50%;
}
input[role='combobox'] {
display: flex;
height: calc(var(--spacing) * 10);
width: 100%;
min-width: calc(var(--spacing) * 0);
flex: 1;
border-radius: var(--radius-md);
background-color: transparent;
padding-block: calc(var(--spacing) * 3);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
--tw-outline-style: none;
outline-style: none;
@media (forced-colors: active) {
outline: 2px solid transparent;
outline-offset: 2px;
}
&::placeholder {
color: var(--color-muted-foreground);
}
&:disabled {
cursor: not-allowed;
}
&:disabled {
opacity: 50%;
}
}
}
[role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: calc(var(--spacing) * 6);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
}
[role='listbox'][data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before {
--tw-content: attr(data-empty);
content: var(--tw-content);
}
[role='listbox']:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before {
--tw-content: 'No results found';
content: var(--tw-content);
}
}
[role='option']:hover, &[data-select-initialized] [role='option'].active, &[data-select-initialized] [role='option']:focus-visible {
background-color: var(--color-accent);
color: var(--color-accent-foreground);
}
}
}
.sidebar {
&:not([data-sidebar-initialized]) {
+4 -10
View File
@@ -33,18 +33,18 @@ toc:
) %}
<div role="group" aria-labelledby="account-options">
<span id="account-options" role="heading">My Account</span>
<button type="button" role="menuitem">
<button type="button" role="menuitem" onclick="console.log('clicked')">
Profile
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</button>
<button type="button" role="menuitem">
<button type="button" role="menuitem" onclick="window.open('https://google.com', '_blank')">
Billing
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
</button>
<button type="button" role="menuitem">
<a role="menuitem" href="https://google.com" target="_blank">
Settings
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
</button>
</a>
<button type="button" role="menuitem">
Keyboard shortcuts
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
@@ -69,13 +69,7 @@ toc:
{% endset %}
{% set code_script %}
<script>
{% fetchCode "src/js/dropdown-menu.js" %}
document.addEventListener('alpine:init', () => {
window.basecoat.registerDropdownMenu(Alpine);
});
</script>
{% endset %}
{{ code_preview("dropdown-menu", code_script | prettyHtml ~ code_html | prettyHtml) }}
+1
View File
@@ -41,6 +41,7 @@ toc:
{% set code_html %}
{{ select(
trigger_attrs={"class": "w-[180px]"},
is_combobox=true,
items=[
{
type: "group",
+135 -100
View File
@@ -502,6 +502,32 @@
}
}
/* Dropdown Menu */
@layer components {
.dropdown-menu {
[popover].popover {
@apply p-1;
min-width: anchor-size(width);
[role='menuitem'],
[role='menuitemcheckbox'],
[role='menuitemradio'] {
@apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none [&_svg]:shrink-0 [&_svg]:size-4 aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate;
@apply focus-visible:bg-accent focus-visible:text-accent-foreground hover:bg-accent hover:text-accent-foreground;
&.active {
@apply bg-accent text-accent-foreground;
}
}
[role='menu'] [role='heading'] {
@apply flex px-2 py-1.5 text-sm font-medium;
}
[role='separator'] {
@apply border-border -mx-1 my-1;
}
}
}
}
/* Input */
@layer components {
.form input[type='text'],
@@ -550,18 +576,69 @@
}
/* Popover */
@import "./components/popover.css";
@layer components {
[popover].popover {
@apply absolute inset-auto bg-popover text-popover-foreground overflow-x-hidden overflow-y-auto p-4 rounded-md border shadow-md z-50 visible opacity-0 scale-95 w-max transition-all transition-discrete;
/* @layer components {
.popover {
@apply relative inline-flex;
&:not([data-side]),
&[data-side='bottom'] {
@apply -translate-y-2;
}
&[data-side='top'] {
@apply translate-y-2;
}
&[data-side='left'] {
@apply translate-x-2;
}
&[data-side='right'] {
@apply -translate-x-2;
}
&:not([data-side]),
&[data-side='bottom'] {
@apply mt-1 top-[anchor(bottom)];
}
&[data-side='top'] {
@apply mb-1 bottom-[anchor(top)];
}
&[data-side='left'] {
@apply mr-1 right-[anchor(left)];
}
&[data-side='right'] {
@apply ml-1 left-[anchor(right)];
}
&:not([data-side]),
&[data-side='bottom'],
&[data-side='top'] {
&:not([data-align]),
&[data-align='start'] {
@apply left-[anchor(left)];
}
&[data-align='end'] {
@apply right-[anchor(right)];
}
&[data-align='center'] {
@apply left-[anchor(center)] -translate-x-1/2;
}
}
&[data-side='left'],
&[data-side='right'] {
&:not([data-align]),
&[data-align='start'] {
@apply top-[anchor(top)];
}
&[data-align='end'] {
@apply bottom-[anchor(bottom)];
}
&[data-align='center'] {
@apply top-[anchor(center)] -translate-y-1/2;
}
}
&:is(:popover-open) {
@apply opacity-100 scale-100 translate-x-0 translate-y-0;
> [data-popover] {
@apply absolute bg-popover text-popover-foreground overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md z-50 visible opacity-100 scale-100 min-w-full w-max transition-all;
@starting-style {
@apply opacity-0 scale-95;
&[aria-hidden='true'] {
@apply invisible opacity-0 scale-95;
&:not([data-side]),
&[data-side='bottom'] {
@apply -translate-y-2;
@@ -576,99 +653,10 @@
@apply -translate-x-2;
}
}
&:not([data-side]),
&[data-side='bottom'] {
@apply mt-1 top-full;
}
&[data-side='top'] {
@apply mb-1 bottom-full;
}
&[data-side='left'] {
@apply mr-1 right-full;
}
&[data-side='right'] {
@apply ml-1 left-full;
}
&:not([data-side]),
&[data-side='bottom'],
&[data-side='top'] {
&:not([data-align]),
&[data-align='start'] {
@apply left-0;
}
&[data-align='end'] {
@apply right-0;
}
&[data-align='center'] {
@apply left-1/2 -translate-x-1/2;
}
}
&[data-side='left'],
&[data-side='right'] {
&:not([data-align]),
&[data-align='start'] {
@apply top-0;
}
&[data-align='end'] {
@apply bottom-0;
}
&[data-align='center'] {
@apply top-1/2 -translate-y-1/2;
}
}
[role='menuitem'],
[role='menuitemcheckbox'],
[role='menuitemradio'],
[role='option'] {
@apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none [&_svg]:shrink-0 [&_svg]:size-4 aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate;
}
[role='menuitem'],
[role='menuitemcheckbox'],
[role='menuitemradio'] {
@apply focus-visible:bg-accent focus-visible:text-accent-foreground hover:bg-accent hover:text-accent-foreground;
}
[role='option'] {
@apply data-[focus]:bg-accent data-[focus]:text-accent-foreground pr-7.5;
&[aria-selected='true'] {
@apply bg-[image:var(--check-icon)] bg-no-repeat bg-position-[center_right_0.5rem] bg-size-[0.875rem];
}
}
[role='menu'] [role='heading'] {
@apply flex px-2 py-1.5 text-sm font-medium;
}
[role='listbox'] [role='heading'] {
@apply flex text-muted-foreground px-2 py-1.5 text-xs;
}
[role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
@apply hidden;
}
[role='separator'] {
@apply border-border -mx-1 my-1;
}
// Combobox
> header {
@apply flex h-9 items-center gap-2 border-b px-3 -mx-1 -mt-1 mb-1;
svg {
@apply size-4 shrink-0 opacity-50;
}
input[role='combobox'] {
@apply placeholder:text-muted-foreground flex h-10 flex-1 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50 min-w-0;
}
}
[role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply flex items-center justify-center p-6 text-sm truncate;
}
[role='listbox'][data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-[attr(data-empty)];
}
[role='listbox']:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-['No_results_found'];
}
}
}
} */
}
/* Radio */
@layer components {
@@ -743,6 +731,53 @@
@apply bg-popover text-popover-foreground;
}
}
div.select {
[popover].popover {
@apply p-1 min-w-[anchor-size(width)];
[role='option'] {
@apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none [&_svg]:shrink-0 [&_svg]:size-4 aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate;
&[aria-selected='true'] {
@apply bg-[image:var(--check-icon)] bg-no-repeat bg-position-[center_right_0.5rem] bg-size-[0.875rem];
}
}
[role='listbox'] [role='heading'] {
@apply flex text-muted-foreground px-2 py-1.5 text-xs;
}
[role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
@apply hidden;
}
[role='separator'] {
@apply border-border -mx-1 my-1;
}
> header {
@apply flex h-9 items-center gap-2 border-b px-3 -mx-1 -mt-1 mb-1;
svg {
@apply size-4 shrink-0 opacity-50;
}
input[role='combobox'] {
@apply placeholder:text-muted-foreground flex h-10 flex-1 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50 min-w-0;
}
}
[role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply flex items-center justify-center p-6 text-sm truncate;
}
[role='listbox'][data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-[attr(data-empty)];
}
[role='listbox']:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-['No_results_found'];
}
}
[role='option']:hover,
&[data-select-initialized] [role='option'].active,
&[data-select-initialized] [role='option']:focus-visible {
@apply bg-accent text-accent-foreground;
}
}
}
/* Sidebar */
-59
View File
@@ -1,59 +0,0 @@
@layer components {
.dialog {
@apply inset-y-0 opacity-0 transition-all transition-discrete;
&:is([open],:popover-open) {
@apply opacity-100;
&::backdrop {
@apply opacity-100;
}
> article {
@apply scale-100;
}
@starting-style {
@apply opacity-0;
&::backdrop {
@apply opacity-0;
}
> article {
@apply scale-95;
}
}
}
&::backdrop {
@apply bg-black/50 opacity-0 transition-all transition-discrete;
}
> article {
@apply bg-background fixed top-[50%] left-[50%] z-50 flex flex-col w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-lg border p-6 shadow-lg sm:max-w-lg max-h-[calc(100%-2rem)];
@apply transition-all scale-95;
> header {
@apply flex flex-col gap-2 text-center sm:text-left;
> h2 {
@apply text-lg leading-none font-semibold;
}
> p {
@apply text-muted-foreground text-sm;
}
}
> section {
@apply flex-1 -mx-6 px-6;
}
> footer {
@apply flex flex-col-reverse gap-2 sm:flex-row sm:justify-end;
}
> form[method='dialog'] {
@apply absolute top-4 right-4;
> button {
@apply ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4;
}
}
}
}
}
-155
View File
@@ -1,155 +0,0 @@
@layer components {
div.select {
[popover].popover {
@apply p-1 min-w-[anchor-size(width)];
[role='option'] {
@apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none [&_svg]:shrink-0 [&_svg]:size-4 aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate;
&[aria-selected='true'] {
@apply bg-[image:var(--check-icon)] bg-no-repeat bg-position-[center_right_0.5rem] bg-size-[0.875rem];
}
}
[role='listbox'] [role='heading'] {
@apply flex text-muted-foreground px-2 py-1.5 text-xs;
}
[role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
@apply hidden;
}
[role='separator'] {
@apply border-border -mx-1 my-1;
}
> header {
@apply flex h-9 items-center gap-2 border-b px-3 -mx-1 -mt-1 mb-1;
svg {
@apply size-4 shrink-0 opacity-50;
}
input[role='combobox'] {
@apply placeholder:text-muted-foreground flex h-10 flex-1 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50 min-w-0;
}
}
[role='listbox']:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply flex items-center justify-center p-6 text-sm truncate;
}
[role='listbox'][data-empty]:not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-[attr(data-empty)];
}
[role='listbox']:not([data-empty]):not(:has([data-value]:not([aria-hidden='true'])))::before {
@apply content-['No_results_found'];
}
}
&:not([data-select-initialized]) [role='option']:hover,
&[data-select-initialized] [role='option'].active,
&[data-select-initialized] [role='option']:focus-visible {
@apply bg-accent text-accent-foreground;
}
}
.dropdown-menu {
[popover].popover {
@apply p-1;
min-width: anchor-size(width);
[role='menuitem'],
[role='menuitemcheckbox'],
[role='menuitemradio'] {
@apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none [&_svg]:shrink-0 [&_svg]:size-4 aria-disabled:opacity-50 aria-disabled:pointer-events-none disabled:opacity-50 disabled:pointer-events-none w-full truncate;
@apply focus-visible:bg-accent focus-visible:text-accent-foreground hover:bg-accent hover:text-accent-foreground;
&.active {
@apply bg-accent text-accent-foreground;
}
}
[role='menu'] [role='heading'] {
@apply flex px-2 py-1.5 text-sm font-medium;
}
[role='separator'] {
@apply border-border -mx-1 my-1;
}
}
}
}
@layer components {
[popover].popover {
@apply absolute inset-auto bg-popover text-popover-foreground overflow-x-hidden overflow-y-auto p-4 rounded-md border shadow-md z-50 visible opacity-0 scale-95 w-max transition-all transition-discrete;
&:not([data-side]),
&[data-side='bottom'] {
@apply -translate-y-2;
}
&[data-side='top'] {
@apply translate-y-2;
}
&[data-side='left'] {
@apply translate-x-2;
}
&[data-side='right'] {
@apply -translate-x-2;
}
&:not([data-side]),
&[data-side='bottom'] {
@apply mt-1 top-[anchor(bottom)];
}
&[data-side='top'] {
@apply mb-1 bottom-[anchor(top)];
}
&[data-side='left'] {
@apply mr-1 right-[anchor(left)];
}
&[data-side='right'] {
@apply ml-1 left-[anchor(right)];
}
&:not([data-side]),
&[data-side='bottom'],
&[data-side='top'] {
&:not([data-align]),
&[data-align='start'] {
@apply left-[anchor(left)];
}
&[data-align='end'] {
@apply right-[anchor(right)];
}
&[data-align='center'] {
@apply left-[anchor(center)] -translate-x-1/2;
}
}
&[data-side='left'],
&[data-side='right'] {
&:not([data-align]),
&[data-align='start'] {
@apply top-[anchor(top)];
}
&[data-align='end'] {
@apply bottom-[anchor(bottom)];
}
&[data-align='center'] {
@apply top-[anchor(center)] -translate-y-1/2;
}
}
&:is(:popover-open) {
@apply opacity-100 scale-100 translate-x-0 translate-y-0;
@starting-style {
@apply opacity-0 scale-95;
&:not([data-side]),
&[data-side='bottom'] {
@apply -translate-y-2;
}
&[data-side='top'] {
@apply translate-y-2;
}
&[data-side='left'] {
@apply translate-x-2;
}
&[data-side='right'] {
@apply -translate-x-2;
}
}
}
}
}
-54
View File
@@ -1,54 +0,0 @@
window.basecoat = window.basecoat || {};
window.basecoat.registerDialog = function(Alpine) {
if (Alpine.components && Alpine.components.dialog) return;
Alpine.data('dialog', (initialOpen = false, initialCloseOnOverlayClick = true) => ({
id: null,
open: false,
closeOnOverlayClick: true,
init() {
this.id = this.$el.id;
if (!this.id) {
console.warn('Dialog component initialized without an `id`. This may cause issues with event targeting and accessibility.');
}
this.open = initialOpen;
this.closeOnOverlayClick = initialCloseOnOverlayClick;
},
show() {
if (!this.open) {
this.open = true;
this.$nextTick(() => {
this.$el.dispatchEvent(new CustomEvent('dialog:opened', { bubbles: true, detail: { id: this.id } }));
setTimeout(() => {
const focusTarget = this.$refs.focusOnOpen || this.$el.querySelector('[role="dialog"]');
if (focusTarget) focusTarget.focus();
}, 50);
});
}
},
hide() {
if (this.open) {
this.open = false;
this.$nextTick(() => {
this.$el.dispatchEvent(new CustomEvent('dialog:closed', { bubbles: true, detail: { id: this.id } }));
});
}
},
$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() },
},
$trigger: {
'@click'() { this.show() },
':aria-expanded'() { return this.open }
},
$content: {
':inert'() { return !this.open },
'@click.self'() { if (this.closeOnOverlayClick) this.hide() },
'x-cloak': ''
}
}));
};
-91
View File
@@ -1,91 +0,0 @@
window.basecoat = window.basecoat || {};
window.basecoat.registerDropdownMenu = function(Alpine) {
if (Alpine.components && Alpine.components.dropdownMenu) return;
Alpine.data('dropdownMenu', () => ({
open: false,
focusedIndex: null,
menuItems: [],
init() {
this.$nextTick(() => {
this.menuItems = Array.from(this.$el.querySelectorAll('[role=menuitem]:not([disabled]),[role=menuitemcheckbox]:not([disabled]),[role=menuitemradio]:not([disabled])'));
});
},
focusMenuitem() {
if (this.menuItems.length === 0) return;
if (this.focusedIndex >= this.menuItems.length) {
this.focusedIndex = this.menuItems.length - 1;
} else if (this.focusedIndex < 0 || this.focusedIndex === null) {
this.focusedIndex = 0;
}
this.menuItems.forEach(item => item.blur());
this.menuItems[this.focusedIndex].focus();
},
moveMenuitemFocus(delta) {
if (this.menuItems.length === 0) return;
let wasOpen = this.open;
if (!this.open) {
this.open = true;
this.focusedIndex = 0;
} else {
this.focusedIndex = this.focusedIndex === null
? 0
: this.focusedIndex + delta;
}
if (wasOpen) {
this.focusMenuitem();
} else {
setTimeout(() => this.focusMenuitem(), 50);
}
},
handleMenuitemClick(event) {
const menuitem = event.target.closest('[role=menuitem],[role=menuitemcheckbox],[role=menuitemradio]');
if (menuitem && menuitem.getAttribute('aria-disabled') !== 'true' && !menuitem.disabled) {
this.$nextTick(() => {
this.$refs.trigger.focus();
this.open = false;
});
}
},
handleMenuitemMousemove(event) {
const menuitem = event.target.closest('[role=menuitem],[role=menuitemcheckbox],[role=menuitemradio]');
if (menuitem && menuitem.getAttribute('aria-disabled') !== 'true' && !menuitem.disabled) {
this.focusedIndex = this.menuItems.indexOf(menuitem);
this.focusMenuitem();
}
},
$trigger: {
'@click'() { this.open = !this.open },
'@keydown.escape.prevent'() {
this.open = false;
this.$refs.trigger.focus();
},
'@keydown.down.prevent'() { this.moveMenuitemFocus(+1); },
'@keydown.up.prevent'() { this.moveMenuitemFocus(-1); },
'@keydown.home.prevent'() { this.focusMenuitem(0) },
'@keydown.end.prevent'() { this.focusMenuitem(this.menuItems.length - 1) },
'@keydown.enter.prevent'() { this.open = !this.open },
':aria-expanded'() { return this.open },
'x-ref': 'trigger'
},
$content: {
'@click'(e) { this.handleMenuitemClick(e) },
'@keydown.escape.prevent'() {
this.open = false;
this.$refs.trigger.focus();
},
'@keydown.down.prevent'() { this.moveMenuitemFocus(+1); },
'@keydown.up.prevent'() { this.moveMenuitemFocus(-1); },
'@keydown.home.prevent'() { this.focusMenuitem(0) },
'@keydown.end.prevent'() { this.focusMenuitem(this.menuItems.length - 1) },
'@mouseover'(e) { this.handleMenuitemMousemove(e) },
':aria-hidden'() { return !this.open },
'x-cloak': ''
},
}));
};
-26
View File
@@ -1,26 +0,0 @@
window.basecoat = window.basecoat || {};
window.basecoat.registerPopover = function(Alpine) {
if (Alpine.components && Alpine.components.popover) return;
Alpine.data('popover', () => ({
open: false,
$trigger: {
'@click'() { this.open = !this.open },
'@keydown.escape.prevent'() {
this.open = false;
this.$refs.trigger.focus();
},
':aria-expanded'() { return this.open },
'x-ref': 'trigger'
},
$content: {
'@keydown.escape.prevent'() {
this.open = false;
this.$refs.trigger.focus();
},
':aria-hidden'() { return !this.open },
'x-cloak': ''
},
}));
};
-156
View File
@@ -1,156 +0,0 @@
window.basecoat = window.basecoat || {};
window.basecoat.registerSelect = function(Alpine) {
if (Alpine.components && Alpine.components.select) return;
Alpine.data('select', (initialValue = null) => ({
open: false,
options: [],
disabledOptions: [],
focusedIndex: null,
selectedLabel: null,
selectedValue: null,
query: '',
init() {
this.$nextTick(() => {
this.options = Array.from(this.$el.querySelectorAll('[role=option]:not([aria-disabled])'));
this.disabledOptions = Array.from(this.$el.querySelectorAll('[role=option][aria-disabled=true]'));
if (this.options.length === 0) return;
if (initialValue) {
const option = this.options.find(opt => opt.getAttribute('data-value') === initialValue);
this.selectOption(option, false);
this.focusedIndex = this.options.indexOf(option);
} else {
this.selectOption(this.options[0], false);
}
});
},
focusOption() {
if (this.options.length === 0) return;
if (this.focusedIndex >= this.options.length) {
this.focusedIndex = this.options.length - 1;
} else if (this.focusedIndex < 0 || this.focusedIndex === null) {
this.focusedIndex = 0;
}
this.options.forEach(opt => opt.removeAttribute('data-focus'));
const option = this.options[this.focusedIndex];
option.setAttribute('data-focus', '');
option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
},
focusOnSelectedOption() {
if (this.options.length === 0 || this.selectedValue === null) return;
const option = this.options.find(opt => opt.getAttribute('data-value') === this.selectedValue);
if (!option) return;
this.focusedIndex = this.options.indexOf(option);
this.focusOption();
},
moveOptionFocus(delta) {
if (this.options.length === 0) return;
if (!this.open) {
this.open = true;
this.focusOnSelectedOption();
} else {
this.focusedIndex = this.focusedIndex === null
? 0
: this.focusedIndex + delta;
}
this.focusOption();
},
handleOptionClick(event) {
const option = event.target.closest('[role=option]');
if (option && option.getAttribute('aria-disabled') !== 'true') {
this.selectOption(option);
this.open = false;
this.$nextTick(() => this.$refs.trigger.focus());
}
},
handleOptionMousemove(event) {
const option = event.target.closest('[role=option]');
if (option && option.getAttribute('aria-disabled') !== 'true') {
this.focusedIndex = this.options.indexOf(option);
this.focusOption();
}
},
handleOptionEnter(event) {
this.selectOption(this.options[this.focusedIndex]);
this.open = !this.open;
},
selectValue(value, dispatch = true) {
if (this.options.length === 0 || value == null) return;
const option = this.options.find(opt => opt.getAttribute('data-value') === String(value));
if (option) {
this.selectOption(option, dispatch);
}
},
selectOption(option, dispatch = true) {
if (this.options.length === 0 || !option || option.disabled || option.getAttribute('data-value') == null) {
return;
}
this.options.forEach(opt => {
opt.setAttribute('aria-selected', opt === option);
});
this.selectedLabel = option.innerHTML;
this.selectedValue = option.getAttribute('data-value');
if (dispatch) {
this.$dispatch('select:change', {
value: this.selectedValue,
label: this.selectedLabel
});
this.$dispatch('change');
}
},
filterOptions(query) {
if (query.length > 0) {
this.disabledOptions.forEach(opt => { opt.setAttribute('aria-hidden', 'true'); });
} else {
this.disabledOptions.forEach(opt => { opt.removeAttribute('aria-hidden'); });
}
this.options.forEach(opt => {
opt.removeAttribute('aria-hidden');
if (opt.getAttribute('data-value') != null && !opt.innerHTML.toLowerCase().includes(query.toLowerCase())) {
opt.setAttribute('aria-hidden', 'true');
}
});
},
$trigger: {
'@click'() { this.open = !this.open; this.focusOnSelectedOption(); },
'@keydown.escape.prevent'() {
this.open = false;
this.$refs.trigger.focus();
},
'@keydown.down.prevent'() { this.moveOptionFocus(+1); },
'@keydown.up.prevent'() { this.moveOptionFocus(-1); },
'@keydown.home.prevent'() { this.focusOption(0) },
'@keydown.end.prevent'() { this.focusOption(this.options.length - 1) },
'@keydown.enter.prevent'() {
if (this.open) this.handleOptionEnter();
else this.open = true;
},
':aria-expanded'() { return this.open },
'x-ref': 'trigger'
},
$content: {
'@click'(e) { this.handleOptionClick(e) },
'@mouseover'(e) { this.handleOptionMousemove(e) },
':aria-hidden'() { return !this.open },
'x-cloak': ''
},
$filter: {
'@input'(e) { this.filterOptions(e.target.value) },
'@keydown.escape.prevent'() {
this.open = false;
this.$refs.trigger.focus();
},
'@keydown.down.prevent'() { this.moveOptionFocus(+1); },
'@keydown.up.prevent'() { this.moveOptionFocus(-1); },
'@keydown.home.prevent'() { this.focusOption(0) },
'@keydown.end.prevent'() { this.focusOption(this.options.length - 1) },
'@keydown.enter.prevent'() { this.handleOptionEnter() },
}
}));
};
-25
View File
@@ -1,25 +0,0 @@
window.basecoat = window.basecoat || {};
window.basecoat.registerSidebar= function(Alpine) {
if (Alpine.components && Alpine.components.sidebar) return;
Alpine.data('sidebar', (initialOpen = true, initialMobileOpen = false) => ({
open: window.innerWidth >= 768
? initialOpen
: initialMobileOpen,
init() {
this.$nextTick(() => {
this.$el.removeAttribute('data-uninitialized');
});
},
$main: {
'@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 }
},
}));
};
-64
View File
@@ -1,64 +0,0 @@
window.basecoat = window.basecoat || {};
window.basecoat.registerTabs = function(Alpine) {
if (Alpine.components && Alpine.components.tabs) return;
Alpine.data('tabs', (initialTabIndex = 0) => ({
activeTabIndex: initialTabIndex,
tabs: [],
panels: [],
init() {
this.$nextTick(() => {
this.tabs = Array.from(this.$el.querySelectorAll(':scope > [role=tablist] [role=tab]:not([disabled])'));
this.panels = Array.from(this.$el.querySelectorAll(':scope > [role=tabpanel]'));
if (this.tabs.length > 0) {
this.selectTab(this.tabs[initialTabIndex], false);
}
});
},
nextTab() {
if (this.tabs.length === 0) return;
let newIndex = (this.activeTabIndex + 1) % this.tabs.length;
this.selectTab(this.tabs[newIndex]);
},
prevTab() {
if (this.tabs.length === 0) return;
let newIndex = (this.activeTabIndex - 1 + this.tabs.length) % this.tabs.length;
this.selectTab(this.tabs[newIndex]);
},
selectTab(tab, focus = true) {
if (!tab || this.tabs.length === 0) return;
this.tabs.forEach((t, index) => {
const isSelected = t === tab;
t.setAttribute('aria-selected', isSelected);
t.setAttribute('tabindex', isSelected ? '0' : '-1');
if (isSelected) {
this.activeTabIndex = index;
this.activeTab = t;
if (focus) {
t.focus();
}
}
});
const panelId = tab.getAttribute('aria-controls');
if (!panelId) return;
this.panels.forEach(panel => {
panel.hidden = (panel.getAttribute('id') !== panelId);
});
},
$tablist: {
['@click'](event) {
const clickedTab = event.target.closest('[role=tab]');
if (clickedTab) {
this.selectTab(clickedTab);
}
},
['@keydown.arrow-right.prevent']() { this.nextTab() },
['@keydown.arrow-left.prevent']() { this.prevTab() },
},
}));
};
-75
View File
@@ -1,75 +0,0 @@
window.basecoat = window.basecoat || {};
window.basecoat.registerToast = function(Alpine) {
if (Alpine.components && Alpine.components.toast) return;
Alpine.store('toaster', { isPaused: false });
Alpine.data('toast', (config={}) => ({
config: config,
open: false,
timeoutDuration: null,
timeoutId: null,
init() {
if (config.duration !== -1) {
this.timeoutDuration = config.duration || (config.category === 'error' ? 5000 : 3000);
this.timeoutId = setTimeout(() => { this.close() }, this.timeoutDuration);
}
this.open = true;
this.$watch('$store.toaster.isPaused', (isPaused) => {
if (!this.open) return;
if (isPaused) {
this.pauseTimeout();
} else {
this.resumeTimeout();
}
});
},
pauseTimeout() {
clearTimeout(this.timeoutId);
this.timeoutId = null;
},
resumeTimeout(index) {
if (this.open && this.timeoutId === null) {
this.timeoutId = setTimeout(() => { this.close() }, this.timeoutDuration);
}
},
close() {
this.pauseTimeout();
this.open = false;
this.$el.blur();
},
executeAction(actionString) {
if (actionString) {
Alpine.evaluate(this.$el, actionString);
}
},
$toastBindings: {
['@mouseenter']() { this.$store.toaster.isPaused = true },
['@mouseleave']() { this.$store.toaster.isPaused = false },
['@keydown.escape.prevent']() { this.close() },
[':aria-hidden']() { return !this.open }
},
}));
Alpine.magic('toast', (el) => (config, toasterId='toaster') => {
const toaster = document.getElementById(toasterId);
const template = document.getElementById('toast-template');
if (!toaster) {
console.error(`Toaster container with id #${toasterId} not found.`);
return;
}
if (!template) {
console.error('Toast template with id #toast-template not found.');
return;
}
const clone = template.content.firstElementChild.cloneNode(true);
clone.setAttribute('x-data', `toast(${JSON.stringify(config)})`);
clone.removeAttribute('id');
toaster.appendChild(clone);
});
};
+114 -81
View File
@@ -1,91 +1,124 @@
window.basecoat = window.basecoat || {};
window.basecoat.registerDropdownMenu = function(Alpine) {
if (Alpine.components && Alpine.components.dropdownMenu) return;
(() => {
const initDropdownMenu = (dropdownMenuComponent) => {
const trigger = dropdownMenuComponent.querySelector('[popovertarget]');
const popover = dropdownMenuComponent.querySelector('[popover]');
const menu = popover?.querySelector('[role="menu"]');
if (!trigger || !popover || !menu) return;
Alpine.data('dropdownMenu', () => ({
open: false,
focusedIndex: null,
menuItems: [],
let menuItems = [];
let activeIndex = -1;
init() {
this.$nextTick(() => {
this.menuItems = Array.from(this.$el.querySelectorAll('[role=menuitem]:not([disabled]),[role=menuitemcheckbox]:not([disabled]),[role=menuitemradio]:not([disabled])'));
});
},
focusMenuitem() {
if (this.menuItems.length === 0) return;
if (this.focusedIndex >= this.menuItems.length) {
this.focusedIndex = this.menuItems.length - 1;
} else if (this.focusedIndex < 0 || this.focusedIndex === null) {
this.focusedIndex = 0;
const setActiveItem = (index) => {
if (activeIndex > -1 && menuItems[activeIndex]) {
menuItems[activeIndex].classList.remove('active');
}
this.menuItems.forEach(item => item.blur());
this.menuItems[this.focusedIndex].focus();
},
moveMenuitemFocus(delta) {
if (this.menuItems.length === 0) return;
let wasOpen = this.open;
if (!this.open) {
this.open = true;
this.focusedIndex = 0;
activeIndex = index;
if (activeIndex > -1 && menuItems[activeIndex]) {
const activeItem = menuItems[activeIndex];
activeItem.classList.add('active');
trigger.setAttribute('aria-activedescendant', activeItem.id);
activeItem.scrollIntoView({ block: 'nearest' });
} else {
this.focusedIndex = this.focusedIndex === null
? 0
: this.focusedIndex + delta;
trigger.removeAttribute('aria-activedescendant');
}
};
const handleKeyDown = (e) => {
// If the menu isn't open, we only care about ArrowUp/Down/Enter/Space to open it.
if (!popover.matches(':popover-open')) {
if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
e.preventDefault();
trigger.click();
}
return;
}
if (wasOpen) {
this.focusMenuitem();
} else {
setTimeout(() => this.focusMenuitem(), 50);
// If the menu IS open, we handle all navigation.
let nextIndex = activeIndex;
if (menuItems.length === 0) return;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
if (activeIndex < menuItems.length - 1) {
nextIndex = activeIndex + 1;
}
break;
case 'ArrowUp':
e.preventDefault();
if (activeIndex > 0) {
nextIndex = activeIndex - 1;
}
break;
case 'Home':
e.preventDefault();
nextIndex = 0;
break;
case 'End':
e.preventDefault();
nextIndex = menuItems.length - 1;
break;
case 'Enter':
case ' ':
e.preventDefault();
menuItems[activeIndex]?.click();
break;
}
},
handleMenuitemClick(event) {
const menuitem = event.target.closest('[role=menuitem],[role=menuitemcheckbox],[role=menuitemradio]');
if (menuitem && menuitem.getAttribute('aria-disabled') !== 'true' && !menuitem.disabled) {
this.$nextTick(() => {
this.$refs.trigger.focus();
this.open = false;
if (nextIndex !== activeIndex) {
setActiveItem(nextIndex);
}
};
trigger.addEventListener('keydown', handleKeyDown);
popover.addEventListener('toggle', (e) => {
trigger.setAttribute('aria-expanded', e.newState === 'open');
if (e.newState === 'open') {
menuItems = Array.from(menu.querySelectorAll('[role^="menuitem"]:not([disabled])'));
// Ensure all menu items have IDs for aria-activedescendant
menuItems.forEach((item, index) => {
if (!item.id) item.id = `${menu.id}-item-${index}`;
});
setActiveItem(0);
} else {
setActiveItem(-1);
}
},
handleMenuitemMousemove(event) {
const menuitem = event.target.closest('[role=menuitem],[role=menuitemcheckbox],[role=menuitemradio]');
if (menuitem && menuitem.getAttribute('aria-disabled') !== 'true' && !menuitem.disabled) {
this.focusedIndex = this.menuItems.indexOf(menuitem);
this.focusMenuitem();
}
},
});
$trigger: {
'@click'() { this.open = !this.open },
'@keydown.escape.prevent'() {
this.open = false;
this.$refs.trigger.focus();
},
'@keydown.down.prevent'() { this.moveMenuitemFocus(+1); },
'@keydown.up.prevent'() { this.moveMenuitemFocus(-1); },
'@keydown.home.prevent'() { this.focusMenuitem(0) },
'@keydown.end.prevent'() { this.focusMenuitem(this.menuItems.length - 1) },
'@keydown.enter.prevent'() { this.open = !this.open },
':aria-expanded'() { return this.open },
'x-ref': 'trigger'
},
$content: {
'@click'(e) { this.handleMenuitemClick(e) },
'@keydown.escape.prevent'() {
this.open = false;
this.$refs.trigger.focus();
},
'@keydown.down.prevent'() { this.moveMenuitemFocus(+1); },
'@keydown.up.prevent'() { this.moveMenuitemFocus(-1); },
'@keydown.home.prevent'() { this.focusMenuitem(0) },
'@keydown.end.prevent'() { this.focusMenuitem(this.menuItems.length - 1) },
'@mouseover'(e) { this.handleMenuitemMousemove(e) },
':aria-hidden'() { return !this.open },
'x-cloak': ''
},
}));
};
menu.addEventListener('click', (e) => {
if (e.target.closest('[role^="menuitem"]')) {
popover.hidePopover();
}
});
menu.addEventListener('mouseover', (e) => {
const item = e.target.closest('[role^="menuitem"]:not([disabled])');
if (item) {
const index = menuItems.indexOf(item);
if (index > -1 && index !== activeIndex) {
setActiveItem(index);
}
}
});
dropdownMenuComponent.dataset.dropdownMenuInitialized = true;
};
document.querySelectorAll('.dropdown-menu:not([data-dropdown-menu-initialized])').forEach(initDropdownMenu);
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.matches('.dropdown-menu:not([data-dropdown-menu-initialized])')) {
initDropdownMenu(node);
}
node.querySelectorAll('.dropdown-menu:not([data-dropdown-menu-initialized])').forEach(initDropdownMenu);
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
})();
+133 -31
View File
@@ -1,13 +1,15 @@
(() => {
const initSelect = (select) => {
const trigger = select.querySelector(':scope > [popovertarget]');
const initSelect = (selectComponent) => {
const trigger = selectComponent.querySelector(':scope > [popovertarget]');
const selectedValue = trigger.querySelector(':scope > span');
const popover = select.querySelector(':scope > [popover]');
const popover = selectComponent.querySelector(':scope > [popover]');
const listbox = popover.querySelector('[role="listbox"]');
const input = select.querySelector(':scope > input[type="hidden"]');
const input = selectComponent.querySelector(':scope > input[type="hidden"]');
const filter = selectComponent.querySelector('header input[type="text"]');
if (!trigger || !popover || !listbox || !input) return;
const options = Array.from(listbox.querySelectorAll('[role="option"]'));
let visibleOptions = [...options];
let activeIndex = -1;
const updateValue = (option) => {
@@ -30,49 +32,150 @@
popover.hidePopover();
};
if (filter) {
const filterOptions = () => {
const searchTerm = filter.value.trim().toLowerCase();
if (activeIndex > -1) {
options[activeIndex].classList.remove('active');
trigger.removeAttribute('aria-activedescendant');
activeIndex = -1;
}
visibleOptions = [];
options.forEach(option => {
const optionText = (option.dataset.label || option.textContent).trim().toLowerCase();
const matches = optionText.includes(searchTerm);
option.setAttribute('aria-hidden', String(!matches));
if (matches) {
visibleOptions.push(option);
}
});
};
filter.addEventListener('input', filterOptions);
}
let initialOption = options.find(opt => input.value && opt.dataset.value === input.value);
if (!initialOption && options.length > 0) initialOption = options[0];
updateValue(initialOption);
trigger.addEventListener('keydown', (e) => {
if (!popover.matches(':popover-open')) return;
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
if (activeIndex > -1) options[activeIndex]?.classList.remove('active');
if (e.key === 'ArrowDown') activeIndex = (activeIndex + 1) % options.length;
else activeIndex = (activeIndex - 1 + options.length) % options.length;
const activeOption = options[activeIndex];
if (activeOption) {
activeOption.classList.add('active');
if (activeOption.id) {
trigger.setAttribute('aria-activedescendant', activeOption.id);
}
activeOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
}
} else if (e.key === 'Enter') {
e.preventDefault();
selectOption(options[activeIndex]);
const handleKeyNavigation = (e) => {
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Home', 'End'].includes(e.key)) {
return;
}
});
if (!popover.matches(':popover-open')) {
if (e.currentTarget === trigger && e.key !== 'Enter') {
e.preventDefault();
trigger.click();
}
return;
}
e.preventDefault();
if (e.key === 'Enter') {
if (activeIndex > -1) {
selectOption(options[activeIndex]);
}
return;
}
if (visibleOptions.length === 0) return;
const currentVisibleIndex = activeIndex > -1 ? visibleOptions.indexOf(options[activeIndex]) : -1;
let nextVisibleIndex = currentVisibleIndex;
switch (e.key) {
case 'ArrowDown':
if (currentVisibleIndex < visibleOptions.length - 1) {
nextVisibleIndex = currentVisibleIndex + 1;
}
break;
case 'ArrowUp':
if (currentVisibleIndex > 0) {
nextVisibleIndex = currentVisibleIndex - 1;
} else if (currentVisibleIndex === -1) {
nextVisibleIndex = 0; // Start from top if nothing is active
}
break;
case 'Home':
nextVisibleIndex = 0;
break;
case 'End':
nextVisibleIndex = visibleOptions.length - 1;
break;
}
if (nextVisibleIndex !== currentVisibleIndex) {
if (currentVisibleIndex > -1) {
visibleOptions[currentVisibleIndex].classList.remove('active');
}
const newActiveOption = visibleOptions[nextVisibleIndex];
newActiveOption.classList.add('active');
activeIndex = options.indexOf(newActiveOption);
if (newActiveOption.id) {
trigger.setAttribute('aria-activedescendant', newActiveOption.id);
}
newActiveOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
}
};
trigger.addEventListener('keydown', handleKeyNavigation);
if (filter) {
filter.addEventListener('keydown', handleKeyNavigation);
}
listbox.addEventListener('click', (e) => {
const clickedOption = e.target.closest('[role="option"]');
if (clickedOption) selectOption(clickedOption);
if (clickedOption) {
selectOption(clickedOption);
}
});
popover.addEventListener('toggle', (e) => {
if (e.newState === 'closed') {
if (e.newState === 'open') {
if (filter) filter.focus();
const selectedOption = listbox.querySelector('[role="option"][aria-selected="true"]');
let startingOption = null;
if (selectedOption && visibleOptions.includes(selectedOption)) {
startingOption = selectedOption;
} else if (visibleOptions.length > 0) {
startingOption = visibleOptions[0];
}
if (activeIndex > -1) options[activeIndex]?.classList.remove('active');
if (startingOption) {
activeIndex = options.indexOf(startingOption);
startingOption.classList.add('active');
if (startingOption.id) {
trigger.setAttribute('aria-activedescendant', startingOption.id);
}
startingOption.scrollIntoView({ block: 'nearest' });
} else {
activeIndex = -1;
}
} else if (e.newState === 'closed') {
if (filter) {
filter.value = '';
visibleOptions = [...options];
options.forEach(opt => opt.setAttribute('aria-hidden', 'false'));
}
trigger.removeAttribute('aria-activedescendant');
if (activeIndex > -1) options[activeIndex]?.classList.remove('active');
activeIndex = -1;
}
});
select.dataset.selectInitialized = true;
selectComponent.dataset.selectInitialized = true;
};
document.querySelectorAll('div.select:not([data-select-initialized])').forEach(initSelect);
@@ -89,7 +192,6 @@
});
});
});
// Start observing the whole document
observer.observe(document.body, { childList: true, subtree: true });
})();
+14 -2
View File
@@ -13,13 +13,18 @@
trigger,
items=None,
trigger_attrs={},
popover_attrs={}
popover_attrs={},
menu_attrs={}
) %}
{% set id = id or ("dropdown-menu-" + (range(100000, 999999) | random | string)) %}
<div class="dropdown-menu">
<button
type="button"
id="{{ id }}-trigger"
popovertarget="{{ id }}"
aria-haspopup="menu"
aria-controls="{{ id }}-menu"
{% for key, value in trigger_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
@@ -34,7 +39,14 @@
{% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
<nav role="menu">
<nav
role="menu"
id="{{ id }}-menu"
aria-labelledby="{{ id }}-trigger"
{% for key, value in menu_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
{% if items %}
{{ render_dropdown_items(items, id ~ "-items" if id else "items") }}
{% else %}