Better styling for router related components (#477)
This commit is contained in:
parent
6931d3904b
commit
b0a98d8b4f
|
@ -176,3 +176,25 @@ pub type ChildrenFn = Box<dyn Fn(Scope) -> Fragment>;
|
|||
/// A type for the `children` property on components that can be called
|
||||
/// more than once, but may mutate the children.
|
||||
pub type ChildrenFnMut = Box<dyn FnMut(Scope) -> Fragment>;
|
||||
|
||||
/// A type for taking anything that implements [`IntoAttribute`].
|
||||
/// Very usefull inside components.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust
|
||||
/// use leptos::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// pub fn MyHeading(
|
||||
/// cx: Scope,
|
||||
/// text: String,
|
||||
/// #[prop(optional, into)]
|
||||
/// class: Option<AttributeValue>
|
||||
/// ) -> impl IntoView {
|
||||
/// view!{
|
||||
/// cx,
|
||||
/// <h1 class=class>{text}</h1>
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub type AttributeValue = Box<dyn IntoAttribute>;
|
||||
|
|
|
@ -102,24 +102,67 @@ impl std::fmt::Debug for Attribute {
|
|||
pub trait IntoAttribute {
|
||||
/// Converts the object into an [Attribute].
|
||||
fn into_attribute(self, cx: Scope) -> Attribute;
|
||||
/// Helper function for dealing with [Box<dyn IntoAttribute>]
|
||||
fn into_attribute_boxed(self: Box<Self>, cx: Scope) -> Attribute;
|
||||
}
|
||||
|
||||
impl<T: IntoAttribute + 'static> From<T> for Box<dyn IntoAttribute> {
|
||||
fn from(value: T) -> Self {
|
||||
Box::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Attribute {
|
||||
#[inline]
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_attribute_boxed(self: Box<Self>, _: Scope) -> Attribute {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_attr_boxed {
|
||||
() => {
|
||||
#[inline]
|
||||
fn into_attribute_boxed(self: Box<Self>, cx: Scope) -> Attribute {
|
||||
self.into_attribute(cx)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<Attribute> {
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
self.unwrap_or(Attribute::Option(cx, None))
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for String {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::String(self)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for bool {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::Bool(self)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<String> {
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
Attribute::Option(cx, self)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl<T, U> IntoAttribute for T
|
||||
|
@ -131,12 +174,35 @@ where
|
|||
let modified_fn = Rc::new(move || (self)().into_attribute(cx));
|
||||
Attribute::Fn(cx, modified_fn)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl<T: IntoAttribute> IntoAttribute for (Scope, T) {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
self.1.into_attribute(self.0)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for (Scope, Option<Box<dyn IntoAttribute>>) {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
match self.1 {
|
||||
Some(bx) => bx.into_attribute_boxed(self.0),
|
||||
None => Attribute::Option(self.0, None),
|
||||
}
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
impl IntoAttribute for (Scope, Box<dyn IntoAttribute>) {
|
||||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
self.1.into_attribute_boxed(self.0)
|
||||
}
|
||||
|
||||
impl_into_attr_boxed! {}
|
||||
}
|
||||
|
||||
macro_rules! attr_type {
|
||||
|
@ -145,12 +211,22 @@ macro_rules! attr_type {
|
|||
fn into_attribute(self, _: Scope) -> Attribute {
|
||||
Attribute::String(self.to_string())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_attribute_boxed(self: Box<Self>, cx: Scope) -> Attribute {
|
||||
self.into_attribute(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoAttribute for Option<$attr_type> {
|
||||
fn into_attribute(self, cx: Scope) -> Attribute {
|
||||
Attribute::Option(cx, self.map(|n| n.to_string()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn into_attribute_boxed(self: Box<Self>, cx: Scope) -> Attribute {
|
||||
self.into_attribute(cx)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -32,6 +32,9 @@ pub fn Form<A>(
|
|||
#[prop(optional)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
on_form_data: Option<Rc<dyn Fn(&web_sys::FormData)>>,
|
||||
/// Sets the `class` attribute on the underlying `<form>` tag, making it easier to style.
|
||||
#[prop(optional, into)]
|
||||
class: Option<AttributeValue>,
|
||||
/// A callback will be called with the [Response](web_sys::Response) the server sends in response
|
||||
/// to a form submission.
|
||||
#[prop(optional)]
|
||||
|
@ -52,6 +55,7 @@ where
|
|||
error: Option<RwSignal<Option<Box<dyn Error>>>>,
|
||||
#[allow(clippy::type_complexity)] on_form_data: Option<Rc<dyn Fn(&web_sys::FormData)>>,
|
||||
#[allow(clippy::type_complexity)] on_response: Option<Rc<dyn Fn(&web_sys::Response)>>,
|
||||
class: Option<Attribute>,
|
||||
children: Children,
|
||||
) -> HtmlElement<Form> {
|
||||
let action_version = version;
|
||||
|
@ -128,6 +132,7 @@ where
|
|||
action=move || action.get()
|
||||
enctype=enctype
|
||||
on:submit=on_submit
|
||||
class=class
|
||||
>
|
||||
{children(cx)}
|
||||
</form>
|
||||
|
@ -135,6 +140,7 @@ where
|
|||
}
|
||||
|
||||
let action = use_resolved_path(cx, move || action.to_href()());
|
||||
let class = class.map(|bx| bx.into_attribute_boxed(cx));
|
||||
inner(
|
||||
cx,
|
||||
method,
|
||||
|
@ -144,6 +150,7 @@ where
|
|||
error,
|
||||
on_form_data,
|
||||
on_response,
|
||||
class,
|
||||
children,
|
||||
)
|
||||
}
|
||||
|
@ -158,6 +165,9 @@ pub fn ActionForm<I, O>(
|
|||
/// by default using [create_server_action](leptos_server::create_server_action) or added
|
||||
/// manually using [leptos_server::Action::using_server_fn].
|
||||
action: Action<I, Result<O, ServerFnError>>,
|
||||
/// Sets the `class` attribute on the underlying `<form>` tag, making it easier to style.
|
||||
#[prop(optional, into)]
|
||||
class: Option<AttributeValue>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
|
@ -208,7 +218,7 @@ where
|
|||
action.set_pending(false);
|
||||
});
|
||||
});
|
||||
|
||||
let class = class.map(|bx| bx.into_attribute_boxed(cx));
|
||||
Form(
|
||||
cx,
|
||||
FormProps::builder()
|
||||
|
@ -217,6 +227,7 @@ where
|
|||
.on_form_data(on_form_data)
|
||||
.on_response(on_response)
|
||||
.method("post")
|
||||
.class(class)
|
||||
.children(children)
|
||||
.build(),
|
||||
)
|
||||
|
@ -232,6 +243,9 @@ pub fn MultiActionForm<I, O>(
|
|||
/// by default using [create_server_action](leptos_server::create_server_action) or added
|
||||
/// manually using [leptos_server::Action::using_server_fn].
|
||||
action: MultiAction<I, Result<O, ServerFnError>>,
|
||||
/// Sets the `class` attribute on the underlying `<form>` tag, making it easier to style.
|
||||
#[prop(optional, into)]
|
||||
class: Option<AttributeValue>,
|
||||
/// Component children; should include the HTML of the form elements.
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
|
@ -265,10 +279,12 @@ where
|
|||
}
|
||||
};
|
||||
|
||||
let class = class.map(|bx| bx.into_attribute_boxed(cx));
|
||||
view! { cx,
|
||||
<form
|
||||
method="POST"
|
||||
action=action
|
||||
class=class
|
||||
on:submit=on_submit
|
||||
>
|
||||
{children(cx)}
|
||||
|
|
|
@ -63,7 +63,7 @@ pub fn A<H>(
|
|||
replace: bool,
|
||||
/// Sets the `class` attribute on the underlying `<a>` tag, making it easier to style.
|
||||
#[prop(optional, into)]
|
||||
class: Option<MaybeSignal<String>>,
|
||||
class: Option<AttributeValue>,
|
||||
/// The nodes or elements to be shown inside the link.
|
||||
children: Children,
|
||||
) -> impl IntoView
|
||||
|
@ -76,7 +76,7 @@ where
|
|||
exact: bool,
|
||||
state: Option<State>,
|
||||
replace: bool,
|
||||
class: Option<MaybeSignal<String>>,
|
||||
class: Option<AttributeValue>,
|
||||
children: Children,
|
||||
) -> HtmlElement<A> {
|
||||
let location = use_location(cx);
|
||||
|
@ -104,7 +104,7 @@ where
|
|||
prop:state={state.map(|s| s.to_js_value())}
|
||||
prop:replace={replace}
|
||||
aria-current=move || if is_active.get() { Some("page") } else { None }
|
||||
class=move || class.as_ref().map(|class| class.get())
|
||||
class=class
|
||||
>
|
||||
{children(cx)}
|
||||
</a>
|
||||
|
|
|
@ -45,8 +45,10 @@
|
|||
//! // LR will enhance the active <a> link with the [aria-current] attribute
|
||||
//! // we can use this for styling them with CSS like `[aria-current] { font-weight: bold; }`
|
||||
//! <A href="contacts">"Contacts"</A>
|
||||
//! <A href="about">"About"</A>
|
||||
//! <A href="settings">"Settings"</A>
|
||||
//! // But we can also use a normal class attribute like it is a normal component
|
||||
//! <A href="settings" class="my-class">"Settings"</A>
|
||||
//! // It also supports signals!
|
||||
//! <A href="about" class=move || "my-class">"About"</A>
|
||||
//! </nav>
|
||||
//! <main>
|
||||
//! // <Routes/> both defines our routes and shows them on the page
|
||||
|
|
Loading…
Reference in New Issue