@graphcommerce/next-ui 10.1.0-canary.20 → 10.1.0-canary.22
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 +18 -0
- package/FramerScroller/SidebarGallery.tsx +2 -4
- package/Theme/MuiFab.ts +4 -1
- package/YoutubeEmbed/YoutubeEmbed.tsx +201 -0
- package/YoutubeEmbed/index.ts +1 -0
- package/index.ts +1 -0
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 10.1.0-canary.22
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2627](https://github.com/graphcommerce-org/graphcommerce/pull/2627) [`95c188f`](https://github.com/graphcommerce-org/graphcommerce/commit/95c188fcd0dc6cb4ca3edc9877d203d45fe18bb0) - Add `YoutubeEmbed` component — a lightweight lazy-loading YouTube player that defers iframe creation until the user clicks the poster. Uses preconnect on hover for fast playback start and is styled with MUI sx, so no external CSS is required. Supports playlists, no-cookie mode, custom aspect ratios and ad-network preconnect hints.
|
|
8
|
+
|
|
9
|
+
`ProductVideo` (used by `ProductPageGallery`) now delegates YouTube playback to `YoutubeEmbed`, so any product whose Magento `media_gallery` contains a YouTube video entry gets the new lazy-loading player on its product page. Vimeo and self-hosted video paths are unchanged. The Magento preview image is passed as the YoutubeEmbed `thumbnail` so the visible poster stays consistent with the rest of the gallery.
|
|
10
|
+
|
|
11
|
+
Fix `SidebarGallery` so it forwards the `Additional` and `slotProps` from each image to `MotionImageAspect`. Before this fix the gallery silently dropped both props, which meant any `Additional` overlay configured by `ProductPageGallery` (the `<ProductVideo>` overlay with its `PlayCircle` and the new `YoutubeEmbed`) never reached the DOM. That was a latent regression that made all video-gallery entries render as static images, with or without this PR's YouTube changes.
|
|
12
|
+
|
|
13
|
+
Fix `playwright.config.ts` so `npx playwright test` actually loads. The config previously imported `examples/magento-graphcms/next.config.ts`, which transitively pulled `@graphcommerce/next-config`'s ESM build into a CJS context and crashed with `ReferenceError: exports is not defined`. Replaced with an opt-in `PLAYWRIGHT_LOCALES` env var for the multi-locale projects that the next.config import was meant to drive. ([@paales](https://github.com/paales))
|
|
14
|
+
|
|
15
|
+
## 10.1.0-canary.21
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#2633](https://github.com/graphcommerce-org/graphcommerce/pull/2633) [`7554ad4`](https://github.com/graphcommerce-org/graphcommerce/commit/7554ad479cf78d790eeedd3f862c5d57403bda51) - Fix: `<Fab variant="extended">` no longer gets a fixed `width` from `MuiFabSizes`. The size-based width/height variants are now scoped to `variant: 'circular'` only, so extended Fabs can grow with their label (controlled via `min-width` instead) as MUI intends. Previously every extended Fab without an explicit `size` matched the default `large` rule and was forced to 54px wide. ([@paales](https://github.com/paales))
|
|
20
|
+
|
|
3
21
|
## 10.1.0-canary.20
|
|
4
22
|
|
|
5
23
|
## 10.1.0-canary.19
|
|
@@ -254,13 +254,11 @@ export function SidebarGallery(props: SidebarGalleryProps) {
|
|
|
254
254
|
<MotionImageAspect
|
|
255
255
|
// eslint-disable-next-line react/no-array-index-key
|
|
256
256
|
key={idx}
|
|
257
|
+
{...image}
|
|
257
258
|
layout
|
|
258
259
|
layoutDependency={zoomed}
|
|
259
|
-
src={image.src}
|
|
260
|
-
width={image.width}
|
|
261
|
-
height={image.height}
|
|
262
260
|
loading={idx === 0 ? 'eager' : 'lazy'}
|
|
263
|
-
sx={{ display: 'block', objectFit: 'contain' }}
|
|
261
|
+
sx={sxx({ display: 'block', objectFit: 'contain' }, image.sx)}
|
|
264
262
|
sizes={{
|
|
265
263
|
0: '100vw',
|
|
266
264
|
[theme.breakpoints.values.md]: zoomed ? '100vw' : '60vw',
|
package/Theme/MuiFab.ts
CHANGED
|
@@ -80,11 +80,14 @@ const sizes: FabSize[] = [
|
|
|
80
80
|
/**
|
|
81
81
|
* This defines the sizes for the added responsive variant.
|
|
82
82
|
*
|
|
83
|
+
* Only applied to circular (default) Fabs — extended Fabs must keep `width: auto` and rely on their
|
|
84
|
+
* content + `min-width`, otherwise they collapse to a fixed square regardless of label.
|
|
85
|
+
*
|
|
83
86
|
* To override the sizes, please do not add variant declarations direcly, but modify
|
|
84
87
|
* `yourTheme.components.MuiFabExtra.sizes` instead.
|
|
85
88
|
*/
|
|
86
89
|
export const MuiFabSizes: FabVariants = sizes.map((size) => ({
|
|
87
|
-
props: { size },
|
|
90
|
+
props: { size, variant: 'circular' },
|
|
88
91
|
style: ({ theme }) => fabWidthHeight(size, theme),
|
|
89
92
|
}))
|
|
90
93
|
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Box, type SxProps, type Theme } from '@mui/material'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { extendableComponent } from '../Styles/extendableComponent'
|
|
4
|
+
import { sxx } from '../utils/sxx'
|
|
5
|
+
|
|
6
|
+
export type YoutubePosterResolution =
|
|
7
|
+
| 'default'
|
|
8
|
+
| 'mqdefault'
|
|
9
|
+
| 'hqdefault'
|
|
10
|
+
| 'sddefault'
|
|
11
|
+
| 'maxresdefault'
|
|
12
|
+
|
|
13
|
+
export type YoutubeEmbedProps = {
|
|
14
|
+
id: string
|
|
15
|
+
title: string
|
|
16
|
+
announce?: string
|
|
17
|
+
adNetwork?: boolean
|
|
18
|
+
aspectHeight?: number
|
|
19
|
+
aspectWidth?: number
|
|
20
|
+
noCookie?: boolean
|
|
21
|
+
cookie?: boolean
|
|
22
|
+
params?: string
|
|
23
|
+
playlist?: boolean
|
|
24
|
+
playlistCoverId?: string
|
|
25
|
+
poster?: YoutubePosterResolution
|
|
26
|
+
webp?: boolean
|
|
27
|
+
muted?: boolean
|
|
28
|
+
thumbnail?: string
|
|
29
|
+
rel?: 'preload' | 'prefetch'
|
|
30
|
+
onIframeAdded?: () => void
|
|
31
|
+
sx?: SxProps<Theme>
|
|
32
|
+
ref?: React.Ref<HTMLIFrameElement>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const componentName = 'YoutubeEmbed' as const
|
|
36
|
+
const parts = ['root', 'poster', 'playButton', 'iframe'] as const
|
|
37
|
+
const { classes } = extendableComponent(componentName, parts)
|
|
38
|
+
|
|
39
|
+
export function YoutubeEmbed(props: YoutubeEmbedProps) {
|
|
40
|
+
const {
|
|
41
|
+
id,
|
|
42
|
+
title,
|
|
43
|
+
announce = 'Watch',
|
|
44
|
+
adNetwork = false,
|
|
45
|
+
aspectHeight = 9,
|
|
46
|
+
aspectWidth = 16,
|
|
47
|
+
noCookie = true,
|
|
48
|
+
cookie = false,
|
|
49
|
+
params = '',
|
|
50
|
+
playlist = false,
|
|
51
|
+
playlistCoverId,
|
|
52
|
+
poster = 'hqdefault',
|
|
53
|
+
webp = false,
|
|
54
|
+
muted = false,
|
|
55
|
+
thumbnail,
|
|
56
|
+
rel = 'preload',
|
|
57
|
+
onIframeAdded,
|
|
58
|
+
sx,
|
|
59
|
+
ref,
|
|
60
|
+
} = props
|
|
61
|
+
|
|
62
|
+
const [preconnected, setPreconnected] = React.useState(false)
|
|
63
|
+
const [iframeReady, setIframeReady] = React.useState(false)
|
|
64
|
+
|
|
65
|
+
const videoId = encodeURIComponent(id)
|
|
66
|
+
const videoPlaylistCoverId =
|
|
67
|
+
typeof playlistCoverId === 'string' ? encodeURIComponent(playlistCoverId) : null
|
|
68
|
+
|
|
69
|
+
const format = webp ? 'webp' : 'jpg'
|
|
70
|
+
const vi = webp ? 'vi_webp' : 'vi'
|
|
71
|
+
const posterUrl =
|
|
72
|
+
thumbnail ||
|
|
73
|
+
(!playlist
|
|
74
|
+
? `https://i.ytimg.com/${vi}/${videoId}/${poster}.${format}`
|
|
75
|
+
: `https://i.ytimg.com/${vi}/${videoPlaylistCoverId}/${poster}.${format}`)
|
|
76
|
+
|
|
77
|
+
const ytUrl = cookie || !noCookie ? 'https://www.youtube.com' : 'https://www.youtube-nocookie.com'
|
|
78
|
+
const paramsSuffix = params ? `&${params}` : ''
|
|
79
|
+
const mutedSuffix = muted ? '&mute=1' : ''
|
|
80
|
+
|
|
81
|
+
const iframeSrc = !playlist
|
|
82
|
+
? `${ytUrl}/embed/${videoId}?autoplay=1&state=1${mutedSuffix}${paramsSuffix}`
|
|
83
|
+
: `${ytUrl}/embed/videoseries?autoplay=1${mutedSuffix}&list=${videoId}${paramsSuffix}`
|
|
84
|
+
|
|
85
|
+
const warmConnections = () => {
|
|
86
|
+
if (!preconnected) setPreconnected(true)
|
|
87
|
+
}
|
|
88
|
+
const addIframe = () => {
|
|
89
|
+
if (!iframeReady) setIframeReady(true)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (iframeReady) onIframeAdded?.()
|
|
94
|
+
}, [iframeReady, onIframeAdded])
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<>
|
|
98
|
+
<link rel={rel} href={posterUrl} as='image' />
|
|
99
|
+
{preconnected && (
|
|
100
|
+
<>
|
|
101
|
+
<link rel='preconnect' href={ytUrl} />
|
|
102
|
+
<link rel='preconnect' href='https://www.google.com' />
|
|
103
|
+
{adNetwork && (
|
|
104
|
+
<>
|
|
105
|
+
<link rel='preconnect' href='https://static.doubleclick.net' />
|
|
106
|
+
<link rel='preconnect' href='https://googleads.g.doubleclick.net' />
|
|
107
|
+
</>
|
|
108
|
+
)}
|
|
109
|
+
</>
|
|
110
|
+
)}
|
|
111
|
+
<Box
|
|
112
|
+
className={classes.root}
|
|
113
|
+
onPointerOver={warmConnections}
|
|
114
|
+
onClick={addIframe}
|
|
115
|
+
data-title={title}
|
|
116
|
+
sx={sxx(
|
|
117
|
+
{
|
|
118
|
+
position: 'relative',
|
|
119
|
+
display: 'block',
|
|
120
|
+
contain: 'content',
|
|
121
|
+
backgroundPosition: 'center center',
|
|
122
|
+
backgroundSize: 'cover',
|
|
123
|
+
cursor: 'pointer',
|
|
124
|
+
maxWidth: '100%',
|
|
125
|
+
aspectRatio: `${aspectWidth} / ${aspectHeight}`,
|
|
126
|
+
backgroundImage: `url(${posterUrl})`,
|
|
127
|
+
'&::before': {
|
|
128
|
+
content: '""',
|
|
129
|
+
display: 'block',
|
|
130
|
+
position: 'absolute',
|
|
131
|
+
top: 0,
|
|
132
|
+
right: 0,
|
|
133
|
+
left: 0,
|
|
134
|
+
height: '60px',
|
|
135
|
+
background:
|
|
136
|
+
'linear-gradient(180deg, rgba(0,0,0,0.247) 0%, rgba(0,0,0,0) 100%)',
|
|
137
|
+
pointerEvents: 'none',
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
sx,
|
|
141
|
+
)}
|
|
142
|
+
>
|
|
143
|
+
{!iframeReady && (
|
|
144
|
+
<Box
|
|
145
|
+
component='button'
|
|
146
|
+
type='button'
|
|
147
|
+
className={classes.playButton}
|
|
148
|
+
aria-label={`${announce} ${title}`}
|
|
149
|
+
sx={{
|
|
150
|
+
border: 0,
|
|
151
|
+
padding: 0,
|
|
152
|
+
cursor: 'pointer',
|
|
153
|
+
width: '68px',
|
|
154
|
+
height: '48px',
|
|
155
|
+
position: 'absolute',
|
|
156
|
+
top: '50%',
|
|
157
|
+
left: '50%',
|
|
158
|
+
marginLeft: '-34px',
|
|
159
|
+
marginTop: '-24px',
|
|
160
|
+
borderRadius: '14%',
|
|
161
|
+
transition: 'background-color 100ms cubic-bezier(0, 0, 0.2, 1)',
|
|
162
|
+
backgroundColor: '#212121',
|
|
163
|
+
opacity: 0.8,
|
|
164
|
+
'&:hover, &:focus': { backgroundColor: 'red', opacity: 1 },
|
|
165
|
+
'&::before': {
|
|
166
|
+
content: '""',
|
|
167
|
+
borderStyle: 'solid',
|
|
168
|
+
borderWidth: '11px 0 11px 19px',
|
|
169
|
+
borderColor: 'transparent transparent transparent #fff',
|
|
170
|
+
display: 'block',
|
|
171
|
+
position: 'absolute',
|
|
172
|
+
top: '50%',
|
|
173
|
+
left: '50%',
|
|
174
|
+
transform: 'translate3d(-50%, -50%, 0)',
|
|
175
|
+
},
|
|
176
|
+
}}
|
|
177
|
+
/>
|
|
178
|
+
)}
|
|
179
|
+
{iframeReady && (
|
|
180
|
+
<Box
|
|
181
|
+
component='iframe'
|
|
182
|
+
ref={ref}
|
|
183
|
+
className={classes.iframe}
|
|
184
|
+
title={title}
|
|
185
|
+
allow='accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture'
|
|
186
|
+
allowFullScreen
|
|
187
|
+
src={iframeSrc}
|
|
188
|
+
sx={{
|
|
189
|
+
border: 0,
|
|
190
|
+
position: 'absolute',
|
|
191
|
+
top: 0,
|
|
192
|
+
left: 0,
|
|
193
|
+
width: '100%',
|
|
194
|
+
height: '100%',
|
|
195
|
+
}}
|
|
196
|
+
/>
|
|
197
|
+
)}
|
|
198
|
+
</Box>
|
|
199
|
+
</>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './YoutubeEmbed'
|
package/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ export * from './ToggleButton/ToggleButton'
|
|
|
66
66
|
export * from './ToggleButtonGroup/ToggleButtonGroup'
|
|
67
67
|
export * from './UspList/UspList'
|
|
68
68
|
export * from './UspList/UspListItem'
|
|
69
|
+
export * from './YoutubeEmbed'
|
|
69
70
|
export * from './utils/cookie'
|
|
70
71
|
export * from './utils/cssFlags'
|
|
71
72
|
export * from './utils/normalizeLocale'
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@graphcommerce/next-ui",
|
|
3
3
|
"homepage": "https://www.graphcommerce.org/",
|
|
4
4
|
"repository": "github:graphcommerce-org/graphcommerce",
|
|
5
|
-
"version": "10.1.0-canary.
|
|
5
|
+
"version": "10.1.0-canary.22",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
|
8
8
|
"eslintConfig": {
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"@emotion/react": "^11.14.0",
|
|
44
44
|
"@emotion/server": "^11.11.0",
|
|
45
45
|
"@emotion/styled": "^11.14.1",
|
|
46
|
-
"@graphcommerce/eslint-config-pwa": "^10.1.0-canary.
|
|
47
|
-
"@graphcommerce/framer-next-pages": "^10.1.0-canary.
|
|
48
|
-
"@graphcommerce/framer-scroller": "^10.1.0-canary.
|
|
49
|
-
"@graphcommerce/framer-utils": "^10.1.0-canary.
|
|
50
|
-
"@graphcommerce/image": "^10.1.0-canary.
|
|
51
|
-
"@graphcommerce/prettier-config-pwa": "^10.1.0-canary.
|
|
52
|
-
"@graphcommerce/typescript-config-pwa": "^10.1.0-canary.
|
|
46
|
+
"@graphcommerce/eslint-config-pwa": "^10.1.0-canary.22",
|
|
47
|
+
"@graphcommerce/framer-next-pages": "^10.1.0-canary.22",
|
|
48
|
+
"@graphcommerce/framer-scroller": "^10.1.0-canary.22",
|
|
49
|
+
"@graphcommerce/framer-utils": "^10.1.0-canary.22",
|
|
50
|
+
"@graphcommerce/image": "^10.1.0-canary.22",
|
|
51
|
+
"@graphcommerce/prettier-config-pwa": "^10.1.0-canary.22",
|
|
52
|
+
"@graphcommerce/typescript-config-pwa": "^10.1.0-canary.22",
|
|
53
53
|
"@lingui/core": "^5",
|
|
54
54
|
"@lingui/macro": "^5",
|
|
55
55
|
"@lingui/react": "^5",
|