streamlit-react-components 1.0.5__py3-none-any.whl → 1.0.7__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 (25) hide show
  1. streamlit_react_components/__init__.py +5 -1
  2. streamlit_react_components/_frontend/index.css +1 -1
  3. streamlit_react_components/_frontend/index.js +143 -142
  4. streamlit_react_components/common/__init__.py +2 -0
  5. streamlit_react_components/common/button_group.py +10 -0
  6. streamlit_react_components/common/chart_legend.py +10 -0
  7. streamlit_react_components/common/data_table.py +10 -0
  8. streamlit_react_components/common/metric_row.py +10 -0
  9. streamlit_react_components/common/panel.py +10 -0
  10. streamlit_react_components/common/plotly_chart.py +10 -0
  11. streamlit_react_components/common/section_header.py +10 -0
  12. streamlit_react_components/common/smart_chart.py +584 -0
  13. streamlit_react_components/common/stat_card.py +10 -0
  14. streamlit_react_components/common/step_indicator.py +10 -0
  15. streamlit_react_components/form/__init__.py +2 -0
  16. streamlit_react_components/form/checkbox_group.py +10 -0
  17. streamlit_react_components/form/form_select.py +10 -0
  18. streamlit_react_components/form/form_slider.py +10 -0
  19. streamlit_react_components/form/radio_group.py +78 -0
  20. streamlit_react_components/themes.py +1203 -0
  21. {streamlit_react_components-1.0.5.dist-info → streamlit_react_components-1.0.7.dist-info}/METADATA +1 -1
  22. streamlit_react_components-1.0.7.dist-info/RECORD +25 -0
  23. {streamlit_react_components-1.0.5.dist-info → streamlit_react_components-1.0.7.dist-info}/WHEEL +1 -1
  24. streamlit_react_components-1.0.5.dist-info/RECORD +0 -22
  25. {streamlit_react_components-1.0.5.dist-info → streamlit_react_components-1.0.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,584 @@
1
+ """SmartChart component - Simplified chart creation for line, gauge, and waterfall charts."""
2
+
3
+ from typing import Dict, Any, Optional, List, Union
4
+ from .plotly_chart import plotly_chart, _dataframe_to_figure
5
+
6
+
7
+ def _dataframe_to_gauge(
8
+ data: Any,
9
+ value_column: str,
10
+ min_value: Optional[float],
11
+ max_value: Optional[float],
12
+ threshold_low: Optional[float],
13
+ threshold_medium: Optional[float],
14
+ threshold_high: Optional[float],
15
+ title: Optional[str],
16
+ ) -> Dict[str, Any]:
17
+ """Convert a DataFrame to a Plotly gauge indicator figure."""
18
+ from ..themes import get_active_theme
19
+
20
+ # Validate required column exists
21
+ if value_column not in data.columns:
22
+ raise ValueError(f"Column '{value_column}' not found in DataFrame. Available columns: {list(data.columns)}")
23
+
24
+ # Extract value (first row if multiple)
25
+ value = float(data[value_column].iloc[0])
26
+
27
+ # Auto-calculate min/max if not provided
28
+ if min_value is None:
29
+ min_value = 0
30
+ if max_value is None:
31
+ max_value = float(data[value_column].max() * 1.2)
32
+
33
+ # Validate threshold ordering
34
+ if threshold_low is not None and threshold_medium is not None and threshold_low >= threshold_medium:
35
+ raise ValueError("threshold_low must be less than threshold_medium")
36
+ if threshold_medium is not None and threshold_high is not None and threshold_medium >= threshold_high:
37
+ raise ValueError("threshold_medium must be less than threshold_high")
38
+
39
+ # Get theme colors
40
+ theme = get_active_theme()
41
+ primary = theme['colors']['primary']
42
+ success = theme['colors']['success']
43
+ warning = theme['colors']['warning']
44
+ error = theme['colors']['error']
45
+
46
+ # Build gauge configuration
47
+ gauge_config = {
48
+ 'axis': {'range': [min_value, max_value]},
49
+ 'bar': {'color': primary}
50
+ }
51
+
52
+ # Add colored threshold ranges
53
+ if threshold_low is not None or threshold_medium is not None or threshold_high is not None:
54
+ steps = []
55
+ if threshold_low is not None:
56
+ steps.append({'range': [min_value, threshold_low], 'color': error})
57
+ if threshold_medium is not None:
58
+ start = threshold_low if threshold_low is not None else min_value
59
+ steps.append({'range': [start, threshold_medium], 'color': warning})
60
+ if threshold_high is not None:
61
+ start = threshold_medium if threshold_medium is not None else min_value
62
+ steps.append({'range': [start, threshold_high], 'color': success})
63
+ # Add a final green band if there's space
64
+ if threshold_high < max_value:
65
+ steps.append({'range': [threshold_high, max_value], 'color': success})
66
+
67
+ gauge_config['steps'] = steps
68
+
69
+ return {
70
+ 'data': [{
71
+ 'type': 'indicator',
72
+ 'mode': 'gauge+number',
73
+ 'value': value,
74
+ 'title': {'text': title or ''},
75
+ 'gauge': gauge_config,
76
+ 'domain': {'x': [0, 1], 'y': [0, 1]}
77
+ }],
78
+ 'layout': {}
79
+ }
80
+
81
+
82
+ def _dataframe_to_waterfall(
83
+ data: Any,
84
+ category_column: str,
85
+ value_column: str,
86
+ measure_column: Optional[str],
87
+ title: Optional[str],
88
+ ) -> Dict[str, Any]:
89
+ """Convert a DataFrame to a Plotly waterfall figure."""
90
+ from ..themes import get_active_theme
91
+
92
+ # Validate required columns exist
93
+ if category_column not in data.columns:
94
+ raise ValueError(f"Column '{category_column}' not found in DataFrame. Available columns: {list(data.columns)}")
95
+ if value_column not in data.columns:
96
+ raise ValueError(f"Column '{value_column}' not found in DataFrame. Available columns: {list(data.columns)}")
97
+
98
+ categories = data[category_column].tolist()
99
+ values = data[value_column].tolist()
100
+
101
+ # Use measure_column if provided, otherwise auto-detect
102
+ if measure_column and measure_column in data.columns:
103
+ measures = data[measure_column].tolist()
104
+ else:
105
+ # Auto-detect: zero values are likely totals
106
+ measures = ['total' if v == 0 else 'relative' for v in values]
107
+
108
+ # Get theme colors
109
+ theme = get_active_theme()
110
+ success = theme['colors']['success']
111
+ error = theme['colors']['error']
112
+ info = theme['colors']['info']
113
+
114
+ return {
115
+ 'data': [{
116
+ 'type': 'waterfall',
117
+ 'x': categories,
118
+ 'y': values,
119
+ 'measure': measures,
120
+ 'connector': {'line': {'color': '#475569'}}, # slate-600
121
+ 'increasing': {'marker': {'color': success}},
122
+ 'decreasing': {'marker': {'color': error}},
123
+ 'totals': {'marker': {'color': info}},
124
+ }],
125
+ 'layout': {
126
+ 'title': title or '',
127
+ 'showlegend': False,
128
+ 'xaxis': {'title': category_column},
129
+ 'yaxis': {'title': 'Value'}
130
+ }
131
+ }
132
+
133
+
134
+ def _dataframe_to_scatter(
135
+ data: Any,
136
+ x: str,
137
+ y: str,
138
+ size: Optional[str] = None,
139
+ color_column: Optional[str] = None,
140
+ text: Optional[str] = None,
141
+ title: Optional[str] = None,
142
+ ) -> Dict[str, Any]:
143
+ """Convert DataFrame to Plotly scatter figure."""
144
+ from ..themes import get_active_theme
145
+
146
+ # Validate required columns
147
+ if x not in data.columns:
148
+ raise ValueError(f"Column '{x}' not found in DataFrame. Available columns: {list(data.columns)}")
149
+ if y not in data.columns:
150
+ raise ValueError(f"Column '{y}' not found in DataFrame. Available columns: {list(data.columns)}")
151
+
152
+ # Build marker config
153
+ marker_config = {}
154
+ if size and size in data.columns:
155
+ marker_config['size'] = data[size].tolist()
156
+ if color_column and color_column in data.columns:
157
+ marker_config['color'] = data[color_column].tolist()
158
+ marker_config['colorscale'] = 'Viridis'
159
+ marker_config['showscale'] = True
160
+
161
+ # Build hover text
162
+ hover_text = data[text].tolist() if text and text in data.columns else None
163
+
164
+ return {
165
+ 'data': [{
166
+ 'type': 'scatter',
167
+ 'mode': 'markers',
168
+ 'x': data[x].tolist(),
169
+ 'y': data[y].tolist(),
170
+ 'marker': marker_config,
171
+ 'text': hover_text,
172
+ 'hovertemplate': f'{x}: %{{x}}<br>{y}: %{{y}}<extra></extra>'
173
+ }],
174
+ 'layout': {
175
+ 'title': title or '',
176
+ 'xaxis': {'title': x},
177
+ 'yaxis': {'title': y}
178
+ }
179
+ }
180
+
181
+
182
+ def _dataframe_to_bar(
183
+ data: Any,
184
+ x: str,
185
+ y: Union[str, List[str]],
186
+ orientation: str = 'v',
187
+ barmode: str = 'group',
188
+ title: Optional[str] = None,
189
+ ) -> Dict[str, Any]:
190
+ """Convert DataFrame to Plotly bar figure."""
191
+ from ..themes import get_active_theme
192
+
193
+ theme = get_active_theme()
194
+ colors = [
195
+ theme['colors']['primary'],
196
+ theme['colors']['secondary'],
197
+ theme['colors']['success'],
198
+ theme['colors']['warning'],
199
+ theme['colors']['info']
200
+ ]
201
+
202
+ # Validate columns
203
+ if x not in data.columns:
204
+ raise ValueError(f"Column '{x}' not found in DataFrame. Available columns: {list(data.columns)}")
205
+
206
+ y_cols = [y] if isinstance(y, str) else y
207
+ for y_col in y_cols:
208
+ if y_col not in data.columns:
209
+ raise ValueError(f"Column '{y_col}' not found in DataFrame. Available columns: {list(data.columns)}")
210
+
211
+ traces = []
212
+ for idx, y_col in enumerate(y_cols):
213
+ if orientation == 'v':
214
+ trace = {
215
+ 'type': 'bar',
216
+ 'x': data[x].tolist(),
217
+ 'y': data[y_col].tolist(),
218
+ 'name': y_col,
219
+ 'marker': {'color': colors[idx % len(colors)]}
220
+ }
221
+ else: # horizontal
222
+ trace = {
223
+ 'type': 'bar',
224
+ 'x': data[y_col].tolist(),
225
+ 'y': data[x].tolist(),
226
+ 'orientation': 'h',
227
+ 'name': y_col,
228
+ 'marker': {'color': colors[idx % len(colors)]}
229
+ }
230
+ traces.append(trace)
231
+
232
+ return {
233
+ 'data': traces,
234
+ 'layout': {
235
+ 'title': title or '',
236
+ 'barmode': barmode,
237
+ 'xaxis': {'title': x if orientation == 'v' else 'Value'},
238
+ 'yaxis': {'title': 'Value' if orientation == 'v' else x}
239
+ }
240
+ }
241
+
242
+
243
+ def _dataframe_to_histogram(
244
+ data: Any,
245
+ x: str,
246
+ nbins: int = 30,
247
+ show_mean: bool = True,
248
+ title: Optional[str] = None,
249
+ ) -> Dict[str, Any]:
250
+ """Convert DataFrame to Plotly histogram figure."""
251
+ from ..themes import get_active_theme
252
+
253
+ if x not in data.columns:
254
+ raise ValueError(f"Column '{x}' not found in DataFrame. Available columns: {list(data.columns)}")
255
+
256
+ theme = get_active_theme()
257
+ primary = theme['colors']['primary']
258
+ success = theme['colors']['success']
259
+
260
+ figure = {
261
+ 'data': [{
262
+ 'type': 'histogram',
263
+ 'x': data[x].tolist(),
264
+ 'nbinsx': nbins,
265
+ 'marker': {'color': primary, 'opacity': 0.75},
266
+ 'name': 'Distribution'
267
+ }],
268
+ 'layout': {
269
+ 'title': title or f'{x} Distribution',
270
+ 'xaxis': {'title': x},
271
+ 'yaxis': {'title': 'Frequency'},
272
+ 'showlegend': True
273
+ }
274
+ }
275
+
276
+ # Add mean line if requested
277
+ if show_mean:
278
+ mean_val = float(data[x].mean())
279
+ figure['layout']['shapes'] = [{
280
+ 'type': 'line',
281
+ 'x0': mean_val,
282
+ 'x1': mean_val,
283
+ 'y0': 0,
284
+ 'y1': 1,
285
+ 'yref': 'paper',
286
+ 'line': {'color': success, 'dash': 'dash', 'width': 2}
287
+ }]
288
+ figure['layout']['annotations'] = [{
289
+ 'x': mean_val,
290
+ 'y': 1,
291
+ 'yref': 'paper',
292
+ 'text': f'Mean: {mean_val:.2f}',
293
+ 'showarrow': False,
294
+ 'yshift': 10
295
+ }]
296
+
297
+ return figure
298
+
299
+
300
+ def _dataframe_to_pie(
301
+ data: Any,
302
+ labels: str,
303
+ values: str,
304
+ hole: float = 0.0,
305
+ title: Optional[str] = None,
306
+ ) -> Dict[str, Any]:
307
+ """Convert DataFrame to Plotly pie/donut figure."""
308
+ from ..themes import get_active_theme
309
+
310
+ if labels not in data.columns:
311
+ raise ValueError(f"Column '{labels}' not found in DataFrame. Available columns: {list(data.columns)}")
312
+ if values not in data.columns:
313
+ raise ValueError(f"Column '{values}' not found in DataFrame. Available columns: {list(data.columns)}")
314
+
315
+ theme = get_active_theme()
316
+ colors = [
317
+ theme['colors']['primary'],
318
+ theme['colors']['secondary'],
319
+ theme['colors']['success'],
320
+ theme['colors']['warning'],
321
+ theme['colors']['info'],
322
+ theme['colors']['error']
323
+ ]
324
+
325
+ return {
326
+ 'data': [{
327
+ 'type': 'pie',
328
+ 'labels': data[labels].tolist(),
329
+ 'values': data[values].tolist(),
330
+ 'hole': hole,
331
+ 'marker': {'colors': colors}
332
+ }],
333
+ 'layout': {
334
+ 'title': title or ''
335
+ }
336
+ }
337
+
338
+
339
+ def smart_chart(
340
+ data: Any,
341
+ chart_type: str,
342
+ # Common chart parameters
343
+ x: Optional[str] = None,
344
+ y: Optional[Union[str, List[str]]] = None,
345
+ # Scatter plot parameters
346
+ size: Optional[str] = None,
347
+ text: Optional[str] = None,
348
+ # Bar chart parameters
349
+ orientation: str = 'v',
350
+ barmode: str = 'group',
351
+ # Histogram parameters
352
+ nbins: int = 30,
353
+ show_mean: bool = True,
354
+ # Pie chart parameters
355
+ labels: Optional[str] = None,
356
+ values: Optional[str] = None,
357
+ hole: float = 0.0,
358
+ # Gauge chart parameters
359
+ value_column: Optional[str] = None,
360
+ min_value: Optional[float] = None,
361
+ max_value: Optional[float] = None,
362
+ threshold_low: Optional[float] = None,
363
+ threshold_medium: Optional[float] = None,
364
+ threshold_high: Optional[float] = None,
365
+ # Waterfall chart parameters
366
+ category_column: Optional[str] = None,
367
+ value_column_waterfall: Optional[str] = None,
368
+ measure_column: Optional[str] = None,
369
+ # Common parameters
370
+ title: Optional[str] = None,
371
+ color: Optional[str] = None,
372
+ # Pass-through to plotly_chart
373
+ on_click: bool = False,
374
+ on_select: bool = False,
375
+ on_hover: bool = False,
376
+ on_relayout: bool = False,
377
+ expandable: bool = False,
378
+ modal_title: str = "",
379
+ # Styling
380
+ style: Optional[Dict[str, Any]] = None,
381
+ class_name: str = "",
382
+ theme: Optional[Dict[str, Any]] = None,
383
+ key: Optional[str] = None,
384
+ ) -> Optional[Dict[str, Any]]:
385
+ """
386
+ Create a smart chart that automatically converts DataFrames to Plotly charts.
387
+
388
+ This is a wrapper component that simplifies chart creation by transforming DataFrames
389
+ into appropriate Plotly figure configurations based on the specified chart_type.
390
+
391
+ Args:
392
+ data: pandas DataFrame containing the data to plot
393
+ chart_type: Type of chart to create - 'line', 'scatter', 'bar', 'bar_horizontal',
394
+ 'histogram', 'pie', 'gauge', or 'waterfall'
395
+
396
+ # Common Chart Parameters
397
+ x: Column name for x-axis (required for line, scatter, bar, histogram charts)
398
+ y: Column name(s) for y-axis - string or list of strings (required for line, scatter, bar charts)
399
+
400
+ # Scatter Plot Parameters
401
+ size: Column name for marker sizes (optional)
402
+ text: Column name for hover text (optional)
403
+
404
+ # Bar Chart Parameters
405
+ orientation: 'v' for vertical (default) or 'h' for horizontal
406
+ barmode: 'group' (default), 'stack', or 'overlay'
407
+
408
+ # Histogram Parameters
409
+ nbins: Number of bins (default: 30)
410
+ show_mean: Show mean line on histogram (default: True)
411
+
412
+ # Pie Chart Parameters
413
+ labels: Column name for pie slice labels (required for pie charts)
414
+ values: Column name for pie slice values (required for pie charts)
415
+ hole: Hole size for donut chart (0.0-1.0, default: 0.0 for pie)
416
+
417
+ # Gauge Chart Parameters
418
+ value_column: Column name containing the value to display (required for gauge charts)
419
+ min_value: Minimum value for gauge range (auto-calculated if not provided)
420
+ max_value: Maximum value for gauge range (auto-calculated if not provided)
421
+ threshold_low: Low threshold value (red zone below this)
422
+ threshold_medium: Medium threshold value (amber zone)
423
+ threshold_high: High threshold value (green zone above this)
424
+
425
+ # Waterfall Chart Parameters
426
+ category_column: Column name for categories (required for waterfall charts)
427
+ value_column_waterfall: Column name for values (required for waterfall charts)
428
+ measure_column: Optional column specifying 'relative' or 'total' for each row.
429
+ If not provided, auto-detects: zeros are 'total', non-zeros are 'relative'
430
+
431
+ # Common Parameters
432
+ title: Chart title
433
+ color: Column name for color grouping (line charts only)
434
+ on_click: Enable click events
435
+ on_select: Enable selection events (box/lasso)
436
+ on_hover: Enable hover events
437
+ on_relayout: Enable relayout events (zoom/pan)
438
+ expandable: Show expand button to open chart in full-page dialog
439
+ modal_title: Title displayed in dialog header when expanded
440
+ style: Inline CSS styles as a dictionary
441
+ class_name: Tailwind CSS classes
442
+ theme: Optional theme dictionary. If None, uses active global theme.
443
+ key: Unique key for the component
444
+
445
+ Returns:
446
+ Event dict with 'type' and event data, or None if no event.
447
+
448
+ Examples:
449
+ # Line Chart
450
+ import pandas as pd
451
+
452
+ df = pd.DataFrame({
453
+ 'month': ['Jan', 'Feb', 'Mar', 'Apr'],
454
+ 'revenue': [100, 150, 120, 180],
455
+ 'costs': [60, 90, 70, 100]
456
+ })
457
+
458
+ smart_chart(
459
+ data=df,
460
+ chart_type='line',
461
+ x='month',
462
+ y=['revenue', 'costs'],
463
+ title='Monthly Performance'
464
+ )
465
+
466
+ # Gauge Chart
467
+ df_gauge = pd.DataFrame({'conversion_rate': [73.5]})
468
+
469
+ smart_chart(
470
+ data=df_gauge,
471
+ chart_type='gauge',
472
+ value_column='conversion_rate',
473
+ min_value=0,
474
+ max_value=100,
475
+ threshold_low=30,
476
+ threshold_medium=70,
477
+ threshold_high=90,
478
+ title='Conversion Rate'
479
+ )
480
+
481
+ # Waterfall Chart
482
+ df_waterfall = pd.DataFrame({
483
+ 'category': ['Sales', 'Consulting', 'Net Revenue', 'Purchases', 'Other', 'Profit'],
484
+ 'amount': [60, 80, 0, -40, -20, 0],
485
+ 'measure': ['relative', 'relative', 'total', 'relative', 'relative', 'total']
486
+ })
487
+
488
+ smart_chart(
489
+ data=df_waterfall,
490
+ chart_type='waterfall',
491
+ category_column='category',
492
+ value_column_waterfall='amount',
493
+ measure_column='measure',
494
+ title='Revenue Breakdown'
495
+ )
496
+ """
497
+ # Validate DataFrame
498
+ if data is None or (hasattr(data, 'empty') and data.empty):
499
+ raise ValueError("DataFrame cannot be None or empty")
500
+
501
+ # Validate chart_type
502
+ valid_types = ['line', 'scatter', 'bar', 'bar_horizontal', 'histogram', 'pie', 'gauge', 'waterfall']
503
+ if chart_type not in valid_types:
504
+ raise ValueError(f"Invalid chart_type: '{chart_type}'. Must be one of: {', '.join(valid_types)}")
505
+
506
+ # Route to appropriate transformer and validate required parameters
507
+ if chart_type == 'scatter':
508
+ if x is None or y is None:
509
+ raise ValueError("Scatter chart requires 'x' and 'y' parameters")
510
+ figure = _dataframe_to_scatter(data, x, y, size, color, text, title)
511
+
512
+ elif chart_type in ['bar', 'bar_horizontal']:
513
+ if x is None or y is None:
514
+ raise ValueError("Bar chart requires 'x' and 'y' parameters")
515
+ orient = 'h' if chart_type == 'bar_horizontal' else 'v'
516
+ figure = _dataframe_to_bar(data, x, y, orient, barmode, title)
517
+
518
+ elif chart_type == 'histogram':
519
+ if x is None:
520
+ raise ValueError("Histogram requires 'x' parameter")
521
+ figure = _dataframe_to_histogram(data, x, nbins, show_mean, title)
522
+
523
+ elif chart_type == 'pie':
524
+ if labels is None or values is None:
525
+ raise ValueError("Pie chart requires 'labels' and 'values' parameters")
526
+ figure = _dataframe_to_pie(data, labels, values, hole, title)
527
+
528
+ elif chart_type == 'line':
529
+ if x is None or y is None:
530
+ raise ValueError("Line chart requires 'x' and 'y' parameters")
531
+
532
+ # Validate columns exist
533
+ if x not in data.columns:
534
+ raise ValueError(f"Column '{x}' not found in DataFrame. Available columns: {list(data.columns)}")
535
+
536
+ y_cols = [y] if isinstance(y, str) else y
537
+ for y_col in y_cols:
538
+ if y_col not in data.columns:
539
+ raise ValueError(f"Column '{y_col}' not found in DataFrame. Available columns: {list(data.columns)}")
540
+
541
+ # Use existing _dataframe_to_figure for line charts
542
+ figure = _dataframe_to_figure(data, x, y, color, 'line', title)
543
+
544
+ elif chart_type == 'gauge':
545
+ if value_column is None:
546
+ raise ValueError("Gauge chart requires 'value_column' parameter")
547
+
548
+ figure = _dataframe_to_gauge(
549
+ data,
550
+ value_column,
551
+ min_value,
552
+ max_value,
553
+ threshold_low,
554
+ threshold_medium,
555
+ threshold_high,
556
+ title
557
+ )
558
+
559
+ elif chart_type == 'waterfall':
560
+ if category_column is None or value_column_waterfall is None:
561
+ raise ValueError("Waterfall chart requires 'category_column' and 'value_column_waterfall' parameters")
562
+
563
+ figure = _dataframe_to_waterfall(
564
+ data,
565
+ category_column,
566
+ value_column_waterfall,
567
+ measure_column,
568
+ title
569
+ )
570
+
571
+ # Delegate to plotly_chart
572
+ return plotly_chart(
573
+ figure=figure,
574
+ on_click=on_click,
575
+ on_select=on_select,
576
+ on_hover=on_hover,
577
+ on_relayout=on_relayout,
578
+ expandable=expandable,
579
+ modal_title=modal_title or title or "",
580
+ style=style,
581
+ class_name=class_name,
582
+ theme=theme,
583
+ key=key,
584
+ )
@@ -25,6 +25,7 @@ def stat_card(
25
25
  action: Optional[Dict[str, str]] = None,
26
26
  style: Optional[Dict[str, Any]] = None,
27
27
  class_name: str = "",
28
+ theme: Optional[Dict[str, Any]] = None,
28
29
  key: Optional[str] = None,
29
30
  ) -> Optional[str]:
30
31
  """
@@ -52,6 +53,8 @@ def stat_card(
52
53
  - className: Optional Tailwind classes for custom styling
53
54
  style: Inline CSS styles as a dictionary
54
55
  class_name: Tailwind CSS classes
56
+ theme: Optional theme dictionary. If None, uses active global theme.
57
+ Set to False to disable theming for this component.
55
58
  key: Unique key for the component
56
59
 
57
60
  Returns:
@@ -116,6 +119,12 @@ def stat_card(
116
119
  }
117
120
  )
118
121
  """
122
+ # Resolve theme (None = use global, False = disable)
123
+ from ..themes import get_active_theme
124
+ resolved_theme = None
125
+ if theme is not False:
126
+ resolved_theme = theme if theme is not None else get_active_theme()
127
+
119
128
  return _component(
120
129
  component="stat_card",
121
130
  label=label,
@@ -130,6 +139,7 @@ def stat_card(
130
139
  action=action,
131
140
  style=style,
132
141
  className=class_name,
142
+ theme=resolved_theme,
133
143
  key=key,
134
144
  default=None,
135
145
  )
@@ -17,6 +17,7 @@ def step_indicator(
17
17
  current_step: int,
18
18
  style: Optional[Dict[str, Any]] = None,
19
19
  class_name: str = "",
20
+ theme: Optional[Dict[str, Any]] = None,
20
21
  key: Optional[str] = None,
21
22
  ) -> Optional[int]:
22
23
  """
@@ -27,6 +28,8 @@ def step_indicator(
27
28
  current_step: Current active step (1-indexed)
28
29
  style: Inline CSS styles as a dictionary
29
30
  class_name: Tailwind CSS classes
31
+ theme: Optional theme dictionary. If None, uses active global theme.
32
+ Set to False to disable theming for this component.
30
33
  key: Unique key for the component
31
34
 
32
35
  Returns:
@@ -40,12 +43,19 @@ def step_indicator(
40
43
  if step:
41
44
  st.session_state.step = step
42
45
  """
46
+ # Resolve theme (None = use global, False = disable)
47
+ from ..themes import get_active_theme
48
+ resolved_theme = None
49
+ if theme is not False:
50
+ resolved_theme = theme if theme is not None else get_active_theme()
51
+
43
52
  return _component(
44
53
  component="step_indicator",
45
54
  steps=steps,
46
55
  currentStep=current_step,
47
56
  style=style,
48
57
  className=class_name,
58
+ theme=resolved_theme,
49
59
  key=key,
50
60
  default=None,
51
61
  )
@@ -3,9 +3,11 @@
3
3
  from .form_select import form_select
4
4
  from .form_slider import form_slider
5
5
  from .checkbox_group import checkbox_group
6
+ from .radio_group import radio_group
6
7
 
7
8
  __all__ = [
8
9
  "form_select",
9
10
  "form_slider",
10
11
  "checkbox_group",
12
+ "radio_group",
11
13
  ]