🚚 Gran actualización de Paquetes a Extensiones

This commit is contained in:
Manuel Cillero 2025-01-15 17:46:46 +01:00
parent f6b76caf8d
commit 3faaaa76ee
443 changed files with 1123 additions and 444 deletions

View file

@ -0,0 +1,143 @@
use pagetop::prelude::*;
use std::fmt;
// Utilities.
mod utility;
pub use utility::*;
// Container.
pub mod container;
pub use container::{Container, ContainerType};
// Grid.
pub mod grid;
pub use grid::Grid;
// Offcanvas.
pub mod offcanvas;
pub use offcanvas::{
Offcanvas, OffcanvasBackdrop, OffcanvasBodyScroll, OffcanvasPlacement, OffcanvasVisibility,
};
// Image.
mod image;
pub use image::{Image, ImageSize};
// Navbar.
pub mod navbar;
pub use navbar::{Navbar, NavbarContent, NavbarToggler};
// Dropdown.
pub mod dropdown;
pub use dropdown::Dropdown;
/// Define los puntos de interrupción (*breakpoints*) usados por Bootstrap para diseño responsivo.
#[rustfmt::skip]
#[derive(AutoDefault)]
pub enum BreakPoint {
#[default] // DIMENSIONES - DISPOSITIVOS ---------------------------------------------------
None, // < 576px Muy pequeños: teléfonos en modo vertical, menos de 576px
SM, // >= 576px Pequeños: teléfonos en modo horizontal, 576px o más
MD, // >= 768px Medianos: tabletas, 768px o más
LG, // >= 992px Grandes: puestos de escritorio, 992px o más
XL, // >= 1200px Muy grandes: puestos de escritorio grandes, 1200px o más
XXL, // >= 1400px Extragrandes: puestos de escritorio más grandes, 1400px o más
// ------------------------------------------------------------------------------
Fluid, // Para Container, aplica el 100% del dispositivo siempre
FluidMax(unit::Value) // Para Container, aplica el 100% del dispositivo hasta un ancho máximo
}
impl BreakPoint {
/// Verifica si es un punto de interrupción efectivo en Bootstrap.
///
/// Devuelve `true` si el valor es `SM`, `MD`, `LG`, `XL` o `XXL`. Y `false` en otro caso.
pub fn is_breakpoint(&self) -> bool {
!matches!(
self,
BreakPoint::None | BreakPoint::Fluid | BreakPoint::FluidMax(_)
)
}
/// Genera un nombre de clase CSS basado en el punto de interrupción.
///
/// Si es un punto de interrupción efectivo (ver [`is_breakpoint()`] se concatena el prefijo
/// proporcionado, un guion (`-`) y el texto asociado al punto de interrupción. En otro caso
/// devuelve únicamente el prefijo.
///
/// # Parámetros
///
/// - `prefix`: Prefijo para concatenar con el punto de interrupción.
///
/// # Ejemplo
///
/// ```rust#ignore
/// let breakpoint = BreakPoint::MD;
/// let class = breakpoint.to_class("col");
/// assert_eq!(class, "col-md".to_string());
///
/// let breakpoint = BreakPoint::Fluid;
/// let class = breakpoint.to_class("offcanvas");
/// assert_eq!(class, "offcanvas".to_string());
/// ```
pub fn to_class(&self, prefix: impl Into<String>) -> String {
let prefix: String = prefix.into();
if self.is_breakpoint() {
join_string!(prefix, "-", self.to_string())
} else {
prefix
}
}
/// Intenta generar un nombre de clase CSS basado en el punto de interrupción.
///
/// Si es un punto de interrupción efectivo (ver [`is_breakpoint()`] se concatena el prefijo
/// proporcionado, un guion (`-`) y el texto asociado al punto de interrupción. En otro caso,
/// devuelve `None`.
///
/// # Parámetros
///
/// - `prefix`: Prefijo a concatenar con el punto de interrupción.
///
/// # Retorno
///
/// - `Some(String)`: Si es un punto de interrupción efectivo.
/// - `None`: En otro caso.
///
/// # Ejemplo
///
/// ```rust#ignore
/// let breakpoint = BreakPoint::MD;
/// let class = breakpoint.try_class("col");
/// assert_eq!(class, Some("col-md".to_string()));
///
/// let breakpoint = BreakPoint::Fluid;
/// let class = breakpoint.try_class("navbar-expanded");
/// assert_eq!(class, None);
/// ```
pub fn try_class(&self, prefix: impl Into<String>) -> Option<String> {
let prefix: String = prefix.into();
if self.is_breakpoint() {
Some(join_string!(prefix, "-", self.to_string()))
} else {
None
}
}
}
/// Devuelve el texto asociado al punto de interrupción usado por Bootstrap.
#[rustfmt::skip]
impl fmt::Display for BreakPoint {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BreakPoint::None => write!(f, ""),
BreakPoint::SM => write!(f, "sm"),
BreakPoint::MD => write!(f, "md"),
BreakPoint::LG => write!(f, "lg"),
BreakPoint::XL => write!(f, "xl"),
BreakPoint::XXL => write!(f, "xxl"),
BreakPoint::Fluid => write!(f, "fluid"),
BreakPoint::FluidMax(_) => write!(f, "fluid"),
}
}
}

View file

@ -0,0 +1,172 @@
use pagetop::prelude::*;
use crate::bs::BreakPoint;
#[rustfmt::skip]
#[derive(AutoDefault)]
pub enum ContainerType {
#[default]
Default, // Contenedor genérico
Main, // Contenido principal
Header, // Encabezado
Footer, // Pie
Section, // Sección específica de contenido
Article, // Artículo dentro de una sección
}
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Container {
id : OptionId,
classes : OptionClasses,
container_type: ContainerType,
breakpoint : BreakPoint,
children : Children,
}
impl ComponentTrait for Container {
fn new() -> Self {
Container::default()
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
self.alter_classes(
ClassesOp::Prepend,
trio_string!("container", "-", self.breakpoint().to_string()),
);
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let output = self.children().render(cx);
if output.is_empty() {
return PrepareMarkup::None;
}
let style = if let BreakPoint::FluidMax(max_width) = self.breakpoint() {
Some(join_string!("max-width: ", max_width.to_string(), ";"))
} else {
None
};
match self.container_type() {
ContainerType::Default => PrepareMarkup::With(html! {
div id=[self.id()] class=[self.classes().get()] style=[style] {
(output)
}
}),
ContainerType::Main => PrepareMarkup::With(html! {
main id=[self.id()] class=[self.classes().get()] style=[style] {
(output)
}
}),
ContainerType::Header => PrepareMarkup::With(html! {
header id=[self.id()] class=[self.classes().get()] style=[style] {
(output)
}
}),
ContainerType::Footer => PrepareMarkup::With(html! {
footer id=[self.id()] class=[self.classes().get()] style=[style] {
(output)
}
}),
ContainerType::Section => PrepareMarkup::With(html! {
section id=[self.id()] class=[self.classes().get()] style=[style] {
(output)
}
}),
ContainerType::Article => PrepareMarkup::With(html! {
article id=[self.id()] class=[self.classes().get()] style=[style] {
(output)
}
}),
}
}
}
impl Container {
pub fn main() -> Self {
Container {
container_type: ContainerType::Main,
..Default::default()
}
}
pub fn header() -> Self {
Container {
container_type: ContainerType::Header,
..Default::default()
}
}
pub fn footer() -> Self {
Container {
container_type: ContainerType::Footer,
..Default::default()
}
}
pub fn section() -> Self {
Container {
container_type: ContainerType::Section,
..Default::default()
}
}
pub fn article() -> Self {
Container {
container_type: ContainerType::Article,
..Default::default()
}
}
// Container BUILDER.
#[fn_builder]
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> Self {
self.classes.alter_value(op, classes);
self
}
#[fn_builder]
pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self {
self.breakpoint = bp;
self
}
pub fn with_child(mut self, child: impl ComponentTrait) -> Self {
self.children.add(Child::with(child));
self
}
#[fn_builder]
pub fn with_children(mut self, op: ChildOp) -> Self {
self.children.alter_child(op);
self
}
// Container GETTERS.
pub fn classes(&self) -> &OptionClasses {
&self.classes
}
pub fn container_type(&self) -> &ContainerType {
&self.container_type
}
pub fn breakpoint(&self) -> &BreakPoint {
&self.breakpoint
}
pub fn children(&self) -> &Children {
&self.children
}
}

