fluidattacks-designs 0.12.0

Fluid Attacks design system, GPUI variant: desktop widgets over the shared tokens
//! What more than one widget needs, in one place so two cannot drift apart.

use std::rc::Rc;

use gpui::{
    div, point, px, rgb, rgb_to_hsla, AnyElement, App, BoxShadow, Div, ElementId,
    InteractiveElement as _, IntoElement as _, ParentElement as _, Rgba, ScrollHandle,
    SharedString, Stateful, Styled as _,
};

use fluidattacks_designs_domain::button::{spacing, Variant};
use fluidattacks_designs_domain::color::{SHADOW_ALPHA, SHADOW_COLOR};

use crate::icon::Icon;
use crate::scrollbar::Scrollbar;
use crate::theme::ActiveTheme as _;

/// A `SharedString` derefs to its own storage rather than to `str`, so the
/// borrow the domain wants is spelled out here rather than at every call.
pub fn text(value: &SharedString) -> &str {
    value
}

/// Called with the value a reader chose. `Rc` because the frame that renders
/// the rows hands a copy to every one of them.
pub type ValueHandler = Rc<dyn Fn(&str, &mut App) + 'static>;

/// Both halves are optional at every call site, so the pairing lives here.
pub fn report(chosen: Option<SharedString>, handler: Option<&ValueHandler>, app: &mut App) {
    if let (Some(value), Some(handler)) = (chosen, handler) {
        handler(&value, app);
    }
}

/// The web's ghost `IconButton`: a glyph in a padded box, rounded to the
/// button radius, that washes on hover. The caller adds the press.
pub fn ghost_icon_button(
    id: impl Into<ElementId>,
    glyph: Icon,
    padding: f32,
    cx: &App,
) -> Stateful<Div> {
    let hover = cx.theme().colors.surface_muted;
    div()
        .id(id)
        .p(px(padding))
        .rounded(px(spacing(Variant::Ghost)))
        .cursor_pointer()
        .hover(move |style| style.bg(rgb(hover)))
        .child(glyph)
}

/// A list under the design system's bar, which is the only thing that says a
/// floating list has more rows than it is showing.
pub fn scrolling(id: &'static str, list: AnyElement, handle: &ScrollHandle) -> AnyElement {
    div()
        .relative()
        .flex_grow(1.0)
        .min_h_0()
        .child(list)
        .child(Scrollbar::vertical(id, handle).track_transparent())
        .into_any_element()
}

/// One ink; only the depth differs between a menu, a dialog and a list.
pub fn elevation(shadow: (f32, f32)) -> Vec<BoxShadow> {
    vec![BoxShadow {
        color: rgb_to_hsla(Rgba {
            alpha: SHADOW_ALPHA,
            ..rgb(SHADOW_COLOR)
        }),
        offset: point(px(0.0), px(shadow.0)),
        blur_radius: px(shadow.1),
        spread_radius: px(0.0),
        inset: false,
    }]
}