@telus-uds/components-base 1.18.1 → 1.20.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 (112) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/__tests17__/ThemeProvider/ThemeProvider.test.jsx +2 -1
  3. package/component-docs.json +1035 -231
  4. package/jest.config-android.js +17 -0
  5. package/jest.config-ios.js +18 -0
  6. package/jest.config-web.js +31 -0
  7. package/lib/BaseProvider/index.js +2 -1
  8. package/lib/Box/Box.js +14 -1
  9. package/lib/Button/ButtonBase.js +6 -2
  10. package/lib/Button/ButtonDropdown.js +207 -0
  11. package/lib/Button/index.js +8 -0
  12. package/lib/Carousel/Carousel.js +34 -6
  13. package/lib/Carousel/CarouselItem/CarouselItem.js +7 -1
  14. package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +22 -14
  15. package/lib/FlexGrid/Col/Col.js +1 -3
  16. package/lib/FlexGrid/FlexGrid.js +3 -5
  17. package/lib/FlexGrid/Row/Row.js +3 -3
  18. package/lib/IconButton/IconButton.js +12 -4
  19. package/lib/MultiSelectFilter/MultiSelectFilter.js +276 -0
  20. package/lib/MultiSelectFilter/dictionary.js +19 -0
  21. package/lib/MultiSelectFilter/index.js +13 -0
  22. package/lib/Pagination/SideButton.js +6 -4
  23. package/lib/Responsive/Responsive.js +58 -0
  24. package/lib/Responsive/index.js +13 -0
  25. package/lib/Search/Search.js +33 -63
  26. package/lib/Select/Picker.native.js +16 -13
  27. package/lib/Select/Select.js +7 -1
  28. package/lib/Select/constants.js +15 -0
  29. package/lib/StepTracker/Step.js +2 -1
  30. package/lib/Tags/Tags.js +10 -4
  31. package/lib/TextInput/TextInput.js +9 -2
  32. package/lib/TextInput/TextInputBase.js +98 -20
  33. package/lib/TextInput/dictionary.js +15 -0
  34. package/lib/ThemeProvider/ThemeProvider.js +6 -1
  35. package/lib/index.js +18 -0
  36. package/lib/utils/BaseView/BaseView.js +64 -0
  37. package/lib/utils/BaseView/BaseView.native.js +16 -0
  38. package/lib/utils/BaseView/index.js +13 -0
  39. package/lib/utils/index.js +10 -1
  40. package/lib/utils/input.js +11 -3
  41. package/lib/utils/props/handlerProps.js +5 -0
  42. package/lib-module/BaseProvider/index.js +2 -1
  43. package/lib-module/Box/Box.js +14 -1
  44. package/lib-module/Button/ButtonBase.js +6 -2
  45. package/lib-module/Button/ButtonDropdown.js +181 -0
  46. package/lib-module/Button/index.js +2 -1
  47. package/lib-module/Carousel/Carousel.js +34 -6
  48. package/lib-module/Carousel/CarouselItem/CarouselItem.js +8 -2
  49. package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +24 -16
  50. package/lib-module/FlexGrid/Col/Col.js +2 -3
  51. package/lib-module/FlexGrid/FlexGrid.js +2 -3
  52. package/lib-module/FlexGrid/Row/Row.js +2 -2
  53. package/lib-module/IconButton/IconButton.js +14 -4
  54. package/lib-module/MultiSelectFilter/MultiSelectFilter.js +248 -0
  55. package/lib-module/MultiSelectFilter/dictionary.js +12 -0
  56. package/lib-module/MultiSelectFilter/index.js +2 -0
  57. package/lib-module/Pagination/SideButton.js +6 -4
  58. package/lib-module/Responsive/Responsive.js +45 -0
  59. package/lib-module/Responsive/index.js +2 -0
  60. package/lib-module/Search/Search.js +33 -61
  61. package/lib-module/Select/Picker.native.js +15 -13
  62. package/lib-module/Select/Select.js +6 -1
  63. package/lib-module/Select/constants.js +5 -0
  64. package/lib-module/StepTracker/Step.js +2 -1
  65. package/lib-module/Tags/Tags.js +10 -4
  66. package/lib-module/TextInput/TextInput.js +6 -0
  67. package/lib-module/TextInput/TextInputBase.js +96 -21
  68. package/lib-module/TextInput/dictionary.js +8 -0
  69. package/lib-module/ThemeProvider/ThemeProvider.js +6 -1
  70. package/lib-module/index.js +2 -0
  71. package/lib-module/utils/BaseView/BaseView.js +43 -0
  72. package/lib-module/utils/BaseView/BaseView.native.js +6 -0
  73. package/lib-module/utils/BaseView/index.js +2 -0
  74. package/lib-module/utils/index.js +2 -1
  75. package/lib-module/utils/input.js +11 -3
  76. package/lib-module/utils/props/handlerProps.js +5 -0
  77. package/package.json +6 -3
  78. package/src/BaseProvider/index.jsx +4 -1
  79. package/src/Box/Box.jsx +14 -1
  80. package/src/Button/ButtonBase.jsx +4 -2
  81. package/src/Button/ButtonDropdown.jsx +179 -0
  82. package/src/Button/index.js +2 -1
  83. package/src/Carousel/Carousel.jsx +48 -13
  84. package/src/Carousel/CarouselItem/CarouselItem.jsx +9 -2
  85. package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +19 -15
  86. package/src/FlexGrid/Col/Col.jsx +4 -4
  87. package/src/FlexGrid/FlexGrid.jsx +11 -10
  88. package/src/FlexGrid/Row/Row.jsx +4 -3
  89. package/src/IconButton/IconButton.jsx +3 -1
  90. package/src/MultiSelectFilter/MultiSelectFilter.jsx +227 -0
  91. package/src/MultiSelectFilter/dictionary.js +12 -0
  92. package/src/MultiSelectFilter/index.js +3 -0
  93. package/src/Pagination/SideButton.jsx +5 -5
  94. package/src/Responsive/Responsive.jsx +33 -0
  95. package/src/Responsive/index.js +3 -0
  96. package/src/Search/Search.jsx +19 -33
  97. package/src/Select/Picker.native.jsx +29 -14
  98. package/src/Select/Select.jsx +7 -1
  99. package/src/Select/constants.js +5 -0
  100. package/src/StepTracker/Step.jsx +5 -1
  101. package/src/Tags/Tags.jsx +46 -33
  102. package/src/TextInput/TextInput.jsx +5 -0
  103. package/src/TextInput/TextInputBase.jsx +85 -20
  104. package/src/TextInput/dictionary.js +8 -0
  105. package/src/ThemeProvider/ThemeProvider.jsx +5 -1
  106. package/src/index.js +2 -0
  107. package/src/utils/BaseView/BaseView.jsx +38 -0
  108. package/src/utils/BaseView/BaseView.native.jsx +6 -0
  109. package/src/utils/BaseView/index.js +3 -0
  110. package/src/utils/index.js +1 -0
  111. package/src/utils/input.js +9 -4
  112. package/src/utils/props/handlerProps.js +4 -0
@@ -1,5 +1,5 @@
1
1
  import React, { forwardRef } from 'react'
2
- import { View, StyleSheet } from 'react-native'
2
+ import { View } from 'react-native'
3
3
 
4
4
  import PropTypes from 'prop-types'
5
5
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider'
@@ -17,7 +17,6 @@ import {
17
17
  } from '../utils'
18
18
  import TextInputBase from '../TextInput/TextInputBase'
19
19
  import ButtonBase from '../Button/ButtonBase'
20
- import StackView from '../StackView'
21
20
  import useCopy from '../utils/useCopy'
22
21
  import dictionary from './dictionary'
23
22
 
@@ -42,7 +41,6 @@ const selectInputTokens = ({ searchTokens, buttonTokens, buttonsGapSize }) => {
42
41
  }
43
42
  const selectButtonTokens = (tokens) => selectTokens('Button', tokens)
44
43
 
45
- const selectIconsContainerStyle = ({ paddingRight }) => ({ paddingRight })
46
44
  const selectIconTokens = ({ iconSize, iconColor }) => ({ color: iconColor, size: iconSize })
47
45
 
48
46
  /**
@@ -110,7 +108,8 @@ const Search = forwardRef(
110
108
 
111
109
  const handleClear = (event) => {
112
110
  setValue('', event)
113
- if (onClear !== undefined) onClear('', event)
111
+ onClear?.('', event)
112
+ ref?.current?.focus()
114
113
  }
115
114
 
116
115
  const handleFocus = (event) => {
@@ -126,7 +125,7 @@ const Search = forwardRef(
126
125
  const { nativeID, testID, ...containerProps } = selectContainerProps(rest)
127
126
 
128
127
  return (
129
- <View style={staticStyles.container} {...containerProps}>
128
+ <View {...containerProps}>
130
129
  <TextInputBase
131
130
  nativeID={nativeID}
132
131
  testID={testID}
@@ -150,28 +149,28 @@ const Search = forwardRef(
150
149
  onSubmitEditing={handleSubmit}
151
150
  onFocus={handleFocus}
152
151
  accessibilityLabel={a11yLabelText}
153
- />
154
- <View style={[staticStyles.iconsContainer, selectIconsContainerStyle(themeTokens)]}>
155
- <StackView direction="row" space={buttonsGap}>
156
- {ClearButtonIcon && !isEmpty && (
152
+ buttons={[
153
+ ClearButtonIcon && !isEmpty && (
157
154
  <ButtonBase
158
- onPress={handleClear}
159
- inactive={inactive}
160
- accessibilityRole="button"
161
155
  accessibilityLabel={getCopy('clearButtonAccessibilityLabel')}
156
+ accessibilityRole="button"
157
+ inactive={inactive}
158
+ key="clear"
159
+ onPress={handleClear}
162
160
  tokens={(appearances) => selectButtonTokens(getButtonTokens(appearances))}
163
161
  >
164
162
  {(buttonState) => (
165
163
  <ClearButtonIcon {...selectIconTokens(getButtonTokens(buttonState))} />
166
164
  )}
167
165
  </ButtonBase>
168
- )}
169
- {SubmitButtonIcon && (
166
+ ),
167
+ SubmitButtonIcon && (
170
168
  <ButtonBase
171
- onPress={handleSubmit}
172
- inactive={inactive}
173
- accessibilityRole="button"
174
169
  accessibilityLabel={getCopy('submitButtonAccessibilityLabel')}
170
+ accessibilityRole="button"
171
+ inactive={inactive}
172
+ key="submit"
173
+ onPress={handleSubmit}
175
174
  tokens={(buttonState) =>
176
175
  selectButtonTokens(getButtonTokens({ ...buttonState, priority: 'high' }))
177
176
  }
@@ -182,9 +181,9 @@ const Search = forwardRef(
182
181
  />
183
182
  )}
184
183
  </ButtonBase>
185
- )}
186
- </StackView>
187
- </View>
184
+ )
185
+ ]}
186
+ />
188
187
  </View>
189
188
  )
190
189
  }
@@ -248,16 +247,3 @@ Search.propTypes = {
248
247
  }
249
248
 
250
249
  export default Search
251
-
252
- const staticStyles = StyleSheet.create({
253
- container: {
254
- // No styles needed here except the View defaults (position: relative etc)
255
- },
256
- iconsContainer: {
257
- position: 'absolute',
258
- right: 0,
259
- top: 0,
260
- bottom: 0,
261
- justifyContent: 'center'
262
- }
263
- })
@@ -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
  />
package/src/Tags/Tags.jsx CHANGED
@@ -30,39 +30,49 @@ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
30
30
  viewProps
31
31
  ])
32
32
 
33
- const selectIconTextTokens = ({
34
- icon,
35
- iconPosition,
36
- iconSpace,
37
- iconSize,
38
- iconColor,
39
- iconBackground,
40
- iconBorderRadius,
41
- iconAlignSelf,
42
- iconPadding,
43
- iconTranslateX,
44
- iconTranslateY
45
- }) => ({
46
- icon,
47
- iconPosition,
48
- iconSpace,
49
- iconWrapperStyle: {
50
- backgroundColor: iconBackground,
51
- borderRadius: iconBorderRadius,
52
- alignSelf: iconAlignSelf,
53
- padding: iconPadding,
54
- ...Platform.select({
55
- // TODO: https://github.com/telus/universal-design-system/issues/487
56
- web: { transition: 'color 200ms, background 200ms' }
57
- })
33
+ const separateIconTextTokens = (
34
+ {
35
+ icon,
36
+ iconPosition,
37
+ iconSpace,
38
+ iconSize,
39
+ iconColor,
40
+ iconBackground,
41
+ iconBorderRadius,
42
+ iconAlignSelf,
43
+ iconPadding,
44
+ iconTranslateX,
45
+ iconTranslateY,
46
+ ...rest
58
47
  },
59
- iconTokens: {
60
- size: iconSize,
61
- color: iconColor,
62
- translateX: iconTranslateX,
63
- translateY: iconTranslateY
64
- }
65
- })
48
+ returnRest
49
+ ) =>
50
+ returnRest
51
+ ? rest
52
+ : {
53
+ icon,
54
+ iconPosition,
55
+ iconSpace,
56
+ iconWrapperStyle: {
57
+ backgroundColor: iconBackground,
58
+ borderRadius: iconBorderRadius,
59
+ alignSelf: iconAlignSelf,
60
+ padding: iconPadding,
61
+ ...Platform.select({
62
+ // TODO: https://github.com/telus/universal-design-system/issues/487
63
+ web: { transition: 'color 200ms, background 200ms' }
64
+ })
65
+ },
66
+ iconTokens: {
67
+ size: iconSize,
68
+ color: iconColor,
69
+ translateX: iconTranslateX,
70
+ translateY: iconTranslateY
71
+ }
72
+ }
73
+
74
+ const selectIconTextTokens = (tokens) => separateIconTextTokens(tokens, false)
75
+ const selectNonIconTextTokens = (tokens) => separateIconTextTokens(tokens, true)
66
76
 
67
77
  const Tags = forwardRef(
68
78
  (
@@ -87,7 +97,10 @@ const Tags = forwardRef(
87
97
  const { direction, space } = themeTokens
88
98
 
89
99
  const getItemTokens = useThemeTokensCallback('TagsItem', tokens, variant)
90
- const getButtonTokens = (buttonState) => selectTokens('Button', getItemTokens(buttonState))
100
+
101
+ const getButtonTokens = (buttonState) =>
102
+ // Remove icon-text-related tokens, since we want to handle them ourselves, not use ButtonBase's handling
103
+ selectTokens('Button', selectNonIconTextTokens(getItemTokens(buttonState)))
91
104
 
92
105
  const { currentValues, toggleOneValue } = useMultipleInputValues({
93
106
  initialValues,
@@ -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,18 +1,22 @@
1
- import React, { forwardRef, useEffect, useState } from 'react'
2
- import { Platform, StyleSheet, TextInput as NativeTextInput, View } from 'react-native'
3
-
1
+ import React, { forwardRef, useEffect, useRef, useState } from 'react'
4
2
  import PropTypes from 'prop-types'
3
+ import { Platform, StyleSheet, TextInput as NativeTextInput, View } from 'react-native'
5
4
  import { applyTextStyles, useTheme, useThemeTokens, applyOuterBorder } from '../ThemeProvider'
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,
15
+ useSpacingScale,
13
16
  variantProp,
14
17
  viewProps
15
18
  } from '../utils'
19
+ import dictionary from './dictionary'
16
20
 
17
21
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([
18
22
  a11yProps,
@@ -116,27 +120,37 @@ const selectIconTokens = ({ iconSize, iconColor }) => ({
116
120
  color: iconColor
117
121
  })
118
122
 
119
- const selectIconContainerStyles = ({ paddingRight, paddingBottom }) => ({
120
- paddingRight,
123
+ const selectIconContainerStyles = (
124
+ { buttonSize, buttonsGapSize, paddingRight, paddingBottom },
125
+ buttonCount
126
+ ) => ({
127
+ paddingRight: paddingRight + buttonCount * (buttonSize + buttonsGapSize),
121
128
  paddingBottom
122
129
  })
123
130
 
131
+ const selectButtonsContainerStyle = ({ buttonsPaddingRight }) => ({
132
+ paddingRight: buttonsPaddingRight
133
+ })
134
+
124
135
  const TextInputBase = forwardRef(
125
136
  (
126
137
  {
127
- value,
138
+ buttons = [],
139
+ copy = 'en',
128
140
  height,
129
- initialValue,
130
141
  inactive,
131
- readOnly,
142
+ initialValue,
143
+ onBlur,
132
144
  onChange,
133
145
  onChangeText,
146
+ onClear,
134
147
  onFocus,
135
- onBlur,
136
- onMouseOver,
137
148
  onMouseOut,
149
+ onMouseOver,
138
150
  pattern,
151
+ readOnly,
139
152
  tokens,
153
+ value,
140
154
  variant = {},
141
155
  ...rest
142
156
  },
@@ -162,14 +176,18 @@ const TextInputBase = forwardRef(
162
176
  if (typeof onMouseOut === 'function') onMouseOut(event)
163
177
  }
164
178
 
165
- const { currentValue, setValue, isControlled } = useInputValue({
179
+ const defaultRef = useRef()
180
+ const inputRef = ref ?? defaultRef
181
+
182
+ const { currentValue, resetValue, setValue, isControlled, isDirty } = useInputValue({
166
183
  value,
167
184
  initialValue,
185
+ inputRef,
168
186
  onChange,
169
187
  readOnly
170
188
  })
171
189
 
172
- const element = ref?.current
190
+ const element = inputRef?.current
173
191
  useEffect(() => {
174
192
  if (Platform.OS === 'web' && pattern && element) {
175
193
  // React Native Web doesn't support `pattern`, so we have to attach it via a ref,
@@ -188,7 +206,25 @@ const TextInputBase = forwardRef(
188
206
 
189
207
  const themeTokens = useThemeTokens('TextInput', tokens, variant, states)
190
208
 
191
- const { icon: IconComponent } = 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
+ }
192
228
 
193
229
  const inputProps = {
194
230
  ...selectProps(rest),
@@ -209,15 +245,25 @@ const TextInputBase = forwardRef(
209
245
 
210
246
  return (
211
247
  <View style={selectOuterBorderStyles(themeTokens)}>
212
- <NativeTextInput ref={ref} style={nativeInputStyle} {...inputProps} />
248
+ <NativeTextInput ref={inputRef} style={nativeInputStyle} {...inputProps} />
213
249
  {IconComponent && (
214
250
  <View
215
251
  pointerEvents="none" // avoid hijacking input press events
216
- style={[staticStyles.iconContainer, selectIconContainerStyles(themeTokens)]}
252
+ style={[
253
+ staticStyles.iconContainer,
254
+ selectIconContainerStyles({ ...themeTokens, buttonsGapSize }, buttons?.length)
255
+ ]}
217
256
  >
218
257
  <IconComponent {...selectIconTokens(themeTokens)} />
219
258
  </View>
220
259
  )}
260
+ {buttons?.length > 0 && (
261
+ <View style={[staticStyles.buttonsContainer, selectButtonsContainerStyle(themeTokens)]}>
262
+ <StackView direction="row" space={buttonsGap}>
263
+ {buttons}
264
+ </StackView>
265
+ </View>
266
+ )}
221
267
  </View>
222
268
  )
223
269
  }
@@ -226,24 +272,43 @@ TextInputBase.displayName = 'TextInputBase'
226
272
 
227
273
  TextInputBase.propTypes = {
228
274
  ...selectedSystemPropTypes,
229
- value: PropTypes.string,
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
+ ]),
230
286
  height: PropTypes.number,
231
- initialValue: PropTypes.string,
232
287
  inactive: PropTypes.bool,
233
- readOnly: PropTypes.bool,
288
+ initialValue: PropTypes.string,
289
+ onBlur: PropTypes.func,
234
290
  onChange: PropTypes.func,
235
291
  onChangeText: PropTypes.func,
292
+ onClear: PropTypes.func,
236
293
  onFocus: PropTypes.func,
237
- onBlur: PropTypes.func,
238
- onMouseOver: PropTypes.func,
239
294
  onMouseOut: PropTypes.func,
295
+ onMouseOver: PropTypes.func,
296
+ readOnly: PropTypes.bool,
240
297
  tokens: getTokensPropType('TextInput', 'TextArea'),
298
+ value: PropTypes.string,
241
299
  variant: variantProp.propType
242
300
  }
243
301
 
244
302
  export default TextInputBase
245
303
 
246
304
  const staticStyles = StyleSheet.create({
305
+ buttonsContainer: {
306
+ position: 'absolute',
307
+ right: 0,
308
+ top: 0,
309
+ bottom: 0,
310
+ justifyContent: 'center'
311
+ },
247
312
  iconContainer: {
248
313
  position: 'absolute',
249
314
  right: 0,
@@ -0,0 +1,8 @@
1
+ export default {
2
+ en: {
3
+ clearButtonAccessibilityLabel: 'Clear'
4
+ },
5
+ fr: {
6
+ clearButtonAccessibilityLabel: 'Effacer'
7
+ }
8
+ }
@@ -12,7 +12,8 @@ const ThemeProvider = ({
12
12
  children,
13
13
  defaultTheme,
14
14
  // TODO: switch `forceAbsoluteFontSizing` to be false by default in the next major version
15
- themeOptions = { forceAbsoluteFontSizing: true }
15
+ // TODO: switch `forceZIndex` to be false by default in the next major version
16
+ themeOptions = { forceAbsoluteFontSizing: true, forceZIndex: true }
16
17
  }) => {
17
18
  const [theme, setTheme] = useState(defaultTheme)
18
19
 
@@ -43,9 +44,12 @@ ThemeProvider.propTypes = {
43
44
  * relative sizing (in `rem`, scales depending on the browser settings)
44
45
  * - `contentMaxWidth`: allows configuration of the content max width to be used in components
45
46
  * such as Footnote and Notification to avoid content to stretch width more then the page's width
47
+ * - `forceZIndex`: available on web only, when set to false, sets zIndex on `View` to be `auto`
48
+ * and when true, sets zIndex to be `0` (the default from `react-native-web`)
46
49
  */