View file

@ -0,0 +1,5 @@
mod component;
pub use component::Dropdown;
mod item;
pub use item::Item;

View file

@ -0,0 +1,99 @@
use pagetop::prelude::*;
use crate::bs::dropdown;
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Dropdown {
id : OptionId,
classes: OptionClasses,
items : Children,
}
impl ComponentTrait for Dropdown {
fn new() -> Self {
Dropdown::default()
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
self.alter_classes(ClassesOp::Prepend, "dropdown");
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let items = self.items().render(cx);
if items.is_empty() {
return PrepareMarkup::None;
}
PrepareMarkup::With(html! {
div id=[self.id()] class=[self.classes().get()] {
button
type="button"
class="btn btn-secondary dropdown-toggle"
data-bs-toggle="dropdown"
aria-expanded="false"
{
("Dropdown button")
}
ul class="dropdown-menu" {
li {
a class="dropdown-item" href="#" {
("Action")
}
}
li {
a class="dropdown-item" href="#" {
("Another action")
}
}
li {
a class="dropdown-item" href="#" {
("Something else here")
}
}
}
}
})
}
}
impl Dropdown {
// Dropdown BUILDER.
#[fn_builder]
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> Self {
self.classes.alter_value(op, classes);
self
}
pub fn with_item(mut self, item: dropdown::Item) -> Self {
self.items.add(Child::with(item));
self
}
#[fn_builder]
pub fn with_items(mut self, op: TypedOp<dropdown::Item>) -> Self {
self.items.alter_typed(op);
self
}
// Dropdown GETTERS.
pub fn classes(&self) -> &OptionClasses {
&self.classes
}
pub fn items(&self) -> &Children {
&self.items
}
}

View file

@ -0,0 +1,109 @@
use pagetop::prelude::*;
type Label = L10n;
#[derive(AutoDefault)]
pub enum ItemType {
#[default]
Void,
Label(Label),
Link(Label, FnContextualPath),
LinkBlank(Label, FnContextualPath),
}
// Item.
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Item {
item_type: ItemType,
}
impl ComponentTrait for Item {
fn new() -> Self {
Item::default()
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let description: Option<String> = None;
// Obtiene la URL actual desde `cx.request`.
let current_path = cx.request().path();
match self.item_type() {
ItemType::Void => PrepareMarkup::None,
ItemType::Label(label) => PrepareMarkup::With(html! {
li class="dropdown-item" {
span title=[description] {
//(left_icon)
(label.escaped(cx.langid()))
//(right_icon)
}
}
}),
ItemType::Link(label, path) => {
let item_path = path(cx);
let (class, aria) = if item_path == current_path {
("dropdown-item active", Some("page"))
} else {
("dropdown-item", None)
};
PrepareMarkup::With(html! {
li class=(class) aria-current=[aria] {
a class="nav-link" href=(item_path) title=[description] {
//(left_icon)
(label.escaped(cx.langid()))
//(right_icon)
}
}
})
}
ItemType::LinkBlank(label, path) => {
let item_path = path(cx);
let (class, aria) = if item_path == current_path {
("dropdown-item active", Some("page"))
} else {
("dropdown-item", None)
};
PrepareMarkup::With(html! {
li class=(class) aria-current=[aria] {
a class="nav-link" href=(item_path) title=[description] target="_blank" {
//(left_icon)
(label.escaped(cx.langid()))
//(right_icon)
}
}
})
}
}
}
}
impl Item {
pub fn label(label: L10n) -> Self {
Item {
item_type: ItemType::Label(label),
..Default::default()
}
}
pub fn link(label: L10n, path: FnContextualPath) -> Self {
Item {
item_type: ItemType::Link(label, path),
..Default::default()
}
}
pub fn link_blank(label: L10n, path: FnContextualPath) -> Self {
Item {
item_type: ItemType::LinkBlank(label, path),
..Default::default()
}
}
// Item GETTERS.
pub fn item_type(&self) -> &ItemType {
&self.item_type
}
}

View file

@ -0,0 +1,110 @@
use pagetop::prelude::*;
use crate::bs::BreakPoint;
use std::fmt;
mod component;
pub use component::Grid;
mod item;
pub use item::Item;
#[derive(AutoDefault)]
pub enum Layout {
#[default]
Default,
Rows(u8),
Cols(u8),
Grid(u8, u8),
}
#[rustfmt::skip]
impl fmt::Display for Layout {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Layout::Rows(r) if *r > 1 => write!(f, "--bs-rows: {r};"),
Layout::Cols(c) if *c > 0 => write!(f, "--bs-columns: {c};"),
Layout::Grid(r, c) => write!(f, "{}", trio_string!(
Layout::Rows(*r).to_string(), " ", Layout::Cols(*c).to_string()
)),
_ => write!(f, ""),
}
}
}
#[derive(AutoDefault)]
pub enum Gap {
#[default]
Default,
Row(unit::Value),
Col(unit::Value),
Grid(unit::Value, unit::Value),
Both(unit::Value),
}
#[rustfmt::skip]
impl fmt::Display for Gap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Gap::Default => write!(f, ""),
Gap::Row(r) => write!(f, "row-gap: {r};"),
Gap::Col(c) => write!(f, "column-gap: {c};"),
Gap::Grid(r, c) => write!(f, "--bs-gap: {r} {c};"),
Gap::Both(v) => write!(f, "--bs-gap: {v};"),
}
}
}
#[derive(AutoDefault)]
pub enum ItemColumns {
#[default]
Default,
Cols(u8),
}
#[rustfmt::skip]
impl fmt::Display for ItemColumns {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ItemColumns::Cols(c) if *c > 1 => write!(f, "g-col-{c}"),
_ => write!(f, ""),
}
}
}
#[derive(AutoDefault)]
pub enum ItemResponsive {
#[default]
Default,
Cols(BreakPoint, u8),
}
#[rustfmt::skip]
impl fmt::Display for ItemResponsive {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ItemResponsive::Cols(bp, c) if bp.is_breakpoint() && *c > 0 => {
write!(f, "g-col-{bp}-{c}")
}
_ => write!(f, ""),
}
}
}
#[derive(AutoDefault)]
pub enum ItemStart {
#[default]
Default,
Col(u8),
}
#[rustfmt::skip]
impl fmt::Display for ItemStart {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ItemStart::Col(c) if *c > 1 => write!(f, "g-start-{c}"),
_ => write!(f, ""),
}
}
}

