story_selector.rs

  1use std::str::FromStr;
  2use std::sync::OnceLock;
  3
  4use crate::stories::*;
  5use anyhow::anyhow;
  6use clap::builder::PossibleValue;
  7use clap::ValueEnum;
  8use gpui::{AnyView, VisualContext};
  9use strum::{EnumIter, EnumString, IntoEnumIterator};
 10use ui::prelude::*;
 11
 12#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
 13#[strum(serialize_all = "snake_case")]
 14pub enum ComponentStory {
 15    AutoHeightEditor,
 16    Avatar,
 17    Button,
 18    Checkbox,
 19    ContextMenu,
 20    Cursor,
 21    Disclosure,
 22    Focus,
 23    Icon,
 24    IconButton,
 25    Keybinding,
 26    Label,
 27    List,
 28    ListHeader,
 29    ListItem,
 30    Scroll,
 31    Text,
 32    ZIndex,
 33    Picker,
 34}
 35
 36impl ComponentStory {
 37    pub fn story(&self, cx: &mut WindowContext) -> AnyView {
 38        match self {
 39            Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(),
 40            Self::Avatar => cx.build_view(|_| ui::AvatarStory).into(),
 41            Self::Button => cx.build_view(|_| ui::ButtonStory).into(),
 42            Self::Checkbox => cx.build_view(|_| ui::CheckboxStory).into(),
 43            Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
 44            Self::Cursor => cx.build_view(|_| crate::stories::CursorStory).into(),
 45            Self::Disclosure => cx.build_view(|_| ui::DisclosureStory).into(),
 46            Self::Focus => FocusStory::view(cx).into(),
 47            Self::Icon => cx.build_view(|_| ui::IconStory).into(),
 48            Self::IconButton => cx.build_view(|_| ui::IconButtonStory).into(),
 49            Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
 50            Self::Label => cx.build_view(|_| ui::LabelStory).into(),
 51            Self::List => cx.build_view(|_| ui::ListStory).into(),
 52            Self::ListHeader => cx.build_view(|_| ui::ListHeaderStory).into(),
 53            Self::ListItem => cx.build_view(|_| ui::ListItemStory).into(),
 54            Self::Scroll => ScrollStory::view(cx).into(),
 55            Self::Text => TextStory::view(cx).into(),
 56            Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
 57            Self::Picker => PickerStory::new(cx).into(),
 58        }
 59    }
 60}
 61
 62#[derive(Debug, PartialEq, Eq, Clone, Copy)]
 63pub enum StorySelector {
 64    Component(ComponentStory),
 65    KitchenSink,
 66}
 67
 68impl FromStr for StorySelector {
 69    type Err = anyhow::Error;
 70
 71    fn from_str(raw_story_name: &str) -> std::result::Result<Self, Self::Err> {
 72        use anyhow::Context;
 73
 74        let story = raw_story_name.to_ascii_lowercase();
 75
 76        if story == "kitchen_sink" {
 77            return Ok(Self::KitchenSink);
 78        }
 79
 80        if let Some((_, story)) = story.split_once("components/") {
 81            let component_story = ComponentStory::from_str(story)
 82                .with_context(|| format!("story not found for component '{story}'"))?;
 83
 84            return Ok(Self::Component(component_story));
 85        }
 86
 87        Err(anyhow!("story not found for '{raw_story_name}'"))
 88    }
 89}
 90
 91impl StorySelector {
 92    pub fn story(&self, cx: &mut WindowContext) -> AnyView {
 93        match self {
 94            Self::Component(component_story) => component_story.story(cx),
 95            Self::KitchenSink => KitchenSinkStory::view(cx).into(),
 96        }
 97    }
 98}
 99
100/// The list of all stories available in the storybook.
101static ALL_STORY_SELECTORS: OnceLock<Vec<StorySelector>> = OnceLock::new();
102
103impl ValueEnum for StorySelector {
104    fn value_variants<'a>() -> &'a [Self] {
105        let stories = ALL_STORY_SELECTORS.get_or_init(|| {
106            let component_stories = ComponentStory::iter().map(StorySelector::Component);
107
108            component_stories
109                .chain(std::iter::once(StorySelector::KitchenSink))
110                .collect::<Vec<_>>()
111        });
112
113        stories
114    }
115
116    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
117        let value = match self {
118            Self::Component(story) => format!("components/{story}"),
119            Self::KitchenSink => "kitchen_sink".to_string(),
120        };
121
122        Some(PossibleValue::new(value))
123    }
124}