fh-matui 0.9.15__tar.gz → 0.9.17__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.
- {fh_matui-0.9.15/fh_matui.egg-info → fh_matui-0.9.17}/PKG-INFO +14 -13
- {fh_matui-0.9.15 → fh_matui-0.9.17}/README.md +13 -12
- fh_matui-0.9.17/fh_matui/__init__.py +1 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui/_modidx.py +1 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui/app_pages.py +3 -3
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui/components.py +126 -120
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui/core.py +7 -7
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui/datatable.py +5 -5
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui/foundations.py +5 -5
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui/web_pages.py +12 -12
- {fh_matui-0.9.15 → fh_matui-0.9.17/fh_matui.egg-info}/PKG-INFO +14 -13
- fh_matui-0.9.17/pyproject.toml +11 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/settings.ini +1 -1
- fh_matui-0.9.15/fh_matui/__init__.py +0 -1
- fh_matui-0.9.15/pyproject.toml +0 -3
- {fh_matui-0.9.15 → fh_matui-0.9.17}/LICENSE +0 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/MANIFEST.in +0 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui.egg-info/SOURCES.txt +0 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui.egg-info/dependency_links.txt +0 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui.egg-info/entry_points.txt +0 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui.egg-info/not-zip-safe +0 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui.egg-info/requires.txt +0 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/fh_matui.egg-info/top_level.txt +0 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/setup.cfg +0 -0
- {fh_matui-0.9.15 → fh_matui-0.9.17}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fh-matui
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.17
|
|
4
4
|
Summary: material-ui for fasthtml
|
|
5
5
|
Home-page: https://github.com/abhisheksreesaila/fh-matui
|
|
6
6
|
Author: abhishek sreesaila
|
|
@@ -84,22 +84,24 @@ responsive web interfaces entirely in Python — no JavaScript required.
|
|
|
84
84
|
|
|
85
85
|
fh-matui includes 15+ pre-configured Material Design 3 color themes:
|
|
86
86
|
|
|
87
|
-
| Theme
|
|
88
|
-
|
|
89
|
-
| `MatTheme.red`
|
|
90
|
-
| `MatTheme.purple`
|
|
91
|
-
| `MatTheme.indigo`
|
|
92
|
-
| `MatTheme.
|
|
93
|
-
| `MatTheme.teal`
|
|
94
|
-
| `MatTheme.
|
|
95
|
-
| `MatTheme.yellow`
|
|
96
|
-
| `MatTheme.orange`
|
|
87
|
+
| Theme | Preview | Theme | Preview |
|
|
88
|
+
|------------------------|---------|------------------------|---------|
|
|
89
|
+
| `MatTheme.red` | 🔴 | `MatTheme.pink` | 🩷 |
|
|
90
|
+
| `MatTheme.purple` | 🟣 | `MatTheme.deep_purple` | 💜 |
|
|
91
|
+
| `MatTheme.indigo` | 🔵 | `MatTheme.blue` | 💙 |
|
|
92
|
+
| `MatTheme.light_blue` | 🩵 | `MatTheme.cyan` | 🌊 |
|
|
93
|
+
| `MatTheme.teal` | 🩶 | `MatTheme.green` | 💚 |
|
|
94
|
+
| `MatTheme.light_green` | 🍀 | `MatTheme.lime` | 💛 |
|
|
95
|
+
| `MatTheme.yellow` | 🌟 | `MatTheme.amber` | 🧡 |
|
|
96
|
+
| `MatTheme.orange` | 🟠 | `MatTheme.deep_orange` | 🔶 |
|
|
97
|
+
| `MatTheme.brown` | 🟤 | `MatTheme.grey` | ⚪ |
|
|
98
|
+
| `MatTheme.blue_grey` | 🔘 | | |
|
|
97
99
|
|
|
98
100
|
**Usage:**
|
|
99
101
|
|
|
100
102
|
``` python
|
|
101
103
|
# Choose your theme
|
|
102
|
-
app, rt = fast_app(hdrs=[MatTheme.
|
|
104
|
+
app, rt = fast_app(hdrs=[MatTheme.deep_purple.headers()])
|
|
103
105
|
```
|
|
104
106
|
|
|
105
107
|
## 🚀 Quick Start
|
|
@@ -120,7 +122,6 @@ def home():
|
|
|
120
122
|
Card(
|
|
121
123
|
H3("Welcome to fh-matui!"),
|
|
122
124
|
P("Build beautiful Material Design apps with Python."),
|
|
123
|
-
FormField("email", label="Email", type="email"),
|
|
124
125
|
Button("Get Started", cls="primary"),
|
|
125
126
|
),
|
|
126
127
|
cls="padding"
|
|
@@ -46,22 +46,24 @@ responsive web interfaces entirely in Python — no JavaScript required.
|
|
|
46
46
|
|
|
47
47
|
fh-matui includes 15+ pre-configured Material Design 3 color themes:
|
|
48
48
|
|
|
49
|
-
| Theme
|
|
50
|
-
|
|
51
|
-
| `MatTheme.red`
|
|
52
|
-
| `MatTheme.purple`
|
|
53
|
-
| `MatTheme.indigo`
|
|
54
|
-
| `MatTheme.
|
|
55
|
-
| `MatTheme.teal`
|
|
56
|
-
| `MatTheme.
|
|
57
|
-
| `MatTheme.yellow`
|
|
58
|
-
| `MatTheme.orange`
|
|
49
|
+
| Theme | Preview | Theme | Preview |
|
|
50
|
+
|------------------------|---------|------------------------|---------|
|
|
51
|
+
| `MatTheme.red` | 🔴 | `MatTheme.pink` | 🩷 |
|
|
52
|
+
| `MatTheme.purple` | 🟣 | `MatTheme.deep_purple` | 💜 |
|
|
53
|
+
| `MatTheme.indigo` | 🔵 | `MatTheme.blue` | 💙 |
|
|
54
|
+
| `MatTheme.light_blue` | 🩵 | `MatTheme.cyan` | 🌊 |
|
|
55
|
+
| `MatTheme.teal` | 🩶 | `MatTheme.green` | 💚 |
|
|
56
|
+
| `MatTheme.light_green` | 🍀 | `MatTheme.lime` | 💛 |
|
|
57
|
+
| `MatTheme.yellow` | 🌟 | `MatTheme.amber` | 🧡 |
|
|
58
|
+
| `MatTheme.orange` | 🟠 | `MatTheme.deep_orange` | 🔶 |
|
|
59
|
+
| `MatTheme.brown` | 🟤 | `MatTheme.grey` | ⚪ |
|
|
60
|
+
| `MatTheme.blue_grey` | 🔘 | | |
|
|
59
61
|
|
|
60
62
|
**Usage:**
|
|
61
63
|
|
|
62
64
|
``` python
|
|
63
65
|
# Choose your theme
|
|
64
|
-
app, rt = fast_app(hdrs=[MatTheme.
|
|
66
|
+
app, rt = fast_app(hdrs=[MatTheme.deep_purple.headers()])
|
|
65
67
|
```
|
|
66
68
|
|
|
67
69
|
## 🚀 Quick Start
|
|
@@ -82,7 +84,6 @@ def home():
|
|
|
82
84
|
Card(
|
|
83
85
|
H3("Welcome to fh-matui!"),
|
|
84
86
|
P("Build beautiful Material Design apps with Python."),
|
|
85
|
-
FormField("email", label="Email", type="email"),
|
|
86
87
|
Button("Get Started", cls="primary"),
|
|
87
88
|
),
|
|
88
89
|
cls="padding"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.9.16"
|
|
@@ -61,6 +61,7 @@ d = { 'settings': { 'branch': 'master',
|
|
|
61
61
|
'fh_matui.components.Radio': ('components.html#radio', 'fh_matui/components.py'),
|
|
62
62
|
'fh_matui.components.Range': ('components.html#range', 'fh_matui/components.py'),
|
|
63
63
|
'fh_matui.components.Select': ('components.html#select', 'fh_matui/components.py'),
|
|
64
|
+
'fh_matui.components.SelectMenu': ('components.html#selectmenu', 'fh_matui/components.py'),
|
|
64
65
|
'fh_matui.components.Small': ('components.html#small', 'fh_matui/components.py'),
|
|
65
66
|
'fh_matui.components.Snackbar': ('components.html#snackbar', 'fh_matui/components.py'),
|
|
66
67
|
'fh_matui.components.SpaceT': ('components.html#spacet', 'fh_matui/components.py'),
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/03_app_pages.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% auto 0
|
|
5
|
+
# %% auto #0
|
|
6
6
|
__all__ = ['LoginScreen']
|
|
7
7
|
|
|
8
|
-
# %% ../nbs/03_app_pages.ipynb
|
|
8
|
+
# %% ../nbs/03_app_pages.ipynb #99dc363f
|
|
9
9
|
from fastcore.utils import *
|
|
10
10
|
from fasthtml.common import *
|
|
11
11
|
from fasthtml.jupyter import *
|
|
@@ -18,7 +18,7 @@ from .components import *
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
# %% ../nbs/03_app_pages.ipynb
|
|
21
|
+
# %% ../nbs/03_app_pages.ipynb #47b3b49d
|
|
22
22
|
# Default inspirational quotes for login screen (fully customizable via parameter)
|
|
23
23
|
_DEFAULT_LOGIN_QUOTES = [
|
|
24
24
|
"The only way to do great work is to love what you do. — Steve Jobs",
|
|
@@ -2,19 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/02_components.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% auto 0
|
|
5
|
+
# %% auto #0
|
|
6
6
|
__all__ = ['BUTTON_SPECIALS', 'ButtonT', 'ANCHOR_SPECIALS', 'AT', 'NavToggleButton', 'SpaceT', 'GridSpanT', 'GridCell', 'Grid',
|
|
7
7
|
'DivHStacked', 'DivLAligned', 'DivVStacked', 'DivRAligned', 'DivCentered', 'DivFullySpaced', 'Icon',
|
|
8
8
|
'NavBar', 'Modal', 'ModalButton', 'ModalCancel', 'ModalConfirm', 'ModalTitle', 'ModalBody', 'ModalFooter',
|
|
9
|
-
'Field', 'LabelInput', 'FormLabel', 'CheckboxX', 'Radio', 'Switch', 'TextArea', 'Range', '
|
|
10
|
-
'FormGrid', 'Progress', 'LoadingIndicator', 'Table', 'Td', 'Th', 'Thead', 'Tbody', 'Tfoot',
|
|
11
|
-
'TableFromDicts', 'TableControls', 'Pagination', 'Card', 'Toolbar', 'Toast', 'Snackbar',
|
|
12
|
-
'FormField', 'FormModal', 'NavContainer', 'NavHeaderLi', 'NavDividerLi', 'NavCloseLi',
|
|
13
|
-
'BottomNav', 'NavSideBarHeader', 'NavSideBarLinks', 'NavSideBarContainer', 'Page', 'Layout',
|
|
14
|
-
'TextPresets', 'CodeSpan', 'CodeBlock', 'Blockquote', 'Q', 'Em', 'Strong', 'Small', 'Mark', 'Abbr',
|
|
15
|
-
'Sup', 'FAQItem', 'CookiesBanner']
|
|
16
|
-
|
|
17
|
-
# %% ../nbs/02_components.ipynb
|
|
9
|
+
'Field', 'LabelInput', 'FormLabel', 'CheckboxX', 'Radio', 'Switch', 'TextArea', 'Range', 'SelectMenu',
|
|
10
|
+
'Select', 'FormGrid', 'Progress', 'LoadingIndicator', 'Table', 'Td', 'Th', 'Thead', 'Tbody', 'Tfoot',
|
|
11
|
+
'TableFromLists', 'TableFromDicts', 'TableControls', 'Pagination', 'Card', 'Toolbar', 'Toast', 'Snackbar',
|
|
12
|
+
'ContainerT', 'FormField', 'FormModal', 'NavContainer', 'NavHeaderLi', 'NavDividerLi', 'NavCloseLi',
|
|
13
|
+
'NavSubtitle', 'BottomNav', 'NavSideBarHeader', 'NavSideBarLinks', 'NavSideBarContainer', 'Page', 'Layout',
|
|
14
|
+
'TextT', 'TextPresets', 'CodeSpan', 'CodeBlock', 'Blockquote', 'Q', 'Em', 'Strong', 'Small', 'Mark', 'Abbr',
|
|
15
|
+
'Sub', 'Sup', 'FAQItem', 'CookiesBanner']
|
|
16
|
+
|
|
17
|
+
# %% ../nbs/02_components.ipynb #32bd23ad
|
|
18
18
|
from fastcore.utils import *
|
|
19
19
|
from fasthtml.common import *
|
|
20
20
|
from fasthtml.jupyter import *
|
|
@@ -26,7 +26,7 @@ from .core import *
|
|
|
26
26
|
from nbdev.showdoc import show_doc
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
# %% ../nbs/02_components.ipynb
|
|
29
|
+
# %% ../nbs/02_components.ipynb #31957c2d
|
|
30
30
|
#| code-fold: true
|
|
31
31
|
def NavToggleButton(target, icon='menu', **kwargs):
|
|
32
32
|
"""Create a navigation toggle button that toggles the 'max' class on the target element.
|
|
@@ -39,7 +39,7 @@ def NavToggleButton(target, icon='menu', **kwargs):
|
|
|
39
39
|
kwargs.update({'onclick': onclick, 'cls': cls})
|
|
40
40
|
return Button(I(icon), **kwargs)
|
|
41
41
|
|
|
42
|
-
# %% ../nbs/02_components.ipynb
|
|
42
|
+
# %% ../nbs/02_components.ipynb #aea20daf
|
|
43
43
|
#| code-fold: true
|
|
44
44
|
BUTTON_SPECIALS = {
|
|
45
45
|
'primary': 'primary',
|
|
@@ -64,7 +64,7 @@ for name, css in BUTTON_SPECIALS.items():
|
|
|
64
64
|
|
|
65
65
|
ButtonT = _ButtonChain()
|
|
66
66
|
|
|
67
|
-
# %% ../nbs/02_components.ipynb
|
|
67
|
+
# %% ../nbs/02_components.ipynb #ba073cd1
|
|
68
68
|
#| code-fold: true
|
|
69
69
|
class _AnchorChain(BeerCssChain):
|
|
70
70
|
"""Chainable anchor style helper"""
|
|
@@ -88,7 +88,7 @@ for name, css in ANCHOR_SPECIALS.items():
|
|
|
88
88
|
|
|
89
89
|
AT = _AnchorChain()
|
|
90
90
|
|
|
91
|
-
# %% ../nbs/02_components.ipynb
|
|
91
|
+
# %% ../nbs/02_components.ipynb #e55766c2
|
|
92
92
|
class SpaceT(VEnum):
|
|
93
93
|
"""Space types using BeerCSS spacing classes"""
|
|
94
94
|
no_space = 'no-space'
|
|
@@ -232,7 +232,7 @@ def Grid(*cells, space=SpaceT.medium_space,
|
|
|
232
232
|
|
|
233
233
|
return Div(*wrapped_cells, cls=stringify(dedupe_preserve_order(grid_cls)), **kwargs)
|
|
234
234
|
|
|
235
|
-
# %% ../nbs/02_components.ipynb
|
|
235
|
+
# %% ../nbs/02_components.ipynb #1264260c
|
|
236
236
|
def DivHStacked(*c, responsive=True, padding=True, cls='', **kwargs):
|
|
237
237
|
"""Responsive horizontal stack with padding and mobile compatibility."""
|
|
238
238
|
cls_tokens = normalize_tokens(cls)
|
|
@@ -250,7 +250,7 @@ def DivHStacked(*c, responsive=True, padding=True, cls='', **kwargs):
|
|
|
250
250
|
return Div(*c, cls=stringify(dedupe_preserve_order(tokens)), **kwargs)
|
|
251
251
|
|
|
252
252
|
|
|
253
|
-
# %% ../nbs/02_components.ipynb
|
|
253
|
+
# %% ../nbs/02_components.ipynb #16cae24c
|
|
254
254
|
def DivLAligned(*c, cls='', **kwargs):
|
|
255
255
|
"""MonsterUI-compatible left-aligned row using BeerCSS tokens."""
|
|
256
256
|
cls_tokens = normalize_tokens(cls)
|
|
@@ -261,7 +261,7 @@ def DivLAligned(*c, cls='', **kwargs):
|
|
|
261
261
|
|
|
262
262
|
|
|
263
263
|
|
|
264
|
-
# %% ../nbs/02_components.ipynb
|
|
264
|
+
# %% ../nbs/02_components.ipynb #2435d500
|
|
265
265
|
def DivVStacked(*c, responsive=True, padding=True, cls='', **kwargs):
|
|
266
266
|
"""Responsive vertical stack with padding and mobile compatibility."""
|
|
267
267
|
cls_tokens = normalize_tokens(cls)
|
|
@@ -279,7 +279,7 @@ def DivVStacked(*c, responsive=True, padding=True, cls='', **kwargs):
|
|
|
279
279
|
return Div(*c, cls=stringify(dedupe_preserve_order(tokens)), **kwargs)
|
|
280
280
|
|
|
281
281
|
|
|
282
|
-
# %% ../nbs/02_components.ipynb
|
|
282
|
+
# %% ../nbs/02_components.ipynb #1c19a699
|
|
283
283
|
def DivRAligned(*c, cls='', **kwargs):
|
|
284
284
|
"""MonsterUI-compatible right-aligned row using BeerCSS tokens."""
|
|
285
285
|
cls_tokens = normalize_tokens(cls)
|
|
@@ -288,7 +288,7 @@ def DivRAligned(*c, cls='', **kwargs):
|
|
|
288
288
|
tokens = [t for t in tokens if t]
|
|
289
289
|
return DivHStacked(*c, cls=stringify(dedupe_preserve_order(tokens)), **kwargs)
|
|
290
290
|
|
|
291
|
-
# %% ../nbs/02_components.ipynb
|
|
291
|
+
# %% ../nbs/02_components.ipynb #0a028d84
|
|
292
292
|
def DivCentered(*c, cls='', **kwargs):
|
|
293
293
|
"""Center-aligned container using BeerCSS tokens."""
|
|
294
294
|
cls_tokens = normalize_tokens(cls)
|
|
@@ -297,7 +297,7 @@ def DivCentered(*c, cls='', **kwargs):
|
|
|
297
297
|
tokens = [t for t in tokens if t]
|
|
298
298
|
return Div(*c, cls=stringify(dedupe_preserve_order(tokens)), **kwargs)
|
|
299
299
|
|
|
300
|
-
# %% ../nbs/02_components.ipynb
|
|
300
|
+
# %% ../nbs/02_components.ipynb #7b232371
|
|
301
301
|
def DivFullySpaced(*c, cls='', **kwargs):
|
|
302
302
|
"""Row with children stretched to far ends using BeerCSS `max` spacers."""
|
|
303
303
|
cls_tokens = normalize_tokens(cls)
|
|
@@ -318,7 +318,7 @@ def DivFullySpaced(*c, cls='', **kwargs):
|
|
|
318
318
|
base = spaced_children
|
|
319
319
|
return Div(*base, cls=stringify(dedupe_preserve_order(tokens)), **kwargs)
|
|
320
320
|
|
|
321
|
-
# %% ../nbs/02_components.ipynb
|
|
321
|
+
# %% ../nbs/02_components.ipynb #0e0bbaca
|
|
322
322
|
#| code-fold: true
|
|
323
323
|
def Icon(icon: str, size: str = None, fill: bool = False, cls = (), **kwargs):
|
|
324
324
|
"""Material Design icon with optional size and fill"""
|
|
@@ -329,7 +329,7 @@ def Icon(icon: str, size: str = None, fill: bool = False, cls = (), **kwargs):
|
|
|
329
329
|
cls_str = ' '.join(icon_cls) if icon_cls else None
|
|
330
330
|
return I(icon, cls=cls_str, **kwargs) if cls_str else I(icon, **kwargs)
|
|
331
331
|
|
|
332
|
-
# %% ../nbs/02_components.ipynb
|
|
332
|
+
# %% ../nbs/02_components.ipynb #f1e25f09
|
|
333
333
|
#| code-fold: true
|
|
334
334
|
def NavBar(*children, brand=None, sticky=False, cls='', size='small',
|
|
335
335
|
hx_boost=True, hx_target='#main-content', **kwargs):
|
|
@@ -357,7 +357,7 @@ def NavBar(*children, brand=None, sticky=False, cls='', size='small',
|
|
|
357
357
|
return Nav(brand, Div(cls='max'), *children, cls=f"row middle-align {padding_cls} {nav_cls}", **kwargs)
|
|
358
358
|
return Nav(*children, cls=f"{padding_cls} {nav_cls}", **kwargs)
|
|
359
359
|
|
|
360
|
-
# %% ../nbs/02_components.ipynb
|
|
360
|
+
# %% ../nbs/02_components.ipynb #6d92c66b
|
|
361
361
|
def Modal(*c, id=None, footer=None, active=False, overlay='default', position=None, cls=(), **kwargs):
|
|
362
362
|
"""BeerCSS modal dialog with position and overlay options.
|
|
363
363
|
|
|
@@ -460,7 +460,7 @@ def ModalFooter(*c, cls=(), **kwargs):
|
|
|
460
460
|
footer_cls.extend(['right-align', 'no-space'])
|
|
461
461
|
return Nav(*c, cls=' '.join(footer_cls), **kwargs)
|
|
462
462
|
|
|
463
|
-
# %% ../nbs/02_components.ipynb
|
|
463
|
+
# %% ../nbs/02_components.ipynb #3a16954d
|
|
464
464
|
#| code-fold: true
|
|
465
465
|
def Field(*c, label: bool = False, prefix: bool = False, suffix: bool = False, cls = '', **kwargs):
|
|
466
466
|
"""BeerCSS field wrapper for inputs with border/label/prefix/suffix styling."""
|
|
@@ -485,7 +485,7 @@ def LabelInput(label: str, id: str = None, placeholder: str = None, input_type:
|
|
|
485
485
|
if suffix_icon: children.append(I(suffix_icon))
|
|
486
486
|
return Field(*children, label=True, prefix=bool(prefix_icon), suffix=bool(suffix_icon), cls=cls)
|
|
487
487
|
|
|
488
|
-
# %% ../nbs/02_components.ipynb
|
|
488
|
+
# %% ../nbs/02_components.ipynb #d6668f1b
|
|
489
489
|
#| code-fold: true
|
|
490
490
|
def LabelInput(label: str, id: str = None, placeholder: str = None, input_type: str = 'text',
|
|
491
491
|
prefix_icon: str = None, suffix_icon: str = None, value: str = None, cls = '', **kwargs):
|
|
@@ -507,7 +507,7 @@ def FormLabel(*c, cls=(), **kwargs):
|
|
|
507
507
|
if cls_str: return Label(*c, cls=cls_str, **kwargs)
|
|
508
508
|
return Label(*c, **kwargs)
|
|
509
509
|
|
|
510
|
-
# %% ../nbs/02_components.ipynb
|
|
510
|
+
# %% ../nbs/02_components.ipynb #b9cf5341
|
|
511
511
|
#| code-fold: true
|
|
512
512
|
def CheckboxX(*c, cls=(), **kwargs):
|
|
513
513
|
"""BeerCSS checkbox with label support."""
|
|
@@ -517,7 +517,7 @@ def CheckboxX(*c, cls=(), **kwargs):
|
|
|
517
517
|
cls_str = stringify(checkbox_cls)
|
|
518
518
|
return Label(Input(type='checkbox', **kwargs), Span(label_text) if label_text else Span(), cls=cls_str)
|
|
519
519
|
|
|
520
|
-
# %% ../nbs/02_components.ipynb
|
|
520
|
+
# %% ../nbs/02_components.ipynb #784de047
|
|
521
521
|
#| code-fold: true
|
|
522
522
|
def Radio(*c, cls=(), **kwargs):
|
|
523
523
|
"""BeerCSS radio button with label."""
|
|
@@ -527,7 +527,7 @@ def Radio(*c, cls=(), **kwargs):
|
|
|
527
527
|
cls_str = stringify(radio_cls)
|
|
528
528
|
return Label(Input(type='radio', **kwargs), Span(label_text) if label_text else Span(), cls=cls_str)
|
|
529
529
|
|
|
530
|
-
# %% ../nbs/02_components.ipynb
|
|
530
|
+
# %% ../nbs/02_components.ipynb #128c2b3c
|
|
531
531
|
#| code-fold: true
|
|
532
532
|
def Switch(*c, cls=(), **kwargs):
|
|
533
533
|
"""BeerCSS toggle switch for on/off states.
|
|
@@ -554,7 +554,7 @@ def Switch(*c, cls=(), **kwargs):
|
|
|
554
554
|
cls='middle-align'
|
|
555
555
|
)
|
|
556
556
|
|
|
557
|
-
# %% ../nbs/02_components.ipynb
|
|
557
|
+
# %% ../nbs/02_components.ipynb #0c1c2718
|
|
558
558
|
#| code-fold: true
|
|
559
559
|
def TextArea(*c, cls=(), **kwargs):
|
|
560
560
|
"""BeerCSS textarea with field wrapper for consistent styling."""
|
|
@@ -562,7 +562,7 @@ def TextArea(*c, cls=(), **kwargs):
|
|
|
562
562
|
textarea = Textarea(content, **kwargs) if content else Textarea(**kwargs)
|
|
563
563
|
return Field(textarea, cls=cls)
|
|
564
564
|
|
|
565
|
-
# %% ../nbs/02_components.ipynb
|
|
565
|
+
# %% ../nbs/02_components.ipynb #ddbbd284
|
|
566
566
|
#| code-fold: true
|
|
567
567
|
def Range(*c, min=None, max=None, step=None, cls=(), **kwargs):
|
|
568
568
|
"""BeerCSS range slider with two-tone fill effect."""
|
|
@@ -591,76 +591,27 @@ def Range(*c, min=None, max=None, step=None, cls=(), **kwargs):
|
|
|
591
591
|
style = f"--_start: 0%; --_end: {percentage:.1f}%;"
|
|
592
592
|
return Label(Input(**input_attrs), Span(), cls=cls_str, style=style)
|
|
593
593
|
|
|
594
|
-
# %% ../nbs/02_components.ipynb
|
|
594
|
+
# %% ../nbs/02_components.ipynb #6262a7d8
|
|
595
595
|
#| code-fold: true
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
# Fixed: Better selector, data-value support, menu closing, dual event dispatch
|
|
599
|
-
_SELECT_SCRIPT = Script("""
|
|
600
|
-
(function() {
|
|
601
|
-
if (window._fhMatuiSelectInit) return;
|
|
602
|
-
window._fhMatuiSelectInit = true;
|
|
603
|
-
|
|
604
|
-
document.addEventListener('click', function(e) {
|
|
605
|
-
// Find clicked li inside a menu that's inside a field wrapper
|
|
606
|
-
const li = e.target.closest('li');
|
|
607
|
-
if (!li) return;
|
|
608
|
-
|
|
609
|
-
// Skip transparent items (headers/labels)
|
|
610
|
-
if (li.classList.contains('transparent')) return;
|
|
611
|
-
|
|
612
|
-
// Find the menu and field wrapper
|
|
613
|
-
const menu = li.closest('menu');
|
|
614
|
-
if (!menu) return;
|
|
615
|
-
|
|
616
|
-
const wrapper = menu.closest('.field');
|
|
617
|
-
if (!wrapper) return;
|
|
618
|
-
|
|
619
|
-
// Get value: prefer data-value attribute, fallback to text content
|
|
620
|
-
const value = li.dataset.value || li.textContent.trim();
|
|
621
|
-
const displayText = li.textContent.trim();
|
|
622
|
-
|
|
623
|
-
// Update display input (readonly visible input)
|
|
624
|
-
const displayInput = wrapper.querySelector('input:not([type="hidden"])');
|
|
625
|
-
if (displayInput) displayInput.value = displayText;
|
|
626
|
-
|
|
627
|
-
// Update hidden input (form submission value)
|
|
628
|
-
const hiddenInput = wrapper.querySelector('input[type="hidden"]');
|
|
629
|
-
if (hiddenInput) hiddenInput.value = value;
|
|
630
|
-
|
|
631
|
-
// Close the menu using BeerCSS ui() function
|
|
632
|
-
if (typeof ui === 'function') {
|
|
633
|
-
ui(menu);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// Dispatch custom event for HTMX integration (on wrapper for hx-trigger)
|
|
637
|
-
wrapper.dispatchEvent(new CustomEvent('itemselected', {
|
|
638
|
-
detail: { value: value, displayText: displayText, li: li },
|
|
639
|
-
bubbles: true
|
|
640
|
-
}));
|
|
641
|
-
|
|
642
|
-
// Also dispatch change event for standard form handling
|
|
643
|
-
if (hiddenInput) {
|
|
644
|
-
hiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
|
|
645
|
-
}
|
|
646
|
-
});
|
|
647
|
-
})();
|
|
648
|
-
""", id="fh-matui-select-init")
|
|
649
|
-
|
|
650
|
-
def Select(*items, value='', placeholder='Select...', prefix_icon=None, name='', id=None, cls=(), **kwargs):
|
|
651
|
-
"""BeerCSS menu-based select dropdown with HTMX integration.
|
|
596
|
+
def SelectMenu(*items, value='', placeholder='Select...', prefix_icon=None, id=None, cls=(), **kwargs):
|
|
597
|
+
"""BeerCSS menu-based dropdown for display menus (profile, actions, navigation).
|
|
652
598
|
|
|
653
|
-
|
|
654
|
-
|
|
599
|
+
Not intended for form input - use Select for forms with native change events.
|
|
600
|
+
This component uses BeerCSS's menu element which opens/closes on click.
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
*items: Menu items - strings are auto-wrapped in Li, or pass Li elements directly
|
|
604
|
+
value: Currently displayed value text
|
|
605
|
+
placeholder: Placeholder text when no value selected
|
|
606
|
+
prefix_icon: Optional icon to show before the input
|
|
607
|
+
id: Optional wrapper ID
|
|
608
|
+
cls: Additional CSS classes
|
|
655
609
|
"""
|
|
656
610
|
menu_items = []
|
|
657
611
|
for item in items:
|
|
658
612
|
if isinstance(item, str): menu_items.append(Li(item))
|
|
659
613
|
else: menu_items.append(item)
|
|
660
614
|
|
|
661
|
-
# Auto-generate ID from name if not provided
|
|
662
|
-
wrapper_id = id or (f"select-{name}" if name else None)
|
|
663
|
-
|
|
664
615
|
children = []
|
|
665
616
|
if prefix_icon: children.append(I(prefix_icon))
|
|
666
617
|
|
|
@@ -668,16 +619,9 @@ def Select(*items, value='', placeholder='Select...', prefix_icon=None, name='',
|
|
|
668
619
|
input_attrs = {'value': value, 'readonly': True, 'placeholder': placeholder if placeholder else ' '}
|
|
669
620
|
children.append(Input(**input_attrs))
|
|
670
621
|
|
|
671
|
-
# Hidden input for form submission (carries the actual value)
|
|
672
|
-
if name:
|
|
673
|
-
children.append(Input(type='hidden', name=name, value=value))
|
|
674
|
-
|
|
675
622
|
children.append(I('arrow_drop_down'))
|
|
676
623
|
children.append(Menu(*menu_items))
|
|
677
624
|
|
|
678
|
-
# Include the one-time init script
|
|
679
|
-
children.append(_SELECT_SCRIPT)
|
|
680
|
-
|
|
681
625
|
field_cls = ['field', 'fill', 'round']
|
|
682
626
|
if prefix_icon: field_cls.append('prefix')
|
|
683
627
|
field_cls.append('suffix')
|
|
@@ -685,13 +629,75 @@ def Select(*items, value='', placeholder='Select...', prefix_icon=None, name='',
|
|
|
685
629
|
cls_str = stringify(field_cls)
|
|
686
630
|
|
|
687
631
|
wrapper_attrs = {'cls': cls_str}
|
|
688
|
-
if
|
|
689
|
-
wrapper_attrs['id'] =
|
|
632
|
+
if id:
|
|
633
|
+
wrapper_attrs['id'] = id
|
|
690
634
|
wrapper_attrs.update(kwargs)
|
|
691
635
|
|
|
692
636
|
return Div(*children, **wrapper_attrs)
|
|
693
637
|
|
|
694
|
-
|
|
638
|
+
|
|
639
|
+
def Select(*options, label=None, value='', name='', id=None, placeholder=None, cls=(), **kwargs):
|
|
640
|
+
"""BeerCSS native HTML select with floating label support.
|
|
641
|
+
|
|
642
|
+
Uses the native <select> element for full browser support including:
|
|
643
|
+
- Standard 'change' event for HTMX (hx-trigger="change")
|
|
644
|
+
- Keyboard navigation
|
|
645
|
+
- Mobile-friendly native picker
|
|
646
|
+
- Works seamlessly with cascading dropdowns
|
|
647
|
+
|
|
648
|
+
Args:
|
|
649
|
+
*options: Option items - strings, Option elements, or dicts with 'value'/'label' keys
|
|
650
|
+
label: Floating label text (optional)
|
|
651
|
+
value: Currently selected value
|
|
652
|
+
name: Form field name
|
|
653
|
+
id: Element ID (auto-generated from name if not provided)
|
|
654
|
+
placeholder: Placeholder option text (shown as disabled first option)
|
|
655
|
+
cls: Additional CSS classes for the field wrapper
|
|
656
|
+
"""
|
|
657
|
+
# Auto-generate ID from name if not provided
|
|
658
|
+
select_id = id or (f"select-{name}" if name else None)
|
|
659
|
+
|
|
660
|
+
# Build options list
|
|
661
|
+
opt_elements = []
|
|
662
|
+
|
|
663
|
+
# Add placeholder as disabled first option if provided
|
|
664
|
+
if placeholder:
|
|
665
|
+
opt_elements.append(Option(placeholder, value='', disabled=True, selected=(not value)))
|
|
666
|
+
|
|
667
|
+
for opt in options:
|
|
668
|
+
if isinstance(opt, str):
|
|
669
|
+
# String option: value and label are the same
|
|
670
|
+
opt_elements.append(Option(opt, value=opt, selected=(opt == value)))
|
|
671
|
+
elif hasattr(opt, 'tag') and opt.tag == 'option':
|
|
672
|
+
# Already an Option element, pass through
|
|
673
|
+
opt_elements.append(opt)
|
|
674
|
+
elif isinstance(opt, dict):
|
|
675
|
+
# Dict with value/label keys
|
|
676
|
+
opt_val = opt.get('value', opt.get('label', ''))
|
|
677
|
+
opt_label = opt.get('label', opt.get('value', ''))
|
|
678
|
+
opt_elements.append(Option(opt_label, value=opt_val, selected=(opt_val == value)))
|
|
679
|
+
|
|
680
|
+
# Build select element
|
|
681
|
+
select_attrs = {}
|
|
682
|
+
if name: select_attrs['name'] = name
|
|
683
|
+
if select_id: select_attrs['id'] = select_id
|
|
684
|
+
select_attrs.update(kwargs)
|
|
685
|
+
|
|
686
|
+
select_el = fc.Select(*opt_elements, **select_attrs)
|
|
687
|
+
|
|
688
|
+
# Build field wrapper - 'max' ensures full width like LabelInput
|
|
689
|
+
field_cls = ['field', 'border', 'round', 'max']
|
|
690
|
+
if label: field_cls.append('label')
|
|
691
|
+
if cls: field_cls.extend(normalize_tokens(cls))
|
|
692
|
+
cls_str = stringify(field_cls)
|
|
693
|
+
|
|
694
|
+
children = [select_el]
|
|
695
|
+
if label:
|
|
696
|
+
children.append(Label(label, fr=select_id))
|
|
697
|
+
|
|
698
|
+
return Div(*children, cls=cls_str)
|
|
699
|
+
|
|
700
|
+
# %% ../nbs/02_components.ipynb #e6576c75
|
|
695
701
|
#| code-fold: true
|
|
696
702
|
def FormGrid(*c, cols: int = 1):
|
|
697
703
|
"""Responsive grid layout for form fields that stacks on mobile."""
|
|
@@ -700,7 +706,7 @@ def FormGrid(*c, cols: int = 1):
|
|
|
700
706
|
wrapped = [Div(child, cls=col_cls) for child in c]
|
|
701
707
|
return Div(*wrapped, cls="grid")
|
|
702
708
|
|
|
703
|
-
# %% ../nbs/02_components.ipynb
|
|
709
|
+
# %% ../nbs/02_components.ipynb #5a159345
|
|
704
710
|
#| code-fold: true
|
|
705
711
|
def Progress(*c, value='', max='100', cls=(), **kwargs):
|
|
706
712
|
"""Linear progress bar with value/max support."""
|
|
@@ -712,7 +718,7 @@ def Progress(*c, value='', max='100', cls=(), **kwargs):
|
|
|
712
718
|
if cls_str: return fc.Progress(*c, cls=cls_str, **progress_attrs)
|
|
713
719
|
return fc.Progress(*c, **progress_attrs)
|
|
714
720
|
|
|
715
|
-
# %% ../nbs/02_components.ipynb
|
|
721
|
+
# %% ../nbs/02_components.ipynb #11d87dd6
|
|
716
722
|
#| code-fold: true
|
|
717
723
|
def LoadingIndicator(size='medium', cls='', **kwargs):
|
|
718
724
|
"""BeerCSS circular spinner for async operations."""
|
|
@@ -720,7 +726,7 @@ def LoadingIndicator(size='medium', cls='', **kwargs):
|
|
|
720
726
|
progress_cls = f"circle {size_cls} {cls}".strip()
|
|
721
727
|
return fc.Progress(cls=progress_cls, **kwargs)
|
|
722
728
|
|
|
723
|
-
# %% ../nbs/02_components.ipynb
|
|
729
|
+
# %% ../nbs/02_components.ipynb #396ebe54
|
|
724
730
|
#| code-fold: true
|
|
725
731
|
def Table(*c, cls = 'border', **kwargs):
|
|
726
732
|
"""BeerCSS table with optional border/stripes classes."""
|
|
@@ -788,14 +794,14 @@ def TableFromDicts(header_data, body_data, footer_data = None, header_cell_rende
|
|
|
788
794
|
Tfoot(Tr(*[footer_cell_render(k, footer_data.get(k, '')) for k in header_data])) if footer_data else None,
|
|
789
795
|
cls=cls, **kwargs)
|
|
790
796
|
|
|
791
|
-
# %% ../nbs/02_components.ipynb
|
|
797
|
+
# %% ../nbs/02_components.ipynb #0cf9871f
|
|
792
798
|
#| code-fold: true
|
|
793
799
|
def TableControls(*controls, cls='', **kwargs):
|
|
794
800
|
"""Toolbar container for table filters, search, and actions."""
|
|
795
801
|
controls_cls = f"padding middle-align space {cls}".strip()
|
|
796
802
|
return Div(*controls, cls=controls_cls, **kwargs)
|
|
797
803
|
|
|
798
|
-
# %% ../nbs/02_components.ipynb
|
|
804
|
+
# %% ../nbs/02_components.ipynb #93a6e771
|
|
799
805
|
#| code-fold: true
|
|
800
806
|
def Pagination(current_page: int, total_pages: int, hx_get: str, hx_target: str = '#table-container',
|
|
801
807
|
show_first_last: bool = True, cls='', **kwargs):
|
|
@@ -830,7 +836,7 @@ def Pagination(current_page: int, total_pages: int, hx_get: str, hx_target: str
|
|
|
830
836
|
nav_cls = f"center-align middle-align {cls}".strip()
|
|
831
837
|
return Nav(*buttons, cls=nav_cls, **kwargs)
|
|
832
838
|
|
|
833
|
-
# %% ../nbs/02_components.ipynb
|
|
839
|
+
# %% ../nbs/02_components.ipynb #c573ee87
|
|
834
840
|
#| code-fold: true
|
|
835
841
|
def Card(*c, header = None, footer = None, body_cls = 'padding', header_cls = (), footer_cls = (), cls = (), **kwargs):
|
|
836
842
|
"""BeerCSS card with optional header/footer sections."""
|
|
@@ -844,7 +850,7 @@ def Card(*c, header = None, footer = None, body_cls = 'padding', header_cls = ()
|
|
|
844
850
|
if footer is not None: sections.append(Nav(footer, cls=footer_cls) if footer_cls else Nav(footer))
|
|
845
851
|
return Article(*sections, cls=cls, **kwargs)
|
|
846
852
|
|
|
847
|
-
# %% ../nbs/02_components.ipynb
|
|
853
|
+
# %% ../nbs/02_components.ipynb #f0c71209
|
|
848
854
|
#| code-fold: true
|
|
849
855
|
def Toolbar(*items, cls='', elevate='large', fill=True, **kwargs):
|
|
850
856
|
"""BeerCSS toolbar for action bars with elevation options."""
|
|
@@ -854,7 +860,7 @@ def Toolbar(*items, cls='', elevate='large', fill=True, **kwargs):
|
|
|
854
860
|
if cls: classes.append(cls)
|
|
855
861
|
return Nav(*items, cls=' '.join(classes), **kwargs)
|
|
856
862
|
|
|
857
|
-
# %% ../nbs/02_components.ipynb
|
|
863
|
+
# %% ../nbs/02_components.ipynb #7019d70e
|
|
858
864
|
#| code-fold: true
|
|
859
865
|
def Toast(*c, cls='', position='top', variant='', action=None, active=False, dur=None, **kwargs):
|
|
860
866
|
"""BeerCSS snackbar/toast notification with position ('top' or 'bottom') and variant options."""
|
|
@@ -898,7 +904,7 @@ def Snackbar(*c, **kwargs):
|
|
|
898
904
|
"""Alias for Toast component."""
|
|
899
905
|
return Toast(*c, **kwargs)
|
|
900
906
|
|
|
901
|
-
# %% ../nbs/02_components.ipynb
|
|
907
|
+
# %% ../nbs/02_components.ipynb #fe1b0be7
|
|
902
908
|
#| code-fold: true
|
|
903
909
|
class ContainerT(VEnum):
|
|
904
910
|
"""Container size options (BeerCSS). Most alias to 'responsive'; use 'expand' for full-width."""
|
|
@@ -909,7 +915,7 @@ class ContainerT(VEnum):
|
|
|
909
915
|
xl = 'responsive'
|
|
910
916
|
expand = 'responsive max'
|
|
911
917
|
|
|
912
|
-
# %% ../nbs/02_components.ipynb
|
|
918
|
+
# %% ../nbs/02_components.ipynb #f53893ec
|
|
913
919
|
#| code-fold: true
|
|
914
920
|
def _get_form_config(col: dict) -> dict:
|
|
915
921
|
"""Extract form config from column, with sensible defaults."""
|
|
@@ -985,7 +991,7 @@ def FormField(
|
|
|
985
991
|
**attrs
|
|
986
992
|
)
|
|
987
993
|
|
|
988
|
-
# %% ../nbs/02_components.ipynb
|
|
994
|
+
# %% ../nbs/02_components.ipynb #aff2ee40
|
|
989
995
|
#| code-fold: true
|
|
990
996
|
from typing import Callable, Any
|
|
991
997
|
|
|
@@ -1104,7 +1110,7 @@ def FormModal(
|
|
|
1104
1110
|
cls="large-width"
|
|
1105
1111
|
)
|
|
1106
1112
|
|
|
1107
|
-
# %% ../nbs/02_components.ipynb
|
|
1113
|
+
# %% ../nbs/02_components.ipynb #945684a7
|
|
1108
1114
|
#| code-fold: true
|
|
1109
1115
|
def NavContainer(*li, title=None, brand=None, position='left', close_button=True, cls='active', id=None, **kwargs):
|
|
1110
1116
|
"""Slide-out navigation drawer with header and close button."""
|
|
@@ -1161,7 +1167,7 @@ def BottomNav(*c, cls='bottom', size='s', **kwargs):
|
|
|
1161
1167
|
final_cls = f"{cls} {size_cls}".strip()
|
|
1162
1168
|
return Nav(*c, cls=final_cls, **kwargs)
|
|
1163
1169
|
|
|
1164
|
-
# %% ../nbs/02_components.ipynb
|
|
1170
|
+
# %% ../nbs/02_components.ipynb #359f9555
|
|
1165
1171
|
#| code-fold: true
|
|
1166
1172
|
def NavSideBarHeader(*c, cls='', **kwargs):
|
|
1167
1173
|
"""Sidebar header section for menu buttons and branding."""
|
|
@@ -1205,7 +1211,7 @@ def NavSideBarContainer(*children, position='left', size='m', cls='', active=Fal
|
|
|
1205
1211
|
|
|
1206
1212
|
return Nav(*children, cls=nav_cls, **kwargs)
|
|
1207
1213
|
|
|
1208
|
-
# %% ../nbs/02_components.ipynb
|
|
1214
|
+
# %% ../nbs/02_components.ipynb #541b2d99
|
|
1209
1215
|
#| code-fold: true
|
|
1210
1216
|
def Page(*c, active=True, position=None, cls='', **kwargs):
|
|
1211
1217
|
"""BeerCSS animated page container.
|
|
@@ -1313,7 +1319,7 @@ def Layout(*content, sidebar=None, sidebar_links=None, nav_bar=None, container_s
|
|
|
1313
1319
|
return Div(*layout_children, cls=final_cls, **kwargs)
|
|
1314
1320
|
|
|
1315
1321
|
|
|
1316
|
-
# %% ../nbs/02_components.ipynb
|
|
1322
|
+
# %% ../nbs/02_components.ipynb #4329c780
|
|
1317
1323
|
#| code-fold: true
|
|
1318
1324
|
class TextT(VEnum):
|
|
1319
1325
|
"""Text styles using BeerCSS typography classes."""
|
|
@@ -1345,7 +1351,7 @@ class TextPresets(VEnum):
|
|
|
1345
1351
|
primary_link = 'link primary-text'
|
|
1346
1352
|
muted_link = 'link secondary-text'
|
|
1347
1353
|
|
|
1348
|
-
# %% ../nbs/02_components.ipynb
|
|
1354
|
+
# %% ../nbs/02_components.ipynb #c62d0db5
|
|
1349
1355
|
#| code-fold: true
|
|
1350
1356
|
def CodeSpan(*c, cls=(), **kwargs):
|
|
1351
1357
|
"""Inline code snippet."""
|
|
@@ -1401,7 +1407,7 @@ def Sup(*c, cls=(), **kwargs):
|
|
|
1401
1407
|
cls_str = stringify(cls) if cls else None
|
|
1402
1408
|
return fc.Sup(*c, cls=cls_str, **kwargs) if cls_str else fc.Sup(*c, **kwargs)
|
|
1403
1409
|
|
|
1404
|
-
# %% ../nbs/02_components.ipynb
|
|
1410
|
+
# %% ../nbs/02_components.ipynb #15d01ef3
|
|
1405
1411
|
#| code-fold: true
|
|
1406
1412
|
def FAQItem(question: str, answer: str, question_cls: str = '', answer_cls: str = ''):
|
|
1407
1413
|
"""Collapsible FAQ item using details/summary.
|
|
@@ -1419,7 +1425,7 @@ def FAQItem(question: str, answer: str, question_cls: str = '', answer_cls: str
|
|
|
1419
1425
|
Summary(Article(Nav(Div(question, cls=f"max bold {question_cls}".strip()), I("expand_more")), cls="round surface-variant border no-elevate")),
|
|
1420
1426
|
Article(P(answer, cls=f"secondary-text {answer_cls}".strip()), cls="round border padding"))
|
|
1421
1427
|
|
|
1422
|
-
# %% ../nbs/02_components.ipynb
|
|
1428
|
+
# %% ../nbs/02_components.ipynb #287c86c3
|
|
1423
1429
|
#| code-fold: true
|
|
1424
1430
|
def CookiesBanner(message='We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.',
|
|
1425
1431
|
accept_text='Accept', decline_text='Decline', settings_text=None, policy_link='/cookies', policy_text='Learn more',
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_core.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% auto 0
|
|
5
|
+
# %% auto #0
|
|
6
6
|
__all__ = ['HEADER_URLS', 'beer_hdrs', 'COLOR_NAMES', 'SIZES', 'WIDTH_HEIGHT', 'ELEVATES', 'DIRECTIONS', 'FORMS', 'MARGINS',
|
|
7
7
|
'PADDINGS', 'POSITIONS', 'RESPONSIVE', 'ALIGNMENTS', 'BLURS', 'OPACITIES', 'SHADOWS', 'SPACES', 'RIPPLES',
|
|
8
8
|
'SCROLLS', 'WAVES', 'ZOOMS', 'THEME_HELPERS', 'TYPOGRAPHY', 'TRIGGERS', 'COLOR_HELPERS', 'ALL_HELPERS',
|
|
9
9
|
'MatTheme', 'BeerCssChain']
|
|
10
10
|
|
|
11
|
-
# %% ../nbs/01_core.ipynb
|
|
11
|
+
# %% ../nbs/01_core.ipynb #4894c2b5
|
|
12
12
|
from fastcore.utils import *
|
|
13
13
|
from fasthtml.common import *
|
|
14
14
|
from fasthtml.jupyter import *
|
|
@@ -17,7 +17,7 @@ from fasthtml.common import A, Button as FhButton, I, Span
|
|
|
17
17
|
from .foundations import normalize_tokens, dedupe_preserve_order, stringify, listify, VEnum
|
|
18
18
|
from nbdev.showdoc import show_doc
|
|
19
19
|
|
|
20
|
-
# %% ../nbs/01_core.ipynb
|
|
20
|
+
# %% ../nbs/01_core.ipynb #8e02ad09
|
|
21
21
|
#| code-fold: true
|
|
22
22
|
#| code-fold: true
|
|
23
23
|
HEADER_URLS = {
|
|
@@ -32,7 +32,7 @@ beer_hdrs = (
|
|
|
32
32
|
Script(src=HEADER_URLS["mdc_js"], type='module'),
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
-
# %% ../nbs/01_core.ipynb
|
|
35
|
+
# %% ../nbs/01_core.ipynb #2e3ec12e
|
|
36
36
|
#| code-fold: true
|
|
37
37
|
#| code-fold: true
|
|
38
38
|
# All BeerCSS color names
|
|
@@ -90,7 +90,7 @@ ALL_HELPERS = (SIZES + WIDTH_HEIGHT + ELEVATES + DIRECTIONS + FORMS + MARGINS +
|
|
|
90
90
|
POSITIONS + RESPONSIVE + ALIGNMENTS + BLURS + OPACITIES + SHADOWS + SPACES +
|
|
91
91
|
RIPPLES + SCROLLS + WAVES + ZOOMS + THEME_HELPERS + TYPOGRAPHY + TRIGGERS + COLOR_HELPERS)
|
|
92
92
|
|
|
93
|
-
# %% ../nbs/01_core.ipynb
|
|
93
|
+
# %% ../nbs/01_core.ipynb #74645b97
|
|
94
94
|
#| code-fold: true
|
|
95
95
|
#| code-fold: true
|
|
96
96
|
class _ThemeChain:
|
|
@@ -157,7 +157,7 @@ class _ThemeChain:
|
|
|
157
157
|
hdrs.append(Title(title))
|
|
158
158
|
return tuple(hdrs)
|
|
159
159
|
|
|
160
|
-
# %% ../nbs/01_core.ipynb
|
|
160
|
+
# %% ../nbs/01_core.ipynb #18fbe01e
|
|
161
161
|
#| code-fold: true
|
|
162
162
|
#| code-fold: true
|
|
163
163
|
class _ThemeNamespace:
|
|
@@ -217,7 +217,7 @@ class _ThemeNamespace:
|
|
|
217
217
|
|
|
218
218
|
MatTheme = _ThemeNamespace()
|
|
219
219
|
|
|
220
|
-
# %% ../nbs/01_core.ipynb
|
|
220
|
+
# %% ../nbs/01_core.ipynb #4672930e
|
|
221
221
|
#| code-fold: true
|
|
222
222
|
#| code-fold: true
|
|
223
223
|
class BeerCssChain:
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/05_datatable.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% auto 0
|
|
5
|
+
# %% auto #0
|
|
6
6
|
__all__ = ['PAGE_SIZES', 'TABLE_STYLES', 'TABLE_SPACES', 'TABLE_ALIGNS', 'logger', 'table_state_from_request', 'DataTable',
|
|
7
7
|
'CrudContext', 'DataTableResource']
|
|
8
8
|
|
|
9
|
-
# %% ../nbs/05_datatable.ipynb
|
|
9
|
+
# %% ../nbs/05_datatable.ipynb #d7f65962
|
|
10
10
|
from fastcore.utils import *
|
|
11
11
|
from fasthtml.common import *
|
|
12
12
|
from fasthtml.jupyter import *
|
|
@@ -18,7 +18,7 @@ from .core import *
|
|
|
18
18
|
from nbdev.showdoc import show_doc
|
|
19
19
|
from .components import *
|
|
20
20
|
|
|
21
|
-
# %% ../nbs/05_datatable.ipynb
|
|
21
|
+
# %% ../nbs/05_datatable.ipynb #3a9c1b0c
|
|
22
22
|
#| code-fold: true
|
|
23
23
|
#| code-fold: true
|
|
24
24
|
from math import ceil
|
|
@@ -384,7 +384,7 @@ def DataTable(
|
|
|
384
384
|
hx_swap="outerHTML"
|
|
385
385
|
)
|
|
386
386
|
|
|
387
|
-
# %% ../nbs/05_datatable.ipynb
|
|
387
|
+
# %% ../nbs/05_datatable.ipynb #b84c744d
|
|
388
388
|
import asyncio
|
|
389
389
|
import logging
|
|
390
390
|
from dataclasses import dataclass
|
|
@@ -480,7 +480,7 @@ class CrudContext:
|
|
|
480
480
|
record_id: Optional[Any] = None # ID for update/delete (None for create)
|
|
481
481
|
feedback_id: Optional[str] = None # Target div ID for HTMX swap (for override handlers)
|
|
482
482
|
|
|
483
|
-
# %% ../nbs/05_datatable.ipynb
|
|
483
|
+
# %% ../nbs/05_datatable.ipynb #63468220
|
|
484
484
|
from typing import Callable, Optional, Any, Union
|
|
485
485
|
from dataclasses import asdict, is_dataclass
|
|
486
486
|
from datetime import datetime
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_foundations.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% auto 0
|
|
5
|
+
# %% auto #0
|
|
6
6
|
__all__ = ['VEnum', 'stringify', 'normalize_tokens', 'dedupe_preserve_order']
|
|
7
7
|
|
|
8
|
-
# %% ../nbs/00_foundations.ipynb
|
|
8
|
+
# %% ../nbs/00_foundations.ipynb #f1c2465a
|
|
9
9
|
from typing import Any, Iterable, Optional, Union
|
|
10
10
|
from enum import Enum
|
|
11
11
|
from fastcore.utils import *
|
|
12
12
|
from nbdev.showdoc import show_doc
|
|
13
13
|
|
|
14
|
-
# %% ../nbs/00_foundations.ipynb
|
|
14
|
+
# %% ../nbs/00_foundations.ipynb #4411c74b
|
|
15
15
|
#| code-fold: true
|
|
16
16
|
#| code-fold: true
|
|
17
17
|
class VEnum(Enum):
|
|
@@ -20,7 +20,7 @@ class VEnum(Enum):
|
|
|
20
20
|
def __add__(self, other): return stringify((self, other))
|
|
21
21
|
def __radd__(self, other): return stringify((other, self))
|
|
22
22
|
|
|
23
|
-
# %% ../nbs/00_foundations.ipynb
|
|
23
|
+
# %% ../nbs/00_foundations.ipynb #f4cac5a4
|
|
24
24
|
#| code-fold: true
|
|
25
25
|
#| code-fold: true
|
|
26
26
|
def stringify(o):
|
|
@@ -29,7 +29,7 @@ def stringify(o):
|
|
|
29
29
|
return ' '.join(map(str, o)) if o else ""
|
|
30
30
|
return str(o)
|
|
31
31
|
|
|
32
|
-
# %% ../nbs/00_foundations.ipynb
|
|
32
|
+
# %% ../nbs/00_foundations.ipynb #865963c6
|
|
33
33
|
#| code-fold: true
|
|
34
34
|
#| code-fold: true
|
|
35
35
|
def normalize_tokens(cls):
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/04_web_pages.ipynb.
|
|
4
4
|
|
|
5
|
-
# %% auto 0
|
|
5
|
+
# %% auto #0
|
|
6
6
|
__all__ = ['SECTION_STYLE', 'SECTION_MARGIN', 'STANDARD_FOOTER_COLUMNS', 'HeroSection', 'FeatureShowcase', 'FeaturesGrid',
|
|
7
7
|
'PricingSection', 'FAQSection', 'PageFooter', 'LandingNavBar', 'LandingPageSimple', 'MarkdownSection',
|
|
8
8
|
'ContentPage']
|
|
9
9
|
|
|
10
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
10
|
+
# %% ../nbs/04_web_pages.ipynb #7f3cabf7
|
|
11
11
|
import importlib
|
|
12
12
|
import markdown
|
|
13
13
|
from .components import *
|
|
@@ -23,7 +23,7 @@ import fasthtml.components as fc
|
|
|
23
23
|
from fasthtml.common import A, Button as FhButton, I, Span
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
26
|
+
# %% ../nbs/04_web_pages.ipynb #ec869572
|
|
27
27
|
def HeroSection(
|
|
28
28
|
title: str, # Main hero title
|
|
29
29
|
subtitle: str, # Hero subtitle/description
|
|
@@ -95,7 +95,7 @@ def HeroSection(
|
|
|
95
95
|
cls=f"hero-section {cls}".strip(),
|
|
96
96
|
)
|
|
97
97
|
|
|
98
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
98
|
+
# %% ../nbs/04_web_pages.ipynb #9aac83c5
|
|
99
99
|
def FeatureShowcase(
|
|
100
100
|
features: list, # List of dicts: {title, description, image_src?, icon?, image_alt?}
|
|
101
101
|
title: str = None, # Optional section title
|
|
@@ -178,7 +178,7 @@ def FeatureShowcase(
|
|
|
178
178
|
|
|
179
179
|
return Section(*content, cls=f"responsive {cls}".strip())
|
|
180
180
|
|
|
181
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
181
|
+
# %% ../nbs/04_web_pages.ipynb #297f69dc
|
|
182
182
|
def FeaturesGrid(
|
|
183
183
|
features: list, # List of dicts with 'icon', 'title', 'description'
|
|
184
184
|
title: str = None, # Optional section title
|
|
@@ -241,7 +241,7 @@ def FeaturesGrid(
|
|
|
241
241
|
# Use responsive for centered max-width layout
|
|
242
242
|
return Section(*content, cls=f"responsive padding {cls}".strip())
|
|
243
243
|
|
|
244
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
244
|
+
# %% ../nbs/04_web_pages.ipynb #ea5213e3
|
|
245
245
|
def _pricing_toggle_js():
|
|
246
246
|
"""Returns Script + Style for pricing toggle functionality."""
|
|
247
247
|
css = """
|
|
@@ -452,7 +452,7 @@ def PricingSection(
|
|
|
452
452
|
cls=f"responsive padding {cls}".strip(),
|
|
453
453
|
)
|
|
454
454
|
|
|
455
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
455
|
+
# %% ../nbs/04_web_pages.ipynb #35bdc231
|
|
456
456
|
def FAQSection(title: str, faqs: list, cls: str = ""):
|
|
457
457
|
"""FAQ section using FAQItem from components.
|
|
458
458
|
|
|
@@ -477,7 +477,7 @@ def FAQSection(title: str, faqs: list, cls: str = ""):
|
|
|
477
477
|
cls=f"responsive column {cls}".strip(),
|
|
478
478
|
)
|
|
479
479
|
|
|
480
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
480
|
+
# %% ../nbs/04_web_pages.ipynb #a6a029c6
|
|
481
481
|
# CSS for decorative sine wave border on footer
|
|
482
482
|
_FOOTER_WAVE_CSS = """
|
|
483
483
|
.footer-wave {
|
|
@@ -563,7 +563,7 @@ def PageFooter(
|
|
|
563
563
|
style="padding-top: 1.5rem;" if wave_border else "", # Space for wave
|
|
564
564
|
)
|
|
565
565
|
|
|
566
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
566
|
+
# %% ../nbs/04_web_pages.ipynb #be3a76d2
|
|
567
567
|
def LandingNavBar(
|
|
568
568
|
brand_name: str, # Brand name for the navbar
|
|
569
569
|
links: list = None, # List of dicts with 'text' and 'href'
|
|
@@ -612,7 +612,7 @@ def LandingNavBar(
|
|
|
612
612
|
|
|
613
613
|
return Header(inner, cls=toolbar_cls)
|
|
614
614
|
|
|
615
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
615
|
+
# %% ../nbs/04_web_pages.ipynb #a45e13b8
|
|
616
616
|
# Section spacing: padding for internal space, margin for breathing room between sections
|
|
617
617
|
SECTION_STYLE = "large-padding" # Internal padding
|
|
618
618
|
SECTION_MARGIN = "extra-margin bottom-margin" # Vertical spacing between sections
|
|
@@ -806,7 +806,7 @@ def LandingPageSimple(
|
|
|
806
806
|
cls=f"column {cls}".strip()
|
|
807
807
|
)
|
|
808
808
|
|
|
809
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
809
|
+
# %% ../nbs/04_web_pages.ipynb #5aa5632d
|
|
810
810
|
def MarkdownSection(
|
|
811
811
|
content: str, # Markdown text to render
|
|
812
812
|
title: str = None, # Optional section title (rendered as H3 for appropriate size)
|
|
@@ -858,7 +858,7 @@ We value your privacy...
|
|
|
858
858
|
article = Article(*elements, cls=f"large-width large-padding {cls}".strip())
|
|
859
859
|
return Div(article, cls="row center-align")
|
|
860
860
|
|
|
861
|
-
# %% ../nbs/04_web_pages.ipynb
|
|
861
|
+
# %% ../nbs/04_web_pages.ipynb #250d86cb
|
|
862
862
|
def ContentPage(
|
|
863
863
|
brand_name: str, # Brand name for navbar
|
|
864
864
|
footer_copyright: str, # Copyright text for footer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fh-matui
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.17
|
|
4
4
|
Summary: material-ui for fasthtml
|
|
5
5
|
Home-page: https://github.com/abhisheksreesaila/fh-matui
|
|
6
6
|
Author: abhishek sreesaila
|
|
@@ -84,22 +84,24 @@ responsive web interfaces entirely in Python — no JavaScript required.
|
|
|
84
84
|
|
|
85
85
|
fh-matui includes 15+ pre-configured Material Design 3 color themes:
|
|
86
86
|
|
|
87
|
-
| Theme
|
|
88
|
-
|
|
89
|
-
| `MatTheme.red`
|
|
90
|
-
| `MatTheme.purple`
|
|
91
|
-
| `MatTheme.indigo`
|
|
92
|
-
| `MatTheme.
|
|
93
|
-
| `MatTheme.teal`
|
|
94
|
-
| `MatTheme.
|
|
95
|
-
| `MatTheme.yellow`
|
|
96
|
-
| `MatTheme.orange`
|
|
87
|
+
| Theme | Preview | Theme | Preview |
|
|
88
|
+
|------------------------|---------|------------------------|---------|
|
|
89
|
+
| `MatTheme.red` | 🔴 | `MatTheme.pink` | 🩷 |
|
|
90
|
+
| `MatTheme.purple` | 🟣 | `MatTheme.deep_purple` | 💜 |
|
|
91
|
+
| `MatTheme.indigo` | 🔵 | `MatTheme.blue` | 💙 |
|
|
92
|
+
| `MatTheme.light_blue` | 🩵 | `MatTheme.cyan` | 🌊 |
|
|
93
|
+
| `MatTheme.teal` | 🩶 | `MatTheme.green` | 💚 |
|
|
94
|
+
| `MatTheme.light_green` | 🍀 | `MatTheme.lime` | 💛 |
|
|
95
|
+
| `MatTheme.yellow` | 🌟 | `MatTheme.amber` | 🧡 |
|
|
96
|
+
| `MatTheme.orange` | 🟠 | `MatTheme.deep_orange` | 🔶 |
|
|
97
|
+
| `MatTheme.brown` | 🟤 | `MatTheme.grey` | ⚪ |
|
|
98
|
+
| `MatTheme.blue_grey` | 🔘 | | |
|
|
97
99
|
|
|
98
100
|
**Usage:**
|
|
99
101
|
|
|
100
102
|
``` python
|
|
101
103
|
# Choose your theme
|
|
102
|
-
app, rt = fast_app(hdrs=[MatTheme.
|
|
104
|
+
app, rt = fast_app(hdrs=[MatTheme.deep_purple.headers()])
|
|
103
105
|
```
|
|
104
106
|
|
|
105
107
|
## 🚀 Quick Start
|
|
@@ -120,7 +122,6 @@ def home():
|
|
|
120
122
|
Card(
|
|
121
123
|
H3("Welcome to fh-matui!"),
|
|
122
124
|
P("Build beautiful Material Design apps with Python."),
|
|
123
|
-
FormField("email", label="Email", type="email"),
|
|
124
125
|
Button("Get Started", cls="primary"),
|
|
125
126
|
),
|
|
126
127
|
cls="padding"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name="fh-matui"
|
|
7
|
+
requires-python=">=3.9"
|
|
8
|
+
dynamic = [ "keywords", "description", "version", "dependencies", "optional-dependencies", "readme", "license", "authors", "classifiers", "entry-points", "scripts", "urls"]
|
|
9
|
+
|
|
10
|
+
[tool.uv]
|
|
11
|
+
cache-keys = [{ file = "pyproject.toml" }, { file = "settings.ini" }, { file = "setup.py" }]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.9.14"
|
fh_matui-0.9.15/pyproject.toml
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|