@graphcommerce/next-ui 8.1.0-canary.8 → 9.0.0-canary.100
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/ActionCard/ActionCard.tsx +66 -39
- package/ActionCard/ActionCardAccordion.tsx +2 -1
- package/ActionCard/ActionCardLayout.tsx +1 -1
- package/ActionCard/ActionCardList.tsx +11 -10
- package/ActionCard/index.ts +0 -1
- package/Blog/BlogAuthor/BlogAuthor.tsx +2 -4
- package/Blog/BlogListItem/BlogListItem.tsx +2 -4
- package/Blog/BlogTags/BlogTag.tsx +4 -1
- package/Breadcrumbs/Breadcrumbs.tsx +195 -0
- package/Breadcrumbs/BreadcrumbsJsonLd.tsx +13 -0
- package/Breadcrumbs/BreadcrumbsList.tsx +101 -0
- package/Breadcrumbs/BreadcrumbsPopper.tsx +48 -0
- package/Breadcrumbs/index.ts +4 -0
- package/Breadcrumbs/jsonLdBreadcrumb.tsx +19 -0
- package/Breadcrumbs/types.ts +11 -0
- package/CHANGELOG.md +341 -98
- package/Config.graphqls +5 -0
- package/Document/DocumentBodyEnd.tsx +7 -0
- package/Document/DocumentBodyStart.tsx +7 -0
- package/Document/DocumentHeadEnd.tsx +7 -0
- package/Document/DocumentHeadStart.tsx +7 -0
- package/Document/index.ts +4 -0
- package/Footer/Footer.tsx +1 -1
- package/FramerScroller/SidebarGallery.tsx +54 -27
- package/FullPageMessage/FullPageMessage.tsx +1 -0
- package/Intl/DateTimeFormat/DateFormat.tsx +10 -0
- package/Intl/DateTimeFormat/DateTimeFormat.tsx +20 -0
- package/Intl/DateTimeFormat/TimeFormat.tsx +10 -0
- package/Intl/DateTimeFormat/index.ts +3 -0
- package/Intl/DateTimeFormat/toDate.ts +16 -0
- package/Intl/DisplayNames/DisplayNames.tsx +22 -0
- package/Intl/DisplayNames/index.ts +1 -0
- package/Intl/ListFormat.tsx +33 -0
- package/Intl/NumberFormat/CurrencyFormat.tsx +191 -0
- package/Intl/NumberFormat/NumberFormat.tsx +24 -0
- package/Intl/NumberFormat/PercentFormat.tsx +5 -0
- package/Intl/NumberFormat/UnitFormat.tsx +59 -0
- package/Intl/NumberFormat/index.ts +4 -0
- package/Intl/RelativeTimeFormat/RelativeTimeFormat.tsx +34 -0
- package/Intl/RelativeTimeFormat/RelativeToTimeFormat.tsx +35 -0
- package/Intl/RelativeTimeFormat/index.ts +2 -0
- package/Intl/RelativeTimeFormat/relativeTimeFormatAutoUnit.ts +23 -0
- package/Intl/index.ts +11 -0
- package/JsonLd/JsonLd.tsx +3 -2
- package/Layout/components/LayoutHeader.tsx +30 -10
- package/Layout/components/LayoutHeaderBack.tsx +20 -9
- package/Layout/components/LayoutHeaderClose.tsx +8 -2
- package/Layout/components/LayoutTitle.tsx +0 -5
- package/LayoutDefault/components/LayoutDefault.tsx +15 -13
- package/LazyHydrate/LazyHydrate.tsx +19 -3
- package/Overlay/components/OverlayBase.tsx +31 -6
- package/Overlay/components/OverlayHeader.tsx +27 -0
- package/Overlay/components/index.ts +1 -0
- package/PageLoadIndicator/PageLoadIndicator.tsx +25 -14
- package/PageMeta/PageMeta.tsx +1 -71
- package/PageMeta/canonicalize.ts +78 -0
- package/Pagination/PaginationExtended.tsx +103 -0
- package/Snackbar/MessageSnackbarImpl.tsx +60 -19
- package/Styles/responsiveVal.tsx +4 -5
- package/Styles/withEmotionCache.tsx +2 -2
- package/Theme/DarkLightModeThemeProvider.tsx +41 -19
- package/Theme/MuiButton.ts +11 -3
- package/TimeAgo/TimeAgo.tsx +3 -0
- package/hooks/index.ts +2 -1
- package/hooks/memoDeep.ts +38 -0
- package/hooks/useDateTimeFormat.ts +4 -4
- package/hooks/{useSsr.ts → useIsSsr.ts} +3 -0
- package/hooks/useLocale.ts +2 -2
- package/hooks/useNumberFormat.ts +4 -3
- package/hooks/useStorefrontConfig.ts +1 -9
- package/hooks/useUrlQuery.ts +6 -4
- package/icons.ts +55 -0
- package/index.ts +11 -2
- package/package.json +14 -14
- package/server.ts +9 -0
- package/utils/cssFlags.tsx +53 -0
- package/utils/getCssFlagInitScript.tsx +20 -0
- package/utils/normalizeLocale.ts +26 -0
- package/utils/robots.ts +41 -0
- package/utils/sitemap.ts +47 -0
- package/utils/storefrontConfig.ts +8 -0
- package/ActionCard/ActionCardListForm.tsx +0 -88
- package/icons/index.ts +0 -48
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { forwardRef, useMemo } from 'react'
|
|
2
|
+
import { useLocale } from '../../hooks/useLocale'
|
|
3
|
+
import { useMemoObject } from '../../hooks/useMemoObject'
|
|
4
|
+
import { relativeTimeFormatUnitAuto } from './relativeTimeFormatAutoUnit'
|
|
5
|
+
|
|
6
|
+
export function useRelativeTimeFormatter(props: Intl.RelativeTimeFormatOptions) {
|
|
7
|
+
const locale = useLocale()
|
|
8
|
+
const memoOptions = useMemoObject(props)
|
|
9
|
+
return useMemo(() => new Intl.RelativeTimeFormat(locale, memoOptions), [locale, memoOptions])
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type RelativeTimeFormatProps = {
|
|
13
|
+
children: number
|
|
14
|
+
unit?: Intl.RelativeTimeFormatUnit
|
|
15
|
+
styleFormat?: Intl.RelativeTimeFormatStyle
|
|
16
|
+
} & Omit<Intl.RelativeTimeFormatOptions, 'style'>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Alternative: {@link file://./RelativeToTimeFormat.tsx}
|
|
20
|
+
*/
|
|
21
|
+
export const RelativeTimeFormat = forwardRef<HTMLSpanElement, RelativeTimeFormatProps>(
|
|
22
|
+
(props, ref) => {
|
|
23
|
+
const { children, unit, styleFormat, localeMatcher, numeric, ...rest } = props
|
|
24
|
+
const formatter = useRelativeTimeFormatter({ localeMatcher, numeric, style: styleFormat })
|
|
25
|
+
|
|
26
|
+
const [value, autoUnit] = relativeTimeFormatUnitAuto({ value: children, unit })
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<span suppressHydrationWarning ref={ref} {...rest}>
|
|
30
|
+
{children ? formatter.format(value, autoUnit) : null}
|
|
31
|
+
</span>
|
|
32
|
+
)
|
|
33
|
+
},
|
|
34
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { forwardRef, useMemo } from 'react'
|
|
2
|
+
import { DateValue, toDate } from '../DateTimeFormat/toDate'
|
|
3
|
+
import { RelativeTimeFormat, RelativeTimeFormatProps } from './RelativeTimeFormat'
|
|
4
|
+
|
|
5
|
+
type RelativeToTimeFormatProps = Omit<RelativeTimeFormatProps, 'children'> & {
|
|
6
|
+
/**
|
|
7
|
+
* Date to format a relative value for.
|
|
8
|
+
*/
|
|
9
|
+
children: DateValue
|
|
10
|
+
/**
|
|
11
|
+
* If provided, the component will format a relative value to this date.
|
|
12
|
+
* Else, it will format a relative value to the current date.
|
|
13
|
+
*/
|
|
14
|
+
to?: DateValue
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const RelativeToTimeFormat = forwardRef<HTMLSpanElement, RelativeToTimeFormatProps>(
|
|
18
|
+
(props, ref) => {
|
|
19
|
+
const { children, to, ...rest } = props
|
|
20
|
+
|
|
21
|
+
const relativeTo = useMemo(() => {
|
|
22
|
+
const date = toDate(children)
|
|
23
|
+
if (!date) return 0
|
|
24
|
+
const toDateValue = (to && toDate(to)) || new Date()
|
|
25
|
+
|
|
26
|
+
return Math.round((date.getTime() - toDateValue.getTime()) / 1000)
|
|
27
|
+
}, [children, to])
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<RelativeTimeFormat {...rest} ref={ref}>
|
|
31
|
+
{relativeTo}
|
|
32
|
+
</RelativeTimeFormat>
|
|
33
|
+
)
|
|
34
|
+
},
|
|
35
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
type UseRelativeTimeFormatUnitAutoProps = {
|
|
2
|
+
value: number
|
|
3
|
+
unit?: Intl.RelativeTimeFormatUnit
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function relativeTimeFormatUnitAuto(
|
|
7
|
+
props: UseRelativeTimeFormatUnitAutoProps,
|
|
8
|
+
): [number, Intl.RelativeTimeFormatUnit] {
|
|
9
|
+
const { value, unit } = props
|
|
10
|
+
|
|
11
|
+
if (unit) return [value, unit]
|
|
12
|
+
|
|
13
|
+
// Calculate the absolute value once
|
|
14
|
+
const absValue = Math.abs(value)
|
|
15
|
+
|
|
16
|
+
if (absValue >= 60 * 60 * 24 * 365) return [Math.round(value / (60 * 60 * 24 * 365)), 'year']
|
|
17
|
+
if (absValue >= 60 * 60 * 24 * 30) return [Math.round(value / (60 * 60 * 24 * 30)), 'month']
|
|
18
|
+
if (absValue >= 60 * 60 * 24 * 7) return [Math.round(value / (60 * 60 * 24 * 7)), 'week']
|
|
19
|
+
if (absValue >= 60 * 60 * 24) return [Math.round(value / (60 * 60 * 24)), 'day']
|
|
20
|
+
if (absValue >= 60 * 60) return [Math.round(value / (60 * 60)), 'hour']
|
|
21
|
+
if (absValue >= 60) return [Math.round(value / 60), 'minute']
|
|
22
|
+
return [Math.round(value), 'second']
|
|
23
|
+
}
|
package/Intl/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator
|
|
2
|
+
// export * from './Collator' // Intl.Collator
|
|
3
|
+
|
|
4
|
+
export * from './DisplayNames' // Intl.DisplayNames
|
|
5
|
+
export * from './DateTimeFormat' // Intl.DateTimeFormat
|
|
6
|
+
export * from './ListFormat' // Intl.ListFormat
|
|
7
|
+
// export * from './Locale' // Intl.Locale
|
|
8
|
+
export * from './NumberFormat' // Intl.NumberFormat
|
|
9
|
+
// export * from './PluralRules' // Intl.PluralRules
|
|
10
|
+
export * from './RelativeTimeFormat' // Intl.RelativeTimeFormat
|
|
11
|
+
// export * from './Segmenter' // Intl.Segmenter
|
package/JsonLd/JsonLd.tsx
CHANGED
|
@@ -2,14 +2,15 @@ import Head from 'next/head'
|
|
|
2
2
|
import { safeJsonLdReplacer } from './safeJsonLdReplacer'
|
|
3
3
|
|
|
4
4
|
export function JsonLd<T extends { '@type': string }>(props: {
|
|
5
|
+
keyVal?: string
|
|
5
6
|
item: T & { '@context': 'https://schema.org' }
|
|
6
7
|
}) {
|
|
7
|
-
const { item } = props
|
|
8
|
+
const { item, keyVal } = props
|
|
8
9
|
|
|
9
10
|
return (
|
|
10
11
|
<Head>
|
|
11
12
|
<script
|
|
12
|
-
key='jsonld'
|
|
13
|
+
key={keyVal ?? 'jsonld'}
|
|
13
14
|
type='application/ld+json'
|
|
14
15
|
// eslint-disable-next-line react/no-danger
|
|
15
16
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(item, safeJsonLdReplacer) }}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { Box, SxProps, Theme } from '@mui/material'
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { extendableComponent } from '../../Styles'
|
|
4
|
-
import { LayoutHeaderBack, useShowBack } from './LayoutHeaderBack'
|
|
4
|
+
import { BackProps, LayoutHeaderBack, useShowBack } from './LayoutHeaderBack'
|
|
5
5
|
import { LayoutHeaderClose, useShowClose } from './LayoutHeaderClose'
|
|
6
6
|
import { LayoutHeaderContent, LayoutHeaderContentProps } from './LayoutHeaderContent'
|
|
7
7
|
import { FloatingProps } from './LayoutHeadertypes'
|
|
8
8
|
|
|
9
9
|
export type LayoutHeaderProps = FloatingProps &
|
|
10
|
-
Omit<LayoutHeaderContentProps, 'left' | 'right'> &
|
|
10
|
+
Omit<LayoutHeaderContentProps, 'left' | 'right'> &
|
|
11
|
+
Pick<BackProps, 'disableBackNavigation'> & {
|
|
11
12
|
/**
|
|
12
13
|
* Button to display on the left side of the title
|
|
13
14
|
*
|
|
@@ -28,6 +29,9 @@ export type LayoutHeaderProps = FloatingProps &
|
|
|
28
29
|
sx?: SxProps<Theme>
|
|
29
30
|
|
|
30
31
|
hideBackButton?: boolean
|
|
32
|
+
|
|
33
|
+
hideSm?: boolean | null
|
|
34
|
+
hideMd?: boolean | null
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
type ComponentStyleProps = {
|
|
@@ -36,15 +40,16 @@ type ComponentStyleProps = {
|
|
|
36
40
|
children: boolean
|
|
37
41
|
floatingSm: boolean
|
|
38
42
|
floatingMd: boolean
|
|
43
|
+
hideSm: boolean
|
|
44
|
+
hideMd: boolean
|
|
39
45
|
size: 'small' | 'responsive'
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
const {
|
|
43
|
-
'
|
|
44
|
-
|
|
45
|
-
)
|
|
48
|
+
const { withState } = extendableComponent<ComponentStyleProps, 'LayoutHeader'>('LayoutHeader', [
|
|
49
|
+
'root',
|
|
50
|
+
] as const)
|
|
46
51
|
|
|
47
|
-
export
|
|
52
|
+
export const LayoutHeader = React.memo<LayoutHeaderProps>((props) => {
|
|
48
53
|
const {
|
|
49
54
|
children,
|
|
50
55
|
divider,
|
|
@@ -56,6 +61,9 @@ export function LayoutHeader(props: LayoutHeaderProps) {
|
|
|
56
61
|
size = 'responsive',
|
|
57
62
|
sx = [],
|
|
58
63
|
bgColor,
|
|
64
|
+
hideSm = false,
|
|
65
|
+
hideMd = false,
|
|
66
|
+
disableBackNavigation,
|
|
59
67
|
} = props
|
|
60
68
|
const showBack = useShowBack() && !hideBackButton
|
|
61
69
|
const showClose = useShowClose()
|
|
@@ -69,7 +77,12 @@ export function LayoutHeader(props: LayoutHeaderProps) {
|
|
|
69
77
|
if (divider || primary || secondary) floatingSm = false
|
|
70
78
|
|
|
71
79
|
const close = showClose && <LayoutHeaderClose />
|
|
72
|
-
const back = showBack &&
|
|
80
|
+
const back = showBack && (
|
|
81
|
+
<LayoutHeaderBack
|
|
82
|
+
breakpoint={floatingSm ? 'xs' : undefined}
|
|
83
|
+
disableBackNavigation={disableBackNavigation}
|
|
84
|
+
/>
|
|
85
|
+
)
|
|
73
86
|
|
|
74
87
|
let left = secondary
|
|
75
88
|
let right = primary
|
|
@@ -88,6 +101,8 @@ export function LayoutHeader(props: LayoutHeaderProps) {
|
|
|
88
101
|
children: !!children,
|
|
89
102
|
divider: !!divider,
|
|
90
103
|
size,
|
|
104
|
+
hideSm: !!hideSm,
|
|
105
|
+
hideMd: !!hideMd,
|
|
91
106
|
})
|
|
92
107
|
|
|
93
108
|
return (
|
|
@@ -115,6 +130,9 @@ export function LayoutHeader(props: LayoutHeaderProps) {
|
|
|
115
130
|
'&.divider': {
|
|
116
131
|
marginBottom: 0,
|
|
117
132
|
},
|
|
133
|
+
'&.hideSm .LayoutHeaderContent-left': {
|
|
134
|
+
display: 'none',
|
|
135
|
+
},
|
|
118
136
|
},
|
|
119
137
|
|
|
120
138
|
[theme.breakpoints.up('md')]: {
|
|
@@ -140,6 +158,9 @@ export function LayoutHeader(props: LayoutHeaderProps) {
|
|
|
140
158
|
'&.divider': {
|
|
141
159
|
marginBottom: 0,
|
|
142
160
|
},
|
|
161
|
+
'&.hideMd .LayoutHeaderContent-left': {
|
|
162
|
+
display: 'none',
|
|
163
|
+
},
|
|
143
164
|
},
|
|
144
165
|
}),
|
|
145
166
|
...(Array.isArray(sx) ? sx : [sx]),
|
|
@@ -159,5 +180,4 @@ export function LayoutHeader(props: LayoutHeaderProps) {
|
|
|
159
180
|
</LayoutHeaderContent>
|
|
160
181
|
</Box>
|
|
161
182
|
)
|
|
162
|
-
}
|
|
163
|
-
LayoutHeader.selectors = selectors
|
|
183
|
+
})
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useUp,
|
|
3
|
+
usePrevUp,
|
|
4
|
+
usePageContext,
|
|
5
|
+
usePrevPageRouter,
|
|
6
|
+
} from '@graphcommerce/framer-next-pages'
|
|
2
7
|
import { i18n } from '@lingui/core'
|
|
3
8
|
import { Box, SxProps, Theme } from '@mui/material'
|
|
4
9
|
import { useRouter } from 'next/router'
|
|
@@ -7,7 +12,12 @@ import { IconSvg } from '../../IconSvg'
|
|
|
7
12
|
import { responsiveVal } from '../../Styles'
|
|
8
13
|
import { iconChevronLeft } from '../../icons'
|
|
9
14
|
|
|
10
|
-
export type BackProps = Omit<LinkOrButtonProps, 'onClick' | 'children'>
|
|
15
|
+
export type BackProps = Omit<LinkOrButtonProps, 'onClick' | 'children'> & {
|
|
16
|
+
/**
|
|
17
|
+
* Will not use `router.back()` if available, and will always use the `up.href`
|
|
18
|
+
*/
|
|
19
|
+
disableBackNavigation?: boolean
|
|
20
|
+
}
|
|
11
21
|
|
|
12
22
|
export function useShowBack() {
|
|
13
23
|
const path = useRouter().asPath.split('?')[0]
|
|
@@ -16,10 +26,7 @@ export function useShowBack() {
|
|
|
16
26
|
const { backSteps } = usePageContext()
|
|
17
27
|
|
|
18
28
|
const canClickBack = backSteps > 0 && path !== prevUp?.href
|
|
19
|
-
|
|
20
|
-
if (canClickBack) return true
|
|
21
|
-
if (up?.href && up.href !== path) return true
|
|
22
|
-
return false
|
|
29
|
+
return canClickBack || (up?.href && up.href !== path)
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
const buttonSx: SxProps<Theme> = (theme) => ({
|
|
@@ -36,19 +43,23 @@ const buttonSx: SxProps<Theme> = (theme) => ({
|
|
|
36
43
|
})
|
|
37
44
|
|
|
38
45
|
export function LayoutHeaderBack(props: BackProps) {
|
|
46
|
+
const { disableBackNavigation = false, ...rest } = props
|
|
39
47
|
const router = useRouter()
|
|
40
48
|
const path = router.asPath.split('?')[0]
|
|
41
49
|
const up = useUp()
|
|
42
50
|
const prevUp = usePrevUp()
|
|
43
51
|
const { backSteps } = usePageContext()
|
|
52
|
+
const prevPageRouter = usePrevPageRouter()
|
|
44
53
|
|
|
45
54
|
const backIcon = <IconSvg src={iconChevronLeft} size='medium' />
|
|
46
|
-
const canClickBack = backSteps > 0 && path !== prevUp?.href
|
|
55
|
+
const canClickBack = backSteps > 0 && path !== prevUp?.href && !disableBackNavigation
|
|
47
56
|
|
|
48
57
|
let label = i18n._(/* i18n */ 'Back')
|
|
49
58
|
if (up?.href === path && up?.title) label = up.title
|
|
50
59
|
if (prevUp?.href === path && prevUp?.title) label = prevUp.title
|
|
51
60
|
|
|
61
|
+
if (up && prevPageRouter?.asPath === up?.href) label = up.title
|
|
62
|
+
|
|
52
63
|
if (canClickBack) {
|
|
53
64
|
return (
|
|
54
65
|
<LinkOrButton
|
|
@@ -57,7 +68,7 @@ export function LayoutHeaderBack(props: BackProps) {
|
|
|
57
68
|
color='inherit'
|
|
58
69
|
startIcon={backIcon}
|
|
59
70
|
aria-label={label}
|
|
60
|
-
{...
|
|
71
|
+
{...rest}
|
|
61
72
|
>
|
|
62
73
|
<Box component='span' sx={{ display: { xs: 'none', md: 'inline' } }}>
|
|
63
74
|
{label}
|
|
@@ -74,7 +85,7 @@ export function LayoutHeaderBack(props: BackProps) {
|
|
|
74
85
|
startIcon={backIcon}
|
|
75
86
|
aria-label={up.title}
|
|
76
87
|
color='inherit'
|
|
77
|
-
{...
|
|
88
|
+
{...rest}
|
|
78
89
|
>
|
|
79
90
|
<Box component='span' sx={{ display: { xs: 'none', md: 'inline' } }}>
|
|
80
91
|
{up.title}
|
|
@@ -6,18 +6,24 @@ import { IconSvg, useIconSvgSize } from '../../IconSvg'
|
|
|
6
6
|
import { useFabSize } from '../../Theme'
|
|
7
7
|
import { iconClose } from '../../icons'
|
|
8
8
|
|
|
9
|
+
type LayoutHeaderCloseProps = {
|
|
10
|
+
onClose?: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
export function useShowClose() {
|
|
10
14
|
const { overlayGroup } = usePageContext()
|
|
11
15
|
return !!overlayGroup
|
|
12
16
|
}
|
|
13
17
|
|
|
14
|
-
export function LayoutHeaderClose() {
|
|
18
|
+
export function LayoutHeaderClose(props: LayoutHeaderCloseProps) {
|
|
19
|
+
const { onClose } = props
|
|
15
20
|
const { closeSteps } = usePageContext()
|
|
16
21
|
const [disabled, setDisabled] = useState(false)
|
|
17
22
|
const go = useGo(closeSteps * -1)
|
|
18
23
|
const onClick = () => {
|
|
19
24
|
setDisabled(true)
|
|
20
|
-
|
|
25
|
+
|
|
26
|
+
return onClose ? onClose() : go()
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
const fabSize = useFabSize('responsive')
|
|
@@ -40,12 +40,7 @@ export const LayoutTitle = React.forwardRef<HTMLDivElement, TitleProps>((props,
|
|
|
40
40
|
alignItems: 'center',
|
|
41
41
|
justifyContent: 'center',
|
|
42
42
|
gap: `6px`,
|
|
43
|
-
flexFlow: 'unset',
|
|
44
|
-
[theme.breakpoints.up('md')]: {
|
|
45
|
-
flexFlow: 'column',
|
|
46
|
-
},
|
|
47
43
|
'&.sizeSmall': {
|
|
48
|
-
flexFlow: 'unset',
|
|
49
44
|
overflow: 'hidden',
|
|
50
45
|
'& svg': {
|
|
51
46
|
width: responsiveVal(24, 28),
|
|
@@ -129,19 +129,21 @@ export function LayoutDefault(props: LayoutDefaultProps) {
|
|
|
129
129
|
})}
|
|
130
130
|
>
|
|
131
131
|
{menuFab}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
132
|
+
{cartFab && (
|
|
133
|
+
<Box
|
|
134
|
+
sx={(theme) => ({
|
|
135
|
+
display: 'flex',
|
|
136
|
+
flexDirection: 'row-reverse',
|
|
137
|
+
gap: theme.spacings.sm,
|
|
138
|
+
[theme.breakpoints.up('md')]: {
|
|
139
|
+
flexDirection: 'column',
|
|
140
|
+
alignItems: 'flex-end',
|
|
141
|
+
},
|
|
142
|
+
})}
|
|
143
|
+
>
|
|
144
|
+
{cartFab}
|
|
145
|
+
</Box>
|
|
146
|
+
)}
|
|
145
147
|
</Box>
|
|
146
148
|
) : (
|
|
147
149
|
<div />
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { Box, BoxProps } from '@mui/material'
|
|
2
|
-
import React, {
|
|
2
|
+
import React, {
|
|
3
|
+
useState,
|
|
4
|
+
useRef,
|
|
5
|
+
startTransition,
|
|
6
|
+
useLayoutEffect,
|
|
7
|
+
useEffect,
|
|
8
|
+
CSSProperties,
|
|
9
|
+
} from 'react'
|
|
3
10
|
|
|
4
11
|
// Make sure the server doesn't choke on the useLayoutEffect
|
|
5
12
|
export const useLayoutEffect2 = typeof window !== 'undefined' ? useLayoutEffect : useEffect
|
|
@@ -18,6 +25,14 @@ export type LazyHydrateProps = BoxProps<'div'> & {
|
|
|
18
25
|
* - Hydrate the component on some state `<LazyHydrate hydrated={someState}>` where someState initially is false and later becomes true.
|
|
19
26
|
*/
|
|
20
27
|
hydrated?: boolean
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* By default LazyHydrate does not defer the rendering of components when they are rendered client
|
|
31
|
+
* side, because using an IntersectionObserver on an element with no height, will cause all siblings to render at once.
|
|
32
|
+
*
|
|
33
|
+
* By proving a height, we can use the IntersectionObserver on the client as well.
|
|
34
|
+
*/
|
|
35
|
+
height?: CSSProperties['height']
|
|
21
36
|
}
|
|
22
37
|
|
|
23
38
|
/**
|
|
@@ -26,7 +41,7 @@ export type LazyHydrateProps = BoxProps<'div'> & {
|
|
|
26
41
|
* This can be a way to improve the TBT of a page.
|
|
27
42
|
*/
|
|
28
43
|
export function LazyHydrate(props: LazyHydrateProps) {
|
|
29
|
-
const { hydrated, children, ...elementProps } = props
|
|
44
|
+
const { hydrated, children, height, ...elementProps } = props
|
|
30
45
|
const rootRef = useRef<HTMLDivElement>(null)
|
|
31
46
|
|
|
32
47
|
const [isHydrated, setIsHydrated] = useState(hydrated || false)
|
|
@@ -37,7 +52,7 @@ export function LazyHydrate(props: LazyHydrateProps) {
|
|
|
37
52
|
if (isHydrated || !rootRef.current) return undefined
|
|
38
53
|
|
|
39
54
|
// If the element wasn't rendered on the server, we hydrate it immediately
|
|
40
|
-
if (!rootRef.current?.hasAttribute('data-lazy-hydrate')) {
|
|
55
|
+
if (!height && !rootRef.current?.hasAttribute('data-lazy-hydrate')) {
|
|
41
56
|
setIsHydrated(true)
|
|
42
57
|
return undefined
|
|
43
58
|
}
|
|
@@ -77,6 +92,7 @@ export function LazyHydrate(props: LazyHydrateProps) {
|
|
|
77
92
|
dangerouslySetInnerHTML={{ __html: '' }}
|
|
78
93
|
suppressHydrationWarning
|
|
79
94
|
{...elementProps}
|
|
95
|
+
style={{ ...elementProps.style, height }}
|
|
80
96
|
/>
|
|
81
97
|
)
|
|
82
98
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Direction } from '@graphcommerce/framer-next-pages'
|
|
1
2
|
import { Scroller, useScrollerContext, useScrollTo } from '@graphcommerce/framer-scroller'
|
|
2
3
|
import {
|
|
3
4
|
dvh,
|
|
@@ -46,7 +47,7 @@ export type LayoutOverlayBaseProps = {
|
|
|
46
47
|
sx?: SxProps<Theme>
|
|
47
48
|
sxBackdrop?: SxProps<Theme>
|
|
48
49
|
active: boolean
|
|
49
|
-
direction?:
|
|
50
|
+
direction?: Direction
|
|
50
51
|
onClosed: () => void
|
|
51
52
|
offsetPageY?: number
|
|
52
53
|
isPresent: boolean
|
|
@@ -123,7 +124,8 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
|
|
|
123
124
|
props.smSpacingTop ?? ((theme) => `calc(${theme.appShell.headerHeightSm} * 0.5)`)
|
|
124
125
|
)(th)
|
|
125
126
|
|
|
126
|
-
const { scrollerRef, snap, scroll, getScrollSnapPositions, disableSnap } =
|
|
127
|
+
const { scrollerRef, snap, scroll, getScrollSnapPositions, disableSnap, enableSnap } =
|
|
128
|
+
useScrollerContext()
|
|
127
129
|
const scrollTo = useScrollTo()
|
|
128
130
|
|
|
129
131
|
const beforeRef = useRef<HTMLDivElement>(null)
|
|
@@ -137,7 +139,7 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
|
|
|
137
139
|
|
|
138
140
|
const match = useMatchMedia()
|
|
139
141
|
const positions = useConstant(() => ({
|
|
140
|
-
open: { x: motionValue(0), y: motionValue(0), visible: motionValue(0) },
|
|
142
|
+
open: { x: motionValue(0), y: motionValue(0), visible: motionValue(direction === 0 ? 1 : 0) },
|
|
141
143
|
closed: { x: motionValue(0), y: motionValue(0) },
|
|
142
144
|
}))
|
|
143
145
|
|
|
@@ -277,17 +279,40 @@ export function OverlayBase(incomingProps: LayoutOverlayBaseProps) {
|
|
|
277
279
|
useIsomorphicLayoutEffect(() => {
|
|
278
280
|
const scroller = scrollerRef.current
|
|
279
281
|
|
|
280
|
-
if (!scroller || !isPresent) return
|
|
282
|
+
if (!scroller || !isPresent || position.get() === OverlayPosition.OPENED) return
|
|
281
283
|
|
|
282
284
|
if (variant() === 'right') document.body.style.overflow = 'hidden'
|
|
283
285
|
|
|
284
|
-
if (
|
|
286
|
+
if (direction === 0) {
|
|
287
|
+
disableSnap()
|
|
288
|
+
scroller.scrollTop = positions.open.y.get()
|
|
289
|
+
scroller.scrollLeft = positions.open.x.get()
|
|
290
|
+
scroll.y.set(positions.open.y.get())
|
|
291
|
+
scroll.x.set(positions.open.x.get())
|
|
292
|
+
position.set(OverlayPosition.OPENED)
|
|
293
|
+
enableSnap()
|
|
294
|
+
} else if (!scroll.animating.get()) {
|
|
285
295
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
286
296
|
scrollTo(openClosePositions().open, { stopAnimationOnScroll: false }).then(() =>
|
|
287
297
|
position.set(OverlayPosition.OPENED),
|
|
288
298
|
)
|
|
289
299
|
}
|
|
290
|
-
}, [
|
|
300
|
+
}, [
|
|
301
|
+
direction,
|
|
302
|
+
disableSnap,
|
|
303
|
+
enableSnap,
|
|
304
|
+
isPresent,
|
|
305
|
+
openClosePositions,
|
|
306
|
+
position,
|
|
307
|
+
positions.open.x,
|
|
308
|
+
positions.open.y,
|
|
309
|
+
scroll.animating,
|
|
310
|
+
scroll.x,
|
|
311
|
+
scroll.y,
|
|
312
|
+
scrollTo,
|
|
313
|
+
scrollerRef,
|
|
314
|
+
variant,
|
|
315
|
+
])
|
|
291
316
|
|
|
292
317
|
// When the overlay is closed by navigating away, we're closing the overlay.
|
|
293
318
|
useEffect(() => {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { LayoutHeaderProps, LayoutTitle, TitleProps } from '../../Layout'
|
|
2
|
+
import { LayoutHeaderClose } from '../../Layout/components/LayoutHeaderClose'
|
|
3
|
+
import { LayoutOverlayHeader } from '../../LayoutOverlay/components/LayoutOverlayHeader'
|
|
4
|
+
|
|
5
|
+
type OverlayHeaderProps = Omit<LayoutHeaderProps, 'hideBackButton' | 'switchPoint'> &
|
|
6
|
+
Pick<TitleProps, 'icon'> & { onClose: () => void }
|
|
7
|
+
|
|
8
|
+
export const OverlayHeader = (props: OverlayHeaderProps) => {
|
|
9
|
+
const { children, onClose, sx = [], icon, primary, secondary, ...rest } = props
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<LayoutOverlayHeader
|
|
13
|
+
noAlign
|
|
14
|
+
sx={[{ '&.noAlign': { mb: 0 } }, ...(Array.isArray(sx) ? sx : [sx])]}
|
|
15
|
+
switchPoint={-10000}
|
|
16
|
+
size='small'
|
|
17
|
+
hideBackButton
|
|
18
|
+
primary={primary ?? <LayoutHeaderClose onClose={onClose} />}
|
|
19
|
+
secondary={primary ? <LayoutHeaderClose onClose={onClose} /> : secondary}
|
|
20
|
+
{...rest}
|
|
21
|
+
>
|
|
22
|
+
<LayoutTitle size='small' component='span' icon={icon}>
|
|
23
|
+
{children}
|
|
24
|
+
</LayoutTitle>
|
|
25
|
+
</LayoutOverlayHeader>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import { LinearProgress,
|
|
1
|
+
import { LinearProgress, LinearProgressProps } from '@mui/material'
|
|
2
|
+
import { m, motionValue, useTransform } from 'framer-motion'
|
|
2
3
|
import { useRouter } from 'next/router'
|
|
3
|
-
import { useEffect, useState } from 'react'
|
|
4
|
+
import { forwardRef, useEffect, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
export const showPageLoadIndicator = motionValue(false)
|
|
7
|
+
|
|
8
|
+
const MLinearProgress = m(
|
|
9
|
+
forwardRef((props: Omit<LinearProgressProps, 'style'>, ref: LinearProgressProps['ref']) => (
|
|
10
|
+
<LinearProgress ref={ref} {...props} />
|
|
11
|
+
)),
|
|
12
|
+
)
|
|
4
13
|
|
|
5
14
|
/**
|
|
6
15
|
* Creates a [LinearProgress](https://mui.com/components/progress/#linear) animation when the route
|
|
@@ -24,18 +33,20 @@ export function PageLoadIndicator() {
|
|
|
24
33
|
}
|
|
25
34
|
}, [events])
|
|
26
35
|
|
|
36
|
+
const opacity = useTransform(() => (showPageLoadIndicator.get() || loading ? 1 : 0))
|
|
37
|
+
|
|
27
38
|
return (
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
<MLinearProgress
|
|
40
|
+
style={{ opacity }}
|
|
41
|
+
sx={{
|
|
42
|
+
position: 'fixed',
|
|
43
|
+
width: '100%',
|
|
44
|
+
top: 0,
|
|
45
|
+
height: 3,
|
|
46
|
+
marginBottom: '-3px',
|
|
47
|
+
zIndex: 'tooltip',
|
|
48
|
+
transition: 'opacity 0.3s',
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
40
51
|
)
|
|
41
52
|
}
|