🚚 Gran actualización de Paquetes a Extensiones
This commit is contained in:
parent
f6b76caf8d
commit
3faaaa76ee
443 changed files with 1123 additions and 444 deletions
143
extensions/pagetop-bootsier/src/bs.rs
Normal file
143
extensions/pagetop-bootsier/src/bs.rs
Normal 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
172
extensions/pagetop-bootsier/src/bs/container.rs
Normal file
172
extensions/pagetop-bootsier/src/bs/container.rs
Normal 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
|
||||
}
|
||||
}
|
||||
5
extensions/pagetop-bootsier/src/bs/dropdown.rs
Normal file
5
extensions/pagetop-bootsier/src/bs/dropdown.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod component;
|
||||
pub use component::Dropdown;
|
||||
|
||||
mod item;
|
||||
pub use item::Item;
|
||||
99
extensions/pagetop-bootsier/src/bs/dropdown/component.rs
Normal file
99
extensions/pagetop-bootsier/src/bs/dropdown/component.rs
Normal 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
|
||||
}
|
||||
}
|
||||
109
extensions/pagetop-bootsier/src/bs/dropdown/item.rs
Normal file
109
extensions/pagetop-bootsier/src/bs/dropdown/item.rs
Normal 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
|
||||
}
|
||||
}
|
||||
110
extensions/pagetop-bootsier/src/bs/grid.rs
Normal file
110
extensions/pagetop-bootsier/src/bs/grid.rs
Normal 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, ""),
|
||||
}
|
||||
}
|
||||
}
|
||||
103
extensions/pagetop-bootsier/src/bs/grid/component.rs
Normal file
103
extensions/pagetop-bootsier/src/bs/grid/component.rs
Normal 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
|
||||
}
|
||||
}
|
||||
119
extensions/pagetop-bootsier/src/bs/grid/item.rs
Normal file
119
extensions/pagetop-bootsier/src/bs/grid/item.rs
Normal 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
|
||||
}
|
||||
}
|
||||
157
extensions/pagetop-bootsier/src/bs/image.rs
Normal file
157
extensions/pagetop-bootsier/src/bs/image.rs
Normal 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
|
||||
}
|
||||
}
|
||||
11
extensions/pagetop-bootsier/src/bs/navbar.rs
Normal file
11
extensions/pagetop-bootsier/src/bs/navbar.rs
Normal 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};
|
||||
102
extensions/pagetop-bootsier/src/bs/navbar/brand.rs
Normal file
102
extensions/pagetop-bootsier/src/bs/navbar/brand.rs
Normal 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
|
||||
}
|
||||
}
|
||||
202
extensions/pagetop-bootsier/src/bs/navbar/component.rs
Normal file
202
extensions/pagetop-bootsier/src/bs/navbar/component.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
113
extensions/pagetop-bootsier/src/bs/navbar/item.rs
Normal file
113
extensions/pagetop-bootsier/src/bs/navbar/item.rs
Normal 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
|
||||
}
|
||||
}
|
||||
75
extensions/pagetop-bootsier/src/bs/navbar/nav.rs
Normal file
75
extensions/pagetop-bootsier/src/bs/navbar/nav.rs
Normal 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
|
||||
}
|
||||
}
|
||||
59
extensions/pagetop-bootsier/src/bs/offcanvas.rs
Normal file
59
extensions/pagetop-bootsier/src/bs/offcanvas.rs
Normal 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,
|
||||
}
|
||||
187
extensions/pagetop-bootsier/src/bs/offcanvas/component.rs
Normal file
187
extensions/pagetop-bootsier/src/bs/offcanvas/component.rs
Normal 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
|
||||
}
|
||||
}
|
||||
10
extensions/pagetop-bootsier/src/bs/utility.rs
Normal file
10
extensions/pagetop-bootsier/src/bs/utility.rs
Normal 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};
|
||||
102
extensions/pagetop-bootsier/src/bs/utility/border.rs
Normal file
102
extensions/pagetop-bootsier/src/bs/utility/border.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
119
extensions/pagetop-bootsier/src/bs/utility/color.rs
Normal file
119
extensions/pagetop-bootsier/src/bs/utility/color.rs
Normal 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
78
extensions/pagetop-bootsier/src/bs/utility/opacity.rs
Normal file
78
extensions/pagetop-bootsier/src/bs/utility/opacity.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
116
extensions/pagetop-bootsier/src/bs/utility/rounded.rs
Normal file
116
extensions/pagetop-bootsier/src/bs/utility/rounded.rs
Normal 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
|
||||
}
|
||||
}
|
||||
43
extensions/pagetop-bootsier/src/config.rs
Normal file
43
extensions/pagetop-bootsier/src/config.rs
Normal 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,
|
||||
}
|
||||
221
extensions/pagetop-bootsier/src/lib.rs
Normal file
221
extensions/pagetop-bootsier/src/lib.rs
Normal 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",
|
||||
_ => "",
|
||||
})
|
||||
}
|
||||
*/
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Offcanvas
|
||||
close = Close
|
||||
|
||||
# Navbar
|
||||
toggle = Toggle navigation
|
||||
9
extensions/pagetop-bootsier/src/locale/en-US/regions.ftl
Normal file
9
extensions/pagetop-bootsier/src/locale/en-US/regions.ftl
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Offcanvas
|
||||
close = Cerrar
|
||||
|
||||
# Navbar
|
||||
toggle = Mostrar/ocultar navegación
|
||||
9
extensions/pagetop-bootsier/src/locale/es-ES/regions.ftl
Normal file
9
extensions/pagetop-bootsier/src/locale/es-ES/regions.ftl
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue