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
|
@ -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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
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(
|
||||
"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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
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(),
|
||||
),
|
||||
]),
|
||||
)],
|
||||
),
|
||||
]
|
||||
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(),
|
||||
)],
|
||||
),
|
||||
])
|
||||
.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 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)",
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
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")),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
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 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,
|
||||
|_, _, _| {},
|
||||
),
|
||||
),
|
||||
])]
|
||||
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_6()
|
||||
.children(vec![example_group_with_title(
|
||||
"States",
|
||||
vec![
|
||||
single_example(
|
||||
"Unselected",
|
||||
CheckboxWithLabel::new(
|
||||
"checkbox_with_label_unselected",
|
||||
Label::new("Always save on quit"),
|
||||
ToggleState::Unselected,
|
||||
|_, _, _| {},
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Indeterminate",
|
||||
CheckboxWithLabel::new(
|
||||
"checkbox_with_label_indeterminate",
|
||||
Label::new("Always save on quit"),
|
||||
ToggleState::Indeterminate,
|
||||
|_, _, _| {},
|
||||
)
|
||||
.into_any_element(),
|
||||
),
|
||||
single_example(
|
||||
"Selected",
|
||||
CheckboxWithLabel::new(
|
||||
"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)]
|
||||
|
||||
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))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue