♻️ (bootsier): Reorganiza en capas token/class/bs
Sustituye `attrs/` y `classes/` por tres capas diferenciadas: - `token/`: primitivas CSS (BreakPoint, Color, ScaleSize, etc.). - `class/`: tipos compuestos (Border, Background, Margin, etc.). - `bs/*/props.rs`: props tipadas para cada componente de Bootstrap. El patrón de construcción es `suffix()` -> `push_to()` -> `to_class()`. `push_to()` es `pub` en `class/` y `bs/*/props.rs` para que los temas derivados puedan acceder a los mismos mecanismos de construcción.
This commit is contained in:
parent
64db114eb0
commit
f37db56ec5
47 changed files with 1307 additions and 1201 deletions
|
|
@ -1,222 +0,0 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::LOCALES_BOOTSIER;
|
||||
use crate::theme::*;
|
||||
|
||||
/// Componente para crear un **panel lateral deslizante** ([`offcanvas`]).
|
||||
///
|
||||
/// Útil para navegación, filtros, formularios o menús contextuales. Incluye las siguientes
|
||||
/// características principales:
|
||||
///
|
||||
/// - Puede mostrar una capa de fondo para centrar la atención del usuario en el panel
|
||||
/// ([`with_backdrop()`](Self::with_backdrop)); o puede bloquear el desplazamiento del documento
|
||||
/// principal ([`with_body_scroll()`](Self::with_body_scroll)).
|
||||
/// - Se puede configurar el borde de la ventana desde el que se desliza el panel
|
||||
/// ([`with_placement()`](Self::with_placement)).
|
||||
/// - Encabezado con título ([`with_title()`](Self::with_title)) y **botón de cierre** integrado.
|
||||
/// - Puede cambiar su comportamiento a partir de un punto de ruptura
|
||||
/// ([`with_breakpoint()`](Self::with_breakpoint)).
|
||||
/// - Asocia título y controles de accesibilidad a un identificador único y expone atributos
|
||||
/// adecuados para lectores de pantalla y navegación por teclado.
|
||||
///
|
||||
/// Si no contiene elementos, el componente **no se renderiza**.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
/// use pagetop_bootsier::theme::*;
|
||||
///
|
||||
/// let panel = Offcanvas::new()
|
||||
/// .with_id("offcanvas_example")
|
||||
/// .with_title(L10n::n("Offcanvas title"))
|
||||
/// .with_placement(offcanvas::Placement::End)
|
||||
/// .with_backdrop(offcanvas::Backdrop::Enabled)
|
||||
/// .with_body_scroll(offcanvas::BodyScroll::Enabled)
|
||||
/// .with_visibility(offcanvas::Visibility::Default)
|
||||
/// .with_child(Dropdown::new()
|
||||
/// .with_title(L10n::n("Menu"))
|
||||
/// .with_item(dropdown::Item::label(L10n::n("Label")))
|
||||
/// .with_item(dropdown::Item::link_blank(L10n::n("Docs"), |_| "https://docs.rs".into()))
|
||||
/// .with_item(dropdown::Item::link(L10n::n("Sign out"), |_| "/signout".into()))
|
||||
/// );
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Offcanvas {
|
||||
/// Devuelve identificador, clases CSS y atributos HTML del componente.
|
||||
props: Props,
|
||||
/// Devuelve el título del panel.
|
||||
title: L10n,
|
||||
/// Devuelve el punto de ruptura configurado para cambiar el comportamiento del panel.
|
||||
breakpoint: BreakPoint,
|
||||
/// Devuelve el comportamiento configurado para la capa de fondo.
|
||||
backdrop: offcanvas::Backdrop,
|
||||
/// Indica si la página principal puede desplazarse mientras el panel está abierto.
|
||||
body_scroll: offcanvas::BodyScroll,
|
||||
/// Devuelve la posición de inicio del panel.
|
||||
placement: offcanvas::Placement,
|
||||
/// Devuelve el estado inicial del panel.
|
||||
visibility: offcanvas::Visibility,
|
||||
/// Devuelve la lista de componentes (`children`) del panel.
|
||||
children: Children,
|
||||
}
|
||||
|
||||
impl Component for Offcanvas {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
fn setup(&mut self, cx: &Context) {
|
||||
// Asegura que el panel tiene un identificador único.
|
||||
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
||||
|
||||
// Clases CSS por defecto para el panel.
|
||||
self.alter_prop(PropsOp::prepend_classes({
|
||||
let mut classes = "offcanvas".to_string();
|
||||
self.breakpoint().push_class(&mut classes, "offcanvas", "");
|
||||
self.placement().push_class(&mut classes);
|
||||
self.visibility().push_class(&mut classes);
|
||||
classes
|
||||
}));
|
||||
}
|
||||
|
||||
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
Ok(self.render_offcanvas(cx, None))
|
||||
}
|
||||
}
|
||||
|
||||
impl Offcanvas {
|
||||
// **< Offcanvas 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 o atributos HTML del componente.
|
||||
#[builder_fn]
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el título del encabezado.
|
||||
#[builder_fn]
|
||||
pub fn with_title(mut self, title: L10n) -> Self {
|
||||
self.title = title;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el punto de ruptura a partir del cual cambia el comportamiento del panel.
|
||||
///
|
||||
/// - **Por debajo** de ese tamaño de pantalla, el componente actúa como panel deslizante
|
||||
/// ([`Offcanvas`]).
|
||||
/// - **Por encima**, el contenido del panel se muestra tal cual, integrado en la página.
|
||||
///
|
||||
/// Por ejemplo, con `BreakPoint::LG`, será *offcanvas* en móviles y tabletas, y visible
|
||||
/// directamente en pantallas grandes. Por defecto usa `BreakPoint::None` para que sea
|
||||
/// *offcanvas* siempre.
|
||||
#[builder_fn]
|
||||
pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self {
|
||||
self.breakpoint = bp;
|
||||
self
|
||||
}
|
||||
|
||||
/// Ajusta la capa de fondo del panel para definir su comportamiento al hacer clic fuera del
|
||||
/// panel.
|
||||
#[builder_fn]
|
||||
pub fn with_backdrop(mut self, backdrop: offcanvas::Backdrop) -> Self {
|
||||
self.backdrop = backdrop;
|
||||
self
|
||||
}
|
||||
|
||||
/// Permite o bloquea el desplazamiento de la página principal mientras el panel está abierto.
|
||||
#[builder_fn]
|
||||
pub fn with_body_scroll(mut self, scrolling: offcanvas::BodyScroll) -> Self {
|
||||
self.body_scroll = scrolling;
|
||||
self
|
||||
}
|
||||
|
||||
/// Indica desde qué borde de la ventana entra y se ancla el panel.
|
||||
#[builder_fn]
|
||||
pub fn with_placement(mut self, placement: offcanvas::Placement) -> Self {
|
||||
self.placement = placement;
|
||||
self
|
||||
}
|
||||
|
||||
/// Fija el estado inicial del panel (oculto o visible al cargar).
|
||||
#[builder_fn]
|
||||
pub fn with_visibility(mut self, visibility: offcanvas::Visibility) -> Self {
|
||||
self.visibility = visibility;
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un nuevo componente al panel o modifica la lista de componentes (`children`) con una
|
||||
/// operación [`ChildOp`].
|
||||
#[builder_fn]
|
||||
pub fn with_child(mut self, op: impl Into<ChildOp>) -> Self {
|
||||
self.children.alter_child(op.into());
|
||||
self
|
||||
}
|
||||
|
||||
// **< Offcanvas HELPERS >**********************************************************************
|
||||
|
||||
pub(crate) fn render_offcanvas(&self, cx: &mut Context, extra: Option<&Children>) -> Markup {
|
||||
let body = self.children().render(cx);
|
||||
let body_extra = extra.map(|c| c.render(cx)).unwrap_or_else(|| html! {});
|
||||
if body.is_empty() && body_extra.is_empty() {
|
||||
return html! {};
|
||||
}
|
||||
|
||||
// `setup()` garantiza que habrá un `id` antes de renderizar.
|
||||
let id = self.id().unwrap();
|
||||
let id_label = util::join!(id, "-label");
|
||||
let id_target = util::join!("#", id);
|
||||
|
||||
let body_scroll = match self.body_scroll() {
|
||||
offcanvas::BodyScroll::Disabled => None,
|
||||
offcanvas::BodyScroll::Enabled => Some("true"),
|
||||
};
|
||||
|
||||
let backdrop = match self.backdrop() {
|
||||
offcanvas::Backdrop::Disabled => Some("false"),
|
||||
offcanvas::Backdrop::Enabled => None,
|
||||
offcanvas::Backdrop::Static => Some("static"),
|
||||
};
|
||||
|
||||
let title = self.title().using(cx);
|
||||
|
||||
html! {
|
||||
div
|
||||
(self.props())
|
||||
tabindex="-1"
|
||||
data-bs-scroll=[body_scroll]
|
||||
data-bs-backdrop=[backdrop]
|
||||
aria-labelledby=(id_label)
|
||||
{
|
||||
div class="offcanvas-header" {
|
||||
@if !title.is_empty() {
|
||||
h5 id=(&id_label) class="offcanvas-title" { (title) }
|
||||
}
|
||||
button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="offcanvas"
|
||||
data-bs-target=(id_target)
|
||||
aria-label=[L10n::t("offcanvas_close", &LOCALES_BOOTSIER).lookup(cx)]
|
||||
{}
|
||||
}
|
||||
div class="offcanvas-body" {
|
||||
(body)
|
||||
(body_extra)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue