mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 12:48:19 +00:00
Compare commits
5
Commits
0.3.10-beta.2
...
0.3.11
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a54d5fb4eb | ||
|
|
8445f6d150 | ||
|
|
713c163fb8 | ||
|
|
a7f686e71b | ||
|
|
128f79c2e4 |
@@ -1,6 +1,43 @@
|
||||
{% from "sidebar.njk" import sidebar %}
|
||||
{% from "popover.njk" import popover %}
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
if (!sidebar) return;
|
||||
|
||||
const updateCurrentLinks = () => {
|
||||
const currentPath = window.location.pathname.replace(/\/$/, '');
|
||||
const currentSearch = window.location.search;
|
||||
const currentKey = `${currentPath}${currentSearch}`;
|
||||
|
||||
sidebar.querySelectorAll('a, button').forEach((item) => {
|
||||
const href = item.getAttribute('href');
|
||||
if (!href) {
|
||||
item.removeAttribute('aria-current');
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(href, window.location.origin);
|
||||
const itemPath = url.pathname.replace(/\/$/, '');
|
||||
const itemKey = `${itemPath}${url.search}`;
|
||||
|
||||
if (itemKey === currentKey) {
|
||||
item.setAttribute('aria-current', 'page');
|
||||
} else {
|
||||
item.removeAttribute('aria-current');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener('htmx:afterSettle', updateCurrentLinks);
|
||||
window.addEventListener('popstate', updateCurrentLinks);
|
||||
updateCurrentLinks();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% set footer %}
|
||||
{% set trigger %}
|
||||
<img src="https://github.com/hunvreus.png" class="rounded-lg shrink-0 size-8">
|
||||
@@ -136,4 +173,4 @@
|
||||
footer=footer,
|
||||
content_attrs={ "class": "scrollbar [&_[data-new-link]::after]:content-['New'] [&_[data-new-link]::after]:ml-auto [&_[data-new-link]::after]:text-xs [&_[data-new-link]::after]:font-medium [&_[data-new-link]::after]:bg-sidebar-primary [&_[data-new-link]::after]:text-sidebar-primary-foreground [&_[data-new-link]::after]:px-2 [&_[data-new-link]::after]:py-0.5 [&_[data-new-link]::after]:rounded-md" },
|
||||
menu=menu
|
||||
) }}
|
||||
) }}
|
||||
@@ -2794,7 +2794,6 @@
|
||||
}
|
||||
@layer components {
|
||||
.button-group {
|
||||
isolation: isolate;
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
align-items: stretch;
|
||||
|
||||
@@ -55,16 +55,15 @@ toc:
|
||||
{{ select(
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
is_combobox=true,
|
||||
multiple=true,
|
||||
items=[
|
||||
{
|
||||
type: "group",
|
||||
label: "Fruits",
|
||||
items: [
|
||||
{ type: "item", label: "Apple", attrs: {"data-force": "true"} },
|
||||
{ type: "item", label: "Banana", attrs: {"data-keywords": "bread"} },
|
||||
{ type: "item", label: "Blueberry" },
|
||||
{ type: "item", value: "pineapple", label: "Grapes" },
|
||||
{ type: "item", value: "apple", label: "Apple" },
|
||||
{ type: "item", value: "banana", label: "Banana" },
|
||||
{ type: "item", value: "blueberry", label: "Blueberry" },
|
||||
{ type: "item", value: "grapes", label: "Grapes" },
|
||||
{ type: "item", value: "pineapple", label: "Pineapple" }
|
||||
]
|
||||
}
|
||||
@@ -127,6 +126,7 @@ toc:
|
||||
<p>Wraps around the entire component. Can have the following attributes:</p>
|
||||
<ul>
|
||||
<li><code>data-placeholder="{ TEXT }"</code> <span class="badge-secondary">Optional</span>: placeholder text shown when no options are selected (multiselect only).</li>
|
||||
<li><code>data-close-on-select="true"</code> <span class="badge-secondary">Optional</span>: closes the popover when selecting an option in multiselect mode.</li>
|
||||
</ul>
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><button type="button"></code></dt>
|
||||
|
||||
@@ -123,9 +123,9 @@ toc:
|
||||
<dd>Individual item.
|
||||
<dl>
|
||||
<dt><code class="highlight language-html"><a></code></dt>
|
||||
<dd>A link. By default, clicking on a link will close the sidebar on mobile unless the <code>data-keep-mobile-sidebar-open</code> attribute is present.</dd>
|
||||
<dd>A link. By default, clicking on a link will close the sidebar on mobile unless the <code>data-keep-mobile-sidebar-open</code> attribute is present. Set <code>aria-current="page"</code> on the active link.</dd>
|
||||
<dt><code class="highlight language-html"><button></code></dt>
|
||||
<dd>A button. By default, clicking on a button will close the sidebar on mobile unless the <code>data-keep-mobile-sidebar-open</code> attribute is present.</dd>
|
||||
<dd>A button. By default, clicking on a button will close the sidebar on mobile unless the <code>data-keep-mobile-sidebar-open</code> attribute is present. If your app uses buttons for navigation, set <code>aria-current="page"</code> on the active item.</dd>
|
||||
<dt><code class="highlight language-html"><details></code></dt>
|
||||
<dd>Collapsible section.
|
||||
<dl>
|
||||
@@ -154,6 +154,15 @@ toc:
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
{% set code_current %}<!-- Server-side or router-driven current link -->
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/my-path?page=overview">Overview</a></li>
|
||||
<li><a href="/my-path?page=settings" aria-current="page">Settings</a></li>
|
||||
</ul>
|
||||
</nav>{% endset %}
|
||||
{{ code_block(code_current | prettyHtml, "html") }}
|
||||
|
||||
<h4 id="usage-html-js-4"><a href="#usage-html-js-4">JavaScript events</a></h4>
|
||||
|
||||
<section class="prose">
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "basecoat",
|
||||
"version": "0.3.10-beta.2",
|
||||
"version": "0.3.11",
|
||||
"description": "shadcn/ui for the rest of us",
|
||||
"author": "hunvreus",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "basecoat-cli",
|
||||
"version": "0.3.10-beta.2",
|
||||
"version": "0.3.11",
|
||||
"description": "Add Basecoat components to your project",
|
||||
"author": "hunvreus",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "basecoat-css",
|
||||
"version": "0.3.10-beta.2",
|
||||
"version": "0.3.11",
|
||||
"description": "Tailwind CSS for Basecoat components",
|
||||
"author": {
|
||||
"name": "hunvreus",
|
||||
|
||||
@@ -388,7 +388,8 @@
|
||||
/* Button Group */
|
||||
@layer components {
|
||||
.button-group {
|
||||
@apply inline-flex w-fit items-stretch isolate;
|
||||
@apply inline-flex w-fit items-stretch;
|
||||
/* isolate */
|
||||
|
||||
> *:focus-visible,
|
||||
> :is(.dropdown-menu, .popover, .select) > button:focus-visible {
|
||||
|
||||
@@ -83,12 +83,10 @@
|
||||
</footer>
|
||||
{% endif %}
|
||||
{% if close_button %}
|
||||
<form method="dialog">
|
||||
<button aria-label="Close dialog">
|
||||
<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-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
<button type="button" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
||||
<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-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</dialog>
|
||||
{% endmacro %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@param name {string} [optional] - The name attribute for the hidden input storing the selected value.
|
||||
@param multiple {boolean} [optional] [default=false] - Enables multiple selection mode.
|
||||
@param placeholder {string} [optional] - Placeholder text shown when no options are selected (multiple mode only).
|
||||
@param close_on_select {boolean} [optional] [default=false] - Closes the popover when selecting an option in multiple mode.
|
||||
@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 popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
|
||||
@@ -21,6 +22,7 @@
|
||||
items=[],
|
||||
multiple=false,
|
||||
placeholder=None,
|
||||
close_on_select=false,
|
||||
main_attrs={},
|
||||
trigger_attrs={},
|
||||
popover_attrs={},
|
||||
@@ -76,6 +78,7 @@
|
||||
id="{{ id }}"
|
||||
class="select {{ main_attrs.class }}"
|
||||
{% if multiple and placeholder %}data-placeholder="{{ placeholder }}"{% endif %}
|
||||
{% if multiple and close_on_select %}data-close-on-select="true"{% endif %}
|
||||
{% for key, value in main_attrs.items() %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
+12
-4
@@ -24,6 +24,7 @@
|
||||
const isMultiple = listbox.getAttribute('aria-multiselectable') === 'true';
|
||||
const selectedOptions = isMultiple ? new Set() : null;
|
||||
const placeholder = isMultiple ? (selectComponent.dataset.placeholder || '') : null;
|
||||
const closeOnSelect = selectComponent.dataset.closeOnSelect === 'true';
|
||||
|
||||
const getValue = (opt) => opt.dataset.value ?? opt.textContent.trim();
|
||||
|
||||
@@ -248,6 +249,9 @@
|
||||
const option = options[activeIndex];
|
||||
if (isMultiple) {
|
||||
toggleMultipleValue(option);
|
||||
if (closeOnSelect) {
|
||||
closePopover();
|
||||
}
|
||||
} else {
|
||||
if (input.value !== getValue(option)) {
|
||||
updateValue(option);
|
||||
@@ -358,11 +362,15 @@
|
||||
|
||||
if (isMultiple) {
|
||||
toggleMultipleValue(option);
|
||||
setActiveOption(options.indexOf(option));
|
||||
if (filter) {
|
||||
filter.focus();
|
||||
if (closeOnSelect) {
|
||||
closePopover();
|
||||
} else {
|
||||
trigger.focus();
|
||||
setActiveOption(options.indexOf(option));
|
||||
if (filter) {
|
||||
filter.focus();
|
||||
} else {
|
||||
trigger.focus();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (input.value !== getValue(option)) {
|
||||
|
||||
+1
-36
@@ -1,21 +1,4 @@
|
||||
(() => {
|
||||
// Monkey patching the history API to detect client-side navigation
|
||||
if (!window.history.__basecoatPatched) {
|
||||
const originalPushState = window.history.pushState;
|
||||
window.history.pushState = function(...args) {
|
||||
originalPushState.apply(this, args);
|
||||
window.dispatchEvent(new Event('basecoat:locationchange'));
|
||||
};
|
||||
|
||||
const originalReplaceState = window.history.replaceState;
|
||||
window.history.replaceState = function(...args) {
|
||||
originalReplaceState.apply(this, args);
|
||||
window.dispatchEvent(new Event('basecoat:locationchange'));
|
||||
};
|
||||
|
||||
window.history.__basecoatPatched = true;
|
||||
}
|
||||
|
||||
const initSidebar = (sidebarComponent) => {
|
||||
const initialOpen = sidebarComponent.dataset.initialOpen !== 'false';
|
||||
const initialMobileOpen = sidebarComponent.dataset.initialMobileOpen === 'true';
|
||||
@@ -25,20 +8,6 @@
|
||||
? (window.innerWidth >= breakpoint ? initialOpen : initialMobileOpen)
|
||||
: initialOpen;
|
||||
|
||||
const updateCurrentPageLinks = () => {
|
||||
const currentPath = window.location.pathname.replace(/\/$/, '');
|
||||
sidebarComponent.querySelectorAll('a').forEach(link => {
|
||||
if (link.hasAttribute('data-ignore-current')) return;
|
||||
|
||||
const linkPath = new URL(link.href).pathname.replace(/\/$/, '');
|
||||
if (linkPath === currentPath) {
|
||||
link.setAttribute('aria-current', 'page');
|
||||
} else {
|
||||
link.removeAttribute('aria-current');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const updateState = () => {
|
||||
sidebarComponent.setAttribute('aria-hidden', !open);
|
||||
if (open) {
|
||||
@@ -89,11 +58,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', updateCurrentPageLinks);
|
||||
window.addEventListener('basecoat:locationchange', updateCurrentPageLinks);
|
||||
|
||||
updateState();
|
||||
updateCurrentPageLinks();
|
||||
sidebarComponent.dataset.sidebarInitialized = true;
|
||||
sidebarComponent.dispatchEvent(new CustomEvent('basecoat:initialized'));
|
||||
};
|
||||
@@ -101,4 +66,4 @@
|
||||
if (window.basecoat) {
|
||||
window.basecoat.register('sidebar', '.sidebar:not([data-sidebar-initialized])', initSidebar);
|
||||
}
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@param name {string} [optional] - The name attribute for the hidden input storing the selected value.
|
||||
@param multiple {boolean} [optional] [default=false] - Enables multiple selection mode.
|
||||
@param placeholder {string} [optional] - Placeholder text shown when no options are selected (multiple mode only).
|
||||
@param close_on_select {boolean} [optional] [default=false] - Closes the popover when selecting an option in multiple mode.
|
||||
@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 popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
|
||||
@@ -21,6 +22,7 @@
|
||||
items=None,
|
||||
multiple=false,
|
||||
placeholder=None,
|
||||
close_on_select=false,
|
||||
main_attrs={},
|
||||
trigger_attrs={},
|
||||
popover_attrs={},
|
||||
@@ -76,6 +78,7 @@
|
||||
id="{{ id }}"
|
||||
class="select {{ main_attrs.class }}"
|
||||
{% if multiple and placeholder %}data-placeholder="{{ placeholder }}"{% endif %}
|
||||
{% if multiple and close_on_select %}data-close-on-select="true"{% endif %}
|
||||
{% for key, value in main_attrs %}
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
Reference in New Issue
Block a user