diff --git a/README.md b/README.md index 82164e18..672d0203 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ 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. -30/30 +31/31 - [x] Accordion - [x] Alert Dialog @@ -60,6 +60,7 @@ We're still in the early days - Many components are still being created and stab - [x] Slider - [x] Switch - [x] Tabs +- [x] Textarea - [x] Toast - [x] Toggle - [x] Toggle Group diff --git a/component.json b/component.json index 26a95e5d..4e8efef2 100644 --- a/component.json +++ b/component.json @@ -34,6 +34,7 @@ "preview/src/components/aspect_ratio", "preview/src/components/scroll_area", "preview/src/components/date_picker", + "preview/src/components/textarea", "preview/src/components/skeleton", "preview/src/components/card" ] diff --git a/preview/src/components/mod.rs b/preview/src/components/mod.rs index baf68358..75791d5c 100644 --- a/preview/src/components/mod.rs +++ b/preview/src/components/mod.rs @@ -85,6 +85,7 @@ examples!( slider, switch, tabs, + textarea[outline, fade, ghost], toast, toggle_group, toggle, diff --git a/preview/src/components/textarea/component.json b/preview/src/components/textarea/component.json new file mode 100644 index 00000000..28910c4b --- /dev/null +++ b/preview/src/components/textarea/component.json @@ -0,0 +1,21 @@ +{ + "name": "textarea", + "description": "a textarea component used to allow users to enter multi-line text input", + "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/textarea/component.rs b/preview/src/components/textarea/component.rs new file mode 100644 index 00000000..7f4db3cc --- /dev/null +++ b/preview/src/components/textarea/component.rs @@ -0,0 +1,78 @@ +use dioxus::prelude::*; + +#[derive(Copy, Clone, PartialEq, Default)] +#[non_exhaustive] +pub enum TextareaVariant { + #[default] + Default, + Fade, + Outline, + Ghost, +} + +impl TextareaVariant { + pub fn class(&self) -> &'static str { + match self { + TextareaVariant::Default => "default", + TextareaVariant::Fade => "fade", + TextareaVariant::Outline => "outline", + TextareaVariant::Ghost => "ghost", + } + } +} + +#[component] +pub fn Textarea( + oninput: Option>, + onchange: Option>, + oninvalid: Option>, + onselect: Option>, + onselectionchange: Option>, + onfocus: Option>, + onblur: Option>, + onfocusin: Option>, + onfocusout: Option>, + onkeydown: Option>, + onkeypress: Option>, + onkeyup: Option>, + oncompositionstart: Option>, + oncompositionupdate: Option>, + oncompositionend: Option>, + oncopy: Option>, + oncut: Option>, + onpaste: Option>, + #[props(default)] variant: TextareaVariant, + #[props(extends=GlobalAttributes)] + #[props(extends=textarea)] + attributes: Vec, + children: Element, +) -> Element { + rsx! { + document::Link { rel: "stylesheet", href: asset!("./style.css") } + textarea { + class: "textarea", + "data-slot": "textarea", + "data-style": variant.class(), + oninput: move |e| _ = oninput.map(|callback| callback(e)), + onchange: move |e| _ = onchange.map(|callback| callback(e)), + oninvalid: move |e| _ = oninvalid.map(|callback| callback(e)), + onselect: move |e| _ = onselect.map(|callback| callback(e)), + onselectionchange: move |e| _ = onselectionchange.map(|callback| callback(e)), + onfocus: move |e| _ = onfocus.map(|callback| callback(e)), + onblur: move |e| _ = onblur.map(|callback| callback(e)), + onfocusin: move |e| _ = onfocusin.map(|callback| callback(e)), + onfocusout: move |e| _ = onfocusout.map(|callback| callback(e)), + onkeydown: move |e| _ = onkeydown.map(|callback| callback(e)), + onkeypress: move |e| _ = onkeypress.map(|callback| callback(e)), + onkeyup: move |e| _ = onkeyup.map(|callback| callback(e)), + oncompositionstart: move |e| _ = oncompositionstart.map(|callback| callback(e)), + oncompositionupdate: move |e| _ = oncompositionupdate.map(|callback| callback(e)), + oncompositionend: move |e| _ = oncompositionend.map(|callback| callback(e)), + oncopy: move |e| _ = oncopy.map(|callback| callback(e)), + oncut: move |e| _ = oncut.map(|callback| callback(e)), + onpaste: move |e| _ = onpaste.map(|callback| callback(e)), + ..attributes, + {children} + } + } +} diff --git a/preview/src/components/textarea/docs.md b/preview/src/components/textarea/docs.md new file mode 100644 index 00000000..d2c9e01e --- /dev/null +++ b/preview/src/components/textarea/docs.md @@ -0,0 +1,14 @@ +The textarea element is used to allow users to enter multi-line text input in a user interface. + +## Component Structure + +```rust +textarea { + // Global html attributes + class: "textarea", + "data-style": "default", + // Children + {children} +} +``` + diff --git a/preview/src/components/textarea/mod.rs b/preview/src/components/textarea/mod.rs new file mode 100644 index 00000000..2590c013 --- /dev/null +++ b/preview/src/components/textarea/mod.rs @@ -0,0 +1,2 @@ +mod component; +pub use component::*; diff --git a/preview/src/components/textarea/style.css b/preview/src/components/textarea/style.css new file mode 100644 index 00000000..d61f7d91 --- /dev/null +++ b/preview/src/components/textarea/style.css @@ -0,0 +1,87 @@ +/* Base */ +.textarea { + width: 100%; + min-height: 4rem; + padding: 8px 12px; + line-height: 1.5; + font-family: inherit; + color: var(--secondary-color-4); + outline: none; + resize: vertical; + transition: background-color 100ms ease-out, border-color 100ms ease-out, box-shadow 100ms ease-out; + + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + margin: 0; + border: none; + background: none; + box-sizing: border-box; + border-radius: 0.5rem; +} + +.textarea:disabled { + color: var(--secondary-color-5); + cursor: not-allowed; +} + +.textarea::placeholder { + color: var(--secondary-color-5); +} + +/* Default Variant */ +.textarea[data-style="default"] { + background: var(--light, var(--primary-color)) var(--dark, var(--primary-color-3)); + box-shadow: inset 0 0 0 1px var(--light, var(--primary-color-6)) var(--dark, var(--primary-color-7)); +} + +.textarea[data-style="default"]:hover:not(:disabled), +.textarea[data-style="default"]:focus { + background: var(--light, var(--primary-color-4)) var(--dark, var(--primary-color-5)); + color: var(--secondary-color-1); +} + +/* Fade Variant */ +.textarea[data-style="fade"] { + background: var(--light, var(--primary-color)) var(--dark, var(--primary-color-3)); +} + +.textarea[data-style="fade"]:hover:not(:disabled), +.textarea[data-style="fade"]:focus { + background: var(--light, var(--primary-color-4)) var(--dark, var(--primary-color-5)); + color: var(--secondary-color-1); +} + +/* Outline Variant */ +.textarea[data-style="outline"] { + border: 1px solid var(--primary-color-6); + background-color: var(--light, var(--primary-color)) + var(--dark, var(--primary-color-3)); +} + +.textarea[data-style="outline"]:hover:not(:disabled):not(:focus) { + border-color: var(--primary-color-7); +} + +.textarea[data-style="outline"]:focus { + border-color: var(--focused-border-color); +} + +.textarea[data-style="outline"]:invalid, +.textarea[data-style="outline"][aria-invalid="true"] { + border-color: var(--primary-error-color); +} + +/* Ghost Variant */ +.textarea[data-style="ghost"] { + background-color: transparent; +} + +.textarea[data-style="ghost"]:hover:not(:disabled) { + background-color: var(--primary-color-5); + color: var(--secondary-color-1); +} + +.textarea[data-style="ghost"]:focus { + border-color: var(--focused-border-color); +} diff --git a/preview/src/components/textarea/variants/fade/mod.rs b/preview/src/components/textarea/variants/fade/mod.rs new file mode 100644 index 00000000..c189bec3 --- /dev/null +++ b/preview/src/components/textarea/variants/fade/mod.rs @@ -0,0 +1,33 @@ +use super::super::component::*; +use crate::components::label::Label; +use dioxus::prelude::*; + +#[component] +pub fn Demo() -> Element { + let mut description = use_signal(String::new); + rsx! { + div { + display: "flex", + flex_direction: "column", + gap: "1.5rem", + + p { id: "textarea-message", "Description here: {description}" } + + div { + display: "flex", + flex_direction: "column", + gap: ".5rem", + justify_content: "center", + + Label { html_for: "fade", "Fade" } + Textarea { + id: "fade", + variant: TextareaVariant::Fade, + placeholder: "Enter your description", + value: description, + oninput: move |e: FormEvent| description.set(e.value()), + } + } + } + } +} diff --git a/preview/src/components/textarea/variants/ghost/mod.rs b/preview/src/components/textarea/variants/ghost/mod.rs new file mode 100644 index 00000000..ad151417 --- /dev/null +++ b/preview/src/components/textarea/variants/ghost/mod.rs @@ -0,0 +1,33 @@ +use super::super::component::*; +use crate::components::label::Label; +use dioxus::prelude::*; + +#[component] +pub fn Demo() -> Element { + let mut description = use_signal(String::new); + rsx! { + div { + display: "flex", + flex_direction: "column", + gap: "1.5rem", + + p { id: "textarea-message", "Description here: {description}" } + + div { + display: "flex", + flex_direction: "column", + gap: ".5rem", + justify_content: "center", + + Label { html_for: "ghost", "Ghost" } + Textarea { + id: "ghost", + variant: TextareaVariant::Ghost, + placeholder: "Enter your description", + value: description, + oninput: move |e: FormEvent| description.set(e.value()), + } + } + } + } +} diff --git a/preview/src/components/textarea/variants/main/mod.rs b/preview/src/components/textarea/variants/main/mod.rs new file mode 100644 index 00000000..f9d1d556 --- /dev/null +++ b/preview/src/components/textarea/variants/main/mod.rs @@ -0,0 +1,33 @@ +use super::super::component::*; +use crate::components::label::Label; +use dioxus::prelude::*; + +#[component] +pub fn Demo() -> Element { + let mut description = use_signal(String::new); + rsx! { + div { + display: "flex", + flex_direction: "column", + gap: "1.5rem", + + p { id: "textarea-message", "Description here: {description}" } + + div { + display: "flex", + flex_direction: "column", + gap: ".5rem", + justify_content: "center", + + Label { html_for: "default", "Default" } + Textarea { + id: "default", + variant: TextareaVariant::Default, + placeholder: "Enter your description", + value: description, + oninput: move |e: FormEvent| description.set(e.value()), + } + } + } + } +} diff --git a/preview/src/components/textarea/variants/outline/mod.rs b/preview/src/components/textarea/variants/outline/mod.rs new file mode 100644 index 00000000..047bac42 --- /dev/null +++ b/preview/src/components/textarea/variants/outline/mod.rs @@ -0,0 +1,32 @@ +use super::super::component::*; +use crate::components::label::Label; +use dioxus::prelude::*; + +#[component] +pub fn Demo() -> Element { + let mut description = use_signal(String::new); + rsx! { + div { + display: "flex", + flex_direction: "column", + gap: "1.5rem", + + p { id: "textarea-message", "Description here: {description}" } + + div { + display: "flex", + flex_direction: "column", + gap: ".5rem", + justify_content: "center", + Label { html_for: "outline", "Outline" } + Textarea { + id: "outline", + variant: TextareaVariant::Outline, + placeholder: "Enter your description", + value: description, + oninput: move |e: FormEvent| description.set(e.value()), + } + } + } + } +}