onboarding: Refine page and component designs (#35387)

Danilo Leal created

Includes adding new variants to the Dropdown and Numeric Stepper
components.

Release Notes:

- N/A

Change summary

crates/onboarding/src/basics_page.rs        | 103 ++++++
crates/onboarding/src/editing_page.rs       | 362 +++++++++++++---------
crates/onboarding/src/onboarding.rs         | 210 ++++++------
crates/ui/src/components/dropdown_menu.rs   |  36 ++
crates/ui/src/components/numeric_stepper.rs | 155 ++++++---
5 files changed, 551 insertions(+), 315 deletions(-)

Detailed changes

crates/onboarding/src/basics_page.rs 🔗

@@ -0,0 +1,103 @@
+use fs::Fs;
+use gpui::{App, IntoElement, Window};
+use settings::{Settings, update_settings_file};
+use theme::{ThemeMode, ThemeSettings};
+use ui::{SwitchField, ToggleButtonGroup, ToggleButtonSimple, ToggleButtonWithIcon, prelude::*};
+
+fn read_theme_selection(cx: &App) -> ThemeMode {
+    let settings = ThemeSettings::get_global(cx);
+    settings
+        .theme_selection
+        .as_ref()
+        .and_then(|selection| selection.mode())
+        .unwrap_or_default()
+}
+
+fn write_theme_selection(theme_mode: ThemeMode, cx: &App) {
+    let fs = <dyn Fs>::global(cx);
+
+    update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
+        settings.set_mode(theme_mode);
+    });
+}
+
+fn render_theme_section(cx: &mut App) -> impl IntoElement {
+    let theme_mode = read_theme_selection(cx);
+
+    h_flex().justify_between().child(Label::new("Theme")).child(
+        ToggleButtonGroup::single_row(
+            "theme-selector-onboarding",
+            [
+                ToggleButtonSimple::new("Light", |_, _, cx| {
+                    write_theme_selection(ThemeMode::Light, cx)
+                }),
+                ToggleButtonSimple::new("Dark", |_, _, cx| {
+                    write_theme_selection(ThemeMode::Dark, cx)
+                }),
+                ToggleButtonSimple::new("System", |_, _, cx| {
+                    write_theme_selection(ThemeMode::System, cx)
+                }),
+            ],
+        )
+        .selected_index(match theme_mode {
+            ThemeMode::Light => 0,
+            ThemeMode::Dark => 1,
+            ThemeMode::System => 2,
+        })
+        .style(ui::ToggleButtonGroupStyle::Outlined)
+        .button_width(rems_from_px(64.)),
+    )
+}
+
+fn render_telemetry_section() -> impl IntoElement {
+    v_flex()
+        .gap_3()
+        .child(Label::new("Telemetry").size(LabelSize::Large))
+        .child(SwitchField::new(
+            "vim_mode",
+            "Help Improve Zed",
+            "Sending anonymous usage data helps us build the right features and create the best experience.",
+            ui::ToggleState::Selected,
+            |_, _, _| {},
+        ))
+        .child(SwitchField::new(
+            "vim_mode",
+            "Help Fix Zed",
+            "Send crash reports so we can fix critical issues fast.",
+            ui::ToggleState::Selected,
+            |_, _, _| {},
+        ))
+}
+
+pub(crate) fn render_basics_page(_: &mut Window, cx: &mut App) -> impl IntoElement {
+    v_flex()
+        .gap_6()
+        .child(render_theme_section(cx))
+        .child(
+            v_flex().gap_2().child(Label::new("Base Keymap")).child(
+                ToggleButtonGroup::two_rows(
+                    "multiple_row_test",
+                    [
+                        ToggleButtonWithIcon::new("VS Code", IconName::AiZed, |_, _, _| {}),
+                        ToggleButtonWithIcon::new("Jetbrains", IconName::AiZed, |_, _, _| {}),
+                        ToggleButtonWithIcon::new("Sublime Text", IconName::AiZed, |_, _, _| {}),
+                    ],
+                    [
+                        ToggleButtonWithIcon::new("Atom", IconName::AiZed, |_, _, _| {}),
+                        ToggleButtonWithIcon::new("Emacs", IconName::AiZed, |_, _, _| {}),
+                        ToggleButtonWithIcon::new("Cursor (Beta)", IconName::AiZed, |_, _, _| {}),
+                    ],
+                )
+                .button_width(rems_from_px(230.))
+                .style(ui::ToggleButtonGroupStyle::Outlined)
+            ),
+        )
+        .child(v_flex().justify_center().child(div().h_0().child("hack").invisible()).child(SwitchField::new(
+            "vim_mode",
+            "Vim Mode",
+            "Coming from Neovim? Zed's first-class implementation of Vim Mode has got your back.",
+            ui::ToggleState::Selected,
+            |_, _, _| {},
+        )))
+        .child(render_telemetry_section())
+}

