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}