@telus-uds/components-base 2.3.0 → 2.4.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.
@@ -247,10 +247,16 @@ const Notification = React.forwardRef(
247
247
  const viewport = useViewport()
248
248
  const getCopy = useCopy({ dictionary, copy })
249
249
 
250
+ // TODO: Remove this once the system style variant is deprecated
251
+ const isSystemEnabled = system || variant?.style?.includes('system')
252
+
250
253
  const { themeOptions } = useTheme()
251
254
  const { enableMediaQueryStyleSheet } = themeOptions
252
255
  const useTokens = enableMediaQueryStyleSheet ? useResponsiveThemeTokens : useThemeTokens
253
- const themeTokens = useTokens('Notification', tokens, variant, { system, viewport })
256
+ const themeTokens = useTokens('Notification', tokens, variant, {
257
+ system: isSystemEnabled,
258
+ viewport
259
+ })
254
260
  const maxWidth = useResponsiveProp(themeOptions?.contentMaxWidth)
255
261
 
256
262
  const notificationComponentRef = React.useRef({
@@ -281,7 +287,7 @@ const Notification = React.forwardRef(
281
287
  mediaIdsRef,
282
288
  dismissible,
283
289
  viewport,
284
- system
290
+ isSystemEnabled
285
291
  )
286
292
  } else {
287
293
  notificationComponentRef.current = getDefaultStyles(
@@ -290,7 +296,7 @@ const Notification = React.forwardRef(
290
296
  maxWidth,
291
297
  dismissible,
292
298
  viewport,
293
- system
299
+ isSystemEnabled
294
300
  )
295
301
  }
296
302
 
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { View } from 'react-native'
2
+ import { View, Platform } from 'react-native'
3
3
 
4
4
  import PropTypes from 'prop-types'
5
5
  import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider'
@@ -20,6 +20,7 @@ import TextInputBase from '../TextInput/TextInputBase'
20
20
  import ButtonBase from '../Button/ButtonBase'
21
21
  import useCopy from '../utils/useCopy'
22
22
  import dictionary from './dictionary'
23
+ import Icon from '../Icon'
23
24
 
24
25
  const [selectContainerProps, selectedContainerPropTypes] = selectSystemProps([
25
26
  a11yProps,
@@ -44,10 +45,25 @@ const selectInputTokens = ({ searchTokens, buttonTokens, buttonsGapSize }) => {
44
45
 
45
46
  return { ...selectTokens('TextInput', searchTokens), paddingRight: paddingWithButtons }
46
47
  }
47
- const selectButtonTokens = (tokens) => selectTokens('Button', tokens)
48
+ const selectButtonTokens = (tokens) => selectTokens('SearchButton', tokens)
48
49
 
49
50
  const selectIconTokens = ({ iconSize, iconColor }) => ({ color: iconColor, size: iconSize })
50
51
 
52
+ const selectBorderTokens = ({ borderWidth, mobileBorderWidth, ...tokens }) => ({
53
+ borderWidth: Platform.OS === 'web' ? borderWidth : mobileBorderWidth,
54
+ ...tokens
55
+ })
56
+
57
+ const modifyButtonTokens = (tokens) => {
58
+ const modifiedTokens = { ...tokens }
59
+
60
+ if (Platform.OS !== 'web') {
61
+ modifiedTokens.backgroundColor = tokens.mobileBackgroundColor
62
+ }
63
+
64
+ return modifiedTokens
65
+ }
66
+
51
67
  /**
52
68
  * The `Search` component is a combination of a `TextInput` and 2 different kinds of custom buttons.
53
69
  * Use `Search` to feature autocomplete interactions.
@@ -77,6 +93,7 @@ const Search = React.forwardRef(
77
93
  tokens,
78
94
  variant,
79
95
  nativeSubmitBtnID,
96
+ searchIconPosition = 'right',
80
97
  dataSet,
81
98
  ...rest
82
99
  },
@@ -137,9 +154,9 @@ const Search = React.forwardRef(
137
154
  testID={testID}
138
155
  {...selectInputProps(rest)}
139
156
  ref={ref}
140
- tokens={(appearances) =>
157
+ tokens={(pressableStates) =>
141
158
  selectInputTokens({
142
- searchTokens: getThemeTokens(appearances),
159
+ searchTokens: selectBorderTokens(getThemeTokens(pressableStates)),
143
160
  buttonTokens,
144
161
  buttonsGapSize,
145
162
  isEmpty
@@ -155,6 +172,7 @@ const Search = React.forwardRef(
155
172
  onSubmitEditing={handleSubmit}
156
173
  onFocus={handleFocus}
157
174
  accessibilityLabel={a11yLabelText}
175
+ direction={searchIconPosition}
158
176
  buttons={[
159
177
  ClearButtonIcon && !isEmpty && (
160
178
  <ButtonBase
@@ -163,32 +181,42 @@ const Search = React.forwardRef(
163
181
  inactive={inactive}
164
182
  key="clear"
165
183
  onPress={handleClear}
166
- tokens={(appearances) => selectButtonTokens(getButtonTokens(appearances))}
184
+ tokens={(pressableStates) =>
185
+ modifyButtonTokens(selectButtonTokens(getButtonTokens(pressableStates)))
186
+ }
167
187
  >
168
188
  {(buttonState) => (
169
189
  <ClearButtonIcon {...selectIconTokens(getButtonTokens(buttonState))} />
170
190
  )}
171
191
  </ButtonBase>
172
192
  ),
173
- SubmitButtonIcon && (
174
- <ButtonBase
175
- accessibilityLabel={getCopy('submitButtonAccessibilityLabel')}
176
- accessibilityRole="button"
177
- inactive={inactive}
178
- key="submit"
179
- onPress={handleSubmit}
180
- tokens={(buttonState) =>
181
- selectButtonTokens(getButtonTokens({ ...buttonState, priority: 'high' }))
182
- }
183
- nativeID={nativeSubmitBtnID}
184
- >
185
- {(buttonState) => (
186
- <SubmitButtonIcon
187
- {...selectIconTokens(getButtonTokens({ ...buttonState, priority: 'high' }))}
188
- />
189
- )}
190
- </ButtonBase>
191
- )
193
+ SubmitButtonIcon &&
194
+ (searchIconPosition === 'right' ? (
195
+ <ButtonBase
196
+ accessibilityLabel={getCopy('submitButtonAccessibilityLabel')}
197
+ accessibilityRole="button"
198
+ inactive={inactive}
199
+ key="submit-right"
200
+ onPress={handleSubmit}
201
+ tokens={(buttonState) =>
202
+ selectButtonTokens(getButtonTokens({ ...buttonState, priority: 'high' }))
203
+ }
204
+ nativeID={nativeSubmitBtnID}
205
+ >
206
+ {(buttonState) => (
207
+ <SubmitButtonIcon
208
+ {...selectIconTokens(getButtonTokens({ ...buttonState, priority: 'high' }))}
209
+ />
210
+ )}
211
+ </ButtonBase>
212
+ ) : (
213
+ <Icon
214
+ icon={SubmitButtonIcon}
215
+ key="submitIcon"
216
+ testID="iconLeft"
217
+ tokens={{ color: themeTokens.iconLeftColor, size: themeTokens.iconLeftSize }}
218
+ />
219
+ ))
192
220
  ]}
193
221
  />
194
222
  </View>
@@ -7,9 +7,11 @@ import { a11yProps, componentPropType } from '../utils'
7
7
  import Group from './Group'
8
8
 
9
9
  import {
10
- ANDROID_HEIGHT_OFFSET,
11
10
  ANDROID_HORIZONTAL_PADDING_OFFSET,
12
- ANDROID_DEFAULT_PADDING
11
+ ANDROID_DEFAULT_PADDING,
12
+ ALLOW_FONT_SCALING,
13
+ ADJUSTS_FONT_SIZE_TO_FIT,
14
+ MAX_FONT_SIZE_MULTIPLIER
13
15
  } from './constants'
14
16
 
15
17
  // Styling of the native input is very limited, most of the styles have to be applied to an additional View
@@ -36,8 +38,7 @@ const selectAndroidContainerStyles = ({
36
38
  ? paddingRight - ANDROID_HORIZONTAL_PADDING_OFFSET
37
39
  : ANDROID_DEFAULT_PADDING,
38
40
  paddingBottom: ANDROID_DEFAULT_PADDING,
39
- paddingTop: ANDROID_DEFAULT_PADDING,
40
- height: rest.height + ANDROID_HEIGHT_OFFSET
41
+ paddingTop: ANDROID_DEFAULT_PADDING
41
42
  })
42
43
 
43
44
  const Picker = React.forwardRef(
@@ -85,6 +86,11 @@ const Picker = React.forwardRef(
85
86
  inputAndroid: selectAndroidInputStyles(style)
86
87
  }}
87
88
  placeholder={placeholder !== undefined ? placeholder : {}}
89
+ textInputProps={{
90
+ allowFontScaling: ALLOW_FONT_SCALING,
91
+ adjustFontSizeToFit: ADJUSTS_FONT_SIZE_TO_FIT,
92
+ maxFontSizeMultiplier: MAX_FONT_SIZE_MULTIPLIER
93
+ }}
88
94
  />
89
95
  )
90
96
 
@@ -1,5 +1,8 @@
1
1
  // Because Android
2
2
  export const ANDROID_VALIDATION_ICON_CONTAINER_OFFSET = 5
3
- export const ANDROID_HEIGHT_OFFSET = 12
4
3
  export const ANDROID_HORIZONTAL_PADDING_OFFSET = 8
5
4
  export const ANDROID_DEFAULT_PADDING = 0
5
+
6
+ export const ALLOW_FONT_SCALING = true
7
+ export const ADJUSTS_FONT_SIZE_TO_FIT = true
8
+ export const MAX_FONT_SIZE_MULTIPLIER = 3
@@ -70,7 +70,6 @@ const TextArea = React.forwardRef(({ tokens, variant = {}, ...rest }, ref) => {
70
70
 
71
71
  const inputProps = {
72
72
  ...selectedProps,
73
- variant: { ...variant, validation: supportsProps.validation },
74
73
  multiline: true,
75
74
  onContentSizeChange: onHeightChange,
76
75
  height: inputHeight,
@@ -78,10 +77,18 @@ const TextArea = React.forwardRef(({ tokens, variant = {}, ...rest }, ref) => {
78
77
  }
79
78
 
80
79
  return (
81
- <InputSupports {...supportsProps}>
82
- {({ inputId, ...props }) => (
83
- <TextInputBase ref={ref} {...inputProps} nativeID={inputId} {...props} />
84
- )}
80
+ <InputSupports {...supportsProps} inputValue={selectedProps?.value}>
81
+ {({ inputId, ...props }) => {
82
+ return (
83
+ <TextInputBase
84
+ ref={ref}
85
+ nativeID={inputId}
86
+ {...props}
87
+ {...inputProps}
88
+ variant={{ ...variant, validation: props.validation }}
89
+ />
90
+ )
91
+ }}
85
92
  </InputSupports>
86
93
  )
87
94
  })
@@ -47,16 +47,23 @@ const TextInput = React.forwardRef(({ tokens, nativeID, variant = {}, ...rest },
47
47
 
48
48
  const inputProps = {
49
49
  ...selectedProps,
50
- tokens,
51
- variant: { ...variant, validation: supportsProps.validation }
50
+ tokens
52
51
  }
53
52
 
54
53
  return (
55
54
  <>
56
- <InputSupports nativeID={nativeID} {...supportsProps}>
57
- {({ inputId, ...propsFromInputSupports }) => (
58
- <TextInputBase ref={ref} nativeID={inputId} {...propsFromInputSupports} {...inputProps} />
59
- )}
55
+ <InputSupports nativeID={nativeID} {...supportsProps} inputValue={selectedProps?.value}>
56
+ {({ inputId, ...propsFromInputSupports }) => {
57
+ return (
58
+ <TextInputBase
59
+ ref={ref}
60
+ nativeID={inputId}
61
+ {...propsFromInputSupports}
62
+ {...inputProps}
63
+ variant={{ ...variant, validation: propsFromInputSupports.validation }}
64
+ />
65
+ )
66
+ }}
60
67
  </InputSupports>
61
68
  {rest.children}
62
69
  </>
@@ -53,7 +53,10 @@ const selectInputStyles = (
53
53
  buttonCount = 0,
54
54
  buttonSize = 0,
55
55
  buttonsGapSize = 0,
56
- isPassword
56
+ isPassword,
57
+ iconLeftWidth,
58
+ iconLeftGap,
59
+ direction
57
60
  ) => {
58
61
  // Subtract border width from padding so overall input width/height doesn't
59
62
  // jump around if the border width changes (avoiding NaN and negative padding)
@@ -85,6 +88,16 @@ const selectInputStyles = (
85
88
  }
86
89
  })
87
90
 
91
+ const getPaddingLeft = () => {
92
+ if (type === 'card') {
93
+ return offsetBorder(paddingLeft + 34)
94
+ }
95
+ if (direction === 'left') {
96
+ return offsetBorder(iconLeftWidth + iconLeftGap + staticStyles.iconLeftContainer.paddingLeft)
97
+ }
98
+ return offsetBorder(paddingLeft)
99
+ }
100
+
88
101
  const buttonSpacing = isPassword ? buttonsGapSize : -buttonsGapSize
89
102
  const adjustedPaddingRight = paddingRight + (buttonCount ? 1 : 0) * (buttonSize + buttonSpacing)
90
103
  const adjustedPaddingWithButtons = buttonCount > 1 ? paddingRight : adjustedPaddingRight
@@ -96,7 +109,7 @@ const selectInputStyles = (
96
109
  borderWidth,
97
110
  borderColor,
98
111
  borderRadius,
99
- paddingLeft: type === 'card' ? offsetBorder(paddingLeft + 34) : offsetBorder(paddingLeft),
112
+ paddingLeft: getPaddingLeft(),
100
113
  paddingRight: icon ? offsetBorder(paddingWithIcon) : offsetBorder(adjustedPaddingWithButtons),
101
114
  paddingTop: offsetBorder(paddingTop),
102
115
  paddingBottom: offsetBorder(paddingBottom),
@@ -136,12 +149,14 @@ const selectIconContainerStyles = (
136
149
  paddingBottom
137
150
  })
138
151
 
139
- const selectLeftIconContainerStyles = ({ leftIconPaddingBottom }) => ({
140
- // not tokenizing paddingLeft as it remains same across brands for now
141
- paddingLeft: 10,
152
+ const selectIconCardLeftContainerStyles = ({ leftIconPaddingBottom }) => ({
142
153
  paddingBottom: leftIconPaddingBottom
143
154
  })
144
155
 
156
+ const selectIconLeftContainerStyles = ({ iconLeftPaddingBottom }) => ({
157
+ paddingBottom: iconLeftPaddingBottom
158
+ })
159
+
145
160
  const selectButtonsContainerStyle = ({ buttonsPaddingRight }) => ({
146
161
  paddingRight: buttonsPaddingRight
147
162
  })
@@ -189,12 +204,14 @@ const TextInputBase = React.forwardRef(
189
204
  variant = {},
190
205
  type,
191
206
  onKeyPress,
207
+ direction,
192
208
  ...rest
193
209
  },
194
210
  ref
195
211
  ) => {
196
212
  const [isFocused, setIsFocused] = React.useState(false)
197
213
  const [showPassword, setShowPassword] = React.useState(false)
214
+
198
215
  const handleFocus = (event) => {
199
216
  setIsFocused(true)
200
217
  if (typeof onFocus === 'function') onFocus(event)
@@ -273,11 +290,17 @@ const TextInputBase = React.forwardRef(
273
290
  defaultCreditIcon,
274
291
  amexIcon,
275
292
  visaIcon,
276
- masterCardIcon
293
+ masterCardIcon,
294
+ iconLeftGap
277
295
  } = themeTokens
278
296
  const buttonsGapSize = useSpacingScale(buttonsGap)
279
297
  const getCopy = useCopy({ dictionary, copy })
280
- const textInputButtons = buttons
298
+ const textInputButtons =
299
+ direction === 'left'
300
+ ? buttons.filter((button) => button && button.key !== 'submitIcon')
301
+ : buttons
302
+ const submitIcon =
303
+ direction === 'left' ? buttons.find((button) => button && button.key === 'submitIcon') : null
281
304
 
282
305
  if (onClear && isDirty) {
283
306
  textInputButtons?.unshift(
@@ -323,6 +346,7 @@ const TextInputBase = React.forwardRef(
323
346
  }
324
347
 
325
348
  const { themeOptions } = useTheme()
349
+ const iconLeftWidth = submitIcon ? submitIcon.props.tokens.size ?? 0 : 0
326
350
  const nativeInputStyle = selectInputStyles(
327
351
  { ...themeTokens, height },
328
352
  themeOptions,
@@ -331,15 +355,30 @@ const TextInputBase = React.forwardRef(
331
355
  buttons?.length,
332
356
  themeTokens.buttonSize,
333
357
  buttonsGapSize,
334
- isPassword
358
+ isPassword,
359
+ iconLeftWidth,
360
+ iconLeftGap,
361
+ direction
335
362
  )
336
363
 
364
+ const shouldShowSubmitIcon = submitIcon && direction === 'left' && !inactive
365
+
337
366
  return (
338
367
  <View style={selectOuterBorderStyles(themeTokens)}>
368
+ {shouldShowSubmitIcon && (
369
+ <View
370
+ style={[staticStyles.iconLeftContainer, selectIconLeftContainerStyles(themeTokens)]}
371
+ >
372
+ {submitIcon}
373
+ </View>
374
+ )}
339
375
  {type === 'card' && (
340
376
  <View
341
377
  pointerEvents="none"
342
- style={[staticStyles.leftIconContainer, selectLeftIconContainerStyles(themeTokens)]}
378
+ style={[
379
+ staticStyles.iconCardLeftContainer,
380
+ selectIconCardLeftContainerStyles(themeTokens)
381
+ ]}
343
382
  >
344
383
  {getIcon(currentValue, { defaultCreditIcon, amexIcon, visaIcon, masterCardIcon })}
345
384
  </View>
@@ -424,7 +463,15 @@ const staticStyles = StyleSheet.create({
424
463
  right: 0,
425
464
  bottom: 0
426
465
  },
427
- leftIconContainer: {
466
+ iconCardLeftContainer: {
467
+ paddingLeft: 10,
468
+ position: 'absolute',
469
+ left: 0,
470
+ bottom: 0,
471
+ zIndex: 1
472
+ },
473
+ iconLeftContainer: {
474
+ paddingLeft: 10,
428
475
  position: 'absolute',
429
476
  left: 0,
430
477
  bottom: 0,
@@ -38,7 +38,15 @@ export default {
38
38
  /**
39
39
  * Use to visually mark an input as valid or invalid.
40
40
  */
41
- validation: PropTypes.oneOf(['error', 'success'])
41
+ validation: PropTypes.oneOf(['error', 'success']),
42
+ /**
43
+ * The text value from an input field.
44
+ */
45
+ inputValue: PropTypes.string,
46
+ /**
47
+ * Max number of characters allowed id an input text.
48
+ */
49
+ maxCharacterAllowed: PropTypes.number
42
50
  },
43
51
  select: ({
44
52
  copy,
@@ -49,7 +57,9 @@ export default {
49
57
  feedbackTokens,
50
58
  feedbackProps,
51
59
  tooltip,
52
- validation
60
+ validation,
61
+ inputValue,
62
+ maxCharacterAllowed
53
63
  }) => ({
54
64
  supportsProps: {
55
65
  copy,
@@ -60,7 +70,9 @@ export default {
60
70
  feedbackTokens,
61
71
  feedbackProps,
62
72
  tooltip,
63
- validation
73
+ validation,
74
+ inputValue,
75
+ maxCharacterAllowed
64
76
  }
65
77
  })
66
78
  }