@telus-uds/components-base 1.4.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/.turbo/turbo-lint.log +13 -0
  3. package/CHANGELOG.json +140 -1
  4. package/CHANGELOG.md +42 -2
  5. package/__tests__/FlexGrid/Row.test.jsx +100 -25
  6. package/__tests__/utils/containUniqueFields.test.js +25 -0
  7. package/component-docs.json +94 -16
  8. package/generate-component-docs.js +20 -7
  9. package/lib/Button/ButtonBase.js +1 -1
  10. package/lib/Button/ButtonGroup.js +20 -12
  11. package/lib/Card/PressableCardBase.js +1 -1
  12. package/lib/Checkbox/Checkbox.js +27 -16
  13. package/lib/Checkbox/CheckboxGroup.js +19 -5
  14. package/lib/ExpandCollapse/Panel.js +10 -10
  15. package/lib/FlexGrid/Col/Col.js +13 -3
  16. package/lib/FlexGrid/Row/Row.js +8 -2
  17. package/lib/HorizontalScroll/HorizontalScroll.js +0 -1
  18. package/lib/HorizontalScroll/HorizontalScrollButton.js +23 -49
  19. package/lib/InputLabel/InputLabel.js +27 -25
  20. package/lib/Link/LinkBase.js +19 -6
  21. package/lib/Link/TextButton.js +1 -10
  22. package/lib/Modal/Modal.js +18 -18
  23. package/lib/Radio/Radio.js +23 -12
  24. package/lib/Radio/RadioGroup.js +12 -3
  25. package/lib/RadioCard/RadioCard.js +1 -1
  26. package/lib/RadioCard/RadioCardGroup.js +11 -2
  27. package/lib/Select/Select.js +2 -3
  28. package/lib/Tags/Tags.js +23 -17
  29. package/lib/TextInput/TextArea.js +2 -2
  30. package/lib/TextInput/TextInput.js +12 -2
  31. package/lib/TextInput/TextInputBase.js +1 -1
  32. package/lib/TextInput/propTypes.js +8 -1
  33. package/lib/ToggleSwitch/ToggleSwitch.js +5 -2
  34. package/lib/ToggleSwitch/ToggleSwitchGroup.js +20 -12
  35. package/lib/utils/containUniqueFields.js +34 -0
  36. package/lib/utils/index.js +10 -1
  37. package/lib/utils/props/handlerProps.js +72 -0
  38. package/lib/utils/props/index.js +14 -0
  39. package/lib/utils/props/inputSupportsProps.js +3 -5
  40. package/lib/utils/props/linkProps.js +3 -7
  41. package/lib-module/Button/ButtonBase.js +2 -2
  42. package/lib-module/Button/ButtonGroup.js +15 -6
  43. package/lib-module/Card/PressableCardBase.js +2 -2
  44. package/lib-module/Checkbox/Checkbox.js +28 -17
  45. package/lib-module/Checkbox/CheckboxGroup.js +20 -7
  46. package/lib-module/ExpandCollapse/Panel.js +10 -10
  47. package/lib-module/FlexGrid/Col/Col.js +13 -3
  48. package/lib-module/FlexGrid/Row/Row.js +8 -2
  49. package/lib-module/HorizontalScroll/HorizontalScroll.js +0 -1
  50. package/lib-module/HorizontalScroll/HorizontalScrollButton.js +24 -49
  51. package/lib-module/InputLabel/InputLabel.js +28 -25
  52. package/lib-module/Link/LinkBase.js +19 -6
  53. package/lib-module/Link/TextButton.js +1 -10
  54. package/lib-module/Modal/Modal.js +19 -19
  55. package/lib-module/Radio/Radio.js +24 -13
  56. package/lib-module/Radio/RadioGroup.js +13 -4
  57. package/lib-module/RadioCard/RadioCard.js +2 -2
  58. package/lib-module/RadioCard/RadioCardGroup.js +12 -3
  59. package/lib-module/Select/Select.js +2 -3
  60. package/lib-module/Tags/Tags.js +18 -11
  61. package/lib-module/TextInput/TextArea.js +3 -3
  62. package/lib-module/TextInput/TextInput.js +11 -3
  63. package/lib-module/TextInput/TextInputBase.js +2 -2
  64. package/lib-module/TextInput/propTypes.js +7 -1
  65. package/lib-module/ToggleSwitch/ToggleSwitch.js +6 -3
  66. package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +15 -6
  67. package/lib-module/utils/containUniqueFields.js +26 -0
  68. package/lib-module/utils/index.js +2 -1
  69. package/lib-module/utils/props/handlerProps.js +59 -0
  70. package/lib-module/utils/props/index.js +1 -0
  71. package/lib-module/utils/props/inputSupportsProps.js +3 -5
  72. package/lib-module/utils/props/linkProps.js +3 -7
  73. package/package.json +5 -5
  74. package/src/Button/ButtonBase.jsx +8 -2
  75. package/src/Button/ButtonGroup.jsx +51 -34
  76. package/src/Card/PressableCardBase.jsx +6 -1
  77. package/src/Checkbox/Checkbox.jsx +35 -23
  78. package/src/Checkbox/CheckboxGroup.jsx +52 -22
  79. package/src/ExpandCollapse/Panel.jsx +9 -9
  80. package/src/FlexGrid/Col/Col.jsx +11 -2
  81. package/src/FlexGrid/Row/Row.jsx +8 -2
  82. package/src/HorizontalScroll/HorizontalScroll.jsx +1 -1
  83. package/src/HorizontalScroll/HorizontalScrollButton.jsx +21 -58
  84. package/src/InputLabel/InputLabel.jsx +36 -27
  85. package/src/Link/LinkBase.jsx +20 -4
  86. package/src/Link/TextButton.jsx +1 -19
  87. package/src/Modal/Modal.jsx +30 -26
  88. package/src/Radio/Radio.jsx +26 -14
  89. package/src/Radio/RadioGroup.jsx +39 -21
  90. package/src/RadioCard/RadioCard.jsx +6 -1
  91. package/src/RadioCard/RadioCardGroup.jsx +17 -1
  92. package/src/Select/Select.jsx +2 -2
  93. package/src/Tags/Tags.jsx +23 -9
  94. package/src/TextInput/TextArea.jsx +5 -1
  95. package/src/TextInput/TextInput.jsx +13 -3
  96. package/src/TextInput/TextInputBase.jsx +6 -1
  97. package/src/TextInput/propTypes.js +7 -1
  98. package/src/ToggleSwitch/ToggleSwitch.jsx +11 -2
  99. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +19 -6
  100. package/src/utils/containUniqueFields.js +32 -0
  101. package/src/utils/index.js +1 -0
  102. package/src/utils/props/handlerProps.js +47 -0
  103. package/src/utils/props/index.js +1 -0
  104. package/src/utils/props/inputSupportsProps.js +3 -4
  105. package/src/utils/props/linkProps.js +3 -6
  106. package/stories/InputLabel/InputLabel.stories.jsx +25 -28
  107. package/stories/Modal/Modal.stories.jsx +25 -0
  108. package/stories/Search/Search.stories.jsx +4 -1
  109. package/stories/TextInput/TextInput.stories.jsx +40 -2
  110. package/__tests__/Link/LinkBase.test.jsx +0 -22
