Onboarding refactor (#39724)

<img width="1648" height="976" alt="Screenshot 2025-10-07 at 6 57 20 PM"
src="https://github.com/user-attachments/assets/ae7289c0-8820-4fdf-ae28-84fb6bd64942"
/>

Fixes #39347

Release Notes:

- Improved onboarding UI by collapsing it to a single page

---------

Co-authored-by: dino <dinojoaocosta@gmail.com>
Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Anthony Eid <hello@anthonyeid.me>
Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
Andrew Farkas
2025-10-08 22:47:25 +00:00
committed by GitHub
co-authored by dino Lukas Wirth Mikayla Maki Anthony Eid Mikayla Maki
parent 4dbd186485
commit ba937d16e7
15 changed files with 415 additions and 1535 deletions
+76 -13
View File
@@ -135,6 +135,9 @@ pub enum ButtonStyle {
/// a fully transparent button.
Outlined,
/// Transparent button that always has an outline.
OutlinedTransparent,
/// A more de-emphasized version of the outlined button.
OutlinedGhost,
@@ -149,11 +152,38 @@ pub enum ButtonStyle {
Transparent,
}
/// Rounding for a button that may have straight edges.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub(crate) enum ButtonLikeRounding {
All,
Left,
Right,
pub(crate) struct ButtonLikeRounding {
/// Top-left corner rounding
pub top_left: bool,
/// Top-right corner rounding
pub top_right: bool,
/// Bottom-right corner rounding
pub bottom_right: bool,
/// Bottom-left corner rounding
pub bottom_left: bool,
}
impl ButtonLikeRounding {
pub const ALL: Self = Self {
top_left: true,
top_right: true,
bottom_right: true,
bottom_left: true,
};
pub const LEFT: Self = Self {
top_left: true,
top_right: false,
bottom_right: false,
bottom_left: true,
};
pub const RIGHT: Self = Self {
top_left: false,
top_right: true,
bottom_right: true,
bottom_left: false,
};
}
#[derive(Debug, Clone)]
@@ -198,6 +228,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::OutlinedTransparent => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_background,
border_color: cx.theme().colors().border_variant,
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::OutlinedGhost => ButtonLikeStyles {
background: transparent_black(),
border_color: cx.theme().colors().border_variant,
@@ -249,6 +285,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::OutlinedTransparent => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_hover,
border_color: cx.theme().colors().border,
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::OutlinedGhost => ButtonLikeStyles {
background: transparent_black(),
border_color: cx.theme().colors().border,
@@ -293,6 +335,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::OutlinedTransparent => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_active,
border_color: cx.theme().colors().border_variant,
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::OutlinedGhost => ButtonLikeStyles {
background: transparent_black(),
border_color: cx.theme().colors().border_variant,
@@ -332,6 +380,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::OutlinedTransparent => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_background,
border_color: cx.theme().colors().border,
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::OutlinedGhost => ButtonLikeStyles {
background: transparent_black(),
border_color: cx.theme().colors().border,
@@ -374,6 +428,12 @@ impl ButtonStyle {
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::OutlinedTransparent => ButtonLikeStyles {
background: cx.theme().colors().ghost_element_disabled,
border_color: cx.theme().colors().border_disabled,
label_color: Color::Default.color(cx),
icon_color: Color::Default.color(cx),
},
ButtonStyle::OutlinedGhost => ButtonLikeStyles {
background: transparent_black(),
border_color: cx.theme().colors().border_disabled,
@@ -455,7 +515,7 @@ impl ButtonLike {
width: None,
height: None,
size: ButtonSize::Default,
rounding: Some(ButtonLikeRounding::All),
rounding: Some(ButtonLikeRounding::ALL),
tooltip: None,
hoverable_tooltip: None,
children: SmallVec::new(),
@@ -469,15 +529,15 @@ impl ButtonLike {
}
pub fn new_rounded_left(id: impl Into<ElementId>) -> Self {
Self::new(id).rounding(ButtonLikeRounding::Left)
Self::new(id).rounding(ButtonLikeRounding::LEFT)
}
pub fn new_rounded_right(id: impl Into<ElementId>) -> Self {
Self::new(id).rounding(ButtonLikeRounding::Right)
Self::new(id).rounding(ButtonLikeRounding::RIGHT)
}
pub fn new_rounded_all(id: impl Into<ElementId>) -> Self {
Self::new(id).rounding(ButtonLikeRounding::All)
Self::new(id).rounding(ButtonLikeRounding::ALL)
}
pub fn opacity(mut self, opacity: f32) -> Self {
@@ -630,14 +690,17 @@ impl RenderOnce for ButtonLike {
.when(
matches!(
self.style,
ButtonStyle::Outlined | ButtonStyle::OutlinedGhost
ButtonStyle::Outlined
| ButtonStyle::OutlinedTransparent
| ButtonStyle::OutlinedGhost
),
|this| this.border_1(),
)
.when_some(self.rounding, |this, rounding| match rounding {
ButtonLikeRounding::All => this.rounded_sm(),
ButtonLikeRounding::Left => this.rounded_l_sm(),
ButtonLikeRounding::Right => this.rounded_r_sm(),
.when_some(self.rounding, |this, rounding| {
this.when(rounding.top_left, |this| this.rounded_tl_sm())
.when(rounding.top_right, |this| this.rounded_tr_sm())
.when(rounding.bottom_right, |this| this.rounded_br_sm())
.when(rounding.bottom_left, |this| this.rounded_bl_sm())
})
.gap(DynamicSpacing::Base04.rems(cx))
.map(|this| match self.size {
@@ -6,15 +6,41 @@ use crate::{ButtonLike, ButtonLikeRounding, ElevationIndex, TintColor, Tooltip,
/// The position of a [`ToggleButton`] within a group of buttons.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ToggleButtonPosition {
/// The toggle button is first in the group.
First,
pub struct ToggleButtonPosition {
/// The toggle button is one of the leftmost of the group.
leftmost: bool,
/// The toggle button is one of the rightmost of the group.
rightmost: bool,
/// The toggle button is one of the topmost of the group.
topmost: bool,
/// The toggle button is one of the bottommost of the group.
bottommost: bool,
}
/// The toggle button is in the middle of the group (i.e., it is not the first or last toggle button).
Middle,
impl ToggleButtonPosition {
pub const HORIZONTAL_FIRST: Self = Self {
leftmost: true,
..Self::HORIZONTAL_MIDDLE
};
pub const HORIZONTAL_MIDDLE: Self = Self {
leftmost: false,
rightmost: false,
topmost: true,
bottommost: true,
};
pub const HORIZONTAL_LAST: Self = Self {
rightmost: true,
..Self::HORIZONTAL_MIDDLE
};
/// The toggle button is last in the group.
Last,
pub(crate) fn to_rounding(self) -> ButtonLikeRounding {
ButtonLikeRounding {
top_left: self.topmost && self.leftmost,
top_right: self.topmost && self.rightmost,
bottom_right: self.bottommost && self.rightmost,
bottom_left: self.bottommost && self.leftmost,
}
}
}
#[derive(IntoElement, RegisterComponent)]
@@ -46,15 +72,15 @@ impl ToggleButton {
}
pub fn first(self) -> Self {
self.position_in_group(ToggleButtonPosition::First)
self.position_in_group(ToggleButtonPosition::HORIZONTAL_FIRST)
}
pub fn middle(self) -> Self {
self.position_in_group(ToggleButtonPosition::Middle)
self.position_in_group(ToggleButtonPosition::HORIZONTAL_MIDDLE)
}
pub fn last(self) -> Self {
self.position_in_group(ToggleButtonPosition::Last)
self.position_in_group(ToggleButtonPosition::HORIZONTAL_LAST)
}
}
@@ -153,10 +179,8 @@ impl RenderOnce for ToggleButton {
};
self.base
.when_some(self.position_in_group, |this, position| match position {
ToggleButtonPosition::First => this.rounding(ButtonLikeRounding::Left),
ToggleButtonPosition::Middle => this.rounding(None),
ToggleButtonPosition::Last => this.rounding(ButtonLikeRounding::Right),
.when_some(self.position_in_group, |this, position| {
this.rounding(position.to_rounding())
})
.child(
Label::new(self.label)
@@ -535,7 +559,15 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
ButtonLike::new((group_name.clone(), entry_index))
.full_width()
.rounding(None)
.rounding(Some(
ToggleButtonPosition {
leftmost: col_index == 0,
rightmost: col_index == COLS - 1,
topmost: row_index == 0,
bottommost: row_index == ROWS - 1,
}
.to_rounding(),
))
.when_some(self.tab_index, |this, tab_index| {
this.tab_index(tab_index + entry_index as isize)
})
+26 -20
View File
@@ -585,7 +585,7 @@ impl RenderOnce for Switch {
///
/// let switch_field = SwitchField::new(
/// "feature-toggle",
/// "Enable feature",
/// Some("Enable feature"),
/// Some("This feature adds new functionality to the app.".into()),
/// ToggleState::Unselected,
/// |state, window, cx| {
@@ -596,7 +596,7 @@ impl RenderOnce for Switch {
#[derive(IntoElement, RegisterComponent)]
pub struct SwitchField {
id: ElementId,
label: SharedString,
label: Option<SharedString>,
description: Option<SharedString>,
toggle_state: ToggleState,
on_click: Arc<dyn Fn(&ToggleState, &mut Window, &mut App) + 'static>,
@@ -609,14 +609,14 @@ pub struct SwitchField {
impl SwitchField {
pub fn new(
id: impl Into<ElementId>,
label: impl Into<SharedString>,
label: Option<impl Into<SharedString>>,
description: Option<SharedString>,
toggle_state: impl Into<ToggleState>,
on_click: impl Fn(&ToggleState, &mut Window, &mut App) + 'static,
) -> Self {
Self {
id: id.into(),
label: label.into(),
label: label.map(Into::into),
description,
toggle_state: toggle_state.into(),
on_click: Arc::new(on_click),
@@ -657,11 +657,11 @@ impl SwitchField {
impl RenderOnce for SwitchField {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let tooltip = self.tooltip.map(|tooltip_fn| {
h_flex()
.gap_0p5()
.child(Label::new(self.label.clone()))
.child(
let tooltip = self
.tooltip
.zip(self.label.clone())
.map(|(tooltip_fn, label)| {
h_flex().gap_0p5().child(Label::new(label)).child(
IconButton::new("tooltip_button", IconName::Info)
.icon_size(IconSize::XSmall)
.icon_color(Color::Muted)
@@ -673,7 +673,7 @@ impl RenderOnce for SwitchField {
})
.on_click(|_, _, _| {}), // Intentional empty on click handler so that clicking on the info tooltip icon doesn't trigger the switch toggle
)
});
});
h_flex()
.id((self.id.clone(), "container"))
@@ -694,11 +694,17 @@ impl RenderOnce for SwitchField {
(Some(description), None) => v_flex()
.gap_0p5()
.max_w_5_6()
.child(Label::new(self.label.clone()))
.when_some(self.label, |this, label| this.child(Label::new(label)))
.child(Label::new(description.clone()).color(Color::Muted))
.into_any_element(),
(None, Some(tooltip)) => tooltip.into_any_element(),
(None, None) => Label::new(self.label.clone()).into_any_element(),
(None, None) => {
if let Some(label) = self.label.clone() {
Label::new(label).into_any_element()
} else {
gpui::Empty.into_any_element()
}
}
})
.child(
Switch::new((self.id.clone(), "switch"), self.toggle_state)
@@ -748,7 +754,7 @@ impl Component for SwitchField {
"Unselected",
SwitchField::new(
"switch_field_unselected",
"Enable notifications",
Some("Enable notifications"),
Some("Receive notifications when new messages arrive.".into()),
ToggleState::Unselected,
|_, _, _| {},
@@ -759,7 +765,7 @@ impl Component for SwitchField {
"Selected",
SwitchField::new(
"switch_field_selected",
"Enable notifications",
Some("Enable notifications"),
Some("Receive notifications when new messages arrive.".into()),
ToggleState::Selected,
|_, _, _| {},
@@ -775,7 +781,7 @@ impl Component for SwitchField {
"Default",
SwitchField::new(
"switch_field_default",
"Default color",
Some("Default color"),
Some("This uses the default switch color.".into()),
ToggleState::Selected,
|_, _, _| {},
@@ -786,7 +792,7 @@ impl Component for SwitchField {
"Accent",
SwitchField::new(
"switch_field_accent",
"Accent color",
Some("Accent color"),
Some("This uses the accent color scheme.".into()),
ToggleState::Selected,
|_, _, _| {},
@@ -802,7 +808,7 @@ impl Component for SwitchField {
"Disabled",
SwitchField::new(
"switch_field_disabled",
"Disabled field",
Some("Disabled field"),
Some("This field is disabled and cannot be toggled.".into()),
ToggleState::Selected,
|_, _, _| {},
@@ -817,7 +823,7 @@ impl Component for SwitchField {
"No Description",
SwitchField::new(
"switch_field_disabled",
"Disabled field",
Some("Disabled field"),
None,
ToggleState::Selected,
|_, _, _| {},
@@ -832,7 +838,7 @@ impl Component for SwitchField {
"Tooltip with Description",
SwitchField::new(
"switch_field_tooltip_with_desc",
"Nice Feature",
Some("Nice Feature"),
Some("Enable advanced configuration options.".into()),
ToggleState::Unselected,
|_, _, _| {},
@@ -844,7 +850,7 @@ impl Component for SwitchField {
"Tooltip without Description",
SwitchField::new(
"switch_field_tooltip_no_desc",
"Nice Feature",
Some("Nice Feature"),
None,
ToggleState::Selected,
|_, _, _| {},