@telus-uds/components-web 1.7.0 → 1.9.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 (220) hide show
  1. package/.eslintignore +2 -0
  2. package/.vscode/settings.json +7 -0
  3. package/CHANGELOG.md +39 -2
  4. package/lib/Autocomplete/Autocomplete.js +393 -0
  5. package/lib/Autocomplete/Loading.js +51 -0
  6. package/lib/Autocomplete/Suggestions.js +81 -0
  7. package/lib/Autocomplete/constants.js +19 -0
  8. package/lib/Autocomplete/dictionary.js +19 -0
  9. package/lib/Autocomplete/index.js +13 -0
  10. package/lib/BlockQuote/BlockQuote.js +173 -0
  11. package/lib/BlockQuote/index.js +13 -0
  12. package/lib/Callout/Callout.js +3 -0
  13. package/lib/Card/Card.js +180 -0
  14. package/lib/Card/CardContent.js +110 -0
  15. package/lib/Card/CardFooter.js +98 -0
  16. package/lib/Card/index.js +13 -0
  17. package/lib/Countdown/Countdown.js +189 -0
  18. package/lib/Countdown/Segment.js +111 -0
  19. package/lib/Countdown/constants.js +14 -0
  20. package/lib/Countdown/dictionary.js +29 -0
  21. package/lib/Countdown/index.js +13 -0
  22. package/lib/Countdown/types.js +39 -0
  23. package/lib/Countdown/useCountdown.js +40 -0
  24. package/lib/IconButton/IconButton.js +70 -0
  25. package/lib/IconButton/index.js +13 -0
  26. package/lib/Listbox/GroupControl.js +94 -0
  27. package/lib/Listbox/Listbox.js +164 -0
  28. package/lib/Listbox/ListboxGroup.js +129 -0
  29. package/lib/Listbox/ListboxItem.js +137 -0
  30. package/lib/Listbox/ListboxOverlay.js +89 -0
  31. package/lib/Listbox/PressableItem.js +149 -0
  32. package/lib/Listbox/index.js +13 -0
  33. package/lib/Modal/ModalContent.js +11 -4
  34. package/lib/NavigationBar/resolveItemSelection.js +24 -0
  35. package/lib/OptimizeImage/OptimizeImage.js +127 -0
  36. package/lib/OptimizeImage/index.js +13 -0
  37. package/lib/OptimizeImage/utils/getFallbackUrl.js +18 -0
  38. package/lib/OptimizeImage/utils/getOptimizedUrl.js +32 -0
  39. package/lib/OptimizeImage/utils/hasWebpSupport.js +38 -0
  40. package/lib/OptimizeImage/utils/index.js +31 -0
  41. package/lib/OptimizeImage/utils/isSvgUrl.js +10 -0
  42. package/lib/QuantitySelector/QuantitySelector.js +253 -0
  43. package/lib/QuantitySelector/dictionary.js +33 -0
  44. package/lib/QuantitySelector/index.js +13 -0
  45. package/lib/QuantitySelector/styles.js +40 -0
  46. package/lib/StoryCard/StoryCard.js +244 -0
  47. package/lib/StoryCard/index.js +13 -0
  48. package/lib/TermsAndConditions/ExpandCollapse.js +141 -0
  49. package/lib/TermsAndConditions/TermsAndConditions.js +221 -0
  50. package/lib/TermsAndConditions/dictionary.js +19 -0
  51. package/lib/TermsAndConditions/index.js +15 -0
  52. package/lib/Testimonial/Testimonial.js +226 -0
  53. package/lib/Testimonial/index.js +13 -0
  54. package/lib/Video/ControlBar/ControlBar.js +315 -0
  55. package/lib/Video/ControlBar/Controls/VideoButton/VideoButton.js +91 -0
  56. package/lib/Video/ControlBar/Controls/VideoMenu/VideoMenu.js +186 -0
  57. package/lib/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.js +221 -0
  58. package/lib/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.js +213 -0
  59. package/lib/Video/MiddleControlButton/MiddleControlButton.js +89 -0
  60. package/lib/Video/Video.js +1072 -0
  61. package/lib/Video/index.js +13 -0
  62. package/lib/Video/videoText.js +62 -0
  63. package/lib/WebVideo/WebVideo.js +170 -0
  64. package/lib/WebVideo/index.js +13 -0
  65. package/lib/baseExports.js +0 -12
  66. package/lib/index.js +118 -1
  67. package/lib/shared/VideoSplash/SplashButton/SplashButton.js +102 -0
  68. package/lib/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.js +234 -0
  69. package/lib/shared/VideoSplash/VideoSplash.js +86 -0
  70. package/lib/shared/VideoSplash/helpers.js +38 -0
  71. package/lib/utils/index.js +8 -0
  72. package/lib/utils/useOverlaidPosition.js +246 -0
  73. package/lib-module/Autocomplete/Autocomplete.js +369 -0
  74. package/lib-module/Autocomplete/Loading.js +38 -0
  75. package/lib-module/Autocomplete/Suggestions.js +64 -0
  76. package/lib-module/Autocomplete/constants.js +5 -0
  77. package/lib-module/Autocomplete/dictionary.js +12 -0
  78. package/lib-module/Autocomplete/index.js +2 -0
  79. package/lib-module/BlockQuote/BlockQuote.js +156 -0
  80. package/lib-module/BlockQuote/index.js +2 -0
  81. package/lib-module/Callout/Callout.js +3 -0
  82. package/lib-module/Card/Card.js +158 -0
  83. package/lib-module/Card/CardContent.js +92 -0
  84. package/lib-module/Card/CardFooter.js +80 -0
  85. package/lib-module/Card/index.js +2 -0
  86. package/lib-module/Countdown/Countdown.js +165 -0
  87. package/lib-module/Countdown/Segment.js +94 -0
  88. package/lib-module/Countdown/constants.js +4 -0
  89. package/lib-module/Countdown/dictionary.js +22 -0
  90. package/lib-module/Countdown/index.js +2 -0
  91. package/lib-module/Countdown/types.js +23 -0
  92. package/lib-module/Countdown/useCountdown.js +32 -0
  93. package/lib-module/IconButton/IconButton.js +52 -0
  94. package/lib-module/IconButton/index.js +2 -0
  95. package/lib-module/Listbox/GroupControl.js +80 -0
  96. package/lib-module/Listbox/Listbox.js +142 -0
  97. package/lib-module/Listbox/ListboxGroup.js +106 -0
  98. package/lib-module/Listbox/ListboxItem.js +112 -0
  99. package/lib-module/Listbox/ListboxOverlay.js +68 -0
  100. package/lib-module/Listbox/PressableItem.js +128 -0
  101. package/lib-module/Listbox/index.js +2 -0
  102. package/lib-module/Modal/ModalContent.js +10 -4
  103. package/lib-module/NavigationBar/resolveItemSelection.js +16 -0
  104. package/lib-module/OptimizeImage/OptimizeImage.js +106 -0
  105. package/lib-module/OptimizeImage/index.js +2 -0
  106. package/lib-module/OptimizeImage/utils/getFallbackUrl.js +8 -0
  107. package/lib-module/OptimizeImage/utils/getOptimizedUrl.js +22 -0
  108. package/lib-module/OptimizeImage/utils/hasWebpSupport.js +32 -0
  109. package/lib-module/OptimizeImage/utils/index.js +4 -0
  110. package/lib-module/OptimizeImage/utils/isSvgUrl.js +3 -0
  111. package/lib-module/QuantitySelector/QuantitySelector.js +232 -0
  112. package/lib-module/QuantitySelector/dictionary.js +26 -0
  113. package/lib-module/QuantitySelector/index.js +2 -0
  114. package/lib-module/QuantitySelector/styles.js +21 -0
  115. package/lib-module/StoryCard/StoryCard.js +220 -0
  116. package/lib-module/StoryCard/index.js +2 -0
  117. package/lib-module/TermsAndConditions/ExpandCollapse.js +120 -0
  118. package/lib-module/TermsAndConditions/TermsAndConditions.js +193 -0
  119. package/lib-module/TermsAndConditions/dictionary.js +12 -0
  120. package/lib-module/TermsAndConditions/index.js +1 -0
  121. package/lib-module/Testimonial/Testimonial.js +204 -0
  122. package/lib-module/Testimonial/index.js +2 -0
  123. package/lib-module/Video/ControlBar/ControlBar.js +292 -0
  124. package/lib-module/Video/ControlBar/Controls/VideoButton/VideoButton.js +74 -0
  125. package/lib-module/Video/ControlBar/Controls/VideoMenu/VideoMenu.js +167 -0
  126. package/lib-module/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.js +201 -0
  127. package/lib-module/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.js +193 -0
  128. package/lib-module/Video/MiddleControlButton/MiddleControlButton.js +72 -0
  129. package/lib-module/Video/Video.js +1042 -0
  130. package/lib-module/Video/index.js +2 -0
  131. package/lib-module/Video/videoText.js +55 -0
  132. package/lib-module/WebVideo/WebVideo.js +144 -0
  133. package/lib-module/WebVideo/index.js +2 -0
  134. package/lib-module/baseExports.js +1 -1
  135. package/lib-module/index.js +13 -0
  136. package/lib-module/shared/VideoSplash/SplashButton/SplashButton.js +85 -0
  137. package/lib-module/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.js +216 -0
  138. package/lib-module/shared/VideoSplash/VideoSplash.js +65 -0
  139. package/lib-module/shared/VideoSplash/helpers.js +23 -0
  140. package/lib-module/utils/index.js +2 -1
  141. package/lib-module/utils/useOverlaidPosition.js +235 -0
  142. package/package.json +7 -5
  143. package/src/Autocomplete/Autocomplete.jsx +354 -0
  144. package/src/Autocomplete/Loading.jsx +18 -0
  145. package/src/Autocomplete/Suggestions.jsx +52 -0
  146. package/src/Autocomplete/constants.js +6 -0
  147. package/src/Autocomplete/dictionary.js +12 -0
  148. package/src/Autocomplete/index.js +3 -0
  149. package/src/BlockQuote/BlockQuote.jsx +130 -0
  150. package/src/BlockQuote/index.js +3 -0
  151. package/src/Callout/Callout.jsx +1 -1
  152. package/src/Card/Card.jsx +170 -0
  153. package/src/Card/CardContent.jsx +88 -0
  154. package/src/Card/CardFooter.jsx +70 -0
  155. package/src/Card/index.js +3 -0
  156. package/src/Countdown/Countdown.jsx +144 -0
  157. package/src/Countdown/Segment.jsx +69 -0
  158. package/src/Countdown/constants.js +4 -0
  159. package/src/Countdown/dictionary.js +22 -0
  160. package/src/Countdown/index.js +3 -0
  161. package/src/Countdown/types.js +23 -0
  162. package/src/Countdown/useCountdown.js +34 -0
  163. package/src/IconButton/IconButton.jsx +46 -0
  164. package/src/IconButton/index.js +3 -0
  165. package/src/Listbox/GroupControl.jsx +65 -0
  166. package/src/Listbox/Listbox.jsx +148 -0
  167. package/src/Listbox/ListboxGroup.jsx +110 -0
  168. package/src/Listbox/ListboxItem.jsx +101 -0
  169. package/src/Listbox/ListboxOverlay.jsx +71 -0
  170. package/src/Listbox/PressableItem.jsx +121 -0
  171. package/src/Listbox/index.js +3 -0
  172. package/src/Modal/ModalContent.jsx +8 -4
  173. package/src/NavigationBar/resolveItemSelection.js +11 -0
  174. package/src/OptimizeImage/OptimizeImage.jsx +131 -0
  175. package/src/OptimizeImage/index.js +3 -0
  176. package/src/OptimizeImage/utils/getFallbackUrl.js +9 -0
  177. package/src/OptimizeImage/utils/getOptimizedUrl.js +30 -0
  178. package/src/OptimizeImage/utils/hasWebpSupport.js +33 -0
  179. package/src/OptimizeImage/utils/index.js +5 -0
  180. package/src/OptimizeImage/utils/isSvgUrl.js +3 -0
  181. package/src/QuantitySelector/QuantitySelector.jsx +245 -0
  182. package/src/QuantitySelector/dictionary.js +27 -0
  183. package/src/QuantitySelector/index.js +3 -0
  184. package/src/QuantitySelector/styles.js +83 -0
  185. package/src/StoryCard/StoryCard.jsx +198 -0
  186. package/src/StoryCard/index.js +3 -0
  187. package/src/TermsAndConditions/ExpandCollapse.jsx +106 -0
  188. package/src/TermsAndConditions/TermsAndConditions.jsx +161 -0
  189. package/src/TermsAndConditions/dictionary.js +12 -0
  190. package/src/TermsAndConditions/index.js +1 -0
  191. package/src/Testimonial/Testimonial.jsx +169 -0
  192. package/src/Testimonial/index.js +3 -0
  193. package/src/Video/ControlBar/ControlBar.jsx +261 -0
  194. package/src/Video/ControlBar/Controls/VideoButton/VideoButton.jsx +61 -0
  195. package/src/Video/ControlBar/Controls/VideoMenu/VideoMenu.jsx +159 -0
  196. package/src/Video/ControlBar/Controls/VideoProgressBar/VideoProgressBar.jsx +185 -0
  197. package/src/Video/ControlBar/Controls/VolumeSlider/VolumeSlider.jsx +184 -0
  198. package/src/Video/MiddleControlButton/MiddleControlButton.jsx +64 -0
  199. package/src/Video/Video.jsx +988 -0
  200. package/src/Video/index.js +3 -0
  201. package/src/Video/videoText.js +58 -0
  202. package/src/WebVideo/WebVideo.jsx +131 -0
  203. package/src/WebVideo/index.js +3 -0
  204. package/src/baseExports.js +0 -2
  205. package/src/index.js +13 -0
  206. package/src/shared/VideoSplash/SplashButton/SplashButton.jsx +64 -0
  207. package/src/shared/VideoSplash/SplashButtonWithDetails/SplashButtonWithDetails.jsx +128 -0
  208. package/src/shared/VideoSplash/VideoSplash.jsx +50 -0
  209. package/src/shared/VideoSplash/helpers.js +27 -0
  210. package/src/utils/index.js +10 -1
  211. package/src/utils/useOverlaidPosition.js +226 -0
  212. package/types/Autocomplete.d.ts +32 -0
  213. package/types/Card.d.ts +45 -0
  214. package/types/ControlBar.d.ts +59 -0
  215. package/types/MiddleControlButton.d.ts +15 -0
  216. package/types/Video.d.ts +39 -0
  217. package/types/VideoButton.d.ts +14 -0
  218. package/types/VideoMenu.d.ts +16 -0
  219. package/types/VideoProgressBar.d.ts +17 -0
  220. package/types/VolumeSlider.d.ts +20 -0
