1use gpui::{
2 AnyElement, App, InteractiveElement as _, IntoElement, ParentElement, Pixels, RenderOnce,
3 SharedString, StyleRefinement, Styled, TextAlign, Window, div, prelude::FluentBuilder as _, px,
4 relative,
5};
6use gpui_base::{
7 Table as BaseTable, TableBody as BaseTableBody, TableCaption as BaseTableCaption,
8 TableCell as BaseTableCell, TableHead as BaseTableHead, TableHeader as BaseTableHeader,
9 TableRow as BaseTableRow,
10};
11
12use crate::{ActiveTheme as _, AnyChildElement, ChildElement, Sizable, Size, StyledExt as _};
13
14const MIN_CELL_WIDTH: Pixels = px(100.);
15
16#[derive(IntoElement)]
41pub struct Table {
42 ix: usize,
43 style: StyleRefinement,
44 children: Vec<AnyChildElement>,
45 size: Size,
46 accessibility_label: Option<SharedString>,
47}
48
49impl Table {
50 pub fn new() -> Self {
51 Self {
52 ix: 0,
53 style: StyleRefinement::default(),
54 children: Vec::new(),
55 size: Size::default(),
56 accessibility_label: None,
57 }
58 }
59
60 pub fn accessibility_label(mut self, label: impl Into<SharedString>) -> Self {
65 self.accessibility_label = Some(label.into());
66 self
67 }
68
69 pub fn child(mut self, child: impl ChildElement + 'static) -> Self {
70 self.children.push(AnyChildElement::new(child));
71 self
72 }
73
74 pub fn children<E: ChildElement + 'static>(
75 mut self,
76 children: impl IntoIterator<Item = E>,
77 ) -> Self {
78 self.children
79 .extend(children.into_iter().map(AnyChildElement::new));
80 self
81 }
82}
83
84impl Styled for Table {
85 fn style(&mut self) -> &mut StyleRefinement {
86 &mut self.style
87 }
88}
89
90impl Sizable for Table {
91 fn with_size(mut self, size: impl Into<Size>) -> Self {
92 self.size = size.into();
93 self
94 }
95}
96
97impl ChildElement for Table {
98 fn with_ix(mut self, ix: usize) -> Self {
99 self.ix = ix;
100 self
101 }
102}
103
104impl RenderOnce for Table {
105 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
106 BaseTable::new(("table", self.ix))
107 .when_some(self.accessibility_label, |this, label| {
108 this.accessibility_label(label)
109 })
110 .w_full()
111 .text_sm()
112 .overflow_hidden()
113 .bg(cx.theme().tokens.table)
114 .refine_style(&self.style)
115 .children(
116 self.children
117 .into_iter()
118 .enumerate()
119 .map(|(ix, c)| c.into_any(ix, self.size)),
120 )
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn stores_an_explicit_accessibility_label() {
130 let plain = Table::new();
131 assert_eq!(plain.accessibility_label, None);
132
133 let named = Table::new().accessibility_label("Recent invoices");
134 assert_eq!(
135 named.accessibility_label.as_deref(),
136 Some("Recent invoices")
137 );
138 }
139}
140
141#[derive(IntoElement)]
143pub struct TableHeader {
144 ix: usize,
145 style: StyleRefinement,
146 children: Vec<AnyChildElement>,
147 size: Size,
148}
149
150impl TableHeader {
151 pub fn new() -> Self {
152 Self {
153 ix: 0,
154 style: StyleRefinement::default(),
155 children: Vec::new(),
156 size: Size::default(),
157 }
158 }
159
160 pub fn child(mut self, child: impl ChildElement + 'static) -> Self {
161 self.children.push(AnyChildElement::new(child));
162 self
163 }
164
165 pub fn children<E: ChildElement + 'static>(
166 mut self,
167 children: impl IntoIterator<Item = E>,
168 ) -> Self {
169 self.children
170 .extend(children.into_iter().map(AnyChildElement::new));
171 self
172 }
173}
174
175impl Styled for TableHeader {
176 fn style(&mut self) -> &mut StyleRefinement {
177 &mut self.style
178 }
179}
180
181impl ChildElement for TableHeader {
182 fn with_ix(mut self, ix: usize) -> Self {
183 self.ix = ix;
184 self
185 }
186}
187
188impl Sizable for TableHeader {
189 fn with_size(mut self, size: impl Into<Size>) -> Self {
190 self.size = size.into();
191 self
192 }
193}
194
195impl RenderOnce for TableHeader {
196 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
197 BaseTableHeader::new(("table-header", self.ix))
198 .w_full()
199 .bg(cx.theme().tokens.table_head)
200 .text_color(cx.theme().table_head_foreground)
201 .refine_style(&self.style)
202 .border_b_1()
203 .border_color(cx.theme().table_row_border)
204 .children(
205 self.children
206 .into_iter()
207 .enumerate()
208 .map(|(ix, c)| c.into_any(ix, self.size)),
209 )
210 }
211}
212
213#[derive(IntoElement)]
215pub struct TableBody {
216 ix: usize,
217 style: StyleRefinement,
218 children: Vec<AnyChildElement>,
219 size: Size,
220}
221
222impl TableBody {
223 pub fn new() -> Self {
224 Self {
225 ix: 0,
226 style: StyleRefinement::default(),
227 children: Vec::new(),
228 size: Size::default(),
229 }
230 }
231
232 pub fn child(mut self, child: impl ChildElement + 'static) -> Self {
233 self.children.push(AnyChildElement::new(child));
234 self
235 }
236
237 pub fn children<E: ChildElement + 'static>(
238 mut self,
239 children: impl IntoIterator<Item = E>,
240 ) -> Self {
241 self.children
242 .extend(children.into_iter().map(AnyChildElement::new));
243 self
244 }
245}
246
247impl Styled for TableBody {
248 fn style(&mut self) -> &mut StyleRefinement {
249 &mut self.style
250 }
251}
252
253impl Sizable for TableBody {
254 fn with_size(mut self, size: impl Into<Size>) -> Self {
255 self.size = size.into();
256 self
257 }
258}
259
260impl ChildElement for TableBody {
261 fn with_ix(mut self, ix: usize) -> Self {
262 self.ix = ix;
263 self
264 }
265}
266
267impl RenderOnce for TableBody {
268 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
269 BaseTableBody::new(("table-body", self.ix))
270 .w_full()
271 .refine_style(&self.style)
272 .children(
273 self.children
274 .into_iter()
275 .enumerate()
276 .map(|(ix, c)| c.into_any(ix, self.size)),
277 )
278 }
279}
280
281#[derive(IntoElement)]
283pub struct TableFooter {
284 ix: usize,
285 style: StyleRefinement,
286 children: Vec<AnyChildElement>,
287 size: Size,
288}
289
290impl TableFooter {
291 pub fn new() -> Self {
292 Self {
293 ix: 0,
294 style: StyleRefinement::default(),
295 children: Vec::new(),
296 size: Size::default(),
297 }
298 }
299
300 pub fn child(mut self, child: impl ChildElement + 'static) -> Self {
301 self.children.push(AnyChildElement::new(child));
302 self
303 }
304
305 pub fn children<E: ChildElement + 'static>(
306 mut self,
307 children: impl IntoIterator<Item = E>,
308 ) -> Self {
309 self.children
310 .extend(children.into_iter().map(AnyChildElement::new));
311 self
312 }
313}
314
315impl Styled for TableFooter {
316 fn style(&mut self) -> &mut StyleRefinement {
317 &mut self.style
318 }
319}
320
321impl Sizable for TableFooter {
322 fn with_size(mut self, size: impl Into<Size>) -> Self {
323 self.size = size.into();
324 self
325 }
326}
327
328impl ChildElement for TableFooter {
329 fn with_ix(mut self, ix: usize) -> Self {
330 self.ix = ix;
331 self
332 }
333}
334
335impl RenderOnce for TableFooter {
336 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
337 div()
338 .id(("table-footer", self.ix))
339 .w_full()
340 .bg(cx.theme().tokens.table_foot)
341 .text_color(cx.theme().table_foot_foreground)
342 .border_t_1()
343 .border_color(cx.theme().table_row_border)
344 .refine_style(&self.style)
345 .children(
346 self.children
347 .into_iter()
348 .enumerate()
349 .map(|(ix, c)| c.into_any(ix, self.size)),
350 )
351 }
352}
353
354#[derive(IntoElement)]
356pub struct TableRow {
357 ix: usize,
358 style: StyleRefinement,
359 children: Vec<AnyChildElement>,
360 size: Size,
361}
362
363impl TableRow {
364 pub fn new() -> Self {
365 Self {
366 ix: 0,
367 style: StyleRefinement::default(),
368 children: Vec::new(),
369 size: Size::default(),
370 }
371 }
372
373 pub fn child(mut self, child: impl ChildElement + 'static) -> Self {
374 self.children.push(AnyChildElement::new(child));
375 self
376 }
377
378 pub fn children<E: ChildElement + 'static>(
379 mut self,
380 children: impl IntoIterator<Item = E>,
381 ) -> Self {
382 self.children
383 .extend(children.into_iter().map(AnyChildElement::new));
384 self
385 }
386}
387
388impl Styled for TableRow {
389 fn style(&mut self) -> &mut StyleRefinement {
390 &mut self.style
391 }
392}
393
394impl Sizable for TableRow {
395 fn with_size(mut self, size: impl Into<Size>) -> Self {
396 self.size = size.into();
397 self
398 }
399}
400
401impl ChildElement for TableRow {
402 fn with_ix(mut self, ix: usize) -> Self {
403 self.ix = ix;
404 self
405 }
406}
407
408impl RenderOnce for TableRow {
409 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
410 BaseTableRow::new(("table-row", self.ix), self.ix + 1)
411 .w_full()
412 .flex()
413 .flex_row()
414 .refine_style(&self.style)
415 .border_color(cx.theme().table_row_border)
416 .when(self.ix > 0, |this| this.border_t_1())
417 .children(
418 self.children
419 .into_iter()
420 .enumerate()
421 .map(|(ix, c)| c.into_any(ix, self.size)),
422 )
423 }
424}
425
426#[derive(IntoElement)]
428pub struct TableHead {
429 ix: usize,
430 style: StyleRefinement,
431 children: Vec<AnyElement>,
432 col_span: usize,
433 align: TextAlign,
434 size: Size,
435}
436
437impl TableHead {
438 pub fn new() -> Self {
439 Self {
440 ix: 0,
441 style: StyleRefinement::default(),
442 children: Vec::new(),
443 col_span: 1,
444 align: TextAlign::Left,
445 size: Size::default(),
446 }
447 }
448
449 pub fn col_span(mut self, span: usize) -> Self {
451 self.col_span = span.max(1);
452 self
453 }
454
455 pub fn text_center(mut self) -> Self {
457 self.align = TextAlign::Center;
458 self
459 }
460
461 pub fn text_right(mut self) -> Self {
463 self.align = TextAlign::Right;
464 self
465 }
466}
467
468impl ParentElement for TableHead {
469 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
470 self.children.extend(elements);
471 }
472}
473
474impl Sizable for TableHead {
475 fn with_size(mut self, size: impl Into<Size>) -> Self {
476 self.size = size.into();
477 self
478 }
479}
480
481impl ChildElement for TableHead {
482 fn with_ix(mut self, ix: usize) -> Self {
483 self.ix = ix;
484 self
485 }
486}
487
488impl Styled for TableHead {
489 fn style(&mut self) -> &mut StyleRefinement {
490 &mut self.style
491 }
492}
493
494impl RenderOnce for TableHead {
495 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
496 let paddings = self.size.table_cell_padding();
497
498 BaseTableHead::new(("table-head", self.ix), self.ix + 1)
499 .flex()
500 .items_center()
501 .when(self.style.size.width.is_none(), |this| {
502 this.flex_shrink_1()
503 .flex_basis(relative(self.col_span as f32))
504 })
505 .min_w(MIN_CELL_WIDTH * self.col_span)
506 .px(paddings.left)
507 .py(paddings.top)
508 .when(self.align == TextAlign::Center, |this| {
509 this.justify_center()
510 })
511 .when(self.align == TextAlign::Right, |this| this.justify_end())
512 .refine_style(&self.style)
513 .children(self.children)
514 }
515}
516
517#[derive(IntoElement)]
519pub struct TableCell {
520 ix: usize,
521 style: StyleRefinement,
522 children: Vec<AnyElement>,
523 col_span: usize,
524 align: TextAlign,
525 size: Size,
526}
527
528impl TableCell {
529 pub fn new() -> Self {
530 Self {
531 ix: 0,
532 style: StyleRefinement::default(),
533 children: Vec::new(),
534 col_span: 1,
535 align: TextAlign::Left,
536 size: Size::default(),
537 }
538 }
539
540 pub fn col_span(mut self, span: usize) -> Self {
542 self.col_span = span.max(1);
543 self
544 }
545
546 pub fn text_center(mut self) -> Self {
548 self.align = TextAlign::Center;
549 self
550 }
551
552 pub fn text_right(mut self) -> Self {
554 self.align = TextAlign::Right;
555 self
556 }
557}
558
559impl ParentElement for TableCell {
560 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
561 self.children.extend(elements);
562 }
563}
564
565impl Sizable for TableCell {
566 fn with_size(mut self, size: impl Into<Size>) -> Self {
567 self.size = size.into();
568 self
569 }
570}
571
572impl ChildElement for TableCell {
573 fn with_ix(mut self, ix: usize) -> Self {
574 self.ix = ix;
575 self
576 }
577}
578
579impl Styled for TableCell {
580 fn style(&mut self) -> &mut StyleRefinement {
581 &mut self.style
582 }
583}
584
585impl RenderOnce for TableCell {
586 fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
587 let paddings = self.size.table_cell_padding();
588
589 BaseTableCell::new(("table-cell", self.ix), self.ix + 1)
590 .flex()
591 .items_center()
592 .when(self.style.size.width.is_none(), |this| {
593 this.flex_shrink_1()
594 .flex_basis(relative(self.col_span as f32))
595 })
596 .min_w(MIN_CELL_WIDTH * self.col_span)
597 .px(paddings.left)
598 .py(paddings.top)
599 .when(self.align == TextAlign::Center, |this| {
600 this.justify_center()
601 })
602 .when(self.align == TextAlign::Right, |this| this.justify_end())
603 .refine_style(&self.style)
604 .children(self.children)
605 }
606}
607
608#[derive(IntoElement)]
610pub struct TableCaption {
611 ix: usize,
612 style: StyleRefinement,
613 children: Vec<AnyElement>,
614 size: Size,
615}
616
617impl TableCaption {
618 pub fn new() -> Self {
619 Self {
620 ix: 0,
621 style: StyleRefinement::default(),
622 children: Vec::new(),
623 size: Size::default(),
624 }
625 }
626}
627
628impl ParentElement for TableCaption {
629 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
630 self.children.extend(elements);
631 }
632}
633
634impl Sizable for TableCaption {
635 fn with_size(mut self, size: impl Into<Size>) -> Self {
636 self.size = size.into();
637 self
638 }
639}
640
641impl ChildElement for TableCaption {
642 fn with_ix(mut self, ix: usize) -> Self {
643 self.ix = ix;
644 self
645 }
646}
647
648impl Styled for TableCaption {
649 fn style(&mut self) -> &mut StyleRefinement {
650 &mut self.style
651 }
652}
653
654impl RenderOnce for TableCaption {
655 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
656 let paddings = self.size.table_cell_padding();
657
658 BaseTableCaption::new(("table-caption", self.ix))
659 .w_full()
660 .px(paddings.left)
661 .py(paddings.top)
662 .text_sm()
663 .text_color(cx.theme().muted_foreground)
664 .text_center()
665 .refine_style(&self.style)
666 .children(self.children)
667 }
668}