Skip to content

Commit 2d480b7

Browse files
committed
[docs][stepper] Fix focus management in examples
1 parent 8d00775 commit 2d480b7

12 files changed

Lines changed: 352 additions & 20 deletions

docs/data/material/components/steppers/DotsMobileStepper.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,25 @@ export default function DotsMobileStepper() {
1717
setActiveStep((prevActiveStep) => prevActiveStep - 1);
1818
};
1919

20+
const nextButtonRef = React.useRef(null);
21+
const backButtonRef = React.useRef(null);
22+
const previousActiveStepRef = React.useRef(activeStep);
23+
24+
// Manage focus when the active step changes.
25+
React.useEffect(() => {
26+
const previousActiveStep = previousActiveStepRef.current;
27+
28+
if (activeStep === 0 && previousActiveStep === 1) {
29+
// If the user is going back to the first step, focus the Next button.
30+
nextButtonRef.current?.focus();
31+
} else if (activeStep === 5 && previousActiveStep === 4) {
32+
// If the user is going to the last step, focus the Back button.
33+
backButtonRef.current?.focus();
34+
}
35+
36+
previousActiveStepRef.current = activeStep;
37+
}, [activeStep]);
38+
2039
return (
2140
<MobileStepper
2241
variant="dots"
@@ -25,7 +44,12 @@ export default function DotsMobileStepper() {
2544
activeStep={activeStep}
2645
sx={{ maxWidth: 400, flexGrow: 1 }}
2746
nextButton={
28-
<Button size="small" onClick={handleNext} disabled={activeStep === 5}>
47+
<Button
48+
size="small"
49+
onClick={handleNext}
50+
disabled={activeStep === 5}
51+
ref={nextButtonRef}
52+
>
2953
Next
3054
{theme.direction === 'rtl' ? (
3155
<KeyboardArrowLeft />
@@ -35,7 +59,12 @@ export default function DotsMobileStepper() {
3559
</Button>
3660
}
3761
backButton={
38-
<Button size="small" onClick={handleBack} disabled={activeStep === 0}>
62+
<Button
63+
size="small"
64+
onClick={handleBack}
65+
disabled={activeStep === 0}
66+
ref={backButtonRef}
67+
>
3968
{theme.direction === 'rtl' ? (
4069
<KeyboardArrowRight />
4170
) : (

docs/data/material/components/steppers/DotsMobileStepper.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,25 @@ export default function DotsMobileStepper() {
1717
setActiveStep((prevActiveStep) => prevActiveStep - 1);
1818
};
1919

20+
const nextButtonRef = React.useRef<HTMLButtonElement>(null);
21+
const backButtonRef = React.useRef<HTMLButtonElement>(null);
22+
const previousActiveStepRef = React.useRef(activeStep);
23+
24+
// Manage focus when the active step changes.
25+
React.useEffect(() => {
26+
const previousActiveStep = previousActiveStepRef.current;
27+
28+
if (activeStep === 0 && previousActiveStep === 1) {
29+
// If the user is going back to the first step, focus the Next button.
30+
nextButtonRef.current?.focus();
31+
} else if (activeStep === 5 && previousActiveStep === 4) {
32+
// If the user is going to the last step, focus the Back button.
33+
backButtonRef.current?.focus();
34+
}
35+
36+
previousActiveStepRef.current = activeStep;
37+
}, [activeStep]);
38+
2039
return (
2140
<MobileStepper
2241
variant="dots"
@@ -25,7 +44,12 @@ export default function DotsMobileStepper() {
2544
activeStep={activeStep}
2645
sx={{ maxWidth: 400, flexGrow: 1 }}
2746
nextButton={
28-
<Button size="small" onClick={handleNext} disabled={activeStep === 5}>
47+
<Button
48+
size="small"
49+
onClick={handleNext}
50+
disabled={activeStep === 5}
51+
ref={nextButtonRef}
52+
>
2953
Next
3054
{theme.direction === 'rtl' ? (
3155
<KeyboardArrowLeft />
@@ -35,7 +59,12 @@ export default function DotsMobileStepper() {
3559
</Button>
3660
}
3761
backButton={
38-
<Button size="small" onClick={handleBack} disabled={activeStep === 0}>
62+
<Button
63+
size="small"
64+
onClick={handleBack}
65+
disabled={activeStep === 0}
66+
ref={backButtonRef}
67+
>
3968
{theme.direction === 'rtl' ? (
4069
<KeyboardArrowRight />
4170
) : (

docs/data/material/components/steppers/HorizontalLinearStepper.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,28 @@ export default function HorizontalLinearStepper() {
5454
setActiveStep(0);
5555
};
5656

57+
const previousActiveStepRef = React.useRef(activeStep);
58+
const resetButtonRef = React.useRef(null);
59+
const nextButtonRef = React.useRef(null);
60+
61+
// Manage focus when the active step changes.
62+
React.useEffect(() => {
63+
const previousActiveStep = previousActiveStepRef.current;
64+
65+
if (activeStep === steps.length) {
66+
// If the user has completed all steps and hits Finish, focus the Reset button.
67+
resetButtonRef.current?.focus();
68+
} else if (activeStep === 0 && previousActiveStep === steps.length) {
69+
// If the user has completed all steps and hits Reset, focus the Next button.
70+
nextButtonRef.current?.focus();
71+
} else if (isStepOptional(previousActiveStep) && !isStepOptional(activeStep)) {
72+
// If the user hits Skip and the next step is not optional, focus the Next button.
73+
nextButtonRef.current?.focus();
74+
}
75+
76+
previousActiveStepRef.current = activeStep;
77+
}, [activeStep]);
78+
5779
return (
5880
<Box sx={{ width: '100%' }}>
5981
<Stepper activeStep={activeStep}>
@@ -82,7 +104,9 @@ export default function HorizontalLinearStepper() {
82104
</Typography>
83105
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
84106
<Box sx={{ flex: '1 1 auto' }} />
85-
<Button onClick={handleReset}>Reset</Button>
107+
<Button onClick={handleReset} ref={resetButtonRef}>
108+
Reset
109+
</Button>
86110
</Box>
87111
</React.Fragment>
88112
) : (
@@ -103,7 +127,7 @@ export default function HorizontalLinearStepper() {
103127
Skip
104128
</Button>
105129
)}
106-
<Button onClick={handleNext}>
130+
<Button onClick={handleNext} ref={nextButtonRef}>
107131
{activeStep === steps.length - 1 ? 'Finish' : 'Next'}
108132
</Button>
109133
</Box>

docs/data/material/components/steppers/HorizontalLinearStepper.tsx

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,28 @@ export default function HorizontalLinearStepper() {
5454
setActiveStep(0);
5555
};
5656

57+
const previousActiveStepRef = React.useRef(activeStep);
58+
const resetButtonRef = React.useRef<HTMLButtonElement>(null);
59+
const nextButtonRef = React.useRef<HTMLButtonElement>(null);
60+
61+
// Manage focus when the active step changes.
62+
React.useEffect(() => {
63+
const previousActiveStep = previousActiveStepRef.current;
64+
65+
if (activeStep === steps.length) {
66+
// If the user has completed all steps and hits Finish, focus the Reset button.
67+
resetButtonRef.current?.focus();
68+
} else if (activeStep === 0 && previousActiveStep === steps.length) {
69+
// If the user has completed all steps and hits Reset, focus the Next button.
70+
nextButtonRef.current?.focus();
71+
} else if (isStepOptional(previousActiveStep) && !isStepOptional(activeStep)) {
72+
// If the user hits Skip and the next step is not optional, focus the Next button.
73+
nextButtonRef.current?.focus();
74+
}
75+
76+
previousActiveStepRef.current = activeStep;
77+
}, [activeStep]);
78+
5779
return (
5880
<Box sx={{ width: '100%' }}>
5981
<Stepper activeStep={activeStep}>
@@ -84,7 +106,9 @@ export default function HorizontalLinearStepper() {
84106
</Typography>
85107
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
86108
<Box sx={{ flex: '1 1 auto' }} />
87-
<Button onClick={handleReset}>Reset</Button>
109+
<Button onClick={handleReset} ref={resetButtonRef}>
110+
Reset
111+
</Button>
88112
</Box>
89113
</React.Fragment>
90114
) : (
@@ -105,7 +129,7 @@ export default function HorizontalLinearStepper() {
105129
Skip
106130
</Button>
107131
)}
108-
<Button onClick={handleNext}>
132+
<Button onClick={handleNext} ref={nextButtonRef}>
109133
{activeStep === steps.length - 1 ? 'Finish' : 'Next'}
110134
</Button>
111135
</Box>

docs/data/material/components/steppers/HorizontalNonLinearStepper.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,31 @@ export default function HorizontalNonLinearStepper() {
5959
setCompleted({});
6060
};
6161

62+
const resetButtonRef = React.useRef(null);
63+
const nextButtonRef = React.useRef(null);
64+
const previousActiveStepRef = React.useRef(activeStep);
65+
66+
// Manage focus when the completed steps change.
67+
React.useEffect(() => {
68+
if (allStepsCompleted()) {
69+
// If the user has completed all steps and hits Finish, focus the Reset button.
70+
resetButtonRef.current?.focus();
71+
} else if (Object.keys(completed).length === 0) {
72+
// If the user has completed all steps and hits Reset, focus the Next button.
73+
nextButtonRef.current?.focus();
74+
}
75+
}, [completed]);
76+
77+
// Manage focus when the active step changes.
78+
React.useEffect(() => {
79+
if (activeStep === 0 && previousActiveStepRef.current === 1) {
80+
// If the user navigated to first step via Back button, focus the Next button.
81+
nextButtonRef.current?.focus();
82+
}
83+
84+
previousActiveStepRef.current = activeStep;
85+
}, [activeStep]);
86+
6287
return (
6388
<Box sx={{ width: '100%' }}>
6489
<Stepper nonLinear activeStep={activeStep}>
@@ -82,7 +107,9 @@ export default function HorizontalNonLinearStepper() {
82107
</Typography>
83108
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
84109
<Box sx={{ flex: '1 1 auto' }} />
85-
<Button onClick={handleReset}>Reset</Button>
110+
<Button onClick={handleReset} ref={resetButtonRef}>
111+
Reset
112+
</Button>
86113
</Box>
87114
</React.Fragment>
88115
) : (
@@ -100,7 +127,7 @@ export default function HorizontalNonLinearStepper() {
100127
Back
101128
</Button>
102129
<Box sx={{ flex: '1 1 auto' }} />
103-
<Button onClick={handleNext} sx={{ mr: 1 }}>
130+
<Button onClick={handleNext} sx={{ mr: 1 }} ref={nextButtonRef}>
104131
Next
105132
</Button>
106133
{activeStep !== steps.length &&

docs/data/material/components/steppers/HorizontalNonLinearStepper.tsx

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,31 @@ export default function HorizontalNonLinearStepper() {
6161
setCompleted({});
6262
};
6363

64+
const resetButtonRef = React.useRef<HTMLButtonElement>(null);
65+
const nextButtonRef = React.useRef<HTMLButtonElement>(null);
66+
const previousActiveStepRef = React.useRef(activeStep);
67+
68+
// Manage focus when the completed steps change.
69+
React.useEffect(() => {
70+
if (allStepsCompleted()) {
71+
// If the user has completed all steps and hits Finish, focus the Reset button.
72+
resetButtonRef.current?.focus();
73+
} else if (Object.keys(completed).length === 0) {
74+
// If the user has completed all steps and hits Reset, focus the Next button.
75+
nextButtonRef.current?.focus();
76+
}
77+
}, [completed]);
78+
79+
// Manage focus when the active step changes.
80+
React.useEffect(() => {
81+
if (activeStep === 0 && previousActiveStepRef.current === 1) {
82+
// If the user navigated to first step via Back button, focus the Next button.
83+
nextButtonRef.current?.focus();
84+
}
85+
86+
previousActiveStepRef.current = activeStep;
87+
}, [activeStep]);
88+
6489
return (
6590
<Box sx={{ width: '100%' }}>
6691
<Stepper nonLinear activeStep={activeStep}>
@@ -84,7 +109,9 @@ export default function HorizontalNonLinearStepper() {
84109
</Typography>
85110
<Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
86111
<Box sx={{ flex: '1 1 auto' }} />
87-
<Button onClick={handleReset}>Reset</Button>
112+
<Button onClick={handleReset} ref={resetButtonRef}>
113+
Reset
114+
</Button>
88115
</Box>
89116
</React.Fragment>
90117
) : (
@@ -102,7 +129,7 @@ export default function HorizontalNonLinearStepper() {
102129
Back
103130
</Button>
104131
<Box sx={{ flex: '1 1 auto' }} />
105-
<Button onClick={handleNext} sx={{ mr: 1 }}>
132+
<Button onClick={handleNext} sx={{ mr: 1 }} ref={nextButtonRef}>
106133
Next
107134
</Button>
108135
{activeStep !== steps.length &&

docs/data/material/components/steppers/ProgressMobileStepper.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,25 @@ export default function ProgressMobileStepper() {
1717
setActiveStep((prevActiveStep) => prevActiveStep - 1);
1818
};
1919

20+
const nextButtonRef = React.useRef(null);
21+
const backButtonRef = React.useRef(null);
22+
const previousActiveStepRef = React.useRef(activeStep);
23+
24+
// Manage focus when the active step changes.
25+
React.useEffect(() => {
26+
const previousActiveStep = previousActiveStepRef.current;
27+
28+
if (activeStep === 0 && previousActiveStep === 1) {
29+
// If the user is going back to the first step, focus the Next button.
30+
nextButtonRef.current?.focus();
31+
} else if (activeStep === 5 && previousActiveStep === 4) {
32+
// If the user is going to the last step, focus the Back button.
33+
backButtonRef.current?.focus();
34+
}
35+
36+
previousActiveStepRef.current = activeStep;
37+
}, [activeStep]);
38+
2039
return (
2140
<MobileStepper
2241
variant="progress"
@@ -25,7 +44,12 @@ export default function ProgressMobileStepper() {
2544
activeStep={activeStep}
2645
sx={{ maxWidth: 400, flexGrow: 1 }}
2746
nextButton={
28-
<Button size="small" onClick={handleNext} disabled={activeStep === 5}>
47+
<Button
48+
size="small"
49+
onClick={handleNext}
50+
disabled={activeStep === 5}
51+
ref={nextButtonRef}
52+
>
2953
Next
3054
{theme.direction === 'rtl' ? (
3155
<KeyboardArrowLeft />
@@ -35,7 +59,12 @@ export default function ProgressMobileStepper() {
3559
</Button>
3660
}
3761
backButton={
38-
<Button size="small" onClick={handleBack} disabled={activeStep === 0}>
62+
<Button
63+
size="small"
64+
onClick={handleBack}
65+
disabled={activeStep === 0}
66+
ref={backButtonRef}
67+
>
3968
{theme.direction === 'rtl' ? (
4069
<KeyboardArrowRight />
4170
) : (

0 commit comments

Comments
 (0)