Skip to main content

ui/components/
table.rs

1//! Lightweight static table primitives (shadcn `Table` anatomy).
2//!
3//! For sortable/filterable tables use [`crate::Table`] in `data_table.rs`.
4
5use gpui::AnyElement;
6use smallvec::SmallVec;
7
8use crate::prelude::*;
9
10/// Root wrapper for a static HTML-style table layout.
11///
12/// For sortable/filterable data grids use [`crate::Table`] (`data_table.rs`).
13#[derive(IntoElement, RegisterComponent)]
14pub struct Table {
15    children: SmallVec<[AnyElement; 3]>,
16    caption: Option<AnyElement>,
17}
18
19impl Table {
20    pub fn new() -> Self {
21        Self {
22            children: SmallVec::new(),
23            caption: None,
24        }
25    }
26
27    pub fn caption(mut self, caption: impl IntoElement) -> Self {
28        self.caption = Some(caption.into_any_element());
29        self
30    }
31}
32
33impl Default for Table {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl ParentElement for Table {
40    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
41        self.children.extend(elements);
42    }
43}
44
45impl RenderOnce for Table {
46    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
47        v_flex()
48            .w_full()
49            .when_some(self.caption, |this, caption| this.child(caption))
50            .child(
51                v_flex()
52                    .w_full()
53                    .overflow_hidden()
54                    .rounded_md()
55                    .border_1()
56                    .border_color(semantic::border(cx))
57                    .children(self.children),
58            )
59    }
60}
61
62/// Table header section (`<thead>`).
63#[derive(IntoElement)]
64pub struct TableHeader {
65    children: SmallVec<[AnyElement; 1]>,
66}
67
68impl TableHeader {
69    pub fn new() -> Self {
70        Self {
71            children: SmallVec::new(),
72        }
73    }
74}
75
76impl Default for TableHeader {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl ParentElement for TableHeader {
83    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
84        self.children.extend(elements);
85    }
86}
87
88impl RenderOnce for TableHeader {
89    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
90        v_flex()
91            .w_full()
92            .bg(semantic::elevated_surface(cx))
93            .border_b_1()
94            .border_color(semantic::border_muted(cx))
95            .children(self.children)
96    }
97}
98
99/// Table body section (`<tbody>`).
100#[derive(IntoElement)]
101pub struct TableBody {
102    children: SmallVec<[AnyElement; 4]>,
103}
104
105impl TableBody {
106    pub fn new() -> Self {
107        Self {
108            children: SmallVec::new(),
109        }
110    }
111}
112
113impl Default for TableBody {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl ParentElement for TableBody {
120    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
121        self.children.extend(elements);
122    }
123}
124
125impl RenderOnce for TableBody {
126    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
127        v_flex().w_full().children(self.children)
128    }
129}
130
131/// Table footer section (`<tfoot>`).
132#[derive(IntoElement)]
133pub struct TableFooter {
134    children: SmallVec<[AnyElement; 1]>,
135}
136
137impl TableFooter {
138    pub fn new() -> Self {
139        Self {
140            children: SmallVec::new(),
141        }
142    }
143}
144
145impl Default for TableFooter {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151impl ParentElement for TableFooter {
152    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
153        self.children.extend(elements);
154    }
155}
156
157impl RenderOnce for TableFooter {
158    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
159        v_flex()
160            .w_full()
161            .bg(semantic::elevated_surface(cx))
162            .border_t_1()
163            .border_color(semantic::border_muted(cx))
164            .children(self.children)
165    }
166}
167
168/// A single table row.
169#[derive(IntoElement)]
170pub struct TableRow {
171    children: SmallVec<[AnyElement; 4]>,
172    hover: bool,
173}
174
175impl TableRow {
176    pub fn new() -> Self {
177        Self {
178            children: SmallVec::new(),
179            hover: true,
180        }
181    }
182
183    pub fn hover(mut self, hover: bool) -> Self {
184        self.hover = hover;
185        self
186    }
187}
188
189impl Default for TableRow {
190    fn default() -> Self {
191        Self::new()
192    }
193}
194
195impl ParentElement for TableRow {
196    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
197        self.children.extend(elements);
198    }
199}
200
201impl RenderOnce for TableRow {
202    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
203        let hover_bg = semantic::hover_bg(cx);
204        h_flex()
205            .w_full()
206            .border_b_1()
207            .border_color(semantic::border_muted(cx))
208            .when(self.hover, |this| this.hover(move |s| s.bg(hover_bg)))
209            .children(self.children)
210    }
211}
212
213/// Header cell (`<th>`).
214#[derive(IntoElement)]
215pub struct TableHead {
216    children: SmallVec<[AnyElement; 1]>,
217    align_end: bool,
218}
219
220impl TableHead {
221    pub fn new() -> Self {
222        Self {
223            children: SmallVec::new(),
224            align_end: false,
225        }
226    }
227
228    pub fn align_end(mut self, align_end: bool) -> Self {
229        self.align_end = align_end;
230        self
231    }
232}
233
234impl Default for TableHead {
235    fn default() -> Self {
236        Self::new()
237    }
238}
239
240impl ParentElement for TableHead {
241    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
242        self.children.extend(elements);
243    }
244}
245
246impl RenderOnce for TableHead {
247    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
248        h_flex()
249            .flex_1()
250            .px_4()
251            .py_3()
252            .when(self.align_end, |this| this.justify_end())
253            .children(self.children)
254    }
255}
256
257/// Body cell (`<td>`).
258#[derive(IntoElement)]
259pub struct TableCell {
260    children: SmallVec<[AnyElement; 1]>,
261    align_end: bool,
262}
263
264impl TableCell {
265    pub fn new() -> Self {
266        Self {
267            children: SmallVec::new(),
268            align_end: false,
269        }
270    }
271
272    pub fn align_end(mut self, align_end: bool) -> Self {
273        self.align_end = align_end;
274        self
275    }
276}
277
278impl Default for TableCell {
279    fn default() -> Self {
280        Self::new()
281    }
282}
283
284impl ParentElement for TableCell {
285    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
286        self.children.extend(elements);
287    }
288}
289
290impl RenderOnce for TableCell {
291    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
292        h_flex()
293            .flex_1()
294            .px_4()
295            .py_3()
296            .when(self.align_end, |this| this.justify_end())
297            .children(self.children)
298    }
299}
300
301/// Optional table caption shown above the bordered table.
302#[derive(IntoElement)]
303pub struct TableCaption {
304    label: SharedString,
305}
306
307impl TableCaption {
308    pub fn new(label: impl Into<SharedString>) -> Self {
309        Self {
310            label: label.into(),
311        }
312    }
313}
314
315impl RenderOnce for TableCaption {
316    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
317        div().mb_2().child(
318            Label::new(self.label)
319                .size(LabelSize::Small)
320                .color(Color::Muted),
321        )
322    }
323}
324
325impl Component for Table {
326    fn scope() -> ComponentScope {
327        ComponentScope::DataDisplay
328    }
329
330    fn description() -> Option<&'static str> {
331        Some("Lightweight static table primitives (Root/Header/Body/Row/Head/Cell/Caption).")
332    }
333
334    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
335        Some(
336            Table::new()
337                .caption(TableCaption::new("A list of recent invoices."))
338                .child(
339                    TableHeader::new().child(
340                        TableRow::new().hover(false).children([
341                            TableHead::new()
342                                .child(Label::new("Invoice").weight(gpui::FontWeight::SEMIBOLD)),
343                            TableHead::new()
344                                .align_end(true)
345                                .child(Label::new("Amount").weight(gpui::FontWeight::SEMIBOLD)),
346                        ]),
347                    ),
348                )
349                .child(
350                    TableBody::new()
351                        .child(
352                            TableRow::new().children([
353                                TableCell::new().child(Label::new("INV001")),
354                                TableCell::new()
355                                    .align_end(true)
356                                    .child(Label::new("$250.00")),
357                            ]),
358                        )
359                        .child(
360                            TableRow::new().children([
361                                TableCell::new().child(Label::new("INV002")),
362                                TableCell::new()
363                                    .align_end(true)
364                                    .child(Label::new("$150.00")),
365                            ]),
366                        ),
367                )
368                .into_any_element(),
369        )
370    }
371}