Compare commits
13 Commits
github-tok
...
add-layout
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6240740605 | ||
|
|
2306d9fd91 | ||
|
|
e7cf1305b8 | ||
|
|
f699083b5b | ||
|
|
956eb5b3b0 | ||
|
|
af237f81a3 | ||
|
|
d6b1949143 | ||
|
|
e5c738daaa | ||
|
|
eba035eb5b | ||
|
|
1c65253b0f | ||
|
|
5d4bc1e492 | ||
|
|
dc84dbb6e2 | ||
|
|
903343e608 |
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -13262,9 +13262,12 @@ name = "ui"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"collections",
|
||||
"gpui",
|
||||
"itertools 0.13.0",
|
||||
"linkme",
|
||||
"menu",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"settings",
|
||||
"smallvec",
|
||||
|
||||
@@ -371,6 +371,7 @@ itertools = "0.13.0"
|
||||
jsonwebtoken = "9.3"
|
||||
libc = "0.2"
|
||||
linkify = "0.10.0"
|
||||
linkme = "0.3"
|
||||
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
|
||||
markup5ever_rcdom = "0.3.0"
|
||||
nanoid = "0.4"
|
||||
|
||||
@@ -80,7 +80,7 @@ gpui_macros.workspace = true
|
||||
http_client = { optional = true, workspace = true }
|
||||
image = "0.25.1"
|
||||
itertools.workspace = true
|
||||
linkme = "0.3"
|
||||
linkme.workspace = true
|
||||
log.workspace = true
|
||||
num_cpus = "1.13"
|
||||
parking = "2.0.0"
|
||||
|
||||
@@ -14,9 +14,12 @@ path = "src/ui.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono.workspace = true
|
||||
collections.workspace = true
|
||||
gpui.workspace = true
|
||||
itertools = { workspace = true, optional = true }
|
||||
linkme.workspace = true
|
||||
menu.workspace = true
|
||||
once_cell.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
|
||||
57
crates/ui/src/component_registry.rs
Normal file
57
crates/ui/src/component_registry.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use collections::HashMap;
|
||||
use gpui::{AnyElement, WindowContext};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub type ComponentPreviewFn = fn(&WindowContext) -> AnyElement;
|
||||
|
||||
static COMPONENTS: Lazy<Mutex<HashMap<&'static str, Vec<(&'static str, ComponentPreviewFn)>>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::default()));
|
||||
|
||||
pub fn register_component(scope: &'static str, name: &'static str, preview: ComponentPreviewFn) {
|
||||
let mut components = COMPONENTS.lock().unwrap();
|
||||
components
|
||||
.entry(scope)
|
||||
.or_insert_with(Vec::new)
|
||||
.push((name, preview));
|
||||
}
|
||||
|
||||
/// Initializes all components that have been registered
|
||||
/// in the UI component registry.
|
||||
pub fn init_component_registry() {
|
||||
for register in __COMPONENT_REGISTRATIONS {
|
||||
register();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a map of all registered components and their previews.
|
||||
pub fn get_all_component_previews() -> HashMap<&'static str, Vec<(&'static str, ComponentPreviewFn)>>
|
||||
{
|
||||
COMPONENTS.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[linkme::distributed_slice]
|
||||
pub static __COMPONENT_REGISTRATIONS: [fn()];
|
||||
|
||||
/// Defines components that should be registered in the component registry.
|
||||
///
|
||||
/// This allows components to be previewed, and eventually tracked for documentation
|
||||
/// purposes and to help the systems team to understand component usage across the codebase.
|
||||
#[macro_export]
|
||||
macro_rules! register_components {
|
||||
($scope:ident, [ $($component:ty),+ $(,)? ]) => {
|
||||
const _: () = {
|
||||
#[linkme::distributed_slice($crate::component_registry::__COMPONENT_REGISTRATIONS)]
|
||||
fn register() {
|
||||
$(
|
||||
$crate::component_registry::register_component(
|
||||
stringify!($scope),
|
||||
stringify!($component),
|
||||
|cx: &$crate::WindowContext| <$component>::render_component_previews(cx),
|
||||
);
|
||||
)+
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod avatar;
|
||||
mod button;
|
||||
mod checkbox;
|
||||
mod content_box;
|
||||
mod context_menu;
|
||||
mod disclosure;
|
||||
mod divider;
|
||||
@@ -36,6 +37,7 @@ mod stories;
|
||||
pub use avatar::*;
|
||||
pub use button::*;
|
||||
pub use checkbox::*;
|
||||
pub use content_box::*;
|
||||
pub use context_menu::*;
|
||||
pub use disclosure::*;
|
||||
pub use divider::*;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::internal::prelude::*;
|
||||
|
||||
use gpui::{AnyView, DefiniteLength};
|
||||
|
||||
use crate::{prelude::*, ElevationIndex, IconPosition, KeyBinding, Spacing, TintColor};
|
||||
use crate::{
|
||||
ButtonCommon, ButtonLike, ButtonSize, ButtonStyle, IconName, IconSize, Label, LineHeightStyle,
|
||||
};
|
||||
use crate::{ElevationIndex, IconPosition, KeyBinding, Spacing, TintColor};
|
||||
|
||||
use super::button_icon::ButtonIcon;
|
||||
|
||||
register_components!(button, [Button]);
|
||||
|
||||
/// An element that creates a button with a label and an optional icon.
|
||||
///
|
||||
/// Common buttons:
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
#![allow(missing_docs)]
|
||||
use super::button_like::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle};
|
||||
use crate::internal::prelude::*;
|
||||
use crate::{ElevationIndex, SelectableButton, Tooltip};
|
||||
use crate::{IconName, IconSize};
|
||||
use gpui::{AnyView, DefiniteLength};
|
||||
|
||||
use super::button_like::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle};
|
||||
use crate::{prelude::*, ElevationIndex, SelectableButton};
|
||||
use crate::{IconName, IconSize};
|
||||
|
||||
use super::button_icon::ButtonIcon;
|
||||
|
||||
register_components!(button, [IconButton]);
|
||||
|
||||
/// The shape of an [`IconButton`].
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub enum IconButtonShape {
|
||||
@@ -165,3 +167,76 @@ impl RenderOnce for IconButton {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for IconButton {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"An IconButton is a button that displays only an icon. It's used for actions that can be represented by a single icon."
|
||||
}
|
||||
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group_with_title(
|
||||
"Basic",
|
||||
vec![
|
||||
single_example("Default", IconButton::new("default", IconName::Check)),
|
||||
single_example(
|
||||
"Selected",
|
||||
IconButton::new("selected", IconName::Check).selected(true),
|
||||
),
|
||||
single_example(
|
||||
"Disabled",
|
||||
IconButton::new("disabled", IconName::Check).disabled(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Shapes",
|
||||
vec![
|
||||
single_example(
|
||||
"Square",
|
||||
IconButton::new("square", IconName::Check).shape(IconButtonShape::Square),
|
||||
),
|
||||
single_example(
|
||||
"Wide",
|
||||
IconButton::new("wide", IconName::Check).shape(IconButtonShape::Wide),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Sizes",
|
||||
vec![
|
||||
single_example(
|
||||
"XSmall",
|
||||
IconButton::new("xsmall", IconName::Check).icon_size(IconSize::XSmall),
|
||||
),
|
||||
single_example(
|
||||
"Small",
|
||||
IconButton::new("small", IconName::Check).icon_size(IconSize::Small),
|
||||
),
|
||||
single_example(
|
||||
"Medium",
|
||||
IconButton::new("medium", IconName::Check).icon_size(IconSize::Medium),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Icon Color",
|
||||
vec![
|
||||
single_example("Default", IconButton::new("default_color", IconName::Check)),
|
||||
single_example(
|
||||
"Custom",
|
||||
IconButton::new("custom_color", IconName::Check).icon_color(Color::Success),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"With Tooltip",
|
||||
vec![single_example(
|
||||
"Tooltip",
|
||||
IconButton::new("tooltip", IconName::Check)
|
||||
.tooltip(|cx| Tooltip::text("This is a tooltip", cx)),
|
||||
)],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use crate::internal::prelude::*;
|
||||
use crate::{Color, Icon, IconName, Selection};
|
||||
use gpui::{div, prelude::*, ElementId, IntoElement, Styled, WindowContext};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{Color, Icon, IconName, Selection};
|
||||
register_components!(checkbox, [Checkbox, CheckboxWithLabel]);
|
||||
|
||||
/// # Checkbox
|
||||
///
|
||||
|
||||
118
crates/ui/src/components/content_box.rs
Normal file
118
crates/ui/src/components/content_box.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use crate::internal::prelude::*;
|
||||
use gpui::{AnyElement, IntoElement, ParentElement, StyleRefinement, Styled};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
register_components!(layout, [ContentBox]);
|
||||
|
||||
/// A flexible container component that can hold other elements.
|
||||
#[derive(IntoElement)]
|
||||
pub struct ContentBox {
|
||||
base: Div,
|
||||
border: bool,
|
||||
fill: bool,
|
||||
children: SmallVec<[AnyElement; 2]>,
|
||||
}
|
||||
|
||||
impl ContentBox {
|
||||
/// Creates a new [ContentBox].
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
base: div(),
|
||||
border: true,
|
||||
fill: true,
|
||||
children: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes the border from the [ContentBox].
|
||||
pub fn borderless(mut self) -> Self {
|
||||
self.border = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes the background fill from the [ContentBox].
|
||||
pub fn unfilled(mut self) -> Self {
|
||||
self.fill = false;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ParentElement for ContentBox {
|
||||
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||
self.children.extend(elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl Styled for ContentBox {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
self.base.style()
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for ContentBox {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
// TODO:
|
||||
// Baked in padding will make scrollable views inside of content boxes awkward.
|
||||
//
|
||||
// Do we make the padding optional, or do we push to use a different component?
|
||||
|
||||
self.base
|
||||
.when(self.fill, |this| {
|
||||
this.bg(cx.theme().colors().text.opacity(0.05))
|
||||
})
|
||||
.when(self.border, |this| {
|
||||
this.border_1().border_color(cx.theme().colors().border)
|
||||
})
|
||||
.rounded_md()
|
||||
.p_2()
|
||||
.children(self.children)
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for ContentBox {
|
||||
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() -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![example_group(vec![
|
||||
single_example(
|
||||
"Default",
|
||||
ContentBox::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.child(Label::new("Default ContentBox")),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Without Border",
|
||||
ContentBox::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.borderless()
|
||||
.child(Label::new("Borderless ContentBox")),
|
||||
)
|
||||
.grow(),
|
||||
single_example(
|
||||
"Without Fill",
|
||||
ContentBox::new()
|
||||
.flex_1()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.h_48()
|
||||
.unfilled()
|
||||
.child(Label::new("Unfilled ContentBox")),
|
||||
)
|
||||
.grow(),
|
||||
])
|
||||
.grow()]
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::internal::prelude::*;
|
||||
use gpui::{ClickEvent, CursorStyle};
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{ClickEvent, CursorStyle};
|
||||
use crate::{Color, IconButton, IconButtonShape, IconName, IconSize};
|
||||
|
||||
use crate::{prelude::*, Color, IconButton, IconButtonShape, IconName, IconSize};
|
||||
register_components!(disclosure, [Disclosure]);
|
||||
|
||||
// TODO: This should be DisclosureControl, not Disclosure
|
||||
#[derive(IntoElement)]
|
||||
pub struct Disclosure {
|
||||
id: ElementId,
|
||||
@@ -71,3 +74,20 @@ impl RenderOnce for Disclosure {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for Disclosure {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"A Disclosure component is used to show or hide content. It's typically used in expandable/collapsible sections or tree-like structures."
|
||||
}
|
||||
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![example_group(vec![
|
||||
single_example("Closed", Disclosure::new("closed", false)),
|
||||
single_example("Open", Disclosure::new("open", true)),
|
||||
single_example(
|
||||
"Open (Selected)",
|
||||
Disclosure::new("open", true).selected(true),
|
||||
),
|
||||
])]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use crate::{prelude::*, Avatar};
|
||||
use crate::internal::prelude::*;
|
||||
use crate::Avatar;
|
||||
use gpui::{AnyElement, StyleRefinement};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
register_components!(facepile, [Facepile]);
|
||||
|
||||
/// A facepile is a collection of faces stacked horizontally–
|
||||
/// always with the leftmost face on top and descending in z-index
|
||||
///
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
#![allow(missing_docs)]
|
||||
use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
|
||||
use crate::internal::prelude::*;
|
||||
use crate::Indicator;
|
||||
|
||||
use gpui::{svg, AnimationElement, AnyElement, Hsla, IntoElement, Rems, Transformation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumIter, EnumString, IntoStaticStr};
|
||||
use strum::{EnumIter, EnumString, IntoEnumIterator, IntoStaticStr};
|
||||
use ui_macros::DerivePathStr;
|
||||
|
||||
use crate::{
|
||||
prelude::*,
|
||||
traits::component_preview::{ComponentExample, ComponentPreview},
|
||||
Indicator,
|
||||
};
|
||||
register_components!(icon, [Icon, DecoratedIcon, IconWithIndicator]);
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub enum AnyIcon {
|
||||
@@ -501,24 +500,147 @@ impl RenderOnce for IconWithIndicator {
|
||||
}
|
||||
|
||||
impl ComponentPreview for Icon {
|
||||
fn examples() -> Vec<ComponentExampleGroup<Icon>> {
|
||||
let arrow_icons = vec![
|
||||
IconName::ArrowDown,
|
||||
IconName::ArrowLeft,
|
||||
IconName::ArrowRight,
|
||||
IconName::ArrowUp,
|
||||
IconName::ArrowCircle,
|
||||
];
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"Icons are visual symbols used to represent ideas, objects, or actions. They communicate messages at a glance, enhance aesthetic appeal, and are used in buttons, labels, and more."
|
||||
}
|
||||
|
||||
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 custom_example(cx: &WindowContext) -> impl Into<Option<AnyElement>> {
|
||||
let all_icons = IconName::iter().collect::<Vec<_>>();
|
||||
let chunk_size = 12;
|
||||
|
||||
Some(
|
||||
v_flex()
|
||||
.gap_4()
|
||||
.children(all_icons.chunks(chunk_size).map(|chunk| {
|
||||
h_flex().gap_4().children(chunk.iter().map(|&icon| {
|
||||
div()
|
||||
.flex()
|
||||
.flex_1()
|
||||
.justify_center()
|
||||
.items_center()
|
||||
.size_8()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.child(Icon::new(icon))
|
||||
}))
|
||||
}))
|
||||
.into_any_element(),
|
||||
)
|
||||
}
|
||||
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group_with_title(
|
||||
"Sizes",
|
||||
vec![
|
||||
single_example("XSmall", Icon::new(IconName::Check).size(IconSize::XSmall)),
|
||||
single_example("Small", Icon::new(IconName::Check).size(IconSize::Small)),
|
||||
single_example("Medium", Icon::new(IconName::Check).size(IconSize::Medium)),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Colors",
|
||||
vec![
|
||||
single_example("Default", Icon::new(IconName::Check)),
|
||||
single_example("Accent", Icon::new(IconName::Check).color(Color::Accent)),
|
||||
single_example("Error", Icon::new(IconName::Check).color(Color::Error)),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for DecoratedIcon {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"DecoratedIcon adds visual enhancements to an icon, such as a strikethrough or an indicator dot."
|
||||
}
|
||||
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group_with_title(
|
||||
"Decorations",
|
||||
vec![
|
||||
single_example(
|
||||
"Strikethrough",
|
||||
DecoratedIcon::new(
|
||||
Icon::new(IconName::Bell),
|
||||
IconDecoration::Strikethrough,
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"IndicatorDot",
|
||||
DecoratedIcon::new(Icon::new(IconName::Bell), IconDecoration::IndicatorDot),
|
||||
),
|
||||
single_example(
|
||||
"X",
|
||||
DecoratedIcon::new(Icon::new(IconName::Bell), IconDecoration::X),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Colors",
|
||||
vec![
|
||||
single_example(
|
||||
"Default",
|
||||
DecoratedIcon::new(Icon::new(IconName::Bell), IconDecoration::IndicatorDot),
|
||||
),
|
||||
single_example(
|
||||
"Custom Color",
|
||||
DecoratedIcon::new(Icon::new(IconName::Bell), IconDecoration::IndicatorDot)
|
||||
.decoration_color(Color::Accent),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentPreview for IconWithIndicator {
|
||||
fn description() -> impl Into<Option<&'static str>> {
|
||||
"IconWithIndicator combines an icon with an indicator, useful for showing status or notifications."
|
||||
}
|
||||
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>> {
|
||||
vec![
|
||||
example_group_with_title(
|
||||
"Indicator Types",
|
||||
vec![
|
||||
single_example(
|
||||
"Dot",
|
||||
IconWithIndicator::new(Icon::new(IconName::Bell), Some(Indicator::dot())),
|
||||
),
|
||||
single_example(
|
||||
"Bar",
|
||||
IconWithIndicator::new(Icon::new(IconName::Bell), Some(Indicator::bar())),
|
||||
),
|
||||
],
|
||||
),
|
||||
example_group_with_title(
|
||||
"Indicator Colors",
|
||||
vec![
|
||||
single_example(
|
||||
"Info",
|
||||
IconWithIndicator::new(
|
||||
Icon::new(IconName::Bell),
|
||||
Some(Indicator::dot().color(Color::Info)),
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"Warning",
|
||||
IconWithIndicator::new(
|
||||
Icon::new(IconName::Bell),
|
||||
Some(Indicator::dot().color(Color::Warning)),
|
||||
),
|
||||
),
|
||||
single_example(
|
||||
"Error",
|
||||
IconWithIndicator::new(
|
||||
Icon::new(IconName::Bell),
|
||||
Some(Indicator::dot().color(Color::Error)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::{prelude::*, AnyIcon};
|
||||
use crate::internal::prelude::*;
|
||||
use crate::AnyIcon;
|
||||
|
||||
register_components!(indicator, [Indicator]);
|
||||
|
||||
#[derive(Default)]
|
||||
enum IndicatorKind {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::{prelude::*, Indicator};
|
||||
use crate::internal::prelude::*;
|
||||
|
||||
use crate::Indicator;
|
||||
use gpui::{div, AnyElement, FontWeight, IntoElement, Length};
|
||||
|
||||
/// A table component
|
||||
|
||||
@@ -9,7 +9,6 @@ pub use gpui::{
|
||||
|
||||
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::selectable::*;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#![allow(missing_docs)]
|
||||
use crate::prelude::*;
|
||||
use gpui::{AnyElement, SharedString};
|
||||
|
||||
/// Which side of the preview to show labels on
|
||||
#[allow(unused)]
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ExampleLabelSide {
|
||||
/// Left side
|
||||
@@ -32,6 +32,10 @@ pub trait ComponentPreview: IntoElement {
|
||||
|
||||
fn examples() -> Vec<ComponentExampleGroup<Self>>;
|
||||
|
||||
fn custom_example(_cx: &WindowContext) -> impl Into<Option<AnyElement>> {
|
||||
None::<AnyElement>
|
||||
}
|
||||
|
||||
fn component_previews() -> Vec<AnyElement> {
|
||||
Self::examples()
|
||||
.into_iter()
|
||||
@@ -47,7 +51,8 @@ pub trait ComponentPreview: IntoElement {
|
||||
let description = Self::description().into();
|
||||
|
||||
v_flex()
|
||||
.gap_3()
|
||||
.w_full()
|
||||
.gap_6()
|
||||
.p_4()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
@@ -73,18 +78,23 @@ pub trait ComponentPreview: IntoElement {
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when_some(Self::custom_example(cx).into(), |this, custom_example| {
|
||||
this.child(custom_example)
|
||||
})
|
||||
.children(Self::component_previews())
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_example_group(group: ComponentExampleGroup<Self>) -> AnyElement {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.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(),
|
||||
@@ -103,6 +113,7 @@ pub trait ComponentPreview: IntoElement {
|
||||
};
|
||||
|
||||
base.gap_1()
|
||||
.when(example.grow, |this| this.flex_1())
|
||||
.child(example.element)
|
||||
.child(
|
||||
Label::new(example.variant_name)
|
||||
@@ -117,6 +128,7 @@ pub trait ComponentPreview: IntoElement {
|
||||
pub struct ComponentExample<T> {
|
||||
variant_name: SharedString,
|
||||
element: T,
|
||||
grow: bool,
|
||||
}
|
||||
|
||||
impl<T> ComponentExample<T> {
|
||||
@@ -125,14 +137,22 @@ impl<T> ComponentExample<T> {
|
||||
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> {
|
||||
@@ -141,15 +161,24 @@ impl<T> ComponentExampleGroup<T> {
|
||||
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
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
//! - [`ui_input`] - the single line input component
|
||||
//!
|
||||
|
||||
mod component_registry;
|
||||
mod components;
|
||||
pub mod prelude;
|
||||
mod styles;
|
||||
@@ -17,6 +18,17 @@ mod tests;
|
||||
mod traits;
|
||||
pub mod utils;
|
||||
|
||||
pub use component_registry::{get_all_component_previews, init_component_registry};
|
||||
pub use components::*;
|
||||
pub use prelude::*;
|
||||
pub use styles::*;
|
||||
|
||||
pub(crate) mod internal {
|
||||
/// A crate-internal extension of the prelude, used to expose the crate-specific
|
||||
/// needs like the component registry or component-preview types
|
||||
pub mod prelude {
|
||||
pub use crate::prelude::*;
|
||||
pub use crate::register_components;
|
||||
pub use crate::traits::component_preview::*;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,22 +502,24 @@ impl ThemePreview {
|
||||
}
|
||||
|
||||
fn render_components_page(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
let layer = ElevationIndex::Surface;
|
||||
let all_previews = ui::get_all_component_previews();
|
||||
|
||||
v_flex()
|
||||
.id("theme-preview-components")
|
||||
.overflow_scroll()
|
||||
.size_full()
|
||||
.gap_2()
|
||||
.child(Checkbox::render_component_previews(cx))
|
||||
.child(CheckboxWithLabel::render_component_previews(cx))
|
||||
.child(Facepile::render_component_previews(cx))
|
||||
.child(Button::render_component_previews(cx))
|
||||
.child(Indicator::render_component_previews(cx))
|
||||
.child(Icon::render_component_previews(cx))
|
||||
.child(Table::render_component_previews(cx))
|
||||
.child(self.render_avatars(cx))
|
||||
.child(self.render_buttons(layer, cx))
|
||||
.gap_4()
|
||||
.children(all_previews.iter().map(|(scope, components)| {
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(Headline::new(*scope).size(HeadlineSize::Small))
|
||||
.children(components.iter().map(|(name, preview_fn)| {
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(Label::new(*name).size(LabelSize::Small).color(Color::Muted))
|
||||
.child(preview_fn(cx))
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
fn render_page_nav(&self, cx: &ViewContext<Self>) -> impl IntoElement {
|
||||
|
||||
@@ -92,6 +92,8 @@ pub fn init(cx: &mut AppContext) {
|
||||
if ReleaseChannel::global(cx) == ReleaseChannel::Dev {
|
||||
cx.on_action(test_panic);
|
||||
}
|
||||
|
||||
ui::init_component_registry();
|
||||
}
|
||||
|
||||
pub fn build_window_options(display_uuid: Option<Uuid>, cx: &mut AppContext) -> WindowOptions {
|
||||
|
||||
Reference in New Issue
Block a user