@pokle/basecoat 0.3.10-beta2.pokle
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.
- package/.eleventy.js +47 -0
- package/.gitattributes +1 -0
- package/.gitignore +12 -0
- package/AGENTS.md +112 -0
- package/CONTRIBUTING.md +15 -0
- package/LICENSE.md +21 -0
- package/README.md +29 -0
- package/ROADMAP.md +20 -0
- package/docs/css/custom.css +92 -0
- package/docs/css/highlight.css +151 -0
- package/docs/css/styles.css +8 -0
- package/docs/css/themes/claude.css +95 -0
- package/docs/css/themes/doom-64.css +95 -0
- package/docs/css/themes/supabase.css +100 -0
- package/docs/src/_data/site.js +10 -0
- package/docs/src/_includes/layouts/base.njk +112 -0
- package/docs/src/_includes/layouts/layout.njk +15 -0
- package/docs/src/_includes/layouts/page.njk +21 -0
- package/docs/src/_includes/macros/code_block.njk +23 -0
- package/docs/src/_includes/macros/code_preview.njk +26 -0
- package/docs/src/_includes/macros/toc.njk +20 -0
- package/docs/src/_includes/partials/header.njk +64 -0
- package/docs/src/_includes/partials/kitchen-sink/accordion.njk +89 -0
- package/docs/src/_includes/partials/kitchen-sink/alert-dialog.njk +26 -0
- package/docs/src/_includes/partials/kitchen-sink/alert.njk +72 -0
- package/docs/src/_includes/partials/kitchen-sink/avatar.njk +37 -0
- package/docs/src/_includes/partials/kitchen-sink/badge.njk +47 -0
- package/docs/src/_includes/partials/kitchen-sink/breadcrumb.njk +42 -0
- package/docs/src/_includes/partials/kitchen-sink/button.njk +101 -0
- package/docs/src/_includes/partials/kitchen-sink/card.njk +147 -0
- package/docs/src/_includes/partials/kitchen-sink/checkbox.njk +34 -0
- package/docs/src/_includes/partials/kitchen-sink/combobox.njk +83 -0
- package/docs/src/_includes/partials/kitchen-sink/dialog.njk +65 -0
- package/docs/src/_includes/partials/kitchen-sink/dropdown-menu.njk +294 -0
- package/docs/src/_includes/partials/kitchen-sink/form.njk +106 -0
- package/docs/src/_includes/partials/kitchen-sink/input.njk +27 -0
- package/docs/src/_includes/partials/kitchen-sink/label.njk +30 -0
- package/docs/src/_includes/partials/kitchen-sink/pagination.njk +34 -0
- package/docs/src/_includes/partials/kitchen-sink/popover.njk +43 -0
- package/docs/src/_includes/partials/kitchen-sink/radio-group.njk +33 -0
- package/docs/src/_includes/partials/kitchen-sink/select.njk +99 -0
- package/docs/src/_includes/partials/kitchen-sink/skeleton.njk +40 -0
- package/docs/src/_includes/partials/kitchen-sink/slider.njk +38 -0
- package/docs/src/_includes/partials/kitchen-sink/switch.njk +27 -0
- package/docs/src/_includes/partials/kitchen-sink/table.njk +74 -0
- package/docs/src/_includes/partials/kitchen-sink/tabs.njk +105 -0
- package/docs/src/_includes/partials/kitchen-sink/textarea.njk +27 -0
- package/docs/src/_includes/partials/kitchen-sink/toast.njk +25 -0
- package/docs/src/_includes/partials/kitchen-sink/tooltip.njk +24 -0
- package/docs/src/_includes/partials/sidebar.njk +139 -0
- package/docs/src/assets/apple-touch-icon.png +0 -0
- package/docs/src/assets/favicon.svg +12 -0
- package/docs/src/assets/images/avatar-1.png +0 -0
- package/docs/src/assets/images/avatar-2.png +0 -0
- package/docs/src/assets/images/avatar-3.png +0 -0
- package/docs/src/assets/images/screenshot.png +0 -0
- package/docs/src/assets/social-screenshot.png +0 -0
- package/docs/src/assets/social.png +0 -0
- package/docs/src/assets/styles.css +6309 -0
- package/docs/src/components/accordion.njk +75 -0
- package/docs/src/components/alert-dialog.njk +119 -0
- package/docs/src/components/alert.njk +108 -0
- package/docs/src/components/avatar.njk +40 -0
- package/docs/src/components/badge.njk +93 -0
- package/docs/src/components/breadcrumb.njk +71 -0
- package/docs/src/components/button-group.njk +290 -0
- package/docs/src/components/button.njk +141 -0
- package/docs/src/components/card.njk +156 -0
- package/docs/src/components/carousel.njk +102 -0
- package/docs/src/components/chart.njk +814 -0
- package/docs/src/components/checkbox.njk +101 -0
- package/docs/src/components/combobox.njk +293 -0
- package/docs/src/components/command.njk +288 -0
- package/docs/src/components/dialog.njk +177 -0
- package/docs/src/components/dropdown-menu.njk +403 -0
- package/docs/src/components/empty.njk +157 -0
- package/docs/src/components/field.njk +459 -0
- package/docs/src/components/form.njk +79 -0
- package/docs/src/components/input-group.njk +372 -0
- package/docs/src/components/input.njk +90 -0
- package/docs/src/components/item.njk +320 -0
- package/docs/src/components/kbd.njk +76 -0
- package/docs/src/components/label.njk +41 -0
- package/docs/src/components/pagination.njk +48 -0
- package/docs/src/components/popover.njk +174 -0
- package/docs/src/components/progress.njk +44 -0
- package/docs/src/components/radio-group.njk +48 -0
- package/docs/src/components/select.njk +457 -0
- package/docs/src/components/sidebar.njk +219 -0
- package/docs/src/components/skeleton.njk +51 -0
- package/docs/src/components/slider.njk +47 -0
- package/docs/src/components/spinner.njk +214 -0
- package/docs/src/components/switch.njk +54 -0
- package/docs/src/components/table.njk +87 -0
- package/docs/src/components/tabs.njk +232 -0
- package/docs/src/components/textarea.njk +90 -0
- package/docs/src/components/theme-switcher.njk +111 -0
- package/docs/src/components/toast.njk +279 -0
- package/docs/src/components/tooltip.njk +53 -0
- package/docs/src/fragments/toast/error.njk +6 -0
- package/docs/src/fragments/toast/info.njk +5 -0
- package/docs/src/fragments/toast/success.njk +7 -0
- package/docs/src/fragments/toast/warning.njk +5 -0
- package/docs/src/index.njk +336 -0
- package/docs/src/installation.njk +275 -0
- package/docs/src/introduction.njk +63 -0
- package/docs/src/kitchen-sink.njk +52 -0
- package/docs/src/llms.txt +506 -0
- package/docs/src/robots.njk +7 -0
- package/docs/src/sitemap.njk +15 -0
- package/docs/src/test.njk +39 -0
- package/package.json +51 -0
- package/packages/cli/README.md +55 -0
- package/packages/cli/index.js +193 -0
- package/packages/cli/package.json +44 -0
- package/packages/css/README.md +63 -0
- package/packages/css/package.json +63 -0
- package/scripts/build.js +170 -0
- package/src/css/basecoat.cdn.css +2 -0
- package/src/css/basecoat.css +1310 -0
- package/src/jinja/command.html.jinja +206 -0
- package/src/jinja/dialog.html.jinja +94 -0
- package/src/jinja/dropdown-menu.html.jinja +124 -0
- package/src/jinja/popover.html.jinja +48 -0
- package/src/jinja/select.html.jinja +196 -0
- package/src/jinja/sidebar.html.jinja +144 -0
- package/src/jinja/tabs.html.jinja +78 -0
- package/src/jinja/toast.html.jinja +117 -0
- package/src/js/basecoat.js +99 -0
- package/src/js/command.js +175 -0
- package/src/js/dropdown-menu.js +171 -0
- package/src/js/popover.js +73 -0
- package/src/js/select.js +432 -0
- package/src/js/sidebar.js +104 -0
- package/src/js/tabs.js +63 -0
- package/src/js/toast.js +181 -0
- package/src/nunjucks/command.njk +206 -0
- package/src/nunjucks/dialog.njk +92 -0
- package/src/nunjucks/dropdown-menu.njk +124 -0
- package/src/nunjucks/popover.njk +48 -0
- package/src/nunjucks/select.njk +196 -0
- package/src/nunjucks/sidebar.njk +144 -0
- package/src/nunjucks/tabs.njk +78 -0
- package/src/nunjucks/toast.njk +117 -0
- package/wrangler.jsonc +7 -0
|
@@ -0,0 +1,124 @@
|
|
|
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 items {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 popover_attrs {object} [optional] - Additional HTML attributes for the dropdown content div.
|
|
10
|
+
#}
|
|
11
|
+
{% macro dropdown_menu(
|
|
12
|
+
trigger,
|
|
13
|
+
id=None,
|
|
14
|
+
items=None,
|
|
15
|
+
main_attrs={},
|
|
16
|
+
trigger_attrs={},
|
|
17
|
+
popover_attrs={},
|
|
18
|
+
menu_attrs={}
|
|
19
|
+
) %}
|
|
20
|
+
{% set id = id or ("dropdown-menu-" + (range(100000, 999999) | random | string)) %}
|
|
21
|
+
|
|
22
|
+
<div
|
|
23
|
+
id="{{ id }}"
|
|
24
|
+
class="dropdown-menu {{ main_attrs.class }}"
|
|
25
|
+
{% for key, value in main_attrs %}
|
|
26
|
+
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
|
27
|
+
{% endfor %}
|
|
28
|
+
>
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
id="{{ id }}-trigger"
|
|
32
|
+
aria-haspopup="menu"
|
|
33
|
+
aria-controls="{{ id }}-menu"
|
|
34
|
+
aria-expanded="false"
|
|
35
|
+
{% for key, value in trigger_attrs %}
|
|
36
|
+
{{ key }}="{{ value }}"
|
|
37
|
+
{% endfor %}
|
|
38
|
+
>
|
|
39
|
+
{{ trigger | safe }}
|
|
40
|
+
</button>
|
|
41
|
+
<div
|
|
42
|
+
id="{{ id }}-popover"
|
|
43
|
+
data-popover
|
|
44
|
+
aria-hidden="true"
|
|
45
|
+
{% for key, value in popover_attrs %}
|
|
46
|
+
{{ key }}="{{ value }}"
|
|
47
|
+
{% endfor %}
|
|
48
|
+
>
|
|
49
|
+
<div
|
|
50
|
+
role="menu"
|
|
51
|
+
id="{{ id }}-menu"
|
|
52
|
+
aria-labelledby="{{ id }}-trigger"
|
|
53
|
+
{% for key, value in menu_attrs %}
|
|
54
|
+
{{ key }}="{{ value }}"
|
|
55
|
+
{% endfor %}
|
|
56
|
+
>
|
|
57
|
+
{% if items %}
|
|
58
|
+
{{ render_dropdown_items(items, id ~ "-items" if id else "items") }}
|
|
59
|
+
{% else %}
|
|
60
|
+
{{ caller() if caller }}
|
|
61
|
+
{% endif %}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
{% endmacro %}
|
|
66
|
+
|
|
67
|
+
{#
|
|
68
|
+
Renders dropdown menu items recursively.
|
|
69
|
+
|
|
70
|
+
@param items {array} - The array of items to render.
|
|
71
|
+
@param parent_id_prefix {string} [optional] - Prefix for generating element IDs.
|
|
72
|
+
#}
|
|
73
|
+
{% macro render_dropdown_items(items, parent_id_prefix="items") %}
|
|
74
|
+
{% for item in items %}
|
|
75
|
+
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
|
|
76
|
+
|
|
77
|
+
{% if item.type == "group" %}
|
|
78
|
+
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
|
|
79
|
+
<div
|
|
80
|
+
role="group"
|
|
81
|
+
aria-labelledby="{{ group_label_id }}"
|
|
82
|
+
{% if item.attrs %}
|
|
83
|
+
{% for key, value in item.attrs %}
|
|
84
|
+
{{ key }}="{{ value }}"
|
|
85
|
+
{% endfor %}
|
|
86
|
+
{% endif %}
|
|
87
|
+
>
|
|
88
|
+
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
|
|
89
|
+
{{ render_dropdown_items(item.items, item_id) if item.items }}
|
|
90
|
+
</div>
|
|
91
|
+
{% elif item.type == "separator" %}
|
|
92
|
+
<hr role="separator" />
|
|
93
|
+
{% elif item.type == "item" or not item.type %}
|
|
94
|
+
{% if item.url %}
|
|
95
|
+
<a
|
|
96
|
+
id="{{ item_id }}"
|
|
97
|
+
role="menuitem"
|
|
98
|
+
href="{{ item.url }}"
|
|
99
|
+
{% if item.attrs %}
|
|
100
|
+
{% for key, value in item.attrs %}
|
|
101
|
+
{% if key != "url" %} {{ key }}="{{ value }}" {% endif %}
|
|
102
|
+
{% endfor %}
|
|
103
|
+
{% endif %}
|
|
104
|
+
>
|
|
105
|
+
{% if item.icon %}{{ item.icon | safe }}{% endif %}
|
|
106
|
+
{{ item.label | safe }}
|
|
107
|
+
</a>
|
|
108
|
+
{% else %}
|
|
109
|
+
<div
|
|
110
|
+
id="{{ item_id }}"
|
|
111
|
+
role="menuitem"
|
|
112
|
+
{% if item.attrs %}
|
|
113
|
+
{% for key, value in item.attrs %}
|
|
114
|
+
{{ key }}="{{ value }}"
|
|
115
|
+
{% endfor %}
|
|
116
|
+
{% endif %}
|
|
117
|
+
>
|
|
118
|
+
{% if item.icon %}{{ item.icon | safe }}{% endif %}
|
|
119
|
+
{{ item.label | safe }}
|
|
120
|
+
</div>
|
|
121
|
+
{% endif %}
|
|
122
|
+
{% endif %}
|
|
123
|
+
{% endfor %}
|
|
124
|
+
{% endmacro %}
|
|
@@ -0,0 +1,48 @@
|
|
|
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 popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
|
|
9
|
+
#}
|
|
10
|
+
{% macro popover(
|
|
11
|
+
trigger,
|
|
12
|
+
id=None,
|
|
13
|
+
main_attrs={},
|
|
14
|
+
trigger_attrs={},
|
|
15
|
+
popover_attrs={}
|
|
16
|
+
) %}
|
|
17
|
+
{% set id = id or ("popover-" + (range(100000, 999999) | random | string)) %}
|
|
18
|
+
|
|
19
|
+
<div
|
|
20
|
+
id="{{ id }}"
|
|
21
|
+
class="popover {{ main_attrs.class }}"
|
|
22
|
+
{% for key, value in main_attrs %}
|
|
23
|
+
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
|
24
|
+
{% endfor %}
|
|
25
|
+
>
|
|
26
|
+
<button
|
|
27
|
+
id="{{ id }}-trigger"
|
|
28
|
+
type="button"
|
|
29
|
+
aria-expanded="false"
|
|
30
|
+
aria-controls="{{ id }}-popover"
|
|
31
|
+
{% for key, value in trigger_attrs %}
|
|
32
|
+
{{ key }}="{{ value }}"
|
|
33
|
+
{% endfor %}
|
|
34
|
+
>
|
|
35
|
+
{{ trigger | safe }}
|
|
36
|
+
</button>
|
|
37
|
+
<div
|
|
38
|
+
id="{{ id }}-popover"
|
|
39
|
+
data-popover
|
|
40
|
+
aria-hidden="true"
|
|
41
|
+
{% for key, value in popover_attrs %}
|
|
42
|
+
{{ key }}="{{ value }}"
|
|
43
|
+
{% endfor %}
|
|
44
|
+
>
|
|
45
|
+
{{ caller() if caller }}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
{% endmacro %}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Renders a select or combobox component.
|
|
3
|
+
|
|
4
|
+
@param id {string} [optional] - Unique identifier for the select component.
|
|
5
|
+
@param selected {string|array} [optional] - The initially selected value(s).
|
|
6
|
+
@param name {string} [optional] - The name attribute for the hidden input storing the selected value.
|
|
7
|
+
@param multiple {boolean} [optional] [default=false] - Enables multiple selection mode.
|
|
8
|
+
@param placeholder {string} [optional] - Placeholder text shown when no options are selected (multiple mode only).
|
|
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 popover_attrs {object} [optional] - Additional HTML attributes for the popover content div.
|
|
12
|
+
@param listbox_attrs {object} [optional] - Additional HTML attributes for the listbox div.
|
|
13
|
+
@param input_attrs {object} [optional] - Additional HTML attributes for the hidden input.
|
|
14
|
+
@param search_placeholder {string} [optional] [default="Search entries..."] - Placeholder text for the search input (combobox only).
|
|
15
|
+
@param is_combobox {boolean} [optional] [default=false] - Renders a combobox with search functionality if true.
|
|
16
|
+
#}
|
|
17
|
+
{% macro select(
|
|
18
|
+
id=None,
|
|
19
|
+
selected=None,
|
|
20
|
+
name=None,
|
|
21
|
+
items=None,
|
|
22
|
+
multiple=false,
|
|
23
|
+
placeholder=None,
|
|
24
|
+
main_attrs={},
|
|
25
|
+
trigger_attrs={},
|
|
26
|
+
popover_attrs={},
|
|
27
|
+
listbox_attrs={},
|
|
28
|
+
input_attrs={},
|
|
29
|
+
search_placeholder="Search entries...",
|
|
30
|
+
is_combobox=false
|
|
31
|
+
) %}
|
|
32
|
+
{% set id = id or ("select-" + (range(100000, 999999) | random | string)) %}
|
|
33
|
+
|
|
34
|
+
{% if selected is defined and selected is iterable and selected is not string %}
|
|
35
|
+
{% set selectedArray = selected %}
|
|
36
|
+
{% elif selected is defined %}
|
|
37
|
+
{% set selectedArray = [selected] %}
|
|
38
|
+
{% else %}
|
|
39
|
+
{% set selectedArray = [] %}
|
|
40
|
+
{% endif %}
|
|
41
|
+
{% set first_option = [] %}
|
|
42
|
+
{% set selected_options = [] %}
|
|
43
|
+
|
|
44
|
+
{% if items %}
|
|
45
|
+
{% for item in items %}
|
|
46
|
+
{% if item.type == "group" %}
|
|
47
|
+
{% for sub_item in item.items %}
|
|
48
|
+
{% if not first_option[0] %}
|
|
49
|
+
{% set first_option = (first_option.push(sub_item), first_option) %}
|
|
50
|
+
{% endif %}
|
|
51
|
+
{% if sub_item.value in selectedArray %}
|
|
52
|
+
{% set selected_options = (selected_options.push(sub_item), selected_options) %}
|
|
53
|
+
{% endif %}
|
|
54
|
+
{% endfor %}
|
|
55
|
+
{% else %}
|
|
56
|
+
{% if not first_option[0] %}
|
|
57
|
+
{% set first_option = (first_option.push(item), first_option) %}
|
|
58
|
+
{% endif %}
|
|
59
|
+
{% if item.value in selectedArray %}
|
|
60
|
+
{% set selected_options = (selected_options.push(item), selected_options) %}
|
|
61
|
+
{% endif %}
|
|
62
|
+
{% endif %}
|
|
63
|
+
{% endfor %}
|
|
64
|
+
{% endif %}
|
|
65
|
+
|
|
66
|
+
{% set default_option = (selected_options[0] if selected_options[0] else (first_option[0] if first_option[0] else None)) %}
|
|
67
|
+
{% set labels = [] %}
|
|
68
|
+
{% if multiple and selected_options %}
|
|
69
|
+
{% for opt in selected_options %}
|
|
70
|
+
{% set labels = (labels.push(opt.label), labels) %}
|
|
71
|
+
{% endfor %}
|
|
72
|
+
{% endif %}
|
|
73
|
+
{% set default_label = (labels | join(', ') if (multiple and selected_options) else ((placeholder or '') if (multiple and not selected_options) else (default_option.label if default_option else ''))) %}
|
|
74
|
+
|
|
75
|
+
<div
|
|
76
|
+
id="{{ id }}"
|
|
77
|
+
class="select {{ main_attrs.class }}"
|
|
78
|
+
{% if multiple and placeholder %}data-placeholder="{{ placeholder }}"{% endif %}
|
|
79
|
+
{% for key, value in main_attrs %}
|
|
80
|
+
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
|
81
|
+
{% endfor %}
|
|
82
|
+
>
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
class="btn-outline {{ trigger_attrs.class }}"
|
|
86
|
+
id="{{ id }}-trigger"
|
|
87
|
+
aria-haspopup="listbox"
|
|
88
|
+
aria-expanded="false"
|
|
89
|
+
aria-controls="{{ id }}-listbox"
|
|
90
|
+
{% for key, value in trigger_attrs %}
|
|
91
|
+
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
|
92
|
+
{% endfor %}
|
|
93
|
+
>
|
|
94
|
+
<span class="truncate">{{ default_label }}</span>
|
|
95
|
+
{% if is_combobox %}
|
|
96
|
+
<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>
|
|
97
|
+
{% else %}
|
|
98
|
+
<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>
|
|
99
|
+
{% endif %}
|
|
100
|
+
</button>
|
|
101
|
+
<div
|
|
102
|
+
id="{{ id }}-popover"
|
|
103
|
+
data-popover
|
|
104
|
+
aria-hidden="true"
|
|
105
|
+
{% for key, value in popover_attrs %}
|
|
106
|
+
{{ key }}="{{ value }}"
|
|
107
|
+
{% endfor %}
|
|
108
|
+
>
|
|
109
|
+
{% if is_combobox %}
|
|
110
|
+
<header>
|
|
111
|
+
<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>
|
|
112
|
+
<input
|
|
113
|
+
type="text"
|
|
114
|
+
value=""
|
|
115
|
+
placeholder="{{ search_placeholder }}"
|
|
116
|
+
autocomplete="off"
|
|
117
|
+
autocorrect="off"
|
|
118
|
+
spellcheck="false"
|
|
119
|
+
aria-autocomplete="list"
|
|
120
|
+
role="combobox"
|
|
121
|
+
aria-expanded="false"
|
|
122
|
+
aria-controls="{{ id }}-listbox"
|
|
123
|
+
aria-labelledby="{{ id }}-trigger"
|
|
124
|
+
>
|
|
125
|
+
</header>
|
|
126
|
+
{% endif %}
|
|
127
|
+
<div
|
|
128
|
+
role="listbox"
|
|
129
|
+
id="{{ id }}-listbox"
|
|
130
|
+
aria-orientation="vertical"
|
|
131
|
+
aria-labelledby="{{ id }}-trigger"
|
|
132
|
+
{% if multiple %}aria-multiselectable="true"{% endif %}
|
|
133
|
+
{% for key, value in listbox_attrs %}
|
|
134
|
+
{{ key }}="{{ value }}"
|
|
135
|
+
{% endfor %}
|
|
136
|
+
>
|
|
137
|
+
{% if items and items.length > 0 %}
|
|
138
|
+
{{ render_select_items(items, selectedArray, id ~ "-items" if id else "items") }}
|
|
139
|
+
{% else %}
|
|
140
|
+
{{ caller() if caller }}
|
|
141
|
+
{% endif %}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
<input
|
|
145
|
+
type="hidden"
|
|
146
|
+
name="{{ name or id ~ '-value' }}"
|
|
147
|
+
value="{% if multiple %}{{ selectedArray | dump }}{% else %}{{ (default_option.value if default_option) or '' }}{% endif %}"
|
|
148
|
+
{% for key, value in input_attrs %}
|
|
149
|
+
{% if key != 'name' and key != 'value' %}{{ key }}="{{ value }}"{% endif %}
|
|
150
|
+
{% endfor %}
|
|
151
|
+
>
|
|
152
|
+
</div>
|
|
153
|
+
{% endmacro %}
|
|
154
|
+
|
|
155
|
+
{#
|
|
156
|
+
Renders a list of items for the select component.
|
|
157
|
+
|
|
158
|
+
@param items {array} - The array of items to render.
|
|
159
|
+
@param parent_id_prefix {string} [optional] - The prefix for the item id.
|
|
160
|
+
#}
|
|
161
|
+
{% macro render_select_items(items, selectedArray, parent_id_prefix="items") %}
|
|
162
|
+
{% for item in items %}
|
|
163
|
+
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
|
|
164
|
+
{% if item.type == "group" %}
|
|
165
|
+
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
|
|
166
|
+
<div
|
|
167
|
+
role="group"
|
|
168
|
+
aria-labelledby="{{ group_label_id }}"
|
|
169
|
+
{% if item.attrs %}
|
|
170
|
+
{% for key, value in item.attrs %}
|
|
171
|
+
{{ key }}="{{ value }}"
|
|
172
|
+
{% endfor %}
|
|
173
|
+
{% endif %}
|
|
174
|
+
>
|
|
175
|
+
<div role="heading" id="{{ group_label_id }}">{{ item.label }}</div>
|
|
176
|
+
{{ render_select_items(item.items, selectedArray, item_id) if item.items }}
|
|
177
|
+
</div>
|
|
178
|
+
{% elif item.type == "separator" %}
|
|
179
|
+
<hr role="separator" />
|
|
180
|
+
{% elif item.type == "item" or not item.type %}
|
|
181
|
+
<div
|
|
182
|
+
id="{{ item_id }}"
|
|
183
|
+
role="option"
|
|
184
|
+
{% if item.value is defined and item.value is not none %}data-value="{{ item.value }}"{% endif %}
|
|
185
|
+
{% if item.value in selectedArray %}aria-selected="true"{% endif %}
|
|
186
|
+
{% if item.attrs %}
|
|
187
|
+
{% for key, value in item.attrs %}
|
|
188
|
+
{{ key }}="{{ value }}"
|
|
189
|
+
{% endfor %}
|
|
190
|
+
{% endif %}
|
|
191
|
+
>
|
|
192
|
+
{{ item.label | safe }}
|
|
193
|
+
</div>
|
|
194
|
+
{% endif %}
|
|
195
|
+
{% endfor %}
|
|
196
|
+
{% endmacro %}
|
|
@@ -0,0 +1,144 @@
|
|
|
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 items {array} [optional] - Array of menu items for the sidebar.
|
|
11
|
+
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
|
|
12
|
+
@param header_attrs {object} [optional] - Additional HTML attributes for the header element.
|
|
13
|
+
@param content_attrs {object} [optional] - Additional HTML attributes for the content section.
|
|
14
|
+
@param footer_attrs {object} [optional] - Additional HTML attributes for the footer element.
|
|
15
|
+
#}
|
|
16
|
+
{% macro sidebar(
|
|
17
|
+
id=None,
|
|
18
|
+
label="Sidebar navigation",
|
|
19
|
+
open=true,
|
|
20
|
+
side=None,
|
|
21
|
+
header=None,
|
|
22
|
+
footer=None,
|
|
23
|
+
menu=None,
|
|
24
|
+
main_attrs={},
|
|
25
|
+
header_attrs={},
|
|
26
|
+
content_attrs={},
|
|
27
|
+
footer_attrs={}
|
|
28
|
+
) %}
|
|
29
|
+
<aside
|
|
30
|
+
{% if id %}id="{{ id }}"{% endif %}
|
|
31
|
+
class="sidebar {{ main_attrs.class }}"
|
|
32
|
+
data-side="{{ side if side else "left" }}"
|
|
33
|
+
aria-hidden="{{ "true" if not open else "false" }}"
|
|
34
|
+
{{ "inert" if not open }}
|
|
35
|
+
{% for key, value in main_attrs %}
|
|
36
|
+
{% if key != "class" %}{{ key }}="{{ value }}"{% endif %}
|
|
37
|
+
{% endfor %}
|
|
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
|
+
</aside>
|
|
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
|
+
{% if item.attrs %}
|
|
93
|
+
{% for key, value in item.attrs %}
|
|
94
|
+
{{ key }}="{{ value }}"
|
|
95
|
+
{% endfor %}
|
|
96
|
+
{% endif %}
|
|
97
|
+
>
|
|
98
|
+
{% if item.label %}
|
|
99
|
+
<h3 id="{{ group_label_id }}">{{ item.label }}</h3>
|
|
100
|
+
{% endif %}
|
|
101
|
+
<ul>
|
|
102
|
+
{{ render_sidebar_content(item.items, item_id) if item.items }}
|
|
103
|
+
</ul>
|
|
104
|
+
</div>
|
|
105
|
+
{% elif item.type == "separator" %}
|
|
106
|
+
<hr role="separator"/>
|
|
107
|
+
{% elif item.type == "submenu" %}
|
|
108
|
+
<li>
|
|
109
|
+
<details
|
|
110
|
+
id="submenu-{{ item_id }}"
|
|
111
|
+
{{ "open" if item.open }}
|
|
112
|
+
{% if item.attrs %}
|
|
113
|
+
{% for key, value in item.attrs %}
|
|
114
|
+
{% if key != "open" %}{{ key }}="{{ value }}"{% endif %}
|
|
115
|
+
{% endfor %}
|
|
116
|
+
{% endif %}
|
|
117
|
+
>
|
|
118
|
+
<summary aria-controls="submenu-{{ item_id }}-content">
|
|
119
|
+
{% if item.icon %}{{ item.icon | safe }}{% endif %}
|
|
120
|
+
{{ item.label | safe }}
|
|
121
|
+
</summary>
|
|
122
|
+
<ul id="submenu-{{ item_id }}-content">
|
|
123
|
+
{{ render_sidebar_content(item.items, item_id) if item.items }}
|
|
124
|
+
</ul>
|
|
125
|
+
</details>
|
|
126
|
+
</li>
|
|
127
|
+
{% elif item.type == "item" or not item.type %}
|
|
128
|
+
<li>
|
|
129
|
+
<a
|
|
130
|
+
href="{{ item.url }}"
|
|
131
|
+
{{ 'aria-current="page"' if item.current }}
|
|
132
|
+
{% if item.attrs %}
|
|
133
|
+
{% for key, value in item.attrs %}
|
|
134
|
+
{% if key != "href" and key != "aria-current" %}{{ key }}="{{ value }}"{% endif %}
|
|
135
|
+
{% endfor %}
|
|
136
|
+
{% endif %}
|
|
137
|
+
>
|
|
138
|
+
{% if item.icon %}{{ item.icon | safe }}{% endif %}
|
|
139
|
+
<span>{{ item.label | safe }}</span>
|
|
140
|
+
</a>
|
|
141
|
+
</li>
|
|
142
|
+
{% endif %}
|
|
143
|
+
{% endfor %}
|
|
144
|
+
{% endmacro %}
|
|
@@ -0,0 +1,78 @@
|
|
|
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=None,
|
|
18
|
+
tabsets=[],
|
|
19
|
+
main_attrs={},
|
|
20
|
+
tablist_attrs={},
|
|
21
|
+
default_tab_index=1
|
|
22
|
+
)
|
|
23
|
+
%}
|
|
24
|
+
{% set id = id or ("tabs-" + (range(100000, 999999) | random | string)) %}
|
|
25
|
+
<div
|
|
26
|
+
class="tabs {{ main_attrs.class }}"
|
|
27
|
+
id="{{ id }}"
|
|
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
|
+
{% for key, value in tablist_attrs %}
|
|
36
|
+
{{ key }}="{{ value }}"
|
|
37
|
+
{% endfor %}
|
|
38
|
+
>
|
|
39
|
+
{% for tabset in tabsets %}
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
role="tab"
|
|
43
|
+
id="{{ id }}-tab-{{ loop.index }}"
|
|
44
|
+
aria-controls="{{ id }}-panel-{{ loop.index }}"
|
|
45
|
+
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
|
|
46
|
+
tabindex="0"
|
|
47
|
+
{% if tabset.tab_attrs %}
|
|
48
|
+
{% for key, value in tabset.tab_attrs %}
|
|
49
|
+
{{ key }}="{{ value }}"
|
|
50
|
+
{% endfor %}
|
|
51
|
+
{% endif %}
|
|
52
|
+
>
|
|
53
|
+
{{ tabset.tab | safe }}
|
|
54
|
+
</button>
|
|
55
|
+
{% endfor %}
|
|
56
|
+
</nav>
|
|
57
|
+
|
|
58
|
+
{% for tabset in tabsets %}
|
|
59
|
+
{% if tabset.panel %}
|
|
60
|
+
<div
|
|
61
|
+
role="tabpanel"
|
|
62
|
+
id="{{ id }}-panel-{{ loop.index }}"
|
|
63
|
+
aria-labelledby="{{ id }}-tab-{{ loop.index }}"
|
|
64
|
+
tabindex="-1"
|
|
65
|
+
aria-selected="{{ 'true' if loop.index == default_tab_index else 'false' }}"
|
|
66
|
+
{% if loop.index != default_tab_index %}hidden{% endif %}
|
|
67
|
+
{% if tabset.panel_attrs %}
|
|
68
|
+
{% for key, value in tabset.panel_attrs %}
|
|
69
|
+
{{ key }}="{{ value }}"
|
|
70
|
+
{% endfor %}
|
|
71
|
+
{% endif %}
|
|
72
|
+
>
|
|
73
|
+
{{ tabset.panel | safe }}
|
|
74
|
+
</div>
|
|
75
|
+
{% endif %}
|
|
76
|
+
{% endfor %}
|
|
77
|
+
</div>
|
|
78
|
+
{% endmacro %}
|