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:
co-authored by
dino
Lukas Wirth
Mikayla Maki
Anthony Eid
Mikayla Maki
parent
4dbd186485
commit
ba937d16e7
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
|_, _, _| {},
|
||||
|
||||
Reference in New Issue
Block a user