1use aetna_core::prelude::*;
7
8#[derive(Clone, Copy)]
9struct TokenDef {
10 name: &'static str,
11 color: Color,
12}
13
14fn main() -> std::io::Result<()> {
15 let viewport = Rect::new(0.0, 0.0, 1220.0, 1040.0);
16 let out_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("out");
17
18 let variants = [
19 ("palette_demo.aetna_dark", "Aetna dark", Theme::aetna_dark()),
20 (
21 "palette_demo.aetna_light",
22 "Aetna light",
23 Theme::aetna_light(),
24 ),
25 (
26 "palette_demo.radix_slate_blue_dark",
27 "Radix slate + blue dark",
28 Theme::radix_slate_blue_dark(),
29 ),
30 (
31 "palette_demo.radix_slate_blue_light",
32 "Radix slate + blue light",
33 Theme::radix_slate_blue_light(),
34 ),
35 (
36 "palette_demo.radix_sand_amber_dark",
37 "Radix sand + amber dark",
38 Theme::radix_sand_amber_dark(),
39 ),
40 (
41 "palette_demo.radix_sand_amber_light",
42 "Radix sand + amber light",
43 Theme::radix_sand_amber_light(),
44 ),
45 (
46 "palette_demo.radix_mauve_violet_dark",
47 "Radix mauve + violet dark",
48 Theme::radix_mauve_violet_dark(),
49 ),
50 (
51 "palette_demo.radix_mauve_violet_light",
52 "Radix mauve + violet light",
53 Theme::radix_mauve_violet_light(),
54 ),
55 ];
56
57 for (file_name, label, theme) in variants {
58 let mut root = palette_demo(label, theme.palette());
59 let bundle = render_bundle_themed(&mut root, viewport, &theme);
60 let written = write_bundle(&bundle, &out_dir, file_name)?;
61 for p in &written {
62 println!("wrote {}", p.display());
63 }
64
65 if !bundle.lint.findings.is_empty() {
66 eprintln!(
67 "\nlint findings for {file_name} ({}):",
68 bundle.lint.findings.len()
69 );
70 eprint!("{}", bundle.lint.text());
71 }
72 }
73
74 Ok(())
75}
76
77fn palette_demo(label: &'static str, palette: &Palette) -> El {
78 column([
79 row([
80 column([
81 h1("Palette demo"),
82 text("Aetna and Radix palettes rendered through Aetna tokens.").muted(),
83 ])
84 .gap(tokens::SPACE_2)
85 .height(Size::Hug),
86 spacer(),
87 badge(label).muted(),
88 ])
89 .align(Align::Start)
90 .height(Size::Hug),
91 row([
92 token_section(
93 "Core tokens",
94 "The shadcn-shaped semantic vocabulary.",
95 &CORE_TOKENS,
96 palette,
97 )
98 .width(Size::Fill(1.25)),
99 column([
100 token_section(
101 "Aetna extensions",
102 "Component and status tokens layered on top.",
103 &EXTENSION_TOKENS,
104 palette,
105 ),
106 component_section(),
107 ])
108 .gap(tokens::SPACE_4)
109 .width(Size::Fill(1.0))
110 .height(Size::Fill(1.0)),
111 ])
112 .gap(tokens::SPACE_4)
113 .align(Align::Stretch)
114 .height(Size::Fill(1.0)),
115 ])
116 .padding(tokens::SPACE_8)
117 .gap(tokens::SPACE_6)
118 .fill_size()
119 .fill(tokens::BACKGROUND)
120}
121
122const CORE_TOKENS: [TokenDef; 19] = [
123 TokenDef {
124 name: "background",
125 color: tokens::BACKGROUND,
126 },
127 TokenDef {
128 name: "foreground",
129 color: tokens::FOREGROUND,
130 },
131 TokenDef {
132 name: "card",
133 color: tokens::CARD,
134 },
135 TokenDef {
136 name: "card-foreground",
137 color: tokens::CARD_FOREGROUND,
138 },
139 TokenDef {
140 name: "popover",
141 color: tokens::POPOVER,
142 },
143 TokenDef {
144 name: "popover-foreground",
145 color: tokens::POPOVER_FOREGROUND,
146 },
147 TokenDef {
148 name: "primary",
149 color: tokens::PRIMARY,
150 },
151 TokenDef {
152 name: "primary-foreground",
153 color: tokens::PRIMARY_FOREGROUND,
154 },
155 TokenDef {
156 name: "secondary",
157 color: tokens::SECONDARY,
158 },
159 TokenDef {
160 name: "secondary-foreground",
161 color: tokens::SECONDARY_FOREGROUND,
162 },
163 TokenDef {
164 name: "muted",
165 color: tokens::MUTED,
166 },
167 TokenDef {
168 name: "muted-foreground",
169 color: tokens::MUTED_FOREGROUND,
170 },
171 TokenDef {
172 name: "accent",
173 color: tokens::ACCENT,
174 },
175 TokenDef {
176 name: "accent-foreground",
177 color: tokens::ACCENT_FOREGROUND,
178 },
179 TokenDef {
180 name: "destructive",
181 color: tokens::DESTRUCTIVE,
182 },
183 TokenDef {
184 name: "destructive-foreground",
185 color: tokens::DESTRUCTIVE_FOREGROUND,
186 },
187 TokenDef {
188 name: "border",
189 color: tokens::BORDER,
190 },
191 TokenDef {
192 name: "input",
193 color: tokens::INPUT,
194 },
195 TokenDef {
196 name: "ring",
197 color: tokens::RING,
198 },
199];
200
201const EXTENSION_TOKENS: [TokenDef; 12] = [
202 TokenDef {
203 name: "success",
204 color: tokens::SUCCESS,
205 },
206 TokenDef {
207 name: "success-foreground",
208 color: tokens::SUCCESS_FOREGROUND,
209 },
210 TokenDef {
211 name: "warning",
212 color: tokens::WARNING,
213 },
214 TokenDef {
215 name: "warning-foreground",
216 color: tokens::WARNING_FOREGROUND,
217 },
218 TokenDef {
219 name: "info",
220 color: tokens::INFO,
221 },
222 TokenDef {
223 name: "info-foreground",
224 color: tokens::INFO_FOREGROUND,
225 },
226 TokenDef {
227 name: "link-foreground",
228 color: tokens::LINK_FOREGROUND,
229 },
230 TokenDef {
231 name: "overlay-scrim",
232 color: tokens::OVERLAY_SCRIM,
233 },
234 TokenDef {
235 name: "scrollbar-thumb",
236 color: tokens::SCROLLBAR_THUMB_FILL,
237 },
238 TokenDef {
239 name: "scrollbar-thumb-active",
240 color: tokens::SCROLLBAR_THUMB_FILL_ACTIVE,
241 },
242 TokenDef {
243 name: "selection-bg",
244 color: tokens::SELECTION_BG,
245 },
246 TokenDef {
247 name: "selection-bg-unfocused",
248 color: tokens::SELECTION_BG_UNFOCUSED,
249 },
250];
251
252fn token_section(
253 title: &'static str,
254 description: &'static str,
255 tokens: &'static [TokenDef],
256 palette: &Palette,
257) -> El {
258 card([
259 card_header([card_title(title), card_description(description)]),
260 card_content([swatch_grid(tokens, palette)]),
261 ])
262 .height(Size::Fill(1.0))
263}
264
265fn swatch_grid(defs: &'static [TokenDef], palette: &Palette) -> El {
266 let rows = defs
267 .chunks(2)
268 .map(|chunk| {
269 row(chunk.iter().map(|token| token_chip(*token, palette)))
270 .gap(tokens::SPACE_3)
271 .align(Align::Center)
272 .width(Size::Fill(1.0))
273 })
274 .collect::<Vec<_>>();
275
276 column(rows)
277 .gap(tokens::SPACE_3)
278 .width(Size::Fill(1.0))
279 .height(Size::Hug)
280}
281
282fn token_chip(token: TokenDef, palette: &Palette) -> El {
283 let resolved = palette.resolve(token.color);
284 row([
285 El::new(Kind::Custom("palette-swatch"))
286 .at(file!(), line!())
287 .fill(token.color)
288 .stroke(tokens::BORDER)
289 .radius(tokens::RADIUS_SM)
290 .width(Size::Fixed(42.0))
291 .height(Size::Fixed(34.0)),
292 column([
293 text(token.name)
294 .label()
295 .ellipsis()
296 .nowrap_text()
297 .width(Size::Fill(1.0)),
298 mono(rgba_label(resolved)).caption().muted(),
299 ])
300 .gap(0.0)
301 .width(Size::Fill(1.0))
302 .height(Size::Hug),
303 ])
304 .gap(tokens::SPACE_2)
305 .padding(Sides::xy(tokens::SPACE_2, tokens::SPACE_2))
306 .align(Align::Center)
307 .fill(tokens::CARD)
308 .stroke(tokens::BORDER)
309 .radius(tokens::RADIUS_MD)
310 .width(Size::Fill(1.0))
311 .height(Size::Fixed(54.0))
312}
313
314fn component_section() -> El {
315 card([
316 card_header([
317 card_title("Stock widgets"),
318 card_description("The same palette applied to regular component constructors."),
319 ]),
320 card_content([
321 row([
322 button("Primary").primary(),
323 button("Secondary").secondary(),
324 button("Outline").outline(),
325 button("Ghost").ghost(),
326 ])
327 .gap(tokens::SPACE_2)
328 .align(Align::Center),
329 row([
330 badge("success").success(),
331 badge("warning").warning(),
332 badge("destructive").destructive(),
333 badge("info").info(),
334 badge("muted").muted(),
335 ])
336 .gap(tokens::SPACE_2)
337 .align(Align::Center),
338 row([
339 text_input("palette search", &Selection::default(), "palette:search")
340 .width(Size::Fill(1.0)),
341 button_with_icon("settings", "Tune").secondary(),
342 ])
343 .gap(tokens::SPACE_2)
344 .align(Align::Center),
345 row([
346 surface_sample("Card", tokens::CARD),
347 surface_sample("Muted", tokens::MUTED),
348 surface_sample("Popover", tokens::POPOVER),
349 ])
350 .gap(tokens::SPACE_3)
351 .align(Align::Stretch),
352 ])
353 .gap(tokens::SPACE_4),
354 ])
355 .height(Size::Hug)
356}
357
358fn surface_sample(title: &'static str, fill: Color) -> El {
359 column([
360 text(title).label(),
361 text("surface sample").caption().muted(),
362 ])
363 .gap(tokens::SPACE_1)
364 .padding(tokens::SPACE_3)
365 .fill(fill)
366 .stroke(tokens::BORDER)
367 .radius(tokens::RADIUS_MD)
368 .width(Size::Fill(1.0))
369 .height(Size::Fixed(76.0))
370}
371
372fn rgba_label(c: Color) -> String {
373 if c.a == 255 {
374 format!("#{:02x}{:02x}{:02x}", c.r, c.g, c.b)
375 } else {
376 format!("#{:02x}{:02x}{:02x}/{:03}", c.r, c.g, c.b, c.a)
377 }
378}