herogpui_components/
textarea.rs1use crate::input::{Input, InputState};
9use gpui::{prelude::*, px, App, Entity, IntoElement, RenderOnce, SharedString, Styled, Window};
10
11#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
13#[derive(IntoElement)]
14pub struct TextArea {
15 inner: Input,
16 min_w: Option<gpui::Pixels>,
18 sx: Option<Box<gpui::StyleRefinement>>,
20}
21
22fn rows_height(rows: u32) -> gpui::Pixels {
25 px((rows.max(1) as f32 * 20.0 + 16.0).max(38.0))
26}
27
28impl TextArea {
29 pub fn value(mut self, value: impl Into<SharedString>) -> Self {
33 self.inner = self.inner.value(value);
34 self
35 }
36
37 pub fn max_length(mut self, n: usize) -> Self {
39 self.inner = self.inner.max_length(n);
40 self
41 }
42
43 pub fn min_length(mut self, n: usize) -> Self {
45 self.inner = self.inner.min_length(n);
46 self
47 }
48
49 pub fn cols(mut self, cols: u32) -> Self {
55 self.min_w = Some(px(cols.max(1) as f32 * 8.0));
56 self
57 }
58
59 pub fn rows(mut self, rows: u32) -> Self {
62 self.inner = self.inner.min_h(rows_height(rows));
63 self
64 }
65
66 pub fn name(mut self, name: impl Into<SharedString>) -> Self {
68 self.inner = self.inner.name(name);
69 self
70 }
71
72 pub fn default_value(mut self, text: impl Into<SharedString>) -> Self {
74 self.inner = self.inner.default_value(text);
75 self
76 }
77
78 pub fn validation_behavior(mut self, behavior: crate::form::ValidationBehavior) -> Self {
80 self.inner = self.inner.validation_behavior(behavior);
81 self
82 }
83
84 pub fn variant(mut self, variant: herogpui_core::FieldVariant) -> Self {
86 self.inner = self.inner.variant(variant);
87 self
88 }
89
90 pub fn full_width(mut self) -> Self {
92 self.inner = self.inner.full_width();
93 self
94 }
95
96 pub fn padding_x(mut self, p: impl Into<gpui::Pixels>) -> Self {
101 self.inner = self.inner.padding_x(p);
102 self
103 }
104
105 pub fn is_bare(mut self, v: bool) -> Self {
107 self.inner = self.inner.is_bare(v);
108 self
109 }
110
111 pub fn focus_ring(mut self, v: bool) -> Self {
114 self.inner = self.inner.focus_ring(v);
115 self
116 }
117
118 pub fn font_family(mut self, family: impl Into<SharedString>) -> Self {
121 self.inner = self.inner.font_family(family);
122 self
123 }
124
125 pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
131 crate::util::refine_sx(&mut self.sx, style);
132 self
133 }
134
135 pub fn is_disabled(mut self, v: bool) -> Self {
137 self.inner = self.inner.is_disabled(v);
138 self
139 }
140
141 pub fn is_read_only(mut self, v: bool) -> Self {
143 self.inner = self.inner.is_read_only(v);
144 self
145 }
146
147 pub fn is_required(mut self, v: bool) -> Self {
149 self.inner = self.inner.is_required(v);
150 self
151 }
152
153 pub fn is_invalid(mut self, v: bool) -> Self {
155 self.inner = self.inner.is_invalid(v);
156 self
157 }
158
159 pub fn validate(mut self, f: impl Fn(&str) -> Option<SharedString> + 'static) -> Self {
161 self.inner = self.inner.validate(f);
162 self
163 }
164
165 pub fn validation_errors(
167 mut self,
168 errors: impl IntoIterator<Item = impl Into<SharedString>>,
169 ) -> Self {
170 self.inner = self.inner.validation_errors(errors);
171 self
172 }
173
174 pub fn error_message(mut self, text: impl Into<SharedString>) -> Self {
176 self.inner = self.inner.error_message(text);
177 self
178 }
179
180 pub fn on_change(mut self, handler: impl Fn(&str, &mut Window, &mut App) + 'static) -> Self {
182 self.inner = self.inner.on_change(handler);
183 self
184 }
185
186 pub fn new(state: impl std::borrow::Borrow<Entity<InputState>>) -> Self {
189 Self {
190 inner: Input::new(state).multiline(true).min_h(rows_height(3)),
192 min_w: None,
193 sx: None,
194 }
195 }
196
197 pub fn label(mut self, l: impl Into<SharedString>) -> Self {
199 self.inner = self.inner.label(l);
200 self
201 }
202
203 pub fn placeholder(mut self, p: impl Into<SharedString>) -> Self {
205 self.inner = self.inner.placeholder(p);
206 self
207 }
208
209 pub fn description(mut self, d: impl Into<SharedString>) -> Self {
211 self.inner = self.inner.description(d);
212 self
213 }
214}
215
216impl TextArea {
217 pub(crate) fn into_group_input(self) -> Input {
223 self.inner
224 }
225}
226
227impl RenderOnce for TextArea {
228 fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
229 let el = gpui::div()
235 .flex()
236 .flex_col()
237 .items_start()
238 .when_some(self.min_w, |e, w| e.min_w(w))
239 .child(self.inner);
240 crate::util::apply_sx(el, &self.sx)
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::rows_height;
247 use gpui::px;
248
249 #[test]
250 fn rows_keep_the_pinned_textarea_minimum() {
251 assert_eq!(rows_height(0), px(38.));
252 assert_eq!(rows_height(1), px(38.));
253 assert_eq!(rows_height(3), px(76.));
254 }
255}
256
257crate::util::impl_component_styled!(TextArea);