table.rs

  1use std::{ops::Range, 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, 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 {
 19    render_item_fn: Box<dyn Fn(Range<usize>, &mut Window, &mut App) -> Vec<AnyElement>>,
 20    element_id: ElementId,
 21    row_count: usize,
 22}
 23
 24enum TableContents<const COLS: usize> {
 25    Vec(Vec<[AnyElement; COLS]>),
 26    UniformList(UniformListData),
 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    // fn listener(this: Entity<Self>, fn: F) ->
159
160    pub fn listener<E: ?Sized>(
161        this: &Entity<Self>,
162        f: impl Fn(&mut Self, &E, &mut Window, &mut Context<Self>) + 'static,
163    ) -> impl Fn(&E, &mut Window, &mut App) + 'static {
164        let view = this.downgrade();
165        move |e: &E, window: &mut Window, cx: &mut App| {
166            view.update(cx, |view, cx| f(view, e, window, cx)).ok();
167        }
168    }
169
170    fn render_vertical_scrollbar(this: &Entity<Self>, cx: &mut App) -> impl IntoElement {
171        div()
172            .id("keymap-editor-vertical-scroll")
173            .occlude()
174            .flex_none()
175            .h_full()
176            .cursor_default()
177            .absolute()
178            .right_0()
179            .top_0()
180            .bottom_0()
181            .w(px(12.))
182            .on_mouse_move(Self::listener(this, |_, _, _, cx| {
183                cx.notify();
184                cx.stop_propagation()
185            }))
186            .on_hover(|_, _, cx| {
187                cx.stop_propagation();
188            })
189            .on_mouse_up(
190                MouseButton::Left,
191                Self::listener(this, |this, _, window, cx| {
192                    if !this.vertical_scrollbar.state.is_dragging()
193                        && !this.focus_handle.contains_focused(window, cx)
194                    {
195                        this.vertical_scrollbar.hide(window, cx);
196                        cx.notify();
197                    }
198
199                    cx.stop_propagation();
200                }),
201            )
202            .on_any_mouse_down(|_, _, cx| {
203                cx.stop_propagation();
204            })
205            .on_scroll_wheel(Self::listener(&this, |_, _, _, cx| {
206                cx.notify();
207            }))
208            .children(Scrollbar::vertical(
209                this.read(cx).vertical_scrollbar.state.clone(),
210            ))
211    }
212
213    /// Renders the horizontal scrollbar.
214    ///
215    /// The right offset is used to determine how far to the right the
216    /// scrollbar should extend to, useful for ensuring it doesn't collide
217    /// with the vertical scrollbar when visible.
218    fn render_horizontal_scrollbar(
219        this: &Entity<Self>,
220        right_offset: Pixels,
221        cx: &mut App,
222    ) -> impl IntoElement {
223        div()
224            .id("keymap-editor-horizontal-scroll")
225            .occlude()
226            .flex_none()
227            .w_full()
228            .cursor_default()
229            .absolute()
230            .bottom_neg_px()
231            .left_0()
232            .right_0()
233            .pr(right_offset)
234            .on_mouse_move(Self::listener(this, |_, _, _, cx| {
235                cx.notify();
236                cx.stop_propagation()
237            }))
238            .on_hover(|_, _, cx| {
239                cx.stop_propagation();
240            })
241            .on_any_mouse_down(|_, _, cx| {
242                cx.stop_propagation();
243            })
244            .on_mouse_up(
245                MouseButton::Left,
246                Self::listener(this, |this, _, window, cx| {
247                    if !this.horizontal_scrollbar.state.is_dragging()
248                        && !this.focus_handle.contains_focused(window, cx)
249                    {
250                        this.horizontal_scrollbar.hide(window, cx);
251                        cx.notify();
252                    }
253
254                    cx.stop_propagation();
255                }),
256            )
257            .on_scroll_wheel(Self::listener(this, |_, _, _, cx| {
258                cx.notify();
259            }))
260            .children(Scrollbar::horizontal(
261                // percentage as f32..end_offset as f32,
262                this.read(cx).horizontal_scrollbar.state.clone(),
263            ))
264    }
265}
266
267/// A table component
268#[derive(RegisterComponent, IntoElement)]
269pub struct Table<const COLS: usize = 3> {
270    striped: bool,
271    width: Length,
272    headers: Option<[AnyElement; COLS]>,
273    rows: TableContents<COLS>,
274    interaction_state: Option<WeakEntity<TableInteractionState>>,
275}
276
277impl<const COLS: usize> Table<COLS> {
278    /// number of headers provided.
279    pub fn new() -> Self {
280        Table {
281            striped: false,
282            width: Length::Auto,
283            headers: None,
284            rows: TableContents::Vec(Vec::new()),
285            interaction_state: None,
286        }
287    }
288
289    /// Enables uniform list rendering.
290    /// The provided function will be passed directly to the `uniform_list` element.
291    /// Therefore, if this method is called, any calls to [`Table::row`] before or after
292    /// this method is called will be ignored.
293    pub fn uniform_list(
294        mut self,
295        id: impl Into<ElementId>,
296        row_count: usize,
297        render_item_fn: impl Fn(Range<usize>, &mut Window, &mut App) -> Vec<AnyElement> + 'static,
298    ) -> Self {
299        self.rows = TableContents::UniformList(UniformListData {
300            element_id: id.into(),
301            row_count: row_count,
302            render_item_fn: Box::new(render_item_fn),
303        });
304        self
305    }
306
307    /// Enables row striping.
308    pub fn striped(mut self) -> Self {
309        self.striped = true;
310        self
311    }
312
313    /// Sets the width of the table.
314    pub fn width(mut self, width: impl Into<Length>) -> Self {
315        self.width = width.into();
316        self
317    }
318
319    pub fn interactable(mut self, interaction_state: &Entity<TableInteractionState>) -> Self {
320        self.interaction_state = Some(interaction_state.downgrade());
321        self
322    }
323
324    pub fn header(mut self, headers: [impl IntoElement; COLS]) -> Self {
325        self.headers = Some(headers.map(IntoElement::into_any_element));
326        self
327    }
328
329    pub fn row(mut self, items: [impl IntoElement; COLS]) -> Self {
330        if let Some(rows) = self.rows.rows_mut() {
331            rows.push(items.map(IntoElement::into_any_element));
332        }
333        self
334    }
335
336    pub fn render_row(&self, items: [impl IntoElement; COLS], cx: &mut App) -> AnyElement {
337        return render_row(0, items, self.rows.len(), self.striped, cx);
338    }
339
340    pub fn render_header(
341        &self,
342        headers: [impl IntoElement; COLS],
343        cx: &mut App,
344    ) -> impl IntoElement {
345        render_header(headers, cx)
346    }
347}
348
349fn base_cell_style(cx: &App) -> Div {
350    div()
351        .px_1p5()
352        .flex_1()
353        .justify_start()
354        .text_ui(cx)
355        .whitespace_nowrap()
356        .text_ellipsis()
357        .overflow_hidden()
358}
359
360pub fn render_row<const COLS: usize>(
361    row_index: usize,
362    items: [impl IntoElement; COLS],
363    row_count: usize,
364    striped: bool,
365    cx: &App,
366) -> AnyElement {
367    let is_last = row_index == row_count - 1;
368    let bg = if row_index % 2 == 1 && striped {
369        Some(cx.theme().colors().text.opacity(0.05))
370    } else {
371        None
372    };
373    div()
374        .w_full()
375        .flex()
376        .flex_row()
377        .items_center()
378        .justify_between()
379        .px_1p5()
380        .py_1()
381        .when_some(bg, |row, bg| row.bg(bg))
382        .when(!is_last, |row| {
383            row.border_b_1().border_color(cx.theme().colors().border)
384        })
385        .children(
386            items
387                .map(IntoElement::into_any_element)
388                .map(|cell| base_cell_style(cx).child(cell)),
389        )
390        .into_any_element()
391}
392
393pub fn render_header<const COLS: usize>(
394    headers: [impl IntoElement; COLS],
395    cx: &mut App,
396) -> impl IntoElement {
397    div()
398        .flex()
399        .flex_row()
400        .items_center()
401        .justify_between()
402        .w_full()
403        .p_2()
404        .border_b_1()
405        .border_color(cx.theme().colors().border)
406        .children(headers.into_iter().map(|h| {
407            base_cell_style(cx)
408                .font_weight(FontWeight::SEMIBOLD)
409                .child(h)
410        }))
411}
412
413impl<const COLS: usize> RenderOnce for Table<COLS> {
414    fn render(mut self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
415        // match self.ro
416        let row_count = self.rows.len();
417        let interaction_state = self.interaction_state.and_then(|state| state.upgrade());
418
419        let scroll_track_size = px(16.);
420        let h_scroll_offset = if interaction_state
421            .as_ref()
422            .is_some_and(|state| state.read(cx).vertical_scrollbar.show_scrollbar)
423        {
424            // magic number
425            px(3.)
426        } else {
427            px(0.)
428        };
429
430        div()
431            .id("todo! how to have id")
432            .w(self.width)
433            .overflow_hidden()
434            .when_some(interaction_state.as_ref(), |this, interaction_state| {
435                this.track_focus(&interaction_state.read(cx).focus_handle)
436                    .on_hover({
437                        let interaction_state = interaction_state.downgrade();
438                        move |hovered, window, cx| {
439                            interaction_state
440                                .update(cx, |interaction_state, cx| {
441                                    if *hovered {
442                                        interaction_state.horizontal_scrollbar.show(cx);
443                                        interaction_state.vertical_scrollbar.show(cx);
444                                        cx.notify();
445                                    } else if !interaction_state
446                                        .focus_handle
447                                        .contains_focused(window, cx)
448                                    {
449                                        interaction_state.hide_scrollbars(window, cx);
450                                    }
451                                })
452                                .ok(); // todo! handle error?
453                        }
454                    })
455            })
456            .when_some(self.headers.take(), |this, headers| {
457                this.child(render_header(headers, cx))
458            })
459            .map(|parent| match self.rows {
460                TableContents::Vec(items) => parent.children(
461                    items
462                        .into_iter()
463                        .enumerate()
464                        .map(|(index, row)| render_row(index, row, row_count, self.striped, cx)),
465                ),
466                TableContents::UniformList(uniform_list_data) => parent
467                    .h_full()
468                    .v_flex()
469                    .child(
470                        div()
471                            .flex_grow()
472                            .w_full()
473                            .relative()
474                            .overflow_hidden()
475                            .child(
476                                uniform_list(
477                                    uniform_list_data.element_id,
478                                    uniform_list_data.row_count,
479                                    uniform_list_data.render_item_fn,
480                                )
481                                .size_full()
482                                .flex_grow()
483                                .with_sizing_behavior(ListSizingBehavior::Auto)
484                                .with_horizontal_sizing_behavior(
485                                    ListHorizontalSizingBehavior::Unconstrained,
486                                )
487                                .when_some(
488                                    interaction_state.as_ref(),
489                                    |this, state| {
490                                        this.track_scroll(
491                                            state.read_with(cx, |s, _| s.scroll_handle.clone()),
492                                        )
493                                    },
494                                ),
495                            )
496                            .when(
497                                interaction_state.as_ref().is_some_and(|state| {
498                                    state.read(cx).vertical_scrollbar.show_track
499                                }),
500                                |this| {
501                                    this.child(
502                                        v_flex()
503                                            .h_full()
504                                            .flex_none()
505                                            .w(scroll_track_size)
506                                            .bg(cx.theme().colors().background)
507                                            .child(
508                                                div()
509                                                    .size_full()
510                                                    .flex_1()
511                                                    .border_l_1()
512                                                    .border_color(cx.theme().colors().border),
513                                            ),
514                                    )
515                                },
516                            )
517                            .when_some(
518                                interaction_state.as_ref().filter(|state| {
519                                    state.read(cx).vertical_scrollbar.show_scrollbar
520                                }),
521                                |this, interaction_state| {
522                                    this.child(TableInteractionState::render_vertical_scrollbar(
523                                        interaction_state,
524                                        cx,
525                                    ))
526                                },
527                            ),
528                    )
529                    .when(
530                        interaction_state
531                            .as_ref()
532                            .is_some_and(|state| state.read(cx).horizontal_scrollbar.show_track),
533                        |this| {
534                            this.child(
535                                h_flex()
536                                    .w_full()
537                                    .h(scroll_track_size)
538                                    .flex_none()
539                                    .relative()
540                                    .child(
541                                        div()
542                                            .w_full()
543                                            .flex_1()
544                                            // for some reason the horizontal scrollbar is 1px
545                                            // taller than the vertical scrollbar??
546                                            .h(scroll_track_size - px(1.))
547                                            .bg(cx.theme().colors().background)
548                                            .border_t_1()
549                                            .border_color(cx.theme().colors().border),
550                                    )
551                                    .when(
552                                        interaction_state.as_ref().is_some_and(|state| {
553                                            state.read(cx).vertical_scrollbar.show_track
554                                        }),
555                                        |this| {
556                                            this.child(
557                                                div()
558                                                    .flex_none()
559                                                    // -1px prevents a missing pixel between the two container borders
560                                                    .w(scroll_track_size - px(1.))
561                                                    .h_full(),
562                                            )
563                                            .child(
564                                                // HACK: Fill the missing 1px 🥲
565                                                div()
566                                                    .absolute()
567                                                    .right(scroll_track_size - px(1.))
568                                                    .bottom(scroll_track_size - px(1.))
569                                                    .size_px()
570                                                    .bg(cx.theme().colors().border),
571                                            )
572                                        },
573                                    ),
574                            )
575                        },
576                    )
577                    .when_some(
578                        interaction_state
579                            .as_ref()
580                            .filter(|state| state.read(cx).horizontal_scrollbar.show_scrollbar),
581                        |this, interaction_state| {
582                            this.child(TableInteractionState::render_horizontal_scrollbar(
583                                interaction_state,
584                                h_scroll_offset,
585                                cx,
586                            ))
587                        },
588                    ),
589            })
590    }
591}
592
593// computed state related to how to render scrollbars
594// one per axis
595// on render we just read this off the keymap editor
596// we update it when
597// - settings change
598// - on focus in, on focus out, on hover, etc.
599#[derive(Debug)]
600pub struct ScrollbarProperties {
601    axis: Axis,
602    show_scrollbar: bool,
603    show_track: bool,
604    auto_hide: bool,
605    hide_task: Option<Task<()>>,
606    state: ScrollbarState,
607}
608
609impl ScrollbarProperties {
610    // Shows the scrollbar and cancels any pending hide task
611    fn show(&mut self, cx: &mut Context<TableInteractionState>) {
612        if !self.auto_hide {
613            return;
614        }
615        self.show_scrollbar = true;
616        self.hide_task.take();
617        cx.notify();
618    }
619
620    fn hide(&mut self, window: &mut Window, cx: &mut Context<TableInteractionState>) {
621        const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
622
623        if !self.auto_hide {
624            return;
625        }
626
627        let axis = self.axis;
628        self.hide_task = Some(cx.spawn_in(window, async move |keymap_editor, cx| {
629            cx.background_executor()
630                .timer(SCROLLBAR_SHOW_INTERVAL)
631                .await;
632
633            if let Some(keymap_editor) = keymap_editor.upgrade() {
634                keymap_editor
635                    .update(cx, |keymap_editor, cx| {
636                        match axis {
637                            Axis::Vertical => {
638                                keymap_editor.vertical_scrollbar.show_scrollbar = false
639                            }
640                            Axis::Horizontal => {
641                                keymap_editor.horizontal_scrollbar.show_scrollbar = false
642                            }
643                        }
644                        cx.notify();
645                    })
646                    .ok();
647            }
648        }));
649    }
650}
651
652impl Component for Table<3> {
653    fn scope() -> ComponentScope {
654        ComponentScope::Layout
655    }
656
657    fn description() -> Option<&'static str> {
658        Some("A table component for displaying data in rows and columns with optional styling.")
659    }
660
661    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
662        Some(
663            v_flex()
664                .gap_6()
665                .children(vec![
666                    example_group_with_title(
667                        "Basic Tables",
668                        vec![
669                            single_example(
670                                "Simple Table",
671                                Table::new()
672                                    .width(px(400.))
673                                    .header(["Name", "Age", "City"])
674                                    .row(["Alice", "28", "New York"])
675                                    .row(["Bob", "32", "San Francisco"])
676                                    .row(["Charlie", "25", "London"])
677                                    .into_any_element(),
678                            ),
679                            single_example(
680                                "Two Column Table",
681                                Table::new()
682                                    .header(["Category", "Value"])
683                                    .width(px(300.))
684                                    .row(["Revenue", "$100,000"])
685                                    .row(["Expenses", "$75,000"])
686                                    .row(["Profit", "$25,000"])
687                                    .into_any_element(),
688                            ),
689                        ],
690                    ),
691                    example_group_with_title(
692                        "Styled Tables",
693                        vec![
694                            single_example(
695                                "Default",
696                                Table::new()
697                                    .width(px(400.))
698                                    .header(["Product", "Price", "Stock"])
699                                    .row(["Laptop", "$999", "In Stock"])
700                                    .row(["Phone", "$599", "Low Stock"])
701                                    .row(["Tablet", "$399", "Out of Stock"])
702                                    .into_any_element(),
703                            ),
704                            single_example(
705                                "Striped",
706                                Table::new()
707                                    .width(px(400.))
708                                    .striped()
709                                    .header(["Product", "Price", "Stock"])
710                                    .row(["Laptop", "$999", "In Stock"])
711                                    .row(["Phone", "$599", "Low Stock"])
712                                    .row(["Tablet", "$399", "Out of Stock"])
713                                    .row(["Headphones", "$199", "In Stock"])
714                                    .into_any_element(),
715                            ),
716                        ],
717                    ),
718                    example_group_with_title(
719                        "Mixed Content Table",
720                        vec![single_example(
721                            "Table with Elements",
722                            Table::new()
723                                .width(px(840.))
724                                .header(["Status", "Name", "Priority", "Deadline", "Action"])
725                                .row([
726                                    Indicator::dot().color(Color::Success).into_any_element(),
727                                    "Project A".into_any_element(),
728                                    "High".into_any_element(),
729                                    "2023-12-31".into_any_element(),
730                                    Button::new("view_a", "View")
731                                        .style(ButtonStyle::Filled)
732                                        .full_width()
733                                        .into_any_element(),
734                                ])
735                                .row([
736                                    Indicator::dot().color(Color::Warning).into_any_element(),
737                                    "Project B".into_any_element(),
738                                    "Medium".into_any_element(),
739                                    "2024-03-15".into_any_element(),
740                                    Button::new("view_b", "View")
741                                        .style(ButtonStyle::Filled)
742                                        .full_width()
743                                        .into_any_element(),
744                                ])
745                                .row([
746                                    Indicator::dot().color(Color::Error).into_any_element(),
747                                    "Project C".into_any_element(),
748                                    "Low".into_any_element(),
749                                    "2024-06-30".into_any_element(),
750                                    Button::new("view_c", "View")
751                                        .style(ButtonStyle::Filled)
752                                        .full_width()
753                                        .into_any_element(),
754                                ])
755                                .into_any_element(),
756                        )],
757                    ),
758                ])
759                .into_any_element(),
760        )
761    }
762}