@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.
- package/CHANGELOG.md +42 -2
- package/__tests17__/ThemeProvider/ThemeProvider.test.jsx +2 -1
- package/component-docs.json +1035 -231
- package/jest.config-android.js +17 -0
- package/jest.config-ios.js +18 -0
- package/jest.config-web.js +31 -0
- package/lib/BaseProvider/index.js +2 -1
- package/lib/Box/Box.js +14 -1
- package/lib/Button/ButtonBase.js +6 -2
- package/lib/Button/ButtonDropdown.js +207 -0
- package/lib/Button/index.js +8 -0
- package/lib/Carousel/Carousel.js +34 -6
- package/lib/Carousel/CarouselItem/CarouselItem.js +7 -1
- package/lib/Carousel/CarouselTabs/CarouselTabsPanel.js +22 -14
- package/lib/FlexGrid/Col/Col.js +1 -3
- package/lib/FlexGrid/FlexGrid.js +3 -5
- package/lib/FlexGrid/Row/Row.js +3 -3
- package/lib/IconButton/IconButton.js +12 -4
- package/lib/MultiSelectFilter/MultiSelectFilter.js +276 -0
- package/lib/MultiSelectFilter/dictionary.js +19 -0
- package/lib/MultiSelectFilter/index.js +13 -0
- package/lib/Pagination/SideButton.js +6 -4
- package/lib/Responsive/Responsive.js +58 -0
- package/lib/Responsive/index.js +13 -0
- package/lib/Search/Search.js +33 -63
- package/lib/Select/Picker.native.js +16 -13
- package/lib/Select/Select.js +7 -1
- package/lib/Select/constants.js +15 -0
- package/lib/StepTracker/Step.js +2 -1
- package/lib/Tags/Tags.js +10 -4
- package/lib/TextInput/TextInput.js +9 -2
- package/lib/TextInput/TextInputBase.js +98 -20
- package/lib/TextInput/dictionary.js +15 -0
- package/lib/ThemeProvider/ThemeProvider.js +6 -1
- package/lib/index.js +18 -0
- package/lib/utils/BaseView/BaseView.js +64 -0
- package/lib/utils/BaseView/BaseView.native.js +16 -0
- package/lib/utils/BaseView/index.js +13 -0
- package/lib/utils/index.js +10 -1
- package/lib/utils/input.js +11 -3
- package/lib/utils/props/handlerProps.js +5 -0
- package/lib-module/BaseProvider/index.js +2 -1
- package/lib-module/Box/Box.js +14 -1
- package/lib-module/Button/ButtonBase.js +6 -2
- package/lib-module/Button/ButtonDropdown.js +181 -0
- package/lib-module/Button/index.js +2 -1
- package/lib-module/Carousel/Carousel.js +34 -6
- package/lib-module/Carousel/CarouselItem/CarouselItem.js +8 -2
- package/lib-module/Carousel/CarouselTabs/CarouselTabsPanel.js +24 -16
- package/lib-module/FlexGrid/Col/Col.js +2 -3
- package/lib-module/FlexGrid/FlexGrid.js +2 -3
- package/lib-module/FlexGrid/Row/Row.js +2 -2
- package/lib-module/IconButton/IconButton.js +14 -4
- package/lib-module/MultiSelectFilter/MultiSelectFilter.js +248 -0
- package/lib-module/MultiSelectFilter/dictionary.js +12 -0
- package/lib-module/MultiSelectFilter/index.js +2 -0
- package/lib-module/Pagination/SideButton.js +6 -4
- package/lib-module/Responsive/Responsive.js +45 -0
- package/lib-module/Responsive/index.js +2 -0
- package/lib-module/Search/Search.js +33 -61
- package/lib-module/Select/Picker.native.js +15 -13
- package/lib-module/Select/Select.js +6 -1
- package/lib-module/Select/constants.js +5 -0
- package/lib-module/StepTracker/Step.js +2 -1
- package/lib-module/Tags/Tags.js +10 -4
- package/lib-module/TextInput/TextInput.js +6 -0
- package/lib-module/TextInput/TextInputBase.js +96 -21
- package/lib-module/TextInput/dictionary.js +8 -0
- package/lib-module/ThemeProvider/ThemeProvider.js +6 -1
- package/lib-module/index.js +2 -0
- package/lib-module/utils/BaseView/BaseView.js +43 -0
- package/lib-module/utils/BaseView/BaseView.native.js +6 -0
- package/lib-module/utils/BaseView/index.js +2 -0
- package/lib-module/utils/index.js +2 -1
- package/lib-module/utils/input.js +11 -3
- package/lib-module/utils/props/handlerProps.js +5 -0
- package/package.json +6 -3
- package/src/BaseProvider/index.jsx +4 -1
- package/src/Box/Box.jsx +14 -1
- package/src/Button/ButtonBase.jsx +4 -2
- package/src/Button/ButtonDropdown.jsx +179 -0
- package/src/Button/index.js +2 -1
- package/src/Carousel/Carousel.jsx +48 -13
- package/src/Carousel/CarouselItem/CarouselItem.jsx +9 -2
- package/src/Carousel/CarouselTabs/CarouselTabsPanel.jsx +19 -15
- package/src/FlexGrid/Col/Col.jsx +4 -4
- package/src/FlexGrid/FlexGrid.jsx +11 -10
- package/src/FlexGrid/Row/Row.jsx +4 -3
- package/src/IconButton/IconButton.jsx +3 -1
- package/src/MultiSelectFilter/MultiSelectFilter.jsx +227 -0
- package/src/MultiSelectFilter/dictionary.js +12 -0
- package/src/MultiSelectFilter/index.js +3 -0
- package/src/Pagination/SideButton.jsx +5 -5
- package/src/Responsive/Responsive.jsx +33 -0
- package/src/Responsive/index.js +3 -0
- package/src/Search/Search.jsx +19 -33
- package/src/Select/Picker.native.jsx +29 -14
- package/src/Select/Select.jsx +7 -1
- package/src/Select/constants.js +5 -0
- package/src/StepTracker/Step.jsx +5 -1
- package/src/Tags/Tags.jsx +46 -33
- package/src/TextInput/TextInput.jsx +5 -0
- package/src/TextInput/TextInputBase.jsx +85 -20
- package/src/TextInput/dictionary.js +8 -0
- package/src/ThemeProvider/ThemeProvider.jsx +5 -1
- package/src/index.js +2 -0
- package/src/utils/BaseView/BaseView.jsx +38 -0
- package/src/utils/BaseView/BaseView.native.jsx +6 -0
- package/src/utils/BaseView/index.js +3 -0
- package/src/utils/index.js +1 -0
- package/src/utils/input.js +9 -4
- package/src/utils/props/handlerProps.js +4 -0
|
@@ -164,7 +164,7 @@ const Carousel = React.forwardRef(
|
|
|
164
164
|
),
|
|
165
165
|
tag = 'ul',
|
|
166
166
|
accessibilityRole,
|
|
167
|
-
accessibilityLabel
|
|
167
|
+
accessibilityLabel,
|
|
168
168
|
accessibilityLiveRegion = 'polite',
|
|
169
169
|
copy,
|
|
170
170
|
...rest
|
|
@@ -296,7 +296,7 @@ const Carousel = React.forwardRef(
|
|
|
296
296
|
|
|
297
297
|
animate(toValue, index)
|
|
298
298
|
|
|
299
|
-
if (onIndexChanged) onIndexChanged(calcDelta)
|
|
299
|
+
if (onIndexChanged) onIndexChanged(calcDelta, index)
|
|
300
300
|
return calcDelta
|
|
301
301
|
},
|
|
302
302
|
[containerLayout.width, activeIndex, animate, children.length, onIndexChanged]
|
|
@@ -422,6 +422,32 @@ const Carousel = React.forwardRef(
|
|
|
422
422
|
const activePanelNavigation =
|
|
423
423
|
tabs && showPanelTabs ? <CarouselTabsPanel items={tabs} /> : panelNavigation
|
|
424
424
|
|
|
425
|
+
const isFirstFocusContainer = Boolean(refocus && !skipLinkHref)
|
|
426
|
+
const containerRef = (element) => {
|
|
427
|
+
// Apply both firstFocusRef to the container
|
|
428
|
+
firstFocusRef.current = element
|
|
429
|
+
// Also apply forwarded ref if there is one (which could be a function ref)
|
|
430
|
+
if (ref) {
|
|
431
|
+
if (typeof ref === 'object') {
|
|
432
|
+
// eslint-disable-next-line no-param-reassign
|
|
433
|
+
ref.current = element
|
|
434
|
+
} else if (typeof ref === 'function') {
|
|
435
|
+
ref(element)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// If container isn't used for focus, give it a label of title if none is passed in,
|
|
440
|
+
// otherwise read the current position on focus
|
|
441
|
+
const containerAccessibilityLabel =
|
|
442
|
+
systemProps.accessibilityLabel ?? isFirstFocusContainer
|
|
443
|
+
? `${title ? `${title} ` : ''}${getCopyWithPlaceholders('stepTrackerLabel')}`
|
|
444
|
+
: title
|
|
445
|
+
const containerProps = {
|
|
446
|
+
accessibilityLabel: containerAccessibilityLabel,
|
|
447
|
+
// If used for focus, attach the ref and draw a focus box around the whole carousel
|
|
448
|
+
...(isFirstFocusContainer && { ref: containerRef, focusable: true })
|
|
449
|
+
}
|
|
450
|
+
|
|
425
451
|
return (
|
|
426
452
|
<CarouselProvider
|
|
427
453
|
activeIndex={activeIndex}
|
|
@@ -434,7 +460,13 @@ const Carousel = React.forwardRef(
|
|
|
434
460
|
refocus={refocus}
|
|
435
461
|
width={containerLayout.width}
|
|
436
462
|
>
|
|
437
|
-
<View
|
|
463
|
+
<View
|
|
464
|
+
style={staticStyles.root}
|
|
465
|
+
onLayout={onContainerLayout}
|
|
466
|
+
ref={ref}
|
|
467
|
+
{...systemProps}
|
|
468
|
+
{...containerProps}
|
|
469
|
+
>
|
|
438
470
|
{showPreviousNextNavigation && (
|
|
439
471
|
<View
|
|
440
472
|
style={selectPreviousNextNavigationButtonStyles(
|
|
@@ -464,14 +496,14 @@ const Carousel = React.forwardRef(
|
|
|
464
496
|
{getCopyWithPlaceholders('skipLink')}
|
|
465
497
|
</SkipLink>
|
|
466
498
|
)}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
499
|
+
{!isFirstFocusContainer && (
|
|
500
|
+
<A11yText
|
|
501
|
+
// Read the current slide position to screen readers on slide.
|
|
502
|
+
// If it's set to refocus and doesn't have a SkipLink to focus to, focus this.
|
|
503
|
+
accessibilityLiveRegion={!skipLinkHref && refocus ? undefined : 'polite'}
|
|
504
|
+
text={getCopyWithPlaceholders('stepTrackerLabel')}
|
|
505
|
+
/>
|
|
506
|
+
)}
|
|
475
507
|
<View style={selectContainerStyles(containerLayout.width)}>
|
|
476
508
|
<Animated.View
|
|
477
509
|
style={StyleSheet.flatten([
|
|
@@ -488,7 +520,10 @@ const Carousel = React.forwardRef(
|
|
|
488
520
|
>
|
|
489
521
|
{childrenArray.map((element, index) => {
|
|
490
522
|
const hidden = !isAnimating && index !== activeIndex
|
|
491
|
-
const clonedElement = React.cloneElement(element, {
|
|
523
|
+
const clonedElement = React.cloneElement(element, {
|
|
524
|
+
elementIndex: index,
|
|
525
|
+
hidden
|
|
526
|
+
})
|
|
492
527
|
return <React.Fragment key={index.toFixed(2)}>{clonedElement}</React.Fragment>
|
|
493
528
|
})}
|
|
494
529
|
</Animated.View>
|
|
@@ -577,7 +612,7 @@ Carousel.propTypes = {
|
|
|
577
612
|
* This function is also provided with a parameter indicating changed index (either 1, or -1)
|
|
578
613
|
* Use it as follows:
|
|
579
614
|
* ```js
|
|
580
|
-
* const onIndexChangedCallback = React.useCallback((changedIndex) => {
|
|
615
|
+
* const onIndexChangedCallback = React.useCallback((changedIndex, currentActiveIndex) => {
|
|
581
616
|
* console.log(changedIndex)
|
|
582
617
|
* }, []) // pass local dependencies as per your component
|
|
583
618
|
* <Carousel
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
getA11yPropsFromHtmlTag,
|
|
7
7
|
selectSystemProps,
|
|
8
8
|
a11yProps,
|
|
9
|
-
viewProps
|
|
9
|
+
viewProps,
|
|
10
|
+
variantProp
|
|
10
11
|
} from '../../utils'
|
|
11
12
|
import { useCarousel } from '../CarouselContext'
|
|
12
13
|
|
|
@@ -18,6 +19,7 @@ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, vie
|
|
|
18
19
|
*/
|
|
19
20
|
const CarouselItem = ({ children, elementIndex, tag = 'li', hidden, ...rest }) => {
|
|
20
21
|
const { width, activeIndex } = useCarousel()
|
|
22
|
+
|
|
21
23
|
const selectedProps = selectProps({
|
|
22
24
|
...rest,
|
|
23
25
|
...getA11yPropsFromHtmlTag(tag, rest.accessibilityRole)
|
|
@@ -38,6 +40,7 @@ const CarouselItem = ({ children, elementIndex, tag = 'li', hidden, ...rest }) =
|
|
|
38
40
|
|
|
39
41
|
CarouselItem.propTypes = {
|
|
40
42
|
...selectedSystemPropTypes,
|
|
43
|
+
variant: variantProp.propType,
|
|
41
44
|
/**
|
|
42
45
|
* Index of the current slide
|
|
43
46
|
* Don't pass this prop when using `Carousel.Item` as it is already being passed by `Carousel` top-level component
|
|
@@ -58,7 +61,11 @@ CarouselItem.propTypes = {
|
|
|
58
61
|
* Carousel's innermost container defaults to `'ul'` which can be overridden. If the tag of either
|
|
59
62
|
* `Carousel` or `Carousel.Item` is overriden, the other should be too, to avoid producing invalid HTML.
|
|
60
63
|
*/
|
|
61
|
-
tag: PropTypes.oneOf(layoutTags)
|
|
64
|
+
tag: PropTypes.oneOf(layoutTags),
|
|
65
|
+
/**
|
|
66
|
+
* Function to set carousel content background color when slide is being display
|
|
67
|
+
*/
|
|
68
|
+
setContentBackgroundColor: PropTypes.func
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
CarouselItem.displayName = 'Carousel.Item'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef, useRef } from 'react'
|
|
1
|
+
import React, { forwardRef, useEffect, useRef, useState } from 'react'
|
|
2
2
|
import { View } from 'react-native'
|
|
3
3
|
|
|
4
4
|
import PropTypes from 'prop-types'
|
|
@@ -7,10 +7,15 @@ import StackView from '../../StackView'
|
|
|
7
7
|
import { useCarousel } from '../CarouselContext'
|
|
8
8
|
import CarouselTabsPanelItem from './CarouselTabsPanelItem'
|
|
9
9
|
|
|
10
|
+
const selectTabPanelStyle = () => ({
|
|
11
|
+
backgroundColor: 'transparent'
|
|
12
|
+
})
|
|
13
|
+
|
|
10
14
|
const CarouselTabsPanel = forwardRef(({ items }, ref) => {
|
|
11
15
|
const { activeIndex, goTo } = useCarousel()
|
|
12
16
|
const nextFocusRef = useRef()
|
|
13
17
|
const firstTabRef = useRef()
|
|
18
|
+
const [isInverse, setIsInverse] = useState(false)
|
|
14
19
|
|
|
15
20
|
// TODO: figure out a better cross-brand way to specify subcomponent variants.
|
|
16
21
|
// For now, this picks an Allium variant, and does nothing in brands that lack it.
|
|
@@ -19,20 +24,16 @@ const CarouselTabsPanel = forwardRef(({ items }, ref) => {
|
|
|
19
24
|
|
|
20
25
|
const lastTabSelected = activeIndex === items.length - 1
|
|
21
26
|
|
|
27
|
+
// Get current select tab style
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
const [selectedVariantIsInverse] = items.filter((_, index) => index === activeIndex)
|
|
30
|
+
setIsInverse(selectedVariantIsInverse?.inverse)
|
|
31
|
+
}, [items, activeIndex])
|
|
32
|
+
|
|
22
33
|
return (
|
|
23
|
-
|
|
24
|
-
<View
|
|
25
|
-
focusable
|
|
26
|
-
accessible
|
|
27
|
-
onFocus={(event) => {
|
|
28
|
-
// When user forward-tabs into this section, focus the next tab; if they backwards-tab
|
|
29
|
-
// (shift-tab) back into the carousel content, don't interfere.
|
|
30
|
-
const previousWebFocus = event.relatedTarget
|
|
31
|
-
if (previousWebFocus !== firstTabRef.current) nextFocusRef.current.focus()
|
|
32
|
-
}}
|
|
33
|
-
/>
|
|
34
|
+
<View style={selectTabPanelStyle()}>
|
|
34
35
|
<StackView direction="row" space={3} divider={{ variant: dividerVariant }} ref={ref}>
|
|
35
|
-
{items.map(({ title, onPress, ...panelItemProps }, index) => {
|
|
36
|
+
{items.map(({ title, onPress, inverse, ...panelItemProps }, index) => {
|
|
36
37
|
const selected = index === activeIndex
|
|
37
38
|
const isNext = index === activeIndex + 1
|
|
38
39
|
|
|
@@ -51,6 +52,7 @@ const CarouselTabsPanel = forwardRef(({ items }, ref) => {
|
|
|
51
52
|
title={title}
|
|
52
53
|
selected={selected}
|
|
53
54
|
onPress={handlePress}
|
|
55
|
+
variant={{ inverse: isInverse }}
|
|
54
56
|
{...panelItemProps}
|
|
55
57
|
/>
|
|
56
58
|
)
|
|
@@ -58,12 +60,14 @@ const CarouselTabsPanel = forwardRef(({ items }, ref) => {
|
|
|
58
60
|
</StackView>
|
|
59
61
|
{/* TODO: integrate with skiplink, replace this with focusing skiplink target */}
|
|
60
62
|
<View focusable accessible ref={lastTabSelected ? nextFocusRef : null} />
|
|
61
|
-
|
|
63
|
+
</View>
|
|
62
64
|
)
|
|
63
65
|
})
|
|
64
66
|
CarouselTabsPanel.displayName = 'CarouselTabsPanel'
|
|
65
67
|
CarouselTabsPanel.propTypes = {
|
|
66
|
-
items: PropTypes.arrayOf(PropTypes.shape(CarouselTabsPanelItem.propTypes || {}))
|
|
68
|
+
items: PropTypes.arrayOf(PropTypes.shape(CarouselTabsPanelItem.propTypes || {})),
|
|
69
|
+
// Color defined by `Carousel.item` variant otherwise fallback to transparent
|
|
70
|
+
contentBackgroundColor: PropTypes.string
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
export default CarouselTabsPanel
|
package/src/FlexGrid/Col/Col.jsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import React, { forwardRef, useContext } from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import { Platform, StyleSheet
|
|
3
|
+
import { Platform, StyleSheet } from 'react-native'
|
|
4
4
|
import { viewports } from '@telus-uds/system-constants'
|
|
5
5
|
|
|
6
6
|
import GutterContext from '../providers/GutterContext'
|
|
7
7
|
import { useViewport } from '../../ViewportProvider'
|
|
8
8
|
import applyInheritance from '../helpers'
|
|
9
|
-
import { responsiveProps } from '../../utils'
|
|
9
|
+
import { responsiveProps, BaseView } from '../../utils'
|
|
10
10
|
|
|
11
11
|
const Col = forwardRef(
|
|
12
12
|
(
|
|
@@ -162,7 +162,7 @@ const Col = forwardRef(
|
|
|
162
162
|
xl: offsetsWithIheritance[4]
|
|
163
163
|
}
|
|
164
164
|
return (
|
|
165
|
-
<
|
|
165
|
+
<BaseView
|
|
166
166
|
ref={ref}
|
|
167
167
|
{...viewProps}
|
|
168
168
|
style={[
|
|
@@ -174,7 +174,7 @@ const Col = forwardRef(
|
|
|
174
174
|
]}
|
|
175
175
|
>
|
|
176
176
|
{children}
|
|
177
|
-
</
|
|
177
|
+
</BaseView>
|
|
178
178
|
)
|
|
179
179
|
}
|
|
180
180
|
)
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import {
|
|
3
|
+
import { StyleSheet } from 'react-native'
|
|
4
4
|
import { viewports } from '@telus-uds/system-constants'
|
|
5
|
-
|
|
6
|
-
import Row from './Row'
|
|
7
|
-
import Col from './Col'
|
|
8
|
-
import { useViewport } from '../ViewportProvider'
|
|
9
|
-
import GutterContext from './providers/GutterContext'
|
|
10
|
-
import applyInheritance from './helpers'
|
|
11
5
|
import {
|
|
12
6
|
a11yProps,
|
|
13
7
|
viewProps,
|
|
14
8
|
getA11yPropsFromHtmlTag,
|
|
15
9
|
layoutTags,
|
|
16
|
-
selectSystemProps
|
|
10
|
+
selectSystemProps,
|
|
11
|
+
BaseView
|
|
17
12
|
} from '../utils'
|
|
18
13
|
|
|
14
|
+
import Row from './Row'
|
|
15
|
+
import Col from './Col'
|
|
16
|
+
import { useViewport } from '../ViewportProvider'
|
|
17
|
+
import GutterContext from './providers/GutterContext'
|
|
18
|
+
import applyInheritance from './helpers'
|
|
19
|
+
|
|
19
20
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -75,7 +76,7 @@ const FlexGrid = forwardRef(
|
|
|
75
76
|
|
|
76
77
|
return (
|
|
77
78
|
<GutterContext.Provider value={gutter}>
|
|
78
|
-
<
|
|
79
|
+
<BaseView
|
|
79
80
|
ref={ref}
|
|
80
81
|
{...props}
|
|
81
82
|
style={[
|
|
@@ -84,7 +85,7 @@ const FlexGrid = forwardRef(
|
|
|
84
85
|
]}
|
|
85
86
|
>
|
|
86
87
|
{children}
|
|
87
|
-
</
|
|
88
|
+
</BaseView>
|
|
88
89
|
</GutterContext.Provider>
|
|
89
90
|
)
|
|
90
91
|
}
|
package/src/FlexGrid/Row/Row.jsx
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React, { forwardRef } from 'react'
|
|
2
2
|
import PropTypes from 'prop-types'
|
|
3
|
-
import {
|
|
3
|
+
import { StyleSheet } from 'react-native'
|
|
4
4
|
import { viewports } from '@telus-uds/system-constants'
|
|
5
5
|
|
|
6
6
|
import { useViewport } from '../../ViewportProvider'
|
|
7
7
|
import applyInheritance from '../helpers'
|
|
8
|
+
import { BaseView } from '../../utils'
|
|
8
9
|
|
|
9
10
|
const horizontalAlignStyles = (horizontalAlign) => {
|
|
10
11
|
switch (horizontalAlign) {
|
|
@@ -96,7 +97,7 @@ const Row = forwardRef(
|
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
return (
|
|
99
|
-
<
|
|
100
|
+
<BaseView
|
|
100
101
|
ref={ref}
|
|
101
102
|
{...rest}
|
|
102
103
|
style={[
|
|
@@ -111,7 +112,7 @@ const Row = forwardRef(
|
|
|
111
112
|
]}
|
|
112
113
|
>
|
|
113
114
|
{children}
|
|
114
|
-
</
|
|
115
|
+
</BaseView>
|
|
115
116
|
)
|
|
116
117
|
}
|
|
117
118
|
)
|
|
@@ -68,7 +68,9 @@ const IconButton = forwardRef(
|
|
|
68
68
|
...rest,
|
|
69
69
|
accessibilityRole
|
|
70
70
|
})
|
|
71
|
-
const handlePress =
|
|
71
|
+
const handlePress = () => {
|
|
72
|
+
linkProps.handleHref({ href, onPress })({ nativeEvent: { target: ref?.current?.id } })
|
|
73
|
+
}
|
|
72
74
|
|
|
73
75
|
const getTokens = useThemeTokensCallback('IconButton', tokens, variant)
|
|
74
76
|
const getOuterStyle = (pressableState) =>
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import React, { forwardRef, useState } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
|
|
4
|
+
import { useThemeTokensCallback } from '../ThemeProvider'
|
|
5
|
+
import {
|
|
6
|
+
containUniqueFields,
|
|
7
|
+
getTokensPropType,
|
|
8
|
+
getPressHandlersWithArgs,
|
|
9
|
+
selectTokens,
|
|
10
|
+
useCopy,
|
|
11
|
+
useMultipleInputValues,
|
|
12
|
+
variantProp
|
|
13
|
+
} from '../utils'
|
|
14
|
+
import dictionary from './dictionary'
|
|
15
|
+
|
|
16
|
+
import Box from '../Box'
|
|
17
|
+
import { Button, ButtonDropdown } from '../Button'
|
|
18
|
+
import { CheckboxGroup } from '../Checkbox'
|
|
19
|
+
import Divider from '../Divider'
|
|
20
|
+
import FlexGrid from '../FlexGrid'
|
|
21
|
+
import Modal from '../Modal'
|
|
22
|
+
import Spacer from '../Spacer'
|
|
23
|
+
import StackView from '../StackView'
|
|
24
|
+
import Typography from '../Typography'
|
|
25
|
+
import { TextButton } from '../Link'
|
|
26
|
+
|
|
27
|
+
const { Col, Row } = FlexGrid
|
|
28
|
+
|
|
29
|
+
const MultiSelectFilter = forwardRef(
|
|
30
|
+
(
|
|
31
|
+
{
|
|
32
|
+
label,
|
|
33
|
+
id = label,
|
|
34
|
+
variant,
|
|
35
|
+
tokens,
|
|
36
|
+
items = [],
|
|
37
|
+
values,
|
|
38
|
+
initialValues,
|
|
39
|
+
maxValues,
|
|
40
|
+
onChange,
|
|
41
|
+
copy = 'en',
|
|
42
|
+
readOnly = false,
|
|
43
|
+
inactive = false,
|
|
44
|
+
rowLimit = 12,
|
|
45
|
+
...rest
|
|
46
|
+
},
|
|
47
|
+
ref
|
|
48
|
+
) => {
|
|
49
|
+
const { currentValues, setValues } = useMultipleInputValues({
|
|
50
|
+
initialValues,
|
|
51
|
+
values,
|
|
52
|
+
maxValues,
|
|
53
|
+
onChange,
|
|
54
|
+
readOnly
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const getItemTokens = useThemeTokensCallback('ButtonDropdown', tokens, variant)
|
|
58
|
+
const getButtonTokens = (buttonState) => selectTokens('Button', getItemTokens(buttonState))
|
|
59
|
+
const getCopy = useCopy({ dictionary, copy })
|
|
60
|
+
|
|
61
|
+
const [isOpen, setIsOpen] = useState(false)
|
|
62
|
+
const [checkedIds, setCheckedIds] = useState(currentValues ?? [])
|
|
63
|
+
|
|
64
|
+
const colSize = items.length > rowLimit ? 2 : 1
|
|
65
|
+
const isSelected = currentValues.length > 0
|
|
66
|
+
|
|
67
|
+
const uniqueFields = ['id', 'label']
|
|
68
|
+
if (!containUniqueFields(items, uniqueFields)) {
|
|
69
|
+
throw new Error(`MultiSelectFilter items must have unique ${uniqueFields.join(', ')}`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Pass an object of relevant component state as first argument for any passed-in press handlers
|
|
73
|
+
const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])
|
|
74
|
+
|
|
75
|
+
const handleChange = (event) => {
|
|
76
|
+
if (pressHandlers.onPress) pressHandlers?.onPress(event)
|
|
77
|
+
setIsOpen(true)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const onApply = (e) => {
|
|
81
|
+
setValues(e)
|
|
82
|
+
setIsOpen(false)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<>
|
|
87
|
+
<Modal
|
|
88
|
+
isOpen={isOpen}
|
|
89
|
+
onClose={() => setIsOpen(false)}
|
|
90
|
+
variant={{ width: colSize > 1 ? 'size576' : 's' }}
|
|
91
|
+
>
|
|
92
|
+
<Row>
|
|
93
|
+
<Typography variant={{ size: 'h4' }}>
|
|
94
|
+
{getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label.toLowerCase())}
|
|
95
|
+
</Typography>
|
|
96
|
+
</Row>
|
|
97
|
+
<Spacer space={4} />
|
|
98
|
+
<Spacer space={1} />
|
|
99
|
+
<Box scroll={true}>
|
|
100
|
+
<Row distribute="between">
|
|
101
|
+
{[...Array(colSize).keys()].map((i) => (
|
|
102
|
+
<Col xs={12 / colSize} key={i}>
|
|
103
|
+
<CheckboxGroup
|
|
104
|
+
items={items.slice(i * rowLimit, (i + 1) * rowLimit)}
|
|
105
|
+
checkedIds={checkedIds}
|
|
106
|
+
onChange={(e) => setCheckedIds(e, i)}
|
|
107
|
+
/>
|
|
108
|
+
<Spacer size={4} />
|
|
109
|
+
</Col>
|
|
110
|
+
))}
|
|
111
|
+
</Row>
|
|
112
|
+
</Box>
|
|
113
|
+
<Divider
|
|
114
|
+
variant={{ width: 'full', color: 'E3E6E8', decorative: true, weight: 'thin' }}
|
|
115
|
+
space={4}
|
|
116
|
+
/>
|
|
117
|
+
<Row>
|
|
118
|
+
<StackView direction="row" space={3} tokens={{ alignItems: 'center' }}>
|
|
119
|
+
<Button
|
|
120
|
+
onPress={() => onApply(checkedIds)}
|
|
121
|
+
variant={{ size: 'small', priority: 'high' }}
|
|
122
|
+
>
|
|
123
|
+
{getCopy('applyButtonLabel')}
|
|
124
|
+
</Button>
|
|
125
|
+
<Box>
|
|
126
|
+
<TextButton onPress={() => setCheckedIds([])}>
|
|
127
|
+
{getCopy('clearButtonLabel')}
|
|
128
|
+
</TextButton>
|
|
129
|
+
</Box>
|
|
130
|
+
</StackView>
|
|
131
|
+
</Row>
|
|
132
|
+
</Modal>
|
|
133
|
+
<ButtonDropdown
|
|
134
|
+
ref={ref}
|
|
135
|
+
key={id}
|
|
136
|
+
{...pressHandlers}
|
|
137
|
+
value={isOpen}
|
|
138
|
+
selected={isSelected}
|
|
139
|
+
label={label}
|
|
140
|
+
onChange={handleChange}
|
|
141
|
+
tokens={getButtonTokens}
|
|
142
|
+
inactive={inactive}
|
|
143
|
+
/>
|
|
144
|
+
</>
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
MultiSelectFilter.displayName = 'MultiSelectFilter'
|
|
149
|
+
|
|
150
|
+
MultiSelectFilter.propTypes = {
|
|
151
|
+
/**
|
|
152
|
+
* The text displayed to the user in a ButtonDropdown.
|
|
153
|
+
*/
|
|
154
|
+
label: PropTypes.string.isRequired,
|
|
155
|
+
/**
|
|
156
|
+
* An optional unique string may be provided to identify the ButtonDropdown.
|
|
157
|
+
* If not provided, the label is used.
|
|
158
|
+
*/
|
|
159
|
+
id: PropTypes.string,
|
|
160
|
+
/**
|
|
161
|
+
* Sets the variant for ButtonDropdown element.
|
|
162
|
+
*/
|
|
163
|
+
variant: variantProp.propType,
|
|
164
|
+
/**
|
|
165
|
+
* Sets the tokens for ButtonDropdown element.
|
|
166
|
+
*/
|
|
167
|
+
tokens: getTokensPropType('ButtonDropdown'),
|
|
168
|
+
/**
|
|
169
|
+
* The options a user may select.
|
|
170
|
+
*/
|
|
171
|
+
items: PropTypes.arrayOf(
|
|
172
|
+
PropTypes.shape({
|
|
173
|
+
/**
|
|
174
|
+
* The text displayed to the user with a checkbox, describing this option.
|
|
175
|
+
*/
|
|
176
|
+
label: PropTypes.string.isRequired,
|
|
177
|
+
/**
|
|
178
|
+
* An optional unique string may be provided to identify this option.
|
|
179
|
+
* If not provided, the label is used.
|
|
180
|
+
*/
|
|
181
|
+
id: PropTypes.string
|
|
182
|
+
})
|
|
183
|
+
),
|
|
184
|
+
/**
|
|
185
|
+
* If the selected item(s) in the checkbox group(s) are to be controlled externally by
|
|
186
|
+
* a parent component, pass an array of strings as well as an `onChange` handler.
|
|
187
|
+
* Passing an array for "values" makes the MultiSelectFilter a "controlled" component that
|
|
188
|
+
* expects its state to be handled via `onChange` and so doesn't handle it itself.
|
|
189
|
+
*/
|
|
190
|
+
values: PropTypes.arrayOf(PropTypes.string),
|
|
191
|
+
/**
|
|
192
|
+
* If `values` is not passed, making the MultiSelectFilter an "uncontrolled" component
|
|
193
|
+
* managing its own selected state, a default set of selections may be provided.
|
|
194
|
+
* Changing the `initialValues` does not change the user's selections.
|
|
195
|
+
*/
|
|
196
|
+
initialValues: PropTypes.arrayOf(PropTypes.string),
|
|
197
|
+
/**
|
|
198
|
+
* If provided, sets a maximum number of items a user may select at once.
|
|
199
|
+
*/
|
|
200
|
+
maxValues: PropTypes.number,
|
|
201
|
+
/**
|
|
202
|
+
* If provided, this function is called when the current selection is changed
|
|
203
|
+
* and is passed an array of the `id`s of all currently selected `items`.
|
|
204
|
+
*/
|
|
205
|
+
onChange: PropTypes.func,
|
|
206
|
+
/**
|
|
207
|
+
* Select English or French copy for the accessible label.
|
|
208
|
+
*/
|
|
209
|
+
copy: PropTypes.oneOf(['en', 'fr']),
|
|
210
|
+
/**
|
|
211
|
+
* If true, the ButtonDropdown cannot be selected by the user and simply show their current state.
|
|
212
|
+
*/
|
|
213
|
+
readOnly: PropTypes.string,
|
|
214
|
+
/**
|
|
215
|
+
* If true, the MultiSelectFilter cannot be interacted with, ButtonDropdown is
|
|
216
|
+
* set as `disabled` and if the theme supports `inactive` appearances rules, these
|
|
217
|
+
* are applied.
|
|
218
|
+
*/
|
|
219
|
+
inactive: PropTypes.string,
|
|
220
|
+
/**
|
|
221
|
+
* Sets the maximum number of items in one column. If number of items are more
|
|
222
|
+
* than the `rowLimit`, they will be rendered in 2 columns.
|
|
223
|
+
*/
|
|
224
|
+
rowLimit: PropTypes.number
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export default MultiSelectFilter
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
en: {
|
|
3
|
+
filterByLabel: 'Filter by %{filterCategory}:',
|
|
4
|
+
applyButtonLabel: 'Apply',
|
|
5
|
+
clearButtonLabel: 'Clear'
|
|
6
|
+
},
|
|
7
|
+
fr: {
|
|
8
|
+
filterByLabel: 'Filtrer par %{filterCategory}:',
|
|
9
|
+
applyButtonLabel: 'Appliquer',
|
|
10
|
+
clearButtonLabel: 'Effacer'
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -13,8 +13,9 @@ import dictionary from './dictionary'
|
|
|
13
13
|
import useCopy from '../utils/useCopy'
|
|
14
14
|
|
|
15
15
|
// We need to drop the icon here since it gets rendered via children and not
|
|
16
|
-
// `ButtonBase` in order to tap into the state of the button
|
|
17
|
-
|
|
16
|
+
// `ButtonBase` in order to tap into the state of the button; `displayLabel` flag
|
|
17
|
+
// is also not needed
|
|
18
|
+
const selectButtonTokens = ({ icon: _, displayLabel: __, ...rest }) => selectTokens('Button', rest)
|
|
18
19
|
const selectIconTokens = ({ color, iconSize, iconDisplace }, direction) => {
|
|
19
20
|
return {
|
|
20
21
|
color,
|
|
@@ -36,13 +37,12 @@ const SideButton = forwardRef(
|
|
|
36
37
|
|
|
37
38
|
const getCopy = useCopy({ dictionary, copy })
|
|
38
39
|
|
|
39
|
-
const { icon } = getTokens(tokens, buttonVariant)
|
|
40
|
+
const { icon, displayLabel } = getTokens(tokens, buttonVariant)
|
|
40
41
|
|
|
41
42
|
const getButtonTokens = (buttonState) => selectButtonTokens(getTokens(buttonState))
|
|
42
43
|
const getIconTokens = (buttonState) => selectIconTokens(getTokens(buttonState), direction)
|
|
43
44
|
|
|
44
45
|
const label = direction === 'previous' ? getCopy('previousText') : getCopy('nextText')
|
|
45
|
-
const showLabel = viewport !== 'sm' && viewport !== 'xs'
|
|
46
46
|
|
|
47
47
|
const accessibilityLabel =
|
|
48
48
|
direction === 'previous' ? getCopy('previousLabel') : getCopy('nextLabel')
|
|
@@ -69,7 +69,7 @@ const SideButton = forwardRef(
|
|
|
69
69
|
iconPosition={directionToSide[direction]}
|
|
70
70
|
iconProps={iconProps}
|
|
71
71
|
>
|
|
72
|
-
{
|
|
72
|
+
{displayLabel && <Text style={textStyles}>{label}</Text>}
|
|
73
73
|
</IconText>
|
|
74
74
|
)
|
|
75
75
|
}}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { viewports } from '@telus-uds/system-constants'
|
|
4
|
+
import { useResponsiveProp } from '../utils'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Responsive conditionally renders children based on whether the viewport matches the provided
|
|
8
|
+
* min and max viewports.
|
|
9
|
+
*
|
|
10
|
+
* In SSR, like other viewport utilities, it treats the viewport as `xs` both in SSR itself and
|
|
11
|
+
* during first hydration on the client side; then if the viewport is not `xs`, it re-renders
|
|
12
|
+
* after hydration. This may cause a layout shift on devices other than the narrowest.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const Responsive = ({ min = 'xs', max, children }) => {
|
|
16
|
+
// Start returning children at the 'min' viewport or greater
|
|
17
|
+
const byViewports = { [min]: children }
|
|
18
|
+
if (max && max !== 'xl') {
|
|
19
|
+
// Stop returning children at the viewport one above 'max' or greater
|
|
20
|
+
const maxIndex = viewports.keys.indexOf(max)
|
|
21
|
+
const maxPlusOne = maxIndex >= 0 ? viewports.keys[maxIndex + 1] : null
|
|
22
|
+
if (maxPlusOne) byViewports[maxPlusOne] = null
|
|
23
|
+
}
|
|
24
|
+
return <>{useResponsiveProp(byViewports, null)}</>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Responsive.propTypes = {
|
|
28
|
+
min: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
|
|
29
|
+
max: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
|
|
30
|
+
children: PropTypes.node.isRequired
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default Responsive
|