tealui 0.3.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.
- tealui-0.3.0/LICENSE +21 -0
- tealui-0.3.0/PKG-INFO +257 -0
- tealui-0.3.0/README.md +238 -0
- tealui-0.3.0/pyproject.toml +33 -0
- tealui-0.3.0/setup.cfg +4 -0
- tealui-0.3.0/tealui/__init__.py +43 -0
- tealui-0.3.0/tealui/__main__.py +4 -0
- tealui-0.3.0/tealui/cli.py +173 -0
- tealui-0.3.0/tealui/core.py +113 -0
- tealui-0.3.0/tealui/devtools.py +126 -0
- tealui-0.3.0/tealui/fonts/LICENSES.txt +287 -0
- tealui-0.3.0/tealui/fonts/inter.woff2 +0 -0
- tealui-0.3.0/tealui/fonts/mono.woff2 +0 -0
- tealui-0.3.0/tealui/fonts/serif-italic.woff2 +0 -0
- tealui-0.3.0/tealui/fonts/serif.woff2 +0 -0
- tealui-0.3.0/tealui/gallery.py +81 -0
- tealui-0.3.0/tealui/icons-LICENSE.txt +43 -0
- tealui-0.3.0/tealui/icons.py +153 -0
- tealui-0.3.0/tealui/look.py +153 -0
- tealui-0.3.0/tealui/lookcli.py +217 -0
- tealui-0.3.0/tealui/looks/__init__.py +7 -0
- tealui-0.3.0/tealui/looks/accents.py +18 -0
- tealui-0.3.0/tealui/looks/full.py +163 -0
- tealui-0.3.0/tealui/looks/paper.py +23 -0
- tealui-0.3.0/tealui/looks/teal.py +352 -0
- tealui-0.3.0/tealui/page.py +87 -0
- tealui-0.3.0/tealui/py.typed +0 -0
- tealui-0.3.0/tealui/tags.py +112 -0
- tealui-0.3.0/tealui/theme.py +184 -0
- tealui-0.3.0/tealui/toolchain.py +246 -0
- tealui-0.3.0/tealui/ui/__init__.py +1 -0
- tealui-0.3.0/tealui/ui/article.py +103 -0
- tealui-0.3.0/tealui/ui/charts.py +111 -0
- tealui-0.3.0/tealui/ui/content.py +76 -0
- tealui-0.3.0/tealui/ui/data.py +96 -0
- tealui-0.3.0/tealui/ui/forms.py +92 -0
- tealui-0.3.0/tealui/ui/forms2.py +150 -0
- tealui-0.3.0/tealui/ui/foundation.py +66 -0
- tealui-0.3.0/tealui/ui/mobile.py +58 -0
- tealui-0.3.0/tealui/ui/nav.py +58 -0
- tealui-0.3.0/tealui/ui/overlay.py +93 -0
- tealui-0.3.0/tealui/ui/shell.py +85 -0
- tealui-0.3.0/tealui/versions.py +7 -0
- tealui-0.3.0/tealui.egg-info/PKG-INFO +257 -0
- tealui-0.3.0/tealui.egg-info/SOURCES.txt +79 -0
- tealui-0.3.0/tealui.egg-info/dependency_links.txt +1 -0
- tealui-0.3.0/tealui.egg-info/entry_points.txt +2 -0
- tealui-0.3.0/tealui.egg-info/top_level.txt +1 -0
- tealui-0.3.0/tests/test_article.py +100 -0
- tealui-0.3.0/tests/test_blog.py +112 -0
- tealui-0.3.0/tests/test_charts.py +192 -0
- tealui-0.3.0/tests/test_cli.py +338 -0
- tealui-0.3.0/tests/test_content.py +157 -0
- tealui-0.3.0/tests/test_contrast.py +50 -0
- tealui-0.3.0/tests/test_core.py +136 -0
- tealui-0.3.0/tests/test_data.py +202 -0
- tealui-0.3.0/tests/test_devtools.py +264 -0
- tealui-0.3.0/tests/test_docs_examples.py +48 -0
- tealui-0.3.0/tests/test_fixes_021.py +185 -0
- tealui-0.3.0/tests/test_forms.py +76 -0
- tealui-0.3.0/tests/test_forms2.py +190 -0
- tealui-0.3.0/tests/test_forms3.py +143 -0
- tealui-0.3.0/tests/test_foundation.py +123 -0
- tealui-0.3.0/tests/test_hooks.py +122 -0
- tealui-0.3.0/tests/test_icons.py +83 -0
- tealui-0.3.0/tests/test_look.py +134 -0
- tealui-0.3.0/tests/test_lookcli.py +164 -0
- tealui-0.3.0/tests/test_looks.py +27 -0
- tealui-0.3.0/tests/test_looks_030.py +167 -0
- tealui-0.3.0/tests/test_mobile.py +144 -0
- tealui-0.3.0/tests/test_nav.py +158 -0
- tealui-0.3.0/tests/test_overlay.py +66 -0
- tealui-0.3.0/tests/test_overlay2.py +126 -0
- tealui-0.3.0/tests/test_page.py +113 -0
- tealui-0.3.0/tests/test_rtl.py +100 -0
- tealui-0.3.0/tests/test_shell.py +126 -0
- tealui-0.3.0/tests/test_spy_trap.py +110 -0
- tealui-0.3.0/tests/test_tags.py +51 -0
- tealui-0.3.0/tests/test_theme.py +45 -0
- tealui-0.3.0/tests/test_toolchain.py +440 -0
- tealui-0.3.0/tests/test_versions.py +18 -0
tealui-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TealUI contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tealui-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tealui
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: A tiny, clean, pythonic UI library that renders plain HTML with Tailwind and Alpine.
|
|
5
|
+
Author: octalynx
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: html,ui,tailwind,alpine,components,themes,flask,fastapi,django
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# TealUI
|
|
21
|
+
|
|
22
|
+
Write a web UI in Python. The output is plain HTML, styled with Tailwind CSS and made interactive with Alpine.js. It works with any backend, because everything renders to a string.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
TealUI is not on PyPI yet. From a checkout:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Python 3.9+. No dependencies.
|
|
33
|
+
|
|
34
|
+
## Hello world
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from tealui import Page, Card, Stat, Button, Div, H1
|
|
38
|
+
|
|
39
|
+
page = Page(
|
|
40
|
+
"Hello, TealUI",
|
|
41
|
+
Div(
|
|
42
|
+
H1("Hello, TealUI", cls="text-2xl font-semibold tracking-tight"),
|
|
43
|
+
Stat("Users", "1,204"),
|
|
44
|
+
Card(Button("Get started")),
|
|
45
|
+
cls="mx-auto max-w-xl space-y-6 p-8",
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
with open("index.html", "w", encoding="utf-8") as f:
|
|
50
|
+
f.write(str(page))
|
|
51
|
+
print("Wrote index.html. Open it in a browser.")
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Plain tags such as `H1` have no default styling (Tailwind resets them). Style them with `cls=`, or use components such as `PageHeader` and `Prose`.
|
|
55
|
+
|
|
56
|
+
With no `css=` argument, TealUI uses the Tailwind CDN build, so this page needs the internet.
|
|
57
|
+
|
|
58
|
+
## Production CSS
|
|
59
|
+
|
|
60
|
+
Tailwind only generates the classes it finds in scanned files, so give `--src` every folder where you write your own class strings (in Python or HTML); repeat `--src` for several folders. Without `--src` the current folder is scanned. Only the folders you name are scanned, nothing else.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
tealui build static/tealui.css --src app/
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The first run downloads the standalone Tailwind CLI (about 100 MB, checksum verified) into `~/.cache/tealui` (needs internet once); later runs work offline. `TEALUI_CACHE` or `--cache` moves the cache. A `fonts/` folder is copied next to the CSS file — keep it there. Installed look packages are included automatically; add a look that lives in your project with `--look looks.py:OCEAN`. `--alpine static/alpine.js` self-hosts Alpine (then `Page(alpine="/static/alpine.js")`); `--htmx static/htmx.js` does the same for HTMX (then `Page(htmx="/static/htmx.js")`). `tealui css` only writes the input file and fonts, for people who run their own Tailwind setup.
|
|
67
|
+
|
|
68
|
+
Then link the stylesheet:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from tealui import Page, Div
|
|
72
|
+
|
|
73
|
+
page = Page("Home", Div("hello"), css="/static/tealui.css")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Versions
|
|
77
|
+
|
|
78
|
+
TealUI follows the latest Alpine 3.x, HTMX 2.x and Tailwind 4.x. Dev mode loads them from jsdelivr by major version. A new major version is adopted only after testing. `tealui update` refreshes the cached tools, `tealui versions` shows installed vs latest.
|
|
79
|
+
|
|
80
|
+
## Looks
|
|
81
|
+
|
|
82
|
+
A look is the theme: colors, radii, shadows and fonts, in light and dark, plus the class recipes components use. Fourteen are built in:
|
|
83
|
+
|
|
84
|
+
| Look | Kind | Character |
|
|
85
|
+
| --- | --- | --- |
|
|
86
|
+
| `TEAL` | default | Product look, teal accent |
|
|
87
|
+
| `PAPER` | full | Editorial: serif headings, warm paper, rust accent |
|
|
88
|
+
| `SLATE` | full | Cool grey, graphite accent |
|
|
89
|
+
| `MIDNIGHT` | full, dark-first | Blue-black with an indigo glow |
|
|
90
|
+
| `LEDGER` | full | Navy, dense and flat (32px controls, 40px rows) |
|
|
91
|
+
| `CONTRAST` | full | Black and white, WCAG AAA (7:1) |
|
|
92
|
+
| `NORDIC` | full | Arctic blue-grey, muted status colors |
|
|
93
|
+
| `SOFT` | full | Pastel lilac, large radii |
|
|
94
|
+
| `TERMINAL` | full, dark-first | Monospace, green on black, square |
|
|
95
|
+
| `BRUTAL` | full | Neo-brutalist: black borders, hard shadows |
|
|
96
|
+
| `INDIGO`, `ENTERPRISE`, `FOREST`, `SUNSET` | accent swap | TEAL with an indigo, corporate blue, deep green or rose accent |
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
from tealui import use, using, with_accent, TEAL, PAPER, SLATE, Page, Div
|
|
100
|
+
|
|
101
|
+
use(SLATE) # change the default look
|
|
102
|
+
use(with_accent(TEAL, "#7c3aed")) # or derive a custom accent from one hex
|
|
103
|
+
|
|
104
|
+
with using(PAPER): # or only for one render
|
|
105
|
+
html = str(Page("Report", Div("hello")))
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The dark-mode choice is saved in the browser (`localStorage`). Dark-first looks start dark until the visitor picks light. `tealui looks` lists every look; `tealui look preview slate gallery.html` writes a page with every component in a look.
|
|
109
|
+
|
|
110
|
+
### Your own look
|
|
111
|
+
|
|
112
|
+
`make_look` copies TEAL (or `base=`) with your changes and checks the result: every variable and recipe present, no unsafe values, and contrast (4.5:1 by default) in light and dark.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from tealui import make_look, use
|
|
116
|
+
|
|
117
|
+
OCEAN = make_look(
|
|
118
|
+
"ocean",
|
|
119
|
+
vars={"accent": "#0e7490", "accent-hover": "#155e75", "accent-ink": "#ffffff",
|
|
120
|
+
"accent-soft": "#e0f5f9", "accent-line": "#a5dbe6", "r-card": "16px"},
|
|
121
|
+
dark={"accent": "#67e8f9", "accent-hover": "#a5f3fc", "accent-ink": "#062a30",
|
|
122
|
+
"accent-soft": "#0c2c33", "accent-line": "#1c5a66"},
|
|
123
|
+
recipes={"button": {"md": "h-10 px-4"}},
|
|
124
|
+
)
|
|
125
|
+
use(OCEAN)
|
|
126
|
+
print(OCEAN["name"])
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`tealui look new ocean` writes a starter file, `tealui look check ocean.py:OCEAN` checks it, and `--package` adds a `pyproject.toml` to publish it as `tealui-look-ocean`. Installed look packages are found by `tealui build` automatically; a look kept in your project is added with `tealui build ... --look looks.py:OCEAN`. The full guide is `docs/looks.md` in the source distribution.
|
|
130
|
+
|
|
131
|
+
## Fonts and icons
|
|
132
|
+
|
|
133
|
+
Override a look's fonts with `with_fonts`, then register the actual font file with a `<style>` tag in the head.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from tealui import use, with_fonts, TEAL, Page, Div, Style, Raw
|
|
137
|
+
|
|
138
|
+
use(with_fonts(TEAL, head='"MyFont", serif', body='"MyFont", sans-serif'))
|
|
139
|
+
page = Page("Font demo", Div("hello"),
|
|
140
|
+
head=[Style(Raw('@font-face{font-family:"MyFont";src:url("/static/myfont.woff2") format("woff2")}'))])
|
|
141
|
+
print(len(str(page)))
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Add your own icons with `register_icon`; the SVG markup is trusted and rendered as-is.
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from tealui import register_icon, Icon
|
|
148
|
+
|
|
149
|
+
register_icon("spark", '<path d="M12 2l2.4 6.6L21 11l-6.6 2.4L12 20l-2.4-6.6L3 11l6.6-2.4z"/>')
|
|
150
|
+
print(Icon("spark"))
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Interactivity
|
|
154
|
+
|
|
155
|
+
Pages for right-to-left languages mirror by themselves: `Page(..., lang="ar")` sets `dir="rtl"`, and every look uses logical classes (`ms-`, `ps-`, `start-`). Server-rendered fragments work with HTMX: any `hx_*` keyword becomes an `hx-*` attribute (`Button("Load", hx_get="/rows", hx_target="#rows")`) and `Page(..., htmx=True)` loads the library.
|
|
156
|
+
|
|
157
|
+
Components are Alpine-powered, and you can always write your own JS:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from tealui import Page, Div, Span, Button
|
|
161
|
+
|
|
162
|
+
page = Page(
|
|
163
|
+
"Counter",
|
|
164
|
+
Div(
|
|
165
|
+
Span(x_text="n"),
|
|
166
|
+
Button("+1", on_click="n++"),
|
|
167
|
+
x_data="counter",
|
|
168
|
+
),
|
|
169
|
+
js="Alpine.data('counter', () => ({ n: 0 }))",
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Add extra tags to the `<head>`:
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
from tealui import Page, Div, Link, Script
|
|
177
|
+
|
|
178
|
+
page = Page(
|
|
179
|
+
"Head example",
|
|
180
|
+
Div("hi"),
|
|
181
|
+
head=[Link(rel="icon", href="/favicon.ico"),
|
|
182
|
+
Script(src="/static/extra.js", defer=True)],
|
|
183
|
+
)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Escape hatches
|
|
187
|
+
|
|
188
|
+
Any tag, any attribute:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from tealui import Div, El, Raw
|
|
192
|
+
|
|
193
|
+
print(Div(El("custom-thing", "hello", any_attr="x")))
|
|
194
|
+
print(Div(Raw("<svg viewBox='0 0 10 10'><circle cx='5' cy='5' r='4'/></svg>")))
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Attribute naming rule: `cls` becomes `class`; `_` becomes `-`; `on_x` becomes `@x`; `bind_x` becomes `:x`; `__` becomes `.`. For example `on_click` -> `@click`, `bind_class` -> `:class`, `x_model__number` -> `x-model.number`. Text is escaped by default; only `Raw` is trusted.
|
|
198
|
+
|
|
199
|
+
## Components
|
|
200
|
+
|
|
201
|
+
- Layout: `Page`, `Shell`, `Sidebar`, `Topbar`, `SiteHeader`, `PageHeader`, `Card`, `Prose`, `DocsNav`, `Toc`, `ReadingProgress`
|
|
202
|
+
- Data: `Stat`, `Delta`, `Table`, `BarList`, `Sparkline`, `AreaChart`, `Progress`, `Badge`, `Avatar`, `Kbd`
|
|
203
|
+
- Forms: `Button`, `TextInput`, `TextBox`, `SelectBox`, `Checkbox`, `Switch`, `RadioGroup`, `Field`, `Segmented`, `SearchBox`, `TagFilter`, `tag_show`
|
|
204
|
+
- Overlays & feedback: `Modal`, `Dropdown`, `Toaster`, `toast_js`, `Callout`, `Tabs`
|
|
205
|
+
- Blog & nav: `PostCard`, `Pager`, `ArticleHeader`, `ShareBar`, `Breadcrumb`, `NavItem`, `DarkToggle`, `Icon`
|
|
206
|
+
|
|
207
|
+
A tag filter needs one wrapper that holds the `x-data` for both the filter and the posts:
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from tealui import Div, TagFilter, PostCard, tag_show
|
|
211
|
+
|
|
212
|
+
Div(
|
|
213
|
+
TagFilter(["python", "css"]),
|
|
214
|
+
PostCard("A", href="/a", tag="python", x_show=tag_show("python")),
|
|
215
|
+
PostCard("B", href="/b", tag="css", x_show=tag_show("css")),
|
|
216
|
+
x_data="{ tag: '' }",
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Rules of the road
|
|
221
|
+
|
|
222
|
+
- `cls=` is added last, so your classes win.
|
|
223
|
+
- Extra keyword arguments land on the root element (on the control itself for form controls).
|
|
224
|
+
- The tag helpers `Input`, `Select`, `Textarea`, `Label`, `Option` and `Form` already exist, so the components are named `TextInput`, `SelectBox`, and so on.
|
|
225
|
+
|
|
226
|
+
## Serve it
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
230
|
+
from tealui import Page, Div, H1
|
|
231
|
+
|
|
232
|
+
class Handler(BaseHTTPRequestHandler):
|
|
233
|
+
def do_GET(self):
|
|
234
|
+
body = str(Page("Home", Div(H1("Served by stdlib"), cls="p-8")))
|
|
235
|
+
data = body.encode("utf-8")
|
|
236
|
+
self.send_response(200)
|
|
237
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
238
|
+
self.send_header("Content-Length", str(len(data)))
|
|
239
|
+
self.end_headers()
|
|
240
|
+
self.wfile.write(data)
|
|
241
|
+
|
|
242
|
+
HTTPServer(("127.0.0.1", 8000), Handler).serve_forever()
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Any framework works the same way: return `str(page)` as the response body.
|
|
246
|
+
|
|
247
|
+
## Developing TealUI
|
|
248
|
+
|
|
249
|
+
`tealui test` runs the unit tests (`--browser` also runs the browser compatibility check and the accessibility and device suite), `tealui export` tests, builds the wheel and sdist into `dist/` and writes the minimal source zip (needs `pip install build`).
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
MIT. The bundled fonts and icons keep their own licenses (see `tealui/fonts/LICENSES.txt` and `tealui/icons-LICENSE.txt`).
|
|
254
|
+
|
|
255
|
+
## Trust boundaries
|
|
256
|
+
|
|
257
|
+
Text and attribute values are escaped by default. `Raw` is for trusted markup only. The values of `x_*`, `on_*`, and `bind_*` attributes are JavaScript expressions that run in the visitor's browser; escaping them as HTML does not make them safe, so never build them from untrusted text. `bind_href` and `bind_src` bypass the server-side URL safety check, so validate those destinations yourself before passing them in. Plain HTML event attributes (`onclick=`, `onload=`...) and `srcdoc=` are the same: they run what they contain. Address attributes (`href`, `src`, `action`, `formaction`, `poster`, `data`, `background`, and the SVG animation values `to`/`from`/`by`/`values`) are checked, and a `javascript:`, `vbscript:` or non-image `data:` address becomes `#`.
|
tealui-0.3.0/README.md
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# TealUI
|
|
2
|
+
|
|
3
|
+
Write a web UI in Python. The output is plain HTML, styled with Tailwind CSS and made interactive with Alpine.js. It works with any backend, because everything renders to a string.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
TealUI is not on PyPI yet. From a checkout:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install .
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Python 3.9+. No dependencies.
|
|
14
|
+
|
|
15
|
+
## Hello world
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from tealui import Page, Card, Stat, Button, Div, H1
|
|
19
|
+
|
|
20
|
+
page = Page(
|
|
21
|
+
"Hello, TealUI",
|
|
22
|
+
Div(
|
|
23
|
+
H1("Hello, TealUI", cls="text-2xl font-semibold tracking-tight"),
|
|
24
|
+
Stat("Users", "1,204"),
|
|
25
|
+
Card(Button("Get started")),
|
|
26
|
+
cls="mx-auto max-w-xl space-y-6 p-8",
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
with open("index.html", "w", encoding="utf-8") as f:
|
|
31
|
+
f.write(str(page))
|
|
32
|
+
print("Wrote index.html. Open it in a browser.")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Plain tags such as `H1` have no default styling (Tailwind resets them). Style them with `cls=`, or use components such as `PageHeader` and `Prose`.
|
|
36
|
+
|
|
37
|
+
With no `css=` argument, TealUI uses the Tailwind CDN build, so this page needs the internet.
|
|
38
|
+
|
|
39
|
+
## Production CSS
|
|
40
|
+
|
|
41
|
+
Tailwind only generates the classes it finds in scanned files, so give `--src` every folder where you write your own class strings (in Python or HTML); repeat `--src` for several folders. Without `--src` the current folder is scanned. Only the folders you name are scanned, nothing else.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
tealui build static/tealui.css --src app/
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The first run downloads the standalone Tailwind CLI (about 100 MB, checksum verified) into `~/.cache/tealui` (needs internet once); later runs work offline. `TEALUI_CACHE` or `--cache` moves the cache. A `fonts/` folder is copied next to the CSS file — keep it there. Installed look packages are included automatically; add a look that lives in your project with `--look looks.py:OCEAN`. `--alpine static/alpine.js` self-hosts Alpine (then `Page(alpine="/static/alpine.js")`); `--htmx static/htmx.js` does the same for HTMX (then `Page(htmx="/static/htmx.js")`). `tealui css` only writes the input file and fonts, for people who run their own Tailwind setup.
|
|
48
|
+
|
|
49
|
+
Then link the stylesheet:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from tealui import Page, Div
|
|
53
|
+
|
|
54
|
+
page = Page("Home", Div("hello"), css="/static/tealui.css")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Versions
|
|
58
|
+
|
|
59
|
+
TealUI follows the latest Alpine 3.x, HTMX 2.x and Tailwind 4.x. Dev mode loads them from jsdelivr by major version. A new major version is adopted only after testing. `tealui update` refreshes the cached tools, `tealui versions` shows installed vs latest.
|
|
60
|
+
|
|
61
|
+
## Looks
|
|
62
|
+
|
|
63
|
+
A look is the theme: colors, radii, shadows and fonts, in light and dark, plus the class recipes components use. Fourteen are built in:
|
|
64
|
+
|
|
65
|
+
| Look | Kind | Character |
|
|
66
|
+
| --- | --- | --- |
|
|
67
|
+
| `TEAL` | default | Product look, teal accent |
|
|
68
|
+
| `PAPER` | full | Editorial: serif headings, warm paper, rust accent |
|
|
69
|
+
| `SLATE` | full | Cool grey, graphite accent |
|
|
70
|
+
| `MIDNIGHT` | full, dark-first | Blue-black with an indigo glow |
|
|
71
|
+
| `LEDGER` | full | Navy, dense and flat (32px controls, 40px rows) |
|
|
72
|
+
| `CONTRAST` | full | Black and white, WCAG AAA (7:1) |
|
|
73
|
+
| `NORDIC` | full | Arctic blue-grey, muted status colors |
|
|
74
|
+
| `SOFT` | full | Pastel lilac, large radii |
|
|
75
|
+
| `TERMINAL` | full, dark-first | Monospace, green on black, square |
|
|
76
|
+
| `BRUTAL` | full | Neo-brutalist: black borders, hard shadows |
|
|
77
|
+
| `INDIGO`, `ENTERPRISE`, `FOREST`, `SUNSET` | accent swap | TEAL with an indigo, corporate blue, deep green or rose accent |
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from tealui import use, using, with_accent, TEAL, PAPER, SLATE, Page, Div
|
|
81
|
+
|
|
82
|
+
use(SLATE) # change the default look
|
|
83
|
+
use(with_accent(TEAL, "#7c3aed")) # or derive a custom accent from one hex
|
|
84
|
+
|
|
85
|
+
with using(PAPER): # or only for one render
|
|
86
|
+
html = str(Page("Report", Div("hello")))
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The dark-mode choice is saved in the browser (`localStorage`). Dark-first looks start dark until the visitor picks light. `tealui looks` lists every look; `tealui look preview slate gallery.html` writes a page with every component in a look.
|
|
90
|
+
|
|
91
|
+
### Your own look
|
|
92
|
+
|
|
93
|
+
`make_look` copies TEAL (or `base=`) with your changes and checks the result: every variable and recipe present, no unsafe values, and contrast (4.5:1 by default) in light and dark.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from tealui import make_look, use
|
|
97
|
+
|
|
98
|
+
OCEAN = make_look(
|
|
99
|
+
"ocean",
|
|
100
|
+
vars={"accent": "#0e7490", "accent-hover": "#155e75", "accent-ink": "#ffffff",
|
|
101
|
+
"accent-soft": "#e0f5f9", "accent-line": "#a5dbe6", "r-card": "16px"},
|
|
102
|
+
dark={"accent": "#67e8f9", "accent-hover": "#a5f3fc", "accent-ink": "#062a30",
|
|
103
|
+
"accent-soft": "#0c2c33", "accent-line": "#1c5a66"},
|
|
104
|
+
recipes={"button": {"md": "h-10 px-4"}},
|
|
105
|
+
)
|
|
106
|
+
use(OCEAN)
|
|
107
|
+
print(OCEAN["name"])
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`tealui look new ocean` writes a starter file, `tealui look check ocean.py:OCEAN` checks it, and `--package` adds a `pyproject.toml` to publish it as `tealui-look-ocean`. Installed look packages are found by `tealui build` automatically; a look kept in your project is added with `tealui build ... --look looks.py:OCEAN`. The full guide is `docs/looks.md` in the source distribution.
|
|
111
|
+
|
|
112
|
+
## Fonts and icons
|
|
113
|
+
|
|
114
|
+
Override a look's fonts with `with_fonts`, then register the actual font file with a `<style>` tag in the head.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from tealui import use, with_fonts, TEAL, Page, Div, Style, Raw
|
|
118
|
+
|
|
119
|
+
use(with_fonts(TEAL, head='"MyFont", serif', body='"MyFont", sans-serif'))
|
|
120
|
+
page = Page("Font demo", Div("hello"),
|
|
121
|
+
head=[Style(Raw('@font-face{font-family:"MyFont";src:url("/static/myfont.woff2") format("woff2")}'))])
|
|
122
|
+
print(len(str(page)))
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Add your own icons with `register_icon`; the SVG markup is trusted and rendered as-is.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from tealui import register_icon, Icon
|
|
129
|
+
|
|
130
|
+
register_icon("spark", '<path d="M12 2l2.4 6.6L21 11l-6.6 2.4L12 20l-2.4-6.6L3 11l6.6-2.4z"/>')
|
|
131
|
+
print(Icon("spark"))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Interactivity
|
|
135
|
+
|
|
136
|
+
Pages for right-to-left languages mirror by themselves: `Page(..., lang="ar")` sets `dir="rtl"`, and every look uses logical classes (`ms-`, `ps-`, `start-`). Server-rendered fragments work with HTMX: any `hx_*` keyword becomes an `hx-*` attribute (`Button("Load", hx_get="/rows", hx_target="#rows")`) and `Page(..., htmx=True)` loads the library.
|
|
137
|
+
|
|
138
|
+
Components are Alpine-powered, and you can always write your own JS:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from tealui import Page, Div, Span, Button
|
|
142
|
+
|
|
143
|
+
page = Page(
|
|
144
|
+
"Counter",
|
|
145
|
+
Div(
|
|
146
|
+
Span(x_text="n"),
|
|
147
|
+
Button("+1", on_click="n++"),
|
|
148
|
+
x_data="counter",
|
|
149
|
+
),
|
|
150
|
+
js="Alpine.data('counter', () => ({ n: 0 }))",
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Add extra tags to the `<head>`:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from tealui import Page, Div, Link, Script
|
|
158
|
+
|
|
159
|
+
page = Page(
|
|
160
|
+
"Head example",
|
|
161
|
+
Div("hi"),
|
|
162
|
+
head=[Link(rel="icon", href="/favicon.ico"),
|
|
163
|
+
Script(src="/static/extra.js", defer=True)],
|
|
164
|
+
)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Escape hatches
|
|
168
|
+
|
|
169
|
+
Any tag, any attribute:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from tealui import Div, El, Raw
|
|
173
|
+
|
|
174
|
+
print(Div(El("custom-thing", "hello", any_attr="x")))
|
|
175
|
+
print(Div(Raw("<svg viewBox='0 0 10 10'><circle cx='5' cy='5' r='4'/></svg>")))
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Attribute naming rule: `cls` becomes `class`; `_` becomes `-`; `on_x` becomes `@x`; `bind_x` becomes `:x`; `__` becomes `.`. For example `on_click` -> `@click`, `bind_class` -> `:class`, `x_model__number` -> `x-model.number`. Text is escaped by default; only `Raw` is trusted.
|
|
179
|
+
|
|
180
|
+
## Components
|
|
181
|
+
|
|
182
|
+
- Layout: `Page`, `Shell`, `Sidebar`, `Topbar`, `SiteHeader`, `PageHeader`, `Card`, `Prose`, `DocsNav`, `Toc`, `ReadingProgress`
|
|
183
|
+
- Data: `Stat`, `Delta`, `Table`, `BarList`, `Sparkline`, `AreaChart`, `Progress`, `Badge`, `Avatar`, `Kbd`
|
|
184
|
+
- Forms: `Button`, `TextInput`, `TextBox`, `SelectBox`, `Checkbox`, `Switch`, `RadioGroup`, `Field`, `Segmented`, `SearchBox`, `TagFilter`, `tag_show`
|
|
185
|
+
- Overlays & feedback: `Modal`, `Dropdown`, `Toaster`, `toast_js`, `Callout`, `Tabs`
|
|
186
|
+
- Blog & nav: `PostCard`, `Pager`, `ArticleHeader`, `ShareBar`, `Breadcrumb`, `NavItem`, `DarkToggle`, `Icon`
|
|
187
|
+
|
|
188
|
+
A tag filter needs one wrapper that holds the `x-data` for both the filter and the posts:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from tealui import Div, TagFilter, PostCard, tag_show
|
|
192
|
+
|
|
193
|
+
Div(
|
|
194
|
+
TagFilter(["python", "css"]),
|
|
195
|
+
PostCard("A", href="/a", tag="python", x_show=tag_show("python")),
|
|
196
|
+
PostCard("B", href="/b", tag="css", x_show=tag_show("css")),
|
|
197
|
+
x_data="{ tag: '' }",
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Rules of the road
|
|
202
|
+
|
|
203
|
+
- `cls=` is added last, so your classes win.
|
|
204
|
+
- Extra keyword arguments land on the root element (on the control itself for form controls).
|
|
205
|
+
- The tag helpers `Input`, `Select`, `Textarea`, `Label`, `Option` and `Form` already exist, so the components are named `TextInput`, `SelectBox`, and so on.
|
|
206
|
+
|
|
207
|
+
## Serve it
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
211
|
+
from tealui import Page, Div, H1
|
|
212
|
+
|
|
213
|
+
class Handler(BaseHTTPRequestHandler):
|
|
214
|
+
def do_GET(self):
|
|
215
|
+
body = str(Page("Home", Div(H1("Served by stdlib"), cls="p-8")))
|
|
216
|
+
data = body.encode("utf-8")
|
|
217
|
+
self.send_response(200)
|
|
218
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
219
|
+
self.send_header("Content-Length", str(len(data)))
|
|
220
|
+
self.end_headers()
|
|
221
|
+
self.wfile.write(data)
|
|
222
|
+
|
|
223
|
+
HTTPServer(("127.0.0.1", 8000), Handler).serve_forever()
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Any framework works the same way: return `str(page)` as the response body.
|
|
227
|
+
|
|
228
|
+
## Developing TealUI
|
|
229
|
+
|
|
230
|
+
`tealui test` runs the unit tests (`--browser` also runs the browser compatibility check and the accessibility and device suite), `tealui export` tests, builds the wheel and sdist into `dist/` and writes the minimal source zip (needs `pip install build`).
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT. The bundled fonts and icons keep their own licenses (see `tealui/fonts/LICENSES.txt` and `tealui/icons-LICENSE.txt`).
|
|
235
|
+
|
|
236
|
+
## Trust boundaries
|
|
237
|
+
|
|
238
|
+
Text and attribute values are escaped by default. `Raw` is for trusted markup only. The values of `x_*`, `on_*`, and `bind_*` attributes are JavaScript expressions that run in the visitor's browser; escaping them as HTML does not make them safe, so never build them from untrusted text. `bind_href` and `bind_src` bypass the server-side URL safety check, so validate those destinations yourself before passing them in. Plain HTML event attributes (`onclick=`, `onload=`...) and `srcdoc=` are the same: they run what they contain. Address attributes (`href`, `src`, `action`, `formaction`, `poster`, `data`, `background`, and the SVG animation values `to`/`from`/`by`/`values`) are checked, and a `javascript:`, `vbscript:` or non-image `data:` address becomes `#`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tealui"
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "A tiny, clean, pythonic UI library that renders plain HTML with Tailwind and Alpine."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{name = "octalynx"}]
|
|
14
|
+
keywords = ["html", "ui", "tailwind", "alpine", "components", "themes", "flask", "fastapi", "django"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
20
|
+
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
"Typing :: Typed",
|
|
23
|
+
]
|
|
24
|
+
dependencies = []
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
tealui = "tealui.cli:main"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools.packages.find]
|
|
30
|
+
include = ["tealui*"]
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.package-data]
|
|
33
|
+
tealui = ["fonts/*", "icons-LICENSE.txt", "py.typed"]
|
tealui-0.3.0/setup.cfg
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""A tiny, clean, pythonic UI library."""
|
|
2
|
+
from . import tags as _tags
|
|
3
|
+
from .core import El, Tag, Raw, render
|
|
4
|
+
from .tags import *
|
|
5
|
+
from .look import use, using, current, r, make_look, check_look, look_problems
|
|
6
|
+
from .looks import (TEAL, PAPER, SLATE, INDIGO, MIDNIGHT, LEDGER, ENTERPRISE, CONTRAST,
|
|
7
|
+
FOREST, SUNSET, NORDIC, SOFT, TERMINAL, BRUTAL)
|
|
8
|
+
from .theme import with_accent, with_fonts
|
|
9
|
+
from .icons import register_icon
|
|
10
|
+
from .page import Page
|
|
11
|
+
from .ui.foundation import Icon, Button, Badge, Avatar, Card, Kbd
|
|
12
|
+
from .ui.charts import Sparkline, AreaChart
|
|
13
|
+
from .ui.data import Delta, Stat, Progress, Table, BarList
|
|
14
|
+
from .ui.nav import Tabs, Breadcrumb
|
|
15
|
+
from .ui.shell import NavItem, Sidebar, Topbar, PageHeader, DarkToggle, Shell
|
|
16
|
+
from .ui.forms import Segmented, SearchBox, TagFilter, tag_show
|
|
17
|
+
from .ui.content import SiteHeader, Prose, CodeBlock, Callout
|
|
18
|
+
from .ui.forms2 import TextInput, SelectBox, Checkbox, Switch, Field, TextBox, RadioGroup
|
|
19
|
+
from .ui.overlay import Modal, Dropdown, Toaster, toast_js
|
|
20
|
+
from .ui.mobile import AppScreen, LargeTitle, ListGroup, ListRow, TabBar
|
|
21
|
+
from .ui.article import Toc, ReadingProgress, ArticleHeader, ShareBar, PostCard, Pager, DocsNav
|
|
22
|
+
use(TEAL)
|
|
23
|
+
__all__ = list(dict.fromkeys([
|
|
24
|
+
"El", "Tag", "Raw", "render", "use", "using", "current", "r",
|
|
25
|
+
"TEAL", "PAPER", "SLATE", "INDIGO", "MIDNIGHT", "LEDGER", "ENTERPRISE", "CONTRAST",
|
|
26
|
+
"FOREST", "SUNSET", "NORDIC", "SOFT", "TERMINAL", "BRUTAL",
|
|
27
|
+
"make_look", "check_look", "look_problems", "with_accent", "Page", "Icon",
|
|
28
|
+
"Button", "Badge", "Avatar", "Card", "Kbd",
|
|
29
|
+
"Sparkline", "AreaChart", "Delta", "Stat", "Progress", "Table", "BarList",
|
|
30
|
+
"Tabs", "Breadcrumb",
|
|
31
|
+
"NavItem", "Sidebar", "Topbar", "PageHeader", "DarkToggle", "Shell",
|
|
32
|
+
"Segmented", "SearchBox",
|
|
33
|
+
"SiteHeader", "Prose", "CodeBlock", "Callout",
|
|
34
|
+
"TextInput", "SelectBox", "Checkbox", "Switch", "Field",
|
|
35
|
+
"Modal",
|
|
36
|
+
"Dropdown", "Toaster", "toast_js",
|
|
37
|
+
"Toc", "ReadingProgress", "ArticleHeader", "ShareBar", "PostCard", "Pager", "DocsNav",
|
|
38
|
+
"AppScreen", "LargeTitle", "ListGroup", "ListRow", "TabBar",
|
|
39
|
+
"with_fonts", "register_icon",
|
|
40
|
+
"TextBox", "RadioGroup", "TagFilter", "tag_show",
|
|
41
|
+
*_tags.__all__
|
|
42
|
+
]))
|
|
43
|
+
__version__ = "0.3.0"
|