basecoat-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,110 @@
1
+ {#
2
+ Renders a dialog component with optional trigger, header, body, and footer.
3
+
4
+ @param id {string} - Unique identifier for the dialog component.
5
+ @param trigger {string} [optional] - Text or HTML for the button that triggers the dialog.
6
+ @param title {string} [optional] - Title text displayed in the dialog header.
7
+ @param description {string} [optional] - Description text displayed below the title.
8
+ @param footer {string} [optional] - HTML content for the dialog footer.
9
+ @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
10
+ @param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
11
+ @param content_attrs {object} [optional] - Additional HTML attributes for the dialog content article.
12
+ @param content_header_attrs {object} [optional] - Additional HTML attributes for the dialog header.
13
+ @param content_body_attrs {object} [optional] - Additional HTML attributes for the dialog body section.
14
+ @param content_footer_attrs {object} [optional] - Additional HTML attributes for the dialog footer.
15
+ @param open {boolean} [optional] [default=false] - Whether the dialog should be open initially.
16
+ @param close_button {boolean} [optional] [default=true] - Whether to include a close button.
17
+ @param close_on_overlay_click {boolean} [optional] [default=true] - Whether clicking the overlay closes the dialog.
18
+ #}
19
+ {% macro dialog(
20
+ id,
21
+ trigger=None,
22
+ title=None,
23
+ description=None,
24
+ footer=None,
25
+ main_attrs={},
26
+ trigger_attrs={},
27
+ content_attrs={},
28
+ content_header_attrs={},
29
+ content_body_attrs={},
30
+ content_footer_attrs={},
31
+ open=false,
32
+ close_button=true,
33
+ close_on_overlay_click=true
34
+ ) %}
35
+ <div
36
+ id="{{ id }}"
37
+ x-data="dialog({{ 'true' if open else 'false' }}, {{ 'true' if close_on_overlay_click else 'false' }})"
38
+ x-bind="$main"
39
+ class="dialog"
40
+ {% for key, value in main_attrs %}
41
+ {{ key }}="{{ value }}"
42
+ {% endfor %}
43
+ >
44
+ {% if trigger %}
45
+ <button
46
+ type="button"
47
+ aria-expanded="false"
48
+ aria-controls="{{ id }}-dialog"
49
+ x-bind="$trigger"
50
+ {% for key, value in trigger_attrs %}
51
+ {{ key }}="{{ value }}"
52
+ {% endfor %}
53
+ >
54
+ {{ trigger }}
55
+ </button>
56
+ {% endif %}
57
+ <div
58
+ role="dialog"
59
+ id="{{ id }}-dialog"
60
+ tabindex="-1"
61
+ aria-modal="true"
62
+ aria-labelledby="{{ id }}-title"
63
+ inert
64
+ x-bind="$content"
65
+ >
66
+ <article
67
+ {% for key, value in content_attrs %}
68
+ {{ key }}="{{ value }}"
69
+ {% endfor %}
70
+ >
71
+ {% if title or description %}
72
+ <header
73
+ {% for key, value in content_header_attrs %}
74
+ {{ key }}="{{ value }}"
75
+ {% endfor %}
76
+ >
77
+ <h2 id="{{ id }}-title">{{ title | safe }}</h2>
78
+ <p>{{ description | safe }}</p>
79
+ </header>
80
+ {% endif %}
81
+ {% if caller %}
82
+ <section
83
+ {% for key, value in content_body_attrs %}
84
+ {{ key }}="{{ value }}"
85
+ {% endfor %}
86
+ >
87
+ {{ caller() }}
88
+ </section>
89
+ {% endif %}
90
+ {% if footer %}
91
+ <footer
92
+ {% for key, value in content_footer_attrs %}
93
+ {{ key }}="{{ value }}"
94
+ {% endfor %}
95
+ >
96
+ {{ footer | safe }}
97
+ </footer>
98
+ {% endif %}
99
+ {% if close_button %}
100
+ <button
101
+ @click="hide()"
102
+ aria-label="Close dialog"
103
+ >
104
+ <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>
105
+ </button>
106
+ {% endif %}
107
+ </article>
108
+ </div>
109
+ </div>
110
+ {% endmacro %}
@@ -0,0 +1,113 @@
1
+ {#
2
+ Renders a dropdown menu component.
3
+
4
+ @param id {string} [optional] - Unique identifier for the dropdown component.
5
+ @param trigger {string} [optional] - HTML content for the button that triggers the dropdown.
6
+ @param menu {array} [optional] - Array of menu items for the dropdown.
7
+ @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
8
+ @param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
9
+ @param content_attrs {object} [optional] - Additional HTML attributes for the dropdown content div.
10
+ #}
11
+ {% macro dropdown_menu(
12
+ id=None,
13
+ trigger=None,
14
+ menu=None,
15
+ main_attrs={},
16
+ trigger_attrs={},
17
+ content_attrs={}
18
+ ) %}
19
+ <div
20
+ class="popover {{ main_attrs.class }}"
21
+ x-data="dropdownMenu"
22
+ @click.away="open = false"
23
+ {% if id %}id="{{ id }}"{% endif %}
24
+ {% for key, value in main_attrs %}
25
+ {% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
26
+ {% endfor %}
27
+ >
28
+ {% if trigger %}
29
+ <button
30
+ type="button"
31
+ aria-haspopup="menu"
32
+ aria-expanded="false"
33
+ x-bind="$trigger"
34
+ {% if id %}
35
+ id="{{ id }}-trigger"
36
+ aria-controls="{{ id }}-menu"
37
+ {% endif %}
38
+ {% for key, value in trigger_attrs %}
39
+ {{ key }}="{{ value }}"
40
+ {% endfor %}
41
+ >
42
+ {{ trigger | safe }}
43
+ </button>
44
+ {% endif %}
45
+ <div
46
+ data-popover
47
+ aria-hidden="true"
48
+ x-bind="$content"
49
+ {% if id %}id="{{ id }}-menu"{% endif %}
50
+ {% for key, value in content_attrs %}
51
+ {{ key }}="{{ value }}"
52
+ {% endfor %}
53
+ >
54
+ <nav role="menu">
55
+ {% if menu %}
56
+ {{ render_dropdown_items(menu, id ~ "-items" if id else "items") }}
57
+ {% else %}
58
+ {{ caller() if caller }}
59
+ {% endif %}
60
+ </nav>
61
+ </div>
62
+ </div>
63
+ {% endmacro %}
64
+
65
+ {#
66
+ Renders dropdown menu items recursively.
67
+
68
+ @param items {array} - The array of items to render.
69
+ @param parent_id_prefix {string} [optional] - Prefix for generating element IDs.
70
+ #}
71
+ {% macro render_dropdown_items(items, parent_id_prefix="items") %}
72
+ {% for item in items %}
73
+ {% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
74
+
75
+ {% if item.type == "group" %}
76
+ {% set group_label_id = item.id if item.id else "group-label-" + item_id %}
77
+ <div
78
+ role="group"
79
+ aria-labelledby="{{ group_label_id }}"
80
+ {% for key, value in item.attrs %}
81
+ {{ key }}="{{ value }}"
82
+ {% endfor %}
83
+ >
84
+ <div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
85
+ {{ render_dropdown_items(item.items, item_id) if item.items }}
86
+ </div>
87
+ {% elif item.type == "separator" %}
88
+ <hr role="separator" />
89
+ {% elif item.type == "item" or not item.type %}
90
+ {% if item.url %}
91
+ <a
92
+ role="menuitem"
93
+ href="{{ item.url }}"
94
+ {% for key, value in item.attrs %}
95
+ {% if key != "url" %} {{ key }}="{{ value }}" {% endif %}
96
+ {% endfor %}
97
+ >
98
+ {{ item.label | safe }}
99
+ </a>
100
+ {% else %}
101
+ <button
102
+ role="menuitem"
103
+ type="button"
104
+ {% for key, value in item.attrs %}
105
+ {{ key }}="{{ value }}"
106
+ {% endfor %}
107
+ >
108
+ {{ item.label | safe }}
109
+ </button>
110
+ {% endif %}
111
+ {% endif %}
112
+ {% endfor %}
113
+ {% endmacro %}
@@ -0,0 +1,55 @@
1
+ {#
2
+ Renders a generic popover component, often used internally by other components like dropdowns or selects.
3
+
4
+ @param id {string} [optional] - Unique identifier for the popover component.
5
+ @param trigger {string} [optional] - HTML content for the element that triggers the popover.
6
+ @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
7
+ @param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger element.
8
+ @param content_attrs {object} [optional] - Additional HTML attributes for the popover content div.
9
+ #}
10
+ {% macro popover(
11
+ id=None,
12
+ trigger=None,
13
+ main_attrs={},
14
+ trigger_attrs={},
15
+ content_attrs={}
16
+ ) %}
17
+ <div
18
+ class="popover {{ main_attrs.class }}"
19
+ x-data="popover"
20
+ @click.away="open = false"
21
+ {% if id %}id="{{ id }}"{% endif %}
22
+ {% for key, value in main_attrs %}
23
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
24
+ {% endfor %}
25
+ >
26
+ {% if trigger %}
27
+ <button
28
+ type="button"
29
+ aria-haspopup="menu"
30
+ aria-expanded="false"
31
+ x-bind="$trigger"
32
+ {% if id %}
33
+ id="{{ id }}-trigger"
34
+ aria-controls="{{ id }}-menu"
35
+ {% endif %}
36
+ {% for key, value in trigger_attrs %}
37
+ {{ key }}="{{ value }}"
38
+ {% endfor %}
39
+ >
40
+ {{ trigger | safe }}
41
+ </button>
42
+ {% endif %}
43
+ <div
44
+ data-popover
45
+ aria-hidden="true"
46
+ x-bind="$content"
47
+ {% if id %}id="{{ id }}-menu"{% endif %}
48
+ {% for key, value in content_attrs %}
49
+ {{ key }}="{{ value }}"
50
+ {% endfor %}
51
+ >
52
+ {{ caller() if caller }}
53
+ </div>
54
+ </div>
55
+ {% endmacro %}
@@ -0,0 +1,151 @@
1
+ {#
2
+ Renders a select or combobox component.
3
+
4
+ @param id {string} [optional] - Unique identifier for the select component.
5
+ @param selected {string} [optional] - The initially selected value.
6
+ @param name {string} [optional] - The name attribute for the hidden input storing the selected value.
7
+ @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
8
+ @param trigger_attrs {object} [optional] - Additional HTML attributes for the trigger button.
9
+ @param content_attrs {object} [optional] - Additional HTML attributes for the popover content div.
10
+ @param listbox_attrs {object} [optional] - Additional HTML attributes for the listbox div.
11
+ @param input_attrs {object} [optional] - Additional HTML attributes for the hidden input.
12
+ @param search_placeholder {string} [optional] [default="Search entries..."] - Placeholder text for the search input (combobox only).
13
+ @param is_combobox {boolean} [optional] [default=false] - Renders a combobox with search functionality if true.
14
+ #}
15
+ {% macro select(
16
+ id=None,
17
+ selected=None,
18
+ name=None,
19
+ items=None,
20
+ main_attrs={},
21
+ trigger_attrs={},
22
+ content_attrs={},
23
+ listbox_attrs={},
24
+ input_attrs={},
25
+ search_placeholder="Search entries...",
26
+ is_combobox=false
27
+ ) %}
28
+ <div
29
+ class="popover {{ main_attrs.class }}"
30
+ x-data="select('{{ name }}', '{{ selected or '' }}')"
31
+ @click.away="open = false"
32
+ {% if id %}id="{{ id }}"{% endif %}
33
+ {% for key, value in main_attrs %}
34
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
35
+ {% endfor %}
36
+ >
37
+ <button
38
+ type="button"
39
+ aria-haspopup="listbox"
40
+ aria-expanded="false"
41
+ x-bind="$trigger"
42
+ {% if id %}
43
+ id="{{ id }}-trigger"
44
+ aria-controls="{{ id }}-content"
45
+ {% endif %}
46
+ class="btn-outline justify-between font-normal {{ trigger_attrs.class }}"
47
+ {% for key, value in trigger_attrs %}
48
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
49
+ {% endfor %}
50
+ >
51
+ <div x-html="selectedLabel" class="flex items-center gap-x-2"
52
+ ></div>
53
+ {% if is_combobox %}
54
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevrons-up-down-icon lucide-chevrons-up-down text-muted-foreground opacity-50 shrink-0"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
55
+ {% else %}
56
+ <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-chevron-down-icon lucide-chevron-down text-muted-foreground opacity-50 shrink-0"><path d="m6 9 6 6 6-6"/></svg>
57
+ {% endif %}
58
+ </button>
59
+ <div
60
+ data-popover
61
+ aria-hidden="true"
62
+ x-bind="$content"
63
+ {% if id %}id="{{ id }}-content"{% endif %}
64
+ {% for key, value in content_attrs %}
65
+ {{ key }}="{{ value }}"
66
+ {% endfor %}
67
+ >
68
+ {% if is_combobox %}
69
+ <header>
70
+ <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-search-icon lucide-search"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
71
+ <input
72
+ type="text"
73
+ value=""
74
+ placeholder="{{ search_placeholder }}"
75
+ autocomplete="off"
76
+ autocorrect="off"
77
+ spellcheck="false"
78
+ aria-autocomplete="list"
79
+ role="combobox"
80
+ aria-expanded="true"
81
+ aria-controls="{{ id }}-content"
82
+ aria-labelledby="{{ id }}-trigger"
83
+ x-model="query"
84
+ x-bind="$filter"
85
+ >
86
+ </header>
87
+ {% endif %}
88
+ <div
89
+ role="listbox"
90
+ aria-orientation="vertical"
91
+ {% for key, value in listbox_attrs %}
92
+ {{ key }}="{{ value }}"
93
+ {% endfor %}
94
+ >
95
+ {% if items %}
96
+ {{ render_select_items(items, id ~ "-items" if id else "items") }}
97
+ {% else %}
98
+ {{ caller() if caller }}
99
+ {% endif %}
100
+ </div>
101
+ </div>
102
+ {% if name is defined %}
103
+ <input
104
+ type="hidden"
105
+ name="{{ name }}"
106
+ x-model="selectedValue"
107
+ {% for key, value in input_attrs %}
108
+ {% if key != 'name' %}{{ key }}="{{ value }}"{% endif %}
109
+ {% endfor %}
110
+ >
111
+ {% endif %}
112
+ </div>
113
+ {% endmacro %}
114
+
115
+ {#
116
+ Renders a list of items for the select component.
117
+
118
+ @param items {array} - The array of items to render.
119
+ @param parent_id_prefix {string} [optional] - The prefix for the item id.
120
+ #}
121
+ {% macro render_select_items(items, parent_id_prefix="items") %}
122
+ {% for item in items %}
123
+ {% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
124
+
125
+ {% if item.type == "group" %}
126
+ {% set group_label_id = item.id if item.id else "group-label-" + item_id %}
127
+ <div
128
+ role="group"
129
+ aria-labelledby="{{ group_label_id }}"
130
+ {% for key, value in item.attrs %}
131
+ {{ key }}="{{ value }}"
132
+ {% endfor %}
133
+ >
134
+ <div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
135
+ {{ render_select_items(item.items, item_id) if item.items }}
136
+ </div>
137
+ {% elif item.type == "separator" %}
138
+ <hr role="separator" />
139
+ {% elif item.type == "item" or not item.type %}
140
+ <div
141
+ role="option"
142
+ data-value="{{ item.value }}"
143
+ {% for key, value in item.attrs %}
144
+ {{ key }}="{{ value }}"
145
+ {% endfor %}
146
+ >
147
+ {{ item.label | safe }}
148
+ </div>
149
+ {% endif %}
150
+ {% endfor %}
151
+ {% endmacro %}
@@ -0,0 +1,138 @@
1
+ {#
2
+ Renders a sidebar component.
3
+
4
+ @param id {string} [optional] - Unique identifier for the sidebar component.
5
+ @param label {string} [optional] - Label for the sidebar navigation.
6
+ @param open {boolean} [optional] - Whether the sidebar is open.
7
+ @param side {string} [optional] - Side of the sidebar to display.
8
+ @param header {string} [optional] - Header content for the sidebar.
9
+ @param footer {string} [optional] - Footer content for the sidebar.
10
+ @param menu {array} [optional] - Array of menu items for the sidebar.
11
+ #}
12
+ {% macro sidebar(
13
+ id=None,
14
+ label="Sidebar navigation",
15
+ open=true,
16
+ side=None,
17
+ header=None,
18
+ footer=None,
19
+ menu=None,
20
+ main_attrs={},
21
+ header_attrs={},
22
+ content_attrs={},
23
+ footer_attrs={},
24
+ content_wrapper_attrs=None
25
+ ) %}
26
+ <div
27
+ {% if id %}id="{{ id }}"{% endif %}
28
+ class="sidebar {{ main_attrs.class }}"
29
+ data-uninitialized
30
+ data-side="{{ side if side else "left" }}"
31
+ aria-hidden="{{ "true" if not open else "false" }}"
32
+ {{ "inert" if not open }}
33
+ {% for key, value in main_attrs %}
34
+ {% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
35
+ {% endfor %}
36
+ x-data="sidebar({{ "true" if open else "false" }})"
37
+ x-bind="$main"
38
+ >
39
+ <nav
40
+ aria-label="{{ label }}"
41
+ >
42
+ {% if header %}
43
+ <header
44
+ {% for key, value in header_attrs %}
45
+ {{ key }}="{{ value }}"
46
+ {% endfor %}
47
+ >
48
+ {{ header | safe }}
49
+ </header>
50
+ {% endif %}
51
+
52
+ <section
53
+ {% for key, value in content_attrs %}
54
+ {{ key }}="{{ value }}"
55
+ {% endfor %}
56
+ >
57
+ {% if menu %}
58
+ {{ render_sidebar_content(menu, id ~ "-content" if id else "content") }}
59
+ {% else %}
60
+ {{ caller() if caller }}
61
+ {% endif %}
62
+ </section>
63
+
64
+ {% if footer %}
65
+ <footer
66
+ {% for key, value in footer_attrs %}
67
+ {{ key }}="{{ value }}"
68
+ {% endfor %}
69
+ >
70
+ {{ footer | safe }}
71
+ </footer>
72
+ {% endif %}
73
+ </nav>
74
+ </div>
75
+ {% endmacro %}
76
+
77
+ {#
78
+ Renders sidebar content recursively (groups, items, submenus, separators).
79
+
80
+ @param items {array} - The array of items to render.
81
+ @param parent_id_prefix {string} [optional] - Prefix for generating element IDs.
82
+ #}
83
+ {% macro render_sidebar_content(items, parent_id_prefix="content") %}
84
+ {% for item in items %}
85
+ {% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
86
+
87
+ {% if item.type == "group" %}
88
+ {% set group_label_id = item.id if item.id else "group-label-" + item_id %}
89
+ <div
90
+ role="group"
91
+ {% if item.label %}aria-labelledby="{{ group_label_id }}"{% endif %}
92
+ {% for key, value in item.attrs %}
93
+ {{ key }}="{{ value }}"
94
+ {% endfor %}
95
+ >
96
+ {% if item.label %}
97
+ <h3 id="{{ group_label_id }}">{{ item.label }}</h3>
98
+ {% endif %}
99
+ <ul>
100
+ {{ render_sidebar_content(item.items, item_id) if item.items }}
101
+ </ul>
102
+ </div>
103
+ {% elif item.type == "separator" %}
104
+ <hr role="separator"/>
105
+ {% elif item.type == "submenu" %}
106
+ <li>
107
+ <details
108
+ id="submenu-{{ item_id }}"
109
+ {{ "open" if item.open }}
110
+ {% for key, value in item.attrs %}
111
+ {% if key != "open" %}{{ key }}="{{ value }}"{% endif %}
112
+ {% endfor %}
113
+ >
114
+ <summary aria-controls="submenu-{{ item_id }}-content">
115
+ {% if item.icon %}{{ item.icon | safe }}{% endif %}
116
+ {{ item.label }}
117
+ </summary>
118
+ <ul id="submenu-{{ item_id }}-content">
119
+ {{ render_sidebar_content(item.items, item_id) if item.items }}
120
+ </ul>
121
+ </details>
122
+ </li>
123
+ {% elif item.type == "item" or not item.type %}
124
+ <li>
125
+ <a
126
+ href="{{ item.url }}"
127
+ {{ 'aria-current="page"' if item.current }}
128
+ {% for key, value in item.attrs %}
129
+ {% if key != "href" and key != "aria-current" %}{{ key }}="{{ value }}"{% endif %}
130
+ {% endfor %}
131
+ >
132
+ {% if item.icon %}{{ item.icon | safe }}{% endif %}
133
+ <span>{{ item.label }}</span>
134
+ </a>
135
+ </li>
136
+ {% endif %}
137
+ {% endfor %}
138
+ {% endmacro %}
@@ -0,0 +1,75 @@
1
+ {#
2
+ Renders a tabs component.
3
+
4
+ @param id {string} - Unique identifier for the tabs component.
5
+ @param tabsets {array} - An array of objects, each representing a tab and its panel.
6
+ Each object should have:
7
+ - id {string}: Unique identifier prefix for the tab and panel.
8
+ - tab {string}: HTML content for the tab button.
9
+ - panel {string} [optional]: HTML content for the tab panel.
10
+ - tab_attrs {object} [optional]: Additional HTML attributes for the tab button.
11
+ - panel_attrs {object} [optional]: Additional HTML attributes for the tab panel div.
12
+ @param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
13
+ @param tablist_attrs {object} [optional] - Additional HTML attributes for the tablist nav element.
14
+ @param default_tab_index {number} [optional] [default=1] - The 1-based index of the tab to be active initially.
15
+ #}
16
+ {% macro tabs(
17
+ id,
18
+ tabsets=[],
19
+ main_attrs=None,
20
+ tablist_attrs=None,
21
+ default_tab_index=1
22
+ )
23
+ %}
24
+ <div
25
+ class="tabs {{ main_attrs.class }}"
26
+ x-data="tabs({{ default_tab_index - 1 }})"
27
+ {% if id %}id="{{ id }}"{% endif %}
28
+ {% for key, value in main_attrs %}
29
+ {% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
30
+ {% endfor %}
31
+ >
32
+ <nav
33
+ role="tablist"
34
+ aria-orientation="horizontal"
35
+ x-bind="$tablist"
36
+ {% for key, value in tablist_attrs %}
37
+ {{ key }}="{{ value }}"
38
+ {% endfor %}
39
+ >
40
+ {% for tabset in tabsets %}
41
+ <button
42
+ type="button"
43
+ role="tab"
44
+ id="{{ tabset.id }}-tab-{{ loop.index }}"
45
+ aria-controls="{{ tabset.id }}-panel-{{ loop.index }}"
46
+ aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
47
+ tabindex="0"
48
+ {% for key, value in tabset.tab_attrs %}
49
+ {{ key }}="{{ value }}"
50
+ {% endfor %}
51
+ >
52
+ {{ tabset.tab | safe }}
53
+ </button>
54
+ {% endfor %}
55
+ </nav>
56
+
57
+ {% for tabset in tabsets %}
58
+ {% if tabset.panel %}
59
+ <div
60
+ role="tabpanel"
61
+ id="{{ tabset.id }}-panel-{{ loop.index }}"
62
+ aria-labelledby="{{ tabset.id }}-tab-{{ loop.index }}"
63
+ tabindex="-1"
64
+ aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
65
+ {% if loop.index != default_tab_index %}hidden{% endif %}
66
+ {% for key, value in tabset.panel_attrs %}
67
+ {{ key }}="{{ value }}"
68
+ {% endfor %}
69
+ >
70
+ {{ tabset.panel | safe }}
71
+ </div>
72
+ {% endif %}
73
+ {% endfor %}
74
+ </div>
75
+ {% endmacro %}