WIP: Añade componente para la gestión de menús #8
33 changed files with 1607 additions and 647 deletions
|
|
@ -7,8 +7,11 @@ mod color;
|
||||||
pub use color::{Color, Opacity};
|
pub use color::{Color, Opacity};
|
||||||
pub use color::{ColorBg, ColorText};
|
pub use color::{ColorBg, ColorText};
|
||||||
|
|
||||||
|
mod layout;
|
||||||
|
pub use layout::{ScaleSize, Side};
|
||||||
|
|
||||||
mod border;
|
mod border;
|
||||||
pub use border::{BorderColor, BorderSize};
|
pub use border::BorderColor;
|
||||||
|
|
||||||
mod rounded;
|
mod rounded;
|
||||||
pub use rounded::RoundedRadius;
|
pub use rounded::RoundedRadius;
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,8 @@ use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::aux::Color;
|
use crate::theme::aux::Color;
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
// **< BorderColor >********************************************************************************
|
|
||||||
|
|
||||||
/// Colores `border-*` para los bordes ([`classes::Border`](crate::theme::classes::Border)).
|
/// Colores `border-*` para los bordes ([`classes::Border`](crate::theme::classes::Border)).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum BorderColor {
|
pub enum BorderColor {
|
||||||
/// No define ninguna clase.
|
/// No define ninguna clase.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -22,60 +18,70 @@ pub enum BorderColor {
|
||||||
White,
|
White,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BorderColor {
|
||||||
|
const BORDER: &str = "border";
|
||||||
|
const BORDER_PREFIX: &str = "border-";
|
||||||
|
|
||||||
|
// Devuelve el sufijo de la clase `border-*`, o `None` si no define ninguna clase.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl fmt::Display for BorderColor {
|
#[inline]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
const fn suffix(self) -> Option<&'static str> {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => Ok(()),
|
Self::Default => None,
|
||||||
Self::Theme(c) => write!(f, "border-{c}"),
|
Self::Theme(_) => Some(""),
|
||||||
Self::Subtle(c) => write!(f, "border-{c}-subtle"),
|
Self::Subtle(_) => Some("-subtle"),
|
||||||
Self::Black => f.write_str("border-black"),
|
Self::Black => Some("-black"),
|
||||||
Self::White => f.write_str("border-white"),
|
Self::White => Some("-white"),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< BorderSize >*********************************************************************************
|
// Añade la clase `border-*` a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
/// Tamaño para el ancho de los bordes ([`classes::Border`](crate::theme::classes::Border)).
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
///
|
if let Some(suffix) = self.suffix() {
|
||||||
/// Mapea a `border`, `border-0` y `border-{1..5}`:
|
if !classes.is_empty() {
|
||||||
///
|
classes.push(' ');
|
||||||
/// - `None` no añade ninguna clase.
|
|
||||||
/// - `Default` genera `border` (borde por defecto del tema).
|
|
||||||
/// - `Zero` genera `border-0` (sin borde).
|
|
||||||
/// - `Scale{1..5}` genera `border-{1..5}` (ancho creciente).
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub enum BorderSize {
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
Default,
|
|
||||||
Zero,
|
|
||||||
Scale1,
|
|
||||||
Scale2,
|
|
||||||
Scale3,
|
|
||||||
Scale4,
|
|
||||||
Scale5,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BorderSize {
|
|
||||||
#[rustfmt::skip]
|
|
||||||
pub(crate) fn to_class(&self, prefix: impl AsRef<str>) -> String {
|
|
||||||
match self {
|
match self {
|
||||||
Self::None => String::new(),
|
Self::Theme(c) | Self::Subtle(c) => {
|
||||||
Self::Default => String::from(prefix.as_ref()),
|
classes.push_str(Self::BORDER_PREFIX);
|
||||||
Self::Zero => join!(prefix, "-0"),
|
classes.push_str(c.as_str());
|
||||||
Self::Scale1 => join!(prefix, "-1"),
|
|
||||||
Self::Scale2 => join!(prefix, "-2"),
|
|
||||||
Self::Scale3 => join!(prefix, "-3"),
|
|
||||||
Self::Scale4 => join!(prefix, "-4"),
|
|
||||||
Self::Scale5 => join!(prefix, "-5"),
|
|
||||||
}
|
}
|
||||||
|
_ => classes.push_str(Self::BORDER),
|
||||||
|
}
|
||||||
|
classes.push_str(suffix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for BorderSize {
|
/// Devuelve la clase `border-*` correspondiente al color de borde.
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
///
|
||||||
write!(f, "{}", self.to_class("border"))
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// assert_eq!(BorderColor::Theme(Color::Primary).to_class(), "border-primary");
|
||||||
|
/// assert_eq!(BorderColor::Subtle(Color::Warning).to_class(), "border-warning-subtle");
|
||||||
|
/// assert_eq!(BorderColor::Black.to_class(), "border-black");
|
||||||
|
/// assert_eq!(BorderColor::Default.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
let base_len = match self {
|
||||||
|
Self::Theme(c) | Self::Subtle(c) => Self::BORDER_PREFIX.len() + c.as_str().len(),
|
||||||
|
_ => Self::BORDER.len(),
|
||||||
|
};
|
||||||
|
let mut class = String::with_capacity(base_len + suffix.len());
|
||||||
|
match self {
|
||||||
|
Self::Theme(c) | Self::Subtle(c) => {
|
||||||
|
class.push_str(Self::BORDER_PREFIX);
|
||||||
|
class.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
_ => class.push_str(Self::BORDER),
|
||||||
|
}
|
||||||
|
class.push_str(suffix);
|
||||||
|
return class;
|
||||||
|
}
|
||||||
|
String::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
/// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*.
|
/// Define los puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*.
|
||||||
#[derive(AutoDefault)]
|
#[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.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -21,71 +19,96 @@ pub enum BreakPoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BreakPoint {
|
impl BreakPoint {
|
||||||
|
// Devuelve la identificación del punto de ruptura.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn suffix(&self) -> Option<&'static str> {
|
pub(crate) const fn as_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::None => None,
|
Self::None => "",
|
||||||
Self::SM => Some("sm"),
|
Self::SM => "sm",
|
||||||
Self::MD => Some("md"),
|
Self::MD => "md",
|
||||||
Self::LG => Some("lg"),
|
Self::LG => "lg",
|
||||||
Self::XL => Some("xl"),
|
Self::XL => "xl",
|
||||||
Self::XXL => Some("xxl"),
|
Self::XXL => "xxl",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Genera un nombre de clase CSS basado en el punto de ruptura.
|
// Añade el punto de ruptura con un prefijo y un sufijo (opcional) separados por un guion `-` a
|
||||||
///
|
// la cadena de clases.
|
||||||
/// Si es un punto de ruptura efectivo concatena el prefijo, un guion (`-`) y el sufijo
|
//
|
||||||
/// asociado. Para `None` devuelve sólo el prefijo.
|
// - Para `None` - `prefix` o `prefix-suffix` (si `suffix` no está vacío).
|
||||||
///
|
// - Para `SM..XXL` - `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`.
|
||||||
/// # Ejemplo
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
|
||||||
/// let breakpoint = BreakPoint::MD;
|
|
||||||
/// assert_eq!(breakpoint.to_class("col"), "col-md");
|
|
||||||
///
|
|
||||||
/// let breakpoint = BreakPoint::None;
|
|
||||||
/// assert_eq!(breakpoint.to_class("offcanvas"), "offcanvas");
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn to_class(&self, prefix: impl AsRef<str>) -> String {
|
pub(crate) fn push_class(self, classes: &mut String, prefix: &str, suffix: &str) {
|
||||||
join_pair!(prefix, "-", self.suffix().unwrap_or_default())
|
if prefix.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
Self::None => classes.push_str(prefix),
|
||||||
|
_ => {
|
||||||
|
classes.push_str(prefix);
|
||||||
|
classes.push('-');
|
||||||
|
classes.push_str(self.as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !suffix.is_empty() {
|
||||||
|
classes.push('-');
|
||||||
|
classes.push_str(suffix);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intenta generar un nombre de clase CSS basado en el punto de ruptura.
|
// Devuelve la clase para el punto de ruptura, con un prefijo y un sufijo opcional, separados
|
||||||
///
|
// por un guion `-`.
|
||||||
/// Si es un punto de ruptura efectivo devuelve `Some(String)` concatenando el prefijo, un guion
|
//
|
||||||
/// (`-`) y el sufijo asociado. En otro caso devuelve `None`.
|
// - Para `None` - `prefix` o `prefix-suffix` (si `suffix` no está vacío).
|
||||||
///
|
// - Para `SM..XXL` - `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`.
|
||||||
/// # Ejemplo
|
// - Si `prefix` está vacío devuelve `""`.
|
||||||
///
|
//
|
||||||
/// ```rust
|
// # Ejemplos
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
//
|
||||||
/// let breakpoint = BreakPoint::MD;
|
// ```rust
|
||||||
/// let class = breakpoint.try_class("col");
|
// # use pagetop_bootsier::prelude::*;
|
||||||
/// assert_eq!(class, Some("col-md".to_string()));
|
// let bp = BreakPoint::MD;
|
||||||
///
|
// assert_eq!(bp.class_with("col", ""), "col-md");
|
||||||
/// let breakpoint = BreakPoint::None;
|
// assert_eq!(bp.class_with("col", "6"), "col-md-6");
|
||||||
/// let class = breakpoint.try_class("navbar-expand");
|
//
|
||||||
/// assert_eq!(class, None);
|
// let bp = BreakPoint::None;
|
||||||
/// ```
|
// assert_eq!(bp.class_with("offcanvas", ""), "offcanvas");
|
||||||
|
// assert_eq!(bp.class_with("col", "12"), "col-12");
|
||||||
|
//
|
||||||
|
// let bp = BreakPoint::LG;
|
||||||
|
// assert_eq!(bp.class_with("", "3"), "");
|
||||||
|
// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn try_class(&self, prefix: impl AsRef<str>) -> Option<String> {
|
pub(crate) fn class_with(self, prefix: &str, suffix: &str) -> String {
|
||||||
self.suffix().map(|suffix| join_pair!(prefix, "-", suffix))
|
if prefix.is_empty() {
|
||||||
}
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for BreakPoint {
|
let bp = self.as_str();
|
||||||
/// Implementa [`Display`](std::fmt::Display) para asociar `"sm"`, `"md"`, `"lg"`, `"xl"` o
|
let has_bp = !bp.is_empty();
|
||||||
/// `"xxl"` a los puntos de ruptura `BreakPoint::SM`, `MD`, `LG`, `XL` o `XXL`, respectivamente.
|
let has_suffix = !suffix.is_empty();
|
||||||
/// Y `""` (cadena vacía) a `BreakPoint::None`.
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
let mut len = prefix.len();
|
||||||
if let Some(suffix) = self.suffix() {
|
if has_bp {
|
||||||
f.write_str(suffix)
|
len += 1 + bp.len();
|
||||||
} else {
|
}
|
||||||
Ok(())
|
if has_suffix {
|
||||||
}
|
len += 1 + suffix.len();
|
||||||
|
}
|
||||||
|
let mut class = String::with_capacity(len);
|
||||||
|
class.push_str(prefix);
|
||||||
|
if has_bp {
|
||||||
|
class.push('-');
|
||||||
|
class.push_str(bp);
|
||||||
|
}
|
||||||
|
if has_suffix {
|
||||||
|
class.push('-');
|
||||||
|
class.push_str(suffix);
|
||||||
|
}
|
||||||
|
class
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,10 @@ use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::aux::Color;
|
use crate::theme::aux::Color;
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
// **< ButtonColor >********************************************************************************
|
// **< ButtonColor >********************************************************************************
|
||||||
|
|
||||||
/// Variantes de color `btn-*` para botones.
|
/// Variantes de color `btn-*` para botones.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ButtonColor {
|
pub enum ButtonColor {
|
||||||
/// No define ninguna clase.
|
/// No define ninguna clase.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -20,14 +18,72 @@ pub enum ButtonColor {
|
||||||
Link,
|
Link,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
impl ButtonColor {
|
||||||
impl fmt::Display for ButtonColor {
|
const BTN_PREFIX: &str = "btn-";
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
const BTN_OUTLINE_PREFIX: &str = "btn-outline-";
|
||||||
|
const BTN_LINK: &str = "btn-link";
|
||||||
|
|
||||||
|
// Añade la clase `btn-*` a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
if let Self::Default = self {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
match self {
|
match self {
|
||||||
Self::Default => Ok(()),
|
Self::Default => unreachable!(),
|
||||||
Self::Background(c) => write!(f, "btn-{c}"),
|
Self::Background(c) => {
|
||||||
Self::Outline(c) => write!(f, "btn-outline-{c}"),
|
classes.push_str(Self::BTN_PREFIX);
|
||||||
Self::Link => f.write_str("btn-link"),
|
classes.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
Self::Outline(c) => {
|
||||||
|
classes.push_str(Self::BTN_OUTLINE_PREFIX);
|
||||||
|
classes.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
Self::Link => {
|
||||||
|
classes.push_str(Self::BTN_LINK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `btn-*` correspondiente al color del botón.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// assert_eq!(
|
||||||
|
/// ButtonColor::Background(Color::Primary).to_class(),
|
||||||
|
/// "btn-primary"
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(
|
||||||
|
/// ButtonColor::Outline(Color::Danger).to_class(),
|
||||||
|
/// "btn-outline-danger"
|
||||||
|
/// );
|
||||||
|
/// assert_eq!(ButtonColor::Link.to_class(), "btn-link");
|
||||||
|
/// assert_eq!(ButtonColor::Default.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Default => String::new(),
|
||||||
|
Self::Background(c) => {
|
||||||
|
let color = c.as_str();
|
||||||
|
let mut class = String::with_capacity(Self::BTN_PREFIX.len() + color.len());
|
||||||
|
class.push_str(Self::BTN_PREFIX);
|
||||||
|
class.push_str(color);
|
||||||
|
class
|
||||||
|
}
|
||||||
|
Self::Outline(c) => {
|
||||||
|
let color = c.as_str();
|
||||||
|
let mut class = String::with_capacity(Self::BTN_OUTLINE_PREFIX.len() + color.len());
|
||||||
|
class.push_str(Self::BTN_OUTLINE_PREFIX);
|
||||||
|
class.push_str(color);
|
||||||
|
class
|
||||||
|
}
|
||||||
|
Self::Link => Self::BTN_LINK.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -35,7 +91,7 @@ impl fmt::Display for ButtonColor {
|
||||||
// **< ButtonSize >*********************************************************************************
|
// **< ButtonSize >*********************************************************************************
|
||||||
|
|
||||||
/// Tamaño visual de un botón.
|
/// Tamaño visual de un botón.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ButtonSize {
|
pub enum ButtonSize {
|
||||||
/// Tamaño por defecto del tema (no añade clase).
|
/// Tamaño por defecto del tema (no añade clase).
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -46,13 +102,42 @@ pub enum ButtonSize {
|
||||||
Large,
|
Large,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
impl ButtonSize {
|
||||||
impl fmt::Display for ButtonSize {
|
const BTN_SM: &str = "btn-sm";
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
const BTN_LG: &str = "btn-lg";
|
||||||
|
|
||||||
|
// Añade la clase de tamaño `btn-sm` o `btn-lg` a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
if let Self::Default = self {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
match self {
|
match self {
|
||||||
Self::Default => Ok(()),
|
Self::Default => unreachable!(),
|
||||||
Self::Small => f.write_str("btn-sm"),
|
Self::Small => classes.push_str(Self::BTN_SM),
|
||||||
Self::Large => f.write_str("btn-lg"),
|
Self::Large => classes.push_str(Self::BTN_LG),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `btn-sm` o `btn-lg` correspondiente al tamaño del botón.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// assert_eq!(ButtonSize::Small.to_class(), "btn-sm");
|
||||||
|
/// assert_eq!(ButtonSize::Large.to_class(), "btn-lg");
|
||||||
|
/// assert_eq!(ButtonSize::Default.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Default => String::new(),
|
||||||
|
Self::Small => Self::BTN_SM.to_string(),
|
||||||
|
Self::Large => Self::BTN_LG.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
// **< Color >**************************************************************************************
|
// **< Color >**************************************************************************************
|
||||||
|
|
||||||
/// Paleta de colores temáticos.
|
/// Paleta de colores temáticos.
|
||||||
|
|
@ -11,7 +9,7 @@ use std::fmt;
|
||||||
/// ([`classes::Background`](crate::theme::classes::Background)), bordes
|
/// ([`classes::Background`](crate::theme::classes::Background)), bordes
|
||||||
/// ([`classes::Border`](crate::theme::classes::Border)) y texto
|
/// ([`classes::Border`](crate::theme::classes::Border)) y texto
|
||||||
/// ([`classes::Text`](crate::theme::classes::Text)).
|
/// ([`classes::Text`](crate::theme::classes::Text)).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Color {
|
pub enum Color {
|
||||||
#[default]
|
#[default]
|
||||||
Primary,
|
Primary,
|
||||||
|
|
@ -24,20 +22,45 @@ pub enum Color {
|
||||||
Dark,
|
Dark,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
// Devuelve el nombre del color.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl fmt::Display for Color {
|
#[inline]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
pub(crate) const fn as_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Self::Primary => f.write_str("primary"),
|
Self::Primary => "primary",
|
||||||
Self::Secondary => f.write_str("secondary"),
|
Self::Secondary => "secondary",
|
||||||
Self::Success => f.write_str("success"),
|
Self::Success => "success",
|
||||||
Self::Info => f.write_str("info"),
|
Self::Info => "info",
|
||||||
Self::Warning => f.write_str("warning"),
|
Self::Warning => "warning",
|
||||||
Self::Danger => f.write_str("danger"),
|
Self::Danger => "danger",
|
||||||
Self::Light => f.write_str("light"),
|
Self::Light => "light",
|
||||||
Self::Dark => f.write_str("dark"),
|
Self::Dark => "dark",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Añade el nombre del color a la cadena de clases (reservado).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(self.as_str());
|
||||||
|
} */
|
||||||
|
|
||||||
|
/// Devuelve la clase correspondiente al color.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// assert_eq!(Color::Primary.to_class(), "primary");
|
||||||
|
/// assert_eq!(Color::Danger.to_class(), "danger");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
self.as_str().to_owned()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Opacity >************************************************************************************
|
// **< Opacity >************************************************************************************
|
||||||
|
|
@ -48,7 +71,7 @@ impl fmt::Display for Color {
|
||||||
/// ([`classes::Background`](crate::theme::classes::Background)), de los bordes `border-opacity-*`
|
/// ([`classes::Background`](crate::theme::classes::Background)), de los bordes `border-opacity-*`
|
||||||
/// ([`classes::Border`](crate::theme::classes::Border)) o del texto `text-opacity-*`
|
/// ([`classes::Border`](crate::theme::classes::Border)) o del texto `text-opacity-*`
|
||||||
/// ([`classes::Text`](crate::theme::classes::Text)).
|
/// ([`classes::Text`](crate::theme::classes::Text)).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Opacity {
|
pub enum Opacity {
|
||||||
/// No define ninguna clase.
|
/// No define ninguna clase.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -68,39 +91,95 @@ pub enum Opacity {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Opacity {
|
impl Opacity {
|
||||||
|
const OPACITY: &str = "opacity";
|
||||||
|
const OPACITY_PREFIX: &str = "-opacity";
|
||||||
|
|
||||||
|
// Devuelve el sufijo para `*opacity-*`, o `None` si no define ninguna clase.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[inline]
|
#[inline]
|
||||||
const fn suffix(&self) -> &'static str {
|
const fn suffix(self) -> Option<&'static str> {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => "",
|
Self::Default => None,
|
||||||
Self::Opaque => "opacity-100",
|
Self::Opaque => Some("-100"),
|
||||||
Self::SemiOpaque => "opacity-75",
|
Self::SemiOpaque => Some("-75"),
|
||||||
Self::Half => "opacity-50",
|
Self::Half => Some("-50"),
|
||||||
Self::SemiTransparent => "opacity-25",
|
Self::SemiTransparent => Some("-25"),
|
||||||
Self::AlmostTransparent => "opacity-10",
|
Self::AlmostTransparent => Some("-10"),
|
||||||
Self::Transparent => "opacity-0",
|
Self::Transparent => Some("-0"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Añade la opacidad a la cadena de clases usando el prefijo dado (`bg`, `border`, `text`, o
|
||||||
|
// vacío para `opacity-*`).
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn to_class(&self, prefix: impl AsRef<str>) -> String {
|
pub(crate) fn push_class(self, classes: &mut String, prefix: &str) {
|
||||||
match self {
|
if let Some(suffix) = self.suffix() {
|
||||||
Self::Default => String::new(),
|
if !classes.is_empty() {
|
||||||
_ => join_pair!(prefix, "-", self.suffix()),
|
classes.push(' ');
|
||||||
}
|
}
|
||||||
|
if prefix.is_empty() {
|
||||||
|
classes.push_str(Self::OPACITY);
|
||||||
|
} else {
|
||||||
|
classes.push_str(prefix);
|
||||||
|
classes.push_str(Self::OPACITY_PREFIX);
|
||||||
|
}
|
||||||
|
classes.push_str(suffix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Opacity {
|
// Devuelve la clase de opacidad con el prefijo dado (`bg`, `border`, `text`, o vacío para
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
// `opacity-*`).
|
||||||
f.write_str(self.suffix())
|
//
|
||||||
|
// # Ejemplos
|
||||||
|
//
|
||||||
|
// ```rust
|
||||||
|
// # use pagetop_bootsier::prelude::*;
|
||||||
|
// assert_eq!(Opacity::Opaque.class_with(""), "opacity-100");
|
||||||
|
// assert_eq!(Opacity::Half.class_with("bg"), "bg-opacity-50");
|
||||||
|
// assert_eq!(Opacity::SemiTransparent.class_with("text"), "text-opacity-25");
|
||||||
|
// assert_eq!(Opacity::Default.class_with("bg"), "");
|
||||||
|
// ```
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn class_with(self, prefix: &str) -> String {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
let base_len = if prefix.is_empty() {
|
||||||
|
Self::OPACITY.len()
|
||||||
|
} else {
|
||||||
|
prefix.len() + Self::OPACITY_PREFIX.len()
|
||||||
|
};
|
||||||
|
let mut class = String::with_capacity(base_len + suffix.len());
|
||||||
|
if prefix.is_empty() {
|
||||||
|
class.push_str(Self::OPACITY);
|
||||||
|
} else {
|
||||||
|
class.push_str(prefix);
|
||||||
|
class.push_str(Self::OPACITY_PREFIX);
|
||||||
|
}
|
||||||
|
class.push_str(suffix);
|
||||||
|
return class;
|
||||||
|
}
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase de opacidad `opacity-*`.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// assert_eq!(Opacity::Opaque.to_class(), "opacity-100");
|
||||||
|
/// assert_eq!(Opacity::Half.to_class(), "opacity-50");
|
||||||
|
/// assert_eq!(Opacity::Default.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
self.class_with("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< ColorBg >************************************************************************************
|
// **< ColorBg >************************************************************************************
|
||||||
|
|
||||||
/// Colores `bg-*` para el fondo.
|
/// Colores `bg-*` para el fondo.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ColorBg {
|
pub enum ColorBg {
|
||||||
/// No define ninguna clase.
|
/// No define ninguna clase.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -123,27 +202,83 @@ pub enum ColorBg {
|
||||||
Transparent,
|
Transparent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ColorBg {
|
||||||
|
const BG: &str = "bg";
|
||||||
|
const BG_PREFIX: &str = "bg-";
|
||||||
|
|
||||||
|
// Devuelve el sufijo de la clase `bg-*`, o `None` si no define ninguna clase.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl fmt::Display for ColorBg {
|
#[inline]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
const fn suffix(self) -> Option<&'static str> {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => Ok(()),
|
Self::Default => None,
|
||||||
Self::Body => f.write_str("bg-body"),
|
Self::Body => Some("-body"),
|
||||||
Self::BodySecondary => f.write_str("bg-body-secondary"),
|
Self::BodySecondary => Some("-body-secondary"),
|
||||||
Self::BodyTertiary => f.write_str("bg-body-tertiary"),
|
Self::BodyTertiary => Some("-body-tertiary"),
|
||||||
Self::Theme(c) => write!(f, "bg-{c}"),
|
Self::Theme(_) => Some(""),
|
||||||
Self::Subtle(c) => write!(f, "bg-{c}-subtle"),
|
Self::Subtle(_) => Some("-subtle"),
|
||||||
Self::Black => f.write_str("bg-black"),
|
Self::Black => Some("-black"),
|
||||||
Self::White => f.write_str("bg-white"),
|
Self::White => Some("-white"),
|
||||||
Self::Transparent => f.write_str("bg-transparent"),
|
Self::Transparent => Some("-transparent"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Añade la clase de fondo `bg-*` a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(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(Self::BG_PREFIX);
|
||||||
|
classes.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
_ => classes.push_str(Self::BG),
|
||||||
|
}
|
||||||
|
classes.push_str(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `bg-*` correspondiente al fondo.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// assert_eq!(ColorBg::Body.to_class(), "bg-body");
|
||||||
|
/// assert_eq!(ColorBg::Theme(Color::Primary).to_class(), "bg-primary");
|
||||||
|
/// assert_eq!(ColorBg::Subtle(Color::Warning).to_class(), "bg-warning-subtle");
|
||||||
|
/// assert_eq!(ColorBg::Transparent.to_class(), "bg-transparent");
|
||||||
|
/// assert_eq!(ColorBg::Default.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
let base_len = match self {
|
||||||
|
Self::Theme(c) | Self::Subtle(c) => Self::BG_PREFIX.len() + c.as_str().len(),
|
||||||
|
_ => Self::BG.len(),
|
||||||
|
};
|
||||||
|
let mut class = String::with_capacity(base_len + suffix.len());
|
||||||
|
match self {
|
||||||
|
Self::Theme(c) | Self::Subtle(c) => {
|
||||||
|
class.push_str(Self::BG_PREFIX);
|
||||||
|
class.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
_ => class.push_str(Self::BG),
|
||||||
|
}
|
||||||
|
class.push_str(suffix);
|
||||||
|
return class;
|
||||||
|
}
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< ColorText >**********************************************************************************
|
// **< ColorText >**********************************************************************************
|
||||||
|
|
||||||
/// Colores `text-*` para el texto.
|
/// Colores `text-*` para el texto.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ColorText {
|
pub enum ColorText {
|
||||||
/// No define ninguna clase.
|
/// No define ninguna clase.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -166,19 +301,75 @@ pub enum ColorText {
|
||||||
White,
|
White,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ColorText {
|
||||||
|
const TEXT: &str = "text";
|
||||||
|
const TEXT_PREFIX: &str = "text-";
|
||||||
|
|
||||||
|
// Devuelve el sufijo de la clase `text-*`, o `None` si no define ninguna clase.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl fmt::Display for ColorText {
|
#[inline]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
const fn suffix(self) -> Option<&'static str> {
|
||||||
match self {
|
match self {
|
||||||
Self::Default => Ok(()),
|
Self::Default => None,
|
||||||
Self::Body => f.write_str("text-body"),
|
Self::Body => Some("-body"),
|
||||||
Self::BodyEmphasis => f.write_str("text-body-emphasis"),
|
Self::BodyEmphasis => Some("-body-emphasis"),
|
||||||
Self::BodySecondary => f.write_str("text-body-secondary"),
|
Self::BodySecondary => Some("-body-secondary"),
|
||||||
Self::BodyTertiary => f.write_str("text-body-tertiary"),
|
Self::BodyTertiary => Some("-body-tertiary"),
|
||||||
Self::Theme(c) => write!(f, "text-{c}"),
|
Self::Theme(_) => Some(""),
|
||||||
Self::Emphasis(c) => write!(f, "text-{c}-emphasis"),
|
Self::Emphasis(_) => Some("-emphasis"),
|
||||||
Self::Black => f.write_str("text-black"),
|
Self::Black => Some("-black"),
|
||||||
Self::White => f.write_str("text-white"),
|
Self::White => Some("-white"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Añade la clase de texto `text-*` a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(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(Self::TEXT_PREFIX);
|
||||||
|
classes.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
_ => classes.push_str(Self::TEXT),
|
||||||
|
}
|
||||||
|
classes.push_str(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `text-*` correspondiente al color del texto.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// assert_eq!(ColorText::Body.to_class(), "text-body");
|
||||||
|
/// assert_eq!(ColorText::Theme(Color::Primary).to_class(), "text-primary");
|
||||||
|
/// assert_eq!(ColorText::Emphasis(Color::Danger).to_class(), "text-danger-emphasis");
|
||||||
|
/// assert_eq!(ColorText::Black.to_class(), "text-black");
|
||||||
|
/// assert_eq!(ColorText::Default.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
let base_len = match self {
|
||||||
|
Self::Theme(c) | Self::Emphasis(c) => Self::TEXT_PREFIX.len() + c.as_str().len(),
|
||||||
|
_ => Self::TEXT.len(),
|
||||||
|
};
|
||||||
|
let mut class = String::with_capacity(base_len + suffix.len());
|
||||||
|
match self {
|
||||||
|
Self::Theme(c) | Self::Emphasis(c) => {
|
||||||
|
class.push_str(Self::TEXT_PREFIX);
|
||||||
|
class.push_str(c.as_str());
|
||||||
|
}
|
||||||
|
_ => class.push_str(Self::TEXT),
|
||||||
|
}
|
||||||
|
class.push_str(suffix);
|
||||||
|
return class;
|
||||||
|
}
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
104
extensions/pagetop-bootsier/src/theme/aux/layout.rs
Normal file
104
extensions/pagetop-bootsier/src/theme/aux/layout.rs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
// **< ScaleSize >**********************************************************************************
|
||||||
|
|
||||||
|
/// Escala discreta de tamaños para definir clases utilitarias.
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum ScaleSize {
|
||||||
|
/// Sin tamaño (no define ninguna clase).
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
/// Tamaño automático.
|
||||||
|
Auto,
|
||||||
|
/// Escala cero.
|
||||||
|
Zero,
|
||||||
|
/// Escala uno.
|
||||||
|
One,
|
||||||
|
/// Escala dos.
|
||||||
|
Two,
|
||||||
|
/// Escala tres.
|
||||||
|
Three,
|
||||||
|
/// Escala cuatro.
|
||||||
|
Four,
|
||||||
|
/// Escala cinco.
|
||||||
|
Five,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScaleSize {
|
||||||
|
// Devuelve el sufijo para el tamaño (`"-0"`, `"-1"`, etc.), o `None` si no define ninguna
|
||||||
|
// clase, o `""` para el tamaño automático.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn suffix(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
Self::None => None,
|
||||||
|
Self::Auto => Some(""),
|
||||||
|
Self::Zero => Some("-0"),
|
||||||
|
Self::One => Some("-1"),
|
||||||
|
Self::Two => Some("-2"),
|
||||||
|
Self::Three => Some("-3"),
|
||||||
|
Self::Four => Some("-4"),
|
||||||
|
Self::Five => Some("-5"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade el tamaño a la cadena de clases usando el prefijo dado.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String, prefix: &str) {
|
||||||
|
if !prefix.is_empty() {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(prefix);
|
||||||
|
classes.push_str(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Devuelve la clase del tamaño para el prefijo, o una cadena vacía si no aplica (reservado).
|
||||||
|
//
|
||||||
|
// # Ejemplo
|
||||||
|
//
|
||||||
|
// ```rust
|
||||||
|
// # use pagetop_bootsier::prelude::*;
|
||||||
|
// assert_eq!(ScaleSize::Auto.class_with("border"), "border");
|
||||||
|
// assert_eq!(ScaleSize::Zero.class_with("m"), "m-0");
|
||||||
|
// assert_eq!(ScaleSize::Three.class_with("p"), "p-3");
|
||||||
|
// assert_eq!(ScaleSize::None.class_with("border"), "");
|
||||||
|
// ```
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn class_with(self, prefix: &str) -> String {
|
||||||
|
if !prefix.is_empty() {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
let mut class = String::with_capacity(prefix.len() + suffix.len());
|
||||||
|
class.push_str(prefix);
|
||||||
|
class.push_str(suffix);
|
||||||
|
return class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::new()
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Side >***************************************************************************************
|
||||||
|
|
||||||
|
/// Lados sobre los que aplicar una clase utilitaria (respetando LTR/RTL).
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum Side {
|
||||||
|
/// Todos los lados.
|
||||||
|
#[default]
|
||||||
|
All,
|
||||||
|
/// Lado superior.
|
||||||
|
Top,
|
||||||
|
/// Lado inferior.
|
||||||
|
Bottom,
|
||||||
|
/// Lado lógico de inicio (respetando RTL).
|
||||||
|
Start,
|
||||||
|
/// Lado lógico de fin (respetando RTL).
|
||||||
|
End,
|
||||||
|
/// Lados lógicos laterales (abreviatura *x*).
|
||||||
|
LeftAndRight,
|
||||||
|
/// Lados superior e inferior (abreviatura *y*).
|
||||||
|
TopAndBottom,
|
||||||
|
}
|
||||||
|
|
@ -1,52 +1,117 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
/// Radio para el redondeo de esquinas ([`classes::Rounded`](crate::theme::classes::Rounded)).
|
/// Radio para el redondeo de esquinas ([`classes::Rounded`](crate::theme::classes::Rounded)).
|
||||||
///
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
/// Mapea a `rounded`, `rounded-0`, `rounded-{1..5}`, `rounded-circle` y `rounded-pill`.
|
|
||||||
///
|
|
||||||
/// - `None` no añade ninguna clase.
|
|
||||||
/// - `Default` genera `rounded` (radio por defecto del tema).
|
|
||||||
/// - `Zero` genera `rounded-0` (sin redondeo).
|
|
||||||
/// - `Scale{1..5}` genera `rounded-{1..5}` (radio creciente).
|
|
||||||
/// - `Circle` genera `rounded-circle`.
|
|
||||||
/// - `Pill` genera `rounded-pill`.
|
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub enum RoundedRadius {
|
pub enum RoundedRadius {
|
||||||
|
/// No define ninguna clase.
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
|
/// Genera `rounded` (radio por defecto del tema).
|
||||||
Default,
|
Default,
|
||||||
|
/// Genera `rounded-0` (sin redondeo).
|
||||||
Zero,
|
Zero,
|
||||||
|
/// Genera `rounded-1`.
|
||||||
Scale1,
|
Scale1,
|
||||||
|
/// Genera `rounded-2`.
|
||||||
Scale2,
|
Scale2,
|
||||||
|
/// Genera `rounded-3`.
|
||||||
Scale3,
|
Scale3,
|
||||||
|
/// Genera `rounded-4`.
|
||||||
Scale4,
|
Scale4,
|
||||||
|
/// Genera `rounded-5`.
|
||||||
Scale5,
|
Scale5,
|
||||||
|
/// Genera `rounded-circle`.
|
||||||
Circle,
|
Circle,
|
||||||
|
/// Genera `rounded-pill`.
|
||||||
Pill,
|
Pill,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoundedRadius {
|
impl RoundedRadius {
|
||||||
|
const ROUNDED: &str = "rounded";
|
||||||
|
|
||||||
|
// Devuelve el sufijo para `*rounded-*`, o `None` si no define ninguna clase, o `""` para el
|
||||||
|
// redondeo por defecto.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub(crate) fn to_class(&self, prefix: impl AsRef<str>) -> String {
|
#[inline]
|
||||||
|
const fn suffix(self) -> Option<&'static str> {
|
||||||
match self {
|
match self {
|
||||||
RoundedRadius::None => String::new(),
|
Self::None => None,
|
||||||
RoundedRadius::Default => String::from(prefix.as_ref()),
|
Self::Default => Some(""),
|
||||||
RoundedRadius::Zero => join!(prefix, "-0"),
|
Self::Zero => Some("-0"),
|
||||||
RoundedRadius::Scale1 => join!(prefix, "-1"),
|
Self::Scale1 => Some("-1"),
|
||||||
RoundedRadius::Scale2 => join!(prefix, "-2"),
|
Self::Scale2 => Some("-2"),
|
||||||
RoundedRadius::Scale3 => join!(prefix, "-3"),
|
Self::Scale3 => Some("-3"),
|
||||||
RoundedRadius::Scale4 => join!(prefix, "-4"),
|
Self::Scale4 => Some("-4"),
|
||||||
RoundedRadius::Scale5 => join!(prefix, "-5"),
|
Self::Scale5 => Some("-5"),
|
||||||
RoundedRadius::Circle => join!(prefix, "-circle"),
|
Self::Circle => Some("-circle"),
|
||||||
RoundedRadius::Pill => join!(prefix, "-pill"),
|
Self::Pill => Some("-pill"),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for RoundedRadius {
|
// Añade el redondeo de esquinas a la cadena de clases usando el prefijo dado (`rounded-top`,
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
// `rounded-bottom-start`, o vacío para `rounded-*`).
|
||||||
write!(f, "{}", self.to_class("rounded"))
|
#[inline]
|
||||||
|
pub(crate) fn push_class(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(Self::ROUNDED);
|
||||||
|
} else {
|
||||||
|
classes.push_str(prefix);
|
||||||
|
}
|
||||||
|
classes.push_str(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devuelve la clase para el redondeo de esquinas con el prefijo dado (`rounded-top`,
|
||||||
|
// `rounded-bottom-start`, o vacío para `rounded-*`).
|
||||||
|
//
|
||||||
|
// # Ejemplos
|
||||||
|
//
|
||||||
|
// ```rust
|
||||||
|
// # use pagetop_bootsier::prelude::*;
|
||||||
|
// assert_eq!(RoundedRadius::Scale2.class_with(""), "rounded-2");
|
||||||
|
// assert_eq!(RoundedRadius::Zero.class_with("rounded-top"), "rounded-top-0");
|
||||||
|
// assert_eq!(RoundedRadius::Scale3.class_with("rounded-top-end"), "rounded-top-end-3");
|
||||||
|
// assert_eq!(RoundedRadius::Circle.class_with(""), "rounded-circle");
|
||||||
|
// assert_eq!(RoundedRadius::None.class_with("rounded-bottom-start"), "");
|
||||||
|
// ```
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn class_with(self, prefix: &str) -> String {
|
||||||
|
if let Some(suffix) = self.suffix() {
|
||||||
|
let base_len = if prefix.is_empty() {
|
||||||
|
Self::ROUNDED.len()
|
||||||
|
} else {
|
||||||
|
prefix.len()
|
||||||
|
};
|
||||||
|
let mut class = String::with_capacity(base_len + suffix.len());
|
||||||
|
if prefix.is_empty() {
|
||||||
|
class.push_str(Self::ROUNDED);
|
||||||
|
} else {
|
||||||
|
class.push_str(prefix);
|
||||||
|
}
|
||||||
|
class.push_str(suffix);
|
||||||
|
return class;
|
||||||
|
}
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Devuelve la clase `rounded-*` para el redondeo de esquinas.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// assert_eq!(RoundedRadius::Default.to_class(), "rounded");
|
||||||
|
/// assert_eq!(RoundedRadius::Zero.to_class(), "rounded-0");
|
||||||
|
/// assert_eq!(RoundedRadius::Scale3.to_class(), "rounded-3");
|
||||||
|
/// assert_eq!(RoundedRadius::Circle.to_class(), "rounded-circle");
|
||||||
|
/// assert_eq!(RoundedRadius::None.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
self.class_with("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,6 @@ pub use border::Border;
|
||||||
|
|
||||||
mod rounded;
|
mod rounded;
|
||||||
pub use rounded::Rounded;
|
pub use rounded::Rounded;
|
||||||
|
|
||||||
|
mod layout;
|
||||||
|
pub use layout::{Margin, Padding};
|
||||||
|
|
|
||||||
|
|
@ -1,122 +1,113 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::aux::{BorderColor, BorderSize, Opacity};
|
use crate::theme::aux::{BorderColor, Opacity, ScaleSize, Side};
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
/// Clases para crear **bordes**.
|
/// Clases para crear **bordes**.
|
||||||
///
|
///
|
||||||
/// Permite:
|
/// Permite:
|
||||||
///
|
///
|
||||||
|
/// - Iniciar un borde sin tamaño inicial (`Border::default()`).
|
||||||
|
/// - Crear un borde con tamaño por defecto (`Border::new()`).
|
||||||
|
/// - Ajustar el tamaño de cada **lado lógico** (`side`, respetando LTR/RTL).
|
||||||
/// - Definir un tamaño **global** para todo el borde (`size`).
|
/// - Definir un tamaño **global** para todo el borde (`size`).
|
||||||
/// - Ajustar el tamaño de cada **lado lógico** (`top`, `end`, `bottom`, `start`, **en este orden**,
|
|
||||||
/// respetando LTR/RTL).
|
|
||||||
/// - Aplicar un **color** al borde (`BorderColor`).
|
/// - Aplicar un **color** al borde (`BorderColor`).
|
||||||
/// - Aplicar un nivel de **opacidad** (`Opacity`).
|
/// - Aplicar un nivel de **opacidad** (`Opacity`).
|
||||||
///
|
///
|
||||||
/// # Comportamiento aditivo / sustractivo
|
/// # Comportamiento aditivo / sustractivo
|
||||||
///
|
///
|
||||||
/// - **Aditivo**: basta con crear un borde sin tamaño con `classes::Border::new()` para ir
|
/// - **Aditivo**: basta con crear un borde sin tamaño con `classes::Border::default()` para ir
|
||||||
/// añadiendo cada lado lógico con el tamaño deseado usando `BorderSize::Scale{1..5}`.
|
/// añadiendo cada lado lógico con el tamaño deseado usando `ScaleSize::{One..Five}`.
|
||||||
///
|
///
|
||||||
/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. utilizando
|
/// - **Sustractivo**: se crea un borde con tamaño predefinido, p. ej. usando
|
||||||
/// `classes::Border::with(BorderSize::Scale2)` y eliminar los lados deseados con `BorderSize::Zero`.
|
/// `classes::Border::new()` o `classes::Border::with(ScaleSize::Two)` y eliminar los lados
|
||||||
|
/// deseados con `ScaleSize::Zero`.
|
||||||
///
|
///
|
||||||
/// - **Anchos diferentes por lado**: usando `BorderSize::Scale{1..5}` en cada lado deseado.
|
/// - **Anchos diferentes por lado**: usando `ScaleSize::{Zero..Five}` en cada lado deseado.
|
||||||
///
|
///
|
||||||
/// # Ejemplos
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// **Borde global:**
|
/// **Borde global:**
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let b = classes::Border::with(BorderSize::Scale2);
|
/// let b = classes::Border::with(ScaleSize::Two);
|
||||||
/// assert_eq!(b.to_string(), "border-2");
|
/// assert_eq!(b.to_class(), "border-2");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// **Aditivo (solo borde superior):**
|
/// **Aditivo (solo borde superior):**
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let b = classes::Border::new().with_top(BorderSize::Scale1);
|
/// let b = classes::Border::default().with_side(Side::Top, ScaleSize::One);
|
||||||
/// assert_eq!(b.to_string(), "border-top-1");
|
/// assert_eq!(b.to_class(), "border-top-1");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// **Sustractivo (borde global menos el superior):**
|
/// **Sustractivo (borde global menos el superior):**
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let b = classes::Border::with(BorderSize::Default).with_top(BorderSize::Zero);
|
/// let b = classes::Border::new().with_side(Side::Top, ScaleSize::Zero);
|
||||||
/// assert_eq!(b.to_string(), "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):**
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let b = classes::Border::new().with_start(BorderSize::Scale2).with_end(BorderSize::Scale4);
|
/// let b = classes::Border::default()
|
||||||
/// assert_eq!(b.to_string(), "border-end-4 border-start-2");
|
/// .with_side(Side::Start, ScaleSize::Two)
|
||||||
|
/// .with_side(Side::End, ScaleSize::Four);
|
||||||
|
/// assert_eq!(b.to_class(), "border-end-4 border-start-2");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// **Combinado (ejemplo completo):**
|
/// **Combinado (ejemplo completo):**
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let b = classes::Border::with(BorderSize::Default) // Borde global por defecto.
|
/// let b = classes::Border::new() // Borde por defecto.
|
||||||
/// .with_top(BorderSize::Zero) // Quita borde superior.
|
/// .with_side(Side::Top, ScaleSize::Zero) // Quita borde superior.
|
||||||
/// .with_end(BorderSize::Scale3) // Ancho 3 para el lado lógico final.
|
/// .with_side(Side::End, ScaleSize::Three) // Ancho 3 para el lado lógico final.
|
||||||
/// .with_color(BorderColor::Theme(Color::Primary))
|
/// .with_color(BorderColor::Theme(Color::Primary))
|
||||||
/// .with_opacity(Opacity::Half);
|
/// .with_opacity(Opacity::Half);
|
||||||
///
|
///
|
||||||
/// assert_eq!(b.to_string(), "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]
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Border {
|
pub struct Border {
|
||||||
size : BorderSize,
|
all : ScaleSize,
|
||||||
top : BorderSize,
|
top : ScaleSize,
|
||||||
end : BorderSize,
|
end : ScaleSize,
|
||||||
bottom : BorderSize,
|
bottom : ScaleSize,
|
||||||
start : BorderSize,
|
start : ScaleSize,
|
||||||
color : BorderColor,
|
color : BorderColor,
|
||||||
opacity: Opacity,
|
opacity: Opacity,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Border {
|
impl Border {
|
||||||
/// Prepara un borde **sin tamaño global** de partida.
|
/// Prepara un borde del tamaño predefinido. Equivale a `border` (ancho por defecto del tema).
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::with(ScaleSize::Auto)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Crea un borde **con tamaño global** (`size`).
|
/// Crea un borde **con un tamaño global** (`size`).
|
||||||
pub fn with(size: BorderSize) -> Self {
|
pub fn with(size: ScaleSize) -> Self {
|
||||||
Self::default().with_size(size)
|
Self::default().with_side(Side::All, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// **< Border BUILDER >*************************************************************************
|
// **< Border BUILDER >*************************************************************************
|
||||||
|
|
||||||
/// Establece el tamaño global del borde (`border*`).
|
pub fn with_side(mut self, side: Side, size: ScaleSize) -> Self {
|
||||||
pub fn with_size(mut self, size: BorderSize) -> Self {
|
match side {
|
||||||
self.size = size;
|
Side::All => self.all = size,
|
||||||
self
|
Side::Top => self.top = size,
|
||||||
}
|
Side::Bottom => self.bottom = size,
|
||||||
|
Side::Start => self.start = size,
|
||||||
/// Establece el tamaño del borde superior (`border-top-*`).
|
Side::End => self.end = size,
|
||||||
pub fn with_top(mut self, size: BorderSize) -> Self {
|
Side::LeftAndRight => {
|
||||||
self.top = size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Establece el tamaño del borde en el lado lógico final (`border-end-*`). Respeta LTR/RTL.
|
|
||||||
pub fn with_end(mut self, size: BorderSize) -> Self {
|
|
||||||
self.end = size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Establece el tamaño del borde inferior (`border-bottom-*`).
|
|
||||||
pub fn with_bottom(mut self, size: BorderSize) -> Self {
|
|
||||||
self.bottom = size;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Establece el tamaño del borde en el lado lógico inicial (`border-start-*`). Respeta LTR/RTL.
|
|
||||||
pub fn with_start(mut self, size: BorderSize) -> Self {
|
|
||||||
self.start = size;
|
self.start = size;
|
||||||
|
self.end = size;
|
||||||
|
}
|
||||||
|
Side::TopAndBottom => {
|
||||||
|
self.top = size;
|
||||||
|
self.bottom = size;
|
||||||
|
}
|
||||||
|
};
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,25 +122,54 @@ impl Border {
|
||||||
self.opacity = opacity;
|
self.opacity = opacity;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **< Border HELPERS >*************************************************************************
|
||||||
|
|
||||||
|
/// Añade las clases de borde a la cadena de clases.
|
||||||
|
///
|
||||||
|
/// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`,
|
||||||
|
/// *color* y *opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
self.all .push_class(classes, "border");
|
||||||
|
self.top .push_class(classes, "border-top");
|
||||||
|
self.end .push_class(classes, "border-end");
|
||||||
|
self.bottom .push_class(classes, "border-bottom");
|
||||||
|
self.start .push_class(classes, "border-start");
|
||||||
|
self.color .push_class(classes);
|
||||||
|
self.opacity.push_class(classes, "border");
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Border {
|
/// Devuelve las clases de borde como cadena (`"border-2"`,
|
||||||
/// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`, *color*
|
/// `"border border-top-0 border-end-3 border-primary border-opacity-50"`, etc.).
|
||||||
/// y *opacidad*; respetando LTR/RTL y omitiendo las definiciones vacías.
|
///
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
/// Si no se define ningún tamaño, color ni opacidad, devuelve `""`.
|
||||||
write!(
|
#[inline]
|
||||||
f,
|
pub fn to_class(self) -> String {
|
||||||
"{}",
|
let mut classes = String::new();
|
||||||
[
|
self.push_class(&mut classes);
|
||||||
self.size.to_string(),
|
classes
|
||||||
self.top.to_class("border-top"),
|
}
|
||||||
self.end.to_class("border-end"),
|
}
|
||||||
self.bottom.to_class("border-bottom"),
|
|
||||||
self.start.to_class("border-start"),
|
/// Atajo para crear un [`classes::Border`](crate::theme::classes::Border) a partir de un tamaño
|
||||||
self.color.to_string(),
|
/// [`ScaleSize`] aplicado a todo el borde.
|
||||||
self.opacity.to_class("border"),
|
///
|
||||||
]
|
/// # Ejemplos
|
||||||
.join_classes()
|
///
|
||||||
)
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// // Convertir explícitamente con `From::from`:
|
||||||
|
/// let b = classes::Border::from(ScaleSize::Two);
|
||||||
|
/// assert_eq!(b.to_class(), "border-2");
|
||||||
|
///
|
||||||
|
/// // Convertir implícitamente con `into()`:
|
||||||
|
/// let b: classes::Border = ScaleSize::Auto.into();
|
||||||
|
/// assert_eq!(b.to_class(), "border");
|
||||||
|
/// ```
|
||||||
|
impl From<ScaleSize> for Border {
|
||||||
|
fn from(size: ScaleSize) -> Self {
|
||||||
|
Self::with(size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@ use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::aux::{ColorBg, ColorText, Opacity};
|
use crate::theme::aux::{ColorBg, ColorText, Opacity};
|
||||||
|
|
||||||
use std::fmt;
|
// **< Background >*********************************************************************************
|
||||||
|
|
||||||
// **< Bg >*****************************************************************************************
|
|
||||||
|
|
||||||
/// Clases para establecer **color/opacidad del fondo**.
|
/// Clases para establecer **color/opacidad del fondo**.
|
||||||
///
|
///
|
||||||
|
|
@ -14,26 +12,25 @@ use std::fmt;
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// // Sin clases.
|
/// // Sin clases.
|
||||||
/// let s = classes::Background::new();
|
/// let s = classes::Background::new();
|
||||||
/// assert_eq!(s.to_string(), "");
|
/// assert_eq!(s.to_class(), "");
|
||||||
///
|
///
|
||||||
/// // Sólo color de fondo.
|
/// // Sólo color de fondo.
|
||||||
/// let s = classes::Background::with(ColorBg::Theme(Color::Primary));
|
/// let s = classes::Background::with(ColorBg::Theme(Color::Primary));
|
||||||
/// assert_eq!(s.to_string(), "bg-primary");
|
/// assert_eq!(s.to_class(), "bg-primary");
|
||||||
///
|
///
|
||||||
/// // Color más opacidad.
|
/// // Color más opacidad.
|
||||||
/// let s = classes::Background::with(ColorBg::BodySecondary).with_opacity(Opacity::Half);
|
/// let s = classes::Background::with(ColorBg::BodySecondary).with_opacity(Opacity::Half);
|
||||||
/// assert_eq!(s.to_string(), "bg-body-secondary bg-opacity-50");
|
/// assert_eq!(s.to_class(), "bg-body-secondary bg-opacity-50");
|
||||||
///
|
///
|
||||||
/// // Usando `From<ColorBg>`.
|
/// // Usando `From<ColorBg>`.
|
||||||
/// let s: classes::Background = ColorBg::Black.into();
|
/// let s: classes::Background = ColorBg::Black.into();
|
||||||
/// assert_eq!(s.to_string(), "bg-black");
|
/// assert_eq!(s.to_class(), "bg-black");
|
||||||
///
|
///
|
||||||
/// // Usando `From<(ColorBg, Opacity)>`.
|
/// // Usando `From<(ColorBg, Opacity)>`.
|
||||||
/// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into();
|
/// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into();
|
||||||
/// assert_eq!(s.to_string(), "bg-white bg-opacity-25");
|
/// assert_eq!(s.to_class(), "bg-white bg-opacity-25");
|
||||||
/// ```
|
/// ```
|
||||||
#[rustfmt::skip]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub struct Background {
|
pub struct Background {
|
||||||
color: ColorBg,
|
color: ColorBg,
|
||||||
opacity: Opacity,
|
opacity: Opacity,
|
||||||
|
|
@ -50,7 +47,7 @@ impl Background {
|
||||||
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-*`).
|
||||||
pub fn with_color(mut self, color: ColorBg) -> Self {
|
pub fn with_color(mut self, color: ColorBg) -> Self {
|
||||||
|
|
@ -63,14 +60,27 @@ impl Background {
|
||||||
self.opacity = opacity;
|
self.opacity = opacity;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **< Background HELPERS >*********************************************************************
|
||||||
|
|
||||||
|
/// Añade las clases de fondo a la cadena de clases.
|
||||||
|
///
|
||||||
|
/// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`),
|
||||||
|
/// omitiendo los fragmentos vacíos.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
self.color.push_class(classes);
|
||||||
|
self.opacity.push_class(classes, "bg");
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Background {
|
/// Devuelve las clases de fondo como cadena (`"bg-primary"`, `"bg-body-secondary bg-opacity-50"`, etc.).
|
||||||
/// Concatena, en este orden, color del fondo (`bg-*`) y opacidad (`bg-opacity-*`), omitiendo
|
///
|
||||||
/// las definiciones vacías.
|
/// Si no se define ni color ni opacidad, devuelve `""`.
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
#[inline]
|
||||||
let classes = [self.color.to_string(), self.opacity.to_class("bg")].join_classes();
|
pub fn to_class(self) -> String {
|
||||||
write!(f, "{classes}")
|
let mut classes = String::new();
|
||||||
|
self.push_class(&mut classes);
|
||||||
|
classes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,7 +93,7 @@ impl From<(ColorBg, Opacity)> for Background {
|
||||||
/// ```
|
/// ```
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into();
|
/// let s: classes::Background = (ColorBg::White, Opacity::SemiTransparent).into();
|
||||||
/// assert_eq!(s.to_string(), "bg-white bg-opacity-25");
|
/// assert_eq!(s.to_class(), "bg-white bg-opacity-25");
|
||||||
/// ```
|
/// ```
|
||||||
fn from((color, opacity): (ColorBg, Opacity)) -> Self {
|
fn from((color, opacity): (ColorBg, Opacity)) -> Self {
|
||||||
Background::with(color).with_opacity(opacity)
|
Background::with(color).with_opacity(opacity)
|
||||||
|
|
@ -98,7 +108,7 @@ impl From<ColorBg> for Background {
|
||||||
/// ```
|
/// ```
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let s: classes::Background = ColorBg::Black.into();
|
/// let s: classes::Background = ColorBg::Black.into();
|
||||||
/// assert_eq!(s.to_string(), "bg-black");
|
/// assert_eq!(s.to_class(), "bg-black");
|
||||||
/// ```
|
/// ```
|
||||||
fn from(color: ColorBg) -> Self {
|
fn from(color: ColorBg) -> Self {
|
||||||
Background::with(color)
|
Background::with(color)
|
||||||
|
|
@ -115,26 +125,25 @@ impl From<ColorBg> for Background {
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// // Sin clases.
|
/// // Sin clases.
|
||||||
/// let s = classes::Text::new();
|
/// let s = classes::Text::new();
|
||||||
/// assert_eq!(s.to_string(), "");
|
/// assert_eq!(s.to_class(), "");
|
||||||
///
|
///
|
||||||
/// // Sólo color del texto.
|
/// // Sólo color del texto.
|
||||||
/// let s = classes::Text::with(ColorText::Theme(Color::Primary));
|
/// let s = classes::Text::with(ColorText::Theme(Color::Primary));
|
||||||
/// assert_eq!(s.to_string(), "text-primary");
|
/// assert_eq!(s.to_class(), "text-primary");
|
||||||
///
|
///
|
||||||
/// // Color del texto y opacidad.
|
/// // Color del texto y opacidad.
|
||||||
/// let s = classes::Text::new().with_color(ColorText::White).with_opacity(Opacity::SemiTransparent);
|
/// let s = classes::Text::new().with_color(ColorText::White).with_opacity(Opacity::SemiTransparent);
|
||||||
/// assert_eq!(s.to_string(), "text-white text-opacity-25");
|
/// assert_eq!(s.to_class(), "text-white text-opacity-25");
|
||||||
///
|
///
|
||||||
/// // Usando `From<ColorText>`.
|
/// // Usando `From<ColorText>`.
|
||||||
/// let s: classes::Text = ColorText::Black.into();
|
/// let s: classes::Text = ColorText::Black.into();
|
||||||
/// assert_eq!(s.to_string(), "text-black");
|
/// assert_eq!(s.to_class(), "text-black");
|
||||||
///
|
///
|
||||||
/// // Usando `From<(ColorText, Opacity)>`.
|
/// // Usando `From<(ColorText, Opacity)>`.
|
||||||
/// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into();
|
/// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into();
|
||||||
/// assert_eq!(s.to_string(), "text-danger text-opacity-100");
|
/// assert_eq!(s.to_class(), "text-danger text-opacity-100");
|
||||||
/// ```
|
/// ```
|
||||||
#[rustfmt::skip]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
#[derive(AutoDefault)]
|
|
||||||
pub struct Text {
|
pub struct Text {
|
||||||
color: ColorText,
|
color: ColorText,
|
||||||
opacity: Opacity,
|
opacity: Opacity,
|
||||||
|
|
@ -164,13 +173,27 @@ impl Text {
|
||||||
self.opacity = opacity;
|
self.opacity = opacity;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **< Text HELPERS >***************************************************************************
|
||||||
|
|
||||||
|
/// Añade las clases de texto a la cadena de clases.
|
||||||
|
///
|
||||||
|
/// Concatena, en este orden, `text-*` y `text-opacity-*`, omitiendo los fragmentos vacíos.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
self.color.push_class(classes);
|
||||||
|
self.opacity.push_class(classes, "text");
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Text {
|
/// Devuelve las clases de texto como cadena (`"text-primary"`, `"text-white text-opacity-25"`,
|
||||||
/// Concatena, en este orden, `text-*` y `text-opacity-*`, omitiendo las definiciones vacías.
|
/// etc.).
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
///
|
||||||
let classes = [self.color.to_string(), self.opacity.to_class("text")].join_classes();
|
/// Si no se define ni color ni opacidad, devuelve `""`.
|
||||||
write!(f, "{classes}")
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
let mut classes = String::new();
|
||||||
|
self.push_class(&mut classes);
|
||||||
|
classes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,7 +206,7 @@ impl From<(ColorText, Opacity)> for Text {
|
||||||
/// ```
|
/// ```
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into();
|
/// let s: classes::Text = (ColorText::Theme(Color::Danger), Opacity::Opaque).into();
|
||||||
/// assert_eq!(s.to_string(), "text-danger text-opacity-100");
|
/// assert_eq!(s.to_class(), "text-danger text-opacity-100");
|
||||||
/// ```
|
/// ```
|
||||||
fn from((color, opacity): (ColorText, Opacity)) -> Self {
|
fn from((color, opacity): (ColorText, Opacity)) -> Self {
|
||||||
Text::with(color).with_opacity(opacity)
|
Text::with(color).with_opacity(opacity)
|
||||||
|
|
@ -199,7 +222,7 @@ impl From<ColorText> for Text {
|
||||||
/// ```
|
/// ```
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let s: classes::Text = ColorText::Black.into();
|
/// let s: classes::Text = ColorText::Black.into();
|
||||||
/// assert_eq!(s.to_string(), "text-black");
|
/// assert_eq!(s.to_class(), "text-black");
|
||||||
/// ```
|
/// ```
|
||||||
fn from(color: ColorText) -> Self {
|
fn from(color: ColorText) -> Self {
|
||||||
Text::with(color)
|
Text::with(color)
|
||||||
|
|
|
||||||
205
extensions/pagetop-bootsier/src/theme/classes/layout.rs
Normal file
205
extensions/pagetop-bootsier/src/theme/classes/layout.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
||||||
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
use crate::theme::aux::{ScaleSize, Side};
|
||||||
|
use crate::theme::BreakPoint;
|
||||||
|
|
||||||
|
// **< Margin >*************************************************************************************
|
||||||
|
|
||||||
|
/// Clases para establecer **margin** por lado, tamaño y punto de ruptura.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// let m = classes::Margin::with(Side::Top, ScaleSize::Three);
|
||||||
|
/// assert_eq!(m.to_class(), "mt-3");
|
||||||
|
///
|
||||||
|
/// let m = classes::Margin::with(Side::Start, ScaleSize::Auto).with_breakpoint(BreakPoint::LG);
|
||||||
|
/// assert_eq!(m.to_class(), "ms-lg-auto");
|
||||||
|
///
|
||||||
|
/// let m = classes::Margin::with(Side::All, ScaleSize::None);
|
||||||
|
/// assert_eq!(m.to_class(), "");
|
||||||
|
/// ```
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct Margin {
|
||||||
|
side: Side,
|
||||||
|
size: ScaleSize,
|
||||||
|
breakpoint: BreakPoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Margin {
|
||||||
|
/// Crea un **margin** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de
|
||||||
|
/// ruptura.
|
||||||
|
pub fn with(side: Side, size: ScaleSize) -> Self {
|
||||||
|
Margin {
|
||||||
|
side,
|
||||||
|
size,
|
||||||
|
breakpoint: BreakPoint::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Margin BUILDER >*************************************************************************
|
||||||
|
|
||||||
|
/// Establece el punto de ruptura a partir del cual se empieza a aplicar el **margin**.
|
||||||
|
pub fn with_breakpoint(mut self, breakpoint: BreakPoint) -> Self {
|
||||||
|
self.breakpoint = breakpoint;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Margin HELPERS >*************************************************************************
|
||||||
|
|
||||||
|
// Devuelve el prefijo `m*` según el lado.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn side_prefix(&self) -> &'static str {
|
||||||
|
match self.side {
|
||||||
|
Side::All => "m",
|
||||||
|
Side::Top => "mt",
|
||||||
|
Side::Bottom => "mb",
|
||||||
|
Side::Start => "ms",
|
||||||
|
Side::End => "me",
|
||||||
|
Side::LeftAndRight => "mx",
|
||||||
|
Side::TopAndBottom => "my",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devuelve el sufijo del tamaño (`auto`, `0`..`5`), o `None` si no define clase.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn size_suffix(&self) -> Option<&'static str> {
|
||||||
|
match self.size {
|
||||||
|
ScaleSize::None => None,
|
||||||
|
ScaleSize::Auto => Some("auto"),
|
||||||
|
ScaleSize::Zero => Some("0"),
|
||||||
|
ScaleSize::One => Some("1"),
|
||||||
|
ScaleSize::Two => Some("2"),
|
||||||
|
ScaleSize::Three => Some("3"),
|
||||||
|
ScaleSize::Four => Some("4"),
|
||||||
|
ScaleSize::Five => Some("5"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Añade la clase de **margin** a la cadena de clases (reservado).
|
||||||
|
//
|
||||||
|
// No añade nada si `size` es `ScaleSize::None`.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
let Some(size) = self.size_suffix() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.breakpoint
|
||||||
|
.push_class(classes, self.side_prefix(), size);
|
||||||
|
} */
|
||||||
|
|
||||||
|
/// Devuelve la clase de **margin** como cadena (`"mt-3"`, `"ms-lg-auto"`, etc.).
|
||||||
|
///
|
||||||
|
/// Si `size` es `ScaleSize::None`, devuelve `""`.
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
let Some(size) = self.size_suffix() else {
|
||||||
|
return String::new();
|
||||||
|
};
|
||||||
|
self.breakpoint.class_with(self.side_prefix(), size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Padding >************************************************************************************
|
||||||
|
|
||||||
|
/// Clases para establecer **padding** por lado, tamaño y punto de ruptura.
|
||||||
|
///
|
||||||
|
/// # Ejemplos
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// let p = classes::Padding::with(Side::LeftAndRight, ScaleSize::Two);
|
||||||
|
/// assert_eq!(p.to_class(), "px-2");
|
||||||
|
///
|
||||||
|
/// let p = classes::Padding::with(Side::End, ScaleSize::Four).with_breakpoint(BreakPoint::SM);
|
||||||
|
/// assert_eq!(p.to_class(), "pe-sm-4");
|
||||||
|
///
|
||||||
|
/// let p = classes::Padding::with(Side::All, ScaleSize::Auto);
|
||||||
|
/// assert_eq!(p.to_class(), ""); // `Auto` no aplica a padding.
|
||||||
|
/// ```
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct Padding {
|
||||||
|
side: Side,
|
||||||
|
size: ScaleSize,
|
||||||
|
breakpoint: BreakPoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Padding {
|
||||||
|
/// Crea un **padding** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de
|
||||||
|
/// ruptura.
|
||||||
|
pub fn with(side: Side, size: ScaleSize) -> Self {
|
||||||
|
Padding {
|
||||||
|
side,
|
||||||
|
size,
|
||||||
|
breakpoint: BreakPoint::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Padding BUILDER >************************************************************************
|
||||||
|
|
||||||
|
/// Establece el punto de ruptura a partir del cual se empieza a aplicar el **padding**.
|
||||||
|
pub fn with_breakpoint(mut self, breakpoint: BreakPoint) -> Self {
|
||||||
|
self.breakpoint = breakpoint;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Padding HELPERS >************************************************************************
|
||||||
|
|
||||||
|
// Devuelve el prefijo `p*` según el lado.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn prefix(&self) -> &'static str {
|
||||||
|
match self.side {
|
||||||
|
Side::All => "p",
|
||||||
|
Side::Top => "pt",
|
||||||
|
Side::Bottom => "pb",
|
||||||
|
Side::Start => "ps",
|
||||||
|
Side::End => "pe",
|
||||||
|
Side::LeftAndRight => "px",
|
||||||
|
Side::TopAndBottom => "py",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devuelve el sufijo del tamaño (`0`..`5`), o `None` si no define clase.
|
||||||
|
//
|
||||||
|
// Nota: `ScaleSize::Auto` **no aplica** a padding ⇒ devuelve `None`.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn suffix(&self) -> Option<&'static str> {
|
||||||
|
match self.size {
|
||||||
|
ScaleSize::None => None,
|
||||||
|
ScaleSize::Auto => None,
|
||||||
|
ScaleSize::Zero => Some("0"),
|
||||||
|
ScaleSize::One => Some("1"),
|
||||||
|
ScaleSize::Two => Some("2"),
|
||||||
|
ScaleSize::Three => Some("3"),
|
||||||
|
ScaleSize::Four => Some("4"),
|
||||||
|
ScaleSize::Five => Some("5"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Añade la clase de **padding** a la cadena de clases (reservado).
|
||||||
|
//
|
||||||
|
// No añade nada si `size` es `ScaleSize::None` o `ScaleSize::Auto`.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
let Some(size) = self.suffix() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.breakpoint.push_class(classes, self.prefix(), size);
|
||||||
|
} */
|
||||||
|
|
||||||
|
// Devuelve la clase de **padding** como cadena (`"px-2"`, `"pe-sm-4"`, etc.).
|
||||||
|
//
|
||||||
|
// Si `size` es `ScaleSize::None` o `ScaleSize::Auto`, devuelve `""`.
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
let Some(size) = self.suffix() else {
|
||||||
|
return String::new();
|
||||||
|
};
|
||||||
|
self.breakpoint.class_with(self.prefix(), size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,6 @@ use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::aux::RoundedRadius;
|
use crate::theme::aux::RoundedRadius;
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
/// Clases para definir **esquinas redondeadas**.
|
/// Clases para definir **esquinas redondeadas**.
|
||||||
///
|
///
|
||||||
/// Permite:
|
/// Permite:
|
||||||
|
|
@ -20,28 +18,28 @@ use std::fmt;
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let r = classes::Rounded::with(RoundedRadius::Default);
|
/// let r = classes::Rounded::with(RoundedRadius::Default);
|
||||||
/// assert_eq!(r.to_string(), "rounded");
|
/// assert_eq!(r.to_class(), "rounded");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// **Sin redondeo:**
|
/// **Sin redondeo:**
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let r = classes::Rounded::new();
|
/// let r = classes::Rounded::new();
|
||||||
/// assert_eq!(r.to_string(), "");
|
/// assert_eq!(r.to_class(), "");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// **Radio en las esquinas de un lado lógico:**
|
/// **Radio en las esquinas de un lado lógico:**
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let r = classes::Rounded::new().with_end(RoundedRadius::Scale2);
|
/// let r = classes::Rounded::new().with_end(RoundedRadius::Scale2);
|
||||||
/// assert_eq!(r.to_string(), "rounded-end-2");
|
/// assert_eq!(r.to_class(), "rounded-end-2");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// **Radio en una esquina concreta:**
|
/// **Radio en una esquina concreta:**
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use pagetop_bootsier::prelude::*;
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
/// let r = classes::Rounded::new().with_top_start(RoundedRadius::Scale3);
|
/// let r = classes::Rounded::new().with_top_start(RoundedRadius::Scale3);
|
||||||
/// assert_eq!(r.to_string(), "rounded-top-start-3");
|
/// assert_eq!(r.to_class(), "rounded-top-start-3");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// **Combinado (ejemplo completo):**
|
/// **Combinado (ejemplo completo):**
|
||||||
|
|
@ -52,10 +50,10 @@ use std::fmt;
|
||||||
/// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta.
|
/// .with_bottom_start(RoundedRadius::Scale4) // Añade una esquina redondeada concreta.
|
||||||
/// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina.
|
/// .with_bottom_end(RoundedRadius::Circle); // Añade redondeo extremo en otra esquina.
|
||||||
///
|
///
|
||||||
/// assert_eq!(r.to_string(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle");
|
/// assert_eq!(r.to_class(), "rounded-top rounded-bottom-start-4 rounded-bottom-end-circle");
|
||||||
/// ```
|
/// ```
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct Rounded {
|
pub struct Rounded {
|
||||||
radius : RoundedRadius,
|
radius : RoundedRadius,
|
||||||
top : RoundedRadius,
|
top : RoundedRadius,
|
||||||
|
|
@ -136,28 +134,36 @@ impl Rounded {
|
||||||
self.bottom_end = radius;
|
self.bottom_end = radius;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for Rounded {
|
// **< Rounded HELPERS >************************************************************************
|
||||||
|
|
||||||
|
/// Añade las clases de redondeo a la cadena de clases.
|
||||||
|
///
|
||||||
/// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`,
|
/// Concatena, en este orden, las clases para *global*, `top`, `end`, `bottom`, `start`,
|
||||||
/// `top-start`, `top-end`, `bottom-start` y `bottom-end`; respetando LTR/RTL y omitiendo las
|
/// `top-start`, `top-end`, `bottom-start` y `bottom-end`; respetando LTR/RTL y omitiendo las
|
||||||
/// definiciones vacías.
|
/// definiciones vacías.
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
#[rustfmt::skip]
|
||||||
write!(
|
#[inline]
|
||||||
f,
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
"{}",
|
self.radius .push_class(classes, "");
|
||||||
[
|
self.top .push_class(classes, "rounded-top");
|
||||||
self.radius.to_string(),
|
self.end .push_class(classes, "rounded-end");
|
||||||
self.top.to_class("rounded-top"),
|
self.bottom .push_class(classes, "rounded-bottom");
|
||||||
self.end.to_class("rounded-end"),
|
self.start .push_class(classes, "rounded-start");
|
||||||
self.bottom.to_class("rounded-bottom"),
|
self.top_start .push_class(classes, "rounded-top-start");
|
||||||
self.start.to_class("rounded-start"),
|
self.top_end .push_class(classes, "rounded-top-end");
|
||||||
self.top_start.to_class("rounded-top-start"),
|
self.bottom_start.push_class(classes, "rounded-bottom-start");
|
||||||
self.top_end.to_class("rounded-top-end"),
|
self.bottom_end .push_class(classes, "rounded-bottom-end");
|
||||||
self.bottom_start.to_class("rounded-bottom-start"),
|
}
|
||||||
self.bottom_end.to_class("rounded-bottom-end"),
|
|
||||||
]
|
/// Devuelve las clases de redondeo como cadena (`"rounded"`,
|
||||||
.join_classes()
|
/// `"rounded-top rounded-bottom-start-4 rounded-bottom-end-circle"`, etc.).
|
||||||
)
|
///
|
||||||
|
/// Si no se define ningún radio, devuelve `""`.
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
let mut classes = String::new();
|
||||||
|
self.push_class(&mut classes);
|
||||||
|
classes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,20 +26,7 @@ impl Component for Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||||
self.alter_classes(
|
self.alter_classes(ClassesOp::Prepend, self.width().to_class());
|
||||||
ClassesOp::Prepend,
|
|
||||||
[join_pair!(
|
|
||||||
"container",
|
|
||||||
"-",
|
|
||||||
match self.width() {
|
|
||||||
container::Width::Default => String::new(),
|
|
||||||
container::Width::From(bp) => bp.to_string(),
|
|
||||||
container::Width::Fluid => "fluid".to_string(),
|
|
||||||
container::Width::FluidMax(_) => "fluid".to_string(),
|
|
||||||
}
|
|
||||||
)]
|
|
||||||
.join_classes(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::theme::aux::BreakPoint;
|
||||||
|
|
||||||
// **< Kind >***************************************************************************************
|
// **< Kind >***************************************************************************************
|
||||||
|
|
||||||
/// Tipo de contenedor ([`Container`]).
|
/// Tipo de contenedor ([`Container`](crate::theme::Container)).
|
||||||
///
|
///
|
||||||
/// Permite aplicar la etiqueta HTML apropiada (`<main>`, `<header>`, etc.) manteniendo una API
|
/// Permite aplicar la etiqueta HTML apropiada (`<main>`, `<header>`, etc.) manteniendo una API
|
||||||
/// común a todos los contenedores.
|
/// común a todos los contenedores.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
/// Contenedor genérico (`<div>`).
|
/// Contenedor genérico (`<div>`).
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -27,8 +27,8 @@ pub enum Kind {
|
||||||
|
|
||||||
// **< Width >**************************************************************************************
|
// **< Width >**************************************************************************************
|
||||||
|
|
||||||
/// Define el comportamiento para ajustar el ancho de un contenedor ([`Container`]).
|
/// Define cómo se comporta el ancho de un contenedor ([`Container`](crate::theme::Container)).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Width {
|
pub enum Width {
|
||||||
/// Comportamiento por defecto, aplica los anchos máximos predefinidos para cada punto de
|
/// Comportamiento por defecto, aplica los anchos máximos predefinidos para cada punto de
|
||||||
/// ruptura. Por debajo del menor punto de ruptura ocupa el 100% del ancho disponible.
|
/// ruptura. Por debajo del menor punto de ruptura ocupa el 100% del ancho disponible.
|
||||||
|
|
@ -42,3 +42,31 @@ pub enum Width {
|
||||||
/// Ocupa el 100% del ancho disponible hasta un ancho máximo explícito.
|
/// Ocupa el 100% del ancho disponible hasta un ancho máximo explícito.
|
||||||
FluidMax(UnitValue),
|
FluidMax(UnitValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Width {
|
||||||
|
const CONTAINER: &str = "container";
|
||||||
|
|
||||||
|
/* Añade el comportamiento del contenedor a la cadena de clases según ancho (reservado).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
match self {
|
||||||
|
Self::Default => BreakPoint::None.push_class(classes, Self::CONTAINER, ""),
|
||||||
|
Self::From(bp) => bp.push_class(classes, Self::CONTAINER, ""),
|
||||||
|
Self::Fluid | Self::FluidMax(_) => {
|
||||||
|
BreakPoint::None.push_class(classes, Self::CONTAINER, "fluid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
/// Devuelve la clase asociada al comportamiento del contenedor según el ajuste de su ancho.
|
||||||
|
#[inline]
|
||||||
|
pub fn to_class(self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Default => BreakPoint::None.class_with(Self::CONTAINER, ""),
|
||||||
|
Self::From(bp) => bp.class_with(Self::CONTAINER, ""),
|
||||||
|
Self::Fluid | Self::FluidMax(_) => {
|
||||||
|
BreakPoint::None.class_with(Self::CONTAINER, "fluid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,21 +45,11 @@ impl Component for Dropdown {
|
||||||
self.id.get()
|
self.id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||||
let g = self.button_grouped();
|
self.alter_classes(
|
||||||
self.alter_classes(ClassesOp::Prepend, [
|
ClassesOp::Prepend,
|
||||||
if g { "btn-group" } else { "" },
|
self.direction().class_with(self.button_grouped()),
|
||||||
match self.direction() {
|
);
|
||||||
dropdown::Direction::Default if g => "",
|
|
||||||
dropdown::Direction::Default => "dropdown",
|
|
||||||
dropdown::Direction::Centered => "dropdown-center",
|
|
||||||
dropdown::Direction::Dropup => "dropup",
|
|
||||||
dropdown::Direction::DropupCentered => "dropup-center",
|
|
||||||
dropdown::Direction::Dropend => "dropend",
|
|
||||||
dropdown::Direction::Dropstart => "dropstart",
|
|
||||||
}
|
|
||||||
].join_classes());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
|
|
@ -75,40 +65,20 @@ impl Component for Dropdown {
|
||||||
PrepareMarkup::With(html! {
|
PrepareMarkup::With(html! {
|
||||||
div id=[self.id()] class=[self.classes().get()] {
|
div id=[self.id()] class=[self.classes().get()] {
|
||||||
@if !title.is_empty() {
|
@if !title.is_empty() {
|
||||||
@let mut btn_classes = AttrClasses::new([
|
@let mut btn_classes = AttrClasses::new({
|
||||||
"btn",
|
let mut classes = "btn".to_string();
|
||||||
&self.button_size().to_string(),
|
self.button_size().push_class(&mut classes);
|
||||||
&self.button_color().to_string(),
|
self.button_color().push_class(&mut classes);
|
||||||
].join_classes());
|
classes
|
||||||
@let (offset, reference) = match self.menu_position() {
|
});
|
||||||
dropdown::MenuPosition::Default => (None, None),
|
@let pos = self.menu_position();
|
||||||
dropdown::MenuPosition::Offset(x, y) => (Some(format!("{x},{y}")), None),
|
@let offset = pos.data_offset();
|
||||||
dropdown::MenuPosition::Parent => (None, Some("parent")),
|
@let reference = pos.data_reference();
|
||||||
};
|
@let auto_close = self.auto_close.as_str();
|
||||||
@let auto_close = match self.auto_close {
|
@let menu_classes = AttrClasses::new({
|
||||||
dropdown::AutoClose::Default => None,
|
let mut classes = "dropdown-menu".to_string();
|
||||||
dropdown::AutoClose::ClickableInside => Some("inside"),
|
self.menu_align().push_class(&mut classes);
|
||||||
dropdown::AutoClose::ClickableOutside => Some("outside"),
|
classes
|
||||||
dropdown::AutoClose::ManualClose => Some("false"),
|
|
||||||
};
|
|
||||||
@let menu_classes = AttrClasses::new("dropdown-menu")
|
|
||||||
.with_value(ClassesOp::Add, match self.menu_align() {
|
|
||||||
dropdown::MenuAlign::Start => String::new(),
|
|
||||||
dropdown::MenuAlign::StartAt(bp) => bp.try_class("dropdown-menu")
|
|
||||||
.map_or(String::new(), |class| join!(class, "-start")),
|
|
||||||
dropdown::MenuAlign::StartAndEnd(bp) => bp.try_class("dropdown-menu")
|
|
||||||
.map_or(
|
|
||||||
"dropdown-menu-start".into(),
|
|
||||||
|class| join!("dropdown-menu-start ", class, "-end")
|
|
||||||
),
|
|
||||||
dropdown::MenuAlign::End => "dropdown-menu-end".into(),
|
|
||||||
dropdown::MenuAlign::EndAt(bp) => bp.try_class("dropdown-menu")
|
|
||||||
.map_or(String::new(), |class| join!(class, "-end")),
|
|
||||||
dropdown::MenuAlign::EndAndStart(bp) => bp.try_class("dropdown-menu")
|
|
||||||
.map_or(
|
|
||||||
"dropdown-menu-end".into(),
|
|
||||||
|class| join!("dropdown-menu-end ", class, "-start")
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Renderizado en modo split (dos botones) o simple (un botón).
|
// Renderizado en modo split (dos botones) o simple (un botón).
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ impl Component for Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemKind::Button { label, disabled } => {
|
ItemKind::Button { label, disabled } => {
|
||||||
let mut classes = "dropdown-item".to_owned();
|
let mut classes = "dropdown-item".to_string();
|
||||||
if *disabled {
|
if *disabled {
|
||||||
classes.push_str(" disabled");
|
classes.push_str(" disabled");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::prelude::*;
|
||||||
/// Estrategia para el cierre automático de un menú [`Dropdown`].
|
/// Estrategia para el cierre automático de un menú [`Dropdown`].
|
||||||
///
|
///
|
||||||
/// Define cuándo se cierra el menú desplegado según la interacción del usuario.
|
/// Define cuándo se cierra el menú desplegado según la interacción del usuario.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum AutoClose {
|
pub enum AutoClose {
|
||||||
/// Comportamiento por defecto, se cierra con clics dentro y fuera del menú, o pulsando `Esc`.
|
/// Comportamiento por defecto, se cierra con clics dentro y fuera del menú, o pulsando `Esc`.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -21,12 +21,26 @@ pub enum AutoClose {
|
||||||
ManualClose,
|
ManualClose,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AutoClose {
|
||||||
|
// Devuelve el valor para `data-bs-auto-close`, o `None` si es el comportamiento por defecto.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
pub(crate) const fn as_str(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
Self::Default => None,
|
||||||
|
Self::ClickableInside => Some("inside"),
|
||||||
|
Self::ClickableOutside => Some("outside"),
|
||||||
|
Self::ManualClose => Some("false"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// **< Direction >**********************************************************************************
|
// **< Direction >**********************************************************************************
|
||||||
|
|
||||||
/// Dirección de despliegue de un menú [`Dropdown`].
|
/// Dirección de despliegue de un menú [`Dropdown`].
|
||||||
///
|
///
|
||||||
/// Controla desde qué posición se muestra el menú respecto al botón.
|
/// Controla desde qué posición se muestra el menú respecto al botón.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
/// Comportamiento por defecto (despliega el menú hacia abajo desde la posición inicial,
|
/// Comportamiento por defecto (despliega el menú hacia abajo desde la posición inicial,
|
||||||
/// respetando LTR/RTL).
|
/// respetando LTR/RTL).
|
||||||
|
|
@ -44,13 +58,58 @@ pub enum Direction {
|
||||||
Dropstart,
|
Dropstart,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Direction {
|
||||||
|
// Mapea la dirección teniendo en cuenta si se agrupa con otros menús [`Dropdown`].
|
||||||
|
#[rustfmt::skip ]
|
||||||
|
#[inline]
|
||||||
|
const fn as_str(self, grouped: bool) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Default if grouped => "",
|
||||||
|
Self::Default => "dropdown",
|
||||||
|
Self::Centered => "dropdown-center",
|
||||||
|
Self::Dropup => "dropup",
|
||||||
|
Self::DropupCentered => "dropup-center",
|
||||||
|
Self::Dropend => "dropend",
|
||||||
|
Self::Dropstart => "dropstart",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade la dirección de despliegue a la cadena de clases teniendo en cuenta si se agrupa con
|
||||||
|
// otros menús [`Dropdown`].
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String, grouped: bool) {
|
||||||
|
if grouped {
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str("btn-group");
|
||||||
|
}
|
||||||
|
let class = self.as_str(grouped);
|
||||||
|
if !class.is_empty() {
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devuelve la clase asociada a la dirección teniendo en cuenta si se agrupa con otros menús
|
||||||
|
// [`Dropdown`], o `""` si no corresponde ninguna.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn class_with(self, grouped: bool) -> String {
|
||||||
|
let mut classes = String::new();
|
||||||
|
self.push_class(&mut classes, grouped);
|
||||||
|
classes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// **< MenuAlign >**********************************************************************************
|
// **< MenuAlign >**********************************************************************************
|
||||||
|
|
||||||
/// Alineación horizontal del menú desplegable [`Dropdown`].
|
/// Alineación horizontal del menú desplegable [`Dropdown`].
|
||||||
///
|
///
|
||||||
/// Permite alinear el menú al inicio o al final del botón (respetando LTR/RTL) y añadirle una
|
/// Permite alinear el menú al inicio o al final del botón (respetando LTR/RTL) y añadirle una
|
||||||
/// alineación diferente a partir de un punto de ruptura ([`BreakPoint`]).
|
/// alineación diferente a partir de un punto de ruptura ([`BreakPoint`]).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum MenuAlign {
|
pub enum MenuAlign {
|
||||||
/// Alineación al inicio (comportamiento por defecto).
|
/// Alineación al inicio (comportamiento por defecto).
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -67,13 +126,74 @@ pub enum MenuAlign {
|
||||||
EndAndStart(BreakPoint),
|
EndAndStart(BreakPoint),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MenuAlign {
|
||||||
|
#[inline]
|
||||||
|
fn push_one(classes: &mut String, class: &str) {
|
||||||
|
if class.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade las clases de alineación a `classes` (sin incluir la base `dropdown-menu`).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
match self {
|
||||||
|
// Alineación por defecto (start), no añade clases extra.
|
||||||
|
Self::Start => {}
|
||||||
|
|
||||||
|
// `dropdown-menu-{bp}-start`
|
||||||
|
Self::StartAt(bp) => {
|
||||||
|
let class = bp.class_with("dropdown-menu", "start");
|
||||||
|
Self::push_one(classes, &class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `dropdown-menu-start` + `dropdown-menu-{bp}-end`
|
||||||
|
Self::StartAndEnd(bp) => {
|
||||||
|
Self::push_one(classes, "dropdown-menu-start");
|
||||||
|
let bp_class = bp.class_with("dropdown-menu", "end");
|
||||||
|
Self::push_one(classes, &bp_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `dropdown-menu-end`
|
||||||
|
Self::End => {
|
||||||
|
Self::push_one(classes, "dropdown-menu-end");
|
||||||
|
}
|
||||||
|
|
||||||
|
// `dropdown-menu-{bp}-end`
|
||||||
|
Self::EndAt(bp) => {
|
||||||
|
let class = bp.class_with("dropdown-menu", "end");
|
||||||
|
Self::push_one(classes, &class);
|
||||||
|
}
|
||||||
|
|
||||||
|
// `dropdown-menu-end` + `dropdown-menu-{bp}-start`
|
||||||
|
Self::EndAndStart(bp) => {
|
||||||
|
Self::push_one(classes, "dropdown-menu-end");
|
||||||
|
let bp_class = bp.class_with("dropdown-menu", "start");
|
||||||
|
Self::push_one(classes, &bp_class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Devuelve las clases de alineación sin incluir `dropdown-menu` (reservado).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn to_class(self) -> String {
|
||||||
|
let mut classes = String::new();
|
||||||
|
self.push_class(&mut classes);
|
||||||
|
classes
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
// **< MenuPosition >*******************************************************************************
|
// **< MenuPosition >*******************************************************************************
|
||||||
|
|
||||||
/// Posición relativa del menú desplegable [`Dropdown`].
|
/// Posición relativa del menú desplegable [`Dropdown`].
|
||||||
///
|
///
|
||||||
/// Permite indicar un desplazamiento (*offset*) manual o referenciar al elemento padre para el
|
/// Permite indicar un desplazamiento (*offset*) manual o referenciar al elemento padre para el
|
||||||
/// cálculo de la posición.
|
/// cálculo de la posición.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum MenuPosition {
|
pub enum MenuPosition {
|
||||||
/// Posicionamiento automático por defecto.
|
/// Posicionamiento automático por defecto.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -84,3 +204,23 @@ pub enum MenuPosition {
|
||||||
/// [`button_split()`](crate::theme::Dropdown::button_split) es `true`.
|
/// [`button_split()`](crate::theme::Dropdown::button_split) es `true`.
|
||||||
Parent,
|
Parent,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MenuPosition {
|
||||||
|
// Devuelve el valor para `data-bs-offset` o `None` si no aplica.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn data_offset(self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Self::Offset(x, y) => Some(format!("{x},{y}")),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Devuelve el valor para `data-bs-reference` o `None` si no aplica.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn data_reference(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
Self::Parent => Some("parent"),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,38 +29,11 @@ impl Component for Image {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||||
self.alter_classes(
|
self.alter_classes(ClassesOp::Prepend, self.source().to_class());
|
||||||
ClassesOp::Prepend,
|
|
||||||
match self.source() {
|
|
||||||
image::Source::Logo(_) => "img-fluid",
|
|
||||||
image::Source::Responsive(_) => "img-fluid",
|
|
||||||
image::Source::Thumbnail(_) => "img-thumbnail",
|
|
||||||
image::Source::Plain(_) => "",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
let dimensions = match self.size() {
|
let dimensions = self.size().to_style();
|
||||||
image::Size::Auto => None,
|
|
||||||
image::Size::Dimensions(w, h) => {
|
|
||||||
let w = w.to_string();
|
|
||||||
let h = h.to_string();
|
|
||||||
Some(join!("width: ", w, "; height: ", h, ";"))
|
|
||||||
}
|
|
||||||
image::Size::Width(w) => {
|
|
||||||
let w = w.to_string();
|
|
||||||
Some(join!("width: ", w, ";"))
|
|
||||||
}
|
|
||||||
image::Size::Height(h) => {
|
|
||||||
let h = h.to_string();
|
|
||||||
Some(join!("height: ", h, ";"))
|
|
||||||
}
|
|
||||||
image::Size::Both(v) => {
|
|
||||||
let v = v.to_string();
|
|
||||||
Some(join!("width: ", v, "; height: ", v, ";"))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let alt_text = self.alternative().lookup(cx).unwrap_or_default();
|
let alt_text = self.alternative().lookup(cx).unwrap_or_default();
|
||||||
let is_decorative = alt_text.is_empty();
|
let is_decorative = alt_text.is_empty();
|
||||||
let source = match self.source() {
|
let source = match self.source() {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use pagetop::prelude::*;
|
||||||
// **< Size >***************************************************************************************
|
// **< Size >***************************************************************************************
|
||||||
|
|
||||||
/// Define las **dimensiones** de una imagen ([`Image`](crate::theme::Image)).
|
/// Define las **dimensiones** de una imagen ([`Image`](crate::theme::Image)).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Size {
|
pub enum Size {
|
||||||
/// Ajuste automático por defecto.
|
/// Ajuste automático por defecto.
|
||||||
///
|
///
|
||||||
|
|
@ -30,10 +30,24 @@ pub enum Size {
|
||||||
Both(UnitValue),
|
Both(UnitValue),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Size {
|
||||||
|
// Devuelve el valor del atributo `style` en función del tamaño, o `None` si no aplica.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn to_style(self) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
Self::Auto => None,
|
||||||
|
Self::Dimensions(w, h) => Some(format!("width: {w}; height: {h};")),
|
||||||
|
Self::Width(w) => Some(format!("width: {w};")),
|
||||||
|
Self::Height(h) => Some(format!("height: {h};")),
|
||||||
|
Self::Both(v) => Some(format!("width: {v}; height: {v};")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// **< Source >*************************************************************************************
|
// **< Source >*************************************************************************************
|
||||||
|
|
||||||
/// Especifica la **fuente** para publicar una imagen ([`Image`](crate::theme::Image)).
|
/// Especifica la **fuente** para publicar una imagen ([`Image`](crate::theme::Image)).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Debug, PartialEq)]
|
||||||
pub enum Source {
|
pub enum Source {
|
||||||
/// Imagen con el logotipo de PageTop.
|
/// Imagen con el logotipo de PageTop.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -51,3 +65,44 @@ pub enum Source {
|
||||||
/// El `String` asociado es la URL (o ruta) de la imagen.
|
/// El `String` asociado es la URL (o ruta) de la imagen.
|
||||||
Plain(String),
|
Plain(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Source {
|
||||||
|
const IMG_FLUID: &str = "img-fluid";
|
||||||
|
const IMG_THUMBNAIL: &str = "img-thumbnail";
|
||||||
|
|
||||||
|
// Devuelve la clase base asociada a la imagen según la fuente.
|
||||||
|
#[inline]
|
||||||
|
fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Source::Logo(_) | Source::Responsive(_) => Self::IMG_FLUID,
|
||||||
|
Source::Thumbnail(_) => Self::IMG_THUMBNAIL,
|
||||||
|
Source::Plain(_) => "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Añade la clase base asociada a la imagen según la fuente a la cadena de clases (reservado).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(&self, classes: &mut String) {
|
||||||
|
let s = self.as_str();
|
||||||
|
if s.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(s);
|
||||||
|
} */
|
||||||
|
|
||||||
|
// Devuelve la clase asociada a la imagen según la fuente.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn to_class(&self) -> String {
|
||||||
|
let s = self.as_str();
|
||||||
|
if s.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
let mut class = String::with_capacity(s.len());
|
||||||
|
class.push_str(s);
|
||||||
|
class
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,28 +30,12 @@ impl Component for Nav {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||||
self.alter_classes(
|
self.alter_classes(ClassesOp::Prepend, {
|
||||||
ClassesOp::Prepend,
|
let mut classes = "nav".to_string();
|
||||||
[
|
self.nav_kind().push_class(&mut classes);
|
||||||
"nav",
|
self.nav_layout().push_class(&mut classes);
|
||||||
match self.nav_kind() {
|
classes
|
||||||
nav::Kind::Default => "",
|
});
|
||||||
nav::Kind::Tabs => "nav-tabs",
|
|
||||||
nav::Kind::Pills => "nav-pills",
|
|
||||||
nav::Kind::Underline => "nav-underline",
|
|
||||||
},
|
|
||||||
match self.nav_layout() {
|
|
||||||
nav::Layout::Default => "",
|
|
||||||
nav::Layout::Start => "justify-content-start",
|
|
||||||
nav::Layout::Center => "justify-content-center",
|
|
||||||
nav::Layout::End => "justify-content-end",
|
|
||||||
nav::Layout::Vertical => "flex-column",
|
|
||||||
nav::Layout::Fill => "nav-fill",
|
|
||||||
nav::Layout::Justified => "nav-justified",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
.join_classes(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,40 @@ pub enum ItemKind {
|
||||||
Dropdown(Typed<Dropdown>),
|
Dropdown(Typed<Dropdown>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ItemKind {
|
||||||
|
const ITEM: &str = "nav-item";
|
||||||
|
const DROPDOWN: &str = "nav-item dropdown";
|
||||||
|
|
||||||
|
// Devuelve las clases base asociadas al tipo de elemento.
|
||||||
|
#[inline]
|
||||||
|
const fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Void => "",
|
||||||
|
Self::Dropdown(_) => Self::DROPDOWN,
|
||||||
|
_ => Self::ITEM,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Añade las clases asociadas al tipo de elemento a la cadena de clases (reservado).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(&self, classes: &mut String) {
|
||||||
|
let class = self.as_str();
|
||||||
|
if class.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(class);
|
||||||
|
} */
|
||||||
|
|
||||||
|
// Devuelve las clases asociadas al tipo de elemento.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn to_class(&self) -> String {
|
||||||
|
self.as_str().to_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// **< Item >***************************************************************************************
|
// **< Item >***************************************************************************************
|
||||||
|
|
||||||
/// Representa un **elemento individual** de un menú [`Nav`](crate::theme::Nav).
|
/// Representa un **elemento individual** de un menú [`Nav`](crate::theme::Nav).
|
||||||
|
|
@ -56,14 +90,7 @@ impl Component for Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||||
self.alter_classes(
|
self.alter_classes(ClassesOp::Prepend, self.item_kind().to_class());
|
||||||
ClassesOp::Prepend,
|
|
||||||
if matches!(self.item_kind(), ItemKind::Dropdown(_)) {
|
|
||||||
"nav-item dropdown"
|
|
||||||
} else {
|
|
||||||
"nav-item"
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
|
|
@ -96,12 +123,12 @@ impl Component for Item {
|
||||||
classes.push_str(" disabled");
|
classes.push_str(" disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
let href = (!disabled).then_some(path);
|
let href = (!*disabled).then_some(path);
|
||||||
let target = (!disabled && *blank).then_some("_blank");
|
let target = (!*disabled && *blank).then_some("_blank");
|
||||||
let rel = (!disabled && *blank).then_some("noopener noreferrer");
|
let rel = (!*disabled && *blank).then_some("noopener noreferrer");
|
||||||
|
|
||||||
let aria_current = (href.is_some() && is_current).then_some("page");
|
let aria_current = (href.is_some() && is_current).then_some("page");
|
||||||
let aria_disabled = disabled.then_some("true");
|
let aria_disabled = (*disabled).then_some("true");
|
||||||
|
|
||||||
PrepareMarkup::With(html! {
|
PrepareMarkup::With(html! {
|
||||||
li id=[self.id()] class=[self.classes().get()] {
|
li id=[self.id()] class=[self.classes().get()] {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use pagetop::prelude::*;
|
||||||
// **< Kind >***************************************************************************************
|
// **< Kind >***************************************************************************************
|
||||||
|
|
||||||
/// Define la variante de presentación de un menú [`Nav`](crate::theme::Nav).
|
/// Define la variante de presentación de un menú [`Nav`](crate::theme::Nav).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Kind {
|
pub enum Kind {
|
||||||
/// Estilo por defecto, lista de enlaces flexible y minimalista.
|
/// Estilo por defecto, lista de enlaces flexible y minimalista.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -16,10 +16,47 @@ pub enum Kind {
|
||||||
Underline,
|
Underline,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Kind {
|
||||||
|
const TABS: &str = "nav-tabs";
|
||||||
|
const PILLS: &str = "nav-pills";
|
||||||
|
const UNDERLINE: &str = "nav-underline";
|
||||||
|
|
||||||
|
// Devuelve la clase base asociada al tipo de menú, o una cadena vacía si no aplica.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Default => "",
|
||||||
|
Self::Tabs => Self::TABS,
|
||||||
|
Self::Pills => Self::PILLS,
|
||||||
|
Self::Underline => Self::UNDERLINE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade la clase asociada al tipo de menú a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
let class = self.as_str();
|
||||||
|
if class.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Devuelve la clase asociada al tipo de menú, o una cadena vacía si no aplica (reservado).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn to_class(self) -> String {
|
||||||
|
self.as_str().to_owned()
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
// **< Layout >*************************************************************************************
|
// **< Layout >*************************************************************************************
|
||||||
|
|
||||||
/// Distribución y orientación de un menú [`Nav`](crate::theme::Nav).
|
/// Distribución y orientación de un menú [`Nav`](crate::theme::Nav).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Layout {
|
pub enum Layout {
|
||||||
/// Comportamiento por defecto, ancho definido por el contenido y sin alineación forzada.
|
/// Comportamiento por defecto, ancho definido por el contenido y sin alineación forzada.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -37,3 +74,47 @@ pub enum Layout {
|
||||||
/// Todos los elementos ocupan el mismo ancho rellenando la fila.
|
/// Todos los elementos ocupan el mismo ancho rellenando la fila.
|
||||||
Justified,
|
Justified,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Layout {
|
||||||
|
const START: &str = "justify-content-start";
|
||||||
|
const CENTER: &str = "justify-content-center";
|
||||||
|
const END: &str = "justify-content-end";
|
||||||
|
const VERTICAL: &str = "flex-column";
|
||||||
|
const FILL: &str = "nav-fill";
|
||||||
|
const JUSTIFIED: &str = "nav-justified";
|
||||||
|
|
||||||
|
// Devuelve la clase base asociada a la distribución y orientación del menú.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Default => "",
|
||||||
|
Self::Start => Self::START,
|
||||||
|
Self::Center => Self::CENTER,
|
||||||
|
Self::End => Self::END,
|
||||||
|
Self::Vertical => Self::VERTICAL,
|
||||||
|
Self::Fill => Self::FILL,
|
||||||
|
Self::Justified => Self::JUSTIFIED,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade la clase asociada a la distribución y orientación del menú a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
let class = self.as_str();
|
||||||
|
if class.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Devuelve la clase asociada a la distribución y orientación del menú, o una cadena vacía si no
|
||||||
|
// aplica (reservado).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn to_class(self) -> String {
|
||||||
|
self.as_str().to_owned()
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,22 +35,12 @@ impl Component for Navbar {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||||
self.alter_classes(
|
self.alter_classes(ClassesOp::Prepend, {
|
||||||
ClassesOp::Prepend,
|
let mut classes = "navbar".to_string();
|
||||||
[
|
self.expand().push_class(&mut classes, "navbar-expand", "");
|
||||||
"navbar".to_string(),
|
self.position().push_class(&mut classes);
|
||||||
self.expand().try_class("navbar-expand").unwrap_or_default(),
|
classes
|
||||||
match self.position() {
|
});
|
||||||
navbar::Position::Static => "",
|
|
||||||
navbar::Position::FixedTop => "fixed-top",
|
|
||||||
navbar::Position::FixedBottom => "fixed-bottom",
|
|
||||||
navbar::Position::StickyTop => "sticky-top",
|
|
||||||
navbar::Position::StickyBottom => "sticky-bottom",
|
|
||||||
}
|
|
||||||
.to_string(),
|
|
||||||
]
|
|
||||||
.join_classes(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,14 @@ impl Component for Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||||
|
if let Self::Nav(nav) = self {
|
||||||
|
if let Some(mut nav) = nav.borrow_mut() {
|
||||||
|
nav.alter_classes(ClassesOp::Prepend, "navbar-nav");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
match self {
|
match self {
|
||||||
Self::Void => PrepareMarkup::None,
|
Self::Void => PrepareMarkup::None,
|
||||||
|
|
@ -48,29 +56,8 @@ impl Component for Item {
|
||||||
if items.is_empty() {
|
if items.is_empty() {
|
||||||
return PrepareMarkup::None;
|
return PrepareMarkup::None;
|
||||||
}
|
}
|
||||||
let classes = AttrClasses::new(
|
|
||||||
[
|
|
||||||
"navbar-nav",
|
|
||||||
match nav.nav_kind() {
|
|
||||||
nav::Kind::Default => "",
|
|
||||||
nav::Kind::Tabs => "nav-tabs",
|
|
||||||
nav::Kind::Pills => "nav-pills",
|
|
||||||
nav::Kind::Underline => "nav-underline",
|
|
||||||
},
|
|
||||||
match nav.nav_layout() {
|
|
||||||
nav::Layout::Default => "",
|
|
||||||
nav::Layout::Start => "justify-content-start",
|
|
||||||
nav::Layout::Center => "justify-content-center",
|
|
||||||
nav::Layout::End => "justify-content-end",
|
|
||||||
nav::Layout::Vertical => "flex-column",
|
|
||||||
nav::Layout::Fill => "nav-fill",
|
|
||||||
nav::Layout::Justified => "nav-justified",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
.join_classes(),
|
|
||||||
);
|
|
||||||
PrepareMarkup::With(html! {
|
PrepareMarkup::With(html! {
|
||||||
ul id=[nav.id()] class=[classes.get()] {
|
ul id=[nav.id()] class=[nav.classes().get()] {
|
||||||
(items)
|
(items)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ pub enum Layout {
|
||||||
// **< Position >***********************************************************************************
|
// **< Position >***********************************************************************************
|
||||||
|
|
||||||
/// Posición global de una barra de navegación [`Navbar`] en el documento.
|
/// Posición global de una barra de navegación [`Navbar`] en el documento.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Position {
|
pub enum Position {
|
||||||
/// Barra normal, fluye con el documento.
|
/// Barra normal, fluye con el documento.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -62,3 +62,37 @@ pub enum Position {
|
||||||
/// La barra de navegación se fija en la parte inferior al hacer *scroll*.
|
/// La barra de navegación se fija en la parte inferior al hacer *scroll*.
|
||||||
StickyBottom,
|
StickyBottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Position {
|
||||||
|
// Devuelve la clase base asociada a la posición de la barra de navegación.
|
||||||
|
#[inline]
|
||||||
|
const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Static => "",
|
||||||
|
Self::FixedTop => "fixed-top",
|
||||||
|
Self::FixedBottom => "fixed-bottom",
|
||||||
|
Self::StickyTop => "sticky-top",
|
||||||
|
Self::StickyBottom => "sticky-bottom",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade la clase asociada a la posición de la barra de navegación a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
let class = self.as_str();
|
||||||
|
if class.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Devuelve la clase asociada a la posición de la barra de navegación, o cadena vacía si no
|
||||||
|
// aplica (reservado).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn to_class(self) -> String {
|
||||||
|
self.as_str().to_string()
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,25 +44,14 @@ impl Component for Offcanvas {
|
||||||
self.id.get()
|
self.id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
|
||||||
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
fn setup_before_prepare(&mut self, _cx: &mut Context) {
|
||||||
self.alter_classes(
|
self.alter_classes(ClassesOp::Prepend, {
|
||||||
ClassesOp::Prepend,
|
let mut classes = "offcanvas".to_string();
|
||||||
[
|
self.breakpoint().push_class(&mut classes, "offcanvas", "");
|
||||||
self.breakpoint().to_class("offcanvas"),
|
self.placement().push_class(&mut classes);
|
||||||
match self.placement() {
|
self.visibility().push_class(&mut classes);
|
||||||
offcanvas::Placement::Start => "offcanvas-start",
|
classes
|
||||||
offcanvas::Placement::End => "offcanvas-end",
|
});
|
||||||
offcanvas::Placement::Top => "offcanvas-top",
|
|
||||||
offcanvas::Placement::Bottom => "offcanvas-bottom",
|
|
||||||
}.to_string(),
|
|
||||||
match self.visibility() {
|
|
||||||
offcanvas::Visibility::Default => "",
|
|
||||||
offcanvas::Visibility::Show => "show",
|
|
||||||
}.to_string(),
|
|
||||||
]
|
|
||||||
.join_classes(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use pagetop::prelude::*;
|
||||||
|
|
||||||
/// Comportamiento de la capa de fondo (*backdrop*) de un panel
|
/// Comportamiento de la capa de fondo (*backdrop*) de un panel
|
||||||
/// [`Offcanvas`](crate::theme::Offcanvas) al deslizarse.
|
/// [`Offcanvas`](crate::theme::Offcanvas) al deslizarse.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Backdrop {
|
pub enum Backdrop {
|
||||||
/// Sin capa de fondo, la página principal permanece visible e interactiva.
|
/// Sin capa de fondo, la página principal permanece visible e interactiva.
|
||||||
Disabled,
|
Disabled,
|
||||||
|
|
@ -20,7 +20,7 @@ pub enum Backdrop {
|
||||||
|
|
||||||
/// Controla si la página principal puede desplazarse al abrir un panel
|
/// Controla si la página principal puede desplazarse al abrir un panel
|
||||||
/// [`Offcanvas`](crate::theme::Offcanvas).
|
/// [`Offcanvas`](crate::theme::Offcanvas).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum BodyScroll {
|
pub enum BodyScroll {
|
||||||
/// Opción por defecto, la página principal se bloquea centrando la interacción en el panel.
|
/// Opción por defecto, la página principal se bloquea centrando la interacción en el panel.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -34,7 +34,7 @@ pub enum BodyScroll {
|
||||||
/// Posición de aparición de un panel [`Offcanvas`](crate::theme::Offcanvas) al deslizarse.
|
/// Posición de aparición de un panel [`Offcanvas`](crate::theme::Offcanvas) al deslizarse.
|
||||||
///
|
///
|
||||||
/// Define desde qué borde de la ventana entra y se ancla el panel.
|
/// Define desde qué borde de la ventana entra y se ancla el panel.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Placement {
|
pub enum Placement {
|
||||||
/// Opción por defecto, desde el borde inicial según dirección de lectura (respetando LTR/RTL).
|
/// Opción por defecto, desde el borde inicial según dirección de lectura (respetando LTR/RTL).
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -47,10 +47,39 @@ pub enum Placement {
|
||||||
Bottom,
|
Bottom,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Placement {
|
||||||
|
// Devuelve la clase base asociada a la posición de aparición del panel.
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[inline]
|
||||||
|
const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Placement::Start => "offcanvas-start",
|
||||||
|
Placement::End => "offcanvas-end",
|
||||||
|
Placement::Top => "offcanvas-top",
|
||||||
|
Placement::Bottom => "offcanvas-bottom",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade la clase asociada a la posición de aparición del panel a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(self.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Devuelve la clase asociada a la posición de aparición del panel (reservado).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn to_class(self) -> String {
|
||||||
|
self.as_str().to_owned()
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
||||||
// **< Visibility >*********************************************************************************
|
// **< Visibility >*********************************************************************************
|
||||||
|
|
||||||
/// Estado inicial de un panel [`Offcanvas`](crate::theme::Offcanvas).
|
/// Estado inicial de un panel [`Offcanvas`](crate::theme::Offcanvas).
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Visibility {
|
pub enum Visibility {
|
||||||
/// El panel permanece oculto desde el principio.
|
/// El panel permanece oculto desde el principio.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -58,3 +87,33 @@ pub enum Visibility {
|
||||||
/// El panel se muestra abierto al cargar.
|
/// El panel se muestra abierto al cargar.
|
||||||
Show,
|
Show,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Visibility {
|
||||||
|
// Devuelve la clase base asociada al estado inicial del panel.
|
||||||
|
#[inline]
|
||||||
|
const fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Visibility::Default => "",
|
||||||
|
Visibility::Show => "show",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Añade la clase asociada al estado inicial del panel a la cadena de clases.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push_class(self, classes: &mut String) {
|
||||||
|
let class = self.as_str();
|
||||||
|
if class.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !classes.is_empty() {
|
||||||
|
classes.push(' ');
|
||||||
|
}
|
||||||
|
classes.push_str(class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Devuelve la clase asociada al estado inicial, o una cadena vacía si no aplica (reservado).
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn to_class(self) -> String {
|
||||||
|
self.as_str().to_owned()
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,9 @@
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
|
|
||||||
// **< FontSize >***********************************************************************************
|
// **< FontSize >***********************************************************************************
|
||||||
|
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum FontSize {
|
pub enum FontSize {
|
||||||
ExtraLarge,
|
ExtraLarge,
|
||||||
XxLarge,
|
XxLarge,
|
||||||
|
|
@ -24,7 +22,7 @@ pub enum FontSize {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl FontSize {
|
impl FontSize {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub const fn as_str(&self) -> &'static str {
|
pub const fn as_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
FontSize::ExtraLarge => "fs__x3l",
|
FontSize::ExtraLarge => "fs__x3l",
|
||||||
FontSize::XxLarge => "fs__x2l",
|
FontSize::XxLarge => "fs__x2l",
|
||||||
|
|
@ -40,12 +38,6 @@ impl FontSize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for FontSize {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_str(self.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// *************************************************************************************************
|
// *************************************************************************************************
|
||||||
|
|
||||||
mod html;
|
mod html;
|
||||||
|
|
|
||||||
|
|
@ -87,9 +87,6 @@ use crate::{core, AutoDefault};
|
||||||
#[allow(type_alias_bounds)]
|
#[allow(type_alias_bounds)]
|
||||||
pub type OptionComponent<C: core::component::Component> = core::component::Typed<C>;
|
pub type OptionComponent<C: core::component::Component> = core::component::Typed<C>;
|
||||||
|
|
||||||
mod join_classes;
|
|
||||||
pub use join_classes::JoinClasses;
|
|
||||||
|
|
||||||
mod unit;
|
mod unit;
|
||||||
pub use unit::UnitValue;
|
pub use unit::UnitValue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ enum Source {
|
||||||
///
|
///
|
||||||
/// Permite especificar en qué contexto se aplica el CSS, adaptándose a diferentes dispositivos o
|
/// Permite especificar en qué contexto se aplica el CSS, adaptándose a diferentes dispositivos o
|
||||||
/// situaciones de impresión.
|
/// situaciones de impresión.
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum TargetMedia {
|
pub enum TargetMedia {
|
||||||
/// Se aplica en todos los casos (el atributo `media` se omite).
|
/// Se aplica en todos los casos (el atributo `media` se omite).
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -39,7 +39,7 @@ pub enum TargetMedia {
|
||||||
/// Devuelve el valor para el atributo `media` (`Some(...)`) o `None` para `Default`.
|
/// Devuelve el valor para el atributo `media` (`Some(...)`) o `None` para `Default`.
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
impl TargetMedia {
|
impl TargetMedia {
|
||||||
fn as_str_opt(&self) -> Option<&str> {
|
const fn as_str(self) -> Option<&'static str> {
|
||||||
match self {
|
match self {
|
||||||
TargetMedia::Default => None,
|
TargetMedia::Default => None,
|
||||||
TargetMedia::Print => Some("print"),
|
TargetMedia::Print => Some("print"),
|
||||||
|
|
@ -171,7 +171,7 @@ impl Asset for StyleSheet {
|
||||||
link
|
link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href=(join_pair!(path, "?v=", &self.version))
|
href=(join_pair!(path, "?v=", &self.version))
|
||||||
media=[self.media.as_str_opt()];
|
media=[self.media.as_str()];
|
||||||
},
|
},
|
||||||
Source::Inline(_, f) => html! {
|
Source::Inline(_, f) => html! {
|
||||||
style { (PreEscaped((f)(cx))) };
|
style { (PreEscaped((f)(cx))) };
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
/// Añade a los *slices* de elementos [`AsRef<str>`] un método para unir clases CSS.
|
|
||||||
///
|
|
||||||
/// El método es [`join_classes()`](JoinClasses::join_classes), que une las cadenas **no vacías**
|
|
||||||
/// del *slice* usando un espacio como separador.
|
|
||||||
pub trait JoinClasses {
|
|
||||||
/// Une las cadenas **no vacías** de un *slice* usando un espacio como separador.
|
|
||||||
///
|
|
||||||
/// Son cadenas vacías únicamente los elementos del *slice* cuya longitud es `0` (p. ej., `""`);
|
|
||||||
/// no se realiza recorte ni normalización, por lo que elementos como `" "` no se consideran
|
|
||||||
/// vacíos.
|
|
||||||
///
|
|
||||||
/// Si todas las cadenas están vacías, devuelve una cadena vacía. Acepta elementos que
|
|
||||||
/// implementen [`AsRef<str>`] como `&str`, [`String`] o `Cow<'_, str>`.
|
|
||||||
///
|
|
||||||
/// # Ejemplos
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use pagetop::prelude::*;
|
|
||||||
/// let classes = ["btn", "", "btn-primary"];
|
|
||||||
/// assert_eq!(classes.join_classes(), "btn btn-primary");
|
|
||||||
///
|
|
||||||
/// let empty: [&str; 3] = ["", "", ""];
|
|
||||||
/// assert_eq!(empty.join_classes(), "");
|
|
||||||
///
|
|
||||||
/// let border = String::from("border");
|
|
||||||
/// let border_top = String::from("border-top-0");
|
|
||||||
/// let v = vec![&border, "", "", "", &border_top];
|
|
||||||
/// assert_eq!(v.as_slice().join_classes(), "border border-top-0");
|
|
||||||
///
|
|
||||||
/// // Elementos con espacios afectan al resultado.
|
|
||||||
/// let spaced = ["btn", " ", "primary "];
|
|
||||||
/// assert_eq!(spaced.join_classes(), "btn primary ");
|
|
||||||
/// ```
|
|
||||||
fn join_classes(&self) -> String;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> JoinClasses for [T]
|
|
||||||
where
|
|
||||||
T: AsRef<str>,
|
|
||||||
{
|
|
||||||
#[inline]
|
|
||||||
fn join_classes(&self) -> String {
|
|
||||||
let mut count = 0usize;
|
|
||||||
let mut total = 0usize;
|
|
||||||
for s in self.iter().map(T::as_ref).filter(|s| !s.is_empty()) {
|
|
||||||
count += 1;
|
|
||||||
total += s.len();
|
|
||||||
}
|
|
||||||
if count == 0 {
|
|
||||||
return String::new();
|
|
||||||
}
|
|
||||||
let separator = " ";
|
|
||||||
let mut result = String::with_capacity(total + separator.len() * count.saturating_sub(1));
|
|
||||||
for (i, s) in self
|
|
||||||
.iter()
|
|
||||||
.map(T::as_ref)
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.enumerate()
|
|
||||||
{
|
|
||||||
if i > 0 {
|
|
||||||
result.push_str(separator);
|
|
||||||
}
|
|
||||||
result.push_str(s);
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -27,7 +27,7 @@ use crate::AutoDefault;
|
||||||
/// };
|
/// };
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
#[derive(AutoDefault)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum PageTopSvg {
|
pub enum PageTopSvg {
|
||||||
/// Versión por defecto con el logotipo a color.
|
/// Versión por defecto con el logotipo a color.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ pub use crate::trace;
|
||||||
// alias obsoletos se volverá a declarar como `pub use crate::html::*;`.
|
// alias obsoletos se volverá a declarar como `pub use crate::html::*;`.
|
||||||
pub use crate::html::{
|
pub use crate::html::{
|
||||||
display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue,
|
display, html_private, Asset, Assets, AttrClasses, AttrId, AttrL10n, AttrName, AttrValue,
|
||||||
ClassesOp, Escaper, Favicon, JavaScript, JoinClasses, Markup, PageTopSvg, PreEscaped,
|
ClassesOp, Escaper, Favicon, JavaScript, Markup, PageTopSvg, PreEscaped, PrepareMarkup,
|
||||||
PrepareMarkup, StyleSheet, TargetMedia, UnitValue, DOCTYPE,
|
StyleSheet, TargetMedia, UnitValue, DOCTYPE,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::locale::*;
|
pub use crate::locale::*;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue