@telus-uds/components-web 1.10.0 → 1.11.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 (77) hide show
  1. package/CHANGELOG.md +26 -2
  2. package/lib/Breadcrumbs/Item/Item.js +31 -6
  3. package/lib/Callout/Callout.js +24 -3
  4. package/lib/Disclaimer/Disclaimer.js +72 -0
  5. package/lib/Disclaimer/index.js +15 -0
  6. package/lib/Footnote/Footnote.js +3 -4
  7. package/lib/Footnote/FootnoteLink.js +11 -13
  8. package/lib/NavigationBar/NavigationBar.js +231 -0
  9. package/lib/NavigationBar/NavigationItem.js +111 -0
  10. package/lib/NavigationBar/NavigationSubMenu.js +179 -0
  11. package/lib/NavigationBar/collapseItems.js +51 -0
  12. package/lib/NavigationBar/index.js +13 -0
  13. package/lib/PriceLockup/PriceLockup.js +40 -17
  14. package/lib/PriceLockup/tokens.js +49 -116
  15. package/lib/Progress/ProgressBar.js +100 -0
  16. package/lib/Progress/index.js +16 -0
  17. package/lib/Ribbon/Ribbon.js +53 -32
  18. package/lib/Spinner/Spinner.js +18 -14
  19. package/lib/Table/Cell.js +15 -1
  20. package/lib/VideoPicker/VideoPicker.js +177 -0
  21. package/lib/VideoPicker/VideoPickerPlayer.js +54 -0
  22. package/lib/VideoPicker/VideoPickerThumbnail.js +201 -0
  23. package/lib/VideoPicker/VideoSlider.js +100 -0
  24. package/lib/VideoPicker/index.js +13 -0
  25. package/lib/VideoPicker/videoPropType.js +25 -0
  26. package/lib/index.js +37 -1
  27. package/lib-module/Breadcrumbs/Item/Item.js +32 -7
  28. package/lib-module/Callout/Callout.js +24 -3
  29. package/lib-module/Disclaimer/Disclaimer.js +54 -0
  30. package/lib-module/Disclaimer/index.js +1 -0
  31. package/lib-module/Footnote/Footnote.js +3 -3
  32. package/lib-module/Footnote/FootnoteLink.js +12 -14
  33. package/lib-module/NavigationBar/NavigationBar.js +207 -0
  34. package/lib-module/NavigationBar/NavigationItem.js +87 -0
  35. package/lib-module/NavigationBar/NavigationSubMenu.js +161 -0
  36. package/lib-module/NavigationBar/collapseItems.js +43 -0
  37. package/lib-module/NavigationBar/index.js +2 -0
  38. package/lib-module/PriceLockup/PriceLockup.js +42 -19
  39. package/lib-module/PriceLockup/tokens.js +54 -119
  40. package/lib-module/Progress/ProgressBar.js +83 -0
  41. package/lib-module/Progress/index.js +4 -0
  42. package/lib-module/Ribbon/Ribbon.js +53 -32
  43. package/lib-module/Spinner/Spinner.js +17 -14
  44. package/lib-module/Table/Cell.js +15 -1
  45. package/lib-module/VideoPicker/VideoPicker.js +151 -0
  46. package/lib-module/VideoPicker/VideoPickerPlayer.js +41 -0
  47. package/lib-module/VideoPicker/VideoPickerThumbnail.js +180 -0
  48. package/lib-module/VideoPicker/VideoSlider.js +83 -0
  49. package/lib-module/VideoPicker/index.js +2 -0
  50. package/lib-module/VideoPicker/videoPropType.js +9 -0
  51. package/lib-module/index.js +4 -0
  52. package/package.json +3 -3
  53. package/src/Breadcrumbs/Item/Item.jsx +18 -4
  54. package/src/Callout/Callout.jsx +27 -3
  55. package/src/Disclaimer/Disclaimer.jsx +39 -0
  56. package/src/Disclaimer/index.js +1 -0
  57. package/src/Footnote/Footnote.jsx +3 -3
  58. package/src/Footnote/FootnoteLink.jsx +28 -18
  59. package/src/NavigationBar/NavigationBar.jsx +217 -0
  60. package/src/NavigationBar/NavigationItem.jsx +83 -0
  61. package/src/NavigationBar/NavigationSubMenu.jsx +121 -0
  62. package/src/NavigationBar/collapseItems.js +29 -0
  63. package/src/NavigationBar/index.js +3 -0
  64. package/src/PriceLockup/PriceLockup.jsx +47 -21
  65. package/src/PriceLockup/tokens.js +34 -54
  66. package/src/Progress/ProgressBar.jsx +67 -0
  67. package/src/Progress/index.js +6 -0
  68. package/src/Ribbon/Ribbon.jsx +21 -9
  69. package/src/Spinner/Spinner.jsx +20 -17
  70. package/src/Table/Cell.jsx +22 -5
  71. package/src/VideoPicker/VideoPicker.jsx +144 -0
  72. package/src/VideoPicker/VideoPickerPlayer.jsx +21 -0
  73. package/src/VideoPicker/VideoPickerThumbnail.jsx +182 -0
  74. package/src/VideoPicker/VideoSlider.jsx +85 -0
  75. package/src/VideoPicker/index.js +3 -0
  76. package/src/VideoPicker/videoPropType.js +12 -0
  77. package/src/index.js +4 -0
