ui: More component previews, UI component cleanup (#25302)

- Don't require ui component docs (this isn't really working)
- Add more component previews
- Update component preview style & navigation

Release Notes:

- N/A
This commit is contained in:
Nate Butler 2025-02-21 09:20:53 -05:00 committed by GitHub
parent c9235ff916
commit 5397ca23a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 813 additions and 856 deletions

View file

@ -2458,8 +2458,8 @@ impl CollabPanel {
Avatar::new(contact.user.avatar_uri.clone())
.indicator::<AvatarAvailabilityIndicator>(if online {
Some(AvatarAvailabilityIndicator::new(match busy {
true => ui::Availability::Busy,
false => ui::Availability::Free,
true => ui::CollaboratorAvailability::Busy,
false => ui::CollaboratorAvailability::Free,
}))
} else {
None

View file

@ -173,9 +173,9 @@ pub enum ExampleLabelSide {
Left,
/// Right side
Right,
#[default]
/// Top side
Top,
#[default]
/// Bottom side
Bottom,
}
@ -200,10 +200,10 @@ impl RenderOnce for ComponentExample {
ExampleLabelSide::Top => base.flex_col_reverse(),
};
base.gap_1()
base.gap_2()
.p_2()
.text_sm()
.text_color(cx.theme().colors().text)
.text_size(px(10.))
.text_color(cx.theme().colors().text_muted)
.when(self.grow, |this| this.flex_1())
.child(self.element)
.child(self.variant_name)
@ -245,12 +245,13 @@ impl RenderOnce for ComponentExampleGroup {
.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().pb_5().child(
this.gap_4().child(
div()
.flex()
.items_center()
.gap_3()
.child(div().h_px().w_4().bg(cx.theme().colors().border_variant))
.pb_1()
.child(div().h_px().w_4().bg(cx.theme().colors().border))
.child(
div()
.flex_none()
@ -271,7 +272,7 @@ impl RenderOnce for ComponentExampleGroup {
.flex()
.items_start()
.w_full()
.gap_8()
.gap_6()
.children(self.examples)
.into_any_element(),
)

View file

@ -3,8 +3,9 @@
//! A view for exploring Zed components.
use component::{components, ComponentMetadata};
use gpui::{prelude::*, App, EventEmitter, FocusHandle, Focusable, Window};
use ui::prelude::*;
use gpui::{list, prelude::*, uniform_list, App, EventEmitter, FocusHandle, Focusable, Window};
use gpui::{ListState, ScrollHandle, UniformListScrollHandle};
use ui::{prelude::*, ListItem};
use workspace::{item::ItemEvent, Item, Workspace, WorkspaceId};
@ -12,7 +13,7 @@ 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);
let component_preview = cx.new(|cx| ComponentPreview::new(window, cx));
workspace.add_item_to_active_pane(
Box::new(component_preview),
None,
@ -28,124 +29,161 @@ pub fn init(cx: &mut App) {
struct ComponentPreview {
focus_handle: FocusHandle,
_view_scroll_handle: ScrollHandle,
nav_scroll_handle: UniformListScrollHandle,
components: Vec<ComponentMetadata>,
component_list: ListState,
selected_index: usize,
}
impl ComponentPreview {
pub fn new(cx: &mut Context<Self>) -> Self {
pub fn new(_window: &mut Window, cx: &mut Context<Self>) -> Self {
let components = components().all_sorted();
let initial_length = components.len();
let component_list = ListState::new(initial_length, gpui::ListAlignment::Top, px(500.0), {
let this = cx.entity().downgrade();
move |ix, window: &mut Window, cx: &mut App| {
this.update(cx, |this, cx| {
this.render_preview(ix, window, cx).into_any_element()
})
.unwrap()
}
});
Self {
focus_handle: cx.focus_handle(),
_view_scroll_handle: ScrollHandle::new(),
nav_scroll_handle: UniformListScrollHandle::new(),
components,
component_list,
selected_index: 0,
}
}
fn render_sidebar(&self, _window: &Window, _cx: &Context<Self>) -> impl IntoElement {
let components = components().all_sorted();
let sorted_components = components.clone();
fn scroll_to_preview(&mut self, ix: usize, cx: &mut Context<Self>) {
self.component_list.scroll_to_reveal_item(ix);
self.selected_index = ix;
cx.notify();
}
v_flex()
.max_w_48()
.gap_px()
.p_1()
.children(
sorted_components
.into_iter()
.map(|component| self.render_sidebar_entry(&component, _cx)),
)
.child(
Label::new("These will be clickable once the layout is moved to a gpui::List.")
.color(Color::Muted)
.size(LabelSize::XSmall)
.italic(),
)
fn get_component(&self, ix: usize) -> ComponentMetadata {
self.components[ix].clone()
}
fn render_sidebar_entry(
&self,
component: &ComponentMetadata,
_cx: &Context<Self>,
ix: usize,
selected: bool,
cx: &Context<Self>,
) -> impl IntoElement {
h_flex()
.w_40()
.px_1p5()
.py_0p5()
.text_sm()
.child(component.name().clone())
let component = self.get_component(ix);
ListItem::new(ix)
.child(Label::new(component.name().clone()).color(Color::Default))
.selectable(true)
.toggle_state(selected)
.inset(true)
.on_click(cx.listener(move |this, _, _, cx| {
this.scroll_to_preview(ix, cx);
}))
}
fn render_preview(
&self,
component: &ComponentMetadata,
ix: usize,
window: &mut Window,
cx: &Context<Self>,
) -> impl IntoElement {
let component = self.get_component(ix);
let name = component.name();
let scope = component.scope();
let description = component.description();
v_flex()
.border_b_1()
.border_color(cx.theme().colors().border)
.w_full()
.gap_3()
.py_6()
.py_2()
.child(
v_flex()
.gap_1()
.border_1()
.border_color(cx.theme().colors().border)
.rounded_md()
.w_full()
.gap_4()
.py_4()
.px_6()
.flex_none()
.child(
h_flex()
v_flex()
.gap_1()
.text_2xl()
.child(div().child(name))
.when_some(scope, |this, scope| {
this.child(div().opacity(0.5).child(format!("({})", scope)))
.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(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))
}),
)
.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_4()
.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 {
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))
.child(
uniform_list(
cx.entity().clone(),
"component-nav",
self.components.len(),
move |this, range, _window, cx| {
range
.map(|ix| this.render_sidebar_entry(ix, ix == this.selected_index, cx))
.collect()
},
)
.track_scroll(self.nav_scroll_handle.clone())
.pt_4()
.w(px(240.))
.h_full()
.flex_grow(),
)
.child(
v_flex()
.id("component-list")
.px_8()
.pt_4()
.size_full()
.child(
list(self.component_list.clone())
.flex_grow()
.with_sizing_behavior(gpui::ListSizingBehavior::Auto),
),
)
}
}
@ -175,13 +213,13 @@ impl Item for ComponentPreview {
fn clone_on_split(
&self,
_workspace_id: Option<WorkspaceId>,
_window: &mut Window,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<gpui::Entity<Self>>
where
Self: Sized,
{
Some(cx.new(Self::new))
Some(cx.new(|cx| Self::new(window, cx)))
}
fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {

View file

@ -14,8 +14,6 @@ use ui::prelude::*;
pub enum ComponentStory {
ApplicationMenu,
AutoHeightEditor,
Avatar,
Button,
CollabNotification,
ContextMenu,
Cursor,
@ -47,8 +45,6 @@ impl ComponentStory {
.new(|cx| title_bar::ApplicationMenuStory::new(window, cx))
.into(),
Self::AutoHeightEditor => AutoHeightEditorStory::new(window, cx).into(),
Self::Avatar => cx.new(|_| ui::AvatarStory).into(),
Self::Button => cx.new(|_| ui::ButtonStory).into(),
Self::CollabNotification => cx
.new(|_| collab_ui::notifications::CollabNotificationStory)
.into(),

View file

@ -1,49 +0,0 @@
# Building UI with GPUI
## Common patterns
### Method ordering
- id
- Flex properties
- Position properties
- Size properties
- Style properties
- Handlers
- State properties
### Using the Label Component to Create UI Text
The `Label` component helps in displaying text on user interfaces. It creates an interface where specific parameters such as label color, line height style, and strikethrough can be set.
Firstly, to create a `Label` instance, use the `Label::new()` function. This function takes a string that will be displayed as text in the interface.
```rust
Label::new("Hello, world!");
```
Now let's dive a bit deeper into how to customize `Label` instances:
- **Setting Color:** To set the color of the label using various predefined color options such as `Default`, `Muted`, `Created`, `Modified`, `Deleted`, etc, the `color()` function is called on the `Label` instance:
```rust
Label::new("Hello, world!").color(LabelColor::Default);
```
- **Setting Line Height Style:** To set the line height style, the `line_height_style()` function is utilized:
```rust
Label::new("Hello, world!").line_height_style(LineHeightStyle::TextLabel);
```
- **Adding a Strikethrough:** To add a strikethrough in a `Label`, the `set_strikethrough()` function is used:
```rust
Label::new("Hello, world!").set_strikethrough(true);
```
That's it! Now you can use the `Label` component to create and customize text on your application's interface.
## Building a new component
TODO

View file

@ -1,160 +0,0 @@
# Hello World
Let's work through the prototypical "Build a todo app" example to showcase how we might build a simple component from scratch.
## Setup
We'll create a headline, a list of todo items, and a form to add new items.
~~~rust
struct TodoList<V: 'static> {
headline: SharedString,
items: Vec<TodoItem>,
submit_form: ClickHandler<V>
}
struct TodoItem<V: 'static> {
text: SharedString,
completed: bool,
delete: ClickHandler<V>
}
impl<V: 'static> TodoList<V> {
pub fn new(
// Here we impl Into<SharedString>
headline: impl Into<SharedString>,
items: Vec<TodoItem>,
submit_form: ClickHandler<V>
) -> Self {
Self {
// and here we call .into() so we can simply pass a string
// when creating the headline. This pattern is used throughout
// outr components
headline: headline.into(),
items: Vec::new(),
submit_form,
}
}
}
~~~
All of this is relatively straightforward.
We use [gpui::SharedString] in components instead of [std::string::String]. This allows us to efficiently handle shared string data across multiple components and threads without the performance overhead of copying strings.
When we want to pass an action we pass a `ClickHandler`. Whenever we want to add an action, the struct it belongs to needs to be generic over the view type `V`.
~~~rust
use gpui::hsla
impl<V: 'static> TodoList<V> {
// ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
div().size_4().bg(hsla(50.0/360.0, 1.0, 0.5, 1.0))
}
}
~~~
Every component needs a render method, and it should return `impl Element<V>`. This basic component will render a 16x16px yellow square on the screen.
A couple of questions might come to mind:
**Why is `size_4()` 16px, not 4px?**
gpui's style system is based on conventions created by [Tailwind CSS](https://tailwindcss.com/). Here is an example of the list of sizes for `width`: [Width - TailwindCSS Docs](https://tailwindcss.com/docs/width).
I'll quote from the Tailwind [Core Concepts](https://tailwindcss.com/docs/utility-first) docs here:
> Now I know what youre thinking, “this is an atrocity, what a horrible mess!”
> and youre right, its kind of ugly. In fact its just about impossible to
> think this is a good idea the first time you see it —
> you have to actually try it.
As you start using the Tailwind-style conventions you will be surprised how quick it makes it to build out UIs.
**Why `50.0/360.0` in `hsla()`?**
gpui [gpui::Hsla] use `0.0-1.0` for all its values, but it is common for tools to use `0-360` for hue.
This may change in the future, but this is a little trick that let's you use familiar looking values.
## Building out the container
Let's grab our [theme::colors::ThemeColors] from the theme and start building out a basic container.
We can access the current theme's colors like this:
~~~rust
impl<V: 'static> TodoList<V> {
// ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let color = cx.theme().colors()
div().size_4().hsla(50.0/360.0, 1.0, 0.5, 1.0)
}
}
~~~
Now we have access to the complete set of colors defined in the theme.
~~~rust
use gpui::hsla
impl<V: 'static> TodoList<V> {
// ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let color = cx.theme().colors()
div().size_4().bg(color.surface)
}
}
~~~
Let's finish up some basic styles for the container then move on to adding the other elements.
~~~rust
use gpui::hsla
impl<V: 'static> TodoList<V> {
// ...
fn render(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
let color = cx.theme().colors()
div()
// Flex properties
.flex()
.flex_col() // Stack elements vertically
.gap_2() // Add 8px of space between elements
// Size properties
.w_96() // Set width to 384px
.p_4() // Add 16px of padding on all sides
// Color properties
.bg(color.surface) // Set background color
.text_color(color.text) // Set text color
// Border properties
.rounded_md() // Add 4px of border radius
.border_1() // Add a 1px border
.border_color(color.border)
.child(
"Hello, world!"
)
}
}
~~~
### Headline
TODO
### List of todo items
TODO
### Input
TODO
### End result
TODO

View file

@ -1,7 +1,302 @@
mod avatar;
mod avatar_audio_status_indicator;
mod avatar_availability_indicator;
use crate::prelude::*;
pub use avatar::*;
pub use avatar_audio_status_indicator::*;
pub use avatar_availability_indicator::*;
use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled};
/// An element that renders a user avatar with customizable appearance options.
///
/// # Examples
///
/// ```
/// use ui::{Avatar, AvatarShape};
///
/// Avatar::new("path/to/image.png")
/// .shape(AvatarShape::Circle)
/// .grayscale(true)
/// .border_color(gpui::red());
/// ```
#[derive(IntoElement, IntoComponent)]
pub struct Avatar {
image: Img,
size: Option<AbsoluteLength>,
border_color: Option<Hsla>,
indicator: Option<AnyElement>,
}
impl Avatar {
/// Creates a new avatar element with the specified image source.
pub fn new(src: impl Into<ImageSource>) -> Self {
Avatar {
image: img(src),
size: None,
border_color: None,
indicator: None,
}
}
/// Applies a grayscale filter to the avatar image.
///
/// # Examples
///
/// ```
/// use ui::{Avatar, AvatarShape};
///
/// let avatar = Avatar::new("path/to/image.png").grayscale(true);
/// ```
pub fn grayscale(mut self, grayscale: bool) -> Self {
self.image = self.image.grayscale(grayscale);
self
}
/// Sets the border color of the avatar.
///
/// This might be used to match the border to the background color of
/// the parent element to create the illusion of cropping another
/// shape underneath (for example in face piles.)
pub fn border_color(mut self, color: impl Into<Hsla>) -> Self {
self.border_color = Some(color.into());
self
}
/// Size overrides the avatar size. By default they are 1rem.
pub fn size<L: Into<AbsoluteLength>>(mut self, size: impl Into<Option<L>>) -> Self {
self.size = size.into().map(Into::into);
self
}
/// Sets the current indicator to be displayed on the avatar, if any.
pub fn indicator<E: IntoElement>(mut self, indicator: impl Into<Option<E>>) -> Self {
self.indicator = indicator.into().map(IntoElement::into_any_element);
self
}
}
impl RenderOnce for Avatar {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let border_width = if self.border_color.is_some() {
px(2.)
} else {
px(0.)
};
let image_size = self.size.unwrap_or_else(|| rems(1.).into());
let container_size = image_size.to_pixels(window.rem_size()) + border_width * 2.;
div()
.size(container_size)
.rounded_full()
.when_some(self.border_color, |this, color| {
this.border(border_width).border_color(color)
})
.child(
self.image
.size(image_size)
.rounded_full()
.bg(cx.theme().colors().ghost_element_background),
)
.children(self.indicator.map(|indicator| div().child(indicator)))
}
}
use gpui::AnyView;
/// The audio status of an player, for use in representing
/// their status visually on their avatar.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum AudioStatus {
/// The player's microphone is muted.
Muted,
/// The player's microphone is muted, and collaboration audio is disabled.
Deafened,
}
/// An indicator that shows the audio status of a player.
#[derive(IntoElement)]
pub struct AvatarAudioStatusIndicator {
audio_status: AudioStatus,
tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
}
impl AvatarAudioStatusIndicator {
/// Creates a new `AvatarAudioStatusIndicator`
pub fn new(audio_status: AudioStatus) -> Self {
Self {
audio_status,
tooltip: None,
}
}
/// Sets the tooltip for the indicator.
pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
self.tooltip = Some(Box::new(tooltip));
self
}
}
impl RenderOnce for AvatarAudioStatusIndicator {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let icon_size = IconSize::Indicator;
let width_in_px = icon_size.rems() * window.rem_size();
let padding_x = px(4.);
div()
.absolute()
.bottom(rems_from_px(-3.))
.right(rems_from_px(-6.))
.w(width_in_px + padding_x)
.h(icon_size.rems())
.child(
h_flex()
.id("muted-indicator")
.justify_center()
.px(padding_x)
.py(px(2.))
.bg(cx.theme().status().error_background)
.rounded_md()
.child(
Icon::new(match self.audio_status {
AudioStatus::Muted => IconName::MicMute,
AudioStatus::Deafened => IconName::AudioOff,
})
.size(icon_size)
.color(Color::Error),
)
.when_some(self.tooltip, |this, tooltip| {
this.tooltip(move |window, cx| tooltip(window, cx))
}),
)
}
}
/// Represents the availability status of a collaborator.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum CollaboratorAvailability {
Free,
Busy,
}
/// Represents the availability and presence status of a collaborator.
#[derive(IntoElement)]
pub struct AvatarAvailabilityIndicator {
availability: CollaboratorAvailability,
avatar_size: Option<Pixels>,
}
impl AvatarAvailabilityIndicator {
/// Creates a new indicator
pub fn new(availability: CollaboratorAvailability) -> Self {
Self {
availability,
avatar_size: None,
}
}
/// Sets the size of the [`Avatar`](crate::Avatar) this indicator appears on.
pub fn avatar_size(mut self, size: impl Into<Option<Pixels>>) -> Self {
self.avatar_size = size.into();
self
}
}
impl RenderOnce for AvatarAvailabilityIndicator {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let avatar_size = self.avatar_size.unwrap_or_else(|| window.rem_size());
// HACK: non-integer sizes result in oval indicators.
let indicator_size = (avatar_size * 0.4).round();
div()
.absolute()
.bottom_0()
.right_0()
.size(indicator_size)
.rounded(indicator_size)
.bg(match self.availability {
CollaboratorAvailability::Free => cx.theme().status().created,
CollaboratorAvailability::Busy => cx.theme().status().deleted,
})
}
}
// View this component preview using `workspace: open component-preview`
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(example_avatar).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(cx.theme().colors().border)
.into_any_element(),
),
],
),
example_group_with_title(
"Audio Status",
vec![
single_example(
"Muted",
Avatar::new(example_avatar)
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted))
.into_any_element(),
),
single_example(
"Deafened",
Avatar::new(example_avatar)
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened))
.into_any_element(),
),
],
),
example_group_with_title(
"Availability",
vec![
single_example(
"Free",
Avatar::new(example_avatar)
.indicator(AvatarAvailabilityIndicator::new(
CollaboratorAvailability::Free,
))
.into_any_element(),
),
single_example(
"Busy",
Avatar::new(example_avatar)
.indicator(AvatarAvailabilityIndicator::new(
CollaboratorAvailability::Busy,
))
.into_any_element(),
),
],
),
])
.into_any_element()
}
}

