guise-ui 1.5.3

A component library for gpui, Zed's GPU-accelerated UI framework: a themed palette, sizing tokens, 130+ composable components, a reactive state layer, and an in-app Safari-style inspector.
Documentation
//! `ActionIcon` — a square icon-only button.

use gpui::prelude::*;
use gpui::{div, px, App, ClickEvent, ElementId, IntoElement, SharedString, Window};

use crate::devtools::Probed;
use crate::icon::Glyph;
use crate::input::ClickHandler;
use crate::style::{icon_size, surface, ColorValue, Variant};
use crate::theme::{theme, ColorName, Size};

/// A compact, square icon button.
#[derive(IntoElement)]
pub struct ActionIcon {
  id: ElementId,
  icon: Glyph,
  label: Option<SharedString>,
  variant: Variant,
  color: ColorValue,
  size: Size,
  radius: Option<Size>,
  disabled: bool,
  on_click: Option<ClickHandler>,
}

impl ActionIcon {
  pub fn new(id: impl Into<ElementId>, icon: impl Into<Glyph>) -> Self {
    ActionIcon {
      id: id.into(),
      icon: icon.into(),
      label: None,
      variant: Variant::Subtle,
      color: ColorValue::Named(ColorName::Gray),
      size: Size::Md,
      radius: None,
      disabled: false,
      on_click: None,
    }
  }

  /// Give the icon-only control a human-readable name and hover tooltip.
  pub fn label(mut self, label: impl Into<SharedString>) -> Self {
    self.label = Some(label.into());
    self
  }

  pub fn variant(mut self, variant: Variant) -> Self {
    self.variant = variant;
    self
  }

  pub fn color(mut self, color: impl Into<ColorValue>) -> Self {
    self.color = color.into();
    self
  }

  pub fn size(mut self, size: Size) -> Self {
    self.size = size;
    self
  }

  pub fn radius(mut self, radius: Size) -> Self {
    self.radius = Some(radius);
    self
  }

  pub fn disabled(mut self, disabled: bool) -> Self {
    self.disabled = disabled;
    self
  }

  pub fn on_click(
    mut self,
    handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
  ) -> Self {
    self.on_click = Some(Box::new(handler));
    self
  }
}

impl RenderOnce for ActionIcon {
  fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
    let t = theme(cx);
    let s = surface(t, self.color, self.variant);
    let dim = icon_size(self.size);
    let radius = t.radius(self.radius.unwrap_or(t.default_radius));
    let focus = t.primary().hsla();

    let mut el = div()
      .id(self.id)
      .w(px(dim))
      .h(px(dim))
      .flex()
      .items_center()
      .justify_center()
      .rounded(px(radius))
      .bg(s.bg)
      .text_color(s.fg)
      .text_size(px(dim * 0.5))
      .child(self.icon)
      .tab_index(0)
      .focus(move |style| style.border_2().border_color(focus))
      .on_key_down(crate::input::handle_tab);
    if let Some(border) = s.border {
      el = el.border_1().border_color(border);
    }

    let label = self.label;
    let element = if self.disabled {
      el.tab_stop(false).opacity(0.5)
    } else {
      let hover_bg = s.bg_hover;
      el = el.hover(move |st| st.bg(hover_bg));
      if let Some(handler) = self.on_click {
        el = el.on_click(handler);
      }
      el
    };

    let element = element.when_some(label.clone(), |element, label| {
      element.tooltip(crate::overlay::tooltip(label))
    });

    element
      .probe("ActionIcon")
      .attr("variant", self.variant.label())
      .attr("size", self.size.label())
      .attr_opt("label", label)
      .attr_if("disabled", self.disabled)
  }
}