diff --git a/README.md b/README.md index f147dd09..b241c63b 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ A UI components for building desktop application by using [GPUI](https://gpui.rs - [x] Accordion - [x] Sidebar - [x] Breadcrumb +- [x] Badge ## Showcase diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index 6f180691..f964086e 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -41,8 +41,8 @@ pub use webview_story::WebViewStory; use gpui::{ actions, div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, - EventEmitter, FocusableView, Hsla, InteractiveElement, IntoElement, ParentElement, Render, - SharedString, Styled as _, View, ViewContext, VisualContext, WindowContext, + Entity as _, EventEmitter, FocusableView, Hsla, InteractiveElement, IntoElement, ParentElement, + Render, SharedString, Styled as _, View, ViewContext, VisualContext, WindowContext, }; use ui::{ @@ -53,8 +53,9 @@ use ui::{ label::Label, notification::Notification, popup_menu::PopupMenu, + scroll::ScrollbarAxis, theme::ActiveTheme, - v_flex, ContextModal, IconName, + v_flex, ContextModal, IconName, StyledExt as _, }; const PANEL_NAME: &str = "StoryContainer"; @@ -334,6 +335,7 @@ impl Render for StoryContainer { v_flex() .id("story-container") .size_full() + .scrollable(cx.view().entity_id(), ScrollbarAxis::Both) .track_focus(&self.focus_handle) .on_action(cx.listener(Self::on_action_panel_info)) .when(self.description.len() > 0, |this| { diff --git a/crates/story/src/text_story.rs b/crates/story/src/text_story.rs index 0fc80c05..d36eab9c 100644 --- a/crates/story/src/text_story.rs +++ b/crates/story/src/text_story.rs @@ -4,6 +4,7 @@ use gpui::{ }; use ui::{ + badge::Badge, button::{Button, ButtonVariant, ButtonVariants as _}, checkbox::Checkbox, clipboard::Clipboard, @@ -11,7 +12,7 @@ use ui::{ label::Label, link::Link, radio::Radio, - v_flex, Disableable as _, IconName, StyledExt, + v_flex, Disableable as _, IconName, Sizable, StyledExt, }; use crate::section; @@ -245,5 +246,24 @@ impl Render for TextStory { ) ), ) + .child( + section("Badge", cx) + .child( + h_flex().gap_2() + .child(Badge::primary().small().child("Badge")) + .child(Badge::secondary().small().child("Secondary")) + .child(Badge::outline().small().child("Outline")) + .child(Badge::destructive().small().child("Destructive")) + .child(Badge::custom(ui::yellow_500(), ui::yellow_800(), ui::yellow_500()).small().child("Custom")) + ) + .child( + h_flex().gap_2() + .child(Badge::primary().child("Badge")) + .child(Badge::secondary().child("Secondary")) + .child(Badge::outline().child("Outline")) + .child(Badge::destructive().child("Destructive")) + .child(Badge::custom(ui::yellow_500(), ui::yellow_800(), ui::yellow_500()).child("Custom")) + ) + ) } } diff --git a/crates/ui/src/badge.rs b/crates/ui/src/badge.rs new file mode 100644 index 00000000..0e7e0f1e --- /dev/null +++ b/crates/ui/src/badge.rs @@ -0,0 +1,123 @@ +use crate::{theme::ActiveTheme as _, Sizable, Size}; +use gpui::{ + div, prelude::FluentBuilder as _, relative, Div, Hsla, InteractiveElement as _, IntoElement, + ParentElement, RenderOnce, Styled, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum BadgeVariant { + #[default] + Primary, + Secondary, + Outline, + Destructive, + Custom { + color: Hsla, + foreground: Hsla, + border: Hsla, + }, +} +impl BadgeVariant { + fn bg(&self, cx: &gpui::WindowContext) -> Hsla { + match self { + Self::Primary => cx.theme().primary, + Self::Secondary => cx.theme().secondary, + Self::Outline => gpui::transparent_black(), + Self::Destructive => cx.theme().destructive, + Self::Custom { color, .. } => *color, + } + } + + fn border(&self, cx: &gpui::WindowContext) -> Hsla { + match self { + Self::Primary => cx.theme().primary, + Self::Secondary => cx.theme().secondary, + Self::Outline => cx.theme().border, + Self::Destructive => cx.theme().destructive, + Self::Custom { border, .. } => *border, + } + } + + fn fg(&self, cx: &gpui::WindowContext) -> Hsla { + match self { + Self::Primary => cx.theme().primary_foreground, + Self::Secondary => cx.theme().secondary_foreground, + Self::Outline => cx.theme().foreground, + Self::Destructive => cx.theme().destructive_foreground, + Self::Custom { foreground, .. } => *foreground, + } + } +} + +/// Badge is a small status indicator for UI elements. +/// +/// Only support: Medium, Small +#[derive(IntoElement)] +pub struct Badge { + base: Div, + veriant: BadgeVariant, + size: Size, +} +impl Badge { + fn new() -> Self { + Self { + base: div().flex().items_center().rounded_md().border_1(), + veriant: BadgeVariant::default(), + size: Size::Medium, + } + } + + pub fn with_variant(mut self, variant: BadgeVariant) -> Self { + self.veriant = variant; + self + } + + pub fn primary() -> Self { + Self::new().with_variant(BadgeVariant::Primary) + } + + pub fn secondary() -> Self { + Self::new().with_variant(BadgeVariant::Secondary) + } + + pub fn outline() -> Self { + Self::new().with_variant(BadgeVariant::Outline) + } + + pub fn destructive() -> Self { + Self::new().with_variant(BadgeVariant::Destructive) + } + + pub fn custom(color: Hsla, foreground: Hsla, border: Hsla) -> Self { + Self::new().with_variant(BadgeVariant::Custom { + color, + foreground, + border, + }) + } +} +impl Sizable for Badge { + fn with_size(mut self, size: impl Into) -> Self { + self.size = size.into(); + self + } +} +impl ParentElement for Badge { + fn extend(&mut self, elements: impl IntoIterator) { + self.base.extend(elements); + } +} +impl RenderOnce for Badge { + fn render(self, cx: &mut gpui::WindowContext) -> impl IntoElement { + self.base + .line_height(relative(1.3)) + .map(|this| match self.size { + Size::XSmall | Size::Small => this.text_xs().px_1p5().py_0(), + _ => this.text_xs().px_2p5().py_0p5(), + }) + .bg(self.veriant.bg(cx)) + .text_color(self.veriant.fg(cx)) + .border_color(self.veriant.border(cx)) + .hover(|this| this.opacity(0.9)) + } +} diff --git a/crates/ui/src/lib.rs b/crates/ui/src/lib.rs index debb92f8..668f2cfa 100644 --- a/crates/ui/src/lib.rs +++ b/crates/ui/src/lib.rs @@ -10,6 +10,7 @@ mod title_bar; pub mod accordion; pub mod animation; +pub mod badge; pub mod breadcrumb; pub mod button; pub mod button_group;