View file

@ -0,0 +1,103 @@
use pagetop::prelude::*;
use crate::bs::grid;
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Grid {
id : OptionId,
classes : OptionClasses,
grid_layout: grid::Layout,
grid_gap : grid::Gap,
items : Children,
}
impl ComponentTrait for Grid {
fn new() -> Self {
Grid::default()
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
self.alter_classes(ClassesOp::Prepend, "grid");
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let output = self.items().render(cx);
if output.is_empty() {
return PrepareMarkup::None;
}
let style = option_string!([self.layout().to_string(), self.gap().to_string()]; " ");
PrepareMarkup::With(html! {
div id=[self.id()] class=[self.classes().get()] style=[style] {
(output)
}
})
}
}
impl Grid {
pub fn with(item: grid::Item) -> Self {
Grid::default().with_item(item)
}
// Grid BUILDER.
#[fn_builder]
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> Self {
self.classes.alter_value(op, classes);
self
}
#[fn_builder]
pub fn with_layout(mut self, layout: grid::Layout) -> Self {
self.grid_layout = layout;
self
}
#[fn_builder]
pub fn with_gap(mut self, gap: grid::Gap) -> Self {
self.grid_gap = gap;
self
}
pub fn with_item(mut self, item: grid::Item) -> Self {
self.items.add(Child::with(item));
self
}
#[fn_builder]
pub fn with_items(mut self, op: TypedOp<grid::Item>) -> Self {
self.items.alter_typed(op);
self
}
// Grid GETTERS.
pub fn classes(&self) -> &OptionClasses {
&self.classes
}
pub fn layout(&self) -> &grid::Layout {
&self.grid_layout
}
pub fn gap(&self) -> &grid::Gap {
&self.grid_gap
}
pub fn items(&self) -> &Children {
&self.items
}
}

View file

@ -0,0 +1,119 @@
use pagetop::prelude::*;
use crate::bs::grid;
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Item {
id : OptionId,
classes : OptionClasses,
columns : grid::ItemColumns,
responsive: grid::ItemResponsive,
start : grid::ItemStart,
children : Children,
}
impl ComponentTrait for Item {
fn new() -> Self {
Item::default()
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
self.alter_classes(
ClassesOp::Prepend,
[
self.columns().to_string(),
self.responsive().to_string(),
self.start().to_string(),
]
.join(" "),
);
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let output = self.children().render(cx);
if output.is_empty() {
return PrepareMarkup::None;
}
PrepareMarkup::With(html! {
div id=[self.id()] class=[self.classes().get()] {
(output)
}
})
}
}
impl Item {
pub fn with(child: impl ComponentTrait) -> Self {
Item::default().with_child(child)
}
// Item BUILDER.
#[fn_builder]
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> Self {
self.classes.alter_value(op, classes);
self
}
#[fn_builder]
pub fn with_columns(mut self, columns: grid::ItemColumns) -> Self {
self.columns = columns;
self
}
#[fn_builder]
pub fn with_responsive(mut self, responsive: grid::ItemResponsive) -> Self {
self.responsive = responsive;
self
}
#[fn_builder]
pub fn with_start(mut self, start: grid::ItemStart) -> Self {
self.start = start;
self
}
pub fn with_child(mut self, child: impl ComponentTrait) -> Self {
self.children.add(Child::with(child));
self
}
#[fn_builder]
pub fn with_children(mut self, op: ChildOp) -> Self {
self.children.alter_child(op);
self
}
// Item GETTERS.
pub fn classes(&self) -> &OptionClasses {
&self.classes
}
pub fn columns(&self) -> &grid::ItemColumns {
&self.columns
}
pub fn responsive(&self) -> &grid::ItemResponsive {
&self.responsive
}
pub fn start(&self) -> &grid::ItemStart {
&self.start
}
pub fn children(&self) -> &Children {
&self.children
}
}

View file

@ -0,0 +1,157 @@
use pagetop::prelude::*;
use crate::bs::Border;
use std::fmt;
#[derive(AutoDefault)]
pub enum ImageType {
#[default]
Fluid,
Thumbnail,
}
#[rustfmt::skip]
impl fmt::Display for ImageType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ImageType::Fluid => write!(f, "img-fluid"),
ImageType::Thumbnail => write!(f, "img-thumbnail"),
}
}
}
#[derive(AutoDefault)]
pub enum ImageSize {
#[default]
Auto,
Size(u16, u16),
Width(u16),
Height(u16),
Both(u16),
}
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Image {
id : OptionId,
classes : OptionClasses,
image_type: ImageType,
source : OptionString,
size : ImageSize,
border : Border,
}
impl ComponentTrait for Image {
fn new() -> Self {
Image::default()
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
self.alter_classes(
ClassesOp::Prepend,
[self.image_type().to_string()].join(" "),
);
}
fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup {
let (width, height) = match self.size() {
ImageSize::Auto => (None, None),
ImageSize::Size(width, height) => (Some(width), Some(height)),
ImageSize::Width(width) => (Some(width), None),
ImageSize::Height(height) => (None, Some(height)),
ImageSize::Both(value) => (Some(value), Some(value)),
};
PrepareMarkup::With(html! {
img
src=[self.source().get()]
id=[self.id()]
class=[self.classes().get()]
width=[width]
height=[height] {}
})
}
}
impl Image {
pub fn with(source: &str) -> Self {
Image::default().with_source(source)
}
pub fn thumbnail(source: &str) -> Self {
Image::default()
.with_source(source)
.with_image_type(ImageType::Thumbnail)
}
/*
pub fn pagetop() -> Self {
Image::default()
.with_source("/base/pagetop-logo.svg")
.with_classes(ClassesOp::Add, IMG_FIXED)
.with_size(ImageSize::Size(64, 64))
}
*/
// Image BUILDER.
#[fn_builder]
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> Self {
self.classes.alter_value(op, classes);
self
}
#[fn_builder]
pub fn with_image_type(mut self, image_type: ImageType) -> Self {
self.image_type = image_type;
self
}
#[fn_builder]
pub fn with_source(mut self, source: &str) -> Self {
self.source.alter_value(source);
self
}
#[fn_builder]
pub fn with_size(mut self, size: ImageSize) -> Self {
self.size = size;
self
}
#[fn_builder]
pub fn with_border(mut self, border: Border) -> Self {
self.border = border;
self
}
// Image GETTERS.
pub fn classes(&self) -> &OptionClasses {
&self.classes
}
pub fn image_type(&self) -> &ImageType {
&self.image_type
}
pub fn source(&self) -> &OptionString {
&self.source
}
pub fn size(&self) -> &ImageSize {
&self.size
}
pub fn border(&self) -> &Border {
&self.border
}
}

