table.rs

  1use std::{ops::Range, rc::Rc, time::Duration};
  2
  3use editor::{EditorSettings, ShowScrollbar, scroll::ScrollbarAutoHide};
  4use gpui::{
  5    AppContext, Axis, Context, Entity, FocusHandle, FontWeight, Length,
  6    ListHorizontalSizingBehavior, ListSizingBehavior, MouseButton, Task, UniformListScrollHandle,
  7    WeakEntity, transparent_black, uniform_list,
  8};
  9use settings::Settings as _;
 10use ui::{
 11    ActiveTheme as _, AnyElement, App, Button, ButtonCommon as _, ButtonStyle, Color, Component,
 12    ComponentScope, Div, ElementId, FixedWidth as _, FluentBuilder as _, Indicator,
 13    InteractiveElement as _, IntoElement, ParentElement, Pixels, RegisterComponent, RenderOnce,
 14    Scrollbar, ScrollbarState, StatefulInteractiveElement as _, Styled, StyledExt as _,
 15    StyledTypography, Window, div, example_group_with_title, h_flex, px, single_example, v_flex,
 16};
 17
 18struct UniformListData<const COLS: usize> {
 19    render_item_fn: Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<[AnyElement; COLS]>>,
 20    element_id: ElementId,
 21    row_count: usize,
 22}
 23
 24enum TableContents<const COLS: usize> {
 25    Vec(Vec<[AnyElement; COLS]>),
 26    UniformList(UniformListData<COLS>),
 27}
 28
 29impl<const COLS: usize> TableContents<COLS> {
 30    fn rows_mut(&mut self) -> Option<&mut Vec<[AnyElement; COLS]>> {
 31        match self {
 32            TableContents::Vec(rows) => Some(rows),
 33            TableContents::UniformList(_) => None,
 34        }
 35    }
 36
 37    fn len(&self) -> usize {
 38        match self {
 39            TableContents::Vec(rows) => rows.len(),
 40            TableContents::UniformList(data) => data.row_count,
 41        }
 42    }
 43}
 44
 45pub struct TableInteractionState {
 46    pub focus_handle: FocusHandle,
 47    pub scroll_handle: UniformListScrollHandle,
 48    pub horizontal_scrollbar: ScrollbarProperties,
 49    pub vertical_scrollbar: ScrollbarProperties,
 50}
 51
 52impl TableInteractionState {
 53    pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
 54        cx.new(|cx| {
 55            let focus_handle = cx.focus_handle();
 56
 57            cx.on_focus_out(&focus_handle, window, |this: &mut Self, _, window, cx| {
 58                this.hide_scrollbars(window, cx);
 59            })
 60            .detach();
 61
 62            let scroll_handle = UniformListScrollHandle::new();
 63            let vertical_scrollbar = ScrollbarProperties {
 64                axis: Axis::Vertical,
 65                state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
 66                show_scrollbar: false,
 67                show_track: false,
 68                auto_hide: false,
 69                hide_task: None,
 70            };
 71
 72            let horizontal_scrollbar = ScrollbarProperties {
 73                axis: Axis::Horizontal,
 74                state: ScrollbarState::new(scroll_handle.clone()).parent_entity(&cx.entity()),
 75                show_scrollbar: false,
 76                show_track: false,
 77                auto_hide: false,
 78                hide_task: None,
 79            };
 80
 81            let mut this = Self {
 82                focus_handle,
 83                scroll_handle,
 84                horizontal_scrollbar,
 85                vertical_scrollbar,
 86            };
 87
 88            this.update_scrollbar_visibility(cx);
 89            this
 90        })
 91    }
 92
 93    fn update_scrollbar_visibility(&mut self, cx: &mut Context<Self>) {
 94        let show_setting = EditorSettings::get_global(cx).scrollbar.show;
 95
 96        let scroll_handle = self.scroll_handle.0.borrow();
 97
 98        let autohide = |show: ShowScrollbar, cx: &mut Context<Self>| match show {
 99            ShowScrollbar::Auto => true,
100            ShowScrollbar::System => cx
101                .try_global::<ScrollbarAutoHide>()
102                .map_or_else(|| cx.should_auto_hide_scrollbars(), |autohide| autohide.0),
103            ShowScrollbar::Always => false,
104            ShowScrollbar::Never => false,
105        };
106
107        let longest_item_width = scroll_handle.last_item_size.and_then(|size| {
108            (size.contents.width > size.item.width).then_some(size.contents.width)
109        });
110
111        // is there an item long enough that we should show a horizontal scrollbar?
112        let item_wider_than_container = if let Some(longest_item_width) = longest_item_width {
113            longest_item_width > px(scroll_handle.base_handle.bounds().size.width.0)
114        } else {
115            true
116        };
117
118        let show_scrollbar = match show_setting {
119            ShowScrollbar::Auto | ShowScrollbar::System | ShowScrollbar::Always => true,
120            ShowScrollbar::Never => false,
121        };
122        let show_vertical = show_scrollbar;
123
124        let show_horizontal = item_wider_than_container && show_scrollbar;
125
126        let show_horizontal_track =
127            show_horizontal && matches!(show_setting, ShowScrollbar::Always);
128
129        // TODO: we probably should hide the scroll track when the list doesn't need to scroll
130        let show_vertical_track = show_vertical && matches!(show_setting, ShowScrollbar::Always);
131
132        self.vertical_scrollbar = ScrollbarProperties {
133            axis: self.vertical_scrollbar.axis,
134            state: self.vertical_scrollbar.state.clone(),
135            show_scrollbar: show_vertical,
136            show_track: show_vertical_track,
137            auto_hide: autohide(show_setting, cx),
138            hide_task: None,
139        };
140
141        self.horizontal_scrollbar = ScrollbarProperties {
142            axis: self.horizontal_scrollbar.axis,
143            state: self.horizontal_scrollbar.state.clone(),
144            show_scrollbar: show_horizontal,
145            show_track: show_horizontal_track,
146            auto_hide: autohide(show_setting, cx),
147            hide_task: None,
148        };
149
150        cx.notify();
151    }
152
153    fn hide_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Self>) {
154        self.horizontal_scrollbar.hide(window, cx);
155        self.vertical_scrollbar.hide(window, cx);
156    }
157
158    pub fn listener<E: ?Sized>(
159        this: &Entity<Self>,
160        f: impl Fn(&mut Self, &E, &mut Window, &mut Context<Self>) + 'static,
161    ) -> impl Fn(&E, &mut Window, &mut App) + 'static {
162        let view = this.downgrade();
163        move |e: &E, window: &mut Window, cx: &mut App| {
164            view.update(cx, |view, cx| f(view, e, window, cx)).ok();
165        }
166    }
167
168    fn render_vertical_scrollbar_track(
169        this: &Entity<Self>,
170        parent: Div,
171        scroll_track_size: Pixels,
172        cx: &mut App,
173    ) -> Div {
174        if !this.read(cx).vertical_scrollbar.show_track {
175            return parent;
176        }
177        let child = v_flex()
178            .h_full()
179            .flex_none()
180            .w(scroll_track_size)
181            .bg(cx.theme().colors().background)
182            .child(
183                div()
184                    .size_full()
185                    .flex_1()
186                    .border_l_1()
187                    .border_color(cx.theme().colors().border),
188            );
189        parent.child(child)
190    }
191
192    fn render_vertical_scrollbar(this: &Entity<Self>, parent: Div, cx: &mut App) -> Div {
193        if !this.read(cx).vertical_scrollbar.show_scrollbar {
194            return parent;
195        }
196        let child = div()
197            .id(("table-vertical-scrollbar", this.entity_id()))
198            .occlude()
199            .flex_none()
200            .h_full()
201            .cursor_default()
202            .absolute()
203            .right_0()
204            .top_0()
205            .bottom_0()
206            .w(px(12.))
207            .on_mouse_move(Self::listener(this, |_, _, _, cx| {
208                cx.notify();
209                cx.stop_propagation()
210            }))
211            .on_hover(|_, _, cx| {
212                cx.stop_propagation();
213            })
214            .on_mouse_up(
215                MouseButton::Left,
216                Self::listener(this, |this, _, window, cx| {
217                    if !this.vertical_scrollbar.state.is_dragging()
218                        && !this.focus_handle.contains_focused(window, cx)
219                    {
220                        this.vertical_scrollbar.hide(window, cx);
221                        cx.notify();
222                    }
223
224                    cx.stop_propagation();
225                }),
226            )
227            .on_any_mouse_down(|_, _, cx| {
228                cx.stop_propagation();
229            })
230            .on_scroll_wheel(Self::listener(&this, |_, _, _, cx| {
231                cx.notify();
232            }))
233            .children(Scrollbar::vertical(
234                this.read(cx).vertical_scrollbar.state.clone(),
235            ));
236        parent.child(child)
237    }
238
239    /// Renders the horizontal scrollbar.
240    ///
241    /// The right offset is used to determine how far to the right the
242    /// scrollbar should extend to, useful for ensuring it doesn't collide
243    /// with the vertical scrollbar when visible.
244    fn render_horizontal_scrollbar(
245        this: &Entity<Self>,
246        parent: Div,
247        right_offset: Pixels,
248        cx: &mut App,
249    ) -> Div {
250        if !this.read(cx).horizontal_scrollbar.show_scrollbar {
251            return parent;
252        }
253        let child = div()
254            .id(("table-horizontal-scrollbar", this.entity_id()))
255            .occlude()
256            .flex_none()
257            .w_full()
258            .cursor_default()
259            .absolute()
260            .bottom_neg_px()
261            .left_0()
262            .right_0()
263            .pr(right_offset)
264            .on_mouse_move(Self::listener(this, |_, _, _, cx| {
265                cx.notify();
266                cx.stop_propagation()
267            }))
268            .on_hover(|_, _, cx| {
269                cx.stop_propagation();
270            })
271            .on_any_mouse_down(|_, _, cx| {
272                cx.stop_propagation();
273            })
274            .on_mouse_up(
275                MouseButton::Left,
276                Self::listener(this, |this, _, window, cx| {
277                    if !this.horizontal_scrollbar.state.is_dragging()
278                        && !this.focus_handle.contains_focused(window, cx)
279                    {
280                        this.horizontal_scrollbar.hide(window, cx);
281                        cx.notify();
282                    }
283
284                    cx.stop_propagation();
285                }),
286            )
287            .on_scroll_wheel(Self::listener(this, |_, _, _, cx| {
288                cx.notify();
289            }))
290            .children(Scrollbar::horizontal(
291                // percentage as f32..end_offset as f32,
292                this.read(cx).horizontal_scrollbar.state.clone(),
293            ));
294        parent.child(child)
295    }
296
297    fn render_horizontal_scrollbar_track(
298        this: &Entity<Self>,
299        parent: Div,
300        scroll_track_size: Pixels,
301        cx: &mut App,
302    ) -> Div {
303        if !this.read(cx).horizontal_scrollbar.show_track {
304            return parent;
305        }
306        let child = h_flex()
307            .w_full()
308            .h(scroll_track_size)
309            .flex_none()
310            .relative()
311            .child(
312                div()
313                    .w_full()
314                    .flex_1()
315                    // for some reason the horizontal scrollbar is 1px
316                    // taller than the vertical scrollbar??
317                    .h(scroll_track_size - px(1.))
318                    .bg(cx.theme().colors().background)
319                    .border_t_1()
320                    .border_color(cx.theme().colors().border),
321            )
322            .when(this.read(cx).vertical_scrollbar.show_track, |parent| {
323                parent
324                    .child(
325                        div()
326                            .flex_none()
327                            // -1px prevents a missing pixel between the two container borders
328                            .w(scroll_track_size - px(1.))
329                            .h_full(),
330                    )
331                    .child(
332                        // HACK: Fill the missing 1px 🥲
333                        div()
334                            .absolute()
335                            .right(scroll_track_size - px(1.))
336                            .bottom(scroll_track_size - px(1.))
337                            .size_px()
338                            .bg(cx.theme().colors().border),
339                    )
340            });
341
342        parent.child(child)
343    }
344}
345
346/// A table component
347#[derive(RegisterComponent, IntoElement)]
348pub struct Table<const COLS: usize = 3> {
349    striped: bool,
350    width: Option<Length>,
351    headers: Option<[AnyElement; COLS]>,
352    rows: TableContents<COLS>,
353    interaction_state: Option<WeakEntity<TableInteractionState>>,
354    column_widths: Option<[Length; COLS]>,
355    map_row: Option<Rc<dyn Fn((usize, Div), &mut Window, &mut App) -> AnyElement>>,
356}
357
358impl<const COLS: usize> Table<COLS> {
359    /// number of headers provided.
360    pub fn new() -> Self {
361        Table {
362            striped: false,
363            width: None,
364            headers: None,
365            rows: TableContents::Vec(Vec::new()),
366            interaction_state: None,
367            column_widths: None,
368            map_row: None,
369        }
370    }
371
372    /// Enables uniform list rendering.
373    /// The provided function will be passed directly to the `uniform_list` element.
374    /// Therefore, if this method is called, any calls to [`Table::row`] before or after
375    /// this method is called will be ignored.
376    pub fn uniform_list(
377        mut self,
378        id: impl Into<ElementId>,
379        row_count: usize,
380        render_item_fn: impl Fn(Range<usize>, &mut Window, &mut App) -> Vec<[AnyElement; COLS]>
381        + 'static,
382    ) -> Self {
383        self.rows = TableContents::UniformList(UniformListData {
384            element_id: id.into(),
385            row_count: row_count,
386            render_item_fn: Box::new(render_item_fn),
387        });
388        self
389    }
390
391    /// Enables row striping.
392    pub fn striped(mut self) -> Self {
393        self.striped = true;
394        self
395    }
396
397    /// Sets the width of the table.
398    /// Will enable horizontal scrolling if [`Self::interactable`] is also called.
399    pub fn width(mut self, width: impl Into<Length>) -> Self {
400        self.width = Some(width.into());
401        self
402    }
403
404    /// Enables interaction (primarily scrolling) with the table.
405    ///
406    /// Vertical scrolling will be enabled by default if the table is taller than its container.
407    ///
408    /// Horizontal scrolling will only be enabled if [`Self::width`] is also called, otherwise
409    /// the list will always shrink the table columns to fit their contents I.e. If [`Self::uniform_list`]
410    /// is used without a width and with [`Self::interactable`], the [`ListHorizontalSizingBehavior`] will
411    /// be set to [`ListHorizontalSizingBehavior::FitList`].
412    pub fn interactable(mut self, interaction_state: &Entity<TableInteractionState>) -> Self {
413        self.interaction_state = Some(interaction_state.downgrade());
414        self
415    }
416
417    pub fn header(mut self, headers: [impl IntoElement; COLS]) -> Self {
418        self.headers = Some(headers.map(IntoElement::into_any_element));
419        self
420    }
421
422    pub fn row(mut self, items: [impl IntoElement; COLS]) -> Self {
423        if let Some(rows) = self.rows.rows_mut() {
424            rows.push(items.map(IntoElement::into_any_element));
425        }
426        self
427    }
428
429    pub fn column_widths(mut self, widths: [impl Into<Length>; COLS]) -> Self {
430        self.column_widths = Some(widths.map(Into::into));
431        self
432    }
433
434    pub fn map_row(
435        mut self,
436        callback: impl Fn((usize, Div), &mut Window, &mut App) -> AnyElement + 'static,
437    ) -> Self {
438        self.map_row = Some(Rc::new(callback));
439        self
440    }
441}
442
443fn base_cell_style(width: Option<Length>, cx: &App) -> Div {
444    div()
445        .px_1p5()
446        .when_some(width, |this, width| this.w(width))
447        .when(width.is_none(), |this| this.flex_1())
448        .justify_start()
449        .text_ui(cx)
450        .whitespace_nowrap()
451        .text_ellipsis()
452        .overflow_hidden()
453}
454
455pub fn render_row<const COLS: usize>(
456    row_index: usize,
457    items: [impl IntoElement; COLS],
458    table_context: TableRenderContext<COLS>,
459    window: &mut Window,
460    cx: &mut App,
461) -> AnyElement {
462    let is_striped = table_context.striped;
463    let is_last = row_index == table_context.total_row_count - 1;
464    let bg = if row_index % 2 == 1 && is_striped {
465        Some(cx.theme().colors().text.opacity(0.05))
466    } else {
467        None
468    };
469    let column_widths = table_context
470        .column_widths
471        .map_or([None; COLS], |widths| widths.map(Some));
472
473    let row = div().w_full().child(
474        div()
475            .w_full()
476            .flex()
477            .flex_row()
478            .items_center()
479            .justify_between()
480            .px_1p5()
481            .py_1()
482            .when_some(bg, |row, bg| row.bg(bg))
483            .when(!is_striped, |row| {
484                row.border_b_1()
485                    .border_color(transparent_black())
486                    .when(!is_last, |row| row.border_color(cx.theme().colors().border))
487            })
488            .children(
489                items
490                    .map(IntoElement::into_any_element)
491                    .into_iter()
492                    .zip(column_widths)
493                    .map(|(cell, width)| base_cell_style(width, cx).child(cell)),
494            ),
495    );
496
497    if let Some(map_row) = table_context.map_row {
498        map_row((row_index, row), window, cx)
499    } else {
500        row.into_any_element()
501    }
502}
503
504pub fn render_header<const COLS: usize>(
505    headers: [impl IntoElement; COLS],
506    table_context: TableRenderContext<COLS>,
507    cx: &mut App,
508) -> impl IntoElement {
509    let column_widths = table_context
510        .column_widths
511        .map_or([None; COLS], |widths| widths.map(Some));
512    div()
513        .flex()
514        .flex_row()
515        .items_center()
516        .justify_between()
517        .w_full()
518        .p_2()
519        .border_b_1()
520        .border_color(cx.theme().colors().border)
521        .children(headers.into_iter().zip(column_widths).map(|(h, width)| {
522            base_cell_style(width, cx)
523                .font_weight(FontWeight::SEMIBOLD)
524                .child(h)
525        }))
526}
527
528#[derive(Clone)]
529pub struct TableRenderContext<const COLS: usize> {
530    pub striped: bool,
531    pub total_row_count: usize,
532    pub column_widths: Option<[Length; COLS]>,
533    pub map_row: Option<Rc<dyn Fn((usize, Div), &mut Window, &mut App) -> AnyElement>>,
534}
535
536impl<const COLS: usize> TableRenderContext<COLS> {
537    fn new(table: &Table<COLS>) -> Self {
538        Self {
539            striped: table.striped,
540            total_row_count: table.rows.len(),
541            column_widths: table.column_widths,
542            map_row: table.map_row.clone(),
543        }
544    }
545}
546
547impl<const COLS: usize> RenderOnce for Table<COLS> {
548    fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
549        let table_context = TableRenderContext::new(&self);
550        let interaction_state = self.interaction_state.and_then(|state| state.upgrade());
551
552        let scroll_track_size = px(16.);
553        let h_scroll_offset = if interaction_state
554            .as_ref()
555            .is_some_and(|state| state.read(cx).vertical_scrollbar.show_scrollbar)
556        {
557            // magic number
558            px(3.)
559        } else {
560            px(0.)
561        };
562
563        let width = self.width;
564
565        let table = div()
566            .when_some(width, |this, width| this.w(width))
567            .h_full()
568            .v_flex()
569            .when_some(self.headers.take(), |this, headers| {
570                this.child(render_header(headers, table_context.clone(), cx))
571            })
572            .child(
573                div()
574                    .flex_grow()
575                    .w_full()
576                    .relative()
577                    .overflow_hidden()
578                    .map(|parent| match self.rows {
579                        TableContents::Vec(items) => {
580                            parent.children(items.into_iter().enumerate().map(|(index, row)| {
581                                render_row(index, row, table_context.clone(), window, cx)
582                            }))
583                        }
584                        TableContents::UniformList(uniform_list_data) => parent.child(
585                            uniform_list(
586                                uniform_list_data.element_id,
587                                uniform_list_data.row_count,
588                                {
589                                    let render_item_fn = uniform_list_data.render_item_fn;
590                                    move |range: Range<usize>, window, cx| {
591                                        let elements = render_item_fn(range.clone(), window, cx);
592                                        elements
593                                            .into_iter()
594                                            .zip(range)
595                                            .map(|(row, row_index)| {
596                                                render_row(
597                                                    row_index,
598                                                    row,
599                                                    table_context.clone(),
600                                                    window,
601                                                    cx,
602                                                )
603                                            })
604                                            .collect()
605                                    }
606                                },
607                            )
608                            .size_full()
609                            .flex_grow()
610                            .with_sizing_behavior(ListSizingBehavior::Auto)
611                            .with_horizontal_sizing_behavior(if width.is_some() {
612                                ListHorizontalSizingBehavior::Unconstrained
613                            } else {
614                                ListHorizontalSizingBehavior::FitList
615                            })
616                            .when_some(
617                                interaction_state.as_ref(),
618                                |this, state| {
619                                    this.track_scroll(
620                                        state.read_with(cx, |s, _| s.scroll_handle.clone()),
621                                    )
622                                },
623                            ),
624                        ),
625                    })
626                    .when_some(interaction_state.as_ref(), |this, interaction_state| {
627                        this.map(|this| {
628                            TableInteractionState::render_vertical_scrollbar_track(
629                                interaction_state,
630                                this,
631                                scroll_track_size,
632                                cx,
633                            )
634                        })
635                        .map(|this| {
636                            TableInteractionState::render_vertical_scrollbar(
637                                interaction_state,
638                                this,
639                                cx,
640                            )
641                        })
642                    }),
643            )
644            .when_some(
645                width.and(interaction_state.as_ref()),
646                |this, interaction_state| {
647                    this.map(|this| {
648                        TableInteractionState::render_horizontal_scrollbar_track(
649                            interaction_state,
650                            this,
651                            scroll_track_size,
652                            cx,
653                        )
654                    })
655                    .map(|this| {
656                        TableInteractionState::render_horizontal_scrollbar(
657                            interaction_state,
658                            this,
659                            h_scroll_offset,
660                            cx,
661                        )
662                    })
663                },
664            );
665
666        if let Some(interaction_state) = interaction_state.as_ref() {
667            table
668                .track_focus(&interaction_state.read(cx).focus_handle)
669                .id(("table", interaction_state.entity_id()))
670                .on_hover({
671                    let interaction_state = interaction_state.downgrade();
672                    move |hovered, window, cx| {
673                        interaction_state
674                            .update(cx, |interaction_state, cx| {
675                                if *hovered {
676                                    interaction_state.horizontal_scrollbar.show(cx);
677                                    interaction_state.vertical_scrollbar.show(cx);
678                                    cx.notify();
679                                } else if !interaction_state
680                                    .focus_handle
681                                    .contains_focused(window, cx)
682                                {
683                                    interaction_state.hide_scrollbars(window, cx);
684                                }
685                            })
686                            .ok();
687                    }
688                })
689                .into_any_element()
690        } else {
691            table.into_any_element()
692        }
693    }
694}
695
696// computed state related to how to render scrollbars
697// one per axis
698// on render we just read this off the keymap editor
699// we update it when
700// - settings change
701// - on focus in, on focus out, on hover, etc.
702#[derive(Debug)]
703pub struct ScrollbarProperties {
704    axis: Axis,
705    show_scrollbar: bool,
706    show_track: bool,
707    auto_hide: bool,
708    hide_task: Option<Task<()>>,
709    state: ScrollbarState,
710}
711
712impl ScrollbarProperties {
713    // Shows the scrollbar and cancels any pending hide task
714    fn show(&mut self, cx: &mut Context<TableInteractionState>) {
715        if !self.auto_hide {
716            return;
717        }
718        self.show_scrollbar = true;
719        self.hide_task.take();
720        cx.notify();
721    }
722
723    fn hide(&mut self, window: &mut Window, cx: &mut Context<TableInteractionState>) {
724        const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
725
726        if !self.auto_hide {
727            return;
728        }
729
730        let axis = self.axis;
731        self.hide_task = Some(cx.spawn_in(window, async move |keymap_editor, cx| {
732            cx.background_executor()
733                .timer(SCROLLBAR_SHOW_INTERVAL)
734                .await;
735
736            if let Some(keymap_editor) = keymap_editor.upgrade() {
737                keymap_editor
738                    .update(cx, |keymap_editor, cx| {
739                        match axis {
740                            Axis::Vertical => {
741                                keymap_editor.vertical_scrollbar.show_scrollbar = false
742                            }
743                            Axis::Horizontal => {
744                                keymap_editor.horizontal_scrollbar.show_scrollbar = false
745                            }
746                        }
747                        cx.notify();
748                    })
749                    .ok();
750            }
751        }));
752    }
753}
754
755impl Component for Table<3> {
756    fn scope() -> ComponentScope {
757        ComponentScope::Layout
758    }
759
760    fn description() -> Option<&'static str> {
761        Some("A table component for displaying data in rows and columns with optional styling.")
762    }
763
764    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
765        Some(
766            v_flex()
767                .gap_6()
768                .children(vec![
769                    example_group_with_title(
770                        "Basic Tables",
771                        vec![
772                            single_example(
773                                "Simple Table",
774                                Table::new()
775                                    .width(px(400.))
776                                    .header(["Name", "Age", "City"])
777                                    .row(["Alice", "28", "New York"])
778                                    .row(["Bob", "32", "San Francisco"])
779                                    .row(["Charlie", "25", "London"])
780                                    .into_any_element(),
781                            ),
782                            single_example(
783                                "Two Column Table",
784                                Table::new()
785                                    .header(["Category", "Value"])
786                                    .width(px(300.))
787                                    .row(["Revenue", "$100,000"])
788                                    .row(["Expenses", "$75,000"])
789                                    .row(["Profit", "$25,000"])
790                                    .into_any_element(),
791                            ),
792                        ],
793                    ),
794                    example_group_with_title(
795                        "Styled Tables",
796                        vec![
797                            single_example(
798                                "Default",
799                                Table::new()
800                                    .width(px(400.))
801                                    .header(["Product", "Price", "Stock"])
802                                    .row(["Laptop", "$999", "In Stock"])
803                                    .row(["Phone", "$599", "Low Stock"])
804                                    .row(["Tablet", "$399", "Out of Stock"])
805                                    .into_any_element(),
806                            ),
807                            single_example(
808                                "Striped",
809                                Table::new()
810                                    .width(px(400.))
811                                    .striped()
812                                    .header(["Product", "Price", "Stock"])
813                                    .row(["Laptop", "$999", "In Stock"])
814                                    .row(["Phone", "$599", "Low Stock"])
815                                    .row(["Tablet", "$399", "Out of Stock"])
816                                    .row(["Headphones", "$199", "In Stock"])
817                                    .into_any_element(),
818                            ),
819                        ],
820                    ),
821                    example_group_with_title(
822                        "Mixed Content Table",
823                        vec![single_example(
824                            "Table with Elements",
825                            Table::new()
826                                .width(px(840.))
827                                .header(["Status", "Name", "Priority", "Deadline", "Action"])
828                                .row([
829                                    Indicator::dot().color(Color::Success).into_any_element(),
830                                    "Project A".into_any_element(),
831                                    "High".into_any_element(),
832                                    "2023-12-31".into_any_element(),
833                                    Button::new("view_a", "View")
834                                        .style(ButtonStyle::Filled)
835                                        .full_width()
836                                        .into_any_element(),
837                                ])
838                                .row([
839                                    Indicator::dot().color(Color::Warning).into_any_element(),
840                                    "Project B".into_any_element(),
841                                    "Medium".into_any_element(),
842                                    "2024-03-15".into_any_element(),
843                                    Button::new("view_b", "View")
844                                        .style(ButtonStyle::Filled)
845                                        .full_width()
846                                        .into_any_element(),
847                                ])
848                                .row([
849                                    Indicator::dot().color(Color::Error).into_any_element(),
850                                    "Project C".into_any_element(),
851                                    "Low".into_any_element(),
852                                    "2024-06-30".into_any_element(),
853                                    Button::new("view_c", "View")
854                                        .style(ButtonStyle::Filled)
855                                        .full_width()
856                                        .into_any_element(),
857                                ])
858                                .into_any_element(),
859                        )],
860                    ),
861                ])
862                .into_any_element(),
863        )
864    }
865}