@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.
- package/CHANGELOG.md +26 -2
- 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 +3 -4
- 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/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/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 +3 -3
- 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/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/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 +3 -3
- 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/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
|
@@ -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'
|