Detailed changes
@@ -2942,6 +2942,28 @@ dependencies = [
"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]]
name = "concurrent-queue"
version = "2.5.0"
@@ -7280,6 +7302,26 @@ dependencies = [
"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]]
name = "linux-raw-sys"
version = "0.4.14"
@@ -8693,9 +8735,9 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.20.2"
+version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "oo7"
@@ -14320,8 +14362,10 @@ name = "ui"
version = "0.1.0"
dependencies = [
"chrono",
+ "component",
"gpui",
"itertools 0.14.0",
+ "linkme",
"menu",
"serde",
"settings",
@@ -14349,6 +14393,7 @@ name = "ui_macros"
version = "0.1.0"
dependencies = [
"convert_case 0.7.1",
+ "linkme",
"proc-macro2",
"quote",
"syn 1.0.109",
@@ -16120,6 +16165,7 @@ dependencies = [
"client",
"clock",
"collections",
+ "component",
"db",
"derive_more",
"env_logger 0.11.6",
@@ -16554,6 +16600,7 @@ dependencies = [
"collections",
"command_palette",
"command_palette_hooks",
+ "component_preview",
"copilot",
"db",
"diagnostics",
@@ -26,6 +26,8 @@ members = [
"crates/collections",
"crates/command_palette",
"crates/command_palette_hooks",
+ "crates/component",
+ "crates/component_preview",
"crates/context_server",
"crates/context_server_settings",
"crates/copilot",
@@ -226,6 +228,8 @@ collab_ui = { path = "crates/collab_ui" }
collections = { path = "crates/collections" }
command_palette = { path = "crates/command_palette" }
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_settings = { path = "crates/context_server_settings" }
copilot = { path = "crates/copilot" }
@@ -426,6 +430,7 @@ jupyter-websocket-client = { version = "0.9.0" }
libc = "0.2"
libsqlite3-sys = { version = "0.30.1", features = ["bundled"] }
linkify = "0.10.0"
+linkme = "0.3.31"
livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "811ceae29fabee455f110c56cd66b3f49a7e5003", features = [
"dispatcher",
"services-dispatcher",
@@ -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 = []
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -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)
+}
@@ -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
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -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]
chrono.workspace = true
+component.workspace = true
gpui.workspace = true
itertools = { workspace = true, optional = true }
+linkme.workspace = true
menu.workspace = true
serde.workspace = true
settings.workspace = true
@@ -31,3 +33,7 @@ windows.workspace = true
[features]
default = []
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};
@@ -14,7 +14,7 @@ use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled};
/// .grayscale(true)
/// .border_color(gpui::red());
/// ```
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
pub struct Avatar {
image: Img,
size: Option<AbsoluteLength>,
@@ -96,3 +96,60 @@ impl RenderOnce for Avatar {
.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)]
-use gpui::{AnyView, DefiniteLength};
+use component::{example_group_with_title, single_example, ComponentPreview};
+use gpui::{AnyElement, AnyView, DefiniteLength};
+use ui_macros::IntoComponent;
use crate::{
prelude::*, Color, DynamicSpacing, ElevationIndex, IconPosition, KeyBinding,
@@ -78,7 +80,7 @@ use super::button_icon::ButtonIcon;
/// });
/// ```
///
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
pub struct Button {
base: ButtonLike,
label: SharedString,
@@ -455,101 +457,124 @@ impl RenderOnce for Button {
}
impl ComponentPreview for Button {
- fn description() -> impl Into<Option<&'static str>> {
- "A button allows users to take actions, and make choices, with a single tap."
- }
-
- fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
- vec![
- example_group_with_title(
- "Styles",
- vec![
- single_example("Default", Button::new("default", "Default")),
- single_example(
- "Filled",
- Button::new("filled", "Filled").style(ButtonStyle::Filled),
- ),
- single_example(
- "Subtle",
- Button::new("outline", "Subtle").style(ButtonStyle::Subtle),
- ),
- single_example(
- "Transparent",
- Button::new("transparent", "Transparent").style(ButtonStyle::Transparent),
- ),
- ],
- ),
- example_group_with_title(
- "Tinted",
- vec![
- single_example(
- "Accent",
- Button::new("tinted_accent", "Accent")
- .style(ButtonStyle::Tinted(TintColor::Accent)),
- ),
- single_example(
- "Error",
- Button::new("tinted_negative", "Error")
- .style(ButtonStyle::Tinted(TintColor::Error)),
- ),
- single_example(
- "Warning",
- Button::new("tinted_warning", "Warning")
- .style(ButtonStyle::Tinted(TintColor::Warning)),
- ),
- single_example(
- "Success",
- Button::new("tinted_positive", "Success")
- .style(ButtonStyle::Tinted(TintColor::Success)),
- ),
- ],
- ),
- example_group_with_title(
- "States",
- vec![
- single_example("Default", Button::new("default_state", "Default")),
- single_example(
- "Disabled",
- Button::new("disabled", "Disabled").disabled(true),
- ),
- single_example(
- "Selected",
- Button::new("selected", "Selected").toggle_state(true),
- ),
- ],
- ),
- example_group_with_title(
- "With Icons",
- vec![
- single_example(
- "Icon Start",
- Button::new("icon_start", "Icon Start")
- .icon(IconName::Check)
- .icon_position(IconPosition::Start),
- ),
- single_example(
- "Icon End",
- Button::new("icon_end", "Icon End")
- .icon(IconName::Check)
- .icon_position(IconPosition::End),
- ),
- single_example(
- "Icon Color",
- Button::new("icon_color", "Icon Color")
- .icon(IconName::Check)
- .icon_color(Color::Accent),
- ),
- 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),
- ),
- ],
- ),
- ]
+ fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
+ v_flex()
+ .gap_6()
+ .children(vec![
+ example_group_with_title(
+ "Styles",
+ vec![
+ single_example(
+ "Default",
+ Button::new("default", "Default").into_any_element(),
+ ),
+ single_example(
+ "Filled",
+ Button::new("filled", "Filled")
+ .style(ButtonStyle::Filled)
+ .into_any_element(),
+ ),
+ single_example(
+ "Subtle",
+ Button::new("outline", "Subtle")
+ .style(ButtonStyle::Subtle)
+ .into_any_element(),
+ ),
+ single_example(
+ "Transparent",
+ Button::new("transparent", "Transparent")
+ .style(ButtonStyle::Transparent)
+ .into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "Tinted",
+ vec![
+ single_example(
+ "Accent",
+ Button::new("tinted_accent", "Accent")
+ .style(ButtonStyle::Tinted(TintColor::Accent))
+ .into_any_element(),
+ ),
+ single_example(
+ "Error",
+ Button::new("tinted_negative", "Error")
+ .style(ButtonStyle::Tinted(TintColor::Error))
+ .into_any_element(),
+ ),
+ single_example(
+ "Warning",
+ Button::new("tinted_warning", "Warning")
+ .style(ButtonStyle::Tinted(TintColor::Warning))
+ .into_any_element(),
+ ),
+ single_example(
+ "Success",
+ Button::new("tinted_positive", "Success")
+ .style(ButtonStyle::Tinted(TintColor::Success))
+ .into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "States",
+ vec![
+ single_example(
+ "Default",
+ Button::new("default_state", "Default").into_any_element(),
+ ),
+ single_example(
+ "Disabled",
+ Button::new("disabled", "Disabled")
+ .disabled(true)
+ .into_any_element(),
+ ),
+ single_example(
+ "Selected",
+ Button::new("selected", "Selected")
+ .toggle_state(true)
+ .into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "With Icons",
+ vec![
+ single_example(
+ "Icon Start",
+ Button::new("icon_start", "Icon Start")
+ .icon(IconName::Check)
+ .icon_position(IconPosition::Start)
+ .into_any_element(),
+ ),
+ 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 component::{example_group, single_example, ComponentPreview};
use gpui::{AnyElement, IntoElement, ParentElement, StyleRefinement, Styled};
use smallvec::SmallVec;
@@ -22,7 +23,8 @@ pub fn h_group() -> ContentGroup {
}
/// A flexible container component that can hold other elements.
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
+#[component(scope = "layout")]
pub struct ContentGroup {
base: Div,
border: bool,
@@ -87,16 +89,8 @@ impl RenderOnce for ContentGroup {
}
impl ComponentPreview for ContentGroup {
- fn description() -> impl Into<Option<&'static str>> {
- "A flexible container component that can hold other elements. It can be customized with or without a border and background fill."
- }
-
- fn example_label_side() -> ExampleLabelSide {
- ExampleLabelSide::Bottom
- }
-
- fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
- vec![example_group(vec![
+ fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
+ example_group(vec![
single_example(
"Default",
ContentGroup::new()
@@ -104,7 +98,8 @@ impl ComponentPreview for ContentGroup {
.items_center()
.justify_center()
.h_48()
- .child(Label::new("Default ContentBox")),
+ .child(Label::new("Default ContentBox"))
+ .into_any_element(),
)
.grow(),
single_example(
@@ -115,7 +110,8 @@ impl ComponentPreview for ContentGroup {
.justify_center()
.h_48()
.borderless()
- .child(Label::new("Borderless ContentBox")),
+ .child(Label::new("Borderless ContentBox"))
+ .into_any_element(),
)
.grow(),
single_example(
@@ -126,10 +122,11 @@ impl ComponentPreview for ContentGroup {
.justify_center()
.h_48()
.unfilled()
- .child(Label::new("Unfilled ContentBox")),
+ .child(Label::new("Unfilled ContentBox"))
+ .into_any_element(),
)
.grow(),
])
- .grow()]
+ .into_any_element()
}
}
@@ -1,4 +1,4 @@
-use crate::{prelude::*, Avatar};
+use crate::prelude::*;
use gpui::{AnyElement, StyleRefinement};
use smallvec::SmallVec;
@@ -60,60 +60,60 @@ impl RenderOnce for Facepile {
}
}
-impl ComponentPreview for Facepile {
- fn description() -> impl Into<Option<&'static str>> {
- "A facepile is a collection of faces stacked horizontallyβ\
- always with the leftmost face on top and descending in z-index.\
- \n\nFacepiles are used to display a group of people or things,\
- such as a list of participants in a collaboration session."
- }
- fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
- let few_faces: [&'static str; 3] = [
- "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/482957?s=60&v=4",
- ];
+// impl ComponentPreview for Facepile {
+// fn description() -> impl Into<Option<&'static str>> {
+// "A facepile is a collection of faces stacked horizontallyβ\
+// always with the leftmost face on top and descending in z-index.\
+// \n\nFacepiles are used to display a group of people or things,\
+// such as a list of participants in a collaboration session."
+// }
+// fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
+// let few_faces: [&'static str; 3] = [
+// "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/482957?s=60&v=4",
+// ];
- let many_faces: [&'static str; 6] = [
- "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/1789?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/1714999?s=60&v=4",
- ];
+// let many_faces: [&'static str; 6] = [
+// "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/1789?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/1714999?s=60&v=4",
+// ];
- vec![example_group_with_title(
- "Examples",
- vec![
- single_example(
- "Few Faces",
- Facepile::new(
- few_faces
- .iter()
- .map(|&url| Avatar::new(url).into_any_element())
- .collect(),
- ),
- ),
- single_example(
- "Many Faces",
- Facepile::new(
- many_faces
- .iter()
- .map(|&url| Avatar::new(url).into_any_element())
- .collect(),
- ),
- ),
- single_example(
- "Custom Size",
- Facepile::new(
- few_faces
- .iter()
- .map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
- .collect(),
- ),
- ),
- ],
- )]
- }
-}
+// vec![example_group_with_title(
+// "Examples",
+// vec![
+// single_example(
+// "Few Faces",
+// Facepile::new(
+// few_faces
+// .iter()
+// .map(|&url| Avatar::new(url).into_any_element())
+// .collect(),
+// ),
+// ),
+// single_example(
+// "Many Faces",
+// Facepile::new(
+// many_faces
+// .iter()
+// .map(|&url| Avatar::new(url).into_any_element())
+// .collect(),
+// ),
+// ),
+// single_example(
+// "Custom Size",
+// Facepile::new(
+// few_faces
+// .iter()
+// .map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
+// .collect(),
+// ),
+// ),
+// ],
+// )]
+// }
+// }
@@ -7,17 +7,13 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
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::*;
use serde::{Deserialize, Serialize};
use strum::{EnumIter, EnumString, IntoStaticStr};
use ui_macros::DerivePathStr;
-use crate::{
- prelude::*,
- traits::component_preview::{ComponentExample, ComponentPreview},
- Indicator,
-};
+use crate::{prelude::*, Indicator};
#[derive(IntoElement)]
pub enum AnyIcon {
@@ -364,7 +360,7 @@ impl IconSource {
}
}
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
pub struct Icon {
source: IconSource,
color: Color,
@@ -494,24 +490,41 @@ impl RenderOnce for IconWithIndicator {
}
impl ComponentPreview for Icon {
- fn examples(_window: &mut Window, _cx: &mut App) -> Vec<ComponentExampleGroup<Icon>> {
- let arrow_icons = vec![
- IconName::ArrowDown,
- IconName::ArrowLeft,
- IconName::ArrowRight,
- IconName::ArrowUp,
- IconName::ArrowCircle,
- ];
-
- vec![example_group_with_title(
- "Arrow Icons",
- arrow_icons
- .into_iter()
- .map(|icon| {
- let name = format!("{:?}", icon).to_string();
- ComponentExample::new(name, Icon::new(icon))
- })
- .collect(),
- )]
+ fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
+ v_flex()
+ .gap_6()
+ .children(vec![
+ example_group_with_title(
+ "Sizes",
+ vec![
+ single_example("Default", Icon::new(IconName::Star).into_any_element()),
+ single_example(
+ "Small",
+ Icon::new(IconName::Star)
+ .size(IconSize::Small)
+ .into_any_element(),
+ ),
+ single_example(
+ "Large",
+ Icon::new(IconName::Star)
+ .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::{
- prelude::*, traits::component_preview::ComponentPreview, IconDecoration, IconDecorationKind,
-};
+use crate::{prelude::*, IconDecoration, IconDecorationKind};
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
pub struct DecoratedIcon {
icon: Icon,
decoration: Option<IconDecoration>,
@@ -27,12 +25,7 @@ impl RenderOnce for DecoratedIcon {
}
impl ComponentPreview for DecoratedIcon {
- fn examples(_: &mut Window, cx: &mut App) -> Vec<ComponentExampleGroup<Self>> {
- 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);
-
+ fn preview(_window: &mut Window, cx: &App) -> AnyElement {
let decoration_x = IconDecoration::new(
IconDecorationKind::X,
cx.theme().colors().surface_background,
@@ -66,22 +59,32 @@ impl ComponentPreview for DecoratedIcon {
y: px(-2.),
});
- let examples = vec![
- single_example("no_decoration", DecoratedIcon::new(icon_1, None)),
- single_example(
- "with_decoration",
- DecoratedIcon::new(icon_2, Some(decoration_x)),
- ),
- single_example(
- "with_decoration",
- DecoratedIcon::new(icon_3, Some(decoration_triangle)),
- ),
- single_example(
- "with_decoration",
- DecoratedIcon::new(icon_4, Some(decoration_dot)),
- ),
- ];
-
- vec![example_group(examples)]
+ v_flex()
+ .gap_6()
+ .children(vec![example_group_with_title(
+ "Decorations",
+ vec![
+ single_example(
+ "No Decoration",
+ DecoratedIcon::new(Icon::new(IconName::FileDoc), None).into_any_element(),
+ ),
+ single_example(
+ "X Decoration",
+ DecoratedIcon::new(Icon::new(IconName::FileDoc), Some(decoration_x))
+ .into_any_element(),
+ ),
+ single_example(
+ "Triangle Decoration",
+ 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 strum::{EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
+use strum::{EnumIter, EnumString, IntoStaticStr};
use ui_macros::DerivePathStr;
-use crate::{prelude::*, traits::component_preview::ComponentPreview};
+use crate::prelude::*;
const ICON_DECORATION_SIZE: Pixels = px(11.);
@@ -149,21 +149,3 @@ impl RenderOnce for IconDecoration {
.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::{ElevationIndex, KeyBinding};
-use gpui::{point, App, BoxShadow, IntoElement, Window};
+use gpui::{point, AnyElement, App, BoxShadow, IntoElement, Window};
use smallvec::smallvec;
/// Represents a hint for a keybinding, optionally with a prefix and suffix.
@@ -17,7 +17,7 @@ use smallvec::smallvec;
/// .prefix("Save:")
/// .size(Pixels::from(14.0));
/// ```
-#[derive(Debug, IntoElement, Clone)]
+#[derive(Debug, IntoElement, IntoComponent)]
pub struct KeybindingHint {
prefix: Option<SharedString>,
suffix: Option<SharedString>,
@@ -206,102 +206,99 @@ impl RenderOnce for KeybindingHint {
}
impl ComponentPreview for KeybindingHint {
- fn description() -> impl Into<Option<&'static str>> {
- "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));
-
+ fn preview(window: &mut Window, _cx: &App) -> AnyElement {
let enter_fallback = gpui::KeyBinding::new("enter", menu::Confirm, None);
let enter = KeyBinding::for_action(&menu::Confirm, window)
.unwrap_or(KeyBinding::new(enter_fallback));
- let escape_fallback = gpui::KeyBinding::new("escape", menu::Cancel, None);
- let escape = KeyBinding::for_action(&menu::Cancel, window)
- .unwrap_or(KeyBinding::new(escape_fallback));
-
- vec![
- example_group_with_title(
- "Basic",
- vec![
- single_example(
- "With Prefix",
- KeybindingHint::with_prefix("Go to Start:", home.clone()),
- ),
- single_example(
- "With Suffix",
- KeybindingHint::with_suffix(end.clone(), "Go to End"),
- ),
- single_example(
- "With Prefix and Suffix",
- KeybindingHint::new(enter.clone())
- .prefix("Confirm:")
- .suffix("Execute selected action"),
- ),
- ],
- ),
- example_group_with_title(
- "Sizes",
- vec![
- single_example(
- "Small",
- KeybindingHint::new(home.clone())
- .size(Pixels::from(12.0))
- .prefix("Small:"),
- ),
- single_example(
- "Medium",
- KeybindingHint::new(end.clone())
- .size(Pixels::from(16.0))
- .suffix("Medium"),
- ),
- single_example(
- "Large",
- KeybindingHint::new(enter.clone())
- .size(Pixels::from(20.0))
- .prefix("Large:")
- .suffix("Size"),
- ),
- ],
- ),
- example_group_with_title(
- "Elevations",
- vec![
- single_example(
- "Surface",
- KeybindingHint::new(home.clone())
- .elevation(ElevationIndex::Surface)
- .prefix("Surface:"),
- ),
- single_example(
- "Elevated Surface",
- KeybindingHint::new(end.clone())
- .elevation(ElevationIndex::ElevatedSurface)
- .suffix("Elevated"),
- ),
- single_example(
- "Editor Surface",
- KeybindingHint::new(enter.clone())
- .elevation(ElevationIndex::EditorSurface)
- .prefix("Editor:")
- .suffix("Surface"),
- ),
- single_example(
- "Modal Surface",
- KeybindingHint::new(escape.clone())
- .elevation(ElevationIndex::ModalSurface)
- .prefix("Modal:")
- .suffix("Escape"),
- ),
- ],
- ),
- ]
+ v_flex()
+ .gap_6()
+ .children(vec![
+ example_group_with_title(
+ "Basic",
+ vec![
+ single_example(
+ "With Prefix",
+ KeybindingHint::with_prefix("Go to Start:", enter.clone())
+ .into_any_element(),
+ ),
+ single_example(
+ "With Suffix",
+ KeybindingHint::with_suffix(enter.clone(), "Go to End")
+ .into_any_element(),
+ ),
+ single_example(
+ "With Prefix and Suffix",
+ KeybindingHint::new(enter.clone())
+ .prefix("Confirm:")
+ .suffix("Execute selected action")
+ .into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "Sizes",
+ vec![
+ single_example(
+ "Small",
+ KeybindingHint::new(enter.clone())
+ .size(Pixels::from(12.0))
+ .prefix("Small:")
+ .into_any_element(),
+ ),
+ single_example(
+ "Medium",
+ KeybindingHint::new(enter.clone())
+ .size(Pixels::from(16.0))
+ .suffix("Medium")
+ .into_any_element(),
+ ),
+ single_example(
+ "Large",
+ KeybindingHint::new(enter.clone())
+ .size(Pixels::from(20.0))
+ .prefix("Large:")
+ .suffix("Size")
+ .into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "Elevations",
+ vec![
+ single_example(
+ "Surface",
+ KeybindingHint::new(enter.clone())
+ .elevation(ElevationIndex::Surface)
+ .prefix("Surface:")
+ .into_any_element(),
+ ),
+ single_example(
+ "Elevated Surface",
+ KeybindingHint::new(enter.clone())
+ .elevation(ElevationIndex::ElevatedSurface)
+ .suffix("Elevated")
+ .into_any_element(),
+ ),
+ single_example(
+ "Editor Surface",
+ KeybindingHint::new(enter.clone())
+ .elevation(ElevationIndex::EditorSurface)
+ .prefix("Editor:")
+ .suffix("Surface")
+ .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)]
-use gpui::{App, StyleRefinement, Window};
+use gpui::{AnyElement, App, StyleRefinement, Window};
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);
/// ```
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
pub struct Label {
base: LabelLike,
label: SharedString,
@@ -184,3 +184,53 @@ impl RenderOnce for 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::*;
-/// A [`Checkbox`] that has a [`Label`].
-///
-/// [`Checkbox`]: crate::components::Checkbox
#[derive(IntoElement)]
pub struct RadioWithLabel {
id: ElementId,
@@ -27,7 +27,7 @@ pub enum TabCloseSide {
End,
}
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
pub struct Tab {
div: Stateful<Div>,
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};
/// A table component
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
pub struct Table {
column_headers: Vec<SharedString>,
rows: Vec<Vec<TableCell>>,
@@ -152,88 +152,110 @@ where
}
impl ComponentPreview for Table {
- fn description() -> impl Into<Option<&'static str>> {
- "Used for showing tabular data. Tables may show both text and elements in their cells."
- }
-
- fn example_label_side() -> ExampleLabelSide {
- ExampleLabelSide::Top
- }
-
- fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
- vec![
- example_group(vec![
- single_example(
- "Simple Table",
- Table::new(vec!["Name", "Age", "City"])
- .width(px(400.))
- .row(vec!["Alice", "28", "New York"])
- .row(vec!["Bob", "32", "San Francisco"])
- .row(vec!["Charlie", "25", "London"]),
+ fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
+ v_flex()
+ .gap_6()
+ .children(vec![
+ example_group_with_title(
+ "Basic Tables",
+ vec![
+ single_example(
+ "Simple Table",
+ Table::new(vec!["Name", "Age", "City"])
+ .width(px(400.))
+ .row(vec!["Alice", "28", "New York"])
+ .row(vec!["Bob", "32", "San Francisco"])
+ .row(vec!["Charlie", "25", "London"])
+ .into_any_element(),
+ ),
+ single_example(
+ "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(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "Styled Tables",
+ vec![
+ single_example(
+ "Default",
+ Table::new(vec!["Product", "Price", "Stock"])
+ .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(),
+ ),
+ ],
),
- single_example(
- "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"]),
+ example_group_with_title(
+ "Mixed Content Table",
+ vec![single_example(
+ "Table with Elements",
+ Table::new(vec!["Status", "Name", "Priority", "Deadline", "Action"])
+ .width(px(840.))
+ .row(vec![
+ element_cell(
+ Indicator::dot().color(Color::Success).into_any_element(),
+ ),
+ string_cell("Project A"),
+ string_cell("High"),
+ string_cell("2023-12-31"),
+ element_cell(
+ Button::new("view_a", "View")
+ .style(ButtonStyle::Filled)
+ .full_width()
+ .into_any_element(),
+ ),
+ ])
+ .row(vec![
+ element_cell(
+ Indicator::dot().color(Color::Warning).into_any_element(),
+ ),
+ string_cell("Project B"),
+ string_cell("Medium"),
+ string_cell("2024-03-15"),
+ element_cell(
+ Button::new("view_b", "View")
+ .style(ButtonStyle::Filled)
+ .full_width()
+ .into_any_element(),
+ ),
+ ])
+ .row(vec![
+ element_cell(
+ Indicator::dot().color(Color::Error).into_any_element(),
+ ),
+ string_cell("Project C"),
+ string_cell("Low"),
+ string_cell("2024-06-30"),
+ element_cell(
+ Button::new("view_c", "View")
+ .style(ButtonStyle::Filled)
+ .full_width()
+ .into_any_element(),
+ ),
+ ])
+ .into_any_element(),
+ )],
),
- ]),
- example_group(vec![single_example(
- "Striped Table",
- Table::new(vec!["Product", "Price", "Stock"])
- .width(px(600.))
- .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"]),
- )]),
- example_group_with_title(
- "Mixed Content Table",
- vec![single_example(
- "Table with Elements",
- Table::new(vec!["Status", "Name", "Priority", "Deadline", "Action"])
- .width(px(840.))
- .row(vec![
- element_cell(Indicator::dot().color(Color::Success).into_any_element()),
- string_cell("Project A"),
- string_cell("High"),
- string_cell("2023-12-31"),
- element_cell(
- Button::new("view_a", "View")
- .style(ButtonStyle::Filled)
- .full_width()
- .into_any_element(),
- ),
- ])
- .row(vec![
- element_cell(Indicator::dot().color(Color::Warning).into_any_element()),
- string_cell("Project B"),
- string_cell("Medium"),
- string_cell("2024-03-15"),
- element_cell(
- Button::new("view_b", "View")
- .style(ButtonStyle::Filled)
- .full_width()
- .into_any_element(),
- ),
- ])
- .row(vec![
- element_cell(Indicator::dot().color(Color::Error).into_any_element()),
- string_cell("Project C"),
- string_cell("Low"),
- string_cell("2024-06-30"),
- element_cell(
- Button::new("view_c", "View")
- .style(ButtonStyle::Filled)
- .full_width()
- .into_any_element(),
- ),
- ]),
- )],
- ),
- ]
+ ])
+ .into_any_element()
}
}
@@ -1,5 +1,6 @@
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;
@@ -38,7 +39,8 @@ pub enum ToggleStyle {
/// Checkboxes are used for multiple choices, not for mutually exclusive choices.
/// Each checkbox works independently from other checkboxes in the list,
/// therefore checking an additional box does not affect any other selections.
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
+#[component(scope = "input")]
pub struct Checkbox {
id: ElementId,
toggle_state: ToggleState,
@@ -237,7 +239,8 @@ impl RenderOnce for Checkbox {
}
/// A [`Checkbox`] that has a [`Label`].
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
+#[component(scope = "input")]
pub struct CheckboxWithLabel {
id: ElementId,
label: Label,
@@ -314,7 +317,8 @@ impl RenderOnce for CheckboxWithLabel {
/// # Switch
///
/// Switches are used to represent opposite states, such as enabled or disabled.
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
+#[component(scope = "input")]
pub struct Switch {
id: ElementId,
toggle_state: ToggleState,
@@ -446,285 +450,190 @@ impl RenderOnce for Switch {
}
impl ComponentPreview for Checkbox {
- fn description() -> impl Into<Option<&'static str>> {
- "A checkbox lets people choose between a pair of opposing states, like enabled and disabled, using a different appearance to indicate each state."
+ fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
+ v_flex()
+ .gap_6()
+ .children(vec![
+ example_group_with_title(
+ "States",
+ vec![
+ single_example(
+ "Unselected",
+ Checkbox::new("checkbox_unselected", ToggleState::Unselected)
+ .into_any_element(),
+ ),
+ single_example(
+ "Indeterminate",
+ Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate)
+ .into_any_element(),
+ ),
+ single_example(
+ "Selected",
+ Checkbox::new("checkbox_selected", ToggleState::Selected)
+ .into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "Styles",
+ vec![
+ single_example(
+ "Default",
+ Checkbox::new("checkbox_default", ToggleState::Selected)
+ .into_any_element(),
+ ),
+ single_example(
+ "Filled",
+ Checkbox::new("checkbox_filled", ToggleState::Selected)
+ .fill()
+ .into_any_element(),
+ ),
+ single_example(
+ "ElevationBased",
+ Checkbox::new("checkbox_elevation", ToggleState::Selected)
+ .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface))
+ .into_any_element(),
+ ),
+ single_example(
+ "Custom Color",
+ Checkbox::new("checkbox_custom", ToggleState::Selected)
+ .style(ToggleStyle::Custom(hsla(142.0 / 360., 0.68, 0.45, 0.7)))
+ .into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "Disabled",
+ vec![
+ single_example(
+ "Unselected",
+ Checkbox::new("checkbox_disabled_unselected", ToggleState::Unselected)
+ .disabled(true)
+ .into_any_element(),
+ ),
+ single_example(
+ "Selected",
+ Checkbox::new("checkbox_disabled_selected", ToggleState::Selected)
+ .disabled(true)
+ .into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "With Label",
+ vec![single_example(
+ "Default",
+ Checkbox::new("checkbox_with_label", ToggleState::Selected)
+ .label("Always save on quit")
+ .into_any_element(),
+ )],
+ ),
+ ])
+ .into_any_element()
}
+}
- fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
- vec![
- example_group_with_title(
- "Default",
- vec![
- single_example(
- "Unselected",
- Checkbox::new("checkbox_unselected", ToggleState::Unselected),
- ),
- single_example(
- "Indeterminate",
- Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate),
- ),
- single_example(
- "Selected",
- Checkbox::new("checkbox_selected", ToggleState::Selected),
- ),
- ],
- ),
- example_group_with_title(
- "Default (Filled)",
- vec![
- single_example(
- "Unselected",
- Checkbox::new("checkbox_unselected", ToggleState::Unselected).fill(),
- ),
- single_example(
- "Indeterminate",
- Checkbox::new("checkbox_indeterminate", ToggleState::Indeterminate).fill(),
- ),
- single_example(
- "Selected",
- Checkbox::new("checkbox_selected", ToggleState::Selected).fill(),
- ),
- ],
- ),
- example_group_with_title(
- "ElevationBased",
- vec![
- single_example(
- "Unselected",
- Checkbox::new("checkbox_unfilled_unselected", ToggleState::Unselected)
- .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
- ),
- single_example(
- "Indeterminate",
- Checkbox::new(
- "checkbox_unfilled_indeterminate",
- ToggleState::Indeterminate,
- )
- .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
- ),
- single_example(
- "Selected",
- Checkbox::new("checkbox_unfilled_selected", ToggleState::Selected)
- .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
- ),
- ],
- ),
- example_group_with_title(
- "ElevationBased (Filled)",
- vec![
- single_example(
- "Unselected",
- Checkbox::new("checkbox_filled_unselected", ToggleState::Unselected)
- .fill()
- .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
- ),
- single_example(
- "Indeterminate",
- Checkbox::new("checkbox_filled_indeterminate", ToggleState::Indeterminate)
- .fill()
- .style(ToggleStyle::ElevationBased(ElevationIndex::EditorSurface)),
- ),
- single_example(
- "Selected",
- 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)",
+impl ComponentPreview for Switch {
+ fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
+ v_flex()
+ .gap_6()
+ .children(vec![
+ example_group_with_title(
+ "States",
+ vec![
+ single_example(
+ "Off",
+ Switch::new("switch_off", ToggleState::Unselected)
+ .on_click(|_, _, _cx| {})
+ .into_any_element(),
+ ),
+ single_example(
+ "On",
+ Switch::new("switch_on", ToggleState::Selected)
+ .on_click(|_, _, _cx| {})
+ .into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "Disabled",
+ vec![
+ single_example(
+ "Off",
+ Switch::new("switch_disabled_off", ToggleState::Unselected)
+ .disabled(true)
+ .into_any_element(),
+ ),
+ single_example(
+ "On",
+ Switch::new("switch_disabled_on", ToggleState::Selected)
+ .disabled(true)
+ .into_any_element(),
+ ),
+ ],
+ ),
+ example_group_with_title(
+ "With Label",
+ vec![
+ single_example(
+ "Label",
+ 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 {
+ fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
+ v_flex()
+ .gap_6()
+ .children(vec![example_group_with_title(
+ "States",
vec![
single_example(
"Unselected",
- Checkbox::new(
- "checkbox_disabled_filled_unselected",
+ CheckboxWithLabel::new(
+ "checkbox_with_label_unselected",
+ Label::new("Always save on quit"),
ToggleState::Unselected,
+ |_, _, _| {},
)
- .fill()
- .disabled(true),
+ .into_any_element(),
),
single_example(
"Indeterminate",
- Checkbox::new(
- "checkbox_disabled_filled_indeterminate",
+ CheckboxWithLabel::new(
+ "checkbox_with_label_indeterminate",
+ Label::new("Always save on quit"),
ToggleState::Indeterminate,
+ |_, _, _| {},
)
- .fill()
- .disabled(true),
+ .into_any_element(),
),
single_example(
"Selected",
- Checkbox::new("checkbox_disabled_filled_selected", ToggleState::Selected)
- .fill()
- .disabled(true),
- ),
- ],
- ),
- ]
- }
-}
-
-impl ComponentPreview for Switch {
- fn description() -> impl Into<Option<&'static str>> {
- "A switch toggles between two mutually exclusive states, typically used for enabling or disabling a setting."
- }
-
- fn examples(_window: &mut Window, _cx: &mut App) -> Vec<ComponentExampleGroup<Self>> {
- vec![
- example_group_with_title(
- "Default",
- vec![
- single_example(
- "Off",
- Switch::new("switch_off", ToggleState::Unselected).on_click(|_, _, _cx| {}),
- ),
- single_example(
- "On",
- Switch::new("switch_on", ToggleState::Selected).on_click(|_, _, _cx| {}),
- ),
- ],
- ),
- example_group_with_title(
- "Disabled",
- vec![
- single_example(
- "Off",
- Switch::new("switch_disabled_off", ToggleState::Unselected).disabled(true),
- ),
- single_example(
- "On",
- Switch::new("switch_disabled_on", ToggleState::Selected).disabled(true),
- ),
- ],
- ),
- example_group_with_title(
- "Label Permutations",
- vec![
- single_example(
- "Label",
- Switch::new("switch_with_label", ToggleState::Selected)
- .label("Always save on quit"),
- ),
- single_example(
- "Keybinding",
- Switch::new("switch_with_label", ToggleState::Selected)
- .key_binding(theme_preview_keybinding("cmd-shift-e")),
+ CheckboxWithLabel::new(
+ "checkbox_with_label_selected",
+ Label::new("Always save on quit"),
+ ToggleState::Selected,
+ |_, _, _| {},
+ )
+ .into_any_element(),
),
],
- ),
- ]
- }
-}
-
-impl ComponentPreview for CheckboxWithLabel {
- fn description() -> impl Into<Option<&'static str>> {
- "A checkbox with an associated label, allowing users to select an option while providing a descriptive text."
- }
-
- fn examples(_window: &mut Window, _: &mut App) -> Vec<ComponentExampleGroup<Self>> {
- vec![example_group(vec![
- single_example(
- "Unselected",
- CheckboxWithLabel::new(
- "checkbox_with_label_unselected",
- Label::new("Always save on quit"),
- ToggleState::Unselected,
- |_, _, _| {},
- ),
- ),
- single_example(
- "Indeterminate",
- CheckboxWithLabel::new(
- "checkbox_with_label_indeterminate",
- Label::new("Always save on quit"),
- ToggleState::Indeterminate,
- |_, _, _| {},
- ),
- ),
- single_example(
- "Selected",
- CheckboxWithLabel::new(
- "checkbox_with_label_selected",
- Label::new("Always save on quit"),
- ToggleState::Selected,
- |_, _, _| {},
- ),
- ),
- ])]
+ )])
+ .into_any_element()
}
}
@@ -1,12 +1,13 @@
#![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 theme::ThemeSettings;
use crate::prelude::*;
use crate::{h_flex, v_flex, Color, KeyBinding, Label, LabelSize, StyledExt};
+#[derive(IntoComponent)]
pub struct Tooltip {
title: 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,
};
+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::traits::clickable::*;
-pub use crate::traits::component_preview::*;
pub use crate::traits::disableable::*;
pub use crate::traits::fixed::*;
pub use crate::traits::styled_ext::*;
@@ -1,5 +1,7 @@
+use crate::prelude::*;
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 theme::{ActiveTheme, ThemeSettings};
@@ -188,7 +190,7 @@ impl HeadlineSize {
/// A headline element, used to emphasize some text and
/// create a visual hierarchy.
-#[derive(IntoElement)]
+#[derive(IntoElement, IntoComponent)]
pub struct Headline {
size: HeadlineSize,
text: SharedString,
@@ -230,3 +232,44 @@ impl Headline {
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 component_preview;
pub mod disableable;
pub mod fixed;
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
[dependencies]
+convert_case.workspace = true
+linkme.workspace = true
proc-macro2.workspace = true
quote.workspace = true
syn.workspace = true
-convert_case.workspace = true
@@ -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 dynamic_spacing;
@@ -58,3 +59,27 @@ pub fn path_str(_args: TokenStream, input: TokenStream) -> TokenStream {
pub fn derive_dynamic_spacing(input: TokenStream) -> TokenStream {
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
clock.workspace = true
collections.workspace = true
+component.workspace = true
db.workspace = true
derive_more.workspace = true
fs.workspace = true
@@ -27,7 +27,6 @@ pub fn init(cx: &mut App) {
enum ThemePreviewPage {
Overview,
Typography,
- Components,
}
impl ThemePreviewPage {
@@ -35,7 +34,6 @@ impl ThemePreviewPage {
match self {
Self::Overview => "Overview",
Self::Typography => "Typography",
- Self::Components => "Components",
}
}
}
@@ -64,9 +62,6 @@ impl ThemePreview {
ThemePreviewPage::Typography => {
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 {
h_flex()
.id("theme-preview-nav")
@@ -148,6 +148,7 @@ actions!(
Open,
OpenFiles,
OpenInTerminal,
+ OpenComponentPreview,
ReloadActiveItem,
SaveAs,
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) {
init_settings(cx);
+ component::init();
theme_preview::init(cx);
cx.on_action(Workspace::close_global);
@@ -39,6 +39,7 @@ collab_ui.workspace = true
collections.workspace = true
command_palette.workspace = true
command_palette_hooks.workspace = true
+component_preview.workspace = true
copilot.workspace = true
db.workspace = true
diagnostics.workspace = true
@@ -54,8 +55,8 @@ file_icons.workspace = true
fs.workspace = true
futures.workspace = true
git.workspace = true
-git_ui.workspace = true
git_hosting_providers.workspace = true
+git_ui.workspace = true
go_to_line.workspace = true
gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] }
gpui_tokio.workspace = true
@@ -490,6 +490,7 @@ fn main() {
project_panel::init(Assets, cx);
git_ui::git_panel::init(cx);
outline_panel::init(Assets, cx);
+ component_preview::init(cx);
tasks_ui::init(cx);
snippets_ui::init(cx);
channel::init(&app_state.client.clone(), app_state.user_store.clone(), cx);