mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Getting ready to clean up the package and release 0.2.0
This commit is contained in:
@@ -59,6 +59,7 @@
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
{# JS #}
|
||||
<script src="/assets/js/dropdown-menu.js" defer></script>
|
||||
<script src="/assets/js/popover.js" defer></script>
|
||||
<script src="/assets/js/select.js" defer></script>
|
||||
<script src="/assets/js/sidebar.js" defer></script>
|
||||
<script src="/assets/js/tabs.js" defer></script>
|
||||
@@ -71,17 +72,17 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/django.min.js"></script>
|
||||
<script>
|
||||
const applyHighlighting = () => { if (window.hljs) {
|
||||
hljs.highlightAll();
|
||||
document.querySelectorAll('code.highlight').forEach((block) => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
}};
|
||||
const highlight = () => {
|
||||
if (!window.hljs) return;
|
||||
document
|
||||
.querySelectorAll('code:not([data-highlighted])')
|
||||
.forEach(el => hljs.highlightElement(el));
|
||||
};
|
||||
|
||||
if (!window._hljsInitialized) {
|
||||
window._hljsInitialized = true;
|
||||
document.addEventListener('DOMContentLoaded', applyHighlighting);
|
||||
document.addEventListener('htmx:afterSwap', applyHighlighting);
|
||||
if (!window._hljsInit) {
|
||||
window._hljsInit = true;
|
||||
document.addEventListener('DOMContentLoaded', highlight);
|
||||
document.addEventListener('htmx:afterSwap', highlight);
|
||||
}
|
||||
</script>
|
||||
{% if head %}{{ head | safe }}{% endif %}
|
||||
|
||||
@@ -20,8 +20,7 @@
|
||||
|
||||
{{ tabs(
|
||||
tabsets=tabsets,
|
||||
default_tab_index=(2 if default_code else 1),
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
default_tab_index=(2 if default_code else 1)
|
||||
) }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="flex flex-wrap items-start gap-4">
|
||||
{% set options_frameworks = ["Next.js", "SvelteKit", "Nuxt.js", "Remix", "Astro"] %}
|
||||
{% call select(
|
||||
content_attrs={"class": "w-48"},
|
||||
popover_attrs={"class": "w-48"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true
|
||||
) %}
|
||||
@@ -56,7 +56,7 @@
|
||||
}
|
||||
] %}
|
||||
{% call select(
|
||||
content_attrs={"class": "w-72"},
|
||||
popover_attrs={"class": "w-72"},
|
||||
listbox_attrs={"data-empty": "No timezone found."},
|
||||
is_combobox=true
|
||||
) %}
|
||||
|
||||
@@ -17,10 +17,9 @@
|
||||
id="demo-dialog-edit-profile",
|
||||
title="Edit profile",
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
main_attrs={"x-ref": "myDialog"},
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
@@ -36,17 +35,16 @@
|
||||
{% endcall %}
|
||||
|
||||
{% set footer %}
|
||||
<button class="btn-outline" @click="open = false">Close</button>
|
||||
<button class="btn-outline" onclick="this.closest('dialog').close()">Close</button>
|
||||
{% endset %}
|
||||
{% call dialog(
|
||||
id="dialog-example",
|
||||
title="Scrollable Content",
|
||||
description="This is a dialog with scrollable content.",
|
||||
main_attrs={"x-ref": "myDialog"},
|
||||
trigger="Scrollable Content",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
content_body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm">
|
||||
@@ -62,15 +60,6 @@
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
<button
|
||||
x-init
|
||||
@click="$dispatch('dialog:open', { id: 'dialog-example' })"
|
||||
type="button"
|
||||
class="btn-outline"
|
||||
>
|
||||
Trigger dialog:open
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -13,68 +13,66 @@
|
||||
id="dropdown-menu-default",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"}
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">My Account</span>
|
||||
<button type="button" role="menuitem">
|
||||
<div role="presentation" id="account-options">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Keyboard shortcuts
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">GitHub</button>
|
||||
<button type="button" role="menuitem">Support</button>
|
||||
<button type="button" role="menuitem" disabled>API</button>
|
||||
<div role="menuitem">GitHub</div>
|
||||
<div role="menuitem">Support</div>
|
||||
<div role="menuitem" disabled>API</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu-checkboxes",
|
||||
trigger="Checkboxes",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">Account Options</span>
|
||||
<button type="button" role="menuitem">
|
||||
<div role="presentation" id="account-options">Account Options</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "settings" %}
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="appearance-options">
|
||||
<span id="appearance-options" role="heading">Appearance</span>
|
||||
<button
|
||||
type="button"
|
||||
<div role="presentation" id="appearance-options">Appearance</div>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="true"
|
||||
class="group"
|
||||
@@ -89,9 +87,8 @@
|
||||
} %}
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</div>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@@ -107,9 +104,8 @@
|
||||
} %}
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</div>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@@ -124,14 +120,14 @@
|
||||
} %}
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{% set profile_menu %}
|
||||
@@ -143,41 +139,39 @@
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
{% lucide "star" %}
|
||||
Upgrade to Pro
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Account
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "bell" %}
|
||||
Notifications
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Signout
|
||||
</button>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{% call dropdown_menu(
|
||||
trigger="Radio Group",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="position-options" x-data="{ checked: 'bottom' }">
|
||||
<span id="position-options" role="heading">Panel Position</span>
|
||||
<hr role="separator">
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@@ -193,9 +187,8 @@
|
||||
</div>
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</div>
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@@ -211,9 +204,8 @@
|
||||
</div>
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</div>
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@@ -229,7 +221,7 @@
|
||||
</div>
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
@@ -256,7 +248,7 @@
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost rounded-full size-8"},
|
||||
content_attrs={"data-align": "end"}
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
@@ -268,21 +260,21 @@
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost"},
|
||||
content_attrs={"class": "min-w-32"}
|
||||
popover_attrs={"class": "min-w-32"}
|
||||
) %}
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
{% lucide "pencil" %}
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "share" %}
|
||||
Share
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
<div role="menuitem" class="text-destructive hover:bg-destructive/10 dark:hover:bg-destructive/20 focus:bg-destructive/10 dark:focus:bg-destructive/20 focus:text-destructive [&_svg]:!text-destructive">
|
||||
{% lucide "trash-2" %}
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
items: [
|
||||
{ type: "item", label: "Apple", value: "apple" },
|
||||
{ type: "item", label: "Banana", value: "banana" },
|
||||
{ type: "item", label: "Blueberry Blueberry Blueberry Blueberry Blueberry", value: "blueberry" }
|
||||
{ type: "item", label: "Blueberry", value: "blueberry" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"
|
||||
}
|
||||
) %}
|
||||
<div class="grid gap-4 p-4">
|
||||
<div class="grid gap-4">
|
||||
<header class="grid gap-1.5">
|
||||
<h2 class="font-semibold">I hope you like Basecoat...</h2>
|
||||
<p class="text-muted-foreground text-sm">My name is <a href="https://ronanberder.com" target="_blank">Ronan</a> and I made this (and <a class="underline underline-offset-4" href="https://pagescms.org" target="_target">other things</a>). If you find it useful, please consider sponsoring me on GitHub or following me on X.</p>
|
||||
|
||||
+151
-74
@@ -5,6 +5,7 @@
|
||||
:root, :host {
|
||||
--font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
|
||||
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
--font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
--color-amber-50: oklch(98.7% 0.022 95.277);
|
||||
@@ -63,6 +64,13 @@
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--shadow-2xs: 0 1px rgb(0 0 0 / 0.05);
|
||||
--shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
||||
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
--animate-spin: spin 1s linear infinite;
|
||||
--animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
@@ -245,6 +253,9 @@
|
||||
}
|
||||
}
|
||||
@layer utilities {
|
||||
.\@container {
|
||||
container-type: inline-size;
|
||||
}
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -280,6 +291,9 @@
|
||||
.top-0 {
|
||||
top: calc(var(--spacing) * 0);
|
||||
}
|
||||
.top-1 {
|
||||
top: calc(var(--spacing) * 1);
|
||||
}
|
||||
.top-1\/2 {
|
||||
top: calc(1/2 * 100%);
|
||||
}
|
||||
@@ -289,9 +303,15 @@
|
||||
.top-22 {
|
||||
top: calc(var(--spacing) * 22);
|
||||
}
|
||||
.right-2 {
|
||||
right: calc(var(--spacing) * 2);
|
||||
}
|
||||
.right-2\.5 {
|
||||
right: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
.left-1 {
|
||||
left: calc(var(--spacing) * 1);
|
||||
}
|
||||
.left-1\/2 {
|
||||
left: calc(1/2 * 100%);
|
||||
}
|
||||
@@ -355,6 +375,9 @@
|
||||
.mb-12 {
|
||||
margin-bottom: calc(var(--spacing) * 12);
|
||||
}
|
||||
.-ml-1 {
|
||||
margin-left: calc(var(--spacing) * -1);
|
||||
}
|
||||
.-ml-1\.5 {
|
||||
margin-left: calc(var(--spacing) * -1.5);
|
||||
}
|
||||
@@ -376,9 +399,6 @@
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.inline {
|
||||
display: inline;
|
||||
}
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
@@ -402,6 +422,10 @@
|
||||
width: calc(var(--spacing) * 2.5);
|
||||
height: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
.size-3 {
|
||||
width: calc(var(--spacing) * 3);
|
||||
height: calc(var(--spacing) * 3);
|
||||
}
|
||||
.size-3\.5 {
|
||||
width: calc(var(--spacing) * 3.5);
|
||||
height: calc(var(--spacing) * 3.5);
|
||||
@@ -466,9 +490,15 @@
|
||||
.min-h-\[350px\] {
|
||||
min-height: 350px;
|
||||
}
|
||||
.w-1 {
|
||||
width: calc(var(--spacing) * 1);
|
||||
}
|
||||
.w-1\/2 {
|
||||
width: calc(1/2 * 100%);
|
||||
}
|
||||
.w-2 {
|
||||
width: calc(var(--spacing) * 2);
|
||||
}
|
||||
.w-2\/3 {
|
||||
width: calc(2/3 * 100%);
|
||||
}
|
||||
@@ -550,21 +580,42 @@
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
.flex-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.-translate-x-1 {
|
||||
--tw-translate-x: calc(var(--spacing) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-x-1\/2 {
|
||||
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-y-1 {
|
||||
--tw-translate-y: calc(var(--spacing) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-y-1\/2 {
|
||||
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.translate-y-0 {
|
||||
--tw-translate-y: calc(var(--spacing) * 0);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.translate-y-0\.5 {
|
||||
--tw-translate-y: calc(var(--spacing) * 0.5);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.transform {
|
||||
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
||||
}
|
||||
.animate-pulse {
|
||||
animation: var(--animate-pulse);
|
||||
}
|
||||
@@ -574,6 +625,9 @@
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
.scroll-m-20 {
|
||||
scroll-margin: calc(var(--spacing) * 20);
|
||||
}
|
||||
@@ -619,6 +673,9 @@
|
||||
.justify-start {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.gap-0 {
|
||||
gap: calc(var(--spacing) * 0);
|
||||
}
|
||||
.gap-0\.5 {
|
||||
gap: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
@@ -674,6 +731,9 @@
|
||||
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
|
||||
}
|
||||
}
|
||||
.gap-x-1 {
|
||||
column-gap: calc(var(--spacing) * 1);
|
||||
}
|
||||
.gap-x-1\.5 {
|
||||
column-gap: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
@@ -763,6 +823,9 @@
|
||||
.border-amber-50 {
|
||||
border-color: var(--color-amber-50);
|
||||
}
|
||||
.border-border {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
.border-border\/50 {
|
||||
border-color: var(--color-border);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -799,6 +862,9 @@
|
||||
.bg-sidebar-primary {
|
||||
background-color: var(--color-sidebar-primary);
|
||||
}
|
||||
.mask-repeat {
|
||||
mask-repeat: repeat;
|
||||
}
|
||||
.object-cover {
|
||||
object-fit: cover;
|
||||
}
|
||||
@@ -841,6 +907,9 @@
|
||||
.px-\[0\.3rem\] {
|
||||
padding-inline: 0.3rem;
|
||||
}
|
||||
.py-1 {
|
||||
padding-block: calc(var(--spacing) * 1);
|
||||
}
|
||||
.py-1\.5 {
|
||||
padding-block: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
@@ -2534,7 +2603,10 @@
|
||||
}
|
||||
@layer components {
|
||||
.dropdown-menu {
|
||||
[popover].popover {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
padding: calc(var(--spacing) * 1);
|
||||
[data-popover] {
|
||||
padding: calc(var(--spacing) * 1);
|
||||
min-width: anchor-size(width);
|
||||
[role='menuitem'], [role='menuitemcheckbox'], [role='menuitemradio'] {
|
||||
@@ -2606,7 +2678,7 @@
|
||||
color: var(--color-accent-foreground);
|
||||
}
|
||||
}
|
||||
[role='menu'] [role='heading'] {
|
||||
[role='menu'] [role="presentation"] {
|
||||
display: flex;
|
||||
padding-inline: calc(var(--spacing) * 2);
|
||||
padding-block: calc(var(--spacing) * 1.5);
|
||||
@@ -2762,15 +2834,15 @@
|
||||
}
|
||||
}
|
||||
@layer components {
|
||||
[popover].popover {
|
||||
[data-popover] {
|
||||
visibility: visible;
|
||||
position: absolute;
|
||||
inset: auto;
|
||||
z-index: 50;
|
||||
width: max-content;
|
||||
--tw-scale-x: 95%;
|
||||
--tw-scale-y: 95%;
|
||||
--tw-scale-z: 95%;
|
||||
min-width: 100%;
|
||||
--tw-scale-x: 100%;
|
||||
--tw-scale-y: 100%;
|
||||
--tw-scale-z: 100%;
|
||||
scale: var(--tw-scale-x) var(--tw-scale-y);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
@@ -2778,106 +2850,85 @@
|
||||
border-style: var(--tw-border-style);
|
||||
border-width: 1px;
|
||||
background-color: var(--color-popover);
|
||||
padding: calc(var(--spacing) * 4);
|
||||
color: var(--color-popover-foreground);
|
||||
opacity: 0%;
|
||||
opacity: 100%;
|
||||
--tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px 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);
|
||||
transition-property: all;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
transition-duration: var(--tw-duration, var(--default-transition-duration));
|
||||
transition-behavior: allow-discrete;
|
||||
&:not([data-side]), &[data-side='bottom'] {
|
||||
--tw-translate-y: calc(var(--spacing) * -2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[data-side='top'] {
|
||||
--tw-translate-y: calc(var(--spacing) * 2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[data-side='left'] {
|
||||
--tw-translate-x: calc(var(--spacing) * 2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[data-side='right'] {
|
||||
--tw-translate-x: calc(var(--spacing) * -2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
&[aria-hidden='true'] {
|
||||
visibility: hidden;
|
||||
--tw-scale-x: 95%;
|
||||
--tw-scale-y: 95%;
|
||||
--tw-scale-z: 95%;
|
||||
scale: var(--tw-scale-x) var(--tw-scale-y);
|
||||
opacity: 0%;
|
||||
&:not([data-side]), &[data-side='bottom'] {
|
||||
--tw-translate-y: calc(var(--spacing) * -2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[data-side='top'] {
|
||||
--tw-translate-y: calc(var(--spacing) * 2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[data-side='left'] {
|
||||
--tw-translate-x: calc(var(--spacing) * 2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[data-side='right'] {
|
||||
--tw-translate-x: calc(var(--spacing) * -2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
}
|
||||
&:not([data-side]), &[data-side='bottom'] {
|
||||
top: anchor(bottom);
|
||||
top: 100%;
|
||||
margin-top: calc(var(--spacing) * 1);
|
||||
}
|
||||
&[data-side='top'] {
|
||||
bottom: anchor(top);
|
||||
bottom: 100%;
|
||||
margin-bottom: calc(var(--spacing) * 1);
|
||||
}
|
||||
&[data-side='left'] {
|
||||
right: anchor(left);
|
||||
right: 100%;
|
||||
margin-right: calc(var(--spacing) * 1);
|
||||
}
|
||||
&[data-side='right'] {
|
||||
left: anchor(right);
|
||||
left: 100%;
|
||||
margin-left: calc(var(--spacing) * 1);
|
||||
}
|
||||
&:not([data-side]), &[data-side='bottom'], &[data-side='top'] {
|
||||
&:not([data-align]), &[data-align='start'] {
|
||||
left: anchor(left);
|
||||
left: calc(var(--spacing) * 0);
|
||||
}
|
||||
&[data-align='end'] {
|
||||
right: anchor(right);
|
||||
right: calc(var(--spacing) * 0);
|
||||
}
|
||||
&[data-align='center'] {
|
||||
left: anchor(center);
|
||||
left: calc(1/2 * 100%);
|
||||
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
}
|
||||
&[data-side='left'], &[data-side='right'] {
|
||||
&:not([data-align]), &[data-align='start'] {
|
||||
top: anchor(top);
|
||||
top: calc(var(--spacing) * 0);
|
||||
}
|
||||
&[data-align='end'] {
|
||||
bottom: anchor(bottom);
|
||||
bottom: calc(var(--spacing) * 0);
|
||||
}
|
||||
&[data-align='center'] {
|
||||
top: anchor(center);
|
||||
top: calc(1/2 * 100%);
|
||||
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
}
|
||||
&:is(:popover-open) {
|
||||
--tw-translate-x: calc(var(--spacing) * 0);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
--tw-translate-y: calc(var(--spacing) * 0);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
--tw-scale-x: 100%;
|
||||
--tw-scale-y: 100%;
|
||||
--tw-scale-z: 100%;
|
||||
scale: var(--tw-scale-x) var(--tw-scale-y);
|
||||
opacity: 100%;
|
||||
@starting-style {
|
||||
--tw-scale-x: 95%;
|
||||
--tw-scale-y: 95%;
|
||||
--tw-scale-z: 95%;
|
||||
scale: var(--tw-scale-x) var(--tw-scale-y);
|
||||
opacity: 0%;
|
||||
&:not([data-side]), &[data-side='bottom'] {
|
||||
--tw-translate-y: calc(var(--spacing) * -2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[data-side='top'] {
|
||||
--tw-translate-y: calc(var(--spacing) * 2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[data-side='left'] {
|
||||
--tw-translate-x: calc(var(--spacing) * 2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
&[data-side='right'] {
|
||||
--tw-translate-x: calc(var(--spacing) * -2);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
}
|
||||
}
|
||||
.popover {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
[data-popover] {
|
||||
padding: calc(var(--spacing) * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3159,8 +3210,9 @@
|
||||
}
|
||||
}
|
||||
*:not(select).select {
|
||||
[popover].popover {
|
||||
min-width: anchor-size(width);
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
[data-popover] {
|
||||
padding: calc(var(--spacing) * 1);
|
||||
[role='option'] {
|
||||
position: relative;
|
||||
@@ -3218,7 +3270,7 @@
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
[role='listbox'] [role='heading'] {
|
||||
[role='listbox'] [role="presentation"] {
|
||||
display: flex;
|
||||
padding-inline: calc(var(--spacing) * 2);
|
||||
padding-block: calc(var(--spacing) * 1.5);
|
||||
@@ -4886,6 +4938,26 @@ code.hljs {
|
||||
inherits: false;
|
||||
initial-value: 0;
|
||||
}
|
||||
@property --tw-rotate-x {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-rotate-y {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-rotate-z {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-skew-x {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-skew-y {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
}
|
||||
@property --tw-space-y-reverse {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -5100,6 +5172,11 @@ code.hljs {
|
||||
--tw-translate-x: 0;
|
||||
--tw-translate-y: 0;
|
||||
--tw-translate-z: 0;
|
||||
--tw-rotate-x: initial;
|
||||
--tw-rotate-y: initial;
|
||||
--tw-rotate-z: initial;
|
||||
--tw-skew-x: initial;
|
||||
--tw-skew-y: initial;
|
||||
--tw-space-y-reverse: 0;
|
||||
--tw-space-x-reverse: 0;
|
||||
--tw-border-style: solid;
|
||||
|
||||
@@ -29,8 +29,7 @@ toc:
|
||||
{% call dropdown_menu(
|
||||
id="demo-breadcrumb-menu",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
trigger_attrs={"class": "flex size-9 items-center justify-center h-4 w-4 hover:text-foreground cursor-pointer"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem" onclick="console.log('Documentation')">Documentation</button>
|
||||
|
||||
@@ -36,8 +36,7 @@ toc:
|
||||
trigger_attrs={"class": "w-[200px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true,
|
||||
search_placeholder="Search framework...",
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
search_placeholder="Search framework..."
|
||||
) %}
|
||||
{% for framework in options_frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
@@ -54,15 +53,10 @@ toc:
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank"><code>select.js</code></a> file. It is recommended to also include the <a href="https://github.com/oddbird/css-anchor-positioning" target="_blank">CSS anchor positioning polyfill</a> to support older browsers. Add this to the <code><head></code> of your page:</p>
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/select.min.js" defer></script>
|
||||
<script type="module">
|
||||
if (!("anchorName" in document.documentElement.style)) {
|
||||
import("https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/css-anchor-positioning.min.js");
|
||||
}
|
||||
</script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -211,16 +205,15 @@ toc:
|
||||
{% set code %}
|
||||
{% call select(
|
||||
trigger_attrs={"class": "w-[200px]"},
|
||||
content_attrs={"class": "w-72"},
|
||||
popover_attrs={"class": "w-72"},
|
||||
listbox_attrs={"data-empty": "No timezone found."},
|
||||
is_combobox=true,
|
||||
search_placeholder="Search timezone...",
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
search_placeholder="Search timezone..."
|
||||
) %}
|
||||
<div class="max-h-64 overflow-y-auto scrollbar">
|
||||
{% for group in options_timezones %}
|
||||
<div role="group" aria-labelledby="demo-combobox-timezones-group-{{ loop.index0 }}">
|
||||
<span id="demo-combobox-timezones-group-{{ loop.index0 }}" role="heading">{{ group.label }}</span>
|
||||
<div role="presentation" id="demo-combobox-timezones-group-{{ loop.index0 }}">{{ group.label }}</div>
|
||||
{% for timezone in group.timezones %}
|
||||
<div role="option" data-value="{{ timezone.value}}">
|
||||
{{ timezone.label }}
|
||||
@@ -247,8 +240,7 @@ toc:
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true,
|
||||
search_placeholder="Search framework...",
|
||||
register_on=["alpine:init", "htmx:afterSwap"],
|
||||
content_attrs={"data-side": "top"}
|
||||
popover_attrs={"data-side": "top"}
|
||||
) %}
|
||||
{% for framework in options_frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
|
||||
@@ -32,13 +32,13 @@ toc:
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-name">Name</label>
|
||||
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" x-ref="focusOnOpen" />
|
||||
<input type="text" value="Pedro Duarte" id="demo-dialog-edit-profile-name" autofocus/>
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-dialog-edit-profile-username">Username</label>
|
||||
@@ -126,7 +126,7 @@ toc:
|
||||
description="Make changes to your profile here. Click save when you're done.",
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
@@ -154,13 +154,11 @@ toc:
|
||||
id="dialog-example",
|
||||
title="Scrollable Content",
|
||||
description="This is a dialog with scrollable content.",
|
||||
main_attrs={"x-ref": "myDialog"},
|
||||
trigger="Scrollable Content",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
content_body_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer=footer,
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
dialog_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
dialog_attrs={"class": "overflow-y-auto scrollbar"},
|
||||
footer=footer
|
||||
) %}
|
||||
<div class="space-y-4 text-sm">
|
||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
|
||||
|
||||
@@ -38,39 +38,39 @@ toc:
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">My Account</span>
|
||||
<button type="button" role="menuitem">
|
||||
<div role="presentation" id="account-options">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Keyboard shortcuts
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
GitHub
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Support
|
||||
</button>
|
||||
<button type="button" role="menuitem" disabled>
|
||||
</div>
|
||||
<div role="menuitem" disabled>
|
||||
API
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
|
||||
@@ -83,15 +83,10 @@ toc:
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/dropdown-menu.js" target="_blank"><code>dropdown-menu.js</code></a> file. It is recommended to also include the <a href="https://github.com/oddbird/css-anchor-positioning" target="_blank">CSS anchor positioning polyfill</a> to support older browsers. Add this to the <code><head></code> of your page:</p>
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/dropdown-menu.min.js" defer></script>
|
||||
<script type="module">
|
||||
if (!("anchorName" in document.documentElement.style)) {
|
||||
import("https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/css-anchor-positioning.min.js");
|
||||
}
|
||||
</script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/dropdown-menu.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -185,43 +180,42 @@ toc:
|
||||
id="dropdown-menu",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">My Account</span>
|
||||
<button type="button" role="menuitem">
|
||||
<div role="presentation" id="account-options">My Account</div>
|
||||
<div role="menuitem">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Keyboard shortcuts
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
GitHub
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
Support
|
||||
</button>
|
||||
<button type="button" role="menuitem" disabled>
|
||||
</div>
|
||||
<div role="menuitem" disabled>
|
||||
API
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endcall %}{% endraw %}{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
|
||||
@@ -233,32 +227,30 @@ toc:
|
||||
id="dropdown-menu-checkboxes",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
popover_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">Account Options</span>
|
||||
<button type="button" role="menuitem">
|
||||
<div role="presentation" id="account-options">Account Options</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
</div>
|
||||
<div role="menuitem">
|
||||
{% lucide "settings" %}
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="appearance-options">
|
||||
<span id="appearance-options" role="heading">Appearance</span>
|
||||
<button
|
||||
type="button"
|
||||
<div role="presentation" id="appearance-options">Appearance</div>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="true"
|
||||
class="group"
|
||||
@@ -273,9 +265,8 @@ toc:
|
||||
} %}
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</div>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@@ -291,9 +282,8 @@ toc:
|
||||
} %}
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</div>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@@ -308,14 +298,14 @@ toc:
|
||||
} %}
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
<div role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endcall %}{% endset %}
|
||||
{{ code_preview("dropdown-menu-checkboxes", code | prettyHtml) }}
|
||||
|
||||
@@ -330,8 +320,7 @@ toc:
|
||||
<div role="group" aria-labelledby="position-options" x-data="{ checked: 'bottom' }">
|
||||
<span id="position-options" role="heading">Panel Position</span>
|
||||
<hr role="separator">
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@@ -347,9 +336,8 @@ toc:
|
||||
</div>
|
||||
Status Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</div>
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@@ -365,9 +353,8 @@ toc:
|
||||
</div>
|
||||
Activity Bar
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
</div>
|
||||
<div
|
||||
role="menuitemradio"
|
||||
aria-checked="false"
|
||||
class="group"
|
||||
@@ -383,7 +370,7 @@ toc:
|
||||
</div>
|
||||
Panel
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}{% endset %}
|
||||
{{ code_preview("dropdown-menu", code | prettyHtml) }}
|
||||
@@ -63,17 +63,13 @@ toc:
|
||||
|
||||
<h3 id="usage-html-js"><a href="#usage-html-js">HTML + JavaScript</a></h3>
|
||||
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1 (Optional): Include the JavaScript file</a></h4>
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript file</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>It is recommended to include the <a href="https://github.com/oddbird/css-anchor-positioning" target="_blank">CSS anchor positioning polyfill</a> to support older browsers. Add this to the <code><head></code> of your page:</p>
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script type="module">
|
||||
if (!("anchorName" in document.documentElement.style)) {
|
||||
import("https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/css-anchor-positioning.min.js");
|
||||
}
|
||||
</script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/popover.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
|
||||
@@ -85,15 +85,10 @@ toc:
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/select.js" target="_blank"><code>select.js</code></a> file. It is recommended to also include the <a href="https://github.com/oddbird/css-anchor-positioning" target="_blank">CSS anchor positioning polyfill</a> to support older browsers. Add this to the <code><head></code> of your page:</p>
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/select.min.js" defer></script>
|
||||
<script type="module">
|
||||
if (!("anchorName" in document.documentElement.style)) {
|
||||
import("https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/css-anchor-positioning.min.js");
|
||||
}
|
||||
</script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -205,8 +200,7 @@ toc:
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
listbox_attrs={"class": "scrollbar overflow-y-auto max-h-64"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
listbox_attrs={"class": "scrollbar overflow-y-auto max-h-64"}
|
||||
) %}
|
||||
{% for i in range(0, 99) %}
|
||||
<div role="option" data-value="item-{{ i }}">Item {{ i }}</div>
|
||||
@@ -220,8 +214,7 @@ toc:
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
trigger_attrs={"disabled": "disabled"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
trigger_attrs={"disabled": "disabled"}
|
||||
) %}
|
||||
<div role="option" data-value="disabled">Disabled</div>
|
||||
{% endcall %}
|
||||
@@ -233,8 +226,7 @@ toc:
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
trigger_attrs={'class': 'w-[180px]'},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
trigger_attrs={'class': 'w-[180px]'}
|
||||
) %}
|
||||
<div type="button" role="option" data-value="bar">
|
||||
<span class="flex items-center gap-2">
|
||||
|
||||
@@ -29,10 +29,10 @@ toc:
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/sidebar.js" target="_blank"><code>sidebar.js</code></a> file. Add this to the <code><head></code> of your page:</p>
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/sidebar.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
|
||||
@@ -71,8 +71,7 @@ toc:
|
||||
id='demo-tabs-with-panels',
|
||||
tabsets=tabsets_demo,
|
||||
main_attrs={ "class": "w-full" },
|
||||
tablist_attrs={ "class": "w-full" },
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
tablist_attrs={ "class": "w-full" }
|
||||
) }}
|
||||
{% endset %}
|
||||
|
||||
@@ -83,10 +82,10 @@ toc:
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript files</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/tabs.js" target="_blank"><code>tabs.js</code></a> file. Add this to the <code><head></code> of your page:</p>
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/tabs.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
@@ -209,7 +208,6 @@ toc:
|
||||
id='demo-tabs-with-panels',
|
||||
tabsets=tabsets_demo,
|
||||
main_attrs={ "class": "w-full" },
|
||||
tablist_attrs={ "class": "w-full" },
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
tablist_attrs={ "class": "w-full" }
|
||||
) }}{% endraw %}{% endset%}
|
||||
{{ code_block(raw_code, "jinja") }}
|
||||
@@ -64,10 +64,10 @@ toc:
|
||||
<h4 id="usage-html-js-1"><a href="#usage-html-js-1">Step 1: Include the JavaScript file</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
<p>Include the <a href="https://github.com/hunvreus/basecoat/blob/main/src/js/toast.js" target="_blank"><code>toast.js</code></a> file. Add this to the <code><head></code> of your page:</p>
|
||||
<p>You can either <a href="/installation/#install-cdn-all">include the JavaScript file for all the components</a>, or just the one for this component by adding this to the <code><head></code> of your page:</p>
|
||||
</section>
|
||||
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/toast.min.js" defer></script>{% endset %}
|
||||
{% set code_script %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ pkg.version }}/dist/js/select.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code_script | prettyHtml, "html") }}
|
||||
|
||||
<div class="flex flex-wrap gap-2 my-6">
|
||||
|
||||
+2
-2
@@ -53,7 +53,7 @@ layout: layouts/layout.njk
|
||||
|
||||
{% call select(
|
||||
is_combobox=true,
|
||||
content_attrs={"data-align": "end"}
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="option" data-value="viewer">Viewer</div>
|
||||
<div role="option" data-value="developer">Developer</div>
|
||||
@@ -70,7 +70,7 @@ layout: layouts/layout.njk
|
||||
|
||||
{% call select(
|
||||
is_combobox=true,
|
||||
content_attrs={"data-align": "end"}
|
||||
popover_attrs={"data-align": "end"}
|
||||
) %}
|
||||
<div role="option" data-value="viewer">Viewer</div>
|
||||
<div role="option" data-value="developer">Developer</div>
|
||||
|
||||
+12
-34
@@ -3,8 +3,6 @@ layout: layouts/page.njk
|
||||
title: Installation
|
||||
description: How to install and use Basecoat in your app.
|
||||
toc:
|
||||
- label: Quickstart
|
||||
id: install-quickstart
|
||||
- label: Install using the CDN
|
||||
id: install-cdn
|
||||
- label: Installing using NPM
|
||||
@@ -33,39 +31,26 @@ toc:
|
||||
{% from "macros/code_block.njk" import code_block %}
|
||||
{% from "tabs.njk" import tabs %}
|
||||
|
||||
<h2 id="install-quickstart"><a href="#install-quickstart">Quickstart</a></h2>
|
||||
<h2 id="install-cdn"><a href="#install-cdn">Install using the CDN</a></h2>
|
||||
|
||||
<h3 id="install-cdn-all"><a href="#install-cdn-all">Install all components</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The fastest way to get up and running is to load the CSS and all of the components' JavaScript using the CDN. Add the following to the <code><head></code> of your HTML file:</p>
|
||||
<p>Add the following to the <code><head></code> of your HTML file:</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/cdn.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/all.min.js" defer></script>
|
||||
<script type="module">
|
||||
if (!("anchorName" in document.documentElement.style)) {
|
||||
import("https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/css-anchor-positioning.min.js");
|
||||
}
|
||||
</script>{% endset %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/all.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>If you're not using all of the components requiring JavaScript (<a href="#install-js">Dropdown Menu</a>, <a href="#install-js">Select</a>, <a href="#install-js">Sidebar</a>, <a href="#install-js">Tabs</a> and <a href="#install-js">Toast</a>), follow the instructions below to only include the JavaScript files you actually need.</p>
|
||||
</section>
|
||||
|
||||
<h2 id="install-cdn"><a href="#install-cdn">Install using the CDN</a></h2>
|
||||
<h3 id="install-cdn-all"><a href="#install-cdn-all">Install specific components</a></h3>
|
||||
|
||||
<section class="prose">
|
||||
<p>The easiest way to use Basecoat is use the CDN. First, add the CSS to the <code><head></code> of your HTML file:</p>
|
||||
<p>While the JavaScript file for all components is small (around 3kB gzipped), you can cherry-pick the components you need as instructed in the component's page (<a href="/components/dropdown-menu">Dropdown Menu</a>, <a href="/components/popover">Popover</a>, <a href="/components/select">Select</a>, <a href="/components/sidebar">Sidebar</a>, <a href="/components/tabs">Tabs</a> and <a href="/components/toast">Toast</a>). For example, to add the <a href="/components/dropdown-menu">Dropdown Menu</a> component, add the following to the <code><head></code> of your HTML file:</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/cdn.min.css">{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
|
||||
<section class="prose">
|
||||
<p>Depending on the components you want to use, you may also need to add JavaScript files as instructed on the component's page. For example, for the <a href="/components/tabs">Tabs</a> component, you would add the following:</p>
|
||||
</section>
|
||||
|
||||
{% set code %}<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/tabs.min.js"></script>{% endset %}
|
||||
{% set code %}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/cdn.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@{{ site.version }}/dist/js/dropdown-menu.min.js" defer></script>{% endset %}
|
||||
{{ code_block(code, "html") }}
|
||||
|
||||
<h2 id="install-npm"><a href="#install-npm">Install using NPM</a></h2>
|
||||
@@ -122,20 +107,13 @@ toc:
|
||||
<h2 id="install-js"><a href="#install-js">Components with JavaScript</a></h2>
|
||||
|
||||
<div class="prose">
|
||||
<p><b>Some components may require JavaScript code to work.</b></p>
|
||||
<p><b>A handful of components require JavaScript code to work.</b>, specifically <a href="/components/dropdown-menu">Dropdown Menu</a>, <a href="/components/popover">Popover</a>, <a href="/components/select">Select</a>, <a href="/components/sidebar">Sidebar</a>, <a href="/components/tabs">Tabs</a> and <a href="/components/toast">Toast</a></p>
|
||||
|
||||
<p>If a component requires JavaScript, the documentation page will provide instructions. There are 2 options to add the JavaScript code to your project:</p>
|
||||
|
||||
<ul>
|
||||
<li><b>CDN</b>: you add the <code><script></code> tag provided on the component's page to the <code><head></code> of your HTML file.</li>
|
||||
<li><b>Local file</b>: you download the script as a separate file and include it in your project (<a href="#install-cli">or let the CLI do it for you</a>). Make sure keep the <code>defer</code> attribute on the <code><script></code> tag when doing so.</p>
|
||||
</ul>
|
||||
|
||||
<p>A few things to keep in mind:</p>
|
||||
<ul>
|
||||
<li>Most components (e.g. <a href="/components/button">Button</a>, <a href="/components/card">Card</a>, <a href="/components/input">Input</a>, etc) do not require any JavaScript at all to work.</li>
|
||||
<li>Some of the JavaScript is optional. For example the <a href="/components/dropdown-menu">Dropdown Menu component</a> suggest to add the <a href="https://github.com/oddbird/css-anchor-positioning" target="_blank">CSS anchor positioning polyfill</a> to support older browser, as well as a <code>dropdown-menu.js</code> to improve accessiblity (WAI-ARIA) and usability (keyboard navigation).</li>
|
||||
<li>Only a handful of components require JavaScript to work (e.g. <a href="/components/sidebar">Sidebar</a>, <a href="/components/toast">Toast</a>, ...).</li>
|
||||
<li><b>CDN</b>: you either <a href="#install-cdn-all">add the code for all of the components</a> to the <code><head></code> of your HTML file, or just the file for the component you want to use as instructed on the component's page.</li>
|
||||
<li><b>Local file</b>: you download the script as a separate file and include it in your project (<a href="#install-cli">or let the CLI do it for you</a>). Once again, you have the choice to include the fiel for all components at once (<a><code>all.min.js</code> or <code>all.js</code>).</p>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Anchor Positioning Test (Top, End)</title>
|
||||
<script type="module">
|
||||
if (!("anchorName" in document.documentElement.style)) {
|
||||
console.log("Loading CSS anchor positioning polyfill...");
|
||||
import("https://unpkg.com/@oddbird/css-anchor-positioning");
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
font-family: sans-serif;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background: #222;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.5em 1em;
|
||||
font-size: 1rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #888;
|
||||
background: #444;
|
||||
color: #eee;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[popover] {
|
||||
border: 1px solid #888;
|
||||
border-radius: 6px;
|
||||
background: #333;
|
||||
color: #eee;
|
||||
padding: 1em;
|
||||
margin: 0;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* The core anchor positioning logic */
|
||||
#my-popover {
|
||||
position: absolute;
|
||||
inset: auto; /* Required to override default popover centering */
|
||||
bottom: anchor(top); /* Position bottom of popover to top of anchor */
|
||||
right: anchor(right); /* Align right edge of popover with right edge of anchor */
|
||||
margin-bottom: 8px; /* A little space between popover and button */
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<button
|
||||
popovertarget="my-popover"
|
||||
id="my-anchor-button"
|
||||
style="anchor-name: --my-anchor;">
|
||||
Click Me
|
||||
</button>
|
||||
|
||||
<div
|
||||
popover
|
||||
id="my-popover"
|
||||
style="position-anchor: --my-anchor;">
|
||||
I should be anchored above the button, aligned to the right.
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Generated
-99
@@ -14,7 +14,6 @@
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^3.0.0",
|
||||
"@grimlink/eleventy-plugin-lucide-icons": "^2.1.4",
|
||||
"@oddbird/css-anchor-positioning": "^0.6.0",
|
||||
"@tailwindcss/cli": "^4.1.3",
|
||||
"concurrently": "^9.1.2",
|
||||
"prettier": "^3.5.3",
|
||||
@@ -256,34 +255,6 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz",
|
||||
"integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz",
|
||||
"integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.7.1",
|
||||
"@floating-ui/utils": "^0.2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@grimlink/eleventy-plugin-lucide-icons": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@grimlink/eleventy-plugin-lucide-icons/-/eleventy-plugin-lucide-icons-2.1.4.tgz",
|
||||
@@ -773,19 +744,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@oddbird/css-anchor-positioning": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@oddbird/css-anchor-positioning/-/css-anchor-positioning-0.6.0.tgz",
|
||||
"integrity": "sha512-5r6AhCHSW/bU45gCwOdaei04WPn2E7oeztC8o0z6euyAPBbAeiYQda5ByIiMRfOf13sa8rVqiCsKpwx/QNyHbw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@types/css-tree": "^2.3.10",
|
||||
"css-tree": "^3.1.0",
|
||||
"nanoid": "^5.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
@@ -1412,13 +1370,6 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/css-tree": {
|
||||
"version": "2.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/css-tree/-/css-tree-2.3.10.tgz",
|
||||
"integrity": "sha512-WcaBazJ84RxABvRttQjjFWgTcHvZR9jGr0Y3hccPkHjFyk/a3N8EuxjKr+QfrwjoM5b1yI1Uj1i7EzOAAwBwag==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/a-sync-waterfall": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
||||
@@ -1891,20 +1842,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
|
||||
"integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.12.2",
|
||||
"source-map-js": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||
@@ -3158,13 +3095,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.12.2",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz",
|
||||
"integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==",
|
||||
"dev": true,
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/mdurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
|
||||
@@ -3321,25 +3251,6 @@
|
||||
"node": "^18.17.0 || >=20.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz",
|
||||
"integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || >=20"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
@@ -3885,16 +3796,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
"devDependencies": {
|
||||
"@11ty/eleventy": "^3.0.0",
|
||||
"@grimlink/eleventy-plugin-lucide-icons": "^2.1.4",
|
||||
"@oddbird/css-anchor-positioning": "^0.6.0",
|
||||
"@tailwindcss/cli": "^4.1.3",
|
||||
"concurrently": "^9.1.2",
|
||||
"prettier": "^3.5.3",
|
||||
|
||||
+1
-15
@@ -119,23 +119,9 @@ async function build() {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle anchor positioning polyfill
|
||||
const polyfillSrc = path.join(projectRoot, 'node_modules', '@oddbird', 'css-anchor-positioning', 'dist', 'css-anchor-positioning.js');
|
||||
const polyfillDest = 'css-anchor-positioning.js';
|
||||
const polyfillMinDest = 'css-anchor-positioning.min.js';
|
||||
|
||||
// Copy original polyfill
|
||||
await fs.copyFile(polyfillSrc, path.join(cliJsDistDir, polyfillDest));
|
||||
await fs.copyFile(polyfillSrc, path.join(cssJsDistDir, polyfillDest));
|
||||
|
||||
// Minify and copy polyfill
|
||||
const cliPolyfillMinDest = path.join(cliJsDistDir, polyfillMinDest);
|
||||
await execPromise(`npx terser ${polyfillSrc} -o ${cliPolyfillMinDest} --compress --mangle`);
|
||||
await fs.copyFile(cliPolyfillMinDest, path.join(cssJsDistDir, polyfillMinDest));
|
||||
|
||||
// Create combined component files
|
||||
console.log('Creating combined component files...');
|
||||
const componentsToCombine = ['dropdown-menu.js', 'select.js', 'sidebar.js', 'tabs.js', 'toast.js'];
|
||||
const componentsToCombine = ['dropdown-menu.js', 'popover.js', 'select.js', 'sidebar.js', 'tabs.js', 'toast.js'];
|
||||
const componentPaths = componentsToCombine.map(f => path.join(srcJsDir, f));
|
||||
|
||||
// Create non-minified bundle
|
||||
|
||||
+44
-51
@@ -504,7 +504,9 @@
|
||||
/* Dropdown Menu */
|
||||
@layer components {
|
||||
.dropdown-menu {
|
||||
[popover].popover {
|
||||
@apply relative inline-flex p-1;
|
||||
|
||||
[data-popover] {
|
||||
@apply p-1;
|
||||
min-width: anchor-size(width);
|
||||
|
||||
@@ -517,7 +519,7 @@
|
||||
@apply bg-accent text-accent-foreground;
|
||||
}
|
||||
}
|
||||
[role='menu'] [role='heading'] {
|
||||
[role='menu'] [role="presentation"] {
|
||||
@apply flex px-2 py-1.5 text-sm font-medium;
|
||||
}
|
||||
[role='separator'] {
|
||||
@@ -576,85 +578,74 @@
|
||||
|
||||
/* Popover */
|
||||
@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;
|
||||
[data-popover] {
|
||||
@apply absolute bg-popover text-popover-foreground overflow-x-hidden overflow-y-auto rounded-md border shadow-md z-50 visible opacity-100 scale-100 min-w-full w-max transition-all;
|
||||
|
||||
&: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;
|
||||
&[aria-hidden='true'] {
|
||||
@apply invisible 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;
|
||||
}
|
||||
}
|
||||
&:not([data-side]),
|
||||
&[data-side='bottom'] {
|
||||
@apply mt-1 top-[anchor(bottom)];
|
||||
@apply mt-1 top-full;
|
||||
}
|
||||
&[data-side='top'] {
|
||||
@apply mb-1 bottom-[anchor(top)];
|
||||
@apply mb-1 bottom-full;
|
||||
}
|
||||
&[data-side='left'] {
|
||||
@apply mr-1 right-[anchor(left)];
|
||||
@apply mr-1 right-full;
|
||||
}
|
||||
&[data-side='right'] {
|
||||
@apply ml-1 left-[anchor(right)];
|
||||
@apply ml-1 left-full;
|
||||
}
|
||||
&:not([data-side]),
|
||||
&[data-side='bottom'],
|
||||
&[data-side='top'] {
|
||||
&:not([data-align]),
|
||||
&[data-align='start'] {
|
||||
@apply left-[anchor(left)];
|
||||
@apply left-0;
|
||||
}
|
||||
&[data-align='end'] {
|
||||
@apply right-[anchor(right)];
|
||||
@apply right-0;
|
||||
}
|
||||
&[data-align='center'] {
|
||||
@apply left-[anchor(center)] -translate-x-1/2;
|
||||
@apply left-1/2 -translate-x-1/2;
|
||||
}
|
||||
}
|
||||
&[data-side='left'],
|
||||
&[data-side='right'] {
|
||||
&:not([data-align]),
|
||||
&[data-align='start'] {
|
||||
@apply top-[anchor(top)];
|
||||
@apply top-0;
|
||||
}
|
||||
&[data-align='end'] {
|
||||
@apply bottom-[anchor(bottom)];
|
||||
@apply bottom-0;
|
||||
}
|
||||
&[data-align='center'] {
|
||||
@apply top-[anchor(center)] -translate-y-1/2;
|
||||
@apply top-1/2 -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.popover {
|
||||
@apply relative inline-flex;
|
||||
|
||||
[data-popover] {
|
||||
@apply p-4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Radio */
|
||||
@@ -731,8 +722,10 @@
|
||||
}
|
||||
}
|
||||
*:not(select).select {
|
||||
[popover].popover {
|
||||
@apply p-1 min-w-[anchor-size(width)];
|
||||
@apply relative inline-flex;
|
||||
|
||||
[data-popover] {
|
||||
@apply p-1;
|
||||
|
||||
[role='option'] {
|
||||
@apply aria-hidden:hidden [&_svg]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm pl-2 py-1.5 pr-7.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;
|
||||
@@ -741,7 +734,7 @@
|
||||
@apply bg-[image:var(--check-icon)] bg-no-repeat bg-position-[center_right_0.5rem] bg-size-[0.875rem];
|
||||
}
|
||||
}
|
||||
[role='listbox'] [role='heading'] {
|
||||
[role='listbox'] [role="presentation"] {
|
||||
@apply flex text-muted-foreground px-2 py-1.5 text-xs;
|
||||
}
|
||||
[role='listbox'] [role='group']:not(:has([role='option']:not([aria-hidden='true']))) {
|
||||
|
||||
+61
-50
@@ -4,53 +4,66 @@
|
||||
@param id {string} [optional] - Unique identifier for the select component.
|
||||
@param selected {string} [optional] - The initially selected value.
|
||||
@param name {string} [optional] - The name attribute for the hidden input storing the selected value.
|
||||
@param items {array} [optional] - An array of items (objects with type, label, value, attrs, and items) to render.
|
||||
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
|
||||
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
|
||||
@param content_attrs {object} [optional] - Additional HTML attributes for the popover content div.
|
||||
@param listbox_attrs {object} [optional] - Additional HTML attributes for the listbox div.
|
||||
@param input_attrs {object} [optional] - Additional HTML attributes for the hidden input.
|
||||
@param search_placeholder {string} [optional] [default="Search entries..."] - Placeholder text for the search input (combobox only).
|
||||
@param is_combobox {boolean} [optional] [default=False] - Renders a combobox with search functionality if True.
|
||||
@param is_combobox {boolean} [optional] [default=false] - Renders a combobox with search functionality if true.
|
||||
#}
|
||||
{% macro select(
|
||||
id=None,
|
||||
selected=None,
|
||||
name=None,
|
||||
items=None,
|
||||
main_attrs={},
|
||||
trigger_attrs={},
|
||||
content_attrs={},
|
||||
popover_attrs={},
|
||||
listbox_attrs={},
|
||||
input_attrs={},
|
||||
search_placeholder="Search entries...",
|
||||
is_combobox=False
|
||||
is_combobox=false
|
||||
) %}
|
||||
<div
|
||||
class="popover {{ main_attrs.class }}"
|
||||
x-data="select('{{ name }}', '{{ selected or '' }}')"
|
||||
@click.away="open = false"
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
{% for key, value in main_attrs.items() %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{% set id = id or ("select-" + (range(100000, 999999) | random | string)) %}
|
||||
|
||||
{% set first_option = [] %}
|
||||
{% set selected_option = [] %}
|
||||
|
||||
{% for item in items.items() %}
|
||||
{% if item.type == "group" %}
|
||||
{% for sub_item in item.items.items() %}
|
||||
{% if not first_option[0] %}
|
||||
{% set first_option = (first_option.push(sub_item), first_option) %}
|
||||
{% endif %}
|
||||
{% if selected and sub_item.value == selected and not selected_option[0] %}
|
||||
{% set selected_option = (selected_option.push(sub_item), selected_option) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% if not first_option[0] %}
|
||||
{% set first_option = (first_option.push(item), first_option) %}
|
||||
{% endif %}
|
||||
{% if selected and item.value == selected and not selected_option[0] %}
|
||||
{% set selected_option = (selected_option.push(item), selected_option) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% set default_option = selected_option[0] or first_option[0] or None %}
|
||||
|
||||
<div class="select">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-outline justify-between font-normal {{ trigger_attrs.class }}"
|
||||
id="{{ id }}-trigger"
|
||||
popovertarget="{{ id }}"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded="false"
|
||||
x-bind="$trigger"
|
||||
{% if id %}
|
||||
id="{{ id }}-trigger"
|
||||
aria-controls="{{ id }}-content"
|
||||
{% endif %}
|
||||
class="btn-outline justify-between font-normal {{ trigger_attrs.class }}"
|
||||
{% for key, value in trigger_attrs.items() %}
|
||||
aria-controls="{{ id }}-listbox"
|
||||
{% for key, value in trigger_attrs %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
<div x-html="selectedLabel" class="flex items-center gap-x-2"
|
||||
></div>
|
||||
<span class="truncate">{{ default_option.label }}</span>
|
||||
{% if is_combobox %}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down text-muted-foreground opacity-50 shrink-0"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
|
||||
{% else %}
|
||||
@@ -58,12 +71,11 @@
|
||||
{% endif %}
|
||||
</button>
|
||||
<div
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
x-bind="$content"
|
||||
{% if id %}id="{{ id }}-content"{% endif %}
|
||||
{% for key, value in content_attrs.items() %}
|
||||
{{ key }}="{{ value }}"
|
||||
popover
|
||||
id="{{ id }}"
|
||||
class="popover {{ popover_attrs.class }}"
|
||||
{% for key, value in popover_attrs.items() %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{% if is_combobox %}
|
||||
@@ -81,35 +93,33 @@
|
||||
aria-expanded="true"
|
||||
aria-controls="{{ id }}-content"
|
||||
aria-labelledby="{{ id }}-trigger"
|
||||
x-model="query"
|
||||
x-bind="$filter"
|
||||
>
|
||||
</header>
|
||||
{% endif %}
|
||||
<div
|
||||
role="listbox"
|
||||
id="{{ id }}-listbox"
|
||||
aria-orientation="vertical"
|
||||
{% for key, value in listbox_attrs.items() %}
|
||||
aria-labelledby="{{ id }}-trigger"
|
||||
{% for key, value in listbox_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if items %}
|
||||
{{ render_select_items(items, id ~ "-items" if id else "items") }}
|
||||
{% if items.length > 0 %}
|
||||
{{ render_select_items(items, default_option.value, id ~ "-items" if id else "items") }}
|
||||
{% else %}
|
||||
{{ caller() if caller }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if name is defined %}
|
||||
<input
|
||||
type="hidden"
|
||||
name="{{ name }}"
|
||||
x-model="selectedValue"
|
||||
{% for key, value in input_attrs.items() %}
|
||||
{% if key != 'name' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{% endif %}
|
||||
<input
|
||||
type="hidden"
|
||||
name="{{ name or id ~ '-value' }}"
|
||||
value="{{ selected or '' }}"
|
||||
{% for key, value in input_attrs.items() %}
|
||||
{% if key != 'name' and key != 'value' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -119,29 +129,30 @@
|
||||
@param items {array} - The array of items to render.
|
||||
@param parent_id_prefix {string} [optional] - The prefix for the item id.
|
||||
#}
|
||||
{% macro render_select_items(items, parent_id_prefix="items") %}
|
||||
{% macro render_select_items(items, selected, parent_id_prefix="items") %}
|
||||
{% for item in items.items() %}
|
||||
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
|
||||
|
||||
{% if item.type == "group" %}
|
||||
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby="{{ group_label_id }}"
|
||||
{% for key, value in item.attrs.items() %}
|
||||
{% for key, value in item.attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
|
||||
{{ render_select_items(item.items, item_id) if item.items }}
|
||||
{{ render_select_items(item.items, selected, item_id) if item.items }}
|
||||
</div>
|
||||
{% elif item.type == "separator" %}
|
||||
<hr role="separator" />
|
||||
{% elif item.type == "item" or not item.type %}
|
||||
<div
|
||||
id="{{ item_id }}"
|
||||
role="option"
|
||||
data-value="{{ item.value }}"
|
||||
{% for key, value in item.attrs.items() %}
|
||||
{% if selected == item.value %}aria-selected="true"{% endif %}
|
||||
{% for key, value in item.attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
|
||||
+60
-45
@@ -1,13 +1,35 @@
|
||||
(() => {
|
||||
const initDropdownMenu = (dropdownMenuComponent) => {
|
||||
const trigger = dropdownMenuComponent.querySelector('[popovertarget]');
|
||||
const popover = dropdownMenuComponent.querySelector('[popover]');
|
||||
const menu = popover?.querySelector('[role="menu"]');
|
||||
if (!trigger || !popover || !menu) return;
|
||||
const trigger = dropdownMenuComponent.querySelector(':scope > button');
|
||||
const popover = dropdownMenuComponent.querySelector(':scope > [data-popover]');
|
||||
const menu = popover.querySelector('[role="menu"]');
|
||||
|
||||
if (!trigger || !menu || !popover) {
|
||||
console.error('Dropdown menu component is missing a trigger, a menu, or a popover content element.', dropdownMenuComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
let menuItems = [];
|
||||
let activeIndex = -1;
|
||||
|
||||
const closeMenu = () => {
|
||||
if (trigger.getAttribute('aria-expanded') === 'false') return;
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
trigger.removeAttribute('aria-activedescendant');
|
||||
popover.setAttribute('aria-hidden', 'true');
|
||||
trigger.focus();
|
||||
activeIndex = -1;
|
||||
};
|
||||
|
||||
const openMenu = () => {
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
popover.setAttribute('aria-hidden', 'false');
|
||||
menuItems = Array.from(menu.querySelectorAll('[role^="menuitem"]:not([disabled])'));
|
||||
if (menuItems.length > 0) {
|
||||
setActiveItem(0);
|
||||
}
|
||||
};
|
||||
|
||||
const setActiveItem = (index) => {
|
||||
if (activeIndex > -1 && menuItems[activeIndex]) {
|
||||
menuItems[activeIndex].classList.remove('active');
|
||||
@@ -17,36 +39,48 @@
|
||||
const activeItem = menuItems[activeIndex];
|
||||
activeItem.classList.add('active');
|
||||
trigger.setAttribute('aria-activedescendant', activeItem.id);
|
||||
activeItem.scrollIntoView({ block: 'nearest' });
|
||||
} else {
|
||||
trigger.removeAttribute('aria-activedescendant');
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (!popover.matches(':popover-open')) {
|
||||
trigger.addEventListener('click', () => {
|
||||
const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
|
||||
if (isExpanded) {
|
||||
closeMenu();
|
||||
} else {
|
||||
openMenu();
|
||||
}
|
||||
});
|
||||
|
||||
dropdownMenuComponent.addEventListener('keydown', (e) => {
|
||||
const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
if (isExpanded) closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isExpanded) {
|
||||
if (['ArrowDown', 'ArrowUp', 'Enter', ' '].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
trigger.click();
|
||||
openMenu();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let nextIndex = activeIndex;
|
||||
if (menuItems.length === 0) return;
|
||||
|
||||
let nextIndex = activeIndex;
|
||||
|
||||
switch (e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
if (activeIndex < menuItems.length - 1) {
|
||||
nextIndex = activeIndex + 1;
|
||||
}
|
||||
nextIndex = activeIndex < menuItems.length - 1 ? activeIndex + 1 : 0;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
if (activeIndex > 0) {
|
||||
nextIndex = activeIndex - 1;
|
||||
}
|
||||
nextIndex = activeIndex > 0 ? activeIndex - 1 : menuItems.length - 1;
|
||||
break;
|
||||
case 'Home':
|
||||
e.preventDefault();
|
||||
@@ -60,42 +94,24 @@
|
||||
case ' ':
|
||||
e.preventDefault();
|
||||
menuItems[activeIndex]?.click();
|
||||
break;
|
||||
closeMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
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])'));
|
||||
menuItems.forEach((item, index) => {
|
||||
if (!item.id) item.id = `${menu.id}-item-${index}`;
|
||||
});
|
||||
setActiveItem(0);
|
||||
} else {
|
||||
setActiveItem(-1);
|
||||
}
|
||||
});
|
||||
|
||||
menu.addEventListener('click', (e) => {
|
||||
if (e.target.closest('[role^="menuitem"]')) {
|
||||
popover.hidePopover();
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!dropdownMenuComponent.contains(e.target)) {
|
||||
closeMenu();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -107,15 +123,14 @@
|
||||
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);
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
||||
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 });
|
||||
})();
|
||||
@@ -0,0 +1,69 @@
|
||||
(() => {
|
||||
const initPopover = (popoverComponent) => {
|
||||
const trigger = popoverComponent.querySelector(':scope > button');
|
||||
const content = popoverComponent.querySelector(':scope > [data-popover]');
|
||||
|
||||
if (!trigger || !content) {
|
||||
console.error('Popover component is missing a trigger button or a content element.', popoverComponent);
|
||||
return;
|
||||
}
|
||||
|
||||
const closePopover = () => {
|
||||
if (trigger.getAttribute('aria-expanded') === 'false') return;
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
content.setAttribute('aria-hidden', 'true');
|
||||
trigger.focus();
|
||||
};
|
||||
|
||||
const openPopover = () => {
|
||||
const elementToFocus = content.querySelector('[autofocus]');
|
||||
if (elementToFocus) {
|
||||
content.addEventListener('transitionend', () => {
|
||||
elementToFocus.focus();
|
||||
}, { once: true });
|
||||
}
|
||||
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
content.setAttribute('aria-hidden', 'false');
|
||||
};
|
||||
|
||||
trigger.addEventListener('click', () => {
|
||||
const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
|
||||
if (isExpanded) {
|
||||
closePopover();
|
||||
} else {
|
||||
openPopover();
|
||||
}
|
||||
});
|
||||
|
||||
popoverComponent.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closePopover();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!popoverComponent.contains(e.target)) {
|
||||
closePopover();
|
||||
}
|
||||
});
|
||||
|
||||
popoverComponent.dataset.popoverInitialized = true;
|
||||
};
|
||||
|
||||
document.querySelectorAll('.popover:not([data-popover-initialized])').forEach(initPopover);
|
||||
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
mutation.addedNodes.forEach((node) => {
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
||||
if (node.matches('.popover:not([data-popover-initialized])')) {
|
||||
initPopover(node);
|
||||
}
|
||||
node.querySelectorAll('.popover:not([data-popover-initialized])').forEach(initPopover);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
})();
|
||||
+59
-49
@@ -1,8 +1,8 @@
|
||||
(() => {
|
||||
const initSelect = (selectComponent) => {
|
||||
const trigger = selectComponent.querySelector(':scope > [popovertarget]');
|
||||
const trigger = selectComponent.querySelector(':scope > button');
|
||||
const selectedValue = trigger.querySelector(':scope > span');
|
||||
const popover = selectComponent.querySelector(':scope > [popover]');
|
||||
const popover = selectComponent.querySelector(':scope > [data-popover]');
|
||||
const listbox = popover.querySelector('[role="listbox"]');
|
||||
const input = selectComponent.querySelector(':scope > input[type="hidden"]');
|
||||
const filter = selectComponent.querySelector('header input[type="text"]');
|
||||
@@ -21,15 +21,26 @@
|
||||
}
|
||||
};
|
||||
|
||||
const closePopover = () => {
|
||||
popover.setAttribute('aria-hidden', 'true');
|
||||
trigger.setAttribute('aria-expanded', 'false');
|
||||
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;
|
||||
}
|
||||
|
||||
const selectOption = (option) => {
|
||||
if (!option) return;
|
||||
|
||||
updateValue(option);
|
||||
|
||||
trigger.removeAttribute('aria-activedescendant');
|
||||
options.forEach(opt => opt.classList.remove('active'));
|
||||
activeIndex = -1;
|
||||
popover.hidePopover();
|
||||
if (option.dataset.value) {
|
||||
updateValue(option);
|
||||
}
|
||||
closePopover();
|
||||
};
|
||||
|
||||
if (filter) {
|
||||
@@ -62,12 +73,14 @@
|
||||
updateValue(initialOption);
|
||||
|
||||
const handleKeyNavigation = (e) => {
|
||||
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Home', 'End'].includes(e.key)) {
|
||||
const isPopoverOpen = popover.getAttribute('aria-hidden') === 'false';
|
||||
|
||||
if (!['ArrowDown', 'ArrowUp', 'Enter', 'Home', 'End', 'Escape'].includes(e.key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!popover.matches(':popover-open')) {
|
||||
if (e.currentTarget === trigger && e.key !== 'Enter') {
|
||||
if (!isPopoverOpen) {
|
||||
if (e.key !== 'Enter' && e.key !== 'Escape') {
|
||||
e.preventDefault();
|
||||
trigger.click();
|
||||
}
|
||||
@@ -76,9 +89,14 @@
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
closePopover();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === 'Enter') {
|
||||
if (activeIndex > -1) {
|
||||
selectOption(options[activeIndex]);
|
||||
selectOption(visibleOptions[activeIndex]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -130,6 +148,31 @@
|
||||
filter.addEventListener('keydown', handleKeyNavigation);
|
||||
}
|
||||
|
||||
trigger.addEventListener('click', () => {
|
||||
const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
|
||||
|
||||
if (isExpanded) {
|
||||
closePopover();
|
||||
} else {
|
||||
popover.setAttribute('aria-hidden', 'false');
|
||||
trigger.setAttribute('aria-expanded', 'true');
|
||||
if (filter) filter.focus();
|
||||
|
||||
const selectedOption = listbox.querySelector('[role="option"][aria-selected="true"]');
|
||||
if (selectedOption) {
|
||||
if (activeIndex > -1) {
|
||||
options[activeIndex]?.classList.remove('active');
|
||||
}
|
||||
activeIndex = options.indexOf(selectedOption);
|
||||
selectedOption.classList.add('active');
|
||||
if (selectedOption.id) {
|
||||
trigger.setAttribute('aria-activedescendant', selectedOption.id);
|
||||
}
|
||||
selectedOption.scrollIntoView({ block: 'nearest' });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
listbox.addEventListener('click', (e) => {
|
||||
const clickedOption = e.target.closest('[role="option"]');
|
||||
if (clickedOption) {
|
||||
@@ -137,46 +180,13 @@
|
||||
}
|
||||
});
|
||||
|
||||
popover.addEventListener('toggle', (e) => {
|
||||
trigger.setAttribute('aria-expanded', e.newState === 'open');
|
||||
|
||||
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;
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!selectComponent.contains(e.target)) {
|
||||
closePopover();
|
||||
}
|
||||
});
|
||||
|
||||
popover.setAttribute('aria-hidden', 'true');
|
||||
selectComponent.dataset.selectInitialized = true;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
@param footer {string} [optional] - HTML content for the dialog footer.
|
||||
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
|
||||
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
|
||||
@param content_attrs {object} [optional] - Additional HTML attributes for the dialog content article.
|
||||
@param content_header_attrs {object} [optional] - Additional HTML attributes for the dialog header.
|
||||
@param content_body_attrs {object} [optional] - Additional HTML attributes for the dialog body section.
|
||||
@param content_footer_attrs {object} [optional] - Additional HTML attributes for the dialog footer.
|
||||
@param dialog_attrs {object} [optional] - Additional HTML attributes for the dialog content article.
|
||||
@param header_attrs {object} [optional] - Additional HTML attributes for the dialog header.
|
||||
@param body_attrs {object} [optional] - Additional HTML attributes for the dialog body section.
|
||||
@param footer_attrs {object} [optional] - Additional HTML attributes for the dialog footer.
|
||||
@param open {boolean} [optional] [default=false] - Whether the dialog should be open initially.
|
||||
@param close_button {boolean} [optional] [default=true] - Whether to include a close button.
|
||||
@param close_on_overlay_click {boolean} [optional] [default=true] - Whether clicking the overlay closes the dialog.
|
||||
|
||||
@@ -6,23 +6,28 @@
|
||||
@param menu {array} [optional] - Array of menu items for the dropdown.
|
||||
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
|
||||
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
|
||||
@param content_attrs {object} [optional] - Additional HTML attributes for the dropdown content div.
|
||||
@param popover_attrs {object} [optional] - Additional HTML attributes for the dropdown content div.
|
||||
#}
|
||||
{% macro dropdown_menu(
|
||||
id=None,
|
||||
trigger,
|
||||
items=None,
|
||||
main_attrs={},
|
||||
trigger_attrs={},
|
||||
popover_attrs={},
|
||||
menu_attrs={}
|
||||
) %}
|
||||
{% set id = id or ("dropdown-menu-" + (range(100000, 999999) | random | string)) %}
|
||||
|
||||
<div class="dropdown-menu">
|
||||
<div
|
||||
class="dropdown-menu {{ main_attrs.class }}"
|
||||
{% for key, value in main_attrs %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
id="{{ id }}-trigger"
|
||||
popovertarget="{{ id }}"
|
||||
aria-haspopup="menu"
|
||||
aria-controls="{{ id }}-menu"
|
||||
aria-expanded="false"
|
||||
@@ -33,14 +38,14 @@
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
<div
|
||||
popover
|
||||
class="popover p-1 {% if popover_attrs.class %} {{ popover_attrs.class }}{% endif %}"
|
||||
id="{{ id }}"
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
{% for key, value in popover_attrs %}
|
||||
{% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<nav
|
||||
<div
|
||||
role="menu"
|
||||
id="{{ id }}-menu"
|
||||
aria-labelledby="{{ id }}-trigger"
|
||||
@@ -53,7 +58,7 @@
|
||||
{% else %}
|
||||
{{ caller() if caller }}
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -77,7 +82,7 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
|
||||
<div role="presentation" id="{{ group_label_id }}">{{ item.label }}</div>
|
||||
{{ render_dropdown_items(item.items, item_id) if item.items }}
|
||||
</div>
|
||||
{% elif item.type == "separator" %}
|
||||
@@ -85,6 +90,7 @@
|
||||
{% elif item.type == "item" or not item.type %}
|
||||
{% if item.url %}
|
||||
<a
|
||||
id="{{ item_id }}"
|
||||
role="menuitem"
|
||||
href="{{ item.url }}"
|
||||
{% for key, value in item.attrs %}
|
||||
@@ -94,15 +100,15 @@
|
||||
{{ item.label | safe }}
|
||||
</a>
|
||||
{% else %}
|
||||
<button
|
||||
<div
|
||||
id="{{ item_id }}"
|
||||
role="menuitem"
|
||||
type="button"
|
||||
{% for key, value in item.attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ item.label | safe }}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
+27
-19
@@ -5,35 +5,43 @@
|
||||
@param trigger {string} [optional] - HTML content for the element that triggers the popover.
|
||||
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
|
||||
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger element.
|
||||
@param content_attrs {object} [optional] - Additional HTML attributes for the popover content div.
|
||||
@param popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
|
||||
#}
|
||||
{% macro popover(
|
||||
id=None,
|
||||
trigger,
|
||||
main_attrs={},
|
||||
trigger_attrs={},
|
||||
popover_attrs={}
|
||||
) %}
|
||||
{% set id = id or ("popover-" + (range(100000, 999999) | random | string)) %}
|
||||
<button
|
||||
type="button"
|
||||
popovertarget="{{ id }}"
|
||||
id="{{ id }}-trigger"
|
||||
onclick="this.setAttribute('aria-expanded', this.getAttribute('aria-expanded') === 'false' ? 'true' : 'false')"
|
||||
aria-expanded="false"
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
|
||||
<div
|
||||
popover
|
||||
id="{{ id }}"
|
||||
class="popover {{ popover_attrs.class }}"
|
||||
{% for key, value in popover_attrs %}
|
||||
class="popover {{ main_attrs.class }}"
|
||||
{% for key, value in main_attrs %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{{ caller() if caller }}
|
||||
>
|
||||
<button
|
||||
id="{{ id }}-trigger"
|
||||
type="button"
|
||||
aria-expanded="false"
|
||||
aria-controls="{{ id }}"
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
<div
|
||||
id="{{ id }}"
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
{% for key, value in popover_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ caller() if caller }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
+34
-26
@@ -6,7 +6,7 @@
|
||||
@param name {string} [optional] - The name attribute for the hidden input storing the selected value.
|
||||
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
|
||||
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
|
||||
@param content_attrs {object} [optional] - Additional HTML attributes for the popover content div.
|
||||
@param popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
|
||||
@param listbox_attrs {object} [optional] - Additional HTML attributes for the listbox div.
|
||||
@param input_attrs {object} [optional] - Additional HTML attributes for the hidden input.
|
||||
@param search_placeholder {string} [optional] [default="Search entries..."] - Placeholder text for the search input (combobox only).
|
||||
@@ -17,9 +17,11 @@
|
||||
selected=None,
|
||||
name=None,
|
||||
items=None,
|
||||
main_attrs={},
|
||||
trigger_attrs={},
|
||||
popover_attrs={},
|
||||
listbox_attrs={},
|
||||
input_attrs={},
|
||||
search_placeholder="Search entries...",
|
||||
is_combobox=false
|
||||
) %}
|
||||
@@ -28,34 +30,40 @@
|
||||
{% set first_option = [] %}
|
||||
{% set selected_option = [] %}
|
||||
|
||||
{% for item in items %}
|
||||
{% if item.type == "group" %}
|
||||
{% for sub_item in item.items %}
|
||||
{% if items %}
|
||||
{% for item in items %}
|
||||
{% if item.type == "group" %}
|
||||
{% for sub_item in item.items %}
|
||||
{% if not first_option[0] %}
|
||||
{% set first_option = (first_option.push(sub_item), first_option) %}
|
||||
{% endif %}
|
||||
{% if selected and sub_item.value == selected and not selected_option[0] %}
|
||||
{% set selected_option = (selected_option.push(sub_item), selected_option) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% if not first_option[0] %}
|
||||
{% set first_option = (first_option.push(sub_item), first_option) %}
|
||||
{% set first_option = (first_option.push(item), first_option) %}
|
||||
{% endif %}
|
||||
{% if selected and sub_item.value == selected and not selected_option[0] %}
|
||||
{% set selected_option = (selected_option.push(sub_item), selected_option) %}
|
||||
{% if selected and item.value == selected and not selected_option[0] %}
|
||||
{% set selected_option = (selected_option.push(item), selected_option) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% if not first_option[0] %}
|
||||
{% set first_option = (first_option.push(item), first_option) %}
|
||||
{% endif %}
|
||||
{% if selected and item.value == selected and not selected_option[0] %}
|
||||
{% set selected_option = (selected_option.push(item), selected_option) %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% set default_option = selected_option[0] or first_option[0] or None %}
|
||||
|
||||
<div class="select">
|
||||
<div
|
||||
class="select {{ main_attrs.class }}"
|
||||
{% for key, value in main_attrs %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-outline justify-between font-normal {{ trigger_attrs.class }}"
|
||||
id="{{ id }}-trigger"
|
||||
popovertarget="{{ id }}"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded="false"
|
||||
aria-controls="{{ id }}-listbox"
|
||||
@@ -71,11 +79,11 @@
|
||||
{% endif %}
|
||||
</button>
|
||||
<div
|
||||
popover
|
||||
id="{{ id }}"
|
||||
class="popover {{ popover_attrs.class }}"
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
{% for key, value in popover_attrs %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if is_combobox %}
|
||||
@@ -90,8 +98,8 @@
|
||||
spellcheck="false"
|
||||
aria-autocomplete="list"
|
||||
role="combobox"
|
||||
aria-expanded="true"
|
||||
aria-controls="{{ id }}-content"
|
||||
aria-expanded="false"
|
||||
aria-controls="{{ id }}-listbox"
|
||||
aria-labelledby="{{ id }}-trigger"
|
||||
>
|
||||
</header>
|
||||
@@ -105,7 +113,7 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if items.length > 0 %}
|
||||
{% if items and items.length > 0 %}
|
||||
{{ render_select_items(items, default_option.value, id ~ "-items" if id else "items") }}
|
||||
{% else %}
|
||||
{{ caller() if caller }}
|
||||
@@ -115,7 +123,7 @@
|
||||
<input
|
||||
type="hidden"
|
||||
name="{{ name or id ~ '-value' }}"
|
||||
value="{{ selected or '' }}"
|
||||
value="{{ (default_option.value if default_option) or '' }}"
|
||||
{% for key, value in input_attrs %}
|
||||
{% if key != 'name' and key != 'value' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
@@ -141,7 +149,7 @@
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
|
||||
<div role="presentation" id="{{ group_label_id }}">{{ item.label }}</div>
|
||||
{{ render_select_items(item.items, selected, item_id) if item.items }}
|
||||
</div>
|
||||
{% elif item.type == "separator" %}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
) %}
|
||||
<div
|
||||
id="{{ id }}"
|
||||
class="toaster"
|
||||
class="toaster {{ attrs.class }}"
|
||||
{% for key, value in attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{% for item in toasts %}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "basecoat-docs",
|
||||
"compatibility_date": "2025-06-15",
|
||||
"assets": {
|
||||
"directory": "./_site"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user