Skip to main content

liora_components/
page_header.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
6pub struct PageHeader {
7    title: SharedString,
8    sub_title: Option<SharedString>,
9    back_icon: Option<IconName>,
10    on_back: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
11    extra: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,
12    content: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,
13    footer: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyElement + 'static>>,
14}
15
16impl PageHeader {
17    pub fn new(title: impl Into<SharedString>) -> Self {
18        Self {
19            title: title.into(),
20            sub_title: None,
21            back_icon: Some(IconName::ArrowLeft),
22            on_back: None,
23            extra: None,
24            content: None,
25            footer: None,
26        }
27    }
28
29    pub fn sub_title(mut self, sub_title: impl Into<SharedString>) -> Self {
30        self.sub_title = Some(sub_title.into());
31        self
32    }
33
34    pub fn back_icon(mut self, icon: IconName) -> Self {
35        self.back_icon = Some(icon);
36        self
37    }
38
39    pub fn on_back(mut self, f: impl Fn(&mut Window, &mut App) + 'static) -> Self {
40        self.on_back = Some(Box::new(f));
41        self
42    }
43
44    pub fn extra<F>(mut self, f: F) -> Self
45    where
46        F: Fn(&mut Window, &mut App) -> AnyElement + 'static,
47    {
48        self.extra = Some(Box::new(f));
49        self
50    }
51
52    pub fn content<F>(mut self, f: F) -> Self
53    where
54        F: Fn(&mut Window, &mut App) -> AnyElement + 'static,
55    {
56        self.content = Some(Box::new(f));
57        self
58    }
59
60    pub fn footer<F>(mut self, f: F) -> Self
61    where
62        F: Fn(&mut Window, &mut App) -> AnyElement + 'static,
63    {
64        self.footer = Some(Box::new(f));
65        self
66    }
67}
68
69impl RenderOnce for PageHeader {
70    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
71        let theme = cx.global::<Config>().theme.clone();
72
73        div()
74            .flex()
75            .flex_col()
76            .w_full()
77            .p_4()
78            .gap_4()
79            .bg(theme.neutral.card)
80            .child(
81                div()
82                    .flex()
83                    .flex_row()
84                    .items_center()
85                    .justify_between()
86                    .child(
87                        div()
88                            .flex()
89                            .flex_row()
90                            .items_center()
91                            .gap_3()
92                            // Back Button
93                            .when_some(self.back_icon, |s, icon| {
94                                s.child(
95                                    div()
96                                        .id("back-btn")
97                                        .cursor_pointer()
98                                        .flex()
99                                        .items_center()
100                                        .justify_center()
101                                        .w_8()
102                                        .h_8()
103                                        .rounded_full()
104                                        .hover(|s| s.bg(theme.neutral.hover))
105                                        .on_click(move |_, window, cx| {
106                                            if let Some(ref f) = self.on_back {
107                                                (f)(window, cx);
108                                            }
109                                        })
110                                        .child(
111                                            Icon::new(icon)
112                                                .size(px(20.0))
113                                                .color(theme.neutral.text_1),
114                                        ),
115                                )
116                            })
117                            // Title
118                            .child(
119                                div()
120                                    .text_lg()
121                                    .font_weight(gpui::FontWeight::BOLD)
122                                    .text_color(theme.neutral.text_1)
123                                    .child(self.title),
124                            )
125                            // Divider
126                            .when(self.sub_title.is_some(), |s| {
127                                s.child(div().w(px(1.0)).h(px(16.0)).bg(theme.neutral.border))
128                            })
129                            // Subtitle
130                            .when_some(self.sub_title, |s, sub| {
131                                s.child(div().text_sm().text_color(theme.neutral.text_3).child(sub))
132                            }),
133                    )
134                    // Extra
135                    .when_some(self.extra, |s, extra| {
136                        s.child(
137                            div()
138                                .flex()
139                                .flex_row()
140                                .items_center()
141                                .child((extra)(window, cx)),
142                        )
143                    }),
144            )
145            // Content
146            .when_some(self.content, |s, content| {
147                s.child(div().child((content)(window, cx)))
148            })
149            // Footer
150            .when_some(self.footer, |s, footer| {
151                s.child(
152                    div()
153                        .border_t_1()
154                        .border_color(theme.neutral.border)
155                        .pt_4()
156                        .child((footer)(window, cx)),
157                )
158            })
159    }
160}
161
162impl IntoElement for PageHeader {
163    type Element = gpui::Component<Self>;
164    fn into_element(self) -> Self::Element {
165        gpui::Component::new(self)
166    }
167}