Skip to main content

gpui_cn/
label.rs

1use gpui_kit::{
2    App, ElementId, Entity, FontWeight, InteractiveElement as _, IntoElement, ParentElement as _,
3    RenderOnce, SharedString, StatefulInteractiveElement as _, StyleRefinement, Styled,
4    TestSupportExt as _, Window,
5    base::{
6        Disableable, StyledExt as _,
7        input::{InputBaseState, InputModeKind},
8    },
9    div,
10    prelude::FluentBuilder as _,
11};
12
13use crate::ActiveTheme as _;
14
15type FocusField = Box<dyn Fn(&mut Window, &mut App)>;
16
17/// A shadcn-style form label: shadcn's `text-sm font-medium leading-none`
18/// moved to the theme's 13px control text, in the foreground color, with
19/// no margin of its own.
20///
21/// [`for_field`](Self::for_field) ties the label to a text field, so a
22/// click on the label moves focus into the field, as a `for` attribute
23/// does. A disabled label paints its text at the disabled strength,
24/// shadcn's `peer-disabled:opacity-50`, and ignores clicks.
25///
26/// ```
27/// use gpui_cn::Label;
28///
29/// let _ = Label::new("email-label", "Email");
30/// ```
31#[derive(IntoElement)]
32#[non_exhaustive]
33pub struct Label {
34    id: ElementId,
35    text: SharedString,
36    style: StyleRefinement,
37    disabled: bool,
38    focus_field: Option<FocusField>,
39}
40
41impl Label {
42    /// A label with a stable id and its text.
43    pub fn new(id: impl Into<ElementId>, text: impl Into<SharedString>) -> Self {
44        Self {
45            id: id.into(),
46            text: text.into(),
47            style: StyleRefinement::default(),
48            disabled: false,
49            focus_field: None,
50        }
51    }
52
53    /// The field a click on the label focuses.
54    pub fn for_field<M: InputModeKind>(mut self, state: &Entity<InputBaseState<M>>) -> Self {
55        let state = state.clone();
56        self.focus_field = Some(Box::new(move |window, cx| {
57            state.update(cx, |state, cx| state.focus(window, cx));
58        }));
59        self
60    }
61}
62
63impl Disableable for Label {
64    fn disabled(mut self, disabled: bool) -> Self {
65        self.disabled = disabled;
66        self
67    }
68}
69
70impl Styled for Label {
71    fn style(&mut self) -> &mut StyleRefinement {
72        &mut self.style
73    }
74}
75
76impl RenderOnce for Label {
77    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
78        let (text, color) = {
79            let theme = cx.theme();
80            let color = theme.foreground();
81            (
82                theme.text_control,
83                if self.disabled {
84                    color.opacity(theme.disabled_opacity)
85                } else {
86                    color
87                },
88            )
89        };
90        div()
91            .id(self.id)
92            .test_support()
93            .text_size(text.size)
94            .line_height(text.line_height)
95            .font_weight(FontWeight::MEDIUM)
96            .text_color(color)
97            .cursor_default()
98            .when_some(
99                self.focus_field.filter(|_| !self.disabled),
100                |this, focus_field| this.on_click(move |_, window, cx| focus_field(window, cx)),
101            )
102            .refine_style(&self.style)
103            .child(self.text)
104    }
105}