@automattic/vip-design-system 2.8.2 → 2.10.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.
@@ -39,6 +39,7 @@ const RadioOption = ( {
39
39
  cursor: 'pointer',
40
40
  borderRadius: 2,
41
41
  minWidth: 220,
42
+ flexGrow: 1,
42
43
  textAlign: 'left',
43
44
  border: '1px solid',
44
45
  borderColor: 'input.radio-box.border.default',
@@ -94,16 +95,6 @@ const RadioOption = ( {
94
95
  );
95
96
  };
96
97
 
97
- RadioOption.propTypes = {
98
- defaultValue: PropTypes.string,
99
- option: PropTypes.object,
100
- name: PropTypes.string,
101
- onChangeHandler: PropTypes.func,
102
- checked: PropTypes.bool,
103
- disabled: PropTypes.bool,
104
- width: PropTypes.string,
105
- };
106
-
107
98
  const RadioBoxGroup = React.forwardRef(
108
99
  (
109
100
  {
@@ -148,11 +139,11 @@ const RadioBoxGroup = React.forwardRef(
148
139
  <fieldset
149
140
  sx={ {
150
141
  border: 0,
151
- p: hasError ? 2 : 0,
152
142
  display: 'inline-block',
153
143
  mb: 2,
144
+ p: 0,
154
145
  ...( hasError
155
- ? { border: '1px solid', borderColor: 'input.border.error', borderRadius: 2 }
146
+ ? { border: '1px solid', borderColor: 'input.border.error', borderRadius: 2, p: 2 }
156
147
  : {} ),
157
148
  } }
158
149
  ref={ forwardRef }
@@ -171,6 +162,7 @@ const RadioBoxGroup = React.forwardRef(
171
162
  <div
172
163
  sx={ {
173
164
  display: 'flex',
165
+ flexWrap: 'wrap',
174
166
  gap: 2,
175
167
  } }
176
168
  >
@@ -1,6 +1,13 @@
1
1
  declare namespace _default {
2
2
  export let title: string;
3
3
  export { RadioBoxGroup as component };
4
+ export namespace parameters {
5
+ namespace docs {
6
+ namespace description {
7
+ let component: string;
8
+ }
9
+ }
10
+ }
4
11
  }
5
12
  export default _default;
6
13
  export function Default(): import("react").JSX.Element;
@@ -11,6 +11,31 @@ import { RadioBoxGroup } from '..';
11
11
  export default {
12
12
  title: 'RadioBoxGroup',
13
13
  component: RadioBoxGroup,
14
+ parameters: {
15
+ docs: {
16
+ description: {
17
+ component: `
18
+ A radio-box-group is a group of radio buttons that are styled as boxes. This component is used
19
+ to allow users to select one option from a list of options.
20
+
21
+ ## Guidance
22
+
23
+ ### When to use the component
24
+
25
+ - <strong>Select an option in a form.</strong> Use a radio-box-group when you want users to select
26
+ a single option from a list of options.
27
+ - <strong>Use as a toggle-group.</strong> Use a radio-box-group with the chip variant when you want
28
+ to allow users to toggle between different options.
29
+
30
+ -------
31
+
32
+ This documentation is heavily inspired by the [U.S Web Design System (USWDS)](https://designsystem.digital.gov/components/tooltip/#package). We use USWDS as trusted source of truth for accessibility and usability best practices.
33
+
34
+ ## Component Properties
35
+ `,
36
+ },
37
+ },
38
+ },
14
39
  };
15
40
 
16
41
  const options = [
@@ -38,7 +63,7 @@ export const Default = () => {
38
63
  defaultValue={ value }
39
64
  onChange={ e => setValue( e.target.value ) }
40
65
  options={ options }
41
- optionWidth="350px"
66
+ optionWidth="300px"
42
67
  />
43
68
  );
44
69
  };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+ import { axe } from 'jest-axe';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { RadioBoxGroup } from './RadioBoxGroup';
11
+
12
+ const defaultProps = {
13
+ options: [
14
+ {
15
+ label: 'One',
16
+ value: 'one',
17
+ description: 'This is desc 1',
18
+ },
19
+ {
20
+ label: 'Two',
21
+ value: 'two',
22
+ description: 'This is desc 2',
23
+ },
24
+ {
25
+ label: 'Three',
26
+ value: 'three',
27
+ description: 'This is desc 3',
28
+ },
29
+ ],
30
+ onChange: jest.fn(),
31
+ };
32
+
33
+ describe( '<RadioBoxGroup />', () => {
34
+ it( 'renders the component', async () => {
35
+ const { container } = render( <RadioBoxGroup { ...defaultProps } /> );
36
+
37
+ const dom = await screen.findAllByRole( 'radio' );
38
+
39
+ expect( dom ).toHaveLength( 3 );
40
+ expect( dom[ 0 ] ).toHaveAttribute( 'value', 'one' );
41
+ expect( dom[ 1 ] ).toHaveAttribute( 'value', 'two' );
42
+ expect( dom[ 2 ] ).toHaveAttribute( 'value', 'three' );
43
+
44
+ // Check for accessibility issues
45
+ expect( await axe( container ) ).toHaveNoViolations();
46
+ } );
47
+ } );
@@ -0,0 +1,25 @@
1
+ /** @jsxImportSource theme-ui */
2
+ /**
3
+ * External dependencies
4
+ */
5
+ import React from 'react';
6
+ type Option = {
7
+ id?: string;
8
+ value: string;
9
+ label: React.ReactNode | string;
10
+ };
11
+ type RadioGroupChipProps = {
12
+ optionWidth?: string;
13
+ name?: string;
14
+ onChange: (e: React.ChangeEvent<HTMLInputElement>, option?: Option) => void;
15
+ groupLabel?: string;
16
+ defaultValue?: string;
17
+ options: Option[];
18
+ disabled?: boolean;
19
+ errorMessage?: string;
20
+ hasError?: boolean;
21
+ required?: boolean;
22
+ size?: 'small' | 'medium';
23
+ };
24
+ declare const RadioGroupChip: React.ForwardRefExoticComponent<RadioGroupChipProps & React.RefAttributes<unknown>>;
25
+ export { RadioGroupChip };
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports.RadioGroupChip = void 0;
5
+ var _react = _interopRequireWildcard(require("react"));
6
+ var _RequiredLabel = require("./RequiredLabel");
7
+ var _Validation = require("./Validation");
8
+ var _ScreenReaderText = _interopRequireDefault(require("../ScreenReaderText"));
9
+ var _jsxRuntime = require("theme-ui/jsx-runtime");
10
+ var _excluded = ["name", "onChange", "groupLabel", "defaultValue", "options", "disabled", "errorMessage", "hasError", "required", "size"];
11
+ /** @jsxImportSource theme-ui */
12
+ /**
13
+ * External dependencies
14
+ */
15
+ /**
16
+ * Internal dependencies
17
+ */
18
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
19
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
20
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
21
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
22
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
23
+ var ChipOption = function ChipOption(_ref) {
24
+ var defaultValue = _ref.defaultValue,
25
+ _ref$option = _ref.option,
26
+ id = _ref$option.id,
27
+ value = _ref$option.value,
28
+ label = _ref$option.label,
29
+ name = _ref.name,
30
+ disabled = _ref.disabled,
31
+ onChangeHandler = _ref.onChangeHandler,
32
+ _ref$size = _ref.size,
33
+ size = _ref$size === void 0 ? 'medium' : _ref$size;
34
+ var checked = "" + defaultValue === "" + value;
35
+ var forLabel = id || value;
36
+ var ref = _react["default"].useRef(null);
37
+ var describedById = "input-radio-box-" + forLabel + "-description";
38
+ return (0, _jsxRuntime.jsxs)("div", {
39
+ id: "o" + forLabel,
40
+ onClick: function onClick() {
41
+ if (ref.current) {
42
+ ref.current.click();
43
+ }
44
+ },
45
+ sx: {
46
+ display: 'inline-flex',
47
+ position: 'relative',
48
+ background: checked ? 'layer.4' : undefined,
49
+ color: 'text',
50
+ minHeight: size === 'small' ? '22px' : '32px',
51
+ boxShadow: checked ? 'low' : undefined,
52
+ '&:hover': {
53
+ background: checked ? 'layer.4' : 'layer.1'
54
+ },
55
+ borderRadius: 1
56
+ },
57
+ children: [(0, _jsxRuntime.jsx)("input", {
58
+ ref: ref,
59
+ type: "radio",
60
+ id: forLabel,
61
+ disabled: disabled,
62
+ name: name,
63
+ checked: checked,
64
+ "aria-checked": checked,
65
+ value: value,
66
+ onChange: onChangeHandler,
67
+ "aria-labelledby": describedById,
68
+ sx: {
69
+ opacity: 0,
70
+ height: 0,
71
+ width: 0,
72
+ position: 'absolute',
73
+ '&:focus-visible + label': function focusVisibleLabel(theme) {
74
+ return theme.outline;
75
+ }
76
+ }
77
+ }), (0, _jsxRuntime.jsx)("label", {
78
+ id: describedById,
79
+ htmlFor: forLabel,
80
+ sx: {
81
+ height: '100%',
82
+ display: 'flex',
83
+ flexDirection: 'column',
84
+ justifyContent: 'center',
85
+ width: '100%',
86
+ px: size === 'small' ? 1 : 3,
87
+ fontWeight: 400,
88
+ fontSize: size === 'small' ? 1 : 2,
89
+ cursor: 'pointer',
90
+ borderRadius: 1
91
+ },
92
+ children: label
93
+ })]
94
+ });
95
+ };
96
+ var RadioGroupChip = exports.RadioGroupChip = /*#__PURE__*/_react["default"].forwardRef(function (_ref2, forwardRef) {
97
+ var _ref2$name = _ref2.name,
98
+ name = _ref2$name === void 0 ? '' : _ref2$name,
99
+ onChange = _ref2.onChange,
100
+ groupLabel = _ref2.groupLabel,
101
+ defaultValue = _ref2.defaultValue,
102
+ options = _ref2.options,
103
+ disabled = _ref2.disabled,
104
+ errorMessage = _ref2.errorMessage,
105
+ hasError = _ref2.hasError,
106
+ required = _ref2.required,
107
+ _ref2$size = _ref2.size,
108
+ size = _ref2$size === void 0 ? 'medium' : _ref2$size,
109
+ props = _objectWithoutPropertiesLoose(_ref2, _excluded);
110
+ var onChangeHandler = (0, _react.useCallback)(function (e) {
111
+ var optionTriggered = options.find(function (option) {
112
+ return "" + option.value === "" + e.target.value;
113
+ });
114
+ onChange(e, optionTriggered);
115
+ }, [onChange]);
116
+ var renderedOptions = options.map(function (option) {
117
+ return (0, _jsxRuntime.jsx)(ChipOption, {
118
+ defaultValue: defaultValue,
119
+ disabled: disabled,
120
+ name: name,
121
+ option: option,
122
+ onChangeHandler: onChangeHandler,
123
+ size: size
124
+ }, (option == null ? void 0 : option.id) || (option == null ? void 0 : option.value));
125
+ });
126
+ return (0, _jsxRuntime.jsxs)("div", {
127
+ children: [(0, _jsxRuntime.jsxs)("fieldset", _extends({
128
+ sx: _extends({
129
+ border: 0,
130
+ background: 'layer.3',
131
+ p: size === 'small' ? '2px' : 1,
132
+ display: 'inline-flex',
133
+ gap: 1,
134
+ borderRadius: 1
135
+ }, hasError ? {
136
+ border: '1px solid',
137
+ borderColor: 'input.border.error',
138
+ borderRadius: 2,
139
+ p: 2
140
+ } : {})
141
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
142
+ // @ts-expect-error
143
+ ,
144
+ ref: forwardRef,
145
+ "aria-required": required,
146
+ role: "radiogroup"
147
+ }, props, {
148
+ children: [groupLabel ? (0, _jsxRuntime.jsxs)("legend", {
149
+ sx: {
150
+ mb: 2
151
+ },
152
+ children: [groupLabel, required ? (0, _jsxRuntime.jsx)(_RequiredLabel.RequiredLabel, {}) : null]
153
+ }) : (0, _jsxRuntime.jsx)(_ScreenReaderText["default"], {
154
+ children: "Choose an option"
155
+ }), (0, _jsxRuntime.jsx)("div", {
156
+ sx: {
157
+ display: 'flex',
158
+ gap: 1
159
+ },
160
+ children: renderedOptions
161
+ })]
162
+ })), hasError && errorMessage && (0, _jsxRuntime.jsx)(_Validation.Validation, {
163
+ isValid: false,
164
+ describedId: groupLabel,
165
+ children: errorMessage
166
+ })]
167
+ });
168
+ });
169
+ RadioGroupChip.displayName = 'RadioGroupChip';
@@ -0,0 +1,35 @@
1
+ /// <reference types="react" />
2
+ declare const _default: {
3
+ title: string;
4
+ component: import("react").ForwardRefExoticComponent<{
5
+ optionWidth?: string | undefined;
6
+ name?: string | undefined;
7
+ onChange: (e: import("react").ChangeEvent<HTMLInputElement>, option?: {
8
+ id?: string | undefined;
9
+ value: string;
10
+ label: import("react").ReactNode;
11
+ } | undefined) => void;
12
+ groupLabel?: string | undefined;
13
+ defaultValue?: string | undefined;
14
+ options: {
15
+ id?: string | undefined;
16
+ value: string;
17
+ label: import("react").ReactNode;
18
+ }[];
19
+ disabled?: boolean | undefined;
20
+ errorMessage?: string | undefined;
21
+ hasError?: boolean | undefined;
22
+ required?: boolean | undefined;
23
+ size?: "small" | "medium" | undefined;
24
+ } & import("react").RefAttributes<unknown>>;
25
+ parameters: {
26
+ docs: {
27
+ description: {
28
+ component: string;
29
+ };
30
+ };
31
+ };
32
+ };
33
+ export default _default;
34
+ export declare const MediumSize: () => import("react").JSX.Element;
35
+ export declare const SmallSize: () => import("react").JSX.Element;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports["default"] = exports.SmallSize = exports.MediumSize = void 0;
5
+ var _react = require("react");
6
+ var _RadioGroupChip = require("./RadioGroupChip");
7
+ var _jsxRuntime = require("theme-ui/jsx-runtime");
8
+ /**
9
+ * External dependencies
10
+ */
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ var _default = exports["default"] = {
15
+ title: 'RadioGroupChip',
16
+ component: _RadioGroupChip.RadioGroupChip,
17
+ parameters: {
18
+ docs: {
19
+ description: {
20
+ component: "\nA radio-group-chip is a group of radio buttons that are styled as boxes. This component is used\nto allow users to toggle between different options\n\n-------\n\nThis documentation is heavily inspired by the [U.S Web Design System (USWDS)](https://designsystem.digital.gov/components/tooltip/#package). We use USWDS as trusted source of truth for accessibility and usability best practices.\n\n## Component Properties\n"
21
+ }
22
+ }
23
+ }
24
+ };
25
+ var MediumSize = exports.MediumSize = function MediumSize() {
26
+ var _useState = (0, _react.useState)('table'),
27
+ value = _useState[0],
28
+ setValue = _useState[1];
29
+ return (0, _jsxRuntime.jsx)(_RadioGroupChip.RadioGroupChip, {
30
+ defaultValue: value,
31
+ onChange: function onChange(e) {
32
+ return setValue(e.target.value);
33
+ },
34
+ options: [{
35
+ label: 'Table',
36
+ value: 'table'
37
+ }, {
38
+ label: 'Grid',
39
+ value: 'grid'
40
+ }]
41
+ });
42
+ };
43
+ var SmallSize = exports.SmallSize = function SmallSize() {
44
+ var _useState2 = (0, _react.useState)('table'),
45
+ value = _useState2[0],
46
+ setValue = _useState2[1];
47
+ return (0, _jsxRuntime.jsx)(_RadioGroupChip.RadioGroupChip, {
48
+ defaultValue: value,
49
+ onChange: function onChange(e) {
50
+ return setValue(e.target.value);
51
+ },
52
+ options: [{
53
+ label: 'Table',
54
+ value: 'table'
55
+ }, {
56
+ label: 'Grid',
57
+ value: 'grid'
58
+ }],
59
+ size: "small"
60
+ });
61
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+
3
+ var _react = require("@testing-library/react");
4
+ var _jestAxe = require("jest-axe");
5
+ var _RadioGroupChip = require("./RadioGroupChip");
6
+ var _jsxRuntime = require("theme-ui/jsx-runtime");
7
+ function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == typeof h && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw new Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(typeof e + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw new Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw new Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
8
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
9
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
10
+ function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } /**
11
+ * External dependencies
12
+ */ /**
13
+ * Internal dependencies
14
+ */
15
+ var defaultProps = {
16
+ options: [{
17
+ label: 'One',
18
+ value: 'one'
19
+ }, {
20
+ label: 'Two',
21
+ value: 'two'
22
+ }, {
23
+ label: 'Three',
24
+ value: 'three'
25
+ }],
26
+ onChange: jest.fn()
27
+ };
28
+ describe('<RadioGroupChip />', function () {
29
+ it('renders the default variant', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
30
+ var _render, container, dom;
31
+ return _regeneratorRuntime().wrap(function _callee$(_context) {
32
+ while (1) switch (_context.prev = _context.next) {
33
+ case 0:
34
+ _render = (0, _react.render)((0, _jsxRuntime.jsx)(_RadioGroupChip.RadioGroupChip, _extends({}, defaultProps))), container = _render.container;
35
+ _context.next = 3;
36
+ return _react.screen.findAllByRole('radio');
37
+ case 3:
38
+ dom = _context.sent;
39
+ expect(dom).toHaveLength(3);
40
+ expect(dom[0]).toHaveAttribute('value', 'one');
41
+ expect(dom[1]).toHaveAttribute('value', 'two');
42
+ expect(dom[2]).toHaveAttribute('value', 'three');
43
+
44
+ // Check for accessibility issues
45
+ _context.t0 = expect;
46
+ _context.next = 11;
47
+ return (0, _jestAxe.axe)(container);
48
+ case 11:
49
+ _context.t1 = _context.sent;
50
+ (0, _context.t0)(_context.t1).toHaveNoViolations();
51
+ case 13:
52
+ case "end":
53
+ return _context.stop();
54
+ }
55
+ }, _callee);
56
+ })));
57
+ });
@@ -13,7 +13,7 @@ var _default = exports["default"] = {
13
13
  parameters: {
14
14
  docs: {
15
15
  description: {
16
- component: "\nA tooltip is a short descriptive message that appears when a user hovers or focuses on an\nelement. Our tooltip aims to be 100% CSS-only. It uses a global css approach to inject the\ntooltip styles.\n\n## Kwown issues\n\n- Storybook uses iframes to render the components. This means that the tooltip box will overlap in the demos, but you can click on each demo page to see the correct render.\n- The current implementation of this component is <strong>NOT</strong> protected from viewport\nclipping (collision). For now, you can pick another <code>vip-tooltip-position</code> if\npossible.\n\n## Guidance\n\n### When to use the tooltip component\n\n- <strong>Helpful, non-critical information.</strong> Use tooltips to strengthen an existing\nmessage.\n- <strong>Enhance confidence.</strong> Use tooltips to increase certainty about an\ninteraction.\n- <strong>Brief descriptions.</strong> Tooltips perform best with succinct helper text.\n- <strong>Lack of space.</strong> Tooltips are useful as a last resort for space-constrained\nUI. Explore other options for keeping content visible without a tooltip.\n\n### When to consider something else\n\n- <strong>Critical information.</strong> Don&apos;t hide information necessary for completing\na task behind an tooltip interaction.\n- <strong>Lengthy descriptions.</strong> Tooltips are microcopy, and should be brief.\nDon&apos;t use a tooltip if you need a lot of text.\n- <strong>Redundant content.</strong> Don&apos;t use a tooltip when its content is repetitive\nor if usability is obvious.\n- <strong>Sufficient space.</strong> If content can fit outside a tooltip, don&apos;t use a\ntooltip.\n\n## Using the component\n\n- <strong>Use affordances.</strong> A hidden tooltip is unusable. Use tooltips only on\nelements that appear interactive, like buttons or links.\n- <strong>Avoid collisions.</strong> Be careful not introduce conflicting hover or focus\nevents.\n- <strong>Use consistently.</strong> If using tooltips in one context, use in all similar\ncontexts.\n- <strong>Don&apos;t block content.</strong> Use the <code>vip-tooltip-position</code> attribute to\nprevent the tooltip from covering other page elements.\n\n-------\n\nThis documentation is heavily inspired by the [U.S Web Design System (USWDS)](https://designsystem.digital.gov/components/tooltip/#package). We use USWDS as trusted source of truth for accessibility and usability best practices.\n\n## Component Properties\n"
16
+ component: "\nA tooltip is a short descriptive message that appears when a user hovers or focuses on an\nelement. Our tooltip aims to be 100% CSS-only. It uses a global css approach to inject the\ntooltip styles.\n\n## Known issues\n\n- Storybook uses iframes to render the components. This means that the tooltip box will overlap in the demos, but you can click on each demo page to see the correct render.\n- The current implementation of this component is <strong>NOT</strong> protected from viewport\nclipping (collision). For now, you can pick another <code>vip-tooltip-position</code> if\npossible.\n\n## Guidance\n\n### When to use the tooltip component\n\n- <strong>Helpful, non-critical information.</strong> Use tooltips to strengthen an existing\nmessage.\n- <strong>Enhance confidence.</strong> Use tooltips to increase certainty about an\ninteraction.\n- <strong>Brief descriptions.</strong> Tooltips perform best with succinct helper text.\n- <strong>Lack of space.</strong> Tooltips are useful as a last resort for space-constrained\nUI. Explore other options for keeping content visible without a tooltip.\n\n### When to consider something else\n\n- <strong>Critical information.</strong> Don&apos;t hide information necessary for completing\na task behind an tooltip interaction.\n- <strong>Lengthy descriptions.</strong> Tooltips are microcopy, and should be brief.\nDon&apos;t use a tooltip if you need a lot of text.\n- <strong>Redundant content.</strong> Don&apos;t use a tooltip when its content is repetitive\nor if usability is obvious.\n- <strong>Sufficient space.</strong> If content can fit outside a tooltip, don&apos;t use a\ntooltip.\n\n## Using the component\n\n- <strong>Use affordances.</strong> A hidden tooltip is unusable. Use tooltips only on\nelements that appear interactive, like buttons or links.\n- <strong>Avoid collisions.</strong> Be careful not introduce conflicting hover or focus\nevents.\n- <strong>Use consistently.</strong> If using tooltips in one context, use in all similar\ncontexts.\n- <strong>Don&apos;t block content.</strong> Use the <code>vip-tooltip-position</code> attribute to\nprevent the tooltip from covering other page elements.\n\n-------\n\nThis documentation is heavily inspired by the [U.S Web Design System (USWDS)](https://designsystem.digital.gov/components/tooltip/#package). We use USWDS as trusted source of truth for accessibility and usability best practices.\n\n## Component Properties\n"
17
17
  }
18
18
  }
19
19
  }
@@ -34,6 +34,7 @@ import { Heading } from './Heading';
34
34
  import { Hr } from './Hr/Hr';
35
35
  import { Input } from './Form';
36
36
  import { Label } from './Form';
37
+ import { ScreenReaderText } from './ScreenReaderText';
37
38
  import { Spinner } from './Spinner';
38
39
  import { Table } from './Table';
39
40
  import { TableRow } from './Table';
@@ -59,4 +60,4 @@ import { Validation } from './Form';
59
60
  import { Wizard } from './Wizard';
60
61
  import { WizardStep } from './Wizard';
61
62
  import theme from './theme';
62
- export { Accordion, Avatar, Badge, Box, Breadcrumbs, Button, ButtonSubmit, ButtonVariant, Card, Checkbox, Code, Dialog, NewDialog, Form, Drawer, Dropdown, DialogButton, DialogMenu, DialogMenuItem, DialogDivider, DialogContent, DialogTrigger, ConfirmationDialog, MobileMenu, MobileMenuTrigger, MobileMenuWrapper, NewConfirmationDialog, Grid, FilterDropdown, Flex, Notice, OptionRow, Heading, Hr, Input, Label, Spinner, Table, TableRow, TableCell, Tooltip, Link, LinkExternal, Radio, RadioBoxGroup, Textarea, Progress, Text, Tabs, Nav, NavItem, TabsTrigger, TabsContent, TabsList, Toggle, ToggleRow, Toolbar, Validation, Wizard, WizardStep, theme };
63
+ export { Accordion, Avatar, Badge, Box, Breadcrumbs, Button, ButtonSubmit, ButtonVariant, Card, Checkbox, Code, Dialog, NewDialog, Form, Drawer, Dropdown, DialogButton, DialogMenu, DialogMenuItem, DialogDivider, DialogContent, DialogTrigger, ConfirmationDialog, MobileMenu, MobileMenuTrigger, MobileMenuWrapper, NewConfirmationDialog, Grid, FilterDropdown, Flex, Notice, OptionRow, Heading, Hr, Input, Label, ScreenReaderText, Spinner, Table, TableRow, TableCell, Tooltip, Link, LinkExternal, Radio, RadioBoxGroup, Textarea, Progress, Text, Tabs, Nav, NavItem, TabsTrigger, TabsContent, TabsList, Toggle, ToggleRow, Toolbar, Validation, Wizard, WizardStep, theme };
@@ -48,6 +48,7 @@ import * as Form from './NewForm';
48
48
  import { Notice } from './Notice';
49
49
  import { OptionRow } from './OptionRow';
50
50
  import { Progress } from './Progress';
51
+ import { ScreenReaderText } from './ScreenReaderText';
51
52
  import { Spinner } from './Spinner';
52
53
  import { Table, TableRow, TableCell } from './Table';
53
54
  import { Tabs, TabsList, TabsContent, TabsTrigger } from './Tabs';
@@ -94,6 +95,7 @@ export {
94
95
  Hr,
95
96
  Input,
96
97
  Label,
98
+ ScreenReaderText,
97
99
  Spinner,
98
100
  Table,
99
101
  TableRow,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip-design-system",
3
- "version": "2.8.2",
3
+ "version": "2.10.0",
4
4
  "main": "build/system/index.js",
5
5
  "scripts": {
6
6
  "build-storybook": "storybook build",
@@ -39,6 +39,7 @@ const RadioOption = ( {
39
39
  cursor: 'pointer',
40
40
  borderRadius: 2,
41
41
  minWidth: 220,
42
+ flexGrow: 1,
42
43
  textAlign: 'left',
43
44
  border: '1px solid',
44
45
  borderColor: 'input.radio-box.border.default',
@@ -94,16 +95,6 @@ const RadioOption = ( {
94
95
  );
95
96
  };
96
97
 
97
- RadioOption.propTypes = {
98
- defaultValue: PropTypes.string,
99
- option: PropTypes.object,
100
- name: PropTypes.string,
101
- onChangeHandler: PropTypes.func,
102
- checked: PropTypes.bool,
103
- disabled: PropTypes.bool,
104
- width: PropTypes.string,
105
- };
106
-
107
98
  const RadioBoxGroup = React.forwardRef(
108
99
  (
109
100
  {
@@ -148,11 +139,11 @@ const RadioBoxGroup = React.forwardRef(
148
139
  <fieldset
149
140
  sx={ {
150
141
  border: 0,
151
- p: hasError ? 2 : 0,
152
142
  display: 'inline-block',
153
143
  mb: 2,
144
+ p: 0,
154
145
  ...( hasError
155
- ? { border: '1px solid', borderColor: 'input.border.error', borderRadius: 2 }
146
+ ? { border: '1px solid', borderColor: 'input.border.error', borderRadius: 2, p: 2 }
156
147
  : {} ),
157
148
  } }
158
149
  ref={ forwardRef }
@@ -171,6 +162,7 @@ const RadioBoxGroup = React.forwardRef(
171
162
  <div
172
163
  sx={ {
173
164
  display: 'flex',
165
+ flexWrap: 'wrap',
174
166
  gap: 2,
175
167
  } }
176
168
  >
@@ -11,6 +11,31 @@ import { RadioBoxGroup } from '..';
11
11
  export default {
12
12
  title: 'RadioBoxGroup',
13
13
  component: RadioBoxGroup,
14
+ parameters: {
15
+ docs: {
16
+ description: {
17
+ component: `
18
+ A radio-box-group is a group of radio buttons that are styled as boxes. This component is used
19
+ to allow users to select one option from a list of options.
20
+
21
+ ## Guidance
22
+
23
+ ### When to use the component
24
+
25
+ - <strong>Select an option in a form.</strong> Use a radio-box-group when you want users to select
26
+ a single option from a list of options.
27
+ - <strong>Use as a toggle-group.</strong> Use a radio-box-group with the chip variant when you want
28
+ to allow users to toggle between different options.
29
+
30
+ -------
31
+
32
+ This documentation is heavily inspired by the [U.S Web Design System (USWDS)](https://designsystem.digital.gov/components/tooltip/#package). We use USWDS as trusted source of truth for accessibility and usability best practices.
33
+
34
+ ## Component Properties
35
+ `,
36
+ },
37
+ },
38
+ },
14
39
  };
15
40
 
16
41
  const options = [
@@ -38,7 +63,7 @@ export const Default = () => {
38
63
  defaultValue={ value }
39
64
  onChange={ e => setValue( e.target.value ) }
40
65
  options={ options }
41
- optionWidth="350px"
66
+ optionWidth="300px"
42
67
  />
43
68
  );
44
69
  };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+ import { axe } from 'jest-axe';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { RadioBoxGroup } from './RadioBoxGroup';
11
+
12
+ const defaultProps = {
13
+ options: [
14
+ {
15
+ label: 'One',
16
+ value: 'one',
17
+ description: 'This is desc 1',
18
+ },
19
+ {
20
+ label: 'Two',
21
+ value: 'two',
22
+ description: 'This is desc 2',
23
+ },
24
+ {
25
+ label: 'Three',
26
+ value: 'three',
27
+ description: 'This is desc 3',
28
+ },
29
+ ],
30
+ onChange: jest.fn(),
31
+ };
32
+
33
+ describe( '<RadioBoxGroup />', () => {
34
+ it( 'renders the component', async () => {
35
+ const { container } = render( <RadioBoxGroup { ...defaultProps } /> );
36
+
37
+ const dom = await screen.findAllByRole( 'radio' );
38
+
39
+ expect( dom ).toHaveLength( 3 );
40
+ expect( dom[ 0 ] ).toHaveAttribute( 'value', 'one' );
41
+ expect( dom[ 1 ] ).toHaveAttribute( 'value', 'two' );
42
+ expect( dom[ 2 ] ).toHaveAttribute( 'value', 'three' );
43
+
44
+ // Check for accessibility issues
45
+ expect( await axe( container ) ).toHaveNoViolations();
46
+ } );
47
+ } );
@@ -0,0 +1,73 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { useState } from 'react';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { RadioGroupChip } from './RadioGroupChip';
10
+
11
+ export default {
12
+ title: 'RadioGroupChip',
13
+ component: RadioGroupChip,
14
+ parameters: {
15
+ docs: {
16
+ description: {
17
+ component: `
18
+ A radio-group-chip is a group of radio buttons that are styled as boxes. This component is used
19
+ to allow users to toggle between different options
20
+
21
+ -------
22
+
23
+ This documentation is heavily inspired by the [U.S Web Design System (USWDS)](https://designsystem.digital.gov/components/tooltip/#package). We use USWDS as trusted source of truth for accessibility and usability best practices.
24
+
25
+ ## Component Properties
26
+ `,
27
+ },
28
+ },
29
+ },
30
+ };
31
+
32
+ export const MediumSize = () => {
33
+ const [ value, setValue ] = useState( 'table' );
34
+
35
+ return (
36
+ <RadioGroupChip
37
+ defaultValue={ value }
38
+ onChange={ e => setValue( e.target.value ) }
39
+ options={ [
40
+ {
41
+ label: 'Table',
42
+ value: 'table',
43
+ },
44
+ {
45
+ label: 'Grid',
46
+ value: 'grid',
47
+ },
48
+ ] }
49
+ />
50
+ );
51
+ };
52
+
53
+ export const SmallSize = () => {
54
+ const [ value, setValue ] = useState( 'table' );
55
+
56
+ return (
57
+ <RadioGroupChip
58
+ defaultValue={ value }
59
+ onChange={ e => setValue( e.target.value ) }
60
+ options={ [
61
+ {
62
+ label: 'Table',
63
+ value: 'table',
64
+ },
65
+ {
66
+ label: 'Grid',
67
+ value: 'grid',
68
+ },
69
+ ] }
70
+ size="small"
71
+ />
72
+ );
73
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { render, screen } from '@testing-library/react';
5
+ import { axe } from 'jest-axe';
6
+
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import { RadioGroupChip } from './RadioGroupChip';
11
+
12
+ const defaultProps = {
13
+ options: [
14
+ {
15
+ label: 'One',
16
+ value: 'one',
17
+ },
18
+ {
19
+ label: 'Two',
20
+ value: 'two',
21
+ },
22
+ {
23
+ label: 'Three',
24
+ value: 'three',
25
+ },
26
+ ],
27
+ onChange: jest.fn(),
28
+ };
29
+
30
+ describe( '<RadioGroupChip />', () => {
31
+ it( 'renders the default variant', async () => {
32
+ const { container } = render( <RadioGroupChip { ...defaultProps } /> );
33
+
34
+ const dom = await screen.findAllByRole( 'radio' );
35
+
36
+ expect( dom ).toHaveLength( 3 );
37
+ expect( dom[ 0 ] ).toHaveAttribute( 'value', 'one' );
38
+ expect( dom[ 1 ] ).toHaveAttribute( 'value', 'two' );
39
+ expect( dom[ 2 ] ).toHaveAttribute( 'value', 'three' );
40
+
41
+ // Check for accessibility issues
42
+ expect( await axe( container ) ).toHaveNoViolations();
43
+ } );
44
+ } );
@@ -0,0 +1,216 @@
1
+ /** @jsxImportSource theme-ui */
2
+
3
+ /**
4
+ * External dependencies
5
+ */
6
+ import React, { useCallback } from 'react';
7
+ import { Theme } from 'theme-ui';
8
+
9
+ import { RequiredLabel } from './RequiredLabel';
10
+ import { Validation } from './Validation';
11
+ import ScreenReaderText from '../ScreenReaderText';
12
+
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+
17
+ interface InputTheme extends Theme {
18
+ outline?: Record< string, string >;
19
+ }
20
+
21
+ type Option = {
22
+ id?: string;
23
+ value: string;
24
+ label: React.ReactNode | string;
25
+ };
26
+
27
+ type ChipOptionProps = {
28
+ defaultValue?: string;
29
+ option: Option;
30
+ name: string;
31
+ disabled?: boolean;
32
+ onChangeHandler: ( e: React.ChangeEvent< HTMLInputElement > ) => void;
33
+ size: 'small' | 'medium';
34
+ };
35
+
36
+ const ChipOption = ( {
37
+ defaultValue,
38
+ option: { id, value, label },
39
+ name,
40
+ disabled,
41
+ onChangeHandler,
42
+ size = 'medium',
43
+ }: ChipOptionProps ) => {
44
+ const checked = `${ defaultValue }` === `${ value }`;
45
+ const forLabel = id || value;
46
+ const ref = React.useRef( null );
47
+ const describedById = `input-radio-box-${ forLabel }-description`;
48
+
49
+ return (
50
+ <div
51
+ id={ `o${ forLabel }` }
52
+ onClick={ () => {
53
+ if ( ref.current ) {
54
+ ( ref.current as HTMLInputElement ).click();
55
+ }
56
+ } }
57
+ sx={ {
58
+ display: 'inline-flex',
59
+ position: 'relative',
60
+ background: checked ? 'layer.4' : undefined,
61
+ color: 'text',
62
+ minHeight: size === 'small' ? '22px' : '32px',
63
+ boxShadow: checked ? 'low' : undefined,
64
+ '&:hover': {
65
+ background: checked ? 'layer.4' : 'layer.1',
66
+ },
67
+ borderRadius: 1,
68
+ } }
69
+ >
70
+ <input
71
+ ref={ ref }
72
+ type="radio"
73
+ id={ forLabel }
74
+ disabled={ disabled }
75
+ name={ name }
76
+ checked={ checked }
77
+ aria-checked={ checked }
78
+ value={ value }
79
+ onChange={ onChangeHandler }
80
+ aria-labelledby={ describedById }
81
+ sx={ {
82
+ opacity: 0,
83
+ height: 0,
84
+ width: 0,
85
+ position: 'absolute',
86
+ '&:focus-visible + label': ( theme: InputTheme ) => theme.outline,
87
+ } }
88
+ />
89
+
90
+ <label
91
+ id={ describedById }
92
+ htmlFor={ forLabel }
93
+ sx={ {
94
+ height: '100%',
95
+ display: 'flex',
96
+ flexDirection: 'column',
97
+ justifyContent: 'center',
98
+ width: '100%',
99
+ px: size === 'small' ? 1 : 3,
100
+ fontWeight: 400,
101
+ fontSize: size === 'small' ? 1 : 2,
102
+ cursor: 'pointer',
103
+ borderRadius: 1,
104
+ } }
105
+ >
106
+ { label }
107
+ </label>
108
+ </div>
109
+ );
110
+ };
111
+
112
+ type RadioGroupChipProps = {
113
+ optionWidth?: string;
114
+ name?: string;
115
+ onChange: ( e: React.ChangeEvent< HTMLInputElement >, option?: Option ) => void;
116
+ groupLabel?: string;
117
+ defaultValue?: string;
118
+ options: Option[];
119
+ disabled?: boolean;
120
+ errorMessage?: string;
121
+ hasError?: boolean;
122
+ required?: boolean;
123
+ size?: 'small' | 'medium';
124
+ };
125
+
126
+ const RadioGroupChip = React.forwardRef(
127
+ (
128
+ {
129
+ name = '',
130
+ onChange,
131
+ groupLabel,
132
+ defaultValue,
133
+ options,
134
+ disabled,
135
+ errorMessage,
136
+ hasError,
137
+ required,
138
+ size = 'medium',
139
+ ...props
140
+ }: RadioGroupChipProps,
141
+ forwardRef
142
+ ) => {
143
+ const onChangeHandler = useCallback(
144
+ ( e: React.ChangeEvent< HTMLInputElement > ) => {
145
+ const optionTriggered = options.find(
146
+ option => `${ option.value }` === `${ e.target.value }`
147
+ );
148
+ onChange( e, optionTriggered );
149
+ },
150
+ [ onChange ]
151
+ );
152
+
153
+ const renderedOptions = options.map( option => (
154
+ <ChipOption
155
+ defaultValue={ defaultValue }
156
+ disabled={ disabled }
157
+ key={ option?.id || option?.value }
158
+ name={ name }
159
+ option={ option }
160
+ onChangeHandler={ onChangeHandler }
161
+ size={ size }
162
+ />
163
+ ) );
164
+
165
+ return (
166
+ <div>
167
+ <fieldset
168
+ sx={ {
169
+ border: 0,
170
+ background: 'layer.3',
171
+ p: size === 'small' ? '2px' : 1,
172
+ display: 'inline-flex',
173
+ gap: 1,
174
+ borderRadius: 1,
175
+ ...( hasError
176
+ ? { border: '1px solid', borderColor: 'input.border.error', borderRadius: 2, p: 2 }
177
+ : {} ),
178
+ } }
179
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
180
+ // @ts-expect-error
181
+ ref={ forwardRef }
182
+ aria-required={ required }
183
+ role="radiogroup"
184
+ { ...props }
185
+ >
186
+ { groupLabel ? (
187
+ <legend sx={ { mb: 2 } }>
188
+ { groupLabel }
189
+ { required ? <RequiredLabel /> : null }
190
+ </legend>
191
+ ) : (
192
+ <ScreenReaderText>Choose an option</ScreenReaderText>
193
+ ) }
194
+ <div
195
+ sx={ {
196
+ display: 'flex',
197
+ gap: 1,
198
+ } }
199
+ >
200
+ { renderedOptions }
201
+ </div>
202
+ </fieldset>
203
+
204
+ { hasError && errorMessage && (
205
+ <Validation isValid={ false } describedId={ groupLabel }>
206
+ { errorMessage }
207
+ </Validation>
208
+ ) }
209
+ </div>
210
+ );
211
+ }
212
+ );
213
+
214
+ RadioGroupChip.displayName = 'RadioGroupChip';
215
+
216
+ export { RadioGroupChip };
@@ -14,7 +14,7 @@ A tooltip is a short descriptive message that appears when a user hovers or focu
14
14
  element. Our tooltip aims to be 100% CSS-only. It uses a global css approach to inject the
15
15
  tooltip styles.
16
16
 
17
- ## Kwown issues
17
+ ## Known issues
18
18
 
19
19
  - Storybook uses iframes to render the components. This means that the tooltip box will overlap in the demos, but you can click on each demo page to see the correct render.
20
20
  - The current implementation of this component is <strong>NOT</strong> protected from viewport
@@ -48,6 +48,7 @@ import * as Form from './NewForm';
48
48
  import { Notice } from './Notice';
49
49
  import { OptionRow } from './OptionRow';
50
50
  import { Progress } from './Progress';
51
+ import { ScreenReaderText } from './ScreenReaderText';
51
52
  import { Spinner } from './Spinner';
52
53
  import { Table, TableRow, TableCell } from './Table';
53
54
  import { Tabs, TabsList, TabsContent, TabsTrigger } from './Tabs';
@@ -94,6 +95,7 @@ export {
94
95
  Hr,
95
96
  Input,
96
97
  Label,
98
+ ScreenReaderText,
97
99
  Spinner,
98
100
  Table,
99
101
  TableRow,