mirror of
https://github.com/hunvreus/basecoat.git
synced 2026-11-11 04:38:18 +00:00
Fixing HTMX issues
This commit is contained in:
@@ -21,19 +21,4 @@ export default async function(eleventyConfig) {
|
||||
callback(err, code);
|
||||
}
|
||||
});
|
||||
// Registry to ensure component assets (JS) load only once per page.
|
||||
let componentRegistry;
|
||||
eleventyConfig.on('beforeBuild', () => { componentRegistry = {}; });
|
||||
eleventyConfig.addNunjucksGlobal("registerComponent", function(componentName) {
|
||||
// Reset registry at the start of each page build
|
||||
if (!this.page) {
|
||||
componentRegistry = {};
|
||||
}
|
||||
|
||||
if (!componentRegistry[componentName]) {
|
||||
componentRegistry[componentName] = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
@@ -12,64 +12,77 @@
|
||||
content_footer_attrs={},
|
||||
open=false,
|
||||
close_button=true,
|
||||
close_on_overlay_click=true
|
||||
close_on_overlay_click=true,
|
||||
include_html=true,
|
||||
include_script=true,
|
||||
register_on=["alpine:init"]
|
||||
) %}
|
||||
{% if registerComponent('dialog') %}
|
||||
{% if include_script %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('dialog', (initialOpen = false, initialCloseOnOverlayClick = true) => ({
|
||||
id: null,
|
||||
open: false,
|
||||
closeOnOverlayClick: true,
|
||||
if (!window._dialogComponentLoaded) {
|
||||
window._dialogComponentLoaded = true;
|
||||
|
||||
init() {
|
||||
this.id = this.$el.id;
|
||||
this.open = initialOpen;
|
||||
this.closeOnOverlayClick = initialCloseOnOverlayClick;
|
||||
},
|
||||
show() {
|
||||
if (!this.open) {
|
||||
this.open = true;
|
||||
this.$nextTick(() => {
|
||||
this.$el.dispatchEvent(new CustomEvent('dialog:opened', { bubbles: true }));
|
||||
setTimeout(() => this.$refs.dialog.focus(), 50);
|
||||
});
|
||||
const registerDialogComponent = () => {
|
||||
Alpine.data('dialog', (initialOpen = false, initialCloseOnOverlayClick = true) => ({
|
||||
id: null,
|
||||
open: false,
|
||||
closeOnOverlayClick: true,
|
||||
|
||||
init() {
|
||||
this.id = this.$el.id;
|
||||
this.open = initialOpen;
|
||||
this.closeOnOverlayClick = initialCloseOnOverlayClick;
|
||||
},
|
||||
show() {
|
||||
if (!this.open) {
|
||||
this.open = 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) {
|
||||
const action = detail.action || 'toggle';
|
||||
if (action === 'open') this.show();
|
||||
else if (action === 'close') this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
$main: {
|
||||
'@dialog:control.window'(e) { this.handleControl(e) },
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.show() },
|
||||
':aria-expanded'() { return this.open }
|
||||
},
|
||||
|
||||
$content: {
|
||||
'@keydown.escape'() { this.open && this.hide() },
|
||||
':inert'() { return !this.open },
|
||||
'@click'(e) { e.target === this.$el && this.closeOnOverlayClick && this.hide() },
|
||||
'x-cloak': ''
|
||||
}
|
||||
},
|
||||
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) {
|
||||
const action = detail.action || 'toggle';
|
||||
if (action === 'open') this.show();
|
||||
else if (action === 'close') this.hide();
|
||||
}
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
$main: {
|
||||
'@dialog:control.window'(e) { this.handleControl(e) },
|
||||
},
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.show() },
|
||||
':aria-expanded'() { return this.open }
|
||||
},
|
||||
|
||||
$content: {
|
||||
'@keydown.escape'() { this.open && this.hide() },
|
||||
':inert'() { return !this.open },
|
||||
'@click'(e) { e.target === this.$el && this.closeOnOverlayClick && this.hide() },
|
||||
'x-cloak': ''
|
||||
}
|
||||
}));
|
||||
});
|
||||
{% for event in register_on %}
|
||||
document.addEventListener('{{ event }}', registerDialogComponent);
|
||||
{% endfor %}
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if include_html %}
|
||||
<div
|
||||
id="{{ id }}"
|
||||
x-data="dialog({{ 'true' if open else 'false' }}, {{ 'true' if close_on_overlay_click else 'false' }})"
|
||||
@@ -146,4 +159,5 @@
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -3,108 +3,120 @@
|
||||
trigger=None,
|
||||
main_attrs=None,
|
||||
trigger_attrs=None,
|
||||
content_attrs=None
|
||||
content_attrs=None,
|
||||
include_html=true,
|
||||
include_script=true,
|
||||
register_on=["alpine:init"]
|
||||
) %}
|
||||
{% if registerComponent('dropdown-menu') %}
|
||||
{% if include_script %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('dropdownMenu', () => ({
|
||||
open: false,
|
||||
focusedIndex: null,
|
||||
menuItems: [],
|
||||
if (!window._dropdownMenuComponentLoaded) {
|
||||
window._dropdownMenuComponentLoaded = true;
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.menuItems = Array.from(this.$el.querySelectorAll('[role=menuitem]:not([disabled])'));
|
||||
});
|
||||
},
|
||||
focusMenuitem() {
|
||||
if (this.menuItems.length === 0) return;
|
||||
const registerDropdownMenuComponent = () => {
|
||||
Alpine.data('dropdownMenu', () => ({
|
||||
open: false,
|
||||
focusedIndex: null,
|
||||
menuItems: [],
|
||||
|
||||
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) {
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.trigger.focus();
|
||||
this.open = false;
|
||||
this.menuItems = Array.from(this.$el.querySelectorAll('[role=menuitem]:not([disabled])'));
|
||||
});
|
||||
}
|
||||
},
|
||||
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();
|
||||
}
|
||||
},
|
||||
},
|
||||
focusMenuitem() {
|
||||
if (this.menuItems.length === 0) return;
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.open = !this.open },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
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();
|
||||
},
|
||||
'@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();
|
||||
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);
|
||||
}
|
||||
},
|
||||
'@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': ''
|
||||
},
|
||||
}));
|
||||
});
|
||||
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': ''
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
{% for event in register_on %}
|
||||
document.addEventListener('{{ event }}', registerDropdownMenuComponent);
|
||||
{% endfor %}
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if include_html %}
|
||||
<div
|
||||
class="popover {% if main_attrs %}{{ main_attrs.pop('class', '') }}{% endif %}"
|
||||
class="popover {{ main_attrs.class }}"
|
||||
x-data="dropdownMenu"
|
||||
@click.away="open = false"
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
{% for key, value in main_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{% if trigger %}
|
||||
@@ -138,4 +150,5 @@
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -3,44 +3,55 @@
|
||||
trigger=None,
|
||||
main_attrs=None,
|
||||
trigger_attrs=None,
|
||||
content_attrs=None
|
||||
content_attrs=None,
|
||||
include_html=true,
|
||||
include_script=true,
|
||||
register_on=["alpine:init"]
|
||||
) %}
|
||||
{% if registerComponent('popover') %}
|
||||
{% if include_script %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('popover', () => ({
|
||||
open: false,
|
||||
if (!window._popoverComponentLoaded) {
|
||||
window._popoverComponentLoaded = true;
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.open = !this.open },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
const registerPopoverComponent = () => {
|
||||
Alpine.data('popover', () => ({
|
||||
open: false,
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.open = !this.open },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
':aria-expanded'() { return this.open },
|
||||
'x-ref': 'trigger'
|
||||
},
|
||||
':aria-expanded'() { return this.open },
|
||||
'x-ref': 'trigger'
|
||||
},
|
||||
$content: {
|
||||
'@click'(e) { this.handleMenuitemClick(e) },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
$content: {
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
},
|
||||
':aria-hidden'() { return !this.open },
|
||||
'x-cloak': ''
|
||||
},
|
||||
}));
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
{% for event in register_on %}
|
||||
document.addEventListener('{{ event }}', registerPopoverComponent);
|
||||
{% endfor %}
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if include_html %}
|
||||
<div
|
||||
class="popover {% if main_attrs %}{{ main_attrs.pop('class', '') }}{% endif %}"
|
||||
class="popover {{ main_attrs.class }}"
|
||||
x-data="popover"
|
||||
@click.away="open = false"
|
||||
{% if id %}id="{{ id }}"{% endif %}
|
||||
{% for key, value in main_attrs %}
|
||||
{{ key }}="{{ value }}"
|
||||
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
||||
{% endfor %}
|
||||
>
|
||||
{% if trigger %}
|
||||
@@ -72,4 +83,5 @@
|
||||
{% if caller %}{{ caller() }}{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
@@ -6,157 +6,169 @@
|
||||
trigger_attrs=None,
|
||||
content_attrs=None,
|
||||
listbox_attrs=None,
|
||||
is_combobox=False
|
||||
is_combobox=False,
|
||||
include_html=true,
|
||||
include_script=true,
|
||||
register_on=["alpine:init"]
|
||||
) %}
|
||||
{% if registerComponent('select') %}
|
||||
{% if include_script %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('select', (name = null, initialValue = null) => ({
|
||||
open: false,
|
||||
name: null,
|
||||
options: [],
|
||||
disabledOptions: [],
|
||||
focusedIndex: null,
|
||||
selectedLabel: null,
|
||||
selectedValue: null,
|
||||
query: '',
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
if (name) this.name = name;
|
||||
this.options = Array.from(this.$el.querySelectorAll('[role=option]:not([aria-disabled])'));
|
||||
this.disabledOptions = Array.from(this.$el.querySelectorAll('[role=option][aria-disabled=true]'));
|
||||
if (!window._selectComponentLoaded) {
|
||||
window._selectComponentLoaded = true;
|
||||
|
||||
const registerSelectComponent = () => {
|
||||
Alpine.data('select', (name = null, initialValue = null) => ({
|
||||
open: false,
|
||||
name: null,
|
||||
options: [],
|
||||
disabledOptions: [],
|
||||
focusedIndex: null,
|
||||
selectedLabel: null,
|
||||
selectedValue: null,
|
||||
query: '',
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
if (name) this.name = name;
|
||||
this.options = Array.from(this.$el.querySelectorAll('[role=option]:not([aria-disabled])'));
|
||||
this.disabledOptions = Array.from(this.$el.querySelectorAll('[role=option][aria-disabled=true]'));
|
||||
if (this.options.length === 0) return;
|
||||
if (initialValue) {
|
||||
const option = this.options.find(opt => opt.getAttribute('data-value') === initialValue);
|
||||
this.selectOption(option);
|
||||
this.focusedIndex = this.options.indexOf(option);
|
||||
this.focusOption();
|
||||
} else {
|
||||
this.selectOption(this.options[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
focusOption() {
|
||||
if (this.options.length === 0) return;
|
||||
if (initialValue) {
|
||||
const option = this.options.find(opt => opt.getAttribute('data-value') === initialValue);
|
||||
this.selectOption(option);
|
||||
this.focusedIndex = this.options.indexOf(option);
|
||||
this.focusOption();
|
||||
} else {
|
||||
this.selectOption(this.options[0]);
|
||||
|
||||
if (this.focusedIndex >= this.options.length) {
|
||||
this.focusedIndex = this.options.length - 1;
|
||||
} else if (this.focusedIndex < 0 || this.focusedIndex === null) {
|
||||
this.focusedIndex = 0;
|
||||
}
|
||||
});
|
||||
},
|
||||
focusOption() {
|
||||
if (this.options.length === 0) return;
|
||||
|
||||
if (this.focusedIndex >= this.options.length) {
|
||||
this.focusedIndex = this.options.length - 1;
|
||||
} else if (this.focusedIndex < 0 || this.focusedIndex === null) {
|
||||
this.focusedIndex = 0;
|
||||
}
|
||||
this.options.forEach(opt => opt.removeAttribute('data-focus'));
|
||||
const option = this.options[this.focusedIndex];
|
||||
option.setAttribute('data-focus', '');
|
||||
option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
||||
},
|
||||
focusOnSelectedOption() {
|
||||
if (this.options.length === 0 || this.selectedValue === null) return;
|
||||
const option = this.options.find(opt => opt.getAttribute('data-value') === this.selectedValue);
|
||||
if (!option) return;
|
||||
this.focusedIndex = this.options.indexOf(option);
|
||||
this.focusOption();
|
||||
},
|
||||
moveOptionFocus(delta) {
|
||||
if (this.options.length === 0) return;
|
||||
|
||||
if (!this.open) {
|
||||
this.open = true;
|
||||
this.focusOnSelectedOption();
|
||||
} else {
|
||||
this.focusedIndex = this.focusedIndex === null
|
||||
? 0
|
||||
: this.focusedIndex + delta;
|
||||
}
|
||||
this.focusOption();
|
||||
},
|
||||
handleOptionClick(event) {
|
||||
const option = event.target.closest('[role=option]');
|
||||
if (option && option.getAttribute('aria-disabled') !== 'true') {
|
||||
this.selectOption(option);
|
||||
this.open = false;
|
||||
this.$nextTick(() => this.$refs.trigger.focus());
|
||||
}
|
||||
},
|
||||
handleOptionMousemove(event) {
|
||||
const option = event.target.closest('[role=option]');
|
||||
if (option && option.getAttribute('aria-disabled') !== 'true') {
|
||||
this.options.forEach(opt => opt.removeAttribute('data-focus'));
|
||||
const option = this.options[this.focusedIndex];
|
||||
option.setAttribute('data-focus', '');
|
||||
option.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
||||
},
|
||||
focusOnSelectedOption() {
|
||||
if (this.options.length === 0 || this.selectedValue === null) return;
|
||||
const option = this.options.find(opt => opt.getAttribute('data-value') === this.selectedValue);
|
||||
if (!option) return;
|
||||
this.focusedIndex = this.options.indexOf(option);
|
||||
this.focusOption();
|
||||
}
|
||||
},
|
||||
handleOptionEnter(event) {
|
||||
this.selectOption(this.options[this.focusedIndex]);
|
||||
this.open = !this.open;
|
||||
},
|
||||
selectOption(option) {
|
||||
if (this.options.length === 0 || !option || option.disabled || !option.getAttribute('data-value')
|
||||
) return;
|
||||
},
|
||||
moveOptionFocus(delta) {
|
||||
if (this.options.length === 0) return;
|
||||
|
||||
this.options.forEach(opt => {
|
||||
opt.setAttribute('aria-selected', opt === option);
|
||||
});
|
||||
this.selectedLabel = option.innerHTML;
|
||||
this.selectedValue = option.getAttribute('data-value');
|
||||
this.$dispatch('select:change', {
|
||||
value: this.selectedValue,
|
||||
label: this.selectedLabel
|
||||
});
|
||||
},
|
||||
filterOptions(query) {
|
||||
if (query.length > 0) {
|
||||
this.disabledOptions.forEach(opt => { opt.setAttribute('aria-hidden', 'true'); });
|
||||
} else {
|
||||
this.disabledOptions.forEach(opt => { opt.removeAttribute('aria-hidden'); });
|
||||
}
|
||||
this.options.forEach(opt => {
|
||||
opt.removeAttribute('aria-hidden');
|
||||
if (opt.getAttribute('data-value') != null && !opt.innerHTML.toLowerCase().includes(query.toLowerCase())) {
|
||||
opt.setAttribute('aria-hidden', 'true');
|
||||
if (!this.open) {
|
||||
this.open = true;
|
||||
this.focusOnSelectedOption();
|
||||
} else {
|
||||
this.focusedIndex = this.focusedIndex === null
|
||||
? 0
|
||||
: this.focusedIndex + delta;
|
||||
}
|
||||
});
|
||||
},
|
||||
this.focusOption();
|
||||
},
|
||||
handleOptionClick(event) {
|
||||
const option = event.target.closest('[role=option]');
|
||||
if (option && option.getAttribute('aria-disabled') !== 'true') {
|
||||
this.selectOption(option);
|
||||
this.open = false;
|
||||
this.$nextTick(() => this.$refs.trigger.focus());
|
||||
}
|
||||
},
|
||||
handleOptionMousemove(event) {
|
||||
const option = event.target.closest('[role=option]');
|
||||
if (option && option.getAttribute('aria-disabled') !== 'true') {
|
||||
this.focusedIndex = this.options.indexOf(option);
|
||||
this.focusOption();
|
||||
}
|
||||
},
|
||||
handleOptionEnter(event) {
|
||||
this.selectOption(this.options[this.focusedIndex]);
|
||||
this.open = !this.open;
|
||||
},
|
||||
selectOption(option) {
|
||||
if (this.options.length === 0 || !option || option.disabled || !option.getAttribute('data-value')
|
||||
) return;
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.open = !this.open; this.focusOnSelectedOption(); },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
this.options.forEach(opt => {
|
||||
opt.setAttribute('aria-selected', opt === option);
|
||||
});
|
||||
this.selectedLabel = option.innerHTML;
|
||||
this.selectedValue = option.getAttribute('data-value');
|
||||
this.$dispatch('select:change', {
|
||||
value: this.selectedValue,
|
||||
label: this.selectedLabel
|
||||
});
|
||||
},
|
||||
'@keydown.down.prevent'() { this.moveOptionFocus(+1); },
|
||||
'@keydown.up.prevent'() { this.moveOptionFocus(-1); },
|
||||
'@keydown.home.prevent'() { this.focusOption(0) },
|
||||
'@keydown.end.prevent'() { this.focusOption(this.options.length - 1) },
|
||||
'@keydown.enter.prevent'() {
|
||||
if (this.open) this.handleOptionEnter();
|
||||
else this.open = true;
|
||||
filterOptions(query) {
|
||||
if (query.length > 0) {
|
||||
this.disabledOptions.forEach(opt => { opt.setAttribute('aria-hidden', 'true'); });
|
||||
} else {
|
||||
this.disabledOptions.forEach(opt => { opt.removeAttribute('aria-hidden'); });
|
||||
}
|
||||
this.options.forEach(opt => {
|
||||
opt.removeAttribute('aria-hidden');
|
||||
if (opt.getAttribute('data-value') != null && !opt.innerHTML.toLowerCase().includes(query.toLowerCase())) {
|
||||
opt.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
});
|
||||
},
|
||||
':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'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
|
||||
$trigger: {
|
||||
'@click'() { this.open = !this.open; this.focusOnSelectedOption(); },
|
||||
'@keydown.escape.prevent'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
'@keydown.down.prevent'() { this.moveOptionFocus(+1); },
|
||||
'@keydown.up.prevent'() { this.moveOptionFocus(-1); },
|
||||
'@keydown.home.prevent'() { this.focusOption(0) },
|
||||
'@keydown.end.prevent'() { this.focusOption(this.options.length - 1) },
|
||||
'@keydown.enter.prevent'() {
|
||||
if (this.open) this.handleOptionEnter();
|
||||
else this.open = true;
|
||||
},
|
||||
':aria-expanded'() { return this.open },
|
||||
'x-ref': 'trigger'
|
||||
},
|
||||
'@keydown.down.prevent'() { this.moveOptionFocus(+1); },
|
||||
'@keydown.up.prevent'() { this.moveOptionFocus(-1); },
|
||||
'@keydown.home.prevent'() { this.focusOption(0) },
|
||||
'@keydown.end.prevent'() { this.focusOption(this.options.length - 1) },
|
||||
'@keydown.enter.prevent'() { this.handleOptionEnter() },
|
||||
}
|
||||
}));
|
||||
});
|
||||
$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'() {
|
||||
this.open = false;
|
||||
this.$refs.trigger.focus();
|
||||
},
|
||||
'@keydown.down.prevent'() { this.moveOptionFocus(+1); },
|
||||
'@keydown.up.prevent'() { this.moveOptionFocus(-1); },
|
||||
'@keydown.home.prevent'() { this.focusOption(0) },
|
||||
'@keydown.end.prevent'() { this.focusOption(this.options.length - 1) },
|
||||
'@keydown.enter.prevent'() { this.handleOptionEnter() },
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
{% for event in register_on %}
|
||||
document.addEventListener('{{ event }}', registerSelectComponent);
|
||||
{% endfor %}
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if include_html %}
|
||||
<div
|
||||
class="popover {{ main_attrs.class }}"
|
||||
x-data="select('{{ name }}', '{{ selected }}')"
|
||||
@@ -231,4 +243,5 @@
|
||||
<input type="hidden" name="{{ name }}" x-model="selectedValue">
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -3,75 +3,87 @@
|
||||
tabsets=[],
|
||||
main_attrs=None,
|
||||
tablist_attrs=None,
|
||||
default_tab_index=1
|
||||
default_tab_index=1,
|
||||
include_html=true,
|
||||
include_script=true,
|
||||
register_on=["alpine:init"]
|
||||
)
|
||||
%}
|
||||
{% if registerComponent('tabs') %}
|
||||
{% if include_script %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('tabs', (initialTabIndex = 0) => ({
|
||||
activeTabIndex: initialTabIndex,
|
||||
tabs: [],
|
||||
panels: [],
|
||||
if (!window._tabsComponentLoaded) {
|
||||
window._tabsComponentLoaded = true;
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.tabs = Array.from(this.$el.querySelectorAll(':scope > [role=tablist] [role=tab]:not([disabled])'));
|
||||
this.panels = Array.from(this.$el.querySelectorAll(':scope > [role=tabpanel]'));
|
||||
if (this.tabs.length > 0) {
|
||||
this.selectTab(this.tabs[initialTabIndex], false);
|
||||
}
|
||||
});
|
||||
},
|
||||
nextTab() {
|
||||
if (this.tabs.length === 0) return;
|
||||
let newIndex = (this.activeTabIndex + 1) % this.tabs.length;
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
},
|
||||
prevTab() {
|
||||
if (this.tabs.length === 0) return;
|
||||
let newIndex = (this.activeTabIndex - 1 + this.tabs.length) % this.tabs.length;
|
||||
this.selectTab(this.tabs[newIndex]);
|
||||
},
|
||||
selectTab(tab, focus = true) {
|
||||
if (!tab || this.tabs.length === 0) return;
|
||||
const registerTabsComponent = () => {
|
||||
Alpine.data('tabs', (initialTabIndex = 0) => ({
|
||||
activeTabIndex: initialTabIndex,
|
||||
tabs: [],
|
||||
panels: [],
|
||||
|
||||
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);
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.tabs = Array.from(this.$el.querySelectorAll(':scope > [role=tablist] [role=tab]:not([disabled])'));
|
||||
this.panels = Array.from(this.$el.querySelectorAll(':scope > [role=tabpanel]'));
|
||||
if (this.tabs.length > 0) {
|
||||
this.selectTab(this.tabs[initialTabIndex], false);
|
||||
}
|
||||
});
|
||||
},
|
||||
['@keydown.arrow-right.prevent']() { this.nextTab() },
|
||||
['@keydown.arrow-left.prevent']() { this.prevTab() },
|
||||
},
|
||||
}));
|
||||
});
|
||||
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() },
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
{% for event in register_on %}
|
||||
document.addEventListener('{{ event }}', registerTabsComponent);
|
||||
{% endfor %}
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if include_html %}
|
||||
<div
|
||||
class="tabs {{ main_attrs.class }}"
|
||||
x-data="tabs({{ default_tab_index - 1 }})"
|
||||
@@ -122,4 +134,5 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -2,55 +2,67 @@
|
||||
id="toaster",
|
||||
toasts=[],
|
||||
main_attrs=None,
|
||||
is_fragment=false
|
||||
is_fragment=false,
|
||||
include_html=true,
|
||||
include_script=true,
|
||||
register_on=["alpine:init"]
|
||||
) %}
|
||||
{% if not is_fragment and registerComponent('toast') %}
|
||||
{% if not is_fragment and include_script %}
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('toaster', { isPaused: false });
|
||||
Alpine.data('toast', (category = '', timeoutDuration = null) => ({
|
||||
open: false,
|
||||
timeoutDuration: null,
|
||||
timeoutId: null,
|
||||
if (!window._toastComponentLoaded) {
|
||||
window._toastComponentLoaded = true;
|
||||
|
||||
init() {
|
||||
this.timeoutDuration = timeoutDuration || (category === 'error' ? 5000 : 3000);
|
||||
this.timeoutId = setTimeout(() => { this.close() }, this.timeoutDuration);
|
||||
this.open = true;
|
||||
this.$watch('$store.toaster.isPaused', (isPaused) => {
|
||||
if (!this.open) return;
|
||||
if (isPaused) {
|
||||
this.pauseTimeout();
|
||||
} else {
|
||||
this.resumeTimeout();
|
||||
}
|
||||
});
|
||||
},
|
||||
pauseTimeout() {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
},
|
||||
resumeTimeout(index) {
|
||||
if (this.open && this.timeoutId === null) {
|
||||
const registerToastComponent = () => {
|
||||
Alpine.store('toaster', { isPaused: false });
|
||||
Alpine.data('toast', (category = '', timeoutDuration = null) => ({
|
||||
open: false,
|
||||
timeoutDuration: null,
|
||||
timeoutId: null,
|
||||
|
||||
init() {
|
||||
this.timeoutDuration = timeoutDuration || (category === 'error' ? 5000 : 3000);
|
||||
this.timeoutId = setTimeout(() => { this.close() }, this.timeoutDuration);
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.pauseTimeout();
|
||||
this.open = false;
|
||||
this.$el.blur();
|
||||
},
|
||||
this.open = true;
|
||||
this.$watch('$store.toaster.isPaused', (isPaused) => {
|
||||
if (!this.open) return;
|
||||
if (isPaused) {
|
||||
this.pauseTimeout();
|
||||
} else {
|
||||
this.resumeTimeout();
|
||||
}
|
||||
});
|
||||
},
|
||||
pauseTimeout() {
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
},
|
||||
resumeTimeout(index) {
|
||||
if (this.open && this.timeoutId === null) {
|
||||
this.timeoutId = setTimeout(() => { this.close() }, this.timeoutDuration);
|
||||
}
|
||||
},
|
||||
close() {
|
||||
this.pauseTimeout();
|
||||
this.open = false;
|
||||
this.$el.blur();
|
||||
},
|
||||
|
||||
$toast: {
|
||||
['@mouseenter']() { this.$store.toaster.isPaused = true },
|
||||
['@mouseleave']() { this.$store.toaster.isPaused = false },
|
||||
['@keydown.escape.prevent']() { this.close() },
|
||||
},
|
||||
}));
|
||||
});
|
||||
$toast: {
|
||||
['@mouseenter']() { this.$store.toaster.isPaused = true },
|
||||
['@mouseleave']() { this.$store.toaster.isPaused = false },
|
||||
['@keydown.escape.prevent']() { this.close() },
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
{% for event in register_on %}
|
||||
document.addEventListener('{{ event }}', registerToastComponent);
|
||||
{% endfor %}
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% if include_html %}
|
||||
{% set iconMapping = {
|
||||
'error': 'circle-x',
|
||||
'success': 'circle-check',
|
||||
@@ -131,4 +143,5 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
@@ -47,6 +47,21 @@
|
||||
<!-- 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>
|
||||
<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>
|
||||
const applyHighlighting = () => { if (window.hljs) hljs.highlightAll(); };
|
||||
|
||||
if (!window._hljsInitialized) {
|
||||
window._hljsInitialized = true;
|
||||
document.addEventListener('DOMContentLoaded', applyHighlighting);
|
||||
document.addEventListener('htmx:afterSwap', applyHighlighting);
|
||||
}
|
||||
</script>
|
||||
|
||||
{% if head %}{{ head | safe }}{% endif %}
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
@@ -107,6 +122,7 @@
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{% from "components/toast.njk" import toaster %}
|
||||
{{ toaster() }}
|
||||
</body>
|
||||
|
||||
@@ -36,11 +36,4 @@ layout: layouts/base.njk
|
||||
{% 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>
|
||||
</main>
|
||||
@@ -4,7 +4,7 @@
|
||||
{% 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 }}">
|
||||
<div {% if class %}class="{{ class }}"{% endif %}>
|
||||
{{ code | safe }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -21,7 +21,8 @@
|
||||
{{ tabs(
|
||||
id="code-block-{{ id }}",
|
||||
tabsets=tabsets,
|
||||
default_tab_index=(2 if default_code else 1)
|
||||
default_tab_index=(2 if default_code else 1),
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) }}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
@@ -28,7 +28,8 @@ toc:
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
footer=footer,
|
||||
close_button=false,
|
||||
close_on_overlay_click=false
|
||||
close_on_overlay_click=false,
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) }}{% endset %}
|
||||
{{ code_preview("alert-dialog", code | prettyHtml) }}
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ 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"}
|
||||
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"]
|
||||
) %}
|
||||
<nav role="menu">
|
||||
<button type="button" role="menuitem">Documentation</button>
|
||||
|
||||
@@ -26,7 +26,8 @@ toc:
|
||||
{% call select(
|
||||
trigger_attrs={"class": "w-[200px]"},
|
||||
listbox_attrs={"data-empty": "No framework found."},
|
||||
is_combobox=true
|
||||
is_combobox=true,
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
{% for framework in options_frameworks %}
|
||||
<div role="option" data-value="{{ framework }}">{{ framework }}</div>
|
||||
@@ -114,7 +115,8 @@ toc:
|
||||
trigger_attrs={"class": "w-[200px]"},
|
||||
content_attrs={"class": "w-72"},
|
||||
listbox_attrs={"data-empty": "No timezone found."},
|
||||
is_combobox=true
|
||||
is_combobox=true,
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div class="max-h-64 overflow-y-auto scrollbar">
|
||||
{% for group in options_timezones %}
|
||||
|
||||
@@ -34,7 +34,8 @@ toc:
|
||||
trigger="Edit Profile",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-full sm:max-w-[425px] max-h-[612px]"},
|
||||
footer=footer
|
||||
footer=footer,
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<form class="form grid gap-4">
|
||||
<div class="grid gap-3">
|
||||
@@ -54,6 +55,10 @@ toc:
|
||||
|
||||
<h3 id="usage-default"><a href="#usage-default">Default</a></h3>
|
||||
|
||||
<section class="prose my-6">
|
||||
<p></p>
|
||||
</section>
|
||||
|
||||
{{ code_block(code | prettyHtml) }}
|
||||
|
||||
<h3 id="usage-jinja"><a href="#usage-jinja">Jinja or Nunjucks</a></h3>
|
||||
@@ -104,7 +109,8 @@ toc:
|
||||
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
|
||||
footer=footer,
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<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>
|
||||
|
||||
@@ -12,7 +12,8 @@ description: Displays a menu to the user — such as a set of actions or functio
|
||||
id="dropdown-menu",
|
||||
trigger="Open",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "min-w-56"}
|
||||
content_attrs={"class": "min-w-56"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="group" aria-labelledby="account-options">
|
||||
<span id="account-options" role="heading">Account Options</span>
|
||||
|
||||
@@ -21,7 +21,8 @@ toc:
|
||||
id="demo-popover",
|
||||
trigger="Open popover",
|
||||
trigger_attrs={"class": "btn-outline"},
|
||||
content_attrs={"class": "w-80 p-4"}
|
||||
content_attrs={"class": "w-80 p-4"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div class="grid gap-4">
|
||||
<header class="grid gap-1.5">
|
||||
|
||||
@@ -36,7 +36,11 @@ toc:
|
||||
{{ code_preview("select-native", code_native) }}
|
||||
|
||||
{% set code_alpine %}
|
||||
{% call select(name="select-js", trigger_attrs={"class": "w-[180px]"}) %}
|
||||
{% call select(
|
||||
name="select-js",
|
||||
trigger_attrs={"class": "w-[180px]"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="group" aria-labelledby="fruit-options-label">
|
||||
<span id="fruit-options-label" role="heading">Fruits</span>
|
||||
<div role="option" data-value="apple">Apple</div>
|
||||
@@ -99,7 +103,8 @@ toc:
|
||||
|
||||
{% set code %}
|
||||
{% call select(
|
||||
listbox_attrs={"class": "scrollbar overflow-y-auto max-h-64"}
|
||||
listbox_attrs={"class": "scrollbar overflow-y-auto max-h-64"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
{% for i in range(0, 99) %}
|
||||
<div role="option" data-value="item-{{ i }}">Item {{ i }}</div>
|
||||
@@ -112,7 +117,10 @@ toc:
|
||||
<h3 id="example-disabled"><a href="#example-disabled">Disabled</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call select(trigger_attrs={"disabled": "disabled"}) %}
|
||||
{% call select(
|
||||
trigger_attrs={"disabled": "disabled"},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div role="option" data-value="disabled">Disabled</div>
|
||||
{% endcall %}
|
||||
{% endset %}
|
||||
@@ -122,7 +130,10 @@ toc:
|
||||
<h3 id="example-with-icon"><a href="#example-with-icon">With icon</a></h3>
|
||||
|
||||
{% set code %}
|
||||
{% call select(trigger_attrs={'class': 'w-[180px]'}) %}
|
||||
{% call select(
|
||||
trigger_attrs={'class': 'w-[180px]'},
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) %}
|
||||
<div type="button" role="option" data-value="bar">
|
||||
{% lucide "chart-bar", {"class": "text-muted-foreground"} %}
|
||||
Bar
|
||||
|
||||
@@ -65,7 +65,8 @@ toc:
|
||||
id='demo-tabs-with-panels',
|
||||
tabsets=tabsets_demo,
|
||||
main_attrs={ "class": "w-full" },
|
||||
tablist_attrs={ "class": "w-full" }
|
||||
tablist_attrs={ "class": "w-full" },
|
||||
register_on=["alpine:init", "htmx:afterSwap"]
|
||||
) }}
|
||||
{% endset %}
|
||||
{{ code_preview("tabs", code | prettyHtml, "max-w-[300px] w-full") }}
|
||||
|
||||
Reference in New Issue
Block a user