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 +61 -0
- faststrap/components/__init__.py +37 -0
- faststrap/components/display/__init__.py +6 -0
- faststrap/components/display/badge.py +82 -0
- faststrap/components/display/card.py +140 -0
- faststrap/components/feedback/__init__.py +7 -0
- faststrap/components/feedback/alert.py +112 -0
- faststrap/components/feedback/modal.py +167 -0
- faststrap/components/feedback/toast.py +205 -0
- faststrap/components/forms/__init__.py +6 -0
- faststrap/components/forms/button.py +88 -0
- faststrap/components/forms/buttongroup.py +143 -0
- faststrap/components/layout/__init__.py +5 -0
- faststrap/components/layout/grid.py +226 -0
- faststrap/components/navigation/__init__.py +6 -0
- faststrap/components/navigation/drawer.py +136 -0
- faststrap/components/navigation/navbar.py +197 -0
- faststrap/core/__init__.py +6 -0
- faststrap/core/assets.py +128 -0
- faststrap/core/base.py +67 -0
- faststrap/core/registry.py +109 -0
- faststrap/py.typed +0 -0
- faststrap/templates/component_template.py +75 -0
- faststrap/templates/test_file_template.py +67 -0
- faststrap/utils/__init__.py +3 -0
- faststrap/utils/attrs.py +35 -0
- faststrap/utils/icons.py +22 -0
- faststrap-0.2.2.dist-info/METADATA +427 -0
- faststrap-0.2.2.dist-info/RECORD +31 -0
- faststrap-0.2.2.dist-info/WHEEL +4 -0
- faststrap-0.2.2.dist-info/licenses/LICENSE +21 -0
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,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,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)
|