Add Tool Strip (#11756)

Nate Butler and Marshall Bowers created

Release Notes:

- N/A

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>

Change summary

crates/storybook/src/story_selector.rs         |  2 
crates/ui/src/components.rs                    |  2 
crates/ui/src/components/stories.rs            |  2 
crates/ui/src/components/stories/tool_strip.rs | 35 ++++++++++++
crates/ui/src/components/tool_strip.rs         | 55 ++++++++++++++++++++
crates/ui/src/prelude.rs                       |  2 
6 files changed, 97 insertions(+), 1 deletion(-)

Detailed changes

crates/storybook/src/story_selector.rs 🔗

@@ -38,6 +38,7 @@ pub enum ComponentStory {
     Text,
     TitleBar,
     ToggleButton,
+    ToolStrip,
     ViewportUnits,
 }
 
@@ -73,6 +74,7 @@ impl ComponentStory {
             Self::TabBar => cx.new_view(|_| ui::TabBarStory).into(),
             Self::TitleBar => cx.new_view(|_| ui::TitleBarStory).into(),
             Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(),
+            Self::ToolStrip => cx.new_view(|_| ui::ToolStripStory).into(),
             Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(),
             Self::Picker => PickerStory::new(cx).into(),
         }

crates/ui/src/components.rs 🔗

@@ -18,6 +18,7 @@ mod stack;
 mod tab;
 mod tab_bar;
 mod title_bar;
+mod tool_strip;
 mod tooltip;
 
 #[cfg(feature = "stories")]
@@ -43,6 +44,7 @@ pub use stack::*;
 pub use tab::*;
 pub use tab_bar::*;
 pub use title_bar::*;
+pub use tool_strip::*;
 pub use tooltip::*;
 
 #[cfg(feature = "stories")]

crates/ui/src/components/stories.rs 🔗

@@ -14,6 +14,7 @@ mod tab;
 mod tab_bar;
 mod title_bar;
 mod toggle_button;
+mod tool_strip;
 
 pub use avatar::*;
 pub use button::*;
@@ -31,3 +32,4 @@ pub use tab::*;
 pub use tab_bar::*;
 pub use title_bar::*;
 pub use toggle_button::*;
+pub use tool_strip::*;

crates/ui/src/components/stories/tool_strip.rs 🔗

@@ -0,0 +1,35 @@
+use gpui::Render;
+use story::{StoryContainer, StoryItem, StorySection};
+
+use crate::{prelude::*, ToolStrip, Tooltip};
+
+pub struct ToolStripStory;
+
+impl Render for ToolStripStory {
+    fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
+        StoryContainer::new(
+            "Tool Strip",
+            "crates/ui/src/components/stories/tool_strip.rs",
+        )
+        .child(
+            StorySection::new().child(StoryItem::new(
+                "Vertical Tool Strip",
+                h_flex().child(
+                    ToolStrip::vertical("tool_strip_example")
+                        .tool(
+                            IconButton::new("example_tool", IconName::AudioOn)
+                                .tooltip(|cx| Tooltip::text("Example tool", cx)),
+                        )
+                        .tool(
+                            IconButton::new("example_tool_2", IconName::MicMute)
+                                .tooltip(|cx| Tooltip::text("Example tool 2", cx)),
+                        )
+                        .tool(
+                            IconButton::new("example_tool_3", IconName::Screen)
+                                .tooltip(|cx| Tooltip::text("Example tool 3", cx)),
+                        ),
+                ),
+            )),
+        )
+    }
+}

crates/ui/src/components/tool_strip.rs 🔗

@@ -0,0 +1,55 @@
+use crate::prelude::*;
+use gpui::*;
+
+#[derive(IntoElement)]
+pub struct ToolStrip {
+    id: ElementId,
+    tools: Vec<IconButton>,
+    axis: Axis,
+}
+
+impl ToolStrip {
+    fn new(id: ElementId, axis: Axis) -> Self {
+        Self {
+            id,
+            tools: vec![],
+            axis,
+        }
+    }
+
+    pub fn vertical(id: impl Into<ElementId>) -> Self {
+        Self::new(id.into(), Axis::Vertical)
+    }
+
+    pub fn tools(mut self, tools: Vec<IconButton>) -> Self {
+        self.tools = tools;
+        self
+    }
+
+    pub fn tool(mut self, tool: IconButton) -> Self {
+        self.tools.push(tool);
+        self
+    }
+}
+
+impl RenderOnce for ToolStrip {
+    fn render(self, cx: &mut WindowContext) -> impl IntoElement {
+        let group = format!("tool_strip_{}", self.id.clone());
+
+        div()
+            .id(self.id.clone())
+            .group(group)
+            .map(|element| match self.axis {
+                Axis::Vertical => element.v_flex(),
+                Axis::Horizontal => element.h_flex(),
+            })
+            .flex_none()
+            .gap(Spacing::Small.rems(cx))
+            .p(Spacing::XSmall.rems(cx))
+            .border_1()
+            .border_color(cx.theme().colors().border)
+            .rounded(rems_from_px(6.0))
+            .bg(cx.theme().colors().elevated_surface_background)
+            .children(self.tools)
+    }
+}

crates/ui/src/prelude.rs 🔗

@@ -11,7 +11,7 @@ pub use crate::clickable::*;
 pub use crate::disableable::*;
 pub use crate::fixed::*;
 pub use crate::selectable::*;
-pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle, StyledTypography};
+pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle, StyledTypography, TextSize};
 pub use crate::visible_on_hover::*;
 pub use crate::Spacing;
 pub use crate::{h_flex, v_flex};