component: Add component
and component_preview
crates to power UI components (#24456)
This PR formalizes design components with the Component and ComponentPreview traits. You can open the preview UI with `workspace: open component preview`. Component previews no longer need to return `Self` allowing for more complex previews, and previews of components like `ui::Tooltip` that supplement other components rather than are rendered by default. `cargo-machete` incorrectly identifies `linkme` as an unused dep on crates that have components deriving `IntoComponent`, so you may need to add this to that crate's `Cargo.toml`: ```toml # cargo-machete doesn't understand that linkme is used in the component macro [package.metadata.cargo-machete] ignored = ["linkme"] ``` Release Notes: - N/A --------- Co-authored-by: Marshall Bowers <git@maxdeviant.com>
This commit is contained in:
parent
56cfc60875
commit
8f1ff189cc
36 changed files with 1582 additions and 976 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
@ -2942,6 +2942,28 @@ dependencies = [
|
||||||
"gpui",
|
"gpui",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "component"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"collections",
|
||||||
|
"gpui",
|
||||||
|
"linkme",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"theme",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "component_preview"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"component",
|
||||||
|
"gpui",
|
||||||
|
"ui",
|
||||||
|
"workspace",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
@ -7280,6 +7302,26 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linkme"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "566336154b9e58a4f055f6dd4cbab62c7dc0826ce3c0a04e63b2d2ecd784cdae"
|
||||||
|
dependencies = [
|
||||||
|
"linkme-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linkme-impl"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "edbe595006d355eaf9ae11db92707d4338cd2384d16866131cc1afdbdd35d8d9"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.90",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
|
@ -8693,9 +8735,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.20.2"
|
version = "1.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oo7"
|
name = "oo7"
|
||||||
|
@ -14320,8 +14362,10 @@ name = "ui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"component",
|
||||||
"gpui",
|
"gpui",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
|
"linkme",
|
||||||
"menu",
|
"menu",
|
||||||
"serde",
|
"serde",
|
||||||
"settings",
|
"settings",
|
||||||
|
@ -14349,6 +14393,7 @@ name = "ui_macros"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case 0.7.1",
|
"convert_case 0.7.1",
|
||||||
|
"linkme",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
|
@ -16120,6 +16165,7 @@ dependencies = [
|
||||||
"client",
|
"client",
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
|
"component",
|
||||||
"db",
|
"db",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"env_logger 0.11.6",
|
"env_logger 0.11.6",
|
||||||
|
@ -16554,6 +16600,7 @@ dependencies = [
|
||||||
"collections",
|
"collections",
|
||||||
"command_palette",
|
"command_palette",
|
||||||
"command_palette_hooks",
|
"command_palette_hooks",
|
||||||
|
"component_preview",
|
||||||
"copilot",
|
"copilot",
|
||||||
"db",
|
"db",
|
||||||
"diagnostics",
|
"diagnostics",
|
||||||
|
|
|
@ -26,6 +26,8 @@ members = [
|
||||||
"crates/collections",
|
"crates/collections",
|
||||||
"crates/command_palette",
|
"crates/command_palette",
|
||||||
"crates/command_palette_hooks",
|
"crates/command_palette_hooks",
|
||||||
|
"crates/component",
|
||||||
|
"crates/component_preview",
|
||||||
"crates/context_server",
|
"crates/context_server",
|
||||||
"crates/context_server_settings",
|
"crates/context_server_settings",
|
||||||
"crates/copilot",
|
"crates/copilot",
|
||||||
|
@ -226,6 +228,8 @@ collab_ui = { path = "crates/collab_ui" }
|
||||||
collections = { path = "crates/collections" }
|
collections = { path = "crates/collections" }
|
||||||
command_palette = { path = "crates/command_palette" }
|
command_palette = { path = "crates/command_palette" }
|
||||||
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
command_palette_hooks = { path = "crates/command_palette_hooks" }
|
||||||
|
component = { path = "crates/component" }
|
||||||
|
component_preview = { path = "crates/component_preview" }
|
||||||
context_server = { path = "crates/context_server" }
|
context_server = { path = "crates/context_server" }
|
||||||
context_server_settings = { path = "crates/context_server_settings" }
|
context_server_settings = { path = "crates/context_server_settings" }
|
||||||
copilot = { path = "crates/copilot" }
|
copilot = { path = "crates/copilot" }
|
||||||
|
@ -426,6 +430,7 @@ jupyter-websocket-client = { version = "0.9.0" }
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
|
||||||
linkify = "0.10.0"
|
linkify = "0.10.0"
|
||||||
|
linkme = "0.3.31"
|
||||||
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "811ceae29fabee455f110c56cd66b3f49a7e5003", features = [
|
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "811ceae29fabee455f110c56cd66b3f49a7e5003", features = [
|
||||||
"dispatcher",
|
"dispatcher",
|
||||||
"services-dispatcher",
|
"services-dispatcher",
|
||||||
|
|
23
crates/component/Cargo.toml
Normal file
23
crates/component/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "component"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/component.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
collections.workspace = true
|
||||||
|
gpui.workspace = true
|
||||||
|
linkme.workspace = true
|
||||||
|
once_cell = "1.20.3"
|
||||||
|
parking_lot.workspace = true
|
||||||
|
theme.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
1
crates/component/LICENSE-GPL
Symbolic link
1
crates/component/LICENSE-GPL
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../LICENSE-GPL
|
305
crates/component/src/component.rs
Normal file
305
crates/component/src/component.rs
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use collections::HashMap;
|
||||||
|
use gpui::{div, prelude::*, AnyElement, App, IntoElement, RenderOnce, SharedString, Window};
|
||||||
|
use linkme::distributed_slice;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
|
||||||
|
pub trait Component {
|
||||||
|
fn scope() -> Option<&'static str>;
|
||||||
|
fn name() -> &'static str {
|
||||||
|
std::any::type_name::<Self>()
|
||||||
|
}
|
||||||
|
fn description() -> Option<&'static str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ComponentPreview: Component {
|
||||||
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[distributed_slice]
|
||||||
|
pub static __ALL_COMPONENTS: [fn()] = [..];
|
||||||
|
|
||||||
|
#[distributed_slice]
|
||||||
|
pub static __ALL_PREVIEWS: [fn()] = [..];
|
||||||
|
|
||||||
|
pub static COMPONENT_DATA: Lazy<RwLock<ComponentRegistry>> =
|
||||||
|
Lazy::new(|| RwLock::new(ComponentRegistry::new()));
|
||||||
|
|
||||||
|
pub struct ComponentRegistry {
|
||||||
|
components: Vec<(Option<&'static str>, &'static str, Option<&'static str>)>,
|
||||||
|
previews: HashMap<&'static str, fn(&mut Window, &App) -> AnyElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentRegistry {
|
||||||
|
fn new() -> Self {
|
||||||
|
ComponentRegistry {
|
||||||
|
components: Vec::new(),
|
||||||
|
previews: HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
let component_fns: Vec<_> = __ALL_COMPONENTS.iter().cloned().collect();
|
||||||
|
let preview_fns: Vec<_> = __ALL_PREVIEWS.iter().cloned().collect();
|
||||||
|
|
||||||
|
for f in component_fns {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
for f in preview_fns {
|
||||||
|
f();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_component<T: Component>() {
|
||||||
|
let component_data = (T::scope(), T::name(), T::description());
|
||||||
|
COMPONENT_DATA.write().components.push(component_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register_preview<T: ComponentPreview>() {
|
||||||
|
let preview_data = (T::name(), T::preview as fn(&mut Window, &App) -> AnyElement);
|
||||||
|
COMPONENT_DATA
|
||||||
|
.write()
|
||||||
|
.previews
|
||||||
|
.insert(preview_data.0, preview_data.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct ComponentId(pub &'static str);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ComponentMetadata {
|
||||||
|
name: SharedString,
|
||||||
|
scope: Option<SharedString>,
|
||||||
|
description: Option<SharedString>,
|
||||||
|
preview: Option<fn(&mut Window, &App) -> AnyElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentMetadata {
|
||||||
|
pub fn name(&self) -> SharedString {
|
||||||
|
self.name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scope(&self) -> Option<SharedString> {
|
||||||
|
self.scope.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn description(&self) -> Option<SharedString> {
|
||||||
|
self.description.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn preview(&self) -> Option<fn(&mut Window, &App) -> AnyElement> {
|
||||||
|
self.preview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AllComponents(pub HashMap<ComponentId, ComponentMetadata>);
|
||||||
|
|
||||||
|
impl AllComponents {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
AllComponents(HashMap::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all components with previews
|
||||||
|
pub fn all_previews(&self) -> Vec<&ComponentMetadata> {
|
||||||
|
self.0.values().filter(|c| c.preview.is_some()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all components with previews sorted by name
|
||||||
|
pub fn all_previews_sorted(&self) -> Vec<ComponentMetadata> {
|
||||||
|
let mut previews: Vec<ComponentMetadata> =
|
||||||
|
self.all_previews().into_iter().cloned().collect();
|
||||||
|
previews.sort_by_key(|a| a.name());
|
||||||
|
previews
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all components
|
||||||
|
pub fn all(&self) -> Vec<&ComponentMetadata> {
|
||||||
|
self.0.values().collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns all components sorted by name
|
||||||
|
pub fn all_sorted(&self) -> Vec<ComponentMetadata> {
|
||||||
|
let mut components: Vec<ComponentMetadata> = self.all().into_iter().cloned().collect();
|
||||||
|
components.sort_by_key(|a| a.name());
|
||||||
|
components
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for AllComponents {
|
||||||
|
type Target = HashMap<ComponentId, ComponentMetadata>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for AllComponents {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn components() -> AllComponents {
|
||||||
|
let data = COMPONENT_DATA.read();
|
||||||
|
let mut all_components = AllComponents::new();
|
||||||
|
|
||||||
|
for &(scope, name, description) in &data.components {
|
||||||
|
let scope = scope.map(Into::into);
|
||||||
|
let preview = data.previews.get(name).cloned();
|
||||||
|
all_components.insert(
|
||||||
|
ComponentId(name),
|
||||||
|
ComponentMetadata {
|
||||||
|
name: name.into(),
|
||||||
|
scope,
|
||||||
|
description: description.map(Into::into),
|
||||||
|
preview,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
all_components
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which side of the preview to show labels on
|
||||||
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ExampleLabelSide {
|
||||||
|
/// Left side
|
||||||
|
Left,
|
||||||
|
/// Right side
|
||||||
|
Right,
|
||||||
|
#[default]
|
||||||
|
/// Top side
|
||||||
|
Top,
|
||||||
|
/// Bottom side
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A single example of a component.
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct ComponentExample {
|
||||||
|
variant_name: SharedString,
|
||||||
|
element: AnyElement,
|
||||||
|
label_side: ExampleLabelSide,
|
||||||
|
grow: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for ComponentExample {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let base = div().flex();
|
||||||
|
|
||||||
|
let base = match self.label_side {
|
||||||
|
ExampleLabelSide::Right => base.flex_row(),
|
||||||
|
ExampleLabelSide::Left => base.flex_row_reverse(),
|
||||||
|
ExampleLabelSide::Bottom => base.flex_col(),
|
||||||
|
ExampleLabelSide::Top => base.flex_col_reverse(),
|
||||||
|
};
|
||||||
|
|
||||||
|
base.gap_1()
|
||||||
|
.text_xs()
|
||||||
|
.text_color(cx.theme().colors().text_muted)
|
||||||
|
.when(self.grow, |this| this.flex_1())
|
||||||
|
.child(self.element)
|
||||||
|
.child(self.variant_name)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentExample {
|
||||||
|
/// Create a new example with the given variant name and example value.
|
||||||
|
pub fn new(variant_name: impl Into<SharedString>, element: AnyElement) -> Self {
|
||||||
|
Self {
|
||||||
|
variant_name: variant_name.into(),
|
||||||
|
element,
|
||||||
|
label_side: ExampleLabelSide::default(),
|
||||||
|
grow: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the example to grow to fill the available horizontal space.
|
||||||
|
pub fn grow(mut self) -> Self {
|
||||||
|
self.grow = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A group of component examples.
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
pub struct ComponentExampleGroup {
|
||||||
|
pub title: Option<SharedString>,
|
||||||
|
pub examples: Vec<ComponentExample>,
|
||||||
|
pub grow: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for ComponentExampleGroup {
|
||||||
|
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
.flex_col()
|
||||||
|
.text_sm()
|
||||||
|
.text_color(cx.theme().colors().text_muted)
|
||||||
|
.when(self.grow, |this| this.w_full().flex_1())
|
||||||
|
.when_some(self.title, |this, title| this.gap_4().child(title))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_start()
|
||||||
|
.w_full()
|
||||||
|
.gap_6()
|
||||||
|
.children(self.examples)
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentExampleGroup {
|
||||||
|
/// Create a new group of examples with the given title.
|
||||||
|
pub fn new(examples: Vec<ComponentExample>) -> Self {
|
||||||
|
Self {
|
||||||
|
title: None,
|
||||||
|
examples,
|
||||||
|
grow: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new group of examples with the given title.
|
||||||
|
pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample>) -> Self {
|
||||||
|
Self {
|
||||||
|
title: Some(title.into()),
|
||||||
|
examples,
|
||||||
|
grow: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the group to grow to fill the available horizontal space.
|
||||||
|
pub fn grow(mut self) -> Self {
|
||||||
|
self.grow = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a single example
|
||||||
|
pub fn single_example(
|
||||||
|
variant_name: impl Into<SharedString>,
|
||||||
|
example: AnyElement,
|
||||||
|
) -> ComponentExample {
|
||||||
|
ComponentExample::new(variant_name, example)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a group of examples without a title
|
||||||
|
pub fn example_group(examples: Vec<ComponentExample>) -> ComponentExampleGroup {
|
||||||
|
ComponentExampleGroup::new(examples)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a group of examples with a title
|
||||||
|
pub fn example_group_with_title(
|
||||||
|
title: impl Into<SharedString>,
|
||||||
|
examples: Vec<ComponentExample>,
|
||||||
|
) -> ComponentExampleGroup {
|
||||||
|
ComponentExampleGroup::with_title(title, examples)
|
||||||
|
}
|
21
crates/component_preview/Cargo.toml
Normal file
21
crates/component_preview/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "component_preview"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
publish.workspace = true
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/component_preview.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
component.workspace = true
|
||||||
|
gpui.workspace = true
|
||||||
|
ui.workspace = true
|
||||||
|
workspace.workspace = true
|
1
crates/component_preview/LICENSE-GPL
Symbolic link
1
crates/component_preview/LICENSE-GPL
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../LICENSE-GPL
|
178
crates/component_preview/src/component_preview.rs
Normal file
178
crates/component_preview/src/component_preview.rs
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
//! # Component Preview
|
||||||
|
//!
|
||||||
|
//! A view for exploring Zed components.
|
||||||
|
|
||||||
|
use component::{components, ComponentMetadata};
|
||||||
|
use gpui::{prelude::*, App, EventEmitter, FocusHandle, Focusable, Window};
|
||||||
|
use ui::prelude::*;
|
||||||
|
|
||||||
|
use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId};
|
||||||
|
|
||||||
|
pub fn init(cx: &mut App) {
|
||||||
|
cx.observe_new(|workspace: &mut Workspace, _, _cx| {
|
||||||
|
workspace.register_action(
|
||||||
|
|workspace, _: &workspace::OpenComponentPreview, window, cx| {
|
||||||
|
let component_preview = cx.new(ComponentPreview::new);
|
||||||
|
workspace.add_item_to_active_pane(
|
||||||
|
Box::new(component_preview),
|
||||||
|
None,
|
||||||
|
true,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ComponentPreview {
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentPreview {
|
||||||
|
pub fn new(cx: &mut Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_sidebar(&self, _window: &Window, _cx: &Context<Self>) -> impl IntoElement {
|
||||||
|
let components = components().all_sorted();
|
||||||
|
let sorted_components = components.clone();
|
||||||
|
|
||||||
|
v_flex().gap_px().p_1().children(
|
||||||
|
sorted_components
|
||||||
|
.into_iter()
|
||||||
|
.map(|component| self.render_sidebar_entry(&component, _cx)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_sidebar_entry(
|
||||||
|
&self,
|
||||||
|
component: &ComponentMetadata,
|
||||||
|
_cx: &Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
h_flex()
|
||||||
|
.w_40()
|
||||||
|
.px_1p5()
|
||||||
|
.py_1()
|
||||||
|
.child(component.name().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_preview(
|
||||||
|
&self,
|
||||||
|
component: &ComponentMetadata,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
let name = component.name();
|
||||||
|
let scope = component.scope();
|
||||||
|
|
||||||
|
let description = component.description();
|
||||||
|
|
||||||
|
v_group()
|
||||||
|
.w_full()
|
||||||
|
.gap_4()
|
||||||
|
.p_8()
|
||||||
|
.rounded_md()
|
||||||
|
.child(
|
||||||
|
v_flex()
|
||||||
|
.gap_1()
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.gap_1()
|
||||||
|
.text_xl()
|
||||||
|
.child(div().child(name))
|
||||||
|
.when_some(scope, |this, scope| {
|
||||||
|
this.child(div().opacity(0.5).child(format!("({})", scope)))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.when_some(description, |this, description| {
|
||||||
|
this.child(
|
||||||
|
div()
|
||||||
|
.text_ui_sm(cx)
|
||||||
|
.text_color(cx.theme().colors().text_muted)
|
||||||
|
.max_w(px(600.0))
|
||||||
|
.child(description),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.when_some(component.preview(), |this, preview| {
|
||||||
|
this.child(preview(window, cx))
|
||||||
|
})
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_previews(&self, window: &mut Window, cx: &Context<Self>) -> impl IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.id("component-previews")
|
||||||
|
.size_full()
|
||||||
|
.overflow_y_scroll()
|
||||||
|
.p_4()
|
||||||
|
.gap_2()
|
||||||
|
.children(
|
||||||
|
components()
|
||||||
|
.all_previews_sorted()
|
||||||
|
.iter()
|
||||||
|
.map(|component| self.render_preview(component, window, cx)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for ComponentPreview {
|
||||||
|
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
|
||||||
|
h_flex()
|
||||||
|
.id("component-preview")
|
||||||
|
.key_context("ComponentPreview")
|
||||||
|
.items_start()
|
||||||
|
.overflow_hidden()
|
||||||
|
.size_full()
|
||||||
|
.max_h_full()
|
||||||
|
.track_focus(&self.focus_handle)
|
||||||
|
.px_2()
|
||||||
|
.bg(cx.theme().colors().editor_background)
|
||||||
|
.child(self.render_sidebar(window, cx))
|
||||||
|
.child(self.render_previews(window, cx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EventEmitter<ItemEvent> for ComponentPreview {}
|
||||||
|
|
||||||
|
impl Focusable for ComponentPreview {
|
||||||
|
fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
|
||||||
|
self.focus_handle.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Item for ComponentPreview {
|
||||||
|
type Event = ItemEvent;
|
||||||
|
|
||||||
|
fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
|
||||||
|
Some("Component Preview".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn telemetry_event_text(&self) -> Option<&'static str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_toolbar(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clone_on_split(
|
||||||
|
&self,
|
||||||
|
_workspace_id: Option<WorkspaceId>,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> Option<gpui::Entity<Self>>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
Some(cx.new(Self::new))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
|
||||||
|
f(*event)
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,8 +14,10 @@ path = "src/ui.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
|
component.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
itertools = { workspace = true, optional = true }
|
itertools = { workspace = true, optional = true }
|
||||||
|
linkme.workspace = true
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
@ -31,3 +33,7 @@ windows.workspace = true
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
stories = ["dep:itertools", "dep:story"]
|
stories = ["dep:itertools", "dep:story"]
|
||||||
|
|
||||||
|
# cargo-machete doesn't understand that linkme is used in the component macro
|
||||||
|
[package.metadata.cargo-machete]
|
||||||
|
ignored = ["linkme"]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::prelude::*;
|
use crate::{prelude::*, Indicator};
|
||||||
|
|
||||||
use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled};
|
use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled};
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled};
|
||||||
/// .grayscale(true)
|
/// .grayscale(true)
|
||||||
/// .border_color(gpui::red());
|
/// .border_color(gpui::red());
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
pub struct Avatar {
|
pub struct Avatar {
|
||||||
image: Img,
|
image: Img,
|
||||||
size: Option<AbsoluteLength>,
|
size: Option<AbsoluteLength>,
|
||||||
|
@ -96,3 +96,60 @@ impl RenderOnce for Avatar {
|
||||||
.children(self.indicator.map(|indicator| div().child(indicator)))
|
.children(self.indicator.map(|indicator| div().child(indicator)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ComponentPreview for Avatar {
|
||||||
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
|
let example_avatar = "https://avatars.githubusercontent.com/u/1714999?v=4";
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.children(vec![
|
||||||
|
example_group_with_title(
|
||||||
|
"Sizes",
|
||||||
|
vec![
|
||||||
|
single_example(
|
||||||
|
"Default",
|
||||||
|
Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4")
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Small",
|
||||||
|
Avatar::new(example_avatar).size(px(24.)).into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Large",
|
||||||
|
Avatar::new(example_avatar).size(px(48.)).into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
example_group_with_title(
|
||||||
|
"Styles",
|
||||||
|
vec![
|
||||||
|
single_example("Default", Avatar::new(example_avatar).into_any_element()),
|
||||||
|
single_example(
|
||||||
|
"Grayscale",
|
||||||
|
Avatar::new(example_avatar)
|
||||||
|
.grayscale(true)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"With Border",
|
||||||
|
Avatar::new(example_avatar)
|
||||||
|
.border_color(gpui::red())
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
example_group_with_title(
|
||||||
|
"With Indicator",
|
||||||
|
vec![single_example(
|
||||||
|
"Dot",
|
||||||
|
Avatar::new(example_avatar)
|
||||||
|
.indicator(Indicator::dot().color(Color::Success))
|
||||||
|
.into_any_element(),
|
||||||
|
)],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
use gpui::{AnyView, DefiniteLength};
|
use component::{example_group_with_title, single_example, ComponentPreview};
|
||||||
|
use gpui::{AnyElement, AnyView, DefiniteLength};
|
||||||
|
use ui_macros::IntoComponent;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
prelude::*, Color, DynamicSpacing, ElevationIndex, IconPosition, KeyBinding,
|
prelude::*, Color, DynamicSpacing, ElevationIndex, IconPosition, KeyBinding,
|
||||||
|
@ -78,7 +80,7 @@ use super::button_icon::ButtonIcon;
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
base: ButtonLike,
|
base: ButtonLike,
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
|
@ -455,101 +457,124 @@ impl RenderOnce for Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for Button {
|
impl ComponentPreview for Button {
|
||||||
fn description() -> impl Into<Option<&'static str>> {
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
"A button allows users to take actions, and make choices, with a single tap."
|
v_flex()
|
||||||
}
|
.gap_6()
|
||||||
|
.children(vec![
|
||||||
fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
example_group_with_title(
|
||||||
vec![
|
"Styles",
|
||||||
example_group_with_title(
|
vec![
|
||||||
"Styles",
|
single_example(
|
||||||
vec![
|
"Default",
|
||||||
single_example("Default", Button::new("default", "Default")),
|
Button::new("default", "Default").into_any_element(),
|
||||||
single_example(
|
),
|
||||||
"Filled",
|
single_example(
|
||||||
Button::new("filled", "Filled").style(ButtonStyle::Filled),
|
"Filled",
|
||||||
),
|
Button::new("filled", "Filled")
|
||||||
single_example(
|
.style(ButtonStyle::Filled)
|
||||||
"Subtle",
|
.into_any_element(),
|
||||||
Button::new("outline", "Subtle").style(ButtonStyle::Subtle),
|
),
|
||||||
),
|
single_example(
|
||||||
single_example(
|
"Subtle",
|
||||||
"Transparent",
|
Button::new("outline", "Subtle")
|
||||||
Button::new("transparent", "Transparent").style(ButtonStyle::Transparent),
|
.style(ButtonStyle::Subtle)
|
||||||
),
|
.into_any_element(),
|
||||||
],
|
),
|
||||||
),
|
single_example(
|
||||||
example_group_with_title(
|
"Transparent",
|
||||||
"Tinted",
|
Button::new("transparent", "Transparent")
|
||||||
vec![
|
.style(ButtonStyle::Transparent)
|
||||||
single_example(
|
.into_any_element(),
|
||||||
"Accent",
|
),
|
||||||
Button::new("tinted_accent", "Accent")
|
],
|
||||||
.style(ButtonStyle::Tinted(TintColor::Accent)),
|
),
|
||||||
),
|
example_group_with_title(
|
||||||
single_example(
|
"Tinted",
|
||||||
"Error",
|
vec![
|
||||||
Button::new("tinted_negative", "Error")
|
single_example(
|
||||||
.style(ButtonStyle::Tinted(TintColor::Error)),
|
"Accent",
|
||||||
),
|
Button::new("tinted_accent", "Accent")
|
||||||
single_example(
|
.style(ButtonStyle::Tinted(TintColor::Accent))
|
||||||
"Warning",
|
.into_any_element(),
|
||||||
Button::new("tinted_warning", "Warning")
|
),
|
||||||
.style(ButtonStyle::Tinted(TintColor::Warning)),
|
single_example(
|
||||||
),
|
"Error",
|
||||||
single_example(
|
Button::new("tinted_negative", "Error")
|
||||||
"Success",
|
.style(ButtonStyle::Tinted(TintColor::Error))
|
||||||
Button::new("tinted_positive", "Success")
|
.into_any_element(),
|
||||||
.style(ButtonStyle::Tinted(TintColor::Success)),
|
),
|
||||||
),
|
single_example(
|
||||||
],
|
"Warning",
|
||||||
),
|
Button::new("tinted_warning", "Warning")
|
||||||
example_group_with_title(
|
.style(ButtonStyle::Tinted(TintColor::Warning))
|
||||||
"States",
|
.into_any_element(),
|
||||||
vec![
|
),
|
||||||
single_example("Default", Button::new("default_state", "Default")),
|
single_example(
|
||||||
single_example(
|
"Success",
|
||||||
"Disabled",
|
Button::new("tinted_positive", "Success")
|
||||||
Button::new("disabled", "Disabled").disabled(true),
|
.style(ButtonStyle::Tinted(TintColor::Success))
|
||||||
),
|
.into_any_element(),
|
||||||
single_example(
|
),
|
||||||
"Selected",
|
],
|
||||||
Button::new("selected", "Selected").toggle_state(true),
|
),
|
||||||
),
|
example_group_with_title(
|
||||||
],
|
"States",
|
||||||
),
|
vec![
|
||||||
example_group_with_title(
|
single_example(
|
||||||
"With Icons",
|
"Default",
|
||||||
vec![
|
Button::new("default_state", "Default").into_any_element(),
|
||||||
single_example(
|
),
|
||||||
"Icon Start",
|
single_example(
|
||||||
Button::new("icon_start", "Icon Start")
|
"Disabled",
|
||||||
.icon(IconName::Check)
|
Button::new("disabled", "Disabled")
|
||||||
.icon_position(IconPosition::Start),
|
.disabled(true)
|
||||||
),
|
.into_any_element(),
|
||||||
single_example(
|
),
|
||||||
"Icon End",
|
single_example(
|
||||||
Button::new("icon_end", "Icon End")
|
"Selected",
|
||||||
.icon(IconName::Check)
|
Button::new("selected", "Selected")
|
||||||
.icon_position(IconPosition::End),
|
.toggle_state(true)
|
||||||
),
|
.into_any_element(),
|
||||||
single_example(
|
),
|
||||||
"Icon Color",
|
],
|
||||||
Button::new("icon_color", "Icon Color")
|
),
|
||||||
.icon(IconName::Check)
|
example_group_with_title(
|
||||||
.icon_color(Color::Accent),
|
"With Icons",
|
||||||
),
|
vec![
|
||||||
single_example(
|
single_example(
|
||||||
"Tinted Icons",
|
"Icon Start",
|
||||||
Button::new("tinted_icons", "Error")
|
Button::new("icon_start", "Icon Start")
|
||||||
.style(ButtonStyle::Tinted(TintColor::Error))
|
.icon(IconName::Check)
|
||||||
.color(Color::Error)
|
.icon_position(IconPosition::Start)
|
||||||
.icon_color(Color::Error)
|
.into_any_element(),
|
||||||
.icon(IconName::Trash)
|
),
|
||||||
.icon_position(IconPosition::Start),
|
single_example(
|
||||||
),
|
"Icon End",
|
||||||
],
|
Button::new("icon_end", "Icon End")
|
||||||
),
|
.icon(IconName::Check)
|
||||||
]
|
.icon_position(IconPosition::End)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Icon Color",
|
||||||
|
Button::new("icon_color", "Icon Color")
|
||||||
|
.icon(IconName::Check)
|
||||||
|
.icon_color(Color::Accent)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Tinted Icons",
|
||||||
|
Button::new("tinted_icons", "Error")
|
||||||
|
.style(ButtonStyle::Tinted(TintColor::Error))
|
||||||
|
.color(Color::Error)
|
||||||
|
.icon_color(Color::Error)
|
||||||
|
.icon(IconName::Trash)
|
||||||
|
.icon_position(IconPosition::Start)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
use component::{example_group, single_example, ComponentPreview};
|
||||||
use gpui::{AnyElement, IntoElement, ParentElement, StyleRefinement, Styled};
|
use gpui::{AnyElement, IntoElement, ParentElement, StyleRefinement, Styled};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
@ -22,7 +23,8 @@ pub fn h_group() -> ContentGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A flexible container component that can hold other elements.
|
/// A flexible container component that can hold other elements.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
|
#[component(scope = "layout")]
|
||||||
pub struct ContentGroup {
|
pub struct ContentGroup {
|
||||||
base: Div,
|
base: Div,
|
||||||
border: bool,
|
border: bool,
|
||||||
|
@ -87,16 +89,8 @@ impl RenderOnce for ContentGroup {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for ContentGroup {
|
impl ComponentPreview for ContentGroup {
|
||||||
fn description() -> impl Into<Option<&'static str>> {
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
"A flexible container component that can hold other elements. It can be customized with or without a border and background fill."
|
example_group(vec![
|
||||||
}
|
|
||||||
|
|
||||||
fn example_label_side() -> ExampleLabelSide {
|
|
||||||
ExampleLabelSide::Bottom
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
|
||||||
vec![example_group(vec![
|
|
||||||
single_example(
|
single_example(
|
||||||
"Default",
|
"Default",
|
||||||
ContentGroup::new()
|
ContentGroup::new()
|
||||||
|
@ -104,7 +98,8 @@ impl ComponentPreview for ContentGroup {
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.h_48()
|
.h_48()
|
||||||
.child(Label::new("Default ContentBox")),
|
.child(Label::new("Default ContentBox"))
|
||||||
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
.grow(),
|
.grow(),
|
||||||
single_example(
|
single_example(
|
||||||
|
@ -115,7 +110,8 @@ impl ComponentPreview for ContentGroup {
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.h_48()
|
.h_48()
|
||||||
.borderless()
|
.borderless()
|
||||||
.child(Label::new("Borderless ContentBox")),
|
.child(Label::new("Borderless ContentBox"))
|
||||||
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
.grow(),
|
.grow(),
|
||||||
single_example(
|
single_example(
|
||||||
|
@ -126,10 +122,11 @@ impl ComponentPreview for ContentGroup {
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.h_48()
|
.h_48()
|
||||||
.unfilled()
|
.unfilled()
|
||||||
.child(Label::new("Unfilled ContentBox")),
|
.child(Label::new("Unfilled ContentBox"))
|
||||||
|
.into_any_element(),
|
||||||
)
|
)
|
||||||
.grow(),
|
.grow(),
|
||||||
])
|
])
|
||||||
.grow()]
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{prelude::*, Avatar};
|
use crate::prelude::*;
|
||||||
use gpui::{AnyElement, StyleRefinement};
|
use gpui::{AnyElement, StyleRefinement};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
@ -60,60 +60,60 @@ impl RenderOnce for Facepile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for Facepile {
|
// impl ComponentPreview for Facepile {
|
||||||
fn description() -> impl Into<Option<&'static str>> {
|
// fn description() -> impl Into<Option<&'static str>> {
|
||||||
"A facepile is a collection of faces stacked horizontally–\
|
// "A facepile is a collection of faces stacked horizontally–\
|
||||||
always with the leftmost face on top and descending in z-index.\
|
// always with the leftmost face on top and descending in z-index.\
|
||||||
\n\nFacepiles are used to display a group of people or things,\
|
// \n\nFacepiles are used to display a group of people or things,\
|
||||||
such as a list of participants in a collaboration session."
|
// such as a list of participants in a collaboration session."
|
||||||
}
|
// }
|
||||||
fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
// fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
||||||
let few_faces: [&'static str; 3] = [
|
// let few_faces: [&'static str; 3] = [
|
||||||
"https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
|
// "https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
|
||||||
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
|
// "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
|
||||||
"https://avatars.githubusercontent.com/u/482957?s=60&v=4",
|
// "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
|
||||||
];
|
// ];
|
||||||
|
|
||||||
let many_faces: [&'static str; 6] = [
|
// let many_faces: [&'static str; 6] = [
|
||||||
"https://avatars.githubusercontent.com/u/326587?s=60&v=4",
|
// "https://avatars.githubusercontent.com/u/326587?s=60&v=4",
|
||||||
"https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
|
// "https://avatars.githubusercontent.com/u/2280405?s=60&v=4",
|
||||||
"https://avatars.githubusercontent.com/u/1789?s=60&v=4",
|
// "https://avatars.githubusercontent.com/u/1789?s=60&v=4",
|
||||||
"https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
|
// "https://avatars.githubusercontent.com/u/67129314?s=60&v=4",
|
||||||
"https://avatars.githubusercontent.com/u/482957?s=60&v=4",
|
// "https://avatars.githubusercontent.com/u/482957?s=60&v=4",
|
||||||
"https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
|
// "https://avatars.githubusercontent.com/u/1714999?s=60&v=4",
|
||||||
];
|
// ];
|
||||||
|
|
||||||
vec![example_group_with_title(
|
// vec![example_group_with_title(
|
||||||
"Examples",
|
// "Examples",
|
||||||
vec![
|
// vec![
|
||||||
single_example(
|
// single_example(
|
||||||
"Few Faces",
|
// "Few Faces",
|
||||||
Facepile::new(
|
// Facepile::new(
|
||||||
few_faces
|
// few_faces
|
||||||
.iter()
|
// .iter()
|
||||||
.map(|&url| Avatar::new(url).into_any_element())
|
// .map(|&url| Avatar::new(url).into_any_element())
|
||||||
.collect(),
|
// .collect(),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
single_example(
|
// single_example(
|
||||||
"Many Faces",
|
// "Many Faces",
|
||||||
Facepile::new(
|
// Facepile::new(
|
||||||
many_faces
|
// many_faces
|
||||||
.iter()
|
// .iter()
|
||||||
.map(|&url| Avatar::new(url).into_any_element())
|
// .map(|&url| Avatar::new(url).into_any_element())
|
||||||
.collect(),
|
// .collect(),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
single_example(
|
// single_example(
|
||||||
"Custom Size",
|
// "Custom Size",
|
||||||
Facepile::new(
|
// Facepile::new(
|
||||||
few_faces
|
// few_faces
|
||||||
.iter()
|
// .iter()
|
||||||
.map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
|
// .map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
|
||||||
.collect(),
|
// .collect(),
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
],
|
// ],
|
||||||
)]
|
// )]
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
|
@ -7,17 +7,13 @@ use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub use decorated_icon::*;
|
pub use decorated_icon::*;
|
||||||
use gpui::{img, svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
|
use gpui::{img, svg, AnimationElement, AnyElement, Hsla, IntoElement, Rems, Transformation};
|
||||||
pub use icon_decoration::*;
|
pub use icon_decoration::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::{EnumIter, EnumString, IntoStaticStr};
|
use strum::{EnumIter, EnumString, IntoStaticStr};
|
||||||
use ui_macros::DerivePathStr;
|
use ui_macros::DerivePathStr;
|
||||||
|
|
||||||
use crate::{
|
use crate::{prelude::*, Indicator};
|
||||||
prelude::*,
|
|
||||||
traits::component_preview::{ComponentExample, ComponentPreview},
|
|
||||||
Indicator,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub enum AnyIcon {
|
pub enum AnyIcon {
|
||||||
|
@ -364,7 +360,7 @@ impl IconSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
pub struct Icon {
|
pub struct Icon {
|
||||||
source: IconSource,
|
source: IconSource,
|
||||||
color: Color,
|
color: Color,
|
||||||
|
@ -494,24 +490,41 @@ impl RenderOnce for IconWithIndicator {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for Icon {
|
impl ComponentPreview for Icon {
|
||||||
fn examples(_window: &mut Window, _cx: &mut App) -> Vec<ComponentExampleGroup<Icon>> {
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
let arrow_icons = vec![
|
v_flex()
|
||||||
IconName::ArrowDown,
|
.gap_6()
|
||||||
IconName::ArrowLeft,
|
.children(vec![
|
||||||
IconName::ArrowRight,
|
example_group_with_title(
|
||||||
IconName::ArrowUp,
|
"Sizes",
|
||||||
IconName::ArrowCircle,
|
vec![
|
||||||
];
|
single_example("Default", Icon::new(IconName::Star).into_any_element()),
|
||||||
|
single_example(
|
||||||
vec![example_group_with_title(
|
"Small",
|
||||||
"Arrow Icons",
|
Icon::new(IconName::Star)
|
||||||
arrow_icons
|
.size(IconSize::Small)
|
||||||
.into_iter()
|
.into_any_element(),
|
||||||
.map(|icon| {
|
),
|
||||||
let name = format!("{:?}", icon).to_string();
|
single_example(
|
||||||
ComponentExample::new(name, Icon::new(icon))
|
"Large",
|
||||||
})
|
Icon::new(IconName::Star)
|
||||||
.collect(),
|
.size(IconSize::XLarge)
|
||||||
)]
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
example_group_with_title(
|
||||||
|
"Colors",
|
||||||
|
vec![
|
||||||
|
single_example("Default", Icon::new(IconName::Bell).into_any_element()),
|
||||||
|
single_example(
|
||||||
|
"Custom Color",
|
||||||
|
Icon::new(IconName::Bell)
|
||||||
|
.color(Color::Error)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use gpui::{IntoElement, Point};
|
use gpui::{AnyElement, IntoElement, Point};
|
||||||
|
|
||||||
use crate::{
|
use crate::{prelude::*, IconDecoration, IconDecorationKind};
|
||||||
prelude::*, traits::component_preview::ComponentPreview, IconDecoration, IconDecorationKind,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
pub struct DecoratedIcon {
|
pub struct DecoratedIcon {
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
decoration: Option<IconDecoration>,
|
decoration: Option<IconDecoration>,
|
||||||
|
@ -27,12 +25,7 @@ impl RenderOnce for DecoratedIcon {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for DecoratedIcon {
|
impl ComponentPreview for DecoratedIcon {
|
||||||
fn examples(_: &mut Window, cx: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
fn preview(_window: &mut Window, cx: &App) -> AnyElement {
|
||||||
let icon_1 = Icon::new(IconName::FileDoc);
|
|
||||||
let icon_2 = Icon::new(IconName::FileDoc);
|
|
||||||
let icon_3 = Icon::new(IconName::FileDoc);
|
|
||||||
let icon_4 = Icon::new(IconName::FileDoc);
|
|
||||||
|
|
||||||
let decoration_x = IconDecoration::new(
|
let decoration_x = IconDecoration::new(
|
||||||
IconDecorationKind::X,
|
IconDecorationKind::X,
|
||||||
cx.theme().colors().surface_background,
|
cx.theme().colors().surface_background,
|
||||||
|
@ -66,22 +59,32 @@ impl ComponentPreview for DecoratedIcon {
|
||||||
y: px(-2.),
|
y: px(-2.),
|
||||||
});
|
});
|
||||||
|
|
||||||
let examples = vec![
|
v_flex()
|
||||||
single_example("no_decoration", DecoratedIcon::new(icon_1, None)),
|
.gap_6()
|
||||||
single_example(
|
.children(vec![example_group_with_title(
|
||||||
"with_decoration",
|
"Decorations",
|
||||||
DecoratedIcon::new(icon_2, Some(decoration_x)),
|
vec![
|
||||||
),
|
single_example(
|
||||||
single_example(
|
"No Decoration",
|
||||||
"with_decoration",
|
DecoratedIcon::new(Icon::new(IconName::FileDoc), None).into_any_element(),
|
||||||
DecoratedIcon::new(icon_3, Some(decoration_triangle)),
|
),
|
||||||
),
|
single_example(
|
||||||
single_example(
|
"X Decoration",
|
||||||
"with_decoration",
|
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_x))
|
||||||
DecoratedIcon::new(icon_4, Some(decoration_dot)),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
];
|
single_example(
|
||||||
|
"Triangle Decoration",
|
||||||
vec![example_group(examples)]
|
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_triangle))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Dot Decoration",
|
||||||
|
DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_dot))
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)])
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use gpui::{svg, Hsla, IntoElement, Point};
|
use gpui::{svg, Hsla, IntoElement, Point};
|
||||||
use strum::{EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
|
use strum::{EnumIter, EnumString, IntoStaticStr};
|
||||||
use ui_macros::DerivePathStr;
|
use ui_macros::DerivePathStr;
|
||||||
|
|
||||||
use crate::{prelude::*, traits::component_preview::ComponentPreview};
|
use crate::prelude::*;
|
||||||
|
|
||||||
const ICON_DECORATION_SIZE: Pixels = px(11.);
|
const ICON_DECORATION_SIZE: Pixels = px(11.);
|
||||||
|
|
||||||
|
@ -149,21 +149,3 @@ impl RenderOnce for IconDecoration {
|
||||||
.child(background)
|
.child(background)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for IconDecoration {
|
|
||||||
fn examples(_: &mut Window, cx: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
|
||||||
let all_kinds = IconDecorationKind::iter().collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let examples = all_kinds
|
|
||||||
.iter()
|
|
||||||
.map(|kind| {
|
|
||||||
single_example(
|
|
||||||
format!("{kind:?}"),
|
|
||||||
IconDecoration::new(*kind, cx.theme().colors().surface_background, cx),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
vec![example_group(examples)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -83,34 +83,3 @@ impl RenderOnce for Indicator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for Indicator {
|
|
||||||
fn description() -> impl Into<Option<&'static str>> {
|
|
||||||
"An indicator visually represents a status or state."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
|
||||||
vec![
|
|
||||||
example_group_with_title(
|
|
||||||
"Types",
|
|
||||||
vec![
|
|
||||||
single_example("Dot", Indicator::dot().color(Color::Info)),
|
|
||||||
single_example("Bar", Indicator::bar().color(Color::Player(2))),
|
|
||||||
single_example(
|
|
||||||
"Icon",
|
|
||||||
Indicator::icon(Icon::new(IconName::Check).color(Color::Success)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
example_group_with_title(
|
|
||||||
"Examples",
|
|
||||||
vec![
|
|
||||||
single_example("Info", Indicator::dot().color(Color::Info)),
|
|
||||||
single_example("Success", Indicator::dot().color(Color::Success)),
|
|
||||||
single_example("Warning", Indicator::dot().color(Color::Warning)),
|
|
||||||
single_example("Error", Indicator::dot().color(Color::Error)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{h_flex, prelude::*};
|
use crate::{h_flex, prelude::*};
|
||||||
use crate::{ElevationIndex, KeyBinding};
|
use crate::{ElevationIndex, KeyBinding};
|
||||||
use gpui::{point, App, BoxShadow, IntoElement, Window};
|
use gpui::{point, AnyElement, App, BoxShadow, IntoElement, Window};
|
||||||
use smallvec::smallvec;
|
use smallvec::smallvec;
|
||||||
|
|
||||||
/// Represents a hint for a keybinding, optionally with a prefix and suffix.
|
/// Represents a hint for a keybinding, optionally with a prefix and suffix.
|
||||||
|
@ -17,7 +17,7 @@ use smallvec::smallvec;
|
||||||
/// .prefix("Save:")
|
/// .prefix("Save:")
|
||||||
/// .size(Pixels::from(14.0));
|
/// .size(Pixels::from(14.0));
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, IntoElement, Clone)]
|
#[derive(Debug, IntoElement, IntoComponent)]
|
||||||
pub struct KeybindingHint {
|
pub struct KeybindingHint {
|
||||||
prefix: Option<SharedString>,
|
prefix: Option<SharedString>,
|
||||||
suffix: Option<SharedString>,
|
suffix: Option<SharedString>,
|
||||||
|
@ -206,102 +206,99 @@ impl RenderOnce for KeybindingHint {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for KeybindingHint {
|
impl ComponentPreview for KeybindingHint {
|
||||||
fn description() -> impl Into<Option<&'static str>> {
|
fn preview(window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
"Used to display hint text for keyboard shortcuts. Can have a prefix and suffix."
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(window: &mut Window, _cx: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
|
||||||
let home_fallback = gpui::KeyBinding::new("home", menu::SelectFirst, None);
|
|
||||||
let home = KeyBinding::for_action(&menu::SelectFirst, window)
|
|
||||||
.unwrap_or(KeyBinding::new(home_fallback));
|
|
||||||
|
|
||||||
let end_fallback = gpui::KeyBinding::new("end", menu::SelectLast, None);
|
|
||||||
let end = KeyBinding::for_action(&menu::SelectLast, window)
|
|
||||||
.unwrap_or(KeyBinding::new(end_fallback));
|
|
||||||
|
|
||||||
let enter_fallback = gpui::KeyBinding::new("enter", menu::Confirm, None);
|
let enter_fallback = gpui::KeyBinding::new("enter", menu::Confirm, None);
|
||||||
let enter = KeyBinding::for_action(&menu::Confirm, window)
|
let enter = KeyBinding::for_action(&menu::Confirm, window)
|
||||||
.unwrap_or(KeyBinding::new(enter_fallback));
|
.unwrap_or(KeyBinding::new(enter_fallback));
|
||||||
|
|
||||||
let escape_fallback = gpui::KeyBinding::new("escape", menu::Cancel, None);
|
v_flex()
|
||||||
let escape = KeyBinding::for_action(&menu::Cancel, window)
|
.gap_6()
|
||||||
.unwrap_or(KeyBinding::new(escape_fallback));
|
.children(vec![
|
||||||
|
example_group_with_title(
|
||||||
vec![
|
"Basic",
|
||||||
example_group_with_title(
|
vec![
|
||||||
"Basic",
|
single_example(
|
||||||
vec![
|
"With Prefix",
|
||||||
single_example(
|
KeybindingHint::with_prefix("Go to Start:", enter.clone())
|
||||||
"With Prefix",
|
.into_any_element(),
|
||||||
KeybindingHint::with_prefix("Go to Start:", home.clone()),
|
),
|
||||||
),
|
single_example(
|
||||||
single_example(
|
"With Suffix",
|
||||||
"With Suffix",
|
KeybindingHint::with_suffix(enter.clone(), "Go to End")
|
||||||
KeybindingHint::with_suffix(end.clone(), "Go to End"),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"With Prefix and Suffix",
|
"With Prefix and Suffix",
|
||||||
KeybindingHint::new(enter.clone())
|
KeybindingHint::new(enter.clone())
|
||||||
.prefix("Confirm:")
|
.prefix("Confirm:")
|
||||||
.suffix("Execute selected action"),
|
.suffix("Execute selected action")
|
||||||
),
|
.into_any_element(),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
example_group_with_title(
|
),
|
||||||
"Sizes",
|
example_group_with_title(
|
||||||
vec![
|
"Sizes",
|
||||||
single_example(
|
vec![
|
||||||
"Small",
|
single_example(
|
||||||
KeybindingHint::new(home.clone())
|
"Small",
|
||||||
.size(Pixels::from(12.0))
|
KeybindingHint::new(enter.clone())
|
||||||
.prefix("Small:"),
|
.size(Pixels::from(12.0))
|
||||||
),
|
.prefix("Small:")
|
||||||
single_example(
|
.into_any_element(),
|
||||||
"Medium",
|
),
|
||||||
KeybindingHint::new(end.clone())
|
single_example(
|
||||||
.size(Pixels::from(16.0))
|
"Medium",
|
||||||
.suffix("Medium"),
|
KeybindingHint::new(enter.clone())
|
||||||
),
|
.size(Pixels::from(16.0))
|
||||||
single_example(
|
.suffix("Medium")
|
||||||
"Large",
|
.into_any_element(),
|
||||||
KeybindingHint::new(enter.clone())
|
),
|
||||||
.size(Pixels::from(20.0))
|
single_example(
|
||||||
.prefix("Large:")
|
"Large",
|
||||||
.suffix("Size"),
|
KeybindingHint::new(enter.clone())
|
||||||
),
|
.size(Pixels::from(20.0))
|
||||||
],
|
.prefix("Large:")
|
||||||
),
|
.suffix("Size")
|
||||||
example_group_with_title(
|
.into_any_element(),
|
||||||
"Elevations",
|
),
|
||||||
vec![
|
],
|
||||||
single_example(
|
),
|
||||||
"Surface",
|
example_group_with_title(
|
||||||
KeybindingHint::new(home.clone())
|
"Elevations",
|
||||||
.elevation(ElevationIndex::Surface)
|
vec![
|
||||||
.prefix("Surface:"),
|
single_example(
|
||||||
),
|
"Surface",
|
||||||
single_example(
|
KeybindingHint::new(enter.clone())
|
||||||
"Elevated Surface",
|
.elevation(ElevationIndex::Surface)
|
||||||
KeybindingHint::new(end.clone())
|
.prefix("Surface:")
|
||||||
.elevation(ElevationIndex::ElevatedSurface)
|
.into_any_element(),
|
||||||
.suffix("Elevated"),
|
),
|
||||||
),
|
single_example(
|
||||||
single_example(
|
"Elevated Surface",
|
||||||
"Editor Surface",
|
KeybindingHint::new(enter.clone())
|
||||||
KeybindingHint::new(enter.clone())
|
.elevation(ElevationIndex::ElevatedSurface)
|
||||||
.elevation(ElevationIndex::EditorSurface)
|
.suffix("Elevated")
|
||||||
.prefix("Editor:")
|
.into_any_element(),
|
||||||
.suffix("Surface"),
|
),
|
||||||
),
|
single_example(
|
||||||
single_example(
|
"Editor Surface",
|
||||||
"Modal Surface",
|
KeybindingHint::new(enter.clone())
|
||||||
KeybindingHint::new(escape.clone())
|
.elevation(ElevationIndex::EditorSurface)
|
||||||
.elevation(ElevationIndex::ModalSurface)
|
.prefix("Editor:")
|
||||||
.prefix("Modal:")
|
.suffix("Surface")
|
||||||
.suffix("Escape"),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
],
|
single_example(
|
||||||
),
|
"Modal Surface",
|
||||||
]
|
KeybindingHint::new(enter.clone())
|
||||||
|
.elevation(ElevationIndex::ModalSurface)
|
||||||
|
.prefix("Modal:")
|
||||||
|
.suffix("Enter")
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use gpui::{App, StyleRefinement, Window};
|
use gpui::{AnyElement, App, StyleRefinement, Window};
|
||||||
|
|
||||||
use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
|
use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ use crate::{prelude::*, LabelCommon, LabelLike, LabelSize, LineHeightStyle};
|
||||||
///
|
///
|
||||||
/// let my_label = Label::new("Deleted").strikethrough(true);
|
/// let my_label = Label::new("Deleted").strikethrough(true);
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
pub struct Label {
|
pub struct Label {
|
||||||
base: LabelLike,
|
base: LabelLike,
|
||||||
label: SharedString,
|
label: SharedString,
|
||||||
|
@ -184,3 +184,53 @@ impl RenderOnce for Label {
|
||||||
self.base.child(self.label)
|
self.base.child(self.label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ComponentPreview for Label {
|
||||||
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.children(vec![
|
||||||
|
example_group_with_title(
|
||||||
|
"Sizes",
|
||||||
|
vec![
|
||||||
|
single_example("Default", Label::new("Default Label").into_any_element()),
|
||||||
|
single_example("Small", Label::new("Small Label").size(LabelSize::Small).into_any_element()),
|
||||||
|
single_example("Large", Label::new("Large Label").size(LabelSize::Large).into_any_element()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
example_group_with_title(
|
||||||
|
"Colors",
|
||||||
|
vec![
|
||||||
|
single_example("Default", Label::new("Default Color").into_any_element()),
|
||||||
|
single_example("Accent", Label::new("Accent Color").color(Color::Accent).into_any_element()),
|
||||||
|
single_example("Error", Label::new("Error Color").color(Color::Error).into_any_element()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
example_group_with_title(
|
||||||
|
"Styles",
|
||||||
|
vec![
|
||||||
|
single_example("Default", Label::new("Default Style").into_any_element()),
|
||||||
|
single_example("Bold", Label::new("Bold Style").weight(gpui::FontWeight::BOLD).into_any_element()),
|
||||||
|
single_example("Italic", Label::new("Italic Style").italic(true).into_any_element()),
|
||||||
|
single_example("Strikethrough", Label::new("Strikethrough Style").strikethrough(true).into_any_element()),
|
||||||
|
single_example("Underline", Label::new("Underline Style").underline(true).into_any_element()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
example_group_with_title(
|
||||||
|
"Line Height Styles",
|
||||||
|
vec![
|
||||||
|
single_example("Default", Label::new("Default Line Height").into_any_element()),
|
||||||
|
single_example("UI Label", Label::new("UI Label Line Height").line_height_style(LineHeightStyle::UiLabel).into_any_element()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
example_group_with_title(
|
||||||
|
"Special Cases",
|
||||||
|
vec![
|
||||||
|
single_example("Single Line", Label::new("Single\nLine\nText").single_line().into_any_element()),
|
||||||
|
single_example("Text Ellipsis", Label::new("This is a very long text that should be truncated with an ellipsis").text_ellipsis().into_any_element()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,9 +4,6 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
/// A [`Checkbox`] that has a [`Label`].
|
|
||||||
///
|
|
||||||
/// [`Checkbox`]: crate::components::Checkbox
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct RadioWithLabel {
|
pub struct RadioWithLabel {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub enum TabCloseSide {
|
||||||
End,
|
End,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
div: Stateful<Div>,
|
div: Stateful<Div>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
@ -171,3 +171,48 @@ impl RenderOnce for Tab {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ComponentPreview for Tab {
|
||||||
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.children(vec![example_group_with_title(
|
||||||
|
"Variations",
|
||||||
|
vec![
|
||||||
|
single_example(
|
||||||
|
"Default",
|
||||||
|
Tab::new("default").child("Default Tab").into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Selected",
|
||||||
|
Tab::new("selected")
|
||||||
|
.toggle_state(true)
|
||||||
|
.child("Selected Tab")
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"First",
|
||||||
|
Tab::new("first")
|
||||||
|
.position(TabPosition::First)
|
||||||
|
.child("First Tab")
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Middle",
|
||||||
|
Tab::new("middle")
|
||||||
|
.position(TabPosition::Middle(Ordering::Equal))
|
||||||
|
.child("Middle Tab")
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Last",
|
||||||
|
Tab::new("last")
|
||||||
|
.position(TabPosition::Last)
|
||||||
|
.child("Last Tab")
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)])
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{prelude::*, Indicator};
|
||||||
use gpui::{div, AnyElement, FontWeight, IntoElement, Length};
|
use gpui::{div, AnyElement, FontWeight, IntoElement, Length};
|
||||||
|
|
||||||
/// A table component
|
/// A table component
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
pub struct Table {
|
pub struct Table {
|
||||||
column_headers: Vec<SharedString>,
|
column_headers: Vec<SharedString>,
|
||||||
rows: Vec<Vec<TableCell>>,
|
rows: Vec<Vec<TableCell>>,
|
||||||
|
@ -152,88 +152,110 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for Table {
|
impl ComponentPreview for Table {
|
||||||
fn description() -> impl Into<Option<&'static str>> {
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
"Used for showing tabular data. Tables may show both text and elements in their cells."
|
v_flex()
|
||||||
}
|
.gap_6()
|
||||||
|
.children(vec![
|
||||||
fn example_label_side() -> ExampleLabelSide {
|
example_group_with_title(
|
||||||
ExampleLabelSide::Top
|
"Basic Tables",
|
||||||
}
|
vec![
|
||||||
|
single_example(
|
||||||
fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
"Simple Table",
|
||||||
vec![
|
Table::new(vec!["Name", "Age", "City"])
|
||||||
example_group(vec![
|
.width(px(400.))
|
||||||
single_example(
|
.row(vec!["Alice", "28", "New York"])
|
||||||
"Simple Table",
|
.row(vec!["Bob", "32", "San Francisco"])
|
||||||
Table::new(vec!["Name", "Age", "City"])
|
.row(vec!["Charlie", "25", "London"])
|
||||||
.width(px(400.))
|
.into_any_element(),
|
||||||
.row(vec!["Alice", "28", "New York"])
|
),
|
||||||
.row(vec!["Bob", "32", "San Francisco"])
|
single_example(
|
||||||
.row(vec!["Charlie", "25", "London"]),
|
"Two Column Table",
|
||||||
|
Table::new(vec!["Category", "Value"])
|
||||||
|
.width(px(300.))
|
||||||
|
.row(vec!["Revenue", "$100,000"])
|
||||||
|
.row(vec!["Expenses", "$75,000"])
|
||||||
|
.row(vec!["Profit", "$25,000"])
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
single_example(
|
example_group_with_title(
|
||||||
"Two Column Table",
|
"Styled Tables",
|
||||||
Table::new(vec!["Category", "Value"])
|
vec![
|
||||||
.width(px(300.))
|
single_example(
|
||||||
.row(vec!["Revenue", "$100,000"])
|
"Default",
|
||||||
.row(vec!["Expenses", "$75,000"])
|
Table::new(vec!["Product", "Price", "Stock"])
|
||||||
.row(vec!["Profit", "$25,000"]),
|
.width(px(400.))
|
||||||
|
.row(vec!["Laptop", "$999", "In Stock"])
|
||||||
|
.row(vec!["Phone", "$599", "Low Stock"])
|
||||||
|
.row(vec!["Tablet", "$399", "Out of Stock"])
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Striped",
|
||||||
|
Table::new(vec!["Product", "Price", "Stock"])
|
||||||
|
.width(px(400.))
|
||||||
|
.striped()
|
||||||
|
.row(vec!["Laptop", "$999", "In Stock"])
|
||||||
|
.row(vec!["Phone", "$599", "Low Stock"])
|
||||||
|
.row(vec!["Tablet", "$399", "Out of Stock"])
|
||||||
|
.row(vec!["Headphones", "$199", "In Stock"])
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
]),
|
example_group_with_title(
|
||||||
example_group(vec![single_example(
|
"Mixed Content Table",
|
||||||
"Striped Table",
|
vec![single_example(
|
||||||
Table::new(vec!["Product", "Price", "Stock"])
|
"Table with Elements",
|
||||||
.width(px(600.))
|
Table::new(vec!["Status", "Name", "Priority", "Deadline", "Action"])
|
||||||
.striped()
|
.width(px(840.))
|
||||||
.row(vec!["Laptop", "$999", "In Stock"])
|
.row(vec![
|
||||||
.row(vec!["Phone", "$599", "Low Stock"])
|
element_cell(
|
||||||
.row(vec!["Tablet", "$399", "Out of Stock"])
|
Indicator::dot().color(Color::Success).into_any_element(),
|
||||||
.row(vec!["Headphones", "$199", "In Stock"]),
|
),
|
||||||
)]),
|
string_cell("Project A"),
|
||||||
example_group_with_title(
|
string_cell("High"),
|
||||||
"Mixed Content Table",
|
string_cell("2023-12-31"),
|
||||||
vec![single_example(
|
element_cell(
|
||||||
"Table with Elements",
|
Button::new("view_a", "View")
|
||||||
Table::new(vec!["Status", "Name", "Priority", "Deadline", "Action"])
|
.style(ButtonStyle::Filled)
|
||||||
.width(px(840.))
|
.full_width()
|
||||||
.row(vec![
|
.into_any_element(),
|
||||||
element_cell(Indicator::dot().color(Color::Success).into_any_element()),
|
),
|
||||||
string_cell("Project A"),
|
])
|
||||||
string_cell("High"),
|
.row(vec![
|
||||||
string_cell("2023-12-31"),
|
element_cell(
|
||||||
element_cell(
|
Indicator::dot().color(Color::Warning).into_any_element(),
|
||||||
Button::new("view_a", "View")
|
),
|
||||||
.style(ButtonStyle::Filled)
|
string_cell("Project B"),
|
||||||
.full_width()
|
string_cell("Medium"),
|
||||||
.into_any_element(),
|
string_cell("2024-03-15"),
|
||||||
),
|
element_cell(
|
||||||
])
|
Button::new("view_b", "View")
|
||||||
.row(vec![
|
.style(ButtonStyle::Filled)
|
||||||
element_cell(Indicator::dot().color(Color::Warning).into_any_element()),
|
.full_width()
|
||||||
string_cell("Project B"),
|
.into_any_element(),
|
||||||
string_cell("Medium"),
|
),
|
||||||
string_cell("2024-03-15"),
|
])
|
||||||
element_cell(
|
.row(vec![
|
||||||
Button::new("view_b", "View")
|
element_cell(
|
||||||
.style(ButtonStyle::Filled)
|
Indicator::dot().color(Color::Error).into_any_element(),
|
||||||
.full_width()
|
),
|
||||||
.into_any_element(),
|
string_cell("Project C"),
|
||||||
),
|
string_cell("Low"),
|
||||||
])
|
string_cell("2024-06-30"),
|
||||||
.row(vec![
|
element_cell(
|
||||||
element_cell(Indicator::dot().color(Color::Error).into_any_element()),
|
Button::new("view_c", "View")
|
||||||
string_cell("Project C"),
|
.style(ButtonStyle::Filled)
|
||||||
string_cell("Low"),
|
.full_width()
|
||||||
string_cell("2024-06-30"),
|
.into_any_element(),
|
||||||
element_cell(
|
),
|
||||||
Button::new("view_c", "View")
|
])
|
||||||
.style(ButtonStyle::Filled)
|
.into_any_element(),
|
||||||
.full_width()
|
)],
|
||||||
.into_any_element(),
|
),
|
||||||
),
|
])
|
||||||
]),
|
.into_any_element()
|
||||||
)],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, hsla, prelude::*, AnyView, CursorStyle, ElementId, Hsla, IntoElement, Styled, Window,
|
div, hsla, prelude::*, AnyElement, AnyView, CursorStyle, ElementId, Hsla, IntoElement, Styled,
|
||||||
|
Window,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
@ -38,7 +39,8 @@ pub enum ToggleStyle {
|
||||||
/// Checkboxes are used for multiple choices, not for mutually exclusive choices.
|
/// Checkboxes are used for multiple choices, not for mutually exclusive choices.
|
||||||
/// Each checkbox works independently from other checkboxes in the list,
|
/// Each checkbox works independently from other checkboxes in the list,
|
||||||
/// therefore checking an additional box does not affect any other selections.
|
/// therefore checking an additional box does not affect any other selections.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
|
#[component(scope = "input")]
|
||||||
pub struct Checkbox {
|
pub struct Checkbox {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
toggle_state: ToggleState,
|
toggle_state: ToggleState,
|
||||||
|
@ -237,7 +239,8 @@ impl RenderOnce for Checkbox {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A [`Checkbox`] that has a [`Label`].
|
/// A [`Checkbox`] that has a [`Label`].
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
|
#[component(scope = "input")]
|
||||||
pub struct CheckboxWithLabel {
|
pub struct CheckboxWithLabel {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
label: Label,
|
label: Label,
|
||||||
|
@ -314,7 +317,8 @@ impl RenderOnce for CheckboxWithLabel {
|
||||||
/// # Switch
|
/// # Switch
|
||||||
///
|
///
|
||||||
/// Switches are used to represent opposite states, such as enabled or disabled.
|
/// Switches are used to represent opposite states, such as enabled or disabled.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
|
#[component(scope = "input")]
|
||||||
pub struct Switch {
|
pub struct Switch {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
toggle_state: ToggleState,
|
toggle_state: ToggleState,
|
||||||
|
@ -446,285 +450,190 @@ impl RenderOnce for Switch {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for Checkbox {
|
impl ComponentPreview for Checkbox {
|
||||||
fn description() -> impl Into<Option<&'static str>> {
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
"A checkbox lets people choose between a pair of opposing states, like enabled and disabled, using a different appearance to indicate each state."
|
v_flex()
|
||||||
}
|
.gap_6()
|
||||||
|
.children(vec![
|
||||||
fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
example_group_with_title(
|
||||||
vec![
|
"States",
|
||||||
example_group_with_title(
|
vec![
|
||||||
"Default",
|
single_example(
|
||||||
vec![
|
"Unselected",
|
||||||
single_example(
|
Checkbox::new("checkbox_unselected", ToggleState::Unselected)
|
||||||
"Unselected",
|
.into_any_element(),
|
||||||
Checkbox::new("checkbox_unselected", ToggleState::Unselected),
|
),
|
||||||
),
|
single_example(
|
||||||
single_example(
|
"Indeterminate",
|
||||||
"Indeterminate",
|
Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate)
|
||||||
Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"Selected",
|
"Selected",
|
||||||
Checkbox::new("checkbox_selected", ToggleState::Selected),
|
Checkbox::new("checkbox_selected", ToggleState::Selected)
|
||||||
),
|
.into_any_element(),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
example_group_with_title(
|
),
|
||||||
"Default (Filled)",
|
example_group_with_title(
|
||||||
vec![
|
"Styles",
|
||||||
single_example(
|
vec![
|
||||||
"Unselected",
|
single_example(
|
||||||
Checkbox::new("checkbox_unselected", ToggleState::Unselected).fill(),
|
"Default",
|
||||||
),
|
Checkbox::new("checkbox_default", ToggleState::Selected)
|
||||||
single_example(
|
.into_any_element(),
|
||||||
"Indeterminate",
|
),
|
||||||
Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate).fill(),
|
single_example(
|
||||||
),
|
"Filled",
|
||||||
single_example(
|
Checkbox::new("checkbox_filled", ToggleState::Selected)
|
||||||
"Selected",
|
.fill()
|
||||||
Checkbox::new("checkbox_selected", ToggleState::Selected).fill(),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
],
|
single_example(
|
||||||
),
|
"ElevationBased",
|
||||||
example_group_with_title(
|
Checkbox::new("checkbox_elevation", ToggleState::Selected)
|
||||||
"ElevationBased",
|
.style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface))
|
||||||
vec![
|
.into_any_element(),
|
||||||
single_example(
|
),
|
||||||
"Unselected",
|
single_example(
|
||||||
Checkbox::new("checkbox_unfilled_unselected", ToggleState::Unselected)
|
"Custom Color",
|
||||||
.style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
|
Checkbox::new("checkbox_custom", ToggleState::Selected)
|
||||||
),
|
.style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7)))
|
||||||
single_example(
|
.into_any_element(),
|
||||||
"Indeterminate",
|
),
|
||||||
Checkbox::new(
|
],
|
||||||
"checkbox_unfilled_indeterminate",
|
),
|
||||||
ToggleState::Indeterminate,
|
example_group_with_title(
|
||||||
)
|
"Disabled",
|
||||||
.style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
|
vec![
|
||||||
),
|
single_example(
|
||||||
single_example(
|
"Unselected",
|
||||||
"Selected",
|
Checkbox::new("checkbox_disabled_unselected", ToggleState::Unselected)
|
||||||
Checkbox::new("checkbox_unfilled_selected", ToggleState::Selected)
|
.disabled(true)
|
||||||
.style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
],
|
single_example(
|
||||||
),
|
"Selected",
|
||||||
example_group_with_title(
|
Checkbox::new("checkbox_disabled_selected", ToggleState::Selected)
|
||||||
"ElevationBased (Filled)",
|
.disabled(true)
|
||||||
vec![
|
.into_any_element(),
|
||||||
single_example(
|
),
|
||||||
"Unselected",
|
],
|
||||||
Checkbox::new("checkbox_filled_unselected", ToggleState::Unselected)
|
),
|
||||||
.fill()
|
example_group_with_title(
|
||||||
.style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
|
"With Label",
|
||||||
),
|
vec![single_example(
|
||||||
single_example(
|
"Default",
|
||||||
"Indeterminate",
|
Checkbox::new("checkbox_with_label", ToggleState::Selected)
|
||||||
Checkbox::new("checkbox_filled_indeterminate", ToggleState::Indeterminate)
|
.label("Always save on quit")
|
||||||
.fill()
|
.into_any_element(),
|
||||||
.style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
|
)],
|
||||||
),
|
),
|
||||||
single_example(
|
])
|
||||||
"Selected",
|
.into_any_element()
|
||||||
Checkbox::new("checkbox_filled_selected", ToggleState::Selected)
|
|
||||||
.fill()
|
|
||||||
.style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
example_group_with_title(
|
|
||||||
"Custom Color",
|
|
||||||
vec![
|
|
||||||
single_example(
|
|
||||||
"Unselected",
|
|
||||||
Checkbox::new("checkbox_custom_unselected", ToggleState::Unselected)
|
|
||||||
.style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7))),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Indeterminate",
|
|
||||||
Checkbox::new("checkbox_custom_indeterminate", ToggleState::Indeterminate)
|
|
||||||
.style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7))),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Selected",
|
|
||||||
Checkbox::new("checkbox_custom_selected", ToggleState::Selected)
|
|
||||||
.style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7))),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
example_group_with_title(
|
|
||||||
"Custom Color (Filled)",
|
|
||||||
vec![
|
|
||||||
single_example(
|
|
||||||
"Unselected",
|
|
||||||
Checkbox::new("checkbox_custom_filled_unselected", ToggleState::Unselected)
|
|
||||||
.fill()
|
|
||||||
.style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7))),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Indeterminate",
|
|
||||||
Checkbox::new(
|
|
||||||
"checkbox_custom_filled_indeterminate",
|
|
||||||
ToggleState::Indeterminate,
|
|
||||||
)
|
|
||||||
.fill()
|
|
||||||
.style(ToggleStyle::Custom(hsla(
|
|
||||||
142.0 / 360.,
|
|
||||||
0.68,
|
|
||||||
0.45,
|
|
||||||
0.7,
|
|
||||||
))),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Selected",
|
|
||||||
Checkbox::new("checkbox_custom_filled_selected", ToggleState::Selected)
|
|
||||||
.fill()
|
|
||||||
.style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7))),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
example_group_with_title(
|
|
||||||
"Disabled",
|
|
||||||
vec![
|
|
||||||
single_example(
|
|
||||||
"Unselected",
|
|
||||||
Checkbox::new("checkbox_disabled_unselected", ToggleState::Unselected)
|
|
||||||
.disabled(true),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Indeterminate",
|
|
||||||
Checkbox::new(
|
|
||||||
"checkbox_disabled_indeterminate",
|
|
||||||
ToggleState::Indeterminate,
|
|
||||||
)
|
|
||||||
.disabled(true),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Selected",
|
|
||||||
Checkbox::new("checkbox_disabled_selected", ToggleState::Selected)
|
|
||||||
.disabled(true),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
example_group_with_title(
|
|
||||||
"Disabled (Filled)",
|
|
||||||
vec![
|
|
||||||
single_example(
|
|
||||||
"Unselected",
|
|
||||||
Checkbox::new(
|
|
||||||
"checkbox_disabled_filled_unselected",
|
|
||||||
ToggleState::Unselected,
|
|
||||||
)
|
|
||||||
.fill()
|
|
||||||
.disabled(true),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Indeterminate",
|
|
||||||
Checkbox::new(
|
|
||||||
"checkbox_disabled_filled_indeterminate",
|
|
||||||
ToggleState::Indeterminate,
|
|
||||||
)
|
|
||||||
.fill()
|
|
||||||
.disabled(true),
|
|
||||||
),
|
|
||||||
single_example(
|
|
||||||
"Selected",
|
|
||||||
Checkbox::new("checkbox_disabled_filled_selected", ToggleState::Selected)
|
|
||||||
.fill()
|
|
||||||
.disabled(true),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for Switch {
|
impl ComponentPreview for Switch {
|
||||||
fn description() -> impl Into<Option<&'static str>> {
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
"A switch toggles between two mutually exclusive states, typically used for enabling or disabling a setting."
|
v_flex()
|
||||||
}
|
.gap_6()
|
||||||
|
.children(vec![
|
||||||
fn examples(_window: &mut Window, _cx: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
example_group_with_title(
|
||||||
vec![
|
"States",
|
||||||
example_group_with_title(
|
vec![
|
||||||
"Default",
|
single_example(
|
||||||
vec![
|
"Off",
|
||||||
single_example(
|
Switch::new("switch_off", ToggleState::Unselected)
|
||||||
"Off",
|
.on_click(|_, _, _cx| {})
|
||||||
Switch::new("switch_off", ToggleState::Unselected).on_click(|_, _, _cx| {}),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
single_example(
|
single_example(
|
||||||
"On",
|
"On",
|
||||||
Switch::new("switch_on", ToggleState::Selected).on_click(|_, _, _cx| {}),
|
Switch::new("switch_on", ToggleState::Selected)
|
||||||
),
|
.on_click(|_, _, _cx| {})
|
||||||
],
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
example_group_with_title(
|
],
|
||||||
"Disabled",
|
),
|
||||||
vec![
|
example_group_with_title(
|
||||||
single_example(
|
"Disabled",
|
||||||
"Off",
|
vec![
|
||||||
Switch::new("switch_disabled_off", ToggleState::Unselected).disabled(true),
|
single_example(
|
||||||
),
|
"Off",
|
||||||
single_example(
|
Switch::new("switch_disabled_off", ToggleState::Unselected)
|
||||||
"On",
|
.disabled(true)
|
||||||
Switch::new("switch_disabled_on", ToggleState::Selected).disabled(true),
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
],
|
single_example(
|
||||||
),
|
"On",
|
||||||
example_group_with_title(
|
Switch::new("switch_disabled_on", ToggleState::Selected)
|
||||||
"Label Permutations",
|
.disabled(true)
|
||||||
vec![
|
.into_any_element(),
|
||||||
single_example(
|
),
|
||||||
"Label",
|
],
|
||||||
Switch::new("switch_with_label", ToggleState::Selected)
|
),
|
||||||
.label("Always save on quit"),
|
example_group_with_title(
|
||||||
),
|
"With Label",
|
||||||
single_example(
|
vec![
|
||||||
"Keybinding",
|
single_example(
|
||||||
Switch::new("switch_with_label", ToggleState::Selected)
|
"Label",
|
||||||
.key_binding(theme_preview_keybinding("cmd-shift-e")),
|
Switch::new("switch_with_label", ToggleState::Selected)
|
||||||
),
|
.label("Always save on quit")
|
||||||
],
|
.into_any_element(),
|
||||||
),
|
),
|
||||||
]
|
// TODO: Where did theme_preview_keybinding go?
|
||||||
|
// single_example(
|
||||||
|
// "Keybinding",
|
||||||
|
// Switch::new("switch_with_keybinding", ToggleState::Selected)
|
||||||
|
// .key_binding(theme_preview_keybinding("cmd-shift-e"))
|
||||||
|
// .into_any_element(),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
])
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentPreview for CheckboxWithLabel {
|
impl ComponentPreview for CheckboxWithLabel {
|
||||||
fn description() -> impl Into<Option<&'static str>> {
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
"A checkbox with an associated label, allowing users to select an option while providing a descriptive text."
|
v_flex()
|
||||||
}
|
.gap_6()
|
||||||
|
.children(vec![example_group_with_title(
|
||||||
fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
|
"States",
|
||||||
vec![example_group(vec![
|
vec![
|
||||||
single_example(
|
single_example(
|
||||||
"Unselected",
|
"Unselected",
|
||||||
CheckboxWithLabel::new(
|
CheckboxWithLabel::new(
|
||||||
"checkbox_with_label_unselected",
|
"checkbox_with_label_unselected",
|
||||||
Label::new("Always save on quit"),
|
Label::new("Always save on quit"),
|
||||||
ToggleState::Unselected,
|
ToggleState::Unselected,
|
||||||
|_, _, _| {},
|
|_, _, _| {},
|
||||||
),
|
)
|
||||||
),
|
.into_any_element(),
|
||||||
single_example(
|
),
|
||||||
"Indeterminate",
|
single_example(
|
||||||
CheckboxWithLabel::new(
|
"Indeterminate",
|
||||||
"checkbox_with_label_indeterminate",
|
CheckboxWithLabel::new(
|
||||||
Label::new("Always save on quit"),
|
"checkbox_with_label_indeterminate",
|
||||||
ToggleState::Indeterminate,
|
Label::new("Always save on quit"),
|
||||||
|_, _, _| {},
|
ToggleState::Indeterminate,
|
||||||
),
|
|_, _, _| {},
|
||||||
),
|
)
|
||||||
single_example(
|
.into_any_element(),
|
||||||
"Selected",
|
),
|
||||||
CheckboxWithLabel::new(
|
single_example(
|
||||||
"checkbox_with_label_selected",
|
"Selected",
|
||||||
Label::new("Always save on quit"),
|
CheckboxWithLabel::new(
|
||||||
ToggleState::Selected,
|
"checkbox_with_label_selected",
|
||||||
|_, _, _| {},
|
Label::new("Always save on quit"),
|
||||||
),
|
ToggleState::Selected,
|
||||||
),
|
|_, _, _| {},
|
||||||
])]
|
)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)])
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use gpui::{Action, AnyView, AppContext as _, FocusHandle, IntoElement, Render};
|
use gpui::{Action, AnyElement, AnyView, AppContext as _, FocusHandle, IntoElement, Render};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::{h_flex, v_flex, Color, KeyBinding, Label, LabelSize, StyledExt};
|
use crate::{h_flex, v_flex, Color, KeyBinding, Label, LabelSize, StyledExt};
|
||||||
|
|
||||||
|
#[derive(IntoComponent)]
|
||||||
pub struct Tooltip {
|
pub struct Tooltip {
|
||||||
title: SharedString,
|
title: SharedString,
|
||||||
meta: Option<SharedString>,
|
meta: Option<SharedString>,
|
||||||
|
@ -204,3 +205,15 @@ impl Render for LinkPreview {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ComponentPreview for Tooltip {
|
||||||
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
|
example_group(vec![single_example(
|
||||||
|
"Text only",
|
||||||
|
Button::new("delete-example", "Delete")
|
||||||
|
.tooltip(Tooltip::text("This is a tooltip!"))
|
||||||
|
.into_any_element(),
|
||||||
|
)])
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@ pub use gpui::{
|
||||||
InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, Window,
|
InteractiveElement, ParentElement, Pixels, Rems, RenderOnce, SharedString, Styled, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use component::{example_group, example_group_with_title, single_example, ComponentPreview};
|
||||||
|
pub use ui_macros::IntoComponent;
|
||||||
|
|
||||||
pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle, StyledTypography, TextSize};
|
pub use crate::styles::{rems_from_px, vh, vw, PlatformStyle, StyledTypography, TextSize};
|
||||||
pub use crate::traits::clickable::*;
|
pub use crate::traits::clickable::*;
|
||||||
pub use crate::traits::component_preview::*;
|
|
||||||
pub use crate::traits::disableable::*;
|
pub use crate::traits::disableable::*;
|
||||||
pub use crate::traits::fixed::*;
|
pub use crate::traits::fixed::*;
|
||||||
pub use crate::traits::styled_ext::*;
|
pub use crate::traits::styled_ext::*;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
use crate::prelude::*;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, rems, App, IntoElement, ParentElement, Rems, RenderOnce, SharedString, Styled, Window,
|
div, rems, AnyElement, App, IntoElement, ParentElement, Rems, RenderOnce, SharedString, Styled,
|
||||||
|
Window,
|
||||||
};
|
};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use theme::{ActiveTheme, ThemeSettings};
|
use theme::{ActiveTheme, ThemeSettings};
|
||||||
|
@ -188,7 +190,7 @@ impl HeadlineSize {
|
||||||
|
|
||||||
/// A headline element, used to emphasize some text and
|
/// A headline element, used to emphasize some text and
|
||||||
/// create a visual hierarchy.
|
/// create a visual hierarchy.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement, IntoComponent)]
|
||||||
pub struct Headline {
|
pub struct Headline {
|
||||||
size: HeadlineSize,
|
size: HeadlineSize,
|
||||||
text: SharedString,
|
text: SharedString,
|
||||||
|
@ -230,3 +232,44 @@ impl Headline {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ComponentPreview for Headline {
|
||||||
|
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||||
|
v_flex()
|
||||||
|
.gap_6()
|
||||||
|
.children(vec![example_group_with_title(
|
||||||
|
"Headline Sizes",
|
||||||
|
vec![
|
||||||
|
single_example(
|
||||||
|
"XLarge",
|
||||||
|
Headline::new("XLarge Headline")
|
||||||
|
.size(HeadlineSize::XLarge)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Large",
|
||||||
|
Headline::new("Large Headline")
|
||||||
|
.size(HeadlineSize::Large)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Medium (Default)",
|
||||||
|
Headline::new("Medium Headline").into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"Small",
|
||||||
|
Headline::new("Small Headline")
|
||||||
|
.size(HeadlineSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
single_example(
|
||||||
|
"XSmall",
|
||||||
|
Headline::new("XSmall Headline")
|
||||||
|
.size(HeadlineSize::XSmall)
|
||||||
|
.into_any_element(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)])
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
pub mod clickable;
|
pub mod clickable;
|
||||||
pub mod component_preview;
|
|
||||||
pub mod disableable;
|
pub mod disableable;
|
||||||
pub mod fixed;
|
pub mod fixed;
|
||||||
pub mod styled_ext;
|
pub mod styled_ext;
|
||||||
|
|
|
@ -1,205 +0,0 @@
|
||||||
#![allow(missing_docs)]
|
|
||||||
use crate::{prelude::*, KeyBinding};
|
|
||||||
use gpui::{AnyElement, SharedString};
|
|
||||||
|
|
||||||
/// Which side of the preview to show labels on
|
|
||||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum ExampleLabelSide {
|
|
||||||
/// Left side
|
|
||||||
Left,
|
|
||||||
/// Right side
|
|
||||||
Right,
|
|
||||||
#[default]
|
|
||||||
/// Top side
|
|
||||||
Top,
|
|
||||||
/// Bottom side
|
|
||||||
Bottom,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implement this trait to enable rich UI previews with metadata in the Theme Preview tool.
|
|
||||||
pub trait ComponentPreview: IntoElement {
|
|
||||||
fn title() -> &'static str {
|
|
||||||
std::any::type_name::<Self>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn description() -> impl Into<Option<&'static str>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn example_label_side() -> ExampleLabelSide {
|
|
||||||
ExampleLabelSide::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn examples(_window: &mut Window, _cx: &mut App) -> Vec<ComponentExampleGroup<Self>>;
|
|
||||||
|
|
||||||
fn custom_example(_window: &mut Window, _cx: &mut App) -> impl Into<Option<AnyElement>> {
|
|
||||||
None::<AnyElement>
|
|
||||||
}
|
|
||||||
|
|
||||||
fn component_previews(window: &mut Window, cx: &mut App) -> Vec<AnyElement> {
|
|
||||||
Self::examples(window, cx)
|
|
||||||
.into_iter()
|
|
||||||
.map(|example| Self::render_example_group(example))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_component_previews(window: &mut Window, cx: &mut App) -> AnyElement {
|
|
||||||
let title = Self::title();
|
|
||||||
let (source, title) = title
|
|
||||||
.rsplit_once("::")
|
|
||||||
.map_or((None, title), |(s, t)| (Some(s), t));
|
|
||||||
let description = Self::description().into();
|
|
||||||
|
|
||||||
v_flex()
|
|
||||||
.w_full()
|
|
||||||
.gap_6()
|
|
||||||
.p_4()
|
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().colors().border)
|
|
||||||
.rounded_md()
|
|
||||||
.child(
|
|
||||||
v_flex()
|
|
||||||
.gap_1()
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.gap_1()
|
|
||||||
.child(Headline::new(title).size(HeadlineSize::Small))
|
|
||||||
.when_some(source, |this, source| {
|
|
||||||
this.child(Label::new(format!("({})", source)).color(Color::Muted))
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.when_some(description, |this, description| {
|
|
||||||
this.child(
|
|
||||||
div()
|
|
||||||
.text_ui_sm(cx)
|
|
||||||
.text_color(cx.theme().colors().text_muted)
|
|
||||||
.max_w(px(600.0))
|
|
||||||
.child(description),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.when_some(
|
|
||||||
Self::custom_example(window, cx).into(),
|
|
||||||
|this, custom_example| this.child(custom_example),
|
|
||||||
)
|
|
||||||
.children(Self::component_previews(window, cx))
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_example_group(group: ComponentExampleGroup<Self>) -> AnyElement {
|
|
||||||
v_flex()
|
|
||||||
.gap_6()
|
|
||||||
.when(group.grow, |this| this.w_full().flex_1())
|
|
||||||
.when_some(group.title, |this, title| {
|
|
||||||
this.child(Label::new(title).size(LabelSize::Small))
|
|
||||||
})
|
|
||||||
.child(
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.gap_6()
|
|
||||||
.children(group.examples.into_iter().map(Self::render_example))
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_example(example: ComponentExample<Self>) -> AnyElement {
|
|
||||||
let base = div().flex();
|
|
||||||
|
|
||||||
let base = match Self::example_label_side() {
|
|
||||||
ExampleLabelSide::Right => base.flex_row(),
|
|
||||||
ExampleLabelSide::Left => base.flex_row_reverse(),
|
|
||||||
ExampleLabelSide::Bottom => base.flex_col(),
|
|
||||||
ExampleLabelSide::Top => base.flex_col_reverse(),
|
|
||||||
};
|
|
||||||
|
|
||||||
base.gap_1()
|
|
||||||
.when(example.grow, |this| this.flex_1())
|
|
||||||
.child(example.element)
|
|
||||||
.child(
|
|
||||||
Label::new(example.variant_name)
|
|
||||||
.size(LabelSize::XSmall)
|
|
||||||
.color(Color::Muted),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A single example of a component.
|
|
||||||
pub struct ComponentExample<T> {
|
|
||||||
variant_name: SharedString,
|
|
||||||
element: T,
|
|
||||||
grow: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ComponentExample<T> {
|
|
||||||
/// Create a new example with the given variant name and example value.
|
|
||||||
pub fn new(variant_name: impl Into<SharedString>, example: T) -> Self {
|
|
||||||
Self {
|
|
||||||
variant_name: variant_name.into(),
|
|
||||||
element: example,
|
|
||||||
grow: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the example to grow to fill the available horizontal space.
|
|
||||||
pub fn grow(mut self) -> Self {
|
|
||||||
self.grow = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A group of component examples.
|
|
||||||
pub struct ComponentExampleGroup<T> {
|
|
||||||
pub title: Option<SharedString>,
|
|
||||||
pub examples: Vec<ComponentExample<T>>,
|
|
||||||
pub grow: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> ComponentExampleGroup<T> {
|
|
||||||
/// Create a new group of examples with the given title.
|
|
||||||
pub fn new(examples: Vec<ComponentExample<T>>) -> Self {
|
|
||||||
Self {
|
|
||||||
title: None,
|
|
||||||
examples,
|
|
||||||
grow: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new group of examples with the given title.
|
|
||||||
pub fn with_title(title: impl Into<SharedString>, examples: Vec<ComponentExample<T>>) -> Self {
|
|
||||||
Self {
|
|
||||||
title: Some(title.into()),
|
|
||||||
examples,
|
|
||||||
grow: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the group to grow to fill the available horizontal space.
|
|
||||||
pub fn grow(mut self) -> Self {
|
|
||||||
self.grow = true;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a single example
|
|
||||||
pub fn single_example<T>(variant_name: impl Into<SharedString>, example: T) -> ComponentExample<T> {
|
|
||||||
ComponentExample::new(variant_name, example)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a group of examples without a title
|
|
||||||
pub fn example_group<T>(examples: Vec<ComponentExample<T>>) -> ComponentExampleGroup<T> {
|
|
||||||
ComponentExampleGroup::new(examples)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a group of examples with a title
|
|
||||||
pub fn example_group_with_title<T>(
|
|
||||||
title: impl Into<SharedString>,
|
|
||||||
examples: Vec<ComponentExample<T>>,
|
|
||||||
) -> ComponentExampleGroup<T> {
|
|
||||||
ComponentExampleGroup::with_title(title, examples)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn theme_preview_keybinding(keystrokes: &str) -> KeyBinding {
|
|
||||||
KeyBinding::new(gpui::KeyBinding::new(keystrokes, gpui::NoAction {}, None))
|
|
||||||
}
|
|
|
@ -13,7 +13,8 @@ path = "src/ui_macros.rs"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
convert_case.workspace = true
|
||||||
|
linkme.workspace = true
|
||||||
proc-macro2.workspace = true
|
proc-macro2.workspace = true
|
||||||
quote.workspace = true
|
quote.workspace = true
|
||||||
syn.workspace = true
|
syn.workspace = true
|
||||||
convert_case.workspace = true
|
|
||||||
|
|
97
crates/ui_macros/src/derive_component.rs
Normal file
97
crates/ui_macros/src/derive_component.rs
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, DeriveInput, Lit, Meta, MetaList, MetaNameValue, NestedMeta};
|
||||||
|
|
||||||
|
pub fn derive_into_component(input: TokenStream) -> TokenStream {
|
||||||
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
|
let mut scope_val = None;
|
||||||
|
let mut description_val = None;
|
||||||
|
|
||||||
|
for attr in &input.attrs {
|
||||||
|
if attr.path.is_ident("component") {
|
||||||
|
if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
|
||||||
|
for item in nested {
|
||||||
|
if let NestedMeta::Meta(Meta::NameValue(MetaNameValue {
|
||||||
|
path,
|
||||||
|
lit: Lit::Str(s),
|
||||||
|
..
|
||||||
|
})) = item
|
||||||
|
{
|
||||||
|
let ident = path.get_ident().map(|i| i.to_string()).unwrap_or_default();
|
||||||
|
if ident == "scope" {
|
||||||
|
scope_val = Some(s.value());
|
||||||
|
} else if ident == "description" {
|
||||||
|
description_val = Some(s.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = &input.ident;
|
||||||
|
|
||||||
|
let scope_impl = if let Some(s) = scope_val {
|
||||||
|
quote! {
|
||||||
|
fn scope() -> Option<&'static str> {
|
||||||
|
Some(#s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
fn scope() -> Option<&'static str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let description_impl = if let Some(desc) = description_val {
|
||||||
|
quote! {
|
||||||
|
fn description() -> Option<&'static str> {
|
||||||
|
Some(#desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {}
|
||||||
|
};
|
||||||
|
|
||||||
|
let register_component_name = syn::Ident::new(
|
||||||
|
&format!(
|
||||||
|
"__register_component_{}",
|
||||||
|
Casing::to_case(&name.to_string(), Case::Snake)
|
||||||
|
),
|
||||||
|
name.span(),
|
||||||
|
);
|
||||||
|
let register_preview_name = syn::Ident::new(
|
||||||
|
&format!(
|
||||||
|
"__register_preview_{}",
|
||||||
|
Casing::to_case(&name.to_string(), Case::Snake)
|
||||||
|
),
|
||||||
|
name.span(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let expanded = quote! {
|
||||||
|
impl component::Component for #name {
|
||||||
|
#scope_impl
|
||||||
|
|
||||||
|
fn name() -> &'static str {
|
||||||
|
stringify!(#name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#description_impl
|
||||||
|
}
|
||||||
|
|
||||||
|
#[linkme::distributed_slice(component::__ALL_COMPONENTS)]
|
||||||
|
fn #register_component_name() {
|
||||||
|
component::register_component::<#name>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[linkme::distributed_slice(component::__ALL_PREVIEWS)]
|
||||||
|
fn #register_preview_name() {
|
||||||
|
component::register_preview::<#name>();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
expanded.into()
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod derive_component;
|
||||||
mod derive_path_str;
|
mod derive_path_str;
|
||||||
mod dynamic_spacing;
|
mod dynamic_spacing;
|
||||||
|
|
||||||
|
@ -58,3 +59,27 @@ pub fn path_str(_args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
pub fn derive_dynamic_spacing(input: TokenStream) -> TokenStream {
|
pub fn derive_dynamic_spacing(input: TokenStream) -> TokenStream {
|
||||||
dynamic_spacing::derive_spacing(input)
|
dynamic_spacing::derive_spacing(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Derives the `Component` trait for a struct.
|
||||||
|
///
|
||||||
|
/// This macro generates implementations for the `Component` trait and associated
|
||||||
|
/// registration functions for the component system.
|
||||||
|
///
|
||||||
|
/// # Attributes
|
||||||
|
///
|
||||||
|
/// - `#[component(scope = "...")]`: Required. Specifies the scope of the component.
|
||||||
|
/// - `#[component(description = "...")]`: Optional. Provides a description for the component.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use ui_macros::Component;
|
||||||
|
///
|
||||||
|
/// #[derive(Component)]
|
||||||
|
/// #[component(scope = "toggle", description = "A element that can be toggled on and off")]
|
||||||
|
/// struct Checkbox;
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_derive(IntoComponent, attributes(component))]
|
||||||
|
pub fn derive_component(input: TokenStream) -> TokenStream {
|
||||||
|
derive_component::derive_into_component(input)
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ call.workspace = true
|
||||||
client.workspace = true
|
client.workspace = true
|
||||||
clock.workspace = true
|
clock.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
|
component.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
derive_more.workspace = true
|
derive_more.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
|
|
|
@ -27,7 +27,6 @@ pub fn init(cx: &mut App) {
|
||||||
enum ThemePreviewPage {
|
enum ThemePreviewPage {
|
||||||
Overview,
|
Overview,
|
||||||
Typography,
|
Typography,
|
||||||
Components,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThemePreviewPage {
|
impl ThemePreviewPage {
|
||||||
|
@ -35,7 +34,6 @@ impl ThemePreviewPage {
|
||||||
match self {
|
match self {
|
||||||
Self::Overview => "Overview",
|
Self::Overview => "Overview",
|
||||||
Self::Typography => "Typography",
|
Self::Typography => "Typography",
|
||||||
Self::Components => "Components",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,9 +62,6 @@ impl ThemePreview {
|
||||||
ThemePreviewPage::Typography => {
|
ThemePreviewPage::Typography => {
|
||||||
self.render_typography_page(window, cx).into_any_element()
|
self.render_typography_page(window, cx).into_any_element()
|
||||||
}
|
}
|
||||||
ThemePreviewPage::Components => {
|
|
||||||
self.render_components_page(window, cx).into_any_element()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -392,28 +387,6 @@ impl ThemePreview {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_components_page(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
||||||
let layer = ElevationIndex::Surface;
|
|
||||||
|
|
||||||
v_flex()
|
|
||||||
.id("theme-preview-components")
|
|
||||||
.overflow_scroll()
|
|
||||||
.size_full()
|
|
||||||
.gap_2()
|
|
||||||
.child(Button::render_component_previews(window, cx))
|
|
||||||
.child(Checkbox::render_component_previews(window, cx))
|
|
||||||
.child(CheckboxWithLabel::render_component_previews(window, cx))
|
|
||||||
.child(ContentGroup::render_component_previews(window, cx))
|
|
||||||
.child(DecoratedIcon::render_component_previews(window, cx))
|
|
||||||
.child(Facepile::render_component_previews(window, cx))
|
|
||||||
.child(Icon::render_component_previews(window, cx))
|
|
||||||
.child(IconDecoration::render_component_previews(window, cx))
|
|
||||||
.child(KeybindingHint::render_component_previews(window, cx))
|
|
||||||
.child(Indicator::render_component_previews(window, cx))
|
|
||||||
.child(Switch::render_component_previews(window, cx))
|
|
||||||
.child(Table::render_component_previews(window, cx))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_page_nav(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_page_nav(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("theme-preview-nav")
|
.id("theme-preview-nav")
|
||||||
|
|
|
@ -148,6 +148,7 @@ actions!(
|
||||||
Open,
|
Open,
|
||||||
OpenFiles,
|
OpenFiles,
|
||||||
OpenInTerminal,
|
OpenInTerminal,
|
||||||
|
OpenComponentPreview,
|
||||||
ReloadActiveItem,
|
ReloadActiveItem,
|
||||||
SaveAs,
|
SaveAs,
|
||||||
SaveWithoutFormat,
|
SaveWithoutFormat,
|
||||||
|
@ -378,6 +379,7 @@ fn prompt_and_open_paths(app_state: Arc<AppState>, options: PathPromptOptions, c
|
||||||
|
|
||||||
pub fn init(app_state: Arc<AppState>, cx: &mut App) {
|
pub fn init(app_state: Arc<AppState>, cx: &mut App) {
|
||||||
init_settings(cx);
|
init_settings(cx);
|
||||||
|
component::init();
|
||||||
theme_preview::init(cx);
|
theme_preview::init(cx);
|
||||||
|
|
||||||
cx.on_action(Workspace::close_global);
|
cx.on_action(Workspace::close_global);
|
||||||
|
|
|
@ -39,6 +39,7 @@ collab_ui.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
command_palette.workspace = true
|
command_palette.workspace = true
|
||||||
command_palette_hooks.workspace = true
|
command_palette_hooks.workspace = true
|
||||||
|
component_preview.workspace = true
|
||||||
copilot.workspace = true
|
copilot.workspace = true
|
||||||
db.workspace = true
|
db.workspace = true
|
||||||
diagnostics.workspace = true
|
diagnostics.workspace = true
|
||||||
|
@ -54,8 +55,8 @@ file_icons.workspace = true
|
||||||
fs.workspace = true
|
fs.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
git.workspace = true
|
git.workspace = true
|
||||||
git_ui.workspace = true
|
|
||||||
git_hosting_providers.workspace = true
|
git_hosting_providers.workspace = true
|
||||||
|
git_ui.workspace = true
|
||||||
go_to_line.workspace = true
|
go_to_line.workspace = true
|
||||||
gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] }
|
gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] }
|
||||||
gpui_tokio.workspace = true
|
gpui_tokio.workspace = true
|
||||||
|
|
|
@ -490,6 +490,7 @@ fn main() {
|
||||||
project_panel::init(Assets, cx);
|
project_panel::init(Assets, cx);
|
||||||
git_ui::git_panel::init(cx);
|
git_ui::git_panel::init(cx);
|
||||||
outline_panel::init(Assets, cx);
|
outline_panel::init(Assets, cx);
|
||||||
|
component_preview::init(cx);
|
||||||
tasks_ui::init(cx);
|
tasks_ui::init(cx);
|
||||||
snippets_ui::init(cx);
|
snippets_ui::init(cx);
|
||||||
channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
|
channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue