@patternfly/react-core 4.225.11 → 4.226.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/esm/components/CalendarMonth/CalendarMonth.d.ts +3 -1
  3. package/dist/esm/components/CalendarMonth/CalendarMonth.d.ts.map +1 -1
  4. package/dist/esm/components/CalendarMonth/CalendarMonth.js +9 -6
  5. package/dist/esm/components/CalendarMonth/CalendarMonth.js.map +1 -1
  6. package/dist/esm/components/Wizard/Wizard.d.ts +4 -0
  7. package/dist/esm/components/Wizard/Wizard.d.ts.map +1 -1
  8. package/dist/esm/components/Wizard/Wizard.js +28 -13
  9. package/dist/esm/components/Wizard/Wizard.js.map +1 -1
  10. package/dist/js/components/CalendarMonth/CalendarMonth.d.ts +3 -1
  11. package/dist/js/components/CalendarMonth/CalendarMonth.d.ts.map +1 -1
  12. package/dist/js/components/CalendarMonth/CalendarMonth.js +9 -6
  13. package/dist/js/components/CalendarMonth/CalendarMonth.js.map +1 -1
  14. package/dist/js/components/Wizard/Wizard.d.ts +4 -0
  15. package/dist/js/components/Wizard/Wizard.d.ts.map +1 -1
  16. package/dist/js/components/Wizard/Wizard.js +28 -13
  17. package/dist/js/components/Wizard/Wizard.js.map +1 -1
  18. package/dist/umd/react-core.css +4 -3
  19. package/dist/umd/react-core.js +37 -19
  20. package/dist/umd/react-core.min.css +38 -37
  21. package/dist/umd/react-core.min.js +2 -2
  22. package/package.json +6 -6
  23. package/src/components/CalendarMonth/CalendarMonth.tsx +22 -5
  24. package/src/components/CalendarMonth/examples/CalendarMonth.md +0 -5
  25. package/src/components/CalendarMonth/examples/CalendarMonthSelectableDate.tsx +6 -1
  26. package/src/components/Pagination/examples/Pagination.md +14 -14
  27. package/src/components/Toolbar/examples/Toolbar.md +2 -2
  28. package/src/components/Truncate/__tests__/Truncate.test.tsx +143 -4
  29. package/src/components/Truncate/__tests__/__snapshots__/Truncate.test.tsx.snap +34 -29
  30. package/src/components/Wizard/Wizard.tsx +34 -12
  31. package/src/components/Wizard/__tests__/Wizard.test.tsx +59 -0
  32. package/src/components/Wizard/examples/Wizard.md +9 -19
  33. package/src/components/CalendarMonth/examples/CalendarMonthDefault.tsx +0 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "4.225.11",
3
+ "version": "4.226.2",
4
4
  "description": "This library provides a set of common React components for use with the PatternFly reference implementation.",
5
5
  "main": "dist/js/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -34,16 +34,16 @@
34
34
  "generate": "node scripts/copyStyles.js"
35
35
  },
36
36
  "dependencies": {
37
- "@patternfly/react-icons": "^4.76.11",
38
- "@patternfly/react-styles": "^4.75.11",
39
- "@patternfly/react-tokens": "^4.77.11",
37
+ "@patternfly/react-icons": "^4.77.2",
38
+ "@patternfly/react-styles": "^4.76.2",
39
+ "@patternfly/react-tokens": "^4.78.2",
40
40
  "focus-trap": "6.9.2",
41
41
  "react-dropzone": "9.0.0",
42
42
  "tippy.js": "5.1.2",
43
43
  "tslib": "^2.0.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@patternfly/patternfly": "4.204.2",
46
+ "@patternfly/patternfly": "4.204.3",
47
47
  "@rollup/plugin-commonjs": "^21.0.0",
48
48
  "@rollup/plugin-node-resolve": "^13.0.0",
49
49
  "@rollup/plugin-replace": "^3.0.0",
@@ -60,5 +60,5 @@
60
60
  "react": "^16.8.0 || ^17.0.0",
61
61
  "react-dom": "^16.8.0 || ^17.0.0"
62
62
  },
63
- "gitHead": "aaeb37eb17dd5180aaa6ac3548fd4ac52c2a6eb7"
63
+ "gitHead": "fb67d6b2dfbfd2b4080e1313101f4d663a0a09c3"
64
64
  }
@@ -57,6 +57,11 @@ export interface CalendarProps extends CalendarFormat, Omit<React.HTMLProps<HTML
57
57
  onSelectToggle?: (open: boolean) => void;
58
58
  /** Flag to set browser focus on the passed date **/
59
59
  isDateFocused?: boolean;
60
+ /** Callback when month or year is changed */
61
+ onMonthChange?: (
62
+ newDate?: Date,
63
+ event?: React.MouseEvent | React.ChangeEvent | React.FormEvent<HTMLInputElement>
64
+ ) => void;
60
65
  }
61
66
 
62
67
  // Must be numeric given current header design
@@ -114,6 +119,7 @@ export const CalendarMonth = ({
114
119
  validators = [() => true],
115
120
  className,
116
121
  onSelectToggle = () => {},
122
+ onMonthChange = () => {},
117
123
  rangeStart,
118
124
  prevMonthAriaLabel = 'Previous month',
119
125
  nextMonthAriaLabel = 'Next month',
@@ -162,10 +168,11 @@ export const CalendarMonth = ({
162
168
  }
163
169
  }, [focusedDate, isDateFocused, focusedDateValidated, focusRef]);
164
170
 
165
- const onMonthClick = (newDate: Date) => {
171
+ const onMonthClick = (newDate: Date, ev: React.MouseEvent) => {
166
172
  setFocusedDate(newDate);
167
173
  setHoveredDate(newDate);
168
174
  setShouldFocus(false);
175
+ onMonthChange(newDate, ev);
169
176
  };
170
177
 
171
178
  const onKeyDown = (ev: React.KeyboardEvent<HTMLTableSectionElement>) => {
@@ -222,7 +229,11 @@ export const CalendarMonth = ({
222
229
  <div className={css(styles.calendarMonth, className)} {...props}>
223
230
  <div className={styles.calendarMonthHeader}>
224
231
  <div className={css(styles.calendarMonthHeaderNavControl, styles.modifiers.prevMonth)}>
225
- <Button variant="plain" aria-label={prevMonthAriaLabel} onClick={() => onMonthClick(prevMonth)}>
232
+ <Button
233
+ variant="plain"
234
+ aria-label={prevMonthAriaLabel}
235
+ onClick={(ev: React.MouseEvent) => onMonthClick(prevMonth, ev)}
236
+ >
226
237
  <AngleLeftIcon aria-hidden={true} />
227
238
  </Button>
228
239
  </div>
@@ -240,7 +251,7 @@ export const CalendarMonth = ({
240
251
  setIsSelectOpen(!isSelectOpen);
241
252
  onSelectToggle(!isSelectOpen);
242
253
  }}
243
- onSelect={(_ev, monthNum) => {
254
+ onSelect={(ev, monthNum) => {
244
255
  // When we put CalendarMonth in a Popover we want the Popover's onDocumentClick
245
256
  // to see the SelectOption as a child so it doesn't close the Popover.
246
257
  setTimeout(() => {
@@ -251,6 +262,7 @@ export const CalendarMonth = ({
251
262
  setFocusedDate(newDate);
252
263
  setHoveredDate(newDate);
253
264
  setShouldFocus(false);
265
+ onMonthChange(newDate, ev);
254
266
  }, 0);
255
267
  }}
256
268
  variant="single"
@@ -268,18 +280,23 @@ export const CalendarMonth = ({
268
280
  aria-label={yearInputAriaLabel}
269
281
  type="number"
270
282
  value={yearFormatted}
271
- onChange={year => {
283
+ onChange={(year: string, ev: React.FormEvent<HTMLInputElement>) => {
272
284
  const newDate = new Date(focusedDate);
273
285
  newDate.setFullYear(+year);
274
286
  setFocusedDate(newDate);
275
287
  setHoveredDate(newDate);
276
288
  setShouldFocus(false);
289
+ onMonthChange(newDate, ev);
277
290
  }}
278
291
  />
279
292
  </div>
280
293
  </InputGroup>
281
294
  <div className={css(styles.calendarMonthHeaderNavControl, styles.modifiers.nextMonth)}>
282
- <Button variant="plain" aria-label={nextMonthAriaLabel} onClick={() => onMonthClick(nextMonth)}>
295
+ <Button
296
+ variant="plain"
297
+ aria-label={nextMonthAriaLabel}
298
+ onClick={(ev: React.MouseEvent) => onMonthClick(nextMonth, ev)}
299
+ >
283
300
  <AngleRightIcon aria-hidden={true} />
284
301
  </Button>
285
302
  </div>
@@ -8,11 +8,6 @@ beta: true
8
8
 
9
9
  ## Examples
10
10
 
11
- ### Default
12
-
13
- ```ts file="./CalendarMonthDefault.tsx"
14
- ```
15
-
16
11
  ### Selectable date
17
12
 
18
13
  ```ts file="./CalendarMonthSelectableDate.tsx"
@@ -4,10 +4,15 @@ import { CalendarMonth } from '@patternfly/react-core';
4
4
  export const CalendarMonthSelectableDate: React.FunctionComponent = () => {
5
5
  const [date, setDate] = React.useState(new Date(2020, 10, 24));
6
6
 
7
+ const onMonthChange = (newDate: Date) => {
8
+ // eslint-disable-next-line no-console
9
+ console.log(`updated month: ${newDate.getMonth()}, updated year: ${newDate.getFullYear()}`);
10
+ };
11
+
7
12
  return (
8
13
  <React.Fragment>
9
14
  <pre>Selected date: {date.toString()}</pre>
10
- <CalendarMonth date={date} onChange={setDate} />
15
+ <CalendarMonth date={date} onChange={setDate} onMonthChange={onMonthChange} />
11
16
  </React.Fragment>
12
17
  );
13
18
  };
@@ -28,9 +28,9 @@ class PaginationTop extends React.Component {
28
28
  });
29
29
  };
30
30
 
31
- this.onPerPageSelect = (_event, perPage) => {
31
+ this.onPerPageSelect = (_event, perPage, page) => {
32
32
  this.setState({
33
- perPage
33
+ page, perPage
34
34
  });
35
35
  };
36
36
  }
@@ -68,9 +68,9 @@ class PaginationBottom extends React.Component {
68
68
  page: pageNumber
69
69
  });
70
70
  };
71
- this.onPerPageSelect = (_event, perPage) => {
71
+ this.onPerPageSelect = (_event, perPage, page) => {
72
72
  this.setState({
73
- perPage
73
+ page, perPage
74
74
  });
75
75
  };
76
76
  }
@@ -149,9 +149,9 @@ class PaginationDisabled extends React.Component {
149
149
  });
150
150
  };
151
151
 
152
- this.onPerPageSelect = (_event, perPage) => {
152
+ this.onPerPageSelect = (_event, perPage, page) => {
153
153
  this.setState({
154
- perPage
154
+ page, perPage
155
155
  });
156
156
  };
157
157
  }
@@ -193,9 +193,9 @@ class PaginationTop extends React.Component {
193
193
  });
194
194
  };
195
195
 
196
- this.onPerPageSelect = (_event, perPage) => {
196
+ this.onPerPageSelect = (_event, perPage, page) => {
197
197
  this.setState({
198
- perPage
198
+ page, perPage
199
199
  });
200
200
  };
201
201
  }
@@ -236,9 +236,9 @@ class PaginationTop extends React.Component {
236
236
  });
237
237
  };
238
238
 
239
- this.onPerPageSelect = (_event, perPage) => {
239
+ this.onPerPageSelect = (_event, perPage, page) => {
240
240
  this.setState({
241
- perPage
241
+ page, perPage
242
242
  });
243
243
  };
244
244
  }
@@ -279,9 +279,9 @@ class PaginationTop extends React.Component {
279
279
  });
280
280
  };
281
281
 
282
- this.onPerPageSelect = (_event, perPage) => {
282
+ this.onPerPageSelect = (_event, perPage, page) => {
283
283
  this.setState({
284
- perPage
284
+ page, perPage
285
285
  });
286
286
  };
287
287
  }
@@ -330,9 +330,9 @@ class PaginationSticky extends React.Component {
330
330
  });
331
331
  };
332
332
 
333
- this.onPerPageSelect = (_event, perPage) => {
333
+ this.onPerPageSelect = (_event, perPage, page) => {
334
334
  this.setState({
335
- perPage
335
+ page, perPage
336
336
  });
337
337
  };
338
338
  }
@@ -1169,9 +1169,9 @@ class ToolbarStacked extends React.Component {
1169
1169
  });
1170
1170
  };
1171
1171
 
1172
- this.onPerPageSelect = (_event, perPage) => {
1172
+ this.onPerPageSelect = (_event, perPage, page) => {
1173
1173
  this.setState({
1174
- perPage
1174
+ page, perPage
1175
1175
  });
1176
1176
  };
1177
1177
 
@@ -1,7 +1,83 @@
1
1
  import React from 'react';
2
- import { render } from '@testing-library/react';
2
+ import { render, screen } from '@testing-library/react';
3
3
  import { Truncate } from '../Truncate';
4
4
 
5
+ jest.mock('../../Tooltip', () => ({
6
+ Tooltip: ({ content, position, children, ...props }) => (
7
+ <div data-testid="Tooltip-mock" {...props}>
8
+ <div data-testid="Tooltip-mock-content-container">Test {content}</div>
9
+ <p>{`position: ${position}`}</p>
10
+ {children}
11
+ </div>
12
+ )
13
+ }));
14
+
15
+ test('renders with class pf-c-truncate', () => {
16
+ render(
17
+ <Truncate
18
+ content={''}
19
+ aria-label='test-id'
20
+ />
21
+ );
22
+
23
+ const test = screen.getByLabelText('test-id');
24
+
25
+ expect(test).toHaveClass('pf-c-truncate');
26
+ });
27
+
28
+ test('renders with custom class name passed via prop', () => {
29
+ render(
30
+ <Truncate
31
+ content={''}
32
+ aria-label='test-id'
33
+ className='custom-classname'
34
+ />
35
+ );
36
+
37
+ const test = screen.getByLabelText('test-id');
38
+
39
+ expect(test).toHaveClass('custom-classname');
40
+ });
41
+
42
+ test('renders truncate with empty content', () => {
43
+ render(
44
+ <Truncate
45
+ content={''}
46
+ aria-label='test-id'
47
+ />
48
+ );
49
+
50
+ const test = screen.getByLabelText('test-id');
51
+
52
+ expect(test).toHaveTextContent('');
53
+ });
54
+
55
+ test('renders truncate with content', () => {
56
+ render(
57
+ <Truncate
58
+ content={'Test'}
59
+ aria-label='test-id'
60
+ />
61
+ );
62
+
63
+ const test = screen.getByLabelText('test-id');
64
+
65
+ expect(test).toHaveTextContent('Test');
66
+ });
67
+
68
+ test('only renders pf-c-truncate__start with default position', () => {
69
+ render(
70
+ <Truncate
71
+ content={'Testing truncate content'}
72
+ />
73
+ );
74
+
75
+ const start = screen.getByText('Testing truncate content');
76
+
77
+ expect(start).toHaveClass('pf-c-truncate__start');
78
+ expect(start).not.toHaveClass('pf-c-truncate__end');
79
+ });
80
+
5
81
  test('renders default truncation', () => {
6
82
  const { asFragment } = render(
7
83
  <Truncate content={'Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.'} />
@@ -9,7 +85,7 @@ test('renders default truncation', () => {
9
85
  expect(asFragment()).toMatchSnapshot();
10
86
  });
11
87
 
12
- test('renders start truncation', () => {
88
+ test('renders start truncation with &lrm; at end', () => {
13
89
  const { asFragment } = render(
14
90
  <Truncate
15
91
  content={'Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.'}
@@ -20,11 +96,74 @@ test('renders start truncation', () => {
20
96
  });
21
97
 
22
98
  test('renders middle truncation', () => {
23
- const { asFragment } = render(
99
+ render(
24
100
  <Truncate
25
101
  content={'Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.'}
26
102
  position={'middle'}
27
103
  />
28
104
  );
29
- expect(asFragment()).toMatchSnapshot();
105
+
106
+ const start = screen.getByText('Vestibulum interdum risus et enim faucibus, sit amet molestie est ac');
107
+
108
+ expect(start).toHaveClass('pf-c-truncate__start');
109
+
110
+ const end = screen.getByText('cumsan.');
111
+
112
+ expect(end).toHaveClass('pf-c-truncate__end');
113
+ });
114
+
115
+ test('renders different content when trailingNumChars is passed with middle truncate', () => {
116
+ render(
117
+ <Truncate
118
+ content={'Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.'}
119
+ trailingNumChars={3}
120
+ position='middle'
121
+ />
122
+ );
123
+
124
+ const start = screen.getByText('Vestibulum interdum risus et enim faucibus, sit amet molestie est accums');
125
+
126
+ expect(start).toHaveClass('pf-c-truncate__start');
127
+
128
+ const end = screen.getByText('an.');
129
+
130
+ expect(end).toHaveClass('pf-c-truncate__end');
131
+ });
132
+
133
+ test('renders tooltip position', () => {
134
+ render(
135
+ <Truncate
136
+ content={''}
137
+ tooltipPosition='top'
138
+ />
139
+ );
140
+
141
+ const input = screen.getByText('position: top');
142
+
143
+ expect(input).toBeVisible();
144
+ });
145
+
146
+ test('renders tooltip content', () => {
147
+ render(
148
+ <Truncate
149
+ content={'Another Tooltip'}
150
+ tooltipPosition='top'
151
+ />
152
+ );
153
+
154
+ const input = screen.getByText('Test Another Tooltip');
155
+
156
+ expect(input).toBeVisible();
157
+ });
158
+
159
+ test('renders with inherited element props spread to the component', () => {
160
+ render(
161
+ <Truncate
162
+ content={'Test'}
163
+ data-testid="test-id"
164
+ aria-label="labelling-id"
165
+ />
166
+ );
167
+
168
+ expect(screen.getByTestId('test-id')).toHaveAccessibleName('labelling-id');
30
169
  });
@@ -2,47 +2,52 @@
2
2
 
3
3
  exports[`renders default truncation 1`] = `
4
4
  <DocumentFragment>
5
- <span
6
- class="pf-c-truncate"
5
+ <div
6
+ data-testid="Tooltip-mock"
7
7
  >
8
- <span
9
- class="pf-c-truncate__start"
8
+ <div
9
+ data-testid="Tooltip-mock-content-container"
10
10
  >
11
- Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.
12
- </span>
13
- </span>
14
- </DocumentFragment>
15
- `;
16
-
17
- exports[`renders middle truncation 1`] = `
18
- <DocumentFragment>
19
- <span
20
- class="pf-c-truncate"
21
- >
11
+ Test Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.
12
+ </div>
13
+ <p>
14
+ position: top
15
+ </p>
22
16
  <span
23
- class="pf-c-truncate__start"
17
+ class="pf-c-truncate"
24
18
  >
25
- Vestibulum interdum risus et enim faucibus, sit amet molestie est ac
19
+ <span
20
+ class="pf-c-truncate__start"
21
+ >
22
+ Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.
23
+ </span>
26
24
  </span>
27
- <span
28
- class="pf-c-truncate__end"
29
- >
30
- cumsan.
31
- </span>
32
- </span>
25
+ </div>
33
26
  </DocumentFragment>
34
27
  `;
35
28
 
36
- exports[`renders start truncation 1`] = `
29
+ exports[`renders start truncation with &lrm; at end 1`] = `
37
30
  <DocumentFragment>
38
- <span
39
- class="pf-c-truncate"
31
+ <div
32
+ data-testid="Tooltip-mock"
40
33
  >
34
+ <div
35
+ data-testid="Tooltip-mock-content-container"
36
+ >
37
+ Test Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.
38
+ </div>
39
+ <p>
40
+ position: top
41
+ </p>
41
42
  <span
42
- class="pf-c-truncate__end"
43
+ class="pf-c-truncate"
43
44
  >
44
- Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.‎
45
+ <span
46
+ class="pf-c-truncate__end"
47
+ >
48
+ Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.‎
49
+ </span>
45
50
  </span>
46
- </span>
51
+ </div>
47
52
  </DocumentFragment>
48
53
  `;
@@ -106,6 +106,8 @@ export interface WizardProps extends React.HTMLProps<HTMLDivElement> {
106
106
  isOpen?: boolean;
107
107
  /** Flag indicating nav items with sub steps are expandable */
108
108
  isNavExpandable?: boolean;
109
+ /** Callback function to signal the current step in the wizard */
110
+ onCurrentStepChanged?: (step: WizardStep) => void;
109
111
  /** @beta Flag indicating the wizard has a drawer for at least one of the wizard steps */
110
112
  hasDrawer?: boolean;
111
113
  /** @beta Flag indicating the wizard drawer is expanded */
@@ -164,6 +166,14 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
164
166
  isNavOpen: false
165
167
  };
166
168
 
169
+ if (props.onCurrentStepChanged) {
170
+ const flattenedSteps = this.getFlattenedSteps();
171
+ if (flattenedSteps.length >= this.state.currentStep) {
172
+ const currentStep = flattenedSteps[this.state.currentStep - 1];
173
+ props.onCurrentStepChanged(currentStep);
174
+ }
175
+ }
176
+
167
177
  this.drawerRef = React.createRef();
168
178
  }
169
179
 
@@ -202,9 +212,7 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
202
212
  }
203
213
  }
204
214
 
205
- this.setState({
206
- currentStep: newStep
207
- });
215
+ this.setCurrentStep(newStep, flattenedSteps[newStep - 1]);
208
216
  const { id: prevId, name: prevName } = flattenedSteps[currentStep - 1];
209
217
  const { id, name } = flattenedSteps[newStep - 1];
210
218
  return onNext && onNext({ id, name }, { prevId, prevName });
@@ -218,9 +226,7 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
218
226
  if (flattenedSteps.length < currentStep) {
219
227
  // Previous step was removed, just update the currentStep state
220
228
  const adjustedStep = flattenedSteps.length;
221
- this.setState({
222
- currentStep: adjustedStep
223
- });
229
+ this.setCurrentStep(adjustedStep, flattenedSteps[adjustedStep - 1]);
224
230
  } else {
225
231
  let newStep = currentStep;
226
232
 
@@ -235,9 +241,7 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
235
241
  }
236
242
  }
237
243
 
238
- this.setState({
239
- currentStep: newStep
240
- });
244
+ this.setCurrentStep(newStep, flattenedSteps[newStep - 1]);
241
245
  const { id: prevId, name: prevName } = flattenedSteps[newStep];
242
246
  const { id, name } = flattenedSteps[newStep - 1];
243
247
  return onBack && onBack({ id, name }, { prevId, prevName });
@@ -259,7 +263,8 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
259
263
  } else if (step > maxSteps) {
260
264
  step = maxSteps;
261
265
  }
262
- this.setState({ currentStep: step, isNavOpen: false });
266
+ this.setCurrentStep(step, flattenedSteps[step - 1]);
267
+ this.setState({ isNavOpen: false });
263
268
  const { id: prevId, name: prevName } = flattenedSteps[currentStep - 1];
264
269
  const { id, name } = flattenedSteps[step - 1];
265
270
  return onGoToStep && onGoToStep({ id, name }, { prevId, prevName });
@@ -275,7 +280,7 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
275
280
  }
276
281
  }
277
282
  if (step) {
278
- this.setState({ currentStep: step });
283
+ this.setCurrentStep(step, flattenedSteps[step - 1]);
279
284
  }
280
285
  };
281
286
 
@@ -289,7 +294,7 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
289
294
  }
290
295
  }
291
296
  if (step) {
292
- this.setState({ currentStep: step });
297
+ this.setCurrentStep(step, flattenedSteps[step - 1]);
293
298
  }
294
299
  };
295
300
 
@@ -338,6 +343,16 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
338
343
  return appendTo || document.body;
339
344
  };
340
345
 
346
+ private setCurrentStep(currentStep: number, currentStepObject: WizardStep) {
347
+ this.setState({
348
+ currentStep
349
+ });
350
+
351
+ if (this.props.onCurrentStepChanged) {
352
+ this.props.onCurrentStepChanged(currentStepObject);
353
+ }
354
+ }
355
+
341
356
  componentDidMount() {
342
357
  const target = typeof document !== 'undefined' ? document.body : null;
343
358
  if (target) {
@@ -352,6 +367,12 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
352
367
  }
353
368
  }
354
369
 
370
+ componentDidUpdate(prevProps: Readonly<WizardProps>) {
371
+ if (prevProps.startAtStep !== this.props.startAtStep) {
372
+ this.setState({ currentStep: this.props.startAtStep });
373
+ }
374
+ }
375
+
355
376
  render() {
356
377
  const {
357
378
  /* eslint-disable @typescript-eslint/no-unused-vars */
@@ -384,6 +405,7 @@ export class Wizard extends React.Component<WizardProps, WizardState> {
384
405
  titleId,
385
406
  descriptionId,
386
407
  isNavExpandable,
408
+ onCurrentStepChanged,
387
409
  hasDrawer,
388
410
  isDrawerExpanded,
389
411
  ...rest
@@ -222,6 +222,34 @@ describe('Wizard', () => {
222
222
  expect(main).toBeInTheDocument();
223
223
  expect(main).toHaveAttribute('aria-labelledby', 'main-aria-labelledby');
224
224
  });
225
+
226
+ test('wiz with onCurrentStepChanged setter', () => {
227
+ const stepA = { name: 'A', component: <p>Step 1</p> };
228
+ const stepB = { name: 'B', component: <p>Step 2</p> };
229
+ const stepC = { name: 'C', component: <p>Step 3</p> };
230
+
231
+ const steps: WizardStep[] = [ stepA, stepB, stepC ];
232
+ const setter = jest.fn();
233
+
234
+ render(
235
+ <Wizard
236
+ title="Wiz title"
237
+ onCurrentStepChanged={ setter }
238
+ steps={steps}
239
+ />
240
+ );
241
+ expect(setter).toHaveBeenLastCalledWith(expect.objectContaining(stepA));
242
+ userEvent.click(screen.getByText(/next/i));
243
+ expect(setter).toHaveBeenLastCalledWith(expect.objectContaining(stepB));
244
+ userEvent.click(screen.getByText(/next/i));
245
+ expect(setter).toHaveBeenLastCalledWith(expect.objectContaining(stepC));
246
+ userEvent.click(screen.getByText('A'));
247
+ expect(setter).toHaveBeenLastCalledWith(expect.objectContaining(stepA));
248
+ userEvent.click(screen.getByText(/next/i));
249
+ expect(setter).toHaveBeenLastCalledWith(expect.objectContaining(stepB));
250
+ userEvent.click(screen.getByText(/back/i));
251
+ expect(setter).toHaveBeenLastCalledWith(expect.objectContaining(stepA));
252
+ });
225
253
  });
226
254
 
227
255
  test('wiz with disabled steps', () => {
@@ -314,3 +342,34 @@ describe('Wizard', () => {
314
342
 
315
343
  expect(screen.getByRole('button',{ name: "A-2" })).toBeDisabled();
316
344
  });
345
+
346
+ test('startAtStep can be used to externally control the current step of the wizard', () => {
347
+ const WizardTest = () => {
348
+ const [step, setStep] = React.useState(1);
349
+
350
+ const incrementStep = () => {
351
+ setStep(prevStep => prevStep + 1);
352
+ };
353
+
354
+ const steps: WizardStep[] = [
355
+ { name: 'A', component: <p>Step 1</p> },
356
+ { name: 'B', component: <p>Step 2</p> },
357
+ { name: 'C', component: <p>Step 3</p> }
358
+ ];
359
+
360
+ return (
361
+ <>
362
+ <Wizard steps={steps} startAtStep={step} />
363
+ <button onClick={() => incrementStep()}>Increment step</button>
364
+ </>
365
+ );
366
+ };
367
+
368
+ render(<WizardTest />);
369
+
370
+ expect(screen.queryByText('Step 2')).not.toBeInTheDocument();
371
+
372
+ userEvent.click(screen.getByRole('button', { name: 'Increment step'}))
373
+
374
+ expect(screen.getByText('Step 2')).toBeVisible();
375
+ });