@@ -1,10 +1,11 @@
1
1
  import React, { forwardRef } from 'react';
2
- import { a11yProps, getTokensPropType, inputSupportsProps, selectSystemProps, variantProp, viewProps } from '../utils';
2
+ import Platform from "react-native-web/dist/exports/Platform";
3
+ import { a11yProps, focusHandlerProps, getTokensPropType, inputSupportsProps, selectSystemProps, textInputHandlerProps, variantProp, viewProps } from '../utils';
3
4
  import InputSupports from '../InputSupports';
4
5
  import TextInputBase from './TextInputBase';
5
6
  import textInputPropTypes from './propTypes';
6
7
  import { jsx as _jsx } from "react/jsx-runtime";
7
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, inputSupportsProps, viewProps]);
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, focusHandlerProps, inputSupportsProps, textInputHandlerProps, viewProps]);
8
9
  /**
9
10
  * A basic text input component. Use in forms or individually to receive user's input.
10
11
  * Due to React Native's implementation of `TextInput` it's not possible to access the current value by passing a ref.
@@ -27,10 +28,17 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, inp
27
28
  const TextInput = /*#__PURE__*/forwardRef(({
28
29
  tokens,
29
30
  variant = {},
31
+ pattern,
30
32
  ...rest
31
33
  }, ref) => {
34
+ React.useEffect(() => {
35
+ if (Platform.OS === 'web' && pattern && ref.current) {
36
+ // eslint-disable-next-line no-param-reassign
37
+ ref.current.pattern = pattern;
38
+ }
39
+ }, [ref, pattern]);
32
40
  const {
33
- props: supportsProps,
41
+ supportsProps,
34
42
  ...selectedProps
35
43
  } = selectProps(rest);
36
44
  const inputProps = { ...selectedProps,
@@ -5,10 +5,10 @@ import NativeTextInput from "react-native-web/dist/exports/TextInput";
5
5
  import View from "react-native-web/dist/exports/View";
6
6
  import PropTypes from 'prop-types';
7
7
  import { applyTextStyles, useThemeTokens, applyOuterBorder } from '../ThemeProvider';
8
- import { a11yProps, getTokensPropType, selectSystemProps, useInputValue, variantProp, viewProps } from '../utils';
8
+ import { a11yProps, getTokensPropType, selectSystemProps, textInputHandlerProps, useInputValue, variantProp, viewProps } from '../utils';
9
9
  import { jsx as _jsx } from "react/jsx-runtime";
10
10
  import { jsxs as _jsxs } from "react/jsx-runtime";
11
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
11
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, textInputHandlerProps, viewProps]);
12
12
 
13
13
  const selectInputStyles = ({
14
14
  backgroundColor,
@@ -1,4 +1,5 @@
1
1
  import PropTypes from 'prop-types';
2
+ import Platform from "react-native-web/dist/exports/Platform";
2
3
  const textInputPropTypes = {
3
4
  /**
4
5
  * If the input's state is to be controlled by a parent component, use this prop
@@ -26,6 +27,11 @@ const textInputPropTypes = {
26
27
  * Use to react upon input's value changes. Required when the `value` prop is set.
27
28
  * Will receive the input's value as an argument.
28
29
  */
29
- onChange: PropTypes.func
30
+ onChange: PropTypes.func,
31
+ ...Platform.select({
32
+ web: {
33
+ pattern: PropTypes.string
34
+ }
35
+ })
30
36
  };
31
37
  export default textInputPropTypes;
@@ -7,11 +7,11 @@ import InputLabel from '../InputLabel';
7
7
  import ButtonBase from '../Button/ButtonBase';
8
8
  import StackView from '../StackView';
9
9
  import { useThemeTokensCallback, applyShadowToken } from '../ThemeProvider';
10
- import { a11yProps, getTokensPropType, selectTokens, pressProps, selectSystemProps, useUniqueId, variantProp, viewProps } from '../utils';
10
+ import { a11yProps, focusHandlerProps, getTokensPropType, selectTokens, pressProps, selectSystemProps, useUniqueId, variantProp, viewProps } from '../utils';
11
11
  import { useInputValue } from '../utils/input';
12
12
  import { jsx as _jsx } from "react/jsx-runtime";
13
13
  import { jsxs as _jsxs } from "react/jsx-runtime";
14
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, pressProps, viewProps]);
14
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, focusHandlerProps, pressProps, viewProps]);
15
15
 
