Skip to content

Commit eace0ef

Browse files
authored
Merge pull request #25 from devrnt/feat/step-index
Feat/step index
2 parents c62e567 + 17f470b commit eace0ef

File tree

9 files changed

+155
-25
lines changed

9 files changed

+155
-25
lines changed

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ const Step1 = () => {
5050

5151
return (
5252
<>
53-
<button onClick={previousStep}>Previous ⏮️</button>
54-
<button onClick={nextStep}>Next ⏭</button>
53+
<button onClick={() => previousStep()}>Previous ⏮️</button>
54+
<button onClick={() => nextStep()}>Next ⏭</button>
5555
</>
5656
);
5757
};
@@ -112,14 +112,16 @@ Used to retrieve all methods and properties related to your wizard. Make sure `W
112112

113113
`handleStep` is used to attach a handler to the step, can either be `async` or a `sync` function. This function will be invoked when calling `nextStep`.
114114

115+
Provide an optional `stepIndex` to either `nextStep` or `previousStep` to overwrite the default "step-flow" behaviour.
116+
115117
**Remark** - You can't use `useWizard` in the same component where `Wizard` is used.
116118

117119
#### Methods
118120

119121
| name | type | description |
120122
| ------------ | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
121-
| nextStep | () => Promise<void> | Go to the next step |
122-
| previousStep | () => void | Go to the previous step |
123+
| nextStep | (stepIndex?: number) => Promise<void> | Go to the next step. Overwrite the default behaviour by providing a step index |
124+
| previousStep | (stepIndex?: number) => void | Go to the previous step. Overwrite the default behaviour by providing a step index |
123125
| handleStep | (handler: Handler) => void | Attach a callback that will be called when calling `nextStep`. `handler` can be either sync or async |
124126
| isLoading | boolean | \* Will reflect the handler promise state: will be `true` if the handler promise is pending and `false` when the handler is either fulfilled or rejected |
125127
| activeStep | number | The current active step of the wizard |
@@ -162,8 +164,8 @@ const Step1 = () => {
162164
<>
163165
<p>Step 1</p>
164166
{isLoading && <p>loading...</p>}
165-
<button onClick={previousStep}>Previous</button>
166-
<button onClick={nextStep}>Next</button>
167+
<button onClick={() => previousStep()}>Previous</button>
168+
<button onClick={() => nextStep()}>Next</button>
167169
<div>
168170
Has next step: {!isLastStep ? '' : ''}
169171
<br />
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import { styled } from 'goober';
22
import * as React from 'react';
33

4-
import { useWizard } from '../../dist';
5-
import { Button } from '../modules/common';
4+
import { useWizard } from '../../../dist';
5+
import { Button } from '../../modules/common';
66

7-
const Actions = styled('div')`
7+
export const Actions = styled('div')`
88
display: flex;
99
justify-content: center;
1010
margin: 1rem 0;
1111
gap: 1rem;
1212
flex-direction: row;
1313
`;
1414

15-
const Info = styled('div')`
15+
export const Info = styled('div')`
1616
display: flex;
1717
justify-content: center;
1818
flex-direction: column;
@@ -32,7 +32,7 @@ const Info = styled('div')`
3232
}
3333
`;
3434

