data_table.rs

  1use std::{ops::Range, rc::Rc};
  2
  3use gpui::{
  4    DefiniteLength, Entity, EntityId, FocusHandle, Length, ListHorizontalSizingBehavior,
  5    ListSizingBehavior, ListState, Point, Stateful, UniformListScrollHandle, WeakEntity, list,
  6    transparent_black, uniform_list,
  7};
  8
  9use crate::{
 10    ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component,
 11    ComponentScope, Context, Div, ElementId, FixedWidth as _, FluentBuilder as _, HeaderResizeInfo,
 12    Indicator, InteractiveElement, IntoElement, ParentElement, Pixels, RedistributableColumnsState,
 13    RegisterComponent, RenderOnce, ScrollAxes, ScrollableHandle, Scrollbars, SharedString,
 14    StatefulInteractiveElement, Styled, StyledExt as _, StyledTypography, Window, WithScrollbar,
 15    bind_redistributable_columns, div, example_group_with_title, h_flex, px,
 16    render_redistributable_columns_resize_handles, single_example,
 17    table_row::{IntoTableRow as _, TableRow},
 18    v_flex,
 19};
 20
 21pub mod table_row;
 22#[cfg(test)]
 23mod tests;
 24
 25/// Represents an unchecked table row, which is a vector of elements.
 26/// Will be converted into `TableRow<T>` internally
 27pub type UncheckedTableRow<T> = Vec<T>;
 28
 29struct UniformListData {
 30    render_list_of_rows_fn:
 31        Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<UncheckedTableRow<AnyElement>>>,
 32    element_id: ElementId,
 33    row_count: usize,
 34}
 35
 36struct VariableRowHeightListData {
 37    /// Unlike UniformList, this closure renders only single row, allowing each one to have its own height
 38    render_row_fn: Box<dyn Fn(usize, &mut Window, &mut App) -> UncheckedTableRow<AnyElement>>,
 39    list_state: ListState,
 40    row_count: usize,
 41}
 42
 43enum TableContents {
 44    Vec(Vec<TableRow<AnyElement>>),
 45    UniformList(UniformListData),
 46    VariableRowHeightList(VariableRowHeightListData),
 47}
 48
 49impl TableContents {
 50    fn rows_mut(&mut self) -> Option<&mut Vec<TableRow<AnyElement>>> {
 51        match self {
 52            TableContents::Vec(rows) => Some(rows),
 53            TableContents::UniformList(_) => None,
 54            TableContents::VariableRowHeightList(_) => None,
 55        }
 56    }
 57
 58    fn len(&self) -> usize {
 59        match self {
 60            TableContents::Vec(rows) => rows.len(),
 61            TableContents::UniformList(data) => data.row_count,
 62            TableContents::VariableRowHeightList(data) => data.row_count,
 63        }
 64    }
 65
 66    fn is_empty(&self) -> bool {
 67        self.len() == 0
 68    }
 69}
 70
 71pub struct TableInteractionState {
 72    pub focus_handle: FocusHandle,
 73    pub scroll_handle: UniformListScrollHandle,
 74    pub custom_scrollbar: Option<Scrollbars>,
 75}
 76
 77impl TableInteractionState {
 78    pub fn new(cx: &mut App) -> Self {
 79        Self {
 80            focus_handle: cx.focus_handle(),
 81            scroll_handle: UniformListScrollHandle::new(),
 82            custom_scrollbar: None,
 83        }
 84    }
 85
 86    pub fn with_custom_scrollbar(mut self, custom_scrollbar: Scrollbars) -> Self {
 87        self.custom_scrollbar = Some(custom_scrollbar);
 88        self
 89    }
 90
 91    pub fn scroll_offset(&self) -> Point<Pixels> {
 92        self.scroll_handle.offset()
 93    }
 94
 95    pub fn set_scroll_offset(&self, offset: Point<Pixels>) {
 96        self.scroll_handle.set_offset(offset);
 97    }
 98
 99    pub fn listener<E: ?Sized>(
100        this: &Entity<Self>,
101        f: impl Fn(&mut Self, &E, &mut Window, &mut Context<Self>) + 'static,
102    ) -> impl Fn(&E, &mut Window, &mut App) + 'static {
103        let view = this.downgrade();
104        move |e: &E, window: &mut Window, cx: &mut App| {
105            view.update(cx, |view, cx| f(view, e, window, cx)).ok();
106        }
107    }
108}
109
110pub enum ColumnWidthConfig {
111    /// Static column widths (no resize handles).
112    Static {
113        widths: StaticColumnWidths,
114        /// Controls widths of the whole table.
115        table_width: Option<DefiniteLength>,
116    },
117    /// Redistributable columns — dragging redistributes the fixed available space
118    /// among columns without changing the overall table width.
119    Redistributable {
120        columns_state: Entity<RedistributableColumnsState>,
121        table_width: Option<DefiniteLength>,
122    },
123}
124
125pub enum StaticColumnWidths {
126    /// All columns share space equally (flex-1 / Length::Auto).
127    Auto,
128    /// Each column has a specific width.
129    Explicit(TableRow<DefiniteLength>),
130}
131
132impl ColumnWidthConfig {
133    /// Auto-width columns, auto-size table.
134    pub fn auto() -> Self {
135        ColumnWidthConfig::Static {
136            widths: StaticColumnWidths::Auto,
137            table_width: None,
138        }
139    }
140
141    /// Redistributable columns with no fixed table width.
142    pub fn redistributable(columns_state: Entity<RedistributableColumnsState>) -> Self {
143        ColumnWidthConfig::Redistributable {
144            columns_state,
145            table_width: None,
146        }
147    }
148
149    /// Auto-width columns, fixed table width.
150    pub fn auto_with_table_width(width: impl Into<DefiniteLength>) -> Self {
151        ColumnWidthConfig::Static {
152            widths: StaticColumnWidths::Auto,
153            table_width: Some(width.into()),
154        }
155    }
156
157    /// Explicit column widths with no fixed table width.
158    pub fn explicit<T: Into<DefiniteLength>>(widths: Vec<T>) -> Self {
159        let cols = widths.len();
160        ColumnWidthConfig::Static {
161            widths: StaticColumnWidths::Explicit(
162                widths
163                    .into_iter()
164                    .map(Into::into)
165                    .collect::<Vec<_>>()
166                    .into_table_row(cols),
167            ),
168            table_width: None,
169        }
170    }
171
172    /// Column widths for rendering.
173    pub fn widths_to_render(&self, cx: &App) -> Option<TableRow<Length>> {
174        match self {
175            ColumnWidthConfig::Static {
176                widths: StaticColumnWidths::Auto,
177                ..
178            } => None,
179            ColumnWidthConfig::Static {
180                widths: StaticColumnWidths::Explicit(widths),
181                ..
182            } => Some(widths.map_cloned(Length::Definite)),
183            ColumnWidthConfig::Redistributable {
184                columns_state: entity,
185                ..
186            } => Some(entity.read(cx).widths_to_render()),
187        }
188    }
189
190    /// Table-level width.
191    pub fn table_width(&self) -> Option<Length> {
192        match self {
193            ColumnWidthConfig::Static { table_width, .. }
194            | ColumnWidthConfig::Redistributable { table_width, .. } => {
195                table_width.map(Length::Definite)
196            }
197        }
198    }
199
200    /// ListHorizontalSizingBehavior for uniform_list.
201    pub fn list_horizontal_sizing(&self) -> ListHorizontalSizingBehavior {
202        match self.table_width() {
203            Some(_) => ListHorizontalSizingBehavior::Unconstrained,
204            None => ListHorizontalSizingBehavior::FitList,
205        }
206    }
207}
208
209/// A table component
210#[derive(RegisterComponent, IntoElement)]
211pub struct Table {
212    striped: bool,
213    show_row_borders: bool,
214    show_row_hover: bool,
215    headers: Option<TableRow<AnyElement>>,
216    rows: TableContents,
217    interaction_state: Option<WeakEntity<TableInteractionState>>,
218    column_width_config: ColumnWidthConfig,
219    map_row: Option<Rc<dyn Fn((usize, Stateful<Div>), &mut Window, &mut App) -> AnyElement>>,
220    use_ui_font: bool,
221    empty_table_callback: Option<Rc<dyn Fn(&mut Window, &mut App) -> AnyElement>>,
222    /// The number of columns in the table. Used to assert column numbers in `TableRow` collections
223    cols: usize,
224    disable_base_cell_style: bool,
225}
226
227impl Table {
228    /// Creates a new table with the specified number of columns.
229    pub fn new(cols: usize) -> Self {
230        Self {
231            cols,
232            striped: false,
233            show_row_borders: true,
234            show_row_hover: true,
235            headers: None,
236            rows: TableContents::Vec(Vec::new()),
237            interaction_state: None,
238            map_row: None,
239            use_ui_font: true,
240            empty_table_callback: None,
241            disable_base_cell_style: false,
242            column_width_config: ColumnWidthConfig::auto(),
243        }
244    }
245
246    /// Disables based styling of row cell (paddings, text ellipsis, nowrap, etc), keeping width settings
247    ///
248    /// Doesn't affect base style of header cell.
249    /// Doesn't remove overflow-hidden
250    pub fn disable_base_style(mut self) -> Self {
251        self.disable_base_cell_style = true;
252        self
253    }
254
255    /// Enables uniform list rendering.
256    /// The provided function will be passed directly to the `uniform_list` element.
257    /// Therefore, if this method is called, any calls to [`Table::row`] before or after
258    /// this method is called will be ignored.
259    pub fn uniform_list(
260        mut self,
261        id: impl Into<ElementId>,
262        row_count: usize,
263        render_item_fn: impl Fn(
264            Range<usize>,
265            &mut Window,
266            &mut App,
267        ) -> Vec<UncheckedTableRow<AnyElement>>
268        + 'static,
269    ) -> Self {
270        self.rows = TableContents::UniformList(UniformListData {
271            element_id: id.into(),
272            row_count,
273            render_list_of_rows_fn: Box::new(render_item_fn),
274        });
275        self
276    }
277
278    /// Enables rendering of tables with variable row heights, allowing each row to have its own height.
279    ///
280    /// This mode is useful for displaying content such as CSV data or multiline cells, where rows may not have uniform heights.
281    /// It is generally slower than [`Table::uniform_list`] due to the need to measure each row individually, but it provides correct layout for non-uniform or multiline content.
282    ///
283    /// # Parameters
284    /// - `row_count`: The total number of rows in the table.
285    /// - `list_state`: The [`ListState`] used for managing scroll position and virtualization. This must be initialized and managed by the caller, and should be kept in sync with the number of rows.
286    /// - `render_row_fn`: A closure that renders a single row, given the row index, a mutable reference to [`Window`], and a mutable reference to [`App`]. It should return an array of [`AnyElement`]s, one for each column.
287    pub fn variable_row_height_list(
288        mut self,
289        row_count: usize,
290        list_state: ListState,
291        render_row_fn: impl Fn(usize, &mut Window, &mut App) -> UncheckedTableRow<AnyElement> + 'static,
292    ) -> Self {
293        self.rows = TableContents::VariableRowHeightList(VariableRowHeightListData {
294            render_row_fn: Box::new(render_row_fn),
295            list_state,
296            row_count,
297        });
298        self
299    }
300
301    /// Enables row striping (alternating row colors)
302    pub fn striped(mut self) -> Self {
303        self.striped = true;
304        self
305    }
306
307    /// Hides the border lines between rows
308    pub fn hide_row_borders(mut self) -> Self {
309        self.show_row_borders = false;
310        self
311    }
312
313    /// Sets a fixed table width with auto column widths.
314    ///
315    /// This is a shorthand for `.width_config(ColumnWidthConfig::auto_with_table_width(width))`.
316    /// For resizable columns or explicit column widths, use [`Table::width_config`] directly.
317    pub fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
318        self.column_width_config = ColumnWidthConfig::auto_with_table_width(width);
319        self
320    }
321
322    /// Sets the column width configuration for the table.
323    pub fn width_config(mut self, config: ColumnWidthConfig) -> Self {
324        self.column_width_config = config;
325        self
326    }
327
328    /// Enables interaction (primarily scrolling) with the table.
329    ///
330    /// Vertical scrolling will be enabled by default if the table is taller than its container.
331    ///
332    /// Horizontal scrolling will only be enabled if a table width is set via [`ColumnWidthConfig`],
333    /// otherwise the list will always shrink the table columns to fit their contents.
334    pub fn interactable(mut self, interaction_state: &Entity<TableInteractionState>) -> Self {
335        self.interaction_state = Some(interaction_state.downgrade());
336        self
337    }
338
339    pub fn header(mut self, headers: UncheckedTableRow<impl IntoElement>) -> Self {
340        self.headers = Some(
341            headers
342                .into_table_row(self.cols)
343                .map(IntoElement::into_any_element),
344        );
345        self
346    }
347
348    pub fn row(mut self, items: UncheckedTableRow<impl IntoElement>) -> Self {
349        if let Some(rows) = self.rows.rows_mut() {
350            rows.push(
351                items
352                    .into_table_row(self.cols)
353                    .map(IntoElement::into_any_element),
354            );
355        }
356        self
357    }
358
359    pub fn no_ui_font(mut self) -> Self {
360        self.use_ui_font = false;
361        self
362    }
363
364    pub fn map_row(
365        mut self,
366        callback: impl Fn((usize, Stateful<Div>), &mut Window, &mut App) -> AnyElement + 'static,
367    ) -> Self {
368        self.map_row = Some(Rc::new(callback));
369        self
370    }
371
372    /// Hides the default hover background on table rows.
373    /// Use this when you want to handle row hover styling manually via `map_row`.
374    pub fn hide_row_hover(mut self) -> Self {
375        self.show_row_hover = false;
376        self
377    }
378
379    /// Provide a callback that is invoked when the table is rendered without any rows
380    pub fn empty_table_callback(
381        mut self,
382        callback: impl Fn(&mut Window, &mut App) -> AnyElement + 'static,
383    ) -> Self {
384        self.empty_table_callback = Some(Rc::new(callback));
385        self
386    }
387}
388
389fn base_cell_style(width: Option<Length>) -> Div {
390    div()
391        .px_1p5()
392        .when_some(width, |this, width| this.w(width))
393        .when(width.is_none(), |this| this.flex_1())
394        .whitespace_nowrap()
395        .text_ellipsis()
396        .overflow_hidden()
397}
398
399fn base_cell_style_text(width: Option<Length>, use_ui_font: bool, cx: &App) -> Div {
400    base_cell_style(width).when(use_ui_font, |el| el.text_ui(cx))
401}
402
403pub fn render_table_row(
404    row_index: usize,
405    items: TableRow<impl IntoElement>,
406    table_context: TableRenderContext,
407    window: &mut Window,
408    cx: &mut App,
409) -> AnyElement {
410    let is_striped = table_context.striped;
411    let is_last = row_index == table_context.total_row_count - 1;
412    let bg = if row_index % 2 == 1 && is_striped {
413        Some(cx.theme().colors().text.opacity(0.05))
414    } else {
415        None
416    };
417    let cols = items.cols();
418    let column_widths = table_context
419        .column_widths
420        .map_or(vec![None; cols].into_table_row(cols), |widths| {
421            widths.map(Some)
422        });
423
424    let mut row = div()
425        // NOTE: `h_flex()` sneakily applies `items_center()` which is not default behavior for div element.
426        // Applying `.flex().flex_row()` manually to overcome that
427        .flex()
428        .flex_row()
429        .id(("table_row", row_index))
430        .size_full()
431        .when_some(bg, |row, bg| row.bg(bg))
432        .when(table_context.show_row_hover, |row| {
433            row.hover(|s| s.bg(cx.theme().colors().element_hover.opacity(0.6)))
434        })
435        .when(!is_striped && table_context.show_row_borders, |row| {
436            row.border_b_1()
437                .border_color(transparent_black())
438                .when(!is_last, |row| row.border_color(cx.theme().colors().border))
439        });
440
441    row = row.children(
442        items
443            .map(IntoElement::into_any_element)
444            .into_vec()
445            .into_iter()
446            .zip(column_widths.into_vec())
447            .map(|(cell, width)| {
448                if table_context.disable_base_cell_style {
449                    div()
450                        .when_some(width, |this, width| this.w(width))
451                        .when(width.is_none(), |this| this.flex_1())
452                        .overflow_hidden()
453                        .child(cell)
454                } else {
455                    base_cell_style_text(width, table_context.use_ui_font, cx)
456                        .px_1()
457                        .py_0p5()
458                        .child(cell)
459                }
460            }),
461    );
462
463    let row = if let Some(map_row) = table_context.map_row {
464        map_row((row_index, row), window, cx)
465    } else {
466        row.into_any_element()
467    };
468
469    div().size_full().child(row).into_any_element()
470}
471
472pub fn render_table_header(
473    headers: TableRow<impl IntoElement>,
474    table_context: TableRenderContext,
475    resize_info: Option<HeaderResizeInfo>,
476    entity_id: Option<EntityId>,
477    cx: &mut App,
478) -> impl IntoElement {
479    let cols = headers.cols();
480    let column_widths = table_context
481        .column_widths
482        .map_or(vec![None; cols].into_table_row(cols), |widths| {
483            widths.map(Some)
484        });
485
486    let element_id = entity_id
487        .map(|entity| entity.to_string())
488        .unwrap_or_default();
489
490    let shared_element_id: SharedString = format!("table-{}", element_id).into();
491
492    div()
493        .flex()
494        .flex_row()
495        .items_center()
496        .w_full()
497        .border_b_1()
498        .border_color(cx.theme().colors().border)
499        .children(
500            headers
501                .into_vec()
502                .into_iter()
503                .enumerate()
504                .zip(column_widths.into_vec())
505                .map(|((header_idx, h), width)| {
506                    base_cell_style_text(width, table_context.use_ui_font, cx)
507                        .px_1()
508                        .py_0p5()
509                        .child(h)
510                        .id(ElementId::NamedInteger(
511                            shared_element_id.clone(),
512                            header_idx as u64,
513                        ))
514                        .when_some(resize_info.as_ref().cloned(), |this, info| {
515                            if info.resize_behavior[header_idx].is_resizable() {
516                                this.on_click(move |event, window, cx| {
517                                    if event.click_count() > 1 {
518                                        info.columns_state
519                                            .update(cx, |column, _| {
520                                                column.reset_column_to_initial_width(
521                                                    header_idx, window,
522                                                );
523                                            })
524                                            .ok();
525                                    }
526                                })
527                            } else {
528                                this
529                            }
530                        })
531                }),
532        )
533}
534
535#[derive(Clone)]
536pub struct TableRenderContext {
537    pub striped: bool,
538    pub show_row_borders: bool,
539    pub show_row_hover: bool,
540    pub total_row_count: usize,
541    pub column_widths: Option<TableRow<Length>>,
542    pub map_row: Option<Rc<dyn Fn((usize, Stateful<Div>), &mut Window, &mut App) -> AnyElement>>,
543    pub use_ui_font: bool,
544    pub disable_base_cell_style: bool,
545}
546
547impl TableRenderContext {
548    fn new(table: &Table, cx: &App) -> Self {
549        Self {
550            striped: table.striped,
551            show_row_borders: table.show_row_borders,
552            show_row_hover: table.show_row_hover,
553            total_row_count: table.rows.len(),
554            column_widths: table.column_width_config.widths_to_render(cx),
555            map_row: table.map_row.clone(),
556            use_ui_font: table.use_ui_font,
557            disable_base_cell_style: table.disable_base_cell_style,
558        }
559    }
560
561    pub fn for_column_widths(column_widths: Option<TableRow<Length>>, use_ui_font: bool) -> Self {
562        Self {
563            striped: false,
564            show_row_borders: true,
565            show_row_hover: true,
566            total_row_count: 0,
567            column_widths,
568            map_row: None,
569            use_ui_font,
570            disable_base_cell_style: false,
571        }
572    }
573}
574
575impl RenderOnce for Table {
576    fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
577        let table_context = TableRenderContext::new(&self, cx);
578        let interaction_state = self.interaction_state.and_then(|state| state.upgrade());
579
580        let header_resize_info =
581            interaction_state
582                .as_ref()
583                .and_then(|_| match &self.column_width_config {
584                    ColumnWidthConfig::Redistributable { columns_state, .. } => {
585                        Some(HeaderResizeInfo::from_state(columns_state, cx))
586                    }
587                    _ => None,
588                });
589
590        let table_width = self.column_width_config.table_width();
591        let horizontal_sizing = self.column_width_config.list_horizontal_sizing();
592        let no_rows_rendered = self.rows.is_empty();
593
594        // Extract redistributable entity for drag/drop/prepaint handlers
595        let redistributable_entity =
596            interaction_state
597                .as_ref()
598                .and_then(|_| match &self.column_width_config {
599                    ColumnWidthConfig::Redistributable {
600                        columns_state: entity,
601                        ..
602                    } => Some(entity.clone()),
603                    _ => None,
604                });
605
606        let resize_handles =
607            interaction_state
608                .as_ref()
609                .and_then(|_| match &self.column_width_config {
610                    ColumnWidthConfig::Redistributable { columns_state, .. } => Some(
611                        render_redistributable_columns_resize_handles(columns_state, window, cx),
612                    ),
613                    _ => None,
614                });
615
616        let table = div()
617            .when_some(table_width, |this, width| this.w(width))
618            .h_full()
619            .v_flex()
620            .when_some(self.headers.take(), |this, headers| {
621                this.child(render_table_header(
622                    headers,
623                    table_context.clone(),
624                    header_resize_info,
625                    interaction_state.as_ref().map(Entity::entity_id),
626                    cx,
627                ))
628            })
629            .when_some(redistributable_entity, |this, widths| {
630                bind_redistributable_columns(this, widths)
631            })
632            .child({
633                let content = div()
634                    .flex_grow()
635                    .w_full()
636                    .relative()
637                    .overflow_hidden()
638                    .map(|parent| match self.rows {
639                        TableContents::Vec(items) => {
640                            parent.children(items.into_iter().enumerate().map(|(index, row)| {
641                                div().child(render_table_row(
642                                    index,
643                                    row,
644                                    table_context.clone(),
645                                    window,
646                                    cx,
647                                ))
648                            }))
649                        }
650                        TableContents::UniformList(uniform_list_data) => parent.child(
651                            uniform_list(
652                                uniform_list_data.element_id,
653                                uniform_list_data.row_count,
654                                {
655                                    let render_item_fn = uniform_list_data.render_list_of_rows_fn;
656                                    move |range: Range<usize>, window, cx| {
657                                        let elements = render_item_fn(range.clone(), window, cx)
658                                            .into_iter()
659                                            .map(|raw_row| raw_row.into_table_row(self.cols))
660                                            .collect::<Vec<_>>();
661                                        elements
662                                            .into_iter()
663                                            .zip(range)
664                                            .map(|(row, row_index)| {
665                                                render_table_row(
666                                                    row_index,
667                                                    row,
668                                                    table_context.clone(),
669                                                    window,
670                                                    cx,
671                                                )
672                                            })
673                                            .collect()
674                                    }
675                                },
676                            )
677                            .size_full()
678                            .flex_grow()
679                            .with_sizing_behavior(ListSizingBehavior::Auto)
680                            .with_horizontal_sizing_behavior(horizontal_sizing)
681                            .when_some(
682                                interaction_state.as_ref(),
683                                |this, state| {
684                                    this.track_scroll(
685                                        &state.read_with(cx, |s, _| s.scroll_handle.clone()),
686                                    )
687                                },
688                            ),
689                        ),
690                        TableContents::VariableRowHeightList(variable_list_data) => parent.child(
691                            list(variable_list_data.list_state.clone(), {
692                                let render_item_fn = variable_list_data.render_row_fn;
693                                move |row_index: usize, window: &mut Window, cx: &mut App| {
694                                    let row = render_item_fn(row_index, window, cx)
695                                        .into_table_row(self.cols);
696                                    render_table_row(
697                                        row_index,
698                                        row,
699                                        table_context.clone(),
700                                        window,
701                                        cx,
702                                    )
703                                }
704                            })
705                            .size_full()
706                            .flex_grow()
707                            .with_sizing_behavior(ListSizingBehavior::Auto),
708                        ),
709                    })
710                    .when_some(resize_handles, |parent, handles| parent.child(handles));
711
712                if let Some(state) = interaction_state.as_ref() {
713                    let scrollbars = state
714                        .read(cx)
715                        .custom_scrollbar
716                        .clone()
717                        .unwrap_or_else(|| Scrollbars::new(ScrollAxes::Both));
718                    content
719                        .custom_scrollbars(
720                            scrollbars.tracked_scroll_handle(&state.read(cx).scroll_handle),
721                            window,
722                            cx,
723                        )
724                        .into_any_element()
725                } else {
726                    content.into_any_element()
727                }
728            })
729            .when_some(
730                no_rows_rendered
731                    .then_some(self.empty_table_callback)
732                    .flatten(),
733                |this, callback| {
734                    this.child(
735                        h_flex()
736                            .size_full()
737                            .p_3()
738                            .items_start()
739                            .justify_center()
740                            .child(callback(window, cx)),
741                    )
742                },
743            );
744
745        if let Some(interaction_state) = interaction_state.as_ref() {
746            table
747                .track_focus(&interaction_state.read(cx).focus_handle)
748                .id(("table", interaction_state.entity_id()))
749                .into_any_element()
750        } else {
751            table.into_any_element()
752        }
753    }
754}
755
756impl Component for Table {
757    fn scope() -> ComponentScope {
758        ComponentScope::Layout
759    }
760
761    fn description() -> Option<&'static str> {
762        Some("A table component for displaying data in rows and columns with optional styling.")
763    }
764
765    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
766        Some(
767            v_flex()
768                .gap_6()
769                .children(vec![
770                    example_group_with_title(
771                        "Basic Tables",
772                        vec![
773                            single_example(
774                                "Simple Table",
775                                Table::new(3)
776                                    .width(px(400.))
777                                    .header(vec!["Name", "Age", "City"])
778                                    .row(vec!["Alice", "28", "New York"])
779                                    .row(vec!["Bob", "32", "San Francisco"])
780                                    .row(vec!["Charlie", "25", "London"])
781                                    .into_any_element(),
782                            ),
783                            single_example(
784                                "Two Column Table",
785                                Table::new(2)
786                                    .header(vec!["Category", "Value"])
787                                    .width(px(300.))
788                                    .row(vec!["Revenue", "$100,000"])
789                                    .row(vec!["Expenses", "$75,000"])
790                                    .row(vec!["Profit", "$25,000"])
791                                    .into_any_element(),
792                            ),
793                        ],
794                    ),
795                    example_group_with_title(
796                        "Styled Tables",
797                        vec![
798                            single_example(
799                                "Default",
800                                Table::new(3)
801                                    .width(px(400.))
802                                    .header(vec!["Product", "Price", "Stock"])
803                                    .row(vec!["Laptop", "$999", "In Stock"])
804                                    .row(vec!["Phone", "$599", "Low Stock"])
805                                    .row(vec!["Tablet", "$399", "Out of Stock"])
806                                    .into_any_element(),
807                            ),
808                            single_example(
809                                "Striped",
810                                Table::new(3)
811                                    .width(px(400.))
812                                    .striped()
813                                    .header(vec!["Product", "Price", "Stock"])
814                                    .row(vec!["Laptop", "$999", "In Stock"])
815                                    .row(vec!["Phone", "$599", "Low Stock"])
816                                    .row(vec!["Tablet", "$399", "Out of Stock"])
817                                    .row(vec!["Headphones", "$199", "In Stock"])
818                                    .into_any_element(),
819                            ),
820                        ],
821                    ),
822                    example_group_with_title(
823                        "Mixed Content Table",
824                        vec![single_example(
825                            "Table with Elements",
826                            Table::new(5)
827                                .width(px(840.))
828                                .header(vec!["Status", "Name", "Priority", "Deadline", "Action"])
829                                .row(vec![
830                                    Indicator::dot().color(Color::Success).into_any_element(),
831                                    "Project A".into_any_element(),
832                                    "High".into_any_element(),
833                                    "2023-12-31".into_any_element(),
834                                    Button::new("view_a", "View")
835                                        .style(ButtonStyle::Filled)
836                                        .full_width()
837                                        .into_any_element(),
838                                ])
839                                .row(vec![
840                                    Indicator::dot().color(Color::Warning).into_any_element(),
841                                    "Project B".into_any_element(),
842                                    "Medium".into_any_element(),
843                                    "2024-03-15".into_any_element(),
844                                    Button::new("view_b", "View")
845                                        .style(ButtonStyle::Filled)
846                                        .full_width()
847                                        .into_any_element(),
848                                ])
849                                .row(vec![
850                                    Indicator::dot().color(Color::Error).into_any_element(),
851                                    "Project C".into_any_element(),
852                                    "Low".into_any_element(),
853                                    "2024-06-30".into_any_element(),
854                                    Button::new("view_c", "View")
855                                        .style(ButtonStyle::Filled)
856                                        .full_width()
857                                        .into_any_element(),
858                                ])
859                                .into_any_element(),
860                        )],
861                    ),
862                ])
863                .into_any_element(),
864        )
865    }
866}