crates/onboarding/src/editing_page.rs 🔗

@@ -6,10 +6,8 @@ use project::project_settings::ProjectSettings;
 use settings::{Settings as _, update_settings_file};
 use theme::{FontFamilyCache, FontFamilyName, ThemeSettings};
 use ui::{
-    Clickable, ContextMenu, DropdownMenu, IconButton, Label, LabelCommon, LabelSize,
-    NumericStepper, ParentElement, SharedString, Styled, SwitchColor, SwitchField,
-    ToggleButtonGroup, ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, div, h_flex, px,
-    v_flex,
+    ButtonLike, ContextMenu, DropdownMenu, NumericStepper, SwitchField, ToggleButtonGroup,
+    ToggleButtonGroupStyle, ToggleButtonSimple, ToggleState, prelude::*,
 };
 
 use crate::{ImportCursorSettings, ImportVsCodeSettings};
@@ -118,153 +116,212 @@ fn write_buffer_font_family(font_family: SharedString, cx: &mut App) {
     });
 }
 
-pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
-    let theme_settings = ThemeSettings::get_global(cx);
-    let ui_font_size = theme_settings.ui_font_size(cx);
-    let font_family = theme_settings.buffer_font.family.clone();
-    let buffer_font_size = theme_settings.buffer_font_size(cx);
-
+fn render_import_settings_section() -> impl IntoElement {
     v_flex()
         .gap_4()
-        .child(Label::new("Import Settings").size(LabelSize::Large))
         .child(
-            Label::new("Automatically pull your settings from other editors.")
-                .size(LabelSize::Small),
+            v_flex()
+                .child(Label::new("Import Settings").size(LabelSize::Large))
+                .child(
+                    Label::new("Automatically pull your settings from other editors.")
+                        .color(Color::Muted),
+                ),
         )
         .child(
             h_flex()
+                .w_full()
+                .gap_4()
                 .child(
-                    IconButton::new("import-vs-code-settings", ui::IconName::Code).on_click(
-                        |_, window, cx| {
-                            window
-                                .dispatch_action(ImportVsCodeSettings::default().boxed_clone(), cx)
-                        },
+                    h_flex().w_full().child(
+                        ButtonLike::new("import_vs_code")
+                            .full_width()
+                            .style(ButtonStyle::Outlined)
+                            .size(ButtonSize::Large)
+                            .child(
+                                h_flex()
+                                    .w_full()
+                                    .gap_1p5()
+                                    .px_1()
+                                    .child(
+                                        Icon::new(IconName::Sparkle)
+                                            .color(Color::Muted)
+                                            .size(IconSize::XSmall),
+                                    )
+                                    .child(Label::new("VS Code")),
+                            )
+                            .on_click(|_, window, cx| {
+                                window.dispatch_action(
+                                    ImportVsCodeSettings::default().boxed_clone(),
+                                    cx,
+                                )
+                            }),
                     ),
                 )
                 .child(
-                    IconButton::new("import-cursor-settings", ui::IconName::CursorIBeam).on_click(
-                        |_, window, cx| {
-                            window
-                                .dispatch_action(ImportCursorSettings::default().boxed_clone(), cx)
-                        },
+                    h_flex().w_full().child(
+                        ButtonLike::new("import_cursor")
+                            .full_width()
+                            .style(ButtonStyle::Outlined)
+                            .size(ButtonSize::Large)
+                            .child(
+                                h_flex()
+                                    .w_full()
+                                    .gap_1p5()
+                                    .px_1()
+                                    .child(
+                                        Icon::new(IconName::Sparkle)
+                                            .color(Color::Muted)
+                                            .size(IconSize::XSmall),
+                                    )
+                                    .child(Label::new("Cursor")),
+                            )
+                            .on_click(|_, window, cx| {
+                                window.dispatch_action(
+                                    ImportCursorSettings::default().boxed_clone(),
+                                    cx,
+                                )
+                            }),
                     ),
                 ),
         )
-        .child(Label::new("Popular Settings").size(LabelSize::Large))
+}
+
+fn render_font_customization_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
+    let theme_settings = ThemeSettings::get_global(cx);
+    let ui_font_size = theme_settings.ui_font_size(cx);
+    let font_family = theme_settings.buffer_font.family.clone();
+    let buffer_font_size = theme_settings.buffer_font_size(cx);
+
+    h_flex()
+        .w_full()
+        .gap_4()
         .child(
-            h_flex()
-                .gap_4()
-                .justify_between()
+            v_flex()
+                .w_full()
+                .gap_1()
+                .child(Label::new("UI Font"))
                 .child(
-                    v_flex()
+                    h_flex()
+                        .w_full()
                         .justify_between()
-                        .gap_1()
-                        .child(Label::new("UI Font"))
+                        .gap_2()
                         .child(
-                            h_flex()
-                                .justify_between()
-                                .gap_2()
-                                .child(div().min_w(px(120.)).child(DropdownMenu::new(
-                                    "ui-font-family",
-                                    theme_settings.ui_font.family.clone(),
-                                    ContextMenu::build(window, cx, |mut menu, _, cx| {
-                                        let font_family_cache = FontFamilyCache::global(cx);
+                            DropdownMenu::new(
+                                "ui-font-family",
+                                theme_settings.ui_font.family.clone(),
+                                ContextMenu::build(window, cx, |mut menu, _, cx| {
+                                    let font_family_cache = FontFamilyCache::global(cx);
 
-                                        for font_name in font_family_cache.list_font_families(cx) {
-                                            menu = menu.custom_entry(
-                                                {
-                                                    let font_name = font_name.clone();
-                                                    move |_window, _cx| {
-                                                        Label::new(font_name.clone())
-                                                            .into_any_element()
-                                                    }
-                                                },
-                                                {
-                                                    let font_name = font_name.clone();
-                                                    move |_window, cx| {
-                                                        write_ui_font_family(font_name.clone(), cx);
-                                                    }
-                                                },
-                                            )
-                                        }
+                                    for font_name in font_family_cache.list_font_families(cx) {
+                                        menu = menu.custom_entry(
+                                            {
+                                                let font_name = font_name.clone();
+                                                move |_window, _cx| {
+                                                    Label::new(font_name.clone()).into_any_element()
+                                                }
+                                            },
+                                            {
+                                                let font_name = font_name.clone();
+                                                move |_window, cx| {
+                                                    write_ui_font_family(font_name.clone(), cx);
+                                                }
+                                            },
+                                        )
+                                    }
 
-                                        menu
-                                    }),
-                                )))
-                                .child(
-                                    NumericStepper::new(
-                                        "ui-font-size",
-                                        ui_font_size.to_string(),
-                                        move |_, _, cx| {
-                                            write_ui_font_size(ui_font_size - px(1.), cx);
-                                        },
-                                        move |_, _, cx| {
-                                            write_ui_font_size(ui_font_size + px(1.), cx);
-                                        },
-                                    )
-                                    .border(),
-                                ),
+                                    menu
+                                }),
+                            )
+                            .style(ui::DropdownStyle::Outlined)
+                            .full_width(true),
+                        )
+                        .child(
+                            NumericStepper::new(
+                                "ui-font-size",
+                                ui_font_size.to_string(),
+                                move |_, _, cx| {
+                                    write_ui_font_size(ui_font_size - px(1.), cx);
+                                },
+                                move |_, _, cx| {
+                                    write_ui_font_size(ui_font_size + px(1.), cx);
+                                },
+                            )
+                            .style(ui::NumericStepperStyle::Outlined),
                         ),
-                )
+                ),
+        )
+        .child(
+            v_flex()
+                .w_full()
+                .gap_1()
+                .child(Label::new("Editor Font"))
                 .child(
-                    v_flex()
+                    h_flex()
+                        .w_full()
                         .justify_between()
-                        .gap_1()
-                        .child(Label::new("Editor Font"))
+                        .gap_2()
                         .child(
-                            h_flex()
-                                .justify_between()
-                                .gap_2()
-                                .child(DropdownMenu::new(
-                                    "buffer-font-family",
-                                    font_family,
-                                    ContextMenu::build(window, cx, |mut menu, _, cx| {
-                                        let font_family_cache = FontFamilyCache::global(cx);
+                            DropdownMenu::new(
+                                "buffer-font-family",
+                                font_family,
+                                ContextMenu::build(window, cx, |mut menu, _, cx| {
+                                    let font_family_cache = FontFamilyCache::global(cx);
 
-                                        for font_name in font_family_cache.list_font_families(cx) {
-                                            menu = menu.custom_entry(
-                                                {
-                                                    let font_name = font_name.clone();
-                                                    move |_window, _cx| {
-                                                        Label::new(font_name.clone())
-                                                            .into_any_element()
-                                                    }
-                                                },
-                                                {
-                                                    let font_name = font_name.clone();
-                                                    move |_window, cx| {
-                                                        write_buffer_font_family(
-                                                            font_name.clone(),
-                                                            cx,
-                                                        );
-                                                    }
-                                                },
-                                            )
-                                        }
+                                    for font_name in font_family_cache.list_font_families(cx) {
+                                        menu = menu.custom_entry(
+                                            {
+                                                let font_name = font_name.clone();
+                                                move |_window, _cx| {
+                                                    Label::new(font_name.clone()).into_any_element()
+                                                }
+                                            },
+                                            {
+                                                let font_name = font_name.clone();
+                                                move |_window, cx| {
+                                                    write_buffer_font_family(font_name.clone(), cx);
+                                                }
+                                            },
+                                        )
+                                    }
 
-                                        menu
-                                    }),
-                                ))
-                                .child(
-                                    NumericStepper::new(
-                                        "buffer-font-size",
-                                        buffer_font_size.to_string(),
-                                        move |_, _, cx| {
-                                            write_buffer_font_size(buffer_font_size - px(1.), cx);
-                                        },
-                                        move |_, _, cx| {
-                                            write_buffer_font_size(buffer_font_size + px(1.), cx);
-                                        },
-                                    )
-                                    .border(),
-                                ),
+                                    menu
+                                }),
+                            )
+                            .style(ui::DropdownStyle::Outlined)
+                            .full_width(true),
+                        )
+                        .child(
+                            NumericStepper::new(
+                                "buffer-font-size",
+                                buffer_font_size.to_string(),
+                                move |_, _, cx| {
+                                    write_buffer_font_size(buffer_font_size - px(1.), cx);
+                                },
+                                move |_, _, cx| {
+                                    write_buffer_font_size(buffer_font_size + px(1.), cx);
+                                },
+                            )
+                            .style(ui::NumericStepperStyle::Outlined),
                         ),
                 ),
         )
+}
+
+fn render_popular_settings_section(window: &mut Window, cx: &mut App) -> impl IntoElement {
+    v_flex()
+        .gap_5()
+        .child(Label::new("Popular Settings").size(LabelSize::Large).mt_8())
+        .child(render_font_customization_section(window, cx))
         .child(
             h_flex()
+                .items_start()
                 .justify_between()
-                .child(Label::new("Mini Map"))
+                .child(
+                    v_flex().child(Label::new("Mini Map")).child(
+                        Label::new("See a high-level overview of your source code.")
+                            .color(Color::Muted),
+                    ),
+                )
                 .child(
                     ToggleButtonGroup::single_row(
                         "onboarding-show-mini-map",
@@ -289,36 +346,37 @@ pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl Int
                     .button_width(ui::rems_from_px(64.)),
                 ),
         )
-        .child(
-            SwitchField::new(
-                "onboarding-enable-inlay-hints",
-                "Inlay Hints",
-                "See parameter names for function and method calls inline.",
-                if read_inlay_hints(cx) {
-                    ui::ToggleState::Selected
-                } else {
-                    ui::ToggleState::Unselected
-                },
-                |toggle_state, _, cx| {
-                    write_inlay_hints(toggle_state == &ToggleState::Selected, cx);
-                },
-            )
-            .color(SwitchColor::Accent),
-        )
-        .child(
-            SwitchField::new(
-                "onboarding-git-blame-switch",
-                "Git Blame",
-                "See who committed each line on a given file.",
-                if read_git_blame(cx) {
-                    ui::ToggleState::Selected
-                } else {
-                    ui::ToggleState::Unselected
-                },
-                |toggle_state, _, cx| {
-                    set_git_blame(toggle_state == &ToggleState::Selected, cx);
-                },
-            )
-            .color(SwitchColor::Accent),
-        )
+        .child(SwitchField::new(
+            "onboarding-enable-inlay-hints",
+            "Inlay Hints",
+            "See parameter names for function and method calls inline.",
+            if read_inlay_hints(cx) {
+                ui::ToggleState::Selected
+            } else {
+                ui::ToggleState::Unselected
+            },
+            |toggle_state, _, cx| {
+                write_inlay_hints(toggle_state == &ToggleState::Selected, cx);
+            },
+        ))
+        .child(SwitchField::new(
+            "onboarding-git-blame-switch",
+            "Git Blame",
+            "See who committed each line on a given file.",
+            if read_git_blame(cx) {
+                ui::ToggleState::Selected
+            } else {
+                ui::ToggleState::Unselected
+            },
+            |toggle_state, _, cx| {
+                set_git_blame(toggle_state == &ToggleState::Selected, cx);
+            },
+        ))
+}
+
+pub(crate) fn render_editing_page(window: &mut Window, cx: &mut App) -> impl IntoElement {
+    v_flex()
+        .gap_4()
+        .child(render_import_settings_section())
+        .child(render_popular_settings_section(window, cx))
 }

crates/onboarding/src/onboarding.rs 🔗

@@ -10,13 +10,9 @@ use gpui::{
 };
 use schemars::JsonSchema;
 use serde::Deserialize;
-use settings::{Settings, SettingsStore, VsCodeSettingsSource, update_settings_file};
+use settings::{SettingsStore, VsCodeSettingsSource};
 use std::sync::Arc;
-use theme::{ThemeMode, ThemeSettings};
-use ui::{
-    Divider, FluentBuilder, Headline, KeyBinding, ParentElement as _, StatefulInteractiveElement,
-    ToggleButtonGroup, ToggleButtonSimple, Vector, VectorName, prelude::*, rems_from_px,
-};
+use ui::{FluentBuilder, KeyBinding, Vector, VectorName, prelude::*, rems_from_px};
 use workspace::{
     AppState, Workspace, WorkspaceId,
     dock::DockPosition,
@@ -24,6 +20,7 @@ use workspace::{
     open_new, with_active_or_new_workspace,
 };
 
+mod basics_page;
 mod editing_page;
 mod welcome;
 
@@ -205,23 +202,6 @@ pub fn show_onboarding_view(app_state: Arc<AppState>, cx: &mut App) -> Task<anyh
     )
 }
 
