Remove 2 suffix for ui, storybook, text
Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
parent
0cf65223ce
commit
4305c5fdbe
142 changed files with 106 additions and 5018 deletions
2919
crates/storybook/Cargo.lock
generated
Normal file
2919
crates/storybook/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
39
crates/storybook/Cargo.toml
Normal file
39
crates/storybook/Cargo.toml
Normal file
|
@ -0,0 +1,39 @@
|
|||
[package]
|
||||
name = "storybook"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "storybook"
|
||||
path = "src/storybook.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
# TODO: Remove after diagnosing stack overflow.
|
||||
backtrace-on-stack-overflow = "0.3.0"
|
||||
chrono = "0.4"
|
||||
clap = { version = "4.4", features = ["derive", "string"] }
|
||||
dialoguer = { version = "0.11.0", features = ["fuzzy-select"] }
|
||||
editor = { path = "../editor" }
|
||||
fuzzy = { path = "../fuzzy" }
|
||||
gpui = { package = "gpui2", path = "../gpui2" }
|
||||
indoc.workspace = true
|
||||
itertools = "0.11.0"
|
||||
language = { path = "../language" }
|
||||
log.workspace = true
|
||||
rust-embed.workspace = true
|
||||
serde.workspace = true
|
||||
settings = { path = "../settings" }
|
||||
simplelog = "0.9"
|
||||
smallvec.workspace = true
|
||||
story = { path = "../story" }
|
||||
strum = { version = "0.25.0", features = ["derive"] }
|
||||
theme2 = { path = "../theme2" }
|
||||
menu = { path = "../menu" }
|
||||
ui = { path = "../ui", features = ["stories"] }
|
||||
util = { path = "../util" }
|
||||
picker = { path = "../picker" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }
|
5
crates/storybook/build.rs
Normal file
5
crates/storybook/build.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
fn main() {
|
||||
// Find WebRTC.framework as a sibling of the executable when running outside of an application bundle.
|
||||
// TODO: We shouldn't depend on WebRTC in editor
|
||||
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
|
||||
}
|
72
crates/storybook/docs/thoughts.md
Normal file
72
crates/storybook/docs/thoughts.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
Much of element styling is now handled by an external engine.
|
||||
|
||||
|
||||
How do I make an element hover.
|
||||
|
||||
There's a hover style.
|
||||
|
||||
Hoverable needs to wrap another element. That element can be styled.
|
||||
|
||||
```rs
|
||||
struct Hoverable<E: Element> {
|
||||
|
||||
}
|
||||
|
||||
impl<V> Element<V> for Hoverable {
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
```rs
|
||||
#[derive(Styled, Interactive)]
|
||||
pub struct Div {
|
||||
declared_style: StyleRefinement,
|
||||
interactions: Interactions
|
||||
}
|
||||
|
||||
pub trait Styled {
|
||||
fn declared_style(&mut self) -> &mut StyleRefinement;
|
||||
fn compute_style(&mut self) -> Style {
|
||||
Style::default().refine(self.declared_style())
|
||||
}
|
||||
|
||||
// All the tailwind classes, modifying self.declared_style()
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn paint_background<V>(layout: Layout, cx: &mut PaintContext<V>);
|
||||
pub fn paint_foreground<V>(layout: Layout, cx: &mut PaintContext<V>);
|
||||
}
|
||||
|
||||
pub trait Interactive<V> {
|
||||
fn interactions(&mut self) -> &mut Interactions<V>;
|
||||
|
||||
fn on_click(self, )
|
||||
}
|
||||
|
||||
struct Interactions<V> {
|
||||
click: SmallVec<[<Rc<dyn Fn(&mut V, &dyn Any, )>; 1]>,
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
|
||||
```rs
|
||||
|
||||
|
||||
trait Stylable {
|
||||
type Style;
|
||||
|
||||
fn with_style(self, style: Self::Style) -> Self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```
|
30
crates/storybook/src/assets.rs
Normal file
30
crates/storybook/src/assets.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use gpui::{AssetSource, SharedString};
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../assets"]
|
||||
#[include = "fonts/**/*"]
|
||||
#[include = "icons/**/*"]
|
||||
#[include = "themes/**/*"]
|
||||
#[include = "sounds/**/*"]
|
||||
#[include = "*.md"]
|
||||
#[exclude = "*.DS_Store"]
|
||||
pub struct Assets;
|
||||
|
||||
impl AssetSource for Assets {
|
||||
fn load(&self, path: &str) -> Result<Cow<[u8]>> {
|
||||
Self::get(path)
|
||||
.map(|f| f.data)
|
||||
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))
|
||||
}
|
||||
|
||||
fn list(&self, path: &str) -> Result<Vec<SharedString>> {
|
||||
Ok(Self::iter()
|
||||
.filter(|p| p.starts_with(path))
|
||||
.map(SharedString::from)
|
||||
.collect())
|
||||
}
|
||||
}
|
21
crates/storybook/src/stories.rs
Normal file
21
crates/storybook/src/stories.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
mod auto_height_editor;
|
||||
mod cursor;
|
||||
mod focus;
|
||||
mod kitchen_sink;
|
||||
mod overflow_scroll;
|
||||
mod picker;
|
||||
mod scroll;
|
||||
mod text;
|
||||
mod viewport_units;
|
||||
mod z_index;
|
||||
|
||||
pub use auto_height_editor::*;
|
||||
pub use cursor::*;
|
||||
pub use focus::*;
|
||||
pub use kitchen_sink::*;
|
||||
pub use overflow_scroll::*;
|
||||
pub use picker::*;
|
||||
pub use scroll::*;
|
||||
pub use text::*;
|
||||
pub use viewport_units::*;
|
||||
pub use z_index::*;
|
32
crates/storybook/src/stories/auto_height_editor.rs
Normal file
32
crates/storybook/src/stories/auto_height_editor.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use editor::Editor;
|
||||
use gpui::{
|
||||
div, white, IntoElement, KeyBinding, ParentElement, Render, Styled, View, ViewContext,
|
||||
VisualContext, WindowContext,
|
||||
};
|
||||
|
||||
pub struct AutoHeightEditorStory {
|
||||
editor: View<Editor>,
|
||||
}
|
||||
|
||||
impl AutoHeightEditorStory {
|
||||
pub fn new(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.bind_keys([KeyBinding::new("enter", editor::Newline, Some("Editor"))]);
|
||||
cx.new_view(|cx| Self {
|
||||
editor: cx.new_view(|cx| {
|
||||
let mut editor = Editor::auto_height(3, cx);
|
||||
editor.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
|
||||
editor
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for AutoHeightEditorStory {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.size_full()
|
||||
.bg(white())
|
||||
.text_sm()
|
||||
.child(div().w_32().bg(gpui::black()).child(self.editor.clone()))
|
||||
}
|
||||
}
|
109
crates/storybook/src/stories/cursor.rs
Normal file
109
crates/storybook/src/stories/cursor.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use gpui::{Div, Render, Stateful};
|
||||
use story::Story;
|
||||
use ui::prelude::*;
|
||||
|
||||
pub struct CursorStory;
|
||||
|
||||
impl Render for CursorStory {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let all_cursors: [(&str, Box<dyn Fn(Stateful<Div>) -> Stateful<Div>>); 19] = [
|
||||
(
|
||||
"cursor_default",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_default()),
|
||||
),
|
||||
(
|
||||
"cursor_pointer",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_pointer()),
|
||||
),
|
||||
(
|
||||
"cursor_text",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_text()),
|
||||
),
|
||||
(
|
||||
"cursor_move",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_move()),
|
||||
),
|
||||
(
|
||||
"cursor_not_allowed",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_not_allowed()),
|
||||
),
|
||||
(
|
||||
"cursor_context_menu",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_context_menu()),
|
||||
),
|
||||
(
|
||||
"cursor_crosshair",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_crosshair()),
|
||||
),
|
||||
(
|
||||
"cursor_vertical_text",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_vertical_text()),
|
||||
),
|
||||
(
|
||||
"cursor_alias",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_alias()),
|
||||
),
|
||||
(
|
||||
"cursor_copy",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_copy()),
|
||||
),
|
||||
(
|
||||
"cursor_no_drop",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_no_drop()),
|
||||
),
|
||||
(
|
||||
"cursor_grab",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_grab()),
|
||||
),
|
||||
(
|
||||
"cursor_grabbing",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_grabbing()),
|
||||
),
|
||||
(
|
||||
"cursor_col_resize",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_col_resize()),
|
||||
),
|
||||
(
|
||||
"cursor_row_resize",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_row_resize()),
|
||||
),
|
||||
(
|
||||
"cursor_n_resize",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_n_resize()),
|
||||
),
|
||||
(
|
||||
"cursor_e_resize",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_e_resize()),
|
||||
),
|
||||
(
|
||||
"cursor_s_resize",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_s_resize()),
|
||||
),
|
||||
(
|
||||
"cursor_w_resize",
|
||||
Box::new(|el: Stateful<Div>| el.cursor_w_resize()),
|
||||
),
|
||||
];
|
||||
|
||||
Story::container()
|
||||
.flex()
|
||||
.gap_1()
|
||||
.child(Story::title("cursor"))
|
||||
.children(all_cursors.map(|(name, apply_cursor)| {
|
||||
div().gap_1().flex().text_color(gpui::white()).child(
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.id(name)
|
||||
.map(apply_cursor)
|
||||
.w_64()
|
||||
.h_8()
|
||||
.bg(gpui::red())
|
||||
.active(|style| style.bg(gpui::green()))
|
||||
.text_sm()
|
||||
.child(Story::label(name)),
|
||||
)
|
||||
}))
|
||||
}
|
||||
}
|
119
crates/storybook/src/stories/focus.rs
Normal file
119
crates/storybook/src/stories/focus.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use gpui::{
|
||||
actions, div, prelude::*, FocusHandle, KeyBinding, Render, Subscription, View, WindowContext,
|
||||
};
|
||||
use ui::prelude::*;
|
||||
|
||||
actions!(focus, [ActionA, ActionB, ActionC]);
|
||||
|
||||
pub struct FocusStory {
|
||||
parent_focus: FocusHandle,
|
||||
child_1_focus: FocusHandle,
|
||||
child_2_focus: FocusHandle,
|
||||
_focus_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl FocusStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("cmd-a", ActionA, Some("parent")),
|
||||
KeyBinding::new("cmd-a", ActionB, Some("child-1")),
|
||||
KeyBinding::new("cmd-c", ActionC, None),
|
||||
]);
|
||||
|
||||
cx.new_view(move |cx| {
|
||||
let parent_focus = cx.focus_handle();
|
||||
let child_1_focus = cx.focus_handle();
|
||||
let child_2_focus = cx.focus_handle();
|
||||
let _focus_subscriptions = vec![
|
||||
cx.on_focus(&parent_focus, |_, _| {
|
||||
println!("Parent focused");
|
||||
}),
|
||||
cx.on_blur(&parent_focus, |_, _| {
|
||||
println!("Parent blurred");
|
||||
}),
|
||||
cx.on_focus(&child_1_focus, |_, _| {
|
||||
println!("Child 1 focused");
|
||||
}),
|
||||
cx.on_blur(&child_1_focus, |_, _| {
|
||||
println!("Child 1 blurred");
|
||||
}),
|
||||
cx.on_focus(&child_2_focus, |_, _| {
|
||||
println!("Child 2 focused");
|
||||
}),
|
||||
cx.on_blur(&child_2_focus, |_, _| {
|
||||
println!("Child 2 blurred");
|
||||
}),
|
||||
];
|
||||
|
||||
Self {
|
||||
parent_focus,
|
||||
child_1_focus,
|
||||
child_2_focus,
|
||||
_focus_subscriptions,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for FocusStory {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
let color_1 = theme.status().created;
|
||||
let color_2 = theme.status().modified;
|
||||
let color_4 = theme.status().conflict;
|
||||
let color_5 = theme.status().ignored;
|
||||
let color_6 = theme.status().renamed;
|
||||
let color_7 = theme.status().hint;
|
||||
|
||||
div()
|
||||
.id("parent")
|
||||
.active(|style| style.bg(color_7))
|
||||
.track_focus(&self.parent_focus)
|
||||
.key_context("parent")
|
||||
.on_action(cx.listener(|_, _action: &ActionA, _cx| {
|
||||
println!("Action A dispatched on parent");
|
||||
}))
|
||||
.on_action(cx.listener(|_, _action: &ActionB, _cx| {
|
||||
println!("Action B dispatched on parent");
|
||||
}))
|
||||
.on_key_down(cx.listener(|_, event, _| println!("Key down on parent {:?}", event)))
|
||||
.on_key_up(cx.listener(|_, event, _| println!("Key up on parent {:?}", event)))
|
||||
.size_full()
|
||||
.bg(color_1)
|
||||
.focus(|style| style.bg(color_2))
|
||||
.child(
|
||||
div()
|
||||
.track_focus(&self.child_1_focus)
|
||||
.key_context("child-1")
|
||||
.on_action(cx.listener(|_, _action: &ActionB, _cx| {
|
||||
println!("Action B dispatched on child 1 during");
|
||||
}))
|
||||
.w_full()
|
||||
.h_6()
|
||||
.bg(color_4)
|
||||
.focus(|style| style.bg(color_5))
|
||||
.in_focus(|style| style.bg(color_6))
|
||||
.on_key_down(
|
||||
cx.listener(|_, event, _| println!("Key down on child 1 {:?}", event)),
|
||||
)
|
||||
.on_key_up(cx.listener(|_, event, _| println!("Key up on child 1 {:?}", event)))
|
||||
.child("Child 1"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.track_focus(&self.child_2_focus)
|
||||
.key_context("child-2")
|
||||
.on_action(cx.listener(|_, _action: &ActionC, _cx| {
|
||||
println!("Action C dispatched on child 2");
|
||||
}))
|
||||
.w_full()
|
||||
.h_6()
|
||||
.bg(color_4)
|
||||
.on_key_down(
|
||||
cx.listener(|_, event, _| println!("Key down on child 2 {:?}", event)),
|
||||
)
|
||||
.on_key_up(cx.listener(|_, event, _| println!("Key up on child 2 {:?}", event)))
|
||||
.child("Child 2"),
|
||||
)
|
||||
}
|
||||
}
|
32
crates/storybook/src/stories/kitchen_sink.rs
Normal file
32
crates/storybook/src/stories/kitchen_sink.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use gpui::{prelude::*, Render, View};
|
||||
use story::Story;
|
||||
use strum::IntoEnumIterator;
|
||||
use ui::prelude::*;
|
||||
|
||||
use crate::story_selector::ComponentStory;
|
||||
|
||||
pub struct KitchenSinkStory;
|
||||
|
||||
impl KitchenSinkStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.new_view(|_cx| Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for KitchenSinkStory {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let component_stories = ComponentStory::iter()
|
||||
.map(|selector| selector.story(cx))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Story::container()
|
||||
.id("kitchen-sink")
|
||||
.overflow_y_scroll()
|
||||
.child(Story::title("Kitchen Sink"))
|
||||
.child(Story::label("Components"))
|
||||
.child(div().flex().flex_col().children(component_stories))
|
||||
// Add a bit of space at the bottom of the kitchen sink so elements
|
||||
// don't end up squished right up against the bottom of the screen.
|
||||
.child(div().p_4())
|
||||
}
|
||||
}
|
39
crates/storybook/src/stories/overflow_scroll.rs
Normal file
39
crates/storybook/src/stories/overflow_scroll.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use gpui::Render;
|
||||
use story::Story;
|
||||
|
||||
use ui::prelude::*;
|
||||
|
||||
pub struct OverflowScrollStory;
|
||||
|
||||
impl Render for OverflowScrollStory {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
Story::container()
|
||||
.child(Story::title("Overflow Scroll"))
|
||||
.child(Story::label("`overflow_x_scroll`"))
|
||||
.child(
|
||||
h_stack()
|
||||
.id("overflow_x_scroll")
|
||||
.gap_2()
|
||||
.overflow_x_scroll()
|
||||
.children((0..100).map(|i| {
|
||||
div()
|
||||
.p_4()
|
||||
.debug_bg_cyan()
|
||||
.child(SharedString::from(format!("Child {}", i + 1)))
|
||||
})),
|
||||
)
|
||||
.child(Story::label("`overflow_y_scroll`"))
|
||||
.child(
|
||||
v_stack()
|
||||
.id("overflow_y_scroll")
|
||||
.gap_2()
|
||||
.overflow_y_scroll()
|
||||
.children((0..100).map(|i| {
|
||||
div()
|
||||
.p_4()
|
||||
.debug_bg_green()
|
||||
.child(SharedString::from(format!("Child {}", i + 1)))
|
||||
})),
|
||||
)
|
||||
}
|
||||
}
|
209
crates/storybook/src/stories/picker.rs
Normal file
209
crates/storybook/src/stories/picker.rs
Normal file
|
@ -0,0 +1,209 @@
|
|||
use fuzzy::StringMatchCandidate;
|
||||
use gpui::{div, prelude::*, KeyBinding, Render, SharedString, Styled, Task, View, WindowContext};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use std::sync::Arc;
|
||||
use ui::{prelude::*, ListItemSpacing};
|
||||
use ui::{Label, ListItem};
|
||||
|
||||
pub struct PickerStory {
|
||||
picker: View<Picker<Delegate>>,
|
||||
}
|
||||
|
||||
struct Delegate {
|
||||
candidates: Arc<[StringMatchCandidate]>,
|
||||
matches: Vec<usize>,
|
||||
selected_ix: usize,
|
||||
}
|
||||
|
||||
impl Delegate {
|
||||
fn new(strings: &[&str]) -> Self {
|
||||
Self {
|
||||
candidates: strings
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.map(|(id, string)| StringMatchCandidate {
|
||||
id,
|
||||
char_bag: string.into(),
|
||||
string: string.into(),
|
||||
})
|
||||
.collect(),
|
||||
matches: vec![],
|
||||
selected_ix: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for Delegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.candidates.len()
|
||||
}
|
||||
|
||||
fn placeholder_text(&self) -> Arc<str> {
|
||||
"Test".into()
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
_cx: &mut gpui::ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let Some(candidate_ix) = self.matches.get(ix) else {
|
||||
return None;
|
||||
};
|
||||
// TASK: Make StringMatchCandidate::string a SharedString
|
||||
let candidate = SharedString::from(self.candidates[*candidate_ix].string.clone());
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.selected(selected)
|
||||
.child(Label::new(candidate)),
|
||||
)
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
self.selected_ix
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut gpui::ViewContext<Picker<Self>>) {
|
||||
self.selected_ix = ix;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn confirm(&mut self, secondary: bool, _cx: &mut gpui::ViewContext<Picker<Self>>) {
|
||||
let candidate_ix = self.matches[self.selected_ix];
|
||||
let candidate = self.candidates[candidate_ix].string.clone();
|
||||
|
||||
if secondary {
|
||||
eprintln!("Secondary confirmed {}", candidate)
|
||||
} else {
|
||||
eprintln!("Confirmed {}", candidate)
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut gpui::ViewContext<Picker<Self>>) {
|
||||
cx.quit();
|
||||
}
|
||||
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
cx: &mut gpui::ViewContext<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let candidates = self.candidates.clone();
|
||||
self.matches = cx
|
||||
.background_executor()
|
||||
.block(fuzzy::match_strings(
|
||||
&candidates,
|
||||
&query,
|
||||
true,
|
||||
100,
|
||||
&Default::default(),
|
||||
cx.background_executor().clone(),
|
||||
))
|
||||
.into_iter()
|
||||
.map(|r| r.candidate_id)
|
||||
.collect();
|
||||
self.selected_ix = 0;
|
||||
Task::ready(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerStory {
|
||||
pub fn new(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.new_view(|cx| {
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("up", menu::SelectPrev, Some("picker")),
|
||||
KeyBinding::new("pageup", menu::SelectFirst, Some("picker")),
|
||||
KeyBinding::new("shift-pageup", menu::SelectFirst, Some("picker")),
|
||||
KeyBinding::new("ctrl-p", menu::SelectPrev, Some("picker")),
|
||||
KeyBinding::new("down", menu::SelectNext, Some("picker")),
|
||||
KeyBinding::new("pagedown", menu::SelectLast, Some("picker")),
|
||||
KeyBinding::new("shift-pagedown", menu::SelectFirst, Some("picker")),
|
||||
KeyBinding::new("ctrl-n", menu::SelectNext, Some("picker")),
|
||||
KeyBinding::new("cmd-up", menu::SelectFirst, Some("picker")),
|
||||
KeyBinding::new("cmd-down", menu::SelectLast, Some("picker")),
|
||||
KeyBinding::new("enter", menu::Confirm, Some("picker")),
|
||||
KeyBinding::new("ctrl-enter", menu::ShowContextMenu, Some("picker")),
|
||||
KeyBinding::new("cmd-enter", menu::SecondaryConfirm, Some("picker")),
|
||||
KeyBinding::new("escape", menu::Cancel, Some("picker")),
|
||||
KeyBinding::new("ctrl-c", menu::Cancel, Some("picker")),
|
||||
]);
|
||||
|
||||
PickerStory {
|
||||
picker: cx.new_view(|cx| {
|
||||
let mut delegate = Delegate::new(&[
|
||||
"Baguette (France)",
|
||||
"Baklava (Turkey)",
|
||||
"Beef Wellington (UK)",
|
||||
"Biryani (India)",
|
||||
"Borscht (Ukraine)",
|
||||
"Bratwurst (Germany)",
|
||||
"Bulgogi (Korea)",
|
||||
"Burrito (USA)",
|
||||
"Ceviche (Peru)",
|
||||
"Chicken Tikka Masala (India)",
|
||||
"Churrasco (Brazil)",
|
||||
"Couscous (North Africa)",
|
||||
"Croissant (France)",
|
||||
"Dim Sum (China)",
|
||||
"Empanada (Argentina)",
|
||||
"Fajitas (Mexico)",
|
||||
"Falafel (Middle East)",
|
||||
"Feijoada (Brazil)",
|
||||
"Fish and Chips (UK)",
|
||||
"Fondue (Switzerland)",
|
||||
"Goulash (Hungary)",
|
||||
"Haggis (Scotland)",
|
||||
"Kebab (Middle East)",
|
||||
"Kimchi (Korea)",
|
||||
"Lasagna (Italy)",
|
||||
"Maple Syrup Pancakes (Canada)",
|
||||
"Moussaka (Greece)",
|
||||
"Pad Thai (Thailand)",
|
||||
"Paella (Spain)",
|
||||
"Pancakes (USA)",
|
||||
"Pasta Carbonara (Italy)",
|
||||
"Pavlova (Australia)",
|
||||
"Peking Duck (China)",
|
||||
"Pho (Vietnam)",
|
||||
"Pierogi (Poland)",
|
||||
"Pizza (Italy)",
|
||||
"Poutine (Canada)",
|
||||
"Pretzel (Germany)",
|
||||
"Ramen (Japan)",
|
||||
"Rendang (Indonesia)",
|
||||
"Sashimi (Japan)",
|
||||
"Satay (Indonesia)",
|
||||
"Shepherd's Pie (Ireland)",
|
||||
"Sushi (Japan)",
|
||||
"Tacos (Mexico)",
|
||||
"Tandoori Chicken (India)",
|
||||
"Tortilla (Spain)",
|
||||
"Tzatziki (Greece)",
|
||||
"Wiener Schnitzel (Austria)",
|
||||
]);
|
||||
delegate.update_matches("".into(), cx).detach();
|
||||
|
||||
let picker = Picker::new(delegate, cx);
|
||||
picker.focus(cx);
|
||||
picker
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for PickerStory {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.bg(cx.theme().styles.colors.background)
|
||||
.size_full()
|
||||
.child(self.picker.clone())
|
||||
}
|
||||
}
|
52
crates/storybook/src/stories/scroll.rs
Normal file
52
crates/storybook/src/stories/scroll.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
use gpui::{div, prelude::*, px, Render, SharedString, Styled, View, WindowContext};
|
||||
use ui::prelude::*;
|
||||
use ui::Tooltip;
|
||||
|
||||
pub struct ScrollStory;
|
||||
|
||||
impl ScrollStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<ScrollStory> {
|
||||
cx.new_view(|_cx| ScrollStory)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ScrollStory {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
let color_1 = theme.status().created;
|
||||
let color_2 = theme.status().modified;
|
||||
|
||||
div()
|
||||
.id("parent")
|
||||
.bg(theme.colors().background)
|
||||
.size_full()
|
||||
.overflow_scroll()
|
||||
.children((0..10).map(|row| {
|
||||
div()
|
||||
.w(px(1000.))
|
||||
.h(px(100.))
|
||||
.flex()
|
||||
.flex_row()
|
||||
.children((0..10).map(|column| {
|
||||
let id = SharedString::from(format!("{}, {}", row, column));
|
||||
let bg = if row % 2 == column % 2 {
|
||||
color_1
|
||||
} else {
|
||||
color_2
|
||||
};
|
||||
div()
|
||||
.id(id)
|
||||
.tooltip(move |cx| Tooltip::text(format!("{}, {}", row, column), cx))
|
||||
.bg(bg)
|
||||
.size(px(100. as f32))
|
||||
.when(row >= 5 && column >= 5, |d| {
|
||||
d.overflow_scroll()
|
||||
.child(div().size(px(50.)).bg(color_1))
|
||||
.child(div().size(px(50.)).bg(color_2))
|
||||
.child(div().size(px(50.)).bg(color_1))
|
||||
.child(div().size(px(50.)).bg(color_2))
|
||||
})
|
||||
}))
|
||||
}))
|
||||
}
|
||||
}
|
175
crates/storybook/src/stories/text.rs
Normal file
175
crates/storybook/src/stories/text.rs
Normal file
|
@ -0,0 +1,175 @@
|
|||
use gpui::{
|
||||
div, green, red, HighlightStyle, InteractiveText, IntoElement, ParentElement, Render, Styled,
|
||||
StyledText, View, VisualContext, WindowContext,
|
||||
};
|
||||
use indoc::indoc;
|
||||
use story::*;
|
||||
|
||||
pub struct TextStory;
|
||||
|
||||
impl TextStory {
|
||||
pub fn view(cx: &mut WindowContext) -> View<Self> {
|
||||
cx.new_view(|_cx| Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TextStory {
|
||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
|
||||
StoryContainer::new("Text Story", "crates/storybook/src/stories/text.rs")
|
||||
.children(
|
||||
vec![
|
||||
|
||||
StorySection::new()
|
||||
.child(
|
||||
StoryItem::new("Default", div().bg(gpui::blue()).child("Hello World!"))
|
||||
.usage(indoc! {r##"
|
||||
div()
|
||||
.child("Hello World!")
|
||||
"##
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
StoryItem::new("Wrapping Text",
|
||||
div().max_w_96()
|
||||
.child(
|
||||
concat!(
|
||||
"The quick brown fox jumps over the lazy dog. ",
|
||||
"Meanwhile, the lazy dog decided it was time for a change. ",
|
||||
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||
)
|
||||
)
|
||||
)
|
||||
.description("Set a width or max-width to enable text wrapping.")
|
||||
.usage(indoc! {r##"
|
||||
div()
|
||||
.max_w_96()
|
||||
.child("Some text that you want to wrap.")
|
||||
"##
|
||||
})
|
||||
)
|
||||
.child(
|
||||
StoryItem::new("tbd",
|
||||
div().flex().w_96().child(div().overflow_hidden().child(concat!(
|
||||
"flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ",
|
||||
"Meanwhile, the lazy dog decided it was time for a change. ",
|
||||
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||
)))
|
||||
)
|
||||
)
|
||||
.child(
|
||||
StoryItem::new("Text in Horizontal Flex",
|
||||
div().flex().w_96().bg(red()).child(concat!(
|
||||
"flex-row. width 96. The quick brown fox jumps over the lazy dog. ",
|
||||
"Meanwhile, the lazy dog decided it was time for a change. ",
|
||||
"He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||
))
|
||||
)
|
||||
.usage(indoc! {r##"
|
||||
// NOTE: When rendering text in a horizonal flex container,
|
||||
// Taffy will not pass width constraints down from the parent.
|
||||
// To fix this, render text in a parent with overflow: hidden
|
||||
|
||||
div()
|
||||
.max_w_96()
|
||||
.child("Some text that you want to wrap.")
|
||||
"##
|
||||
})
|
||||
)
|
||||
.child(
|
||||
StoryItem::new("Interactive Text",
|
||||
InteractiveText::new(
|
||||
"interactive",
|
||||
StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
|
||||
(6..11, HighlightStyle {
|
||||
background_color: Some(green()),
|
||||
..Default::default()
|
||||
}),
|
||||
]),
|
||||
)
|
||||
.on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
|
||||
println!("Clicked range {range_ix}");
|
||||
})
|
||||
)
|
||||
.usage(indoc! {r##"
|
||||
InteractiveText::new(
|
||||
"interactive",
|
||||
StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
|
||||
(6..11, HighlightStyle {
|
||||
background_color: Some(green()),
|
||||
..Default::default()
|
||||
}),
|
||||
]),
|
||||
)
|
||||
.on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
|
||||
println!("Clicked range {range_ix}");
|
||||
})
|
||||
"##
|
||||
})
|
||||
)
|
||||
]
|
||||
).into_element()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check all were updated to new style and remove
|
||||
|
||||
// impl Render for TextStory {
|
||||
// type Element = Div;
|
||||
|
||||
// fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||
// v_stack()
|
||||
// .bg(blue())
|
||||
// .child(
|
||||
// div()
|
||||
// .flex()
|
||||
// .child(div().max_w_96().bg(white()).child(concat!(
|
||||
// "max-width: 96. The quick brown fox jumps over the lazy dog. ",
|
||||
// "Meanwhile, the lazy dog decided it was time for a change. ",
|
||||
// "He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||
// ))),
|
||||
// )
|
||||
// .child(div().h_5())
|
||||
// .child(div().flex().flex_col().w_96().bg(white()).child(concat!(
|
||||
// "flex-col. width: 96; The quick brown fox jumps over the lazy dog. ",
|
||||
// "Meanwhile, the lazy dog decided it was time for a change. ",
|
||||
// "He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||
// )))
|
||||
// .child(div().h_5())
|
||||
// .child(
|
||||
// div()
|
||||
// .flex()
|
||||
// .child(div().min_w_96().bg(white()).child(concat!(
|
||||
// "min-width: 96. The quick brown fox jumps over the lazy dog. ",
|
||||
// "Meanwhile, the lazy dog decided it was time for a change. ",
|
||||
// "He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||
// ))))
|
||||
// .child(div().h_5())
|
||||
// .child(div().flex().w_96().bg(white()).child(div().overflow_hidden().child(concat!(
|
||||
// "flex-row. width 96. overflow-hidden. The quick brown fox jumps over the lazy dog. ",
|
||||
// "Meanwhile, the lazy dog decided it was time for a change. ",
|
||||
// "He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||
// ))))
|
||||
// // NOTE: When rendering text in a horizonal flex container,
|
||||
// // Taffy will not pass width constraints down from the parent.
|
||||
// // To fix this, render text in a parent with overflow: hidden
|
||||
// .child(div().h_5())
|
||||
// .child(div().flex().w_96().bg(red()).child(concat!(
|
||||
// "flex-row. width 96. The quick brown fox jumps over the lazy dog. ",
|
||||
// "Meanwhile, the lazy dog decided it was time for a change. ",
|
||||
// "He started daily workout routines, ate healthier and became the fastest dog in town.",
|
||||
// ))).child(
|
||||
// InteractiveText::new(
|
||||
// "interactive",
|
||||
// StyledText::new("Hello world, how is it going?").with_highlights(&cx.text_style(), [
|
||||
// (6..11, HighlightStyle {
|
||||
// background_color: Some(green()),
|
||||
// ..Default::default()
|
||||
// }),
|
||||
// ]),
|
||||
// )
|
||||
// .on_click(vec![2..4, 1..3, 7..9], |range_ix, _cx| {
|
||||
// println!("Clicked range {range_ix}");
|
||||
// })
|
||||
// )
|
||||
// }
|
||||
// }
|
32
crates/storybook/src/stories/viewport_units.rs
Normal file
32
crates/storybook/src/stories/viewport_units.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use gpui::Render;
|
||||
use story::Story;
|
||||
|
||||
use ui::prelude::*;
|
||||
|
||||
pub struct ViewportUnitsStory;
|
||||
|
||||
impl Render for ViewportUnitsStory {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
Story::container().child(
|
||||
div()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.child(
|
||||
div()
|
||||
.w(vw(0.5, cx))
|
||||
.h(vh(0.8, cx))
|
||||
.bg(gpui::red())
|
||||
.text_color(gpui::white())
|
||||
.child("50vw, 80vh"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(vw(0.25, cx))
|
||||
.h(vh(0.33, cx))
|
||||
.bg(gpui::green())
|
||||
.text_color(gpui::white())
|
||||
.child("25vw, 33vh"),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
172
crates/storybook/src/stories/z_index.rs
Normal file
172
crates/storybook/src/stories/z_index.rs
Normal file
|
@ -0,0 +1,172 @@
|
|||
use gpui::{px, rgb, Div, Hsla, IntoElement, Render, RenderOnce};
|
||||
use story::Story;
|
||||
use ui::prelude::*;
|
||||
|
||||
/// A reimplementation of the MDN `z-index` example, found here:
|
||||
/// [https://developer.mozilla.org/en-US/docs/Web/CSS/z-index](https://developer.mozilla.org/en-US/docs/Web/CSS/z-index).
|
||||
pub struct ZIndexStory;
|
||||
|
||||
impl Render for ZIndexStory {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
Story::container().child(Story::title("z-index")).child(
|
||||
div()
|
||||
.flex()
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: auto"))
|
||||
.child(ZIndexExample::new(0)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 1"))
|
||||
.child(ZIndexExample::new(1)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 3"))
|
||||
.child(ZIndexExample::new(3)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 5"))
|
||||
.child(ZIndexExample::new(5)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(px(250.))
|
||||
.child(Story::label("z-index: 7"))
|
||||
.child(ZIndexExample::new(7)),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
trait Styles: Styled + Sized {
|
||||
// Trailing `_` is so we don't collide with `block` style `StyleHelpers`.
|
||||
fn block_(self) -> Self {
|
||||
self.absolute()
|
||||
.w(px(150.))
|
||||
.h(px(50.))
|
||||
.text_color(rgb::<Hsla>(0x000000))
|
||||
}
|
||||
|
||||
fn blue(self) -> Self {
|
||||
self.bg(rgb::<Hsla>(0xe5e8fc))
|
||||
.border_5()
|
||||
.border_color(rgb::<Hsla>(0x112382))
|
||||
.line_height(px(55.))
|
||||
// HACK: Simulate `text-align: center`.
|
||||
.pl(px(24.))
|
||||
}
|
||||
|
||||
fn red(self) -> Self {
|
||||
self.bg(rgb::<Hsla>(0xfce5e7))
|
||||
.border_5()
|
||||
.border_color(rgb::<Hsla>(0xe3a1a7))
|
||||
// HACK: Simulate `text-align: center`.
|
||||
.pl(px(8.))
|
||||
}
|
||||
}
|
||||
|
||||
impl Styles for Div {}
|
||||
|
||||
#[derive(IntoElement)]
|
||||
struct ZIndexExample {
|
||||
z_index: u8,
|
||||
}
|
||||
|
||||
impl RenderOnce for ZIndexExample {
|
||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||
div()
|
||||
.relative()
|
||||
.size_full()
|
||||
// Example element.
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top(px(15.))
|
||||
.left(px(15.))
|
||||
.w(px(180.))
|
||||
.h(px(230.))
|
||||
.bg(rgb::<Hsla>(0xfcfbe5))
|
||||
.text_color(rgb::<Hsla>(0x000000))
|
||||
.border_5()
|
||||
.border_color(rgb::<Hsla>(0xe3e0a1))
|
||||
.line_height(px(215.))
|
||||
// HACK: Simulate `text-align: center`.
|
||||
.pl(px(24.))
|
||||
.z_index(self.z_index)
|
||||
.child(SharedString::from(format!(
|
||||
"z-index: {}",
|
||||
if self.z_index == 0 {
|
||||
"auto".to_string()
|
||||
} else {
|
||||
self.z_index.to_string()
|
||||
}
|
||||
))),
|
||||
)
|
||||
// Blue blocks.
|
||||
.child(
|
||||
div()
|
||||
.blue()
|
||||
.block_()
|
||||
.top(px(0.))
|
||||
.left(px(0.))
|
||||
.z_index(6)
|
||||
.child("z-index: 6"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.blue()
|
||||
.block_()
|
||||
.top(px(30.))
|
||||
.left(px(30.))
|
||||
.z_index(4)
|
||||
.child("z-index: 4"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.blue()
|
||||
.block_()
|
||||
.top(px(60.))
|
||||
.left(px(60.))
|
||||
.z_index(2)
|
||||
.child("z-index: 2"),
|
||||
)
|
||||
// Red blocks.
|
||||
.child(
|
||||
div()
|
||||
.red()
|
||||
.block_()
|
||||
.top(px(150.))
|
||||
.left(px(0.))
|
||||
.child("z-index: auto"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.red()
|
||||
.block_()
|
||||
.top(px(180.))
|
||||
.left(px(30.))
|
||||
.child("z-index: auto"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.red()
|
||||
.block_()
|
||||
.top(px(210.))
|
||||
.left(px(60.))
|
||||
.child("z-index: auto"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ZIndexExample {
|
||||
pub fn new(z_index: u8) -> Self {
|
||||
Self { z_index }
|
||||
}
|
||||
}
|
134
crates/storybook/src/story_selector.rs
Normal file
134
crates/storybook/src/story_selector.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
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,
|
||||
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::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))
|
||||
}
|
||||
}
|
137
crates/storybook/src/storybook.rs
Normal file
137
crates/storybook/src/storybook.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
mod assets;
|
||||
mod stories;
|
||||
mod story_selector;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use clap::Parser;
|
||||
use dialoguer::FuzzySelect;
|
||||
use gpui::{
|
||||
div, px, size, AnyView, AppContext, Bounds, Render, ViewContext, VisualContext, WindowBounds,
|
||||
WindowOptions,
|
||||
};
|
||||
use log::LevelFilter;
|
||||
use settings::{default_settings, Settings, SettingsStore};
|
||||
use simplelog::SimpleLogger;
|
||||
use strum::IntoEnumIterator;
|
||||
use theme2::{ThemeRegistry, ThemeSettings};
|
||||
use ui::prelude::*;
|
||||
|
||||
use crate::assets::Assets;
|
||||
use crate::story_selector::{ComponentStory, StorySelector};
|
||||
pub use indoc::indoc;
|
||||
|
||||
// gpui::actions! {
|
||||
// storybook,
|
||||
// [ToggleInspector]
|
||||
// }
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(value_enum)]
|
||||
story: Option<StorySelector>,
|
||||
|
||||
/// The name of the theme to use in the storybook.
|
||||
///
|
||||
/// If not provided, the default theme will be used.
|
||||
#[arg(long)]
|
||||
theme: Option<String>,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// unsafe { backtrace_on_stack_overflow::enable() };
|
||||
|
||||
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
|
||||
|
||||
let args = Args::parse();
|
||||
|
||||
let story_selector = args.story.clone().unwrap_or_else(|| {
|
||||
let stories = ComponentStory::iter().collect::<Vec<_>>();
|
||||
|
||||
let selection = FuzzySelect::new()
|
||||
.with_prompt("Choose a story to run:")
|
||||
.items(&stories)
|
||||
.interact()
|
||||
.unwrap();
|
||||
|
||||
StorySelector::Component(stories[selection])
|
||||
});
|
||||
let theme_name = args.theme.unwrap_or("One Dark".to_string());
|
||||
|
||||
let asset_source = Arc::new(Assets);
|
||||
gpui::App::production(asset_source).run(move |cx| {
|
||||
load_embedded_fonts(cx).unwrap();
|
||||
|
||||
let mut store = SettingsStore::default();
|
||||
store
|
||||
.set_default_settings(default_settings().as_ref(), cx)
|
||||
.unwrap();
|
||||
cx.set_global(store);
|
||||
|
||||
theme2::init(theme2::LoadThemes::All, cx);
|
||||
|
||||
let selector = story_selector;
|
||||
|
||||
let theme_registry = cx.global::<ThemeRegistry>();
|
||||
let mut theme_settings = ThemeSettings::get_global(cx).clone();
|
||||
theme_settings.active_theme = theme_registry.get(&theme_name).unwrap();
|
||||
ThemeSettings::override_global(theme_settings, cx);
|
||||
|
||||
language::init(cx);
|
||||
editor::init(cx);
|
||||
|
||||
let _window = cx.open_window(
|
||||
WindowOptions {
|
||||
bounds: WindowBounds::Fixed(Bounds {
|
||||
origin: Default::default(),
|
||||
size: size(px(1500.), px(780.)).into(),
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
move |cx| {
|
||||
let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
|
||||
cx.set_rem_size(ui_font_size);
|
||||
|
||||
cx.new_view(|cx| StoryWrapper::new(selector.story(cx)))
|
||||
},
|
||||
);
|
||||
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct StoryWrapper {
|
||||
story: AnyView,
|
||||
}
|
||||
|
||||
impl StoryWrapper {
|
||||
pub(crate) fn new(story: AnyView) -> Self {
|
||||
Self { story }
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for StoryWrapper {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.size_full()
|
||||
.font("Zed Mono")
|
||||
.child(self.story.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn load_embedded_fonts(cx: &AppContext) -> gpui::Result<()> {
|
||||
let font_paths = cx.asset_source().list("fonts")?;
|
||||
let mut embedded_fonts = Vec::new();
|
||||
for font_path in font_paths {
|
||||
if font_path.ends_with(".ttf") {
|
||||
let font_bytes = cx.asset_source().load(&font_path)?.to_vec();
|
||||
embedded_fonts.push(Arc::from(font_bytes));
|
||||
}
|
||||
}
|
||||
|
||||
cx.text_system().add_fonts(&embedded_fonts)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue