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