@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
|
@@ -13,7 +13,7 @@ import { extendableComponent, responsiveVal } from '../Styles'
|
|
|
13
13
|
import { breakpointVal } from '../Styles/breakpointVal'
|
|
14
14
|
|
|
15
15
|
type Variants = 'outlined' | 'default'
|
|
16
|
-
type Size = 'large' | 'medium' | 'small'
|
|
16
|
+
type Size = 'large' | 'medium' | 'small' | 'responsive'
|
|
17
17
|
type Color = 'primary' | 'secondary' | 'success' | 'error' | 'info' | 'warning'
|
|
18
18
|
type Layout = 'inline' | 'grid' | 'list' | 'stack'
|
|
19
19
|
|
|
@@ -37,9 +37,9 @@ export type ActionCardProps = {
|
|
|
37
37
|
price?: React.ReactNode
|
|
38
38
|
after?: React.ReactNode
|
|
39
39
|
secondaryAction?: React.ReactNode
|
|
40
|
-
onClick?: (event: React.MouseEvent<HTMLElement>, value: string | number | null) => void
|
|
40
|
+
onClick?: (event: React.MouseEvent<HTMLElement>, value: string | number | boolean | null) => void
|
|
41
41
|
selected?: boolean
|
|
42
|
-
value: string | number | null
|
|
42
|
+
value: string | number | boolean | null
|
|
43
43
|
reset?: React.ReactNode
|
|
44
44
|
disabled?: boolean
|
|
45
45
|
error?: boolean
|
|
@@ -47,6 +47,7 @@ export type ActionCardProps = {
|
|
|
47
47
|
|
|
48
48
|
const parts = [
|
|
49
49
|
'root',
|
|
50
|
+
'rootInner',
|
|
50
51
|
'image',
|
|
51
52
|
'title',
|
|
52
53
|
'action',
|
|
@@ -77,6 +78,13 @@ const { withState, selectors } = extendableComponent<StateProps, typeof name, ty
|
|
|
77
78
|
|
|
78
79
|
export const actionCardSelectors = selectors
|
|
79
80
|
|
|
81
|
+
export const actionCardImageSizes = {
|
|
82
|
+
small: responsiveVal(60, 80),
|
|
83
|
+
medium: responsiveVal(60, 80),
|
|
84
|
+
large: responsiveVal(100, 120),
|
|
85
|
+
responsive: responsiveVal(60, 120),
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
export function ActionCard(props: ActionCardProps) {
|
|
81
89
|
const {
|
|
82
90
|
title,
|
|
@@ -92,7 +100,7 @@ export function ActionCard(props: ActionCardProps) {
|
|
|
92
100
|
selected = false,
|
|
93
101
|
reset,
|
|
94
102
|
disabled = false,
|
|
95
|
-
size = '
|
|
103
|
+
size = 'responsive',
|
|
96
104
|
color = 'primary',
|
|
97
105
|
variant = 'outlined',
|
|
98
106
|
layout = 'list',
|
|
@@ -140,18 +148,24 @@ export function ActionCard(props: ActionCardProps) {
|
|
|
140
148
|
py: responsiveVal(12, 14),
|
|
141
149
|
display: 'block',
|
|
142
150
|
},
|
|
151
|
+
'&.sizeResponsive': {
|
|
152
|
+
px: responsiveVal(8, 16),
|
|
153
|
+
py: responsiveVal(4, 14),
|
|
154
|
+
display: { xs: 'flex', md: 'block', lg: 'block' },
|
|
155
|
+
[theme.breakpoints.down('md')]: { typography: 'body2' },
|
|
156
|
+
},
|
|
143
157
|
|
|
144
158
|
'&.variantDefault': {
|
|
145
159
|
position: 'relative',
|
|
146
|
-
'&::after': {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
},
|
|
160
|
+
// '&::after': {
|
|
161
|
+
// content: '""',
|
|
162
|
+
// position: 'absolute',
|
|
163
|
+
// width: '100%',
|
|
164
|
+
// left: 0,
|
|
165
|
+
// bottom: '-1px',
|
|
166
|
+
// borderBottom: `1px solid ${theme.palette.divider}`,
|
|
167
|
+
// display: 'block',
|
|
168
|
+
// },
|
|
155
169
|
'&.selected': {
|
|
156
170
|
backgroundColor:
|
|
157
171
|
theme.palette.mode === 'light'
|
|
@@ -165,34 +179,41 @@ export function ActionCard(props: ActionCardProps) {
|
|
|
165
179
|
)}`,
|
|
166
180
|
},
|
|
167
181
|
|
|
168
|
-
'&.sizeSmall': {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
},
|
|
175
|
-
'&.sizeMedium': {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
},
|
|
182
|
-
'&.sizeLarge': {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
},
|
|
182
|
+
// '&.sizeSmall': {
|
|
183
|
+
// mt: { xs: '2px', sm: '3px', md: '5px' },
|
|
184
|
+
// mb: { xs: '3px', sm: '4px', md: '6px' },
|
|
185
|
+
// '&::after': {
|
|
186
|
+
// mb: { xs: '-2px', sm: '-3px', md: '-5px' },
|
|
187
|
+
// },
|
|
188
|
+
// },
|
|
189
|
+
// '&.sizeMedium': {
|
|
190
|
+
// mt: { xs: '4px', sm: '5px', md: '6px' },
|
|
191
|
+
// mb: { xs: '5px', sm: '6px', md: '7px' },
|
|
192
|
+
// '&::after': {
|
|
193
|
+
// mb: { xs: '-4px', sm: '-5px', md: '-6px' },
|
|
194
|
+
// },
|
|
195
|
+
// },
|
|
196
|
+
// '&.sizeLarge': {
|
|
197
|
+
// mt: { xs: '5px', sm: '7px', md: '8px' },
|
|
198
|
+
// mb: { xs: '6px', sm: '8px', md: '9px' },
|
|
199
|
+
// '&::after': {
|
|
200
|
+
// mb: { xs: '-5px', sm: '-7px', md: '-8px' },
|
|
201
|
+
// },
|
|
202
|
+
// },
|
|
203
|
+
// '&.sizeResponsive': {
|
|
204
|
+
// mt: responsiveVal(2, 8),
|
|
205
|
+
// mb: responsiveVal(3, 9),
|
|
206
|
+
// '&::after': {
|
|
207
|
+
// mb: responsiveVal(-2, -8),
|
|
208
|
+
// },
|
|
209
|
+
// },
|
|
189
210
|
},
|
|
190
211
|
|
|
191
212
|
'&.variantOutlined': {
|
|
192
213
|
backgroundColor: theme.palette.background.paper,
|
|
193
214
|
boxShadow: `inset 0 0 0 1px ${theme.palette.divider}`,
|
|
194
215
|
'&:not(:last-of-type)': {
|
|
195
|
-
marginBottom: '-
|
|
216
|
+
marginBottom: '-2px',
|
|
196
217
|
},
|
|
197
218
|
'&.layoutList': {
|
|
198
219
|
borderRadius: 0,
|
|
@@ -230,7 +251,7 @@ export function ActionCard(props: ActionCardProps) {
|
|
|
230
251
|
borderColor: 'transparent',
|
|
231
252
|
boxShadow: `inset 0 0 0 2px ${theme.palette[color].main}`,
|
|
232
253
|
},
|
|
233
|
-
'&.selected:focus': {
|
|
254
|
+
'&.selected:focus, &.error:focus': {
|
|
234
255
|
borderColor: 'transparent',
|
|
235
256
|
boxShadow: `inset 0 0 0 2px ${theme.palette[color].main}, 0 0 0 4px ${alpha(
|
|
236
257
|
theme.palette[color].main,
|
|
@@ -245,12 +266,15 @@ export function ActionCard(props: ActionCardProps) {
|
|
|
245
266
|
},
|
|
246
267
|
|
|
247
268
|
'&.error': {
|
|
248
|
-
boxShadow: `0 0 0 2px ${theme.palette.error.main}`,
|
|
269
|
+
boxShadow: `inset 0 0 0 2px ${theme.palette.error.main}`,
|
|
249
270
|
},
|
|
250
271
|
},
|
|
251
272
|
'&.selected': {
|
|
252
273
|
zIndex: 1,
|
|
253
274
|
},
|
|
275
|
+
'&:focus, &.selected:focus, &.error:focus': {
|
|
276
|
+
zIndex: 2,
|
|
277
|
+
},
|
|
254
278
|
'&.disabled': {
|
|
255
279
|
background: theme.palette.action.disabledBackground,
|
|
256
280
|
opacity: theme.palette.action.disabledOpacity,
|
|
@@ -261,6 +285,7 @@ export function ActionCard(props: ActionCardProps) {
|
|
|
261
285
|
]}
|
|
262
286
|
>
|
|
263
287
|
<Box
|
|
288
|
+
className={classes.rootInner}
|
|
264
289
|
sx={{
|
|
265
290
|
display: 'flex',
|
|
266
291
|
flexDirection: 'row',
|
|
@@ -298,9 +323,10 @@ export function ActionCard(props: ActionCardProps) {
|
|
|
298
323
|
<Box
|
|
299
324
|
className={classes.title}
|
|
300
325
|
sx={{
|
|
301
|
-
'&.sizeSmall': { typography: '
|
|
326
|
+
'&.sizeSmall': { typography: 'body1' },
|
|
302
327
|
'&.sizeMedium': { typography: 'body1' },
|
|
303
328
|
'&.sizeLarge': { typography: 'h6' },
|
|
329
|
+
'&.sizeResponsive': { typography: { xs: 'body1', md: 'body1', lg: 'body1' } },
|
|
304
330
|
}}
|
|
305
331
|
>
|
|
306
332
|
{title}
|
|
@@ -341,8 +367,9 @@ export function ActionCard(props: ActionCardProps) {
|
|
|
341
367
|
sx={{
|
|
342
368
|
textAlign: 'right',
|
|
343
369
|
typography: 'body1',
|
|
344
|
-
'&.sizeMedium': {
|
|
370
|
+
'&.sizeMedium': { typography: 'subtitle1' },
|
|
345
371
|
'&.sizeLarge': { typography: 'h6' },
|
|
372
|
+
'&.sizeResponsive': { typography: { xs: 'body1', md: 'subtitle1', lg: 'h6' } },
|
|
346
373
|
}}
|
|
347
374
|
>
|
|
348
375
|
{price}
|
|
@@ -29,6 +29,7 @@ export function ActionCardAccordion(props: ActionCardAccordionProps) {
|
|
|
29
29
|
'&.Mui-expanded': { my: 0 },
|
|
30
30
|
'::before': { display: 'none' },
|
|
31
31
|
border: 'none',
|
|
32
|
+
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
32
33
|
'&:not(.Mui-expanded)': { borderBottom: `1px solid ${theme.palette.divider}` },
|
|
33
34
|
}),
|
|
34
35
|
...(Array.isArray(sx) ? sx : [sx]),
|
|
@@ -39,7 +40,7 @@ export function ActionCardAccordion(props: ActionCardAccordionProps) {
|
|
|
39
40
|
expandIcon={<IconSvg src={iconChevronDown} />}
|
|
40
41
|
sx={{
|
|
41
42
|
px: 0,
|
|
42
|
-
typography: '
|
|
43
|
+
typography: 'subtitle1',
|
|
43
44
|
minHeight: 54,
|
|
44
45
|
'& .MuiAccordionSummary-content': {
|
|
45
46
|
display: 'flex',
|
|
@@ -6,7 +6,7 @@ import { ActionCardProps } from './ActionCard'
|
|
|
6
6
|
export type ActionCardLayoutProps = {
|
|
7
7
|
children?: React.ReactNode
|
|
8
8
|
} & Pick<ActionCardProps, 'layout'> &
|
|
9
|
-
Pick<BoxProps, 'sx' | 'className'>
|
|
9
|
+
Pick<BoxProps, 'sx' | 'className' | 'tabIndex'>
|
|
10
10
|
|
|
11
11
|
const parts = ['root'] as const
|
|
12
12
|
const name = 'ActionCardLayout'
|
|
@@ -12,13 +12,13 @@ import { ActionCardLayout } from './ActionCardLayout'
|
|
|
12
12
|
type MultiSelect = {
|
|
13
13
|
multiple: true
|
|
14
14
|
collapse?: false
|
|
15
|
-
value: (string | number | null)[]
|
|
15
|
+
value: (string | boolean | number | null)[]
|
|
16
16
|
|
|
17
17
|
onChange?: (event: React.MouseEvent<HTMLElement>, value: MultiSelect['value']) => void
|
|
18
18
|
}
|
|
19
19
|
type Select = {
|
|
20
20
|
multiple?: boolean
|
|
21
|
-
value: string | number | null
|
|
21
|
+
value: string | boolean | number | null
|
|
22
22
|
collapse?: boolean
|
|
23
23
|
|
|
24
24
|
/** Value is null when deselected when not required */
|
|
@@ -44,6 +44,7 @@ function isValueSelected(
|
|
|
44
44
|
candidate?: Select['value'] | MultiSelect['value'],
|
|
45
45
|
) {
|
|
46
46
|
if (candidate === undefined) return false
|
|
47
|
+
if (typeof value === 'boolean') return value
|
|
47
48
|
if (Array.isArray(candidate)) return candidate.indexOf(value) >= 0
|
|
48
49
|
return value === candidate
|
|
49
50
|
}
|
|
@@ -57,6 +58,8 @@ const { withState } = extendableComponent<HoistedActionCardProps, typeof name, t
|
|
|
57
58
|
parts,
|
|
58
59
|
)
|
|
59
60
|
|
|
61
|
+
const isNotFrag = <T extends React.ReactElement>(el: T): el is T => !isFragment(el)
|
|
62
|
+
|
|
60
63
|
export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListProps>(
|
|
61
64
|
(props, ref) => {
|
|
62
65
|
const {
|
|
@@ -115,17 +118,15 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
|
|
|
115
118
|
.filter(React.isValidElement)
|
|
116
119
|
.filter(isActionCardLike)
|
|
117
120
|
.filter((child) => {
|
|
121
|
+
const notFrag = isNotFrag(child)
|
|
118
122
|
if (process.env.NODE_ENV !== 'production') {
|
|
119
|
-
if (
|
|
123
|
+
if (!notFrag)
|
|
120
124
|
console.error(
|
|
121
|
-
|
|
122
|
-
"@graphcommerce/next-ui: The ActionCardList component doesn't accept a Fragment as a child.",
|
|
123
|
-
'Consider providing an array instead',
|
|
124
|
-
].join('\n'),
|
|
125
|
+
"@graphcommerce/next-ui: The ActionCardList component doesn't accept a Fragment as a child. Consider providing an array instead",
|
|
125
126
|
)
|
|
126
127
|
}
|
|
127
128
|
|
|
128
|
-
return
|
|
129
|
+
return notFrag
|
|
129
130
|
})
|
|
130
131
|
|
|
131
132
|
// Make sure the selected values is in the list of all possible values
|
|
@@ -137,8 +138,8 @@ export const ActionCardList = React.forwardRef<HTMLDivElement, ActionCardListPro
|
|
|
137
138
|
const classes = withState({ size, color, variant, layout })
|
|
138
139
|
|
|
139
140
|
return (
|
|
140
|
-
<div
|
|
141
|
-
<ActionCardLayout sx={sx} className={classes.root} layout={layout}>
|
|
141
|
+
<div>
|
|
142
|
+
<ActionCardLayout sx={sx} className={classes.root} layout={layout} ref={ref} tabIndex={0}>
|
|
142
143
|
{childActionCards.map((child, index) => {
|
|
143
144
|
if (collapse && Boolean(value) && !isValueSelected(child.props.value, value))
|
|
144
145
|
return null
|
package/ActionCard/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Avatar, Box, Chip, SxProps, Theme } from '@mui/material'
|
|
2
2
|
import { responsiveVal } from '../../Styles/responsiveVal'
|
|
3
|
-
import {
|
|
3
|
+
import { DateFormat } from '../../Intl/DateTimeFormat'
|
|
4
4
|
|
|
5
5
|
export type BlogAuthorProps = {
|
|
6
6
|
author: string
|
|
@@ -11,8 +11,6 @@ export type BlogAuthorProps = {
|
|
|
11
11
|
export function BlogAuthor(props: BlogAuthorProps) {
|
|
12
12
|
const { author, date, sx = [] } = props
|
|
13
13
|
|
|
14
|
-
const formatter = useDateTimeFormat({ month: 'long', day: 'numeric' })
|
|
15
|
-
|
|
16
14
|
return (
|
|
17
15
|
<Box
|
|
18
16
|
maxWidth='md'
|
|
@@ -48,7 +46,7 @@ export function BlogAuthor(props: BlogAuthorProps) {
|
|
|
48
46
|
{author}
|
|
49
47
|
</Box>
|
|
50
48
|
<Box sx={(theme) => ({ lineHeight: 1.4, color: theme.palette.text.disabled })}>
|
|
51
|
-
{
|
|
49
|
+
<DateFormat dateStyle='long'>{date}</DateFormat>
|
|
52
50
|
</Box>
|
|
53
51
|
</section>
|
|
54
52
|
}
|
|
@@ -2,7 +2,7 @@ import { Box, Link, SxProps, Theme, Typography } from '@mui/material'
|
|
|
2
2
|
import React from 'react'
|
|
3
3
|
import { extendableComponent } from '../../Styles'
|
|
4
4
|
import { breakpointVal } from '../../Styles/breakpointVal'
|
|
5
|
-
import {
|
|
5
|
+
import { DateFormat } from '../../Intl'
|
|
6
6
|
|
|
7
7
|
export type BlogListItemProps = {
|
|
8
8
|
asset: React.ReactNode
|
|
@@ -19,8 +19,6 @@ const { classes } = extendableComponent(name, parts)
|
|
|
19
19
|
export function BlogListItem(props: BlogListItemProps) {
|
|
20
20
|
const { asset, url, date, title, sx = [] } = props
|
|
21
21
|
|
|
22
|
-
const formatter = useDateTimeFormat({ dateStyle: 'long' })
|
|
23
|
-
|
|
24
22
|
return (
|
|
25
23
|
<Box
|
|
26
24
|
className={classes.item}
|
|
@@ -67,7 +65,7 @@ export function BlogListItem(props: BlogListItemProps) {
|
|
|
67
65
|
color: theme.palette.text.secondary,
|
|
68
66
|
})}
|
|
69
67
|
>
|
|
70
|
-
{
|
|
68
|
+
<DateFormat dateStyle='long'>{date}</DateFormat>
|
|
71
69
|
</Box>
|
|
72
70
|
)}
|
|
73
71
|
|
|
@@ -14,7 +14,10 @@ export function BlogTag(props: BlogTagsProps) {
|
|
|
14
14
|
href={`/${url}`}
|
|
15
15
|
component={NextLink}
|
|
16
16
|
label={title}
|
|
17
|
-
sx={[
|
|
17
|
+
sx={[
|
|
18
|
+
{ marginRight: 3, borderRadius: 2, cursor: 'pointer' },
|
|
19
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
|
20
|
+
]}
|
|
18
21
|
/>
|
|
19
22
|
)
|
|
20
23
|
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { Trans } from '@lingui/react'
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Breadcrumbs as MuiBreadcrumbs,
|
|
5
|
+
BreadcrumbsProps as MuiBreadcrumbProps,
|
|
6
|
+
ClickAwayListener,
|
|
7
|
+
IconButton,
|
|
8
|
+
Link,
|
|
9
|
+
Typography,
|
|
10
|
+
useEventCallback,
|
|
11
|
+
useTheme,
|
|
12
|
+
SxProps,
|
|
13
|
+
Theme,
|
|
14
|
+
LinkProps,
|
|
15
|
+
} from '@mui/material'
|
|
16
|
+
import dynamic from 'next/dynamic'
|
|
17
|
+
import { useState, MouseEvent } from 'react'
|
|
18
|
+
import { IconSvg } from '../IconSvg'
|
|
19
|
+
import { iconClose, iconEllypsis } from '../icons'
|
|
20
|
+
import type { BreadcrumbsType } from './types'
|
|
21
|
+
import { i18n } from '@lingui/core'
|
|
22
|
+
import { Button } from '../Button'
|
|
23
|
+
|
|
24
|
+
const BreadcrumbsPopper = dynamic(
|
|
25
|
+
async () => (await import('./BreadcrumbsPopper')).BreadcrumbsPopper,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
export type BreadcrumbsProps = BreadcrumbsType &
|
|
29
|
+
Omit<MuiBreadcrumbProps, 'children'> & {
|
|
30
|
+
maxItems?: number
|
|
31
|
+
lastIsLink?: boolean
|
|
32
|
+
breadcrumbsAmountDesktop?: number
|
|
33
|
+
breadcrumbsAmountMobile?: number
|
|
34
|
+
itemSx?: SxProps<Theme>
|
|
35
|
+
linkProps?: Omit<LinkProps, 'href'>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function Breadcrumbs(props: BreadcrumbsProps) {
|
|
39
|
+
const {
|
|
40
|
+
breadcrumbs,
|
|
41
|
+
sx,
|
|
42
|
+
breadcrumbsAmountDesktop = 4,
|
|
43
|
+
breadcrumbsAmountMobile = 2,
|
|
44
|
+
lastIsLink = false,
|
|
45
|
+
maxItems,
|
|
46
|
+
itemSx = [],
|
|
47
|
+
linkProps,
|
|
48
|
+
...rest
|
|
49
|
+
} = props
|
|
50
|
+
const [anchorElement, setAnchorElement] = useState<HTMLButtonElement | null>(null)
|
|
51
|
+
const theme = useTheme()
|
|
52
|
+
|
|
53
|
+
const isDefaultMobile = breadcrumbsAmountMobile === 0
|
|
54
|
+
const showButtonMobile = breadcrumbs.length > breadcrumbsAmountMobile && !isDefaultMobile
|
|
55
|
+
const isDefaultDesktop = breadcrumbsAmountDesktop === 0
|
|
56
|
+
const showButtonDesktop = breadcrumbs.length > breadcrumbsAmountDesktop && !isDefaultDesktop
|
|
57
|
+
|
|
58
|
+
const handleClick = useEventCallback((event: MouseEvent<HTMLButtonElement>) => {
|
|
59
|
+
setAnchorElement((el) => (el !== event.currentTarget ? event.currentTarget : null))
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const handleClose = () => setAnchorElement(null)
|
|
63
|
+
|
|
64
|
+
const breadcrumbLinks = [...(lastIsLink ? breadcrumbs : breadcrumbs.slice(0, -1))]
|
|
65
|
+
const last = lastIsLink ? null : breadcrumbs[breadcrumbs.length - 1]
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<MuiBreadcrumbs
|
|
69
|
+
{...rest}
|
|
70
|
+
aria-label={i18n._(/* i18n*/ `Breadcrumbs`)}
|
|
71
|
+
maxItems={maxItems}
|
|
72
|
+
color='inherit'
|
|
73
|
+
sx={[
|
|
74
|
+
{},
|
|
75
|
+
!maxItems && {
|
|
76
|
+
'& .MuiBreadcrumbs-ol': {
|
|
77
|
+
flexWrap: 'nowrap',
|
|
78
|
+
'& .MuiBreadcrumbs-li': {
|
|
79
|
+
'&:nth-of-type(1)': {
|
|
80
|
+
display: {
|
|
81
|
+
xs: showButtonMobile ? 'flex' : 'none',
|
|
82
|
+
md: showButtonDesktop ? 'flex' : 'none',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
'&:nth-last-of-type(1)': {
|
|
86
|
+
display: 'inline-flex',
|
|
87
|
+
overflowX: 'hidden',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
'& .MuiBreadcrumbs-separator': {
|
|
92
|
+
'&:nth-of-type(2)': {
|
|
93
|
+
display: {
|
|
94
|
+
xs: !showButtonMobile && 'none',
|
|
95
|
+
md: !showButtonDesktop && 'none',
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
[theme.breakpoints.down('md')]: showButtonMobile && {
|
|
101
|
+
'& .MuiBreadcrumbs-li, & .MuiBreadcrumbs-separator': {
|
|
102
|
+
display: 'none',
|
|
103
|
+
[`&:nth-last-of-type(-n+${breadcrumbsAmountMobile * 2})`]: {
|
|
104
|
+
display: 'flex',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
[theme.breakpoints.up('md')]: showButtonDesktop && {
|
|
110
|
+
'& .MuiBreadcrumbs-li, & .MuiBreadcrumbs-separator': {
|
|
111
|
+
display: 'none',
|
|
112
|
+
[`&:nth-last-of-type(-n+${breadcrumbsAmountDesktop * 2})`]: {
|
|
113
|
+
display: 'flex',
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
|
119
|
+
]}
|
|
120
|
+
>
|
|
121
|
+
{!maxItems && (
|
|
122
|
+
<ClickAwayListener
|
|
123
|
+
mouseEvent='onMouseDown'
|
|
124
|
+
touchEvent='onTouchStart'
|
|
125
|
+
onClickAway={handleClose}
|
|
126
|
+
>
|
|
127
|
+
<Box sx={{ position: 'relative', display: 'flex' }}>
|
|
128
|
+
<Button
|
|
129
|
+
aria-describedby={anchorElement ? 'breadcrumb-list' : undefined}
|
|
130
|
+
color='inherit'
|
|
131
|
+
variant='pill'
|
|
132
|
+
size='small'
|
|
133
|
+
onClick={handleClick}
|
|
134
|
+
sx={{
|
|
135
|
+
minWidth: 0,
|
|
136
|
+
// borderRadius: 2,
|
|
137
|
+
// boxShadow: 6,
|
|
138
|
+
// color: 'text.primary',
|
|
139
|
+
// px: 1,
|
|
140
|
+
// py: { xs: 0.3, md: 0.5 },
|
|
141
|
+
// typography: 'caption',
|
|
142
|
+
// backgroundColor: 'background.paper',
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
<IconSvg src={anchorElement ? iconClose : iconEllypsis} />
|
|
146
|
+
</Button>
|
|
147
|
+
<BreadcrumbsPopper
|
|
148
|
+
breadcrumbs={breadcrumbs}
|
|
149
|
+
anchorElement={anchorElement}
|
|
150
|
+
onClose={handleClose}
|
|
151
|
+
showDesktopAmount={breadcrumbsAmountDesktop}
|
|
152
|
+
showMobileAmount={breadcrumbsAmountMobile}
|
|
153
|
+
/>
|
|
154
|
+
</Box>
|
|
155
|
+
</ClickAwayListener>
|
|
156
|
+
)}
|
|
157
|
+
|
|
158
|
+
<Link
|
|
159
|
+
href='/'
|
|
160
|
+
color='inherit'
|
|
161
|
+
underline='hover'
|
|
162
|
+
{...linkProps}
|
|
163
|
+
sx={[...(Array.isArray(itemSx) ? itemSx : [itemSx])]}
|
|
164
|
+
>
|
|
165
|
+
<Trans id='Home' />
|
|
166
|
+
</Link>
|
|
167
|
+
{breadcrumbLinks.map((breadcrumb) => (
|
|
168
|
+
<Link
|
|
169
|
+
key={breadcrumb.href}
|
|
170
|
+
color='inherit'
|
|
171
|
+
underline='hover'
|
|
172
|
+
{...breadcrumb}
|
|
173
|
+
{...linkProps}
|
|
174
|
+
sx={[
|
|
175
|
+
...(Array.isArray(breadcrumb.sx) ? breadcrumb.sx : [breadcrumb.sx]),
|
|
176
|
+
...(Array.isArray(itemSx) ? itemSx : [itemSx]),
|
|
177
|
+
]}
|
|
178
|
+
>
|
|
179
|
+
{breadcrumb.name}
|
|
180
|
+
</Link>
|
|
181
|
+
))}
|
|
182
|
+
|
|
183
|
+
{last && (
|
|
184
|
+
<Typography
|
|
185
|
+
component='span'
|
|
186
|
+
noWrap
|
|
187
|
+
color='inherit'
|
|
188
|
+
sx={[{ fontWeight: '600' }, ...(Array.isArray(itemSx) ? itemSx : [itemSx])]}
|
|
189
|
+
>
|
|
190
|
+
{last.name}
|
|
191
|
+
</Typography>
|
|
192
|
+
)}
|
|
193
|
+
</MuiBreadcrumbs>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { JsonLd } from '../JsonLd/JsonLd'
|
|
2
|
+
import type { BreadcrumbsType } from './types'
|
|
3
|
+
|
|
4
|
+
type BreadcrumbsJsonLdProps<T extends { '@type': string }> = BreadcrumbsType & {
|
|
5
|
+
render: (breadcrumbs: BreadcrumbsType['breadcrumbs']) => T & { '@context': 'https://schema.org' }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function BreadcrumbsJsonLd<T extends { '@type': string }>(props: BreadcrumbsJsonLdProps<T>) {
|
|
9
|
+
const { render, breadcrumbs } = props
|
|
10
|
+
|
|
11
|
+
if (!breadcrumbs.length) return null
|
|
12
|
+
return <JsonLd<T> item={render(breadcrumbs)} keyVal='breadcrumb-jsonld' />
|
|
13
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { Trans } from '@lingui/react'
|
|
2
|
+
import { Box, Link, alpha, useTheme } from '@mui/material'
|
|
3
|
+
import { useEffect, useRef, KeyboardEvent } from 'react'
|
|
4
|
+
import type { BreadcrumbsType } from './types'
|
|
5
|
+
|
|
6
|
+
type PopperBreadcrumbsListProps = {
|
|
7
|
+
autoFocus: boolean
|
|
8
|
+
breadcrumbs: BreadcrumbsType['breadcrumbs']
|
|
9
|
+
showDesktopAmount?: number
|
|
10
|
+
showMobileAmount?: number
|
|
11
|
+
onClose: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function BreadcrumbsList(props: PopperBreadcrumbsListProps) {
|
|
15
|
+
const { autoFocus, breadcrumbs, showDesktopAmount = 4, showMobileAmount = 3, onClose } = props
|
|
16
|
+
const listRef = useRef<HTMLDivElement | null>(null)
|
|
17
|
+
const theme = useTheme()
|
|
18
|
+
|
|
19
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
20
|
+
if (event.key === 'Escape') {
|
|
21
|
+
onClose()
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (autoFocus) {
|
|
27
|
+
listRef.current?.focus()
|
|
28
|
+
}
|
|
29
|
+
}, [autoFocus])
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Box
|
|
33
|
+
ref={listRef}
|
|
34
|
+
onKeyDown={handleKeyDown}
|
|
35
|
+
tabIndex={-1}
|
|
36
|
+
sx={{
|
|
37
|
+
backgroundColor: 'background.paper',
|
|
38
|
+
borderRadius: 3,
|
|
39
|
+
boxShadow: 12,
|
|
40
|
+
display: 'flex',
|
|
41
|
+
flexDirection: 'column',
|
|
42
|
+
overflow: 'hidden auto',
|
|
43
|
+
py: `calc(${theme.spacings.xxs} / 2)`,
|
|
44
|
+
[theme.breakpoints.up('md')]: {
|
|
45
|
+
'& .MuiLink-root': {
|
|
46
|
+
[`:nth-last-of-type(-n+${showDesktopAmount})`]: {
|
|
47
|
+
display: 'none',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
[theme.breakpoints.down('md')]: {
|
|
52
|
+
'& .MuiLink-root': {
|
|
53
|
+
[`:nth-last-of-type(-n+${showMobileAmount})`]: {
|
|
54
|
+
display: 'none',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}}
|
|
59
|
+
>
|
|
60
|
+
<Link
|
|
61
|
+
href='/'
|
|
62
|
+
underline='none'
|
|
63
|
+
color='text.primary'
|
|
64
|
+
variant='body1'
|
|
65
|
+
noWrap
|
|
66
|
+
onClick={onClose}
|
|
67
|
+
tabIndex={0}
|
|
68
|
+
sx={{
|
|
69
|
+
flex: 1,
|
|
70
|
+
padding: `calc(${theme.spacings.xxs} / 2) ${theme.spacings.xs}`,
|
|
71
|
+
'&:hover': {
|
|
72
|
+
backgroundColor: alpha(theme.palette.action.hover, 0.025),
|
|
73
|
+
},
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<Trans id='Home' />
|
|
77
|
+
</Link>
|
|
78
|
+
{breadcrumbs.slice(0, breadcrumbs.length).map((breadcrumb) => (
|
|
79
|
+
<Link
|
|
80
|
+
{...breadcrumb}
|
|
81
|
+
key={breadcrumb.href}
|
|
82
|
+
underline='none'
|
|
83
|
+
color='text.primary'
|
|
84
|
+
variant='body1'
|
|
85
|
+
noWrap
|
|
86
|
+
onClick={onClose}
|
|
87
|
+
tabIndex={0}
|
|
88
|
+
sx={{
|
|
89
|
+
flex: 1,
|
|
90
|
+
padding: `calc(${theme.spacings.xxs} / 2) ${theme.spacings.xs}`,
|
|
91
|
+
'&:hover': {
|
|
92
|
+
backgroundColor: alpha(theme.palette.action.hover, 0.025),
|
|
93
|
+
},
|
|
94
|
+
}}
|
|
95
|
+
>
|
|
96
|
+
{breadcrumb.name}
|
|
97
|
+
</Link>
|
|
98
|
+
))}
|
|
99
|
+
</Box>
|
|
100
|
+
)
|
|
101
|
+
}
|