use crate::{use_location, use_resolved_path, State};
use leptos::{leptos_dom::IntoView, *};
use std::borrow::Cow;
pub trait ToHref {
fn to_href(&self) -> Box<dyn Fn() -> String + '_>;
}
impl ToHref for &str {
fn to_href(&self) -> Box<dyn Fn() -> String> {
let s = self.to_string();
Box::new(move || s.clone())
}
}
impl ToHref for String {
fn to_href(&self) -> Box<dyn Fn() -> String> {
let s = self.clone();
Box::new(move || s.clone())
}
}
impl<F> ToHref for F
where
F: Fn() -> String + 'static,
{
fn to_href(&self) -> Box<dyn Fn() -> String + '_> {
Box::new(self)
}
}
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "info", skip_all,)
)]
#[component]
pub fn A<H>(
cx: Scope,
href: H,
#[prop(optional)]
exact: bool,
#[prop(optional, into)]
active_class: Option<Cow<'static, str>>,
#[prop(optional)]
state: Option<State>,
#[prop(optional)]
replace: bool,
#[prop(optional, into)]
class: Option<AttributeValue>,
#[prop(optional, into)]
id: Option<String>,
children: Children,
) -> impl IntoView
where
H: ToHref + 'static,
{
#[cfg_attr(
any(debug_assertions, feature = "ssr"),
tracing::instrument(level = "trace", skip_all,)
)]
fn inner(
cx: Scope,
href: Memo<Option<String>>,
exact: 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,
) -> View {
#[cfg(not(any(feature = "hydrate", feature = "csr")))]
{
_ = state;
}
#[cfg(not(any(feature = "hydrate", feature = "csr")))]
{
_ = replace;
}
let location = use_location(cx);
let is_active = create_memo(cx, move |_| match href.get() {
None => false,
Some(to) => {
let path = to
.split(['?', '#'])
.next()
.unwrap_or_default()
.to_lowercase();
let loc = location.pathname.get().to_lowercase();
if exact {
loc == path
} else {
loc.starts_with(&path)
}
}
});
#[cfg(feature = "ssr")]
{
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)
}
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)
}
}
#[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,
active_class,
id,
children,
)
}