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