
This PR adds some basic stories for collab notifications to make them easier to work on: <img width="1076" alt="Screenshot 2024-01-08 at 9 43 39 PM" src="https://github.com/zed-industries/zed/assets/1486634/4a0adcfa-1134-49c2-b589-74ac1d52af4c"> I factored out a `CollabNotification` component that defines the general structure for one of these notifications, and this is the component that we use in the stories, with representative values passed to it to simulate the different instances of the notification. We can't use the actual notification components in the stories due to their data dependencies. Release Notes: - N/A
138 lines
4.5 KiB
Rust
138 lines
4.5 KiB
Rust
use std::str::FromStr;
|
|
use std::sync::OnceLock;
|
|
|
|
use crate::stories::*;
|
|
use anyhow::anyhow;
|
|
use clap::builder::PossibleValue;
|
|
use clap::ValueEnum;
|
|
use gpui::{AnyView, VisualContext};
|
|
use strum::{EnumIter, EnumString, IntoEnumIterator};
|
|
use ui::prelude::*;
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
|
|
#[strum(serialize_all = "snake_case")]
|
|
pub enum ComponentStory {
|
|
AutoHeightEditor,
|
|
Avatar,
|
|
Button,
|
|
Checkbox,
|
|
CollabNotification,
|
|
ContextMenu,
|
|
Cursor,
|
|
Disclosure,
|
|
Focus,
|
|
Icon,
|
|
IconButton,
|
|
Keybinding,
|
|
Label,
|
|
List,
|
|
ListHeader,
|
|
ListItem,
|
|
OverflowScroll,
|
|
Scroll,
|
|
Tab,
|
|
TabBar,
|
|
ToggleButton,
|
|
Text,
|
|
ViewportUnits,
|
|
ZIndex,
|
|
Picker,
|
|
}
|
|
|
|
impl ComponentStory {
|
|
pub fn story(&self, cx: &mut WindowContext) -> AnyView {
|
|
match self {
|
|
Self::AutoHeightEditor => AutoHeightEditorStory::new(cx).into(),
|
|
Self::Avatar => cx.new_view(|_| ui::AvatarStory).into(),
|
|
Self::Button => cx.new_view(|_| ui::ButtonStory).into(),
|
|
Self::Checkbox => cx.new_view(|_| ui::CheckboxStory).into(),
|
|
Self::CollabNotification => cx
|
|
.new_view(|_| collab_ui::notifications::CollabNotificationStory)
|
|
.into(),
|
|
Self::ContextMenu => cx.new_view(|_| ui::ContextMenuStory).into(),
|
|
Self::Cursor => cx.new_view(|_| crate::stories::CursorStory).into(),
|
|
Self::Disclosure => cx.new_view(|_| ui::DisclosureStory).into(),
|
|
Self::Focus => FocusStory::view(cx).into(),
|
|
Self::Icon => cx.new_view(|_| ui::IconStory).into(),
|
|
Self::IconButton => cx.new_view(|_| ui::IconButtonStory).into(),
|
|
Self::Keybinding => cx.new_view(|_| ui::KeybindingStory).into(),
|
|
Self::Label => cx.new_view(|_| ui::LabelStory).into(),
|
|
Self::List => cx.new_view(|_| ui::ListStory).into(),
|
|
Self::ListHeader => cx.new_view(|_| ui::ListHeaderStory).into(),
|
|
Self::ListItem => cx.new_view(|_| ui::ListItemStory).into(),
|
|
Self::OverflowScroll => cx.new_view(|_| crate::stories::OverflowScrollStory).into(),
|
|
Self::Scroll => ScrollStory::view(cx).into(),
|
|
Self::Text => TextStory::view(cx).into(),
|
|
Self::Tab => cx.new_view(|_| ui::TabStory).into(),
|
|
Self::TabBar => cx.new_view(|_| ui::TabBarStory).into(),
|
|
Self::ToggleButton => cx.new_view(|_| ui::ToggleButtonStory).into(),
|
|
Self::ViewportUnits => cx.new_view(|_| crate::stories::ViewportUnitsStory).into(),
|
|
Self::ZIndex => cx.new_view(|_| ZIndexStory).into(),
|
|
Self::Picker => PickerStory::new(cx).into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
pub enum StorySelector {
|
|
Component(ComponentStory),
|
|
KitchenSink,
|
|
}
|
|
|
|
impl FromStr for StorySelector {
|
|
type Err = anyhow::Error;
|
|
|
|
fn from_str(raw_story_name: &str) -> std::result::Result<Self, Self::Err> {
|
|
use anyhow::Context;
|
|
|
|
let story = raw_story_name.to_ascii_lowercase();
|
|
|
|
if story == "kitchen_sink" {
|
|
return Ok(Self::KitchenSink);
|
|
}
|
|
|
|
if let Some((_, story)) = story.split_once("components/") {
|
|
let component_story = ComponentStory::from_str(story)
|
|
.with_context(|| format!("story not found for component '{story}'"))?;
|
|
|
|
return Ok(Self::Component(component_story));
|
|
}
|
|
|
|
Err(anyhow!("story not found for '{raw_story_name}'"))
|
|
}
|
|
}
|
|
|
|
impl StorySelector {
|
|
pub fn story(&self, cx: &mut WindowContext) -> AnyView {
|
|
match self {
|
|
Self::Component(component_story) => component_story.story(cx),
|
|
Self::KitchenSink => KitchenSinkStory::view(cx).into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The list of all stories available in the storybook.
|
|
static ALL_STORY_SELECTORS: OnceLock<Vec<StorySelector>> = OnceLock::new();
|
|
|
|
impl ValueEnum for StorySelector {
|
|
fn value_variants<'a>() -> &'a [Self] {
|
|
let stories = ALL_STORY_SELECTORS.get_or_init(|| {
|
|
let component_stories = ComponentStory::iter().map(StorySelector::Component);
|
|
|
|
component_stories
|
|
.chain(std::iter::once(StorySelector::KitchenSink))
|
|
.collect::<Vec<_>>()
|
|
});
|
|
|
|
stories
|
|
}
|
|
|
|
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
|
|
let value = match self {
|
|
Self::Component(story) => format!("components/{story}"),
|
|
Self::KitchenSink => "kitchen_sink".to_string(),
|
|
};
|
|
|
|
Some(PossibleValue::new(value))
|
|
}
|
|
}
|