Files
Ely-GPUI-Components/examples/gallery/pages/data/people.rs
T

244 lines
9.5 KiB
Rust

use std::path::{Path, PathBuf};
use ely_gpui_component::{
buttons::{Button, ButtonVariant},
data_display::{
Avatar, AvatarGroup, Badge, CountBadge, DotBadge, KpiCard, Presence, Statistic, Tag, Tone,
TrendIndicator, UserChip,
},
forms::AvatarUpload,
primitives::{Icon, IconName, Severity},
theme::{ActiveTheme, AvatarSize, IconSize, TextSize},
typography::Caption,
};
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div};
use crate::{
probe::probe,
ui::{keep, row, section, set, specimen, specimens},
};
const DUNES: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/examples/gallery/assets/dunes-square.jpg"
);
pub fn badges(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let unread = keep("badge-unread", || 3usize, window, cx);
let (count, more) = (*unread.read(cx), unread.clone());
let muted = cx.theme().colors.fg_muted;
let bell = || Icon::new(IconName::Bell).size(IconSize::Lg).color(muted);
section(
"Badge / CountBadge / DotBadge",
"Small labels tinted by what they mean, counts whose digits roll, and a dot for something new.",
cx,
)
.child(
row()
.child(Badge::new("Draft"))
.child(Badge::new("Beta").tone(Tone::Accent))
.child(Badge::new("Live").tone(Severity::Success).dot())
.child(Badge::new("Review").tone(Severity::Warning).dot())
.child(Badge::new("Failed").tone(Severity::Danger).dot())
.child(Badge::new("New").tone(Severity::Info)),
)
.child(
specimens()
.child(specimen("count", CountBadge::new("count-bell", count).over(bell()), cx))
.child(specimen("capped", CountBadge::new("count-inbox", 128).over(Icon::new(IconName::Inbox).size(IconSize::Lg).color(muted)), cx))
.child(specimen("dot", DotBadge::new().over(bell()), cx))
.child(specimen("alone", CountBadge::new("count-alone", 7), cx)),
)
.child(row().child(probe(
"badge-more",
Button::new("badge-more", "One more")
.variant(ButtonVariant::Ghost)
.on_click(move |_, _, cx| set(&more, count + 1, cx)),
)))
}
pub fn tags(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
const START: [&str; 4] = ["Design", "Motion", "Accessibility", "Rust"];
let kept = keep("tags-kept", || START.to_vec(), window, cx);
let now = kept.read(cx).clone();
let reset = kept.clone();
section(
"Tag / Chip",
"Short labels for a kind, a topic or a filter; an x takes one away.",
cx,
)
.child(
row()
.child(Tag::new("tag-plain", "Research"))
.child(Tag::new("tag-icon", "Pinned").icon(IconName::Pin))
.child(
Tag::new("tag-tone", "Shipped")
.tone(Severity::Success)
.icon(IconName::Check),
),
)
.child(
row()
.children(now.iter().enumerate().map(|(ix, name)| {
let kept = kept.clone();
let name = *name;
Tag::new(SharedString::from(format!("tag-remove-{ix}")), name).on_remove(
move |_, cx| {
let then: Vec<&str> = kept
.read(cx)
.iter()
.copied()
.filter(|tag| *tag != name)
.collect();
set(&kept, then, cx)
},
)
}))
.child(
Button::new("tags-reset", "Reset")
.variant(ButtonVariant::Ghost)
.on_click(move |_, _, cx| set(&reset, START.to_vec(), cx)),
),
)
}
pub fn avatars(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let picked = keep("avatar-picked", || None::<PathBuf>, window, cx);
let (chosen, take) = (picked.read(cx).clone(), picked.clone());
let sizes = [
(AvatarSize::Xs, "xs"),
(AvatarSize::Sm, "sm"),
(AvatarSize::Md, "md"),
(AvatarSize::Lg, "lg"),
(AvatarSize::Xl, "xl"),
];
let upload = Avatar::new("upload-face", "Noor Haddad").size(AvatarSize::Xl);
let upload = match chosen.clone() {
Some(path) => upload.image(path),
None => upload,
};
section(
"Avatar / AvatarUpload",
"A picture, or initials on a tone the name keeps, or an icon. Presence sits on the corner. Hover the last one, then press it to choose a picture, or drop one on it.",
cx,
)
.child(specimens().children(sizes.map(|(size, name)| {
specimen(name, Avatar::new(SharedString::from(format!("avatar-{name}")), "Kofi Mensah").size(size), cx)
})))
.child(
specimens()
.child(specimen("picture", Avatar::new("avatar-photo", "Dunes").image(Path::new(DUNES)).size(AvatarSize::Lg), cx))
.child(specimen("online", Avatar::new("avatar-online", "Aiko Tanaka").size(AvatarSize::Lg).presence(Presence::Online), cx))
.child(specimen("away", Avatar::new("avatar-away", "Lucía Romero").size(AvatarSize::Lg).presence(Presence::Away), cx))
.child(specimen("busy", Avatar::new("avatar-busy", "Ben Carter").size(AvatarSize::Lg).presence(Presence::Busy), cx))
.child(specimen("team", Avatar::new("avatar-team", "Studio North").size(AvatarSize::Lg).square(), cx))
.child(specimen("bot", Avatar::new("avatar-bot", "Build bot").icon(IconName::Bot).size(AvatarSize::Lg), cx))
.child(specimen(
"upload",
probe("avatar-upload", AvatarUpload::new("avatar-upload", upload).on_change(move |path, _, cx| set(&take, Some(path), cx))),
cx,
)),
)
.child(Caption::new(match chosen {
Some(path) => format!("Chose {}", path.display()),
None => "No picture chosen yet.".to_string(),
}))
}
pub fn groups(cx: &mut App) -> impl IntoElement + use<> {
const PEOPLE: [&str; 7] = [
"Ada Lovelace",
"Alan Turing",
"Grace Hopper",
"Katherine Johnson",
"Tim Berners-Lee",
"Radia Perlman",
"Sophie Wilson",
];
let theme = cx.theme();
let people = |prefix: &'static str| {
PEOPLE
.iter()
.enumerate()
.map(move |(ix, name)| Avatar::new(SharedString::from(format!("{prefix}-{ix}")), *name))
};
section(
"AvatarGroup / UserChip",
"People side by side, each tucked under the next, with a count of the rest; someone named inline.",
cx,
)
.child(
specimens()
.child(specimen("four of seven", AvatarGroup::new(people("group")), cx))
.child(specimen("small", AvatarGroup::new(people("group-sm")).max(5).size(AvatarSize::Sm), cx)),
)
.child(
div()
.flex()
.flex_wrap()
.items_center()
.gap_1p5()
.text_size(theme.text_size(TextSize::Base))
.child("Assigned to")
.child(UserChip::new("chip-grace", "Grace Hopper"))
.child("and")
.child(UserChip::new("chip-dunes", "Dune Studio").image(Path::new(DUNES)))
.child("for review."),
)
}
pub fn numbers(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let turn = keep("stats-turn", || 0usize, window, cx);
let (now, next) = (*turn.read(cx), turn.clone());
let revenue = [48_210.0, 51_380.0, 49_905.0][now % 3];
let users = [3_812.0, 3_994.0, 4_120.0][now % 3];
let latency = [212.0, 198.0, 187.0][now % 3];
section(
"Statistic / KPI Card / Metric / TrendIndicator",
"Figures that matter, rolling to each new value, with how they moved. For latency, down is the good way.",
cx,
)
.child(
div()
.flex()
.gap_3()
.child(
div().flex_1().child(
KpiCard::new(Statistic::new("kpi-revenue", "Revenue", revenue).prefix("$").trend(TrendIndicator::new(0.124)))
.icon(IconName::DollarSign)
.caption("This month, against the last"),
),
)
.child(
div().flex_1().child(
KpiCard::new(Statistic::new("kpi-users", "Active people", users).trend(TrendIndicator::new(0.031)))
.icon(IconName::Users)
.caption("Seen in the last seven days"),
),
)
.child(
div().flex_1().child(
KpiCard::new(
Statistic::new("kpi-latency", "p95 latency", latency)
.suffix("ms")
.trend(TrendIndicator::new(-0.08).down_is_good()),
)
.icon(IconName::Gauge)
.caption("Across all regions"),
),
),
)
.child(
specimens()
.child(specimen("metric", Statistic::new("metric-uptime", "Uptime", 99.98).decimals(2).suffix("%").size(TextSize::Lg), cx))
.child(specimen("metric", Statistic::new("metric-queue", "In queue", 14.0).size(TextSize::Lg), cx))
.child(specimen("flat", TrendIndicator::new(0.0), cx)),
)
.child(row().child(probe(
"stats-next",
Button::new("stats-next", "Next week")
.variant(ButtonVariant::Ghost)
.on_click(move |_, _, cx| set(&next, now + 1, cx)),
)))
}