shinyglass 0.5.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.
- shinyglass-0.5.0/.gitignore +31 -0
- shinyglass-0.5.0/PKG-INFO +223 -0
- shinyglass-0.5.0/README.md +200 -0
- shinyglass-0.5.0/examples/app_sidebar.py +151 -0
- shinyglass-0.5.0/hatch_build.py +39 -0
- shinyglass-0.5.0/pyproject.toml +50 -0
- shinyglass-0.5.0/scripts/vendor_assets.py +188 -0
- shinyglass-0.5.0/src/shinyglass/__init__.py +53 -0
- shinyglass-0.5.0/src/shinyglass/_assets.py +107 -0
- shinyglass-0.5.0/src/shinyglass/_version.py +10 -0
- shinyglass-0.5.0/src/shinyglass/controls.py +270 -0
- shinyglass-0.5.0/src/shinyglass/static/MANIFEST.txt +3 -0
- shinyglass-0.5.0/src/shinyglass/static/glass-intensity-head.js +62 -0
- shinyglass-0.5.0/src/shinyglass/static/glass-ios27.js +1123 -0
- shinyglass-0.5.0/src/shinyglass/static/glass-rules-dark.css +6 -0
- shinyglass-0.5.0/src/shinyglass/static/glass-rules-light.css +6 -0
- shinyglass-0.5.0/src/shinyglass/static/glass.scss +7281 -0
- shinyglass-0.5.0/src/shinyglass/static/shiny-glass.js +2269 -0
- shinyglass-0.5.0/src/shinyglass/static/theme-dark.css +10 -0
- shinyglass-0.5.0/src/shinyglass/static/theme-light.css +10 -0
- shinyglass-0.5.0/src/shinyglass/theme.py +717 -0
- shinyglass-0.5.0/src/shinyglass/tokens.py +219 -0
- shinyglass-0.5.0/tests/test_controls_tokens.py +124 -0
- shinyglass-0.5.0/tests/test_theme.py +219 -0
- shinyglass-0.5.0/tests/test_wheel_isolation.py +45 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.Rproj.user
|
|
2
|
+
.Rhistory
|
|
3
|
+
.RData
|
|
4
|
+
.Ruserdata
|
|
5
|
+
.Renviron
|
|
6
|
+
*.o
|
|
7
|
+
*.so
|
|
8
|
+
*.tar.gz
|
|
9
|
+
.DS_Store
|
|
10
|
+
.Rcheck/
|
|
11
|
+
*Rcheck/
|
|
12
|
+
*.Rproj
|
|
13
|
+
inst/examples/screenshot*.png
|
|
14
|
+
visual-test-output/
|
|
15
|
+
docs/
|
|
16
|
+
inst/external/
|
|
17
|
+
# Python
|
|
18
|
+
python/**/__pycache__/
|
|
19
|
+
python/**/*.egg-info/
|
|
20
|
+
python/.pytest_cache/
|
|
21
|
+
python/.venv/
|
|
22
|
+
python/dist/
|
|
23
|
+
python/build/
|
|
24
|
+
python/.ruff_cache/
|
|
25
|
+
*.pyc
|
|
26
|
+
# Keep vendored static CSS/JS in git; ignore only caches
|
|
27
|
+
|
|
28
|
+
# Browser regression harness
|
|
29
|
+
node_modules/
|
|
30
|
+
test-results/
|
|
31
|
+
playwright-report/
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: shinyglass
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: Liquid Glass themes for Shiny for Python
|
|
5
|
+
Project-URL: Homepage, https://github.com/ericrayanderson/shinyglass
|
|
6
|
+
Project-URL: Repository, https://github.com/ericrayanderson/shinyglass
|
|
7
|
+
Project-URL: Issues, https://github.com/ericrayanderson/shinyglass/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/ericrayanderson/shinyglass/blob/main/NEWS.md
|
|
9
|
+
Author-email: Eric Anderson <eric.ray.anderson@gmail.com>
|
|
10
|
+
License-Expression: GPL-3.0-only
|
|
11
|
+
Keywords: bootstrap,bslib,glass,shiny,theme
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Requires-Dist: htmltools>=0.5
|
|
14
|
+
Requires-Dist: shiny>=1.0
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: build; extra == 'dev'
|
|
17
|
+
Requires-Dist: libsass>=0.22; extra == 'dev'
|
|
18
|
+
Requires-Dist: matplotlib; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
20
|
+
Provides-Extra: theme
|
|
21
|
+
Requires-Dist: libsass>=0.22; extra == 'theme'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# shinyglass (Python)
|
|
25
|
+
|
|
26
|
+
Liquid Glass themes for **Shiny for Python**, sharing design assets with the R
|
|
27
|
+
package under [`inst/`](https://github.com/ericrayanderson/shinyglass/tree/main/inst).
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
### From PyPI
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install shinyglass
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### From a git checkout (developers)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
cd python
|
|
41
|
+
pip install -e ".[dev,theme]"
|
|
42
|
+
python scripts/vendor_assets.py # compile CSS/JS into src/shinyglass/static/
|
|
43
|
+
pytest
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### From a built wheel
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cd python
|
|
50
|
+
python scripts/vendor_assets.py
|
|
51
|
+
python -m build --wheel
|
|
52
|
+
pip install dist/shinyglass-*.whl
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Default themes use **precompiled CSS**. **libsass is not required** at runtime.
|
|
56
|
+
For custom `primary` / `blur` / `radius`, install the optional extra:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install "shinyglass[theme]" # pulls libsass
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from shiny import App, ui
|
|
66
|
+
from shinyglass import glass_theme
|
|
67
|
+
|
|
68
|
+
app_ui = ui.page_sidebar(
|
|
69
|
+
ui.sidebar("Filters", ui.input_text("q", "Query")),
|
|
70
|
+
ui.card(ui.card_header("Hello"), "Glass content"),
|
|
71
|
+
theme=glass_theme(preset="light"), # or "dark"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
app = App(app_ui, None)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Parameters (same spirit as R)
|
|
78
|
+
|
|
79
|
+
| Argument | Default | |
|
|
80
|
+
| --- | --- | --- |
|
|
81
|
+
| `preset` | `"light"` | `"light"`, `"dark"`, or `"auto"` |
|
|
82
|
+
| `primary` | `"#007AFF"` | Accent (custom → needs `[theme]`) |
|
|
83
|
+
| `blur` | `36` | Backdrop blur px |
|
|
84
|
+
| `saturation` | `200` | Backdrop saturate % |
|
|
85
|
+
| `radius` | `"1.5rem"` | Corner radius |
|
|
86
|
+
| `intensity` | `0.5` | Ultra Clear (`0`) to Tinted (`1`); iOS 27 midpoint. The web cannot read the OS slider. |
|
|
87
|
+
| `plot_surface` | `"clear"` | `"opaque"` densifies plot / table hosts (~94% panel); chrome stays glass |
|
|
88
|
+
| `scene` | `"default"` | `tahoe` / `dusk` / `mesh` / `aurora` / `harbor` / `grove` |
|
|
89
|
+
| `flatten` | `False` | Print / screenshot mode (or `?glass_flatten=1`) |
|
|
90
|
+
| `persist` | `False` | Remember preset, intensity, scene, plot surface |
|
|
91
|
+
| `refraction` | `True` | Chromium squircle lens. Safari/Firefox keep blur and tint |
|
|
92
|
+
| `morph` | `True` | Spring the active pill. Menus scale out of the trigger |
|
|
93
|
+
| `press` | `True` | Springy press on buttons and nav links |
|
|
94
|
+
| `scroll_edge` | `True` | Toolbar backing while content scrolls underneath |
|
|
95
|
+
| `minimize_tabs` | `True` | Shrink tab bars on scroll down |
|
|
96
|
+
| `icon_style` | `"clear"` | Monochrome bar icons. `"color"` keeps author colors |
|
|
97
|
+
| `floating_sidebar` | `False` | Opt-in detached sidebar |
|
|
98
|
+
|
|
99
|
+
### Controls and tokens
|
|
100
|
+
|
|
101
|
+
These helpers match the R functions of the same names. Inputs take `id`
|
|
102
|
+
instead of `inputId`, and choice dicts map values to labels like
|
|
103
|
+
`ui.input_select()`.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from shiny import App, reactive, ui
|
|
107
|
+
import shinyglass as sg
|
|
108
|
+
|
|
109
|
+
theme = sg.glass_add_tokens(sg.glass_theme(preset="auto"), blur="28px")
|
|
110
|
+
|
|
111
|
+
app_ui = ui.page_sidebar(
|
|
112
|
+
ui.sidebar(
|
|
113
|
+
sg.glass_theme_toggle(),
|
|
114
|
+
sg.glass_preset_input(),
|
|
115
|
+
sg.glass_accent_input(),
|
|
116
|
+
sg.glass_scene_input(),
|
|
117
|
+
ui.input_action_button("flat", "Flatten for capture"),
|
|
118
|
+
),
|
|
119
|
+
ui.card(ui.card_header("Hello"), "Glass content"),
|
|
120
|
+
title="Controls demo",
|
|
121
|
+
theme=theme,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def server(input, output, session):
|
|
126
|
+
sg.observe_glass_theme_toggle(input, session)
|
|
127
|
+
sg.observe_glass_preset_input(input, session)
|
|
128
|
+
sg.observe_glass_accent(input, session)
|
|
129
|
+
sg.observe_glass_scene(input, session)
|
|
130
|
+
|
|
131
|
+
@reactive.effect
|
|
132
|
+
@reactive.event(input.flat)
|
|
133
|
+
def _():
|
|
134
|
+
sg.glass_flatten(session, True)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
app = App(app_ui, server)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
| Helper | What it does |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `glass_theme_toggle()` | Light / Dark / Auto button group |
|
|
143
|
+
| `glass_preset_input()` | Light / Dark / Auto select, applied as soon as it changes |
|
|
144
|
+
| `glass_accent_input()` | iOS accent color wells (`glass_system_colors()`) |
|
|
145
|
+
| `glass_scene_input()` | Wallpaper scene select (`glass_scenes()`) |
|
|
146
|
+
| `observe_glass_*()` | Echo those inputs through `update_glass_theme()` on the server |
|
|
147
|
+
| `glass_flatten(session, on)` | Turn blur off for print, PDF, and screenshots |
|
|
148
|
+
| `glass_css_tokens()` | Catalog of public `--glass-*` CSS variables |
|
|
149
|
+
| `glass_token_pack(preset)` | Default variable values for light or dark |
|
|
150
|
+
| `glass_add_tokens(theme, ...)` | Override variables on a theme without editing SCSS |
|
|
151
|
+
|
|
152
|
+
## Demo
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
cd python
|
|
156
|
+
shiny run examples/app_sidebar.py
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Layout (Option 2)
|
|
160
|
+
|
|
161
|
+
| Path | Role |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `../inst/scss/glass.scss` | Shared source of truth |
|
|
164
|
+
| `../inst/js/shiny-glass.js` | Shared JS |
|
|
165
|
+
| `src/shinyglass/static/` | **Vendored** copy + precompiled `theme-*.css` (ships in wheel) |
|
|
166
|
+
| `scripts/vendor_assets.py` | Build step: copy + compile |
|
|
167
|
+
| `hatch_build.py` | Hatch hook re-vendors when monorepo `inst/` is present |
|
|
168
|
+
|
|
169
|
+
Asset resolution order at runtime:
|
|
170
|
+
|
|
171
|
+
1. Package `static/` (wheel / vendored)
|
|
172
|
+
2. Monorepo `inst/` via `SHINYGLASS_PKG_ROOT` or path walk (editable dev)
|
|
173
|
+
|
|
174
|
+
## Tests & CI
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
pytest
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
GitHub Actions (`.github/workflows/python.yml`):
|
|
181
|
+
|
|
182
|
+
1. Editable install + vendor + sync check + pytest
|
|
183
|
+
2. Build wheel, install in a **clean venv** with no monorepo `inst/`, re-run tests
|
|
184
|
+
|
|
185
|
+
## R / Python feature scope
|
|
186
|
+
|
|
187
|
+
R is the primary implementation. Python is experimental and does not yet offer
|
|
188
|
+
all of the R runtime controls. Shared assets do not imply API parity.
|
|
189
|
+
|
|
190
|
+
| Feature | R | Python |
|
|
191
|
+
| --- | --- | --- |
|
|
192
|
+
| Presets | Light / Dark / Auto | Light / Dark / Auto |
|
|
193
|
+
| Live preset / accent updates | Public server and UI helpers | `update_glass_theme()` + shared JS |
|
|
194
|
+
| Intensity / material controls | Public API and slider | `intensity=` + `glass_intensity_slider()` |
|
|
195
|
+
| `plot_surface` / scenes / flatten | Public API | Same knobs on `glass_theme()` |
|
|
196
|
+
| Scroll-edge chrome densify | Shared JS / CSS | Shared vendored JS / CSS |
|
|
197
|
+
| Default blur / radius | 36 px / `1.5rem` | 36 px / `1.5rem` |
|
|
198
|
+
| Default CSS delivery | bslib Sass compilation | Precompiled wheel assets |
|
|
199
|
+
| Custom theme knobs | bslib / sass | Optional `[theme]` extra |
|
|
200
|
+
| Theme toggle, preset / accent / scene inputs | Public UI + observers | Same helpers and `observe_glass_*()` |
|
|
201
|
+
| Flatten for capture | `glass_flatten()` | `glass_flatten()` |
|
|
202
|
+
| CSS token catalog / overrides | `glass_css_tokens()`, `glass_token_pack()`, `glass_add_tokens()` | Same names |
|
|
203
|
+
|
|
204
|
+
When editing shared JavaScript, update its vendored copy too. When editing SCSS,
|
|
205
|
+
run `python scripts/vendor_assets.py` from this directory to rebuild wheel CSS.
|
|
206
|
+
The Python CI jobs verify editable and isolated wheel installations.
|
|
207
|
+
|
|
208
|
+
The editable job also re-runs `vendor_assets.py` and fails if the vendored
|
|
209
|
+
`glass.scss`, JavaScript, or `glass-rules-*.css` files differ from what is
|
|
210
|
+
committed. If that check fails, run `python python/scripts/vendor_assets.py`
|
|
211
|
+
from the repo root and commit the updated files under
|
|
212
|
+
`src/shinyglass/static/`. The `theme-*.css` bundles are not compared because
|
|
213
|
+
they include Bootstrap from whichever shiny version is installed.
|
|
214
|
+
|
|
215
|
+
## Not yet
|
|
216
|
+
|
|
217
|
+
- Published PyPI release (package version tracks R **0.5.0**; not on PyPI)
|
|
218
|
+
- Full R demo gallery parity
|
|
219
|
+
- `glass_page()`, `glass_plot_surface_input()`, `glass_resolved_preset()`,
|
|
220
|
+
`observe_glass()` and the intensity / plot surface observers
|
|
221
|
+
- Plot and table helpers (`theme_glass()`, `plotly_glass()`, `gt_theme_glass()`,
|
|
222
|
+
`dt_options_glass()`, `glass_plot_colors()`)
|
|
223
|
+
- React components
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# shinyglass (Python)
|
|
2
|
+
|
|
3
|
+
Liquid Glass themes for **Shiny for Python**, sharing design assets with the R
|
|
4
|
+
package under [`inst/`](https://github.com/ericrayanderson/shinyglass/tree/main/inst).
|
|
5
|
+
|
|
6
|
+
## Install
|
|
7
|
+
|
|
8
|
+
### From PyPI
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install shinyglass
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### From a git checkout (developers)
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cd python
|
|
18
|
+
pip install -e ".[dev,theme]"
|
|
19
|
+
python scripts/vendor_assets.py # compile CSS/JS into src/shinyglass/static/
|
|
20
|
+
pytest
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### From a built wheel
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd python
|
|
27
|
+
python scripts/vendor_assets.py
|
|
28
|
+
python -m build --wheel
|
|
29
|
+
pip install dist/shinyglass-*.whl
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Default themes use **precompiled CSS**. **libsass is not required** at runtime.
|
|
33
|
+
For custom `primary` / `blur` / `radius`, install the optional extra:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install "shinyglass[theme]" # pulls libsass
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from shiny import App, ui
|
|
43
|
+
from shinyglass import glass_theme
|
|
44
|
+
|
|
45
|
+
app_ui = ui.page_sidebar(
|
|
46
|
+
ui.sidebar("Filters", ui.input_text("q", "Query")),
|
|
47
|
+
ui.card(ui.card_header("Hello"), "Glass content"),
|
|
48
|
+
theme=glass_theme(preset="light"), # or "dark"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
app = App(app_ui, None)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Parameters (same spirit as R)
|
|
55
|
+
|
|
56
|
+
| Argument | Default | |
|
|
57
|
+
| --- | --- | --- |
|
|
58
|
+
| `preset` | `"light"` | `"light"`, `"dark"`, or `"auto"` |
|
|
59
|
+
| `primary` | `"#007AFF"` | Accent (custom → needs `[theme]`) |
|
|
60
|
+
| `blur` | `36` | Backdrop blur px |
|
|
61
|
+
| `saturation` | `200` | Backdrop saturate % |
|
|
62
|
+
| `radius` | `"1.5rem"` | Corner radius |
|
|
63
|
+
| `intensity` | `0.5` | Ultra Clear (`0`) to Tinted (`1`); iOS 27 midpoint. The web cannot read the OS slider. |
|
|
64
|
+
| `plot_surface` | `"clear"` | `"opaque"` densifies plot / table hosts (~94% panel); chrome stays glass |
|
|
65
|
+
| `scene` | `"default"` | `tahoe` / `dusk` / `mesh` / `aurora` / `harbor` / `grove` |
|
|
66
|
+
| `flatten` | `False` | Print / screenshot mode (or `?glass_flatten=1`) |
|
|
67
|
+
| `persist` | `False` | Remember preset, intensity, scene, plot surface |
|
|
68
|
+
| `refraction` | `True` | Chromium squircle lens. Safari/Firefox keep blur and tint |
|
|
69
|
+
| `morph` | `True` | Spring the active pill. Menus scale out of the trigger |
|
|
70
|
+
| `press` | `True` | Springy press on buttons and nav links |
|
|
71
|
+
| `scroll_edge` | `True` | Toolbar backing while content scrolls underneath |
|
|
72
|
+
| `minimize_tabs` | `True` | Shrink tab bars on scroll down |
|
|
73
|
+
| `icon_style` | `"clear"` | Monochrome bar icons. `"color"` keeps author colors |
|
|
74
|
+
| `floating_sidebar` | `False` | Opt-in detached sidebar |
|
|
75
|
+
|
|
76
|
+
### Controls and tokens
|
|
77
|
+
|
|
78
|
+
These helpers match the R functions of the same names. Inputs take `id`
|
|
79
|
+
instead of `inputId`, and choice dicts map values to labels like
|
|
80
|
+
`ui.input_select()`.
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from shiny import App, reactive, ui
|
|
84
|
+
import shinyglass as sg
|
|
85
|
+
|
|
86
|
+
theme = sg.glass_add_tokens(sg.glass_theme(preset="auto"), blur="28px")
|
|
87
|
+
|
|
88
|
+
app_ui = ui.page_sidebar(
|
|
89
|
+
ui.sidebar(
|
|
90
|
+
sg.glass_theme_toggle(),
|
|
91
|
+
sg.glass_preset_input(),
|
|
92
|
+
sg.glass_accent_input(),
|
|
93
|
+
sg.glass_scene_input(),
|
|
94
|
+
ui.input_action_button("flat", "Flatten for capture"),
|
|
95
|
+
),
|
|
96
|
+
ui.card(ui.card_header("Hello"), "Glass content"),
|
|
97
|
+
title="Controls demo",
|
|
98
|
+
theme=theme,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def server(input, output, session):
|
|
103
|
+
sg.observe_glass_theme_toggle(input, session)
|
|
104
|
+
sg.observe_glass_preset_input(input, session)
|
|
105
|
+
sg.observe_glass_accent(input, session)
|
|
106
|
+
sg.observe_glass_scene(input, session)
|
|
107
|
+
|
|
108
|
+
@reactive.effect
|
|
109
|
+
@reactive.event(input.flat)
|
|
110
|
+
def _():
|
|
111
|
+
sg.glass_flatten(session, True)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
app = App(app_ui, server)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Helper | What it does |
|
|
118
|
+
| --- | --- |
|
|
119
|
+
| `glass_theme_toggle()` | Light / Dark / Auto button group |
|
|
120
|
+
| `glass_preset_input()` | Light / Dark / Auto select, applied as soon as it changes |
|
|
121
|
+
| `glass_accent_input()` | iOS accent color wells (`glass_system_colors()`) |
|
|
122
|
+
| `glass_scene_input()` | Wallpaper scene select (`glass_scenes()`) |
|
|
123
|
+
| `observe_glass_*()` | Echo those inputs through `update_glass_theme()` on the server |
|
|
124
|
+
| `glass_flatten(session, on)` | Turn blur off for print, PDF, and screenshots |
|
|
125
|
+
| `glass_css_tokens()` | Catalog of public `--glass-*` CSS variables |
|
|
126
|
+
| `glass_token_pack(preset)` | Default variable values for light or dark |
|
|
127
|
+
| `glass_add_tokens(theme, ...)` | Override variables on a theme without editing SCSS |
|
|
128
|
+
|
|
129
|
+
## Demo
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
cd python
|
|
133
|
+
shiny run examples/app_sidebar.py
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Layout (Option 2)
|
|
137
|
+
|
|
138
|
+
| Path | Role |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `../inst/scss/glass.scss` | Shared source of truth |
|
|
141
|
+
| `../inst/js/shiny-glass.js` | Shared JS |
|
|
142
|
+
| `src/shinyglass/static/` | **Vendored** copy + precompiled `theme-*.css` (ships in wheel) |
|
|
143
|
+
| `scripts/vendor_assets.py` | Build step: copy + compile |
|
|
144
|
+
| `hatch_build.py` | Hatch hook re-vendors when monorepo `inst/` is present |
|
|
145
|
+
|
|
146
|
+
Asset resolution order at runtime:
|
|
147
|
+
|
|
148
|
+
1. Package `static/` (wheel / vendored)
|
|
149
|
+
2. Monorepo `inst/` via `SHINYGLASS_PKG_ROOT` or path walk (editable dev)
|
|
150
|
+
|
|
151
|
+
## Tests & CI
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pytest
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
GitHub Actions (`.github/workflows/python.yml`):
|
|
158
|
+
|
|
159
|
+
1. Editable install + vendor + sync check + pytest
|
|
160
|
+
2. Build wheel, install in a **clean venv** with no monorepo `inst/`, re-run tests
|
|
161
|
+
|
|
162
|
+
## R / Python feature scope
|
|
163
|
+
|
|
164
|
+
R is the primary implementation. Python is experimental and does not yet offer
|
|
165
|
+
all of the R runtime controls. Shared assets do not imply API parity.
|
|
166
|
+
|
|
167
|
+
| Feature | R | Python |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| Presets | Light / Dark / Auto | Light / Dark / Auto |
|
|
170
|
+
| Live preset / accent updates | Public server and UI helpers | `update_glass_theme()` + shared JS |
|
|
171
|
+
| Intensity / material controls | Public API and slider | `intensity=` + `glass_intensity_slider()` |
|
|
172
|
+
| `plot_surface` / scenes / flatten | Public API | Same knobs on `glass_theme()` |
|
|
173
|
+
| Scroll-edge chrome densify | Shared JS / CSS | Shared vendored JS / CSS |
|
|
174
|
+
| Default blur / radius | 36 px / `1.5rem` | 36 px / `1.5rem` |
|
|
175
|
+
| Default CSS delivery | bslib Sass compilation | Precompiled wheel assets |
|
|
176
|
+
| Custom theme knobs | bslib / sass | Optional `[theme]` extra |
|
|
177
|
+
| Theme toggle, preset / accent / scene inputs | Public UI + observers | Same helpers and `observe_glass_*()` |
|
|
178
|
+
| Flatten for capture | `glass_flatten()` | `glass_flatten()` |
|
|
179
|
+
| CSS token catalog / overrides | `glass_css_tokens()`, `glass_token_pack()`, `glass_add_tokens()` | Same names |
|
|
180
|
+
|
|
181
|
+
When editing shared JavaScript, update its vendored copy too. When editing SCSS,
|
|
182
|
+
run `python scripts/vendor_assets.py` from this directory to rebuild wheel CSS.
|
|
183
|
+
The Python CI jobs verify editable and isolated wheel installations.
|
|
184
|
+
|
|
185
|
+
The editable job also re-runs `vendor_assets.py` and fails if the vendored
|
|
186
|
+
`glass.scss`, JavaScript, or `glass-rules-*.css` files differ from what is
|
|
187
|
+
committed. If that check fails, run `python python/scripts/vendor_assets.py`
|
|
188
|
+
from the repo root and commit the updated files under
|
|
189
|
+
`src/shinyglass/static/`. The `theme-*.css` bundles are not compared because
|
|
190
|
+
they include Bootstrap from whichever shiny version is installed.
|
|
191
|
+
|
|
192
|
+
## Not yet
|
|
193
|
+
|
|
194
|
+
- Published PyPI release (package version tracks R **0.5.0**; not on PyPI)
|
|
195
|
+
- Full R demo gallery parity
|
|
196
|
+
- `glass_page()`, `glass_plot_surface_input()`, `glass_resolved_preset()`,
|
|
197
|
+
`observe_glass()` and the intensity / plot surface observers
|
|
198
|
+
- Plot and table helpers (`theme_glass()`, `plotly_glass()`, `gt_theme_glass()`,
|
|
199
|
+
`dt_options_glass()`, `glass_plot_colors()`)
|
|
200
|
+
- React components
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Minimal page_sidebar demo for the shinyglass Python spike.
|
|
2
|
+
|
|
3
|
+
Run from the monorepo root (or with SHINYGLASS_PKG_ROOT set)::
|
|
4
|
+
|
|
5
|
+
cd python
|
|
6
|
+
pip install -e .
|
|
7
|
+
shiny run examples/app_sidebar.py
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from shiny import App, reactive, render, ui
|
|
13
|
+
|
|
14
|
+
from shinyglass import glass_intensity_slider, glass_theme, update_glass_theme
|
|
15
|
+
|
|
16
|
+
app_ui = ui.page_sidebar(
|
|
17
|
+
ui.sidebar(
|
|
18
|
+
glass_intensity_slider("glass_intensity", value=0.5),
|
|
19
|
+
ui.h4("Filters"),
|
|
20
|
+
ui.input_select(
|
|
21
|
+
"species",
|
|
22
|
+
"Species",
|
|
23
|
+
choices=["All", "setosa", "versicolor", "virginica"],
|
|
24
|
+
selected="All",
|
|
25
|
+
),
|
|
26
|
+
ui.input_slider("n", "Sample n", min=20, max=150, value=80),
|
|
27
|
+
ui.input_switch("smooth", "Smooth", True),
|
|
28
|
+
ui.input_select(
|
|
29
|
+
"plot_surface",
|
|
30
|
+
"Plot / table surface",
|
|
31
|
+
choices={"clear": "Clear (show wallpaper)", "opaque": "Opaque (readable)"},
|
|
32
|
+
selected="clear",
|
|
33
|
+
),
|
|
34
|
+
ui.input_select(
|
|
35
|
+
"scene",
|
|
36
|
+
"Scene",
|
|
37
|
+
choices={
|
|
38
|
+
"tahoe": "Tahoe",
|
|
39
|
+
"dusk": "Dusk",
|
|
40
|
+
"aurora": "Aurora",
|
|
41
|
+
"harbor": "Harbor",
|
|
42
|
+
"grove": "Grove",
|
|
43
|
+
},
|
|
44
|
+
selected="tahoe",
|
|
45
|
+
),
|
|
46
|
+
title="Glass sidebar",
|
|
47
|
+
width=280,
|
|
48
|
+
),
|
|
49
|
+
ui.h2("shinyglass · Shiny for Python"),
|
|
50
|
+
ui.layout_columns(
|
|
51
|
+
ui.value_box("Rows", ui.output_text("vb_n"), theme="primary"),
|
|
52
|
+
ui.value_box("Species", ui.output_text("vb_sp"), theme="success"),
|
|
53
|
+
ui.value_box("Mean SL", ui.output_text("vb_mu"), theme="info"),
|
|
54
|
+
fill=False,
|
|
55
|
+
),
|
|
56
|
+
ui.layout_columns(
|
|
57
|
+
ui.card(
|
|
58
|
+
ui.card_header("Histogram"),
|
|
59
|
+
ui.output_plot("hist", height="280px"),
|
|
60
|
+
full_screen=True,
|
|
61
|
+
),
|
|
62
|
+
ui.card(
|
|
63
|
+
ui.card_header("Notes"),
|
|
64
|
+
ui.p(
|
|
65
|
+
"Python spike reuses the R package ",
|
|
66
|
+
ui.tags.code("inst/scss/glass.scss"),
|
|
67
|
+
" and ",
|
|
68
|
+
ui.tags.code("inst/js/shiny-glass.js"),
|
|
69
|
+
" (Option 2 layout).",
|
|
70
|
+
),
|
|
71
|
+
ui.input_action_button("go", "Notify", class_="btn-primary"),
|
|
72
|
+
),
|
|
73
|
+
col_widths=[8, 4],
|
|
74
|
+
),
|
|
75
|
+
title="shinyglass (Python)",
|
|
76
|
+
fillable=True,
|
|
77
|
+
theme=glass_theme(
|
|
78
|
+
preset="auto",
|
|
79
|
+
intensity=0.5,
|
|
80
|
+
plot_surface="clear",
|
|
81
|
+
scene="tahoe",
|
|
82
|
+
persist=True,
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def server(input, output, session):
|
|
88
|
+
@reactive.calc
|
|
89
|
+
def dat():
|
|
90
|
+
# Tiny in-memory iris subset without requiring pandas
|
|
91
|
+
import random
|
|
92
|
+
|
|
93
|
+
# Approximate iris sepal lengths by species for the demo
|
|
94
|
+
base = {
|
|
95
|
+
"setosa": [5.1, 4.9, 4.7, 4.6, 5.0, 5.4, 4.6, 5.0],
|
|
96
|
+
"versicolor": [7.0, 6.4, 6.9, 5.5, 6.5, 5.7, 6.3, 4.9],
|
|
97
|
+
"virginica": [6.3, 5.8, 7.1, 6.3, 6.5, 7.6, 4.9, 7.3],
|
|
98
|
+
}
|
|
99
|
+
species = input.species()
|
|
100
|
+
keys = list(base.keys()) if species == "All" else [species]
|
|
101
|
+
vals: list[tuple[str, float]] = []
|
|
102
|
+
for sp in keys:
|
|
103
|
+
for v in base[sp]:
|
|
104
|
+
vals.append((sp, v))
|
|
105
|
+
random.seed(1)
|
|
106
|
+
random.shuffle(vals)
|
|
107
|
+
n = min(input.n(), len(vals))
|
|
108
|
+
return vals[:n]
|
|
109
|
+
|
|
110
|
+
@render.text
|
|
111
|
+
def vb_n():
|
|
112
|
+
return str(len(dat()))
|
|
113
|
+
|
|
114
|
+
@render.text
|
|
115
|
+
def vb_sp():
|
|
116
|
+
return str(len({s for s, _ in dat()}))
|
|
117
|
+
|
|
118
|
+
@render.text
|
|
119
|
+
def vb_mu():
|
|
120
|
+
xs = [v for _, v in dat()]
|
|
121
|
+
return f"{sum(xs) / len(xs):.2f}" if xs else "—"
|
|
122
|
+
|
|
123
|
+
@render.plot
|
|
124
|
+
def hist():
|
|
125
|
+
import matplotlib.pyplot as plt
|
|
126
|
+
|
|
127
|
+
xs = [v for _, v in dat()]
|
|
128
|
+
fig, ax = plt.subplots()
|
|
129
|
+
ax.hist(xs, bins=10, color="#007AFF", edgecolor="none")
|
|
130
|
+
ax.set_xlabel("Sepal length")
|
|
131
|
+
ax.set_ylabel("Count")
|
|
132
|
+
fig.tight_layout()
|
|
133
|
+
return fig
|
|
134
|
+
|
|
135
|
+
@reactive.effect
|
|
136
|
+
@reactive.event(input.plot_surface)
|
|
137
|
+
def _plot_surface():
|
|
138
|
+
update_glass_theme(session, plot_surface=input.plot_surface())
|
|
139
|
+
|
|
140
|
+
@reactive.effect
|
|
141
|
+
@reactive.event(input.scene)
|
|
142
|
+
def _scene():
|
|
143
|
+
update_glass_theme(session, scene=input.scene())
|
|
144
|
+
|
|
145
|
+
@reactive.effect
|
|
146
|
+
@reactive.event(input.go)
|
|
147
|
+
def _notify():
|
|
148
|
+
ui.notification_show("shinyglass Python spike", type="message")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
app = App(app_ui, server)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Hatch build hook: vendor static assets when monorepo inst/ is available."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CustomBuildHook(BuildHookInterface):
|
|
12
|
+
"""Before wheel/sdist: refresh static/ from monorepo if possible."""
|
|
13
|
+
|
|
14
|
+
PLUGIN_NAME = "custom"
|
|
15
|
+
|
|
16
|
+
def initialize(self, version: str, build_data: dict) -> None:
|
|
17
|
+
root = Path(self.root)
|
|
18
|
+
static_css = root / "src" / "shinyglass" / "static" / "theme-light.css"
|
|
19
|
+
monorepo_scss = root.parent / "inst" / "scss" / "glass.scss"
|
|
20
|
+
script = root / "scripts" / "vendor_assets.py"
|
|
21
|
+
|
|
22
|
+
if monorepo_scss.is_file() and script.is_file():
|
|
23
|
+
# Building from git checkout — always re-vendor so CSS matches SCSS.
|
|
24
|
+
sys.path.insert(0, str(root / "scripts"))
|
|
25
|
+
from vendor_assets import vendor # noqa: WPS433
|
|
26
|
+
|
|
27
|
+
print("hatch: vendoring assets from monorepo inst/")
|
|
28
|
+
vendor(quiet=False)
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
if not static_css.is_file():
|
|
32
|
+
raise RuntimeError(
|
|
33
|
+
"shinyglass static/theme-light.css is missing and monorepo "
|
|
34
|
+
"inst/scss was not found. Run `python scripts/vendor_assets.py` "
|
|
35
|
+
"from a full git checkout before building, or build from the "
|
|
36
|
+
"shinyglass repository root."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
print("hatch: using committed/prebuilt static/ assets")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling", "libsass>=0.22", "shiny>=1.0", "htmltools>=0.5"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "shinyglass"
|
|
7
|
+
version = "0.5.0"
|
|
8
|
+
description = "Liquid Glass themes for Shiny for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "GPL-3.0-only"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Eric Anderson", email = "eric.ray.anderson@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["shiny", "theme", "bootstrap", "glass", "bslib"]
|
|
16
|
+
dependencies = [
|
|
17
|
+
"shiny>=1.0",
|
|
18
|
+
"htmltools>=0.5",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
# Runtime Sass for custom primary/blur/radius (defaults use precompiled CSS)
|
|
23
|
+
theme = ["libsass>=0.22"]
|
|
24
|
+
dev = ["pytest>=7.0", "libsass>=0.22", "build", "matplotlib"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/ericrayanderson/shinyglass"
|
|
28
|
+
Repository = "https://github.com/ericrayanderson/shinyglass"
|
|
29
|
+
Issues = "https://github.com/ericrayanderson/shinyglass/issues"
|
|
30
|
+
Changelog = "https://github.com/ericrayanderson/shinyglass/blob/main/NEWS.md"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.wheel]
|
|
33
|
+
packages = ["src/shinyglass"]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.sdist]
|
|
36
|
+
include = [
|
|
37
|
+
"src/shinyglass",
|
|
38
|
+
"scripts",
|
|
39
|
+
"hatch_build.py",
|
|
40
|
+
"README.md",
|
|
41
|
+
"examples",
|
|
42
|
+
"tests",
|
|
43
|
+
"pyproject.toml",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.hooks.custom]
|
|
47
|
+
path = "hatch_build.py"
|
|
48
|
+
|
|
49
|
+
[tool.pytest.ini_options]
|
|
50
|
+
testpaths = ["tests"]
|