@automattic/vip-design-system 0.21.0 → 0.23.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.
@@ -24,7 +24,7 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
24
24
  */
25
25
  var styles = {
26
26
  height: '1px',
27
- backgroundColor: 'border',
27
+ backgroundColor: 'borders.2',
28
28
  my: '5px'
29
29
  };
30
30
  exports.styles = styles;
@@ -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,13 @@ var _FormSelectContent = require("./FormSelectContent");
21
21
 
22
22
  var _jsxRuntime = require("theme-ui/jsx-runtime");
23
23
 
24
- var _excluded = ["isInline", "placeholder", "forLabel", "options", "label"];
24
+ var _excluded = ["isInline", "placeholder", "forLabel", "options", "label", "getOptionLabel", "getOptionValue", "onChange"],
25
+ _excluded2 = ["options"];
26
+
27
+ 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); }
28
+
29
+ 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; }
30
+
25
31
  var MAX_SUGGESTED_OPTIONS = 15;
26
32
  var isDev = process.env.NODE_ENV !== 'production';
27
33
  var defaultStyles = {
@@ -69,6 +75,9 @@ var FormSelect = /*#__PURE__*/_react["default"].forwardRef(function (_ref2, forw
69
75
  forLabel = _ref2.forLabel,
70
76
  options = _ref2.options,
71
77
  label = _ref2.label,
78
+ getOptionLabel = _ref2.getOptionLabel,
79
+ getOptionValue = _ref2.getOptionValue,
80
+ onChange = _ref2.onChange,
72
81
  props = (0, _objectWithoutPropertiesLoose2["default"])(_ref2, _excluded);
73
82
 
74
83
  if (isDev && options.length > MAX_SUGGESTED_OPTIONS) {
@@ -76,6 +85,21 @@ var FormSelect = /*#__PURE__*/_react["default"].forwardRef(function (_ref2, forw
76
85
  console.info('For 16 or more items, consider using another component with a typeahead capability.');
77
86
  }
78
87
 
88
+ var optionLabel = (0, _react.useCallback)(function (option) {
89
+ return getOptionLabel ? getOptionLabel(option) : option.label;
90
+ }, [getOptionLabel]);
91
+ var optionValue = (0, _react.useCallback)(function (option) {
92
+ return getOptionValue ? getOptionValue(option) : option.value;
93
+ }, [getOptionValue]);
94
+ var getOptionByValue = (0, _react.useCallback)(function (value) {
95
+ return options.find(function (option) {
96
+ return optionValue(option) === value;
97
+ });
98
+ }, [options, optionValue]);
99
+ var onValueChange = (0, _react.useCallback)(function (event) {
100
+ return onChange ? onChange(getOptionByValue(event.target.value), event) : getOptionByValue(event.target.value);
101
+ }, [onChange, getOptionByValue]);
102
+
79
103
  var SelectLabel = function SelectLabel() {
80
104
  return (0, _jsxRuntime.jsx)(_Label.Label, {
81
105
  htmlFor: forLabel || props.id,
@@ -89,16 +113,17 @@ var FormSelect = /*#__PURE__*/_react["default"].forwardRef(function (_ref2, forw
89
113
  isInline: inlineLabel,
90
114
  label: inlineLabel ? (0, _jsxRuntime.jsx)(SelectLabel, {}) : null,
91
115
  children: [(0, _jsxRuntime.jsxs)("select", (0, _extends2["default"])({
116
+ onChange: onValueChange,
92
117
  ref: forwardRef,
93
118
  sx: defaultStyles
94
119
  }, props, {
95
120
  children: [placeholder && (0, _jsxRuntime.jsx)("option", {
96
121
  children: placeholder
97
122
  }), options.map(function (_ref3) {
98
- var optionLabel = _ref3.label,
99
- value = _ref3.value,
100
- groupOptions = _ref3.options;
101
- return value ? renderOption(optionLabel, value) : renderGroup(optionLabel, groupOptions);
123
+ var groupOptions = _ref3.options,
124
+ option = (0, _objectWithoutPropertiesLoose2["default"])(_ref3, _excluded2);
125
+ var value = optionValue(option);
126
+ return value ? renderOption(optionLabel(option), value) : renderGroup(optionLabel(option), groupOptions);
102
127
  })]
103
128
  })), (0, _jsxRuntime.jsx)(_FormSelectArrow.FormSelectArrow, {})]
104
129
  })]
@@ -112,6 +137,9 @@ FormSelect.propTypes = {
112
137
  forLabel: _propTypes["default"].string,
113
138
  placeholder: _propTypes["default"].string,
114
139
  label: _propTypes["default"].string,
115
- options: _propTypes["default"].array
140
+ options: _propTypes["default"].array,
141
+ getOptionLabel: _propTypes["default"].func,
142
+ getOptionValue: _propTypes["default"].func,
143
+ onChange: _propTypes["default"].func
116
144
  };
117
145
  FormSelect.displayName = 'FormSelect';
@@ -3,17 +3,19 @@
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
4
 
5
5
  exports.__esModule = true;
6
- exports["default"] = exports.WithGroup = exports.IsInline = exports.Default = void 0;
6
+ exports["default"] = exports.WithOptionLabelAndValue = exports.WithOnChange = exports.WithGroup = exports.IsInline = exports.Default = void 0;
7
7
 
8
8
  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 = require("react");
13
+
12
14
  var Form = _interopRequireWildcard(require("."));
13
15
 
14
16
  var _jsxRuntime = require("theme-ui/jsx-runtime");
15
17
 
16
- var _excluded = ["label", "width"];
18
+ var _excluded = ["label", "width", "onChange"];
17
19
 
18
20
  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
21
 
@@ -59,6 +61,7 @@ var DefaultComponent = function DefaultComponent(_ref) {
59
61
  label = _ref$label === void 0 ? 'Label' : _ref$label,
60
62
  _ref$width = _ref.width,
61
63
  width = _ref$width === void 0 ? 250 : _ref$width,
64
+ onChange = _ref.onChange,
62
65
  rest = (0, _objectWithoutPropertiesLoose2["default"])(_ref, _excluded);
63
66
  return (0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
64
67
  children: [(0, _jsxRuntime.jsxs)("p", {
@@ -79,7 +82,8 @@ var DefaultComponent = function DefaultComponent(_ref) {
79
82
  },
80
83
  children: (0, _jsxRuntime.jsx)(Form.Select, (0, _extends2["default"])({
81
84
  id: "form-select",
82
- label: label
85
+ label: label,
86
+ onChange: onChange
83
87
  }, rest))
84
88
  })
85
89
  })]
@@ -117,4 +121,55 @@ IsInline.args = {
117
121
  label: 'Another Group name',
118
122
  options: options
119
123
  }]
120
- };
124
+ };
125
+ var WithOptionLabelAndValue = DefaultComponent.bind({});
126
+ exports.WithOptionLabelAndValue = WithOptionLabelAndValue;
127
+ WithOptionLabelAndValue.args = {
128
+ label: 'Select with getOptionLabel / getOptionValue',
129
+ width: '100%',
130
+ options: options.map(function (_ref2) {
131
+ var label = _ref2.label,
132
+ value = _ref2.value;
133
+ return {
134
+ name: label,
135
+ id: value
136
+ };
137
+ }),
138
+ getOptionLabel: function getOptionLabel(option) {
139
+ return option.name;
140
+ },
141
+ getOptionValue: function getOptionValue(option) {
142
+ return option.id;
143
+ }
144
+ };
145
+
146
+ var WithOnChange = function WithOnChange() {
147
+ var _useState = (0, _react.useState)(null),
148
+ option = _useState[0],
149
+ setOption = _useState[1];
150
+
151
+ var onChange = (0, _react.useCallback)(function (val, event) {
152
+ return setOption({
153
+ obj: val,
154
+ eventValue: event.target.value
155
+ });
156
+ });
157
+ var args = {
158
+ label: 'Select with onChange',
159
+ placeholder: '- Select -',
160
+ width: '100%',
161
+ onChange: onChange,
162
+ options: options
163
+ };
164
+ return (0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
165
+ children: [(0, _jsxRuntime.jsx)(DefaultComponent, (0, _extends2["default"])({
166
+ onChange: onChange
167
+ }, args)), option && (0, _jsxRuntime.jsxs)("p", {
168
+ children: ["Object to JSON: ", JSON.stringify(option.obj)]
169
+ }), option && (0, _jsxRuntime.jsxs)("p", {
170
+ children: ["Original Event Value: ", option.eventValue]
171
+ })]
172
+ });
173
+ };
174
+
175
+ exports.WithOnChange = WithOnChange;
@@ -140,4 +140,54 @@ 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;
145
+
146
+ return _regenerator["default"].wrap(function _callee4$(_context4) {
147
+ while (1) {
148
+ switch (_context4.prev = _context4.next) {
149
+ case 0:
150
+ props = (0, _extends2["default"])({}, defaultProps, {
151
+ options: options.map(function (_ref5) {
152
+ var label = _ref5.label,
153
+ value = _ref5.value;
154
+ return {
155
+ name: label,
156
+ id: value
157
+ };
158
+ }),
159
+ getOptionLabel: function getOptionLabel(option) {
160
+ return option.name;
161
+ },
162
+ getOptionValue: function getOptionValue(option) {
163
+ return option.id;
164
+ }
165
+ });
166
+ _render4 = (0, _react.render)((0, _jsxRuntime.jsx)(_FormSelect.FormSelect, (0, _extends2["default"])({
167
+ id: "my_desert_list"
168
+ }, props))), container = _render4.container;
169
+ expect(_react.screen.getByLabelText(defaultProps.label)).toBeInTheDocument();
170
+ expect(_react.screen.getByRole('combobox')).toBeInTheDocument();
171
+ expect(_react.screen.getAllByRole('option')).toHaveLength(3);
172
+ expect(_react.screen.getByText(options[0].label)).toBeInTheDocument();
173
+ expect(_react.screen.getByText(options[1].label)).toBeInTheDocument();
174
+ expect(_react.screen.getByText(options[2].label)).toBeInTheDocument();
175
+ expect(_react.screen.queryByRole('group')).not.toBeInTheDocument(); // Check for accessibility issues
176
+
177
+ _context4.t0 = expect;
178
+ _context4.next = 12;
179
+ return (0, _jestAxe.axe)(container);
180
+
181
+ case 12:
182
+ _context4.t1 = _context4.sent;
183
+ _context4.next = 15;
184
+ return (0, _context4.t0)(_context4.t1).toHaveNoViolations();
185
+
186
+ case 15:
187
+ case "end":
188
+ return _context4.stop();
189
+ }
190
+ }
191
+ }, _callee4);
192
+ })));
143
193
  });
