fh-matui 0.9.4__py3-none-any.whl → 0.9.5__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/_modidx.py CHANGED
@@ -128,10 +128,15 @@ d = { 'settings': { 'branch': 'master',
128
128
  'fh_matui.core._ThemeNamespace.violet': ('core.html#_themenamespace.violet', 'fh_matui/core.py'),
129
129
  'fh_matui.core._ThemeNamespace.yellow': ('core.html#_themenamespace.yellow', 'fh_matui/core.py'),
130
130
  'fh_matui.core._ThemeNamespace.zinc': ('core.html#_themenamespace.zinc', 'fh_matui/core.py')},
131
- 'fh_matui.datatable': { 'fh_matui.datatable.DataTable': ('datatable.html#datatable', 'fh_matui/datatable.py'),
131
+ 'fh_matui.datatable': { 'fh_matui.datatable.CrudContext': ('datatable.html#crudcontext', 'fh_matui/datatable.py'),
132
+ 'fh_matui.datatable.DataTable': ('datatable.html#datatable', 'fh_matui/datatable.py'),
132
133
  'fh_matui.datatable.DataTableResource': ('datatable.html#datatableresource', 'fh_matui/datatable.py'),
133
134
  'fh_matui.datatable.DataTableResource.__init__': ( 'datatable.html#datatableresource.__init__',
134
135
  'fh_matui/datatable.py'),
136
+ 'fh_matui.datatable.DataTableResource._build_context': ( 'datatable.html#datatableresource._build_context',
137
+ 'fh_matui/datatable.py'),
138
+ 'fh_matui.datatable.DataTableResource._call_hook': ( 'datatable.html#datatableresource._call_hook',
139
+ 'fh_matui/datatable.py'),
135
140
  'fh_matui.datatable.DataTableResource._error_toast': ( 'datatable.html#datatableresource._error_toast',
136
141
  'fh_matui/datatable.py'),
137
142
  'fh_matui.datatable.DataTableResource._filter_by_search': ( 'datatable.html#datatableresource._filter_by_search',
fh_matui/datatable.py CHANGED
@@ -3,7 +3,7 @@
3
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/05_datatable.ipynb.
4
4
 
5
5
  # %% auto 0
6
- __all__ = ['PAGE_SIZES', 'table_state_from_request', 'DataTable', 'DataTableResource']
6
+ __all__ = ['PAGE_SIZES', 'logger', 'table_state_from_request', 'DataTable', 'CrudContext', 'DataTableResource']
7
7
 
8
8
  # %% ../nbs/05_datatable.ipynb 2
9
9
  from fastcore.utils import *
@@ -17,7 +17,7 @@ from .core import *
17
17
  from nbdev.showdoc import show_doc
18
18
  from .components import *
19
19
 
20
- # %% ../nbs/05_datatable.ipynb 6
20
+ # %% ../nbs/05_datatable.ipynb 7
21
21
  #| code-fold: true
22
22
  from math import ceil
23
23
  from urllib.parse import urlencode
@@ -288,7 +288,102 @@ def DataTable(
288
288
 
289
289
  return Div(card, Div(id=feedback_id), id=container_id)
290
290
 
291
- # %% ../nbs/05_datatable.ipynb 8
291
+ # %% ../nbs/05_datatable.ipynb 9
292
+ import asyncio
293
+ import logging
294
+ from dataclasses import dataclass
295
+ from typing import Callable, Any, Optional
296
+ from starlette.responses import HTMLResponse
297
+
298
+ logger = logging.getLogger(__name__)
299
+
300
+ @dataclass
301
+ class CrudContext:
302
+ """
303
+ 🎯 Context object passed to enhanced CRUD operation hooks.
304
+
305
+ Provides rich access to request state, user info, database, and record data.
306
+ Perfect for implementing complex business logic and external API integration.
307
+
308
+ ## 📦 Fields
309
+
310
+ - `request`: Full Starlette request object (headers, query params, session, state)
311
+ - `user`: Current user dict from `request.state.user` (if available)
312
+ - `db`: Database instance from `request.state.tenant_db` (if available)
313
+ - `tbl`: Table instance from `request.state.tables[table_name]` (if available)
314
+ - `record`: Form data dict with field values
315
+ - `record_id`: Record ID for update/delete operations (None for create)
316
+
317
+ ## 💡 Usage in Hooks
318
+
319
+ ### Example: Create with External API
320
+ ```python
321
+ async def quiltt_create_connection(ctx: CrudContext) -> dict:
322
+ # Access user info
323
+ user_id = ctx.user['user_id']
324
+
325
+ # Call external API
326
+ api = QuilttClient()
327
+ response = await api.create_connection(
328
+ institution=ctx.record['institution_name'],
329
+ user_id=user_id
330
+ )
331
+
332
+ # Enrich record with API response
333
+ ctx.record['connection_id'] = response['id']
334
+ ctx.record['account_id'] = response['account_id']
335
+ ctx.record['status'] = 'pending'
336
+
337
+ return ctx.record # DataTableResource will insert this
338
+
339
+ DataTableResource(
340
+ ...,
341
+ on_create=quiltt_create_connection # 🆕 Enhanced hook
342
+ )
343
+ ```
344
+
345
+ ### Example: Soft Delete
346
+ ```python
347
+ def soft_delete_budget(ctx: CrudContext) -> None:
348
+ # Access table directly
349
+ ctx.tbl.update({
350
+ 'id': ctx.record_id,
351
+ 'is_deleted': True,
352
+ 'deleted_at': datetime.now().isoformat(),
353
+ 'deleted_by': ctx.user['user_id']
354
+ })
355
+ # No return needed for delete hooks
356
+
357
+ DataTableResource(
358
+ ...,
359
+ on_delete=soft_delete_budget, # 🆕 Custom delete logic
360
+ get_table=lambda req: req.state.tables['budgets']
361
+ )
362
+ ```
363
+
364
+ ### Example: Update with Sync
365
+ ```python
366
+ async def sync_transaction_update(ctx: CrudContext) -> dict:
367
+ # Update external API first
368
+ api = TransactionAPI()
369
+ await api.update_transaction(
370
+ transaction_id=ctx.record_id,
371
+ data=ctx.record
372
+ )
373
+
374
+ # Add sync timestamp
375
+ ctx.record['last_synced'] = datetime.now().isoformat()
376
+ return ctx.record
377
+ ```
378
+ """
379
+ request: Any # Full Starlette request
380
+ user: Optional[dict] = None # request.state.user (if available)
381
+ db: Optional[Any] = None # request.state.tenant_db (if available)
382
+ tbl: Optional[Any] = None # request.state.tables[table_name] (if available)
383
+ record: dict = None # Form data dict
384
+ record_id: Optional[Any] = None # ID for update/delete (None for create)
385
+
386
+ # %% ../nbs/05_datatable.ipynb 12
292
387
  from typing import Callable, Optional, Any, Union
293
388
  from dataclasses import asdict, is_dataclass
294
389
  from datetime import datetime
@@ -309,7 +404,20 @@ def _to_dict(obj: Any) -> dict:
309
404
 
310
405
 
311
406
  class DataTableResource:
312
- "High-level resource that auto-registers all routes for a data table."
407
+ """
408
+ 🔧 High-level resource that auto-registers all routes for a data table.
409
+
410
+ **Features:**
411
+ - Custom CRUD hooks with `CrudContext` for rich business logic
412
+ - Async/sync hook support for external API integration
413
+ - Auto-refresh table via HX-Trigger after mutations
414
+ - Request state accessors for multi-tenant apps
415
+
416
+ **Auto-registers 3 routes:**
417
+ - `GET {base_route}` → DataTable list view
418
+ - `GET {base_route}/action` → FormModal for create/edit/view/delete
419
+ - `POST {base_route}/save` → Save handler with hooks
420
+ """
313
421
 
314
422
  def __init__(
315
423
  self,
@@ -328,15 +436,16 @@ class DataTableResource:
328
436
  search_placeholder: str = "Search...",
329
437
  create_label: str = "New Record",
330
438
  empty_message: str = "No records found.",
331
- # Lifecycle hooks
332
- on_before_create: Callable[[dict], dict] = None,
333
- on_after_create: Callable[[Any], None] = None,
334
- on_before_update: Callable[[Any, dict], dict] = None,
335
- on_after_update: Callable[[Any], None] = None,
336
- on_before_delete: Callable[[Any], bool] = None,
337
- on_after_delete: Callable[[Any], None] = None,
439
+ # CRUD Hooks (with CrudContext)
440
+ on_create: Callable[[CrudContext], dict] = None,
441
+ on_update: Callable[[CrudContext], dict] = None,
442
+ on_delete: Callable[[CrudContext], None] = None,
338
443
  # Multi-tenant
339
444
  user_filter: Callable = None,
445
+ # Request state accessors (for CrudContext)
446
+ get_user: Callable = None,
447
+ get_db: Callable = None,
448
+ get_table: Callable = None,
340
449
  # Custom generators
341
450
  id_generator: Callable[[], Any] = None,
342
451
  timestamp_fields: dict = None
@@ -359,24 +468,26 @@ class DataTableResource:
359
468
  # Determine CRUD ops from provided functions
360
469
  if crud_ops is None:
361
470
  self.crud_ops = {
362
- "create": create is not None,
363
- "update": update is not None,
364
- "delete": delete is not None
471
+ "create": create is not None or on_create is not None,
472
+ "update": update is not None or on_update is not None,
473
+ "delete": delete is not None or on_delete is not None
365
474
  }
366
475
  else:
367
476
  self.crud_ops = crud_ops
368
477
 
369
- # Lifecycle hooks
370
- self.on_before_create = on_before_create
371
- self.on_after_create = on_after_create
372
- self.on_before_update = on_before_update
373
- self.on_after_update = on_after_update
374
- self.on_before_delete = on_before_delete
375
- self.on_after_delete = on_after_delete
478
+ # CRUD hooks
479
+ self.on_create_hook = on_create
480
+ self.on_update_hook = on_update
481
+ self.on_delete_hook = on_delete
376
482
 
377
483
  # Multi-tenant
378
484
  self.user_filter = user_filter
379
485
 
486
+ # Request state accessors
487
+ self.get_user = get_user
488
+ self.get_db = get_db
489
+ self.get_table = get_table
490
+
380
491
  # Generators
381
492
  self.id_generator = id_generator
382
493
  self.timestamp_fields = timestamp_fields or {}
@@ -385,25 +496,68 @@ class DataTableResource:
385
496
  self.container_id = f"crud-table-{base_route.replace('/', '-').strip('-')}"
386
497
  self.feedback_id = f"{self.container_id}-feedback"
387
498
  self.modal_id = f"{self.container_id}-modal"
499
+ self.refresh_trigger = f"{self.container_id}-refresh"
388
500
 
389
501
  # Register routes
390
502
  self._register_routes()
391
503
 
504
+ async def _call_hook(self, hook, ctx: CrudContext):
505
+ """🔄 Call hook function, handling both sync and async."""
506
+ if hook is None:
507
+ return None
508
+ if asyncio.iscoroutinefunction(hook):
509
+ return await hook(ctx)
510
+ return hook(ctx)
511
+
512
+ def _build_context(self, req, record: dict = None, record_id: Any = None) -> CrudContext:
513
+ """🏗️ Build CrudContext from request state."""
514
+ user = None
515
+ db = None
516
+ tbl = None
517
+
518
+ try:
519
+ if self.get_user and callable(self.get_user):
520
+ user = self.get_user(req)
521
+ elif hasattr(req, 'state') and hasattr(req.state, 'user'):
522
+ user = req.state.user
523
+ except AttributeError:
524
+ pass
525
+
526
+ try:
527
+ if self.get_db and callable(self.get_db):
528
+ db = self.get_db(req)
529
+ elif hasattr(req, 'state') and hasattr(req.state, 'tenant_db'):
530
+ db = req.state.tenant_db
531
+ except AttributeError:
532
+ pass
533
+
534
+ try:
535
+ if self.get_table and callable(self.get_table):
536
+ tbl = self.get_table(req)
537
+ except AttributeError:
538
+ pass
539
+
540
+ return CrudContext(
541
+ request=req,
542
+ user=user,
543
+ db=db,
544
+ tbl=tbl,
545
+ record=record or {},
546
+ record_id=record_id
547
+ )
548
+
392
549
  def _register_routes(self):
393
550
  """Register all data table routes with the app."""
394
551
  rt = self.app.route
395
552
 
396
- # Main table route
397
553
  @rt(self.base_route)
398
554
  def _table_handler(req):
399
555
  return self._handle_table(req)
400
556
 
401
- # Action route (view/edit/create/delete)
402
557
  @rt(f"{self.base_route}/action")
403
558
  def _action_handler(req):
404
559
  return self._handle_action(req)
405
560
 
406
- # Save route (form submission)
407
561
  @rt(f"{self.base_route}/save")
408
562
  async def _save_handler(req):
409
563
  return await self._handle_save(req)
@@ -411,11 +565,8 @@ class DataTableResource:
411
565
  def _get_filtered_data(self, req) -> list:
412
566
  """Get all data, optionally filtered by user_filter."""
413
567
  all_data = self.get_all()
414
-
415
- # Convert to list of dicts
416
568
  data = [_to_dict(item) for item in all_data]
417
569
 
418
- # Apply user filter if provided
419
570
  if self.user_filter and callable(self.user_filter):
420
571
  filter_criteria = self.user_filter(req)
421
572
  if filter_criteria:
@@ -423,7 +574,6 @@ class DataTableResource:
423
574
  row for row in data
424
575
  if all(row.get(k) == v for k, v in filter_criteria.items())
425
576
  ]
426
-
427
577
  return data
428
578
 
429
579
  def _filter_by_search(self, data: list, search: str) -> list:
@@ -437,7 +587,6 @@ class DataTableResource:
437
587
  if col.get("searchable", False)
438
588
  ]
439
589
 
440
- # If no columns marked searchable, search all
441
590
  if not searchable_keys:
442
591
  searchable_keys = [col["key"] for col in self.columns]
443
592
 
@@ -451,26 +600,19 @@ class DataTableResource:
451
600
  total = len(data)
452
601
  total_pages = max(1, ceil(total / page_size))
453
602
  page = min(max(1, page), total_pages)
454
-
455
603
  start = (page - 1) * page_size
456
604
  end = start + page_size
457
-
458
605
  return data[start:end], total, page
459
606
 
460
607
  def _handle_table(self, req):
461
608
  """Handle main table route."""
462
- # Extract pagination state
463
609
  state = table_state_from_request(req, page_sizes=self.page_sizes)
464
610
  search, page, page_size = state["search"], state["page"], state["page_size"]
465
611
 
466
- # Get and filter data
467
612
  data = self._get_filtered_data(req)
468
613
  filtered = self._filter_by_search(data, search)
469
-
470
- # Paginate
471
614
  page_data, total, page = self._paginate(filtered, page, page_size)
472
615
 
473
- # Build table
474
616
  table = DataTable(
475
617
  data=page_data,
476
618
  total=total,
@@ -489,31 +631,40 @@ class DataTableResource:
489
631
  empty_message=self.empty_message
490
632
  )
491
633
 
492
- return table
634
+ # Wrap table in auto-refresh container
635
+ params = urlencode({"search": search, "page": page, "page_size": page_size})
636
+ table_container = Div(
637
+ table,
638
+ id=self.container_id,
639
+ hx_trigger=f"{self.refresh_trigger} from:body",
640
+ hx_get=f"{self.base_route}?{params}",
641
+ hx_target=f"#{self.container_id}",
642
+ hx_swap="outerHTML"
643
+ )
644
+
645
+ feedback = Div(id=self.feedback_id)
646
+ return Div(feedback, table_container)
493
647
 
494
648
  def _handle_action(self, req):
495
649
  """Handle action route (view/edit/create/delete)."""
496
650
  params = getattr(req, "query_params", {})
497
651
  getter = params.get if hasattr(params, "get") else (lambda k, d=None: params[k] if k in params else d)
498
652
 
499
- # Handle dismiss
500
653
  if getter("dismiss") is not None:
501
654
  return Div(id=self.feedback_id)
502
655
 
503
656
  record_id = getter("id")
504
- # Try to convert to int if numeric
505
657
  if record_id:
506
658
  try:
507
659
  record_id = int(record_id)
508
660
  except (TypeError, ValueError):
509
- pass # Keep as string (e.g., UUID)
661
+ pass
510
662
 
511
663
  action = (getter("action", "view") or "view").lower()
512
664
  search = getter("search", "") or ""
513
665
  page = _safe_int(getter("page", 1), 1)
514
666
  page_size = _safe_int(getter("page_size", 10), 10)
515
667
 
516
- # URLs
517
668
  return_params = urlencode({"search": search, "page": page, "page_size": page_size})
518
669
  cancel_url = f"{self.base_route}/action?dismiss=1"
519
670
  save_url = f"{self.base_route}/save?{return_params}"
@@ -581,21 +732,23 @@ class DataTableResource:
581
732
  if not self.crud_ops.get("delete"):
582
733
  return self._error_toast("Delete operation not enabled.")
583
734
 
584
- # Check before_delete hook
585
- if self.on_before_delete:
586
- if not self.on_before_delete(record_id):
587
- return self._error_toast("Delete cancelled by validation.")
588
-
589
- # Perform delete
590
735
  try:
591
- self.delete_fn(record_id)
736
+ ctx = self._build_context(req, record=record, record_id=record_id)
592
737
 
593
- # After delete hook
594
- if self.on_after_delete:
595
- self.on_after_delete(record_id)
738
+ # Use on_delete hook if provided
739
+ if self.on_delete_hook:
740
+ if asyncio.iscoroutinefunction(self.on_delete_hook):
741
+ loop = asyncio.get_event_loop()
742
+ loop.run_until_complete(self.on_delete_hook(ctx))
743
+ else:
744
+ self.on_delete_hook(ctx)
745
+ else:
746
+ # Default: use delete_fn
747
+ self.delete_fn(record_id)
596
748
 
597
- return self._success_toast(f"Record deleted successfully.")
749
+ return self._success_toast("Record deleted successfully.")
598
750
  except Exception as e:
751
+ logger.error(f"Delete failed: {e}", exc_info=True)
599
752
  return self._error_toast(f"Delete failed: {str(e)}")
600
753
 
601
754
  return Div(id=self.feedback_id)
@@ -606,14 +759,13 @@ class DataTableResource:
606
759
  form_data = await req.form()
607
760
  record_id = form_data.get(self.row_id_field)
608
761
 
609
- # Try to convert ID
610
762
  if record_id:
611
763
  try:
612
764
  record_id = int(record_id)
613
765
  except (TypeError, ValueError):
614
766
  pass
615
767
 
616
- # Build data dict from form, excluding the ID field
768
+ # Build data dict from form
617
769
  data = {}
618
770
  for col in self.columns:
619
771
  key = col["key"]
@@ -626,7 +778,6 @@ class DataTableResource:
626
778
 
627
779
  value = form_data.get(key)
628
780
 
629
- # Type conversion based on form config
630
781
  field_type = form_cfg.get("type", "text")
631
782
  if field_type == "number" and value:
632
783
  try:
@@ -648,33 +799,36 @@ class DataTableResource:
648
799
 
649
800
  # CREATE or UPDATE
650
801
  if record_id:
651
- # UPDATE
652
- if self.on_before_update:
653
- data = self.on_before_update(record_id, data)
654
-
655
- result = self.update_fn(record_id, data)
802
+ # ===== UPDATE =====
803
+ ctx = self._build_context(req, record=data, record_id=record_id)
656
804
 
657
- if self.on_after_update:
658
- self.on_after_update(result)
805
+ if self.on_update_hook:
806
+ data = await self._call_hook(self.on_update_hook, ctx)
807
+ if data is not None:
808
+ self.update_fn(record_id, data)
809
+ else:
810
+ self.update_fn(record_id, data)
659
811
 
660
812
  return self._success_toast("Record updated successfully.")
661
813
  else:
662
- # CREATE
663
- if self.on_before_create:
664
- data = self.on_before_create(data)
665
-
814
+ # ===== CREATE =====
666
815
  # Generate ID if needed
667
- if self.id_generator:
816
+ if self.id_generator and self.row_id_field not in data:
668
817
  data[self.row_id_field] = self.id_generator()
669
818
 
670
- result = self.create_fn(data)
819
+ ctx = self._build_context(req, record=data, record_id=None)
671
820
 
672
- if self.on_after_create:
673
- self.on_after_create(result)
821
+ if self.on_create_hook:
822
+ data = await self._call_hook(self.on_create_hook, ctx)
823
+ if data is not None:
824
+ self.create_fn(data)
825
+ else:
826
+ self.create_fn(data)
674
827
 
675
828
  return self._success_toast("Record created successfully.")
676
829
 
677
830
  except Exception as e:
831
+ logger.error(f"Save failed: {e}", exc_info=True)
678
832
  return self._error_toast(f"Save failed: {str(e)}")
679
833
 
680
834
  def _wrap_modal(self, modal):
@@ -684,7 +838,7 @@ class DataTableResource:
684
838
  return Div(modal, id=self.feedback_id)
685
839
 
686
840
  def _success_toast(self, message: str):
687
- """Return a success toast in the feedback div."""
841
+ """Return a success toast with auto-refresh trigger."""
688
842
  toast = Toast(
689
843
  message,
690
844
  variant="success",
@@ -698,10 +852,16 @@ class DataTableResource:
698
852
  ),
699
853
  active=True
700
854
  )
701
- return Div(toast, id=self.feedback_id)
855
+
856
+ # Return as HTMLResponse with HX-Trigger for auto-refresh
857
+ from fasthtml.common import to_xml
858
+ html_content = to_xml(Div(toast, id=self.feedback_id))
859
+ response = HTMLResponse(content=html_content)
860
+ response.headers['HX-Trigger'] = self.refresh_trigger
861
+ return response
702
862
 
703
863
  def _error_toast(self, message: str):
704
- """Return an error toast in the feedback div."""
864
+ """Return an error toast (no auto-refresh on errors)."""
705
865
  toast = Toast(
706
866
  message,
707
867
  variant="error",
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: fh-matui
3
+ Version: 0.9.5
4
+ Summary: material-ui for fasthtml
5
+ Home-page: https://github.com/abhisheksreesaila/fh-matui
6
+ Author: abhishek sreesaila
7
+ Author-email: abhishek.sreesaila@gmail.com
8
+ License: Apache Software License 2.0
9
+ Keywords: nbdev jupyter notebook python
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Natural Language :: English
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: License :: OSI Approved :: Apache Software License
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: python-fasthtml
22
+ Requires-Dist: fastcore
23
+ Requires-Dist: markdown
24
+ Provides-Extra: dev
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
30
+ Dynamic: home-page
31
+ Dynamic: keywords
32
+ Dynamic: license
33
+ Dynamic: license-file
34
+ Dynamic: provides-extra
35
+ Dynamic: requires-dist
36
+ Dynamic: requires-python
37
+ Dynamic: summary
38
+
39
+ # fh-matui
40
+
41
+
42
+ <!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->
43
+
44
+ ## What is fh-matui?
45
+
46
+ **fh-matui** is a Python library that brings Google’s Material Design to
47
+ [FastHTML](https://fastht.ml/) applications. It provides a comprehensive
48
+ set of pre-built UI components that integrate seamlessly with FastHTML’s
49
+ hypermedia-driven architecture.
50
+
51
+ Built on top of [BeerCSS](https://www.beercss.com/) (a lightweight
52
+ Material Design 3 CSS framework), fh-matui enables you to create modern,
53
+ responsive web interfaces entirely in Python — no JavaScript required.
54
+
55
+ ## ✨ Key Features
56
+
57
+ | Feature | Description |
58
+ |----|----|
59
+ | 🎨 **Material Design 3** | Modern, beautiful components following Google’s latest design language |
60
+ | ⚡ **Zero JavaScript** | Build interactive UIs entirely in Python with FastHTML |
61
+ | 📱 **Responsive** | Mobile-first design with automatic breakpoint handling |
62
+ | 🌙 **Dark Mode** | Built-in light/dark theme support with 20+ color schemes |
63
+ | 🧩 **Composable** | Chainable styling APIs inspired by MonsterUI |
64
+ | 📊 **Data Tables** | Full-featured tables with pagination, search, sorting, and CRUD |
65
+ | 🔧 **nbdev-powered** | Literate programming with documentation built from notebooks |
66
+
67
+ ## 🏗️ Architecture
68
+
69
+ ┌─────────────────────────────────────────────────────────────┐
70
+ │ fh-matui │
71
+ ├─────────────────────────────────────────────────────────────┤
72
+ │ Foundations │ Core styling utilities, helpers, enums │
73
+ │ Core │ Theme system, MatTheme color presets │
74
+ │ Components │ Buttons, Cards, Modals, Forms, Tables │
75
+ │ App Pages │ Full-page layouts, navigation patterns │
76
+ │ Data Tables │ DataTable, DataTableResource for CRUD │
77
+ │ Web Pages │ Landing pages, marketing components │
78
+ ├─────────────────────────────────────────────────────────────┤
79
+ │ BeerCSS │ Material Design 3 CSS framework │
80
+ │ FastHTML │ Python hypermedia web framework │
81
+ └─────────────────────────────────────────────────────────────┘
82
+
83
+ ## 🎨 Available Themes
84
+
85
+ fh-matui includes 15+ pre-configured Material Design 3 color themes:
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` | 🔶 |
97
+
98
+ **Usage:**
99
+
100
+ ``` python
101
+ # Choose your theme
102
+ app, rt = fast_app(hdrs=[MatTheme.deepPurple.headers()])
103
+ ```
104
+
105
+ ## 🚀 Quick Start
106
+
107
+ Here’s a minimal example to get you started:
108
+
109
+ ``` python
110
+ from fasthtml.common import *
111
+ from fh_matui.core import MatTheme
112
+ from fh_matui.components import Button, Card, FormField
113
+
114
+ # Create a themed FastHTML app
115
+ app, rt = fast_app(hdrs=[MatTheme.indigo.headers()])
116
+
117
+ @rt('/')
118
+ def home():
119
+ return Div(
120
+ Card(
121
+ H3("Welcome to fh-matui!"),
122
+ P("Build beautiful Material Design apps with Python."),
123
+ FormField("email", label="Email", type="email"),
124
+ Button("Get Started", cls="primary"),
125
+ ),
126
+ cls="padding"
127
+ )
128
+
129
+ serve()
130
+ ```
131
+
132
+ ## 📦 Installation
133
+
134
+ ``` bash
135
+ pip install fh-matui
136
+ ```
137
+
138
+ ### Dependencies
139
+
140
+ fh-matui automatically includes: - **python-fasthtml** - The core
141
+ FastHTML framework - **BeerCSS** - Loaded via CDN for Material Design 3
142
+ styling
143
+
144
+ ### What This Code Does
145
+
146
+ 1. **`MatTheme.indigo.headers()`** - Loads BeerCSS with the indigo
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
152
+ 4. **`Button`** - Renders a Material Design button with ripple effects
153
+
154
+ ## 📚 Module Reference
155
+
156
+ | Module | Description | Key Components |
157
+ |----|----|----|
158
+ | [Foundations](foundations.html) | Base utilities and helper functions | `BeerHeaders`, `display`, styling helpers |
159
+ | [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) |
161
+ | [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 |
163
+ | [Web Pages](web_pages.html) | Marketing/landing pages | Hero sections, feature grids, testimonials |
164
+
165
+ ## 🛠️ Development
166
+
167
+ ### Install in Development Mode
168
+
169
+ ``` bash
170
+ # Clone the repository
171
+ git clone https://github.com/user/fh-matui.git
172
+ cd fh-matui
173
+
174
+ # Install in editable mode
175
+ pip install -e .
176
+
177
+ # Make changes under nbs/ directory, then compile
178
+ nbdev_prepare
179
+ ```
180
+
181
+ ## 🤝 Why fh-matui?
182
+
183
+ | Challenge | fh-matui Solution |
184
+ |----|----|
185
+ | **CSS complexity** | Pre-built Material Design 3 components via BeerCSS |
186
+ | **JavaScript fatigue** | FastHTML handles interactivity declaratively |
187
+ | **Component consistency** | Unified API across all components |
188
+ | **Dark mode support** | Built-in with automatic system preference detection |
189
+ | **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 |
192
+
193
+ ## 🤖 For LLM Users
194
+
195
+ fh-matui includes **comprehensive documentation bundles** for Large
196
+ Language Models, enabling AI assistants (like Claude, ChatGPT, or GitHub
197
+ Copilot) to help you build FastHTML apps with complete knowledge of the
198
+ component APIs.
199
+
200
+ ### 📥 Download Context File
201
+
202
+ **[📄
203
+ llms-ctx.txt](https://raw.githubusercontent.com/abhisheksreesaila/fh-matui/main/llms-ctx.txt)**
204
+ — Complete API documentation in LLM-optimized format
205
+
206
+ ### 💡 How to Use
207
+
208
+ 1. **Download the context file** from the link above
209
+ 2. **Attach it to your LLM conversation** (drag & drop or paste
210
+ contents)
211
+ 3. **Ask for implementation** using natural language
212
+
213
+ **Example Prompt:**
214
+
215
+ I'm using fh-matui (context attached). Create a dashboard with:
216
+ - A sidebar navigation with 5 menu items
217
+ - A DataTable showing products with pagination
218
+ - A modal form to add/edit products
219
+ - Use the deep purple theme
220
+
221
+ The LLM will generate production-ready FastHTML code using the exact
222
+ component APIs from the documentation.
223
+
224
+ ### 🔄 Staying Up to Date
225
+
226
+ The `llms-ctx.txt` file is automatically regenerated with each release
227
+ to ensure it stays synchronized with the latest API changes. Always
228
+ download the version matching your installed package version for the
229
+ most accurate results.
230
+
231
+ > **📌 Note:** The context file is generated from the same literate
232
+ > programming notebooks that build the library itself, ensuring 100%
233
+ > accuracy with the actual implementation.
234
+
235
+ ## 📄 License
236
+
237
+ This project is licensed under the Apache 2.0 License - see the
238
+ [LICENSE](https://github.com/user/fh-matui/blob/main/LICENSE) file for
239
+ details.
240
+
241
+ ------------------------------------------------------------------------
242
+
243
+ **Built with ❤️ using FastHTML and nbdev**
@@ -1,14 +1,14 @@
1
1
  fh_matui/__init__.py,sha256=iPcoATf7BiWjSu-KocRdM5zFTR4wx4ktCHlGGpvdc1M,23
2
- fh_matui/_modidx.py,sha256=hLZ_V7aRsAvavJ82m02XyvImfzYB5_DnOce8eKRD3xU,22865
2
+ fh_matui/_modidx.py,sha256=RTGErPb_KBkO8z8KjcFH3rczKntoTLkHKAK-4a0oax0,23511
3
3
  fh_matui/app_pages.py,sha256=Sn9-tgBpaPNbR-0nZtPLoSCmAWLOGB4UQ88IkFvzBRY,10361
4
4
  fh_matui/components.py,sha256=KjdTHzWRXpVWBEIGskW1HfhjPpzRYzi6UA_yRjZyMWM,48254
5
5
  fh_matui/core.py,sha256=xtVBN8CtC50ZJ4Iu7o-mUhaA87tWdnz8gBfKRk63Zhs,10680
6
- fh_matui/datatable.py,sha256=MAEibyjRwYlBLMD9dIIocufuWn84jTvrgRt1XKFAN9U,25506
6
+ fh_matui/datatable.py,sha256=DsK73GhB18s7KRk5n_2QU54AuRtfFGTckIVOQ_a8n1w,31632
7
7
  fh_matui/foundations.py,sha256=b7PnObJpKN8ZAU9NzCm9xpfnHzFjjAROU7E2YvA_tj4,1820
8
8
  fh_matui/web_pages.py,sha256=4mF-jpfVcZTVepfQ-aMGgIUp-nBp0YCkvcdsWhUYeaA,34879
9
- fh_matui-0.9.4.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
10
- fh_matui-0.9.4.dist-info/METADATA,sha256=CBK48Mce-W9Lgll_Pj6XvYGDG_GFkqVor8aCqwI84fk,8369
11
- fh_matui-0.9.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- fh_matui-0.9.4.dist-info/entry_points.txt,sha256=zn4CR4gNTiAAxbFsCxHAf2tQhtW29_YOffjbUTgeoWI,38
13
- fh_matui-0.9.4.dist-info/top_level.txt,sha256=l80d5eoA2ZjqtPYwAorLMS5PiHxUxz3zKzxMJ41Xoso,9
14
- fh_matui-0.9.4.dist-info/RECORD,,
9
+ fh_matui-0.9.5.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
10
+ fh_matui-0.9.5.dist-info/METADATA,sha256=dUxJJcfK4MX0RbEdlchOVr2KbHPmn03NVm-nUvlgdcE,10490
11
+ fh_matui-0.9.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ fh_matui-0.9.5.dist-info/entry_points.txt,sha256=zn4CR4gNTiAAxbFsCxHAf2tQhtW29_YOffjbUTgeoWI,38
13
+ fh_matui-0.9.5.dist-info/top_level.txt,sha256=l80d5eoA2ZjqtPYwAorLMS5PiHxUxz3zKzxMJ41Xoso,9
14
+ fh_matui-0.9.5.dist-info/RECORD,,
@@ -1,187 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: fh-matui
3
- Version: 0.9.4
4
- Summary: material-ui for fasthtml
5
- Home-page: https://github.com/abhisheksreesaila/fh-matui
6
- Author: abhishek sreesaila
7
- Author-email: abhishek.sreesaila@gmail.com
8
- License: Apache Software License 2.0
9
- Keywords: nbdev jupyter notebook python
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Natural Language :: English
13
- Classifier: Programming Language :: Python :: 3.9
14
- Classifier: Programming Language :: Python :: 3.10
15
- Classifier: Programming Language :: Python :: 3.11
16
- Classifier: Programming Language :: Python :: 3.12
17
- Classifier: License :: OSI Approved :: Apache Software License
18
- Requires-Python: >=3.9
19
- Description-Content-Type: text/markdown
20
- License-File: LICENSE
21
- Requires-Dist: python-fasthtml
22
- Requires-Dist: fastcore
23
- Requires-Dist: markdown
24
- Provides-Extra: dev
25
- Dynamic: author
26
- Dynamic: author-email
27
- Dynamic: classifier
28
- Dynamic: description
29
- Dynamic: description-content-type
30
- Dynamic: home-page
31
- Dynamic: keywords
32
- Dynamic: license
33
- Dynamic: license-file
34
- Dynamic: provides-extra
35
- Dynamic: requires-dist
36
- Dynamic: requires-python
37
- Dynamic: summary
38
-
39
-
40
- ## What is fh-matui?
41
-
42
- **fh-matui** is a Python library that brings Google's Material Design to [FastHTML](https://fastht.ml/) applications. It provides a comprehensive set of pre-built UI components that integrate seamlessly with FastHTML's hypermedia-driven architecture.
43
-
44
- Built on top of [BeerCSS](https://www.beercss.com/) (a lightweight Material Design 3 CSS framework), fh-matui enables you to create modern, responsive web interfaces entirely in Python — no JavaScript required.
45
-
46
- ## ✨ Key Features
47
-
48
- | Feature | Description |
49
- |---------|-------------|
50
- | 🎨 **Material Design 3** | Modern, beautiful components following Google's latest design language |
51
- | ⚡ **Zero JavaScript** | Build interactive UIs entirely in Python with FastHTML |
52
- | 📱 **Responsive** | Mobile-first design with automatic breakpoint handling |
53
- | 🌙 **Dark Mode** | Built-in light/dark theme support with 20+ color schemes |
54
- | 🧩 **Composable** | Chainable styling APIs inspired by MonsterUI |
55
- | 📊 **Data Tables** | Full-featured tables with pagination, search, sorting, and CRUD |
56
- | 🔧 **nbdev-powered** | Literate programming with documentation built from notebooks |
57
-
58
- ## 🏗️ Architecture
59
-
60
- ```
61
- ┌─────────────────────────────────────────────────────────────┐
62
- │ fh-matui │
63
- ├─────────────────────────────────────────────────────────────┤
64
- │ Foundations │ Core styling utilities, helpers, enums │
65
- │ Core │ Theme system, MatTheme color presets │
66
- │ Components │ Buttons, Cards, Modals, Forms, Tables │
67
- │ App Pages │ Full-page layouts, navigation patterns │
68
- │ Data Tables │ DataTable, DataTableResource for CRUD │
69
- │ Web Pages │ Landing pages, marketing components │
70
- ├─────────────────────────────────────────────────────────────┤
71
- │ BeerCSS │ Material Design 3 CSS framework │
72
- │ FastHTML │ Python hypermedia web framework │
73
- └─────────────────────────────────────────────────────────────┘
74
- ```
75
-
76
- ## 🎨 Available Themes
77
-
78
- fh-matui includes 15+ pre-configured Material Design 3 color themes:
79
-
80
- | Theme | Preview | Theme | Preview |
81
- |-------|---------|-------|---------|
82
- | `MatTheme.red` | 🔴 | `MatTheme.pink` | 🩷 |
83
- | `MatTheme.purple` | 🟣 | `MatTheme.deepPurple` | 💜 |
84
- | `MatTheme.indigo` | 🔵 | `MatTheme.blue` | 💙 |
85
- | `MatTheme.lightBlue` | 🩵 | `MatTheme.cyan` | 🌊 |
86
- | `MatTheme.teal` | 🩶 | `MatTheme.green` | 💚 |
87
- | `MatTheme.lightGreen` | 🍀 | `MatTheme.lime` | 💛 |
88
- | `MatTheme.yellow` | 🌟 | `MatTheme.amber` | 🧡 |
89
- | `MatTheme.orange` | 🟠 | `MatTheme.deepOrange` | 🔶 |
90
-
91
- **Usage:**
92
- ```python
93
- # Choose your theme
94
- app, rt = fast_app(hdrs=[MatTheme.deepPurple.headers()])
95
- ```
96
-
97
- ## 🚀 Quick Start
98
-
99
- Here's a minimal example to get you started:
100
-
101
- ```python
102
- from fasthtml.common import *
103
- from fh_matui.core import MatTheme
104
- from fh_matui.components import Button, Card, FormField
105
-
106
- # Create a themed FastHTML app
107
- app, rt = fast_app(hdrs=[MatTheme.indigo.headers()])
108
-
109
- @rt('/')
110
- def home():
111
- return Div(
112
- Card(
113
- H3("Welcome to fh-matui!"),
114
- P("Build beautiful Material Design apps with Python."),
115
- Button("Get Started", cls="primary"),
116
- ),
117
- cls="padding"
118
- )
119
-
120
- serve()
121
- ```
122
-
123
- ## 📦 Installation
124
-
125
- ```bash
126
- pip install fh-matui
127
- ```
128
-
129
- ### Dependencies
130
-
131
- fh-matui automatically includes:
132
- - **python-fasthtml** - The core FastHTML framework
133
- - **BeerCSS** - Loaded via CDN for Material Design 3 styling
134
-
135
- ### What This Code Does
136
-
137
- 1. **`MatTheme.indigo.headers()`** - Loads BeerCSS with the indigo color scheme
138
- 2. **[`Card`](https://abhisheksreesaila.github.io/fh-matui/components.html#card)** - Creates a Material Design card component with elevation
139
- 3. **[`FormField`](https://abhisheksreesaila.github.io/fh-matui/components.html#formfield)** - Generates a styled input with floating label
140
- 4. **`Button`** - Renders a Material Design button with ripple effects
141
-
142
- ## 📚 Module Reference
143
-
144
- | Module | Description | Key Components |
145
- |--------|-------------|----------------|
146
- | [Foundations](https://abhisheksreesaila.github.io/fh-matui/foundations.html) | Base utilities and helper functions | `BeerHeaders`, `display`, styling helpers |
147
- | [Core](https://abhisheksreesaila.github.io/fh-matui/core.html) | Theme system and styling | `MatTheme`, color presets, theme configuration |
148
- | [Components](https://abhisheksreesaila.github.io/fh-matui/components.html) | UI component library | `Button`, `Card`, `FormField`, `FormModal`, `Grid` |
149
- | [App Pages](https://abhisheksreesaila.github.io/fh-matui/app_pages.html) | Application layouts | Navigation, sidebars, full-page layouts |
150
- | [Data Tables](https://abhisheksreesaila.github.io/fh-matui/datatable.html) | Data management components | `DataTable`, `DataTableResource`, CRUD operations |
151
- | [Web Pages](https://abhisheksreesaila.github.io/fh-matui/web_pages.html) | Marketing/landing pages | Hero sections, feature grids, testimonials |
152
-
153
- ## 🛠️ Development
154
-
155
- ### Install in Development Mode
156
-
157
- ```bash
158
- # Clone the repository
159
- git clone https://github.com/user/fh-matui.git
160
- cd fh-matui
161
-
162
- # Install in editable mode
163
- pip install -e .
164
-
165
- # Make changes under nbs/ directory, then compile
166
- nbdev_prepare
167
- ```
168
-
169
- ## 🤝 Why fh-matui?
170
-
171
- | Challenge | fh-matui Solution |
172
- |-----------|-------------------|
173
- | **CSS complexity** | Pre-built Material Design 3 components via BeerCSS |
174
- | **JavaScript fatigue** | FastHTML handles interactivity declaratively |
175
- | **Component consistency** | Unified API across all components |
176
- | **Dark mode support** | Built-in with automatic system preference detection |
177
- | **Responsive design** | Mobile-first grid system and responsive utilities |
178
- | **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 |
179
- | **Data management** | [`DataTable`](https://abhisheksreesaila.github.io/fh-matui/table.html#datatable) and [`DataTableResource`](https://abhisheksreesaila.github.io/fh-matui/table.html#datatableresource) for CRUD operations |
180
-
181
- ## 📄 License
182
-
183
- This project is licensed under the Apache 2.0 License - see the [LICENSE](https://github.com/user/fh-matui/blob/main/LICENSE) file for details.
184
-
185
- ---
186
-
187
- **Built with ❤️ using FastHTML and nbdev**