@graphcommerce/next-ui 8.1.0-canary.9 → 9.0.0-canary.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/ActionCard/ActionCard.tsx +60 -36
  2. package/ActionCard/ActionCardAccordion.tsx +2 -1
  3. package/ActionCard/ActionCardList.tsx +3 -2
  4. package/ActionCard/ActionCardListForm.tsx +1 -1
  5. package/Blog/BlogAuthor/BlogAuthor.tsx +2 -4
  6. package/Blog/BlogListItem/BlogListItem.tsx +2 -4
  7. package/Breadcrumbs/Breadcrumbs.tsx +195 -0
  8. package/Breadcrumbs/BreadcrumbsJsonLd.tsx +13 -0
  9. package/Breadcrumbs/BreadcrumbsList.tsx +101 -0
  10. package/Breadcrumbs/BreadcrumbsPopper.tsx +48 -0
  11. package/Breadcrumbs/index.ts +4 -0
  12. package/Breadcrumbs/jsonLdBreadcrumb.tsx +19 -0
  13. package/Breadcrumbs/types.ts +11 -0
  14. package/CHANGELOG.md +162 -0
  15. package/Config.graphqls +5 -0
  16. package/Document/DocumentBodyEnd.tsx +7 -0
  17. package/Document/DocumentBodyStart.tsx +7 -0
  18. package/Document/DocumentHeadEnd.tsx +7 -0
  19. package/Document/DocumentHeadStart.tsx +7 -0
  20. package/Document/index.ts +4 -0
  21. package/Footer/Footer.tsx +1 -1
  22. package/FramerScroller/SidebarGallery.tsx +54 -27
  23. package/Intl/DateTimeFormat/DateFormat.tsx +10 -0
  24. package/Intl/DateTimeFormat/DateTimeFormat.tsx +25 -0
  25. package/Intl/DateTimeFormat/TimeFormat.tsx +10 -0
  26. package/Intl/DateTimeFormat/index.ts +3 -0
  27. package/Intl/DisplayNames/DisplayNames.tsx +22 -0
  28. package/Intl/DisplayNames/index.ts +1 -0
  29. package/Intl/ListFormat.tsx +32 -0
  30. package/Intl/NumberFormat/CurrencyFormat.tsx +191 -0
  31. package/Intl/NumberFormat/NumberFormat.tsx +24 -0
  32. package/Intl/NumberFormat/PercentFormat.tsx +5 -0
  33. package/Intl/NumberFormat/UnitFormat.tsx +59 -0
  34. package/Intl/NumberFormat/index.ts +4 -0
  35. package/Intl/index.ts +11 -0
  36. package/JsonLd/JsonLd.tsx +3 -2
  37. package/Layout/components/LayoutHeader.tsx +17 -3
  38. package/Layout/components/LayoutHeaderBack.tsx +9 -1
  39. package/Layout/components/LayoutTitle.tsx +0 -5
  40. package/LazyHydrate/LazyHydrate.tsx +19 -3
  41. package/PageLoadIndicator/PageLoadIndicator.tsx +25 -14
  42. package/PageMeta/PageMeta.tsx +1 -76
  43. package/PageMeta/canonicalize.ts +78 -0
  44. package/Pagination/PaginationExtended.tsx +103 -0
  45. package/Snackbar/MessageSnackbarImpl.tsx +60 -19
  46. package/Styles/responsiveVal.tsx +4 -5
  47. package/Styles/withEmotionCache.tsx +2 -2
  48. package/Theme/DarkLightModeThemeProvider.tsx +41 -19
  49. package/hooks/index.ts +1 -1
  50. package/hooks/useDateTimeFormat.ts +4 -4
  51. package/hooks/{useSsr.ts → useIsSsr.ts} +3 -0
  52. package/hooks/useLocale.ts +2 -2
  53. package/hooks/useNumberFormat.ts +4 -3
  54. package/hooks/useStorefrontConfig.ts +1 -9
  55. package/hooks/useUrlQuery.ts +6 -4
  56. package/icons.ts +55 -0
  57. package/index.ts +10 -3
  58. package/package.json +8 -9
  59. package/server.ts +8 -0
  60. package/utils/cssFlags.tsx +53 -0
  61. package/utils/getCssFlagInitScript.tsx +20 -0
  62. package/utils/normalizeLocale.ts +26 -0
  63. package/utils/sitemap.ts +1 -1
  64. package/utils/storefrontConfig.ts +8 -0
  65. package/icons/index.ts +0 -48