@@ -3,7 +3,7 @@
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
4
 
5
5
  exports.__esModule = true;
6
- exports.ScreenReaderText = void 0;
6
+ exports.screenReaderTextClass = exports.ScreenReaderText = void 0;
7
7
 
8
8
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
9
 
@@ -22,21 +22,24 @@ var _jsxRuntime = require("theme-ui/jsx-runtime");
22
22
  /**
23
23
  * Internal dependencies
24
24
  */
25
+ var screenReaderTextClass = {
26
+ border: 'none',
27
+ clip: 'rect(1px, 1px, 1px, 1px)',
28
+ clipPath: 'inset(50%)',
29
+ height: '1px',
30
+ margin: '-1px',
31
+ overflow: 'hidden',
32
+ padding: '0',
33
+ position: 'absolute',
34
+ width: '1px',
35
+ wordWrap: 'normal !important'
36
+ };
37
+ exports.screenReaderTextClass = screenReaderTextClass;
38
+
25
39
  var ScreenReaderText = /*#__PURE__*/_react["default"].forwardRef(function (props, forwardRef) {
26
40
  return (0, _jsxRuntime.jsx)("span", (0, _extends2["default"])({
27
41
  className: "screen-reader-text",
28
- sx: {
29
- border: 'none',
30
- clip: 'rect(1px, 1px, 1px, 1px)',
31
- clipPath: 'inset(50%)',
32
- height: '1px',
33
- margin: '-1px',
34
- overflow: 'hidden',
35
- padding: '0',
36
- position: 'absolute',
37
- width: '1px',
38
- wordWrap: 'normal !important'
39
- }
42
+ sx: screenReaderTextClass
40
43
  }, props, {
41
44
  ref: forwardRef,
42
45
  children: props.children
@@ -15,15 +15,26 @@ var _propTypes = _interopRequireDefault(require("prop-types"));
15
15
 
16
16
  var _classnames = _interopRequireDefault(require("classnames"));
17
17
 
18
+ var _ScreenReaderText = require("../ScreenReaderText/ScreenReaderText");
19
+
18
20
  var _jsxRuntime = require("theme-ui/jsx-runtime");
19
21
 
20
- var _excluded = ["sx", "className"];
22
+ var _excluded = ["sx", "className", "children", "caption"];
21
23
 
22
24
  var Table = /*#__PURE__*/_react["default"].forwardRef(function (_ref, forwardRef) {
23
25
  var sx = _ref.sx,
24
26
  className = _ref.className,
27
+ children = _ref.children,
28
+ _ref$caption = _ref.caption,
29
+ caption = _ref$caption === void 0 ? null : _ref$caption,
25
30
  props = (0, _objectWithoutPropertiesLoose2["default"])(_ref, _excluded);
26
- return (0, _jsxRuntime.jsx)("table", (0, _extends2["default"])({
31
+
32
+ if (!caption) {
33
+ // eslint-disable-next-line no-console
34
+ console.warn('[A11Y] Please, add a caption to your table.');
35
+ }
36
+
37
+ return (0, _jsxRuntime.jsxs)("table", (0, _extends2["default"])({
27
38
  sx: (0, _extends2["default"])({
28
39
  width: '100%',
29
40
  minWidth: 1024
@@ -32,12 +43,19 @@ var Table = /*#__PURE__*/_react["default"].forwardRef(function (_ref, forwardRef
32
43
  cellSpacing: 0,
33
44
  className: (0, _classnames["default"])('vip-table-component', className),
34
45
  ref: forwardRef
35
- }, props));
46
+ }, props, {
47
+ children: [caption && (0, _jsxRuntime.jsx)("caption", {
48
+ sx: _ScreenReaderText.screenReaderTextClass,
49
+ children: caption
50
+ }), children]
51
+ }));
36
52
  });
37
53
 
38
54
  exports.Table = Table;
39
55
  Table.displayName = 'Table';
40
56
  Table.propTypes = {
41
57
  sx: _propTypes["default"].object,
42
- className: _propTypes["default"].any
58
+ className: _propTypes["default"].any,
59
+ children: _propTypes["default"].any,
60
+ caption: _propTypes["default"].string.isRequired
43
61
  };
@@ -7,6 +7,8 @@ var _ = require("..");
7
7
 
8
8
  var _jsxRuntime = require("theme-ui/jsx-runtime");
9
9
 
10
+ /** @jsxImportSource theme-ui */
11
+
10
12
  /**
11
13
  * Internal dependencies
12
14
  */
@@ -18,6 +20,7 @@ exports["default"] = _default;
18
20
 
19
21
  var Default = function Default() {
20
22
  return (0, _jsxRuntime.jsxs)(_.Table, {
23
+ caption: "Storybook Example",
21
24
  children: [(0, _jsxRuntime.jsx)("thead", {
22
25
  children: (0, _jsxRuntime.jsx)(_.TableRow, {
23
26
  head: true,
@@ -55,7 +58,8 @@ var Default = function Default() {
55
58
  color: 'muted'
56
59
  },
57
60
  children: "11th Mar 2020, 16:49:22"
58
- }, "time")]
61
+ }, "time")],
62
+ gbc: true
59
63
  }), (0, _jsxRuntime.jsxs)(_.TableRow, {
60
64
  children: [(0, _jsxRuntime.jsx)(_.TableCell, {
61
65
  sx: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip-design-system",
3
- "version": "0.21.0",
3
+ "version": "0.23.0",
4
4
  "main": "build/system/index.js",
5
5
  "scripts": {
6
6
  "build-storybook": "build-storybook",
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "@radix-ui/react-checkbox": "^1.0.0",
24
24
  "@radix-ui/react-dialog": "^1.0.0",
25
- "@radix-ui/react-dropdown-menu": "^1.0.0",
25
+ "@radix-ui/react-dropdown-menu": "^1.0.1-rc.6",
26
26
  "@radix-ui/react-radio-group": "^1.0.0",
27
27
  "@radix-ui/react-switch": "^1.0.0",
28
28
  "@radix-ui/react-tooltip": "^1.0.0",
@@ -8,7 +8,7 @@ import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
8
8
 
9
9
  export const styles = {
10
10
  height: '1px',
11
- backgroundColor: 'border',
11
+ backgroundColor: 'borders.2',
12
12
  my: '5px',
13
13
  };
14
14
 
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * External dependencies
5
5
  */
6
- import React from 'react';
6
+ import React, { useCallback } from 'react';
7
7
  import PropTypes from 'prop-types';
8
8
  import { Label } from '../Form/Label';
9
9
 
@@ -47,7 +47,20 @@ const renderGroup = ( groupLabel, groupOptions ) => {
47
47
  };
48
48
 
49
49
  const FormSelect = React.forwardRef(
50
- ( { isInline, placeholder, forLabel, options, label, ...props }, forwardRef ) => {
50
+ (
51
+ {
52
+ isInline,
53
+ placeholder,
54
+ forLabel,
55
+ options,
56
+ label,
57
+ getOptionLabel,
58
+ getOptionValue,
59
+ onChange,
60
+ ...props
61
+ },
62
+ forwardRef
63
+ ) => {
51
64
  if ( isDev && options.length > MAX_SUGGESTED_OPTIONS ) {
52
65
  // eslint-disable-next-line no-console
53
66
  console.info(
@@ -55,7 +68,31 @@ const FormSelect = React.forwardRef(
55
68
  );
56
69
  }
57
70
 
71
+ const optionLabel = useCallback(
72
+ option => ( getOptionLabel ? getOptionLabel( option ) : option.label ),
73
+ [ getOptionLabel ]
74
+ );
75
+
76
+ const optionValue = useCallback(
77
+ option => ( getOptionValue ? getOptionValue( option ) : option.value ),
78
+ [ getOptionValue ]
79
+ );
80
+
81
+ const getOptionByValue = useCallback(
82
+ value => options.find( option => optionValue( option ) === value ),
83
+ [ options, optionValue ]
84
+ );
85
+
86
+ const onValueChange = useCallback(
87
+ event =>
88
+ onChange
89
+ ? onChange( getOptionByValue( event.target.value ), event )
90
+ : getOptionByValue( event.target.value ),
91
+ [ onChange, getOptionByValue ]
92
+ );
93
+
58
94
  const SelectLabel = () => <Label htmlFor={ forLabel || props.id }>{ label }</Label>;
95
+
59
96
  const inlineLabel = !! ( isInline && label );
60
97
 
61
98
  return (
@@ -63,11 +100,15 @@ const FormSelect = React.forwardRef(
63
100
  { label && ! isInline && <SelectLabel /> }
64
101
 
65
102
  <FormSelectContent isInline={ inlineLabel } label={ inlineLabel ? <SelectLabel /> : null }>
66
- <select ref={ forwardRef } sx={ defaultStyles } { ...props }>
103
+ <select onChange={ onValueChange } ref={ forwardRef } sx={ defaultStyles } { ...props }>
67
104
  { placeholder && <option>{ placeholder }</option> }
68
- { options.map( ( { label: optionLabel, value, options: groupOptions } ) =>
69
- value ? renderOption( optionLabel, value ) : renderGroup( optionLabel, groupOptions )
70
- ) }
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
+ } ) }
71
112
  </select>
72
113
 
73
114
  <FormSelectArrow />
@@ -84,6 +125,9 @@ FormSelect.propTypes = {
84
125
  placeholder: PropTypes.string,
85
126
  label: PropTypes.string,
86
127
  options: PropTypes.array,
128
+ getOptionLabel: PropTypes.func,
129
+ getOptionValue: PropTypes.func,
130
+ onChange: PropTypes.func,
87
131
  };
88
132
 
89
133
  FormSelect.displayName = 'FormSelect';
@@ -3,6 +3,7 @@
3
3
  /**
4
4
  * Internal dependencies
5
5
  */
6
+ import { useCallback, useState } from 'react';
6
7
  import * as Form from '.';
7
8
 
8
9
  export default {
@@ -26,7 +27,7 @@ const options = [
26
27
  ];
27
28
 
28
29
  // eslint-disable-next-line react/prop-types
29
- const DefaultComponent = ( { label = 'Label', width = 250, ...rest } ) => (
30
+ const DefaultComponent = ( { label = 'Label', width = 250, onChange, ...rest } ) => (
30
31
  <>
31
32
  <p>
32
33
  This is a simple wrapper at the top of a browser default select component. This component
@@ -49,7 +50,7 @@ const DefaultComponent = ( { label = 'Label', width = 250, ...rest } ) => (
49
50
  </p>
50
51
  <Form.Root>
51
52
  <div sx={ { width } }>
52
- <Form.Select id="form-select" label={ label } { ...rest } />
53
+ <Form.Select id="form-select" label={ label } onChange={ onChange } { ...rest } />
53
54
  </div>
54
55
  </Form.Root>
55
56
  </>
@@ -94,3 +95,40 @@ IsInline.args = {
94
95
  },
95
96
  ],
96
97
  };
98
+
99
+ export const WithOptionLabelAndValue = DefaultComponent.bind( {} );
100
+
101
+ WithOptionLabelAndValue.args = {
102
+ label: 'Select with getOptionLabel / getOptionValue',
103
+ width: '100%',
104
+ options: options.map( ( { label, value } ) => ( {
105
+ name: label,
106
+ id: value,
107
+ } ) ),
108
+ getOptionLabel: option => option.name,
109
+ getOptionValue: option => option.id,
110
+ };
111
+
112
+ export const WithOnChange = () => {
113
+ const [ option, setOption ] = useState( null );
114
+
115
+ const onChange = useCallback( ( val, event ) =>
116
+ setOption( { obj: val, eventValue: event.target.value } )
117
+ );
118
+
119
+ const args = {
120
+ label: 'Select with onChange',
121
+ placeholder: '- Select -',
122
+ width: '100%',
123
+ onChange,
124
+ options,
125
+ };
126
+
127
+ return (
128
+ <>
129
+ <DefaultComponent onChange={ onChange } { ...args } />
130
+ { option && <p>Object to JSON: { JSON.stringify( option.obj ) }</p> }
131
+ { option && <p>Original Event Value: { option.eventValue }</p> }
132
+ </>
133
+ );
134
+ };
@@ -69,4 +69,29 @@ describe( '<FormSelect />', () => {
69
69
  // Check for accessibility issues
70
70
  await expect( await axe( container ) ).toHaveNoViolations();
71
71
  } );
72
+
73
+ it( 'renders the FormSelect component when getOptionLabel and getOptionValue', async () => {
74
+ const props = {
75
+ ...defaultProps,
76
+ options: options.map( ( { label, value } ) => ( {
77
+ name: label,
78
+ id: value,
79
+ } ) ),
80
+ getOptionLabel: option => option.name,
81
+ getOptionValue: option => option.id,
82
+ };
83
+
84
+ const { container } = render( <FormSelect id="my_desert_list" { ...props } /> );
85
+
86
+ expect( screen.getByLabelText( defaultProps.label ) ).toBeInTheDocument();
87
+ expect( screen.getByRole( 'combobox' ) ).toBeInTheDocument();
88
+ expect( screen.getAllByRole( 'option' ) ).toHaveLength( 3 );
89
+ expect( screen.getByText( options[ 0 ].label ) ).toBeInTheDocument();
90
+ expect( screen.getByText( options[ 1 ].label ) ).toBeInTheDocument();
91
+ expect( screen.getByText( options[ 2 ].label ) ).toBeInTheDocument();
92
+ expect( screen.queryByRole( 'group' ) ).not.toBeInTheDocument();
93
+
94
+ // Check for accessibility issues
95
+ await expect( await axe( container ) ).toHaveNoViolations();
96
+ } );
72
97
  } );
@@ -10,24 +10,21 @@ import PropTypes from 'prop-types';
10
10
  * Internal dependencies
11
11
  */
12
12
 
13
+ export const screenReaderTextClass = {
14
+ border: 'none',
15
+ clip: 'rect(1px, 1px, 1px, 1px)',
16
+ clipPath: 'inset(50%)',
17
+ height: '1px',
18
+ margin: '-1px',
19
+ overflow: 'hidden',
20
+ padding: '0',
21
+ position: 'absolute',
22
+ width: '1px',
23
+ wordWrap: 'normal !important',
24
+ };
25
+
13
26
  export const ScreenReaderText = React.forwardRef( ( props, forwardRef ) => (
14
- <span
15
- className="screen-reader-text"
16
- sx={ {
17
- border: 'none',
18
- clip: 'rect(1px, 1px, 1px, 1px)',
19
- clipPath: 'inset(50%)',
20
- height: '1px',
21
- margin: '-1px',
22
- overflow: 'hidden',
23
- padding: '0',
24
- position: 'absolute',
25
- width: '1px',
26
- wordWrap: 'normal !important',
27
- } }
28
- { ...props }
29
- ref={ forwardRef }
30
- >
27
+ <span className="screen-reader-text" sx={ screenReaderTextClass } { ...props } ref={ forwardRef }>
31
28
  { props.children }
32
29
  </span>
33
30
  ) );
@@ -6,23 +6,38 @@
6
6
  import React from 'react';
7
7
  import PropTypes from 'prop-types';
8
8
  import classNames from 'classnames';
9
+ import { screenReaderTextClass } from '../ScreenReaderText/ScreenReaderText';
9
10
 
10
- const Table = React.forwardRef( ( { sx, className, ...props }, forwardRef ) => (
11
- <table
12
- sx={ { width: '100%', minWidth: 1024, ...sx } }
13
- cellPadding={ 0 }
14
- cellSpacing={ 0 }
15
- className={ classNames( 'vip-table-component', className ) }
16
- ref={ forwardRef }
17
- { ...props }
18
- />
19
- ) );
11
+ const Table = React.forwardRef(
12
+ ( { sx, className, children, caption = null, ...props }, forwardRef ) => {
13
+ if ( ! caption ) {
14
+ // eslint-disable-next-line no-console
15
+ console.warn( '[A11Y] Please, add a caption to your table.' );
16
+ }
17
+
18
+ return (
19
+ <table
20
+ sx={ { width: '100%', minWidth: 1024, ...sx } }
21
+ cellPadding={ 0 }
22
+ cellSpacing={ 0 }
23
+ className={ classNames( 'vip-table-component', className ) }
24
+ ref={ forwardRef }
25
+ { ...props }
26
+ >
27
+ { caption && <caption sx={ screenReaderTextClass }>{ caption }</caption> }
28
+ { children }
29
+ </table>
30
+ );
31
+ }
32
+ );
20
33
 
21
34
  Table.displayName = 'Table';
22
35
 
23
36
  Table.propTypes = {
24
37
  sx: PropTypes.object,
25
38
  className: PropTypes.any,
39
+ children: PropTypes.any,
40
+ caption: PropTypes.string.isRequired,
26
41
  };
27
42
 
28
43
  export { Table };
@@ -1,3 +1,5 @@
1
+ /** @jsxImportSource theme-ui */
2
+
1
3
  /**
2
4
  * Internal dependencies
3
5
  */
@@ -9,7 +11,7 @@ export default {
9
11
  };
10
12
 
11
13
  export const Default = () => (
12
- <Table>
14
+ <Table caption="Storybook Example">
13
15
  <thead>
14
16
  <TableRow head cells={ [ 'User', 'Command', 'Duration', 'Time' ] } />
15
17
  </thead>
@@ -35,6 +37,7 @@ export const Default = () => (
35
37
  11th Mar 2020, 16:49:22
36
38
  </Text>,
37
39
  ] }
40
+ gbc
38
41
  />
39
42
  <TableRow>
40
43
  <TableCell sx={ { backgroundColor: 'lightgray' } }>