streamtex 0.2.0__tar.gz
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.
- streamtex-0.2.0/PKG-INFO +869 -0
- streamtex-0.2.0/README.md +311 -0
- streamtex-0.2.0/documentation/streamtex_cheatsheet_en.md +850 -0
- streamtex-0.2.0/pyproject.toml +64 -0
- streamtex-0.2.0/setup.cfg +4 -0
- streamtex-0.2.0/streamtex/__init__.py +88 -0
- streamtex-0.2.0/streamtex/__main__.py +70 -0
- streamtex-0.2.0/streamtex/auth.py +284 -0
- streamtex-0.2.0/streamtex/bib.py +1253 -0
- streamtex-0.2.0/streamtex/bib_preview.py +382 -0
- streamtex-0.2.0/streamtex/block_helpers.py +249 -0
- streamtex-0.2.0/streamtex/blocks.py +350 -0
- streamtex-0.2.0/streamtex/book.py +1049 -0
- streamtex-0.2.0/streamtex/code.py +73 -0
- streamtex-0.2.0/streamtex/collection.py +294 -0
- streamtex-0.2.0/streamtex/constants.py +4 -0
- streamtex-0.2.0/streamtex/container.py +102 -0
- streamtex-0.2.0/streamtex/enums.py +47 -0
- streamtex-0.2.0/streamtex/export.py +230 -0
- streamtex-0.2.0/streamtex/export_widgets.py +280 -0
- streamtex-0.2.0/streamtex/grid.py +189 -0
- streamtex-0.2.0/streamtex/gsheet.py +340 -0
- streamtex-0.2.0/streamtex/image.py +130 -0
- streamtex-0.2.0/streamtex/image_utils.py +49 -0
- streamtex-0.2.0/streamtex/inspector.py +918 -0
- streamtex-0.2.0/streamtex/link_preview.py +181 -0
- streamtex-0.2.0/streamtex/list.py +190 -0
- streamtex-0.2.0/streamtex/marker.py +499 -0
- streamtex-0.2.0/streamtex/mermaid.py +218 -0
- streamtex-0.2.0/streamtex/overlay.py +79 -0
- streamtex-0.2.0/streamtex/plantuml.py +248 -0
- streamtex-0.2.0/streamtex/search.py +124 -0
- streamtex-0.2.0/streamtex/space.py +34 -0
- streamtex-0.2.0/streamtex/static/default.css +138 -0
- streamtex-0.2.0/streamtex/styles/__init__.py +28 -0
- streamtex-0.2.0/streamtex/styles/base.py +66 -0
- streamtex-0.2.0/streamtex/styles/container.py +389 -0
- streamtex-0.2.0/streamtex/styles/core.py +316 -0
- streamtex-0.2.0/streamtex/styles/text.py +290 -0
- streamtex-0.2.0/streamtex/styles/visibility.py +13 -0
- streamtex-0.2.0/streamtex/tikz.py +321 -0
- streamtex-0.2.0/streamtex/toc.py +142 -0
- streamtex-0.2.0/streamtex/utils.py +33 -0
- streamtex-0.2.0/streamtex/write.py +130 -0
- streamtex-0.2.0/streamtex/zoom.py +95 -0
- streamtex-0.2.0/streamtex.egg-info/PKG-INFO +869 -0
- streamtex-0.2.0/streamtex.egg-info/SOURCES.txt +76 -0
- streamtex-0.2.0/streamtex.egg-info/dependency_links.txt +1 -0
- streamtex-0.2.0/streamtex.egg-info/requires.txt +12 -0
- streamtex-0.2.0/streamtex.egg-info/top_level.txt +1 -0
- streamtex-0.2.0/tests/test_auth.py +281 -0
- streamtex-0.2.0/tests/test_bib.py +895 -0
- streamtex-0.2.0/tests/test_book_integration.py +263 -0
- streamtex-0.2.0/tests/test_code.py +179 -0
- streamtex-0.2.0/tests/test_collection.py +325 -0
- streamtex-0.2.0/tests/test_container.py +694 -0
- streamtex-0.2.0/tests/test_enums.py +25 -0
- streamtex-0.2.0/tests/test_export.py +298 -0
- streamtex-0.2.0/tests/test_export_guard.py +70 -0
- streamtex-0.2.0/tests/test_export_widgets.py +391 -0
- streamtex-0.2.0/tests/test_grid.py +543 -0
- streamtex-0.2.0/tests/test_gsheet.py +256 -0
- streamtex-0.2.0/tests/test_image.py +276 -0
- streamtex-0.2.0/tests/test_inspector.py +870 -0
- streamtex-0.2.0/tests/test_lazy_blocks.py +324 -0
- streamtex-0.2.0/tests/test_list.py +538 -0
- streamtex-0.2.0/tests/test_load_atomic.py +55 -0
- streamtex-0.2.0/tests/test_marker.py +230 -0
- streamtex-0.2.0/tests/test_mermaid.py +215 -0
- streamtex-0.2.0/tests/test_overlay.py +259 -0
- streamtex-0.2.0/tests/test_plantuml.py +212 -0
- streamtex-0.2.0/tests/test_space.py +312 -0
- streamtex-0.2.0/tests/test_styles.py +245 -0
- streamtex-0.2.0/tests/test_tikz.py +229 -0
- streamtex-0.2.0/tests/test_toc.py +111 -0
- streamtex-0.2.0/tests/test_utils.py +128 -0
- streamtex-0.2.0/tests/test_write.py +65 -0
- streamtex-0.2.0/tests/test_zoom.py +172 -0
streamtex-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: streamtex
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A Streamlit wrapper for styled content rendering
|
|
5
|
+
License: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: streamlit>=1.54.0
|
|
9
|
+
Requires-Dist: beautifulsoup4>=4.10.0
|
|
10
|
+
Requires-Dist: requests>=2.28.0
|
|
11
|
+
Requires-Dist: watchdog
|
|
12
|
+
Requires-Dist: graphviz>=0.21
|
|
13
|
+
Requires-Dist: matplotlib>=3.10.8
|
|
14
|
+
Requires-Dist: streamlit-mermaid>=0.3.0
|
|
15
|
+
Requires-Dist: mermaid-py>=0.5.0
|
|
16
|
+
Requires-Dist: streamlit-ace>=0.1.1
|
|
17
|
+
Provides-Extra: inspector
|
|
18
|
+
Requires-Dist: streamlit-ace>=0.1.1; extra == "inspector"
|
|
19
|
+
|
|
20
|
+
# StreamTeX Complete Cheatsheet
|
|
21
|
+
|
|
22
|
+
## Essential Imports
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
# Block files (mandatory)
|
|
26
|
+
import streamlit as st
|
|
27
|
+
from streamtex import *
|
|
28
|
+
import streamtex as stx
|
|
29
|
+
from streamtex.styles import Style as ns, StyleGrid as sg
|
|
30
|
+
from streamtex.enums import Tags as t, ListTypes as lt
|
|
31
|
+
from custom.styles import Styles as s
|
|
32
|
+
|
|
33
|
+
# Book entry point (book.py)
|
|
34
|
+
import streamlit as st
|
|
35
|
+
import setup
|
|
36
|
+
import streamtex as stx
|
|
37
|
+
from streamtex import st_book, TOCConfig, MarkerConfig
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from custom.styles import Styles as s
|
|
40
|
+
from custom.themes import dark
|
|
41
|
+
import streamtex.styles as sts
|
|
42
|
+
import blocks
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Style Organization
|
|
46
|
+
|
|
47
|
+
### Custom Style Class
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
class BlockStyles:
|
|
51
|
+
"""Custom styles defined locally and used only for this block"""
|
|
52
|
+
# Composed styles
|
|
53
|
+
content = s.Large + s.center_txt
|
|
54
|
+
lime_bold = s.text.colors.lime + s.bold
|
|
55
|
+
bold_green = s.project.colors.green_01 + s.bold
|
|
56
|
+
|
|
57
|
+
# Styles with alignment
|
|
58
|
+
green_title = bold_green + s.huge + s.center_txt
|
|
59
|
+
|
|
60
|
+
# Styles with borders
|
|
61
|
+
border = s.container.borders.color(s.text.colors.black) + \
|
|
62
|
+
s.container.borders.solid_border + \
|
|
63
|
+
s.container.borders.size("2px")
|
|
64
|
+
|
|
65
|
+
# Styles with padding
|
|
66
|
+
side_padding = ns("padding: 10pt 36pt;")
|
|
67
|
+
bs = BlockStyles
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Style Composition
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
# Create from CSS string
|
|
74
|
+
my_style = Style("color: red; font-weight: bold;", "my_style")
|
|
75
|
+
|
|
76
|
+
# Copy existing style with new ID
|
|
77
|
+
my_title = Style.create(s.bold + s.Large + s.center_txt, "my_title")
|
|
78
|
+
|
|
79
|
+
# Compose with + operator
|
|
80
|
+
heading = s.huge + s.bold + s.project.colors.primary
|
|
81
|
+
|
|
82
|
+
# Remove properties with - operator
|
|
83
|
+
no_bold = heading - s.bold
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Basic Elements
|
|
87
|
+
|
|
88
|
+
### Blocks and Text
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
# Simple block with style
|
|
92
|
+
with st_block(s.center_txt):
|
|
93
|
+
st_write(bs.green_title, "My Title")
|
|
94
|
+
st_space(size=3)
|
|
95
|
+
|
|
96
|
+
# Block with list
|
|
97
|
+
with st_block(s.center_txt):
|
|
98
|
+
with st_list(
|
|
99
|
+
list_type=lt.ordered,
|
|
100
|
+
li_style=bs.content) as l:
|
|
101
|
+
with l.item(): st_write("First item")
|
|
102
|
+
with l.item(): st_write("Second item")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### st_write — Full Signature
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
st_write(
|
|
109
|
+
*args, # Style objects, text, or (Style, text) tuples
|
|
110
|
+
tag=t.span, # HTML tag: t.div, t.span, t.h1, t.p, t.section...
|
|
111
|
+
link="", # Optional hyperlink URL
|
|
112
|
+
no_link_decor=False, # Remove underline from links
|
|
113
|
+
toc_lvl=None, # TOC level: "1", "+1", "-1"
|
|
114
|
+
label="", # Custom TOC entry label
|
|
115
|
+
marker=None, # Per-heading marker control (True/False/None=auto)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Inline mixed styles — ONE st_write with tuples (multiple calls stack vertically!)
|
|
119
|
+
st_write(s.Large, (s.text.colors.red, "Red "), (s.text.colors.blue, "Blue"))
|
|
120
|
+
|
|
121
|
+
# Register in TOC
|
|
122
|
+
st_write(bs.title, "Section Title", tag=t.div, toc_lvl="1")
|
|
123
|
+
st_write(bs.subtitle, "Subsection", toc_lvl="+1")
|
|
124
|
+
|
|
125
|
+
# Exclude a heading from marker navigation
|
|
126
|
+
st_write(s.huge, "Appendix", toc_lvl="1", marker=False)
|
|
127
|
+
# Force include in markers
|
|
128
|
+
st_write(s.huge, "Important", toc_lvl="2", marker=True)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Images and Media
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
# Simple image
|
|
135
|
+
st_image(uri="image.png")
|
|
136
|
+
|
|
137
|
+
# Image with dimensions
|
|
138
|
+
st_image(uri="image.png", width="1150px", height="735.34px")
|
|
139
|
+
|
|
140
|
+
# Image with link
|
|
141
|
+
st_image(uri="image.png", link="https://example.com")
|
|
142
|
+
|
|
143
|
+
# Image with auto-height style
|
|
144
|
+
st_image(s.container.sizes.height_auto, uri="image.png")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Grids and Tables (Responsive-First)
|
|
148
|
+
|
|
149
|
+
Multi-column grids MUST use responsive patterns so columns stack on narrow screens.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
# GOOD — responsive 2-column (stacks below 350px per column)
|
|
153
|
+
with st_grid(
|
|
154
|
+
cols=s.project.containers.responsive_2col,
|
|
155
|
+
grid_style=s.project.containers.gap_24,
|
|
156
|
+
) as g:
|
|
157
|
+
with g.cell(): st_write("Panel A")
|
|
158
|
+
with g.cell(): st_write("Panel B")
|
|
159
|
+
|
|
160
|
+
# GOOD — responsive 3-column
|
|
161
|
+
with st_grid(cols=s.project.containers.responsive_3col) as g:
|
|
162
|
+
with g.cell(): st_write("Card 1")
|
|
163
|
+
with g.cell(): st_write("Card 2")
|
|
164
|
+
with g.cell(): st_write("Card 3")
|
|
165
|
+
|
|
166
|
+
# GOOD — responsive card grid
|
|
167
|
+
with st_grid(cols=s.project.containers.responsive_cards) as g:
|
|
168
|
+
with g.cell(): st_image(uri="image1.png")
|
|
169
|
+
with g.cell(): st_image(uri="image2.png")
|
|
170
|
+
with g.cell(): st_image(uri="image3.png")
|
|
171
|
+
|
|
172
|
+
# Responsive presets (defined in custom/styles.py):
|
|
173
|
+
# responsive_2col = "repeat(auto-fit, minmax(350px, 1fr))"
|
|
174
|
+
# responsive_3col = "repeat(auto-fit, minmax(280px, 1fr))"
|
|
175
|
+
# responsive_cards = "repeat(auto-fit, minmax(200px, 1fr))"
|
|
176
|
+
|
|
177
|
+
# BAD — fixed columns, never wraps on narrow screens
|
|
178
|
+
# st_grid(cols=2)
|
|
179
|
+
# st_grid(cols="2fr 3fr")
|
|
180
|
+
|
|
181
|
+
# OK — fixed columns ONLY for data tables with known column count
|
|
182
|
+
with st_grid(cols=2, cell_styles=bs.border) as g:
|
|
183
|
+
with g.cell(): st_write("Header 1")
|
|
184
|
+
with g.cell(): st_write("Header 2")
|
|
185
|
+
|
|
186
|
+
# Grid (table) with per-cell styles
|
|
187
|
+
with st_grid(
|
|
188
|
+
cols=2,
|
|
189
|
+
cell_styles=sg.create("A1,A3", s.project.colors.orange_02) +
|
|
190
|
+
sg.create("A2", s.project.colors.red_01) +
|
|
191
|
+
sg.create("A1:B3", s.bold + s.LARGE)
|
|
192
|
+
) as g:
|
|
193
|
+
with g.cell(): st_write("Title")
|
|
194
|
+
with g.cell(): st_write("Link")
|
|
195
|
+
with g.cell(): st_write("Item 1")
|
|
196
|
+
with g.cell(): st_write("link1")
|
|
197
|
+
with g.cell(): st_write("Item 2")
|
|
198
|
+
with g.cell(): st_write("link2")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Overlays (Absolute Positioning)
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
with st_overlay() as ov:
|
|
205
|
+
with ov.layer(top=10, left=20):
|
|
206
|
+
st_write(s.large, "Positioned at top:10px left:20px")
|
|
207
|
+
with ov.layer(top=50, right=20):
|
|
208
|
+
st_image(uri="badge.png", width="80px")
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Links and Navigation
|
|
212
|
+
|
|
213
|
+
### Links
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
# Simple link
|
|
217
|
+
st_write("Click here", link="https://example.com")
|
|
218
|
+
|
|
219
|
+
# Styled link
|
|
220
|
+
link_style = s.text.colors.blue + s.text.decors.underline_text
|
|
221
|
+
st_write(link_style, "Styled link", link="https://example.com", no_link_decor=True)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Table of Contents
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
# Register headings
|
|
228
|
+
st_write(style, "Section", toc_lvl="1")
|
|
229
|
+
st_write(style, "Subsection", toc_lvl="+1")
|
|
230
|
+
|
|
231
|
+
# TOCConfig — full options
|
|
232
|
+
toc = TOCConfig(
|
|
233
|
+
numerate_titles=False, # Auto-numbering of headings
|
|
234
|
+
toc_position=0, # 0=start, -1=end, None=no TOC
|
|
235
|
+
title_style=s.project.titles.section_title + s.center_txt,
|
|
236
|
+
content_style=s.large + s.text.colors.reset,
|
|
237
|
+
sidebar_max_level=None, # None = auto (paginated: 1, continuous: 2)
|
|
238
|
+
search=True, # Full-text search in sidebar
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Marker Navigation
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from streamtex import st_marker, MarkerConfig, st_book
|
|
246
|
+
|
|
247
|
+
# Place markers manually
|
|
248
|
+
st_marker("Section Start", visible=True) # Visible marker (dashed border + label)
|
|
249
|
+
st_marker("Hidden Waypoint") # Invisible marker (default)
|
|
250
|
+
|
|
251
|
+
# Auto-markers from TOC headings (in book.py)
|
|
252
|
+
marker_config = MarkerConfig(
|
|
253
|
+
auto_marker_on_toc=1, # Level-1 TOC headings become markers
|
|
254
|
+
next_keys=["PageDown"], # Navigate forward
|
|
255
|
+
prev_keys=["PageUp"], # Navigate backward
|
|
256
|
+
)
|
|
257
|
+
st_book([...], marker_config=marker_config)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Predefined Styles
|
|
261
|
+
|
|
262
|
+
### Text Colors
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
s.text.colors.red # 140+ CSS named colors available
|
|
266
|
+
s.text.colors.lime
|
|
267
|
+
s.text.colors.alice_blue
|
|
268
|
+
s.text.colors.reset # color: initial
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Text Sizes
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
# Title sizes
|
|
275
|
+
s.GIANT # 196pt s.Giant # 128pt s.giant # 112pt
|
|
276
|
+
s.Huge # 96pt s.huge # 80pt
|
|
277
|
+
# Header sizes
|
|
278
|
+
s.LARGE # 64pt s.Large # 48pt s.large # 32pt
|
|
279
|
+
# Body sizes
|
|
280
|
+
s.big # 24pt s.medium # 16pt s.little # 12pt
|
|
281
|
+
s.small # 8pt s.tiny # 4pt
|
|
282
|
+
# Dynamic sizes
|
|
283
|
+
s.text.sizes.size(20, "custom_20pt") # Factory method
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Alignment and Layout
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
s.center_txt # text-align: center
|
|
290
|
+
s.text.alignments.right_align # text-align: right
|
|
291
|
+
s.text.alignments.justify_align # text-align: justify
|
|
292
|
+
s.container.flex.center_align_items # align-items: center
|
|
293
|
+
s.container.layouts.vertical_center_layout # Flex centered both axes
|
|
294
|
+
s.container.layouts.center # width: fit-content + auto margin
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Decorations
|
|
298
|
+
|
|
299
|
+
```python
|
|
300
|
+
s.bold # font-weight: bold
|
|
301
|
+
s.italic # font-style: italic
|
|
302
|
+
s.text.decors.underline_text # text-decoration: underline
|
|
303
|
+
s.text.decors.strike_text # text-decoration: line-through
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Container Styles
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
# Padding
|
|
310
|
+
s.container.paddings.little_padding # 9pt
|
|
311
|
+
s.container.paddings.small_padding # 6pt
|
|
312
|
+
s.container.paddings.medium_padding # 12pt
|
|
313
|
+
s.container.paddings.size("10px", "20px", style_id="custom_pad") # Factory
|
|
314
|
+
|
|
315
|
+
# Borders
|
|
316
|
+
s.container.borders.solid_border
|
|
317
|
+
s.container.borders.dashed_border
|
|
318
|
+
s.container.borders.size("2px") # Factory
|
|
319
|
+
s.container.borders.color(s.text.colors.blue) # Factory
|
|
320
|
+
|
|
321
|
+
# Background colors
|
|
322
|
+
s.container.bg_colors.red_bg # 140+ named background colors
|
|
323
|
+
s.container.bg_colors.reset_bg # background-color: initial
|
|
324
|
+
|
|
325
|
+
# Flex
|
|
326
|
+
s.container.flex.flex # display: flex
|
|
327
|
+
s.container.flex.center_flex # flex + center both axes
|
|
328
|
+
s.container.flex.space_between_justify # justify-content: space-between
|
|
329
|
+
|
|
330
|
+
# Sizes
|
|
331
|
+
s.container.sizes.width_full # width: 100%
|
|
332
|
+
s.container.sizes.height_auto # height: auto
|
|
333
|
+
|
|
334
|
+
# Lists
|
|
335
|
+
s.container.lists.g_docs # Google Docs symbols
|
|
336
|
+
s.container.lists.ordered_lowercase # lower-alpha list
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Book Orchestration
|
|
340
|
+
|
|
341
|
+
### book.py Pattern
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
import streamlit as st
|
|
345
|
+
import setup
|
|
346
|
+
import streamtex as stx
|
|
347
|
+
from streamtex import st_book, TOCConfig, MarkerConfig
|
|
348
|
+
from pathlib import Path
|
|
349
|
+
from custom.styles import Styles as s
|
|
350
|
+
from custom.themes import dark
|
|
351
|
+
import streamtex.styles as sts
|
|
352
|
+
import blocks
|
|
353
|
+
|
|
354
|
+
# Configure static sources
|
|
355
|
+
stx.set_static_sources([str(Path(__file__).parent / "static")])
|
|
356
|
+
|
|
357
|
+
# Page configuration
|
|
358
|
+
st.set_page_config(
|
|
359
|
+
page_title="My Project",
|
|
360
|
+
layout="wide",
|
|
361
|
+
initial_sidebar_state="collapsed",
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# Inject dark theme
|
|
365
|
+
sts.theme = dark
|
|
366
|
+
|
|
367
|
+
# TOC + Markers
|
|
368
|
+
toc = TOCConfig(numerate_titles=False, toc_position=0, search=True)
|
|
369
|
+
marker_config = MarkerConfig(auto_marker_on_toc=1, next_keys=["PageDown"], prev_keys=["PageUp"])
|
|
370
|
+
|
|
371
|
+
# Orchestrate blocks
|
|
372
|
+
st_book(
|
|
373
|
+
[
|
|
374
|
+
blocks.bck_01_welcome,
|
|
375
|
+
blocks.bck_02_content,
|
|
376
|
+
],
|
|
377
|
+
toc_config=toc,
|
|
378
|
+
marker_config=marker_config,
|
|
379
|
+
paginate=True,
|
|
380
|
+
inspector=stx.InspectorConfig(enabled=True),
|
|
381
|
+
)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### st_book — Full Signature
|
|
385
|
+
|
|
386
|
+
```python
|
|
387
|
+
st_book(
|
|
388
|
+
module_list, # List of block modules
|
|
389
|
+
toc_config=None, # TOCConfig object
|
|
390
|
+
marker_config=None, # MarkerConfig object
|
|
391
|
+
separator=None, # Module rendered between blocks (optional)
|
|
392
|
+
export=True, # Enable HTML export
|
|
393
|
+
export_title="StreamTeX Export",
|
|
394
|
+
paginate=False, # One block per page
|
|
395
|
+
bib_sources=None, # List of .bib/.json/.ris paths
|
|
396
|
+
bib_config=None, # BibConfig for bibliography
|
|
397
|
+
inspector=None, # InspectorConfig for block inspector
|
|
398
|
+
page_width=90, # Page width as % of browser width (default 90)
|
|
399
|
+
)
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### InspectorConfig
|
|
403
|
+
|
|
404
|
+
```python
|
|
405
|
+
import streamtex as stx
|
|
406
|
+
|
|
407
|
+
# Enable the block inspector panel
|
|
408
|
+
st_book([...], inspector=stx.InspectorConfig(enabled=True))
|
|
409
|
+
|
|
410
|
+
# Full options
|
|
411
|
+
stx.InspectorConfig(
|
|
412
|
+
enabled=True,
|
|
413
|
+
password=None, # Optional password protection
|
|
414
|
+
panel_width="35vw", # Right panel width
|
|
415
|
+
backup=True, # Create .bak files before saving
|
|
416
|
+
)
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Block Infrastructure
|
|
420
|
+
|
|
421
|
+
### Block Registry — blocks/__init__.py
|
|
422
|
+
|
|
423
|
+
```python
|
|
424
|
+
"""Blocks package — lazy-loaded via streamtex.ProjectBlockRegistry."""
|
|
425
|
+
from pathlib import Path
|
|
426
|
+
from streamtex import ProjectBlockRegistry, BlockNotFoundError, BlockImportError
|
|
427
|
+
|
|
428
|
+
registry = ProjectBlockRegistry(Path(__file__).parent)
|
|
429
|
+
__all__ = ["registry", "BlockNotFoundError", "BlockImportError"]
|
|
430
|
+
|
|
431
|
+
def __getattr__(name: str):
|
|
432
|
+
try:
|
|
433
|
+
return registry.get(name)
|
|
434
|
+
except (BlockNotFoundError, BlockImportError) as e:
|
|
435
|
+
raise AttributeError(str(e)) from e
|
|
436
|
+
|
|
437
|
+
def __dir__():
|
|
438
|
+
return sorted(registry.list_blocks() + __all__)
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### LazyBlockRegistry — Shared Blocks
|
|
442
|
+
|
|
443
|
+
```python
|
|
444
|
+
# In book.py — load blocks from external directories
|
|
445
|
+
shared_path = str(Path(__file__).parent.parent / "shared-blocks" / "blocks")
|
|
446
|
+
shared_blocks = stx.LazyBlockRegistry([shared_path])
|
|
447
|
+
|
|
448
|
+
st_book([
|
|
449
|
+
shared_blocks.bck_header, # From shared library
|
|
450
|
+
blocks.bck_content, # From local blocks/
|
|
451
|
+
shared_blocks.bck_footer, # From shared library
|
|
452
|
+
])
|
|
453
|
+
|
|
454
|
+
# Multi-source with priority (first source wins)
|
|
455
|
+
shared = stx.LazyBlockRegistry([
|
|
456
|
+
"blocks/overrides", # Checked first (project overrides)
|
|
457
|
+
"../../shared-blocks/blocks", # Checked second (originals)
|
|
458
|
+
])
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Composite Blocks (Atomic Sub-blocks)
|
|
462
|
+
|
|
463
|
+
```python
|
|
464
|
+
# Composite block: loads atomic sub-blocks from _atomic/ subfolder
|
|
465
|
+
import streamtex as stx
|
|
466
|
+
from streamtex import st_include
|
|
467
|
+
|
|
468
|
+
bck_text_basics = stx.load_atomic_block("bck_text_basics", __file__)
|
|
469
|
+
bck_text_styles = stx.load_atomic_block("bck_text_styles", __file__)
|
|
470
|
+
|
|
471
|
+
class BlockStyles:
|
|
472
|
+
pass
|
|
473
|
+
|
|
474
|
+
def build():
|
|
475
|
+
st_include(bck_text_basics)
|
|
476
|
+
st_include(bck_text_styles)
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
- `load_atomic_block(name, __file__)` loads `_atomic/{name}.py` relative to caller
|
|
480
|
+
- Raises `BlockNotFoundError` / `BlockImportError` on failure
|
|
481
|
+
|
|
482
|
+
### Static Asset Resolution
|
|
483
|
+
|
|
484
|
+
```python
|
|
485
|
+
import streamtex as stx
|
|
486
|
+
from pathlib import Path
|
|
487
|
+
|
|
488
|
+
# Single source
|
|
489
|
+
stx.set_static_sources([str(Path(__file__).parent / "static")])
|
|
490
|
+
|
|
491
|
+
# Multi-source with priority (first directory containing the file wins)
|
|
492
|
+
stx.set_static_sources([
|
|
493
|
+
str(Path(__file__).parent / "static"), # Local (highest priority)
|
|
494
|
+
str(Path(__file__).parent.parent / "shared-blocks" / "static"), # Shared fallback
|
|
495
|
+
])
|
|
496
|
+
|
|
497
|
+
# Manual resolution
|
|
498
|
+
path = stx.resolve_static("logo.png") # Searches each source in order
|
|
499
|
+
# st_image() calls resolve_static() internally
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
## Block Helpers
|
|
503
|
+
|
|
504
|
+
### Config Injection Pattern (Recommended)
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
# blocks/helpers.py — inject project styles globally
|
|
508
|
+
from streamtex import (
|
|
509
|
+
BlockHelperConfig, set_block_helper_config,
|
|
510
|
+
show_code as _show_code,
|
|
511
|
+
show_explanation as _show_explanation,
|
|
512
|
+
show_details as _show_details,
|
|
513
|
+
)
|
|
514
|
+
from custom.styles import Styles as s
|
|
515
|
+
|
|
516
|
+
class ProjectBlockHelperConfig(BlockHelperConfig):
|
|
517
|
+
def get_code_style(self):
|
|
518
|
+
return s.project.containers.code_box
|
|
519
|
+
def get_explanation_style(self):
|
|
520
|
+
return s.project.containers.explanation_box
|
|
521
|
+
def get_details_style(self):
|
|
522
|
+
return s.project.containers.details_box
|
|
523
|
+
|
|
524
|
+
set_block_helper_config(ProjectBlockHelperConfig())
|
|
525
|
+
|
|
526
|
+
# Convenience wrappers
|
|
527
|
+
def show_code(code_string: str, language: str = "python", line_numbers: bool = True):
|
|
528
|
+
return _show_code(code_string, language, line_numbers)
|
|
529
|
+
|
|
530
|
+
def show_explanation(text: str):
|
|
531
|
+
return _show_explanation(text)
|
|
532
|
+
|
|
533
|
+
def show_details(text: str):
|
|
534
|
+
return _show_details(text)
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Standalone Functions
|
|
538
|
+
|
|
539
|
+
```python
|
|
540
|
+
from streamtex import show_code, show_explanation, show_details
|
|
541
|
+
|
|
542
|
+
show_code("print('hello')") # Uses injected config style
|
|
543
|
+
show_code("print('hello')", style=s.custom.style) # Override with explicit style
|
|
544
|
+
show_explanation("This explains the concept...")
|
|
545
|
+
show_details("Additional details here...")
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### OOP Inheritance (Advanced)
|
|
549
|
+
|
|
550
|
+
```python
|
|
551
|
+
from streamtex import BlockHelper
|
|
552
|
+
|
|
553
|
+
class ProjectBlockHelper(BlockHelper):
|
|
554
|
+
def show_comparison(self, before: str, after: str):
|
|
555
|
+
self.show_code(before)
|
|
556
|
+
self.show_code(after)
|
|
557
|
+
|
|
558
|
+
helper = ProjectBlockHelper()
|
|
559
|
+
helper.show_comparison(old_code, new_code)
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## Raw HTML (`st_html`)
|
|
563
|
+
|
|
564
|
+
Use `stx.st_html()` when you need to render raw HTML content (bar charts, decorative rules, embedded iframes). It routes through the dual-rendering pipeline (live + export buffer) and auto-injects `font-family: Source Sans Pro` in iframes.
|
|
565
|
+
|
|
566
|
+
```python
|
|
567
|
+
# Inline HTML (height=0, default) — renders via st.html()
|
|
568
|
+
stx.st_html('<hr style="border:none;height:3px;">')
|
|
569
|
+
|
|
570
|
+
# Iframe HTML (height>0) — renders via components.html() with auto font injection
|
|
571
|
+
stx.st_html('<div>chart content</div>', height=400)
|
|
572
|
+
|
|
573
|
+
# Iframe with scrolling
|
|
574
|
+
stx.st_html('<div>long content</div>', height=600, scrolling=True)
|
|
575
|
+
|
|
576
|
+
# Light background (force white bg in dark mode)
|
|
577
|
+
stx.st_html('<svg>...</svg>', height=300, light_bg=True)
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**Parameters:**
|
|
581
|
+
- `html` — HTML string to render
|
|
582
|
+
- `height` — When > 0, render in an iframe with explicit pixel height (default: 0 = inline)
|
|
583
|
+
- `light_bg` — Force light color-scheme in the iframe (default: False)
|
|
584
|
+
- `scrolling` — Enable scrolling in the iframe (default: False)
|
|
585
|
+
|
|
586
|
+
## Export-Aware Widgets
|
|
587
|
+
|
|
588
|
+
Use `stx.st_*` wrappers instead of raw `st.*` calls for data visualization — they appear in both the live app AND the HTML export.
|
|
589
|
+
|
|
590
|
+
### Charts
|
|
591
|
+
|
|
592
|
+
```python
|
|
593
|
+
stx.st_line_chart(data, x="col_x", y="col_y")
|
|
594
|
+
stx.st_bar_chart(data, x="Category", y="Value")
|
|
595
|
+
stx.st_area_chart(data)
|
|
596
|
+
stx.st_scatter_chart(data, x="x", y="y")
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Tables & Data
|
|
600
|
+
|
|
601
|
+
```python
|
|
602
|
+
stx.st_dataframe(df, use_container_width=True) # Interactive table
|
|
603
|
+
stx.st_table(data) # Static table
|
|
604
|
+
stx.st_json({"key": "value"}) # JSON viewer
|
|
605
|
+
stx.st_metric("Revenue", "$1M", delta="+5%") # KPI metric
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Diagrams
|
|
609
|
+
|
|
610
|
+
```python
|
|
611
|
+
# Graphviz (DOT language)
|
|
612
|
+
stx.st_graphviz('digraph { A -> B -> C }')
|
|
613
|
+
|
|
614
|
+
# Mermaid (interactive zoom/pan support)
|
|
615
|
+
stx.st_mermaid("""
|
|
616
|
+
graph TD
|
|
617
|
+
A[Start] --> B{Decision}
|
|
618
|
+
B -->|Yes| C[OK]
|
|
619
|
+
B -->|No| D[End]
|
|
620
|
+
""")
|
|
621
|
+
|
|
622
|
+
# Mermaid with options
|
|
623
|
+
stx.st_mermaid(code, style=my_style, light_bg=True, height=500)
|
|
624
|
+
|
|
625
|
+
# Mermaid fit modes (initial zoom on first render)
|
|
626
|
+
stx.st_mermaid(code, fit="contain") # default: fit entire diagram in viewport
|
|
627
|
+
stx.st_mermaid(code, fit="width") # fill viewport width
|
|
628
|
+
stx.st_mermaid(code, fit="none") # natural size (scale 1)
|
|
629
|
+
|
|
630
|
+
# TikZ (requires LaTeX + Ghostscript)
|
|
631
|
+
stx.st_tikz(r"""
|
|
632
|
+
\begin{tikzpicture}
|
|
633
|
+
\draw (0,0) -- (1,1) -- (2,0) -- cycle;
|
|
634
|
+
\end{tikzpicture}
|
|
635
|
+
""", preamble=r"\usepackage{tikz}")
|
|
636
|
+
|
|
637
|
+
# PlantUML (server-rendered, configurable)
|
|
638
|
+
stx.st_plantuml("""
|
|
639
|
+
@startuml
|
|
640
|
+
Alice -> Bob: Authentication Request
|
|
641
|
+
Bob --> Alice: Authentication Response
|
|
642
|
+
@enduml
|
|
643
|
+
""")
|
|
644
|
+
|
|
645
|
+
# PlantUML with options
|
|
646
|
+
stx.st_plantuml(code, style=my_style, light_bg=True, height=500,
|
|
647
|
+
server="https://www.plantuml.com/plantuml")
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
### Audio & Video
|
|
651
|
+
|
|
652
|
+
```python
|
|
653
|
+
stx.st_audio("path/to/audio.wav", format="audio/wav")
|
|
654
|
+
stx.st_video("path/to/video.mp4")
|
|
655
|
+
stx.st_video("https://www.youtube.com/watch?v=...")
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
### Generic Fallback
|
|
659
|
+
|
|
660
|
+
```python
|
|
661
|
+
# For any widget not covered above
|
|
662
|
+
with stx.st_export('<p>Fallback HTML for export</p>'):
|
|
663
|
+
st.plotly_chart(fig)
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
> **Note:** Interactive widgets (`st.button`, `st.slider`, `st.selectbox`) have no static representation and are absent from the export.
|
|
667
|
+
|
|
668
|
+
## Zoom Control
|
|
669
|
+
|
|
670
|
+
```python
|
|
671
|
+
import streamtex as stx
|
|
672
|
+
|
|
673
|
+
# Zoom is managed automatically by st_book().
|
|
674
|
+
# Two independent sidebar controls: Width % and Zoom % (pure CSS, no JavaScript).
|
|
675
|
+
|
|
676
|
+
# If calling manually:
|
|
677
|
+
stx.add_zoom_options() # Defaults: width=100%, zoom=100%
|
|
678
|
+
stx.add_zoom_options(default_page_width=80) # Start at 80% width
|
|
679
|
+
stx.add_zoom_options(default_page_width=80, default_zoom=125) # 80% width, 125% zoom
|
|
680
|
+
|
|
681
|
+
# Low-level injection (rarely needed):
|
|
682
|
+
stx.inject_zoom_logic(100, 100) # Width 100%, Zoom 100%
|
|
683
|
+
stx.inject_zoom_logic(80, 150) # Width 80%, Zoom 150%
|
|
684
|
+
stx.inject_zoom_logic(120, 50) # Width 120%, Zoom 50%
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
## Bibliography
|
|
688
|
+
|
|
689
|
+
```python
|
|
690
|
+
from streamtex.bib import BibConfig, BibFormat, CitationStyle
|
|
691
|
+
|
|
692
|
+
# Configure bibliography
|
|
693
|
+
bib_config = BibConfig(
|
|
694
|
+
format=BibFormat.APA,
|
|
695
|
+
citation_style=CitationStyle.AUTHOR_YEAR,
|
|
696
|
+
hover_enabled=True, # Hover preview of citations
|
|
697
|
+
hover_show_abstract=True,
|
|
698
|
+
)
|
|
699
|
+
|
|
700
|
+
# Load sources (supports .bib, .json, .ris, .csl-json)
|
|
701
|
+
bib_sources = ["references.bib"]
|
|
702
|
+
|
|
703
|
+
# Pass to st_book
|
|
704
|
+
st_book([...], bib_sources=bib_sources, bib_config=bib_config)
|
|
705
|
+
|
|
706
|
+
# In-text citations (inside blocks)
|
|
707
|
+
from streamtex.bib import st_cite, st_bibliography
|
|
708
|
+
st_cite("author2024key") # Inline citation widget
|
|
709
|
+
st_bibliography() # Render full bibliography
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
## Collection System (Multi-Project Hub)
|
|
713
|
+
|
|
714
|
+
### collection.toml
|
|
715
|
+
|
|
716
|
+
```toml
|
|
717
|
+
[collection]
|
|
718
|
+
title = "My Course Library"
|
|
719
|
+
description = "A collection of StreamTeX courses"
|
|
720
|
+
cards_per_row = 2
|
|
721
|
+
|
|
722
|
+
[projects.intro]
|
|
723
|
+
title = "Introduction Course"
|
|
724
|
+
description = "Learn the basics"
|
|
725
|
+
cover = "static/images/covers/intro.png"
|
|
726
|
+
project_url = "http://localhost:8502"
|
|
727
|
+
order = 1
|
|
728
|
+
|
|
729
|
+
[projects.advanced]
|
|
730
|
+
title = "Advanced Course"
|
|
731
|
+
description = "Master advanced concepts"
|
|
732
|
+
cover = "static/images/covers/advanced.png"
|
|
733
|
+
project_url = "http://localhost:8503"
|
|
734
|
+
order = 2
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### Automatic Collection UI
|
|
738
|
+
|
|
739
|
+
```python
|
|
740
|
+
from streamtex import st_collection, CollectionConfig
|
|
741
|
+
|
|
742
|
+
config = CollectionConfig.from_toml("collection.toml")
|
|
743
|
+
st_collection(config=config, home_styles=s)
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### Custom Collection with st_book
|
|
747
|
+
|
|
748
|
+
```python
|
|
749
|
+
# For full control over the collection UI
|
|
750
|
+
st_book([
|
|
751
|
+
blocks.bck_home, # Custom home page with cards
|
|
752
|
+
blocks.bck_management, # Documentation
|
|
753
|
+
], toc_config=toc, paginate=False)
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
## Google Sheets Import
|
|
757
|
+
|
|
758
|
+
```python
|
|
759
|
+
from streamtex import GSheetConfig, set_gsheet_config, load_gsheet, load_gsheet_df
|
|
760
|
+
|
|
761
|
+
# Configure
|
|
762
|
+
config = GSheetConfig(...)
|
|
763
|
+
set_gsheet_config(config)
|
|
764
|
+
|
|
765
|
+
# Load as module or DataFrame
|
|
766
|
+
block_module = load_gsheet("path/to/source")
|
|
767
|
+
df = load_gsheet_df("path/to/source")
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
## Utilities
|
|
771
|
+
|
|
772
|
+
### Spacing
|
|
773
|
+
|
|
774
|
+
```python
|
|
775
|
+
st_space(size=3) # 3em vertical space
|
|
776
|
+
st_space("v", size=2) # 2em vertical space
|
|
777
|
+
st_space("h", size=1) # 1em horizontal space
|
|
778
|
+
st_space("h", size="40px") # 40px horizontal space
|
|
779
|
+
st_br() # Line break
|
|
780
|
+
st_br(count=3) # 3 line breaks
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### Containers
|
|
784
|
+
|
|
785
|
+
```python
|
|
786
|
+
# Styled block container (vertical)
|
|
787
|
+
with st_block(s.center_txt):
|
|
788
|
+
st_write("Centered content")
|
|
789
|
+
|
|
790
|
+
# Inline styled container
|
|
791
|
+
with st_span(s.bold + s.text.colors.red):
|
|
792
|
+
st_write("Inline bold red")
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
## Project Structure
|
|
796
|
+
|
|
797
|
+
```
|
|
798
|
+
project_name/
|
|
799
|
+
book.py # Entry point
|
|
800
|
+
setup.py # PATH setup
|
|
801
|
+
blocks/
|
|
802
|
+
__init__.py # ProjectBlockRegistry
|
|
803
|
+
bck_*.py # Each block: BlockStyles + build()
|
|
804
|
+
helpers.py # Block helper configuration
|
|
805
|
+
_atomic/ # Atomic sub-blocks (optional)
|
|
806
|
+
custom/
|
|
807
|
+
styles.py # Project styles (extends StxStyles)
|
|
808
|
+
themes.py # Dark theme overrides (dict)
|
|
809
|
+
static/images/ # Image assets
|
|
810
|
+
.streamlit/config.toml # enableStaticServing = true
|
|
811
|
+
```
|
|
812
|
+
|
|
813
|
+
## Custom Styles Pattern (custom/styles.py)
|
|
814
|
+
|
|
815
|
+
```python
|
|
816
|
+
from streamtex.styles import Style, Text, Container, StxStyles
|
|
817
|
+
|
|
818
|
+
class ColorsCustom:
|
|
819
|
+
primary = Style("color: #4A90D9;", "primary")
|
|
820
|
+
accent = Style("color: #2EC4B6;", "accent")
|
|
821
|
+
|
|
822
|
+
class BackgroundsCustom:
|
|
823
|
+
callout_bg = Style("background-color: rgba(74, 144, 217, 0.12);", "callout_bg")
|
|
824
|
+
|
|
825
|
+
class TextStylesCustom:
|
|
826
|
+
course_title = Style.create(
|
|
827
|
+
ColorsCustom.primary + Text.weights.bold_weight + Text.sizes.Giant_size,
|
|
828
|
+
"course_title"
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
class ContainerStylesCustom:
|
|
832
|
+
callout = Style.create(
|
|
833
|
+
BackgroundsCustom.callout_bg
|
|
834
|
+
+ Container.borders.solid_border
|
|
835
|
+
+ Style("border-color: #4A90D9; border-width: 0 0 0 4px;", "callout_border")
|
|
836
|
+
+ Container.paddings.medium_padding,
|
|
837
|
+
"callout"
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
class Custom:
|
|
841
|
+
colors = ColorsCustom
|
|
842
|
+
backgrounds = BackgroundsCustom
|
|
843
|
+
titles = TextStylesCustom
|
|
844
|
+
containers = ContainerStylesCustom
|
|
845
|
+
|
|
846
|
+
class Styles(StxStyles):
|
|
847
|
+
project = Custom
|
|
848
|
+
```
|
|
849
|
+
|
|
850
|
+
## Theme Overrides (custom/themes.py)
|
|
851
|
+
|
|
852
|
+
```python
|
|
853
|
+
# Keys are style_ids, values are replacement CSS strings
|
|
854
|
+
dark = {
|
|
855
|
+
"primary": "color: #7AB8F5;",
|
|
856
|
+
"course_title": "color: #7AB8F5; font-weight: bold; font-size: 128pt;",
|
|
857
|
+
"callout_bg": "background-color: rgba(74, 144, 217, 0.20);",
|
|
858
|
+
}
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
## Tips and Best Practices
|
|
862
|
+
|
|
863
|
+
1. Group common styles in a `BlockStyles` class — one per block
|
|
864
|
+
2. ONE `st_write()` with tuples for inline mixed-style text (multiple calls stack vertically)
|
|
865
|
+
3. Never hardcode black/white — let Streamlit handle Light/Dark mode
|
|
866
|
+
4. No raw HTML/CSS — use Style composition
|
|
867
|
+
5. Use `stx.*` for content, `st.*` only for interactivity (buttons, sliders, inputs)
|
|
868
|
+
6. One generic style, reused everywhere — no duplicates
|
|
869
|
+
7. Use `tag=t.div` for block-level elements, default `t.span` for inline
|