@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.
Files changed (188) hide show
  1. package/CHANGELOG.md +41 -2
  2. package/lib/Autocomplete/Autocomplete.js +393 -0
  3. package/lib/Autocomplete/Loading.js +51 -0
  4. package/lib/Autocomplete/Suggestions.js +81 -0
  5. package/lib/Autocomplete/constants.js +19 -0
  6. package/lib/Autocomplete/dictionary.js +19 -0
  7. package/lib/Autocomplete/index.js +13 -0
  8. package/lib/Breadcrumbs/Breadcrumbs.js +8 -3
  9. package/lib/Callout/Callout.js +3 -0
  10. package/lib/Card/Card.js +180 -0
  11. package/lib/Card/CardContent.js +110 -0
  12. package/lib/Card/CardFooter.js +98 -0
  13. package/lib/Card/index.js +13 -0
  14. package/lib/Countdown/Countdown.js +189 -0
  15. package/lib/Countdown/Segment.js +111 -0
  16. package/lib/Countdown/constants.js +14 -0
  17. package/lib/Countdown/dictionary.js +29 -0
  18. package/lib/Countdown/index.js +13 -0
  19. package/lib/Countdown/types.js +39 -0
  20. package/lib/Countdown/useCountdown.js +40 -0
  21. package/lib/Footnote/Footnote.js +67 -24
  22. package/lib/Modal/ModalContent.js +11 -4
  23. package/lib/OptimizeImage/OptimizeImage.js +127 -0
  24. package/lib/OptimizeImage/index.js +13 -0
  25. package/lib/OptimizeImage/utils/getFallbackUrl.js +18 -0
  26. package/lib/OptimizeImage/utils/getOptimizedUrl.js +32 -0
  27. package/lib/OptimizeImage/utils/hasWebpSupport.js +38 -0
  28. package/lib/OptimizeImage/utils/index.js +31 -0
  29. package/lib/OptimizeImage/utils/isSvgUrl.js +10 -0
  30. package/lib/QuantitySelector/QuantitySelector.js +253 -0
  31. package/lib/QuantitySelector/dictionary.js +33 -0
  32. package/lib/QuantitySelector/index.js +13 -0
  33. package/lib/QuantitySelector/styles.js +40 -0
  34. package/lib/StoryCard/StoryCard.js +244 -0
  35. package/lib/StoryCard/index.js +13 -0
  36. package/lib/TermsAndConditions/ExpandCollapse.js +141 -0
  37. package/lib/TermsAndConditions/TermsAndConditions.js +221 -0
  38. package/lib/TermsAndConditions/dictionary.js +19 -0
  39. package/lib/TermsAndConditions/index.js +15 -0
  40. package/lib/Testimonial/Testimonial.js +226 -0
  41. package/lib/Testimonial/index.js +13 -0
  42. package/lib/Toast/Toast.js +15 -8
  43. package/lib/Video/ControlBar/ControlBar.js +315 -0
  44. package/lib/Video/ControlBar/Controls/VideoButton/VideoButton.js +91 -0
  45. package/lib/Video/ControlBar/Controls/VideoMenu/VideoMenu.js +186 -0
  46. package/lib/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.js +221 -0
  47. package/lib/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.js +213 -0
  48. package/lib/Video/MiddleControlButton/MiddleControlButton.js +89 -0
  49. package/lib/Video/Video.js +1072 -0
  50. package/lib/Video/index.js +13 -0
  51. package/lib/Video/videoText.js +62 -0
  52. package/lib/WebVideo/WebVideo.js +170 -0
  53. package/lib/WebVideo/index.js +13 -0
  54. package/lib/baseExports.js +0 -6
  55. package/lib/index.js +91 -1
  56. package/lib/shared/VideoSplash/SplashButton/SplashButton.js +102 -0
  57. package/lib/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.js +234 -0
  58. package/lib/shared/VideoSplash/VideoSplash.js +86 -0
  59. package/lib/shared/VideoSplash/helpers.js +38 -0
  60. package/lib/utils/index.js +8 -0
  61. package/lib-module/Autocomplete/Autocomplete.js +369 -0
  62. package/lib-module/Autocomplete/Loading.js +38 -0
  63. package/lib-module/Autocomplete/Suggestions.js +64 -0
  64. package/lib-module/Autocomplete/constants.js +5 -0
  65. package/lib-module/Autocomplete/dictionary.js +12 -0
  66. package/lib-module/Autocomplete/index.js +2 -0
  67. package/lib-module/Breadcrumbs/Breadcrumbs.js +8 -3
  68. package/lib-module/Callout/Callout.js +3 -0
  69. package/lib-module/Card/Card.js +158 -0
  70. package/lib-module/Card/CardContent.js +92 -0
  71. package/lib-module/Card/CardFooter.js +80 -0
  72. package/lib-module/Card/index.js +2 -0
  73. package/lib-module/Countdown/Countdown.js +165 -0
  74. package/lib-module/Countdown/Segment.js +94 -0
  75. package/lib-module/Countdown/constants.js +4 -0
  76. package/lib-module/Countdown/dictionary.js +22 -0
  77. package/lib-module/Countdown/index.js +2 -0
  78. package/lib-module/Countdown/types.js +23 -0
  79. package/lib-module/Countdown/useCountdown.js +32 -0
  80. package/lib-module/Footnote/Footnote.js +65 -24
  81. package/lib-module/Modal/ModalContent.js +10 -4
  82. package/lib-module/OptimizeImage/OptimizeImage.js +106 -0
  83. package/lib-module/OptimizeImage/index.js +2 -0
  84. package/lib-module/OptimizeImage/utils/getFallbackUrl.js +8 -0
  85. package/lib-module/OptimizeImage/utils/getOptimizedUrl.js +22 -0
  86. package/lib-module/OptimizeImage/utils/hasWebpSupport.js +32 -0
  87. package/lib-module/OptimizeImage/utils/index.js +4 -0
  88. package/lib-module/OptimizeImage/utils/isSvgUrl.js +3 -0
  89. package/lib-module/QuantitySelector/QuantitySelector.js +232 -0
  90. package/lib-module/QuantitySelector/dictionary.js +26 -0
  91. package/lib-module/QuantitySelector/index.js +2 -0
  92. package/lib-module/QuantitySelector/styles.js +21 -0
  93. package/lib-module/StoryCard/StoryCard.js +220 -0
  94. package/lib-module/StoryCard/index.js +2 -0
  95. package/lib-module/TermsAndConditions/ExpandCollapse.js +120 -0
  96. package/lib-module/TermsAndConditions/TermsAndConditions.js +193 -0
  97. package/lib-module/TermsAndConditions/dictionary.js +12 -0
  98. package/lib-module/TermsAndConditions/index.js +1 -0
  99. package/lib-module/Testimonial/Testimonial.js +204 -0
  100. package/lib-module/Testimonial/index.js +2 -0
  101. package/lib-module/Toast/Toast.js +15 -8
  102. package/lib-module/Video/ControlBar/ControlBar.js +292 -0
  103. package/lib-module/Video/ControlBar/Controls/VideoButton/VideoButton.js +74 -0
  104. package/lib-module/Video/ControlBar/Controls/VideoMenu/VideoMenu.js +167 -0
  105. package/lib-module/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.js +201 -0
  106. package/lib-module/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.js +193 -0
  107. package/lib-module/Video/MiddleControlButton/MiddleControlButton.js +72 -0
  108. package/lib-module/Video/Video.js +1042 -0
  109. package/lib-module/Video/index.js +2 -0
  110. package/lib-module/Video/videoText.js +55 -0
  111. package/lib-module/WebVideo/WebVideo.js +144 -0
  112. package/lib-module/WebVideo/index.js +2 -0
  113. package/lib-module/baseExports.js +1 -1
  114. package/lib-module/index.js +10 -0
  115. package/lib-module/shared/VideoSplash/SplashButton/SplashButton.js +85 -0
  116. package/lib-module/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.js +216 -0
  117. package/lib-module/shared/VideoSplash/VideoSplash.js +65 -0
  118. package/lib-module/shared/VideoSplash/helpers.js +23 -0
  119. package/lib-module/utils/index.js +2 -1
  120. package/package.json +7 -5
  121. package/src/Autocomplete/Autocomplete.jsx +354 -0
  122. package/src/Autocomplete/Loading.jsx +18 -0
  123. package/src/Autocomplete/Suggestions.jsx +52 -0
  124. package/src/Autocomplete/constants.js +6 -0
  125. package/src/Autocomplete/dictionary.js +12 -0
  126. package/src/Autocomplete/index.js +3 -0
  127. package/src/Breadcrumbs/Breadcrumbs.jsx +4 -3
  128. package/src/Callout/Callout.jsx +1 -1
  129. package/src/Card/Card.jsx +170 -0
  130. package/src/Card/CardContent.jsx +88 -0
  131. package/src/Card/CardFooter.jsx +70 -0
  132. package/src/Card/index.js +3 -0
  133. package/src/Countdown/Countdown.jsx +144 -0
  134. package/src/Countdown/Segment.jsx +69 -0
  135. package/src/Countdown/constants.js +4 -0
  136. package/src/Countdown/dictionary.js +22 -0
  137. package/src/Countdown/index.js +3 -0
  138. package/src/Countdown/types.js +23 -0
  139. package/src/Countdown/useCountdown.js +34 -0
  140. package/src/Footnote/Footnote.jsx +73 -23
  141. package/src/Modal/ModalContent.jsx +8 -4
  142. package/src/OptimizeImage/OptimizeImage.jsx +131 -0
  143. package/src/OptimizeImage/index.js +3 -0
  144. package/src/OptimizeImage/utils/getFallbackUrl.js +9 -0
  145. package/src/OptimizeImage/utils/getOptimizedUrl.js +30 -0
  146. package/src/OptimizeImage/utils/hasWebpSupport.js +33 -0
  147. package/src/OptimizeImage/utils/index.js +5 -0
  148. package/src/OptimizeImage/utils/isSvgUrl.js +3 -0
  149. package/src/QuantitySelector/QuantitySelector.jsx +245 -0
  150. package/src/QuantitySelector/dictionary.js +27 -0
  151. package/src/QuantitySelector/index.js +3 -0
  152. package/src/QuantitySelector/styles.js +83 -0
  153. package/src/StoryCard/StoryCard.jsx +198 -0
  154. package/src/StoryCard/index.js +3 -0
  155. package/src/TermsAndConditions/ExpandCollapse.jsx +106 -0
  156. package/src/TermsAndConditions/TermsAndConditions.jsx +161 -0
  157. package/src/TermsAndConditions/dictionary.js +12 -0
  158. package/src/TermsAndConditions/index.js +1 -0
  159. package/src/Testimonial/Testimonial.jsx +169 -0
  160. package/src/Testimonial/index.js +3 -0
  161. package/src/Toast/Toast.jsx +12 -5
  162. package/src/Video/ControlBar/ControlBar.jsx +261 -0
  163. package/src/Video/ControlBar/Controls/VideoButton/VideoButton.jsx +61 -0
  164. package/src/Video/ControlBar/Controls/VideoMenu/VideoMenu.jsx +159 -0
  165. package/src/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.jsx +185 -0
  166. package/src/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.jsx +184 -0
  167. package/src/Video/MiddleControlButton/MiddleControlButton.jsx +64 -0
  168. package/src/Video/Video.jsx +988 -0
  169. package/src/Video/index.js +3 -0
  170. package/src/Video/videoText.js +58 -0
  171. package/src/WebVideo/WebVideo.jsx +131 -0
  172. package/src/WebVideo/index.js +3 -0
  173. package/src/baseExports.js +0 -1
  174. package/src/index.js +10 -0
  175. package/src/shared/VideoSplash/SplashButton/SplashButton.jsx +64 -0
  176. package/src/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.jsx +128 -0
  177. package/src/shared/VideoSplash/VideoSplash.jsx +50 -0
  178. package/src/shared/VideoSplash/helpers.js +27 -0
  179. package/src/utils/index.js +10 -1
  180. package/types/Autocomplete.d.ts +32 -0
  181. package/types/Card.d.ts +45 -0
  182. package/types/ControlBar.d.ts +59 -0
  183. package/types/MiddleControlButton.d.ts +15 -0
  184. package/types/Video.d.ts +39 -0
  185. package/types/VideoButton.d.ts +14 -0
  186. package/types/VideoMenu.d.ts +16 -0
  187. package/types/VideoProgressBar.d.ts +17 -0
  188. package/types/VolumeSlider.d.ts +20 -0
