component: Add component and component_preview crates to power UI components (#24456)

This PR formalizes design components with the Component and
ComponentPreview traits.

You can open the preview UI with `workspace: open component preview`.

Component previews no longer need to return `Self` allowing for more
complex previews, and previews of components like `ui::Tooltip` that
supplement other components rather than are rendered by default.

`cargo-machete` incorrectly identifies `linkme` as an unused dep on
crates that have components deriving `IntoComponent`, so you may need to
add this to that crate's `Cargo.toml`:

```toml
# cargo-machete doesn't understand that linkme is used in the component macro
[package.metadata.cargo-machete]
ignored = ["linkme"]
```

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>
This commit is contained in:
Nate Butler 2025-02-09 13:25:03 -05:00 committed by GitHub
parent 56cfc60875
commit 8f1ff189cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 1582 additions and 976 deletions

View file

@ -1,5 +1,7 @@
#![allow(missing_docs)]
use gpui::{AnyView, DefiniteLength};
use component::{example_group_with_title, single_example, ComponentPreview};
use gpui::{AnyElement, AnyView, DefiniteLength};
use ui_macros::IntoComponent;
use crate::{
prelude::*, Color, DynamicSpacing, ElevationIndex, IconPosition, KeyBinding,
@ -78,7 +80,7 @@ use super::button_icon::ButtonIcon;
/// });
/// ```
///
#[derive(IntoElement)]
#[derive(IntoElement, IntoComponent)]
pub struct Button {
base: ButtonLike,
label: SharedString,
@ -455,101 +457,124 @@ impl RenderOnce for Button {
}
impl ComponentPreview for Button {
fn description() -> impl Into<Option<&'static str>> {
"A button allows users to take actions, and make choices, with a single tap."
}
fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
vec![
example_group_with_title(
"Styles",
vec![
single_example("Default", Button::new("default", "Default")),
single_example(
"Filled",
Button::new("filled", "Filled").style(ButtonStyle::Filled),
),
single_example(
"Subtle",
Button::new("outline", "Subtle").style(ButtonStyle::Subtle),
),
single_example(
"Transparent",
Button::new("transparent", "Transparent").style(ButtonStyle::Transparent),
),
],
),
example_group_with_title(
"Tinted",
vec![
single_example(
"Accent",
Button::new("tinted_accent", "Accent")
.style(ButtonStyle::Tinted(TintColor::Accent)),
),
single_example(
"Error",
Button::new("tinted_negative", "Error")
.style(ButtonStyle::Tinted(TintColor::Error)),
),
single_example(
"Warning",
Button::new("tinted_warning", "Warning")
.style(ButtonStyle::Tinted(TintColor::Warning)),
),
single_example(
"Success",
Button::new("tinted_positive", "Success")
.style(ButtonStyle::Tinted(TintColor::Success)),
),
],
),
example_group_with_title(
"States",
vec![
single_example("Default", Button::new("default_state", "Default")),
single_example(
"Disabled",
Button::new("disabled", "Disabled").disabled(true),
),
single_example(
"Selected",
Button::new("selected", "Selected").toggle_state(true),
),
],
),
example_group_with_title(
"With Icons",
vec![
single_example(
"Icon Start",
Button::new("icon_start", "Icon Start")
.icon(IconName::Check)
.icon_position(IconPosition::Start),
),
single_example(
"Icon End",
Button::new("icon_end", "Icon End")
.icon(IconName::Check)
.icon_position(IconPosition::End),
),
single_example(
"Icon Color",
Button::new("icon_color", "Icon Color")
.icon(IconName::Check)
.icon_color(Color::Accent),
),
single_example(
"Tinted Icons",
Button::new("tinted_icons", "Error")
.style(ButtonStyle::Tinted(TintColor::Error))
.color(Color::Error)
.icon_color(Color::Error)
.icon(IconName::Trash)
.icon_position(IconPosition::Start),
),
],
),
]
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
v_flex()
.gap_6()
.children(vec![
example_group_with_title(
"Styles",
vec![
single_example(
"Default",
Button::new("default", "Default").into_any_element(),
),
single_example(
"Filled",
Button::new("filled", "Filled")
.style(ButtonStyle::Filled)
.into_any_element(),
),
single_example(
"Subtle",
Button::new("outline", "Subtle")
.style(ButtonStyle::Subtle)
.into_any_element(),
),
single_example(
"Transparent",
Button::new("transparent", "Transparent")
.style(ButtonStyle::Transparent)
.into_any_element(),
),
],
),
example_group_with_title(
"Tinted",
vec![
single_example(
"Accent",
Button::new("tinted_accent", "Accent")
.style(ButtonStyle::Tinted(TintColor::Accent))
.into_any_element(),
),
single_example(
"Error",
Button::new("tinted_negative", "Error")
.style(ButtonStyle::Tinted(TintColor::Error))
.into_any_element(),
),
single_example(
"Warning",
Button::new("tinted_warning", "Warning")
.style(ButtonStyle::Tinted(TintColor::Warning))
.into_any_element(),
),
single_example(
"Success",
Button::new("tinted_positive", "Success")
.style(ButtonStyle::Tinted(TintColor::Success))
.into_any_element(),
),
],
),
example_group_with_title(
"States",
vec![
single_example(
"Default",
Button::new("default_state", "Default").into_any_element(),
),
single_example(
"Disabled",
Button::new("disabled", "Disabled")
.disabled(true)
.into_any_element(),
),
single_example(
"Selected",
Button::new("selected", "Selected")
.toggle_state(true)
.into_any_element(),
),
],
),
example_group_with_title(
"With Icons",
vec![
single_example(
"Icon Start",
Button::new("icon_start", "Icon Start")
.icon(IconName::Check)
.icon_position(IconPosition::Start)
.into_any_element(),
),
single_example(
"Icon End",
Button::new("icon_end", "Icon End")
.icon(IconName::Check)
.icon_position(IconPosition::End)
.into_any_element(),
),
single_example(
"Icon Color",
Button::new("icon_color", "Icon Color")
.icon(IconName::Check)
.icon_color(Color::Accent)
.into_any_element(),
),
single_example(
"Tinted Icons",
Button::new("tinted_icons", "Error")
.style(ButtonStyle::Tinted(TintColor::Error))
.color(Color::Error)
.icon_color(Color::Error)
.icon(IconName::Trash)
.icon_position(IconPosition::Start)
.into_any_element(),
),
],
),
])
.into_any_element()
}
}