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
co-authored by Marshall Bowers
parent 56cfc60875
commit 8f1ff189cc
36 changed files with 1582 additions and 976 deletions
+31 -28
View File
@@ -1,10 +1,8 @@
use gpui::{IntoElement, Point};
use gpui::{AnyElement, IntoElement, Point};
use crate::{
prelude::*, traits::component_preview::ComponentPreview, IconDecoration, IconDecorationKind,
};
use crate::{prelude::*, IconDecoration, IconDecorationKind};
#[derive(IntoElement)]
#[derive(IntoElement, IntoComponent)]
pub struct DecoratedIcon {
icon: Icon,
decoration: Option<IconDecoration>,
@@ -27,12 +25,7 @@ impl RenderOnce for DecoratedIcon {
}
impl ComponentPreview for DecoratedIcon {
fn examples(_: &mut Window, cx: &mut App) -> Vec<ComponentExampleGroup<Self>> {
let icon_1 = Icon::new(IconName::FileDoc);
let icon_2 = Icon::new(IconName::FileDoc);
let icon_3 = Icon::new(IconName::FileDoc);
let icon_4 = Icon::new(IconName::FileDoc);
fn preview(_window: &mut Window, cx: &App) -> AnyElement {
let decoration_x = IconDecoration::new(
IconDecorationKind::X,
cx.theme().colors().surface_background,
@@ -66,22 +59,32 @@ impl ComponentPreview for DecoratedIcon {
y: px(-2.),
});
let examples = vec![
single_example("no_decoration", DecoratedIcon::new(icon_1, None)),
single_example(
"with_decoration",
DecoratedIcon::new(icon_2, Some(decoration_x)),
),
single_example(
"with_decoration",
DecoratedIcon::new(icon_3, Some(decoration_triangle)),
),
single_example(
"with_decoration",
DecoratedIcon::new(icon_4, Some(decoration_dot)),
),
];
vec![example_group(examples)]
v_flex()
.gap_6()
.children(vec![example_group_with_title(
"Decorations",
vec![
single_example(
"No Decoration",
DecoratedIcon::new(Icon::new(IconName::FileDoc), None).into_any_element(),
),
single_example(
"X Decoration",
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_x))
.into_any_element(),
),
single_example(
"Triangle Decoration",
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_triangle))
.into_any_element(),
),
single_example(
"Dot Decoration",
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_dot))
.into_any_element(),
),
],
)])
.into_any_element()
}
}
@@ -1,8 +1,8 @@
use gpui::{svg, Hsla, IntoElement, Point};
use strum::{EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
use strum::{EnumIter, EnumString, IntoStaticStr};
use ui_macros::DerivePathStr;
use crate::{prelude::*, traits::component_preview::ComponentPreview};
use crate::prelude::*;
const ICON_DECORATION_SIZE: Pixels = px(11.);
@@ -149,21 +149,3 @@ impl RenderOnce for IconDecoration {
.child(background)
}
}
impl ComponentPreview for IconDecoration {
fn examples(_: &mut Window, cx: &mut App) -> Vec<ComponentExampleGroup<Self>> {
let all_kinds = IconDecorationKind::iter().collect::<Vec<_>>();
let examples = all_kinds
.iter()
.map(|kind| {
single_example(
format!("{kind:?}"),
IconDecoration::new(*kind, cx.theme().colors().surface_background, cx),
)
})
.collect();
vec![example_group(examples)]
}
}