View file

@ -1,156 +0,0 @@
use crate::{prelude::*, Indicator};
use gpui::{img, AnyElement, Hsla, ImageSource, Img, IntoElement, Styled};
/// An element that renders a user avatar with customizable appearance options.
///
/// # Examples
///
/// ```
/// use ui::{Avatar, AvatarShape};
///
/// Avatar::new("path/to/image.png")
/// .shape(AvatarShape::Circle)
/// .grayscale(true)
/// .border_color(gpui::red());
/// ```
#[derive(IntoElement, IntoComponent)]
pub struct Avatar {
image: Img,
size: Option<AbsoluteLength>,
border_color: Option<Hsla>,
indicator: Option<AnyElement>,
}
impl Avatar {
/// Creates a new avatar element with the specified image source.
pub fn new(src: impl Into<ImageSource>) -> Self {
Avatar {
image: img(src),
size: None,
border_color: None,
indicator: None,
}
}
/// Applies a grayscale filter to the avatar image.
///
/// # Examples
///
/// ```
/// use ui::{Avatar, AvatarShape};
///
/// let avatar = Avatar::new("path/to/image.png").grayscale(true);
/// ```
pub fn grayscale(mut self, grayscale: bool) -> Self {
self.image = self.image.grayscale(grayscale);
self
}
/// Sets the border color of the avatar.
///
/// This might be used to match the border to the background color of
/// the parent element to create the illusion of cropping another
/// shape underneath (for example in face piles.)
pub fn border_color(mut self, color: impl Into<Hsla>) -> Self {
self.border_color = Some(color.into());
self
}
/// Size overrides the avatar size. By default they are 1rem.
pub fn size<L: Into<AbsoluteLength>>(mut self, size: impl Into<Option<L>>) -> Self {
self.size = size.into().map(Into::into);
self
}
/// Sets the current indicator to be displayed on the avatar, if any.
pub fn indicator<E: IntoElement>(mut self, indicator: impl Into<Option<E>>) -> Self {
self.indicator = indicator.into().map(IntoElement::into_any_element);
self
}
}
impl RenderOnce for Avatar {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let border_width = if self.border_color.is_some() {
px(2.)
} else {
px(0.)
};
let image_size = self.size.unwrap_or_else(|| rems(1.).into());
let container_size = image_size.to_pixels(window.rem_size()) + border_width * 2.;
div()
.size(container_size)
.rounded_full()
.when_some(self.border_color, |this, color| {
this.border(border_width).border_color(color)
})
.child(
self.image
.size(image_size)
.rounded_full()
.bg(cx.theme().colors().ghost_element_background),
)
.children(self.indicator.map(|indicator| div().child(indicator)))
}
}
// View this component preview using `workspace: open component-preview`
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()
}
}

View file

@ -1,72 +0,0 @@
use gpui::AnyView;
use crate::prelude::*;
/// The audio status of an player, for use in representing
/// their status visually on their avatar.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum AudioStatus {
/// The player's microphone is muted.
Muted,
/// The player's microphone is muted, and collaboration audio is disabled.
Deafened,
}
/// An indicator that shows the audio status of a player.
#[derive(IntoElement)]
pub struct AvatarAudioStatusIndicator {
audio_status: AudioStatus,
tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
}
impl AvatarAudioStatusIndicator {
/// Creates a new `AvatarAudioStatusIndicator`
pub fn new(audio_status: AudioStatus) -> Self {
Self {
audio_status,
tooltip: None,
}
}
/// Sets the tooltip for the indicator.
pub fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
self.tooltip = Some(Box::new(tooltip));
self
}
}
impl RenderOnce for AvatarAudioStatusIndicator {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let icon_size = IconSize::Indicator;
let width_in_px = icon_size.rems() * window.rem_size();
let padding_x = px(4.);
div()
.absolute()
.bottom(rems_from_px(-3.))
.right(rems_from_px(-6.))
.w(width_in_px + padding_x)
.h(icon_size.rems())
.child(
h_flex()
.id("muted-indicator")
.justify_center()
.px(padding_x)
.py(px(2.))
.bg(cx.theme().status().error_background)
.rounded_md()
.child(
Icon::new(match self.audio_status {
AudioStatus::Muted => IconName::MicMute,
AudioStatus::Deafened => IconName::AudioOff,
})
.size(icon_size)
.color(Color::Error),
)
.when_some(self.tooltip, |this, tooltip| {
this.tooltip(move |window, cx| tooltip(window, cx))
}),
)
}
}

View file

@ -1,49 +0,0 @@
#![allow(missing_docs)]
use crate::prelude::*;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum Availability {
Free,
Busy,
}
#[derive(IntoElement)]
pub struct AvatarAvailabilityIndicator {
availability: Availability,
avatar_size: Option<Pixels>,
}
impl AvatarAvailabilityIndicator {
pub fn new(availability: Availability) -> Self {
Self {
availability,
avatar_size: None,
}
}
/// Sets the size of the [`Avatar`](crate::Avatar) this indicator appears on.
pub fn avatar_size(mut self, size: impl Into<Option<Pixels>>) -> Self {
self.avatar_size = size.into();
self
}
}
impl RenderOnce for AvatarAvailabilityIndicator {
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
let avatar_size = self.avatar_size.unwrap_or_else(|| window.rem_size());
// HACK: non-integer sizes result in oval indicators.
let indicator_size = (avatar_size * 0.4).round();
div()
.absolute()
.bottom_0()
.right_0()
.size(indicator_size)
.rounded(indicator_size)
.bg(match self.availability {
Availability::Free => cx.theme().status().created,
Availability::Busy => cx.theme().status().deleted,
})
}
}

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use component::{example_group_with_title, single_example, ComponentPreview};
use gpui::{AnyElement, AnyView, DefiniteLength};
use ui_macros::IntoComponent;
@ -81,6 +80,7 @@ use super::button_icon::ButtonIcon;
/// ```
///
#[derive(IntoElement, IntoComponent)]
#[component(scope = "input")]
pub struct Button {
base: ButtonLike,
label: SharedString,
@ -463,7 +463,7 @@ impl ComponentPreview for Button {
.gap_6()
.children(vec![
example_group_with_title(
"Styles",
"Button Styles",
vec![
single_example(
"Default",
@ -481,6 +481,12 @@ impl ComponentPreview for Button {
.style(ButtonStyle::Subtle)
.into_any_element(),
),
single_example(
"Tinted",
Button::new("tinted_accent_style", "Accent")
.style(ButtonStyle::Tinted(TintColor::Accent))
.into_any_element(),
),
single_example(
"Transparent",
Button::new("transparent", "Transparent")
@ -490,7 +496,7 @@ impl ComponentPreview for Button {
],
),
example_group_with_title(
"Tinted",
"Tint Styles",
vec![
single_example(
"Accent",
@ -519,7 +525,7 @@ impl ComponentPreview for Button {
],
),
example_group_with_title(
"States",
"Special States",
vec![
single_example(
"Default",
@ -540,7 +546,7 @@ impl ComponentPreview for Button {
],
),
example_group_with_title(
"With Icons",
"Buttons with Icons",
vec![
single_example(
"Icon Start",
@ -563,16 +569,6 @@ impl ComponentPreview for Button {
.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(),
),
],
),
])

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use crate::{prelude::*, Icon, IconName, IconSize, IconWithIndicator, Indicator};
use gpui::Hsla;

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use gpui::{relative, CursorStyle, DefiniteLength, MouseButton};
use gpui::{transparent_black, AnyElement, AnyView, ClickEvent, Hsla, Rems};
use smallvec::SmallVec;

View file

@ -1,8 +1,7 @@
#![allow(missing_docs)]
use gpui::{AnyView, DefiniteLength, Hsla};
use super::button_like::{ButtonCommon, ButtonLike, ButtonSize, ButtonStyle};
use crate::{prelude::*, ElevationIndex, Indicator, SelectableButton};
use crate::{prelude::*, ElevationIndex, Indicator, SelectableButton, TintColor};
use crate::{IconName, IconSize};
use super::button_icon::ButtonIcon;
@ -14,7 +13,8 @@ pub enum IconButtonShape {
Wide,
}
#[derive(IntoElement)]
#[derive(IntoElement, IntoComponent)]
#[component(scope = "input")]
pub struct IconButton {
base: ButtonLike,
shape: IconButtonShape,
@ -200,3 +200,160 @@ impl RenderOnce for IconButton {
)
}
}
impl ComponentPreview for IconButton {
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
v_flex()
.gap_6()
.children(vec![
example_group_with_title(
"Icon Button Styles",
vec![
single_example(
"Default",
IconButton::new("default", IconName::Check)
.layer(ElevationIndex::Background)
.into_any_element(),
),
single_example(
"Filled",
IconButton::new("filled", IconName::Check)
.layer(ElevationIndex::Background)
.style(ButtonStyle::Filled)
.into_any_element(),
),
single_example(
"Subtle",
IconButton::new("subtle", IconName::Check)
.layer(ElevationIndex::Background)
.style(ButtonStyle::Subtle)
.into_any_element(),
),
single_example(
"Tinted",
IconButton::new("tinted", IconName::Check)
.layer(ElevationIndex::Background)
.style(ButtonStyle::Tinted(TintColor::Accent))
.into_any_element(),
),
single_example(
"Transparent",
IconButton::new("transparent", IconName::Check)
.layer(ElevationIndex::Background)
.style(ButtonStyle::Transparent)
.into_any_element(),
),
],
),
example_group_with_title(
"Icon Button Shapes",
vec![
single_example(
"Square",
IconButton::new("square", IconName::Check)
.shape(IconButtonShape::Square)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
single_example(
"Wide",
IconButton::new("wide", IconName::Check)
.shape(IconButtonShape::Wide)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
],
),
example_group_with_title(
"Icon Button Sizes",
vec![
single_example(
"Small",
IconButton::new("small", IconName::Check)
.icon_size(IconSize::XSmall)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
single_example(
"Small",
IconButton::new("small", IconName::Check)
.icon_size(IconSize::Small)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
single_example(
"Medium",
IconButton::new("medium", IconName::Check)
.icon_size(IconSize::Medium)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
single_example(
"XLarge",
IconButton::new("xlarge", IconName::Check)
.icon_size(IconSize::XLarge)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
],
),
example_group_with_title(
"Special States",
vec![
single_example(
"Disabled",
IconButton::new("disabled", IconName::Check)
.disabled(true)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
single_example(
"Selected",
IconButton::new("selected", IconName::Check)
.toggle_state(true)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
single_example(
"With Indicator",
IconButton::new("indicator", IconName::Check)
.indicator(Indicator::dot().color(Color::Success))
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
],
),
example_group_with_title(
"Custom Colors",
vec![
single_example(
"Custom Icon Color",
IconButton::new("custom_color", IconName::Check)
.icon_color(Color::Accent)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
single_example(
"With Alpha",
IconButton::new("alpha", IconName::Check)
.alpha(0.5)
.style(ButtonStyle::Filled)
.layer(ElevationIndex::Background)
.into_any_element(),
),
],
),
])
.into_any_element()
}
}

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use gpui::{AnyView, ClickEvent};
use crate::{prelude::*, ButtonLike, ButtonLikeRounding, ElevationIndex};
@ -16,7 +15,8 @@ pub enum ToggleButtonPosition {
Last,
}
#[derive(IntoElement)]
#[derive(IntoElement, IntoComponent)]
#[component(scope = "input")]
pub struct ToggleButton {
base: ButtonLike,
position_in_group: Option<ToggleButtonPosition>,
@ -142,3 +142,130 @@ impl RenderOnce for ToggleButton {
)
}
}
impl ComponentPreview for ToggleButton {
fn preview(_window: &mut Window, _cx: &App) -> AnyElement {
v_flex()
.gap_6()
.children(vec![
example_group_with_title(
"Button Styles",
vec![
single_example(
"Off",
ToggleButton::new("off", "Off")
.layer(ElevationIndex::Background)
.style(ButtonStyle::Filled)
.into_any_element(),
),
single_example(
"On",
ToggleButton::new("on", "On")
.layer(ElevationIndex::Background)
.toggle_state(true)
.style(ButtonStyle::Filled)
.into_any_element(),
),
single_example(
"Off Disabled",
ToggleButton::new("disabled_off", "Disabled Off")
.layer(ElevationIndex::Background)
.disabled(true)
.style(ButtonStyle::Filled)
.into_any_element(),
),
single_example(
"On Disabled",
ToggleButton::new("disabled_on", "Disabled On")
.layer(ElevationIndex::Background)
.disabled(true)
.toggle_state(true)
.style(ButtonStyle::Filled)
.into_any_element(),
),
],
),
example_group_with_title(
"Button Group",
vec![
single_example(
"Three Buttons",
h_flex()
.child(
ToggleButton::new("three_btn_first", "First")
.layer(ElevationIndex::Background)
.style(ButtonStyle::Filled)
.first()
.into_any_element(),
)
.child(
ToggleButton::new("three_btn_middle", "Middle")
.layer(ElevationIndex::Background)
.style(ButtonStyle::Filled)
.middle()
.toggle_state(true)
.into_any_element(),
)
.child(
ToggleButton::new("three_btn_last", "Last")
.layer(ElevationIndex::Background)
.style(ButtonStyle::Filled)
.last()
.into_any_element(),
)
.into_any_element(),
),
single_example(
"Two Buttons",
h_flex()
.child(
ToggleButton::new("two_btn_first", "First")
.layer(ElevationIndex::Background)
.style(ButtonStyle::Filled)
.first()
.into_any_element(),
)
.child(
ToggleButton::new("two_btn_last", "Last")
.layer(ElevationIndex::Background)
.style(ButtonStyle::Filled)
.last()
.into_any_element(),
)
.into_any_element(),
),
],
),
example_group_with_title(
"Alternate Sizes",
vec![
single_example(
"None",
ToggleButton::new("none", "None")
.layer(ElevationIndex::Background)
.style(ButtonStyle::Filled)
.size(ButtonSize::None)
.into_any_element(),
),
single_example(
"Compact",
ToggleButton::new("compact", "Compact")
.layer(ElevationIndex::Background)
.style(ButtonStyle::Filled)
.size(ButtonSize::Compact)
.into_any_element(),
),
single_example(
"Large",
ToggleButton::new("large", "Large")
.layer(ElevationIndex::Background)
.style(ButtonStyle::Filled)
.size(ButtonSize::Large)
.into_any_element(),
),
],
),
])
.into_any_element()
}
}

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use crate::{
h_flex, prelude::*, utils::WithRemSize, v_flex, Icon, IconName, IconSize, KeyBinding, Label,
List, ListItem, ListSeparator, ListSubHeader,

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use std::sync::Arc;
use gpui::{ClickEvent, CursorStyle};

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use gpui::{Hsla, IntoElement};
use crate::prelude::*;

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use gpui::{ClickEvent, Corner, CursorStyle, Entity, MouseButton};
use crate::{prelude::*, ContextMenu, PopoverMenu};

View file

@ -1,4 +1,4 @@
use crate::prelude::*;
use crate::{prelude::*, Avatar};
use gpui::{AnyElement, StyleRefinement};
use smallvec::SmallVec;
@ -7,7 +7,7 @@ use smallvec::SmallVec;
///
/// Facepiles are used to display a group of people or things,
/// such as a list of participants in a collaboration session.
#[derive(IntoElement)]
#[derive(IntoElement, IntoComponent)]
pub struct Facepile {
base: Div,
faces: SmallVec<[AnyElement; 2]>,
@ -60,60 +60,57 @@ 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 preview(_window: &mut Window, _cx: &App) -> AnyElement {
let 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(),
// ),
// ),
// ],
// )]
// }
// }
v_flex()
.gap_6()
.children(vec![
example_group_with_title(
"Facepile Examples",
vec![
single_example(
"Default",
Facepile::new(
faces
.iter()
.map(|&url| Avatar::new(url).into_any_element())
.collect(),
)
.into_any_element(),
),
single_example(
"Custom Size",
Facepile::new(
faces
.iter()
.map(|&url| Avatar::new(url).size(px(24.)).into_any_element())
.collect(),
)
.into_any_element(),
),
],
),
example_group_with_title(
"Special Cases",
vec![
single_example("Empty Facepile", Facepile::empty().into_any_element()),
single_example(
"Single Face",
Facepile::new(vec![Avatar::new(faces[0]).into_any_element()].into())
.into_any_element(),
),
],
),
])
.into_any_element()
}
}

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
mod decorated_icon;
mod icon_decoration;

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use gpui::{svg, App, IntoElement, Rems, RenderOnce, Size, Styled, Window};
use serde::{Deserialize, Serialize};
use strum::{EnumIter, EnumString, IntoStaticStr};

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use std::{cmp::Ordering, ops::Range, rc::Rc};
use gpui::{

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use crate::{prelude::*, AnyIcon};
#[derive(Default)]

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use crate::PlatformStyle;
use crate::{h_flex, prelude::*, Icon, IconName, IconSize};
use gpui::{

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use std::ops::Range;
use gpui::{FontWeight, HighlightStyle, StyledText};

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use gpui::AnyElement;
use smallvec::SmallVec;

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use std::sync::Arc;
use crate::{h_flex, prelude::*, Disclosure, Label};

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use std::sync::Arc;
use gpui::{px, AnyElement, AnyView, ClickEvent, MouseButton, MouseDownEvent, Pixels};

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use crate::prelude::*;
#[derive(IntoElement)]

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use crate::prelude::*;
use crate::{h_flex, Icon, IconName, IconSize, Label};

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use crate::{
h_flex, v_flex, Clickable, Color, DynamicSpacing, Headline, HeadlineSize, IconButton,
IconButtonShape, IconName, Label, LabelCommon, LabelSize,

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use gpui::ClickEvent;
use crate::{prelude::*, IconButtonShape};

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use crate::prelude::*;
use crate::v_flex;
use gpui::{

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use std::{cell::RefCell, rc::Rc};
use gpui::{

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use std::sync::Arc;
use crate::prelude::*;

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use std::{cell::RefCell, rc::Rc};
use gpui::{

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use std::{any::Any, cell::Cell, fmt::Debug, ops::Range, rc::Rc, sync::Arc};
use crate::{prelude::*, px, relative, IntoElement};

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use gpui::AnyElement;
use smallvec::SmallVec;

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use gpui::AnyElement;
use smallvec::SmallVec;

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use gpui::{div, Div};
use crate::StyledExt;

View file

@ -1,8 +1,3 @@
// We allow missing docs for stories as the docs will more or less be
// "This is the ___ story", which is not very useful.
#![allow(missing_docs)]
mod avatar;
mod button;
mod context_menu;
mod disclosure;
mod icon;
@ -15,8 +10,6 @@ mod tab;
mod tab_bar;
mod toggle_button;
pub use avatar::*;
pub use button::*;
pub use context_menu::*;
pub use disclosure::*;
pub use icon::*;

View file

@ -1,64 +0,0 @@
use gpui::Render;
use story::{Story, StoryItem, StorySection};
use crate::{prelude::*, AudioStatus, Availability, AvatarAvailabilityIndicator};
use crate::{Avatar, AvatarAudioStatusIndicator};
pub struct AvatarStory;
impl Render for AvatarStory {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
.child(Story::title_for::<Avatar>())
.child(
StorySection::new()
.child(StoryItem::new(
"Default",
Avatar::new("https://avatars.githubusercontent.com/u/1714999?v=4"),
))
.child(StoryItem::new(
"Default",
Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4"),
)),
)
.child(
StorySection::new()
.child(StoryItem::new(
"With free availability indicator",
Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4")
.indicator(AvatarAvailabilityIndicator::new(Availability::Free)),
))
.child(StoryItem::new(
"With busy availability indicator",
Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4")
.indicator(AvatarAvailabilityIndicator::new(Availability::Busy)),
)),
)
.child(
StorySection::new()
.child(StoryItem::new(
"With info border",
Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4")
.border_color(cx.theme().status().info_border),
))
.child(StoryItem::new(
"With error border",
Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4")
.border_color(cx.theme().status().error_border),
)),
)
.child(
StorySection::new()
.child(StoryItem::new(
"With muted audio indicator",
Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4")
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Muted)),
))
.child(StoryItem::new(
"With deafened audio indicator",
Avatar::new("https://avatars.githubusercontent.com/u/326587?v=4")
.indicator(AvatarAudioStatusIndicator::new(AudioStatus::Deafened)),
)),
)
}
}

View file

@ -1,38 +0,0 @@
use gpui::Render;
use story::Story;
use crate::{prelude::*, IconName};
use crate::{Button, ButtonStyle};
pub struct ButtonStory;
impl Render for ButtonStory {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
Story::container()
.child(Story::title_for::<Button>())
.child(Story::label("Default"))
.child(Button::new("default_filled", "Click me"))
.child(Story::label("Selected"))
.child(Button::new("selected_filled", "Click me").toggle_state(true))
.child(Story::label("Selected with `selected_label`"))
.child(
Button::new("selected_label_filled", "Click me")
.toggle_state(true)
.selected_label("I have been selected"),
)
.child(Story::label("With `label_color`"))
.child(Button::new("filled_with_label_color", "Click me").color(Color::Created))
.child(Story::label("With `icon`"))
.child(Button::new("filled_with_icon", "Click me").icon(IconName::FileGit))
.child(Story::label("Selected with `icon`"))
.child(
Button::new("filled_and_selected_with_icon", "Click me")
.toggle_state(true)
.icon(IconName::FileGit),
)
.child(Story::label("Default (Subtle)"))
.child(Button::new("default_subtle", "Click me").style(ButtonStyle::Subtle))
.child(Story::label("Default (Transparent)"))
.child(Button::new("default_transparent", "Click me").style(ButtonStyle::Transparent))
}
}

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use std::cmp::Ordering;
use gpui::{AnyElement, IntoElement, Stateful};

View file

@ -1,4 +1,3 @@
#![allow(missing_docs)]
use gpui::{AnyElement, ScrollHandle};
use smallvec::SmallVec;

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use gpui::{Action, AnyElement, AnyView, AppContext as _, FocusHandle, IntoElement, Render};
use settings::Settings;
use theme::ThemeSettings;

View file

@ -237,40 +237,37 @@ impl Headline {
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(),
),
],
)])
.gap_1()
.children(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()
}
}

View file

@ -1,5 +1,3 @@
#![deny(missing_docs)]
//! # UI Zed UI Primitives & Components
//!
//! This crate provides a set of UI primitives and components that are used to build all of the elements in Zed's UI.

View file

@ -1,5 +1,4 @@
// This won't be documented further as it is intended to be removed, or merged with the `time_format` crate.
#![allow(missing_docs)]
use chrono::{DateTime, Local, NaiveDateTime};

View file

@ -1,5 +1,3 @@
#![allow(missing_docs)]
use gpui::Pixels;
pub struct SearchInputWidth;

View file

@ -3,10 +3,10 @@ use gpui::{actions, hsla, AnyElement, App, Entity, EventEmitter, FocusHandle, Fo
use strum::IntoEnumIterator;
use theme::all_theme_colors;
use ui::{
element_cell, prelude::*, string_cell, utils::calculate_contrast_ratio, AudioStatus,
Availability, Avatar, AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike,
Checkbox, CheckboxWithLabel, ContentGroup, DecoratedIcon, ElevationIndex, Facepile,
IconDecoration, Indicator, KeybindingHint, Switch, Table, TintColor, Tooltip,
element_cell, prelude::*, string_cell, utils::calculate_contrast_ratio, AudioStatus, Avatar,
AvatarAudioStatusIndicator, AvatarAvailabilityIndicator, ButtonLike, Checkbox,
CheckboxWithLabel, CollaboratorAvailability, ContentGroup, DecoratedIcon, ElevationIndex,
Facepile, IconDecoration, Indicator, KeybindingHint, Switch, Table, TintColor, Tooltip,
};
use crate::{Item, Workspace};