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 ContextMenu,
17 Cursor,
18 Focus,
19 OverflowScroll,
20 Picker,
21 Scroll,
22 Text,
23 ViewportUnits,
24 WithRemSize,
25 IndentGuides,
26}
27
28impl ComponentStory {
29 pub fn story(&self, window: &mut Window, cx: &mut App) -> AnyView {
30 match self {
31 Self::ApplicationMenu => cx
32 .new(|cx| title_bar::ApplicationMenuStory::new(window, cx))
33 .into(),
34 Self::AutoHeightEditor => AutoHeightEditorStory::new(window, cx).into(),
35 Self::ContextMenu => cx.new(|_| ui::ContextMenuStory).into(),
36 Self::Cursor => cx.new(|_| crate::stories::CursorStory).into(),
37 Self::Focus => FocusStory::model(window, cx).into(),
38 Self::OverflowScroll => cx.new(|_| crate::stories::OverflowScrollStory).into(),
39 Self::Picker => PickerStory::new(window, cx).into(),
40 Self::Scroll => ScrollStory::model(cx).into(),
41 Self::Text => TextStory::model(cx).into(),
42 Self::ViewportUnits => cx.new(|_| crate::stories::ViewportUnitsStory).into(),
43 Self::WithRemSize => cx.new(|_| crate::stories::WithRemSizeStory).into(),
44 Self::IndentGuides => crate::stories::IndentGuidesStory::model(window, cx).into(),
45 }
46 }
47}
48
49#[derive(Debug, PartialEq, Eq, Clone, Copy)]
50pub enum StorySelector {
51 Component(ComponentStory),
52 KitchenSink,
53}
54
55impl FromStr for StorySelector {
56 type Err = anyhow::Error;
57
58 fn from_str(raw_story_name: &str) -> std::result::Result<Self, Self::Err> {
59 use anyhow::Context as _;
60
61 let story = raw_story_name.to_ascii_lowercase();
62
63 if story == "kitchen_sink" {
64 return Ok(Self::KitchenSink);
65 }
66
67 if let Some((_, story)) = story.split_once("components/") {
68 let component_story = ComponentStory::from_str(story)
69 .with_context(|| format!("story not found for component '{story}'"))?;
70
71 return Ok(Self::Component(component_story));
72 }
73
74 anyhow::bail!("story not found for '{raw_story_name}'")
75 }
76}
77
78impl StorySelector {
79 pub fn story(&self, window: &mut Window, cx: &mut App) -> AnyView {
80 match self {
81 Self::Component(component_story) => component_story.story(window, cx),
82 Self::KitchenSink => KitchenSinkStory::model(cx).into(),
83 }
84 }
85}
86
87/// The list of all stories available in the storybook.
88static ALL_STORY_SELECTORS: OnceLock<Vec<StorySelector>> = OnceLock::new();
89
90impl ValueEnum for StorySelector {
91 fn value_variants<'a>() -> &'a [Self] {
92 (ALL_STORY_SELECTORS.get_or_init(|| {
93 let component_stories = ComponentStory::iter().map(StorySelector::Component);
94
95 component_stories
96 .chain(std::iter::once(StorySelector::KitchenSink))
97 .collect::<Vec<_>>()
98 })) as _
99 }
100
101 fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
102 let value = match self {
103 Self::Component(story) => format!("components/{story}"),
104 Self::KitchenSink => "kitchen_sink".to_string(),
105 };
106
107 Some(PossibleValue::new(value))
108 }
109}