47
50
  themeOptions: PropTypes.shape({
48
51
  forceAbsoluteFontSizing: PropTypes.bool,
52
+ forceZIndex: PropTypes.bool,
49
53
  contentMaxWidth: responsiveProps.getTypeOptionallyByViewport(PropTypes.number)
50
54
  })
51
55
  }
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'
@@ -29,6 +30,7 @@ export { default as Radio } from './Radio'
29
30
  export * from './Radio'
30
31
  export { default as RadioCard } from './RadioCard'
31
32
  export * from './RadioCard'
33
+ export { default as Responsive } from './Responsive'
32
34
  export { default as Search } from './Search'
33
35
  export { default as Select } from './Select'
34
36
  export { default as SideNav } from './SideNav'
@@ -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'
@@ -66,7 +66,7 @@ export const useInputValue = (props = {}, hookName = 'useInputValue') => {
66
66
  const [isControlled] = useState(isCurrentlyControlled)
67
67
  validateProps(props, { isControlled, isCurrentlyControlled }, hookName)
68
68
 
69
- const { value, initialValue, onChange, readOnly = false } = props
69
+ const { value, initialValue, inputRef, onChange, readOnly = false } = props
70
70
  const [ownValue, setOwnValue] = useState(!isControlled && initialValue)
71
71
  const currentValue = isControlled ? value : ownValue
72
72
 
@@ -76,19 +76,24 @@ export const useInputValue = (props = {}, hookName = 'useInputValue') => {
76
76
  // Make current value accessible inside useCallback without rememoizing every time the value changes
77
77
  valueRef.current.value = currentValue
78
78
 
79
+ const isDirty = currentValue !== valueRef.current.initial
80
+
79
81
  const setValue = useCallback(
80
82
  (arg, event) => {
81
83
  if (readOnly) return
82
84
  const newValue = typeof arg === 'function' ? arg(valueRef.current.value) : arg
83
- if (!isControlled) setOwnValue(newValue)
85
+ if (!isControlled) {
86
+ setOwnValue(newValue)
87
+ if (inputRef?.current) inputRef.current.value = newValue ?? ''
88
+ }
84
89
  // Call onChange handler if there's something for it to handle (event or a changed value)
85
90
  if (onChange && (event || valueRef.current.value !== newValue)) onChange(newValue, event)
86
91
  },
87
- [isControlled, onChange, readOnly]
92
+ [inputRef, isControlled, onChange, readOnly]
88
93
  )
89
94
  const resetValue = useCallback((event) => setValue(valueRef.current.initial, event), [setValue])
90
95
 
91
- return { currentValue, setValue, resetValue, isControlled }
96
+ return { currentValue, setValue, resetValue, isControlled, isDirty }
92
97
  }
93
98
 
94
99
  /**
@@ -26,6 +26,10 @@ const textInputHandlerProps = {
26
26
  * onChangeText handler
27
27
  */
28
28
  onChangeText: PropTypes.func,
29
+ /**
30
+ * onClear handler
31
+ */
32
+ onClear: PropTypes.func,
29
33
  /**
30
34
  * onSubmit handler
31
35
  */