Updated Jinja templates, fixed Nunjucks

This commit is contained in:
Ronan Berder
2025-06-16 21:11:36 +08:00
parent 1513075cb6
commit 43673ad316
13 changed files with 334 additions and 397 deletions
+71 -87
View File
@@ -8,103 +8,87 @@
@param footer {string} [optional] - HTML content for the dialog footer.
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
@param content_attrs {object} [optional] - Additional HTML attributes for the dialog content article.
@param content_header_attrs {object} [optional] - Additional HTML attributes for the dialog header.
@param content_body_attrs {object} [optional] - Additional HTML attributes for the dialog body section.
@param content_footer_attrs {object} [optional] - Additional HTML attributes for the dialog footer.
@param open {boolean} [optional] [default=False] - Whether the dialog should be open initially.
@param close_button {boolean} [optional] [default=True] - Whether to include a close button.
@param close_on_overlay_click {boolean} [optional] [default=True] - Whether clicking the overlay closes the dialog.
@param dialog_attrs {object} [optional] - Additional HTML attributes for the dialog content article.
@param header_attrs {object} [optional] - Additional HTML attributes for the dialog header.
@param body_attrs {object} [optional] - Additional HTML attributes for the dialog body section.
@param footer_attrs {object} [optional] - Additional HTML attributes for the dialog footer.
@param open {boolean} [optional] [default=false] - Whether the dialog should be open initially.
@param close_button {boolean} [optional] [default=true] - Whether to include a close button.
@param close_on_overlay_click {boolean} [optional] [default=true] - Whether clicking the overlay closes the dialog.
#}
{% macro dialog(
id,
id=None,
trigger=None,
title=None,
description=None,
footer=None,
main_attrs={},
dialog_attrs={},
trigger_attrs={},
content_attrs={},
content_header_attrs={},
content_body_attrs={},
content_footer_attrs={},
open=False,
close_button=True,
close_on_overlay_click=True
header_attrs={},
body_attrs={},
footer_attrs={},
open=false,
close_button=true,
close_on_overlay_click=true
) %}
<div
id="{{ id }}"
x-data="dialog({{ 'true' if open else 'false' }}, {{ 'true' if close_on_overlay_click else 'false' }})"
x-bind="$main"
class="dialog"
{% for key, value in main_attrs.items() %}
{% set id = id or ("dialog-" + (range(100000, 999999) | random | string)) %}
{% if trigger %}
<button
type="button"
onclick="document.getElementById('{{ id }}').showModal()"
{% for key, value in trigger_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{% if trigger %}
<button
type="button"
aria-expanded="false"
aria-controls="{{ id }}-dialog"
x-bind="$trigger"
{% for key, value in trigger_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ trigger }}
</button>
{% endif %}
<div
role="dialog"
id="{{ id }}-dialog"
tabindex="-1"
aria-modal="true"
aria-labelledby="{{ id }}-title"
inert
x-bind="$content"
>
<article
{% for key, value in content_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{% if title or description %}
<header
{% for key, value in content_header_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
<h2 id="{{ id }}-title">{{ title | safe }}</h2>
{% if description %}<p>{{ description | safe }}</p>{% endif %}
</header>
{% endif %}
{% if caller %}
<section
{% for key, value in content_body_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ caller() }}
</section>
{% endif %}
{% if footer %}
<footer
{% for key, value in content_footer_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ footer | safe }}
</footer>
{% endif %}
{% if close_button %}
<button
@click="hide()"
aria-label="Close dialog"
>
{{ trigger }}
</button>
{% endif %}
<dialog
id="{{ id }}"
class="dialog"
aria-labelledby="{{ id }}-title"
{% if description %}aria-describedby="{{ id }}-description"{% endif %}
{% if close_on_overlay_click %}onclick="this.close()"{% endif %}
{% for key, value in dialog_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
<article {% if close_on_overlay_click %}onclick="event.stopPropagation()"{% endif %}>
{% if title or description %}
<header
{% for key, value in header_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
>
<h2 id="{{ id }}-title">{{ title | safe }}</h2>
{% if description %}<p id="{{ id }}-description">{{ description | safe }}</p>{% endif %}
</header>
{% endif %}
{% if caller %}
<section
{% for key, value in body_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ caller() }}
</section>
{% endif %}
{% if footer %}
<footer
{% for key, value in footer_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ footer | safe }}
</footer>
{% endif %}
{% if close_button %}
<form method="dialog">
<button aria-label="Close dialog">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x-icon lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
</button>
{% endif %}
</article>
</div>
</div>
</button>
</form>
{% endif %}
</article>
</dialog>
{% endmacro %}
+46 -38
View File
@@ -3,61 +3,62 @@
@param id {string} [optional] - Unique identifier for the dropdown component.
@param trigger {string} [optional] - HTML content for the button that triggers the dropdown.
@param menu {array} [optional] - Array of menu items for the dropdown.
@param items {array} [optional] - Array of menu items for the dropdown.
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
@param content_attrs {object} [optional] - Additional HTML attributes for the dropdown content div.
@param popover_attrs {object} [optional] - Additional HTML attributes for the dropdown content div.
#}
{% macro dropdown_menu(
trigger,
id=None,
trigger=None,
menu=None,
items=None,
main_attrs={},
trigger_attrs={},
content_attrs={}
popover_attrs={},
menu_attrs={}
) %}
{% set id = id or ("dropdown-menu-" + (range(100000, 999999) | random | string)) %}
<div
class="popover {{ main_attrs.class }}"
x-data="dropdownMenu"
@click.away="open = false"
{% if id %}id="{{ id }}"{% endif %}
class="dropdown-menu {{ main_attrs.class }}"
{% for key, value in main_attrs.items() %}
{% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
{% if trigger %}
<button
type="button"
aria-haspopup="menu"
aria-expanded="false"
x-bind="$trigger"
{% if id %}
id="{{ id }}-trigger"
aria-haspopup="menu"
aria-controls="{{ id }}-menu"
{% endif %}
aria-expanded="false"
{% for key, value in trigger_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ trigger | safe }}
</button>
{% endif %}
<div
id="{{ id }}"
data-popover
aria-hidden="true"
x-bind="$content"
{% if id %}id="{{ id }}-menu"{% endif %}
{% for key, value in content_attrs.items() %}
{% for key, value in popover_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
<nav role="menu">
{% if menu %}
{{ render_dropdown_items(menu, id ~ "-items" if id else "items") }}
<div
role="menu"
id="{{ id }}-menu"
aria-labelledby="{{ id }}-trigger"
{% for key, value in menu_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{% if items %}
{{ render_dropdown_items(items, id ~ "-items" if id else "items") }}
{% else %}
{{ caller() if caller }}
{% endif %}
</nav>
</div>
</div>
</div>
{% endmacro %}
@@ -69,7 +70,7 @@
@param parent_id_prefix {string} [optional] - Prefix for generating element IDs.
#}
{% macro render_dropdown_items(items, parent_id_prefix="items") %}
{% for item in items.items() %}
{% for item in items %}
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
{% if item.type == "group" %}
@@ -77,11 +78,13 @@
<div
role="group"
aria-labelledby="{{ group_label_id }}"
{% for key, value in item.attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
<div role="presentation" id="{{ group_label_id }}">{{ item.label }}</div>
{{ render_dropdown_items(item.items, item_id) if item.items }}
</div>
{% elif item.type == "separator" %}
@@ -89,24 +92,29 @@
{% elif item.type == "item" or not item.type %}
{% if item.url %}
<a
id="{{ item_id }}"
role="menuitem"
href="{{ item.url }}"
{% for key, value in item.attrs.items() %}
{% if key != "url" %} {{ key }}="{{ value }}" {% endif %}
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs.items() %}
{% if key != "url" %} {{ key }}="{{ value }}" {% endif %}
{% endfor %}
{% endif %}
>
{{ item.label | safe }}
</a>
{% else %}
<button
<div
id="{{ item_id }}"
role="menuitem"
type="button"
{% for key, value in item.attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{{ item.label | safe }}
</button>
</div>
{% endif %}
{% endif %}
{% endfor %}
+12 -20
View File
@@ -5,51 +5,43 @@
@param trigger {string} [optional] - HTML content for the element that triggers the popover.
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger element.
@param content_attrs {object} [optional] - Additional HTML attributes for the popover content div.
@param popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
#}
{% macro popover(
trigger,
id=None,
trigger=None,
main_attrs={},
trigger_attrs={},
content_attrs={}
popover_attrs={}
) %}
{% set id = id or ("popover-" + (range(100000, 999999) | random | string)) %}
<div
class="popover {{ main_attrs.class }}"
x-data="popover"
@click.away="open = false"
{% if id %}id="{{ id }}"{% endif %}
{% for key, value in main_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% 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 %}
type="button"
aria-expanded="false"
aria-controls="{{ id }}"
{% for key, value in trigger_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{{ trigger | safe }}
</button>
{% endif %}
<div
id="{{ id }}"
data-popover
aria-hidden="true"
x-bind="$content"
{% if id %}id="{{ id }}-menu"{% endif %}
{% for key, value in content_attrs.items() %}
{% for key, value in popover_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
>
{{ caller() if caller }}
</div>
</div>
{% endmacro %}
{% endmacro %}
+43 -33
View File
@@ -6,7 +6,7 @@
@param name {string} [optional] - The name attribute for the hidden input storing the selected value.
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
@param content_attrs {object} [optional] - Additional HTML attributes for the popover content div.
@param popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
@param listbox_attrs {object} [optional] - Additional HTML attributes for the listbox div.
@param input_attrs {object} [optional] - Additional HTML attributes for the hidden input.
@param search_placeholder {string} [optional] [default="Search entries..."] - Placeholder text for the search input (combobox only).
@@ -17,49 +17,55 @@
selected=None,
name=None,
items=None,
main_attrs={},
trigger_attrs={},
popover_attrs={},
listbox_attrs={},
input_attrs={},
search_placeholder="Search entries...",
is_combobox=false
) %}
{% set id = id or ("select-" + (range(100000, 999999) | random | string)) %}
{% set first_option = [] %}
{% set selected_option = [] %}
{% set first_option = namespace(item=None) %}
{% set selected_option = namespace(item=None) %}
{% for item in items.items() %}
{% for item in items %}
{% if item.type == "group" %}
{% for sub_item in item.items.items() %}
{% if not first_option[0] %}
{% set first_option = (first_option.push(sub_item), first_option) %}
{% for sub_item in item.items %}
{% if not first_option.item %}
{% set first_option.item = sub_item %}
{% endif %}
{% if selected and sub_item.value == selected and not selected_option[0] %}
{% set selected_option = (selected_option.push(sub_item), selected_option) %}
{% if selected and sub_item.value == selected and not selected_option.item %}
{% set selected_option.item = sub_item %}
{% endif %}
{% endfor %}
{% else %}
{% if not first_option[0] %}
{% set first_option = (first_option.push(item), first_option) %}
{% if not first_option.item %}
{% set first_option.item = item %}
{% endif %}
{% if selected and item.value == selected and not selected_option[0] %}
{% set selected_option = (selected_option.push(item), selected_option) %}
{% if selected and item.value == selected and not selected_option.item %}
{% set selected_option.item = item %}
{% endif %}
{% endif %}
{% endfor %}
{% set default_option = selected_option[0] or first_option[0] or None %}
{% set default_option = selected_option.item or first_option.item or None %}
<div class="select">
<div
class="select {{ main_attrs.class }}"
{% for key, value in main_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
<button
type="button"
class="btn-outline justify-between font-normal {{ trigger_attrs.class }}"
id="{{ id }}-trigger"
popovertarget="{{ id }}"
aria-haspopup="listbox"
aria-expanded="false"
aria-controls="{{ id }}-listbox"
{% for key, value in trigger_attrs %}
{% for key, value in trigger_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
>
@@ -71,11 +77,11 @@
{% endif %}
</button>
<div
popover
id="{{ id }}"
class="popover {{ popover_attrs.class }}"
data-popover
aria-hidden="true"
{% for key, value in popover_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{{ key }}="{{ value }}"
{% endfor %}
>
{% if is_combobox %}
@@ -90,8 +96,8 @@
spellcheck="false"
aria-autocomplete="list"
role="combobox"
aria-expanded="true"
aria-controls="{{ id }}-content"
aria-expanded="false"
aria-controls="{{ id }}-listbox"
aria-labelledby="{{ id }}-trigger"
>
</header>
@@ -101,11 +107,11 @@
id="{{ id }}-listbox"
aria-orientation="vertical"
aria-labelledby="{{ id }}-trigger"
{% for key, value in listbox_attrs %}
{% for key, value in listbox_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{% if items.length > 0 %}
{% if items %}
{{ render_select_items(items, default_option.value, id ~ "-items" if id else "items") }}
{% else %}
{{ caller() if caller }}
@@ -115,7 +121,7 @@
<input
type="hidden"
name="{{ name or id ~ '-value' }}"
value="{{ selected or '' }}"
value="{{ (default_option.value if default_option) or '' }}"
{% for key, value in input_attrs.items() %}
{% if key != 'name' and key != 'value' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
@@ -130,18 +136,20 @@
@param parent_id_prefix {string} [optional] - The prefix for the item id.
#}
{% macro render_select_items(items, selected, parent_id_prefix="items") %}
{% for item in items.items() %}
{% for item in items %}
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
{% if item.type == "group" %}
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
<div
role="group"
aria-labelledby="{{ group_label_id }}"
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
<div role="presentation" id="{{ group_label_id }}">{{ item.label }}</div>
{{ render_select_items(item.items, selected, item_id) if item.items }}
</div>
{% elif item.type == "separator" %}
@@ -152,9 +160,11 @@
role="option"
data-value="{{ item.value }}"
{% if selected == item.value %}aria-selected="true"{% endif %}
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{{ item.label | safe }}
</div>
+28 -22
View File
@@ -2,17 +2,21 @@
Renders a sidebar component.
@param id {string} [optional] - Unique identifier for the sidebar component.
@param label {string} [optional] [default="Sidebar navigation"] - Label for the sidebar navigation.
@param open {boolean} [optional] [default=True] - Whether the sidebar is open.
@param label {string} [optional] - Label for the sidebar navigation.
@param open {boolean} [optional] - Whether the sidebar is open.
@param side {string} [optional] - Side of the sidebar to display.
@param header {string} [optional] - Header content for the sidebar.
@param footer {string} [optional] - Footer content for the sidebar.
@param menu {array} [optional] - Array of menu items for the sidebar.
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
@param header_attrs {object} [optional] - Additional HTML attributes for the header div.
@param content_attrs {object} [optional] - Additional HTML attributes for the content div.
@param footer_attrs {object} [optional] - Additional HTML attributes for the footer div.
#}
{% macro sidebar(
id=None,
label="Sidebar navigation",
open=True,
open=true,
side=None,
header=None,
footer=None,
@@ -20,21 +24,17 @@
main_attrs={},
header_attrs={},
content_attrs={},
footer_attrs={},
content_wrapper_attrs=None
footer_attrs={}
) %}
<div
<aside
{% if id %}id="{{ id }}"{% endif %}
class="sidebar {{ main_attrs.class }}"
data-uninitialized
data-side="{{ side if side else "left" }}"
aria-hidden="{{ "true" if not open else "false" }}"
{{ "inert" if not open }}
{% for key, value in main_attrs.items() %}
{% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
x-data="sidebar({{ "true" if open else "false" }})"
x-bind="$main"
>
<nav
aria-label="{{ label }}"
@@ -71,7 +71,7 @@
</footer>
{% endif %}
</nav>
</div>
</aside>
{% endmacro %}
{#
@@ -81,7 +81,7 @@
@param parent_id_prefix {string} [optional] - Prefix for generating element IDs.
#}
{% macro render_sidebar_content(items, parent_id_prefix="content") %}
{% for item in items.items() %}
{% for item in items %}
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
{% if item.type == "group" %}
@@ -89,15 +89,17 @@
<div
role="group"
{% if item.label %}aria-labelledby="{{ group_label_id }}"{% endif %}
{% for key, value in item.attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{% if item.label %}
<h3 id="{{ group_label_id }}">{{ item.label }}</h3>
{% endif %}
<ul>
{{ render_sidebar_content(item.items, item_id) if item.items }}
{{ render_sidebar_content(item["items"], item_id) if item["items"] }}
</ul>
</div>
{% elif item.type == "separator" %}
@@ -107,16 +109,18 @@
<details
id="submenu-{{ item_id }}"
{{ "open" if item.open }}
{% for key, value in item.attrs.items() %}
{% if key != "open" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs.items() %}
{% if key != "open" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
{% endif %}
>
<summary aria-controls="submenu-{{ item_id }}-content">
{% if item.icon %}{{ item.icon | safe }}{% endif %}
{{ item.label }}
</summary>
<ul id="submenu-{{ item_id }}-content">
{{ render_sidebar_content(item.items, item_id) if item.items }}
{{ render_sidebar_content(item["items"], item_id) if item["items"] }}
</ul>
</details>
</li>
@@ -125,9 +129,11 @@
<a
href="{{ item.url }}"
{{ 'aria-current="page"' if item.current }}
{% for key, value in item.attrs.items() %}
{% if key != "href" and key != "aria-current" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs.items() %}
{% if key != "href" and key != "aria-current" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
{% endif %}
>
{% if item.icon %}{{ item.icon | safe }}{% endif %}
<span>{{ item.label }}</span>
+21 -18
View File
@@ -14,17 +14,17 @@
@param default_tab_index {number} [optional] [default=1] - The 1-based index of the tab to be active initially.
#}
{% macro tabs(
id,
id=None,
tabsets=[],
main_attrs=None,
tablist_attrs=None,
main_attrs={},
tablist_attrs={},
default_tab_index=1
)
%}
{% set id = id or ("tabs-" + (range(100000, 999999) | random | string)) %}
<div
class="tabs {{ main_attrs.class }}"
x-data="tabs({{ default_tab_index - 1 }})"
{% if id %}id="{{ id }}"{% endif %}
id="{{ id }}"
{% for key, value in main_attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
@@ -32,40 +32,43 @@
<nav
role="tablist"
aria-orientation="horizontal"
x-bind="$tablist"
{% for key, value in tablist_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
>
{% for tabset in tabsets.items() %}
{% for tabset in tabsets %}
<button
type="button"
role="tab"
id="{{ tabset.id }}-tab-{{ loop.index }}"
aria-controls="{{ tabset.id }}-panel-{{ loop.index }}"
id="{{ id }}-tab-{{ loop.index }}"
aria-controls="{{ id }}-panel-{{ loop.index }}"
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
tabindex="0"
{% for key, value in tabset.tab_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% if tabset.tab_attrs %}
{% for key, value in tabset.tab_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{{ tabset.tab | safe }}
</button>
{% endfor %}
</nav>
{% for tabset in tabsets.items() %}
{% for tabset in tabsets %}
{% if tabset.panel %}
<div
role="tabpanel"
id="{{ tabset.id }}-panel-{{ loop.index }}"
aria-labelledby="{{ tabset.id }}-tab-{{ loop.index }}"
id="{{ id }}-panel-{{ loop.index }}"
aria-labelledby="{{ 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.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% if tabset.panel_attrs %}
{% for key, value in tabset.panel_attrs.items() %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{{ tabset.panel | safe }}
</div>
+47 -136
View File
@@ -5,21 +5,19 @@
@param id {string} [optional] [default="toaster"] - Unique identifier for the toaster container.
@param toasts {array} [optional] - An array of toast objects to render initially. See the <code>toast()</code> macro for more details.
@param main_attrs {object} [optional] - Additional HTML attributes for the main toaster container div.
@param is_fragment {boolean} [optional] [default=False] - If True, renders only the toast elements with hx-swap-oob="beforeend", suitable for htmx responses. Skips script and template inclusion.
@param is_fragment {boolean} [optional] [default=false] - If true, renders only the toast elements with hx-swap-oob="beforeend", suitable for htmx responses. Skips script and template inclusion.
#}
{% macro toaster(
id="toaster",
toasts=[],
main_attrs={},
is_fragment=False
attrs={}
) %}
<div
id="{{ id }}"
class="toaster"
{% for key, value in main_attrs.items() %}
{{ key }}="{{ value }}"
class="toaster {{ attrs.class }}"
{% for key, value in attrs.items() %}
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
{% if is_fragment %}hx-swap-oob="beforeend"{% endif %}
>
{% for item in toasts %}
{{ toast(
@@ -31,79 +29,6 @@
) }}
{% endfor %}
</div>
{% if not is_fragment %}
<template id="toast-template">
<div
class="toast"
role="status"
aria-atomic="true"
x-bind="$toastBindings"
>
<div class="toast-content">
<div class="flex items-center justify-between gap-x-3 p-4 [&>svg]:size-4 [&>svg]:shrink-0 [&>[role=img]]:size-4 [&>[role=img]]:shrink-0 [&>[role=img]>svg]:size-4">
<template x-if="config.icon">
<span aria-hidden="true" role="img" x-html="config.icon"></span>
</template>
<template x-if="!config.icon && config.category === 'success'">
{{ toast_icons.success | safe }}
</template>
<template x-if="!config.icon && config.category === 'error'">
{{ toast_icons.error | safe }}
</template>
<template x-if="!config.icon && config.category === 'info'">
{{ toast_icons.info | safe }}
</template>
<template x-if="!config.icon && config.category === 'warning'">
{{ toast_icons.warning | safe }}
</template>
<section class="flex-1 flex flex-col gap-0.5 items-start">
<template x-if="config.title">
<h2 class="font-medium" x-text="config.title"></h2>
</template>
<template x-if="config.description">
<p class="text-muted-foreground" x-text="config.description"></p>
</template>
</section>
<template x-if="config.action || config.cancel">
<footer class="flex flex-col gap-1 self-start">
<template x-if="config.action?.click">
<button
type="button"
class="btn h-6 text-xs px-2.5 rounded-sm"
@click="executeAction(config.action.click)"
x-text="config.action.label"
></button>
</template>
<template x-if="config.action?.url">
<a
:href="config.action.url"
class="btn h-6 text-xs px-2.5 rounded-sm"
x-text="config.action.label"
></a>
</template>
<template x-if="config.cancel?.click">
<button
type="button"
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
@click="executeAction(config.cancel.click)"
x-text="config.cancel.label"
></button>
</template>
<template x-if="config.cancel?.url">
<a
:href="config.cancel.url"
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
x-text="config.cancel.label"
></a>
</template>
</footer>
</template>
</div>
</div>
</div>
</template>
{% endif %}
{% endmacro %}
{#
@@ -134,65 +59,51 @@
aria-atomic="true"
aria-hidden="false"
{% if category %}data-category="{{ category }}"{% endif %}
x-data="toast({
category: '{{ category }}',
duration: {{ duration or 'null' }}
})"
x-bind="$toastBindings"
>
<div class="toast-content">
<div class="flex items-center justify-between gap-x-3 p-4 [&>svg]:size-4 [&>svg]:shrink-0 [&>[role=img]]:size-4 [&>[role=img]]:shrink-0 [&>[role=img]>svg]:size-4">
{% if category in ["error", "success", "info", "warning"] %}
{{ toast_icons[category] | safe }}
{% if category in ["error", "success", "info", "warning"] %}
{% if category == "success" %}
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>
{% elif category == "error" %}
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>
{% elif category == "info" %}
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
{% elif category == "warning" %}
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
{% endif %}
<section class="flex-1 flex flex-col gap-0.5 items-start">
{% if title %}
<h2 class="font-medium">{{ title }}</h2>
{% endif %}
{% if description %}
<p class="text-muted-foreground">{{ description }}</p>
{% endif %}
</section>
{% if action or cancel %}
<footer class="flex flex-col gap-1 self-start">
{% if action %}
{% if action.click %}
<button
type="button"
class="btn h-6 text-xs px-2.5 rounded-sm"
@click="{{ action.click }}"
>{{ action.label }}</button>
{% elif action.url %}
<a
href="{{ action.url }}"
class="btn h-6 text-xs px-2.5 rounded-sm"
>{{ action.label }}</a>
{% endif %}
{% endif %}
<section>
{% if title %}<h2>{{ title }}</h2>{% endif %}
{% if description %}<p>{{ description }}</p>{% endif %}
</section>
{% if action or cancel %}
<footer>
{% if action %}
{% if action.href %}
<a
href="{{ action.href }}"
class="btn-sm"
data-toast-action
>{{ action.label }}</a>
{% else %}
<button
type="button"
class="btn"
data-toast-action
{% if action.onclick %}onclick="{{ action.onclick }}"{% endif %}
>{{ action.label }}</button>
{% endif %}
{% if cancel %}
{% if cancel.click %}
<button
type="button"
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
@click="{{ cancel.click }}"
>{{ cancel.label }}</button>
{% elif cancel.url %}
<a
href="{{ cancel.url }}"
class="btn-outline h-6 text-xs px-2.5 rounded-sm"
>{{ toast.cancel.label }}</a>
{% endif %}
{% endif %}
</footer>
{% endif %}
</div>
{% endif %}
{% if cancel %}
<button
type="button"
class="btn-sm-outline"
data-toast-cancel
{% if cancel.onclick %}onclick="{{ cancel.onclick }}"{% endif %}
>{{ cancel.label }}</button>
{% endif %}
</footer>
{% endif %}
</div>
</div>
{% endmacro %}
{% set toast_icons = {
'success': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-check-icon lucide-circle-check"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',
'error': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-x-icon lucide-circle-x"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',
'info': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info-icon lucide-info"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
'warning': '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-triangle-alert-icon lucide-triangle-alert"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>'
} %}
{% endmacro %}
+17 -11
View File
@@ -3,14 +3,14 @@
@param id {string} [optional] - Unique identifier for the dropdown component.
@param trigger {string} [optional] - HTML content for the button that triggers the dropdown.
@param menu {array} [optional] - Array of menu items for the dropdown.
@param items {array} [optional] - Array of menu items for the dropdown.
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
@param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
@param popover_attrs {object} [optional] - Additional HTML attributes for the dropdown content div.
#}
{% macro dropdown_menu(
id=None,
trigger,
id=None,
items=None,
main_attrs={},
trigger_attrs={},
@@ -78,9 +78,11 @@
<div
role="group"
aria-labelledby="{{ group_label_id }}"
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
<div role="presentation" id="{{ group_label_id }}">{{ item.label }}</div>
{{ render_dropdown_items(item.items, item_id) if item.items }}
@@ -93,9 +95,11 @@
id="{{ item_id }}"
role="menuitem"
href="{{ item.url }}"
{% for key, value in item.attrs %}
{% if key != "url" %} {{ key }}="{{ value }}" {% endif %}
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs %}
{% if key != "url" %} {{ key }}="{{ value }}" {% endif %}
{% endfor %}
{% endif %}
>
{{ item.label | safe }}
</a>
@@ -103,9 +107,11 @@
<div
id="{{ item_id }}"
role="menuitem"
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{{ item.label | safe }}
</div>
+1 -1
View File
@@ -8,8 +8,8 @@
@param popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
#}
{% macro popover(
id=None,
trigger,
id=None,
main_attrs={},
trigger_attrs={},
popover_attrs={}
+10 -6
View File
@@ -145,9 +145,11 @@
<div
role="group"
aria-labelledby="{{ group_label_id }}"
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
<div role="presentation" id="{{ group_label_id }}">{{ item.label }}</div>
{{ render_select_items(item.items, selected, item_id) if item.items }}
@@ -160,9 +162,11 @@
role="option"
data-value="{{ item.value }}"
{% if selected == item.value %}aria-selected="true"{% endif %}
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{{ item.label | safe }}
</div>
+21 -12
View File
@@ -7,7 +7,11 @@
@param side {string} [optional] - Side of the sidebar to display.
@param header {string} [optional] - Header content for the sidebar.
@param footer {string} [optional] - Footer content for the sidebar.
@param menu {array} [optional] - Array of menu items for the sidebar.
@param items {array} [optional] - Array of menu items for the sidebar.
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
@param header_attrs {object} [optional] - Additional HTML attributes for the header element.
@param content_attrs {object} [optional] - Additional HTML attributes for the content section.
@param footer_attrs {object} [optional] - Additional HTML attributes for the footer element.
#}
{% macro sidebar(
id=None,
@@ -20,8 +24,7 @@
main_attrs={},
header_attrs={},
content_attrs={},
footer_attrs={},
content_wrapper_attrs=None
footer_attrs={}
) %}
<aside
{% if id %}id="{{ id }}"{% endif %}
@@ -86,9 +89,11 @@
<div
role="group"
{% if item.label %}aria-labelledby="{{ group_label_id }}"{% endif %}
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{% if item.label %}
<h3 id="{{ group_label_id }}">{{ item.label }}</h3>
@@ -104,9 +109,11 @@
<details
id="submenu-{{ item_id }}"
{{ "open" if item.open }}
{% for key, value in item.attrs %}
{% if key != "open" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs %}
{% if key != "open" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
{% endif %}
>
<summary aria-controls="submenu-{{ item_id }}-content">
{% if item.icon %}{{ item.icon | safe }}{% endif %}
@@ -122,9 +129,11 @@
<a
href="{{ item.url }}"
{{ 'aria-current="page"' if item.current }}
{% for key, value in item.attrs %}
{% if key != "href" and key != "aria-current" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
{% if item.attrs %}
{% for key, value in item.attrs %}
{% if key != "href" and key != "aria-current" %}{{ key }}="{{ value }}"{% endif %}
{% endfor %}
{% endif %}
>
{% if item.icon %}{{ item.icon | safe }}{% endif %}
<span>{{ item.label }}</span>
+12 -8
View File
@@ -16,8 +16,8 @@
{% macro tabs(
id=None,
tabsets=[],
main_attrs=None,
tablist_attrs=None,
main_attrs={},
tablist_attrs={},
default_tab_index=1
)
%}
@@ -44,9 +44,11 @@
aria-controls="{{ 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 %}
{% if tabset.tab_attrs %}
{% for key, value in tabset.tab_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{{ tabset.tab | safe }}
</button>
@@ -62,9 +64,11 @@
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 %}
{% if tabset.panel_attrs %}
{% for key, value in tabset.panel_attrs %}
{{ key }}="{{ value }}"
{% endfor %}
{% endif %}
>
{{ tabset.panel | safe }}
</div>
+5 -5
View File
@@ -10,7 +10,7 @@
{% macro toaster(
id="toaster",
toasts=[],
attrs=None
attrs={}
) %}
<div
id="{{ id }}"
@@ -63,13 +63,13 @@
<div class="toast-content">
{% if category in ["error", "success", "info", "warning"] %}
{% if category == "success" %}
{% lucide "circle-check" %}
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>
{% elif category == "error" %}
{% lucide "circle-x" %}
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>
{% elif category == "info" %}
{% lucide "info" %}
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
{% elif category == "warning" %}
{% lucide "triangle-alert" %}
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>
{% endif %}
{% endif %}
<section>