@telus-uds/components-base 4.0.0-beta.0 → 4.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -19,9 +19,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
19
19
 
20
20
  const GRID_COLUMNS = 12
21
21
 
22
- const isOverlayColor = (color) => {
23
- return color && typeof color === 'string' && color.startsWith('rgba(')
24
- }
22
+ const isOverlayColor = (color) => color && typeof color === 'string' && color.startsWith('rgba(')
25
23
 
26
24
  const setBackgroundImage = ({
27
25
  src,
@@ -30,7 +28,8 @@ const setBackgroundImage = ({
30
28
  backgroundImagePosition,
31
29
  backgroundImageAlign,
32
30
  content,
33
- cardStyle
31
+ cardStyle,
32
+ testID
34
33
  }) => {
35
34
  const borderRadius = cardStyle?.borderRadius || 0
36
35
  const borderWidth = cardStyle?.borderWidth || 0
@@ -96,6 +95,7 @@ const setBackgroundImage = ({
96
95
  style={[staticStyles.imageBackground, backgroundImageStyle]}
97
96
  role="img"
98
97
  aria-label={alt}
98
+ testID={testID}
99
99
  >
100
100
  {content}
101
101
  </View>
@@ -113,6 +113,7 @@ const setBackgroundImage = ({
113
113
  accessible={true}
114
114
  accessibilityLabel={alt}
115
115
  accessibilityIgnoresInvertColors={true}
116
+ testID={testID}
116
117
  />
117
118
  <View style={staticStyles.contentOverlay}>{content}</View>
118
119
  </View>
@@ -127,33 +128,18 @@ const setBackgroundImage = ({
127
128
  style={staticStyles.imageBackground}
128
129
  accessible={true}
129
130
  accessibilityLabel={alt}
131
+ testID={testID}
130
132
  >
131
133
  {content}
132
134
  </ImageBackground>
133
135
  )
134
136
  }
135
137
 
136
- const selectPaddedContentStyles = ({
138
+ const selectPaddedContentStyles = ({ paddingTop, paddingBottom, paddingLeft, paddingRight }) => ({
137
139
  paddingTop,
138
140
  paddingBottom,
139
141
  paddingLeft,
140
- paddingRight,
141
- borderWidth,
142
- borderColor,
143
- borderRadius,
144
- hasInteractiveBorder
145
- }) => ({
146
- paddingTop,
147
- paddingBottom,
148
- paddingLeft,
149
- paddingRight,
150
- ...(hasInteractiveBorder
151
- ? {
152
- borderWidth,
153
- borderColor,
154
- borderRadius
155
- }
156
- : {})
142
+ paddingRight
157
143
  })
158
144
 
159
145
  const selectInteractiveOverlayStyles = ({ backgroundColor, borderRadius, borderWidth }) => {
@@ -167,32 +153,49 @@ const selectInteractiveOverlayStyles = ({ backgroundColor, borderRadius, borderW
167
153
  backgroundColor,
168
154
  borderRadius: adjustedBorderRadius,
169
155
  pointerEvents: 'none',
170
- zIndex: 1
156
+ zIndex: 3
171
157
  }
172
158
  }
173
159
 
174
- // Ensure explicit selection of tokens
175
- export const selectStyles = ({
176
- flex,
160
+ const selectOuterContainerStyles = ({
161
+ containerStyle,
177
162
  backgroundColor,
178
- borderColor,
179
163
  borderRadius,
164
+ borderColor,
180
165
  borderWidth,
181
- paddingBottom,
182
- paddingLeft,
183
- paddingRight,
184
- paddingTop,
185
- marginTop,
186
- marginBottom,
187
- marginLeft,
188
- marginRight,
189
- minWidth,
190
- shadow,
191
- backgroundGradient,
192
- gradient,
193
- maxHeight,
194
- overflowY
195
- }) => {
166
+ hasGradientToken
167
+ }) => ({
168
+ ...containerStyle,
169
+ backgroundColor,
170
+ borderRadius,
171
+ ...(hasGradientToken ? {} : { borderColor, borderWidth })
172
+ })
173
+
174
+ // Ensure explicit selection of tokens
175
+ export const selectStyles = (
176
+ {
177
+ flex,
178
+ backgroundColor,
179
+ borderColor,
180
+ borderRadius,
181
+ borderWidth,
182
+ paddingBottom,
183
+ paddingLeft,
184
+ paddingRight,
185
+ paddingTop,
186
+ marginTop,
187
+ marginBottom,
188
+ marginLeft,
189
+ marginRight,
190
+ minWidth,
191
+ shadow,
192
+ backgroundGradient,
193
+ gradient,
194
+ maxHeight,
195
+ overflowY
196
+ },
197
+ { hasBgImage = false } = {}
198
+ ) => {
196
199
  const hasGradient = (gradient || backgroundGradient) && Platform.OS === 'web'
197
200
 
198
201
  let backgroundImageValue = null
@@ -231,7 +234,9 @@ export const selectStyles = ({
231
234
  ? {
232
235
  backgroundImage: backgroundImageValue,
233
236
  backgroundOrigin: `border-box`,
234
- boxShadow: `inset 0 1000px ${boxShadowColor}`,
237
+ // bgImage child fills the content-box and replaces what the inset shadow
238
+ // used to cover, so we omit it to avoid hiding the image.
239
+ ...(hasBgImage ? {} : { boxShadow: `inset 0 1000px ${boxShadowColor}` }),
235
240
  border: `${borderWidth}px solid transparent`
236
241
  }
237
242
  : {}),
@@ -249,14 +254,13 @@ export const selectStyles = ({
249
254
  * intended to be used in apps or sites directly: build themed components on top of this.
250
255
  */
251
256
  export const CardBase = React.forwardRef(
252
- ({ children, tokens, dataSet, backgroundImage, fullBleedContent, cardState, ...rest }, ref) => {
257
+ (
258
+ { children, tokens, dataSet, backgroundImage, fullBleedContent, cardState, testID, ...rest },
259
+ ref
260
+ ) => {
253
261
  const resolvedTokens = typeof tokens === 'function' ? tokens(cardState) : tokens
254
- const tokensToUse =
255
- backgroundImage && backgroundImage.src
256
- ? { ...resolvedTokens, gradient: undefined, backgroundGradient: undefined }
257
- : resolvedTokens
258
-
259
- const cardStyle = selectStyles(tokensToUse)
262
+ const hasBgImage = Boolean(backgroundImage && backgroundImage.src)
263
+ const cardStyle = selectStyles(resolvedTokens, { hasBgImage })
260
264
  const props = selectProps(rest)
261
265
 
262
266
  let content = children
@@ -288,31 +292,28 @@ export const CardBase = React.forwardRef(
288
292
  } = cardStyle
289
293
 
290
294
  const hasPadding = paddingTop || paddingBottom || paddingLeft || paddingRight
291
- const hasInteractiveBorder = borderWidth && borderWidth > 0
292
295
  const hasInteractiveOverlay = isOverlayColor(backgroundColor)
296
+ const outerBackgroundColor = hasInteractiveOverlay ? undefined : backgroundColor
297
+ const hasGradientToken = Boolean(resolvedTokens.gradient) && Platform.OS === 'web'
293
298
 
294
- const paddedContent =
295
- hasPadding || hasInteractiveBorder ? (
296
- <View
297
- style={selectPaddedContentStyles({
298
- paddingTop,
299
- paddingBottom,
300
- paddingLeft,
301
- paddingRight,
302
- borderWidth,
303
- borderColor,
304
- borderRadius,
305
- hasInteractiveBorder
306
- })}
307
- >
308
- {children}
309
- </View>
310
- ) : (
311
- children
312
- )
299
+ const paddedContent = hasPadding ? (
300
+ <View
301
+ style={selectPaddedContentStyles({
302
+ paddingTop,
303
+ paddingBottom,
304
+ paddingLeft,
305
+ paddingRight
306
+ })}
307
+ >
308
+ {children}
309
+ </View>
310
+ ) : (
311
+ children
312
+ )
313
313
 
314
314
  const contentWithOverlay = (
315
315
  <>
316
+ <View style={staticStyles.contentOverlay}>{paddedContent}</View>
316
317
  {hasInteractiveOverlay && Platform.OS === 'web' && (
317
318
  <View
318
319
  style={selectInteractiveOverlayStyles({
@@ -320,9 +321,9 @@ export const CardBase = React.forwardRef(
320
321
  borderRadius,
321
322
  borderWidth
322
323
  })}
324
+ testID={testID && `${testID}-card-base-bg-overlay`}
323
325
  />
324
326
  )}
325
- <View style={staticStyles.contentOverlay}>{paddedContent}</View>
326
327
  </>
327
328
  )
328
329
 
@@ -333,11 +334,25 @@ export const CardBase = React.forwardRef(
333
334
  backgroundImagePosition,
334
335
  backgroundImageAlign,
335
336
  content: contentWithOverlay,
336
- cardStyle: { ...containerStyle, borderRadius }
337
+ cardStyle: { borderRadius, borderWidth },
338
+ testID: testID && `${testID}-card-base-bg-image`
337
339
  })
338
340
 
339
341
  return (
340
- <View style={{ ...containerStyle, borderRadius }} dataSet={dataSet} ref={ref} {...props}>
342
+ <View
343
+ style={selectOuterContainerStyles({
344
+ containerStyle,
345
+ backgroundColor: outerBackgroundColor,
346
+ borderRadius,
347
+ borderColor,
348
+ borderWidth,
349
+ hasGradientToken
350
+ })}
351
+ dataSet={dataSet}
352
+ ref={ref}
353
+ testID={testID}
354
+ {...props}
355
+ >
341
356
  {content}
342
357
  </View>
343
358
  )
@@ -370,7 +385,7 @@ export const CardBase = React.forwardRef(
370
385
  )
371
386
 
372
387
  return (
373
- <View style={containerStyle} dataSet={dataSet} ref={ref} {...props}>
388
+ <View style={containerStyle} dataSet={dataSet} ref={ref} testID={testID} {...props}>
374
389
  <FlexGrid>
375
390
  <FlexGridRow>
376
391
  {imageFirst ? (
@@ -391,7 +406,7 @@ export const CardBase = React.forwardRef(
391
406
  }
392
407
 
393
408
  return (
394
- <View style={cardStyle} dataSet={dataSet} ref={ref} {...props}>
409
+ <View style={cardStyle} dataSet={dataSet} ref={ref} testID={testID} {...props}>
395
410
  {content}
396
411
  </View>
397
412
  )
@@ -439,6 +454,10 @@ CardBase.propTypes = {
439
454
  ...selectedSystemPropTypes,
440
455
  children: PropTypes.node,
441
456
  tokens: getTokensPropType('Card'),
457
+ /**
458
+ * Identifier for testing purposes.
459
+ */
460
+ testID: PropTypes.string,
442
461
  /**
443
462
  * Apply background image to the card.
444
463
  */
@@ -1,11 +1,12 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
- import { View, Pressable, Platform } from 'react-native'
4
+ import { View, Pressable, Platform, StyleSheet } from 'react-native'
5
5
  import { resolvePressableTokens } from '../utils/pressability'
6
6
  import { applyShadowToken } from '../ThemeProvider/utils/styles'
7
7
  import { getTokensPropType } from '../utils/props/tokens'
8
8
  import { Tooltip } from '../Tooltip/Tooltip.native'
9
+ import { UNAVAILABLE_VARIANT } from './constants'
9
10
 
10
11
  const selectGeneralBubbleTokens = ({
11
12
  outerBubbleHeight,
@@ -52,13 +53,41 @@ const selectBorderBubbleTokens = ({
52
53
  borderRadius: bubbleBorderRadius
53
54
  })
54
55
 
56
+ const selectOverlayStyles = ({ overlayColor, innerBubbleBorderRadius }) => ({
57
+ borderRadius: innerBubbleBorderRadius,
58
+ backgroundColor: overlayColor
59
+ })
60
+
61
+ const selectSlashStyles = (
62
+ { slashLightColor, slashDarkColor, slashWidth, slashOffset },
63
+ unavailable
64
+ ) => ({
65
+ width: slashWidth,
66
+ backgroundColor: unavailable === UNAVAILABLE_VARIANT.DARK ? slashDarkColor : slashLightColor,
67
+ marginLeft: slashOffset
68
+ })
69
+
55
70
  export const ColourBubble = React.forwardRef(
56
- ({ tokens = {}, id, colourHexCode, colourName, isSelected, onPress, showTooltip }, ref) => {
71
+ (
72
+ {
73
+ tokens = () => ({}),
74
+ id,
75
+ colourHexCode,
76
+ colourName,
77
+ isSelected,
78
+ onPress,
79
+ showTooltip,
80
+ unavailable,
81
+ isDisabled
82
+ },
83
+ ref
84
+ ) => {
57
85
  const defaultTokens = tokens({ selected: isSelected })
58
86
 
59
- const resolveColourBubbleTokens = (pressState) => resolvePressableTokens(tokens, pressState, {})
87
+ const resolveColourBubbleTokens = (pressState) =>
88
+ resolvePressableTokens(tokens, isDisabled ? {} : pressState, {})
60
89
 
61
- const themeTokens = React.useMemo(() => tokens(), [tokens])
90
+ const themeTokens = tokens()
62
91
 
63
92
  const pressable = (
64
93
  <Pressable
@@ -67,14 +96,23 @@ export const ColourBubble = React.forwardRef(
67
96
  isSelected && selectBorderBubbleTokens(defaultTokens)
68
97
  ]}
69
98
  onPress={onPress}
99
+ disabled={isDisabled}
100
+ focusable={!isDisabled}
70
101
  accessible
71
102
  accessibilityRole="radio"
72
103
  accessibilityLabel={colourName}
73
- accessibilityState={{ checked: isSelected }}
104
+ accessibilityState={{ checked: isSelected, disabled: isDisabled }}
74
105
  ref={ref}
75
106
  testID={id}
76
107
  >
77
- <View style={[selectInnerBubbleTokens(themeTokens), { backgroundColor: colourHexCode }]} />
108
+ <View style={[selectInnerBubbleTokens(themeTokens), { backgroundColor: colourHexCode }]}>
109
+ {unavailable && (
110
+ <View style={[StyleSheet.absoluteFillObject, selectOverlayStyles(themeTokens)]} />
111
+ )}
112
+ </View>
113
+ {unavailable && (
114
+ <View style={[staticStyles.indicator, selectSlashStyles(themeTokens, unavailable)]} />
115
+ )}
78
116
  </Pressable>
79
117
  )
80
118
 
@@ -121,5 +159,22 @@ ColourBubble.propTypes = {
121
159
  /**
122
160
  * When true, wraps the bubble in a Tooltip that displays the colourName on hover (web only).
123
161
  */
124
- showTooltip: PropTypes.bool
162
+ showTooltip: PropTypes.bool,
163
+ /**
164
+ * When set, renders a diagonal indicator over the bubble. Accepts either 'dark' or 'light' to determine the colour of the indicator.
165
+ */
166
+ unavailable: PropTypes.oneOf(Object.values(UNAVAILABLE_VARIANT)),
167
+ /**
168
+ * When true, disables interaction on the bubble.
169
+ */
170
+ isDisabled: PropTypes.bool
125
171
  }
172
+
173
+ const staticStyles = StyleSheet.create({
174
+ indicator: {
175
+ position: 'absolute',
176
+ top: '50%',
177
+ height: 2,
178
+ transform: [{ rotate: '-45deg' }]
179
+ }
180
+ })
@@ -8,31 +8,50 @@ import { getTokensPropType } from '../utils/props/tokens'
8
8
  import { selectSystemProps } from '../utils/props/selectSystemProps'
9
9
  import { variantProp } from '../utils/props/variantProp'
10
10
  import { viewProps } from '../utils/props/viewProps'
11
+ import { useCopy } from '../utils/useCopy'
11
12
  import { StackWrap } from '../StackView/StackWrap'
12
13
  import { Typography } from '../Typography/Typography'
13
14
  import { ColourBubble } from './ColourBubble'
15
+ import { UNAVAILABLE_VARIANT, DICTIONARY_CONTENT_SHAPE } from './constants'
16
+ import { DEFAULT_COLOUR_TOGGLE_DICTIONARY } from './dictionary'
14
17
 
15
18
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
16
19
 
17
20
  export const ColourToggle = React.forwardRef(
18
- ({ tokens, variant, defaultColourId, items, onChange, showTooltips, ...rest }, ref) => {
21
+ (
22
+ {
23
+ tokens,
24
+ variant,
25
+ defaultColourId,
26
+ items,
27
+ onChange,
28
+ showTooltips,
29
+ copy = 'en',
30
+ dictionary = DEFAULT_COLOUR_TOGGLE_DICTIONARY,
31
+ ...rest
32
+ },
33
+ ref
34
+ ) => {
19
35
  const [currentColourId, setCurrentColourId] = React.useState(defaultColourId)
20
36
  const getTokens = useThemeTokensCallback('ColourToggle', tokens, variant)
21
-
37
+ const getCopy = useCopy({ dictionary, copy })
22
38
  const { space } = getTokens()
23
- const { colourName: currentColourName = '' } =
24
- items.find((item) => item.id === currentColourId) || ''
39
+ const currentItem = items.find(({ id }) => id === currentColourId)
25
40
 
26
41
  return (
27
42
  <View ref={ref} {...selectProps(rest)}>
28
- <Typography>{currentColourName}</Typography>
43
+ <Typography>
44
+ {currentItem?.unavailable
45
+ ? `${currentItem.colourName} ${getCopy('unavailable')}`
46
+ : currentItem?.colourName ?? ''}
47
+ </Typography>
29
48
  <StackWrap space={space} accessibilityRole="radiogroup">
30
- {items.map(({ id, colourHexCode, colourName }, index) => {
49
+ {items.map(({ id, colourHexCode, colourName, unavailable, disabled }, index) => {
31
50
  const colourBubbleId = id || `ColourBubble[${index}]`
32
51
 
33
52
  const handleChangeColour = (event) => {
34
53
  setCurrentColourId(id)
35
- onChange?.(event, { id, colourHexCode, colourName })
54
+ onChange?.(event, { id, colourHexCode, colourName, unavailable })
36
55
  }
37
56
 
38
57
  return (
@@ -45,6 +64,8 @@ export const ColourToggle = React.forwardRef(
45
64
  colourName={colourName}
46
65
  onPress={handleChangeColour}
47
66
  showTooltip={showTooltips}
67
+ unavailable={unavailable}
68
+ isDisabled={!!disabled}
48
69
  />
49
70
  )
50
71
  })}
@@ -76,7 +97,9 @@ ColourToggle.propTypes = {
76
97
  PropTypes.exact({
77
98
  colourHexCode: PropTypes.string,
78
99
  colourName: PropTypes.string,
79
- id: PropTypes.string
100
+ id: PropTypes.string,
101
+ unavailable: PropTypes.oneOf(Object.values(UNAVAILABLE_VARIANT)),
102
+ disabled: PropTypes.bool
80
103
  })
81
104
  ),
82
105
  /**
@@ -86,5 +109,16 @@ ColourToggle.propTypes = {
86
109
  /**
87
110
  * When true, displays each colour's name as a tooltip on hover (web only).
88
111
  */
89
- showTooltips: PropTypes.bool
112
+ showTooltips: PropTypes.bool,
113
+ /**
114
+ * Select English or French copy.
115
+ */
116
+ copy: PropTypes.oneOf(['en', 'fr']),
117
+ /**
118
+ * Override default labels.
119
+ */
120
+ dictionary: PropTypes.shape({
121
+ en: DICTIONARY_CONTENT_SHAPE,
122
+ fr: DICTIONARY_CONTENT_SHAPE
123
+ })
90
124
  }
@@ -0,0 +1,10 @@
1
+ import PropTypes from 'prop-types'
2
+
3
+ export const UNAVAILABLE_VARIANT = {
4
+ DARK: 'dark',
5
+ LIGHT: 'light'
6
+ }
7
+
8
+ export const DICTIONARY_CONTENT_SHAPE = PropTypes.shape({
9
+ unavailable: PropTypes.string
10
+ })
@@ -0,0 +1,4 @@
1
+ export const DEFAULT_COLOUR_TOGGLE_DICTIONARY = {
2
+ en: { unavailable: '(Out of stock)' },
3
+ fr: { unavailable: '(En rupture de stock)' }
4
+ }
@@ -205,7 +205,11 @@ export const Validator = React.forwardRef(
205
205
 
206
206
  // Sync external value prop to internal state
207
207
  React.useEffect(() => {
208
- if (value && Number(value).toString() !== 'NaN') {
208
+ if (value === '') {
209
+ setCodes(Array(validatorsLength).fill(''))
210
+ return
211
+ }
212
+ if (Number(value).toString() !== 'NaN') {
209
213
  const digits = value.split('').slice(0, validatorsLength)
210
214
  const newCodes = Array(validatorsLength).fill('')
211
215
  digits.forEach((digit, i) => {