Give hover state to picker items, keystrokes in command palette

Max Brunsfeld created

Change summary

assets/themes/cave-dark.json                  | 37 +++++++++-----------
assets/themes/cave-light.json                 | 37 +++++++++-----------
assets/themes/dark.json                       | 37 +++++++++-----------
assets/themes/light.json                      | 37 +++++++++-----------
assets/themes/solarized-dark.json             | 37 +++++++++-----------
assets/themes/solarized-light.json            | 37 +++++++++-----------
assets/themes/sulphurpool-dark.json           | 37 +++++++++-----------
assets/themes/sulphurpool-light.json          | 37 +++++++++-----------
crates/command_palette/src/command_palette.rs | 18 +++++----
crates/diagnostics/src/items.rs               | 14 +------
crates/file_finder/src/file_finder.rs         | 14 ++++---
crates/outline/src/outline.rs                 | 14 ++++---
crates/picker/src/picker.rs                   | 38 ++++++++++++--------
crates/project_symbols/src/project_symbols.rs | 23 +++++++-----
crates/theme/src/theme.rs                     | 34 +++++++++++-------
crates/theme_selector/src/theme_selector.rs   | 14 ++++---
crates/workspace/src/sidebar.rs               |  8 ---
crates/workspace/src/workspace.rs             | 27 +++++---------
styles/src/styleTree/commandPalette.ts        |  3 +
styles/src/styleTree/picker.ts                | 38 +++++++++-----------
20 files changed, 261 insertions(+), 280 deletions(-)

Detailed changes

assets/themes/cave-dark.json 🔗

@@ -21,28 +21,18 @@
         "color": "#576ddb",
         "weight": "bold",
         "size": 14
-      }
-    },
-    "active_item": {
-      "padding": {
-        "bottom": 4,
-        "left": 12,
-        "right": 12,
-        "top": 4
       },
-      "corner_radius": 8,
-      "text": {
-        "family": "Zed Sans",
-        "color": "#e2dfe7",
-        "size": 14
-      },
-      "highlight_text": {
-        "family": "Zed Sans",
-        "color": "#576ddb",
-        "weight": "bold",
-        "size": 14
+      "active": {
+        "background": "#5852607a",
+        "text": {
+          "family": "Zed Sans",
+          "color": "#e2dfe7",
+          "size": 14
+        }
       },
-      "background": "#5852607a"
+      "hover": {
+        "background": "#58526052"
+      }
     },
     "border": {
       "color": "#19171c",
@@ -906,6 +896,13 @@
       },
       "margin": {
         "left": 2
+      },
+      "active": {
+        "text": {
+          "family": "Zed Mono",
+          "color": "#efecf4",
+          "size": 12
+        }
       }
     }
   },

assets/themes/cave-light.json 🔗

@@ -21,28 +21,18 @@
         "color": "#576ddb",
         "weight": "bold",
         "size": 14
-      }
-    },
-    "active_item": {
-      "padding": {
-        "bottom": 4,
-        "left": 12,
-        "right": 12,
-        "top": 4
       },
-      "corner_radius": 8,
-      "text": {
-        "family": "Zed Sans",
-        "color": "#26232a",
-        "size": 14
-      },
-      "highlight_text": {
-        "family": "Zed Sans",
-        "color": "#576ddb",
-        "weight": "bold",
-        "size": 14
+      "active": {
+        "background": "#8b87922e",
+        "text": {
+          "family": "Zed Sans",
+          "color": "#26232a",
+          "size": 14
+        }
       },
-      "background": "#8b87922e"
+      "hover": {
+        "background": "#8b87921f"
+      }
     },
     "border": {
       "color": "#efecf4",
@@ -906,6 +896,13 @@
       },
       "margin": {
         "left": 2
+      },
+      "active": {
+        "text": {
+          "family": "Zed Mono",
+          "color": "#19171c",
+          "size": 12
+        }
       }
     }
   },

assets/themes/dark.json 🔗

@@ -21,28 +21,18 @@
         "color": "#4f8ff7",
         "weight": "bold",
         "size": 14
