@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,206 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Renders a standalone command menu component with search and keyboard navigation.
|
|
3
|
+
|
|
4
|
+
@param id {string} [optional] - Unique identifier for the command component.
|
|
5
|
+
@param items {array} [optional] - Array of items to render (alternative to using caller).
|
|
6
|
+
@param placeholder {string} [optional] [default="Type a command or search..."] - Placeholder text for the search input.
|
|
7
|
+
@param empty_text {string} [optional] [default="No results found."] - Text displayed when no results match the search.
|
|
8
|
+
@param main_attrs {object} [optional] - Additional HTML attributes for the main container div.
|
|
9
|
+
@param input_attrs {object} [optional] - Additional HTML attributes for the search input.
|
|
10
|
+
@param menu_attrs {object} [optional] - Additional HTML attributes for the menu div.
|
|
11
|
+
#}
|
|
12
|
+
{% macro command(
|
|
13
|
+
id=None,
|
|
14
|
+
items=None,
|
|
15
|
+
placeholder="Type a command or search...",
|
|
16
|
+
empty_text="No results found.",
|
|
17
|
+
main_attrs={},
|
|
18
|
+
input_attrs={},
|
|
19
|
+
menu_attrs={}
|
|
20
|
+
) %}
|
|
21
|
+
{% set id = id or ("command-" ~ range(100000, 999999) | random | string) %}
|
|
22
|
+
<div
|
|
23
|
+
id="{{ id }}"
|
|
24
|
+
class="command {{ main_attrs.class }}"
|
|
25
|
+
aria-label="Command menu"
|
|
26
|
+
{% for key, value in main_attrs.items() %}
|
|
27
|
+
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
|
28
|
+
{% endfor %}
|
|
29
|
+
>
|
|
30
|
+
<header>
|
|
31
|
+
<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>
|
|
32
|
+
<input
|
|
33
|
+
type="text"
|
|
34
|
+
id="{{ id }}-input"
|
|
35
|
+
placeholder="{{ placeholder }}"
|
|
36
|
+
autocomplete="off"
|
|
37
|
+
autocorrect="off"
|
|
38
|
+
spellcheck="false"
|
|
39
|
+
aria-autocomplete="list"
|
|
40
|
+
role="combobox"
|
|
41
|
+
aria-expanded="true"
|
|
42
|
+
aria-controls="{{ id }}-menu"
|
|
43
|
+
{% for key, value in input_attrs.items() %}
|
|
44
|
+
{{ key }}="{{ value }}"
|
|
45
|
+
{% endfor %}
|
|
46
|
+
>
|
|
47
|
+
</header>
|
|
48
|
+
<div
|
|
49
|
+
role="menu"
|
|
50
|
+
id="{{ id }}-menu"
|
|
51
|
+
aria-orientation="vertical"
|
|
52
|
+
data-empty="{{ empty_text }}"
|
|
53
|
+
class="scrollbar {{ menu_attrs.class }}"
|
|
54
|
+
{% for key, value in menu_attrs.items() %}
|
|
55
|
+
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
|
56
|
+
{% endfor %}
|
|
57
|
+
>
|
|
58
|
+
{% if items and items|length > 0 %}
|
|
59
|
+
{{ render_command_items(items, id ~ "-items" if id else "items") }}
|
|
60
|
+
{% else %}
|
|
61
|
+
{{ caller() if caller }}
|
|
62
|
+
{% endif %}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
{% endmacro %}
|
|
66
|
+
|
|
67
|
+
{#
|
|
68
|
+
Renders a command dialog (modal command palette).
|
|
69
|
+
|
|
70
|
+
@param id {string} [optional] - Unique identifier for the command dialog.
|
|
71
|
+
@param items {array} [optional] - Array of items to render (alternative to using caller).
|
|
72
|
+
@param placeholder {string} [optional] [default="Type a command or search..."] - Placeholder text for the search input.
|
|
73
|
+
@param empty_text {string} [optional] [default="No results found."] - Text displayed when no results match the search.
|
|
74
|
+
@param dialog_attrs {object} [optional] - Additional HTML attributes for the dialog element.
|
|
75
|
+
@param input_attrs {object} [optional] - Additional HTML attributes for the search input.
|
|
76
|
+
@param menu_attrs {object} [optional] - Additional HTML attributes for the menu div.
|
|
77
|
+
@param open {boolean} [optional] [default=False] - Whether the command dialog should be open initially.
|
|
78
|
+
#}
|
|
79
|
+
{% macro command_dialog(
|
|
80
|
+
id=None,
|
|
81
|
+
items=None,
|
|
82
|
+
placeholder="Type a command or search...",
|
|
83
|
+
empty_text="No results found.",
|
|
84
|
+
dialog_attrs={},
|
|
85
|
+
input_attrs={},
|
|
86
|
+
menu_attrs={},
|
|
87
|
+
open=False
|
|
88
|
+
) %}
|
|
89
|
+
{% set id = id or ("command-dialog-" ~ range(100000, 999999) | random | string) %}
|
|
90
|
+
<dialog
|
|
91
|
+
id="{{ id }}"
|
|
92
|
+
class="dialog command-dialog {{ dialog_attrs.class }}"
|
|
93
|
+
aria-label="Command menu"
|
|
94
|
+
{% if open %}open{% endif %}
|
|
95
|
+
onclick="if (event.target === this) this.close()"
|
|
96
|
+
{% for key, value in dialog_attrs.items() %}
|
|
97
|
+
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
|
98
|
+
{% endfor %}
|
|
99
|
+
>
|
|
100
|
+
<div class="command">
|
|
101
|
+
<header>
|
|
102
|
+
<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>
|
|
103
|
+
<input
|
|
104
|
+
type="text"
|
|
105
|
+
id="{{ id }}-input"
|
|
106
|
+
placeholder="{{ placeholder }}"
|
|
107
|
+
autocomplete="off"
|
|
108
|
+
autocorrect="off"
|
|
109
|
+
spellcheck="false"
|
|
110
|
+
aria-autocomplete="list"
|
|
111
|
+
role="combobox"
|
|
112
|
+
aria-expanded="true"
|
|
113
|
+
aria-controls="{{ id }}-menu"
|
|
114
|
+
{% for key, value in input_attrs.items() %}
|
|
115
|
+
{{ key }}="{{ value }}"
|
|
116
|
+
{% endfor %}
|
|
117
|
+
>
|
|
118
|
+
</header>
|
|
119
|
+
<div
|
|
120
|
+
role="menu"
|
|
121
|
+
id="{{ id }}-menu"
|
|
122
|
+
aria-orientation="vertical"
|
|
123
|
+
data-empty="{{ empty_text }}"
|
|
124
|
+
class="scrollbar {{ menu_attrs.class }}"
|
|
125
|
+
{% for key, value in menu_attrs.items() %}
|
|
126
|
+
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
|
127
|
+
{% endfor %}
|
|
128
|
+
>
|
|
129
|
+
{% if items and items|length > 0 %}
|
|
130
|
+
{{ render_command_items(items, id ~ "-items" if id else "items") }}
|
|
131
|
+
{% else %}
|
|
132
|
+
{{ caller() if caller }}
|
|
133
|
+
{% endif %}
|
|
134
|
+
</div>
|
|
135
|
+
<button type="button" aria-label="Close dialog" onclick="this.closest('dialog').close()">
|
|
136
|
+
<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>
|
|
137
|
+
</button>
|
|
138
|
+
</div>
|
|
139
|
+
</dialog>
|
|
140
|
+
{% endmacro %}
|
|
141
|
+
|
|
142
|
+
{#
|
|
143
|
+
Renders a list of items for the command component.
|
|
144
|
+
|
|
145
|
+
@param items {array} - Array of items to render. Each item can be:
|
|
146
|
+
- { type: "item", label: "Text or HTML", icon: "HTML", url: "href", keywords: "search terms", disabled: true, attrs: {} }
|
|
147
|
+
- { type: "group", label: "Group Title", items: [...] }
|
|
148
|
+
- { type: "separator" }
|
|
149
|
+
Note: Use attrs to add data-label for alternative search text if needed. If url is provided, renders as <a role="menuitem"> instead of <div role="menuitem">. Label supports HTML (marked as safe).
|
|
150
|
+
@param parent_id_prefix {string} [optional] - The prefix for the item id.
|
|
151
|
+
#}
|
|
152
|
+
{% macro render_command_items(items, parent_id_prefix="items") %}
|
|
153
|
+
{% for item in items %}
|
|
154
|
+
{% set item_id = parent_id_prefix ~ "-" ~ loop.index %}
|
|
155
|
+
{% if item.type == "group" %}
|
|
156
|
+
{% set group_label_id = item.id if item.id else "group-label-" + item_id %}
|
|
157
|
+
<div
|
|
158
|
+
role="group"
|
|
159
|
+
aria-labelledby="{{ group_label_id }}"
|
|
160
|
+
{% if item.attrs %}
|
|
161
|
+
{% for key, value in item.attrs.items() %}
|
|
162
|
+
{{ key }}="{{ value }}"
|
|
163
|
+
{% endfor %}
|
|
164
|
+
{% endif %}
|
|
165
|
+
>
|
|
166
|
+
<span role="heading" id="{{ group_label_id }}">{{ item.label | safe }}</span>
|
|
167
|
+
{{ render_command_items(item['items'], item_id) if item['items'] }}
|
|
168
|
+
</div>
|
|
169
|
+
{% elif item.type == "separator" %}
|
|
170
|
+
<hr role="separator" />
|
|
171
|
+
{% elif item.type == "item" or not item.type %}
|
|
172
|
+
{% if item.url %}
|
|
173
|
+
<a
|
|
174
|
+
id="{{ item_id }}"
|
|
175
|
+
href="{{ item.url }}"
|
|
176
|
+
role="menuitem"
|
|
177
|
+
{% if item.keywords %}data-keywords="{{ item.keywords }}"{% endif %}
|
|
178
|
+
{% if item.disabled %}aria-disabled="true"{% endif %}
|
|
179
|
+
{% if item.attrs %}
|
|
180
|
+
{% for key, value in item.attrs.items() %}
|
|
181
|
+
{% if key != "href" %}{{ key }}="{{ value }}"{% endif %}
|
|
182
|
+
{% endfor %}
|
|
183
|
+
{% endif %}
|
|
184
|
+
>
|
|
185
|
+
{% if item.icon %}{{ item.icon | safe }}{% endif %}
|
|
186
|
+
{{ item.label | safe }}
|
|
187
|
+
</a>
|
|
188
|
+
{% else %}
|
|
189
|
+
<div
|
|
190
|
+
id="{{ item_id }}"
|
|
191
|
+
role="menuitem"
|
|
192
|
+
{% if item.keywords %}data-keywords="{{ item.keywords }}"{% endif %}
|
|
193
|
+
{% if item.disabled %}aria-disabled="true"{% endif %}
|
|
194
|
+
{% if item.attrs %}
|
|
195
|
+
{% for key, value in item.attrs.items() %}
|
|
196
|
+
{{ key }}="{{ value }}"
|
|
197
|
+
{% endfor %}
|
|
198
|
+
{% endif %}
|
|
199
|
+
>
|
|
200
|
+
{% if item.icon %}{{ item.icon | safe }}{% endif %}
|
|
201
|
+
{{ item.label | safe }}
|
|
202
|
+
</div>
|
|
203
|
+
{% endif %}
|
|
204
|
+
{% endif %}
|
|
205
|
+
{% endfor %}
|
|
206
|
+
{% endmacro %}
|
|
@@ -0,0 +1,94 @@
|
|
|
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 dialog_attrs {object} [optional] - Additional HTML attributes for the dialog content container.
|
|
12
|
+
@param header_attrs {object} [optional] - Additional HTML attributes for the dialog header.
|
|
13
|
+
@param body_attrs {object} [optional] - Additional HTML attributes for the dialog body section.
|
|
14
|
+
@param 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=None,
|
|
21
|
+
trigger=None,
|
|
22
|
+
title=None,
|
|
23
|
+
description=None,
|
|
24
|
+
footer=None,
|
|
25
|
+
dialog_attrs={},
|
|
26
|
+
trigger_attrs={},
|
|
27
|
+
header_attrs={},
|
|
28
|
+
body_attrs={},
|
|
29
|
+
footer_attrs={},
|
|
30
|
+
open=false,
|
|
31
|
+
close_button=true,
|
|
32
|
+
close_on_overlay_click=true
|
|
33
|
+
) %}
|
|
34
|
+
{% set id = id or ("dialog-" + (range(100000, 999999) | random | string)) %}
|
|
35
|
+
{% if trigger %}
|
|
36
|
+
<button
|
|
37
|
+
type="button"
|
|
38
|
+
onclick="document.getElementById('{{ id }}').showModal()"
|
|
39
|
+
{% for key, value in trigger_attrs.items() %}
|
|
40
|
+
{{ key }}="{{ value }}"
|
|
41
|
+
{% endfor %}
|
|
42
|
+
>
|
|
43
|
+
{{ trigger | safe }}
|
|
44
|
+
</button>
|
|
45
|
+
{% endif %}
|
|
46
|
+
<dialog
|
|
47
|
+
id="{{ id }}"
|
|
48
|
+
class="dialog {{ dialog_attrs.class }}"
|
|
49
|
+
aria-labelledby="{{ id }}-title"
|
|
50
|
+
{% if description %}aria-describedby="{{ id }}-description"{% endif %}
|
|
51
|
+
{% if close_on_overlay_click %}onclick="if (event.target === this) this.close()"{% endif %}
|
|
52
|
+
{% for key, value in dialog_attrs.items() %}
|
|
53
|
+
{% if key != 'class' %}{{ key }}="{{ value }}"{% endif %}
|
|
54
|
+
{% endfor %}
|
|
55
|
+
>
|
|
56
|
+
<div>
|
|
57
|
+
{% if title or description %}
|
|
58
|
+
<header
|
|
59
|
+
{% for key, value in header_attrs.items() %}
|
|
60
|
+
{{ key }}="{{ value }}"
|
|
61
|
+
{% endfor %}
|
|
62
|
+
>
|
|
63
|
+
<h2 id="{{ id }}-title">{{ title | safe }}</h2>
|
|
64
|
+
{% if description %}<p id="{{ id }}-description">{{ description | safe }}</p>{% endif %}
|
|
65
|
+
</header>
|
|
66
|
+
{% endif %}
|
|
67
|
+
{% if caller %}
|
|
68
|
+
<section
|
|
69
|
+
{% for key, value in body_attrs.items() %}
|
|
70
|
+
{{ key }}="{{ value }}"
|
|
71
|
+
{% endfor %}
|
|
72
|
+
>
|
|
73
|
+
{{ caller() }}
|
|
74
|
+
</section>
|
|
75
|
+
{% endif %}
|
|
76
|
+
{% if footer %}
|
|
77
|
+
<footer
|
|
78
|
+
{% for key, value in footer_attrs.items() %}
|
|
79
|
+
{{ key }}="{{ value }}"
|
|
80
|
+
{% endfor %}
|
|
81
|
+
>
|
|
82
|
+
{{ footer | safe }}
|
|
83
|
+
</footer>
|
|
84
|
+
{% endif %}
|
|
85
|
+
{% if close_button %}
|
|
86
|
+
<form method="dialog">
|
|
87
|
+
<button aria-label="Close dialog">
|
|
88
|
+
<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>
|
|
89
|
+
</button>
|
|
90
|
+
</form>
|
|
91
|
+
{% endif %}
|
|
92
|
+
</div>
|
|
93
|
+
</dialog>
|
|
94
|
+
{% endmacro %}
|
|
@@ -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.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
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=[],
|
|
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 = namespace(item=None) %}
|
|
42
|
+
{% set selected_options = namespace(items=[]) %}
|
|
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.item %}
|
|
49
|
+
{% set first_option.item = sub_item %}
|
|
50
|
+
{% endif %}
|
|
51
|
+
{% if sub_item.value in selectedArray %}
|
|
52
|
+
{% set selected_options.items = selected_options.items + [sub_item] %}
|
|
53
|
+
{% endif %}
|
|
54
|
+
{% endfor %}
|
|
55
|
+
{% else %}
|
|
56
|
+
{% if not first_option.item %}
|
|
57
|
+
{% set first_option.item = item %}
|
|
58
|
+
{% endif %}
|
|
59
|
+
{% if item.value in selectedArray %}
|
|
60
|
+
{% set selected_options.items = selected_options.items + [item] %}
|
|
61
|
+
{% endif %}
|
|
62
|
+
{% endif %}
|
|
63
|
+
{% endfor %}
|
|
64
|
+
{% endif %}
|
|
65
|
+
|
|
66
|
+
{% set default_option = (selected_options.items[0] if selected_options.items else first_option.item) %}
|
|
67
|
+
{% set labels = [] %}
|
|
68
|
+
{% if multiple and selected_options.items %}
|
|
69
|
+
{% for opt in selected_options.items %}
|
|
70
|
+
{% set labels = labels + [opt.label] %}
|
|
71
|
+
{% endfor %}
|
|
72
|
+
{% endif %}
|
|
73
|
+
{% set default_label = (', '.join(labels) if (multiple and selected_options.items) else ((placeholder or '') if (multiple and not selected_options.items) 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.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
134
|
+
{{ key }}="{{ value }}"
|
|
135
|
+
{% endfor %}
|
|
136
|
+
>
|
|
137
|
+
{% if items %}
|
|
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 | tojson }}{% else %}{{ (default_option.value if default_option) or '' }}{% endif %}"
|
|
148
|
+
{% for key, value in input_attrs.items() %}
|
|
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.items() %}
|
|
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.items() %}
|
|
188
|
+
{{ key }}="{{ value }}"
|
|
189
|
+
{% endfor %}
|
|
190
|
+
{% endif %}
|
|
191
|
+
>
|
|
192
|
+
{{ item.label | safe }}
|
|
193
|
+
</div>
|
|
194
|
+
{% endif %}
|
|
195
|
+
{% endfor %}
|
|
196
|
+
{% endmacro %}
|