diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index f2838d7a..7b83bde2 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -145,6 +145,7 @@ impl Theme for Bootsier { setup_component!(component, { Button => |c| theme::bs::button::setup(c), Container => |c| theme::bs::container::setup(c), + Image => |c| theme::bs::image::setup(c), form::input::Field => |c| theme::bs::form::input::setup(c), form::select::Field => |c| theme::bs::form::select::setup(c), form::Textarea => |c| theme::bs::form::textarea::setup(c), diff --git a/extensions/pagetop-bootsier/src/theme/bs/image.rs b/extensions/pagetop-bootsier/src/theme/bs/image.rs index 837d25a1..e5a1f649 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/image.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/image.rs @@ -1,7 +1,24 @@ //! Definiciones para renderizar imágenes ([`Image`]). -mod props; -pub use props::{Size, Source}; +use pagetop::prelude::*; -mod component; -pub use component::Image; +pub use pagetop::base::component::image::{Image, Size, Source}; + +// **< Image SETUP >******************************************************************************** + +pub(crate) fn setup(image: &mut Image) { + match image.source() { + Source::Logo(_) | Source::Responsive(_) => { + image.alter_prop(PropsOp::replace_classes("image image-fluid", "img-fluid")); + } + Source::Thumbnail(_) => { + image.alter_prop(PropsOp::replace_classes( + "image image-thumbnail", + "img-thumbnail", + )); + } + Source::Plain(_) => { + image.alter_prop(PropsOp::remove_classes("image")); + } + } +} diff --git a/src/base/component.rs b/src/base/component.rs index 52809f46..28fcf30a 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -30,6 +30,10 @@ pub use form::Form; mod html; pub use html::Html; +pub mod image; +#[doc(inline)] +pub use image::Image; + mod intro; pub use intro::{Intro, IntroOpening}; diff --git a/src/base/component/image.rs b/src/base/component/image.rs new file mode 100644 index 00000000..837d25a1 --- /dev/null +++ b/src/base/component/image.rs @@ -0,0 +1,7 @@ +//! Definiciones para renderizar imágenes ([`Image`]). + +mod props; +pub use props::{Size, Source}; + +mod component; +pub use component::Image; diff --git a/extensions/pagetop-bootsier/src/theme/bs/image/component.rs b/src/base/component/image/component.rs similarity index 56% rename from extensions/pagetop-bootsier/src/theme/bs/image/component.rs rename to src/base/component/image/component.rs index 37bc1371..ed799886 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/image/component.rs +++ b/src/base/component/image/component.rs @@ -1,25 +1,33 @@ -use pagetop::prelude::*; - -use crate::theme::*; +use crate::prelude::*; /// Componente para renderizar una **imagen**. /// /// A una imagen se le puede: /// -/// - Establecer su contenido a partir del origen definido en -/// [`image::Source`](crate::theme::bs::image::Source). -/// - Configurar sus **dimensiones** ([`with_size()`](Self::with_size)), **borde** -/// ([`Border`](crate::theme::class::Border)) y **redondeo de esquinas** -/// ([`Rounded`](crate::theme::class::Rounded)). +/// - Establecer su contenido a partir del origen definido en [`image::Source`]. +/// - Configurar sus **dimensiones** ([`with_size()`](Self::with_size)). /// - Aplicar el texto alternativo `alt` con **localización** mediante [`Lc`]. +/// +/// # Ejemplo +/// +/// ```rust,no_run +/// use pagetop::prelude::*; +/// +/// let logo = Image::with(image::Source::logo(PageTopSvg::Color)) +/// .with_alternative(Lc::n("PageTop")); +/// +/// let photo = Image::with(image::Source::responsive("/files/photo.jpg")) +/// .with_size(image::Size::Width(UnitValue::Px(320))) +/// .with_alternative(Lc::n("Team photo")); +/// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Image { /// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente. props: Props, /// Devuelve las dimensiones de la imagen. - size: bs::image::Size, + size: image::Size, /// Devuelve el origen de la imagen. - source: bs::image::Source, + source: image::Source, /// Devuelve el texto alternativo localizado. alternative: Attr, } @@ -35,20 +43,40 @@ impl Component for Image { } fn setup(&mut self, _cx: &Context) { - // Clases CSS por defecto para la imagen, según el origen seleccionado. - self.alter_prop(PropsOp::prepend_classes(self.source().to_class())); + self.alter_prop(PropsOp::prepend_classes(match self.source() { + image::Source::Logo(_) => "image image-fluid", + image::Source::Responsive(_) => "image image-fluid", + image::Source::Thumbnail(_) => "image image-thumbnail", + image::Source::Plain(_) => "image", + })); + // El tamaño se aplica como declaraciones `style` individuales sobre `Props`. + match *self.size() { + image::Size::Auto => {} + image::Size::Dimensions(w, h) => { + self.alter_prop(PropsOp::add_style("width", w.to_string())); + self.alter_prop(PropsOp::add_style("height", h.to_string())); + } + image::Size::Width(w) => { + self.alter_prop(PropsOp::add_style("width", w.to_string())); + } + image::Size::Height(h) => { + self.alter_prop(PropsOp::add_style("height", h.to_string())); + } + image::Size::Both(v) => { + self.alter_prop(PropsOp::add_style("width", v.to_string())); + self.alter_prop(PropsOp::add_style("height", v.to_string())); + } + } } async fn prepare(&self, cx: &mut Context) -> Result { - let dimensions = self.size().to_style(); let alt_text = self.alternative().lookup(cx).unwrap_or_default(); - let is_decorative = alt_text.is_empty(); let source = match self.source() { - bs::image::Source::Logo(logo) => { + image::Source::Logo(logo) => { + let is_decorative = alt_text.is_empty(); return Ok(html! { span (self.props()) - style=[dimensions] role=[(!is_decorative).then_some("img")] aria-label=[(!is_decorative).then_some(alt_text)] aria-hidden=[is_decorative.then_some("true")] @@ -57,23 +85,22 @@ impl Component for Image { } }); } - bs::image::Source::Responsive(source) => Some(source), - bs::image::Source::Thumbnail(source) => Some(source), - bs::image::Source::Plain(source) => Some(source), + image::Source::Responsive(source) => Some(source), + image::Source::Thumbnail(source) => Some(source), + image::Source::Plain(source) => Some(source), }; Ok(html! { img src=[source] alt=(alt_text) - (self.props()) - style=[dimensions] {} + (self.props()) {} }) } } impl Image { /// Crea rápidamente una imagen especificando su origen. - pub fn with(source: bs::image::Source) -> Self { + pub fn with(source: image::Source) -> Self { Self::default().with_source(source) } @@ -87,11 +114,6 @@ impl Image { } /// Modifica identificador, clases CSS, atributos HTML o valores extra del componente. - /// - /// También acepta clases predefinidas para: - /// - /// - Establecer bordes ([`Border`]). - /// - Redondear las esquinas ([`Rounded`]). #[builder_fn] pub fn with_prop(mut self, op: PropsOp) -> Self { self.props.alter_prop(op); @@ -100,14 +122,14 @@ impl Image { /// Define las dimensiones de la imagen (auto, ancho/alto, ambos). #[builder_fn] - pub fn with_size(mut self, size: bs::image::Size) -> Self { + pub fn with_size(mut self, size: image::Size) -> Self { self.size = size; self } /// Establece el origen de la imagen, influyendo en su disposición en el contenido. #[builder_fn] - pub fn with_source(mut self, source: bs::image::Source) -> Self { + pub fn with_source(mut self, source: image::Source) -> Self { self.source = source; self } diff --git a/extensions/pagetop-bootsier/src/theme/bs/image/props.rs b/src/base/component/image/props.rs similarity index 54% rename from extensions/pagetop-bootsier/src/theme/bs/image/props.rs rename to src/base/component/image/props.rs index 89c117f3..d84d6380 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/image/props.rs +++ b/src/base/component/image/props.rs @@ -1,8 +1,8 @@ -use pagetop::prelude::*; +use crate::prelude::*; // **< Size >*************************************************************************************** -/// Define las **dimensiones** de una imagen ([`Image`](crate::theme::bs::Image)). +/// Define las **dimensiones** de una imagen ([`Image`](super::Image)). #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Size { /// Ajuste automático por defecto. @@ -30,23 +30,13 @@ pub enum Size { Both(UnitValue), } -impl Size { - /// Devuelve el valor del atributo `style` en función del tamaño, o `None` si no aplica. - #[inline] - pub fn to_style(self) -> Option { - 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 >************************************************************************************* -/// Especifica la **fuente** para publicar una imagen ([`Image`](crate::theme::bs::Image)). +/// Especifica la **fuente** para publicar una imagen ([`Image`](super::Image)). +/// +/// Las variantes son puramente semánticas. El componente aplica una clase CSS base según la +/// variante en su propio `setup()`; los temas pueden sobrescribirla interceptando el renderizado +/// del componente. #[derive(AutoDefault, Clone, Debug, PartialEq)] pub enum Source { /// Imagen con el logotipo de PageTop. @@ -56,71 +46,39 @@ pub enum Source { /// /// Lleva asociada la URL (o ruta) de la imagen. Responsive(CowStr), - /// Imagen que aplica el estilo **miniatura** de Bootstrap. + /// Imagen que aplica un estilo de miniatura. /// /// Lleva asociada la URL (o ruta) de la imagen. Thumbnail(CowStr), - /// Imagen sin clases específicas de Bootstrap, útil para controlar con CSS propio. + /// Imagen sin modificadores adicionales de estilo, útil para controlar la apariencia con CSS + /// propio. /// /// Lleva asociada la URL (o ruta) de la imagen. Plain(CowStr), } impl Source { - const IMG_FLUID: &str = "img-fluid"; - const IMG_THUMBNAIL: &str = "img-thumbnail"; - /// Imagen con el logotipo de PageTop. #[inline] pub fn logo(svg: PageTopSvg) -> Self { Self::Logo(svg) } - /// Imagen responsive (`img-fluid`). + /// Imagen responsive. #[inline] pub fn responsive(url: impl Into) -> Self { Self::Responsive(url.into()) } - /// Imagen miniatura (`img-thumbnail`). + /// Imagen miniatura. #[inline] pub fn thumbnail(url: impl Into) -> Self { Self::Thumbnail(url.into()) } - /// Imagen sin clases adicionales. + /// Imagen sin modificadores adicionales de estilo. #[inline] pub fn plain(url: impl Into) -> Self { Self::Plain(url.into()) } - - /// Devuelve la clase base asociada a la imagen según la fuente. - #[inline] - pub const 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 asociada al tipo de imagen a la cadena de clases. - #[inline] - pub fn push_to(&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 al tipo de imagen. - pub fn to_class(&self) -> String { - let mut class = String::new(); - self.push_to(&mut class); - class - } }