-      }
-    },
-    "active_item": {
-      "padding": {
-        "bottom": 4,
-        "left": 12,
-        "right": 12,
-        "top": 4
       },
-      "corner_radius": 8,
-      "text": {
-        "family": "Zed Sans",
-        "color": "#f1f1f1",
-        "size": 14
-      },
-      "highlight_text": {
-        "family": "Zed Sans",
-        "color": "#4f8ff7",
-        "weight": "bold",
-        "size": 14
+      "active": {
+        "background": "#2b2b2b",
+        "text": {
+          "family": "Zed Sans",
+          "color": "#f1f1f1",
+          "size": 14
+        }
       },
-      "background": "#2b2b2b"
+      "hover": {
+        "background": "#232323"
+      }
     },
     "border": {
       "color": "#070707",
@@ -906,6 +896,13 @@
       },
       "margin": {
         "left": 2
+      },
+      "active": {
+        "text": {
+          "family": "Zed Mono",
+          "color": "#ffffff",
+          "size": 12
+        }
       }
     }
   },

assets/themes/light.json 🔗

@@ -21,28 +21,18 @@
         "color": "#484bed",
         "weight": "bold",
         "size": 14
-      }
-    },
-    "active_item": {
-      "padding": {
-        "bottom": 4,
-        "left": 12,
-        "right": 12,
-        "top": 4
       },
-      "corner_radius": 8,
-      "text": {
-        "family": "Zed Sans",
-        "color": "#2b2b2b",
-        "size": 14
-      },
-      "highlight_text": {
-        "family": "Zed Sans",
-        "color": "#484bed",
-        "weight": "bold",
-        "size": 14
+      "active": {
+        "background": "#e3e3e3",
+        "text": {
+          "family": "Zed Sans",
+          "color": "#2b2b2b",
+          "size": 14
+        }
       },
-      "background": "#e3e3e3"
+      "hover": {
+        "background": "#eaeaea"
+      }
     },
     "border": {
       "color": "#d5d5d5",
@@ -906,6 +896,13 @@
       },
       "margin": {
         "left": 2
+      },
+      "active": {
+        "text": {
+          "family": "Zed Mono",
+          "color": "#000000",
+          "size": 12
+        }
       }
     }
   },

assets/themes/solarized-dark.json 🔗

@@ -21,28 +21,18 @@
         "color": "#268bd2",
         "weight": "bold",
         "size": 14
-      }
-    },
-    "active_item": {
-      "padding": {
-        "bottom": 4,
-        "left": 12,
-        "right": 12,
-        "top": 4
       },
-      "corner_radius": 8,
-      "text": {
-        "family": "Zed Sans",
-        "color": "#eee8d5",
-        "size": 14
-      },
-      "highlight_text": {
-        "family": "Zed Sans",
-        "color": "#268bd2",
-        "weight": "bold",
-        "size": 14
+      "active": {
+        "background": "#586e757a",
+        "text": {
+          "family": "Zed Sans",
+          "color": "#eee8d5",
+          "size": 14
+        }
       },
-      "background": "#586e757a"
+      "hover": {
+        "background": "#586e7552"
+      }
     },
     "border": {
       "color": "#002b36",
@@ -906,6 +896,13 @@
       },
       "margin": {
         "left": 2
+      },
+      "active": {
+        "text": {
+          "family": "Zed Mono",
+          "color": "#fdf6e3",
+          "size": 12
+        }
       }
     }
   },

assets/themes/solarized-light.json 🔗

@@ -21,28 +21,18 @@
         "color": "#268bd2",
         "weight": "bold",
         "size": 14
-      }
-    },
-    "active_item": {
-      "padding": {
-        "bottom": 4,
-        "left": 12,
-        "right": 12,
-        "top": 4
       },
-      "corner_radius": 8,
-      "text": {
-        "family": "Zed Sans",
-        "color": "#073642",
-        "size": 14
-      },
-      "highlight_text": {
-        "family": "Zed Sans",
-        "color": "#268bd2",
-        "weight": "bold",
-        "size": 14
+      "active": {
+        "background": "#93a1a12e",
+        "text": {
+          "family": "Zed Sans",
+          "color": "#073642",
+          "size": 14
+        }
       },
-      "background": "#93a1a12e"
+      "hover": {
+        "background": "#93a1a11f"
+      }
     },
     "border": {
       "color": "#fdf6e3",
@@ -906,6 +896,13 @@
       },
       "margin": {
         "left": 2
+      },
+      "active": {
+        "text": {
+          "family": "Zed Mono",
+          "color": "#002b36",
+          "size": 12
+        }
       }
     }
   },

assets/themes/sulphurpool-dark.json 🔗

@@ -21,28 +21,18 @@
         "color": "#3d8fd1",
         "weight": "bold",
         "size": 14
