1use gpui::{AnyElement, App, Component, Pixels, SharedString, Window, div, prelude::*, px};
2use liora_core::Config;
3
4pub struct Form {
5 _label_width: Option<Pixels>,
6 inline: bool,
7 children: Vec<AnyElement>,
8}
9
10impl Form {
11 pub fn new() -> Self {
12 Self {
13 _label_width: None,
14 inline: false,
15 children: Vec::new(),
16 }
17 }
18
19 pub fn label_width(mut self, width: impl Into<Pixels>) -> Self {
20 self._label_width = Some(width.into());
21 self
22 }
23 pub fn inline(mut self, inline: bool) -> Self {
24 self.inline = inline;
25 self
26 }
27 pub fn child(mut self, child: impl IntoElement) -> Self {
28 self.children.push(child.into_any_element());
29 self
30 }
31}
32
33impl IntoElement for Form {
34 type Element = Component<Self>;
35 fn into_element(self) -> Self::Element {
36 Component::new(self)
37 }
38}
39
40impl RenderOnce for Form {
41 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
42 div()
43 .flex()
44 .when(self.inline, |s| s.flex_row().gap_4().flex_wrap())
45 .when(!self.inline, |s| s.flex_col().gap_4())
46 .children(self.children)
47 }
48}
49
50pub struct FormItem {
51 label: Option<SharedString>,
52 label_width: Option<Pixels>,
53 required: bool,
54 error: Option<SharedString>,
55 content: Option<AnyElement>,
56}
57
58impl FormItem {
59 pub fn new() -> Self {
60 Self {
61 label: None,
62 label_width: None,
63 required: false,
64 error: None,
65 content: None,
66 }
67 }
68
69 pub fn label(mut self, label: impl Into<SharedString>) -> Self {
70 self.label = Some(label.into());
71 self
72 }
73 pub fn label_width(mut self, width: impl Into<Pixels>) -> Self {
74 self.label_width = Some(width.into());
75 self
76 }
77 pub fn required(mut self, r: bool) -> Self {
78 self.required = r;
79 self
80 }
81 pub fn error(mut self, e: impl Into<SharedString>) -> Self {
82 self.error = Some(e.into());
83 self
84 }
85
86 pub fn child(mut self, child: impl IntoElement) -> Self {
87 self.content = Some(child.into_any_element());
88 self
89 }
90}
91
92impl IntoElement for FormItem {
93 type Element = Component<Self>;
94 fn into_element(self) -> Self::Element {
95 Component::new(self)
96 }
97}
98
99impl RenderOnce for FormItem {
100 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
101 let theme = &cx.global::<Config>().theme;
102
103 div()
104 .flex()
105 .flex_col()
106 .gap_1()
107 .child(div().flex().flex_row().items_center().gap_1().when_some(
108 self.label,
109 |this, label| {
110 this.child(
111 div()
112 .flex()
113 .flex_row()
114 .items_center()
115 .gap_1()
116 .when_some(self.label_width, |s, w| s.w(w))
117 .child(
118 div()
119 .text_size(px(theme.font_size.md))
120 .text_color(theme.neutral.text_1)
121 .child(label),
122 )
123 .when(self.required, |this| {
124 this.child(div().text_color(theme.danger.base).child("*"))
125 }),
126 )
127 },
128 ))
129 .when_some(self.content, |this, content| this.child(content))
130 .when_some(self.error, |this, error| {
131 this.child(
132 div()
133 .text_size(px(theme.font_size.sm))
134 .text_color(theme.danger.base)
135 .child(error),
136 )
137 })
138 }
139}