@telus-uds/components-base 3.21.0 → 3.23.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/Button/Button.js +12 -3
- package/lib/cjs/Button/ButtonBase.js +63 -10
- package/lib/cjs/Button/ButtonDropdown.js +2 -0
- package/lib/cjs/Button/ButtonGroup.js +45 -38
- package/lib/cjs/Button/propTypes.js +6 -0
- package/lib/cjs/Card/PressableCardBase.js +3 -1
- package/lib/cjs/Carousel/Carousel.js +63 -22
- package/lib/cjs/Carousel/CarouselItem/CarouselItem.js +23 -3
- package/lib/cjs/Icon/Icon.js +8 -11
- package/lib/cjs/Icon/IconText.js +0 -1
- package/lib/cjs/Listbox/GroupControl.js +33 -39
- package/lib/cjs/Listbox/Listbox.js +22 -13
- package/lib/cjs/Listbox/ListboxGroup.js +2 -1
- package/lib/cjs/Listbox/ListboxOverlay.js +5 -2
- package/lib/cjs/Listbox/PressableItem.js +8 -4
- package/lib/cjs/TextInput/TextInputBase.js +5 -1
- package/lib/cjs/ThemeProvider/index.js +9 -1
- package/lib/cjs/ThemeProvider/useResponsiveThemeTokensCallback.js +124 -0
- package/lib/cjs/Validator/Validator.js +171 -135
- package/lib/cjs/index.js +7 -0
- package/lib/esm/Button/Button.js +13 -4
- package/lib/esm/Button/ButtonBase.js +64 -11
- package/lib/esm/Button/ButtonDropdown.js +2 -0
- package/lib/esm/Button/ButtonGroup.js +44 -39
- package/lib/esm/Button/propTypes.js +6 -0
- package/lib/esm/Card/PressableCardBase.js +3 -1
- package/lib/esm/Carousel/Carousel.js +63 -22
- package/lib/esm/Carousel/CarouselItem/CarouselItem.js +23 -3
- package/lib/esm/Icon/Icon.js +8 -11
- package/lib/esm/Icon/IconText.js +0 -1
- package/lib/esm/Listbox/GroupControl.js +33 -39
- package/lib/esm/Listbox/Listbox.js +23 -14
- package/lib/esm/Listbox/ListboxGroup.js +2 -1
- package/lib/esm/Listbox/ListboxOverlay.js +5 -2
- package/lib/esm/Listbox/PressableItem.js +8 -4
- package/lib/esm/TextInput/TextInputBase.js +5 -1
- package/lib/esm/ThemeProvider/index.js +1 -0
- package/lib/esm/ThemeProvider/useResponsiveThemeTokensCallback.js +117 -0
- package/lib/esm/Validator/Validator.js +171 -135
- package/lib/esm/index.js +1 -1
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/src/Button/Button.jsx +26 -5
- package/src/Button/ButtonBase.jsx +79 -16
- package/src/Button/ButtonDropdown.jsx +2 -0
- package/src/Button/ButtonGroup.jsx +62 -45
- package/src/Button/propTypes.js +6 -0
- package/src/Card/PressableCardBase.jsx +3 -1
- package/src/Carousel/Carousel.jsx +71 -7
- package/src/Carousel/CarouselItem/CarouselItem.jsx +31 -3
- package/src/Icon/Icon.jsx +11 -14
- package/src/Icon/IconText.jsx +0 -1
- package/src/Listbox/GroupControl.jsx +41 -47
- package/src/Listbox/Listbox.jsx +26 -9
- package/src/Listbox/ListboxGroup.jsx +2 -1
- package/src/Listbox/ListboxOverlay.jsx +7 -2
- package/src/Listbox/PressableItem.jsx +8 -4
- package/src/PriceLockup/utils/renderPrice.jsx +15 -17
- package/src/TextInput/TextInputBase.jsx +5 -1
- package/src/ThemeProvider/index.js +1 -0
- package/src/ThemeProvider/useResponsiveThemeTokensCallback.js +129 -0
- package/src/Validator/Validator.jsx +180 -159
- package/src/index.js +2 -1
|
@@ -16,7 +16,9 @@ import {
|
|
|
16
16
|
viewProps,
|
|
17
17
|
wrapStringsInText,
|
|
18
18
|
withLinkRouter,
|
|
19
|
-
contentfulProps
|
|
19
|
+
contentfulProps,
|
|
20
|
+
createMediaQueryStyles,
|
|
21
|
+
StyleSheet as StyleSheetUtils
|
|
20
22
|
} from '../utils'
|
|
21
23
|
import { IconText } from '../Icon'
|
|
22
24
|
|
|
@@ -47,21 +49,26 @@ const selectFlexAndWidthStyles = ({ width, flex }) => {
|
|
|
47
49
|
return styles
|
|
48
50
|
}
|
|
49
51
|
|
|
50
|
-
const selectOuterContainerStyles = (
|
|
52
|
+
const selectOuterContainerStyles = (
|
|
53
|
+
{
|
|
54
|
+
opacity,
|
|
55
|
+
outerBorderColor,
|
|
56
|
+
outerBorderWidth,
|
|
57
|
+
outerBorderGap,
|
|
58
|
+
borderRadius,
|
|
59
|
+
outerBackgroundColor,
|
|
60
|
+
alignSelf
|
|
61
|
+
},
|
|
62
|
+
heightFull
|
|
63
|
+
) => ({
|
|
64
|
+
backgroundColor: outerBackgroundColor,
|
|
51
65
|
opacity,
|
|
52
|
-
outerBorderColor,
|
|
53
|
-
outerBorderWidth,
|
|
54
|
-
outerBorderGap,
|
|
55
|
-
borderRadius,
|
|
56
|
-
outerBackgroundColor
|
|
57
|
-
}) => ({
|
|
58
66
|
...Platform.select({
|
|
59
67
|
native: {
|
|
60
|
-
alignSelf: 'flex-start'
|
|
61
|
-
}
|
|
68
|
+
alignSelf: alignSelf !== undefined ? alignSelf : 'flex-start'
|
|
69
|
+
},
|
|
70
|
+
web: heightFull ? {} : { alignSelf: 'flex-start' }
|
|
62
71
|
}),
|
|
63
|
-
backgroundColor: outerBackgroundColor,
|
|
64
|
-
opacity,
|
|
65
72
|
...applyOuterBorder({
|
|
66
73
|
outerBorderGap,
|
|
67
74
|
outerBorderWidth,
|
|
@@ -217,10 +224,14 @@ const ButtonBase = React.forwardRef(
|
|
|
217
224
|
icon,
|
|
218
225
|
iconPosition = icon ? 'left' : undefined,
|
|
219
226
|
iconProps,
|
|
227
|
+
heightFull = true,
|
|
220
228
|
...rawRest
|
|
221
229
|
},
|
|
222
230
|
ref
|
|
223
231
|
) => {
|
|
232
|
+
const { themeOptions } = useTheme()
|
|
233
|
+
const { viewport } = rawRest
|
|
234
|
+
const enableMediaQueryStyleSheet = themeOptions.enableMediaQueryStyleSheet && viewport
|
|
224
235
|
const { onPress, ...rest } = clickProps.toPressProps(rawRest)
|
|
225
236
|
const extraButtonState = { inactive, selected, iconPosition }
|
|
226
237
|
const resolveButtonTokens = (pressableState) =>
|
|
@@ -228,9 +239,49 @@ const ButtonBase = React.forwardRef(
|
|
|
228
239
|
|
|
229
240
|
const systemProps = selectProps(rest)
|
|
230
241
|
|
|
242
|
+
let layoutMediaQueryStyles
|
|
243
|
+
let flexAndWidthStylesIds
|
|
244
|
+
|
|
245
|
+
if (enableMediaQueryStyleSheet) {
|
|
246
|
+
const defaultPressableState = { pressed: false, hovered: false, focused: false }
|
|
247
|
+
const defaultTokensByViewport = resolveButtonTokens(defaultPressableState)
|
|
248
|
+
|
|
249
|
+
const layoutTokensByViewport = Object.entries(defaultTokensByViewport).reduce(
|
|
250
|
+
(acc, [vp, viewportTokens]) => {
|
|
251
|
+
const flexAndWidthStyles =
|
|
252
|
+
viewportTokens.width === '100%' && viewportTokens.flex === 1
|
|
253
|
+
? selectFlexAndWidthStyles(viewportTokens)
|
|
254
|
+
: {}
|
|
255
|
+
|
|
256
|
+
acc[vp] = {
|
|
257
|
+
...staticStyles.row,
|
|
258
|
+
...selectWebOnlyStyles(inactive, viewportTokens, systemProps),
|
|
259
|
+
...(Object.keys(flexAndWidthStyles).length > 0 ? flexAndWidthStyles : {}),
|
|
260
|
+
...selectOuterSizeStyles(viewportTokens)
|
|
261
|
+
}
|
|
262
|
+
return acc
|
|
263
|
+
},
|
|
264
|
+
{}
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
const mediaQueryStyles = createMediaQueryStyles(layoutTokensByViewport)
|
|
268
|
+
const { ids, styles } = StyleSheetUtils.create({
|
|
269
|
+
layout: {
|
|
270
|
+
...mediaQueryStyles
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
layoutMediaQueryStyles = styles.layout
|
|
275
|
+
flexAndWidthStylesIds = ids.layout
|
|
276
|
+
}
|
|
277
|
+
|
|
231
278
|
const getPressableStyle = (pressableState) => {
|
|
279
|
+
if (enableMediaQueryStyleSheet) {
|
|
280
|
+
const themeTokens = resolveButtonTokens(pressableState)[viewport]
|
|
281
|
+
return [layoutMediaQueryStyles, selectOuterContainerStyles(themeTokens)]
|
|
282
|
+
}
|
|
283
|
+
|
|
232
284
|
const themeTokens = resolveButtonTokens(pressableState)
|
|
233
|
-
// Only apply flex and width styles when they are explicitly set (e.g., from ButtonGroup with width: 'equal') to not to affect other use cases
|
|
234
285
|
const flexAndWidthStyles =
|
|
235
286
|
themeTokens.width === '100%' && themeTokens.flex === 1
|
|
236
287
|
? selectFlexAndWidthStyles(themeTokens)
|
|
@@ -239,12 +290,21 @@ const ButtonBase = React.forwardRef(
|
|
|
239
290
|
return [
|
|
240
291
|
staticStyles.row,
|
|
241
292
|
selectWebOnlyStyles(inactive, themeTokens, systemProps),
|
|
242
|
-
selectOuterContainerStyles(themeTokens),
|
|
293
|
+
selectOuterContainerStyles(themeTokens, heightFull),
|
|
243
294
|
...(Object.keys(flexAndWidthStyles).length > 0 ? [flexAndWidthStyles] : []),
|
|
244
295
|
selectOuterSizeStyles(themeTokens)
|
|
245
296
|
]
|
|
246
297
|
}
|
|
247
|
-
|
|
298
|
+
|
|
299
|
+
const dataSetProp =
|
|
300
|
+
flexAndWidthStylesIds || rawRest.dataSet
|
|
301
|
+
? {
|
|
302
|
+
dataSet: {
|
|
303
|
+
...(flexAndWidthStylesIds ? { media: flexAndWidthStylesIds } : {}),
|
|
304
|
+
...rawRest.dataSet
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
: {}
|
|
248
308
|
|
|
249
309
|
return (
|
|
250
310
|
<Pressable
|
|
@@ -255,9 +315,12 @@ const ButtonBase = React.forwardRef(
|
|
|
255
315
|
disabled={inactive}
|
|
256
316
|
hrefAttrs={hrefAttrs}
|
|
257
317
|
{...systemProps}
|
|
318
|
+
{...dataSetProp}
|
|
258
319
|
>
|
|
259
320
|
{(pressableState) => {
|
|
260
|
-
const themeTokens =
|
|
321
|
+
const themeTokens = enableMediaQueryStyleSheet
|
|
322
|
+
? resolveButtonTokens(pressableState)[viewport]
|
|
323
|
+
: resolveButtonTokens(pressableState)
|
|
261
324
|
const containerStyles = selectInnerContainerStyles(themeTokens)
|
|
262
325
|
const borderStyles = selectBorderStyles(themeTokens)
|
|
263
326
|
const textStyles = [
|
|
@@ -105,6 +105,7 @@ const ButtonDropdown = React.forwardRef(
|
|
|
105
105
|
accessibilityRole = 'radio',
|
|
106
106
|
description,
|
|
107
107
|
singleOption,
|
|
108
|
+
heightFull = true,
|
|
108
109
|
...props
|
|
109
110
|
},
|
|
110
111
|
ref
|
|
@@ -151,6 +152,7 @@ const ButtonDropdown = React.forwardRef(
|
|
|
151
152
|
{...pressHandlers}
|
|
152
153
|
onPress={handlePress}
|
|
153
154
|
tokens={getButtonTokens}
|
|
155
|
+
heightFull={heightFull}
|
|
154
156
|
inactive={singleOption || inactive}
|
|
155
157
|
icon={() => null}
|
|
156
158
|
accessibilityRole={accessibilityRole}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useCallback, useMemo } from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
3
|
import ABBPropTypes from 'airbnb-prop-types'
|
|
4
|
-
import { Platform } from 'react-native'
|
|
4
|
+
import { Platform, View, StyleSheet } from 'react-native'
|
|
5
5
|
|
|
6
6
|
import ButtonBase from './ButtonBase'
|
|
7
|
-
import { StackWrap } from '../StackView'
|
|
8
7
|
import Fieldset from '../Fieldset'
|
|
9
8
|
import { useViewport } from '../ViewportProvider'
|
|
10
9
|
import { useThemeTokens, useThemeTokensCallback } from '../ThemeProvider'
|
|
11
10
|
import {
|
|
11
|
+
useSpacingScale,
|
|
12
12
|
a11yProps,
|
|
13
13
|
containUniqueFields,
|
|
14
14
|
focusHandlerProps,
|
|
15
15
|
pressProps,
|
|
16
16
|
getTokensPropType,
|
|
17
17
|
selectSystemProps,
|
|
18
|
-
selectTokens,
|
|
19
18
|
useMultipleInputValues,
|
|
20
19
|
variantProp,
|
|
21
20
|
viewProps
|
|
@@ -30,16 +29,6 @@ const [selectItemProps, selectedItemPropTypes] = selectSystemProps([
|
|
|
30
29
|
viewProps
|
|
31
30
|
])
|
|
32
31
|
|
|
33
|
-
const getStackWrapTokens = (variant) => {
|
|
34
|
-
return Platform.select({
|
|
35
|
-
web: {
|
|
36
|
-
justifyContent: variant?.width === 'equal' ? 'space-evenly' : 'flex-start',
|
|
37
|
-
width: variant?.width === 'equal' ? '100%' : 'auto'
|
|
38
|
-
},
|
|
39
|
-
default: {}
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
|
|
43
32
|
const ButtonGroup = React.forwardRef(
|
|
44
33
|
(
|
|
45
34
|
{
|
|
@@ -72,25 +61,52 @@ const ButtonGroup = React.forwardRef(
|
|
|
72
61
|
) => {
|
|
73
62
|
const viewport = useViewport()
|
|
74
63
|
const themeTokens = useThemeTokens('ButtonGroup', tokens, variant, { viewport })
|
|
75
|
-
const themeStackTokens = selectTokens('StackView', themeTokens)
|
|
76
|
-
const variantStackTokens = getStackWrapTokens(variant)
|
|
77
|
-
const stackTokens = {
|
|
78
|
-
...themeStackTokens,
|
|
79
|
-
...variantStackTokens
|
|
80
|
-
}
|
|
81
64
|
const { direction, space, fieldSpace, borderRadius, backgroundColor, padding, gap } =
|
|
82
65
|
themeTokens
|
|
83
66
|
|
|
67
|
+
const isMobileNonContained =
|
|
68
|
+
Platform.OS !== 'web' && (!variant || variant?.style !== 'contained')
|
|
69
|
+
|
|
84
70
|
const themeButtonTokensCallback = useThemeTokensCallback('ButtonGroupItem', tokens, variant)
|
|
71
|
+
const gapValue = useSpacingScale(gap || space)
|
|
85
72
|
|
|
86
|
-
const getButtonTokens = (
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
73
|
+
const getButtonTokens = useCallback(
|
|
74
|
+
(state) => {
|
|
75
|
+
const themeButtonTokens = themeButtonTokensCallback(state)
|
|
76
|
+
|
|
77
|
+
const shouldUseTransparentBackground =
|
|
78
|
+
isMobileNonContained && !state.selected && !state.pressed && !state.hover && !state.focus
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
...themeButtonTokens,
|
|
82
|
+
...(variant?.width === 'equal' && staticStyles.equalWidth),
|
|
83
|
+
...(shouldUseTransparentBackground && { backgroundColor: 'transparent' }),
|
|
84
|
+
alignSelf: themeButtonTokens.width ? 'flex-start' : 'center'
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
[themeButtonTokensCallback, isMobileNonContained, variant?.width]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
const fieldsetStyles = useMemo(
|
|
91
|
+
() => ({
|
|
92
|
+
...staticStyles.fieldsetBase,
|
|
93
|
+
borderRadius,
|
|
94
|
+
backgroundColor: isMobileNonContained ? 'transparent' : backgroundColor || 'transparent',
|
|
95
|
+
padding,
|
|
96
|
+
width: variant?.width === 'equal' ? '100%' : 'auto'
|
|
97
|
+
}),
|
|
98
|
+
[borderRadius, backgroundColor, padding, variant?.width, isMobileNonContained]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const viewStyles = useMemo(
|
|
102
|
+
() => ({
|
|
103
|
+
...staticStyles.viewBase,
|
|
104
|
+
flexDirection: direction === 'column' ? 'column' : 'row',
|
|
105
|
+
gap: gapValue || 0,
|
|
106
|
+
justifyContent: variant?.width === 'equal' ? 'space-evenly' : 'flex-start'
|
|
107
|
+
}),
|
|
108
|
+
[direction, gapValue, variant?.width]
|
|
109
|
+
)
|
|
94
110
|
|
|
95
111
|
const { currentValues, toggleOneValue } = useMultipleInputValues({
|
|
96
112
|
initialValues,
|
|
@@ -130,24 +146,10 @@ const ButtonGroup = React.forwardRef(
|
|
|
130
146
|
inactive={inactive}
|
|
131
147
|
validation={validation}
|
|
132
148
|
accessibilityRole={accessibilityRole}
|
|
133
|
-
style={
|
|
134
|
-
borderRadius,
|
|
135
|
-
backgroundColor,
|
|
136
|
-
padding,
|
|
137
|
-
...(Platform.OS === 'web'
|
|
138
|
-
? { gap, width: variant?.width === 'equal' ? '100%' : 'auto' }
|
|
139
|
-
: { alignSelf: 'flex-start' })
|
|
140
|
-
}}
|
|
149
|
+
style={fieldsetStyles}
|
|
141
150
|
{...selectProps(rest)}
|
|
142
151
|
>
|
|
143
|
-
<
|
|
144
|
-
accessibilityRole={innerRole}
|
|
145
|
-
space={space}
|
|
146
|
-
direction={direction}
|
|
147
|
-
tokens={stackTokens}
|
|
148
|
-
gap={gap}
|
|
149
|
-
ref={ref}
|
|
150
|
-
>
|
|
152
|
+
<View accessibilityRole={innerRole} style={viewStyles}>
|
|
151
153
|
{items.map(
|
|
152
154
|
({ label, id = label, accessibilityLabel, ref: itemRef, ...itemRest }, index) => {
|
|
153
155
|
const isSelected = currentValues.includes(id)
|
|
@@ -193,7 +195,7 @@ const ButtonGroup = React.forwardRef(
|
|
|
193
195
|
)
|
|
194
196
|
}
|
|
195
197
|
)}
|
|
196
|
-
</
|
|
198
|
+
</View>
|
|
197
199
|
</Fieldset>
|
|
198
200
|
)
|
|
199
201
|
}
|
|
@@ -309,4 +311,19 @@ ButtonGroup.propTypes = {
|
|
|
309
311
|
copy: PropTypes.oneOf(['en', 'fr'])
|
|
310
312
|
}
|
|
311
313
|
|
|
314
|
+
const staticStyles = StyleSheet.create({
|
|
315
|
+
fieldsetBase: {
|
|
316
|
+
alignSelf: 'flex-start',
|
|
317
|
+
display: 'inline'
|
|
318
|
+
},
|
|
319
|
+
viewBase: {
|
|
320
|
+
flexWrap: 'wrap',
|
|
321
|
+
alignItems: 'center'
|
|
322
|
+
},
|
|
323
|
+
equalWidth: {
|
|
324
|
+
width: '100%',
|
|
325
|
+
flex: 1
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
|
|
312
329
|
export default ButtonGroup
|
package/src/Button/propTypes.js
CHANGED
|
@@ -10,6 +10,12 @@ export const textAndA11yText = ABBPropTypes.childrenOf(
|
|
|
10
10
|
|
|
11
11
|
const buttonPropTypes = {
|
|
12
12
|
tokens: getTokensPropType('Button'),
|
|
13
|
+
/**
|
|
14
|
+
* If true, the button will honor the align-items value from its parent flex container.
|
|
15
|
+
* If false, the button will always align to 'flex-start' in a flex container.
|
|
16
|
+
* Currently, `heightFull`'s default behaviour will expand to the full height of its parent's flex container. In an upcoming major release, the default behaviour will be changed to expand based on the content. To maintain the expected behaviour, you must explicitly set `heightFull: true`
|
|
17
|
+
*/
|
|
18
|
+
heightFull: PropTypes.bool,
|
|
13
19
|
/**
|
|
14
20
|
* If true, prevents the button from being pressed, changes the cursor (on web) and accessibility
|
|
15
21
|
* attributes to communicate this to the user, and applies `inactive: true` appearances from the theme
|
|
@@ -399,6 +399,7 @@ const Carousel = React.forwardRef(
|
|
|
399
399
|
copy,
|
|
400
400
|
slideDuration = 0,
|
|
401
401
|
transitionDuration = 0,
|
|
402
|
+
loopDuration = transitionDuration,
|
|
402
403
|
autoPlay = false,
|
|
403
404
|
enablePeeking = false,
|
|
404
405
|
...rest
|
|
@@ -406,6 +407,7 @@ const Carousel = React.forwardRef(
|
|
|
406
407
|
ref
|
|
407
408
|
) => {
|
|
408
409
|
let childrenArray = unpackFragment(children)
|
|
410
|
+
const isTransitioningRef = React.useRef(false)
|
|
409
411
|
const viewport = useViewport()
|
|
410
412
|
const totalItems = getTotalItems(enableDisplayMultipleItemsPerSlide, childrenArray, viewport)
|
|
411
413
|
const autoPlayFeatureEnabled =
|
|
@@ -481,10 +483,14 @@ const Carousel = React.forwardRef(
|
|
|
481
483
|
)
|
|
482
484
|
const handleAnimationEnd = React.useCallback(
|
|
483
485
|
(...args) => {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
+
const result = args[args.length - 1]
|
|
487
|
+
if (result?.finished) {
|
|
488
|
+
if (typeof onAnimationEnd === 'function') onAnimationEnd(...args)
|
|
489
|
+
setIsAnimating(false)
|
|
490
|
+
isTransitioningRef.current = false
|
|
491
|
+
}
|
|
486
492
|
},
|
|
487
|
-
[onAnimationEnd]
|
|
493
|
+
[onAnimationEnd, isTransitioningRef]
|
|
488
494
|
)
|
|
489
495
|
|
|
490
496
|
const updateOffset = React.useCallback(() => {
|
|
@@ -533,6 +539,8 @@ const Carousel = React.forwardRef(
|
|
|
533
539
|
|
|
534
540
|
const animate = React.useCallback(
|
|
535
541
|
(panToAnimate, toValue, toIndex) => {
|
|
542
|
+
const applicableTransitionDuration =
|
|
543
|
+
isLastSlide && toIndex === 0 ? loopDuration : transitionDuration
|
|
536
544
|
const handleAnimationEndToIndex = (...args) => handleAnimationEnd(toIndex, ...args)
|
|
537
545
|
if (reduceMotionRef.current || isSwiping.current) {
|
|
538
546
|
Animated.timing(panToAnimate, { toValue, duration: 1, useNativeDriver: false }).start(
|
|
@@ -543,14 +551,14 @@ const Carousel = React.forwardRef(
|
|
|
543
551
|
...springConfig,
|
|
544
552
|
toValue,
|
|
545
553
|
useNativeDriver: false,
|
|
546
|
-
duration:
|
|
554
|
+
duration: applicableTransitionDuration * 1000
|
|
547
555
|
}).start(handleAnimationEndToIndex)
|
|
548
556
|
} else if (enablePeeking || enableDisplayMultipleItemsPerSlide) {
|
|
549
557
|
Animated.timing(panToAnimate, {
|
|
550
558
|
...springConfig,
|
|
551
559
|
toValue,
|
|
552
560
|
useNativeDriver: false,
|
|
553
|
-
duration:
|
|
561
|
+
duration: applicableTransitionDuration ? applicableTransitionDuration * 1000 : 1000
|
|
554
562
|
}).start(handleAnimationEndToIndex)
|
|
555
563
|
} else {
|
|
556
564
|
Animated.spring(panToAnimate, {
|
|
@@ -564,6 +572,8 @@ const Carousel = React.forwardRef(
|
|
|
564
572
|
springConfig,
|
|
565
573
|
handleAnimationEnd,
|
|
566
574
|
transitionDuration,
|
|
575
|
+
loopDuration,
|
|
576
|
+
isLastSlide,
|
|
567
577
|
isAutoPlayEnabled,
|
|
568
578
|
enablePeeking,
|
|
569
579
|
enableDisplayMultipleItemsPerSlide
|
|
@@ -597,7 +607,9 @@ const Carousel = React.forwardRef(
|
|
|
597
607
|
|
|
598
608
|
const index = activeIndexRef.current + calcDelta
|
|
599
609
|
if (skipChanges) {
|
|
610
|
+
isTransitioningRef.current = true
|
|
600
611
|
animate(pan, toValue, index)
|
|
612
|
+
|
|
601
613
|
if (enableHero) {
|
|
602
614
|
animate(heroPan, toValue, index)
|
|
603
615
|
}
|
|
@@ -617,6 +629,7 @@ const Carousel = React.forwardRef(
|
|
|
617
629
|
toValue.x = finalWidth * -1 * calcDelta
|
|
618
630
|
const heroToValue = { x: 0, y: 0 }
|
|
619
631
|
heroToValue.x = heroContainerLayoutRef.current.width * -1 * calcDelta
|
|
632
|
+
isTransitioningRef.current = true
|
|
620
633
|
animate(pan, toValue, index)
|
|
621
634
|
if (enableHero) {
|
|
622
635
|
animate(heroPan, heroToValue, index)
|
|
@@ -1007,6 +1020,15 @@ const Carousel = React.forwardRef(
|
|
|
1007
1020
|
setisCarouselPlaying((prevState) => !prevState)
|
|
1008
1021
|
}, [isCarouselPlaying, stopAutoplay, startAutoplay])
|
|
1009
1022
|
|
|
1023
|
+
const handleKeyDown = React.useCallback(
|
|
1024
|
+
(event) => {
|
|
1025
|
+
if (isTransitioningRef.current && event.key === 'Tab') {
|
|
1026
|
+
event.preventDefault()
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
[isTransitioningRef]
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1010
1032
|
return (
|
|
1011
1033
|
<View style={selectRootContainerStyles(enableHero, viewport)}>
|
|
1012
1034
|
<View style={selectMainContainerStyles(enableHero, viewport)}>
|
|
@@ -1036,6 +1058,7 @@ const Carousel = React.forwardRef(
|
|
|
1036
1058
|
ref={ref}
|
|
1037
1059
|
{...systemProps}
|
|
1038
1060
|
{...containerProps}
|
|
1061
|
+
{...(Platform.OS === 'web' ? { onKeyDown: handleKeyDown } : {})}
|
|
1039
1062
|
>
|
|
1040
1063
|
{isAutoPlayEnabled ? (
|
|
1041
1064
|
<View
|
|
@@ -1125,10 +1148,45 @@ const Carousel = React.forwardRef(
|
|
|
1125
1148
|
accessibilityLiveRegion={accessibilityLiveRegion}
|
|
1126
1149
|
>
|
|
1127
1150
|
{childrenArray.map((element, index) => {
|
|
1128
|
-
|
|
1151
|
+
let hidden = !isAnimating && index !== activeIndex
|
|
1152
|
+
|
|
1153
|
+
if (enablePeeking && !isAnimating) {
|
|
1154
|
+
if (enableDisplayMultipleItemsPerSlide) {
|
|
1155
|
+
const maxItemsForSlide = getMaximumItemsForSlide(
|
|
1156
|
+
enableDisplayMultipleItemsPerSlide,
|
|
1157
|
+
viewport
|
|
1158
|
+
)
|
|
1159
|
+
if (
|
|
1160
|
+
index >= activeIndex * maxItemsForSlide - 1 &&
|
|
1161
|
+
index < activeIndex * maxItemsForSlide + maxItemsForSlide + 1
|
|
1162
|
+
) {
|
|
1163
|
+
hidden = false
|
|
1164
|
+
} else {
|
|
1165
|
+
hidden = true
|
|
1166
|
+
}
|
|
1167
|
+
} else if (index >= activeIndex - 1 && index <= activeIndex + 1) {
|
|
1168
|
+
hidden = false
|
|
1169
|
+
}
|
|
1170
|
+
} else if (
|
|
1171
|
+
!enablePeeking &&
|
|
1172
|
+
enableDisplayMultipleItemsPerSlide &&
|
|
1173
|
+
!isAnimating
|
|
1174
|
+
) {
|
|
1175
|
+
const maxItemsForSlide = getMaximumItemsForSlide(
|
|
1176
|
+
enableDisplayMultipleItemsPerSlide,
|
|
1177
|
+
viewport
|
|
1178
|
+
)
|
|
1179
|
+
if (
|
|
1180
|
+
index >= activeIndex * maxItemsForSlide &&
|
|
1181
|
+
index < activeIndex * maxItemsForSlide + maxItemsForSlide
|
|
1182
|
+
) {
|
|
1183
|
+
hidden = false
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1129
1187
|
const clonedElement = React.cloneElement(element, {
|
|
1130
1188
|
elementIndex: index,
|
|
1131
|
-
hidden
|
|
1189
|
+
hidden,
|
|
1132
1190
|
enablePeeking,
|
|
1133
1191
|
peekingProps: getPeekingProps(viewport),
|
|
1134
1192
|
enableDisplayMultipleItemsPerSlide,
|
|
@@ -1390,6 +1448,12 @@ Carousel.propTypes = {
|
|
|
1390
1448
|
* - `autoPlay` and `slideDuration` are required to be set for this to work
|
|
1391
1449
|
*/
|
|
1392
1450
|
transitionDuration: PropTypes.number,
|
|
1451
|
+
/**
|
|
1452
|
+
* Time it takes in seconds to transition from last slide to first slide
|
|
1453
|
+
* - Default value equals `transitionDuration`'s value
|
|
1454
|
+
* - `autoPlay` and `transitionDuration` are required to be set for this to work
|
|
1455
|
+
*/
|
|
1456
|
+
loopDuration: PropTypes.number,
|
|
1393
1457
|
/**
|
|
1394
1458
|
* If set to `true`, the Carousel will show the previous and next slides
|
|
1395
1459
|
* - Default value is `false`
|
|
@@ -114,15 +114,43 @@ const CarouselItem = React.forwardRef(
|
|
|
114
114
|
|
|
115
115
|
const handleFocus = React.useCallback(
|
|
116
116
|
(event) => {
|
|
117
|
-
if (Platform.OS === 'web'
|
|
118
|
-
|
|
117
|
+
if (Platform.OS === 'web') {
|
|
118
|
+
if (enablePeeking) {
|
|
119
|
+
if (enableDisplayMultipleItemsPerSlide) {
|
|
120
|
+
const startIndex = maximumItemsForSlide * activeIndex
|
|
121
|
+
const endIndex = startIndex + maximumItemsForSlide - 1
|
|
122
|
+
if (elementIndex < startIndex) {
|
|
123
|
+
if (activeIndex - 1 < 0) {
|
|
124
|
+
goTo(0)
|
|
125
|
+
} else {
|
|
126
|
+
goTo(activeIndex - 1)
|
|
127
|
+
}
|
|
128
|
+
} else if (elementIndex > endIndex) {
|
|
129
|
+
goTo(activeIndex + 1)
|
|
130
|
+
}
|
|
131
|
+
} else if (elementIndex !== activeIndex) {
|
|
132
|
+
if (elementIndex > activeIndex) {
|
|
133
|
+
goTo(activeIndex + 1)
|
|
134
|
+
} else if (elementIndex < activeIndex) {
|
|
135
|
+
goTo(activeIndex - 1)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
119
139
|
}
|
|
120
140
|
|
|
121
141
|
if (rest.onFocus) {
|
|
122
142
|
rest.onFocus(event)
|
|
123
143
|
}
|
|
124
144
|
},
|
|
125
|
-
[
|
|
145
|
+
[
|
|
146
|
+
elementIndex,
|
|
147
|
+
activeIndex,
|
|
148
|
+
goTo,
|
|
149
|
+
maximumItemsForSlide,
|
|
150
|
+
rest,
|
|
151
|
+
enablePeeking,
|
|
152
|
+
enableDisplayMultipleItemsPerSlide
|
|
153
|
+
]
|
|
126
154
|
)
|
|
127
155
|
|
|
128
156
|
return (
|
package/src/Icon/Icon.jsx
CHANGED
|
@@ -43,20 +43,17 @@ const Icon = React.forwardRef(
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const getIconContentForMobile = () => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
return iconContent
|
|
46
|
+
return (
|
|
47
|
+
<View
|
|
48
|
+
style={{
|
|
49
|
+
backgroundColor: themeTokens.backgroundColor,
|
|
50
|
+
borderRadius: themeTokens.borderRadius,
|
|
51
|
+
...paddingStyles
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
{iconContent}
|
|
55
|
+
</View>
|
|
56
|
+
)
|
|
60
57
|
}
|
|
61
58
|
|
|
62
59
|
return Platform.OS === 'web' ? (
|
package/src/Icon/IconText.jsx
CHANGED