@@ -0,0 +1,78 @@
1
+ import { addBasePath } from 'next/dist/client/add-base-path'
2
+ import { addLocale } from 'next/dist/client/add-locale'
3
+ import { getDomainLocale } from 'next/dist/client/get-domain-locale'
4
+ import { resolveHref } from 'next/dist/client/resolve-href'
5
+ import { NextRouter } from 'next/dist/shared/lib/router/router'
6
+ import type {} from '@graphcommerce/next-config'
7
+ import { useRouter } from 'next/router'
8
+ import { storefrontConfig } from '../utils/storefrontConfig'
9
+
10
+ type PartialNextRouter = Pick<
11
+ NextRouter,
12
+ 'pathname' | 'locale' | 'locales' | 'isLocaleDomain' | 'domainLocales' | 'defaultLocale'
13
+ >
14
+
15
+ export function canonicalize(router: PartialNextRouter, incoming?: Canonical) {
16
+ let canonical = incoming
17
+
18
+ if (!canonical) return canonical
19
+
20
+ if (!canonical.startsWith('http') && !canonical.startsWith('/')) {
21
+ if (process.env.NODE_ENV !== 'production') {
22
+ console.warn(
23
+ `canonical is relative (${canonical}), a canonical must start with '/', 'http://' or 'https://'`,
24
+ )
25
+ }
26
+ canonical = `/${canonical}`
27
+ }
28
+
29
+ if (canonical.startsWith('/')) {
30
+ let [href, as = href] = resolveHref(router as NextRouter, canonical, true)
31
+
32
+ const curLocale = router.locale
33
+
34
+ // Copied from here https://github.com/vercel/next.js/blob/213c42f446874d29d07fa2cca6e6b11fc9c3b711/packages/next/client/link.tsx#L512
35
+ const localeDomain = getDomainLocale(
36
+ as,
37
+ curLocale,
38
+ router && router.locales,
39
+ router.domainLocales,
40
+ )
41
+
42
+ if (localeDomain) {
43
+ canonical = localeDomain
44
+ } else {
45
+ const conf = storefrontConfig(router.locale)
46
+
47
+ href = addBasePath(
48
+ addLocale(as, curLocale, conf?.domain ? conf.locale : router.defaultLocale),
49
+ )
50
+
51
+ let siteUrl = conf?.canonicalBaseUrl || import.meta.graphCommerce.canonicalBaseUrl
52
+
53
+ if (conf?.domain && !conf?.canonicalBaseUrl) siteUrl = `https://${conf.domain}`
54
+
55
+ if (siteUrl.endsWith('/')) siteUrl = siteUrl.slice(0, -1)
56
+
57
+ canonical = `${siteUrl}${href}`
58
+ }
59
+ }
60
+
61
+ if (!canonical.startsWith('http')) {
62
+ if (process.env.NODE_ENV !== 'production') {
63
+ throw new Error(
64
+ `canonical must start with '/', 'http://' or 'https://', '${canonical}' given`,
65
+ )
66
+ }
67
+ canonical = undefined
68
+ }
69
+
70
+ return canonical
71
+ }
72
+
73
+ export type Canonical = `http://${string}` | `https://${string}` | `/${string}` | string
74
+
75
+ export function useCanonical(incoming?: Canonical) {
76
+ const router = useRouter()
77
+ return canonicalize(router, incoming)
78
+ }
@@ -0,0 +1,103 @@
1
+ import {
2
+ PaginationProps,
3
+ Box,
4
+ SxProps,
5
+ Theme,
6
+ Pagination,
7
+ PaginationItem,
8
+ PaginationRenderItemParams,
9
+ } from '@mui/material'
10
+ import { IconSvg } from '../IconSvg'
11
+ import { extendableComponent } from '../Styles'
12
+ import { NextLink } from '../Theme'
13
+ import { iconChevronLeft, iconChevronRight } from '../icons'
14
+
15
+ export type PaginationExtendedProps = {
16
+ count: number
17
+ page: number
18
+ sx?: SxProps<Theme>
19
+ size?: 'small' | 'medium' | 'large'
20
+ paginationHref: (params: PaginationRenderItemParams) => string
21
+ siblingCount?: number
22
+ boundaryCount?: number
23
+ } & Omit<PaginationProps, 'count' | 'url' | 'defaultPage' | 'page'>
24
+
25
+ const parts = ['root', 'button', 'icon'] as const
26
+ const { classes } = extendableComponent('Pagination', parts)
27
+
28
+ function Prev() {
29
+ return <IconSvg src={iconChevronLeft} className={classes.icon} size='medium' />
30
+ }
31
+ function Next() {
32
+ return <IconSvg src={iconChevronRight} className={classes.icon} size='medium' />
33
+ }
34
+
35
+ /**
36
+ * Rel="prev" and rel="next" are deprecated by Google.
37
+ *
38
+ * Read more: https://ahrefs.com/blog/rel-prev-next-pagination/
39
+ */
40
+ export function PaginationExtended(props: PaginationExtendedProps) {
41
+ const {
42
+ count,
43
+ page,
44
+ sx = [],
45
+ size,
46
+ paginationHref,
47
+ renderItem,
48
+ siblingCount,
49
+ boundaryCount,
50
+ } = props
51
+
52
+ return (
53
+ <Box
54
+ className={classes.root}
55
+ sx={[
56
+ (theme) => ({
57
+ margin: '0 auto',
58
+ marginTop: theme.spacings.lg,
59
+ marginBottom: theme.spacings.lg,
60
+ display: 'flex',
61
+ alignItems: 'center',
62
+ justifyContent: 'center',
63
+ gap: '6px',
64
+ '& .Mui-disabled': {
65
+ background: 'none',
66
+ },
67
+ '& .MuiPagination-ul': {
68
+ justifyContent: 'center',
69
+ },
70
+ '& .MuiPaginationItem-root': {
71
+ minWidth: '35px',
72
+ height: '35px',
73
+ padding: 0,
74
+ '&.MuiPaginationItem-previousNext, &.MuiPaginationItem-ellipsis': {
75
+ margin: 0,
76
+ },
77
+ },
78
+ }),
79
+ ...(Array.isArray(sx) ? sx : [sx]),
80
+ ]}
81
+ >
82
+ <Pagination
83
+ count={count}
84
+ defaultPage={page}
85
+ page={page}
86
+ siblingCount={siblingCount ?? 1}
87
+ boundaryCount={boundaryCount ?? 1}
88
+ size={size ?? 'large'}
89
+ renderItem={
90
+ renderItem ??
91
+ ((item) => (
92
+ <PaginationItem
93
+ component={NextLink}
94
+ href={paginationHref(item)}
95
+ slots={{ previous: Prev, next: Next }}
96
+ {...item}
97
+ />
98
+ ))
99
+ }
100
+ />
101
+ </Box>
102
+ )
103
+ }
@@ -9,9 +9,10 @@ import {
9
9
  SxProps,
10
10
  Theme,
11
11
  Portal,
12
+ SvgIconProps,
12
13
  } from '@mui/material'
