@telus-uds/components-base 1.3.1 → 1.6.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.
Files changed (105) hide show
  1. package/.turbo/turbo-build.log +8 -8
  2. package/.turbo/turbo-lint.log +13 -0
  3. package/CHANGELOG.json +142 -1
  4. package/CHANGELOG.md +51 -2
  5. package/__tests__/FlexGrid/Row.test.jsx +100 -25
  6. package/__tests__/utils/containUniqueFields.test.js +25 -0
  7. package/component-docs.json +50 -15
  8. package/lib/Button/ButtonBase.js +1 -1
  9. package/lib/Button/ButtonGroup.js +20 -12
  10. package/lib/Card/PressableCardBase.js +1 -1
  11. package/lib/Checkbox/Checkbox.js +27 -16
  12. package/lib/Checkbox/CheckboxGroup.js +19 -5
  13. package/lib/ExpandCollapse/Panel.js +10 -10
  14. package/lib/FlexGrid/Col/Col.js +13 -3
  15. package/lib/FlexGrid/Row/Row.js +8 -2
  16. package/lib/HorizontalScroll/HorizontalScroll.js +0 -1
  17. package/lib/HorizontalScroll/HorizontalScrollButton.js +23 -49
  18. package/lib/InputLabel/InputLabel.js +27 -25
  19. package/lib/Link/LinkBase.js +19 -6
  20. package/lib/Modal/Modal.js +18 -18
  21. package/lib/Notification/Notification.js +5 -6
  22. package/lib/Radio/Radio.js +23 -12
  23. package/lib/Radio/RadioGroup.js +12 -3
  24. package/lib/RadioCard/RadioCard.js +1 -1
  25. package/lib/RadioCard/RadioCardGroup.js +11 -2
  26. package/lib/Select/Select.js +2 -3
  27. package/lib/Tags/Tags.js +23 -17
  28. package/lib/TextInput/TextArea.js +2 -2
  29. package/lib/TextInput/TextInput.js +12 -2
  30. package/lib/TextInput/TextInputBase.js +1 -1
  31. package/lib/TextInput/propTypes.js +8 -1
  32. package/lib/ToggleSwitch/ToggleSwitch.js +5 -2
  33. package/lib/ToggleSwitch/ToggleSwitchGroup.js +20 -12
  34. package/lib/utils/containUniqueFields.js +34 -0
  35. package/lib/utils/index.js +10 -1
  36. package/lib/utils/props/handlerProps.js +72 -0
  37. package/lib/utils/props/index.js +14 -0
  38. package/lib/utils/props/inputSupportsProps.js +3 -5
  39. package/lib-module/Button/ButtonBase.js +2 -2
  40. package/lib-module/Button/ButtonGroup.js +15 -6
  41. package/lib-module/Card/PressableCardBase.js +2 -2
  42. package/lib-module/Checkbox/Checkbox.js +28 -17
  43. package/lib-module/Checkbox/CheckboxGroup.js +20 -7
  44. package/lib-module/ExpandCollapse/Panel.js +10 -10
  45. package/lib-module/FlexGrid/Col/Col.js +13 -3
  46. package/lib-module/FlexGrid/Row/Row.js +8 -2
  47. package/lib-module/HorizontalScroll/HorizontalScroll.js +0 -1
  48. package/lib-module/HorizontalScroll/HorizontalScrollButton.js +24 -49
  49. package/lib-module/InputLabel/InputLabel.js +28 -25
  50. package/lib-module/Link/LinkBase.js +19 -6
  51. package/lib-module/Modal/Modal.js +19 -19
  52. package/lib-module/Notification/Notification.js +6 -6
  53. package/lib-module/Radio/Radio.js +24 -13
  54. package/lib-module/Radio/RadioGroup.js +13 -4
  55. package/lib-module/RadioCard/RadioCard.js +2 -2
  56. package/lib-module/RadioCard/RadioCardGroup.js +12 -3
  57. package/lib-module/Select/Select.js +2 -3
  58. package/lib-module/Tags/Tags.js +18 -11
  59. package/lib-module/TextInput/TextArea.js +3 -3
  60. package/lib-module/TextInput/TextInput.js +11 -3
  61. package/lib-module/TextInput/TextInputBase.js +2 -2
  62. package/lib-module/TextInput/propTypes.js +7 -1
  63. package/lib-module/ToggleSwitch/ToggleSwitch.js +6 -3
  64. package/lib-module/ToggleSwitch/ToggleSwitchGroup.js +15 -6
  65. package/lib-module/utils/containUniqueFields.js +26 -0
  66. package/lib-module/utils/index.js +2 -1
  67. package/lib-module/utils/props/handlerProps.js +59 -0
  68. package/lib-module/utils/props/index.js +1 -0
  69. package/lib-module/utils/props/inputSupportsProps.js +3 -5
  70. package/package.json +5 -5
  71. package/src/Button/ButtonBase.jsx +8 -2
  72. package/src/Button/ButtonGroup.jsx +51 -34
  73. package/src/Card/PressableCardBase.jsx +6 -1
  74. package/src/Checkbox/Checkbox.jsx +35 -23
  75. package/src/Checkbox/CheckboxGroup.jsx +52 -22
  76. package/src/ExpandCollapse/Panel.jsx +9 -9
  77. package/src/FlexGrid/Col/Col.jsx +11 -2
  78. package/src/FlexGrid/Row/Row.jsx +8 -2
  79. package/src/HorizontalScroll/HorizontalScroll.jsx +1 -1
  80. package/src/HorizontalScroll/HorizontalScrollButton.jsx +21 -58
  81. package/src/InputLabel/InputLabel.jsx +36 -27
  82. package/src/Link/LinkBase.jsx +20 -4
  83. package/src/Modal/Modal.jsx +30 -26
  84. package/src/Notification/Notification.jsx +7 -4
  85. package/src/Radio/Radio.jsx +26 -14
  86. package/src/Radio/RadioGroup.jsx +39 -21
  87. package/src/RadioCard/RadioCard.jsx +6 -1
  88. package/src/RadioCard/RadioCardGroup.jsx +17 -1
  89. package/src/Select/Select.jsx +2 -2
  90. package/src/Tags/Tags.jsx +23 -9
  91. package/src/TextInput/TextArea.jsx +5 -1
  92. package/src/TextInput/TextInput.jsx +13 -3
  93. package/src/TextInput/TextInputBase.jsx +6 -1
  94. package/src/TextInput/propTypes.js +7 -1
  95. package/src/ToggleSwitch/ToggleSwitch.jsx +11 -2
  96. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +19 -6
  97. package/src/utils/containUniqueFields.js +32 -0
  98. package/src/utils/index.js +1 -0
  99. package/src/utils/props/handlerProps.js +47 -0
  100. package/src/utils/props/index.js +1 -0
  101. package/src/utils/props/inputSupportsProps.js +3 -4
  102. package/stories/InputLabel/InputLabel.stories.jsx +25 -28
  103. package/stories/Modal/Modal.stories.jsx +25 -0
  104. package/stories/Search/Search.stories.jsx +4 -1
  105. package/stories/TextInput/TextInput.stories.jsx +40 -2
