@telus-uds/components-web 1.9.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.
- package/CHANGELOG.md +40 -2
- package/lib/Breadcrumbs/Breadcrumbs.js +8 -3
- package/lib/Breadcrumbs/Item/Item.js +31 -6
- package/lib/Callout/Callout.js +24 -3
- package/lib/Disclaimer/Disclaimer.js +72 -0
- package/lib/Disclaimer/index.js +15 -0
- package/lib/Footnote/Footnote.js +70 -28
- package/lib/Footnote/FootnoteLink.js +11 -13
- package/lib/NavigationBar/NavigationBar.js +231 -0
- package/lib/NavigationBar/NavigationItem.js +111 -0
- package/lib/NavigationBar/NavigationSubMenu.js +179 -0
- package/lib/NavigationBar/collapseItems.js +51 -0
- package/lib/NavigationBar/index.js +13 -0
- package/lib/PriceLockup/PriceLockup.js +40 -17
- package/lib/PriceLockup/tokens.js +49 -116
- package/lib/Progress/ProgressBar.js +100 -0
- package/lib/Progress/index.js +16 -0
- package/lib/Ribbon/Ribbon.js +53 -32
- package/lib/Spinner/Spinner.js +18 -14
- package/lib/Table/Cell.js +15 -1
- package/lib/Toast/Toast.js +15 -8
- package/lib/VideoPicker/VideoPicker.js +177 -0
- package/lib/VideoPicker/VideoPickerPlayer.js +54 -0
- package/lib/VideoPicker/VideoPickerThumbnail.js +201 -0
- package/lib/VideoPicker/VideoSlider.js +100 -0
- package/lib/VideoPicker/index.js +13 -0
- package/lib/VideoPicker/videoPropType.js +25 -0
- package/lib/index.js +37 -1
- package/lib-module/Breadcrumbs/Breadcrumbs.js +8 -3
- package/lib-module/Breadcrumbs/Item/Item.js +32 -7
- package/lib-module/Callout/Callout.js +24 -3
- package/lib-module/Disclaimer/Disclaimer.js +54 -0
- package/lib-module/Disclaimer/index.js +1 -0
- package/lib-module/Footnote/Footnote.js +68 -27
- package/lib-module/Footnote/FootnoteLink.js +12 -14
- package/lib-module/NavigationBar/NavigationBar.js +207 -0
- package/lib-module/NavigationBar/NavigationItem.js +87 -0
- package/lib-module/NavigationBar/NavigationSubMenu.js +161 -0
- package/lib-module/NavigationBar/collapseItems.js +43 -0
- package/lib-module/NavigationBar/index.js +2 -0
- package/lib-module/PriceLockup/PriceLockup.js +42 -19
- package/lib-module/PriceLockup/tokens.js +54 -119
- package/lib-module/Progress/ProgressBar.js +83 -0
- package/lib-module/Progress/index.js +4 -0
- package/lib-module/Ribbon/Ribbon.js +53 -32
- package/lib-module/Spinner/Spinner.js +17 -14
- package/lib-module/Table/Cell.js +15 -1
- package/lib-module/Toast/Toast.js +15 -8
- package/lib-module/VideoPicker/VideoPicker.js +151 -0
- package/lib-module/VideoPicker/VideoPickerPlayer.js +41 -0
- package/lib-module/VideoPicker/VideoPickerThumbnail.js +180 -0
- package/lib-module/VideoPicker/VideoSlider.js +83 -0
- package/lib-module/VideoPicker/index.js +2 -0
- package/lib-module/VideoPicker/videoPropType.js +9 -0
- package/lib-module/index.js +4 -0
- package/package.json +3 -3
- package/src/Breadcrumbs/Breadcrumbs.jsx +4 -3
- package/src/Breadcrumbs/Item/Item.jsx +18 -4
- package/src/Callout/Callout.jsx +27 -3
- package/src/Disclaimer/Disclaimer.jsx +39 -0
- package/src/Disclaimer/index.js +1 -0
- package/src/Footnote/Footnote.jsx +76 -26
- package/src/Footnote/FootnoteLink.jsx +28 -18
- package/src/NavigationBar/NavigationBar.jsx +217 -0
- package/src/NavigationBar/NavigationItem.jsx +83 -0
- package/src/NavigationBar/NavigationSubMenu.jsx +121 -0
- package/src/NavigationBar/collapseItems.js +29 -0
- package/src/NavigationBar/index.js +3 -0
- package/src/PriceLockup/PriceLockup.jsx +47 -21
- package/src/PriceLockup/tokens.js +34 -54
- package/src/Progress/ProgressBar.jsx +67 -0
- package/src/Progress/index.js +6 -0
- package/src/Ribbon/Ribbon.jsx +21 -9
- package/src/Spinner/Spinner.jsx +20 -17
- package/src/Table/Cell.jsx +22 -5
- package/src/Toast/Toast.jsx +12 -5
- package/src/VideoPicker/VideoPicker.jsx +144 -0
- package/src/VideoPicker/VideoPickerPlayer.jsx +21 -0
- package/src/VideoPicker/VideoPickerThumbnail.jsx +182 -0
- package/src/VideoPicker/VideoSlider.jsx +85 -0
- package/src/VideoPicker/index.js +3 -0
- package/src/VideoPicker/videoPropType.js +12 -0
- package/src/index.js +4 -0
package/src/Toast/Toast.jsx
CHANGED
|
@@ -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={
|
|
119
|
-
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,144 @@
|
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react'
|
|
2
|
+
import PropTypes from 'prop-types'
|
|
3
|
+
import { viewports } from '@telus-uds/system-constants'
|
|
4
|
+
import {
|
|
5
|
+
selectSystemProps,
|
|
6
|
+
StackView,
|
|
7
|
+
useThemeTokens,
|
|
8
|
+
useViewport
|
|
9
|
+
} from '@telus-uds/components-base'
|
|
10
|
+
|
|
11
|
+
import styled, { css } from 'styled-components'
|
|
12
|
+
import { VideoPropType } from './videoPropType'
|
|
13
|
+
import VideoPickerThumbnail from './VideoPickerThumbnail'
|
|
14
|
+
import VideoPickerPlayer from './VideoPickerPlayer'
|
|
15
|
+
import VideoSlider from './VideoSlider'
|
|
16
|
+
import { htmlAttrs } from '../utils'
|
|
17
|
+
|
|
18
|
+
const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
|
|
19
|
+
|
|
20
|
+
const framedContainerStyles = ({
|
|
21
|
+
framedContainerBackgroundColor,
|
|
22
|
+
framedContainerBorderWidth,
|
|
23
|
+
framedContainerBorderColor,
|
|
24
|
+
framedContainerBorderRadius,
|
|
25
|
+
framedMaxHeight
|
|
26
|
+
}) => css`
|
|
27
|
+
background-color: ${framedContainerBackgroundColor};
|
|
28
|
+
border: ${framedContainerBorderWidth}px solid ${framedContainerBorderColor};
|
|
29
|
+
border-radius: ${framedContainerBorderRadius}px;
|
|
30
|
+
|
|
31
|
+
& > div {
|
|
32
|
+
max-height: ${framedMaxHeight}px;
|
|
33
|
+
}
|
|
34
|
+
`
|
|
35
|
+
|
|
36
|
+
const VideoPickerContainer = styled.div`
|
|
37
|
+
${({ isFramed, ...tokens }) => isFramed && framedContainerStyles(tokens)}
|
|
38
|
+
`
|
|
39
|
+
|
|
40
|
+
const framedPlayerContainerStyles = ({ framedMaxHeight, framedContainerPadding }) => css`
|
|
41
|
+
width: 66.67%;
|
|
42
|
+
padding: ${framedContainerPadding}px;
|
|
43
|
+
max-height: ${framedMaxHeight}px;
|
|
44
|
+
flex-shrink: 0;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
`
|
|
47
|
+
|
|
48
|
+
const VideoPlayerContainer = styled.div`
|
|
49
|
+
${({ isFramed, ...tokens }) => isFramed && framedPlayerContainerStyles(tokens)}
|
|
50
|
+
overflow: auto;
|
|
51
|
+
`
|
|
52
|
+
|
|
53
|
+
const framedVideoListContainerStyle = css`
|
|
54
|
+
max-height: ${({ framedMaxHeight }) => framedMaxHeight}px;
|
|
55
|
+
overflow: auto;
|
|
56
|
+
flex-direction: column;
|
|
57
|
+
`
|
|
58
|
+
|
|
59
|
+
const VideoListContainer = styled.div`
|
|
60
|
+
display: flex;
|
|
61
|
+
justify-content: flex-start;
|
|
62
|
+
position: relative;
|
|
63
|
+
flex-grow: 1;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
${(props) => props.isFramed && framedVideoListContainerStyle}
|
|
66
|
+
`
|
|
67
|
+
|
|
68
|
+
const VideoPicker = ({ videoList = [], selectedVideo = videoList[0]?.videoId, frame, ...rest }) => {
|
|
69
|
+
const viewport = useViewport()
|
|
70
|
+
const { stackViewDividerColor, ...themeTokens } = useThemeTokens('VideoPicker')
|
|
71
|
+
const [currentVideoId, setCurrentVideoId] = useState(selectedVideo)
|
|
72
|
+
const videoPlayerRef = useRef(null)
|
|
73
|
+
|
|
74
|
+
const currentVideo = videoList.find((video) => video.videoId === currentVideoId)
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
// Update current video if parent changes which video id it passes down
|
|
77
|
+
setCurrentVideoId(selectedVideo)
|
|
78
|
+
}, [selectedVideo])
|
|
79
|
+
|
|
80
|
+
// `frame` variant should only work on larger screens
|
|
81
|
+
const isFramed = frame && [viewports.md, viewports.lg, viewports.xl].includes(viewport)
|
|
82
|
+
|
|
83
|
+
const hasSlider = !frame && [viewports.md, viewports.lg, viewports.xl].includes(viewport)
|
|
84
|
+
|
|
85
|
+
const listElements = videoList.map((video, index) => (
|
|
86
|
+
<VideoPickerThumbnail
|
|
87
|
+
key={video.videoId}
|
|
88
|
+
video={video}
|
|
89
|
+
videoPlayerRef={videoPlayerRef}
|
|
90
|
+
selectedVideoId={currentVideoId}
|
|
91
|
+
onSelectVideo={(current) => setCurrentVideoId(current.videoId)}
|
|
92
|
+
layout={!hasSlider ? 'horizontal' : 'vertical'}
|
|
93
|
+
index={index}
|
|
94
|
+
isFramed={isFramed}
|
|
95
|
+
/>
|
|
96
|
+
))
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<VideoPickerContainer isFramed={isFramed} {...selectProps(rest)} {...themeTokens}>
|
|
100
|
+
<StackView
|
|
101
|
+
divider={
|
|
102
|
+
isFramed
|
|
103
|
+
? { tokens: { color: stackViewDividerColor } }
|
|
104
|
+
: { variant: { decorative: true } }
|
|
105
|
+
}
|
|
106
|
+
space={isFramed ? 0 : 4} // everything has internal padding in `frame` variant
|
|
107
|
+
direction={isFramed ? 'row' : 'column'}
|
|
108
|
+
>
|
|
109
|
+
<VideoPlayerContainer {...themeTokens} isFramed={isFramed}>
|
|
110
|
+
<VideoPickerPlayer video={currentVideo} videoPlayerRef={videoPlayerRef} />
|
|
111
|
+
</VideoPlayerContainer>
|
|
112
|
+
{hasSlider ? (
|
|
113
|
+
<VideoSlider>{listElements}</VideoSlider>
|
|
114
|
+
) : (
|
|
115
|
+
<VideoListContainer
|
|
116
|
+
viewport={viewport}
|
|
117
|
+
data-testid="video-list-container"
|
|
118
|
+
isFramed={isFramed}
|
|
119
|
+
>
|
|
120
|
+
{listElements}
|
|
121
|
+
</VideoListContainer>
|
|
122
|
+
)}
|
|
123
|
+
</StackView>
|
|
124
|
+
</VideoPickerContainer>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
VideoPicker.propTypes = {
|
|
129
|
+
...selectedSystemPropTypes,
|
|
130
|
+
/**
|
|
131
|
+
* Id of the currently selected video (defaults to the first video if not set).
|
|
132
|
+
*/
|
|
133
|
+
selectedVideo: PropTypes.string,
|
|
134
|
+
/**
|
|
135
|
+
* An array of Video objects.
|
|
136
|
+
*/
|
|
137
|
+
videoList: PropTypes.arrayOf(VideoPropType),
|
|
138
|
+
/**
|
|
139
|
+
* Use to display the picker in a container and the playlist as a sidebar (only available for breakpoints LG and larger)
|
|
140
|
+
*/
|
|
141
|
+
frame: PropTypes.bool
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default VideoPicker
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { StackView, Typography } from '@telus-uds/components-base'
|
|
3
|
+
import WebVideo from '../WebVideo/WebVideo'
|
|
4
|
+
import { VideoPropType, RefPropType } from './videoPropType'
|
|
5
|
+
|
|
6
|
+
const VideoPickerPlayer = ({ video = {}, videoPlayerRef }) => (
|
|
7
|
+
<StackView space={3} tokens={{ flexShrink: 1 }}>
|
|
8
|
+
<div ref={videoPlayerRef}>{video.videoId && <WebVideo {...video} />}</div>
|
|
9
|
+
<StackView space={2}>
|
|
10
|
+
<Typography variant={{ size: 'h2', colour: 'secondary' }}>{video.title}</Typography>
|
|
11
|
+
<Typography>{video.description}</Typography>
|
|
12
|
+
</StackView>
|
|
13
|
+
</StackView>
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
VideoPickerPlayer.propTypes = {
|
|
17
|
+
video: VideoPropType,
|
|
18
|
+
videoPlayerRef: RefPropType
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default VideoPickerPlayer
|
|
@@ -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,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'
|