35-
const Footer: React.FC = React.memo(() => {
35+
const Footer: React.FC = () => {
3636
const {
3737
nextStep,
3838
previousStep,
@@ -57,20 +57,20 @@ const Footer: React.FC = React.memo(() => {
5757
<Actions>
5858
<Button
5959
label="Previous"
60-
onClick={previousStep}
60+
onClick={() => previousStep()}
6161
disabled={isLoading || isFirstStep}
6262
>
6363
Previous
6464
</Button>
6565
<Button
6666
label="Next"
67-
onClick={nextStep}
67+
onClick={() => nextStep()}
6868
disabled={isLoading || isLastStep}
6969
/>
7070
</Actions>
7171
</code>
7272
</>
7373
);
74-
});
74+
};
7575

7676
export default Footer;
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as React from 'react';
2+
3+
import { useWizard } from '../../../dist';
4+
import { Button } from '../../modules/common';
5+
import { Actions, Info } from './footer';
6+
7+
const FooterCustomStepIndex: React.FC = () => {
8+
const {
9+
nextStep,
10+
previousStep,
11+
isLoading,
12+
activeStep,
13+
isLastStep,
14+
isFirstStep,
15+
} = useWizard();
16+
17+
return (
18+
<>
19+
<code>
20+
<Info>
21+
<p>Has previous step: {!isFirstStep ? '✅' : '⛔'}</p>
22+
<br />
23+
<p>Has next step: {!isLastStep ? '✅' : '⛔'} </p>
24+
<br />
25+
<p>
26+
Active step: {activeStep + 1} <br />
27+
</p>
28+
</Info>
29+
<Actions>
30+
<Button
31+
label="Previous"
32+
onClick={() => previousStep()}
33+
disabled={isLoading || isFirstStep}
34+
>
35+
Previous
36+
</Button>
37+
<Button
38+
label="Next"
39+
onClick={() => nextStep(2)}
40+
disabled={isLoading || isLastStep}
41+
/>
42+
</Actions>
43+
</code>
44+
</>
45+
);
46+
};
47+
48+
export default FooterCustomStepIndex;

playground/components/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { default as Step } from './step';
22
export { default as AsyncStep } from './asyncStep';
3-
export { default as Footer } from './footer';
3+
export { default as Footer } from './footer/footer';
4+
export { default as FooterStepIndex } from './footer/footerStepIndex';
45
export { default as AnimatedStep } from './animatedStep';
56
export { default as Tooltip } from './tooltip';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as React from 'react';
2+
3+
import { Wizard } from '../../../../dist';
4+
import { AsyncStep, FooterStepIndex, Step } from '../../../components';
5+
import Section from '../../common/section';
6+
7+
const CustomNextStepIndex: React.FC = () => {
8+
return (
9+
<Section
10+
title="Custom step index"
11+
description="With custom step index on next step"
12+
>
13+
<Wizard footer={<FooterStepIndex />}>
14+
<AsyncStep number={1} />
15+
<Step number={2} />
16+
<AsyncStep number={3} />
17+
<Step number={4} />
18+
</Wizard>
19+
</Section>
20+
);
21+
};
22+
23+
export default CustomNextStepIndex;

playground/modules/wizard/wizard.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { styled } from 'goober';
22
import * as React from 'react';
33

44
import AnimatedSection from './animated';
5+
import CustomNextStepIndex from './customNextStepIndex';
56
import ReactQuerySection from './reactQuery';
67
import SimpleSection from './simple';
78

@@ -17,6 +18,7 @@ const WizardModule = () => {
1718
<SimpleSection />
1819
<AnimatedSection />
1920
<ReactQuerySection />
21+
<CustomNextStepIndex />
2022
</Container>
2123
);
2224
};

src/types.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,18 @@ export type WizardProps = {
1010
};
1111

1212
export type WizardValues = {
13-
/** Go to the next step */
14-
nextStep: () => Promise<void>;
15-
/** Go to the previous step */
16-
previousStep: () => void;
13+
/**
14+
* Go to the next step
15+
*
16+
* @param stepIndex Overwrite the default behaviour by providing a step index
17+
*/
18+
nextStep: (stepIndex?: number) => Promise<void>;
19+
/**
20+
* Go to the previous step
21+
*
22+
* @param stepIndex Overwrite the default behaviour by providing a step index
23+
* */
24+
previousStep: (stepIndex?: number) => void;
1725
/**
1826
* Attach a callback that will be called when calling `nextStep()`
1927
*

src/wizard.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ const Wizard: React.FC<WizardProps> = React.memo(
1616
activeStep < React.Children.toArray(children).length - 1;
1717
hasPreviousStep.current = activeStep > 0;
1818

19-
const goToNextStep = React.useRef(() => {
19+
const goToNextStep = React.useRef((stepIndex?: number) => {
2020
if (hasNextStep.current) {
21-
setActiveStep((activeStep) => activeStep + 1);
21+
setActiveStep((activeStep) => stepIndex ?? activeStep + 1);
2222
}
2323
});
2424

25-
const previousStep = React.useRef(() => {
25+
const previousStep = React.useRef((step?: number) => {
2626
if (hasPreviousStep.current) {
27-
setActiveStep((activeStep) => activeStep - 1);
27+
setActiveStep((activeStep) => step ?? activeStep - 1);
2828
}
2929
});
3030

@@ -33,14 +33,14 @@ const Wizard: React.FC<WizardProps> = React.memo(
3333
nextStepHandler.current = handler;
3434
});
3535

36-
const doNextStep = React.useRef(async () => {
36+
const doNextStep = React.useRef(async (stepIndex?: number) => {
3737
if (hasNextStep.current && nextStepHandler.current) {
3838
try {
3939
setIsLoading(true);
4040
await nextStepHandler.current();
4141
setIsLoading(false);
4242
nextStepHandler.current = null;
43-
goToNextStep.current();
43+
goToNextStep.current(stepIndex);
4444
} catch (error) {
4545
setIsLoading(false);
4646
throw error;

test/useWizard.test.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,52 @@ describe('useWizard', () => {
9191
});
9292
});
9393

94+
test('should go to passed step index on next step', () => {
95+
const { result } = renderHook(() => useWizard(), {
96+
wrapper: ({ children }: { children: React.ReactNode }) => {
97+
return <Wizard>{children}</Wizard>;
98+
},
99+
initialProps: {
100+
children: (
101+
<>
102+
<p>step 1</p>
103+
<p>step 2</p>
104+
<p>step 3</p>
105+
</>
106+
),
107+
},
108+
});
109+
// Wait for an element to appear
110+
waitFor(() => {
111+
result.current.nextStep(2);
112+
expect(result.current.isFirstStep).toBe(false);
113+
expect(result.current.isLastStep).toBe(true);
114+
});
115+
});
116+
117+
test('should go to passed step index on previous step', () => {
118+
const { result } = renderHook(() => useWizard(), {
119+
wrapper: ({ children }: { children: React.ReactNode }) => {
120+
return <Wizard>{children}</Wizard>;
121+
},
122+
initialProps: {
123+
children: (
124+
<>
125+
<p>step 1</p>
126+
<p>step 2</p>
127+
<p>step 3</p>
128+
</>
129+
),
130+
},
131+
});
132+
// Wait for an element to appear
133+
waitFor(() => {
134+
result.current.previousStep(2);
135+
expect(result.current.isFirstStep).toBe(false);
136+
expect(result.current.isLastStep).toBe(true);
137+
});
138+
});
139+
94140
test('should not go to previous step if first step', () => {
95141
const { result } = renderUseWizardHook();
96142

0 commit comments

Comments
 (0)