Skip to content

Commit

Permalink
feat(ui): Add a reusable GuidedSteps component (#67939)
Browse files Browse the repository at this point in the history
  • Loading branch information
malwilley authored Apr 2, 2024
1 parent a3e767c commit dc111b1
Show file tree
Hide file tree
Showing 3 changed files with 403 additions and 0 deletions.
68 changes: 68 additions & 0 deletions static/app/components/guidedSteps/guidedSteps.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';

import {GuidedSteps} from 'sentry/components/guidedSteps/guidedSteps';

describe('GuidedSteps', function () {
it('can navigate through steps and shows previous ones as completed', async function () {
render(
<GuidedSteps>
<GuidedSteps.Step title="Step 1 Title">
This is the first step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
<GuidedSteps.Step title="Step 2 Title">
This is the second step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
<GuidedSteps.Step title="Step 3 Title">
This is the third step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
</GuidedSteps>
);

expect(screen.getByText('This is the first step.')).toBeInTheDocument();
expect(screen.queryByText('This is the second step.')).not.toBeInTheDocument();
expect(screen.queryByText('This is the third step.')).not.toBeInTheDocument();

await userEvent.click(screen.getByText('Next'));

expect(screen.queryByText('This is the first step.')).not.toBeInTheDocument();
expect(screen.getByText('This is the second step.')).toBeInTheDocument();
expect(screen.queryByText('This is the third step.')).not.toBeInTheDocument();

// First step is shown as completed
expect(
within(screen.getByTestId('guided-step-1')).getByTestId('icon-check-mark')
).toBeInTheDocument();
});

it('starts at the first incomplete step', function () {
render(
<GuidedSteps>
<GuidedSteps.Step title="Step 1 Title" isCompleted>
This is the first step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
<GuidedSteps.Step title="Step 2 Title" isCompleted={false}>
This is the second step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
<GuidedSteps.Step title="Step 3 Title" isCompleted={false}>
This is the third step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
</GuidedSteps>
);

// First step is shown as completed
expect(
within(screen.getByTestId('guided-step-1')).getByTestId('icon-check-mark')
).toBeInTheDocument();

// Second step is shown as active
expect(screen.queryByText('This is the first step.')).not.toBeInTheDocument();
expect(screen.getByText('This is the second step.')).toBeInTheDocument();
expect(screen.queryByText('This is the third step.')).not.toBeInTheDocument();
});
});
114 changes: 114 additions & 0 deletions static/app/components/guidedSteps/guidedSteps.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import {Fragment} from 'react';

import {Button} from 'sentry/components/button';
import {
GuidedSteps,
useGuidedStepsContext,
} from 'sentry/components/guidedSteps/guidedSteps';
import JSXNode from 'sentry/components/stories/jsxNode';
import SizingWindow from 'sentry/components/stories/sizingWindow';
import storyBook from 'sentry/stories/storyBook';

export default storyBook(GuidedSteps, story => {
story('Default', () => (
<Fragment>
<p>
To create a GuideStep component, you should use <JSXNode name="GuidedSteps" /> as
the container and <JSXNode name="GuidedSteps.Step" /> as direct children.
</p>
<p>
You have complete control over what to render in the step titles and step content.
You may use <JSXNode name="GuidedSteps.StepButtons" /> to render the default
back/next buttons, but can also render your own.
</p>
<SizingWindow display="block">
<GuidedSteps>
<GuidedSteps.Step title="Step 1 Title">
This is the first step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
<GuidedSteps.Step title="Step 2 Title">
This is the second step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
<GuidedSteps.Step title="Step 3 Title">
This is the third step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
</GuidedSteps>
</SizingWindow>
</Fragment>
));

story('Custom button behavior', () => {
function SkipToLastButton() {
const {setCurrentStep, totalSteps} = useGuidedStepsContext();
return (
<GuidedSteps.ButtonWrapper>
<Button size="sm" onClick={() => setCurrentStep(totalSteps)}>
Skip to Last Step
</Button>
</GuidedSteps.ButtonWrapper>
);
}

return (
<Fragment>
<p>
A hook is provided to access and control the step state:{' '}
<code>useGuidedStepsContext()</code>. This can be used to create step buttons
with custom behavior.
</p>
<SizingWindow display="block">
<GuidedSteps>
<GuidedSteps.Step title="Step 1 Title">
This is the first step.
<SkipToLastButton />
</GuidedSteps.Step>
<GuidedSteps.Step title="Step 2 Title">
This is the second step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
<GuidedSteps.Step title="Step 3 Title">
This is the third step.
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
</GuidedSteps>
</SizingWindow>
</Fragment>
);
});

story('Controlling completed state', () => {
return (
<Fragment>
<p>
By default, previous steps are considered completed. However, if the completed
state is known it can be controlled using the <code>isCompleted</code> property
on <JSXNode name="GuidedSteps.Step" />. The GuidedStep component will begin on
the first incomplete step.
</p>
<SizingWindow display="block">
<GuidedSteps>
<GuidedSteps.Step title="Step 1 Title" isCompleted>
Congrats, you finished the first step!
<GuidedSteps.StepButtons />
</GuidedSteps.Step>
<GuidedSteps.Step title="Step 2 Title" isCompleted={false}>
You haven't completed the second step yet, here's how you do it.
<GuidedSteps.ButtonWrapper>
<GuidedSteps.BackButton />
</GuidedSteps.ButtonWrapper>
</GuidedSteps.Step>
<GuidedSteps.Step title="Step 3 Title" isCompleted={false}>
You haven't completed the third step yet, here's how you do it.
<GuidedSteps.ButtonWrapper>
<GuidedSteps.BackButton />
</GuidedSteps.ButtonWrapper>
</GuidedSteps.Step>
</GuidedSteps>
</SizingWindow>
</Fragment>
);
});
});
Loading

0 comments on commit dc111b1

Please sign in to comment.