fh-matui 0.9.14__tar.gz → 0.9.16__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {fh_matui-0.9.14/fh_matui.egg-info → fh_matui-0.9.16}/PKG-INFO +7 -9
  2. {fh_matui-0.9.14 → fh_matui-0.9.16}/README.md +6 -8
  3. fh_matui-0.9.16/fh_matui/__init__.py +1 -0
  4. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui/_modidx.py +1 -0
  5. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui/app_pages.py +3 -3
  6. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui/components.py +180 -74
  7. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui/core.py +7 -7
  8. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui/datatable.py +37 -10
  9. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui/foundations.py +5 -5
  10. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui/web_pages.py +12 -12
  11. {fh_matui-0.9.14 → fh_matui-0.9.16/fh_matui.egg-info}/PKG-INFO +7 -9
  12. fh_matui-0.9.16/pyproject.toml +11 -0
  13. {fh_matui-0.9.14 → fh_matui-0.9.16}/settings.ini +1 -1
  14. fh_matui-0.9.14/fh_matui/__init__.py +0 -1
  15. fh_matui-0.9.14/pyproject.toml +0 -3
  16. {fh_matui-0.9.14 → fh_matui-0.9.16}/LICENSE +0 -0
  17. {fh_matui-0.9.14 → fh_matui-0.9.16}/MANIFEST.in +0 -0
  18. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui.egg-info/SOURCES.txt +0 -0
  19. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui.egg-info/dependency_links.txt +0 -0
  20. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui.egg-info/entry_points.txt +0 -0
  21. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui.egg-info/not-zip-safe +0 -0
  22. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui.egg-info/requires.txt +0 -0
  23. {fh_matui-0.9.14 → fh_matui-0.9.16}/fh_matui.egg-info/top_level.txt +0 -0
  24. {fh_matui-0.9.14 → fh_matui-0.9.16}/setup.cfg +0 -0
  25. {fh_matui-0.9.14 → fh_matui-0.9.16}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fh-matui
3
- Version: 0.9.14
3
+ Version: 0.9.16
4
4
  Summary: material-ui for fasthtml
5
5
  Home-page: https://github.com/abhisheksreesaila/fh-matui
6
6
  Author: abhishek sreesaila
@@ -145,10 +145,8 @@ styling
145
145
 
146
146
  1. **`MatTheme.indigo.headers()`** - Loads BeerCSS with the indigo
147
147
  color scheme
