mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-30 14:17:02 +00:00
Extend Story components, allow linking to story file
Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
This commit is contained in:
parent
4ace342cf0
commit
c041799c6a
5 changed files with 344 additions and 71 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -9211,6 +9211,7 @@ name = "story"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"gpui2",
|
"gpui2",
|
||||||
|
"smallvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -9224,6 +9225,7 @@ dependencies = [
|
||||||
"editor2",
|
"editor2",
|
||||||
"fuzzy2",
|
"fuzzy2",
|
||||||
"gpui2",
|
"gpui2",
|
||||||
|
"indoc",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"language2",
|
"language2",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -8,3 +8,4 @@ publish = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
smallvec.workspace = true
|
||||||
|
|
|
@ -1,22 +1,109 @@
|
||||||
use gpui::prelude::*;
|
use gpui::{div, hsla, AnyElement, Div, ElementId, Hsla, SharedString, Stateful, WindowContext};
|
||||||
use gpui::{div, hsla, Div, SharedString};
|
use gpui::{prelude::*, px};
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
|
pub fn reasonably_unique_id() -> String {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
let timestamp = now.duration_since(UNIX_EPOCH).unwrap();
|
||||||
|
|
||||||
|
let cnt = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
|
let id = format!("{}_{}", timestamp.as_nanos(), cnt);
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct StoryColor {
|
||||||
|
pub primary: Hsla,
|
||||||
|
pub secondary: Hsla,
|
||||||
|
pub border: Hsla,
|
||||||
|
pub background: Hsla,
|
||||||
|
pub card_background: Hsla,
|
||||||
|
pub divider: Hsla,
|
||||||
|
pub link: Hsla,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StoryColor {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
primary: hsla(216. / 360., 11. / 100., 0. / 100., 1.),
|
||||||
|
secondary: hsla(216. / 360., 11. / 100., 16. / 100., 1.),
|
||||||
|
border: hsla(216. / 360., 11. / 100., 91. / 100., 1.),
|
||||||
|
background: hsla(0. / 360., 0. / 100., 100. / 100., 1.),
|
||||||
|
card_background: hsla(0. / 360., 0. / 100., 96. / 100., 1.),
|
||||||
|
divider: hsla(216. / 360., 11. / 100., 91. / 100., 1.),
|
||||||
|
link: hsla(206. / 360., 100. / 100., 50. / 100., 1.),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn story_color() -> StoryColor {
|
||||||
|
StoryColor::new()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Story {}
|
pub struct Story {}
|
||||||
|
|
||||||
impl Story {
|
impl Story {
|
||||||
pub fn container() -> Div {
|
pub fn container() -> Div {
|
||||||
div().size_full().flex().flex_col().pt_2().px_4().bg(hsla(
|
div()
|
||||||
0. / 360.,
|
.size_full()
|
||||||
0. / 100.,
|
.flex()
|
||||||
100. / 100.,
|
.flex_col()
|
||||||
1.,
|
.bg(story_color().background)
|
||||||
))
|
}
|
||||||
|
|
||||||
|
// TODO: Move all stories to container2, then rename
|
||||||
|
pub fn container2<T>(relative_path: &'static str) -> Div {
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.bg(story_color().background)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.justify_between()
|
||||||
|
.p_2()
|
||||||
|
.border_b()
|
||||||
|
.border_color(story_color().border)
|
||||||
|
.child(Story::title_for::<T>())
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(story_color().primary)
|
||||||
|
.child(Story::open_story_link(relative_path)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_story_link(relative_path: &'static str) -> impl Element {
|
||||||
|
let path = PathBuf::from_iter([relative_path]);
|
||||||
|
div()
|
||||||
|
.id(SharedString::from(format!("id_{}", relative_path)))
|
||||||
|
.text_xs()
|
||||||
|
.text_color(story_color().primary)
|
||||||
|
.on_click({
|
||||||
|
let path = path.clone();
|
||||||
|
|
||||||
|
move |_event, _cx| {
|
||||||
|
let path = format!("{}:0:0", path.to_string_lossy());
|
||||||
|
|
||||||
|
std::process::Command::new("zed").arg(path).spawn().ok();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.child(Story::link(path.to_string_lossy().to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn title(title: impl Into<SharedString>) -> impl Element {
|
pub fn title(title: impl Into<SharedString>) -> impl Element {
|
||||||
div()
|
div()
|
||||||
.text_xl()
|
.text_xs()
|
||||||
.text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.))
|
.text_color(story_color().primary)
|
||||||
.child(title.into())
|
.child(title.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,12 +111,173 @@ impl Story {
|
||||||
Self::title(std::any::type_name::<T>())
|
Self::title(std::any::type_name::<T>())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn section() -> Div {
|
||||||
|
div().mt_4().mb_2()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn section_title() -> Div {
|
||||||
|
div().text_lg().text_color(story_color().primary)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn group() -> Div {
|
||||||
|
div().my_2().bg(story_color().background)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn code_block(code: impl Into<SharedString>) -> Div {
|
||||||
|
div()
|
||||||
|
.size_full()
|
||||||
|
.p_2()
|
||||||
|
.bg(gpui::black())
|
||||||
|
.border()
|
||||||
|
.border_color(story_color().border)
|
||||||
|
.rounded_md()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(gpui::white())
|
||||||
|
.child(code.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn divider() -> Div {
|
||||||
|
div().my_2().h(px(1.)).bg(story_color().divider)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn link(link: impl Into<SharedString>) -> impl Element {
|
||||||
|
div()
|
||||||
|
.id(ElementId::from(SharedString::from(reasonably_unique_id())))
|
||||||
|
.text_xs()
|
||||||
|
.text_color(story_color().link)
|
||||||
|
.cursor(gpui::CursorStyle::PointingHand)
|
||||||
|
.child(link.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(description: impl Into<SharedString>) -> impl Element {
|
||||||
|
div()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(story_color().secondary)
|
||||||
|
.min_w_96()
|
||||||
|
.child(description.into())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn label(label: impl Into<SharedString>) -> impl Element {
|
pub fn label(label: impl Into<SharedString>) -> impl Element {
|
||||||
div()
|
div()
|
||||||
.mt_4()
|
|
||||||
.mb_2()
|
|
||||||
.text_xs()
|
.text_xs()
|
||||||
.text_color(hsla(0. / 360., 0. / 100., 0. / 100., 1.))
|
.text_color(story_color().primary)
|
||||||
.child(label.into())
|
.child(label.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Note: Not ui::v_stack() as the story crate doesn't depend on the ui crate.
|
||||||
|
pub fn v_stack() -> Div {
|
||||||
|
div().flex().flex_col().gap_1()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct StoryItem {
|
||||||
|
label: SharedString,
|
||||||
|
item: AnyElement,
|
||||||
|
description: Option<SharedString>,
|
||||||
|
usage: Option<SharedString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StoryItem {
|
||||||
|
pub fn new(label: impl Into<SharedString>, item: impl IntoElement) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.into(),
|
||||||
|
item: item.into_any_element(),
|
||||||
|
description: None,
|
||||||
|
usage: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(mut self, description: impl Into<SharedString>) -> Self {
|
||||||
|
self.description = Some(description.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn usage(mut self, code: impl Into<SharedString>) -> Self {
|
||||||
|
self.usage = Some(code.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for StoryItem {
|
||||||
|
type Rendered = Div;
|
||||||
|
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
|
div()
|
||||||
|
.my_2()
|
||||||
|
.flex()
|
||||||
|
.w_full()
|
||||||
|
.child(
|
||||||
|
Story::v_stack()
|
||||||
|
.px_2()
|
||||||
|
.flex_none()
|
||||||
|
.w_1_2()
|
||||||
|
.min_h_px()
|
||||||
|
.child(Story::label(self.label))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.rounded_sm()
|
||||||
|
.bg(story_color().card_background)
|
||||||
|
.border()
|
||||||
|
.border_color(story_color().border)
|
||||||
|
.child(self.item),
|
||||||
|
)
|
||||||
|
.when_some(self.description, |this, description| {
|
||||||
|
this.child(Story::description(description))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Story::v_stack()
|
||||||
|
.px_2()
|
||||||
|
.flex_none()
|
||||||
|
.w_1_2()
|
||||||
|
.min_h_px()
|
||||||
|
.when_some(self.usage, |this, usage| {
|
||||||
|
this.child(Story::label("Usage"))
|
||||||
|
.child(Story::code_block(usage))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct StorySection {
|
||||||
|
description: Option<SharedString>,
|
||||||
|
children: SmallVec<[AnyElement; 2]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StorySection {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
description: None,
|
||||||
|
children: SmallVec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(mut self, description: impl Into<SharedString>) -> Self {
|
||||||
|
self.description = Some(description.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for StorySection {
|
||||||
|
type Rendered = Div;
|
||||||
|
|
||||||
|
fn render(self, _cx: &mut WindowContext) -> Self::Rendered {
|
||||||
|
Story::section()
|
||||||
|
// Section title
|
||||||
|
.py_2()
|
||||||
|
// Section description
|
||||||
|
.when_some(self.description.clone(), |section, description| {
|
||||||
|
section.child(Story::description(description))
|
||||||
|
})
|
||||||
|
.child(div().flex().flex_col().gap_2().children(self.children))
|
||||||
|
.child(Story::divider())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParentElement for StorySection {
|
||||||
|
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> {
|
||||||
|
&mut self.children
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ editor = { package = "editor2", path = "../editor2" }
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
fuzzy = { package = "fuzzy2", path = "../fuzzy2" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
|
indoc.workspace = true
|
||||||
itertools = "0.11.0"
|
itertools = "0.11.0"
|
||||||
language = { package = "language2", path = "../language2" }
|
language = { package = "language2", path = "../language2" }
|
||||||
log.workspace = true
|
log.workspace = true
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use gpui::{
|
use gpui::{div, Div, ParentElement, Render, Styled, View, VisualContext, WindowContext};
|
||||||
blue, div, green, red, white, Div, HighlightStyle, InteractiveText, ParentElement, Render,
|
use indoc::indoc;
|
||||||
Styled, StyledText, View, VisualContext, WindowContext,
|
use story::*;
|
||||||
};
|
|
||||||
use ui::v_stack;
|
|
||||||
|
|
||||||
pub struct TextStory;
|
pub struct TextStory;
|
||||||
|
|
||||||
|
@ -16,59 +14,82 @@ impl Render for TextStory {
|
||||||
type Element = Div;
|
type Element = Div;
|
||||||
|
|
||||||
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> Self::Element {
|
||||||
v_stack()
|
// let # = "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.";
|
||||||
.bg(blue())
|
|
||||||
.child(
|
Story::container2::<TextStory>("crates/storybook2/src/stories/text.rs").child(
|
||||||
div()
|
StorySection::new().child(
|
||||||
.flex()
|
StoryItem::new(
|
||||||
.child(div().max_w_96().bg(white()).child(concat!(
|
"Default Text",
|
||||||
"max-width: 96. The quick brown fox jumps over the lazy dog. ",
|
div().flex().child(div().max_w_96().child("foo")),
|
||||||
"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| {
|
.description("Text with a max-width. Wraps based on set max-width.")
|
||||||
println!("Clicked range {range_ix}");
|
.usage(indoc! {r##"
|
||||||
})
|
div().max_w_96()
|
||||||
)
|
.child("Some text that you want to wrap.")
|
||||||
|
"##
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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}");
|
||||||
|
// })
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
Loading…
Reference in a new issue