-fn read_theme_selection(cx: &App) -> ThemeMode {
-    let settings = ThemeSettings::get_global(cx);
-    settings
-        .theme_selection
-        .as_ref()
-        .and_then(|selection| selection.mode())
-        .unwrap_or_default()
-}
-
-fn write_theme_selection(theme_mode: ThemeMode, cx: &App) {
-    let fs = <dyn Fs>::global(cx);
-
-    update_settings_file::<ThemeSettings>(fs, cx, move |settings, _| {
-        settings.set_mode(theme_mode);
-    });
-}
-
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 enum SelectedPage {
     Basics,
@@ -246,7 +226,7 @@ impl Onboarding {
         })
     }
 
-    fn render_page_nav(
+    fn render_nav_button(
         &mut self,
         page: SelectedPage,
         _: &mut Window,
@@ -257,54 +237,119 @@ impl Onboarding {
             SelectedPage::Editing => "Editing",
             SelectedPage::AiSetup => "AI Setup",
         };
+
         let binding = match page {
             SelectedPage::Basics => {
                 KeyBinding::new(vec![gpui::Keystroke::parse("cmd-1").unwrap()], cx)
+                    .map(|kb| kb.size(rems_from_px(12.)))
             }
             SelectedPage::Editing => {
                 KeyBinding::new(vec![gpui::Keystroke::parse("cmd-2").unwrap()], cx)
+                    .map(|kb| kb.size(rems_from_px(12.)))
             }
             SelectedPage::AiSetup => {
                 KeyBinding::new(vec![gpui::Keystroke::parse("cmd-3").unwrap()], cx)
+                    .map(|kb| kb.size(rems_from_px(12.)))
             }
         };
+
         let selected = self.selected_page == page;
+
         h_flex()
             .id(text)
-            .rounded_sm()
-            .child(text)
-            .child(binding)
-            .h_8()
+            .relative()
+            .w_full()
             .gap_2()
             .px_2()
             .py_0p5()
-            .w_full()
             .justify_between()
-            .map(|this| {
-                if selected {
-                    this.bg(Color::Selected.color(cx))
-                        .border_l_1()
-                        .border_color(Color::Accent.color(cx))
-                } else {
-                    this.text_color(Color::Muted.color(cx))
-                }
+            .rounded_sm()
+            .when(selected, |this| {
+                this.child(
+                    div()
+                        .h_4()
+                        .w_px()
+                        .bg(cx.theme().colors().text_accent)
+                        .absolute()
+                        .left_0(),
+                )
             })
-            .hover(|style| {
+            .hover(|style| style.bg(cx.theme().colors().element_hover))
+            .child(Label::new(text).map(|this| {
                 if selected {
-                    style.bg(Color::Selected.color(cx).opacity(0.6))
+                    this.color(Color::Default)
                 } else {
-                    style.bg(Color::Selected.color(cx).opacity(0.3))
+                    this.color(Color::Muted)
                 }
-            })
+            }))
+            .child(binding)
             .on_click(cx.listener(move |this, _, _, cx| {
                 this.selected_page = page;
                 cx.notify();
             }))
     }
 
+    fn render_nav(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+        v_flex()
+            .h_full()
+            .w(rems_from_px(220.))
+            .flex_shrink_0()
+            .gap_4()
+            .justify_between()
+            .child(
+                v_flex()
+                    .gap_6()
+                    .child(
+                        h_flex()
+                            .px_2()
+                            .gap_4()
+                            .child(Vector::square(VectorName::ZedLogo, rems(2.5)))
+                            .child(
+                                v_flex()
+                                    .child(
+                                        Headline::new("Welcome to Zed").size(HeadlineSize::Small),
+                                    )
+                                    .child(
+                                        Label::new("The editor for what's next")
+                                            .color(Color::Muted)
+                                            .size(LabelSize::Small)
+                                            .italic(),
+                                    ),
+                            ),
+                    )
+                    .child(
+                        v_flex()
+                            .gap_4()
+                            .child(
+                                v_flex()
+                                    .py_4()
+                                    .border_y_1()
+                                    .border_color(cx.theme().colors().border_variant.opacity(0.5))
+                                    .gap_1()
+                                    .children([
+                                        self.render_nav_button(SelectedPage::Basics, window, cx)
+                                            .into_element(),
+                                        self.render_nav_button(SelectedPage::Editing, window, cx)
+                                            .into_element(),
+                                        self.render_nav_button(SelectedPage::AiSetup, window, cx)
+                                            .into_element(),
+                                    ]),
+                            )
+                            .child(Button::new("skip_all", "Skip All")),
+                    ),
+            )
+            .child(
+                Button::new("sign_in", "Sign In")
+                    .style(ButtonStyle::Outlined)
+                    .full_width(),
+            )
+    }
+
     fn render_page(&mut self, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
         match self.selected_page {
-            SelectedPage::Basics => self.render_basics_page(window, cx).into_any_element(),
+            SelectedPage::Basics => {
+                crate::basics_page::render_basics_page(window, cx).into_any_element()
+            }
             SelectedPage::Editing => {
                 crate::editing_page::render_editing_page(window, cx).into_any_element()
             }
@@ -312,36 +357,6 @@ impl Onboarding {
         }
     }
 
-    fn render_basics_page(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let theme_mode = read_theme_selection(cx);
-
-        v_flex().child(
-            h_flex().justify_between().child(Label::new("Theme")).child(
-                ToggleButtonGroup::single_row(
-                    "theme-selector-onboarding",
-                    [
-                        ToggleButtonSimple::new("Light", |_, _, cx| {
-                            write_theme_selection(ThemeMode::Light, cx)
-                        }),
-                        ToggleButtonSimple::new("Dark", |_, _, cx| {
-                            write_theme_selection(ThemeMode::Dark, cx)
-                        }),
-                        ToggleButtonSimple::new("System", |_, _, cx| {
-                            write_theme_selection(ThemeMode::System, cx)
-                        }),
-                    ],
-                )
-                .selected_index(match theme_mode {
-                    ThemeMode::Light => 0,
-                    ThemeMode::Dark => 1,
-                    ThemeMode::System => 2,
-                })
-                .style(ui::ToggleButtonGroupStyle::Outlined)
-                .button_width(rems_from_px(64.)),
-            ),
-        )
-    }
-
     fn render_ai_setup_page(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
         div().child("ai setup page")
     }
@@ -352,44 +367,27 @@ impl Render for Onboarding {
         h_flex()
             .image_cache(gpui::retain_all("onboarding-page"))
             .key_context("onboarding-page")
-            .px_24()
-            .py_12()
-            .items_start()
+            .size_full()
+            .bg(cx.theme().colors().editor_background)
             .child(
-                v_flex()
-                    .w_1_3()
-                    .h_full()
-                    .child(
-                        h_flex()
-                            .pt_0p5()
-                            .child(Vector::square(VectorName::ZedLogo, rems(2.)))
-                            .child(
-                                v_flex()
-                                    .left_1()
-                                    .items_center()
-                                    .child(Headline::new("Welcome to Zed"))
-                                    .child(
-                                        Label::new("The editor for what's next")
-                                            .color(Color::Muted)
-                                            .italic(),
-                                    ),
-                            ),
-                    )
-                    .p_1()
-                    .child(Divider::horizontal())
+                h_flex()
+                    .max_w(rems_from_px(1100.))
+                    .size_full()
+                    .m_auto()
+                    .py_20()
+                    .px_12()
+                    .items_start()
+                    .gap_12()
+                    .child(self.render_nav(window, cx))
                     .child(
-                        v_flex().gap_1().children([
-                            self.render_page_nav(SelectedPage::Basics, window, cx)
-                                .into_element(),
-                            self.render_page_nav(SelectedPage::Editing, window, cx)
-                                .into_element(),
-                            self.render_page_nav(SelectedPage::AiSetup, window, cx)
-                                .into_element(),
-                        ]),
+                        div()
+                            .pl_12()
+                            .border_l_1()
+                            .border_color(cx.theme().colors().border_variant.opacity(0.5))
+                            .size_full()
+                            .child(self.render_page(window, cx)),
                     ),
             )
-            .child(div().child(Divider::vertical()).h_full())
-            .child(div().w_2_3().h_full().child(self.render_page(window, cx)))
     }
 }
 

crates/ui/src/components/dropdown_menu.rs 🔗

@@ -8,6 +8,7 @@ use super::PopoverMenuHandle;
 pub enum DropdownStyle {
     #[default]
     Solid,
+    Outlined,
     Ghost,
 }
 
@@ -147,6 +148,23 @@ impl Component for DropdownMenu {
                             ),
                         ],
                     ),
+                    example_group_with_title(
+                        "Styles",
+                        vec![
+                            single_example(
+                                "Outlined",
+                                DropdownMenu::new("outlined", "Outlined Dropdown", menu.clone())
+                                    .style(DropdownStyle::Outlined)
+                                    .into_any_element(),
+                            ),
+                            single_example(
+                                "Ghost",
+                                DropdownMenu::new("ghost", "Ghost Dropdown", menu.clone())
+                                    .style(DropdownStyle::Ghost)
+                                    .into_any_element(),
+                            ),
+                        ],
+                    ),
                     example_group_with_title(
                         "States",
                         vec![single_example(
@@ -170,10 +188,13 @@ pub struct DropdownTriggerStyle {
 impl DropdownTriggerStyle {
     pub fn for_style(style: DropdownStyle, cx: &App) -> Self {
         let colors = cx.theme().colors();
+
         let bg = match style {
             DropdownStyle::Solid => colors.editor_background,
+            DropdownStyle::Outlined => colors.surface_background,
             DropdownStyle::Ghost => colors.ghost_element_background,
         };
+
         Self { bg }
     }
 }
@@ -244,17 +265,24 @@ impl RenderOnce for DropdownMenuTrigger {
         let disabled = self.disabled;
 
         let style = DropdownTriggerStyle::for_style(self.style, cx);
+        let is_outlined = matches!(self.style, DropdownStyle::Outlined);
 
         h_flex()
             .id("dropdown-menu-trigger")
-            .justify_between()
-            .rounded_sm()
-            .bg(style.bg)
+            .min_w_20()
             .pl_2()
             .pr_1p5()
             .py_0p5()
             .gap_2()
-            .min_w_20()
+            .justify_between()
+            .rounded_sm()
+            .bg(style.bg)
+            .hover(|s| s.bg(cx.theme().colors().element_hover))
+            .when(is_outlined, |this| {
+                this.border_1()
+                    .border_color(cx.theme().colors().border)
+                    .overflow_hidden()
+            })
             .map(|el| {
                 if self.full_width {
                     el.w_full()

crates/ui/src/components/numeric_stepper.rs 🔗

@@ -1,17 +1,24 @@
 use gpui::ClickEvent;
 
-use crate::{Divider, IconButtonShape, prelude::*};
+use crate::{IconButtonShape, prelude::*};
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum NumericStepperStyle {
+    Outlined,
+    #[default]
+    Ghost,
+}
 
 #[derive(IntoElement, RegisterComponent)]
 pub struct NumericStepper {
     id: ElementId,
     value: SharedString,
+    style: NumericStepperStyle,
     on_decrement: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
     on_increment: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
     /// Whether to reserve space for the reset button.
     reserve_space_for_reset: bool,
     on_reset: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
-    border: bool,
 }
 
 impl NumericStepper {
@@ -24,14 +31,19 @@ impl NumericStepper {
         Self {
             id: id.into(),
             value: value.into(),
+            style: NumericStepperStyle::default(),
             on_decrement: Box::new(on_decrement),
             on_increment: Box::new(on_increment),
-            border: false,
             reserve_space_for_reset: false,
             on_reset: None,
         }
     }
 
+    pub fn style(mut self, style: NumericStepperStyle) -> Self {
+        self.style = style;
+        self
+    }
+
     pub fn reserve_space_for_reset(mut self, reserve_space_for_reset: bool) -> Self {
         self.reserve_space_for_reset = reserve_space_for_reset;
         self
@@ -44,11 +56,6 @@ impl NumericStepper {
         self.on_reset = Some(Box::new(on_reset));
         self
     }
-
-    pub fn border(mut self) -> Self {
-        self.border = true;
-        self
-    }
 }
 
 impl RenderOnce for NumericStepper {
@@ -56,6 +63,8 @@ impl RenderOnce for NumericStepper {
         let shape = IconButtonShape::Square;
         let icon_size = IconSize::Small;
 
+        let is_outlined = matches!(self.style, NumericStepperStyle::Outlined);
+
         h_flex()
             .id(self.id)
             .gap_1()
@@ -81,31 +90,65 @@ impl RenderOnce for NumericStepper {
             .child(
                 h_flex()
                     .gap_1()
-                    .when(self.border, |this| {
-                        this.border_1().border_color(cx.theme().colors().border)
-                    })
-                    .px_1()
                     .rounded_sm()
-                    .bg(cx.theme().colors().editor_background)
-                    .child(
-                        IconButton::new("decrement", IconName::Dash)
-                            .shape(shape)
-                            .icon_size(icon_size)
-                            .on_click(self.on_decrement),
-                    )
-                    .when(self.border, |this| {
-                        this.child(Divider::vertical().color(super::DividerColor::Border))
+                    .map(|this| {
+                        if is_outlined {
+                            this.overflow_hidden()
+                                .bg(cx.theme().colors().surface_background)
+                                .border_1()
+                                .border_color(cx.theme().colors().border)
+                        } else {
+                            this.px_1().bg(cx.theme().colors().editor_background)
+                        }
                     })
-                    .child(Label::new(self.value))
-                    .when(self.border, |this| {
-                        this.child(Divider::vertical().color(super::DividerColor::Border))
+                    .map(|decrement| {
+                        if is_outlined {
+                            decrement.child(
+                                h_flex()
+                                    .id("decrement_button")
+                                    .p_1p5()
+                                    .size_full()
+                                    .justify_center()
+                                    .hover(|s| s.bg(cx.theme().colors().element_hover))
+                                    .border_r_1()
+                                    .border_color(cx.theme().colors().border)
+                                    .child(Icon::new(IconName::Dash).size(IconSize::Small))
+                                    .on_click(self.on_decrement),
+                            )
+                        } else {
+                            decrement.child(
+                                IconButton::new("decrement", IconName::Dash)
+                                    .shape(shape)
+                                    .icon_size(icon_size)
+                                    .on_click(self.on_decrement),
+                            )
+                        }
                     })
-                    .child(
-                        IconButton::new("increment", IconName::Plus)
-                            .shape(shape)
-                            .icon_size(icon_size)
-                            .on_click(self.on_increment),
-                    ),
+                    .when(is_outlined, |this| this)
+                    .child(Label::new(self.value).mx_3())
+                    .map(|increment| {
+                        if is_outlined {
+                            increment.child(
+                                h_flex()
+                                    .id("increment_button")
+                                    .p_1p5()
+                                    .size_full()
+                                    .justify_center()
+                                    .hover(|s| s.bg(cx.theme().colors().element_hover))
+                                    .border_l_1()
+                                    .border_color(cx.theme().colors().border)
+                                    .child(Icon::new(IconName::Plus).size(IconSize::Small))
+                                    .on_click(self.on_increment),
+                            )
+                        } else {
+                            increment.child(
+                                IconButton::new("increment", IconName::Dash)
+                                    .shape(shape)
+                                    .icon_size(icon_size)
+                                    .on_click(self.on_increment),
+                            )
+                        }
+                    }),
             )
     }
 }
@@ -116,7 +159,7 @@ impl Component for NumericStepper {
     }
 
     fn name() -> &'static str {
-        "NumericStepper"
+        "Numeric Stepper"
     }
 
     fn sort_name() -> &'static str {
@@ -124,33 +167,39 @@ impl Component for NumericStepper {
     }
 
     fn description() -> Option<&'static str> {
-        Some("A button used to increment or decrement a numeric value. ")
+        Some("A button used to increment or decrement a numeric value.")
     }
 
     fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
         Some(
             v_flex()
-                .child(single_example(
-                    "Borderless",
-                    NumericStepper::new(
-                        "numeric-stepper-component-preview",
-                        "10",
-                        move |_, _, _| {},
-                        move |_, _, _| {},
-                    )
-                    .into_any_element(),
-                ))
-                .child(single_example(
-                    "Border",
-                    NumericStepper::new(
-                        "numeric-stepper-with-border-component-preview",
-                        "10",
-                        move |_, _, _| {},
-                        move |_, _, _| {},
-                    )
-                    .border()
-                    .into_any_element(),
-                ))
+                .gap_6()
+                .children(vec![example_group_with_title(
+                    "Styles",
+                    vec![
+                        single_example(
+                            "Default",
+                            NumericStepper::new(
+                                "numeric-stepper-component-preview",
+                                "10",
+                                move |_, _, _| {},
+                                move |_, _, _| {},
+                            )
+                            .into_any_element(),
+                        ),
+                        single_example(
+                            "Outlined",
+                            NumericStepper::new(
+                                "numeric-stepper-with-border-component-preview",
+                                "10",
+                                move |_, _, _| {},
+                                move |_, _, _| {},
+                            )
+                            .style(NumericStepperStyle::Outlined)
+                            .into_any_element(),
+                        ),
+                    ],
+                )])
                 .into_any_element(),
         )
     }