From 397503dc8a2cbe755e13e62accb29ee22150e81c Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 13:15:09 +0200 Subject: [PATCH 01/17] Update Workspace for Leptos 0.7 --- Cargo.toml | 109 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ce06992c..aceb6cde 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,34 +1,56 @@ [workspace] -# Temporarily disabled to upgrade individual packages to Leptos 0.7. -# members = [ -# "book-examples/*/*", -# "packages/colors", -# "packages/icons/*", -# "packages/primitives/*/*", -# "packages/themes/*", -# "scripts", -# "stories/*", -# ] +# Temporarily disabled subcrates to be upgraded individually to Leptos 0.7. +# Once a crate is ready, uncomment it here. members = [ "book-examples/*/*", "packages/colors", - "packages/icons/*", - "packages/primitives/leptos/accessible-icon", - "packages/primitives/leptos/arrow", - "packages/primitives/leptos/aspect-ratio", - "packages/primitives/leptos/direction", - "packages/primitives/leptos/id", - "packages/primitives/leptos/label", - "packages/primitives/leptos/use-controllable-state", - "packages/primitives/leptos/use-escape-keydown", - "packages/primitives/leptos/use-previous", - "packages/primitives/leptos/use-size", - "packages/primitives/leptos/visually-hidden", + "packages/icons/dioxus", + "packages/icons/yew", + + # -- Leptos Primitives (commented until they're upgraded) -- + # "packages/primitives/leptos/accessible-icon", + # "packages/primitives/leptos/arrow", + # "packages/primitives/leptos/aspect-ratio", + # "packages/primitives/leptos/avatar", + # "packages/primitives/leptos/checkbox", + # "packages/primitives/leptos/collection", + # "packages/primitives/leptos/compose-refs", + # "packages/primitives/leptos/direction", + # "packages/primitives/leptos/dismissable-layer", + # "packages/primitives/leptos/dropdown-menu", + # "packages/primitives/leptos/focus-guards", + # "packages/primitives/leptos/focus-scope", + # "packages/primitives/leptos/id", + # "packages/primitives/leptos/label", + # "packages/primitives/leptos/menu", + # "packages/primitives/leptos/popover", + # "packages/primitives/leptos/popper", + # "packages/primitives/leptos/portal", + # "packages/primitives/leptos/presence", + # "packages/primitives/leptos/primitive", + # "packages/primitives/leptos/progress", + # "packages/primitives/leptos/roving-focus", + # "packages/primitives/leptos/select", + # "packages/primitives/leptos/separator", + # "packages/primitives/leptos/slot", + # "packages/primitives/leptos/switch", + # "packages/primitives/leptos/tabs", + # "packages/primitives/leptos/toggle", + # "packages/primitives/leptos/use-controllable-state", + # "packages/primitives/leptos/use-escape-keydown", + # "packages/primitives/leptos/use-previous", + # "packages/primitives/leptos/use-size", + # "packages/primitives/leptos/visually-hidden", + + # -- Yew Primitives -- "packages/primitives/yew/*", + + # -- Themes, Scripts, and Stories -- "packages/themes/yew", "scripts", "stories/*", ] + resolver = "2" [workspace.package] @@ -39,14 +61,17 @@ repository = "https://github.com/RustForWeb/radix" version = "0.0.2" [workspace.dependencies] -console_log = "1.0.0" console_error_panic_hook = "0.1.7" +console_log = "1.0.0" dioxus = "0.6.1" leptos = "0.7.2" leptos_dom = "0.7.2" leptos_router = "0.7.2" leptos-node-ref = "0.0.3" +leptos-maybe-callback = "0.0.3" leptos-style = "0.0.3" +leptos-typed-fallback-show = "0.0.3" +leptos-use = "0.15.2" log = "0.4.22" send_wrapper = "0.6.0" serde = "1.0.198" @@ -58,6 +83,44 @@ yew-router = "0.18.0" yew-struct-component = "0.1.4" yew-style = "0.1.4" +# Subcrate packages (paths remain the same; you can comment out any subcrate not yet upgraded). +# We centralize shared dependencies in [workspace.dependencies] for a single source of truth, +# reducing duplication, preventing version drift, and keeping the Cargo.lock consistent. +#radix-leptos-arrow.path = "./packages/primitives/leptos/arrow" +#radix-leptos-aspect-ratio.path = "./packages/primitives/leptos/aspect-ratio" +#radix-leptos-accessible-icon.path = "./packages/primitives/leptos/accessible-icon" +#radix-leptos-avatar.path = "./packages/primitives/leptos/avatar" +#radix-leptos-checkbox.path = "./packages/primitives/leptos/checkbox" +#radix-leptos-collection.path = "./packages/primitives/leptos/collection" +#radix-leptos-compose-refs.path = "./packages/primitives/leptos/compose-refs" +#radix-leptos-direction.path = "./packages/primitives/leptos/direction" +#radix-leptos-dismissable-layer.path = "./packages/primitives/leptos/dismissable-layer" +#radix-leptos-dropdown-menu.path = "./packages/primitives/leptos/dropdown-menu" +#radix-leptos-focus-guards.path = "./packages/primitives/leptos/focus-guards" +#radix-leptos-focus-scope.path = "./packages/primitives/leptos/focus-scope" +#radix-leptos-id.path = "./packages/primitives/leptos/id" +#radix-leptos-label.path = "./packages/primitives/leptos/label" +#radix-leptos-menu.path = "./packages/primitives/leptos/menu" +#radix-leptos-popper.path = "./packages/primitives/leptos/popper" +#radix-leptos-portal.path = "./packages/primitives/leptos/portal" +#radix-leptos-presence.path = "./packages/primitives/leptos/presence" +#radix-leptos-primitive.path = "./packages/primitives/leptos/primitive" +#radix-leptos-progress.path = "./packages/primitives/leptos/progress" +#radix-leptos-roving-focus.path = "./packages/primitives/leptos/roving-focus" +#radix-leptos-select.path = "./packages/primitives/leptos/select" +#radix-leptos-separator.path = "./packages/primitives/leptos/separator" +#radix-leptos-slot.path = "./packages/primitives/leptos/slot" +#radix-leptos-switch.path = "./packages/primitives/leptos/switch" +#radix-leptos-tabs.path = "./packages/primitives/leptos/tabs" +#radix-leptos-toggle.path = "./packages/primitives/leptos/toggle" +#radix-leptos-use-controllable-state.path = "./packages/primitives/leptos/use-controllable-state" +#radix-leptos-use-escape-keydown.path = "./packages/primitives/leptos/use-escape-keydown" +#radix-leptos-use-previous.path = "./packages/primitives/leptos/use-previous" +#radix-leptos-use-size.path = "./packages/primitives/leptos/use-size" +#radix-leptos-visually-hidden.path = "./packages/primitives/leptos/visually-hidden" + [patch.crates-io] yew = { git = "https://github.com/RustForWeb/yew.git", branch = "feature/use-composed-ref" } yew-router = { git = "https://github.com/RustForWeb/yew.git", branch = "feature/use-composed-ref" } +leptos-node-ref = { git = "https://github.com/geoffreygarrett/leptos-utils", branch = "feature/any-node-ref" } +radix-leptos-primitive = { git = "https://github.com/geoffreygarrett/radix", branch = "update/leptos-0.7-primitive" } From 70159621139cc0c0150299b518aafaa4cb819d50 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 14:16:57 +0200 Subject: [PATCH 02/17] Update Workspace for Leptos 0.7 --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index aceb6cde..ab7670fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,4 +123,3 @@ yew-style = "0.1.4" yew = { git = "https://github.com/RustForWeb/yew.git", branch = "feature/use-composed-ref" } yew-router = { git = "https://github.com/RustForWeb/yew.git", branch = "feature/use-composed-ref" } leptos-node-ref = { git = "https://github.com/geoffreygarrett/leptos-utils", branch = "feature/any-node-ref" } -radix-leptos-primitive = { git = "https://github.com/geoffreygarrett/radix", branch = "update/leptos-0.7-primitive" } From e896ace2c06b0f85a58bd61957d1d352f6c9695b Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Mon, 6 Jan 2025 19:19:46 +0200 Subject: [PATCH 03/17] Update Primitive to Leptos 0.7 --- .../primitives/leptos/primitive/Cargo.toml | 14 +++ .../primitives/leptos/primitive/README.md | 98 ++++++++++++++++ .../primitives/leptos/primitive/src/lib.rs | 9 ++ .../leptos/primitive/src/primitive.rs | 109 ++++++++++++++++++ 4 files changed, 230 insertions(+) create mode 100644 packages/primitives/leptos/primitive/Cargo.toml create mode 100644 packages/primitives/leptos/primitive/README.md create mode 100644 packages/primitives/leptos/primitive/src/lib.rs create mode 100644 packages/primitives/leptos/primitive/src/primitive.rs diff --git a/packages/primitives/leptos/primitive/Cargo.toml b/packages/primitives/leptos/primitive/Cargo.toml new file mode 100644 index 00000000..fecb0b9d --- /dev/null +++ b/packages/primitives/leptos/primitive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "radix-leptos-primitive" +description = "Leptos port of Radix Primitive." + +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +leptos.workspace = true +leptos-node-ref.workspace = true +leptos-typed-fallback-show.workspace = true \ No newline at end of file diff --git a/packages/primitives/leptos/primitive/README.md b/packages/primitives/leptos/primitive/README.md new file mode 100644 index 00000000..296e16dc --- /dev/null +++ b/packages/primitives/leptos/primitive/README.md @@ -0,0 +1,98 @@ + +

