Add `List` component

Marshall Bowers created

Change summary

crates/gpui3/src/color.rs                         |   2 
crates/storybook2/src/stories/components/panel.rs |   4 
crates/storybook2/src/stories/elements/label.rs   |   4 
crates/storybook2/src/stories/kitchen_sink.rs     |   4 
crates/storybook2/src/story_selector.rs           |   6 
crates/storybook2/src/storybook2.rs               |   1 
crates/storybook2/src/ui/components.rs            |   2 
crates/storybook2/src/ui/components/list.rs       | 519 +++++++++++++++++
crates/storybook2/src/ui/elements/label.rs        |   4 
crates/storybook2/src/ui/prelude.rs               | 243 +++++++
10 files changed, 777 insertions(+), 12 deletions(-)

Detailed changes

crates/gpui3/src/color.rs 🔗

@@ -4,7 +4,7 @@ use serde::de::{self, Deserialize, Deserializer, Visitor};
 use std::fmt;
 use std::num::ParseIntError;
 
-pub fn rgb(hex: u32) -> Rgba {
+pub fn rgb<C: From<Rgba>>(hex: u32) -> C {
     let r = ((hex >> 16) & 0xFF) as f32 / 255.0;
     let g = ((hex >> 8) & 0xFF) as f32 / 255.0;
     let b = (hex & 0xFF) as f32 / 255.0;

crates/storybook2/src/stories/components/panel.rs 🔗

@@ -6,11 +6,11 @@ use crate::ui::{Label, Panel};
 use crate::story::Story;
 
 #[derive(Element)]
-pub struct PanelStory<S: 'static + Send + Sync> {
+pub struct PanelStory<S: 'static + Send + Sync + Clone> {
     state_type: PhantomData<S>,
 }
 
-impl<S: 'static + Send + Sync> PanelStory<S> {
+impl<S: 'static + Send + Sync + Clone> PanelStory<S> {
     pub fn new() -> Self {
         Self {
             state_type: PhantomData,

crates/storybook2/src/stories/elements/label.rs 🔗

@@ -6,11 +6,11 @@ use crate::ui::Label;
 use crate::story::Story;
 
 #[derive(Element)]
-pub struct LabelStory<S: 'static + Send + Sync> {
+pub struct LabelStory<S: 'static + Send + Sync + Clone> {
     state_type: PhantomData<S>,
 }
 
-impl<S: 'static + Send + Sync> LabelStory<S> {
+impl<S: 'static + Send + Sync + Clone> LabelStory<S> {
     pub fn new() -> Self {
         Self {
             state_type: PhantomData,

crates/storybook2/src/stories/kitchen_sink.rs 🔗

@@ -7,11 +7,11 @@ use crate::story_selector::{ComponentStory, ElementStory};
 use crate::ui::prelude::*;
 
 #[derive(Element)]
-pub struct KitchenSinkStory<S: 'static + Send + Sync> {
+pub struct KitchenSinkStory<S: 'static + Send + Sync + Clone> {
     state_type: PhantomData<S>,
 }
 
-impl<S: 'static + Send + Sync> KitchenSinkStory<S> {
+impl<S: 'static + Send + Sync + Clone> KitchenSinkStory<S> {
     pub fn new() -> Self {
         Self {
             state_type: PhantomData,

crates/storybook2/src/story_selector.rs 🔗

@@ -18,7 +18,7 @@ pub enum ElementStory {
 }
 
 impl ElementStory {
-    pub fn story<S: 'static + Send + Sync>(&self) -> AnyElement<S> {
+    pub fn story<S: 'static + Send + Sync + Clone>(&self) -> AnyElement<S> {
         use crate::stories::elements;
 
         match self {
@@ -36,7 +36,7 @@ pub enum ComponentStory {
 }
 
 impl ComponentStory {
-    pub fn story<S: 'static + Send + Sync>(&self) -> AnyElement<S> {
+    pub fn story<S: 'static + Send + Sync + Clone>(&self) -> AnyElement<S> {
         use crate::stories::components;
 
         match self {
@@ -81,7 +81,7 @@ impl FromStr for StorySelector {
 }
 
 impl StorySelector {
-    pub fn story<S: 'static + Send + Sync>(&self) -> AnyElement<S> {
+    pub fn story<S: 'static + Send + Sync + Clone>(&self) -> AnyElement<S> {
         match self {
             Self::Element(element_story) => element_story.story(),
             Self::Component(component_story) => component_story.story(),

crates/storybook2/src/ui/components/list.rs 🔗

@@ -0,0 +1,519 @@
+use std::marker::PhantomData;
+
+use gpui3::{div, Div, Hsla, WindowContext};
+
+use crate::theme::theme;
+use crate::ui::prelude::*;
+use crate::ui::{
+    h_stack, token, v_stack, Avatar, Icon, IconColor, IconElement, IconSize, Label, LabelColor,
+    LabelSize,
+};
+
+#[derive(Clone, Copy, Default, Debug, PartialEq)]
+pub enum ListItemVariant {
+    /// The list item extends to the far left and right of the list.
+    #[default]
+    FullWidth,
+    Inset,
+}
+
+#[derive(Element, Clone)]
+pub struct ListHeader<S: 'static + Send + Sync + Clone> {
+    state_type: PhantomData<S>,
+    label: &'static str,
+    left_icon: Option<Icon>,
+    variant: ListItemVariant,
+    state: InteractionState,
+    toggleable: Toggleable,
+}
+
+impl<S: 'static + Send + Sync + Clone> ListHeader<S> {
+    pub fn new(label: &'static str) -> Self {
+        Self {
+            state_type: PhantomData,
+            label,
+            left_icon: None,
+            variant: ListItemVariant::default(),
+            state: InteractionState::default(),
+            toggleable: Toggleable::default(),
+        }
+    }
+
+    pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
+        self.toggleable = toggle.into();
+        self
+    }
+
+    pub fn set_toggleable(mut self, toggleable: Toggleable) -> Self {
+        self.toggleable = toggleable;
+        self
+    }
+
+    pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
+        self.left_icon = left_icon;
+        self
+    }
+
+    pub fn state(mut self, state: InteractionState) -> Self {
+        self.state = state;
+        self
+    }
+
+    fn disclosure_control(&self) -> Div<S> {
+        let is_toggleable = self.toggleable != Toggleable::NotToggleable;
+        let is_toggled = Toggleable::is_toggled(&self.toggleable);
+
+        match (is_toggleable, is_toggled) {
+            (false, _) => div(),
+            (_, true) => div().child(IconElement::new(Icon::ChevronRight).color(IconColor::Muted)),
+            (_, false) => div().child(IconElement::new(Icon::ChevronDown).size(IconSize::Small)),
+        }
+    }
+
+    fn background_color(&self, cx: &WindowContext) -> Hsla {
+        let theme = theme(cx);
+        let system_color = SystemColor::new();
+
+        match self.state {
+            InteractionState::Hovered => theme.lowest.base.hovered.background,
+            InteractionState::Active => theme.lowest.base.pressed.background,
+            InteractionState::Enabled => theme.lowest.on.default.background,
+            _ => system_color.transparent,
+        }
+    }
+
+    fn label_color(&self) -> LabelColor {
+        match self.state {
+            InteractionState::Disabled => LabelColor::Disabled,
+            _ => Default::default(),
+        }
+    }
+
+    fn icon_color(&self) -> IconColor {
+        match self.state {
+            InteractionState::Disabled => IconColor::Disabled,
+            _ => Default::default(),
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+        let token = token();
+        let system_color = SystemColor::new();
+        let background_color = self.background_color(cx);
+
+        let is_toggleable = self.toggleable != Toggleable::NotToggleable;
+        let is_toggled = Toggleable::is_toggled(&self.toggleable);
+
+        let disclosure_control = self.disclosure_control();
+
+        h_stack()
+            .flex_1()
+            .w_full()
+            .fill(background_color)
+            // .when(self.state == InteractionState::Focused, |this| {
+            //     this.border()
+            //         .border_color(theme.lowest.accent.default.border)
+            // })
+            .relative()
+            .py_1()
+            .child(
+                div()
+                    .h_6()
+                    // .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
+                    .flex()
+                    .flex_1()
+                    .w_full()
+                    .gap_1()
+                    .items_center()
+                    .justify_between()
+                    .child(
+                        div()
+                            .flex()
+                            .gap_1()
+                            .items_center()
+                            .children(self.left_icon.map(|i| {
+                                IconElement::new(i)
+                                    .color(IconColor::Muted)
+                                    .size(IconSize::Small)
+                            }))
+                            .child(
+                                Label::new(self.label.clone())
+                                    .color(LabelColor::Muted)
+                                    .size(LabelSize::Small),
+                            ),
+                    )
+                    .child(disclosure_control),
+            )
+    }
+}
+
+#[derive(Element, Clone)]
+pub struct ListSubHeader<S: 'static + Send + Sync + Clone> {
+    state_type: PhantomData<S>,
+    label: &'static str,
+    left_icon: Option<Icon>,
+    variant: ListItemVariant,
+}
+
+impl<S: 'static + Send + Sync + Clone> ListSubHeader<S> {
+    pub fn new(label: &'static str) -> Self {
+        Self {
+            state_type: PhantomData,
+            label,
+            left_icon: None,
+            variant: ListItemVariant::default(),
+        }
+    }
+
+    pub fn left_icon(mut self, left_icon: Option<Icon>) -> Self {
+        self.left_icon = left_icon;
+        self
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+        let token = token();
+
+        h_stack().flex_1().w_full().relative().py_1().child(
+            div()
+                .h_6()
+                // .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
+                .flex()
+                .flex_1()
+                .w_full()
+                .gap_1()
+                .items_center()
+                .justify_between()
+                .child(
+                    div()
+                        .flex()
+                        .gap_1()
+                        .items_center()
+                        .children(self.left_icon.map(|i| {
+                            IconElement::new(i)
+                                .color(IconColor::Muted)
+                                .size(IconSize::Small)
+                        }))
+                        .child(
+                            Label::new(self.label.clone())
+                                .color(LabelColor::Muted)
+                                .size(LabelSize::Small),
+                        ),
+                ),
+        )
+    }
+}
+
+#[derive(Clone)]
+pub enum LeftContent {
+    Icon(Icon),
+    Avatar(&'static str),
+}
+
+#[derive(Default, PartialEq, Copy, Clone)]
+pub enum ListEntrySize {
+    #[default]
+    Small,
+    Medium,
+}
+
+#[derive(Clone, Element)]
+pub enum ListItem<S: 'static + Send + Sync + Clone> {
+    Entry(ListEntry<S>),
+    Separator(ListSeparator<S>),
+    Header(ListSubHeader<S>),
+}
+
+impl<S: 'static + Send + Sync + Clone> From<ListEntry<S>> for ListItem<S> {
+    fn from(entry: ListEntry<S>) -> Self {
+        Self::Entry(entry)
+    }
+}
+
+impl<S: 'static + Send + Sync + Clone> From<ListSeparator<S>> for ListItem<S> {
+    fn from(entry: ListSeparator<S>) -> Self {
+        Self::Separator(entry)
+    }
+}
+
+impl<S: 'static + Send + Sync + Clone> From<ListSubHeader<S>> for ListItem<S> {
+    fn from(entry: ListSubHeader<S>) -> Self {
+        Self::Header(entry)
+    }
+}
+
+impl<S: 'static + Send + Sync + Clone> ListItem<S> {
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        match self {
+            ListItem::Entry(entry) => div().child(entry.render(cx)),
+            ListItem::Separator(separator) => div().child(separator.render(cx)),
+            ListItem::Header(header) => div().child(header.render(cx)),
+        }
+    }
+
+    pub fn new(label: Label<S>) -> Self {
+        Self::Entry(ListEntry::new(label))
+    }
+
+    pub fn as_entry(&mut self) -> Option<&mut ListEntry<S>> {
+        if let Self::Entry(entry) = self {
+            Some(entry)
+        } else {
+            None
+        }
+    }
+}
+
+#[derive(Element, Clone)]
+pub struct ListEntry<S: 'static + Send + Sync + Clone> {
+    disclosure_control_style: DisclosureControlVisibility,
+    indent_level: u32,
+    label: Label<S>,
+    left_content: Option<LeftContent>,
+    variant: ListItemVariant,
+    size: ListEntrySize,
+    state: InteractionState,
+    toggle: Option<ToggleState>,
+}
+
+impl<S: 'static + Send + Sync + Clone> ListEntry<S> {
+    pub fn new(label: Label<S>) -> Self {
+        Self {
+            disclosure_control_style: DisclosureControlVisibility::default(),
+            indent_level: 0,
+            label,
+            variant: ListItemVariant::default(),
+            left_content: None,
+            size: ListEntrySize::default(),
+            state: InteractionState::default(),
+            toggle: None,
+        }
+    }
+    pub fn variant(mut self, variant: ListItemVariant) -> Self {
+        self.variant = variant;
+        self
+    }
+    pub fn indent_level(mut self, indent_level: u32) -> Self {
+        self.indent_level = indent_level;
+        self
+    }
+
+    pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
+        self.toggle = Some(toggle);
+        self
+    }
+
+    pub fn left_content(mut self, left_content: LeftContent) -> Self {
+        self.left_content = Some(left_content);
+        self
+    }
+
+    pub fn left_icon(mut self, left_icon: Icon) -> Self {
+        self.left_content = Some(LeftContent::Icon(left_icon));
+        self
+    }
+
+    pub fn left_avatar(mut self, left_avatar: &'static str) -> Self {
+        self.left_content = Some(LeftContent::Avatar(left_avatar));
+        self
+    }
+
+    pub fn state(mut self, state: InteractionState) -> Self {
+        self.state = state;
+        self
+    }
+
+    pub fn size(mut self, size: ListEntrySize) -> Self {
+        self.size = size;
+        self
+    }
+
+    pub fn disclosure_control_style(
+        mut self,
+        disclosure_control_style: DisclosureControlVisibility,
+    ) -> Self {
+        self.disclosure_control_style = disclosure_control_style;
+        self
+    }
+
+    fn background_color(&self, cx: &WindowContext) -> Hsla {
+        let theme = theme(cx);
+        let system_color = SystemColor::new();
+
+        match self.state {
+            InteractionState::Hovered => theme.lowest.base.hovered.background,
+            InteractionState::Active => theme.lowest.base.pressed.background,
+            InteractionState::Enabled => theme.lowest.on.default.background,
+            _ => system_color.transparent,
+        }
+    }
+
+    fn label_color(&self) -> LabelColor {
+        match self.state {
+            InteractionState::Disabled => LabelColor::Disabled,
+            _ => Default::default(),
+        }
+    }
+
+    fn icon_color(&self) -> IconColor {
+        match self.state {
+            InteractionState::Disabled => IconColor::Disabled,
+            _ => Default::default(),
+        }
+    }
+
+    fn disclosure_control(&mut self, cx: &mut ViewContext<S>) -> Option<impl Element<State = S>> {
+        let theme = theme(cx);
+        let token = token();
+
+        let disclosure_control_icon = if let Some(ToggleState::Toggled) = self.toggle {
+            IconElement::new(Icon::ChevronDown)
+        } else {
+            IconElement::new(Icon::ChevronRight)
+        }
+        .color(IconColor::Muted)
+        .size(IconSize::Small);
+
+        match (self.toggle, self.disclosure_control_style) {
+            (Some(_), DisclosureControlVisibility::OnHover) => {
+                Some(
+                    div()
+                        .absolute()
+                        // .neg_left_5()
+                        .child(disclosure_control_icon),
+                )
+            }
+            (Some(_), DisclosureControlVisibility::Always) => {
+                Some(div().child(disclosure_control_icon))
+            }
+            (None, _) => None,
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+        let token = token();
+        let system_color = SystemColor::new();
+        let background_color = self.background_color(cx);
+
+        let left_content = match self.left_content {
+            Some(LeftContent::Icon(i)) => {
+                Some(h_stack().child(IconElement::new(i).size(IconSize::Small)))
+            }
+            Some(LeftContent::Avatar(src)) => Some(h_stack().child(Avatar::new(src))),
+            None => None,
+        };
+
+        let sized_item = match self.size {
+            ListEntrySize::Small => div().h_6(),
+            ListEntrySize::Medium => div().h_7(),
+        };
+
+        div()
+            .fill(background_color)
+            // .when(self.state == InteractionState::Focused, |this| {
+            //     this.border()
+            //         .border_color(theme.lowest.accent.default.border)
+            // })
+            .relative()
+            .py_1()
+            .child(
+                sized_item
+                    // .when(self.variant == ListItemVariant::Inset, |this| this.px_2())
+                    // .ml(rems(0.75 * self.indent_level as f32))
+                    .children((0..self.indent_level).map(|_| {
+                        div()
+                            // .w(token.list_indent_depth)
+                            .h_full()
+                            .flex()
+                            .justify_center()
+                            .child(h_stack().child(div().w_px().h_full()).child(
+                                div().w_px().h_full().fill(theme.middle.base.default.border),
+                            ))
+                    }))
+                    .flex()
+                    .gap_1()
+                    .items_center()
+                    .relative()
+                    .children(self.disclosure_control(cx))
+                    .children(left_content)
+                    .child(self.label.clone()),
+            )
+    }
+}
+
+#[derive(Clone, Element)]
+pub struct ListSeparator<S: 'static + Send + Sync> {
+    state_type: PhantomData<S>,
+}
+
+impl<S: 'static + Send + Sync> ListSeparator<S> {
+    pub fn new() -> Self {
+        Self {
+            state_type: PhantomData,
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+
+        div().h_px().w_full().fill(theme.lowest.base.default.border)
+    }
+}
+
+#[derive(Element)]
+pub struct List<S: 'static + Send + Sync + Clone> {
+    items: Vec<ListItem<S>>,
+    empty_message: &'static str,
+    header: Option<ListHeader<S>>,
+    toggleable: Toggleable,
+}
+
+impl<S: 'static + Send + Sync + Clone> List<S> {
+    pub fn new(items: Vec<ListItem<S>>) -> Self {
+        Self {
+            items,
+            empty_message: "No items",
+            header: None,
+            toggleable: Toggleable::default(),
+        }
+    }
+
+    pub fn empty_message(mut self, empty_message: &'static str) -> Self {
+        self.empty_message = empty_message;
+        self
+    }
+
+    pub fn header(mut self, header: ListHeader<S>) -> Self {
+        self.header = Some(header);
+        self
+    }
+
+    pub fn set_toggle(mut self, toggle: ToggleState) -> Self {
+        self.toggleable = toggle.into();
+        self
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+        let token = token();
+        let is_toggleable = self.toggleable != Toggleable::NotToggleable;
+        let is_toggled = Toggleable::is_toggled(&self.toggleable);
+
+        let list_content = match (self.items.is_empty(), is_toggled) {
+            (_, false) => div(),
+            (false, _) => div().children(self.items.iter().cloned()),
+            (true, _) => div().child(Label::new(self.empty_message).color(LabelColor::Muted)),
+        };
+
+        v_stack()
+            .py_1()
+            .children(
+                self.header
+                    .clone()
+                    .map(|header| header.set_toggleable(self.toggleable)),
+            )
+            .child(list_content)
+    }
+}

