use topcoat::{
Result,
view::{Attributes, Child, StaticClass, View, attributes, class, component, view},
};
use super::label::label;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[allow(dead_code)]
pub enum FieldOrientation {
#[default]
Vertical,
Horizontal,
Responsive,
}
impl FieldOrientation {
fn classes(self) -> StaticClass {
match self {
Self::Vertical => class!("flex-col gap-2"),
Self::Horizontal => {
class!("flex-row items-center gap-3 [&>[data-slot=field-label]]:flex-1")
}
Self::Responsive => class!(
"flex-col gap-2 @md/field-group:flex-row @md/field-group:items-start \
@md/field-group:gap-4 @md/field-group:[&>[data-slot=field-label]]:w-1/3 \
@md/field-group:[&>[data-slot=field-label]]:shrink-0",
),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[allow(dead_code)]
pub enum FieldLegendVariant {
#[default]
Legend,
Label,
}
impl FieldLegendVariant {
fn classes(self) -> StaticClass {
match self {
Self::Legend => class!("text-base font-semibold"),
Self::Label => class!("text-sm font-medium"),
}
}
}
#[component]
pub async fn field_set(
#[default] mut attrs: Attributes,
#[default] child: Child<'_>,
) -> Result<impl View> {
Ok(view! {
<fieldset
class=(class!("flex min-w-0 flex-col gap-5", attrs.remove("class")))
(attrs)
>
(child)
</fieldset>
})
}
#[component]
pub async fn field_legend(
#[default] variant: FieldLegendVariant,
#[default] mut attrs: Attributes,
#[default] child: Child<'_>,
) -> Result<impl View> {
Ok(view! {
<legend
class=(class!("mb-3", variant.classes(), attrs.remove("class")))
(attrs)
>
(child)
</legend>
})
}
#[component]
pub async fn field_group(
#[default] mut attrs: Attributes,
#[default] child: Child<'_>,
) -> Result<impl View> {
Ok(view! {
<div
class=(class!(
"@container/field-group flex flex-col gap-5",
attrs.remove("class"),
))
(attrs)
>
(child)
</div>
})
}
#[component]
pub async fn field(
#[default] orientation: FieldOrientation,
#[default] mut attrs: Attributes,
#[default] child: Child<'_>,
) -> Result<impl View> {
Ok(view! {
<div
role="group"
data-slot="field"
class=(class!(
"group/field flex min-w-0",
orientation.classes(),
attrs.remove("class"),
))
(attrs)
>
(child)
</div>
})
}
#[component]
pub async fn field_content(
#[default] mut attrs: Attributes,
#[default] child: Child<'_>,
) -> Result<impl View> {
Ok(view! {
<div
class=(class!("flex min-w-0 flex-1 flex-col gap-1.5", attrs.remove("class")))
(attrs)
>
(child)
</div>
})
}
#[component]
pub async fn field_label(
#[default] mut attrs: Attributes,
#[default] child: Child<'_>,
) -> Result<impl View> {
Ok(view! {
label(
attrs: attributes! {
data-slot="field-label"
class=(class!(
"leading-snug group-has-[:disabled]/field:opacity-50 \
group-has-[[aria-invalid=true]]/field:text-destructive \
group-data-[invalid=true]/field:text-destructive",
attrs.remove("class"),
))
(attrs)
},
(child)
)
})
}
#[component]
pub async fn field_title(
#[default] mut attrs: Attributes,
#[default] child: Child<'_>,
) -> Result<impl View> {
Ok(view! {
<div
class=(class!("text-sm leading-snug font-medium", attrs.remove("class")))
(attrs)
>
(child)
</div>
})
}
#[component]
pub async fn field_description(
#[default] mut attrs: Attributes,
#[default] child: Child<'_>,
) -> Result<impl View> {
Ok(view! {
<p
class=(class!(
"text-sm leading-relaxed text-muted-foreground [&_a]:underline [&_a]:underline-offset-4",
attrs.remove("class"),
))
(attrs)
>
(child)
</p>
})
}
#[component]
pub async fn field_separator(
#[default] mut attrs: Attributes,
#[default] child: Child<'_>,
) -> Result<impl View> {
Ok(view! {
<div
class=(class!(
"flex items-center gap-3 text-xs text-muted-foreground has-[>span:empty]:gap-0 before:h-px before:flex-1 before:bg-border after:h-px after:flex-1 after:bg-border",
attrs.remove("class"),
))
(attrs)
>
<span class="empty:hidden">(child)</span>
</div>
})
}
#[component]
pub async fn field_error(
#[default] mut attrs: Attributes,
#[default] child: Child<'_>,
) -> Result<impl View> {
Ok(view! {
<div
role="alert"
class=(class!("text-sm text-destructive", attrs.remove("class")))
(attrs)
>
(child)
</div>
})
}