table.rs

  1use std::ops::Range;
  2
  3use db::smol::stream::iter;
  4use gpui::{Entity, FontWeight, Length, uniform_list};
  5use ui::{
  6    ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component,
  7    ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator, IntoElement,
  8    ParentElement, RegisterComponent, RenderOnce, Styled, StyledTypography, Window, div,
  9    example_group_with_title, px, single_example, v_flex,
 10};
 11
 12struct UniformListData {
 13    render_item_fn: Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<AnyElement>>,
 14    element_id: ElementId,
 15    row_count: usize,
 16}
 17
 18enum TableContents<const COLS: usize> {
 19    Vec(Vec<[AnyElement; COLS]>),
 20    UniformList(UniformListData),
 21}
 22
 23impl<const COLS: usize> TableContents<COLS> {
 24    fn rows_mut(&mut self) -> Option<&mut Vec<[AnyElement; COLS]>> {
 25        match self {
 26            TableContents::Vec(rows) => Some(rows),
 27            TableContents::UniformList(_) => None,
 28        }
 29    }
 30
 31    fn len(&self) -> usize {
 32        match self {
 33            TableContents::Vec(rows) => rows.len(),
 34            TableContents::UniformList(data) => data.row_count,
 35        }
 36    }
 37}
 38
 39/// A table component
 40#[derive(RegisterComponent, IntoElement)]
 41pub struct Table<const COLS: usize = 3> {
 42    striped: bool,
 43    width: Length,
 44    headers: Option<[AnyElement; COLS]>,
 45    rows: TableContents<COLS>,
 46}
 47
 48impl<const COLS: usize> Table<COLS> {
 49    pub fn uniform_list(
 50        id: impl Into<ElementId>,
 51        row_count: usize,
 52        render_item_fn: impl Fn(Range<usize>, &mut Window, &mut App) -> Vec<AnyElement> + 'static,
 53    ) -> Self {
 54        Table {
 55            striped: false,
 56            width: Length::Auto,
 57            headers: None,
 58            rows: TableContents::UniformList(UniformListData {
 59                element_id: id.into(),
 60                row_count: row_count,
 61                render_item_fn: Box::new(render_item_fn),
 62            }),
 63        }
 64    }
 65
 66    /// Create a new table with a column count equal to the
 67    /// number of headers provided.
 68    pub fn new() -> Self {
 69        Table {
 70            striped: false,
 71            width: Length::Auto,
 72            headers: None,
 73            rows: TableContents::Vec(Vec::new()),
 74        }
 75    }
 76
 77    /// Enables row striping.
 78    pub fn striped(mut self) -> Self {
 79        self.striped = true;
 80        self
 81    }
 82
 83    /// Sets the width of the table.
 84    pub fn width(mut self, width: impl Into<Length>) -> Self {
 85        self.width = width.into();
 86        self
 87    }
 88
 89    pub fn header(mut self, headers: [impl IntoElement; COLS]) -> Self {
 90        self.headers = Some(headers.map(IntoElement::into_any_element));
 91        self
 92    }
 93
 94    pub fn row(mut self, items: [impl IntoElement; COLS]) -> Self {
 95        if let Some(rows) = self.rows.rows_mut() {
 96            rows.push(items.map(IntoElement::into_any_element));
 97        }
 98        self
 99    }
100
101    pub fn render_row(&self, items: [impl IntoElement; COLS], cx: &mut App) -> AnyElement {
102        return render_row(0, items, self.rows.len(), self.striped, cx);
103    }
104
105    pub fn render_header(
106        &self,
107        headers: [impl IntoElement; COLS],
108        cx: &mut App,
109    ) -> impl IntoElement {
110        render_header(headers, cx)
111    }
112}
113
114fn base_cell_style(cx: &App) -> Div {
115    div()
116        .px_1p5()
117        .flex_1()
118        .justify_start()
119        .text_ui(cx)
120        .whitespace_nowrap()
121        .text_ellipsis()
122        .overflow_hidden()
123}
124
125pub fn render_row<const COLS: usize>(
126    row_index: usize,
127    items: [impl IntoElement; COLS],
128    row_count: usize,
129    striped: bool,
130    cx: &App,
131) -> AnyElement {
132    let is_last = row_index == row_count - 1;
133    let bg = if row_index % 2 == 1 && striped {
134        Some(cx.theme().colors().text.opacity(0.05))
135    } else {
136        None
137    };
138    div()
139        .w_full()
140        .flex()
141        .flex_row()
142        .items_center()
143        .justify_between()
144        .px_1p5()
145        .py_1()
146        .when_some(bg, |row, bg| row.bg(bg))
147        .when(!is_last, |row| {
148            row.border_b_1().border_color(cx.theme().colors().border)
149        })
150        .children(
151            items
152                .map(IntoElement::into_any_element)
153                .map(|cell| base_cell_style(cx).child(cell)),
154        )
155        .into_any_element()
156}
157
158pub fn render_header<const COLS: usize>(
159    headers: [impl IntoElement; COLS],
160    cx: &mut App,
161) -> impl IntoElement {
162    div()
163        .flex()
164        .flex_row()
165        .items_center()
166        .justify_between()
167        .w_full()
168        .p_2()
169        .border_b_1()
170        .border_color(cx.theme().colors().border)
171        .children(headers.into_iter().map(|h| {
172            base_cell_style(cx)
173                .font_weight(FontWeight::SEMIBOLD)
174                .child(h)
175        }))
176}
177
178impl<const COLS: usize> RenderOnce for Table<COLS> {
179    fn render(mut self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
180        // match self.ro
181        let row_count = self.rows.len();
182        div()
183            .w(self.width)
184            .overflow_hidden()
185            .when_some(self.headers.take(), |this, headers| {
186                this.child(render_header(headers, cx))
187            })
188            .map(|div| match self.rows {
189                TableContents::Vec(items) => div.children(
190                    items
191                        .into_iter()
192                        .enumerate()
193                        .map(|(index, row)| render_row(index, row, row_count, self.striped, cx)),
194                ),
195                TableContents::UniformList(uniform_list_data) => div.child(uniform_list(
196                    uniform_list_data.element_id,
197                    uniform_list_data.row_count,
198                    uniform_list_data.render_item_fn,
199                )),
200            })
201    }
202}
203
204impl Component for Table<3> {
205    fn scope() -> ComponentScope {
206        ComponentScope::Layout
207    }
208
209    fn description() -> Option<&'static str> {
210        Some("A table component for displaying data in rows and columns with optional styling.")
211    }
212
213    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
214        Some(
215            v_flex()
216                .gap_6()
217                .children(vec![
218                    example_group_with_title(
219                        "Basic Tables",
220                        vec![
221                            single_example(
222                                "Simple Table",
223                                Table::new()
224                                    .width(px(400.))
225                                    .header(["Name", "Age", "City"])
226                                    .row(["Alice", "28", "New York"])
227                                    .row(["Bob", "32", "San Francisco"])
228                                    .row(["Charlie", "25", "London"])
229                                    .into_any_element(),
230                            ),
231                            single_example(
232                                "Two Column Table",
233                                Table::new()
234                                    .header(["Category", "Value"])
235                                    .width(px(300.))
236                                    .row(["Revenue", "$100,000"])
237                                    .row(["Expenses", "$75,000"])
238                                    .row(["Profit", "$25,000"])
239                                    .into_any_element(),
240                            ),
241                        ],
242                    ),
243                    example_group_with_title(
244                        "Styled Tables",
245                        vec![
246                            single_example(
247                                "Default",
248                                Table::new()
249                                    .width(px(400.))
250                                    .header(["Product", "Price", "Stock"])
251                                    .row(["Laptop", "$999", "In Stock"])
252                                    .row(["Phone", "$599", "Low Stock"])
253                                    .row(["Tablet", "$399", "Out of Stock"])
254                                    .into_any_element(),
255                            ),
256                            single_example(
257                                "Striped",
258                                Table::new()
259                                    .width(px(400.))
260                                    .striped()
261                                    .header(["Product", "Price", "Stock"])
262                                    .row(["Laptop", "$999", "In Stock"])
263                                    .row(["Phone", "$599", "Low Stock"])
264                                    .row(["Tablet", "$399", "Out of Stock"])
265                                    .row(["Headphones", "$199", "In Stock"])
266                                    .into_any_element(),
267                            ),
268                        ],
269                    ),
270                    example_group_with_title(
271                        "Mixed Content Table",
272                        vec![single_example(
273                            "Table with Elements",
274                            Table::new()
275                                .width(px(840.))
276                                .header(["Status", "Name", "Priority", "Deadline", "Action"])
277                                .row([
278                                    Indicator::dot().color(Color::Success).into_any_element(),
279                                    "Project A".into_any_element(),
280                                    "High".into_any_element(),
281                                    "2023-12-31".into_any_element(),
282                                    Button::new("view_a", "View")
283                                        .style(ButtonStyle::Filled)
284                                        .full_width()
285                                        .into_any_element(),
286                                ])
287                                .row([
288                                    Indicator::dot().color(Color::Warning).into_any_element(),
289                                    "Project B".into_any_element(),
290                                    "Medium".into_any_element(),
291                                    "2024-03-15".into_any_element(),
292                                    Button::new("view_b", "View")
293                                        .style(ButtonStyle::Filled)
294                                        .full_width()
295                                        .into_any_element(),
296                                ])
297                                .row([
298                                    Indicator::dot().color(Color::Error).into_any_element(),
299                                    "Project C".into_any_element(),
300                                    "Low".into_any_element(),
301                                    "2024-06-30".into_any_element(),
302                                    Button::new("view_c", "View")
303                                        .style(ButtonStyle::Filled)
304                                        .full_width()
305                                        .into_any_element(),
306                                ])
307                                .into_any_element(),
308                        )],
309                    ),
310                ])
311                .into_any_element(),
312        )
313    }
314}