Checkpoint

Nate Butler created

Change summary

crates/storybook/src/components/icon_button.rs | 28 +++++++-----
crates/storybook/src/modules/chat_panel.rs     | 14 +-----
crates/storybook/src/modules/tab_bar.rs        | 37 ++++++----------
crates/storybook/src/modules/title_bar.rs      | 33 +++------------
crates/storybook/src/prelude.rs                | 34 +++++++++++----
docs/ui/states.md                              | 43 ++++++++++++++++++++
6 files changed, 110 insertions(+), 79 deletions(-)

Detailed changes

crates/storybook/src/components/icon_button.rs 🔗

@@ -1,4 +1,4 @@
-use crate::prelude::{ButtonVariant, UIState};
+use crate::prelude::{ButtonVariant, InteractionState};
 use crate::theme::theme;
 use gpui2::elements::svg;
 use gpui2::style::{StyleHelpers, Styleable};
@@ -6,31 +6,37 @@ use gpui2::{elements::div, IntoElement};
 use gpui2::{Element, ParentElement, ViewContext};
 
 #[derive(Element)]
-pub(crate) struct IconButton {
+pub struct IconButton {
     path: &'static str,
     variant: ButtonVariant,
-    state: UIState,
+    state: InteractionState,
 }
 
-pub fn icon_button<V: 'static>(
-    path: &'static str,
-    variant: ButtonVariant,
-    state: UIState,
-) -> impl Element<V> {
+pub fn icon_button<V: 'static>(path: &'static str) -> IconButton {
     IconButton {
         path,
-        variant,
-        state,
+        variant: ButtonVariant::default(),
+        state: InteractionState::default(),
     }
 }
 
 impl IconButton {
+    pub fn variant(mut self, variant: ButtonVariant) -> Self {
+        self.variant = variant;
+        self
+    }
+
+    pub fn state(mut self, state: InteractionState) -> Self {
+        self.state = state;
+        self
+    }
+
     fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
         let theme = theme(cx);
 
         let icon_color;
 
-        if self.state == UIState::Disabled {
+        if self.state == InteractionState::Disabled {
             icon_color = theme.highest.base.disabled.foreground;
         } else {
             icon_color = theme.highest.base.default.foreground;

crates/storybook/src/modules/chat_panel.rs 🔗

@@ -1,12 +1,12 @@
 use std::marker::PhantomData;
 
 use crate::components::icon_button;
-use crate::prelude::{ButtonVariant, UIState};
 use crate::theme::theme;
 use gpui2::elements::div::ScrollState;
 use gpui2::style::StyleHelpers;
 use gpui2::{elements::div, IntoElement};
 use gpui2::{Element, ParentElement, ViewContext};
+use theme::IconButton;
 
 #[derive(Element)]
 pub struct ChatPanel<V: 'static> {
@@ -58,16 +58,8 @@ impl<V: 'static> ChatPanel<V> {
                             .flex()
                             .items_center()
                             .gap_px()
-                            .child(icon_button(
-                                "icons/plus.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Default,
-                            ))
-                            .child(icon_button(
-                                "icons/split.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Default,
-                            )),
+                            .child(icon_button::<IconButton>("icons/plus.svg"))
+                            .child(icon_button::<IconButton>("icons/split.svg")),
                     ),
             )
     }

crates/storybook/src/modules/tab_bar.rs 🔗

@@ -1,12 +1,13 @@
 use std::marker::PhantomData;
 
 use crate::components::{icon_button, tab};
-use crate::prelude::{ButtonVariant, UIState};
+use crate::prelude::InteractionState;
 use crate::theme::theme;
 use gpui2::elements::div::ScrollState;
 use gpui2::style::StyleHelpers;
 use gpui2::{elements::div, IntoElement};
 use gpui2::{Element, ParentElement, ViewContext};
+use theme::IconButton;
 
 #[derive(Element)]
 pub struct TabBar<V: 'static> {
@@ -24,7 +25,8 @@ pub fn tab_bar<V: 'static>(scroll_state: ScrollState) -> TabBar<V> {
 impl<V: 'static> TabBar<V> {
     fn render(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
         let theme = theme(cx);
-
+        let can_navigate_back = true;
+        let can_navigate_forward = false;
         div()
             .w_full()
             .flex()
@@ -41,16 +43,15 @@ impl<V: 'static> TabBar<V> {
                             .flex()
                             .items_center()
                             .gap_px()
-                            .child(icon_button(
-                                "icons/arrow_left.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Default,
-                            ))
-                            .child(icon_button(
-                                "icons/arrow_right.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Disabled,
-                            )),
+                            .child(
+                                icon_button::<IconButton>("icons/arrow_left.svg")
+                                    .state(InteractionState::Enabled.if_enabled(can_navigate_back)),
+                            )
+                            .child(
+                                icon_button::<IconButton>("icons/arrow_right.svg").state(
+                                    InteractionState::Enabled.if_enabled(can_navigate_forward),
+                                ),
+                            ),
                     ),
             )
             .child(
@@ -83,16 +84,8 @@ impl<V: 'static> TabBar<V> {
                             .flex()
                             .items_center()
                             .gap_px()
-                            .child(icon_button(
-                                "icons/plus.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Default,
-                            ))
-                            .child(icon_button(
-                                "icons/split.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Default,
-                            )),
+                            .child(icon_button::<IconButton>("icons/plus.svg"))
+                            .child(icon_button::<IconButton>("icons/split.svg")),
                     ),
             )
     }

