@telus-uds/components-base 3.30.0 → 3.32.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.
- package/CHANGELOG.md +31 -1
- package/lib/cjs/Autocomplete/Autocomplete.js +13 -1
- package/lib/cjs/Autocomplete/constants.js +1 -1
- package/lib/cjs/Button/ButtonGroup.js +91 -23
- package/lib/cjs/Card/CardBase.js +75 -47
- package/lib/cjs/ColourToggle/ColourBubble.js +65 -12
- package/lib/cjs/ColourToggle/ColourToggle.js +40 -11
- package/lib/cjs/ColourToggle/constants.js +15 -0
- package/lib/cjs/ColourToggle/dictionary.js +15 -0
- package/lib/cjs/Listbox/ListboxOverlay.js +7 -1
- package/lib/cjs/MultiSelectFilter/ModalOverlay.js +7 -1
- package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +2 -28
- package/lib/cjs/TextInput/TextInputBase.js +2 -1
- package/lib/cjs/Validator/Validator.js +5 -1
- package/lib/cjs/utils/useOverlaidPosition.js +83 -42
- package/lib/esm/Autocomplete/Autocomplete.js +13 -1
- package/lib/esm/Autocomplete/constants.js +1 -1
- package/lib/esm/Button/ButtonGroup.js +91 -23
- package/lib/esm/Card/CardBase.js +75 -47
- package/lib/esm/ColourToggle/ColourBubble.js +66 -13
- package/lib/esm/ColourToggle/ColourToggle.js +41 -12
- package/lib/esm/ColourToggle/constants.js +8 -0
- package/lib/esm/ColourToggle/dictionary.js +9 -0
- package/lib/esm/Listbox/ListboxOverlay.js +7 -1
- package/lib/esm/MultiSelectFilter/ModalOverlay.js +8 -2
- package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +2 -28
- package/lib/esm/TextInput/TextInputBase.js +2 -1
- package/lib/esm/Validator/Validator.js +5 -1
- package/lib/esm/utils/useOverlaidPosition.js +83 -42
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/src/Autocomplete/Autocomplete.jsx +11 -2
- package/src/Autocomplete/constants.js +1 -1
- package/src/Button/ButtonGroup.jsx +93 -24
- package/src/Card/CardBase.jsx +94 -75
- package/src/ColourToggle/ColourBubble.jsx +62 -7
- package/src/ColourToggle/ColourToggle.jsx +50 -10
- package/src/ColourToggle/constants.js +10 -0
- package/src/ColourToggle/dictionary.js +6 -0
- package/src/Listbox/ListboxOverlay.jsx +6 -2
- package/src/MultiSelectFilter/ModalOverlay.jsx +9 -3
- package/src/MultiSelectFilter/MultiSelectFilter.jsx +1 -31
- package/src/TextInput/TextInputBase.jsx +2 -1
- package/src/Validator/Validator.jsx +5 -1
- package/src/utils/useOverlaidPosition.js +84 -34
package/src/Card/CardBase.jsx
CHANGED
|
@@ -14,9 +14,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
|
|
|
14
14
|
|
|
15
15
|
const GRID_COLUMNS = 12
|
|
16
16
|
|
|
17
|
-
const isOverlayColor = (color) =>
|
|
18
|
-
return color && typeof color === 'string' && color.startsWith('rgba(')
|
|
19
|
-
}
|
|
17
|
+
const isOverlayColor = (color) => color && typeof color === 'string' && color.startsWith('rgba(')
|
|
20
18
|
|
|
21
19
|
const setBackgroundImage = ({
|
|
22
20
|
src,
|
|
@@ -25,7 +23,8 @@ const setBackgroundImage = ({
|
|
|
25
23
|
backgroundImagePosition,
|
|
26
24
|
backgroundImageAlign,
|
|
27
25
|
content,
|
|
28
|
-
cardStyle
|
|
26
|
+
cardStyle,
|
|
27
|
+
testID
|
|
29
28
|
}) => {
|
|
30
29
|
const borderRadius = cardStyle?.borderRadius || 0
|
|
31
30
|
const borderWidth = cardStyle?.borderWidth || 0
|
|
@@ -91,6 +90,7 @@ const setBackgroundImage = ({
|
|
|
91
90
|
style={[staticStyles.imageBackground, backgroundImageStyle]}
|
|
92
91
|
role="img"
|
|
93
92
|
aria-label={alt}
|
|
93
|
+
testID={testID}
|
|
94
94
|
>
|
|
95
95
|
{content}
|
|
96
96
|
</View>
|
|
@@ -108,6 +108,7 @@ const setBackgroundImage = ({
|
|
|
108
108
|
accessible={true}
|
|
109
109
|
accessibilityLabel={alt}
|
|
110
110
|
accessibilityIgnoresInvertColors={true}
|
|
111
|
+
testID={testID}
|
|
111
112
|
/>
|
|
112
113
|
<View style={staticStyles.contentOverlay}>{content}</View>
|
|
113
114
|
</View>
|
|
@@ -122,33 +123,18 @@ const setBackgroundImage = ({
|
|
|
122
123
|
style={staticStyles.imageBackground}
|
|
123
124
|
accessible={true}
|
|
124
125
|
accessibilityLabel={alt}
|
|
126
|
+
testID={testID}
|
|
125
127
|
>
|
|
126
128
|
{content}
|
|
127
129
|
</ImageBackground>
|
|
128
130
|
)
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
const selectPaddedContentStyles = ({
|
|
133
|
+
const selectPaddedContentStyles = ({ paddingTop, paddingBottom, paddingLeft, paddingRight }) => ({
|
|
132
134
|
paddingTop,
|
|
133
135
|
paddingBottom,
|
|
134
136
|
paddingLeft,
|
|
135
|
-
paddingRight
|
|
136
|
-
borderWidth,
|
|
137
|
-
borderColor,
|
|
138
|
-
borderRadius,
|
|
139
|
-
hasInteractiveBorder
|
|
140
|
-
}) => ({
|
|
141
|
-
paddingTop,
|
|
142
|
-
paddingBottom,
|
|
143
|
-
paddingLeft,
|
|
144
|
-
paddingRight,
|
|
145
|
-
...(hasInteractiveBorder
|
|
146
|
-
? {
|
|
147
|
-
borderWidth,
|
|
148
|
-
borderColor,
|
|
149
|
-
borderRadius
|
|
150
|
-
}
|
|
151
|
-
: {})
|
|
137
|
+
paddingRight
|
|
152
138
|
})
|
|
153
139
|
|
|
154
140
|
const selectInteractiveOverlayStyles = ({ backgroundColor, borderRadius, borderWidth }) => {
|
|
@@ -162,32 +148,49 @@ const selectInteractiveOverlayStyles = ({ backgroundColor, borderRadius, borderW
|
|
|
162
148
|
backgroundColor,
|
|
163
149
|
borderRadius: adjustedBorderRadius,
|
|
164
150
|
pointerEvents: 'none',
|
|
165
|
-
zIndex:
|
|
151
|
+
zIndex: 3
|
|
166
152
|
}
|
|
167
153
|
}
|
|
168
154
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
flex,
|
|
155
|
+
const selectOuterContainerStyles = ({
|
|
156
|
+
containerStyle,
|
|
172
157
|
backgroundColor,
|
|
173
|
-
borderColor,
|
|
174
158
|
borderRadius,
|
|
159
|
+
borderColor,
|
|
175
160
|
borderWidth,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
161
|
+
hasGradientToken
|
|
162
|
+
}) => ({
|
|
163
|
+
...containerStyle,
|
|
164
|
+
backgroundColor,
|
|
165
|
+
borderRadius,
|
|
166
|
+
...(hasGradientToken ? {} : { borderColor, borderWidth })
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// Ensure explicit selection of tokens
|
|
170
|
+
export const selectStyles = (
|
|
171
|
+
{
|
|
172
|
+
flex,
|
|
173
|
+
backgroundColor,
|
|
174
|
+
borderColor,
|
|
175
|
+
borderRadius,
|
|
176
|
+
borderWidth,
|
|
177
|
+
paddingBottom,
|
|
178
|
+
paddingLeft,
|
|
179
|
+
paddingRight,
|
|
180
|
+
paddingTop,
|
|
181
|
+
marginTop,
|
|
182
|
+
marginBottom,
|
|
183
|
+
marginLeft,
|
|
184
|
+
marginRight,
|
|
185
|
+
minWidth,
|
|
186
|
+
shadow,
|
|
187
|
+
backgroundGradient,
|
|
188
|
+
gradient,
|
|
189
|
+
maxHeight,
|
|
190
|
+
overflowY
|
|
191
|
+
},
|
|
192
|
+
{ hasBgImage = false } = {}
|
|
193
|
+
) => {
|
|
191
194
|
const hasGradient = (gradient || backgroundGradient) && Platform.OS === 'web'
|
|
192
195
|
|
|
193
196
|
let backgroundImageValue = null
|
|
@@ -226,7 +229,9 @@ export const selectStyles = ({
|
|
|
226
229
|
? {
|
|
227
230
|
backgroundImage: backgroundImageValue,
|
|
228
231
|
backgroundOrigin: `border-box`,
|
|
229
|
-
|
|
232
|
+
// bgImage child fills the content-box and replaces what the inset shadow
|
|
233
|
+
// used to cover, so we omit it to avoid hiding the image.
|
|
234
|
+
...(hasBgImage ? {} : { boxShadow: `inset 0 1000px ${boxShadowColor}` }),
|
|
230
235
|
border: `${borderWidth}px solid transparent`
|
|
231
236
|
}
|
|
232
237
|
: {}),
|
|
@@ -244,14 +249,13 @@ export const selectStyles = ({
|
|
|
244
249
|
* intended to be used in apps or sites directly: build themed components on top of this.
|
|
245
250
|
*/
|
|
246
251
|
const CardBase = React.forwardRef(
|
|
247
|
-
(
|
|
252
|
+
(
|
|
253
|
+
{ children, tokens, dataSet, backgroundImage, fullBleedContent, cardState, testID, ...rest },
|
|
254
|
+
ref
|
|
255
|
+
) => {
|
|
248
256
|
const resolvedTokens = typeof tokens === 'function' ? tokens(cardState) : tokens
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
? { ...resolvedTokens, gradient: undefined, backgroundGradient: undefined }
|
|
252
|
-
: resolvedTokens
|
|
253
|
-
|
|
254
|
-
const cardStyle = selectStyles(tokensToUse)
|
|
257
|
+
const hasBgImage = Boolean(backgroundImage && backgroundImage.src)
|
|
258
|
+
const cardStyle = selectStyles(resolvedTokens, { hasBgImage })
|
|
255
259
|
const props = selectProps(rest)
|
|
256
260
|
|
|
257
261
|
let content = children
|
|
@@ -283,31 +287,28 @@ const CardBase = React.forwardRef(
|
|
|
283
287
|
} = cardStyle
|
|
284
288
|
|
|
285
289
|
const hasPadding = paddingTop || paddingBottom || paddingLeft || paddingRight
|
|
286
|
-
const hasInteractiveBorder = borderWidth && borderWidth > 0
|
|
287
290
|
const hasInteractiveOverlay = isOverlayColor(backgroundColor)
|
|
291
|
+
const outerBackgroundColor = hasInteractiveOverlay ? undefined : backgroundColor
|
|
292
|
+
const hasGradientToken = Boolean(resolvedTokens.gradient) && Platform.OS === 'web'
|
|
288
293
|
|
|
289
|
-
const paddedContent =
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
{children}
|
|
304
|
-
</View>
|
|
305
|
-
) : (
|
|
306
|
-
children
|
|
307
|
-
)
|
|
294
|
+
const paddedContent = hasPadding ? (
|
|
295
|
+
<View
|
|
296
|
+
style={selectPaddedContentStyles({
|
|
297
|
+
paddingTop,
|
|
298
|
+
paddingBottom,
|
|
299
|
+
paddingLeft,
|
|
300
|
+
paddingRight
|
|
301
|
+
})}
|
|
302
|
+
>
|
|
303
|
+
{children}
|
|
304
|
+
</View>
|
|
305
|
+
) : (
|
|
306
|
+
children
|
|
307
|
+
)
|
|
308
308
|
|
|
309
309
|
const contentWithOverlay = (
|
|
310
310
|
<>
|
|
311
|
+
<View style={staticStyles.contentOverlay}>{paddedContent}</View>
|
|
311
312
|
{hasInteractiveOverlay && Platform.OS === 'web' && (
|
|
312
313
|
<View
|
|
313
314
|
style={selectInteractiveOverlayStyles({
|
|
@@ -315,9 +316,9 @@ const CardBase = React.forwardRef(
|
|
|
315
316
|
borderRadius,
|
|
316
317
|
borderWidth
|
|
317
318
|
})}
|
|
319
|
+
testID={testID && `${testID}-card-base-bg-overlay`}
|
|
318
320
|
/>
|
|
319
321
|
)}
|
|
320
|
-
<View style={staticStyles.contentOverlay}>{paddedContent}</View>
|
|
321
322
|
</>
|
|
322
323
|
)
|
|
323
324
|
|
|
@@ -328,11 +329,25 @@ const CardBase = React.forwardRef(
|
|
|
328
329
|
backgroundImagePosition,
|
|
329
330
|
backgroundImageAlign,
|
|
330
331
|
content: contentWithOverlay,
|
|
331
|
-
cardStyle: {
|
|
332
|
+
cardStyle: { borderRadius, borderWidth },
|
|
333
|
+
testID: testID && `${testID}-card-base-bg-image`
|
|
332
334
|
})
|
|
333
335
|
|
|
334
336
|
return (
|
|
335
|
-
<View
|
|
337
|
+
<View
|
|
338
|
+
style={selectOuterContainerStyles({
|
|
339
|
+
containerStyle,
|
|
340
|
+
backgroundColor: outerBackgroundColor,
|
|
341
|
+
borderRadius,
|
|
342
|
+
borderColor,
|
|
343
|
+
borderWidth,
|
|
344
|
+
hasGradientToken
|
|
345
|
+
})}
|
|
346
|
+
dataSet={dataSet}
|
|
347
|
+
ref={ref}
|
|
348
|
+
testID={testID}
|
|
349
|
+
{...props}
|
|
350
|
+
>
|
|
336
351
|
{content}
|
|
337
352
|
</View>
|
|
338
353
|
)
|
|
@@ -365,7 +380,7 @@ const CardBase = React.forwardRef(
|
|
|
365
380
|
)
|
|
366
381
|
|
|
367
382
|
return (
|
|
368
|
-
<View style={containerStyle} dataSet={dataSet} ref={ref} {...props}>
|
|
383
|
+
<View style={containerStyle} dataSet={dataSet} ref={ref} testID={testID} {...props}>
|
|
369
384
|
<FlexGrid>
|
|
370
385
|
<FlexGridRow>
|
|
371
386
|
{imageFirst ? (
|
|
@@ -386,7 +401,7 @@ const CardBase = React.forwardRef(
|
|
|
386
401
|
}
|
|
387
402
|
|
|
388
403
|
return (
|
|
389
|
-
<View style={cardStyle} dataSet={dataSet} ref={ref} {...props}>
|
|
404
|
+
<View style={cardStyle} dataSet={dataSet} ref={ref} testID={testID} {...props}>
|
|
390
405
|
{content}
|
|
391
406
|
</View>
|
|
392
407
|
)
|
|
@@ -434,6 +449,10 @@ CardBase.propTypes = {
|
|
|
434
449
|
...selectedSystemPropTypes,
|
|
435
450
|
children: PropTypes.node,
|
|
436
451
|
tokens: getTokensPropType('Card'),
|
|
452
|
+
/**
|
|
453
|
+
* Identifier for testing purposes.
|
|
454
|
+
*/
|
|
455
|
+
testID: PropTypes.string,
|
|
437
456
|
/**
|
|
438
457
|
* Apply background image to the card.
|
|
439
458
|
*/
|
|
@@ -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'
|
|
7
7
|
import { getTokensPropType } from '../utils'
|
|
8
8
|
import Tooltip from '../Tooltip'
|
|
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
|
const ColourBubble = React.forwardRef(
|
|
56
|
-
(
|
|
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) =>
|
|
87
|
+
const resolveColourBubbleTokens = (pressState) =>
|
|
88
|
+
resolvePressableTokens(tokens, isDisabled ? {} : pressState, {})
|
|
60
89
|
|
|
61
|
-
const themeTokens =
|
|
90
|
+
const themeTokens = tokens()
|
|
62
91
|
|
|
63
92
|
const pressable = (
|
|
64
93
|
<Pressable
|
|
@@ -67,14 +96,23 @@ 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,7 +159,24 @@ 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
|
}
|
|
126
172
|
|
|
173
|
+
const staticStyles = StyleSheet.create({
|
|
174
|
+
indicator: {
|
|
175
|
+
position: 'absolute',
|
|
176
|
+
top: '50%',
|
|
177
|
+
height: 2,
|
|
178
|
+
transform: [{ rotate: '-45deg' }]
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
|
|
127
182
|
export default ColourBubble
|
|
@@ -3,32 +3,57 @@ import { View } from 'react-native'
|
|
|
3
3
|
import PropTypes from 'prop-types'
|
|
4
4
|
|
|
5
5
|
import { useThemeTokensCallback } from '../ThemeProvider'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
a11yProps,
|
|
8
|
+
getTokensPropType,
|
|
9
|
+
selectSystemProps,
|
|
10
|
+
useCopy,
|
|
11
|
+
variantProp,
|
|
12
|
+
viewProps
|
|
13
|
+
} from '../utils'
|
|
7
14
|
import { StackWrap } from '../StackView'
|
|
8
15
|
import Typography from '../Typography'
|
|
9
16
|
import ColourBubble from './ColourBubble'
|
|
17
|
+
import { UNAVAILABLE_VARIANT, DICTIONARY_CONTENT_SHAPE } from './constants'
|
|
18
|
+
import DEFAULT_COLOUR_TOGGLE_DICTIONARY from './dictionary'
|
|
10
19
|
|
|
11
20
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
|
|
12
21
|
|
|
13
22
|
const ColourToggle = React.forwardRef(
|
|
14
|
-
(
|
|
23
|
+
(
|
|
24
|
+
{
|
|
25
|
+
tokens,
|
|
26
|
+
variant,
|
|
27
|
+
defaultColourId,
|
|
28
|
+
items,
|
|
29
|
+
onChange,
|
|
30
|
+
showTooltips,
|
|
31
|
+
copy = 'en',
|
|
32
|
+
dictionary = DEFAULT_COLOUR_TOGGLE_DICTIONARY,
|
|
33
|
+
...rest
|
|
34
|
+
},
|
|
35
|
+
ref
|
|
36
|
+
) => {
|
|
15
37
|
const [currentColourId, setCurrentColourId] = React.useState(defaultColourId)
|
|
16
38
|
const getTokens = useThemeTokensCallback('ColourToggle', tokens, variant)
|
|
17
|
-
|
|
39
|
+
const getCopy = useCopy({ dictionary, copy })
|
|
18
40
|
const { space } = getTokens()
|
|
19
|
-
const {
|
|
20
|
-
items.find((item) => item.id === currentColourId) || ''
|
|
41
|
+
const currentItem = items.find(({ id }) => id === currentColourId)
|
|
21
42
|
|
|
22
43
|
return (
|
|
23
44
|
<View ref={ref} {...selectProps(rest)}>
|
|
24
|
-
<Typography>
|
|
45
|
+
<Typography>
|
|
46
|
+
{currentItem?.unavailable
|
|
47
|
+
? `${currentItem.colourName} ${getCopy('unavailable')}`
|
|
48
|
+
: currentItem?.colourName ?? ''}
|
|
49
|
+
</Typography>
|
|
25
50
|
<StackWrap space={space} accessibilityRole="radiogroup">
|
|
26
|
-
{items.map(({ id, colourHexCode, colourName }, index) => {
|
|
51
|
+
{items.map(({ id, colourHexCode, colourName, unavailable, disabled }, index) => {
|
|
27
52
|
const colourBubbleId = id || `ColourBubble[${index}]`
|
|
28
53
|
|
|
29
54
|
const handleChangeColour = (event) => {
|
|
30
55
|
setCurrentColourId(id)
|
|
31
|
-
onChange?.(event, { id, colourHexCode, colourName })
|
|
56
|
+
onChange?.(event, { id, colourHexCode, colourName, unavailable })
|
|
32
57
|
}
|
|
33
58
|
|
|
34
59
|
return (
|
|
@@ -41,6 +66,8 @@ const ColourToggle = React.forwardRef(
|
|
|
41
66
|
colourName={colourName}
|
|
42
67
|
onPress={handleChangeColour}
|
|
43
68
|
showTooltip={showTooltips}
|
|
69
|
+
unavailable={unavailable}
|
|
70
|
+
isDisabled={!!disabled}
|
|
44
71
|
/>
|
|
45
72
|
)
|
|
46
73
|
})}
|
|
@@ -72,7 +99,9 @@ ColourToggle.propTypes = {
|
|
|
72
99
|
PropTypes.exact({
|
|
73
100
|
colourHexCode: PropTypes.string,
|
|
74
101
|
colourName: PropTypes.string,
|
|
75
|
-
id: PropTypes.string
|
|
102
|
+
id: PropTypes.string,
|
|
103
|
+
unavailable: PropTypes.oneOf(Object.values(UNAVAILABLE_VARIANT)),
|
|
104
|
+
disabled: PropTypes.bool
|
|
76
105
|
})
|
|
77
106
|
),
|
|
78
107
|
/**
|
|
@@ -82,7 +111,18 @@ ColourToggle.propTypes = {
|
|
|
82
111
|
/**
|
|
83
112
|
* When true, displays each colour's name as a tooltip on hover (web only).
|
|
84
113
|
*/
|
|
85
|
-
showTooltips: PropTypes.bool
|
|
114
|
+
showTooltips: PropTypes.bool,
|
|
115
|
+
/**
|
|
116
|
+
* Select English or French copy.
|
|
117
|
+
*/
|
|
118
|
+
copy: PropTypes.oneOf(['en', 'fr']),
|
|
119
|
+
/**
|
|
120
|
+
* Override default labels.
|
|
121
|
+
*/
|
|
122
|
+
dictionary: PropTypes.shape({
|
|
123
|
+
en: DICTIONARY_CONTENT_SHAPE,
|
|
124
|
+
fr: DICTIONARY_CONTENT_SHAPE
|
|
125
|
+
})
|
|
86
126
|
}
|
|
87
127
|
|
|
88
128
|
export default ColourToggle
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import { View, StyleSheet, Platform } from 'react-native'
|
|
3
|
+
import { View, StyleSheet, Platform, ScrollView } from 'react-native'
|
|
4
4
|
import Portal from '../Portal'
|
|
5
5
|
import { useThemeTokens } from '../ThemeProvider'
|
|
6
6
|
import Card from '../Card'
|
|
@@ -69,7 +69,11 @@ const DropdownOverlay = React.forwardRef(
|
|
|
69
69
|
paddingRight: paddingHorizontal
|
|
70
70
|
}}
|
|
71
71
|
>
|
|
72
|
-
{
|
|
72
|
+
{Platform.OS !== 'web' ? (
|
|
73
|
+
<ScrollView style={{ maxHeight }}>{children}</ScrollView>
|
|
74
|
+
) : (
|
|
75
|
+
children
|
|
76
|
+
)}
|
|
73
77
|
</Card>
|
|
74
78
|
</View>
|
|
75
79
|
)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
3
|
import { View, StyleSheet, Platform } from 'react-native'
|
|
4
|
-
import { Portal } from '@gorhom/portal'
|
|
4
|
+
import { Portal as GorhomPortal } from '@gorhom/portal'
|
|
5
|
+
import LocalPortal from '../Portal/Portal'
|
|
5
6
|
import { useCopy, copyPropTypes, getTokensPropType, variantProp } from '../utils'
|
|
6
7
|
import { useViewport } from '../ViewportProvider'
|
|
7
8
|
import { useThemeTokens } from '../ThemeProvider'
|
|
@@ -105,8 +106,13 @@ const ModalOverlay = React.forwardRef(
|
|
|
105
106
|
const getCopy = useCopy({ dictionary, copy })
|
|
106
107
|
const closeLabel = getCopy('closeButton')
|
|
107
108
|
|
|
109
|
+
// On web, use the local Portal (always appends to document.body) to ensure correct
|
|
110
|
+
// absolute positioning in MFE/iframe contexts. @gorhom/portal may render its host
|
|
111
|
+
// outside the iframe's document, causing coordinate mismatches when scrolled.
|
|
112
|
+
const PortalComponent = Platform.OS === 'web' ? LocalPortal : GorhomPortal
|
|
113
|
+
|
|
108
114
|
return (
|
|
109
|
-
<
|
|
115
|
+
<PortalComponent>
|
|
110
116
|
<View
|
|
111
117
|
ref={containerRef}
|
|
112
118
|
onLayout={onLayout}
|
|
@@ -135,7 +141,7 @@ const ModalOverlay = React.forwardRef(
|
|
|
135
141
|
{children}
|
|
136
142
|
</Card>
|
|
137
143
|
</View>
|
|
138
|
-
</
|
|
144
|
+
</PortalComponent>
|
|
139
145
|
)
|
|
140
146
|
}
|
|
141
147
|
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import { Portal } from '@gorhom/portal'
|
|
4
3
|
import { View, StyleSheet, Dimensions, SafeAreaView, Platform, ScrollView } from 'react-native'
|
|
5
4
|
import { useThemeTokens, useThemeTokensCallback, applyTextStyles } from '../ThemeProvider'
|
|
6
5
|
import {
|
|
@@ -221,27 +220,11 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
221
220
|
onCancel()
|
|
222
221
|
}
|
|
223
222
|
|
|
224
|
-
const appRootRef = React.useRef(null)
|
|
225
|
-
const [rootOffsets, setRootOffsets] = React.useState(null)
|
|
226
|
-
|
|
227
|
-
React.useEffect(() => {
|
|
228
|
-
if (rootOffsets) return
|
|
229
|
-
appRootRef.current?.measureInWindow((x, y) => {
|
|
230
|
-
// Only set offsets if they are positive
|
|
231
|
-
// this is because we want to avoid negative offsets that could cause
|
|
232
|
-
// the dropdown to be positioned incorrectly in some situations
|
|
233
|
-
if (y > 0) setRootOffsets({ horizontal: x, vertical: y })
|
|
234
|
-
})
|
|
235
|
-
}, [isOpen, rootOffsets])
|
|
236
|
-
|
|
237
223
|
const { align, offsets } = useResponsiveProp({
|
|
238
224
|
xs: { align: { top: 'top', left: 'left' } },
|
|
239
225
|
sm: {
|
|
240
226
|
align: { top: 'bottom', left: 'left' },
|
|
241
|
-
offsets: {
|
|
242
|
-
vertical: 4 - (rootOffsets?.vertical || 0),
|
|
243
|
-
horizontal: -rootOffsets?.horizontal || 0
|
|
244
|
-
}
|
|
227
|
+
offsets: { vertical: 4 }
|
|
245
228
|
}
|
|
246
229
|
})
|
|
247
230
|
|
|
@@ -367,14 +350,6 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
367
350
|
|
|
368
351
|
return (
|
|
369
352
|
<>
|
|
370
|
-
{/*
|
|
371
|
-
This View is rendered inside a Portal to determine the application's root position.
|
|
372
|
-
Capturing it is crucial to determine offsets to position the Modal correctly, preventing
|
|
373
|
-
misalignment that can occur when the Portal does not render the Modal at the body level.
|
|
374
|
-
*/}
|
|
375
|
-
<Portal>
|
|
376
|
-
<View ref={appRootRef} style={styles.appRootRef} />
|
|
377
|
-
</Portal>
|
|
378
353
|
<ButtonDropdown
|
|
379
354
|
ref={sourceRef}
|
|
380
355
|
key={id}
|
|
@@ -464,11 +439,6 @@ const styles = StyleSheet.create({
|
|
|
464
439
|
},
|
|
465
440
|
scrollContainer: {
|
|
466
441
|
padding: 1
|
|
467
|
-
},
|
|
468
|
-
appRootRef: {
|
|
469
|
-
position: 'absolute',
|
|
470
|
-
top: 0,
|
|
471
|
-
left: 0
|
|
472
442
|
}
|
|
473
443
|
})
|
|
474
444
|
|
|
@@ -256,7 +256,8 @@ const TextInputBase = React.forwardRef(
|
|
|
256
256
|
|
|
257
257
|
const handleChangeText = (event) => {
|
|
258
258
|
const text = event.nativeEvent?.text ?? event.target?.value
|
|
259
|
-
|
|
259
|
+
// Do NOT use `|| undefined`: empty string is falsy and would break the controlled contract
|
|
260
|
+
let filteredText = isNumeric ? text?.replace(/[^\d]/g, '') ?? '' : text
|
|
260
261
|
if (type === 'card' && filteredText) {
|
|
261
262
|
const formattedValue = filteredText.replace(/[^a-zA-Z0-9]/g, '')
|
|
262
263
|
const regex = new RegExp(`([a-zA-Z0-9]{4})(?=[a-zA-Z0-9])`, 'g')
|
|
@@ -204,7 +204,11 @@ const Validator = React.forwardRef(
|
|
|
204
204
|
|
|
205
205
|
// Sync external value prop to internal state
|
|
206
206
|
React.useEffect(() => {
|
|
207
|
-
if (value
|
|
207
|
+
if (value === '') {
|
|
208
|
+
setCodes(Array(validatorsLength).fill(''))
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
if (Number(value).toString() !== 'NaN') {
|
|
208
212
|
const digits = value.split('').slice(0, validatorsLength)
|
|
209
213
|
const newCodes = Array(validatorsLength).fill('')
|
|
210
214
|
digits.forEach((digit, i) => {
|