mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Working on docs
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
@@ -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()"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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]) {
|
||||
|
||||
@@ -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) }}
|
||||
|
||||
@@ -41,6 +41,7 @@ toc:
|
||||
{% set code_html %}
|
||||
{{ select(
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
is_combobox=true,
|
||||
items=[
|
||||
{
|
||||
type: "group",
|
||||
|
||||
+135
-100
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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': ''
|
||||
}
|
||||
}));
|
||||
};
|
||||
@@ -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': ''
|
||||
},
|
||||
}));
|
||||
};
|
||||
@@ -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': ''
|
||||
},
|
||||
}));
|
||||
};
|
||||
@@ -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() },
|
||||
}
|
||||
}));
|
||||
};
|
||||
@@ -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 }
|
||||
},
|
||||
}));
|
||||
};
|
||||
@@ -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() },
|
||||
},
|
||||
}));
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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 });
|
||||
})();
|
||||
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user