-      }
-    },
-    "active_item": {
-      "padding": {
-        "bottom": 4,
-        "left": 12,
-        "right": 12,
-        "top": 4
       },
-      "corner_radius": 8,
-      "text": {
-        "family": "Zed Sans",
-        "color": "#dfe2f1",
-        "size": 14
-      },
-      "highlight_text": {
-        "family": "Zed Sans",
-        "color": "#3d8fd1",
-        "weight": "bold",
-        "size": 14
+      "active": {
+        "background": "#5e66877a",
+        "text": {
+          "family": "Zed Sans",
+          "color": "#dfe2f1",
+          "size": 14
+        }
       },
-      "background": "#5e66877a"
+      "hover": {
+        "background": "#5e668752"
+      }
     },
     "border": {
       "color": "#202746",
@@ -906,6 +896,13 @@
       },
       "margin": {
         "left": 2
+      },
+      "active": {
+        "text": {
+          "family": "Zed Mono",
+          "color": "#f5f7ff",
+          "size": 12
+        }
       }
     }
   },

assets/themes/sulphurpool-light.json 🔗

@@ -21,28 +21,18 @@
         "color": "#3d8fd1",
         "weight": "bold",
         "size": 14
-      }
-    },
-    "active_item": {
-      "padding": {
-        "bottom": 4,
-        "left": 12,
-        "right": 12,
-        "top": 4
       },
-      "corner_radius": 8,
-      "text": {
-        "family": "Zed Sans",
-        "color": "#293256",
-        "size": 14
-      },
-      "highlight_text": {
-        "family": "Zed Sans",
-        "color": "#3d8fd1",
-        "weight": "bold",
-        "size": 14
+      "active": {
+        "background": "#979db42e",
+        "text": {
+          "family": "Zed Sans",
+          "color": "#293256",
+          "size": 14
+        }
       },
-      "background": "#979db42e"
+      "hover": {
+        "background": "#979db41f"
+      }
     },
     "border": {
       "color": "#f5f7ff",
@@ -906,6 +896,13 @@
       },
       "margin": {
         "left": 2
+      },
+      "active": {
+        "text": {
+          "family": "Zed Mono",
+          "color": "#202746",
+          "size": 12
+        }
       }
     }
   },

crates/command_palette/src/command_palette.rs 🔗

@@ -1,7 +1,7 @@
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
     actions,
-    elements::{ChildView, Flex, Label, ParentElement},
+    elements::{ChildView, Flex, Label, MouseState, ParentElement},
     keymap::Keystroke,
     Action, Element, Entity, MutableAppContext, View, ViewContext, ViewHandle,
 };
@@ -200,17 +200,19 @@ impl PickerDelegate for CommandPalette {
         }
     }
 