@@ -0,0 +1,182 @@
1
+ import React from 'react'
2
+ import { Pressable, StyleSheet } from 'react-native-web'
3
+ import PropTypes from 'prop-types'
4
+ import { viewports } from '@telus-uds/system-constants'
5
+ import styled from 'styled-components'
6
+ import {
7
+ StackView,
8
+ Typography,
9
+ useViewport,
10
+ horizontalScrollUtils,
11
+ useThemeTokens
12
+ } from '@telus-uds/components-base'
13
+ import { getTimestamp } from '../shared/VideoSplash/helpers'
14
+ import { VideoPropType, RefPropType } from './videoPropType'
15
+ import VideoSplash from '../shared/VideoSplash/VideoSplash'
16
+
17
+ const { getItemPositionLayoutHandler, itemPositionsPropType } = horizontalScrollUtils
18
+
19
+ // Use a React Native (web) outer container so it can take an onLayout callback, to
20
+ // access position in VideoSlider's UDS HorizontalScroll and update its itemPositions
21
+ const createReactNativeStyles = ({
22
+ pressablePaddingBottom,
23
+ pressablePaddingVertical,
24
+ pressablePaddingHorizontal,
25
+ pressableBorderTopWidth,
26
+ pressableBorderTopColor
27
+ }) =>
28
+ StyleSheet.create({
29
+ container: {
30
+ cursor: 'pointer'
31
+ },
32
+ horizontal: {
33
+ paddingBottom: pressablePaddingBottom
34
+ },
35
+ framed: {
36
+ paddingVertical: pressablePaddingVertical,
37
+ paddingHorizontal: pressablePaddingHorizontal
38
+ },
39
+ framedLine: {
40
+ borderTopWidth: pressableBorderTopWidth,
41
+ borderTopColor: pressableBorderTopColor
42
+ }
43
+ })
44
+
45
+ const VideoThumbnail = styled.div`
46
+ position: relative;
47
+ width: ${(props) => (props.layout === 'vertical' ? '100%' : '144px')};
48
+ flex-shrink: 0;
49
+
50
+ * button {
51
+ display: none;
52
+ }
53
+
54
+ // use a pseudo-element to ensure aspect ratio
55
+ &::before {
56
+ content: '';
57
+ display: block;
58
+ padding-bottom: 56.25%;
59
+ }
60
+
61
+ &::after {
62
+ content: '';
63
+ border: ${({ borderWidth }) => borderWidth}px solid;
64
+ border-color: ${({ isPlaying, borderColor }) => (isPlaying ? borderColor : 'transparent')};
65
+ border-radius: ${({ borderRadius }) => borderRadius}px;
66
+ position: absolute;
67
+ top: 0;
68
+ left: 0;
69
+ right: 0;
70
+ bottom: 0;
71
+ }
72
+
73
+ & > div {
74
+ border-radius: ${({ borderRadius }) => borderRadius}px;
75
+ }
76
+ `
77
+
78
+ const ThumbnailTitleContainer = styled.div`
79
+ display: -webkit-box;
80
+ -webkit-line-clamp: 2;
81
+ -webkit-box-orient: vertical;
82
+ overflow: hidden;
83
+ `
84
+
85
+ const VideoPickerThumbnail = ({
86
+ videoPlayerRef,
87
+ selectedVideoId,
88
+ video,
89
+ onSelectVideo,
90
+ layout = 'vertical',
91
+ isFramed,
92
+ itemPositions,
93
+ index,
94
+ width = '100%'
95
+ }) => {
96
+ const viewport = useViewport()
97
+ const { titleColor, subTitleColor, ...themeTokens } = useThemeTokens('VideoPickerThumbnail')
98
+
99
+ const rnStyles = createReactNativeStyles(themeTokens)
100
+ const { timestamp } = getTimestamp(video.videoLength, video.copy)
101
+ const isPlaying = selectedVideoId === video.videoId
102
+
103
+ const renderThumbnailImage = () => (
104
+ <VideoThumbnail {...themeTokens} isPlaying={isPlaying} layout={layout}>
105
+ <VideoSplash
106
+ simpleMode
107
+ poster={video.posterSrc || `https://img.youtube.com/vi/${video.videoId}/maxresdefault.jpg`}
108
+ videoLength={video.videoLength}
109
+ copy={video.copy}
110
+ />
111
+ </VideoThumbnail>
112
+ )
113
+
114
+ const renderThumbnailInfo = () => (
115
+ <StackView space={2} tokens={{ flexShrink: 1 }}>
116
+ <ThumbnailTitleContainer viewport={viewport}>
117
+ <Typography variant={{ bold: true }} tokens={{ color: isPlaying ? titleColor : 'none' }}>
118
+ {video.title}
119
+ </Typography>
120
+ </ThumbnailTitleContainer>
121
+ {viewport !== viewports.xs && (
122
+ <Typography variant={{ size: 'micro' }} tokens={{ color: subTitleColor }}>
123
+ {timestamp}
124
+ </Typography>
125
+ )}
126
+ </StackView>
127
+ )
128
+
129
+ const handleLayout =
130
+ itemPositions !== undefined
131
+ ? getItemPositionLayoutHandler(itemPositions.positions, index)
132
+ : undefined
133
+
134
+ const onKeyPress = (event) => {
135
+ if (['Space', 'Enter'].includes(event.key)) {
136
+ onSelectVideo(video)
137
+ const splashButton = videoPlayerRef.current?.querySelector('button')
138
+ if (splashButton) splashButton.focus()
139
+ }
140
+ }
141
+
142
+ return (
143
+ <Pressable
144
+ key={video.videoId}
145
+ onLayout={handleLayout}
146
+ onPress={() => onSelectVideo(video)}
147
+ testID={`thumbnail-container-${video.videoId}`}
148
+ onKeyPress={onKeyPress}
149
+ accessibilityRole="radio"
150
+ accessibilityState={{ checked: isPlaying }}
151
+ style={[
152
+ rnStyles.container,
153
+ layout === 'horizontal' && rnStyles.horizontal,
154
+ isFramed && rnStyles.framed,
155
+ isFramed && index > 0 && rnStyles.framedLine,
156
+ { width }
157
+ ]}
158
+ >
159
+ <StackView
160
+ space={layout === 'vertical' ? 2 : 3}
161
+ direction={layout === 'vertical' ? 'column' : 'row'}
162
+ >
163
+ {renderThumbnailImage()}
164
+ {renderThumbnailInfo()}
165
+ </StackView>
166
+ </Pressable>
167
+ )
168
+ }
169
+
170
+ VideoPickerThumbnail.propTypes = {
171
+ selectedVideoId: PropTypes.string,
172
+ onSelectVideo: PropTypes.func,
173
+ video: VideoPropType,
174
+ videoPlayerRef: RefPropType,
175
+ layout: PropTypes.oneOf(['vertical', 'horizontal']),
176
+ isFramed: PropTypes.bool,
177
+ itemPositions: itemPositionsPropType,
178
+ index: PropTypes.number,
179
+ width: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
180
+ }
181
+
182
+ export default VideoPickerThumbnail
@@ -0,0 +1,85 @@
1
+ import React, { cloneElement, useState } from 'react'
2
+ import {
3
+ HorizontalScroll,
4
+ HorizontalScrollButton,
5
+ horizontalScrollUtils,
6
+ StackView,
7
+ useThemeTokens,
8
+ useViewport
9
+ } from '@telus-uds/components-base'
10
+ import { View } from 'react-native-web'
11
+ import PropTypes from 'prop-types'
12
+
13
+ const { useItemPositions } = horizontalScrollUtils
14
+
15
+ const VideoSlider = ({ children }) => {
16
+ const viewport = useViewport()
17
+ const [itemPositions] = useItemPositions()
18
+ const [containerWidth, setContainerWidth] = useState(null)
19
+ const { previousIcon: PreviousIcon, nextIcon: NextIcon } = useThemeTokens('VideoPickerSlider')
20
+
21
+ const onLayout = ({
22
+ nativeEvent: {
23
+ layout: { width }
24
+ }
25
+ }) => {
26
+ setContainerWidth(width)
27
+ }
28
+
29
+ const itemsGap = 24 // '4' on spacing scale
30
+ const itemsCount = viewport === 'lg' || viewport === 'xl' ? 4 : 3
31
+ const itemGapPortioned = ((itemsCount - 1) * itemsGap) / itemsCount
32
+ const itemWidth =
33
+ containerWidth === null
34
+ ? // For first render, we don't know container width, so avoid flicker with static % width
35
+ `calc(${Math.round(100 / itemsCount)}% - ${Math.round(itemGapPortioned)}px)`
36
+ : // After first render, can't use % widths because parent is > 100% width horizontal scroll area
37
+ Math.max(
38
+ containerWidth / itemsCount - itemGapPortioned,
39
+ 0 // Prevent negative width breaking layout on very narrow containers
40
+ )
41
+
42
+ const content = (
43
+ <StackView space={4} direction="row" accessibilityRole="radiogroup" tokens={{ flexGrow: 1 }}>
44
+ {React.Children.map(children, (child, index) =>
45
+ cloneElement(child, {
46
+ index,
47
+ itemPositions,
48
+ width: itemWidth
49
+ })
50
+ )}
51
+ </StackView>
52
+ )
53
+
54
+ const overflow = containerWidth === null && { overflow: 'hidden' }
55
+ const horizontalScrollTokens = {
56
+ nextIcon: NextIcon,
57
+ previousIcon: PreviousIcon,
58
+ gutter: 0,
59
+ borderBottomWidth: 0,
60
+ borderBottomColor: 'transparent',
61
+ buttonClearance: 0
62
+ }
63
+ return (
64
+ <View onLayout={onLayout} style={overflow}>
65
+ {containerWidth === null ? (
66
+ // Use a 100% width non-scrollable parent until containerWidth is known, to avoid flicker
67
+ content
68
+ ) : (
69
+ <HorizontalScroll
70
+ ScrollButton={HorizontalScrollButton}
71
+ itemPositions={itemPositions}
72
+ tokens={horizontalScrollTokens}
73
+ >
74
+ {content}
75
+ </HorizontalScroll>
76
+ )}
77
+ </View>
78
+ )
79
+ }
80
+
81
+ VideoSlider.propTypes = {
82
+ children: PropTypes.node
83
+ }
84
+
85
+ export default VideoSlider
@@ -0,0 +1,3 @@
1
+ import VideoPicker from './VideoPicker'
2
+
3
+ export default VideoPicker
@@ -0,0 +1,12 @@
1
+ import PropTypes from 'prop-types'
2
+ import { VideoProps } from '../WebVideo/WebVideo'
3
+
4
+ export const VideoPropType = PropTypes.shape({
5
+ ...VideoProps,
6
+ title: PropTypes.string,
7
+ description: PropTypes.string
8
+ })
9
+
10
+ export const RefPropType = PropTypes.shape({
11
+ current: PropTypes.object
12
+ })
package/src/index.js CHANGED
@@ -27,9 +27,13 @@ export { default as WebVideo } from './WebVideo'
27
27
  export { default as WaffleGrid } from './WaffleGrid'
28
28
  export { default as Spinner } from './Spinner'
29
29
  export { default as Listbox } from './Listbox'
30
+ export { default as VideoPicker } from './VideoPicker'
30
31
  export { default as Video } from './Video'
31
32
  export { default as StoryCard } from './StoryCard'
33
+ export { default as Disclaimer } from './Disclaimer'
32
34
  export { default as Card } from './Card'
33
35
  export { default as TermsAndConditions } from './TermsAndConditions'
36
+ export { default as NavigationBar } from './NavigationBar'
37
+ export { default as Progress } from './Progress'
34
38
 
35
39
  export * from './baseExports'