View file

@ -0,0 +1,11 @@
mod component;
pub use component::{Navbar, NavbarContent, NavbarToggler};
//mod brand;
//pub use brand::Brand;
mod nav;
pub use nav::Nav;
mod item;
pub use item::{Item, ItemType};

View file

@ -0,0 +1,102 @@
use pagetop::prelude::*;
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Brand {
id : OptionId,
#[default(_code = "global::SETTINGS.app.name.to_owned()")]
app_name : String,
slogan : OptionTranslated,
logo : OptionComponent<Image>,
#[default(_code = "|_| \"/\"")]
home : FnContextualPath,
}
impl ComponentTrait for Brand {
fn new() -> Self {
Brand::default()
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let logo = self.logo().render(cx);
let home = self.home()(cx);
let title = &L10n::l("site_home").using(cx.langid());
PrepareMarkup::With(html! {
div id=[self.id()] class="branding__container" {
div class="branding__content" {
@if !logo.is_empty() {
a class="branding__logo" href=(home) title=[title] rel="home" {
(logo)
}
}
div class="branding__text" {
a class="branding__name" href=(home) title=[title] rel="home" {
(self.app_name())
}
@if let Some(slogan) = self.slogan().using(cx.langid()) {
div class="branding__slogan" {
(slogan)
}
}
}
}
}
})
}
}
impl Brand {
// Brand BUILDER.
#[fn_builder]
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_app_name(mut self, app_name: impl Into<String>) -> Self {
self.app_name = app_name.into();
self
}
#[fn_builder]
pub fn with_slogan(mut self, slogan: L10n) -> Self {
self.slogan.alter_value(slogan);
self
}
#[fn_builder]
pub fn with_logo(mut self, logo: Option<Image>) -> Self {
self.logo.alter_value(logo);
self
}
#[fn_builder]
pub fn with_home(mut self, home: FnContextualPath) -> Self {
self.home = home;
self
}
// Brand GETTERS.
pub fn app_name(&self) -> &String {
&self.app_name
}
pub fn slogan(&self) -> &OptionTranslated {
&self.slogan
}
pub fn logo(&self) -> &OptionComponent<Image> {
&self.logo
}
pub fn home(&self) -> &FnContextualPath {
&self.home
}
}

View file

@ -0,0 +1,202 @@
use pagetop::prelude::*;
use crate::bs::navbar;
use crate::bs::{BreakPoint, Offcanvas};
use crate::LOCALES_BOOTSIER;
const TOGGLE_COLLAPSE: &str = "collapse";
const TOGGLE_OFFCANVAS: &str = "offcanvas";
#[derive(AutoDefault)]
pub enum NavbarToggler {
#[default]
Enabled,
Disabled,
}
#[derive(AutoDefault)]
pub enum NavbarContent {
#[default]
None,
Nav(Typed<navbar::Nav>),
Offcanvas(Typed<Offcanvas>),
Text(L10n),
}
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Navbar {
id : OptionId,
classes: OptionClasses,
expand : BreakPoint,
toggler: NavbarToggler,
content: NavbarContent,
}
impl ComponentTrait for Navbar {
fn new() -> Self {
Navbar::default()
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
self.alter_classes(
ClassesOp::Prepend,
[
"navbar".to_string(),
self.expand().try_class("navbar-expand").unwrap_or_default(),
]
.join(" "),
);
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let id = cx.required_id::<Self>(self.id());
let content = match self.content() {
NavbarContent::None => return PrepareMarkup::None,
NavbarContent::Nav(nav) => {
let id_content = join_string!(id, "-content");
match self.toggler() {
NavbarToggler::Enabled => self.toggler_wrapper(
TOGGLE_COLLAPSE,
L10n::t("toggle", &LOCALES_BOOTSIER).using(cx.langid()),
id_content,
nav.render(cx),
),
NavbarToggler::Disabled => nav.render(cx),
}
}
NavbarContent::Offcanvas(oc) => {
let id_content = oc.id().unwrap_or_default();
self.toggler_wrapper(
TOGGLE_OFFCANVAS,
L10n::t("toggle", &LOCALES_BOOTSIER).using(cx.langid()),
id_content,
oc.render(cx),
)
}
NavbarContent::Text(text) => html! {
span class="navbar-text" {
(text.escaped(cx.langid()))
}
},
};
self.nav_wrapper(id, content)
}
}
impl Navbar {
pub fn with_nav(nav: navbar::Nav) -> Self {
Navbar::default().with_content(NavbarContent::Nav(Typed::with(nav)))
}
pub fn with_offcanvas(offcanvas: Offcanvas) -> Self {
Navbar::default().with_content(NavbarContent::Offcanvas(Typed::with(offcanvas)))
}
// Navbar BUILDER.
#[fn_builder]
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> Self {
self.classes.alter_value(op, classes);
self
}
#[fn_builder]
pub fn with_expand(mut self, bp: BreakPoint) -> Self {
self.expand = bp;
self
}
#[fn_builder]
pub fn with_toggler(mut self, toggler: NavbarToggler) -> Self {
self.toggler = toggler;
self
}
#[fn_builder]
pub fn with_content(mut self, content: NavbarContent) -> Self {
self.content = content;
self
}
// Navbar GETTERS.
pub fn classes(&self) -> &OptionClasses {
&self.classes
}
pub fn expand(&self) -> &BreakPoint {
&self.expand
}
pub fn toggler(&self) -> &NavbarToggler {
&self.toggler
}
pub fn content(&self) -> &NavbarContent {
&self.content
}
// Navbar HELPERS.
fn nav_wrapper(&self, id: String, content: Markup) -> PrepareMarkup {
if content.is_empty() {
PrepareMarkup::None
} else {
PrepareMarkup::With(html! {
nav id=(id) class=[self.classes().get()] {
div class="container-fluid" {
(content)
}
}
})
}
}
fn toggler_wrapper(
&self,
data_bs_toggle: &str,
aria_label: Option<String>,
id_content: String,
content: Markup,
) -> Markup {
if content.is_empty() {
html! {}
} else {
let id_content_target = join_string!("#", id_content);
let aria_expanded = if data_bs_toggle == TOGGLE_COLLAPSE {
Some("false")
} else {
None
};
html! {
button
type="button"
class="navbar-toggler"
data-bs-toggle=(data_bs_toggle)
data-bs-target=(id_content_target)
aria-controls=(id_content)
aria-expanded=[aria_expanded]
aria-label=[aria_label]
{
span class="navbar-toggler-icon" {}
}
div id=(id_content) class="collapse navbar-collapse" {
(content)
}
}
}
}
}

View file

