@automattic/vip-design-system 0.23.0 → 0.23.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.
@@ -38,6 +38,8 @@ var defaultStyles = {
38
38
  py: 0,
39
39
  borderColor: 'border',
40
40
  borderRadius: 1,
41
+ backgroundColor: 'background',
42
+ color: 'text',
41
43
  appearance: 'none',
42
44
  minHeight: '36px',
43
45
  '&:focus': function focus(theme) {
@@ -85,6 +87,17 @@ var FormSelect = /*#__PURE__*/_react["default"].forwardRef(function (_ref2, forw
85
87
  console.info('For 16 or more items, consider using another component with a typeahead capability.');
86
88
  }
87
89
 
90
+ var getAllOptions = (0, _react.useMemo)(function () {
91
+ return [].concat(options.filter(function (option) {
92
+ return !option.options;
93
+ }), options.filter(function (option) {
94
+ return option.options;
95
+ }).map(function (option) {
96
+ return option.options;
97
+ })).reduce(function (a, b) {
98
+ return a.concat(b);
99
+ }, []);
100
+ }, [options]);
88
101
  var optionLabel = (0, _react.useCallback)(function (option) {
89
102
  return getOptionLabel ? getOptionLabel(option) : option.label;
90
103
  }, [getOptionLabel]);
@@ -92,10 +105,10 @@ var FormSelect = /*#__PURE__*/_react["default"].forwardRef(function (_ref2, forw
92
105
  return getOptionValue ? getOptionValue(option) : option.value;
93
106
  }, [getOptionValue]);
94
107
  var getOptionByValue = (0, _react.useCallback)(function (value) {
95
- return options.find(function (option) {
96
- return optionValue(option) === value;
108
+ return getAllOptions.find(function (option) {
109
+ return "" + optionValue(option) === "" + value;
97
110
  });
98
- }, [options, optionValue]);
111
+ }, [getAllOptions, optionValue]);
99
112
  var onValueChange = (0, _react.useCallback)(function (event) {
100
113
  return onChange ? onChange(getOptionByValue(event.target.value), event) : getOptionByValue(event.target.value);
101
114
  }, [onChange, getOptionByValue]);
@@ -122,8 +135,7 @@ var FormSelect = /*#__PURE__*/_react["default"].forwardRef(function (_ref2, forw
122
135
  }), options.map(function (_ref3) {
123
136
  var groupOptions = _ref3.options,
124
137
  option = (0, _objectWithoutPropertiesLoose2["default"])(_ref3, _excluded2);
125
- var value = optionValue(option);
126
- return value ? renderOption(optionLabel(option), value) : renderGroup(optionLabel(option), groupOptions);
138
+ return groupOptions ? renderGroup(optionLabel(option), groupOptions) : renderOption(optionLabel(option), optionValue(option));
127
139
  })]
128
140
  })), (0, _jsxRuntime.jsx)(_FormSelectArrow.FormSelectArrow, {})]
129
141
  })]
@@ -54,6 +54,13 @@ var options = [{
54
54
  }, {
55
55
  value: 'vanilla',
56
56
  label: 'Vanilla'
57
+ }];
58
+ var groupedOptions = [{
59
+ label: 'Group name',
60
+ options: options
61
+ }, {
62
+ label: 'Another Group name',
63
+ options: options
57
64
  }]; // eslint-disable-next-line react/prop-types
58
65
 
