cjm-fasthtml-card-stack 0.0.1__py3-none-any.whl → 0.0.3__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.
Files changed (36) hide show
  1. cjm_fasthtml_card_stack/__init__.py +1 -1
  2. cjm_fasthtml_card_stack/components/__init__.py +0 -0
  3. cjm_fasthtml_card_stack/components/controls.py +115 -0
  4. cjm_fasthtml_card_stack/components/progress.py +44 -0
  5. cjm_fasthtml_card_stack/components/states.py +107 -0
  6. cjm_fasthtml_card_stack/components/viewport.py +320 -0
  7. cjm_fasthtml_card_stack/core/__init__.py +0 -0
  8. cjm_fasthtml_card_stack/core/button_ids.py +69 -0
  9. cjm_fasthtml_card_stack/core/config.py +48 -0
  10. cjm_fasthtml_card_stack/core/constants.py +41 -0
  11. cjm_fasthtml_card_stack/core/html_ids.py +94 -0
  12. cjm_fasthtml_card_stack/core/models.py +53 -0
  13. cjm_fasthtml_card_stack/helpers/__init__.py +0 -0
  14. cjm_fasthtml_card_stack/helpers/focus.py +72 -0
  15. cjm_fasthtml_card_stack/js/__init__.py +0 -0
  16. cjm_fasthtml_card_stack/js/core.py +303 -0
  17. cjm_fasthtml_card_stack/js/navigation.py +37 -0
  18. cjm_fasthtml_card_stack/js/scroll.py +76 -0
  19. cjm_fasthtml_card_stack/js/viewport.py +124 -0
  20. cjm_fasthtml_card_stack/keyboard/__init__.py +0 -0
  21. cjm_fasthtml_card_stack/keyboard/actions.py +200 -0
  22. cjm_fasthtml_card_stack/routes/__init__.py +0 -0
  23. cjm_fasthtml_card_stack/routes/handlers.py +158 -0
  24. cjm_fasthtml_card_stack/routes/router.py +150 -0
  25. {cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.3.dist-info}/METADATA +32 -24
  26. cjm_fasthtml_card_stack-0.0.3.dist-info/RECORD +36 -0
  27. {cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.3.dist-info}/top_level.txt +1 -0
  28. demos/__init__.py +0 -0
  29. demos/basic.py +115 -0
  30. demos/bottom.py +104 -0
  31. demos/data.py +29 -0
  32. demos/shared.py +153 -0
  33. cjm_fasthtml_card_stack-0.0.1.dist-info/RECORD +0 -8
  34. {cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.3.dist-info}/WHEEL +0 -0
  35. {cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.3.dist-info}/entry_points.txt +0 -0
  36. {cjm_fasthtml_card_stack-0.0.1.dist-info → cjm_fasthtml_card_stack-0.0.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,150 @@
1
+ """Convenience router factory that wires up standard card stack routes (Tier 2 API)."""
2
+
3
+ # AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/routes/router.ipynb.
4
+
5
+ # %% auto #0
6
+ __all__ = ['init_card_stack_router']
7
+
8
+ # %% ../../nbs/routes/router.ipynb #r1000003
9
+ from typing import Any, Callable, List, Optional, Tuple
10
+
11
+ from fasthtml.common import APIRouter
12
+
13
+ from ..core.config import CardStackConfig
14
+ from ..core.html_ids import CardStackHtmlIds
15
+ from ..core.models import CardStackState, CardStackUrls
16
+ from cjm_fasthtml_card_stack.routes.handlers import (
17
+ card_stack_navigate,
18
+ card_stack_navigate_to_index,
19
+ card_stack_update_viewport,
20
+ card_stack_save_width,
21
+ card_stack_save_scale,
22
+ )
23
+
24
+ # %% ../../nbs/routes/router.ipynb #r1000005
25
+ def init_card_stack_router(
26
+ config: CardStackConfig, # Card stack configuration
27
+ state_getter: Callable[[], CardStackState], # Function to get current state
28
+ state_setter: Callable[[CardStackState], None], # Function to save state
29
+ get_items: Callable[[], List[Any]], # Function to get current items list
30
+ render_card: Callable, # Card renderer callback: (item, CardRenderContext) -> FT
31
+ route_prefix: str = "/card-stack", # Route prefix for all card stack routes
32
+ progress_label: str = "Item", # Label for progress indicator
33
+ ) -> Tuple[APIRouter, CardStackUrls]: # (router, urls) tuple
34
+ """Initialize an APIRouter with all standard card stack routes."""
35
+ router = APIRouter(prefix=route_prefix)
36
+ ids = CardStackHtmlIds(prefix=config.prefix)
37
+
38
+ # -----------------------------------------------------------------
39
+ # Navigation Routes
40
+ # -----------------------------------------------------------------
41
+
42
+ def _nav(direction: str) -> Any:
43
+ """Shared navigation handler."""
44
+ state = state_getter()
45
+ items = get_items()
46
+ result = card_stack_navigate(
47
+ direction=direction, card_items=items, state=state,
48
+ config=config, ids=ids, urls=urls,
49
+ render_card=render_card, progress_label=progress_label,
50
+ )
51
+ state_setter(state)
52
+ return result
53
+
54
+ @router
55
+ def nav_up() -> Any:
56
+ """Navigate to previous item."""
57
+ return _nav("up")
58
+
59
+ @router
60
+ def nav_down() -> Any:
61
+ """Navigate to next item."""
62
+ return _nav("down")
63
+
64
+ @router
65
+ def nav_first() -> Any:
66
+ """Navigate to first item."""
67
+ return _nav("first")
68
+
69
+ @router
70
+ def nav_last() -> Any:
71
+ """Navigate to last item."""
72
+ return _nav("last")
73
+
74
+ @router
75
+ def nav_page_up() -> Any:
76
+ """Navigate up by page."""
77
+ return _nav("page_up")
78
+
79
+ @router
80
+ def nav_page_down() -> Any:
81
+ """Navigate down by page."""
82
+ return _nav("page_down")
83
+
84
+ @router
85
+ def nav_to_index(target_index: int) -> Any:
86
+ """Navigate to a specific item index (click-to-focus)."""
87
+ state = state_getter()
88
+ items = get_items()
89
+ result = card_stack_navigate_to_index(
90
+ target_index=target_index, card_items=items, state=state,
91
+ config=config, ids=ids, urls=urls,
92
+ render_card=render_card, progress_label=progress_label,
93
+ )
94
+ state_setter(state)
95
+ return result
96
+
97
+ # -----------------------------------------------------------------
98
+ # Viewport Route
99
+ # -----------------------------------------------------------------
100
+
101
+ @router
102
+ def update_viewport(visible_count: int) -> Any:
103
+ """Update viewport with new card count (full outerHTML swap)."""
104
+ state = state_getter()
105
+ items = get_items()
106
+ result = card_stack_update_viewport(
107
+ visible_count=visible_count, card_items=items, state=state,
108
+ config=config, ids=ids, urls=urls, render_card=render_card,
109
+ )
110
+ state_setter(state)
111
+ return result
112
+
113
+ # -----------------------------------------------------------------
114
+ # Preference Persistence Routes
115
+ # -----------------------------------------------------------------
116
+
117
+ @router
118
+ def save_width(card_width: int) -> Any:
119
+ """Save card stack width to server state."""
120
+ state = state_getter()
121
+ card_stack_save_width(state, card_width, config)
122
+ state_setter(state)
123
+ return ""
124
+
125
+ @router
126
+ def save_scale(card_scale: int) -> Any:
127
+ """Save card stack scale to server state."""
128
+ state = state_getter()
129
+ card_stack_save_scale(state, card_scale, config)
130
+ state_setter(state)
131
+ return ""
132
+
133
+ # -----------------------------------------------------------------
134
+ # Build URL bundle from registered routes
135
+ # -----------------------------------------------------------------
136
+
137
+ urls = CardStackUrls(
138
+ nav_up=nav_up.to(),
139
+ nav_down=nav_down.to(),
140
+ nav_first=nav_first.to(),
141
+ nav_last=nav_last.to(),
142
+ nav_page_up=nav_page_up.to(),
143
+ nav_page_down=nav_page_down.to(),
144
+ nav_to_index=nav_to_index.to(),
145
+ update_viewport=update_viewport.to(),
146
+ save_width=save_width.to(),
147
+ save_scale=save_scale.to(),
148
+ )
149
+
150
+ return router, urls
@@ -1,19 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cjm-fasthtml-card-stack
3
- Version: 0.0.1
3
+ Version: 0.0.3
4
4
  Summary: A fixed-viewport card stack component for FastHTML with keyboard navigation, scroll-to-nav, configurable focus position, and HTMX-driven OOB updates.
5
5
  Home-page: https://github.com/cj-mills/cjm-fasthtml-card-stack
6
6
  Author: Christian J. Mills
7
- Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
7
+ Author-email: 9126128+cj-mills@users.noreply.github.com
8
8
  License: Apache-2.0
9
- Project-URL: Repository, https://github.com/cj-mills/cjm-fasthtml-card-stack
10
- Project-URL: Documentation, https://cj-mills.github.io/cjm-fasthtml-card-stack
11
- Keywords: nbdev,jupyter,notebook,python
12
- Classifier: Natural Language :: English
9
+ Keywords: nbdev jupyter notebook python
10
+ Classifier: Development Status :: 4 - Beta
13
11
  Classifier: Intended Audience :: Developers
14
- Classifier: Development Status :: 3 - Alpha
15
- Classifier: Programming Language :: Python :: 3
16
- Classifier: Programming Language :: Python :: 3 :: Only
12
+ Classifier: Natural Language :: English
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
17
15
  Requires-Python: >=3.12
18
16
  Description-Content-Type: text/markdown
19
17
  License-File: LICENSE
@@ -21,10 +19,20 @@ Requires-Dist: python-fasthtml
21
19
  Requires-Dist: cjm-fasthtml-tailwind
22
20
  Requires-Dist: cjm-fasthtml-daisyui
23
21
  Requires-Dist: cjm-fasthtml-keyboard-navigation
22
+ Provides-Extra: dev
24
23
  Dynamic: author
24
+ Dynamic: author-email
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
25
28
  Dynamic: home-page
29
+ Dynamic: keywords
30
+ Dynamic: license
26
31
  Dynamic: license-file
32
+ Dynamic: provides-extra
33
+ Dynamic: requires-dist
27
34
  Dynamic: requires-python
35
+ Dynamic: summary
28
36
 
29
37
  # cjm-fasthtml-card-stack
30
38
 
@@ -92,41 +100,41 @@ graph LR
92
100
  components_controls --> core_html_ids
93
101
  components_progress --> core_html_ids
94
102
  components_states --> core_html_ids
95
- components_viewport --> core_models
96
103
  components_viewport --> core_config
97
- components_viewport --> core_constants
98
- components_viewport --> helpers_focus
99
104
  components_viewport --> core_html_ids
100
105
  components_viewport --> components_states
106
+ components_viewport --> core_constants
107
+ components_viewport --> core_models
108
+ components_viewport --> helpers_focus
101
109
  helpers_focus --> core_html_ids
102
110
  js_core --> core_constants
103
- js_core --> core_button_ids
104
- js_core --> core_models
105
111
  js_core --> core_config
106
- js_core --> js_scroll
107
112
  js_core --> core_html_ids
108
- js_core --> js_navigation
109
113
  js_core --> js_viewport
114
+ js_core --> js_navigation
115
+ js_core --> js_scroll
116
+ js_core --> core_models
117
+ js_core --> core_button_ids
110
118
  js_navigation --> core_button_ids
111
- js_scroll --> core_button_ids
112
119
  js_scroll --> core_constants
120
+ js_scroll --> core_button_ids
113
121
  js_scroll --> core_html_ids
114
122
  js_viewport --> core_html_ids
115
- keyboard_actions --> core_button_ids
116
- keyboard_actions --> core_models
117
123
  keyboard_actions --> core_config
118
124
  keyboard_actions --> core_html_ids
119
125
  keyboard_actions --> js_core
126
+ keyboard_actions --> core_models
127
+ keyboard_actions --> core_button_ids
120
128
  routes_handlers --> core_models
121
- routes_handlers --> components_viewport
122
- routes_handlers --> helpers_focus
123
129
  routes_handlers --> core_config
124
- routes_handlers --> components_progress
125
130
  routes_handlers --> core_html_ids
126
- routes_router --> routes_handlers
127
- routes_router --> core_models
131
+ routes_handlers --> components_progress
132
+ routes_handlers --> helpers_focus
133
+ routes_handlers --> components_viewport
128
134
  routes_router --> core_config
129
135
  routes_router --> core_html_ids
136
+ routes_router --> routes_handlers
137
+ routes_router --> core_models
130
138
  ```
131
139
 
132
140
  *39 cross-module dependencies detected*
@@ -0,0 +1,36 @@
1
+ cjm_fasthtml_card_stack/__init__.py,sha256=4GZKi13lDTD25YBkGakhZyEQZWTER_OWQMNPoH_UM2c,22
2
+ cjm_fasthtml_card_stack/_modidx.py,sha256=jYNj6LPbD5I8yDghD3Lx9XWsDnWqc_oNCESguSF8blA,25105
3
+ cjm_fasthtml_card_stack/components/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ cjm_fasthtml_card_stack/components/controls.py,sha256=C3iRvaiRe0ti8S7ogTSKRRRdw-z6vz77mkuvKVmIz-Y,4093
5
+ cjm_fasthtml_card_stack/components/progress.py,sha256=BBeR4EWEikA54bVG41iLdlyyGRFwMJtEh8-cX9L9QLI,1500
6
+ cjm_fasthtml_card_stack/components/states.py,sha256=ClgpdS3e19ccO6VHU6ajulU_yb7c1bQVZf9nSsd5QPc,3797
7
+ cjm_fasthtml_card_stack/components/viewport.py,sha256=vLH9oTbSXlkWZEDHTTKWs38Shd9_ajvSpq1mrvcuOGU,11741
8
+ cjm_fasthtml_card_stack/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ cjm_fasthtml_card_stack/core/button_ids.py,sha256=elKKLBrt-Hu6vgWhrcVlCXgdUeYXHQyfn3NyhhEmxW4,2123
10
+ cjm_fasthtml_card_stack/core/config.py,sha256=XYaptNnzZ8sRcCdxWA_RB1FwBiXU8Ko6mEsNSbxhtb0,1714
11
+ cjm_fasthtml_card_stack/core/constants.py,sha256=MaVTAInPk66VTHzmMtGkxvAgr4R2c7HEWvKDNvmv7hM,1673
12
+ cjm_fasthtml_card_stack/core/html_ids.py,sha256=N5ut7EkEMt2ynjnYv5naE_22gviPzLccGbWU29VsJWg,3099
13
+ cjm_fasthtml_card_stack/core/models.py,sha256=soivwLNBbSNQmcCb9EpFz09gaDhb9HesXbwfq9t_MTY,2448
14
+ cjm_fasthtml_card_stack/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ cjm_fasthtml_card_stack/helpers/focus.py,sha256=PTbsZagutEM8pwpLUKvM8Gjhv7rVNMbNbO27x5uC3YM,2626
16
+ cjm_fasthtml_card_stack/js/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ cjm_fasthtml_card_stack/js/core.py,sha256=fs6mwNOoqxw4K_Wo5X5zKEAXVz6ed7lvg95nYkE9XuQ,12485
18
+ cjm_fasthtml_card_stack/js/navigation.py,sha256=471F3NDxdH-BQPLPQuUvtyACqi6g6EOcELYs_sP1hyk,1288
19
+ cjm_fasthtml_card_stack/js/scroll.py,sha256=X6j5JMOjzgH57sDK1fazNRyGYH2GGEDQeZ0dURFEjC4,3062
20
+ cjm_fasthtml_card_stack/js/viewport.py,sha256=hFWr3zt9odyuaYdRdJmhKJcS_vsJyLRhz-HlZPvGh54,5653
21
+ cjm_fasthtml_card_stack/keyboard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ cjm_fasthtml_card_stack/keyboard/actions.py,sha256=rfCD8lLVXXVPrML-8dj48bT_zOKN7g8VmPMNE21IAJI,7573
23
+ cjm_fasthtml_card_stack/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
+ cjm_fasthtml_card_stack/routes/handlers.py,sha256=GX1h_9t4-2G59IaDyzWTeWUaDFjrXYCqaaUT4UZ_Viw,6823
25
+ cjm_fasthtml_card_stack/routes/router.py,sha256=w6mTQGb-OE0X7t-6dIz7bXg5A0U4llSzNsec-PTkacs,5131
26
+ cjm_fasthtml_card_stack-0.0.3.dist-info/licenses/LICENSE,sha256=xV8xoN4VOL0uw9X8RSs2IMuD_Ss_a9yAbtGNeBWZwnw,11337
27
+ demos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
+ demos/basic.py,sha256=8GVV7eSWOzcz67L6XiPqwKFy886AT5y7Ok-T2ZPX32Q,3815
29
+ demos/bottom.py,sha256=ZLpgpUfOvCzM3rVf3ufvZnSAxuzOQ5gND7RsSUgNpJo,3341
30
+ demos/data.py,sha256=8yVV_E0nLcr0L1PcrTG-DaT-V36apF74ALKNIZuSQ70,4471
31
+ demos/shared.py,sha256=8AMZhgjyobY0pnt_7lmZ91gxb8X1UA1biK0KPfherjo,5384
32
+ cjm_fasthtml_card_stack-0.0.3.dist-info/METADATA,sha256=mv8IdrvEVlWeuYLBn5BKQBZpH8rlUXo9GQeRdhQEvds,36832
33
+ cjm_fasthtml_card_stack-0.0.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
34
+ cjm_fasthtml_card_stack-0.0.3.dist-info/entry_points.txt,sha256=qTw6qaijkEUH1AcPQhpTqJ8TPiUSMnpn0Cvg4hXoX90,68
35
+ cjm_fasthtml_card_stack-0.0.3.dist-info/top_level.txt,sha256=ydBTJsY2ONaDryp85HkjhdGHEuuUOoMGdrOgFA2ddyg,30
36
+ cjm_fasthtml_card_stack-0.0.3.dist-info/RECORD,,
demos/__init__.py ADDED
File without changes
demos/basic.py ADDED
@@ -0,0 +1,115 @@
1
+ """Basic centered card stack demo."""
2
+
3
+ from fasthtml.common import Div, P, Span
4
+
5
+ from cjm_fasthtml_daisyui.components.data_display.card import card, card_body
6
+ from cjm_fasthtml_daisyui.components.data_display.badge import badge, badge_colors
7
+ from cjm_fasthtml_daisyui.utilities.semantic_colors import bg_dui, text_dui
8
+ from cjm_fasthtml_tailwind.utilities.spacing import m
9
+ from cjm_fasthtml_tailwind.utilities.sizing import w
10
+ from cjm_fasthtml_tailwind.utilities.typography import font_size, font_weight
11
+ from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import flex_display, items, gap
12
+ from cjm_fasthtml_tailwind.utilities.effects import opacity
13
+ from cjm_fasthtml_tailwind.core.base import combine_classes
14
+
15
+ from cjm_fasthtml_card_stack.core.config import CardStackConfig
16
+ from cjm_fasthtml_card_stack.core.models import CardStackState, CardRenderContext
17
+ from cjm_fasthtml_card_stack.core.html_ids import CardStackHtmlIds
18
+ from cjm_fasthtml_card_stack.core.button_ids import CardStackButtonIds
19
+ from cjm_fasthtml_card_stack.routes.router import init_card_stack_router
20
+
21
+ from demos.data import SAMPLE_ITEMS
22
+
23
+
24
+ def render_card(item, context: CardRenderContext):
25
+ """Render a card with index badge and text content."""
26
+ is_focused = context.card_role == "focused"
27
+
28
+ index_badge = Span(
29
+ f"#{context.index + 1}",
30
+ cls=combine_classes(
31
+ badge,
32
+ badge_colors.primary if is_focused else badge_colors.neutral,
33
+ )
34
+ )
35
+
36
+ text = P(
37
+ f'"{item}"',
38
+ cls=combine_classes(
39
+ font_weight.medium if is_focused else font_weight.normal,
40
+ text_dui.base_content,
41
+ ),
42
+ style="font-size: calc(1rem * var(--card-stack-scale, 100) / 100)",
43
+ )
44
+
45
+ return Div(
46
+ Div(
47
+ Div(
48
+ index_badge,
49
+ Span(
50
+ f"Item {context.index + 1} of {context.total_items}",
51
+ cls=combine_classes(font_size.xs, text_dui.base_content, str(opacity(60))),
52
+ ),
53
+ cls=combine_classes(flex_display, items.center, gap(2), m.b(2)),
54
+ ),
55
+ text,
56
+ cls=card_body,
57
+ ),
58
+ cls=combine_classes(
59
+ card,
60
+ bg_dui.base_100,# if is_focused else bg_dui.base_200,
61
+ w.full,
62
+ ),
63
+ )
64
+
65
+
66
+ def setup(route_prefix="/basic"):
67
+ """Set up the basic centered card stack demo.
68
+
69
+ Returns dict with config, ids, btn_ids, router, urls, state management,
70
+ and page rendering dependencies.
71
+ """
72
+ config = CardStackConfig(prefix="basic", click_to_focus=True)
73
+ ids = CardStackHtmlIds(prefix=config.prefix)
74
+ btn_ids = CardStackButtonIds(prefix=config.prefix)
75
+
76
+ state = CardStackState(visible_count=5, card_width=60)
77
+
78
+ def get_state():
79
+ return state
80
+
81
+ def set_state(s):
82
+ nonlocal state
83
+ state.focused_index = s.focused_index
84
+ state.visible_count = s.visible_count
85
+ state.card_width = s.card_width
86
+ state.card_scale = s.card_scale
87
+ state.active_mode = s.active_mode
88
+ state.focus_position = s.focus_position
89
+
90
+ def get_items():
91
+ return SAMPLE_ITEMS
92
+
93
+ router, urls = init_card_stack_router(
94
+ config=config,
95
+ state_getter=get_state,
96
+ state_setter=set_state,
97
+ get_items=get_items,
98
+ render_card=render_card,
99
+ route_prefix=route_prefix,
100
+ )
101
+
102
+ return dict(
103
+ config=config,
104
+ ids=ids,
105
+ btn_ids=btn_ids,
106
+ router=router,
107
+ urls=urls,
108
+ get_state=get_state,
109
+ get_items=get_items,
110
+ render_card=render_card,
111
+ container_id="basic-demo-container",
112
+ title="Basic Card Stack",
113
+ description="Centered focus. Navigate with arrow keys, scroll wheel, or click any card.",
114
+ progress_label="Item",
115
+ )
demos/bottom.py ADDED
@@ -0,0 +1,104 @@
1
+ """Bottom-anchored card stack demo (chat-style)."""
2
+
3
+ from fasthtml.common import Div, P, Span
4
+
5
+ from cjm_fasthtml_daisyui.components.data_display.card import card, card_body
6
+ from cjm_fasthtml_daisyui.components.data_display.badge import badge, badge_colors
7
+ from cjm_fasthtml_daisyui.utilities.semantic_colors import bg_dui, text_dui
8
+ from cjm_fasthtml_tailwind.utilities.spacing import p, m
9
+ from cjm_fasthtml_tailwind.utilities.sizing import w
10
+ from cjm_fasthtml_tailwind.core.base import combine_classes
11
+
12
+ from cjm_fasthtml_card_stack.core.config import CardStackConfig
13
+ from cjm_fasthtml_card_stack.core.models import CardStackState, CardRenderContext
14
+ from cjm_fasthtml_card_stack.core.html_ids import CardStackHtmlIds
15
+ from cjm_fasthtml_card_stack.core.button_ids import CardStackButtonIds
16
+ from cjm_fasthtml_card_stack.routes.router import init_card_stack_router
17
+
18
+ from demos.data import SAMPLE_ITEMS
19
+
20
+
21
+ def render_card(item, context: CardRenderContext):
22
+ """Render a compact chat-style message card."""
23
+ is_focused = context.card_role == "focused"
24
+
25
+ index_badge = Span(
26
+ f"#{context.index + 1}",
27
+ cls=combine_classes(
28
+ badge,
29
+ badge_colors.secondary if is_focused else badge_colors.neutral,
30
+ )
31
+ )
32
+
33
+ return Div(
34
+ Div(
35
+ Div(index_badge, cls=combine_classes(m.b(1))),
36
+ P(
37
+ item,
38
+ cls=combine_classes(text_dui.base_content),
39
+ style="font-size: calc(0.875rem * var(--card-stack-scale, 100) / 100)",
40
+ ),
41
+ cls=combine_classes(card_body, p(3)),
42
+ ),
43
+ cls=combine_classes(
44
+ card,
45
+ bg_dui.base_100,# if is_focused else bg_dui.base_200,
46
+ w.full,
47
+ ),
48
+ )
49
+
50
+
51
+ def setup(route_prefix="/bottom"):
52
+ """Set up the bottom-anchored card stack demo.
53
+
54
+ Returns dict with config, ids, btn_ids, router, urls, state management,
55
+ and page rendering dependencies.
56
+ """
57
+ config = CardStackConfig(prefix="bottom", click_to_focus=True)
58
+ ids = CardStackHtmlIds(prefix=config.prefix)
59
+ btn_ids = CardStackButtonIds(prefix=config.prefix)
60
+
61
+ state = CardStackState(
62
+ visible_count=5,
63
+ card_width=60,
64
+ focus_position=-1, # Bottom-anchored
65
+ )
66
+
67
+ def get_state():
68
+ return state
69
+
70
+ def set_state(s):
71
+ nonlocal state
72
+ state.focused_index = s.focused_index
73
+ state.visible_count = s.visible_count
74
+ state.card_width = s.card_width
75
+ state.card_scale = s.card_scale
76
+ state.active_mode = s.active_mode
77
+ state.focus_position = s.focus_position
78
+
79
+ def get_items():
80
+ return SAMPLE_ITEMS
81
+
82
+ router, urls = init_card_stack_router(
83
+ config=config,
84
+ state_getter=get_state,
85
+ state_setter=set_state,
86
+ get_items=get_items,
87
+ render_card=render_card,
88
+ route_prefix=route_prefix,
89
+ )
90
+
91
+ return dict(
92
+ config=config,
93
+ ids=ids,
94
+ btn_ids=btn_ids,
95
+ router=router,
96
+ urls=urls,
97
+ get_state=get_state,
98
+ get_items=get_items,
99
+ render_card=render_card,
100
+ container_id="bottom-demo-container",
101
+ title="Bottom-Anchored Card Stack",
102
+ description="Chat-style layout with focused card at the bottom. Context cards appear above.",
103
+ progress_label="Message",
104
+ )
demos/data.py ADDED
@@ -0,0 +1,29 @@
1
+ """Sample data for card stack demos."""
2
+
3
+ SAMPLE_ITEMS = [
4
+ "Machine learning is a subset of artificial intelligence that enables systems to learn and improve from experience without being explicitly programmed.",
5
+ "The core idea is to feed data into an algorithm, let it find patterns, and then use those patterns to make predictions or decisions on new, unseen data.",
6
+ "There are three main paradigms: supervised learning, unsupervised learning, and reinforcement learning. Each addresses a fundamentally different type of problem.",
7
+ "In supervised learning, the algorithm is trained on labeled examples — input-output pairs where the correct answer is known. The goal is to learn a mapping from inputs to outputs.",
8
+ "Classification and regression are the two primary supervised learning tasks. Classification predicts discrete categories, while regression predicts continuous numerical values.",
9
+ "Common supervised algorithms include linear regression, logistic regression, decision trees, random forests, support vector machines, and neural networks.",
10
+ "Unsupervised learning works with unlabeled data. The algorithm must discover hidden structure, groupings, or patterns without any guidance about what the correct output should be.",
11
+ "Clustering is the most common unsupervised task — grouping similar data points together. K-means, DBSCAN, and hierarchical clustering are widely used approaches.",
12
+ "Dimensionality reduction is another key unsupervised technique. Methods like PCA and t-SNE compress high-dimensional data into fewer dimensions while preserving important structure.",
13
+ "Reinforcement learning trains an agent to make sequences of decisions by interacting with an environment. The agent receives rewards or penalties and learns to maximize cumulative reward.",
14
+ "The training process in supervised learning involves minimizing a loss function — a measure of how far the model's predictions are from the true values.",
15
+ "Gradient descent is the optimization algorithm most commonly used to minimize the loss. It iteratively adjusts model parameters in the direction that reduces the error.",
16
+ "Overfitting occurs when a model learns the training data too well, including its noise and outliers, and performs poorly on new data. It is one of the central challenges in machine learning.",
17
+ "Regularization techniques like L1, L2, and dropout help prevent overfitting by adding constraints that discourage overly complex models.",
18
+ "The bias-variance tradeoff is a fundamental concept. High bias means the model is too simple and underfits; high variance means it is too complex and overfits.",
19
+ "Cross-validation is a technique for evaluating model performance by splitting data into multiple train-test folds, reducing the risk of evaluation bias from a single split.",
20
+ "Feature engineering — the process of creating, selecting, and transforming input variables — often has more impact on model performance than the choice of algorithm.",
21
+ "Neural networks are composed of layers of interconnected nodes. Each connection has a learnable weight, and each node applies a nonlinear activation function to its inputs.",
22
+ "Deep learning refers to neural networks with many layers. These architectures can automatically learn hierarchical feature representations from raw data.",
23
+ "Convolutional neural networks (CNNs) are specialized for grid-like data such as images. They use learnable filters that detect local patterns like edges, textures, and shapes.",
24
+ "Recurrent neural networks (RNNs) and their variants like LSTMs are designed for sequential data. They maintain hidden state that carries information across time steps.",
25
+ "Transformers replaced RNNs for most sequence tasks by using self-attention mechanisms that can relate any position in a sequence to any other position in parallel.",
26
+ "Large language models like GPT and Claude are transformer-based models trained on massive text corpora. They learn to predict the next token and develop broad language understanding.",
27
+ "Transfer learning allows a model pretrained on a large dataset to be fine-tuned on a smaller, task-specific dataset. This dramatically reduces the data and compute needed for new tasks.",
28
+ "The field continues to evolve rapidly, with active research in areas like multimodal learning, efficient architectures, alignment, interpretability, and learning from human feedback.",
29
+ ]