Onboarding · Flows and highlights: focus that holds as the flow turns, a tip that closes
navigation::Wizard keeps one focus handle for Next and Finish, so a second press on the step before last finishes; Back that reaches the first step, and a finished step picked by key, hand focus to Next. Popover::with_opener hands its content a close that gives focus back: Got it closes the hotspot's tip before the owner hides it, and the event details close before Edit and Delete run, so focus never lands on a panel that left the tree. The wizard's count and the setup checklist's tally name their selectors after their words, and the bar takes the count's own value.
This commit is contained in:
1 parent
eea99b8e41
commit
1879f6f08b
15 files changed
+180
-54
No files matched your search
+28
-17
@@ -10,7 +10,7 @@ use crate::{
|
||||
buttons::{Button, ButtonVariant},
|
||||
forms::{Choice, Run},
|
||||
motion,
|
||||
primitives::{FocusRing, Icon, IconName},
|
||||
primitives::{FocusRing, Icon, IconName, tab_stop},
|
||||
theme::{ActiveTheme, ControlSize, IconSize, TextSize},
|
||||
};
|
||||
|
||||
@@ -208,7 +208,7 @@ impl RenderOnce for Steps {
|
||||
}
|
||||
}
|
||||
|
||||
/// Steps, the current step's content, then Back and Next. Next becomes Finish on the last step.
|
||||
/// Steps, the current step's content, then Back and Next. Next becomes Finish on the last step, keeping its focus; focus moves to it when Back reaches the first step or a finished step is picked.
|
||||
#[derive(IntoElement)]
|
||||
pub struct Wizard {
|
||||
id: ElementId,
|
||||
@@ -293,7 +293,9 @@ impl RenderOnce for Wizard {
|
||||
on_step(to, window, cx);
|
||||
})
|
||||
};
|
||||
let advance = tab_stop((id.clone(), "advance").into(), true, window, cx);
|
||||
let (back, next, jump) = (step.clone(), step.clone(), step);
|
||||
let (to_advance, jumped) = (advance.clone(), advance.clone());
|
||||
let finish = {
|
||||
let id = id.clone();
|
||||
move |window: &mut Window, cx: &mut App| {
|
||||
@@ -315,17 +317,17 @@ impl RenderOnce for Wizard {
|
||||
},
|
||||
)
|
||||
});
|
||||
let advance = if current == last {
|
||||
Button::new((id.clone(), "finish"), "Finish")
|
||||
.primary()
|
||||
.disabled(!self.ready)
|
||||
.on_click(move |_, window, cx| finish(window, cx))
|
||||
} else {
|
||||
Button::new((id.clone(), "next"), "Next")
|
||||
.primary()
|
||||
.disabled(!self.ready)
|
||||
.on_click(move |_, window, cx| next(current + 1, window, cx))
|
||||
};
|
||||
let forward = Button::new(
|
||||
(id.clone(), "advance"),
|
||||
if current == last { "Finish" } else { "Next" },
|
||||
)
|
||||
.primary()
|
||||
.disabled(!self.ready)
|
||||
.focus_handle(&advance)
|
||||
.on_click(move |_, window, cx| match current == last {
|
||||
true => finish(window, cx),
|
||||
false => next(current + 1, window, cx),
|
||||
});
|
||||
div()
|
||||
.id(self.id.clone())
|
||||
.flex()
|
||||
@@ -334,8 +336,12 @@ impl RenderOnce for Wizard {
|
||||
.w_full()
|
||||
.when(!self.headless, |wizard| {
|
||||
wizard.child(
|
||||
Steps::new((id.clone(), "steps"), self.steps, current)
|
||||
.on_select(move |to, window, cx| jump(to, window, cx)),
|
||||
Steps::new((id.clone(), "steps"), self.steps, current).on_select(
|
||||
move |to, window, cx| {
|
||||
window.focus(&jumped);
|
||||
jump(to, window, cx)
|
||||
},
|
||||
),
|
||||
)
|
||||
})
|
||||
.children(content)
|
||||
@@ -348,9 +354,14 @@ impl RenderOnce for Wizard {
|
||||
Button::new((id.clone(), "back"), "Back")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.disabled(current == 0)
|
||||
.on_click(move |_, window, cx| back(current - 1, window, cx)),
|
||||
.on_click(move |_, window, cx| {
|
||||
if current == 1 {
|
||||
window.focus(&to_advance);
|
||||
}
|
||||
back(current - 1, window, cx)
|
||||
}),
|
||||
)
|
||||
.child(advance),
|
||||
.child(forward),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -155,6 +155,24 @@ fn the_wizard_walks_forward_back_and_finishes(cx: &mut TestAppContext) {
|
||||
assert!(view.read_with(cx, |view, _| view.done));
|
||||
}
|
||||
|
||||
/// On the last step the first stop is the first finished step; picking it hands focus to Next, so Enter walks on from there.
|
||||
#[gpui::test]
|
||||
fn a_picked_step_hands_focus_to_next(cx: &mut TestAppContext) {
|
||||
setup(cx);
|
||||
let (view, cx) = cx.add_window_view(|_, _| Flow {
|
||||
step: 2,
|
||||
done: false,
|
||||
});
|
||||
cx.update(|window, _| {
|
||||
window.blur();
|
||||
window.focus_next();
|
||||
});
|
||||
press("enter", cx);
|
||||
assert_eq!(view.read_with(cx, |view, _| view.step), 0);
|
||||
press("enter", cx);
|
||||
assert_eq!(view.read_with(cx, |view, _| view.step), 1);
|
||||
}
|
||||
|
||||
struct Docs {
|
||||
chosen: SharedString,
|
||||
clicked: Option<SharedString>,
|
||||
|
||||
Reference in new issue
Block a user