diff --git a/component.json b/component.json index c84d50e3..4e8cbadc 100644 --- a/component.json +++ b/component.json @@ -37,6 +37,7 @@ "preview/src/components/textarea", "preview/src/components/skeleton", "preview/src/components/card", - "preview/src/components/sheet" + "preview/src/components/sheet", + "preview/src/components/badge" ] } diff --git a/preview/src/components/avatar/component.rs b/preview/src/components/avatar/component.rs index ddb43d77..8cf019ba 100644 --- a/preview/src/components/avatar/component.rs +++ b/preview/src/components/avatar/component.rs @@ -19,6 +19,22 @@ impl AvatarImageSize { } } +#[derive(Clone, Copy, PartialEq, Default)] +pub enum AvatarShape { + #[default] + Circle, + Rounded, +} + +impl AvatarShape { + fn to_class(self) -> &'static str { + match self { + AvatarShape::Circle => "avatar-circle", + AvatarShape::Rounded => "avatar-rounded", + } + } +} + /// The props for the [`Avatar`] component. #[derive(Props, Clone, PartialEq)] pub struct AvatarProps { @@ -37,6 +53,9 @@ pub struct AvatarProps { #[props(default)] pub size: AvatarImageSize, + #[props(default)] + pub shape: AvatarShape, + /// Additional attributes for the avatar element #[props(extends = GlobalAttributes)] pub attributes: Vec, @@ -51,7 +70,7 @@ pub fn Avatar(props: AvatarProps) -> Element { document::Link { rel: "stylesheet", href: asset!("./style.css") } avatar::Avatar { - class: "avatar {props.size.to_class()}", + class: "avatar {props.size.to_class()} {props.shape.to_class()}", on_load: props.on_load, on_error: props.on_error, on_state_change: props.on_state_change, diff --git a/preview/src/components/avatar/style.css b/preview/src/components/avatar/style.css index 2d487dc1..d239e02e 100644 --- a/preview/src/components/avatar/style.css +++ b/preview/src/components/avatar/style.css @@ -21,7 +21,6 @@ flex-shrink: 0; align-items: center; justify-content: center; - border-radius: 3.40282e+38px; color: var(--secondary-color-4); cursor: pointer; font-weight: 500; @@ -52,13 +51,22 @@ font-size: 1.75rem; } +/* Avatar shape */ +.avatar-circle { + border-radius: 50%; +} + +.avatar-rounded { + border-radius: 8px; +} + /* State-specific styles */ .avatar[data-state="loading"] { animation: pulse 1.5s infinite ease-in-out; } .avatar[data-state="empty"] { - background: var(--primary-color-2); + background: var(--primary-color-7); } @keyframes pulse { diff --git a/preview/src/components/badge/component.json b/preview/src/components/badge/component.json new file mode 100644 index 00000000..e061a270 --- /dev/null +++ b/preview/src/components/badge/component.json @@ -0,0 +1,13 @@ +{ + "name": "Badge", + "description": "Show notifications, counts or status information on its children", + "authors": ["Evan Almloff"], + "exclude": ["variants", "docs.md", "component.json"], + "cargoDependencies": [ + { + "name": "dioxus-primitives", + "git": "https://github.com/DioxusLabs/components" + } + ], + "globalAssets": ["../../../assets/dx-components-theme.css"] +} diff --git a/preview/src/components/badge/component.rs b/preview/src/components/badge/component.rs new file mode 100644 index 00000000..1d19e3ad --- /dev/null +++ b/preview/src/components/badge/component.rs @@ -0,0 +1,19 @@ +use dioxus::prelude::*; +use dioxus_primitives::badge::{self, BadgeProps}; + +#[component] +pub fn Badge(props: BadgeProps) -> Element { + rsx! { + document::Link { rel: "stylesheet", href: asset!("./style.css") } + + badge::Badge { + count: props.count, + overflow_count: props.overflow_count, + dot: props.dot, + show_zero: props.show_zero, + color: props.color, + attributes: props.attributes, + {props.children} + } + } +} diff --git a/preview/src/components/badge/docs.md b/preview/src/components/badge/docs.md new file mode 100644 index 00000000..4c96fef8 --- /dev/null +++ b/preview/src/components/badge/docs.md @@ -0,0 +1,10 @@ +Badges are used as a small numerical value or status descriptor for its children elements. +Badge will be hidden when count is 0, but we can use show_zero to show it. + +## Component Structure + +```rust +Badge { + {children} +} +``` \ No newline at end of file diff --git a/preview/src/components/badge/mod.rs b/preview/src/components/badge/mod.rs new file mode 100644 index 00000000..9a8ae556 --- /dev/null +++ b/preview/src/components/badge/mod.rs @@ -0,0 +1,2 @@ +mod component; +pub use component::*; \ No newline at end of file diff --git a/preview/src/components/badge/style.css b/preview/src/components/badge/style.css new file mode 100644 index 00000000..c49b72a4 --- /dev/null +++ b/preview/src/components/badge/style.css @@ -0,0 +1,41 @@ +.badge-example { + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; +} + +.badge-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.5rem; +} + +.badge-label { + color: var(--secondary-color-4); + font-size: 0.875rem; +} + +.badge { + position: absolute; + display: inline-flex; + min-width: 20px; + height: 20px; + align-items: center; + justify-content: center; + border-radius: 10px; + background-color: var(--badge-color); + box-shadow: 0 0 0 1px var(--primary-color-2); + font-size: 12px; + transform: translate(-50%, -50%); +} + +.badge[padding="true"] { + padding: 0 8px; +} + +.badge[dot="true"] { + min-width: 8px; + height: 8px; +} \ No newline at end of file diff --git a/preview/src/components/badge/variants/main/mod.rs b/preview/src/components/badge/variants/main/mod.rs new file mode 100644 index 00000000..6bfd5a85 --- /dev/null +++ b/preview/src/components/badge/variants/main/mod.rs @@ -0,0 +1,86 @@ +use dioxus::prelude::*; + +use super::super::component::*; +use crate::components::avatar::*; + +#[component] +pub fn Demo() -> Element { + rsx! { + div { + class: "badge-example", + + div { + class: "badge-item", + p { class: "badge-label", "Basic" } + Badge { + count: 5, + Avatar { + size: AvatarImageSize::Medium, + shape: AvatarShape::Rounded, + aria_label: "Space item", + } + } + } + + div { + class: "badge-item", + p { class: "badge-label", "Show Zero" } + + Badge { + count: 0, + show_zero: true, + Avatar { + size: AvatarImageSize::Medium, + shape: AvatarShape::Rounded, + aria_label: "Space item", + } + } + } + + div { + class: "badge-item", + p { class: "badge-label", "Overflow" } + + Badge { + count: 100, + overflow_count: 99, + Avatar { + size: AvatarImageSize::Medium, + shape: AvatarShape::Rounded, + aria_label: "Space item", + } + } + } + + div { + class: "badge-item", + p { class: "badge-label", "Colorful" } + + Badge { + count: 7, + color: String::from("52c41a"), + Avatar { + size: AvatarImageSize::Medium, + shape: AvatarShape::Rounded, + aria_label: "Space item", + } + } + } + + div { + class: "badge-item", + p { class: "badge-label", "As Dot" } + + Badge { + count: 5, + dot: true, + Avatar { + size: AvatarImageSize::Medium, + shape: AvatarShape::Rounded, + aria_label: "Space item", + } + } + } + } + } +} diff --git a/preview/src/components/mod.rs b/preview/src/components/mod.rs index a2195d26..c19f0f54 100644 --- a/preview/src/components/mod.rs +++ b/preview/src/components/mod.rs @@ -61,6 +61,7 @@ examples!( alert_dialog, aspect_ratio, avatar, + badge, button, calendar[simple, internationalized, range, multi_month, unavailable_dates], checkbox, diff --git a/primitives/src/badge.rs b/primitives/src/badge.rs new file mode 100644 index 00000000..f96501f8 --- /dev/null +++ b/primitives/src/badge.rs @@ -0,0 +1,92 @@ +//! Defines the [`Badge`] component + +use dioxus::prelude::*; + +const DEF_COLOR: &str = "EB5160"; + +/// The props for the [`Badge`] component. +#[derive(Props, Clone, PartialEq)] +pub struct BadgeProps { + /// Number to show in badge + pub count: u32, + + /// Max count to show + #[props(default = u32::MAX)] + pub overflow_count: u32, + + /// Whether to display a dot instead of count + #[props(default = false)] + pub dot: bool, + + /// Whether to show badge when count is zero + #[props(default = false)] + pub show_zero: bool, + + /// Customize Badge color (as HEX) + #[props(default = String::from(DEF_COLOR))] + pub color: String, + + /// Additional attributes to extend the badge element + #[props(extends = GlobalAttributes)] + pub attributes: Vec, + + /// The children of the badge element + pub children: Element, +} + +/// # Badge +/// +/// The [`Badge`] component displays a small badge to the top-right of its child(ren). +/// +/// ## Example +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_primitives::badge::Badge; +/// use dioxus_primitives::avatar::*; +/// #[component] +/// fn Demo() -> Element { +/// rsx! { +/// Badge { +/// count: 100, +/// overflow_count: 99, +/// Avatar { +/// aria_label: "Space item", +/// } +/// } +/// } +/// } +/// ``` +#[component] +pub fn Badge(props: BadgeProps) -> Element { + let text = if props.dot { + String::default() + } else if props.overflow_count < props.count { + format!("{}+", props.overflow_count) + } else { + format!("{}", props.count) + }; + + let add_padding = text.chars().count() > 1; + let color = if u32::from_str_radix(&props.color, 16).is_ok() { + props.color + } else { + DEF_COLOR.to_string() + }; + + rsx! { + span { + {props.children} + + if props.count > 0 || props.show_zero { + span { + class: "badge", + style: "--badge-color: #{color}", + "padding": if add_padding { true }, + "dot": if props.dot { true }, + ..props.attributes, + {text} + } + } + } + } +} diff --git a/primitives/src/date_picker.rs b/primitives/src/date_picker.rs index f9355c82..b1b0ec78 100644 --- a/primitives/src/date_picker.rs +++ b/primitives/src/date_picker.rs @@ -103,7 +103,7 @@ pub struct DatePickerProps { /// use dioxus_primitives::{calendar::Calendar, date_picker::*, popover::*, ContentAlign}; /// use time::Date; /// #[component] -/// pub fn Demo() -> Element { +/// fn Demo() -> Element { /// let mut selected_date = use_signal(|| None::); /// rsx! { /// div { @@ -242,7 +242,7 @@ pub struct DateRangePickerProps { /// use dioxus::prelude::*; /// use dioxus_primitives::{calendar::{DateRange, RangeCalendar}, date_picker::*, popover::*, ContentAlign}; /// #[component] -/// pub fn Demo() -> Element { +/// fn Demo() -> Element { /// let mut selected_range = use_signal(|| None::); /// rsx! { /// div { @@ -350,7 +350,7 @@ pub struct DatePickerPopoverProps { /// use dioxus_primitives::{calendar::Calendar, date_picker::*, popover::*, ContentAlign}; /// use time::Date; /// #[component] -/// pub fn Demo() -> Element { +/// fn Demo() -> Element { /// let mut selected_date = use_signal(|| None::); /// rsx! { /// div { @@ -473,7 +473,7 @@ pub struct DatePickerCalendarProps Element { +/// fn Demo() -> Element { /// let mut selected_date = use_signal(|| None::); /// rsx! { /// div { @@ -550,7 +550,7 @@ pub fn DatePickerCalendar(props: DatePickerCalendarProps) -> Elem /// use dioxus::prelude::*; /// use dioxus_primitives::{calendar::{DateRange, RangeCalendar}, date_picker::*, popover::*, ContentAlign}; /// #[component] -/// pub fn Demo() -> Element { +/// fn Demo() -> Element { /// let mut selected_range = use_signal(|| None::); /// rsx! { /// div { @@ -1010,7 +1010,7 @@ pub struct DatePickerInputProps { /// use dioxus_primitives::{calendar::Calendar, date_picker::*, popover::*, ContentAlign}; /// use time::Date; /// #[component] -/// pub fn Demo() -> Element { +/// fn Demo() -> Element { /// let mut selected_date = use_signal(|| None::); /// rsx! { /// div { @@ -1069,7 +1069,7 @@ pub fn DatePickerInput(props: DatePickerInputProps) -> Element { /// use dioxus::prelude::*; /// use dioxus_primitives::{calendar::{DateRange, RangeCalendar}, date_picker::*, popover::*, ContentAlign}; /// #[component] -/// pub fn Demo() -> Element { +/// fn Demo() -> Element { /// let mut selected_range = use_signal(|| None::); /// rsx! { /// div { diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index b58fa05f..c0c50bdf 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -13,6 +13,7 @@ pub mod accordion; pub mod alert_dialog; pub mod aspect_ratio; pub mod avatar; +pub mod badge; pub mod calendar; pub mod checkbox; pub mod collapsible;