Files
Ely-GPUI-Components/src/tooling/tokens.rs
T
ZacharyZhang-NYandClaude Opus 5.5 ed1014bc34 Tooling · Catalogs: the audit reads active and code; avatar and container sizes
The audit now holds fg_subtle on active, a selected row's fill, and
each syntax color over the editor's surface, a code block and the
current line. What it found is fixed at the source: fg_subtle is
#696764 light and #94928f dark, syntax.comment follows it, and the
light number, constant and attribute darken to #a3591b, #aa5529 and
#9f5d12. The token viewer adds avatar and container sizes, draws a
container at its share of the widest so it stays in its row, and
names what it shows: the shared colors and scales. Tests pin every
variant, the hue each chart row reads and the code rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 08:57:52 -04:00

327 lines
11 KiB
Rust

use gpui::{
AnyElement, App, FontWeight, Hsla, InteractiveElement, IntoElement, ParentElement, Pixels,
Rems, RenderOnce, Rgba, Styled, Window, div, relative, rems,
};
use crate::{
forms::hex,
primitives::{Icon, IconName},
theme::{
ActiveTheme, AvatarSize, ContainerSize, ControlSize, Elevation, HUE_NAMES, IconSize,
Palette, Radius, Syntax, TextSize,
},
typography::{Ellipsis, literal, tabular},
};
const ANSI: [&str; 16] = [
"black",
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
"bright black",
"bright red",
"bright green",
"bright yellow",
"bright blue",
"bright magenta",
"bright cyan",
"bright white",
];
/// gpui's spacing steps shown, each a quarter rem, as `gap_2` reads.
const STEPS: [f32; 6] = [1.0, 2.0, 3.0, 4.0, 6.0, 8.0];
fn pixels(value: Pixels) -> String {
((f32::from(value) * 10.0).round() / 10.0).to_string()
}
/// A titled group of tokens.
fn group(title: &'static str, cx: &App) -> gpui::Div {
div().flex().flex_col().gap_3().child(
div()
.text_size(cx.theme().text_size(TextSize::Sm))
.font_weight(FontWeight::SEMIBOLD)
.child(title),
)
}
/// A color's tile: its swatch, its name as code reads it, and its hex.
fn swatch(name: String, color: Hsla, cx: &App) -> AnyElement {
let theme = cx.theme();
let sizes = theme.tooling();
let code = hex(Rgba::from(color));
let shown = format!("token-{name}-{code}");
div()
.debug_selector(move || shown)
.flex()
.items_center()
.gap_2()
.w(sizes.tile)
.min_w_0()
.child(
div()
.flex_none()
.size(sizes.swatch)
.rounded(theme.radius(Radius::Md))
.border_1()
.border_color(theme.colors.border)
.bg(color),
)
.child(
literal(div())
.flex_1()
.min_w_0()
.flex()
.flex_col()
.text_size(theme.text_size(TextSize::Xs))
.child(Ellipsis::new(name))
.child(div().text_color(theme.colors.fg_muted).child(code)),
)
.into_any_element()
}
/// A measure's row: its name as code reads it and its size in pixels, then a sample of it.
fn measure(name: String, size: Pixels, sample: impl IntoElement, cx: &App) -> AnyElement {
let theme = cx.theme();
let value = pixels(size);
let shown = format!("token-{name}-{value}");
div()
.debug_selector(move || shown)
.flex()
.flex_wrap()
.items_center()
.gap_x_4()
.gap_y_1()
.child(
div()
.flex_none()
.w(theme.tooling().tile)
.flex()
.justify_between()
.gap_2()
.text_size(theme.text_size(TextSize::Xs))
.child(literal(div()).child(name))
.child(
tabular(div())
.text_color(theme.colors.fg_muted)
.child(format!("{value} px")),
),
)
.child(div().flex_1().min_w_0().flex().items_center().child(sample))
.into_any_element()
}
/// The theme's shared colors and scales in the mode shown, each named as code reads it: every palette, syntax, chart and terminal color with its hex, and the type, radius, control, icon, avatar and container sizes, the elevations and gpui's spacing steps. A chapter's own measures stay with the chapter.
#[derive(IntoElement, Default)]
pub struct DesignTokenViewer;
impl DesignTokenViewer {
pub fn new() -> Self {
Self
}
}
impl RenderOnce for DesignTokenViewer {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let rem = window.rem_size();
let at = |size: Rems| size.to_pixels(rem);
let theme = cx.theme();
let (colors, sizes) = (&theme.colors, theme.tooling());
let tiles = |title, tiles: Vec<AnyElement>| {
group(title, cx).child(div().flex().flex_wrap().gap_3().children(tiles))
};
let palette = Palette::NAMES
.iter()
.map(|name| swatch(format!("colors.{name}"), colors.token(name), cx));
let syntax = Syntax::NAMES.iter().map(|name| {
swatch(
format!("colors.syntax.{name}"),
colors.syntax.token(name),
cx,
)
});
let chart = HUE_NAMES.iter().enumerate().map(|(ix, name)| {
swatch(
format!("colors.hue({ix}) {name}"),
colors.hue(ix, "token viewer"),
cx,
)
});
let ansi = ANSI
.iter()
.zip(colors.ansi)
.enumerate()
.map(|(ix, (name, color))| swatch(format!("colors.ansi[{ix}] {name}"), color, cx));
let text = [
(TextSize::Xs, "Xs"),
(TextSize::Sm, "Sm"),
(TextSize::Base, "Base"),
(TextSize::Md, "Md"),
(TextSize::Lg, "Lg"),
(TextSize::Xl, "Xl"),
(TextSize::Xxl, "Xxl"),
(TextSize::Display, "Display"),
]
.map(|(size, name)| {
let sample = div()
.min_w_0()
.text_size(theme.text_size(size))
.child(Ellipsis::new("Sphinx of black quartz"));
measure(
format!("TextSize::{name}"),
at(theme.text_size(size)),
sample,
cx,
)
});
let radii = [
(Radius::Sm, "Sm"),
(Radius::Md, "Md"),
(Radius::Lg, "Lg"),
(Radius::Xl, "Xl"),
]
.map(|(radius, name)| {
let sample = div()
.size(sizes.swatch)
.rounded(theme.radius(radius))
.border_1()
.border_color(colors.border_strong)
.bg(colors.sunken);
measure(
format!("Radius::{name}"),
at(theme.radius(radius)),
sample,
cx,
)
});
let controls = [
(ControlSize::Sm, "Sm"),
(ControlSize::Md, "Md"),
(ControlSize::Lg, "Lg"),
]
.map(|(size, name)| {
let height = theme.control_height(size);
let sample = div()
.h(height)
.w(sizes.tile)
.rounded(theme.radius(Radius::Md))
.border_1()
.border_color(colors.border_strong)
.bg(colors.surface);
measure(format!("ControlSize::{name}"), at(height), sample, cx)
});
let avatars = [
(AvatarSize::Xs, "Xs"),
(AvatarSize::Sm, "Sm"),
(AvatarSize::Md, "Md"),
(AvatarSize::Lg, "Lg"),
(AvatarSize::Xl, "Xl"),
]
.map(|(size, name)| {
let side = theme.avatar_size(size);
let sample = div()
.size(side)
.rounded_full()
.border_1()
.border_color(colors.border_strong)
.bg(colors.sunken);
measure(format!("AvatarSize::{name}"), at(side), sample, cx)
});
let containers = [
(ContainerSize::Sm, "Sm"),
(ContainerSize::Md, "Md"),
(ContainerSize::Lg, "Lg"),
]
.map(|(size, name)| {
let width = theme.container_width(size);
let widest = theme.container_width(ContainerSize::Lg);
// Drawn at its share of the widest, so the bar stays inside the row.
let sample = div().relative().flex_1().h_2().child(
div()
.absolute()
.top_0()
.left_0()
.h_full()
.w(relative(width.0 / widest.0))
.rounded(theme.radius(Radius::Sm))
.bg(colors.border_strong),
);
measure(format!("ContainerSize::{name}"), at(width), sample, cx)
});
let icons = [
(IconSize::Xs, "Xs"),
(IconSize::Sm, "Sm"),
(IconSize::Md, "Md"),
(IconSize::Lg, "Lg"),
(IconSize::Xl, "Xl"),
(IconSize::Xxl, "Xxl"),
]
.map(|(size, name)| {
let sample = Icon::new(IconName::Star).size(size).color(colors.fg);
measure(
format!("IconSize::{name}"),
at(theme.icon_size(size)),
sample,
cx,
)
});
let elevations = [
(Elevation::Raised, "Raised"),
(Elevation::Floating, "Floating"),
(Elevation::Modal, "Modal"),
]
.map(|(level, name)| {
let card = div()
.w(sizes.tile)
.h(sizes.swatch)
.rounded(theme.radius(Radius::Md))
.bg(colors.surface)
.shadow(theme.elevation(level));
let shown = format!("token-Elevation::{name}");
div()
.debug_selector(move || shown)
.flex()
.flex_col()
.gap_2()
.child(card)
.child(
literal(div())
.text_size(theme.text_size(TextSize::Xs))
.child(format!("Elevation::{name}")),
)
});
let steps = STEPS.map(|step| {
let width = rems(step / 4.0);
let sample = div()
.h_2()
.w(width)
.rounded(theme.radius(Radius::Sm))
.bg(colors.accent);
measure(format!("gap_{step}"), at(width), sample, cx)
});
div()
.flex()
.flex_col()
.gap_8()
.child(tiles("Colors", palette.collect()))
.child(tiles("Syntax", syntax.collect()))
.child(tiles("Chart", chart.collect()))
.child(tiles("Terminal", ansi.collect()))
.child(group("Type", cx).children(text))
.child(group("Radii", cx).children(radii))
.child(group("Controls", cx).children(controls))
.child(group("Icons", cx).children(icons))
.child(group("Avatars", cx).children(avatars))
.child(group("Containers", cx).children(containers))
.child(
group("Elevation", cx)
.child(div().flex().flex_wrap().gap_6().p_2().children(elevations)),
)
.child(group("Spacing", cx).children(steps))
}
}