1use ishou_tokens::{Rgb, TokenSet};
17
18fn role(t: &TokenSet, name: &str) -> String {
22 let key = t
23 .roles
24 .pairs()
25 .into_iter()
26 .find(|(r, _)| *r == name)
27 .map(|(_, k)| k)
28 .unwrap_or("background");
29 palette(t, key)
30}
31
32fn palette(t: &TokenSet, key: &str) -> String {
34 t.color
35 .get(key)
36 .map(|c: Rgb| c.hex())
37 .unwrap_or_else(|| "#000000".to_string())
38}
39
40#[must_use]
42pub fn render(t: &TokenSet) -> String {
43 let pairs: [(&str, String); 34] = [
46 ("primary", role(t, "primary")),
47 ("on-primary", role(t, "background")),
48 ("primary-container", role(t, "structural")),
49 ("on-primary-container", role(t, "text")),
50 ("secondary", role(t, "structural")),
51 ("on-secondary", role(t, "background")),
52 ("secondary-container", role(t, "surface-elevated")),
53 ("on-secondary-container", role(t, "text")),
54 ("tertiary", role(t, "accent")),
55 ("on-tertiary", role(t, "background")),
56 ("tertiary-container", role(t, "surface-elevated")),
57 ("on-tertiary-container", role(t, "text")),
58 ("error", role(t, "error")),
59 ("on-error", role(t, "background")),
60 ("error-container", role(t, "surface-elevated")),
61 ("on-error-container", role(t, "text")),
62 ("background", role(t, "background")),
63 ("on-background", role(t, "text")),
64 ("surface", role(t, "surface")),
65 ("on-surface", role(t, "text")),
66 ("surface-variant", role(t, "surface-elevated")),
67 ("on-surface-variant", role(t, "text-muted")),
68 ("outline", role(t, "text-dim")),
69 ("outline-variant", role(t, "surface-elevated")),
70 ("inverse-surface", role(t, "text")),
71 ("inverse-on-surface", role(t, "background")),
72 ("inverse-primary", role(t, "structural")),
73 ("surface-dim", role(t, "background")),
74 ("surface-bright", role(t, "text-dim")),
75 ("surface-container-lowest", palette(t, "ink")),
76 ("surface-container-low", role(t, "background")),
77 ("surface-container", role(t, "surface")),
78 ("surface-container-high", role(t, "surface-elevated")),
79 ("surface-container-highest", role(t, "text-dim")),
80 ];
81
82 let mut out =
83 String::from("/* ishou — Material Design 3 system colors (generated; do not edit) */\n\n");
84 out.push_str(":root {\n");
85 for (suffix, hex) in &pairs {
86 out.push_str(&format!(" --md-sys-color-{suffix}: {hex};\n"));
87 }
88 out.push_str("}\n");
89 out
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 const MD3_SYSTEM_COLORS: [&str; 34] = [
98 "primary",
99 "on-primary",
100 "primary-container",
101 "on-primary-container",
102 "secondary",
103 "on-secondary",
104 "secondary-container",
105 "on-secondary-container",
106 "tertiary",
107 "on-tertiary",
108 "tertiary-container",
109 "on-tertiary-container",
110 "error",
111 "on-error",
112 "error-container",
113 "on-error-container",
114 "background",
115 "on-background",
116 "surface",
117 "on-surface",
118 "surface-variant",
119 "on-surface-variant",
120 "outline",
121 "outline-variant",
122 "inverse-surface",
123 "inverse-on-surface",
124 "inverse-primary",
125 "surface-dim",
126 "surface-bright",
127 "surface-container-lowest",
128 "surface-container-low",
129 "surface-container",
130 "surface-container-high",
131 "surface-container-highest",
132 ];
133
134 #[test]
135 fn emits_every_md3_system_color_pleme_mui_consumes() {
136 let out = render(&TokenSet::pleme());
137 for prop in MD3_SYSTEM_COLORS {
138 let needle = format!("--md-sys-color-{prop}:");
139 assert!(out.contains(&needle), "missing MD3 color: {prop}");
140 }
141 }
142
143 #[test]
144 fn every_role_resolves_to_a_real_token_hex() {
145 let out = render(&TokenSet::pleme());
146 assert!(
148 !out.contains("#000000"),
149 "an MD3 role failed to resolve from ishou tokens"
150 );
151 assert_eq!(
153 out.matches('#').count(),
154 34,
155 "expected 34 resolved hex values"
156 );
157 }
158
159 #[test]
160 fn is_deterministic() {
161 assert_eq!(render(&TokenSet::pleme()), render(&TokenSet::pleme()));
162 }
163
164 #[test]
165 fn steel_theme_renders_the_metallic_surface() {
166 let out = render(&TokenSet::steel());
167 assert!(
169 out.contains("--md-sys-color-primary: #5E8CC4"),
170 "steel primary should be blued-steel"
171 );
172 assert!(out.contains("--md-sys-color-background: #0B0E12"));
174 assert!(!out.contains("#000000"), "steel md3 should fully resolve");
176 }
177}