@ -0,0 +1,113 @@
use pagetop::prelude::*;
use crate::bs::Dropdown;
type Label = L10n;
#[derive(AutoDefault)]
pub enum ItemType {
#[default]
Void,
Label(Label),
Link(Label, FnContextualPath),
LinkBlank(Label, FnContextualPath),
Dropdown(Typed<Dropdown>),
}
// Item.
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Item {
item_type: ItemType,
}
impl ComponentTrait for Item {
fn new() -> Self {
Item::default()
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let description: Option<String> = None;
// Obtiene la URL actual desde `cx.request`.
let current_path = cx.request().path();
match self.item_type() {
ItemType::Void => PrepareMarkup::None,
ItemType::Label(label) => PrepareMarkup::With(html! {
li class="nav-item" {
span title=[description] {
//(left_icon)
(label.escaped(cx.langid()))
//(right_icon)
}
}
}),
ItemType::Link(label, path) => {
let item_path = path(cx);
let (class, aria) = if item_path == current_path {
("nav-item active", Some("page"))
} else {
("nav-item", None)
};
PrepareMarkup::With(html! {
li class=(class) aria-current=[aria] {
a class="nav-link" href=(item_path) title=[description] {
//(left_icon)
(label.escaped(cx.langid()))
//(right_icon)
}
}
})
}
ItemType::LinkBlank(label, path) => {
let item_path = path(cx);
let (class, aria) = if item_path == current_path {
("nav-item active", Some("page"))
} else {
("nav-item", None)
};
PrepareMarkup::With(html! {
li class=(class) aria-current=[aria] {
a class="nav-link" href=(item_path) title=[description] target="_blank" {
//(left_icon)
(label.escaped(cx.langid()))
//(right_icon)
}
}
})
}
ItemType::Dropdown(menu) => PrepareMarkup::With(html! { (menu.render(cx)) }),
}
}
}
impl Item {
pub fn label(label: L10n) -> Self {
Item {
item_type: ItemType::Label(label),
..Default::default()
}
}
pub fn link(label: L10n, path: FnContextualPath) -> Self {
Item {
item_type: ItemType::Link(label, path),
..Default::default()
}
}
pub fn link_blank(label: L10n, path: FnContextualPath) -> Self {
Item {
item_type: ItemType::LinkBlank(label, path),
..Default::default()
}
}
// Item GETTERS.
pub fn item_type(&self) -> &ItemType {
&self.item_type
}
}

View file

@ -0,0 +1,75 @@
use pagetop::prelude::*;
use crate::bs::navbar;
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Nav {
id : OptionId,
classes: OptionClasses,
items : Children,
}
impl ComponentTrait for Nav {
fn new() -> Self {
Nav::default()
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
self.alter_classes(ClassesOp::Prepend, "navbar-nav");
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let items = self.items().render(cx);
if items.is_empty() {
return PrepareMarkup::None;
}
PrepareMarkup::With(html! {
ul id=[self.id()] class=[self.classes().get()] {
(items)
}
})
}
}
impl Nav {
// Nav BUILDER.
#[fn_builder]
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> Self {
self.classes.alter_value(op, classes);
self
}
pub fn with_item(mut self, item: navbar::Item) -> Self {
self.items.add(Child::with(item));
self
}
#[fn_builder]
pub fn with_items(mut self, op: TypedOp<navbar::Item>) -> Self {
self.items.alter_typed(op);
self
}
// Nav GETTERS.
pub fn classes(&self) -> &OptionClasses {
&self.classes
}
pub fn items(&self) -> &Children {
&self.items
}
}

View file

@ -0,0 +1,59 @@
use pagetop::prelude::*;
use std::fmt;
mod component;
pub use component::Offcanvas;
#[derive(AutoDefault)]
pub enum OffcanvasPlacement {
#[default]
Start,
End,
Top,
Bottom,
}
#[rustfmt::skip]
impl fmt::Display for OffcanvasPlacement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OffcanvasPlacement::Start => write!(f, "offcanvas-start"),
OffcanvasPlacement::End => write!(f, "offcanvas-end"),
OffcanvasPlacement::Top => write!(f, "offcanvas-top"),
OffcanvasPlacement::Bottom => write!(f, "offcanvas-bottom"),
}
}
}
#[derive(AutoDefault)]
pub enum OffcanvasVisibility {
#[default]
Default,
Show,
}
#[rustfmt::skip]
impl fmt::Display for OffcanvasVisibility {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OffcanvasVisibility::Default => write!(f, "show"),
OffcanvasVisibility::Show => write!(f, ""),
}
}
}
#[derive(AutoDefault)]
pub enum OffcanvasBodyScroll {
#[default]
Disabled,
Enabled,
}
#[derive(AutoDefault)]
pub enum OffcanvasBackdrop {
Disabled,
#[default]
Enabled,
Static,
}

View file

