@telus-uds/components-web 2.33.2 → 2.34.1

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 (192) hide show
  1. package/CHANGELOG.md +29 -3
  2. package/lib/Badge/Badge.js +4 -2
  3. package/lib/BlockQuote/BlockQuote.js +4 -2
  4. package/lib/Breadcrumbs/Breadcrumbs.js +7 -5
  5. package/lib/Breadcrumbs/Item/Item.js +2 -13
  6. package/lib/Callout/Callout.js +4 -2
  7. package/lib/Card/Card.js +3 -5
  8. package/lib/Card/CardContent.js +4 -2
  9. package/lib/Countdown/Countdown.js +2 -6
  10. package/lib/Countdown/Segment.js +4 -2
  11. package/lib/DatePicker/CalendarContainer.js +2 -2
  12. package/lib/DatePicker/DatePicker.js +21 -35
  13. package/lib/Disclaimer/Disclaimer.js +4 -2
  14. package/lib/ExpandCollapseMini/ExpandCollapseMini.js +5 -11
  15. package/lib/ExpandCollapseMini/ExpandCollapseMiniControl.js +4 -2
  16. package/lib/Footnote/Footnote.js +32 -37
  17. package/lib/Footnote/FootnoteLink.js +9 -6
  18. package/lib/IconButton/IconButton.js +4 -11
  19. package/lib/Image/Image.js +5 -3
  20. package/lib/List/ListItem.js +2 -7
  21. package/lib/NavigationBar/NavigationBar.js +8 -18
  22. package/lib/NavigationBar/NavigationItem.js +4 -9
  23. package/lib/NavigationBar/NavigationSubMenu.js +8 -7
  24. package/lib/NavigationBar/index.js +2 -0
  25. package/lib/OptimizeImage/OptimizeImage.js +8 -8
  26. package/lib/OrderedList/Item.js +3 -9
  27. package/lib/OrderedList/OrderedList.js +5 -13
  28. package/lib/OrderedList/OrderedListBase.js +3 -8
  29. package/lib/Paragraph/Paragraph.js +5 -3
  30. package/lib/PreviewCard/PreviewCard.js +3 -5
  31. package/lib/PriceLockup/PriceLockup.js +4 -2
  32. package/lib/Progress/ProgressBar.js +4 -2
  33. package/lib/QuantitySelector/QuantitySelector.js +21 -24
  34. package/lib/QuantitySelector/SideButton.js +12 -20
  35. package/lib/ResponsiveImage/ResponsiveImage.js +4 -2
  36. package/lib/Ribbon/Ribbon.js +4 -2
  37. package/lib/SkeletonProvider/SkeletonImage.js +5 -3
  38. package/lib/SkeletonProvider/SkeletonProvider.js +3 -5
  39. package/lib/SkeletonProvider/SkeletonTypography.js +5 -3
  40. package/lib/Span/Span.js +5 -3
  41. package/lib/Spinner/Spinner.js +4 -2
  42. package/lib/Spinner/SpinnerContent.js +4 -2
  43. package/lib/StoryCard/StoryCard.js +3 -5
  44. package/lib/Table/Body.js +4 -2
  45. package/lib/Table/Cell.js +5 -3
  46. package/lib/Table/Header.js +6 -6
  47. package/lib/Table/Row.js +6 -6
  48. package/lib/Table/SubHeading.js +6 -6
  49. package/lib/Table/Table.js +6 -8
  50. package/lib/TermsAndConditions/ExpandCollapse.js +2 -7
  51. package/lib/TermsAndConditions/TermsAndConditions.js +5 -14
  52. package/lib/Testimonial/Testimonial.js +4 -2
  53. package/lib/Toast/Toast.js +4 -2
  54. package/lib/Video/Video.js +19 -55
  55. package/lib/VideoPicker/VideoPicker.js +38 -9
  56. package/lib/VideoPicker/VideoPickerPlayer.js +4 -2
  57. package/lib/VideoPicker/VideoPickerThumbnail.js +4 -2
  58. package/lib/VideoPicker/VideoSlider.js +7 -7
  59. package/lib/WaffleGrid/WaffleGrid.js +4 -2
  60. package/lib/WebVideo/WebVideo.js +51 -13
  61. package/lib/WebVideo/utils/index.js +58 -0
  62. package/lib/baseExports.js +6 -0
  63. package/lib/utils/theming/with-client-theme.js +1 -1
  64. package/lib/utils/theming/with-server-theme.js +1 -1
  65. package/lib-module/Badge/Badge.js +4 -2
  66. package/lib-module/BlockQuote/BlockQuote.js +4 -2
  67. package/lib-module/Breadcrumbs/Breadcrumbs.js +7 -5
  68. package/lib-module/Breadcrumbs/Item/Item.js +2 -11
  69. package/lib-module/Callout/Callout.js +4 -2
  70. package/lib-module/Card/Card.js +2 -3
  71. package/lib-module/Card/CardContent.js +4 -2
  72. package/lib-module/Countdown/Countdown.js +2 -3
  73. package/lib-module/Countdown/Segment.js +4 -2
  74. package/lib-module/DatePicker/CalendarContainer.js +2 -2
  75. package/lib-module/DatePicker/DatePicker.js +21 -33
  76. package/lib-module/Disclaimer/Disclaimer.js +4 -2
  77. package/lib-module/ExpandCollapseMini/ExpandCollapseMini.js +5 -9
  78. package/lib-module/ExpandCollapseMini/ExpandCollapseMiniControl.js +4 -2
  79. package/lib-module/Footnote/Footnote.js +31 -36
  80. package/lib-module/Footnote/FootnoteLink.js +9 -7
  81. package/lib-module/IconButton/IconButton.js +4 -9
  82. package/lib-module/Image/Image.js +5 -3
  83. package/lib-module/List/ListItem.js +2 -5
  84. package/lib-module/NavigationBar/NavigationBar.js +9 -17
  85. package/lib-module/NavigationBar/NavigationItem.js +5 -8
  86. package/lib-module/NavigationBar/NavigationSubMenu.js +9 -6
  87. package/lib-module/NavigationBar/index.js +2 -0
  88. package/lib-module/OptimizeImage/OptimizeImage.js +8 -6
  89. package/lib-module/OrderedList/Item.js +3 -7
  90. package/lib-module/OrderedList/OrderedList.js +6 -12
  91. package/lib-module/OrderedList/OrderedListBase.js +3 -6
  92. package/lib-module/Paragraph/Paragraph.js +6 -4
  93. package/lib-module/PreviewCard/PreviewCard.js +2 -3
  94. package/lib-module/PriceLockup/PriceLockup.js +4 -2
  95. package/lib-module/Progress/ProgressBar.js +4 -2
  96. package/lib-module/QuantitySelector/QuantitySelector.js +22 -23
  97. package/lib-module/QuantitySelector/SideButton.js +13 -21
  98. package/lib-module/ResponsiveImage/ResponsiveImage.js +4 -2
  99. package/lib-module/Ribbon/Ribbon.js +4 -2
  100. package/lib-module/SkeletonProvider/SkeletonImage.js +5 -3
  101. package/lib-module/SkeletonProvider/SkeletonProvider.js +3 -3
  102. package/lib-module/SkeletonProvider/SkeletonTypography.js +5 -3
  103. package/lib-module/Span/Span.js +6 -4
  104. package/lib-module/Spinner/Spinner.js +4 -2
  105. package/lib-module/Spinner/SpinnerContent.js +4 -2
  106. package/lib-module/StoryCard/StoryCard.js +2 -3
  107. package/lib-module/Table/Body.js +4 -2
  108. package/lib-module/Table/Cell.js +5 -3
  109. package/lib-module/Table/Header.js +6 -4
  110. package/lib-module/Table/Row.js +6 -4
  111. package/lib-module/Table/SubHeading.js +6 -4
  112. package/lib-module/Table/Table.js +6 -6
  113. package/lib-module/TermsAndConditions/ExpandCollapse.js +2 -5
  114. package/lib-module/TermsAndConditions/TermsAndConditions.js +5 -12
  115. package/lib-module/Testimonial/Testimonial.js +4 -2
  116. package/lib-module/Toast/Toast.js +4 -2
  117. package/lib-module/Video/Video.js +19 -53
  118. package/lib-module/VideoPicker/VideoPicker.js +37 -8
  119. package/lib-module/VideoPicker/VideoPickerPlayer.js +4 -2
  120. package/lib-module/VideoPicker/VideoPickerThumbnail.js +4 -2
  121. package/lib-module/VideoPicker/VideoSlider.js +7 -5
  122. package/lib-module/WaffleGrid/WaffleGrid.js +4 -2
  123. package/lib-module/WebVideo/WebVideo.js +51 -11
  124. package/lib-module/WebVideo/utils/index.js +50 -0
  125. package/lib-module/baseExports.js +1 -1
  126. package/lib-module/utils/theming/with-client-theme.js +2 -2
  127. package/lib-module/utils/theming/with-server-theme.js +2 -2
  128. package/package.json +3 -3
  129. package/src/Badge/Badge.jsx +5 -2
  130. package/src/BlockQuote/BlockQuote.jsx +120 -112
  131. package/src/Breadcrumbs/Breadcrumbs.jsx +84 -77
  132. package/src/Breadcrumbs/Item/Item.jsx +2 -9
  133. package/src/Callout/Callout.jsx +37 -40
  134. package/src/Card/Card.jsx +2 -3
  135. package/src/Card/CardContent.jsx +19 -14
  136. package/src/Countdown/Countdown.jsx +72 -71
  137. package/src/Countdown/Segment.jsx +40 -28
  138. package/src/DatePicker/CalendarContainer.jsx +2 -2
  139. package/src/DatePicker/DatePicker.jsx +21 -34
  140. package/src/Disclaimer/Disclaimer.jsx +5 -3
  141. package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +37 -40
  142. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +52 -44
  143. package/src/Footnote/Footnote.jsx +32 -38
  144. package/src/Footnote/FootnoteLink.jsx +45 -49
  145. package/src/IconButton/IconButton.jsx +19 -20
  146. package/src/Image/Image.jsx +40 -43
  147. package/src/List/ListItem.jsx +3 -5
  148. package/src/NavigationBar/NavigationBar.jsx +9 -18
  149. package/src/NavigationBar/NavigationItem.jsx +6 -5
  150. package/src/NavigationBar/NavigationSubMenu.jsx +104 -88
  151. package/src/NavigationBar/index.js +3 -0
  152. package/src/OptimizeImage/OptimizeImage.jsx +48 -41
  153. package/src/OrderedList/Item.jsx +34 -35
  154. package/src/OrderedList/OrderedList.jsx +4 -6
  155. package/src/OrderedList/OrderedListBase.jsx +2 -3
  156. package/src/Paragraph/Paragraph.jsx +21 -16
  157. package/src/PreviewCard/PreviewCard.jsx +2 -3
  158. package/src/PriceLockup/PriceLockup.jsx +143 -136
  159. package/src/Progress/ProgressBar.jsx +11 -3
  160. package/src/QuantitySelector/QuantitySelector.jsx +162 -154
  161. package/src/QuantitySelector/SideButton.jsx +52 -56
  162. package/src/ResponsiveImage/ResponsiveImage.jsx +16 -22
  163. package/src/Ribbon/Ribbon.jsx +85 -83
  164. package/src/SkeletonProvider/SkeletonImage.jsx +24 -15
  165. package/src/SkeletonProvider/SkeletonProvider.jsx +3 -3
  166. package/src/SkeletonProvider/SkeletonTypography.jsx +18 -13
  167. package/src/Span/Span.jsx +7 -5
  168. package/src/Spinner/Spinner.jsx +86 -79
  169. package/src/Spinner/SpinnerContent.jsx +31 -33
  170. package/src/StoryCard/StoryCard.jsx +2 -3
  171. package/src/Table/Body.jsx +5 -3
  172. package/src/Table/Cell.jsx +77 -67
  173. package/src/Table/Header.jsx +7 -5
  174. package/src/Table/Row.jsx +7 -5
  175. package/src/Table/SubHeading.jsx +7 -5
  176. package/src/Table/Table.jsx +6 -6
  177. package/src/TermsAndConditions/ExpandCollapse.jsx +2 -6
  178. package/src/TermsAndConditions/TermsAndConditions.jsx +5 -13
  179. package/src/Testimonial/Testimonial.jsx +148 -137
  180. package/src/Toast/Toast.jsx +68 -63
  181. package/src/Video/Video.jsx +25 -45
  182. package/src/VideoPicker/VideoPicker.jsx +114 -75
  183. package/src/VideoPicker/VideoPickerPlayer.jsx +13 -9
  184. package/src/VideoPicker/VideoPickerThumbnail.jsx +102 -94
  185. package/src/VideoPicker/VideoSlider.jsx +8 -6
  186. package/src/WaffleGrid/WaffleGrid.jsx +36 -40
  187. package/src/WebVideo/WebVideo.jsx +114 -60
  188. package/src/WebVideo/utils/index.js +56 -0
  189. package/src/baseExports.js +1 -0
  190. package/src/utils/theming/with-client-theme.jsx +2 -2
  191. package/src/utils/theming/with-server-theme.jsx +2 -2
  192. package/types/WebVideo.d.ts +2 -1
