1use super::*;
4
5#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
10#[non_exhaustive]
11pub struct ColorFieldRenderState {
12 pub is_disabled: bool,
14 pub is_invalid: bool,
16 pub is_read_only: bool,
18 pub is_required: bool,
20 pub is_focused: bool,
22 pub is_focus_within: bool,
24 pub is_focus_visible: bool,
26}
27
28#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
33#[derive(IntoElement)]
34pub struct ColorField {
35 content: Option<Arc<dyn Fn(ColorFieldRenderState) -> gpui::AnyElement + 'static>>,
37 suffix: Option<gpui::AnyElement>,
41 validation_behavior: crate::form::ValidationBehavior,
43 name: Option<SharedString>,
46 default_value: Option<Option<PickerColor>>,
50 id: ElementId,
51 value: Option<PickerColor>,
53 channel: Option<ColorChannel>,
54 color_space: ColorSpace,
56 validate: Option<crate::validation::Validator<PickerColor>>,
58 validation_errors: Vec<SharedString>,
60 is_wheel_disabled: bool,
62 auto_focus: bool,
64 placeholder: Option<SharedString>,
65 font_family: Option<SharedString>,
67 state: Option<Entity<crate::input::InputState>>,
70 on_change: Option<OnColorFieldChange>,
71 on_blur: Option<Arc<dyn Fn(&mut Window, &mut App) + 'static>>,
75 label: Option<SharedString>,
76 description: Option<SharedString>,
77 variant: FieldVariant,
78 full_width: bool,
79 is_disabled: bool,
80 is_invalid: bool,
81 is_read_only: bool,
82 is_required: bool,
83 field: util::FieldBox,
85 radius: Option<Pixels>,
87 sx: Option<Box<gpui::StyleRefinement>>,
89 form_state: Rc<RefCell<crate::form::LiveFormFieldState>>,
90}
91
92impl ColorField {
93 pub fn is_read_only(mut self, v: bool) -> Self {
95 self.is_read_only = v;
96 self
97 }
98
99 pub fn is_required(mut self, v: bool) -> Self {
101 self.is_required = v;
102 self
103 }
104
105 pub fn content(
108 mut self,
109 render: impl Fn(ColorFieldRenderState) -> gpui::AnyElement + 'static,
110 ) -> Self {
111 self.content = Some(Arc::new(render));
112 self
113 }
114
115 pub fn suffix(mut self, el: impl IntoElement) -> Self {
117 self.suffix = Some(el.into_any_element());
118 self
119 }
120
121 pub fn font_family(mut self, family: impl Into<SharedString>) -> Self {
127 self.font_family = Some(family.into());
128 self
129 }
130
131 pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
140 util::refine_sx(&mut self.sx, style);
141 self
142 }
143
144 pub fn new(id: impl Into<ElementId>, value: impl Into<Option<PickerColor>>) -> Self {
146 Self {
147 content: None,
148 suffix: None,
149 validation_behavior: crate::form::ValidationBehavior::Native,
150 name: None,
151 default_value: None,
152 id: id.into(),
153 value: value.into(),
154 channel: None,
155 color_space: ColorSpace::default(),
156 validate: None,
157 validation_errors: Vec::new(),
158 is_wheel_disabled: false,
159 auto_focus: false,
160 placeholder: None,
161 font_family: None,
162 state: None,
163 on_change: None,
164 on_blur: None,
165 label: None,
166 description: None,
167 variant: FieldVariant::Primary,
168 full_width: false,
169 is_disabled: false,
170 is_invalid: false,
171 is_read_only: false,
172 is_required: false,
173 field: util::FieldBox::default(),
174 radius: None,
175 sx: None,
176 form_state: live_color_form_state(
177 crate::form::FormValue::Text(SharedString::default()),
178 ),
179 }
180 }
181
182 pub fn validation_behavior(mut self, behavior: crate::form::ValidationBehavior) -> Self {
185 self.validation_behavior = behavior;
186 self
187 }
188
189 pub fn name(mut self, name: impl Into<SharedString>) -> Self {
191 self.name = Some(name.into());
192 self
193 }
194
195 pub fn form_field(&self) -> Option<crate::form::FormField> {
219 let name = self.name.clone()?;
220 let value = self.default_value.unwrap_or(self.value);
221 let validity = crate::validation::resolve(
222 self.is_invalid,
223 &self.validation_errors,
224 self.validate
225 .as_ref()
226 .and_then(|f| value.as_ref().and_then(|value| f(value))),
227 None,
228 );
229 sync_color_form_state(
230 &self.form_state,
231 color_field_form_value(value, self.channel, self.color_space),
232 !self.is_disabled,
233 validity.is_invalid,
234 );
235 Some(
236 crate::form::FormField::live(name, self.form_state.clone())
237 .is_required(self.is_required)
238 .validation_behavior(self.validation_behavior),
239 )
240 }
241
242 pub fn default_value(mut self, value: impl Into<Option<PickerColor>>) -> Self {
249 self.default_value = Some(value.into());
250 self
251 }
252
253 pub fn color_space(mut self, space: ColorSpace) -> Self {
255 self.color_space = space;
256 self
257 }
258
259 pub fn validate(mut self, f: impl Fn(&PickerColor) -> Option<SharedString> + 'static) -> Self {
262 self.validate = Some(Arc::new(f));
263 self
264 }
265
266 pub fn validation_errors(
269 mut self,
270 errors: impl IntoIterator<Item = impl Into<SharedString>>,
271 ) -> Self {
272 self.validation_errors = errors.into_iter().map(Into::into).collect();
273 self
274 }
275
276 pub fn auto_focus(mut self, v: bool) -> Self {
279 self.auto_focus = v;
280 self
281 }
282
283 pub fn is_wheel_disabled(mut self, v: bool) -> Self {
288 self.is_wheel_disabled = v;
289 self
290 }
291
292 pub fn placeholder(mut self, text: impl Into<SharedString>) -> Self {
294 self.placeholder = Some(text.into());
295 self
296 }
297
298 pub fn state(mut self, state: Entity<crate::input::InputState>) -> Self {
300 self.state = Some(state);
301 self
302 }
303
304 pub fn on_change(
308 mut self,
309 f: impl Fn(&Option<PickerColor>, &mut Window, &mut App) + 'static,
310 ) -> Self {
311 self.on_change = Some(Arc::new(f));
312 self
313 }
314
315 pub fn on_blur(mut self, f: impl Fn(&mut Window, &mut App) + 'static) -> Self {
323 self.on_blur = Some(Arc::new(f));
324 self
325 }
326
327 pub fn channel(mut self, channel: ColorChannel) -> Self {
329 self.channel = Some(channel);
330 self
331 }
332
333 pub fn label(mut self, text: impl Into<SharedString>) -> Self {
335 self.label = Some(text.into());
336 self
337 }
338
339 pub fn description(mut self, text: impl Into<SharedString>) -> Self {
341 self.description = Some(text.into());
342 self
343 }
344
345 pub fn variant(mut self, variant: FieldVariant) -> Self {
347 self.variant = variant;
348 self
349 }
350
351 pub fn full_width(mut self, v: bool) -> Self {
353 self.full_width = v;
354 self
355 }
356
357 pub fn is_disabled(mut self, v: bool) -> Self {
359 self.is_disabled = v;
360 self
361 }
362
363 pub fn is_invalid(mut self, v: bool) -> Self {
365 self.is_invalid = v;
366 self
367 }
368
369 pub fn height(mut self, h: impl Into<Pixels>) -> Self {
372 self.field.height = Some(h.into());
373 self
374 }
375
376 pub fn padding_x(mut self, p: impl Into<Pixels>) -> Self {
378 self.field.padding_x = Some(p.into());
379 self
380 }
381
382 pub fn is_bare(mut self, v: bool) -> Self {
385 self.field.is_bare = v;
386 self.field.is_bare_is_set = true;
387 self
388 }
389
390 pub fn focus_ring(mut self, v: bool) -> Self {
393 self.field.focus_ring = Some(v);
394 self
395 }
396
397 pub fn radius(mut self, radius: impl Into<Pixels>) -> Self {
406 self.radius = Some(radius.into());
407 self
408 }
409}
410
411impl ColorField {
412 fn display_text(&self) -> String {
415 color_field_display_text(self.value, self.channel, self.color_space)
416 }
417}
418
419pub(super) fn parse_color_field(
420 value: PickerColor,
421 channel: Option<ColorChannel>,
422 color_space: ColorSpace,
423 text: &str,
424) -> Option<PickerColor> {
425 match channel {
426 None => PickerColor::from_hex(text),
427 Some(channel) => {
428 let text = text.trim();
429 let mut number: f32 = text.trim_end_matches('%').trim().parse().ok()?;
430 if is_normalized_channel(channel) {
431 number /= 100.0;
432 }
433 let (min, max) = channel.range();
434 if number < min || number > max {
435 return None;
436 }
437 Some(value.with_channel_in(channel, color_space, number))
438 }
439 }
440}
441
442pub(super) fn step_color_channel(
443 value: PickerColor,
444 channel: ColorChannel,
445 color_space: ColorSpace,
446 direction: f32,
447) -> PickerColor {
448 let (min, max) = channel.range();
449 let step = color_channel_step(channel);
450 let current = value.channel_in(channel, color_space);
451 let next = snap_color_channel(channel, (current + direction * step).clamp(min, max));
452 value.with_channel_in(channel, color_space, next)
453}
454
455pub(super) fn color_channel_step(channel: ColorChannel) -> f32 {
456 let (min, max) = channel.range();
457 if max - min > 2.0 {
458 1.0
459 } else {
460 0.01
461 }
462}
463
464pub(super) fn snap_color_channel(channel: ColorChannel, value: f32) -> f32 {
465 let (min, max) = channel.range();
466 let step = color_channel_step(channel);
467 (((value - min) / step).round() * step + min).clamp(min, max)
468}
469
470pub(super) fn is_normalized_channel(channel: ColorChannel) -> bool {
471 matches!(
472 channel,
473 ColorChannel::Saturation
474 | ColorChannel::Brightness
475 | ColorChannel::Lightness
476 | ColorChannel::Alpha
477 )
478}
479
480pub(super) fn format_color_channel_value(
481 value: PickerColor,
482 channel: ColorChannel,
483 color_space: ColorSpace,
484) -> String {
485 let value = value.channel_in(channel, color_space);
486 if is_normalized_channel(channel) {
487 format!("{}%", (value * 100.0).round())
488 } else {
489 format!("{}", value.round())
490 }
491}
492
493#[allow(clippy::too_many_arguments)] pub(super) fn report_color_field_change(
495 next: PickerColor,
496 channel: ColorChannel,
497 color_space: ColorSpace,
498 state: &Entity<crate::input::InputState>,
499 own: &Option<Entity<Option<PickerColor>>>,
500 on_change: &Option<OnColorFieldChange>,
501 window: &mut Window,
502 cx: &mut App,
503) {
504 let text = format_color_channel_value(next, channel, color_space);
505 state.update(cx, |state, cx| {
506 state.set_value(text);
507 cx.notify();
508 });
509 if let Some(held) = own {
510 held.update(cx, |value, cx| {
511 *value = Some(next);
512 cx.notify();
513 });
514 }
515 if let Some(callback) = on_change {
516 callback(&Some(next), window, cx);
517 }
518}
519
520impl RenderOnce for ColorField {
521 fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
522 let (resolved, own) = util::controlled(
525 window,
526 cx,
527 element_id::scoped(&self.id, "field-value"),
528 self.default_value.is_none().then_some(self.value),
529 self.default_value.unwrap_or(self.value),
530 );
531 self.value = resolved;
532 let validity = crate::validation::resolve(
534 self.is_invalid,
535 &self.validation_errors,
536 self.validate
537 .as_ref()
538 .and_then(|f| self.value.as_ref().and_then(|value| f(value))),
539 None,
540 );
541 if let Some(render) = self.content.clone() {
542 let focused = self
544 .state
545 .as_ref()
546 .is_some_and(|s| s.read(cx).focus_handle.is_focused(window));
547 let within = self
548 .state
549 .as_ref()
550 .is_some_and(|s| s.read(cx).focus_handle.contains_focused(window, cx));
551 return render(ColorFieldRenderState {
552 is_disabled: self.is_disabled,
553 is_invalid: validity.is_invalid,
554 is_read_only: self.is_read_only,
555 is_required: self.is_required,
556 is_focused: focused,
557 is_focus_within: within,
558 is_focus_visible: focused && util::focus_visible(cx),
559 })
560 .into_any_element();
561 }
562 let form_default = window.use_keyed_state(
563 element_id::scoped(&self.id, "field-form-default"),
564 cx,
565 |_, _| None::<Option<PickerColor>>,
566 );
567 if form_default.read(cx).is_none() {
568 let initial = self.value;
569 form_default.update(cx, |slot, cx| {
570 *slot = Some(initial);
571 cx.notify();
572 });
573 }
574 let restore_default = form_default.read(cx).unwrap_or(self.value);
575 sync_color_form_state(
578 &self.form_state,
579 color_field_form_value(self.value, self.channel, self.color_space),
580 !self.is_disabled,
581 validity.is_invalid,
582 );
583 let restore_own = own.clone();
584 let restore_on_change = self.on_change.clone();
585 let restore_form_state = Rc::downgrade(&self.form_state);
588 let restore_input = self.state.clone();
589 let restore_channel = self.channel;
590 let restore_space = self.color_space;
591 let restore_is_disabled = self.is_disabled;
592 let restore: Arc<dyn Fn(&mut Window, &mut App)> = util::shared(move |window, cx| {
593 if let Some(own) = &restore_own {
594 own.update(cx, |current, cx| {
595 *current = restore_default;
596 cx.notify();
597 });
598 }
599 if let Some(state) = &restore_input {
600 let text =
601 color_field_display_text(restore_default, restore_channel, restore_space);
602 state.update(cx, |state, cx| {
603 state.set_value(text);
604 cx.notify();
605 });
606 }
607 if let Some(callback) = &restore_on_change {
608 callback(&restore_default, window, cx);
609 }
610 if let Some(state) = restore_form_state.upgrade() {
611 sync_color_form_state(
612 &state,
613 color_field_form_value(restore_default, restore_channel, restore_space),
614 !restore_is_disabled,
615 false,
616 );
617 }
618 });
619 self.form_state.borrow_mut().restore = Some(restore);
620 if let Some(state) = &self.state {
621 self.form_state.borrow_mut().focus = Some(state.read(cx).focus_handle.clone());
622 }
623 let colors = cx.colors().clone();
627 let layout = cx.layout().clone();
628 let text = self.display_text();
629
630 if let Some(state) = self.state.clone() {
633 let blur_focus = state.read(cx).focus_handle.clone();
650 let blur_seen = window.use_keyed_state(
651 element_id::scoped(&self.id, "field-blur-seen"),
652 cx,
653 |_, _| false,
654 );
655 let blur_subscription = window.use_keyed_state(
656 element_id::scoped(&self.id, "field-blur-subscription"),
657 cx,
658 |_, _| None::<gpui::Subscription>,
659 );
660 let revert: Rc<dyn Fn(&mut Window, &mut App)> = {
661 let hook = self.on_blur.clone();
662 let seen = blur_seen.clone();
663 let input = state.downgrade();
664 let committed = self.value;
665 let channel = self.channel;
666 let space = self.color_space;
667 Rc::new(move |window, cx| {
668 seen.update(cx, |seen, _| *seen = false);
669 if let Some(hook) = &hook {
672 hook(window, cx);
673 }
674 let Some(input) = input.upgrade() else {
675 return;
676 };
677 let text = input.read(cx).value().to_owned();
678 if text.is_empty()
681 || parse_color_field(committed.unwrap_or_default(), channel, space, &text)
682 .is_some()
683 {
684 return;
685 }
686 let restored = color_field_display_text(committed, channel, space);
687 input.update(cx, |state, cx| {
688 state.set_value(restored);
689 cx.notify();
690 });
691 })
692 };
693 if !self.is_disabled {
694 let disarmer = blur_subscription.downgrade();
704 let listener = window.on_focus_out(&blur_focus, cx, {
705 let revert = Rc::clone(&revert);
706 move |_, window, cx| {
707 if let Some(disarmer) = disarmer.upgrade() {
708 disarmer.update(cx, |slot, _| *slot = None);
709 }
710 revert(window, cx);
711 }
712 });
713 blur_subscription.update(cx, |slot, _| *slot = Some(listener));
714 if blur_focus.is_focused(window) {
715 blur_seen.update(cx, |seen, _| *seen = true);
716 } else if *blur_seen.read(cx)
717 && window.focused(cx).is_some_and(|focused| focused.tab_stop)
718 {
719 blur_seen.update(cx, |seen, _| *seen = false);
725 window.defer(cx, {
726 let revert = Rc::clone(&revert);
727 move |window, cx| revert(window, cx)
728 });
729 }
730 } else if blur_subscription.read(cx).is_some() {
731 blur_subscription.update(cx, |slot, _| *slot = None);
732 }
733 let mut input = Input::new(state.clone())
734 .variant(self.variant)
735 .is_disabled(self.is_disabled)
736 .is_read_only(self.is_read_only)
737 .is_required(self.is_required)
738 .is_invalid(validity.is_invalid)
739 .validation_errors(self.validation_errors.clone())
740 .auto_focus(self.auto_focus);
741 if let Some(value) = self.value {
742 input = input.start_content(ColorSwatch::new(value).size(SizeXl::Xs));
743 }
744 input = input
745 .with_field_box(self.field)
746 .with_sx_refinement(self.sx.take());
747 if let Some(family) = self.font_family.clone() {
748 input = input.font_family(family);
749 }
750 input = match self.radius {
754 Some(radius) => input.radius(radius),
755 None => input,
756 };
757 if let Some(message) = validity.first() {
758 input = input.error_message(message);
759 }
760 if let Some(ph) = self.placeholder.clone() {
761 input = input.placeholder(ph);
762 } else {
763 input = input.placeholder(text);
764 }
765 if let Some(label) = self.label.clone() {
766 input = input.label(label);
767 }
768 if let Some(description) = self.description.clone() {
769 input = input.description(description);
770 }
771 if let Some(suffix) = self.suffix.take() {
772 input = input.end_content(
773 div()
774 .flex()
775 .items_center()
776 .flex_shrink_0()
777 .text_color(colors.field.placeholder)
778 .child(suffix),
779 );
780 }
781 if self.full_width {
782 input = input.full_width();
783 }
784 if self.on_change.is_some() || own.is_some() {
785 let cb = self.on_change.clone();
786 let own = own.clone();
787 let parse_value = self.value.unwrap_or_default();
788 let parse_channel = self.channel;
789 let parse_space = self.color_space;
790 input = input.on_change(move |text, window, cx| {
791 let next = parse_color_field(parse_value, parse_channel, parse_space, text);
792 if let (Some(held), Some(c)) = (&own, next) {
795 held.update(cx, |v, cx| {
796 *v = Some(c);
797 cx.notify();
798 });
799 }
800 if let Some(cb) = &cb {
801 cb(&next, window, cx);
802 }
803 });
804 }
805 let rendered = input.render(window, cx).into_any_element();
806 let Some(channel) = self.channel else {
807 return rendered;
808 };
809 if self.is_disabled || self.is_read_only {
810 return rendered;
811 }
812
813 let mut field = div()
814 .id(element_id::scoped(&self.id, "channel-events"))
815 .child(rendered);
816 if self.on_change.is_some() || own.is_some() {
817 let key_value = self.value.unwrap_or_default();
818 let key_space = self.color_space;
819 let key_state = state.clone();
820 let key_own = own.clone();
821 let key_change = self.on_change.clone();
822 field = field.on_key_down(move |event, window, cx| {
823 let direction = match event.keystroke.key.as_str() {
824 "up" => 1.0,
825 "down" => -1.0,
826 _ => return,
827 };
828 let next = step_color_channel(key_value, channel, key_space, direction);
829 report_color_field_change(
830 next,
831 channel,
832 key_space,
833 &key_state,
834 &key_own,
835 &key_change,
836 window,
837 cx,
838 );
839 cx.stop_propagation();
840 });
841
842 if !self.is_wheel_disabled {
843 let wheel_value = self.value.unwrap_or_default();
844 let wheel_space = self.color_space;
845 let wheel_state = state;
846 let wheel_own = own;
847 let wheel_change = self.on_change;
848 field = field.on_scroll_wheel(move |event, window, cx| {
849 if !wheel_state
850 .read(cx)
851 .focus_handle
852 .contains_focused(window, cx)
853 {
854 return;
855 }
856 let (dx, dy) = match event.delta {
857 gpui::ScrollDelta::Pixels(point) => {
858 (f32::from(point.x), f32::from(point.y))
859 }
860 gpui::ScrollDelta::Lines(point) => (point.x, point.y),
861 };
862 if dy == 0.0 || dy.abs() <= dx.abs() {
863 return;
864 }
865 let next =
866 step_color_channel(wheel_value, channel, wheel_space, dy.signum());
867 report_color_field_change(
868 next,
869 channel,
870 wheel_space,
871 &wheel_state,
872 &wheel_own,
873 &wheel_change,
874 window,
875 cx,
876 );
877 cx.stop_propagation();
878 });
879 }
880 }
881 return field.into_any_element();
882 }
883
884 let field_box = self.field;
885 let radius = self.radius.unwrap_or_else(|| util::field_radius(cx));
887 let mut field = div()
888 .id(self.id.clone())
889 .when_some(self.font_family.clone(), |field, family| {
890 field.font_family(family)
891 })
892 .flex()
893 .flex_row()
894 .items_center()
895 .gap(px(8.))
896 .px(field_box.resolved_padding_x())
897 .h(field_box.resolved_height())
898 .rounded(radius)
899 .text_size(util::FIELD_TEXT)
900 .line_height(px(20.))
901 .text_color(colors.field.foreground);
902 if let Some(value) = self.value {
906 field = field.child(ColorSwatch::new(value).size(SizeXl::Xs));
907 }
908 field = field.child(div().flex_1().child(text));
909 field = field.children(self.suffix.map(|el| {
913 div()
914 .flex()
915 .items_center()
916 .flex_shrink_0()
917 .text_color(colors.field.placeholder)
918 .child(el)
919 }));
920
921 if !field_box.is_bare {
922 let focused = self
923 .state
924 .as_ref()
925 .is_some_and(|s| s.read(cx).focus_handle.is_focused(window));
926 field = util::apply_field_chrome_overlay(
930 field,
931 self.variant,
932 self.is_invalid,
933 focused,
934 field_box.focus_ring.unwrap_or(true),
935 Some(radius),
936 cx,
937 );
938
939 if validity.is_invalid {
943 field = field.bg(match self.variant {
944 FieldVariant::Primary => colors.field.focus(),
945 FieldVariant::Secondary => colors.default.color,
946 });
947 }
948
949 if !self.is_disabled && !self.is_invalid && !focused {
950 let idle_bg = match self.variant {
951 FieldVariant::Primary => colors.field.background,
952 FieldVariant::Secondary => colors.default.color,
953 };
954 let hover_bg = match self.variant {
955 FieldVariant::Primary => colors.field.hover(),
956 FieldVariant::Secondary => colors.default.hover(),
959 };
960 let hover_border = colors.field.border_hover();
961 field = crate::anim::hover_fade_with_duration_and_easing(
966 field,
967 element_id::scoped(&self.id, "field-hover-fade"),
968 (idle_bg, hover_bg),
969 None,
970 Some(hover_border),
971 |fill| fill.rounded(radius),
972 Some(150),
973 crate::anim::HoverFadeEasing::EaseSmooth,
974 window,
975 cx,
976 );
977 }
978 }
979
980 if let (Some(channel), false, Some(cb)) = (
984 self.channel,
985 self.is_wheel_disabled || self.is_disabled || self.is_read_only,
986 self.on_change.clone(),
987 ) {
988 let value = self.value.unwrap_or_default();
989 let space = self.color_space;
990 field = field.on_scroll_wheel(move |ev: &gpui::ScrollWheelEvent, window, cx| {
991 let dy = match ev.delta {
992 gpui::ScrollDelta::Pixels(p) => f32::from(p.y),
993 gpui::ScrollDelta::Lines(p) => p.y,
994 };
995 if dy == 0.0 {
996 return;
997 }
998 let (min, max) = channel.range();
999 let step = ((max - min) / 100.0).max(1.0);
1002 let next = (value.channel_in(channel, space) + step * dy.signum()).clamp(min, max);
1003 cb(
1004 &Some(value.with_channel_in(channel, space, next)),
1005 window,
1006 cx,
1007 );
1008 });
1009 }
1010
1011 if validity.is_invalid && !field_box.is_bare {
1012 field = field.border_1().border_color(colors.danger.color);
1013 }
1014 if self.is_disabled || self.is_read_only {
1017 field = field.opacity(layout.disabled_opacity);
1018 }
1019 if self.full_width {
1020 field = field.w_full();
1021 } else {
1022 field = field.w(px(200.));
1023 }
1024 field = field.a11y_named(
1027 a11y::Role::TextInput,
1028 &a11y::Name::field(self.name.as_ref(), None, &validity),
1029 );
1030
1031 let mut root = div()
1033 .flex()
1034 .flex_col()
1035 .gap(px(4.))
1036 .when(self.full_width, |root| root.w_full());
1037 if let Some(label) = self.label {
1038 root = root.child(
1039 crate::field::Label::new(label)
1040 .is_required(self.is_required)
1041 .is_disabled(self.is_disabled)
1042 .is_invalid(self.is_invalid),
1043 );
1044 }
1045 root = root.child(field);
1046 if let Some(description) = self.description {
1047 root = root.child(crate::field::Description::new(description));
1048 }
1049 util::apply_sx(root, &self.sx).into_any_element()
1050 }
1051}
1052
1053crate::util::impl_component_styled!(ColorField);