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