Layers panel: one row per item in the parity table, and the hidden component name reaches 3:1 on its selected row (#1911 issue)

The merge's docs union left the UI3 layout and light/dark themes rows twice; each is one row again. Dim text (text_tertiary) goes from #8A8A8A to #848484 in the light theme: 2.93:1 became 3.18:1 on the selected component row, and the theme floor test now covers the component rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Trevor Brown
2026-11-06 22:56:48 -05:00
co-authored by Claude Fable 5.1
parent a794e57e3d
commit 7ca01645c3
3 changed files with 18 additions and 14 deletions
@@ -92,14 +92,14 @@ fn rows_inside_a_selected_component_take_a_tone_of_its_row() {
}
}
/// The Layers panel with the component "Button" hidden and nothing selected.
fn drawn_hidden(dark: bool) -> (Vec<Shape>, Tokens) {
/// The Layers panel with the component "Button" hidden, and selected or not.
fn drawn_hidden(dark: bool, selected: bool) -> (Vec<Shape>, Tokens) {
let mut s = Session::new();
let f = id(s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 100, "height": 40, "props": {"name": "Button"}})).unwrap());
let c = id(s.execute("component.create", &json!({"ids": [f]})).unwrap());
s.execute(if dark { "view.themeDark" } else { "view.themeLight" }, &json!({})).unwrap();
s.execute("node.set", &json!({"id": c, "props": {"visible": false}})).unwrap();
s.execute("select.set", &json!({"ids": []})).unwrap();
s.execute("select.set", &json!({"ids": if selected { vec![c] } else { vec![] }})).unwrap();
let mut u = Ui::new(s);
u.frame(vec![]);
let shapes = crate::live_tests::flat(&u.frame_output(vec![]).shapes);
@@ -116,12 +116,16 @@ fn over(fg: Color32, bg: Color32) -> Color32 {
#[test]
fn a_hidden_component_name_keeps_the_dim_text_floor_of_3_to_1() {
for dark in [false, true] {
let (shapes, t) = drawn_hidden(dark);
assert_eq!(t.dark, dark);
let col = name_colour(&shapes, "Button");
for (what, bg) in [("the panel", t.panel), ("a hovered row", t.hover)] {
let ratio = color(over(col, bg)).contrast(color(bg));
assert!(ratio >= 3.0, "hidden component name {col:?} is {ratio:.2}:1 on {what} (dark {dark})");
for (selected, rows) in [(false, 0), (true, 1)] {
let (shapes, t) = drawn_hidden(dark, selected);
assert_eq!(t.dark, dark);
let col = name_colour(&shapes, "Button");
let all = [("the panel", t.panel), ("a hovered row", t.hover), ("the selected component row", t.component_row)];
// Unselected, the row shows the panel (or the hover); selected, its own tinted row.
for (what, bg) in &all[..2 + rows] {
let ratio = color(over(col, *bg)).contrast(color(*bg));
assert!(ratio >= 3.0, "hidden component name {col:?} is {ratio:.2}:1 on {what} (dark {dark}, selected {selected})");
}
}
}
}
+4 -2
View File
@@ -108,7 +108,9 @@ impl Tokens {
text: Color32::from_rgb(0x1E, 0x1E, 0x1E),
// 4.7:1 on fields and hovered rows (#F5F5F5), 4.6:1 on selected rows: WCAG AA.
text_secondary: Color32::from_rgb(0x6E, 0x6E, 0x6E),
text_tertiary: Color32::from_rgb(0x8A, 0x8A, 0x8A),
// Dim text: 3.2:1 on the selected component row (#F1E9FF, the tightest of the
// rows it lands on; #8A8A8A was 2.93:1 there), still dimmer than secondary text.
text_tertiary: Color32::from_rgb(0x84, 0x84, 0x84),
icon: Color32::from_rgb(0x1E, 0x1E, 0x1E),
icon_secondary: Color32::from_rgb(0x75, 0x75, 0x75),
hover: Color32::from_rgb(0xF5, 0xF5, 0xF5),
@@ -507,7 +509,7 @@ mod tests {
let theme = if t.dark { "dark" } else { "light" };
// Disabled text (a disabled button's label, placeholders) on panels, fields and
// selected rows, still dimmer than secondary text.
for bg in [t.panel, t.field, t.hover, t.selected_row, t.selected_row_child] {
for bg in [t.panel, t.field, t.hover, t.selected_row, t.selected_row_child, t.component_row, t.component_row_child] {
let ratio = c(t.text_tertiary).contrast(c(bg));
assert!(ratio >= 3.0, "{theme}: dim text {:?} on {bg:?} is {ratio:.2}:1", t.text_tertiary);
assert!(ratio < c(t.text_secondary).contrast(c(bg)), "{theme}: dim text reads dimmer than secondary text");
+1 -3
View File
@@ -15,10 +15,8 @@ interaction feel not yet compared.
| Item | Status | Notes |
|---|---|---|
| UI3 layout: tab strip as title bar, icon rail, file header, pages + layers, floating bottom toolbar with flyouts, Design / Prototype / Inspect panels | done | frameless window on Windows/Linux with drawn buttons; on a Wayland compositor that offers no server-side decorations (GNOME) the window gets winit's Adwaita controls instead (#592; checked by its author on GNOME, KDE not run) |
| Light and dark themes | done | colours from `theme::Tokens`; component, variant and instance rows in the Layers panel take the theme's component tint, at AA on the panel and on their selected row (hidden ones in the faded text colour, at least 3:1), and the rows inside a selected component a tone of its row (`layers_component_rows_tests`, #1903); a file or a page made while the dark theme shows gets a `#1E1E1E` page (`#F5F5F5` in the light theme), its own from then on: changing the theme repaints no page and opened files keep theirs; the file the app opens with takes the theme once it is known; the CLI, MCP and exports have no theme and keep `#F5F5F5`; text drawn with the Text tool is white on a dark page and black on a light one (so is text a command makes in the app without naming a fill; headless sessions keep black) (`theme_pages_tests`, `dark_page_text_tests`) |
| Light and dark themes | done | colours from `theme::Tokens`; component, variant and instance rows in the Layers panel take the theme's component tint, at AA on the panel and on their selected row (hidden ones in the faded text colour, at least 3:1 on the panel, a hovered row and their selected row), and the rows inside a selected component a tone of its row (`layers_component_rows_tests`, #1903); a file or a page made while the dark theme shows gets a `#1E1E1E` page (`#F5F5F5` in the light theme), its own from then on: changing the theme repaints no page and opened files keep theirs; the file the app opens with takes the theme once it is known; the CLI, MCP and exports have no theme and keep `#F5F5F5`; text drawn with the Text tool is white on a dark page and black on a light one (so is text a command makes in the app without naming a fill; headless sessions keep black) (`theme_pages_tests`, `dark_page_text_tests`) |
| UI3 layout: tab strip as title bar, icon rail, file header, pages + layers, floating bottom toolbar with flyouts, Design / Prototype / Inspect panels | done | frameless window on Windows/Linux with drawn buttons; tabs reorder by dragging along the strip, and closing one whose file has unsaved changes asks Don't Save / Cancel / Save (Alt+close drops the changes at once, #2032); on a Wayland compositor that offers no server-side decorations (GNOME) the window gets winit's Adwaita controls instead (#592; checked by its author on GNOME, KDE not run) |
| Light and dark themes | done | colours from `theme::Tokens`; a file or a page made while the dark theme shows gets a `#1E1E1E` page (`#F5F5F5` in the light theme), its own from then on: changing the theme repaints no page and opened files keep theirs; the file the app opens with takes the theme once it is known; the CLI, MCP and exports have no theme and keep `#F5F5F5`; text drawn with the Text tool is white on a dark page and black on a light one (so is text a command makes in the app without naming a fill; headless sessions keep black) (`theme_pages_tests`, `dark_page_text_tests`) |
| Minimize UI (Shift+Cmd/Ctrl+\\, the Minimize button by the file name) and Show/Hide UI (Cmd/Ctrl+\\) | done | Minimize collapses the navigation bar and both sidebars and keeps the toolbar and the panel widths; "If you select an object while the panels are minimized, the left sidebar remains minimized while the right sidebar expands [...] Once all objects are deselected, the right panel minimizes again" (Explore the navigation bar and left sidebar); a small Expand button at the top left of the canvas puts them back (FigCraft's own, the article names no control for it). Hide UI takes the toolbar too (`panel_width_tests`, `shortcuts_tests`; #1835) |
| Resizable panels | done | each panel's edge drags its width (left 200–480, right 240–480, 240 by default; a double-click on the edge or View ▸ Panels ▸ Reset panel widths puts the default back); the widths are each window's own, the main window's are kept in the preferences when a drag ends and come back at the next start, a new window opens with those of the window it comes from; Minimize UI hides the panels and brings them back as they were; the view keeps its centre and zoom while a panel is resized; in a window too small for them the panels give way so the canvas keeps 160 px (the left one first), down to the smallest window (800×500) (`panel_width_tests`). Not compared with Figma's widths; no snap points |
| Design panel's sections on one grid | done | every section lays its rows out on the same grid: the panel is 240 pt, its content 16 pt in from the left and 8 pt from the right, two 88 pt columns at that width with 8 pt gutters and a 24 pt column for the buttons at a row's end (the columns are flex, so they share the panel when it is dragged wider); rows 24 pt in a 32 pt track, 10 pt captions over them; section titles 11 pt semibold and a row's values 11 pt, their buttons 24 pt and 4 pt apart. The panel's edge is drawn inside it: a frame stroke counts as margin and took 1 pt off each side, putting every right margin 1–2 pt out (checked in the app on Windows over the control channel) |