@telus-uds/components-web 1.7.0 → 1.9.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.
Files changed (220) hide show
  1. package/.eslintignore +2 -0
  2. package/.vscode/settings.json +7 -0
  3. package/CHANGELOG.md +39 -2
  4. package/lib/Autocomplete/Autocomplete.js +393 -0
  5. package/lib/Autocomplete/Loading.js +51 -0
  6. package/lib/Autocomplete/Suggestions.js +81 -0
  7. package/lib/Autocomplete/constants.js +19 -0
  8. package/lib/Autocomplete/dictionary.js +19 -0
  9. package/lib/Autocomplete/index.js +13 -0
  10. package/lib/BlockQuote/BlockQuote.js +173 -0
  11. package/lib/BlockQuote/index.js +13 -0
  12. package/lib/Callout/Callout.js +3 -0
  13. package/lib/Card/Card.js +180 -0
  14. package/lib/Card/CardContent.js +110 -0
  15. package/lib/Card/CardFooter.js +98 -0
  16. package/lib/Card/index.js +13 -0
  17. package/lib/Countdown/Countdown.js +189 -0
  18. package/lib/Countdown/Segment.js +111 -0
  19. package/lib/Countdown/constants.js +14 -0
  20. package/lib/Countdown/dictionary.js +29 -0
  21. package/lib/Countdown/index.js +13 -0
  22. package/lib/Countdown/types.js +39 -0
  23. package/lib/Countdown/useCountdown.js +40 -0
  24. package/lib/IconButton/IconButton.js +70 -0
  25. package/lib/IconButton/index.js +13 -0
  26. package/lib/Listbox/GroupControl.js +94 -0
  27. package/lib/Listbox/Listbox.js +164 -0
  28. package/lib/Listbox/ListboxGroup.js +129 -0
  29. package/lib/Listbox/ListboxItem.js +137 -0
  30. package/lib/Listbox/ListboxOverlay.js +89 -0
  31. package/lib/Listbox/PressableItem.js +149 -0
  32. package/lib/Listbox/index.js +13 -0
  33. package/lib/Modal/ModalContent.js +11 -4
  34. package/lib/NavigationBar/resolveItemSelection.js +24 -0
  35. package/lib/OptimizeImage/OptimizeImage.js +127 -0
  36. package/lib/OptimizeImage/index.js +13 -0
  37. package/lib/OptimizeImage/utils/getFallbackUrl.js +18 -0
  38. package/lib/OptimizeImage/utils/getOptimizedUrl.js +32 -0
  39. package/lib/OptimizeImage/utils/hasWebpSupport.js +38 -0
  40. package/lib/OptimizeImage/utils/index.js +31 -0
  41. package/lib/OptimizeImage/utils/isSvgUrl.js +10 -0
  42. package/lib/QuantitySelector/QuantitySelector.js +253 -0
  43. package/lib/QuantitySelector/dictionary.js +33 -0
  44. package/lib/QuantitySelector/index.js +13 -0
  45. package/lib/QuantitySelector/styles.js +40 -0
  46. package/lib/StoryCard/StoryCard.js +244 -0
  47. package/lib/StoryCard/index.js +13 -0
  48. package/lib/TermsAndConditions/ExpandCollapse.js +141 -0
  49. package/lib/TermsAndConditions/TermsAndConditions.js +221 -0
  50. package/lib/TermsAndConditions/dictionary.js +19 -0
  51. package/lib/TermsAndConditions/index.js +15 -0
  52. package/lib/Testimonial/Testimonial.js +226 -0
  53. package/lib/Testimonial/index.js +13 -0
  54. package/lib/Video/ControlBar/ControlBar.js +315 -0
  55. package/lib/Video/ControlBar/Controls/VideoButton/VideoButton.js +91 -0
  56. package/lib/Video/ControlBar/Controls/VideoMenu/VideoMenu.js +186 -0
  57. package/lib/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.js +221 -0
  58. package/lib/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.js +213 -0
  59. package/lib/Video/MiddleControlButton/MiddleControlButton.js +89 -0
  60. package/lib/Video/Video.js +1072 -0
  61. package/lib/Video/index.js +13 -0
  62. package/lib/Video/videoText.js +62 -0
  63. package/lib/WebVideo/WebVideo.js +170 -0
  64. package/lib/WebVideo/index.js +13 -0
  65. package/lib/baseExports.js +0 -12
  66. package/lib/index.js +118 -1
  67. package/lib/shared/VideoSplash/SplashButton/SplashButton.js +102 -0
  68. package/lib/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.js +234 -0
  69. package/lib/shared/VideoSplash/VideoSplash.js +86 -0
  70. package/lib/shared/VideoSplash/helpers.js +38 -0
  71. package/lib/utils/index.js +8 -0
  72. package/lib/utils/useOverlaidPosition.js +246 -0
  73. package/lib-module/Autocomplete/Autocomplete.js +369 -0
  74. package/lib-module/Autocomplete/Loading.js +38 -0
  75. package/lib-module/Autocomplete/Suggestions.js +64 -0
  76. package/lib-module/Autocomplete/constants.js +5 -0
  77. package/lib-module/Autocomplete/dictionary.js +12 -0
  78. package/lib-module/Autocomplete/index.js +2 -0
  79. package/lib-module/BlockQuote/BlockQuote.js +156 -0
  80. package/lib-module/BlockQuote/index.js +2 -0
  81. package/lib-module/Callout/Callout.js +3 -0
  82. package/lib-module/Card/Card.js +158 -0
  83. package/lib-module/Card/CardContent.js +92 -0
  84. package/lib-module/Card/CardFooter.js +80 -0
  85. package/lib-module/Card/index.js +2 -0
  86. package/lib-module/Countdown/Countdown.js +165 -0
  87. package/lib-module/Countdown/Segment.js +94 -0
  88. package/lib-module/Countdown/constants.js +4 -0
  89. package/lib-module/Countdown/dictionary.js +22 -0
  90. package/lib-module/Countdown/index.js +2 -0
  91. package/lib-module/Countdown/types.js +23 -0
  92. package/lib-module/Countdown/useCountdown.js +32 -0
  93. package/lib-module/IconButton/IconButton.js +52 -0
  94. package/lib-module/IconButton/index.js +2 -0
  95. package/lib-module/Listbox/GroupControl.js +80 -0
  96. package/lib-module/Listbox/Listbox.js +142 -0
  97. package/lib-module/Listbox/ListboxGroup.js +106 -0
  98. package/lib-module/Listbox/ListboxItem.js +112 -0
  99. package/lib-module/Listbox/ListboxOverlay.js +68 -0
  100. package/lib-module/Listbox/PressableItem.js +128 -0
  101. package/lib-module/Listbox/index.js +2 -0
  102. package/lib-module/Modal/ModalContent.js +10 -4
  103. package/lib-module/NavigationBar/resolveItemSelection.js +16 -0
  104. package/lib-module/OptimizeImage/OptimizeImage.js +106 -0
  105. package/lib-module/OptimizeImage/index.js +2 -0
  106. package/lib-module/OptimizeImage/utils/getFallbackUrl.js +8 -0
  107. package/lib-module/OptimizeImage/utils/getOptimizedUrl.js +22 -0
  108. package/lib-module/OptimizeImage/utils/hasWebpSupport.js +32 -0
  109. package/lib-module/OptimizeImage/utils/index.js +4 -0
  110. package/lib-module/OptimizeImage/utils/isSvgUrl.js +3 -0
  111. package/lib-module/QuantitySelector/QuantitySelector.js +232 -0
  112. package/lib-module/QuantitySelector/dictionary.js +26 -0
  113. package/lib-module/QuantitySelector/index.js +2 -0
  114. package/lib-module/QuantitySelector/styles.js +21 -0
  115. package/lib-module/StoryCard/StoryCard.js +220 -0
  116. package/lib-module/StoryCard/index.js +2 -0
  117. package/lib-module/TermsAndConditions/ExpandCollapse.js +120 -0
  118. package/lib-module/TermsAndConditions/TermsAndConditions.js +193 -0
  119. package/lib-module/TermsAndConditions/dictionary.js +12 -0
  120. package/lib-module/TermsAndConditions/index.js +1 -0
  121. package/lib-module/Testimonial/Testimonial.js +204 -0
  122. package/lib-module/Testimonial/index.js +2 -0
  123. package/lib-module/Video/ControlBar/ControlBar.js +292 -0
  124. package/lib-module/Video/ControlBar/Controls/VideoButton/VideoButton.js +74 -0
  125. package/lib-module/Video/ControlBar/Controls/VideoMenu/VideoMenu.js +167 -0
  126. package/lib-module/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.js +201 -0
  127. package/lib-module/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.js +193 -0
  128. package/lib-module/Video/MiddleControlButton/MiddleControlButton.js +72 -0
  129. package/lib-module/Video/Video.js +1042 -0
  130. package/lib-module/Video/index.js +2 -0
  131. package/lib-module/Video/videoText.js +55 -0
  132. package/lib-module/WebVideo/WebVideo.js +144 -0
  133. package/lib-module/WebVideo/index.js +2 -0
  134. package/lib-module/baseExports.js +1 -1
  135. package/lib-module/index.js +13 -0
  136. package/lib-module/shared/VideoSplash/SplashButton/SplashButton.js +85 -0
  137. package/lib-module/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.js +216 -0
  138. package/lib-module/shared/VideoSplash/VideoSplash.js +65 -0
  139. package/lib-module/shared/VideoSplash/helpers.js +23 -0
  140. package/lib-module/utils/index.js +2 -1
  141. package/lib-module/utils/useOverlaidPosition.js +235 -0
  142. package/package.json +7 -5
  143. package/src/Autocomplete/Autocomplete.jsx +354 -0
  144. package/src/Autocomplete/Loading.jsx +18 -0
  145. package/src/Autocomplete/Suggestions.jsx +52 -0
  146. package/src/Autocomplete/constants.js +6 -0
  147. package/src/Autocomplete/dictionary.js +12 -0
  148. package/src/Autocomplete/index.js +3 -0
  149. package/src/BlockQuote/BlockQuote.jsx +130 -0
  150. package/src/BlockQuote/index.js +3 -0
  151. package/src/Callout/Callout.jsx +1 -1
  152. package/src/Card/Card.jsx +170 -0
  153. package/src/Card/CardContent.jsx +88 -0
  154. package/src/Card/CardFooter.jsx +70 -0
  155. package/src/Card/index.js +3 -0
  156. package/src/Countdown/Countdown.jsx +144 -0
  157. package/src/Countdown/Segment.jsx +69 -0
  158. package/src/Countdown/constants.js +4 -0
  159. package/src/Countdown/dictionary.js +22 -0
  160. package/src/Countdown/index.js +3 -0
  161. package/src/Countdown/types.js +23 -0
  162. package/src/Countdown/useCountdown.js +34 -0
  163. package/src/IconButton/IconButton.jsx +46 -0
  164. package/src/IconButton/index.js +3 -0
  165. package/src/Listbox/GroupControl.jsx +65 -0
  166. package/src/Listbox/Listbox.jsx +148 -0
  167. package/src/Listbox/ListboxGroup.jsx +110 -0
  168. package/src/Listbox/ListboxItem.jsx +101 -0
  169. package/src/Listbox/ListboxOverlay.jsx +71 -0
  170. package/src/Listbox/PressableItem.jsx +121 -0
  171. package/src/Listbox/index.js +3 -0
  172. package/src/Modal/ModalContent.jsx +8 -4
  173. package/src/NavigationBar/resolveItemSelection.js +11 -0
  174. package/src/OptimizeImage/OptimizeImage.jsx +131 -0
  175. package/src/OptimizeImage/index.js +3 -0
  176. package/src/OptimizeImage/utils/getFallbackUrl.js +9 -0
  177. package/src/OptimizeImage/utils/getOptimizedUrl.js +30 -0
  178. package/src/OptimizeImage/utils/hasWebpSupport.js +33 -0
  179. package/src/OptimizeImage/utils/index.js +5 -0
  180. package/src/OptimizeImage/utils/isSvgUrl.js +3 -0
  181. package/src/QuantitySelector/QuantitySelector.jsx +245 -0
  182. package/src/QuantitySelector/dictionary.js +27 -0
  183. package/src/QuantitySelector/index.js +3 -0
  184. package/src/QuantitySelector/styles.js +83 -0
  185. package/src/StoryCard/StoryCard.jsx +198 -0
  186. package/src/StoryCard/index.js +3 -0
  187. package/src/TermsAndConditions/ExpandCollapse.jsx +106 -0
  188. package/src/TermsAndConditions/TermsAndConditions.jsx +161 -0
  189. package/src/TermsAndConditions/dictionary.js +12 -0
  190. package/src/TermsAndConditions/index.js +1 -0
  191. package/src/Testimonial/Testimonial.jsx +169 -0
  192. package/src/Testimonial/index.js +3 -0
  193. package/src/Video/ControlBar/ControlBar.jsx +261 -0
  194. package/src/Video/ControlBar/Controls/VideoButton/VideoButton.jsx +61 -0
  195. package/src/Video/ControlBar/Controls/VideoMenu/VideoMenu.jsx +159 -0
  196. package/src/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.jsx +185 -0
  197. package/src/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.jsx +184 -0
  198. package/src/Video/MiddleControlButton/MiddleControlButton.jsx +64 -0
  199. package/src/Video/Video.jsx +988 -0
  200. package/src/Video/index.js +3 -0
  201. package/src/Video/videoText.js +58 -0
  202. package/src/WebVideo/WebVideo.jsx +131 -0
  203. package/src/WebVideo/index.js +3 -0
  204. package/src/baseExports.js +0 -2
  205. package/src/index.js +13 -0
  206. package/src/shared/VideoSplash/SplashButton/SplashButton.jsx +64 -0
  207. package/src/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.jsx +128 -0
  208. package/src/shared/VideoSplash/VideoSplash.jsx +50 -0
  209. package/src/shared/VideoSplash/helpers.js +27 -0
  210. package/src/utils/index.js +10 -1
  211. package/src/utils/useOverlaidPosition.js +226 -0
  212. package/types/Autocomplete.d.ts +32 -0
  213. package/types/Card.d.ts +45 -0
  214. package/types/ControlBar.d.ts +59 -0
  215. package/types/MiddleControlButton.d.ts +15 -0
  216. package/types/Video.d.ts +39 -0
  217. package/types/VideoButton.d.ts +14 -0
  218. package/types/VideoMenu.d.ts +16 -0
  219. package/types/VideoProgressBar.d.ts +17 -0
  220. package/types/VolumeSlider.d.ts +20 -0