@@ -67,11 +67,16 @@ const animation = (props) => css`
67
67
  animation: ${transform('fill')(props.animationFillColorBefore, props.animationFillColorAfter)}
68
68
  1s ease-in-out 3s forwards;
69
69
  }
70
+ & > a div {
71
+ animation: ${transform('color')(props.animationFillColorBefore, props.animationFillColorAfter)}
72
+ 1s ease-in-out 3s forwards;
73
+ }
70
74
  `
71
75
 
72
76
  const ToastContainer = styled.div`
73
77
  display: flex;
74
78
  justify-content: center;
79
+ align-items: center;
75
80
  background: ${({ containerBackgroundColor }) => containerBackgroundColor};
76
81
  gap: ${({ containerGap }) => containerGap};
77
82
  height: 0;
@@ -92,11 +97,13 @@ const Toast = ({ toast, copy, headline, link, tokens, variant = {}, ...rest }) =
92
97
  animationBackgroundColorBefore,
93
98
  animationBackgroundColorAfter,
94
99
  animationColorBefore,
95
- animationColorAfter,
96
- animationFillColorBefore,
97
- animationFillColorAfter
100
+ animationColorAfter
98
101
  } = useThemeTokens('Toast', tokens, variant)
99
102
 
103
+ // inherit ChevronLink tokens for animation colors
104
+ const { color: chevronDefaultColor } = useThemeTokens('ChevronLink', {}, {})
105
+ const { color: chevronInverseColor } = useThemeTokens('ChevronLink', {}, { inverse: true })
106
+
100
107
  if (!toast) {
101
108
  return null
102
109
  }
@@ -115,8 +122,8 @@ const Toast = ({ toast, copy, headline, link, tokens, variant = {}, ...rest }) =
115
122
  animationBackgroundColorAfter={animationBackgroundColorAfter}
116
123
  animationColorBefore={animationColorBefore}
117
124
  animationColorAfter={animationColorAfter}
118
- animationFillColorBefore={animationFillColorBefore}
119
- animationFillColorAfter={animationFillColorAfter}
125
+ animationFillColorBefore={chevronInverseColor}
126
+ animationFillColorAfter={chevronDefaultColor}
120
127
  {...selectProps(rest)}
121
128
  >
122
129
  {headline && <Typography variant={{ bold: true, inverse: true }}>{headline}</Typography>}
@@ -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
@@ -0,0 +1,159 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import styled from 'styled-components'
4
+
5
+ import {
6
+ StackView,
7
+ Typography,
8
+ useThemeTokens,
9
+ selectSystemProps,
10
+ Icon
11
+ } from '@telus-uds/components-base'
12
+
13
+ import videoText from '../../../videoText'
14
+ import { htmlAttrs } from '../../../../utils'
15
+
16
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
17
+
18
+ const MenuContainer = styled.div(({ padding, background }) => ({
19
+ width: 114,
20
+ padding,
21
+ backgroundColor: background,
22
+ borderRadius: 5
23
+ }))
24
+
25
+ const MenuButton = styled.button({
26
+ background: 'none',
27
+ border: 'none',
28
+ padding: 0,
29
+
30
+ width: '100%',
31
+ cursor: 'pointer',
32
+ display: 'flex',
33
+ justifyContent: 'space-between',
34
+ verticalAlign: 'middle'
35
+ })
36
+
37
+ const CheckmarkContainer = styled.div(
38
+ ({
39
+ isSelectedItem,
40
+ checkMarkWidth,
41
+ checkMarkHeight,
42
+ checkMarkSelectedColor,
43
+ checkMarkHoverColor,
44
+ checkMarkFocusColor,
45
+ checkMarkUnselectedColor
46
+ }) => ({
47
+ width: checkMarkWidth,
48
+ height: checkMarkHeight,
49
+ marginTop: 2,
50
+ outline: 'none',
51
+
52
+ [`${MenuButton} && svg`]: {
53
+ fill: isSelectedItem ? checkMarkSelectedColor : checkMarkUnselectedColor
54
+ },
55
+ [`${MenuButton}:hover && svg`]: {
56
+ fill: isSelectedItem ? checkMarkSelectedColor : checkMarkHoverColor
57
+ },
58
+ [`${MenuButton}:focus && svg`]: {
59
+ fill: isSelectedItem ? checkMarkSelectedColor : checkMarkFocusColor
60
+ }
61
+ })
62
+ )
63
+
64
+ const VideoMenu = ({
65
+ menuLabel,
66
+ menuOptions,
67
+ setSelection,
68
+ selectedItem,
69
+ copy,
70
+ tokens,
71
+ variant,
72
+ ...rest
73
+ }) => {
74
+ const {
75
+ padding,
76
+ background,
77
+ checkMarkWidth,
78
+ checkMarkHeight,
79
+ checkMarkSelectedColor,
80
+ checkMarkHoverColor,
81
+ checkMarkFocusColor,
82
+ checkMarkUnselectedColor,
83
+ checkMarkIcon
84
+ } = useThemeTokens('VideoMenu', tokens, variant)
85
+
86
+ const getMenuItems = () => {
87
+ return menuOptions.map((option) => {
88
+ if (option.name) {
89
+ return (
90
+ <MenuButton
91
+ aria-haspopup="true"
92
+ role="menuitem"
93
+ aria-label={`${option.name} ${menuLabel}. ${
94
+ selectedItem === option.value
95
+ ? videoText[copy].itemSelected
96
+ : videoText[copy].itemUnselected
97
+ }`}
98
+ selectedItem={selectedItem}
99
+ itemValue={option.value}
100
+ onClick={() => {
101
+ if (selectedItem !== option.value) {
102
+ setSelection(option.value)
103
+ }
104
+ }}
105
+ key={option.value}
106
+ >
107
+ <Typography variant={{ bold: true, inverse: true }}>{option.name}</Typography>
108
+ <CheckmarkContainer
109
+ isSelectedItem={option.value === selectedItem}
110
+ checkMarkWidth={checkMarkWidth}
111
+ checkMarkHeight={checkMarkHeight}
112
+ checkMarkSelectedColor={checkMarkSelectedColor}
113
+ checkMarkHoverColor={checkMarkHoverColor}
114
+ checkMarkFocusColor={checkMarkFocusColor}
115
+ checkMarkUnselectedColor={checkMarkUnselectedColor}
116
+ >
117
+ <Icon icon={checkMarkIcon} />
118
+ </CheckmarkContainer>
119
+ </MenuButton>
120
+ )
121
+ }
122
+ return null
123
+ })
124
+ }
125
+
126
+ const handleOnKeyDown = (event) => {
127
+ const key = event.key || event.keyCode
128
+
129
+ // Disables playing by space bar, as that can be used to click a button
130
+ if (key === ' ' || key === 32) {
131
+ event.stopPropagation()
132
+ }
133
+ }
134
+
135
+ return (
136
+ <MenuContainer
137
+ onKeyDown={handleOnKeyDown}
138
+ padding={padding}
139
+ background={background}
140
+ {...selectProps(rest)}
141
+ >
142
+ <StackView space={2} divider>
143
+ <Typography variant={{ bold: true, inverse: true }}>{menuLabel}</Typography>
144
+ <div role="menu">{getMenuItems()}</div>
145
+ </StackView>
146
+ </MenuContainer>
147
+ )
148
+ }
149
+
150
+ VideoMenu.propTypes = {
151
+ ...selectedSystemPropTypes,
152
+ menuLabel: PropTypes.string.isRequired,
153
+ menuOptions: PropTypes.array.isRequired,
154
+ setSelection: PropTypes.func.isRequired,
155
+ selectedItem: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
156
+ copy: PropTypes.oneOf(['en', 'fr']).isRequired
157
+ }
158
+
159
+ export default VideoMenu
@@ -0,0 +1,185 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import styled from 'styled-components'
4
+
5
+ import { Typography, useThemeTokens, selectSystemProps } from '@telus-uds/components-base'
6
+
7
+ import videoText from '../../../videoText'
8
+ import { htmlAttrs } from '../../../../utils'
9
+
10
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
11
+
12
+ const ProgressBarContainer = styled.div({
13
+ display: 'flex',
14
+ width: '100%',
15
+ alignItems: 'center'
16
+ })
17
+
18
+ const sharedStyles = ({
19
+ thumbHeight,
20
+ thumbWidth,
21
+ thumbBackground,
22
+ trackGradientStart,
23
+ trackGradientEnd
24
+ }) => ({
25
+ thumb: {
26
+ appearance: 'none',
27
+ height: thumbHeight,
28
+ width: thumbWidth,
29
+ borderRadius: '50%',
30
+ background: thumbBackground,
31
+ cursor: 'pointer',
32
+ marginTop: -3,
33
+
34
+ '&:hover': {
35
+ transform: 'scale(1.5)'
36
+ }
37
+ },
38
+ track: (videoBufferDisplay) => ({
39
+ width: '100%',
40
+ height: 2,
41
+ cursor: 'pointer',
42
+ borderRadius: 1.3,
43
+ background: `linear-gradient(to right, ${trackGradientStart} 0%,${trackGradientEnd} ${
44
+ videoBufferDisplay - 0.01
45
+ }% ,rgba(255,255,255,0.5) ${videoBufferDisplay}%)` // TODO: replace with opaque white from palette
46
+ })
47
+ })
48
+
49
+ const StyledProgressBar = styled.input.attrs(({ videoCurrentTime }) => ({
50
+ value: videoCurrentTime
51
+ }))(({ videoBufferDisplay, rangeBackground, ...sharedProps }) => ({
52
+ width: '100%',
53
+ cursor: 'pointer',
54
+ padding: '5px 0',
55
+ 'input[type=range]&': {
56
+ appearance: 'none',
57
+ width: '100%',
58
+ background: rangeBackground
59
+ },
60
+
61
+ 'input[type=range]&:focus': {
62
+ outline: 'none'
63
+ },
64
+
65
+ 'input[type=range]&::-webkit-slider-thumb': sharedStyles(sharedProps).thumb,
66
+ 'input[type=range]&::-moz-range-thumb': {
67
+ ...sharedStyles(sharedProps).thumb,
68
+ border: 'none'
69
+ },
70
+ 'input[type=range]&::-ms-thumb': {
71
+ ...sharedStyles(sharedProps).thumb,
72
+ margin: 0,
73
+ border: 'none'
74
+ },
75
+
76
+ 'input[type=range]&::-webkit-slider-runnable-track':
77
+ sharedStyles(sharedProps).track(videoBufferDisplay),
78
+ 'input[type=range]&::-moz-range-track': sharedStyles(sharedProps).track(videoBufferDisplay),
79
+ 'input[type=range]&::-ms-track': {
80
+ ...sharedStyles(sharedProps).track(videoBufferDisplay),
81
+ margin: '6px 0',
82
+ border: 'none'
83
+ },
84
+
85
+ 'input[type=range]&::-ms-fill-lower': {
86
+ background: rangeBackground
87
+ },
88
+ 'input[type=range]&::-ms-tooltip': {
89
+ display: 'none'
90
+ }
91
+ }))
92
+
93
+ const StyledTimestamp = styled.span(({ margin }) => ({
94
+ margin
95
+ }))
96
+ // TODO: unify with the helper from `VideoSplash`
97
+ function getTimestamp(duration) {
98
+ const minutes = Math.floor(duration / 60)
99
+ const seconds = Math.floor(duration - 60 * minutes)
100
+
101
+ return `${minutes}:${seconds < 10 ? 0 : ''}${seconds}`
102
+ }
103
+
104
+ const VideoProgressBar = ({
105
+ copy = 'en',
106
+ videoLength,
107
+ videoCurrentTime,
108
+ videoBufferEnd,
109
+ setSeek,
110
+ resetInactivityTimer,
111
+ tokens,
112
+ variant,
113
+ ...rest
114
+ }) => {
115
+ const {
116
+ thumbHeight,
117
+ thumbWidth,
118
+ thumbBackground,
119
+ timestampMarginLeft,
120
+ timestampMarginRight,
121
+ trackGradientStart,
122
+ trackGradientEnd,
123
+ rangeBackground
124
+ } = useThemeTokens('VideoProgressBar', tokens, variant)
125
+
126
+ const videoProgressBar = React.createRef()
127
+
128
+ const handleVideoSkip = () => {
129
+ setSeek(videoProgressBar.current.value)
130
+ }
131
+
132
+ const videoBufferDisplay = (videoBufferEnd / videoLength) * 100
133
+
134
+ const isVideoUnplayed = videoCurrentTime === -1
135
+ const currentTime = isVideoUnplayed ? 0 : videoCurrentTime
136
+ const remainingTime = isVideoUnplayed ? videoLength : videoLength - videoCurrentTime
137
+
138
+ const currentTimestamp = getTimestamp(currentTime)
139
+ const remainingTimestamp = getTimestamp(remainingTime)
140
+
141
+ const sharedProps = {
142
+ thumbWidth,
143
+ thumbHeight,
144
+ thumbBackground,
145
+ trackGradientStart,
146
+ trackGradientEnd,
147
+ rangeBackground
148
+ }
149
+
150
+ return (
151
+ <ProgressBarContainer {...selectProps(rest)}>
152
+ <StyledTimestamp margin={`0 ${timestampMarginRight}px 0 ${timestampMarginLeft}px`}>
153
+ <Typography variant={{ inverse: true }}>{currentTimestamp}</Typography>
154
+ </StyledTimestamp>
155
+ <StyledProgressBar
156
+ aria-label={videoText[copy].videoProgressBarLabel}
157
+ type="range"
158
+ step="any"
159
+ max={videoLength}
160
+ videoCurrentTime={videoCurrentTime}
161
+ onChange={handleVideoSkip}
162
+ onFocus={resetInactivityTimer}
163
+ videoBufferDisplay={videoBufferDisplay}
164
+ ref={videoProgressBar}
165
+ tabIndex="-1"
166
+ {...sharedProps}
167
+ />
168
+ <StyledTimestamp>
169
+ <Typography variant={{ inverse: true }}>{remainingTimestamp}</Typography>
170
+ </StyledTimestamp>
171
+ </ProgressBarContainer>
172
+ )
173
+ }
174
+
175
+ VideoProgressBar.propTypes = {
176
+ ...selectedSystemPropTypes,
177
+ copy: PropTypes.oneOf(['en', 'fr']),
178
+ videoLength: PropTypes.number.isRequired,
179
+ videoCurrentTime: PropTypes.number.isRequired,
180
+ videoBufferEnd: PropTypes.number.isRequired,
181
+ setSeek: PropTypes.func.isRequired,
182
+ resetInactivityTimer: PropTypes.func.isRequired
183
+ }
184
+
185
+ export default VideoProgressBar