Compare commits
No commits in common. "3e83f8e35e7581398d73225d02d4756840d1a46e" and "1a3a295f189054cc8196f7e56a5aea169d2e5c05" have entirely different histories.
3e83f8e35e
...
1a3a295f18
17 changed files with 684 additions and 775 deletions
|
|
@ -447,12 +447,12 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#intro-badges {
|
#intro-badges {
|
||||||
min-height: 3.2rem;
|
min-height: 3rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
#intro-badges img {
|
#intro-badges img {
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 1.1rem;
|
||||||
}
|
}
|
||||||
#intro-release:not([src]) {
|
#intro-release:not([src]) {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
||||||
|
|
@ -145,12 +145,12 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
// Botones de acción.
|
// Botones de acción.
|
||||||
.with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop(
|
.with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop(
|
||||||
PropsOp::add_classes(class::ButtonColor::solid(
|
PropsOp::add_classes(class::ButtonColor::solid(
|
||||||
ThemeColor::Primary,
|
token::Color::Primary,
|
||||||
)),
|
)),
|
||||||
))
|
))
|
||||||
.with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop(
|
.with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop(
|
||||||
PropsOp::add_classes(class::ButtonColor::outline(
|
PropsOp::add_classes(class::ButtonColor::outline(
|
||||||
ThemeColor::Secondary,
|
token::Color::Secondary,
|
||||||
)),
|
)),
|
||||||
))
|
))
|
||||||
.with_child(
|
.with_child(
|
||||||
|
|
@ -269,12 +269,12 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
// Botones de acción.
|
// Botones de acción.
|
||||||
.with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop(
|
.with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop(
|
||||||
PropsOp::add_classes(class::ButtonColor::solid(
|
PropsOp::add_classes(class::ButtonColor::solid(
|
||||||
ThemeColor::Primary,
|
token::Color::Primary,
|
||||||
)),
|
)),
|
||||||
))
|
))
|
||||||
.with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop(
|
.with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop(
|
||||||
PropsOp::add_classes(class::ButtonColor::outline(
|
PropsOp::add_classes(class::ButtonColor::outline(
|
||||||
ThemeColor::Secondary,
|
token::Color::Secondary,
|
||||||
)),
|
)),
|
||||||
))
|
))
|
||||||
.with_child(
|
.with_child(
|
||||||
|
|
@ -423,12 +423,12 @@ fn form_lists() -> Form {
|
||||||
// Botones de acción.
|
// Botones de acción.
|
||||||
.with_child(
|
.with_child(
|
||||||
Button::submit(L10n::t("btn_submit", &LOC)).with_prop(PropsOp::add_classes(
|
Button::submit(L10n::t("btn_submit", &LOC)).with_prop(PropsOp::add_classes(
|
||||||
class::ButtonColor::solid(ThemeColor::Primary),
|
class::ButtonColor::solid(token::Color::Primary),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
Button::reset(L10n::t("btn_reset", &LOC)).with_prop(PropsOp::add_classes(
|
Button::reset(L10n::t("btn_reset", &LOC)).with_prop(PropsOp::add_classes(
|
||||||
class::ButtonColor::outline(ThemeColor::Secondary),
|
class::ButtonColor::outline(token::Color::Secondary),
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ impl Extension for SuperMenu {
|
||||||
|
|
||||||
async fn initialize(&self) {
|
async fn initialize(&self) {
|
||||||
let navbar_menu = bs::Navbar::brand_left(bs::navbar::Brand::new())
|
let navbar_menu = bs::Navbar::brand_left(bs::navbar::Brand::new())
|
||||||
.with_expand(BreakPoint::LG)
|
.with_expand(token::BreakPoint::LG)
|
||||||
.with_item(bs::navbar::Item::nav(
|
.with_item(bs::navbar::Item::nav(
|
||||||
bs::Nav::new()
|
bs::Nav::new()
|
||||||
.with_item(bs::nav::Item::link(L10n::t("menus_item_link", &LOC), "/"))
|
.with_item(bs::nav::Item::link(L10n::t("menus_item_link", &LOC), "/"))
|
||||||
|
|
@ -89,8 +89,8 @@ impl Extension for SuperMenu {
|
||||||
.with_item(bs::navbar::Item::nav(
|
.with_item(bs::navbar::Item::nav(
|
||||||
bs::Nav::new()
|
bs::Nav::new()
|
||||||
.with_prop(PropsOp::add_classes(class::Margin::with(
|
.with_prop(PropsOp::add_classes(class::Margin::with(
|
||||||
BoxSide::Start,
|
token::Side::Start,
|
||||||
ScaleSize::Auto,
|
token::ScaleSize::Auto,
|
||||||
)))
|
)))
|
||||||
.with_item(bs::nav::Item::link(
|
.with_item(bs::nav::Item::link(
|
||||||
L10n::t("menus_item_sign_up", &LOC),
|
L10n::t("menus_item_sign_up", &LOC),
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,16 @@
|
||||||
//! Define clases para aplicar en componentes del tema.
|
//! Define clases para aplicar en componentes del tema.
|
||||||
//!
|
|
||||||
//! Incluyen puntos de ruptura, colores y niveles de opacidad, escalas de tamaño y lados, necesarios
|
|
||||||
//! para crear determinadas clases del tema:
|
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
|
||||||
//! use pagetop_bootsier::theme::*;
|
|
||||||
//!
|
|
||||||
//! let bg = class::Bg::with(ThemeColor::Primary);
|
|
||||||
//! let border = class::Border::new()
|
|
||||||
//! .with_side(BoxSide::Top, ScaleSize::Zero)
|
|
||||||
//! .with_color(ThemeColor::Danger);
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
mod color;
|
mod color;
|
||||||
pub use color::{Bg, BgColor};
|
pub use color::{Background, Text};
|
||||||
pub use color::{Text, TextColor};
|
|
||||||
|
|
||||||
mod button;
|
mod button;
|
||||||
pub use button::{ButtonColor, ButtonColorStyle, ButtonSize, ButtonSizeKind};
|
pub use button::{ButtonColor, ButtonSize};
|
||||||
|
|
||||||
mod border;
|
mod border;
|
||||||
pub use border::{Border, BorderColor};
|
pub use border::Border;
|
||||||
|
|
||||||
mod rounded;
|
mod rounded;
|
||||||
pub use rounded::{Rounded, RoundedRadius};
|
pub use rounded::Rounded;
|
||||||
|
|
||||||
mod layout;
|
mod layout;
|
||||||
pub use layout::{Margin, Padding};
|
pub use layout::{Margin, Padding};
|
||||||
|
|
|
||||||
|
|
@ -1,114 +1,6 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::{BoxSide, OpacityLevel, ScaleSize, ThemeColor};
|
use crate::theme::token::{ColorBorder, Opacity, ScaleSize, Side};
|
||||||
|
|
||||||
// **< BorderColor >********************************************************************************
|
|
||||||
|
|
||||||
/// Esquema de color para los bordes ([`Border`]).
|
|
||||||
///
|
|
||||||
/// - `Solid(ThemeColor)` y `Subtle(ThemeColor)` usan la paleta de colores temáticos
|
|
||||||
/// ([`ThemeColor`]).
|
|
||||||
/// - `Black` y `White` son colores fijos independientes del tema.
|
|
||||||
/// - `Default` no genera ninguna clase.
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum BorderColor {
|
|
||||||
/// No define ninguna clase.
|
|
||||||
#[default]
|
|
||||||
Default,
|
|
||||||
/// Genera la clase `border-{color}`.
|
|
||||||
Solid(ThemeColor),
|
|
||||||
/// Genera la clase `border-{color}-subtle` (un tono suavizado del color).
|
|
||||||
Subtle(ThemeColor),
|
|
||||||
/// Color negro.
|
|
||||||
Black,
|
|
||||||
/// Color blanco.
|
|
||||||
White,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BorderColor {
|
|
||||||
// Devuelve el sufijo de la clase `border-*`, o `None` si no define ninguna clase.
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[inline]
|
|
||||||
const fn suffix(self) -> Option<&'static str> {
|
|
||||||
match self {
|
|
||||||
Self::Default => None,
|
|
||||||
Self::Solid(_) => Some(""),
|
|
||||||
Self::Subtle(_) => Some("-subtle"),
|
|
||||||
Self::Black => Some("-black"),
|
|
||||||
Self::White => Some("-white"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Añade la clase `border-*` a la cadena de clases.
|
|
||||||
#[inline]
|
|
||||||
pub fn push_to(self, classes: &mut String) {
|
|
||||||
if let Some(suffix) = self.suffix() {
|
|
||||||
if !classes.is_empty() {
|
|
||||||
classes.push(' ');
|
|
||||||
}
|
|
||||||
match self {
|
|
||||||
Self::Solid(c) | Self::Subtle(c) => {
|
|
||||||
classes.push_str("border-");
|
|
||||||
classes.push_str(c.as_str());
|
|
||||||
}
|
|
||||||
_ => classes.push_str("border"),
|
|
||||||
}
|
|
||||||
classes.push_str(suffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la clase `border-*` correspondiente al color de borde.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop_bootsier::theme::*;
|
|
||||||
/// let solid = class::BorderColor::Solid(ThemeColor::Primary).to_class();
|
|
||||||
/// assert_eq!(solid, "border-primary");
|
|
||||||
///
|
|
||||||
/// let subtle = class::BorderColor::Subtle(ThemeColor::Warning).to_class();
|
|
||||||
/// assert_eq!(subtle, "border-warning-subtle");
|
|
||||||
///
|
|
||||||
/// let black = class::BorderColor::Black.to_class();
|
|
||||||
/// assert_eq!(black, "border-black");
|
|
||||||
///
|
|
||||||
/// let none = class::BorderColor::Default.to_class();
|
|
||||||
/// assert_eq!(none, "");
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub fn to_class(self) -> String {
|
|
||||||
let mut class = String::new();
|
|
||||||
self.push_to(&mut class);
|
|
||||||
class
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ThemeColor> for BorderColor {
|
|
||||||
/// Convierte un [`ThemeColor`] en [`BorderColor::Solid`].
|
|
||||||
///
|
|
||||||
/// Es el atajo habitual para los colores temáticos. Para los demás esquemas (`Subtle`, `Black`,
|
|
||||||
/// `White`) sigue usando [`BorderColor`].
|
|
||||||
///
|
|
||||||
/// # Ejemplo
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop_bootsier::theme::*;
|
|
||||||
/// let border: class::BorderColor = ThemeColor::Success.into();
|
|
||||||
/// assert_eq!(border.to_class(), "border-success");
|
|
||||||
/// ```
|
|
||||||
fn from(color: ThemeColor) -> Self {
|
|
||||||
Self::Solid(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<CowStr> for BorderColor {
|
|
||||||
/// Permite pasar [`BorderColor`] directamente a [`PropsOp`](pagetop::prelude::PropsOp).
|
|
||||||
fn into(self) -> CowStr {
|
|
||||||
self.to_class().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Border >*************************************************************************************
|
|
||||||
|
|
||||||
/// Clases para definir **bordes**.
|
/// Clases para definir **bordes**.
|
||||||
///
|
///
|
||||||
|
|
@ -118,19 +10,19 @@ impl Into<CowStr> for BorderColor {
|
||||||
/// - Crear un borde con tamaño por defecto (`Border::new()`).
|
/// - Crear un borde con tamaño por defecto (`Border::new()`).
|
||||||
/// - Ajustar el tamaño de cada **lado lógico** (`side`, respetando LTR/RTL).
|
/// - Ajustar el tamaño de cada **lado lógico** (`side`, respetando LTR/RTL).
|
||||||
/// - Asignar **un tamaño global** para todo el borde (`size`).
|
/// - Asignar **un tamaño global** para todo el borde (`size`).
|
||||||
/// - Aplicar un **color** al borde (`BorderColor`).
|
/// - Aplicar un **color** al borde (`ColorBorder`).
|
||||||
/// - Aplicar un nivel de **opacidad** (`OpacityLevel`).
|
/// - Aplicar un nivel de **opacidad** (`Opacity`).
|
||||||
///
|
///
|
||||||
/// # Comportamiento aditivo / sustractivo
|
/// # Comportamiento aditivo / sustractivo
|
||||||
///
|
///
|
||||||
/// - **Aditivo**: basta con crear un borde sin tamaño con `class::Border::default()` para ir
|
/// - **Aditivo**: basta con crear un borde sin tamaño con `class::Border::default()` para ir
|
||||||
/// añadiendo cada lado lógico con el tamaño deseado usando `ScaleSize::{One..Five}`.
|
/// añadiendo cada lado lógico con el tamaño deseado usando `token::ScaleSize::{One..Five}`.
|
||||||
///
|
///
|
||||||
/// - **Sustractivo**: se crea un borde con tamaño predefinido, por ejemplo usando
|
/// - **Sustractivo**: se crea un borde con tamaño predefinido, por ejemplo usando
|
||||||
/// `class::Border::new()` o `class::Border::with(ScaleSize::Two)` y eliminar los lados deseados
|
/// `class::Border::new()` o `class::Border::with(token::ScaleSize::Two)` y eliminar los lados
|
||||||
/// con `ScaleSize::Zero`.
|
/// deseados con `token::ScaleSize::Zero`.
|
||||||
///
|
///
|
||||||
/// - **Anchos diferentes por lado**: usando `ScaleSize::{Zero..Five}` en cada lado deseado.
|
/// - **Anchos diferentes por lado**: usando `token::ScaleSize::{Zero..Five}` en cada lado deseado.
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
|
|
@ -138,29 +30,29 @@ impl Into<CowStr> for BorderColor {
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// // Borde global.
|
/// // Borde global.
|
||||||
/// let b = class::Border::with(ScaleSize::Two);
|
/// let b = class::Border::with(token::ScaleSize::Two);
|
||||||
/// assert_eq!(b.to_class(), "border-2");
|
/// assert_eq!(b.to_class(), "border-2");
|
||||||
///
|
///
|
||||||
/// // Aditivo (sólo borde superior):
|
/// // Aditivo (sólo borde superior):
|
||||||
/// let b = class::Border::default().with_side(BoxSide::Top, ScaleSize::One);
|
/// let b = class::Border::default().with_side(token::Side::Top, token::ScaleSize::One);
|
||||||
/// assert_eq!(b.to_class(), "border-top-1");
|
/// assert_eq!(b.to_class(), "border-top-1");
|
||||||
///
|
///
|
||||||
/// // Sustractivo (borde global menos el superior):
|
/// // Sustractivo (borde global menos el superior):
|
||||||
/// let b = class::Border::new().with_side(BoxSide::Top, ScaleSize::Zero);
|
/// let b = class::Border::new().with_side(token::Side::Top, token::ScaleSize::Zero);
|
||||||
/// assert_eq!(b.to_class(), "border border-top-0");
|
/// assert_eq!(b.to_class(), "border border-top-0");
|
||||||
///
|
///
|
||||||
/// // Ancho por lado (lado lógico inicial a 2 y final a 4):
|
/// // Ancho por lado (lado lógico inicial a 2 y final a 4):
|
||||||
/// let b = class::Border::default()
|
/// let b = class::Border::default()
|
||||||
/// .with_side(BoxSide::Start, ScaleSize::Two)
|
/// .with_side(token::Side::Start, token::ScaleSize::Two)
|
||||||
/// .with_side(BoxSide::End, ScaleSize::Four);
|
/// .with_side(token::Side::End, token::ScaleSize::Four);
|
||||||
/// assert_eq!(b.to_class(), "border-end-4 border-start-2");
|
/// assert_eq!(b.to_class(), "border-end-4 border-start-2");
|
||||||
///
|
///
|
||||||
/// // Combinado (ejemplo completo):
|
/// // Combinado (ejemplo completo):
|
||||||
/// let b = class::Border::new() // Borde por defecto.
|
/// let b = class::Border::new() // Borde por defecto.
|
||||||
/// .with_side(BoxSide::Top, ScaleSize::Zero) // Quita borde superior.
|
/// .with_side(token::Side::Top, token::ScaleSize::Zero) // Quita borde superior.
|
||||||
/// .with_side(BoxSide::End, ScaleSize::Three) // Ancho 3 para lado lógico final.
|
/// .with_side(token::Side::End, token::ScaleSize::Three) // Ancho 3 para lado lógico final.
|
||||||
/// .with_color(ThemeColor::Primary)
|
/// .with_color(token::Color::Primary)
|
||||||
/// .with_opacity(OpacityLevel::Half);
|
/// .with_opacity(token::Opacity::Half);
|
||||||
/// assert_eq!(b.to_class(), "border border-top-0 border-end-3 border-primary border-opacity-50");
|
/// assert_eq!(b.to_class(), "border border-top-0 border-end-3 border-primary border-opacity-50");
|
||||||
/// ```
|
/// ```
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|
@ -171,8 +63,8 @@ pub struct Border {
|
||||||
end : ScaleSize,
|
end : ScaleSize,
|
||||||
bottom : ScaleSize,
|
bottom : ScaleSize,
|
||||||
start : ScaleSize,
|
start : ScaleSize,
|
||||||
color : BorderColor,
|
color : ColorBorder,
|
||||||
opacity: OpacityLevel,
|
opacity: Opacity,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Border {
|
impl Border {
|
||||||
|
|
@ -183,24 +75,24 @@ impl Border {
|
||||||
|
|
||||||
/// Define un borde con un tamaño global (`size`) para todos los lados.
|
/// Define un borde con un tamaño global (`size`) para todos los lados.
|
||||||
pub fn with(size: ScaleSize) -> Self {
|
pub fn with(size: ScaleSize) -> Self {
|
||||||
Self::default().with_side(BoxSide::All, size)
|
Self::default().with_side(Side::All, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Border BUILDER >*************************************************************************
|
// **< Border BUILDER >*************************************************************************
|
||||||
|
|
||||||
/// Ajusta el tamaño del borde en el lado indicado (ver [`BoxSide`](crate::theme::BoxSide)).
|
/// Ajusta el tamaño del borde en el lado indicado (ver [`Side`](crate::theme::token::Side)).
|
||||||
pub fn with_side(mut self, side: BoxSide, size: ScaleSize) -> Self {
|
pub fn with_side(mut self, side: Side, size: ScaleSize) -> Self {
|
||||||
match side {
|
match side {
|
||||||
BoxSide::All => self.all = size,
|
Side::All => self.all = size,
|
||||||
BoxSide::Top => self.top = size,
|
Side::Top => self.top = size,
|
||||||
BoxSide::Bottom => self.bottom = size,
|
Side::Bottom => self.bottom = size,
|
||||||
BoxSide::Start => self.start = size,
|
Side::Start => self.start = size,
|
||||||
BoxSide::End => self.end = size,
|
Side::End => self.end = size,
|
||||||
BoxSide::LeftAndRight => {
|
Side::LeftAndRight => {
|
||||||
self.start = size;
|
self.start = size;
|
||||||
self.end = size;
|
self.end = size;
|
||||||
}
|
}
|
||||||
BoxSide::TopAndBottom => {
|
Side::TopAndBottom => {
|
||||||
self.top = size;
|
self.top = size;
|
||||||
self.bottom = size;
|
self.bottom = size;
|
||||||
}
|
}
|
||||||
|
|
@ -210,15 +102,15 @@ impl Border {
|
||||||
|
|
||||||
/// Establece el color del borde.
|
/// Establece el color del borde.
|
||||||
///
|
///
|
||||||
/// Acepta un tipo convertible en [`BorderColor`]. Un [`ThemeColor`] se convierte
|
/// Acepta un tipo convertible en [`ColorBorder`]. Un [`Color`](crate::theme::token::Color) se
|
||||||
/// automáticamente en [`BorderColor::Solid`].
|
/// convierte automáticamente en [`ColorBorder::Theme`].
|
||||||
pub fn with_color(mut self, color: impl Into<BorderColor>) -> Self {
|
pub fn with_color(mut self, color: impl Into<ColorBorder>) -> Self {
|
||||||
self.color = color.into();
|
self.color = color.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Establece la opacidad del borde.
|
/// Establece la opacidad del borde.
|
||||||
pub fn with_opacity(mut self, opacity: OpacityLevel) -> Self {
|
pub fn with_opacity(mut self, opacity: Opacity) -> Self {
|
||||||
self.opacity = opacity;
|
self.opacity = opacity;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -258,11 +150,11 @@ impl From<ScaleSize> for Border {
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// // Convertir explícitamente con `From::from`:
|
/// // Convertir explícitamente con `From::from`:
|
||||||
/// let b = class::Border::from(ScaleSize::Two);
|
/// let b = class::Border::from(token::ScaleSize::Two);
|
||||||
/// assert_eq!(b.to_class(), "border-2");
|
/// assert_eq!(b.to_class(), "border-2");
|
||||||
///
|
///
|
||||||
/// // Convertir implícitamente con `into()`:
|
/// // Convertir implícitamente con `into()`:
|
||||||
/// let b: class::Border = ScaleSize::Auto.into();
|
/// let b: class::Border = token::ScaleSize::Auto.into();
|
||||||
/// assert_eq!(b.to_class(), "border");
|
/// assert_eq!(b.to_class(), "border");
|
||||||
/// ```
|
/// ```
|
||||||
fn from(size: ScaleSize) -> Self {
|
fn from(size: ScaleSize) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,15 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::ThemeColor;
|
use crate::theme::token::Color;
|
||||||
|
|
||||||
// **< ButtonColor >********************************************************************************
|
// **< ButtonColor >********************************************************************************
|
||||||
|
|
||||||
/// Estilo visual aplicado al color de un botón ([`ButtonColor`]).
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ButtonColorStyle {
|
enum ButtonColorStyle {
|
||||||
/// Sin clase de color (estilo por defecto del tema).
|
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
/// Botón sólido: genera la clase `btn-{color}`.
|
|
||||||
Solid,
|
Solid,
|
||||||
/// Botón con contorno: genera la clase `btn-outline-{color}`.
|
|
||||||
Outline,
|
Outline,
|
||||||
/// Botón tipo enlace: genera la clase `btn-link`.
|
|
||||||
Link,
|
Link,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,11 +23,11 @@ pub enum ButtonColorStyle {
|
||||||
///
|
///
|
||||||
/// // Botón sólido.
|
/// // Botón sólido.
|
||||||
/// let save = bs::Button::submit(L10n::n("Save"))
|
/// let save = bs::Button::submit(L10n::n("Save"))
|
||||||
/// .with_prop(PropsOp::add_classes(class::ButtonColor::solid(ThemeColor::Primary)));
|
/// .with_prop(PropsOp::add_classes(class::ButtonColor::solid(token::Color::Primary)));
|
||||||
///
|
///
|
||||||
/// // Botón con contorno.
|
/// // Botón con contorno.
|
||||||
/// let cancel = bs::Button::plain(L10n::n("Cancel"))
|
/// let cancel = bs::Button::plain(L10n::n("Cancel"))
|
||||||
/// .with_prop(PropsOp::add_classes(class::ButtonColor::outline(ThemeColor::Secondary)));
|
/// .with_prop(PropsOp::add_classes(class::ButtonColor::outline(token::Color::Secondary)));
|
||||||
///
|
///
|
||||||
/// // Botón tipo enlace.
|
/// // Botón tipo enlace.
|
||||||
/// let back = bs::Button::plain(L10n::n("Back"))
|
/// let back = bs::Button::plain(L10n::n("Back"))
|
||||||
|
|
@ -41,7 +36,7 @@ pub enum ButtonColorStyle {
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct ButtonColor {
|
pub struct ButtonColor {
|
||||||
style: ButtonColorStyle,
|
style: ButtonColorStyle,
|
||||||
color: ThemeColor,
|
color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ButtonColor {
|
impl ButtonColor {
|
||||||
|
|
@ -51,7 +46,7 @@ impl ButtonColor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Botón sólido: genera la clase `btn-{color}`.
|
/// Botón sólido: genera la clase `btn-{color}`.
|
||||||
pub fn solid(color: ThemeColor) -> Self {
|
pub fn solid(color: Color) -> Self {
|
||||||
Self {
|
Self {
|
||||||
style: ButtonColorStyle::Solid,
|
style: ButtonColorStyle::Solid,
|
||||||
color,
|
color,
|
||||||
|
|
@ -60,7 +55,7 @@ impl ButtonColor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Botón con contorno: genera la clase `btn-outline-{color}`.
|
/// Botón con contorno: genera la clase `btn-outline-{color}`.
|
||||||
pub fn outline(color: ThemeColor) -> Self {
|
pub fn outline(color: Color) -> Self {
|
||||||
Self {
|
Self {
|
||||||
style: ButtonColorStyle::Outline,
|
style: ButtonColorStyle::Outline,
|
||||||
color,
|
color,
|
||||||
|
|
@ -79,17 +74,11 @@ impl ButtonColor {
|
||||||
// **< ButtonColor BUILDER >********************************************************************
|
// **< ButtonColor BUILDER >********************************************************************
|
||||||
|
|
||||||
/// Cambia el color aplicado al botón (`btn-*` o `btn-outline-*`).
|
/// Cambia el color aplicado al botón (`btn-*` o `btn-outline-*`).
|
||||||
pub fn with_color(mut self, color: ThemeColor) -> Self {
|
pub fn with_color(mut self, color: Color) -> Self {
|
||||||
self.color = color;
|
self.color = color;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cambia el estilo aplicado al botón (sólido, contorno o enlace).
|
|
||||||
pub fn with_style(mut self, style: ButtonColorStyle) -> Self {
|
|
||||||
self.style = style;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< ButtonColor HELPERS >********************************************************************
|
// **< ButtonColor HELPERS >********************************************************************
|
||||||
|
|
||||||
/// Añade la clase `btn-*` a la cadena de clases.
|
/// Añade la clase `btn-*` a la cadena de clases.
|
||||||
|
|
@ -128,15 +117,11 @@ impl Into<CowStr> for ButtonColor {
|
||||||
|
|
||||||
// **< ButtonSize >*********************************************************************************
|
// **< ButtonSize >*********************************************************************************
|
||||||
|
|
||||||
/// Tamaño aplicado a un botón ([`ButtonSize`]).
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ButtonSizeKind {
|
enum ButtonSizeVariant {
|
||||||
/// Sin clase de tamaño (tamaño por defecto del tema).
|
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
/// Botón compacto: genera la clase `btn-sm`.
|
|
||||||
Small,
|
Small,
|
||||||
/// Botón grande: genera la clase `btn-lg`.
|
|
||||||
Large,
|
Large,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,7 +141,7 @@ pub enum ButtonSizeKind {
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct ButtonSize {
|
pub struct ButtonSize {
|
||||||
size: ButtonSizeKind,
|
size: ButtonSizeVariant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ButtonSize {
|
impl ButtonSize {
|
||||||
|
|
@ -168,34 +153,26 @@ impl ButtonSize {
|
||||||
/// Botón compacto: genera la clase `btn-sm`.
|
/// Botón compacto: genera la clase `btn-sm`.
|
||||||
pub fn small() -> Self {
|
pub fn small() -> Self {
|
||||||
Self {
|
Self {
|
||||||
size: ButtonSizeKind::Small,
|
size: ButtonSizeVariant::Small,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Botón grande: genera la clase `btn-lg`.
|
/// Botón grande: genera la clase `btn-lg`.
|
||||||
pub fn large() -> Self {
|
pub fn large() -> Self {
|
||||||
Self {
|
Self {
|
||||||
size: ButtonSizeKind::Large,
|
size: ButtonSizeVariant::Large,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< ButtonSize BUILDER >*********************************************************************
|
|
||||||
|
|
||||||
/// Cambia el tamaño aplicado al botón.
|
|
||||||
pub fn with_size(mut self, size: ButtonSizeKind) -> Self {
|
|
||||||
self.size = size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< ButtonSize HELPERS >*********************************************************************
|
// **< ButtonSize HELPERS >*********************************************************************
|
||||||
|
|
||||||
/// Añade la clase `btn-sm` o `btn-lg` a la cadena de clases.
|
/// Añade la clase `btn-sm` o `btn-lg` a la cadena de clases.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn push_to(self, classes: &mut String) {
|
pub fn push_to(self, classes: &mut String) {
|
||||||
let class = match self.size {
|
let class = match self.size {
|
||||||
ButtonSizeKind::None => return,
|
ButtonSizeVariant::None => return,
|
||||||
ButtonSizeKind::Small => "btn-sm",
|
ButtonSizeVariant::Small => "btn-sm",
|
||||||
ButtonSizeKind::Large => "btn-lg",
|
ButtonSizeVariant::Large => "btn-lg",
|
||||||
};
|
};
|
||||||
if !classes.is_empty() {
|
if !classes.is_empty() {
|
||||||
classes.push(' ');
|
classes.push(' ');
|
||||||
|
|
|
||||||
|
|
@ -1,130 +1,8 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::{OpacityLevel, ThemeColor};
|
use crate::theme::token::{ColorBg, ColorText, Opacity};
|
||||||
|
|
||||||
// **< BgColor >************************************************************************************
|
// **< Background >*********************************************************************************
|
||||||
|
|
||||||
/// Esquema de color para el fondo ([`Bg`]).
|
|
||||||
///
|
|
||||||
/// - `Body`, `BodySecondary` y `BodyTertiary` siguen el esquema del tema (claro/oscuro).
|
|
||||||
/// - `Solid(ThemeColor)` y `Subtle(ThemeColor)` usan la paleta de colores temáticos
|
|
||||||
/// ([`ThemeColor`]).
|
|
||||||
/// - `Black`, `White`, `Transparent` son colores fijos independientes del tema.
|
|
||||||
/// - `Default` no genera ninguna clase.
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum BgColor {
|
|
||||||
/// No define ninguna clase.
|
|
||||||
#[default]
|
|
||||||
Default,
|
|
||||||
/// Fondo predefinido del tema (`bg-body`).
|
|
||||||
Body,
|
|
||||||
/// Fondo predefinido del tema (`bg-body-secondary`).
|
|
||||||
BodySecondary,
|
|
||||||
/// Fondo predefinido del tema (`bg-body-tertiary`).
|
|
||||||
BodyTertiary,
|
|
||||||
/// Genera la clase `bg-{color}` (p. ej., `bg-primary`).
|
|
||||||
Solid(ThemeColor),
|
|
||||||
/// Genera la clase `bg-{color}-subtle` (un tono suavizado del color).
|
|
||||||
Subtle(ThemeColor),
|
|
||||||
/// Color negro.
|
|
||||||
Black,
|
|
||||||
/// Color blanco.
|
|
||||||
White,
|
|
||||||
/// Fondo transparente (`bg-transparent`).
|
|
||||||
Transparent,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BgColor {
|
|
||||||
// Devuelve el sufijo de la clase `bg-*`, o `None` si no define ninguna clase.
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[inline]
|
|
||||||
const fn suffix(self) -> Option<&'static str> {
|
|
||||||
match self {
|
|
||||||
Self::Default => None,
|
|
||||||
Self::Body => Some("-body"),
|
|
||||||
Self::BodySecondary => Some("-body-secondary"),
|
|
||||||
Self::BodyTertiary => Some("-body-tertiary"),
|
|
||||||
Self::Solid(_) => Some(""),
|
|
||||||
Self::Subtle(_) => Some("-subtle"),
|
|
||||||
Self::Black => Some("-black"),
|
|
||||||
Self::White => Some("-white"),
|
|
||||||
Self::Transparent => Some("-transparent"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Añade la clase de fondo `bg-*` a la cadena de clases.
|
|
||||||
#[inline]
|
|
||||||
pub fn push_to(self, classes: &mut String) {
|
|
||||||
if let Some(suffix) = self.suffix() {
|
|
||||||
if !classes.is_empty() {
|
|
||||||
classes.push(' ');
|
|
||||||
}
|
|
||||||
match self {
|
|
||||||
Self::Solid(c) | Self::Subtle(c) => {
|
|
||||||
classes.push_str("bg-");
|
|
||||||
classes.push_str(c.as_str());
|
|
||||||
}
|
|
||||||
_ => classes.push_str("bg"),
|
|
||||||
}
|
|
||||||
classes.push_str(suffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la clase `bg-*` correspondiente al fondo.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop_bootsier::theme::*;
|
|
||||||
/// let body = class::BgColor::Body.to_class();
|
|
||||||
/// assert_eq!(body, "bg-body");
|
|
||||||
///
|
|
||||||
/// let solid = class::BgColor::Solid(ThemeColor::Primary).to_class();
|
|
||||||
/// assert_eq!(solid, "bg-primary");
|
|
||||||
///
|
|
||||||
/// let subtle = class::BgColor::Subtle(ThemeColor::Warning).to_class();
|
|
||||||
/// assert_eq!(subtle, "bg-warning-subtle");
|
|
||||||
///
|
|
||||||
/// let transparent = class::BgColor::Transparent.to_class();
|
|
||||||
/// assert_eq!(transparent, "bg-transparent");
|
|
||||||
///
|
|
||||||
/// let none = class::BgColor::Default.to_class();
|
|
||||||
/// assert_eq!(none, "");
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub fn to_class(self) -> String {
|
|
||||||
let mut class = String::new();
|
|
||||||
self.push_to(&mut class);
|
|
||||||
class
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ThemeColor> for BgColor {
|
|
||||||
/// Convierte un [`ThemeColor`] en [`BgColor::Solid`].
|
|
||||||
///
|
|
||||||
/// Es el atajo habitual para los colores temáticos. Para los demás esquemas (`Body`, `Subtle`,
|
|
||||||
/// `Black`, etc.) sigue usando [`BgColor`].
|
|
||||||
///
|
|
||||||
/// # Ejemplo
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop_bootsier::theme::*;
|
|
||||||
/// let bg: class::BgColor = ThemeColor::Primary.into();
|
|
||||||
/// assert_eq!(bg.to_class(), "bg-primary");
|
|
||||||
/// ```
|
|
||||||
fn from(color: ThemeColor) -> Self {
|
|
||||||
Self::Solid(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<CowStr> for BgColor {
|
|
||||||
/// Permite pasar [`BgColor`] directamente a [`PropsOp`](pagetop::prelude::PropsOp).
|
|
||||||
fn into(self) -> CowStr {
|
|
||||||
self.to_class().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Bg >*****************************************************************************************
|
|
||||||
|
|
||||||
/// Clases para establecer **color/opacidad del fondo**.
|
/// Clases para establecer **color/opacidad del fondo**.
|
||||||
///
|
///
|
||||||
|
|
@ -134,32 +12,33 @@ impl Into<CowStr> for BgColor {
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// // Sin clases.
|
/// // Sin clases.
|
||||||
/// let s = class::Bg::new();
|
/// let s = class::Background::new();
|
||||||
/// assert_eq!(s.to_class(), "");
|
/// assert_eq!(s.to_class(), "");
|
||||||
///
|
///
|
||||||
/// // Sólo color de fondo (forma corta con ThemeColor).
|
/// // Sólo color de fondo (forma corta con Color).
|
||||||
/// let s = class::Bg::with(ThemeColor::Primary);
|
/// let s = class::Background::with(token::Color::Primary);
|
||||||
/// assert_eq!(s.to_class(), "bg-primary");
|
/// assert_eq!(s.to_class(), "bg-primary");
|
||||||
///
|
///
|
||||||
/// // Color más opacidad.
|
/// // Color más opacidad.
|
||||||
/// let s = class::Bg::with(class::BgColor::BodySecondary).with_opacity(OpacityLevel::Half);
|
/// let s = class::Background::with(token::ColorBg::BodySecondary)
|
||||||
|
/// .with_opacity(token::Opacity::Half);
|
||||||
/// assert_eq!(s.to_class(), "bg-body-secondary bg-opacity-50");
|
/// assert_eq!(s.to_class(), "bg-body-secondary bg-opacity-50");
|
||||||
///
|
///
|
||||||
/// // Usando `From<BgColor>`.
|
/// // Usando `From<ColorBg>`.
|
||||||
/// let s: class::Bg = class::BgColor::Black.into();
|
/// let s: class::Background = token::ColorBg::Black.into();
|
||||||
/// assert_eq!(s.to_class(), "bg-black");
|
/// assert_eq!(s.to_class(), "bg-black");
|
||||||
///
|
///
|
||||||
/// // Usando `From<(BgColor, OpacityLevel)>`.
|
/// // Usando `From<(ColorBg, Opacity)>`.
|
||||||
/// let s: class::Bg = (class::BgColor::White, OpacityLevel::SemiTransparent).into();
|
/// let s: class::Background = (token::ColorBg::White, token::Opacity::SemiTransparent).into();
|
||||||
/// assert_eq!(s.to_class(), "bg-white bg-opacity-25");
|
/// assert_eq!(s.to_class(), "bg-white bg-opacity-25");
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Bg {
|
pub struct Background {
|
||||||
color: BgColor,
|
color: ColorBg,
|
||||||
opacity: OpacityLevel,
|
opacity: Opacity,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bg {
|
impl Background {
|
||||||
/// Prepara un nuevo estilo para aplicar al fondo.
|
/// Prepara un nuevo estilo para aplicar al fondo.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
|
|
@ -167,41 +46,41 @@ impl Bg {
|
||||||
|
|
||||||
/// Crea un estilo fijando el color de fondo (`bg-*`).
|
/// Crea un estilo fijando el color de fondo (`bg-*`).
|
||||||
///
|
///
|
||||||
/// Acepta cualquier tipo convertible en [`BgColor`]. Un [`ThemeColor`] se convierte
|
/// Acepta cualquier tipo convertible en [`ColorBg`]. Un [`Color`](crate::theme::token::Color)
|
||||||
/// automáticamente en [`BgColor::Solid`]:
|
/// se convierte automáticamente en [`ColorBg::Theme`]:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// // Forma corta con ThemeColor:
|
/// // Forma corta con Color:
|
||||||
/// let s = class::Bg::with(ThemeColor::Primary);
|
/// let s = class::Background::with(token::Color::Primary);
|
||||||
/// assert_eq!(s.to_class(), "bg-primary");
|
/// assert_eq!(s.to_class(), "bg-primary");
|
||||||
///
|
///
|
||||||
/// // Forma explícita para variantes no temáticas:
|
/// // Forma explícita para variantes no temáticas:
|
||||||
/// let s = class::Bg::with(class::BgColor::Body);
|
/// let s = class::Background::with(token::ColorBg::Body);
|
||||||
/// assert_eq!(s.to_class(), "bg-body");
|
/// assert_eq!(s.to_class(), "bg-body");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn with(color: impl Into<BgColor>) -> Self {
|
pub fn with(color: impl Into<ColorBg>) -> Self {
|
||||||
Self::default().with_color(color)
|
Self::default().with_color(color)
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Bg BUILDER >*****************************************************************************
|
// **< Background BUILDER >*********************************************************************
|
||||||
|
|
||||||
/// Establece el color de fondo (`bg-*`).
|
/// Establece el color de fondo (`bg-*`).
|
||||||
///
|
///
|
||||||
/// Acepta cualquier tipo convertible en [`BgColor`]. Un [`ThemeColor`] se convierte
|
/// Acepta cualquier tipo convertible en [`ColorBg`]. Un [`Color`](crate::theme::token::Color)
|
||||||
/// automáticamente en [`BgColor::Solid`].
|
/// se convierte automáticamente en [`ColorBg::Theme`].
|
||||||
pub fn with_color(mut self, color: impl Into<BgColor>) -> Self {
|
pub fn with_color(mut self, color: impl Into<ColorBg>) -> Self {
|
||||||
self.color = color.into();
|
self.color = color.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Establece la opacidad del fondo (`bg-opacity-*`).
|
/// Establece la opacidad del fondo (`bg-opacity-*`).
|
||||||
pub fn with_opacity(mut self, opacity: OpacityLevel) -> Self {
|
pub fn with_opacity(mut self, opacity: Opacity) -> Self {
|
||||||
self.opacity = opacity;
|
self.opacity = opacity;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Bg HELPERS >*****************************************************************************
|
// **< Background HELPERS >*********************************************************************
|
||||||
|
|
||||||
/// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`), omitiendo
|
/// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`), omitiendo
|
||||||
/// los fragmentos vacíos.
|
/// los fragmentos vacíos.
|
||||||
|
|
@ -222,173 +101,40 @@ impl Bg {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(BgColor, OpacityLevel)> for Bg {
|
impl From<(ColorBg, Opacity)> for Background {
|
||||||
/// Crea la clase para un [`Bg`](crate::theme::class::Bg) a partir del color de fondo y la
|
/// Crea la clase para un [`Background`](crate::theme::class::Background) a partir del color de
|
||||||
/// opacidad.
|
/// fondo y la opacidad.
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let s: class::Bg = (class::BgColor::White, OpacityLevel::SemiTransparent).into();
|
/// let s: class::Background = (token::ColorBg::White, token::Opacity::SemiTransparent).into();
|
||||||
/// assert_eq!(s.to_class(), "bg-white bg-opacity-25");
|
/// assert_eq!(s.to_class(), "bg-white bg-opacity-25");
|
||||||
/// ```
|
/// ```
|
||||||
fn from((color, opacity): (BgColor, OpacityLevel)) -> Self {
|
fn from((color, opacity): (ColorBg, Opacity)) -> Self {
|
||||||
Bg::with(color).with_opacity(opacity)
|
Background::with(color).with_opacity(opacity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<BgColor> for Bg {
|
impl From<ColorBg> for Background {
|
||||||
/// Crea la clase para un [`Bg`](crate::theme::class::Bg) a partir del color de fondo.
|
/// Crea la clase para un [`Background`](crate::theme::class::Background) a partir del color de
|
||||||
|
/// fondo.
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let s: class::Bg = class::BgColor::Black.into();
|
/// let s: class::Background = token::ColorBg::Black.into();
|
||||||
/// assert_eq!(s.to_class(), "bg-black");
|
/// assert_eq!(s.to_class(), "bg-black");
|
||||||
/// ```
|
/// ```
|
||||||
fn from(color: BgColor) -> Self {
|
fn from(color: ColorBg) -> Self {
|
||||||
Bg::with(color)
|
Background::with(color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<CowStr> for Bg {
|
impl Into<CowStr> for Background {
|
||||||
/// Permite pasar [`Bg`] directamente a [`PropsOp`](pagetop::prelude::PropsOp).
|
/// Permite pasar [`Background`] directamente a [`PropsOp`](pagetop::prelude::PropsOp).
|
||||||
fn into(self) -> CowStr {
|
|
||||||
self.to_class().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< TextColor >**********************************************************************************
|
|
||||||
|
|
||||||
/// Esquema de color para el texto ([`Text`]).
|
|
||||||
///
|
|
||||||
/// - `Body`, `BodyEmphasis`, `BodySecondary` y `BodyTertiary` siguen el tema (claro/oscuro).
|
|
||||||
/// - `Solid(ThemeColor)` y `Emphasis(ThemeColor)` usan la paleta de colores temáticos
|
|
||||||
/// ([`ThemeColor`]).
|
|
||||||
/// - `Bg(ThemeColor)` genera la utilidad combinada `text-bg-{color}` (fondo más un color de texto
|
|
||||||
/// de contraste garantizado; no es una utilidad puramente de texto).
|
|
||||||
/// - `Black` y `White` son colores fijos independientes del tema.
|
|
||||||
/// - `Default` no genera ninguna clase.
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum TextColor {
|
|
||||||
/// No define ninguna clase.
|
|
||||||
#[default]
|
|
||||||
Default,
|
|
||||||
/// Color predefinido del tema (`text-body`).
|
|
||||||
Body,
|
|
||||||
/// Color de mayor contraste según el tema (`text-body-emphasis`).
|
|
||||||
BodyEmphasis,
|
|
||||||
/// Color predefinido del tema (`text-body-secondary`).
|
|
||||||
BodySecondary,
|
|
||||||
/// Color predefinido del tema (`text-body-tertiary`).
|
|
||||||
BodyTertiary,
|
|
||||||
/// Genera la clase `text-{color}`.
|
|
||||||
Solid(ThemeColor),
|
|
||||||
/// Genera la clase `text-{color}-emphasis` (mayor contraste acorde al tema).
|
|
||||||
Emphasis(ThemeColor),
|
|
||||||
/// Genera la clase `text-bg-{color}` (fondo con color de texto de contraste garantizado).
|
|
||||||
Bg(ThemeColor),
|
|
||||||
/// Color negro.
|
|
||||||
Black,
|
|
||||||
/// Color blanco.
|
|
||||||
White,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextColor {
|
|
||||||
// Devuelve el sufijo de la clase `text-*`, o `None` si no define ninguna clase.
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[inline]
|
|
||||||
const fn suffix(self) -> Option<&'static str> {
|
|
||||||
match self {
|
|
||||||
Self::Default => None,
|
|
||||||
Self::Body => Some("-body"),
|
|
||||||
Self::BodyEmphasis => Some("-body-emphasis"),
|
|
||||||
Self::BodySecondary => Some("-body-secondary"),
|
|
||||||
Self::BodyTertiary => Some("-body-tertiary"),
|
|
||||||
Self::Solid(_) => Some(""),
|
|
||||||
Self::Emphasis(_) => Some("-emphasis"),
|
|
||||||
Self::Bg(_) => Some(""),
|
|
||||||
Self::Black => Some("-black"),
|
|
||||||
Self::White => Some("-white"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Añade la clase de texto `text-*` a la cadena de clases.
|
|
||||||
#[inline]
|
|
||||||
pub fn push_to(self, classes: &mut String) {
|
|
||||||
if let Some(suffix) = self.suffix() {
|
|
||||||
if !classes.is_empty() {
|
|
||||||
classes.push(' ');
|
|
||||||
}
|
|
||||||
match self {
|
|
||||||
Self::Solid(c) | Self::Emphasis(c) => {
|
|
||||||
classes.push_str("text-");
|
|
||||||
classes.push_str(c.as_str());
|
|
||||||
}
|
|
||||||
Self::Bg(c) => {
|
|
||||||
classes.push_str("text-bg-");
|
|
||||||
classes.push_str(c.as_str());
|
|
||||||
}
|
|
||||||
_ => classes.push_str("text"),
|
|
||||||
}
|
|
||||||
classes.push_str(suffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la clase `text-*` correspondiente al color del texto.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop_bootsier::theme::*;
|
|
||||||
/// let body = class::TextColor::Body.to_class();
|
|
||||||
/// assert_eq!(body, "text-body");
|
|
||||||
///
|
|
||||||
/// let solid = class::TextColor::Solid(ThemeColor::Primary).to_class();
|
|
||||||
/// assert_eq!(solid, "text-primary");
|
|
||||||
///
|
|
||||||
/// let emphasis = class::TextColor::Emphasis(ThemeColor::Danger).to_class();
|
|
||||||
/// assert_eq!(emphasis, "text-danger-emphasis");
|
|
||||||
///
|
|
||||||
/// let bg = class::TextColor::Bg(ThemeColor::Secondary).to_class();
|
|
||||||
/// assert_eq!(bg, "text-bg-secondary");
|
|
||||||
///
|
|
||||||
/// let black = class::TextColor::Black.to_class();
|
|
||||||
/// assert_eq!(black, "text-black");
|
|
||||||
///
|
|
||||||
/// let none = class::TextColor::Default.to_class();
|
|
||||||
/// assert_eq!(none, "");
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub fn to_class(self) -> String {
|
|
||||||
let mut class = String::new();
|
|
||||||
self.push_to(&mut class);
|
|
||||||
class
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ThemeColor> for TextColor {
|
|
||||||
/// Convierte un [`ThemeColor`] en [`TextColor::Solid`].
|
|
||||||
///
|
|
||||||
/// Es el atajo habitual para los colores temáticos. Para los demás esquemas (`Body`,
|
|
||||||
/// `Emphasis`, `Black`, etc.) sigue usando [`TextColor`].
|
|
||||||
///
|
|
||||||
/// # Ejemplo
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop_bootsier::theme::*;
|
|
||||||
/// let text: class::TextColor = ThemeColor::Danger.into();
|
|
||||||
/// assert_eq!(text.to_class(), "text-danger");
|
|
||||||
/// ```
|
|
||||||
fn from(color: ThemeColor) -> Self {
|
|
||||||
Self::Solid(color)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<CowStr> for TextColor {
|
|
||||||
/// Permite pasar [`TextColor`] directamente a [`PropsOp`](pagetop::prelude::PropsOp).
|
|
||||||
fn into(self) -> CowStr {
|
fn into(self) -> CowStr {
|
||||||
self.to_class().into()
|
self.to_class().into()
|
||||||
}
|
}
|
||||||
|
|
@ -407,30 +153,30 @@ impl Into<CowStr> for TextColor {
|
||||||
/// let s = class::Text::new();
|
/// let s = class::Text::new();
|
||||||
/// assert_eq!(s.to_class(), "");
|
/// assert_eq!(s.to_class(), "");
|
||||||
///
|
///
|
||||||
/// // Sólo color del texto (forma corta con ThemeColor).
|
/// // Sólo color del texto (forma corta con Color).
|
||||||
/// let s = class::Text::with(ThemeColor::Primary);
|
/// let s = class::Text::with(token::Color::Primary);
|
||||||
/// assert_eq!(s.to_class(), "text-primary");
|
/// assert_eq!(s.to_class(), "text-primary");
|
||||||
///
|
///
|
||||||
/// // Color del texto y opacidad.
|
/// // Color del texto y opacidad.
|
||||||
/// let s = class::Text::new().with_color(class::TextColor::White)
|
/// let s = class::Text::new().with_color(token::ColorText::White)
|
||||||
/// .with_opacity(OpacityLevel::SemiTransparent);
|
/// .with_opacity(token::Opacity::SemiTransparent);
|
||||||
/// assert_eq!(s.to_class(), "text-white text-opacity-25");
|
/// assert_eq!(s.to_class(), "text-white text-opacity-25");
|
||||||
///
|
///
|
||||||
/// // Usando `From<TextColor>`.
|
/// // Usando `From<ColorText>`.
|
||||||
/// let s: class::Text = class::TextColor::Black.into();
|
/// let s: class::Text = token::ColorText::Black.into();
|
||||||
/// assert_eq!(s.to_class(), "text-black");
|
/// assert_eq!(s.to_class(), "text-black");
|
||||||
///
|
///
|
||||||
/// // Usando `From<(TextColor, OpacityLevel)>`.
|
/// // Usando `From<(ColorText, Opacity)>`.
|
||||||
/// let s: class::Text = (
|
/// let s: class::Text = (
|
||||||
/// class::TextColor::Solid(ThemeColor::Danger),
|
/// token::ColorText::Theme(token::Color::Danger),
|
||||||
/// OpacityLevel::Opaque,
|
/// token::Opacity::Opaque,
|
||||||
/// ).into();
|
/// ).into();
|
||||||
/// assert_eq!(s.to_class(), "text-danger text-opacity-100");
|
/// assert_eq!(s.to_class(), "text-danger text-opacity-100");
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
color: TextColor,
|
color: ColorText,
|
||||||
opacity: OpacityLevel,
|
opacity: Opacity,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Text {
|
impl Text {
|
||||||
|
|
@ -441,20 +187,20 @@ impl Text {
|
||||||
|
|
||||||
/// Crea un estilo fijando el color del texto (`text-*`).
|
/// Crea un estilo fijando el color del texto (`text-*`).
|
||||||
///
|
///
|
||||||
/// Acepta cualquier tipo convertible en [`TextColor`]. Un [`ThemeColor`] se convierte
|
/// Acepta cualquier tipo convertible en [`ColorText`]. Un [`Color`](crate::theme::token::Color)
|
||||||
/// automáticamente en [`TextColor::Solid`]:
|
/// se convierte automáticamente en [`ColorText::Theme`]:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// // Forma corta con ThemeColor:
|
/// // Forma corta con Color:
|
||||||
/// let s = class::Text::with(ThemeColor::Danger);
|
/// let s = class::Text::with(token::Color::Danger);
|
||||||
/// assert_eq!(s.to_class(), "text-danger");
|
/// assert_eq!(s.to_class(), "text-danger");
|
||||||
///
|
///
|
||||||
/// // Forma explícita para variantes no temáticas:
|
/// // Forma explícita para variantes no temáticas:
|
||||||
/// let s = class::Text::with(class::TextColor::Body);
|
/// let s = class::Text::with(token::ColorText::Body);
|
||||||
/// assert_eq!(s.to_class(), "text-body");
|
/// assert_eq!(s.to_class(), "text-body");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn with(color: impl Into<TextColor>) -> Self {
|
pub fn with(color: impl Into<ColorText>) -> Self {
|
||||||
Self::default().with_color(color)
|
Self::default().with_color(color)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -462,15 +208,15 @@ impl Text {
|
||||||
|
|
||||||
/// Establece el color del texto (`text-*`).
|
/// Establece el color del texto (`text-*`).
|
||||||
///
|
///
|
||||||
/// Acepta cualquier tipo convertible en [`TextColor`]. Un [`ThemeColor`] se convierte
|
/// Acepta cualquier tipo convertible en [`ColorText`]. Un [`Color`](crate::theme::token::Color)
|
||||||
/// automáticamente en [`TextColor::Solid`].
|
/// se convierte automáticamente en [`ColorText::Theme`].
|
||||||
pub fn with_color(mut self, color: impl Into<TextColor>) -> Self {
|
pub fn with_color(mut self, color: impl Into<ColorText>) -> Self {
|
||||||
self.color = color.into();
|
self.color = color.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Establece la opacidad del texto (`text-opacity-*`).
|
/// Establece la opacidad del texto (`text-opacity-*`).
|
||||||
pub fn with_opacity(mut self, opacity: OpacityLevel) -> Self {
|
pub fn with_opacity(mut self, opacity: Opacity) -> Self {
|
||||||
self.opacity = opacity;
|
self.opacity = opacity;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -495,7 +241,7 @@ impl Text {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(TextColor, OpacityLevel)> for Text {
|
impl From<(ColorText, Opacity)> for Text {
|
||||||
/// Crea la clase para [`Text`](crate::theme::class::Text) a partir del color del texto y su
|
/// Crea la clase para [`Text`](crate::theme::class::Text) a partir del color del texto y su
|
||||||
/// opacidad.
|
/// opacidad.
|
||||||
///
|
///
|
||||||
|
|
@ -504,27 +250,27 @@ impl From<(TextColor, OpacityLevel)> for Text {
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let s: class::Text = (
|
/// let s: class::Text = (
|
||||||
/// class::TextColor::Solid(ThemeColor::Danger),
|
/// token::ColorText::Theme(token::Color::Danger),
|
||||||
/// OpacityLevel::Opaque,
|
/// token::Opacity::Opaque,
|
||||||
/// ).into();
|
/// ).into();
|
||||||
/// assert_eq!(s.to_class(), "text-danger text-opacity-100");
|
/// assert_eq!(s.to_class(), "text-danger text-opacity-100");
|
||||||
/// ```
|
/// ```
|
||||||
fn from((color, opacity): (TextColor, OpacityLevel)) -> Self {
|
fn from((color, opacity): (ColorText, Opacity)) -> Self {
|
||||||
Text::with(color).with_opacity(opacity)
|
Text::with(color).with_opacity(opacity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<TextColor> for Text {
|
impl From<ColorText> for Text {
|
||||||
/// Crea la clase para [`Text`](crate::theme::class::Text) a partir del color del texto.
|
/// Crea la clase para [`Text`](crate::theme::class::Text) a partir del color del texto.
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplo
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// let s: class::Text = class::TextColor::Black.into();
|
/// let s: class::Text = token::ColorText::Black.into();
|
||||||
/// assert_eq!(s.to_class(), "text-black");
|
/// assert_eq!(s.to_class(), "text-black");
|
||||||
/// ```
|
/// ```
|
||||||
fn from(color: TextColor) -> Self {
|
fn from(color: ColorText) -> Self {
|
||||||
Text::with(color)
|
Text::with(color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::{BoxSide, BreakPoint, ScaleSize};
|
use crate::theme::token::{BreakPoint, ScaleSize, Side};
|
||||||
|
|
||||||
// **< Margin >*************************************************************************************
|
// **< Margin >*************************************************************************************
|
||||||
|
|
||||||
|
|
@ -11,19 +11,19 @@ use crate::theme::{BoxSide, BreakPoint, ScaleSize};
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// let m = class::Margin::with(BoxSide::Top, ScaleSize::Three);
|
/// let m = class::Margin::with(token::Side::Top, token::ScaleSize::Three);
|
||||||
/// assert_eq!(m.to_class(), "mt-3");
|
/// assert_eq!(m.to_class(), "mt-3");
|
||||||
///
|
///
|
||||||
/// let m = class::Margin::with(BoxSide::Start, ScaleSize::Auto)
|
/// let m = class::Margin::with(token::Side::Start, token::ScaleSize::Auto)
|
||||||
/// .with_breakpoint(BreakPoint::LG);
|
/// .with_breakpoint(token::BreakPoint::LG);
|
||||||
/// assert_eq!(m.to_class(), "ms-lg-auto");
|
/// assert_eq!(m.to_class(), "ms-lg-auto");
|
||||||
///
|
///
|
||||||
/// let m = class::Margin::with(BoxSide::All, ScaleSize::None);
|
/// let m = class::Margin::with(token::Side::All, token::ScaleSize::None);
|
||||||
/// assert_eq!(m.to_class(), "");
|
/// assert_eq!(m.to_class(), "");
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Margin {
|
pub struct Margin {
|
||||||
side: BoxSide,
|
side: Side,
|
||||||
size: ScaleSize,
|
size: ScaleSize,
|
||||||
breakpoint: BreakPoint,
|
breakpoint: BreakPoint,
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +31,7 @@ pub struct Margin {
|
||||||
impl Margin {
|
impl Margin {
|
||||||
/// Crea un **margin** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de
|
/// Crea un **margin** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de
|
||||||
/// ruptura.
|
/// ruptura.
|
||||||
pub fn with(side: BoxSide, size: ScaleSize) -> Self {
|
pub fn with(side: Side, size: ScaleSize) -> Self {
|
||||||
Margin {
|
Margin {
|
||||||
side,
|
side,
|
||||||
size,
|
size,
|
||||||
|
|
@ -54,13 +54,13 @@ impl Margin {
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn side_prefix(&self) -> &'static str {
|
const fn side_prefix(&self) -> &'static str {
|
||||||
match self.side {
|
match self.side {
|
||||||
BoxSide::All => "m",
|
Side::All => "m",
|
||||||
BoxSide::Top => "mt",
|
Side::Top => "mt",
|
||||||
BoxSide::Bottom => "mb",
|
Side::Bottom => "mb",
|
||||||
BoxSide::Start => "ms",
|
Side::Start => "ms",
|
||||||
BoxSide::End => "me",
|
Side::End => "me",
|
||||||
BoxSide::LeftAndRight => "mx",
|
Side::LeftAndRight => "mx",
|
||||||
BoxSide::TopAndBottom => "my",
|
Side::TopAndBottom => "my",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,19 +114,19 @@ impl Into<CowStr> for Margin {
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// let p = class::Padding::with(BoxSide::LeftAndRight, ScaleSize::Two);
|
/// let p = class::Padding::with(token::Side::LeftAndRight, token::ScaleSize::Two);
|
||||||
/// assert_eq!(p.to_class(), "px-2");
|
/// assert_eq!(p.to_class(), "px-2");
|
||||||
///
|
///
|
||||||
/// let p = class::Padding::with(BoxSide::End, ScaleSize::Four)
|
/// let p = class::Padding::with(token::Side::End, token::ScaleSize::Four)
|
||||||
/// .with_breakpoint(BreakPoint::SM);
|
/// .with_breakpoint(token::BreakPoint::SM);
|
||||||
/// assert_eq!(p.to_class(), "pe-sm-4");
|
/// assert_eq!(p.to_class(), "pe-sm-4");
|
||||||
///
|
///
|
||||||
/// let p = class::Padding::with(BoxSide::All, ScaleSize::Auto);
|
/// let p = class::Padding::with(token::Side::All, token::ScaleSize::Auto);
|
||||||
/// assert_eq!(p.to_class(), ""); // `Auto` no aplica a padding.
|
/// assert_eq!(p.to_class(), ""); // `Auto` no aplica a padding.
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Padding {
|
pub struct Padding {
|
||||||
side: BoxSide,
|
side: Side,
|
||||||
size: ScaleSize,
|
size: ScaleSize,
|
||||||
breakpoint: BreakPoint,
|
breakpoint: BreakPoint,
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +134,7 @@ pub struct Padding {
|
||||||
impl Padding {
|
impl Padding {
|
||||||
/// Crea un **padding** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de
|
/// Crea un **padding** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de
|
||||||
/// ruptura.
|
/// ruptura.
|
||||||
pub fn with(side: BoxSide, size: ScaleSize) -> Self {
|
pub fn with(side: Side, size: ScaleSize) -> Self {
|
||||||
Padding {
|
Padding {
|
||||||
side,
|
side,
|
||||||
size,
|
size,
|
||||||
|
|
@ -157,13 +157,13 @@ impl Padding {
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn side_prefix(&self) -> &'static str {
|
const fn side_prefix(&self) -> &'static str {
|
||||||
match self.side {
|
match self.side {
|
||||||
BoxSide::All => "p",
|
Side::All => "p",
|
||||||
BoxSide::Top => "pt",
|
Side::Top => "pt",
|
||||||
BoxSide::Bottom => "pb",
|
Side::Bottom => "pb",
|
||||||
BoxSide::Start => "ps",
|
Side::Start => "ps",
|
||||||
BoxSide::End => "pe",
|
Side::End => "pe",
|
||||||
BoxSide::LeftAndRight => "px",
|
Side::LeftAndRight => "px",
|
||||||
BoxSide::TopAndBottom => "py",
|
Side::TopAndBottom => "py",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,98 +1,6 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
// **< RoundedRadius >******************************************************************************
|
use crate::theme::token::RoundedRadius;
|
||||||
|
|
||||||
/// Radio para el redondeo de esquinas ([`Rounded`]).
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum RoundedRadius {
|
|
||||||
/// No define ninguna clase.
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
/// Genera `rounded` (radio por defecto del tema).
|
|
||||||
Default,
|
|
||||||
/// Genera `rounded-0` (sin redondeo).
|
|
||||||
Zero,
|
|
||||||
/// Genera `rounded-1`.
|
|
||||||
Scale1,
|
|
||||||
/// Genera `rounded-2`.
|
|
||||||
Scale2,
|
|
||||||
/// Genera `rounded-3`.
|
|
||||||
Scale3,
|
|
||||||
/// Genera `rounded-4`.
|
|
||||||
Scale4,
|
|
||||||
/// Genera `rounded-5`.
|
|
||||||
Scale5,
|
|
||||||
/// Genera `rounded-circle`.
|
|
||||||
Circle,
|
|
||||||
/// Genera `rounded-pill`.
|
|
||||||
Pill,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RoundedRadius {
|
|
||||||
// Devuelve el sufijo para `*rounded-*`, o `None` si no define ninguna clase, o `""` para el
|
|
||||||
// redondeo por defecto.
|
|
||||||
#[rustfmt::skip]
|
|
||||||
#[inline]
|
|
||||||
const fn suffix(self) -> Option<&'static str> {
|
|
||||||
match self {
|
|
||||||
Self::None => None,
|
|
||||||
Self::Default => Some(""),
|
|
||||||
Self::Zero => Some("-0"),
|
|
||||||
Self::Scale1 => Some("-1"),
|
|
||||||
Self::Scale2 => Some("-2"),
|
|
||||||
Self::Scale3 => Some("-3"),
|
|
||||||
Self::Scale4 => Some("-4"),
|
|
||||||
Self::Scale5 => Some("-5"),
|
|
||||||
Self::Circle => Some("-circle"),
|
|
||||||
Self::Pill => Some("-pill"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Añade el redondeo de esquinas a la cadena de clases usando el prefijo dado (`rounded-top`,
|
|
||||||
/// `rounded-bottom-start`, o vacío para `rounded-*`).
|
|
||||||
#[inline]
|
|
||||||
pub fn push_to(self, classes: &mut String, prefix: &str) {
|
|
||||||
if let Some(suffix) = self.suffix() {
|
|
||||||
if !classes.is_empty() {
|
|
||||||
classes.push(' ');
|
|
||||||
}
|
|
||||||
if prefix.is_empty() {
|
|
||||||
classes.push_str("rounded");
|
|
||||||
} else {
|
|
||||||
classes.push_str(prefix);
|
|
||||||
}
|
|
||||||
classes.push_str(suffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la clase `rounded-*` para el redondeo de esquinas.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop_bootsier::theme::*;
|
|
||||||
/// assert_eq!(class::RoundedRadius::Default.to_class(), "rounded");
|
|
||||||
/// assert_eq!(class::RoundedRadius::Zero.to_class(), "rounded-0");
|
|
||||||
/// assert_eq!(class::RoundedRadius::Scale3.to_class(), "rounded-3");
|
|
||||||
/// assert_eq!(class::RoundedRadius::Circle.to_class(), "rounded-circle");
|
|
||||||
/// assert_eq!(class::RoundedRadius::None.to_class(), "");
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub fn to_class(self) -> String {
|
|
||||||
let mut class = String::new();
|
|
||||||
self.push_to(&mut class, "");
|
|
||||||
class
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<CowStr> for RoundedRadius {
|
|
||||||
/// Permite pasar [`RoundedRadius`] directamente a [`PropsOp`](pagetop::prelude::PropsOp).
|
|
||||||
fn into(self) -> CowStr {
|
|
||||||
self.to_class().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Rounded >************************************************************************************
|
|
||||||
|
|
||||||
/// Clases para definir **esquinas redondeadas**.
|
/// Clases para definir **esquinas redondeadas**.
|
||||||
///
|
///
|
||||||
|
|
@ -110,7 +18,7 @@ impl Into<CowStr> for RoundedRadius {
|
||||||
/// use pagetop_bootsier::theme::*;
|
/// use pagetop_bootsier::theme::*;
|
||||||
///
|
///
|
||||||
/// // Radio global:
|
/// // Radio global:
|
||||||
/// let r = class::Rounded::with(class::RoundedRadius::Default);
|
/// let r = class::Rounded::with(token::RoundedRadius::Default);
|
||||||
/// assert_eq!(r.to_class(), "rounded");
|
/// assert_eq!(r.to_class(), "rounded");
|
||||||
///
|
///
|
||||||
/// // Sin redondeo:
|
/// // Sin redondeo:
|
||||||
|
|
@ -118,18 +26,18 @@ impl Into<CowStr> for RoundedRadius {
|
||||||
/// assert_eq!(r.to_class(), "");
|
/// assert_eq!(r.to_class(), "");
|
||||||
///
|
///
|
||||||
/// // Radio en las esquinas de un lado lógico:
|
/// // Radio en las esquinas de un lado lógico:
|
||||||
/// let r = class::Rounded::new().with_end(class::RoundedRadius::Scale2);
|
/// let r = class::Rounded::new().with_end(token::RoundedRadius::Scale2);
|
||||||
/// assert_eq!(r.to_class(), "rounded-end-2");
|
/// assert_eq!(r.to_class(), "rounded-end-2");
|
||||||
///
|
///
|
||||||
/// // Radio en una esquina concreta:
|
/// // Radio en una esquina concreta:
|
||||||
/// let r = class::Rounded::new().with_top_start(class::RoundedRadius::Scale3);
|
/// let r = class::Rounded::new().with_top_start(token::RoundedRadius::Scale3);
|
||||||
/// assert_eq!(r.to_class(), "rounded-top-start-3");
|
/// assert_eq!(r.to_class(), "rounded-top-start-3");
|
||||||
///
|
///
|
||||||
/// // Combinado (ejemplo completo):
|
/// // Combinado (ejemplo completo):
|
||||||
/// let r = class::Rounded::new()
|
/// let r = class::Rounded::new()
|
||||||
/// .with_top(class::RoundedRadius::Default) // Añade redondeo arriba.
|
/// .with_top(token::RoundedRadius::Default) // Añade redondeo arriba.
|
||||||
/// .with_bottom_start(class::RoundedRadius::Scale4) // Añade una esquina redondeada concreta.
|
/// .with_bottom_start(token::RoundedRadius::Scale4) // Añade una esquina redondeada concreta.
|
||||||
/// .with_bottom_end(class::RoundedRadius::Circle); // Añade redondeo extremo en otra esquina.
|
/// .with_bottom_end(token::RoundedRadius::Circle); // Añade redondeo extremo en otra esquina.
|
||||||
/// assert_eq!(r.to_class(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle");
|
/// assert_eq!(r.to_class(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle");
|
||||||
/// ```
|
/// ```
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,28 @@
|
||||||
//! Tipos enumerados para construir clases del tema.
|
//! Tipos enumerados para construir clases del tema.
|
||||||
|
//!
|
||||||
|
//! Incluyen puntos de ruptura, colores, escalas de tamaño, lados, etc. Se pueden importar
|
||||||
|
//! globalmente junto al resto del tema:
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use pagetop_bootsier::theme::*;
|
||||||
|
//!
|
||||||
|
//! let bg = class::Background::with(token::Color::Primary);
|
||||||
|
//! let border = class::Border::new()
|
||||||
|
//! .with_side(token::Side::Top, token::ScaleSize::Zero)
|
||||||
|
//! .with_color(token::Color::Danger);
|
||||||
|
//! ```
|
||||||
|
|
||||||
mod breakpoint;
|
mod breakpoint;
|
||||||
pub use breakpoint::BreakPoint;
|
pub use breakpoint::BreakPoint;
|
||||||
|
|
||||||
mod color;
|
mod color;
|
||||||
pub use color::{OpacityLevel, ThemeColor};
|
pub use color::{Color, ColorBg, ColorText, Opacity};
|
||||||
|
|
||||||
|
mod border;
|
||||||
|
pub use border::ColorBorder;
|
||||||
|
|
||||||
|
mod rounded;
|
||||||
|
pub use rounded::RoundedRadius;
|
||||||
|
|
||||||
mod layout;
|
mod layout;
|
||||||
pub use layout::{BoxSide, ScaleSize};
|
pub use layout::{ScaleSize, Side};
|
||||||
|
|
|
||||||
103
extensions/pagetop-bootsier/src/theme/token/border.rs
Normal file
103
extensions/pagetop-bootsier/src/theme/token/border.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
use crate::theme::token::Color;
|
||||||
|
|
||||||
|
/// Esquema de color para los bordes ([`Border`](crate::theme::class::Border)).
|
||||||
|
///
|
||||||
|
/// - `Theme(Color)` y `Subtle(Color)` usan la paleta de colores temáticos ([`Color`]).
|
||||||
|
/// - `Black` y `White` son colores fijos independientes del tema.
|
||||||
|
/// - `Default` no genera ninguna clase.
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum ColorBorder {
|
||||||
|
/// No define ninguna clase.
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
/// Genera la clase `border-{color}`.
|
||||||
|
Theme(Color),
|
||||||
|
/// Genera la clase `border-{color}-subtle` (un tono suavizado del color).
|
||||||
|
Subtle(Color),
|
||||||
|
/// Color negro.
|
||||||
|
Black,
|
||||||
|
/// Color blanco.
|
||||||
|
White,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorBorder {
|
||||||
|
// Devuelve el sufijo de la clase `border-*`, o `None` si no define ninguna clase.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn suffix(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
Self::Default => None,
|
||||||
|
Self::Theme(_) => Some(""),
|
||||||
|
Self::Subtle(_) => Some("-subtle"),
|
||||||
|
Self::Black => Some("-black"),
|
||||||
|
Self::White => Some("-white"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade la clase `border-*` a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_to(self, classes: &mut String) {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
Self::Theme(c) | Self::Subtle(c) => {
|
||||||
|
classes.push_str("border-");
|
||||||
|
classes.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
_ => classes.push_str("border"),
|
||||||
|
}
|
||||||
|
classes.push_str(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `border-*` correspondiente al color de borde.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::theme::*;
|
||||||
|
/// let theme = token::ColorBorder::Theme(token::Color::Primary).to_class();
|
||||||
|
/// assert_eq!(theme, "border-primary");
|
||||||
|
///
|
||||||
|
/// let subtle = token::ColorBorder::Subtle(token::Color::Warning).to_class();
|
||||||
|
/// assert_eq!(subtle, "border-warning-subtle");
|
||||||
|
///
|
||||||
|
/// let black = token::ColorBorder::Black.to_class();
|
||||||
|
/// assert_eq!(black, "border-black");
|
||||||
|
///
|
||||||
|
/// let none = token::ColorBorder::Default.to_class();
|
||||||
|
/// assert_eq!(none, "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
let mut class = String::new();
|
||||||
|
self.push_to(&mut class);
|
||||||
|
class
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for ColorBorder {
|
||||||
|
/// Convierte un [`Color`] en [`ColorBorder::Theme`].
|
||||||
|
///
|
||||||
|
/// Permite pasar un [`Color`] directamente donde se espera un [`ColorBorder`], sin necesidad
|
||||||
|
/// de envolver el valor en [`ColorBorder::Theme`]. Es el atajo habitual para los colores
|
||||||
|
/// temáticos.
|
||||||
|
///
|
||||||
|
/// Para los demás esquemas (`Subtle`, `Black`, `White`) se sigue usando [`ColorBorder`]
|
||||||
|
/// directamente.
|
||||||
|
///
|
||||||
|
/// # Ejemplo
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::theme::*;
|
||||||
|
/// let border: token::ColorBorder = token::Color::Success.into();
|
||||||
|
/// assert_eq!(border.to_class(), "border-success");
|
||||||
|
/// ```
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
Self::Theme(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
/// Puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*.
|
/// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*.
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum BreakPoint {
|
pub enum BreakPoint {
|
||||||
/// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical.
|
/// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical.
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,15 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
// **< ThemeColor >*********************************************************************************
|
// **< Color >**************************************************************************************
|
||||||
|
|
||||||
/// Paleta de colores temáticos.
|
/// Paleta de colores temáticos.
|
||||||
///
|
///
|
||||||
/// Equivalen a los nombres estándar definidos por Bootstrap (`primary`, `secondary`, `success`,
|
/// Equivalen a los nombres estándar definidos por Bootstrap (`primary`, `secondary`, `success`,
|
||||||
/// etc.). Se utiliza para componer las clases de color de [`Bg`], [`Border`] o [`Text`].
|
/// etc.). Este tipo enumerado sirve de referencia para componer las clases de color para el fondo
|
||||||
///
|
/// ([`Background`](crate::theme::class::Background)), bordes
|
||||||
/// [`Bg`]: crate::theme::class::Bg
|
/// ([`Border`](crate::theme::class::Border)) o texto ([`Text`](crate::theme::class::Text)).
|
||||||
/// [`Border`]: crate::theme::class::Border
|
|
||||||
/// [`Text`]: crate::theme::class::Text
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ThemeColor {
|
pub enum Color {
|
||||||
#[default]
|
#[default]
|
||||||
Primary,
|
Primary,
|
||||||
Secondary,
|
Secondary,
|
||||||
|
|
@ -23,7 +21,7 @@ pub enum ThemeColor {
|
||||||
Dark,
|
Dark,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThemeColor {
|
impl Color {
|
||||||
/// Devuelve el nombre del color Bootstrap (`"primary"`, `"danger"`, etc.).
|
/// Devuelve el nombre del color Bootstrap (`"primary"`, `"danger"`, etc.).
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -41,18 +39,16 @@ impl ThemeColor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< OpacityLevel >*******************************************************************************
|
// **< Opacity >************************************************************************************
|
||||||
|
|
||||||
/// Niveles de opacidad (`opacity-*`).
|
/// Niveles de opacidad (`opacity-*`).
|
||||||
///
|
///
|
||||||
/// Se utiliza para las clases que definen la transparencia de [`Bg`] (`bg-opacity-*`), [`Border`]
|
/// Se usa normalmente para graduar la transparencia del color de fondo `bg-opacity-*`
|
||||||
/// (`border-opacity-*`) o [`Text`] (`text-opacity-*`).
|
/// ([`Background`](crate::theme::class::Background)), de los bordes `border-opacity-*`
|
||||||
///
|
/// ([`Border`](crate::theme::class::Border)) o del texto `text-opacity-*`
|
||||||
/// [`Bg`]: crate::theme::class::Bg
|
/// ([`Text`](crate::theme::class::Text)).
|
||||||
/// [`Border`]: crate::theme::class::Border
|
|
||||||
/// [`Text`]: crate::theme::class::Text
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum OpacityLevel {
|
pub enum Opacity {
|
||||||
/// No define ninguna clase.
|
/// No define ninguna clase.
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
|
|
@ -70,7 +66,7 @@ pub enum OpacityLevel {
|
||||||
Transparent,
|
Transparent,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpacityLevel {
|
impl Opacity {
|
||||||
// Devuelve el sufijo para `*opacity-*`, o `None` si no define ninguna clase.
|
// Devuelve el sufijo para `*opacity-*`, o `None` si no define ninguna clase.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -110,9 +106,9 @@ impl OpacityLevel {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::theme::*;
|
/// # use pagetop_bootsier::theme::*;
|
||||||
/// assert_eq!(OpacityLevel::Opaque.to_class(), "opacity-100");
|
/// assert_eq!(token::Opacity::Opaque.to_class(), "opacity-100");
|
||||||
/// assert_eq!(OpacityLevel::Half.to_class(), "opacity-50");
|
/// assert_eq!(token::Opacity::Half.to_class(), "opacity-50");
|
||||||
/// assert_eq!(OpacityLevel::Default.to_class(), "");
|
/// assert_eq!(token::Opacity::Default.to_class(), "");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_class(self) -> String {
|
pub fn to_class(self) -> String {
|
||||||
|
|
@ -121,3 +117,237 @@ impl OpacityLevel {
|
||||||
class
|
class
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **< ColorBg >************************************************************************************
|
||||||
|
|
||||||
|
/// Esquema de color para el fondo ([`Background`](crate::theme::class::Background)).
|
||||||
|
///
|
||||||
|
/// - `Body`, `BodySecondary` y `BodyTertiary` siguen el esquema del tema (claro/oscuro).
|
||||||
|
/// - `Theme(Color)` y `Subtle(Color)` usan la paleta de colores temáticos ([`Color`]).
|
||||||
|
/// - `Black`, `White`, `Transparent` son colores fijos independientes del tema.
|
||||||
|
/// - `Default` no genera ninguna clase.
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum ColorBg {
|
||||||
|
/// No define ninguna clase.
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
/// Fondo predefinido del tema (`bg-body`).
|
||||||
|
Body,
|
||||||
|
/// Fondo predefinido del tema (`bg-body-secondary`).
|
||||||
|
BodySecondary,
|
||||||
|
/// Fondo predefinido del tema (`bg-body-tertiary`).
|
||||||
|
BodyTertiary,
|
||||||
|
/// Genera la clase `bg-{color}` (p. ej., `bg-primary`).
|
||||||
|
Theme(Color),
|
||||||
|
/// Genera la clase `bg-{color}-subtle` (un tono suavizado del color).
|
||||||
|
Subtle(Color),
|
||||||
|
/// Color negro.
|
||||||
|
Black,
|
||||||
|
/// Color blanco.
|
||||||
|
White,
|
||||||
|
/// Fondo transparente (`bg-transparent`).
|
||||||
|
Transparent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorBg {
|
||||||
|
// Devuelve el sufijo de la clase `bg-*`, o `None` si no define ninguna clase.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn suffix(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
Self::Default => None,
|
||||||
|
Self::Body => Some("-body"),
|
||||||
|
Self::BodySecondary => Some("-body-secondary"),
|
||||||
|
Self::BodyTertiary => Some("-body-tertiary"),
|
||||||
|
Self::Theme(_) => Some(""),
|
||||||
|
Self::Subtle(_) => Some("-subtle"),
|
||||||
|
Self::Black => Some("-black"),
|
||||||
|
Self::White => Some("-white"),
|
||||||
|
Self::Transparent => Some("-transparent"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade la clase de fondo `bg-*` a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_to(self, classes: &mut String) {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
Self::Theme(c) | Self::Subtle(c) => {
|
||||||
|
classes.push_str("bg-");
|
||||||
|
classes.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
_ => classes.push_str("bg"),
|
||||||
|
}
|
||||||
|
classes.push_str(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `bg-*` correspondiente al fondo.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::theme::*;
|
||||||
|
/// let body = token::ColorBg::Body.to_class();
|
||||||
|
/// assert_eq!(body, "bg-body");
|
||||||
|
///
|
||||||
|
/// let theme = token::ColorBg::Theme(token::Color::Primary).to_class();
|
||||||
|
/// assert_eq!(theme, "bg-primary");
|
||||||
|
///
|
||||||
|
/// let subtle = token::ColorBg::Subtle(token::Color::Warning).to_class();
|
||||||
|
/// assert_eq!(subtle, "bg-warning-subtle");
|
||||||
|
///
|
||||||
|
/// let transparent = token::ColorBg::Transparent.to_class();
|
||||||
|
/// assert_eq!(transparent, "bg-transparent");
|
||||||
|
///
|
||||||
|
/// let none = token::ColorBg::Default.to_class();
|
||||||
|
/// assert_eq!(none, "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
let mut class = String::new();
|
||||||
|
self.push_to(&mut class);
|
||||||
|
class
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for ColorBg {
|
||||||
|
/// Convierte un [`Color`] en [`ColorBg::Theme`].
|
||||||
|
///
|
||||||
|
/// Permite pasar un [`Color`] directamente donde se espera un [`ColorBg`], sin necesidad de
|
||||||
|
/// envolver el valor en [`ColorBg::Theme`]. Es el atajo habitual para los colores temáticos.
|
||||||
|
///
|
||||||
|
/// Para los demás esquemas (`Body`, `Subtle`, `Black`, etc.) se sigue usando [`ColorBg`]
|
||||||
|
/// directamente.
|
||||||
|
///
|
||||||
|
/// # Ejemplo
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::theme::*;
|
||||||
|
/// let bg: token::ColorBg = token::Color::Primary.into();
|
||||||
|
/// assert_eq!(bg.to_class(), "bg-primary");
|
||||||
|
/// ```
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
Self::Theme(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< ColorText >**********************************************************************************
|
||||||
|
|
||||||
|
/// Esquema de color para el texto ([`Text`](crate::theme::class::Text)).
|
||||||
|
///
|
||||||
|
/// - `Body`, `BodyEmphasis`, `BodySecondary` y `BodyTertiary` siguen el tema (claro/oscuro).
|
||||||
|
/// - `Theme(Color)` y `Emphasis(Color)` usan la paleta de colores temáticos ([`Color`]).
|
||||||
|
/// - `Black` y `White` son colores fijos independientes del tema.
|
||||||
|
/// - `Default` no genera ninguna clase.
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum ColorText {
|
||||||
|
/// No define ninguna clase.
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
/// Color predefinido del tema (`text-body`).
|
||||||
|
Body,
|
||||||
|
/// Color de mayor contraste según el tema (`text-body-emphasis`).
|
||||||
|
BodyEmphasis,
|
||||||
|
/// Color predefinido del tema (`text-body-secondary`).
|
||||||
|
BodySecondary,
|
||||||
|
/// Color predefinido del tema (`text-body-tertiary`).
|
||||||
|
BodyTertiary,
|
||||||
|
/// Genera la clase `text-{color}`.
|
||||||
|
Theme(Color),
|
||||||
|
/// Genera la clase `text-{color}-emphasis` (mayor contraste acorde al tema).
|
||||||
|
Emphasis(Color),
|
||||||
|
/// Color negro.
|
||||||
|
Black,
|
||||||
|
/// Color blanco.
|
||||||
|
White,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorText {
|
||||||
|
// Devuelve el sufijo de la clase `text-*`, o `None` si no define ninguna clase.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn suffix(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
Self::Default => None,
|
||||||
|
Self::Body => Some("-body"),
|
||||||
|
Self::BodyEmphasis => Some("-body-emphasis"),
|
||||||
|
Self::BodySecondary => Some("-body-secondary"),
|
||||||
|
Self::BodyTertiary => Some("-body-tertiary"),
|
||||||
|
Self::Theme(_) => Some(""),
|
||||||
|
Self::Emphasis(_) => Some("-emphasis"),
|
||||||
|
Self::Black => Some("-black"),
|
||||||
|
Self::White => Some("-white"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade la clase de texto `text-*` a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_to(self, classes: &mut String) {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
Self::Theme(c) | Self::Emphasis(c) => {
|
||||||
|
classes.push_str("text-");
|
||||||
|
classes.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
_ => classes.push_str("text"),
|
||||||
|
}
|
||||||
|
classes.push_str(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `text-*` correspondiente al color del texto.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::theme::*;
|
||||||
|
/// let body = token::ColorText::Body.to_class();
|
||||||
|
/// assert_eq!(body, "text-body");
|
||||||
|
///
|
||||||
|
/// let theme = token::ColorText::Theme(token::Color::Primary).to_class();
|
||||||
|
/// assert_eq!(theme, "text-primary");
|
||||||
|
///
|
||||||
|
/// let emphasis = token::ColorText::Emphasis(token::Color::Danger).to_class();
|
||||||
|
/// assert_eq!(emphasis, "text-danger-emphasis");
|
||||||
|
///
|
||||||
|
/// let black = token::ColorText::Black.to_class();
|
||||||
|
/// assert_eq!(black, "text-black");
|
||||||
|
///
|
||||||
|
/// let none = token::ColorText::Default.to_class();
|
||||||
|
/// assert_eq!(none, "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
let mut class = String::new();
|
||||||
|
self.push_to(&mut class);
|
||||||
|
class
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Color> for ColorText {
|
||||||
|
/// Convierte un [`Color`] en [`ColorText::Theme`].
|
||||||
|
///
|
||||||
|
/// Permite pasar un [`Color`] directamente donde se espera un [`ColorText`], sin necesidad de
|
||||||
|
/// envolver el valor en [`ColorText::Theme`]. Es el atajo habitual para los colores temáticos.
|
||||||
|
///
|
||||||
|
/// Para los demás esquemas (`Body`, `Emphasis`, `Black`, etc.) se sigue usando [`ColorText`]
|
||||||
|
/// directamente.
|
||||||
|
///
|
||||||
|
/// # Ejemplo
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::theme::*;
|
||||||
|
/// let text: token::ColorText = token::Color::Danger.into();
|
||||||
|
/// assert_eq!(text.to_class(), "text-danger");
|
||||||
|
/// ```
|
||||||
|
fn from(color: Color) -> Self {
|
||||||
|
Self::Theme(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,11 @@ use pagetop::prelude::*;
|
||||||
|
|
||||||
// **< ScaleSize >**********************************************************************************
|
// **< ScaleSize >**********************************************************************************
|
||||||
|
|
||||||
/// Escala discreta de tamaños para clases utilitarias.
|
/// Escala discreta de tamaños para clases utilitarias de Bootstrap.
|
||||||
///
|
///
|
||||||
/// Se usa como parámetro de tamaño para las clases de [`Border`], [`Margin`] y [`Padding`]. La
|
/// Se usa como parámetro de tamaño en [`Border`](crate::theme::class::Border),
|
||||||
|
/// [`Margin`](crate::theme::class::Margin) y [`Padding`](crate::theme::class::Padding). La
|
||||||
/// variante `Auto` no aplica en `Padding`.
|
/// variante `Auto` no aplica en `Padding`.
|
||||||
///
|
|
||||||
/// [`Border`]: crate::theme::class::Border
|
|
||||||
/// [`Margin`]: crate::theme::class::Margin
|
|
||||||
/// [`Padding`]: crate::theme::class::Padding
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ScaleSize {
|
pub enum ScaleSize {
|
||||||
/// Sin tamaño (no define ninguna clase).
|
/// Sin tamaño (no define ninguna clase).
|
||||||
|
|
@ -64,17 +61,14 @@ impl ScaleSize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< BoxSide >************************************************************************************
|
// **< Side >***************************************************************************************
|
||||||
|
|
||||||
/// Lados sobre los que aplicar una clase utilitaria (respetando LTR/RTL).
|
/// Lados sobre los que aplicar una clase utilitaria (respetando LTR/RTL).
|
||||||
///
|
///
|
||||||
/// Se usa como selector de lado para las clases de [`Border`], [`Margin`] y [`Padding`].
|
/// Se usa como selector de lado en [`Border`](crate::theme::class::Border),
|
||||||
///
|
/// [`Margin`](crate::theme::class::Margin) y [`Padding`](crate::theme::class::Padding).
|
||||||
/// [`Border`]: crate::theme::class::Border
|
|
||||||
/// [`Margin`]: crate::theme::class::Margin
|
|
||||||
/// [`Padding`]: crate::theme::class::Padding
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum BoxSide {
|
pub enum Side {
|
||||||
/// Todos los lados.
|
/// Todos los lados.
|
||||||
#[default]
|
#[default]
|
||||||
All,
|
All,
|
||||||
|
|
|
||||||
84
extensions/pagetop-bootsier/src/theme/token/rounded.rs
Normal file
84
extensions/pagetop-bootsier/src/theme/token/rounded.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
/// Radio para el redondeo de esquinas ([`Rounded`](crate::theme::class::Rounded)).
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum RoundedRadius {
|
||||||
|
/// No define ninguna clase.
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
/// Genera `rounded` (radio por defecto del tema).
|
||||||
|
Default,
|
||||||
|
/// Genera `rounded-0` (sin redondeo).
|
||||||
|
Zero,
|
||||||
|
/// Genera `rounded-1`.
|
||||||
|
Scale1,
|
||||||
|
/// Genera `rounded-2`.
|
||||||
|
Scale2,
|
||||||
|
/// Genera `rounded-3`.
|
||||||
|
Scale3,
|
||||||
|
/// Genera `rounded-4`.
|
||||||
|
Scale4,
|
||||||
|
/// Genera `rounded-5`.
|
||||||
|
Scale5,
|
||||||
|
/// Genera `rounded-circle`.
|
||||||
|
Circle,
|
||||||
|
/// Genera `rounded-pill`.
|
||||||
|
Pill,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RoundedRadius {
|
||||||
|
// Devuelve el sufijo para `*rounded-*`, o `None` si no define ninguna clase, o `""` para el
|
||||||
|
// redondeo por defecto.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn suffix(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
Self::None => None,
|
||||||
|
Self::Default => Some(""),
|
||||||
|
Self::Zero => Some("-0"),
|
||||||
|
Self::Scale1 => Some("-1"),
|
||||||
|
Self::Scale2 => Some("-2"),
|
||||||
|
Self::Scale3 => Some("-3"),
|
||||||
|
Self::Scale4 => Some("-4"),
|
||||||
|
Self::Scale5 => Some("-5"),
|
||||||
|
Self::Circle => Some("-circle"),
|
||||||
|
Self::Pill => Some("-pill"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade el redondeo de esquinas a la cadena de clases usando el prefijo dado (`rounded-top`,
|
||||||
|
// `rounded-bottom-start`, o vacío para `rounded-*`).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_to(self, classes: &mut String, prefix: &str) {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
if prefix.is_empty() {
|
||||||
|
classes.push_str("rounded");
|
||||||
|
} else {
|
||||||
|
classes.push_str(prefix);
|
||||||
|
}
|
||||||
|
classes.push_str(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `rounded-*` para el redondeo de esquinas.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::theme::*;
|
||||||
|
/// assert_eq!(token::RoundedRadius::Default.to_class(), "rounded");
|
||||||
|
/// assert_eq!(token::RoundedRadius::Zero.to_class(), "rounded-0");
|
||||||
|
/// assert_eq!(token::RoundedRadius::Scale3.to_class(), "rounded-3");
|
||||||
|
/// assert_eq!(token::RoundedRadius::Circle.to_class(), "rounded-circle");
|
||||||
|
/// assert_eq!(token::RoundedRadius::None.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
let mut class = String::new();
|
||||||
|
self.push_to(&mut class, "");
|
||||||
|
class
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -455,7 +455,7 @@ impl Context {
|
||||||
pub fn route(&self, path: impl Into<RoutePath>) -> RoutePath {
|
pub fn route(&self, path: impl Into<RoutePath>) -> RoutePath {
|
||||||
let mut route = path.into();
|
let mut route = path.into();
|
||||||
if !route.is_external() && self.locale.needs_lang_query() {
|
if !route.is_external() && self.locale.needs_lang_query() {
|
||||||
route.alter_param("lang", self.locale.lang_query_value());
|
route.alter_param("lang", self.locale.langid().to_string());
|
||||||
}
|
}
|
||||||
route
|
route
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,53 +7,45 @@ use super::{LangId, LanguageIdentifier, Locale};
|
||||||
///
|
///
|
||||||
/// Determina qué idioma se usará para renderizar la respuesta asociada a una petición. También
|
/// Determina qué idioma se usará para renderizar la respuesta asociada a una petición. También
|
||||||
/// indica si es necesario propagar ese idioma en los enlaces usando el parámetro de *query*
|
/// indica si es necesario propagar ese idioma en los enlaces usando el parámetro de *query*
|
||||||
/// `?lang=...`. El comportamiento concreto depende de la política global [`LangNegotiation`]
|
/// `?lang=...`. El comportamiento concreto depende de la política global
|
||||||
/// configurada en la aplicación.
|
/// [`LangNegotiation`](crate::global::LangNegotiation) configurada en la aplicación.
|
||||||
///
|
///
|
||||||
/// El idioma resultante se expone a través del *trait* [`LangId`], de modo que pueda usarse
|
/// El idioma resultante se expone a través del *trait* [`LangId`], de modo que pueda usarse
|
||||||
/// [`RequestLocale`] como cualquier otra fuente de idioma en PageTop.
|
/// [`RequestLocale`] como cualquier otra fuente de idioma en PageTop.
|
||||||
///
|
|
||||||
/// [`LangNegotiation`]: crate::global::LangNegotiation
|
|
||||||
pub struct RequestLocale {
|
pub struct RequestLocale {
|
||||||
// Idioma elegido por la aplicación para esta petición, combinando la configuración, la cabecera
|
// Idioma elegido por la aplicación para esta petición, combinando la configuración, la cabecera
|
||||||
// `Accept-Language` y/o el idioma de respaldo.
|
// `Accept-Language` y/o el idioma de respaldo.
|
||||||
base: &'static LanguageIdentifier,
|
base: &'static LanguageIdentifier,
|
||||||
// Idioma finalmente aplicado a la petición (puede coincidir con `base` o no).
|
// Idioma finalmente aplicado a la petición (puede coincidir con `base` o no).
|
||||||
effective: &'static LanguageIdentifier,
|
effective: &'static LanguageIdentifier,
|
||||||
// Valor original de `?lang=...`, tal como llegó en la petición, si resultó en un idioma
|
|
||||||
// soportado. Permite propagarlo en los enlaces con su grafía original (p. ej. "en") en vez de
|
|
||||||
// expandirlo siempre a la forma canónica del idioma resuelto (p. ej. "en-US").
|
|
||||||
query_lang: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestLocale {
|
impl RequestLocale {
|
||||||
/// Construye un `RequestLocale` a partir de una petición HTTP.
|
/// Construye un `RequestLocale` a partir de una petición HTTP.
|
||||||
///
|
///
|
||||||
/// El idioma de la petición se decide según la estrategia definida por [`LangNegotiation`]:
|
/// El idioma de la petición se decide según la estrategia definida por
|
||||||
|
/// [`LangNegotiation`](crate::global::LangNegotiation):
|
||||||
///
|
///
|
||||||
/// - [`LangNegotiation::Full`] determina el idioma en este orden:
|
/// - [`LangNegotiation::Full`](crate::global::LangNegotiation::Full) determina el idioma en
|
||||||
|
/// este orden:
|
||||||
/// 1. Parámetro de *query* `?lang=...`, si existe y corresponde a un idioma soportado.
|
/// 1. Parámetro de *query* `?lang=...`, si existe y corresponde a un idioma soportado.
|
||||||
/// 2. [`Locale::try_langid()`], si la aplicación tiene un idioma por defecto válido.
|
/// 2. [`Locale::try_langid()`], si la aplicación tiene un idioma por defecto válido.
|
||||||
/// 3. Cabecera `Accept-Language`, si puede resolverse con [`Locale::resolve()`].
|
/// 3. Cabecera `Accept-Language`, si puede resolverse con [`Locale::resolve()`].
|
||||||
/// 4. Idioma de respaldo.
|
/// 4. Idioma de respaldo.
|
||||||
///
|
///
|
||||||
/// - [`LangNegotiation::NoQuery`] descarta el uso del parámetro `?lang=...` y determina el
|
/// - [`LangNegotiation::NoQuery`](crate::global::LangNegotiation::NoQuery) descarta el uso del
|
||||||
/// idioma en este orden:
|
/// parámetro `?lang=...` y determina el idioma en este orden:
|
||||||
/// 1. [`Locale::try_langid()`], si la aplicación tiene un idioma por defecto válido.
|
/// 1. [`Locale::try_langid()`], si la aplicación tiene un idioma por defecto válido.
|
||||||
/// 2. Cabecera `Accept-Language`, si puede resolverse con [`Locale::resolve()`].
|
/// 2. Cabecera `Accept-Language`, si puede resolverse con [`Locale::resolve()`].
|
||||||
/// 3. Idioma de respaldo.
|
/// 3. Idioma de respaldo.
|
||||||
///
|
///
|
||||||
/// - [`LangNegotiation::ConfigOnly`] sólo usa la configuración de la aplicación mediante
|
/// - [`LangNegotiation::ConfigOnly`](crate::global::LangNegotiation::ConfigOnly) sólo usa la
|
||||||
/// [`Locale::default_langid()`], sin consultar la cabecera `Accept-Language` ni el parámetro
|
/// configuración de la aplicación mediante [`Locale::default_langid()`], sin consultar la
|
||||||
/// `?lang`. Este modo también aplica el idioma de respaldo si es necesario.
|
/// cabecera `Accept-Language` ni el parámetro `?lang`. Este modo también aplica el idioma de
|
||||||
|
/// respaldo si es necesario.
|
||||||
///
|
///
|
||||||
/// En todos los casos, el idioma resultante es siempre un [`LanguageIdentifier`] soportado por
|
/// En todos los casos, el idioma resultante es siempre un [`LanguageIdentifier`] soportado por
|
||||||
/// la aplicación y será el que PageTop utilice para renderizar la respuesta de la petición.
|
/// la aplicación y será el que PageTop utilice para renderizar la respuesta de la petición.
|
||||||
///
|
|
||||||
/// [`LangNegotiation`]: crate::global::LangNegotiation
|
|
||||||
/// [`LangNegotiation::Full`]: crate::global::LangNegotiation::Full
|
|
||||||
/// [`LangNegotiation::NoQuery`]: crate::global::LangNegotiation::NoQuery
|
|
||||||
/// [`LangNegotiation::ConfigOnly`]: crate::global::LangNegotiation::ConfigOnly
|
|
||||||
pub fn from_request(request: Option<&HttpRequest>) -> Self {
|
pub fn from_request(request: Option<&HttpRequest>) -> Self {
|
||||||
let mode = global::SETTINGS.app.lang_negotiation;
|
let mode = global::SETTINGS.app.lang_negotiation;
|
||||||
|
|
||||||
|
|
@ -106,44 +98,38 @@ impl RequestLocale {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Valor de `?lang=...`, si existe y corresponde a un idioma soportado. Se conserva junto al
|
// Idioma aplicado a la petición tras considerar la *query* `?lang=...`.
|
||||||
// `LanguageIdentifier` resuelto para poder propagar más tarde su grafía original.
|
let effective: &'static LanguageIdentifier = match mode {
|
||||||
let query_lang: Option<(&'static LanguageIdentifier, String)> = match mode {
|
|
||||||
global::LangNegotiation::ConfigOnly | global::LangNegotiation::NoQuery => {
|
global::LangNegotiation::ConfigOnly | global::LangNegotiation::NoQuery => {
|
||||||
// En estos modos no se permite que la URL modifique el idioma.
|
// En estos modos no se permite que la URL modifique el idioma.
|
||||||
None
|
base
|
||||||
}
|
}
|
||||||
global::LangNegotiation::Full => request
|
global::LangNegotiation::Full => {
|
||||||
// Se obtiene el valor de `lang` de la petición, si existe.
|
request
|
||||||
.and_then(|req| {
|
// Se obtiene el valor de `lang` de la petición, si existe.
|
||||||
req.query_string().split('&').find_map(|pair| {
|
.and_then(|req| {
|
||||||
let mut param = pair.splitn(2, '=');
|
req.query_string().split('&').find_map(|pair| {
|
||||||
match (param.next(), param.next()) {
|
let mut param = pair.splitn(2, '=');
|
||||||
(Some("lang"), Some(value)) if !value.is_empty() => Some(value),
|
match (param.next(), param.next()) {
|
||||||
_ => None,
|
(Some("lang"), Some(value)) if !value.is_empty() => Some(value),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// Se comprueba si es un idioma soportado.
|
||||||
|
.and_then(|language| {
|
||||||
|
if let Locale::Resolved(langid) = Locale::resolve(language) {
|
||||||
|
Some(langid)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
// Si no hay `lang` o no es válido, se usa `base`.
|
||||||
// Se comprueba si es un idioma soportado.
|
.unwrap_or(base)
|
||||||
.and_then(|language| {
|
}
|
||||||
if let Locale::Resolved(langid) = Locale::resolve(language) {
|
|
||||||
Some((langid, language.to_string()))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Idioma aplicado a la petición tras considerar la *query* `?lang=...`. Si no hay `lang` o
|
RequestLocale { base, effective }
|
||||||
// no es válido, se usa `base`.
|
|
||||||
let effective: &'static LanguageIdentifier =
|
|
||||||
query_lang.as_ref().map_or(base, |(langid, _)| *langid);
|
|
||||||
|
|
||||||
RequestLocale {
|
|
||||||
base,
|
|
||||||
effective,
|
|
||||||
query_lang: query_lang.map(|(_, raw)| raw),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fuerza el idioma que se utilizará para las traducciones de esta petición.
|
/// Fuerza el idioma que se utilizará para las traducciones de esta petición.
|
||||||
|
|
@ -154,29 +140,26 @@ impl RequestLocale {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_langid(&mut self, language: &impl LangId) -> &mut Self {
|
pub fn with_langid(&mut self, language: &impl LangId) -> &mut Self {
|
||||||
self.effective = language.langid();
|
self.effective = language.langid();
|
||||||
self.query_lang = None;
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indica si conviene propagar `lang=...` en los enlaces generados.
|
/// Indica si conviene propagar `lang=...` en los enlaces generados.
|
||||||
///
|
///
|
||||||
/// El comportamiento depende de la estrategia configurada en [`LangNegotiation`]:
|
/// El comportamiento depende de la estrategia configurada en
|
||||||
|
/// [`LangNegotiation`](crate::global::LangNegotiation):
|
||||||
///
|
///
|
||||||
/// - En modo [`LangNegotiation::Full`] devuelve `true` cuando la respuesta se está generando en
|
/// - En modo [`LangNegotiation::Full`](crate::global::LangNegotiation::Full) devuelve `true`
|
||||||
/// un idioma distinto del que la aplicación habría elegido automáticamente a partir de la
|
/// cuando la respuesta se está generando en un idioma distinto del que la aplicación habría
|
||||||
/// configuración, el navegador y el idioma de respaldo. En la práctica suele significar que
|
/// elegido automáticamente a partir de la configuración, el navegador y el idioma de
|
||||||
/// el usuario ha pedido expresamente otro idioma (por ejemplo, con `?lang=...`) o que se ha
|
/// respaldo. En la práctica suele significar que el usuario ha pedido expresamente otro
|
||||||
/// forzado con [`with_langid()`](Self::with_langid), y por tanto es recomendable propagar
|
/// idioma (por ejemplo, con `?lang=...`) o que se ha forzado con
|
||||||
/// `lang=...` en los enlaces para mantener esa preferencia mientras se navega.
|
/// [`with_langid()`](Self::with_langid), y por tanto es recomendable propagar `lang=...` en
|
||||||
|
/// los enlaces para mantener esa preferencia mientras se navega.
|
||||||
///
|
///
|
||||||
/// - En modos [`LangNegotiation::NoQuery`] y [`LangNegotiation::ConfigOnly`] siempre devuelve
|
/// - En modos [`LangNegotiation::NoQuery`](crate::global::LangNegotiation::NoQuery) y
|
||||||
/// `false`, ya que en estas estrategias la aplicación no utiliza el parámetro `?lang=...`
|
/// [`LangNegotiation::ConfigOnly`](crate::global::LangNegotiation::ConfigOnly) siempre
|
||||||
/// para seleccionar ni para propagar el idioma.
|
/// devuelve `false`, ya que en estas estrategias la aplicación no utiliza el parámetro
|
||||||
///
|
/// `?lang=...` para seleccionar ni para propagar el idioma.
|
||||||
/// [`LangNegotiation`]: crate::global::LangNegotiation
|
|
||||||
/// [`LangNegotiation::Full`]: crate::global::LangNegotiation::Full
|
|
||||||
/// [`LangNegotiation::NoQuery`]: crate::global::LangNegotiation::NoQuery
|
|
||||||
/// [`LangNegotiation::ConfigOnly`]: crate::global::LangNegotiation::ConfigOnly
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn needs_lang_query(&self) -> bool {
|
pub(crate) fn needs_lang_query(&self) -> bool {
|
||||||
match global::SETTINGS.app.lang_negotiation {
|
match global::SETTINGS.app.lang_negotiation {
|
||||||
|
|
@ -184,19 +167,6 @@ impl RequestLocale {
|
||||||
global::LangNegotiation::NoQuery | global::LangNegotiation::ConfigOnly => false,
|
global::LangNegotiation::NoQuery | global::LangNegotiation::ConfigOnly => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Devuelve el valor a propagar en el parámetro `?lang=...` de los enlaces generados.
|
|
||||||
///
|
|
||||||
/// Si el idioma efectivo procede del parámetro `?lang=...` de la petición, devuelve ese valor
|
|
||||||
/// tal como llegó, conservando su grafía original (p. ej. `"en"` o `"en-US"`). En caso
|
|
||||||
/// contrario (por ejemplo, si el idioma se forzó con [`with_langid()`](Self::with_langid)),
|
|
||||||
/// devuelve la forma canónica del idioma efectivo.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn lang_query_value(&self) -> String {
|
|
||||||
self.query_lang
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| self.effective.to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Permite a [`RequestLocale`] actuar como proveedor de idioma.
|
/// Permite a [`RequestLocale`] actuar como proveedor de idioma.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue