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