59
66
  var DefaultComponent = function DefaultComponent(_ref) {
@@ -100,13 +107,7 @@ var WithGroup = DefaultComponent.bind({});
100
107
  exports.WithGroup = WithGroup;
101
108
  WithGroup.args = {
102
109
  label: 'Group Label',
103
- options: [{
104
- label: 'Group name',
105
- options: options
106
- }, {
107
- label: 'Another Group name',
108
- options: options
109
- }]
110
+ options: [].concat(options, groupedOptions)
110
111
  };
111
112
  var IsInline = DefaultComponent.bind({});
112
113
  exports.IsInline = IsInline;
@@ -114,13 +115,7 @@ IsInline.args = {
114
115
  label: 'Inline Select',
115
116
  isInline: true,
116
117
  width: '100%',
117
- options: [{
118
- label: 'Group name',
119
- options: options
120
- }, {
121
- label: 'Another Group name',
122
- options: options
123
- }]
118
+ options: groupedOptions
124
119
  };
125
120
  var WithOptionLabelAndValue = DefaultComponent.bind({});
126
121
  exports.WithOptionLabelAndValue = WithOptionLabelAndValue;
@@ -159,7 +154,7 @@ var WithOnChange = function WithOnChange() {
159
154
  placeholder: '- Select -',
160
155
  width: '100%',
161
156
  onChange: onChange,
162
- options: options
157
+ options: [].concat(options, groupedOptions)
163
158
  };
164
159
  return (0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
165
160
  children: [(0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({
@@ -140,17 +140,52 @@ describe('<FormSelect />', function () {
140
140
  }
141
141
  }, _callee3);
142
142
  })));
143
- it('renders the FormSelect component when getOptionLabel and getOptionValue', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4() {
144
- var props, _render4, container;
143
+ it('renders the FormSelect with nullish options', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4() {
144
+ var nullishOptions, _render4, container;
145
145
 
146
146
  return _regenerator["default"].wrap(function _callee4$(_context4) {
147
147
  while (1) {
148
148
  switch (_context4.prev = _context4.next) {
149
+ case 0:
150
+ nullishOptions = [].concat(options, [{
151
+ value: null,
152
+ label: 'Empty'
153
+ }]);
154
+ _render4 = (0, _react.render)((0, _jsxRuntime.jsx)(_FormSelect.FormSelect, (0, _extends2["default"])({
155
+ id: "my_desert_list"
156
+ }, defaultProps, {
157
+ options: nullishOptions
158
+ }))), container = _render4.container;
159
+ expect(_react.screen.getByLabelText(defaultProps.label)).toBeInTheDocument();
160
+ expect(_react.screen.getByRole('combobox')).toBeInTheDocument(); // Check for accessibility issues
161
+
162
+ _context4.t0 = expect;
163
+ _context4.next = 7;
164
+ return (0, _jestAxe.axe)(container);
165
+
166
+ case 7:
167
+ _context4.t1 = _context4.sent;
168
+ _context4.next = 10;
169
+ return (0, _context4.t0)(_context4.t1).toHaveNoViolations();
170
+
171
+ case 10:
172
+ case "end":
173
+ return _context4.stop();
174
+ }
175
+ }
176
+ }, _callee4);
177
+ })));
178
+ it('renders the FormSelect component when getOptionLabel and getOptionValue', /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee5() {
179
+ var props, _render5, container;
180
+
181
+ return _regenerator["default"].wrap(function _callee5$(_context5) {
182
+ while (1) {
183
+ switch (_context5.prev = _context5.next) {
149
184
  case 0:
150
185
  props = (0, _extends2["default"])({}, defaultProps, {
151
- options: options.map(function (_ref5) {
152
- var label = _ref5.label,
153
- value = _ref5.value;
186
+ options: options.map(function (_ref6) {
187
+ var label = _ref6.label,
188
+ value = _ref6.value;
154
189
  return {
155
190
  name: label,
156
191
  id: value
@@ -163,9 +198,9 @@ describe('<FormSelect />', function () {
163
198
  return option.id;
164
199
  }
165
200
  });
166
- _render4 = (0, _react.render)((0, _jsxRuntime.jsx)(_FormSelect.FormSelect, (0, _extends2["default"])({
201
+ _render5 = (0, _react.render)((0, _jsxRuntime.jsx)(_FormSelect.FormSelect, (0, _extends2["default"])({
167
202
  id: "my_desert_list"
168
- }, props))), container = _render4.container;
203
+ }, props))), container = _render5.container;
169
204
  expect(_react.screen.getByLabelText(defaultProps.label)).toBeInTheDocument();
170
205
  expect(_react.screen.getByRole('combobox')).toBeInTheDocument();
171
206
  expect(_react.screen.getAllByRole('option')).toHaveLength(3);
@@ -174,20 +209,20 @@ describe('<FormSelect />', function () {
174
209
  expect(_react.screen.getByText(options[2].label)).toBeInTheDocument();
175
210
  expect(_react.screen.queryByRole('group')).not.toBeInTheDocument(); // Check for accessibility issues
176
211
 
177
- _context4.t0 = expect;
178
- _context4.next = 12;
212
+ _context5.t0 = expect;
213
+ _context5.next = 12;
179
214
  return (0, _jestAxe.axe)(container);
180
215
 
181
216
  case 12:
182
- _context4.t1 = _context4.sent;
183
- _context4.next = 15;
184
- return (0, _context4.t0)(_context4.t1).toHaveNoViolations();
217
+ _context5.t1 = _context5.sent;
218
+ _context5.next = 15;
219
+ return (0, _context5.t0)(_context5.t1).toHaveNoViolations();
185
220
 
186
221
  case 15:
187
222
  case "end":
188
- return _context4.stop();
223
+ return _context5.stop();
189
224
  }
190
225
  }
191
- }, _callee4);
226
+ }, _callee5);
192
227
  })));
193
228
  });
@@ -24,12 +24,12 @@ var FormSelectArrow = /*#__PURE__*/_react["default"].forwardRef(function (props,
24
24
  "aria-hidden": "true",
25
25
  size: 24,
26
26
  sx: {
27
+ position: 'absolute',
27
28
  paddingLeft: 2,
28
29
  borderLeftWidth: '1px',
29
30
  borderLeftStyle: 'solid',
30
31
  borderLeftColor: 'border',
31
- position: 'relative',
32
- right: 34,
32
+ right: 2,
33
33
  pointerEvents: 'none'
34
34
  }
35
35
  }, props));
@@ -23,12 +23,14 @@ var _jsxRuntime = require("theme-ui/jsx-runtime");
23
23
  * Internal dependencies
24
24
  */
25
25
  var defaultStyles = {
26
- '&:hover select': {
27
- borderColor: 'border'
28
- },
26
+ position: 'relative',
27
+ width: '100%',
29
28
  display: 'inline-flex',
30
29
  flexDirection: 'row',
31
- alignItems: 'center'
30
+ alignItems: 'center',
31
+ '&:hover select': {
32
+ borderColor: 'border'
33
+ }
32
34
  };
33
35
 
34
36
  var FormSelectContent = /*#__PURE__*/_react["default"].forwardRef(function (_ref, forwardRef) {
@@ -3,8 +3,9 @@
3
3
  exports.__esModule = true;
4
4
  exports.inlineStyles = void 0;
5
5
  var inlineStyles = {
6
+ display: 'grid',
7
+ gridTemplateColumns: 'auto 1fr',
6
8
  position: 'relative',
7
- display: 'inline-flex',
8
9
  alignItems: 'center',
9
10
  borderColor: 'border',
10
11
  borderRadius: 1,
@@ -66,6 +66,35 @@ var Default = function Default() {
66
66
  },
67
67
  children: "This notice has a title and children"
68
68
  })
69
+ }), (0, _jsxRuntime.jsxs)(_.Notice, {
70
+ variant: "alert",
71
+ sx: {
72
+ mb: 4
73
+ },
74
+ children: [(0, _jsxRuntime.jsx)(_.Heading, {
75
+ variant: "h4",
76
+ children: "There are errors in your form"
77
+ }), (0, _jsxRuntime.jsxs)("ul", {
78
+ sx: {
79
+ mb: 0
80
+ },
81
+ children: [(0, _jsxRuntime.jsx)("li", {
82
+ children: (0, _jsxRuntime.jsx)("a", {
83
+ href: "#name",
84
+ children: "Please enter your name."
85
+ })
86
+ }), (0, _jsxRuntime.jsx)("li", {
87
+ children: (0, _jsxRuntime.jsx)("a", {
88
+ href: "#email",
89
+ children: "Please enter your email address."
90
+ })
91
+ }), (0, _jsxRuntime.jsx)("li", {
92
+ children: (0, _jsxRuntime.jsx)("a", {
93
+ href: "#terms",
94
+ children: "Please agree to the terms."
95
+ })
96
+ })]
97
+ })]
69
98
  })]
