liora-components 0.1.2

Enterprise-style native GPUI component library for Liora applications.
Documentation
use crate::gpui_compat::element_id;
use gpui::{App, IntoElement, RenderOnce, SharedString, Window, div, prelude::*, px};
use liora_core::Config;
use liora_icons::Icon;
use liora_icons_lucide::IconName;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BreadcrumbSeparator {
    String(SharedString),
    Icon(IconName),
}

pub struct BreadcrumbItem {
    pub label: SharedString,
    pub icon: Option<IconName>,
    pub on_click: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
}

pub struct Breadcrumb {
    separator: BreadcrumbSeparator,
    items: Vec<BreadcrumbItem>,
}

impl BreadcrumbItem {
    pub fn new(label: impl Into<SharedString>) -> Self {
        Self {
            label: label.into(),
            icon: None,
            on_click: None,
        }
    }

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

    pub fn on_click(mut self, f: impl Fn(&mut Window, &mut App) + 'static) -> Self {
        self.on_click = Some(Box::new(f));
        self
    }
}

impl Breadcrumb {
    pub fn new() -> Self {
        Self {
            separator: BreadcrumbSeparator::String("/".into()),
            items: vec![],
        }
    }

    pub fn separator(mut self, s: impl Into<SharedString>) -> Self {
        self.separator = BreadcrumbSeparator::String(s.into());
        self
    }

    pub fn separator_icon(mut self, icon: IconName) -> Self {
        self.separator = BreadcrumbSeparator::Icon(icon);
        self
    }

    pub fn item(mut self, item: BreadcrumbItem) -> Self {
        self.items.push(item);
        self
    }
}

impl RenderOnce for Breadcrumb {
    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
        let theme = cx.global::<Config>().theme.clone();
        let items_count = self.items.len();
        let separator = self.separator;

        div().flex().flex_row().items_center().gap_1().children(
            self.items.into_iter().enumerate().map(|(i, item)| {
                let is_last = i == items_count - 1;
                let has_click = item.on_click.is_some();
                let on_click = item.on_click;

                div()
                    .flex()
                    .flex_row()
                    .items_center()
                    .child(
                        div()
                            .id(element_id(format!("breadcrumb-item-{}", i)))
                            .flex()
                            .flex_row()
                            .items_center()
                            .gap_1()
                            .text_color(if is_last {
                                theme.neutral.text_1
                            } else {
                                theme.neutral.text_2
                            })
                            .when(is_last, |s| s.font_weight(gpui::FontWeight::BOLD))
                            .when(!is_last && has_click, |s| {
                                s.cursor_pointer()
                                    .hover(|s| s.text_color(theme.primary.base))
                                    .on_click(move |_, window, cx| {
                                        if let Some(ref f) = on_click {
                                            (f)(window, cx);
                                        }
                                    })
                            })
                            .when_some(item.icon, |s, icon| {
                                s.child(Icon::new(icon).size(px(14.0)).color(theme.neutral.icon))
                            })
                            .child(div().text_sm().child(item.label)),
                    )
                    .when(!is_last, |s| {
                        s.child(
                            div().px_2().flex().items_center().justify_center().child(
                                match separator.clone() {
                                    BreadcrumbSeparator::String(sep) => div()
                                        .text_sm()
                                        .text_color(theme.neutral.text_3)
                                        .child(sep)
                                        .into_any_element(),
                                    BreadcrumbSeparator::Icon(icon) => Icon::new(icon)
                                        .size(px(12.0))
                                        .color(theme.neutral.icon)
                                        .into_any_element(),
                                },
                            ),
                        )
                    })
            }),
        )
    }
}

impl IntoElement for Breadcrumb {
    type Element = gpui::Component<Self>;
    fn into_element(self) -> Self::Element {
        gpui::Component::new(self)
    }
}