Update storybook to support stories for individual components (#3010)

This PR updates the `storybook` with support for adding stories for
individual components.

### Motivation

Right now we just have one story in the storybook that renders an entire
`WorkspaceElement`.

While iterating on the various UI components, it will be helpful to be
able to create stories of those components just by themselves.

This is especially true for components that have a number of different
states, as we can render the components in all of the various states in
a single layout.

### Explanation

We achieve this by adding a simple CLI to the storybook.

The `storybook` binary now accepts an optional `[STORY]` parameter that
can be used to indicate which story should be loaded. If this parameter
is not provided, it will load the workspace story as it currently does.

Passing a story name will load the corresponding story, if it exists.

For example:

```
cargo run -- elements/avatar
```

<img width="723" alt="Screenshot 2023-09-21 at 10 29 52 PM"
src="https://github.com/zed-industries/zed/assets/1486634/5df489ed-8607-4024-9c19-c5f4541f97c9">

```
cargo run -- components/facepile
```

<img width="785" alt="Screenshot 2023-09-21 at 10 30 07 PM"
src="https://github.com/zed-industries/zed/assets/1486634/e04a4577-7403-405d-b23c-e765b7a06229">



Release Notes:

- N/A
This commit is contained in:
Marshall Bowers 2023-09-21 22:41:53 -04:00 committed by GitHub
parent 4628639ac6
commit 66358f2900
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 180 additions and 19 deletions

1
Cargo.lock generated
View file

@ -7390,6 +7390,7 @@ name = "storybook"
version = "0.1.0"
dependencies = [
"anyhow",
"clap 3.2.25",
"gpui2",
"log",
"rust-embed",

View file

@ -9,8 +9,9 @@ name = "storybook"
path = "src/storybook.rs"
[dependencies]
gpui2 = { path = "../gpui2" }
anyhow.workspace = true
clap = { version = "3.1", features = ["derive"] }
gpui2 = { path = "../gpui2" }
log.workspace = true
rust-embed.workspace = true
serde.workspace = true

View file

@ -0,0 +1,2 @@
pub mod components;
pub mod elements;

View file

@ -0,0 +1 @@
pub mod facepile;

View file

@ -0,0 +1,69 @@
use gpui2::elements::div;
use gpui2::style::StyleHelpers;
use gpui2::{rgb, Element, Hsla, IntoElement, ParentElement, ViewContext};
use ui::{avatar, theme};
use ui::{facepile, prelude::*};
#[derive(Element, Default)]
pub struct FacepileStory {}
impl FacepileStory {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.size_full()
.flex()
.flex_col()
.pt_2()
.px_4()
.font("Zed Mono Extended")
.fill(rgb::<Hsla>(0x282c34))
.child(
div()
.text_2xl()
.text_color(rgb::<Hsla>(0xffffff))
.child(std::any::type_name::<ui::Facepile>()),
)
.child(
div()
.flex()
.gap_3()
.child(facepile(vec![avatar(
"https://avatars.githubusercontent.com/u/1714999?v=4",
)]))
.child(facepile(vec![
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
]))
.child(facepile(vec![
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
avatar("https://avatars.githubusercontent.com/u/1714999?v=4"),
])),
)
.child(
div()
.flex()
.gap_3()
.child(facepile(vec![avatar(
"https://avatars.githubusercontent.com/u/1714999?v=4",
)
.shape(Shape::RoundedRectangle)]))
.child(facepile(vec![
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
.shape(Shape::RoundedRectangle),
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
.shape(Shape::RoundedRectangle),
]))
.child(facepile(vec![
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
.shape(Shape::RoundedRectangle),
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
.shape(Shape::RoundedRectangle),
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
.shape(Shape::RoundedRectangle),
])),
)
}
}

View file

@ -0,0 +1 @@
pub mod avatar;

View file

@ -0,0 +1,41 @@
use gpui2::elements::div;
use gpui2::style::StyleHelpers;
use gpui2::{rgb, Element, Hsla, IntoElement, ParentElement, ViewContext};
use ui::prelude::*;
use ui::{avatar, theme};
#[derive(Element, Default)]
pub struct AvatarStory {}
impl AvatarStory {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);
div()
.size_full()
.flex()
.flex_col()
.pt_2()
.px_4()
.font("Zed Mono Extended")
.fill(rgb::<Hsla>(0x282c34))
.child(
div()
.text_2xl()
.text_color(rgb::<Hsla>(0xffffff))
.child(std::any::type_name::<ui::Avatar>()),
)
.child(
div()
.flex()
.gap_3()
.child(avatar(
"https://avatars.githubusercontent.com/u/1714999?v=4",
))
.child(
avatar("https://avatars.githubusercontent.com/u/1714999?v=4")
.shape(Shape::RoundedRectangle),
),
)
}
}

