Skip to content

Commit e31ef42

Browse files
committed
Resolve PR comments
1 parent d73e174 commit e31ef42

File tree

3 files changed

+132
-103
lines changed

3 files changed

+132
-103
lines changed

src/stepper/Stepper.tsx

Lines changed: 88 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import React, { useState, useEffect, Children, cloneElement, memo } from 'react';
1+
import React, {
2+
useState,
3+
useEffect,
4+
Children,
5+
cloneElement,
6+
memo,
7+
} from 'react';
28
import { StepperContext } from './UseStepper';
39
import { classNames } from '../common';
410

@@ -23,19 +29,11 @@ export type StepperProps = {
2329
* Defines the className of the entire stepper.
2430
*/
2531
stepperClassName?: string;
26-
/**
27-
* Defines the className of the header container.
28-
*/
29-
headerContainerClassNames?: string;
3032
/**
3133
* Defines the style of all StepHeader elements from the parent.
3234
* To style them independently, use className on the StepHeader element.
3335
*/
3436
headerClassName?: string;
35-
/**
36-
* Defines the className of the content container.
37-
*/
38-
contentContainerClassNames?: string;
3937
/**
4038
* Defines the style of all StepContent elements from the parent.
4139
* To style them independently, use className on the StepContent element.
@@ -47,86 +45,97 @@ export type StepperProps = {
4745
orientation?: 'horizontal' | 'vertical';
4846
};
4947

50-
export const Stepper = memo(({
51-
children,
52-
separator,
53-
selectedStep = 0,
54-
activeStepClassName,
55-
stepperClassName,
56-
headerClassName,
57-
contentClassName,
58-
orientation = 'horizontal',
59-
}: StepperProps) => {
60-
const [activeStep, setActiveStep] = useState(selectedStep);
61-
62-
useEffect(() => {
63-
setActiveStep(selectedStep);
64-
}, [selectedStep]);
48+
export const Stepper = memo(
49+
({
50+
children,
51+
separator,
52+
selectedStep = 0,
53+
activeStepClassName,
54+
stepperClassName,
55+
headerClassName,
56+
contentClassName,
57+
orientation = 'horizontal',
58+
}: StepperProps) => {
59+
const [activeStep, setActiveStep] = useState(selectedStep);
6560

66-
const onClickHandler = (index: number) => setActiveStep(index);
61+
useEffect(() => {
62+
setActiveStep(selectedStep);
63+
}, [selectedStep]);
6764

68-
const steps: JSX.Element[] = [];
65+
const onClickHandler = (index: number) => setActiveStep(index);
6966

70-
Children.forEach(children, (child, index) => {
71-
if (child.type.name === 'Step') {
72-
let stepHeader: JSX.Element | null = null;
73-
let stepContent: JSX.Element | null = null;
67+
const steps: JSX.Element[] = [];
7468

75-
Children.forEach(child.props.children, (child) => {
76-
if (child.type.name === 'StepHeader') {
77-
const headerClasses = classNames([
78-
{ 'dcx-active-step': index === activeStep },
79-
{ [`${activeStepClassName}`]: index === activeStep },
80-
headerClassName,
81-
]);
69+
Children.forEach(children, (child, index) => {
70+
if (child.type.name === 'Step') {
71+
let stepHeader: JSX.Element | null = null;
72+
let stepContent: JSX.Element | null = null;
8273

83-
stepHeader = cloneElement(child, {
84-
key: `header-${index}`,
85-
_index: index,
86-
headerClassName: headerClasses,
87-
'aria-selected': index === activeStep ? 'true' : 'false',
88-
'aria-posinset': index + 1,
89-
'aria-setsize': Children.count(children),
90-
tabIndex: index === activeStep ? '0' : '-1',
91-
onClick: () => onClickHandler(index),
92-
});
93-
} else if (child.type.name === 'StepContent') {
94-
stepContent = cloneElement(child, {
95-
key: `content-${index}`,
96-
className: contentClassName,
97-
visible: index === activeStep,
98-
});
99-
}
100-
});
74+
Children.forEach(child.props.children, (child) => {
75+
if (child.type.name === 'StepHeader') {
76+
const headerClasses = classNames([
77+
{ 'dcx-active-step': index === activeStep },
78+
{ [`${activeStepClassName}`]: index === activeStep },
79+
headerClassName,
80+
]);
10181

102-
if (stepHeader && stepContent) {
103-
steps.push(
104-
<div key={`step-${index}`} className="dcx-step">
105-
{stepHeader}
106-
{stepContent}
107-
</div>
108-
);
82+
stepHeader = cloneElement(child, {
83+
key: `header-${index}`,
84+
_index: index,
85+
headerClassName: headerClasses,
86+
'aria-selected': index === activeStep ? 'true' : 'false',
87+
'aria-posinset': index + 1,
88+
'aria-setsize': Children.count(children),
89+
tabIndex: index === activeStep ? '0' : '-1',
90+
onClick: () => onClickHandler(index),
91+
});
92+
} else if (child.type.name === 'StepContent') {
93+
stepContent = cloneElement(child, {
94+
key: `content-${index}`,
95+
className: contentClassName,
96+
visible: index === activeStep,
97+
});
98+
}
99+
});
109100

110-
if (separator && index < children.length - 1) {
101+
if (stepHeader && stepContent) {
111102
steps.push(
112-
cloneElement(separator, { key: `separator-${index}`, className: 'dcx-separator' })
103+
<div key={`step-${index}`} className="dcx-step">
104+
<div key={`header-${index}`} className="dcx-step-header">
105+
{stepHeader}
106+
</div>
107+
<div key={`content-${index}`} className="dcx-step-content">
108+
{stepContent}
109+
</div>
110+
</div>
113111
);
112+
113+
if (separator && index < children.length - 1) {
114+
steps.push(
115+
cloneElement(separator, {
116+
key: `separator-${index}`,
117+
className: 'dcx-separator',
118+
})
119+
);
120+
}
114121
}
115122
}
116-
}
117-
});
123+
});
118124

119-
const containerClasses = classNames([
120-
'dcx-stepper',
121-
orientation === 'horizontal' ? 'dcx-horizontal-stepper' : 'dcx-vertical-stepper',
122-
stepperClassName,
123-
]);
125+
const containerClasses = classNames([
126+
'dcx-stepper',
127+
orientation === 'horizontal'
128+
? 'dcx-horizontal-stepper'
129+
: 'dcx-vertical-stepper',
130+
stepperClassName,
131+
]);
124132

125-
return (
126-
<StepperContext.Provider value={{ activeStep, changeActiveStep: onClickHandler }}>
127-
<div className={containerClasses}>
128-
{steps}
129-
</div>
130-
</StepperContext.Provider>
131-
);
132-
});
133+
return (
134+
<StepperContext.Provider
135+
value={{ activeStep, changeActiveStep: onClickHandler }}
136+
>
137+
<div className={containerClasses}>{steps}</div>
138+
</StepperContext.Provider>
139+
);
140+
}
141+
);

src/stepper/__test__/Stepper.test.tsx

Lines changed: 43 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ describe('Stepper Component', () => {
5353
</Stepper>
5454
);
5555

56-
expect(screen.getByText('Step 2').parentElement).toHaveClass('dcx-step');
56+
expect(screen.getByText('Step 2').parentElement).toHaveClass(
57+
'dcx-step-header'
58+
);
5759
});
5860

5961
it('changes active step on header click', () => {
@@ -71,16 +73,14 @@ describe('Stepper Component', () => {
7173
);
7274

7375
fireEvent.click(screen.getByText('Step 2'));
74-
expect(screen.getByText('Step 2').parentElement).toHaveClass('dcx-step');
76+
expect(screen.getByText('Step 2').parentElement).toHaveClass(
77+
'dcx-step-header'
78+
);
7579
});
7680

7781
it('applies custom class names', () => {
7882
render(
79-
<Stepper
80-
stepperClassName="custom-stepper"
81-
headerContainerClassNames="custom-header-container"
82-
contentContainerClassNames="custom-content-container"
83-
>
83+
<Stepper stepperClassName="custom-stepper">
8484
<Step>
8585
<StepHeader>Step 1</StepHeader>
8686
<StepContent>Content 1</StepContent>
@@ -92,8 +92,12 @@ describe('Stepper Component', () => {
9292
</Stepper>
9393
);
9494

95-
expect(screen.getByText('Step 1').parentElement?.parentElement).toHaveClass('dcx-stepper custom-stepper');
96-
expect(screen.getByText('Content 1').parentElement?.parentElement).toHaveClass('dcx-stepper custom-stepper');
95+
expect(screen.getByText('Step 1').closest('.dcx-stepper')).toHaveClass(
96+
'custom-stepper'
97+
);
98+
expect(screen.getByText('Content 1').closest('.dcx-stepper')).toHaveClass(
99+
'custom-stepper'
100+
);
97101
});
98102

99103
it('renders custom separator', () => {
@@ -129,7 +133,7 @@ describe('Stepper Component', () => {
129133
</Stepper>
130134
);
131135

132-
expect(screen.getByText('Step 1').parentElement).toHaveClass('dcx-step');
136+
expect(screen.getByText('Step 1').parentElement).toHaveClass('dcx-step-header');
133137

134138
rerender(
135139
<Stepper selectedStep={1}>
@@ -144,7 +148,9 @@ describe('Stepper Component', () => {
144148
</Stepper>
145149
);
146150

147-
expect(screen.getByText('Step 2').parentElement).toHaveClass('dcx-step');
151+
expect(screen.getByText('Step 2').parentElement).toHaveClass(
152+
'dcx-step-header'
153+
);
148154
});
149155

150156
it('renders correctly with no steps', () => {
@@ -160,7 +166,7 @@ describe('Stepper Component', () => {
160166
<Step key={1}>
161167
<StepHeader>Step 1</StepHeader>
162168
<StepContent>Content 1</StepContent>
163-
</Step>
169+
</Step>,
164170
]}
165171
</Stepper>
166172
);
@@ -177,7 +183,7 @@ describe('Stepper Component', () => {
177183
{/* No StepHeader or StepContent */}
178184
<></>
179185
<></>
180-
</Step>
186+
</Step>,
181187
]}
182188
</Stepper>
183189
);
@@ -193,7 +199,7 @@ describe('Stepper Component', () => {
193199
<div>Non-Step Component</div>
194200
</Stepper>
195201
);
196-
202+
197203
expect(screen.queryByText('Hello')).not.toBeInTheDocument();
198204
expect(screen.queryByText('Non-Step Component')).not.toBeInTheDocument();
199205
});
@@ -212,7 +218,9 @@ describe('Stepper Component', () => {
212218
</Stepper>
213219
);
214220

215-
expect(screen.getByText('Step 2').parentElement).toHaveClass('dcx-step');
221+
expect(screen.getByText('Step 2').parentElement).toHaveClass(
222+
'dcx-step-header'
223+
);
216224
});
217225

218226
it('updates context when step header is clicked', () => {
@@ -230,7 +238,9 @@ describe('Stepper Component', () => {
230238
);
231239

232240
fireEvent.click(screen.getByText('Step 2'));
233-
expect(screen.getByText('Content 2').parentElement).toHaveClass('dcx-step');
241+
expect(screen.getByText('Content 2').parentElement).toHaveClass(
242+
'dcx-step-content'
243+
);
234244
});
235245

236246
it('applies activeStepClassName to the active step', () => {
@@ -247,12 +257,17 @@ describe('Stepper Component', () => {
247257
</Stepper>
248258
);
249259

250-
expect(screen.getByText('Step 2').parentElement).toHaveClass('dcx-step');
260+
expect(screen.getByText('Step 2').parentElement).toHaveClass(
261+
'dcx-step-header'
262+
);
251263
});
252264

253265
it('applies headerClassName and contentClassName to StepHeader and StepContent', () => {
254266
render(
255-
<Stepper headerClassName="custom-header" contentClassName="custom-content">
267+
<Stepper
268+
headerClassName="custom-header"
269+
contentClassName="custom-content"
270+
>
256271
<Step>
257272
<StepHeader>Step 1</StepHeader>
258273
<StepContent>Content 1</StepContent>
@@ -281,9 +296,11 @@ describe('Stepper Component', () => {
281296
</Step>
282297
</Stepper>
283298
);
284-
285-
expect(screen.getByText('Step 1').parentElement?.parentElement).toHaveClass('dcx-horizontal-stepper');
286-
299+
300+
expect(screen.getByText('Step 1').closest('.dcx-stepper')).toHaveClass(
301+
'dcx-horizontal-stepper'
302+
);
303+
287304
rerender(
288305
<Stepper orientation="vertical">
289306
<Step>
@@ -296,7 +313,9 @@ describe('Stepper Component', () => {
296313
</Step>
297314
</Stepper>
298315
);
299-
300-
expect(screen.getByText('Step 1').parentElement?.parentElement).toHaveClass('dcx-vertical-stepper');
316+
317+
expect(screen.getByText('Step 1').closest('.dcx-stepper')).toHaveClass(
318+
'dcx-vertical-stepper'
319+
);
301320
});
302-
});
321+
});

stories/Stepper/StepperDemo.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
box-sizing: border-box;
105105
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
106106
overflow: hidden;
107+
overflow-wrap: break-word;
107108
}
108109

109110
/* Button container */

0 commit comments

Comments
 (0)