@@ -0,0 +1,71 @@
1
+ /* eslint-disable react/require-default-props */
2
+ import React, { forwardRef } from 'react'
3
+ import { Card, Portal, useThemeTokens } from '@telus-uds/components-base'
4
+ import PropTypes from 'prop-types'
5
+ import { View, StyleSheet } from 'react-native-web'
6
+
7
+ const staticStyles = StyleSheet.create({
8
+ positioner: {
9
+ flex: 1, // Grow to maxWidth when possible, shrink when not possible
10
+ position: 'absolute',
11
+ zIndex: 10000 // Position on top of all the other overlays, including backdrops and modals
12
+ },
13
+ hidden: {
14
+ // Use opacity not visibility to hide the dropdown during positioning
15
+ // so on web, children may be focused from the first render
16
+ opacity: 0
17
+ }
18
+ })
19
+
20
+ const paddingVertical = 0
21
+ const paddingHorizontal = 0
22
+
23
+ const DropdownOverlay = forwardRef(
24
+ ({ children, isReady = false, overlaidPosition, maxWidth, minWidth, onLayout }, ref) => {
25
+ const systemTokens = useThemeTokens('ListBox', {}, {})
26
+
27
+ return (
28
+ <Portal>
29
+ <View
30
+ ref={ref}
31
+ onLayout={onLayout}
32
+ style={[
33
+ overlaidPosition,
34
+ { maxWidth, minWidth },
35
+ staticStyles.positioner,
36
+ !isReady && staticStyles.hidden
37
+ ]}
38
+ >
39
+ <Card
40
+ tokens={{
41
+ shadow: systemTokens.shadow,
42
+ paddingBottom: paddingVertical,
43
+ paddingTop: paddingVertical,
44
+ paddingLeft: paddingHorizontal,
45
+ paddingRight: paddingHorizontal
46
+ }}
47
+ >
48
+ {children}
49
+ </Card>
50
+ </View>
51
+ </Portal>
52
+ )
53
+ }
54
+ )
55
+
56
+ DropdownOverlay.displayName = 'DropdownOverlay'
57
+
58
+ DropdownOverlay.propTypes = {
59
+ children: PropTypes.node.isRequired,
60
+ isReady: PropTypes.bool,
61
+ overlaidPosition: PropTypes.shape({
62
+ top: PropTypes.number,
63
+ left: PropTypes.number,
64
+ width: PropTypes.number
65
+ }),
66
+ maxWidth: PropTypes.number,
67
+ minWidth: PropTypes.number,
68
+ onLayout: PropTypes.func
69
+ }
70
+
71
+ export default DropdownOverlay
@@ -0,0 +1,121 @@
1
+ /* eslint-disable react/require-default-props */
2
+ import React, { forwardRef } from 'react'
3
+ import PropTypes from 'prop-types'
4
+ import { selectSystemProps, useThemeTokens } from '@telus-uds/components-base'
5
+ import styled from 'styled-components'
6
+ import { htmlAttrs } from '../utils'
7
+
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
9
+
10
+ const getItemStyles = ({ tokens }) => ({
11
+ fontFamily: `${tokens.groupFontName}${tokens.groupFontWeight}normal`,
12
+ fontSize: tokens.itemFontSize,
13
+ paddingTop: tokens.itemPaddingTop,
14
+ paddingBottom: tokens.itemPaddingBottom,
15
+ paddingLeft: tokens.itemPaddingLeft,
16
+ paddingRight: tokens.itemPaddingRight,
17
+ borderWidth: tokens.itemBorderWidth,
18
+ width: '100%',
19
+ backgroundColor: tokens.itemBackgroundColor,
20
+ color: tokens.groupColor,
21
+ display: tokens.itemDisplay,
22
+ outline: tokens.itemOutline,
23
+ textDecoration: tokens.itemTextDecoration
24
+ })
25
+
26
+ const StyledLink = styled.a(({ isChild, tokens }) => ({
27
+ ...getItemStyles({ isChild, tokens }),
28
+ borderLeft: isChild
29
+ ? `${tokens.itemBorderWidth}px solid ${tokens.itemBorderBackgroundColor}`
30
+ : 'none',
31
+ cursor: 'pointer',
32
+
33
+ [`&:hover, &:focus, &:active`]: {
34
+ color: tokens.itemColor
35
+ },
36
+
37
+ '&:hover': {
38
+ backgroundColor: tokens.groupBackgroundColor
39
+ },
40
+
41
+ // Highlighting for pressable items that are not links per se
42
+ // TODO: find a better way to display and style a pressable link
43
+ '&:hover > div, &:hover > div > span': {
44
+ color: `${tokens.itemColor} !important`
45
+ },
46
+
47
+ '&:focus': {
48
+ border: `${tokens.groupBorderWidth}px solid ${tokens.itemBorderLeftColor}`,
49
+ borderLeft: isChild && `${tokens.itemBorderWidth}px solid ${tokens.itemBorderLeftColor}`,
50
+ borderRadius: !isChild && tokens.groupBorderRadius,
51
+ paddingLeft: !isChild && `calc(${tokens.itemPaddingLeft}px - ${tokens.groupBorderWidth}px)`,
52
+ paddingRight: `calc(${tokens.itemPaddingRight}px - ${tokens.groupBorderWidth}px)`,
53
+ paddingTop: `calc(${tokens.itemPaddingTop}px - ${tokens.groupBorderWidth}px)`,
54
+ paddingBottom: `calc(${tokens.itemPaddingBottom}px - ${tokens.groupBorderWidth}px)`,
55
+ backgroundColor: tokens.groupBackgroundColor
56
+ },
57
+
58
+ '&:active': {
59
+ backgroundColor: tokens.itemBorderBackgroundColor
60
+ }
61
+ }))
62
+
63
+ const PressableItem = forwardRef(
64
+ (
65
+ {
66
+ children,
67
+ href,
68
+ isChild = false,
69
+ onBlur,
70
+ onPress,
71
+ tabIndex = 0,
72
+ nextItemRef,
73
+ prevItemRef,
74
+ tokens,
75
+ variant = {},
76
+ ...rest
77
+ },
78
+ ref
79
+ ) => {
80
+ const handleKeyPress = (event) => {
81
+ if (['Enter', ' '].includes(event?.key)) {
82
+ onPress?.(event)
83
+ } else if (event?.key === 'ArrowDown' && nextItemRef?.current) {
84
+ nextItemRef.current.focus()
85
+ } else if (event?.key === 'ArrowUp' && prevItemRef?.current) {
86
+ prevItemRef.current.focus()
87
+ }
88
+ }
89
+ const systemTokens = useThemeTokens('ListBox', tokens, variant, { isChild, hover: true })
90
+
91
+ return (
92
+ <StyledLink
93
+ isChild={isChild}
94
+ tokens={systemTokens}
95
+ onBlur={onBlur}
96
+ onClick={onPress}
97
+ onKeyPress={handleKeyPress}
98
+ ref={ref}
99
+ tabIndex={tabIndex}
100
+ {...(href && { href })}
101
+ {...(onPress && { onClick: onPress })}
102
+ {...selectProps(rest)}
103
+ >
104
+ {children}
105
+ </StyledLink>
106
+ )
107
+ }
108
+ )
109
+ PressableItem.displayName = 'PressableItem'
110
+ PressableItem.propTypes = {
111
+ ...selectedSystemPropTypes,
112
+ href: PropTypes.string,
113
+ isChild: PropTypes.bool,
114
+ children: PropTypes.node.isRequired,
115
+ onBlur: PropTypes.func,
116
+ onPress: PropTypes.func,
117
+ nextItemRef: PropTypes.object,
118
+ prevItemRef: PropTypes.object
119
+ }
120
+
121
+ export default PressableItem
@@ -0,0 +1,3 @@
1
+ import Listbox from './Listbox'
2
+
3
+ export default Listbox
@@ -28,6 +28,10 @@ const StyledSubHeading = styled.div`
28
28
  margin-top: ${({ marginTop }) => marginTop}px;
29
29
  `
