faststrap 0.2.2__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.
faststrap/__init__.py ADDED
@@ -0,0 +1,61 @@
1
+ """FastStrap - Modern Bootstrap 5 components for FastHTML.
2
+
3
+ Build beautiful web UIs in pure Python with zero JavaScript knowledge.
4
+ """
5
+
6
+ __version__ = "0.2.0" # ← Update version!
7
+ __author__ = "FastStrap Contributors"
8
+ __license__ = "MIT"
9
+
10
+ # Core functionality
11
+ # Display
12
+ from .components.display import Badge, Card
13
+
14
+ # Feedback
15
+ from .components.feedback import Alert, Modal, Toast, ToastContainer
16
+
17
+ # Forms
18
+ from .components.forms import Button, ButtonGroup, ButtonToolbar
19
+
20
+ # Layout
21
+ from .components.layout import Col, Container, Row
22
+
23
+ # Navigation
24
+ from .components.navigation import Drawer, Navbar
25
+ from .core.assets import add_bootstrap, get_assets
26
+ from .core.base import merge_classes
27
+
28
+ # Utils
29
+ from .utils.icons import Icon
30
+
31
+ __all__ = [
32
+ # Core
33
+ "add_bootstrap",
34
+ "get_assets",
35
+ "merge_classes",
36
+ # Forms
37
+ "Button",
38
+ "ButtonGroup",
39
+ "ButtonToolbar",
40
+ # Display
41
+ "Badge",
42
+ "Card",
43
+ # Feedback
44
+ "Alert",
45
+ "Toast",
46
+ "ToastContainer",
47
+ "Modal",
48
+ # Layout
49
+ "Container",
50
+ "Row",
51
+ "Col",
52
+ # Navigation
53
+ "Drawer",
54
+ "Navbar",
55
+ # Utils
56
+ "Icon",
57
+ # Metadata
58
+ "__version__",
59
+ "__author__",
60
+ "__license__",
61
+ ]
@@ -0,0 +1,37 @@
1
+ """FastStrap components."""
2
+
3
+ # Forms
4
+ # Display
5
+ from .display import Badge, Card
6
+
7
+ # Feedback
8
+ from .feedback import Alert, Modal, Toast, ToastContainer
9
+ from .forms import Button, ButtonGroup, ButtonToolbar
10
+
11
+ # Layout
12
+ from .layout import Col, Container, Row
13
+
14
+ # Navigation
15
+ from .navigation import Drawer, Navbar
16
+
17
+ __all__ = [
18
+ # Forms
19
+ "Button",
20
+ "ButtonGroup",
21
+ "ButtonToolbar",
22
+ # Display
23
+ "Badge",
24
+ "Card",
25
+ # Feedback
26
+ "Alert",
27
+ "Toast",
28
+ "ToastContainer",
29
+ "Modal",
30
+ # Layout
31
+ "Container",
32
+ "Row",
33
+ "Col",
34
+ # Navigation
35
+ "Drawer",
36
+ "Navbar",
37
+ ]
@@ -0,0 +1,6 @@
1
+ """Form components."""
2
+
3
+ from .badge import Badge
4
+ from .card import Card
5
+
6
+ __all__ = ["Badge", "Card"]
@@ -0,0 +1,82 @@
1
+ """Bootstrap Badge component for status indicators and labels."""
2
+
3
+ from typing import Any, Literal
4
+
5
+ from fasthtml.common import Span
6
+
7
+ from ...core.base import merge_classes
8
+ from ...utils.attrs import convert_attrs
9
+
10
+ VariantType = Literal[
11
+ "primary",
12
+ "secondary",
13
+ "success",
14
+ "danger",
15
+ "warning",
16
+ "info",
17
+ "light",
18
+ "dark",
19
+ ]
20
+
21
+
22
+ def Badge(
23
+ *children: Any,
24
+ variant: VariantType = "primary",
25
+ pill: bool = False,
26
+ **kwargs: Any,
27
+ ) -> Span:
28
+ """Bootstrap Badge component for status indicators and labels.
29
+
30
+ Args:
31
+ *children: Badge content (text, numbers, icons)
32
+ variant: Bootstrap color variant
33
+ pill: Use rounded pill style
34
+ **kwargs: Additional HTML attributes (cls, id, hx-*, data-*, etc.)
35
+
36
+ Returns:
37
+ FastHTML Span element with badge classes
38
+
39
+ Example:
40
+ Basic usage:
41
+ >>> Badge("New", variant="success")
42
+
43
+ Pill style:
44
+ >>> Badge("99+", variant="danger", pill=True)
45
+
46
+ With icon:
47
+ >>> Badge(Icon("check"), "Verified", variant="info")
48
+
49
+ In button:
50
+ >>> Button("Messages ", Badge("4", variant="danger"))
51
+
52
+ With HTMX:
53
+ >>> Badge("Live", variant="warning", hx_get="/status", hx_trigger="every 5s")
54
+
55
+ Note:
56
+ Badges scale to match their immediate parent's font size.
57
+ Use text-bg-* classes for colored backgrounds with contrasting text.
58
+
59
+ See Also:
60
+ Bootstrap docs: https://getbootstrap.com/docs/5.3/components/badge/
61
+ """
62
+ # Build base classes
63
+ classes = ["badge"]
64
+
65
+ # Add variant background
66
+ classes.append(f"text-bg-{variant}")
67
+
68
+ # Add pill style if requested
69
+ if pill:
70
+ classes.append("rounded-pill")
71
+
72
+ # Merge with user classes
73
+ user_cls = kwargs.pop("cls", "")
74
+ all_classes = merge_classes(" ".join(classes), user_cls)
75
+
76
+ # Build attributes
77
+ attrs: dict[str, Any] = {"cls": all_classes}
78
+
79
+ # Convert remaining kwargs
80
+ attrs.update(convert_attrs(kwargs))
81
+
82
+ return Span(*children, **attrs)
@@ -0,0 +1,140 @@
1
+ """Bootstrap Card component with header, body, footer support."""
2
+
3
+ from typing import Any
4
+
5
+ from fasthtml.common import H5, Div, Img
6
+
7
+ from ...core.base import merge_classes
8
+ from ...utils.attrs import convert_attrs
9
+
10
+
11
+ def Card(
12
+ *children: Any,
13
+ title: str | None = None,
14
+ subtitle: str | None = None,
15
+ header: Any | None = None,
16
+ footer: Any | None = None,
17
+ img_top: str | None = None,
18
+ img_bottom: str | None = None,
19
+ img_overlay: bool = False,
20
+ **kwargs: Any,
21
+ ) -> Div:
22
+ """Bootstrap Card component for flexible content containers.
23
+
24
+ Args:
25
+ *children: Card body content
26
+ title: Card title (styled with card-title)
27
+ subtitle: Card subtitle (styled with card-subtitle)
28
+ header: Card header content (separate section above body)
29
+ footer: Card footer content (separate section below body)
30
+ img_top: Image URL for top of card
31
+ img_bottom: Image URL for bottom of card
32
+ img_overlay: Use image as background with overlay text
33
+ **kwargs: Additional HTML attributes (cls, id, hx-*, data-*, etc.)
34
+
35
+ Returns:
36
+ FastHTML Div element with card structure
37
+
38
+ Example:
39
+ Basic card:
40
+ >>> Card("Card content", title="Card Title")
41
+
42
+ With header and footer:
43
+ >>> Card(
44
+ ... "Main content",
45
+ ... title="Title",
46
+ ... header="Featured",
47
+ ... footer="Last updated 3 mins ago"
48
+ ... )
49
+
50
+ With image:
51
+ >>> Card(
52
+ ... "Beautiful landscape description",
53
+ ... title="Mountain View",
54
+ ... img_top="mountain.jpg"
55
+ ... )
56
+
57
+ Image overlay:
58
+ >>> Card(
59
+ ... "Overlay text",
60
+ ... title="Title on Image",
61
+ ... img_top="bg.jpg",
62
+ ... img_overlay=True
63
+ ... )
64
+
65
+ With HTMX:
66
+ >>> Card(
67
+ ... "Click to load more",
68
+ ... title="Dynamic Card",
69
+ ... hx_get="/more",
70
+ ... hx_trigger="click"
71
+ ... )
72
+
73
+ Note:
74
+ Cards are flexible containers with minimal required markup.
75
+ Use the grid system for layouts with multiple cards.
76
+
77
+ See Also:
78
+ Bootstrap docs: https://getbootstrap.com/docs/5.3/components/card/
79
+ """
80
+ # Build base classes
81
+ classes = ["card"]
82
+
83
+ # Merge with user classes
84
+ user_cls = kwargs.pop("cls", "")
85
+ all_classes = merge_classes(" ".join(classes), user_cls)
86
+
87
+ # Build attributes
88
+ attrs: dict[str, Any] = {"cls": all_classes}
89
+ attrs.update(convert_attrs(kwargs))
90
+
91
+ # Build card structure
92
+ parts = []
93
+
94
+ # Add header if provided
95
+ if header:
96
+ parts.append(Div(header, cls="card-header"))
97
+
98
+ # Add top image
99
+ if img_top and not img_overlay:
100
+ parts.append(Img(src=img_top, cls="card-img-top", alt=""))
101
+
102
+ # Build body content
103
+ body_content = []
104
+
105
+ # Add image for overlay mode
106
+ if img_overlay and img_top:
107
+ parts.append(Img(src=img_top, cls="card-img", alt=""))
108
+ body_cls = "card-img-overlay"
109
+ else:
110
+ body_cls = "card-body"
111
+
112
+ # Add title
113
+ if title:
114
+ body_content.append(H5(title, cls="card-title"))
115
+
116
+ # Add subtitle
117
+ if subtitle:
118
+ body_content.append(Div(subtitle, cls="card-subtitle mb-2 text-muted"))
119
+
120
+ # Add main content
121
+ if children:
122
+ # If there's a title/subtitle, wrap content in P for better semantics
123
+ if title or subtitle:
124
+ body_content.append(Div(*children, cls="card-text"))
125
+ else:
126
+ body_content.extend(children)
127
+
128
+ # Add body
129
+ if body_content:
130
+ parts.append(Div(*body_content, cls=body_cls))
131
+
132
+ # Add bottom image
133
+ if img_bottom and not img_overlay:
134
+ parts.append(Img(src=img_bottom, cls="card-img-bottom", alt=""))
135
+
136
+ # Add footer
137
+ if footer:
138
+ parts.append(Div(footer, cls="card-footer text-muted"))
139
+
140
+ return Div(*parts, **attrs)
@@ -0,0 +1,7 @@
1
+ """Feedback components."""
2
+
3
+ from .alert import Alert
4
+ from .modal import Modal
5
+ from .toast import Toast, ToastContainer
6
+
7
+ __all__ = ["Alert", "Toast", "ToastContainer", "Modal"]
@@ -0,0 +1,112 @@
1
+ """Bootstrap Alert component for contextual feedback messages."""
2
+
3
+ from typing import Any, Literal
4
+
5
+ from fasthtml.common import Button, Div, Span
6
+
7
+ from ...core.base import merge_classes
8
+ from ...utils.attrs import convert_attrs
9
+
10
+ VariantType = Literal[
11
+ "primary",
12
+ "secondary",
13
+ "success",
14
+ "danger",
15
+ "warning",
16
+ "info",
17
+ "light",
18
+ "dark",
19
+ ]
20
+
21
+
22
+ def Alert(
23
+ *children: Any,
24
+ variant: VariantType = "primary",
25
+ dismissible: bool = False,
26
+ heading: str | None = None,
27
+ **kwargs: Any,
28
+ ) -> Div:
29
+ """Bootstrap Alert component for contextual feedback messages.
30
+
31
+ Args:
32
+ *children: Alert content
33
+ variant: Bootstrap color variant
34
+ dismissible: Add close button to dismiss alert
35
+ heading: Optional heading text (styled with alert-heading)
36
+ **kwargs: Additional HTML attributes (cls, id, hx-*, data-*, etc.)
37
+
38
+ Returns:
39
+ FastHTML Div element with alert classes
40
+
41
+ Example:
42
+ Basic usage:
43
+ >>> Alert("Operation successful!", variant="success")
44
+
45
+ With heading:
46
+ >>> Alert(
47
+ ... "Check your inbox for confirmation.",
48
+ ... variant="info",
49
+ ... heading="Email Sent"
50
+ ... )
51
+
52
+ Dismissible:
53
+ >>> Alert(
54
+ ... "This alert can be closed.",
55
+ ... variant="warning",
56
+ ... dismissible=True
57
+ ... )
58
+
59
+ With HTMX:
60
+ >>> Alert(
61
+ ... "Loading...",
62
+ ... variant="info",
63
+ ... hx_get="/status",
64
+ ... hx_trigger="load"
65
+ ... )
66
+
67
+ Note:
68
+ Dismissible alerts require Bootstrap's JavaScript.
69
+ The alert will fade out when the close button is clicked.
70
+
71
+ See Also:
72
+ Bootstrap docs: https://getbootstrap.com/docs/5.3/components/alerts/
73
+ """
74
+ # Build base classes
75
+ classes = ["alert", f"alert-{variant}"]
76
+
77
+ # Add dismissible class if needed
78
+ if dismissible:
79
+ classes.append("alert-dismissible fade show")
80
+
81
+ # Merge with user classes
82
+ user_cls = kwargs.pop("cls", "")
83
+ all_classes = merge_classes(" ".join(classes), user_cls)
84
+
85
+ # Build attributes
86
+ attrs: dict[str, Any] = {"cls": all_classes, "role": "alert"}
87
+
88
+ # Convert remaining kwargs
89
+ attrs.update(convert_attrs(kwargs))
90
+
91
+ # Build content
92
+ content = []
93
+
94
+ # Add heading if provided
95
+ if heading:
96
+ content.append(Div(heading, cls="alert-heading h4"))
97
+
98
+ # Add main content
99
+ content.extend(children)
100
+
101
+ # Add close button if dismissible
102
+ if dismissible:
103
+ close_btn = Button(
104
+ Span("×", aria_hidden="true"),
105
+ type="button",
106
+ cls="btn-close",
107
+ data_bs_dismiss="alert",
108
+ aria_label="Close",
109
+ )
110
+ content.append(close_btn)
111
+
112
+ return Div(*content, **attrs)
@@ -0,0 +1,167 @@
1
+ """Bootstrap Modal component for dialog boxes."""
2
+
3
+ from typing import Any, Literal
4
+
5
+ from fasthtml.common import H5, Button, Div
6
+
7
+ from ...core.base import merge_classes
8
+ from ...utils.attrs import convert_attrs
9
+
10
+ SizeType = Literal["sm", "lg", "xl"]
11
+
12
+
13
+ # @register(category="feedback", requires_js=True)
14
+ def Modal(
15
+ *children: Any,
16
+ modal_id: str, # Using modal_id to avoid conflict
17
+ title: str | None = None,
18
+ footer: Any | None = None,
19
+ size: SizeType | None = None,
20
+ centered: bool = False,
21
+ scrollable: bool = False,
22
+ fullscreen: bool | Literal["sm-down", "md-down", "lg-down", "xl-down", "xxl-down"] = False,
23
+ static_backdrop: bool = False,
24
+ fade: bool = True,
25
+ **kwargs: Any,
26
+ ) -> Div:
27
+ """Bootstrap Modal component for dialog boxes and overlays.
28
+
29
+ Args:
30
+ *children: Modal body content
31
+ modal_id: Unique ID for the modal (required for Bootstrap JS)
32
+ title: Modal header title
33
+ footer: Modal footer content (buttons, text, etc.)
34
+ size: Modal size (sm, lg, xl)
35
+ centered: Vertically center modal
36
+ scrollable: Make modal body scrollable
37
+ fullscreen: Full-screen modal (True or breakpoint like "md-down")
38
+ static_backdrop: Clicking backdrop doesn't close modal
39
+ **kwargs: Additional HTML attributes (cls, hx-*, data-*, etc.)
40
+
41
+ Returns:
42
+ FastHTML Div element with modal structure
43
+
44
+ Example:
45
+ Basic modal:
46
+ >>> Modal(
47
+ ... P("Are you sure you want to delete this?"),
48
+ ... modal_id="deleteModal",
49
+ ... title="Confirm Delete",
50
+ ... footer=Div(
51
+ ... Button("Cancel", variant="secondary", data_bs_dismiss="modal"),
52
+ ... Button("Delete", variant="danger")
53
+ ... )
54
+ ... )
55
+
56
+ Large centered modal:
57
+ >>> Modal(
58
+ ... "Modal content here",
59
+ ... modal_id="largeModal",
60
+ ... title="Large Modal",
61
+ ... size="lg",
62
+ ... centered=True
63
+ ... )
64
+
65
+ Scrollable modal:
66
+ >>> Modal(
67
+ ... "Very long content...",
68
+ ... modal_id="scrollModal",
69
+ ... title="Scrollable",
70
+ ... scrollable=True
71
+ ... )
72
+
73
+ Full-screen on mobile:
74
+ >>> Modal(
75
+ ... "Content",
76
+ ... modal_id="fullModal",
77
+ ... title="Full Screen",
78
+ ... fullscreen="md-down"
79
+ ... )
80
+
81
+ Note:
82
+ To trigger a modal, use Bootstrap's data attributes:
83
+ >>> Button("Open Modal", data_bs_toggle="modal", data_bs_target="#deleteModal")
84
+
85
+ Or use HTMX to load modal content dynamically:
86
+ >>> Button("Load Modal", hx_get="/modal-content", hx_target="#modalContainer")
87
+
88
+ See Also:
89
+ Bootstrap docs: https://getbootstrap.com/docs/5.3/components/modal/
90
+ """
91
+ # Build modal dialog classes
92
+ dialog_classes = ["modal-dialog"]
93
+
94
+ if size:
95
+ dialog_classes.append(f"modal-{size}")
96
+
97
+ if centered:
98
+ dialog_classes.append("modal-dialog-centered")
99
+
100
+ if scrollable:
101
+ dialog_classes.append("modal-dialog-scrollable")
102
+
103
+ if fullscreen:
104
+ if fullscreen is True:
105
+ dialog_classes.append("modal-fullscreen")
106
+ else:
107
+ dialog_classes.append(f"modal-fullscreen-{fullscreen}")
108
+
109
+ # Build modal attributes
110
+ modal_classes = ["modal"]
111
+ if fade:
112
+ modal_classes.append("fade")
113
+
114
+ user_cls = kwargs.pop("cls", "")
115
+ all_modal_classes = merge_classes(" ".join(modal_classes), user_cls)
116
+
117
+ attrs: dict[str, Any] = {
118
+ "cls": all_modal_classes,
119
+ "tabindex": "-1",
120
+ "aria_labelledby": f"{modal_id}Label",
121
+ "aria_hidden": "true",
122
+ }
123
+
124
+ # Static backdrop
125
+ if static_backdrop:
126
+ attrs["data_bs_backdrop"] = "static"
127
+ attrs["data_bs_keyboard"] = "false"
128
+
129
+ # Convert remaining kwargs (excluding modal_id)
130
+ converted_kwargs = convert_attrs({k: v for k, v in kwargs.items() if k != "modal_id"})
131
+ attrs.update(converted_kwargs)
132
+
133
+ # Build modal structure
134
+ content_parts = []
135
+
136
+ # Header
137
+ if title:
138
+ header = Div(
139
+ H5(title, cls="modal-title", id=f"{modal_id}Label"),
140
+ Button(
141
+ type="button",
142
+ cls="btn-close",
143
+ data_bs_dismiss="modal",
144
+ aria_label="Close",
145
+ ),
146
+ cls="modal-header",
147
+ )
148
+ content_parts.append(header)
149
+
150
+ # Body
151
+ body = Div(*children, cls="modal-body")
152
+ content_parts.append(body)
153
+
154
+ # Footer
155
+ if footer:
156
+ footer_div = Div(footer, cls="modal-footer")
157
+ content_parts.append(footer_div)
158
+
159
+ # Assemble modal
160
+ modal_content = Div(*content_parts, cls="modal-content")
161
+ modal_dialog = Div(modal_content, cls=" ".join(dialog_classes))
162
+
163
+ # Assemble modal
164
+ modal_content = Div(*content_parts, cls="modal-content")
165
+ modal_dialog = Div(modal_content, cls=" ".join(dialog_classes))
166
+
167
+ return Div(modal_dialog, id=modal_id, **attrs)