70
99
  });
71
100
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip-design-system",
3
- "version": "0.23.0",
3
+ "version": "0.23.2",
4
4
  "main": "build/system/index.js",
5
5
  "scripts": {
6
6
  "build-storybook": "build-storybook",
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * External dependencies
5
5
  */
6
- import React, { useCallback } from 'react';
6
+ import React, { useCallback, useMemo } from 'react';
7
7
  import PropTypes from 'prop-types';
8
8
  import { Label } from '../Form/Label';
9
9
 
@@ -23,6 +23,8 @@ const defaultStyles = {
23
23
  py: 0,
24
24
  borderColor: 'border',
25
25
  borderRadius: 1,
26
+ backgroundColor: 'background',
27
+ color: 'text',
26
28
  appearance: 'none',
27
29
  minHeight: '36px',
28
30
  '&:focus': theme => theme.outline,
@@ -68,6 +70,15 @@ const FormSelect = React.forwardRef(
68
70
  );
69
71
  }
70
72
 
73
+ const getAllOptions = useMemo(
74
+ () =>
75
+ [
76
+ ...options.filter( option => ! option.options ),
77
+ ...options.filter( option => option.options ).map( option => option.options ),
78
+ ].reduce( ( a, b ) => a.concat( b ), [] ),
79
+ [ options ]
80
+ );
81
+
71
82
  const optionLabel = useCallback(
72
83
  option => ( getOptionLabel ? getOptionLabel( option ) : option.label ),
73
84
  [ getOptionLabel ]
@@ -79,8 +90,8 @@ const FormSelect = React.forwardRef(
79
90
  );
80
91
 
81
92
  const getOptionByValue = useCallback(
82
- value => options.find( option => optionValue( option ) === value ),
83
- [ options, optionValue ]
93
+ value => getAllOptions.find( option => `${ optionValue( option ) }` === `${ value }` ),
94
+ [ getAllOptions, optionValue ]
84
95
  );
85
96
 
86
97
  const onValueChange = useCallback(
@@ -102,13 +113,11 @@ const FormSelect = React.forwardRef(
102
113
  <FormSelectContent isInline={ inlineLabel } label={ inlineLabel ? <SelectLabel /> : null }>
103
114
  <select onChange={ onValueChange } ref={ forwardRef } sx={ defaultStyles } { ...props }>
104
115
  { placeholder && <option>{ placeholder }</option> }
105
- { options.map( ( { options: groupOptions, ...option } ) => {
106
- const value = optionValue( option );
107
-
108
- return value
109
- ? renderOption( optionLabel( option ), value )
110
- : renderGroup( optionLabel( option ), groupOptions );
111
- } ) }
116
+ { options.map( ( { options: groupOptions, ...option } ) =>
117
+ groupOptions
118
+ ? renderGroup( optionLabel( option ), groupOptions )
119
+ : renderOption( optionLabel( option ), optionValue( option ) )
120
+ ) }
112
121
  </select>
113
122
 
114
123
  <FormSelectArrow />
@@ -26,6 +26,17 @@ const options = [
26
26
  { value: 'vanilla', label: 'Vanilla' },
27
27
  ];
28
28
 
29
+ const groupedOptions = [
30
+ {
31
+ label: 'Group name',
32
+ options: options,
33
+ },
34
+ {
35
+ label: 'Another Group name',
36
+ options: options,
37
+ },
38
+ ];
39
+
29
40
  // eslint-disable-next-line react/prop-types
30
41
  const DefaultComponent = ( { label = 'Label', width = 250, onChange, ...rest } ) => (
31
42
  <>
@@ -66,16 +77,7 @@ export const WithGroup = DefaultComponent.bind( {} );
66
77
 
67
78
  WithGroup.args = {
68
79
  label: 'Group Label',
69
- options: [
70
- {
71
- label: 'Group name',
72
- options: options,
73
- },
74
- {
75
- label: 'Another Group name',
76
- options: options,
77
- },
78
- ],
80
+ options: [ ...options, ...groupedOptions ],
79
81
  };
80
82
 
81
83
  export const IsInline = DefaultComponent.bind( {} );
@@ -84,16 +86,7 @@ IsInline.args = {
84
86
  label: 'Inline Select',
85
87
  isInline: true,
86
88
  width: '100%',
87
- options: [
88
- {
89
- label: 'Group name',
90
- options: options,
91
- },
92
- {
93
- label: 'Another Group name',
94
- options: options,
95
- },
96
- ],
89
+ options: groupedOptions,
97
90
  };
98
91
 
99
92
  export const WithOptionLabelAndValue = DefaultComponent.bind( {} );
@@ -121,7 +114,7 @@ export const WithOnChange = () => {
121
114
  placeholder: '- Select -',
122
115
  width: '100%',
123
116
  onChange,
124
- options,
117
+ options: [ ...options, ...groupedOptions ],
125
118
  };
126
119
 
127
120
  return (
@@ -70,6 +70,20 @@ describe( '<FormSelect />', () => {
70
70
  await expect( await axe( container ) ).toHaveNoViolations();
71
71
  } );
72
72
 
73
+ it( 'renders the FormSelect with nullish options', async () => {
74
+ const nullishOptions = [ ...options, { value: null, label: 'Empty' } ];
75
+
76
+ const { container } = render(
77
+ <FormSelect id="my_desert_list" { ...defaultProps } options={ nullishOptions } />
78
+ );
79
+
80
+ expect( screen.getByLabelText( defaultProps.label ) ).toBeInTheDocument();
81
+ expect( screen.getByRole( 'combobox' ) ).toBeInTheDocument();
82
+
83
+ // Check for accessibility issues
84
+ await expect( await axe( container ) ).toHaveNoViolations();
85
+ } );
86
+
73
87
  it( 'renders the FormSelect component when getOptionLabel and getOptionValue', async () => {
74
88
  const props = {
75
89
  ...defaultProps,
@@ -12,12 +12,12 @@ export const FormSelectArrow = React.forwardRef( ( props, forwardRef ) => (
12
12
  aria-hidden="true"
13
13
  size={ 24 }
14
14
  sx={ {
15
+ position: 'absolute',
15
16
  paddingLeft: 2,
16
17
  borderLeftWidth: '1px',
17
18
  borderLeftStyle: 'solid',
18
19
  borderLeftColor: 'border',
19
- position: 'relative',
20
- right: 34,
20
+ right: 2,
21
21
  pointerEvents: 'none',
22
22
  } }
23
23
  { ...props }
@@ -12,10 +12,12 @@ import { inlineStyles } from './FormSelectInline';
12
12
  */
13
13
 
14
14
  const defaultStyles = {
15
- '&:hover select': { borderColor: 'border' },
15
+ position: 'relative',
16
+ width: '100%',
16
17
  display: 'inline-flex',
17
18
  flexDirection: 'row',
18
19
  alignItems: 'center',
20
+ '&:hover select': { borderColor: 'border' },
19
21
  };
20
22
 
21
23
  const FormSelectContent = React.forwardRef( ( { isInline, label, children }, forwardRef ) => (
@@ -1,6 +1,7 @@
1
1
  export const inlineStyles = {
2
+ display: 'grid',
3
+ gridTemplateColumns: 'auto 1fr',
2
4
  position: 'relative',
3
- display: 'inline-flex',
4
5
  alignItems: 'center',
5
6
  borderColor: 'border',
6
7
  borderRadius: 1,
@@ -31,5 +31,20 @@ export const Default = () => (
31
31
  <Notice variant="success" sx={ { mb: 4 } } title="You made it!">
32
32
  <Text sx={ { mb: 0 } }>This notice has a title and children</Text>
33
33
  </Notice>
34
+
35
+ <Notice variant="alert" sx={ { mb: 4 } }>
36
+ <Heading variant="h4">There are errors in your form</Heading>
37
+ <ul sx={ { mb: 0 } }>
38
+ <li>
39
+ <a href="#name">Please enter your name.</a>
40
+ </li>
41
+ <li>
42
+ <a href="#email">Please enter your email address.</a>
43
+ </li>
44
+ <li>
45
+ <a href="#terms">Please agree to the terms.</a>
46
+ </li>
47
+ </ul>
48
+ </Notice>
34
49
  </React.Fragment>
35
50
  );