@@ -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
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telus-uds/components-base",
3
- "version": "1.3.1",
3
+ "version": "1.6.0",
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.2.2",
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
  })
@@ -4,11 +4,27 @@ import ABBPropTypes from 'airbnb-prop-types'
4
4
 
5
5
  import { useViewport } from '../ViewportProvider'
6
6
  import { useThemeTokens } from '../ThemeProvider'
7
- import { getTokensPropType, useMultipleInputValues, variantProp } from '../utils'
7
+ import {
8
+ a11yProps,
9
+ containUniqueFields,
10
+ focusHandlerProps,
11
+ getTokensPropType,
12
+ selectSystemProps,
13
+ useMultipleInputValues,
14
+ variantProp,
15
+ viewProps
16
+ } from '../utils'
8
17
  import { getStackedContent } from '../StackView'
9
18
  import Checkbox from './Checkbox'
10
19
  import Fieldset from '../Fieldset'
11
20
 
21
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
22
+ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
23
+ a11yProps,
24
+ focusHandlerProps,
25
+ viewProps
26
+ ])
27
+
12
28
  /**
13
29
  * A group of Checkboxs that behave as a fieldset. Use when users select any number of choices from options.
14
30
  *
@@ -73,7 +89,8 @@ const CheckboxGroup = forwardRef(
73
89
  onChange,
74
90
  readOnly,
75
91
  name: inputGroupName,
76
- inactive
92
+ inactive,
93
+ ...rest
77
94
  },
78
95
  ref
79
96
  ) => {
@@ -89,27 +106,37 @@ const CheckboxGroup = forwardRef(
89
106
  onChange,
90
107
  readOnly: readOnly || inactive
91
108
  })
92
- const checkboxes = items.map(({ label, id, onChange: itemOnChange, ref: itemRef }, index) => {
93
- const checkboxId = id || `Checkbox[${index}]`
94
- const handleChange = (newCheckedState, event) => {
95
- if (typeof itemOnChange === 'function') itemOnChange(newCheckedState, event)
96
- toggleOneValue(checkboxId, event)
109
+
110
+ const uniqueFields = ['id', 'label']
111
+ if (!containUniqueFields(items, uniqueFields)) {
112
+ throw new Error(`CheckboxGroup items must have unique ${uniqueFields.join(', ')}`)
113
+ }
114
+
115
+ const checkboxes = items.map(
116
+ ({ label, id, onChange: itemOnChange, ref: itemRef, ...itemRest }, index) => {
117
+ const checkboxId = id || `Checkbox[${index}]`
118
+ const handleChange = (newCheckedState, event) => {
119
+ if (typeof itemOnChange === 'function') itemOnChange(newCheckedState, event)
120
+ toggleOneValue(checkboxId, event)
121
+ }
122
+
123
+ return (
124
+ <Checkbox
125
+ ref={itemRef}
126
+ key={checkboxId}
127
+ id={checkboxId}
128
+ checked={currentValues.includes(checkboxId)}
129
+ onChange={handleChange}
130
+ inactive={inactive}
131
+ label={label}
132
+ name={inputGroupName}
133
+ tokens={radioTokens}
134
+ variant={variant}
135
+ {...selectItemProps(itemRest)}
136
+ />
137
+ )
97
138
  }
98
- return (
99
- <Checkbox
100
- ref={itemRef}
101
- key={checkboxId}
102
- id={checkboxId}
103
- checked={currentValues.includes(checkboxId)}
104
- onChange={handleChange}
105
- inactive={inactive}
106
- label={label}
107
- name={inputGroupName}
108
- tokens={radioTokens}
109
- variant={variant}
110
- />
111
- )
112
- })
139
+ )
113
140
 
114
141
  return (
115
142
  <Fieldset
@@ -122,6 +149,7 @@ const CheckboxGroup = forwardRef(
122
149
  feedback={feedback}
123
150
  inactive={inactive}
124
151
  validation={validation}
152
+ {...selectProps(rest)}
125
153
  >
126
154
  {getStackedContent(checkboxes, { space, direction: 'column' })}
127
155
  </Fieldset>
@@ -131,6 +159,7 @@ const CheckboxGroup = forwardRef(
131
159
  CheckboxGroup.displayName = 'CheckboxGroup'
132
160
 
133
161
  CheckboxGroup.propTypes = {
162
+ ...selectedSystemPropTypes,
134
163
  /**
135
164
  * Optional theme token overrides for the outer CheckboxGroup component
136
165
  */
