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