@ -0,0 +1,187 @@
use pagetop::prelude::*;
use crate::bs::BreakPoint;
use crate::bs::{OffcanvasBackdrop, OffcanvasBodyScroll, OffcanvasPlacement, OffcanvasVisibility};
use crate::LOCALES_BOOTSIER;
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Offcanvas {
id : OptionId,
classes : OptionClasses,
title : OptionTranslated,
breakpoint: BreakPoint,
placement : OffcanvasPlacement,
visibility: OffcanvasVisibility,
scrolling : OffcanvasBodyScroll,
backdrop : OffcanvasBackdrop,
children : Children,
}
impl ComponentTrait for Offcanvas {
fn new() -> Self {
Offcanvas::default()
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn setup_before_prepare(&mut self, _cx: &mut Context) {
self.alter_classes(
ClassesOp::Prepend,
[
self.breakpoint().to_class("offcanvas"),
self.placement().to_string(),
self.visibility().to_string(),
]
.join(" "),
);
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let body = self.children().render(cx);
if body.is_empty() {
return PrepareMarkup::None;
}
let id = cx.required_id::<Self>(self.id());
let id_label = join_string!(id, "-label");
let id_target = join_string!("#", id);
let body_scroll = match self.body_scroll() {
OffcanvasBodyScroll::Disabled => None,
OffcanvasBodyScroll::Enabled => Some("true".to_string()),
};
let backdrop = match self.backdrop() {
OffcanvasBackdrop::Disabled => Some("true".to_string()),
OffcanvasBackdrop::Enabled => None,
OffcanvasBackdrop::Static => Some("static".to_string()),
};
PrepareMarkup::With(html! {
div
id=(id)
class=[self.classes().get()]
tabindex="-1"
data-bs-scroll=[body_scroll]
data-bs-backdrop=[backdrop]
aria-labelledby=(id_label)
{
div class="offcanvas-header" {
h5 class="offcanvas-title" id=(id_label) {
(self.title().escaped(cx.langid()))
}
button
type="button"
class="btn-close"
data-bs-dismiss="offcanvas"
data-bs-target=(id_target)
aria-label=[L10n::t("close", &LOCALES_BOOTSIER).using(cx.langid())]
{}
}
div class="offcanvas-body" {
(body)
}
}
})
}
}
impl Offcanvas {
// Offcanvas BUILDER.
#[fn_builder]
pub fn with_id(mut self, id: impl Into<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> Self {
self.classes.alter_value(op, classes);
self
}
#[fn_builder]
pub fn with_title(mut self, title: L10n) -> Self {
self.title.alter_value(title);
self
}
#[fn_builder]
pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self {
self.breakpoint = bp;
self
}
#[fn_builder]
pub fn with_placement(mut self, placement: OffcanvasPlacement) -> Self {
self.placement = placement;
self
}
#[fn_builder]
pub fn with_visibility(mut self, visibility: OffcanvasVisibility) -> Self {
self.visibility = visibility;
self
}
#[fn_builder]
pub fn with_body_scroll(mut self, scrolling: OffcanvasBodyScroll) -> Self {
self.scrolling = scrolling;
self
}
#[fn_builder]
pub fn with_backdrop(mut self, backdrop: OffcanvasBackdrop) -> Self {
self.backdrop = backdrop;
self
}
pub fn with_child(mut self, child: impl ComponentTrait) -> Self {
self.children.add(Child::with(child));
self
}
#[fn_builder]
pub fn with_children(mut self, op: ChildOp) -> Self {
self.children.alter_child(op);
self
}
// Offcanvas GETTERS.
pub fn classes(&self) -> &OptionClasses {
&self.classes
}
pub fn title(&self) -> &OptionTranslated {
&self.title
}
pub fn breakpoint(&self) -> &BreakPoint {
&self.breakpoint
}
pub fn placement(&self) -> &OffcanvasPlacement {
&self.placement
}
pub fn visibility(&self) -> &OffcanvasVisibility {
&self.visibility
}
pub fn body_scroll(&self) -> &OffcanvasBodyScroll {
&self.scrolling
}
pub fn backdrop(&self) -> &OffcanvasBackdrop {
&self.backdrop
}
pub fn children(&self) -> &Children {
&self.children
}
}

View file

@ -0,0 +1,10 @@
mod color;
pub use color::Color;
pub use color::{BgColor, BorderColor, TextColor};
mod opacity;
pub use opacity::Opacity;
pub use opacity::{BgOpacity, BorderOpacity, TextOpacity};
mod border;
pub use border::{Border, BorderSize};

View file

@ -0,0 +1,102 @@
use pagetop::{prelude::*, strict_string};
use crate::bs::{BorderColor, BorderOpacity};
use std::fmt;
#[derive(AutoDefault)]
pub enum BorderSize {
#[default]
Default,
Zero,
Width1,
Width2,
Width3,
Width4,
Width5,
}
#[rustfmt::skip]
impl fmt::Display for BorderSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BorderSize::Default => write!(f, ""),
BorderSize::Zero => write!(f, "0"),
BorderSize::Width1 => write!(f, "1"),
BorderSize::Width2 => write!(f, "2"),
BorderSize::Width3 => write!(f, "3"),
BorderSize::Width4 => write!(f, "4"),
BorderSize::Width5 => write!(f, "5"),
}
}
}
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Border {
color : BorderColor,
opacity: BorderOpacity,
size : BorderSize,
top : BorderSize,
end : BorderSize,
bottom : BorderSize,
start : BorderSize,
}
impl Border {
pub fn new() -> Self {
Self::default()
}
pub fn with(size: BorderSize) -> Self {
Self::default().with_size(size)
}
// Border BUILDER.
pub fn with_color(mut self, color: BorderColor) -> Self {
self.color = color;
self
}
pub fn with_opacity(mut self, opacity: BorderOpacity) -> Self {
self.opacity = opacity;
self
}
pub fn with_size(mut self, size: BorderSize) -> Self {
self.size = size;
self
}
pub fn with_top(mut self, size: BorderSize) -> Self {
self.top = size;
self
}
pub fn with_end(mut self, size: BorderSize) -> Self {
self.end = size;
self
}
pub fn with_bottom(mut self, size: BorderSize) -> Self {
self.bottom = size;
self
}
pub fn with_start(mut self, size: BorderSize) -> Self {
self.start = size;
self
}
}
#[rustfmt::skip]
impl fmt::Display for Border {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", strict_string!([
"border",
&self.color.to_string(),
&self.opacity.to_string(),
]; " ").unwrap_or_default())
}
}

View file

@ -0,0 +1,119 @@
use pagetop::prelude::*;
use std::fmt;
#[derive(AutoDefault)]
pub enum Color {
#[default]
Primary,
Secondary,
Success,
Info,
Warning,
Danger,
Light,
Dark,
}
#[rustfmt::skip]
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Color::Primary => write!(f, "primary"),
Color::Secondary => write!(f, "secondary"),
Color::Success => write!(f, "success"),
Color::Info => write!(f, "info"),
Color::Warning => write!(f, "warning"),
Color::Danger => write!(f, "danger"),
Color::Light => write!(f, "light"),
Color::Dark => write!(f, "dark"),
}
}
}
#[derive(AutoDefault)]
pub enum BgColor {
#[default]
Default,
Body,
BodySecondary,
BodyTertiary,
Theme(Color),
Subtle(Color),
Black,
White,
Transparent,
}
#[rustfmt::skip]
impl fmt::Display for BgColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BgColor::Default => write!(f, ""),
BgColor::Body => write!(f, "bg-body"),
BgColor::BodySecondary => write!(f, "bg-body-secondary"),
BgColor::BodyTertiary => write!(f, "bg-body-tertiary"),
BgColor::Theme(c) => write!(f, "bg-{}", c),
BgColor::Subtle(c) => write!(f, "bg-{}-subtle", c),
BgColor::Black => write!(f, "bg-black"),
BgColor::White => write!(f, "bg-white"),
BgColor::Transparent => write!(f, "bg-transparent"),
}
}
}
#[derive(AutoDefault)]
pub enum BorderColor {
#[default]
Default,
Theme(Color),
Subtle(Color),
Black,
White,
}
#[rustfmt::skip]
impl fmt::Display for BorderColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BorderColor::Default => write!(f, ""),
BorderColor::Theme(c) => write!(f, "border-{}", c),
BorderColor::Subtle(c) => write!(f, "border-{}-subtle", c),
BorderColor::Black => write!(f, "border-black"),
BorderColor::White => write!(f, "border-white"),
}
}
}
#[derive(AutoDefault)]
pub enum TextColor {
#[default]
Default,
Body,
BodyEmphasis,
BodySecondary,
BodyTertiary,
Theme(Color),
Emphasis(Color),
Background(Color),
Black,
White,
}
#[rustfmt::skip]
impl fmt::Display for TextColor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TextColor::Default => write!(f, ""),
TextColor::Body => write!(f, "text-body"),
TextColor::BodyEmphasis => write!(f, "text-body-emphasis"),
TextColor::BodySecondary => write!(f, "text-body-secondary"),
TextColor::BodyTertiary => write!(f, "text-body-tertiary"),
TextColor::Theme(c) => write!(f, "text-{}", c),
TextColor::Emphasis(c) => write!(f, "text-{}-emphasis", c),
TextColor::Background(c) => write!(f, "text-bg-{}", c),
TextColor::Black => write!(f, "text-black"),
TextColor::White => write!(f, "text-white"),
}
}
}

View file

