T14a Tables core: DataTable, Table, HeatmapTable, cells, Sparkline (review pending: codex 401)

This commit is contained in:
ZacharyZhang-NY committed 2026-09-25 20:15:45 -04:00
1 parent 9eddfeb48b
commit dcd988e4b8
18 files changed
+1690 -19

No files matched your search

+2
View File
@@ -10,6 +10,7 @@ mod navigation;
mod overlays;
mod primitives;
mod shell;
mod tables;
mod theme;
mod typography;
@@ -41,6 +42,7 @@ pub const ALL: &[Page] = &[
motion::PAGE,
data::PAGE,
lists::PAGE,
tables::PAGE,
theme::PAGE,
];
+247
View File
@@ -0,0 +1,247 @@
use std::rc::Rc;
use ely_gpui_component::{
buttons::SegmentedControl,
data_display::Tone,
forms::{SearchInput, TextInput},
tables::{Aggregate, Cell, Column, DataTable, HeatmapTable, Row, Table},
theme::{ActiveTheme, ControlSize, Density, Radius},
typography::Caption,
};
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px, rems};
use crate::{
probe::probe,
ui::{keep, section, set},
};
pub fn plain(cx: &mut App) -> impl IntoElement + use<> {
section(
"Table",
"A header and rows, hairlines between. Nothing more.",
cx,
)
.child(
div().w(px(560.)).child(
Table::new(["Name", "Role", "City"])
.row(["Ada Lovelace", "Analyst", "London"])
.row(["Grace Hopper", "Engineer", "Arlington"])
.row(["Katherine Johnson", "Mathematician", "Hampton"]),
),
)
}
const PEOPLE: [&str; 10] = [
"Ada Lovelace",
"Alan Turing",
"Grace Hopper",
"Katherine Johnson",
"Tim Berners-Lee",
"Radia Perlman",
"Sophie Wilson",
"Barbara Liskov",
"Ken Thompson",
"Margaret Hamilton",
];
const REGIONS: [&str; 4] = ["Europe", "Americas", "Asia", "Oceania"];
/// A steady pseudo-random series, so captures repeat.
fn noise(seed: u64) -> impl FnMut() -> f64 {
let mut state = seed;
move || {
state = state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(state >> 33) as f64 / (1u64 << 31) as f64
}
}
fn order_rows() -> Vec<Row> {
let mut next = noise(7);
(0..24)
.map(|ix| {
let (status, tone) = match (next() * 3.0) as u32 {
0 => ("Live", Tone::Success),
1 => ("Draft", Tone::Neutral),
_ => ("Review", Tone::Warning),
};
let spark: Vec<f32> = (0..8).map(|_| next() as f32).collect();
Row::new(
format!("order-{ix}"),
[
Cell::Person(PEOPLE[ix % PEOPLE.len()].into()),
Cell::Tag(status.into(), tone),
REGIONS[(next() * 4.0) as usize].into(),
(next() * 90_000.0 + 8_000.0).round().into(),
((next() - 0.35) * 40.0).into(),
Cell::Spark(spark),
Cell::Progress(next() as f32),
],
)
})
.collect()
}
fn order_columns() -> [Column; 7] {
[
Column::new("owner", "Owner"),
Column::new("status", "Status").width(rems(6.)),
Column::new("region", "Region").width(rems(6.)),
Column::new("revenue", "Revenue")
.end()
.prefix("$")
.aggregate(Aggregate::Sum)
.width(rems(8.)),
Column::new("growth", "Growth")
.end()
.decimals(1)
.suffix("%")
.scale()
.aggregate(Aggregate::Average)
.width(rems(6.)),
Column::new("trend", "Trend").unsorted().width(rems(6.)),
Column::new("quota", "Quota")
.width(rems(9.))
.aggregate(Aggregate::Average),
]
}
pub fn orders(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let search = window.use_keyed_state("orders-search", cx, TextInput::new);
let query = search.read(cx).text().to_string();
let picked = keep("orders-picked", Vec::<SharedString>::new, window, cx);
let density = keep(
"orders-density",
|| SharedString::from("standard"),
window,
cx,
);
let (chosen, store, dense, pick) = (
picked.read(cx).clone(),
picked.clone(),
density.read(cx).clone(),
density.clone(),
);
let spacing = match dense.as_ref() {
"compact" => Density::Compact,
"comfortable" => Density::Comfortable,
_ => Density::Standard,
};
section(
"DataTable / CellRenderer / RowSelection / TableDensity / AggregationFooter / ConditionalFormatting",
"Press a header to sort, rising then falling. Search keeps rows that hold the text; boxes select, Shift takes a range. Cells draw people, tags, sparklines and progress; growth is tinted by where it sits; the footer sums and averages what the search keeps.",
cx,
)
.child(
div()
.flex()
.items_center()
.justify_between()
.w(px(900.))
.pb_3()
.child(div().w(px(260.)).child(SearchInput::new("orders-find", &search).size(ControlSize::Sm)))
.child(
SegmentedControl::new("orders-density", dense)
.segment("compact", "Compact", None)
.segment("standard", "Standard", None)
.segment("comfortable", "Comfortable", None)
.size(ControlSize::Sm)
.on_change(move |value, _, cx| set(&pick, value.clone(), cx)),
),
)
.child(probe(
"orders",
div().w(px(900.)).child(
DataTable::new("orders", order_columns())
.rows(order_rows())
.query(query)
.paged(8)
.density(spacing)
.selected(chosen.clone())
.on_select(move |keys, _, cx| set(&store, keys.to_vec(), cx)),
),
))
.child(Caption::new(format!("{} selected", chosen.len())))
}
pub fn long(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let rows = keep(
"long-rows",
|| {
let mut next = noise(11);
Rc::new(
(0..100_000)
.map(|ix| {
Row::new(
format!("long-{ix}"),
[
format!("Event {}", ix + 1).into(),
REGIONS[ix % 4].into(),
(next() * 1000.0).round().into(),
],
)
})
.collect::<Vec<_>>(),
)
},
window,
cx,
)
.read(cx)
.clone();
let theme = cx.theme();
section(
"VirtualTable",
"A hundred thousand rows; only the ones in view are drawn, so it scrolls as lightly as ten.",
cx,
)
.child(
div()
.w(px(560.))
.h(px(300.))
.rounded(theme.radius(Radius::Md))
.border_1()
.border_color(theme.colors.border)
.child(
DataTable::new(
"long",
[
Column::new("event", "Event"),
Column::new("region", "Region"),
Column::new("value", "Value").end().aggregate(Aggregate::Sum),
],
)
.rows(rows)
.virtualized()
.density(Density::Compact)
.size_full(),
),
)
}
pub fn heatmap(cx: &mut App) -> impl IntoElement + use<> {
let mut next = noise(3);
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
let table = days.iter().enumerate().fold(
HeatmapTable::new(["6", "9", "12", "15", "18", "21"]),
|table, (day, name)| {
let busy = if day >= 5 { 0.5 } else { 1.0 };
table.row(
*name,
(0..6).map(|slot| {
(busy
* (40.0 + 60.0 * next())
* if (1..=4).contains(&slot) { 1.6 } else { 0.7 })
.round()
}),
)
},
);
section(
"HeatmapTable",
"Visits by weekday and hour; each cell is shaded by where it sits between the quietest and the busiest.",
cx,
)
.child(div().w(px(560.)).child(table))
.child(Caption::new("ComparisonTable is Comparison, in Data Display."))
}
+39
View File
@@ -0,0 +1,39 @@
mod core;
use gpui::{AnyElement, App, IntoElement, ParentElement, Window, div};
use super::Page;
use crate::script::Step;
pub const PAGE: Page = Page {
number: 14,
slug: "tables",
title: "Tables",
summary: "Rows and columns that sort, filter, page and select, and stay quick at a hundred thousand rows.",
render,
script: SCRIPT,
};
const SCRIPT: &[Step] = &[
Step::Rest,
Step::DownAt("orders", 440.0, 16.0),
Step::UpAt("orders", 440.0, 16.0),
Step::Wait(200),
Step::Shot("sorted"),
Step::DownAt("orders", 18.0, 54.0),
Step::UpAt("orders", 18.0, 54.0),
Step::DownAt("orders", 18.0, 126.0),
Step::UpAt("orders", 18.0, 126.0),
Step::Wait(200),
Step::Shot("selected"),
Step::Rest,
];
fn render(window: &mut Window, cx: &mut App) -> AnyElement {
div()
.child(core::plain(cx))
.child(core::orders(window, cx))
.child(core::long(window, cx))
.child(core::heatmap(cx))
.into_any_element()
}