Skip to content

Commit cbe1c5f

Browse files
committed
initial Wizard component
1 parent 0546fd7 commit cbe1c5f

File tree

10 files changed

+133
-29
lines changed

10 files changed

+133
-29
lines changed

packages/wizard/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
21
# Wizard
32

43
![npm (scoped)](https://img.shields.io/npm/v/@leafygreen-ui/wizard.svg)
4+
55
#### [View on MongoDB.design](https://www.mongodb.design/component/wizard/live-example/)
66

77
## Installation
@@ -23,4 +23,3 @@ yarn add @leafygreen-ui/wizard
2323
```shell
2424
npm install @leafygreen-ui/wizard
2525
```
26-
Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
1-
21
import React from 'react';
3-
import { StoryFn } from '@storybook/react';
2+
import { StoryObj } from '@storybook/react';
43

54
import { Wizard } from '.';
65

76
export default {
87
title: 'Components/Wizard',
98
component: Wizard,
10-
}
11-
12-
const Template: StoryFn<typeof Wizard> = (props) => (
13-
<Wizard {...props} />
14-
);
9+
};
1510

16-
export const Basic = Template.bind({});
11+
export const LiveExample: StoryObj<typeof Wizard> = {
12+
parameters: {
13+
controls: {
14+
exclude: ['children', 'activeStep', 'onStepChange'],
15+
},
16+
},
17+
render: props => <Wizard {...props}></Wizard>,
18+
};
1719

20+
export const Controlled: StoryObj<typeof Wizard> = {
21+
parameters: {
22+
controls: {
23+
exclude: ['children', 'onStepChange'],
24+
},
25+
},
26+
render: ({ activeStep, ...props }) => {
27+
return (
28+
<Wizard
29+
activeStep={activeStep}
30+
// eslint-disable-next-line no-console
31+
onStepChange={x => console.log(`Set activeStep to ${x}`)}
32+
{...props}
33+
></Wizard>
34+
);
35+
},
36+
};
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
21
import React from 'react';
32
import { render } from '@testing-library/react';
43

54
import { Wizard } from '.';
65

76
describe('packages/wizard', () => {
8-
test('condition', () => {
9-
10-
})
11-
})
7+
test('condition', () => {});
8+
});
Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
21
import { css } from '@leafygreen-ui/emotion';
32

4-
export const baseStyles = css``;
3+
export const wizardContainerStyles = css`
4+
display: flex;
5+
flex-direction: column;
6+
gap: 24px;
7+
`;
8+
9+
export const stepContentStyles = css`
10+
flex: 1;
11+
min-height: 0; /* Allow content to shrink */
12+
`;
Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,75 @@
1-
import React from 'react';
1+
import React, { Children, cloneElement, isValidElement, useState } from 'react';
2+
3+
import { stepContentStyles, wizardContainerStyles } from './Wizard.styles';
24
import { WizardProps } from './Wizard.types';
35

4-
export function Wizard({}: WizardProps) {
5-
return <div>your content here</div>;
6+
export function Wizard({
7+
activeStep: controlledActiveStep,
8+
onStepChange,
9+
children,
10+
}: WizardProps) {
11+
// Internal state for uncontrolled mode
12+
const [internalActiveStep, setInternalActiveStep] = useState<number>(0);
13+
14+
// Use controlled prop if provided, otherwise use internal state
15+
const isControlled = controlledActiveStep !== undefined;
16+
const activeStep = isControlled ? controlledActiveStep : internalActiveStep;
17+
18+
// Handle step changes
19+
const handleStepChange = (newStep: number) => {
20+
if (!isControlled) {
21+
setInternalActiveStep(newStep);
22+
}
23+
onStepChange?.(newStep);
24+
};
25+
26+
// Filter children to separate steps from footer
27+
const childrenArray = Children.toArray(children);
28+
29+
// For now, we'll look for components with displayName ending in 'Step' or 'Footer'
30+
// This will be more precise once Wizard.Step and Wizard.Footer are implemented
31+
const stepChildren = childrenArray.filter(child => {
32+
if (isValidElement(child)) {
33+
const displayName = (child.type as any)?.displayName;
34+
return displayName && displayName.includes('Step');
35+
}
36+
37+
return false;
38+
});
39+
40+
const footerChild = childrenArray.find(child => {
41+
if (isValidElement(child)) {
42+
const displayName = (child.type as any)?.displayName;
43+
return displayName && displayName.includes('Footer');
44+
}
45+
46+
return false;
47+
});
48+
49+
// Get the current step to render
50+
const currentStep = stepChildren[activeStep] || null;
51+
52+
// Clone footer with step navigation handlers if it exists
53+
const clonedFooter =
54+
footerChild && isValidElement(footerChild)
55+
? cloneElement(footerChild as React.ReactElement<any>, {
56+
activeStep,
57+
totalSteps: stepChildren.length,
58+
onStepChange: handleStepChange,
59+
isControlled,
60+
})
61+
: null;
62+
63+
return (
64+
<div className={wizardContainerStyles}>
65+
<code>activeStep: {activeStep}</code>
66+
{/* Render current step */}
67+
<div className={stepContentStyles}>{currentStep}</div>
68+
69+
{/* Render footer */}
70+
{clonedFooter}
71+
</div>
72+
);
673
}
774

875
Wizard.displayName = 'Wizard';
Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,18 @@
1-
export interface WizardProps {}
1+
import { ComponentPropsWithRef, ReactNode } from 'react';
2+
3+
export interface WizardProps extends ComponentPropsWithRef<'div'> {
4+
/**
5+
* The current active step index (0-based). If provided, the component operates in controlled mode.
6+
*/
7+
activeStep?: number;
8+
9+
/**
10+
* Callback fired when the active step changes
11+
*/
12+
onStepChange?: (step: number) => void;
13+
14+
/**
15+
* The wizard steps and footer as children
16+
*/
17+
children: ReactNode;
18+
}
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
2-
export { Wizard } from './Wizard';
1+
export { Wizard } from './Wizard';
32
export { type WizardProps } from './Wizard.types';

packages/wizard/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { Wizard, type WizardProps } from './Wizard';
1+
export { Wizard, type WizardProps } from './Wizard';

packages/wizard/src/testing/getTestUtils.spec.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,5 @@ import { render } from '@testing-library/react';
44
import { Wizard } from '.';
55

66
describe('packages/wizard/getTestUtils', () => {
7-
test('condition', () => {
8-
9-
})
10-
})
7+
test('condition', () => {});
8+
});
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export interface TestUtilsReturnType {}
1+
export interface TestUtilsReturnType {}

0 commit comments

Comments
 (0)