-    fn render_match(&self, ix: usize, selected: bool, cx: &gpui::AppContext) -> gpui::ElementBox {
+    fn render_match(
+        &self,
+        ix: usize,
+        mouse_state: &MouseState,
+        selected: bool,
+        cx: &gpui::AppContext,
+    ) -> gpui::ElementBox {
         let mat = &self.matches[ix];
         let command = &self.actions[mat.candidate_id];
         let settings = cx.global::<Settings>();
         let theme = &settings.theme;
-        let style = if selected {
-            &theme.picker.active_item
-        } else {
-            &theme.picker.item
-        };
-        let key_style = &theme.command_palette.key;
+        let style = theme.picker.item.style_for(mouse_state, selected);
+        let key_style = &theme.command_palette.key.style_for(mouse_state, selected);
         let keystroke_spacing = theme.command_palette.keystroke_spacing;
 
         Flex::row()

crates/diagnostics/src/items.rs 🔗

@@ -95,12 +95,8 @@ impl View for DiagnosticIndicator {
                     .theme
                     .workspace
                     .status_bar
-                    .diagnostic_summary;
-                let style = if state.hovered {
-                    style.hover()
-                } else {
-                    &style.default
-                };
+                    .diagnostic_summary
+                    .style_for(state, false);
 
                 let mut summary_row = Flex::row();
                 if self.summary.error_count > 0 {
@@ -190,11 +186,7 @@ impl View for DiagnosticIndicator {
                 MouseEventHandler::new::<Message, _, _>(1, cx, |state, _| {
                     Label::new(
                         diagnostic.message.split('\n').next().unwrap().to_string(),
-                        if state.hovered {
-                            message_style.hover().text.clone()
-                        } else {
-                            message_style.default.text.clone()
-                        },
+                        message_style.style_for(state, false).text.clone(),
                     )
                     .aligned()
                     .contained()

crates/file_finder/src/file_finder.rs 🔗

@@ -223,14 +223,16 @@ impl PickerDelegate for FileFinder {
         cx.emit(Event::Dismissed);
     }
 
-    fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox {
+    fn render_match(
+        &self,
+        ix: usize,
+        mouse_state: &MouseState,
+        selected: bool,
+        cx: &AppContext,
+    ) -> ElementBox {
         let path_match = &self.matches[ix];
         let settings = cx.global::<Settings>();
-        let style = if selected {
-            &settings.theme.picker.active_item
-        } else {
-            &settings.theme.picker.item
-        };
+        let style = settings.theme.picker.item.style_for(mouse_state, selected);
         let (file_name, file_name_positions, full_path, full_path_positions) =
             self.labels_for_match(path_match);
         Flex::column()

crates/outline/src/outline.rs 🔗

@@ -228,14 +228,16 @@ impl PickerDelegate for OutlineView {
         cx.emit(Event::Dismissed);
     }
 
-    fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox {
+    fn render_match(
+        &self,
+        ix: usize,
+        mouse_state: &MouseState,
+        selected: bool,
+        cx: &AppContext,
+    ) -> ElementBox {
         let settings = cx.global::<Settings>();
         let string_match = &self.matches[ix];
-        let style = if selected {
-            &settings.theme.picker.active_item
-        } else {
-            &settings.theme.picker.item
-        };
+        let style = settings.theme.picker.item.style_for(mouse_state, selected);
         let outline_item = &self.outline.items[string_match.candidate_id];
 
         Text::new(outline_item.text.clone(), style.label.text.clone())

crates/picker/src/picker.rs 🔗

@@ -1,12 +1,14 @@
 use editor::Editor;
 use gpui::{
     elements::{
-        ChildView, EventHandler, Flex, Label, ParentElement, ScrollTarget, UniformList,
-        UniformListState,
+        ChildView, Flex, Label, MouseEventHandler, MouseState, ParentElement, ScrollTarget,
+        UniformList, UniformListState,
     },
     geometry::vector::{vec2f, Vector2F},
-    keymap, AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, Task,
-    View, ViewContext, ViewHandle, WeakViewHandle,
+    keymap,
+    platform::CursorStyle,
+    AppContext, Axis, Element, ElementBox, Entity, MutableAppContext, RenderContext, Task, View,
+    ViewContext, ViewHandle, WeakViewHandle,
 };
 use settings::Settings;
 use std::cmp;
@@ -29,7 +31,13 @@ pub trait PickerDelegate: View {
     fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> Task<()>;
     fn confirm(&mut self, cx: &mut ViewContext<Self>);
     fn dismiss(&mut self, cx: &mut ViewContext<Self>);
-    fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox;
+    fn render_match(
+        &self,
+        ix: usize,
+        state: &MouseState,
+        selected: bool,
+        cx: &AppContext,
+    ) -> ElementBox;
     fn center_selection_after_match_updates(&self) -> bool {
         false
     }
@@ -73,18 +81,18 @@ impl<D: PickerDelegate> View for Picker<D> {
                         self.list_state.clone(),
                         match_count,
                         move |mut range, items, cx| {
-                            let cx = cx.as_ref();
                             let delegate = delegate.upgrade(cx).unwrap();
-                            let delegate = delegate.read(cx);
-                            let selected_ix = delegate.selected_index();
-                            range.end = cmp::min(range.end, delegate.match_count());
+                            let selected_ix = delegate.read(cx).selected_index();
+                            range.end = cmp::min(range.end, delegate.read(cx).match_count());
                             items.extend(range.map(move |ix| {
-                                EventHandler::new(delegate.render_match(ix, ix == selected_ix, cx))
-                                    .on_mouse_down(move |cx| {
-                                        cx.dispatch_action(SelectIndex(ix));
-                                        true
-                                    })
-                                    .boxed()
+                                MouseEventHandler::new::<D, _, _>(ix, cx, |state, cx| {
+                                    delegate
+                                        .read(cx)
+                                        .render_match(ix, state, ix == selected_ix, cx)
+                                })
+                                .on_mouse_down(move |cx| cx.dispatch_action(SelectIndex(ix)))
+                                .with_cursor_style(CursorStyle::PointingHand)
+                                .boxed()
                             }));
                         },
                     )

crates/project_symbols/src/project_symbols.rs 🔗

@@ -220,14 +220,17 @@ impl PickerDelegate for ProjectSymbolsView {
         Task::ready(())
     }
 
-    fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox {
+    fn render_match(
+        &self,
+        ix: usize,
+        mouse_state: &MouseState,
+        selected: bool,
+        cx: &AppContext,
+    ) -> ElementBox {
         let string_match = &self.matches[ix];
         let settings = cx.global::<Settings>();
-        let style = if selected {
-            &settings.theme.picker.active_item
-        } else {
-            &settings.theme.picker.item
-        };
+        let style = &settings.theme.picker.item;
+        let current_style = style.style_for(mouse_state, selected);
         let symbol = &self.symbols[string_match.candidate_id];
         let syntax_runs = styled_runs_for_code_label(&symbol.label, &settings.theme.editor.syntax);
 
@@ -246,11 +249,11 @@ impl PickerDelegate for ProjectSymbolsView {
 
         Flex::column()
             .with_child(
-                Text::new(symbol.label.text.clone(), style.label.text.clone())
+                Text::new(symbol.label.text.clone(), current_style.label.text.clone())
                     .with_soft_wrap(false)
                     .with_highlights(combine_syntax_and_fuzzy_match_highlights(
                         &symbol.label.text,
-                        style.label.text.clone().into(),
+                        current_style.label.text.clone().into(),
                         syntax_runs,
                         &string_match.positions,
                     ))
@@ -259,10 +262,10 @@ impl PickerDelegate for ProjectSymbolsView {
             .with_child(
                 // Avoid styling the path differently when it is selected, since
                 // the symbol's syntax highlighting doesn't change when selected.
-                Label::new(path.to_string(), settings.theme.picker.item.label.clone()).boxed(),
+                Label::new(path.to_string(), style.default.label.clone()).boxed(),
             )
             .contained()
-            .with_style(style.container)
+            .with_style(current_style.container)
             .boxed()
     }
 }

crates/theme/src/theme.rs 🔗

@@ -2,7 +2,7 @@ mod theme_registry;
 
 use gpui::{
     color::Color,
-    elements::{ContainerStyle, ImageStyle, LabelStyle},
+    elements::{ContainerStyle, ImageStyle, LabelStyle, MouseState},
     fonts::{HighlightStyle, TextStyle},
     Border,
 };
@@ -229,7 +229,7 @@ pub struct ProjectPanelEntry {
 
 #[derive(Debug, Deserialize, Default)]
 pub struct CommandPalette {
-    pub key: ContainedLabel,
+    pub key: Interactive<ContainedLabel>,
     pub keystroke_spacing: f32,
 }
 
@@ -293,8 +293,7 @@ pub struct Picker {
     pub container: ContainerStyle,
     pub empty: ContainedLabel,
     pub input_editor: FieldEditor,
-    pub item: ContainedLabel,
-    pub active_item: ContainedLabel,
+    pub item: Interactive<ContainedLabel>,
 }
 
 #[derive(Clone, Debug, Deserialize, Default)]
@@ -419,16 +418,23 @@ pub struct Interactive<T> {
 }
 
 impl<T> Interactive<T> {
-    pub fn active(&self) -> &T {
-        self.active.as_ref().unwrap_or(&self.default)
-    }
-
-    pub fn hover(&self) -> &T {
-        self.hover.as_ref().unwrap_or(&self.default)
-    }
-
-    pub fn active_hover(&self) -> &T {
-        self.active_hover.as_ref().unwrap_or(self.active())
+    pub fn style_for(&self, state: &MouseState, active: bool) -> &T {
+        if active {
+            if state.hovered {
+                self.active_hover
+                    .as_ref()
+                    .or(self.active.as_ref())
+                    .unwrap_or(&self.default)
+            } else {
+                self.active.as_ref().unwrap_or(&self.default)
+            }
+        } else {
+            if state.hovered {
+                self.hover.as_ref().unwrap_or(&self.default)
+            } else {
+                &self.default
+            }
+        }
     }
 }
 

crates/theme_selector/src/theme_selector.rs 🔗

@@ -203,15 +203,17 @@ impl PickerDelegate for ThemeSelector {
         })
     }
 
-    fn render_match(&self, ix: usize, selected: bool, cx: &AppContext) -> ElementBox {
+    fn render_match(
+        &self,
+        ix: usize,
+        mouse_state: &MouseState,
+        selected: bool,
+        cx: &AppContext,
+    ) -> ElementBox {
         let settings = cx.global::<Settings>();
         let theme = &settings.theme;
         let theme_match = &self.matches[ix];
-        let style = if selected {
-            &theme.picker.active_item
-        } else {
-            &theme.picker.item
-        };
+        let style = theme.picker.item.style_for(mouse_state, selected);
 
         Label::new(theme_match.string.clone(), style.label.clone())
             .with_highlights(theme_match.positions.clone())

crates/workspace/src/sidebar.rs 🔗

@@ -193,13 +193,7 @@ impl View for SidebarButtons {
         Flex::row()
             .with_children(items.iter().enumerate().map(|(ix, item)| {
                 MouseEventHandler::new::<Self, _, _>(ix, cx, move |state, _| {
-                    let style = if Some(ix) == active_ix {
-                        item_style.active()
-                    } else if state.hovered {
-                        item_style.hover()
-                    } else {
-                        &item_style.default
-                    };
+                    let style = item_style.style_for(state, Some(ix) == active_ix);
                     Svg::new(item.icon_path)
                         .with_color(style.icon_color)
                         .constrained()

crates/workspace/src/workspace.rs 🔗

@@ -1574,11 +1574,11 @@ impl Workspace {
         } else {
             Some(
                 MouseEventHandler::new::<Authenticate, _, _>(0, cx, |state, _| {
-                    let style = if state.hovered {
-                        &theme.workspace.titlebar.sign_in_prompt.hover()
-                    } else {
-                        &theme.workspace.titlebar.sign_in_prompt.default
-                    };
+                    let style = theme
+                        .workspace
+                        .titlebar
+                        .sign_in_prompt
+                        .style_for(state, false);
                     Label::new("Sign in".to_string(), style.text.clone())
                         .contained()
                         .with_style(style.container)
@@ -1649,18 +1649,11 @@ impl Workspace {
         {
             Some(
                 MouseEventHandler::new::<ToggleShare, _, _>(0, cx, |state, cx| {
-                    let style = &theme.workspace.titlebar.share_icon;
-                    let style = if self.project().read(cx).is_shared() {
-                        if state.hovered {
-                            style.active_hover()
-                        } else {
-                            &style.active()
-                        }
-                    } else if state.hovered {
-                        &style.active()
-                    } else {
-                        &style.default
-                    };
+                    let style = &theme
+                        .workspace
+                        .titlebar
+                        .share_icon
+                        .style_for(state, self.project().read(cx).is_shared());
                     Svg::new("icons/share.svg")
                         .with_color(style.color)
                         .constrained()

styles/src/styleTree/commandPalette.ts 🔗

@@ -18,6 +18,9 @@ export default function commandPalette(theme: Theme) {
       margin: {
         left: 2
       },
+      active: {
+        text: text(theme, "mono", "active", { size: "xs" }),
+      }
     }
   }
 }

styles/src/styleTree/picker.ts 🔗

@@ -2,30 +2,28 @@ import Theme from "../themes/theme";
 import { backgroundColor, border, player, shadow, text } from "./components";
 
 export default function picker(theme: Theme) {
-  const item = {
-    padding: {
-      bottom: 4,
-      left: 12,
-      right: 12,
-      top: 4,
-    },
-    cornerRadius: 8,
-    text: text(theme, "sans", "secondary"),
-    highlightText: text(theme, "sans", "feature", { weight: "bold" }),
-  };
-
-  const activeItem = {
-    ...item,
-    background: backgroundColor(theme, 300, "active"),
-    text: text(theme, "sans", "primary"),
-  };
-
   return {
     background: backgroundColor(theme, 300),
     cornerRadius: 8,
     padding: 8,
-    item,
-    activeItem,
+    item: {
+      padding: {
+        bottom: 4,
+        left: 12,
+        right: 12,
+        top: 4,
+      },
+      cornerRadius: 8,
+      text: text(theme, "sans", "secondary"),
+      highlightText: text(theme, "sans", "feature", { weight: "bold" }),
+      active: {
+        background: backgroundColor(theme, 300, "active"),
+        text: text(theme, "sans", "primary"),
+      },
+      hover: {
+        background: backgroundColor(theme, 300, "hovered"),
+      }
+    },
     border: border(theme, "primary"),
     empty: {
       text: text(theme, "sans", "placeholder"),