Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/app/news/+fixed-color-demo-contrast.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Keep labels readable in both themes in the styling guide’s Stack examples and the Foreach color swatches.
91 changes: 91 additions & 0 deletions docs/app/tests/test_fixed_palette_contrast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Fixed-color demo palettes must remain readable independently of the theme."""

import ast
import re
from pathlib import Path

DOCS = Path(__file__).resolve().parents[2]
PYTHON_BLOCK = re.compile(r"^```python[^\n]*\n(.*?)^```", re.DOTALL | re.MULTILINE)


def _trees(path: str) -> list[ast.Module]:
"""Parse the Python examples in a documentation page."""
return [
ast.parse(block) for block in PYTHON_BLOCK.findall((DOCS / path).read_text())
]


def _contrast(first: str, second: str) -> float:
"""Return the WCAG contrast ratio of two opaque hexadecimal sRGB colors."""

def luminance(color: str) -> float:
channels = [int(color[index : index + 2], 16) / 255 for index in (1, 3, 5)]
linear = [
value / 12.92 if value <= 0.04045 else ((value + 0.055) / 1.055) ** 2.4
for value in channels
]
return sum(
value * weight
for value, weight in zip(linear, (0.2126, 0.7152, 0.0722), strict=True)
)

light, dark = sorted((luminance(first), luminance(second)), reverse=True)
return (light + 0.05) / (dark + 0.05)


def test_foreach_swatch_palette_has_normal_text_contrast():
"""Every literal swatch, including nested dictionaries, supports its white label."""
trees = _trees("library/dynamic-rendering/foreach.md")
colors = {
node.value
for tree in trees
for node in ast.walk(tree)
if isinstance(node, ast.Constant)
and isinstance(node.value, str)
and re.fullmatch(r"#[0-9A-Fa-f]{6}", node.value)
}
assert len(colors) == 6
helper = next(
node
for tree in trees
for node in tree.body
if isinstance(node, ast.FunctionDef) and node.name == "color_swatch"
)
foregrounds = [
ast.literal_eval(keyword.value)
for node in ast.walk(helper)
if isinstance(node, ast.Call)
for keyword in node.keywords
if keyword.arg == "color"
]
assert foregrounds == ["white"]
assert {
color: round(_contrast(color, "#FFFFFF"), 2)
for color in colors
if _contrast(color, "#FFFFFF") < 4.5
} == {}


def test_styling_stack_pins_readable_foregrounds():
"""The four Stack boxes cannot inherit white text from the dark theme."""
boxes = []
for tree in _trees("styling/layout.md"):
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
values = {
keyword.arg: keyword.value.value
for keyword in node.keywords
if isinstance(keyword.value, ast.Constant)
}
if values.get("bg") in {"orange", "lightblue"}:
boxes.append((values["bg"], values.get("color")))
assert sorted(boxes) == [
("lightblue", "black"),
("lightblue", "black"),
("orange", "black"),
("orange", "black"),
]
assert all(
_contrast(background, "#000000") >= 4.5 for background in ("#FFA500", "#ADD8E6")
)
14 changes: 7 additions & 7 deletions docs/library/dynamic-rendering/foreach.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ This is useful for dynamically rendering a list of items defined in a state.
```python demo exec
class ForeachState(rx.State):
colors: list[str] = [
"#E5484D",
"#12A594",
"#CE2C31",
"#008573",
"#3E63DD",
"#AD5700",
"#F76B15",
"#CC4E00",
"#8E4EC6",
]

Expand Down Expand Up @@ -182,7 +182,7 @@ Using the color example, we can slightly modify the code to use dicts as shown b

```python demo exec
class SimpleDictForeachState(rx.State):
color_chart: dict[int, str] = {1: "#3E63DD", 2: "#E5484D", 3: "#12A594"}
color_chart: dict[int, str] = {1: "#3E63DD", 2: "#CE2C31", 3: "#008573"}


def display_color(color: rx.Var[tuple[str, str]]):
Expand All @@ -202,9 +202,9 @@ This example groups related hex colors in a dictionary and renders both the keys
```python demo exec
class ComplexDictForeachState(rx.State):
color_chart: dict[str, list[str]] = {
"#8E4EC6": ["#E5484D", "#3E63DD"],
"#F76B15": ["#AD5700", "#E5484D"],
"#12A594": ["#3E63DD", "#AD5700"],
"#8E4EC6": ["#CE2C31", "#3E63DD"],
"#CC4E00": ["#AD5700", "#CE2C31"],
"#008573": ["#3E63DD", "#AD5700"],
}


Expand Down
4 changes: 4 additions & 0 deletions docs/styling/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,14 @@ rx.flex(
rx.box(
"Example",
bg="orange",
color="black",
border_radius="3px",
width="20%",
),
rx.box(
"Example",
bg="lightblue",
color="black",
border_radius="3px",
width="30%",
),
Expand All @@ -67,12 +69,14 @@ rx.flex(
rx.box(
"Example",
bg="orange",
color="black",
border_radius="3px",
width="20%",
),
rx.box(
"Example",
bg="lightblue",
color="black",
border_radius="3px",
width="30%",
),
Expand Down
Loading