Skip to main content

liora_components/
descriptions.rs

1use gpui::{AnyElement, App, IntoElement, RenderOnce, SharedString, Window, div, prelude::*, px};
2use liora_core::Config;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5pub enum DescriptionsDirection {
6    #[default]
7    Horizontal,
8    Vertical,
9}
10
11pub struct DescriptionItem {
12    pub label: SharedString,
13    pub value: AnyElement,
14    pub span: u32,
15}
16
17pub struct Descriptions {
18    title: Option<SharedString>,
19    extra: Option<AnyElement>,
20    column: u32,
21    direction: DescriptionsDirection,
22    border: bool,
23    items: Vec<DescriptionItem>,
24}
25
26impl Descriptions {
27    pub fn new() -> Self {
28        Self {
29            title: None,
30            extra: None,
31            column: 3,
32            direction: DescriptionsDirection::Horizontal,
33            border: false,
34            items: vec![],
35        }
36    }
37
38    pub fn title(mut self, title: impl Into<SharedString>) -> Self {
39        self.title = Some(title.into());
40        self
41    }
42
43    pub fn extra(mut self, extra: impl IntoElement) -> Self {
44        self.extra = Some(extra.into_any_element());
45        self
46    }
47
48    pub fn column(mut self, c: u32) -> Self {
49        self.column = c.max(1);
50        self
51    }
52
53    pub fn direction(mut self, d: DescriptionsDirection) -> Self {
54        self.direction = d;
55        self
56    }
57
58    pub fn border(mut self, b: bool) -> Self {
59        self.border = b;
60        self
61    }
62
63    pub fn item(
64        mut self,
65        label: impl Into<SharedString>,
66        value: impl IntoElement,
67        span: u32,
68    ) -> Self {
69        self.items.push(DescriptionItem {
70            label: label.into(),
71            value: value.into_any_element(),
72            span: span.max(1),
73        });
74        self
75    }
76}
77
78impl RenderOnce for Descriptions {
79    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
80        let theme = cx.global::<Config>().theme.clone();
81        let column = self.column;
82        let border = self.border;
83        let direction = self.direction;
84
85        // Group items into rows
86        let mut rows: Vec<Vec<DescriptionItem>> = vec![];
87        let mut current_row: Vec<DescriptionItem> = vec![];
88        let mut current_span = 0;
89
90        for item in self.items {
91            let item_span = item.span.min(column);
92            if current_span + item_span > column {
93                if let Some(last) = current_row.last_mut() {
94                    last.span += column - current_span;
95                }
96                rows.push(current_row);
97                current_row = vec![];
98                current_span = 0;
99            }
100            current_span += item_span;
101            current_row.push(item);
102        }
103        if !current_row.is_empty() {
104            if let Some(last) = current_row.last_mut() {
105                last.span += column - current_span;
106            }
107            rows.push(current_row);
108        }
109
110        div()
111            .flex()
112            .flex_col()
113            .w_full()
114            .gap_4()
115            .when(self.title.is_some() || self.extra.is_some(), |s| {
116                s.child(
117                    div()
118                        .flex()
119                        .flex_row()
120                        .items_center()
121                        .justify_between()
122                        .when_some(self.title, |s, t| {
123                            s.child(
124                                div()
125                                    .text_lg()
126                                    .font_weight(gpui::FontWeight::BOLD)
127                                    .text_color(theme.neutral.text_1)
128                                    .child(t),
129                            )
130                        })
131                        .when_some(self.extra, |s, e| s.child(e)),
132                )
133            })
134            .child(
135                div()
136                    .flex()
137                    .flex_col()
138                    .when(border, |s| {
139                        s.border_1()
140                            .border_color(theme.neutral.border)
141                            .rounded(px(theme.radius.sm))
142                            .overflow_hidden()
143                    })
144                    .children(rows.into_iter().enumerate().map(|(row_idx, row)| {
145                        div()
146                            .flex()
147                            .flex_row()
148                            .w_full()
149                            .when(border && row_idx > 0, |s| {
150                                s.border_t_1().border_color(theme.neutral.border)
151                            })
152                            .children(row.into_iter().enumerate().map(|(col_idx, item)| {
153                                let width = gpui::relative(item.span as f32 / column as f32);
154                                let cell = div().w(width).flex().when(border && col_idx > 0, |s| {
155                                    s.border_l_1().border_color(theme.neutral.border)
156                                });
157
158                                match direction {
159                                    DescriptionsDirection::Horizontal => {
160                                        if border {
161                                            cell.flex_row()
162                                                .items_start()
163                                                .child(
164                                                    div()
165                                                        .p_3()
166                                                        .bg(theme.neutral.hover)
167                                                        .border_r_1()
168                                                        .border_color(theme.neutral.border)
169                                                        .flex()
170                                                        .items_center()
171                                                        .child(
172                                                            div()
173                                                                .text_sm()
174                                                                .font_weight(gpui::FontWeight::BOLD)
175                                                                .text_color(theme.neutral.text_2)
176                                                                .child(item.label),
177                                                        ),
178                                                )
179                                                .child(
180                                                    div()
181                                                        .flex_1()
182                                                        .p_3()
183                                                        .flex()
184                                                        .items_center()
185                                                        .child(
186                                                            div()
187                                                                .text_sm()
188                                                                .text_color(theme.neutral.text_1)
189                                                                .child(item.value),
190                                                        ),
191                                                )
192                                        } else {
193                                            cell.flex_row()
194                                                .items_center()
195                                                .gap_2()
196                                                .p_1()
197                                                .child(
198                                                    div()
199                                                        .text_sm()
200                                                        .text_color(theme.neutral.text_3)
201                                                        .child(format!("{}:", item.label)),
202                                                )
203                                                .child(
204                                                    div()
205                                                        .text_sm()
206                                                        .text_color(theme.neutral.text_1)
207                                                        .child(item.value),
208                                                )
209                                        }
210                                    }
211                                    DescriptionsDirection::Vertical => {
212                                        if border {
213                                            cell.flex_col()
214                                                .child(
215                                                    div()
216                                                        .p_3()
217                                                        .bg(theme.neutral.hover)
218                                                        .border_b_1()
219                                                        .border_color(theme.neutral.border)
220                                                        .child(
221                                                            div()
222                                                                .text_sm()
223                                                                .font_weight(gpui::FontWeight::BOLD)
224                                                                .text_color(theme.neutral.text_2)
225                                                                .child(item.label),
226                                                        ),
227                                                )
228                                                .child(
229                                                    div().p_3().child(
230                                                        div()
231                                                            .text_sm()
232                                                            .text_color(theme.neutral.text_1)
233                                                            .child(item.value),
234                                                    ),
235                                                )
236                                        } else {
237                                            cell.flex_col()
238                                                .gap_1()
239                                                .p_1()
240                                                .child(
241                                                    div()
242                                                        .text_xs()
243                                                        .text_color(theme.neutral.text_3)
244                                                        .child(item.label),
245                                                )
246                                                .child(
247                                                    div()
248                                                        .text_sm()
249                                                        .text_color(theme.neutral.text_1)
250                                                        .child(item.value),
251                                                )
252                                        }
253                                    }
254                                }
255                            }))
256                    })),
257            )
258    }
259}
260
261impl IntoElement for Descriptions {
262    type Element = gpui::Component<Self>;
263    fn into_element(self) -> Self::Element {
264        gpui::Component::new(self)
265    }
266}