tablo_ui/components/primitives/
field.rs1use topcoat::{
3 Result,
4 view::{Attributes, Child, StaticClass, View, attributes, class, component, view},
5};
6
7use super::label::label;
8
9#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
11#[allow(dead_code)]
12pub enum FieldOrientation {
13 #[default]
15 Vertical,
16 Horizontal,
18 Responsive,
20}
21
22impl FieldOrientation {
23 fn classes(self) -> StaticClass {
24 match self {
25 Self::Vertical => class!("flex-col gap-2"),
26 Self::Horizontal => {
27 class!("flex-row items-center gap-3 [&>[data-slot=field-label]]:flex-1")
28 }
29 Self::Responsive => class!(
30 "flex-col gap-2 @md/field-group:flex-row @md/field-group:items-start \
31 @md/field-group:gap-4 @md/field-group:[&>[data-slot=field-label]]:w-1/3 \
32 @md/field-group:[&>[data-slot=field-label]]:shrink-0",
33 ),
34 }
35 }
36}
37
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
40#[allow(dead_code)]
41pub enum FieldLegendVariant {
42 #[default]
44 Legend,
45 Label,
47}
48
49impl FieldLegendVariant {
50 fn classes(self) -> StaticClass {
51 match self {
52 Self::Legend => class!("text-base font-semibold"),
53 Self::Label => class!("text-sm font-medium"),
54 }
55 }
56}
57
58#[component]
64pub async fn field_set(
65 #[default] mut attrs: Attributes,
66 #[default] child: Child<'_>,
67) -> Result<impl View> {
68 Ok(view! {
69 <fieldset
70 class=(class!("flex min-w-0 flex-col gap-5", attrs.remove("class")))
71 (attrs)
72 >
73 (child)
74 </fieldset>
75 })
76}
77
78#[component]
80pub async fn field_legend(
81 #[default] variant: FieldLegendVariant,
82 #[default] mut attrs: Attributes,
83 #[default] child: Child<'_>,
84) -> Result<impl View> {
85 Ok(view! {
86 <legend
87 class=(class!("mb-3", variant.classes(), attrs.remove("class")))
88 (attrs)
89 >
90 (child)
91 </legend>
92 })
93}
94
95#[component]
97pub async fn field_group(
98 #[default] mut attrs: Attributes,
99 #[default] child: Child<'_>,
100) -> Result<impl View> {
101 Ok(view! {
102 <div
103 class=(class!(
104 "@container/field-group flex flex-col gap-5",
105 attrs.remove("class"),
106 ))
107 (attrs)
108 >
109 (child)
110 </div>
111 })
112}
113
114#[component]
124pub async fn field(
125 #[default] orientation: FieldOrientation,
126 #[default] mut attrs: Attributes,
127 #[default] child: Child<'_>,
128) -> Result<impl View> {
129 Ok(view! {
130 <div
131 role="group"
132 data-slot="field"
133 class=(class!(
134 "group/field flex min-w-0",
135 orientation.classes(),
136 attrs.remove("class"),
137 ))
138 (attrs)
139 >
140 (child)
141 </div>
142 })
143}
144
145#[component]
147pub async fn field_content(
148 #[default] mut attrs: Attributes,
149 #[default] child: Child<'_>,
150) -> Result<impl View> {
151 Ok(view! {
152 <div
153 class=(class!("flex min-w-0 flex-1 flex-col gap-1.5", attrs.remove("class")))
154 (attrs)
155 >
156 (child)
157 </div>
158 })
159}
160
161#[component]
165pub async fn field_label(
166 #[default] mut attrs: Attributes,
167 #[default] child: Child<'_>,
168) -> Result<impl View> {
169 Ok(view! {
170 label(
171 attrs: attributes! {
172 data-slot="field-label"
173 class=(class!(
174 "leading-snug group-has-[:disabled]/field:opacity-50 \
175 group-has-[[aria-invalid=true]]/field:text-destructive \
176 group-data-[invalid=true]/field:text-destructive",
177 attrs.remove("class"),
178 ))
179 (attrs)
180 },
181 (child)
182 )
183 })
184}
185
186#[component]
188pub async fn field_title(
189 #[default] mut attrs: Attributes,
190 #[default] child: Child<'_>,
191) -> Result<impl View> {
192 Ok(view! {
193 <div
194 class=(class!("text-sm leading-snug font-medium", attrs.remove("class")))
195 (attrs)
196 >
197 (child)
198 </div>
199 })
200}
201
202#[component]
204pub async fn field_description(
205 #[default] mut attrs: Attributes,
206 #[default] child: Child<'_>,
207) -> Result<impl View> {
208 Ok(view! {
209 <p
210 class=(class!(
211 "text-sm leading-relaxed text-muted-foreground [&_a]:underline [&_a]:underline-offset-4",
212 attrs.remove("class"),
213 ))
214 (attrs)
215 >
216 (child)
217 </p>
218 })
219}
220
221#[component]
223pub async fn field_separator(
224 #[default] mut attrs: Attributes,
225 #[default] child: Child<'_>,
226) -> Result<impl View> {
227 Ok(view! {
228 <div
229 class=(class!(
230 "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",
231 attrs.remove("class"),
232 ))
233 (attrs)
234 >
235 <span class="empty:hidden">(child)</span>
236 </div>
237 })
238}
239
240#[component]
246pub async fn field_error(
247 #[default] mut attrs: Attributes,
248 #[default] child: Child<'_>,
249) -> Result<impl View> {
250 Ok(view! {
251 <div
252 role="alert"
253 class=(class!("text-sm text-destructive", attrs.remove("class")))
254 (attrs)
255 >
256 (child)
257 </div>
258 })
259}