✨ [bootsier] Añade más componentes y repasa código
- Se incorpora nuevo componente Dropdown. - Se crea un componente Navbar con soporte para marca, elementos de navegación. - Se implementa el componente Offcanvas con opciones de posición, visibilidad y fondo personalizables. - Mejora el manejo de imágenes con un nuevo componente de Image. - Se reorganizan los componentes del tema para una mejor estructura y usabilidad.
This commit is contained in:
parent
cf40af13bb
commit
82837c622e
29 changed files with 2608 additions and 9 deletions
18
extensions/pagetop-bootsier/src/theme/aux.rs
Normal file
18
extensions/pagetop-bootsier/src/theme/aux.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
//! Coleción de elementos auxiliares de Bootstrap para Bootsier.
|
||||
|
||||
mod breakpoint;
|
||||
pub use breakpoint::BreakPoint;
|
||||
|
||||
mod color;
|
||||
pub use color::Color;
|
||||
pub use color::{BgColor, BorderColor, TextColor};
|
||||
|
||||
mod opacity;
|
||||
pub use opacity::Opacity;
|
||||
pub use opacity::{BgOpacity, BorderOpacity, TextOpacity};
|
||||
|
||||
mod border;
|
||||
pub use border::{Border, BorderSize};
|
||||
|
||||
mod rounded;
|
||||
pub use rounded::{Rounded, RoundedRadius};
|
||||
202
extensions/pagetop-bootsier/src/theme/aux/border.rs
Normal file
202
extensions/pagetop-bootsier/src/theme/aux/border.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// **< BorderSize >*********************************************************************************
|
||||
|
||||
/// Tamaño (**ancho**) para los bordes ([`Border`]).
|
||||
///
|
||||
/// Mapea a `border`, `border-0` y `border-{1..5}`:
|
||||
///
|
||||
/// - `None` no añade clase (devuelve `""`).
|
||||
/// - `Default` genera `border` (borde por defecto del tema).
|
||||
/// - `Zero` genera `border-0` (sin borde).
|
||||
/// - `Scale{1..5}` genera `border-{1..5}` (ancho creciente).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BorderSize {
|
||||
#[default]
|
||||
None,
|
||||
Default,
|
||||
Zero,
|
||||
Scale1,
|
||||
Scale2,
|
||||
Scale3,
|
||||
Scale4,
|
||||
Scale5,
|
||||
}
|
||||
|
||||
impl BorderSize {
|
||||
#[rustfmt::skip]
|
||||
fn to_class(&self, prefix: impl AsRef<str>) -> String {
|
||||
match self {
|
||||
Self::None => String::new(),
|
||||
Self::Default => String::from(prefix.as_ref()),
|
||||
Self::Zero => join!(prefix, "-0"),
|
||||
Self::Scale1 => join!(prefix, "-1"),
|
||||
Self::Scale2 => join!(prefix, "-2"),
|
||||
Self::Scale3 => join!(prefix, "-3"),
|
||||
Self::Scale4 => join!(prefix, "-4"),
|
||||
Self::Scale5 => join!(prefix, "-5"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BorderSize {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.to_class("border"))
|
||||
}
|
||||
}
|
||||
|
||||
// **< Border >*************************************************************************************
|
||||
|
||||
/// Agrupa propiedades para crear **bordes**.
|
||||
///
|
||||
/// Permite:
|
||||
///
|
||||
/// - Definir un tamaño **global** para todo el borde (`size`).
|
||||
/// - Ajustar el tamaño de cada **lado lógico** (`top`, `end`, `bottom`, `start`, **en este orden**,
|
||||
/// respetando LTR/RTL).
|
||||
/// - Aplicar un **color** al borde (`BorderColor`).
|
||||
/// - Aplicar un nivel de **opacidad** (`BorderOpacity`).
|
||||
///
|
||||
/// # Comportamiento aditivo / sustractivo
|
||||
///
|
||||
/// - **Aditivo**: basta con crear un borde sin tamaño con `Border::new()` para ir añadiendo cada
|
||||
/// lado lógico con el tamaño deseado usando `BorderSize::Scale{1..5}`.
|
||||
///
|
||||
/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. utilizando
|
||||
/// `Border::with(BorderSize::Scale2)` y eliminar los lados deseados con `BorderSize::Zero`.
|
||||
///
|
||||
/// - **Anchos diferentes por lado**: usando `BorderSize::Scale{1..5}` en cada lado deseado.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// **Borde global:**
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// let b = Border::with(BorderSize::Scale2);
|
||||
/// assert_eq!(b.to_string(), "border-2");
|
||||
/// ```
|
||||
///
|
||||
/// **Aditivo (solo borde superior):**
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// let b = Border::new().with_top(BorderSize::Scale1);
|
||||
/// assert_eq!(b.to_string(), "border-top-1");
|
||||
/// ```
|
||||
///
|
||||
/// **Sustractivo (borde global menos el superior):**
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// let b = Border::with(BorderSize::Default).with_top(BorderSize::Zero);
|
||||
/// assert_eq!(b.to_string(), "border border-top-0");
|
||||
/// ```
|
||||
///
|
||||
/// **Ancho por lado (lado lógico inicial a 2 y final a 4):**
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// let b = Border::new().with_start(BorderSize::Scale2).with_end(BorderSize::Scale4);
|
||||
/// assert_eq!(b.to_string(), "border-end-4 border-start-2");
|
||||
/// ```
|
||||
///
|
||||
/// **Combinado (ejemplo completo):**
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// let b = Border::with(BorderSize::Default) // Borde global por defecto.
|
||||
/// .with_top(BorderSize::Zero) // Quita borde superior.
|
||||
/// .with_end(BorderSize::Scale3) // Ancho 3 para el lado lógico final.
|
||||
/// .with_color(BorderColor::Theme(Color::Primary))
|
||||
/// .with_opacity(BorderOpacity::Theme(Opacity::Half));
|
||||
///
|
||||
/// assert_eq!(b.to_string(), "border border-top-0 border-end-3 border-primary border-opacity-50");
|
||||
/// ```
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Border {
|
||||
size : BorderSize,
|
||||
top : BorderSize,
|
||||
end : BorderSize,
|
||||
bottom : BorderSize,
|
||||
start : BorderSize,
|
||||
color : BorderColor,
|
||||
opacity: BorderOpacity,
|
||||
}
|
||||
|
||||
impl Border {
|
||||
/// Prepara un borde **sin tamaño global** de partida.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Crea un borde **con tamaño global** (`size`).
|
||||
pub fn with(size: BorderSize) -> Self {
|
||||
Self::default().with_size(size)
|
||||
}
|
||||
|
||||
// **< Border BUILDER >*************************************************************************
|
||||
|
||||
/// Establece el tamaño global del borde (`border*`).
|
||||
pub fn with_size(mut self, size: BorderSize) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el tamaño del borde superior (`border-top-*`).
|
||||
pub fn with_top(mut self, size: BorderSize) -> Self {
|
||||
self.top = size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el tamaño del borde en el lado lógico final (`border-end-*`). Respeta LTR/RTL.
|
||||
pub fn with_end(mut self, size: BorderSize) -> Self {
|
||||
self.end = size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el tamaño del borde inferior (`border-bottom-*`).
|
||||
pub fn with_bottom(mut self, size: BorderSize) -> Self {
|
||||
self.bottom = size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el tamaño del borde en el lado lógico inicial (`border-start-*`). Respeta LTR/RTL.
|
||||
pub fn with_start(mut self, size: BorderSize) -> Self {
|
||||
self.start = size;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el **color** del borde.
|
||||
pub fn with_color(mut self, color: BorderColor) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la **opacidad** del borde.
|
||||
pub fn with_opacity(mut self, opacity: BorderOpacity) -> Self {
|
||||
self.opacity = opacity;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Border {
|
||||
/// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start`, *color* y
|
||||
/// *opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías.
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
join_opt!([
|
||||
self.size.to_string(),
|
||||
self.top.to_class("border-top"),
|
||||
self.end.to_class("border-end"),
|
||||
self.bottom.to_class("border-bottom"),
|
||||
self.start.to_class("border-start"),
|
||||
self.color.to_string(),
|
||||
self.opacity.to_string(),
|
||||
]; " ")
|
||||
.unwrap_or_default()
|
||||
)
|
||||
}
|
||||
}
|
||||
127
extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs
Normal file
127
extensions/pagetop-bootsier/src/theme/aux/breakpoint.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
/// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*.
|
||||
///
|
||||
/// - `"sm"`, `"md"`, `"lg"`, `"xl"` o `"xxl"` para los puntos de ruptura `SM`, `MD`, `LG`, `XL` o
|
||||
/// `XXL`, respectivamente.
|
||||
/// - `""` (cadena vacía) para `None`.
|
||||
/// - `"fluid"` para las variantes `Fluid` y `FluidMax(_)`, útil para modelar `container-fluid` en
|
||||
/// [`Container`](crate::theme::Container). Se debe tener en cuenta que `"fluid"` **no** es un
|
||||
/// sufijo de *breakpoint*. Para construir clases válidas con prefijo (p. ej., `"col-md"`), se
|
||||
/// recomienda usar `to_class()` (Self::to_class) o `try_class()` (Self::try_class).
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// assert_eq!(BreakPoint::MD.to_string(), "md");
|
||||
/// assert_eq!(BreakPoint::None.to_string(), "");
|
||||
/// assert_eq!(BreakPoint::Fluid.to_string(), "fluid");
|
||||
///
|
||||
/// // Forma correcta para clases con prefijo:
|
||||
/// //assert_eq!(BreakPoint::MD.to_class("col"), "col-md");
|
||||
/// //assert_eq!(BreakPoint::Fluid.to_class("offcanvas"), "offcanvas");
|
||||
/// ```
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BreakPoint {
|
||||
/// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical.
|
||||
#[default]
|
||||
None,
|
||||
/// **576px o más** - Dispositivos pequeños: teléfonos en modo horizontal.
|
||||
SM,
|
||||
/// **768px o más** - Dispositivos medianos: tabletas.
|
||||
MD,
|
||||
/// **992px o más** - Dispositivos grandes: puestos de escritorio.
|
||||
LG,
|
||||
/// **1200px o más** - Dispositivos muy grandes: puestos de escritorio grandes.
|
||||
XL,
|
||||
/// **1400px o más** - Dispositivos extragrandes: puestos de escritorio más grandes.
|
||||
XXL,
|
||||
/// Para [`Container`](crate::theme::Container), ocupa el 100% del ancho del dispositivo.
|
||||
Fluid,
|
||||
/// Para [`Container`](crate::theme::Container), ocupa el 100% del ancho del dispositivo hasta
|
||||
/// un ancho máximo indicado.
|
||||
FluidMax(UnitValue)
|
||||
}
|
||||
|
||||
impl BreakPoint {
|
||||
/// Comprueba si es un punto de ruptura efectivo.
|
||||
///
|
||||
/// Devuelve `true` si el valor es `SM`, `MD`, `LG`, `XL` o `XXL`; y `false` en otro caso.
|
||||
#[inline]
|
||||
pub const fn is_breakpoint(&self) -> bool {
|
||||
matches!(self, Self::SM | Self::MD | Self::LG | Self::XL | Self::XXL)
|
||||
}
|
||||
|
||||
/// Genera un nombre de clase CSS basado en el punto de ruptura.
|
||||
///
|
||||
/// Si es un punto de ruptura efectivo (ver [`is_breakpoint()`](Self::is_breakpoint), concatena
|
||||
/// el prefijo, un guion (`-`) y el sufijo asociado. En otro caso devuelve únicamente el
|
||||
/// prefijo.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust
|
||||
/// let breakpoint = BreakPoint::MD;
|
||||
/// let class = breakpoint.to_class("col");
|
||||
/// assert_eq!(class, "col-md".to_string());
|
||||
///
|
||||
/// let breakpoint = BreakPoint::Fluid;
|
||||
/// let class = breakpoint.to_class("offcanvas");
|
||||
/// assert_eq!(class, "offcanvas".to_string());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn to_class(&self, prefix: impl AsRef<str>) -> String {
|
||||
if self.is_breakpoint() {
|
||||
join!(prefix, "-", self.to_string())
|
||||
} else {
|
||||
String::from(prefix.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
/// Intenta generar un nombre de clase CSS basado en el punto de ruptura.
|
||||
///
|
||||
/// Si es un punto de ruptura efectivo (ver [`is_breakpoint()`](Self::is_breakpoint), devuelve
|
||||
/// `Some(String)` concatenando el prefijo, un guion (`-`) y el sufijo asociado. En otro caso
|
||||
/// devuelve `None`.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust
|
||||
/// let breakpoint = BreakPoint::MD;
|
||||
/// let class = breakpoint.try_class("col");
|
||||
/// assert_eq!(class, Some("col-md".to_string()));
|
||||
///
|
||||
/// let breakpoint = BreakPoint::Fluid;
|
||||
/// let class = breakpoint.try_class("navbar-expanded");
|
||||
/// assert_eq!(class, None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn try_class(&self, prefix: impl AsRef<str>) -> Option<String> {
|
||||
if self.is_breakpoint() {
|
||||
Some(join!(prefix, "-", self.to_string()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for BreakPoint {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::None => Ok(()),
|
||||
Self::SM => f.write_str("sm"),
|
||||
Self::MD => f.write_str("md"),
|
||||
Self::LG => f.write_str("lg"),
|
||||
Self::XL => f.write_str("xl"),
|
||||
Self::XXL => f.write_str("xxl"),
|
||||
// Devuelven "fluid" (para modelar `container-fluid`).
|
||||
Self::Fluid => f.write_str("fluid"),
|
||||
Self::FluidMax(_) => f.write_str("fluid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
155
extensions/pagetop-bootsier/src/theme/aux/color.rs
Normal file
155
extensions/pagetop-bootsier/src/theme/aux/color.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// **< Color >**************************************************************************************
|
||||
|
||||
/// Paleta de colores **temáticos**.
|
||||
///
|
||||
/// Equivalente a los nombres estándar de Bootstrap (`primary`, `secondary`, `success`, etc.). Sirve
|
||||
/// como base para componer clases de fondo ([`BgColor`]), borde ([`BorderColor`]) y texto
|
||||
/// ([`TextColor`]).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Color {
|
||||
#[default]
|
||||
Primary,
|
||||
Secondary,
|
||||
Success,
|
||||
Info,
|
||||
Warning,
|
||||
Danger,
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for Color {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Primary => f.write_str("primary"),
|
||||
Self::Secondary => f.write_str("secondary"),
|
||||
Self::Success => f.write_str("success"),
|
||||
Self::Info => f.write_str("info"),
|
||||
Self::Warning => f.write_str("warning"),
|
||||
Self::Danger => f.write_str("danger"),
|
||||
Self::Light => f.write_str("light"),
|
||||
Self::Dark => f.write_str("dark"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< BgColor >************************************************************************************
|
||||
|
||||
/// Colores de fondo (`bg-*`).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Body*` usa fondos predefinidos del tema (`bg-body`, `bg-body-secondary`, `bg-body-tertiary`).
|
||||
/// - `Theme(Color)` genera `bg-{color}` (p. ej., `bg-primary`).
|
||||
/// - `Subtle(Color)` genera `bg-{color}-subtle` (tono suave).
|
||||
/// - `Black` y `White` son colores explícitos.
|
||||
/// - `Transparent` no aplica color de fondo (`bg-transparent`).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BgColor {
|
||||
#[default]
|
||||
Default,
|
||||
Body,
|
||||
BodySecondary,
|
||||
BodyTertiary,
|
||||
Theme(Color),
|
||||
Subtle(Color),
|
||||
Black,
|
||||
White,
|
||||
Transparent,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for BgColor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Body => f.write_str("bg-body"),
|
||||
Self::BodySecondary => f.write_str("bg-body-secondary"),
|
||||
Self::BodyTertiary => f.write_str("bg-body-tertiary"),
|
||||
Self::Theme(c) => write!(f, "bg-{c}"),
|
||||
Self::Subtle(c) => write!(f, "bg-{c}-subtle"),
|
||||
Self::Black => f.write_str("bg-black"),
|
||||
Self::White => f.write_str("bg-white"),
|
||||
Self::Transparent => f.write_str("bg-transparent"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< BorderColor >********************************************************************************
|
||||
|
||||
/// Colores (`border-*`) para los bordes ([`Border`](crate::theme::aux::Border)).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Theme(Color)` genera `border-{color}`.
|
||||
/// - `Subtle(Color)` genera `border-{color}-subtle` (versión suavizada).
|
||||
/// - `Black` y `White` son colores explícitos.
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BorderColor {
|
||||
#[default]
|
||||
Default,
|
||||
Theme(Color),
|
||||
Subtle(Color),
|
||||
Black,
|
||||
White,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for BorderColor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Theme(c) => write!(f, "border-{c}"),
|
||||
Self::Subtle(c) => write!(f, "border-{c}-subtle"),
|
||||
Self::Black => f.write_str("border-black"),
|
||||
Self::White => f.write_str("border-white"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< TextColor >**********************************************************************************
|
||||
|
||||
/// Colores de texto y fondos de texto (`text-*`).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Body*` aplica colores predefinidos del tema (`text-body`, `text-body-emphasis`,
|
||||
/// `text-body-secondary`, `text-body-tertiary`).
|
||||
/// - `Theme(Color)` genera `text-{color}`.
|
||||
/// - `Emphasis(Color)` genera `text-{color}-emphasis` (contraste mayor acorde al tema).
|
||||
/// - `Background(Color)` genera `text-bg-{color}` (para color de fondo del texto).
|
||||
/// - `Black` y `White` son colores explícitos.
|
||||
#[derive(AutoDefault)]
|
||||
pub enum TextColor {
|
||||
#[default]
|
||||
Default,
|
||||
Body,
|
||||
BodyEmphasis,
|
||||
BodySecondary,
|
||||
BodyTertiary,
|
||||
Theme(Color),
|
||||
Emphasis(Color),
|
||||
Background(Color),
|
||||
Black,
|
||||
White,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for TextColor {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Body => f.write_str("text-body"),
|
||||
Self::BodyEmphasis => f.write_str("text-body-emphasis"),
|
||||
Self::BodySecondary => f.write_str("text-body-secondary"),
|
||||
Self::BodyTertiary => f.write_str("text-body-tertiary"),
|
||||
Self::Theme(c) => write!(f, "text-{c}"),
|
||||
Self::Emphasis(c) => write!(f, "text-{c}-emphasis"),
|
||||
Self::Background(c) => write!(f, "text-bg-{c}"),
|
||||
Self::Black => f.write_str("text-black"),
|
||||
Self::White => f.write_str("text-white"),
|
||||
}
|
||||
}
|
||||
}
|
||||
109
extensions/pagetop-bootsier/src/theme/aux/opacity.rs
Normal file
109
extensions/pagetop-bootsier/src/theme/aux/opacity.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// **< Opacity >************************************************************************************
|
||||
|
||||
/// Niveles de **opacidad** (`opacity-*`).
|
||||
///
|
||||
/// Se usa para modular la transparencia del color de fondo `bg-opacity-*` ([`BgOpacity`]), borde
|
||||
/// `border-opacity-*` ([`BorderOpacity`]) o texto `text-opacity-*` ([`TextOpacity`]), según las
|
||||
/// siguientes equivalencias:
|
||||
///
|
||||
/// - `Opaque` => `opacity-100` (100% de opacidad).
|
||||
/// - `SemiOpaque` => `opacity-75` (75%).
|
||||
/// - `Half` => `opacity-50` (50%).
|
||||
/// - `SemiTransparent` => `opacity-25` (25%).
|
||||
/// - `AlmostTransparent` => `opacity-10` (10%).
|
||||
/// - `Transparent` => `opacity-0` (0%, totalmente transparente).
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub enum Opacity {
|
||||
#[default]
|
||||
Opaque, // 100%
|
||||
SemiOpaque, // 75%
|
||||
Half, // 50%
|
||||
SemiTransparent, // 25%
|
||||
AlmostTransparent, // 10%
|
||||
Transparent, // 0%
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for Opacity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Opaque => f.write_str("opacity-100"),
|
||||
Self::SemiOpaque => f.write_str("opacity-75"),
|
||||
Self::Half => f.write_str("opacity-50"),
|
||||
Self::SemiTransparent => f.write_str("opacity-25"),
|
||||
Self::AlmostTransparent => f.write_str("opacity-10"),
|
||||
Self::Transparent => f.write_str("opacity-0"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< BgOpacity >**********************************************************************************
|
||||
|
||||
/// Opacidad para el fondo (`bg-opacity-*`).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Theme(Opacity)` genera `bg-{opacity}` (p. ej., `bg-opacity-50`).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BgOpacity {
|
||||
#[default]
|
||||
Default,
|
||||
Theme(Opacity),
|
||||
}
|
||||
|
||||
impl fmt::Display for BgOpacity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Theme(o) => write!(f, "bg-{o}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< BorderOpacity >******************************************************************************
|
||||
|
||||
/// Opacidad (`border-opacity-*`) para los bordes ([`Border`](crate::theme::aux::Border)).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Theme(Opacity)` genera `border-{opacity}` (p. ej., `border-opacity-25`).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum BorderOpacity {
|
||||
#[default]
|
||||
Default,
|
||||
Theme(Opacity),
|
||||
}
|
||||
|
||||
impl fmt::Display for BorderOpacity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Theme(o) => write!(f, "border-{o}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< TextOpacity >********************************************************************************
|
||||
|
||||
/// Opacidad para el texto (`text-opacity-*`).
|
||||
///
|
||||
/// - `Default` no añade clase (devuelve `""` para facilitar la composición de clases).
|
||||
/// - `Theme(Opacity)` genera `text-{opacity}` (p. ej., `text-opacity-100`).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum TextOpacity {
|
||||
#[default]
|
||||
Default,
|
||||
Theme(Opacity),
|
||||
}
|
||||
|
||||
impl fmt::Display for TextOpacity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Default => Ok(()),
|
||||
Self::Theme(o) => write!(f, "text-{o}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
214
extensions/pagetop-bootsier/src/theme/aux/rounded.rs
Normal file
214
extensions/pagetop-bootsier/src/theme/aux/rounded.rs
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// **< RoundedRadius >******************************************************************************
|
||||
|
||||
/// Radio (**redondeo**) para las esquinas ([`Rounded`]).
|
||||
///
|
||||
/// Mapea a `rounded`, `rounded-0`, `rounded-{1..5}`, `rounded-circle` y `rounded-pill`.
|
||||
///
|
||||
/// - `None` no añade clase (devuelve `""`).
|
||||
/// - `Default` genera `rounded` (radio por defecto del tema).
|
||||
/// - `Zero` genera `rounded-0` (sin redondeo).
|
||||
/// - `Scale{1..5}` genera `rounded-{1..5}` (radio creciente).
|
||||
/// - `Circle` genera `rounded-circle`.
|
||||
/// - `Pill` genera `rounded-pill`.
|
||||
#[derive(AutoDefault)]
|
||||
pub enum RoundedRadius {
|
||||
#[default]
|
||||
None,
|
||||
Default,
|
||||
Zero,
|
||||
Scale1,
|
||||
Scale2,
|
||||
Scale3,
|
||||
Scale4,
|
||||
Scale5,
|
||||
Circle,
|
||||
Pill,
|
||||
}
|
||||
|
||||
impl RoundedRadius {
|
||||
#[rustfmt::skip]
|
||||
fn to_class(&self, base: impl AsRef<str>) -> String {
|
||||
match self {
|
||||
RoundedRadius::None => String::new(),
|
||||
RoundedRadius::Default => String::from(base.as_ref()),
|
||||
RoundedRadius::Zero => join!(base, "-0"),
|
||||
RoundedRadius::Scale1 => join!(base, "-1"),
|
||||
RoundedRadius::Scale2 => join!(base, "-2"),
|
||||
RoundedRadius::Scale3 => join!(base, "-3"),
|
||||
RoundedRadius::Scale4 => join!(base, "-4"),
|
||||
RoundedRadius::Scale5 => join!(base, "-5"),
|
||||
RoundedRadius::Circle => join!(base, "-circle"),
|
||||
RoundedRadius::Pill => join!(base, "-pill"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RoundedRadius {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.to_class("rounded"))
|
||||
}
|
||||
}
|
||||
|
||||
// **< Rounded >************************************************************************************
|
||||
|
||||
/// Agrupa propiedades para crear **esquinas redondeadas**.
|
||||
///
|
||||
/// Permite:
|
||||
///
|
||||
/// - Definir un radio **global para todas las esquinas** (`radius`).
|
||||
/// - Ajustar el radio asociado a las **esquinas de cada lado lógico** (`top`, `end`, `bottom`,
|
||||
/// `start`, **en este orden**, respetando LTR/RTL).
|
||||
/// - Ajustar el radio de las **esquinas concretas** (`top-start`, `top-end`, `bottom-start`,
|
||||
/// `bottom-end`, **en este orden**, respetando LTR/RTL).
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// **Radio global:**
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// let r = Rounded::with(RoundedRadius::Default);
|
||||
/// assert_eq!(r.to_string(), "rounded");
|
||||
/// ```
|
||||
///
|
||||
/// **Sin redondeo:**
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// let r = Rounded::new();
|
||||
/// assert_eq!(r.to_string(), "");
|
||||
/// ```
|
||||
///
|
||||
/// **Radio en las esquinas de un lado lógico:**
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// let r = Rounded::new().with_end(RoundedRadius::Scale2);
|
||||
/// assert_eq!(r.to_string(), "rounded-end-2");
|
||||
/// ```
|
||||
///
|
||||
/// **Radio en una esquina concreta:**
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// let r = Rounded::new().with_top_start(RoundedRadius::Scale3);
|
||||
/// assert_eq!(r.to_string(), "rounded-top-start-3");
|
||||
/// ```
|
||||
///
|
||||
/// **Combinado (ejemplo completo):**
|
||||
/// ```rust
|
||||
/// # use pagetop_bootsier::prelude::*;
|
||||
/// let r = Rounded::new()
|
||||
/// .with_top(RoundedRadius::Default) // Añade redondeo arriba.
|
||||
/// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta.
|
||||
/// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina.
|
||||
///
|
||||
/// assert_eq!(r.to_string(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle");
|
||||
/// ```
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Rounded {
|
||||
radius : RoundedRadius,
|
||||
top : RoundedRadius,
|
||||
end : RoundedRadius,
|
||||
bottom : RoundedRadius,
|
||||
start : RoundedRadius,
|
||||
top_start : RoundedRadius,
|
||||
top_end : RoundedRadius,
|
||||
bottom_start: RoundedRadius,
|
||||
bottom_end : RoundedRadius,
|
||||
}
|
||||
|
||||
impl Rounded {
|
||||
/// Prepara las esquinas **sin redondeo global** de partida.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Crea las esquinas **con redondeo global** (`radius`).
|
||||
pub fn with(radius: RoundedRadius) -> Self {
|
||||
Self::default().with_radius(radius)
|
||||
}
|
||||
|
||||
// **< Rounded BUILDER >************************************************************************
|
||||
|
||||
/// Establece el radio global de las esquinas (`rounded*`).
|
||||
pub fn with_radius(mut self, radius: RoundedRadius) -> Self {
|
||||
self.radius = radius;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el radio en las esquinas del lado superior (`rounded-top-*`).
|
||||
pub fn with_top(mut self, radius: RoundedRadius) -> Self {
|
||||
self.top = radius;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el radio en las esquinas del lado lógico final (`rounded-end-*`). Respeta LTR/RTL.
|
||||
pub fn with_end(mut self, radius: RoundedRadius) -> Self {
|
||||
self.end = radius;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el radio en las esquinas del lado inferior (`rounded-bottom-*`).
|
||||
pub fn with_bottom(mut self, radius: RoundedRadius) -> Self {
|
||||
self.bottom = radius;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el radio en las esquinas del lado lógico inicial (`rounded-start-*`). Respeta
|
||||
/// LTR/RTL.
|
||||
pub fn with_start(mut self, radius: RoundedRadius) -> Self {
|
||||
self.start = radius;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el radio en la esquina superior-inicial (`rounded-top-start-*`). Respeta LTR/RTL.
|
||||
pub fn with_top_start(mut self, radius: RoundedRadius) -> Self {
|
||||
self.top_start = radius;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el radio en la esquina superior-final (`rounded-top-end-*`). Respeta LTR/RTL.
|
||||
pub fn with_top_end(mut self, radius: RoundedRadius) -> Self {
|
||||
self.top_end = radius;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el radio en la esquina inferior-inicial (`rounded-bottom-start-*`). Respeta
|
||||
/// LTR/RTL.
|
||||
pub fn with_bottom_start(mut self, radius: RoundedRadius) -> Self {
|
||||
self.bottom_start = radius;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el radio en la esquina inferior-final (`rounded-bottom-end-*`). Respeta LTR/RTL.
|
||||
pub fn with_bottom_end(mut self, radius: RoundedRadius) -> Self {
|
||||
self.bottom_end = radius;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Rounded {
|
||||
/// Concatena cada definición en el orden: *global*, `top`, `end`, `bottom`, `start`,
|
||||
/// `top-start`, `top-end`, `bottom-start` y `bottom-end`; respetando LTR/RTL y omitiendo las
|
||||
/// definiciones vacías.
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
join_opt!([
|
||||
self.radius.to_string(),
|
||||
self.top.to_class("rounded-top"),
|
||||
self.end.to_class("rounded-end"),
|
||||
self.bottom.to_class("rounded-bottom"),
|
||||
self.start.to_class("rounded-start"),
|
||||
self.top_start.to_class("rounded-top-start"),
|
||||
self.top_end.to_class("rounded-top-end"),
|
||||
self.bottom_start.to_class("rounded-bottom-start"),
|
||||
self.bottom_end.to_class("rounded-bottom-end"),
|
||||
]; " ")
|
||||
.unwrap_or_default()
|
||||
)
|
||||
}
|
||||
}
|
||||
269
extensions/pagetop-bootsier/src/theme/container.rs
Normal file
269
extensions/pagetop-bootsier/src/theme/container.rs
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Tipo de contenedor ([`Container`]).
|
||||
///
|
||||
/// Permite aplicar la etiqueta HTML apropiada (`<main>`, `<header>`, etc.) manteniendo una API
|
||||
/// común a todos los contenedores.
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ContainerType {
|
||||
/// Contenedor genérico (`<div>`).
|
||||
#[default]
|
||||
Default,
|
||||
/// Contenido principal de la página (`<main>`).
|
||||
Main,
|
||||
/// Encabezado de la página o de sección (`<header>`).
|
||||
Header,
|
||||
/// Pie de la página o de sección (`<footer>`).
|
||||
Footer,
|
||||
/// Sección de contenido (`<section>`).
|
||||
Section,
|
||||
/// Artículo de contenido (`<article>`).
|
||||
Article,
|
||||
}
|
||||
|
||||
/// Componente genérico para crear un contenedor de componentes.
|
||||
///
|
||||
/// Envuelve el contenido con la etiqueta HTML indicada por [`ContainerType`]. Sólo se renderiza si
|
||||
/// existen componentes hijos (*children*).
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Container {
|
||||
id : AttrId,
|
||||
classes : AttrClasses,
|
||||
container_type: ContainerType,
|
||||
breakpoint : BreakPoint,
|
||||
children : Children,
|
||||
bg_color : BgColor,
|
||||
text_color : TextColor,
|
||||
border : Border,
|
||||
rounded : Rounded,
|
||||
}
|
||||
|
||||
impl Component for Container {
|
||||
fn new() -> Self {
|
||||
Container::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.alter_classes(
|
||||
ClassesOp::Prepend,
|
||||
[
|
||||
join_pair!("container", "-", self.breakpoint().to_string()),
|
||||
self.bg_color().to_string(),
|
||||
self.text_color().to_string(),
|
||||
self.border().to_string(),
|
||||
self.rounded().to_string(),
|
||||
]
|
||||
.join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let output = self.children().render(cx);
|
||||
if output.is_empty() {
|
||||
return PrepareMarkup::None;
|
||||
}
|
||||
let style = match self.breakpoint() {
|
||||
BreakPoint::FluidMax(w) if w.is_measurable() => {
|
||||
Some(join!("max-width: ", w.to_string(), ";"))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
match self.container_type() {
|
||||
ContainerType::Default => PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ContainerType::Main => PrepareMarkup::With(html! {
|
||||
main id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ContainerType::Header => PrepareMarkup::With(html! {
|
||||
header id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ContainerType::Footer => PrepareMarkup::With(html! {
|
||||
footer id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ContainerType::Section => PrepareMarkup::With(html! {
|
||||
section id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
ContainerType::Article => PrepareMarkup::With(html! {
|
||||
article id=[self.id()] class=[self.classes().get()] style=[style] {
|
||||
(output)
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Container {
|
||||
/// Crea un contenedor de tipo `Main` (`<main>`).
|
||||
pub fn main() -> Self {
|
||||
Container {
|
||||
container_type: ContainerType::Main,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un contenedor de tipo `Header` (`<header>`).
|
||||
pub fn header() -> Self {
|
||||
Container {
|
||||
container_type: ContainerType::Header,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un contenedor de tipo `Footer` (`<footer>`).
|
||||
pub fn footer() -> Self {
|
||||
Container {
|
||||
container_type: ContainerType::Footer,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un contenedor de tipo `Section` (`<section>`).
|
||||
pub fn section() -> Self {
|
||||
Container {
|
||||
container_type: ContainerType::Section,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Crea un contenedor de tipo `Article` (`<article>`).
|
||||
pub fn article() -> Self {
|
||||
Container {
|
||||
container_type: ContainerType::Article,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// **< Container BUILDER >**********************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del contenedor.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica la lista de clases CSS aplicadas al contenedor.
|
||||
#[builder_fn]
|
||||
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||
self.classes.alter_value(op, classes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el *punto de ruptura* del contenedor.
|
||||
#[builder_fn]
|
||||
pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self {
|
||||
self.breakpoint = bp;
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un nuevo componente hijo al contenedor.
|
||||
pub fn add_child(mut self, component: impl Component) -> Self {
|
||||
self.children
|
||||
.alter_child(ChildOp::Add(Child::with(component)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`].
|
||||
#[builder_fn]
|
||||
pub fn with_child(mut self, op: ChildOp) -> Self {
|
||||
self.children.alter_child(op);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el color de fondo ([`BgColor`]).
|
||||
#[builder_fn]
|
||||
pub fn with_bg_color(mut self, color: BgColor) -> Self {
|
||||
self.bg_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el color del texto ([`TextColor`]).
|
||||
#[builder_fn]
|
||||
pub fn with_text_color(mut self, color: TextColor) -> Self {
|
||||
self.text_color = color;
|
||||
self
|
||||
}
|
||||
|
||||
/// Atajo para definir los colores de fondo y texto a la vez.
|
||||
#[builder_fn]
|
||||
pub fn with_colors(mut self, bg_color: BgColor, text_color: TextColor) -> Self {
|
||||
self.bg_color = bg_color;
|
||||
self.text_color = text_color;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el borde del contenedor ([`Border`]).
|
||||
#[builder_fn]
|
||||
pub fn with_border(mut self, border: Border) -> Self {
|
||||
self.border = border;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece esquinas redondeadas para el contenedor.
|
||||
#[builder_fn]
|
||||
pub fn with_rounded(mut self, rounded: Rounded) -> Self {
|
||||
self.rounded = rounded;
|
||||
self
|
||||
}
|
||||
|
||||
// **< Container GETTERS >**********************************************************************
|
||||
|
||||
/// Devuelve las clases CSS asociadas al contenedor.
|
||||
pub fn classes(&self) -> &AttrClasses {
|
||||
&self.classes
|
||||
}
|
||||
|
||||
/// Devuelve el tipo semántico del contenedor.
|
||||
pub fn container_type(&self) -> &ContainerType {
|
||||
&self.container_type
|
||||
}
|
||||
|
||||
/// Devuelve el *punto de ruptura* actualmente configurado.
|
||||
pub fn breakpoint(&self) -> &BreakPoint {
|
||||
&self.breakpoint
|
||||
}
|
||||
|
||||
/// Devuelve la lista de hijos (`children`) del contenedor.
|
||||
pub fn children(&self) -> &Children {
|
||||
&self.children
|
||||
}
|
||||
|
||||
/// Devuelve el color de fondo del contenedor.
|
||||
pub fn bg_color(&self) -> &BgColor {
|
||||
&self.bg_color
|
||||
}
|
||||
|
||||
/// Devuelve el color del texto del contenedor.
|
||||
pub fn text_color(&self) -> &TextColor {
|
||||
&self.text_color
|
||||
}
|
||||
|
||||
/// Devuelve el borde del contenedor.
|
||||
pub fn border(&self) -> &Border {
|
||||
&self.border
|
||||
}
|
||||
|
||||
/// Devuelve las esquinas redondeadas del contenedor.
|
||||
pub fn rounded(&self) -> &Rounded {
|
||||
&self.rounded
|
||||
}
|
||||
}
|
||||
5
extensions/pagetop-bootsier/src/theme/dropdown.rs
Normal file
5
extensions/pagetop-bootsier/src/theme/dropdown.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod component;
|
||||
pub use component::Dropdown;
|
||||
|
||||
mod item;
|
||||
pub use item::Item;
|
||||
99
extensions/pagetop-bootsier/src/theme/dropdown/component.rs
Normal file
99
extensions/pagetop-bootsier/src/theme/dropdown/component.rs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Dropdown {
|
||||
id : AttrId,
|
||||
classes: AttrClasses,
|
||||
items : Children,
|
||||
}
|
||||
|
||||
impl Component for Dropdown {
|
||||
fn new() -> Self {
|
||||
Dropdown::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.alter_classes(ClassesOp::Prepend, "dropdown");
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let items = self.items().render(cx);
|
||||
if items.is_empty() {
|
||||
return PrepareMarkup::None;
|
||||
}
|
||||
|
||||
PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class=[self.classes().get()] {
|
||||
button
|
||||
type="button"
|
||||
class="btn btn-secondary dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
{
|
||||
("Dropdown button")
|
||||
}
|
||||
ul class="dropdown-menu" {
|
||||
li {
|
||||
a class="dropdown-item" href="#" {
|
||||
("Action")
|
||||
}
|
||||
}
|
||||
li {
|
||||
a class="dropdown-item" href="#" {
|
||||
("Another action")
|
||||
}
|
||||
}
|
||||
li {
|
||||
a class="dropdown-item" href="#" {
|
||||
("Something else here")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Dropdown {
|
||||
// **< Dropdown BUILDER >***********************************************************************
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||
self.classes.alter_value(op, classes);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_item(mut self, item: dropdown::Item) -> Self {
|
||||
self.items.add(Child::with(item));
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_items(mut self, op: TypedOp<dropdown::Item>) -> Self {
|
||||
self.items.alter_typed(op);
|
||||
self
|
||||
}
|
||||
|
||||
// **< Dropdown GETTERS >***********************************************************************
|
||||
|
||||
pub fn classes(&self) -> &AttrClasses {
|
||||
&self.classes
|
||||
}
|
||||
|
||||
pub fn items(&self) -> &Children {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
109
extensions/pagetop-bootsier/src/theme/dropdown/item.rs
Normal file
109
extensions/pagetop-bootsier/src/theme/dropdown/item.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
// **< ItemType >***********************************************************************************
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ItemType {
|
||||
#[default]
|
||||
Void,
|
||||
Label(L10n),
|
||||
Link(L10n, FnPathByContext),
|
||||
LinkBlank(L10n, FnPathByContext),
|
||||
}
|
||||
|
||||
// **< Item >***************************************************************************************
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Item {
|
||||
item_type: ItemType,
|
||||
}
|
||||
|
||||
impl Component for Item {
|
||||
fn new() -> Self {
|
||||
Item::default()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let description: Option<String> = None;
|
||||
|
||||
// Obtiene la URL actual desde `cx.request`.
|
||||
let current_path = cx.request().map(|request| request.path());
|
||||
|
||||
match self.item_type() {
|
||||
ItemType::Void => PrepareMarkup::None,
|
||||
ItemType::Label(label) => PrepareMarkup::With(html! {
|
||||
li class="dropdown-item" {
|
||||
span title=[description] {
|
||||
//(left_icon)
|
||||
(label.using(cx))
|
||||
//(right_icon)
|
||||
}
|
||||
}
|
||||
}),
|
||||
ItemType::Link(label, path) => {
|
||||
let item_path = path(cx);
|
||||
let (class, aria) = if current_path == Some(item_path) {
|
||||
("dropdown-item active", Some("page"))
|
||||
} else {
|
||||
("dropdown-item", None)
|
||||
};
|
||||
PrepareMarkup::With(html! {
|
||||
li class=(class) aria-current=[aria] {
|
||||
a class="nav-link" href=(item_path) title=[description] {
|
||||
//(left_icon)
|
||||
(label.using(cx))
|
||||
//(right_icon)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
ItemType::LinkBlank(label, path) => {
|
||||
let item_path = path(cx);
|
||||
let (class, aria) = if current_path == Some(item_path) {
|
||||
("dropdown-item active", Some("page"))
|
||||
} else {
|
||||
("dropdown-item", None)
|
||||
};
|
||||
PrepareMarkup::With(html! {
|
||||
li class=(class) aria-current=[aria] {
|
||||
a class="nav-link" href=(item_path) title=[description] target="_blank" {
|
||||
//(left_icon)
|
||||
(label.using(cx))
|
||||
//(right_icon)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn label(label: L10n) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Label(label),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link(label: L10n, path: FnPathByContext) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Link(label, path),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link_blank(label: L10n, path: FnPathByContext) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::LinkBlank(label, path),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// Item GETTERS.
|
||||
|
||||
pub fn item_type(&self) -> &ItemType {
|
||||
&self.item_type
|
||||
}
|
||||
}
|
||||
186
extensions/pagetop-bootsier/src/theme/image.rs
Normal file
186
extensions/pagetop-bootsier/src/theme/image.rs
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ImageSource {
|
||||
#[default]
|
||||
//Logo(PageTopLogo),
|
||||
Responsive(String),
|
||||
Thumbnail(String),
|
||||
Static(String),
|
||||
}
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ImageSize {
|
||||
#[default]
|
||||
Auto,
|
||||
Dimensions(UnitValue, UnitValue),
|
||||
Width(UnitValue),
|
||||
Height(UnitValue),
|
||||
Both(UnitValue),
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Image {
|
||||
id : AttrId,
|
||||
classes: AttrClasses,
|
||||
source : ImageSource,
|
||||
alt : AttrL10n,
|
||||
size : ImageSize,
|
||||
border : Border,
|
||||
rounded: Rounded,
|
||||
}
|
||||
|
||||
impl Component for Image {
|
||||
fn new() -> Self {
|
||||
Image::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.alter_classes(
|
||||
ClassesOp::Prepend,
|
||||
[
|
||||
String::from(match self.source() {
|
||||
//ImageSource::Logo(_) => "img-fluid",
|
||||
ImageSource::Responsive(_) => "img-fluid",
|
||||
ImageSource::Thumbnail(_) => "img-thumbnail",
|
||||
_ => "",
|
||||
}),
|
||||
self.border().to_string(),
|
||||
self.rounded().to_string(),
|
||||
]
|
||||
.join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let dimensions = match self.size() {
|
||||
ImageSize::Auto => None,
|
||||
ImageSize::Dimensions(w, h) => {
|
||||
let w = w.to_string();
|
||||
let h = h.to_string();
|
||||
Some(join!("width: ", w, "; height: ", h, ";"))
|
||||
}
|
||||
ImageSize::Width(w) => {
|
||||
let w = w.to_string();
|
||||
Some(join!("width: ", w, ";"))
|
||||
}
|
||||
ImageSize::Height(h) => {
|
||||
let h = h.to_string();
|
||||
Some(join!("height: ", h, ";"))
|
||||
}
|
||||
ImageSize::Both(v) => {
|
||||
let v = v.to_string();
|
||||
Some(join!("width: ", v, "; height: ", v, ";"))
|
||||
}
|
||||
};
|
||||
let source = match self.source() {
|
||||
/*
|
||||
ImageSource::Logo(logo) => {
|
||||
return PrepareMarkup::With(html! {
|
||||
span
|
||||
id=[self.id()]
|
||||
class=[self.classes().get()]
|
||||
style=[dimensions]
|
||||
{
|
||||
(logo.render(cx))
|
||||
}
|
||||
})
|
||||
}
|
||||
*/
|
||||
ImageSource::Responsive(source) => Some(source),
|
||||
ImageSource::Thumbnail(source) => Some(source),
|
||||
ImageSource::Static(source) => Some(source),
|
||||
};
|
||||
PrepareMarkup::With(html! {
|
||||
img
|
||||
src=[source]
|
||||
alt=[self.alternative().lookup(cx)]
|
||||
id=[self.id()]
|
||||
class=[self.classes().get()]
|
||||
style=[dimensions] {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub fn with(source: ImageSource) -> Self {
|
||||
Image::default().with_source(source)
|
||||
}
|
||||
|
||||
// **< Image BUILDER >**************************************************************************
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||
self.classes.alter_value(op, classes);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_source(mut self, source: ImageSource) -> Self {
|
||||
self.source = source;
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_alternative(mut self, alt: L10n) -> Self {
|
||||
self.alt.alter_value(alt);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_size(mut self, size: ImageSize) -> Self {
|
||||
self.size = size;
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_border(mut self, border: Border) -> Self {
|
||||
self.border = border;
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_rounded(mut self, rounded: Rounded) -> Self {
|
||||
self.rounded = rounded;
|
||||
self
|
||||
}
|
||||
|
||||
// **< Image GETTERS >**************************************************************************
|
||||
|
||||
pub fn classes(&self) -> &AttrClasses {
|
||||
&self.classes
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &ImageSource {
|
||||
&self.source
|
||||
}
|
||||
|
||||
pub fn alternative(&self) -> &AttrL10n {
|
||||
&self.alt
|
||||
}
|
||||
|
||||
pub fn size(&self) -> &ImageSize {
|
||||
&self.size
|
||||
}
|
||||
|
||||
pub fn border(&self) -> &Border {
|
||||
&self.border
|
||||
}
|
||||
|
||||
pub fn rounded(&self) -> &Rounded {
|
||||
&self.rounded
|
||||
}
|
||||
}
|
||||
17
extensions/pagetop-bootsier/src/theme/navbar.rs
Normal file
17
extensions/pagetop-bootsier/src/theme/navbar.rs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
mod component;
|
||||
pub use component::{Navbar, NavbarToggler, NavbarType};
|
||||
|
||||
mod button_toggler;
|
||||
pub use button_toggler::ButtonToggler;
|
||||
|
||||
mod content;
|
||||
pub use content::{Content, ContentType};
|
||||
|
||||
mod brand;
|
||||
pub use brand::Brand;
|
||||
|
||||
mod nav;
|
||||
pub use nav::Nav;
|
||||
|
||||
mod item;
|
||||
pub use item::{Item, ItemType};
|
||||
104
extensions/pagetop-bootsier/src/theme/navbar/brand.rs
Normal file
104
extensions/pagetop-bootsier/src/theme/navbar/brand.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Brand {
|
||||
id : AttrId,
|
||||
#[default(_code = "global::SETTINGS.app.name.to_owned()")]
|
||||
app_name : String,
|
||||
slogan : AttrL10n,
|
||||
logo : Typed<Image>,
|
||||
#[default(_code = "|_| \"/\"")]
|
||||
home : FnPathByContext,
|
||||
}
|
||||
|
||||
impl Component for Brand {
|
||||
fn new() -> Self {
|
||||
Brand::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let logo = self.logo().render(cx);
|
||||
let home = self.home()(cx);
|
||||
let title = &L10n::l("site_home").lookup(cx);
|
||||
PrepareMarkup::With(html! {
|
||||
div id=[self.id()] class="branding__container" {
|
||||
div class="branding__content" {
|
||||
@if !logo.is_empty() {
|
||||
a class="branding__logo" href=(home) title=[title] rel="home" {
|
||||
(logo)
|
||||
}
|
||||
}
|
||||
div class="branding__text" {
|
||||
a class="branding__name" href=(home) title=[title] rel="home" {
|
||||
(self.app_name())
|
||||
}
|
||||
@if let Some(slogan) = self.slogan().lookup(cx) {
|
||||
div class="branding__slogan" {
|
||||
(slogan)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Brand {
|
||||
// Brand BUILDER.
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_app_name(mut self, app_name: impl Into<String>) -> Self {
|
||||
self.app_name = app_name.into();
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_slogan(mut self, slogan: L10n) -> Self {
|
||||
self.slogan.alter_value(slogan);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_logo(mut self, logo: Option<Image>) -> Self {
|
||||
self.logo.alter_component(logo);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_home(mut self, home: FnPathByContext) -> Self {
|
||||
self.home = home;
|
||||
self
|
||||
}
|
||||
|
||||
// Brand GETTERS.
|
||||
|
||||
pub fn app_name(&self) -> &String {
|
||||
&self.app_name
|
||||
}
|
||||
|
||||
pub fn slogan(&self) -> &AttrL10n {
|
||||
&self.slogan
|
||||
}
|
||||
|
||||
pub fn logo(&self) -> &Typed<Image> {
|
||||
&self.logo
|
||||
}
|
||||
|
||||
pub fn home(&self) -> &FnPathByContext {
|
||||
&self.home
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::LOCALES_BOOTSIER;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
#[derive(AutoDefault, PartialEq)]
|
||||
pub(crate) enum Toggle {
|
||||
#[default]
|
||||
Collapse,
|
||||
Offcanvas,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for Toggle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Toggle::Collapse => write!(f, "collapse"),
|
||||
Toggle::Offcanvas => write!(f, "offcanvas"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub struct ButtonToggler;
|
||||
|
||||
impl Component for ButtonToggler {
|
||||
fn new() -> Self {
|
||||
ButtonToggler::default()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
|
||||
PrepareMarkup::With(html! {
|
||||
button
|
||||
type="button"
|
||||
class="navbar-toggler"
|
||||
{
|
||||
span class="navbar-toggler-icon" {}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ButtonToggler {
|
||||
// ButtonToggler PRIVATE RENDER.
|
||||
|
||||
pub(crate) fn render(
|
||||
&self,
|
||||
cx: &mut Context,
|
||||
id_content: String,
|
||||
data_bs_toggle: Toggle,
|
||||
) -> Markup {
|
||||
let id_content_target = join!("#", id_content);
|
||||
let aria_expanded = if data_bs_toggle == Toggle::Collapse {
|
||||
Some("false")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
html! {
|
||||
button
|
||||
type="button"
|
||||
class="navbar-toggler"
|
||||
data-bs-toggle=(data_bs_toggle)
|
||||
data-bs-target=(id_content_target)
|
||||
aria-controls=(id_content)
|
||||
aria-expanded=[aria_expanded]
|
||||
aria-label=[L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx)]
|
||||
{
|
||||
span class="navbar-toggler-icon" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
233
extensions/pagetop-bootsier/src/theme/navbar/component.rs
Normal file
233
extensions/pagetop-bootsier/src/theme/navbar/component.rs
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::LOCALES_BOOTSIER;
|
||||
|
||||
const TOGGLE_COLLAPSE: &str = "collapse";
|
||||
const TOGGLE_OFFCANVAS: &str = "offcanvas";
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum NavbarToggler {
|
||||
#[default]
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum NavbarType {
|
||||
#[default]
|
||||
None,
|
||||
Nav(Typed<navbar::Nav>),
|
||||
Offcanvas(Typed<Offcanvas>),
|
||||
Text(L10n),
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Navbar {
|
||||
id : AttrId,
|
||||
classes : AttrClasses,
|
||||
expand : BreakPoint,
|
||||
toggler : NavbarToggler,
|
||||
navbar_type: NavbarType,
|
||||
contents : Children,
|
||||
brand : Typed<navbar::Brand>,
|
||||
}
|
||||
|
||||
impl Component for Navbar {
|
||||
fn new() -> Self {
|
||||
Navbar::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.alter_classes(
|
||||
ClassesOp::Prepend,
|
||||
[
|
||||
"navbar".to_string(),
|
||||
self.expand().try_class("navbar-expand").unwrap_or_default(),
|
||||
]
|
||||
.join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let id = cx.required_id::<Self>(self.id());
|
||||
|
||||
let navbar_type = match self.navbar_type() {
|
||||
NavbarType::None => return PrepareMarkup::None,
|
||||
NavbarType::Nav(nav) => {
|
||||
let id_content = join!(id, "-content");
|
||||
match self.toggler() {
|
||||
NavbarToggler::Enabled => self.toggler_wrapper(
|
||||
TOGGLE_COLLAPSE,
|
||||
L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx),
|
||||
id_content,
|
||||
self.brand().render(cx),
|
||||
nav.render(cx),
|
||||
),
|
||||
NavbarToggler::Disabled => nav.render(cx),
|
||||
}
|
||||
}
|
||||
NavbarType::Offcanvas(oc) => {
|
||||
let id_content = oc.id().unwrap_or_default();
|
||||
self.toggler_wrapper(
|
||||
TOGGLE_OFFCANVAS,
|
||||
L10n::t("toggle", &LOCALES_BOOTSIER).lookup(cx),
|
||||
id_content,
|
||||
self.brand().render(cx),
|
||||
oc.render(cx),
|
||||
)
|
||||
}
|
||||
NavbarType::Text(text) => html! {
|
||||
span class="navbar-text" {
|
||||
(text.using(cx))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
self.nav_wrapper(id, self.brand().render(cx), navbar_type)
|
||||
}
|
||||
}
|
||||
|
||||
impl Navbar {
|
||||
pub fn with_nav(nav: navbar::Nav) -> Self {
|
||||
Navbar::default().with_navbar_type(NavbarType::Nav(Typed::with(nav)))
|
||||
}
|
||||
|
||||
pub fn with_offcanvas(offcanvas: Offcanvas) -> Self {
|
||||
Navbar::default().with_navbar_type(NavbarType::Offcanvas(Typed::with(offcanvas)))
|
||||
}
|
||||
|
||||
// Navbar BUILDER.
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||
self.classes.alter_value(op, classes);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_expand(mut self, bp: BreakPoint) -> Self {
|
||||
self.expand = bp;
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_toggler(mut self, toggler: NavbarToggler) -> Self {
|
||||
self.toggler = toggler;
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_navbar_type(mut self, navbar_type: NavbarType) -> Self {
|
||||
self.navbar_type = navbar_type;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_content(mut self, content: navbar::Content) -> Self {
|
||||
self.contents.add(Child::with(content));
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_contents(mut self, op: TypedOp<navbar::Content>) -> Self {
|
||||
self.contents.alter_typed(op);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_brand(mut self, brand: Option<navbar::Brand>) -> Self {
|
||||
self.brand.alter_component(brand);
|
||||
self
|
||||
}
|
||||
|
||||
// Navbar GETTERS.
|
||||
|
||||
pub fn classes(&self) -> &AttrClasses {
|
||||
&self.classes
|
||||
}
|
||||
|
||||
pub fn expand(&self) -> &BreakPoint {
|
||||
&self.expand
|
||||
}
|
||||
|
||||
pub fn toggler(&self) -> &NavbarToggler {
|
||||
&self.toggler
|
||||
}
|
||||
|
||||
pub fn navbar_type(&self) -> &NavbarType {
|
||||
&self.navbar_type
|
||||
}
|
||||
|
||||
pub fn contents(&self) -> &Children {
|
||||
&self.contents
|
||||
}
|
||||
|
||||
pub fn brand(&self) -> &Typed<navbar::Brand> {
|
||||
&self.brand
|
||||
}
|
||||
|
||||
// Navbar HELPERS.
|
||||
|
||||
fn nav_wrapper(&self, id: String, brand: Markup, content: Markup) -> PrepareMarkup {
|
||||
if content.is_empty() {
|
||||
PrepareMarkup::None
|
||||
} else {
|
||||
PrepareMarkup::With(html! {
|
||||
(brand)
|
||||
nav id=(id) class=[self.classes().get()] {
|
||||
div class="container-fluid" {
|
||||
(content)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn toggler_wrapper(
|
||||
&self,
|
||||
data_bs_toggle: &str,
|
||||
aria_label: Option<String>,
|
||||
id_content: String,
|
||||
brand: Markup,
|
||||
content: Markup,
|
||||
) -> Markup {
|
||||
if content.is_empty() {
|
||||
html! {}
|
||||
} else {
|
||||
let id_content_target = join!("#", id_content);
|
||||
let aria_expanded = if data_bs_toggle == TOGGLE_COLLAPSE {
|
||||
Some("false")
|
||||
} else {
|
||||
None
|
||||
};
|
||||
html! {
|
||||
(brand)
|
||||
button
|
||||
type="button"
|
||||
class="navbar-toggler"
|
||||
data-bs-toggle=(data_bs_toggle)
|
||||
data-bs-target=(id_content_target)
|
||||
aria-controls=(id_content)
|
||||
aria-expanded=[aria_expanded]
|
||||
aria-label=[aria_label]
|
||||
{
|
||||
span class="navbar-toggler-icon" {}
|
||||
}
|
||||
div id=(id_content) class="collapse navbar-collapse" {
|
||||
(content)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
extensions/pagetop-bootsier/src/theme/navbar/content.rs
Normal file
69
extensions/pagetop-bootsier/src/theme/navbar/content.rs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::theme::navbar;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ContentType {
|
||||
#[default]
|
||||
None,
|
||||
Brand(Typed<navbar::Brand>),
|
||||
Nav(Typed<navbar::Nav>),
|
||||
Text(L10n),
|
||||
}
|
||||
|
||||
// Item.
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Content {
|
||||
content: ContentType,
|
||||
}
|
||||
|
||||
impl Component for Content {
|
||||
fn new() -> Self {
|
||||
Content::default()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
match self.content() {
|
||||
ContentType::None => PrepareMarkup::None,
|
||||
ContentType::Brand(brand) => PrepareMarkup::With(html! {
|
||||
(brand.render(cx))
|
||||
}),
|
||||
ContentType::Nav(nav) => PrepareMarkup::With(html! {
|
||||
(nav.render(cx))
|
||||
}),
|
||||
ContentType::Text(text) => PrepareMarkup::With(html! {
|
||||
span class="navbar-text" {
|
||||
(text.using(cx))
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Content {
|
||||
pub fn brand(content: navbar::Brand) -> Self {
|
||||
Content {
|
||||
content: ContentType::Brand(Typed::with(content)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nav(content: navbar::Nav) -> Self {
|
||||
Content {
|
||||
content: ContentType::Nav(Typed::with(content)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text(content: L10n) -> Self {
|
||||
Content {
|
||||
content: ContentType::Text(content),
|
||||
}
|
||||
}
|
||||
|
||||
// Content GETTERS.
|
||||
|
||||
pub fn content(&self) -> &ContentType {
|
||||
&self.content
|
||||
}
|
||||
}
|
||||
113
extensions/pagetop-bootsier/src/theme/navbar/item.rs
Normal file
113
extensions/pagetop-bootsier/src/theme/navbar/item.rs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::theme::Dropdown;
|
||||
|
||||
type Label = L10n;
|
||||
|
||||
#[derive(AutoDefault)]
|
||||
pub enum ItemType {
|
||||
#[default]
|
||||
Void,
|
||||
Label(Label),
|
||||
Link(Label, FnPathByContext),
|
||||
LinkBlank(Label, FnPathByContext),
|
||||
Dropdown(Typed<Dropdown>),
|
||||
}
|
||||
|
||||
// Item.
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Item {
|
||||
item_type: ItemType,
|
||||
}
|
||||
|
||||
impl Component for Item {
|
||||
fn new() -> Self {
|
||||
Item::default()
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let description: Option<String> = None;
|
||||
|
||||
// Obtiene la URL actual desde `cx.request`.
|
||||
let current_path = cx.request().map(|request| request.path());
|
||||
|
||||
match self.item_type() {
|
||||
ItemType::Void => PrepareMarkup::None,
|
||||
ItemType::Label(label) => PrepareMarkup::With(html! {
|
||||
li class="nav-item" {
|
||||
span title=[description] {
|
||||
//(left_icon)
|
||||
(label.using(cx))
|
||||
//(right_icon)
|
||||
}
|
||||
}
|
||||
}),
|
||||
ItemType::Link(label, path) => {
|
||||
let item_path = path(cx);
|
||||
let (class, aria) = if current_path == Some(item_path) {
|
||||
("nav-item active", Some("page"))
|
||||
} else {
|
||||
("nav-item", None)
|
||||
};
|
||||
PrepareMarkup::With(html! {
|
||||
li class=(class) aria-current=[aria] {
|
||||
a class="nav-link" href=(item_path) title=[description] {
|
||||
//(left_icon)
|
||||
(label.using(cx))
|
||||
//(right_icon)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
ItemType::LinkBlank(label, path) => {
|
||||
let item_path = path(cx);
|
||||
let (class, aria) = if current_path == Some(item_path) {
|
||||
("nav-item active", Some("page"))
|
||||
} else {
|
||||
("nav-item", None)
|
||||
};
|
||||
PrepareMarkup::With(html! {
|
||||
li class=(class) aria-current=[aria] {
|
||||
a class="nav-link" href=(item_path) title=[description] target="_blank" {
|
||||
//(left_icon)
|
||||
(label.using(cx))
|
||||
//(right_icon)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
ItemType::Dropdown(menu) => PrepareMarkup::With(html! { (menu.render(cx)) }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn label(label: L10n) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Label(label),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link(label: L10n, path: FnPathByContext) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::Link(label, path),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link_blank(label: L10n, path: FnPathByContext) -> Self {
|
||||
Item {
|
||||
item_type: ItemType::LinkBlank(label, path),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// Item GETTERS.
|
||||
|
||||
pub fn item_type(&self) -> &ItemType {
|
||||
&self.item_type
|
||||
}
|
||||
}
|
||||
75
extensions/pagetop-bootsier/src/theme/navbar/nav.rs
Normal file
75
extensions/pagetop-bootsier/src/theme/navbar/nav.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::theme::navbar;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Nav {
|
||||
id : AttrId,
|
||||
classes: AttrClasses,
|
||||
items : Children,
|
||||
}
|
||||
|
||||
impl Component for Nav {
|
||||
fn new() -> Self {
|
||||
Nav::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.alter_classes(ClassesOp::Prepend, "navbar-nav");
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let items = self.items().render(cx);
|
||||
if items.is_empty() {
|
||||
return PrepareMarkup::None;
|
||||
}
|
||||
|
||||
PrepareMarkup::With(html! {
|
||||
ul id=[self.id()] class=[self.classes().get()] {
|
||||
(items)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Nav {
|
||||
// Nav BUILDER.
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||
self.classes.alter_value(op, classes);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_item(mut self, item: navbar::Item) -> Self {
|
||||
self.items.add(Child::with(item));
|
||||
self
|
||||
}
|
||||
|
||||
#[builder_fn]
|
||||
pub fn with_items(mut self, op: TypedOp<navbar::Item>) -> Self {
|
||||
self.items.alter_typed(op);
|
||||
self
|
||||
}
|
||||
|
||||
// Nav GETTERS.
|
||||
|
||||
pub fn classes(&self) -> &AttrClasses {
|
||||
&self.classes
|
||||
}
|
||||
|
||||
pub fn items(&self) -> &Children {
|
||||
&self.items
|
||||
}
|
||||
}
|
||||
302
extensions/pagetop-bootsier/src/theme/offcanvas.rs
Normal file
302
extensions/pagetop-bootsier/src/theme/offcanvas.rs
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::LOCALES_BOOTSIER;
|
||||
|
||||
use std::fmt;
|
||||
|
||||
// **< OffcanvasPlacement >*************************************************************************
|
||||
|
||||
/// Posición de aparición del panel **deslizante** ([`Offcanvas`]).
|
||||
///
|
||||
/// Define desde qué borde de la ventana entra y se ancla el panel.
|
||||
#[derive(AutoDefault)]
|
||||
pub enum OffcanvasPlacement {
|
||||
/// Opción por defecto, desde el borde inicial según dirección de lectura (respetando LTR/RTL).
|
||||
#[default]
|
||||
Start,
|
||||
/// Desde el borde final según dirección de lectura (respetando LTR/RTL).
|
||||
End,
|
||||
/// Desde la parte superior.
|
||||
Top,
|
||||
/// Desde la parte inferior.
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
impl fmt::Display for OffcanvasPlacement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
OffcanvasPlacement::Start => f.write_str("offcanvas-start"),
|
||||
OffcanvasPlacement::End => f.write_str("offcanvas-end"),
|
||||
OffcanvasPlacement::Top => f.write_str("offcanvas-top"),
|
||||
OffcanvasPlacement::Bottom => f.write_str("offcanvas-bottom"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< OffcanvasVisibility >************************************************************************
|
||||
|
||||
/// Estado inicial del panel ([`Offcanvas`]).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum OffcanvasVisibility {
|
||||
/// El panel **permanece oculto** desde el principio.
|
||||
#[default]
|
||||
Default,
|
||||
/// El panel **se muestra abierto** al cargar.
|
||||
Show,
|
||||
}
|
||||
|
||||
impl fmt::Display for OffcanvasVisibility {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
OffcanvasVisibility::Default => Ok(()),
|
||||
OffcanvasVisibility::Show => f.write_str("show"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **< OffcanvasBodyScroll >************************************************************************
|
||||
|
||||
/// Controla si la página principal puede **desplazarse** al abrir el panel ([`Offcanvas`]).
|
||||
#[derive(AutoDefault)]
|
||||
pub enum OffcanvasBodyScroll {
|
||||
/// Opción por defecto, la página principal se **bloquea** centrando la interacción en el panel.
|
||||
#[default]
|
||||
Disabled,
|
||||
/// **Permite** el desplazamiento de la página principal.
|
||||
Enabled,
|
||||
}
|
||||
|
||||
// **< OffcanvasBackdrop >**************************************************************************
|
||||
|
||||
/// Comportamiento de la **capa de fondo** (*backdrop*) del panel ([`Offcanvas`]) al desplegarse.
|
||||
#[derive(AutoDefault)]
|
||||
pub enum OffcanvasBackdrop {
|
||||
/// **Sin capa** de fondo; la página principal permanece visible e interactiva.
|
||||
Disabled,
|
||||
/// Opción por defecto, se **oscurece** el fondo; un clic fuera del panel suele cerrarlo.
|
||||
#[default]
|
||||
Enabled,
|
||||
/// Se muestra capa de fondo pero **no** se cierra al pulsar fuera (útil cuando se requiere
|
||||
/// completar una acción antes de salir).
|
||||
Static,
|
||||
}
|
||||
|
||||
// **< Offcanvas >**********************************************************************************
|
||||
|
||||
/// Panel lateral **deslizante** para contenido complementario.
|
||||
///
|
||||
/// Útil para navegación, filtros, formularios o menús contextuales. Incluye las siguientes
|
||||
/// características principales:
|
||||
///
|
||||
/// - **Entrada configurable desde un borde** de la ventana.
|
||||
/// - **Encabezado con título** y **botón de cierre** integrado.
|
||||
/// - **Accesibilidad**: asocia título y controles a un identificador único y expone atributos
|
||||
/// adecuados para lectores de pantalla y navegación por teclado.
|
||||
/// - **Opcionalmente** bloquea el desplazamiento del documento y/o muestra una capa de fondo para
|
||||
/// centrar la atención del usuario.
|
||||
/// - **Responsive**: puede cambiar su comportamiento según el punto de ruptura indicado.
|
||||
/// - **No se renderiza** si no tiene contenido.
|
||||
#[rustfmt::skip]
|
||||
#[derive(AutoDefault)]
|
||||
pub struct Offcanvas {
|
||||
id : AttrId,
|
||||
classes : AttrClasses,
|
||||
title : L10n,
|
||||
breakpoint: BreakPoint,
|
||||
placement : OffcanvasPlacement,
|
||||
visibility: OffcanvasVisibility,
|
||||
scrolling : OffcanvasBodyScroll,
|
||||
backdrop : OffcanvasBackdrop,
|
||||
children : Children,
|
||||
}
|
||||
|
||||
impl Component for Offcanvas {
|
||||
fn new() -> Self {
|
||||
Offcanvas::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.id.get()
|
||||
}
|
||||
|
||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||
self.alter_classes(
|
||||
ClassesOp::Prepend,
|
||||
[
|
||||
self.breakpoint().to_class("offcanvas"),
|
||||
self.placement().to_string(),
|
||||
self.visibility().to_string(),
|
||||
]
|
||||
.join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||
let body = self.children().render(cx);
|
||||
if body.is_empty() {
|
||||
return PrepareMarkup::None;
|
||||
}
|
||||
|
||||
let id = cx.required_id::<Self>(self.id());
|
||||
let id_label = join!(id, "-label");
|
||||
let id_target = join!("#", id);
|
||||
|
||||
let body_scroll = match self.body_scroll() {
|
||||
OffcanvasBodyScroll::Disabled => None,
|
||||
OffcanvasBodyScroll::Enabled => Some("true"),
|
||||
};
|
||||
|
||||
let backdrop = match self.backdrop() {
|
||||
OffcanvasBackdrop::Disabled => Some("false"),
|
||||
OffcanvasBackdrop::Enabled => None,
|
||||
OffcanvasBackdrop::Static => Some("static"),
|
||||
};
|
||||
|
||||
let title = self.title().using(cx);
|
||||
|
||||
PrepareMarkup::With(html! {
|
||||
div
|
||||
id=(id)
|
||||
class=[self.classes().get()]
|
||||
tabindex="-1"
|
||||
data-bs-scroll=[body_scroll]
|
||||
data-bs-backdrop=[backdrop]
|
||||
aria-labelledby=(id_label)
|
||||
{
|
||||
div class="offcanvas-header" {
|
||||
@if !title.is_empty() {
|
||||
h5 class="offcanvas-title" id=(id_label) { (title) }
|
||||
}
|
||||
button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="offcanvas"
|
||||
data-bs-target=(id_target)
|
||||
aria-label=[L10n::t("close", &LOCALES_BOOTSIER).lookup(cx)]
|
||||
{}
|
||||
}
|
||||
div class="offcanvas-body" {
|
||||
(body)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Offcanvas {
|
||||
// **< Offcanvas BUILDER >**********************************************************************
|
||||
|
||||
/// Establece el identificador único (`id`) del panel.
|
||||
#[builder_fn]
|
||||
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||
self.id.alter_value(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica la lista de clases CSS aplicadas al panel.
|
||||
#[builder_fn]
|
||||
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||
self.classes.alter_value(op, classes);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el **título** del encabezado.
|
||||
#[builder_fn]
|
||||
pub fn with_title(mut self, title: L10n) -> Self {
|
||||
self.title = title;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configura el **punto de ruptura** para activar el comportamiento responsive del panel.
|
||||
#[builder_fn]
|
||||
pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self {
|
||||
self.breakpoint = bp;
|
||||
self
|
||||
}
|
||||
|
||||
/// Indica la **posición** desde la que entra el panel.
|
||||
#[builder_fn]
|
||||
pub fn with_placement(mut self, placement: OffcanvasPlacement) -> 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: OffcanvasVisibility) -> Self {
|
||||
self.visibility = visibility;
|
||||
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: OffcanvasBodyScroll) -> Self {
|
||||
self.scrolling = scrolling;
|
||||
self
|
||||
}
|
||||
|
||||
/// Ajusta la **capa de fondo** del panel para definir su comportamiento al interactuar fuera.
|
||||
#[builder_fn]
|
||||
pub fn with_backdrop(mut self, backdrop: OffcanvasBackdrop) -> Self {
|
||||
self.backdrop = backdrop;
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un nuevo componente hijo al panel.
|
||||
pub fn add_child(mut self, child: impl Component) -> Self {
|
||||
self.children.add(Child::with(child));
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica la lista de hijos (`children`) aplicando una operación [`ChildOp`].
|
||||
#[builder_fn]
|
||||
pub fn with_children(mut self, op: ChildOp) -> Self {
|
||||
self.children.alter_child(op);
|
||||
self
|
||||
}
|
||||
|
||||
// **< Offcanvas GETTERS >**********************************************************************
|
||||
|
||||
/// Devuelve las clases CSS asociadas al panel.
|
||||
pub fn classes(&self) -> &AttrClasses {
|
||||
&self.classes
|
||||
}
|
||||
|
||||
/// Devuelve el título del panel como [`L10n`].
|
||||
pub fn title(&self) -> &L10n {
|
||||
&self.title
|
||||
}
|
||||
|
||||
/// Devuelve el punto de ruptura configurado.
|
||||
pub fn breakpoint(&self) -> &BreakPoint {
|
||||
&self.breakpoint
|
||||
}
|
||||
|
||||
/// Devuelve la posición del panel.
|
||||
pub fn placement(&self) -> &OffcanvasPlacement {
|
||||
&self.placement
|
||||
}
|
||||
|
||||
/// Devuelve el estado inicial del panel.
|
||||
pub fn visibility(&self) -> &OffcanvasVisibility {
|
||||
&self.visibility
|
||||
}
|
||||
|
||||
/// Indica si la página principal puede desplazarse mientras el panel está abierto.
|
||||
pub fn body_scroll(&self) -> &OffcanvasBodyScroll {
|
||||
&self.scrolling
|
||||
}
|
||||
|
||||
/// Devuelve la configuración de la capa de fondo.
|
||||
pub fn backdrop(&self) -> &OffcanvasBackdrop {
|
||||
&self.backdrop
|
||||
}
|
||||
|
||||
/// Devuelve la lista de hijos (`children`) del panel.
|
||||
pub fn children(&self) -> &Children {
|
||||
&self.children
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue