More previews (#20329)

Nate Butler created

Release Notes:

- N/A

Change summary

crates/ui/src/components/button/button.rs | 102 ++++++++++++++++++++++++
crates/ui/src/components/facepile.rs      |  63 ++++++++++++++
crates/ui/src/components/indicator.rs     |  51 ++++++++++++
crates/workspace/src/theme_preview.rs     |   5 
4 files changed, 217 insertions(+), 4 deletions(-)

Detailed changes

crates/ui/src/components/button/button.rs ๐Ÿ”—

@@ -1,7 +1,7 @@
 #![allow(missing_docs)]
 use gpui::{AnyView, DefiniteLength};
 
-use crate::{prelude::*, ElevationIndex, IconPosition, KeyBinding, Spacing};
+use crate::{prelude::*, ElevationIndex, IconPosition, KeyBinding, Spacing, TintColor};
 use crate::{
     ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle,
 };
@@ -437,3 +437,103 @@ 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() -> Vec<ComponentExampleGroup<Self>> {
+        vec![
+            example_group(
+                "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(
+                "Tinted",
+                vec![
+                    single_example(
+                        "Accent",
+                        Button::new("tinted_accent", "Accent")
+                            .style(ButtonStyle::Tinted(TintColor::Accent)),
+                    ),
+                    single_example(
+                        "Negative",
+                        Button::new("tinted_negative", "Negative")
+                            .style(ButtonStyle::Tinted(TintColor::Negative)),
+                    ),
+                    single_example(
+                        "Warning",
+                        Button::new("tinted_warning", "Warning")
+                            .style(ButtonStyle::Tinted(TintColor::Warning)),
+                    ),
+                    single_example(
+                        "Positive",
+                        Button::new("tinted_positive", "Positive")
+                            .style(ButtonStyle::Tinted(TintColor::Positive)),
+                    ),
+                ],
+            ),
+            example_group(
+                "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").selected(true),
+                    ),
+                ],
+            ),
+            example_group(
+                "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("icon_color", "Delete")
+                            .style(ButtonStyle::Tinted(TintColor::Negative))
+                            .color(Color::Error)
+                            .icon_color(Color::Error)
+                            .icon(IconName::Trash)
+                            .icon_position(IconPosition::Start),
+                    ),
+                ],
+            ),
+        ]
+    }
+}

crates/ui/src/components/facepile.rs ๐Ÿ”—

@@ -1,5 +1,4 @@
-#![allow(missing_docs)]
-use crate::prelude::*;
+use crate::{prelude::*, Avatar};
 use gpui::{AnyElement, StyleRefinement};
 use smallvec::SmallVec;
 
@@ -15,10 +14,12 @@ pub struct Facepile {
 }
 
 impl Facepile {
+    /// Creates a new empty facepile.
     pub fn empty() -> Self {
         Self::new(SmallVec::new())
     }
 
+    /// Creates a new facepile with the given faces.
     pub fn new(faces: SmallVec<[AnyElement; 2]>) -> Self {
         Self { base: div(), faces }
     }
@@ -58,3 +59,61 @@ impl RenderOnce for Facepile {
             )
     }
 }
+
+impl ComponentPreview for Facepile {
+    fn description() -> impl Into<Option<&'static str>> {
+        "A facepile is a collection of faces stacked horizontallyโ€“\
+        always with the leftmost face on top and descending in z-index.\
+        \n\nFacepiles are used to display a group of people or things,\
+        such as a list of participants in a collaboration session."
+    }
+    fn examples() -> Vec<ComponentExampleGroup<Self>> {
+        let few_faces: [&'static str; 3] = [
+            "https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
+            "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
+            "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
+        ];
+
+        let many_faces: [&'static str; 6] = [
+            "https://avatars.githubusercontent.com/u/326587?s=60&v=4",
+            "https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
+            "https://avatars.githubusercontent.com/u/1789?s=60&v=4",
+            "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
+            "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
+            "https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
+        ];
+
+        vec![example_group(
+            "Examples",
+            vec![
+                single_example(
+                    "Few Faces",
+                    Facepile::new(
+                        few_faces
+                            .iter()
+                            .map(|&url| Avatar::new(url).into_any_element())
+                            .collect(),
+                    ),
+                ),
+                single_example(
+                    "Many Faces",
+                    Facepile::new(
+                        many_faces
+                            .iter()
+                            .map(|&url| Avatar::new(url).into_any_element())
+                            .collect(),
+                    ),
+                ),
+                single_example(
+                    "Custom Size",
+                    Facepile::new(
+                        few_faces
+                            .iter()
+                            .map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
+                            .collect(),
+                    ),
+                ),
+            ],
+        )]
+    }
+}

