♻️ (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
|
|
@ -1,59 +0,0 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
pub fn render(c: &form::input::Field, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let container_id = c.id();
|
||||
let input_id = container_id.as_deref().map(|id| util::join!(id, "-input"));
|
||||
let floating = c.props().has_class("form-floating");
|
||||
let input_class = if *c.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(c.placeholder().lookup(cx).unwrap_or_default())
|
||||
} else {
|
||||
c.placeholder().lookup(cx)
|
||||
};
|
||||
let label = match c.label().lookup(cx) {
|
||||
Some(text) => html! {
|
||||
label for=[input_id.as_deref()] class="form-label" {
|
||||
(text)
|
||||
@if *c.required() {
|
||||
span
|
||||
class="form-required"
|
||||
title=(L10n::l("field_required").using(cx))
|
||||
{
|
||||
"*"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => html! {},
|
||||
};
|
||||
Ok(html! {
|
||||
div (c.props()) {
|
||||
@if !floating { (label) }
|
||||
input
|
||||
type=(c.kind())
|
||||
id=[input_id.as_deref()]
|
||||
class=(input_class)
|
||||
name=[c.name().get()]
|
||||
value=[c.value().get()]
|
||||
minlength=[c.minlength().get()]
|
||||
maxlength=[c.maxlength().get()]
|
||||
placeholder=[placeholder]
|
||||
inputmode=[c.inputmode().get()]
|
||||
autocomplete=[c.autocomplete().get()]
|
||||
autofocus[*c.autofocus()]
|
||||
readonly[*c.readonly() || *c.plaintext()]
|
||||
required[*c.required()]
|
||||
disabled[*c.disabled()];
|
||||
@if floating { (label) }
|
||||
@if let Some(description) = c.help_text().lookup(cx) {
|
||||
div class="form-text" { (description) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
pub fn setup(c: &mut form::select::Field) {
|
||||
if c.props().has_class("form-floating") {
|
||||
c.alter_multiple(false);
|
||||
c.alter_rows(None::<u16>);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(c: &form::select::Field, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let container_id = c.id();
|
||||
let select_id = container_id.as_deref().map(|id| util::join!(id, "-select"));
|
||||
let floating = c.props().has_class("form-floating");
|
||||
let label = match c.label().lookup(cx) {
|
||||
Some(text) => html! {
|
||||
label for=[select_id.as_deref()] class="form-label" {
|
||||
(text)
|
||||
@if *c.required() {
|
||||
span
|
||||
class="form-required"
|
||||
title=(L10n::l("field_required").using(cx))
|
||||
{
|
||||
"*"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => html! {},
|
||||
};
|
||||
Ok(html! {
|
||||
div (c.props()) {
|
||||
@if !floating { (label) }
|
||||
select
|
||||
id=[select_id.as_deref()]
|
||||
class="form-select"
|
||||
name=[c.name().get()]
|
||||
multiple[*c.multiple()]
|
||||
size=[c.rows().get()]
|
||||
autocomplete=[c.autocomplete().get()]
|
||||
autofocus[*c.autofocus()]
|
||||
required[*c.required()]
|
||||
disabled[*c.disabled()]
|
||||
{
|
||||
@for entry in c.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) = c.help_text().lookup(cx) {
|
||||
div class="form-text" { (description) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
pub fn setup(c: &mut form::Textarea) {
|
||||
if c.props().has_class("form-floating") {
|
||||
c.alter_rows(None::<u16>);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(c: &form::Textarea, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let container_id = c.id();
|
||||
let textarea_id = container_id
|
||||
.as_deref()
|
||||
.map(|id| util::join!(id, "-textarea"));
|
||||
let floating = c.props().has_class("form-floating");
|
||||
// La etiqueta flotante requiere `placeholder` para animar la etiqueta; si no está definido se
|
||||
// fuerza `placeholder=""`.
|
||||
let placeholder = if floating {
|
||||
Some(c.placeholder().lookup(cx).unwrap_or_default())
|
||||
} else {
|
||||
c.placeholder().lookup(cx)
|
||||
};
|
||||
let label = match c.label().lookup(cx) {
|
||||
Some(text) => html! {
|
||||
label for=[textarea_id.as_deref()] class="form-label" {
|
||||
(text)
|
||||
@if *c.required() {
|
||||
span
|
||||
class="form-required"
|
||||
title=(L10n::l("field_required").using(cx))
|
||||
{
|
||||
"*"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
None => html! {},
|
||||
};
|
||||
Ok(html! {
|
||||
div (c.props()) {
|
||||
@if !floating { (label) }
|
||||
textarea
|
||||
id=[textarea_id.as_deref()]
|
||||
class="form-control"
|
||||
name=[c.name().get()]
|
||||
rows=[c.rows().get()]
|
||||
minlength=[c.minlength().get()]
|
||||
maxlength=[c.maxlength().get()]
|
||||
placeholder=[placeholder]
|
||||
autocomplete=[c.autocomplete().get()]
|
||||
autofocus[*c.autofocus()]
|
||||
readonly[*c.readonly()]
|
||||
required[*c.required()]
|
||||
disabled[*c.disabled()]
|
||||
{
|
||||
@if let Some(value) = c.value().get() { (value) }
|
||||
}
|
||||
@if floating { (label) }
|
||||
@if let Some(description) = c.help_text().lookup(cx) {
|
||||
div class="form-text" { (description) }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -143,14 +143,15 @@ impl Theme for Bootsier {
|
|||
cx: &mut Context,
|
||||
) -> Option<Result<Markup, ComponentError>> {
|
||||
setup_component!(component, {
|
||||
Button => |c| handlers::button::setup(c),
|
||||
form::select::Field => |c| handlers::select::setup(c),
|
||||
form::Textarea => |c| handlers::textarea::setup(c),
|
||||
Button => |c| theme::bs::button::setup(c),
|
||||
form::input::Field => |c| theme::bs::form::input::setup(c),
|
||||
form::select::Field => |c| theme::bs::form::select::setup(c),
|
||||
form::Textarea => |c| theme::bs::form::textarea::setup(c),
|
||||
});
|
||||
render_component!(component, {
|
||||
form::input::Field => |c| handlers::input::render(c, cx),
|
||||
form::select::Field => |c| handlers::select::render(c, cx),
|
||||
form::Textarea => |c| handlers::textarea::render(c, cx),
|
||||
form::input::Field => |c| theme::bs::form::input::render(c, cx),
|
||||
form::select::Field => |c| theme::bs::form::select::render(c, cx),
|
||||
form::Textarea => |c| theme::bs::form::textarea::render(c, cx),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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