|
| 1 | +"""Tier 1 canonical example — a design-tokens reference page. |
| 2 | +
|
| 3 | +Pure markup + CSS, no server. Demonstrates `Document`, `StyleSheet`, the |
| 4 | +`Style` fluent builder, the `Colors` named registry, `ElementSet.spawn` for |
| 5 | +generated grids, and the `Unit` types (px, rem, em, %). |
| 6 | +
|
| 7 | +Run: |
| 8 | +
|
| 9 | + python examples/01_static.py |
| 10 | +
|
| 11 | +Then open `01_static.html` in a browser. The script writes that file and |
| 12 | +`01_static.css` alongside itself. |
| 13 | +
|
| 14 | +Note: violetear's CSS selector parser only supports compound selectors |
| 15 | +(tag + classes + id + pseudo) — no descendant combinators. So every |
| 16 | +nested element gets its own flat class name (`.swatch-chip` instead of |
| 17 | +`.swatch .chip`). This is a real framework limitation worth tracking. |
| 18 | +""" |
| 19 | + |
| 20 | +from pathlib import Path |
| 21 | + |
| 22 | +from violetear import Document, HTML, Style, StyleSheet |
| 23 | +from violetear.color import Colors, hex |
| 24 | +from violetear.units import em, pc, px, rem |
| 25 | + |
| 26 | + |
| 27 | +# --------------------------------------------------------------------------- |
| 28 | +# Token tables |
| 29 | +# --------------------------------------------------------------------------- |
| 30 | + |
| 31 | +PALETTE = [ |
| 32 | + Colors.Coral, |
| 33 | + Colors.Tomato, |
| 34 | + Colors.Gold, |
| 35 | + Colors.Khaki, |
| 36 | + Colors.OliveDrab, |
| 37 | + Colors.ForestGreen, |
| 38 | + Colors.SeaGreen, |
| 39 | + Colors.Teal, |
| 40 | + Colors.SteelBlue, |
| 41 | + Colors.RoyalBlue, |
| 42 | + Colors.MidnightBlue, |
| 43 | + Colors.Indigo, |
| 44 | + Colors.BlueViolet, |
| 45 | + Colors.MediumOrchid, |
| 46 | + Colors.Crimson, |
| 47 | + Colors.DeepPink, |
| 48 | + Colors.Chocolate, |
| 49 | + Colors.SaddleBrown, |
| 50 | + Colors.Lavender, |
| 51 | + Colors.Silver, |
| 52 | + Colors.DimGray, |
| 53 | + Colors.Black, |
| 54 | +] |
| 55 | + |
| 56 | +# Typography demo: rendered as spans with size-variant classes so we don't |
| 57 | +# rely on a descendant combinator to style "sample h1" etc. |
| 58 | +TYPE_SCALE = [ |
| 59 | + ("type-h1", "Aa — Heading 1", "2.5rem · 700"), |
| 60 | + ("type-h2", "Aa — Heading 2", "2.0rem · 700"), |
| 61 | + ("type-h3", "Aa — Heading 3", "1.5rem · 600"), |
| 62 | + ("type-h4", "Aa — Heading 4", "1.25rem · 600"), |
| 63 | + ("type-h5", "Aa — Heading 5", "1.1rem · 600"), |
| 64 | + ("type-h6", "Aa — Heading 6", "1.0rem · 600"), |
| 65 | + ( |
| 66 | + "type-body", |
| 67 | + "Body — the quick brown fox jumps over the lazy dog.", |
| 68 | + "1.0rem · 400", |
| 69 | + ), |
| 70 | + ("type-small", "Small — annotations and metadata.", "0.875rem · 400"), |
| 71 | + ("type-caption", "Caption — image labels and footnotes.", "0.75rem · 400"), |
| 72 | +] |
| 73 | + |
| 74 | +SPACING_STEPS = [4, 8, 16, 24, 32, 48, 64] |
| 75 | + |
| 76 | +UNITS_DEMO = [ |
| 77 | + ("32px (absolute)", px(32)), |
| 78 | + ("2rem (root-relative)", rem(2.0)), |
| 79 | + ("2em (parent-relative)", em(2.0)), |
| 80 | + ("25% (container-relative)", pc(0.25)), |
| 81 | +] |
| 82 | + |
| 83 | + |
| 84 | +# --------------------------------------------------------------------------- |
| 85 | +# Stylesheet — flat class selectors only |
| 86 | +# --------------------------------------------------------------------------- |
| 87 | + |
| 88 | +sheet = StyleSheet(normalize=True) |
| 89 | + |
| 90 | +sheet.select("body").font( |
| 91 | + size=rem(1.0), family="system-ui, -apple-system, 'Segoe UI', sans-serif" |
| 92 | +).color(Colors.DarkSlateGray).background(Colors.WhiteSmoke).padding(rem(2.5)) |
| 93 | + |
| 94 | +sheet.select(".page").rules(max_width="960px").margin("auto").background( |
| 95 | + Colors.White |
| 96 | +).padding(rem(2.5)).rounded(px(8)).border(px(1), Colors.Gainsboro) |
| 97 | + |
| 98 | +sheet.select(".page-title").font(size=rem(2.5), weight=700).color(Colors.Indigo).margin( |
| 99 | + bottom=rem(0.25) |
| 100 | +) |
| 101 | + |
| 102 | +sheet.select(".subtitle").color(Colors.SlateGray).font(size=rem(1.0)).margin( |
| 103 | + bottom=rem(2.0) |
| 104 | +) |
| 105 | + |
| 106 | +sheet.select(".tokens-section").margin(top=rem(2.0)) |
| 107 | + |
| 108 | +sheet.select(".tokens-heading").font(size=rem(1.5), weight=600).color( |
| 109 | + Colors.DarkSlateGray |
| 110 | +).rules(border_bottom=f"2px solid {Colors.Lavender}").padding(bottom=rem(0.25)).margin( |
| 111 | + bottom=rem(1.0) |
| 112 | +) |
| 113 | + |
| 114 | +# Palette grid |
| 115 | +sheet.select(".palette").rules( |
| 116 | + display="grid", |
| 117 | + grid_template_columns="repeat(auto-fill, minmax(120px, 1fr))", |
| 118 | + gap="16px", |
| 119 | +) |
| 120 | +sheet.select(".swatch").flexbox(direction="column", gap=px(6)) |
| 121 | +sheet.select(".swatch-chip").rules( |
| 122 | + height="72px", border="1px solid rgba(0,0,0,0.08)" |
| 123 | +).rounded(px(6)) |
| 124 | +sheet.select(".swatch-name").font(size=rem(0.875), weight=600).color( |
| 125 | + Colors.DarkSlateGray |
| 126 | +) |
| 127 | +sheet.select(".swatch-hex").font( |
| 128 | + size=rem(0.75), family="ui-monospace, monospace" |
| 129 | +).color(Colors.SlateGray) |
| 130 | + |
| 131 | +# Typography rows — each row is sample text + spec |
| 132 | +sheet.select(".type-row").flexbox( |
| 133 | + direction="row", gap=px(24), align="baseline" |
| 134 | +).padding(top=rem(0.5), bottom=rem(0.5)).rules( |
| 135 | + border_bottom=f"1px solid {Colors.Gainsboro}" |
| 136 | +) |
| 137 | +sheet.select(".type-sample").rules(flex_grow=1) |
| 138 | +sheet.select(".type-rule").font(size=rem(0.8), family="ui-monospace, monospace").color( |
| 139 | + Colors.SlateGray |
| 140 | +) |
| 141 | +sheet.select(".type-h1").font(size=rem(2.5), weight=700).color(Colors.Indigo) |
| 142 | +sheet.select(".type-h2").font(size=rem(2.0), weight=700).color(Colors.Indigo) |
| 143 | +sheet.select(".type-h3").font(size=rem(1.5), weight=600).color(Colors.DarkSlateGray) |
| 144 | +sheet.select(".type-h4").font(size=rem(1.25), weight=600).color(Colors.DarkSlateGray) |
| 145 | +sheet.select(".type-h5").font(size=rem(1.1), weight=600).color(Colors.DarkSlateGray) |
| 146 | +sheet.select(".type-h6").font(size=rem(1.0), weight=600).color(Colors.DarkSlateGray) |
| 147 | +sheet.select(".type-body").font(size=rem(1.0), weight=400).color(Colors.DarkSlateGray) |
| 148 | +sheet.select(".type-small").font(size=rem(0.875), weight=400).color(Colors.SlateGray) |
| 149 | +sheet.select(".type-caption").font(size=rem(0.75), weight=400).color(Colors.SlateGray) |
| 150 | + |
| 151 | +# Spacing |
| 152 | +sheet.select(".spacing").flexbox(direction="column", gap=px(8)) |
| 153 | +sheet.select(".spacing-row").flexbox(direction="row", gap=px(12), align="center") |
| 154 | +sheet.select(".spacing-bar").rules(height="16px").background(Colors.Coral).rounded( |
| 155 | + px(2) |
| 156 | +) |
| 157 | +sheet.select(".spacing-label").font( |
| 158 | + size=rem(0.8), family="ui-monospace, monospace" |
| 159 | +).color(Colors.SlateGray).rules(min_width="60px") |
| 160 | + |
| 161 | +# Units |
| 162 | +sheet.select(".units").flexbox(direction="column", gap=px(8)) |
| 163 | +sheet.select(".units-row").flexbox(direction="row", gap=px(12), align="center") |
| 164 | +sheet.select(".units-bar").rules(height="16px").background(Colors.SteelBlue).rounded( |
| 165 | + px(2) |
| 166 | +) |
| 167 | +sheet.select(".units-label").font( |
| 168 | + size=rem(0.8), family="ui-monospace, monospace" |
| 169 | +).color(Colors.SlateGray).rules(min_width="160px") |
| 170 | + |
| 171 | + |
| 172 | +# --------------------------------------------------------------------------- |
| 173 | +# Document tree |
| 174 | +# --------------------------------------------------------------------------- |
| 175 | + |
| 176 | +doc = Document(title="violetear · Design Tokens") |
| 177 | +doc.style(href="01_static.css") |
| 178 | + |
| 179 | + |
| 180 | +def _populate_swatch(color, el): |
| 181 | + """Fill one swatch cell. When spawn() iterates an iterable, the first |
| 182 | + arg is the item itself (here a Color), not an integer index.""" |
| 183 | + el.classes("swatch").extend( |
| 184 | + HTML.div(classes="swatch-chip").style(Style().background(color)), |
| 185 | + HTML.div(text=color.name or "—", classes="swatch-name"), |
| 186 | + HTML.div(text=hex(color), classes="swatch-hex"), |
| 187 | + ) |
| 188 | + |
| 189 | + |
| 190 | +with doc.body as body: |
| 191 | + with body.div(classes="page") as page: |
| 192 | + page.h1(text="Design Tokens").classes("page-title") |
| 193 | + page.p( |
| 194 | + text="A reference page for the violetear named-color palette, type scale, spacing, and units." |
| 195 | + ).classes("subtitle") |
| 196 | + |
| 197 | + # Palette — generated grid via ElementSet.spawn. |
| 198 | + with page.div(classes="tokens-section") as palette_sec: |
| 199 | + palette_sec.h2(text="Palette").classes("tokens-heading") |
| 200 | + palette_grid = palette_sec.div(classes="palette") |
| 201 | + palette_grid.spawn(PALETTE, "div").each(_populate_swatch) |
| 202 | + |
| 203 | + # Typography — each row uses a size-variant class on the sample span. |
| 204 | + with page.div(classes="tokens-section") as type_sec: |
| 205 | + type_sec.h2(text="Typography").classes("tokens-heading") |
| 206 | + for variant, sample_text, rule in TYPE_SCALE: |
| 207 | + with type_sec.div(classes="type-row") as row: |
| 208 | + row.div(classes="type-sample").add( |
| 209 | + HTML.span(text=sample_text).classes(variant) |
| 210 | + ) |
| 211 | + row.div(text=rule, classes="type-rule") |
| 212 | + |
| 213 | + # Spacing |
| 214 | + with page.div(classes="tokens-section") as space_sec: |
| 215 | + space_sec.h2(text="Spacing").classes("tokens-heading") |
| 216 | + with space_sec.div(classes="spacing") as box: |
| 217 | + for step in SPACING_STEPS: |
| 218 | + with box.div(classes="spacing-row") as row: |
| 219 | + row.div(text=f"{step}px", classes="spacing-label") |
| 220 | + row.div(classes="spacing-bar").style(Style().width(px(step))) |
| 221 | + |
| 222 | + # Units |
| 223 | + with page.div(classes="tokens-section") as units_sec: |
| 224 | + units_sec.h2(text="Units").classes("tokens-heading") |
| 225 | + with units_sec.div(classes="units") as box: |
| 226 | + for label, unit in UNITS_DEMO: |
| 227 | + with box.div(classes="units-row") as row: |
| 228 | + row.div(text=label, classes="units-label") |
| 229 | + row.div(classes="units-bar").style(Style().width(unit)) |
| 230 | + |
| 231 | + |
| 232 | +# --------------------------------------------------------------------------- |
| 233 | +# Entry point |
| 234 | +# --------------------------------------------------------------------------- |
| 235 | + |
| 236 | + |
| 237 | +def write_to(out_dir: Path) -> tuple[Path, Path]: |
| 238 | + """Render the document and stylesheet to disk. Returns (html_path, css_path).""" |
| 239 | + html_path = out_dir / "01_static.html" |
| 240 | + css_path = out_dir / "01_static.css" |
| 241 | + doc.render(html_path) |
| 242 | + sheet.render(css_path) |
| 243 | + return html_path, css_path |
| 244 | + |
| 245 | + |
| 246 | +if __name__ == "__main__": |
| 247 | + html_path, css_path = write_to(Path(__file__).parent) |
| 248 | + print(f"Wrote {html_path}") |
| 249 | + print(f"Wrote {css_path}") |
0 commit comments