@@ -148,6 +177,7 @@ CheckboxGroup.propTypes = {
148
177
  */
149
178
  items: PropTypes.arrayOf(
150
179
  PropTypes.exact({
180
+ ...selectedItemPropTypes,
151
181
  label: PropTypes.string,
152
182
  id: PropTypes.string,
153
183
  onChange: PropTypes.func,
@@ -74,8 +74,8 @@ const ExpandCollapsePanel = forwardRef(
74
74
  }
75
75
 
76
76
  const onContainerLayout = (event) => {
77
- if (containerHeight === null) {
78
- const { layout: { height = 0 } = {} } = event.nativeEvent
77
+ const { layout: { height = 0 } = {} } = event.nativeEvent
78
+ if (Platform.OS === 'web' || (Platform.OS !== 'web' && containerHeight === null)) {
79
79
  setContainerHeight(height)
80
80
  }
81
81
  }
@@ -104,14 +104,14 @@ const ExpandCollapsePanel = forwardRef(
104
104
  >
105
105
  {control}
106
106
  </ExpandCollapseControl>
107
- <View style={overflowContainerStyles} {...focusabilityProps}>
108
- <Animated.View
109
- onLayout={onContainerLayout}
110
- style={[staticStyles.itemsContainer, animatedStyles]}
111
- >
107
+ <Animated.View
108
+ style={[overflowContainerStyles, animatedStyles, staticStyles.itemsContainer]}
109
+ {...focusabilityProps}
110
+ >
111
+ <View onLayout={onContainerLayout}>
112
112
  <View style={selectContainerStyles(themeTokens)}>{children}</View>
113
- </Animated.View>
114
- </View>
113
+ </View>
114
+ </Animated.View>
115
115
  </View>
116
116
  )
117
117
  }