eidosui 0.8.0__tar.gz → 0.9.0__tar.gz
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.
- {eidosui-0.8.0 → eidosui-0.9.0}/PKG-INFO +1 -1
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/__init__.py +31 -1
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/components/__init__.py +9 -1
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/components/headers.py +4 -4
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/components/table.py +4 -2
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/components/tabs.py +22 -22
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/css/styles.css +318 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/css/themes/dark.css +3 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/css/themes/eidos-variables.css +53 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/plugins/markdown/extensions/alerts.py +8 -6
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/plugins/markdown/renderer.py +15 -3
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/styles.py +33 -1
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/tags.py +148 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/utils.py +1 -1
- {eidosui-0.8.0 → eidosui-0.9.0}/pyproject.toml +1 -1
- {eidosui-0.8.0 → eidosui-0.9.0}/.gitignore +0 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/LICENSE +0 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/README.md +0 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/components/navigation.py +0 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/css/themes/light.css +0 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/js/eidos.js +0 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/plugins/__init__.py +0 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/plugins/markdown/__init__.py +0 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/plugins/markdown/components.py +0 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/plugins/markdown/css/markdown.css +0 -0
- {eidosui-0.8.0 → eidosui-0.9.0}/eidos/plugins/markdown/extensions/__init__.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: eidosui
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.9.0
|
4
4
|
Summary: A modern, Tailwind CSS-based UI library for air development
|
5
5
|
Project-URL: Homepage, https://github.com/isaac-flath/EidosUI
|
6
6
|
Project-URL: Repository, https://github.com/isaac-flath/EidosUI
|
@@ -44,12 +44,16 @@ from .tags import (
|
|
44
44
|
Button,
|
45
45
|
Canvas,
|
46
46
|
Caption,
|
47
|
+
# Form elements
|
48
|
+
Checkbox,
|
47
49
|
Cite,
|
48
50
|
Code,
|
49
51
|
Col,
|
50
52
|
Colgroup,
|
53
|
+
ColorPicker,
|
51
54
|
Data,
|
52
55
|
Datalist,
|
56
|
+
DatePicker,
|
53
57
|
Dd,
|
54
58
|
Del,
|
55
59
|
Details,
|
@@ -59,12 +63,16 @@ from .tags import (
|
|
59
63
|
Dl,
|
60
64
|
Dt,
|
61
65
|
Em,
|
66
|
+
EmailInput,
|
62
67
|
Embed,
|
63
68
|
Fieldset,
|
64
69
|
Figcaption,
|
65
70
|
Figure,
|
71
|
+
FileInput,
|
66
72
|
Footer,
|
67
73
|
Form,
|
74
|
+
FormError,
|
75
|
+
FormHelp,
|
68
76
|
Head,
|
69
77
|
Header,
|
70
78
|
Hgroup,
|
@@ -88,6 +96,7 @@ from .tags import (
|
|
88
96
|
Meter,
|
89
97
|
Nav,
|
90
98
|
Noscript,
|
99
|
+
NumberInput,
|
91
100
|
Object,
|
92
101
|
Ol,
|
93
102
|
Optgroup,
|
@@ -95,10 +104,12 @@ from .tags import (
|
|
95
104
|
Output,
|
96
105
|
P,
|
97
106
|
Param,
|
107
|
+
PasswordInput,
|
98
108
|
Picture,
|
99
109
|
Pre,
|
100
110
|
Progress,
|
101
111
|
Q,
|
112
|
+
Radio,
|
102
113
|
Rp,
|
103
114
|
Rt,
|
104
115
|
Ruby,
|
@@ -106,6 +117,7 @@ from .tags import (
|
|
106
117
|
Samp,
|
107
118
|
Script,
|
108
119
|
Search,
|
120
|
+
SearchInput,
|
109
121
|
Section,
|
110
122
|
Select,
|
111
123
|
Small,
|
@@ -121,24 +133,27 @@ from .tags import (
|
|
121
133
|
Table,
|
122
134
|
Tbody,
|
123
135
|
Td,
|
136
|
+
TelInput,
|
124
137
|
Template,
|
125
138
|
Textarea,
|
126
139
|
Tfoot,
|
127
140
|
Th,
|
128
141
|
Thead,
|
129
142
|
Time,
|
143
|
+
TimePicker,
|
130
144
|
Title,
|
131
145
|
Tr,
|
132
146
|
Track,
|
133
147
|
U,
|
134
148
|
Ul,
|
149
|
+
UrlInput,
|
135
150
|
Var,
|
136
151
|
Video,
|
137
152
|
Wbr,
|
138
153
|
)
|
139
154
|
|
140
155
|
# Version info
|
141
|
-
__version__ = "0.
|
156
|
+
__version__ = "0.9.0"
|
142
157
|
|
143
158
|
# Define what's available with "from eidos import *"
|
144
159
|
__all__ = [
|
@@ -267,4 +282,19 @@ __all__ = [
|
|
267
282
|
"Ul",
|
268
283
|
"Video",
|
269
284
|
"Wbr",
|
285
|
+
# Form elements
|
286
|
+
"Checkbox",
|
287
|
+
"Radio",
|
288
|
+
"DatePicker",
|
289
|
+
"TimePicker",
|
290
|
+
"ColorPicker",
|
291
|
+
"NumberInput",
|
292
|
+
"EmailInput",
|
293
|
+
"PasswordInput",
|
294
|
+
"SearchInput",
|
295
|
+
"UrlInput",
|
296
|
+
"TelInput",
|
297
|
+
"FileInput",
|
298
|
+
"FormError",
|
299
|
+
"FormHelp",
|
270
300
|
]
|
@@ -8,4 +8,12 @@ from .navigation import NavBar
|
|
8
8
|
from .table import DataTable
|
9
9
|
from .tabs import TabContainer, TabList, TabPanel, Tabs
|
10
10
|
|
11
|
-
__all__ = [
|
11
|
+
__all__ = [
|
12
|
+
"DataTable",
|
13
|
+
"NavBar",
|
14
|
+
"EidosHeaders",
|
15
|
+
"TabContainer",
|
16
|
+
"TabList",
|
17
|
+
"TabPanel",
|
18
|
+
"Tabs",
|
19
|
+
]
|
@@ -1,9 +1,9 @@
|
|
1
1
|
from typing import Literal
|
2
2
|
|
3
|
-
from air import Link, Meta, Script
|
3
|
+
from air import Link, Meta, Script, Tag
|
4
4
|
|
5
5
|
|
6
|
-
def get_css_urls():
|
6
|
+
def get_css_urls() -> list[str]:
|
7
7
|
"""Return list of CSS URLs for EidosUI."""
|
8
8
|
return [
|
9
9
|
"/eidos/css/styles.css",
|
@@ -18,7 +18,7 @@ def EidosHeaders(
|
|
18
18
|
include_lucide: bool = True,
|
19
19
|
include_eidos_js: bool = True,
|
20
20
|
theme: Literal["light", "dark"] = "light",
|
21
|
-
):
|
21
|
+
) -> list[Tag]:
|
22
22
|
"""Complete EidosUI headers with EidosUI JavaScript support.
|
23
23
|
|
24
24
|
Args:
|
@@ -37,7 +37,7 @@ def EidosHeaders(
|
|
37
37
|
headers.append(Script(src="https://cdn.tailwindcss.com"))
|
38
38
|
|
39
39
|
if include_lucide:
|
40
|
-
headers.append(Script(src="https://unpkg.com/lucide@latest
|
40
|
+
headers.append(Script(src="https://unpkg.com/lucide@latest"))
|
41
41
|
|
42
42
|
# EidosUI CSS
|
43
43
|
for css_url in get_css_urls():
|
@@ -1,5 +1,7 @@
|
|
1
1
|
from typing import Any
|
2
2
|
|
3
|
+
from air import Tag
|
4
|
+
|
3
5
|
from ..tags import Table as BaseTable
|
4
6
|
from ..tags import Tbody, Td, Th, Thead, Tr
|
5
7
|
|
@@ -14,7 +16,7 @@ class DataTable:
|
|
14
16
|
headers: list[str] | None = None,
|
15
17
|
class_: str | list[str] | None = None,
|
16
18
|
**kwargs: Any,
|
17
|
-
) ->
|
19
|
+
) -> Tag:
|
18
20
|
"""Create table from list of lists.
|
19
21
|
|
20
22
|
Args:
|
@@ -50,7 +52,7 @@ class DataTable:
|
|
50
52
|
headers: list[str] | None = None,
|
51
53
|
class_: str | list[str] | None = None,
|
52
54
|
**kwargs: Any,
|
53
|
-
) ->
|
55
|
+
) -> Tag:
|
54
56
|
"""Create table from list of dictionaries.
|
55
57
|
|
56
58
|
Args:
|
@@ -1,3 +1,5 @@
|
|
1
|
+
from typing import Any, Literal
|
2
|
+
|
1
3
|
from air import Button, Div, Tag
|
2
4
|
|
3
5
|
from .. import styles
|
@@ -5,22 +7,22 @@ from ..utils import stringify
|
|
5
7
|
|
6
8
|
|
7
9
|
def TabContainer(
|
8
|
-
*content,
|
10
|
+
*content: Tag,
|
9
11
|
initial_tab_url: str,
|
10
12
|
class_: str = "",
|
11
13
|
target_id: str = "tabs",
|
12
|
-
**kwargs,
|
14
|
+
**kwargs: Any,
|
13
15
|
) -> Tag:
|
14
16
|
"""HTMX-based tab container that loads tabs dynamically.
|
15
|
-
|
17
|
+
|
16
18
|
Args:
|
17
19
|
initial_tab_url: URL to load the initial tab content
|
18
20
|
cls: Additional classes for the container
|
19
21
|
target_id: ID for the tab container (default: "tabs")
|
20
|
-
|
22
|
+
|
21
23
|
Returns:
|
22
24
|
Tag: The tab container that will be populated via HTMX
|
23
|
-
|
25
|
+
|
24
26
|
Example:
|
25
27
|
TabContainer("/settings/general")
|
26
28
|
"""
|
@@ -41,21 +43,23 @@ def TabList(
|
|
41
43
|
selected: int = 0,
|
42
44
|
class_: str = "",
|
43
45
|
hx_target: str = "#tabs",
|
44
|
-
hx_swap:
|
45
|
-
|
46
|
+
hx_swap: Literal[
|
47
|
+
"innerHTML", "outerHTML", "beforebegin", "afterbegin", "beforeend", "afterend", "delete", "none"
|
48
|
+
] = "innerHTML",
|
49
|
+
**kwargs: Any,
|
46
50
|
) -> Tag:
|
47
51
|
"""HTMX-based tab list for server-rendered tabs.
|
48
|
-
|
52
|
+
|
49
53
|
Args:
|
50
54
|
*tabs: Variable number of (label, url) tuples
|
51
55
|
selected: Index of the selected tab (0-based)
|
52
56
|
tab_cls: Additional classes for tab buttons
|
53
57
|
hx_target: HTMX target for tab content (default: "#tabs")
|
54
58
|
hx_swap: HTMX swap method (default: "innerHTML")
|
55
|
-
|
59
|
+
|
56
60
|
Returns:
|
57
61
|
Tag: The tab list component
|
58
|
-
|
62
|
+
|
59
63
|
Example:
|
60
64
|
TabList(
|
61
65
|
("General", "/settings/general"),
|
@@ -77,11 +81,7 @@ def TabList(
|
|
77
81
|
role="tab",
|
78
82
|
aria_selected="true" if is_selected else "false",
|
79
83
|
aria_controls="tab-content",
|
80
|
-
class_=stringify(
|
81
|
-
styles.tabs.tab,
|
82
|
-
styles.tabs.tab_active if is_selected else "",
|
83
|
-
class_
|
84
|
-
),
|
84
|
+
class_=stringify(styles.tabs.tab, styles.tabs.tab_active if is_selected else "", class_),
|
85
85
|
)
|
86
86
|
tab_buttons.append(tab_button)
|
87
87
|
|
@@ -96,14 +96,14 @@ def TabList(
|
|
96
96
|
def TabPanel(
|
97
97
|
content: Tag,
|
98
98
|
class_: str = "",
|
99
|
-
**kwargs,
|
99
|
+
**kwargs: Any,
|
100
100
|
) -> Tag:
|
101
101
|
"""Tab panel content wrapper.
|
102
|
-
|
102
|
+
|
103
103
|
Args:
|
104
104
|
content: The content to display in the tab panel
|
105
105
|
panel_cls: Additional classes for the panel
|
106
|
-
|
106
|
+
|
107
107
|
Returns:
|
108
108
|
Tag: The tab panel component
|
109
109
|
"""
|
@@ -120,18 +120,18 @@ def Tabs(
|
|
120
120
|
tab_list: Tag,
|
121
121
|
tab_panel: Tag,
|
122
122
|
cls: str = "",
|
123
|
-
**kwargs,
|
123
|
+
**kwargs: Any,
|
124
124
|
) -> Tag:
|
125
125
|
"""Complete tab component with list and panel.
|
126
|
-
|
126
|
+
|
127
127
|
Args:
|
128
128
|
tab_list: The TabList component
|
129
129
|
tab_panel: The TabPanel component
|
130
130
|
cls: Additional classes for the container
|
131
|
-
|
131
|
+
|
132
132
|
Returns:
|
133
133
|
Tag: The complete tabs component
|
134
|
-
|
134
|
+
|
135
135
|
Example:
|
136
136
|
# In your route handler:
|
137
137
|
tab_list = TabList(
|
@@ -582,4 +582,322 @@
|
|
582
582
|
opacity: 1;
|
583
583
|
transform: translateY(0);
|
584
584
|
transition: opacity var(--transition-normal), transform var(--transition-normal);
|
585
|
+
}
|
586
|
+
|
587
|
+
/* ===========================
|
588
|
+
Form Styles
|
589
|
+
=========================== */
|
590
|
+
|
591
|
+
/* Fieldset */
|
592
|
+
.eidos-fieldset {
|
593
|
+
border: var(--form-fieldset-border);
|
594
|
+
border-radius: var(--form-fieldset-radius);
|
595
|
+
padding: var(--form-fieldset-padding);
|
596
|
+
background-color: var(--form-fieldset-bg);
|
597
|
+
margin-bottom: var(--space-lg);
|
598
|
+
}
|
599
|
+
|
600
|
+
.eidos-fieldset legend {
|
601
|
+
font-size: var(--font-size-lg);
|
602
|
+
font-weight: var(--font-weight-semibold);
|
603
|
+
color: var(--color-text);
|
604
|
+
padding: 0 var(--space-sm);
|
605
|
+
}
|
606
|
+
|
607
|
+
/* Form Group */
|
608
|
+
.eidos-form-group {
|
609
|
+
margin-bottom: var(--space-lg);
|
610
|
+
}
|
611
|
+
|
612
|
+
/* Labels */
|
613
|
+
.eidos-label {
|
614
|
+
display: inline-block;
|
615
|
+
font-size: var(--form-label-font-size);
|
616
|
+
font-weight: var(--form-label-font-weight);
|
617
|
+
color: var(--form-label-color);
|
618
|
+
margin-bottom: var(--form-label-margin-bottom);
|
619
|
+
}
|
620
|
+
|
621
|
+
.eidos-label-inline {
|
622
|
+
display: inline-flex;
|
623
|
+
align-items: center;
|
624
|
+
gap: var(--space-sm);
|
625
|
+
cursor: pointer;
|
626
|
+
margin-bottom: 0;
|
627
|
+
color: var(--form-label-color);
|
628
|
+
font-size: var(--font-size-base);
|
629
|
+
}
|
630
|
+
|
631
|
+
/* Base Input Styles */
|
632
|
+
.eidos-input,
|
633
|
+
.eidos-textarea,
|
634
|
+
.eidos-select {
|
635
|
+
display: block;
|
636
|
+
width: 100%;
|
637
|
+
padding: var(--form-input-padding-y) var(--form-input-padding-x);
|
638
|
+
font-size: var(--font-size-base);
|
639
|
+
line-height: var(--line-height-normal);
|
640
|
+
color: var(--form-input-text);
|
641
|
+
background-color: var(--form-input-bg);
|
642
|
+
border: var(--form-input-border-width) solid var(--form-input-border-color);
|
643
|
+
border-radius: var(--form-input-radius);
|
644
|
+
box-shadow: var(--form-input-shadow);
|
645
|
+
transition: all var(--transition-fast);
|
646
|
+
outline: none;
|
647
|
+
}
|
648
|
+
|
649
|
+
.eidos-input {
|
650
|
+
height: var(--form-input-height);
|
651
|
+
}
|
652
|
+
|
653
|
+
.eidos-textarea {
|
654
|
+
min-height: calc(var(--form-input-height) * 2);
|
655
|
+
resize: vertical;
|
656
|
+
}
|
657
|
+
|
658
|
+
.eidos-select {
|
659
|
+
height: var(--form-input-height);
|
660
|
+
cursor: pointer;
|
661
|
+
padding-right: calc(var(--form-input-padding-x) * 2.5);
|
662
|
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");
|
663
|
+
background-repeat: no-repeat;
|
664
|
+
background-position: right var(--form-input-padding-x) center;
|
665
|
+
background-size: 16px 12px;
|
666
|
+
appearance: none;
|
667
|
+
}
|
668
|
+
|
669
|
+
/* Input States */
|
670
|
+
.eidos-input:hover,
|
671
|
+
.eidos-textarea:hover,
|
672
|
+
.eidos-select:hover {
|
673
|
+
border-color: var(--form-input-border-color-hover);
|
674
|
+
}
|
675
|
+
|
676
|
+
.eidos-input:focus,
|
677
|
+
.eidos-textarea:focus,
|
678
|
+
.eidos-select:focus {
|
679
|
+
border-color: var(--form-input-border-color-focus);
|
680
|
+
box-shadow: var(--form-input-shadow-focus);
|
681
|
+
}
|
682
|
+
|
683
|
+
.eidos-input:disabled,
|
684
|
+
.eidos-textarea:disabled,
|
685
|
+
.eidos-select:disabled {
|
686
|
+
background-color: var(--form-input-bg-disabled);
|
687
|
+
color: var(--form-input-text-disabled);
|
688
|
+
cursor: not-allowed;
|
689
|
+
opacity: var(--opacity-60);
|
690
|
+
}
|
691
|
+
|
692
|
+
.eidos-input::placeholder,
|
693
|
+
.eidos-textarea::placeholder {
|
694
|
+
color: var(--form-input-placeholder);
|
695
|
+
opacity: 1;
|
696
|
+
}
|
697
|
+
|
698
|
+
/* Checkbox and Radio */
|
699
|
+
.eidos-checkbox,
|
700
|
+
.eidos-radio {
|
701
|
+
width: var(--form-checkbox-size);
|
702
|
+
height: var(--form-checkbox-size);
|
703
|
+
margin-right: var(--space-sm);
|
704
|
+
cursor: pointer;
|
705
|
+
flex-shrink: 0;
|
706
|
+
accent-color: var(--color-primary);
|
707
|
+
background-color: var(--form-checkbox-bg);
|
708
|
+
border: var(--form-input-border-width) solid var(--form-checkbox-border);
|
709
|
+
}
|
710
|
+
|
711
|
+
.eidos-checkbox:checked,
|
712
|
+
.eidos-radio:checked {
|
713
|
+
background-color: var(--form-checkbox-bg-checked);
|
714
|
+
border-color: var(--form-checkbox-bg-checked);
|
715
|
+
}
|
716
|
+
|
717
|
+
.eidos-radio {
|
718
|
+
border-radius: var(--radius-full);
|
719
|
+
}
|
720
|
+
|
721
|
+
/* Error States */
|
722
|
+
.eidos-input[aria-invalid="true"],
|
723
|
+
.eidos-textarea[aria-invalid="true"],
|
724
|
+
.eidos-select[aria-invalid="true"] {
|
725
|
+
border-color: var(--form-error-border-color);
|
726
|
+
}
|
727
|
+
|
728
|
+
.eidos-input[aria-invalid="true"]:focus,
|
729
|
+
.eidos-textarea[aria-invalid="true"]:focus,
|
730
|
+
.eidos-select[aria-invalid="true"]:focus {
|
731
|
+
border-color: var(--form-error-border-color);
|
732
|
+
box-shadow: 0 0 0 3px rgba(var(--color-error-rgb), 0.1);
|
733
|
+
}
|
734
|
+
|
735
|
+
/* Error Messages */
|
736
|
+
.eidos-error {
|
737
|
+
display: block;
|
738
|
+
font-size: var(--form-error-font-size);
|
739
|
+
color: var(--form-error-color);
|
740
|
+
margin-top: var(--form-help-margin-top);
|
741
|
+
}
|
742
|
+
|
743
|
+
/* Help Text */
|
744
|
+
.eidos-help {
|
745
|
+
display: block;
|
746
|
+
font-size: var(--form-help-font-size);
|
747
|
+
color: var(--form-help-color);
|
748
|
+
margin-top: var(--form-help-margin-top);
|
749
|
+
}
|
750
|
+
|
751
|
+
/* File Input */
|
752
|
+
.eidos-file {
|
753
|
+
font-size: var(--font-size-sm);
|
754
|
+
cursor: pointer;
|
755
|
+
}
|
756
|
+
|
757
|
+
.eidos-file::file-selector-button {
|
758
|
+
padding: var(--space-xs) var(--space-sm);
|
759
|
+
margin-right: var(--space-sm);
|
760
|
+
font-size: var(--font-size-sm);
|
761
|
+
font-weight: var(--font-weight-medium);
|
762
|
+
color: var(--color-primary);
|
763
|
+
background-color: var(--color-primary-light);
|
764
|
+
border: var(--border) solid var(--color-primary);
|
765
|
+
border-radius: var(--radius-md);
|
766
|
+
cursor: pointer;
|
767
|
+
transition: all var(--transition-fast);
|
768
|
+
}
|
769
|
+
|
770
|
+
.eidos-file::file-selector-button:hover {
|
771
|
+
background-color: var(--color-primary);
|
772
|
+
color: var(--color-primary-foreground);
|
773
|
+
}
|
774
|
+
|
775
|
+
/* Form Layout Helpers */
|
776
|
+
.eidos-form-row {
|
777
|
+
display: flex;
|
778
|
+
gap: var(--space-md);
|
779
|
+
margin-bottom: var(--space-lg);
|
780
|
+
}
|
781
|
+
|
782
|
+
.eidos-form-col {
|
783
|
+
flex: 1;
|
784
|
+
}
|
785
|
+
|
786
|
+
/* Input Groups */
|
787
|
+
.eidos-input-group {
|
788
|
+
display: flex;
|
789
|
+
align-items: stretch;
|
790
|
+
}
|
791
|
+
|
792
|
+
.eidos-input-group .eidos-input {
|
793
|
+
border-radius: 0;
|
794
|
+
}
|
795
|
+
|
796
|
+
.eidos-input-group .eidos-input:first-child {
|
797
|
+
border-top-left-radius: var(--form-input-radius);
|
798
|
+
border-bottom-left-radius: var(--form-input-radius);
|
799
|
+
}
|
800
|
+
|
801
|
+
.eidos-input-group .eidos-input:last-child {
|
802
|
+
border-top-right-radius: var(--form-input-radius);
|
803
|
+
border-bottom-right-radius: var(--form-input-radius);
|
804
|
+
}
|
805
|
+
|
806
|
+
.eidos-input-addon {
|
807
|
+
display: flex;
|
808
|
+
align-items: center;
|
809
|
+
padding: var(--form-input-padding-y) var(--form-input-padding-x);
|
810
|
+
font-size: var(--font-size-base);
|
811
|
+
font-weight: var(--font-weight-normal);
|
812
|
+
color: var(--color-text-muted);
|
813
|
+
background-color: var(--color-surface);
|
814
|
+
border: var(--form-input-border-width) solid var(--form-input-border-color);
|
815
|
+
}
|
816
|
+
|
817
|
+
.eidos-input-addon:first-child {
|
818
|
+
border-right: 0;
|
819
|
+
border-radius: var(--form-input-radius) 0 0 var(--form-input-radius);
|
820
|
+
}
|
821
|
+
|
822
|
+
.eidos-input-addon:last-child {
|
823
|
+
border-left: 0;
|
824
|
+
border-radius: 0 var(--form-input-radius) var(--form-input-radius) 0;
|
825
|
+
}
|
826
|
+
|
827
|
+
|
828
|
+
/* Color Input */
|
829
|
+
.eidos-input[type="color"] {
|
830
|
+
padding: var(--space-xs);
|
831
|
+
cursor: pointer;
|
832
|
+
}
|
833
|
+
|
834
|
+
.eidos-input[type="color"]::-webkit-color-swatch-wrapper {
|
835
|
+
padding: 0;
|
836
|
+
}
|
837
|
+
|
838
|
+
.eidos-input[type="color"]::-webkit-color-swatch {
|
839
|
+
border: none;
|
840
|
+
border-radius: var(--radius-sm);
|
841
|
+
}
|
842
|
+
|
843
|
+
/* Date and Time Input Styling */
|
844
|
+
.eidos-input[type="date"],
|
845
|
+
.eidos-input[type="time"],
|
846
|
+
.eidos-input[type="datetime-local"],
|
847
|
+
.eidos-input[type="month"],
|
848
|
+
.eidos-input[type="week"] {
|
849
|
+
color-scheme: light dark;
|
850
|
+
background-color: var(--form-datetime-bg);
|
851
|
+
color: var(--form-datetime-text);
|
852
|
+
}
|
853
|
+
|
854
|
+
/* Style the calendar/time picker icon */
|
855
|
+
.eidos-input[type="date"]::-webkit-calendar-picker-indicator,
|
856
|
+
.eidos-input[type="time"]::-webkit-calendar-picker-indicator,
|
857
|
+
.eidos-input[type="datetime-local"]::-webkit-calendar-picker-indicator,
|
858
|
+
.eidos-input[type="month"]::-webkit-calendar-picker-indicator,
|
859
|
+
.eidos-input[type="week"]::-webkit-calendar-picker-indicator {
|
860
|
+
cursor: pointer;
|
861
|
+
opacity: 0.8;
|
862
|
+
filter: var(--color-scheme-filter, none);
|
863
|
+
transition: opacity var(--transition-fast);
|
864
|
+
}
|
865
|
+
|
866
|
+
.eidos-input[type="date"]:hover::-webkit-calendar-picker-indicator,
|
867
|
+
.eidos-input[type="time"]:hover::-webkit-calendar-picker-indicator,
|
868
|
+
.eidos-input[type="datetime-local"]:hover::-webkit-calendar-picker-indicator,
|
869
|
+
.eidos-input[type="month"]:hover::-webkit-calendar-picker-indicator,
|
870
|
+
.eidos-input[type="week"]:hover::-webkit-calendar-picker-indicator {
|
871
|
+
opacity: 1;
|
872
|
+
}
|
873
|
+
|
874
|
+
/* Dark theme filter */
|
875
|
+
[data-theme="dark"] .eidos-input[type="date"]::-webkit-calendar-picker-indicator,
|
876
|
+
[data-theme="dark"] .eidos-input[type="time"]::-webkit-calendar-picker-indicator,
|
877
|
+
[data-theme="dark"] .eidos-input[type="datetime-local"]::-webkit-calendar-picker-indicator,
|
878
|
+
[data-theme="dark"] .eidos-input[type="month"]::-webkit-calendar-picker-indicator,
|
879
|
+
[data-theme="dark"] .eidos-input[type="week"]::-webkit-calendar-picker-indicator {
|
880
|
+
filter: invert(1);
|
881
|
+
}
|
882
|
+
|
883
|
+
/* Better Select Dropdown Styling */
|
884
|
+
.eidos-select option {
|
885
|
+
background-color: var(--color-background);
|
886
|
+
color: var(--color-text);
|
887
|
+
padding: var(--space-sm);
|
888
|
+
}
|
889
|
+
|
890
|
+
.eidos-select option:hover,
|
891
|
+
.eidos-select option:focus {
|
892
|
+
background-color: var(--color-surface);
|
893
|
+
}
|
894
|
+
|
895
|
+
.eidos-select option:checked {
|
896
|
+
background-color: var(--color-primary);
|
897
|
+
color: var(--color-primary-foreground);
|
898
|
+
}
|
899
|
+
|
900
|
+
.eidos-select option:disabled {
|
901
|
+
color: var(--color-text-muted);
|
902
|
+
opacity: 0.6;
|
585
903
|
}
|
@@ -74,6 +74,9 @@
|
|
74
74
|
--shadow-xl: 0 0 0 1px rgb(255 255 255 / 0.1), 0 20px 25px -5px rgb(0 0 0 / 0.3);
|
75
75
|
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.5);
|
76
76
|
--shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.3);
|
77
|
+
|
78
|
+
/* Dark mode specific */
|
79
|
+
--invert-icon: 1;
|
77
80
|
}
|
78
81
|
|
79
82
|
/* Dark theme button overrides for better aesthetics */
|
@@ -72,6 +72,8 @@
|
|
72
72
|
--color-accent-rgb: 168, 85, 247;
|
73
73
|
--color-surface-rgb: 248, 250, 252;
|
74
74
|
--color-background-rgb: 255, 255, 255;
|
75
|
+
--color-error-rgb: 220, 38, 38;
|
76
|
+
--color-success-rgb: 16, 185, 129;
|
75
77
|
|
76
78
|
/* Spacing Scale */
|
77
79
|
--space-xs: 0.25rem; /* 4px */
|
@@ -187,4 +189,55 @@
|
|
187
189
|
--breakpoint-lg: 1024px;
|
188
190
|
--breakpoint-xl: 1280px;
|
189
191
|
--breakpoint-2xl: 1536px;
|
192
|
+
|
193
|
+
/* Form-specific Variables */
|
194
|
+
--form-input-height: 2.5rem; /* 40px */
|
195
|
+
--form-input-padding-x: var(--space-md);
|
196
|
+
--form-input-padding-y: var(--space-sm);
|
197
|
+
--form-input-border-width: var(--border);
|
198
|
+
--form-input-border-color: var(--color-border);
|
199
|
+
--form-input-border-color-hover: var(--color-border-hover);
|
200
|
+
--form-input-border-color-focus: var(--color-primary);
|
201
|
+
--form-input-bg: var(--color-input);
|
202
|
+
--form-input-bg-disabled: var(--color-surface);
|
203
|
+
--form-input-text: var(--color-text);
|
204
|
+
--form-input-text-disabled: var(--color-text-muted);
|
205
|
+
--form-input-placeholder: var(--color-text-subtle);
|
206
|
+
--form-input-radius: var(--radius-md);
|
207
|
+
--form-input-shadow: var(--shadow-xs);
|
208
|
+
--form-input-shadow-focus: 0 0 0 3px rgba(var(--color-primary-rgb), 0.1);
|
209
|
+
|
210
|
+
--form-label-font-size: var(--font-size-sm);
|
211
|
+
--form-label-font-weight: var(--font-weight-medium);
|
212
|
+
--form-label-color: var(--color-text);
|
213
|
+
--form-label-margin-bottom: var(--space-xs);
|
214
|
+
|
215
|
+
--form-help-font-size: var(--font-size-sm);
|
216
|
+
--form-help-color: var(--color-text-muted);
|
217
|
+
--form-help-margin-top: var(--space-xs);
|
218
|
+
|
219
|
+
--form-error-color: var(--color-error);
|
220
|
+
--form-error-border-color: var(--color-error);
|
221
|
+
--form-error-bg: var(--color-error-light);
|
222
|
+
--form-error-font-size: var(--font-size-sm);
|
223
|
+
|
224
|
+
--form-fieldset-border: var(--border) solid var(--color-border);
|
225
|
+
--form-fieldset-padding: var(--space-lg);
|
226
|
+
--form-fieldset-radius: var(--radius-lg);
|
227
|
+
--form-fieldset-bg: var(--color-surface);
|
228
|
+
|
229
|
+
--form-checkbox-size: 1.25rem; /* 20px */
|
230
|
+
--form-radio-size: 1.25rem; /* 20px */
|
231
|
+
--form-checkbox-bg: var(--color-background);
|
232
|
+
--form-checkbox-bg-checked: var(--color-primary);
|
233
|
+
--form-checkbox-border: var(--color-border);
|
234
|
+
--form-checkbox-check-color: var(--color-primary-foreground);
|
235
|
+
|
236
|
+
/* Date/Time Picker Variables */
|
237
|
+
--form-datetime-bg: var(--color-background);
|
238
|
+
--form-datetime-text: var(--color-text);
|
239
|
+
--form-datetime-border: var(--color-border);
|
240
|
+
--form-datetime-button-bg: var(--color-surface);
|
241
|
+
--form-datetime-button-hover: var(--color-border);
|
242
|
+
--form-datetime-icon-color: var(--color-text-muted);
|
190
243
|
}
|
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
import re
|
4
4
|
import xml.etree.ElementTree as etree
|
5
|
-
from
|
5
|
+
from re import Pattern
|
6
|
+
from xml.etree.ElementTree import Element, SubElement
|
6
7
|
|
8
|
+
from markdown import Markdown
|
7
9
|
from markdown.blockprocessors import BlockProcessor
|
8
10
|
from markdown.extensions import Extension
|
9
11
|
|
@@ -12,10 +14,10 @@ class AlertBlockProcessor(BlockProcessor):
|
|
12
14
|
"""Process GitHub-style alert blocks"""
|
13
15
|
|
14
16
|
# Pattern to match > [!TYPE] at the start of a blockquote
|
15
|
-
RE_ALERT = re.compile(r"^> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]", re.MULTILINE)
|
17
|
+
RE_ALERT: Pattern[str] = re.compile(r"^> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]", re.MULTILINE)
|
16
18
|
|
17
19
|
# Alert type configurations
|
18
|
-
ALERT_TYPES = {
|
20
|
+
ALERT_TYPES: dict[str, dict[str, str]] = {
|
19
21
|
"NOTE": {"class": "eidos-alert eidos-alert-info", "icon": "ℹ️", "title": "Note"},
|
20
22
|
"TIP": {
|
21
23
|
"class": "eidos-alert eidos-alert-success",
|
@@ -39,11 +41,11 @@ class AlertBlockProcessor(BlockProcessor):
|
|
39
41
|
},
|
40
42
|
}
|
41
43
|
|
42
|
-
def test(self, parent, block):
|
44
|
+
def test(self, parent: Element, block: str) -> bool:
|
43
45
|
"""Test if the block is a GitHub-style alert"""
|
44
46
|
return bool(self.RE_ALERT.match(block))
|
45
47
|
|
46
|
-
def run(self, parent, blocks):
|
48
|
+
def run(self, parent: Element, blocks: list[str]) -> bool:
|
47
49
|
"""Process the alert block"""
|
48
50
|
block = blocks.pop(0)
|
49
51
|
|
@@ -115,7 +117,7 @@ class AlertBlockProcessor(BlockProcessor):
|
|
115
117
|
class AlertExtension(Extension):
|
116
118
|
"""Add GitHub-style alerts to markdown"""
|
117
119
|
|
118
|
-
def extendMarkdown(self, md):
|
120
|
+
def extendMarkdown(self, md: Markdown) -> None:
|
119
121
|
"""Add the alert processor to the markdown instance"""
|
120
122
|
md.parser.blockprocessors.register(
|
121
123
|
AlertBlockProcessor(md.parser),
|
@@ -6,7 +6,16 @@ from .extensions.alerts import AlertExtension
|
|
6
6
|
|
7
7
|
|
8
8
|
class MarkdownRenderer:
|
9
|
-
"""Core markdown rendering with theme integration
|
9
|
+
"""Core markdown rendering with theme integration.
|
10
|
+
|
11
|
+
Warning:
|
12
|
+
This renderer outputs raw HTML without sanitization to support advanced
|
13
|
+
features like forms, embeds, and custom styling. Never use with untrusted
|
14
|
+
user content without additional sanitization.
|
15
|
+
"""
|
16
|
+
|
17
|
+
extensions: list[str | markdown.Extension]
|
18
|
+
md: markdown.Markdown
|
10
19
|
|
11
20
|
def __init__(self, extensions: list[str | markdown.Extension] | None = None):
|
12
21
|
"""Initialize the renderer with optional extensions.
|
@@ -36,13 +45,16 @@ class MarkdownRenderer:
|
|
36
45
|
Returns:
|
37
46
|
HTML string wrapped with eidos-md class for styling
|
38
47
|
"""
|
39
|
-
|
48
|
+
# Reset markdown processor state to prevent contamination between renders
|
49
|
+
# This is required by Python-Markdown when reusing instances, especially
|
50
|
+
# with stateful extensions like footnotes or custom parsers
|
51
|
+
self.md.reset()
|
40
52
|
|
41
53
|
html_content = self.md.convert(markdown_text)
|
42
54
|
|
43
55
|
return f'<div class="eidos-md">{html_content}</div>'
|
44
56
|
|
45
|
-
def add_extension(self, extension: str) -> None:
|
57
|
+
def add_extension(self, extension: str | markdown.Extension) -> None:
|
46
58
|
"""Add a markdown extension.
|
47
59
|
|
48
60
|
Args:
|
@@ -39,7 +39,6 @@ class Typography:
|
|
39
39
|
h5: Final[str] = "eidos-h5"
|
40
40
|
h6: Final[str] = "eidos-h6"
|
41
41
|
|
42
|
-
|
43
42
|
# Text formatting
|
44
43
|
strong: Final[str] = "eidos-strong"
|
45
44
|
i: Final[str] = "eidos-i"
|
@@ -119,9 +118,42 @@ class Tabs:
|
|
119
118
|
panel_active: Final[str] = "eidos-tab-panel-active"
|
120
119
|
|
121
120
|
|
121
|
+
class Forms:
|
122
|
+
"""Form-related CSS classes from styles.css."""
|
123
|
+
|
124
|
+
# Container elements
|
125
|
+
fieldset: Final[str] = "eidos-fieldset"
|
126
|
+
form_group: Final[str] = "eidos-form-group"
|
127
|
+
|
128
|
+
# Labels
|
129
|
+
label: Final[str] = "eidos-label"
|
130
|
+
label_inline: Final[str] = "eidos-label-inline"
|
131
|
+
|
132
|
+
# Input elements
|
133
|
+
input: Final[str] = "eidos-input"
|
134
|
+
textarea: Final[str] = "eidos-textarea"
|
135
|
+
select: Final[str] = "eidos-select"
|
136
|
+
checkbox: Final[str] = "eidos-checkbox"
|
137
|
+
radio: Final[str] = "eidos-radio"
|
138
|
+
file: Final[str] = "eidos-file"
|
139
|
+
|
140
|
+
# Feedback elements
|
141
|
+
error: Final[str] = "eidos-error"
|
142
|
+
help: Final[str] = "eidos-help"
|
143
|
+
|
144
|
+
# Layout helpers
|
145
|
+
form_row: Final[str] = "eidos-form-row"
|
146
|
+
form_col: Final[str] = "eidos-form-col"
|
147
|
+
|
148
|
+
# Input groups
|
149
|
+
input_group: Final[str] = "eidos-input-group"
|
150
|
+
input_addon: Final[str] = "eidos-input-addon"
|
151
|
+
|
152
|
+
|
122
153
|
# Create singleton instance for easy access
|
123
154
|
buttons = Buttons()
|
124
155
|
typography = Typography()
|
125
156
|
tables = Tables()
|
126
157
|
lists = Lists()
|
127
158
|
tabs = Tabs()
|
159
|
+
forms = Forms()
|
@@ -2,6 +2,7 @@ from typing import Any
|
|
2
2
|
|
3
3
|
import air
|
4
4
|
from air.tags import *
|
5
|
+
|
5
6
|
from . import styles
|
6
7
|
from .utils import stringify
|
7
8
|
|
@@ -247,6 +248,153 @@ def Li(*content: Any, class_: str | list[str] | None = None, **kwargs: Any) -> a
|
|
247
248
|
return air.Li(*content, class_=stringify(styles.lists.li, class_), **kwargs)
|
248
249
|
|
249
250
|
|
251
|
+
# Form elements with default styling
|
252
|
+
def Fieldset(*content: Any, class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
253
|
+
"""Styled fieldset element."""
|
254
|
+
return air.Fieldset(*content, class_=stringify(styles.forms.fieldset, class_), **kwargs)
|
255
|
+
|
256
|
+
|
257
|
+
def Label(*content: Any, class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
258
|
+
"""Styled label element."""
|
259
|
+
return air.Label(*content, class_=stringify(styles.forms.label, class_), **kwargs)
|
260
|
+
|
261
|
+
|
262
|
+
def Input(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
263
|
+
"""Styled input element."""
|
264
|
+
input_type = kwargs.get("type", "text")
|
265
|
+
|
266
|
+
# Apply appropriate class based on input type
|
267
|
+
if input_type == "checkbox":
|
268
|
+
default_class = styles.forms.checkbox
|
269
|
+
elif input_type == "radio":
|
270
|
+
default_class = styles.forms.radio
|
271
|
+
elif input_type == "file":
|
272
|
+
default_class = styles.forms.file
|
273
|
+
else:
|
274
|
+
default_class = styles.forms.input
|
275
|
+
|
276
|
+
return air.Input(class_=stringify(default_class, class_), **kwargs)
|
277
|
+
|
278
|
+
|
279
|
+
def Textarea(*content: Any, class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
280
|
+
"""Styled textarea element."""
|
281
|
+
return air.Textarea(*content, class_=stringify(styles.forms.textarea, class_), **kwargs)
|
282
|
+
|
283
|
+
|
284
|
+
def Select(*content: Any, class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
285
|
+
"""Styled select element."""
|
286
|
+
return air.Select(*content, class_=stringify(styles.forms.select, class_), **kwargs)
|
287
|
+
|
288
|
+
|
289
|
+
def Option(*content: Any, **kwargs: Any) -> air.Tag:
|
290
|
+
"""Option element."""
|
291
|
+
return air.Option(*content, **kwargs)
|
292
|
+
|
293
|
+
|
294
|
+
def DatePicker(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
295
|
+
"""Styled date input."""
|
296
|
+
return air.Input(type="date", class_=stringify(styles.forms.input, class_), **kwargs)
|
297
|
+
|
298
|
+
|
299
|
+
def TimePicker(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
300
|
+
"""Styled time input."""
|
301
|
+
return air.Input(type="time", class_=stringify(styles.forms.input, class_), **kwargs)
|
302
|
+
|
303
|
+
|
304
|
+
def ColorPicker(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
305
|
+
"""Styled color input."""
|
306
|
+
return air.Input(type="color", class_=stringify(styles.forms.input, class_), **kwargs)
|
307
|
+
|
308
|
+
|
309
|
+
def NumberInput(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
310
|
+
"""Styled number input."""
|
311
|
+
return air.Input(type="number", class_=stringify(styles.forms.input, class_), **kwargs)
|
312
|
+
|
313
|
+
|
314
|
+
def EmailInput(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
315
|
+
"""Styled email input."""
|
316
|
+
return air.Input(type="email", class_=stringify(styles.forms.input, class_), **kwargs)
|
317
|
+
|
318
|
+
|
319
|
+
def PasswordInput(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
320
|
+
"""Styled password input."""
|
321
|
+
return air.Input(type="password", class_=stringify(styles.forms.input, class_), **kwargs)
|
322
|
+
|
323
|
+
|
324
|
+
def SearchInput(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
325
|
+
"""Styled search input."""
|
326
|
+
return air.Input(type="search", class_=stringify(styles.forms.input, class_), **kwargs)
|
327
|
+
|
328
|
+
|
329
|
+
def UrlInput(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
330
|
+
"""Styled URL input."""
|
331
|
+
return air.Input(type="url", class_=stringify(styles.forms.input, class_), **kwargs)
|
332
|
+
|
333
|
+
|
334
|
+
def TelInput(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
335
|
+
"""Styled telephone input."""
|
336
|
+
return air.Input(type="tel", class_=stringify(styles.forms.input, class_), **kwargs)
|
337
|
+
|
338
|
+
|
339
|
+
def Checkbox(
|
340
|
+
name: str | None = None, label: str | None = None, class_: str | list[str] | None = None, **kwargs: Any
|
341
|
+
) -> air.Tag:
|
342
|
+
"""Styled checkbox input with optional label."""
|
343
|
+
# Generate ID if not provided
|
344
|
+
input_id = kwargs.get("id", f"{name}-{kwargs.get('value', 'checkbox')}".replace(" ", "-") if name else None)
|
345
|
+
|
346
|
+
# Create checkbox
|
347
|
+
checkbox = air.Input(type="checkbox", name=name, id=input_id, class_=stringify(styles.forms.checkbox), **kwargs)
|
348
|
+
|
349
|
+
if label:
|
350
|
+
# Wrap in label
|
351
|
+
return Label(checkbox, label, for_=input_id, class_=stringify(styles.forms.label_inline, class_))
|
352
|
+
else:
|
353
|
+
# Apply additional classes if provided
|
354
|
+
if class_:
|
355
|
+
checkbox = air.Input(
|
356
|
+
type="checkbox", name=name, id=input_id, class_=stringify(styles.forms.checkbox, class_), **kwargs
|
357
|
+
)
|
358
|
+
return checkbox
|
359
|
+
|
360
|
+
|
361
|
+
def Radio(
|
362
|
+
name: str | None = None, label: str | None = None, class_: str | list[str] | None = None, **kwargs: Any
|
363
|
+
) -> air.Tag:
|
364
|
+
"""Styled radio input with optional label."""
|
365
|
+
# Generate ID if not provided
|
366
|
+
input_id = kwargs.get("id", f"{name}-{kwargs.get('value', 'radio')}".replace(" ", "-") if name else None)
|
367
|
+
|
368
|
+
# Create radio
|
369
|
+
radio = air.Input(type="radio", name=name, id=input_id, class_=stringify(styles.forms.radio), **kwargs)
|
370
|
+
|
371
|
+
if label:
|
372
|
+
# Wrap in label
|
373
|
+
return Label(radio, label, for_=input_id, class_=stringify(styles.forms.label_inline, class_))
|
374
|
+
else:
|
375
|
+
# Apply additional classes if provided
|
376
|
+
if class_:
|
377
|
+
radio = air.Input(
|
378
|
+
type="radio", name=name, id=input_id, class_=stringify(styles.forms.radio, class_), **kwargs
|
379
|
+
)
|
380
|
+
return radio
|
381
|
+
|
382
|
+
|
383
|
+
def FileInput(class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
384
|
+
"""Styled file input."""
|
385
|
+
return air.Input(type="file", class_=stringify(styles.forms.file, class_), **kwargs)
|
386
|
+
|
387
|
+
|
388
|
+
# Helper form elements
|
389
|
+
def FormError(text: str, class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
390
|
+
"""Styled error message."""
|
391
|
+
return Small(text, class_=stringify(styles.forms.error, class_), **kwargs)
|
392
|
+
|
393
|
+
|
394
|
+
def FormHelp(text: str, class_: str | list[str] | None = None, **kwargs: Any) -> air.Tag:
|
395
|
+
"""Styled help text."""
|
396
|
+
return Small(text, class_=stringify(styles.forms.help, class_), **kwargs)
|
397
|
+
|
250
398
|
|
251
399
|
# Pass-through tags from air.tags
|
252
400
|
# Import all standard HTML tags that don't have custom styling
|
@@ -37,7 +37,7 @@ def stringify(*classes: str | list[str] | None) -> str:
|
|
37
37
|
return " ".join(result)
|
38
38
|
|
39
39
|
|
40
|
-
def get_eidos_static_files(markdown: bool = False) -> dict:
|
40
|
+
def get_eidos_static_files(markdown: bool = False) -> dict[str, str]:
|
41
41
|
"""
|
42
42
|
Get a dictionary mapping URL paths to static file directories.
|
43
43
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|