View file

@ -1,25 +1,66 @@
#![allow(dead_code, unused_variables)]
mod collab_panel;
mod stories;
mod workspace;
use std::str::FromStr;
use ::theme as legacy_theme;
use gpui2::{serde_json, vec2f, view, Element, RectF, ViewContext, WindowBounds};
use clap::Parser;
use gpui2::{serde_json, vec2f, view, Element, IntoElement, RectF, ViewContext, WindowBounds};
use legacy_theme::ThemeSettings;
use log::LevelFilter;
use settings::{default_settings, SettingsStore};
use simplelog::SimpleLogger;
use stories::components::facepile::FacepileStory;
use stories::elements::avatar::AvatarStory;
use ui::{ElementExt, Theme};
mod collab_panel;
mod workspace;
gpui2::actions! {
storybook,
[ToggleInspector]
}
#[derive(Debug, Clone, Copy)]
enum Story {
Element(ElementStory),
Component(ComponentStory),
}
impl FromStr for Story {
type Err = anyhow::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
"elements/avatar" => Ok(Self::Element(ElementStory::Avatar)),
"components/facepile" => Ok(Self::Component(ComponentStory::Facepile)),
_ => Err(anyhow!("story not found for '{s}'")),
}
}
}
#[derive(Debug, Clone, Copy)]
enum ElementStory {
Avatar,
}
#[derive(Debug, Clone, Copy)]
enum ComponentStory {
Facepile,
}
#[derive(Parser)]
struct Args {
story: Option<Story>,
}
fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
gpui2::App::new(Assets).unwrap().run(|cx| {
let args = Args::parse();
gpui2::App::new(Assets).unwrap().run(move |cx| {
let mut store = SettingsStore::default();
store
.set_default_settings(default_settings().as_ref(), cx)
@ -34,19 +75,27 @@ fn main() {
center: true,
..Default::default()
},
|cx| {
view(|cx| {
// cx.enable_inspector();
storybook(&mut ViewContext::new(cx))
})
|cx| match args.story {
Some(Story::Element(ElementStory::Avatar)) => {
view(|cx| render_story(&mut ViewContext::new(cx), AvatarStory::default()))
}
Some(Story::Component(ComponentStory::Facepile)) => {
view(|cx| render_story(&mut ViewContext::new(cx), FacepileStory::default()))
}
None => {
view(|cx| render_story(&mut ViewContext::new(cx), WorkspaceElement::default()))
}
},
);
cx.platform().activate(true);
});
}
fn storybook<V: 'static>(cx: &mut ViewContext<V>) -> impl Element<V> {
workspace().themed(current_theme(cx))
fn render_story<V: 'static, S: IntoElement<V>>(
cx: &mut ViewContext<V>,
story: S,
) -> impl Element<V> {
story.into_element().themed(current_theme(cx))
}
// Nathan: During the transition to gpui2, we will include the base theme on the legacy Theme struct.
@ -69,7 +118,7 @@ fn current_theme<V: 'static>(cx: &mut ViewContext<V>) -> Theme {
use anyhow::{anyhow, Result};
use gpui2::AssetSource;
use rust_embed::RustEmbed;
use workspace::workspace;
use workspace::WorkspaceElement;
#[derive(RustEmbed)]
#[folder = "../../assets"]

View file

@ -6,16 +6,12 @@ use gpui2::{
use ui::{chat_panel, project_panel, status_bar, tab_bar, theme, title_bar};
#[derive(Element, Default)]
struct WorkspaceElement {
pub struct WorkspaceElement {
left_scroll_state: ScrollState,
right_scroll_state: ScrollState,
tab_bar_scroll_state: ScrollState,
}
pub fn workspace<V: 'static>() -> impl Element<V> {
WorkspaceElement::default()
}
impl WorkspaceElement {
fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
let theme = theme(cx);