Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion component.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
8 changes: 4 additions & 4 deletions playwright/avatar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ test("test", async ({ page }) => {
let image = avatar.locator("img");
await expect(image).toHaveAttribute("src", "https://avatars.githubusercontent.com/u/66571940?s=96&v=4");

// Get the second avatar element
const secondAvatar = page.locator(".avatar-item").nth(1);
// Verify the second avatar has fallback text
await expect(secondAvatar).toContainText("JK");
// Get the third avatar element (Error State - has invalid image URL, shows fallback)
const errorAvatar = page.locator(".avatar-item").nth(2);
// Verify the error state avatar has fallback text
await expect(errorAvatar).toContainText("JK");
});
21 changes: 20 additions & 1 deletion preview/src/components/avatar/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<Attribute>,
Expand All @@ -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,
Expand Down
12 changes: 10 additions & 2 deletions preview/src/components/avatar/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down
19 changes: 18 additions & 1 deletion preview/src/components/avatar/variants/main/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,29 @@ pub fn Demo() -> Element {
AvatarFallback { class: "avatar-fallback", "EA" }
}
}
div { class: "avatar-item",
p { class: "avatar-label", "Rounded" }
Avatar {
size: AvatarImageSize::Small,
shape: AvatarShape::Rounded,
on_state_change: move |state| {
avatar_state.set(format!("Avatar 2: {state:?}"));
},
aria_label: "Basic avatar",
AvatarImage {
class: "avatar-image",
src: "https://avatars.githubusercontent.com/u/66571940?s=96&v=4",
alt: "User avatar",
}
AvatarFallback { class: "avatar-fallback", "EA" }
}
}
div { class: "avatar-item",
p { class: "avatar-label", "Error State" }
Avatar {
size: AvatarImageSize::Medium,
on_state_change: move |state| {
avatar_state.set(format!("Avatar 2: {state:?}"));
avatar_state.set(format!("Avatar 3: {state:?}"));
},
aria_label: "Error avatar",
AvatarImage {
Expand Down
13 changes: 13 additions & 0 deletions preview/src/components/badge/component.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "badge",
"description": "A small label to display status or categorization",
"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"]
}
81 changes: 81 additions & 0 deletions preview/src/components/badge/component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use dioxus::prelude::*;

#[derive(Copy, Clone, PartialEq, Default)]
#[non_exhaustive]
pub enum BadgeVariant {
#[default]
Primary,
Secondary,
Destructive,
Outline,
}

impl BadgeVariant {
pub fn class(&self) -> &'static str {
match self {
BadgeVariant::Primary => "primary",
BadgeVariant::Secondary => "secondary",
BadgeVariant::Destructive => "destructive",
BadgeVariant::Outline => "outline",
}
}
}

/// The props for the [`Badge`] component.
#[derive(Props, Clone, PartialEq)]
pub struct BadgeProps {
#[props(default)]
pub variant: BadgeVariant,

/// Additional attributes to extend the badge element
#[props(extends = GlobalAttributes)]
pub attributes: Vec<Attribute>,

/// The children of the badge element
pub children: Element,
}

#[component]
pub fn Badge(props: BadgeProps) -> Element {
rsx! {
document::Link { rel: "stylesheet", href: asset!("./style.css") }

BadgeElement {
"padding": true,
variant: props.variant,
attributes: props.attributes,
{props.children}
}
}
}

#[component]
fn BadgeElement(props: BadgeProps) -> Element {
rsx! {
span {
class: "badge",
"data-style": props.variant.class(),
..props.attributes,
{props.children}
}
}
}

#[component]
pub fn VerifiedIcon() -> Element {
rsx! {
svg {
view_box: "0 0 24 24",
xmlns: "http://www.w3.org/2000/svg",
width: "12",
height: "12",
fill: "none",
stroke: "var(--secondary-color-4)",
stroke_linecap: "round",
stroke_linejoin: "round",
stroke_width: 2,
path { d: "M3.85 8.62a4 4 0 0 1 4.78-4.77 4 4 0 0 1 6.74 0 4 4 0 0 1 4.78 4.78 4 4 0 0 1 0 6.74 4 4 0 0 1-4.77 4.78 4 4 0 0 1-6.75 0 4 4 0 0 1-4.78-4.77 4 4 0 0 1 0-6.76Z" }
path { d: "m9 12 2 2 4-4" }
}
}
}
9 changes: 9 additions & 0 deletions preview/src/components/badge/docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The Badge is a component designed to display small, distinct labels that help highlight important content or status indicators. Perfect for use cases like notifications, status labels, or categorization.

## Component Structure

```rust
Badge {
{children}
}
```
2 changes: 2 additions & 0 deletions preview/src/components/badge/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
mod component;
pub use component::*;
42 changes: 42 additions & 0 deletions preview/src/components/badge/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.badge-example {
display: flex;
align-items: center;
gap: 1rem;
}

.badge {
display: inline-flex;
min-width: 20px;
height: 20px;
align-items: center;
justify-content: center;
border-radius: 10px;
box-shadow: 0 0 0 1px var(--primary-color-2);
font-size: 12px;
gap: 4px
}

.badge[padding="true"] {
padding: 0 8px;
}

.badge[data-style="primary"] {
background-color: var(--secondary-color-2);
color: var(--primary-color);
}

.badge[data-style="secondary"] {
background-color: var(--primary-color-5);
color: var(--secondary-color-1);
}

.badge[data-style="outline"] {
border: 1px solid var(--primary-color-6);
background-color: var(--light, var(--primary-color)) var(--dark, var(--primary-color-3));
color: var(--secondary-color-4);
}

.badge[data-style="destructive"] {
background-color: var(--primary-error-color);
color: var(--contrast-error-color);
}
22 changes: 22 additions & 0 deletions preview/src/components/badge/variants/main/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use dioxus::prelude::*;

use super::super::component::*;

#[component]
pub fn Demo() -> Element {
rsx! {
div { class: "badge-example",

Badge { "Primary" }
Badge { variant: BadgeVariant::Secondary, "Secondary" }
Badge { variant: BadgeVariant::Destructive, "Destructive" }
Badge { variant: BadgeVariant::Outline, "Outline" }
Badge {
variant: BadgeVariant::Secondary,
style: "background-color: var(--focused-border-color)",
VerifiedIcon {}
"Verified"
}
}
}
}
1 change: 1 addition & 0 deletions preview/src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ examples!(
alert_dialog,
aspect_ratio,
avatar,
badge,
button,
calendar[simple, internationalized, range, multi_month, unavailable_dates],
checkbox,
Expand Down
14 changes: 7 additions & 7 deletions primitives/src/date_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Date>);
/// rsx! {
/// div {
Expand Down Expand Up @@ -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::<DateRange>);
/// rsx! {
/// div {
Expand Down Expand Up @@ -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::<Date>);
/// rsx! {
/// div {
Expand Down Expand Up @@ -473,7 +473,7 @@ pub struct DatePickerCalendarProps<T: DefaultCalendarProps + Properties + Partia
/// 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::<Date>);
/// rsx! {
/// div {
Expand Down Expand Up @@ -550,7 +550,7 @@ pub fn DatePickerCalendar(props: DatePickerCalendarProps<CalendarProps>) -> 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::<DateRange>);
/// rsx! {
/// div {
Expand Down Expand Up @@ -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::<Date>);
/// rsx! {
/// div {
Expand Down Expand Up @@ -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::<DateRange>);
/// rsx! {
/// div {
Expand Down
Loading