crates/storybook/src/modules/title_bar.rs 🔗

@@ -1,11 +1,12 @@
 use std::marker::PhantomData;
 
 use crate::components::{avatar, icon_button, tool_divider};
-use crate::prelude::{ButtonVariant, Shape, UIState};
+use crate::prelude::Shape;
 use crate::theme::theme;
 use gpui2::style::{StyleHelpers, Styleable};
 use gpui2::{elements::div, IntoElement};
 use gpui2::{Element, ParentElement, ViewContext};
+use theme::IconButton;
 
 #[derive(Element)]
 pub struct TitleBar<V: 'static> {
@@ -111,16 +112,8 @@ impl<V: 'static> TitleBar<V> {
                             .flex()
                             .items_center()
                             .gap_1()
-                            .child(icon_button(
-                                "icons/stop_sharing.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Default,
-                            ))
-                            .child(icon_button(
-                                "icons/exit.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Default,
-                            )),
+                            .child(icon_button::<IconButton>("icons/stop_sharing.svg"))
+                            .child(icon_button::<IconButton>("icons/exit.svg")),
                     )
                     .child(tool_divider())
                     .child(
@@ -129,21 +122,9 @@ impl<V: 'static> TitleBar<V> {
                             .flex()
                             .items_center()
                             .gap_1()
-                            .child(icon_button(
-                                "icons/radix/mic.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Default,
-                            ))
-                            .child(icon_button(
-                                "icons/radix/speaker-loud.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Default,
-                            ))
-                            .child(icon_button(
-                                "icons/radix/desktop.svg",
-                                ButtonVariant::Ghost,
-                                UIState::Default,
-                            )),
+                            .child(icon_button::<IconButton>("icons/radix/mic.svg"))
+                            .child(icon_button::<IconButton>("icons/radix/speaker-loud.svg"))
+                            .child(icon_button::<IconButton>("icons/radix/desktop.svg")),
                     )
                     .child(div().px_2().flex().items_center().child(avatar(
                         "https://avatars.githubusercontent.com/u/1714999?v=4",

crates/storybook/src/prelude.rs 🔗

@@ -1,26 +1,42 @@
-#[derive(PartialEq)]
+#[derive(Default, PartialEq)]
 pub enum ButtonVariant {
+    #[default]
     Ghost,
     Filled,
 }
 
-#[derive(PartialEq)]
+#[derive(Default, PartialEq)]
 pub enum Shape {
+    #[default]
     Circle,
     RoundedRectangle,
 }
 
-#[derive(PartialEq)]
-pub enum UIState {
-    Default,
+#[derive(Default, PartialEq, Clone, Copy)]
+pub enum InteractionState {
+    #[default]
+    Enabled,
     Hovered,
     Active,
     Focused,
+    Dragged,
     Disabled,
 }
 
-#[derive(PartialEq)]
-pub enum UIToggleState {
-    Default,
-    Enabled,
+impl InteractionState {
+    pub fn if_enabled(&self, enabled: bool) -> Self {
+        if enabled {
+            *self
+        } else {
+            InteractionState::Disabled
+        }
+    }
+}
+
+#[derive(Default, PartialEq)]
+pub enum SelectedState {
+    #[default]
+    Unselected,
+    PartiallySelected,
+    Selected,
 }

docs/ui/states.md 🔗

@@ -0,0 +1,43 @@
+## Interaction State
+
+**Enabled**
+
+An enabled state communicates an interactive component or element.
+
+**Disabled**
+
+A disabled state communicates a inoperable component or element.
+
+**Hover**
+
+A hover state communicates when a user has placed a cursor above an interactive element.
+
+**Focused**
+
+A focused state communicates when a user has highlighted an element, using an input method such as a keyboard or voice.
+
+**Activated**
+
+An activated state communicates a highlighted destination, whether initiated by the user or by default.
+
+**Pressed**
+
+A pressed state communicates a user tap.
+
+**Dragged**
+
+A dragged state communicates when a user presses and moves an element.
+
+## Selected State
+
+**Unselected**
+
+dfa
+
+**Partially Selected**
+
+daf
+
+**Selected**
+
+dfa