T07a navigation tabs and paths: tabs, editor tabs, tab overflow menu, breadcrumb, pagination, steps and wizard; shared slide, list trigger and tab stop
This commit is contained in:
50 files changed
+2490
-330
No files matched your search
@@ -4,29 +4,13 @@ use ely_gpui_component::{
|
||||
},
|
||||
primitives::IconName,
|
||||
};
|
||||
use gpui::{App, Entity, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{code, section, specimen},
|
||||
ui::{code, keep, section, set, specimen},
|
||||
};
|
||||
|
||||
pub(super) fn keep<T: 'static>(
|
||||
key: &'static str,
|
||||
init: impl FnOnce() -> T,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<T> {
|
||||
window.use_keyed_state(key, cx, move |_, _| init())
|
||||
}
|
||||
|
||||
pub(super) fn set<T: 'static>(state: &Entity<T>, value: T, cx: &mut App) {
|
||||
state.update(cx, |state, cx| {
|
||||
*state = value;
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
|
||||
fn values(list: &[&'static str]) -> Vec<SharedString> {
|
||||
list.iter()
|
||||
.map(|value| SharedString::from(*value))
|
||||
|
||||
@@ -4,10 +4,9 @@ use ely_gpui_component::{
|
||||
};
|
||||
use gpui::{App, Hsla, IntoElement, ParentElement, Styled, Window, div, px, rgb, rgba};
|
||||
|
||||
use super::choices::{keep, set};
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{code, section, specimen, specimens},
|
||||
ui::{code, keep, section, set, specimen, specimens},
|
||||
};
|
||||
|
||||
fn hex(color: Hsla) -> String {
|
||||
|
||||
@@ -8,13 +8,10 @@ use ely_gpui_component::forms::{
|
||||
use gpui::{App, Div, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
use jiff::civil::{Date, DateTime, Time, date, time};
|
||||
|
||||
use super::{
|
||||
choices::{keep, set},
|
||||
text::field,
|
||||
};
|
||||
use super::text::field;
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{code, section, specimen, specimens},
|
||||
ui::{code, keep, section, set, specimen, specimens},
|
||||
};
|
||||
|
||||
/// Today, fixed so captures stay the same from day to day.
|
||||
|
||||
@@ -3,10 +3,9 @@ use std::path::PathBuf;
|
||||
use ely_gpui_component::forms::{DropZone, FileInput};
|
||||
use gpui::{App, IntoElement, ParentElement, Styled, Window, div, px};
|
||||
|
||||
use super::choices::{keep, set};
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{section, specimen, specimens},
|
||||
ui::{keep, section, set, specimen, specimens},
|
||||
};
|
||||
|
||||
fn names(paths: &[PathBuf]) -> String {
|
||||
|
||||
@@ -8,13 +8,10 @@ use ely_gpui_component::{
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
|
||||
use super::{
|
||||
choices::{keep, set},
|
||||
text::field,
|
||||
};
|
||||
use super::text::field;
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{section, specimen, specimens},
|
||||
ui::{keep, section, set, specimen, specimens},
|
||||
};
|
||||
|
||||
pub fn signature(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
|
||||
@@ -4,13 +4,10 @@ use ely_gpui_component::{
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
|
||||
use super::{
|
||||
choices::{keep, set},
|
||||
text::field,
|
||||
};
|
||||
use super::text::field;
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{blocked, section, specimen, specimens},
|
||||
ui::{blocked, keep, section, set, specimen, specimens},
|
||||
};
|
||||
|
||||
fn cities() -> Vec<Choice> {
|
||||
|
||||
@@ -9,13 +9,10 @@ use gpui::{
|
||||
App, AppContext, Entity, IntoElement, ParentElement, Styled, Window, div, prelude::*, px,
|
||||
};
|
||||
|
||||
use super::{
|
||||
choices::{keep, set},
|
||||
text::field,
|
||||
};
|
||||
use super::text::field;
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{section, specimen},
|
||||
ui::{keep, section, set, specimen},
|
||||
};
|
||||
|
||||
type Saved = (String, String, String, Vec<String>);
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
use ely_gpui_component::forms::{Choice, ChoiceChips, Knob, RangeSlider, Rating, Slider, Stepper};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
|
||||
use super::choices::{keep, set};
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{section, specimen, specimens},
|
||||
ui::{keep, section, set, specimen, specimens},
|
||||
};
|
||||
|
||||
pub fn chips(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod buttons;
|
||||
mod forms;
|
||||
mod layout;
|
||||
mod navigation;
|
||||
mod primitives;
|
||||
mod shell;
|
||||
mod theme;
|
||||
@@ -27,6 +28,7 @@ pub const ALL: &[Page] = &[
|
||||
shell::PAGE,
|
||||
buttons::PAGE,
|
||||
forms::PAGE,
|
||||
navigation::PAGE,
|
||||
theme::PAGE,
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
mod paths;
|
||||
mod tabs;
|
||||
|
||||
use gpui::{AnyElement, App, IntoElement, ParentElement, Window, div};
|
||||
|
||||
use super::Page;
|
||||
use crate::script::Step;
|
||||
|
||||
pub const PAGE: Page = Page {
|
||||
number: 7,
|
||||
slug: "navigation",
|
||||
title: "Navigation",
|
||||
summary: "Tabs, paths, pages and steps: where you are, and the ways out.",
|
||||
render,
|
||||
script: SCRIPT,
|
||||
};
|
||||
|
||||
const SCRIPT: &[Step] = &[
|
||||
Step::Rest,
|
||||
Step::DownAt("tabs-top", 128.0, 14.0),
|
||||
Step::UpAt("tabs-top", 128.0, 14.0),
|
||||
Step::Wait(500),
|
||||
Step::Shot("tabs"),
|
||||
Step::Hover("editor-tabs"),
|
||||
Step::Wait(300),
|
||||
Step::Shot("editor-hover"),
|
||||
Step::DownAt("editor-tabs", 544.0, 16.0),
|
||||
Step::UpAt("editor-tabs", 544.0, 16.0),
|
||||
Step::Wait(300),
|
||||
Step::Shot("editor-overflow"),
|
||||
Step::Key("escape"),
|
||||
Step::DownAt("breadcrumb", 173.0, 10.0),
|
||||
Step::UpAt("breadcrumb", 173.0, 10.0),
|
||||
Step::Wait(300),
|
||||
Step::Key("down"),
|
||||
Step::Wait(200),
|
||||
Step::Shot("breadcrumb-open"),
|
||||
Step::Key("enter"),
|
||||
Step::DownAt("pagination", 150.0, 10.0),
|
||||
Step::UpAt("pagination", 150.0, 10.0),
|
||||
Step::Wait(300),
|
||||
Step::Shot("pagination"),
|
||||
Step::DownAt("wizard", 531.0, 173.0),
|
||||
Step::UpAt("wizard", 531.0, 173.0),
|
||||
Step::Wait(500),
|
||||
Step::Shot("wizard"),
|
||||
Step::Rest,
|
||||
];
|
||||
|
||||
fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
div()
|
||||
.child(tabs::tabs(window, cx))
|
||||
.child(tabs::editor_tabs(window, cx))
|
||||
.child(paths::breadcrumb(window, cx))
|
||||
.child(paths::pagination(window, cx))
|
||||
.child(paths::steps(window, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
use ely_gpui_component::{
|
||||
forms::Choice,
|
||||
navigation::{Breadcrumb, Crumb, Pagination, Steps, Wizard},
|
||||
primitives::IconName,
|
||||
typography::Paragraph,
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{keep, section, set, specimen, specimens},
|
||||
};
|
||||
|
||||
pub fn breadcrumb(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let project = keep("crumb-project", || SharedString::from("ely"), window, cx);
|
||||
let went = keep("crumb-went", || None::<String>, window, cx);
|
||||
let now = project.read(cx).clone();
|
||||
let caption = went
|
||||
.read(cx)
|
||||
.clone()
|
||||
.unwrap_or_else(|| "click a level; one with a chevron lists its siblings".into());
|
||||
section(
|
||||
"Breadcrumb",
|
||||
"Each level is a place. A level with siblings opens them, the way an editor's path bar does.",
|
||||
cx,
|
||||
)
|
||||
.child(specimen(
|
||||
caption,
|
||||
probe(
|
||||
"breadcrumb",
|
||||
Breadcrumb::new(
|
||||
"breadcrumb",
|
||||
[
|
||||
Crumb::new("home", "Home").icon(IconName::House),
|
||||
Crumb::new("projects", "Projects"),
|
||||
Crumb::new(now.clone(), now.clone()).siblings([
|
||||
Choice::new("ely", "ely"),
|
||||
Choice::new("zed", "zed"),
|
||||
Choice::new("helix", "helix"),
|
||||
]),
|
||||
Crumb::new("src", "src"),
|
||||
Crumb::new("main", "main.rs"),
|
||||
],
|
||||
)
|
||||
.on_select(move |level, value, _, cx| {
|
||||
if level == 2 {
|
||||
set(&project, value.clone(), cx);
|
||||
}
|
||||
set(&went, Some(format!("went to {value} at level {level}")), cx);
|
||||
}),
|
||||
),
|
||||
cx,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn pagination(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let page = keep("pagination-page", || 6usize, window, cx);
|
||||
let now = *page.read(cx);
|
||||
section(
|
||||
"Pagination",
|
||||
"The first, the last, and the pages around this one. A gap stands for two or more pages.",
|
||||
cx,
|
||||
)
|
||||
.child(specimen(
|
||||
format!("page {now} of 20"),
|
||||
probe(
|
||||
"pagination",
|
||||
Pagination::new("pagination", now, 20).on_change(move |to, _, cx| set(&page, to, cx)),
|
||||
),
|
||||
cx,
|
||||
))
|
||||
}
|
||||
|
||||
fn setup_steps() -> [Choice; 3] {
|
||||
[
|
||||
Choice::new("account", "Account").note("Name and email"),
|
||||
Choice::new("plan", "Plan").note("Seats and billing"),
|
||||
Choice::new("confirm", "Confirm").note("Check and start"),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn steps(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let step = keep("wizard-step", || 0usize, window, cx);
|
||||
let finished = keep("wizard-finished", || false, window, cx);
|
||||
let (now, done) = (*step.read(cx), *finished.read(cx));
|
||||
let body = match now {
|
||||
0 => "Who runs this workspace? One name, one address; both can change later.",
|
||||
1 => "Ten seats to start. Billing runs monthly and stops when you do.",
|
||||
_ => "Ready. The workspace opens as soon as you finish.",
|
||||
};
|
||||
let (walk, finish) = (step.clone(), finished);
|
||||
section(
|
||||
"Steps / Wizard",
|
||||
"Finished steps carry a check and take you back. The line into the current step fills as you advance.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
specimens()
|
||||
.child(specimen(
|
||||
"vertical",
|
||||
div().w(px(240.0)).child(Steps::new("steps-vertical", setup_steps(), 1).vertical()),
|
||||
cx,
|
||||
))
|
||||
.child(specimen(
|
||||
if done { "finished" } else { "wizard" },
|
||||
probe(
|
||||
"wizard",
|
||||
div().w(px(560.0)).child(
|
||||
Wizard::new("wizard", setup_steps(), now)
|
||||
.content(div().h(px(72.0)).child(Paragraph::new(body)))
|
||||
.on_step(move |to, _, cx| set(&walk, to, cx))
|
||||
.on_finish(move |_, cx| set(&finish, true, cx)),
|
||||
),
|
||||
),
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
use ely_gpui_component::{
|
||||
forms::Choice,
|
||||
navigation::{EditorTab, EditorTabs, TabPlacement, Tabs},
|
||||
primitives::IconName,
|
||||
typography::Paragraph,
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{keep, section, set, specimen, specimens},
|
||||
};
|
||||
|
||||
fn panel(text: &'static str) -> impl IntoElement {
|
||||
div().py_2().child(Paragraph::new(text))
|
||||
}
|
||||
|
||||
pub fn tabs(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let top = keep("tabs-top", || SharedString::from("overview"), window, cx);
|
||||
let left = keep("tabs-left", || SharedString::from("general"), window, cx);
|
||||
let bottom = keep("tabs-bottom", || SharedString::from("console"), window, cx);
|
||||
let (top_now, left_now, bottom_now) = (
|
||||
top.read(cx).clone(),
|
||||
left.read(cx).clone(),
|
||||
bottom.read(cx).clone(),
|
||||
);
|
||||
let top_panel = match top_now.as_ref() {
|
||||
"overview" => "Three services, all healthy. The last deploy went out an hour ago.",
|
||||
"activity" => "Twelve events since noon: four deploys, eight config reads.",
|
||||
_ => "Alerts go to the on-call channel. Deploys need one review.",
|
||||
};
|
||||
section(
|
||||
"Tabs",
|
||||
"A line slides to the chosen tab. Arrows move it; disabled tabs are skipped.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
specimens()
|
||||
.child(specimen(
|
||||
"top",
|
||||
probe(
|
||||
"tabs-top",
|
||||
div().w(px(420.0)).child(
|
||||
Tabs::new(
|
||||
"tabs-top",
|
||||
[
|
||||
Choice::new("overview", "Overview"),
|
||||
Choice::new("activity", "Activity").note("12"),
|
||||
Choice::new("settings", "Settings"),
|
||||
Choice::new("archived", "Archived").disabled(),
|
||||
],
|
||||
top_now,
|
||||
)
|
||||
.panel(panel(top_panel))
|
||||
.on_change(move |next, _, cx| set(&top, next.clone(), cx)),
|
||||
),
|
||||
),
|
||||
cx,
|
||||
))
|
||||
.child(specimen(
|
||||
"left",
|
||||
div().w(px(420.0)).child(
|
||||
Tabs::new(
|
||||
"tabs-left",
|
||||
[
|
||||
Choice::new("general", "General").icon(IconName::Settings),
|
||||
Choice::new("appearance", "Appearance").icon(IconName::Palette),
|
||||
Choice::new("keys", "Keyboard").icon(IconName::Keyboard),
|
||||
],
|
||||
left_now,
|
||||
)
|
||||
.placement(TabPlacement::Left)
|
||||
.panel(panel(
|
||||
"Settings for this section sit here, beside their tab.",
|
||||
))
|
||||
.on_change(move |next, _, cx| set(&left, next.clone(), cx)),
|
||||
),
|
||||
cx,
|
||||
))
|
||||
.child(specimen(
|
||||
"bottom",
|
||||
div().w(px(420.0)).child(
|
||||
Tabs::new(
|
||||
"tabs-bottom",
|
||||
[
|
||||
Choice::new("console", "Console"),
|
||||
Choice::new("output", "Output"),
|
||||
Choice::new("problems", "Problems").note("3"),
|
||||
],
|
||||
bottom_now,
|
||||
)
|
||||
.placement(TabPlacement::Bottom)
|
||||
.panel(panel("Logs stream above their tabs, as a panel dock does."))
|
||||
.on_change(move |next, _, cx| set(&bottom, next.clone(), cx)),
|
||||
),
|
||||
cx,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
/// An open document, as the demo app keeps it.
|
||||
#[derive(Clone)]
|
||||
struct Doc {
|
||||
id: &'static str,
|
||||
title: &'static str,
|
||||
dirty: bool,
|
||||
pinned: bool,
|
||||
preview: bool,
|
||||
}
|
||||
|
||||
fn open_docs() -> Vec<Doc> {
|
||||
let doc = |id, title| Doc {
|
||||
id,
|
||||
title,
|
||||
dirty: false,
|
||||
pinned: false,
|
||||
preview: false,
|
||||
};
|
||||
vec![
|
||||
Doc {
|
||||
pinned: true,
|
||||
..doc("cargo", "Cargo.toml")
|
||||
},
|
||||
doc("main", "main.rs"),
|
||||
Doc {
|
||||
dirty: true,
|
||||
..doc("lib", "lib.rs")
|
||||
},
|
||||
doc("theme", "theme.rs"),
|
||||
doc("tokens", "tokens.rs"),
|
||||
Doc {
|
||||
preview: true,
|
||||
..doc("readme", "README.md")
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn edit(docs: &gpui::Entity<Vec<Doc>>, id: &SharedString, cx: &mut App, change: impl Fn(&mut Doc)) {
|
||||
docs.update(cx, |docs, cx| {
|
||||
docs.iter_mut()
|
||||
.filter(|doc| doc.id == id.as_ref())
|
||||
.for_each(change);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
||||
pub fn editor_tabs(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let docs = keep("editor-docs", open_docs, window, cx);
|
||||
let chosen = keep("editor-chosen", || SharedString::from("main"), window, cx);
|
||||
let tabs: Vec<EditorTab> = docs
|
||||
.read(cx)
|
||||
.iter()
|
||||
.map(|doc| {
|
||||
let tab = EditorTab::new(doc.id, doc.title)
|
||||
.dirty(doc.dirty)
|
||||
.pinned(doc.pinned)
|
||||
.preview(doc.preview);
|
||||
if doc.title.ends_with(".rs") {
|
||||
tab.icon(IconName::FileText)
|
||||
} else {
|
||||
tab
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let (closing, pinning, keeping, moving) = (docs.clone(), docs.clone(), docs.clone(), docs);
|
||||
let (picked, select) = (chosen.read(cx).clone(), chosen);
|
||||
section(
|
||||
"EditorTabs / TabOverflowMenu",
|
||||
"Close, pin, drag to reorder. A dot marks unsaved work until you hover; italics mark a preview. The chevron lists every tab.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"editor-tabs",
|
||||
div().w(px(560.0)).child(
|
||||
EditorTabs::new("editor-tabs", tabs)
|
||||
.selected(picked)
|
||||
.on_select(move |id, _, cx| set(&select, id.clone(), cx))
|
||||
.on_close(move |id, _, cx| {
|
||||
closing.update(cx, |docs, cx| {
|
||||
docs.retain(|doc| doc.id != id.as_ref());
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.on_pin(move |id, _, cx| edit(&pinning, id, cx, |doc| doc.pinned = false))
|
||||
.on_keep(move |id, _, cx| edit(&keeping, id, cx, |doc| doc.preview = false))
|
||||
.on_reorder(move |from, to, _, cx| {
|
||||
moving.update(cx, |docs, cx| {
|
||||
let doc = docs.remove(from);
|
||||
docs.insert(to, doc);
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
),
|
||||
))
|
||||
}
|
||||
+21
-1
@@ -1,5 +1,7 @@
|
||||
use ely_gpui_component::theme::{ActiveTheme, TextSize};
|
||||
use gpui::{App, Div, FontWeight, IntoElement, ParentElement, SharedString, Styled, div};
|
||||
use gpui::{
|
||||
App, Div, Entity, FontWeight, IntoElement, ParentElement, SharedString, Styled, Window, div,
|
||||
};
|
||||
|
||||
/// Titled block of demos.
|
||||
pub fn section(title: impl Into<SharedString>, note: impl Into<SharedString>, cx: &App) -> Div {
|
||||
@@ -74,3 +76,21 @@ pub fn blocked(text: impl Into<SharedString>, cx: &App) -> impl IntoElement {
|
||||
.child(div().size_1p5().rounded_full().bg(theme.colors.warning))
|
||||
.child(text.into())
|
||||
}
|
||||
|
||||
/// A demo's state, kept across frames under `key`.
|
||||
pub fn keep<T: 'static>(
|
||||
key: &'static str,
|
||||
init: impl FnOnce() -> T,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<T> {
|
||||
window.use_keyed_state(key, cx, move |_, _| init())
|
||||
}
|
||||
|
||||
/// Replaces a demo's state and redraws.
|
||||
pub fn set<T: 'static>(state: &Entity<T>, value: T, cx: &mut App) {
|
||||
state.update(cx, |state, cx| {
|
||||
*state = value;
|
||||
cx.notify();
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user