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