streamlit-react-components 1.7.3__py3-none-any.whl → 1.8.0__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.
- streamlit_react_components/__init__.py +23 -1
- streamlit_react_components/_frontend/index.css +1 -1
- streamlit_react_components/_frontend/index.js +109 -109
- streamlit_react_components/form/__init__.py +2 -0
- streamlit_react_components/form/checkbox_group.py +17 -1
- streamlit_react_components/form/form_date_slider.py +147 -0
- streamlit_react_components/form/form_select.py +17 -1
- streamlit_react_components/form/form_slider.py +19 -1
- streamlit_react_components/form/radio_group.py +17 -1
- streamlit_react_components/styled_container.py +193 -0
- streamlit_react_components/tailwind.py +1536 -0
- {streamlit_react_components-1.7.3.dist-info → streamlit_react_components-1.8.0.dist-info}/METADATA +1 -1
- {streamlit_react_components-1.7.3.dist-info → streamlit_react_components-1.8.0.dist-info}/RECORD +15 -12
- {streamlit_react_components-1.7.3.dist-info → streamlit_react_components-1.8.0.dist-info}/WHEEL +0 -0
- {streamlit_react_components-1.7.3.dist-info → streamlit_react_components-1.8.0.dist-info}/top_level.txt +0 -0
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
from .form_select import form_select
|
|
4
4
|
from .form_slider import form_slider
|
|
5
|
+
from .form_date_slider import form_date_slider
|
|
5
6
|
from .checkbox_group import checkbox_group
|
|
6
7
|
from .radio_group import radio_group
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
10
|
"form_select",
|
|
10
11
|
"form_slider",
|
|
12
|
+
"form_date_slider",
|
|
11
13
|
"checkbox_group",
|
|
12
14
|
"radio_group",
|
|
13
15
|
]
|
|
@@ -19,6 +19,7 @@ def checkbox_group(
|
|
|
19
19
|
style: Optional[Dict[str, Any]] = None,
|
|
20
20
|
class_name: str = "",
|
|
21
21
|
theme: Optional[Dict[str, Any]] = None,
|
|
22
|
+
defer_update: bool = False,
|
|
22
23
|
key: Optional[str] = None,
|
|
23
24
|
) -> List[str]:
|
|
24
25
|
"""
|
|
@@ -36,7 +37,10 @@ def checkbox_group(
|
|
|
36
37
|
class_name: Tailwind CSS classes
|
|
37
38
|
theme: Optional theme dictionary. If None, uses active global theme.
|
|
38
39
|
Set to False to disable theming for this component.
|
|
39
|
-
|
|
40
|
+
defer_update: If True, don't trigger Streamlit rerun on change.
|
|
41
|
+
Value is stored locally and sent on next rerun (e.g., Apply button).
|
|
42
|
+
Requires 'key' to be set.
|
|
43
|
+
key: Unique key for the component (required if defer_update=True)
|
|
40
44
|
|
|
41
45
|
Returns:
|
|
42
46
|
List of checked item IDs
|
|
@@ -60,6 +64,16 @@ def checkbox_group(
|
|
|
60
64
|
items=[...],
|
|
61
65
|
layout="horizontal"
|
|
62
66
|
)
|
|
67
|
+
|
|
68
|
+
# Deferred update (no rerun until Apply button clicked)
|
|
69
|
+
selected = checkbox_group(
|
|
70
|
+
label="Parameters",
|
|
71
|
+
items=[...],
|
|
72
|
+
defer_update=True,
|
|
73
|
+
key="params_checkbox"
|
|
74
|
+
)
|
|
75
|
+
if st.button("Apply"):
|
|
76
|
+
st.rerun()
|
|
63
77
|
"""
|
|
64
78
|
# Get default checked items
|
|
65
79
|
default_checked = [item["id"] for item in items if item.get("checked", False)]
|
|
@@ -78,6 +92,8 @@ def checkbox_group(
|
|
|
78
92
|
style=style,
|
|
79
93
|
className=class_name,
|
|
80
94
|
theme=resolved_theme,
|
|
95
|
+
deferUpdate=defer_update,
|
|
96
|
+
componentKey=key,
|
|
81
97
|
key=key,
|
|
82
98
|
default=default_checked,
|
|
83
99
|
)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""FormDateSlider component - A date slider with optional range selection."""
|
|
2
|
+
|
|
3
|
+
import streamlit.components.v1 as components
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from datetime import date, timedelta
|
|
6
|
+
from typing import Dict, Any, Optional, Union, Tuple
|
|
7
|
+
|
|
8
|
+
_FRONTEND_DIR = Path(__file__).parent.parent / "_frontend"
|
|
9
|
+
|
|
10
|
+
_component = components.declare_component(
|
|
11
|
+
"streamlit_react_components",
|
|
12
|
+
path=str(_FRONTEND_DIR),
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def form_date_slider(
|
|
17
|
+
label: str,
|
|
18
|
+
min_val: date,
|
|
19
|
+
max_val: date,
|
|
20
|
+
value: Union[date, Tuple[date, date]],
|
|
21
|
+
step_type: Optional[str] = "month",
|
|
22
|
+
step: Optional[timedelta] = None,
|
|
23
|
+
format: str = "YYYY-MM",
|
|
24
|
+
color: str = "blue",
|
|
25
|
+
style: Optional[Dict[str, Any]] = None,
|
|
26
|
+
class_name: str = "",
|
|
27
|
+
theme: Optional[Dict[str, Any]] = None,
|
|
28
|
+
defer_update: bool = False,
|
|
29
|
+
key: Optional[str] = None,
|
|
30
|
+
) -> Union[date, Tuple[date, date]]:
|
|
31
|
+
"""
|
|
32
|
+
Display a date slider with single or range selection.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
label: Label text for the slider
|
|
36
|
+
min_val: Minimum selectable date
|
|
37
|
+
max_val: Maximum selectable date
|
|
38
|
+
value: Current value - single date or tuple of (start, end) for range mode
|
|
39
|
+
step_type: Step granularity - "month", "quarter", "year", "week", "day", or None
|
|
40
|
+
When set to a calendar unit, snaps to boundaries (e.g., 1st of month).
|
|
41
|
+
When "day" or None, uses the `step` timedelta parameter.
|
|
42
|
+
step: Step increment as timedelta (only used when step_type is "day" or None)
|
|
43
|
+
Defaults to timedelta(days=1)
|
|
44
|
+
format: Display format string - "YYYY-MM", "YYYY-MM-DD", "MMM YYYY", etc.
|
|
45
|
+
color: Accent color - preset name or hex value
|
|
46
|
+
style: Inline CSS styles as a dictionary
|
|
47
|
+
class_name: Tailwind CSS classes
|
|
48
|
+
theme: Optional theme dictionary. If None, uses active global theme.
|
|
49
|
+
defer_update: If True, don't trigger Streamlit rerun on change.
|
|
50
|
+
key: Unique key for the component
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Single date if value was a date, or tuple (start, end) if value was a tuple
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
# Monthly range selection
|
|
57
|
+
start, end = form_date_slider(
|
|
58
|
+
label="Date Range",
|
|
59
|
+
min_val=date(2024, 1, 1),
|
|
60
|
+
max_val=date(2025, 12, 31),
|
|
61
|
+
value=(date(2024, 3, 1), date(2024, 9, 1)),
|
|
62
|
+
step_type="month",
|
|
63
|
+
format="YYYY-MM",
|
|
64
|
+
key="date_range"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Single month selection
|
|
68
|
+
selected = form_date_slider(
|
|
69
|
+
label="Select Month",
|
|
70
|
+
min_val=date(2024, 1, 1),
|
|
71
|
+
max_val=date(2025, 12, 31),
|
|
72
|
+
value=date(2024, 6, 1),
|
|
73
|
+
step_type="month",
|
|
74
|
+
format="YYYY-MM",
|
|
75
|
+
key="single_month"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Daily stepping with custom interval
|
|
79
|
+
selected = form_date_slider(
|
|
80
|
+
label="Select Date",
|
|
81
|
+
min_val=date(2024, 1, 1),
|
|
82
|
+
max_val=date(2024, 12, 31),
|
|
83
|
+
value=date(2024, 6, 15),
|
|
84
|
+
step_type="day",
|
|
85
|
+
step=timedelta(days=7), # Weekly steps
|
|
86
|
+
format="YYYY-MM-DD",
|
|
87
|
+
key="weekly_date"
|
|
88
|
+
)
|
|
89
|
+
"""
|
|
90
|
+
# Determine if range mode based on value type
|
|
91
|
+
is_range = isinstance(value, (tuple, list))
|
|
92
|
+
|
|
93
|
+
# Convert dates to ISO strings
|
|
94
|
+
min_val_str = min_val.isoformat()
|
|
95
|
+
max_val_str = max_val.isoformat()
|
|
96
|
+
|
|
97
|
+
if is_range:
|
|
98
|
+
value_data = [value[0].isoformat(), value[1].isoformat()]
|
|
99
|
+
default_data = value_data
|
|
100
|
+
else:
|
|
101
|
+
value_data = value.isoformat()
|
|
102
|
+
default_data = value_data
|
|
103
|
+
|
|
104
|
+
# Convert step timedelta to days if provided
|
|
105
|
+
step_days = None
|
|
106
|
+
if step is not None:
|
|
107
|
+
step_days = step.days
|
|
108
|
+
elif step_type in ("day", None):
|
|
109
|
+
step_days = 1 # Default to 1 day
|
|
110
|
+
|
|
111
|
+
# Resolve theme (None = use global, False = disable)
|
|
112
|
+
from ..themes import get_active_theme
|
|
113
|
+
resolved_theme = None
|
|
114
|
+
if theme is not False:
|
|
115
|
+
resolved_theme = theme if theme is not None else get_active_theme()
|
|
116
|
+
|
|
117
|
+
result = _component(
|
|
118
|
+
component="form_date_slider",
|
|
119
|
+
label=label,
|
|
120
|
+
minVal=min_val_str,
|
|
121
|
+
maxVal=max_val_str,
|
|
122
|
+
value=value_data,
|
|
123
|
+
stepType=step_type,
|
|
124
|
+
stepDays=step_days,
|
|
125
|
+
format=format,
|
|
126
|
+
color=color,
|
|
127
|
+
style=style,
|
|
128
|
+
className=class_name,
|
|
129
|
+
theme=resolved_theme,
|
|
130
|
+
deferUpdate=defer_update,
|
|
131
|
+
componentKey=key,
|
|
132
|
+
key=key,
|
|
133
|
+
default=default_data,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Parse result back to date objects
|
|
137
|
+
if result is None:
|
|
138
|
+
return value
|
|
139
|
+
|
|
140
|
+
if is_range:
|
|
141
|
+
if isinstance(result, (list, tuple)) and len(result) == 2:
|
|
142
|
+
return (date.fromisoformat(result[0]), date.fromisoformat(result[1]))
|
|
143
|
+
return value
|
|
144
|
+
else:
|
|
145
|
+
if isinstance(result, str):
|
|
146
|
+
return date.fromisoformat(result)
|
|
147
|
+
return value
|
|
@@ -20,6 +20,7 @@ def form_select(
|
|
|
20
20
|
style: Optional[Dict[str, Any]] = None,
|
|
21
21
|
class_name: str = "",
|
|
22
22
|
theme: Optional[Dict[str, Any]] = None,
|
|
23
|
+
defer_update: bool = False,
|
|
23
24
|
key: Optional[str] = None,
|
|
24
25
|
) -> str:
|
|
25
26
|
"""
|
|
@@ -36,7 +37,10 @@ def form_select(
|
|
|
36
37
|
class_name: Tailwind CSS classes
|
|
37
38
|
theme: Optional theme dictionary. If None, uses active global theme.
|
|
38
39
|
Set to False to disable theming for this component.
|
|
39
|
-
|
|
40
|
+
defer_update: If True, don't trigger Streamlit rerun on change.
|
|
41
|
+
Value is stored locally and sent on next rerun (e.g., Apply button).
|
|
42
|
+
Requires 'key' to be set.
|
|
43
|
+
key: Unique key for the component (required if defer_update=True)
|
|
40
44
|
|
|
41
45
|
Returns:
|
|
42
46
|
The currently selected value
|
|
@@ -57,6 +61,16 @@ def form_select(
|
|
|
57
61
|
{"label": "Scenarios", "options": ["Q2 Demand Surge"]}
|
|
58
62
|
]
|
|
59
63
|
)
|
|
64
|
+
|
|
65
|
+
# Deferred update (no rerun until Apply button clicked)
|
|
66
|
+
site = form_select(
|
|
67
|
+
label="Site",
|
|
68
|
+
options=["AML_14", "ADL", "Devens"],
|
|
69
|
+
defer_update=True,
|
|
70
|
+
key="site_select"
|
|
71
|
+
)
|
|
72
|
+
if st.button("Apply"):
|
|
73
|
+
st.rerun()
|
|
60
74
|
"""
|
|
61
75
|
# Resolve theme (None = use global, False = disable)
|
|
62
76
|
from ..themes import get_active_theme
|
|
@@ -73,6 +87,8 @@ def form_select(
|
|
|
73
87
|
style=style,
|
|
74
88
|
className=class_name,
|
|
75
89
|
theme=resolved_theme,
|
|
90
|
+
deferUpdate=defer_update,
|
|
91
|
+
componentKey=key,
|
|
76
92
|
key=key,
|
|
77
93
|
default=value,
|
|
78
94
|
)
|
|
@@ -23,6 +23,7 @@ def form_slider(
|
|
|
23
23
|
style: Optional[Dict[str, Any]] = None,
|
|
24
24
|
class_name: str = "",
|
|
25
25
|
theme: Optional[Dict[str, Any]] = None,
|
|
26
|
+
defer_update: bool = False,
|
|
26
27
|
key: Optional[str] = None,
|
|
27
28
|
) -> float:
|
|
28
29
|
"""
|
|
@@ -41,7 +42,10 @@ def form_slider(
|
|
|
41
42
|
class_name: Tailwind CSS classes
|
|
42
43
|
theme: Optional theme dictionary. If None, uses active global theme.
|
|
43
44
|
Set to False to disable theming for this component.
|
|
44
|
-
|
|
45
|
+
defer_update: If True, don't trigger Streamlit rerun on change.
|
|
46
|
+
Value is stored locally and sent on next rerun (e.g., Apply button).
|
|
47
|
+
Requires 'key' to be set.
|
|
48
|
+
key: Unique key for the component (required if defer_update=True)
|
|
45
49
|
|
|
46
50
|
Returns:
|
|
47
51
|
The current slider value
|
|
@@ -65,6 +69,18 @@ def form_slider(
|
|
|
65
69
|
max_val=100,
|
|
66
70
|
color="#ff5733"
|
|
67
71
|
)
|
|
72
|
+
|
|
73
|
+
# Deferred update (no rerun until Apply button clicked)
|
|
74
|
+
threshold = form_slider(
|
|
75
|
+
label="Threshold",
|
|
76
|
+
value=50,
|
|
77
|
+
min_val=0,
|
|
78
|
+
max_val=100,
|
|
79
|
+
defer_update=True,
|
|
80
|
+
key="threshold_slider"
|
|
81
|
+
)
|
|
82
|
+
if st.button("Apply"):
|
|
83
|
+
st.rerun()
|
|
68
84
|
"""
|
|
69
85
|
# Resolve theme (None = use global, False = disable)
|
|
70
86
|
from ..themes import get_active_theme
|
|
@@ -84,6 +100,8 @@ def form_slider(
|
|
|
84
100
|
style=style,
|
|
85
101
|
className=class_name,
|
|
86
102
|
theme=resolved_theme,
|
|
103
|
+
deferUpdate=defer_update,
|
|
104
|
+
componentKey=key,
|
|
87
105
|
key=key,
|
|
88
106
|
default=value,
|
|
89
107
|
)
|
|
@@ -19,6 +19,7 @@ def radio_group(
|
|
|
19
19
|
style: Optional[Dict[str, Any]] = None,
|
|
20
20
|
class_name: str = "",
|
|
21
21
|
theme: Optional[Dict[str, Any]] = None,
|
|
22
|
+
defer_update: bool = False,
|
|
22
23
|
key: Optional[str] = None,
|
|
23
24
|
) -> Optional[str]:
|
|
24
25
|
"""
|
|
@@ -36,7 +37,10 @@ def radio_group(
|
|
|
36
37
|
class_name: Tailwind CSS classes
|
|
37
38
|
theme: Optional theme dictionary. If None, uses active global theme.
|
|
38
39
|
Set to False to disable theming for this component.
|
|
39
|
-
|
|
40
|
+
defer_update: If True, don't trigger Streamlit rerun on change.
|
|
41
|
+
Value is stored locally and sent on next rerun (e.g., Apply button).
|
|
42
|
+
Requires 'key' to be set.
|
|
43
|
+
key: Unique key for the component (required if defer_update=True)
|
|
40
44
|
|
|
41
45
|
Returns:
|
|
42
46
|
ID of the selected item (string), or None if nothing selected
|
|
@@ -51,6 +55,16 @@ def radio_group(
|
|
|
51
55
|
]
|
|
52
56
|
)
|
|
53
57
|
# Returns: "credit" (only one can be selected)
|
|
58
|
+
|
|
59
|
+
# Deferred update (no rerun until Apply button clicked)
|
|
60
|
+
selected = radio_group(
|
|
61
|
+
label="Payment Method",
|
|
62
|
+
items=[...],
|
|
63
|
+
defer_update=True,
|
|
64
|
+
key="payment_radio"
|
|
65
|
+
)
|
|
66
|
+
if st.button("Apply"):
|
|
67
|
+
st.rerun()
|
|
54
68
|
"""
|
|
55
69
|
# Get default selected item (first checked item)
|
|
56
70
|
default_selected = None
|
|
@@ -73,6 +87,8 @@ def radio_group(
|
|
|
73
87
|
style=style,
|
|
74
88
|
className=class_name,
|
|
75
89
|
theme=resolved_theme,
|
|
90
|
+
deferUpdate=defer_update,
|
|
91
|
+
componentKey=key,
|
|
76
92
|
key=key,
|
|
77
93
|
default=default_selected,
|
|
78
94
|
)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Styled container component for wrapping native Streamlit components with Tailwind styling.
|
|
3
|
+
|
|
4
|
+
This module provides a context manager that enables Tailwind CSS-like styling
|
|
5
|
+
for native Streamlit components (st.slider, st.button, st.text_input, etc.).
|
|
6
|
+
|
|
7
|
+
Supports:
|
|
8
|
+
- All Tailwind utility classes
|
|
9
|
+
- Opacity modifiers (bg-blue-500/50)
|
|
10
|
+
- Hover, focus, and active variants
|
|
11
|
+
- Transitions and animations
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from contextlib import contextmanager
|
|
15
|
+
from typing import Dict, Any, Optional, Generator
|
|
16
|
+
import uuid
|
|
17
|
+
|
|
18
|
+
import streamlit as st
|
|
19
|
+
|
|
20
|
+
from .tailwind import parse_tailwind_classes
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@contextmanager
|
|
24
|
+
def styled_container(
|
|
25
|
+
*classes: str,
|
|
26
|
+
style: Optional[Dict[str, Any]] = None,
|
|
27
|
+
key: Optional[str] = None,
|
|
28
|
+
) -> Generator[None, None, None]:
|
|
29
|
+
"""
|
|
30
|
+
Context manager for styling native Streamlit components with Tailwind classes.
|
|
31
|
+
|
|
32
|
+
Wraps Streamlit components in a styled container with support for Tailwind CSS
|
|
33
|
+
utility classes, including hover:, focus:, and active: variants.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
*classes: Tailwind CSS class names (e.g., "bg-slate-800", "hover:bg-slate-700")
|
|
37
|
+
style: Optional dict of additional inline CSS properties to merge
|
|
38
|
+
key: Optional unique key for the container (auto-generated if not provided)
|
|
39
|
+
|
|
40
|
+
Yields:
|
|
41
|
+
None - use as a context manager with 'with' statement
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
Basic usage with Tailwind classes::
|
|
45
|
+
|
|
46
|
+
with styled_container("bg-slate-800", "border", "border-blue-500", "rounded-xl", "p-4"):
|
|
47
|
+
st.slider("Risk Threshold", 0, 100, 75)
|
|
48
|
+
st.button("Apply")
|
|
49
|
+
|
|
50
|
+
With hover and focus states::
|
|
51
|
+
|
|
52
|
+
with styled_container(
|
|
53
|
+
"bg-slate-800",
|
|
54
|
+
"border-2",
|
|
55
|
+
"border-slate-600",
|
|
56
|
+
"rounded-xl",
|
|
57
|
+
"p-6",
|
|
58
|
+
"transition-all",
|
|
59
|
+
"duration-200",
|
|
60
|
+
"hover:bg-slate-700",
|
|
61
|
+
"hover:border-blue-500",
|
|
62
|
+
"focus:border-blue-400"
|
|
63
|
+
):
|
|
64
|
+
st.text_input("Username")
|
|
65
|
+
st.text_input("Password", type="password")
|
|
66
|
+
st.button("Login")
|
|
67
|
+
|
|
68
|
+
With opacity modifiers::
|
|
69
|
+
|
|
70
|
+
with styled_container(
|
|
71
|
+
"bg-rose-500/20",
|
|
72
|
+
"border",
|
|
73
|
+
"border-rose-500/50",
|
|
74
|
+
"rounded-lg",
|
|
75
|
+
"p-4"
|
|
76
|
+
):
|
|
77
|
+
st.write("Rose-tinted container")
|
|
78
|
+
|
|
79
|
+
With gradient backgrounds::
|
|
80
|
+
|
|
81
|
+
with styled_container(
|
|
82
|
+
"bg-gradient-to-br",
|
|
83
|
+
"from-slate-800",
|
|
84
|
+
"to-slate-900",
|
|
85
|
+
"rounded-2xl",
|
|
86
|
+
"p-6"
|
|
87
|
+
):
|
|
88
|
+
st.metric("Revenue", "$12,450", "+8.2%")
|
|
89
|
+
|
|
90
|
+
With custom inline styles::
|
|
91
|
+
|
|
92
|
+
with styled_container(
|
|
93
|
+
"bg-slate-800",
|
|
94
|
+
"rounded-lg",
|
|
95
|
+
style={"min-height": "200px", "box-shadow": "0 0 20px rgba(59,130,246,0.3)"}
|
|
96
|
+
):
|
|
97
|
+
st.write("Custom styled container")
|
|
98
|
+
|
|
99
|
+
Note:
|
|
100
|
+
- The focus: variant uses :focus-within, which triggers when any child
|
|
101
|
+
element (like an input) receives focus
|
|
102
|
+
- Transitions work with hover/focus states for smooth animations
|
|
103
|
+
- Gradient classes (from-, to-, via-) work with bg-gradient-to-* classes
|
|
104
|
+
"""
|
|
105
|
+
# Generate unique container ID
|
|
106
|
+
container_id = key or f"stc-{uuid.uuid4().hex[:8]}"
|
|
107
|
+
|
|
108
|
+
# Parse classes into categorized CSS dicts
|
|
109
|
+
parsed = parse_tailwind_classes(classes)
|
|
110
|
+
|
|
111
|
+
# Merge with custom style dict
|
|
112
|
+
if style:
|
|
113
|
+
parsed["base"].update(style)
|
|
114
|
+
|
|
115
|
+
# Build CSS rules for each state
|
|
116
|
+
css_blocks = []
|
|
117
|
+
|
|
118
|
+
# Use a more specific selector that targets the container's parent block
|
|
119
|
+
selector = f'[data-testid="stVerticalBlock"]:has(> [data-stc-id="{container_id}"])'
|
|
120
|
+
|
|
121
|
+
# Base styles
|
|
122
|
+
if parsed["base"]:
|
|
123
|
+
base_css = "; ".join(f"{k}: {v}" for k, v in parsed["base"].items())
|
|
124
|
+
css_blocks.append(f"{selector} {{ {base_css} }}")
|
|
125
|
+
|
|
126
|
+
# Hover styles
|
|
127
|
+
if parsed["hover"]:
|
|
128
|
+
hover_css = "; ".join(f"{k}: {v}" for k, v in parsed["hover"].items())
|
|
129
|
+
css_blocks.append(f"{selector}:hover {{ {hover_css} }}")
|
|
130
|
+
|
|
131
|
+
# Focus styles (uses :focus-within for child focus)
|
|
132
|
+
if parsed["focus"]:
|
|
133
|
+
focus_css = "; ".join(f"{k}: {v}" for k, v in parsed["focus"].items())
|
|
134
|
+
css_blocks.append(f"{selector}:focus-within {{ {focus_css} }}")
|
|
135
|
+
|
|
136
|
+
# Active styles
|
|
137
|
+
if parsed["active"]:
|
|
138
|
+
active_css = "; ".join(f"{k}: {v}" for k, v in parsed["active"].items())
|
|
139
|
+
css_blocks.append(f"{selector}:active {{ {active_css} }}")
|
|
140
|
+
|
|
141
|
+
# Inject CSS and marker element
|
|
142
|
+
css_content = "\n ".join(css_blocks)
|
|
143
|
+
st.markdown(
|
|
144
|
+
f"""
|
|
145
|
+
<style>
|
|
146
|
+
{css_content}
|
|
147
|
+
</style>
|
|
148
|
+
<div data-stc-id="{container_id}" style="display:none;"></div>
|
|
149
|
+
""",
|
|
150
|
+
unsafe_allow_html=True,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
yield
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# Convenience function for inline style conversion
|
|
157
|
+
def css(*classes: str, **extra_styles: str) -> str:
|
|
158
|
+
"""
|
|
159
|
+
Convert Tailwind classes to an inline CSS string.
|
|
160
|
+
|
|
161
|
+
Useful for applying Tailwind-like styles to st.markdown HTML content.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
*classes: Tailwind CSS class names
|
|
165
|
+
**extra_styles: Additional CSS properties as keyword arguments
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
CSS string suitable for inline style attribute
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
>>> css("bg-slate-800", "p-4", "rounded-lg")
|
|
172
|
+
'background-color: #1e293b; padding: 1rem; border-radius: 0.5rem'
|
|
173
|
+
|
|
174
|
+
>>> css("text-blue-500", font_size="20px")
|
|
175
|
+
'color: #3b82f6; font-size: 20px'
|
|
176
|
+
|
|
177
|
+
With st.markdown::
|
|
178
|
+
|
|
179
|
+
st.markdown(
|
|
180
|
+
f'<div style="{css("bg-slate-800", "p-4", "rounded-lg")}">Content</div>',
|
|
181
|
+
unsafe_allow_html=True
|
|
182
|
+
)
|
|
183
|
+
"""
|
|
184
|
+
from .tailwind import tw
|
|
185
|
+
|
|
186
|
+
styles = tw(*classes)
|
|
187
|
+
|
|
188
|
+
# Add extra styles (convert underscores to hyphens)
|
|
189
|
+
for key, value in extra_styles.items():
|
|
190
|
+
css_key = key.replace("_", "-")
|
|
191
|
+
styles[css_key] = value
|
|
192
|
+
|
|
193
|
+
return "; ".join(f"{k}: {v}" for k, v in styles.items())
|