@automattic/vip-design-system 0.30.1 → 0.31.0

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.
@@ -37,7 +37,7 @@ var _Form = require("../Form");
37
37
 
38
38
  var _jsxRuntime = require("theme-ui/jsx-runtime");
39
39
 
40
- var _excluded = ["autoFilter", "className", "debounce", "displayMenu", "dropdownArrow", "errorMessage", "forLabel", "getOptionLabel", "getOptionValue", "hasError", "isInline", "label", "loading", "minLength", "noOptionsMessage", "onChange", "onInputChange", "options", "required", "searchIcon", "showAllValues", "source", "value"];
40
+ var _excluded = ["autoFilter", "className", "debounce", "displayMenu", "dropdownArrow", "errorMessage", "forLabel", "getOptionLabel", "getOptionValue", "hasError", "isInline", "label", "loading", "minLength", "noOptionsMessage", "onChange", "onInputChange", "options", "required", "searchIcon", "showAllValues", "resetOnBlur", "source", "value"];
41
41
 
42
42
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
43
43
 
@@ -49,7 +49,8 @@ var baseBorderTextColors = (0, _extends2["default"])({}, _Input.baseControlBorde
49
49
  borderRadius: 1
50
50
  });
51
51
  var defaultStyles = (0, _extends2["default"])({
52
- width: '100%'
52
+ width: '100%',
53
+ mb: 2
53
54
  }, baseBorderTextColors, {
54
55
  py: 0,
55
56
  minHeight: '36px',
@@ -125,8 +126,8 @@ var inlineStyles = {
125
126
  borderWidth: 0
126
127
  };
127
128
  var searchIconStyles = {
128
- '& .autocomplete__input.autocomplete__input--show-all-values': {
129
- paddingLeft: '30px'
129
+ '& .autocomplete__input.autocomplete__input': {
130
+ paddingLeft: 4
130
131
  }
131
132
  };
132
133
 
@@ -170,6 +171,8 @@ var FormAutocomplete = /*#__PURE__*/_react["default"].forwardRef(function (_ref,
170
171
  searchIcon = _ref.searchIcon,
171
172
  _ref$showAllValues = _ref.showAllValues,
172
173
  showAllValues = _ref$showAllValues === void 0 ? false : _ref$showAllValues,
174
+ _ref$resetOnBlur = _ref.resetOnBlur,
175
+ resetOnBlur = _ref$resetOnBlur === void 0 ? false : _ref$resetOnBlur,
173
176
  source = _ref.source,
174
177
  value = _ref.value,
175
178
  props = (0, _objectWithoutPropertiesLoose2["default"])(_ref, _excluded);
@@ -178,8 +181,20 @@ var FormAutocomplete = /*#__PURE__*/_react["default"].forwardRef(function (_ref,
178
181
  isDirty = _useState[0],
179
182
  setIsDirty = _useState[1];
180
183
 
184
+ var _useState2 = (0, _react.useState)(value || ''),
185
+ selectedValue = _useState2[0],
186
+ setSelectedValue = _useState2[1];
187
+
188
+ var _useState3 = (0, _react.useState)(value),
189
+ inputQuery = _useState3[0],
190
+ setInputQuery = _useState3[1];
191
+
181
192
  var debounceTimeout;
182
193
 
194
+ if (!forwardRef) {
195
+ forwardRef = /*#__PURE__*/_react["default"].createRef();
196
+ }
197
+
183
198
  var SelectLabel = function SelectLabel() {
184
199
  return (0, _jsxRuntime.jsx)(_Label.Label, {
185
200
  required: required,
@@ -208,11 +223,43 @@ var FormAutocomplete = /*#__PURE__*/_react["default"].forwardRef(function (_ref,
208
223
  return "" + optionLabel(option) === "" + inputValue;
209
224
  });
210
225
  }, [getAllOptions, optionLabel]);
226
+ /**
227
+ * Reset the underlying component state to show the selected value
228
+ */
229
+
230
+ var resetInputState = (0, _react.useCallback)(function () {
231
+ var _forwardRef;
232
+
233
+ if (resetOnBlur && (_forwardRef = forwardRef) != null && _forwardRef.current && inputQuery !== selectedValue) {
234
+ // resets the input field to the selected value or the empty string
235
+ forwardRef.current.setState((0, _extends2["default"])({}, forwardRef.current.state, {
236
+ query: inputQuery && inputQuery !== '' ? selectedValue != null ? selectedValue : '' : '' // selected value should not be null or the component will crash
237
+
238
+ }));
239
+ }
240
+ }, [forwardRef]); // sets the internal state variables and calls the onChange callback
241
+
242
+ var setAutocompleteState = function setAutocompleteState(inputValue) {
243
+ setInputQuery(inputValue);
244
+ setSelectedValue(inputValue);
245
+ onChange(getOptionByLabel(inputValue), inputValue);
246
+ setIsDirty(false);
247
+ }; // this method gets called when we confirm the selection via click/enter
248
+
249
+
211
250
  var onValueChange = (0, _react.useCallback)(function (inputValue) {
212
251
  if (inputValue) {
213
- onChange(getOptionByLabel(inputValue), inputValue);
252
+ setAutocompleteState(inputValue);
253
+ } else if (resetOnBlur && inputQuery !== selectedValue) {
254
+ if (inputQuery && inputQuery !== '') {
255
+ // reset the content to the selected value
256
+ setAutocompleteState(selectedValue);
257
+ } else {
258
+ // reset the content to empty if there's no match
259
+ setAutocompleteState(null);
260
+ }
214
261
  }
215
- }, [onChange, getOptionByLabel]);
262
+ }, [onChange, getOptionByLabel, setAutocompleteState]);
216
263
  var handleTypeChange = (0, _react.useCallback)(function (query) {
217
264
  return options.filter(function (option) {
218
265
  return optionLabel(option).toLowerCase().indexOf(query.toLowerCase()) >= 0;
@@ -247,7 +294,18 @@ var FormAutocomplete = /*#__PURE__*/_react["default"].forwardRef(function (_ref,
247
294
  populateResults((_data = data) == null ? void 0 : _data.map(function (option) {
248
295
  return optionLabel(option);
249
296
  }));
250
- }, [autoFilter, isDirty, onInputChange, options]);
297
+ }, [autoFilter, isDirty, onInputChange, options]); // internal function to save the inputQuery
298
+
299
+ var handleSource = function handleSource(query, populateResults) {
300
+ setInputQuery(query); // user function to fetch the results has the precedence
301
+
302
+ if (source) {
303
+ return source(query, populateResults);
304
+ }
305
+
306
+ return suggest(query, populateResults);
307
+ };
308
+
251
309
  (0, _react.useEffect)(function () {
252
310
  global.document.querySelector('.autocomplete__input').setAttribute('aria-activedescendant', '');
253
311
  }, []);
@@ -264,8 +322,13 @@ var FormAutocomplete = /*#__PURE__*/_react["default"].forwardRef(function (_ref,
264
322
  input.setAttribute('aria-required', required);
265
323
  }, [required]);
266
324
  (0, _react.useEffect)(function () {
267
- global.document.querySelector("#" + forLabel).addEventListener('keydown', function () {
268
- setIsDirty(true);
325
+ global.document.querySelector("#" + forLabel).addEventListener('keydown', function (e) {
326
+ // pressed escape, we want to reset the status
327
+ if (e.keyCode === 27 && resetOnBlur) {
328
+ resetInputState();
329
+ } else {
330
+ setIsDirty(true);
331
+ }
269
332
  });
270
333
  }, [setIsDirty]); // For accessibility, we need to add the error message to the aria-describedby attribute
271
334
 
@@ -273,6 +336,11 @@ var FormAutocomplete = /*#__PURE__*/_react["default"].forwardRef(function (_ref,
273
336
  var input = global.document.querySelector("#" + forLabel);
274
337
  input == null ? void 0 : input.setAttribute('aria-describedby', "describe-" + forLabel + "-validation " + input.getAttribute('aria-describedby'));
275
338
  }, []);
339
+ (0, _react.useEffect)(function () {
340
+ global.document.querySelector("#" + forLabel).addEventListener('blur', function () {
341
+ resetInputState();
342
+ });
343
+ }, [forwardRef]);
276
344
  return (0, _jsxRuntime.jsxs)("div", {
277
345
  className: (0, _classnames["default"])('vip-form-autocomplete-component', className),
278
346
  children: [label && !isInline && (0, _jsxRuntime.jsx)(SelectLabel, {}), (0, _jsxRuntime.jsx)("div", {
@@ -285,7 +353,7 @@ var FormAutocomplete = /*#__PURE__*/_react["default"].forwardRef(function (_ref,
285
353
  "aria-busy": loading,
286
354
  showAllValues: showAllValues,
287
355
  ref: forwardRef,
288
- source: source || suggest,
356
+ source: handleSource,
289
357
  defaultValue: value,
290
358
  displayMenu: displayMenu,
291
359
  onConfirm: onValueChange,
@@ -330,6 +398,7 @@ FormAutocomplete.propTypes = {
330
398
  required: _propTypes["default"].bool,
331
399
  searchIcon: _propTypes["default"].bool,
332
400
  showAllValues: _propTypes["default"].bool,
401
+ resetOnBlur: _propTypes["default"].bool,
333
402
  source: _propTypes["default"].func,
334
403
  value: _propTypes["default"].string,
335
404
  dropdownArrow: _propTypes["default"].node
@@ -3,7 +3,11 @@
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
4
 
5
5
  exports.__esModule = true;
6
- exports["default"] = exports.WithSearchIcon = exports.WithLoading = exports.WithErrors = exports.WithDefaultValue = exports.WithDebounce = exports.WithCustomMessages = exports.WithCustomArrow = exports.WithArrow = exports.Inline = exports.Default = void 0;
6
+ exports["default"] = exports.WithSlowSearch = exports.WithSearchIcon = exports.WithLoading = exports.WithErrors = exports.WithDefaultValue = exports.WithDebounce = exports.WithCustomMessages = exports.WithCustomArrow = exports.WithArrow = exports.Inline = exports.Default = void 0;
7
+
8
+ var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+
10
+ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
7
11
 
8
12
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
13
 
@@ -41,6 +45,12 @@ var _default = {
41
45
  control: {
42
46
  type: 'text'
43
47
  }
48
+ },
49
+ resetOnBlur: {
50
+ options: [false, true],
51
+ control: {
52
+ type: 'boolean'
53
+ }
44
54
  }
45
55
  }
46
56
  };
@@ -57,7 +67,9 @@ var options = [{
57
67
  }];
58
68
  var args = {
59
69
  label: 'Label',
60
- options: options
70
+ options: options,
71
+ resetOnBlur: false,
72
+ placeholder: 'Start typing...'
61
73
  }; // eslint-disable-next-line react/prop-types
62
74
 
63
75
  var DefaultComponent = function DefaultComponent(_ref) {
@@ -94,61 +106,30 @@ var DefaultComponent = function DefaultComponent(_ref) {
94
106
  });
95
107
  };
96
108
 
97
- var Default = function Default() {
98
- return (0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
99
- children: (0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({
100
- required: true
101
- }, args, {
102
- placeholder: "Start typing..."
103
- }))
104
- });
105
- };
106
-
109
+ var Default = DefaultComponent.bind({});
107
110
  exports.Default = Default;
108
-
109
- var Inline = function Inline() {
110
- var customArgs = (0, _extends2["default"])({}, args, {
111
- isInline: true
112
- });
113
- return (0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
114
- children: (0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({}, customArgs))
115
- });
116
- };
117
-
111
+ Default.args = args;
112
+ var Inline = DefaultComponent.bind({});
118
113
  exports.Inline = Inline;
119
-
120
- var WithDefaultValue = function WithDefaultValue() {
121
- var customArgs = (0, _extends2["default"])({}, args, {
122
- value: 'Chocolate'
123
- });
124
- return (0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
125
- children: (0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({}, customArgs))
126
- });
127
- };
128
-
114
+ Inline.args = (0, _extends2["default"])({}, Default.args, {
115
+ isInline: true
116
+ });
117
+ var WithDefaultValue = DefaultComponent.bind({});
129
118
  exports.WithDefaultValue = WithDefaultValue;
130
-
131
- var WithSearchIcon = function WithSearchIcon() {
132
- var customArgs = (0, _extends2["default"])({}, args, {
133
- searchIcon: true
134
- });
135
- return (0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
136
- children: (0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({}, customArgs))
137
- });
138
- };
139
-
119
+ WithDefaultValue.args = (0, _extends2["default"])({}, Default.args, {
120
+ value: 'Chocolate'
121
+ });
122
+ var WithSearchIcon = DefaultComponent.bind({});
140
123
  exports.WithSearchIcon = WithSearchIcon;
141
-
142
- var WithLoading = function WithLoading() {
143
- var customArgs = (0, _extends2["default"])({}, args, {
144
- loading: true
145
- });
146
- return (0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
147
- children: (0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({}, customArgs))
148
- });
149
- };
150
-
124
+ WithSearchIcon.args = (0, _extends2["default"])({}, Default.args, {
125
+ searchIcon: true,
126
+ placeholder: 'Type to search'
127
+ });
128
+ var WithLoading = DefaultComponent.bind({});
151
129
  exports.WithLoading = WithLoading;
130
+ WithLoading.args = (0, _extends2["default"])({}, Default.args, {
131
+ loading: true
132
+ });
152
133
 
153
134
  var WithDebounce = function WithDebounce() {
154
135
  var _useState2 = (0, _react.useState)(null),
@@ -168,65 +149,80 @@ var WithDebounce = function WithDebounce() {
168
149
  };
169
150
 
170
151
  exports.WithDebounce = WithDebounce;
152
+ var WithSlowSearch = DefaultComponent.bind({});
153
+ exports.WithSlowSearch = WithSlowSearch;
154
+ WithSlowSearch.args = (0, _extends2["default"])({}, Default.args, {
155
+ label: 'Label',
156
+ autoFilter: false,
157
+ minLength: 3,
158
+ required: true,
159
+ source: function () {
160
+ var _source = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(query, populateResults) {
161
+ return _regenerator["default"].wrap(function _callee$(_context) {
162
+ while (1) {
163
+ switch (_context.prev = _context.next) {
164
+ case 0:
165
+ if (!query || query.length >= 3) {
166
+ setTimeout(function () {
167
+ var filteredResults = args.options.map(function (option) {
168
+ return option.label;
169
+ }).filter(function (result) {
170
+ return result.toLowerCase().indexOf(query == null ? void 0 : query.toLowerCase()) !== -1;
171
+ });
172
+ populateResults(filteredResults);
173
+ }, 1000);
174
+ }
175
+
176
+ case 1:
177
+ case "end":
178
+ return _context.stop();
179
+ }
180
+ }
181
+ }, _callee);
182
+ }));
171
183
 
172
- var WithCustomMessages = function WithCustomMessages() {
173
- var customArgs = (0, _extends2["default"])({}, args, {
174
- noOptionsMessage: function noOptionsMessage() {
175
- return 'No data';
176
- },
177
- placeholder: 'Type to search'
178
- });
179
- return (0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
180
- children: (0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({}, customArgs))
181
- });
182
- };
184
+ function source(_x, _x2) {
185
+ return _source.apply(this, arguments);
186
+ }
183
187
 
188
+ return source;
189
+ }()
190
+ });
191
+ var WithCustomMessages = DefaultComponent.bind({});
184
192
  exports.WithCustomMessages = WithCustomMessages;
185
-
186
- var WithErrors = function WithErrors() {
187
- var customArgs = (0, _extends2["default"])({}, args, {
188
- hasError: true,
189
- errorMessage: 'Please select a value.',
190
- required: true
191
- });
192
- return (0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
193
- children: (0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({}, customArgs))
194
- });
195
- };
196
-
193
+ WithCustomMessages.args = (0, _extends2["default"])({}, Default.args, {
194
+ noOptionsMessage: function noOptionsMessage() {
195
+ return 'No data';
196
+ },
197
+ placeholder: 'Type to search'
198
+ });
199
+ var WithErrors = DefaultComponent.bind({});
197
200
  exports.WithErrors = WithErrors;
198
-
199
- var WithArrow = function WithArrow() {
200
- var customArgs = (0, _extends2["default"])({}, args, {
201
- showAllValues: true
202
- });
203
- return (0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
204
- children: (0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({}, customArgs))
205
- });
206
- };
207
-
201
+ WithErrors.args = (0, _extends2["default"])({}, Default.args, {
202
+ hasError: true,
203
+ errorMessage: 'Please select a value.',
204
+ required: true
205
+ });
206
+ var WithArrow = DefaultComponent.bind({});
208
207
  exports.WithArrow = WithArrow;
209
-
210
- var WithCustomArrow = function WithCustomArrow() {
211
- var customArgs = (0, _extends2["default"])({}, args, {
212
- showAllValues: true,
213
- // eslint-disable-next-line react/display-name
214
- dropdownArrow: function dropdownArrow() {
215
- return (0, _jsxRuntime.jsx)("span", {
216
- sx: {
217
- position: 'absolute',
218
- top: '2px',
219
- right: '10px',
220
- pointerEvents: 'none'
221
- },
222
- children: "\uD83D\uDC47"
223
- });
224
- }
225
- });
226
- return (0, _jsxRuntime.jsx)(_jsxRuntime.Fragment, {
227
- children: (0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({}, customArgs))
228
- });
229
- };
230
-
208
+ WithArrow.args = (0, _extends2["default"])({}, Default.args, {
209
+ showAllValues: true
210
+ });
211
+ var WithCustomArrow = DefaultComponent.bind({});
231
212
  exports.WithCustomArrow = WithCustomArrow;
213
+ WithCustomArrow.args = (0, _extends2["default"])({}, Default.args, {
214
+ showAllValues: true,
215
+ // eslint-disable-next-line react/display-name
216
+ dropdownArrow: function dropdownArrow() {
217
+ return (0, _jsxRuntime.jsx)("span", {
218
+ sx: {
219
+ position: 'absolute',
220
+ top: '2px',
221
+ right: '10px',
222
+ pointerEvents: 'none'
223
+ },
224
+ children: "\uD83D\uDC47"
225
+ });
226
+ }
227
+ });
232
228
  WithCustomArrow.displayName = 'WithCustomArrow';
@@ -36,13 +36,12 @@ var TableCell = function TableCell(_ref) {
36
36
  }, (0, _extends2["default"])({}, rest, {
37
37
  sx: sx
38
38
  }), {
39
- children: head ? (0, _jsxRuntime.jsx)(_.Heading, {
40
- variant: "caps",
41
- as: "div",
39
+ children: head ? (0, _jsxRuntime.jsx)("span", {
42
40
  sx: {
43
41
  mb: 0,
44
- color: head ? 'table.heading' : 'table.text',
45
- fontSize: 2
42
+ color: 'table.heading',
43
+ fontSize: 2,
44
+ fontWeight: 'bold'
46
45
  },
47
46
  children: children
48
47
  }) : children
@@ -9,7 +9,7 @@ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")
9
9
 
10
10
  var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
11
11
 
12
- var _react = _interopRequireDefault(require("react"));
12
+ var _react = _interopRequireWildcard(require("react"));
13
13
 
14
14
  var _propTypes = _interopRequireDefault(require("prop-types"));
15
15
 
@@ -21,7 +21,11 @@ var _ = require("..");
21
21
 
22
22
  var _jsxRuntime = require("theme-ui/jsx-runtime");
23
23
 
24
- var _excluded = ["steps", "activeStep", "variant", "completed", "className"];
24
+ var _excluded = ["steps", "activeStep", "variant", "completed", "className", "titleAutofocus"];
25
+
26
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
27
+
28
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
25
29
 
26
30
  var Wizard = /*#__PURE__*/_react["default"].forwardRef(function (_ref, forwardRef) {
27
31
  var steps = _ref.steps,
@@ -31,7 +35,17 @@ var Wizard = /*#__PURE__*/_react["default"].forwardRef(function (_ref, forwardRe
31
35
  completed = _ref$completed === void 0 ? [] : _ref$completed,
32
36
  _ref$className = _ref.className,
33
37
  className = _ref$className === void 0 ? null : _ref$className,
38
+ _ref$titleAutofocus = _ref.titleAutofocus,
39
+ titleAutofocus = _ref$titleAutofocus === void 0 ? false : _ref$titleAutofocus,
34
40
  props = (0, _objectWithoutPropertiesLoose2["default"])(_ref, _excluded);
41
+ var didMount = (0, _react.useRef)(false); // didMount helps us to track the initial render, so we can focus the title only subsequent renders
42
+ // to avoid stealing the focus from the page we're in.
43
+
44
+ (0, _react.useLayoutEffect)(function () {
45
+ if (!didMount.current) {
46
+ didMount.current = true;
47
+ }
48
+ }, [activeStep]);
35
49
  return (0, _jsxRuntime.jsx)(_.Box, {
36
50
  className: (0, _classnames["default"])('vip-wizard-component', className),
37
51
  ref: forwardRef,
@@ -73,6 +87,7 @@ var Wizard = /*#__PURE__*/_react["default"].forwardRef(function (_ref, forwardRe
73
87
  subTitle: subTitle,
74
88
  title: title,
75
89
  titleVariant: titleVariant,
90
+ shouldFocusTitle: titleAutofocus && didMount.current,
76
91
  children: children
77
92
  }, index);
78
93
  })
@@ -86,5 +101,6 @@ Wizard.propTypes = {
86
101
  activeStep: _propTypes["default"].number,
87
102
  variant: _propTypes["default"].string,
88
103
  completed: _propTypes["default"].array,
89
- className: _propTypes["default"].any
104
+ className: _propTypes["default"].any,
105
+ titleAutofocus: _propTypes["default"].bool
90
106
  };
@@ -3,7 +3,7 @@
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
4
 
5
5
  exports.__esModule = true;
6
- exports["default"] = exports.Default = exports.CustomTitle = void 0;
6
+ exports["default"] = exports.WithTitleAutoFocus = exports.Default = exports.CustomTitle = void 0;
7
7
 
8
8
  var _react = _interopRequireDefault(require("react"));
9
9
 
@@ -84,6 +84,82 @@ var Default = function Default() {
84
84
 
85
85
  exports.Default = Default;
86
86
 
87
+ var WithTitleAutoFocus = function WithTitleAutoFocus() {
88
+ var _React$useState = _react["default"].useState(0),
89
+ activeStep = _React$useState[0],
90
+ setActiveStep = _React$useState[1];
91
+
92
+ var _React$useState2 = _react["default"].useState(true),
93
+ autoFocus = _React$useState2[0],
94
+ setAutoFocus = _React$useState2[1];
95
+
96
+ var steps = [{
97
+ title: 'Choose Domain',
98
+ titleVariant: 'h2',
99
+ children: (0, _jsxRuntime.jsxs)(_.Box, {
100
+ children: [(0, _jsxRuntime.jsx)(_.Label, {
101
+ children: "Domain"
102
+ }), (0, _jsxRuntime.jsx)(_.Input, {
103
+ placeholder: "yourdomain.com"
104
+ }), (0, _jsxRuntime.jsx)(_.Button, {
105
+ sx: {
106
+ mt: 3
107
+ },
108
+ onClick: function onClick() {
109
+ return setActiveStep(1);
110
+ },
111
+ children: "Continue"
112
+ })]
113
+ })
114
+ }, {
115
+ title: 'Configure DNS',
116
+ titleVariant: 'h2',
117
+ children: (0, _jsxRuntime.jsxs)(_.Box, {
118
+ children: [(0, _jsxRuntime.jsx)(_.Label, {
119
+ children: "DNS"
120
+ }), (0, _jsxRuntime.jsx)(_.Button, {
121
+ sx: {
122
+ mt: 3
123
+ },
124
+ onClick: function onClick() {
125
+ return setActiveStep(0);
126
+ },
127
+ children: "back"
128
+ })]
129
+ })
130
+ }];
131
+ return (0, _jsxRuntime.jsxs)(_react["default"].Fragment, {
132
+ children: [(0, _jsxRuntime.jsx)(_.Box, {
133
+ mt: 4,
134
+ children: (0, _jsxRuntime.jsx)(_.Wizard, {
135
+ activeStep: activeStep,
136
+ steps: steps,
137
+ titleAutofocus: autoFocus,
138
+ className: "vip-wizard-xyz"
139
+ })
140
+ }), (0, _jsxRuntime.jsx)(_.Box, {
141
+ mt: 4,
142
+ children: (0, _jsxRuntime.jsx)(_.Form.Select, {
143
+ id: "wizard-autofocus",
144
+ label: "Autofocus status",
145
+ value: autoFocus,
146
+ onChange: function onChange(e) {
147
+ return setAutoFocus(e.value);
148
+ },
149
+ options: [{
150
+ value: true,
151
+ label: 'On'
152
+ }, {
153
+ value: false,
154
+ label: 'Off'
155
+ }]
156
+ })
157
+ })]
158
+ });
159
+ };
160
+
161
+ exports.WithTitleAutoFocus = WithTitleAutoFocus;
162
+
87
163
  var CustomTitle = function CustomTitle() {
88
164
  var steps = [{
89
165
  title: (0, _jsxRuntime.jsx)("h3", {
@@ -5,7 +5,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
5
5
  exports.__esModule = true;
6
6
  exports.WizardStep = void 0;
7
7
 
8
- var _react = _interopRequireDefault(require("react"));
8
+ var _react = _interopRequireWildcard(require("react"));
9
9
 
10
10
  var _md = require("react-icons/md");
11
11
 
@@ -15,6 +15,10 @@ var _ = require("..");
15
15
 
16
16
  var _jsxRuntime = require("theme-ui/jsx-runtime");
17
17
 
18
+ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
19
+
20
+ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
21
+
18
22
  /** @jsxImportSource theme-ui */
19
23
 
20
24
  /**
@@ -32,8 +36,12 @@ var WizardStep = /*#__PURE__*/_react["default"].forwardRef(function (_ref, forwa
32
36
  children = _ref.children,
33
37
  active = _ref.active,
34
38
  order = _ref.order,
39
+ shouldFocusTitle = _ref.shouldFocusTitle,
35
40
  _ref$titleVariant = _ref.titleVariant,
36
41
  titleVariant = _ref$titleVariant === void 0 ? 'h3' : _ref$titleVariant;
42
+
43
+ var titleRef = _react["default"].useRef(null);
44
+
37
45
  var borderLeftColor = 'border';
38
46
 
39
47
  if (complete) {
@@ -50,6 +58,11 @@ var WizardStep = /*#__PURE__*/_react["default"].forwardRef(function (_ref, forwa
50
58
  color = 'heading';
51
59
  }
52
60
 
61
+ (0, _react.useLayoutEffect)(function () {
62
+ if (active && titleRef != null && titleRef.current && shouldFocusTitle) {
63
+ titleRef.current.focus();
64
+ }
65
+ }, [active, shouldFocusTitle]);
53
66
  return (0, _jsxRuntime.jsxs)(_.Card, {
54
67
  as: "section",
55
68
  sx: {
@@ -81,6 +94,8 @@ var WizardStep = /*#__PURE__*/_react["default"].forwardRef(function (_ref, forwa
81
94
  fontSize: 2,
82
95
  fontWeight: active ? 'bold' : 'normal'
83
96
  },
97
+ ref: titleRef,
98
+ tabIndex: shouldFocusTitle ? -1 : undefined,
84
99
  children: [(0, _jsxRuntime.jsx)(_md.MdCheckCircle, {
85
100
  "aria-hidden": "true",
86
101
  sx: {
@@ -118,5 +133,6 @@ WizardStep.propTypes = {
118
133
  order: _propTypes["default"].number.isRequired,
119
134
  subTitle: _propTypes["default"].node,
120
135
  title: _propTypes["default"].node,
121
- titleVariant: _propTypes["default"].string
136
+ titleVariant: _propTypes["default"].string,
137
+ shouldFocusTitle: _propTypes["default"].bool
122
138
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip-design-system",
3
- "version": "0.30.1",
3
+ "version": "0.31.0",
4
4
  "main": "build/system/index.js",
5
5
  "scripts": {
6
6
  "build-storybook": "build-storybook",
@@ -29,6 +29,7 @@ const baseBorderTextColors = {
29
29
 
30
30
  const defaultStyles = {
31
31
  width: '100%',
32
+ mb: 2,
32
33
  ...baseBorderTextColors,
33
34
 
34
35
  py: 0,
@@ -87,8 +88,8 @@ const inlineStyles = {
87
88
  };
88
89
 
89
90
  const searchIconStyles = {
90
- '& .autocomplete__input.autocomplete__input--show-all-values': {
91
- paddingLeft: '30px',
91
+ '& .autocomplete__input.autocomplete__input': {
92
+ paddingLeft: 4,
92
93
  },
93
94
  };
94
95
 
@@ -118,6 +119,7 @@ const FormAutocomplete = React.forwardRef(
118
119
  required,
119
120
  searchIcon,
120
121
  showAllValues = false,
122
+ resetOnBlur = false, // resets the input value to the selection if the input is blurred. Returns null if selection is empty
121
123
  source,
122
124
  value,
123
125
  ...props
@@ -125,7 +127,13 @@ const FormAutocomplete = React.forwardRef(
125
127
  forwardRef
126
128
  ) => {
127
129
  const [ isDirty, setIsDirty ] = useState( false );
130
+
131
+ const [ selectedValue, setSelectedValue ] = useState( value || '' );
132
+ const [ inputQuery, setInputQuery ] = useState( value );
128
133
  let debounceTimeout;
134
+ if ( ! forwardRef ) {
135
+ forwardRef = React.createRef();
136
+ }
129
137
 
130
138
  const SelectLabel = () => (
131
139
  <Label required={ required } htmlFor={ forLabel }>
@@ -154,21 +162,49 @@ const FormAutocomplete = React.forwardRef(
154
162
  getAllOptions.find( option => `${ optionLabel( option ) }` === `${ inputValue }` ),
155
163
  [ getAllOptions, optionLabel ]
156
164
  );
157
-
165
+ /**
166
+ * Reset the underlying component state to show the selected value
167
+ */
168
+ const resetInputState = useCallback( () => {
169
+ if ( resetOnBlur && forwardRef?.current && inputQuery !== selectedValue ) {
170
+ // resets the input field to the selected value or the empty string
171
+ forwardRef.current.setState( {
172
+ ...forwardRef.current.state,
173
+ query: inputQuery && inputQuery !== '' ? selectedValue ?? '' : '', // selected value should not be null or the component will crash
174
+ } );
175
+ }
176
+ }, [ forwardRef ] );
177
+ // sets the internal state variables and calls the onChange callback
178
+ const setAutocompleteState = inputValue => {
179
+ setInputQuery( inputValue );
180
+ setSelectedValue( inputValue );
181
+ onChange( getOptionByLabel( inputValue ), inputValue );
182
+ setIsDirty( false );
183
+ };
184
+ // this method gets called when we confirm the selection via click/enter
158
185
  const onValueChange = useCallback(
159
186
  inputValue => {
160
187
  if ( inputValue ) {
161
- onChange( getOptionByLabel( inputValue ), inputValue );
188
+ setAutocompleteState( inputValue );
189
+ } else if ( resetOnBlur && inputQuery !== selectedValue ) {
190
+ if ( inputQuery && inputQuery !== '' ) {
191
+ // reset the content to the selected value
192
+ setAutocompleteState( selectedValue );
193
+ } else {
194
+ // reset the content to empty if there's no match
195
+ setAutocompleteState( null );
196
+ }
162
197
  }
163
198
  },
164
- [ onChange, getOptionByLabel ]
199
+ [ onChange, getOptionByLabel, setAutocompleteState ]
165
200
  );
166
201
 
167
202
  const handleTypeChange = useCallback(
168
- query =>
169
- options.filter(
203
+ query => {
204
+ return options.filter(
170
205
  option => optionLabel( option ).toLowerCase().indexOf( query.toLowerCase() ) >= 0
171
- ),
206
+ );
207
+ },
172
208
  [ options ]
173
209
  );
174
210
 
@@ -178,6 +214,7 @@ const FormAutocomplete = React.forwardRef(
178
214
  return onInputChange( query );
179
215
  }
180
216
  clearTimeout( debounceTimeout );
217
+
181
218
  if ( ! query.length || query.length >= minLength ) {
182
219
  debounceTimeout = setTimeout( () => {
183
220
  onInputChange( query );
@@ -200,7 +237,15 @@ const FormAutocomplete = React.forwardRef(
200
237
  },
201
238
  [ autoFilter, isDirty, onInputChange, options ]
202
239
  );
203
-
240
+ // internal function to save the inputQuery
241
+ const handleSource = ( query, populateResults ) => {
242
+ setInputQuery( query );
243
+ // user function to fetch the results has the precedence
244
+ if ( source ) {
245
+ return source( query, populateResults );
246
+ }
247
+ return suggest( query, populateResults );
248
+ };
204
249
  useEffect( () => {
205
250
  global.document
206
251
  .querySelector( '.autocomplete__input' )
@@ -224,8 +269,13 @@ const FormAutocomplete = React.forwardRef(
224
269
  }, [ required ] );
225
270
 
226
271
  useEffect( () => {
227
- global.document.querySelector( `#${ forLabel }` ).addEventListener( 'keydown', () => {
228
- setIsDirty( true );
272
+ global.document.querySelector( `#${ forLabel }` ).addEventListener( 'keydown', e => {
273
+ // pressed escape, we want to reset the status
274
+ if ( e.keyCode === 27 && resetOnBlur ) {
275
+ resetInputState();
276
+ } else {
277
+ setIsDirty( true );
278
+ }
229
279
  } );
230
280
  }, [ setIsDirty ] );
231
281
 
@@ -239,6 +289,11 @@ const FormAutocomplete = React.forwardRef(
239
289
  );
240
290
  }, [] );
241
291
 
292
+ useEffect( () => {
293
+ global.document.querySelector( `#${ forLabel }` ).addEventListener( 'blur', () => {
294
+ resetInputState();
295
+ } );
296
+ }, [ forwardRef ] );
242
297
  return (
243
298
  <div className={ classNames( 'vip-form-autocomplete-component', className ) }>
244
299
  { label && ! isInline && <SelectLabel /> }
@@ -261,7 +316,7 @@ const FormAutocomplete = React.forwardRef(
261
316
  aria-busy={ loading }
262
317
  showAllValues={ showAllValues }
263
318
  ref={ forwardRef }
264
- source={ source || suggest }
319
+ source={ handleSource }
265
320
  defaultValue={ value }
266
321
  displayMenu={ displayMenu }
267
322
  onConfirm={ onValueChange }
@@ -306,6 +361,7 @@ FormAutocomplete.propTypes = {
306
361
  required: PropTypes.bool,
307
362
  searchIcon: PropTypes.bool,
308
363
  showAllValues: PropTypes.bool,
364
+ resetOnBlur: PropTypes.bool,
309
365
  source: PropTypes.func,
310
366
  value: PropTypes.string,
311
367
  dropdownArrow: PropTypes.node,
@@ -17,6 +17,10 @@ export default {
17
17
  type: { name: 'string', required: false },
18
18
  control: { type: 'text' },
19
19
  },
20
+ resetOnBlur: {
21
+ options: [ false, true ],
22
+ control: { type: 'boolean' },
23
+ },
20
24
  },
21
25
  };
22
26
 
@@ -29,6 +33,8 @@ const options = [
29
33
  const args = {
30
34
  label: 'Label',
31
35
  options,
36
+ resetOnBlur: false,
37
+ placeholder: 'Start typing...',
32
38
  };
33
39
 
34
40
  // eslint-disable-next-line react/prop-types
@@ -53,64 +59,30 @@ const DefaultComponent = ( { label = 'Label', width = 250, ...rest } ) => {
53
59
  );
54
60
  };
55
61
 
56
- export const Default = () => {
57
- return (
58
- <>
59
- <DefaultComponent required { ...args } placeholder="Start typing..." />
60
- </>
61
- );
62
- };
62
+ export const Default = DefaultComponent.bind( {} );
63
+ Default.args = args;
63
64
 
64
- export const Inline = () => {
65
- const customArgs = {
66
- ...args,
67
- isInline: true,
68
- };
69
-
70
- return (
71
- <>
72
- <DefaultComponent { ...customArgs } />
73
- </>
74
- );
65
+ export const Inline = DefaultComponent.bind( {} );
66
+ Inline.args = {
67
+ ...Default.args,
68
+ isInline: true,
75
69
  };
76
70
 
77
- export const WithDefaultValue = () => {
78
- const customArgs = {
79
- ...args,
80
- value: 'Chocolate',
81
- };
82
-
83
- return (
84
- <>
85
- <DefaultComponent { ...customArgs } />
86
- </>
87
- );
71
+ export const WithDefaultValue = DefaultComponent.bind( {} );
72
+ WithDefaultValue.args = {
73
+ ...Default.args,
74
+ value: 'Chocolate',
88
75
  };
89
-
90
- export const WithSearchIcon = () => {
91
- const customArgs = {
92
- ...args,
93
- searchIcon: true,
94
- };
95
-
96
- return (
97
- <>
98
- <DefaultComponent { ...customArgs } />
99
- </>
100
- );
76
+ export const WithSearchIcon = DefaultComponent.bind( {} );
77
+ WithSearchIcon.args = {
78
+ ...Default.args,
79
+ searchIcon: true,
80
+ placeholder: 'Type to search',
101
81
  };
102
-
103
- export const WithLoading = () => {
104
- const customArgs = {
105
- ...args,
106
- loading: true,
107
- };
108
-
109
- return (
110
- <>
111
- <DefaultComponent { ...customArgs } />
112
- </>
113
- );
82
+ export const WithLoading = DefaultComponent.bind( {} );
83
+ WithLoading.args = {
84
+ ...Default.args,
85
+ loading: true,
114
86
  };
115
87
 
116
88
  export const WithDebounce = () => {
@@ -131,65 +103,52 @@ export const WithDebounce = () => {
131
103
  </>
132
104
  );
133
105
  };
134
-
135
- export const WithCustomMessages = () => {
136
- const customArgs = {
137
- ...args,
138
- noOptionsMessage: () => 'No data',
139
- placeholder: 'Type to search',
140
- };
141
-
142
- return (
143
- <>
144
- <DefaultComponent { ...customArgs } />
145
- </>
146
- );
106
+ export const WithSlowSearch = DefaultComponent.bind( {} );
107
+ WithSlowSearch.args = {
108
+ ...Default.args,
109
+ label: 'Label',
110
+ autoFilter: false,
111
+ minLength: 3,
112
+ required: true,
113
+ source: async ( query, populateResults ) => {
114
+ if ( ! query || query.length >= 3 ) {
115
+ setTimeout( () => {
116
+ const filteredResults = args.options
117
+ .map( option => option.label )
118
+ .filter( result => result.toLowerCase().indexOf( query?.toLowerCase() ) !== -1 );
119
+ populateResults( filteredResults );
120
+ }, 1000 );
121
+ }
122
+ },
147
123
  };
148
- export const WithErrors = () => {
149
- const customArgs = {
150
- ...args,
151
- hasError: true,
152
- errorMessage: 'Please select a value.',
153
- required: true,
154
- };
155
-
156
- return (
157
- <>
158
- <DefaultComponent { ...customArgs } />
159
- </>
160
- );
124
+ export const WithCustomMessages = DefaultComponent.bind( {} );
125
+ WithCustomMessages.args = {
126
+ ...Default.args,
127
+ noOptionsMessage: () => 'No data',
128
+ placeholder: 'Type to search',
161
129
  };
162
-
163
- export const WithArrow = () => {
164
- const customArgs = {
165
- ...args,
166
- showAllValues: true,
167
- };
168
-
169
- return (
170
- <>
171
- <DefaultComponent { ...customArgs } />
172
- </>
173
- );
130
+ export const WithErrors = DefaultComponent.bind( {} );
131
+ WithErrors.args = {
132
+ ...Default.args,
133
+ hasError: true,
134
+ errorMessage: 'Please select a value.',
135
+ required: true,
174
136
  };
175
137
 
176
- export const WithCustomArrow = () => {
177
- const customArgs = {
178
- ...args,
179
- showAllValues: true,
180
- // eslint-disable-next-line react/display-name
181
- dropdownArrow: () => (
182
- <span sx={ { position: 'absolute', top: '2px', right: '10px', pointerEvents: 'none' } }>
183
- 👇
184
- </span>
185
- ),
186
- };
187
-
188
- return (
189
- <>
190
- <DefaultComponent { ...customArgs } />
191
- </>
192
- );
138
+ export const WithArrow = DefaultComponent.bind( {} );
139
+ WithArrow.args = {
140
+ ...Default.args,
141
+ showAllValues: true,
142
+ };
143
+ export const WithCustomArrow = DefaultComponent.bind( {} );
144
+ WithCustomArrow.args = {
145
+ ...Default.args,
146
+ showAllValues: true,
147
+ // eslint-disable-next-line react/display-name
148
+ dropdownArrow: () => (
149
+ <span sx={ { position: 'absolute', top: '2px', right: '10px', pointerEvents: 'none' } }>
150
+ 👇
151
+ </span>
152
+ ),
193
153
  };
194
-
195
154
  WithCustomArrow.displayName = 'WithCustomArrow';
@@ -9,7 +9,7 @@ import PropTypes from 'prop-types';
9
9
  /**
10
10
  * Internal dependencies
11
11
  */
12
- import { Heading, Box } from '../';
12
+ import { Box } from '../';
13
13
 
14
14
  const TableCell = ( { head, children, ...rest } ) => {
15
15
  const sx = {
@@ -27,13 +27,9 @@ const TableCell = ( { head, children, ...rest } ) => {
27
27
  return (
28
28
  <Box as={ head ? 'th' : 'td' } { ...{ ...rest, sx } }>
29
29
  { head ? (
30
- <Heading
31
- variant="caps"
32
- as="div"
33
- sx={ { mb: 0, color: head ? 'table.heading' : 'table.text', fontSize: 2 } }
34
- >
30
+ <span sx={ { mb: 0, color: 'table.heading', fontSize: 2, fontWeight: 'bold' } }>
35
31
  { children }
36
- </Heading>
32
+ </span>
37
33
  ) : (
38
34
  children
39
35
  ) }
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * External dependencies
5
5
  */
6
- import React from 'react';
6
+ import React, { useLayoutEffect, useRef } from 'react';
7
7
  import PropTypes from 'prop-types';
8
8
  import classNames from 'classnames';
9
9
  import { MdArrowForward } from 'react-icons/md';
@@ -14,7 +14,26 @@ import { MdArrowForward } from 'react-icons/md';
14
14
  import { Box, WizardStep, Flex, WizardStepHorizontal } from '..';
15
15
 
16
16
  const Wizard = React.forwardRef(
17
- ( { steps, activeStep, variant, completed = [], className = null, ...props }, forwardRef ) => {
17
+ (
18
+ {
19
+ steps,
20
+ activeStep,
21
+ variant,
22
+ completed = [],
23
+ className = null,
24
+ titleAutofocus = false,
25
+ ...props
26
+ },
27
+ forwardRef
28
+ ) => {
29
+ const didMount = useRef( false );
30
+ // didMount helps us to track the initial render, so we can focus the title only subsequent renders
31
+ // to avoid stealing the focus from the page we're in.
32
+ useLayoutEffect( () => {
33
+ if ( ! didMount.current ) {
34
+ didMount.current = true;
35
+ }
36
+ }, [ activeStep ] );
18
37
  return (
19
38
  <Box className={ classNames( 'vip-wizard-component', className ) } ref={ forwardRef }>
20
39
  { variant === 'horizontal' ? (
@@ -52,6 +71,7 @@ const Wizard = React.forwardRef(
52
71
  subTitle={ subTitle }
53
72
  title={ title }
54
73
  titleVariant={ titleVariant }
74
+ shouldFocusTitle={ titleAutofocus && didMount.current }
55
75
  >
56
76
  { children }
57
77
  </WizardStep>
@@ -70,6 +90,7 @@ Wizard.propTypes = {
70
90
  variant: PropTypes.string,
71
91
  completed: PropTypes.array,
72
92
  className: PropTypes.any,
93
+ titleAutofocus: PropTypes.bool,
73
94
  };
74
95
 
75
96
  export { Wizard };
@@ -59,6 +59,62 @@ export const Default = () => {
59
59
  );
60
60
  };
61
61
 
62
+ export const WithTitleAutoFocus = () => {
63
+ const [ activeStep, setActiveStep ] = React.useState( 0 );
64
+ const [ autoFocus, setAutoFocus ] = React.useState( true );
65
+ const steps = [
66
+ {
67
+ title: 'Choose Domain',
68
+ titleVariant: 'h2',
69
+ children: (
70
+ <Box>
71
+ <Label>Domain</Label>
72
+ <Input placeholder="yourdomain.com" />
73
+ <Button sx={ { mt: 3 } } onClick={ () => setActiveStep( 1 ) }>
74
+ Continue
75
+ </Button>
76
+ </Box>
77
+ ),
78
+ },
79
+ {
80
+ title: 'Configure DNS',
81
+ titleVariant: 'h2',
82
+ children: (
83
+ <Box>
84
+ <Label>DNS</Label>
85
+ <Button sx={ { mt: 3 } } onClick={ () => setActiveStep( 0 ) }>
86
+ back
87
+ </Button>
88
+ </Box>
89
+ ),
90
+ },
91
+ ];
92
+ return (
93
+ <React.Fragment>
94
+ <Box mt={ 4 }>
95
+ <Wizard
96
+ activeStep={ activeStep }
97
+ steps={ steps }
98
+ titleAutofocus={ autoFocus }
99
+ className="vip-wizard-xyz"
100
+ />
101
+ </Box>
102
+ <Box mt={ 4 }>
103
+ <Form.Select
104
+ id="wizard-autofocus"
105
+ label="Autofocus status"
106
+ value={ autoFocus }
107
+ onChange={ e => setAutoFocus( e.value ) }
108
+ options={ [
109
+ { value: true, label: 'On' },
110
+ { value: false, label: 'Off' },
111
+ ] }
112
+ />
113
+ </Box>
114
+ </React.Fragment>
115
+ );
116
+ };
117
+
62
118
  export const CustomTitle = () => {
63
119
  const steps = [
64
120
  {
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * External dependencies
5
5
  */
6
- import React from 'react';
6
+ import React, { useLayoutEffect } from 'react';
7
7
  import { MdCheckCircle } from 'react-icons/md';
8
8
  import PropTypes from 'prop-types';
9
9
 
@@ -14,9 +14,20 @@ import { Card, Heading, Text, Flex } from '..';
14
14
 
15
15
  const WizardStep = React.forwardRef(
16
16
  (
17
- { title, subTitle, complete = false, children, active, order, titleVariant = 'h3' },
17
+ {
18
+ title,
19
+ subTitle,
20
+ complete = false,
21
+ children,
22
+ active,
23
+ order,
24
+ shouldFocusTitle,
25
+ titleVariant = 'h3',
26
+ },
18
27
  forwardRef
19
28
  ) => {
29
+ const titleRef = React.useRef( null );
30
+
20
31
  let borderLeftColor = 'border';
21
32
 
22
33
  if ( complete ) {
@@ -32,7 +43,11 @@ const WizardStep = React.forwardRef(
32
43
  } else if ( active ) {
33
44
  color = 'heading';
34
45
  }
35
-
46
+ useLayoutEffect( () => {
47
+ if ( active && titleRef?.current && shouldFocusTitle ) {
48
+ titleRef.current.focus();
49
+ }
50
+ }, [ active, shouldFocusTitle ] );
36
51
  return (
37
52
  <Card
38
53
  as="section"
@@ -67,6 +82,8 @@ const WizardStep = React.forwardRef(
67
82
  fontSize: 2,
68
83
  fontWeight: active ? 'bold' : 'normal',
69
84
  } }
85
+ ref={ titleRef }
86
+ tabIndex={ shouldFocusTitle ? -1 : undefined }
70
87
  >
71
88
  <MdCheckCircle
72
89
  aria-hidden="true"
@@ -102,6 +119,7 @@ WizardStep.propTypes = {
102
119
  subTitle: PropTypes.node,
103
120
  title: PropTypes.node,
104
121
  titleVariant: PropTypes.string,
122
+ shouldFocusTitle: PropTypes.bool,
105
123
  };
106
124
 
107
125
  export { WizardStep };