30
30
 
31
+ const StyledTextButtonContainer = styled.div({
32
+ display: 'flex'
33
+ })
34
+
31
35
  const StyledFooter = styled.footer(
32
36
  ({
33
37
  hasBorder,
@@ -131,13 +135,13 @@ const ModalContent = ({
131
135
  {confirmButtonText}
132
136
  </Button>
133
137
  )}
134
- <div>
135
- {hasCancelButton && (
138
+ {hasCancelButton ? (
139
+ <StyledTextButtonContainer>
136
140
  <CancelButton tokens={{ color: cancelButtonColor }} onPress={onCancel}>
137
141
  {cancelButtonText}
138
142
  </CancelButton>
139
- )}
140
- </div>
143
+ </StyledTextButtonContainer>
144
+ ) : null}
141
145
  </StyledFooter>
142
146
  )}
143
147
  </StyledModalContent>
@@ -0,0 +1,11 @@
1
+ const resolveItemSelection = ({ id, label, items }, selectedId) => {
2
+ const itemId = id ?? label
3
+
4
+ // Treat item as selected if it or any nested child matches the selected id
5
+ const selected = Boolean(
6
+ selectedId === itemId || items?.some((item) => resolveItemSelection(item, selectedId).selected)
7
+ )
8
+ return { itemId, selected }
9
+ }
10
+
11
+ export default resolveItemSelection
@@ -0,0 +1,131 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { selectSystemProps } from '@telus-uds/components-base'
4
+ import ResponsiveImage from '../ResponsiveImage'
5
+ import { hasWebpSupport, getOptimizedUrl, getFallbackUrl } from './utils'
6
+ import { htmlAttrs } from '../utils'
7
+
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
9
+
10
+ const OptimizeImage = ({
11
+ contentfulAssetUrl,
12
+ alt,
13
+ quality = 80,
14
+ xs = 320,
15
+ sm = 576,
16
+ md = 768,
17
+ lg = 992,
18
+ xl = 1200,
19
+ sizeByHeight = false,
20
+ disableRetina = false,
21
+ ...rest
22
+ }) => {
23
+ const [imgUrls, setImgUrls] = useState()
24
+
25
+ // `useHeight` is a deprecated TDS prop, replaced by `sizeByHeight`
26
+ const dimension = sizeByHeight || rest.useHeight ? 'h' : 'w'
27
+
28
+ useEffect(() => {
29
+ // Currently not all browsers support webP
30
+ hasWebpSupport().then((supportsWebp) => {
31
+ setImgUrls({
32
+ xsSrc: getOptimizedUrl(
33
+ contentfulAssetUrl,
34
+ dimension,
35
+ xs,
36
+ quality,
37
+ disableRetina,
38
+ supportsWebp
39
+ ),
40
+ smSrc: getOptimizedUrl(
41
+ contentfulAssetUrl,
42
+ dimension,
43
+ sm,
44
+ quality,
45
+ disableRetina,
46
+ supportsWebp
47
+ ),
48
+ mdSrc: getOptimizedUrl(
49
+ contentfulAssetUrl,
50
+ dimension,
51
+ md,
52
+ quality,
53
+ disableRetina,
54
+ supportsWebp
55
+ ),
56
+ lgSrc: getOptimizedUrl(
57
+ contentfulAssetUrl,
58
+ dimension,
59
+ lg,
60
+ quality,
61
+ disableRetina,
62
+ supportsWebp
63
+ ),
64
+ xlSrc: getOptimizedUrl(
65
+ contentfulAssetUrl,
66
+ dimension,
67
+ xl,
68
+ quality,
69
+ disableRetina,
70
+ supportsWebp
71
+ ),
72
+ fallbackSrc: getFallbackUrl(contentfulAssetUrl, xl, quality)
73
+ })
74
+ })
75
+ }, [contentfulAssetUrl, dimension, disableRetina, lg, md, quality, sm, xl, xs])
76
+
77
+ if (!imgUrls) return null
78
+ return <ResponsiveImage {...imgUrls} alt={alt} {...selectProps(rest)} />
79
+ }
80
+
81
+ OptimizeImage.propTypes = {
82
+ ...selectedSystemPropTypes,
83
+ /**
84
+ * The source to load the image. Only contentful image urls are supported. See https://www.contentful.com/developers/docs/references/images-api/ for details.
85
+ */
86
+ contentfulAssetUrl: PropTypes.string.isRequired,
87
+ /**
88
+ * Alternative text to display if image cannot be loaded or a screen reader is used.
89
+ */
90
+ alt: PropTypes.string.isRequired,
91
+ /**
92
+ * Customize quality as a percentage between 1 and 100.
93
+ */
94
+ quality: PropTypes.number,
95
+ /**
96
+ * Customize width for xs screen size in px, this may affect the quality of the image.
97
+ */
98
+ xs: PropTypes.number,
99
+ /**
100
+ * Customize width for sm screen size in px, this may affect the quality of the image.
101
+ */
102
+ sm: PropTypes.number,
103
+ /**
104
+ * Customize width for md screen size in px, this may affect the quality of the image.
105
+ */
106
+ md: PropTypes.number,
107
+ /**
108
+ * Customize width for lg screen size in px, this may affect the quality of the image.
109
+ */
110
+ lg: PropTypes.number,
111
+ /**
112
+ * Customize width for xl screen size in px, this may affect the quality of the image.
113
+ */
114
+ xl: PropTypes.number,
115
+ /**
116
+ * Switches size dimension to height, default is false
117
+ */
118
+ sizeByHeight: PropTypes.bool,
119
+ /**
120
+ * Turns off retina display functionality
121
+ */
122
+ disableRetina: PropTypes.bool,
123
+ /**
124
+ * Loading strategy.
125
+ * @default 'eager'
126
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading
127
+ */
128
+ loading: PropTypes.oneOf(['eager', 'lazy'])
129
+ }
130
+
131
+ export default OptimizeImage
@@ -0,0 +1,3 @@
1
+ import OptimizeImage from './OptimizeImage'
2
+
3
+ export default OptimizeImage
@@ -0,0 +1,9 @@
1
+ import isSvgUrl from './isSvgUrl'
2
+
3
+ export default function getFallbackUrl(url, width, quality) {
4
+ if (!isSvgUrl(url)) {
5
+ return `${url}?w=${width}&q=${quality}`
6
+ }
7
+
8
+ return url
9
+ }
@@ -0,0 +1,30 @@
1
+ import isSvgUrl from './isSvgUrl'
2
+
3
+ export default function getOptimizedUrl(
4
+ url,
5
+ dimension,
6
+ size,
7
+ quality,
8
+ disableRetina,
9
+ supportsWebp
10
+ ) {
11
+ if (!isSvgUrl(url)) {
12
+ let format = ''
13
+
14
+ if (supportsWebp) {
15
+ format = 'fm=webp'
16
+ } else if (url.match(/\.jpe?g$/i)) {
17
+ format = 'fm=jpg&fl=progressive'
18
+ }
19
+
20
+ let optimizedUrl = `${url}?${dimension}=${size}&q=${quality}&${format}`
21
+
22
+ if (!disableRetina) {
23
+ optimizedUrl += `, ${url}?${dimension}=${size * 2}&q=${quality}&${format} 2x`
24
+ }
25
+
26
+ return optimizedUrl
27
+ }
28
+
29
+ return url
30
+ }
@@ -0,0 +1,33 @@
1
+ let promise
2
+
3
+ /**
4
+ * Taken directly from Google developers guide on how to detect browser support for WebP.
5
+ *
6
+ * @see https://developers.google.com/speed/webp/faq#in_your_own_javascript
7
+ * @return {Promise<boolean>}
8
+ */
9
+ export default function hasWebpSupport() {
10
+ // cache the result, so that this function runs only once
11
+ if (!promise) {
12
+ promise = new Promise((resolve) => {
13
+ // basic support. other test forms exist for lossless, alpha, and animation types.
14
+ // check google guide if data strings are needed
15
+ const lossy = 'UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'
16
+
17
+ const img = document.createElement('img')
18
+
19
+ img.onload = function onLoad() {
20
+ const result = img.width > 0 && img.height > 0
21
+
22
+ resolve(result)
23
+ }
24
+ img.onerror = function onError() {
25
+ resolve(false)
26
+ }
27
+
28
+ img.src = `data:image/webp;base64,${lossy}`
29
+ })
30
+ }
31
+
32
+ return promise
33
+ }
@@ -0,0 +1,5 @@
1
+ import getOptimizedUrl from './getOptimizedUrl'
2
+ import getFallbackUrl from './getFallbackUrl'
3
+ import hasWebpSupport from './hasWebpSupport'
4
+
5
+ export { getOptimizedUrl, getFallbackUrl, hasWebpSupport }
@@ -0,0 +1,3 @@
1
+ export default function isSvgUrl(url) {
2
+ return !!url.match(/\.svg$/i)
3
+ }