htmui 0.1.0__py3-none-any.whl
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.
- htmui/__init__.py +1 -0
- htmui/basecoat/__init__.py +0 -0
- htmui/basecoat/accordion.py +76 -0
- htmui/basecoat/alert.py +44 -0
- htmui/basecoat/avatar.py +52 -0
- htmui/basecoat/badge.py +32 -0
- htmui/basecoat/breadcrumb.py +46 -0
- htmui/basecoat/button.py +36 -0
- htmui/basecoat/button_group.py +22 -0
- htmui/basecoat/card.py +32 -0
- htmui/basecoat/cdn.py +14 -0
- htmui/basecoat/chart.py +86 -0
- htmui/basecoat/codeblock.py +109 -0
- htmui/basecoat/combobox.py +190 -0
- htmui/basecoat/command.py +128 -0
- htmui/basecoat/dialog.py +165 -0
- htmui/basecoat/drawer.py +66 -0
- htmui/basecoat/dropdown_menu.py +59 -0
- htmui/basecoat/field.py +91 -0
- htmui/basecoat/input_group.py +49 -0
- htmui/basecoat/item.py +94 -0
- htmui/basecoat/menu.py +131 -0
- htmui/basecoat/pagination.py +60 -0
- htmui/basecoat/popover.py +69 -0
- htmui/basecoat/progress.py +57 -0
- htmui/basecoat/radio_group.py +83 -0
- htmui/basecoat/select.py +190 -0
- htmui/basecoat/separator.py +8 -0
- htmui/basecoat/sidebar.py +270 -0
- htmui/basecoat/slider.py +50 -0
- htmui/basecoat/spinner.py +43 -0
- htmui/basecoat/switch.py +50 -0
- htmui/basecoat/table.py +38 -0
- htmui/basecoat/tabs.py +109 -0
- htmui/basecoat/theme_switcher.py +58 -0
- htmui/basecoat/toast.py +97 -0
- htmui/basecoat/typing.py +21 -0
- htmui/cli.py +257 -0
- htmui-0.1.0.dist-info/METADATA +159 -0
- htmui-0.1.0.dist-info/RECORD +43 -0
- htmui-0.1.0.dist-info/WHEEL +4 -0
- htmui-0.1.0.dist-info/entry_points.txt +5 -0
- htmui-0.1.0.dist-info/licenses/LICENSE +21 -0
htmui/basecoat/select.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any, Literal
|
|
3
|
+
|
|
4
|
+
from htmy import ComponentType, PropertyValue, SafeStr, XBool, html, join_classes
|
|
5
|
+
|
|
6
|
+
__version__ = "0.2.0"
|
|
7
|
+
__framework__ = "BasecoatUI"
|
|
8
|
+
__framework_version__ = "1"
|
|
9
|
+
__framework_url__ = "https://basecoatui.com/components/select/"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
js = SafeStr(
|
|
13
|
+
'<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1/dist/js/select.min.js" defer></script>'
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# `chevron-down` icon, matching the BasecoatUI reference markup.
|
|
18
|
+
_trigger_icon = SafeStr(
|
|
19
|
+
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" '
|
|
20
|
+
'fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" '
|
|
21
|
+
'class="lucide lucide-chevron-down text-muted-foreground opacity-50 shrink-0">'
|
|
22
|
+
'<path d="m6 9 6 6 6-6" /></svg>'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def option(
|
|
27
|
+
*children: ComponentType,
|
|
28
|
+
value: str | None = None,
|
|
29
|
+
label: str | None = None,
|
|
30
|
+
disabled: bool = False,
|
|
31
|
+
selected: bool = False,
|
|
32
|
+
**kwargs: PropertyValue,
|
|
33
|
+
) -> ComponentType:
|
|
34
|
+
"""
|
|
35
|
+
`div` with `role="option"` for `select`.
|
|
36
|
+
|
|
37
|
+
Arguments:
|
|
38
|
+
value: Maps to `data-value`.
|
|
39
|
+
label: Optional `data-label` overriding the trigger/display text.
|
|
40
|
+
disabled: Whether the option is disabled.
|
|
41
|
+
selected: Whether the option is selected.
|
|
42
|
+
**kwargs: Other attributes passed directly to the element.
|
|
43
|
+
"""
|
|
44
|
+
kwargs["role"] = "option"
|
|
45
|
+
if value is not None:
|
|
46
|
+
kwargs["data-value"] = value
|
|
47
|
+
if label is not None:
|
|
48
|
+
kwargs["data-label"] = label
|
|
49
|
+
if disabled:
|
|
50
|
+
kwargs["aria-disabled"] = "true"
|
|
51
|
+
if selected:
|
|
52
|
+
kwargs["aria-selected"] = "true"
|
|
53
|
+
return html.div(*children, **kwargs)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def group(
|
|
57
|
+
*items: ComponentType,
|
|
58
|
+
heading: ComponentType,
|
|
59
|
+
heading_id: str,
|
|
60
|
+
**kwargs: PropertyValue,
|
|
61
|
+
) -> ComponentType:
|
|
62
|
+
"""
|
|
63
|
+
`div` with `role="group"`, containing options with a linked heading.
|
|
64
|
+
|
|
65
|
+
Arguments:
|
|
66
|
+
label: Group heading content.
|
|
67
|
+
heading_id: Id of the heading span; also used as `aria-labelledby`.
|
|
68
|
+
**kwargs: Other attributes passed through to the root element.
|
|
69
|
+
"""
|
|
70
|
+
kwargs["role"] = "group"
|
|
71
|
+
kwargs["aria-labelledby"] = heading_id
|
|
72
|
+
return html.div(
|
|
73
|
+
html.span(heading, role="heading", id=heading_id),
|
|
74
|
+
*items,
|
|
75
|
+
**kwargs,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def select( # noqa: C901
|
|
80
|
+
*items: ComponentType,
|
|
81
|
+
name: str,
|
|
82
|
+
placeholder: str | None = None,
|
|
83
|
+
multiple: bool = False,
|
|
84
|
+
close_on_select: bool = False,
|
|
85
|
+
format: Literal["object"] | None = None,
|
|
86
|
+
value: str | list[Any] | None = None,
|
|
87
|
+
disabled: bool = False,
|
|
88
|
+
invalid: bool = False,
|
|
89
|
+
trigger_class: str | None = None,
|
|
90
|
+
listbox_class: str | None = None,
|
|
91
|
+
popover_class: str | None = None,
|
|
92
|
+
class_: str | None = None,
|
|
93
|
+
**kwargs: PropertyValue,
|
|
94
|
+
) -> ComponentType:
|
|
95
|
+
"""
|
|
96
|
+
Select component root.
|
|
97
|
+
|
|
98
|
+
It contains a hidden input, which is submitted under `name`. Internal element IDs
|
|
99
|
+
are derived from `name` automatically.
|
|
100
|
+
|
|
101
|
+
Arguments:
|
|
102
|
+
name: The name of the hidden input.
|
|
103
|
+
placeholder: Text shown in the trigger when nothing is selected.
|
|
104
|
+
multiple: Allow selecting multiple options.
|
|
105
|
+
close_on_select: Whether to close the select after selecting an option.
|
|
106
|
+
It only applies to the multi-select case.
|
|
107
|
+
format: Value format submitted by the hidden input. Use `"object"` to
|
|
108
|
+
send the full option object.
|
|
109
|
+
value: Initially selected value. Pass a string for single select or
|
|
110
|
+
a list for multiple select.
|
|
111
|
+
disabled: Disable the trigger button.
|
|
112
|
+
invalid: Mark the select as invalid for accessibility and validation.
|
|
113
|
+
trigger_class: Additional classes for the trigger button.
|
|
114
|
+
listbox_class: Additional classes for the listbox popup.
|
|
115
|
+
popover_class: Additional classes for the popover wrapper.
|
|
116
|
+
class_: Additional classes for the root element.
|
|
117
|
+
**kwargs: Extra attributes for the root element.
|
|
118
|
+
"""
|
|
119
|
+
id = f"{name}-root"
|
|
120
|
+
trigger_id = f"{id}-trigger"
|
|
121
|
+
popover_id = f"{id}-popover"
|
|
122
|
+
listbox_id = f"{id}-listbox"
|
|
123
|
+
|
|
124
|
+
root_attrs: dict[str, PropertyValue] = dict(kwargs)
|
|
125
|
+
if placeholder is not None:
|
|
126
|
+
root_attrs["data-placeholder"] = placeholder
|
|
127
|
+
if multiple and close_on_select:
|
|
128
|
+
root_attrs["data-close-on-select"] = "true"
|
|
129
|
+
if format is not None:
|
|
130
|
+
root_attrs["data-format"] = format
|
|
131
|
+
|
|
132
|
+
trigger_attrs: dict[str, PropertyValue] = {
|
|
133
|
+
"type": "button",
|
|
134
|
+
"id": trigger_id,
|
|
135
|
+
"aria-haspopup": "listbox",
|
|
136
|
+
"aria-expanded": "false",
|
|
137
|
+
"aria-controls": listbox_id,
|
|
138
|
+
}
|
|
139
|
+
if disabled:
|
|
140
|
+
trigger_attrs["disabled"] = XBool.true
|
|
141
|
+
if invalid:
|
|
142
|
+
trigger_attrs["aria-invalid"] = "true"
|
|
143
|
+
trigger_attrs["class"] = trigger_class
|
|
144
|
+
|
|
145
|
+
popover_attrs: dict[str, PropertyValue] = {
|
|
146
|
+
"id": popover_id,
|
|
147
|
+
"data-popover": "",
|
|
148
|
+
"aria-hidden": "true",
|
|
149
|
+
}
|
|
150
|
+
if popover_class:
|
|
151
|
+
popover_attrs["class"] = popover_class
|
|
152
|
+
|
|
153
|
+
listbox_attrs: dict[str, PropertyValue] = {
|
|
154
|
+
"role": "listbox",
|
|
155
|
+
"id": listbox_id,
|
|
156
|
+
"aria-orientation": "vertical",
|
|
157
|
+
"aria-labelledby": trigger_id,
|
|
158
|
+
}
|
|
159
|
+
if multiple:
|
|
160
|
+
listbox_attrs["aria-multiselectable"] = "true"
|
|
161
|
+
if listbox_class:
|
|
162
|
+
listbox_attrs["class"] = listbox_class
|
|
163
|
+
|
|
164
|
+
effective_value: str
|
|
165
|
+
if value is None:
|
|
166
|
+
effective_value = ""
|
|
167
|
+
elif isinstance(value, (list, tuple)):
|
|
168
|
+
effective_value = json.dumps(list(value))
|
|
169
|
+
else:
|
|
170
|
+
effective_value = str(value)
|
|
171
|
+
|
|
172
|
+
return html.div(
|
|
173
|
+
html.button(
|
|
174
|
+
html.span(placeholder or "", class_="truncate"),
|
|
175
|
+
_trigger_icon,
|
|
176
|
+
**trigger_attrs,
|
|
177
|
+
),
|
|
178
|
+
html.div(
|
|
179
|
+
html.div(*items, **listbox_attrs),
|
|
180
|
+
**popover_attrs,
|
|
181
|
+
),
|
|
182
|
+
html.input_(
|
|
183
|
+
type="hidden",
|
|
184
|
+
name=name,
|
|
185
|
+
value=effective_value,
|
|
186
|
+
),
|
|
187
|
+
id=id,
|
|
188
|
+
class_=join_classes("select", class_),
|
|
189
|
+
**root_attrs,
|
|
190
|
+
)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from htmy import (
|
|
6
|
+
ComponentType,
|
|
7
|
+
PropertyValue,
|
|
8
|
+
SafeStr,
|
|
9
|
+
Tag,
|
|
10
|
+
XBool,
|
|
11
|
+
as_component_sequence,
|
|
12
|
+
html,
|
|
13
|
+
join_classes,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .button import ButtonSize, ButtonVariant
|
|
17
|
+
from .typing import HorizontalSide
|
|
18
|
+
|
|
19
|
+
__version__ = "0.2.0"
|
|
20
|
+
__framework__ = "BasecoatUI"
|
|
21
|
+
__framework_version__ = "1"
|
|
22
|
+
__framework_url__ = "https://basecoatui.com/components/sidebar/"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
js = SafeStr(
|
|
26
|
+
'<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1/dist/js/sidebar.min.js" defer></script>'
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
SidebarItemVariant = Literal["default", "outline"]
|
|
31
|
+
"""Visual variant for menu items and submenu summaries."""
|
|
32
|
+
|
|
33
|
+
SidebarItemSize = Literal["default", "sm", "lg"]
|
|
34
|
+
"""Size for menu items and submenu summaries."""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
menu_icon = SafeStr(
|
|
38
|
+
'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" '
|
|
39
|
+
'stroke-width="1.5" stroke="currentColor" class="size-6">'
|
|
40
|
+
'<path stroke-linecap="round" stroke-linejoin="round" '
|
|
41
|
+
'd="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/></svg>'
|
|
42
|
+
)
|
|
43
|
+
"""`bars-3` icon from https://heroicons.com/."""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def sidebar(
|
|
47
|
+
*children: ComponentType,
|
|
48
|
+
id: str,
|
|
49
|
+
aria_label: str | None = None,
|
|
50
|
+
header: ComponentType | None = None,
|
|
51
|
+
footer: ComponentType | None = None,
|
|
52
|
+
side: HorizontalSide = "left",
|
|
53
|
+
initial_open: bool = True,
|
|
54
|
+
initial_mobile_open: bool = False,
|
|
55
|
+
class_: str | None = None,
|
|
56
|
+
nav_class: str | None = None,
|
|
57
|
+
section_class: str | None = "scrollbar-sm",
|
|
58
|
+
**kwargs: PropertyValue,
|
|
59
|
+
) -> ComponentType:
|
|
60
|
+
"""
|
|
61
|
+
Sidebar component.
|
|
62
|
+
|
|
63
|
+
Arguments:
|
|
64
|
+
*children: The content of the sidebar.
|
|
65
|
+
id: The sidebar's ID.
|
|
66
|
+
aria_label: Accessibility label for the sidebar's navigation component.
|
|
67
|
+
header: Optional content at the top of the sidebar.
|
|
68
|
+
footer: Optional content at the bottom of the sidebar.
|
|
69
|
+
side: Side of the viewport the sidebar attaches to.
|
|
70
|
+
initial_open: Whether the sidebar starts open on desktop. When `False`,
|
|
71
|
+
the sidebar starts hidden and inert.
|
|
72
|
+
initial_mobile_open: Whether the sidebar starts open below the
|
|
73
|
+
mobile breakpoint.
|
|
74
|
+
class_: Additional classes for the root element.
|
|
75
|
+
nav_class: Additional classes for the nav element.
|
|
76
|
+
section_class: Additional classes for the scrollable content section.
|
|
77
|
+
**kwargs: Extra attributes for the root element.
|
|
78
|
+
"""
|
|
79
|
+
if not initial_open:
|
|
80
|
+
kwargs["data_initial_open"] = "false"
|
|
81
|
+
kwargs["aria_hidden"] = "true"
|
|
82
|
+
kwargs["inert"] = XBool.true
|
|
83
|
+
|
|
84
|
+
if initial_mobile_open:
|
|
85
|
+
kwargs["data_initial_mobile_open"] = "true"
|
|
86
|
+
|
|
87
|
+
return html.aside(
|
|
88
|
+
html.nav(
|
|
89
|
+
None if header is None else html.header(*as_component_sequence(header)),
|
|
90
|
+
html.section(*children, class_=section_class),
|
|
91
|
+
None if footer is None else html.footer(*as_component_sequence(footer)),
|
|
92
|
+
**({"aria_label": aria_label} if aria_label else {}),
|
|
93
|
+
class_=nav_class,
|
|
94
|
+
),
|
|
95
|
+
id=id,
|
|
96
|
+
class_=join_classes("sidebar", class_),
|
|
97
|
+
data_side=side,
|
|
98
|
+
**kwargs,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def sidebar_group(
|
|
103
|
+
*children: ComponentType,
|
|
104
|
+
label: ComponentType | None = None,
|
|
105
|
+
label_id: str | None = None,
|
|
106
|
+
group_class: str | None = None,
|
|
107
|
+
list_class: str | None = None,
|
|
108
|
+
heading_tag: str = "h3",
|
|
109
|
+
**kwargs: PropertyValue,
|
|
110
|
+
) -> ComponentType:
|
|
111
|
+
"""
|
|
112
|
+
Named navigation group inside the sidebar.
|
|
113
|
+
|
|
114
|
+
Arguments:
|
|
115
|
+
*children: The content of the sidebar group.
|
|
116
|
+
label: Optional group heading content.
|
|
117
|
+
label_id: Id of the heading element; also used as `aria-labelledby`.
|
|
118
|
+
group_class: Additional classes for the group wrapper.
|
|
119
|
+
list_class: Additional classes for the list element.
|
|
120
|
+
heading_tag: Tag name for the heading element.
|
|
121
|
+
**kwargs: Attributes passed directly to the group wrapper.
|
|
122
|
+
"""
|
|
123
|
+
if label_id is not None:
|
|
124
|
+
kwargs["aria_labelledby"] = label_id
|
|
125
|
+
|
|
126
|
+
return html.div(
|
|
127
|
+
None if label is None else Tag(heading_tag)(label, id=label_id),
|
|
128
|
+
html.ul(*children, class_=list_class),
|
|
129
|
+
class_=group_class,
|
|
130
|
+
role="group",
|
|
131
|
+
**kwargs,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def sidebar_item(
|
|
136
|
+
label: ComponentType,
|
|
137
|
+
*,
|
|
138
|
+
href: str | None = None,
|
|
139
|
+
icon: ComponentType | None = None,
|
|
140
|
+
current: bool = False,
|
|
141
|
+
variant: SidebarItemVariant | None = None,
|
|
142
|
+
size: SidebarItemSize | None = None,
|
|
143
|
+
active: bool = False,
|
|
144
|
+
disabled: bool = False,
|
|
145
|
+
keep_mobile_open: bool = False,
|
|
146
|
+
**kwargs: PropertyValue,
|
|
147
|
+
) -> ComponentType:
|
|
148
|
+
"""
|
|
149
|
+
Sidebar item.
|
|
150
|
+
|
|
151
|
+
Arguments:
|
|
152
|
+
label: The label of the item.
|
|
153
|
+
href: Optional destination URL. When omitted, an action button is rendered.
|
|
154
|
+
icon: Optional content rendered before the label.
|
|
155
|
+
current: Whether this is the current page.
|
|
156
|
+
variant: Visual variant of the item.
|
|
157
|
+
size: The size of the item.
|
|
158
|
+
active: Whether the item is in an active state.
|
|
159
|
+
disabled: Whether the item is non-interactive.
|
|
160
|
+
keep_mobile_open: Keep the mobile sidebar open when this item is
|
|
161
|
+
clicked, instead of closing it.
|
|
162
|
+
**kwargs: Attributes passed directly to the item.
|
|
163
|
+
"""
|
|
164
|
+
if variant is not None:
|
|
165
|
+
kwargs["data_variant"] = variant
|
|
166
|
+
if size is not None:
|
|
167
|
+
kwargs["data_size"] = size
|
|
168
|
+
if active:
|
|
169
|
+
kwargs["data_active"] = "true"
|
|
170
|
+
if keep_mobile_open:
|
|
171
|
+
kwargs["data_keep_mobile_sidebar_open"] = "true"
|
|
172
|
+
|
|
173
|
+
if href is None:
|
|
174
|
+
if disabled:
|
|
175
|
+
kwargs["disabled"] = XBool.true
|
|
176
|
+
|
|
177
|
+
return html.li(html.button(icon, html.span(label), type="button", **kwargs))
|
|
178
|
+
|
|
179
|
+
if current:
|
|
180
|
+
kwargs["aria_current"] = "page"
|
|
181
|
+
if disabled:
|
|
182
|
+
kwargs["aria_disabled"] = "true"
|
|
183
|
+
return html.li(html.a(icon, html.span(label), href=href, **kwargs))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def submenu(
|
|
187
|
+
*children: ComponentType,
|
|
188
|
+
label: ComponentType,
|
|
189
|
+
id: str | None = None,
|
|
190
|
+
icon: ComponentType | None = None,
|
|
191
|
+
open: bool = False,
|
|
192
|
+
active: bool = False,
|
|
193
|
+
variant: SidebarItemVariant | None = None,
|
|
194
|
+
size: SidebarItemSize | None = None,
|
|
195
|
+
**kwargs: PropertyValue,
|
|
196
|
+
) -> ComponentType:
|
|
197
|
+
"""
|
|
198
|
+
Sidebar submenu.
|
|
199
|
+
|
|
200
|
+
Arguments:
|
|
201
|
+
*children: Items wrapped by the submenu.
|
|
202
|
+
label: Text label shown after the optional icon.
|
|
203
|
+
icon: Optional content rendered before the label.
|
|
204
|
+
open: Whether the submenu starts expanded.
|
|
205
|
+
id: The ID of the element.
|
|
206
|
+
variant: Visual variant for the summary control.
|
|
207
|
+
size: Size for the summary control.
|
|
208
|
+
active: Whether the summary control is in an active state.
|
|
209
|
+
**kwargs: Attributes passed directly to the summary element.
|
|
210
|
+
"""
|
|
211
|
+
content_id = None if id is None else f"{id}-content"
|
|
212
|
+
if variant is not None:
|
|
213
|
+
kwargs["data_variant"] = variant
|
|
214
|
+
if size is not None:
|
|
215
|
+
kwargs["data_size"] = size
|
|
216
|
+
if active:
|
|
217
|
+
kwargs["data_active"] = "true"
|
|
218
|
+
if content_id is not None:
|
|
219
|
+
kwargs["aria_controls"] = content_id
|
|
220
|
+
|
|
221
|
+
details_props: dict[str, PropertyValue] = {}
|
|
222
|
+
if id is not None:
|
|
223
|
+
details_props["id"] = id
|
|
224
|
+
if open:
|
|
225
|
+
details_props["open"] = XBool.true
|
|
226
|
+
|
|
227
|
+
return html.li(
|
|
228
|
+
html.details(
|
|
229
|
+
html.summary(
|
|
230
|
+
icon,
|
|
231
|
+
html.span(label),
|
|
232
|
+
**kwargs,
|
|
233
|
+
),
|
|
234
|
+
html.ul(*children, id=content_id),
|
|
235
|
+
**details_props,
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def sidebar_toggle(
|
|
241
|
+
content: ComponentType = menu_icon,
|
|
242
|
+
*,
|
|
243
|
+
sidebar_id: str,
|
|
244
|
+
class_: str | None = None,
|
|
245
|
+
variant: ButtonVariant | None = None,
|
|
246
|
+
size: ButtonSize | None = None,
|
|
247
|
+
**kwargs: PropertyValue,
|
|
248
|
+
) -> ComponentType:
|
|
249
|
+
"""
|
|
250
|
+
Button that toggles the sidebar with the given ID.
|
|
251
|
+
|
|
252
|
+
Arguments:
|
|
253
|
+
content: Button content. Defaults to `menu_icon`.
|
|
254
|
+
sidebar_id: Id of the sidebar to toggle.
|
|
255
|
+
variant: Button variant.
|
|
256
|
+
size: Button size.
|
|
257
|
+
**kwargs: Other attributes passed directly to the button.
|
|
258
|
+
"""
|
|
259
|
+
if class_ is not None:
|
|
260
|
+
kwargs["class_"] = class_
|
|
261
|
+
if variant is not None:
|
|
262
|
+
kwargs["data_variant"] = variant
|
|
263
|
+
if size is not None:
|
|
264
|
+
kwargs["data_size"] = size
|
|
265
|
+
return html.button(
|
|
266
|
+
content,
|
|
267
|
+
type="button",
|
|
268
|
+
onclick=f"document.getElementById('{sidebar_id}')?.toggle()",
|
|
269
|
+
**kwargs,
|
|
270
|
+
)
|
htmui/basecoat/slider.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from htmy import ComponentType, PropertyValue, SafeStr, XBool, html, join_classes
|
|
2
|
+
|
|
3
|
+
__version__ = "0.2.0"
|
|
4
|
+
__framework__ = "BasecoatUI"
|
|
5
|
+
__framework_version__ = "1"
|
|
6
|
+
__framework_url__ = "https://basecoatui.com/components/slider/"
|
|
7
|
+
|
|
8
|
+
js = SafeStr(
|
|
9
|
+
'<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1/dist/js/range.min.js" defer></script>'
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def slider(
|
|
14
|
+
*,
|
|
15
|
+
start_label: ComponentType | None = None,
|
|
16
|
+
middle_label: ComponentType | None = None,
|
|
17
|
+
end_label: ComponentType | None = None,
|
|
18
|
+
disabled: bool = False,
|
|
19
|
+
class_: str | None = None,
|
|
20
|
+
**kwargs: PropertyValue,
|
|
21
|
+
) -> ComponentType:
|
|
22
|
+
"""
|
|
23
|
+
Range input styled as a slider, with optional labels.
|
|
24
|
+
|
|
25
|
+
Arguments:
|
|
26
|
+
start_label: Optional label shown at the start of a row above the slider.
|
|
27
|
+
middle_label: Optional label shown in the middle of the row above the slider.
|
|
28
|
+
end_label: Optional label shown at the end of the row above the slider.
|
|
29
|
+
disabled: Whether the slider is disabled.
|
|
30
|
+
class_: Extra CSS classes for the input element.
|
|
31
|
+
**kwargs: Extra attributes for the input element.
|
|
32
|
+
"""
|
|
33
|
+
if disabled:
|
|
34
|
+
kwargs["disabled"] = XBool.true
|
|
35
|
+
|
|
36
|
+
input_ = html.input_(type="range", class_=join_classes("input", class_), **kwargs)
|
|
37
|
+
|
|
38
|
+
if start_label is None and middle_label is None and end_label is None:
|
|
39
|
+
return input_
|
|
40
|
+
|
|
41
|
+
return html.div(
|
|
42
|
+
html.div(
|
|
43
|
+
start_label if start_label is not None else html.label(),
|
|
44
|
+
middle_label if middle_label is not None else html.label(),
|
|
45
|
+
end_label if end_label is not None else html.label(),
|
|
46
|
+
class_="flex items-center justify-between gap-2",
|
|
47
|
+
),
|
|
48
|
+
input_,
|
|
49
|
+
class_="grid w-full gap-1",
|
|
50
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from htmy import ComponentType, PropertyValue, SafeStr, html, join_classes
|
|
2
|
+
|
|
3
|
+
__version__ = "0.2.0"
|
|
4
|
+
__framework__ = "BasecoatUI"
|
|
5
|
+
__framework_version__ = "1"
|
|
6
|
+
__framework_url__ = "https://basecoatui.com/components/spinner/"
|
|
7
|
+
|
|
8
|
+
spinner_path = SafeStr('<path d="M21 12a9 9 0 1 1-6.219-8.56" />')
|
|
9
|
+
"""Path for the `loader-circle` icon from https://lucide.dev."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def spinner(
|
|
13
|
+
*children: ComponentType,
|
|
14
|
+
aria_label: str = "Loading",
|
|
15
|
+
class_: str | None = None,
|
|
16
|
+
**kwargs: PropertyValue,
|
|
17
|
+
) -> ComponentType:
|
|
18
|
+
"""
|
|
19
|
+
Animated spinner.
|
|
20
|
+
|
|
21
|
+
Arguments:
|
|
22
|
+
*children: SVG children (typically `<path>` elements).
|
|
23
|
+
Defaults to `spinner_path`.
|
|
24
|
+
aria_label: Accessible label for the spinner.
|
|
25
|
+
class_: Extra CSS classes for the root SVG.
|
|
26
|
+
**kwargs: Extra attributes for the SVG.
|
|
27
|
+
"""
|
|
28
|
+
return html.svg(
|
|
29
|
+
*(children if len(children) > 0 else (spinner_path,)),
|
|
30
|
+
xmlns="http://www.w3.org/2000/svg",
|
|
31
|
+
width="24",
|
|
32
|
+
height="24",
|
|
33
|
+
viewBox="0 0 24 24",
|
|
34
|
+
fill="none",
|
|
35
|
+
stroke="currentColor",
|
|
36
|
+
stroke_width="2",
|
|
37
|
+
stroke_linecap="round",
|
|
38
|
+
stroke_linejoin="round",
|
|
39
|
+
role="status",
|
|
40
|
+
aria_label=aria_label,
|
|
41
|
+
class_=join_classes("animate-spin", class_),
|
|
42
|
+
**kwargs,
|
|
43
|
+
)
|
htmui/basecoat/switch.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from htmy import ComponentType, PropertyValue, XBool, html, join_classes
|
|
4
|
+
|
|
5
|
+
__version__ = "0.2.0"
|
|
6
|
+
__framework__ = "BasecoatUI"
|
|
7
|
+
__framework_version__ = "1"
|
|
8
|
+
__framework_url__ = "https://basecoatui.com/components/switch/"
|
|
9
|
+
|
|
10
|
+
SwitchSize = Literal["sm"]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def switch(
|
|
14
|
+
*,
|
|
15
|
+
name: str,
|
|
16
|
+
checked: bool = False,
|
|
17
|
+
disabled: bool = False,
|
|
18
|
+
invalid: bool = False,
|
|
19
|
+
size: SwitchSize | None = None,
|
|
20
|
+
class_: str | None = None,
|
|
21
|
+
**kwargs: PropertyValue,
|
|
22
|
+
) -> ComponentType:
|
|
23
|
+
"""
|
|
24
|
+
Checkbox input styled as a switch.
|
|
25
|
+
|
|
26
|
+
Arguments:
|
|
27
|
+
name: The name of the input.
|
|
28
|
+
checked: Whether the switch is initially checked.
|
|
29
|
+
disabled: Whether the switch is disabled.
|
|
30
|
+
invalid: Whether to mark the switch as invalid.
|
|
31
|
+
size: Optional size variant.
|
|
32
|
+
class_: Extra CSS classes for the input element.
|
|
33
|
+
**kwargs: Extra attributes for the input element.
|
|
34
|
+
"""
|
|
35
|
+
if checked:
|
|
36
|
+
kwargs["checked"] = XBool.true
|
|
37
|
+
if disabled:
|
|
38
|
+
kwargs["disabled"] = XBool.true
|
|
39
|
+
if invalid:
|
|
40
|
+
kwargs["aria_invalid"] = "true"
|
|
41
|
+
if size is not None:
|
|
42
|
+
kwargs["data_size"] = size
|
|
43
|
+
|
|
44
|
+
return html.input_(
|
|
45
|
+
type="checkbox",
|
|
46
|
+
role="switch",
|
|
47
|
+
name=name,
|
|
48
|
+
class_=join_classes("input", class_),
|
|
49
|
+
**kwargs,
|
|
50
|
+
)
|
htmui/basecoat/table.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from htmy import ComponentType, PropertyValue, html, join_classes
|
|
2
|
+
|
|
3
|
+
__version__ = "0.2.0"
|
|
4
|
+
__framework__ = "BasecoatUI"
|
|
5
|
+
__framework_version__ = "1"
|
|
6
|
+
__framework_url__ = "https://basecoatui.com/components/table/"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def table(
|
|
10
|
+
*children: ComponentType,
|
|
11
|
+
class_: str | None = None,
|
|
12
|
+
**kwargs: PropertyValue,
|
|
13
|
+
) -> ComponentType:
|
|
14
|
+
"""
|
|
15
|
+
Table component.
|
|
16
|
+
|
|
17
|
+
Arguments:
|
|
18
|
+
*children: Table content.
|
|
19
|
+
class_: Extra CSS classes for the table.
|
|
20
|
+
**kwargs: Extra attributes for the table element.
|
|
21
|
+
"""
|
|
22
|
+
return html.table(*children, class_=join_classes("table", class_), **kwargs)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def table_container(
|
|
26
|
+
*children: ComponentType,
|
|
27
|
+
class_: str | None = None,
|
|
28
|
+
**kwargs: PropertyValue,
|
|
29
|
+
) -> ComponentType:
|
|
30
|
+
"""
|
|
31
|
+
Horizontally scrollable wrapper for wide tables.
|
|
32
|
+
|
|
33
|
+
Arguments:
|
|
34
|
+
*children: Container content, usually a single `table`.
|
|
35
|
+
class_: Extra CSS classes for the container.
|
|
36
|
+
**kwargs: Extra attributes for the container.
|
|
37
|
+
"""
|
|
38
|
+
return html.div(*children, class_=join_classes("table-container", class_), **kwargs)
|