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