1use std::str::FromStr;
2use std::sync::OnceLock;
3
4use crate::stories::*;
5use anyhow::anyhow;
6use clap::builder::PossibleValue;
7use clap::ValueEnum;
8use gpui2::{AnyView, VisualContext};
9use strum::{EnumIter, EnumString, IntoEnumIterator};
10use ui::{prelude::*, AvatarStory, ButtonStory, DetailsStory, IconStory, InputStory, LabelStory};
11
12#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
13#[strum(serialize_all = "snake_case")]
14pub enum ElementStory {
15 Avatar,
16 Button,
17 Colors,
18 Details,
19 Focus,
20 Icon,
21 Input,
22 Label,
23 Scroll,
24 Text,
25 ZIndex,
26}
27
28impl ElementStory {
29 pub fn story(&self, cx: &mut WindowContext) -> AnyView {
30 match self {
31 Self::Colors => cx.build_view(|_| ColorsStory).into(),
32 Self::Avatar => cx.build_view(|_| AvatarStory).into(),
33 Self::Button => cx.build_view(|_| ButtonStory).into(),
34 Self::Details => cx.build_view(|_| DetailsStory).into(),
35 Self::Focus => FocusStory::view(cx).into(),
36 Self::Icon => cx.build_view(|_| IconStory).into(),
37 Self::Input => cx.build_view(|_| InputStory).into(),
38 Self::Label => cx.build_view(|_| LabelStory).into(),
39 Self::Scroll => ScrollStory::view(cx).into(),
40 Self::Text => TextStory::view(cx).into(),
41 Self::ZIndex => cx.build_view(|_| ZIndexStory).into(),
42 }
43 }
44}
45
46#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
47#[strum(serialize_all = "snake_case")]
48pub enum ComponentStory {
49 AssistantPanel,
50 Breadcrumb,
51 Buffer,
52 ChatPanel,
53 CollabPanel,
54 CommandPalette,
55 Copilot,
56 ContextMenu,
57 Facepile,
58 Keybinding,
59 LanguageSelector,
60 MultiBuffer,
61 NotificationsPanel,
62 Palette,
63 Panel,
64 ProjectPanel,
65 RecentProjects,
66 Tab,
67 TabBar,
68 Terminal,
69 ThemeSelector,
70 TitleBar,
71 Toast,
72 Toolbar,
73 TrafficLights,
74 Workspace,
75}
76
77impl ComponentStory {
78 pub fn story(&self, cx: &mut WindowContext) -> AnyView {
79 match self {
80 Self::AssistantPanel => cx.build_view(|_| ui::AssistantPanelStory).into(),
81 Self::Buffer => cx.build_view(|_| ui::BufferStory).into(),
82 Self::Breadcrumb => cx.build_view(|_| ui::BreadcrumbStory).into(),
83 Self::ChatPanel => cx.build_view(|_| ui::ChatPanelStory).into(),
84 Self::CollabPanel => cx.build_view(|_| ui::CollabPanelStory).into(),
85 Self::CommandPalette => cx.build_view(|_| ui::CommandPaletteStory).into(),
86 Self::ContextMenu => cx.build_view(|_| ui::ContextMenuStory).into(),
87 Self::Facepile => cx.build_view(|_| ui::FacepileStory).into(),
88 Self::Keybinding => cx.build_view(|_| ui::KeybindingStory).into(),
89 Self::LanguageSelector => cx.build_view(|_| ui::LanguageSelectorStory).into(),
90 Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into(),
91 Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into(),
92 Self::Palette => cx.build_view(|cx| ui::PaletteStory).into(),
93 Self::Panel => cx.build_view(|cx| ui::PanelStory).into(),
94 Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into(),
95 Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into(),
96 Self::Tab => cx.build_view(|_| ui::TabStory).into(),
97 Self::TabBar => cx.build_view(|_| ui::TabBarStory).into(),
98 Self::Terminal => cx.build_view(|_| ui::TerminalStory).into(),
99 Self::ThemeSelector => cx.build_view(|_| ui::ThemeSelectorStory).into(),
100 Self::Toast => cx.build_view(|_| ui::ToastStory).into(),
101 Self::Toolbar => cx.build_view(|_| ui::ToolbarStory).into(),
102 Self::TrafficLights => cx.build_view(|_| ui::TrafficLightsStory).into(),
103 Self::Copilot => cx.build_view(|_| ui::CopilotModalStory).into(),
104 Self::TitleBar => ui::TitleBarStory::view(cx).into(),
105 Self::Workspace => ui::WorkspaceStory::view(cx).into(),
106 }
107 }
108}
109
110#[derive(Debug, PartialEq, Eq, Clone, Copy)]
111pub enum StorySelector {
112 Element(ElementStory),
113 Component(ComponentStory),
114 KitchenSink,
115}
116
117impl FromStr for StorySelector {
118 type Err = anyhow::Error;
119
120 fn from_str(raw_story_name: &str) -> std::result::Result<Self, Self::Err> {
121 use anyhow::Context;
122
123 let story = raw_story_name.to_ascii_lowercase();
124
125 if story == "kitchen_sink" {
126 return Ok(Self::KitchenSink);
127 }
128
129 if let Some((_, story)) = story.split_once("elements/") {
130 let element_story = ElementStory::from_str(story)
131 .with_context(|| format!("story not found for element '{story}'"))?;
132
133 return Ok(Self::Element(element_story));
134 }
135
136 if let Some((_, story)) = story.split_once("components/") {
137 let component_story = ComponentStory::from_str(story)
138 .with_context(|| format!("story not found for component '{story}'"))?;
139
140 return Ok(Self::Component(component_story));
141 }
142
143 Err(anyhow!("story not found for '{raw_story_name}'"))
144 }
145}
146
147impl StorySelector {
148 pub fn story(&self, cx: &mut WindowContext) -> AnyView {
149 match self {
150 Self::Element(element_story) => element_story.story(cx),
151 Self::Component(component_story) => component_story.story(cx),
152 Self::KitchenSink => KitchenSinkStory::view(cx).into(),
153 }
154 }
155}
156
157/// The list of all stories available in the storybook.
158static ALL_STORY_SELECTORS: OnceLock<Vec<StorySelector>> = OnceLock::new();
159
160impl ValueEnum for StorySelector {
161 fn value_variants<'a>() -> &'a [Self] {
162 let stories = ALL_STORY_SELECTORS.get_or_init(|| {
163 let element_stories = ElementStory::iter().map(StorySelector::Element);
164 let component_stories = ComponentStory::iter().map(StorySelector::Component);
165
166 element_stories
167 .chain(component_stories)
168 .chain(std::iter::once(StorySelector::KitchenSink))
169 .collect::<Vec<_>>()
170 });
171
172 stories
173 }
174
175 fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
176 let value = match self {
177 Self::Element(story) => format!("elements/{story}"),
178 Self::Component(story) => format!("components/{story}"),
179 Self::KitchenSink => "kitchen_sink".to_string(),
180 };
181
182 Some(PossibleValue::new(value))
183 }
184}