16
16
  const selectButtonTokens = tokens => selectTokens('Button', { ...tokens,
17
17
  // Width tokens are applied to our inner track. Disable Button width token so it wraps our track width.
@@ -117,7 +117,7 @@ const ToggleSwitch = /*#__PURE__*/forwardRef(({
117
117
  space: 2,
118
118
  direction: "row",
119
119
  children: [Boolean(label) && /*#__PURE__*/_jsx(View, {
120
- style: selectLabelStyles(themeTokens),
120
+ style: [selectLabelStyles(themeTokens), staticStyles.containText],
121
121
  children: /*#__PURE__*/_jsx(InputLabel, {
122
122
  forId: inputId,
123
123
  label: label,
@@ -219,6 +219,9 @@ const staticStyles = StyleSheet.create({
219
219
  switch: {
220
220
  alignItems: 'center',
221
221
  justifyContent: 'center'
222
+ },
223
+ containText: {
224
+ flexShrink: 1
222
225
  }
223
226
  });
224
227
  export default ToggleSwitch;
@@ -7,10 +7,10 @@ import Fieldset from '../Fieldset';
7
7
  import { getStackedContent } from '../StackView';
8
8
  import { useViewport } from '../ViewportProvider';
9
9
  import { useThemeTokens } from '../ThemeProvider';
10
- import { a11yProps, getTokensPropType, pressProps, selectSystemProps, variantProp, viewProps } from '../utils/props';
11
- import { useMultipleInputValues } from '../utils/input';
10
+ import { a11yProps, containUniqueFields, focusHandlerProps, getTokensPropType, selectSystemProps, useMultipleInputValues, variantProp, viewProps } from '../utils';
12
11
  import { jsx as _jsx } from "react/jsx-runtime";
13
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, pressProps, viewProps]);
12
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps]);
13
+ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([a11yProps, focusHandlerProps, viewProps]);
14
14
  const ToggleSwitchGroup = /*#__PURE__*/forwardRef(({
15
15
  variant,
16
16
  tokens,
@@ -57,13 +57,20 @@ const ToggleSwitchGroup = /*#__PURE__*/forwardRef(({
57
57
  ...rest
58
58
  });
59
59
  const itemA11yRole = selectedProps.accessibilityRole === 'radiogroup' ? 'radio' : 'switch';
60
+ const uniqueFields = ['id', 'label'];
61
+
62
+ if (!containUniqueFields(items, uniqueFields)) {
63
+ throw new Error(`ToggleSwitchGroup items must have unique ${uniqueFields.join(', ')}`);
64
+ }
65
+
60
66
  const toggleSwitches = items.map(({
61
67
  label,
62
68
  id = label,
63
69
  accessibilityLabel = label,
64
70
  onChange: itemOnChange,
65
71
  ref: itemRef,
66
- tooltip: itemTooltip
72
+ tooltip: itemTooltip,
73
+ ...itemRest
67
74
  }, index) => {
68
75
  const isSelected = currentValues.includes(id);
69
76
 
@@ -89,7 +96,8 @@ const ToggleSwitchGroup = /*#__PURE__*/forwardRef(({
89
96
  inactive: inactive,
90
97
  label: label,
91
98
  tooltip: itemTooltip,
92
- ...itemA11y
99
+ ...itemA11y,
100
+ ...selectItemProps(itemRest)
93
101
  }, id);
94
102
  });
95
103
  return /*#__PURE__*/_jsx(Fieldset, {
@@ -123,7 +131,8 @@ ToggleSwitchGroup.propTypes = { ...selectedSystemPropTypes,
123
131
  /**
124
132
  * The options a user may select
125
133
  */
126
- items: PropTypes.arrayOf(PropTypes.shape({
134
+ items: PropTypes.arrayOf(PropTypes.shape({ ...selectedItemPropTypes,
135
+
127
136
  /**
128
137
  * The text displayed to the user on the label.
129
138
  */
@@ -0,0 +1,26 @@
1
+ // Returns true if there are no duplicate values of the fields listed
2
+ // in the `fields` array across the objects in the `items` array, false
3
+ // otherwise.
4
+ // Note that if a value of a field in an item is not set, it will be
5
+ // excluded from comparison.
6
+ const containUniqueFields = (items, fields) => {
7
+ const map = [];
8
+ const itemsHaveDuplicateFields = items.some(item => fields.some(field => {
9
+ if (!map[field]) {
10
+ map[field] = [];
11
+ }
12
+
13
+ if (!item[field]) {
14
+ // We exclude empty values from comparison
15
+ return false;
16
+ } // Duplicate found!
17
+
18
+
19
+ if (map[field][item[field]]) return true;
20
+ map[field][item[field]] = true;
21
+ return false;
22
+ }));
23
+ return !itemsHaveDuplicateFields;
24
+ };
25
+
26
+ export default containUniqueFields;
@@ -12,4 +12,5 @@ export { default as useResponsiveProp } from './useResponsiveProp';
12
12
  export * from './useResponsiveProp';
13
13
  export { default as useUniqueId } from './useUniqueId';
14
14
  export { default as withLinkRouter } from './withLinkRouter';
15
- export * from './ssr';
15
+ export * from './ssr';
16
+ export { default as containUniqueFields } from './containUniqueFields';
@@ -0,0 +1,59 @@
1
+ import PropTypes from 'prop-types';
2
+ export const focusHandlerProps = {
3
+ types: {
4
+ /**
5
+ * onBlur handler
6
+ */
7
+ onBlur: PropTypes.func,
8
+
9
+ /**
10
+ * onFocus handler
11
+ */
12
+ onFocus: PropTypes.func
13
+ },
14
+ select: ({
15
+ onBlur,
16
+ onFocus
17
+ }) => ({
18
+ onBlur,
19
+ onFocus
20
+ })
21
+ };
22
+ export const textInputHandlerProps = {
23
+ types: {
24
+ /**
25
+ * onChange handler
26
+ */
27
+ onChange: PropTypes.func,
28
+
29
+ /**
30
+ * onChangeText handler
31
+ */
32
+ onChangeText: PropTypes.func,
33
+
34
+ /**
35
+ * onSubmit handler
36
+ */
37
+ onSubmit: PropTypes.func,
38
+
39
+ /**
40
+ * onSubmitEditing handler
41
+ */
42
+ onSubmitEditing: PropTypes.func
43
+ },
44
+ select: ({
45
+ onChange,
46
+ onChangeText,
47
+ onSubmit,
48
+ onSubmitEditing
49
+ }) => ({
50
+ onChange,
51
+ onChangeText,
52
+ onSubmit,
53
+ onSubmitEditing
54
+ })
55
+ };
56
+ export default {
57
+ focusHandlerProps,
58
+ textInputHandlerProps
59
+ };
@@ -1,4 +1,5 @@
1
1
  export * from './tokens';
2
+ export * from './handlerProps';
2
3
  export { default as a11yProps } from './a11yProps';
3
4
  export { default as clickProps } from './clickProps';
4
5
  export { default as copyPropTypes } from './copyPropTypes';
@@ -38,17 +38,15 @@ export default {
38
38
  hintPosition,
39
39
  feedback,
40
40
  tooltip,
41
- validation,
42
- ...rest
41
+ validation
43
42
  }) => ({
44
- props: {
43
+ supportsProps: {
45
44
  label,
46
45
  hint,
47
46
  hintPosition,
48
47
  feedback,
49
48
  tooltip,
50
49
  validation
51
- },
52
- ...rest
50
+ }
53
51
  })
54
52
  };
@@ -22,20 +22,16 @@ export default {
22
22
  select: getPropSelector(linkPropTypes),
23
23
 
24
24
  /**
25
- * Turn hrefs into press handlers on Native and throw if not given `onPress` or `href`.
25
+ * Turn hrefs into press handlers that open links on Native.
26
26
  *
27
27
  * @param {({ onPress?: () => void, href?: string })}
28
- * @returns {(() => void)|undefined} Returns a press handler, or undefined on web if href
29
- * string is provided. Expects calling component to use href string on web (e.g. in `<a>`).
28
+ * @returns {(() => void)|undefined} Returns a press handler, or undefined on web if no press
29
+ * handler is provided (expects calling component to render as `<a href={href}>` on web).
30
30
  */
31
31
  handleHref: ({
32
32
  onPress,
33
33
  href
34
34
  }) => {
35
- if (!href && !onPress) {
36
- throw new Error('handleHref requires either href or onPress');
37
- }
38
-
39
35
  return Platform.select({
40
36
  web: onPress,
41
37
  default: (...args) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telus-uds/components-base",
3
- "version": "1.4.0",
3
+ "version": "1.6.1",
4
4
  "description": "Base components",
5
5
  "keywords": [
6
6
  "base"
@@ -22,14 +22,14 @@
22
22
  },
23
23
  "scripts": {
24
24
  "test": "jest",
25
- "lint": "telus-standard",
26
- "lint:fix": "telus-standard --fix",
25
+ "lint": "yarn --cwd ../.. lint:path --color packages/components-base",
26
+ "lint:fix": "yarn --cwd ../.. lint:path --fix packages/components-base",
27
27
  "format": "prettier --write .",
28
28
  "build": "yarn build:code && yarn build:docs",
29
29
  "build:main": "babel src -d lib",
30
30
  "build:module": "babel src -d lib-module --env-name module",
31
31
  "build:code": "yarn build:main && yarn build:module",
32
- "build:docs": "babel-node --plugins=react-docgen-alpha generate-component-docs.js",
32
+ "build:docs": "babel-node --plugins=@nearform/babel-plugin-react-docgen generate-component-docs.js",
33
33
  "storybook": "start-storybook",
34
34
  "dev": "yarn build:code --watch",
35
35
  "release": "standard-version"
@@ -59,7 +59,7 @@
59
59
  "dependencies": {
60
60
  "airbnb-prop-types": "^2.16.0",
61
61
  "@telus-uds/system-constants": "^1.0.2",
62
- "@telus-uds/system-theme-tokens": "^1.3.0",
62
+ "@telus-uds/system-theme-tokens": "^1.5.0",
63
63
  "lodash.debounce": "^4.0.8",
64
64
  "lodash.merge": "^4.6.2",
65
65
  "prop-types": "^15.7.2",
@@ -6,8 +6,9 @@ import { applyTextStyles, applyShadowToken, applyOuterBorder } from '../ThemePro
6
6
  import buttonPropTypes from './propTypes'
7
7
  import {
8
8
  a11yProps,
9
- getCursorStyle,
10
9
  clickProps,
10
+ focusHandlerProps,
11
+ getCursorStyle,
11
12
  linkProps,
12
13
  resolvePressableState,
13
14
  resolvePressableTokens,
@@ -17,7 +18,12 @@ import {
17
18
  withLinkRouter
18
19
  } from '../utils'
19
20
 
20
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, linkProps, viewProps])
21
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
22
+ a11yProps,
23
+ focusHandlerProps,
24
+ linkProps,
25
+ viewProps
26
+ ])
21
27
 
22
28
  const getOuterBorderOffset = ({ outerBorderGap = 0, outerBorderWidth = 0 }) =>
23
29
  outerBorderGap + outerBorderWidth
@@ -9,17 +9,25 @@ import { useViewport } from '../ViewportProvider'
9
9
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider'
10
10
  import {
11
11
  a11yProps,
12
+ containUniqueFields,
13
+ focusHandlerProps,
12
14
  pressProps,
13
15
  getTokensPropType,
14
16
  selectSystemProps,
15
17
  selectTokens,
18
+ useMultipleInputValues,
16
19
  variantProp,
17
20
  viewProps
18
- } from '../utils/props'
19
- import { useMultipleInputValues } from '../utils/input'
21
+ } from '../utils'
20
22
  import { getPressHandlersWithArgs } from '../utils/pressability'
21
23
 
22
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, pressProps, viewProps])
24
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
25
+ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
26
+ a11yProps,
27
+ focusHandlerProps,
28
+ pressProps,
29
+ viewProps
30
+ ])
23
31
 
24
32
  const ButtonGroup = forwardRef(
25
33
  (
@@ -61,6 +69,11 @@ const ButtonGroup = forwardRef(
61
69
  })
62
70
  const itemA11yRole = systemProps.accessibilityRole === 'radiogroup' ? 'radio' : 'checkbox'
63
71
 
72
+ const uniqueFields = ['id', 'label']
73
+ if (!containUniqueFields(items, uniqueFields)) {
74
+ throw new Error(`ButtonGroup items must have unique ${uniqueFields.join(', ')}`)
75
+ }
76
+
64
77
  return (
65
78
  <StackWrap
66
79
  {...systemProps}
@@ -69,41 +82,44 @@ const ButtonGroup = forwardRef(
69
82
  tokens={stackTokens}
70
83
  ref={ref}
71
84
  >
72
- {items.map(({ label, id = label, accessibilityLabel, ref: itemRef }, index) => {
73
- const isSelected = currentValues.includes(id)
85
+ {items.map(
86
+ ({ label, id = label, accessibilityLabel, ref: itemRef, ...itemRest }, index) => {
87
+ const isSelected = currentValues.includes(id)
74
88
 
75
- // Pass an object of relevant component state as first argument for any passed-in press handlers
76
- const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])
89
+ // Pass an object of relevant component state as first argument for any passed-in press handlers
90
+ const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])
77
91
 
78
- const handlePress = (event) => {
79
- if (pressHandlers.onPress) pressHandlers.onPress()
80
- toggleOneValue(id, event)
81
- }
92
+ const handlePress = (event) => {
93
+ if (pressHandlers.onPress) pressHandlers.onPress(event)
94
+ toggleOneValue(id, event)
95
+ }
82
96
 
83
- const itemA11y = {
84
- accessibilityState: { checked: isSelected },
85
- accessibilityRole: itemA11yRole,
86
- accessibilityLabel,
87
- ...a11yProps.getPositionInSet(items.length, index)
88
- }
97
+ const itemA11y = {
98
+ accessibilityState: { checked: isSelected },
99
+ accessibilityRole: itemA11yRole,
100
+ accessibilityLabel,
101
+ ...a11yProps.getPositionInSet(items.length, index)
102
+ }
89
103
 
90
- // Ensure button is direct child of group as MacOS voiceover only applies "X of Y" to
91
- // "radio" if it's a direct child of "radiogroup", even if aria-posinset etc exists
92
- return (
93
- <ButtonBase
94
- ref={itemRef}
95
- key={id}
96
- {...pressHandlers}
97
- onPress={handlePress}
98
- tokens={getButtonTokens}
99
- selected={isSelected}
100
- inactive={inactive}
101
- {...itemA11y}
102
- >
103
- {label}
104
- </ButtonBase>
105
- )
106
- })}
104
+ // Ensure button is direct child of group as MacOS voiceover only applies "X of Y" to
105
+ // "radio" if it's a direct child of "radiogroup", even if aria-posinset etc exists
106
+ return (
107
+ <ButtonBase
108
+ ref={itemRef}
109
+ key={id}
110
+ {...pressHandlers}
111
+ onPress={handlePress}
112
+ tokens={getButtonTokens}
113
+ selected={isSelected}
114
+ inactive={inactive}
115
+ {...itemA11y}
116
+ {...selectItemProps(itemRest)}
117
+ >
118
+ {label}
119
+ </ButtonBase>
120
+ )
121
+ }
122
+ )}
107
123
  </StackWrap>
108
124
  )
109
125
  }
@@ -124,6 +140,7 @@ ButtonGroup.propTypes = {
124
140
  */
125
141
  items: PropTypes.arrayOf(
126
142
  PropTypes.shape({
143
+ ...selectedItemPropTypes,
127
144
  /**
128
145
  * The text displayed to the user in the button, describing this option,
129
146
  * passed to the button as its child.
@@ -7,6 +7,7 @@ import { applyOuterBorder, validateThemeTokens } from '../ThemeProvider'
7
7
  import {
8
8
  a11yProps,
9
9
  clickProps,
10
+ focusHandlerProps,
10
11
  getTokenNames,
11
12
  getTokensSetPropType,
12
13
  linkProps,
@@ -20,7 +21,11 @@ import {
20
21
  } from '../utils'
21
22
  import CardBase from './CardBase'
22
23
 
23
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
24
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
25
+ a11yProps,
26
+ focusHandlerProps,
27
+ viewProps
28
+ ])
24
29
 
25
30
  const tokenKeys = [
26
31
  ...getTokenNames('Card'),
@@ -9,6 +9,7 @@ import StackView from '../StackView'
9
9
  import { applyShadowToken, applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
10
10
  import {
11
11
  a11yProps,
12
+ focusHandlerProps,
12
13
  getTokensPropType,
13
14
  selectSystemProps,
14
15
  useInputValue,
@@ -17,7 +18,11 @@ import {
17
18
  } from '../utils'
18
19
  import useUniqueId from '../utils/useUniqueId'
19
20
 
20
- const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
21
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([
22
+ a11yProps,
23
+ focusHandlerProps,
24
+ viewProps
25
+ ])
21
26
 
22
27
  const selectInputStyles = (
23
28
  {
@@ -179,32 +184,38 @@ const Checkbox = forwardRef(
179
184
  {({ focused: focus, hovered: hover, pressed }) => {
180
185
  const { icon: IconComponent, ...stateTokens } = getTokens({ focus, hover, pressed })
181
186
  const iconTokens = selectIconTokens(stateTokens)
187
+ const labelStyles = selectLabelStyles(stateTokens)
188
+ const alignWithLabel = label
189
+ ? [staticStyles.alignWithLabel, { height: labelStyles.lineHeight }]
190
+ : null
182
191
 
183
192
  return (
184
193
  <View style={staticStyles.container}>
185
- <View
186
- style={StyleSheet.flatten([
187
- staticStyles.defaultInputStyles,
188
- selectInputStyles(stateTokens, isChecked)
189
- ])}
190
- testID="Checkbox-Input"
191
- >
192
- {/* Add a real input on Web, skip on native platforms */}
193
- <CheckboxInput
194
- checked={isChecked}
195
- defaultChecked={defaultChecked}
196
- disabled={inactive}
197
- id={inputId}
198
- isControlled={isControlled}
199
- name={name}
200
- value={value}
201
- />
202
- {isChecked && IconComponent && (
203
- <IconComponent {...iconTokens} testID="Checkbox-Icon" />
204
- )}
194
+ <View style={alignWithLabel}>
195
+ <View
196
+ style={[
197
+ staticStyles.defaultInputStyles,
198
+ selectInputStyles(stateTokens, isChecked)
199
+ ]}
200
+ testID="Checkbox-Input"
201
+ >
202
+ {/* Add a real input on Web, skip on native platforms */}
203
+ <CheckboxInput
204
+ checked={isChecked}
205
+ defaultChecked={defaultChecked}
206
+ disabled={inactive}
207
+ id={inputId}
208
+ isControlled={isControlled}
209
+ name={name}
210
+ value={value}
211
+ />
212
+ {isChecked && IconComponent && (
213
+ <IconComponent {...iconTokens} testID="Checkbox-Icon" />
214
+ )}
215
+ </View>
205
216
  </View>
206
217
  {Boolean(label) && (
207
- <Text style={selectLabelStyles(stateTokens)}>
218
+ <Text style={labelStyles}>
208
219
  <CheckboxLabel forId={inputId}>{label}</CheckboxLabel>
209
220
  </Text>
210
221
  )}
@@ -285,5 +296,6 @@ export default Checkbox
285
296
  const staticStyles = StyleSheet.create({
286
297
  wrapper: { backgroundColor: 'transparent' },
287
298
  container: { flexDirection: 'row', alignItems: 'center' },
288
- defaultInputStyles: { alignItems: 'center', justifyContent: 'center' }
299
+ defaultInputStyles: { alignItems: 'center', justifyContent: 'center' },
300
+ alignWithLabel: { alignSelf: 'flex-start', justifyContent: 'center' }
289
301
  })