Skip to main content

herogpui_components/
kbd.rs

1//! Kbd — port of `@heroui/kbd`.
2
3use gpui::{px, AnyElement, App, IntoElement, ParentElement, Pixels, RenderOnce, Styled, Window};
4use herogpui_theme::ActiveTheme;
5
6/// Visual style of a key (`variant`).
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
8pub enum KbdVariant {
9    /// Neutral key on the `bg-default` chip.
10    #[default]
11    Default,
12    /// Transparent key for inline prose.
13    Light,
14}
15
16impl KbdVariant {
17    /// Every Kbd variant, in declaration order.
18    pub const ALL: [KbdVariant; 2] = [KbdVariant::Default, KbdVariant::Light];
19
20    /// A human-readable label for this variant.
21    pub fn label(self) -> &'static str {
22        match self {
23            KbdVariant::Default => "Default",
24            KbdVariant::Light => "Light",
25        }
26    }
27}
28
29/// Keyboard key display (`<Kbd>`).
30#[must_use = "a component does nothing until it is rendered: add it as a child or return it from `render`"]
31#[derive(IntoElement)]
32pub struct Kbd {
33    variant: KbdVariant,
34    children: Vec<AnyElement>,
35    /// The corner radius, in place of `--radius-lg`.
36    radius: Option<Pixels>,
37    /// The `sx` slot, refined over the root style at the end of render.
38    sx: Option<Box<gpui::StyleRefinement>>,
39}
40
41impl Kbd {
42    /// Creates an empty Kbd.
43    pub fn new() -> Self {
44        Self {
45            variant: KbdVariant::Default,
46            children: Vec::new(),
47            radius: None,
48            sx: None,
49        }
50    }
51
52    /// Sets the Kbd variant.
53    pub fn variant(mut self, v: KbdVariant) -> Self {
54        self.variant = v;
55        self
56    }
57
58    /// The corner radius, in place of `--radius-lg`. Not a v3 prop; the
59    /// removed v2 `radius` prop is prohibited and this is a per-component
60    /// repository extension.
61    pub fn radius(mut self, radius: impl Into<Pixels>) -> Self {
62        self.radius = Some(radius.into());
63        self
64    }
65
66    /// The one slot for caller-owned low-level styling: GPUI's styling methods
67    /// (`bg`, `text_color`, `w`, `h`, `p`, `rounded`, `border_color`, …)
68    /// applied to the key's root element after every value the variant and the
69    /// active theme chose, so they win.
70    pub fn sx(mut self, style: impl FnOnce(gpui::Div) -> gpui::Div) -> Self {
71        crate::util::refine_sx(&mut self.sx, style);
72        self
73    }
74}
75
76impl Default for Kbd {
77    fn default() -> Self {
78        Self::new()
79    }
80}
81
82impl ParentElement for Kbd {
83    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
84        self.children.extend(elements);
85    }
86}
87
88impl RenderOnce for Kbd {
89    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
90        let colors = cx.colors();
91
92        let (h, text) = (px(24.), px(14.));
93
94        let mut el = gpui::div()
95            .flex()
96            .items_center()
97            .justify_center()
98            .text_center()
99            .gap(px(2.))
100            .px(px(8.))
101            .h(h)
102            .rounded(self.radius.unwrap_or_else(|| crate::util::key_radius(cx)))
103            // Tailwind's `text-sm` pairs 14px with a 20px leading; gpui's phi
104            // default would give 14 x 1.618 ≈ 23px.
105            .text_size(text)
106            .line_height(px(20.))
107            .font_weight(gpui::FontWeight::MEDIUM)
108            .whitespace_nowrap()
109            .text_color(colors.muted);
110
111        el = match self.variant {
112            KbdVariant::Default => el.bg(colors.default.color),
113            KbdVariant::Light => el.bg(gpui::transparent_black()),
114        };
115
116        // `.kbd__content` is the key text itself; `.kbd__abbr` is the `<abbr>`
117        // v3 wraps it in for screen readers, which has no analogue here.
118        el = el.children(self.children);
119        el = crate::util::apply_sx(el, &self.sx);
120        el
121    }
122}
123
124crate::util::impl_component_styled!(Kbd);