kael_ui 0.2.0

Professional shadcn-inspired UI component library for Kael. 100+ accessible components for building beautiful, performant desktop applications.
use kael::{prelude::FluentBuilder as _, *};
use std::time::Duration;

use crate::animations::easings;
use crate::components::progress::{ProgressSize, ProgressVariant};
use crate::theme::use_theme;

#[derive(IntoElement)]
pub struct AnimatedProgress {
    id: ElementId,
    base: Div,
    value: f32,
    variant: ProgressVariant,
    size: ProgressSize,
    shimmer: bool,
    color: Option<Hsla>,
    duration: Duration,
}

impl AnimatedProgress {
    pub fn new(id: impl Into<ElementId>) -> Self {
        Self {
            id: id.into(),
            base: div(),
            value: 0.0,
            variant: ProgressVariant::Default,
            size: ProgressSize::Md,
            shimmer: false,
            color: None,
            duration: Duration::from_millis(500),
        }
    }

    pub fn value(mut self, value: f32) -> Self {
        self.value = value.clamp(0.0, 1.0);
        self
    }

    pub fn variant(mut self, variant: ProgressVariant) -> Self {
        self.variant = variant;
        self
    }

    pub fn size(mut self, size: ProgressSize) -> Self {
        self.size = size;
        self
    }

    pub fn shimmer(mut self, shimmer: bool) -> Self {
        self.shimmer = shimmer;
        self
    }

    pub fn color(mut self, color: Hsla) -> Self {
        self.color = Some(color);
        self
    }

    pub fn duration(mut self, duration: Duration) -> Self {
        self.duration = duration;
        self
    }
}

impl Styled for AnimatedProgress {
    fn style(&mut self) -> &mut StyleRefinement {
        self.base.style()
    }
}

impl RenderOnce for AnimatedProgress {
    fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
        let theme = use_theme();

        let height = match self.size {
            ProgressSize::Sm => px(4.0),
            ProgressSize::Md => px(8.0),
            ProgressSize::Lg => px(12.0),
        };

        let bar_color = self.color.unwrap_or_else(|| match self.variant {
            ProgressVariant::Default => theme.tokens.primary,
            ProgressVariant::Success => rgb(0x22c55e).into(),
            ProgressVariant::Warning => rgb(0xf59e0b).into(),
            ProgressVariant::Destructive => theme.tokens.destructive,
        });

        let target_value = self.value;
        let duration = self.duration;
        let shimmer_enabled = self.shimmer;
        let value_key = (target_value * 10000.0) as u32;

        self.base.w_full().child(
            div()
                .relative()
                .w_full()
                .h(height)
                .rounded(theme.tokens.radius_lg)
                .bg(theme.tokens.muted)
                .overflow_hidden()
                .child(
                    div()
                        .id(self.id.clone())
                        .absolute()
                        .top_0()
                        .left_0()
                        .h_full()
                        .bg(bar_color)
                        .rounded(theme.tokens.radius_lg)
                        .overflow_hidden()
                        .when(shimmer_enabled, |el| {
                            el.child(
                                div()
                                    .absolute()
                                    .top_0()
                                    .bottom_0()
                                    .w(px(120.0))
                                    .bg(kael::linear_gradient(
                                        90.0,
                                        kael::linear_color_stop(kael::transparent_black(), 0.0),
                                        kael::linear_color_stop(hsla(0.0, 0.0, 1.0, 0.2), 1.0),
                                    ))
                                    .with_animation(
                                        "shimmer-sweep",
                                        Animation::new(Duration::from_millis(1500))
                                            .repeat_forever()
                                            .with_easing(kael::linear),
                                        move |el, delta| {
                                            let start = px(-120.0);
                                            let end = px(600.0);
                                            let pos = start + (end - start) * delta;
                                            el.left(pos)
                                        },
                                    ),
                            )
                        })
                        .with_animation(
                            ("progress-fill", value_key),
                            Animation::new(duration).with_easing(easings::ease_out_cubic),
                            move |el, delta| {
                                let width = target_value * delta;
                                el.w(relative(width))
                            },
                        ),
                ),
        )
    }
}