use std::panic::Location;
use super::text::text;
use crate::metrics::MetricsRole;
use crate::tokens;
use crate::tree::*;
#[track_caller]
pub fn form<I, E>(children: I) -> El
where
I: IntoIterator<Item = E>,
E: Into<El>,
{
column(children)
.at_loc(Location::caller())
.metrics_role(MetricsRole::Form)
.width(Size::Fill(1.0))
.height(Size::Hug)
.default_gap(tokens::SPACE_3)
.default_padding(Sides::xy(tokens::RING_WIDTH, 0.0))
}
#[track_caller]
pub fn form_section<I, E>(children: I) -> El
where
I: IntoIterator<Item = E>,
E: Into<El>,
{
column(children)
.at_loc(Location::caller())
.metrics_role(MetricsRole::Form)
.width(Size::Fill(1.0))
.height(Size::Hug)
.default_gap(tokens::SPACE_3)
}
#[track_caller]
pub fn form_item<I, E>(children: I) -> El
where
I: IntoIterator<Item = E>,
E: Into<El>,
{
column(children)
.at_loc(Location::caller())
.metrics_role(MetricsRole::FormItem)
.width(Size::Fill(1.0))
.height(Size::Hug)
.default_gap(tokens::SPACE_2)
}
#[track_caller]
pub fn form_label(label: impl Into<String>) -> El {
text(label)
.at_loc(Location::caller())
.label()
.ellipsis()
.width(Size::Fill(1.0))
}
#[track_caller]
pub fn form_control(control: impl Into<El>) -> El {
El::new(Kind::Custom("form_control"))
.at_loc(Location::caller())
.child(control)
.width(Size::Fill(1.0))
.height(Size::Hug)
}
#[track_caller]
pub fn form_description(description: impl Into<String>) -> El {
text(description)
.at_loc(Location::caller())
.muted()
.wrap_text()
.width(Size::Fill(1.0))
}
#[track_caller]
pub fn form_message(message: impl Into<String>) -> El {
text(message)
.at_loc(Location::caller())
.font_weight(FontWeight::Medium)
.destructive()
.wrap_text()
.width(Size::Fill(1.0))
}
#[track_caller]
pub fn field_row(label: impl Into<String>, control: impl Into<El>) -> El {
crate::row([text(label).label(), crate::spacer(), control.into()])
.at_loc(Location::caller())
.gap(tokens::SPACE_3)
.align(Align::Center)
.width(Size::Fill(1.0))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::widgets::switch::switch;
#[test]
fn form_item_lays_out_label_control_and_description() {
let item = form_item([
form_label("Email"),
form_control("alicia@example.com"),
form_description("Used for notifications."),
]);
assert_eq!(item.metrics_role, Some(MetricsRole::FormItem));
assert_eq!(item.axis, Axis::Column);
assert_eq!(item.children.len(), 3);
assert_eq!(item.children[0].text.as_deref(), Some("Email"));
assert_eq!(item.children[0].text_role, TextRole::Label);
assert_eq!(item.children[1].kind, Kind::Custom("form_control"));
assert_eq!(item.children[2].text_role, TextRole::Body);
assert_eq!(item.children[2].font_size, tokens::TEXT_SM.size);
assert_eq!(item.children[2].line_height, tokens::TEXT_SM.line_height);
assert_eq!(item.children[2].text_color, Some(tokens::MUTED_FOREGROUND));
}
#[test]
fn form_message_uses_error_treatment() {
let message = form_message("Email is required.");
assert_eq!(message.text_role, TextRole::Body);
assert_eq!(message.font_size, tokens::TEXT_SM.size);
assert_eq!(message.line_height, tokens::TEXT_SM.line_height);
assert_eq!(message.font_weight, FontWeight::Medium);
assert_eq!(message.text_color, Some(tokens::DESTRUCTIVE));
}
#[test]
fn field_row_lays_out_label_spacer_control() {
let r = field_row("Auto-save", switch(false).key("auto_save"));
assert_eq!(r.children.len(), 3);
assert_eq!(r.axis, Axis::Row);
assert!(r.key.is_none(), "field_row carries no key of its own");
let label = &r.children[0];
assert_eq!(label.text.as_deref(), Some("Auto-save"));
assert_eq!(label.text_role, TextRole::Label);
let spacer = &r.children[1];
assert_eq!(spacer.kind, Kind::Spacer);
let control = &r.children[2];
assert_eq!(control.key.as_deref(), Some("auto_save"));
}
#[test]
fn field_row_fills_width_and_centers_vertically() {
let r = field_row("Theme", switch(true).key("theme"));
assert!(matches!(r.width, Size::Fill(_)));
assert_eq!(r.align, Align::Center);
}
#[test]
fn field_row_accepts_dynamic_label() {
let r = field_row(format!("Volume ({}%)", 52), switch(false).key("k"));
let label = &r.children[0];
assert_eq!(label.text.as_deref(), Some("Volume (52%)"));
}
}