feat: add active_class prop on <A/> (#1323)

This commit is contained in:
Greg Johnston 2023-07-12 16:21:07 -04:00 committed by GitHub
parent d4b1ceda90
commit e9665b34e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 94 additions and 16 deletions

View file

@ -289,4 +289,4 @@ fn None(cx: Scope) -> impl IntoView {
</div>
</div>
}
}
}

View file

@ -1,5 +1,6 @@
use crate::{use_location, use_resolved_path, State};
use leptos::{leptos_dom::IntoView, *};
use std::borrow::Cow;
/// Describes a value that is either a static or a reactive URL, i.e.,
/// a [String], a [&str], or a reactive `Fn() -> String`.
@ -56,6 +57,16 @@ pub fn A<H>(
/// if false, link is marked active if the current route starts with it.
#[prop(optional)]
exact: bool,
/// Provides a class to be added when the link is active. If provided, it will
/// be added at the same time that the `aria-current` attribute is set.
///
/// This supports multiple space-separated class names.
///
/// **Performance**: If its possible to style the link using the CSS with the
/// `[aria-current=page]` selector, you should prefer that, as it enables significant
/// SSR optimizations.
#[prop(optional, into)]
active_class: Option<Cow<'static, str>>,
/// An object of any type that will be pushed to router state
#[prop(optional)]
state: Option<State>,
@ -83,12 +94,13 @@ where
cx: Scope,
href: Memo<Option<String>>,
exact: bool,
state: Option<State>,
replace: bool,
#[allow(unused)] state: Option<State>,
#[allow(unused)] replace: bool,
class: Option<AttributeValue>,
#[allow(unused)] active_class: Option<Cow<'static, str>>,
id: Option<String>,
children: Children,
) -> HtmlElement<leptos::html::A> {
) -> View {
#[cfg(not(any(feature = "hydrate", feature = "csr")))]
{
_ = state;
@ -118,20 +130,86 @@ where
}
});
view! { cx,
<a
href=move || href.get().unwrap_or_default()
prop:state={state.map(|s| s.to_js_value())}
prop:replace={replace}
aria-current=move || if is_active.get() { Some("page") } else { None }
class=class
id=id
>
{children(cx)}
</a>
#[cfg(feature = "ssr")]
{
// if we have `active_class`, the SSR optimization doesn't play nicely
// so we use the builder instead
if let Some(active_class) = active_class {
let mut a = leptos::html::a(cx)
.attr("href", move || href.get().unwrap_or_default())
.attr("aria-current", move || {
if is_active.get() {
Some("page")
} else {
None
}
})
.attr(
"class",
class.map(|class| class.into_attribute_boxed(cx)),
);
for class_name in active_class.split_ascii_whitespace() {
a = a.class(class_name.to_string(), move || is_active.get())
}
a.attr("id", id).child(children(cx)).into_view(cx)
}
// but keep the nice SSR optimization in most cases
else {
view! { cx,
<a
href=move || href.get().unwrap_or_default()
aria-current=move || if is_active.get() { Some("page") } else { None }
class=class
id=id
>
{children(cx)}
</a>
}
.into_view(cx)
}
}
// the non-SSR version doesn't need the SSR optimizations
// DRY here to avoid WASM binary size bloat
#[cfg(not(feature = "ssr"))]
{
let a = view! { cx,
<a
href=move || href.get().unwrap_or_default()
prop:state={state.map(|s| s.to_js_value())}
prop:replace={replace}
aria-current=move || if is_active.get() { Some("page") } else { None }
class=class
id=id
>
{children(cx)}
</a>
};
if let Some(active_class) = active_class {
let mut a = a;
for class_name in active_class.split_ascii_whitespace() {
a = a.class(class_name.to_string(), move || is_active.get())
}
a
} else {
a
}
.into_view(cx)
}
}
let href = use_resolved_path(cx, move || href.to_href()());
inner(cx, href, exact, state, replace, class, id, children)
inner(
cx,
href,
exact,
state,
replace,
class,
active_class,
id,
children,
)
}