Detailed changes
@@ -0,0 +1,86 @@
+use component::{example_group_with_title, single_example};
+use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
+use smallvec::SmallVec;
+use ui::{Label, prelude::*};
+
+#[derive(IntoElement, RegisterComponent)]
+pub struct CalloutRow {
+ title: SharedString,
+ lines: SmallVec<[SharedString; 4]>,
+}
+
+impl CalloutRow {
+ pub fn new(title: impl Into<SharedString>) -> Self {
+ Self {
+ title: title.into(),
+ lines: SmallVec::new(),
+ }
+ }
+
+ pub fn line(mut self, line: impl Into<SharedString>) -> Self {
+ self.lines.push(line.into());
+ self
+ }
+}
+
+impl RenderOnce for CalloutRow {
+ fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
+ div().px_2().child(
+ v_flex()
+ .p_3()
+ .gap_1()
+ .bg(cx.theme().colors().surface_background)
+ .border_1()
+ .border_color(cx.theme().colors().border_variant)
+ .rounded_md()
+ .child(Label::new(self.title).weight(gpui::FontWeight::MEDIUM))
+ .children(
+ self.lines
+ .into_iter()
+ .map(|line| Label::new(line).size(LabelSize::Small).color(Color::Muted)),
+ ),
+ )
+ }
+}
+
+impl Component for CalloutRow {
+ fn scope() -> ComponentScope {
+ ComponentScope::Layout
+ }
+
+ fn sort_name() -> &'static str {
+ "RowCallout"
+ }
+
+ fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+ let examples = example_group_with_title(
+ "CalloutRow Examples",
+ vec![
+ single_example(
+ "Privacy Notice",
+ CalloutRow::new("We don't use your code to train AI models")
+ .line("You choose which providers you enable, and they have their own privacy policies.")
+ .line("Read more about our privacy practices in our Privacy Policy.")
+ .into_any_element(),
+ ),
+ single_example(
+ "Single Line",
+ CalloutRow::new("Important Notice")
+ .line("This is a single line of information.")
+ .into_any_element(),
+ ),
+ single_example(
+ "Multi Line",
+ CalloutRow::new("Getting Started")
+ .line("Welcome to Zed! Here are some things to know:")
+ .line("• Use Cmd+P to quickly open files")
+ .line("• Use Cmd+Shift+P to access the command palette")
+ .line("• Check out the documentation for more tips")
+ .into_any_element(),
+ ),
+ ],
+ );
+
+ Some(v_flex().p_4().gap_4().child(examples).into_any_element())
+ }
+}
@@ -0,0 +1,134 @@
+use component::{example_group_with_title, single_example};
+use gpui::StatefulInteractiveElement as _;
+use gpui::{AnyElement, App, ClickEvent, IntoElement, RenderOnce, Window};
+use ui::prelude::*;
+
+#[derive(IntoElement, RegisterComponent)]
+pub struct CheckboxRow {
+ label: SharedString,
+ description: Option<SharedString>,
+ checked: bool,
+ on_click: Option<Box<dyn Fn(&mut Window, &mut App) + 'static>>,
+}
+
+impl CheckboxRow {
+ pub fn new(label: impl Into<SharedString>) -> Self {
+ Self {
+ label: label.into(),
+ description: None,
+ checked: false,
+ on_click: None,
+ }
+ }
+
+ pub fn description(mut self, description: impl Into<SharedString>) -> Self {
+ self.description = Some(description.into());
+ self
+ }
+
+ pub fn checked(mut self, checked: bool) -> Self {
+ self.checked = checked;
+ self
+ }
+
+ pub fn on_click(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self {
+ self.on_click = Some(Box::new(handler));
+ self
+ }
+}
+
+impl RenderOnce for CheckboxRow {
+ fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
+ let checked = self.checked;
+ let on_click = self.on_click;
+
+ let checkbox = gpui::div()
+ .w_4()
+ .h_4()
+ .rounded_sm()
+ .border_1()
+ .border_color(cx.theme().colors().border)
+ .when(checked, |this| {
+ this.bg(cx.theme().colors().element_selected)
+ .border_color(cx.theme().colors().border_selected)
+ })
+ .hover(|this| this.bg(cx.theme().colors().element_hover))
+ .child(gpui::div().when(checked, |this| {
+ this.size_full()
+ .flex()
+ .items_center()
+ .justify_center()
+ .child(Icon::new(IconName::Check))
+ }));
+
+ let main_row = if let Some(on_click) = on_click {
+ gpui::div()
+ .id("checkbox-row")
+ .h_flex()
+ .gap_2()
+ .items_center()
+ .child(checkbox)
+ .child(Label::new(self.label))
+ .cursor_pointer()
+ .on_click(move |_event, window, cx| on_click(window, cx))
+ } else {
+ gpui::div()
+ .id("checkbox-row")
+ .h_flex()
+ .gap_2()
+ .items_center()
+ .child(checkbox)
+ .child(Label::new(self.label))
+ };
+
+ v_flex()
+ .px_5()
+ .py_1()
+ .gap_1()
+ .child(main_row)
+ .when_some(self.description, |this, desc| {
+ this.child(
+ gpui::div()
+ .ml_6()
+ .child(Label::new(desc).size(LabelSize::Small).color(Color::Muted)),
+ )
+ })
+ }
+}
+
+impl Component for CheckboxRow {
+ fn scope() -> ComponentScope {
+ ComponentScope::Layout
+ }
+
+ fn sort_name() -> &'static str {
+ "RowCheckbox"
+ }
+
+ fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+ let examples = example_group_with_title(
+ "CheckboxRow Examples",
+ vec![
+ single_example(
+ "Unchecked",
+ CheckboxRow::new("Enable Vim Mode").into_any_element(),
+ ),
+ single_example(
+ "Checked",
+ CheckboxRow::new("Send Crash Reports")
+ .checked(true)
+ .into_any_element(),
+ ),
+ single_example(
+ "With Description",
+ CheckboxRow::new("Send Telemetry")
+ .description("Help improve Zed by sending anonymous usage data")
+ .checked(true)
+ .into_any_element(),
+ ),
+ ],
+ );
+
+ Some(v_flex().p_4().gap_4().child(examples).into_any_element())
+ }
+}
@@ -0,0 +1,78 @@
+use component::{example_group_with_title, single_example};
+use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
+use ui::{Label, prelude::*};
+
+#[derive(IntoElement, RegisterComponent)]
+pub struct HeaderRow {
+ label: SharedString,
+ end_slot: Option<AnyElement>,
+}
+
+impl HeaderRow {
+ pub fn new(label: impl Into<SharedString>) -> Self {
+ Self {
+ label: label.into(),
+ end_slot: None,
+ }
+ }
+
+ pub fn end_slot(mut self, slot: impl IntoElement) -> Self {
+ self.end_slot = Some(slot.into_any_element());
+ self
+ }
+}
+
+impl RenderOnce for HeaderRow {
+ fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
+ h_flex()
+ .h(px(32.))
+ .w_full()
+ .px_5()
+ .justify_between()
+ .child(Label::new(self.label))
+ .when_some(self.end_slot, |this, slot| this.child(slot))
+ }
+}
+
+impl Component for HeaderRow {
+ fn scope() -> ComponentScope {
+ ComponentScope::Layout
+ }
+
+ fn sort_name() -> &'static str {
+ "RowHeader"
+ }
+
+ fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+ let examples = example_group_with_title(
+ "HeaderRow Examples",
+ vec![
+ single_example(
+ "Simple Header",
+ HeaderRow::new("Pick a Theme").into_any_element(),
+ ),
+ single_example(
+ "Header with Button",
+ HeaderRow::new("Pick a Theme")
+ .end_slot(
+ Button::new("more_themes", "More Themes")
+ .style(ButtonStyle::Subtle)
+ .color(Color::Muted),
+ )
+ .into_any_element(),
+ ),
+ single_example(
+ "Header with Icon Button",
+ HeaderRow::new("Settings")
+ .end_slot(
+ IconButton::new("refresh", IconName::RotateCw)
+ .style(ButtonStyle::Subtle),
+ )
+ .into_any_element(),
+ ),
+ ],
+ );
+
+ Some(v_flex().p_4().gap_4().child(examples).into_any_element())
+ }
+}
@@ -1 +1,5 @@
+mod callout_row;
+mod checkbox_row;
+mod header_row;
mod selectable_tile;
+mod selectable_tile_row;
@@ -0,0 +1,124 @@
+use super::selectable_tile::SelectableTile;
+use component::{example_group_with_title, single_example};
+use gpui::{
+ AnyElement, App, IntoElement, RenderOnce, StatefulInteractiveElement, Window, prelude::*,
+};
+use smallvec::SmallVec;
+use ui::{Label, prelude::*};
+
+#[derive(IntoElement, RegisterComponent)]
+pub struct SelectableTileRow {
+ gap: Pixels,
+ tiles: SmallVec<[SelectableTile; 8]>,
+}
+
+impl SelectableTileRow {
+ pub fn new() -> Self {
+ Self {
+ gap: px(12.),
+ tiles: SmallVec::new(),
+ }
+ }
+
+ pub fn gap(mut self, gap: impl Into<Pixels>) -> Self {
+ self.gap = gap.into();
+ self
+ }
+
+ pub fn tile(mut self, tile: SelectableTile) -> Self {
+ self.tiles.push(tile);
+ self
+ }
+}
+
+impl RenderOnce for SelectableTileRow {
+ fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
+ h_flex().w_full().px_5().gap(self.gap).children(self.tiles)
+ }
+}
+
+impl Component for SelectableTileRow {
+ fn scope() -> ComponentScope {
+ ComponentScope::Layout
+ }
+
+ fn sort_name() -> &'static str {
+ "RowSelectableTile"
+ }
+
+ fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
+ let examples = example_group_with_title(
+ "SelectableTileRow Examples",
+ vec![
+ single_example(
+ "Theme Tiles",
+ SelectableTileRow::new()
+ .gap(px(12.))
+ .tile(
+ SelectableTile::new("tile1", px(100.), px(80.))
+ .selected(true)
+ .child(
+ div()
+ .size_full()
+ .bg(gpui::red())
+ .flex()
+ .items_center()
+ .justify_center()
+ .child(Label::new("Dark")),
+ ),
+ )
+ .tile(
+ SelectableTile::new("tile2", px(100.), px(80.)).child(
+ div()
+ .size_full()
+ .bg(gpui::green())
+ .flex()
+ .items_center()
+ .justify_center()
+ .child(Label::new("Light")),
+ ),
+ )
+ .tile(
+ SelectableTile::new("tile3", px(100.), px(80.))
+ .parent_focused(true)
+ .child(
+ div()
+ .size_full()
+ .bg(gpui::blue())
+ .flex()
+ .items_center()
+ .justify_center()
+ .child(Label::new("Auto")),
+ ),
+ )
+ .into_any_element(),
+ ),
+ single_example(
+ "Icon Tiles",
+ SelectableTileRow::new()
+ .gap(px(8.))
+ .tile(
+ SelectableTile::new("icon1", px(48.), px(48.))
+ .selected(true)
+ .child(Icon::new(IconName::Code).size(IconSize::Medium)),
+ )
+ .tile(
+ SelectableTile::new("icon2", px(48.), px(48.))
+ .child(Icon::new(IconName::Terminal).size(IconSize::Medium)),
+ )
+ .tile(
+ SelectableTile::new("icon3", px(48.), px(48.))
+ .child(Icon::new(IconName::FileCode).size(IconSize::Medium)),
+ )
+ .tile(
+ SelectableTile::new("icon4", px(48.), px(48.))
+ .child(Icon::new(IconName::Settings).size(IconSize::Medium)),
+ )
+ .into_any_element(),
+ ),
+ ],
+ );
+
+ Some(v_flex().p_4().gap_4().child(examples).into_any_element())
+ }
+}