crates/storybook2/src/ui/elements/label.rs 🔗

@@ -46,7 +46,7 @@ pub enum LabelSize {
 }
 
 #[derive(Element, Clone)]
-pub struct Label<S: 'static + Send + Sync> {
+pub struct Label<S: 'static + Send + Sync + Clone> {
     state_type: PhantomData<S>,
     label: String,
     color: LabelColor,
@@ -55,7 +55,7 @@ pub struct Label<S: 'static + Send + Sync> {
     strikethrough: bool,
 }
 
-impl<S: 'static + Send + Sync> Label<S> {
+impl<S: 'static + Send + Sync + Clone> Label<S> {
     pub fn new<L>(label: L) -> Self
     where
         L: Into<String>,

crates/storybook2/src/ui/prelude.rs 🔗

@@ -1,14 +1,257 @@
 pub use gpui3::{
     div, Element, IntoAnyElement, ParentElement, ScrollState, StyleHelpers, ViewContext,
+    WindowContext,
 };
 
 pub use crate::ui::{HackyChildren, HackyChildrenPayload};
 
+use gpui3::{hsla, rgb, Hsla};
 use strum::EnumIter;
 
+use crate::theme::{theme, Theme};
+
+#[derive(Default)]
+pub struct SystemColor {
+    pub transparent: Hsla,
+    pub mac_os_traffic_light_red: Hsla,
+    pub mac_os_traffic_light_yellow: Hsla,
+    pub mac_os_traffic_light_green: Hsla,
+}
+
+impl SystemColor {
+    pub fn new() -> SystemColor {
+        SystemColor {
+            transparent: hsla(0.0, 0.0, 0.0, 0.0),
+            mac_os_traffic_light_red: rgb::<Hsla>(0xEC695E),
+            mac_os_traffic_light_yellow: rgb::<Hsla>(0xF4BF4F),
+            mac_os_traffic_light_green: rgb::<Hsla>(0x62C554),
+        }
+    }
+    pub fn color(&self) -> Hsla {
+        self.transparent
+    }
+}
+
+#[derive(Default, PartialEq, EnumIter, Clone, Copy)]
+pub enum HighlightColor {
+    #[default]
+    Default,
+    Comment,
+    String,
+    Function,
+    Keyword,
+}
+
+impl HighlightColor {
+    pub fn hsla(&self, theme: &Theme) -> Hsla {
+        let system_color = SystemColor::new();
+
+        match self {
+            Self::Default => theme
+                .syntax
+                .get("primary")
+                .expect("no theme.syntax.primary")
+                .clone(),
+            Self::Comment => theme
+                .syntax
+                .get("comment")
+                .expect("no theme.syntax.comment")
+                .clone(),
+            Self::String => theme
+                .syntax
+                .get("string")
+                .expect("no theme.syntax.string")
+                .clone(),
+            Self::Function => theme
+                .syntax
+                .get("function")
+                .expect("no theme.syntax.function")
+                .clone(),
+            Self::Keyword => theme
+                .syntax
+                .get("keyword")
+                .expect("no theme.syntax.keyword")
+                .clone(),
+        }
+    }
+}
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
+pub enum FileSystemStatus {
+    #[default]
+    None,
+    Conflict,
+    Deleted,
+}
+
+impl FileSystemStatus {
+    pub fn to_string(&self) -> String {
+        match self {
+            Self::None => "None".to_string(),
+            Self::Conflict => "Conflict".to_string(),
+            Self::Deleted => "Deleted".to_string(),
+        }
+    }
+}
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
+pub enum GitStatus {
+    #[default]
+    None,
+    Created,
+    Modified,
+    Deleted,
+    Conflict,
+    Renamed,
+}
+
+impl GitStatus {
+    pub fn to_string(&self) -> String {
+        match self {
+            Self::None => "None".to_string(),
+            Self::Created => "Created".to_string(),
+            Self::Modified => "Modified".to_string(),
+            Self::Deleted => "Deleted".to_string(),
+            Self::Conflict => "Conflict".to_string(),
+            Self::Renamed => "Renamed".to_string(),
+        }
+    }
+
+    pub fn hsla(&self, cx: &WindowContext) -> Hsla {
+        let theme = theme(cx);
+        let system_color = SystemColor::new();
+
+        match self {
+            Self::None => system_color.transparent,
+            Self::Created => theme.lowest.positive.default.foreground,
+            Self::Modified => theme.lowest.warning.default.foreground,
+            Self::Deleted => theme.lowest.negative.default.foreground,
+            Self::Conflict => theme.lowest.warning.default.foreground,
+            Self::Renamed => theme.lowest.accent.default.foreground,
+        }
+    }
+}
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
+pub enum DiagnosticStatus {
+    #[default]
+    None,
+    Error,
+    Warning,
+    Info,
+}
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
+pub enum IconSide {
+    #[default]
+    Left,
+    Right,
+}
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
+pub enum OrderMethod {
+    #[default]
+    Ascending,
+    Descending,
+    MostRecent,
+}
+
 #[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
 pub enum Shape {
     #[default]
     Circle,
     RoundedRectangle,
 }
