fh-matui 0.9.15__py3-none-any.whl → 0.9.17__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fh_matui/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.9.14"
1
+ __version__ = "0.9.16"
fh_matui/_modidx.py CHANGED
@@ -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'),
fh_matui/app_pages.py CHANGED
@@ -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",
fh_matui/components.py CHANGED
@@ -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'
@@ -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 16
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 17
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 19
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 22
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 24
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 26
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 29
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 32
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 35
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 38
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 41
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 44
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 47
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 50
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 53
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 56
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 59
594
+ # %% ../nbs/02_components.ipynb #6262a7d8
595
595
  #| code-fold: true
596
- # One-time script for Select component HTMX integration
597
- # Handles menu item clicks, syncs hidden input, dispatches 'itemselected' event
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
- Dispatches 'itemselected' custom event when a menu item is clicked,
654
- enabling HTMX patterns like hx-trigger="itemselected".
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 wrapper_id:
689
- wrapper_attrs['id'] = wrapper_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
- # %% ../nbs/02_components.ipynb 70
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 73
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 76
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 79
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 82
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 85
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 88
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 91
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 95
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 98
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 100
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 102
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 104
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 107
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 109
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 115
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 116
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 118
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 122
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',
fh_matui/core.py CHANGED
@@ -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:
fh_matui/datatable.py CHANGED
@@ -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 2
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 7
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 9
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 14
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
fh_matui/foundations.py CHANGED
@@ -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):
fh_matui/web_pages.py CHANGED
@@ -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.15
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 | Preview | Theme | Preview |
88
- |-----------------------|---------|-----------------------|---------|
89
- | `MatTheme.red` | 🔴 | `MatTheme.pink` | 🩷 |
90
- | `MatTheme.purple` | 🟣 | `MatTheme.deepPurple` | 💜 |
91
- | `MatTheme.indigo` | 🔵 | `MatTheme.blue` | 💙 |
92
- | `MatTheme.lightBlue` | 🩵 | `MatTheme.cyan` | 🌊 |
93
- | `MatTheme.teal` | 🩶 | `MatTheme.green` | 💚 |
94
- | `MatTheme.lightGreen` | 🍀 | `MatTheme.lime` | 💛 |
95
- | `MatTheme.yellow` | 🌟 | `MatTheme.amber` | 🧡 |
96
- | `MatTheme.orange` | 🟠 | `MatTheme.deepOrange` | 🔶 |
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.deepPurple.headers()])
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,14 @@
1
+ fh_matui/__init__.py,sha256=1Trt9wiX-dulRPRdt82fqX0JH-xfZMdFAJe6dA4gaQM,24
2
+ fh_matui/_modidx.py,sha256=x9AV-LcrfUpON34QN4WnbO-E_GKQ2PhdlY5Zh0NEAT0,24162
3
+ fh_matui/app_pages.py,sha256=DtaStRuYJ_oiEB2nBzMApCv0NfBkE2wUZMUfKBMhMFs,10378
4
+ fh_matui/components.py,sha256=y00glXXR5dB4zHwCDnqXCeGiYKMETK1WO6l7Ee2Bdls,59103
5
+ fh_matui/core.py,sha256=3j-4ot0MgmyBpsIg7qRh3-KTlRYmgVeetQhQNQPQ19E,11108
6
+ fh_matui/datatable.py,sha256=DRgA6nmm4h_QCMmd6CJK8QluRJ0S7c8j9H-yHjpUjBo,42590
7
+ fh_matui/foundations.py,sha256=jSJuELof3B-YeZ4jlsIV-ABpOn9Hv2Qd67o0LuBTqVY,1912
8
+ fh_matui/web_pages.py,sha256=6l0axbrCYmqSmSGrq0qt3nPUNblArCCAy_LlN-iCsNc,35183
9
+ fh_matui-0.9.17.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
10
+ fh_matui-0.9.17.dist-info/METADATA,sha256=6ngbutacYFlJoMqDm6IKv_uWOa_ARVdl_2nryVQ4E1Q,10601
11
+ fh_matui-0.9.17.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
+ fh_matui-0.9.17.dist-info/entry_points.txt,sha256=zn4CR4gNTiAAxbFsCxHAf2tQhtW29_YOffjbUTgeoWI,38
13
+ fh_matui-0.9.17.dist-info/top_level.txt,sha256=l80d5eoA2ZjqtPYwAorLMS5PiHxUxz3zKzxMJ41Xoso,9
14
+ fh_matui-0.9.17.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- fh_matui/__init__.py,sha256=AXW1LY5wrgbjQVfPOZJRcsfowYQAXor5ryaBdvw-SJo,24
2
- fh_matui/_modidx.py,sha256=r9X_k4ptjzUPy6u6AS6411Qn0Fq0NuxMyV5PrpMH2oA,24032
3
- fh_matui/app_pages.py,sha256=Sn9-tgBpaPNbR-0nZtPLoSCmAWLOGB4UQ88IkFvzBRY,10361
4
- fh_matui/components.py,sha256=_rndyhubrazX6xTr2EnYGQPkGbvCy6VGUFLUfV_1lKg,58416
5
- fh_matui/core.py,sha256=fMSw0fHvUTYt1Qd1DzaWDS4xWIDbkyUCcr-LIz2IJ5k,11062
6
- fh_matui/datatable.py,sha256=9jiVu5YU2tc3-4PKhHxIoVEjwwikVCyJhPGPvWIVgzQ,42558
7
- fh_matui/foundations.py,sha256=xiQOeyV4FhNzPpw6mbtIrPWMyzlMi3BENo41IlGnAz8,1880
8
- fh_matui/web_pages.py,sha256=at_M34Vxc1i9O0ukS41PaRJntkXXDzMqyzlcrgESUcw,35103
9
- fh_matui-0.9.15.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
10
- fh_matui-0.9.15.dist-info/METADATA,sha256=E71WzNH6S2mhg8LnE4b9tTbrI7D5zsMtF6hTZxljFQM,10491
11
- fh_matui-0.9.15.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
- fh_matui-0.9.15.dist-info/entry_points.txt,sha256=zn4CR4gNTiAAxbFsCxHAf2tQhtW29_YOffjbUTgeoWI,38
13
- fh_matui-0.9.15.dist-info/top_level.txt,sha256=l80d5eoA2ZjqtPYwAorLMS5PiHxUxz3zKzxMJ41Xoso,9
14
- fh_matui-0.9.15.dist-info/RECORD,,