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