//! Definiciones para crear áreas de texto en formularios. 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 /// superpone al control mientras no hay ninguna opción seleccionada y permanece flotante cuando hay /// una selección activa. /// /// ```rust,no_run /// # use pagetop::prelude::*; /// use pagetop_bootsier::theme::*; /// /// let comentario = bs::form::Textarea::new() /// .with_name("comment") /// .with_label(L10n::n("Comment")) /// .with_placeholder(L10n::n("Write here...")) /// .with_floating_label(true); /// ``` pub trait TextareaBootsier { /// Establece si la etiqueta se muestra flotante sobre el campo. /// /// Cuando está activo, la etiqueta se superpone al área de texto y asciende al enfocarlo o /// cuando tiene contenido. Requiere que el campo tenga un atributo `placeholder` definido; /// si no se especifica, se fuerza `placeholder=""` antes del renderizado. /// /// Si se usa la etiqueta flotante, se anula el valor establecido con /// [`with_rows()`](form::Textarea::with_rows) antes del renderizado. #[builder_fn] fn with_floating_label(self, floating: bool) -> Self; } impl TextareaBootsier for Textarea { #[builder_fn] fn with_floating_label(mut self, floating: bool) -> Self { self.alter_prop(PropsOp::set_extra(EXTRA_FLOATING_LABEL, floating)); self } } // **< 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::); } else { field.alter_prop(PropsOp::remove_classes("form-floating")); } } // **< Textarea RENDER >**************************************************************************** pub(crate) fn render(field: &Textarea, cx: &mut Context) -> Result { 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) } } } }) }