liora_components/
page_header.rs1use 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 .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 .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 .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 .when_some(self.sub_title, |s, sub| {
131 s.child(div().text_sm().text_color(theme.neutral.text_3).child(sub))
132 }),
133 )
134 .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 .when_some(self.content, |s, content| {
147 s.child(div().child((content)(window, cx)))
148 })
149 .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}