mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-13 13:48:28 +00:00
Starting work on documentation
This commit is contained in:
@@ -27,21 +27,21 @@
|
||||
this.open = initialOpen;
|
||||
this.closeOnOverlayClick = initialCloseOnOverlayClick;
|
||||
},
|
||||
|
||||
show() {
|
||||
if (!this.open) {
|
||||
this.open = true;
|
||||
this.$nextTick(() => this.$el.dispatchEvent(new CustomEvent('dialog:opened', { bubbles: true })));
|
||||
this.$nextTick(() => {
|
||||
this.$el.dispatchEvent(new CustomEvent('dialog:opened', { bubbles: true }));
|
||||
setTimeout(() => this.$refs.dialog.focus(), 50);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
hide() {
|
||||
if (this.open) {
|
||||
this.open = false;
|
||||
this.$el.dispatchEvent(new CustomEvent('dialog:closed', { bubbles: true }));
|
||||
}
|
||||
},
|
||||
|
||||
handleControl(event) {
|
||||
const detail = event.detail;
|
||||
if (detail && detail.targetId === this.id) {
|
||||
@@ -52,9 +52,7 @@
|
||||
},
|
||||
|
||||
$main: {
|
||||
'@dialog:control.window'(e) {
|
||||
this.handleControl(e)
|
||||
},
|
||||
'@dialog:control.window'(e) { this.handleControl(e) },
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
@@ -63,9 +61,9 @@
|
||||
},
|
||||
|
||||
$content: {
|
||||
'@keydown.escape'() { this.open && this.hide() },
|
||||
':inert'() { return !this.open },
|
||||
'@click'(e) { e.target === this.$el && this.closeOnOverlayClick && this.hide() },
|
||||
'@keydown.escape.prevent'() { this.open && this.hide() },
|
||||
'x-cloak': ''
|
||||
}
|
||||
}));
|
||||
@@ -97,6 +95,8 @@
|
||||
<div
|
||||
role="dialog"
|
||||
id="{{ id }}-dialog"
|
||||
x-ref="dialog"
|
||||
tabindex="-1"
|
||||
aria-modal="true"
|
||||
aria-labelledby="{{ id }}-title"
|
||||
inert
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
{% macro dropdown_menu(
|
||||
id=None,
|
||||
trigger=None,
|
||||
main_attrs=None,
|
||||
trigger_attrs=None,
|
||||
content_attrs=None
|
||||
) %}
|
||||
{% if registerComponent('dropdown-menu') %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('dropdownMenu', () => ({
|
||||
open: false,
|
||||
focusedIndex: null,
|
||||
menuItems: [],
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.menuItems = Array.from(this.$el.querySelectorAll('[role=menuitem]:not([disabled])'));
|
||||
});
|
||||
},
|
||||
focusMenuitem() {
|
||||
if (this.menuItems.length === 0) return;
|
||||
|
||||
if (this.focusedIndex >= this.menuItems.length) {
|
||||
this.focusedIndex = this.menuItems.length - 1;
|
||||
} else if (this.focusedIndex < 0 || this.focusedIndex === null) {
|
||||
this.focusedIndex = 0;
|
||||
}
|
||||
this.menuItems.forEach(item => item.blur());
|
||||
this.menuItems[this.focusedIndex].focus();
|
||||
},
|
||||
moveMenuitemFocus(delta) {
|
||||
if (this.menuItems.length === 0) return;
|
||||
|
||||
let wasOpen = this.open;
|
||||
if (!this.open) {
|
||||
this.open = true;
|
||||
this.focusedIndex = 0;
|
||||
} else {
|
||||
this.focusedIndex = this.focusedIndex === null
|
||||
? 0
|
||||
: this.focusedIndex + delta;
|
||||
}
|
||||
|
||||
if (wasOpen) {
|
||||
this.focusMenuitem();
|
||||
} else {
|
||||
setTimeout(() => this.focusMenuitem(), 50);
|
||||
}
|
||||
},
|
||||
handleMenuitemClick(event) {
|
||||
const menuitem = event.target.closest('[role=menuitem]');
|
||||
if (menuitem && menuitem.getAttribute('aria-disabled') !== 'true' && !menuitem.disabled) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.trigger.focus();
|
||||
this.open = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
handleMenuitemMousemove(event) {
|
||||
const menuitem = event.target.closest('[role=menuitem]');
|
||||
if (menuitem && menuitem.getAttribute('aria-disabled') !== 'true' && !menuitem.disabled) {
|
||||
this.focusedIndex = this.menuItems.indexOf(menuitem);
|
||||
this.focusMenuitem();
|
||||
}
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.open = !this.open },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
'@keydown.down.prevent'() { this.moveMenuitemFocus(+1); },
|
||||
'@keydown.up.prevent'() { this.moveMenuitemFocus(-1); },
|
||||
'@keydown.home.prevent'() { this.focusMenuitem(0) },
|
||||
'@keydown.end.prevent'() { this.focusMenuitem(this.menuItems.length - 1) },
|
||||
'@keydown.enter.prevent'() { this.open = !this.open },
|
||||
':aria-expanded'() { return this.open },
|
||||
'x-ref': 'trigger'
|
||||
},
|
||||
$content: {
|
||||
'@click'(e) { this.handleMenuitemClick(e) },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
'@keydown.down.prevent'() { this.moveMenuitemFocus(+1); },
|
||||
'@keydown.up.prevent'() { this.moveMenuitemFocus(-1); },
|
||||
'@keydown.home.prevent'() { this.focusMenuitem(0) },
|
||||
'@keydown.end.prevent'() { this.focusMenuitem(this.menuItems.length - 1) },
|
||||
'@mousemove'(e) { this.handleMenuitemMousemove(e) },
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
},
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<div
|
||||
class="popover {% if main_attrs %}{{ main_attrs.pop('class', '') }}{% endif %}"
|
||||
x-data="dropdownMenu"
|
||||
@click.away="open = false"
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
{% for key, value in main_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{% if trigger %}
|
||||
<button
|
||||
type="button"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
x-bind="$trigger"
|
||||
{% if id %}
|
||||
id="{{ id }}-trigger"
|
||||
aria-controls="{{ id }}-menu"
|
||||
{% endif %}
|
||||
{% for key, value in trigger_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
{% endif %}
|
||||
<div
|
||||
data-popover
|
||||
aria-hidden="true"
|
||||
x-bind="$content"
|
||||
{% if id %}id="{{ id }}-menu"{% endif %}
|
||||
{% for key, value in content_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
<nav role="menu">
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -10,76 +10,22 @@
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('popover', () => ({
|
||||
open: false,
|
||||
focusedIndex: null,
|
||||
menuItems: [],
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.menuItems = Array.from(this.$el.querySelectorAll('[role=menuitem]'));
|
||||
});
|
||||
},
|
||||
|
||||
handleTriggerClick() {
|
||||
this.open = !this.open;
|
||||
},
|
||||
|
||||
handleEscape() {
|
||||
if (this.open) this.open = false;
|
||||
},
|
||||
|
||||
focusNavItem() {
|
||||
if (this.menuItems.length === 0) return;
|
||||
if (this.focusedIndex >= this.menuItems.length) {
|
||||
this.focusedIndex = this.menuItems.length - 1;
|
||||
}
|
||||
if (this.focusedIndex < 0) {
|
||||
this.focusedIndex = 0;
|
||||
}
|
||||
this.menuItems[this.focusedIndex].focus();
|
||||
},
|
||||
|
||||
nextNavItem() {
|
||||
if (this.menuItems.length === 0) return;
|
||||
if (this.focusedIndex === null) {
|
||||
this.focusedIndex = 0;
|
||||
} else {
|
||||
this.focusedIndex++;
|
||||
}
|
||||
this.focusNavItem();
|
||||
},
|
||||
|
||||
prevNavItem() {
|
||||
if (this.menuItems.length === 0) return;
|
||||
if (this.focusedIndex === null) {
|
||||
this.focusedIndex = 0;
|
||||
} else {
|
||||
this.focusedIndex--;
|
||||
}
|
||||
this.focusNavItem();
|
||||
},
|
||||
|
||||
handleNavItemClick(event) {
|
||||
if (this.menuItems.length === 0) return;
|
||||
const menuItem = event.target.closest('[role=menuitem]');
|
||||
if (menuItem && !menuItem.disabled) {
|
||||
menuItem.blur();
|
||||
this.open = false;
|
||||
}
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.handleTriggerClick() },
|
||||
'@keydown.down.prevent'() { this.open && this.nextNavItem() },
|
||||
'@click'() { this.open = !this.open },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
':aria-expanded'() { return this.open },
|
||||
'x-ref': 'trigger'
|
||||
},
|
||||
|
||||
$content: {
|
||||
'@click'(e) { this.handleNavItemClick(e) },
|
||||
'@keydown.escape.prevent'() { this.handleEscape() },
|
||||
'@keydown.down.prevent'() { this.nextNavItem() },
|
||||
'@keydown.up.prevent'() { this.prevNavItem() },
|
||||
'@keydown.home.prevent'() { this.focusNavItem(0) },
|
||||
'@keydown.end.prevent'() { this.focusNavItem(this.menuItems.length - 1) },
|
||||
'@click'(e) { this.handleMenuitemClick(e) },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
},
|
||||
|
||||
@@ -46,7 +46,9 @@
|
||||
this.focusedIndex = 0;
|
||||
}
|
||||
this.options.forEach(opt => opt.removeAttribute('data-focus'));
|
||||
this.options[this.focusedIndex].setAttribute('data-focus', '');
|
||||
const option = this.options[this.focusedIndex];
|
||||
option.setAttribute('data-focus', '');
|
||||
option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
||||
},
|
||||
focusOnSelectedOption() {
|
||||
if (this.options.length === 0 || this.selectedValue === null) return;
|
||||
@@ -77,7 +79,6 @@
|
||||
}
|
||||
},
|
||||
handleOptionMousemove(event) {
|
||||
console.log("here");
|
||||
const option = event.target.closest('[role=option]');
|
||||
if (option && option.getAttribute('aria-disabled') !== 'true') {
|
||||
this.focusedIndex = this.options.indexOf(option);
|
||||
@@ -126,18 +127,19 @@
|
||||
'@keydown.up.prevent'() { this.moveOptionFocus(-1); },
|
||||
'@keydown.home.prevent'() { this.focusOption(0) },
|
||||
'@keydown.end.prevent'() { this.focusOption(this.options.length - 1) },
|
||||
'@keydown.enter.prevent'() { this.handleOptionEnter() },
|
||||
'@keydown.enter.prevent'() {
|
||||
if (this.open) this.handleOptionEnter();
|
||||
else this.open = true;
|
||||
},
|
||||
':aria-expanded'() { return this.open },
|
||||
'x-ref': 'trigger'
|
||||
},
|
||||
|
||||
$content: {
|
||||
'@click'(e) { this.handleOptionClick(e) },
|
||||
'@mousemove'(e) { this.handleOptionMousemove(e) },
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
},
|
||||
|
||||
$filter: {
|
||||
'@input'(e) { this.filterOptions(e.target.value) },
|
||||
'@keydown.escape.prevent'() {
|
||||
@@ -180,7 +182,11 @@
|
||||
>
|
||||
<div x-html="selectedLabel" class="flex items-center gap-x-2"
|
||||
></div>
|
||||
{% lucide "chevron-down", class="text-muted-foreground opacity-50 shrink-0" %}
|
||||
{% if is_combobox %}
|
||||
{% lucide "chevrons-up-down", class="text-muted-foreground opacity-50 shrink-0" %}
|
||||
{% else %}
|
||||
{% lucide "chevron-down", class="text-muted-foreground opacity-50 shrink-0" %}
|
||||
{% endif %}
|
||||
</button>
|
||||
<div
|
||||
data-popover
|
||||
|
||||
@@ -1,75 +1,73 @@
|
||||
{% macro tabs(
|
||||
tabs,
|
||||
id,
|
||||
tabsets=[],
|
||||
main_attrs=None,
|
||||
trigger_attrs=None,
|
||||
content_attrs=None
|
||||
tablist_attrs=None,
|
||||
default_tab_index=1
|
||||
)
|
||||
%}
|
||||
{% if registerComponent('toast') %}
|
||||
{% if registerComponent('tabs') %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('toast', () => ({
|
||||
open: false,
|
||||
focusedIndex: -1,
|
||||
menuItems: [],
|
||||
document.addEventListener('alpine:init', (initialTabIndex = 0) => {
|
||||
Alpine.data('tabs', () => ({
|
||||
activeTabIndex: initialTabIndex,
|
||||
tabs: [],
|
||||
panels: [],
|
||||
|
||||
init() {
|
||||
console.log("initialTabIndex", initialTabIndex);
|
||||
this.$nextTick(() => {
|
||||
const menuElement = this.$el.querySelector('[role="menu"]');
|
||||
this.menuItems = Array.from(menuElement.querySelectorAll('[role=menuitem]'));
|
||||
this.tabs = Array.from(this.$el.querySelectorAll('[role=tab]:not([disabled])'));
|
||||
this.panels = Array.from(this.$el.querySelectorAll('[role=tabpanel]'));
|
||||
|
||||
if (this.tabs.length > 0) {
|
||||
this.selectTab(this.tabs[initialTabIndex], false);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.handleClick() },
|
||||
'@keydown.down.prevent'() { this.open && this.nextItem() },
|
||||
':aria-expanded'() { return this.open },
|
||||
':aria-pressed'() { return this.open }
|
||||
nextTab() {
|
||||
if (this.tabs.length === 0) return;
|
||||
let newIndex = (this.activeTabIndex + 1) % this.tabs.length;
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
},
|
||||
|
||||
$menu: {
|
||||
'@click'(e) { this.handleMenuItemClick(e) },
|
||||
'@keydown.escape.prevent'() { this.handleEscape() },
|
||||
'@keydown.down.prevent'() { this.nextItem() },
|
||||
'@keydown.up.prevent'() { this.prevItem() },
|
||||
'@keydown.home.prevent'() { this.focusItem(0) },
|
||||
'@keydown.end.prevent'() { this.focusItem(this.menuItems.length - 1) },
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
prevTab() {
|
||||
if (this.tabs.length === 0) return;
|
||||
let newIndex = (this.activeTabIndex - 1 + this.tabs.length) % this.tabs.length;
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
},
|
||||
|
||||
focusItem(index) {
|
||||
if (index >= 0 && index < this.menuItems.length) {
|
||||
this.focusedIndex = index;
|
||||
this.menuItems[index].focus();
|
||||
}
|
||||
},
|
||||
|
||||
nextItem() {
|
||||
this.focusItem((this.focusedIndex + 1) % this.menuItems.length);
|
||||
},
|
||||
|
||||
prevItem() {
|
||||
this.focusItem(this.focusedIndex <= 0 ? this.menuItems.length - 1 : this.focusedIndex - 1);
|
||||
},
|
||||
|
||||
handleClick() {
|
||||
this.open = !this.open;
|
||||
this.$nextTick(() => {
|
||||
if(this.open && this.menuItems.length) this.focusItem(0);
|
||||
selectTab(tab, focus = true) {
|
||||
if (!tab || this.tabs.length === 0) return;
|
||||
|
||||
this.tabs.forEach((t, index) => {
|
||||
const isSelected = t === tab;
|
||||
t.setAttribute('aria-selected', isSelected);
|
||||
t.setAttribute('tabindex', isSelected ? '0' : '-1');
|
||||
if (isSelected) {
|
||||
this.activeTabIndex = index;
|
||||
this.activeTab = t;
|
||||
if (focus) {
|
||||
t.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const panelId = tab.getAttribute('aria-controls');
|
||||
if (!panelId) return;
|
||||
|
||||
this.panels.forEach(panel => {
|
||||
panel.hidden = (panel.getAttribute('id') !== panelId);
|
||||
});
|
||||
},
|
||||
|
||||
handleEscape() {
|
||||
if (this.open) this.open = false;
|
||||
},
|
||||
|
||||
handleMenuItemClick(event) {
|
||||
const menuItem = event.target.closest('[role=menuitem]');
|
||||
if (menuItem && !menuItem.disabled) {
|
||||
menuItem.blur();
|
||||
this.open = false;
|
||||
}
|
||||
|
||||
$tablist: {
|
||||
['@click'](event) {
|
||||
const clickedTab = event.target.closest('[role=tab]');
|
||||
if (clickedTab) {
|
||||
this.selectTab(clickedTab);
|
||||
}
|
||||
},
|
||||
['@keydown.arrow-right.prevent']() { this.nextTab() },
|
||||
['@keydown.arrow-left.prevent']() { this.prevTab() },
|
||||
},
|
||||
}));
|
||||
});
|
||||
@@ -77,44 +75,53 @@
|
||||
{% endif %}
|
||||
|
||||
<div
|
||||
class="toast {% if main_attrs %}{{ main_attrs.pop('class', '') }}{% endif %}"
|
||||
x-data="toast"
|
||||
@click.away="open = false"
|
||||
class="tabs {{ main_attrs.class }}"
|
||||
x-data="tabs({{ default_tab_index }})"
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
{% for key, value in main_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{% if trigger %}
|
||||
<button
|
||||
type="button"
|
||||
aria-haspopup="menu"
|
||||
aria-expanded="false"
|
||||
aria-pressed="false"
|
||||
x-bind="$trigger"
|
||||
{% if id %}
|
||||
id="{{ id }}-trigger"
|
||||
aria-controls="{{ id }}-menu"
|
||||
{% endif %}
|
||||
{% for key, value in trigger_attrs %}
|
||||
<div
|
||||
role="tablist"
|
||||
aria-orientation="horizontal"
|
||||
x-bind="$tablist"
|
||||
{% for key, value in tablist_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ trigger | safe }}
|
||||
</button>
|
||||
{% endif %}
|
||||
<nav
|
||||
role="menu"
|
||||
aria-orientation="vertical"
|
||||
aria-hidden="true"
|
||||
x-bind="$menu"
|
||||
{% if id %}id="{{ id }}-menu"{% endif %}
|
||||
{% for key, value in menu_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% for tabset in tabsets %}
|
||||
<button
|
||||
role="tab"
|
||||
id="{{ tabset.id }}-tab-{{ loop.index }}"
|
||||
aria-controls="{{ tabset.id }}-panel-{{ loop.index }}"
|
||||
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
|
||||
tabindex="0"
|
||||
{% for key, value in tabset.tab_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ tabset.tab | safe }}
|
||||
</button>
|
||||
{% endfor %}
|
||||
>
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
</div>
|
||||
|
||||
{% for tabset in tabsets %}
|
||||
{% if tabset.panel %}
|
||||
<div
|
||||
role="tabpanel"
|
||||
id="{{ tabset.id }}-panel-{{ loop.index }}"
|
||||
aria-labelledby="{{ tabset.id }}-tab-{{ loop.index }}"
|
||||
tabindex="-1"
|
||||
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
|
||||
{% if loop.index != default_tab_index %}hidden{% endif %}
|
||||
{% for key, value in tabset.panel_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% endfor %}
|
||||
>
|
||||
{{ tabset.panel | safe }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html class="dark"><head>
|
||||
<!-- Theme -->
|
||||
<script>
|
||||
function isDark() {
|
||||
return localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
}
|
||||
|
||||
if (isDark()) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('theme', {
|
||||
isDark: isDark(),
|
||||
set(value) {
|
||||
this.isDark = value;
|
||||
localStorage.setItem('theme', value ? 'dark' : 'light');
|
||||
},
|
||||
toggle() {
|
||||
this.isDark = document.documentElement.classList.toggle('dark');
|
||||
localStorage.setItem('theme', this.isDark ? 'dark' : 'light');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Language" content="en">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Title & Description -->
|
||||
<title>{{ site.title + ": " + title if page.url === "/" else title + " - " + site.title }}</title>
|
||||
<meta name="description" content="{{ description or site.description }}"/>
|
||||
<meta name="title" content="{{ title }} | Basecoat">
|
||||
<meta name="description" content="{{ description }}">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="preconnect" href="https://rsms.me/">
|
||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
|
||||
<!-- JS -->
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
id="sidebar"
|
||||
class="fixed inset-y-0 left-0 z-50 h-svh border-r bg-sidebar text-sidebar-foreground flex flex-col w-[var(--sidebar-width)] transition-transform ease-in-out peer aria-[hidden=true]:-translate-x-full"
|
||||
aria-hidden="false"
|
||||
:aria-hidden="open ? 'false' : 'true'"
|
||||
@sidebar:toggle.window="toggle()"
|
||||
x-data="{
|
||||
open: true,
|
||||
toggle() { this.open = !this.open }
|
||||
}"
|
||||
>
|
||||
{% include "partials/sidebar.njk" %}
|
||||
</div>
|
||||
|
||||
<main id="content" class="relative ml-(--sidebar-width) peer-aria-hidden:ml-0 transition-[margin-left] ease-in-out">
|
||||
<header class="bg-background sticky inset-x-0 top-0 isolate z-10 flex shrink-0 items-center gap-2 border-b">
|
||||
<div class="flex h-14 w-full items-center gap-2 px-4">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Hide sidebar"
|
||||
data-tooltip="Hide sidebar"
|
||||
:data-tooltip="sidebarOpen ? 'Hide sidebar' : 'Show sidebar'"
|
||||
data-side="bottom"
|
||||
data-align="start"
|
||||
class="btn-icon-ghost size-8 mr-auto"
|
||||
x-init
|
||||
x-data="{ sidebarOpen: true }"
|
||||
@click="
|
||||
sidebarOpen = !sidebarOpen;
|
||||
$dispatch('sidebar:toggle');
|
||||
"
|
||||
>
|
||||
{% lucide "panel-left" %}
|
||||
</button>
|
||||
<select class="select h-8 leading-none">
|
||||
<option value="light">Default</option>
|
||||
<option value="light">Other</option>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle theme"
|
||||
data-tooltip="Toggle theme"
|
||||
data-side="bottom"
|
||||
data-align="end"
|
||||
class="btn-icon-outline size-8"
|
||||
x-init
|
||||
@click="$store.theme.toggle()"
|
||||
>
|
||||
<span x-show="$store.theme.isDark">{% lucide "sun" %}</span>
|
||||
<span x-show="!$store.theme.isDark">{% lucide "moon" %}</span>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container grid gap-4 p-4">
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
</main>
|
||||
{% from "components/toast.njk" import toaster %}
|
||||
{{ toaster() }}
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
layout: layouts/base.njk
|
||||
---
|
||||
|
||||
<main class="mx-auto relative py-6 lg:gap-10 lg:py-8 flex w-full max-w-screen-lg">
|
||||
<div class="mx-auto w-full min-w-0 max-w-screen-md">
|
||||
<header class="space-y-2">
|
||||
<h1 class="text-3xl font-semibold tracking-tight">{{ title }}</h1>
|
||||
<p class="text-muted-foreground">{{ description }}</p>
|
||||
</header>
|
||||
<article class="pb-12 pt-8 [&>h2]:mt-16 [&>h2]:scroll-m-22 [&>h2]:border-b [&>h2]:pb-4 [&>h2]:text-xl [&>h2]:font-semibold [&>h2]:tracking-tight [&>h2]:first:mt-0 [&>h3]:font-heading [&>h3]:mt-8 [&>h3]:scroll-m-22 [&>h3]:text-lg [&>h3]:font-semibold [&>h3]:tracking-tight content">
|
||||
{{ content | safe }}
|
||||
</article>
|
||||
</div>
|
||||
<div class="hidden text-sm xl:block w-full max-w-[300px]">
|
||||
<nav class="sticky top-22 space-y-2 [&_ul]:m-0 [&_ul]:list-none [&_ul_ul]:pl-4 [&_li]:mt-0 [&_li]:pt-2 [&_a]:inline-block [&_a]:no-underline [&_a]:transition-colors [&_a]:hover:text-foreground [&_a]:text-muted-foreground">
|
||||
<h4 class="font-medium">On This Page</h4>
|
||||
{% if toc %}
|
||||
{% macro renderToc(items) %}
|
||||
{% if items and items.length %}
|
||||
<ul>
|
||||
{% for item in items %}
|
||||
<li>
|
||||
<a href="#{{ item.id }}">{{ item.label }}</a>
|
||||
{# Recursively call the macro if there are children #}
|
||||
{% if item.children and item.children.length %}
|
||||
{{ renderToc(item.children) }}
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
{{ renderToc(toc) }}
|
||||
{% else %}
|
||||
<p class="text-muted-foreground text-center bg-muted rounded-md p-2">No table of contents.</p>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
||||
<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/html.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/django.min.js"></script>
|
||||
<script>hljs.highlightAll();</script>
|
||||
@@ -0,0 +1,29 @@
|
||||
{% macro code_block(code, language="html") %}
|
||||
<div
|
||||
class="relative"
|
||||
x-data="{
|
||||
copied: false,
|
||||
copy() {
|
||||
const codeElement = $refs.code;
|
||||
if (!codeElement) return;
|
||||
navigator.clipboard.writeText(codeElement.textContent || '').then(() => {
|
||||
this.copied = true;
|
||||
setTimeout(() => { this.copied = false }, 2000);
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy text: ', err);
|
||||
});
|
||||
}
|
||||
}"
|
||||
>
|
||||
<pre x-ref="code" class="text-sm max-h-[650px] overflow-y-auto rounded-xl scrollbar"><code class="language-{{ language }} !bg-neutral-900 p-0">{{- code | escape -}}</code></pre>
|
||||
|
||||
<button
|
||||
@click="copy()"
|
||||
class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 z-10 h-6 w-6 text-zinc-50 hover:bg-zinc-700 hover:text-zinc-50 [&_svg]:h-3 [&_svg]:w-3 absolute right-3 top-3"
|
||||
>
|
||||
{% lucide "clipboard", { "class": "size-4", "x-show": "!copied" } %}
|
||||
{% lucide "check", { "class": "size-4", "x-show": "copied" } %}
|
||||
</button>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{% macro code_preview(id, code, class=None, default_code=false) %}
|
||||
{% from "./code_block.njk" import code_block %}
|
||||
{% set code_panel = code_block(code) %}
|
||||
{% set preview_panel %}
|
||||
<div class="ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 relative rounded-md border">
|
||||
<div class="preview flex min-h-[350px] w-full justify-center p-10 items-center">
|
||||
<div class="{{ class }}">
|
||||
{{ code | safe }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
<div class="relative my-4">
|
||||
{% from "components/tabs.njk" import tabs %}
|
||||
{% set tabsets = [
|
||||
{ tab: "Preview", panel: preview_panel },
|
||||
{ tab: "Code", panel: code_panel }
|
||||
] %}
|
||||
|
||||
{{ tabs(
|
||||
id="code-block-{{ id }}",
|
||||
tabsets=tabsets,
|
||||
default_tab_index=(2 if default_code else 1)
|
||||
) }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -1,150 +0,0 @@
|
||||
<section id="dropdown-menu" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Dropdown Menu</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-start gap-4">
|
||||
{% call popover(
|
||||
id="dropdown-menu",
|
||||
trigger="Open Menu",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">Account Options</span>
|
||||
<button type="button" role="menuitem">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Keyboard shortcuts
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
|
||||
</button>
|
||||
</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>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
|
||||
{% set profile_menu %}
|
||||
<nav role="menu">
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "star" %}
|
||||
Upgrade to Pro
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Account
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "bell" %}
|
||||
Notifications
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="theme-options">
|
||||
<span id="theme-options" role="heading">Theme</span>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "check", { "class": "text-foreground" } %}
|
||||
Light
|
||||
</button>
|
||||
<button type="button" role="menuitem" >
|
||||
{% lucide "check", { "class": "text-transparent" } %}
|
||||
Dark
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "check", { "class": "text-transparent" } %}
|
||||
System
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Signout
|
||||
</button>
|
||||
</nav>
|
||||
{% endset %}
|
||||
|
||||
{% set trigger %}
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
{% lucide "chevrons-up-down", { "class": "text-muted-foreground ml-auto" } %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-outline h-12 justify-start px-2 md:max-w-[200px]"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
|
||||
{% set trigger %}
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost rounded-full size-8"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost"},
|
||||
content_attrs={"class": "min-w-32"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "pencil" %}
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "share" %}
|
||||
Share
|
||||
</button>
|
||||
<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">
|
||||
{% lucide "trash-2" %}
|
||||
Delete
|
||||
</button>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
{% from "components/dialog.njk" import dialog %}
|
||||
|
||||
<section id="alert-dialog" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Alert Dialog</h2>
|
||||
@@ -59,15 +59,6 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{# <div class="alert">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Something went wrong!</h2>
|
||||
<section>Your session has expired. Please log in again.</section>
|
||||
<button type="button" class="btn-sm-outline">
|
||||
Undo
|
||||
</button>
|
||||
</div> #}
|
||||
|
||||
<div class="alert border-amber-50 bg-amber-50 text-amber-900 dark:border-amber-950 dark:bg-amber-950 dark:text-amber-100">
|
||||
{% lucide "circle-check" %}
|
||||
<h2>Plot Twist: This Alert is Actually Amber!</h2>
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
{% from "components/popover.njk" import popover %}
|
||||
|
||||
<section id="breadcrumb" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Breadcrumb</h2>
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
{% from "components/select.njk" import select %}
|
||||
|
||||
<section id="combobox" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Combobox</h2>
|
||||
@@ -1,3 +1,5 @@
|
||||
{% from "components/dialog.njk" import dialog %}
|
||||
|
||||
<section id="dialog" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Dialog</h2>
|
||||
@@ -0,0 +1,146 @@
|
||||
{% from "components/dropdown-menu.njk" import dropdown_menu %}
|
||||
|
||||
<section id="dropdown-menu" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Dropdown Menu</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-wrap items-start gap-4">
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-menu",
|
||||
trigger="Open Menu",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_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">
|
||||
Profile
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Billing
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Settings
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
Keyboard shortcuts
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
|
||||
</button>
|
||||
</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>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
Logout
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
|
||||
</button>
|
||||
{% endcall %}
|
||||
|
||||
{% set profile_menu %}
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "star" %}
|
||||
Upgrade to Pro
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Account
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "bell" %}
|
||||
Notifications
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<div role="group" aria-labelledby="theme-options">
|
||||
<span id="theme-options" role="heading">Theme</span>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "check", { "class": "text-foreground" } %}
|
||||
Light
|
||||
</button>
|
||||
<button type="button" role="menuitem" >
|
||||
{% lucide "check", { "class": "text-transparent" } %}
|
||||
Dark
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "check", { "class": "text-transparent" } %}
|
||||
System
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Signout
|
||||
</button>
|
||||
{% endset %}
|
||||
|
||||
{% set trigger %}
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
{% lucide "chevrons-up-down", { "class": "text-muted-foreground ml-auto" } %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-outline h-12 justify-start px-2 md:max-w-[200px]"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
|
||||
{% set trigger %}
|
||||
<img alt="@hunvreus" src="https://github.com/hunvreus.png" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost rounded-full size-8"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
|
||||
{% set trigger %}
|
||||
{% lucide "ellipsis" %}
|
||||
{% endset %}
|
||||
{% call dropdown_menu(
|
||||
id="dropdown-checkboxes",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-icon-ghost"},
|
||||
content_attrs={"class": "min-w-32"}
|
||||
) %}
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "pencil" %}
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "share" %}
|
||||
Share
|
||||
</button>
|
||||
<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">
|
||||
{% lucide "trash-2" %}
|
||||
Delete
|
||||
</button>
|
||||
{% endcall %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,3 +1,5 @@
|
||||
{% from "components/popover.njk" import popover %}
|
||||
|
||||
<section id="popover" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Popover</h2>
|
||||
+3
-3
@@ -5,9 +5,9 @@
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-y-6">
|
||||
<fieldset class="grid gap-3">
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="yes" class="input">Yes</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="no" class="input">No</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="maybe" class="input">Maybe</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="default" class="input">Default</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="comfortable" class="input">Comfortable</label>
|
||||
<label class="label"><input type="radio" name="demo-radio-group" value="compact" class="input">Compact</label>
|
||||
</fieldset>
|
||||
<fieldset class="grid gap-3 max-w-sm">
|
||||
<label class="label gap-3 items-start hover:bg-accent/50 rounded-lg border p-4 has-[input[type='radio']:checked]:border-green-600 has-[input[type='radio']:checked]:bg-green-50 dark:has-[input[type='radio']:checked]:border-green-900 dark:has-[input[type='radio']:checked]:bg-green-950">
|
||||
@@ -1,3 +1,5 @@
|
||||
{% from "components/select.njk" import select %}
|
||||
|
||||
<section id="select" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Select</h2>
|
||||
@@ -0,0 +1,102 @@
|
||||
{% from "components/tabs.njk" import tabs %}
|
||||
|
||||
<section id="tabs" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Tabs</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
{% set account_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Account</h2>
|
||||
<p>Make changes to your account here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-name">Name</label>
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save changes</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% set password_panel %}
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Password</h2>
|
||||
<p>Change your password here. After saving, you'll be logged out.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-current">Current password</label>
|
||||
<input type="password" id="demo-tabs-password-current" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<input type="password" id="demo-tabs-password-new"/>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save Password</button>
|
||||
</footer>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% set tabsets = [
|
||||
{ tab: "Account", panel: account_panel },
|
||||
{ tab: "Password", panel: password_panel }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-with-panels',
|
||||
tabsets=tabsets,
|
||||
main_attrs={ "class": "max-w-[300px]" },
|
||||
tablist_attrs={ "class": "w-full" }
|
||||
) }}
|
||||
|
||||
{% set tabsets = [
|
||||
{ tab: "Home" },
|
||||
{ tab: "Settings" }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-without-panels',
|
||||
tabsets=tabsets
|
||||
) }}
|
||||
|
||||
{% set tabsets = [
|
||||
{ tab: "Home" },
|
||||
{ tab: "Disabled", tab_attrs: { "disabled": "disabled" } }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-disabled',
|
||||
tabsets=tabsets
|
||||
) }}
|
||||
|
||||
{% set label_preview %}
|
||||
{% lucide 'app-window' %}
|
||||
Preview
|
||||
{% endset %}
|
||||
{% set label_code %}
|
||||
{% lucide 'code' %}
|
||||
Code
|
||||
{% endset %}
|
||||
{% set tabsets = [
|
||||
{ tab: label_preview },
|
||||
{ tab: label_code }
|
||||
] %}
|
||||
{{ tabs(
|
||||
id='demo-tabs-with-icons',
|
||||
tabsets=tabsets
|
||||
) }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,149 @@
|
||||
{% set components = [
|
||||
{ "id": "accordion", "name": "Accordion" },
|
||||
{ "id": "alert", "name": "Alert" },
|
||||
{ "id": "alert-dialog", "name": "Alert Dialog" },
|
||||
{ "id": "avatar", "name": "Avatar" },
|
||||
{ "id": "badge", "name": "Badge" },
|
||||
{ "id": "breadcrumb", "name": "Breadcrumb" },
|
||||
{ "id": "button", "name": "Button" },
|
||||
{ "id": "card", "name": "Card" },
|
||||
{ "id": "checkbox", "name": "Checkbox" },
|
||||
{ "id": "combobox", "name": "Combobox" },
|
||||
{ "id": "dialog", "name": "Dialog" },
|
||||
{ "id": "dropdown-menu", "name": "Dropdown Menu" },
|
||||
{ "id": "form", "name": "Form" },
|
||||
{ "id": "input", "name": "Input" },
|
||||
{ "id": "label", "name": "Label" },
|
||||
{ "id": "pagination", "name": "Pagination" },
|
||||
{ "id": "popover", "name": "Popover" },
|
||||
{ "id": "radio-group", "name": "Radio Group" },
|
||||
{ "id": "select", "name": "Select" },
|
||||
{ "id": "skeleton", "name": "Skeleton" },
|
||||
{ "id": "slider", "name": "Slider" },
|
||||
{ "id": "switch", "name": "Switch" },
|
||||
{ "id": "table", "name": "Table" },
|
||||
{ "id": "tabs", "name": "Tabs" },
|
||||
{ "id": "textarea", "name": "Textarea" },
|
||||
{ "id": "toast", "name": "Toast" },
|
||||
{ "id": "tooltip", "name": "Tooltip" }
|
||||
] %}
|
||||
|
||||
<header class="flex flex-col gap-2 p-2">
|
||||
<a href="/" class="btn-ghost p-2 h-12 w-full justify-start">
|
||||
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||
{% lucide "paint-roller", { "class": "size-4" } %}
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Basecoat</span>
|
||||
<span class="truncate text-xs">v0.0.1</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex flex-col gap-2 px-2 text-sm">
|
||||
<form class="form relative">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<input type="text" placeholder="Search the docs..." class="h-8 w-full shadow-none pl-8">
|
||||
{% lucide "search", { "class": "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" } %}
|
||||
</form>
|
||||
</div>
|
||||
</header>
|
||||
<section class="flex flex-1 flex-col gap-2 overflow-y-auto scrollbar">
|
||||
<nav aria-label="Pages navigation" class="flex flex-col p-2">
|
||||
<h2 class="text-sidebar-foreground/70 flex h-8 items-center px-2 text-xs font-medium">Getting Started</h2>
|
||||
<ul class="flex flex-col gap-1">
|
||||
<li>
|
||||
<a
|
||||
href="/"
|
||||
class="h-8 flex items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
Introduction
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/installation"
|
||||
class="h-8 flex items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
Installation
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/kitchen-sink"
|
||||
class="h-8 flex items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
Kitchen sink
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="/theming"
|
||||
class="h-8 flex items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
Theming
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/hunvreus/basecoat"
|
||||
target="_blank"
|
||||
class="h-8 flex items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
GitHub
|
||||
{% lucide "arrow-up-right", { "class": "text-muted-foreground size-3.5 ml-auto" } %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav aria-label="Components navigation" class="flex flex-col p-2">
|
||||
<h2 class="text-sidebar-foreground/70 flex h-8 items-center px-2 text-xs font-medium">Components</h2>
|
||||
<ul class="flex flex-col gap-1">
|
||||
{% for component in components %}
|
||||
<li>
|
||||
<a
|
||||
href="/components/{{ component.id }}"
|
||||
class="h-8 flex items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
|
||||
>
|
||||
{{ component.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
<footer class="flex flex-col gap-2 p-2">
|
||||
{% set profile_menu %}
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<img src="https://avatar.vercel.sh/ronan_berder" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "star" %}
|
||||
Upgrade to Pro
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Account
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "bell" %}
|
||||
Notifications
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Signout
|
||||
</button>
|
||||
</nav>
|
||||
{% endset %}
|
||||
</footer>
|
||||
@@ -1,167 +0,0 @@
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('tabs', () => ({
|
||||
activeTabIndex: 0,
|
||||
tabs: [],
|
||||
panels: [],
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.tabs = Array.from(this.$el.querySelectorAll('[role=tab]:not([disabled])'));
|
||||
this.panels = Array.from(this.$el.querySelectorAll('[role=tabpanel]'));
|
||||
|
||||
if (this.tabs.length > 0) {
|
||||
this.selectTab(this.tabs[0], false);
|
||||
}
|
||||
});
|
||||
},
|
||||
nextTab() {
|
||||
if (this.tabs.length === 0) return;
|
||||
let newIndex = (this.activeTabIndex + 1) % this.tabs.length;
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
},
|
||||
prevTab() {
|
||||
if (this.tabs.length === 0) return;
|
||||
let newIndex = (this.activeTabIndex - 1 + this.tabs.length) % this.tabs.length;
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
},
|
||||
selectTab(tab, focus = true) {
|
||||
if (!tab || this.tabs.length === 0) return;
|
||||
|
||||
this.tabs.forEach((t, index) => {
|
||||
const isSelected = t === tab;
|
||||
t.setAttribute('aria-selected', isSelected);
|
||||
t.setAttribute('tabindex', isSelected ? '0' : '-1');
|
||||
if (isSelected) {
|
||||
this.activeTabIndex = index;
|
||||
this.activeTab = t;
|
||||
if (focus) {
|
||||
t.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const panelId = tab.getAttribute('aria-controls');
|
||||
if (!panelId) return;
|
||||
|
||||
this.panels.forEach(panel => {
|
||||
panel.hidden = (panel.getAttribute('id') !== panelId);
|
||||
});
|
||||
},
|
||||
|
||||
$tablist: {
|
||||
['@click'](event) {
|
||||
const clickedTab = event.target.closest('[role=tab]');
|
||||
if (clickedTab) {
|
||||
this.selectTab(clickedTab);
|
||||
}
|
||||
},
|
||||
['@keydown.arrow-right.prevent']() { this.nextTab() },
|
||||
['@keydown.arrow-left.prevent']() { this.prevTab() },
|
||||
},
|
||||
}));
|
||||
});
|
||||
</script>
|
||||
|
||||
<section id="tabs" class="w-full rounded-lg border scroll-mt-16">
|
||||
<header class="border-b px-4 py-3">
|
||||
<h2 class="text-sm font-medium">Tabs</h2>
|
||||
</header>
|
||||
<div class="p-4">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div class="tabs flex flex-col gap-2 max-w-[300px]" x-data="tabs">
|
||||
<div role="tablist" aria-orientation="horizontal" aria-label="Feature Sections" class="w-full" x-bind="$tablist">
|
||||
<button role="tab" id="demo-tab-account" aria-controls="demo-panel-account" aria-selected="true" tabindex="0">
|
||||
Account
|
||||
</button>
|
||||
<button role="tab" id="demo-tab-password" aria-controls="demo-panel-password" aria-selected="false" tabindex="-1">
|
||||
Password
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" id="demo-panel-account" aria-labelledby="demo-tab-account" tabindex="-1">
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Account</h2>
|
||||
<p>Make changes to your account here. Click save when you're done.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-name">Name</label>
|
||||
<input type="text" id="demo-tabs-account-name" value="Pedro Duarte" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-account-username">Username</label>
|
||||
<input type="text" id="demo-tabs-account-username" value="@peduarte" />
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save changes</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" id="demo-panel-password" aria-labelledby="demo-tab-password" tabindex="-1" hidden>
|
||||
<div class="card">
|
||||
<header>
|
||||
<h2>Password</h2>
|
||||
<p>Change your password here. After saving, you'll be logged out.</p>
|
||||
</header>
|
||||
<section>
|
||||
<form class="form grid gap-6">
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-current">Current password</label>
|
||||
<input type="password" id="demo-tabs-password-current" />
|
||||
</div>
|
||||
<div class="grid gap-3">
|
||||
<label for="demo-tabs-password-new">New password</label>
|
||||
<input type="password" id="demo-tabs-password-new"/>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
<footer>
|
||||
<button type="button" class="btn">Save Password</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs" x-data="tabs">
|
||||
<div role="tablist" aria-orientation="horizontal" aria-label="Feature Sections" x-bind="$tablist">
|
||||
<button role="tab" aria-selected="true" tabindex="0">
|
||||
Home
|
||||
</button>
|
||||
<button role="tab" aria-selected="false" tabindex="-1">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs" x-data="tabs">
|
||||
<div role="tablist" aria-orientation="horizontal" aria-label="Feature Sections" x-bind="$tablist">
|
||||
<button role="tab" aria-selected="true" tabindex="0">
|
||||
Home
|
||||
</button>
|
||||
<button role="tab" aria-selected="false" tabindex="-1" disabled>
|
||||
Disabled
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs" x-data="tabs">
|
||||
<div role="tablist" aria-orientation="horizontal" aria-label="Feature Sections" x-bind="$tablist">
|
||||
<button role="tab" aria-selected="true" tabindex="0">
|
||||
{% lucide 'app-window' %}
|
||||
Preview
|
||||
</button>
|
||||
<button role="tab" aria-selected="false" tabindex="-1">
|
||||
{% lucide 'code' %}
|
||||
Code
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1,193 +0,0 @@
|
||||
<header class="flex flex-col gap-2 p-2">
|
||||
{% set trigger %}
|
||||
<div class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||
{% lucide "gallery-vertical-end", { "class": "size-4" } %}
|
||||
</div>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">Acme Inc</span>
|
||||
<span class="truncate text-xs">Enterprise</span>
|
||||
</div>
|
||||
{% lucide "chevrons-up-down", { "class": "ml-auto" } %}
|
||||
{% endset %}
|
||||
{% call popover(
|
||||
id="demo-sidebar-dropdown-org",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-ghost p-2 h-12 w-full justify-start"},
|
||||
content_attrs={"data-side": "right"}
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<div role="group" aria-labelledby="teams-label">
|
||||
<span id="teams-label" role="heading" class="text-xs text-muted-foreground">Teams</span>
|
||||
<button type="button" role="menuitem" class="p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border">
|
||||
{% lucide "gallery-vertical-end", { "class": "size-3.5 shrink-0" } %}
|
||||
</div>
|
||||
Acme Inc
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘1</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem" class="p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border">
|
||||
{% lucide "audio-waveform", { "class": "size-3.5 shrink-0" } %}
|
||||
</div>
|
||||
Acme Corp
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘2</span>
|
||||
</button>
|
||||
<button type="button" role="menuitem" class="p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border">
|
||||
{% lucide "command", { "class": "size-3.5 shrink-0" } %}
|
||||
</div>
|
||||
Evil Corp
|
||||
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘3</span>
|
||||
</button>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem" class="p-2">
|
||||
<div class="flex size-6 items-center justify-center rounded-md border">
|
||||
{% lucide "plus", { "class": "size-3.5 shrink-0" } %}
|
||||
</div>
|
||||
<span class="text-muted-foreground font-medium">Add team</span>
|
||||
</button>
|
||||
</nav>
|
||||
{% endcall %}
|
||||
|
||||
<div class="flex flex-col gap-2 px-2 text-sm">
|
||||
<form class="form relative">
|
||||
<label for="search" class="sr-only">Search</label>
|
||||
<input type="text" placeholder="Search the docs..." class="h-8 w-full shadow-none pl-8">
|
||||
{% lucide "search", { "class": "pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 opacity-50 select-none" } %}
|
||||
</form>
|
||||
</div>
|
||||
</header>
|
||||
<section class="flex flex-1 flex-col gap-2 overflow-y-auto scrollbar">
|
||||
<nav aria-label="Pages navigation" class="flex flex-col p-2">
|
||||
<h2 class="text-sidebar-foreground/70 flex h-8 items-center px-2 text-xs font-medium">Pages</h2>
|
||||
<ul class="flex flex-col gap-1">
|
||||
<li>
|
||||
<a
|
||||
href="#alert-dialog"
|
||||
class="h-8 flex items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
{% lucide "house" %}
|
||||
Homepage
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#alert-dialog"
|
||||
class="h-8 flex items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
{% lucide "book-open" %}
|
||||
Docs
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="#alert-dialog"
|
||||
class="h-8 flex items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
{% lucide "github" %}
|
||||
GitHub
|
||||
{% lucide "arrow-up-right", { "class": "text-muted-foreground size-3.5 ml-auto" } %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<nav aria-label="Components navigation" class="flex flex-col p-2">
|
||||
<h2 class="text-sidebar-foreground/70 flex h-8 items-center px-2 text-xs font-medium">Components</h2>
|
||||
<ul class="flex flex-col gap-1">
|
||||
{% set components = [
|
||||
{ "id": "accordion", "name": "Accordion" },
|
||||
{ "id": "alert", "name": "Alert" },
|
||||
{ "id": "alert-dialog", "name": "Alert Dialog" },
|
||||
{ "id": "avatar", "name": "Avatar" },
|
||||
{ "id": "badge", "name": "Badge" },
|
||||
{ "id": "breadcrumb", "name": "Breadcrumb" },
|
||||
{ "id": "button", "name": "Button" },
|
||||
{ "id": "card", "name": "Card" },
|
||||
{ "id": "checkbox", "name": "Checkbox" },
|
||||
{ "id": "combobox", "name": "Combobox" },
|
||||
{ "id": "dialog", "name": "Dialog" },
|
||||
{ "id": "dropdown-menu", "name": "Dropdown Menu" },
|
||||
{ "id": "form", "name": "Form" },
|
||||
{ "id": "input", "name": "Input" },
|
||||
{ "id": "label", "name": "Label" },
|
||||
{ "id": "pagination", "name": "Pagination" },
|
||||
{ "id": "popover", "name": "Popover" },
|
||||
{ "id": "radio-group", "name": "Radio Group" },
|
||||
{ "id": "select", "name": "Select" },
|
||||
{ "id": "skeleton", "name": "Skeleton" },
|
||||
{ "id": "slider", "name": "Slider" },
|
||||
{ "id": "switch", "name": "Switch" },
|
||||
{ "id": "table", "name": "Table" },
|
||||
{ "id": "tabs", "name": "Tabs" },
|
||||
{ "id": "textarea", "name": "Textarea" },
|
||||
{ "id": "toast", "name": "Toast" },
|
||||
{ "id": "tooltip", "name": "Tooltip" }
|
||||
] %}
|
||||
{% for component in components %}
|
||||
<li>
|
||||
<a
|
||||
href="#{{ component.id }}"
|
||||
class="h-8 flex items-center gap-2 rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0"
|
||||
>
|
||||
{{ component.name }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
<footer class="flex flex-col gap-2 p-2">
|
||||
{% set profile_menu %}
|
||||
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<img src="https://avatar.vercel.sh/ronan_berder" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr role="separator">
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "star" %}
|
||||
Upgrade to Pro
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "user" %}
|
||||
Account
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "credit-card" %}
|
||||
Billing
|
||||
</button>
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "bell" %}
|
||||
Notifications
|
||||
</button>
|
||||
<hr role="separator">
|
||||
<button type="button" role="menuitem">
|
||||
{% lucide "log-out" %}
|
||||
Signout
|
||||
</button>
|
||||
</nav>
|
||||
{% endset %}
|
||||
|
||||
{% set trigger %}
|
||||
<img src="https://avatar.vercel.sh/ronan_berder" class="size-8 shrink-0 rounded-full" alt="Ronan Berder"/>
|
||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
||||
<span class="truncate font-medium">hunvreus</span>
|
||||
<span class="text-muted-foreground truncate text-xs">hunvreus@example.com</span>
|
||||
</div>
|
||||
{% lucide "chevrons-up-down", { "class": "text-muted-foreground ml-auto" } %}
|
||||
{% endset %}
|
||||
|
||||
{% call popover(
|
||||
id="demo-sidebar-dropdown-account",
|
||||
trigger=trigger,
|
||||
trigger_attrs={"class": "btn-ghost h-12 justify-start p-2 w-full"},
|
||||
content_attrs={"class": "min-w-56", "data-side": "right", "data-align": "end"}
|
||||
) %}
|
||||
{{ profile_menu | safe }}
|
||||
{% endcall %}
|
||||
</footer>
|
||||
Reference in New Issue
Block a user