Skip to main content

liora_components/
result.rs

1use gpui::{AnyElement, App, IntoElement, RenderOnce, SharedString, Window, div, prelude::*, px};
2use liora_core::Config;
3use liora_icons::Icon;
4use liora_icons_lucide::IconName;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum ResultStatus {
8    #[default]
9    Info,
10    Success,
11    Warning,
12    Error,
13}
14
15pub struct Result {
16    status: ResultStatus,
17    title: SharedString,
18    sub_title: Option<SharedString>,
19    icon: Option<AnyElement>,
20    extra: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,
21}
22
23impl Result {
24    pub fn new(title: impl Into<SharedString>) -> Self {
25        Self {
26            status: ResultStatus::Info,
27            title: title.into(),
28            sub_title: None,
29            icon: None,
30            extra: None,
31        }
32    }
33
34    pub fn status(mut self, s: ResultStatus) -> Self {
35        self.status = s;
36        self
37    }
38
39    pub fn sub_title(mut self, sub: impl Into<SharedString>) -> Self {
40        self.sub_title = Some(sub.into());
41        self
42    }
43
44    pub fn icon(mut self, icon: impl IntoElement) -> Self {
45        self.icon = Some(icon.into_any_element());
46        self
47    }
48
49    pub fn extra<F>(mut self, f: F) -> Self
50    where
51        F: Fn(&mut Window, &mut App) -> AnyElement + 'static,
52    {
53        self.extra = Some(Box::new(f));
54        self
55    }
56}
57
58impl RenderOnce for Result {
59    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
60        let theme = cx.global::<Config>().theme.clone();
61
62        let (icon_name, color) = match self.status {
63            ResultStatus::Success => (IconName::CircleCheckBig, theme.success.base),
64            ResultStatus::Warning => (IconName::TriangleAlert, theme.warning.base),
65            ResultStatus::Error => (IconName::CircleX, theme.danger.base),
66            ResultStatus::Info => (IconName::Info, theme.primary.base),
67        };
68
69        div()
70            .flex()
71            .flex_col()
72            .items_center()
73            .justify_center()
74            .w_full()
75            .p_12()
76            .gap_4()
77            .child(
78                div()
79                    .flex()
80                    .items_center()
81                    .justify_center()
82                    .w(px(72.0))
83                    .h(px(72.0))
84                    .child(match self.icon {
85                        Some(icon) => icon,
86                        None => Icon::new(icon_name)
87                            .size(px(72.0))
88                            .color(color)
89                            .into_any_element(),
90                    }),
91            )
92            .child(
93                div()
94                    .text_xl()
95                    .font_weight(gpui::FontWeight::BOLD)
96                    .text_color(theme.neutral.text_1)
97                    .child(self.title),
98            )
99            .when_some(self.sub_title, |s, sub| {
100                s.child(div().text_sm().text_color(theme.neutral.text_3).child(sub))
101            })
102            .when_some(self.extra, |s, extra| {
103                s.child(div().mt_4().child((extra)(window, cx)))
104            })
105    }
106}
107
108impl IntoElement for Result {
109    type Element = gpui::Component<Self>;
110    fn into_element(self) -> Self::Element {
111        gpui::Component::new(self)
112    }
113}