@graphcommerce/next-ui 10.1.0-canary.7 → 11.0.0-canary.45
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 +119 -0
- package/Fab/Fab.tsx +1 -0
- package/Footer/Footer.tsx +3 -3
- package/FramerScroller/SidebarGallery.tsx +2 -4
- package/FullPageMessage/FullPageMessage.tsx +1 -1
- package/LayoutDefault/components/LayoutDefault.tsx +8 -0
- package/Navigation/components/NavigationFab.tsx +52 -28
- package/PageLoadIndicator/PageLoadIndicator.tsx +1 -0
- package/Pagination/PaginationExtended.tsx +1 -2
- package/Row/HeroBanner/HeroBanner.tsx +36 -28
- 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,124 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
+
## 11.0.0-canary.45
|
|
4
|
+
|
|
5
|
+
## 10.1.0-canary.44
|
|
6
|
+
|
|
7
|
+
## 10.1.0-canary.43
|
|
8
|
+
|
|
9
|
+
## 10.1.0-canary.42
|
|
10
|
+
|
|
11
|
+
## 10.1.0-canary.41
|
|
12
|
+
|
|
13
|
+
## 10.1.0-canary.40
|
|
14
|
+
|
|
15
|
+
## 10.1.0-canary.39
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- [#2652](https://github.com/graphcommerce-org/graphcommerce/pull/2652) [`132d1e1`](https://github.com/graphcommerce-org/graphcommerce/commit/132d1e1075b22843ed5934314c723df06ea13a6a) - `HeroBanner` gains an `asset?: React.ReactNode` prop, and `videoSrc` is deprecated.
|
|
20
|
+
|
|
21
|
+
The banner rendered a `<video>` itself, from a raw URL — so it could only ever hold a video, and left nowhere to hang a poster, the one place a poster matters most (a full-bleed autoplaying video above the fold). It now takes a node and renders it, the way its sibling `SpecialBanner` already does; positioning stays with the banner, stretching whatever is passed to fill via `& img, & video`.
|
|
22
|
+
|
|
23
|
+
```diff
|
|
24
|
+
- <HeroBanner videoSrc={asset.filename} … />
|
|
25
|
+
+ <HeroBanner asset={<Asset asset={asset} poster={poster} />} … />
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Taking a node rather than a source also keeps `next-ui` free of any CMS: the Storyblok and Hygraph examples each pass their own `<Asset>`, both of which already render images and video.
|
|
29
|
+
|
|
30
|
+
`videoSrc` still works but is deprecated: when set (and `asset` is not) it renders a bare autoplaying video, keeping the `HeroBanner-video` class. The only behavioural change on that path is that the scroll parallax is gone, along with the `framer-motion`, `useScrollY` and `clientSize` machinery it needed. Migrate to `asset` to regain images, posters, and control over how the media is rendered. ([@bramvanderholst](https://github.com/bramvanderholst))
|
|
31
|
+
|
|
32
|
+
## 10.1.0-canary.38
|
|
33
|
+
|
|
34
|
+
## 10.1.0-canary.37
|
|
35
|
+
|
|
36
|
+
## 10.1.0-canary.36
|
|
37
|
+
|
|
38
|
+
## 10.1.0-canary.35
|
|
39
|
+
|
|
40
|
+
## 10.1.0-canary.34
|
|
41
|
+
|
|
42
|
+
## 10.1.0-canary.33
|
|
43
|
+
|
|
44
|
+
## 10.1.0-canary.32
|
|
45
|
+
|
|
46
|
+
## 10.1.0-canary.31
|
|
47
|
+
|
|
48
|
+
## 10.1.0-canary.30
|
|
49
|
+
|
|
50
|
+
### Patch Changes
|
|
51
|
+
|
|
52
|
+
- [#2643](https://github.com/graphcommerce-org/graphcommerce/pull/2643) [`04ba9be`](https://github.com/graphcommerce-org/graphcommerce/commit/04ba9be6f050cc686133bec57f40da9f8eecb4c8) - Only render disabled pagination items as links when enabled. Disabled `PaginationItem`s no longer receive a `component`/`href`, preventing crawlers from following links to an infinite number of list pages and inflating the static page cache. ([@Giovanni-Schroevers](https://github.com/Giovanni-Schroevers))
|
|
53
|
+
|
|
54
|
+
## 10.1.0-canary.29
|
|
55
|
+
|
|
56
|
+
## 10.1.0-canary.28
|
|
57
|
+
|
|
58
|
+
## 10.1.0-canary.27
|
|
59
|
+
|
|
60
|
+
## 10.1.0-canary.26
|
|
61
|
+
|
|
62
|
+
## 10.1.0-canary.25
|
|
63
|
+
|
|
64
|
+
### Minor Changes
|
|
65
|
+
|
|
66
|
+
- [#2637](https://github.com/graphcommerce-org/graphcommerce/pull/2637) [`bdaa6ec`](https://github.com/graphcommerce-org/graphcommerce/commit/bdaa6ec6aa2669b74fc6702ad46336db4c154b7c) - Refactored `LayoutNavigation` into composable pieces (`Header`, `HeaderContainer`, `MenuOverlay`, project-local `LayoutDefault`). `LayoutDefault` / `LayoutDefaultProps` in `@graphcommerce/next-ui` are marked `@deprecated`. If you are upgrading and do not want these changes, you can just discard them. This is just a structural change for more ease of use. No visually change. ([@bramvanderholst](https://github.com/bramvanderholst))
|
|
67
|
+
|
|
68
|
+
### Patch Changes
|
|
69
|
+
|
|
70
|
+
- [#2637](https://github.com/graphcommerce-org/graphcommerce/pull/2637) [`9f5e765`](https://github.com/graphcommerce-org/graphcommerce/commit/9f5e76575e3932f8fcd8689d8ef42e4c44b923d1) - Added disableScrollEffects prop to CartFab & NavigationFab for easier customization of the header ([@bramvanderholst](https://github.com/bramvanderholst))
|
|
71
|
+
|
|
72
|
+
- [#2637](https://github.com/graphcommerce-org/graphcommerce/pull/2637) [`21a676b`](https://github.com/graphcommerce-org/graphcommerce/commit/21a676bb50888278d437b88a6e49e6f3d0c4d4bb) - Changed Footer props type to allow setting footer container props ([@bramvanderholst](https://github.com/bramvanderholst))
|
|
73
|
+
|
|
74
|
+
## 10.1.0-canary.24
|
|
75
|
+
|
|
76
|
+
## 10.1.0-canary.23
|
|
77
|
+
|
|
78
|
+
## 10.1.0-canary.22
|
|
79
|
+
|
|
80
|
+
### Minor Changes
|
|
81
|
+
|
|
82
|
+
- [#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.
|
|
83
|
+
|
|
84
|
+
`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.
|
|
85
|
+
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
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))
|
|
89
|
+
|
|
90
|
+
## 10.1.0-canary.21
|
|
91
|
+
|
|
92
|
+
### Patch Changes
|
|
93
|
+
|
|
94
|
+
- [#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))
|
|
95
|
+
|
|
96
|
+
## 10.1.0-canary.20
|
|
97
|
+
|
|
98
|
+
## 10.1.0-canary.19
|
|
99
|
+
|
|
100
|
+
## 10.1.0-canary.18
|
|
101
|
+
|
|
102
|
+
## 10.1.0-canary.17
|
|
103
|
+
|
|
104
|
+
## 10.1.0-canary.16
|
|
105
|
+
|
|
106
|
+
## 10.1.0-canary.15
|
|
107
|
+
|
|
108
|
+
## 10.1.0-canary.14
|
|
109
|
+
|
|
110
|
+
## 10.1.0-canary.13
|
|
111
|
+
|
|
112
|
+
## 10.1.0-canary.12
|
|
113
|
+
|
|
114
|
+
## 10.1.0-canary.11
|
|
115
|
+
|
|
116
|
+
## 10.1.0-canary.10
|
|
117
|
+
|
|
118
|
+
## 10.1.0-canary.9
|
|
119
|
+
|
|
120
|
+
## 10.1.0-canary.8
|
|
121
|
+
|
|
3
122
|
## 10.1.0-canary.7
|
|
4
123
|
|
|
5
124
|
### Patch Changes
|
package/Fab/Fab.tsx
CHANGED
package/Footer/Footer.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { sxx } from '@graphcommerce/next-ui'
|
|
2
|
-
import type { ContainerProps } from '@mui/material'
|
|
3
2
|
import { Box } from '@mui/material'
|
|
4
|
-
import React from 'react'
|
|
3
|
+
import type React from 'react'
|
|
4
|
+
import type { ContainerSizingProps } from '../Container/Container'
|
|
5
5
|
import { Container } from '../Container/Container'
|
|
6
6
|
import { LazyHydrate } from '../LazyHydrate'
|
|
7
7
|
import { extendableComponent } from '../Styles'
|
|
@@ -12,7 +12,7 @@ export type FooterProps = {
|
|
|
12
12
|
customerService?: React.ReactNode
|
|
13
13
|
copyright?: React.ReactNode
|
|
14
14
|
children?: React.ReactNode
|
|
15
|
-
} &
|
|
15
|
+
} & ContainerSizingProps
|
|
16
16
|
|
|
17
17
|
const { classes, selectors } = extendableComponent('Footer', [
|
|
18
18
|
'root',
|
|
@@ -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',
|
|
@@ -52,7 +52,7 @@ export function FullPageMessage(props: FullPageMessageProps) {
|
|
|
52
52
|
justifyItems: 'center',
|
|
53
53
|
}}
|
|
54
54
|
>
|
|
55
|
-
<Box className={classes.iconWrapper}>{icon}</Box>
|
|
55
|
+
<Box className={classes.iconWrapper} aria-hidden='true'>{icon}</Box>
|
|
56
56
|
|
|
57
57
|
<Box
|
|
58
58
|
className={classes.subject}
|
|
@@ -10,6 +10,10 @@ import { SkipLink } from '../../SkipLink/SkipLink'
|
|
|
10
10
|
import { extendableComponent } from '../../Styles'
|
|
11
11
|
import { useFabSize } from '../../Theme'
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated Import `LayoutDefaultProps` from your project's
|
|
15
|
+
* `components/Layout/LayoutDefault.tsx` instead.
|
|
16
|
+
*/
|
|
13
17
|
export type LayoutDefaultProps = {
|
|
14
18
|
className?: string
|
|
15
19
|
beforeHeader?: React.ReactNode
|
|
@@ -31,6 +35,10 @@ const { withState } = extendableComponent<OwnerState, 'LayoutDefault', typeof pa
|
|
|
31
35
|
parts,
|
|
32
36
|
)
|
|
33
37
|
|
|
38
|
+
/**
|
|
39
|
+
* @deprecated Import `LayoutDefault` from your project's
|
|
40
|
+
* `components/Layout/LayoutDefault.tsx` instead.
|
|
41
|
+
*/
|
|
34
42
|
export function LayoutDefault(props: LayoutDefaultProps) {
|
|
35
43
|
const {
|
|
36
44
|
children,
|
|
@@ -17,6 +17,8 @@ const MotionDiv = styled(m.div)({})
|
|
|
17
17
|
export type NavigationFabProps = {
|
|
18
18
|
menuIcon?: React.ReactNode
|
|
19
19
|
closeIcon?: React.ReactNode
|
|
20
|
+
disableScrollEffects?: boolean
|
|
21
|
+
disableElevation?: boolean
|
|
20
22
|
sx?: SxProps<Theme>
|
|
21
23
|
} & Pick<FabProps, 'color' | 'size' | 'variant' | 'onClick'>
|
|
22
24
|
|
|
@@ -29,7 +31,14 @@ type OwnerState = {
|
|
|
29
31
|
const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
|
|
30
32
|
|
|
31
33
|
export function NavigationFab(props: NavigationFabProps) {
|
|
32
|
-
const {
|
|
34
|
+
const {
|
|
35
|
+
menuIcon,
|
|
36
|
+
closeIcon,
|
|
37
|
+
disableScrollEffects,
|
|
38
|
+
disableElevation,
|
|
39
|
+
sx = [],
|
|
40
|
+
...fabProps
|
|
41
|
+
} = props
|
|
33
42
|
const router = useRouter()
|
|
34
43
|
const [openEl, setOpenEl] = React.useState<null | HTMLElement>(null)
|
|
35
44
|
|
|
@@ -53,28 +62,41 @@ export function NavigationFab(props: NavigationFabProps) {
|
|
|
53
62
|
<Box sx={sxx({ position: 'relative', width: fabIconSize, height: fabIconSize }, sx)}>
|
|
54
63
|
<MotionDiv
|
|
55
64
|
className={classes.wrapper}
|
|
56
|
-
sx={
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
sx={
|
|
66
|
+
disableScrollEffects
|
|
67
|
+
? {
|
|
68
|
+
opacity: '1 !important',
|
|
69
|
+
transform: 'none !important',
|
|
70
|
+
}
|
|
71
|
+
: {
|
|
72
|
+
[theme.breakpoints.down('md')]: {
|
|
73
|
+
opacity: '1 !important',
|
|
74
|
+
transform: 'none !important',
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
}
|
|
62
78
|
style={{ opacity }}
|
|
63
79
|
>
|
|
64
80
|
<Fab
|
|
65
81
|
color='inherit'
|
|
66
82
|
aria-label='Open Menu'
|
|
67
83
|
size='responsive'
|
|
68
|
-
sx={
|
|
69
|
-
|
|
70
|
-
'&:hover, &:focus': {
|
|
84
|
+
sx={sxx(
|
|
85
|
+
{
|
|
71
86
|
boxShadow: 'none',
|
|
87
|
+
pointerEvents: 'all',
|
|
88
|
+
'&:hover, &:focus': {
|
|
89
|
+
boxShadow: 'none',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
!fabProps.color && {
|
|
93
|
+
'&:hover, &:focus': {
|
|
94
|
+
background: theme.vars.palette.text.primary,
|
|
95
|
+
},
|
|
72
96
|
background: theme.vars.palette.text.primary,
|
|
97
|
+
color: theme.vars.palette.background.paper,
|
|
73
98
|
},
|
|
74
|
-
|
|
75
|
-
pointerEvents: 'all',
|
|
76
|
-
color: theme.vars.palette.background.paper,
|
|
77
|
-
}}
|
|
99
|
+
)}
|
|
78
100
|
className={classes.fab}
|
|
79
101
|
{...fabProps}
|
|
80
102
|
>
|
|
@@ -109,20 +131,22 @@ export function NavigationFab(props: NavigationFabProps) {
|
|
|
109
131
|
/>
|
|
110
132
|
)}
|
|
111
133
|
</Fab>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
134
|
+
{!disableElevation && (
|
|
135
|
+
<MotionDiv
|
|
136
|
+
sx={{
|
|
137
|
+
pointerEvents: 'none',
|
|
138
|
+
borderRadius: '99em',
|
|
139
|
+
position: 'absolute',
|
|
140
|
+
height: '100%',
|
|
141
|
+
width: '100%',
|
|
142
|
+
boxShadow: theme.shadows[6],
|
|
143
|
+
top: 0,
|
|
144
|
+
[theme.breakpoints.down('md')]: { opacity: '1 !important' },
|
|
145
|
+
}}
|
|
146
|
+
className={classes.shadow}
|
|
147
|
+
style={disableScrollEffects ? undefined : { opacity: shadowOpacity }}
|
|
148
|
+
/>
|
|
149
|
+
)}
|
|
126
150
|
</MotionDiv>
|
|
127
151
|
</Box>
|
|
128
152
|
)
|
|
@@ -84,8 +84,7 @@ export function PaginationExtended(props: PaginationExtendedProps) {
|
|
|
84
84
|
renderItem ??
|
|
85
85
|
((item) => (
|
|
86
86
|
<PaginationItem
|
|
87
|
-
component
|
|
88
|
-
href={paginationHref(item)}
|
|
87
|
+
{...(!item.disabled && { component: NextLink, href: paginationHref(item) })}
|
|
89
88
|
slots={{ previous: Prev, next: Next }}
|
|
90
89
|
{...item}
|
|
91
90
|
/>
|
|
@@ -1,32 +1,37 @@
|
|
|
1
|
-
import { clientSize } from '@graphcommerce/framer-utils'
|
|
2
|
-
import { sxx } from '@graphcommerce/next-ui'
|
|
3
1
|
import type { ContainerProps, SxProps, Theme } from '@mui/material'
|
|
4
|
-
import { Box
|
|
5
|
-
import { m, useTransform } from 'framer-motion'
|
|
2
|
+
import { Box } from '@mui/material'
|
|
6
3
|
import React from 'react'
|
|
7
|
-
import { useScrollY } from '../../Layout/hooks/useScrollY'
|
|
8
4
|
import { extendableComponent, responsiveVal } from '../../Styles'
|
|
9
5
|
import { Row } from '../Row'
|
|
10
6
|
|
|
11
7
|
export type HeroBannerProps = ContainerProps & {
|
|
12
8
|
pageLinks: React.ReactNode
|
|
13
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Rendered full-bleed behind the copy. Takes a node rather than a source, so
|
|
11
|
+
* the banner is agnostic about what fills it — an image, a video, a CMS
|
|
12
|
+
* component. See `SpecialBanner`, which takes its asset the same way.
|
|
13
|
+
*/
|
|
14
|
+
asset?: React.ReactNode
|
|
15
|
+
/**
|
|
16
|
+
* @deprecated Pass an `asset` node instead — e.g. `<Asset asset={…} />`,
|
|
17
|
+
* which also renders images and can carry a poster. When set (and `asset`
|
|
18
|
+
* is not), it renders a bare autoplaying video, without the scroll parallax
|
|
19
|
+
* earlier versions applied.
|
|
20
|
+
*/
|
|
21
|
+
videoSrc?: string
|
|
14
22
|
children: React.ReactNode
|
|
15
23
|
sx?: SxProps<Theme>
|
|
16
24
|
}
|
|
17
25
|
|
|
18
26
|
const compName = 'HeroBanner'
|
|
27
|
+
// `video` and `animated` are unused since the banner stopped rendering its own
|
|
28
|
+
// motion video, but stay in the parts list so the generated class names remain
|
|
29
|
+
// available to consumers targeting them in global CSS.
|
|
19
30
|
const parts = ['root', 'wrapper', 'copy', 'asset', 'animated', 'video'] as const
|
|
20
31
|
const { classes } = extendableComponent(compName, parts)
|
|
21
32
|
|
|
22
|
-
const MotionVideo = styled(m.video)({})
|
|
23
|
-
|
|
24
33
|
export function HeroBanner(props: HeroBannerProps) {
|
|
25
|
-
const { pageLinks, videoSrc, children, sx = [], ...containerProps } = props
|
|
26
|
-
const scrollY = useScrollY()
|
|
27
|
-
const scale = useTransform([scrollY, clientSize.y], ([scrollYCurr, clientSizeYCurr]: number[]) =>
|
|
28
|
-
clientSizeYCurr ? (scrollYCurr / clientSizeYCurr) * 1.7 + 1 : 1,
|
|
29
|
-
)
|
|
34
|
+
const { pageLinks, asset, videoSrc, children, sx = [], ...containerProps } = props
|
|
30
35
|
|
|
31
36
|
return (
|
|
32
37
|
<Row maxWidth={false} {...containerProps} className={classes.root} sx={sx}>
|
|
@@ -60,25 +65,28 @@ export function HeroBanner(props: HeroBannerProps) {
|
|
|
60
65
|
sx={{
|
|
61
66
|
gridArea: '1 / 1',
|
|
62
67
|
position: 'relative',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
src={videoSrc}
|
|
67
|
-
autoPlay
|
|
68
|
-
muted
|
|
69
|
-
loop
|
|
70
|
-
playsInline
|
|
71
|
-
disableRemotePlayback
|
|
72
|
-
className={classes.video}
|
|
73
|
-
style={{ scale }}
|
|
74
|
-
sx={{
|
|
68
|
+
// The banner sizes itself from its copy, so whatever is passed has
|
|
69
|
+
// to fill the box rather than dictate it.
|
|
70
|
+
'& img, & video': {
|
|
75
71
|
position: 'absolute',
|
|
76
|
-
transition: 'transform 0.5s cubic-bezier(0.33, 1, 0.68, 1)',
|
|
77
72
|
objectFit: 'cover',
|
|
78
73
|
width: '100%',
|
|
79
74
|
height: '100%',
|
|
80
|
-
}
|
|
81
|
-
|
|
75
|
+
},
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{asset ??
|
|
79
|
+
(videoSrc ? (
|
|
80
|
+
<video
|
|
81
|
+
src={videoSrc}
|
|
82
|
+
autoPlay
|
|
83
|
+
muted
|
|
84
|
+
loop
|
|
85
|
+
playsInline
|
|
86
|
+
disableRemotePlayback
|
|
87
|
+
className={classes.video}
|
|
88
|
+
/>
|
|
89
|
+
) : null)}
|
|
82
90
|
</Box>
|
|
83
91
|
</Box>
|
|
84
92
|
</Row>
|
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": "
|
|
5
|
+
"version": "11.0.0-canary.45",
|
|
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": "^
|
|
47
|
-
"@graphcommerce/framer-next-pages": "^
|
|
48
|
-
"@graphcommerce/framer-scroller": "^
|
|
49
|
-
"@graphcommerce/framer-utils": "^
|
|
50
|
-
"@graphcommerce/image": "^
|
|
51
|
-
"@graphcommerce/prettier-config-pwa": "^
|
|
52
|
-
"@graphcommerce/typescript-config-pwa": "^
|
|
46
|
+
"@graphcommerce/eslint-config-pwa": "^11.0.0-canary.45",
|
|
47
|
+
"@graphcommerce/framer-next-pages": "^11.0.0-canary.45",
|
|
48
|
+
"@graphcommerce/framer-scroller": "^11.0.0-canary.45",
|
|
49
|
+
"@graphcommerce/framer-utils": "^11.0.0-canary.45",
|
|
50
|
+
"@graphcommerce/image": "^11.0.0-canary.45",
|
|
51
|
+
"@graphcommerce/prettier-config-pwa": "^11.0.0-canary.45",
|
|
52
|
+
"@graphcommerce/typescript-config-pwa": "^11.0.0-canary.45",
|
|
53
53
|
"@lingui/core": "^5",
|
|
54
54
|
"@lingui/macro": "^5",
|
|
55
55
|
"@lingui/react": "^5",
|