Skip to main content

programmatic/
programmatic.rs

1//! Build a minimal token spec entirely in Rust (no JSON) and render it to CSS.
2//!
3//! Run with `cargo run --example programmatic`.
4
5#![allow(clippy::too_many_lines)]
6
7use std::collections::BTreeMap;
8use std::error::Error;
9
10use tangible::color::{ColorDef, ColorSpace, GradientDef, GradientStop, InterpolationMode};
11use tangible::spec::{
12    Borders, Elevation, Fonts, Glow, Gradient, InkConfig, Scales, SemanticValue, Shadows,
13    TextShadowElevation,
14};
15use tangible::{Renderer, Spec};
16
17fn main() -> Result<(), Box<dyn Error>> {
18    let mut colors = BTreeMap::new();
19    colors.insert(
20        "neutral".into(),
21        ColorDef::Explicit(vec!["#f5f5f5".into(), "#888888".into(), "#1a1a1a".into()]),
22    );
23    colors.insert(
24        "primary".into(),
25        ColorDef::Gradient(GradientDef {
26            stops: vec![
27                GradientStop::Color("#b3f0e6".into()),
28                GradientStop::Color("#00c9a7".into()),
29                GradientStop::Color("#061e1c".into()),
30            ],
31            mode: InterpolationMode::Linear,
32            blend: ColorSpace::Oklab,
33        }),
34    );
35
36    let mut semantic = BTreeMap::new();
37    semantic.insert(
38        "bg".into(),
39        SemanticValue::PaletteRef("neutral".into(), 900),
40    );
41    semantic.insert(
42        "text".into(),
43        SemanticValue::PaletteRef("neutral".into(), 100),
44    );
45    semantic.insert(
46        "interactive".into(),
47        SemanticValue::PaletteRef("primary".into(), 500),
48    );
49
50    let mut shadow_elevations = BTreeMap::new();
51    shadow_elevations.insert(
52        "low".into(),
53        Elevation {
54            layers: 1,
55            offsets: vec![(0.0, 1.0)],
56            opacity: 0.1,
57        },
58    );
59
60    let mut shadow_colors = BTreeMap::new();
61    shadow_colors.insert("neutral".into(), "220 10% 10%".into());
62
63    let mut text_shadows = BTreeMap::new();
64    text_shadows.insert(
65        "medium".into(),
66        TextShadowElevation {
67            blur: vec![2, 4],
68            opacity: 0.4,
69        },
70    );
71
72    let mut glows = BTreeMap::new();
73    glows.insert(
74        "primary".into(),
75        Glow {
76            color: "hsla(170, 100%, 40%, {a})".into(),
77            radii: vec![4, 8, 16],
78        },
79    );
80
81    let mut transitions = BTreeMap::new();
82    transitions.insert("default".into(), "150ms ease".into());
83
84    let mut z = BTreeMap::new();
85    z.insert("base".into(), serde_json::json!(0));
86
87    let mut opacity = BTreeMap::new();
88    opacity.insert("disabled".into(), 0.4);
89
90    let mut letter_spacing = BTreeMap::new();
91    letter_spacing.insert("tight".into(), "-0.01em".into());
92
93    let mut families = BTreeMap::new();
94    families.insert("sans".into(), vec!["Inter".into(), "sans-serif".into()]);
95
96    let mut radius = BTreeMap::new();
97    radius.insert("sm".into(), "4px".into());
98    radius.insert("md".into(), "8px".into());
99    radius.insert("lg".into(), "16px".into());
100
101    let spec = Spec {
102        scales: Scales {
103            shades: vec![100, 500, 900],
104            sizes: vec!["sm".into(), "md".into(), "lg".into()],
105            elevations: vec!["low".into(), "medium".into(), "high".into()],
106        },
107        colors,
108        semantic,
109        fonts: Fonts {
110            families,
111            sizes: vec!["0.75rem".into(), "1rem".into(), "1.25rem".into()],
112            weights: vec![400, 700],
113            line_heights: vec![1.4, 1.5, 1.6],
114            letter_spacing,
115        },
116        space: vec!["0.5rem".into(), "1rem".into(), "2rem".into()],
117        dimensions: vec!["16rem".into(), "32rem".into(), "64rem".into()],
118        borders: Borders {
119            radius,
120            widths: vec!["1px".into(), "2px".into(), "4px".into()],
121        },
122        shadows: Shadows {
123            colors: shadow_colors,
124            elevations: shadow_elevations,
125        },
126        text_shadows,
127        ink: InkConfig {
128            light: ("neutral".into(), 100),
129            dark: ("neutral".into(), 900),
130        },
131        glows,
132        gradients: vec![Gradient {
133            name: "sweep".into(),
134            gradient_type: "linear".into(),
135            angle: 135,
136            stops: vec![("primary".into(), 100), ("primary".into(), 900)],
137            blend: Some(ColorSpace::Oklab),
138            mode: Some(InterpolationMode::Linear),
139            samples: 5,
140        }],
141        transitions,
142        z,
143        opacity,
144        overlay: BTreeMap::new(),
145    };
146
147    let manifest = Renderer::new().render(&spec)?;
148    print!("{manifest}");
149    Ok(())
150}