Skip to content

Commit

Permalink
fix(package): skip disabled step
Browse files Browse the repository at this point in the history
Signed-off-by: Rai Siqueira <[email protected]>
  • Loading branch information
raisiqueira committed Aug 18, 2023
1 parent dcdd224 commit 3c7e06d
Show file tree
Hide file tree
Showing 9 changed files with 130 additions and 9 deletions.
1 change: 1 addition & 0 deletions packages/headless-stepper/node_modules/react

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/headless-stepper/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "headless-stepper",
"description": "Production ready React hook to create awesome stepper components. Effortless to use, easy to customize.",
"version": "1.8.3",
"version": "1.9.0-beta.0",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Story, Meta } from '@storybook/react';
import { StoryFn, Meta } from '@storybook/react';
import { HeadlessStepper, HeadlessStepperProps } from './HeadlessStepper';

export default {
component: HeadlessStepper,
title: 'HeadlessStepper/useStepper',
} as Meta;

const Template: Story<HeadlessStepperProps> = (args) => (
const Template: StoryFn<HeadlessStepperProps> = (args) => (
<HeadlessStepper {...args} />
);

Expand Down
11 changes: 10 additions & 1 deletion packages/headless-stepper/src/lib/examples/HeadlessStepper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ export function HeadlessStepper(props: HeadlessStepperProps) {
<div>
<nav style={{ display: 'flex' }} {...stepperProps}>
{stepsProps?.map((step, index) => (
<ol key={index}>
<ol
key={index}
style={{
opacity: steps[index].disabled ? 0.6 : 1,
fontWeight: state.currentStep === index ? 'bold' : 'unset',
}}
>
<a {...step}>{steps[index].label}</a>
</ol>
))}
Expand All @@ -51,6 +57,9 @@ export function HeadlessStepper(props: HeadlessStepperProps) {
Next
</button>
<div className="bg-gray-400 w-100% h-1/2" {...progressProps} />
<pre>
<code>{JSON.stringify(state, null, 2)}</code>
</pre>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { StoryFn, Meta } from '@storybook/react';
import StepperComponent from './StepperComponent';

export default {
component: StepperComponent,
title: 'HeadlessStepper/StepperComponent',
} as Meta;

const Template: StoryFn = (args) => <StepperComponent {...args} />;

export const StepperComponentStory = Template.bind({});
StepperComponentStory.args = {};
19 changes: 19 additions & 0 deletions packages/headless-stepper/src/lib/examples/StepperComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// eslint-disable-next-line @nx/enforce-module-boundaries
import { Stepper, Step } from 'headless-stepper/components';
// TODO: Fix the line above. It should avoid circular dependencies.

const StepperComponent = () => {
return (
<Stepper currentStep={0} className="example">
<Step label="Step 1">Step 1 content</Step>

<Step label="Step 2" disabled>
Step 2 content
</Step>

<Step label="Step 3">Step 3 content</Step>
</Stepper>
);
};

export default StepperComponent;
51 changes: 51 additions & 0 deletions packages/headless-stepper/src/lib/hooks/useStepper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ const steps: Steps[] = [
},
];

const disabledSteps = [
{
label: 'step 01',
},
{
label: 'step 02',
disabled: true,
},
{
label: 'step 03',
},
];

describe('useStepper', () => {
it('should start with 0 as current step and dont have previous step', () => {
const { result } = renderHook(({ steps }) => useStepper({ steps }), {
Expand Down Expand Up @@ -85,4 +98,42 @@ describe('useStepper', () => {
expect(result?.current?.state?.currentStep).toBe(3);
expect(result?.current?.state?.hasPreviousStep).toBeTruthy();
});

it('should skip the disabled step when use the nextStep fn', async () => {
const { result } = renderHook(
({ steps, currentStep }) =>
useStepper({
steps,
currentStep,
}),
{ initialProps: { steps: disabledSteps, currentStep: 0 } }
);

expect(result?.current?.state?.currentStep).toBe(0);

act(() => {
result.current?.nextStep();
});

expect(result?.current?.state?.currentStep).toBe(2);
});

it('should skip the disabled step and go back to the previous step', async () => {
const { result } = renderHook(
({ steps, currentStep }) =>
useStepper({
steps,
currentStep, // start from the last step.
}),
{ initialProps: { steps: disabledSteps, currentStep: 2 } }
);

expect(result?.current?.state?.currentStep).toBe(2);

act(() => {
result.current?.prevStep();
});

expect(result?.current?.state?.currentStep).toBe(0);
});
});
36 changes: 32 additions & 4 deletions packages/headless-stepper/src/lib/hooks/useStepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,44 @@ const useStepper = ({
hasNextStep.current = _currentStep < steps.length - 1;

// Handlers
const guessNextAvailableStep = React.useCallback(
(currentIndex: number): number => {
const nextStep = steps[currentIndex + 1];
const isNextStepDisabled = nextStep?.disabled;
if (isNextStepDisabled) {
return guessNextAvailableStep(currentIndex + 1);
}
return currentIndex + 1;
},
[steps]
);

const guessPreviousAvailableStep = React.useCallback(
(currentIndex: number) => {
const previousStep = steps[currentIndex - 1];
const isPreviousStepDisabled = previousStep?.disabled;
if (isPreviousStepDisabled) {
return guessPreviousAvailableStep(currentIndex - 1);
}
return currentIndex - 1;
},
[steps]
);

const nextStep = React.useCallback(() => {
if (_currentStep === steps?.length - 1) return;

setCurrentStep((currentStep) => currentStep + 1);
}, [_currentStep, steps]);
setCurrentStep((currentStep) => {
return guessNextAvailableStep(currentStep);
});
}, [_currentStep, guessNextAvailableStep, steps?.length]);

const prevStep = React.useCallback(() => {
if (_currentStep === 0) return;
setCurrentStep((currentStep) => currentStep - 1);
}, [_currentStep]);
setCurrentStep((currentStep) => {
return guessPreviousAvailableStep(currentStep);
});
}, [_currentStep, guessPreviousAvailableStep]);

const _getNextArrayItem = (el: HTMLElement) => {
const currentIndex = stepElementsRef.current.indexOf(el);
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"skipDefaultLibCheck": true,
"baseUrl": ".",
"paths": {
"headless-stepper": ["packages/headless-stepper/src/index.ts"]
"headless-stepper": ["packages/headless-stepper/src/index.ts"],
"headless-stepper/components": ["packages/headless-stepper/components/index.ts"],
}
},
"exclude": ["node_modules", "tmp"]
Expand Down

0 comments on commit 3c7e06d

Please sign in to comment.