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}