nicegui-bootstrap-components 0.1.0__py3-none-any.whl
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_bootstrap_components/__init__.py +7 -0
- nicegui_bootstrap_components/_base.py +590 -0
- nicegui_bootstrap_components/_host.py +259 -0
- nicegui_bootstrap_components/assets.py +371 -0
- nicegui_bootstrap_components/bs/__init__.py +267 -0
- nicegui_bootstrap_components/bs/_actions.py +232 -0
- nicegui_bootstrap_components/bs/_alert_spinner.py +423 -0
- nicegui_bootstrap_components/bs/_carousel.py +312 -0
- nicegui_bootstrap_components/bs/_choices.py +779 -0
- nicegui_bootstrap_components/bs/_content.py +1343 -0
- nicegui_bootstrap_components/bs/_forms.py +378 -0
- nicegui_bootstrap_components/bs/_inputs.py +560 -0
- nicegui_bootstrap_components/bs/_layout.py +341 -0
- nicegui_bootstrap_components/bs/_nav.py +650 -0
- nicegui_bootstrap_components/bs/_navbar.py +445 -0
- nicegui_bootstrap_components/bs/_overlay_root.py +73 -0
- nicegui_bootstrap_components/bs/_overlays.py +2633 -0
- nicegui_bootstrap_components/bs/_scope.py +28 -0
- nicegui_bootstrap_components/bs/_tabs.py +605 -0
- nicegui_bootstrap_components/bs/_toast.py +137 -0
- nicegui_bootstrap_components/bs/_tooltip_popover.py +537 -0
- nicegui_bootstrap_components/contracts/dbc-2.0.4.json +16811 -0
- nicegui_bootstrap_components/dbc/__init__.py +108 -0
- nicegui_bootstrap_components/icons.py +29 -0
- nicegui_bootstrap_components/persist.py +201 -0
- nicegui_bootstrap_components/py.typed +0 -0
- nicegui_bootstrap_components/static/dist/fonts/bootstrap-icons.woff +0 -0
- nicegui_bootstrap_components/static/dist/fonts/bootstrap-icons.woff2 +0 -0
- nicegui_bootstrap_components/static/dist/ngbs-bootstrap-unscoped.css +12047 -0
- nicegui_bootstrap_components/static/dist/ngbs-bootstrap.css +12057 -0
- nicegui_bootstrap_components/static/dist/ngbs-cerulean-unscoped.css +12338 -0
- nicegui_bootstrap_components/static/dist/ngbs-cerulean.css +12348 -0
- nicegui_bootstrap_components/static/dist/ngbs-compat-unscoped.css +12 -0
- nicegui_bootstrap_components/static/dist/ngbs-compat.css +12 -0
- nicegui_bootstrap_components/static/dist/ngbs-cosmo-unscoped.css +11825 -0
- nicegui_bootstrap_components/static/dist/ngbs-cosmo.css +11835 -0
- nicegui_bootstrap_components/static/dist/ngbs-cyborg-unscoped.css +12213 -0
- nicegui_bootstrap_components/static/dist/ngbs-cyborg.css +12223 -0
- nicegui_bootstrap_components/static/dist/ngbs-darkly-unscoped.css +12143 -0
- nicegui_bootstrap_components/static/dist/ngbs-darkly.css +12153 -0
- nicegui_bootstrap_components/static/dist/ngbs-flatly-unscoped.css +12129 -0
- nicegui_bootstrap_components/static/dist/ngbs-flatly.css +12139 -0
- nicegui_bootstrap_components/static/dist/ngbs-grid-unscoped.css +4084 -0
- nicegui_bootstrap_components/static/dist/ngbs-grid.css +4094 -0
- nicegui_bootstrap_components/static/dist/ngbs-host-unscoped.css +9 -0
- nicegui_bootstrap_components/static/dist/ngbs-host.css +9 -0
- nicegui_bootstrap_components/static/dist/ngbs-icons-bootstrap.css +5 -0
- nicegui_bootstrap_components/static/dist/ngbs-icons-fontawesome.css +9 -0
- nicegui_bootstrap_components/static/dist/ngbs-journal-unscoped.css +12112 -0
- nicegui_bootstrap_components/static/dist/ngbs-journal.css +12122 -0
- nicegui_bootstrap_components/static/dist/ngbs-litera-unscoped.css +12214 -0
- nicegui_bootstrap_components/static/dist/ngbs-litera.css +12224 -0
- nicegui_bootstrap_components/static/dist/ngbs-lumen-unscoped.css +12377 -0
- nicegui_bootstrap_components/static/dist/ngbs-lumen.css +12387 -0
- nicegui_bootstrap_components/static/dist/ngbs-lux-unscoped.css +12048 -0
- nicegui_bootstrap_components/static/dist/ngbs-lux.css +12058 -0
- nicegui_bootstrap_components/static/dist/ngbs-materia-unscoped.css +13197 -0
- nicegui_bootstrap_components/static/dist/ngbs-materia.css +13207 -0
- nicegui_bootstrap_components/static/dist/ngbs-minty-unscoped.css +12184 -0
- nicegui_bootstrap_components/static/dist/ngbs-minty.css +12194 -0
- nicegui_bootstrap_components/static/dist/ngbs-morph-unscoped.css +12829 -0
- nicegui_bootstrap_components/static/dist/ngbs-morph.css +12839 -0
- nicegui_bootstrap_components/static/dist/ngbs-pulse-unscoped.css +11901 -0
- nicegui_bootstrap_components/static/dist/ngbs-pulse.css +11911 -0
- nicegui_bootstrap_components/static/dist/ngbs-quartz-unscoped.css +12625 -0
- nicegui_bootstrap_components/static/dist/ngbs-quartz.css +12635 -0
- nicegui_bootstrap_components/static/dist/ngbs-sandstone-unscoped.css +12214 -0
- nicegui_bootstrap_components/static/dist/ngbs-sandstone.css +12224 -0
- nicegui_bootstrap_components/static/dist/ngbs-simplex-unscoped.css +12199 -0
- nicegui_bootstrap_components/static/dist/ngbs-simplex.css +12209 -0
- nicegui_bootstrap_components/static/dist/ngbs-sketchy-unscoped.css +12518 -0
- nicegui_bootstrap_components/static/dist/ngbs-sketchy.css +12528 -0
- nicegui_bootstrap_components/static/dist/ngbs-slate-unscoped.css +12527 -0
- nicegui_bootstrap_components/static/dist/ngbs-slate.css +12537 -0
- nicegui_bootstrap_components/static/dist/ngbs-solar-unscoped.css +12152 -0
- nicegui_bootstrap_components/static/dist/ngbs-solar.css +12162 -0
- nicegui_bootstrap_components/static/dist/ngbs-spacelab-unscoped.css +12279 -0
- nicegui_bootstrap_components/static/dist/ngbs-spacelab.css +12289 -0
- nicegui_bootstrap_components/static/dist/ngbs-superhero-unscoped.css +12223 -0
- nicegui_bootstrap_components/static/dist/ngbs-superhero.css +12233 -0
- nicegui_bootstrap_components/static/dist/ngbs-united-unscoped.css +12090 -0
- nicegui_bootstrap_components/static/dist/ngbs-united.css +12100 -0
- nicegui_bootstrap_components/static/dist/ngbs-vapor-unscoped.css +12556 -0
- nicegui_bootstrap_components/static/dist/ngbs-vapor.css +12566 -0
- nicegui_bootstrap_components/static/dist/ngbs-yeti-unscoped.css +12333 -0
- nicegui_bootstrap_components/static/dist/ngbs-yeti.css +12343 -0
- nicegui_bootstrap_components/static/dist/ngbs-zephyr-unscoped.css +12296 -0
- nicegui_bootstrap_components/static/dist/ngbs-zephyr.css +12306 -0
- nicegui_bootstrap_components/static/prototype-ngbs.css +541 -0
- nicegui_bootstrap_components/static/webfonts/fa-brands-400.ttf +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-brands-400.woff2 +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-regular-400.ttf +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-regular-400.woff2 +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-solid-900.ttf +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-solid-900.woff2 +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-v4compatibility.ttf +0 -0
- nicegui_bootstrap_components/static/webfonts/fa-v4compatibility.woff2 +0 -0
- nicegui_bootstrap_components/theme.py +238 -0
- nicegui_bootstrap_components/themes.py +131 -0
- nicegui_bootstrap_components-0.1.0.dist-info/METADATA +234 -0
- nicegui_bootstrap_components-0.1.0.dist-info/RECORD +103 -0
- nicegui_bootstrap_components-0.1.0.dist-info/WHEEL +4 -0
- nicegui_bootstrap_components-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Per-client color mode and stylesheet selection."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from . import themes as _themes
|
|
9
|
+
from ._host import add_head_html, client_store, get_client, run_javascript
|
|
10
|
+
from .assets import _validate_stylesheet_url, _validated_theme_cdn, get_asset_manager
|
|
11
|
+
|
|
12
|
+
__all__ = ["ThemeController"]
|
|
13
|
+
|
|
14
|
+
THEME_STYLE_ID = "ngbs-theme-client"
|
|
15
|
+
|
|
16
|
+
_MODE_VALUES = {"light", "dark", "auto"}
|
|
17
|
+
|
|
18
|
+
_RUNTIME_JS = r"""
|
|
19
|
+
(() => {
|
|
20
|
+
if (window.ngbsSetColorMode) return;
|
|
21
|
+
const themeStyleId = __NGBS_THEME_STYLE_ID__;
|
|
22
|
+
const styleMode = __NGBS_STYLE_MODE__;
|
|
23
|
+
let observer = null;
|
|
24
|
+
let media = null;
|
|
25
|
+
let domReadyHandler = null;
|
|
26
|
+
let lastResolved = 'light';
|
|
27
|
+
const targets = () => {
|
|
28
|
+
if (styleMode === 'unscoped') {
|
|
29
|
+
return document.documentElement ? [document.documentElement] : [];
|
|
30
|
+
}
|
|
31
|
+
return document.querySelectorAll('.ngbs, .ngbs-overlay');
|
|
32
|
+
};
|
|
33
|
+
const apply = (value) => {
|
|
34
|
+
lastResolved = value;
|
|
35
|
+
targets().forEach((el) => el.setAttribute('data-bs-theme', value));
|
|
36
|
+
};
|
|
37
|
+
const clearNiceguiObserver = () => {
|
|
38
|
+
if (observer) { observer.disconnect(); observer = null; }
|
|
39
|
+
if (domReadyHandler) {
|
|
40
|
+
document.removeEventListener('DOMContentLoaded', domReadyHandler);
|
|
41
|
+
domReadyHandler = null;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const armNiceguiObserver = (read) => {
|
|
45
|
+
const body = document.body;
|
|
46
|
+
if (!body) {
|
|
47
|
+
domReadyHandler = () => {
|
|
48
|
+
domReadyHandler = null;
|
|
49
|
+
armNiceguiObserver(read);
|
|
50
|
+
};
|
|
51
|
+
document.addEventListener('DOMContentLoaded', domReadyHandler, { once: true });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
observer = new MutationObserver(() => apply(read() ? 'dark' : 'light'));
|
|
55
|
+
observer.observe(body, { attributes: true, attributeFilter: ['class'] });
|
|
56
|
+
apply(read() ? 'dark' : 'light');
|
|
57
|
+
};
|
|
58
|
+
window.ngbsApplyCurrentTheme = () => apply(lastResolved);
|
|
59
|
+
window.ngbsSetColorMode = (mode, followNiceguiDark) => {
|
|
60
|
+
clearNiceguiObserver();
|
|
61
|
+
if (media) { media.removeEventListener('change', media._ngbsRun); media = null; }
|
|
62
|
+
if (mode === 'light' || mode === 'dark') { apply(mode); return; }
|
|
63
|
+
if (followNiceguiDark) {
|
|
64
|
+
const read = () => document.body ? document.body.classList.contains('body--dark') : false;
|
|
65
|
+
armNiceguiObserver(read);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
media = window.matchMedia('(prefers-color-scheme: dark)');
|
|
69
|
+
media._ngbsRun = () => apply(media.matches ? 'dark' : 'light');
|
|
70
|
+
media.addEventListener('change', media._ngbsRun);
|
|
71
|
+
media._ngbsRun();
|
|
72
|
+
};
|
|
73
|
+
const escapeCssString = (value) => value
|
|
74
|
+
.replace(/\\/g, '\\\\')
|
|
75
|
+
.replace(/"/g, '\\"')
|
|
76
|
+
.replace(/\r/g, '\\D ')
|
|
77
|
+
.replace(/\n/g, '\\A ');
|
|
78
|
+
window.ngbsSetThemeSheet = (href) => {
|
|
79
|
+
let tag = document.getElementById(themeStyleId);
|
|
80
|
+
if (!tag) {
|
|
81
|
+
tag = document.createElement('style');
|
|
82
|
+
tag.id = themeStyleId;
|
|
83
|
+
document.head.appendChild(tag);
|
|
84
|
+
}
|
|
85
|
+
tag.textContent = '@import url("' + escapeCssString(href) + '") layer(overrides);';
|
|
86
|
+
};
|
|
87
|
+
})();
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _runtime_js(style_mode: str) -> str:
|
|
92
|
+
return _RUNTIME_JS.replace("__NGBS_THEME_STYLE_ID__", json.dumps(THEME_STYLE_ID)).replace(
|
|
93
|
+
"__NGBS_STYLE_MODE__", json.dumps(style_mode)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _normalize_mode(mode: str | None) -> str:
|
|
98
|
+
value = str(mode).lower()
|
|
99
|
+
if value not in _MODE_VALUES:
|
|
100
|
+
raise ValueError(f"color_mode must be light|dark|auto, got {mode!r}")
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class ThemeController:
|
|
105
|
+
"""Page-lifetime theme state for one NiceGUI client.
|
|
106
|
+
|
|
107
|
+
Obtained through :meth:`for_client`; never constructed directly.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
client: Any,
|
|
113
|
+
*,
|
|
114
|
+
color_mode: str,
|
|
115
|
+
follow_nicegui_dark: bool,
|
|
116
|
+
theme: _themes.Theme,
|
|
117
|
+
) -> None:
|
|
118
|
+
self._client = client
|
|
119
|
+
self._color_mode = color_mode
|
|
120
|
+
self._follow_nicegui_dark = follow_nicegui_dark
|
|
121
|
+
self._theme = theme
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def for_client(cls, client: Any | None = None) -> ThemeController:
|
|
125
|
+
"""Bind or return the controller for ``client`` (defaults to the current one)."""
|
|
126
|
+
target = client if client is not None else get_client()
|
|
127
|
+
if target is None:
|
|
128
|
+
raise RuntimeError("ThemeController requires an active client")
|
|
129
|
+
store = client_store(target)
|
|
130
|
+
existing = store.get("theme_controller")
|
|
131
|
+
if isinstance(existing, cls):
|
|
132
|
+
return existing
|
|
133
|
+
manager = get_asset_manager()
|
|
134
|
+
raw_mode = manager.color_mode if manager is not None else "auto"
|
|
135
|
+
color_mode = _normalize_mode(raw_mode)
|
|
136
|
+
style_mode = manager.mode.value if manager is not None else "mixed"
|
|
137
|
+
controller = cls(
|
|
138
|
+
target,
|
|
139
|
+
color_mode=color_mode,
|
|
140
|
+
follow_nicegui_dark=manager.follow_nicegui_dark if manager is not None else True,
|
|
141
|
+
theme=manager.theme if manager is not None else _themes.BOOTSTRAP,
|
|
142
|
+
)
|
|
143
|
+
add_head_html(f"<script>{_runtime_js(style_mode)}</script>", shared=False, client=target)
|
|
144
|
+
controller._apply()
|
|
145
|
+
store["theme_controller"] = controller
|
|
146
|
+
return controller
|
|
147
|
+
|
|
148
|
+
# -- state ---------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
@property
|
|
151
|
+
def color_mode(self) -> str:
|
|
152
|
+
return self._color_mode
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def theme(self) -> _themes.Theme:
|
|
156
|
+
return self._theme
|
|
157
|
+
|
|
158
|
+
def set_color_mode(self, mode: str) -> None:
|
|
159
|
+
"""Switch this client to ``light``, ``dark``, or ``auto``."""
|
|
160
|
+
self._color_mode = _normalize_mode(mode)
|
|
161
|
+
self._apply()
|
|
162
|
+
|
|
163
|
+
def set_theme(self, theme: _themes.Theme | str) -> None:
|
|
164
|
+
"""Swap this client's stylesheet without touching other clients."""
|
|
165
|
+
resolved = _resolve_theme(theme)
|
|
166
|
+
manager = get_asset_manager()
|
|
167
|
+
href = _stylesheet_url(resolved, manager)
|
|
168
|
+
if href is None:
|
|
169
|
+
raise RuntimeError("bundled theme selection requires setup() to serve its stylesheet")
|
|
170
|
+
run_javascript(
|
|
171
|
+
f"window.ngbsSetThemeSheet && window.ngbsSetThemeSheet({json.dumps(href)});",
|
|
172
|
+
client=self._client,
|
|
173
|
+
)
|
|
174
|
+
self._theme = resolved
|
|
175
|
+
|
|
176
|
+
def bind_to_nicegui_dark(self, enabled: bool = True) -> None:
|
|
177
|
+
"""Choose whether ``auto`` follows the host dark state or the OS preference."""
|
|
178
|
+
self._follow_nicegui_dark = bool(enabled)
|
|
179
|
+
if self._color_mode == "auto":
|
|
180
|
+
self._apply()
|
|
181
|
+
|
|
182
|
+
# -- internals -----------------------------------------------------
|
|
183
|
+
|
|
184
|
+
def _apply(self) -> None:
|
|
185
|
+
mode = self._color_mode
|
|
186
|
+
follow = "true" if self._follow_nicegui_dark else "false"
|
|
187
|
+
run_javascript(
|
|
188
|
+
f"window.ngbsSetColorMode && window.ngbsSetColorMode({json.dumps(mode)}, {follow});",
|
|
189
|
+
client=self._client,
|
|
190
|
+
)
|
|
191
|
+
# Live anchors are updated by the runtime; the stored value seeds any
|
|
192
|
+
# scope or overlay root created later in this client.
|
|
193
|
+
store = client_store(self._client)
|
|
194
|
+
store["overlay_theme"] = None if mode == "auto" else mode
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _resolve_theme(theme: _themes.Theme | str) -> _themes.Theme:
|
|
198
|
+
if isinstance(theme, _themes.Theme):
|
|
199
|
+
for candidate in _themes._CATALOGUE:
|
|
200
|
+
if theme is candidate or theme == candidate:
|
|
201
|
+
return candidate
|
|
202
|
+
if theme.cdn:
|
|
203
|
+
_validate_stylesheet_url(theme.cdn)
|
|
204
|
+
elif not theme.bundled:
|
|
205
|
+
raise ValueError("theme must provide a bundled name or a plain http(s) URL")
|
|
206
|
+
return theme
|
|
207
|
+
if isinstance(theme, str):
|
|
208
|
+
for candidate in _themes._CATALOGUE:
|
|
209
|
+
if theme in {candidate.name, candidate.bundled}:
|
|
210
|
+
return candidate
|
|
211
|
+
if theme.lower().startswith(("http://", "https://")):
|
|
212
|
+
_validate_stylesheet_url(theme)
|
|
213
|
+
return _themes.Theme(name=theme, cdn=theme, bundled="")
|
|
214
|
+
valid_names = ", ".join(candidate.name for candidate in _themes._CATALOGUE)
|
|
215
|
+
raise ValueError(f"unknown theme {theme!r}; valid names: {valid_names}")
|
|
216
|
+
raise TypeError(f"theme must be a Theme or a name/url string, got {type(theme)!r}")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _stylesheet_url(theme: _themes.Theme, manager: Any) -> str | None:
|
|
220
|
+
if manager is not None and manager.cdn:
|
|
221
|
+
return _validated_theme_cdn(theme)
|
|
222
|
+
if not theme.bundled:
|
|
223
|
+
return _validate_stylesheet_url(theme.cdn)
|
|
224
|
+
return manager.bundled_url(theme) if manager is not None else None
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def ensure_theme_bound() -> None:
|
|
228
|
+
"""Lazy-bind the controller on first library use inside a page."""
|
|
229
|
+
try:
|
|
230
|
+
store = client_store()
|
|
231
|
+
except Exception:
|
|
232
|
+
return
|
|
233
|
+
if "theme_controller" in store:
|
|
234
|
+
return
|
|
235
|
+
try:
|
|
236
|
+
ThemeController.for_client()
|
|
237
|
+
except Exception:
|
|
238
|
+
return
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Selectable stylesheet constants and their pinned CDN twins."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BOOTSTRAP",
|
|
9
|
+
"GRID",
|
|
10
|
+
"CERULEAN",
|
|
11
|
+
"COSMO",
|
|
12
|
+
"CYBORG",
|
|
13
|
+
"DARKLY",
|
|
14
|
+
"FLATLY",
|
|
15
|
+
"JOURNAL",
|
|
16
|
+
"LITERA",
|
|
17
|
+
"LUMEN",
|
|
18
|
+
"LUX",
|
|
19
|
+
"MATERIA",
|
|
20
|
+
"MINTY",
|
|
21
|
+
"MORPH",
|
|
22
|
+
"PULSE",
|
|
23
|
+
"QUARTZ",
|
|
24
|
+
"SANDSTONE",
|
|
25
|
+
"SIMPLEX",
|
|
26
|
+
"SKETCHY",
|
|
27
|
+
"SLATE",
|
|
28
|
+
"SOLAR",
|
|
29
|
+
"SPACELAB",
|
|
30
|
+
"SUPERHERO",
|
|
31
|
+
"UNITED",
|
|
32
|
+
"VAPOR",
|
|
33
|
+
"YETI",
|
|
34
|
+
"ZEPHYR",
|
|
35
|
+
"Theme",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
_CDN_ROOT = "https://cdn.jsdelivr.net/npm"
|
|
39
|
+
_BOOTSTRAP_VERSION = "5.3.8"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class Theme:
|
|
44
|
+
"""One selectable stylesheet: a bundled build plus its pinned CDN twin.
|
|
45
|
+
|
|
46
|
+
``bundled`` names the package stylesheet pair: ``<bundled>.css`` is the
|
|
47
|
+
scoped build and ``<bundled>-unscoped.css`` applies document-wide.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
name: str
|
|
51
|
+
cdn: str
|
|
52
|
+
bundled: str
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
BOOTSTRAP = Theme(
|
|
56
|
+
name="bootstrap",
|
|
57
|
+
cdn=f"{_CDN_ROOT}/bootstrap@{_BOOTSTRAP_VERSION}/dist/css/bootstrap.min.css",
|
|
58
|
+
bundled="ngbs-bootstrap",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
GRID = Theme(
|
|
62
|
+
name="grid",
|
|
63
|
+
cdn=f"{_CDN_ROOT}/bootstrap@{_BOOTSTRAP_VERSION}/dist/css/bootstrap-grid.min.css",
|
|
64
|
+
bundled="ngbs-grid",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _bootswatch(name: str) -> Theme:
|
|
69
|
+
return Theme(
|
|
70
|
+
name=name,
|
|
71
|
+
cdn=f"{_CDN_ROOT}/bootswatch@{_BOOTSTRAP_VERSION}/dist/{name}/bootstrap.min.css",
|
|
72
|
+
bundled=f"ngbs-{name}",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
CERULEAN = _bootswatch("cerulean")
|
|
77
|
+
COSMO = _bootswatch("cosmo")
|
|
78
|
+
CYBORG = _bootswatch("cyborg")
|
|
79
|
+
DARKLY = _bootswatch("darkly")
|
|
80
|
+
FLATLY = _bootswatch("flatly")
|
|
81
|
+
JOURNAL = _bootswatch("journal")
|
|
82
|
+
LITERA = _bootswatch("litera")
|
|
83
|
+
LUMEN = _bootswatch("lumen")
|
|
84
|
+
LUX = _bootswatch("lux")
|
|
85
|
+
MATERIA = _bootswatch("materia")
|
|
86
|
+
MINTY = _bootswatch("minty")
|
|
87
|
+
MORPH = _bootswatch("morph")
|
|
88
|
+
PULSE = _bootswatch("pulse")
|
|
89
|
+
QUARTZ = _bootswatch("quartz")
|
|
90
|
+
SANDSTONE = _bootswatch("sandstone")
|
|
91
|
+
SIMPLEX = _bootswatch("simplex")
|
|
92
|
+
SKETCHY = _bootswatch("sketchy")
|
|
93
|
+
SLATE = _bootswatch("slate")
|
|
94
|
+
SOLAR = _bootswatch("solar")
|
|
95
|
+
SPACELAB = _bootswatch("spacelab")
|
|
96
|
+
SUPERHERO = _bootswatch("superhero")
|
|
97
|
+
UNITED = _bootswatch("united")
|
|
98
|
+
VAPOR = _bootswatch("vapor")
|
|
99
|
+
YETI = _bootswatch("yeti")
|
|
100
|
+
ZEPHYR = _bootswatch("zephyr")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
_CATALOGUE = (
|
|
104
|
+
BOOTSTRAP,
|
|
105
|
+
GRID,
|
|
106
|
+
CERULEAN,
|
|
107
|
+
COSMO,
|
|
108
|
+
CYBORG,
|
|
109
|
+
DARKLY,
|
|
110
|
+
FLATLY,
|
|
111
|
+
JOURNAL,
|
|
112
|
+
LITERA,
|
|
113
|
+
LUMEN,
|
|
114
|
+
LUX,
|
|
115
|
+
MATERIA,
|
|
116
|
+
MINTY,
|
|
117
|
+
MORPH,
|
|
118
|
+
PULSE,
|
|
119
|
+
QUARTZ,
|
|
120
|
+
SANDSTONE,
|
|
121
|
+
SIMPLEX,
|
|
122
|
+
SKETCHY,
|
|
123
|
+
SLATE,
|
|
124
|
+
SOLAR,
|
|
125
|
+
SPACELAB,
|
|
126
|
+
SUPERHERO,
|
|
127
|
+
UNITED,
|
|
128
|
+
VAPOR,
|
|
129
|
+
YETI,
|
|
130
|
+
ZEPHYR,
|
|
131
|
+
)
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: nicegui-bootstrap-components
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bootstrap 5 components for NiceGUI, matching the dash-bootstrap-components API, docs, and examples
|
|
5
|
+
Project-URL: Homepage, https://github.com/raffieeey/nicegui-bootstrap-components
|
|
6
|
+
Project-URL: Documentation, https://raffieeey.github.io/nicegui-bootstrap-components/
|
|
7
|
+
Author: raffieeey
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: bootstrap,components,dashboard,nicegui,ui
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: nicegui<4,>=3.17.1
|
|
21
|
+
Provides-Extra: dbc-ref
|
|
22
|
+
Requires-Dist: dash-bootstrap-components==2.0.4; extra == 'dbc-ref'
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
25
|
+
Requires-Dist: pandas-stubs; extra == 'dev'
|
|
26
|
+
Requires-Dist: pillow>=10; extra == 'dev'
|
|
27
|
+
Requires-Dist: playwright>=1.47; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
31
|
+
Requires-Dist: selenium>=4.20; extra == 'dev'
|
|
32
|
+
Provides-Extra: examples
|
|
33
|
+
Requires-Dist: pandas; extra == 'examples'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# nicegui-bootstrap-components
|
|
37
|
+
|
|
38
|
+
Bootstrap 5 components for [NiceGUI](https://nicegui.io). The public API matches
|
|
39
|
+
the [dash-bootstrap-components](https://github.com/facultyai/dash-bootstrap-components)
|
|
40
|
+
2.0.4 API for the component set listed below.
|
|
41
|
+
|
|
42
|
+
[](https://github.com/raffieeey/nicegui-bootstrap-components/actions/workflows/ci.yml)
|
|
43
|
+
[](https://www.python.org/)
|
|
44
|
+
[](LICENSE)
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
The package requires Python 3.10 or later and NiceGUI >=3.17.1,<4; it has been
|
|
49
|
+
tested against NiceGUI 3.17.1. It bundles Bootstrap 5.3.8. CI checks the oldest
|
|
50
|
+
supported NiceGUI release and the newest available 3.x release.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install nicegui-bootstrap-components
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`Table.from_dataframe` needs pandas, installed by the optional extra:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
pip install "nicegui-bootstrap-components[examples]"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Quickstart
|
|
63
|
+
|
|
64
|
+
Save this as `app.py` and run `python app.py`. Open the local URL printed in the
|
|
65
|
+
terminal.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from nicegui import ui
|
|
69
|
+
from nicegui_bootstrap_components import bs
|
|
70
|
+
|
|
71
|
+
with bs.scope(), bs.container():
|
|
72
|
+
with bs.row():
|
|
73
|
+
with bs.col(width=12, md=6):
|
|
74
|
+
bs.input(placeholder="Name")
|
|
75
|
+
bs.button("Save", color="primary", on_click=lambda: ui.notify("Saved"))
|
|
76
|
+
|
|
77
|
+
ui.run()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Two API surfaces
|
|
81
|
+
|
|
82
|
+
`nicegui_bootstrap_components.bs` is the native surface: 67 PascalCase classes
|
|
83
|
+
and 67 snake_case aliases (134 exports). Use it for new NiceGUI code.
|
|
84
|
+
|
|
85
|
+
`nicegui_bootstrap_components.dbc` is the compatibility surface (66 exports)
|
|
86
|
+
with dash-bootstrap-components 2.0.4 prop names. Use it when porting Dash layouts.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from nicegui import ui
|
|
90
|
+
from nicegui_bootstrap_components import dbc
|
|
91
|
+
|
|
92
|
+
dbc.Container(
|
|
93
|
+
dbc.Row(dbc.Col(dbc.Button("Save", color="primary"), md=6)),
|
|
94
|
+
fluid=True,
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Top-level exports: `StyleMode`, `ThemeController`, `bs`, `dbc`, `icons`, `setup`,
|
|
99
|
+
`themes`, `__version__`.
|
|
100
|
+
|
|
101
|
+
## Style modes
|
|
102
|
+
|
|
103
|
+
`setup()` registers library CSS once, process-wide, and is idempotent for the same
|
|
104
|
+
arguments. A conflicting mode, or scoped mode with `cdn=True`, raises `ValueError`.
|
|
105
|
+
|
|
106
|
+
- `StyleMode.UNSCOPED` (`"unscoped"`): Bootstrap-first page.
|
|
107
|
+
- `StyleMode.MIXED` (`"mixed"`): scoped Bootstrap islands inside an existing
|
|
108
|
+
NiceGUI/Quasar page. Mark islands with `bs.scope()` (`bs.Scope` is the class form).
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
from nicegui_bootstrap_components import StyleMode, setup, themes
|
|
112
|
+
|
|
113
|
+
setup(mode=StyleMode.MIXED, theme=themes.FLATLY, color_mode="auto")
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Themes and dark mode
|
|
117
|
+
|
|
118
|
+
27 constants on `themes`: Bootstrap itself, the grid-only build, and 25 Bootswatch
|
|
119
|
+
themes. `color_mode="auto"` follows the host app. Pass `icons.BOOTSTRAP` or
|
|
120
|
+
`icons.FONT_AWESOME` to `setup(icons=...)`.
|
|
121
|
+
|
|
122
|
+
`BOOTSTRAP`, `CERULEAN`, `COSMO`, `CYBORG`, `DARKLY`, `FLATLY`, `GRID`, `JOURNAL`,
|
|
123
|
+
`LITERA`, `LUMEN`, `LUX`, `MATERIA`, `MINTY`, `MORPH`, `PULSE`, `QUARTZ`, `SANDSTONE`,
|
|
124
|
+
`SIMPLEX`, `SKETCHY`, `SLATE`, `SOLAR`, `SPACELAB`, `SUPERHERO`, `UNITED`, `VAPOR`,
|
|
125
|
+
`YETI`, `ZEPHYR`.
|
|
126
|
+
|
|
127
|
+
## Components
|
|
128
|
+
|
|
129
|
+
67 native names (each has a snake_case alias on `bs`):
|
|
130
|
+
|
|
131
|
+
Accordion, AccordionItem, Alert, Badge, Breadcrumb, Button, ButtonGroup, Card, CardBody,
|
|
132
|
+
CardFooter, CardGroup, CardHeader, CardImg, CardImgOverlay, CardLink, Carousel, Checkbox,
|
|
133
|
+
Checklist, Col, Collapse, Container, DropdownMenu, DropdownMenuItem, Fade, Form,
|
|
134
|
+
FormFeedback, FormFloating, FormText, Input, InputGroup, InputGroupText, Label, ListGroup,
|
|
135
|
+
ListGroupItem, Modal, ModalBody, ModalFooter, ModalHeader, ModalTitle, Nav, NavItem,
|
|
136
|
+
NavLink, Navbar, NavbarBrand, NavbarSimple, NavbarToggler, Offcanvas, Pagination,
|
|
137
|
+
Placeholder, Popover, PopoverBody, PopoverHeader, Progress, RadioButton, RadioItems, Row,
|
|
138
|
+
Scope, Select, Spinner, Stack, Switch, Tab, Table, Tabs, Textarea, Toast, Tooltip.
|
|
139
|
+
|
|
140
|
+
## Layout
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from nicegui_bootstrap_components import bs
|
|
144
|
+
|
|
145
|
+
with bs.scope(), bs.container():
|
|
146
|
+
with bs.row():
|
|
147
|
+
with bs.col(width=12, md=4):
|
|
148
|
+
bs.label("Full width on phones, one third from md")
|
|
149
|
+
with bs.col(width=12, md=4):
|
|
150
|
+
bs.label("Middle")
|
|
151
|
+
with bs.col(width=12, md=4):
|
|
152
|
+
bs.label("Right")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Forms
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from nicegui_bootstrap_components import bs
|
|
159
|
+
|
|
160
|
+
with bs.scope():
|
|
161
|
+
bs.input(placeholder="Your name", on_change=lambda: None)
|
|
162
|
+
bs.select(options={"my": "Malaysia", "sg": "Singapore"}, value="my")
|
|
163
|
+
bs.checkbox(label="Remember me")
|
|
164
|
+
bs.switch(label="Enable alerts")
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Cards, alerts, and badges
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from nicegui_bootstrap_components import bs
|
|
171
|
+
|
|
172
|
+
with bs.scope(), bs.card():
|
|
173
|
+
bs.card_header("Header")
|
|
174
|
+
with bs.card_body():
|
|
175
|
+
bs.alert("Something happened.", color="info", dismissable=True)
|
|
176
|
+
bs.badge("New", color="success")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Modal
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from nicegui_bootstrap_components import bs
|
|
183
|
+
|
|
184
|
+
with bs.scope():
|
|
185
|
+
with bs.modal() as modal:
|
|
186
|
+
bs.modal_header("Title")
|
|
187
|
+
bs.modal_body("Content")
|
|
188
|
+
bs.modal_footer(bs.button("Close", on_click=modal.close))
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Table
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
import pandas as pd
|
|
195
|
+
from nicegui_bootstrap_components import bs
|
|
196
|
+
|
|
197
|
+
df = pd.DataFrame({"city": ["Kuala Lumpur"], "country": ["Malaysia"]})
|
|
198
|
+
|
|
199
|
+
with bs.scope():
|
|
200
|
+
bs.Table.from_dataframe(df, striped=True, bordered=False, hover=True)
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Examples
|
|
204
|
+
|
|
205
|
+
The gallery mirrors dash-bootstrap-components: Layer 1 component examples, Layer 2
|
|
206
|
+
composite apps (Iris, Graphs in Tabs, Simple Sidebar), and Layer 3 templates.
|
|
207
|
+
|
|
208
|
+
## Documentation
|
|
209
|
+
|
|
210
|
+
[https://raffieeey.github.io/nicegui-bootstrap-components/](https://raffieeey.github.io/nicegui-bootstrap-components/)
|
|
211
|
+
|
|
212
|
+
## Development
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
git clone https://github.com/raffieeey/nicegui-bootstrap-components.git
|
|
216
|
+
cd nicegui-bootstrap-components
|
|
217
|
+
pip install -e ".[dev]"
|
|
218
|
+
pytest
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`python -m playwright install chromium`. At 0.1.0, the default run carries
|
|
222
|
+
545 tests plus the Playwright browser suite and the visual matrix; mypy and
|
|
223
|
+
ruff are clean. CI runs three GitHub Actions jobs (checks, docs, browser). The
|
|
224
|
+
browser suite uses real Chromium. Every Python block in the docs and every
|
|
225
|
+
example is executed by tests, so a snippet that cannot run fails CI.
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT. See [LICENSE](LICENSE).
|
|
230
|
+
|
|
231
|
+
## Status
|
|
232
|
+
|
|
233
|
+
0.1.0, first public release. Matches the dash-bootstrap-components 2.0.4 API for the
|
|
234
|
+
component set listed above.
|