148
- 2. **[`Card`](https://abhisheksreesaila.github.io/fh-matui/components.html#card)** -
149
- Creates a Material Design card component with elevation
150
- 3. **[`FormField`](https://abhisheksreesaila.github.io/fh-matui/components.html#formfield)** -
151
- Generates a styled input with floating label
148
+ 2. **`Card`** - Creates a Material Design card component with elevation
149
+ 3. **`FormField`** - Generates a styled input with floating label
152
150
  4. **`Button`** - Renders a Material Design button with ripple effects
153
151
 
154
152
  ## 📚 Module Reference
@@ -157,9 +155,9 @@ styling
157
155
  |----|----|----|
158
156
  | [Foundations](foundations.html) | Base utilities and helper functions | `BeerHeaders`, `display`, styling helpers |
159
157
  | [Core](core.html) | Theme system and styling | `MatTheme`, color presets, theme configuration |
160
- | [Components](components.html) | UI component library | `Button`, [`Card`](https://abhisheksreesaila.github.io/fh-matui/components.html#card), [`FormField`](https://abhisheksreesaila.github.io/fh-matui/components.html#formfield), [`FormModal`](https://abhisheksreesaila.github.io/fh-matui/components.html#formmodal), [`Grid`](https://abhisheksreesaila.github.io/fh-matui/components.html#grid) |
158
+ | [Components](components.html) | UI component library | `Button`, `Card`, `FormField`, `FormModal`, `Grid` |
161
159
  | [App Pages](app_pages.html) | Application layouts | Navigation, sidebars, full-page layouts |
162
- | [Data Tables](05_table.html) | Data management components | [`DataTable`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatable), [`DataTableResource`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatableresource), CRUD operations |
160
+ | [Data Tables](05_table.html) | Data management components | `DataTable`, `DataTableResource`, CRUD operations |
163
161
  | [Web Pages](web_pages.html) | Marketing/landing pages | Hero sections, feature grids, testimonials |
164
162
 
165
163
  ## 🛠️ Development
@@ -187,8 +185,8 @@ nbdev_prepare
187
185
  | **Component consistency** | Unified API across all components |
188
186
  | **Dark mode support** | Built-in with automatic system preference detection |
189
187
  | **Responsive design** | Mobile-first grid system and responsive utilities |
190
- | **Form handling** | [`FormField`](https://abhisheksreesaila.github.io/fh-matui/components.html#formfield), [`FormGrid`](https://abhisheksreesaila.github.io/fh-matui/components.html#formgrid), [`FormModal`](https://abhisheksreesaila.github.io/fh-matui/components.html#formmodal) for rapid form building |
191
- | **Data management** | [`DataTable`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatable) and [`DataTableResource`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatableresource) for CRUD operations |
188
+ | **Form handling** | `FormField`, `FormGrid`, `FormModal` for rapid form building |
189
+ | **Data management** | `DataTable` and `DataTableResource` for CRUD operations |
192
190
 
193
191
  ## 🤖 For LLM Users
194
192
 
@@ -107,10 +107,8 @@ styling
107
107
 
108
108
  1. **`MatTheme.indigo.headers()`** - Loads BeerCSS with the indigo
109
109
  color scheme
110
- 2. **[`Card`](https://abhisheksreesaila.github.io/fh-matui/components.html#card)** -
111
- Creates a Material Design card component with elevation
112
- 3. **[`FormField`](https://abhisheksreesaila.github.io/fh-matui/components.html#formfield)** -
113
- Generates a styled input with floating label
110
+ 2. **`Card`** - Creates a Material Design card component with elevation
111
+ 3. **`FormField`** - Generates a styled input with floating label
114
112
  4. **`Button`** - Renders a Material Design button with ripple effects
115
113
 
116
114
  ## 📚 Module Reference
@@ -119,9 +117,9 @@ styling
119
117
  |----|----|----|
120
118
  | [Foundations](foundations.html) | Base utilities and helper functions | `BeerHeaders`, `display`, styling helpers |
121
119
  | [Core](core.html) | Theme system and styling | `MatTheme`, color presets, theme configuration |
122
- | [Components](components.html) | UI component library | `Button`, [`Card`](https://abhisheksreesaila.github.io/fh-matui/components.html#card), [`FormField`](https://abhisheksreesaila.github.io/fh-matui/components.html#formfield), [`FormModal`](https://abhisheksreesaila.github.io/fh-matui/components.html#formmodal), [`Grid`](https://abhisheksreesaila.github.io/fh-matui/components.html#grid) |
120
+ | [Components](components.html) | UI component library | `Button`, `Card`, `FormField`, `FormModal`, `Grid` |
123
121
  | [App Pages](app_pages.html) | Application layouts | Navigation, sidebars, full-page layouts |
124
- | [Data Tables](05_table.html) | Data management components | [`DataTable`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatable), [`DataTableResource`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatableresource), CRUD operations |
122
+ | [Data Tables](05_table.html) | Data management components | `DataTable`, `DataTableResource`, CRUD operations |
125
123
  | [Web Pages](web_pages.html) | Marketing/landing pages | Hero sections, feature grids, testimonials |
126
124
 
127
125
  ## 🛠️ Development
@@ -149,8 +147,8 @@ nbdev_prepare
149
147
  | **Component consistency** | Unified API across all components |
150
148
  | **Dark mode support** | Built-in with automatic system preference detection |
151
149
  | **Responsive design** | Mobile-first grid system and responsive utilities |
152
- | **Form handling** | [`FormField`](https://abhisheksreesaila.github.io/fh-matui/components.html#formfield), [`FormGrid`](https://abhisheksreesaila.github.io/fh-matui/components.html#formgrid), [`FormModal`](https://abhisheksreesaila.github.io/fh-matui/components.html#formmodal) for rapid form building |
153
- | **Data management** | [`DataTable`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatable) and [`DataTableResource`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatableresource) for CRUD operations |
150
+ | **Form handling** | `FormField`, `FormGrid`, `FormModal` for rapid form building |
151
+ | **Data management** | `DataTable` and `DataTableResource` for CRUD operations |
154
152
 
155
153
  ## 🤖 For LLM Users
156
154
 
@@ -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 2
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 6
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', 'Select',
10
- 'FormGrid', 'Progress', 'LoadingIndicator', 'Table', 'Td', 'Th', 'Thead', 'Tbody', 'Tfoot', 'TableFromLists',
11
- 'TableFromDicts', 'TableControls', 'Pagination', 'Card', 'Toolbar', 'Toast', 'Snackbar', 'ContainerT',
12
- 'FormField', 'FormModal', 'NavContainer', 'NavHeaderLi', 'NavDividerLi', 'NavCloseLi', 'NavSubtitle',
13
- 'BottomNav', 'NavSideBarHeader', 'NavSideBarLinks', 'NavSideBarContainer', 'Page', 'Layout', 'TextT',
14
- 'TextPresets', 'CodeSpan', 'CodeBlock', 'Blockquote', 'Q', 'Em', 'Strong', 'Small', 'Mark', 'Abbr', 'Sub',
15
- 'Sup', 'FAQItem', 'CookiesBanner']
16
-
17
- # %% ../nbs/02_components.ipynb 2
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 6
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 7
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 10
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 13
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'
@@ -163,28 +163,20 @@ def _snap_to_valid_cols(n: int) -> int:
163
163
  return 12
164
164
 
165
165
 
166
- def _wrap_grid_children(cells, cols=None, cols_sm=None, cols_md=None, cols_lg=None):
166
+ def _wrap_grid_children(cells, cols_sm=None, cols_md=None, cols_lg=None):
167
167
  """Wrap grid children with span classes based on column counts."""
168
168
  # If no column counts specified, return cells as-is
169
- if not any([cols, cols_sm, cols_md, cols_lg]):
169
+ if not any([cols_sm, cols_md, cols_lg]):
170
170
  return cells
171
171
 
172
172
  # Build span string from column counts
173
173
  spans = []
174
174
  if cols_sm:
175
175
  spans.append(f's{12 // cols_sm}')
176
- elif cols:
177
- spans.append(f's{12 // cols}')
178
-
179
176
  if cols_md:
180
177
  spans.append(f'm{12 // cols_md}')
181
- elif cols:
182
- spans.append(f'm{12 // cols}')
183
-
184
178
  if cols_lg:
185
179
  spans.append(f'l{12 // cols_lg}')
186
- elif cols:
187
- spans.append(f'l{12 // cols}')
188
180
 
189
181
  span_str = ' '.join(spans) if spans else 's12'
190
182
 
@@ -196,11 +188,21 @@ def Grid(*cells, space=SpaceT.medium_space,
196
188
  cols_min: int = 1, cols_max: int = 4,
197
189
  cols: int = None, cols_sm: int = None, cols_md: int = None, cols_lg: int = None,
198
190
  responsive: bool = True, padding: bool = True, cls: str = '', **kwargs):
199
- """BeerCSS responsive grid with smart column defaults and mobile-first design."""
191
+ """BeerCSS responsive grid with smart column defaults and mobile-first design.
192
+
193
+ MonsterUI-compatible API: `cols` parameter auto-derives responsive breakpoints:
194
+ cols=4 -> cols_sm=1, cols_md=2, cols_lg=4 (mobile stacks, tablet halves, desktop full)
195
+
196
+ For explicit control, use cols_sm, cols_md, cols_lg directly.
197
+ """
200
198
  # Smart defaults based on content count (MonsterUI pattern)
201
199
  if cols:
202
- # Fixed cols for all breakpoints
203
- cols_sm = cols_md = cols_lg = _snap_to_valid_cols(cols)
200
+ # Auto-derive responsive breakpoints from cols (mobile-friendly by default)
201
+ # cols=4 -> stack on mobile (1), half on tablet (2), full on desktop (4)
202
+ snapped = _snap_to_valid_cols(cols)
203
+ cols_lg = cols_lg or snapped
204
+ cols_md = cols_md or _snap_to_valid_cols(max(1, snapped // 2))
205
+ cols_sm = cols_sm or 1 # Always stack on mobile for readability
204
206
  elif not any([cols_sm, cols_md, cols_lg]):
205
207
  # Auto-calculate responsive columns based on item count
206
208
  n = len(cells)
@@ -214,21 +216,23 @@ def Grid(*cells, space=SpaceT.medium_space,
214
216
  if cols_md: cols_md = _snap_to_valid_cols(cols_md)
215
217
  if cols_lg: cols_lg = _snap_to_valid_cols(cols_lg)
216
218
 
217
- wrapped_cells = _wrap_grid_children(cells, cols, cols_sm, cols_md, cols_lg)
219
+ wrapped_cells = _wrap_grid_children(cells, cols_sm, cols_md, cols_lg)
218
220
 
219
221
  cls_tokens = normalize_tokens(cls)
220
222
  grid_cls = ['grid']
221
- if responsive and 'responsive' not in cls_tokens:
222
- grid_cls.append('responsive')
223
+
223
224
  if padding and 'padding' not in cls_tokens and 'no-padding' not in cls_tokens:
224
225
  grid_cls.append('padding')
226
+
225
227
  if space and not _has_space_token(cls_tokens):
226
228
  grid_cls.extend(normalize_tokens(space))
229
+
227
230
  grid_cls.extend(cls_tokens)
228
231
  grid_cls = [t for t in grid_cls if t]
232
+
229
233
  return Div(*wrapped_cells, cls=stringify(dedupe_preserve_order(grid_cls)), **kwargs)
230
234
 
231
- # %% ../nbs/02_components.ipynb 16
235
+ # %% ../nbs/02_components.ipynb #1264260c
232
236
  def DivHStacked(*c, responsive=True, padding=True, cls='', **kwargs):
233
237
  """Responsive horizontal stack with padding and mobile compatibility."""
234
238
  cls_tokens = normalize_tokens(cls)
@@ -246,7 +250,7 @@ def DivHStacked(*c, responsive=True, padding=True, cls='', **kwargs):
246
250
  return Div(*c, cls=stringify(dedupe_preserve_order(tokens)), **kwargs)
247
251
 
248
252
 
249
- # %% ../nbs/02_components.ipynb 17
253
+ # %% ../nbs/02_components.ipynb #16cae24c
250
254
  def DivLAligned(*c, cls='', **kwargs):
251
255
  """MonsterUI-compatible left-aligned row using BeerCSS tokens."""
252
256
  cls_tokens = normalize_tokens(cls)
@@ -257,7 +261,7 @@ def DivLAligned(*c, cls='', **kwargs):
257
261
 
258
262
 
259
263
 
260
- # %% ../nbs/02_components.ipynb 19
264
+ # %% ../nbs/02_components.ipynb #2435d500
261
265
  def DivVStacked(*c, responsive=True, padding=True, cls='', **kwargs):
262
266
  """Responsive vertical stack with padding and mobile compatibility."""
263
267
  cls_tokens = normalize_tokens(cls)
@@ -275,7 +279,7 @@ def DivVStacked(*c, responsive=True, padding=True, cls='', **kwargs):
275
279
  return Div(*c, cls=stringify(dedupe_preserve_order(tokens)), **kwargs)
276
280
 
277
281
 
278
- # %% ../nbs/02_components.ipynb 22
282
+ # %% ../nbs/02_components.ipynb #1c19a699
279
283
  def DivRAligned(*c, cls='', **kwargs):
280
284
  """MonsterUI-compatible right-aligned row using BeerCSS tokens."""
281
285
  cls_tokens = normalize_tokens(cls)
@@ -284,7 +288,7 @@ def DivRAligned(*c, cls='', **kwargs):
284
288
  tokens = [t for t in tokens if t]
285
289
  return DivHStacked(*c, cls=stringify(dedupe_preserve_order(tokens)), **kwargs)
286
290
 
287
- # %% ../nbs/02_components.ipynb 24
291
+ # %% ../nbs/02_components.ipynb #0a028d84
288
292
  def DivCentered(*c, cls='', **kwargs):
289
293
  """Center-aligned container using BeerCSS tokens."""
290
294
  cls_tokens = normalize_tokens(cls)
@@ -293,7 +297,7 @@ def DivCentered(*c, cls='', **kwargs):
293
297
  tokens = [t for t in tokens if t]
294
298
  return Div(*c, cls=stringify(dedupe_preserve_order(tokens)), **kwargs)
295
299
 
296
- # %% ../nbs/02_components.ipynb 26
300
+ # %% ../nbs/02_components.ipynb #7b232371
297
301
  def DivFullySpaced(*c, cls='', **kwargs):
298
302
  """Row with children stretched to far ends using BeerCSS `max` spacers."""
299
303
  cls_tokens = normalize_tokens(cls)
@@ -314,7 +318,7 @@ def DivFullySpaced(*c, cls='', **kwargs):
314
318
  base = spaced_children
315
319
  return Div(*base, cls=stringify(dedupe_preserve_order(tokens)), **kwargs)
316
320
 
317
- # %% ../nbs/02_components.ipynb 29
321
+ # %% ../nbs/02_components.ipynb #0e0bbaca
318
322
  #| code-fold: true
319
323
  def Icon(icon: str, size: str = None, fill: bool = False, cls = (), **kwargs):
320
324
  """Material Design icon with optional size and fill"""
@@ -325,7 +329,7 @@ def Icon(icon: str, size: str = None, fill: bool = False, cls = (), **kwargs):
325
329
  cls_str = ' '.join(icon_cls) if icon_cls else None
326
330
  return I(icon, cls=cls_str, **kwargs) if cls_str else I(icon, **kwargs)
327
331
 
328
- # %% ../nbs/02_components.ipynb 32
332
+ # %% ../nbs/02_components.ipynb #f1e25f09
329
333
  #| code-fold: true
330
334
  def NavBar(*children, brand=None, sticky=False, cls='', size='small',
331
335
  hx_boost=True, hx_target='#main-content', **kwargs):
@@ -353,7 +357,7 @@ def NavBar(*children, brand=None, sticky=False, cls='', size='small',
353
357
  return Nav(brand, Div(cls='max'), *children, cls=f"row middle-align {padding_cls} {nav_cls}", **kwargs)
354
358
  return Nav(*children, cls=f"{padding_cls} {nav_cls}", **kwargs)
355
359
 
356
- # %% ../nbs/02_components.ipynb 35
360
+ # %% ../nbs/02_components.ipynb #6d92c66b
357
361
  def Modal(*c, id=None, footer=None, active=False, overlay='default', position=None, cls=(), **kwargs):
358
362
  """BeerCSS modal dialog with position and overlay options.
359
363
 
@@ -456,7 +460,7 @@ def ModalFooter(*c, cls=(), **kwargs):
456
460
  footer_cls.extend(['right-align', 'no-space'])
457
461
  return Nav(*c, cls=' '.join(footer_cls), **kwargs)
458
462
 
459
- # %% ../nbs/02_components.ipynb 38
463
+ # %% ../nbs/02_components.ipynb #3a16954d
460
464
  #| code-fold: true
461
465
  def Field(*c, label: bool = False, prefix: bool = False, suffix: bool = False, cls = '', **kwargs):
462
466
  """BeerCSS field wrapper for inputs with border/label/prefix/suffix styling."""
@@ -481,7 +485,7 @@ def LabelInput(label: str, id: str = None, placeholder: str = None, input_type:
481
485
  if suffix_icon: children.append(I(suffix_icon))
482
486
  return Field(*children, label=True, prefix=bool(prefix_icon), suffix=bool(suffix_icon), cls=cls)
483
487
 
484
- # %% ../nbs/02_components.ipynb 41
488
+ # %% ../nbs/02_components.ipynb #d6668f1b
485
489
  #| code-fold: true
486
490
  def LabelInput(label: str, id: str = None, placeholder: str = None, input_type: str = 'text',
487
491
  prefix_icon: str = None, suffix_icon: str = None, value: str = None, cls = '', **kwargs):
@@ -503,7 +507,7 @@ def FormLabel(*c, cls=(), **kwargs):
503
507
  if cls_str: return Label(*c, cls=cls_str, **kwargs)
504
508
  return Label(*c, **kwargs)
505
509
 
506
- # %% ../nbs/02_components.ipynb 44
510
+ # %% ../nbs/02_components.ipynb #b9cf5341
507
511
  #| code-fold: true
508
512
  def CheckboxX(*c, cls=(), **kwargs):
509
513
  """BeerCSS checkbox with label support."""
@@ -513,7 +517,7 @@ def CheckboxX(*c, cls=(), **kwargs):
513
517
  cls_str = stringify(checkbox_cls)
514
518
  return Label(Input(type='checkbox', **kwargs), Span(label_text) if label_text else Span(), cls=cls_str)
515
519
 
516
- # %% ../nbs/02_components.ipynb 47
520
+ # %% ../nbs/02_components.ipynb #784de047
517
521
  #| code-fold: true
518
522
  def Radio(*c, cls=(), **kwargs):
519
523
  """BeerCSS radio button with label."""
@@ -523,7 +527,7 @@ def Radio(*c, cls=(), **kwargs):
523
527
  cls_str = stringify(radio_cls)
524
528
  return Label(Input(type='radio', **kwargs), Span(label_text) if label_text else Span(), cls=cls_str)
525
529
 
526
- # %% ../nbs/02_components.ipynb 50
530
+ # %% ../nbs/02_components.ipynb #128c2b3c
527
531
  #| code-fold: true
528
532
  def Switch(*c, cls=(), **kwargs):
529
533
  """BeerCSS toggle switch for on/off states.
@@ -550,7 +554,7 @@ def Switch(*c, cls=(), **kwargs):
550
554
  cls='middle-align'
551
555
  )
552
556
 
553
- # %% ../nbs/02_components.ipynb 53
557
+ # %% ../nbs/02_components.ipynb #0c1c2718
554
558
  #| code-fold: true
555
559
  def TextArea(*c, cls=(), **kwargs):
556
560
  """BeerCSS textarea with field wrapper for consistent styling."""
@@ -558,7 +562,7 @@ def TextArea(*c, cls=(), **kwargs):
558
562
  textarea = Textarea(content, **kwargs) if content else Textarea(**kwargs)
559
563
  return Field(textarea, cls=cls)
560
564
 
561
- # %% ../nbs/02_components.ipynb 56
565
+ # %% ../nbs/02_components.ipynb #ddbbd284
562
566
  #| code-fold: true
563
567
  def Range(*c, min=None, max=None, step=None, cls=(), **kwargs):
564
568
  """BeerCSS range slider with two-tone fill effect."""
@@ -587,10 +591,22 @@ def Range(*c, min=None, max=None, step=None, cls=(), **kwargs):
587
591
  style = f"--_start: 0%; --_end: {percentage:.1f}%;"
588
592
  return Label(Input(**input_attrs), Span(), cls=cls_str, style=style)
589
593
 
590
- # %% ../nbs/02_components.ipynb 59
594
+ # %% ../nbs/02_components.ipynb #6262a7d8
591
595
  #| code-fold: true
592
- def Select(*items, value='', placeholder='Select...', prefix_icon=None, name='', cls=(), **kwargs):
593
- """BeerCSS menu-based select dropdown with rich styling."""
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).
598
+
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
609
+ """
594
610
  menu_items = []
595
611
  for item in items:
596
612
  if isinstance(item, str): menu_items.append(Li(item))
@@ -598,10 +614,11 @@ def Select(*items, value='', placeholder='Select...', prefix_icon=None, name='',
598
614
 
599
615
  children = []
600
616
  if prefix_icon: children.append(I(prefix_icon))
617
+
618
+ # Display input (readonly, shows selected value)
601
619
  input_attrs = {'value': value, 'readonly': True, 'placeholder': placeholder if placeholder else ' '}
602
- if name: input_attrs['name'] = name
603
- input_attrs.update(kwargs)
604
620
  children.append(Input(**input_attrs))
621
+
605
622
  children.append(I('arrow_drop_down'))
606
623
  children.append(Menu(*menu_items))
607
624
 
@@ -610,9 +627,77 @@ def Select(*items, value='', placeholder='Select...', prefix_icon=None, name='',
610
627
  field_cls.append('suffix')
611
628
  if cls: field_cls.extend(normalize_tokens(cls))
612
629
  cls_str = stringify(field_cls)
630
+
631
+ wrapper_attrs = {'cls': cls_str}
632
+ if id:
633
+ wrapper_attrs['id'] = id
634
+ wrapper_attrs.update(kwargs)
635
+
636
+ return Div(*children, **wrapper_attrs)
637
+
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
689
+ field_cls = ['field', 'border', 'round']
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
+
613
698
  return Div(*children, cls=cls_str)
614
699
 
615
- # %% ../nbs/02_components.ipynb 68
700
+ # %% ../nbs/02_components.ipynb #e6576c75
616
701
  #| code-fold: true
617
702
  def FormGrid(*c, cols: int = 1):
618
703
  """Responsive grid layout for form fields that stacks on mobile."""
@@ -621,7 +706,7 @@ def FormGrid(*c, cols: int = 1):
621
706
  wrapped = [Div(child, cls=col_cls) for child in c]
622
707
  return Div(*wrapped, cls="grid")
623
708
 
624
- # %% ../nbs/02_components.ipynb 71
709
+ # %% ../nbs/02_components.ipynb #5a159345
625
710
  #| code-fold: true
626
711
  def Progress(*c, value='', max='100', cls=(), **kwargs):
627
712
  """Linear progress bar with value/max support."""
@@ -633,7 +718,7 @@ def Progress(*c, value='', max='100', cls=(), **kwargs):
633
718
  if cls_str: return fc.Progress(*c, cls=cls_str, **progress_attrs)
634
719
  return fc.Progress(*c, **progress_attrs)
635
720
 
636
- # %% ../nbs/02_components.ipynb 74
721
+ # %% ../nbs/02_components.ipynb #11d87dd6
637
722
  #| code-fold: true
638
723
  def LoadingIndicator(size='medium', cls='', **kwargs):
639
724
  """BeerCSS circular spinner for async operations."""
@@ -641,7 +726,7 @@ def LoadingIndicator(size='medium', cls='', **kwargs):
641
726
  progress_cls = f"circle {size_cls} {cls}".strip()
642
727
  return fc.Progress(cls=progress_cls, **kwargs)
643
728
 
644
- # %% ../nbs/02_components.ipynb 77
729
+ # %% ../nbs/02_components.ipynb #396ebe54
645
730
  #| code-fold: true
646
731
  def Table(*c, cls = 'border', **kwargs):
647
732
  """BeerCSS table with optional border/stripes classes."""
@@ -709,14 +794,14 @@ def TableFromDicts(header_data, body_data, footer_data = None, header_cell_rende
709
794
  Tfoot(Tr(*[footer_cell_render(k, footer_data.get(k, '')) for k in header_data])) if footer_data else None,
710
795
  cls=cls, **kwargs)
711
796
 
712
- # %% ../nbs/02_components.ipynb 80
797
+ # %% ../nbs/02_components.ipynb #0cf9871f
713
798
  #| code-fold: true
714
799
  def TableControls(*controls, cls='', **kwargs):
715
800
  """Toolbar container for table filters, search, and actions."""
716
801
  controls_cls = f"padding middle-align space {cls}".strip()
717
802
  return Div(*controls, cls=controls_cls, **kwargs)
718
803
 
719
- # %% ../nbs/02_components.ipynb 83
804
+ # %% ../nbs/02_components.ipynb #93a6e771
720
805
  #| code-fold: true
721
806
  def Pagination(current_page: int, total_pages: int, hx_get: str, hx_target: str = '#table-container',
722
807
  show_first_last: bool = True, cls='', **kwargs):
@@ -751,7 +836,7 @@ def Pagination(current_page: int, total_pages: int, hx_get: str, hx_target: str
751
836
  nav_cls = f"center-align middle-align {cls}".strip()
752
837
  return Nav(*buttons, cls=nav_cls, **kwargs)
753
838
 
754
- # %% ../nbs/02_components.ipynb 86
839
+ # %% ../nbs/02_components.ipynb #c573ee87
755
840
  #| code-fold: true
756
841
  def Card(*c, header = None, footer = None, body_cls = 'padding', header_cls = (), footer_cls = (), cls = (), **kwargs):
757
842
  """BeerCSS card with optional header/footer sections."""
@@ -765,7 +850,7 @@ def Card(*c, header = None, footer = None, body_cls = 'padding', header_cls = ()
765
850
  if footer is not None: sections.append(Nav(footer, cls=footer_cls) if footer_cls else Nav(footer))
766
851
  return Article(*sections, cls=cls, **kwargs)
767
852
 
768
- # %% ../nbs/02_components.ipynb 89
853
+ # %% ../nbs/02_components.ipynb #f0c71209
769
854
  #| code-fold: true
770
855
  def Toolbar(*items, cls='', elevate='large', fill=True, **kwargs):
771
856
  """BeerCSS toolbar for action bars with elevation options."""
@@ -775,7 +860,7 @@ def Toolbar(*items, cls='', elevate='large', fill=True, **kwargs):
775
860
  if cls: classes.append(cls)
776
861
  return Nav(*items, cls=' '.join(classes), **kwargs)
777
862
 
778
- # %% ../nbs/02_components.ipynb 93
863
+ # %% ../nbs/02_components.ipynb #7019d70e
779
864
  #| code-fold: true
780
865
  def Toast(*c, cls='', position='top', variant='', action=None, active=False, dur=None, **kwargs):
781
866
  """BeerCSS snackbar/toast notification with position ('top' or 'bottom') and variant options."""
@@ -819,7 +904,7 @@ def Snackbar(*c, **kwargs):
819
904
  """Alias for Toast component."""
820
905
  return Toast(*c, **kwargs)
821
906
 
822
- # %% ../nbs/02_components.ipynb 96
907
+ # %% ../nbs/02_components.ipynb #fe1b0be7
823
908
  #| code-fold: true
824
909
  class ContainerT(VEnum):
825
910
  """Container size options (BeerCSS). Most alias to 'responsive'; use 'expand' for full-width."""
@@ -830,7 +915,7 @@ class ContainerT(VEnum):
830
915
  xl = 'responsive'
831
916
  expand = 'responsive max'
832
917
 
833
- # %% ../nbs/02_components.ipynb 98
918
+ # %% ../nbs/02_components.ipynb #f53893ec
834
919
  #| code-fold: true
835
920
  def _get_form_config(col: dict) -> dict:
836
921
  """Extract form config from column, with sensible defaults."""
@@ -906,7 +991,7 @@ def FormField(
906
991
  **attrs
907
992
  )
908
993
 
909
- # %% ../nbs/02_components.ipynb 100
994
+ # %% ../nbs/02_components.ipynb #aff2ee40
910
995
  #| code-fold: true
911
996
  from typing import Callable, Any
912
997
 
@@ -1025,7 +1110,7 @@ def FormModal(
1025
1110
  cls="large-width"
1026
1111
  )
1027
1112
 
1028
- # %% ../nbs/02_components.ipynb 102
1113
+ # %% ../nbs/02_components.ipynb #945684a7
1029
1114
  #| code-fold: true
1030
1115
  def NavContainer(*li, title=None, brand=None, position='left', close_button=True, cls='active', id=None, **kwargs):
1031
1116
  """Slide-out navigation drawer with header and close button."""
@@ -1082,7 +1167,7 @@ def BottomNav(*c, cls='bottom', size='s', **kwargs):
1082
1167
  final_cls = f"{cls} {size_cls}".strip()
1083
1168
  return Nav(*c, cls=final_cls, **kwargs)
1084
1169
 
1085
- # %% ../nbs/02_components.ipynb 105
1170
+ # %% ../nbs/02_components.ipynb #359f9555
1086
1171
  #| code-fold: true
1087
1172
  def NavSideBarHeader(*c, cls='', **kwargs):
1088
1173
  """Sidebar header section for menu buttons and branding."""
@@ -1126,7 +1211,7 @@ def NavSideBarContainer(*children, position='left', size='m', cls='', active=Fal
1126
1211
 
1127
1212
  return Nav(*children, cls=nav_cls, **kwargs)
1128
1213
 
1129
- # %% ../nbs/02_components.ipynb 107
1214
+ # %% ../nbs/02_components.ipynb #541b2d99
1130
1215
  #| code-fold: true
1131
1216
  def Page(*c, active=True, position=None, cls='', **kwargs):
1132
1217
  """BeerCSS animated page container.
@@ -1150,7 +1235,7 @@ def Page(*c, active=True, position=None, cls='', **kwargs):
1150
1235
 
1151
1236
  def Layout(*content, sidebar=None, sidebar_links=None, nav_bar=None, container_size=ContainerT.expand,
1152
1237
  main_bg='surface', sidebar_id='app-sidebar', main_id='main-content', cls='', **kwargs):
1153
- """App layout with HTMX SPA navigation.
1238
+ """App layout with HTMX SPA navigation and responsive mobile support.
1154
1239
 
1155
1240
  Args:
1156
1241
  main_id: ID for main content area (default 'main-content') - use as hx-target
@@ -1160,6 +1245,11 @@ def Layout(*content, sidebar=None, sidebar_links=None, nav_bar=None, container_s
1160
1245
  - hx-history-elt for back/forward button caching
1161
1246
  - Routes should check `req.headers` for HX-Request and return content only for HTMX requests
1162
1247
 
1248
+ Mobile Responsive:
1249
+ - Desktop (>992px): Left sidebar visible, bottom nav hidden
1250
+ - Mobile (≤992px): Bottom nav visible, sidebar hidden
1251
+ - Both navigations share the same sidebar_links for consistent behavior
1252
+
1163
1253
  Usage:
1164
1254
  @rt("/dashboard")
1165
1255
  def dashboard(req):
@@ -1193,9 +1283,24 @@ def Layout(*content, sidebar=None, sidebar_links=None, nav_bar=None, container_s
1193
1283
  if is_listy(sidebar): sidebar_children.extend(sidebar)
1194
1284
  else: sidebar_children.append(sidebar)
1195
1285
 
1196
- nav_rail = NavSideBarContainer(*sidebar_children, position='left', size='l', id=sidebar_id)
1286
+ nav_rail = NavSideBarContainer(*sidebar_children, position='left', size='l', id=sidebar_id, cls='desktop-nav')
1287
+
1288
+ # Mobile bottom navigation with same links
1289
+ bottom_nav_children = sidebar_links if sidebar_links else (list(sidebar) if is_listy(sidebar) else [sidebar] if sidebar else [])
1290
+ nav_bottom = BottomNav(*bottom_nav_children, cls='bottom mobile-nav',
1291
+ hx_boost='true', hx_target=f'#{main_id}', hx_push_url='true')
1292
+
1293
+ # Responsive CSS for desktop/mobile navigation switching
1294
+ # Only hide elements when needed - don't override Beer CSS flex layout on desktop
1295
+ responsive_nav_css = Style("""
1296
+ .mobile-nav { display: none !important; }
1297
+ @media (max-width: 992px) {
1298
+ .mobile-nav { display: flex !important; }
1299
+ .desktop-nav { display: none !important; }
1300
+ }
1301
+ """)
1197
1302
 
1198
- layout_children = []
1303
+ layout_children = [responsive_nav_css]
1199
1304
 
1200
1305
  if nav_bar:
1201
1306
  if hasattr(nav_bar, 'attrs') and 'cls' in nav_bar.attrs:
@@ -1203,9 +1308,10 @@ def Layout(*content, sidebar=None, sidebar_links=None, nav_bar=None, container_s
1203
1308
  layout_children.append(nav_bar)
1204
1309
 
1205
1310
  layout_children.append(nav_rail)
1311
+ layout_children.append(nav_bottom)
1206
1312
 
1207
1313
  if content_wrapper:
1208
- container_cls = stringify((container_size, 'surface-container', 'round', 'padding', 'bottom-margin'))
1314
+ container_cls = stringify((container_size, 'surface-container', 'round', 'padding', 'bottom-margin', 'horizontal-margin'))
1209
1315
  page_content = Page(content_wrapper, id=main_id)
1210
1316
  layout_children.append(Main(page_content, cls=container_cls))
1211
1317
 
@@ -1213,7 +1319,7 @@ def Layout(*content, sidebar=None, sidebar_links=None, nav_bar=None, container_s
1213
1319
  return Div(*layout_children, cls=final_cls, **kwargs)
1214
1320
 
1215
1321
 
1216
- # %% ../nbs/02_components.ipynb 113
1322
+ # %% ../nbs/02_components.ipynb #4329c780
1217
1323
  #| code-fold: true
1218
1324
  class TextT(VEnum):
1219
1325
  """Text styles using BeerCSS typography classes."""
@@ -1245,7 +1351,7 @@ class TextPresets(VEnum):
1245
1351
  primary_link = 'link primary-text'
1246
1352
  muted_link = 'link secondary-text'
1247
1353
 
1248
- # %% ../nbs/02_components.ipynb 114
1354
+ # %% ../nbs/02_components.ipynb #c62d0db5
1249
1355
  #| code-fold: true
1250
1356
  def CodeSpan(*c, cls=(), **kwargs):
1251
1357
  """Inline code snippet."""
@@ -1301,7 +1407,7 @@ def Sup(*c, cls=(), **kwargs):
1301
1407
  cls_str = stringify(cls) if cls else None
1302
1408
  return fc.Sup(*c, cls=cls_str, **kwargs) if cls_str else fc.Sup(*c, **kwargs)
1303
1409
 
1304
- # %% ../nbs/02_components.ipynb 116
1410
+ # %% ../nbs/02_components.ipynb #15d01ef3
1305
1411
  #| code-fold: true
1306
1412
  def FAQItem(question: str, answer: str, question_cls: str = '', answer_cls: str = ''):
1307
1413
  """Collapsible FAQ item using details/summary.
@@ -1319,7 +1425,7 @@ def FAQItem(question: str, answer: str, question_cls: str = '', answer_cls: str
1319
1425
  Summary(Article(Nav(Div(question, cls=f"max bold {question_cls}".strip()), I("expand_more")), cls="round surface-variant border no-elevate")),
1320
1426
  Article(P(answer, cls=f"secondary-text {answer_cls}".strip()), cls="round border padding"))
1321
1427
 
1322
- # %% ../nbs/02_components.ipynb 120
1428
+ # %% ../nbs/02_components.ipynb #287c86c3
1323
1429
  #| code-fold: true
1324
1430
  def CookiesBanner(message='We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.',
1325
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 2
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 5
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 7
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 10
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 11
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 15
220
+ # %% ../nbs/01_core.ipynb #4672930e
221
221
  #| code-fold: true
222
222
  #| code-fold: true
223
223
  class BeerCssChain:
@@ -2,10 +2,11 @@
2
2
 
3
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/05_datatable.ipynb.
4
4
 
5
- # %% auto 0
6
- __all__ = ['PAGE_SIZES', 'logger', 'table_state_from_request', 'DataTable', 'CrudContext', 'DataTableResource']
5
+ # %% auto #0
6
+ __all__ = ['PAGE_SIZES', 'TABLE_STYLES', 'TABLE_SPACES', 'TABLE_ALIGNS', 'logger', 'table_state_from_request', 'DataTable',
7
+ 'CrudContext', 'DataTableResource']
7
8
 
8
- # %% ../nbs/05_datatable.ipynb 2
9
+ # %% ../nbs/05_datatable.ipynb #d7f65962
9
10
  from fastcore.utils import *
10
11
  from fasthtml.common import *
11
12
  from fasthtml.jupyter import *
@@ -17,7 +18,7 @@ from .core import *
17
18
  from nbdev.showdoc import show_doc
18
19
  from .components import *
19
20
 
20
- # %% ../nbs/05_datatable.ipynb 7
21
+ # %% ../nbs/05_datatable.ipynb #3a9c1b0c
21
22
  #| code-fold: true
22
23
  #| code-fold: true
23
24
  from math import ceil
@@ -28,6 +29,11 @@ from dataclasses import asdict, is_dataclass
28
29
  # Default page size options
29
30
  PAGE_SIZES = [5, 10, 20, 50]
30
31
 
32
+ # BeerCSS table styles
33
+ TABLE_STYLES = ['border', 'stripes', 'min', 'fixed'] # Can combine: 'border stripes'
34
+ TABLE_SPACES = ['no-space', 'small-space', 'medium-space', 'large-space']
35
+ TABLE_ALIGNS = ['left-align', 'right-align', 'center-align']
36
+
31
37
 
32
38
  def _to_dict(obj: Any) -> dict:
33
39
  """
@@ -207,9 +213,20 @@ def DataTable(
207
213
  page_sizes: list = None,
208
214
  search_placeholder: str = 'Search...',
209
215
  create_label: str = 'New Record',
210
- empty_message: str = 'No records match the current filters.'
216
+ empty_message: str = 'No records match the current filters.',
217
+ # BeerCSS table styling options
218
+ table_style: str = 'border', # border, stripes, min, fixed (can combine: 'border stripes')
219
+ space: str = 'small-space', # no-space, small-space, medium-space, large-space
220
+ align: str = None # left-align, right-align, center-align (None = default left)
211
221
  ):
212
- "Generic data table with server-side pagination, search, and row actions."
222
+ """
223
+ Generic data table with server-side pagination, search, and row actions.
224
+
225
+ Table Styling (BeerCSS):
226
+ table_style: Table visual style - 'border', 'stripes', 'min', 'fixed' or combine like 'border stripes'
227
+ space: Row spacing - 'no-space', 'small-space', 'medium-space', 'large-space'
228
+ align: Text alignment - 'left-align', 'right-align', 'center-align' (None for default)
229
+ """
213
230
  # Defaults
214
231
  crud_ops = crud_ops or {"create": False, "update": False, "delete": False}
215
232
  crud_enabled = crud_enabled or {k: bool(v) for k, v in crud_ops.items() if k != 'custom_actions'}
@@ -313,6 +330,16 @@ def DataTable(
313
330
  cls="max"
314
331
  )
315
332
 
333
+ # Compose table classes from BeerCSS styling options
334
+ table_classes = []
335
+ if table_style:
336
+ table_classes.append(table_style)
337
+ if space:
338
+ table_classes.append(space)
339
+ if align:
340
+ table_classes.append(align)
341
+ table_cls = ' '.join(table_classes) if table_classes else None
342
+
316
343
  # Build table with proper Thead/Tbody structure
317
344
  data_table = Table(
318
345
  Thead(Tr(*[Th(label) for label in header_labels])),
@@ -320,7 +347,7 @@ def DataTable(
320
347
  Tr(*[Td(row_dict.get(key, '')) for key in header_keys])
321
348
  for row_dict in table_rows
322
349
  ]),
323
- cls="border"
350
+ cls=table_cls
324
351
  ) if data else Div(empty_message, cls="center-align padding")
325
352
 
326
353
  # Footer section with page size selector and pagination
@@ -341,7 +368,7 @@ def DataTable(
341
368
  # Single row when no pagination needed
342
369
  footer = page_info_row
343
370
 
344
- return Article(
371
+ return Div(
345
372
  Div(id=feedback_id), # Feedback container for modals/toasts
346
373
  Div(
347
374
  search_form,
@@ -357,7 +384,7 @@ def DataTable(
357
384
  hx_swap="outerHTML"
358
385
  )
359
386
 
360
- # %% ../nbs/05_datatable.ipynb 9
387
+ # %% ../nbs/05_datatable.ipynb #b84c744d
361
388
  import asyncio
362
389
  import logging
363
390
  from dataclasses import dataclass
@@ -453,7 +480,7 @@ class CrudContext:
453
480
  record_id: Optional[Any] = None # ID for update/delete (None for create)
454
481
  feedback_id: Optional[str] = None # Target div ID for HTMX swap (for override handlers)
455
482
 
456
- # %% ../nbs/05_datatable.ipynb 14
483
+ # %% ../nbs/05_datatable.ipynb #63468220
457
484
  from typing import Callable, Optional, Any, Union
458
485
  from dataclasses import asdict, is_dataclass
459
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 2
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 4
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 8
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 12
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 3
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 6
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 10
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 12
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 16
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 20
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 23
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 25
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 27
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 30
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 31
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.14
3
+ Version: 0.9.16
4
4
  Summary: material-ui for fasthtml
5
5
  Home-page: https://github.com/abhisheksreesaila/fh-matui
6
6
  Author: abhishek sreesaila
@@ -145,10 +145,8 @@ styling
145
145
 
146
146
  1. **`MatTheme.indigo.headers()`** - Loads BeerCSS with the indigo
147
147
  color scheme
148
- 2. **[`Card`](https://abhisheksreesaila.github.io/fh-matui/components.html#card)** -
149
- Creates a Material Design card component with elevation
150
- 3. **[`FormField`](https://abhisheksreesaila.github.io/fh-matui/components.html#formfield)** -
151
- Generates a styled input with floating label
148
+ 2. **`Card`** - Creates a Material Design card component with elevation
149
+ 3. **`FormField`** - Generates a styled input with floating label
152
150
  4. **`Button`** - Renders a Material Design button with ripple effects
153
151
 
154
152
  ## 📚 Module Reference
@@ -157,9 +155,9 @@ styling
157
155
  |----|----|----|
158
156
  | [Foundations](foundations.html) | Base utilities and helper functions | `BeerHeaders`, `display`, styling helpers |
159
157
  | [Core](core.html) | Theme system and styling | `MatTheme`, color presets, theme configuration |
160
- | [Components](components.html) | UI component library | `Button`, [`Card`](https://abhisheksreesaila.github.io/fh-matui/components.html#card), [`FormField`](https://abhisheksreesaila.github.io/fh-matui/components.html#formfield), [`FormModal`](https://abhisheksreesaila.github.io/fh-matui/components.html#formmodal), [`Grid`](https://abhisheksreesaila.github.io/fh-matui/components.html#grid) |
158
+ | [Components](components.html) | UI component library | `Button`, `Card`, `FormField`, `FormModal`, `Grid` |
161
159
  | [App Pages](app_pages.html) | Application layouts | Navigation, sidebars, full-page layouts |
162
- | [Data Tables](05_table.html) | Data management components | [`DataTable`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatable), [`DataTableResource`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatableresource), CRUD operations |
160
+ | [Data Tables](05_table.html) | Data management components | `DataTable`, `DataTableResource`, CRUD operations |
163
161
  | [Web Pages](web_pages.html) | Marketing/landing pages | Hero sections, feature grids, testimonials |
164
162
 
165
163
  ## 🛠️ Development
@@ -187,8 +185,8 @@ nbdev_prepare
187
185
  | **Component consistency** | Unified API across all components |
188
186
  | **Dark mode support** | Built-in with automatic system preference detection |
189
187
  | **Responsive design** | Mobile-first grid system and responsive utilities |
190
- | **Form handling** | [`FormField`](https://abhisheksreesaila.github.io/fh-matui/components.html#formfield), [`FormGrid`](https://abhisheksreesaila.github.io/fh-matui/components.html#formgrid), [`FormModal`](https://abhisheksreesaila.github.io/fh-matui/components.html#formmodal) for rapid form building |
191
- | **Data management** | [`DataTable`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatable) and [`DataTableResource`](https://abhisheksreesaila.github.io/fh-matui/datatable.html#datatableresource) for CRUD operations |
188
+ | **Form handling** | `FormField`, `FormGrid`, `FormModal` for rapid form building |
189
+ | **Data management** | `DataTable` and `DataTableResource` for CRUD operations |
192
190
 
193
191
  ## 🤖 For LLM Users
194
192
 
@@ -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" }]
@@ -5,7 +5,7 @@
5
5
  ### Python library ###
6
6
  repo = fh-matui
7
7
  lib_name = %(repo)s
8
- version = 0.9.14
8
+ version = 0.9.16
9
9
  min_python = 3.9
10
10
  license = apache2
11
11
  black_formatting = False
@@ -1 +0,0 @@
1
- __version__ = "0.9.13"
@@ -1,3 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools>=64.0"]
3
- build-backend = "setuptools.build_meta"
File without changes
File without changes
File without changes
File without changes