use std::time::Duration;
use gpui_kit::{
App, ElementId, InteractiveElement as _, IntoElement, ParentElement as _, RenderOnce, Role,
SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled, TestSupportExt as _,
Window, assets::IconName, base::StyledExt as _, div, percentage,
};
use crate::{Icon, Theme, looping};
#[derive(IntoElement)]
pub struct Spinner {
id: ElementId,
style: StyleRefinement,
accessibility_label: Option<SharedString>,
}
impl Spinner {
pub fn new(id: impl Into<ElementId>) -> Self {
Self {
id: id.into(),
style: StyleRefinement::default(),
accessibility_label: None,
}
}
pub fn accessibility_label(mut self, label: impl Into<SharedString>) -> Self {
self.accessibility_label = Some(label.into());
self
}
}
impl Styled for Spinner {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
const TURN: Duration = Duration::from_secs(1);
const TURN_FPS: u32 = 30;
const DEFAULT_LABEL: &str = "Loading";
impl RenderOnce for Spinner {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let icon = Icon::from(IconName::LoaderCircle).size_full();
let icon = if Theme::holds_still(cx) {
icon
} else {
icon.rotate(percentage(looping::phase(TURN, TURN_FPS, window, cx)))
};
div()
.id(self.id)
.role(Role::Status)
.aria_label(
self.accessibility_label
.unwrap_or_else(|| DEFAULT_LABEL.into()),
)
.test_support()
.flex_shrink_0()
.size_4()
.refine_style(&self.style)
.child(icon)
}
}