From cf18b488dbdbb6d950d93da186f2d311ef3ce85b Mon Sep 17 00:00:00 2001 From: luoxiao Date: Wed, 1 Nov 2023 14:04:12 +0800 Subject: [PATCH] feat: theme --- demo/src/app.rs | 17 +++- demo/src/pages/home.rs | 15 +-- demo/src/pages/mobile.rs | 2 +- src/auto_complete/auto-complete.css | 4 +- src/auto_complete/mod.rs | 25 ++++- src/auto_complete/theme.rs | 23 +++++ src/color_picker/color-picker.css | 4 +- src/color_picker/mod.rs | 18 +++- src/color_picker/theme.rs | 20 ++++ src/mobile/nav_bar/mod.rs | 16 ++- src/mobile/nav_bar/nav-bar.css | 2 +- src/mobile/nav_bar/theme.rs | 20 ++++ src/mobile/tabbar/mod.rs | 20 +++- src/mobile/tabbar/tabbar.css | 4 +- src/mobile/tabbar/theme.rs | 20 ++++ src/select/mod.rs | 32 +++--- src/select/theme.rs | 12 +-- src/theme/mod.rs | 18 +++- src/utils/callback.rs | 152 ++++++++++++++-------------- src/utils/mod.rs | 4 +- 20 files changed, 295 insertions(+), 133 deletions(-) create mode 100644 src/auto_complete/theme.rs create mode 100644 src/color_picker/theme.rs create mode 100644 src/mobile/nav_bar/theme.rs create mode 100644 src/mobile/tabbar/theme.rs diff --git a/demo/src/app.rs b/demo/src/app.rs index d2531d5..33aef7e 100644 --- a/demo/src/app.rs +++ b/demo/src/app.rs @@ -5,7 +5,22 @@ use melt_ui::*; #[component] pub fn App() -> impl IntoView { - let theme = create_rw_signal(Theme::light()); + fn use_query_value(key: &str) -> Option { + let href = window().location().href().ok()?; + let url = Url::try_from(href.as_str()).ok()?; + url.search_params.get(key).cloned() + } + let theme = use_query_value("theme").map_or_else(Theme::light, |name| { + if name == "light" { + Theme::light() + } else if name == "dark" { + Theme::dark() + } else { + Theme::light() + } + }); + let theme = create_rw_signal(theme); + provide_context(theme); view! { diff --git a/demo/src/pages/home.rs b/demo/src/pages/home.rs index 12f6fbb..4857f3e 100644 --- a/demo/src/pages/home.rs +++ b/demo/src/pages/home.rs @@ -1,5 +1,5 @@ use leptos::*; -use leptos_router::{use_navigate, use_query_map, Url}; +use leptos_router::{use_navigate, use_query_map}; use melt_ui::*; #[component] @@ -8,19 +8,6 @@ pub fn Home() -> impl IntoView { // mobile page if let Some(path) = query_map.get("path") { let navigate = use_navigate(); - if let Some((_, search)) = path.split_once("?") { - if let Some((key, value)) = search.split_once("=") { - if key == "theme" { - let theme = use_rw_theme(); - let theme_name = theme.with_untracked(|theme| theme.name.clone()); - if value == "light" && theme_name != "light" { - theme.set(Theme::light()) - } else if value == "dark" && theme_name != "dark" { - theme.set(Theme::dark()) - } - } - } - } navigate(path, Default::default()); } view! { diff --git a/demo/src/pages/mobile.rs b/demo/src/pages/mobile.rs index cbdbb01..bc4f28a 100644 --- a/demo/src/pages/mobile.rs +++ b/demo/src/pages/mobile.rs @@ -4,7 +4,7 @@ use melt_ui::{use_theme, Theme}; #[component] pub fn MobilePage(path: &'static str) -> impl IntoView { let theme = use_theme(Theme::light); - let src = create_memo(move |_| theme.with(|theme| format!("{path}?theme={}", theme.name))); + let src = create_memo(move |_| theme.with(|theme| format!("{path}&theme={}", theme.name))); let style = create_memo(move |_| { theme.with(|theme| { let mut style = String::from("margin-top: 5vh; width: 350px; height: 680px; border-radius: 16px; box-shadow: 0 6px 16px -9px rgba(0, 0, 0, .08), 0 9px 28px 0 rgba(0, 0, 0, .05), 0 12px 48px 16px rgba(0, 0, 0, .03);"); diff --git a/src/auto_complete/auto-complete.css b/src/auto_complete/auto-complete.css index 57fd629..2d553c1 100644 --- a/src/auto_complete/auto-complete.css +++ b/src/auto_complete/auto-complete.css @@ -3,7 +3,7 @@ display: inline-block; max-height: 200px; padding: 5px; - background-color: #fff; + background-color: var(--melt-background-color); border-radius: 3px; box-sizing: border-box; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), @@ -16,5 +16,5 @@ cursor: pointer; } .melt-auto-complete__menu-item:hover { - background-color: #f6f6f7; + background-color: var(--melt-background-color-hover); } diff --git a/src/auto_complete/mod.rs b/src/auto_complete/mod.rs index 28ec2a2..4cb5c58 100644 --- a/src/auto_complete/mod.rs +++ b/src/auto_complete/mod.rs @@ -1,5 +1,10 @@ -use crate::{mount_style, teleport::Teleport, utils::maybe_rw_signal::MaybeRwSignal, Input}; +mod theme; + +use crate::{ + mount_style, teleport::Teleport, use_theme, utils::maybe_rw_signal::MaybeRwSignal, Input, Theme, +}; use leptos::*; +pub use theme::AutoCompleteTheme; #[derive(Clone, PartialEq)] pub struct AutoCompleteOption { @@ -14,6 +19,22 @@ pub fn AutoComplete( #[prop(optional, into)] options: MaybeSignal>, ) -> impl IntoView { mount_style("auto-complete", include_str!("./auto-complete.css")); + let theme = use_theme(Theme::light); + let menu_css_vars = create_memo(move |_| { + let mut css_vars = String::new(); + theme.with(|theme| { + css_vars.push_str(&format!( + "--melt-background-color: {};", + theme.select.menu_background_color + )); + css_vars.push_str(&format!( + "--melt-background-color-hover: {};", + theme.select.menu_background_color_hover + )); + }); + css_vars + }); + let is_show_menu = create_rw_signal(false); let auto_complete_ref = create_node_ref::(); let auto_complete_menu_ref = create_node_ref::(); @@ -58,7 +79,7 @@ pub fn AutoComplete(
Self { + Self { + menu_background_color: "#fff".into(), + menu_background_color_hover: "#f3f5f6".into(), + } + } + + fn dark() -> Self { + Self { + menu_background_color: "#48484e".into(), + menu_background_color_hover: "#ffffff17".into(), + } + } +} diff --git a/src/color_picker/color-picker.css b/src/color_picker/color-picker.css index f4aa05f..7902729 100644 --- a/src/color_picker/color-picker.css +++ b/src/color_picker/color-picker.css @@ -24,7 +24,7 @@ right: 0; width: 240px; padding: 12px; - background-color: #fff; + background-color: var(--melt-background-color); border-radius: 3px; box-sizing: border-box; box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), @@ -65,7 +65,7 @@ border-radius: 6px; box-sizing: border-box; border: 2px solid white; - box-shadow: 0 0 2px 0 rgba(0, 0, 0, .45); + box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.45); } .melt-color-picker-slider { diff --git a/src/color_picker/mod.rs b/src/color_picker/mod.rs index 46af88a..f416ce4 100644 --- a/src/color_picker/mod.rs +++ b/src/color_picker/mod.rs @@ -1,14 +1,28 @@ mod color; +mod theme; -use crate::{mount_style, teleport::Teleport, utils::maybe_rw_signal::MaybeRwSignal}; +use crate::{ + mount_style, teleport::Teleport, use_theme, utils::maybe_rw_signal::MaybeRwSignal, Theme, +}; pub use color::*; use leptos::leptos_dom::helpers::WindowListenerHandle; use leptos::*; +pub use theme::ColorPickerTheme; use wasm_bindgen::__rt::IntoJsResult; #[component] pub fn ColorPicker(#[prop(optional, into)] value: MaybeRwSignal) -> impl IntoView { mount_style("color-picker", include_str!("./color-picker.css")); + let theme = use_theme(Theme::light); + let popover_css_vars = create_memo(move |_| { + theme.with(|theme| { + format!( + "--melt-background-color: {};", + theme.color_picker.popover_background_color + ) + }) + }); + let hue = create_rw_signal(0); let sv = create_rw_signal((0.0, 0.0)); let label = create_rw_signal(String::new()); @@ -95,7 +109,7 @@ pub fn ColorPicker(#[prop(optional, into)] value: MaybeRwSignal) -> impl I class="melt-color-picker-popover" ref=popover_ref style=move || { - if !is_show_popover.get() { Some("display: none") } else { None } + if is_show_popover.get() { popover_css_vars.get() } else { "display: none".to_string() } } > diff --git a/src/color_picker/theme.rs b/src/color_picker/theme.rs new file mode 100644 index 0000000..5fa8ea7 --- /dev/null +++ b/src/color_picker/theme.rs @@ -0,0 +1,20 @@ +use crate::theme::ThemeMethod; + +#[derive(Clone)] +pub struct ColorPickerTheme { + pub popover_background_color: String, +} + +impl ThemeMethod for ColorPickerTheme { + fn light() -> Self { + Self { + popover_background_color: "#fff".into(), + } + } + + fn dark() -> Self { + Self { + popover_background_color: "#48484e".into(), + } + } +} diff --git a/src/mobile/nav_bar/mod.rs b/src/mobile/nav_bar/mod.rs index 0289b0b..54cf6e5 100644 --- a/src/mobile/nav_bar/mod.rs +++ b/src/mobile/nav_bar/mod.rs @@ -1,9 +1,14 @@ +mod theme; + use crate::{ components::*, icon::*, + use_theme, utils::{mount_style::mount_style, StoredMaybeSignal}, + Theme, }; use leptos::*; +pub use theme::NavBarTheme; #[component] pub fn NavBar( @@ -15,6 +20,15 @@ pub fn NavBar( #[prop(optional, into)] on_click_right: Option>, ) -> impl IntoView { mount_style("nav-bar", include_str!("./nav-bar.css")); + let theme = use_theme(Theme::light); + let css_vars = create_memo(move |_| { + theme.with(|theme| { + format!( + "--melt-background-color: {};", + theme.nav_bar.background_color + ) + }) + }); let title: StoredMaybeSignal<_> = title.into(); let left_text: StoredMaybeSignal<_> = left_text.into(); let right_text: StoredMaybeSignal<_> = right_text.into(); @@ -32,7 +46,7 @@ pub fn NavBar( }; view! { -
+
diff --git a/src/mobile/nav_bar/nav-bar.css b/src/mobile/nav_bar/nav-bar.css index bc5195a..6057535 100644 --- a/src/mobile/nav_bar/nav-bar.css +++ b/src/mobile/nav_bar/nav-bar.css @@ -5,7 +5,7 @@ right: 0; height: 46px; line-height: 46px; - background-color: #fff; + background-color: var(--melt-background-color); } .melt-nav-bar__center { diff --git a/src/mobile/nav_bar/theme.rs b/src/mobile/nav_bar/theme.rs new file mode 100644 index 0000000..0af212a --- /dev/null +++ b/src/mobile/nav_bar/theme.rs @@ -0,0 +1,20 @@ +use crate::theme::ThemeMethod; + +#[derive(Clone)] +pub struct NavBarTheme { + pub background_color: String, +} + +impl ThemeMethod for NavBarTheme { + fn light() -> Self { + Self { + background_color: "#fff".into(), + } + } + + fn dark() -> Self { + Self { + background_color: "#323233".into(), + } + } +} diff --git a/src/mobile/tabbar/mod.rs b/src/mobile/tabbar/mod.rs index 1ef3349..a782cb2 100644 --- a/src/mobile/tabbar/mod.rs +++ b/src/mobile/tabbar/mod.rs @@ -1,9 +1,14 @@ mod tabbar_item; +mod theme; -use crate::utils::{maybe_rw_signal::MaybeRwSignal, mount_style::mount_style}; +use crate::{ + use_theme, + utils::{maybe_rw_signal::MaybeRwSignal, mount_style::mount_style}, + Theme, +}; use leptos::*; - pub use tabbar_item::*; +pub use theme::TabbarTheme; #[component] pub fn Tabbar( @@ -11,6 +16,15 @@ pub fn Tabbar( children: Children, ) -> impl IntoView { mount_style("tabbar", include_str!("./tabbar.css")); + let theme = use_theme(Theme::light); + let css_vars = create_memo(move |_| { + theme.with(|theme| { + format!( + "--melt-background-color: {};", + theme.tabbar.background_color + ) + }) + }); let tabbar_injection_key = create_rw_signal(TabbarInjectionKey::new(value.get())); create_effect(move |_| { @@ -29,7 +43,7 @@ pub fn Tabbar( } }); provide_context(tabbar_injection_key); - view! {
{children()}
} + view! {
{children()}
} } #[derive(Clone)] diff --git a/src/mobile/tabbar/tabbar.css b/src/mobile/tabbar/tabbar.css index e0abe1f..889bfb2 100644 --- a/src/mobile/tabbar/tabbar.css +++ b/src/mobile/tabbar/tabbar.css @@ -1,5 +1,5 @@ .melt-tabbar { - background-color: #fff; + background-color: var(--melt-background-color); position: fixed; left: 0; right: 0; @@ -7,4 +7,4 @@ height: 50px; display: flex; justify-content: space-around; -} \ No newline at end of file +} diff --git a/src/mobile/tabbar/theme.rs b/src/mobile/tabbar/theme.rs new file mode 100644 index 0000000..8eabfe4 --- /dev/null +++ b/src/mobile/tabbar/theme.rs @@ -0,0 +1,20 @@ +use crate::theme::ThemeMethod; + +#[derive(Clone)] +pub struct TabbarTheme { + pub background_color: String, +} + +impl ThemeMethod for TabbarTheme { + fn light() -> Self { + Self { + background_color: "#fff".into(), + } + } + + fn dark() -> Self { + Self { + background_color: "#323233".into(), + } + } +} diff --git a/src/select/mod.rs b/src/select/mod.rs index fcd282a..2c7e72d 100644 --- a/src/select/mod.rs +++ b/src/select/mod.rs @@ -47,16 +47,16 @@ where css_vars }); - let popover_css_vars = create_memo(move |_| { + let menu_css_vars = create_memo(move |_| { let mut css_vars = String::new(); theme.with(|theme| { css_vars.push_str(&format!( "--melt-background-color: {};", - theme.select.popover_background_color + theme.select.menu_background_color )); css_vars.push_str(&format!( "--melt-background-color-hover: {};", - theme.select.popover_background_color_hover + theme.select.menu_background_color_hover )); css_vars.push_str(&format!("--melt-font-color: {};", theme.select.font_color)); css_vars.push_str(&format!( @@ -67,14 +67,14 @@ where css_vars }); - let is_show_popover = create_rw_signal(false); + let is_show_menu = create_rw_signal(false); let trigger_ref = create_node_ref::(); - let popover_ref = create_node_ref::(); - let show_popover = move |_| { + let menu_ref = create_node_ref::(); + let show_menu = move |_| { let rect = trigger_ref.get().unwrap().get_bounding_client_rect(); - is_show_popover.set(true); - if let Some(popover_ref) = popover_ref.get() { - popover_ref + is_show_menu.set(true); + if let Some(menu_ref) = menu_ref.get() { + menu_ref .style("width", format!("{}px", rect.width())) .style( "transform", @@ -95,14 +95,14 @@ where if current_el == *body { break; }; - if current_el == ***popover_ref.get().unwrap() + if current_el == ***menu_ref.get().unwrap() || current_el == ***trigger_ref.get().unwrap() { return; } el = current_el.parent_element(); } - is_show_popover.set(false); + is_show_menu.set(false); }); on_cleanup(move || timer.remove()); @@ -116,7 +116,7 @@ where None => String::new(), }); view! { -
+
{move || select_option_label.get()} @@ -125,14 +125,14 @@ where
Self { @@ -68,6 +78,10 @@ impl Theme { slider: SliderTheme::dark(), switch: SwitchTheme::dark(), upload: UploadTheme::dark(), + nav_bar: NavBarTheme::dark(), + tabbar: TabbarTheme::dark(), + auto_complete: AutoCompleteTheme::dark(), + color_picker: ColorPickerTheme::dark(), } } } diff --git a/src/utils/callback.rs b/src/utils/callback.rs index 5002dd0..2be518d 100644 --- a/src/utils/callback.rs +++ b/src/utils/callback.rs @@ -1,89 +1,89 @@ -use leptos::StoredValue; -use std::{fmt, future::Future, pin::Pin, rc::Rc}; +// use leptos::StoredValue; +// use std::{fmt, future::Future, pin::Pin, rc::Rc}; -pub struct AsyncCallback( - #[allow(clippy::complexity)] StoredValue Pin>>>>, -); +// pub struct AsyncCallback( +// #[allow(clippy::complexity)] StoredValue Pin>>>>, +// ); -impl fmt::Debug for AsyncCallback { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - fmt.write_str("AsyncCallback") - } -} +// impl fmt::Debug for AsyncCallback { +// fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { +// fmt.write_str("AsyncCallback") +// } +// } -impl Clone for AsyncCallback { - fn clone(&self) -> Self { - *self - } -} +// impl Clone for AsyncCallback { +// fn clone(&self) -> Self { +// *self +// } +// } -impl Copy for AsyncCallback {} +// impl Copy for AsyncCallback {} -impl AsyncCallback { - pub fn new(f: F) -> Self - where - F: Fn(In) -> Fu + 'static, - Fu: Future + 'static, - { - let f = Rc::new(move |input: In| { - let fut = f(input); - Box::pin(fut) as Pin>> - }); - Self(StoredValue::new(f)) - } +// impl AsyncCallback { +// pub fn new(f: F) -> Self +// where +// F: Fn(In) -> Fu + 'static, +// Fu: Future + 'static, +// { +// let f = Rc::new(move |input: In| { +// let fut = f(input); +// Box::pin(fut) as Pin>> +// }); +// Self(StoredValue::new(f)) +// } - pub async fn call(&self, input: In) -> Out { - let f = self.0.get_value(); - f(input).await - } -} +// pub async fn call(&self, input: In) -> Out { +// let f = self.0.get_value(); +// f(input).await +// } +// } -impl From for AsyncCallback -where - F: Fn(In) -> Fu + 'static, - Fu: Future + 'static, -{ - fn from(f: F) -> AsyncCallback { - AsyncCallback::new(f) - } -} +// impl From for AsyncCallback +// where +// F: Fn(In) -> Fu + 'static, +// Fu: Future + 'static, +// { +// fn from(f: F) -> AsyncCallback { +// AsyncCallback::new(f) +// } +// } -#[cfg(test)] -mod tests { - use crate::utils::AsyncCallback; - use leptos::create_runtime; +// #[cfg(test)] +// mod tests { +// use crate::utils::AsyncCallback; +// use leptos::create_runtime; - struct NoClone {} +// struct NoClone {} - #[test] - fn clone_async_callback() { - let rt = create_runtime(); - let callback = AsyncCallback::new(move |_no_clone: NoClone| async { NoClone {} }); - let _cloned = callback.clone(); - rt.dispose(); - } +// #[test] +// fn clone_async_callback() { +// let rt = create_runtime(); +// let callback = AsyncCallback::new(move |_no_clone: NoClone| async { NoClone {} }); +// let _cloned = callback.clone(); +// rt.dispose(); +// } - #[test] - fn async_callback_from() { - let rt = create_runtime(); - let _callback: AsyncCallback<(), String> = (|()| async { "test".to_string() }).into(); - rt.dispose(); - } +// #[test] +// fn async_callback_from() { +// let rt = create_runtime(); +// let _callback: AsyncCallback<(), String> = (|()| async { "test".to_string() }).into(); +// rt.dispose(); +// } - #[test] - fn async_callback_from_html() { - let rt = create_runtime(); - use leptos::{ - html::{HtmlElement, H1}, - *, - }; +// #[test] +// fn async_callback_from_html() { +// let rt = create_runtime(); +// use leptos::{ +// html::{HtmlElement, H1}, +// *, +// }; - let _callback: AsyncCallback> = (|x: String| async move { - view! { -

{x}

- } - }) - .into(); - rt.dispose(); - } -} +// let _callback: AsyncCallback> = (|x: String| async move { +// view! { +//

{x}

+// } +// }) +// .into(); +// rt.dispose(); +// } +// } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index bcd9bcf..ea5eab4 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,8 +1,8 @@ -mod callback; +// mod callback; pub mod maybe_rw_signal; mod maybe_signal_store; pub mod mount_style; pub mod signal; -pub use callback::AsyncCallback; +// pub use callback::AsyncCallback; pub use maybe_signal_store::*;