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