@@ -0,0 +1,161 @@
1
+ import React, { forwardRef } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import styled from 'styled-components'
4
+ import {
5
+ Box,
6
+ Divider,
7
+ selectSystemProps,
8
+ Typography,
9
+ useCopy,
10
+ useThemeTokens
11
+ } from '@telus-uds/components-base'
12
+ import ExpandCollapse from './ExpandCollapse'
13
+ import OrderedListBase from '../OrderedList/OrderedListBase'
14
+ import { htmlAttrs, media, renderStructuredContent } from '../utils'
15
+ import dictionary from './dictionary'
16
+
17
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
18
+
19
+ const ContentContainer = styled.div(({ tokens }) => ({
20
+ paddingBottom: tokens.contentPaddingBottom,
21
+ paddingLeft: tokens.contentPaddingLeft,
22
+ ...media().from('md').css({
23
+ paddingBottom: tokens.mdContentPaddingBottom,
24
+ paddingLeft: tokens.mdContentPaddingLeft
25
+ })
26
+ }))
27
+
28
+ const Ordered = styled(OrderedListBase)(({ tokens }) => ({
29
+ listStylePosition: 'outside',
30
+ padding: tokens.orderedPadding
31
+ }))
32
+
33
+ const Unordered = styled.ul(({ tokens }) => ({
34
+ listStyleType: 'none',
35
+ margin: 0,
36
+ padding: tokens.unorderedPadding
37
+ }))
38
+
39
+ const ListItem = styled(OrderedListBase.Item)(({ tokens }) => ({
40
+ display: 'list-item',
41
+ '&::marker': {
42
+ fontFamily: `${tokens.listFontName}${tokens.listFontWeight}normal`,
43
+ fontSize: tokens.listFontSize,
44
+ lineHeight: tokens.listLineHeight,
45
+ textAlign: 'end !important'
46
+ },
47
+ color: tokens.listColor,
48
+ fontFamily: `${tokens.listFontName}${tokens.listFontWeight}normal`,
49
+ fontSize: tokens.listFontSize,
50
+ lineHeight: tokens.listLineHeight,
51
+ marginBottom: tokens.listMarginBottom,
52
+ marginLeft: tokens.listMarginLeft,
53
+ wordBreak: 'break-word'
54
+ }))
55
+
56
+ const NonIndexedContentTitle = styled.div(({ tokens }) => ({
57
+ color: tokens.titleColor,
58
+ fontSize: tokens.titleFontSize,
59
+ lineHeight: tokens.titleLineHeight,
60
+ paddingLeft: tokens.titlePaddingLeft
61
+ }))
62
+
63
+ /**
64
+ * Use `TermsAndConditions` to display important legal content.
65
+ *
66
+ * ## Usage Criteria
67
+ *
68
+ * - Display before the page footer, except for call-to-action callback cards
69
+ * - Responsive display based on breakpoints
70
+ * - Use `copy` to set language, ‘en’ for English or ‘fr’ for French
71
+ */
72
+ const TermsAndConditions = forwardRef(
73
+ ({ copy = 'en', indexedContent, nonIndexedContent, tokens, variant = {}, ...rest }, ref) => {
74
+ const getCopy = useCopy({ dictionary, copy })
75
+ const hasIndexedContent = indexedContent.length > 0
76
+ const hasNonIndexedContent = nonIndexedContent.length > 0
77
+
78
+ const themeTokens = useThemeTokens('TermsAndConditions', tokens, variant)
79
+
80
+ return (
81
+ <div {...selectProps(rest)}>
82
+ <Divider />
83
+ <ExpandCollapse
84
+ collapseTitle={getCopy('headingView')}
85
+ expandTitle={getCopy('headingHide')}
86
+ ref={ref}
87
+ >
88
+ <ContentContainer tokens={themeTokens}>
89
+ {hasIndexedContent && (
90
+ <Ordered tokens={themeTokens}>
91
+ {indexedContent.map((contentItem, idx) => (
92
+ // eslint-disable-next-line react/no-array-index-key
93
+ <ListItem tokens={themeTokens} key={idx}>
94
+ {renderStructuredContent(contentItem)}
95
+ </ListItem>
96
+ ))}
97
+ </Ordered>
98
+ )}
99
+ {hasNonIndexedContent && (
100
+ <Box between={3}>
101
+ <NonIndexedContentTitle tokens={themeTokens}>
102
+ <Typography block heading variant={{ size: 'h4' }}>
103
+ {getCopy('nonIndexedTitle')}
104
+ </Typography>
105
+ </NonIndexedContentTitle>
106
+ <Unordered tokens={themeTokens}>
107
+ {nonIndexedContent.map((contentItem, idx) => (
108
+ // eslint-disable-next-line react/no-array-index-key
109
+ <ListItem tokens={themeTokens} key={idx}>
110
+ {renderStructuredContent(contentItem)}
111
+ </ListItem>
112
+ ))}
113
+ </Unordered>
114
+ </Box>
115
+ )}
116
+ </ContentContainer>
117
+ </ExpandCollapse>
118
+ <Divider />
119
+ </div>
120
+ )
121
+ }
122
+ )
123
+
124
+ TermsAndConditions.displayName = 'TermsAndConditions'
125
+
126
+ TermsAndConditions.propTypes = {
127
+ ...selectedSystemPropTypes,
128
+ /**
129
+ * Use the `copy` prop to either select provided English or French copy by passing 'en' or 'fr' respectively.
130
+ *
131
+ * To provide your own, pass a JSON object with the keys `headingView`, `headingHide`, and `nonIndexedTitle`.
132
+ */
133
+ copy: PropTypes.oneOfType([
134
+ PropTypes.oneOf(['en', 'fr']),
135
+ PropTypes.shape({
136
+ headingView: PropTypes.string,
137
+ headingHide: PropTypes.string,
138
+ nonIndexedTitle: PropTypes.string
139
+ })
140
+ ]),
141
+ /**
142
+ * An array of nodes, strings, or a combination to be displayed in an ordered list.
143
+ *
144
+ * Each item in the array must have a corresponding superscript in the page.
145
+ */
146
+ indexedContent: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.node, PropTypes.string])),
147
+ /**
148
+ * An array of nodes, strings, or a combination to be displayed in an unordered list.
149
+ *
150
+ * nonIndexedContent do not have a corresponding superscript and instead apply to the page as a whole.
151
+ */
152
+ nonIndexedContent: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.node, PropTypes.string]))
153
+ }
154
+
155
+ TermsAndConditions.defaultProps = {
156
+ copy: 'en',
157
+ indexedContent: [],
158
+ nonIndexedContent: []
159
+ }
160
+
161
+ export default TermsAndConditions
@@ -0,0 +1,12 @@
1
+ export default {
2
+ en: {
3
+ headingHide: 'Hide terms and conditions',
4
+ headingView: 'View terms and conditions',
5
+ nonIndexedTitle: 'The following applies to all terms and conditions above'
6
+ },
7
+ fr: {
8
+ headingHide: 'Masquer les modalités et conditions',
9
+ headingView: 'Voir les modalités et conditions',
10
+ nonIndexedTitle: 'Ce qui suit s’applique aux modalités et conditions ci-dessus'
11
+ }
12
+ }
@@ -0,0 +1 @@
1
+ export { default } from './TermsAndConditions'
@@ -0,0 +1,169 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import styled from 'styled-components'
4
+ import { Icon, selectSystemProps, Typography, useThemeTokens } from '@telus-uds/components-base'
5
+ import Image from '../Image'
6
+ import { htmlAttrs } from '../utils'
7
+
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
9
+
10
+ const TestimonialContainer = styled.figure(({ testimonialContainerGap }) => ({
11
+ display: 'flex',
12
+ flexDirection: 'column',
13
+ gap: testimonialContainerGap,
14
+ margin: 0
15
+ }))
16
+
17
+ const QuoteContainer = styled.div(({ quoteContainerGap }) => ({
18
+ display: 'flex',
19
+ alignItems: 'center',
20
+ gap: quoteContainerGap
21
+ }))
22
+
23
+ const Divider = styled.div(({ dividerBorder, dividerBackgroundColor }) => ({
24
+ height: dividerBorder,
25
+ background: dividerBackgroundColor,
26
+ width: '100%'
27
+ }))
28
+
29
+ const BlockQuote = styled.blockquote({
30
+ margin: 0
31
+ })
32
+
33
+ const AuthorInfoContainer = styled.div({
34
+ display: 'flex',
35
+ flexDirection: 'column'
36
+ })
37
+
38
+ const Figcaption = styled.figcaption(({ figcaptionGap }) => ({
39
+ display: 'flex',
40
+ alignItems: 'center',
41
+ gap: figcaptionGap
42
+ }))
43
+
44
+ const Testimonial = ({
45
+ showDivider,
46
+ testimonial,
47
+ title,
48
+ imageSrc,
49
+ image = imageSrc,
50
+ additionalInfo,
51
+ testimonialStyle = 'heading',
52
+ tokens,
53
+ variant = {},
54
+ ...rest
55
+ }) => {
56
+ const {
57
+ testimonialContainerGap,
58
+ quoteContainerGap,
59
+ dividerBorder,
60
+ dividerBackgroundColor,
61
+ figcaptionGap,
62
+ textColor,
63
+ icon,
64
+ iconColor,
65
+ imageSize
66
+ } = useThemeTokens('Testimonial', tokens, variant)
67
+ return (
68
+ <TestimonialContainer testimonialContainerGap={testimonialContainerGap} {...selectProps(rest)}>
69
+ <QuoteContainer quoteContainerGap={quoteContainerGap}>
70
+ <Icon tokens={{ color: iconColor }} variant={{ size: 'micro' }} icon={icon} />
71
+ {showDivider && (
72
+ <Divider
73
+ dividerBackgroundColor={dividerBackgroundColor}
74
+ dividerBorder={dividerBorder}
75
+ role="separator"
76
+ />
77
+ )}
78
+ </QuoteContainer>
79
+ <BlockQuote>
80
+ <Typography
81
+ variant={{ size: testimonialStyle === 'large' ? 'large' : 'h3' }}
82
+ tokens={{
83
+ color: textColor,
84
+ fontWeight: '400'
85
+ }}
86
+ >
87
+ {`\u201C${testimonial}\u201D`}
88
+ </Typography>
89
+ </BlockQuote>
90
+ {(image || title || additionalInfo) && (
91
+ <Figcaption figcaptionGap={figcaptionGap}>
92
+ {image &&
93
+ (typeof image === 'string' ? (
94
+ <Image
95
+ rounded="circle"
96
+ src={image}
97
+ alt={title}
98
+ width={imageSize}
99
+ height={imageSize}
100
+ />
101
+ ) : (
102
+ image
103
+ ))}
104
+ {(title || additionalInfo) && (
105
+ <AuthorInfoContainer>
106
+ {title && (
107
+ <Typography
108
+ variant={{ size: 'small', colour: 'secondary' }}
109
+ tokens={{ fontWeight: '500' }}
110
+ >
111
+ {title}
112
+ </Typography>
113
+ )}
114
+ {additionalInfo && (
115
+ <Typography
116
+ variant={{ size: 'micro', colour: 'secondary' }}
117
+ tokens={{ fontWeight: '400' }}
118
+ >
119
+ {additionalInfo}
120
+ </Typography>
121
+ )}
122
+ </AuthorInfoContainer>
123
+ )}
124
+ </Figcaption>
125
+ )}
126
+ {showDivider && (
127
+ <Divider
128
+ dividerBackgroundColor={dividerBackgroundColor}
129
+ dividerBorder={dividerBorder}
130
+ role="separator"
131
+ />
132
+ )}
133
+ </TestimonialContainer>
134
+ )
135
+ }
136
+
137
+ Testimonial.propTypes = {
138
+ ...selectedSystemPropTypes,
139
+ /**
140
+ * Testimonial content
141
+ */
142
+ testimonial: PropTypes.string.isRequired,
143
+ /**
144
+ * Testimonial author or source
145
+ */
146
+ title: PropTypes.string,
147
+ /**
148
+ * Additional information on the author or source
149
+ */
150
+ additionalInfo: PropTypes.string,
151
+ /**
152
+ * Testimonial style
153
+ */
154
+ testimonialStyle: PropTypes.oneOf(['large', 'heading']),
155
+ /**
156
+ * Whether to show or not dividers at the top and the bottom of the testimonial
157
+ showDivider: PropTypes.bool,
158
+ /**
159
+ * The src attribute for the Image component or custom JSX content to render instead
160
+ */
161
+ image: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
162
+ /**
163
+ * The src attribute for the `Image` component to be displayed on the testimonial
164
+ * @deprecated please use the `image` prop instead
165
+ */
166
+ imageSrc: PropTypes.string
167
+ }
168
+
169
+ export default Testimonial
@@ -0,0 +1,3 @@
1
+ import Testimonial from './Testimonial'
2
+
3
+ export default Testimonial
@@ -0,0 +1,261 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import styled from 'styled-components'
4
+
5
+ import { StackView, useThemeTokens, selectSystemProps, Icon } from '@telus-uds/components-base'
6
+
7
+ import VideoProgressBar from './Controls/VideoProgressBar/VideoProgressBar'
8
+ import VolumeSlider from './Controls/VolumeSlider/VolumeSlider'
9
+ import VideoButton from './Controls/VideoButton/VideoButton'
10
+ import VideoMenu from './Controls/VideoMenu/VideoMenu'
11
+
12
+ import videoText from '../videoText'
13
+ import { htmlAttrs } from '../../utils'
14
+
15
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
16
+
17
+ const getIcon = (icon) => <Icon icon={icon} />
18
+
19
+ const ControlBarContainer = styled.div(({ isHidden, isMobile }) => ({
20
+ width: '100%',
21
+ position: 'relative',
22
+ transition: 'opacity 0.4s',
23
+ opacity: isHidden ? 0 : 1,
24
+ display: isMobile ? 'none' : undefined
25
+ }))
26
+
27
+ const StyledControlBar = styled.div(({ padding }) => ({
28
+ boxSizing: 'border-box',
29
+ position: 'absolute',
30
+ width: '100%',
31
+ height: 56,
32
+ padding,
33
+ bottom: 0,
34
+ backgroundColor: 'rgba(42, 44, 46, 0.85)', // TODO: replace with opaque greyThunder
35
+ margin: 'auto',
36
+ display: 'flex',
37
+ justifyContent: 'space-between',
38
+ zIndex: 9
39
+ }))
40
+
41
+ const VideoProgressBarContainer = styled.div({
42
+ display: 'flex',
43
+ flexGrow: 1
44
+ })
45
+
46
+ const MenuContainer = styled.div(({ isOpen, menuBottom, menuRight, menuMarginLeft }) => ({
47
+ position: 'absolute',
48
+ bottom: menuBottom,
49
+ right: menuRight,
50
+ display: isOpen ? 'block' : 'none',
51
+ marginLeft: menuMarginLeft
52
+ }))
53
+
54
+ const ControlBar = ({
55
+ videoPlayer,
56
+ videoPlayerContainer,
57
+ sources,
58
+ tracks,
59
+ videoPlaying,
60
+ videoUnplayed,
61
+ videoBufferEnd,
62
+ isHidden = false,
63
+ videoLength,
64
+ videoCurrentTime,
65
+ videoCurrentVolume,
66
+ videoIsMuted,
67
+ setVolume,
68
+ isMobile,
69
+ tracksAvailable,
70
+ togglePlayPause,
71
+ setSeek,
72
+ toggleMute,
73
+ toggleFullscreen,
74
+ videoIsFullscreen,
75
+ setTextTracks,
76
+ selectedTextTrack,
77
+ resetInactivityTimer,
78
+ videoQuality,
79
+ setVideoQuality,
80
+ captionsMenuOpen,
81
+ setCaptionsMenuOpen,
82
+ qualityMenuOpen,
83
+ setQualityMenuOpen,
84
+ clearInactivityTimer,
85
+ copy,
86
+ compactModeThreshold,
87
+ videoPlayerWidth,
88
+ tokens,
89
+ variant,
90
+ ...rest
91
+ }) => {
92
+ const {
93
+ paddingTop,
94
+ paddingBottom,
95
+ paddingLeft: paddingLeftDefault,
96
+ paddingRight: paddingRightDefault,
97
+ paddingLeftCompactMode,
98
+ paddingRightCompactMode,
99
+ menuBottom,
100
+ menuRight,
101
+ menuMarginLeft,
102
+ captionsIcon,
103
+ settingsIcon,
104
+ minimizeIcon,
105
+ maximizeIcon
106
+ } = useThemeTokens('VideoControlBar', tokens, variant)
107
+
108
+ const parseVideoQuality = () => {
109
+ return sources.map((source) => {
110
+ if (!source.isFallback) {
111
+ return { name: source.qualityName, value: source.qualityRank }
112
+ }
113
+ return {}
114
+ })
115
+ }
116
+
117
+ const parseTracks = () => {
118
+ const parsed = tracks.map((track, trackNumber) => {
119
+ return { name: videoText[copy][track.language], value: trackNumber }
120
+ })
121
+ parsed.unshift({ name: videoText[copy].captionsNone, value: -1 })
122
+ return parsed
123
+ }
124
+
125
+ const paddingLeft =
126
+ videoPlayerWidth > compactModeThreshold ? paddingLeftDefault : paddingLeftCompactMode
127
+ const paddingRight =
128
+ videoPlayerWidth > compactModeThreshold ? paddingRightDefault : paddingRightCompactMode
129
+ const menuContainerStyleProps = { menuBottom, menuRight, menuMarginLeft }
130
+
131
+ return (
132
+ <ControlBarContainer isHidden={isHidden} isMobile={isMobile} {...selectProps(rest)}>
133
+ <StyledControlBar
134
+ padding={`${paddingTop}px ${paddingRight}px ${paddingBottom}px ${paddingLeft}px`}
135
+ >
136
+ <VideoProgressBarContainer>
137
+ <VideoProgressBar
138
+ copy={copy}
139
+ videoPlayer={videoPlayer}
140
+ videoLength={videoLength}
141
+ videoCurrentTime={videoCurrentTime}
142
+ videoBufferEnd={videoBufferEnd}
143
+ setSeek={setSeek}
144
+ resetInactivityTimer={resetInactivityTimer}
145
+ />
146
+ </VideoProgressBarContainer>
147
+
148
+ <VolumeSlider
149
+ videoPlayer={videoPlayer}
150
+ videoCurrentVolume={videoCurrentVolume}
151
+ setVolume={setVolume}
152
+ videoIsMuted={videoIsMuted}
153
+ toggleMute={toggleMute}
154
+ resetInactivityTimer={resetInactivityTimer}
155
+ copy={copy}
156
+ compactModeThreshold={compactModeThreshold}
157
+ videoPlayerWidth={videoPlayerWidth}
158
+ disableFocus={videoUnplayed}
159
+ />
160
+
161
+ <StackView space={3} direction="row">
162
+ {tracksAvailable && (
163
+ <VideoButton
164
+ disableFocus={videoUnplayed}
165
+ label={videoText[copy].captionsToggle}
166
+ icon={getIcon(captionsIcon)}
167
+ onClick={() => {
168
+ setCaptionsMenuOpen(!captionsMenuOpen)
169
+ setQualityMenuOpen(false)
170
+ clearInactivityTimer()
171
+ }}
172
+ onFocus={resetInactivityTimer}
173
+ onBlur={resetInactivityTimer}
174
+ />
175
+ )}
176
+ <VideoButton
177
+ disableFocus={videoUnplayed}
178
+ label={videoText[copy].qualityToggle}
179
+ icon={getIcon(settingsIcon)}
180
+ onClick={() => {
181
+ setQualityMenuOpen(!qualityMenuOpen)
182
+ setCaptionsMenuOpen(false)
183
+ clearInactivityTimer()
184
+ }}
185
+ onFocus={resetInactivityTimer}
186
+ onBlur={resetInactivityTimer}
187
+ />
188
+ <VideoButton
189
+ disableFocus={videoUnplayed}
190
+ label={videoText[copy].fullScreenToggle}
191
+ icon={videoIsFullscreen ? getIcon(minimizeIcon) : getIcon(maximizeIcon)}
192
+ onClick={toggleFullscreen}
193
+ onFocus={resetInactivityTimer}
194
+ onBlur={resetInactivityTimer}
195
+ />
196
+ </StackView>
197
+ {captionsMenuOpen && (
198
+ <MenuContainer isOpen={captionsMenuOpen} {...menuContainerStyleProps}>
199
+ <VideoMenu
200
+ menuLabel={videoText[copy].captionsDialogue}
201
+ menuOptions={parseTracks()}
202
+ setSelection={setTextTracks}
203
+ selectedItem={selectedTextTrack}
204
+ copy={copy}
205
+ />
206
+ </MenuContainer>
207
+ )}
208
+ {qualityMenuOpen && (
209
+ <MenuContainer isOpen={qualityMenuOpen} {...menuContainerStyleProps}>
210
+ <VideoMenu
211
+ menuLabel={videoText[copy].qualityDialogue}
212
+ menuOptions={parseVideoQuality()}
213
+ setSelection={setVideoQuality}
214
+ selectedItem={videoQuality}
215
+ copy={copy}
216
+ />
217
+ </MenuContainer>
218
+ )}
219
+ </StyledControlBar>
220
+ </ControlBarContainer>
221
+ )
222
+ }
223
+
224
+ ControlBar.propTypes = {
225
+ ...selectedSystemPropTypes,
226
+ videoPlayer: PropTypes.object.isRequired,
227
+ videoPlayerContainer: PropTypes.object.isRequired,
228
+ sources: PropTypes.array.isRequired,
229
+ tracks: PropTypes.array,
230
+ videoPlaying: PropTypes.bool.isRequired,
231
+ videoUnplayed: PropTypes.bool.isRequired,
232
+ videoBufferEnd: PropTypes.number.isRequired,
233
+ isHidden: PropTypes.bool,
234
+ videoLength: PropTypes.number.isRequired,
235
+ videoCurrentTime: PropTypes.number.isRequired,
236
+ videoCurrentVolume: PropTypes.number.isRequired,
237
+ videoIsMuted: PropTypes.bool.isRequired,
238
+ setVolume: PropTypes.func.isRequired,
239
+ isMobile: PropTypes.bool.isRequired,
240
+ tracksAvailable: PropTypes.bool.isRequired,
241
+ togglePlayPause: PropTypes.func.isRequired,
242
+ setSeek: PropTypes.func.isRequired,
243
+ toggleMute: PropTypes.func.isRequired,
244
+ toggleFullscreen: PropTypes.func.isRequired,
245
+ videoIsFullscreen: PropTypes.bool.isRequired,
246
+ setTextTracks: PropTypes.func.isRequired,
247
+ selectedTextTrack: PropTypes.number.isRequired,
248
+ resetInactivityTimer: PropTypes.func.isRequired,
249
+ videoQuality: PropTypes.number.isRequired,
250
+ setVideoQuality: PropTypes.func.isRequired,
251
+ captionsMenuOpen: PropTypes.bool.isRequired,
252
+ setCaptionsMenuOpen: PropTypes.func.isRequired,
253
+ qualityMenuOpen: PropTypes.bool.isRequired,
254
+ setQualityMenuOpen: PropTypes.func.isRequired,
255
+ clearInactivityTimer: PropTypes.func.isRequired,
256
+ copy: PropTypes.oneOf(['en', 'fr']).isRequired,
257
+ compactModeThreshold: PropTypes.number.isRequired,
258
+ videoPlayerWidth: PropTypes.number.isRequired
259
+ }
260
+
261
+ export default ControlBar
@@ -0,0 +1,61 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import styled from 'styled-components'
4
+
5
+ import { useThemeTokens, selectSystemProps } from '@telus-uds/components-base'
6
+ import { htmlAttrs } from '../../../../utils'
7
+
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
9
+
10
+ const StyledButton = styled.button(({ color }) => ({
11
+ background: 'none',
12
+ border: 'none',
13
+ padding: 0,
14
+ cursor: 'pointer',
15
+ display: 'inline-flex',
16
+ alignItems: 'stretch',
17
+ 'svg path': {
18
+ fill: color
19
+ }
20
+ }))
21
+
22
+ const VideoButton = ({ icon, label, disableFocus, children, tokens, variant, ...rest }) => {
23
+ const { color } = useThemeTokens('VideoButton', tokens, variant)
24
+
25
+ const handleOnKeyDown = (event) => {
26
+ const key = event.key || event.keyCode
27
+
28
+ // Disables playing by space bar, as that can be used to click a button
29
+ if (key === ' ' || key === 32) {
30
+ event.stopPropagation()
31
+ }
32
+ }
33
+ const handleClick = (event) => {
34
+ event.preventDefault()
35
+ rest.onClick?.(event)
36
+ }
37
+
38
+ return (
39
+ <StyledButton
40
+ aria-label={label}
41
+ onKeyDown={handleOnKeyDown}
42
+ tabIndex={disableFocus ? '-1' : undefined}
43
+ color={color}
44
+ {...selectProps(rest)}
45
+ onClick={handleClick}
46
+ >
47
+ {icon}
48
+ {children}
49
+ </StyledButton>
50
+ )
51
+ }
52
+
53
+ VideoButton.propTypes = {
54
+ ...selectedSystemPropTypes,
55
+ icon: PropTypes.element.isRequired,
56
+ label: PropTypes.string.isRequired,
57
+ disableFocus: PropTypes.bool.isRequired,
58
+ children: PropTypes.node
59
+ }
60
+
61
+ export default VideoButton