Skip to main content

guise/data/
list.rs

1//! `List` — an ordered or unordered list of text items.
2
3use gpui::prelude::*;
4use gpui::{div, px, App, IntoElement, SharedString, Window};
5
6use crate::theme::{theme, Size};
7
8/// A bulleted or numbered list. The Mantine `List`.
9#[derive(IntoElement)]
10pub struct List {
11    items: Vec<SharedString>,
12    ordered: bool,
13    size: Size,
14    spacing: Size,
15    /// Custom bullet glyph for unordered lists (defaults to a dot).
16    icon: Option<SharedString>,
17}
18
19impl List {
20    pub fn new() -> Self {
21        List {
22            items: Vec::new(),
23            ordered: false,
24            size: Size::Md,
25            spacing: Size::Xs,
26            icon: None,
27        }
28    }
29
30    pub fn item(mut self, item: impl Into<SharedString>) -> Self {
31        self.items.push(item.into());
32        self
33    }
34
35    pub fn items<I, S>(mut self, items: I) -> Self
36    where
37        I: IntoIterator<Item = S>,
38        S: Into<SharedString>,
39    {
40        self.items.extend(items.into_iter().map(Into::into));
41        self
42    }
43
44    pub fn ordered(mut self, ordered: bool) -> Self {
45        self.ordered = ordered;
46        self
47    }
48
49    pub fn size(mut self, size: Size) -> Self {
50        self.size = size;
51        self
52    }
53
54    pub fn spacing(mut self, spacing: Size) -> Self {
55        self.spacing = spacing;
56        self
57    }
58
59    pub fn icon(mut self, icon: impl Into<SharedString>) -> Self {
60        self.icon = Some(icon.into());
61        self
62    }
63}
64
65impl Default for List {
66    fn default() -> Self {
67        List::new()
68    }
69}
70
71impl RenderOnce for List {
72    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
73        let t = theme(cx);
74        let font = t.font_size(self.size);
75        let gap = t.spacing(self.spacing);
76        let text = t.text().hsla();
77        let marker = t.dimmed().hsla();
78        let ordered = self.ordered;
79        let icon = self.icon.clone();
80
81        let rows = self.items.into_iter().enumerate().map(move |(i, item)| {
82            let bullet: SharedString = if ordered {
83                SharedString::from(format!("{}.", i + 1))
84            } else if let Some(glyph) = icon.clone() {
85                glyph
86            } else {
87                SharedString::new_static("\u{2022}")
88            };
89            div()
90                .flex()
91                .items_start()
92                .gap(px(8.0))
93                .text_size(px(font))
94                .child(div().min_w(px(font * 1.2)).text_color(marker).child(bullet))
95                .child(div().text_color(text).child(item))
96        });
97
98        div().flex().flex_col().gap(px(gap)).children(rows)
99    }
100}