mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
commit
d9afe2a730
361 changed files with 13030 additions and 10273 deletions
20
Cargo.toml
20
Cargo.toml
|
@ -1,9 +1,21 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["thaw", "thaw_components", "thaw_utils", "demo", "demo_markdown"]
|
||||
members = [
|
||||
"thaw",
|
||||
"thaw_components",
|
||||
"thaw_macro",
|
||||
"thaw_utils",
|
||||
"demo",
|
||||
"demo_markdown",
|
||||
]
|
||||
exclude = ["examples"]
|
||||
|
||||
[workspace.dependencies]
|
||||
thaw = { version = "0.3.3", path = "./thaw" }
|
||||
thaw_components = { version = "0.1.3", path = "./thaw_components" }
|
||||
thaw_utils = { version = "0.0.5", path = "./thaw_utils" }
|
||||
thaw = { version = "0.4.0-alpha", path = "./thaw" }
|
||||
thaw_components = { version = "0.2.0-alpha", path = "./thaw_components" }
|
||||
thaw_macro = { version = "0.1.0-alpha", path = "./thaw_macro" }
|
||||
thaw_utils = { version = "0.1.0-alpha", path = "./thaw_utils" }
|
||||
|
||||
leptos = { git = "https://github.com/leptos-rs/leptos", rev = "867036559489e86c1f25cdf7133d803d88b0579b" }
|
||||
leptos_meta = { git = "https://github.com/leptos-rs/leptos", rev = "867036559489e86c1f25cdf7133d803d88b0579b" }
|
||||
leptos_router = { git = "https://github.com/leptos-rs/leptos", rev = "867036559489e86c1f25cdf7133d803d88b0579b" }
|
||||
|
|
11
README.md
11
README.md
|
@ -6,20 +6,21 @@
|
|||
|
||||
## Documentation & Community
|
||||
|
||||
[https://thawui.vercel.app](https://thawui.vercel.app)
|
||||
[https://next-thawui.vercel.app](https://next-thawui.vercel.app)
|
||||
|
||||
[Discord](https://discord.gg/YPxuprzu6M)
|
||||
[Discord](https://discord.com/channels/1031524867910148188/1270735289437913108)
|
||||
|
||||
## Leptos compatibility
|
||||
|
||||
| Crate version | Compatible Leptos version |
|
||||
| --------------------------------------------------------------- | ------------------------- |
|
||||
| 0.1 | 0.5 |
|
||||
| -------------------------------------------------------------------- | ------------------------- |
|
||||
| 0.2 / 0.3 | 0.6 |
|
||||
| [thaw/fluent](https://github.com/thaw-ui/thaw/tree/thaw/fluent) | 0.7 |
|
||||
| 0.4([thaw/fluent](https://github.com/thaw-ui/thaw/tree/thaw/fluent)) | 0.7 |
|
||||
|
||||
## Resources
|
||||
|
||||
[Fluent UI](https://react.fluentui.dev)
|
||||
|
||||
[Pigment](https://github.com/kobaltedev/pigment)
|
||||
|
||||
[Naive UI](https://github.com/tusen-ai/naive-ui)
|
||||
|
|
|
@ -7,34 +7,33 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
leptos = { version = "0.6.10" }
|
||||
leptos_meta = { version = "0.6.10" }
|
||||
leptos_router = { version = "0.6.10" }
|
||||
leptos_devtools = { version = "0.0.1", optional = true }
|
||||
leptos = { workspace = true }
|
||||
leptos_meta = { workspace = true }
|
||||
leptos_router = { workspace = true }
|
||||
thaw = { path = "../thaw" }
|
||||
demo_markdown = { path = "../demo_markdown" }
|
||||
icondata = "0.3.0"
|
||||
palette = "0.7.4"
|
||||
chrono = "0.4.33"
|
||||
cfg-if = "1.0.0"
|
||||
# leptos-use = "0.10.10"
|
||||
send_wrapper = "0.6"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
tracing = ["leptos/tracing"]
|
||||
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr", "thaw/csr"]
|
||||
csr = ["leptos/csr", "thaw/csr"]
|
||||
ssr = ["leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", "thaw/ssr"]
|
||||
hydrate = [
|
||||
"leptos/hydrate",
|
||||
"leptos_meta/hydrate",
|
||||
"leptos_router/hydrate",
|
||||
"thaw/hydrate",
|
||||
]
|
||||
nightly = ["leptos/nightly", "leptos_meta/nightly", "leptos_router/nightly"]
|
||||
hydrate = ["leptos/hydrate", "thaw/hydrate"]
|
||||
nightly = ["leptos/nightly", "leptos_router/nightly", "thaw/nightly"]
|
||||
|
||||
# https://benw.is/posts/how-i-improved-my-rust-compile-times-by-seventy-five-percent#optimization-level
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
# [profile.dev]
|
||||
# opt-level = 1
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
# [profile.dev.package."*"]
|
||||
# opt-level = 3
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[build]
|
||||
target = "index.html"
|
||||
# public_url = "/thaw/"
|
||||
# release = true
|
||||
release = true
|
||||
# filehash = false
|
||||
|
||||
[watch]
|
||||
|
|
204
demo/src/app.rs
204
demo/src/app.rs
|
@ -1,127 +1,123 @@
|
|||
use crate::pages::*;
|
||||
use leptos::*;
|
||||
use leptos::{prelude::*, reactive_graph::wrappers::write::SignalSetter};
|
||||
use leptos_meta::provide_meta_context;
|
||||
use leptos_router::*;
|
||||
use leptos_router::{
|
||||
components::{ParentRoute, Route, Router, Routes},
|
||||
path,
|
||||
};
|
||||
// use leptos_use::{
|
||||
// storage::use_local_storage,
|
||||
// utils::{FromToStringCodec, StringCodec},
|
||||
// };
|
||||
use thaw::*;
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
let is_routing = create_rw_signal(false);
|
||||
let set_is_routing = SignalSetter::map(move |is_routing_data| {
|
||||
is_routing.set(is_routing_data);
|
||||
});
|
||||
provide_meta_context();
|
||||
// let (read_theme, _, _) = use_local_storage::<String, FromToStringCodec>("theme");
|
||||
// let theme = RwSignal::new(Theme::from(read_theme.get_untracked()));
|
||||
|
||||
view! {
|
||||
<Router set_is_routing>
|
||||
<TheProvider>
|
||||
<TheRouter is_routing/>
|
||||
</TheProvider>
|
||||
</Router>
|
||||
<ConfigProvider>
|
||||
<ToasterProvider>
|
||||
<LoadingBarProvider>
|
||||
<TheRouter />
|
||||
</LoadingBarProvider>
|
||||
</ToasterProvider>
|
||||
</ConfigProvider>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
|
||||
let loading_bar = use_loading_bar();
|
||||
_ = is_routing.watch(move |is_routing| {
|
||||
fn TheRouter() -> impl IntoView {
|
||||
let loading_bar = LoadingBarInjection::expect_use();
|
||||
let is_routing = RwSignal::new(false);
|
||||
let set_is_routing = SignalSetter::map(move |is_routing_data| {
|
||||
is_routing.set(is_routing_data);
|
||||
});
|
||||
|
||||
Effect::watch(
|
||||
move || is_routing.get(),
|
||||
move |is_routing, _, _| {
|
||||
if *is_routing {
|
||||
loading_bar.start();
|
||||
} else {
|
||||
loading_bar.finish();
|
||||
}
|
||||
});
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
view! {
|
||||
<Routes>
|
||||
<Route path="/" view=Home/>
|
||||
<Route path="/guide" view=GuidePage>
|
||||
<Route path="/installation" view=InstallationMdPage/>
|
||||
<Route path="/usage" view=UsageMdPage/>
|
||||
<Route path="/server-sider-rendering" view=ServerSiderRenderingMdPage/>
|
||||
<Route path="/development/guide" view=DevelopmentGuideMdPage/>
|
||||
<Route path="/development/components" view=DevelopmentComponentsMdPage/>
|
||||
</Route>
|
||||
<Route path="/components" view=ComponentsPage>
|
||||
<Route path="/tabbar" view=TabbarPage/>
|
||||
<Route path="/nav-bar" view=NavBarPage/>
|
||||
<Route path="/toast" view=ToastPage/>
|
||||
<Route path="/alert" view=AlertMdPage/>
|
||||
<Route path="/anchor" view=AnchorMdPage/>
|
||||
<Route path="/auto-complete" view=AutoCompleteMdPage/>
|
||||
<Route path="/avatar" view=AvatarMdPage/>
|
||||
<Route path="/back-top" view=BackTopMdPage/>
|
||||
<Route path="/badge" view=BadgeMdPage/>
|
||||
<Route path="/breadcrumb" view=BreadcrumbMdPage/>
|
||||
<Route path="/button" view=ButtonMdPage/>
|
||||
<Route path="/calendar" view=CalendarMdPage/>
|
||||
<Route path="/card" view=CardMdPage/>
|
||||
<Route path="/checkbox" view=CheckboxMdPage/>
|
||||
<Route path="/collapse" view=CollapseMdPage/>
|
||||
<Route path="/color-picker" view=ColorPickerMdPage/>
|
||||
<Route path="/date-picker" view=DatePickerMdPage/>
|
||||
<Route path="/divider" view=DividerMdPage/>
|
||||
<Route path="/drawer" view=DrawerMdPage/>
|
||||
<Route path="/dropdown" view=DropdownMdPage/>
|
||||
<Route path="/grid" view=GridMdPage/>
|
||||
<Route path="/icon" view=IconMdPage/>
|
||||
<Route path="/image" view=ImageMdPage/>
|
||||
<Route path="/input" view=InputMdPage/>
|
||||
<Route path="/input-number" view=InputNumberMdPage/>
|
||||
<Route path="/layout" view=LayoutMdPage/>
|
||||
<Route path="/loading-bar" view=LoadingBarMdPage/>
|
||||
<Route path="/menu" view=MenuMdPage/>
|
||||
<Route path="/message" view=MessageMdPage/>
|
||||
<Route path="/modal" view=ModalMdPage/>
|
||||
<Route path="/pagination" view=PaginationMdPage/>
|
||||
<Route path="/popover" view=PopoverMdPage/>
|
||||
<Route path="/progress" view=ProgressMdPage/>
|
||||
<Route path="/radio" view=RadioMdPage/>
|
||||
<Route path="/scrollbar" view=ScrollbarMdPage/>
|
||||
<Route path="/select" view=SelectMdPage/>
|
||||
<Route path="/skeleton" view=SkeletonMdPage/>
|
||||
<Route path="/slider" view=SliderMdPage/>
|
||||
<Route path="/space" view=SpaceMdPage/>
|
||||
<Route path="/spinner" view=SpinnerMdPage/>
|
||||
<Route path="/switch" view=SwitchMdPage/>
|
||||
<Route path="/table" view=TableMdPage/>
|
||||
<Route path="/tabs" view=TabsMdPage/>
|
||||
<Route path="/tag" view=TagMdPage/>
|
||||
<Route path="/theme" view=ThemeMdPage/>
|
||||
<Route path="/time-picker" view=TimePickerMdPage/>
|
||||
<Route path="/typography" view=TypographyMdPage/>
|
||||
<Route path="/upload" view=UploadMdPage/>
|
||||
</Route>
|
||||
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
||||
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
||||
<Route path="/mobile/toast" view=ToastDemoPage/>
|
||||
<Router set_is_routing>
|
||||
<Routes fallback=|| "404">
|
||||
<Route path=path!("/") view=Home/>
|
||||
<ParentRoute path=path!("/guide") view=ComponentsPage>
|
||||
<Route path=path!("/installation") view=InstallationMdPage/>
|
||||
<Route path=path!("/server-sider-rendering") view=ServerSiderRenderingMdPage/>
|
||||
<Route path=path!("/development/components") view=DevelopmentComponentsMdPage/>
|
||||
</ParentRoute>
|
||||
<ParentRoute path=path!("/components") view=ComponentsPage>
|
||||
{
|
||||
view! {
|
||||
<Route path=path!("/accordion") view=AccordionMdPage/>
|
||||
<Route path=path!("/anchor") view=AnchorMdPage/>
|
||||
<Route path=path!("/auto-complete") view=AutoCompleteMdPage/>
|
||||
<Route path=path!("/avatar") view=AvatarMdPage/>
|
||||
<Route path=path!("/back-top") view=BackTopMdPage/>
|
||||
<Route path=path!("/badge") view=BadgeMdPage/>
|
||||
<Route path=path!("/breadcrumb") view=BreadcrumbMdPage/>
|
||||
<Route path=path!("/button") view=ButtonMdPage/>
|
||||
<Route path=path!("/calendar") view=CalendarMdPage/>
|
||||
<Route path=path!("/card") view=CardMdPage/>
|
||||
<Route path=path!("/checkbox") view=CheckboxMdPage/>
|
||||
<Route path=path!("/color-picker") view=ColorPickerMdPage/>
|
||||
<Route path=path!("/combobox") view=ComboboxMdPage/>
|
||||
<Route path=path!("/config-provider") view=ConfigProviderMdPage/>
|
||||
}
|
||||
}
|
||||
{
|
||||
view! {
|
||||
<Route path=path!("date-picker") view=DatePickerMdPage/>
|
||||
<Route path=path!("/dialog") view=DialogMdPage/>
|
||||
<Route path=path!("/divider") view=DividerMdPage/>
|
||||
<Route path=path!("/drawer") view=DrawerMdPage/>
|
||||
<Route path=path!("/menu") view=MenuMdPage/>
|
||||
<Route path=path!("/grid") view=GridMdPage/>
|
||||
<Route path=path!("/icon") view=IconMdPage/>
|
||||
<Route path=path!("/image") view=ImageMdPage/>
|
||||
<Route path=path!("/input") view=InputMdPage/>
|
||||
<Route path=path!("/layout") view=LayoutMdPage/>
|
||||
<Route path=path!("/loading-bar") view=LoadingBarMdPage/>
|
||||
<Route path=path!("/message-bar") view=MessageBarMdPage/>
|
||||
<Route path=path!("/nav") view=NavMdPage/>
|
||||
<Route path=path!("/pagination") view=PaginationMdPage/>
|
||||
<Route path=path!("/popover") view=PopoverMdPage/>
|
||||
<Route path=path!("/progress-bar") view=ProgressBarMdPage/>
|
||||
}
|
||||
}
|
||||
{
|
||||
view! {
|
||||
<Route path=path!("/radio") view=RadioMdPage/>
|
||||
<Route path=path!("/scrollbar") view=ScrollbarMdPage/>
|
||||
<Route path=path!("/skeleton") view=SkeletonMdPage/>
|
||||
<Route path=path!("/slider") view=SliderMdPage/>
|
||||
<Route path=path!("/space") view=SpaceMdPage/>
|
||||
<Route path=path!("/spin-button") view=SpinButtonMdPage/>
|
||||
<Route path=path!("/spinner") view=SpinnerMdPage/>
|
||||
<Route path=path!("/switch") view=SwitchMdPage/>
|
||||
<Route path=path!("/tab-list") view=TabListMdPage/>
|
||||
<Route path=path!("/table") view=TableMdPage/>
|
||||
<Route path=path!("/tag") view=TagMdPage/>
|
||||
<Route path=path!("/text") view=TextMdPage/>
|
||||
<Route path=path!("/textarea") view=TextareaMdPage/>
|
||||
<Route path=path!("/time-picker") view=TimePickerMdPage/>
|
||||
<Route path=path!("/toast") view=ToastMdPage />
|
||||
<Route path=path!("/upload") view=UploadMdPage/>
|
||||
}
|
||||
}
|
||||
</ParentRoute>
|
||||
</Routes>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn TheProvider(children: Children) -> impl IntoView {
|
||||
fn use_query_value(key: &str) -> Option<String> {
|
||||
let query_map = use_query_map();
|
||||
query_map.with_untracked(|query| query.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);
|
||||
|
||||
view! {
|
||||
<ThemeProvider theme>
|
||||
<GlobalStyle/>
|
||||
<MessageProvider>
|
||||
<LoadingBarProvider>{children()}</LoadingBarProvider>
|
||||
</MessageProvider>
|
||||
</ThemeProvider>
|
||||
</Router>
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 5.5 KiB |
|
@ -1,4 +1,4 @@
|
|||
use leptos::*;
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::Style;
|
||||
use thaw::*;
|
||||
|
||||
|
@ -6,16 +6,17 @@ use thaw::*;
|
|||
pub struct DemoCode {
|
||||
#[prop(default = true)]
|
||||
is_highlight: bool,
|
||||
children: Children,
|
||||
#[prop(into)]
|
||||
text: String,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Demo(demo_code: DemoCode, #[prop(optional)] children: Option<Children>) -> impl IntoView {
|
||||
let theme = use_theme(Theme::light);
|
||||
let theme = Theme::use_theme(Theme::light);
|
||||
let css_vars = Memo::new(move |_| {
|
||||
let mut css_vars = String::new();
|
||||
theme.with(|theme| {
|
||||
if theme.common.color_scheme == "dark" {
|
||||
if theme.color.color_scheme == "dark" {
|
||||
css_vars.push_str("--demo-color: #ffffff60;");
|
||||
css_vars.push_str("--demo-color-hover: #ffffffe0;");
|
||||
css_vars.push_str("--demo-border-color: #383f52;");
|
||||
|
@ -23,10 +24,7 @@ pub fn Demo(demo_code: DemoCode, #[prop(optional)] children: Option<Children>) -
|
|||
} else {
|
||||
css_vars.push_str("--demo-color: #00000060;");
|
||||
css_vars.push_str("--demo-color-hover: #000000e0;");
|
||||
css_vars.push_str(&format!(
|
||||
"--demo-border-color: {};",
|
||||
theme.common.border_color
|
||||
));
|
||||
css_vars.push_str(&format!("--demo-border-color: var(--colorNeutralStroke2);",));
|
||||
css_vars.push_str("--demo-background-color: #f9fafb;");
|
||||
}
|
||||
});
|
||||
|
@ -34,26 +32,12 @@ pub fn Demo(demo_code: DemoCode, #[prop(optional)] children: Option<Children>) -
|
|||
});
|
||||
|
||||
let code_class = Memo::new(move |_| {
|
||||
theme.with(|theme| {
|
||||
format!(
|
||||
"demo-demo__code color-scheme--{}",
|
||||
theme.common.color_scheme
|
||||
)
|
||||
})
|
||||
theme.with(|theme| format!("demo-demo__code color-scheme--{}", theme.color.color_scheme))
|
||||
});
|
||||
let is_show_code = RwSignal::new(children.is_none());
|
||||
|
||||
let is_highlight = demo_code.is_highlight;
|
||||
let frag = (demo_code.children)();
|
||||
let mut html = String::new();
|
||||
for node in frag.nodes {
|
||||
match node {
|
||||
View::Text(text) => html.push_str(&text.content),
|
||||
_ => {
|
||||
leptos::logging::warn!("Only text nodes are supported as children of <DemoCode />.")
|
||||
}
|
||||
}
|
||||
}
|
||||
let html = demo_code.text;
|
||||
|
||||
view! {
|
||||
<Style id="leptos-thaw-syntect-css">
|
||||
|
@ -68,7 +52,7 @@ pub fn Demo(demo_code: DemoCode, #[prop(optional)] children: Option<Children>) -
|
|||
view! {
|
||||
<div class="demo-demo__view">{children()}</div>
|
||||
<div class="demo-demo__toolbar" class=("demo-demo__toolbar--code", move || !is_show_code.get())>
|
||||
<Popover tooltip=true>
|
||||
<Popover appearance=PopoverAppearance::Inverted>
|
||||
<PopoverTrigger slot>
|
||||
<span on:click=move |_| is_show_code.update(|show| *show = !*show) class="demo-demo__toolbar-btn">
|
||||
{
|
||||
|
@ -99,7 +83,7 @@ pub fn Demo(demo_code: DemoCode, #[prop(optional)] children: Option<Children>) -
|
|||
None
|
||||
}
|
||||
}
|
||||
<div class=move || code_class.get() style:display=move || (!is_show_code.get()).then_some("none")>
|
||||
<div class=move || code_class.get() style:display=move || (!is_show_code.get()).then_some("none").unwrap_or_default()>
|
||||
{
|
||||
if is_highlight {
|
||||
view! {
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
use super::switch_version::SwitchVersion;
|
||||
use leptos::*;
|
||||
use leptos::{ev::MouseEvent, prelude::*};
|
||||
use leptos_meta::Style;
|
||||
use leptos_router::{use_location, use_navigate};
|
||||
use leptos_router::hooks::use_navigate;
|
||||
// use leptos_use::{storage::use_local_storage, utils::FromToStringCodec};
|
||||
use thaw::*;
|
||||
|
||||
#[component]
|
||||
pub fn SiteHeader() -> impl IntoView {
|
||||
let theme = use_rw_theme();
|
||||
let theme_name = create_memo(move |_| {
|
||||
let navigate = use_navigate();
|
||||
let navigate_signal = RwSignal::new(use_navigate());
|
||||
let theme = Theme::use_rw_theme();
|
||||
let theme_name = Memo::new(move |_| {
|
||||
theme.with(|theme| {
|
||||
if theme.name == *"light" {
|
||||
"Dark".to_string()
|
||||
|
@ -16,19 +19,21 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
}
|
||||
})
|
||||
});
|
||||
let change_theme = Callback::new(move |_| {
|
||||
// let (_, write_theme, _) = use_local_storage::<String, FromToStringCodec>("theme");
|
||||
let change_theme = move |_| {
|
||||
if theme_name.get_untracked() == "Light" {
|
||||
theme.set(Theme::light());
|
||||
// write_theme.set("light".to_string());
|
||||
} else {
|
||||
theme.set(Theme::dark());
|
||||
// write_theme.set("dark".to_string());
|
||||
}
|
||||
});
|
||||
let style = create_memo(move |_| {
|
||||
theme.with(|theme| format!("border-bottom: 1px solid {}", theme.common.border_color))
|
||||
});
|
||||
let search_value = create_rw_signal(String::new());
|
||||
let search_all_options = store_value(gen_search_all_options());
|
||||
let search_options = create_memo(move |_| {
|
||||
};
|
||||
|
||||
let search_value = RwSignal::new(String::new());
|
||||
let search_all_options = StoredValue::new(gen_search_all_options());
|
||||
|
||||
let search_options = Memo::new(move |_| {
|
||||
let search_value = search_value.get();
|
||||
if search_value.is_empty() {
|
||||
return vec![];
|
||||
|
@ -62,11 +67,16 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
.collect()
|
||||
})
|
||||
});
|
||||
let on_search_select = move |path: String| {
|
||||
let navigate = use_navigate();
|
||||
let on_search_select = {
|
||||
let navigate = navigate.clone();
|
||||
move |path: String| {
|
||||
navigate(&path, Default::default());
|
||||
}
|
||||
};
|
||||
let auto_complete_ref = create_component_ref::<AutoCompleteRef>();
|
||||
let auto_complete_ref = ComponentRef::<AutoCompleteRef>::new();
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
{
|
||||
use leptos::ev;
|
||||
let handle = window_event_listener(ev::keydown, move |event| {
|
||||
let key = event.key();
|
||||
if key == *"/" {
|
||||
|
@ -77,8 +87,9 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
}
|
||||
});
|
||||
on_cleanup(move || handle.remove());
|
||||
}
|
||||
|
||||
let menu_value = use_menu_value(change_theme);
|
||||
// let menu_value = use_menu_value(change_theme);
|
||||
view! {
|
||||
<Style id="demo-header">
|
||||
"
|
||||
|
@ -88,6 +99,9 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
border-bottom: 1px solid var(--colorNeutralStroke2);
|
||||
}
|
||||
.demo-name {
|
||||
cursor: pointer;
|
||||
|
@ -100,8 +114,8 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
.demo-header__menu-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
.demo-header__menu-popover-mobile {
|
||||
padding: 0;
|
||||
.demo-header__right-btn .thaw-select {
|
||||
width: 60px;
|
||||
}
|
||||
@media screen and (max-width: 600px) {
|
||||
.demo-header {
|
||||
|
@ -121,87 +135,103 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
}
|
||||
"
|
||||
</Style>
|
||||
<LayoutHeader class="demo-header" style>
|
||||
<Space
|
||||
on:click=move |_| {
|
||||
let navigate = use_navigate();
|
||||
<LayoutHeader attr:class=("demo-header", true)>
|
||||
<Space on:click=move |_| {
|
||||
navigate("/", Default::default());
|
||||
}>
|
||||
<img src="/logo.svg" style="width: 36px"/>
|
||||
<div class="demo-name">
|
||||
"Thaw UI"
|
||||
</div>
|
||||
<div class="demo-name">"Thaw UI"</div>
|
||||
</Space>
|
||||
<Space>
|
||||
<Space align=SpaceAlign::Center>
|
||||
<AutoComplete
|
||||
value=search_value
|
||||
placeholder="Type '/' to search"
|
||||
options=search_options
|
||||
clear_after_select=true
|
||||
blur_after_select=true
|
||||
on_select=on_search_select
|
||||
comp_ref=auto_complete_ref
|
||||
>
|
||||
<For each=move || search_options.get() key=|option| option.label.clone() let:option>
|
||||
<AutoCompleteOption value=option.value>{option.label}</AutoCompleteOption>
|
||||
</For>
|
||||
<AutoCompletePrefix slot>
|
||||
<Icon icon=icondata::AiSearchOutlined style="font-size: 18px; color: var(--thaw-placeholder-color);"/>
|
||||
<Icon
|
||||
icon=icondata::AiSearchOutlined
|
||||
style="font-size: 18px; color: var(--thaw-placeholder-color);"
|
||||
/>
|
||||
</AutoCompletePrefix>
|
||||
</AutoComplete>
|
||||
<Popover placement=PopoverPlacement::BottomEnd class="demo-header__menu-popover-mobile">
|
||||
<PopoverTrigger slot class="demo-header__menu-mobile">
|
||||
<Menu
|
||||
position=MenuPosition::BottomEnd
|
||||
on_select=move |value : String| match value.as_str() {
|
||||
"Dark" => change_theme(MouseEvent::new("click").unwrap()),
|
||||
"Light" => change_theme(MouseEvent::new("click").unwrap()),
|
||||
"github" => { _ = window().open_with_url("http://github.com/thaw-ui/thaw"); },
|
||||
"discord" => { _ = window().open_with_url("https://discord.com/channels/1031524867910148188/1270735289437913108"); },
|
||||
_ => navigate_signal.get()(&value, Default::default())
|
||||
|
||||
}
|
||||
>
|
||||
<MenuTrigger slot class="demo-header__menu-mobile">
|
||||
<Button
|
||||
variant=ButtonVariant::Text
|
||||
appearance=ButtonAppearance::Subtle
|
||||
icon=icondata::AiUnorderedListOutlined
|
||||
style="font-size: 22px; padding: 0px 6px;"
|
||||
attr:style="font-size: 22px; padding: 0px 6px;"
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<div style="height: 70vh; overflow: auto;">
|
||||
<Menu value=menu_value>
|
||||
<MenuItem key=theme_name label=theme_name />
|
||||
<MenuItem key="github" label="Github" />
|
||||
</MenuTrigger>
|
||||
<MenuItem value=theme_name>{theme_name}</MenuItem>
|
||||
<MenuItem icon=icondata::AiGithubOutlined value="github">"Github"</MenuItem>
|
||||
<MenuItem icon=icondata::BiDiscordAlt value="discord">"Discord"</MenuItem>
|
||||
{
|
||||
use crate::pages::{gen_guide_menu_data, gen_menu_data};
|
||||
vec![
|
||||
gen_guide_menu_data().into_view(),
|
||||
gen_menu_data().into_view(),
|
||||
]
|
||||
use crate::pages::{gen_nav_data, NavGroupOption, NavItemOption};
|
||||
gen_nav_data().into_iter().map(|data| {
|
||||
let NavGroupOption { label, children } = data;
|
||||
view! {
|
||||
<Caption1Strong style="margin-inline-start: 10px; margin-top: 10px; display: block">
|
||||
{label}
|
||||
</Caption1Strong>
|
||||
{
|
||||
children.into_iter().map(|item| {
|
||||
let NavItemOption { label, value } = item;
|
||||
view! {
|
||||
<MenuItem value=value>{label}</MenuItem>
|
||||
}
|
||||
}).collect_view()
|
||||
}
|
||||
}
|
||||
}).collect_view()
|
||||
}
|
||||
</Menu>
|
||||
</div>
|
||||
</Popover>
|
||||
<Space class="demo-header__right-btn" align=SpaceAlign::Center>
|
||||
<Button
|
||||
variant=ButtonVariant::Text
|
||||
on_click=move |_| {
|
||||
let navigate = use_navigate();
|
||||
navigate("/guide/installation", Default::default());
|
||||
}
|
||||
>
|
||||
|
||||
"Guide"
|
||||
</Button>
|
||||
<Button
|
||||
variant=ButtonVariant::Text
|
||||
on_click=move |_| {
|
||||
let navigate = use_navigate();
|
||||
navigate("/components/button", Default::default());
|
||||
}
|
||||
>
|
||||
|
||||
"Components"
|
||||
</Button>
|
||||
<Button variant=ButtonVariant::Text on_click=Callback::new(move |_| change_theme.call(()))>
|
||||
{move || theme_name.get()}
|
||||
</Button>
|
||||
<SwitchVersion/>
|
||||
<Button
|
||||
variant=ButtonVariant::Text
|
||||
icon=Memo::new(move |_| {
|
||||
theme.with(|theme| {
|
||||
if theme.name == "light" {
|
||||
icondata::BiMoonRegular
|
||||
} else {
|
||||
icondata::BiSunRegular
|
||||
}
|
||||
})
|
||||
})
|
||||
on_click=change_theme
|
||||
>
|
||||
{move || theme_name.get()}
|
||||
</Button>
|
||||
<Button
|
||||
icon=icondata::BiDiscordAlt
|
||||
on_click=move |_| {
|
||||
_ = window().open_with_url("https://discord.com/channels/1031524867910148188/1270735289437913108");
|
||||
}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon=icondata::AiGithubOutlined
|
||||
round=true
|
||||
style="font-size: 22px; padding: 0px 6px;"
|
||||
on_click=move |_| {
|
||||
_ = window().open_with_url("http://github.com/thaw-ui/thaw");
|
||||
}
|
||||
/>
|
||||
|
||||
</Space>
|
||||
</Space>
|
||||
|
||||
|
@ -209,63 +239,62 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
struct AutoCompleteOption {
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
fn gen_search_all_options() -> Vec<AutoCompleteOption> {
|
||||
use crate::pages::{gen_guide_menu_data, gen_menu_data};
|
||||
let mut options: Vec<_> = gen_menu_data()
|
||||
crate::pages::gen_nav_data()
|
||||
.into_iter()
|
||||
.flat_map(|group| {
|
||||
group.children.into_iter().map(|item| AutoCompleteOption {
|
||||
value: format!("/components/{}", item.value),
|
||||
value: item.value,
|
||||
label: item.label,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
options.extend(gen_guide_menu_data().into_iter().flat_map(|group| {
|
||||
group.children.into_iter().map(|item| AutoCompleteOption {
|
||||
value: format!("/guide/{}", item.value),
|
||||
label: item.label,
|
||||
})
|
||||
}));
|
||||
options
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn use_menu_value(change_theme: Callback<()>) -> RwSignal<String> {
|
||||
use crate::pages::gen_guide_menu_data;
|
||||
let guide = store_value(gen_guide_menu_data());
|
||||
let navigate = use_navigate();
|
||||
let loaction = use_location();
|
||||
// TODO
|
||||
// fn use_menu_value(change_theme: Callback<()>) -> RwSignal<String> {
|
||||
// use crate::pages::gen_guide_menu_data;
|
||||
// let guide = store_value(gen_guide_menu_data());
|
||||
// let navigate = use_navigate();
|
||||
// let loaction = use_location();
|
||||
|
||||
let menu_value = create_rw_signal({
|
||||
let mut pathname = loaction.pathname.get_untracked();
|
||||
if pathname.starts_with("/components/") {
|
||||
pathname.drain(12..).collect()
|
||||
} else if pathname.starts_with("/guide/") {
|
||||
pathname.drain(7..).collect()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
});
|
||||
// let menu_value = create_rw_signal({
|
||||
// let mut pathname = loaction.pathname.get_untracked();
|
||||
// if pathname.starts_with("/components/") {
|
||||
// pathname.drain(12..).collect()
|
||||
// } else if pathname.starts_with("/guide/") {
|
||||
// pathname.drain(7..).collect()
|
||||
// } else {
|
||||
// String::new()
|
||||
// }
|
||||
// });
|
||||
|
||||
_ = menu_value.watch(move |name| {
|
||||
if name == "Dark" || name == "Light" {
|
||||
change_theme.call(());
|
||||
return;
|
||||
} else if name == "github" {
|
||||
_ = window().open_with_url("http://github.com/thaw-ui/thaw");
|
||||
return;
|
||||
}
|
||||
let pathname = loaction.pathname.get_untracked();
|
||||
if guide.with_value(|menu| {
|
||||
menu.iter()
|
||||
.any(|group| group.children.iter().any(|item| &item.value == name))
|
||||
}) {
|
||||
if !pathname.eq(&format!("/guide/{name}")) {
|
||||
navigate(&format!("/guide/{name}"), Default::default());
|
||||
}
|
||||
} else if !pathname.eq(&format!("/components/{name}")) {
|
||||
navigate(&format!("/components/{name}"), Default::default());
|
||||
}
|
||||
});
|
||||
// _ = menu_value.watch(move |name| {
|
||||
// if name == "Dark" || name == "Light" {
|
||||
// change_theme.call(());
|
||||
// return;
|
||||
// } else if name == "github" {
|
||||
// _ = window().open_with_url("http://github.com/thaw-ui/thaw");
|
||||
// return;
|
||||
// }
|
||||
// let pathname = loaction.pathname.get_untracked();
|
||||
// if guide.with_value(|menu| {
|
||||
// menu.iter()
|
||||
// .any(|group| group.children.iter().any(|item| &item.value == name))
|
||||
// }) {
|
||||
// if !pathname.eq(&format!("/guide/{name}")) {
|
||||
// navigate(&format!("/guide/{name}"), Default::default());
|
||||
// }
|
||||
// } else if !pathname.eq(&format!("/components/{name}")) {
|
||||
// navigate(&format!("/components/{name}"), Default::default());
|
||||
// }
|
||||
// });
|
||||
|
||||
menu_value
|
||||
}
|
||||
// menu_value
|
||||
// }
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
use leptos::*;
|
||||
use leptos::prelude::*;
|
||||
use thaw::*;
|
||||
|
||||
#[component]
|
||||
pub fn SwitchVersion() -> impl IntoView {
|
||||
let options = vec![
|
||||
SelectOption::new("main", "https://thawui.vercel.app".into()),
|
||||
SelectOption::new("0.3.3", "https://thaw-npgo2zdtv-thaw.vercel.app".into()),
|
||||
SelectOption::new("0.3.2", "https://thaw-czldv7au5-thaw.vercel.app".into()),
|
||||
SelectOption::new("0.3.1", "https://thaw-bwh2r7eok-thaw.vercel.app".into()),
|
||||
SelectOption::new("0.3.0", "https://thaw-gxcwse9r5-thaw.vercel.app".into()),
|
||||
SelectOption::new("0.2.6", "https://thaw-mzh1656cm-thaw.vercel.app".into()),
|
||||
SelectOption::new("0.2.5", "https://thaw-8og1kv8zs-thaw.vercel.app".into()),
|
||||
("main", "https://thawui.vercel.app"),
|
||||
("0.3.0", "https://thaw-gxcwse9r5-thaw.vercel.app"),
|
||||
("0.2.6", "https://thaw-mzh1656cm-thaw.vercel.app"),
|
||||
("0.2.5", "https://thaw-8og1kv8zs-thaw.vercel.app"),
|
||||
];
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
|
@ -26,11 +23,17 @@ pub fn SwitchVersion() -> impl IntoView {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
let version = RwSignal::new(None::<String>);
|
||||
// let version = RwSignal::new(None::<String>);
|
||||
}
|
||||
}
|
||||
|
||||
view! {
|
||||
<Select value=version options/>
|
||||
<Combobox>
|
||||
{
|
||||
options.into_iter().map(|option| view! {
|
||||
<ComboboxOption value=option.1 text=option.0 />
|
||||
}).collect_view()
|
||||
}
|
||||
</Combobox>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,11 @@ mod components;
|
|||
mod pages;
|
||||
|
||||
use app::App;
|
||||
use leptos::*;
|
||||
use leptos::prelude::*;
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "tracing")]
|
||||
leptos_devtools::devtools!();
|
||||
let _ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
mount_to_body(App)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use crate::components::SiteHeader;
|
||||
use leptos::*;
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::Style;
|
||||
use leptos_router::{use_location, use_navigate, Outlet};
|
||||
use leptos_router::{
|
||||
components::Outlet,
|
||||
hooks::{use_location, use_navigate},
|
||||
};
|
||||
use thaw::*;
|
||||
|
||||
#[component]
|
||||
|
@ -9,21 +12,15 @@ pub fn ComponentsPage() -> impl IntoView {
|
|||
let navigate = use_navigate();
|
||||
let loaction = use_location();
|
||||
|
||||
let select_name = create_rw_signal(String::new());
|
||||
create_effect(move |_| {
|
||||
let mut pathname = loaction.pathname.get();
|
||||
let name = if pathname.starts_with("/components/") {
|
||||
pathname.drain(12..).collect()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
select_name.set(name);
|
||||
let select_name = RwSignal::new(String::new());
|
||||
Effect::new(move |_| {
|
||||
select_name.set(loaction.pathname.get());
|
||||
});
|
||||
|
||||
_ = select_name.watch(move |name| {
|
||||
let pathname = loaction.pathname.get_untracked();
|
||||
if !pathname.eq(&format!("/components/{name}")) {
|
||||
navigate(&format!("/components/{name}"), Default::default());
|
||||
if &pathname != name {
|
||||
navigate(name, Default::default());
|
||||
}
|
||||
});
|
||||
view! {
|
||||
|
@ -57,15 +54,30 @@ pub fn ComponentsPage() -> impl IntoView {
|
|||
</Style>
|
||||
<Layout position=LayoutPosition::Absolute>
|
||||
<SiteHeader/>
|
||||
<Layout has_sider=true position=LayoutPosition::Absolute style="top: 64px;">
|
||||
<LayoutSider class="demo-components__sider">
|
||||
<Menu value=select_name>
|
||||
|
||||
{gen_menu_data().into_view()}
|
||||
|
||||
</Menu>
|
||||
</LayoutSider>
|
||||
<Layout content_style="padding: 8px 12px 28px; display: flex;" class="doc-content">
|
||||
<Layout has_sider=true position=LayoutPosition::Absolute attr:style="top: 64px;">
|
||||
<div class="demo-components__sider">
|
||||
<NavDrawer selected_value=select_name>
|
||||
{
|
||||
gen_nav_data().into_iter().map(|data| {
|
||||
let NavGroupOption { label, children } = data;
|
||||
view! {
|
||||
<Caption1Strong style="margin-inline-start: 10px; margin-top: 10px; display: inline-block">
|
||||
{label}
|
||||
</Caption1Strong>
|
||||
{
|
||||
children.into_iter().map(|item| {
|
||||
let NavItemOption { label, value } = item;
|
||||
view! {
|
||||
<NavItem value>{label}</NavItem>
|
||||
}
|
||||
}).collect_view()
|
||||
}
|
||||
}
|
||||
}).collect_view()
|
||||
}
|
||||
</NavDrawer>
|
||||
</div>
|
||||
<Layout content_style="padding: 8px 12px 28px; display: flex;" attr:class=("doc-content", true)>
|
||||
<Outlet/>
|
||||
</Layout>
|
||||
</Layout>
|
||||
|
@ -73,271 +85,227 @@ pub fn ComponentsPage() -> impl IntoView {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MenuGroupOption {
|
||||
pub(crate) struct NavGroupOption {
|
||||
pub label: String,
|
||||
pub children: Vec<MenuItemOption>,
|
||||
pub children: Vec<NavItemOption>,
|
||||
}
|
||||
|
||||
impl IntoView for MenuGroupOption {
|
||||
fn into_view(self) -> View {
|
||||
let Self { label, children } = self;
|
||||
view! {
|
||||
<MenuGroup label=format!(
|
||||
"{label} ({})", children.len()
|
||||
)>
|
||||
|
||||
{children.into_iter().map(|v| v.into_view()).collect_view()}
|
||||
|
||||
</MenuGroup>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MenuItemOption {
|
||||
pub(crate) struct NavItemOption {
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl IntoView for MenuItemOption {
|
||||
fn into_view(self) -> View {
|
||||
let Self { label, value } = self;
|
||||
view! { <MenuItem key=value label/> }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
||||
pub(crate) fn gen_nav_data() -> Vec<NavGroupOption> {
|
||||
vec![
|
||||
MenuGroupOption {
|
||||
label: "Common Components".into(),
|
||||
NavGroupOption {
|
||||
label: "Getting Started".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
value: "avatar".into(),
|
||||
label: "Avatar".into(),
|
||||
NavItemOption {
|
||||
value: "/guide/installation".into(),
|
||||
label: "Installation".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "button".into(),
|
||||
label: "Button".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "card".into(),
|
||||
label: "Card".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "collapse".into(),
|
||||
label: "Collapse".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "divider".into(),
|
||||
label: "Divider".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "dropdown".into(),
|
||||
label: "Dropdown".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "icon".into(),
|
||||
label: "Icon".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "tag".into(),
|
||||
label: "Tag".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "typography".into(),
|
||||
label: "Typography".into(),
|
||||
NavItemOption {
|
||||
value: "/guide/server-sider-rendering".into(),
|
||||
label: "Server Sider Rendering".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
MenuGroupOption {
|
||||
label: "Data Input Components".into(),
|
||||
// NavGroupOption {
|
||||
// label: "Development".into(),
|
||||
// children: vec![
|
||||
// NavItemOption {
|
||||
// value: "/guide/development/components".into(),
|
||||
// label: "Components".into(),
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
NavGroupOption {
|
||||
label: "Components".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
value: "auto-complete".into(),
|
||||
label: "Auto Complete".into(),
|
||||
NavItemOption {
|
||||
value: "/components/accordion".into(),
|
||||
label: "Accordion".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "color-picker".into(),
|
||||
label: "Color Picker".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "checkbox".into(),
|
||||
label: "Checkbox".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "date-picker".into(),
|
||||
label: "Date Picker".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "input".into(),
|
||||
label: "Input".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "input-number".into(),
|
||||
label: "Input Number".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "radio".into(),
|
||||
label: "Radio".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "select".into(),
|
||||
label: "Select".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "slider".into(),
|
||||
label: "Slider".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "switch".into(),
|
||||
label: "Switch".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "time-picker".into(),
|
||||
label: "Time Picker".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "upload".into(),
|
||||
label: "Upload".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
MenuGroupOption {
|
||||
label: "Data Display Components".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
value: "calendar".into(),
|
||||
label: "Calendar".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "image".into(),
|
||||
label: "Image".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "table".into(),
|
||||
label: "Table".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
MenuGroupOption {
|
||||
label: "Navigation Components".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
value: "anchor".into(),
|
||||
NavItemOption {
|
||||
value: "/components/anchor".into(),
|
||||
label: "Anchor".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "back-top".into(),
|
||||
NavItemOption {
|
||||
value: "/components/auto-complete".into(),
|
||||
label: "Auto Complete".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/avatar".into(),
|
||||
label: "Avatar".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/back-top".into(),
|
||||
label: "Back Top".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "breadcrumb".into(),
|
||||
label: "Breadcrumb".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "loading-bar".into(),
|
||||
label: "Loading Bar".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "menu".into(),
|
||||
label: "Menu".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "pagination".into(),
|
||||
label: "Pagination".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "tabs".into(),
|
||||
label: "Tabs".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
MenuGroupOption {
|
||||
label: "Feedback Components".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
value: "alert".into(),
|
||||
label: "Alert".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "badge".into(),
|
||||
NavItemOption {
|
||||
value: "/components/badge".into(),
|
||||
label: "Badge".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "drawer".into(),
|
||||
NavItemOption {
|
||||
value: "/components/breadcrumb".into(),
|
||||
label: "Breadcrumb".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/button".into(),
|
||||
label: "Button".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/calendar".into(),
|
||||
label: "Calendar".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/card".into(),
|
||||
label: "Card".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/checkbox".into(),
|
||||
label: "Checkbox".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/color-picker".into(),
|
||||
label: "Color Picker".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/combobox".into(),
|
||||
label: "Combobox".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/config-provider".into(),
|
||||
label: "Config Provider".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/date-picker".into(),
|
||||
label: "Date Picker".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/dialog".into(),
|
||||
label: "Dialog".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/divider".into(),
|
||||
label: "Divider".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/drawer".into(),
|
||||
label: "Drawer".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "message".into(),
|
||||
label: "Message".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "modal".into(),
|
||||
label: "Modal".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "popover".into(),
|
||||
label: "Popover".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "progress".into(),
|
||||
label: "Progress".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "spinner".into(),
|
||||
label: "Spinner".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "skeleton".into(),
|
||||
label: "Skeleton".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
MenuGroupOption {
|
||||
label: "Layout Components".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
value: "layout".into(),
|
||||
label: "Layout".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "grid".into(),
|
||||
NavItemOption {
|
||||
value: "/components/grid".into(),
|
||||
label: "Grid".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "space".into(),
|
||||
NavItemOption {
|
||||
value: "/components/icon".into(),
|
||||
label: "Icon".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/image".into(),
|
||||
label: "Image".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/input".into(),
|
||||
label: "Input".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/layout".into(),
|
||||
label: "Layout".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/loading-bar".into(),
|
||||
label: "Loading Bar".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/menu".into(),
|
||||
label: "Menu".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/message-bar".into(),
|
||||
label: "Message Bar".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/nav".into(),
|
||||
label: "Nav".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/pagination".into(),
|
||||
label: "Pagination".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/popover".into(),
|
||||
label: "Popover".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/progress-bar".into(),
|
||||
label: "ProgressBar".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/radio".into(),
|
||||
label: "Radio".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/scrollbar".into(),
|
||||
label: "Scrollbar".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/skeleton".into(),
|
||||
label: "Skeleton".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/slider".into(),
|
||||
label: "Slider".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/space".into(),
|
||||
label: "Space".into(),
|
||||
},
|
||||
],
|
||||
NavItemOption {
|
||||
value: "/components/spin-button".into(),
|
||||
label: "Spin Button".into(),
|
||||
},
|
||||
MenuGroupOption {
|
||||
label: "Utility Components".into(),
|
||||
children: vec![MenuItemOption {
|
||||
value: "scrollbar".into(),
|
||||
label: "Scrollbar".into(),
|
||||
}],
|
||||
NavItemOption {
|
||||
value: "/components/spinner".into(),
|
||||
label: "Spinner".into(),
|
||||
},
|
||||
MenuGroupOption {
|
||||
label: "Config Components".into(),
|
||||
children: vec![MenuItemOption {
|
||||
value: "theme".into(),
|
||||
label: "Theme".into(),
|
||||
}],
|
||||
NavItemOption {
|
||||
value: "/components/switch".into(),
|
||||
label: "Switch".into(),
|
||||
},
|
||||
MenuGroupOption {
|
||||
label: "Mobile Components".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
value: "nav-bar".into(),
|
||||
label: "Nav Bar".into(),
|
||||
NavItemOption {
|
||||
value: "/components/tab-list".into(),
|
||||
label: "Tab List".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "tabbar".into(),
|
||||
label: "Tabbar".into(),
|
||||
NavItemOption {
|
||||
value: "/components/table".into(),
|
||||
label: "Table".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "toast".into(),
|
||||
NavItemOption {
|
||||
value: "/components/tag".into(),
|
||||
label: "Tag".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/text".into(),
|
||||
label: "Text".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/textarea".into(),
|
||||
label: "Textarea".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/time-picker".into(),
|
||||
label: "Time Picker".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/toast".into(),
|
||||
label: "Toast".into(),
|
||||
},
|
||||
NavItemOption {
|
||||
value: "/components/upload".into(),
|
||||
label: "Upload".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
|
@ -1,141 +0,0 @@
|
|||
use crate::components::SiteHeader;
|
||||
use leptos::*;
|
||||
use leptos_meta::Style;
|
||||
use leptos_router::{use_location, use_navigate, Outlet};
|
||||
use thaw::*;
|
||||
|
||||
#[component]
|
||||
pub fn GuidePage() -> impl IntoView {
|
||||
let navigate = use_navigate();
|
||||
let selected = create_rw_signal({
|
||||
let loaction = use_location();
|
||||
let mut pathname = loaction.pathname.get_untracked();
|
||||
|
||||
if pathname.starts_with("/guide/") {
|
||||
pathname.drain(7..).collect()
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
});
|
||||
|
||||
create_effect(move |value| {
|
||||
let selected = selected.get();
|
||||
if value.is_some() {
|
||||
navigate(&format!("/guide/{selected}"), Default::default());
|
||||
}
|
||||
selected
|
||||
});
|
||||
view! {
|
||||
<Style>
|
||||
"
|
||||
.demo-components__component {
|
||||
width: 896px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.demo-components__toc {
|
||||
width: 190px;
|
||||
margin: 12px 2px 12px 12px;
|
||||
}
|
||||
.demo-components__toc > .thaw-anchor {
|
||||
position: sticky;
|
||||
top: 36px;
|
||||
}
|
||||
.demo-md-table-box {
|
||||
overflow: auto;
|
||||
}
|
||||
@media screen and (max-width: 1200px) {
|
||||
.demo-components__toc,
|
||||
.demo-guide__sider {
|
||||
display: none;
|
||||
}
|
||||
.demo-components__component {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
"
|
||||
</Style>
|
||||
<Layout position=LayoutPosition::Absolute>
|
||||
<SiteHeader/>
|
||||
<Layout has_sider=true position=LayoutPosition::Absolute style="top: 64px;">
|
||||
<LayoutSider class="demo-guide__sider">
|
||||
<Menu value=selected>
|
||||
|
||||
{gen_guide_menu_data().into_view()}
|
||||
|
||||
</Menu>
|
||||
</LayoutSider>
|
||||
<Layout content_style="padding: 8px 12px 28px; display: flex;" class="doc-content">
|
||||
<Outlet/>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Layout>
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MenuGroupOption {
|
||||
pub label: String,
|
||||
pub children: Vec<MenuItemOption>,
|
||||
}
|
||||
|
||||
impl IntoView for MenuGroupOption {
|
||||
fn into_view(self) -> View {
|
||||
let Self { label, children } = self;
|
||||
view! {
|
||||
<MenuGroup label=label>
|
||||
|
||||
{children.into_iter().map(|v| v.into_view()).collect_view()}
|
||||
|
||||
</MenuGroup>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct MenuItemOption {
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl IntoView for MenuItemOption {
|
||||
fn into_view(self) -> View {
|
||||
let Self { label, value } = self;
|
||||
view! { <MenuItem key=value label/> }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gen_guide_menu_data() -> Vec<MenuGroupOption> {
|
||||
vec![
|
||||
MenuGroupOption {
|
||||
label: "Getting Started".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
value: "installation".into(),
|
||||
label: "Installation".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "usage".into(),
|
||||
label: "Usage".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
MenuGroupOption {
|
||||
label: "Guides".into(),
|
||||
children: vec![MenuItemOption {
|
||||
value: "server-sider-rendering".into(),
|
||||
label: "Server Sider Rendering".into(),
|
||||
}],
|
||||
},
|
||||
MenuGroupOption {
|
||||
label: "Development".into(),
|
||||
children: vec![
|
||||
MenuItemOption {
|
||||
value: "development/guide".into(),
|
||||
label: "Guide".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "development/components".into(),
|
||||
label: "Components".into(),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
|
@ -1,14 +1,15 @@
|
|||
use leptos::*;
|
||||
use leptos_router::{use_navigate, use_query_map};
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::hooks::{use_navigate, use_query_map};
|
||||
use thaw::*;
|
||||
|
||||
#[component]
|
||||
pub fn Home() -> impl IntoView {
|
||||
let query_map = use_query_map().get_untracked();
|
||||
let navigate = use_navigate();
|
||||
|
||||
// mobile page
|
||||
if let Some(path) = query_map.get("path") {
|
||||
let navigate = use_navigate();
|
||||
navigate(path, Default::default());
|
||||
navigate(&path, Default::default());
|
||||
}
|
||||
view! {
|
||||
<Layout
|
||||
|
@ -18,12 +19,16 @@ pub fn Home() -> impl IntoView {
|
|||
<h1 style="font-size: 80px; line-height: 1;margin: 0 0 18px;">"Thaw UI"</h1>
|
||||
<p>"An easy to use leptos component library"</p>
|
||||
<Space>
|
||||
<Button on_click=move |_| {
|
||||
let navigate = use_navigate();
|
||||
navigate("/components/button", Default::default());
|
||||
}>"Read the docs"</Button>
|
||||
<Button
|
||||
variant=ButtonVariant::Text
|
||||
appearance=ButtonAppearance::Primary
|
||||
on_click=move |_| {
|
||||
navigate("/components/button", Default::default());
|
||||
}
|
||||
>
|
||||
"Read the docs"
|
||||
</Button>
|
||||
<Button
|
||||
appearance=ButtonAppearance::Subtle
|
||||
on_click=move |_| {
|
||||
_ = window().open_with_url("http://github.com/thaw-ui/thaw");
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::components::{Demo, DemoCode};
|
||||
use leptos::*;
|
||||
use leptos::{ev, prelude::*};
|
||||
use thaw::*;
|
||||
|
||||
demo_markdown::include_md! {}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
mod components;
|
||||
mod guide;
|
||||
mod home;
|
||||
mod markdown;
|
||||
mod mobile;
|
||||
mod nav_bar;
|
||||
mod tabbar;
|
||||
mod toast;
|
||||
// mod mobile;
|
||||
// mod nav_bar;
|
||||
// mod tabbar;
|
||||
// mod toast;
|
||||
|
||||
pub use components::*;
|
||||
pub use guide::*;
|
||||
pub use home::*;
|
||||
pub use markdown::*;
|
||||
pub use mobile::*;
|
||||
pub use nav_bar::*;
|
||||
pub use tabbar::*;
|
||||
pub use toast::*;
|
||||
// pub use mobile::*;
|
||||
// pub use nav_bar::*;
|
||||
// pub use tabbar::*;
|
||||
// pub use toast::*;
|
||||
|
|
|
@ -4,6 +4,7 @@ use leptos::*;
|
|||
use thaw::mobile::{NavBar, NavBarRight};
|
||||
use thaw::Icon;
|
||||
|
||||
|
||||
#[component]
|
||||
pub fn NavBarPage() -> impl IntoView {
|
||||
view! {
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# Development guide
|
||||
|
||||
### Code style
|
||||
|
||||
It is recommended to use the Rust style instead of the functional style in the newly added reactive code.
|
||||
|
||||
```rust
|
||||
RwSignal::new(12) // ✅
|
||||
create_rw_signal(12) // 🙅
|
||||
|
||||
Memo::new(|_| {}) // ✅
|
||||
create_memo(|_| {}) // 🙅
|
||||
|
||||
Effect::new(|_| {}) // ✅
|
||||
create_effect(|_| {}) // 🙅
|
||||
```
|
|
@ -1,7 +1,40 @@
|
|||
# Installation
|
||||
## Installation
|
||||
|
||||
Installation thaw
|
||||
|
||||
```shell
|
||||
cargo add thaw --features=csr
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
You just need to import thaw and use it.
|
||||
|
||||
```rust
|
||||
// Import all
|
||||
use thaw::*;
|
||||
// Import on Demand
|
||||
use thaw::{Button, ButtonAppearance};
|
||||
```
|
||||
|
||||
A small example:
|
||||
|
||||
```rust
|
||||
use leptos::prelude::*;
|
||||
use thaw::*;
|
||||
|
||||
fn main() {
|
||||
mount_to_body(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
view! {
|
||||
<ConfigProvider>
|
||||
<Button appearance=ButtonAppearance::Primary>
|
||||
"Primary"
|
||||
</Button>
|
||||
</ConfigProvider>
|
||||
}
|
||||
}
|
||||
```
|
|
@ -12,7 +12,9 @@ To enable the hydrate mode, the following configurations are required:
|
|||
thaw = { ..., features = ["hydrate"] }
|
||||
```
|
||||
|
||||
Remember to add thaw to your `Cargo.toml` file in the corresponding feature, e.g.
|
||||
### cargo-leptos
|
||||
|
||||
if you use [cargo-leptos](https://github.com/leptos-rs/cargo-leptos), Remember to add thaw to your `Cargo.toml` file in the corresponding feature, e.g.
|
||||
|
||||
```toml
|
||||
[features]
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
# Usage
|
||||
|
||||
You just need to import thaw and use it.
|
||||
|
||||
```rust
|
||||
// Import all
|
||||
use thaw::*;
|
||||
// Import on Demand
|
||||
use thaw::{Button, ButtonVariant};
|
||||
```
|
||||
|
||||
A small example:
|
||||
|
||||
```rust
|
||||
use leptos::*;
|
||||
use thaw::*;
|
||||
|
||||
fn main() {
|
||||
mount_to_body(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
view! {
|
||||
<Button variant=ButtonVariant::Primary>"Primary"</Button>
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,26 +0,0 @@
|
|||
# Alert
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Alert variant=AlertVariant::Success title="title">
|
||||
"success"
|
||||
</Alert>
|
||||
<Alert variant=AlertVariant::Warning title="title">
|
||||
"warning"
|
||||
</Alert>
|
||||
<Alert variant=AlertVariant::Error title="title">
|
||||
"error"
|
||||
</Alert>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Alert Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------- | ----------------------------------- | -------------------- | ----------------------------------------- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Additional classes for the alert element. |
|
||||
| title | `Option<MaybeSignal<String>>` | `Default::default()` | Title of the alert. |
|
||||
| variant | `MaybeSignal<AlertVariant>` | | Alert variant. |
|
||||
| children | `Children` | | The content of the alert. |
|
|
@ -1,23 +1,30 @@
|
|||
# Auto Complete
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(String::new());
|
||||
let options = create_memo(move |_| {
|
||||
let value = RwSignal::new(String::new());
|
||||
let options = Memo::<Vec<_>>::new(move |_| {
|
||||
let prefix = value
|
||||
.get()
|
||||
.split_once('@')
|
||||
.map_or(value.get(), |v| v.0.to_string());
|
||||
vec!["@gmail.com", "@163.com"]
|
||||
.into_iter()
|
||||
.map(|suffix| AutoCompleteOption {
|
||||
label: format!("{prefix}{suffix}"),
|
||||
value: format!("{prefix}{suffix}"),
|
||||
})
|
||||
.map(|suffix| (format!("{prefix}{suffix}"), format!("{prefix}{suffix}")))
|
||||
.collect()
|
||||
});
|
||||
|
||||
view! {
|
||||
<AutoComplete value options placeholder="Email"/>
|
||||
<AutoComplete value placeholder="Email">
|
||||
<For
|
||||
each=move || options.get()
|
||||
key=|option| option.0.clone()
|
||||
let:option
|
||||
>
|
||||
<AutoCompleteOption value=option.0>
|
||||
{option.1}
|
||||
</AutoCompleteOption>
|
||||
</For>
|
||||
</AutoComplete>
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -29,14 +36,6 @@ view! {
|
|||
}
|
||||
```
|
||||
|
||||
### Invalid
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<AutoComplete placeholder="Email" invalid=true/>
|
||||
}
|
||||
```
|
||||
|
||||
### Prefix & Suffix
|
||||
|
||||
```rust demo
|
||||
|
|
|
@ -2,10 +2,53 @@
|
|||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<Avatar />
|
||||
}
|
||||
```
|
||||
|
||||
### Name
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Avatar name="Ashley McCarthy" />
|
||||
}
|
||||
```
|
||||
|
||||
### Image
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Avatar src="https://s3.bmp.ovh/imgs/2021/10/723d457d627fe706.jpg" />
|
||||
<Avatar src="https://s3.bmp.ovh/imgs/2021/10/723d457d627fe706.jpg" round=true/>
|
||||
<Avatar src="https://s3.bmp.ovh/imgs/2021/10/723d457d627fe706.jpg" size=50/>
|
||||
}
|
||||
```
|
||||
|
||||
### Shape
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Avatar shape=AvatarShape::Square />
|
||||
}
|
||||
```
|
||||
|
||||
### Size
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<Avatar initials="16" size=16 />
|
||||
<Avatar initials="20" size=20 />
|
||||
<Avatar initials="24" size=24 />
|
||||
<Avatar initials="28" size=28 />
|
||||
<Avatar initials="32" size=32 />
|
||||
<Avatar initials="36" size=36 />
|
||||
<Avatar initials="40" size=40 />
|
||||
<Avatar initials="48" size=48 />
|
||||
<Avatar initials="56" size=56 />
|
||||
<Avatar initials="64" size=64 />
|
||||
<Avatar initials="72" size=72 />
|
||||
<Avatar initials="96" size=96 />
|
||||
<Avatar initials="120" size=120 />
|
||||
<Avatar initials="128" size=128 />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,33 +1,92 @@
|
|||
# Badge
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0);
|
||||
view! {
|
||||
<Badge />
|
||||
}
|
||||
```
|
||||
|
||||
### Appearance
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<Badge value max=10>
|
||||
<Avatar/>
|
||||
</Badge>
|
||||
<Badge variant=BadgeVariant::Success value max=10>
|
||||
<Avatar/>
|
||||
</Badge>
|
||||
<Badge variant=BadgeVariant::Warning value max=10>
|
||||
<Avatar/>
|
||||
</Badge>
|
||||
<Badge variant=BadgeVariant::Warning dot=true>
|
||||
<Avatar/>
|
||||
</Badge>
|
||||
<Button on_click=move |_| value.update(|v| *v += 1)>"+1"</Button>
|
||||
<Button on_click=move |_| {
|
||||
value
|
||||
.update(|v| {
|
||||
if *v != 0 {
|
||||
*v -= 1;
|
||||
<Badge appearance=BadgeAppearance::Filled>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Ghost>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Outline>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Tint>"999+"</Badge>
|
||||
</Space>
|
||||
}
|
||||
})
|
||||
}>"-1"</Button>
|
||||
"value:"
|
||||
{move || value.get()}
|
||||
```
|
||||
|
||||
### Sizes
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<Badge size=BadgeSize::Tiny/>
|
||||
<Badge size=BadgeSize::ExtraSmall/>
|
||||
<Badge size=BadgeSize::Small/>
|
||||
<Badge size=BadgeSize::Medium/>
|
||||
<Badge size=BadgeSize::Large/>
|
||||
<Badge size=BadgeSize::ExtraLarge/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Color
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Space>
|
||||
<Badge appearance=BadgeAppearance::Filled color=BadgeColor::Brand>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Ghost color=BadgeColor::Brand>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Outline color=BadgeColor::Brand>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Tint color=BadgeColor::Brand>"999+"</Badge>
|
||||
</Space>
|
||||
<Space>
|
||||
<Badge appearance=BadgeAppearance::Filled color=BadgeColor::Danger>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Ghost color=BadgeColor::Danger>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Outline color=BadgeColor::Danger>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Tint color=BadgeColor::Danger>"999+"</Badge>
|
||||
</Space>
|
||||
<Space>
|
||||
<Badge appearance=BadgeAppearance::Filled color=BadgeColor::Important>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Ghost color=BadgeColor::Important>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Outline color=BadgeColor::Important>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Tint color=BadgeColor::Important>"999+"</Badge>
|
||||
</Space>
|
||||
<Space>
|
||||
<Badge appearance=BadgeAppearance::Filled color=BadgeColor::Informative>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Ghost color=BadgeColor::Informative>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Outline color=BadgeColor::Informative>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Tint color=BadgeColor::Informative>"999+"</Badge>
|
||||
</Space>
|
||||
<Space>
|
||||
<Badge appearance=BadgeAppearance::Filled color=BadgeColor::Severe>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Ghost color=BadgeColor::Severe>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Outline color=BadgeColor::Severe>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Tint color=BadgeColor::Severe>"999+"</Badge>
|
||||
</Space>
|
||||
<Space>
|
||||
<Badge appearance=BadgeAppearance::Filled color=BadgeColor::Subtle>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Ghost color=BadgeColor::Subtle>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Outline color=BadgeColor::Subtle>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Tint color=BadgeColor::Subtle>"999+"</Badge>
|
||||
</Space>
|
||||
<Space>
|
||||
<Badge appearance=BadgeAppearance::Filled color=BadgeColor::Success>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Ghost color=BadgeColor::Success>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Outline color=BadgeColor::Success>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Tint color=BadgeColor::Success>"999+"</Badge>
|
||||
</Space>
|
||||
<Space>
|
||||
<Badge appearance=BadgeAppearance::Filled color=BadgeColor::Warning>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Ghost color=BadgeColor::Warning>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Outline color=BadgeColor::Warning>"999+"</Badge>
|
||||
<Badge appearance=BadgeAppearance::Tint color=BadgeColor::Warning>"999+"</Badge>
|
||||
</Space>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
|
|
@ -3,21 +3,17 @@
|
|||
```rust demo
|
||||
view! {
|
||||
<Breadcrumb>
|
||||
<BreadcrumbItem>"Leptos"</BreadcrumbItem>
|
||||
<BreadcrumbItem>"UI"</BreadcrumbItem>
|
||||
<BreadcrumbItem>"Thaw"</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
}
|
||||
```
|
||||
|
||||
### Separator
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Breadcrumb separator=">">
|
||||
<BreadcrumbItem>"Leptos"</BreadcrumbItem>
|
||||
<BreadcrumbItem>"UI"</BreadcrumbItem>
|
||||
<BreadcrumbItem>"Thaw"</BreadcrumbItem>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbButton>"Leptos"</BreadcrumbButton>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbDivider />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbButton>"UI"</BreadcrumbButton>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbDivider />
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbButton current=true>"Thaw"</BreadcrumbButton>
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
}
|
||||
```
|
||||
|
|
|
@ -3,23 +3,22 @@
|
|||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<Button variant=ButtonVariant::Primary>"Primary"</Button>
|
||||
<Button variant=ButtonVariant::Outlined>"Outlined"</Button>
|
||||
<Button variant=ButtonVariant::Text>"Text"</Button>
|
||||
<Button variant=ButtonVariant::Link>"Link"</Button>
|
||||
<Button>"Secondary"</Button>
|
||||
<Button appearance=ButtonAppearance::Primary>"Primary"</Button>
|
||||
<Button appearance=ButtonAppearance::Subtle>"Subtle"</Button>
|
||||
<Button appearance=ButtonAppearance::Transparent>"Transparent"</Button>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Color
|
||||
### Shape
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<Button color=ButtonColor::Primary>"Primary Color"</Button>
|
||||
<Button color=ButtonColor::Success>"Success Color"</Button>
|
||||
<Button color=ButtonColor::Warning>"Warning Color"</Button>
|
||||
<Button color=ButtonColor::Error>"Error Color"</Button>
|
||||
<Button>"Rounded"</Button>
|
||||
<Button shape=ButtonShape::Circular>"Circular"</Button>
|
||||
<Button shape=ButtonShape::Square>"Square"</Button>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
@ -27,7 +26,7 @@ view! {
|
|||
### Icon
|
||||
|
||||
```rust demo
|
||||
let icon = create_rw_signal(Some(icondata::AiCloseOutlined));
|
||||
let icon = RwSignal::new(Some(icondata::AiCloseOutlined));
|
||||
|
||||
let on_click = move |_| {
|
||||
icon.update(|icon| {
|
||||
|
@ -55,70 +54,12 @@ view! {
|
|||
</Button>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button color=ButtonColor::Error icon=icondata::AiCloseOutlined>
|
||||
<Button icon=icondata::AiCloseOutlined>
|
||||
"Error Color Icon"
|
||||
</Button>
|
||||
<Button color=ButtonColor::Error icon=icondata::AiCloseOutlined>
|
||||
"Error Color Icon"
|
||||
</Button>
|
||||
<Button
|
||||
color=ButtonColor::Error
|
||||
icon=icondata::AiCloseOutlined
|
||||
round=true
|
||||
/>
|
||||
<Button
|
||||
color=ButtonColor::Error
|
||||
icon=icondata::AiCloseOutlined
|
||||
circle=true
|
||||
/>
|
||||
</Space>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Loading
|
||||
|
||||
```rust demo
|
||||
let loading = create_rw_signal(false);
|
||||
let on_click = move |_| {
|
||||
loading.set(true);
|
||||
set_timeout(
|
||||
move || {
|
||||
loading.set(false);
|
||||
},
|
||||
std::time::Duration::new(2, 0),
|
||||
);
|
||||
};
|
||||
|
||||
view! {
|
||||
<Space>
|
||||
<Button loading on_click icon=icondata::AiCloseOutlined>
|
||||
"Click Me"
|
||||
</Button>
|
||||
<Button loading on_click>
|
||||
"Click Me"
|
||||
<Button icon=icondata::AiCloseOutlined>
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<Button variant=ButtonVariant::Primary disabled=true>
|
||||
"Primary"
|
||||
</Button>
|
||||
<Button variant=ButtonVariant::Outlined disabled=true>
|
||||
"Outlined"
|
||||
</Button>
|
||||
<Button variant=ButtonVariant::Text disabled=true>
|
||||
"Text"
|
||||
</Button>
|
||||
<Button variant=ButtonVariant::Link disabled=true>
|
||||
"Link"
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
@ -127,11 +68,68 @@ view! {
|
|||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Space>
|
||||
<Button size=ButtonSize::Tiny>"Primary"</Button>
|
||||
<Button size=ButtonSize::Small>"Primary"</Button>
|
||||
<Button size=ButtonSize::Medium>"Primary"</Button>
|
||||
<Button size=ButtonSize::Large>"Primary"</Button>
|
||||
<Button size=ButtonSize::Small>"Small"</Button>
|
||||
<Button size=ButtonSize::Small icon=icondata::AiCloseOutlined>
|
||||
"Small with calendar icon"
|
||||
</Button>
|
||||
<Button size=ButtonSize::Small icon=icondata::AiCloseOutlined>
|
||||
</Button>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button>"Medium"</Button>
|
||||
<Button icon=icondata::AiCloseOutlined>
|
||||
"Medium with calendar icon"
|
||||
</Button>
|
||||
<Button icon=icondata::AiCloseOutlined>
|
||||
</Button>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button size=ButtonSize::Large>"Large"</Button>
|
||||
<Button size=ButtonSize::Large icon=icondata::AiCloseOutlined>
|
||||
"Large with calendar icon"
|
||||
</Button>
|
||||
<Button size=ButtonSize::Large icon=icondata::AiCloseOutlined>
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Space>
|
||||
<Button disabled=true>
|
||||
"Secondary"
|
||||
</Button>
|
||||
<Button appearance=ButtonAppearance::Primary disabled=true>
|
||||
"Primary"
|
||||
</Button>
|
||||
<Button appearance=ButtonAppearance::Subtle disabled=true>
|
||||
"Subtle"
|
||||
</Button>
|
||||
<Button appearance=ButtonAppearance::Transparent disabled=true>
|
||||
"Transparent"
|
||||
</Button>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button disabled_focusable=true>
|
||||
"Secondary"
|
||||
</Button>
|
||||
<Button appearance=ButtonAppearance::Primary disabled_focusable=true>
|
||||
"Primary"
|
||||
</Button>
|
||||
<Button appearance=ButtonAppearance::Subtle disabled_focusable=true>
|
||||
"Subtle"
|
||||
</Button>
|
||||
<Button appearance=ButtonAppearance::Transparent disabled_focusable=true>
|
||||
"Transparent"
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
@ -140,9 +138,7 @@ view! {
|
|||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Button block=true>"Primary"</Button>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -152,14 +148,14 @@ view! {
|
|||
view! {
|
||||
<Space>
|
||||
<ButtonGroup>
|
||||
<Button variant=ButtonVariant::Outlined>"Outlined"</Button>
|
||||
<Button variant=ButtonVariant::Outlined>"Outlined"</Button>
|
||||
<Button variant=ButtonVariant::Outlined>"Outlined"</Button>
|
||||
<Button>"Outlined"</Button>
|
||||
<Button>"Outlined"</Button>
|
||||
<Button>"Outlined"</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup vertical=true>
|
||||
<Button variant=ButtonVariant::Outlined>"Outlined"</Button>
|
||||
<Button variant=ButtonVariant::Outlined>"Outlined"</Button>
|
||||
<Button variant=ButtonVariant::Outlined>"Outlined"</Button>
|
||||
<Button>"Outlined"</Button>
|
||||
<Button>"Outlined"</Button>
|
||||
<Button>"Outlined"</Button>
|
||||
</ButtonGroup>
|
||||
</Space>
|
||||
}
|
||||
|
@ -171,7 +167,7 @@ view! {
|
|||
| --- | --- | --- | --- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Additional classes for the button element. |
|
||||
| style | `Option<MaybeSignal<String>>` | `Default::default()` | Button's style. |
|
||||
| variant | `MaybeSignal<ButtonVariant>` | `ButtonVariant::Primary` | Button's variant. |
|
||||
| appearance | `MaybeSignal<ButtonAppearance>` | `ButtonAppearance::Primary` | Button's variant. |
|
||||
| color | `MaybeSignal<ButtonColor>` | `ButtonColor::Primary` | Button's color. |
|
||||
| block | `MaybeSignal<bool>` | `false` | Whether the button is displayed as block. |
|
||||
| round | `MaybeSignal<bool>` | `false` | Whether the button shows rounded corners. |
|
||||
|
|
|
@ -2,10 +2,14 @@
|
|||
|
||||
```rust demo
|
||||
use chrono::prelude::*;
|
||||
let value = create_rw_signal(Some(Local::now().date_naive()));
|
||||
let value = RwSignal::new(Local::now().date_naive());
|
||||
let option_value = RwSignal::new(Some(Local::now().date_naive()));
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Calendar value />
|
||||
<Calendar value=option_value />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -2,22 +2,28 @@
|
|||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Card title="title">"content"</Card>
|
||||
<Card title="title">
|
||||
<CardHeaderExtra slot>"header-extra"</CardHeaderExtra>
|
||||
"content"
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Body1>
|
||||
<b>"Header"</b>" 2022-02-22"
|
||||
</Body1>
|
||||
<CardHeaderDescription slot>
|
||||
<Caption1>"Description"</Caption1>
|
||||
</CardHeaderDescription>
|
||||
<CardHeaderAction slot>
|
||||
<Button appearance=ButtonAppearance::Transparent>
|
||||
"..."
|
||||
</Button>
|
||||
</CardHeaderAction>
|
||||
</CardHeader>
|
||||
<CardPreview>
|
||||
<img src="https://s3.bmp.ovh/imgs/2021/10/2c3b013418d55659.jpg" style="width: 100%"/>
|
||||
</CardPreview>
|
||||
<CardFooter>
|
||||
<Button>"Reply"</Button>
|
||||
<Button>"Share"</Button>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Card title="title">
|
||||
<CardHeader slot>"header"</CardHeader>
|
||||
"content"
|
||||
</Card>
|
||||
<Card title="title">
|
||||
<CardHeaderExtra slot>"header-extra"</CardHeaderExtra>
|
||||
"content"
|
||||
<CardFooter slot>"footer"</CardFooter>
|
||||
</Card>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
# Checkbox
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(false);
|
||||
let checked = RwSignal::new(false);
|
||||
|
||||
view! {
|
||||
<Checkbox value>"Click"</Checkbox>
|
||||
<Checkbox checked label="Click"/>
|
||||
<Checkbox />
|
||||
}
|
||||
```
|
||||
|
@ -14,13 +14,13 @@ view! {
|
|||
```rust demo
|
||||
use std::collections::HashSet;
|
||||
|
||||
let value = create_rw_signal(HashSet::new());
|
||||
let value = RwSignal::new(HashSet::new());
|
||||
|
||||
view! {
|
||||
<CheckboxGroup value>
|
||||
<CheckboxItem label="apple" key="a"/>
|
||||
<CheckboxItem label="b" key="b"/>
|
||||
<CheckboxItem label="c" key="c"/>
|
||||
<Checkbox label="apple" value="a"/>
|
||||
<Checkbox label="b" value="b"/>
|
||||
<Checkbox label="c" value="c"/>
|
||||
</CheckboxGroup>
|
||||
<div style="margin-top: 1rem">"value: " {move || format!("{:?}", value.get())}</div>
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
# Collapse
|
||||
|
||||
```rust demo
|
||||
use std::collections::HashSet;
|
||||
|
||||
let value = create_rw_signal(HashSet::from(["thaw".to_string()]));
|
||||
|
||||
view! {
|
||||
<Collapse value>
|
||||
<CollapseItem title="Leptos" key="leptos">
|
||||
"Build fast web applications with Rust."
|
||||
</CollapseItem>
|
||||
<CollapseItem title="Thaw" key="thaw">
|
||||
"An easy to use leptos component library"
|
||||
</CollapseItem>
|
||||
</Collapse>
|
||||
}
|
||||
```
|
||||
|
||||
### Accordion
|
||||
|
||||
Like an accordion.
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Collapse accordion=true>
|
||||
<CollapseItem title="Leptos" key="leptos">
|
||||
"Build fast web applications with Rust."
|
||||
</CollapseItem>
|
||||
<CollapseItem title="Thaw" key="thaw">
|
||||
"An easy to use leptos component library."
|
||||
</CollapseItem>
|
||||
<CollapseItem title="Naive UI" key="naive-ui">
|
||||
"A Vue 3 Component Library. Fairly Complete. Theme Customizable. Uses TypeScript. Fast."
|
||||
</CollapseItem>
|
||||
</Collapse>
|
||||
}
|
||||
```
|
||||
|
||||
### Collapse Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --------- | ----------------------------------- | -------------------- | ------------------------------------------- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the collapse element. |
|
||||
| value | `Model<HashSet<String>>` | `Default::default()` | Currently active panel. |
|
||||
| accordion | `bool` | `false` | Only allow one panel open at a time. |
|
||||
| children | `Children` | | Collapse's content. |
|
||||
|
||||
### CollapseItem Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the collapse item element. |
|
||||
| title | `MaybeSignal<String>` | | The title of the CollapseItem. |
|
||||
| key | `MaybeSignal<String>` | | The indentifier of CollapseItem. |
|
||||
| chilren | `Children` | | CollapseItem's content. |
|
|
@ -3,7 +3,7 @@
|
|||
```rust demo
|
||||
use palette::Srgb;
|
||||
|
||||
let value = create_rw_signal(Color::from(Srgb::new(0.0, 0.0, 0.0)));
|
||||
let value = RwSignal::new(Color::from(Srgb::new(0.0, 0.0, 0.0)));
|
||||
|
||||
view! {
|
||||
<ColorPicker value/>
|
||||
|
@ -17,9 +17,9 @@ Encoding formats, support RGB, HSV, HSL.
|
|||
```rust demo
|
||||
use palette::{Hsl, Hsv, Srgb};
|
||||
|
||||
let rgb = create_rw_signal(Color::from(Srgb::new(0.0, 0.0, 0.0)));
|
||||
let hsv = create_rw_signal(Color::from(Hsv::new(0.0, 0.0, 0.0)));
|
||||
let hsl = create_rw_signal(Color::from(Hsl::new(0.0, 0.0, 0.0)));
|
||||
let rgb = RwSignal::new(Color::from(Srgb::new(0.0, 0.0, 0.0)));
|
||||
let hsv = RwSignal::new(Color::from(Hsv::new(0.0, 0.0, 0.0)));
|
||||
let hsl = RwSignal::new(Color::from(Hsl::new(0.0, 0.0, 0.0)));
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
|
|
48
demo_markdown/docs/config_provider/mod.md
Normal file
48
demo_markdown/docs/config_provider/mod.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# ConfigProvider
|
||||
|
||||
### Theme
|
||||
|
||||
```rust demo
|
||||
let theme = RwSignal::new(Theme::light());
|
||||
|
||||
view! {
|
||||
<ConfigProvider theme>
|
||||
<Card>
|
||||
<Space>
|
||||
<Button on_click=move |_| theme.set(Theme::light())>"Light"</Button>
|
||||
<Button on_click=move |_| theme.set(Theme::dark())>"Dark"</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
</ConfigProvider>
|
||||
}
|
||||
```
|
||||
|
||||
### Customize Theme
|
||||
|
||||
```rust demo
|
||||
let theme = RwSignal::new(Theme::light());
|
||||
let on_customize_theme = move |_| {
|
||||
theme.update(|theme| {
|
||||
theme.color.color_brand_background = "#f5222d".to_string();
|
||||
theme.color.color_brand_background_hover = "#ff4d4f".to_string();
|
||||
theme.color.color_brand_background_pressed = "#cf1322".to_string();
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<ConfigProvider theme>
|
||||
<Card>
|
||||
<Space>
|
||||
<Button appearance=ButtonAppearance::Primary on_click=move |_| theme.set(Theme::light())>"Light"</Button>
|
||||
<Button appearance=ButtonAppearance::Primary on_click=on_customize_theme>"Customize Theme"</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
</ConfigProvider>
|
||||
}
|
||||
```
|
||||
|
||||
### ConfigProvider Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ----- | ------------------------- | -------------------- | ----------- |
|
||||
| theme | `Option<RwSignal<Theme>>` | `Default::default()` | Theme. |
|
|
@ -2,10 +2,14 @@
|
|||
|
||||
```rust demo
|
||||
use chrono::prelude::*;
|
||||
let value = create_rw_signal(Some(Local::now().date_naive()));
|
||||
let value = RwSignal::new(Local::now().date_naive());
|
||||
let option_value = RwSignal::new(Some(Local::now().date_naive()));
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<DatePicker value/>
|
||||
<DatePicker value=option_value/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
# Modal
|
||||
# Dialog
|
||||
|
||||
```rust demo
|
||||
let show = create_rw_signal(false);
|
||||
let open = RwSignal::new(false);
|
||||
|
||||
view! {
|
||||
<Button on_click=move |_| show.set(true)>"Open Modal"</Button>
|
||||
<Modal title="title" show>
|
||||
"hello"
|
||||
</Modal>
|
||||
<Button on_click=move |_| open.set(true)>"Open Dialog"</Button>
|
||||
<Dialog open>
|
||||
<DialogSurface>
|
||||
<DialogBody>
|
||||
<DialogTitle>"Dialog title"</DialogTitle>
|
||||
<DialogContent>
|
||||
"Dialog body"
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button appearance=ButtonAppearance::Primary>"Do Something"</Button>
|
||||
</DialogActions>
|
||||
</DialogBody>
|
||||
</DialogSurface>
|
||||
</Dialog>
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -15,14 +25,12 @@ view! {
|
|||
|
||||
| Name | Type | Default | Description |
|
||||
| -------------- | --------------------- | -------------------- | ------------------------------------------- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the modal element. |
|
||||
| show | `Model<bool>` | | Whether to show modal. |
|
||||
| title | `MaybeSignal<String>` | `Default::default()` | Modal title. |
|
||||
| width | `MaybeSignal<String>` | `600px` | Modal width. |
|
||||
| z_index | `MaybeSignal<i16>` | `2000` | z-index of the modal. |
|
||||
| mask_closeable | `MaybeSignal<bool>` | `true` | Whether to emit hide event when click mask. |
|
||||
| close_on_esc | `bool` | `true` | Whether to close modal on Esc is pressed. |
|
||||
| closable | `bool` | `true` | Whether to display the close button. |
|
||||
| children | `Children` | | Modal's content. |
|
||||
|
||||
### Modal Slots
|
|
@ -2,9 +2,29 @@
|
|||
|
||||
```rust demo
|
||||
view! {
|
||||
"top"
|
||||
<Space vertical=true>
|
||||
<div style="padding: 30px 0; background-color: var(--colorNeutralBackground1);">
|
||||
<Divider />
|
||||
"bottom"
|
||||
</div>
|
||||
<div style="padding: 30px 0; background-color: var(--colorNeutralBackground1);">
|
||||
<Divider>"Text"</Divider>
|
||||
</div>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Vertical
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true gap=SpaceGap::Large>
|
||||
<div style="height: 100px; background-color: var(--colorNeutralBackground1);">
|
||||
<Divider vertical=true/>
|
||||
</div>
|
||||
<div style="height: 100px; background-color: var(--colorNeutralBackground1);">
|
||||
<Divider vertical=true>"Text"</Divider>
|
||||
</div>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,54 +1,143 @@
|
|||
# Drawer
|
||||
|
||||
```rust demo
|
||||
let show = create_rw_signal(false);
|
||||
let placement = create_rw_signal(DrawerPlacement::Top);
|
||||
let open = RwSignal::new(false);
|
||||
let position = RwSignal::new(DrawerPosition::Top);
|
||||
|
||||
let open = Callback::new(move |new_placement: DrawerPlacement| {
|
||||
let open_f = move |new_position: DrawerPosition| {
|
||||
// Note: Since `show` changes are made in real time,
|
||||
// please put it in front of `show.set(true)` when changing `placement`.
|
||||
placement.set(new_placement);
|
||||
show.set(true);
|
||||
});
|
||||
position.set(new_position);
|
||||
open.set(true);
|
||||
};
|
||||
|
||||
view! {
|
||||
<ButtonGroup>
|
||||
<Button on_click=Callback::new(move |_| leptos::Callable::call(&open, DrawerPlacement::Top))>"Top"</Button>
|
||||
<Button on_click=Callback::new(move |_| leptos::Callable::call(&open, DrawerPlacement::Right))>"Right"</Button>
|
||||
<Button on_click=Callback::new(move |_| leptos::Callable::call(&open, DrawerPlacement::Bottom))>"Bottom"</Button>
|
||||
<Button on_click=Callback::new(move |_| leptos::Callable::call(&open, DrawerPlacement::Left))>"Left"</Button>
|
||||
<Button on_click=move |_| open_f(DrawerPosition::Top)>"Top"</Button>
|
||||
<Button on_click=move |_| open_f(DrawerPosition::Right)>"Right"</Button>
|
||||
<Button on_click=move |_| open_f(DrawerPosition::Bottom)>"Bottom"</Button>
|
||||
<Button on_click=move |_| open_f(DrawerPosition::Left)>"Left"</Button>
|
||||
</ButtonGroup>
|
||||
<Drawer title="Title" show placement>
|
||||
"Hello"
|
||||
</Drawer>
|
||||
<OverlayDrawer open position>
|
||||
<DrawerHeader>
|
||||
<DrawerHeaderTitle>
|
||||
<DrawerHeaderTitleAction slot>
|
||||
<Button
|
||||
appearance=ButtonAppearance::Subtle
|
||||
on_click=move |_| open.set(false)
|
||||
>
|
||||
"x"
|
||||
</Button>
|
||||
</DrawerHeaderTitleAction>
|
||||
"Default Drawer"
|
||||
</DrawerHeaderTitle>
|
||||
</DrawerHeader>
|
||||
<DrawerBody>
|
||||
<p>"Drawer content"</p>
|
||||
</DrawerBody>
|
||||
</OverlayDrawer>
|
||||
}
|
||||
```
|
||||
|
||||
### Customize display area
|
||||
### Inline
|
||||
|
||||
```rust demo
|
||||
let show = create_rw_signal(false);
|
||||
let open_left = RwSignal::new(false);
|
||||
let open_right = RwSignal::new(false);
|
||||
let open_buttom = RwSignal::new(false);
|
||||
|
||||
view! {
|
||||
<div style="position: relative; overflow: hidden; height: 200px; background-color: #0078ff88;">
|
||||
<Button on_click=move |_| show.set(true)>"Open"</Button>
|
||||
<Drawer show mount=DrawerMount::None width="50%">
|
||||
"Current position"
|
||||
</Drawer>
|
||||
<div style="display: flex; flex-direction: column; overflow: hidden; height: 400px; background-color: #0078ff88;">
|
||||
<div style="display: flex; overflow: hidden; height: 400px;">
|
||||
<InlineDrawer open=open_left>
|
||||
<DrawerHeader>
|
||||
<DrawerHeaderTitle>
|
||||
<DrawerHeaderTitleAction slot>
|
||||
<Button
|
||||
appearance=ButtonAppearance::Subtle
|
||||
on_click=move |_| open_left.set(false)
|
||||
>
|
||||
"x"
|
||||
</Button>
|
||||
</DrawerHeaderTitleAction>
|
||||
"Inline Drawer"
|
||||
</DrawerHeaderTitle>
|
||||
</DrawerHeader>
|
||||
<DrawerBody>
|
||||
<p>"Drawer content"</p>
|
||||
</DrawerBody>
|
||||
</InlineDrawer>
|
||||
<div style="flex: 1">
|
||||
<Button on_click=move |_| open_left.set(true)>"Open left"</Button>
|
||||
<Button on_click=move |_| open_right.set(true)>"Open right"</Button>
|
||||
<Button on_click=move |_| open_buttom.set(true)>"Open buttom"</Button>
|
||||
</div>
|
||||
<InlineDrawer open=open_right position=DrawerPosition::Right>
|
||||
<DrawerHeader>
|
||||
<DrawerHeaderTitle>
|
||||
<DrawerHeaderTitleAction slot>
|
||||
<Button
|
||||
appearance=ButtonAppearance::Subtle
|
||||
on_click=move |_| open_right.set(false)
|
||||
>
|
||||
"x"
|
||||
</Button>
|
||||
</DrawerHeaderTitleAction>
|
||||
"Inline Drawer"
|
||||
</DrawerHeaderTitle>
|
||||
</DrawerHeader>
|
||||
<DrawerBody>
|
||||
<p>"Drawer content"</p>
|
||||
</DrawerBody>
|
||||
</InlineDrawer>
|
||||
</div>
|
||||
<InlineDrawer open=open_buttom position=DrawerPosition::Bottom>
|
||||
<DrawerHeader>
|
||||
<DrawerHeaderTitle>
|
||||
<DrawerHeaderTitleAction slot>
|
||||
<Button
|
||||
appearance=ButtonAppearance::Subtle
|
||||
on_click=move |_| open_buttom.set(false)
|
||||
>
|
||||
"x"
|
||||
</Button>
|
||||
</DrawerHeaderTitleAction>
|
||||
"Inline Drawer"
|
||||
</DrawerHeaderTitle>
|
||||
</DrawerHeader>
|
||||
<DrawerBody>
|
||||
<p>"Drawer content"</p>
|
||||
</DrawerBody>
|
||||
</InlineDrawer>
|
||||
</div>
|
||||
}
|
||||
```
|
||||
|
||||
### Scroll content
|
||||
### With Scroll
|
||||
|
||||
```rust demo
|
||||
let show = create_rw_signal(false);
|
||||
let open = RwSignal::new(false);
|
||||
|
||||
view! {
|
||||
<Button on_click=move |_| show.set(true)>"Open"</Button>
|
||||
<Drawer show width="160px" title="Scroll content">
|
||||
r#"This being said, the world is moving in the direction opposite to Clarke's predictions. In 2001: A Space Odyssey, in the year of 2001, which has already passed, human beings have built magnificent cities in space, and established permanent colonies on the moon, and huge nuclear-powered spacecraft have sailed to Saturn. However, today, in 2018, the walk on the moon has become a distant memory.And the farthest reach of our manned space flights is just as long as the two-hour mileage of a high-speed train passing through my city. At the same time, information technology is developing at an unimaginable speed. With the entire world covered by the Internet, people have gradually lost their interest in space, as they find themselves increasingly comfortable in the space created by IT. Instead of an exploration of the real space, which is full of real difficulties, people now just prefer to experience virtual space through VR. Just like someone said, "You promised me an ocean of stars, but you actually gave me Facebook.""#
|
||||
</Drawer>
|
||||
<Button on_click=move |_| open.set(true)>"Open"</Button>
|
||||
<OverlayDrawer open>
|
||||
<DrawerHeader>
|
||||
<DrawerHeaderTitle>
|
||||
<DrawerHeaderTitleAction slot>
|
||||
<Button
|
||||
appearance=ButtonAppearance::Subtle
|
||||
on_click=move |_| open.set(false)
|
||||
>
|
||||
"x"
|
||||
</Button>
|
||||
</DrawerHeaderTitleAction>
|
||||
"Default Drawer"
|
||||
</DrawerHeaderTitle>
|
||||
</DrawerHeader>
|
||||
<DrawerBody>
|
||||
<p style="line-height: 40px">r#"This being said, the world is moving in the direction opposite to Clarke's predictions. In 2001: A Space Odyssey, in the year of 2001, which has already passed, human beings have built magnificent cities in space, and established permanent colonies on the moon, and huge nuclear-powered spacecraft have sailed to Saturn. However, today, in 2018, the walk on the moon has become a distant memory.And the farthest reach of our manned space flights is just as long as the two-hour mileage of a high-speed train passing through my city. At the same time, information technology is developing at an unimaginable speed. With the entire world covered by the Internet, people have gradually lost their interest in space, as they find themselves increasingly comfortable in the space created by IT. Instead of an exploration of the real space, which is full of real difficulties, people now just prefer to experience virtual space through VR. Just like someone said, "You promised me an ocean of stars, but you actually gave me Facebook.""#</p>
|
||||
</DrawerBody>
|
||||
</OverlayDrawer>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,182 +0,0 @@
|
|||
# Dropdown
|
||||
|
||||
```rust demo
|
||||
let message = use_message();
|
||||
|
||||
let on_select = move |key: String| {
|
||||
match key.as_str() {
|
||||
"facebook" => message.create( "Facebook".into(), MessageVariant::Success, Default::default(),),
|
||||
"twitter" => message.create( "Twitter".into(), MessageVariant::Warning, Default::default(),),
|
||||
_ => ()
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
view! {
|
||||
<Space>
|
||||
<Dropdown on_select trigger_type=DropdownTriggerType::Hover>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Hover"</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownItem key="facebook" icon=icondata::AiFacebookOutlined label="Facebook"></DropdownItem>
|
||||
<DropdownItem key="twitter" disabled=true icon=icondata::AiTwitterOutlined label="Twitter"></DropdownItem>
|
||||
</Dropdown>
|
||||
|
||||
<Dropdown on_select>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Click"</Button>
|
||||
</DropdownTrigger>
|
||||
<DropdownItem key="facebook" icon=icondata::AiFacebookOutlined label="Facebook"></DropdownItem>
|
||||
<DropdownItem key="twitter" icon=icondata::AiTwitterOutlined label="Twitter"></DropdownItem>
|
||||
<DropdownItem key="no_icon" disabled=true label="Mastodon"></DropdownItem>
|
||||
</Dropdown>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Placement
|
||||
|
||||
```rust demo
|
||||
use leptos_meta::Style;
|
||||
|
||||
let on_select = move |key| println!("{}", key);
|
||||
|
||||
view! {
|
||||
<Style>
|
||||
".demo-dropdown .thaw-button { width: 100% } .demo-dropdown .thaw-dropdown-trigger { display: block }"
|
||||
</Style>
|
||||
<Grid x_gap=8 y_gap=8 cols=3 class="demo-dropdown">
|
||||
<GridItem>
|
||||
<Dropdown on_select placement=DropdownPlacement::TopStart>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Top Start"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Dropdown on_select placement=DropdownPlacement::Top>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Top"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Dropdown on_select placement=DropdownPlacement::TopEnd>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Top End"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Dropdown on_select placement=DropdownPlacement::LeftStart>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Left Start"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem offset=1>
|
||||
<Dropdown on_select placement=DropdownPlacement::RightStart>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Right Start"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Dropdown on_select placement=DropdownPlacement::Left>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Left"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem offset=1>
|
||||
<Dropdown on_select placement=DropdownPlacement::Right>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Right"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Dropdown on_select placement=DropdownPlacement::LeftEnd>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Left End"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem offset=1>
|
||||
<Dropdown on_select placement=DropdownPlacement::RightEnd>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Right End"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Dropdown on_select placement=DropdownPlacement::BottomStart>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Bottom Start"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Dropdown on_select placement=DropdownPlacement::Bottom>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Bottom"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Dropdown on_select placement=DropdownPlacement::BottomEnd>
|
||||
<DropdownTrigger slot>
|
||||
<Button>"Bottom End"</Button>
|
||||
</DropdownTrigger>
|
||||
"Content"
|
||||
</Dropdown>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
}
|
||||
```
|
||||
|
||||
### Dropdown Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ------------ | ----------------------------------- | ---------------------------- | ------------------------------------------- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the dropdown element. |
|
||||
| on_select | `Callback<String>` | | Called when item is selected. |
|
||||
| trigger_type | `DropdownTriggerType` | `DropdownTriggerType::Click` | Action that displays the dropdown. |
|
||||
| placement | `DropdownPlacement` | `DropdownPlacement::Bottom` | Dropdown placement. |
|
||||
| children | `Children` | | The content inside dropdown. |
|
||||
|
||||
### DropdownItem Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------- | -------------------------------------------- | -------------------- | ------------------------------------------------ |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the dropdown item element. |
|
||||
| key | `MaybeSignal<String>` | `Default::default()` | The key of the dropdown item. |
|
||||
| label | `MaybeSignal<String>` | `Default::default()` | The label of the dropdown item. |
|
||||
| icon | `OptionalMaybeSignal<icondata_core::Icon>` | `None` | The icon of the dropdown item. |
|
||||
| disabled | `MaybeSignal<bool>` | `false` | Whether the dropdown item is disabled. |
|
||||
|
||||
|
||||
### Dropdown Slots
|
||||
|
||||
| Name | Default | Description |
|
||||
| --------------- | ------- | ------------------------------------------------ |
|
||||
| DropdownTrigger | `None` | The element or component that triggers dropdown. |
|
||||
|
||||
### DropdownTriger Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ------------ | ----------------------------------- | ---------------------------- | -------------------------------------------------- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the dropdown trigger element. |
|
||||
| children | `Children` | | The content inside dropdown trigger. |
|
||||
|
|
@ -3,10 +3,21 @@
|
|||
```rust demo
|
||||
view! {
|
||||
<Image src="https://s3.bmp.ovh/imgs/2021/10/2c3b013418d55659.jpg" width="500px"/>
|
||||
<Image src="https://s3.bmp.ovh/imgs/2021/10/2c3b013418d55659.jpg" width="200px" height="200px" object_fit="cover"/>
|
||||
<Image width="200px" height="200px"/>
|
||||
}
|
||||
```
|
||||
|
||||
### Shape
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Image src="https://s3.bmp.ovh/imgs/2021/10/2c3b013418d55659.jpg" width="200px" height="200px"/>
|
||||
<Image src="https://s3.bmp.ovh/imgs/2021/10/2c3b013418d55659.jpg" width="200px" height="200px" shape=ImageShape::Circular/>
|
||||
<Image src="https://s3.bmp.ovh/imgs/2021/10/2c3b013418d55659.jpg" width="200px" height="200px" shape=ImageShape::Rounded/>
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Image Props
|
||||
|
||||
| Name | Type | Default | Desciption |
|
||||
|
|
|
@ -1,13 +1,37 @@
|
|||
# Input
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(String::from("o"));
|
||||
let value = RwSignal::new(String::from("o"));
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Input value/>
|
||||
<Input value variant=InputVariant::Password placeholder="Password"/>
|
||||
<TextArea value placeholder="Textarea"/>
|
||||
<Input value input_type=InputType::Password placeholder="Password"/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
## Prefix & Suffix
|
||||
|
||||
```rust demo
|
||||
let value = RwSignal::new(String::from("o"));
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Input value>
|
||||
<InputPrefix slot>
|
||||
<Icon icon=icondata::AiUserOutlined/>
|
||||
</InputPrefix>
|
||||
</Input>
|
||||
<Input value>
|
||||
<InputSuffix slot>
|
||||
<Icon icon=icondata::AiGithubOutlined/>
|
||||
</InputSuffix>
|
||||
</Input>
|
||||
<Input value>
|
||||
<InputPrefix slot>"$"</InputPrefix>
|
||||
<InputSuffix slot>".00"</InputSuffix>
|
||||
</Input>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
@ -15,42 +39,34 @@ view! {
|
|||
### Disabled
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(String::from("o"));
|
||||
let value = RwSignal::new(String::from("o"));
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Input value disabled=true/>
|
||||
<TextArea value disabled=true/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Invalid
|
||||
### Placeholder
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(String::from("o"));
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Input value invalid=true/>
|
||||
<TextArea value invalid=true/>
|
||||
</Space>
|
||||
<Input placeholder="This is a placeholder"/>
|
||||
}
|
||||
```
|
||||
|
||||
### Imperative handle
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(String::from("o"));
|
||||
let input_ref = create_component_ref::<InputRef>();
|
||||
let value = RwSignal::new(String::from("o"));
|
||||
let input_ref = ComponentRef::<InputRef>::new();
|
||||
|
||||
let focus = Callback::new(move |_| {
|
||||
let focus = move |_| {
|
||||
input_ref.get_untracked().unwrap().focus()
|
||||
});
|
||||
};
|
||||
|
||||
let blur = Callback::new(move |_| {
|
||||
let blur = move |_| {
|
||||
input_ref.get_untracked().unwrap().blur()
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
|
@ -67,56 +83,20 @@ view! {
|
|||
}
|
||||
```
|
||||
|
||||
### Input attrs
|
||||
### Custom parsing
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<label for="demo-input-attrs">"Do you like cheese?"</label>
|
||||
<Input attr:id="demo-input-attrs"/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
let value = RwSignal::new(String::from("loren_ipsun"));
|
||||
|
||||
## Prefix & Suffix
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(String::from("o"));
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Input value>
|
||||
<InputPrefix slot>
|
||||
<Icon icon=icondata::AiUserOutlined/>
|
||||
</InputPrefix>
|
||||
</Input>
|
||||
<Input value>
|
||||
<InputSuffix slot>"$"</InputSuffix>
|
||||
</Input>
|
||||
<Input value>
|
||||
<InputSuffix slot>
|
||||
<Icon icon=icondata::AiGithubOutlined/>
|
||||
</InputSuffix>
|
||||
</Input>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Formatter
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(String::from("loren_ipsun"));
|
||||
|
||||
let formatter = Callback::<String, String>::new(move |v: String| {
|
||||
let format = move |v: String| {
|
||||
v.replace("_", " ")
|
||||
});
|
||||
|
||||
let parser = Callback::<String, String>::new(move |v: String| {
|
||||
v.replace(" ", "_")
|
||||
});
|
||||
};
|
||||
let parser = move |v: String| {
|
||||
Some(v.replace(" ", "_"))
|
||||
};
|
||||
|
||||
view! {
|
||||
<Input value parser formatter />
|
||||
<Input value parser format />
|
||||
<p>"Underlying value: "{ value }</p>
|
||||
}
|
||||
```
|
||||
|
@ -135,8 +115,6 @@ view! {
|
|||
| on_focus | `Option<Callback<ev::FocusEvent>>` | `None` | Callback triggered when the input is focussed on. |
|
||||
| on_blur | `Option<Callback<ev::FocusEvent>>` | `None` | Callback triggered when the input is blurred. |
|
||||
| attr: | `Vec<(&'static str, Attribute)>` | `Default::default()` | The dom attrs of the input element inside the component. |
|
||||
| parser | `OptionalProp<Callback<String, String>>` | `Default::default()` | Modifies the user input before assigning it to the value |
|
||||
| formatter | `OptionalProp<Callback<String, String>>` | `Default::default()` | Formats the value to be shown to the user |
|
||||
|
||||
### Input Slots
|
||||
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
# Input Number
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0);
|
||||
let value_f64 = create_rw_signal(0.0);
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<InputNumber value step=1/>
|
||||
<InputNumber value=value_f64 step=1.0/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Min / Max
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0);
|
||||
|
||||
view! {
|
||||
<InputNumber value step=1 min=-1 max=2/>
|
||||
}
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0);
|
||||
|
||||
view! {
|
||||
<InputNumber value step=1 disabled=true/>
|
||||
}
|
||||
```
|
||||
|
||||
### Invalid
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0);
|
||||
|
||||
view! {
|
||||
<InputNumber value step=1 invalid=true/>
|
||||
}
|
||||
```
|
||||
|
||||
### Formatter
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0.0);
|
||||
let value_2 = create_rw_signal(0.0);
|
||||
|
||||
let formatter = Callback::<f64, String>::new(move |v: f64| {
|
||||
let v = v.to_string();
|
||||
let dot_pos = v.chars().position(|c| c == '.').unwrap_or_else(|| v.chars().count());
|
||||
let mut int: String = v.chars().take(dot_pos).collect();
|
||||
|
||||
let sign: String = if v.chars().take(1).collect::<String>() == String::from("-") {
|
||||
int = int.chars().skip(1).collect();
|
||||
String::from("-")
|
||||
} else {
|
||||
String::from("")
|
||||
};
|
||||
|
||||
let dec: String = v.chars().skip(dot_pos + 1).take(2).collect();
|
||||
|
||||
let int = int
|
||||
.as_bytes()
|
||||
.rchunks(3)
|
||||
.rev()
|
||||
.map(std::str::from_utf8)
|
||||
.collect::<Result<Vec<&str>, _>>()
|
||||
.unwrap()
|
||||
.join(".");
|
||||
format!("{}{},{:0<2}", sign, int, dec)
|
||||
});
|
||||
|
||||
let parser = Callback::<String, f64>::new(move |v: String| {
|
||||
let comma_pos = v.chars().position(|c| c == ',').unwrap_or_else(|| v.chars().count());
|
||||
let int_part = v.chars().take(comma_pos).filter(|a| a.is_digit(10)).collect::<String>();
|
||||
let dec_part = v.chars().skip(comma_pos + 1).take(2).filter(|a| a.is_digit(10)).collect::<String>();
|
||||
format!("{:0<1}.{:0<2}", int_part, dec_part).parse::<f64>().unwrap_or_default()
|
||||
});
|
||||
|
||||
view! {
|
||||
<InputNumber value parser formatter step=1.0 />
|
||||
<p>"Underlying value: "{ value }</p>
|
||||
}
|
||||
```
|
||||
### InputNumber Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the input element. |
|
||||
| value | `Model<T>` | `T::default()` | Set the input value. |
|
||||
| placeholder | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Placeholder of input number. |
|
||||
| step | `MaybeSignal<T>` | | The number which the current value is increased or decreased on key or button press. |
|
||||
| min | `MaybeSignal<T>` | `T::min_value()` | The minimum number that the input value can take. |
|
||||
| max | `MaybeSignal<T>` | `T::max_value()` | The maximum number that the input value can take. |
|
||||
| disabled | `MaybeSignal<bool>` | `false` | Whether the input is disabled. |
|
||||
| invalid | `MaybeSignal<bool>` | `false` | Whether the input is invalid. |
|
||||
| attr: | `Vec<(&'static str, Attribute)>` | `Default::default()` | The dom attrs of the input element inside the component. |
|
||||
| parser | `OptionalProp<Callback<String, T>>` | `Default::default()` | Modifies the user input before assigning it to the value |
|
||||
| formatter | `OptionalProp<Callback<T, String>>` | `Default::default()` | Formats the value to be shown to the user |
|
||||
|
||||
#### T impl
|
||||
|
||||
`T: Add<Output = T> + Sub<Output = T> + PartialOrd + num::Bounded + Default + Clone + FromStr + ToString + 'static`
|
||||
|
||||
### InputNumber Ref
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----- | ----------- | ------------------------ |
|
||||
| focus | `Fn(&self)` | Focus the input element. |
|
||||
| blur | `Fn(&self)` | Blur the input element. |
|
|
@ -3,10 +3,10 @@
|
|||
```rust demo
|
||||
view! {
|
||||
<Layout>
|
||||
<LayoutHeader style="background-color: #0078ffaa; padding: 20px;">
|
||||
<LayoutHeader attr:style="background-color: #0078ffaa; padding: 20px;">
|
||||
"Header"
|
||||
</LayoutHeader>
|
||||
<Layout style="background-color: #0078ff88; padding: 20px;">"Content"</Layout>
|
||||
<Layout attr:style="background-color: #0078ff88; padding: 20px;">"Content"</Layout>
|
||||
</Layout>
|
||||
}
|
||||
```
|
||||
|
@ -16,14 +16,14 @@ view! {
|
|||
```rust demo
|
||||
view! {
|
||||
<Layout has_sider=true>
|
||||
<LayoutSider style="background-color: #0078ff99; padding: 20px;">
|
||||
<LayoutSider attr:style="background-color: #0078ff99; padding: 20px;">
|
||||
"Sider"
|
||||
</LayoutSider>
|
||||
<Layout>
|
||||
<LayoutHeader style="background-color: #0078ffaa; padding: 20px;">
|
||||
<LayoutHeader attr:style="background-color: #0078ffaa; padding: 20px;">
|
||||
"Header"
|
||||
</LayoutHeader>
|
||||
<Layout style="background-color: #0078ff88; padding: 20px;">
|
||||
<Layout attr:style="background-color: #0078ff88; padding: 20px;">
|
||||
"Content"
|
||||
</Layout>
|
||||
</Layout>
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
# Loading Bar
|
||||
|
||||
<Alert variant=AlertVariant::Warning title="Prerequisite">
|
||||
<MessageBar layout=MessageBarLayout::Multiline intent=MessageBarIntent::Warning>
|
||||
<MessageBarBody>
|
||||
<h3 style="margin: 0">"Prerequisite"</h3>
|
||||
<p>
|
||||
"If you want to use loading bar, you need to wrap the component where you call related methods inside LoadingBarProvider and use use_loading_bar to get the API."
|
||||
</Alert>
|
||||
</p>
|
||||
</MessageBarBody>
|
||||
</MessageBar>
|
||||
|
||||
```rust demo
|
||||
let loading_bar = use_loading_bar();
|
||||
let start = Callback::new(move |_| {
|
||||
let loading_bar = LoadingBarInjection::expect_use();
|
||||
let start = move |_| {
|
||||
loading_bar.start();
|
||||
});
|
||||
let finish = Callback::new(move |_| {
|
||||
};
|
||||
let finish = move |_| {
|
||||
loading_bar.finish();
|
||||
});
|
||||
let error = Callback::new(move |_| {
|
||||
};
|
||||
let error = move |_| {
|
||||
loading_bar.error();
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<Space>
|
||||
|
|
|
@ -1,25 +1,150 @@
|
|||
# Menu
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(String::from("o"));
|
||||
let toaster = ToasterInjection::expect_context();
|
||||
|
||||
let on_select = move |key: String| {
|
||||
leptos::logging::warn!("{}", key);
|
||||
toaster.dispatch_toast(view! {
|
||||
<Toast>
|
||||
<ToastBody>
|
||||
"key"
|
||||
</ToastBody>
|
||||
</Toast>
|
||||
}.into_any(), Default::default());
|
||||
};
|
||||
|
||||
view! {
|
||||
<Menu value default_expanded_keys=vec![String::from("area")]>
|
||||
<MenuItem key="a" label="And"/>
|
||||
<MenuItem key="o" label="Or"/>
|
||||
<MenuItem icon=icondata::AiAreaChartOutlined key="area" label="Area Chart">
|
||||
<MenuItem key="target" label="Target"/>
|
||||
<MenuItem key="above" label="Above"/>
|
||||
<MenuItem key="below" label="Below"/>
|
||||
</MenuItem>
|
||||
<MenuItem icon=icondata::AiPieChartOutlined key="pie" label="Pie Chart">
|
||||
<MenuItem key="pie-target" label="Target"/>
|
||||
<MenuItem key="pie-above" label="Above"/>
|
||||
<MenuItem key="pie-below" label="Below"/>
|
||||
</MenuItem>
|
||||
<MenuItem icon=icondata::AiGithubOutlined key="github" label="Github"/>
|
||||
<MenuItem icon=icondata::AiChromeOutlined key="chrome" label="Chrome"/>
|
||||
<Space>
|
||||
<Menu on_select trigger_type=MenuTriggerType::Hover>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Hover"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="facebook" icon=icondata::AiFacebookOutlined>"Facebook"</MenuItem>
|
||||
<MenuItem value="twitter" disabled=true icon=icondata::AiTwitterOutlined>"Twitter"</MenuItem>
|
||||
</Menu>
|
||||
|
||||
<Menu on_select>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Click"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="facebook" icon=icondata::AiFacebookOutlined>"Facebook"</MenuItem>
|
||||
<MenuItem value="twitter" icon=icondata::AiTwitterOutlined>"Twitter"</MenuItem>
|
||||
<MenuItem value="no_icon" disabled=true>"Mastodon"</MenuItem>
|
||||
</Menu>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Placement
|
||||
|
||||
```rust demo
|
||||
use leptos_meta::Style;
|
||||
|
||||
let on_select = move |value| leptos::logging::warn!("{}", value);
|
||||
|
||||
view! {
|
||||
<Style>
|
||||
".demo-menu .thaw-button { width: 100% } .demo-menu .thaw-menu-trigger { display: block }"
|
||||
</Style>
|
||||
<Grid x_gap=8 y_gap=8 cols=3 class="demo-menu">
|
||||
<GridItem>
|
||||
<Menu on_select position=MenuPosition::TopStart>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Top Start"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Menu on_select position=MenuPosition::Top>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Top"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Menu on_select position=MenuPosition::TopEnd>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Top End"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Menu on_select position=MenuPosition::LeftStart>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Left Start"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem offset=1>
|
||||
<Menu on_select position=MenuPosition::RightStart>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Right Start"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Menu on_select position=MenuPosition::Left>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Left"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem offset=1>
|
||||
<Menu on_select position=MenuPosition::Right>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Right"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Menu on_select position=MenuPosition::LeftEnd>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Left End"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem offset=1>
|
||||
<Menu on_select position=MenuPosition::RightEnd>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Right End"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Menu on_select position=MenuPosition::BottomStart>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Bottom Start"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Menu on_select position=MenuPosition::Bottom>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Bottom"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Menu on_select position=MenuPosition::BottomEnd>
|
||||
<MenuTrigger slot>
|
||||
<Button>"Bottom End"</Button>
|
||||
</MenuTrigger>
|
||||
<MenuItem value="content">"Content"</MenuItem>
|
||||
</Menu>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -28,24 +153,30 @@ view! {
|
|||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the menu element. |
|
||||
| value | `Model<String>` | `Default::default()` | The selected item key of the menu. |
|
||||
| default_expanded_keys | `Vec<String>` | `Default::default()` | The default expanded submenu keys. |
|
||||
| children | `Children` | | Menu's content. |
|
||||
|
||||
### MenuGroup Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the menu group element. |
|
||||
| label | `String` | | The label of the menu group. |
|
||||
| children | `Children` | | MenuGroup's content. |
|
||||
| on_select | `Callback<String>` | | Called when item is selected. |
|
||||
| trigger_type | `MenuTriggerType` | `MenuTriggerType::Click` | Action that displays the menu. |
|
||||
| position | `MenuPosition` | `MenuPosition::Bottom` | Menu position. |
|
||||
| children | `Children` | | The content inside menu. |
|
||||
|
||||
### MenuItem Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the menu item element. |
|
||||
| value | `MaybeSignal<String>` | `Default::default()` | The value of the menu item. |
|
||||
| label | `MaybeSignal<String>` | `Default::default()` | The label of the menu item. |
|
||||
| key | `MaybeSignal<String>` | `Default::default()` | The indentifier of the menu item. |
|
||||
| icon | `OptionalMaybeSignal<icondata_core::Icon>` | `None` | The icon of the menu item. |
|
||||
| children | `Option<Children>` | `None` | MenuItem's content. |
|
||||
| disabled | `MaybeSignal<bool>` | `false` | Whether the menu item is disabled. |
|
||||
|
||||
### Menu Slots
|
||||
|
||||
| Name | Default | Description |
|
||||
| ----------- | ------- | -------------------------------------------- |
|
||||
| MenuTrigger | `None` | The element or component that triggers menu. |
|
||||
|
||||
### MenuTriger Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the menu trigger element. |
|
||||
| children | `Children` | | The content inside menu trigger. |
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
# Message
|
||||
|
||||
<Alert variant=AlertVariant::Warning title="Prerequisite">
|
||||
"If you want to use message, you need to wrap the component where you call related methods inside MessageProvider and use use_message to get the API."
|
||||
</Alert>
|
||||
|
||||
```rust demo
|
||||
let message = use_message();
|
||||
let success = move |_| {
|
||||
message.create(
|
||||
"Success".into(),
|
||||
MessageVariant::Success,
|
||||
MessageOptions {closable: true, duration: std::time::Duration::from_secs(0)},
|
||||
);
|
||||
};
|
||||
let warning = move |_| {
|
||||
message.create(
|
||||
"Warning".into(),
|
||||
MessageVariant::Warning,
|
||||
Default::default(),
|
||||
);
|
||||
};
|
||||
let error = move |_| {
|
||||
message.create("Error".into(), MessageVariant::Error, Default::default());
|
||||
};
|
||||
|
||||
view! {
|
||||
<Space>
|
||||
<Button on_click=success>"Success"</Button>
|
||||
<Button on_click=warning>"Warning"</Button>
|
||||
<Button on_click=error>"Error"</Button>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### MessageProvider Props
|
||||
|
||||
| Name | Type | Default | Desciption |
|
||||
| --------- | ------------------ | ----------------------- | ------------------------------- |
|
||||
| placement | `MessagePlacement` | `MessagePlacement::Top` | Position to place the messages. |
|
||||
|
||||
### MessageProvider Injection Methods
|
||||
|
||||
| Name | Type | Description |
|
||||
| ------ | ------------------------------------------------------------------------------ | ------------------------ |
|
||||
| create | `fn(&self, content: String, variant: MessageVariant, options: MessageOptions)` | Use create type message. |
|
||||
|
||||
### MessageOptions fields
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| duration | `Duration` | `std::time::Duration::from_secs(3)` | How long the message will be displayed. 0 for permanent message |
|
||||
| closable | `bool` | `false` | Can the message be manually closed. |
|
67
demo_markdown/docs/message_bar/mod.md
Normal file
67
demo_markdown/docs/message_bar/mod.md
Normal file
|
@ -0,0 +1,67 @@
|
|||
# MessageBar
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<MessageBar>
|
||||
<MessageBarBody>
|
||||
<MessageBarTitle>"Descriptive title"</MessageBarTitle>
|
||||
"Message providing information to the user with actionable insights."
|
||||
</MessageBarBody>
|
||||
<MessageBarActions>
|
||||
<Button size=ButtonSize::Small>"Action"</Button>
|
||||
<Button size=ButtonSize::Small>"Action"</Button>
|
||||
<MessageBarContainerAction slot>
|
||||
<Button size=ButtonSize::Small appearance=ButtonAppearance::Transparent>
|
||||
"X"
|
||||
</Button>
|
||||
</MessageBarContainerAction>
|
||||
</MessageBarActions>
|
||||
</MessageBar>
|
||||
}
|
||||
```
|
||||
|
||||
### Intent
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<MessageBar>
|
||||
<MessageBarBody>
|
||||
<MessageBarTitle>"Intent info"</MessageBarTitle>
|
||||
"Message providing information to the user with actionable insights."
|
||||
</MessageBarBody>
|
||||
</MessageBar>
|
||||
<MessageBar intent=MessageBarIntent::Warning>
|
||||
<MessageBarBody>
|
||||
<MessageBarTitle>"Intent warning"</MessageBarTitle>
|
||||
"Message providing information to the user with actionable insights."
|
||||
</MessageBarBody>
|
||||
</MessageBar>
|
||||
<MessageBar intent=MessageBarIntent::Error>
|
||||
<MessageBarBody>
|
||||
<MessageBarTitle>"Intent error"</MessageBarTitle>
|
||||
"Message providing information to the user with actionable insights."
|
||||
</MessageBarBody>
|
||||
</MessageBar>
|
||||
<MessageBar intent=MessageBarIntent::Success>
|
||||
<MessageBarBody>
|
||||
<MessageBarTitle>"Intent success"</MessageBarTitle>
|
||||
"Message providing information to the user with actionable insights."
|
||||
</MessageBarBody>
|
||||
</MessageBar>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Manual Layout
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<MessageBar layout=MessageBarLayout::Multiline>
|
||||
<MessageBarBody>
|
||||
<h3 style="margin: 0">"Descriptive title"</h3>
|
||||
<p>"Message providing information to the user with actionable insights."</p>
|
||||
</MessageBarBody>
|
||||
</MessageBar>
|
||||
}
|
||||
```
|
48
demo_markdown/docs/nav/mod.md
Normal file
48
demo_markdown/docs/nav/mod.md
Normal file
|
@ -0,0 +1,48 @@
|
|||
# Nav
|
||||
|
||||
```rust demo
|
||||
|
||||
view! {
|
||||
<NavDrawer>
|
||||
<NavCategory value="area">
|
||||
<NavCategoryItem slot icon=icondata::AiAreaChartOutlined>
|
||||
"Area Chart"
|
||||
</NavCategoryItem>
|
||||
<NavSubItem value="target">
|
||||
"Target"
|
||||
</NavSubItem>
|
||||
<NavSubItem value="above">
|
||||
"Above"
|
||||
</NavSubItem>
|
||||
<NavSubItem value="below">
|
||||
"Below"
|
||||
</NavSubItem>
|
||||
</NavCategory>
|
||||
<NavCategory value="pie">
|
||||
<NavCategoryItem slot icon=icondata::AiPieChartOutlined>
|
||||
"Pie Chart"
|
||||
</NavCategoryItem>
|
||||
<NavSubItem value="pie-target">
|
||||
"Pie Target"
|
||||
</NavSubItem>
|
||||
<NavSubItem value="pin-above">
|
||||
"Pin Above"
|
||||
</NavSubItem>
|
||||
<NavSubItem value="pin-below">
|
||||
"Pin Below"
|
||||
</NavSubItem>
|
||||
</NavCategory>
|
||||
<NavItem
|
||||
icon=icondata::AiGithubOutlined
|
||||
value="github"
|
||||
href="https://github.com/thaw-ui/thaw"
|
||||
attr:target="_blank"
|
||||
>
|
||||
"Github"
|
||||
</NavItem>
|
||||
<NavItem icon=icondata::AiChromeOutlined value="chrome">
|
||||
"Chrome"
|
||||
</NavItem>
|
||||
</NavDrawer>
|
||||
}
|
||||
```
|
|
@ -1,49 +0,0 @@
|
|||
# Pagination
|
||||
|
||||
```rust demo
|
||||
let page = create_rw_signal(1);
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<div>"Page: " {move || page.get()}</div>
|
||||
<Pagination page count=10 />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Size
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Pagination count=100 size=ButtonSize::Tiny />
|
||||
<Pagination count=100 size=ButtonSize::Small />
|
||||
<Pagination count=100 size=ButtonSize::Medium />
|
||||
<Pagination count=100 size=ButtonSize::Large />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Pagination ranges
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Pagination count=100 sibling_count=0 />
|
||||
<Pagination count=100 sibling_count=1 />
|
||||
<Pagination count=100 sibling_count=2 />
|
||||
<Pagination count=100 sibling_count=3 />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Pagination Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Additional classes. |
|
||||
| page | `Model<usize>` | `1` | The current page starts from 1. |
|
||||
| count | `MaybeSignal<usize>` | | The total numbers of pages. |
|
||||
| sibling_count | `MaybeSignal<usize>` | `1` | Number of visible pages after and before the current page. |
|
||||
| size | `MaybeSignal<ButtonSize>` | `ButtonSize::Medium` | Button size. |
|
||||
| on_change | `Option<Callback<usize>>` | `None` | Callback fired when the page is changed. |
|
|
@ -30,7 +30,7 @@ view! {
|
|||
</Style>
|
||||
<Grid x_gap=8 y_gap=8 cols=3 class="demo-popover">
|
||||
<GridItem>
|
||||
<Popover placement=PopoverPlacement::TopStart>
|
||||
<Popover position=PopoverPosition::TopStart>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Top Start"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -38,7 +38,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Popover placement=PopoverPlacement::Top>
|
||||
<Popover position=PopoverPosition::Top>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Top"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -46,7 +46,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Popover placement=PopoverPlacement::TopEnd>
|
||||
<Popover position=PopoverPosition::TopEnd>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Top End"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -54,7 +54,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Popover placement=PopoverPlacement::LeftStart>
|
||||
<Popover position=PopoverPosition::LeftStart trigger_type=PopoverTriggerType::Click>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Left Start"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -62,7 +62,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem offset=1>
|
||||
<Popover placement=PopoverPlacement::RightStart>
|
||||
<Popover position=PopoverPosition::RightStart>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Right Start"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -70,7 +70,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Popover placement=PopoverPlacement::Left>
|
||||
<Popover position=PopoverPosition::Left trigger_type=PopoverTriggerType::Click>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Left"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -78,7 +78,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem offset=1>
|
||||
<Popover placement=PopoverPlacement::Right>
|
||||
<Popover position=PopoverPosition::Right>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Right"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -86,7 +86,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Popover placement=PopoverPlacement::LeftEnd>
|
||||
<Popover position=PopoverPosition::LeftEnd>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Left End"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -94,7 +94,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem offset=1>
|
||||
<Popover placement=PopoverPlacement::RightEnd>
|
||||
<Popover position=PopoverPosition::RightEnd>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Right End"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -102,7 +102,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Popover placement=PopoverPlacement::BottomStart>
|
||||
<Popover position=PopoverPosition::BottomStart>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Bottom Start"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -110,7 +110,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Popover placement=PopoverPlacement::Bottom>
|
||||
<Popover position=PopoverPosition::Bottom>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Bottom"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -118,7 +118,7 @@ view! {
|
|||
</Popover>
|
||||
</GridItem>
|
||||
<GridItem>
|
||||
<Popover placement=PopoverPlacement::BottomEnd>
|
||||
<Popover position=PopoverPosition::BottomEnd>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Bottom End"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -129,12 +129,18 @@ view! {
|
|||
}
|
||||
```
|
||||
|
||||
### Tooltip
|
||||
### Appearance
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<Popover tooltip=true>
|
||||
<Popover appearance=PopoverAppearance::Brand>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Hover"</Button>
|
||||
</PopoverTrigger>
|
||||
"Content"
|
||||
</Popover>
|
||||
<Popover appearance=PopoverAppearance::Inverted>
|
||||
<PopoverTrigger slot>
|
||||
<Button>"Hover"</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -147,10 +153,9 @@ view! {
|
|||
### Popover Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------------| ----------------------------------- | -------------------------- | --------------------------------- |
|
||||
| -------- | ----------------------------------- | ---------------------- | ----------------------------- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Content class of the popover. |
|
||||
| placement | `PopoverPlacement` | `PopoverPlacement::Top` | Popover placement. |
|
||||
| trigger_type | `PopoverTriggerType` | `PopoverTriggerType::Hover`| Action that displays the dropdown |
|
||||
| position | `PopoverPosition` | `PopoverPosition::Top` | Popover position. |
|
||||
| tooltip | `bool` | `false` | Tooltip. |
|
||||
| children | `Children` | | The content inside popover. |
|
||||
|
||||
|
@ -159,11 +164,3 @@ view! {
|
|||
| Name | Default | Description |
|
||||
| -------------- | ------- | ----------------------------------------------- |
|
||||
| PopoverTrigger | | The element or component that triggers popover. |
|
||||
|
||||
### PopoverTriger Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ------------ | ----------------------------------- | ---------------------------- | -------------------------------------------------- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the popover trigger element. |
|
||||
| children | `Children` | | The content inside popover trigger. |
|
||||
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
# Progress
|
||||
# ProgressBar
|
||||
|
||||
```rust demo
|
||||
let percentage = create_rw_signal(0.0f32);
|
||||
let value = RwSignal::new(0.0);
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Progress percentage show_indicator=false/>
|
||||
<Progress percentage/>
|
||||
<Progress percentage indicator_placement=ProgressIndicatorPlacement::Inside/>
|
||||
<Progress percentage color=ProgressColor::Success/>
|
||||
<Progress percentage color=ProgressColor::Warning/>
|
||||
<Progress percentage color=ProgressColor::Error/>
|
||||
<ProgressBar value/>
|
||||
<ProgressBar value color=ProgressBarColor::Success/>
|
||||
<ProgressBar value color=ProgressBarColor::Warning/>
|
||||
<ProgressBar value color=ProgressBarColor::Error/>
|
||||
<Space>
|
||||
<Button on_click=move |_| percentage.update(|v| *v -= 10.0)>"-10%"</Button>
|
||||
<Button on_click=move |_| percentage.update(|v| *v += 10.0)>"+10%"</Button>
|
||||
<Button on_click=move |_| value.update(|v| *v -= 0.1)>"-10%"</Button>
|
||||
<Button on_click=move |_| value.update(|v| *v += 0.1)>"+10%"</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
}
|
||||
|
@ -22,18 +20,18 @@ view! {
|
|||
### Circle
|
||||
|
||||
```rust demo
|
||||
let percentage = create_rw_signal(0.0f32);
|
||||
let value = RwSignal::new(0.0);
|
||||
|
||||
view! {
|
||||
<Space>
|
||||
<ProgressCircle percentage/>
|
||||
<ProgressCircle percentage color=ProgressColor::Success/>
|
||||
<ProgressCircle percentage color=ProgressColor::Warning/>
|
||||
<ProgressCircle percentage color=ProgressColor::Error/>
|
||||
<ProgressCircle value/>
|
||||
<ProgressCircle value color=ProgressCircleColor::Success/>
|
||||
<ProgressCircle value color=ProgressCircleColor::Warning/>
|
||||
<ProgressCircle value color=ProgressCircleColor::Error/>
|
||||
</Space>
|
||||
<Space>
|
||||
<Button on_click=move |_| percentage.update(|v| *v -= 10.0)>"-10%"</Button>
|
||||
<Button on_click=move |_| percentage.update(|v| *v += 10.0)>"+10%"</Button>
|
||||
<Button on_click=move |_| value.update(|v| *v -= 10.0)>"-10%"</Button>
|
||||
<Button on_click=move |_| value.update(|v| *v += 10.0)>"+10%"</Button>
|
||||
</Space>
|
||||
}
|
||||
```
|
|
@ -1,29 +1,25 @@
|
|||
# Radio
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(false);
|
||||
|
||||
view! {
|
||||
<Radio value>"Click"</Radio>
|
||||
}
|
||||
```
|
||||
|
||||
### Group
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(None);
|
||||
let value = RwSignal::new(String::new());
|
||||
let option_value = RwSignal::new(None);
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<RadioGroup value>
|
||||
<RadioItem key="a">
|
||||
"Apple"
|
||||
</RadioItem>
|
||||
<RadioItem key="o">
|
||||
"Orange"
|
||||
</RadioItem>
|
||||
<Radio value="a" label="Apple"/>
|
||||
<Radio value="o" label="Orange"/>
|
||||
</RadioGroup>
|
||||
<RadioGroup value=option_value>
|
||||
<Radio value="a" label="Apple"/>
|
||||
<Radio value="o" label="Orange"/>
|
||||
</RadioGroup>
|
||||
</Space>
|
||||
<div style="margin-top: 1rem">
|
||||
"value: " {move || format!("{:?}", value.get())}
|
||||
"value: " {move || format!("{}", value.get())}
|
||||
</div>
|
||||
<div style="margin-top: 1rem">
|
||||
"option_value: " {move || format!("{:?}", option_value.get())}
|
||||
</div>
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
# Select
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(None::<String>);
|
||||
|
||||
let options = vec![
|
||||
SelectOption::new("RwSignal", String::from("rw_signal")),
|
||||
SelectOption::new("Memo", String::from("memo")),
|
||||
];
|
||||
|
||||
view! {
|
||||
<Select value options />
|
||||
}
|
||||
```
|
||||
|
||||
# Multiple Select
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(vec![
|
||||
"rust".to_string(),
|
||||
"javascript".to_string(),
|
||||
"zig".to_string(),
|
||||
"python".to_string(),
|
||||
"cpp".to_string(),
|
||||
]);
|
||||
|
||||
let options = vec![
|
||||
MultiSelectOption::new("Rust", String::from("rust")).with_variant(TagVariant::Success),
|
||||
MultiSelectOption::new("JavaScript", String::from("javascript")),
|
||||
MultiSelectOption::new("Python", String::from("python")).with_variant(TagVariant::Warning),
|
||||
MultiSelectOption::new("C++", String::from("cpp")).with_variant(TagVariant::Error),
|
||||
MultiSelectOption::new("Lua", String::from("lua")),
|
||||
MultiSelectOption::new("Zig", String::from("zig")),
|
||||
];
|
||||
|
||||
view! {
|
||||
<MultiSelect value options />
|
||||
}
|
||||
```
|
||||
|
||||
### Select Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ------- | ----------------------------------- | -------------------- | ----------------------------------------- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the select element. |
|
||||
| value | `Model<Option<T>>` | `None` | Checked value. |
|
||||
| options | `MaybeSignal<Vec<SelectOption<T>>>` | `vec![]` | Options that can be selected. |
|
||||
|
||||
### Multiple Select Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --------- | ----------------------------------- | -------------------- | ----------------------------------------- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the select element. |
|
||||
| value | `Model<Vec<T>>` | `vec![]` | Checked values. |
|
||||
| options | `MaybeSignal<Vec<SelectOption<T>>>` | `vec![]` | Options that can be selected. |
|
||||
| clearable | `MaybeSignal<bool>` | `false` | Allow the options to be cleared. |
|
||||
|
||||
### Select Slots
|
||||
|
||||
| Name | Default | Description |
|
||||
| ----------- | ------- | ------------- |
|
||||
| SelectLabel | `None` | Select label. |
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
```rust demo
|
||||
view! {
|
||||
<Skeleton repeat=2 text=true/>
|
||||
<Skeleton width="60%" text=true/>
|
||||
<Skeleton>
|
||||
<SkeletonItem/>
|
||||
</Skeleton>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
# Slider
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0.0);
|
||||
let value = RwSignal::new(0.0);
|
||||
|
||||
view! {
|
||||
<Slider value/>
|
||||
|
@ -11,20 +11,20 @@ view! {
|
|||
### Step
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0.0);
|
||||
let value = RwSignal::new(0.0);
|
||||
|
||||
view! {
|
||||
<Slider step=10.0 value/>
|
||||
<Slider step=25.0 value/>
|
||||
}
|
||||
```
|
||||
|
||||
## Slider Label
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(0.0);
|
||||
let value = RwSignal::new(0.0);
|
||||
|
||||
view! {
|
||||
<Slider value max=10.0 step=1.0>
|
||||
<Slider value max=10.0 step=5.0>
|
||||
<SliderLabel value=0.0>
|
||||
"0"
|
||||
</SliderLabel>
|
||||
|
|
|
@ -45,18 +45,18 @@ view! {
|
|||
view! {
|
||||
<Space vertical=true>
|
||||
<Space align=SpaceAlign::Start>
|
||||
<Button style="height: 60px">"Start"</Button>
|
||||
<Button style="height: 40px">"Start"</Button>
|
||||
<Button attr:style="height: 60px">"Start"</Button>
|
||||
<Button attr:style="height: 40px">"Start"</Button>
|
||||
<Button>"Start"</Button>
|
||||
</Space>
|
||||
<Space align=SpaceAlign::Center>
|
||||
<Button style="height: 60px">"Center"</Button>
|
||||
<Button style="height: 40px">"Center"</Button>
|
||||
<Button attr:style="height: 60px">"Center"</Button>
|
||||
<Button attr:style="height: 40px">"Center"</Button>
|
||||
<Button>"Center"</Button>
|
||||
</Space>
|
||||
<Space align=SpaceAlign::End>
|
||||
<Button style="height: 60px">"End"</Button>
|
||||
<Button style="height: 40px">"End"</Button>
|
||||
<Button attr:style="height: 60px">"End"</Button>
|
||||
<Button attr:style="height: 40px">"End"</Button>
|
||||
<Button>"End"</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
|
|
77
demo_markdown/docs/spin_button/mod.md
Normal file
77
demo_markdown/docs/spin_button/mod.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
# SpinButton
|
||||
|
||||
```rust demo
|
||||
let value = RwSignal::new(0);
|
||||
let value_f64 = RwSignal::new(0.0);
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<SpinButton value step_page=1/>
|
||||
<SpinButton value=value_f64 step_page=1.2/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Min / Max
|
||||
|
||||
```rust demo
|
||||
let value = RwSignal::new(0);
|
||||
|
||||
view! {
|
||||
<SpinButton value step_page=1 min=-1 max=2/>
|
||||
}
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
```rust demo
|
||||
let value = RwSignal::new(0);
|
||||
|
||||
view! {
|
||||
<SpinButton value step_page=1 disabled=true/>
|
||||
}
|
||||
```
|
||||
|
||||
### Custom parsing
|
||||
|
||||
```rust demo
|
||||
let value = RwSignal::new(0.0);
|
||||
|
||||
let format = move |v: f64| {
|
||||
let v = v.to_string();
|
||||
let dot_pos = v.chars().position(|c| c == '.').unwrap_or_else(|| v.chars().count());
|
||||
let mut int: String = v.chars().take(dot_pos).collect();
|
||||
|
||||
let sign: String = if v.chars().take(1).collect::<String>() == String::from("-") {
|
||||
int = int.chars().skip(1).collect();
|
||||
String::from("-")
|
||||
} else {
|
||||
String::from("")
|
||||
};
|
||||
|
||||
let dec: String = v.chars().skip(dot_pos + 1).take(2).collect();
|
||||
|
||||
let int = int
|
||||
.as_bytes()
|
||||
.rchunks(3)
|
||||
.rev()
|
||||
.map(std::str::from_utf8)
|
||||
.collect::<Result<Vec<&str>, _>>()
|
||||
.unwrap()
|
||||
.join(".");
|
||||
format!("{}{},{:0<2}", sign, int, dec)
|
||||
};
|
||||
|
||||
let parser = move |v: String| {
|
||||
let comma_pos = v.chars().position(|c| c == ',').unwrap_or_else(|| v.chars().count());
|
||||
let int_part = v.chars().take(comma_pos).filter(|a| a.is_digit(10)).collect::<String>();
|
||||
let dec_part = v.chars().skip(comma_pos + 1).take(2).filter(|a| a.is_digit(10)).collect::<String>();
|
||||
|
||||
format!("{:0<1}.{:0<2}", int_part, dec_part).parse::<f64>().ok()
|
||||
};
|
||||
|
||||
view! {
|
||||
<SpinButton value parser format step_page=1.0 />
|
||||
<p>"Underlying value: "{ value }</p>
|
||||
}
|
||||
```
|
|
@ -10,11 +10,15 @@ view! {
|
|||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<Spinner size=SpinnerSize::Tiny/>
|
||||
<Spinner size=SpinnerSize::Small/>
|
||||
<Spinner size=SpinnerSize::Medium/>
|
||||
<Spinner size=SpinnerSize::Large/>
|
||||
<Space vertical=true>
|
||||
<Spinner size=SpinnerSize::ExtraTiny label="Extra Tiny Spinner"/>
|
||||
<Spinner size=SpinnerSize::Tiny label="Tiny Spinner"/>
|
||||
<Spinner size=SpinnerSize::ExtraSmall label="Extra Small Spinner"/>
|
||||
<Spinner size=SpinnerSize::Small label="Small Spinner"/>
|
||||
<Spinner size=SpinnerSize::Medium label="Medium Spinner"/>
|
||||
<Spinner size=SpinnerSize::Large label="Large Spinner"/>
|
||||
<Spinner size=SpinnerSize::ExtraLarge label="Extra Large Spinner"/>
|
||||
<Spinner size=SpinnerSize::Huge label="Huge Spinner"/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
|
|
@ -1,26 +1,16 @@
|
|||
# Switch
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(false);
|
||||
|
||||
let message = use_message();
|
||||
let on_change = move |value: bool| {
|
||||
message.create(
|
||||
format!("{value}"),
|
||||
MessageVariant::Success,
|
||||
Default::default(),
|
||||
);
|
||||
};
|
||||
let checked = RwSignal::new(false);
|
||||
|
||||
view! {
|
||||
<Switch value on_change/>
|
||||
<Switch checked />
|
||||
}
|
||||
```
|
||||
|
||||
### Switch Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| --------- | ----------------------------------- | -------------------- | ------------------------------------------- |
|
||||
| ----- | ----------------------------------- | -------------------- | ----------------------------------------- |
|
||||
| class | `OptionalProp<MaybeSignal<String>>` | `Default::default()` | Addtional classes for the switch element. |
|
||||
| value | `Model<bool>` | `false` | Switch's value. |
|
||||
| on_change | `Option<Callback>` | `None` | Trigger when the checked state is changing. |
|
||||
|
|
|
@ -1,44 +1,23 @@
|
|||
# Tabs
|
||||
|
||||
```rust demo
|
||||
let value = create_rw_signal(String::from("apple"));
|
||||
let selected_value = RwSignal::new(String::new());
|
||||
|
||||
view! {
|
||||
<Tabs value>
|
||||
<Tab key="apple" label="Apple">
|
||||
"apple"
|
||||
<TabList selected_value>
|
||||
<Tab value="apple">
|
||||
"Apple"
|
||||
</Tab>
|
||||
<Tab key="pear" label="Pear">
|
||||
"pear"
|
||||
<Tab value="pear">
|
||||
"Pear"
|
||||
</Tab>
|
||||
</Tabs>
|
||||
}
|
||||
```
|
||||
|
||||
### Custom tab label
|
||||
|
||||
```rust demo
|
||||
use leptos_meta::Style;
|
||||
let value = create_rw_signal(String::from("apple"));
|
||||
|
||||
view! {
|
||||
<Style id="demo-tab-label">
|
||||
".p-0 { padding: 0 }"
|
||||
</Style>
|
||||
<Tabs value>
|
||||
<Tab key="apple">
|
||||
<TabLabel slot class="p-0">
|
||||
"🍎 Apple"
|
||||
</TabLabel>
|
||||
"apple"
|
||||
<Tab value="item1">
|
||||
"Item 1"
|
||||
</Tab>
|
||||
<Tab key="pear">
|
||||
<TabLabel slot>
|
||||
"🍐 Pear"
|
||||
</TabLabel>
|
||||
"pear"
|
||||
<Tab value="item2">
|
||||
"Item 2"
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</TabList>
|
||||
}
|
||||
```
|
||||
|
|
@ -3,25 +3,37 @@
|
|||
```rust demo
|
||||
view! {
|
||||
<Table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>"tag"</th>
|
||||
<th>"count"</th>
|
||||
<th>"date"</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>"div"</td>
|
||||
<td>"2"</td>
|
||||
<td>"2023-10-08"</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"span"</td>
|
||||
<td>"2"</td>
|
||||
<td>"2023-10-08"</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell>"Tag"</TableHeaderCell>
|
||||
<TableHeaderCell>"Count"</TableHeaderCell>
|
||||
<TableHeaderCell>"Date"</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<TableCellLayout>
|
||||
"div"
|
||||
</TableCellLayout>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCellLayout>
|
||||
"2"
|
||||
</TableCellLayout>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<TableCellLayout>
|
||||
"2023-10-08"
|
||||
</TableCellLayout>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell>"span"</TableCell>
|
||||
<TableCell>"2"</TableCell>
|
||||
<TableCell>"2023-10-08"</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
}
|
||||
```
|
||||
|
|
|
@ -2,34 +2,24 @@
|
|||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space>
|
||||
<Tag>"default"</Tag>
|
||||
<Tag variant=TagVariant::Success>"success"</Tag>
|
||||
<Tag variant=TagVariant::Warning>"warning"</Tag>
|
||||
<Tag variant=TagVariant::Error>"error"</Tag>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Closable
|
||||
|
||||
```rust demo
|
||||
let message = use_message();
|
||||
let success = move |_| {
|
||||
message.create(
|
||||
"tag close".into(),
|
||||
MessageVariant::Success,
|
||||
Default::default(),
|
||||
);
|
||||
// let message = use_message();
|
||||
let success = move |_: ev::MouseEvent| {
|
||||
// message.create(
|
||||
// "tag close".into(),
|
||||
// MessageVariant::Success,
|
||||
// Default::default(),
|
||||
// );
|
||||
};
|
||||
|
||||
view! {
|
||||
<Space>
|
||||
<Tag closable=true on_close=success>"Default"</Tag>
|
||||
<Tag closable=true on_close=success variant=TagVariant::Success>"Success"</Tag>
|
||||
<Tag closable=true on_close=success variant=TagVariant::Warning>"Warning"</Tag>
|
||||
<Tag closable=true on_close=success variant=TagVariant::Error>"Error"</Tag>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
view! {
|
||||
<Space>
|
||||
<Text>"text"</Text>
|
||||
<Text code=true>"code"</Text>
|
||||
<Text tag=TextTag::Code>"code"</Text>
|
||||
<Caption1>"Caption1"</Caption1>
|
||||
<Caption1Strong>"Caption1Strong"</Caption1Strong>
|
||||
<Body1>"Body1"</Body1>
|
||||
</Space>
|
||||
}
|
||||
```
|
35
demo_markdown/docs/textarea/mod.md
Normal file
35
demo_markdown/docs/textarea/mod.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Input
|
||||
|
||||
```rust demo
|
||||
let value = RwSignal::new(String::from("o"));
|
||||
|
||||
view! {
|
||||
<Textarea value placeholder="Textarea"/>
|
||||
}
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
```rust demo
|
||||
let value = RwSignal::new(String::from("o"));
|
||||
|
||||
view! {
|
||||
<Textarea value disabled=true/>
|
||||
}
|
||||
```
|
||||
|
||||
### Resize
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<Textarea placeholder=r#"Textarea with resize set to "none""#/>
|
||||
<Textarea placeholder=r#"Textarea with resize set to "vertical""# resize=TextareaResize::Vertical/>
|
||||
<Textarea placeholder=r#"Textarea with resize set to "horizontal""# resize=TextareaResize::Horizontal/>
|
||||
<Textarea placeholder=r#"Textarea with resize set to "both""# resize=TextareaResize::Both/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
### Textarea Props
|
||||
|
63
demo_markdown/docs/theme/mod.md
vendored
63
demo_markdown/docs/theme/mod.md
vendored
|
@ -1,63 +0,0 @@
|
|||
# Theme
|
||||
|
||||
### ThemeProvider
|
||||
|
||||
```rust demo
|
||||
let theme = create_rw_signal(Theme::light());
|
||||
|
||||
view! {
|
||||
<ThemeProvider theme>
|
||||
<Card>
|
||||
<Space>
|
||||
<Button on_click=move |_| theme.set(Theme::light())>"Light"</Button>
|
||||
<Button on_click=move |_| theme.set(Theme::dark())>"Dark"</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
</ThemeProvider>
|
||||
}
|
||||
```
|
||||
|
||||
### GlobalStyle
|
||||
|
||||
You can use GlobalStyle to sync common global style to the body element.
|
||||
|
||||
```rust
|
||||
let theme = create_rw_signal(Theme::light());
|
||||
|
||||
view! {
|
||||
<ThemeProvider theme>
|
||||
<GlobalStyle />
|
||||
"..."
|
||||
</ThemeProvider>
|
||||
}
|
||||
```
|
||||
|
||||
### Customize Theme
|
||||
|
||||
```rust demo
|
||||
let theme = create_rw_signal(Theme::light());
|
||||
let on_customize_theme = move |_| {
|
||||
theme.update(|theme| {
|
||||
theme.common.color_primary = "#f5222d".to_string();
|
||||
theme.common.color_primary_hover = "#ff4d4f".to_string();
|
||||
theme.common.color_primary_active = "#cf1322".to_string();
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<ThemeProvider theme>
|
||||
<Card>
|
||||
<Space>
|
||||
<Button on_click=move |_| theme.set(Theme::light())>"Light"</Button>
|
||||
<Button on_click=on_customize_theme>"Customize Theme"</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
</ThemeProvider>
|
||||
}
|
||||
```
|
||||
|
||||
### ThemeProvider Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| ----- | ------------------------- | -------------------- | ----------- |
|
||||
| theme | `Option<RwSignal<Theme>>` | `Default::default()` | Theme. |
|
|
@ -3,10 +3,14 @@
|
|||
```rust demo
|
||||
use chrono::prelude::*;
|
||||
|
||||
let value = create_rw_signal(Some(Local::now().time()));
|
||||
let value = RwSignal::new(Local::now().time());
|
||||
let option_value = RwSignal::new(Local::now().time());
|
||||
|
||||
view! {
|
||||
<Space vertical=true>
|
||||
<TimePicker value />
|
||||
<TimePicker value=option_value />
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
||||
|
|
64
demo_markdown/docs/toast/mod.md
Normal file
64
demo_markdown/docs/toast/mod.md
Normal file
|
@ -0,0 +1,64 @@
|
|||
# Toast
|
||||
|
||||
```rust demo
|
||||
let toaster = ToasterInjection::expect_context();
|
||||
|
||||
let on_click = move |_| {
|
||||
toaster.dispatch_toast(view! {
|
||||
<Toast>
|
||||
<ToastTitle>"Email sent"</ToastTitle>
|
||||
<ToastBody>
|
||||
"This is a toast body"
|
||||
<ToastBodySubtitle slot>
|
||||
"Subtitle"
|
||||
</ToastBodySubtitle>
|
||||
</ToastBody>
|
||||
<ToastFooter>
|
||||
"Footer"
|
||||
// <Link>Action</Link>
|
||||
// <Link>Action</Link>
|
||||
</ToastFooter>
|
||||
</Toast>
|
||||
}.into_any(), Default::default());
|
||||
};
|
||||
|
||||
view! {
|
||||
<Button on_click=on_click>"Make toast"</Button>
|
||||
}
|
||||
```
|
||||
|
||||
### Toast Positions
|
||||
|
||||
```rust demo
|
||||
let toaster = ToasterInjection::expect_context();
|
||||
|
||||
fn dispatch_toast(toaster: ToasterInjection, position: ToastPosition) {
|
||||
toaster.dispatch_toast(view! {
|
||||
<Toast>
|
||||
<ToastTitle>"Email sent"</ToastTitle>
|
||||
<ToastBody>
|
||||
"This is a toast body"
|
||||
<ToastBodySubtitle slot>
|
||||
"Subtitle"
|
||||
</ToastBodySubtitle>
|
||||
</ToastBody>
|
||||
<ToastFooter>
|
||||
"Footer"
|
||||
// <Link>Action</Link>
|
||||
// <Link>Action</Link>
|
||||
</ToastFooter>
|
||||
</Toast>
|
||||
}.into_any(), ToastOptions::default().with_position(position));
|
||||
};
|
||||
|
||||
view! {
|
||||
<Space>
|
||||
<Button on_click=move |_| dispatch_toast(toaster, ToastPosition::Bottom)>"Bottom"</Button>
|
||||
<Button on_click=move |_| dispatch_toast(toaster, ToastPosition::BottomStart)>"BottomStart"</Button>
|
||||
<Button on_click=move |_| dispatch_toast(toaster, ToastPosition::BottomEnd)>"BottomEnd"</Button>
|
||||
<Button on_click=move |_| dispatch_toast(toaster, ToastPosition::Top)>"Top"</Button>
|
||||
<Button on_click=move |_| dispatch_toast(toaster, ToastPosition::TopStart)>"Topstart"</Button>
|
||||
<Button on_click=move |_| dispatch_toast(toaster, ToastPosition::TopEnd)>"TopEnd"</Button>
|
||||
</Space>
|
||||
}
|
||||
```
|
|
@ -1,13 +1,15 @@
|
|||
# Upload
|
||||
|
||||
```rust demo
|
||||
let message = use_message();
|
||||
let custom_request = move |file_list: FileList| {
|
||||
message.create(
|
||||
format!("Number of uploaded files: {}", file_list.length()),
|
||||
MessageVariant::Success,
|
||||
Default::default(),
|
||||
);
|
||||
use send_wrapper::SendWrapper;
|
||||
|
||||
// let message = use_message();
|
||||
let custom_request = move |file_list: SendWrapper<FileList>| {
|
||||
// message.create(
|
||||
// format!("Number of uploaded files: {}", file_list.length()),
|
||||
// MessageVariant::Success,
|
||||
// Default::default(),
|
||||
// );
|
||||
};
|
||||
|
||||
view!{
|
||||
|
@ -22,13 +24,14 @@ view!{
|
|||
### Drag to upload
|
||||
|
||||
```rust demo
|
||||
let message = use_message();
|
||||
|
||||
// let message = use_message();
|
||||
let custom_request = move |file_list: FileList| {
|
||||
message.create(
|
||||
format!("Number of uploaded files: {}", file_list.length()),
|
||||
MessageVariant::Success,
|
||||
Default::default(),
|
||||
);
|
||||
// message.create(
|
||||
// format!("Number of uploaded files: {}", file_list.length()),
|
||||
// MessageVariant::Success,
|
||||
// Default::default(),
|
||||
// );
|
||||
};
|
||||
|
||||
view! {
|
||||
|
|
|
@ -21,15 +21,11 @@ macro_rules! file_path {
|
|||
pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let file_list = file_path! {
|
||||
"DevelopmentComponentsMdPage" => "../docs/_guide/development/components.md",
|
||||
"DevelopmentGuideMdPage" => "../docs/_guide/development/guide.md",
|
||||
"InstallationMdPage" => "../docs/_guide/installation.md",
|
||||
"ServerSiderRenderingMdPage" => "../docs/_guide/server_sider_rendering.md",
|
||||
"UsageMdPage" => "../docs/_guide/usage.md",
|
||||
"NavBarMdPage" => "../docs/_mobile/nav_bar/mod.md",
|
||||
"TabbarMdPage" => "../docs/_mobile/tabbar/mod.md",
|
||||
"ToastMdPage" => "../docs/_mobile/toast/mod.md",
|
||||
"AlertMdPage" => "../docs/alert/mod.md",
|
||||
"AnchorMdPage" => "../docs/anchor/mod.md",
|
||||
"AccordionMdPage" => "../../thaw/src/accordion/docs/mod.md",
|
||||
// "AlertMdPage" => "../docs/alert/mod.md",
|
||||
"AnchorMdPage" => "../../thaw/src/anchor/docs/mod.md",
|
||||
"AutoCompleteMdPage" => "../docs/auto_complete/mod.md",
|
||||
"AvatarMdPage" => "../docs/avatar/mod.md",
|
||||
"BackTopMdPage" => "../docs/back_top/mod.md",
|
||||
|
@ -39,40 +35,41 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
|
|||
"CalendarMdPage" => "../docs/calendar/mod.md",
|
||||
"CardMdPage" => "../docs/card/mod.md",
|
||||
"CheckboxMdPage" => "../docs/checkbox/mod.md",
|
||||
"CollapseMdPage" => "../docs/collapse/mod.md",
|
||||
"ColorPickerMdPage" => "../docs/color_picker/mod.md",
|
||||
"ComboboxMdPage" => "../../thaw/src/combobox/docs/mod.md",
|
||||
"ConfigProviderMdPage" => "../docs/config_provider/mod.md",
|
||||
"DatePickerMdPage" => "../docs/date_picker/mod.md",
|
||||
"DialogMdPage" => "../docs/dialog/mod.md",
|
||||
"DividerMdPage" => "../docs/divider/mod.md",
|
||||
"DrawerMdPage" => "../docs/drawer/mod.md",
|
||||
"GridMdPage" => "../docs/grid/mod.md",
|
||||
"IconMdPage" => "../docs/icon/mod.md",
|
||||
"ImageMdPage" => "../docs/image/mod.md",
|
||||
"InputMdPage" => "../docs/input/mod.md",
|
||||
"InputNumberMdPage" => "../docs/input_number/mod.md",
|
||||
"LayoutMdPage" => "../docs/layout/mod.md",
|
||||
"LoadingBarMdPage" => "../docs/loading_bar/mod.md",
|
||||
"MenuMdPage" => "../docs/menu/mod.md",
|
||||
"MessageMdPage" => "../docs/message/mod.md",
|
||||
"ModalMdPage" => "../docs/modal/mod.md",
|
||||
"PaginationMdPage" => "../docs/pagination/mod.md",
|
||||
"MessageBarMdPage" => "../docs/message_bar/mod.md",
|
||||
"NavMdPage" => "../docs/nav/mod.md",
|
||||
"PaginationMdPage" => "../../thaw/src/pagination/docs/mod.md",
|
||||
"PopoverMdPage" => "../docs/popover/mod.md",
|
||||
"ProgressMdPage" => "../docs/progress/mod.md",
|
||||
"ProgressBarMdPage" => "../docs/progress_bar/mod.md",
|
||||
"RadioMdPage" => "../docs/radio/mod.md",
|
||||
"ScrollbarMdPage" => "../docs/scrollbar/mod.md",
|
||||
"SelectMdPage" => "../docs/select/mod.md",
|
||||
"SkeletonMdPage" => "../docs/skeleton/mod.md",
|
||||
"SliderMdPage" => "../docs/slider/mod.md",
|
||||
"SpaceMdPage" => "../docs/space/mod.md",
|
||||
"SpinButtonMdPage" => "../docs/spin_button/mod.md",
|
||||
"SpinnerMdPage" => "../docs/spinner/mod.md",
|
||||
"SwitchMdPage" => "../docs/switch/mod.md",
|
||||
"TabListMdPage" => "../docs/tab_list/mod.md",
|
||||
"TableMdPage" => "../docs/table/mod.md",
|
||||
"TabsMdPage" => "../docs/tabs/mod.md",
|
||||
"TagMdPage" => "../docs/tag/mod.md",
|
||||
"ThemeMdPage" => "../docs/theme/mod.md",
|
||||
"TextMdPage" => "../docs/text/mod.md",
|
||||
"TextareaMdPage" => "../docs/textarea/mod.md",
|
||||
"TimePickerMdPage" => "../docs/time_picker/mod.md",
|
||||
"TypographyMdPage" => "../docs/typography/mod.md",
|
||||
"UploadMdPage" => "../docs/upload/mod.md",
|
||||
"DropdownMdPage" => "../docs/dropdown/mod.md"
|
||||
"ToastMdPage" => "../docs/toast/mod.md",
|
||||
"UploadMdPage" => "../docs/upload/mod.md"
|
||||
};
|
||||
|
||||
let mut fn_list = vec![];
|
||||
|
@ -98,7 +95,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
|
|||
links
|
||||
);
|
||||
syn::parse_str::<ItemFn>(&toc)
|
||||
.unwrap_or_else(|_| panic!("Cannot be resolved as a function: \n {toc}"))
|
||||
.expect(&format!("Cannot be resolved as a function: \n {toc}"))
|
||||
};
|
||||
|
||||
let demos: Vec<ItemFn> = demos
|
||||
|
|
|
@ -18,7 +18,8 @@ pub fn to_tokens(code_block: &NodeCodeBlock, demos: &mut Vec<String>) -> TokenSt
|
|||
let literal = langs
|
||||
.iter()
|
||||
.find(|lang| lang != &&"demo")
|
||||
.and_then(|lang| highlight_to_html(&code_block.literal, lang))
|
||||
.map(|lang| highlight_to_html(&code_block.literal, lang))
|
||||
.flatten()
|
||||
.unwrap_or_else(|| {
|
||||
is_highlight = false;
|
||||
code_block.literal.clone()
|
||||
|
@ -27,8 +28,7 @@ pub fn to_tokens(code_block: &NodeCodeBlock, demos: &mut Vec<String>) -> TokenSt
|
|||
quote! {
|
||||
<Demo>
|
||||
<#demo />
|
||||
<DemoCode slot is_highlight=#is_highlight>
|
||||
#literal
|
||||
<DemoCode slot is_highlight=#is_highlight text=#literal>
|
||||
</DemoCode>
|
||||
</Demo>
|
||||
}
|
||||
|
@ -36,15 +36,15 @@ pub fn to_tokens(code_block: &NodeCodeBlock, demos: &mut Vec<String>) -> TokenSt
|
|||
let mut is_highlight = true;
|
||||
let literal = langs
|
||||
.first()
|
||||
.and_then(|lang| highlight_to_html(&code_block.literal, lang))
|
||||
.map(|lang| highlight_to_html(&code_block.literal, lang))
|
||||
.flatten()
|
||||
.unwrap_or_else(|| {
|
||||
is_highlight = false;
|
||||
code_block.literal.clone()
|
||||
});
|
||||
quote! {
|
||||
<Demo>
|
||||
<DemoCode slot is_highlight=#is_highlight>
|
||||
#literal
|
||||
<DemoCode slot is_highlight=#is_highlight text=#literal>
|
||||
</DemoCode>
|
||||
</Demo>
|
||||
}
|
||||
|
@ -54,8 +54,10 @@ pub fn to_tokens(code_block: &NodeCodeBlock, demos: &mut Vec<String>) -> TokenSt
|
|||
static SYNTAX_SET: OnceLock<SyntaxSet> = OnceLock::new();
|
||||
|
||||
fn highlight_to_html(text: &str, syntax: &str) -> Option<String> {
|
||||
let syntax_set = SYNTAX_SET.get_or_init(SyntaxSet::load_defaults_newlines);
|
||||
let syntax = syntax_set.find_syntax_by_token(syntax)?;
|
||||
let syntax_set = SYNTAX_SET.get_or_init(|| SyntaxSet::load_defaults_newlines());
|
||||
let Some(syntax) = syntax_set.find_syntax_by_token(syntax) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut html_generator = ClassedHTMLGenerator::new_with_class_style(
|
||||
syntax,
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
mod code_block;
|
||||
|
||||
use comrak::{
|
||||
nodes::{AstNode, LineColumn, NodeValue},
|
||||
nodes::{AstNode, LineColumn, NodeLink, NodeValue},
|
||||
parse_document, Arena,
|
||||
};
|
||||
use proc_macro2::{Ident, Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::ItemMacro;
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn parse_markdown(
|
||||
md_text: &str,
|
||||
) -> Result<(TokenStream, Vec<String>, Vec<(String, String)>), String> {
|
||||
|
@ -19,7 +18,7 @@ pub fn parse_markdown(
|
|||
let mut options = comrak::Options::default();
|
||||
options.extension.table = true;
|
||||
|
||||
let root = parse_document(&arena, md_text, &options);
|
||||
let root = parse_document(&arena, &md_text, &options);
|
||||
let body = iter_nodes(md_text, root, &mut demos, &mut toc);
|
||||
Ok((body, demos, toc))
|
||||
}
|
||||
|
@ -48,12 +47,10 @@ fn iter_nodes<'a>(
|
|||
NodeValue::HtmlBlock(node_html_block) => {
|
||||
let html =
|
||||
syn::parse_str::<ItemMacro>(&format!("view! {{ {} }}", node_html_block.literal))
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
.expect(&format!(
|
||||
"Cannot be resolved as a macro: \n {}",
|
||||
node_html_block.literal
|
||||
)
|
||||
});
|
||||
));
|
||||
quote!(
|
||||
{
|
||||
#html
|
||||
|
@ -67,10 +64,10 @@ fn iter_nodes<'a>(
|
|||
),
|
||||
NodeValue::Heading(node_h) => {
|
||||
let sourcepos = node.data.borrow().sourcepos;
|
||||
let text = range_text(md_text, sourcepos.start, sourcepos.end);
|
||||
let text = range_text(md_text, sourcepos.start.clone(), sourcepos.end.clone());
|
||||
let level = node_h.level as usize + 1;
|
||||
let text = text[level..].to_string();
|
||||
let h_id = text.replace(' ', "-").to_ascii_lowercase().to_string();
|
||||
let h_id = format!("{}", text.replace(' ', "-").to_ascii_lowercase());
|
||||
toc.push((h_id.clone(), text));
|
||||
let h = Ident::new(&format!("h{}", node_h.level), Span::call_site());
|
||||
quote!(
|
||||
|
@ -101,22 +98,22 @@ fn iter_nodes<'a>(
|
|||
|
||||
quote!(
|
||||
<div class="demo-md-table-box">
|
||||
<Table single_column=true>
|
||||
<thead>
|
||||
<Table attr:style="table-layout: auto">
|
||||
<TableHeader>
|
||||
#(#header_children)*
|
||||
</thead>
|
||||
<tbody>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
#(#children)*
|
||||
</tbody>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
NodeValue::TableRow(_) => {
|
||||
quote!(
|
||||
<tr>
|
||||
<TableRow>
|
||||
#(#children)*
|
||||
</tr>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
NodeValue::TableCell => {
|
||||
|
@ -127,15 +124,15 @@ fn iter_nodes<'a>(
|
|||
};
|
||||
if is_header {
|
||||
quote!(
|
||||
<th>
|
||||
<TableHeaderCell>
|
||||
#(#children)*
|
||||
</th>
|
||||
</TableHeaderCell>
|
||||
)
|
||||
} else {
|
||||
quote!(
|
||||
<td>
|
||||
<TableCell>
|
||||
#(#children)*
|
||||
</td>
|
||||
</TableCell>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +146,7 @@ fn iter_nodes<'a>(
|
|||
NodeValue::Code(node_code) => {
|
||||
let code = node_code.literal.clone();
|
||||
quote!(
|
||||
<Text code=true>
|
||||
<Text tag=TextTag::Code>
|
||||
#code
|
||||
</Text>
|
||||
)
|
||||
|
@ -159,7 +156,15 @@ fn iter_nodes<'a>(
|
|||
NodeValue::Strong => quote!("Strong todo!!!"),
|
||||
NodeValue::Strikethrough => quote!("Strikethrough todo!!!"),
|
||||
NodeValue::Superscript => quote!("Superscript todo!!!"),
|
||||
NodeValue::Link(_) => quote!("Link todo!!!"),
|
||||
NodeValue::Link(node_link) => {
|
||||
let NodeLink { url, title } = node_link;
|
||||
|
||||
quote!(
|
||||
<a href=#url title=#title>
|
||||
#(#children)*
|
||||
</a>
|
||||
)
|
||||
}
|
||||
NodeValue::Image(_) => quote!("Image todo!!!"),
|
||||
NodeValue::FootnoteReference(_) => quote!("FootnoteReference todo!!!"),
|
||||
NodeValue::MultilineBlockQuote(_) => quote!("FootnoteReference todo!!!"),
|
||||
|
@ -191,7 +196,7 @@ fn range_text(text: &str, start: LineColumn, end: LineColumn) -> &str {
|
|||
let mut current_line_num = start_line + 1;
|
||||
while current_line_num < end_line {
|
||||
let next_line = lines.next().unwrap_or("");
|
||||
start_line_text = next_line;
|
||||
start_line_text = &next_line;
|
||||
current_line_num += 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,30 +9,23 @@ crate-type = ["cdylib", "rlib"]
|
|||
[dependencies]
|
||||
axum = { version = "0.7.4", optional = true }
|
||||
console_error_panic_hook = "0.1"
|
||||
console_log = "1"
|
||||
cfg-if = "1"
|
||||
leptos = { version = "0.6.10" }
|
||||
leptos_axum = { version = "0.6.10", optional = true }
|
||||
leptos_meta = { version = "0.6.10" }
|
||||
leptos_router = { version = "0.6.10" }
|
||||
log = "0.4"
|
||||
simple_logger = "4"
|
||||
tokio = { version = "1.35.1", features = ["rt-multi-thread"], optional = true }
|
||||
tower = { version = "0.4.13", optional = true }
|
||||
tower-http = { version = "0.5.1", features = ["fs"], optional = true }
|
||||
leptos = { git = "https://github.com/leptos-rs/leptos", rev = "867036559489e86c1f25cdf7133d803d88b0579b" }
|
||||
leptos_axum = { git = "https://github.com/leptos-rs/leptos", rev = "867036559489e86c1f25cdf7133d803d88b0579b", optional = true }
|
||||
leptos_meta = { git = "https://github.com/leptos-rs/leptos", rev = "867036559489e86c1f25cdf7133d803d88b0579b" }
|
||||
leptos_router = { git = "https://github.com/leptos-rs/leptos", rev = "867036559489e86c1f25cdf7133d803d88b0579b" }
|
||||
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
||||
tower = { version = "0.4", optional = true }
|
||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
wasm-bindgen = "=0.2.92"
|
||||
thiserror = "1.0.56"
|
||||
tracing = { version = "0.1.40", optional = true }
|
||||
http = "0.2.8"
|
||||
thiserror = "1"
|
||||
tracing = { version = "0.1", optional = true }
|
||||
http = "1"
|
||||
console_log = "1"
|
||||
log = "0.4"
|
||||
demo = { path = "../../demo", default-features = false }
|
||||
|
||||
[features]
|
||||
hydrate = [
|
||||
"leptos/hydrate",
|
||||
"leptos_meta/hydrate",
|
||||
"leptos_router/hydrate",
|
||||
"demo/hydrate",
|
||||
]
|
||||
hydrate = ["leptos/hydrate", "demo/hydrate"]
|
||||
ssr = [
|
||||
"dep:axum",
|
||||
"dep:tokio",
|
||||
|
|
74
examples/ssr_axum/end2end/package-lock.json
generated
74
examples/ssr_axum/end2end/package-lock.json
generated
|
@ -1,74 +0,0 @@
|
|||
{
|
||||
"name": "end2end",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "end2end",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.28.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz",
|
||||
"integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.28.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz",
|
||||
"integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@playwright/test": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz",
|
||||
"integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.28.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.11.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
|
||||
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
|
||||
"dev": true
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.28.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz",
|
||||
"integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"name": "end2end",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.28.0"
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
import type { PlaywrightTestConfig } from "@playwright/test";
|
||||
import { devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
const config: PlaywrightTestConfig = {
|
||||
testDir: "./tests",
|
||||
/* Maximum time one test can run for. */
|
||||
timeout: 30 * 1000,
|
||||
expect: {
|
||||
/**
|
||||
* Maximum time expect() should wait for the condition to be met.
|
||||
* For example in `await expect(locator).toHaveText();`
|
||||
*/
|
||||
timeout: 5000,
|
||||
},
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: "html",
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
|
||||
actionTimeout: 0,
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://localhost:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "firefox",
|
||||
use: {
|
||||
...devices["Desktop Firefox"],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "webkit",
|
||||
use: {
|
||||
...devices["Desktop Safari"],
|
||||
},
|
||||
},
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: {
|
||||
// ...devices['Pixel 5'],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: {
|
||||
// ...devices['iPhone 12'],
|
||||
// },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: {
|
||||
// channel: 'msedge',
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: {
|
||||
// channel: 'chrome',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Folder for test artifacts such as screenshots, videos, traces, etc. */
|
||||
// outputDir: 'test-results/',
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// port: 3000,
|
||||
// },
|
||||
};
|
||||
|
||||
export default config;
|
|
@ -1,9 +0,0 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test("homepage has title and links to intro page", async ({ page }) => {
|
||||
await page.goto("http://localhost:3000/");
|
||||
|
||||
await expect(page).toHaveTitle("Welcome to Leptos");
|
||||
|
||||
await expect(page.locator("h1")).toHaveText("Welcome to Leptos!");
|
||||
});
|
21
examples/ssr_axum/src/app.rs
Normal file
21
examples/ssr_axum/src/app.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
pub use demo::App;
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::MetaTags;
|
||||
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
view! {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<AutoReload options=options.clone() />
|
||||
<HydrationScripts options/>
|
||||
<MetaTags/>
|
||||
</head>
|
||||
<body>
|
||||
<App/>
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
extract::State,
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
response::{IntoResponse, Response as AxumResponse},
|
||||
};
|
||||
use demo::App;
|
||||
use leptos::{view, LeptosOptions};
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
pub async fn file_and_error_handler(
|
||||
uri: Uri,
|
||||
State(options): State<LeptosOptions>,
|
||||
req: Request<Body>,
|
||||
) -> AxumResponse {
|
||||
let root = options.site_root.clone();
|
||||
let res = get_static_file(uri.clone(), &root).await.unwrap();
|
||||
|
||||
if res.status() == StatusCode::OK {
|
||||
res.into_response()
|
||||
} else {
|
||||
let handler = leptos_axum::render_app_to_stream(options.to_owned(), move || view! {<App/>});
|
||||
handler(req).await.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_static_file(uri: Uri, root: &str) -> Result<Response<Body>, (StatusCode, String)> {
|
||||
let req = Request::builder()
|
||||
.uri(uri.clone())
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
||||
// This path is relative to the cargo root
|
||||
match ServeDir::new(root).oneshot(req).await {
|
||||
Ok(res) => Ok(res.into_response()),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {err}"),
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
#[cfg(feature = "ssr")]
|
||||
pub mod fileserv;
|
||||
pub mod app;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
use demo::App;
|
||||
|
||||
// initializes logging using the `log` crate
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
use crate::app::*;
|
||||
let _ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
leptos::mount_to_body(App);
|
||||
leptos::mount::hydrate_body(App);
|
||||
}
|
||||
|
|
|
@ -1,34 +1,28 @@
|
|||
#[cfg(feature = "ssr")]
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use axum::{routing::post, Router};
|
||||
use demo::App;
|
||||
use leptos::*;
|
||||
use axum::Router;
|
||||
use leptos::prelude::*;
|
||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||
use ssr_axum::fileserv::file_and_error_handler;
|
||||
use ssr_axum::app::*;
|
||||
|
||||
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
||||
|
||||
// Setting get_configuration(None) means we'll be using cargo-leptos's env values
|
||||
// For deployment these variables are:
|
||||
// <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
|
||||
// Alternately a file can be specified such as Some("Cargo.toml")
|
||||
// The file would need to be included with the executable when moved to deployment
|
||||
let conf = get_configuration(None).await.unwrap();
|
||||
let conf = get_configuration(None).unwrap();
|
||||
let addr = conf.leptos_options.site_addr;
|
||||
let leptos_options = conf.leptos_options;
|
||||
let addr = leptos_options.site_addr;
|
||||
// Generate the list of routes in your Leptos App
|
||||
let routes = generate_route_list(App);
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
||||
.leptos_routes(&leptos_options, routes, App)
|
||||
.fallback(file_and_error_handler)
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
})
|
||||
.fallback(leptos_axum::file_and_error_handler(shell))
|
||||
.with_state(leptos_options);
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
log::info!("listening on http://{}", &addr);
|
||||
log!("listening on http://{}", &addr);
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await
|
||||
|
|
13
logo.svg
13
logo.svg
|
@ -1,11 +1,10 @@
|
|||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle cx="12" cy="12" r="12" fill="#0078ff" />
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 325 325">
|
||||
<path
|
||||
d="M1116.79,462.73l-2-7.25-.65-2-1.43-4.14-2.12-5.4-2.88-6.28-2.27-4.34-4.3-7-1.82-2.63-1.36-1.84-5.72-6.83-6.77-6.62-1.27-1.09-1.54-1.28-7.45-5.46-5.41-3.34-4.53-2.45L1055,390.15l-.65-.25-.25-.1-.56-.21-11-3.58-4.11-1.08-6.65-1.5-2-.39-7-1.21-6.54-.89-2.91-.34-6.22-.59-5.24-.4-5.51-.31-4.92-.21-4.57-.15-6.67-.14-4.43-.05-4.35,0-7,0h-8.67l-7,0-4.35,0-4.44.05-6.67.14-4.57.15-4.92.21-5.51.31-5.24.4-6.21.59-2.91.34-6.55.89-7,1.21-2,.39-6.66,1.5-4.1,1.08-11,3.58-.55.21-.25.1-.66.25-10.23,4.57-4.53,2.45L845,400.51,837.51,406,836,407.25l-1.27,1.09L827.93,415l-5.72,6.83-1.35,1.84L819,426.26l-4.29,7-2.28,4.34-2.88,6.28-2.12,5.4L806,453.44l-.65,2-2,7.25L802.13,468l-.27,1.24-.38,1.9-1.71,10.3-.47,3.62-.51,4.62-.54,6-.36,5.17-.3,5.83-.16,4-.16,6-.11,6.26,0,3.27,0,6.29v15.82l0,6.29,0,3.28.11,6.25.16,6,.16,4,.3,5.83.36,5.16.54,6,.51,4.62.47,3.62,1.71,10.31.38,1.89.27,1.25,1.23,5.28,2,7.25.65,2,1.43,4.15,2.12,5.4,2.88,6.28,2.28,4.33,4.29,7,1.83,2.62,1.35,1.84,5.72,6.83,6.77,6.62,1.27,1.09,1.54,1.28,7.45,5.47,5.42,3.33,4.53,2.45,10.23,4.57.66.26.25.09.55.21,11,3.58,4.1,1.08,6.66,1.5,2,.39,7,1.21,6.55.9,2.91.33,6.21.6,5.24.39,5.51.32,4.92.21,4.57.14,6.67.14,4.44,0,4.35,0,7,0h8.67l7,0,4.35,0,4.43,0,6.67-.14,4.57-.14,4.92-.21,5.51-.32,5.24-.39,6.22-.6,2.91-.33,6.54-.9,7-1.21,2-.39,6.65-1.5,4.11-1.08,11-3.58.56-.21.25-.09.65-.26,10.24-4.57,4.53-2.45,5.41-3.33,7.45-5.47,1.54-1.28,1.27-1.09,6.77-6.62,5.72-6.83,1.36-1.84,1.82-2.62,4.3-7,2.27-4.33,2.88-6.28,2.12-5.4,1.43-4.15.65-2,2-7.25,1.24-5.28.26-1.25.38-1.89,1.71-10.31.47-3.62.51-4.62.55-6,.35-5.16.31-5.83.15-4,.17-6,.1-6.25,0-3.28,0-6.29V532.55l0-6.29,0-3.27-.1-6.26-.17-6-.15-4-.31-5.83-.35-5.17-.55-6-.51-4.62-.47-3.62-1.71-10.3-.38-1.9L1118,468Z"
|
||||
transform="translate(-797.09 -378.7)"/>
|
||||
<path
|
||||
d="M21 11h-3.17l2.54-2.54a.996.996 0 0 0 0-1.41c-.39-.39-1.03-.39-1.42 0L15 11h-2V9l3.95-3.95c.39-.39.39-1.03 0-1.42a.996.996 0 0 0-1.41 0L13 6.17V3c0-.55-.45-1-1-1s-1 .45-1 1v3.17L8.46 3.63a.996.996 0 0 0-1.41 0c-.39.39-.39 1.03 0 1.42L11 9v2H9L5.05 7.05c-.39-.39-1.03-.39-1.42 0a.996.996 0 0 0 0 1.41L6.17 11H3c-.55 0-1 .45-1 1s.45 1 1 1h3.17l-2.54 2.54a.996.996 0 0 0 0 1.41c.39.39 1.03.39 1.42 0L9 13h2v2l-3.95 3.95c-.39.39-.39 1.03 0 1.42c.39.39 1.02.39 1.41 0L11 17.83V21c0 .55.45 1 1 1s1-.45 1-1v-3.17l2.54 2.54c.39.39 1.02.39 1.41 0c.39-.39.39-1.03 0-1.42L13 15v-2h2l3.95 3.95c.39.39 1.03.39 1.42 0a.996.996 0 0 0 0-1.41L17.83 13H21c.55 0 1-.45 1-1s-.45-1-1-1z"
|
||||
fill="#fff">
|
||||
fill="#fff" transform="translate(8 8) scale(13)">
|
||||
</path>
|
||||
</svg>
|
Before Width: | Height: | Size: 897 B After Width: | Height: | Size: 2.6 KiB |
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "thaw"
|
||||
version = "0.3.3"
|
||||
version = "0.4.0-alpha"
|
||||
edition = "2021"
|
||||
keywords = ["web", "leptos", "ui", "thaw", "component"]
|
||||
readme = "../README.md"
|
||||
|
@ -9,12 +9,14 @@ description = "An easy to use leptos component library"
|
|||
homepage = "https://github.com/thaw-ui/thaw"
|
||||
repository = "https://github.com/thaw-ui/thaw"
|
||||
license = "MIT"
|
||||
exclude = ["src/**/*.md"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
leptos = { version = "0.6.10" }
|
||||
leptos = { workspace = true }
|
||||
thaw_components = { workspace = true }
|
||||
thaw_macro = { workspace = true }
|
||||
thaw_utils = { workspace = true }
|
||||
web-sys = { version = "0.3.69", features = [
|
||||
"DomRect",
|
||||
|
@ -23,17 +25,21 @@ web-sys = { version = "0.3.69", features = [
|
|||
"DataTransfer",
|
||||
"ScrollToOptions",
|
||||
"ScrollBehavior",
|
||||
"TreeWalker",
|
||||
"NodeFilter",
|
||||
] }
|
||||
wasm-bindgen = "0.2.92"
|
||||
icondata_core = "0.1.0"
|
||||
icondata_ai = "0.0.10"
|
||||
uuid = { version = "1.7.0", features = ["v4"] }
|
||||
uuid = { version = "1.10.0", features = ["v4", "js"] }
|
||||
cfg-if = "1.0.0"
|
||||
chrono = "0.4.35"
|
||||
palette = "0.7.5"
|
||||
num-traits = "0.2.18"
|
||||
send_wrapper = "0.6"
|
||||
|
||||
[features]
|
||||
csr = ["leptos/csr", "thaw_components/csr", "thaw_utils/csr"]
|
||||
ssr = ["leptos/ssr", "thaw_components/ssr", "thaw_utils/ssr"]
|
||||
hydrate = ["leptos/hydrate", "thaw_components/hydrate", "thaw_utils/hydrate"]
|
||||
nightly = ["leptos/nightly", "thaw_utils/nightly"]
|
||||
|
|
4
thaw/src/_aria/active_descendant/mod.rs
Normal file
4
thaw/src/_aria/active_descendant/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
mod use_active_descendant;
|
||||
mod use_option_walker;
|
||||
|
||||
pub use use_active_descendant::{use_active_descendant, ActiveDescendantController};
|
99
thaw/src/_aria/active_descendant/use_active_descendant.rs
Normal file
99
thaw/src/_aria/active_descendant/use_active_descendant.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
use super::use_option_walker::{use_option_walker, OptionWalker};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::{cell::RefCell, sync::Arc};
|
||||
use thaw_utils::scroll_into_view;
|
||||
use web_sys::{HtmlElement, Node};
|
||||
|
||||
/// Applied to the element that is active descendant
|
||||
const ACTIVEDESCENDANT_ATTRIBUTE: &str = "data-activedescendant";
|
||||
|
||||
/// Applied to the active descendant when the user is navigating with keyboard
|
||||
const ACTIVEDESCENDANT_FOCUSVISIBLE_ATTRIBUTE: &str = "data-activedescendant-focusvisible";
|
||||
|
||||
pub fn use_active_descendant<MF>(
|
||||
match_option: MF,
|
||||
) -> (Arc<dyn Fn(Node) + Send + Sync>, ActiveDescendantController)
|
||||
where
|
||||
MF: Fn(HtmlElement) -> bool + Send + Sync + 'static,
|
||||
{
|
||||
let (set_listbox, option_walker) = use_option_walker(match_option);
|
||||
//TODO
|
||||
let set_listbox = Arc::new(move |node| {
|
||||
set_listbox(&node);
|
||||
});
|
||||
let controller = ActiveDescendantController {
|
||||
option_walker,
|
||||
active: Arc::new(SendWrapper::new(Default::default())),
|
||||
// last_active: Default::default(),
|
||||
};
|
||||
|
||||
(set_listbox, controller)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ActiveDescendantController {
|
||||
option_walker: OptionWalker,
|
||||
active: Arc<SendWrapper<RefCell<Option<HtmlElement>>>>,
|
||||
// last_active: RefCell<Option<HtmlElement>>,
|
||||
}
|
||||
|
||||
impl ActiveDescendantController {
|
||||
fn blur_active_descendant(&self) {
|
||||
let mut active = self.active.borrow_mut();
|
||||
let Some(active_el) = active.as_mut() else {
|
||||
return;
|
||||
};
|
||||
let _ = active_el.remove_attribute(ACTIVEDESCENDANT_ATTRIBUTE);
|
||||
let _ = active_el.remove_attribute(ACTIVEDESCENDANT_FOCUSVISIBLE_ATTRIBUTE);
|
||||
|
||||
*active = None;
|
||||
}
|
||||
|
||||
fn focus_active_descendant(&self, next_active: HtmlElement) {
|
||||
self.blur_active_descendant();
|
||||
scroll_into_view(&next_active);
|
||||
let _ = next_active.set_attribute(ACTIVEDESCENDANT_ATTRIBUTE, "");
|
||||
let _ = next_active.set_attribute(ACTIVEDESCENDANT_FOCUSVISIBLE_ATTRIBUTE, "");
|
||||
|
||||
*self.active.borrow_mut() = Some(next_active);
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveDescendantController {
|
||||
pub fn first(&self) {
|
||||
if let Some(first) = self.option_walker.first() {
|
||||
self.focus_active_descendant(first);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last(&self) {
|
||||
if let Some(last) = self.option_walker.last() {
|
||||
self.focus_active_descendant(last);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&self) {
|
||||
if let Some(next) = self.option_walker.next() {
|
||||
self.focus_active_descendant(next);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prev(&self) {
|
||||
if let Some(prev) = self.option_walker.prev() {
|
||||
self.focus_active_descendant(prev);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blur(&self) {
|
||||
self.blur_active_descendant();
|
||||
}
|
||||
|
||||
pub fn active(&self) -> Option<HtmlElement> {
|
||||
let active = self.active.borrow();
|
||||
if let Some(active) = active.as_ref() {
|
||||
Some(active.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
90
thaw/src/_aria/active_descendant/use_option_walker.rs
Normal file
90
thaw/src/_aria/active_descendant/use_option_walker.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use leptos::{prelude::document, reactive_graph::owner::StoredValue};
|
||||
use send_wrapper::SendWrapper;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::{closure::Closure, JsCast, UnwrapThrowExt};
|
||||
use web_sys::{HtmlElement, Node, NodeFilter, TreeWalker};
|
||||
|
||||
pub fn use_option_walker<MF>(match_option: MF) -> (Box<dyn Fn(&Node) + Send + Sync>, OptionWalker)
|
||||
where
|
||||
MF: Fn(HtmlElement) -> bool + Send + Sync + 'static,
|
||||
{
|
||||
let tree_walker = StoredValue::new(
|
||||
None::<(
|
||||
SendWrapper<TreeWalker>,
|
||||
SendWrapper<Closure<dyn Fn(Node) -> u32>>,
|
||||
)>,
|
||||
);
|
||||
let option_walker = OptionWalker(tree_walker);
|
||||
let match_option = Arc::new(match_option);
|
||||
let set_listbox = move |el: &Node| {
|
||||
let match_option = match_option.clone();
|
||||
let cb: Closure<dyn Fn(Node) -> u32> = Closure::new(move |node: Node| {
|
||||
if let Ok(html_element) = node.dyn_into() {
|
||||
if match_option(html_element) {
|
||||
return 1u32;
|
||||
}
|
||||
}
|
||||
|
||||
3u32
|
||||
});
|
||||
let mut node_filter = NodeFilter::new();
|
||||
node_filter.accept_node(cb.as_ref().unchecked_ref());
|
||||
|
||||
let tw = document()
|
||||
.create_tree_walker_with_what_to_show_and_filter(el, 0x1, Some(&node_filter))
|
||||
.unwrap_throw();
|
||||
tree_walker.set_value(Some((SendWrapper::new(tw), SendWrapper::new(cb))));
|
||||
};
|
||||
|
||||
(Box::new(set_listbox), option_walker)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OptionWalker(
|
||||
StoredValue<
|
||||
Option<(
|
||||
SendWrapper<TreeWalker>,
|
||||
SendWrapper<Closure<dyn Fn(Node) -> u32>>,
|
||||
)>,
|
||||
>,
|
||||
);
|
||||
|
||||
impl OptionWalker {
|
||||
pub fn first(&self) -> Option<HtmlElement> {
|
||||
self.0.with_value(|tree_walker| {
|
||||
let Some((tree_walker, _)) = tree_walker.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
tree_walker.set_current_node(&tree_walker.root());
|
||||
tree_walker.first_child().unwrap_throw()?.dyn_into().ok()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn last(&self) -> Option<HtmlElement> {
|
||||
self.0.with_value(|tree_walker| {
|
||||
let Some((tree_walker, _)) = tree_walker.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
tree_walker.set_current_node(&tree_walker.root());
|
||||
tree_walker.last_child().unwrap_throw()?.dyn_into().ok()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Option<HtmlElement> {
|
||||
self.0.with_value(|tree_walker| {
|
||||
let Some((tree_walker, _)) = tree_walker.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
tree_walker.next_node().unwrap_throw()?.dyn_into().ok()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn prev(&self) -> Option<HtmlElement> {
|
||||
self.0.with_value(|tree_walker| {
|
||||
let Some((tree_walker, _)) = tree_walker.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
tree_walker.previous_node().unwrap_throw()?.dyn_into().ok()
|
||||
})
|
||||
}
|
||||
}
|
3
thaw/src/_aria/mod.rs
Normal file
3
thaw/src/_aria/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod active_descendant;
|
||||
|
||||
pub use active_descendant::*;
|
73
thaw/src/accordion/accordion-item.css
Normal file
73
thaw/src/accordion/accordion-item.css
Normal file
|
@ -0,0 +1,73 @@
|
|||
.thaw-accordion-header {
|
||||
margin: 0;
|
||||
background-color: var(--colorTransparentBackground);
|
||||
color: var(--colorNeutralForeground1);
|
||||
border-radius: var(--borderRadiusMedium);
|
||||
}
|
||||
|
||||
.thaw-accordion-header__button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding-left: var(--spacingHorizontalMNudge);
|
||||
padding-right: var(--spacingHorizontalM);
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
text-align: unset;
|
||||
line-height: var(--lineHeightBase300);
|
||||
font-family: var(--fontFamilyBase);
|
||||
font-size: var(--fontSizeBase300);
|
||||
font-weight: var(--fontWeightRegular);
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
border-width: 0px;
|
||||
appearance: button;
|
||||
overflow: visible;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.thaw-accordion-header__expand-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
padding-right: var(--spacingHorizontalS);
|
||||
font-size: var(--fontSizeBase500);
|
||||
line-height: var(--lineHeightBase500);
|
||||
}
|
||||
|
||||
.thaw-accordion-header__expand-icon > svg {
|
||||
display: inline;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.thaw-accordion-panel {
|
||||
margin: 0 var(--spacingHorizontalM);
|
||||
}
|
||||
|
||||
.thaw-accordion-panel-enter-from,
|
||||
.thaw-accordion-panel-enter-to {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.thaw-accordion-panel-leave-to,
|
||||
.thaw-accordion-panel-enter-from {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
.thaw-accordion-panel-leave-active {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.15s cubic-bezier(0.4, 0, 0.2, 1) 0s,
|
||||
opacity 0.15s cubic-bezier(0, 0, 0.2, 1) 0s,
|
||||
padding-top 0.15s cubic-bezier(0.4, 0, 0.2, 1) 0s;
|
||||
}
|
||||
|
||||
.thaw-accordion-panel-enter-active {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.15s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
opacity 0.15s cubic-bezier(0.4, 0, 1, 1),
|
||||
padding-top 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
101
thaw/src/accordion/accordion_item.rs
Normal file
101
thaw/src/accordion/accordion_item.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
use crate::AccordionInjection;
|
||||
use leptos::{html, prelude::*};
|
||||
use thaw_components::CSSTransition;
|
||||
use thaw_utils::{class_list, mount_style, update, with, StoredMaybeSignal};
|
||||
|
||||
#[component]
|
||||
pub fn AccordionItem(
|
||||
#[prop(optional, into)] class: MaybeProp<String>,
|
||||
/// Required value that identifies this item inside an Accordion component.
|
||||
#[prop(into)]
|
||||
value: MaybeSignal<String>,
|
||||
accordion_header: AccordionHeader,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
mount_style("accordion-item", include_str!("./accordion-item.css"));
|
||||
let AccordionInjection {
|
||||
open_items,
|
||||
multiple,
|
||||
collapsible,
|
||||
} = AccordionInjection::expect_context();
|
||||
let panel_ref = NodeRef::<html::Div>::new();
|
||||
let value: StoredMaybeSignal<_> = value.into();
|
||||
|
||||
let is_show_panel = Memo::new(move |_| with!(|open_items, value| open_items.contains(value)));
|
||||
|
||||
let on_click = move |_| {
|
||||
let is_show_panel = is_show_panel.get_untracked();
|
||||
update!(move |open_items| {
|
||||
if is_show_panel {
|
||||
if collapsible {
|
||||
with!(|value| open_items.remove(value));
|
||||
} else if multiple {
|
||||
with!(|value| open_items.remove(value));
|
||||
}
|
||||
} else {
|
||||
if !multiple {
|
||||
open_items.clear();
|
||||
}
|
||||
open_items.insert(value.get_untracked());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<div class=class_list!["thaw-accordion-item", class]>
|
||||
<div class="thaw-accordion-header">
|
||||
<button
|
||||
class="thaw-accordion-header__button"
|
||||
aria-expanded=move || is_show_panel.get().to_string()
|
||||
type="button"
|
||||
on:click=on_click
|
||||
>
|
||||
<span
|
||||
class="thaw-accordion-header__expand-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 20 20"
|
||||
style=move || if is_show_panel.get() {
|
||||
"transform: rotate(90deg)"
|
||||
} else {
|
||||
"transform: rotate(0deg)"
|
||||
}
|
||||
>
|
||||
<path d="M7.65 4.15c.2-.2.5-.2.7 0l5.49 5.46c.21.22.21.57 0 .78l-5.49 5.46a.5.5 0 0 1-.7-.7L12.8 10 7.65 4.85a.5.5 0 0 1 0-.7Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
{(accordion_header.children)()}
|
||||
</button>
|
||||
</div>
|
||||
<CSSTransition
|
||||
node_ref=panel_ref
|
||||
show=is_show_panel
|
||||
name="thaw-accordion-panel"
|
||||
let:display
|
||||
>
|
||||
<div
|
||||
class="thaw-accordion-panel"
|
||||
node_ref=panel_ref
|
||||
style=move || display.get().unwrap_or_default()
|
||||
>
|
||||
{children()}
|
||||
</div>
|
||||
</CSSTransition>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[slot]
|
||||
pub struct AccordionHeader {
|
||||
children: Children,
|
||||
}
|
||||
|
||||
#[slot]
|
||||
pub struct AccordionPanel {
|
||||
children: Children,
|
||||
}
|
66
thaw/src/accordion/docs/mod.md
Normal file
66
thaw/src/accordion/docs/mod.md
Normal file
|
@ -0,0 +1,66 @@
|
|||
# Accordion
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Accordion>
|
||||
<AccordionItem value="leptos">
|
||||
<AccordionHeader slot>
|
||||
"Leptos"
|
||||
</AccordionHeader>
|
||||
"Build fast web applications with Rust."
|
||||
</AccordionItem>
|
||||
<AccordionItem value="thaw">
|
||||
<AccordionHeader slot>
|
||||
"Thaw"
|
||||
</AccordionHeader>
|
||||
"An easy to use leptos component library"
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
}
|
||||
```
|
||||
|
||||
### Collapsible
|
||||
|
||||
An accordion can have multiple panels collapsed at the same time.
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Accordion collapsible=true>
|
||||
<AccordionItem value="leptos">
|
||||
<AccordionHeader slot>
|
||||
"Leptos"
|
||||
</AccordionHeader>
|
||||
"Build fast web applications with Rust."
|
||||
</AccordionItem>
|
||||
<AccordionItem value="thaw">
|
||||
<AccordionHeader slot>
|
||||
"Thaw"
|
||||
</AccordionHeader>
|
||||
"An easy to use leptos component library"
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
}
|
||||
```
|
||||
|
||||
### Multiple
|
||||
|
||||
An accordion supports multiple panels expanded simultaneously. Since it's not simple to determine which panel will be opened by default, multiple will also be collapsed by default on the first render.
|
||||
|
||||
```rust demo
|
||||
view! {
|
||||
<Accordion multiple=true>
|
||||
<AccordionItem value="leptos">
|
||||
<AccordionHeader slot>
|
||||
"Leptos"
|
||||
</AccordionHeader>
|
||||
"Build fast web applications with Rust."
|
||||
</AccordionItem>
|
||||
<AccordionItem value="thaw">
|
||||
<AccordionHeader slot>
|
||||
"Thaw"
|
||||
</AccordionHeader>
|
||||
"An easy to use leptos component library"
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
}
|
||||
```
|
47
thaw/src/accordion/mod.rs
Normal file
47
thaw/src/accordion/mod.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
mod accordion_item;
|
||||
|
||||
pub use accordion_item::*;
|
||||
|
||||
use leptos::{context::Provider, prelude::*};
|
||||
use std::collections::HashSet;
|
||||
use thaw_utils::{class_list, Model};
|
||||
|
||||
#[component]
|
||||
pub fn Accordion(
|
||||
#[prop(optional, into)] class: MaybeProp<String>,
|
||||
/// Controls the state of the panel.
|
||||
#[prop(optional, into)]
|
||||
open_items: Model<HashSet<String>>,
|
||||
/// Indicates if Accordion support multiple Panels opened at the same time.
|
||||
#[prop(optional)]
|
||||
multiple: bool,
|
||||
/// Indicates if Accordion support multiple Panels closed at the same time.
|
||||
#[prop(optional)]
|
||||
collapsible: bool,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
<Provider value=AccordionInjection {
|
||||
open_items,
|
||||
collapsible,
|
||||
multiple
|
||||
}>
|
||||
<div class=class_list!["thaw-accordion", class]>
|
||||
{children()}
|
||||
</div>
|
||||
</Provider>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct AccordionInjection {
|
||||
pub open_items: Model<HashSet<String>>,
|
||||
pub multiple: bool,
|
||||
pub collapsible: bool,
|
||||
}
|
||||
|
||||
impl AccordionInjection {
|
||||
pub fn expect_context() -> AccordionInjection {
|
||||
expect_context()
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
.thaw-alert {
|
||||
position: relative;
|
||||
padding: 14px 20px 14px 42px;
|
||||
background-color: var(--thaw-background-color);
|
||||
border: 1px solid var(--thaw-border-color);
|
||||
border-radius: 3px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.thaw-alert__icon {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 10px;
|
||||
font-size: 24px;
|
||||
color: var(--thaw-icon-color);
|
||||
}
|
||||
|
||||
.thaw-alert__header {
|
||||
font-size: 16px;
|
||||
line-height: 19px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.thaw-alert__header + .thaw-alert__content {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
mod theme;
|
||||
|
||||
pub use theme::AlertTheme;
|
||||
|
||||
use crate::{theme::use_theme, Icon, Theme};
|
||||
use leptos::*;
|
||||
use thaw_components::OptionComp;
|
||||
use thaw_utils::{class_list, mount_style, OptionalProp};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum AlertVariant {
|
||||
Success,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl AlertVariant {
|
||||
fn theme_icon_color(&self, theme: &Theme) -> String {
|
||||
match self {
|
||||
AlertVariant::Success => theme.common.color_success.clone(),
|
||||
AlertVariant::Warning => theme.common.color_warning.clone(),
|
||||
AlertVariant::Error => theme.common.color_error.clone(),
|
||||
}
|
||||
}
|
||||
fn theme_background_color(&self, theme: &Theme) -> String {
|
||||
match self {
|
||||
AlertVariant::Success => theme.alert.success_background_color.clone(),
|
||||
AlertVariant::Warning => theme.alert.warning_background_color.clone(),
|
||||
AlertVariant::Error => theme.alert.error_background_color.clone(),
|
||||
}
|
||||
}
|
||||
fn theme_border_color(&self, theme: &Theme) -> String {
|
||||
match self {
|
||||
AlertVariant::Success => theme.alert.success_border_color.clone(),
|
||||
AlertVariant::Warning => theme.alert.warning_border_color.clone(),
|
||||
AlertVariant::Error => theme.alert.error_border_color.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Alert(
|
||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||
#[prop(optional, into)] title: Option<MaybeSignal<String>>,
|
||||
#[prop(into)] variant: MaybeSignal<AlertVariant>,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
mount_style("alert", include_str!("./alert.css"));
|
||||
let theme = use_theme(Theme::light);
|
||||
|
||||
let css_vars = create_memo({
|
||||
let variant = variant.clone();
|
||||
|
||||
move |_| {
|
||||
let mut css_vars = String::new();
|
||||
|
||||
theme.with(|theme| {
|
||||
let variant = variant.get();
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-icon-color: {};",
|
||||
variant.theme_icon_color(theme)
|
||||
));
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-background-color: {};",
|
||||
variant.theme_background_color(theme)
|
||||
));
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-border-color: {};",
|
||||
variant.theme_border_color(theme)
|
||||
));
|
||||
});
|
||||
|
||||
css_vars
|
||||
}
|
||||
});
|
||||
let icon = create_memo(move |_| match variant.get() {
|
||||
AlertVariant::Success => icondata_ai::AiCheckCircleFilled,
|
||||
AlertVariant::Warning => icondata_ai::AiExclamationCircleFilled,
|
||||
AlertVariant::Error => icondata_ai::AiCloseCircleFilled,
|
||||
});
|
||||
|
||||
view! {
|
||||
<div
|
||||
class=class_list!["thaw-alert", class.map(| c | move || c.get())]
|
||||
style=move || css_vars.get()
|
||||
>
|
||||
<Icon icon class="thaw-alert__icon"/>
|
||||
<div>
|
||||
<OptionComp value=title let:title>
|
||||
<div class="thaw-alert__header">{move || title.get()}</div>
|
||||
</OptionComp>
|
||||
<div class="thaw-alert__content">{children()}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
use crate::theme::ThemeMethod;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AlertTheme {
|
||||
pub success_background_color: String,
|
||||
pub success_border_color: String,
|
||||
pub warning_background_color: String,
|
||||
pub warning_border_color: String,
|
||||
pub error_background_color: String,
|
||||
pub error_border_color: String,
|
||||
}
|
||||
|
||||
impl ThemeMethod for AlertTheme {
|
||||
fn light() -> Self {
|
||||
Self {
|
||||
success_background_color: "#edf7f2".into(),
|
||||
success_border_color: "#c5e7d5".into(),
|
||||
warning_background_color: "#fef7ed".into(),
|
||||
warning_border_color: "#fae0b5".into(),
|
||||
error_background_color: "#fbeef1".into(),
|
||||
error_border_color: "#f3cbd3".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dark() -> Self {
|
||||
Self {
|
||||
success_background_color: "#2a947d40".into(),
|
||||
success_border_color: "#2a947d59".into(),
|
||||
warning_background_color: "#f08a0040".into(),
|
||||
warning_border_color: "#f08a0059".into(),
|
||||
error_background_color: "#d03a5240".into(),
|
||||
error_border_color: "#d03a5259".into(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,17 +8,6 @@
|
|||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
.thaw-anchor-background {
|
||||
max-width: 0;
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
width: 100%;
|
||||
background-color: var(--thaw-link-background-color);
|
||||
transition: top 0.15s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
max-width 0.15s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.thaw-anchor-rail {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
@ -28,7 +17,7 @@
|
|||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
transition: background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
background-color: var(--thaw-rail-background-color);
|
||||
background-color: var(--colorNeutralStroke2);
|
||||
}
|
||||
|
||||
.thaw-anchor-rail__bar {
|
||||
|
@ -41,21 +30,21 @@
|
|||
}
|
||||
|
||||
.thaw-anchor-rail__bar.thaw-anchor-rail__bar--active {
|
||||
background-color: var(--thaw-title-color-active);
|
||||
background-color: var(--colorBrandBackground);
|
||||
}
|
||||
|
||||
.thaw-anchor-link {
|
||||
padding: 0 0 0 16px;
|
||||
position: relative;
|
||||
line-height: 1.5;
|
||||
font-size: 13px;
|
||||
line-height: var(--lineHeightBase200);
|
||||
font-size: var(--fontSizeBase200);
|
||||
min-height: 1.5em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.thaw-anchor-link.thaw-anchor-link--active > .thaw-anchor-link__title {
|
||||
color: var(--thaw-title-color-active);
|
||||
color: var(--colorNeutralForeground2Active);
|
||||
}
|
||||
|
||||
.thaw-anchor-link__title {
|
||||
|
@ -70,8 +59,9 @@
|
|||
padding-right: 16px;
|
||||
color: inherit;
|
||||
transition: color 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: var(--colorNeutralForeground2);
|
||||
}
|
||||
|
||||
.thaw-anchor-link__title:hover {
|
||||
color: var(--thaw-title-color-hover);
|
||||
color: var(--colorNeutralForeground2Hover);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
use crate::use_anchor;
|
||||
use leptos::*;
|
||||
use super::AnchorInjection;
|
||||
use leptos::{html, prelude::*};
|
||||
use thaw_components::OptionComp;
|
||||
use thaw_utils::{class_list, OptionalProp, StoredMaybeSignal};
|
||||
use thaw_utils::{class_list, StoredMaybeSignal};
|
||||
|
||||
#[component]
|
||||
pub fn AnchorLink(
|
||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||
#[prop(optional, into)] class: MaybeProp<String>,
|
||||
/// The content of link.
|
||||
#[prop(into)] title: MaybeSignal<String>,
|
||||
/// The target of link.
|
||||
#[prop(into)] href: String,
|
||||
#[prop(optional)] children: Option<Children>,
|
||||
) -> impl IntoView {
|
||||
let anchor = use_anchor();
|
||||
let anchor = AnchorInjection::expect_context();
|
||||
|
||||
let title: StoredMaybeSignal<_> = title.into();
|
||||
let title_ref = NodeRef::<html::A>::new();
|
||||
|
@ -39,17 +41,15 @@ pub fn AnchorLink(
|
|||
});
|
||||
});
|
||||
|
||||
title_ref.on_load(move |title_el| {
|
||||
let _ = watch(
|
||||
move || is_active.get(),
|
||||
move |is_active, _, _| {
|
||||
if *is_active {
|
||||
Effect::new(move |_| {
|
||||
let Some(title_el) = title_ref.get() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if is_active.get() {
|
||||
let title_rect = title_el.get_bounding_client_rect();
|
||||
anchor.update_background_position(title_rect);
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -63,14 +63,12 @@ pub fn AnchorLink(
|
|||
|
||||
view! {
|
||||
<div class=class_list![
|
||||
"thaw-anchor-link", ("thaw-anchor-link--active", move || is_active.get()), class.map(| c
|
||||
| move || c.get())
|
||||
]>
|
||||
"thaw-anchor-link", ("thaw-anchor-link--active", move || is_active.get()), class]>
|
||||
<a
|
||||
href=href
|
||||
class="thaw-anchor-link__title"
|
||||
on:click=on_click
|
||||
ref=title_ref
|
||||
node_ref=title_ref
|
||||
title=move || title.get()
|
||||
>
|
||||
{move || title.get()}
|
||||
|
|
|
@ -1,62 +1,39 @@
|
|||
mod anchor_link;
|
||||
mod theme;
|
||||
|
||||
pub use anchor_link::AnchorLink;
|
||||
pub use theme::AnchorTheme;
|
||||
|
||||
use crate::{use_theme, Theme};
|
||||
use leptos::*;
|
||||
use std::cmp::Ordering;
|
||||
use thaw_utils::{add_event_listener_with_bool, class_list, mount_style, throttle, OptionalProp};
|
||||
use leptos::{context::Provider, html, prelude::*};
|
||||
use thaw_utils::{class_list, mount_style};
|
||||
use web_sys::{DomRect, Element};
|
||||
|
||||
#[component]
|
||||
pub fn Anchor(
|
||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||
#[prop(into, optional)] offset_target: Option<OffsetTarget>,
|
||||
#[prop(optional, into)] class: MaybeProp<String>,
|
||||
/// The element or selector used to calc offset of link elements.
|
||||
/// If you are not scrolling the entire document but only a part of it, you may need to set this.
|
||||
#[prop(into, optional)]
|
||||
offset_target: Option<OffsetTarget>,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
mount_style("anchor", include_str!("./anchor.css"));
|
||||
let theme = use_theme(Theme::light);
|
||||
let css_vars = Memo::new(move |_| {
|
||||
let mut css_vars = String::new();
|
||||
theme.with(|theme| {
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-rail-background-color: {};",
|
||||
theme.anchor.rail_background_color
|
||||
));
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-title-color-hover: {};",
|
||||
theme.common.color_primary_hover
|
||||
));
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-title-color-active: {};",
|
||||
theme.common.color_primary
|
||||
));
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-link-background-color: {}1a;",
|
||||
theme.common.color_primary
|
||||
));
|
||||
});
|
||||
css_vars
|
||||
});
|
||||
let anchor_ref = NodeRef::new();
|
||||
let background_ref = NodeRef::<html::Div>::new();
|
||||
let bar_ref = NodeRef::new();
|
||||
let element_ids = RwSignal::new(Vec::<String>::new());
|
||||
let active_id = RwSignal::new(None::<String>);
|
||||
|
||||
let _ = watch(
|
||||
move || active_id.get(),
|
||||
move |id, _, _| {
|
||||
if id.is_none() {
|
||||
if let Some(background_el) = background_ref.get_untracked() {
|
||||
let _ = background_el.style("max-width", "0");
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
{
|
||||
use leptos::ev;
|
||||
use std::cmp::Ordering;
|
||||
use thaw_utils::{add_event_listener_with_bool, throttle};
|
||||
|
||||
struct LinkInfo {
|
||||
top: f64,
|
||||
id: String,
|
||||
}
|
||||
}
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
let offset_target = send_wrapper::SendWrapper::new(offset_target);
|
||||
|
||||
let on_scroll = move || {
|
||||
element_ids.with(|ids| {
|
||||
let offset_target_top = if let Some(offset_target) = offset_target.as_ref() {
|
||||
|
@ -122,11 +99,16 @@ pub fn Anchor(
|
|||
on_cleanup(move || {
|
||||
scroll_handle.remove();
|
||||
});
|
||||
}
|
||||
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
|
||||
{
|
||||
let _ = offset_target;
|
||||
}
|
||||
|
||||
view! {
|
||||
<div
|
||||
class=class_list!["thaw-anchor", class.map(| c | move || c.get())]
|
||||
ref=anchor_ref
|
||||
style=move || css_vars.get()
|
||||
class=class_list!["thaw-anchor", class]
|
||||
node_ref=anchor_ref
|
||||
>
|
||||
<div class="thaw-anchor-rail">
|
||||
<div
|
||||
|
@ -136,13 +118,11 @@ pub fn Anchor(
|
|||
move || active_id.with(|id| id.is_some()),
|
||||
)
|
||||
|
||||
ref=bar_ref
|
||||
node_ref=bar_ref
|
||||
></div>
|
||||
</div>
|
||||
<div class="thaw-anchor-background" ref=background_ref></div>
|
||||
<Provider value=AnchorInjection::new(
|
||||
anchor_ref,
|
||||
background_ref,
|
||||
bar_ref,
|
||||
element_ids,
|
||||
active_id,
|
||||
|
@ -154,7 +134,6 @@ pub fn Anchor(
|
|||
#[derive(Clone)]
|
||||
pub(crate) struct AnchorInjection {
|
||||
anchor_ref: NodeRef<html::Div>,
|
||||
background_ref: NodeRef<html::Div>,
|
||||
bar_ref: NodeRef<html::Div>,
|
||||
element_ids: RwSignal<Vec<String>>,
|
||||
pub active_id: RwSignal<Option<String>>,
|
||||
|
@ -163,16 +142,18 @@ pub(crate) struct AnchorInjection {
|
|||
impl Copy for AnchorInjection {}
|
||||
|
||||
impl AnchorInjection {
|
||||
pub fn expect_context() -> Self {
|
||||
expect_context()
|
||||
}
|
||||
|
||||
fn new(
|
||||
anchor_ref: NodeRef<html::Div>,
|
||||
background_ref: NodeRef<html::Div>,
|
||||
bar_ref: NodeRef<html::Div>,
|
||||
element_ids: RwSignal<Vec<String>>,
|
||||
active_id: RwSignal<Option<String>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
anchor_ref,
|
||||
background_ref,
|
||||
bar_ref,
|
||||
element_ids,
|
||||
active_id,
|
||||
|
@ -202,33 +183,16 @@ impl AnchorInjection {
|
|||
|
||||
pub fn update_background_position(&self, title_rect: DomRect) {
|
||||
if let Some(anchor_el) = self.anchor_ref.get_untracked() {
|
||||
let background_el = self.background_ref.get_untracked().unwrap();
|
||||
let bar_el = self.bar_ref.get_untracked().unwrap();
|
||||
let anchor_rect = anchor_el.get_bounding_client_rect();
|
||||
|
||||
let offset_top = title_rect.top() - anchor_rect.top();
|
||||
let offset_left = title_rect.left() - anchor_rect.left();
|
||||
let _ = background_el
|
||||
.style("top", format!("{}px", offset_top))
|
||||
.style("height", format!("{}px", title_rect.height()))
|
||||
.style(
|
||||
"max-width",
|
||||
format!("{}px", title_rect.width() + offset_left),
|
||||
);
|
||||
let _ = bar_el
|
||||
.style("top", format!("{}px", offset_top))
|
||||
.style("height", format!("{}px", title_rect.height()));
|
||||
}
|
||||
}
|
||||
}
|
||||
// let offset_left = title_rect.left() - anchor_rect.left();
|
||||
|
||||
pub(crate) fn use_anchor() -> AnchorInjection {
|
||||
expect_context()
|
||||
bar_el.style(("top", format!("{}px", offset_top)));
|
||||
bar_el.style(("height", format!("{}px", title_rect.height())));
|
||||
}
|
||||
}
|
||||
|
||||
struct LinkInfo {
|
||||
top: f64,
|
||||
id: String,
|
||||
}
|
||||
|
||||
pub enum OffsetTarget {
|
||||
|
@ -236,6 +200,7 @@ pub enum OffsetTarget {
|
|||
Element(Element),
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "csr", feature = "hydrate"))]
|
||||
impl OffsetTarget {
|
||||
fn get_bounding_client_rect(&self) -> Option<DomRect> {
|
||||
match self {
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
use crate::theme::ThemeMethod;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AnchorTheme {
|
||||
pub rail_background_color: String,
|
||||
}
|
||||
|
||||
impl ThemeMethod for AnchorTheme {
|
||||
fn light() -> Self {
|
||||
Self {
|
||||
rail_background_color: "#dbdbdf".into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dark() -> Self {
|
||||
Self {
|
||||
rail_background_color: "#ffffff33".into(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,27 @@
|
|||
.thaw-auto-complete__menu {
|
||||
.thaw-auto-complete {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.thaw-auto-complete > .thaw-input {
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
div.thaw-auto-complete__listbox {
|
||||
width: 100%;
|
||||
|
||||
row-gap: var(--spacingHorizontalXXS);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 160px;
|
||||
/* max-height: 80vh; */
|
||||
max-height: 200px;
|
||||
padding: 5px;
|
||||
background-color: var(--thaw-background-color);
|
||||
border-radius: 3px;
|
||||
background-color: var(--colorNeutralBackground1);
|
||||
padding: var(--spacingHorizontalXS);
|
||||
outline: 1px solid var(--colorTransparentStroke);
|
||||
border-radius: var(--borderRadiusMedium);
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||
overflow: auto;
|
||||
box-shadow: var(--shadow16);
|
||||
overflow-y: auto;
|
||||
}
|
||||
.thaw-auto-complete__menu-item {
|
||||
padding: 6px 5px;
|
||||
|
@ -19,30 +33,39 @@
|
|||
background-color: var(--thaw-background-color-hover);
|
||||
}
|
||||
|
||||
.thaw-auto-complete__menu.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),
|
||||
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
.thaw-auto-complete-option {
|
||||
column-gap: var(--spacingHorizontalXS);
|
||||
position: relative;
|
||||
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);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.thaw-auto-complete__menu.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),
|
||||
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
.thaw-auto-complete-option[data-activedescendant-focusvisible]::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: -2px;
|
||||
left: -2px;
|
||||
bottom: -2px;
|
||||
top: -2px;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
border-radius: var(--borderRadiusMedium);
|
||||
border: 2px solid var(--colorStrokeFocus2);
|
||||
}
|
||||
|
||||
.thaw-auto-complete__menu.fade-in-scale-up-transition-enter-from,
|
||||
.thaw-auto-complete__menu.fade-in-scale-up-transition-leave-to {
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
.thaw-auto-complete-option:hover {
|
||||
color: var(--colorNeutralForeground1Hover);
|
||||
background-color: var(--colorNeutralBackground1Hover);
|
||||
}
|
||||
|
||||
.thaw-auto-complete__menu.fade-in-scale-up-transition-leave-from,
|
||||
.thaw-auto-complete__menu.fade-in-scale-up-transition-enter-to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
.thaw-auto-complete-option:active {
|
||||
color: var(--colorNeutralForeground1Pressed);
|
||||
background-color: var(--colorNeutralBackground1Pressed);
|
||||
}
|
||||
|
|
42
thaw/src/auto_complete/auto_complete_option.rs
Normal file
42
thaw/src/auto_complete/auto_complete_option.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
use super::AutoCompleteInjection;
|
||||
use crate::combobox::listbox::ListboxInjection;
|
||||
use leptos::prelude::*;
|
||||
use thaw_utils::class_list;
|
||||
|
||||
#[component]
|
||||
pub fn AutoCompleteOption(
|
||||
#[prop(optional, into)] class: MaybeProp<String>,
|
||||
value: String,
|
||||
children: Children,
|
||||
) -> impl IntoView {
|
||||
let auto_complete = AutoCompleteInjection::expect_context();
|
||||
let listbox = ListboxInjection::expect_context();
|
||||
let is_selected = Memo::new({
|
||||
let value = value.clone();
|
||||
let auto_complete = auto_complete.clone();
|
||||
move |_| auto_complete.is_selected(&value)
|
||||
});
|
||||
let id = uuid::Uuid::new_v4().to_string();
|
||||
{
|
||||
auto_complete.insert_option(id.clone(), value.clone());
|
||||
let id = id.clone();
|
||||
listbox.trigger();
|
||||
let auto_complete = auto_complete.clone();
|
||||
on_cleanup(move || {
|
||||
auto_complete.remove_option(&id);
|
||||
listbox.trigger();
|
||||
});
|
||||
}
|
||||
|
||||
view! {
|
||||
<div
|
||||
class=class_list!["thaw-auto-complete-option", class]
|
||||
role="option"
|
||||
id=id
|
||||
aria-selected=move || if is_selected.get() { "true" } else { "false" }
|
||||
on:click=move |_| auto_complete.select_option(value.clone())
|
||||
>
|
||||
{children()}
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -1,17 +1,16 @@
|
|||
mod theme;
|
||||
mod auto_complete_option;
|
||||
|
||||
pub use theme::AutoCompleteTheme;
|
||||
pub use auto_complete_option::AutoCompleteOption;
|
||||
|
||||
use crate::{use_theme, ComponentRef, Input, InputPrefix, InputRef, InputSuffix, Theme};
|
||||
use leptos::*;
|
||||
use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement, FollowerWidth};
|
||||
use thaw_utils::{class_list, mount_style, Model, OptionalProp, StoredMaybeSignal};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct AutoCompleteOption {
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
use crate::{
|
||||
combobox::listbox::{listbox_keyboard_event, Listbox},
|
||||
ComponentRef, Input, InputPrefix, InputRef, InputSuffix,
|
||||
_aria::use_active_descendant,
|
||||
};
|
||||
use leptos::{context::Provider, either::Either, html, prelude::*};
|
||||
use std::collections::HashMap;
|
||||
use thaw_components::{Binder, Follower, FollowerPlacement, FollowerWidth};
|
||||
use thaw_utils::{class_list, mount_style, ArcOneCallback, BoxOneCallback, Model};
|
||||
|
||||
#[slot]
|
||||
pub struct AutoCompletePrefix {
|
||||
|
@ -26,152 +25,93 @@ pub struct AutoCompleteSuffix {
|
|||
#[component]
|
||||
pub fn AutoComplete(
|
||||
#[prop(optional, into)] value: Model<String>,
|
||||
#[prop(optional, into)] placeholder: OptionalProp<MaybeSignal<String>>,
|
||||
#[prop(optional, into)] options: MaybeSignal<Vec<AutoCompleteOption>>,
|
||||
#[prop(optional, into)] placeholder: MaybeProp<String>,
|
||||
#[prop(optional, into)] clear_after_select: MaybeSignal<bool>,
|
||||
#[prop(optional, into)] blur_after_select: MaybeSignal<bool>,
|
||||
#[prop(optional, into)] on_select: Option<Callback<String>>,
|
||||
#[prop(optional, into)] on_select: Option<BoxOneCallback<String>>,
|
||||
#[prop(optional, into)] disabled: MaybeSignal<bool>,
|
||||
#[prop(optional, into)] allow_free_input: bool,
|
||||
#[prop(optional, into)] invalid: MaybeSignal<bool>,
|
||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
||||
#[prop(optional, into)] class: MaybeProp<String>,
|
||||
#[prop(optional)] auto_complete_prefix: Option<AutoCompletePrefix>,
|
||||
#[prop(optional)] auto_complete_suffix: Option<AutoCompleteSuffix>,
|
||||
#[prop(optional)] comp_ref: ComponentRef<AutoCompleteRef>,
|
||||
#[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
|
||||
#[prop(optional)] children: Option<Children>,
|
||||
) -> 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!(
|
||||
"--thaw-background-color: {};",
|
||||
theme.select.menu_background_color
|
||||
));
|
||||
css_vars.push_str(&format!(
|
||||
"--thaw-background-color-hover: {};",
|
||||
theme.select.menu_background_color_hover
|
||||
));
|
||||
});
|
||||
css_vars
|
||||
});
|
||||
|
||||
let input_ref = ComponentRef::<InputRef>::new();
|
||||
let listbox_ref = NodeRef::<html::Div>::new();
|
||||
let auto_complete_ref = NodeRef::<html::Div>::new();
|
||||
let open_listbox = RwSignal::new(false);
|
||||
let options = StoredValue::new(HashMap::<String, String>::new());
|
||||
|
||||
let default_index = if allow_free_input { None } else { Some(0) };
|
||||
|
||||
let select_option_index = create_rw_signal::<Option<usize>>(default_index);
|
||||
let menu_ref = create_node_ref::<html::Div>();
|
||||
let is_show_menu = create_rw_signal(false);
|
||||
let auto_complete_ref = create_node_ref::<html::Div>();
|
||||
let options = StoredMaybeSignal::from(options);
|
||||
let open_menu = move || {
|
||||
select_option_index.set(default_index);
|
||||
is_show_menu.set(true);
|
||||
};
|
||||
let allow_value = move |_| {
|
||||
if !is_show_menu.get_untracked() {
|
||||
open_menu();
|
||||
if !open_listbox.get_untracked() {
|
||||
open_listbox.set(true);
|
||||
}
|
||||
true
|
||||
};
|
||||
|
||||
let select_value = move |option_value: String| {
|
||||
let select_option = ArcOneCallback::new(move |option_value: String| {
|
||||
if clear_after_select.get_untracked() {
|
||||
value.set(String::new());
|
||||
} else {
|
||||
value.set(option_value.clone());
|
||||
}
|
||||
if let Some(on_select) = on_select {
|
||||
on_select.call(option_value);
|
||||
if let Some(on_select) = on_select.as_ref() {
|
||||
on_select(option_value);
|
||||
}
|
||||
if allow_free_input {
|
||||
select_option_index.set(None);
|
||||
}
|
||||
is_show_menu.set(false);
|
||||
|
||||
open_listbox.set(false);
|
||||
if blur_after_select.get_untracked() {
|
||||
if let Some(input_ref) = input_ref.get_untracked() {
|
||||
input_ref.blur();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// we unset selection index whenever options get changed
|
||||
// otherwise e.g. selection could move from one item to
|
||||
// another staying on the same index
|
||||
create_effect(move |_| {
|
||||
options.track();
|
||||
select_option_index.set(default_index);
|
||||
});
|
||||
|
||||
let on_keydown = move |event: ev::KeyboardEvent| {
|
||||
if !is_show_menu.get_untracked() {
|
||||
return;
|
||||
}
|
||||
let key = event.key();
|
||||
if key == *"ArrowDown" {
|
||||
select_option_index.update(|index| {
|
||||
if *index == Some(options.with_untracked(|options| options.len()) - 1) {
|
||||
*index = default_index
|
||||
} else {
|
||||
*index = Some(index.map_or(0, |index| index + 1))
|
||||
}
|
||||
});
|
||||
} else if key == *"ArrowUp" {
|
||||
select_option_index.update(|index| {
|
||||
match *index {
|
||||
None => *index = Some(options.with_untracked(|options| options.len()) - 1),
|
||||
Some(0) => {
|
||||
if allow_free_input {
|
||||
*index = None
|
||||
} else {
|
||||
*index = Some(options.with_untracked(|options| options.len()) - 1)
|
||||
}
|
||||
}
|
||||
Some(prev_index) => *index = Some(prev_index - 1),
|
||||
};
|
||||
});
|
||||
} else if key == *"Enter" {
|
||||
event.prevent_default();
|
||||
let option_value = options.with_untracked(|options| {
|
||||
let index = select_option_index.get_untracked();
|
||||
match index {
|
||||
None if allow_free_input => {
|
||||
let value = value.get_untracked();
|
||||
(!value.is_empty()).then_some(value)
|
||||
}
|
||||
Some(index) if options.len() > index => {
|
||||
let option = &options[index];
|
||||
Some(option.value.clone())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
if let Some(option_value) = option_value {
|
||||
select_value(option_value);
|
||||
}
|
||||
let (set_listbox, active_descendant_controller) =
|
||||
use_active_descendant(move |el| el.class_list().contains("thaw-auto-complete-option"));
|
||||
let on_blur = {
|
||||
let active_descendant_controller = active_descendant_controller.clone();
|
||||
move |_| {
|
||||
active_descendant_controller.blur();
|
||||
open_listbox.set(false);
|
||||
}
|
||||
};
|
||||
input_ref.on_load(move |_| {
|
||||
let on_keydown = {
|
||||
let select_option = select_option.clone();
|
||||
move |e| {
|
||||
let select_option = select_option.clone();
|
||||
listbox_keyboard_event(
|
||||
e,
|
||||
open_listbox,
|
||||
false,
|
||||
&active_descendant_controller,
|
||||
move |option| {
|
||||
options.with_value(|options| {
|
||||
if let Some(value) = options.get(&option.id()) {
|
||||
select_option(value.clone());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
comp_ref.load(AutoCompleteRef { input_ref });
|
||||
});
|
||||
|
||||
view! {
|
||||
<Binder target_ref=auto_complete_ref>
|
||||
<div
|
||||
class=class_list!["thaw-auto-complete", class.map(| c | move || c.get())]
|
||||
ref=auto_complete_ref
|
||||
class=class_list!["thaw-auto-complete", class]
|
||||
node_ref=auto_complete_ref
|
||||
on:keydown=on_keydown
|
||||
>
|
||||
<Input
|
||||
attrs
|
||||
value
|
||||
placeholder
|
||||
disabled
|
||||
invalid
|
||||
on_focus=move |_| open_menu()
|
||||
on_blur=move |_| is_show_menu.set(false)
|
||||
on_focus=move |_| open_listbox.set(true)
|
||||
on_blur=on_blur
|
||||
allow_value
|
||||
comp_ref=input_ref
|
||||
>
|
||||
|
@ -197,92 +137,56 @@ pub fn AutoComplete(
|
|||
</div>
|
||||
<Follower
|
||||
slot
|
||||
show=is_show_menu
|
||||
show=open_listbox
|
||||
placement=FollowerPlacement::BottomStart
|
||||
width=FollowerWidth::Target
|
||||
>
|
||||
<CSSTransition
|
||||
node_ref=menu_ref
|
||||
name="fade-in-scale-up-transition"
|
||||
appear=is_show_menu.get_untracked()
|
||||
show=is_show_menu
|
||||
let:display
|
||||
>
|
||||
<div
|
||||
class="thaw-auto-complete__menu"
|
||||
style=move || {
|
||||
display
|
||||
.get()
|
||||
.map(|d| d.to_string())
|
||||
.unwrap_or_else(|| menu_css_vars.get())
|
||||
}
|
||||
|
||||
ref=menu_ref
|
||||
>
|
||||
|
||||
{move || {
|
||||
options
|
||||
.get()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(index, v)| {
|
||||
let AutoCompleteOption { value: option_value, label } = v;
|
||||
let menu_item_ref = create_node_ref::<html::Div>();
|
||||
let on_click = move |_| {
|
||||
select_value(option_value.clone());
|
||||
};
|
||||
let on_mouseenter = move |_| {
|
||||
select_option_index.set(Some(index));
|
||||
};
|
||||
let on_mousedown = move |ev: ev::MouseEvent| {
|
||||
ev.prevent_default();
|
||||
};
|
||||
create_effect(move |_| {
|
||||
if Some(index) == select_option_index.get() {
|
||||
if !is_show_menu.get() {
|
||||
return;
|
||||
}
|
||||
if let Some(menu_item_ref) = menu_item_ref.get() {
|
||||
let menu_ref = menu_ref.get().unwrap();
|
||||
let menu_rect = menu_ref.get_bounding_client_rect();
|
||||
let item_rect = menu_item_ref.get_bounding_client_rect();
|
||||
if item_rect.y() < menu_rect.y() {
|
||||
menu_item_ref.scroll_into_view_with_bool(true);
|
||||
} else if item_rect.y() + item_rect.height()
|
||||
> menu_rect.y() + menu_rect.height()
|
||||
<Provider value=AutoCompleteInjection{value, select_option, options}>
|
||||
<Listbox open=open_listbox.read_only() set_listbox listbox_ref class="thaw-auto-complete__listbox">
|
||||
{
|
||||
menu_item_ref.scroll_into_view_with_bool(false);
|
||||
if let Some(children) = children {
|
||||
Either::Left(children())
|
||||
} else {
|
||||
Either::Right(())
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
view! {
|
||||
<div
|
||||
class="thaw-auto-complete__menu-item"
|
||||
class=(
|
||||
"thaw-auto-complete__menu-item--selected",
|
||||
move || Some(index) == select_option_index.get(),
|
||||
)
|
||||
|
||||
on:click=on_click
|
||||
on:mousedown=on_mousedown
|
||||
on:mouseenter=on_mouseenter
|
||||
ref=menu_item_ref
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
}
|
||||
})
|
||||
.collect_view()
|
||||
}}
|
||||
|
||||
</div>
|
||||
</CSSTransition>
|
||||
</Listbox>
|
||||
</Provider>
|
||||
</Follower>
|
||||
</Binder>
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct AutoCompleteInjection {
|
||||
value: Model<String>,
|
||||
select_option: ArcOneCallback<String>,
|
||||
options: StoredValue<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl AutoCompleteInjection {
|
||||
pub fn expect_context() -> Self {
|
||||
expect_context()
|
||||
}
|
||||
|
||||
pub fn is_selected(&self, key: &String) -> bool {
|
||||
self.value.with(|value| value == key)
|
||||
}
|
||||
|
||||
pub fn select_option(&self, value: String) {
|
||||
(self.select_option)(value);
|
||||
}
|
||||
|
||||
pub fn insert_option(&self, id: String, value: String) {
|
||||
self.options
|
||||
.update_value(|options| options.insert(id, value));
|
||||
}
|
||||
|
||||
pub fn remove_option(&self, id: &String) {
|
||||
self.options.update_value(|options| options.remove(id));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AutoCompleteRef {
|
||||
input_ref: ComponentRef<InputRef>,
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
use crate::theme::ThemeMethod;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AutoCompleteTheme {
|
||||
pub menu_background_color: String,
|
||||
pub menu_background_color_hover: String,
|
||||
}
|
||||
|
||||
impl ThemeMethod for AutoCompleteTheme {
|
||||
fn light() -> 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(),
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue