Detailed changes
@@ -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
@@ -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(),
)
@@ -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)) {
@@ -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(),
@@ -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
@@ -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 youβre thinking, βthis is an atrocity, what a horrible mess!β
-> and youβre right, itβs kind of ugly. In fact itβs 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
@@ -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()
+ }
+}
@@ -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()
- }
-}
@@ -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))
- }),
- )
- }
-}
@@ -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,
- })
- }
-}
@@ -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(),
- ),
],
),
])
@@ -1,4 +1,3 @@
-#![allow(missing_docs)]
use crate::{prelude::*, Icon, IconName, IconSize, IconWithIndicator, Indicator};
use gpui::Hsla;
@@ -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;
@@ -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()
+ }
+}
@@ -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()
+ }
+}
@@ -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,
@@ -1,4 +1,3 @@
-#![allow(missing_docs)]
use std::sync::Arc;
use gpui::{ClickEvent, CursorStyle};
@@ -1,4 +1,3 @@
-#![allow(missing_docs)]
use gpui::{Hsla, IntoElement};
use crate::prelude::*;
@@ -1,4 +1,3 @@
-#![allow(missing_docs)]
use gpui::{ClickEvent, Corner, CursorStyle, Entity, MouseButton};
use crate::{prelude::*, ContextMenu, PopoverMenu};
@@ -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()
+ }
+}
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
mod decorated_icon;
mod icon_decoration;
@@ -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};
@@ -1,4 +1,3 @@
-#![allow(missing_docs)]
use std::{cmp::Ordering, ops::Range, rc::Rc};
use gpui::{
@@ -1,4 +1,3 @@
-#![allow(missing_docs)]
use crate::{prelude::*, AnyIcon};
#[derive(Default)]
@@ -1,4 +1,3 @@
-#![allow(missing_docs)]
use crate::PlatformStyle;
use crate::{h_flex, prelude::*, Icon, IconName, IconSize};
use gpui::{
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use std::ops::Range;
use gpui::{FontWeight, HighlightStyle, StyledText};
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use gpui::AnyElement;
use smallvec::SmallVec;
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use std::sync::Arc;
use crate::{h_flex, prelude::*, Disclosure, Label};
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use std::sync::Arc;
use gpui::{px, AnyElement, AnyView, ClickEvent, MouseButton, MouseDownEvent, Pixels};
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use crate::prelude::*;
#[derive(IntoElement)]
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use crate::prelude::*;
use crate::{h_flex, Icon, IconName, IconSize, Label};
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use crate::{
h_flex, v_flex, Clickable, Color, DynamicSpacing, Headline, HeadlineSize, IconButton,
IconButtonShape, IconName, Label, LabelCommon, LabelSize,
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use gpui::ClickEvent;
use crate::{prelude::*, IconButtonShape};
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use crate::prelude::*;
use crate::v_flex;
use gpui::{
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use std::{cell::RefCell, rc::Rc};
use gpui::{
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use std::sync::Arc;
use crate::prelude::*;
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use std::{cell::RefCell, rc::Rc};
use gpui::{
@@ -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};
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use gpui::AnyElement;
use smallvec::SmallVec;
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use gpui::AnyElement;
use smallvec::SmallVec;
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use gpui::{div, Div};
use crate::StyledExt;
@@ -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::*;
@@ -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)),
- )),
- )
- }
-}
@@ -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))
- }
-}
@@ -1,4 +1,3 @@
-#![allow(missing_docs)]
use std::cmp::Ordering;
use gpui::{AnyElement, IntoElement, Stateful};
@@ -1,4 +1,3 @@
-#![allow(missing_docs)]
use gpui::{AnyElement, ScrollHandle};
use smallvec::SmallVec;
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use gpui::{Action, AnyElement, AnyView, AppContext as _, FocusHandle, IntoElement, Render};
use settings::Settings;
use theme::ThemeSettings;
@@ -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()
}
}
@@ -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.
@@ -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};
@@ -1,5 +1,3 @@
-#![allow(missing_docs)]
-
use gpui::Pixels;
pub struct SearchInputWidth;
@@ -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};