@ -0,0 +1,78 @@
use pagetop::prelude::*;
use std::fmt;
#[rustfmt::skip]
#[derive(AutoDefault)]
pub enum Opacity {
#[default]
Opaque, // 100%
SemiOpaque, // 75%
Half, // 50%
SemiTransparent, // 25%
AlmostTransparent, // 10%
}
#[rustfmt::skip]
impl fmt::Display for Opacity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Opacity::Opaque => write!(f, "opacity-100"),
Opacity::SemiOpaque => write!(f, "opacity-75"),
Opacity::Half => write!(f, "opacity-50"),
Opacity::SemiTransparent => write!(f, "opacity-25"),
Opacity::AlmostTransparent => write!(f, "opacity-10"),
}
}
}
#[derive(AutoDefault)]
pub enum BgOpacity {
#[default]
Default,
Theme(Opacity),
}
#[rustfmt::skip]
impl fmt::Display for BgOpacity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BgOpacity::Default => write!(f, ""),
BgOpacity::Theme(o) => write!(f, "bg-{}", o),
}
}
}
#[derive(AutoDefault)]
pub enum BorderOpacity {
#[default]
Default,
Theme(Opacity),
}
#[rustfmt::skip]
impl fmt::Display for BorderOpacity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BorderOpacity::Default => write!(f, ""),
BorderOpacity::Theme(o) => write!(f, "border-{}", o),
}
}
}
#[derive(AutoDefault)]
pub enum TextOpacity {
#[default]
Default,
Theme(Opacity),
}
#[rustfmt::skip]
impl fmt::Display for TextOpacity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TextOpacity::Default => write!(f, ""),
TextOpacity::Theme(o) => write!(f, "text-{}", o),
}
}
}

View file

