@telus-uds/components-base 1.19.0 → 1.21.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 (97) hide show
  1. package/CHANGELOG.md +34 -2
  2. package/__tests17__/ThemeProvider/ThemeProvider.test.jsx +3 -1
  3. package/component-docs.json +838 -125
  4. package/lib/BaseProvider/index.js +2 -1
  5. package/lib/Box/Box.js +14 -1
  6. package/lib/Button/ButtonDropdown.js +207 -0
  7. package/lib/Button/index.js +8 -0
  8. package/lib/Carousel/Carousel.js +2 -2
  9. package/lib/Carousel/CarouselItem/CarouselItem.js +7 -1
  10. package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +21 -4
  11. package/lib/FlexGrid/Col/Col.js +1 -3
  12. package/lib/FlexGrid/FlexGrid.js +3 -5
  13. package/lib/FlexGrid/Row/Row.js +3 -3
  14. package/lib/IconButton/IconButton.js +12 -4
  15. package/lib/MultiSelectFilter/MultiSelectFilter.js +276 -0
  16. package/lib/MultiSelectFilter/dictionary.js +19 -0
  17. package/lib/MultiSelectFilter/index.js +13 -0
  18. package/lib/Search/Search.js +4 -1
  19. package/lib/Select/Picker.native.js +16 -13
  20. package/lib/Select/Select.js +7 -1
  21. package/lib/Select/constants.js +15 -0
  22. package/lib/StepTracker/Step.js +2 -1
  23. package/lib/TextInput/TextInput.js +9 -2
  24. package/lib/TextInput/TextInputBase.js +52 -8
  25. package/lib/TextInput/dictionary.js +15 -0
  26. package/lib/ThemeProvider/ThemeProvider.js +24 -7
  27. package/lib/ThemeProvider/utils/styles.js +3 -1
  28. package/lib/index.js +18 -0
  29. package/lib/utils/BaseView/BaseView.js +64 -0
  30. package/lib/utils/BaseView/BaseView.native.js +16 -0
  31. package/lib/utils/BaseView/index.js +13 -0
  32. package/lib/utils/index.js +10 -1
  33. package/lib/utils/input.js +11 -3
  34. package/lib/utils/props/handlerProps.js +5 -0
  35. package/lib-module/BaseProvider/index.js +2 -1
  36. package/lib-module/Box/Box.js +14 -1
  37. package/lib-module/Button/ButtonDropdown.js +181 -0
  38. package/lib-module/Button/index.js +2 -1
  39. package/lib-module/Carousel/Carousel.js +2 -2
  40. package/lib-module/Carousel/CarouselItem/CarouselItem.js +8 -2
  41. package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +23 -6
  42. package/lib-module/FlexGrid/Col/Col.js +2 -3
  43. package/lib-module/FlexGrid/FlexGrid.js +2 -3
  44. package/lib-module/FlexGrid/Row/Row.js +2 -2
  45. package/lib-module/IconButton/IconButton.js +14 -4
  46. package/lib-module/MultiSelectFilter/MultiSelectFilter.js +248 -0
  47. package/lib-module/MultiSelectFilter/dictionary.js +12 -0
  48. package/lib-module/MultiSelectFilter/index.js +2 -0
  49. package/lib-module/Search/Search.js +4 -1
  50. package/lib-module/Select/Picker.native.js +15 -13
  51. package/lib-module/Select/Select.js +6 -1
  52. package/lib-module/Select/constants.js +5 -0
  53. package/lib-module/StepTracker/Step.js +2 -1
  54. package/lib-module/TextInput/TextInput.js +6 -0
  55. package/lib-module/TextInput/TextInputBase.js +52 -10
  56. package/lib-module/TextInput/dictionary.js +8 -0
  57. package/lib-module/ThemeProvider/ThemeProvider.js +24 -7
  58. package/lib-module/ThemeProvider/utils/styles.js +3 -1
  59. package/lib-module/index.js +2 -0
  60. package/lib-module/utils/BaseView/BaseView.js +43 -0
  61. package/lib-module/utils/BaseView/BaseView.native.js +6 -0
  62. package/lib-module/utils/BaseView/index.js +2 -0
  63. package/lib-module/utils/index.js +2 -1
  64. package/lib-module/utils/input.js +11 -3
  65. package/lib-module/utils/props/handlerProps.js +5 -0
  66. package/package.json +3 -3
  67. package/src/BaseProvider/index.jsx +4 -1
  68. package/src/Box/Box.jsx +14 -1
  69. package/src/Button/ButtonDropdown.jsx +179 -0
  70. package/src/Button/index.js +2 -1
  71. package/src/Carousel/Carousel.jsx +6 -3
  72. package/src/Carousel/CarouselItem/CarouselItem.jsx +9 -2
  73. package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +19 -5
  74. package/src/FlexGrid/Col/Col.jsx +4 -4
  75. package/src/FlexGrid/FlexGrid.jsx +11 -10
  76. package/src/FlexGrid/Row/Row.jsx +4 -3
  77. package/src/IconButton/IconButton.jsx +3 -1
  78. package/src/MultiSelectFilter/MultiSelectFilter.jsx +227 -0
  79. package/src/MultiSelectFilter/dictionary.js +12 -0
  80. package/src/MultiSelectFilter/index.js +3 -0
  81. package/src/Search/Search.jsx +2 -1
  82. package/src/Select/Picker.native.jsx +29 -14
  83. package/src/Select/Select.jsx +7 -1
  84. package/src/Select/constants.js +5 -0
  85. package/src/StepTracker/Step.jsx +5 -1
  86. package/src/TextInput/TextInput.jsx +5 -0
  87. package/src/TextInput/TextInputBase.jsx +43 -8
  88. package/src/TextInput/dictionary.js +8 -0
  89. package/src/ThemeProvider/ThemeProvider.jsx +23 -6
  90. package/src/ThemeProvider/utils/styles.js +3 -1
  91. package/src/index.js +2 -0
  92. package/src/utils/BaseView/BaseView.jsx +38 -0
  93. package/src/utils/BaseView/BaseView.native.jsx +6 -0
  94. package/src/utils/BaseView/index.js +3 -0
  95. package/src/utils/index.js +1 -0
  96. package/src/utils/input.js +9 -4
  97. package/src/utils/props/handlerProps.js +4 -0
