♻️ (bootsier): Mueve input/select/textarea a theme
Consolida los handlers de estos campos en la capa del tema. Además, `with_floating_label()` usa un valor extra que lee en `setup()` para forzar `multiple`/`rows` en 'select` y `textarea`.
This commit is contained in:
parent
bff7cf1b38
commit
87ad78778d
7 changed files with 241 additions and 223 deletions
|
|
@ -4,6 +4,8 @@ use pagetop::prelude::*;
|
|||
|
||||
pub use pagetop::base::component::form::input::{Field, Kind, Mode};
|
||||
|
||||
const EXTRA_FLOATING_LABEL: &str = "bootsier.form.input.floating_label";
|
||||
|
||||
/// Extensión de Bootsier para [`form::input::Field`].
|
||||
///
|
||||
/// Proporciona soporte para **etiquetas flotantes** (*floating label*). La etiqueta flotante se
|
||||
|
|
@ -31,10 +33,76 @@ pub trait InputBootsier {
|
|||
|
||||
impl InputBootsier for Field {
|
||||
fn with_floating_label(self, floating: bool) -> Self {
|
||||
if floating {
|
||||
self.with_prop(PropsOp::add_classes("form-floating"))
|
||||
} else {
|
||||
self.with_prop(PropsOp::remove_classes("form-floating"))
|
||||
}
|
||||
self.with_prop(PropsOp::set_extra(EXTRA_FLOATING_LABEL, floating))
|
||||
}
|
||||
}
|
||||
|
||||
// **< Field SETUP >********************************************************************************
|
||||
|
||||
pub(crate) fn setup(field: &mut Field) {
|
||||
if field.props().extra_or(EXTRA_FLOATING_LABEL, false) {
|
||||
field.alter_prop(PropsOp::add_classes("form-floating"));
|
||||
} else {
|
||||
field.alter_prop(PropsOp::remove_classes("form-floating"));
|
||||
}
|
||||
}
|
||||
|
||||
// **< Field RENDER >*******************************************************************************
|
||||
|
||||
pub(crate) fn render(field: &Field, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let container_id = field.id();
|
||||
let input_id = container_id.as_deref().map(|id| util::join!(id, "-input"));
|
||||
let floating = field.props().extra_or(EXTRA_FLOATING_LABEL, false);
|
||||
let input_class = if *field.plaintext() {
|
||||
"form-control-plaintext"
|
||||
} else {
|
||||
"form-control"
|
||||
};
|
||||
// La etiqueta flotante requiere `placeholder` para animar la etiqueta; si no está definido, se
|
||||
// fuerza `placeholder=""`.
|
||||
let placeholder = if floating {
|
||||
Some(field.placeholder().lookup(cx).unwrap_or_default())
|
||||
} else {
|
||||
field.placeholder().lookup(cx)
|
||||
};
|
||||
let label = match field.label().lookup(cx) {
|
||||
Some(text) => html! {
|
||||
label for=[input_id.as_deref()] class="form-label" {
|
||||
(text)
|
||||
@if *field.required() {
|
||||
span
|
||||
class="form-required"
|
||||
title=(L10n::l("field_required").using(cx))
|
||||
{
|
||||
"*"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => html! {},
|
||||
};
|
||||
Ok(html! {
|
||||
div (field.props()) {
|
||||
@if !floating { (label) }
|
||||
input
|
||||
type=(field.kind())
|
||||
id=[input_id.as_deref()]
|
||||
class=(input_class)
|
||||
name=[field.name().get()]
|
||||
value=[field.value().get()]
|
||||
minlength=[field.minlength().get()]
|
||||
maxlength=[field.maxlength().get()]
|
||||
placeholder=[placeholder]
|
||||
inputmode=[field.inputmode().get()]
|
||||
autocomplete=[field.autocomplete().get()]
|
||||
autofocus[*field.autofocus()]
|
||||
readonly[*field.readonly() || *field.plaintext()]
|
||||
required[*field.required()]
|
||||
disabled[*field.disabled()];
|
||||
@if floating { (label) }
|
||||
@if let Some(description) = field.help_text().lookup(cx) {
|
||||
div class="form-text" { (description) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ use pagetop::prelude::*;
|
|||
|
||||
pub use pagetop::base::component::form::select::{Entry, Field, Group, Item};
|
||||
|
||||
const EXTRA_FLOATING_LABEL: &str = "bootsier.form.select.floating_label";
|
||||
|
||||
/// Extensión de Bootsier para [`form::select::Field`].
|
||||
///
|
||||
/// Proporciona soporte para **etiquetas flotantes** (*floating label*). La etiqueta flotante se
|
||||
|
|
@ -36,10 +38,92 @@ pub trait SelectBootsier {
|
|||
|
||||
impl SelectBootsier for Field {
|
||||
fn with_floating_label(self, floating: bool) -> Self {
|
||||
if floating {
|
||||
self.with_prop(PropsOp::add_classes("form-floating"))
|
||||
} else {
|
||||
self.with_prop(PropsOp::remove_classes("form-floating"))
|
||||
}
|
||||
self.with_prop(PropsOp::set_extra(EXTRA_FLOATING_LABEL, floating))
|
||||
}
|
||||
}
|
||||
|
||||
// **< Field SETUP >********************************************************************************
|
||||
|
||||
pub(crate) fn setup(field: &mut Field) {
|
||||
if field.props().extra_or(EXTRA_FLOATING_LABEL, false) {
|
||||
field.alter_prop(PropsOp::add_classes("form-floating"));
|
||||
field.alter_multiple(false);
|
||||
field.alter_rows(None::<u16>);
|
||||
} else {
|
||||
field.alter_prop(PropsOp::remove_classes("form-floating"));
|
||||
}
|
||||
}
|
||||
|
||||
// **< Field RENDER >*******************************************************************************
|
||||
|
||||
pub(crate) fn render(field: &Field, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let container_id = field.id();
|
||||
let select_id = container_id.as_deref().map(|id| util::join!(id, "-select"));
|
||||
let floating = field.props().extra_or(EXTRA_FLOATING_LABEL, false);
|
||||
let label = match field.label().lookup(cx) {
|
||||
Some(text) => html! {
|
||||
label for=[select_id.as_deref()] class="form-label" {
|
||||
(text)
|
||||
@if *field.required() {
|
||||
span
|
||||
class="form-required"
|
||||
title=(L10n::l("field_required").using(cx))
|
||||
{
|
||||
"*"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => html! {},
|
||||
};
|
||||
Ok(html! {
|
||||
div (field.props()) {
|
||||
@if !floating { (label) }
|
||||
select
|
||||
id=[select_id.as_deref()]
|
||||
class="form-select"
|
||||
name=[field.name().get()]
|
||||
multiple[*field.multiple()]
|
||||
size=[field.rows().get()]
|
||||
autocomplete=[field.autocomplete().get()]
|
||||
autofocus[*field.autofocus()]
|
||||
required[*field.required()]
|
||||
disabled[*field.disabled()]
|
||||
{
|
||||
@for entry in field.entries() {
|
||||
@match entry {
|
||||
form::select::Entry::Item(opt) => {
|
||||
option
|
||||
value=(opt.value().as_str().unwrap_or(""))
|
||||
selected[*opt.selected()]
|
||||
disabled[*opt.disabled()]
|
||||
{
|
||||
(opt.label().using(cx))
|
||||
}
|
||||
}
|
||||
form::select::Entry::Group(group) => {
|
||||
optgroup
|
||||
label=(group.label().using(cx))
|
||||
disabled[*group.disabled()]
|
||||
{
|
||||
@for opt in group.items() {
|
||||
option
|
||||
value=(opt.value().as_str().unwrap_or(""))
|
||||
selected[*opt.selected()]
|
||||
disabled[*opt.disabled()]
|
||||
{
|
||||
(opt.label().using(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@if floating { (label) }
|
||||
@if let Some(description) = field.help_text().lookup(cx) {
|
||||
div class="form-text" { (description) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ use pagetop::prelude::*;
|
|||
|
||||
pub use pagetop::base::component::form::Textarea;
|
||||
|
||||
const EXTRA_FLOATING_LABEL: &str = "bootsier.form.textarea.floating_label";
|
||||
|
||||
/// Extensión de Bootsier para [`form::Textarea`].
|
||||
///
|
||||
/// Proporciona soporte para **etiquetas flotantes** (*floating label*). La etiqueta flotante se
|
||||
|
|
@ -34,10 +36,75 @@ pub trait TextareaBootsier {
|
|||
|
||||
impl TextareaBootsier for Textarea {
|
||||
fn with_floating_label(self, floating: bool) -> Self {
|
||||
if floating {
|
||||
self.with_prop(PropsOp::add_classes("form-floating"))
|
||||
} else {
|
||||
self.with_prop(PropsOp::remove_classes("form-floating"))
|
||||
}
|
||||
self.with_prop(PropsOp::set_extra(EXTRA_FLOATING_LABEL, floating))
|
||||
}
|
||||
}
|
||||
|
||||
// **< Textarea SETUP >*****************************************************************************
|
||||
|
||||
pub(crate) fn setup(field: &mut Textarea) {
|
||||
if field.props().extra_or(EXTRA_FLOATING_LABEL, false) {
|
||||
field.alter_prop(PropsOp::add_classes("form-floating"));
|
||||
field.alter_rows(None::<u16>);
|
||||
} else {
|
||||
field.alter_prop(PropsOp::remove_classes("form-floating"));
|
||||
}
|
||||
}
|
||||
|
||||
// **< Textarea RENDER >****************************************************************************
|
||||
|
||||
pub(crate) fn render(field: &Textarea, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let container_id = field.id();
|
||||
let textarea_id = container_id
|
||||
.as_deref()
|
||||
.map(|id| util::join!(id, "-textarea"));
|
||||
let floating = field.props().extra_or(EXTRA_FLOATING_LABEL, false);
|
||||
// La etiqueta flotante requiere `placeholder` para animar la etiqueta; si no está definido se
|
||||
// fuerza `placeholder=""`.
|
||||
let placeholder = if floating {
|
||||
Some(field.placeholder().lookup(cx).unwrap_or_default())
|
||||
} else {
|
||||
field.placeholder().lookup(cx)
|
||||
};
|
||||
let label = match field.label().lookup(cx) {
|
||||
Some(text) => html! {
|
||||
label for=[textarea_id.as_deref()] class="form-label" {
|
||||
(text)
|
||||
@if *field.required() {
|
||||
span
|
||||
class="form-required"
|
||||
title=(L10n::l("field_required").using(cx))
|
||||
{
|
||||
"*"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => html! {},
|
||||
};
|
||||
Ok(html! {
|
||||
div (field.props()) {
|
||||
@if !floating { (label) }
|
||||
textarea
|
||||
id=[textarea_id.as_deref()]
|
||||
class="form-control"
|
||||
name=[field.name().get()]
|
||||
rows=[field.rows().get()]
|
||||
minlength=[field.minlength().get()]
|
||||
maxlength=[field.maxlength().get()]
|
||||
placeholder=[placeholder]
|
||||
autocomplete=[field.autocomplete().get()]
|
||||
autofocus[*field.autofocus()]
|
||||
readonly[*field.readonly()]
|
||||
required[*field.required()]
|
||||
disabled[*field.disabled()]
|
||||
{
|
||||
@if let Some(value) = field.value().get() { (value) }
|
||||
}
|
||||
@if floating { (label) }
|
||||
@if let Some(description) = field.help_text().lookup(cx) {
|
||||
div class="form-text" { (description) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue