From c7be3914410cd6287547e55bbaebd077e01222e4 Mon Sep 17 00:00:00 2001 From: luoxiao Date: Thu, 27 Jun 2024 14:51:36 +0800 Subject: [PATCH] feat: adds Combobox component --- demo/src/app.rs | 1 + demo/src/pages/components.rs | 4 + demo_markdown/docs/combobox/mod.md | 33 +++++ demo_markdown/src/lib.rs | 1 + thaw/src/combobox/combobox.css | 192 +++++++++++++++++++++++++++ thaw/src/combobox/combobox.rs | 112 ++++++++++++++++ thaw/src/combobox/combobox_option.rs | 49 +++++++ thaw/src/combobox/mod.rs | 5 + thaw/src/lib.rs | 2 + 9 files changed, 399 insertions(+) create mode 100644 demo_markdown/docs/combobox/mod.md create mode 100644 thaw/src/combobox/combobox.css create mode 100644 thaw/src/combobox/combobox.rs create mode 100644 thaw/src/combobox/combobox_option.rs create mode 100644 thaw/src/combobox/mod.rs diff --git a/demo/src/app.rs b/demo/src/app.rs index 8f72401..9eb5d89 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -59,6 +59,7 @@ fn TheRouter(is_routing: RwSignal) -> impl IntoView { + diff --git a/demo/src/pages/components.rs b/demo/src/pages/components.rs index d9b1f31..ed7c29d 100644 --- a/demo/src/pages/components.rs +++ b/demo/src/pages/components.rs @@ -187,6 +187,10 @@ pub(crate) fn gen_menu_data() -> Vec { value: "/components/color-picker".into(), label: "Color Picker".into(), }, + MenuItemOption { + value: "/components/combobox".into(), + label: "Combobox".into(), + }, MenuItemOption { value: "/components/config-provider".into(), label: "Config Provider".into(), diff --git a/demo_markdown/docs/combobox/mod.md b/demo_markdown/docs/combobox/mod.md new file mode 100644 index 0000000..cef4145 --- /dev/null +++ b/demo_markdown/docs/combobox/mod.md @@ -0,0 +1,33 @@ +# Combobox + +```rust demo +let value = RwSignal::new(vec![]); + +view! { + + + "Cat" + + + "Dog" + + +} +``` + +### Multiselect + +```rust demo +let value = RwSignal::new(vec![]); + +view! { + + + "Cat" + + + "Dog" + + +} +``` diff --git a/demo_markdown/src/lib.rs b/demo_markdown/src/lib.rs index 91867ef..5fb6821 100644 --- a/demo_markdown/src/lib.rs +++ b/demo_markdown/src/lib.rs @@ -41,6 +41,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt "CardMdPage" => "../docs/card/mod.md", "CheckboxMdPage" => "../docs/checkbox/mod.md", "ColorPickerMdPage" => "../docs/color_picker/mod.md", + "ComboboxMdPage" => "../docs/combobox/mod.md", "ConfigProviderMdPage" => "../docs/config_provider/mod.md", "DatePickerMdPage" => "../docs/date_picker/mod.md", "DividerMdPage" => "../docs/divider/mod.md", diff --git a/thaw/src/combobox/combobox.css b/thaw/src/combobox/combobox.css new file mode 100644 index 0000000..5801cfe --- /dev/null +++ b/thaw/src/combobox/combobox.css @@ -0,0 +1,192 @@ +.thaw-combobox { + position: relative; + display: inline-grid; + justify-content: space-between; + align-items: center; + grid-template-columns: 1fr auto; + column-gap: var(--spacingHorizontalXXS); + min-width: 250px; + height: 32px; + padding-right: var(--spacingHorizontalMNudge); + background-color: var(--colorNeutralBackground1); + border-radius: var(--borderRadiusMedium); + border: var(--strokeWidthThin) solid var(--colorNeutralStroke1); + border-bottom-color: var(--colorNeutralStrokeAccessible); + box-sizing: border-box; +} + +.thaw-combobox:hover { + border-color: var(--colorNeutralStroke1Hover); + border-bottom-color: var(--colorNeutralStrokeAccessible); +} + +.thaw-combobox:active { + border-color: var(--colorNeutralStroke1Pressed); + border-bottom-color: var(--colorNeutralStrokeAccessible); +} + +.thaw-combobox:focus-within { + outline-color: transparent; + outline-style: solid; + outline-width: 2px; +} + +.thaw-combobox::after { + content: ""; + position: absolute; + bottom: -1px; + right: -1px; + left: -1px; + + height: max(2px, var(--borderRadiusMedium)); + border-bottom-left-radius: var(--borderRadiusMedium); + border-bottom-right-radius: var(--borderRadiusMedium); + border-bottom: var(--strokeWidthThick) solid var(--colorCompoundBrandStroke); + transition-delay: var(--curveAccelerateMid); + transition-duration: var(--durationUltraFast); + transition-property: transform; + transform: scaleX(0); + clip-path: inset(calc(100% - 2px) 0px 0px); + box-sizing: border-box; +} + +.thaw-combobox:focus-within::after { + transition-delay: var(--curveDecelerateMid); + transition-duration: var(--durationNormal); + transition-property: transform; + transform: scaleX(1); +} + +.thaw-combobox:focus-within:active::after { + border-bottom-color: var(--colorCompoundBrandStrokePressed); +} + +.thaw-combobox__input { + align-self: stretch; + background-color: var(--colorTransparentBackground); + line-height: var(--lineHeightBase300); + font-weight: var(--fontWeightRegular); + font-size: var(--fontSizeBase300); + font-family: var(--fontFamilyBase); + color: var(--colorNeutralForeground1); + padding: 0 0 0 + calc(var(--spacingHorizontalMNudge) + var(--spacingHorizontalXXS)); + border: none; +} + +.thaw-combobox__input:focus { + outline-style: none; +} + +.thaw-combobox__expand-icon { + display: block; + margin-left: var(--spacingHorizontalXXS); + color: var(--colorNeutralStrokeAccessible); + box-sizing: border-box; + cursor: pointer; + font-size: 20px; +} + +.thaw-combobox__listbox { + row-gap: var(--spacingHorizontalXXS); + display: flex; + flex-direction: column; + min-width: 160px; + max-height: 80vh; + background-color: var(--colorNeutralBackground1); + padding: var(--spacingHorizontalXS); + outline: 1px solid var(--colorTransparentStroke); + border-radius: var(--borderRadiusMedium); + box-shadow: var(--shadow16); + box-sizing: border-box; + overflow-y: auto; +} + +.thaw-combobox__listbox.fade-in-scale-up-transition-leave-active { + transform-origin: inherit; + transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1), + transform 0.2s cubic-bezier(0.4, 0, 1, 1); +} + +.thaw-combobox__listbox.fade-in-scale-up-transition-enter-active { + transform-origin: inherit; + transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), + transform 0.2s cubic-bezier(0, 0, 0.2, 1); +} + +.thaw-combobox__listbox.fade-in-scale-up-transition-enter-from, +.thaw-combobox__listbox.fade-in-scale-up-transition-leave-to { + opacity: 0; + transform: scale(0.9); +} + +.thaw-combobox__listbox.fade-in-scale-up-transition-leave-from, +.thaw-combobox__listbox.fade-in-scale-up-transition-enter-to { + opacity: 1; + transform: scale(1); +} + +.thaw-combobox-option { + column-gap: var(--spacingHorizontalXS); + position: relative; + cursor: pointer; + display: flex; + align-items: center; + padding: var(--spacingVerticalSNudge) var(--spacingHorizontalS); + line-height: var(--lineHeightBase300); + font-size: var(--fontSizeBase300); + font-family: var(--fontFamilyBase); + color: var(--colorNeutralForeground1); + border-radius: var(--borderRadiusMedium); +} + +.thaw-combobox-option:hover { + color: var(--colorNeutralForeground1Hover); + background-color: var(--colorNeutralBackground1Hover); +} + +.thaw-combobox-option:active { + color: var(--colorNeutralForeground1Pressed); + background-color: var(--colorNeutralBackground1Pressed); +} + +.thaw-combobox-option__check-icon { + visibility: hidden; + + margin-left: calc(var(--spacingHorizontalXXS) * -1); + margin-right: var(--spacingHorizontalXXS); + font-size: var(--fontSizeBase400); +} + +.thaw-combobox-option--selected > .thaw-combobox-option__check-icon { + visibility: visible; +} + +.thaw-combobox-option__check-icon--multiselect > svg, +.thaw-combobox-option__check-icon > svg { + display: block; + line-height: 0; +} + +.thaw-combobox-option__check-icon--multiselect { + display: flex; + align-items: center; + justify-content: center; + visibility: visible; + margin-left: calc(var(--spacingHorizontalXXS) * -1); + margin-right: var(--spacingHorizontalXXS); + width: 16px; + height: 16px; + font-size: 12px; + border-radius: var(--borderRadiusSmall); + border: var(--strokeWidthThin) solid var(--colorNeutralStrokeAccessible); + box-sizing: border-box; + fill: currentcolor; +} + +.thaw-combobox-option--selected + > .thaw-combobox-option__check-icon--multiselect { + border-color: var(--colorCompoundBrandBackground); + color: var(--colorNeutralForegroundInverted); + background-color: var(--colorCompoundBrandBackground); +} diff --git a/thaw/src/combobox/combobox.rs b/thaw/src/combobox/combobox.rs new file mode 100644 index 0000000..e8fc16b --- /dev/null +++ b/thaw/src/combobox/combobox.rs @@ -0,0 +1,112 @@ +use crate::ConfigInjection; +use leptos::*; +use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement, FollowerWidth}; +use thaw_utils::{mount_style, Model}; + +#[component] +pub fn Combobox( + #[prop(optional, into)] value: Model>, + #[prop(optional)] multiselect: bool, + children: Children, +) -> impl IntoView { + mount_style("combobox", include_str!("./combobox.css")); + let config_provider = ConfigInjection::use_(); + let trigger_ref = NodeRef::::new(); + let listbox_ref = NodeRef::::new(); + let is_show_listbox = RwSignal::new(false); + + view! { + +
+ + + + +
+ + + +
+ {children()} +
+
+
+
+
+ } +} + +#[derive(Clone, Copy)] +pub(crate) struct ComboboxInjection { + value: Model>, + pub multiselect: bool, +} + +impl ComboboxInjection { + pub fn use_() -> Self { + expect_context() + } + + pub fn is_selected(&self, key: &String) -> bool { + self.value.with(|value| value.contains(key)) + } + + pub fn on_option_select(&self, key: &String) { + self.value.update(|value| { + if self.multiselect { + if let Some(index) = value.iter().position(|k| k == key) { + value.remove(index); + return; + } + } else { + value.clear(); + } + value.push(key.clone()); + }); + } +} diff --git a/thaw/src/combobox/combobox_option.rs b/thaw/src/combobox/combobox_option.rs new file mode 100644 index 0000000..e9cab83 --- /dev/null +++ b/thaw/src/combobox/combobox_option.rs @@ -0,0 +1,49 @@ +use crate::ComboboxInjection; +use leptos::*; +use thaw_components::{If, Then}; +use thaw_utils::class_list; + +#[component] +pub fn ComboboxOption(#[prop(into)] key: String, children: Children) -> impl IntoView { + let combobox = ComboboxInjection::use_(); + let key = StoredValue::new(key); + + view! { +
+ { + if combobox.multiselect { + view! { + + } + } else { + view! { + + } + } + } + {children()} +
+ } +} diff --git a/thaw/src/combobox/mod.rs b/thaw/src/combobox/mod.rs new file mode 100644 index 0000000..c66c24d --- /dev/null +++ b/thaw/src/combobox/mod.rs @@ -0,0 +1,5 @@ +mod combobox; +mod combobox_option; + +pub use combobox::*; +pub use combobox_option::*; diff --git a/thaw/src/lib.rs b/thaw/src/lib.rs index 909b259..0125ccf 100644 --- a/thaw/src/lib.rs +++ b/thaw/src/lib.rs @@ -12,6 +12,7 @@ mod card; mod checkbox; mod code; mod color_picker; +mod combobox; mod config_provider; mod date_picker; mod divider; @@ -59,6 +60,7 @@ pub use card::*; pub use checkbox::*; pub use code::*; pub use color_picker::*; +pub use combobox::*; pub use config_provider::*; pub use date_picker::*; pub use divider::*;