@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,88 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import {
|
|
4
|
+
getTokensPropType,
|
|
5
|
+
selectSystemProps,
|
|
6
|
+
useThemeTokens,
|
|
7
|
+
useViewport,
|
|
8
|
+
variantProp
|
|
9
|
+
} from '@telus-uds/components-base'
|
|
10
|
+
import styled from 'styled-components'
|
|
11
|
+
import { htmlAttrs } from '../utils'
|
|
12
|
+
|
|
13
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
|
|
14
|
+
|
|
15
|
+
const CardContentContainer = styled.div(
|
|
16
|
+
({
|
|
17
|
+
backgroundColor,
|
|
18
|
+
borderRadius,
|
|
19
|
+
paddingBottom,
|
|
20
|
+
paddingLeft,
|
|
21
|
+
paddingRight,
|
|
22
|
+
paddingTop,
|
|
23
|
+
withFooter,
|
|
24
|
+
contentAlignItem: alignItem,
|
|
25
|
+
contentFlexGrow: flexGrow,
|
|
26
|
+
contentFlexShrink: flexShrink,
|
|
27
|
+
contentJustifyContent: justifyContent
|
|
28
|
+
}) => ({
|
|
29
|
+
backgroundColor,
|
|
30
|
+
// We need to make sure to have sharp corners on the bottom
|
|
31
|
+
// if the card has a footer
|
|
32
|
+
borderBottomLeftRadius: withFooter ? 0 : borderRadius,
|
|
33
|
+
borderBottomRightRadius: withFooter ? 0 : borderRadius,
|
|
34
|
+
borderTopLeftRadius: borderRadius,
|
|
35
|
+
borderTopRightRadius: borderRadius,
|
|
36
|
+
paddingBottom,
|
|
37
|
+
paddingLeft,
|
|
38
|
+
paddingRight,
|
|
39
|
+
paddingTop,
|
|
40
|
+
display: 'flex',
|
|
41
|
+
flexDirection: 'column',
|
|
42
|
+
alignItem,
|
|
43
|
+
flexGrow,
|
|
44
|
+
flexShrink,
|
|
45
|
+
justifyContent
|
|
46
|
+
})
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Card content, applying the card tokens as per the theme used.
|
|
51
|
+
*/
|
|
52
|
+
const CardContent = ({ children, flexContent, tokens, variant, withFooter = false, ...rest }) => {
|
|
53
|
+
const viewport = useViewport()
|
|
54
|
+
const themeTokens = useThemeTokens('Card', tokens, variant, { viewport })
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<CardContentContainer
|
|
58
|
+
{...themeTokens}
|
|
59
|
+
flexContent={flexContent}
|
|
60
|
+
withFooter={withFooter}
|
|
61
|
+
{...selectProps(rest)}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
</CardContentContainer>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
CardContent.propTypes = {
|
|
69
|
+
...selectedSystemPropTypes,
|
|
70
|
+
/**
|
|
71
|
+
* Card section content.
|
|
72
|
+
*/
|
|
73
|
+
children: PropTypes.node,
|
|
74
|
+
/**
|
|
75
|
+
* Card tokens.
|
|
76
|
+
*/
|
|
77
|
+
tokens: getTokensPropType('Card'),
|
|
78
|
+
/**
|
|
79
|
+
* Card variant.
|
|
80
|
+
*/
|
|
81
|
+
variant: variantProp.propType,
|
|
82
|
+
/**
|
|
83
|
+
* Whether the card has a footer.
|
|
84
|
+
*/
|
|
85
|
+
withFooter: PropTypes.bool
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export default CardContent
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import {
|
|
4
|
+
getTokensPropType,
|
|
5
|
+
paddingProp,
|
|
6
|
+
selectSystemProps,
|
|
7
|
+
useThemeTokens,
|
|
8
|
+
useViewport,
|
|
9
|
+
variantProp
|
|
10
|
+
} from '@telus-uds/components-base'
|
|
11
|
+
import styled from 'styled-components'
|
|
12
|
+
import { htmlAttrs } from '../utils'
|
|
13
|
+
|
|
14
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
|
|
15
|
+
|
|
16
|
+
const CardFooterContainer = styled.div(
|
|
17
|
+
({ backgroundColor, borderRadius, paddingBottom, paddingLeft, paddingRight, paddingTop }) => ({
|
|
18
|
+
backgroundColor,
|
|
19
|
+
borderBottomLeftRadius: borderRadius,
|
|
20
|
+
borderBottomRightRadius: borderRadius,
|
|
21
|
+
// @todo circle back to the following non-standard value to
|
|
22
|
+
// see if it can be integrated into the palette
|
|
23
|
+
boxShadow: 'inset 0px 1px 3px rgba(0, 0, 0, 0.05)',
|
|
24
|
+
paddingBottom,
|
|
25
|
+
paddingLeft,
|
|
26
|
+
paddingRight,
|
|
27
|
+
paddingTop
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Card footer, applying the tokens as per the theme used.
|
|
33
|
+
*/
|
|
34
|
+
const CardFooter = ({ children, padding, tokens, variant, ...rest }) => {
|
|
35
|
+
const viewport = useViewport()
|
|
36
|
+
const themeTokens = useThemeTokens(
|
|
37
|
+
'Card',
|
|
38
|
+
tokens,
|
|
39
|
+
{ ...variant, background: 'alternative' },
|
|
40
|
+
{ viewport }
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<CardFooterContainer {...themeTokens} {...padding} {...selectProps(rest)}>
|
|
45
|
+
{children}
|
|
46
|
+
</CardFooterContainer>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
CardFooter.propTypes = {
|
|
51
|
+
...selectedSystemPropTypes,
|
|
52
|
+
/**
|
|
53
|
+
* Card footer content.
|
|
54
|
+
*/
|
|
55
|
+
children: PropTypes.node,
|
|
56
|
+
/**
|
|
57
|
+
* Card footer padding.
|
|
58
|
+
*/
|
|
59
|
+
padding: paddingProp.propType,
|
|
60
|
+
/**
|
|
61
|
+
* Card tokens.
|
|
62
|
+
*/
|
|
63
|
+
tokens: getTokensPropType('Card'),
|
|
64
|
+
/**
|
|
65
|
+
* Card variant.
|
|
66
|
+
*/
|
|
67
|
+
variant: variantProp.propType
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export default CardFooter
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/* eslint-disable react/require-default-props */
|
|
2
|
+
import React, { forwardRef } from 'react'
|
|
3
|
+
import PropTypes from 'prop-types'
|
|
4
|
+
import {
|
|
5
|
+
Spacer,
|
|
6
|
+
StackView,
|
|
7
|
+
Typography,
|
|
8
|
+
selectSystemProps,
|
|
9
|
+
useViewport,
|
|
10
|
+
useThemeTokens,
|
|
11
|
+
applyTextStyles
|
|
12
|
+
} from '@telus-uds/components-base'
|
|
13
|
+
import { viewports } from '@telus-uds/system-constants'
|
|
14
|
+
import styled from 'styled-components'
|
|
15
|
+
// Reading these from the RN palette since they will be used to generate
|
|
16
|
+
// the `Typography` tokens
|
|
17
|
+
import { htmlAttrs, transformGradient } from '../utils'
|
|
18
|
+
import Segment from './Segment'
|
|
19
|
+
import useCountdown from './useCountdown'
|
|
20
|
+
import { countdownVariantPropType, dictionaryContentShape } from './types'
|
|
21
|
+
|
|
22
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
|
|
23
|
+
|
|
24
|
+
const Container = styled.div(({ variant: { feature, inverse, large }, themeTokens, gradient }) => ({
|
|
25
|
+
...(large || (feature && { display: 'flex', flex: 0 })),
|
|
26
|
+
...(feature && {
|
|
27
|
+
borderRadius: themeTokens.containerBorderRadius,
|
|
28
|
+
justifyContent: 'center',
|
|
29
|
+
paddingBottom: `${themeTokens.containerPaddingBottomTop}px`,
|
|
30
|
+
paddingLeft: `${themeTokens.containerPaddingLeftRight}px`,
|
|
31
|
+
paddingRight: `${themeTokens.containerPaddingLeftRight}px`,
|
|
32
|
+
paddingTop: `${themeTokens.containerPaddingBottomTop}px`,
|
|
33
|
+
width: 'fit-content'
|
|
34
|
+
}),
|
|
35
|
+
...(feature &&
|
|
36
|
+
!inverse && {
|
|
37
|
+
background: `linear-gradient(#fff 0 0) padding-box, ${gradient} border-box`,
|
|
38
|
+
border: `${themeTokens.containerInverseBorder}px solid transparent`
|
|
39
|
+
}),
|
|
40
|
+
...(feature &&
|
|
41
|
+
inverse && {
|
|
42
|
+
border: `${themeTokens.containerInverseBorder}px solid ${themeTokens.inverseBorderColor}`
|
|
43
|
+
})
|
|
44
|
+
}))
|
|
45
|
+
|
|
46
|
+
const getLabelTokens = (themeTokens) => ({
|
|
47
|
+
color: themeTokens.labelBorderColor,
|
|
48
|
+
fontWeight: themeTokens.textTimerFontWeight,
|
|
49
|
+
fontSize: `${themeTokens.labelFontSize}`,
|
|
50
|
+
lineHeight: `${themeTokens.labelLineHeight}`
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const getMainTextTokens = (themeTokens) => ({
|
|
54
|
+
color: themeTokens.labelBorderColor,
|
|
55
|
+
fontWeight: themeTokens.textTimerFontWeight,
|
|
56
|
+
fontSize: themeTokens.textFontSize,
|
|
57
|
+
lineHeight: themeTokens.textLineHeight
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const Countdown = forwardRef(({ copy = 'en', targetTime, tokens, variant = {}, ...rest }, ref) => {
|
|
61
|
+
const [days, hours, minutes, seconds] = useCountdown(targetTime)
|
|
62
|
+
const viewport = useViewport()
|
|
63
|
+
const { feature, large, label, noDivider } = variant
|
|
64
|
+
if (noDivider && !label) {
|
|
65
|
+
throw new Error('`noDivider` variant can only be used with `label` enabled!')
|
|
66
|
+
}
|
|
67
|
+
const themeTokens = useThemeTokens('Countdown', tokens, variant, { viewport })
|
|
68
|
+
const segmentFontSize = themeTokens.textFontSize
|
|
69
|
+
const semanticGradient =
|
|
70
|
+
themeTokens.containerGradient && transformGradient(themeTokens.containerGradient)
|
|
71
|
+
|
|
72
|
+
const mainTextTokens = getMainTextTokens(themeTokens)
|
|
73
|
+
|
|
74
|
+
const divider =
|
|
75
|
+
noDivider === true ? (
|
|
76
|
+
// StackView-based subcontainer adds a 1-step space on the each side of the divider
|
|
77
|
+
<Spacer direction="row" space={(feature || large) && viewport !== viewports.xs ? 7 : 2} />
|
|
78
|
+
) : (
|
|
79
|
+
<Typography tokens={mainTextTokens}>:</Typography>
|
|
80
|
+
)
|
|
81
|
+
const labelTokens = getLabelTokens(themeTokens)
|
|
82
|
+
const commonProps = {
|
|
83
|
+
copy,
|
|
84
|
+
labelTokens,
|
|
85
|
+
numberTokens: mainTextTokens,
|
|
86
|
+
segmentFontSize,
|
|
87
|
+
variant
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
// Making it focusable for accessibility purposes
|
|
92
|
+
<Container
|
|
93
|
+
ref={ref}
|
|
94
|
+
variant={variant}
|
|
95
|
+
{...selectProps(rest)}
|
|
96
|
+
tabIndex={0}
|
|
97
|
+
themeTokens={themeTokens}
|
|
98
|
+
gradient={semanticGradient}
|
|
99
|
+
>
|
|
100
|
+
<StackView direction="row" space={1}>
|
|
101
|
+
<Segment labelKey="day" number={days} segmentWidth={String(days).length} {...commonProps} />
|
|
102
|
+
{divider}
|
|
103
|
+
<Segment
|
|
104
|
+
labelKey="hour"
|
|
105
|
+
number={hours}
|
|
106
|
+
{...commonProps}
|
|
107
|
+
{...applyTextStyles(themeTokens)}
|
|
108
|
+
/>
|
|
109
|
+
{divider}
|
|
110
|
+
<Segment
|
|
111
|
+
labelKey="minute"
|
|
112
|
+
number={minutes}
|
|
113
|
+
{...commonProps}
|
|
114
|
+
{...applyTextStyles(themeTokens)}
|
|
115
|
+
/>
|
|
116
|
+
{divider}
|
|
117
|
+
<Segment
|
|
118
|
+
labelKey="second"
|
|
119
|
+
number={seconds}
|
|
120
|
+
{...commonProps}
|
|
121
|
+
{...applyTextStyles(themeTokens)}
|
|
122
|
+
/>
|
|
123
|
+
</StackView>
|
|
124
|
+
</Container>
|
|
125
|
+
)
|
|
126
|
+
})
|
|
127
|
+
Countdown.displayName = 'Countdown'
|
|
128
|
+
|
|
129
|
+
Countdown.propTypes = {
|
|
130
|
+
...selectedSystemPropTypes,
|
|
131
|
+
/**
|
|
132
|
+
* Copy language identifier (`'en'` or `'fr'`) or a dictionary instance (an object with
|
|
133
|
+
* the following keys: days, day, hours, hour, minutes, minute, seconds, second)
|
|
134
|
+
*/
|
|
135
|
+
copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), dictionaryContentShape]),
|
|
136
|
+
/**
|
|
137
|
+
* An instance of JavaScript `Date` object or a string parseable via `Date.parse()`
|
|
138
|
+
* representing a point in the future to count down to.
|
|
139
|
+
*/
|
|
140
|
+
targetTime: PropTypes.instanceOf(Date),
|
|
141
|
+
variant: countdownVariantPropType
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default Countdown
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { StackView, Typography, useCopy } from '@telus-uds/components-base'
|
|
4
|
+
import styled from 'styled-components'
|
|
5
|
+
import dictionary from './dictionary'
|
|
6
|
+
import { countdownVariantPropType, dictionaryContentShape } from './types'
|
|
7
|
+
import { SEGMENT_WIDTH_TO_FONT_SIZE_RATIO } from './constants'
|
|
8
|
+
|
|
9
|
+
// Pads with zeros on the left if it's a single digit number
|
|
10
|
+
const pad = (number, segmentWidth = 2) => String(number).padStart(segmentWidth, '0')
|
|
11
|
+
|
|
12
|
+
const Container = styled.div(({ segmentFontSize, segmentWidth = 2, variant: { feature } }) => ({
|
|
13
|
+
justifyContent: 'space-evenly',
|
|
14
|
+
display: 'inline-flex',
|
|
15
|
+
...(feature && {
|
|
16
|
+
width: `${segmentFontSize * SEGMENT_WIDTH_TO_FONT_SIZE_RATIO * segmentWidth}px`,
|
|
17
|
+
display: 'flex'
|
|
18
|
+
})
|
|
19
|
+
}))
|
|
20
|
+
|
|
21
|
+
// A segment of the countdown string: we need to make sure it
|
|
22
|
+
// keeps its width constant to prevent the whole component from
|
|
23
|
+
// being automatically resized while using variable size fonts
|
|
24
|
+
const Segment = ({
|
|
25
|
+
copy = 'en',
|
|
26
|
+
segmentFontSize,
|
|
27
|
+
labelKey,
|
|
28
|
+
labelTokens,
|
|
29
|
+
number,
|
|
30
|
+
numberTokens,
|
|
31
|
+
segmentWidth = 2,
|
|
32
|
+
variant = {}
|
|
33
|
+
}) => {
|
|
34
|
+
const getCopy = useCopy({ dictionary, copy })
|
|
35
|
+
const { label, large, feature } = variant
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Container segmentFontSize={segmentFontSize} segmentWidth={segmentWidth} variant={variant}>
|
|
39
|
+
<StackView
|
|
40
|
+
direction={large || feature ? 'column' : 'row'}
|
|
41
|
+
space={large || feature ? 0 : 1}
|
|
42
|
+
tokens={{ alignItems: 'center' }}
|
|
43
|
+
>
|
|
44
|
+
<Typography tokens={numberTokens}>{pad(number, segmentWidth)}</Typography>
|
|
45
|
+
{label && (
|
|
46
|
+
<Typography tokens={labelTokens}>
|
|
47
|
+
{getCopy(number === 1 ? labelKey : `${labelKey}s`)}
|
|
48
|
+
</Typography>
|
|
49
|
+
)}
|
|
50
|
+
</StackView>
|
|
51
|
+
</Container>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Segment.propTypes = {
|
|
56
|
+
/**
|
|
57
|
+
* Copy language identifier or a dictionary instance.
|
|
58
|
+
*/
|
|
59
|
+
copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), dictionaryContentShape]),
|
|
60
|
+
segmentFontSize: PropTypes.number,
|
|
61
|
+
labelKey: PropTypes.oneOf(['day', 'hour', 'minute', 'second']),
|
|
62
|
+
labelTokens: PropTypes.object,
|
|
63
|
+
number: PropTypes.number,
|
|
64
|
+
numberTokens: PropTypes.object,
|
|
65
|
+
segmentWidth: PropTypes.number,
|
|
66
|
+
variant: countdownVariantPropType
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export default Segment
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
en: {
|
|
3
|
+
days: 'Days',
|
|
4
|
+
day: 'Day',
|
|
5
|
+
hours: 'Hours',
|
|
6
|
+
hour: 'Hour',
|
|
7
|
+
minutes: 'Minutes',
|
|
8
|
+
minute: 'Minute',
|
|
9
|
+
seconds: 'Seconds',
|
|
10
|
+
second: 'Second'
|
|
11
|
+
},
|
|
12
|
+
fr: {
|
|
13
|
+
days: 'Jours',
|
|
14
|
+
day: 'Jour',
|
|
15
|
+
hours: 'Heures',
|
|
16
|
+
hour: 'Heure',
|
|
17
|
+
minutes: 'Minutes',
|
|
18
|
+
minute: 'Minute',
|
|
19
|
+
seconds: 'Secondes',
|
|
20
|
+
second: 'Seconde'
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import PropTypes from 'prop-types'
|
|
2
|
+
|
|
3
|
+
export const countdownVariantPropType = PropTypes.shape({
|
|
4
|
+
feature: PropTypes.bool,
|
|
5
|
+
inverse: PropTypes.bool,
|
|
6
|
+
label: PropTypes.bool,
|
|
7
|
+
large: PropTypes.bool,
|
|
8
|
+
noDivider: PropTypes.bool
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
// If a language dictionary entry is provided, it must contain every key
|
|
12
|
+
export const dictionaryContentShape = PropTypes.shape({
|
|
13
|
+
days: PropTypes.string.isRequired,
|
|
14
|
+
day: PropTypes.string.isRequired,
|
|
15
|
+
hours: PropTypes.string.isRequired,
|
|
16
|
+
hour: PropTypes.string.isRequired,
|
|
17
|
+
minutes: PropTypes.string.isRequired,
|
|
18
|
+
minute: PropTypes.string.isRequired,
|
|
19
|
+
seconds: PropTypes.string.isRequired,
|
|
20
|
+
second: PropTypes.string.isRequired
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export default { countdownVariantPropType, dictionaryContentShape }
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
const getTimeCounts = (countdown) => {
|
|
4
|
+
if (countdown <= 0) {
|
|
5
|
+
return [0, 0, 0, 0]
|
|
6
|
+
}
|
|
7
|
+
const days = Math.floor(countdown / (1000 * 60 * 60 * 24))
|
|
8
|
+
const hours = Math.floor((countdown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
|
|
9
|
+
const minutes = Math.floor((countdown % (1000 * 60 * 60)) / (1000 * 60))
|
|
10
|
+
const seconds = Math.floor((countdown % (1000 * 60)) / 1000)
|
|
11
|
+
|
|
12
|
+
return [days, hours, minutes, seconds]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const useCountdown = (targetTime) => {
|
|
16
|
+
const countdownTime = new Date(targetTime).getTime()
|
|
17
|
+
if (!countdownTime) {
|
|
18
|
+
throw new Error('Invalid target time is provided!')
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const [countdown, setCountdown] = useState(countdownTime - new Date().getTime())
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
const interval = setInterval(() => {
|
|
25
|
+
setCountdown(countdownTime - new Date().getTime())
|
|
26
|
+
}, 1000)
|
|
27
|
+
|
|
28
|
+
return () => clearInterval(interval)
|
|
29
|
+
}, [countdownTime])
|
|
30
|
+
|
|
31
|
+
return getTimeCounts(countdown)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default useCountdown
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
import Close from '../../__fixtures__/icons/Close'
|
|
16
16
|
import OrderedListBase from '../OrderedList/OrderedListBase'
|
|
17
17
|
import { htmlAttrs, media, renderStructuredContent } from '../utils'
|
|
18
|
-
import
|
|
18
|
+
import defaultDictionary from './dictionary'
|
|
19
19
|
|
|
20
20
|
const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
|
|
21
21
|
|
|
@@ -154,6 +154,10 @@ const ContentContainer = styled.div(
|
|
|
154
154
|
})
|
|
155
155
|
)
|
|
156
156
|
|
|
157
|
+
const StyledCustomContentContainer = styled.div(({ color }) => ({
|
|
158
|
+
color,
|
|
159
|
+
fontFamily: 'HelveticaNow400normal'
|
|
160
|
+
}))
|
|
157
161
|
const usePrevious = (value) => {
|
|
158
162
|
const ref = useRef()
|
|
159
163
|
useEffect(() => {
|
|
@@ -184,7 +188,17 @@ const usePrevious = (value) => {
|
|
|
184
188
|
* - When `Footnote` is closed, focus must return to the initiating element
|
|
185
189
|
*/
|
|
186
190
|
const Footnote = (props) => {
|
|
187
|
-
const {
|
|
191
|
+
const {
|
|
192
|
+
copy,
|
|
193
|
+
number,
|
|
194
|
+
content,
|
|
195
|
+
onClose,
|
|
196
|
+
isOpen,
|
|
197
|
+
tokens,
|
|
198
|
+
variant = {},
|
|
199
|
+
dictionary = defaultDictionary,
|
|
200
|
+
...rest
|
|
201
|
+
} = props
|
|
188
202
|
const {
|
|
189
203
|
footnoteBackground,
|
|
190
204
|
footnoteBorderTopSizeMd,
|
|
@@ -216,7 +230,7 @@ const Footnote = (props) => {
|
|
|
216
230
|
const footnoteRef = useRef(null)
|
|
217
231
|
const headerRef = useRef(null)
|
|
218
232
|
const bodyRef = useRef(null)
|
|
219
|
-
const
|
|
233
|
+
const contentRef = useRef(null)
|
|
220
234
|
const headingRef = useRef(null)
|
|
221
235
|
const [data, setData] = useState({ content: null, number: null })
|
|
222
236
|
const [headerHeight, setHeaderHeight] = useState('auto')
|
|
@@ -265,7 +279,7 @@ const Footnote = (props) => {
|
|
|
265
279
|
)
|
|
266
280
|
|
|
267
281
|
const saveCurrentHeight = () => {
|
|
268
|
-
const oldHeight =
|
|
282
|
+
const oldHeight = contentRef.current.offsetHeight
|
|
269
283
|
setBodyHeight(oldHeight)
|
|
270
284
|
}
|
|
271
285
|
|
|
@@ -287,14 +301,14 @@ const Footnote = (props) => {
|
|
|
287
301
|
event.persist()
|
|
288
302
|
if (event.propertyName === 'opacity' && !isTextVisible) {
|
|
289
303
|
setData({ content, number })
|
|
290
|
-
if (bodyHeight !==
|
|
304
|
+
if (bodyHeight !== contentRef.current.offsetHeight) {
|
|
291
305
|
// Set new height
|
|
292
|
-
setBodyHeight(
|
|
306
|
+
setBodyHeight(contentRef.current.offsetHeight)
|
|
293
307
|
} else {
|
|
294
308
|
setIsTextVisible(true)
|
|
295
309
|
}
|
|
296
310
|
} else {
|
|
297
|
-
setBodyHeight(
|
|
311
|
+
setBodyHeight(contentRef.current.offsetHeight)
|
|
298
312
|
}
|
|
299
313
|
|
|
300
314
|
if (event.propertyName === 'height' && !isTextVisible) {
|
|
@@ -359,6 +373,41 @@ const Footnote = (props) => {
|
|
|
359
373
|
// Reset footnote on close
|
|
360
374
|
useEffect(resetFootnote, [isOpen])
|
|
361
375
|
|
|
376
|
+
const getFootnoteBodyContent = useCallback(() => {
|
|
377
|
+
if (!data.number || !data.content) {
|
|
378
|
+
return null
|
|
379
|
+
}
|
|
380
|
+
if (React.isValidElement(data.content)) {
|
|
381
|
+
return (
|
|
382
|
+
<StyledCustomContentContainer ref={contentRef}>{data.content}</StyledCustomContentContainer>
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
return (
|
|
386
|
+
<List start={data.number} ref={contentRef} listPaddingLeft={listPaddingLeft}>
|
|
387
|
+
<ListItem
|
|
388
|
+
listItemMarkerFontSize={listItemMarkerFontSize}
|
|
389
|
+
listItemMarkerLineHeight={listItemMarkerLineHeight}
|
|
390
|
+
listItemColor={listItemColor}
|
|
391
|
+
listItemFontSize={listItemFontSize}
|
|
392
|
+
listItemLineHeight={listItemLineHeight}
|
|
393
|
+
listItemPaddingLeft={listItemPaddingLeft}
|
|
394
|
+
>
|
|
395
|
+
<Typography>{renderStructuredContent(data.content)}</Typography>
|
|
396
|
+
</ListItem>
|
|
397
|
+
</List>
|
|
398
|
+
)
|
|
399
|
+
}, [
|
|
400
|
+
data.content,
|
|
401
|
+
data.number,
|
|
402
|
+
listItemColor,
|
|
403
|
+
listItemFontSize,
|
|
404
|
+
listItemLineHeight,
|
|
405
|
+
listItemMarkerFontSize,
|
|
406
|
+
listItemMarkerLineHeight,
|
|
407
|
+
listItemPaddingLeft,
|
|
408
|
+
listPaddingLeft
|
|
409
|
+
])
|
|
410
|
+
|
|
362
411
|
return (
|
|
363
412
|
<Portal>
|
|
364
413
|
<div {...selectProps(rest)}>
|
|
@@ -402,20 +451,7 @@ const Footnote = (props) => {
|
|
|
402
451
|
footnoteBodyBackground={footnoteBodyBackground}
|
|
403
452
|
footnoteBodyPadding={`${footnoteBodyPaddingTop}px ${footnoteBodyPaddingRight}px ${footnoteBodyPaddingBottom}px ${footnoteBodyPaddingLeft}px`}
|
|
404
453
|
>
|
|
405
|
-
{
|
|
406
|
-
<List start={data.number} ref={listRef} listPaddingLeft={listPaddingLeft}>
|
|
407
|
-
<ListItem
|
|
408
|
-
listItemMarkerFontSize={listItemMarkerFontSize}
|
|
409
|
-
listItemMarkerLineHeight={listItemMarkerLineHeight}
|
|
410
|
-
listItemColor={listItemColor}
|
|
411
|
-
listItemFontSize={listItemFontSize}
|
|
412
|
-
listItemLineHeight={listItemLineHeight}
|
|
413
|
-
listItemPaddingLeft={listItemPaddingLeft}
|
|
414
|
-
>
|
|
415
|
-
<Typography>{renderStructuredContent(data.content)}</Typography>
|
|
416
|
-
</ListItem>
|
|
417
|
-
</List>
|
|
418
|
-
)}
|
|
454
|
+
{getFootnoteBodyContent()}
|
|
419
455
|
</StyledFootnoteBody>
|
|
420
456
|
</ContentContainer>
|
|
421
457
|
</StyledFootnote>
|
|
@@ -429,12 +465,19 @@ const copyShape = PropTypes.shape({
|
|
|
429
465
|
heading: PropTypes.string.isRequired
|
|
430
466
|
})
|
|
431
467
|
|
|
468
|
+
// If a language dictionary entry is provided, it must contain every key
|
|
469
|
+
const dictionaryContentShape = PropTypes.shape({
|
|
470
|
+
a11yLabel: PropTypes.string.isRequired,
|
|
471
|
+
close: PropTypes.string.isRequired,
|
|
472
|
+
heading: PropTypes.string.isRequired
|
|
473
|
+
})
|
|
474
|
+
|
|
432
475
|
Footnote.propTypes = {
|
|
433
476
|
...selectedSystemPropTypes,
|
|
434
477
|
/**
|
|
435
478
|
* The content.
|
|
436
479
|
*/
|
|
437
|
-
content: PropTypes.string,
|
|
480
|
+
content: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
|
|
438
481
|
/**
|
|
439
482
|
* Use the `copy` prop to either select provided English or French copy by passing 'en' or 'fr' respectively.
|
|
440
483
|
* To provide your own, pass a JSON object with the keys `heading` and `close`.
|
|
@@ -455,7 +498,14 @@ Footnote.propTypes = {
|
|
|
455
498
|
* @param {Object} options Custom options
|
|
456
499
|
* @param {boolean} options.returnFocus Should the `Footnote` return focus on close
|
|
457
500
|
*/
|
|
458
|
-
onClose: PropTypes.func.isRequired
|
|
501
|
+
onClose: PropTypes.func.isRequired,
|
|
502
|
+
/**
|
|
503
|
+
* Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
|
|
504
|
+
*/
|
|
505
|
+
dictionary: PropTypes.shape({
|
|
506
|
+
en: dictionaryContentShape,
|
|
507
|
+
fr: dictionaryContentShape
|
|
508
|
+
})
|
|
459
509
|
}
|
|
460
510
|
|
|
461
511
|
Footnote.defaultProps = {
|
|
@@ -28,6 +28,10 @@ const StyledSubHeading = styled.div`
|
|
|
28
28
|
margin-top: ${({ marginTop }) => marginTop}px;
|
|
29
29
|
`
|
|
30
30
|
|
|
31
|
+
const StyledTextButtonContainer = styled.div({
|
|
32
|
+
display: 'flex'
|
|
33
|
+
})
|
|
34
|
+
|
|
31
35
|
const StyledFooter = styled.footer(
|
|
32
36
|
({
|
|
33
37
|
hasBorder,
|
|
@@ -131,13 +135,13 @@ const ModalContent = ({
|
|
|
131
135
|
{confirmButtonText}
|
|
132
136
|
</Button>
|
|
133
137
|
)}
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
{hasCancelButton ? (
|
|
139
|
+
<StyledTextButtonContainer>
|
|
136
140
|
<CancelButton tokens={{ color: cancelButtonColor }} onPress={onCancel}>
|
|
137
141
|
{cancelButtonText}
|
|
138
142
|
</CancelButton>
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
</StyledTextButtonContainer>
|
|
144
|
+
) : null}
|
|
141
145
|
</StyledFooter>
|
|
142
146
|
)}
|
|
143
147
|
</StyledModalContent>
|