+ + Rust Radix Logo + +

+ +

radix-leptos-primitive

+ +This is an internal utility, not intended for public usage. + +[Rust Radix](https://github.com/RustForWeb/radix) is a Rust port of [Radix](https://www.radix-ui.com/primitives). + +## Overview + +```rust +use leptos::*; +use leptos_node_ref::AnyNodeRef; +use leptos_typed_fallback_show::TypedFallbackShow; + +/// A generic Primitive component. Renders `element()` by default, or its +/// children directly if `as_child` is `true`. We rely on `TypedChildrenFn` +/// so that attributes can pass through at runtime—critical in Leptos v0.7 +/// because `Children`-based types block such passthrough. +#[component] +#[allow(non_snake_case)] +pub fn Primitive( + element: fn() -> HtmlElement, + children: TypedChildrenFn, + #[prop(optional, into)] as_child: MaybeProp, + #[prop(optional, into)] node_ref: AnyNodeRef, +) -> impl IntoView +where + E: ElementType + 'static, + C: IntoView + 'static, +{ + let children = StoredValue::new(children.into_inner()); + view! { + + {children.with_value(|c| c()) + .add_any_attr(leptos_node_ref::any_node_ref(node_ref))} + + } +} + +/// Same idea, but for elements that do not take children (e.g. `img`, `input`). +#[component] +#[allow(non_snake_case)] +pub fn VoidPrimitive( + element: fn() -> HtmlElement, + children: TypedChildrenFn, + #[prop(optional, into)] as_child: MaybeProp, + #[prop(optional, into)] node_ref: AnyNodeRef, +) -> impl IntoView +where + E: ElementType + 'static, +{ + let children = StoredValue::new(children.into_inner()); + view! { + + {children.with_value(|c| c()) + .add_any_attr(leptos_node_ref::any_node_ref(node_ref))} + + } +} + +// (Compose callbacks is an internal piece from Radix Core; omitted for brevity.) +``` + +## Notes + +- **Why `TypedChildrenFn`?**: Leptos attribute passthrough only works if a component doesn't rely on `AnyView` or `Children`. Using typed children ensures classes, events, etc. from the parent can flow to the rendered DOM node. +- **`as_child`**: Mimics `asChild` in Radix’s React version, but we skip an explicit ``: Leptos’s approach to typed fallback rendering covers “slot-like” logic. +- **Class Handling**: Static classes from a parent can overwrite child-defined classes. No built-in merging exists. +- **Attribute System Limitations**: Leptos limits you to 26 dynamic attributes. Past that, nest components or try a custom approach. +- **Parity with React**: In React, `...props` merges everything automatically. In Leptos, we rely on typed props/attributes and can intercept unknown ones with `AttributeInterceptor`. + +## Documentation + +See [the Rust Radix book](https://radix.rustforweb.org/) for documentation. + +## Rust For Web + +The Rust Radix project is part of the [Rust For Web](https://github.com/RustForWeb). + +[Rust For Web](https://github.com/RustForWeb) creates and ports web UI libraries for Rust. All projects are free and open source. diff --git a/packages/primitives/leptos/primitive/src/lib.rs b/packages/primitives/leptos/primitive/src/lib.rs new file mode 100644 index 00000000..304fec6d --- /dev/null +++ b/packages/primitives/leptos/primitive/src/lib.rs @@ -0,0 +1,9 @@ +//! Leptos port of [Radix Primitive](https://www.radix-ui.com/primitives). +//! +//! This is an internal utility, not intended for public usage. + +//! See [`@radix-ui/react-primitive`](https://www.npmjs.com/package/@radix-ui/react-primitive) for the original package. + +mod primitive; + +pub use primitive::*; diff --git a/packages/primitives/leptos/primitive/src/primitive.rs b/packages/primitives/leptos/primitive/src/primitive.rs new file mode 100644 index 00000000..7d4c94ab --- /dev/null +++ b/packages/primitives/leptos/primitive/src/primitive.rs @@ -0,0 +1,109 @@ +use leptos::{ + attr::Attribute, + ev::Event, + html::{ElementType, HtmlElement}, + prelude::*, + wasm_bindgen::JsCast, + tachys::html::{class::IntoClass, node_ref::NodeRefContainer, style::IntoStyle}, +}; +use leptos_node_ref::{any_node_ref, AnyNodeRef}; +use leptos_typed_fallback_show::TypedFallbackShow; + +/* ------------------------------------------------------------------------------------------------- + * Primitive + * -----------------------------------------------------------------------------------------------*/ + +#[component] +#[allow(non_snake_case)] +pub fn Primitive( + element: fn() -> HtmlElement, + children: TypedChildrenFn, + #[prop(optional, into)] as_child: MaybeProp, + #[prop(optional, into)] node_ref: AnyNodeRef, +) -> impl IntoView +where + E: ElementType + 'static, + C: IntoView + 'static, + View: RenderHtml, + HtmlElement: ElementChild>, + as ElementChild>>::Output: IntoView, + ::Output: JsCast, + AnyNodeRef: NodeRefContainer, +{ + let children = StoredValue::new(children.into_inner()); + + view! { + + {children.with_value(|children| children()).add_any_attr(any_node_ref(node_ref))} + + } +} + +#[component] +#[allow(non_snake_case)] +pub fn VoidPrimitive( + element: fn() -> HtmlElement, + children: TypedChildrenFn, + #[prop(into, optional)] as_child: MaybeProp, + #[prop(into, optional)] node_ref: AnyNodeRef, +) -> impl IntoView +where + E: ElementType + 'static, + C: IntoView + 'static, + View: RenderHtml, + ::Output: JsCast, + AnyNodeRef: NodeRefContainer, +{ + let children = StoredValue::new(children.into_inner()); + view! { + + {children.with_value(|children| children()).add_any_attr(any_node_ref(node_ref))} + + } +} + +/* ------------------------------------------------------------------------------------------------- + * Utils + * -----------------------------------------------------------------------------------------------*/ + +pub fn compose_callbacks( + original_handler: Option>, + our_handler: Option>, + check_default_prevented: Option, +) -> impl Fn(E) +where + E: Clone + Into + 'static, +{ + let check_default_prevented = check_default_prevented.unwrap_or(true); + + move |event: E| { + // Run original handler first, matching TypeScript behavior + if let Some(original) = &original_handler { + original.run(event.clone()); + } + + // Only run our handler if default wasn't prevented (when checking is enabled) + if !check_default_prevented || !event.clone().into().default_prevented() { + if let Some(our) = &our_handler { + our.run(event); + } + } + } +} + +/* ------------------------------------------------------------------------------------------------- + * Primitive re-exports + * -----------------------------------------------------------------------------------------------*/ + +pub mod primitive { + pub use super::*; + pub use Primitive as Root; +} \ No newline at end of file From 2ee135bf5ed6fc773a14fa68ec5af2a40ea2a37a Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 14:13:52 +0200 Subject: [PATCH 04/17] Update Primitive for Leptos 0.7 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ab7670fe..a16ac6ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -104,7 +104,7 @@ yew-style = "0.1.4" #radix-leptos-popper.path = "./packages/primitives/leptos/popper" #radix-leptos-portal.path = "./packages/primitives/leptos/portal" #radix-leptos-presence.path = "./packages/primitives/leptos/presence" -#radix-leptos-primitive.path = "./packages/primitives/leptos/primitive" +radix-leptos-primitive.path = "./packages/primitives/leptos/primitive" #radix-leptos-progress.path = "./packages/primitives/leptos/progress" #radix-leptos-roving-focus.path = "./packages/primitives/leptos/roving-focus" #radix-leptos-select.path = "./packages/primitives/leptos/select" From 08e08acfb65091751421c61b46fbe721a36929bb Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 14:24:49 +0200 Subject: [PATCH 05/17] Update Primitive for Leptos 0.7 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a16ac6ef..16f109e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ members = [ # "packages/primitives/leptos/popper", # "packages/primitives/leptos/portal", # "packages/primitives/leptos/presence", - # "packages/primitives/leptos/primitive", + "packages/primitives/leptos/primitive", # "packages/primitives/leptos/progress", # "packages/primitives/leptos/roving-focus", # "packages/primitives/leptos/select", From 98abf14db7ce26e07e4e4621b9bc9d0acacc5189 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 14:26:14 +0200 Subject: [PATCH 06/17] Update Primitive for Leptos 0.7 --- .../primitives/leptos/primitive/src/primitive.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/primitives/leptos/primitive/src/primitive.rs b/packages/primitives/leptos/primitive/src/primitive.rs index 7d4c94ab..fd39d291 100644 --- a/packages/primitives/leptos/primitive/src/primitive.rs +++ b/packages/primitives/leptos/primitive/src/primitive.rs @@ -1,10 +1,9 @@ use leptos::{ - attr::Attribute, ev::Event, html::{ElementType, HtmlElement}, prelude::*, wasm_bindgen::JsCast, - tachys::html::{class::IntoClass, node_ref::NodeRefContainer, style::IntoStyle}, + tachys::html::{node_ref::NodeRefContainer}, }; use leptos_node_ref::{any_node_ref, AnyNodeRef}; use leptos_typed_fallback_show::TypedFallbackShow; @@ -98,12 +97,3 @@ where } } } - -/* ------------------------------------------------------------------------------------------------- - * Primitive re-exports - * -----------------------------------------------------------------------------------------------*/ - -pub mod primitive { - pub use super::*; - pub use Primitive as Root; -} \ No newline at end of file From 648da02a131dacb2023db0a9c167952d7c6be444 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Mon, 6 Jan 2025 20:10:12 +0200 Subject: [PATCH 07/17] Update Context to Leptos 0.7 --- packages/primitives/leptos/context/Cargo.toml | 12 +++ packages/primitives/leptos/context/README.md | 21 +++++ .../leptos/context/src/create_context.rs | 84 +++++++++++++++++++ packages/primitives/leptos/context/src/lib.rs | 3 + 4 files changed, 120 insertions(+) create mode 100644 packages/primitives/leptos/context/Cargo.toml create mode 100644 packages/primitives/leptos/context/README.md create mode 100644 packages/primitives/leptos/context/src/create_context.rs create mode 100644 packages/primitives/leptos/context/src/lib.rs diff --git a/packages/primitives/leptos/context/Cargo.toml b/packages/primitives/leptos/context/Cargo.toml new file mode 100644 index 00000000..411e26ce --- /dev/null +++ b/packages/primitives/leptos/context/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "radix-leptos-context" +description = "Leptos port of Radix Context." + +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +leptos.workspace = true diff --git a/packages/primitives/leptos/context/README.md b/packages/primitives/leptos/context/README.md new file mode 100644 index 00000000..7f54f041 --- /dev/null +++ b/packages/primitives/leptos/context/README.md @@ -0,0 +1,21 @@ +

+ + Rust Radix Logo + +

+ +

radix-leptos-context

+ +This is an internal utility, not intended for public usage. + +[Rust Radix](https://github.com/RustForWeb/radix) is a Rust port of [Radix](https://www.radix-ui.com/primitives). + +## Documentation + +See [the Rust Radix book](https://radix.rustforweb.org/) for documentation. + +## Rust For Web + +The Rust Radix project is part of the [Rust For Web](https://github.com/RustForWeb). + +[Rust For Web](https://github.com/RustForWeb) creates and ports web UI libraries for Rust. All projects are free and open source. diff --git a/packages/primitives/leptos/context/src/create_context.rs b/packages/primitives/leptos/context/src/create_context.rs new file mode 100644 index 00000000..92d05f46 --- /dev/null +++ b/packages/primitives/leptos/context/src/create_context.rs @@ -0,0 +1,84 @@ +/// Macro to create a context provider and a hook to consume the context. +/// +/// # Example +/// ```rust +/// use leptos::prelude::*; +/// use radix_leptos_context::create_context; +/// +/// #[derive(Clone)] +/// struct CountContext(i32); +/// +/// create_context!( +/// context_type: CountContext, +/// provider: CountProvider, +/// hook: use_count, +/// root: "Count" +/// ); +/// +/// #[component] +/// fn Counter() -> impl IntoView { +/// let count = use_count("Counter"); +/// view! {
"Count: "{count.0}
} +/// } +/// +/// #[component] +/// fn App() -> impl IntoView { +/// view! { +/// +/// +/// +/// } +/// } +/// ``` +/// +/// # Panics +/// +/// The hook will panic if used in a component that is not wrapped in its provider: +/// ```should_panic +/// use leptos::prelude::*; +/// use radix_leptos_context::create_context; +/// +/// #[derive(Clone)] +/// struct CountContext(i32); +/// +/// create_context!( +/// context_type: CountContext, +/// provider: CountProvider, +/// hook: use_count, +/// root: "Count" +/// ); +/// +/// #[component] +/// fn BadApp() -> impl IntoView { +/// let count = use_count("BadApp"); // Panics: "`BadApp` must be used within `Count`" +/// view! {
{count.0}
} +/// } +/// ``` +#[macro_export] +macro_rules! create_context { + ( + context_type: $context_ty:ty, + provider: $provider:ident, + hook: $hook:ident, + root: $root_component_name:expr + ) => { + use leptos::prelude::*; + + #[component] + #[allow(non_snake_case)] + pub fn $provider( + value: $context_ty, + children: TypedChildren, + ) -> impl IntoView { + view! { } + } + + pub fn $hook(component_name: &'static str) -> $context_ty { + use_context::<$context_ty>() + .expect(&format!("`{}` must be used within `{}`", component_name, $root_component_name)) + .clone() + } + }; +} +// TODO: Default context support + diff --git a/packages/primitives/leptos/context/src/lib.rs b/packages/primitives/leptos/context/src/lib.rs new file mode 100644 index 00000000..01d9501f --- /dev/null +++ b/packages/primitives/leptos/context/src/lib.rs @@ -0,0 +1,3 @@ +mod create_context; + +// pub use create_context::*; From 090a1f3988863417d42164274dce55e57daaf19c Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Mon, 6 Jan 2025 20:23:32 +0200 Subject: [PATCH 08/17] Update Context to Leptos 0.7 --- Cargo.toml | 49 ++++++++++++------------------------------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 16f109e0..4eab4c3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,43 +6,18 @@ members = [ "packages/colors", "packages/icons/dioxus", "packages/icons/yew", - - # -- Leptos Primitives (commented until they're upgraded) -- - # "packages/primitives/leptos/accessible-icon", - # "packages/primitives/leptos/arrow", - # "packages/primitives/leptos/aspect-ratio", - # "packages/primitives/leptos/avatar", - # "packages/primitives/leptos/checkbox", - # "packages/primitives/leptos/collection", - # "packages/primitives/leptos/compose-refs", - # "packages/primitives/leptos/direction", - # "packages/primitives/leptos/dismissable-layer", - # "packages/primitives/leptos/dropdown-menu", - # "packages/primitives/leptos/focus-guards", - # "packages/primitives/leptos/focus-scope", - # "packages/primitives/leptos/id", - # "packages/primitives/leptos/label", - # "packages/primitives/leptos/menu", - # "packages/primitives/leptos/popover", - # "packages/primitives/leptos/popper", - # "packages/primitives/leptos/portal", - # "packages/primitives/leptos/presence", - "packages/primitives/leptos/primitive", - # "packages/primitives/leptos/progress", - # "packages/primitives/leptos/roving-focus", - # "packages/primitives/leptos/select", - # "packages/primitives/leptos/separator", - # "packages/primitives/leptos/slot", - # "packages/primitives/leptos/switch", - # "packages/primitives/leptos/tabs", - # "packages/primitives/leptos/toggle", - # "packages/primitives/leptos/use-controllable-state", - # "packages/primitives/leptos/use-escape-keydown", - # "packages/primitives/leptos/use-previous", - # "packages/primitives/leptos/use-size", - # "packages/primitives/leptos/visually-hidden", - - # -- Yew Primitives -- + "packages/primitives/leptos/accessible-icon", + "packages/primitives/leptos/arrow", + "packages/primitives/leptos/aspect-ratio", + "packages/primitives/leptos/context", + "packages/primitives/leptos/direction", + "packages/primitives/leptos/id", + "packages/primitives/leptos/label", + "packages/primitives/leptos/use-controllable-state", + "packages/primitives/leptos/use-escape-keydown", + "packages/primitives/leptos/use-previous", + "packages/primitives/leptos/use-size", + "packages/primitives/leptos/visually-hidden", "packages/primitives/yew/*", # -- Themes, Scripts, and Stories -- From 652480c58d566148012bdafe4fbf7eb038590012 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 15:09:31 +0200 Subject: [PATCH 09/17] Update Context to Leptos 0.7 --- Cargo.toml | 48 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4eab4c3b..6eeba080 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,18 +6,44 @@ members = [ "packages/colors", "packages/icons/dioxus", "packages/icons/yew", - "packages/primitives/leptos/accessible-icon", - "packages/primitives/leptos/arrow", - "packages/primitives/leptos/aspect-ratio", + + # -- Leptos Primitives (commented until they're upgraded) -- + # "packages/primitives/leptos/accessible-icon", + # "packages/primitives/leptos/arrow", + # "packages/primitives/leptos/aspect-ratio", + # "packages/primitives/leptos/avatar", + # "packages/primitives/leptos/checkbox", "packages/primitives/leptos/context", - "packages/primitives/leptos/direction", - "packages/primitives/leptos/id", - "packages/primitives/leptos/label", - "packages/primitives/leptos/use-controllable-state", - "packages/primitives/leptos/use-escape-keydown", - "packages/primitives/leptos/use-previous", - "packages/primitives/leptos/use-size", - "packages/primitives/leptos/visually-hidden", + # "packages/primitives/leptos/collection", + # "packages/primitives/leptos/compose-refs", + # "packages/primitives/leptos/direction", + # "packages/primitives/leptos/dismissable-layer", + # "packages/primitives/leptos/dropdown-menu", + # "packages/primitives/leptos/focus-guards", + # "packages/primitives/leptos/focus-scope", + # "packages/primitives/leptos/id", + # "packages/primitives/leptos/label", + # "packages/primitives/leptos/menu", + # "packages/primitives/leptos/popover", + # "packages/primitives/leptos/popper", + # "packages/primitives/leptos/portal", + # "packages/primitives/leptos/presence", + # "packages/primitives/leptos/primitive", + # "packages/primitives/leptos/progress", + # "packages/primitives/leptos/roving-focus", + # "packages/primitives/leptos/select", + # "packages/primitives/leptos/separator", + # "packages/primitives/leptos/slot", + # "packages/primitives/leptos/switch", + # "packages/primitives/leptos/tabs", + # "packages/primitives/leptos/toggle", + # "packages/primitives/leptos/use-controllable-state", + # "packages/primitives/leptos/use-escape-keydown", + # "packages/primitives/leptos/use-previous", + # "packages/primitives/leptos/use-size", + # "packages/primitives/leptos/visually-hidden", + + # -- Yew Primitives -- "packages/primitives/yew/*", # -- Themes, Scripts, and Stories -- From ef6b540015fc7ccd7c3615c22bc0f010f333aff5 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 15:19:41 +0200 Subject: [PATCH 10/17] Update Context to Leptos 0.7 --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 6eeba080..92609712 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ yew-style = "0.1.4" #radix-leptos-accessible-icon.path = "./packages/primitives/leptos/accessible-icon" #radix-leptos-avatar.path = "./packages/primitives/leptos/avatar" #radix-leptos-checkbox.path = "./packages/primitives/leptos/checkbox" +radix-leptos-context.path = "./packages/primitives/leptos/context" #radix-leptos-collection.path = "./packages/primitives/leptos/collection" #radix-leptos-compose-refs.path = "./packages/primitives/leptos/compose-refs" #radix-leptos-direction.path = "./packages/primitives/leptos/direction" From f4d80ff08c594554cb4328929f097179d38d6b68 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Mon, 6 Jan 2025 20:32:44 +0200 Subject: [PATCH 11/17] Update Avatar to Leptos 0.7 Closes #421 --- Cargo.toml | 55 +--- packages/primitives/leptos/avatar/Cargo.toml | 5 + packages/primitives/leptos/avatar/README.md | 2 +- .../primitives/leptos/avatar/src/avatar.rs | 307 +++++++++++------- 4 files changed, 206 insertions(+), 163 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 92609712..206c2e20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,44 +6,18 @@ members = [ "packages/colors", "packages/icons/dioxus", "packages/icons/yew", - - # -- Leptos Primitives (commented until they're upgraded) -- - # "packages/primitives/leptos/accessible-icon", - # "packages/primitives/leptos/arrow", - # "packages/primitives/leptos/aspect-ratio", - # "packages/primitives/leptos/avatar", - # "packages/primitives/leptos/checkbox", - "packages/primitives/leptos/context", - # "packages/primitives/leptos/collection", - # "packages/primitives/leptos/compose-refs", - # "packages/primitives/leptos/direction", - # "packages/primitives/leptos/dismissable-layer", - # "packages/primitives/leptos/dropdown-menu", - # "packages/primitives/leptos/focus-guards", - # "packages/primitives/leptos/focus-scope", - # "packages/primitives/leptos/id", - # "packages/primitives/leptos/label", - # "packages/primitives/leptos/menu", - # "packages/primitives/leptos/popover", - # "packages/primitives/leptos/popper", - # "packages/primitives/leptos/portal", - # "packages/primitives/leptos/presence", - # "packages/primitives/leptos/primitive", - # "packages/primitives/leptos/progress", - # "packages/primitives/leptos/roving-focus", - # "packages/primitives/leptos/select", - # "packages/primitives/leptos/separator", - # "packages/primitives/leptos/slot", - # "packages/primitives/leptos/switch", - # "packages/primitives/leptos/tabs", - # "packages/primitives/leptos/toggle", - # "packages/primitives/leptos/use-controllable-state", - # "packages/primitives/leptos/use-escape-keydown", - # "packages/primitives/leptos/use-previous", - # "packages/primitives/leptos/use-size", - # "packages/primitives/leptos/visually-hidden", - - # -- Yew Primitives -- + "packages/primitives/leptos/accessible-icon", + "packages/primitives/leptos/arrow", + "packages/primitives/leptos/avatar", + "packages/primitives/leptos/aspect-ratio", + "packages/primitives/leptos/direction", + "packages/primitives/leptos/id", + "packages/primitives/leptos/label", + "packages/primitives/leptos/use-controllable-state", + "packages/primitives/leptos/use-escape-keydown", + "packages/primitives/leptos/use-previous", + "packages/primitives/leptos/use-size", + "packages/primitives/leptos/visually-hidden", "packages/primitives/yew/*", # -- Themes, Scripts, and Stories -- @@ -70,6 +44,7 @@ leptos_dom = "0.7.2" leptos_router = "0.7.2" leptos-node-ref = "0.0.3" leptos-maybe-callback = "0.0.3" +leptos-use = "0.15.2" leptos-style = "0.0.3" leptos-typed-fallback-show = "0.0.3" leptos-use = "0.15.2" @@ -83,6 +58,8 @@ yew = "0.21.0" yew-router = "0.18.0" yew-struct-component = "0.1.4" yew-style = "0.1.4" +radix-leptos-primitive.version = "0.0.2" +radix-leptos-context.version = "0.0.2" # Subcrate packages (paths remain the same; you can comment out any subcrate not yet upgraded). # We centralize shared dependencies in [workspace.dependencies] for a single source of truth, @@ -125,3 +102,5 @@ radix-leptos-primitive.path = "./packages/primitives/leptos/primitive" yew = { git = "https://github.com/RustForWeb/yew.git", branch = "feature/use-composed-ref" } yew-router = { git = "https://github.com/RustForWeb/yew.git", branch = "feature/use-composed-ref" } leptos-node-ref = { git = "https://github.com/geoffreygarrett/leptos-utils", branch = "feature/any-node-ref" } +radix-leptos-primitive = { git = "https://github.com/geoffreygarrett/radix", branch = "update/leptos-0.7-primitive" } +radix-leptos-context = { git = "https://github.com/geoffreygarrett/radix", branch = "update/leptos-0.7-context" } \ No newline at end of file diff --git a/packages/primitives/leptos/avatar/Cargo.toml b/packages/primitives/leptos/avatar/Cargo.toml index 96c4f8b3..5468890c 100644 --- a/packages/primitives/leptos/avatar/Cargo.toml +++ b/packages/primitives/leptos/avatar/Cargo.toml @@ -11,4 +11,9 @@ version.workspace = true [dependencies] leptos.workspace = true +leptos-node-ref.workspace = true +leptos-maybe-callback.workspace = true +leptos-use.workspace = true +radix-leptos-primitive.workspace = true +radix-leptos-context.workspace = true web-sys.workspace = true diff --git a/packages/primitives/leptos/avatar/README.md b/packages/primitives/leptos/avatar/README.md index 1f53f3d2..36754b87 100644 --- a/packages/primitives/leptos/avatar/README.md +++ b/packages/primitives/leptos/avatar/README.md @@ -14,6 +14,6 @@ See [the Rust Radix book](https://radix.rustforweb.org/) for documentation. ## Rust For Web -The Rust Radix project is part of [Rust For Web](https://github.com/RustForWeb). +The Rust Radix project is part of the [Rust For Web](https://github.com/RustForWeb). [Rust For Web](https://github.com/RustForWeb) creates and ports web UI libraries for Rust. All projects are free and open source. diff --git a/packages/primitives/leptos/avatar/src/avatar.rs b/packages/primitives/leptos/avatar/src/avatar.rs index 9569599f..1922c258 100644 --- a/packages/primitives/leptos/avatar/src/avatar.rs +++ b/packages/primitives/leptos/avatar/src/avatar.rs @@ -1,8 +1,18 @@ -use leptos::{html::AnyElement, *}; -use web_sys::{ - wasm_bindgen::{closure::Closure, JsCast}, - HtmlImageElement, -}; +use leptos::prelude::*; +use leptos::context::Provider; +use leptos::{html}; +use leptos::html::Img; +use leptos::wasm_bindgen::closure::Closure; +use leptos::wasm_bindgen::JsCast; +use leptos_node_ref::prelude::*; +use leptos_use::{use_timeout_fn, UseTimeoutFnReturn}; +use leptos_maybe_callback::MaybeCallback; +use radix_leptos_context::create_context; +use radix_leptos_primitive::{Primitive, VoidPrimitive}; + +/* ------------------------------------------------------------------------------------------------- + * Types + * -----------------------------------------------------------------------------------------------*/ #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub enum ImageLoadingStatus { @@ -18,182 +28,231 @@ struct AvatarContextValue { on_image_loading_status_change: Callback, } +/* ------------------------------------------------------------------------------------------------- + * Avatar (Root) + * -----------------------------------------------------------------------------------------------*/ + +const AVATAR_NAME: &'static str = "Avatar"; + +create_context!( + context_type: AvatarContextValue, + provider: AvatarProvider, + hook: use_avatar_context, + root: AVATAR_NAME +); + #[component] +#[allow(non_snake_case)] pub fn Avatar( + /// If `true`, renders only its children without a `` wrapper. #[prop(into, optional)] as_child: MaybeProp, - #[prop(optional)] node_ref: NodeRef, - #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, - children: ChildrenFn, + /// A reference to the underlying `` element, if needed. + #[prop(into, optional)] node_ref: AnyNodeRef, + /// The children of the Avatar component. + children: TypedChildrenFn, ) -> impl IntoView { - let (image_loading_status, set_image_loading_status) = create_signal(ImageLoadingStatus::Idle); + let children = StoredValue::new(children.into_inner()); + + // Initialize the image loading status signal using `RwSignal` + let image_loading_status = RwSignal::new(ImageLoadingStatus::Idle); + // Define the context value with the current loading status and a callback to update it let context_value = AvatarContextValue { - image_loading_status, - on_image_loading_status_change: Callback::new(move |image_loading_status| { - set_image_loading_status.set(image_loading_status) + image_loading_status: image_loading_status.read_only(), + on_image_loading_status_change: Callback::new(move |status| { + image_loading_status.set(status); }), }; view! { - - - {children()} + + + {children.with_value(|children| children())} - + } } +/* ------------------------------------------------------------------------------------------------- + * AvatarImage + * -----------------------------------------------------------------------------------------------*/ + +const IMAGE_NAME: &'static str = "AvatarImage"; + #[component] +#[allow(non_snake_case)] pub fn AvatarImage( #[prop(into, optional)] src: MaybeProp, - #[prop(into, optional)] on_loading_status_change: Option>, + #[prop(into, optional)] referrer_policy: MaybeProp, + #[prop(into, optional)] on_loading_status_change: MaybeCallback, #[prop(into, optional)] as_child: MaybeProp, - #[prop(optional)] node_ref: NodeRef, - #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, - #[prop(optional)] children: Option, + #[prop(optional)] node_ref: NodeRef, ) -> impl IntoView { - let children = StoredValue::new(children); - - let context = expect_context::(); - let image_loading_status = use_image_loading_status(src.clone()); - let handle_loading_status_change = move |status: ImageLoadingStatus| { - if let Some(on_loading_status_change) = on_loading_status_change { - on_loading_status_change.call(status); - } - context.on_image_loading_status_change.call(status); - }; + let context = use_avatar_context(IMAGE_NAME); + let loading_status = use_image_loading_status(src.clone(), referrer_policy.clone()); + // Update context and callback when loading status changes Effect::new(move |_| { - let image_loading_status = image_loading_status.get(); - if image_loading_status != ImageLoadingStatus::Idle { - handle_loading_status_change(image_loading_status); - } + let status = loading_status.get(); + context.on_image_loading_status_change.run(status); + on_loading_status_change.run(status); }); - let mut attrs = attrs.clone(); - attrs.extend([("src", src.into_attribute())]); - let attrs = StoredValue::new(attrs); - view! { - - + - {children.with_value(|children| children.as_ref().map(|children| children()))} - + {()} + } } +/* ------------------------------------------------------------------------------------------------- + * AvatarFallback + * -----------------------------------------------------------------------------------------------*/ + +const FALLBACK_NAME: &'static str = "AvatarFallback"; + #[component] pub fn AvatarFallback( - #[prop(into, optional)] delay_ms: MaybeProp, + /// Children (for example, initials or an icon). + children: TypedChildrenFn, + /// Delay (in ms) before showing the fallback ``. If no delay, fallback appears immediately. + #[prop(into, optional)] delay_ms: MaybeProp, + /// If `true`, renders only its children without a `` wrapper. #[prop(into, optional)] as_child: MaybeProp, - #[prop(optional)] node_ref: NodeRef, - #[prop(attrs)] attrs: Vec<(&'static str, Attribute)>, - #[prop(optional)] children: Option, + /// A reference to the `` element for the fallback. + #[prop(into, optional)] node_ref: AnyNodeRef, ) -> impl IntoView { - let attrs = StoredValue::new(attrs); - let children = StoredValue::new(children); + let children = StoredValue::new(children.into_inner()); + let context = use_avatar_context(FALLBACK_NAME); - let context = expect_context::(); - let (can_render, set_can_render) = create_signal(delay_ms.get().is_none()); + // use_timeout_fn from leptos_use to handle the delay before showing fallback + let UseTimeoutFnReturn { start, stop, is_pending, .. } = use_timeout_fn( + move |_| {}, + delay_ms.get().unwrap_or_default(), + ); - let handler: Closure = Closure::new(move || { - set_can_render.set(true); - }); + // If no delay is set, fallback can render immediately + let can_render = RwSignal::new(delay_ms.get().is_none()); - let timer_id = StoredValue::new(None::); - Effect::new(move |_| { - if let Some(timer_id) = timer_id.get_value() { - window().clear_timeout_with_handle(timer_id); - } + // Re-initialize the timer whenever `delay_ms` changes + Effect::new(move || { + stop(); + can_render.set(delay_ms.get().is_none()); + + #[cfg(debug_assertions)] + leptos::logging::log!( + "[{FALLBACK_NAME}] delay_ms changed: {:?}", + delay_ms.get() + ); - if let Some(delay_ms) = delay_ms.get() { - timer_id.set_value(Some( - window() - .set_timeout_with_callback_and_timeout_and_arguments_0( - handler.as_ref().unchecked_ref(), - delay_ms, - ) - .expect("Timeout should be set."), - )); + if let Some(ms) = delay_ms.get() { + #[cfg(debug_assertions)] + leptos::logging::log!("[{FALLBACK_NAME}] Starting timeout for {} ms", ms); + start(ms as i32); } }); - on_cleanup(move || { - if let Some(timer_id) = timer_id.get_value() { - window().clear_timeout_with_handle(timer_id); + // Watch if the timer has completed + Effect::new(move || { + if !is_pending.get() && delay_ms.get().is_some() { + #[cfg(debug_assertions)] + leptos::logging::log!("[{FALLBACK_NAME}] Timer completed, can_render=true"); + can_render.set(true); } }); + // Render fallback only if `can_render` is true and the image is not loaded view! { - - - {children.with_value(|children| children.as_ref().map(|children| children()))} + + + {children.with_value(|children| children())} } } -fn use_image_loading_status(src: MaybeProp) -> ReadSignal { - let (loading_status, set_loading_status) = create_signal(ImageLoadingStatus::Idle); - let is_mounted = StoredValue::new(true); +/* -----------------------------------------------------------------------------------------------*/ - let update_status_loaded: Closure = Closure::new(move || { - if is_mounted.get_value() { - set_loading_status.set(ImageLoadingStatus::Loaded); - } - }); - let update_status_error: Closure = Closure::new(move || { - if is_mounted.get_value() { - set_loading_status.set(ImageLoadingStatus::Error); - } - }); +fn use_image_loading_status( + src: MaybeProp, + referrer_policy: MaybeProp, +) -> ReadSignal { + let loading_status = RwSignal::new(ImageLoadingStatus::Idle); Effect::new(move |_| { - if let Some(src) = src.get() { - let image = document() - .create_element("img") - .map(|element| element.unchecked_into::()) - .expect("Image element should be created."); - - set_loading_status.set(ImageLoadingStatus::Loading); - - image - .add_event_listener_with_callback( - "load", - update_status_loaded.as_ref().unchecked_ref(), - ) - .expect("Load event listener should be added."); - image - .add_event_listener_with_callback( - "error", - update_status_error.as_ref().unchecked_ref(), - ) - .expect("Error event listener should be added."); - image.set_src(&src); + if let Some(src_val) = src.get() { + #[cfg(debug_assertions)] + leptos::logging::log!("[{IMAGE_NAME}] Starting load for: {}", src_val); + + loading_status.set(ImageLoadingStatus::Loading); + + let image = web_sys::HtmlImageElement::new().unwrap(); + + // Clone image for closures + let image_clone = image.clone(); + let onload = Closure::wrap(Box::new(move || { + if image_clone.natural_width() > 0 { + #[cfg(debug_assertions)] + leptos::logging::log!("[{IMAGE_NAME}] Load successful"); + loading_status.set(ImageLoadingStatus::Loaded); + } else { + #[cfg(debug_assertions)] + leptos::logging::log!("[{IMAGE_NAME}] Load failed - invalid image"); + loading_status.set(ImageLoadingStatus::Error); + } + }) as Box); + + let onerror = Closure::wrap(Box::new(move || { + #[cfg(debug_assertions)] + leptos::logging::log!("[{IMAGE_NAME}] Load failed"); + loading_status.set(ImageLoadingStatus::Error); + }) as Box); + + image.set_onload(Some(onload.as_ref().unchecked_ref())); + image.set_onerror(Some(onerror.as_ref().unchecked_ref())); + + if let Some(policy) = referrer_policy.get() { + image.set_referrer_policy(&policy); + } + + image.set_src(&src_val); + + onload.forget(); + onerror.forget(); } else { - set_loading_status.set(ImageLoadingStatus::Error); + #[cfg(debug_assertions)] + leptos::logging::log!("[{IMAGE_NAME}] No src provided"); + loading_status.set(ImageLoadingStatus::Error); } }); - on_cleanup(move || { - is_mounted.set_value(false); - }); + loading_status.read_only() +} + +/* ------------------------------------------------------------------------------------------------- + * Primitive re-exports + * -----------------------------------------------------------------------------------------------*/ - loading_status +pub mod primitive { + // Re-export core items so consumers can use avatar::primitive::* as AvatarPrimitive + pub use super::*; + pub use Avatar as Root; + pub use AvatarImage as Image; + pub use AvatarFallback as Fallback; } From 176c9ab941ede6918199eb9684e7eead96d17d27 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Mon, 6 Jan 2025 21:03:26 +0200 Subject: [PATCH 12/17] Update Avatar to Leptos 0.7 Closes #421 --- packages/primitives/leptos/avatar/src/avatar.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/primitives/leptos/avatar/src/avatar.rs b/packages/primitives/leptos/avatar/src/avatar.rs index 1922c258..118087cf 100644 --- a/packages/primitives/leptos/avatar/src/avatar.rs +++ b/packages/primitives/leptos/avatar/src/avatar.rs @@ -66,7 +66,7 @@ pub fn Avatar( view! { - + {children.with_value(|children| children())} @@ -82,6 +82,7 @@ const IMAGE_NAME: &'static str = "AvatarImage"; #[component] #[allow(non_snake_case)] pub fn AvatarImage( + #[prop(optional)] children: Option, #[prop(into, optional)] src: MaybeProp, #[prop(into, optional)] referrer_policy: MaybeProp, #[prop(into, optional)] on_loading_status_change: MaybeCallback, @@ -89,6 +90,7 @@ pub fn AvatarImage( #[prop(optional)] node_ref: NodeRef, ) -> impl IntoView { let context = use_avatar_context(IMAGE_NAME); + let children = StoredValue::new(children); let loading_status = use_image_loading_status(src.clone(), referrer_policy.clone()); // Update context and callback when loading status changes @@ -110,7 +112,7 @@ pub fn AvatarImage( attr:src=move || src.get() attr:referrerpolicy=move || referrer_policy.get() > - {()} + {children.with_value(|children| children.as_ref().map(|children| children()))} } From 5a93e6f58de009715c362d558ec8cfd2b4ba1c9f Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Tue, 7 Jan 2025 09:20:44 +0200 Subject: [PATCH 13/17] Add documentation for Avatar component --- stories/leptos/Cargo.toml | 2 +- stories/leptos/src/app.rs | 20 +-- stories/leptos/src/primitives.rs | 2 +- stories/leptos/src/primitives/avatar.rs | 174 +++++++++++++----------- 4 files changed, 105 insertions(+), 93 deletions(-) diff --git a/stories/leptos/Cargo.toml b/stories/leptos/Cargo.toml index b56a152e..4f1a836e 100644 --- a/stories/leptos/Cargo.toml +++ b/stories/leptos/Cargo.toml @@ -18,7 +18,7 @@ log.workspace = true radix-leptos-accessible-icon = { path = "../../packages/primitives/leptos/accessible-icon" } radix-leptos-arrow = { path = "../../packages/primitives/leptos/arrow" } radix-leptos-aspect-ratio = { path = "../../packages/primitives/leptos/aspect-ratio" } -# radix-leptos-avatar = { path = "../../packages/primitives/leptos/avatar" } + radix-leptos-avatar = { path = "../../packages/primitives/leptos/avatar" } # radix-leptos-checkbox = { path = "../../packages/primitives/leptos/checkbox" } # radix-leptos-collection = { path = "../../packages/primitives/leptos/collection" } # radix-leptos-focus-scope = { path = "../../packages/primitives/leptos/focus-scope" } diff --git a/stories/leptos/src/app.rs b/stories/leptos/src/app.rs index 6f066343..5d78008f 100644 --- a/stories/leptos/src/app.rs +++ b/stories/leptos/src/app.rs @@ -4,7 +4,7 @@ use leptos_router::{ path, }; -use crate::primitives::{accessible_icon, arrow, aspect_ratio, label, visually_hidden}; +use crate::primitives::{accessible_icon, arrow, avatar, aspect_ratio, label, visually_hidden}; #[component] fn NavLink(href: H, children: Children) -> impl IntoView @@ -61,14 +61,14 @@ pub fn App() -> impl IntoView {
  • Chromatic
  • - //
  • - // Avatar +
  • + Avatar - //
      - //
    • Styled
    • - //
    • Chromatic
    • - //
    - //
  • +
      +
    • Styled
    • +
    • Chromatic
    • +
    + //
  • // Checkbox @@ -218,8 +218,8 @@ pub fn App() -> impl IntoView { - // - // + + // // diff --git a/stories/leptos/src/primitives.rs b/stories/leptos/src/primitives.rs index 4af00626..937fd629 100644 --- a/stories/leptos/src/primitives.rs +++ b/stories/leptos/src/primitives.rs @@ -1,7 +1,7 @@ pub mod accessible_icon; pub mod arrow; pub mod aspect_ratio; -// pub mod avatar; +pub mod avatar; // pub mod checkbox; // pub mod collection; // pub mod focus_scope; diff --git a/stories/leptos/src/primitives/avatar.rs b/stories/leptos/src/primitives/avatar.rs index f49d033b..39525386 100644 --- a/stories/leptos/src/primitives/avatar.rs +++ b/stories/leptos/src/primitives/avatar.rs @@ -1,101 +1,113 @@ -use leptos::*; +use leptos::prelude::*; use radix_leptos_avatar::*; -use tailwind_fuse::*; -const SRC: &str = "https://picsum.photos/id/1005/400/400"; -const SRC_BROKEN: &str = "https://broken.link.com/broken-pic.jpg"; +const COLM_IMG: &str = "https://images.unsplash.com/photo-1492633423870-43d1cd2775eb?&w=128&h=128&dpr=2&q=80"; +const PEDRO_IMG: &str = "https://images.unsplash.com/photo-1511485977113-f34c92461ad9?ixlib=rb-1.2.1&w=128&h=128&dpr=2&q=80"; +const BROKEN_IMG: &str = "https://broken.link.com/broken-pic.jpg"; + +const ROOT_CLASS: &str = "inline-flex items-center justify-center align-middle overflow-hidden select-none rounded-full w-[45px] h-[45px] bg-black/10"; +const IMAGE_CLASS: &str = "w-full h-full object-cover rounded-[inherit]"; +const FALLBACK_CLASS: &str = "w-full h-full flex items-center justify-center bg-white text-violet-900 text-[15px] leading-none font-medium"; #[component] pub fn Styled() -> impl IntoView { - let root_class = Memo::new(move |_| RootClass::default().to_class()); - let image_class = Memo::new(move |_| ImageClass::default().to_class()); - let fallback_class = Memo::new(move |_| FallbackClass::default().to_class()); - view! { -

    Without image & with fallback

    - - JS - +
    +
    +

    Basic

    +
    + + + CT + + + + + PD + + + + PD + +
    +
    + +
    +

    Sizes

    +
    + + + S + -

    With image & with fallback

    - - - - JS - - + + + M + -

    With image & with fallback (but broken src)

    - - - - - - - } + + + L + +
    +
    +
    + } } #[component] pub fn Chromatic() -> impl IntoView { - let root_class = Memo::new(move |_| RootClass::default().to_class()); - let image_class = Memo::new(move |_| ImageClass::default().to_class()); - let fallback_class = Memo::new(move |_| FallbackClass::default().to_class()); - view! { -

    Without image & with fallback

    - - JS - +
    +
    +

    Loading States

    +
    +
    +

    Instant fallback

    + + + IF + +
    -

    With image & with fallback

    - - - - JS - - +
    +

    With delay

    + + + 6D + +
    +
    +
    -

    With image & with fallback (but broken src)

    - - - - - - - } +
    +

    Fallback Types

    +
    + + + + + + +
    +
    +
    + } } #[component] fn AvatarIcon() -> impl IntoView { view! { - - - - } -} - -#[derive(TwClass, Default, Clone, Copy)] -#[tw( - class = "inline-flex items-center justify-center align-middle overflow-hidden select-none rounded-[9999px] w-[48px] h-[48px]" -)] -struct RootClass {} - -#[derive(TwClass, Default, Clone, Copy)] -#[tw(class = "w-full h-full object-cover")] -struct ImageClass {} - -#[derive(TwClass, Default, Clone, Copy)] -#[tw(class = "w-full h-full flex items-center justify-center bg-[#111] text-[#fff]")] -struct FallbackClass {} + + + + } +} \ No newline at end of file From 7191e0214aea55e080d2952ee282fd1f9e4038b6 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Tue, 7 Jan 2025 09:24:11 +0200 Subject: [PATCH 14/17] Change Avatar delay type to integer --- packages/primitives/leptos/avatar/src/avatar.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/primitives/leptos/avatar/src/avatar.rs b/packages/primitives/leptos/avatar/src/avatar.rs index 118087cf..54281aeb 100644 --- a/packages/primitives/leptos/avatar/src/avatar.rs +++ b/packages/primitives/leptos/avatar/src/avatar.rs @@ -1,7 +1,6 @@ use leptos::prelude::*; use leptos::context::Provider; use leptos::{html}; -use leptos::html::Img; use leptos::wasm_bindgen::closure::Closure; use leptos::wasm_bindgen::JsCast; use leptos_node_ref::prelude::*; @@ -87,7 +86,7 @@ pub fn AvatarImage( #[prop(into, optional)] referrer_policy: MaybeProp, #[prop(into, optional)] on_loading_status_change: MaybeCallback, #[prop(into, optional)] as_child: MaybeProp, - #[prop(optional)] node_ref: NodeRef, + #[prop(into, optional)] node_ref: AnyNodeRef, ) -> impl IntoView { let context = use_avatar_context(IMAGE_NAME); let children = StoredValue::new(children); @@ -129,7 +128,7 @@ pub fn AvatarFallback( /// Children (for example, initials or an icon). children: TypedChildrenFn, /// Delay (in ms) before showing the fallback ``. If no delay, fallback appears immediately. - #[prop(into, optional)] delay_ms: MaybeProp, + #[prop(into, optional)] delay_ms: MaybeProp, /// If `true`, renders only its children without a `` wrapper. #[prop(into, optional)] as_child: MaybeProp, /// A reference to the `` element for the fallback. @@ -141,7 +140,7 @@ pub fn AvatarFallback( // use_timeout_fn from leptos_use to handle the delay before showing fallback let UseTimeoutFnReturn { start, stop, is_pending, .. } = use_timeout_fn( move |_| {}, - delay_ms.get().unwrap_or_default(), + delay_ms.get().unwrap_or_default() as f64, ); // If no delay is set, fallback can render immediately From 48ce4a2314466ef01f91efdec4441ba867195713 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 15:37:51 +0200 Subject: [PATCH 15/17] Update Avatar to Leptos 0.7 --- Cargo.toml | 55 +++++++++++++++++-------- stories/leptos/src/primitives/avatar.rs | 4 +- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 206c2e20..0499ea01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,18 +6,44 @@ members = [ "packages/colors", "packages/icons/dioxus", "packages/icons/yew", - "packages/primitives/leptos/accessible-icon", - "packages/primitives/leptos/arrow", - "packages/primitives/leptos/avatar", - "packages/primitives/leptos/aspect-ratio", - "packages/primitives/leptos/direction", - "packages/primitives/leptos/id", - "packages/primitives/leptos/label", - "packages/primitives/leptos/use-controllable-state", - "packages/primitives/leptos/use-escape-keydown", - "packages/primitives/leptos/use-previous", - "packages/primitives/leptos/use-size", - "packages/primitives/leptos/visually-hidden", + + # -- Leptos Primitives (commented until they're upgraded) -- + # "packages/primitives/leptos/accessible-icon", + # "packages/primitives/leptos/arrow", + # "packages/primitives/leptos/aspect-ratio", + # "packages/primitives/leptos/avatar", + # "packages/primitives/leptos/checkbox", + "packages/primitives/leptos/context", + # "packages/primitives/leptos/collection", + # "packages/primitives/leptos/compose-refs", + # "packages/primitives/leptos/direction", + # "packages/primitives/leptos/dismissable-layer", + # "packages/primitives/leptos/dropdown-menu", + # "packages/primitives/leptos/focus-guards", + # "packages/primitives/leptos/focus-scope", + # "packages/primitives/leptos/id", + # "packages/primitives/leptos/label", + # "packages/primitives/leptos/menu", + # "packages/primitives/leptos/popover", + # "packages/primitives/leptos/popper", + # "packages/primitives/leptos/portal", + # "packages/primitives/leptos/presence", + "packages/primitives/leptos/primitive", + # "packages/primitives/leptos/progress", + # "packages/primitives/leptos/roving-focus", + # "packages/primitives/leptos/select", + # "packages/primitives/leptos/separator", + # "packages/primitives/leptos/slot", + # "packages/primitives/leptos/switch", + # "packages/primitives/leptos/tabs", + # "packages/primitives/leptos/toggle", + # "packages/primitives/leptos/use-controllable-state", + # "packages/primitives/leptos/use-escape-keydown", + # "packages/primitives/leptos/use-previous", + # "packages/primitives/leptos/use-size", + # "packages/primitives/leptos/visually-hidden", + + # -- Yew Primitives -- "packages/primitives/yew/*", # -- Themes, Scripts, and Stories -- @@ -44,7 +70,6 @@ leptos_dom = "0.7.2" leptos_router = "0.7.2" leptos-node-ref = "0.0.3" leptos-maybe-callback = "0.0.3" -leptos-use = "0.15.2" leptos-style = "0.0.3" leptos-typed-fallback-show = "0.0.3" leptos-use = "0.15.2" @@ -58,8 +83,6 @@ yew = "0.21.0" yew-router = "0.18.0" yew-struct-component = "0.1.4" yew-style = "0.1.4" -radix-leptos-primitive.version = "0.0.2" -radix-leptos-context.version = "0.0.2" # Subcrate packages (paths remain the same; you can comment out any subcrate not yet upgraded). # We centralize shared dependencies in [workspace.dependencies] for a single source of truth, @@ -102,5 +125,3 @@ radix-leptos-primitive.path = "./packages/primitives/leptos/primitive" yew = { git = "https://github.com/RustForWeb/yew.git", branch = "feature/use-composed-ref" } yew-router = { git = "https://github.com/RustForWeb/yew.git", branch = "feature/use-composed-ref" } leptos-node-ref = { git = "https://github.com/geoffreygarrett/leptos-utils", branch = "feature/any-node-ref" } -radix-leptos-primitive = { git = "https://github.com/geoffreygarrett/radix", branch = "update/leptos-0.7-primitive" } -radix-leptos-context = { git = "https://github.com/geoffreygarrett/radix", branch = "update/leptos-0.7-context" } \ No newline at end of file diff --git a/stories/leptos/src/primitives/avatar.rs b/stories/leptos/src/primitives/avatar.rs index 39525386..8208fd56 100644 --- a/stories/leptos/src/primitives/avatar.rs +++ b/stories/leptos/src/primitives/avatar.rs @@ -109,5 +109,5 @@ fn AvatarIcon() -> impl IntoView { fill="currentColor" /> - } -} \ No newline at end of file + } +} From cc7e10f98875657cd6ede5b9a35fe4faaad84cf5 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 16:11:39 +0200 Subject: [PATCH 16/17] Update Avatar to Leptos 0.7 --- book/src/primitives/components/avatar.md | 146 ++++++++++++++++++----- 1 file changed, 113 insertions(+), 33 deletions(-) diff --git a/book/src/primitives/components/avatar.md b/book/src/primitives/components/avatar.md index 0eac8ef4..11164202 100644 --- a/book/src/primitives/components/avatar.md +++ b/book/src/primitives/components/avatar.md @@ -25,9 +25,9 @@ files = ["src/avatar.rs"] ## Features -- Automatic and manual control over when the image renders. -- Fallback part accepts any children. -- Optionally delay fallback rendering to avoid content flashing. +- Automatic and manual control over when the image renders. +- Fallback part accepts any children. +- Optionally delay fallback rendering to avoid content flashing. ## Installation @@ -40,9 +40,9 @@ Install the component from your command line. cargo add radix-leptos-avatar ``` -- [View on crates.io](https://crates.io/crates/radix-leptos-avatar) -- [View on docs.rs](https://docs.rs/radix-leptos-avatar/latest/radix_leptos_avatar/) -- [View source](https://github.com/RustForWeb/radix/tree/main/packages/primitives/leptos/avatar) +- [View on crates.io](https://crates.io/crates/radix-leptos-avatar) +- [View on docs.rs](https://docs.rs/radix-leptos-avatar/latest/radix_leptos_avatar/) +- [View source](https://github.com/RustForWeb/radix/tree/main/packages/primitives/leptos/avatar) {{#endtab }} {{#tab name="Yew" }} @@ -51,9 +51,9 @@ cargo add radix-leptos-avatar cargo add radix-yew-avatar ``` -- [View on crates.io](https://crates.io/crates/radix-yew-avatar) -- [View on docs.rs](https://docs.rs/radix-yew-avatar/latest/radix_yew_avatar/) -- [View source](https://github.com/RustForWeb/radix/tree/main/packages/primitives/yew/avatar) +- [View on crates.io](https://crates.io/crates/radix-yew-avatar) +- [View on docs.rs](https://docs.rs/radix-yew-avatar/latest/radix_yew_avatar/) +- [View source](https://github.com/RustForWeb/radix/tree/main/packages/primitives/yew/avatar) {{#endtab }} {{#endtabs }} @@ -67,15 +67,17 @@ Import all parts and piece them together. ```rust,ignore use leptos::*; -use radix_leptos_avatar::*; +use radix_leptos_avatar::primitive as Avatar; #[component] fn Anatomy() -> impl IntoView { view! { - - - - + + + + {"AB"} + + } } ``` @@ -110,60 +112,70 @@ Contains all the parts of an avatar. {{#tabs global="framework" }} {{#tab name="Leptos" }} -| Prop | Type | Default | -| ---------- | ----------------- | ------- | -| `as_child` | `MaybeProp` | `false` | +| Prop | Type | Default | Description | +|------------|-------------------|---------|------------------------------------------------------------------------------------------| +| `as_child` | `MaybeProp` | `false` | If `true`, renders only its children without a `` wrapper. | +| `node_ref` | `AnyNodeRef` | - | Optional reference to the underlying `` element. | +| `children` | `TypedChildrenFn` | - | The content of the `Avatar` component (commonly `Avatar::Image` and `Avatar::Fallback`). | {{#endtab }} {{#tab name="Yew" }} | Prop | Type | Default | -| ---------- | ------------------------------------------ | ------- | +|------------|--------------------------------------------|---------| | `as_child` | `Option>` | - | {{#endtab }} {{#endtabs }} -### Image +### AvatarImage -The image to render. By default it will only render when it has loaded. You can use the `on_loading_status_change` handler if you need more control. +Displays the image. By default, it only renders if the image successfully loads. +Use the `on_loading_status_change` callback or check the context (see `use_avatar_context`) if you need more control. {{#tabs global="framework" }} {{#tab name="Leptos" }} -| Prop | Type | Default | -| -------------------------- | -------------------------------------- | ------- | -| `as_child` | `MaybeProp` | `false` | -| `on_loading_status_change` | `Option>` | - | +| Prop | Type | Default | Description | +|----------------------------|-------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| `src` | `MaybeProp` | - | The source URL for the avatar image. | +| `referrer_policy` | `MaybeProp` | - | Sets the `referrerpolicy` attribute of the `` tag if needed. | +| `as_child` | `MaybeProp` | `false` | If `true`, renders only its children without an `` tag. | +| `on_loading_status_change` | `MaybeCallback` | - | Callback to be fired when the image loading status changes. Receives an `ImageLoadingStatus` enum value (`Idle`, `Loading`, `Loaded`, `Error`). | +| `node_ref` | `AnyNodeRef` | - | Optional reference to the underlying `` element. | +| `children` | `Option` | - | If `as_child = true`, can pass child elements to be used instead of an ``. | {{#endtab }} {{#tab name="Yew" }} | Prop | Type | Default | -| -------------------------- | ----------------------------------------------- | ------- | +|----------------------------|-------------------------------------------------|---------| | `as_child` | `Option>` | - | | `on_loading_status_change` | `Callback` | - | {{#endtab }} {{#endtabs }} -### Fallback +### AvatarFallback -An element that renders when the image hasn't loaded. This means whilst it's loading, or if there was an error. If you notice a flash during loading, you can provide a `delay_ms` prop to delay its rendering so it only renders for those with slower connections. For more control, use the `on_loading_status_change` handler on `AvatarImage`. +Renders its children while the image is loading or if it fails to load. +Use `delay_ms` to avoid flashing the fallback for users with a fast connection. {{#tabs global="framework" }} {{#tab name="Leptos" }} -| Prop | Type | Default | -| ---------- | ----------------- | ------- | -| `as_child` | `MaybeProp` | `false` | -| `delay_ms` | `MaybeProp` | - | +| Prop | Type | Default | Description | +|------------|-------------------|---------|--------------------------------------------------------------------------------------------------------------------------------------------| +| `delay_ms` | `MaybeProp` | - | Delay (in milliseconds) before showing the fallback content. If none is provided, fallback shows up immediately if the image isn't loaded. | +| `as_child` | `MaybeProp` | `false` | If `true`, renders only its children without a `` wrapper. | +| `node_ref` | `AnyNodeRef` | - | Optional reference to the `` element. | +| `children` | `TypedChildrenFn` | - | Typically, initials or an icon to display in place of the image. | {{#endtab }} {{#tab name="Yew" }} | Prop | Type | Default | -| ---------- | -------------------------------------------------- | ------- | +|------------|----------------------------------------------------|---------| | `as_child` | `Option>` | - | | `delay_ms` | `Option` | - | @@ -172,8 +184,76 @@ An element that renders when the image hasn't loaded. This means whilst it's loa ## Examples +**Basic Usage** + +{{#tabs global="framework" }} +{{#tab name="Leptos" }} + +```rust,ignore +use leptos::*; +use radix_leptos_avatar::primitive as Avatar; + +#[component] +fn DemoAvatar() -> impl IntoView { + view! { + + + + {"JD"} + + + } +} +``` + +{{#endtab }} +{{#tab name="Yew" }} TODO +{{#endtab }} +{{#endtabs }} + +**Custom Callback** + +{{#tabs global="framework" }} +{{#tab name="Leptos" }} + +```rust,ignore +use leptos::*; +use radix_leptos_avatar::primitive as Avatar; +use radix_leptos_avatar::ImageLoadingStatus; + +#[component] +fn AvatarWithCallback() -> impl IntoView { + let on_status_change = move |status: ImageLoadingStatus| { + match status { + ImageLoadingStatus::Idle => log!("Avatar Image: Idle"), + ImageLoadingStatus::Loading => log!("Avatar Image: Loading"), + ImageLoadingStatus::Loaded => log!("Avatar Image: Loaded"), + ImageLoadingStatus::Error => log!("Avatar Image: Failed to load"), + } + }; + + view! { + + + + {"Fallback"} + + + } +} +``` + +{{#endtab }} +{{#tab name="Yew" }} +TODO +{{#endtab }} +{{#endtabs }} ## See Also -- [Radix documentation](https://www.radix-ui.com/primitives/docs/components/avatar) +- [Radix UI documentation](https://www.radix-ui.com/primitives/docs/components/avatar) +- [Repository and more examples](https://github.com/RustForWeb/radix) \ No newline at end of file From f559f6f73b7981805a63e6a2da72578b4a986826 Mon Sep 17 00:00:00 2001 From: geoffreygarrett Date: Wed, 8 Jan 2025 17:13:06 +0200 Subject: [PATCH 17/17] Update Avatar to Leptos 0.7 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0499ea01..a77d82e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,7 @@ yew-style = "0.1.4" #radix-leptos-arrow.path = "./packages/primitives/leptos/arrow" #radix-leptos-aspect-ratio.path = "./packages/primitives/leptos/aspect-ratio" #radix-leptos-accessible-icon.path = "./packages/primitives/leptos/accessible-icon" -#radix-leptos-avatar.path = "./packages/primitives/leptos/avatar" +radix-leptos-avatar.path = "./packages/primitives/leptos/avatar" #radix-leptos-checkbox.path = "./packages/primitives/leptos/checkbox" radix-leptos-context.path = "./packages/primitives/leptos/context" #radix-leptos-collection.path = "./packages/primitives/leptos/collection"