pyweft-ui 0.1.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 (37) hide show
  1. pyweft_ui-0.1.0/.gitignore +10 -0
  2. pyweft_ui-0.1.0/CHANGELOG.md +23 -0
  3. pyweft_ui-0.1.0/LICENSE +21 -0
  4. pyweft_ui-0.1.0/PKG-INFO +184 -0
  5. pyweft_ui-0.1.0/README.md +165 -0
  6. pyweft_ui-0.1.0/docs/guide.md +929 -0
  7. pyweft_ui-0.1.0/gallery/app.weft +289 -0
  8. pyweft_ui-0.1.0/gallery/pyproject.toml +4 -0
  9. pyweft_ui-0.1.0/gallery/styles.css +4 -0
  10. pyweft_ui-0.1.0/pyproject.toml +41 -0
  11. pyweft_ui-0.1.0/src/pyweft_ui/__init__.py +23 -0
  12. pyweft_ui-0.1.0/src/pyweft_ui/alert.weft +48 -0
  13. pyweft_ui-0.1.0/src/pyweft_ui/app_shell.weft +47 -0
  14. pyweft_ui-0.1.0/src/pyweft_ui/badge.weft +25 -0
  15. pyweft_ui-0.1.0/src/pyweft_ui/button.weft +64 -0
  16. pyweft_ui-0.1.0/src/pyweft_ui/card.weft +45 -0
  17. pyweft_ui-0.1.0/src/pyweft_ui/checkbox.weft +26 -0
  18. pyweft_ui-0.1.0/src/pyweft_ui/column.weft +23 -0
  19. pyweft_ui-0.1.0/src/pyweft_ui/dialog.weft +91 -0
  20. pyweft_ui-0.1.0/src/pyweft_ui/grid.weft +25 -0
  21. pyweft_ui-0.1.0/src/pyweft_ui/icon.weft +50 -0
  22. pyweft_ui-0.1.0/src/pyweft_ui/number_field.weft +69 -0
  23. pyweft_ui-0.1.0/src/pyweft_ui/radio_group.weft +60 -0
  24. pyweft_ui-0.1.0/src/pyweft_ui/row.weft +28 -0
  25. pyweft_ui-0.1.0/src/pyweft_ui/select.weft +81 -0
  26. pyweft_ui-0.1.0/src/pyweft_ui/spinner.weft +21 -0
  27. pyweft_ui-0.1.0/src/pyweft_ui/switch.weft +39 -0
  28. pyweft_ui-0.1.0/src/pyweft_ui/table.weft +223 -0
  29. pyweft_ui-0.1.0/src/pyweft_ui/tabs.weft +122 -0
  30. pyweft_ui-0.1.0/src/pyweft_ui/text_area.weft +48 -0
  31. pyweft_ui-0.1.0/src/pyweft_ui/text_field.weft +51 -0
  32. pyweft_ui-0.1.0/src/pyweft_ui/theme.weft +81 -0
  33. pyweft_ui-0.1.0/src/pyweft_ui/toaster.weft +117 -0
  34. pyweft_ui-0.1.0/src/pyweft_ui/toasts.py +73 -0
  35. pyweft_ui-0.1.0/tests/conftest.py +14 -0
  36. pyweft_ui-0.1.0/tests/test_controls.py +555 -0
  37. pyweft_ui-0.1.0/tests/test_guide_examples.py +194 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .pytest_cache/
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+ dist-desktop/
8
+ .venv/
9
+ CLAUDE.md
10
+ PROMPTS.md
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ What changed in each release of `pyweft-ui`. Newest first.
4
+
5
+ Add a line under **Unreleased** with each change worth knowing about;
6
+ releasing moves them under the new version (see
7
+ [RELEASING.md](../../RELEASING.md)).
8
+
9
+ ## Unreleased
10
+
11
+ ## 0.1.0
12
+
13
+ The first release, for `pyweft` 0.2.
14
+
15
+ - **22 controls**: `Theme`, `AppShell`, `Row`, `Column`, `Grid`, `Card`,
16
+ `Button`, `Icon`, `TextField`, `TextArea`, `NumberField`, `Select`,
17
+ `RadioGroup`, `Checkbox`, `Switch`, `Alert`, `Badge`, `Spinner`,
18
+ `Dialog`, `Tabs`, `Table` and `Toaster` with `toast()`.
19
+ - Light, dark and system themes, every colour a `--ui-*` CSS variable.
20
+ - Keyboard and screen reader support: labelled fields, focus kept inside
21
+ dialogs and returned on close, arrow keys between tabs, sortable table
22
+ headings announced.
23
+ - A gallery app and a guide with tested examples.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Prassanth Nicholas
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.
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.5
2
+ Name: pyweft-ui
3
+ Version: 0.1.0
4
+ Summary: Ready-made controls for PyWeft: buttons, fields, cards, layout, dialogs. No HTML or CSS required.
5
+ Project-URL: Homepage, https://github.com/prassanthn/PyWeft
6
+ Project-URL: Documentation, https://github.com/prassanthn/PyWeft/blob/main/packages/pyweft-ui/docs/guide.md
7
+ Project-URL: Issues, https://github.com/prassanthn/PyWeft/issues
8
+ Author: Prassanth Nicholas
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: components,controls,pyodide,pyweft,ui
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: User Interfaces
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: pyweft<0.3,>=0.2
18
+ Description-Content-Type: text/markdown
19
+
20
+ # pyweft-ui
21
+
22
+ Ready-made controls for [PyWeft](https://github.com/prassanthn/PyWeft):
23
+ buttons, form fields, cards, layout, tabs, tables, dialogs, toasts and more,
24
+ styled and accessible out of the box. Build a good-looking app in Python without writing HTML or
25
+ CSS.
26
+
27
+ ```xml
28
+ <template>
29
+ <ui.Theme>
30
+ <ui.AppShell title="Orders">
31
+ <ui.Card title="New order">
32
+ <ui.Column>
33
+ <ui.TextField label="Customer" @bind="customer" required="true" />
34
+ <ui.Select label="Size" options="Small, Medium, Large" @bind="size" />
35
+ <ui.Switch label="Express delivery" @bind="express" />
36
+ </ui.Column>
37
+ <Fill name="footer">
38
+ <ui.Button variant="ghost" @click="reset">Reset</ui.Button>
39
+ <ui.Button variant="primary" icon="send" :loading="saving" @click="submit">
40
+ Place order
41
+ </ui.Button>
42
+ </Fill>
43
+ </ui.Card>
44
+ </ui.AppShell>
45
+ </ui.Theme>
46
+ </template>
47
+
48
+ <code>
49
+ from pyweft_ui import ui
50
+
51
+ customer: str = ""
52
+ size: str = "Medium"
53
+ express: bool = False
54
+ saving: bool = False
55
+
56
+ def reset(self):
57
+ self.customer, self.size, self.express = "", "Medium", False
58
+
59
+ async def submit(self):
60
+ ...
61
+ </code>
62
+ ```
63
+
64
+ **New to the toolkit? Read the [guide](docs/guide.md)**: page layout, forms
65
+ with validation, dialogs, themes and building your own components, with
66
+ complete examples. This README is the compact reference.
67
+
68
+ ## Install
69
+
70
+ It isn't on PyPI yet, so install it from GitHub, into the same environment
71
+ as PyWeft itself (see PyWeft's [quick start](../../README.md#quick-start)):
72
+
73
+ ```bash
74
+ uv add "pyweft-ui @ git+https://github.com/prassanthn/PyWeft.git#subdirectory=packages/pyweft-ui"
75
+ ```
76
+
77
+ With pip:
78
+
79
+ ```bash
80
+ pip install "git+https://github.com/prassanthn/PyWeft.git#subdirectory=packages/pyweft-ui"
81
+ ```
82
+
83
+ Then add `"pyweft-ui"` to the `dependencies` list in your app's own
84
+ `pyproject.toml` (`myapp/pyproject.toml`), import it in a component's code
85
+ block with `from pyweft_ui import ui`, and use the controls as `<ui.Name>`.
86
+ `pyweft run` and `pyweft build` see that it is installed locally and send it
87
+ to the browser for you.
88
+
89
+ ## Controls
90
+
91
+ Every control is a PyWeft component: attributes set its fields, `@bind`
92
+ connects it to a field of yours, and attributes it doesn't have, such as
93
+ `class`, `style`, `id` and `aria-*`, go onto its outer element. Plain
94
+ attribute values are converted to each field's type, so `cols="3"` is the
95
+ number 3 and `disabled="true"` is `True`.
96
+
97
+ ### Layout
98
+
99
+ | Control | Fields | Notes |
100
+ |---|---|---|
101
+ | `Theme` | `mode` (light, dark, auto), `primary`, `radius`, `font` | Wrap your app in it to set colours and corners for everything inside. Optional. |
102
+ | `AppShell` | `title`, `width` (narrow, normal, wide, full) | Header, optional sidebar and content. Fills: `brand`, `actions`, `nav`. |
103
+ | `Row` | `gap`, `align`, `justify` (start, center, end, between), `wrap` | Children side by side. |
104
+ | `Column` | `gap`, `align` | Children stacked. |
105
+ | `Grid` | `cols`, `min_width`, `gap` | Fixed columns, or as many as fit. |
106
+ | `Card` | `title`, `subtitle` | Fills: `title`, `actions`, `footer`. |
107
+
108
+ Gaps take `none`, `xs`, `sm`, `md`, `lg`, `xl`, or any CSS length.
109
+
110
+ ### Form fields
111
+
112
+ All have `label`, `hint`, `error`, `required`, `disabled`, `name`, and take
113
+ `@bind`.
114
+
115
+ | Control | Binds | Extra fields |
116
+ |---|---|---|
117
+ | `TextField` | `str` | `type` (text, email, password, search, url, tel, date, time), `placeholder`, `autocomplete`; emits `enter` on Enter |
118
+ | `TextArea` | `str` | `rows`, `placeholder` |
119
+ | `NumberField` | `int` or `float` | `min`, `max`, `step`, `placeholder` |
120
+ | `Select` | the chosen option's value | `options`, `placeholder` |
121
+ | `RadioGroup` | the chosen option's value | `options`, `inline` |
122
+ | `Checkbox` | `bool` | |
123
+ | `Switch` | `bool` | |
124
+
125
+ `options` can be a comma-separated string (`"Small, Medium, Large"`), a list
126
+ of values, a list of `(value, label)` pairs or a list of
127
+ `{"value": ..., "label": ...}` dicts. The bound field gets the value itself,
128
+ so `[(1, "One"), (2, "Two")]` binds an `int`.
129
+
130
+ ### Data and navigation
131
+
132
+ | Control | Fields | Notes |
133
+ |---|---|---|
134
+ | `Tabs` | `options`, `label`, `variant` (line, pills) | `@bind` to the chosen option's value. The content between the tags is the panel; `tab` inside it is the chosen value. Arrow keys, Home and End move between tabs. |
135
+ | `Table` | `rows`, `columns`, `row_key`, `sortable`, `sort`, `striped`, `dense`, `caption`, `empty` | `rows` are dicts or objects. Emits `select` with the clicked row. Fills: `cell` (with `row`, `column`, `value`, `text`), `empty`. |
136
+
137
+ `Table` columns are field names (`"id, customer"`), `(field, heading)` pairs,
138
+ or dicts with `key` and optionally `label`, `format` (`",.2f"` or a
139
+ function), `align` and `sortable`. Left out, they come from the first row.
140
+ `sort="-total"` starts sorted by `total`, largest first.
141
+
142
+ ### Actions and feedback
143
+
144
+ | Control | Fields | Notes |
145
+ |---|---|---|
146
+ | `Button` | `variant` (primary, secondary, ghost, danger), `size` (sm, md, lg), `icon`, `loading`, `disabled`, `block`, `type` | Emits `click`. |
147
+ | `Dialog` | `title`, `width`, `dismissible` | `@bind` to a `bool`: open while it is `True`. Esc, a click outside or the X close it. Keeps Tab inside while open and gives focus back when closed. Fill: `footer`. |
148
+ | `Toaster` | `position` (bottom-right, bottom-left, bottom-center, top-left, top-center, top-right), `limit` | Put one on the page; it shows what `toast()` raises. |
149
+ | `Alert` | `kind` (info, success, warning, danger), `title`, `dismissible` | Emits `close`. |
150
+ | `Badge` | `kind` (neutral, primary, info, success, warning, danger) | |
151
+ | `Spinner` | `size`, `label` | |
152
+ | `Icon` | `name`, `size` | check, x, plus, minus, search, info, alert, chevron-up/down/left/right, menu, trash, edit, send, user, home, star, heart, download, upload, external, copy, settings, sun, moon |
153
+
154
+ ## Toasts
155
+
156
+ ```python
157
+ from pyweft_ui import toast, clear_toasts
158
+
159
+ toast("Saved") # info, closes after 4 s
160
+ toast("Payment failed", "danger", title="Oops") # kind: info, success, warning, danger
161
+ toast("Archived", action="Undo", on_action=undo) # a button that calls undo()
162
+ note = toast("Uploading…", duration=None) # stays until note.close()
163
+ ```
164
+
165
+ Call it from any handler or task; the page's `<ui.Toaster />` shows it.
166
+
167
+ ## Theming
168
+
169
+ ```xml
170
+ <ui.Theme mode="auto" primary="#0f766e" radius="12px">
171
+ ...
172
+ </ui.Theme>
173
+ ```
174
+
175
+ Every colour is a CSS variable (`--ui-primary`, `--ui-text`, `--ui-surface`,
176
+ `--ui-border`, `--ui-danger` ...), so you can also set them in your own CSS.
177
+
178
+ ## The gallery
179
+
180
+ `gallery/` is a PyWeft app showing every control. From the PyWeft repository:
181
+
182
+ ```bash
183
+ uv run pyweft run packages/pyweft-ui/gallery
184
+ ```
@@ -0,0 +1,165 @@
1
+ # pyweft-ui
2
+
3
+ Ready-made controls for [PyWeft](https://github.com/prassanthn/PyWeft):
4
+ buttons, form fields, cards, layout, tabs, tables, dialogs, toasts and more,
5
+ styled and accessible out of the box. Build a good-looking app in Python without writing HTML or
6
+ CSS.
7
+
8
+ ```xml
9
+ <template>
10
+ <ui.Theme>
11
+ <ui.AppShell title="Orders">
12
+ <ui.Card title="New order">
13
+ <ui.Column>
14
+ <ui.TextField label="Customer" @bind="customer" required="true" />
15
+ <ui.Select label="Size" options="Small, Medium, Large" @bind="size" />
16
+ <ui.Switch label="Express delivery" @bind="express" />
17
+ </ui.Column>
18
+ <Fill name="footer">
19
+ <ui.Button variant="ghost" @click="reset">Reset</ui.Button>
20
+ <ui.Button variant="primary" icon="send" :loading="saving" @click="submit">
21
+ Place order
22
+ </ui.Button>
23
+ </Fill>
24
+ </ui.Card>
25
+ </ui.AppShell>
26
+ </ui.Theme>
27
+ </template>
28
+
29
+ <code>
30
+ from pyweft_ui import ui
31
+
32
+ customer: str = ""
33
+ size: str = "Medium"
34
+ express: bool = False
35
+ saving: bool = False
36
+
37
+ def reset(self):
38
+ self.customer, self.size, self.express = "", "Medium", False
39
+
40
+ async def submit(self):
41
+ ...
42
+ </code>
43
+ ```
44
+
45
+ **New to the toolkit? Read the [guide](docs/guide.md)**: page layout, forms
46
+ with validation, dialogs, themes and building your own components, with
47
+ complete examples. This README is the compact reference.
48
+
49
+ ## Install
50
+
51
+ It isn't on PyPI yet, so install it from GitHub, into the same environment
52
+ as PyWeft itself (see PyWeft's [quick start](../../README.md#quick-start)):
53
+
54
+ ```bash
55
+ uv add "pyweft-ui @ git+https://github.com/prassanthn/PyWeft.git#subdirectory=packages/pyweft-ui"
56
+ ```
57
+
58
+ With pip:
59
+
60
+ ```bash
61
+ pip install "git+https://github.com/prassanthn/PyWeft.git#subdirectory=packages/pyweft-ui"
62
+ ```
63
+
64
+ Then add `"pyweft-ui"` to the `dependencies` list in your app's own
65
+ `pyproject.toml` (`myapp/pyproject.toml`), import it in a component's code
66
+ block with `from pyweft_ui import ui`, and use the controls as `<ui.Name>`.
67
+ `pyweft run` and `pyweft build` see that it is installed locally and send it
68
+ to the browser for you.
69
+
70
+ ## Controls
71
+
72
+ Every control is a PyWeft component: attributes set its fields, `@bind`
73
+ connects it to a field of yours, and attributes it doesn't have, such as
74
+ `class`, `style`, `id` and `aria-*`, go onto its outer element. Plain
75
+ attribute values are converted to each field's type, so `cols="3"` is the
76
+ number 3 and `disabled="true"` is `True`.
77
+
78
+ ### Layout
79
+
80
+ | Control | Fields | Notes |
81
+ |---|---|---|
82
+ | `Theme` | `mode` (light, dark, auto), `primary`, `radius`, `font` | Wrap your app in it to set colours and corners for everything inside. Optional. |
83
+ | `AppShell` | `title`, `width` (narrow, normal, wide, full) | Header, optional sidebar and content. Fills: `brand`, `actions`, `nav`. |
84
+ | `Row` | `gap`, `align`, `justify` (start, center, end, between), `wrap` | Children side by side. |
85
+ | `Column` | `gap`, `align` | Children stacked. |
86
+ | `Grid` | `cols`, `min_width`, `gap` | Fixed columns, or as many as fit. |
87
+ | `Card` | `title`, `subtitle` | Fills: `title`, `actions`, `footer`. |
88
+
89
+ Gaps take `none`, `xs`, `sm`, `md`, `lg`, `xl`, or any CSS length.
90
+
91
+ ### Form fields
92
+
93
+ All have `label`, `hint`, `error`, `required`, `disabled`, `name`, and take
94
+ `@bind`.
95
+
96
+ | Control | Binds | Extra fields |
97
+ |---|---|---|
98
+ | `TextField` | `str` | `type` (text, email, password, search, url, tel, date, time), `placeholder`, `autocomplete`; emits `enter` on Enter |
99
+ | `TextArea` | `str` | `rows`, `placeholder` |
100
+ | `NumberField` | `int` or `float` | `min`, `max`, `step`, `placeholder` |
101
+ | `Select` | the chosen option's value | `options`, `placeholder` |
102
+ | `RadioGroup` | the chosen option's value | `options`, `inline` |
103
+ | `Checkbox` | `bool` | |
104
+ | `Switch` | `bool` | |
105
+
106
+ `options` can be a comma-separated string (`"Small, Medium, Large"`), a list
107
+ of values, a list of `(value, label)` pairs or a list of
108
+ `{"value": ..., "label": ...}` dicts. The bound field gets the value itself,
109
+ so `[(1, "One"), (2, "Two")]` binds an `int`.
110
+
111
+ ### Data and navigation
112
+
113
+ | Control | Fields | Notes |
114
+ |---|---|---|
115
+ | `Tabs` | `options`, `label`, `variant` (line, pills) | `@bind` to the chosen option's value. The content between the tags is the panel; `tab` inside it is the chosen value. Arrow keys, Home and End move between tabs. |
116
+ | `Table` | `rows`, `columns`, `row_key`, `sortable`, `sort`, `striped`, `dense`, `caption`, `empty` | `rows` are dicts or objects. Emits `select` with the clicked row. Fills: `cell` (with `row`, `column`, `value`, `text`), `empty`. |
117
+
118
+ `Table` columns are field names (`"id, customer"`), `(field, heading)` pairs,
119
+ or dicts with `key` and optionally `label`, `format` (`",.2f"` or a
120
+ function), `align` and `sortable`. Left out, they come from the first row.
121
+ `sort="-total"` starts sorted by `total`, largest first.
122
+
123
+ ### Actions and feedback
124
+
125
+ | Control | Fields | Notes |
126
+ |---|---|---|
127
+ | `Button` | `variant` (primary, secondary, ghost, danger), `size` (sm, md, lg), `icon`, `loading`, `disabled`, `block`, `type` | Emits `click`. |
128
+ | `Dialog` | `title`, `width`, `dismissible` | `@bind` to a `bool`: open while it is `True`. Esc, a click outside or the X close it. Keeps Tab inside while open and gives focus back when closed. Fill: `footer`. |
129
+ | `Toaster` | `position` (bottom-right, bottom-left, bottom-center, top-left, top-center, top-right), `limit` | Put one on the page; it shows what `toast()` raises. |
130
+ | `Alert` | `kind` (info, success, warning, danger), `title`, `dismissible` | Emits `close`. |
131
+ | `Badge` | `kind` (neutral, primary, info, success, warning, danger) | |
132
+ | `Spinner` | `size`, `label` | |
133
+ | `Icon` | `name`, `size` | check, x, plus, minus, search, info, alert, chevron-up/down/left/right, menu, trash, edit, send, user, home, star, heart, download, upload, external, copy, settings, sun, moon |
134
+
135
+ ## Toasts
136
+
137
+ ```python
138
+ from pyweft_ui import toast, clear_toasts
139
+
140
+ toast("Saved") # info, closes after 4 s
141
+ toast("Payment failed", "danger", title="Oops") # kind: info, success, warning, danger
142
+ toast("Archived", action="Undo", on_action=undo) # a button that calls undo()
143
+ note = toast("Uploading…", duration=None) # stays until note.close()
144
+ ```
145
+
146
+ Call it from any handler or task; the page's `<ui.Toaster />` shows it.
147
+
148
+ ## Theming
149
+
150
+ ```xml
151
+ <ui.Theme mode="auto" primary="#0f766e" radius="12px">
152
+ ...
153
+ </ui.Theme>
154
+ ```
155
+
156
+ Every colour is a CSS variable (`--ui-primary`, `--ui-text`, `--ui-surface`,
157
+ `--ui-border`, `--ui-danger` ...), so you can also set them in your own CSS.
158
+
159
+ ## The gallery
160
+
161
+ `gallery/` is a PyWeft app showing every control. From the PyWeft repository:
162
+
163
+ ```bash
164
+ uv run pyweft run packages/pyweft-ui/gallery
165
+ ```