diff --git a/README.md b/README.md index aaa67e6f..82164e18 100644 --- a/README.md +++ b/README.md @@ -33,13 +33,14 @@ Building styled and more featured component libraries on top of Dioxus Primitive We're still in the early days - Many components are still being created and stabilized. -28/29 +30/30 - [x] Accordion - [x] Alert Dialog - [x] Aspect Ratio - [x] Avatar - [x] Calendar +- [x] Card - [x] Checkbox - [x] Collapsible - [x] Context Menu diff --git a/component.json b/component.json index 628343eb..07e2e65a 100644 --- a/component.json +++ b/component.json @@ -33,6 +33,7 @@ "preview/src/components/context_menu", "preview/src/components/aspect_ratio", "preview/src/components/scroll_area", - "preview/src/components/date_picker" + "preview/src/components/date_picker", + "preview/src/components/card" ] } diff --git a/preview/src/components/card/component.json b/preview/src/components/card/component.json new file mode 100644 index 00000000..4aeb91d0 --- /dev/null +++ b/preview/src/components/card/component.json @@ -0,0 +1,21 @@ +{ + "name": "card", + "description": "A simple card component", + "authors": [ + "zhiyanzhaijie" + ], + "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/card/component.rs b/preview/src/components/card/component.rs new file mode 100644 index 00000000..036749a7 --- /dev/null +++ b/preview/src/components/card/component.rs @@ -0,0 +1,107 @@ +use dioxus::prelude::*; + +#[component] +pub fn Card( + #[props(extends=GlobalAttributes)] attributes: Vec, + children: Element, +) -> Element { + rsx! { + document::Link { rel: "stylesheet", href: asset!("./style.css") } + div { + class: "card", + "data-slot": "card", + ..attributes, + {children} + } + } +} + +#[component] +pub fn CardHeader( + #[props(extends=GlobalAttributes)] attributes: Vec, + children: Element, +) -> Element { + rsx! { + div { + class: "card-header", + "data-slot": "card-header", + ..attributes, + {children} + } + } +} + +#[component] +pub fn CardTitle( + #[props(extends=GlobalAttributes)] attributes: Vec, + children: Element, +) -> Element { + rsx! { + div { + class: "card-title", + "data-slot": "card-title", + ..attributes, + {children} + } + } +} + +#[component] +pub fn CardDescription( + #[props(extends=GlobalAttributes)] attributes: Vec, + children: Element, +) -> Element { + rsx! { + div { + class: "card-description", + "data-slot": "card-description", + ..attributes, + {children} + } + } +} + +#[component] +pub fn CardAction( + #[props(extends=GlobalAttributes)] attributes: Vec, + children: Element, +) -> Element { + rsx! { + div { + class: "card-action", + "data-slot": "card-action", + ..attributes, + {children} + } + } +} + +#[component] +pub fn CardContent( + #[props(extends=GlobalAttributes)] attributes: Vec, + children: Element, +) -> Element { + rsx! { + div { + class: "card-content", + "data-slot": "card-content", + ..attributes, + {children} + } + } +} + +#[component] +pub fn CardFooter( + #[props(extends=GlobalAttributes)] attributes: Vec, + children: Element, +) -> Element { + rsx! { + div { + class: "card-footer", + "data-slot": "card-footer", + ..attributes, + {children} + } + } +} diff --git a/preview/src/components/card/docs.md b/preview/src/components/card/docs.md new file mode 100644 index 00000000..da98a235 --- /dev/null +++ b/preview/src/components/card/docs.md @@ -0,0 +1,32 @@ +The card component is a flexible container for grouping related content and actions. It provides a structured layout with optional header, content, and footer sections. + +## Component Structure + +```rust +// The Card component must wrap all card elements. +Card { + // CardHeader contains the title, description, and optional action. + CardHeader { + // CardTitle displays the main heading. + CardTitle { "Card Title" } + // CardDescription provides supporting text. + CardDescription { "Card description goes here." } + // CardAction positions action elements (e.g., buttons) in the header. + CardAction { + Button { "Action" } + } + } + // CardContent holds the main body content. + CardContent { + p { "Main content of the card." } + } + // CardFooter contains footer actions or information. + CardFooter { + Button { "Submit" } + } +} +``` + +## Layout Notes + +- When `CardAction` is present inside `CardHeader`, the header automatically switches to a two-column grid layout. diff --git a/preview/src/components/card/mod.rs b/preview/src/components/card/mod.rs new file mode 100644 index 00000000..a3527a11 --- /dev/null +++ b/preview/src/components/card/mod.rs @@ -0,0 +1,3 @@ +mod component; +pub use component::*; + diff --git a/preview/src/components/card/style.css b/preview/src/components/card/style.css new file mode 100644 index 00000000..8c1ca459 --- /dev/null +++ b/preview/src/components/card/style.css @@ -0,0 +1,54 @@ +.card { + display: flex; + flex-direction: column; + + gap: 1.5rem; + padding: 1.5rem 0; + color: var(--secondary-color-4); + border: 1px solid var(--light, var(--primary-color-6)) var(--dark, var(--primary-color-5)); + border-radius: 1rem; + background-color: var(--light, var(--secondary-color-2)) var(--dark, var(--primary-color-3)); + box-shadow: 0 2px 10px rgb(0 0 0 / 10%); +} + +.card-header { + display: grid; + grid-template-rows: auto auto; + grid-auto-rows: min-content; + align-items: start; + gap: 0.5rem; + padding: 0 1.5rem; +} + +.card-header:has([data-slot="card-action"]) { + grid-template-columns: 1fr auto; +} + +.card-title { + line-height: 1; + font-weight: 600; + font-size: 1rem; +} + +.card-description { + color: var(--secondary-color-5); + font-size: 0.875rem; + line-height: 1.25rem; +} + +.card-action { + grid-column-start: 2; + grid-row: 1 / span 2; + align-self: start; + justify-self: end; +} + +.card-content { + padding: 0 1.5rem; +} + +.card-footer { + display: flex; + align-items: center; + padding: 0 1.5rem; +} diff --git a/preview/src/components/card/variants/main/mod.rs b/preview/src/components/card/variants/main/mod.rs new file mode 100644 index 00000000..466971de --- /dev/null +++ b/preview/src/components/card/variants/main/mod.rs @@ -0,0 +1,49 @@ +use super::super::component::*; +use crate::components::button::{Button, ButtonVariant}; +use crate::components::input::Input; +use crate::components::label::Label; +use dioxus::prelude::*; + +#[component] +pub fn Demo() -> Element { + rsx! { + Card { style: "width: 100%; max-width: 24rem;", + CardHeader { + CardTitle { "Login to your account" } + CardDescription { "Enter your email below to login to your account" } + CardAction { + Button { variant: ButtonVariant::Ghost, "Sign Up" } + } + } + CardContent { + form { + div { style: "display: flex; flex-direction: column; gap: 1.5rem;", + div { style: "display: grid; gap: 0.5rem;", + Label { html_for: "email", "Email" } + Input { + id: "email", + r#type: "email", + placeholder: "m@example.com", + } + } + div { style: "display: grid; gap: 0.5rem;", + div { style: "display: flex; align-items: center;", + Label { html_for: "password", "Password" } + a { + href: "#", + style: "margin-left: auto; font-size: 0.875rem; color: var(--secondary-color-5); text-decoration: underline; text-underline-offset: 4px;", + "Forgot your password?" + } + } + Input { id: "password", r#type: "password" } + } + } + } + } + CardFooter { style: "flex-direction: column; gap: 0.5rem;", + Button { r#type: "submit", style: "width: 100%;", "Login" } + Button { variant: ButtonVariant::Outline, style: "width: 100%;", "Login with Google" } + } + } + } +} diff --git a/preview/src/components/input/style.css b/preview/src/components/input/style.css index 0725871b..9faf0c95 100644 --- a/preview/src/components/input/style.css +++ b/preview/src/components/input/style.css @@ -12,8 +12,7 @@ border-radius: 0.5rem; border-radius: calc(0.5rem); background: none; - background: var(--light, var(--primary-color)) - var(--dark, var(--primary-color-3)); + background-color: var(--light, var(--primary-color)) var(--dark, color-mix(in oklab, #FFFFFF26 30%, transparent)); box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) var(--dark, var(--primary-color-7)); color: var(--secondary-color-4); @@ -34,7 +33,7 @@ .input:hover:not(:disabled), .input:focus-visible { background: var(--light, var(--primary-color-4)) - var(--dark, var(--primary-color-5)); + var(--dark, color-mix(in oklab, #FFFFFF26 50%, transparent)); color: var(--secondary-color-1); outline: none; } diff --git a/preview/src/components/mod.rs b/preview/src/components/mod.rs index 887b2bbe..65f9ed37 100644 --- a/preview/src/components/mod.rs +++ b/preview/src/components/mod.rs @@ -63,6 +63,7 @@ examples!( avatar, button, calendar[simple, internationalized, range, unavailable_dates], + card, checkbox, collapsible, context_menu,