@ -0,0 +1,116 @@
use pagetop::prelude::*;
use crate::bs::{Color, Opacity};
#[derive(AutoDefault)]
pub enum BorderSize {
#[default]
None,
Width1,
Width2,
Width3,
Width4,
Width5,
Free(unit::Value),
}
#[derive(AutoDefault)]
pub enum BorderRadius {
#[default]
None,
Rounded1,
Rounded2,
Rounded3,
Rounded4,
Rounded5,
Circle,
Pill,
Free(f32),
}
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct BorderProperty {
color : Color,
opacity: Opacity,
size : BorderSize,
radius : BorderRadius,
}
impl BorderProperty {
pub fn new() -> Self {
BorderProperty::default()
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn with_opacity(mut self, opacity: Opacity) -> Self {
self.opacity = opacity;
self
}
pub fn with_size(mut self, size: BorderSize) -> Self {
self.size = size;
self
}
pub fn with_radius(mut self, radius: BorderRadius) -> Self {
self.radius = radius;
self
}
}
#[rustfmt::skip]
#[derive(AutoDefault)]
pub struct Border {
all : Option<BorderProperty>,
top : Option<BorderProperty>,
end : Option<BorderProperty>,
bottom: Option<BorderProperty>,
start : Option<BorderProperty>,
}
impl Border {
pub fn new() -> Self {
Self::default()
}
// Border BUILDER.
pub fn with_all(mut self, border: BorderProperty) -> Self {
self.all = Some(border);
self
}
pub fn with_top(mut self, border: BorderProperty) -> Self {
self.top = Some(border);
self
}
pub fn with_end(mut self, border: BorderProperty) -> Self {
self.end = Some(border);
self
}
pub fn with_bottom(mut self, border: BorderProperty) -> Self {
self.bottom = Some(border);
self
}
pub fn with_start(mut self, border: BorderProperty) -> Self {
self.start = Some(border);
self
}
pub fn with_none(mut self) -> Self {
self.all = None;
self.top = None;
self.end = None;
self.bottom = None;
self.start = None;
self
}
}

View file

@ -0,0 +1,43 @@
//! Opciones de configuración.
//!
//! Ejemplo:
//!
//! ```toml
//! [bootsier]
//! max_width = "90rem"
//! ```
//!
//! Uso:
//!
//! ```rust#ignore
//! use pagetop_bootsier::config;
//!
//! assert_eq!(config::SETTINGS.bootsier.max_width, unit::Value::Rem(90));
//! ```
//!
//! Consulta [`pagetop::config`] para aprender cómo `PageTop` lee los archivos de opciones y aplica
//! los valores de configuración.
use pagetop::prelude::*;
use serde::Deserialize;
include_config!(SETTINGS: Settings => [
// [bootsier]
"bootsier.max_width" => "1440px",
]);
#[derive(Debug, Deserialize)]
/// Opciones de configuración para la sección [`[bootsier]`](Bootsier) (ver [`SETTINGS`]).
pub struct Settings {
pub bootsier: Bootsier,
}
#[derive(Debug, Deserialize)]
/// Sección `[bootsier]` de la configuración.
///
/// Ver [`Settings`].
pub struct Bootsier {
/// Ancho máximo predeterminado para la página, por ejemplo "100%" o "90rem".
/// Valor por defecto: *"1440px"*
pub max_width: unit::Value,
}

View file

@ -0,0 +1,221 @@
use pagetop::prelude::*;
// GLOBAL ******************************************************************************************
include_files!(bootsier_bs);
include_files!(bootsier_js);
include_locales!(LOCALES_BOOTSIER);
const BOOTSTRAP_VERSION: &str = "5.3.3"; // Versión de la librería Bootstrap.
// API *********************************************************************************************
pub mod config;
pub mod bs;
pub struct Bootsier;
impl ExtensionTrait for Bootsier {
fn theme(&self) -> Option<ThemeRef> {
Some(&Bootsier)
}
fn actions(&self) -> Vec<ActionBox> {
actions![
//action::theme::BeforeRender::<Region>::new(&Self, before_render_region),
//action::theme::BeforePrepare::<Button>::new(&Self, before_prepare_button),
//action::theme::BeforePrepare::<Heading>::new(&Self, before_prepare_heading),
//action::theme::BeforePrepare::<Paragraph>::new(&Self, before_prepare_paragraph),
//action::theme::RenderComponent::<Error404>::new(&Self, render_error404),
]
}
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
include_files_service!(scfg, bootsier_bs => "/bootsier/bs");
include_files_service!(scfg, bootsier_js => "/bootsier/js");
}
}
impl ThemeTrait for Bootsier {
#[rustfmt::skip]
fn regions(&self) -> Vec<(&'static str, L10n)> {
vec![
("region-header", L10n::t("header", &LOCALES_BOOTSIER)),
("region-nav_branding", L10n::t("nav_branding", &LOCALES_BOOTSIER)),
("region-nav_main", L10n::t("nav_main", &LOCALES_BOOTSIER)),
("region-nav_additional", L10n::t("nav_additional", &LOCALES_BOOTSIER)),
("region-breadcrumb", L10n::t("breadcrumb", &LOCALES_BOOTSIER)),
("region-content", L10n::t("content", &LOCALES_BOOTSIER)),
("region-sidebar_first", L10n::t("sidebar_first", &LOCALES_BOOTSIER)),
("region-sidebar_second", L10n::t("sidebar_second", &LOCALES_BOOTSIER)),
("region-footer", L10n::t("footer", &LOCALES_BOOTSIER)),
]
}
fn render_page_body(&self, page: &mut Page) -> Markup {
html! {
body id=[page.body_id().get()] class=[page.body_classes().get()] {
//@if let Some(skip) = L10n::l("skip_to_content").using(page.context().langid()) {
// div class="skip__to_content" {
// a href=(concat_string!("#", skip_to_id)) { (skip) }
// }
//}
(bs::Container::new()
.with_id("container-wrapper")
.with_breakpoint(bs::BreakPoint::FluidMax(config::SETTINGS.bootsier.max_width))
.with_child(Region::of("region-content"))
.render(page.context()))
}
}
}
fn after_render_page_body(&self, page: &mut Page) {
page.alter_assets(AssetsOp::AddStyleSheet(
StyleSheet::from("/bootsier/bs/bootstrap.min.css")
.with_version(BOOTSTRAP_VERSION)
.with_weight(-99),
))
.alter_assets(AssetsOp::AddJavaScript(
JavaScript::defer("/bootsier/js/bootstrap.min.js")
.with_version(BOOTSTRAP_VERSION)
.with_weight(-99),
));
}
/*
fn prepare_body(&self, page: &mut Page) -> PrepareMarkup {
let skip_to_id = page.body_skip_to().get().unwrap_or("content".to_owned());
PrepareMarkup::With(html! {
body id=[page.body_id().get()] class=[page.body_classes().get()] {
@if let Some(skip) = L10n::l("skip_to_content").using(page.context().langid()) {
div class="skip__to_content" {
a href=(concat_string!("#", skip_to_id)) { (skip) }
}
}
(match page.context().layout() {
"admin" => flex::Container::new()
.add_item(flex::Item::region().with_id("top-menu"))
.add_item(flex::Item::region().with_id("side-menu"))
.add_item(flex::Item::region().with_id("content")),
_ => flex::Container::new()
.add_item(flex::Item::region().with_id("header"))
.add_item(flex::Item::region().with_id("nav_branding"))
.add_item(flex::Item::region().with_id("nav_main"))
.add_item(flex::Item::region().with_id("nav_additional"))
.add_item(flex::Item::region().with_id("breadcrumb"))
.add_item(flex::Item::region().with_id("content"))
.add_item(flex::Item::region().with_id("sidebar_first"))
.add_item(flex::Item::region().with_id("sidebar_second"))
.add_item(flex::Item::region().with_id("footer")),
}.render(page.context()))
}
})
}
*/
/*
}
fn before_prepare_icon(i: &mut Icon, _cx: &mut Context) {
i.set_classes(
ClassesOp::Replace(i.font_size().to_string()),
with_font(i.font_size()),
);
}
#[rustfmt::skip]
fn before_prepare_button(b: &mut Button, _cx: &mut Context) {
b.set_classes(ClassesOp::Replace("button__tap".to_owned()), "btn");
b.set_classes(
ClassesOp::Replace(b.style().to_string()),
match b.style() {
StyleBase::Default => "btn-primary",
StyleBase::Info => "btn-info",
StyleBase::Success => "btn-success",
StyleBase::Warning => "btn-warning",
StyleBase::Danger => "btn-danger",
StyleBase::Light => "btn-light",
StyleBase::Dark => "btn-dark",
StyleBase::Link => "btn-link",
},
);
b.set_classes(
ClassesOp::Replace(b.font_size().to_string()),
with_font(b.font_size()),
);
}
#[rustfmt::skip]
fn before_prepare_heading(h: &mut Heading, _cx: &mut Context) {
h.set_classes(
ClassesOp::Replace(h.size().to_string()),
match h.size() {
HeadingSize::ExtraLarge => "display-1",
HeadingSize::XxLarge => "display-2",
HeadingSize::XLarge => "display-3",
HeadingSize::Large => "display-4",
HeadingSize::Medium => "display-5",
_ => "",
},
);
}
fn before_prepare_paragraph(p: &mut Paragraph, _cx: &mut Context) {
p.set_classes(
ClassesOp::Replace(p.font_size().to_string()),
with_font(p.font_size()),
);
}
fn render_error404(_: &Error404, cx: &mut Context) -> Option<Markup> {
Some(html! {
div class="jumbotron" {
div class="media" {
img
src="/bootsier/images/caution.png"
class="mr-4"
style="width: 20%; max-width: 188px"
alt="Caution!";
div class="media-body" {
h1 class="display-4" { ("RESOURCE NOT FOUND") }
p class="lead" {
(L10n::t("e404-description", &LOCALES_BOOTSIER)
.escaped(cx.langid()))
}
hr class="my-4";
p {
(L10n::t("e404-description", &LOCALES_BOOTSIER)
.escaped(cx.langid()))
}
a
class="btn btn-primary btn-lg"
href="/"
role="button"
{
(L10n::t("back-homepage", &LOCALES_BOOTSIER)
.escaped(cx.langid()))
}
}
}
}
})
*/
}
/*
#[rustfmt::skip]
fn with_font(font_size: &FontSize) -> String {
String::from(match font_size {
FontSize::ExtraLarge => "fs-1",
FontSize::XxLarge => "fs-2",
FontSize::XLarge => "fs-3",
FontSize::Large => "fs-4",
FontSize::Medium => "fs-5",
_ => "",
})
}
*/

View file

@ -0,0 +1,5 @@
e404-description = Oops! Page Not Found
e404-message = The page you are looking for may have been removed, had its name changed, or is temporarily unavailable.
e500-description = Oops! Unexpected Error
e500-message = We're having an issue. Please report this error to an administrator.
back-homepage = Back to homepage

View file

@ -0,0 +1,5 @@
# Offcanvas
close = Close
# Navbar
toggle = Toggle navigation

View file

@ -0,0 +1,9 @@
header = Header
nav_branding = Navigation branding region
nav_main = Main navigation region
nav_additional = Additional navigation region (eg search form, social icons, etc)
breadcrumb = Breadcrumb
content = Main content
sidebar_first = Sidebar first
sidebar_second = Sidebar second
footer = Footer

View file

@ -0,0 +1,5 @@
e404-description = ¡Vaya! Página No Encontrada
e404-message = La página que está buscando puede haber sido eliminada, cambiada de nombre o no está disponible temporalmente.
e500-description = ¡Vaya! Error Inesperado
e500-message = Está ocurriendo una incidencia. Por favor, informe de este error a un administrador.
back-homepage = Volver al inicio

View file

@ -0,0 +1,5 @@
# Offcanvas
close = Cerrar
# Navbar
toggle = Mostrar/ocultar navegación

View file

@ -0,0 +1,9 @@
header = Cabecera
nav_branding = Navegación y marca
nav_main = Navegación principal
nav_additional = Navegación adicional (p.e. formulario de búsqueda, iconos sociales, etc.)
breadcrumb = Ruta de posicionamiento
content = Contenido principal
sidebar_first = Barra lateral primera
sidebar_second = Barra lateral segunda
footer = Pie