@@ -88,105 +88,113 @@ const ThumbnailTitleContainer = styled.div`
88
88
  overflow: hidden;
89
89
  `
90
90
 
91
- const VideoPickerThumbnail = ({
92
- videoPlayerRef,
93
- selectedVideoId,
94
- video,
95
- onSelectVideo,
96
- layout = 'vertical',
97
- isFramed,
98
- itemPositions,
99
- index,
100
- width = '100%'
101
- }) => {
102
- const viewport = useViewport()
103
- const getTokens = useThemeTokensCallback('VideoPickerThumbnail', {}, {})
104
-
105
- const { timestamp } = getTimestamp(video.videoLength, video.copy)
106
- const isPlaying = selectedVideoId === video.videoId
107
-
108
- const renderThumbnailImage = (themeTokens) => {
109
- return (
110
- <VideoThumbnail {...themeTokens} isPlaying={isPlaying} layout={layout}>
111
- <VideoSplash
112
- simpleMode
113
- poster={
114
- video.posterSrc || `https://img.youtube.com/vi/${video.videoId}/maxresdefault.jpg`
115
- }
116
- videoLength={video.videoLength}
117
- copy={video.copy}
118
- />
119
- </VideoThumbnail>
91
+ const VideoPickerThumbnail = React.forwardRef(
92
+ (
93
+ {
94
+ videoPlayerRef,
95
+ selectedVideoId,
96
+ video,
97
+ onSelectVideo,
98
+ layout = 'vertical',
99
+ isFramed,
100
+ itemPositions,
101
+ index,
102
+ width = '100%'
103
+ },
104
+ ref
105
+ ) => {
106
+ const viewport = useViewport()
107
+ const getTokens = useThemeTokensCallback('VideoPickerThumbnail', {}, {})
108
+
109
+ const { timestamp } = getTimestamp(video.videoLength, video.copy)
110
+ const isPlaying = selectedVideoId === video.videoId
111
+
112
+ const renderThumbnailImage = (themeTokens) => {
113
+ return (
114
+ <VideoThumbnail {...themeTokens} isPlaying={isPlaying} layout={layout}>
115
+ <VideoSplash
116
+ simpleMode
117
+ poster={
118
+ video.posterSrc || `https://img.youtube.com/vi/${video.videoId}/maxresdefault.jpg`
119
+ }
120
+ videoLength={video.videoLength}
121
+ copy={video.copy}
122
+ />
123
+ </VideoThumbnail>
124
+ )
125
+ }
126
+
127
+ const renderThumbnailInfo = ({ titleColor, subTitleColor }) => (
128
+ <StackView space={2} tokens={{ flexShrink: 1 }}>
129
+ <ThumbnailTitleContainer viewport={viewport}>
130
+ <Typography variant={{ bold: true }} tokens={{ color: titleColor }}>
131
+ {video.title}
132
+ </Typography>
133
+ </ThumbnailTitleContainer>
134
+ {viewport !== viewports.xs && (
135
+ <Typography variant={{ size: 'micro' }} tokens={{ color: subTitleColor }}>
136
+ {timestamp}
137
+ </Typography>
138
+ )}
139
+ </StackView>
120
140
  )
121
- }
122
141
 
123
- const renderThumbnailInfo = ({ titleColor, subTitleColor }) => (
124
- <StackView space={2} tokens={{ flexShrink: 1 }}>
125
- <ThumbnailTitleContainer viewport={viewport}>
126
- <Typography variant={{ bold: true }} tokens={{ color: titleColor }}>
127
- {video.title}
128
- </Typography>
129
- </ThumbnailTitleContainer>
130
- {viewport !== viewports.xs && (
131
- <Typography variant={{ size: 'micro' }} tokens={{ color: subTitleColor }}>
132
- {timestamp}
133
- </Typography>
134
- )}
135
- </StackView>
136
- )
137
-
138
- const handleLayout =
139
- itemPositions !== undefined
140
- ? getItemPositionLayoutHandler(itemPositions.positions, index)
141
- : undefined
142
-
143
- const onKeyPress = (event) => {
144
- if (['Space', 'Enter'].includes(event.key)) {
145
- onSelectVideo(video)
146
- const splashButton = videoPlayerRef.current?.querySelector('button')
147
- if (splashButton) splashButton.focus()
142
+ const handleLayout =
143
+ itemPositions !== undefined
144
+ ? getItemPositionLayoutHandler(itemPositions.positions, index)
145
+ : undefined
146
+
147
+ const onKeyPress = (event) => {
148
+ if (['Space', 'Enter'].includes(event.key)) {
149
+ onSelectVideo(video)
150
+ const splashButton = videoPlayerRef.current?.querySelector('button')
151
+ if (splashButton) splashButton.focus()
152
+ }
148
153
  }
154
+
155
+ return (
156
+ <Pressable
157
+ key={video.videoId}
158
+ onLayout={handleLayout}
159
+ onPress={() => onSelectVideo(video)}
160
+ testID={`thumbnail-container-${video.videoId}`}
161
+ onKeyPress={onKeyPress}
162
+ accessibilityRole="radio"
163
+ accessibilityState={{ checked: isPlaying }}
164
+ style={({ hovered: hover, focused: focus, pressed }) => {
165
+ const themeTokens = getTokens({ hover, focus, pressed, selected: isPlaying })
166
+
167
+ const rnStyles = createReactNativeStyles(themeTokens)
168
+ return [
169
+ rnStyles.container,
170
+ layout === 'horizontal' && rnStyles.horizontal,
171
+ isFramed && rnStyles.framed,
172
+ isFramed && index > 0 && rnStyles.framedLine,
173
+ { width },
174
+ { outline: 'none' }
175
+ ]
176
+ }}
177
+ ref={ref}
178
+ >
179
+ {({ hovered: hover, focused: focus, pressed }) => {
180
+ const themeTokens = getTokens({ hover, focus, pressed, selected: isPlaying })
181
+
182
+ return (
183
+ <StackView
184
+ space={layout === 'vertical' ? 2 : 3}
185
+ direction={layout === 'vertical' ? 'column' : 'row'}
186
+ >
187
+ <ImageContainer {...themeTokens}>{renderThumbnailImage(themeTokens)}</ImageContainer>
188
+ {renderThumbnailInfo(themeTokens)}
189
+ </StackView>
190
+ )
191
+ }}
192
+ </Pressable>
193
+ )
149
194
  }
195
+ )
150
196
 
151
- return (
152
- <Pressable
153
- key={video.videoId}
154
- onLayout={handleLayout}
155
- onPress={() => onSelectVideo(video)}
156
- testID={`thumbnail-container-${video.videoId}`}
157
- onKeyPress={onKeyPress}
158
- accessibilityRole="radio"
159
- accessibilityState={{ checked: isPlaying }}
160
- style={({ hovered: hover, focused: focus, pressed }) => {
161
- const themeTokens = getTokens({ hover, focus, pressed, selected: isPlaying })
162
-
163
- const rnStyles = createReactNativeStyles(themeTokens)
164
- return [
165
- rnStyles.container,
166
- layout === 'horizontal' && rnStyles.horizontal,
167
- isFramed && rnStyles.framed,
168
- isFramed && index > 0 && rnStyles.framedLine,
169
- { width },
170
- { outline: 'none' }
171
- ]
172
- }}
173
- >
174
- {({ hovered: hover, focused: focus, pressed }) => {
175
- const themeTokens = getTokens({ hover, focus, pressed, selected: isPlaying })
176
-
177
- return (
178
- <StackView
179
- space={layout === 'vertical' ? 2 : 3}
180
- direction={layout === 'vertical' ? 'column' : 'row'}
181
- >
182
- <ImageContainer {...themeTokens}>{renderThumbnailImage(themeTokens)}</ImageContainer>
183
- {renderThumbnailInfo(themeTokens)}
184
- </StackView>
185
- )
186
- }}
187
- </Pressable>
188
- )
189
- }
197
+ VideoPickerThumbnail.displayName = 'VideoPickerThumbnail'
190
198
 
191
199
  VideoPickerThumbnail.propTypes = {
192
200
  selectedVideoId: PropTypes.string,
@@ -1,4 +1,4 @@
1
- import React, { cloneElement, useState } from 'react'
1
+ import React from 'react'
2
2
  import {
3
3
  HorizontalScroll,
4
4
  HorizontalScrollButton,
@@ -12,10 +12,10 @@ import PropTypes from 'prop-types'
12
12
 
13
13
  const { useItemPositions } = horizontalScrollUtils
14
14
 
15
- const VideoSlider = ({ children }) => {
15
+ const VideoSlider = React.forwardRef(({ children }, ref) => {
16
16
  const viewport = useViewport()
17
17
  const [itemPositions] = useItemPositions()
18
- const [containerWidth, setContainerWidth] = useState(null)
18
+ const [containerWidth, setContainerWidth] = React.useState(null)
19
19
  const { previousIcon: PreviousIcon, nextIcon: NextIcon } = useThemeTokens('VideoPickerSlider')
20
20
 
21
21
  const onLayout = ({
@@ -42,7 +42,7 @@ const VideoSlider = ({ children }) => {
42
42
  const content = (
43
43
  <StackView space={5} direction="row" accessibilityRole="radiogroup" tokens={{ flexGrow: 1 }}>
44
44
  {React.Children.map(children, (child, index) =>
45
- cloneElement(child, {
45
+ React.cloneElement(child, {
46
46
  index,
47
47
  itemPositions,
48
48
  width: itemWidth
@@ -61,7 +61,7 @@ const VideoSlider = ({ children }) => {
61
61
  buttonClearance: 0
62
62
  }
63
63
  return (
64
- <View onLayout={onLayout} style={overflow}>
64
+ <View onLayout={onLayout} style={overflow} ref={ref}>
65
65
  {containerWidth === null ? (
66
66
  // Use a 100% width non-scrollable parent until containerWidth is known, to avoid flicker
67
67
  content
@@ -76,7 +76,9 @@ const VideoSlider = ({ children }) => {
76
76
  )}
77
77
  </View>
78
78
  )
79
- }
79
+ })
80
+
81
+ VideoSlider.displayName = 'VideoSlider'
80
82
 
81
83
  VideoSlider.propTypes = {
82
84
  children: PropTypes.node
@@ -64,48 +64,44 @@ const Center = styled('div')({
64
64
  /**
65
65
  * The WaffleGrid is used to show items in a waffle like manner with borders surrounding the element
66
66
  */
67
- const WaffleGrid = ({
68
- items,
69
- rowSize = null,
70
- LinkRouter,
71
- tokens,
72
- variant,
73
- linkRouterProps,
74
- ...rest
75
- }) => {
76
- const viewport = useViewport()
77
- const themeTokens = useThemeTokens('WaffleGrid', tokens, variant, { viewport })
78
- const currentRowSize = useResponsiveProp(rowSize)
67
+ const WaffleGrid = React.forwardRef(
68
+ ({ items, rowSize = null, LinkRouter, tokens, variant, linkRouterProps, ...rest }, ref) => {
69
+ const viewport = useViewport()
70
+ const themeTokens = useThemeTokens('WaffleGrid', tokens, variant, { viewport })
71
+ const currentRowSize = useResponsiveProp(rowSize)
79
72
 
80
- return (
81
- <Container {...selectProps(rest)}>
82
- {items.map((child) => (
83
- <Item
84
- {...themeTokens}
85
- key={child.href}
86
- rowSize={rowSize ? currentRowSize : themeTokens.rowSize}
87
- >
88
- <Link
89
- href={child.href}
90
- LinkRouter={child.LinkRouter || LinkRouter}
91
- linkRouterProps={{ ...linkRouterProps, ...child.linkRouterProps }}
73
+ return (
74
+ <Container ref={ref} {...selectProps(rest)}>
75
+ {items.map((child) => (
76
+ <Item
77
+ {...themeTokens}
78
+ key={child.href}
79
+ rowSize={rowSize ? currentRowSize : themeTokens.rowSize}
92
80
  >
93
- <Center>
94
- {typeof child.image === 'string' ? (
95
- // Assuming that string passed is the image URL
96
- <Image src={child.image} alt={child.imageAltText} width={96} />
97
- ) : (
98
- // Otherwise it must be an arbitrary content, which we just display by itself
99
- child.image
100
- )}
101
- <Typography variant={{ weight: 'semibold' }}>{child.text}</Typography>
102
- </Center>
103
- </Link>
104
- </Item>
105
- ))}
106
- </Container>
107
- )
108
- }
81
+ <Link
82
+ href={child.href}
83
+ LinkRouter={child.LinkRouter || LinkRouter}
84
+ linkRouterProps={{ ...linkRouterProps, ...child.linkRouterProps }}
85
+ >
86
+ <Center>
87
+ {typeof child.image === 'string' ? (
88
+ // Assuming that string passed is the image URL
89
+ <Image src={child.image} alt={child.imageAltText} width={96} />
90
+ ) : (
91
+ // Otherwise it must be an arbitrary content, which we just display by itself
92
+ child.image
93
+ )}
94
+ <Typography variant={{ weight: 'semibold' }}>{child.text}</Typography>
95
+ </Center>
96
+ </Link>
97
+ </Item>
98
+ ))}
99
+ </Container>
100
+ )
101
+ }
102
+ )
103
+
104
+ WaffleGrid.displayName = 'WaffleGrid'
109
105
 
110
106
  WaffleGrid.propTypes = {
111
107
  ...selectedSystemPropTypes,
@@ -1,10 +1,11 @@
1
1
  import { selectSystemProps } from '@telus-uds/components-base'
2
2
  import PropTypes from 'prop-types'
3
- import React, { useState } from 'react'
3
+ import React from 'react'
4
4
  import YouTube from 'react-youtube'
5
5
  import styled from 'styled-components'
6
6
  import VideoSplash from '../shared/VideoSplash/VideoSplash'
7
7
  import { htmlAttrs } from '../utils'
8
+ import { triggerInProgressVideoIntervals, YoutubePlayerState } from './utils'
8
9
 
9
10
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
10
11
 
@@ -33,68 +34,111 @@ const AspectLimiter = styled.div((props) => ({
33
34
  position: 'relative'
34
35
  }))
35
36
 
36
- const WebVideo = ({
37
- videoId,
38
- aspectRatio = '16:9',
39
- posterSrc,
40
- defaultVolume = 1,
41
- beginMuted = false,
42
- videoLength,
43
- copy,
44
- onPlay,
45
- onPause,
46
- onEnd,
47
- onStart = () => {},
48
- ...rest
49
- }) => {
50
- const [started, setStarted] = useState(false)
51
-
52
- const initializeYoutubePlayer = (event) => {
53
- onStart()
54
-
55
- if (beginMuted) {
56
- event.target.mute()
37
+ const WebVideo = React.forwardRef(
38
+ (
39
+ {
40
+ videoId,
41
+ aspectRatio = '16:9',
42
+ posterSrc,
43
+ defaultVolume = 1,
44
+ beginMuted = false,
45
+ videoLength,
46
+ copy,
47
+ onPlay = () => {},
48
+ onPause = () => {},
49
+ onEnd = () => {},
50
+ onProgress = () => {},
51
+ onStart = () => {},
52
+ ...rest
53
+ },
54
+ ref
55
+ ) => {
56
+ const [started, setStarted] = React.useState(false)
57
+ const videoStateData = React.useRef({
58
+ requestAnimationIds: [],
59
+ state: YoutubePlayerState.UNSTARTED
60
+ })
61
+ const playerRef = React.useRef(null)
62
+
63
+ const onPlayCallback = (event) => {
64
+ onPlay(event, videoStateData.current.state === YoutubePlayerState.PAUSED)
65
+ videoStateData.current.state = YoutubePlayerState.PLAYING
66
+ if (onProgress) {
67
+ videoStateData.current.requestAnimationIds = triggerInProgressVideoIntervals(
68
+ onProgress,
69
+ playerRef,
70
+ event
71
+ )
72
+ }
57
73
  }
58
74
 
59
- event.target.setVolume(defaultVolume)
60
- event.target.playVideo() // This plays the video after passing the splash screen on mobile.
75
+ const initializeYoutubePlayer = (event) => {
76
+ onStart()
77
+ playerRef.current = event.target
78
+
79
+ if (beginMuted) {
80
+ event.target.mute()
81
+ }
82
+
83
+ event.target.setVolume(defaultVolume)
84
+ event.target.playVideo() // This plays the video after passing the splash screen on mobile.
85
+ }
86
+
87
+ const onEndVideoCallback = (event) => {
88
+ onEnd(event)
89
+ videoStateData.current.requestAnimationIds.forEach((id) => cancelAnimationFrame(id))
90
+ videoStateData.current.requestAnimationIds = []
91
+ videoStateData.current.state = YoutubePlayerState.ENDED
92
+ if (onProgress) {
93
+ onProgress(event, 100)
94
+ }
95
+ }
96
+
97
+ const onPauseVideoCallback = (event) => {
98
+ videoStateData.current.requestAnimationIds.forEach((id) => cancelAnimationFrame(id))
99
+ onPause(event)
100
+ videoStateData.current.state = YoutubePlayerState.PAUSED
101
+ videoStateData.current.requestAnimationIds = []
102
+ }
103
+
104
+ return (
105
+ <StyledPlayerContainer ref={ref} data-testid="web-video-container" {...selectProps(rest)}>
106
+ <AspectLimiter aspectRatio={aspectRatio}>
107
+ {started ? (
108
+ <StyledYoutubePlayer
109
+ videoId={videoId}
110
+ opts={{
111
+ width: '100%',
112
+ height: '100%',
113
+ playerVars: {
114
+ autoplay: 1,
115
+ modestbranding: 1,
116
+ playsinline: 1,
117
+ rel: 0
118
+ }
119
+ }}
120
+ onReady={initializeYoutubePlayer}
121
+ onPlay={onPlayCallback}
122
+ onPause={onPauseVideoCallback}
123
+ onEnd={onEndVideoCallback}
124
+ />
125
+ ) : (
126
+ <VideoSplash
127
+ poster={posterSrc || `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`}
128
+ videoLength={videoLength}
129
+ copy={copy}
130
+ onClick={() => {
131
+ setStarted(true)
132
+ }}
133
+ />
134
+ )}
135
+ </AspectLimiter>
136
+ </StyledPlayerContainer>
137
+ )
61
138
  }
139
+ )
62
140
 
63
- return (
64
- <StyledPlayerContainer data-testid="web-video-container" {...selectProps(rest)}>
65
- <AspectLimiter aspectRatio={aspectRatio}>
66
- {started ? (
67
- <StyledYoutubePlayer
68
- videoId={videoId}
69
- opts={{
70
- width: '100%',
71
- height: '100%',
72
- playerVars: {
73
- autoplay: 1,
74
- modestbranding: 1,
75
- playsinline: 1,
76
- rel: 0
77
- }
78
- }}
79
- onReady={initializeYoutubePlayer}
80
- onPlay={onPlay}
81
- onPause={onPause}
82
- onEnd={onEnd}
83
- />
84
- ) : (
85
- <VideoSplash
86
- poster={posterSrc || `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`}
87
- videoLength={videoLength}
88
- copy={copy}
89
- onClick={() => {
90
- setStarted(true)
91
- }}
92
- />
93
- )}
94
- </AspectLimiter>
95
- </StyledPlayerContainer>
96
- )
97
- }
141
+ WebVideo.displayName = 'WebVideo'
98
142
 
99
143
  export const VideoProps = {
100
144
  ...selectedSystemPropTypes,
@@ -144,7 +188,17 @@ export const VideoProps = {
144
188
  /**
145
189
  * A function to be run when the video ends.
146
190
  */
147
- onEnd: PropTypes.func
191
+ onEnd: PropTypes.func,
192
+
193
+ /**
194
+ * A function to be run when the video resumes.
195
+ */
196
+ onResume: PropTypes.func,
197
+
198
+ /**
199
+ * A function to be run when the video progresses. This function will be run at 10%, 25%, 50%, 75% and 100%.
200
+ */
201
+ onProgress: PropTypes.func
148
202
  }
149
203
 
150
204
  WebVideo.propTypes = VideoProps
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Fires analytics events for inProgress analytics
3
+ * @param {current progress} progress
4
+ * @param {callback to run on defined milestones} onProgress
5
+ * @param {YouTube Video Event} event
6
+ */
7
+ const fireVideoProgressEvents = (progress, onProgress, event) => {
8
+ const validProgressValues = [10, 25, 50, 75]
9
+ if (validProgressValues.includes(progress)) {
10
+ onProgress(event, progress)
11
+ }
12
+ }
13
+
14
+ export const YoutubePlayerState = {
15
+ UNSTARTED: -1,
16
+ ENDED: 0,
17
+ PLAYING: 1,
18
+ PAUSED: 2,
19
+ BUFFERING: 3,
20
+ CUED: 5
21
+ }
22
+
23
+ const PERCENTAGE_MULTIPLIER = 100
24
+ /**
25
+ * Run an interval to check the progress of the video and fire events at 10%, 25%, 50% and 75%
26
+ * @param {callback to run on defined milestones} onProgress
27
+ * @param {player reference} playerRef
28
+ * @param {YouTube video event} event
29
+ */
30
+ export const triggerInProgressVideoIntervals = (onProgress, playerRef, event) => {
31
+ const duration = playerRef.current.getDuration()
32
+ let lastProgress = null
33
+ const requestAnimationFrameIds = []
34
+
35
+ const frame = () => {
36
+ const currentTime = playerRef.current.getCurrentTime()
37
+ const progress = Math.round((currentTime / duration) * PERCENTAGE_MULTIPLIER)
38
+
39
+ if (progress !== lastProgress) {
40
+ fireVideoProgressEvents(progress, onProgress, event)
41
+ lastProgress = progress
42
+ }
43
+
44
+ if (currentTime < duration) {
45
+ requestAnimationFrameIds.push(requestAnimationFrame(frame))
46
+ }
47
+ }
48
+
49
+ // Cancel any previous animation frames
50
+ requestAnimationFrameIds.forEach((id) => cancelAnimationFrame(id))
51
+
52
+ // Start a new animation frame
53
+ requestAnimationFrameIds.push(requestAnimationFrame(frame))
54
+
55
+ return requestAnimationFrameIds
56
+ }
@@ -17,6 +17,7 @@ export {
17
17
  ButtonDropdown,
18
18
  ButtonGroup,
19
19
  ButtonLink,
20
+ CardGroup,
20
21
  Carousel,
21
22
  CarouselTabs,
22
23
  Checkbox,
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
- import { useThemeTokens } from '@telus-uds/components-base'
4
+ import { getTokensPropType, useThemeTokens } from '@telus-uds/components-base'
5
5
 
6
6
  const withClientTheme = (Component) => {
7
7
  const UdsStyledComponent = ({ tokens: tokenOverrides, variant, ...props }) => {
@@ -10,7 +10,7 @@ const withClientTheme = (Component) => {
10
10
  }
11
11
 
12
12
  UdsStyledComponent.propTypes = {
13
- tokens: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
13
+ tokens: getTokensPropType(Component.displayName),
14
14
  variant: PropTypes.string
15
15
  }
16
16
 
@@ -1,6 +1,6 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { getThemeTokens } from '@telus-uds/components-base/server'
3
+ import { getThemeTokens, getTokensPropType } from '@telus-uds/components-base/server'
4
4
 
5
5
  import getTheme from './get-theme-from-server'
6
6
 
@@ -11,7 +11,7 @@ const withServerTheme = (Component, componentName) => {
11
11
  return <Component theme={themeTokens} {...props} />
12
12
  }
13
13
  UdsStyledComponent.propTypes = {
14
- tokens: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
14
+ tokens: getTokensPropType(componentName),
15
15
  variant: PropTypes.string
16
16
  }
17
17
 
@@ -11,9 +11,10 @@ export interface WebVideoProps extends HTMLAttrs {
11
11
  videoLength: number
12
12
  copy?: 'en' | 'fr'
13
13
  onStart?: () => void
14
- onPlay?: (event: YouTubeEvent<number>) => void
14
+ onPlay?: (event: YouTubeEvent<number>, videoResumed: boolean) => void
15
15
  onEnd?: (event: YouTubeEvent<number>) => void
16
16
  onPause?: (event: YouTubeEvent<number>) => void
17
+ onProgress?: (event: YouTubeEvent<number>, milestone: number) => void
17
18
  }
18
19
 
19
20
  declare const WebVideo: ComponentType<WebVideoProps>