@@ -0,0 +1,227 @@
1
+ import React, { forwardRef, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { useThemeTokensCallback } from '../ThemeProvider'
5
+ import {
6
+ containUniqueFields,
7
+ getTokensPropType,
8
+ getPressHandlersWithArgs,
9
+ selectTokens,
10
+ useCopy,
11
+ useMultipleInputValues,
12
+ variantProp
13
+ } from '../utils'
14
+ import dictionary from './dictionary'
15
+
16
+ import Box from '../Box'
17
+ import { Button, ButtonDropdown } from '../Button'
18
+ import { CheckboxGroup } from '../Checkbox'
19
+ import Divider from '../Divider'
20
+ import FlexGrid from '../FlexGrid'
21
+ import Modal from '../Modal'
22
+ import Spacer from '../Spacer'
23
+ import StackView from '../StackView'
24
+ import Typography from '../Typography'
25
+ import { TextButton } from '../Link'
26
+
27
+ const { Col, Row } = FlexGrid
28
+
29
+ const MultiSelectFilter = forwardRef(
30
+ (
31
+ {
32
+ label,
33
+ id = label,
34
+ variant,
35
+ tokens,
36
+ items = [],
37
+ values,
38
+ initialValues,
39
+ maxValues,
40
+ onChange,
41
+ copy = 'en',
42
+ readOnly = false,
43
+ inactive = false,
44
+ rowLimit = 12,
45
+ ...rest
46
+ },
47
+ ref
48
+ ) => {
49
+ const { currentValues, setValues } = useMultipleInputValues({
50
+ initialValues,
51
+ values,
52
+ maxValues,
53
+ onChange,
54
+ readOnly
55
+ })
56
+
57
+ const getItemTokens = useThemeTokensCallback('ButtonDropdown', tokens, variant)
58
+ const getButtonTokens = (buttonState) => selectTokens('Button', getItemTokens(buttonState))
59
+ const getCopy = useCopy({ dictionary, copy })
60
+
61
+ const [isOpen, setIsOpen] = useState(false)
62
+ const [checkedIds, setCheckedIds] = useState(currentValues ?? [])
63
+
64
+ const colSize = items.length > rowLimit ? 2 : 1
65
+ const isSelected = currentValues.length > 0
66
+
67
+ const uniqueFields = ['id', 'label']
68
+ if (!containUniqueFields(items, uniqueFields)) {
69
+ throw new Error(`MultiSelectFilter items must have unique ${uniqueFields.join(', ')}`)
70
+ }
71
+
72
+ // Pass an object of relevant component state as first argument for any passed-in press handlers
73
+ const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])
74
+
75
+ const handleChange = (event) => {
76
+ if (pressHandlers.onPress) pressHandlers?.onPress(event)
77
+ setIsOpen(true)
78
+ }
79
+
80
+ const onApply = (e) => {
81
+ setValues(e)
82
+ setIsOpen(false)
83
+ }
84
+
85
+ return (
86
+ <>
87
+ <Modal
88
+ isOpen={isOpen}
89
+ onClose={() => setIsOpen(false)}
90
+ variant={{ width: colSize > 1 ? 'size576' : 's' }}
91
+ >
92
+ <Row>
93
+ <Typography variant={{ size: 'h4' }}>
94
+ {getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label.toLowerCase())}
95
+ </Typography>
96
+ </Row>
97
+ <Spacer space={4} />
98
+ <Spacer space={1} />
99
+ <Box scroll={true}>
100
+ <Row distribute="between">
101
+ {[...Array(colSize).keys()].map((i) => (
102
+ <Col xs={12 / colSize} key={i}>
103
+ <CheckboxGroup
104
+ items={items.slice(i * rowLimit, (i + 1) * rowLimit)}
105
+ checkedIds={checkedIds}
106
+ onChange={(e) => setCheckedIds(e, i)}
107
+ />
108
+ <Spacer size={4} />
109
+ </Col>
110
+ ))}
111
+ </Row>
112
+ </Box>
113
+ <Divider
114
+ variant={{ width: 'full', color: 'E3E6E8', decorative: true, weight: 'thin' }}
115
+ space={4}
116
+ />
117
+ <Row>
118
+ <StackView direction="row" space={3} tokens={{ alignItems: 'center' }}>
119
+ <Button
120
+ onPress={() => onApply(checkedIds)}
121
+ variant={{ size: 'small', priority: 'high' }}
122
+ >
123
+ {getCopy('applyButtonLabel')}
124
+ </Button>
125
+ <Box>
126
+ <TextButton onPress={() => setCheckedIds([])}>
127
+ {getCopy('clearButtonLabel')}
128
+ </TextButton>
129
+ </Box>
130
+ </StackView>
131
+ </Row>
132
+ </Modal>
133
+ <ButtonDropdown
134
+ ref={ref}
135
+ key={id}
136
+ {...pressHandlers}
137
+ value={isOpen}
138
+ selected={isSelected}
139
+ label={label}
140
+ onChange={handleChange}
141
+ tokens={getButtonTokens}
142
+ inactive={inactive}
143
+ />
144
+ </>
145
+ )
146
+ }
147
+ )
148
+ MultiSelectFilter.displayName = 'MultiSelectFilter'
149
+
150
+ MultiSelectFilter.propTypes = {
151
+ /**
152
+ * The text displayed to the user in a ButtonDropdown.
153
+ */
154
+ label: PropTypes.string.isRequired,
155
+ /**
156
+ * An optional unique string may be provided to identify the ButtonDropdown.
157
+ * If not provided, the label is used.
158
+ */
159
+ id: PropTypes.string,
160
+ /**
161
+ * Sets the variant for ButtonDropdown element.
162
+ */
163
+ variant: variantProp.propType,
164
+ /**
165
+ * Sets the tokens for ButtonDropdown element.
166
+ */
167
+ tokens: getTokensPropType('ButtonDropdown'),
168
+ /**
169
+ * The options a user may select.
170
+ */
171
+ items: PropTypes.arrayOf(
172
+ PropTypes.shape({
173
+ /**
174
+ * The text displayed to the user with a checkbox, describing this option.
175
+ */
176
+ label: PropTypes.string.isRequired,
177
+ /**
178
+ * An optional unique string may be provided to identify this option.
179
+ * If not provided, the label is used.
180
+ */
181
+ id: PropTypes.string
182
+ })
183
+ ),
184
+ /**
185
+ * If the selected item(s) in the checkbox group(s) are to be controlled externally by
186
+ * a parent component, pass an array of strings as well as an `onChange` handler.
187
+ * Passing an array for "values" makes the MultiSelectFilter a "controlled" component that
188
+ * expects its state to be handled via `onChange` and so doesn't handle it itself.
189
+ */
190
+ values: PropTypes.arrayOf(PropTypes.string),
191
+ /**
192
+ * If `values` is not passed, making the MultiSelectFilter an "uncontrolled" component
193
+ * managing its own selected state, a default set of selections may be provided.
194
+ * Changing the `initialValues` does not change the user's selections.
195
+ */
196
+ initialValues: PropTypes.arrayOf(PropTypes.string),
197
+ /**
198
+ * If provided, sets a maximum number of items a user may select at once.
199
+ */
200
+ maxValues: PropTypes.number,
201
+ /**
202
+ * If provided, this function is called when the current selection is changed
203
+ * and is passed an array of the `id`s of all currently selected `items`.
204
+ */
205
+ onChange: PropTypes.func,
206
+ /**
207
+ * Select English or French copy for the accessible label.
208
+ */
209
+ copy: PropTypes.oneOf(['en', 'fr']),
210
+ /**
211
+ * If true, the ButtonDropdown cannot be selected by the user and simply show their current state.
212
+ */
213
+ readOnly: PropTypes.string,
214
+ /**
215
+ * If true, the MultiSelectFilter cannot be interacted with, ButtonDropdown is
216
+ * set as `disabled` and if the theme supports `inactive` appearances rules, these
217
+ * are applied.
218
+ */
219
+ inactive: PropTypes.string,
220
+ /**
221
+ * Sets the maximum number of items in one column. If number of items are more
222
+ * than the `rowLimit`, they will be rendered in 2 columns.
223
+ */
224
+ rowLimit: PropTypes.number
225
+ }
226
+
227
+ export default MultiSelectFilter
@@ -0,0 +1,12 @@
1
+ export default {
2
+ en: {
3
+ filterByLabel: 'Filter by %{filterCategory}:',
4
+ applyButtonLabel: 'Apply',
5
+ clearButtonLabel: 'Clear'
6
+ },
7
+ fr: {
8
+ filterByLabel: 'Filtrer par %{filterCategory}:',
9
+ applyButtonLabel: 'Appliquer',
10
+ clearButtonLabel: 'Effacer'
11
+ }
12
+ }
@@ -0,0 +1,3 @@
1
+ import MultiSelectFilter from './MultiSelectFilter'
2
+
3
+ export default MultiSelectFilter
@@ -108,7 +108,8 @@ const Search = forwardRef(
108
108
 
109
109
  const handleClear = (event) => {
110
110
  setValue('', event)
111
- if (onClear !== undefined) onClear('', event)
111
+ onClear?.('', event)
112
+ ref?.current?.focus()
112
113
  }
113
114
 
114
115
  const handleFocus = (event) => {
@@ -6,28 +6,43 @@ import NativePicker from 'react-native-picker-select'
6
6
  import { a11yProps, componentPropType } from '../utils'
7
7
  import Group from './Group'
8
8
 
9
- // styling of the native input is very limited, most of the styles have to be applied to an additional View
10
- const selectAndroidInputStyles = ({
11
- height = 0,
12
- paddingBottom = 0,
13
- paddingTop = 0,
14
- borderWidth = 0,
15
- color
16
- }) => ({
17
- height: height - paddingTop - paddingBottom - 2 * borderWidth,
9
+ import {
10
+ ANDROID_HEIGHT_OFFSET,
11
+ ANDROID_HORIZONTAL_PADDING_OFFSET,
12
+ ANDROID_DEFAULT_PADDING
13
+ } from './constants'
14
+
15
+ // Styling of the native input is very limited, most of the styles have to be applied to an additional View
16
+ const selectAndroidInputStyles = ({ height = 0, color }) => ({
17
+ height,
18
+ paddingBottom: 0,
19
+ paddingTop: 0,
18
20
  color
19
21
  })
20
22
 
21
- // the native input has a side padding of 8px, which can't be adjusted, so we have to account for that in the container
22
- const selectAndroidContainerStyles = ({ paddingLeft = 0, paddingRight = 0, ...rest }) => ({
23
- paddingLeft: paddingLeft > 8 ? paddingLeft - 8 : 0,
24
- paddingRight: paddingRight > 8 ? paddingRight - 8 : 0,
23
+ // The native input has a side padding of 8px, which can't be adjusted, so we have to account for that in the container
24
+ const selectAndroidContainerStyles = ({
25
+ paddingLeft = ANDROID_DEFAULT_PADDING,
26
+ paddingRight = ANDROID_DEFAULT_PADDING,
25
27
  ...rest
28
+ }) => ({
29
+ ...rest,
30
+ paddingLeft:
31
+ paddingLeft > ANDROID_HORIZONTAL_PADDING_OFFSET
32
+ ? paddingLeft - ANDROID_HORIZONTAL_PADDING_OFFSET
33
+ : ANDROID_DEFAULT_PADDING,
34
+ paddingRight:
35
+ paddingRight > ANDROID_HORIZONTAL_PADDING_OFFSET
36
+ ? paddingRight - ANDROID_HORIZONTAL_PADDING_OFFSET
37
+ : ANDROID_DEFAULT_PADDING,
38
+ paddingBottom: ANDROID_DEFAULT_PADDING,
39
+ paddingTop: ANDROID_DEFAULT_PADDING,
40
+ height: rest.height + ANDROID_HEIGHT_OFFSET
26
41
  })
27
42
 
28
43
  const Picker = forwardRef(
29
44
  ({ value, onChange, onFocus, onBlur, style, inactive, children, placeholder, ...rest }, ref) => {
30
- // ungroup items, since there's no way to support groups on native
45
+ // Ungroup items, since there's no way to support groups on native
31
46
  const flatChildren = Children.toArray(children).flatMap((child) => {
32
47
  if (child.type === Group) {
33
48
  return child.props.children
@@ -16,6 +16,8 @@ import {
16
16
  import Picker from './Picker'
17
17
  import InputSupports from '../InputSupports'
18
18
 
19
+ import { ANDROID_VALIDATION_ICON_CONTAINER_OFFSET } from './constants'
20
+
19
21
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([
20
22
  a11yProps,
21
23
  inputSupportsProps,
@@ -133,7 +135,11 @@ const selectValidationIconContainerStyles = ({
133
135
  paddingBottom
134
136
  }) => ({
135
137
  paddingRight: icon ? paddingRight + iconSize : paddingRight,
136
- paddingBottom
138
+ ...(Platform.OS === 'android'
139
+ ? {
140
+ paddingBottom: paddingBottom + ANDROID_VALIDATION_ICON_CONTAINER_OFFSET
141
+ }
142
+ : { paddingBottom })
137
143
  })
138
144
 
139
145
  /**
@@ -0,0 +1,5 @@
1
+ // Because Android
2
+ export const ANDROID_VALIDATION_ICON_CONTAINER_OFFSET = 5
3
+ export const ANDROID_HEIGHT_OFFSET = 12
4
+ export const ANDROID_HORIZONTAL_PADDING_OFFSET = 8
5
+ export const ANDROID_DEFAULT_PADDING = 0
@@ -138,7 +138,11 @@ const Step = ({ label, name, status = 0, stepCount = 0, stepIndex = 0, tokens, .
138
138
  accessibilityCurrent={status === stepIndex}
139
139
  {...selectProps(rest)}
140
140
  >
141
- <StackView direction="row" space={0} tokens={{ alignItems: 'center', flexGrow: 0 }}>
141
+ <StackView
142
+ direction="row"
143
+ space={0}
144
+ tokens={{ alignItems: 'center', flexGrow: 0, justifyContent: 'center' }}
145
+ >
142
146
  <View
143
147
  style={[staticStyles.connector, !isFirst && selectConnectorStyles(themeTokens, isActive)]}
144
148
  />
@@ -1,4 +1,5 @@
1
1
  import React, { forwardRef } from 'react'
2
+ import PropTypes from 'prop-types'
2
3
  import {
3
4
  a11yProps,
4
5
  focusHandlerProps,
@@ -63,6 +64,10 @@ TextInput.displayName = 'TextInput'
63
64
  TextInput.propTypes = {
64
65
  ...selectedSystemPropTypes,
65
66
  ...textInputPropTypes,
67
+ /**
68
+ * A callback which if provided will get a clear button rendered and will be called whenever that button gets pressed.
69
+ */
70
+ onClear: PropTypes.func,
66
71
  tokens: getTokensPropType('TextInput'),
67
72
  variant: variantProp.propType
68
73
  }
@@ -1,19 +1,22 @@
1
- import React, { forwardRef, useEffect, useState } from 'react'
1
+ import React, { forwardRef, useEffect, useRef, useState } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import { Platform, StyleSheet, TextInput as NativeTextInput, View } from 'react-native'
4
4
  import { applyTextStyles, useTheme, useThemeTokens, applyOuterBorder } from '../ThemeProvider'
5
5
  import StackView from '../StackView'
6
+ import IconButton from '../IconButton'
6
7
  import {
7
8
  a11yProps,
8
9
  getTokensPropType,
9
10
  selectSystemProps,
10
11
  textInputHandlerProps,
11
12
  textInputProps,
13
+ useCopy,
12
14
  useInputValue,
13
15
  useSpacingScale,
14
16
  variantProp,
15
17
  viewProps
16
18
  } from '../utils'
19
+ import dictionary from './dictionary'
17
20
 
18
21
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([
19
22
  a11yProps,
@@ -133,12 +136,14 @@ const TextInputBase = forwardRef(
133
136
  (
134
137
  {
135
138
  buttons = [],
139
+ copy = 'en',
136
140
  height,
137
141
  inactive,
138
142
  initialValue,
139
143
  onBlur,
140
144
  onChange,
141
145
  onChangeText,
146
+ onClear,
142
147
  onFocus,
143
148
  onMouseOut,
144
149
  onMouseOver,
@@ -171,14 +176,18 @@ const TextInputBase = forwardRef(
171
176
  if (typeof onMouseOut === 'function') onMouseOut(event)
172
177
  }
173
178
 
174
- const { currentValue, setValue, isControlled } = useInputValue({
179
+ const defaultRef = useRef()
180
+ const inputRef = ref ?? defaultRef
181
+
182
+ const { currentValue, resetValue, setValue, isControlled, isDirty } = useInputValue({
175
183
  value,
176
184
  initialValue,
185
+ inputRef,
177
186
  onChange,
178
187
  readOnly
179
188
  })
180
189
 
181
- const element = ref?.current
190
+ const element = inputRef?.current
182
191
  useEffect(() => {
183
192
  if (Platform.OS === 'web' && pattern && element) {
184
193
  // React Native Web doesn't support `pattern`, so we have to attach it via a ref,
@@ -197,7 +206,25 @@ const TextInputBase = forwardRef(
197
206
 
198
207
  const themeTokens = useThemeTokens('TextInput', tokens, variant, states)
199
208
 
200
- const { icon: IconComponent, buttonsGap } = themeTokens
209
+ const { buttonsGap, clearButtonIcon: ClearButtonIcon, icon: IconComponent } = themeTokens
210
+ const buttonsGapSize = useSpacingScale(buttonsGap)
211
+ const getCopy = useCopy({ dictionary, copy })
212
+ if (onClear && isDirty) {
213
+ const handleClear = (event) => {
214
+ onClear?.(event)
215
+ resetValue(event)
216
+ inputRef?.current?.focus()
217
+ }
218
+ buttons?.unshift(
219
+ <IconButton
220
+ accessibilityLabel={getCopy('clearButtonAccessibilityLabel')}
221
+ icon={ClearButtonIcon}
222
+ key="clear"
223
+ onPress={handleClear}
224
+ variant={{ compact: true }}
225
+ />
226
+ )
227
+ }
201
228
 
202
229
  const inputProps = {
203
230
  ...selectProps(rest),
@@ -213,15 +240,12 @@ const TextInputBase = forwardRef(
213
240
  value: isControlled ? currentValue : undefined
214
241
  }
215
242
 
216
- // Get the actual gap value for the current viewport
217
- const buttonsGapSize = useSpacingScale(buttonsGap)
218
-
219
243
  const { themeOptions } = useTheme()
220
244
  const nativeInputStyle = selectInputStyles({ ...themeTokens, height }, themeOptions, inactive)
221
245
 
222
246
  return (
223
247
  <View style={selectOuterBorderStyles(themeTokens)}>
224
- <NativeTextInput ref={ref} style={nativeInputStyle} {...inputProps} />
248
+ <NativeTextInput ref={inputRef} style={nativeInputStyle} {...inputProps} />
225
249
  {IconComponent && (
226
250
  <View
227
251
  pointerEvents="none" // avoid hijacking input press events
@@ -249,12 +273,23 @@ TextInputBase.displayName = 'TextInputBase'
249
273
  TextInputBase.propTypes = {
250
274
  ...selectedSystemPropTypes,
251
275
  buttons: PropTypes.arrayOf(PropTypes.node),
276
+ /**
277
+ * Select English or French copy for the accessible labels.
278
+ * You may also pass in a custom dictionary object.
279
+ */
280
+ copy: PropTypes.oneOfType([
281
+ PropTypes.oneOf(['en', 'fr']),
282
+ PropTypes.shape({
283
+ clearButtonAccessibilityLabel: PropTypes.string
284
+ })
285
+ ]),
252
286
  height: PropTypes.number,
253
287
  inactive: PropTypes.bool,
254
288
  initialValue: PropTypes.string,
255
289
  onBlur: PropTypes.func,
256
290
  onChange: PropTypes.func,
257
291
  onChangeText: PropTypes.func,
292
+ onClear: PropTypes.func,
258
293
  onFocus: PropTypes.func,
259
294
  onMouseOut: PropTypes.func,
260
295
  onMouseOver: PropTypes.func,
@@ -0,0 +1,8 @@
1
+ export default {
2
+ en: {
3
+ clearButtonAccessibilityLabel: 'Clear'
4
+ },
5
+ fr: {
6
+ clearButtonAccessibilityLabel: 'Effacer'
7
+ }
8
+ }
@@ -8,14 +8,22 @@ export const uninitialisedError = new Error('Theme context used outside of Theme
8
8
  export const ThemeContext = createContext(uninitialisedError)
9
9
  export const ThemeSetterContext = createContext(uninitialisedError)
10
10
 
11
- const ThemeProvider = ({
12
- children,
13
- defaultTheme,
11
+ // These options default to `true` in v1.x to match legacy defaults and avoid breaking changes.
12
+ // This should change in future major releases to become "opt-in" legacy support.
13
+ const defaultThemeOptions = {
14
14
  // TODO: switch `forceAbsoluteFontSizing` to be false by default in the next major version
15
- themeOptions = { forceAbsoluteFontSizing: true }
16
- }) => {
15
+ forceAbsoluteFontSizing: true,
16
+ // TODO: switch `forceZIndex` to be false by default in the next major version
17
+ forceZIndex: true,
18
+ // TODO: switch `enableHelmetSSR` to be false by default in the next major version
19
+ enableHelmetSSR: true
20
+ }
21
+
22
+ const ThemeProvider = ({ children, defaultTheme, themeOptions = {} }) => {
17
23
  const [theme, setTheme] = useState(defaultTheme)
18
24
 
25
+ const appliedThemeOptions = { ...defaultThemeOptions, ...themeOptions }
26
+
19
27
  // Validate the theme tokens version on every render.
20
28
  // This will intentionally break the application when attempting to use an invalid theme.
21
29
  // This will surface an incompatibility quickly rather than allowing the potential for strange bugs due to missing or incompatible tokens.
@@ -23,7 +31,9 @@ const ThemeProvider = ({
23
31
 
24
32
  return (
25
33
  <ThemeSetterContext.Provider value={setTheme}>
26
- <ThemeContext.Provider value={{ ...theme, themeOptions }}>{children}</ThemeContext.Provider>
34
+ <ThemeContext.Provider value={{ ...theme, themeOptions: appliedThemeOptions }}>
35
+ {children}
36
+ </ThemeContext.Provider>
27
37
  </ThemeSetterContext.Provider>
28
38
  )
29
39
  }
@@ -43,9 +53,16 @@ ThemeProvider.propTypes = {
43
53
  * relative sizing (in `rem`, scales depending on the browser settings)
44
54
  * - `contentMaxWidth`: allows configuration of the content max width to be used in components
45
55
  * such as Footnote and Notification to avoid content to stretch width more then the page's width
56
+ * - `forceZIndex`: available on web only, when set to false, sets zIndex on `View` to be `auto`
57
+ * and when true, sets zIndex to be `0` (the default from `react-native-web`)
58
+ * - `enableHelmetSSR`: on Web SSR, allows React Helmet to run during server-side rendering. This should be
59
+ * disabled unless a web app has been specifically configured to stop React Helmet accumulating
60
+ * instances (which may cause a memory leak). See React Helmet's docs: https://github.com/nfl/react-helmet
46
61
  */
47
62
  themeOptions: PropTypes.shape({
48
63
  forceAbsoluteFontSizing: PropTypes.bool,
64
+ forceZIndex: PropTypes.bool,
65
+ enableHelmetSSR: PropTypes.bool,
49
66
  contentMaxWidth: responsiveProps.getTypeOptionallyByViewport(PropTypes.number)
50
67
  })
51
68
  }
@@ -45,7 +45,9 @@ export function applyTextStyles({
45
45
  // Don't set undefined font families. May need some validation here that the font is available.
46
46
  // Android doesn't recognise font weights natively so apply custom font weights via `fontFamily`.
47
47
  styles.fontFamily = `${fontName}${fontWeight}${fontStyle}`
48
- } else if (fontWeight) {
48
+ }
49
+
50
+ if (fontWeight) {
49
51
  // If using system default font, apply the font weight directly.
50
52
  // Font weight support in Android is limited to 'bold' or anything else === 'normal'.
51
53
  styles.fontWeight = Platform.OS === 'android' && Number(fontWeight) > 400 ? 'bold' : fontWeight
package/src/index.js CHANGED
@@ -21,6 +21,7 @@ export { default as InputSupports } from './InputSupports'
21
21
  export * from './Link'
22
22
  export { default as List, ListItem, ListBase } from './List'
23
23
  export { default as Modal } from './Modal'
24
+ export { default as MultiSelectFilter } from './MultiSelectFilter'
24
25
  export { default as Notification } from './Notification'
25
26
  export { default as Pagination } from './Pagination'
26
27
  export { default as Progress } from './Progress'
@@ -50,6 +51,7 @@ export { default as Typography } from './Typography'
50
51
 
51
52
  export { default as A11yInfoProvider, useA11yInfo } from './A11yInfoProvider'
52
53
  export { default as BaseProvider } from './BaseProvider'
54
+ export { useHydrationContext } from './BaseProvider/HydrationContext'
53
55
  export { default as ViewportProvider, useViewport, ViewportContext } from './ViewportProvider'
54
56
  export {
55
57
  default as ThemeProvider,
@@ -0,0 +1,38 @@
1
+ import React, { forwardRef } from 'react'
2
+ import { View as NativeView, StyleSheet } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+ import { useTheme } from '../../ThemeProvider'
5
+
6
+ /**
7
+ * Identical to React Native's View and supporting all the same props, but with:
8
+ * - a zIndex: 'auto' style added to prevent unexpectedly causing children to overlap other elements from other stacking contexts
9
+ */
10
+ const BaseView = forwardRef(({ children, style, ...rest }, ref) => {
11
+ const { themeOptions } = useTheme()
12
+ const styleProp = Array.isArray(style) ? [...style] : [style]
13
+
14
+ if (!themeOptions.forceZIndex) {
15
+ styleProp.unshift(styles.resetZIndex)
16
+ }
17
+
18
+ return (
19
+ <NativeView {...rest} style={styleProp} ref={ref}>
20
+ {children}
21
+ </NativeView>
22
+ )
23
+ })
24
+
25
+ BaseView.displayName = 'BaseView'
26
+
27
+ const styles = StyleSheet.create({
28
+ resetZIndex: {
29
+ zIndex: 'auto'
30
+ }
31
+ })
32
+
33
+ BaseView.propTypes = {
34
+ children: PropTypes.node,
35
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array])
36
+ }
37
+
38
+ export default BaseView
@@ -0,0 +1,6 @@
1
+ import { View as BaseView } from 'react-native'
2
+
3
+ /**
4
+ * Android crashes on non-standard style properties like `zIndex` so adding a `BaseView` for native platforms
5
+ */
6
+ export default BaseView
@@ -0,0 +1,3 @@
1
+ import BaseView from './BaseView'
2
+
3
+ export default BaseView
@@ -17,3 +17,4 @@ export { default as useUniqueId } from './useUniqueId'
17
17
  export { default as withLinkRouter } from './withLinkRouter'
18
18
  export * from './ssr'
19
19
  export { default as containUniqueFields } from './containUniqueFields'
20
+ export { default as BaseView } from './BaseView'