♻️ (bootsier): Elimina prelude para usar theme

This commit is contained in:
Manuel Cillero 2026-05-10 00:31:33 +02:00
parent bd8a34341d
commit a0805ed0fb
43 changed files with 315 additions and 348 deletions

View file

@ -96,12 +96,6 @@ pub mod config;
pub mod theme;
/// *Prelude* del tema.
pub mod prelude {
pub use crate::config::*;
pub use crate::theme::*;
}
/// Plantillas que Bootsier añade.
#[derive(AutoDefault)]
pub enum BootsierTemplate {

View file

@ -58,7 +58,7 @@ impl BorderColor {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(BorderColor::Theme(Color::Primary).to_class(), "border-primary");
/// assert_eq!(BorderColor::Subtle(Color::Warning).to_class(), "border-warning-subtle");
/// assert_eq!(BorderColor::Black.to_class(), "border-black");

View file

@ -70,7 +70,7 @@ impl BreakPoint {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let bp = BreakPoint::MD;
/// assert_eq!(bp.class_with("col", ""), "col-md");
/// assert_eq!(bp.class_with("col", "6"), "col-md-6");

View file

@ -76,7 +76,7 @@ impl ButtonColor {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(
/// ButtonColor::Background(Color::Primary).to_class(),
/// "btn-primary"
@ -132,7 +132,7 @@ impl ButtonSize {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(ButtonSize::Small.to_class(), "btn-sm");
/// assert_eq!(ButtonSize::Large.to_class(), "btn-lg");
/// assert_eq!(ButtonSize::Default.to_class(), "");

View file

@ -44,7 +44,7 @@ impl Color {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(Color::Primary.to_class(), "primary");
/// assert_eq!(Color::Danger.to_class(), "danger");
/// ```
@ -124,7 +124,7 @@ impl Opacity {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(Opacity::Opaque.class_with(""), "opacity-100");
/// assert_eq!(Opacity::Half.class_with("bg"), "bg-opacity-50");
/// assert_eq!(Opacity::SemiTransparent.class_with("text"), "text-opacity-25");
@ -156,7 +156,7 @@ impl Opacity {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(Opacity::Opaque.to_class(), "opacity-100");
/// assert_eq!(Opacity::Half.to_class(), "opacity-50");
/// assert_eq!(Opacity::Default.to_class(), "");
@ -237,7 +237,7 @@ impl ColorBg {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(ColorBg::Body.to_class(), "bg-body");
/// assert_eq!(ColorBg::Theme(Color::Primary).to_class(), "bg-primary");
/// assert_eq!(ColorBg::Subtle(Color::Warning).to_class(), "bg-warning-subtle");
@ -321,7 +321,7 @@ impl ColorText {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(ColorText::Body.to_class(), "text-body");
/// assert_eq!(ColorText::Theme(Color::Primary).to_class(), "text-primary");
/// assert_eq!(ColorText::Emphasis(Color::Danger).to_class(), "text-danger-emphasis");

View file

@ -61,7 +61,7 @@ impl ScaleSize {
/// # Ejemplo
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(ScaleSize::Auto.class_with("border"), "border");
/// assert_eq!(ScaleSize::Zero.class_with("m"), "m-0");
/// assert_eq!(ScaleSize::Three.class_with("p"), "p-3");

View file

@ -71,7 +71,7 @@ impl RoundedRadius {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(RoundedRadius::Scale2.class_with(""), "rounded-2");
/// assert_eq!(RoundedRadius::Zero.class_with("rounded-top"), "rounded-top-0");
/// assert_eq!(RoundedRadius::Scale3.class_with("rounded-top-end"), "rounded-top-end-3");
@ -103,7 +103,7 @@ impl RoundedRadius {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// assert_eq!(RoundedRadius::Default.to_class(), "rounded");
/// assert_eq!(RoundedRadius::Zero.to_class(), "rounded-0");
/// assert_eq!(RoundedRadius::Scale3.to_class(), "rounded-3");

View file

@ -19,8 +19,9 @@ use crate::theme::{ButtonAction, ButtonColor, ButtonSize};
/// # Ejemplo
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// use pagetop::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// let save = Button::submit(L10n::n("Save"))
/// .with_color(ButtonColor::Background(Color::Primary));
///

View file

@ -26,45 +26,33 @@ use crate::theme::attrs::{BorderColor, Opacity, ScaleSize, Side};
///
/// # Ejemplos
///
/// **Borde global:**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// // Borde global.
/// let b = classes::Border::with(ScaleSize::Two);
/// assert_eq!(b.to_class(), "border-2");
/// ```
///
/// **Aditivo (solo borde superior):**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// // Aditivo (sólo borde superior):
/// let b = classes::Border::default().with_side(Side::Top, ScaleSize::One);
/// assert_eq!(b.to_class(), "border-top-1");
/// ```
///
/// **Sustractivo (borde global menos el superior):**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// // Sustractivo (borde global menos el superior):
/// let b = classes::Border::new().with_side(Side::Top, ScaleSize::Zero);
/// assert_eq!(b.to_class(), "border border-top-0");
/// ```
///
/// **Ancho por lado (lado lógico inicial a 2 y final a 4):**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// // Ancho por lado (lado lógico inicial a 2 y final a 4):
/// let b = classes::Border::default()
/// .with_side(Side::Start, ScaleSize::Two)
/// .with_side(Side::End, ScaleSize::Four);
/// assert_eq!(b.to_class(), "border-end-4 border-start-2");
/// ```
///
/// **Combinado (ejemplo completo):**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// // Combinado (ejemplo completo):
/// let b = classes::Border::new() // Borde por defecto.
/// .with_side(Side::Top, ScaleSize::Zero) // Quita borde superior.
/// .with_side(Side::End, ScaleSize::Three) // Ancho 3 para el lado lógico final.
/// .with_color(BorderColor::Theme(Color::Primary))
/// .with_opacity(Opacity::Half);
///
/// assert_eq!(b.to_class(), "border border-top-0 border-end-3 border-primary border-opacity-50");
/// ```
#[rustfmt::skip]
@ -158,7 +146,7 @@ impl Border {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// // Convertir explícitamente con `From::from`:
/// let b = classes::Border::from(ScaleSize::Two);
/// assert_eq!(b.to_class(), "border-2");

View file

@ -9,7 +9,8 @@ use crate::theme::attrs::{ColorBg, ColorText, Opacity};
/// # Ejemplos
///
/// ```
/// # use pagetop_bootsier::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// // Sin clases.
/// let s = classes::Background::new();
/// assert_eq!(s.to_class(), "");
@ -90,7 +91,7 @@ impl From<(ColorBg, Opacity)> for Background {
/// # Ejemplo
///
/// ```
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into();
/// assert_eq!(s.to_class(), "bg-white bg-opacity-25");
/// ```
@ -105,7 +106,7 @@ impl From<ColorBg> for Background {
/// # Ejemplo
///
/// ```
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let s: classes::Background = ColorBg::Black.into();
/// assert_eq!(s.to_class(), "bg-black");
/// ```
@ -121,7 +122,8 @@ impl From<ColorBg> for Background {
/// # Ejemplos
///
/// ```
/// # use pagetop_bootsier::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// // Sin clases.
/// let s = classes::Text::new();
/// assert_eq!(s.to_class(), "");
@ -202,7 +204,7 @@ impl From<(ColorText, Opacity)> for Text {
/// # Ejemplo
///
/// ```
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into();
/// assert_eq!(s.to_class(), "text-danger text-opacity-100");
/// ```
@ -218,7 +220,7 @@ impl From<ColorText> for Text {
/// # Ejemplo
///
/// ```
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let s: classes::Text = ColorText::Black.into();
/// assert_eq!(s.to_class(), "text-black");
/// ```

View file

@ -10,7 +10,8 @@ use crate::theme::BreakPoint;
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// let m = classes::Margin::with(Side::Top, ScaleSize::Three);
/// assert_eq!(m.to_class(), "mt-3");
///
@ -97,7 +98,8 @@ impl Margin {
/// # Ejemplos
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// let p = classes::Padding::with(Side::LeftAndRight, ScaleSize::Two);
/// assert_eq!(p.to_class(), "px-2");
///

View file

@ -14,42 +14,30 @@ use crate::theme::attrs::RoundedRadius;
///
/// # Ejemplos
///
/// **Radio global:**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// // Radio global:
/// let r = classes::Rounded::with(RoundedRadius::Default);
/// assert_eq!(r.to_class(), "rounded");
/// ```
///
/// **Sin redondeo:**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// // Sin redondeo:
/// let r = classes::Rounded::new();
/// assert_eq!(r.to_class(), "");
/// ```
///
/// **Radio en las esquinas de un lado lógico:**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// // Radio en las esquinas de un lado lógico:
/// let r = classes::Rounded::new().with_end(RoundedRadius::Scale2);
/// assert_eq!(r.to_class(), "rounded-end-2");
/// ```
///
/// **Radio en una esquina concreta:**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// // Radio en una esquina concreta:
/// let r = classes::Rounded::new().with_top_start(RoundedRadius::Scale3);
/// assert_eq!(r.to_class(), "rounded-top-start-3");
/// ```
///
/// **Combinado (ejemplo completo):**
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// // Combinado (ejemplo completo):
/// let r = classes::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_class(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle");
/// ```
#[rustfmt::skip]

View file

@ -6,16 +6,6 @@
//! Con [`container::Width`](crate::theme::container::Width) se puede definir el ancho y el
//! comportamiento *responsive* del contenedor. También permite aplicar utilidades de estilo para el
//! fondo, texto, borde o esquinas redondeadas.
//!
//! # Ejemplo
//!
//! ```rust
//! # use pagetop::prelude::*;
//! # use pagetop_bootsier::prelude::*;
//! let main = Container::main()
//! .with_id("main-page")
//! .with_width(container::Width::From(BreakPoint::LG));
//! ```
mod props;
pub use props::{Kind, Width};

View file

@ -1,11 +1,23 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
/// Componente para crear un **contenedor de componentes**.
/// Componente para crear un **contenedor de componentes** ([`container`]).
///
/// Envuelve un contenido con la etiqueta HTML indicada por [`container::Kind`]. Sólo se renderiza
/// si existen componentes hijos (*children*).
/// Envuelve un conjunto de componentes en un contenedor establecido que se crea aplicando uno de
/// los tipos definidos en [`container::Kind`].
///
/// Si no contiene elementos, el componente **no se renderiza**.
///
/// # Ejemplo
///
/// ```rust
/// use pagetop_bootsier::theme::*;
///
/// let main = Container::main()
/// .with_id("main-page")
/// .with_width(container::Width::From(BreakPoint::LG));
/// ```
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Container {
#[getters(skip)]

View file

@ -1,4 +1,4 @@
//! Definiciones para crear menús desplegables [`Dropdown`].
//! Definiciones para crear menús desplegables ([`Dropdown`]).
//!
//! Cada [`dropdown::Item`](crate::theme::dropdown::Item) representa un elemento individual del
//! desplegable [`Dropdown`], con distintos comportamientos según su finalidad, como enlaces de
@ -6,23 +6,6 @@
//!
//! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y
//! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n).
//!
//! # Ejemplo
//!
//! ```rust
//! # use pagetop::prelude::*;
//! # use pagetop_bootsier::prelude::*;
//! let dd = Dropdown::new()
//! .with_title(L10n::n("Menu"))
//! .with_button_color(ButtonColor::Background(Color::Secondary))
//! .with_auto_close(dropdown::AutoClose::ClickableInside)
//! .with_direction(dropdown::Direction::Dropend)
//! .with_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into()))
//! .with_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into()))
//! .with_item(dropdown::Item::divider())
//! .with_item(dropdown::Item::header(L10n::n("User session")))
//! .with_item(dropdown::Item::button(L10n::n("Sign out")));
//! ```
mod props;
pub use props::{AutoClose, Direction, MenuAlign, MenuPosition};

View file

@ -1,14 +1,14 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
use crate::LOCALES_BOOTSIER;
/// Componente para crear un **menú desplegable**.
/// Componente para crear un **menú desplegable** ([`dropdown`]).
///
/// Renderiza un botón (único o desdoblado, ver [`with_button_split()`](Self::with_button_split))
/// para mostrar un menú desplegable de elementos [`dropdown::Item`], que se muestra/oculta según la
/// interacción del usuario. Admite variaciones de tamaño/color del botón, también dirección de
/// apertura, alineación o política de cierre.
/// para mostrar un menú desplegable de elementos [`dropdown::Item`], que se muestra u oculta según
/// la interacción del usuario. Admite variaciones para el tamaño y el color del botón, también para
/// la dirección de apertura, alineación o política de cierre.
///
/// Si no tiene título (ver [`with_title()`](Self::with_title)) se muestra únicamente la lista de
/// elementos sin ningún botón para interactuar.
@ -17,8 +17,25 @@ use crate::LOCALES_BOOTSIER;
/// cuenta **el título** (si no existe le asigna uno por defecto) y **la lista de elementos**; el
/// resto de propiedades no afectarán a su representación en [`Nav`].
///
/// Ver ejemplo en el módulo [`dropdown`].
/// Si no contiene elementos, el componente **no se renderiza**.
///
/// # Ejemplo
///
/// ```rust
/// use pagetop::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// let dd = Dropdown::new()
/// .with_title(L10n::n("Menu"))
/// .with_button_color(ButtonColor::Background(Color::Secondary))
/// .with_auto_close(dropdown::AutoClose::ClickableInside)
/// .with_direction(dropdown::Direction::Dropend)
/// .with_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into()))
/// .with_item(dropdown::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into()))
/// .with_item(dropdown::Item::divider())
/// .with_item(dropdown::Item::header(L10n::n("User session")))
/// .with_item(dropdown::Item::button(L10n::n("Sign out")));
/// ```
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Dropdown {
#[getters(skip)]

View file

@ -1,6 +1,6 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
// **< AutoClose >**********************************************************************************

View file

@ -1,36 +1,4 @@
//! Definiciones para crear formularios ([`Form`]).
//!
//! # Ejemplo
//!
//! ```rust
//! use pagetop::prelude::*;
//! use pagetop_bootsier::prelude::*;
//!
//! let form_login = Form::new()
//! .with_id("login")
//! .with_action("/login")
//! .with_child(
//! form::input::Field::email()
//! .with_name("email")
//! .with_label(L10n::n("Email"))
//! .with_required(true),
//! )
//! .with_child(
//! form::input::Field::password()
//! .with_name("password")
//! .with_label(L10n::n("Password"))
//! .with_required(true),
//! )
//! .with_child(
//! form::Checkbox::check()
//! .with_name("remember")
//! .with_label(L10n::n("Remember me")),
//! )
//! .with_child(
//! Button::submit(L10n::n("Sign in"))
//! .with_color(ButtonColor::Background(Color::Primary)),
//! );
//! ```
mod props;
pub use props::{Autocomplete, AutofillField, CheckboxKind, Method};

View file

@ -17,7 +17,7 @@ use pagetop::prelude::*;
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let item = form::check::Item::new("apple", L10n::n("Apple")).with_checked(true);
/// ```
#[derive(AutoDefault, Clone, Debug, Getters)]
@ -82,7 +82,7 @@ impl Item {
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let interests = form::check::Field::new()
/// .with_name("interests")
/// .with_label(L10n::n("Areas of interest"))

View file

@ -17,7 +17,7 @@ use crate::LOCALES_BOOTSIER;
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let accept_terms = form::Checkbox::check() // También sirve new() o default().
/// .with_name("terms_accepted")
/// .with_label(L10n::n("I accept the terms and conditions"))

View file

@ -2,9 +2,9 @@ use pagetop::prelude::*;
use crate::theme::form;
/// Componente para crear un **formulario**.
/// Componente para crear un **formulario** ([`form`]).
///
/// Este componente renderiza un `<form>` estándar con soporte para los atributos más habituales:
/// Este componente renderiza un formulario estándar con soporte para los atributos más habituales:
///
/// - `id`: identificador opcional del formulario.
/// - `classes`: clases CSS adicionales (p. ej. utilidades CSS).
@ -17,13 +17,33 @@ use crate::theme::form;
/// # Ejemplo
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// let search = Form::new()
/// .with_id("search")
/// .with_action("/search")
/// .with_method(form::Method::Get)
/// .with_child(form::input::Field::search().with_name("q"));
/// use pagetop::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// let form_login = Form::new()
/// .with_id("login")
/// .with_action("/login")
/// .with_child(
/// form::input::Field::email()
/// .with_name("email")
/// .with_label(L10n::n("Email"))
/// .with_required(true),
/// )
/// .with_child(
/// form::input::Field::password()
/// .with_name("password")
/// .with_label(L10n::n("Password"))
/// .with_required(true),
/// )
/// .with_child(
/// form::Checkbox::check()
/// .with_name("remember")
/// .with_label(L10n::n("Remember me")),
/// )
/// .with_child(
/// Button::submit(L10n::n("Sign in"))
/// .with_color(ButtonColor::Background(Color::Primary)),
/// );
/// ```
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Form {

View file

@ -15,7 +15,7 @@ use pagetop::prelude::*;
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let personal_data = form::Fieldset::new()
/// .with_legend(L10n::n("Personal data"))
/// .with_description(L10n::n("Enter your full name and contact email."))

View file

@ -12,7 +12,7 @@ use pagetop::prelude::*;
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let token = form::Hidden::new()
/// .with_name("csrf_token")
/// .with_value("a1b2c3d4e5");

View file

@ -106,7 +106,7 @@ impl fmt::Display for Mode {
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let email = form::input::Field::email()
/// .with_name("email")
/// .with_label(L10n::n("Email address"))

View file

@ -52,7 +52,7 @@ pub enum CheckboxKind {
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// // Correo electrónico con sugerencia semántica del navegador.
/// let ac = form::Autocomplete::email();
///
@ -244,7 +244,7 @@ impl fmt::Display for Autocomplete {
/// # Ejemplo
///
/// ```rust
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let ac = form::Autocomplete::token(form::AutofillField::Username);
/// let ac = form::Autocomplete::shipping(form::AutofillField::StreetAddress);
/// let ac = form::Autocomplete::section("job", form::AutofillField::Email);

View file

@ -16,7 +16,7 @@ use crate::LOCALES_BOOTSIER;
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let item = form::radio::Item::new("monthly", L10n::n("Monthly")).with_checked(true);
/// ```
#[derive(AutoDefault, Clone, Debug, Getters)]
@ -76,7 +76,7 @@ impl Item {
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let plan = form::radio::Field::new()
/// .with_name("plan")
/// .with_label(L10n::n("Subscription plan"))

View file

@ -10,7 +10,7 @@ use pagetop::prelude::*;
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let volume = form::Range::new()
/// .with_name("volume")
/// .with_label(L10n::n("Volume"))

View file

@ -20,7 +20,7 @@ use crate::LOCALES_BOOTSIER;
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let item = form::select::Item::new("es", L10n::n("Spanish")).with_selected(true);
/// ```
#[derive(AutoDefault, Clone, Debug, Getters)]
@ -76,7 +76,7 @@ impl Item {
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let group = form::select::Group::new(L10n::n("Europe"))
/// .with_item(form::select::Item::new("es", L10n::n("Spanish")))
/// .with_item(form::select::Item::new("fr", L10n::n("French")));
@ -149,7 +149,7 @@ pub enum Entry {
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let idioma = form::select::Field::new()
/// .with_name("language")
/// .with_label(L10n::n("Language"))

View file

@ -13,7 +13,7 @@ use crate::LOCALES_BOOTSIER;
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let descripcion = form::Textarea::new()
/// .with_name("description")
/// .with_label(L10n::n("Description"))

View file

@ -1,4 +1,4 @@
use crate::prelude::*;
use crate::theme::*;
const DEFAULT_VIEWBOX: &str = "0 0 16 16";

View file

@ -1,14 +1,16 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
/// Componente para renderizar una **imagen**.
/// Componente para renderizar una **imagen** ([`image`]).
///
/// - Ajusta su disposición según el origen definido en [`image::Source`].
/// - Permite configurar **dimensiones** ([`with_size()`](Self::with_size)), **borde**
/// A una imagen se le puede:
///
/// - Establecer su contenido a partir del origen definido en [`image::Source`].
/// - Configurar sus **dimensiones** ([`with_size()`](Self::with_size)), **borde**
/// ([`classes::Border`](crate::theme::classes::Border)) y **redondeo de esquinas**
/// ([`classes::Rounded`](crate::theme::classes::Rounded)).
/// - Resuelve el texto alternativo `alt` con **localización** mediante [`L10n`].
/// - Aplicar el texto alternativo `alt` con **localización** mediante [`L10n`].
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Image {
#[getters(skip)]

View file

@ -1,4 +1,4 @@
//! Definiciones para crear menús [`Nav`] o alguna de sus variantes de presentación.
//! Definiciones para crear menús ([`Nav`]).
//!
//! Cada [`nav::Item`](crate::theme::nav::Item) representa un elemento individual del menú [`Nav`],
//! con distintos comportamientos según su finalidad, como enlaces de navegación o menús
@ -6,26 +6,6 @@
//!
//! Los ítems pueden estar activos, deshabilitados o abrirse en nueva ventana según su contexto y
//! configuración, y permiten incluir etiquetas localizables usando [`L10n`](pagetop::locale::L10n).
//!
//! # Ejemplo
//!
//! ```rust
//! # use pagetop::prelude::*;
//! # use pagetop_bootsier::prelude::*;
//! let nav = Nav::tabs()
//! .with_layout(nav::Layout::End)
//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into()))
//! .with_item(nav::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into()))
//! .with_item(nav::Item::dropdown(
//! Dropdown::new()
//! .with_title(L10n::n("Options"))
//! .with_item(ChildOp::AddMany(vec![
//! dropdown::Item::link(L10n::n("Action"), |_| "/action".into()).into(),
//! dropdown::Item::link(L10n::n("Another"), |_| "/another".into()).into(),
//! ])),
//! ))
//! .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into()));
//! ```
mod props;
pub use props::{Kind, Layout};

View file

@ -1,15 +1,35 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
/// Componente para crear un **menú** o alguna de sus variantes ([`nav::Kind`]).
/// Componente para crear un **menú** ([`nav`]).
///
/// Presenta un menú con una lista de elementos usando una vista básica, o alguna de sus variantes
/// como *pestañas* (`Tabs`), *botones* (`Pills`) o *subrayado* (`Underline`). También permite
/// controlar su distribución y orientación ([`nav::Layout`](crate::theme::nav::Layout)).
/// ([`nav::Kind`]) como *pestañas* (`Tabs`), *botones* (`Pills`) o *subrayado* (`Underline`).
/// También permite controlar su distribución y orientación ([`nav::Layout`](crate::theme::nav::Layout)).
///
/// Ver ejemplo en el módulo [`nav`].
/// Si no contiene elementos, el componente **no se renderiza**.
///
/// # Ejemplo
///
/// ```rust
/// use pagetop::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// let nav = Nav::tabs()
/// .with_layout(nav::Layout::End)
/// .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into()))
/// .with_item(nav::Item::link_blank(L10n::n("External"), |_| "https://docs.rs".into()))
/// .with_item(nav::Item::dropdown(
/// Dropdown::new()
/// .with_title(L10n::n("Options"))
/// .with_item(ChildOp::AddMany(vec![
/// dropdown::Item::link(L10n::n("Action"), |_| "/action".into()).into(),
/// dropdown::Item::link(L10n::n("Another"), |_| "/another".into()).into(),
/// ])),
/// ))
/// .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into()));
/// ```
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Nav {
#[getters(skip)]

View file

@ -1,6 +1,6 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
use crate::LOCALES_BOOTSIER;
// **< ItemKind >***********************************************************************************

View file

@ -1,4 +1,4 @@
//! Definiciones para crear barras de navegación [`Navbar`].
//! Definiciones para crear barras de navegación ([`Navbar`]).
//!
//! Cada [`navbar::Item`](crate::theme::navbar::Item) representa un elemento individual de la barra
//! de navegación [`Navbar`], con distintos comportamientos según su finalidad, como menús
@ -6,126 +6,6 @@
//!
//! También puede mostrar una marca de identidad ([`navbar::Brand`](crate::theme::navbar::Brand))
//! que identifique la compañía, producto o nombre del proyecto asociado a la solución web.
//!
//! # Ejemplos
//!
//! Barra **simple**, sólo con un menú horizontal:
//!
//! ```rust
//! # use pagetop::prelude::*;
//! # use pagetop_bootsier::prelude::*;
//! let navbar = Navbar::simple()
//! .with_item(navbar::Item::nav(
//! Nav::new()
//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into()))
//! .with_item(nav::Item::link(L10n::n("About"), |_| "/about".into()))
//! .with_item(nav::Item::link(L10n::n("Contact"), |_| "/contact".into()))
//! ));
//! ```
//!
//! Barra **colapsable**, con botón de despliegue y contenido en el desplegable cuando colapsa:
//!
//! ```rust
//! # use pagetop::prelude::*;
//! # use pagetop_bootsier::prelude::*;
//! let navbar = Navbar::simple_toggle()
//! .with_expand(BreakPoint::MD)
//! .with_item(navbar::Item::nav(
//! Nav::new()
//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into()))
//! .with_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://docs.rs".into()))
//! .with_item(nav::Item::link(L10n::n("Support"), |_| "/support".into()))
//! ));
//! ```
//!
//! Barra con **marca de identidad a la izquierda** y menú a la derecha, típica de una cabecera:
//!
//! ```rust
//! # use pagetop::prelude::*;
//! # use pagetop_bootsier::prelude::*;
//! let brand = navbar::Brand::new()
//! .with_title(L10n::n("PageTop"))
//! .with_route(Some(|cx| cx.route("/")));
//!
//! let navbar = Navbar::brand_left(brand)
//! .with_item(navbar::Item::nav(
//! Nav::new()
//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into()))
//! .with_item(nav::Item::dropdown(
//! Dropdown::new()
//! .with_title(L10n::n("Tools"))
//! .with_item(dropdown::Item::link(
//! L10n::n("Generator"), |_| "/tools/gen".into())
//! )
//! .with_item(dropdown::Item::link(
//! L10n::n("Reports"), |_| "/tools/reports".into())
//! )
//! ))
//! .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into()))
//! ));
//! ```
//!
//! Barra con **botón de despliegue a la izquierda** y **marca de identidad a la derecha**:
//!
//! ```rust
//! # use pagetop::prelude::*;
//! # use pagetop_bootsier::prelude::*;
//! let brand = navbar::Brand::new()
//! .with_title(L10n::n("Intranet"))
//! .with_route(Some(|cx| cx.route("/")));
//!
//! let navbar = Navbar::brand_right(brand)
//! .with_expand(BreakPoint::LG)
//! .with_item(navbar::Item::nav(
//! Nav::pills()
//! .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into()))
//! .with_item(nav::Item::link(L10n::n("Users"), |_| "/users".into()))
//! ));
//! ```
//!
//! Barra con el **contenido en un *offcanvas***, ideal para dispositivos móviles o menús largos:
//!
//! ```rust
//! # use pagetop::prelude::*;
//! # use pagetop_bootsier::prelude::*;
//! let oc = Offcanvas::new()
//! .with_id("main_offcanvas")
//! .with_title(L10n::n("Main menu"))
//! .with_placement(offcanvas::Placement::Start)
//! .with_backdrop(offcanvas::Backdrop::Enabled);
//!
//! let navbar = Navbar::offcanvas(oc)
//! .with_item(navbar::Item::nav(
//! Nav::new()
//! .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into()))
//! .with_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into()))
//! .with_item(nav::Item::dropdown(
//! Dropdown::new()
//! .with_title(L10n::n("More"))
//! .with_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into()))
//! .with_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into()))
//! ))
//! ));
//! ```
//!
//! Barra **fija arriba**:
//!
//! ```rust
//! # use pagetop::prelude::*;
//! # use pagetop_bootsier::prelude::*;
//! let brand = navbar::Brand::new()
//! .with_title(L10n::n("Main App"))
//! .with_route(Some(|cx| cx.route("/")));
//!
//! let navbar = Navbar::brand_left(brand)
//! .with_position(navbar::Position::FixedTop)
//! .with_item(navbar::Item::nav(
//! Nav::new()
//! .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/".into()))
//! .with_item(nav::Item::link(L10n::n("Donors"), |_| "/donors".into()))
//! .with_item(nav::Item::link(L10n::n("Stock"), |_| "/stock".into()))
//! ));
//! ```
mod props;
pub use props::{Layout, Position};

View file

@ -1,6 +1,6 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
/// Marca de identidad para mostrar en una barra de navegación [`Navbar`].
///

View file

@ -1,19 +1,139 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
use crate::LOCALES_BOOTSIER;
const TOGGLE_COLLAPSE: &str = "collapse";
const TOGGLE_OFFCANVAS: &str = "offcanvas";
/// Componente para crear una **barra de navegación**.
/// Componente para crear una **barra de navegación** ([`navbar`]).
///
/// Permite mostrar enlaces, menús y una marca de identidad en distintas disposiciones (simples, con
/// botón de despliegue o dentro de un [`offcanvas`]), controladas por [`navbar::Layout`]. También
/// puede fijarse en la parte superior o inferior del documento mediante [`navbar::Position`].
///
/// Ver ejemplos en el módulo [`navbar`].
/// Si no contiene elementos, el componente **no se renderiza**.
///
/// # Ejemplos
///
/// Barra **simple**, sólo con un menú horizontal:
///
/// ```rust
/// use pagetop::prelude::*;
/// use pagetop_bootsier::theme::*;
///
/// let navbar = Navbar::simple()
/// .with_item(navbar::Item::nav(
/// Nav::new()
/// .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into()))
/// .with_item(nav::Item::link(L10n::n("About"), |_| "/about".into()))
/// .with_item(nav::Item::link(L10n::n("Contact"), |_| "/contact".into()))
/// ));
/// ```
///
/// Barra **colapsable**, con botón de despliegue y contenido en el desplegable cuando colapsa:
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let navbar = Navbar::simple_toggle()
/// .with_expand(BreakPoint::MD)
/// .with_item(navbar::Item::nav(
/// Nav::new()
/// .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into()))
/// .with_item(nav::Item::link_blank(L10n::n("Docs"), |_| "https://docs.rs".into()))
/// .with_item(nav::Item::link(L10n::n("Support"), |_| "/support".into()))
/// ));
/// ```
///
/// Barra con **marca de identidad a la izquierda** y menú a la derecha, típica de una cabecera:
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let brand = navbar::Brand::new()
/// .with_title(L10n::n("PageTop"))
/// .with_route(Some(|cx| cx.route("/")));
///
/// let navbar = Navbar::brand_left(brand)
/// .with_item(navbar::Item::nav(
/// Nav::new()
/// .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into()))
/// .with_item(nav::Item::dropdown(
/// Dropdown::new()
/// .with_title(L10n::n("Tools"))
/// .with_item(dropdown::Item::link(
/// L10n::n("Generator"), |_| "/tools/gen".into())
/// )
/// .with_item(dropdown::Item::link(
/// L10n::n("Reports"), |_| "/tools/reports".into())
/// )
/// ))
/// .with_item(nav::Item::link_disabled(L10n::n("Disabled"), |_| "#".into()))
/// ));
/// ```
///
/// Barra con **botón de despliegue a la izquierda** y **marca de identidad a la derecha**:
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let brand = navbar::Brand::new()
/// .with_title(L10n::n("Intranet"))
/// .with_route(Some(|cx| cx.route("/")));
///
/// let navbar = Navbar::brand_right(brand)
/// .with_expand(BreakPoint::LG)
/// .with_item(navbar::Item::nav(
/// Nav::pills()
/// .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/dashboard".into()))
/// .with_item(nav::Item::link(L10n::n("Users"), |_| "/users".into()))
/// ));
/// ```
///
/// Barra con el **contenido en un *offcanvas***, ideal para dispositivos móviles o menús largos:
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let oc = Offcanvas::new()
/// .with_id("main_offcanvas")
/// .with_title(L10n::n("Main menu"))
/// .with_placement(offcanvas::Placement::Start)
/// .with_backdrop(offcanvas::Backdrop::Enabled);
///
/// let navbar = Navbar::offcanvas(oc)
/// .with_item(navbar::Item::nav(
/// Nav::new()
/// .with_item(nav::Item::link(L10n::n("Home"), |_| "/".into()))
/// .with_item(nav::Item::link(L10n::n("Profile"), |_| "/profile".into()))
/// .with_item(nav::Item::dropdown(
/// Dropdown::new()
/// .with_title(L10n::n("More"))
/// .with_item(dropdown::Item::link(L10n::n("Settings"), |_| "/settings".into()))
/// .with_item(dropdown::Item::link(L10n::n("Help"), |_| "/help".into()))
/// ))
/// ));
/// ```
///
/// Barra **fija arriba**:
///
/// ```rust
/// # use pagetop::prelude::*;
/// # use pagetop_bootsier::theme::*;
/// let brand = navbar::Brand::new()
/// .with_title(L10n::n("Main App"))
/// .with_route(Some(|cx| cx.route("/")));
///
/// let navbar = Navbar::brand_left(brand)
/// .with_position(navbar::Position::FixedTop)
/// .with_item(navbar::Item::nav(
/// Nav::new()
/// .with_item(nav::Item::link(L10n::n("Dashboard"), |_| "/".into()))
/// .with_item(nav::Item::link(L10n::n("Donors"), |_| "/donors".into()))
/// .with_item(nav::Item::link(L10n::n("Stock"), |_| "/stock".into()))
/// ));
/// ```
#[derive(AutoDefault, Clone, Debug, Getters)]
pub struct Navbar {
#[getters(skip)]

View file

@ -1,6 +1,6 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
/// Elementos que puede contener una barra de navegación [`Navbar`](crate::theme::Navbar).
///

View file

@ -1,6 +1,6 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
// **< Layout >*************************************************************************************

View file

@ -1,24 +1,4 @@
//! Definiciones para crear paneles laterales deslizantes [`Offcanvas`].
//!
//! # Ejemplo
//!
//! ```rust
//! # use pagetop::prelude::*;
//! # use pagetop_bootsier::prelude::*;
//! 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()))
//! );
//! ```
//! Definiciones para crear paneles laterales deslizantes ([`Offcanvas`]).
mod props;
pub use props::{Backdrop, BodyScroll, Placement, Visibility};

View file

@ -1,9 +1,9 @@
use pagetop::prelude::*;
use crate::prelude::*;
use crate::theme::*;
use crate::LOCALES_BOOTSIER;
/// Componente para crear un **panel lateral deslizante** con contenidos adicionales.
/// Componente para crear un **panel lateral deslizante** ([`offcanvas`]).
///
/// Útil para navegación, filtros, formularios o menús contextuales. Incluye las siguientes
/// características principales:
@ -19,8 +19,28 @@ use crate::LOCALES_BOOTSIER;
/// - Asocia título y controles de accesibilidad a un identificador único y expone atributos
/// adecuados para lectores de pantalla y navegación por teclado.
///
/// Ver ejemplo en el módulo [`offcanvas`].
/// Si no contiene elementos, el componente **no se renderiza**.
///
/// # Ejemplo
///
/// ```rust
/// 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 {
#[getters(skip)]