13
14
  import React, { useEffect, useState } from 'react'
14
- import { IconSvg } from '../IconSvg'
15
+ import { IconSvg, IconSvgProps } from '../IconSvg'
15
16
  import { extendableComponent, breakpointVal } from '../Styles'
16
17
  import { iconClose, iconCheckmark, iconSadFace } from '../icons'
17
18
  import iconInfo from '../icons/info.svg'
@@ -28,6 +29,8 @@ export type MessageSnackbarProps = Omit<
28
29
  children?: React.ReactNode
29
30
  onClose?: () => void
30
31
  sx?: SxProps<Theme>
32
+ disableClose?: boolean
33
+ icon?: IconSvgProps['src']
31
34
  } & OwnerState
32
35
 
33
36
  type OwnerState = {
@@ -39,6 +42,8 @@ type OwnerState = {
39
42
  * Setting this to true allows interaction with the rest of the page without closing the Snackbar
40
43
  */
41
44
  disableBackdropClick?: boolean
45
+ disableClose?: boolean
46
+ disableIcon?: boolean
42
47
  }
43
48
 
44
49
  const name = 'MessageSnackbarImpl' as const
@@ -62,10 +67,21 @@ export default function MessageSnackbarImpl(props: MessageSnackbarProps) {
62
67
  severity = 'info',
63
68
  sx,
64
69
  disableBackdropClick,
70
+ disableClose,
71
+ disableIcon,
72
+ icon,
65
73
  ...snackbarProps
66
74
  } = props
67
75
 
68
- const classes = withState({ sticky, size, severity, variant })
76
+ const classes = withState({
77
+ sticky,
78
+ size,
79
+ severity,
80
+ variant,
81
+ disableBackdropClick,
82
+ disableClose,
83
+ disableIcon,
84
+ })
69
85
 
70
86
  useEffect(() => {
71
87
  setShowSnackbar(!!open)
@@ -89,9 +105,10 @@ export default function MessageSnackbarImpl(props: MessageSnackbarProps) {
89
105
  }
90
106
  }
91
107
 
92
- let icon = iconCheckmark
93
- if (severity === 'info') icon = iconInfo
94
- if (severity === 'error') icon = iconSadFace
108
+ let icon2 = iconCheckmark
109
+ if (severity === 'info') icon2 = iconInfo
110
+ if (severity === 'error') icon2 = iconSadFace
111
+ if (icon) icon2 = icon
95
112
 
96
113
  return (
97
114
  <Portal>
@@ -148,10 +165,32 @@ export default function MessageSnackbarImpl(props: MessageSnackbarProps) {
148
165
  gridArea: 'children',
149
166
  },
150
167
  },
168
+
169
+ '&.disableIcon .MuiSnackbarContent-message': {
170
+ gridTemplate: {
171
+ xs: `"children close"
172
+ "action action"`,
173
+ md: '"children action close"',
174
+ },
175
+ },
176
+ '&.disableClose .MuiSnackbarContent-message': {
177
+ gridTemplate: {
178
+ xs: `"icon children"
179
+ "action action"`,
180
+ md: '"icon children action"',
181
+ },
182
+ },
183
+ '&.disableIcon.disableClose .MuiSnackbarContent-message': {
184
+ gridTemplate: {
185
+ xs: `"children"
186
+ "action"`,
187
+ md: '"children action"',
188
+ },
189
+ },
151
190
  })}
