@telus-uds/components-web 1.8.0 → 1.10.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 +41 -2
- package/lib/Autocomplete/Autocomplete.js +393 -0
- package/lib/Autocomplete/Loading.js +51 -0
- package/lib/Autocomplete/Suggestions.js +81 -0
- package/lib/Autocomplete/constants.js +19 -0
- package/lib/Autocomplete/dictionary.js +19 -0
- package/lib/Autocomplete/index.js +13 -0
- package/lib/Breadcrumbs/Breadcrumbs.js +8 -3
- package/lib/Callout/Callout.js +3 -0
- package/lib/Card/Card.js +180 -0
- package/lib/Card/CardContent.js +110 -0
- package/lib/Card/CardFooter.js +98 -0
- package/lib/Card/index.js +13 -0
- package/lib/Countdown/Countdown.js +189 -0
- package/lib/Countdown/Segment.js +111 -0
- package/lib/Countdown/constants.js +14 -0
- package/lib/Countdown/dictionary.js +29 -0
- package/lib/Countdown/index.js +13 -0
- package/lib/Countdown/types.js +39 -0
- package/lib/Countdown/useCountdown.js +40 -0
- package/lib/Footnote/Footnote.js +67 -24
- package/lib/Modal/ModalContent.js +11 -4
- package/lib/OptimizeImage/OptimizeImage.js +127 -0
- package/lib/OptimizeImage/index.js +13 -0
- package/lib/OptimizeImage/utils/getFallbackUrl.js +18 -0
- package/lib/OptimizeImage/utils/getOptimizedUrl.js +32 -0
- package/lib/OptimizeImage/utils/hasWebpSupport.js +38 -0
- package/lib/OptimizeImage/utils/index.js +31 -0
- package/lib/OptimizeImage/utils/isSvgUrl.js +10 -0
- package/lib/QuantitySelector/QuantitySelector.js +253 -0
- package/lib/QuantitySelector/dictionary.js +33 -0
- package/lib/QuantitySelector/index.js +13 -0
- package/lib/QuantitySelector/styles.js +40 -0
- package/lib/StoryCard/StoryCard.js +244 -0
- package/lib/StoryCard/index.js +13 -0
- package/lib/TermsAndConditions/ExpandCollapse.js +141 -0
- package/lib/TermsAndConditions/TermsAndConditions.js +221 -0
- package/lib/TermsAndConditions/dictionary.js +19 -0
- package/lib/TermsAndConditions/index.js +15 -0
- package/lib/Testimonial/Testimonial.js +226 -0
- package/lib/Testimonial/index.js +13 -0
- package/lib/Toast/Toast.js +15 -8
- package/lib/Video/ControlBar/ControlBar.js +315 -0
- package/lib/Video/ControlBar/Controls/VideoButton/VideoButton.js +91 -0
- package/lib/Video/ControlBar/Controls/VideoMenu/VideoMenu.js +186 -0
- package/lib/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.js +221 -0
- package/lib/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.js +213 -0
- package/lib/Video/MiddleControlButton/MiddleControlButton.js +89 -0
- package/lib/Video/Video.js +1072 -0
- package/lib/Video/index.js +13 -0
- package/lib/Video/videoText.js +62 -0
- package/lib/WebVideo/WebVideo.js +170 -0
- package/lib/WebVideo/index.js +13 -0
- package/lib/baseExports.js +0 -6
- package/lib/index.js +91 -1
- package/lib/shared/VideoSplash/SplashButton/SplashButton.js +102 -0
- package/lib/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.js +234 -0
- package/lib/shared/VideoSplash/VideoSplash.js +86 -0
- package/lib/shared/VideoSplash/helpers.js +38 -0
- package/lib/utils/index.js +8 -0
- package/lib-module/Autocomplete/Autocomplete.js +369 -0
- package/lib-module/Autocomplete/Loading.js +38 -0
- package/lib-module/Autocomplete/Suggestions.js +64 -0
- package/lib-module/Autocomplete/constants.js +5 -0
- package/lib-module/Autocomplete/dictionary.js +12 -0
- package/lib-module/Autocomplete/index.js +2 -0
- package/lib-module/Breadcrumbs/Breadcrumbs.js +8 -3
- package/lib-module/Callout/Callout.js +3 -0
- package/lib-module/Card/Card.js +158 -0
- package/lib-module/Card/CardContent.js +92 -0
- package/lib-module/Card/CardFooter.js +80 -0
- package/lib-module/Card/index.js +2 -0
- package/lib-module/Countdown/Countdown.js +165 -0
- package/lib-module/Countdown/Segment.js +94 -0
- package/lib-module/Countdown/constants.js +4 -0
- package/lib-module/Countdown/dictionary.js +22 -0
- package/lib-module/Countdown/index.js +2 -0
- package/lib-module/Countdown/types.js +23 -0
- package/lib-module/Countdown/useCountdown.js +32 -0
- package/lib-module/Footnote/Footnote.js +65 -24
- package/lib-module/Modal/ModalContent.js +10 -4
- package/lib-module/OptimizeImage/OptimizeImage.js +106 -0
- package/lib-module/OptimizeImage/index.js +2 -0
- package/lib-module/OptimizeImage/utils/getFallbackUrl.js +8 -0
- package/lib-module/OptimizeImage/utils/getOptimizedUrl.js +22 -0
- package/lib-module/OptimizeImage/utils/hasWebpSupport.js +32 -0
- package/lib-module/OptimizeImage/utils/index.js +4 -0
- package/lib-module/OptimizeImage/utils/isSvgUrl.js +3 -0
- package/lib-module/QuantitySelector/QuantitySelector.js +232 -0
- package/lib-module/QuantitySelector/dictionary.js +26 -0
- package/lib-module/QuantitySelector/index.js +2 -0
- package/lib-module/QuantitySelector/styles.js +21 -0
- package/lib-module/StoryCard/StoryCard.js +220 -0
- package/lib-module/StoryCard/index.js +2 -0
- package/lib-module/TermsAndConditions/ExpandCollapse.js +120 -0
- package/lib-module/TermsAndConditions/TermsAndConditions.js +193 -0
- package/lib-module/TermsAndConditions/dictionary.js +12 -0
- package/lib-module/TermsAndConditions/index.js +1 -0
- package/lib-module/Testimonial/Testimonial.js +204 -0
- package/lib-module/Testimonial/index.js +2 -0
- package/lib-module/Toast/Toast.js +15 -8
- package/lib-module/Video/ControlBar/ControlBar.js +292 -0
- package/lib-module/Video/ControlBar/Controls/VideoButton/VideoButton.js +74 -0
- package/lib-module/Video/ControlBar/Controls/VideoMenu/VideoMenu.js +167 -0
- package/lib-module/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.js +201 -0
- package/lib-module/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.js +193 -0
- package/lib-module/Video/MiddleControlButton/MiddleControlButton.js +72 -0
- package/lib-module/Video/Video.js +1042 -0
- package/lib-module/Video/index.js +2 -0
- package/lib-module/Video/videoText.js +55 -0
- package/lib-module/WebVideo/WebVideo.js +144 -0
- package/lib-module/WebVideo/index.js +2 -0
- package/lib-module/baseExports.js +1 -1
- package/lib-module/index.js +10 -0
- package/lib-module/shared/VideoSplash/SplashButton/SplashButton.js +85 -0
- package/lib-module/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.js +216 -0
- package/lib-module/shared/VideoSplash/VideoSplash.js +65 -0
- package/lib-module/shared/VideoSplash/helpers.js +23 -0
- package/lib-module/utils/index.js +2 -1
- package/package.json +7 -5
- package/src/Autocomplete/Autocomplete.jsx +354 -0
- package/src/Autocomplete/Loading.jsx +18 -0
- package/src/Autocomplete/Suggestions.jsx +52 -0
- package/src/Autocomplete/constants.js +6 -0
- package/src/Autocomplete/dictionary.js +12 -0
- package/src/Autocomplete/index.js +3 -0
- package/src/Breadcrumbs/Breadcrumbs.jsx +4 -3
- package/src/Callout/Callout.jsx +1 -1
- package/src/Card/Card.jsx +170 -0
- package/src/Card/CardContent.jsx +88 -0
- package/src/Card/CardFooter.jsx +70 -0
- package/src/Card/index.js +3 -0
- package/src/Countdown/Countdown.jsx +144 -0
- package/src/Countdown/Segment.jsx +69 -0
- package/src/Countdown/constants.js +4 -0
- package/src/Countdown/dictionary.js +22 -0
- package/src/Countdown/index.js +3 -0
- package/src/Countdown/types.js +23 -0
- package/src/Countdown/useCountdown.js +34 -0
- package/src/Footnote/Footnote.jsx +73 -23
- package/src/Modal/ModalContent.jsx +8 -4
- package/src/OptimizeImage/OptimizeImage.jsx +131 -0
- package/src/OptimizeImage/index.js +3 -0
- package/src/OptimizeImage/utils/getFallbackUrl.js +9 -0
- package/src/OptimizeImage/utils/getOptimizedUrl.js +30 -0
- package/src/OptimizeImage/utils/hasWebpSupport.js +33 -0
- package/src/OptimizeImage/utils/index.js +5 -0
- package/src/OptimizeImage/utils/isSvgUrl.js +3 -0
- package/src/QuantitySelector/QuantitySelector.jsx +245 -0
- package/src/QuantitySelector/dictionary.js +27 -0
- package/src/QuantitySelector/index.js +3 -0
- package/src/QuantitySelector/styles.js +83 -0
- package/src/StoryCard/StoryCard.jsx +198 -0
- package/src/StoryCard/index.js +3 -0
- package/src/TermsAndConditions/ExpandCollapse.jsx +106 -0
- package/src/TermsAndConditions/TermsAndConditions.jsx +161 -0
- package/src/TermsAndConditions/dictionary.js +12 -0
- package/src/TermsAndConditions/index.js +1 -0
- package/src/Testimonial/Testimonial.jsx +169 -0
- package/src/Testimonial/index.js +3 -0
- package/src/Toast/Toast.jsx +12 -5
- package/src/Video/ControlBar/ControlBar.jsx +261 -0
- package/src/Video/ControlBar/Controls/VideoButton/VideoButton.jsx +61 -0
- package/src/Video/ControlBar/Controls/VideoMenu/VideoMenu.jsx +159 -0
- package/src/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.jsx +185 -0
- package/src/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.jsx +184 -0
- package/src/Video/MiddleControlButton/MiddleControlButton.jsx +64 -0
- package/src/Video/Video.jsx +988 -0
- package/src/Video/index.js +3 -0
- package/src/Video/videoText.js +58 -0
- package/src/WebVideo/WebVideo.jsx +131 -0
- package/src/WebVideo/index.js +3 -0
- package/src/baseExports.js +0 -1
- package/src/index.js +10 -0
- package/src/shared/VideoSplash/SplashButton/SplashButton.jsx +64 -0
- package/src/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.jsx +128 -0
- package/src/shared/VideoSplash/VideoSplash.jsx +50 -0
- package/src/shared/VideoSplash/helpers.js +27 -0
- package/src/utils/index.js +10 -1
- package/types/Autocomplete.d.ts +32 -0
- package/types/Card.d.ts +45 -0
- package/types/ControlBar.d.ts +59 -0
- package/types/MiddleControlButton.d.ts +15 -0
- package/types/Video.d.ts +39 -0
- package/types/VideoButton.d.ts +14 -0
- package/types/VideoMenu.d.ts +16 -0
- package/types/VideoProgressBar.d.ts +17 -0
- package/types/VolumeSlider.d.ts +20 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { selectSystemProps } from '@telus-uds/components-base'
|
|
4
|
+
import ResponsiveImage from '../ResponsiveImage'
|
|
5
|
+
import { hasWebpSupport, getOptimizedUrl, getFallbackUrl } from './utils'
|
|
6
|
+
import { htmlAttrs } from '../utils'
|
|
7
|
+
|
|
8
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
|
|
9
|
+
|
|
10
|
+
const OptimizeImage = ({
|
|
11
|
+
contentfulAssetUrl,
|
|
12
|
+
alt,
|
|
13
|
+
quality = 80,
|
|
14
|
+
xs = 320,
|
|
15
|
+
sm = 576,
|
|
16
|
+
md = 768,
|
|
17
|
+
lg = 992,
|
|
18
|
+
xl = 1200,
|
|
19
|
+
sizeByHeight = false,
|
|
20
|
+
disableRetina = false,
|
|
21
|
+
...rest
|
|
22
|
+
}) => {
|
|
23
|
+
const [imgUrls, setImgUrls] = useState()
|
|
24
|
+
|
|
25
|
+
// `useHeight` is a deprecated TDS prop, replaced by `sizeByHeight`
|
|
26
|
+
const dimension = sizeByHeight || rest.useHeight ? 'h' : 'w'
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
// Currently not all browsers support webP
|
|
30
|
+
hasWebpSupport().then((supportsWebp) => {
|
|
31
|
+
setImgUrls({
|
|
32
|
+
xsSrc: getOptimizedUrl(
|
|
33
|
+
contentfulAssetUrl,
|
|
34
|
+
dimension,
|
|
35
|
+
xs,
|
|
36
|
+
quality,
|
|
37
|
+
disableRetina,
|
|
38
|
+
supportsWebp
|
|
39
|
+
),
|
|
40
|
+
smSrc: getOptimizedUrl(
|
|
41
|
+
contentfulAssetUrl,
|
|
42
|
+
dimension,
|
|
43
|
+
sm,
|
|
44
|
+
quality,
|
|
45
|
+
disableRetina,
|
|
46
|
+
supportsWebp
|
|
47
|
+
),
|
|
48
|
+
mdSrc: getOptimizedUrl(
|
|
49
|
+
contentfulAssetUrl,
|
|
50
|
+
dimension,
|
|
51
|
+
md,
|
|
52
|
+
quality,
|
|
53
|
+
disableRetina,
|
|
54
|
+
supportsWebp
|
|
55
|
+
),
|
|
56
|
+
lgSrc: getOptimizedUrl(
|
|
57
|
+
contentfulAssetUrl,
|
|
58
|
+
dimension,
|
|
59
|
+
lg,
|
|
60
|
+
quality,
|
|
61
|
+
disableRetina,
|
|
62
|
+
supportsWebp
|
|
63
|
+
),
|
|
64
|
+
xlSrc: getOptimizedUrl(
|
|
65
|
+
contentfulAssetUrl,
|
|
66
|
+
dimension,
|
|
67
|
+
xl,
|
|
68
|
+
quality,
|
|
69
|
+
disableRetina,
|
|
70
|
+
supportsWebp
|
|
71
|
+
),
|
|
72
|
+
fallbackSrc: getFallbackUrl(contentfulAssetUrl, xl, quality)
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
}, [contentfulAssetUrl, dimension, disableRetina, lg, md, quality, sm, xl, xs])
|
|
76
|
+
|
|
77
|
+
if (!imgUrls) return null
|
|
78
|
+
return <ResponsiveImage {...imgUrls} alt={alt} {...selectProps(rest)} />
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
OptimizeImage.propTypes = {
|
|
82
|
+
...selectedSystemPropTypes,
|
|
83
|
+
/**
|
|
84
|
+
* The source to load the image. Only contentful image urls are supported. See https://www.contentful.com/developers/docs/references/images-api/ for details.
|
|
85
|
+
*/
|
|
86
|
+
contentfulAssetUrl: PropTypes.string.isRequired,
|
|
87
|
+
/**
|
|
88
|
+
* Alternative text to display if image cannot be loaded or a screen reader is used.
|
|
89
|
+
*/
|
|
90
|
+
alt: PropTypes.string.isRequired,
|
|
91
|
+
/**
|
|
92
|
+
* Customize quality as a percentage between 1 and 100.
|
|
93
|
+
*/
|
|
94
|
+
quality: PropTypes.number,
|
|
95
|
+
/**
|
|
96
|
+
* Customize width for xs screen size in px, this may affect the quality of the image.
|
|
97
|
+
*/
|
|
98
|
+
xs: PropTypes.number,
|
|
99
|
+
/**
|
|
100
|
+
* Customize width for sm screen size in px, this may affect the quality of the image.
|
|
101
|
+
*/
|
|
102
|
+
sm: PropTypes.number,
|
|
103
|
+
/**
|
|
104
|
+
* Customize width for md screen size in px, this may affect the quality of the image.
|
|
105
|
+
*/
|
|
106
|
+
md: PropTypes.number,
|
|
107
|
+
/**
|
|
108
|
+
* Customize width for lg screen size in px, this may affect the quality of the image.
|
|
109
|
+
*/
|
|
110
|
+
lg: PropTypes.number,
|
|
111
|
+
/**
|
|
112
|
+
* Customize width for xl screen size in px, this may affect the quality of the image.
|
|
113
|
+
*/
|
|
114
|
+
xl: PropTypes.number,
|
|
115
|
+
/**
|
|
116
|
+
* Switches size dimension to height, default is false
|
|
117
|
+
*/
|
|
118
|
+
sizeByHeight: PropTypes.bool,
|
|
119
|
+
/**
|
|
120
|
+
* Turns off retina display functionality
|
|
121
|
+
*/
|
|
122
|
+
disableRetina: PropTypes.bool,
|
|
123
|
+
/**
|
|
124
|
+
* Loading strategy.
|
|
125
|
+
* @default 'eager'
|
|
126
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading
|
|
127
|
+
*/
|
|
128
|
+
loading: PropTypes.oneOf(['eager', 'lazy'])
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export default OptimizeImage
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import isSvgUrl from './isSvgUrl'
|
|
2
|
+
|
|
3
|
+
export default function getOptimizedUrl(
|
|
4
|
+
url,
|
|
5
|
+
dimension,
|
|
6
|
+
size,
|
|
7
|
+
quality,
|
|
8
|
+
disableRetina,
|
|
9
|
+
supportsWebp
|
|
10
|
+
) {
|
|
11
|
+
if (!isSvgUrl(url)) {
|
|
12
|
+
let format = ''
|
|
13
|
+
|
|
14
|
+
if (supportsWebp) {
|
|
15
|
+
format = 'fm=webp'
|
|
16
|
+
} else if (url.match(/\.jpe?g$/i)) {
|
|
17
|
+
format = 'fm=jpg&fl=progressive'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let optimizedUrl = `${url}?${dimension}=${size}&q=${quality}&${format}`
|
|
21
|
+
|
|
22
|
+
if (!disableRetina) {
|
|
23
|
+
optimizedUrl += `, ${url}?${dimension}=${size * 2}&q=${quality}&${format} 2x`
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return optimizedUrl
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return url
|
|
30
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
let promise
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Taken directly from Google developers guide on how to detect browser support for WebP.
|
|
5
|
+
*
|
|
6
|
+
* @see https://developers.google.com/speed/webp/faq#in_your_own_javascript
|
|
7
|
+
* @return {Promise<boolean>}
|
|
8
|
+
*/
|
|
9
|
+
export default function hasWebpSupport() {
|
|
10
|
+
// cache the result, so that this function runs only once
|
|
11
|
+
if (!promise) {
|
|
12
|
+
promise = new Promise((resolve) => {
|
|
13
|
+
// basic support. other test forms exist for lossless, alpha, and animation types.
|
|
14
|
+
// check google guide if data strings are needed
|
|
15
|
+
const lossy = 'UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'
|
|
16
|
+
|
|
17
|
+
const img = document.createElement('img')
|
|
18
|
+
|
|
19
|
+
img.onload = function onLoad() {
|
|
20
|
+
const result = img.width > 0 && img.height > 0
|
|
21
|
+
|
|
22
|
+
resolve(result)
|
|
23
|
+
}
|
|
24
|
+
img.onerror = function onError() {
|
|
25
|
+
resolve(false)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
img.src = `data:image/webp;base64,${lossy}`
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return promise
|
|
33
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import {
|
|
4
|
+
getThemeTokens,
|
|
5
|
+
Box,
|
|
6
|
+
TextInput,
|
|
7
|
+
Spacer,
|
|
8
|
+
Feedback,
|
|
9
|
+
InputLabel,
|
|
10
|
+
useInputValue,
|
|
11
|
+
useCopy,
|
|
12
|
+
useThemeTokens,
|
|
13
|
+
useTheme
|
|
14
|
+
} from '@telus-uds/components-base'
|
|
15
|
+
|
|
16
|
+
import IconButton from '../IconButton'
|
|
17
|
+
import { InputField, InputWrapper, LeftButtonWrapper, RightButtonWrapper } from './styles'
|
|
18
|
+
import defaultDictionary from './dictionary'
|
|
19
|
+
|
|
20
|
+
const { isNaN } = Number
|
|
21
|
+
|
|
22
|
+
const isNumber = (value) => {
|
|
23
|
+
return typeof value === 'number' && !isNaN(value)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const QuantitySelector = ({
|
|
27
|
+
id,
|
|
28
|
+
minNumber,
|
|
29
|
+
maxNumber,
|
|
30
|
+
defaultValue,
|
|
31
|
+
label,
|
|
32
|
+
hint,
|
|
33
|
+
hintPosition,
|
|
34
|
+
tooltip,
|
|
35
|
+
onChange,
|
|
36
|
+
dictionary = defaultDictionary,
|
|
37
|
+
accessibilityLabel,
|
|
38
|
+
copy,
|
|
39
|
+
variant,
|
|
40
|
+
tokens,
|
|
41
|
+
testID
|
|
42
|
+
}) => {
|
|
43
|
+
const {
|
|
44
|
+
components: { QuantitySelector: componentTheme }
|
|
45
|
+
} = useTheme()
|
|
46
|
+
|
|
47
|
+
const { leftIcon, rightIcon, padding } = useThemeTokens('QuantitySelector', tokens, variant)
|
|
48
|
+
const [error, setError] = useState('')
|
|
49
|
+
const { alternative } = variant
|
|
50
|
+
const getCopy = useCopy({ dictionary, copy })
|
|
51
|
+
|
|
52
|
+
const getValidatedNumber = (numberToEvaluate) => {
|
|
53
|
+
if (isNaN(numberToEvaluate)) return null
|
|
54
|
+
if (isNumber(minNumber) && numberToEvaluate < minNumber)
|
|
55
|
+
throw getCopy('errors').numberIsTooSmall(minNumber)
|
|
56
|
+
if (isNumber(maxNumber) && numberToEvaluate > maxNumber)
|
|
57
|
+
throw getCopy('errors').numberIsTooBig(maxNumber)
|
|
58
|
+
return numberToEvaluate
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const initialValue = getValidatedNumber(defaultValue)
|
|
62
|
+
|
|
63
|
+
const { currentValue: number, setValue: setNumber } = useInputValue({
|
|
64
|
+
value: undefined,
|
|
65
|
+
initialValue,
|
|
66
|
+
onChange
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const isDecreaseEnabled = !isNumber(minNumber) || number > minNumber
|
|
70
|
+
const isIncreaseEnabled = !isNumber(maxNumber) || number < maxNumber
|
|
71
|
+
const inputValue = isNumber(number) ? number.toString() : ''
|
|
72
|
+
|
|
73
|
+
const updateNumber = (newNumber, originalInputEvent) => {
|
|
74
|
+
try {
|
|
75
|
+
const validatedNumber = getValidatedNumber(newNumber)
|
|
76
|
+
setNumber(validatedNumber, originalInputEvent)
|
|
77
|
+
setError('')
|
|
78
|
+
} catch (e) {
|
|
79
|
+
setError(e)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const inputChangeHandler = (textInputValue, event) => {
|
|
84
|
+
if (textInputValue === '') setNumber(null)
|
|
85
|
+
const numberFromTextInput = Number(textInputValue)
|
|
86
|
+
if (isNumber(numberFromTextInput)) {
|
|
87
|
+
updateNumber(numberFromTextInput, event)
|
|
88
|
+
} else {
|
|
89
|
+
setError(getCopy('errors').invalidCharacters)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const renderLabel = () =>
|
|
94
|
+
label ? (
|
|
95
|
+
<InputLabel
|
|
96
|
+
forId={id}
|
|
97
|
+
label={label}
|
|
98
|
+
hint={hint}
|
|
99
|
+
hintPosition={hintPosition}
|
|
100
|
+
tooltip={tooltip}
|
|
101
|
+
/>
|
|
102
|
+
) : null
|
|
103
|
+
|
|
104
|
+
const renderTextInput = () => (
|
|
105
|
+
<TextInput
|
|
106
|
+
nativeID={id}
|
|
107
|
+
value={inputValue}
|
|
108
|
+
tokens={(textInputState) => {
|
|
109
|
+
const { inputBorderColor, inputBackgroundColor } = getThemeTokens(
|
|
110
|
+
componentTheme,
|
|
111
|
+
tokens,
|
|
112
|
+
variant,
|
|
113
|
+
textInputState
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
borderRadius: 0,
|
|
118
|
+
margin: 0,
|
|
119
|
+
padding: 0,
|
|
120
|
+
borderColor: inputBorderColor,
|
|
121
|
+
backgroundColor: inputBackgroundColor
|
|
122
|
+
}
|
|
123
|
+
}}
|
|
124
|
+
onChange={inputChangeHandler}
|
|
125
|
+
// Using title as an accessibility workaround
|
|
126
|
+
// given that accessibilityLabel is not available
|
|
127
|
+
keyboardType="numeric"
|
|
128
|
+
accessibilityLabel={accessibilityLabel ?? getCopy('accessibility').a11yLabel}
|
|
129
|
+
accessibilityRole="spinbutton"
|
|
130
|
+
accessibilityValueMax={maxNumber}
|
|
131
|
+
accessibilityValueMin={minNumber}
|
|
132
|
+
accessibilityValueNow={number}
|
|
133
|
+
/>
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
const getButtonTokens = (isEnabled) => (buttonState) => {
|
|
137
|
+
const disabled = !isEnabled
|
|
138
|
+
const { ...buttonTokens } = getThemeTokens(componentTheme, tokens, variant, {
|
|
139
|
+
...buttonState,
|
|
140
|
+
disabled
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
...buttonTokens,
|
|
145
|
+
outerBorderGap: 0,
|
|
146
|
+
padding
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const renderLeftButton = () => {
|
|
151
|
+
return (
|
|
152
|
+
<IconButton
|
|
153
|
+
icon={leftIcon}
|
|
154
|
+
tokens={getButtonTokens(isDecreaseEnabled)}
|
|
155
|
+
onPress={(event) => updateNumber(number - 1, event)}
|
|
156
|
+
onDoubleClick={(event) => updateNumber(number - 1, event)}
|
|
157
|
+
accessibilityLabel={getCopy('accessibility').decreaseButton}
|
|
158
|
+
accessibilityDisabled={!isDecreaseEnabled}
|
|
159
|
+
/>
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const renderRightButton = () => (
|
|
164
|
+
<IconButton
|
|
165
|
+
icon={rightIcon}
|
|
166
|
+
tokens={getButtonTokens(isIncreaseEnabled)}
|
|
167
|
+
onPress={() => updateNumber(number + 1)}
|
|
168
|
+
onDoubleClick={() => updateNumber(number + 1)}
|
|
169
|
+
accessibilityLabel={getCopy('accessibility').increaseButton}
|
|
170
|
+
accessibilityDisabled={!isIncreaseEnabled}
|
|
171
|
+
/>
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<Box space={2} testID={testID}>
|
|
176
|
+
{renderLabel()}
|
|
177
|
+
<Spacer space={2} />
|
|
178
|
+
<InputWrapper>
|
|
179
|
+
<InputField className={`${alternative ? 'alternative' : ''}`}>
|
|
180
|
+
{renderTextInput()}
|
|
181
|
+
</InputField>
|
|
182
|
+
<RightButtonWrapper className={`${alternative ? 'alternative' : ''}`}>
|
|
183
|
+
{renderRightButton()}
|
|
184
|
+
</RightButtonWrapper>
|
|
185
|
+
<LeftButtonWrapper className={`${alternative ? 'alternative' : ''}`}>
|
|
186
|
+
{renderLeftButton()}
|
|
187
|
+
</LeftButtonWrapper>
|
|
188
|
+
</InputWrapper>
|
|
189
|
+
{error ? (
|
|
190
|
+
<Box vertical={2}>
|
|
191
|
+
<Feedback validation="error">{error}</Feedback>
|
|
192
|
+
</Box>
|
|
193
|
+
) : null}
|
|
194
|
+
</Box>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// If a language dictionary entry is provided, it must contain every key
|
|
199
|
+
const dictionaryContentShape = PropTypes.shape({
|
|
200
|
+
accessibility: PropTypes.shape({
|
|
201
|
+
a11yLabel: PropTypes.string.isRequired,
|
|
202
|
+
increaseButton: PropTypes.string.isRequired,
|
|
203
|
+
decreaseButton: PropTypes.string.isRequired
|
|
204
|
+
}).isRequired,
|
|
205
|
+
errors: PropTypes.shape({
|
|
206
|
+
numberIsTooSmall: PropTypes.func.isRequired,
|
|
207
|
+
numberIsTooBig: PropTypes.func.isRequired,
|
|
208
|
+
invalidCharacters: PropTypes.string.isRequired
|
|
209
|
+
}).isRequired
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
QuantitySelector.propTypes = {
|
|
213
|
+
id: PropTypes.string,
|
|
214
|
+
minNumber: PropTypes.number,
|
|
215
|
+
maxNumber: PropTypes.number,
|
|
216
|
+
onChange: PropTypes.func,
|
|
217
|
+
defaultValue: PropTypes.number,
|
|
218
|
+
label: PropTypes.string,
|
|
219
|
+
hint: PropTypes.string,
|
|
220
|
+
hintPosition: PropTypes.oneOf(['inline', 'below']),
|
|
221
|
+
tooltip: PropTypes.string,
|
|
222
|
+
accessibilityLabel: PropTypes.string,
|
|
223
|
+
dictionary: PropTypes.shape({
|
|
224
|
+
en: dictionaryContentShape,
|
|
225
|
+
fr: dictionaryContentShape
|
|
226
|
+
}),
|
|
227
|
+
copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr'])]),
|
|
228
|
+
variant: PropTypes.exact({
|
|
229
|
+
alternative: PropTypes.bool
|
|
230
|
+
}),
|
|
231
|
+
tokens: PropTypes.oneOf([PropTypes.object, PropTypes.func]),
|
|
232
|
+
testID: PropTypes.string
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
QuantitySelector.defaultProps = {
|
|
236
|
+
id: 'quantity-selector',
|
|
237
|
+
hintPosition: 'inline',
|
|
238
|
+
copy: 'en',
|
|
239
|
+
variant: {
|
|
240
|
+
alternative: false
|
|
241
|
+
},
|
|
242
|
+
testID: 'quantity-selector-testid'
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default QuantitySelector
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
en: {
|
|
3
|
+
accessibility: {
|
|
4
|
+
a11yLabel: 'Quantity Selector control',
|
|
5
|
+
increaseButton: 'Increase quantity button',
|
|
6
|
+
decreaseButton: 'Decrease quantity button'
|
|
7
|
+
},
|
|
8
|
+
errors: {
|
|
9
|
+
numberIsTooSmall: (minNumber) => `Number must be equal or greater than ${minNumber}`,
|
|
10
|
+
numberIsTooBig: (maxNumber) => `Number must be equal or less than ${maxNumber}`,
|
|
11
|
+
invalidCharacters:
|
|
12
|
+
'This control does not allow alphabetical or symbol characters. Try using numbers only'
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
fr: {
|
|
16
|
+
accessibility: {
|
|
17
|
+
a11yLabel: 'Contrôleur de sélection de nombre',
|
|
18
|
+
increaseButton: `Bouton d'augmentation de quantité`,
|
|
19
|
+
decreaseButton: `Bouton de diminution de quantité`
|
|
20
|
+
},
|
|
21
|
+
errors: {
|
|
22
|
+
numberIsTooSmall: (minNumber) => `Le nombre doit être plus grand ou égal que ${minNumber}`,
|
|
23
|
+
numberIsTooBig: (maxNumber) => `Le nombre doit être plus petit ou égal que ${maxNumber}`,
|
|
24
|
+
invalidCharacters: `Ce champ ne supporte pas les caractères alphabétiques ni les symboles. Essayez d'utiliser des chiffres uniquement`
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import styled from 'styled-components'
|
|
2
|
+
|
|
3
|
+
export const InputField = styled.div`
|
|
4
|
+
order: 2;
|
|
5
|
+
min-width: 3.5rem;
|
|
6
|
+
max-width: 5 rem;
|
|
7
|
+
height: 28px;
|
|
8
|
+
padding: 0;
|
|
9
|
+
width: 64px;
|
|
10
|
+
text-align: center;
|
|
11
|
+
z-index: 10;
|
|
12
|
+
input {
|
|
13
|
+
text-align: center;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&.alternative {
|
|
17
|
+
input {
|
|
18
|
+
height: 40px;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
const ButtonWrapper = styled.div`
|
|
24
|
+
&.alternative {
|
|
25
|
+
div[role='button'] {
|
|
26
|
+
height: 42px;
|
|
27
|
+
> div {
|
|
28
|
+
height: 40px;
|
|
29
|
+
> div {
|
|
30
|
+
padding: 12px 16px;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
`
|
|
36
|
+
|
|
37
|
+
export const LeftButtonWrapper = styled(ButtonWrapper)`
|
|
38
|
+
order: 0;
|
|
39
|
+
div[role='button'] {
|
|
40
|
+
border-radius: 4px 0px 0px 4px !important;
|
|
41
|
+
> div {
|
|
42
|
+
border-right: none;
|
|
43
|
+
border-radius: 4px 0px 0px 4px !important;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
&.alternative {
|
|
48
|
+
div[role='button'] {
|
|
49
|
+
border-radius: 36px 0px 0px 36px !important;
|
|
50
|
+
> div {
|
|
51
|
+
border-radius: 24px 0px 0px 24px !important;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
`
|
|
56
|
+
|
|
57
|
+
export const RightButtonWrapper = styled(ButtonWrapper)`
|
|
58
|
+
order: 3;
|
|
59
|
+
div[role='button'] {
|
|
60
|
+
border-radius: 0px 4px 4px 0px !important;
|
|
61
|
+
> div {
|
|
62
|
+
border-left: none;
|
|
63
|
+
border-radius: 0px 4px 4px 0px !important;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&.alternative {
|
|
68
|
+
div[role='button'] {
|
|
69
|
+
border-radius: 0px 36px 36px 0px !important;
|
|
70
|
+
> div {
|
|
71
|
+
border-radius: 0px 36px 36px 0px !important;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
`
|
|
76
|
+
|
|
77
|
+
export const InputWrapper = styled.div`
|
|
78
|
+
textalign: start;
|
|
79
|
+
display: flex;
|
|
80
|
+
flexdirection: row;
|
|
81
|
+
flexwrap: nowrap;
|
|
82
|
+
justifycontent: center;
|
|
83
|
+
`
|