tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
⎇ dba855e446ca2bd4e7d146bdc168fb232cad15a4
widgets/stat-card/src/lib.rs
History
Who touched what
raw
feat: llimphi standalone — framework UI soberano extraído del monorepo
1//! `llimphi-widget-stat-card` — tarjeta de dashboard con accent.
2//!
3//! Compone (sobre `llimphi-widget-card`):
4//! - **Border-l-4** con un color de accent que el caller decide.
5//! - **Label** chico arriba en el color del accent.
6//! - **Value** grande (28 px) en el color principal del texto.
7//! - **Description** chica en el color tenue.
8//! - **Listing opcional** de items recientes con sub-header
9//! `"recent (N):"`.
10//!
11//! Análogo Llimphi al `nahual-widget-stat-card` GPUI. Pensado para
12//! dashboards estilo `minga-explorer`, `brahman-broker-explorer`.
13
14#![forbid(unsafe_code)]
15
16use llimphi_ui::llimphi_layout::taffy::{
17 prelude::{length, percent, Size, Style},
18 Rect,
19};
20use llimphi_ui::llimphi_raster::peniko::Color;
21use llimphi_ui::llimphi_text::Alignment;
22use llimphi_ui::View;
23use llimphi_widget_card::{card_view, CardOptions, CardPalette};
24
25/// Paleta del stat-card. `accent` se setea por instancia (verde/rojo/
26/// ámbar etc.), los otros vienen del theme.
27#[derive(Debug, Clone, Copy)]
28pub struct StatCardPalette {
29 pub bg: Color,
30 pub fg_text: Color,
31 pub fg_muted: Color,
32}
33
34impl Default for StatCardPalette {
35 fn default() -> Self {
36 Self::from_theme(&llimphi_theme::Theme::dark())
37 }
38}
39
40impl StatCardPalette {
41 pub fn from_theme(t: &llimphi_theme::Theme) -> Self {
42 Self {
43 bg: t.bg_panel,
44 fg_text: t.fg_text,
45 fg_muted: t.fg_muted,
46 }
47 }
48}
49
50/// Compone un stat-card.
51///
52/// - `label`: header chico en color `accent`.
53/// - `value`: texto principal grande.
54/// - `description`: línea chica tenue debajo del value.
55/// - `accent`: color del border-l + del label.
56/// - `recent_items`: si no vacío, agrega "recent (N):" + una fila por
57/// item.
58pub fn stat_card_view<Msg: Clone + 'static>(
59 label: &str,
60 value: impl Into<String>,
61 description: &str,
62 accent: Color,
63 recent_items: &[String],
64 palette: &StatCardPalette,
65) -> View<Msg> {
66 let label_row = View::new(Style {
67 size: Size {
68 width: percent(1.0_f32),
69 height: length(14.0_f32),
70 },
71 ..Default::default()
72 })
73 .text_aligned(label.to_string(), 11.0, accent, Alignment::Start);
74
75 let value_row = View::new(Style {
76 size: Size {
77 width: percent(1.0_f32),
78 height: length(36.0_f32),
79 },
80 ..Default::default()
81 })
82 .text_aligned(value.into(), 28.0, palette.fg_text, Alignment::Start);
83
84 let desc_row = View::new(Style {
85 size: Size {
86 width: percent(1.0_f32),
87 height: length(14.0_f32),
88 },
89 ..Default::default()
90 })
91 .text_aligned(
92 description.to_string(),
93 11.0,
94 palette.fg_muted,
95 Alignment::Start,
96 );
97
98 let mut children: Vec<View<Msg>> = vec![label_row, value_row, desc_row];
99
100 if !recent_items.is_empty() {
101 children.push(
102 View::new(Style {
103 size: Size {
104 width: percent(1.0_f32),
105 height: length(16.0_f32),
106 },
107 padding: Rect {
108 left: length(0.0_f32),
109 right: length(0.0_f32),
110 top: length(6.0_f32),
111 bottom: length(0.0_f32),
112 },
113 ..Default::default()
114 })
115 .text_aligned(
116 format!("recent ({}):", recent_items.len()),
117 10.0,
118 palette.fg_muted,
119 Alignment::Start,
120 ),
121 );
122 for it in recent_items {
123 children.push(
124 View::new(Style {
125 size: Size {
126 width: percent(1.0_f32),
127 height: length(14.0_f32),
128 },
129 ..Default::default()
130 })
131 .text_aligned(it.clone(), 11.0, palette.fg_text, Alignment::Start),
132 );
133 }
134 }
135
136 card_view(
137 children,
138 CardOptions {
139 accent: Some(accent),
140 ..Default::default()
141 },
142 &CardPalette { bg: palette.bg },
143 )
144}