152
191
  message={
153
192
  <>
154
- <IconSvg src={icon} size='large' />
193
+ {!disableIcon && <IconSvg src={icon2} size='large' />}
155
194
  <Box gridArea='children'>{children}</Box>
156
195
  {/* </Box> */}
157
196
  {action && (
@@ -159,19 +198,21 @@ export default function MessageSnackbarImpl(props: MessageSnackbarProps) {
159
198
  {action}
160
199
  </Box>
161
200
  )}
162
- <Fab
163
- className={classes.close}
164
- aria-label={i18n._(/* i18n */ 'Close')}
165
- size='small'
166
- onClick={hideSnackbar}
167
- onMouseDown={preventAnimationBubble}
168
- onTouchStart={preventAnimationBubble}
169
- sx={(theme) => ({
170
- backgroundColor: lighten(theme.palette.background.paper, 0.1),
171
- })}
172
- >
173
- <IconSvg src={iconClose} />
174
- </Fab>
201
+ {!disableClose && (
202
+ <Fab
203
+ className={classes.close}
204
+ aria-label={i18n._(/* i18n */ 'Close')}
205
+ size='small'
206
+ onClick={hideSnackbar}
207
+ onMouseDown={preventAnimationBubble}
208
+ onTouchStart={preventAnimationBubble}
209
+ sx={(theme) => ({
210
+ backgroundColor: lighten(theme.palette.background.paper, 0.1),
211
+ })}
212
+ >
213
+ <IconSvg src={iconClose} />
214
+ </Fab>
215
+ )}
175
216
  </>
176
217
  }
177
218
  />
@@ -6,15 +6,14 @@
6
6
  export function responsiveVal(
7
7
  min: number,
8
8
  max: number,
9
- maxBreakpoint = 1280,
10
- ): `max(${number}px, min(${string}, ${number}px))` {
9
+ minBreakpoint = 320,
10
+ maxBreakpoint = 2560,
11
+ ): `clamp(${number}px, ${string}, ${number}px)` {
11
12
  const round = (x: number, n: number): number => Math.round(x * 10 ** n) / 10 ** n
12
13
 
13
- const minBreakpoint = 320
14
14
  const growth = (max - min) / (maxBreakpoint - minBreakpoint)
15
15
  const base = round(min - growth * minBreakpoint, 2)
16
16
  const vsize = round(growth * 100, 2)
17
17
 
18
- const calc = `(${base}px + ${vsize}vw)`
19
- return `max(${min}px, min(${calc}, ${max}px))`
18
+ return `clamp(${min}px, (${base}px + ${vsize}vw), ${max}px)`
20
19
  }
@@ -1,10 +1,10 @@
1
1
  import type { EmotionJSX } from '@emotion/react/types/jsx-namespace'
2
2
  import createEmotionServer from '@emotion/server/create-instance'
3
- // eslint-disable-next-line @next/next/no-document-import-in-page
4
3
  import { AppType } from 'next/app'
4
+ // eslint-disable-next-line @next/next/no-document-import-in-page
5
5
  import type NextDocument from 'next/document'
6
6
  // eslint-disable-next-line @next/next/no-document-import-in-page
7
- import type { DocumentContext, DocumentInitialProps } from 'next/document'
7
+ import type { DocumentContext } from 'next/document'
8
8
  import { EmotionProviderProps } from './EmotionProvider'
9
9
  import { createEmotionCache } from './createEmotionCache'
10
10
 
@@ -14,6 +14,7 @@ import { useRouter } from 'next/router'
14
14
  import { createContext, useContext, useEffect, useMemo, useState } from 'react'
15
15
  import { IconSvg } from '../IconSvg'
16
16
  import { iconMoon, iconSun } from '../icons'
17
+ import { getCssFlag, setCssFlag } from '../utils/cssFlags'
17
18
 
18
19
  type Mode = 'dark' | 'light'
19
20
  type UserMode = 'auto' | Mode
@@ -22,6 +23,7 @@ type ColorModeContext = {
22
23
  userMode: UserMode
23
24
  browserMode: Mode
24
25
  currentMode: Mode
26
+ isSingleMode: boolean
25
27
  toggle: () => void
26
28
  }
27
29
 
@@ -29,48 +31,59 @@ export const colorModeContext = createContext(undefined as unknown as ColorModeC
29
31
  colorModeContext.displayName = 'ColorModeContext'
30
32
 
31
33
  type ThemeProviderProps = {
32
- // eslint-disable-next-line react/no-unused-prop-types
33
- light: Theme
34
- // eslint-disable-next-line react/no-unused-prop-types
35
- dark: Theme
36
-
37
34
  children: React.ReactNode
38
- }
35
+ ssrMode?: Mode
36
+ listenToBrowser?: boolean
37
+ } & (
38
+ | { light: Theme; dark?: undefined }
39
+ | { light?: undefined; dark: Theme }
40
+ | { light: Theme; dark: Theme }
41
+ )
39
42
 
40
43
  /**
41
44
  * Wrapper around `import { ThemeProvider } from '@mui/material'`
42
45
  *
43
46
  * The multi DarkLightModeThemeProvider allows switching between light and dark mode based on URL
44
47
  * and on user input.
45
- *
46
- * If you _just_ wan't a single theme, use the import { ThemeProvider } from '@mui/material' instead.
47
48
  */
48
49
  export function DarkLightModeThemeProvider(props: ThemeProviderProps) {
49
- const { children, light, dark } = props
50
+ const { children, light, dark, ssrMode = 'light', listenToBrowser = true } = props
51
+ const [configuredMode, setConfiguredMode] = useState<UserMode>(listenToBrowser ? 'auto' : ssrMode)
52
+ const setThemeMode = (mode: UserMode) => {
53
+ setConfiguredMode(mode)
54
+ setCssFlag('color-scheme', mode)
55
+ }
50
56
 
51
- // todo: Save this in local storage
52
- const [userMode, setUserMode] = useState<UserMode>('auto')
53
57
  const browserMode: Mode = useMediaQuery('(prefers-color-scheme: dark)') ? 'dark' : 'light'
54
58
 
55
59
  // If the user has set a mode, use that. Otherwise, use the browser mode.
56
- const currentMode = userMode === 'auto' ? browserMode : userMode
57
- const theme = currentMode === 'light' ? light : dark
60
+ const currentMode = configuredMode === 'auto' ? browserMode : configuredMode
61
+
62
+ let theme: Theme = light || dark // Default
63
+ if (light && currentMode === 'light') theme = light
64
+ else if (dark) theme = dark
65
+
66
+ useEffect(() => {
67
+ const flag = getCssFlag('color-scheme') as Mode
68
+ if (flag) setConfiguredMode(flag)
69
+ }, [setConfiguredMode])
58
70
 
59
71
  // If a URL parameter is present, switch from auto to light or dark mode
60
72
  const { asPath } = useRouter()
61
73
  useEffect(() => {
62
- if (asPath.includes('darkmode')) setUserMode('dark')
74
+ if (asPath.includes('darkmode')) setConfiguredMode('dark')
63
75
  }, [asPath])
64
76
 
65
77
  // Create the context
66
78
  const colorContext: ColorModeContext = useMemo(
67
79
  () => ({
68
80
  browserMode,
69
- userMode,
81
+ userMode: configuredMode,
70
82
  currentMode,
71
- toggle: () => setUserMode(currentMode === 'light' ? 'dark' : 'light'),
83
+ isSingleMode: !light || !dark,
84
+ toggle: () => setThemeMode(currentMode === 'light' ? 'dark' : 'light'),
72
85
  }),
73
- [browserMode, currentMode, userMode],
86
+ [browserMode, configuredMode, currentMode, light, dark],
74
87
  )
75
88
 
76
89
  return (
@@ -85,7 +98,12 @@ export function useColorMode() {
85
98
  }
86
99
 
87
100
  export function DarkLightModeToggleFab(props: Omit<FabProps, 'onClick'>) {
88
- const { currentMode, toggle } = useColorMode()
101
+ const { currentMode, isSingleMode, toggle } = useColorMode()
102
+
103
+ if (isSingleMode) {
104
+ return null
105
+ }
106
+
89
107
  return (
90
108
  <Fab size='large' color='inherit' onClick={toggle} {...props}>
91
109
  <IconSvg src={currentMode === 'light' ? iconMoon : iconSun} size='large' />
@@ -100,7 +118,11 @@ export function DarkLightModeToggleFab(props: Omit<FabProps, 'onClick'>) {
100
118
  */
101
119
  export function DarkLightModeMenuSecondaryItem(props: ListItemButtonProps) {
102
120
  const { sx = [] } = props
103
- const { currentMode, toggle } = useColorMode()
121
+ const { currentMode, isSingleMode, toggle } = useColorMode()
122
+
123
+ if (isSingleMode) {
124
+ return null
125
+ }
104
126
 
105
127
  return (
106
128
  <ListItemButton {...props} sx={[{}, ...(Array.isArray(sx) ? sx : [sx])]} dense onClick={toggle}>
package/hooks/index.ts CHANGED
@@ -5,5 +5,5 @@ export * from './useNumberFormat'
5
5
  export * from './useMemoObject'
6
6
  export * from './useStorefrontConfig'
7
7
  export * from './useUrlQuery'
8
- export * from './useSsr'
8
+ export * from './useIsSsr'
9
9
  export * from './useLocale'
@@ -1,11 +1,11 @@
1
1
  import { useMemo } from 'react'
2
2
  import { useLocale } from './useLocale'
3
3
 
4
- export type DateTimeFormatProps = Intl.DateTimeFormatOptions
5
-
6
- export function useDateTimeFormat(props?: DateTimeFormatProps) {
4
+ /**
5
+ * @deprecated use <DateFormat/>, <TimeFormat/> or <DateTimeFormat/> instead
6
+ */
7
+ export function useDateTimeFormat(props?: Intl.DateTimeFormatOptions) {
7
8
  const locale = useLocale()
8
-
9
9
  const formatter = useMemo(() => new Intl.DateTimeFormat(locale, props), [locale, props])
10
10
  return formatter
11
11
  }
@@ -2,6 +2,9 @@ import { useSyncExternalStore } from 'react'
2
2
 
3
3
  const emptySubscribe = () => () => {}
4
4
 
5
+ /**
6
+ * This method will return true on the server and during hydration and false after that.
7
+ */
5
8
  export function useIsSSR() {
6
9
  return useSyncExternalStore(
7
10
  emptySubscribe,
@@ -1,7 +1,7 @@
1
- import { normalizeLocale } from '@graphcommerce/lingui-next'
1
+ import { normalizeLocale } from '../utils/normalizeLocale'
2
2
  import { useStorefrontConfig } from './useStorefrontConfig'
3
3
 
4
- export function useLocale() {
4
+ export function useLocale(): Intl.BCP47LanguageTag {
5
5
  const { locale } = useStorefrontConfig()
6
6
  return normalizeLocale(locale)
7
7
  }
@@ -1,9 +1,10 @@
1
1
  import { useMemo } from 'react'
2
2
  import { useLocale } from './useLocale'
3
3
 
4
- export type NumberFormatProps = Intl.NumberFormatOptions
5
-
6
- export function useNumberFormat(props?: NumberFormatProps) {
4
+ /**
5
+ * @deprecated use <NumberFormat />, <PercentFormat /> or <UnitFormat /> instead
6
+ */
7
+ export function useNumberFormat(props?: Intl.NumberFormatOptions) {
7
8
  const locale = useLocale()
8
9
  const formatter = useMemo(() => new Intl.NumberFormat(locale, props), [locale, props])
9
10
  return formatter
@@ -1,13 +1,5 @@
1
1
  import { useRouter } from 'next/router'
2
-
3
- export const storefrontAll = import.meta.graphCommerce.storefront
4
-
5
- /** Get the current storefront config based on the provided locale */
6
- export const storefrontConfig = (locale?: string | undefined) =>
7
- storefrontAll.find((l) => l.locale === locale)
8
-
9
- export const storefrontConfigDefault = () =>
10
- storefrontAll.find((l) => l.defaultLocale) ?? storefrontAll[0]
2
+ import { storefrontConfig } from '../utils/storefrontConfig'
11
3
 
12
4
  /** Automatically selects the correct storefront config based on the current locale */
13
5
  export function useStorefrontConfig(locale?: string | undefined) {
@@ -1,8 +1,8 @@
1
1
  import { useRouter } from 'next/router'
2
2
  import { useCallback } from 'react'
3
3
 
4
- export function useUrlQuery<T extends Record<string, string | null>>() {
5
- const { query, replace } = useRouter()
4
+ export function useUrlQuery<T extends Record<string, string | null>>(doPush?: boolean) {
5
+ const { query, replace, push } = useRouter()
6
6
 
7
7
  const setRouterQuery = useCallback(
8
8
  (incoming: T) => {
@@ -13,9 +13,11 @@ export function useUrlQuery<T extends Record<string, string | null>>() {
13
13
 
14
14
  if (JSON.stringify(current) === JSON.stringify(newQuery)) return Promise.resolve(true)
15
15
 
16
- return replace({ query: newQuery }, undefined, { shallow: true })
16
+ return doPush
17
+ ? push({ query: newQuery })
18
+ : replace({ query: newQuery }, undefined, { shallow: true })
17
19
  },
18
- [replace],
20
+ [doPush, push, replace],
19
21
  )
20
22
 
21
23
  return [query as T, setRouterQuery] as const
package/icons.ts ADDED
@@ -0,0 +1,55 @@
1
+ export { default as iconArrowDown } from './icons/arrow-down.svg'
2
+ export { default as iconArrowBack } from './icons/arrow-left.svg'
3
+ export { default as iconArrowForward } from './icons/arrow-right.svg'
4
+ export { default as iconArrowUp } from './icons/arrow-up.svg'
5
+ export { default as iconShoppingBag } from './icons/bag.svg'
6
+ export { default as iconBin } from './icons/bin.svg'
7
+ export { default as iconInvoice } from './icons/box-alt.svg'
8
+ export { default as iconBox } from './icons/box.svg'
9
+ export { default as iconOrderBefore } from './icons/calendar.svg'
10
+ export { default as iconCancelAlt } from './icons/cancel-alt.svg'
11
+ export { default as iconCancel } from './icons/cancel.svg'
12
+ export { default as iconCartAdd } from './icons/cart-add.svg'
13
+ export { default as iconCart } from './icons/cart.svg'
14
+ export { default as iconChat } from './icons/chat-alt.svg'
15
+ export { default as iconCustomerService } from './icons/chat.svg'
16
+ export { default as iconChevronDown } from './icons/chevron-down.svg'
17
+ export { default as iconChevronBack, default as iconChevronLeft } from './icons/chevron-left.svg'
18
+ export { default as iconChevronRight } from './icons/chevron-right.svg'
19
+ export { default as iconChevronUp } from './icons/chevron-up.svg'
20
+ export { default as iconCirle } from './icons/circle.svg'
21
+ export { default as iconClose } from './icons/close.svg'
22
+ export { default as iconCompare } from './icons/compare-arrows.svg'
23
+ export { default as iconContrast } from './icons/contrast.svg'
24
+ export { default as iconCreditCard, default as iconId } from './icons/credit-card.svg'
25
+ export { default as iconEllypsis } from './icons/ellypsis.svg'
26
+ export { default as iconEmail, default as iconEmailOutline } from './icons/envelope-alt.svg'
27
+ export { default as iconExit } from './icons/exit.svg'
28
+ export { default as icon404 } from './icons/explore.svg'
29
+ export { default as iconEyeClosed } from './icons/eye-closed.svg'
30
+ export { default as iconEyeCrossed } from './icons/eye-crossed.svg'
31
+ export { default as iconEye } from './icons/eye.svg'
32
+ export { default as iconHeart } from './icons/favourite.svg'
33
+ export { default as iconMenu } from './icons/hamburger.svg'
34
+ export { default as iconParty } from './icons/happy-face.svg'
35
+ export { default as iconAddresses, default as iconHome } from './icons/home-alt.svg'
36
+ export { default as iconLanguage } from './icons/language.svg'
37
+ export { default as iconLocation } from './icons/location.svg'
38
+ export { default as iconLock } from './icons/lock.svg'
39
+ export { default as iconFullscreen } from './icons/maximise.svg'
40
+ export { default as iconFullscreenExit } from './icons/minimise.svg'
41
+ export { default as iconMin } from './icons/minus.svg'
42
+ export { default as iconMoon } from './icons/moon.svg'
43
+ export { default as iconNewspaper } from './icons/news.svg'
44
+ export { default as iconCheckmark } from './icons/ok.svg'
45
+ export { default as iconPerson } from './icons/person-alt.svg'
46
+ export { default as iconPlay } from './icons/play.svg'
47
+ export { default as iconPlus } from './icons/plus.svg'
48
+ export { default as iconShutdown } from './icons/power.svg'
49
+ export { default as iconRefresh } from './icons/refresh.svg'
50
+ export { default as iconSadFace } from './icons/sad-face.svg'
51
+ export { default as iconSearch } from './icons/search.svg'
52
+ export { default as iconPhone } from './icons/smartphone.svg'
53
+ export { default as iconStar } from './icons/star.svg'
54
+ export { default as iconSun } from './icons/sun.svg'
55
+ export { default as iconInfo } from './icons/info.svg'
package/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from './Blog/BlogList/BlogList'
7
7
  export * from './Blog/BlogListItem/BlogListItem'
8
8
  export * from './Blog/BlogTags/BlogTags'
9
9
  export * from './Blog/BlogTitle/BlogTitle'
10
+ export * from './Breadcrumbs'
10
11
  export * from './Button'
11
12
  export * from './ChipMenu/ChipMenu'
12
13
  export * from './ContainerWithHeader/ContainerWithHeader'
@@ -22,8 +23,11 @@ export * from './Form/InputCheckmark'
22
23
  export * from './FramerScroller'
23
24
  export * from './FullPageMessage/FullPageMessage'
24
25
  export * from './Highlight/Highlight'
26
+ export * from './hooks'
25
27
  export * from './IconHeader/IconHeader'
28
+ export * from './icons'
26
29
  export * from './IconSvg'
30
+ export * from './Intl'
27
31
  export * from './JsonLd/JsonLd'
28
32
  export * from './Layout'
29
33
  export * from './LayoutDefault'
@@ -35,8 +39,10 @@ export * from './Overlay'
35
39
  export * from './OverlayOrPopperChip'
36
40
  export * from './Page'
37
41
  export * from './PageLoadIndicator/PageLoadIndicator'
42
+ export * from './PageMeta/canonicalize'
38
43
  export * from './PageMeta/PageMeta'
39
44
  export * from './Pagination/Pagination'
45
+ export * from './Pagination/PaginationExtended'
40
46
  export * from './RenderType'
41
47
  export * from './Row'
42
48
  export * from './SectionContainer/SectionContainer'
@@ -56,8 +62,9 @@ export * from './ToggleButton/ToggleButton'
56
62
  export * from './ToggleButtonGroup/ToggleButtonGroup'
57
63
  export * from './UspList/UspList'
58
64
  export * from './UspList/UspListItem'
59
- export * from './hooks'
60
- export * from './icons'
61
65
  export * from './utils/cookie'
62
- export * from './utils/sitemap'
66
+ export * from './utils/cssFlags'
67
+ export * from './utils/normalizeLocale'
63
68
  export * from './utils/robots'
69
+ export * from './utils/sitemap'
70
+ export * from './utils/storefrontConfig'