+
+#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, EnumIter)]
+pub enum DisclosureControlVisibility {
+    #[default]
+    OnHover,
+    Always,
+}
+
+#[derive(Default, PartialEq, Copy, Clone, EnumIter, strum::Display)]
+pub enum InteractionState {
+    #[default]
+    Enabled,
+    Hovered,
+    Active,
+    Focused,
+    Disabled,
+}
+
+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,
+}
+
+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Toggleable {
+    Toggleable(ToggleState),
+    #[default]
+    NotToggleable,
+}
+
+impl Toggleable {
+    pub fn is_toggled(&self) -> bool {
+        match self {
+            Self::Toggleable(ToggleState::Toggled) => true,
+            _ => false,
+        }
+    }
+}
+
+impl From<ToggleState> for Toggleable {
+    fn from(state: ToggleState) -> Self {
+        Self::Toggleable(state)
+    }
+}
+
+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
+pub enum ToggleState {
+    /// The "on" state of a toggleable element.
+    ///
+    /// Example:
+    ///     - A collasable list that is currently expanded
+    ///     - A toggle button that is currently on.
+    Toggled,
+    /// The "off" state of a toggleable element.
+    ///
+    /// Example:
+    ///     - A collasable list that is currently collapsed
+    ///     - A toggle button that is currently off.
+    #[default]
+    NotToggled,
+}
+
+impl From<Toggleable> for ToggleState {
+    fn from(toggleable: Toggleable) -> Self {
+        match toggleable {
+            Toggleable::Toggleable(state) => state,
+            Toggleable::NotToggleable => ToggleState::NotToggled,
+        }
+    }
+}
+
+impl From<bool> for ToggleState {
+    fn from(toggled: bool) -> Self {
+        if toggled {
+            ToggleState::Toggled
+        } else {
+            ToggleState::NotToggled
+        }
+    }
+}