kael_ui 0.2.0

Professional shadcn-inspired UI component library for Kael. 100+ accessible components for building beautiful, performant desktop applications.
//! Status bar component with customizable sections.

use crate::{
    components::{
        badge::{Badge, BadgeVariant},
        icon::Icon,
        icon_source::IconSource,
        text::caption,
    },
    theme::use_theme,
};
use kael::{prelude::FluentBuilder as _, InteractiveElement, *};
use std::rc::Rc;

#[derive(Clone)]
pub struct StatusItem {
    pub icon: Option<IconSource>,
    pub text: Option<SharedString>,
    pub badge: Option<SharedString>,
    pub badge_variant: BadgeVariant,
    pub on_click: Option<Rc<dyn Fn(&mut Window, &mut App)>>,
    pub disabled: bool,
    pub tooltip: Option<SharedString>,
}

impl StatusItem {
    pub fn text(text: impl Into<SharedString>) -> Self {
        Self {
            icon: None,
            text: Some(text.into()),
            badge: None,
            badge_variant: BadgeVariant::Default,
            on_click: None,
            disabled: false,
            tooltip: None,
        }
    }

    pub fn icon(icon: impl Into<IconSource>) -> Self {
        Self {
            icon: Some(icon.into()),
            text: None,
            badge: None,
            badge_variant: BadgeVariant::Default,
            on_click: None,
            disabled: false,
            tooltip: None,
        }
    }

    pub fn icon_text(icon: impl Into<IconSource>, text: impl Into<SharedString>) -> Self {
        Self {
            icon: Some(icon.into()),
            text: Some(text.into()),
            badge: None,
            badge_variant: BadgeVariant::Default,
            on_click: None,
            disabled: false,
            tooltip: None,
        }
    }

    pub fn badge(text: impl Into<SharedString>, tooltip: impl Into<SharedString>) -> Self {
        Self {
            icon: None,
            text: None,
            badge: Some(text.into()),
            badge_variant: BadgeVariant::Default,
            on_click: None,
            disabled: false,
            tooltip: Some(tooltip.into()),
        }
    }

    pub fn icon_badge(icon: impl Into<IconSource>, badge: impl Into<SharedString>) -> Self {
        Self {
            icon: Some(icon.into()),
            text: None,
            badge: Some(badge.into()),
            badge_variant: BadgeVariant::Default,
            on_click: None,
            disabled: false,
            tooltip: None,
        }
    }

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

    pub fn on_click<F>(mut self, handler: F) -> Self
    where
        F: Fn(&mut Window, &mut App) + 'static,
    {
        self.on_click = Some(Rc::new(handler));
        self
    }

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

    pub fn tooltip(mut self, tooltip: impl Into<SharedString>) -> Self {
        self.tooltip = Some(tooltip.into());
        self
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StatusBarSection {
    Left,
    Center,
    Right,
}

pub struct StatusBar {
    left_items: Vec<StatusItem>,
    center_items: Vec<StatusItem>,
    right_items: Vec<StatusItem>,
    height: Pixels,
    style: StyleRefinement,
}

impl StatusBar {
    pub fn new() -> Self {
        Self {
            left_items: Vec::new(),
            center_items: Vec::new(),
            right_items: Vec::new(),
            height: px(28.0),
            style: StyleRefinement::default(),
        }
    }

    pub fn left(mut self, items: Vec<StatusItem>) -> Self {
        self.left_items = items;
        self
    }

    pub fn center(mut self, items: Vec<StatusItem>) -> Self {
        self.center_items = items;
        self
    }

    pub fn right(mut self, items: Vec<StatusItem>) -> Self {
        self.right_items = items;
        self
    }

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

    pub fn add_left(mut self, item: StatusItem) -> Self {
        self.left_items.push(item);
        self
    }

    pub fn add_center(mut self, item: StatusItem) -> Self {
        self.center_items.push(item);
        self
    }

    pub fn add_right(mut self, item: StatusItem) -> Self {
        self.right_items.push(item);
        self
    }
}

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

impl Default for StatusBar {
    fn default() -> Self {
        Self::new()
    }
}

impl Render for StatusBar {
    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
        let theme = use_theme();
        let user_style = self.style.clone();

        div()
            .flex()
            .items_center()
            .justify_between()
            .h(self.height)
            .px(px(12.0))
            .py(px(6.0))
            .gap(px(12.0))
            .bg(theme.tokens.card)
            .border_t_1()
            .border_color(theme.tokens.border)
            .map(|this| {
                let mut div = this;
                div.style().refine(&user_style);
                div
            })
            .child(
                div().flex().items_center().gap(px(12.0)).children(
                    self.left_items
                        .iter()
                        .map(|item| render_status_item(item.clone())),
                ),
            )
            .child(
                div().flex().items_center().gap(px(12.0)).children(
                    self.center_items
                        .iter()
                        .map(|item| render_status_item(item.clone())),
                ),
            )
            .child(
                div().flex().items_center().gap(px(12.0)).children(
                    self.right_items
                        .iter()
                        .map(|item| render_status_item(item.clone())),
                ),
            )
    }
}

fn render_status_item(item: StatusItem) -> impl IntoElement {
    let theme = use_theme();

    div()
        .flex()
        .items_center()
        .gap(px(6.0))
        .px(px(8.0))
        .py(px(4.0))
        .rounded(theme.tokens.radius_sm)
        .when(!item.disabled && item.on_click.is_some(), |div| {
            div.cursor(CursorStyle::PointingHand)
                .hover(|style| style.bg(theme.tokens.muted))
        })
        .when(item.disabled, |div| div.opacity(0.5))
        .when_some(item.on_click.filter(|_| !item.disabled), |div, handler| {
            div.on_mouse_down(MouseButton::Left, move |_event, window, cx| {
                handler(window, cx);
            })
        })
        .when_some(item.icon, |div, icon| {
            div.child(Icon::new(icon).size(px(14.0)).color(if item.disabled {
                theme.tokens.muted_foreground
            } else {
                theme.tokens.foreground
            }))
        })
        .when_some(item.text, |div, text| {
            div.child(caption(text).color(if item.disabled {
                theme.tokens.muted_foreground
            } else {
                theme.tokens.foreground
            }))
        })
        .when_some(item.badge, |div, badge_text| {
            div.child(Badge::new(badge_text).variant(item.badge_variant))
        })
}