✨ (base): Añade componente Breadcrumb
This commit is contained in:
parent
20b6c77c9e
commit
6bea4a5793
7 changed files with 247 additions and 0 deletions
|
|
@ -276,6 +276,38 @@ input:disabled + label {
|
|||
background-color: color-mix(in srgb, var(--val-color--primary) 85%, black);
|
||||
}
|
||||
|
||||
/*
|
||||
* Breadcrumb component
|
||||
*/
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
list-style: none;
|
||||
margin: 0 0 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.breadcrumb-item + .breadcrumb-item {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
.breadcrumb-item + .breadcrumb-item::before {
|
||||
float: left;
|
||||
padding-right: 0.5rem;
|
||||
color: var(--val-color--text--muted);
|
||||
content: "/";
|
||||
}
|
||||
.breadcrumb-item.active {
|
||||
color: var(--val-color--text--muted);
|
||||
}
|
||||
.breadcrumb-item a {
|
||||
color: var(--val-color--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
.breadcrumb-item a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/*
|
||||
* Region Footer
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ pub mod layout;
|
|||
mod badge;
|
||||
pub use badge::Badge;
|
||||
|
||||
pub mod breadcrumb;
|
||||
#[doc(inline)]
|
||||
pub use breadcrumb::Breadcrumb;
|
||||
|
||||
mod block;
|
||||
pub use block::Block;
|
||||
|
||||
|
|
|
|||
7
src/base/component/breadcrumb.rs
Normal file
7
src/base/component/breadcrumb.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
//! Definiciones para mostrar rutas de navegación ([`Breadcrumb`]).
|
||||
|
||||
mod component;
|
||||
pub use component::Breadcrumb;
|
||||
|
||||
mod crumb;
|
||||
pub use crumb::Crumb;
|
||||
93
src/base/component/breadcrumb/component.rs
Normal file
93
src/base/component/breadcrumb/component.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
/// Componente para representar una ruta de navegación (*breadcrumb*).
|
||||
///
|
||||
/// Renderiza la estructura HTML de cualquier breadcrumb encapsulando en un elemento `<nav>` una
|
||||
/// lista con un [`breadcrumb::Crumb`] por cada nivel de la ruta de navegación. Deja en manos de
|
||||
/// quien lo use la construcción de esos niveles. No sabe nada del origen de los datos, ni de menús,
|
||||
/// ni de ningún otro esquema de navegación concreto; cada extensión resuelve sus propios datos (por
|
||||
/// ejemplo, el *active trail* de un menú) y construye los [`breadcrumb::Crumb`] correspondientes.
|
||||
///
|
||||
/// Si no contiene ningún elemento, el componente **no se renderiza**.
|
||||
///
|
||||
/// # Clases CSS
|
||||
///
|
||||
/// - `.breadcrumb`: clase de la lista que contiene los niveles.
|
||||
/// - `.breadcrumb-item`: presente en todos los elementos de la lista.
|
||||
/// - `.active`: añadida al elemento actual (ver [`breadcrumb::Crumb::current()`]).
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let bc = Breadcrumb::new()
|
||||
/// .with_crumb(breadcrumb::Crumb::new(L10n::n("Home"), "/"))
|
||||
/// .with_crumb(breadcrumb::Crumb::new(L10n::n("Users"), "/admin/users"))
|
||||
/// .with_crumb(breadcrumb::Crumb::current(L10n::n("Julia")));
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Breadcrumb {
|
||||
/// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente.
|
||||
props: Props,
|
||||
/// Devuelve la lista de elementos del breadcrumb, en orden de aparición.
|
||||
crumbs: Vec<breadcrumb::Crumb>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Component for Breadcrumb {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, cx: &Context) {
|
||||
for crumb in self.crumbs.iter_mut() {
|
||||
crumb.setup(cx);
|
||||
}
|
||||
}
|
||||
|
||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
if self.crumbs().is_empty() {
|
||||
return Ok(html! {});
|
||||
}
|
||||
|
||||
Ok(html! {
|
||||
nav (self.props()) aria-label=[L10n::l("breadcrumb_label").lookup(cx)] {
|
||||
ol.breadcrumb {
|
||||
@for crumb in self.crumbs() {
|
||||
(crumb.render_crumb(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Breadcrumb {
|
||||
// **< Breadcrumb BUILDER >*********************************************************************
|
||||
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica identificador, clases CSS, atributos HTML o valores extra del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un nuevo elemento al final del breadcrumb.
|
||||
#[builder_fn]
|
||||
pub fn with_crumb(mut self, crumb: breadcrumb::Crumb) -> Self {
|
||||
self.crumbs.push(crumb);
|
||||
self
|
||||
}
|
||||
}
|
||||
105
src/base/component/breadcrumb/crumb.rs
Normal file
105
src/base/component/breadcrumb/crumb.rs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
/// Representa un elemento de un [`Breadcrumb`](super::Breadcrumb).
|
||||
///
|
||||
/// Hay tres formas de crear un `Crumb`, según el papel que ocupe en la lista:
|
||||
///
|
||||
/// - [`Crumb::new()`]: un elemento enlazado, con destino propio.
|
||||
/// - [`Crumb::current()`]: el elemento final, sin enlace, que representa la página actual.
|
||||
/// - [`Crumb::text()`]: texto plano sin enlace y sin marcar como página actual, por ejemplo un
|
||||
/// nivel intermedio sin URL propia.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let bc = Breadcrumb::new()
|
||||
/// .with_crumb(breadcrumb::Crumb::new(L10n::n("Home"), "/"))
|
||||
/// .with_crumb(breadcrumb::Crumb::text(L10n::n("Users")))
|
||||
/// .with_crumb(breadcrumb::Crumb::current(L10n::n("Julia")));
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Crumb {
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del elemento.
|
||||
props: Props,
|
||||
/// Devuelve la etiqueta del elemento.
|
||||
label: L10n,
|
||||
/// Devuelve la ruta de destino del elemento, si es un enlace.
|
||||
route: Option<Route>,
|
||||
/// Devuelve si el elemento representa la página actual.
|
||||
is_current: bool,
|
||||
}
|
||||
|
||||
impl Crumb {
|
||||
/// Crea un elemento enlazado a la ruta indicada.
|
||||
pub fn new(label: L10n, route: impl Into<Route>) -> Self {
|
||||
Self {
|
||||
label,
|
||||
route: Some(route.into()),
|
||||
is_current: false,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea el elemento final de la lista, sin enlace, que representa la página actual.
|
||||
///
|
||||
/// Al renderizarse dentro de un `Breadcrumb`, el elemento lleva `aria-current="page"` y la
|
||||
/// clase `.active`.
|
||||
pub fn current(label: L10n) -> Self {
|
||||
Self {
|
||||
label,
|
||||
is_current: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un elemento de sólo texto, sin enlace ni marca de página actual (por ejemplo, un nivel
|
||||
/// intermedio sin URL propia).
|
||||
pub fn text(label: L10n) -> Self {
|
||||
Self {
|
||||
label,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// **< Crumb BUILDER >**************************************************************************
|
||||
|
||||
/// Establece el identificador único del elemento.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica identificador, clases CSS o atributos HTML del elemento.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
self
|
||||
}
|
||||
|
||||
// Normaliza la clase base según el papel del elemento. Sólo lo usa `Breadcrumb`.
|
||||
pub(super) fn setup(&mut self, _cx: &Context) {
|
||||
if *self.is_current() {
|
||||
self.alter_prop(PropsOp::prepend_classes("active"))
|
||||
.alter_prop(PropsOp::set("aria-current", "page"));
|
||||
}
|
||||
self.alter_prop(PropsOp::prepend_classes("breadcrumb-item"));
|
||||
}
|
||||
|
||||
// Renderiza con enlace si tiene ruta, o texto plano en otro caso. Sólo lo usa `Breadcrumb`.
|
||||
pub(super) fn render_crumb(&self, cx: &Context) -> Markup {
|
||||
let label = self.label().using(cx);
|
||||
match self.route() {
|
||||
Some(route) => html! {
|
||||
li (self.props()) {
|
||||
a href=(route.resolve(cx).to_string()) { (label) }
|
||||
}
|
||||
},
|
||||
None => html! {
|
||||
li (self.props()) { (label) }
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,9 @@ intro_have_fun = Coding is creating
|
|||
# PoweredBy component.
|
||||
poweredby_pagetop = Powered by { $pagetop_link }
|
||||
|
||||
# Breadcrumb component.
|
||||
breadcrumb_label = Breadcrumb navigation
|
||||
|
||||
# Pager component.
|
||||
pager_aria_label = Page navigation
|
||||
pager_previous_label = Previous
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ intro_have_fun = Programar es crear
|
|||
# PoweredBy component.
|
||||
poweredby_pagetop = Funciona con { $pagetop_link }
|
||||
|
||||
# Breadcrumb component.
|
||||
breadcrumb_label = Ruta de navegación
|
||||
|
||||
# Pager component.
|
||||
pager_aria_label = Navegación de páginas
|
||||
pager_previous_label = Anterior
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue