nicegui-shadcn 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.
- nicegui_shadcn-0.1.0/AGENT.md +325 -0
- nicegui_shadcn-0.1.0/LICENSE +21 -0
- nicegui_shadcn-0.1.0/PKG-INFO +481 -0
- nicegui_shadcn-0.1.0/README.md +458 -0
- nicegui_shadcn-0.1.0/examples/demo.py +229 -0
- nicegui_shadcn-0.1.0/frontend/tailwind.css +295 -0
- nicegui_shadcn-0.1.0/frontend/vendor/reka-entry.js +135 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/__init__.py +46 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/_tw_merge.py +274 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/__init__.py +44 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/base.py +135 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/icon.py +35 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_accordion.py +133 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_accordion.vue +27 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_accordion_content.vue +17 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_accordion_item.vue +18 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_accordion_trigger.vue +33 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_button.py +162 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_checkbox.vue +36 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_controls.py +185 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_dialog.vue +21 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_dialog_content.vue +73 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_dialog_trigger.vue +17 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_display.py +377 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_dropdown_menu.vue +74 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_form.py +229 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_input.vue +28 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_layout.py +153 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_overlay.py +290 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_popover.vue +20 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_popover_content.vue +28 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_popover_trigger.vue +17 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_radio_group.vue +53 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_select.py +63 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_select.vue +108 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_slider.vue +34 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_switch.vue +27 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_tabs.py +136 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_tabs.vue +23 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_tabs_content.vue +17 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_tabs_list.vue +25 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_textarea.vue +26 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_toggle.vue +23 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_toggle_group.vue +34 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/elements/shadcn_tooltip.vue +35 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/icons.py +108 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/shadcn.py +16 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/static/shadcn.css +6425 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/static/vendor/reka-ui.js +15 -0
- nicegui_shadcn-0.1.0/nicegui_shadcn/theme.py +84 -0
- nicegui_shadcn-0.1.0/package-lock.json +2179 -0
- nicegui_shadcn-0.1.0/package.json +32 -0
- nicegui_shadcn-0.1.0/pyproject.toml +68 -0
- nicegui_shadcn-0.1.0/tests/audit_classes.py +123 -0
- nicegui_shadcn-0.1.0/tests/test_render.py +104 -0
- nicegui_shadcn-0.1.0/tests/test_tw_merge.py +105 -0
- nicegui_shadcn-0.1.0/tests/visual_check.mjs +322 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# AGENT.md
|
|
2
|
+
|
|
3
|
+
Notes for anyone — human or agent — changing this repository.
|
|
4
|
+
|
|
5
|
+
`nicegui-shadcn` is a [NiceGUI](https://nicegui.io) extension that provides shadcn/ui
|
|
6
|
+
components. It is unusual in one respect: it has **no build step for the user**, which means
|
|
7
|
+
the Tailwind stylesheet and the Vue component bundle are compiled here and committed. Most
|
|
8
|
+
of the rules below exist to keep that illusion intact.
|
|
9
|
+
|
|
10
|
+
Read `README.md` for what the library does. This file is about how to change it without
|
|
11
|
+
breaking it.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 1. Layout: build inputs vs runtime assets
|
|
16
|
+
|
|
17
|
+
The split is deliberate and load-bearing. `frontend/` is **not** part of the importable
|
|
18
|
+
package, so importing `nicegui_shadcn` never reads a `.css` or a `.js` source file.
|
|
19
|
+
|
|
20
|
+
| Path | Role | Ships in |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `nicegui_shadcn/*.py` | Runtime: `theme.py`, `shadcn.py`, `icons.py`, `_tw_merge.py` | wheel + sdist |
|
|
23
|
+
| `nicegui_shadcn/elements/*.py` | Runtime: one module per component group | wheel + sdist |
|
|
24
|
+
| `nicegui_shadcn/elements/*.vue` | Runtime: templates, parsed by NiceGUI's own `VBuild` | wheel + sdist |
|
|
25
|
+
| `nicegui_shadcn/static/shadcn.css` | Runtime: compiled stylesheet (~191 kB) | wheel + sdist |
|
|
26
|
+
| `nicegui_shadcn/static/vendor/reka-ui.js` | Runtime: tree-shaken reka-ui bundle (~283 kB) | wheel + sdist |
|
|
27
|
+
| `frontend/tailwind.css` | **Build input**: Tailwind v4 source, design tokens, `@source` globs | sdist |
|
|
28
|
+
| `frontend/vendor/reka-entry.js` | **Build input**: esbuild entry for the reka-ui bundle | sdist |
|
|
29
|
+
| `examples/demo.py` | **Build input + dev aid**: the demo the browser test drives | sdist |
|
|
30
|
+
| `tests/` | Dev | sdist |
|
|
31
|
+
| `package.json`, `package-lock.json` | Dev: pins the Tailwind CLI and esbuild | sdist |
|
|
32
|
+
| `node_modules/` | Installed, never shipped | — |
|
|
33
|
+
| `AGENT.md` | This file | sdist |
|
|
34
|
+
|
|
35
|
+
If you add a runtime file, check `[tool.poetry].include` in `pyproject.toml`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. The edit → build → verify loop
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install # once
|
|
43
|
+
npm run build # = build:css + build:vendor, ~250 ms total
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Rebuild the CSS after touching any `.py` or `.vue` under `nicegui_shadcn/`.** Tailwind
|
|
47
|
+
only emits utilities it can see, so a new class in a Python constant or a `.vue` template
|
|
48
|
+
does not exist until you rebuild. Forgetting this is the single most common way to ship a
|
|
49
|
+
component that renders unstyled.
|
|
50
|
+
|
|
51
|
+
Then verify, in this order — each layer catches what the one before it cannot:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python tests/test_tw_merge.py # 37 cases: cn() conflict resolution, no NiceGUI needed
|
|
55
|
+
python tests/test_render.py # 30 checks: every component renders server-side, no browser
|
|
56
|
+
python tests/audit_classes.py # 223 tokens: every class used in Python exists in the CSS
|
|
57
|
+
python examples/demo.py # start the demo (port 8123), then:
|
|
58
|
+
node tests/visual_check.mjs http://127.0.0.1:8123/ # 34 checks in headless Edge
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- `tests/test_render.py` derives one `<stem> registered` check per `*.vue` file, so a new
|
|
62
|
+
component is covered as soon as the file exists.
|
|
63
|
+
- `tests/audit_classes.py` is the only guard against Tailwind's **silent** failure mode: an
|
|
64
|
+
unresolvable candidate is dropped without a warning, and you only see the damage as an
|
|
65
|
+
unstyled element in a browser. Add a component, rebuild, and run it.
|
|
66
|
+
- `tests/visual_check.mjs` asserts computed styles, so it is the only layer that can tell
|
|
67
|
+
whether a design *token* — not merely a class name — reached the element. It writes
|
|
68
|
+
`_shot-light.png` / `_shot-dark.png`; set `SHADCN_SHOT_DIR` to redirect them.
|
|
69
|
+
|
|
70
|
+
`examples/demo.py` runs with `reload=False`, so **restart it after every code change**
|
|
71
|
+
otherwise the browser test checks the previous build.
|
|
72
|
+
|
|
73
|
+
The bundled browser tools are not always available in this environment; the Playwright +
|
|
74
|
+
headless Edge path in `tests/visual_check.mjs` is the supported way to look at a page.
|
|
75
|
+
It already knows the Edge/Chrome paths to try.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## 3. NiceGUI extension contract
|
|
80
|
+
|
|
81
|
+
Everything here was read out of NiceGUI 3.x and is easy to get wrong. Paths are under
|
|
82
|
+
`C:\Python\Py313\Lib\site-packages\nicegui` in this environment.
|
|
83
|
+
|
|
84
|
+
### Registering a component
|
|
85
|
+
|
|
86
|
+
`Element.__init_subclass__(cls, *, component, dependencies, esm, default_classes,
|
|
87
|
+
default_style, default_props)` (`element.py:92-131`). `component` and `dependencies` are
|
|
88
|
+
globs resolved **relative to the file that defines the subclass**, which is why every
|
|
89
|
+
component module carries its own `.vue` next to it.
|
|
90
|
+
|
|
91
|
+
### What `VBuild` will and will not accept
|
|
92
|
+
|
|
93
|
+
`vbuild.py` is a ~100-line `HTMLParser`, **not** a Vue SFC compiler. Its limits shape every
|
|
94
|
+
`.vue` file in this repo:
|
|
95
|
+
|
|
96
|
+
- Exactly **one top-level tag** inside `<template>`, or it raises
|
|
97
|
+
`ValueError('File has more than one top level tag')`.
|
|
98
|
+
- **`<script setup>` does not work.** Only an Options API `export default {...}` is read.
|
|
99
|
+
- **Nested `<template>` truncates the component.** `handle_endtag` closes the captured
|
|
100
|
+
template at the *first* `</template>` it sees, without checking nesting level, so
|
|
101
|
+
`<template v-for>` / `<template #slot>` silently cuts the rest of the markup off.
|
|
102
|
+
Write `v-for` on a real element, or use `<component :is>`.
|
|
103
|
+
- Void tags (`input`, `img`, `br`, `hr`, …) are not level-counted, so an `<input>` root is
|
|
104
|
+
legal.
|
|
105
|
+
- The `<script>` block is served verbatim as an ES module and **must have a default
|
|
106
|
+
export**; `import` statements work.
|
|
107
|
+
- Scoped `<style>` selectors are rewritten to `*[data-<name>]`.
|
|
108
|
+
|
|
109
|
+
### Naming rules
|
|
110
|
+
|
|
111
|
+
- The file **stem** is used as a raw JavaScript identifier (`import { default as NAME } from
|
|
112
|
+
…`), so **no hyphens** — hence `shadcn_tabs_list.vue`, never `shadcn-tabs-list.vue`.
|
|
113
|
+
- Stems must be **globally unique** across both `.vue` and `.js` components
|
|
114
|
+
(`Component._names` is a shared `ClassVar` with an `assert`).
|
|
115
|
+
- All components are prefixed `shadcn_` for that reason.
|
|
116
|
+
|
|
117
|
+
### The `data-<name>` marker
|
|
118
|
+
|
|
119
|
+
`VBuild` puts `data-<stem>` on the **first tag of the template**. If the root is a fragment
|
|
120
|
+
(a reka `*Root`, or a `*Portal`), the attribute is dropped and your mount assertions fail.
|
|
121
|
+
Two consequences:
|
|
122
|
+
|
|
123
|
+
- Put an explicit marker on the meaningful element:
|
|
124
|
+
`data-shadcn_select`, `data-shadcn_dialog_content`, `data-shadcn_popover_content`,
|
|
125
|
+
`data-shadcn_dropdown_menu`.
|
|
126
|
+
- A fragment root needs `inheritAttrs: false` in `export default`, otherwise Vue warns about
|
|
127
|
+
extraneous non-prop attributes. Use `v-bind="$attrs"` on the element that should receive
|
|
128
|
+
the caller's classes.
|
|
129
|
+
|
|
130
|
+
### Props and events
|
|
131
|
+
|
|
132
|
+
- `element._props` entries become **real Vue props** on the resolved component
|
|
133
|
+
(`static/nicegui.js:230-325`), camelized by Vue: Python `model-value` → `modelValue`.
|
|
134
|
+
- Python event names go through `event_type_to_camel_case`, and the client only
|
|
135
|
+
capitalises the first letter, so `self.on('update:model-value', …)` becomes
|
|
136
|
+
`onUpdate:modelValue` — exactly what `emit('update:modelValue', v)` looks up.
|
|
137
|
+
- A component that emits a **single** value arrives server-side as that value, not a list
|
|
138
|
+
(`client.py:347-349` unwraps `len(args) == 1`).
|
|
139
|
+
- `ValueElement` uses `VALUE_PROP = 'model-value'`; set `LOOPBACK = False` when the
|
|
140
|
+
component writes the value back itself (see `shadcn_form.py`).
|
|
141
|
+
- **Declare every prop you pass.** An undeclared prop falls through to the DOM; NiceGUI's
|
|
142
|
+
own `loopback` prop once leaked onto the select trigger as `<button loopback="true">`
|
|
143
|
+
until `shadcn_select.vue` declared it.
|
|
144
|
+
|
|
145
|
+
### Text and children
|
|
146
|
+
|
|
147
|
+
`renderRecursively` unshifts `element.text` **before** the children of the default slot.
|
|
148
|
+
So an element's own `_text` always renders first — a Button cannot use `_text` for its
|
|
149
|
+
label if it wants an icon on the left. Build the label as a child element instead.
|
|
150
|
+
|
|
151
|
+
Use `elements/base.py:Text` (a `<span>`) rather than `ui.label` (a `<div>`) for anything
|
|
152
|
+
inside a `<button>`; a `<div>` in a button is invalid HTML.
|
|
153
|
+
|
|
154
|
+
### Page-level registration
|
|
155
|
+
|
|
156
|
+
`theme.py` does three things at import time, all of which must happen before the first
|
|
157
|
+
component renders:
|
|
158
|
+
|
|
159
|
+
1. `app.add_static_files('/_nicegui_shadcn', …)`;
|
|
160
|
+
2. `register_importmap_override('reka-ui', …)` so `.vue` scripts can
|
|
161
|
+
`import { … } from 'reka-ui'`;
|
|
162
|
+
3. `ui.add_head_html('<link rel="stylesheet" …>', shared=True)`.
|
|
163
|
+
|
|
164
|
+
Use a `<link>`, **not** `ui.add_css`. `add_css` inlines the stylesheet into an
|
|
165
|
+
`addStyle(...)` JavaScript call, so it only lands after the socket handshake — a visible
|
|
166
|
+
flash of unstyled content on every reload. `shared=True` is what makes the call legal
|
|
167
|
+
outside a client context (import time).
|
|
168
|
+
|
|
169
|
+
Keep `vue` **external** in the reka-ui bundle (`esbuild --external:vue`). NiceGUI already
|
|
170
|
+
puts its own Vue 3.5 on the import map; a second copy breaks `provide`/`inject` and
|
|
171
|
+
`Teleport` across components.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 4. Tailwind and the cascade
|
|
176
|
+
|
|
177
|
+
NiceGUI declares the layer order once, in `templates/index.html:13`:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
@layer theme, base, quasar, nicegui, components, utilities, overrides, quasar_importants;
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Everything we emit joins those layers.
|
|
184
|
+
|
|
185
|
+
- **Import `utilities` with `layer(utilities) important`.** `quasar.important.css` lands in
|
|
186
|
+
the *last* layer and contains `.bg-primary { background: var(--q-primary) !important }`.
|
|
187
|
+
Layer precedence is reversed for important declarations, so making ours important too is
|
|
188
|
+
the only way `shadcn.button()` comes out shadcn-black instead of Quasar-blue.
|
|
189
|
+
- **Do not import Tailwind's preflight.** Quasar already normalises; a second reset fights
|
|
190
|
+
it. The one preflight rule we need — `[hidden] { display: none !important }`, used by
|
|
191
|
+
mounted-but-closed panels — is added by hand in `@layer base`.
|
|
192
|
+
- Bind the dark variant to the class NiceGUI already toggles:
|
|
193
|
+
`@custom-variant dark (&:where(body.body--dark, body.body--dark *));`
|
|
194
|
+
- Expose design tokens through `@theme inline { --color-primary: var(--primary); … }` so
|
|
195
|
+
`bg-primary` / `text-muted-foreground` resolve to the shadcn variables.
|
|
196
|
+
- The build uses `source(none)` plus explicit `@source` globs, so the compiled CSS is a
|
|
197
|
+
pure function of the checkout's `.py`/`.vue` files and does not drift with where it was
|
|
198
|
+
built.
|
|
199
|
+
- Runtime `classes=` strings are covered by `@source inline(...)` blocks. That vocabulary is
|
|
200
|
+
a **deliberate subset**: semantic colours plus common layout/spacing/typography. Raw
|
|
201
|
+
palette colours (`bg-blue-600`) are not generated. Extending it means adding to the
|
|
202
|
+
matrix and rebuilding; the README documents the user-facing workflow.
|
|
203
|
+
|
|
204
|
+
### Where class strings live
|
|
205
|
+
|
|
206
|
+
**All CSS classes live in Python**, in module-level constants or `default_classes`. The
|
|
207
|
+
`.vue` templates contain no `class="…"` of their own except `:class` bindings fed from a
|
|
208
|
+
prop. That is what makes `classes=` mergeable and auditable.
|
|
209
|
+
|
|
210
|
+
`tests/audit_classes.py` encodes the conventions it depends on:
|
|
211
|
+
|
|
212
|
+
- constants must be named `_?[A-Z][A-Z0-9_]*_(BASE|CLASSES|VARIANTS|SIZES)`;
|
|
213
|
+
- it parses Python with `ast`, not regex, and reads `default_classes=`, `classes=` and
|
|
214
|
+
`.classes(...)` keyword arguments;
|
|
215
|
+
- `CLASS_ATTR_RE` uses a lookbehind so `:class` / `v-bind:class` (JavaScript expressions) are
|
|
216
|
+
ignored.
|
|
217
|
+
|
|
218
|
+
If you rename a constant or move a class into a template, the audit will either go quiet or
|
|
219
|
+
go red — check it.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 5. reka-ui
|
|
224
|
+
|
|
225
|
+
The bundle is built from `frontend/vendor/reka-entry.js`, which re-exports **only** the
|
|
226
|
+
primitives we wrap so esbuild can tree-shake the rest. Rebuild it after changing that file:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
npm run build:vendor
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Two things worth knowing:
|
|
233
|
+
|
|
234
|
+
- **`provide`/`inject` does not survive a NiceGUI slot for `TabsList` → `TabsTrigger`.**
|
|
235
|
+
reka's `TabsTrigger` uses `RovingFocusItem` unconditionally and throws
|
|
236
|
+
`` Injection `Symbol(RovingFocusGroupContext)` not found ``. Every *other* cross-boundary
|
|
237
|
+
inject works (dialog, popover, dropdown, tooltip, select, accordion). The fix was to make
|
|
238
|
+
the tab bar data-driven: `shadcn_tabs_list.vue` takes a `tabs` array prop and generates
|
|
239
|
+
the triggers itself, which keeps reka's real roving-focus/arrow-key behaviour inside one
|
|
240
|
+
component tree. Anything else that needs a context provided by a sibling should follow
|
|
241
|
+
the same pattern.
|
|
242
|
+
- **Force-mounted content is not hidden by reka.** It only sets `data-state`, never
|
|
243
|
+
`hidden`. The children of `TabsContent`/`AccordionContent` stay mounted on purpose (so a
|
|
244
|
+
server-side update always finds its element), and hiding is done with
|
|
245
|
+
`data-[state=inactive]:hidden` / `data-[state=closed]:hidden`. The cost is that the
|
|
246
|
+
accordion has no *closing* animation.
|
|
247
|
+
- `PinInputSeparator` does not exist in reka-ui 2.10.5. Check the export list before adding
|
|
248
|
+
an import; a bad import fails the esbuild step with
|
|
249
|
+
`No matching export in "node_modules/reka-ui/dist/index.js"`.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 6. Python API conventions
|
|
254
|
+
|
|
255
|
+
- **`cn()` semantics are mandatory for components that take `classes=`.** NiceGUI's
|
|
256
|
+
`.classes()` *appends*, so `shadcn.button('Save', classes='bg-destructive')` would emit
|
|
257
|
+
both `bg-primary` and `bg-destructive` and let CSS source order decide. `ShadcnElement`
|
|
258
|
+
merges through `_tw_merge.tw_merge` so the caller wins.
|
|
259
|
+
- In `_tw_merge`, always slice with `b[len(prefix):]`. Two bugs came from hand-counted
|
|
260
|
+
indices (`b[6:]` for `border-`, `b[7:]` for `rounded-`), and both made unrelated utilities
|
|
261
|
+
share a conflict group.
|
|
262
|
+
- Validate enum-ish arguments with `option(kind, value, options)` so the error names the
|
|
263
|
+
valid choices.
|
|
264
|
+
- Expose a lowercase factory (`shadcn.button(...)`) and keep the class in the same module.
|
|
265
|
+
`elements/__init__.py` builds `__all__` from each module's `__all__` **before** the star
|
|
266
|
+
imports, because `from .icon import *` rebinds the name `icon` from the module to the
|
|
267
|
+
factory.
|
|
268
|
+
- Icons are inlined in `icons.py` (39 glyphs), not bundled from `lucide-vue-next`; a new
|
|
269
|
+
glyph is one entry in `_ICONS`.
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 7. Packaging
|
|
274
|
+
|
|
275
|
+
Poetry, PEP 621 metadata, `poetry-core` backend.
|
|
276
|
+
|
|
277
|
+
- `requires-python` **must** carry an upper bound (`>=3.10,<4.0`). A bare `>=3.10` makes
|
|
278
|
+
`poetry lock` fail with *"a possible solution would be to set the `python` property to
|
|
279
|
+
`>=3.10,<4`"*.
|
|
280
|
+
- `license = "MIT"` + `license-files = ["LICENSE"]` (PEP 639). Do not add a
|
|
281
|
+
`License ::` classifier; it conflicts.
|
|
282
|
+
- Poetry honours `.gitignore` when building the sdist. Anything you add there also
|
|
283
|
+
disappears from the source distribution — that is how `node_modules/` and `dist/` stay
|
|
284
|
+
out.
|
|
285
|
+
- **Poetry 2.4.1 defines no `testpypi` repository.** `poetry publish -r testpypi` fails with
|
|
286
|
+
`Repository testpypi is not defined` until you run
|
|
287
|
+
`poetry config repositories.testpypi https://test.pypi.org/legacy/`.
|
|
288
|
+
- `poetry publish` uploads whatever is in `dist/`; it does not rebuild unless you pass
|
|
289
|
+
`--build`. Use `poetry publish --dry-run` to check repository resolution and the artifact
|
|
290
|
+
list without credentials.
|
|
291
|
+
- After changing the runtime assets, rebuild the wheel and **verify it in a fresh venv**.
|
|
292
|
+
Run the check with a working directory outside the repository, or `C:\dsh` shadows
|
|
293
|
+
site-packages on `sys.path` and you will happily test the checkout instead of the wheel.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## 8. Pitfalls already paid for
|
|
298
|
+
|
|
299
|
+
A short list of mistakes that cost real time here. Most were invisible until a browser
|
|
300
|
+
looked at the result.
|
|
301
|
+
|
|
302
|
+
1. **Tailwind drops unknown classes silently.** A typo produces no warning and no rule.
|
|
303
|
+
`tests/audit_classes.py` exists for exactly this.
|
|
304
|
+
2. **`getComputedStyle` reports a blockified `display`.** A button that is a flex item
|
|
305
|
+
reports `flex` even though its class list says `inline-flex`, and removing the class
|
|
306
|
+
gives `block`. Assert `flex` *or* `inline-flex`; the class list is the source of truth.
|
|
307
|
+
3. **A page-level overflow check misses per-element overflow.** A `size="icon"` button with
|
|
308
|
+
a text label overflowed by 22 px while `document.scrollWidth` was clean; the check had to
|
|
309
|
+
compare each element's `scrollWidth` against its `clientWidth`. The Button now degrades
|
|
310
|
+
to `sr-only` + `aria-label`.
|
|
311
|
+
4. **Assign text to the element that should show it.** `Avatar.__init__` once did
|
|
312
|
+
`self._text = fallback`, which put "CN" in the outer container next to the circle instead
|
|
313
|
+
of inside it. For a child element, assign to the child.
|
|
314
|
+
5. **Compare tokens, not colours.** Assertions convert through a probe element
|
|
315
|
+
(`background-color: var(--primary)`) and compare computed strings; Chromium echoes
|
|
316
|
+
`oklch()` back unchanged, so the test proves *which token* was used rather than that some
|
|
317
|
+
dark colour appeared.
|
|
318
|
+
6. **`Button` has no `.vue`.** It renders a native `<button>` (`tag='button'`). Asserting
|
|
319
|
+
`tpl-shadcn_button` is wrong; the repository has exactly 24 `.vue` files, which is why
|
|
320
|
+
`test_render.py` has 30 checks (6 static + 24).
|
|
321
|
+
7. **NiceGUI 3.x removed `classes=` / `style=` / `props=` from `Element.__init__`.** Set them
|
|
322
|
+
through `_props` / `.classes()` / `.style()` after construction, as `base.py` does.
|
|
323
|
+
8. **`add_slot('default', template)` cannot inject markup.** `_collect_slot_dict()` excludes
|
|
324
|
+
the default slot, so a default-slot template is never sent to the client. Build real
|
|
325
|
+
child elements.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 XiangQinxi
|
|
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.
|