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.
Files changed (81) hide show
  1. tealui-0.3.0/LICENSE +21 -0
  2. tealui-0.3.0/PKG-INFO +257 -0
  3. tealui-0.3.0/README.md +238 -0
  4. tealui-0.3.0/pyproject.toml +33 -0
  5. tealui-0.3.0/setup.cfg +4 -0
  6. tealui-0.3.0/tealui/__init__.py +43 -0
  7. tealui-0.3.0/tealui/__main__.py +4 -0
  8. tealui-0.3.0/tealui/cli.py +173 -0
  9. tealui-0.3.0/tealui/core.py +113 -0
  10. tealui-0.3.0/tealui/devtools.py +126 -0
  11. tealui-0.3.0/tealui/fonts/LICENSES.txt +287 -0
  12. tealui-0.3.0/tealui/fonts/inter.woff2 +0 -0
  13. tealui-0.3.0/tealui/fonts/mono.woff2 +0 -0
  14. tealui-0.3.0/tealui/fonts/serif-italic.woff2 +0 -0
  15. tealui-0.3.0/tealui/fonts/serif.woff2 +0 -0
  16. tealui-0.3.0/tealui/gallery.py +81 -0
  17. tealui-0.3.0/tealui/icons-LICENSE.txt +43 -0
  18. tealui-0.3.0/tealui/icons.py +153 -0
  19. tealui-0.3.0/tealui/look.py +153 -0
  20. tealui-0.3.0/tealui/lookcli.py +217 -0
  21. tealui-0.3.0/tealui/looks/__init__.py +7 -0
  22. tealui-0.3.0/tealui/looks/accents.py +18 -0
  23. tealui-0.3.0/tealui/looks/full.py +163 -0
  24. tealui-0.3.0/tealui/looks/paper.py +23 -0
  25. tealui-0.3.0/tealui/looks/teal.py +352 -0
  26. tealui-0.3.0/tealui/page.py +87 -0
  27. tealui-0.3.0/tealui/py.typed +0 -0
  28. tealui-0.3.0/tealui/tags.py +112 -0
  29. tealui-0.3.0/tealui/theme.py +184 -0
  30. tealui-0.3.0/tealui/toolchain.py +246 -0
  31. tealui-0.3.0/tealui/ui/__init__.py +1 -0
  32. tealui-0.3.0/tealui/ui/article.py +103 -0
  33. tealui-0.3.0/tealui/ui/charts.py +111 -0
  34. tealui-0.3.0/tealui/ui/content.py +76 -0
  35. tealui-0.3.0/tealui/ui/data.py +96 -0
  36. tealui-0.3.0/tealui/ui/forms.py +92 -0
  37. tealui-0.3.0/tealui/ui/forms2.py +150 -0
  38. tealui-0.3.0/tealui/ui/foundation.py +66 -0
  39. tealui-0.3.0/tealui/ui/mobile.py +58 -0
  40. tealui-0.3.0/tealui/ui/nav.py +58 -0
  41. tealui-0.3.0/tealui/ui/overlay.py +93 -0
  42. tealui-0.3.0/tealui/ui/shell.py +85 -0
  43. tealui-0.3.0/tealui/versions.py +7 -0
  44. tealui-0.3.0/tealui.egg-info/PKG-INFO +257 -0
  45. tealui-0.3.0/tealui.egg-info/SOURCES.txt +79 -0
  46. tealui-0.3.0/tealui.egg-info/dependency_links.txt +1 -0
  47. tealui-0.3.0/tealui.egg-info/entry_points.txt +2 -0
  48. tealui-0.3.0/tealui.egg-info/top_level.txt +1 -0
  49. tealui-0.3.0/tests/test_article.py +100 -0
  50. tealui-0.3.0/tests/test_blog.py +112 -0
  51. tealui-0.3.0/tests/test_charts.py +192 -0
  52. tealui-0.3.0/tests/test_cli.py +338 -0
  53. tealui-0.3.0/tests/test_content.py +157 -0
  54. tealui-0.3.0/tests/test_contrast.py +50 -0
  55. tealui-0.3.0/tests/test_core.py +136 -0
  56. tealui-0.3.0/tests/test_data.py +202 -0
  57. tealui-0.3.0/tests/test_devtools.py +264 -0
  58. tealui-0.3.0/tests/test_docs_examples.py +48 -0
  59. tealui-0.3.0/tests/test_fixes_021.py +185 -0
  60. tealui-0.3.0/tests/test_forms.py +76 -0
  61. tealui-0.3.0/tests/test_forms2.py +190 -0
  62. tealui-0.3.0/tests/test_forms3.py +143 -0
  63. tealui-0.3.0/tests/test_foundation.py +123 -0
  64. tealui-0.3.0/tests/test_hooks.py +122 -0
  65. tealui-0.3.0/tests/test_icons.py +83 -0
  66. tealui-0.3.0/tests/test_look.py +134 -0
  67. tealui-0.3.0/tests/test_lookcli.py +164 -0
  68. tealui-0.3.0/tests/test_looks.py +27 -0
  69. tealui-0.3.0/tests/test_looks_030.py +167 -0
  70. tealui-0.3.0/tests/test_mobile.py +144 -0
  71. tealui-0.3.0/tests/test_nav.py +158 -0
  72. tealui-0.3.0/tests/test_overlay.py +66 -0
  73. tealui-0.3.0/tests/test_overlay2.py +126 -0
  74. tealui-0.3.0/tests/test_page.py +113 -0
  75. tealui-0.3.0/tests/test_rtl.py +100 -0
  76. tealui-0.3.0/tests/test_shell.py +126 -0
  77. tealui-0.3.0/tests/test_spy_trap.py +110 -0
  78. tealui-0.3.0/tests/test_tags.py +51 -0
  79. tealui-0.3.0/tests/test_theme.py +45 -0
  80. tealui-0.3.0/tests/test_toolchain.py +440 -0
  81. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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"
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())