crates/ui/src/components/indicator.rs ๐Ÿ”—

@@ -12,6 +12,7 @@ enum IndicatorKind {
 #[derive(IntoElement)]
 pub struct Indicator {
     kind: IndicatorKind,
+    border_color: Option<Color>,
     pub color: Color,
 }
 
@@ -19,6 +20,7 @@ impl Indicator {
     pub fn dot() -> Self {
         Self {
             kind: IndicatorKind::Dot,
+            border_color: None,
             color: Color::Default,
         }
     }
@@ -26,6 +28,8 @@ impl Indicator {
     pub fn bar() -> Self {
         Self {
             kind: IndicatorKind::Bar,
+            border_color: None,
+
             color: Color::Default,
         }
     }
@@ -33,6 +37,8 @@ impl Indicator {
     pub fn icon(icon: impl Into<AnyIcon>) -> Self {
         Self {
             kind: IndicatorKind::Icon(icon.into()),
+            border_color: None,
+
             color: Color::Default,
         }
     }
@@ -41,11 +47,25 @@ impl Indicator {
         self.color = color;
         self
     }
+
+    pub fn border_color(mut self, color: Color) -> Self {
+        self.border_color = Some(color);
+        self
+    }
 }
 
 impl RenderOnce for Indicator {
     fn render(self, cx: &mut WindowContext) -> impl IntoElement {
         let container = div().flex_none();
+        let container = if let Some(border_color) = self.border_color {
+            if matches!(self.kind, IndicatorKind::Dot | IndicatorKind::Bar) {
+                container.border_1().border_color(border_color.color(cx))
+            } else {
+                container
+            }
+        } else {
+            container
+        };
 
         match self.kind {
             IndicatorKind::Icon(icon) => container
@@ -63,3 +83,34 @@ impl RenderOnce for Indicator {
         }
     }
 }
+
+impl ComponentPreview for Indicator {
+    fn description() -> impl Into<Option<&'static str>> {
+        "An indicator visually represents a status or state."
+    }
+
+    fn examples() -> Vec<ComponentExampleGroup<Self>> {
+        vec![
+            example_group(
+                "Types",
+                vec![
+                    single_example("Dot", Indicator::dot().color(Color::Info)),
+                    single_example("Bar", Indicator::bar().color(Color::Player(2))),
+                    single_example(
+                        "Icon",
+                        Indicator::icon(Icon::new(IconName::Check).color(Color::Success)),
+                    ),
+                ],
+            ),
+            example_group(
+                "Examples",
+                vec![
+                    single_example("Info", Indicator::dot().color(Color::Info)),
+                    single_example("Success", Indicator::dot().color(Color::Success)),
+                    single_example("Warning", Indicator::dot().color(Color::Warning)),
+                    single_example("Error", Indicator::dot().color(Color::Error)),
+                ],
+            ),
+        ]
+    }
+}

crates/workspace/src/theme_preview.rs ๐Ÿ”—

@@ -5,7 +5,7 @@ use theme::all_theme_colors;
 use ui::{
     prelude::*, utils::calculate_contrast_ratio, AudioStatus, Availability, Avatar,
     AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike, Checkbox, ElevationIndex,
-    Facepile, TintColor, Tooltip,
+    Facepile, Indicator, TintColor, Tooltip,
 };
 
 use crate::{Item, Workspace};
@@ -510,6 +510,9 @@ impl ThemePreview {
             .size_full()
             .gap_2()
             .child(Checkbox::render_component_previews(cx))
+            .child(Facepile::render_component_previews(cx))
+            .child(Button::render_component_previews(cx))
+            .child(Indicator::render_component_previews(cx))
             .child(Icon::render_component_previews(cx))
             .child(self.render_avatars(cx))
             .child(self.render_buttons(layer, cx))