liora_components/
loading.rs1use crate::motion::{fade_in, spin_icon};
2use gpui::{App, Component, IntoElement, RenderOnce, SharedString, Window, div, prelude::*, px};
3use liora_core::Config;
4use liora_icons::Icon;
5use liora_icons_lucide::IconName;
6
7pub struct Loading {
8 text: Option<SharedString>,
9 full_screen: bool,
10}
11
12impl Loading {
13 pub fn new() -> Self {
14 Self {
15 text: None,
16 full_screen: false,
17 }
18 }
19
20 pub fn text(mut self, text: impl Into<SharedString>) -> Self {
21 self.text = Some(text.into());
22 self
23 }
24
25 pub fn full_screen(mut self) -> Self {
26 self.full_screen = true;
27 self
28 }
29}
30
31impl RenderOnce for Loading {
32 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
33 let theme = cx.global::<Config>().theme.clone();
34
35 let spinner_icon = spin_icon(
36 "liora-loading-spinner-motion",
37 Icon::new(IconName::LoaderCircle)
38 .size(px(32.0))
39 .color(theme.primary.base),
40 );
41
42 let spinner = div()
43 .flex()
44 .flex_col()
45 .items_center()
46 .gap_2()
47 .child(spinner_icon)
48 .when_some(self.text, |s, t| {
49 s.child(div().text_sm().text_color(theme.primary.base).child(t))
50 });
51
52 if self.full_screen {
53 fade_in(
54 "liora-loading-fullscreen-motion",
55 div()
56 .absolute()
57 .size_full()
58 .bg(theme.neutral.mask)
59 .flex()
60 .items_center()
61 .justify_center()
62 .child(spinner),
63 )
64 } else {
65 fade_in("liora-loading-inline-motion", spinner)
66 }
67 }
68}
69
70impl IntoElement for Loading {
71 type Element = Component<Self>;
72 fn into_element(self) -> Self::Element {
73 Component::new(self)
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 #[test]
80 fn loading_uses_spin_and_fade_motion() {
81 let source = include_str!("loading.rs")
82 .split("#[cfg(test)]")
83 .next()
84 .unwrap();
85
86 assert!(source.contains("spin_icon("));
87 assert!(source.contains("fade_in("));
88 assert!(source.contains("liora-loading-spinner-motion"));
89 }
90}