Skip to main content

glassy_ui/
label.rs

1use crate::motion::StyledSlot;
2use crate::theme::ActiveTheme;
3use gpui::{
4    div, prelude::*, px, App, FocusHandle, FontWeight, IntoElement, RenderOnce, SharedString,
5    StyleRefinement, Styled, Window,
6};
7
8use crate::compat::{AccessibilityExt, Role};
9
10/// 13/500 name that sits above a field. Never inside it.
11#[derive(IntoElement)]
12pub struct Label {
13    id: SharedString,
14    text: SharedString,
15    required: bool,
16    optional: bool,
17    focus_handle: Option<FocusHandle>,
18    style: StyleRefinement,
19}
20
21impl Label {
22    pub fn new(text: impl Into<SharedString>) -> Self {
23        let text = text.into();
24        Self {
25            id: SharedString::from(format!("label-{text}")),
26            text,
27            required: false,
28            optional: false,
29            focus_handle: None,
30            style: StyleRefinement::default(),
31        }
32    }
33
34    pub fn id(mut self, id: impl Into<SharedString>) -> Self {
35        self.id = id.into();
36        self
37    }
38
39    pub fn required(mut self, required: bool) -> Self {
40        self.required = required;
41        self
42    }
43
44    pub fn optional(mut self, optional: bool) -> Self {
45        self.optional = optional;
46        self
47    }
48
49    /// Clicking the label focuses this control.
50    pub fn focus_handle(mut self, focus_handle: FocusHandle) -> Self {
51        self.focus_handle = Some(focus_handle);
52        self
53    }
54}
55
56impl Styled for Label {
57    fn style(&mut self) -> &mut StyleRefinement {
58        &mut self.style
59    }
60}
61
62impl RenderOnce for Label {
63    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
64        let theme = cx.theme();
65        let focus_handle = self.focus_handle;
66        div()
67            .id(self.id)
68            .role(Role::Label)
69            .flex()
70            .items_center()
71            .gap(px(4.))
72            .font_family(theme.font_family)
73            .font_weight(FontWeight::MEDIUM)
74            .text_size(px(13.))
75            .line_height(px(16.))
76            .text_color(theme.label)
77            .refine_style(&self.style)
78            .when(focus_handle.is_some(), |el| el.cursor_pointer())
79            .when_some(focus_handle, |el, focus_handle| {
80                el.on_click(move |_, window, _cx| {
81                    focus_handle.focus(window);
82                })
83            })
84            .child(self.text)
85            .when(self.required, |el| {
86                el.child(
87                    div()
88                        .font_family(theme.font_family)
89                        .font_weight(FontWeight::MEDIUM)
90                        .text_size(px(13.))
91                        .line_height(px(16.))
92                        .text_color(theme.destructive)
93                        .child("*"),
94                )
95            })
96            .when(self.optional, |el| {
97                el.child(
98                    div()
99                        .font_family(theme.font_family)
100                        .font_weight(FontWeight::MEDIUM)
101                        .text_size(px(12.))
102                        .line_height(px(16.))
103                        .text_color(theme.body)
104                        .child("Optional"),
105                )
106            })
107    }
108}