@graphcommerce/next-ui 8.0.0-canary.75 → 8.0.0-canary.77

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # Change Log
2
2
 
3
+ ## 8.0.0-canary.77
4
+
5
+ ### Patch Changes
6
+
7
+ - [`e33660f`](https://github.com/graphcommerce-org/graphcommerce/commit/e33660f172466dcfa0ab7262cee612d9a3e47776) - a11y improvements (see https://github.com/graphcommerce-org/graphcommerce/issues/1995 for more info)
8
+ ([@FrankHarland](https://github.com/FrankHarland))
9
+
10
+ ## 8.0.0-canary.76
11
+
3
12
  ## 8.0.0-canary.75
4
13
 
5
14
  ## 8.0.0-canary.74
@@ -3,6 +3,7 @@ import { dvh } from '@graphcommerce/framer-utils'
3
3
  import { Box, SxProps, Theme } from '@mui/material'
4
4
  import { useTransform, useScroll } from 'framer-motion'
5
5
  import { LayoutProvider } from '../../Layout/components/LayoutProvider'
6
+ import { SkipLink } from '../../SkipLink/SkipLink'
6
7
  import { extendableComponent } from '../../Styles'
7
8
  import { useFabSize } from '../../Theme'
8
9
 
@@ -67,6 +68,7 @@ export function LayoutDefault(props: LayoutDefaultProps) {
67
68
  ...(Array.isArray(sx) ? sx : [sx]),
68
69
  ]}
69
70
  >
71
+ <SkipLink />
70
72
  <LayoutProvider scroll={scrollYOffset}>
71
73
  {beforeHeader}
72
74
  <Box
@@ -144,7 +146,10 @@ export function LayoutDefault(props: LayoutDefaultProps) {
144
146
  ) : (
145
147
  <div />
146
148
  )}
147
- <div className={classes.children}>{children}</div>
149
+ <div className={classes.children}>
150
+ <div id='skip-nav' tabIndex={-1} />
151
+ {children}
152
+ </div>
148
153
  <div className={classes.footer}>{footer}</div>
149
154
  </LayoutProvider>
150
155
  </Box>
@@ -69,6 +69,7 @@ export function DesktopNavBar(props: MenuTabsProps) {
69
69
  }}
70
70
  direction='left'
71
71
  size='small'
72
+ tabIndex={-1}
72
73
  className={`${classes.left} ${classes.button}`}
73
74
  >
74
75
  <IconSvg src={iconLeft ?? iconChevronLeft} />
@@ -94,6 +95,7 @@ export function DesktopNavBar(props: MenuTabsProps) {
94
95
  }}
95
96
  direction='right'
96
97
  size='small'
98
+ tabIndex={-1}
97
99
  className={`${classes.right} ${classes.button}`}
98
100
  >
99
101
  <IconSvg src={iconRight ?? iconChevronRight} />
@@ -1,5 +1,5 @@
1
1
  import { useMotionValueValue } from '@graphcommerce/framer-utils'
2
- import { Fab, styled, Box, SxProps, Theme, FabProps } from '@mui/material'
2
+ import { Fab, styled, Box, SxProps, Theme, FabProps, useTheme } from '@mui/material'
3
3
  import { m } from 'framer-motion'
4
4
  import { useRouter } from 'next/router'
5
5
  import React, { useEffect } from 'react'
@@ -35,6 +35,8 @@ export function NavigationFab(props: NavigationFabProps) {
35
35
  const scrollY = useScrollY()
36
36
  const scrolled = useMotionValueValue(scrollY, (y) => y > 10)
37
37
 
38
+ const theme = useTheme()
39
+
38
40
  useEffect(() => {
39
41
  const clear = () => setOpenEl(null)
40
42
  router.events.on('routeChangeStart', clear)
@@ -54,19 +56,19 @@ export function NavigationFab(props: NavigationFabProps) {
54
56
  >
55
57
  <MotionDiv
56
58
  className={classes.wrapper}
57
- sx={(theme) => ({
59
+ sx={{
58
60
  [theme.breakpoints.down('md')]: {
59
61
  opacity: '1 !important',
60
62
  transform: 'none !important',
61
63
  },
62
- })}
64
+ }}
63
65
  style={{ opacity }}
64
66
  >
65
67
  <Fab
66
68
  color='inherit'
67
69
  aria-label='Open Menu'
68
70
  size='responsive'
69
- sx={(theme) => ({
71
+ sx={{
70
72
  boxShadow: 'none',
71
73
  '&:hover, &:focus': {
72
74
  boxShadow: 'none',
@@ -75,7 +77,7 @@ export function NavigationFab(props: NavigationFabProps) {
75
77
  background: theme.palette.text.primary,
76
78
  pointerEvents: 'all',
77
79
  color: theme.palette.background.paper,
78
- })}
80
+ }}
79
81
  className={classes.fab}
80
82
  {...fabProps}
81
83
  >
@@ -87,7 +89,7 @@ export function NavigationFab(props: NavigationFabProps) {
87
89
  )}
88
90
  </Fab>
89
91
  <MotionDiv
90
- sx={(theme) => ({
92
+ sx={{
91
93
  pointerEvents: 'none',
92
94
  borderRadius: '99em',
93
95
  position: 'absolute',
@@ -96,7 +98,7 @@ export function NavigationFab(props: NavigationFabProps) {
96
98
  boxShadow: theme.shadows[6],
97
99
  top: 0,
98
100
  [theme.breakpoints.down('md')]: { opacity: '1 !important' },
99
- })}
101
+ }}
100
102
  className={classes.shadow}
101
103
  style={{ opacity: shadowOpacity }}
102
104
  />
@@ -2,7 +2,7 @@ import { useMotionValueValue, useMotionSelector, dvw } from '@graphcommerce/fram
2
2
  import { i18n } from '@lingui/core'
3
3
  import { useTheme, Box, Fab, SxProps, Theme, useEventCallback, styled } from '@mui/material'
4
4
  import { m } from 'framer-motion'
5
- import React, { useEffect } from 'react'
5
+ import React, { useEffect, useRef } from 'react'
6
6
  import type { LiteralUnion } from 'type-fest'
7
7
  import { IconSvg, useIconSvgSize } from '../../IconSvg'
8
8
  import { LayoutHeaderContent } from '../../Layout/components/LayoutHeaderContent'
@@ -71,12 +71,22 @@ export const NavigationOverlay = React.memo((props: NavigationOverlayProps) => {
71
71
  } else selection.set([])
72
72
  })
73
73
 
74
+ const a11yFocusRef = useRef<HTMLButtonElement | null>(null)
75
+
74
76
  const selectedLevel = useMotionValueValue(selection, (s) => (s === false ? -1 : s.length))
75
77
  const selectionValue = useMotionValueValue(selection, (s) => (s ? s.join('') : s))
76
78
  const activeAndNotClosing = useMotionSelector([selection, closing], ([s, c]) =>
77
79
  c ? false : s !== false,
78
80
  )
79
81
 
82
+ useEffect(() => {
83
+ animating.set(true)
84
+
85
+ if (activeAndNotClosing) {
86
+ a11yFocusRef.current?.focus()
87
+ }
88
+ }, [activeAndNotClosing, animating])
89
+
80
90
  const afterClose = useEventCallback(() => {
81
91
  if (!closing.get()) return
82
92
  setTimeout(() => {
@@ -157,6 +167,7 @@ export const NavigationOverlay = React.memo((props: NavigationOverlayProps) => {
157
167
  sx={{ boxShadow: 'none', my: fabMarginY }}
158
168
  size='responsive'
159
169
  aria-label={i18n._(/* i18n */ 'Close')}
170
+ ref={a11yFocusRef}
160
171
  >
161
172
  <IconSvg src={iconClose} size='large' aria-hidden />
162
173
  </Fab>
@@ -0,0 +1,38 @@
1
+ import { Trans } from '@lingui/react'
2
+ import { Link } from '@mui/material'
3
+
4
+ export function SkipLink() {
5
+ const setFocus = (e) => {
6
+ e.preventDefault()
7
+ globalThis.document.querySelector<HTMLDivElement>('#skip-nav')?.focus()
8
+ globalThis.document.querySelector<HTMLDivElement>('#skip-nav')?.scrollIntoView()
9
+ }
10
+
11
+ return (
12
+ <Link
13
+ href='#skip-nav'
14
+ tabIndex={0}
15
+ onClick={setFocus}
16
+ onKeyDown={(e) => {
17
+ if (e.key === 'Enter') {
18
+ setFocus(e)
19
+ }
20
+ }}
21
+ sx={(theme) => ({
22
+ position: 'absolute',
23
+ top: theme.page.vertical,
24
+ zIndex: '-1',
25
+ marginLeft: theme.page.horizontal,
26
+ padding: theme.spacings.xxs,
27
+ backgroundColor: theme.palette.background.paper,
28
+ border: theme.palette.text.primary,
29
+ borderRadius: theme.shape.borderRadius,
30
+ '&:focus': {
31
+ zIndex: 9999,
32
+ },
33
+ })}
34
+ >
35
+ <Trans id='Skip to main content' />
36
+ </Link>
37
+ )
38
+ }
@@ -162,8 +162,8 @@ export function TextInputNumber(props: TextInputNumberProps) {
162
162
  updateDisabled(e.target)
163
163
  }}
164
164
  inputProps={{
165
- ...inputProps,
166
165
  'aria-label': i18n._(/* i18n */ 'Number'),
166
+ ...inputProps,
167
167
  sx: [
168
168
  {
169
169
  typography: 'body1',
package/index.ts CHANGED
@@ -42,6 +42,7 @@ export * from './Row'
42
42
  export * from './SectionContainer/SectionContainer'
43
43
  export * from './SectionHeader/SectionHeader'
44
44
  export * from './Separator/Separator'
45
+ export * from './SkipLink/SkipLink'
45
46
  export * from './Snackbar/ErrorSnackbar'
46
47
  export * from './Snackbar/MessageSnackbar'
47
48
  export * from './Snackbar/MessageSnackbarImpl'
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": "8.0.0-canary.75",
5
+ "version": "8.0.0-canary.77",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -26,13 +26,13 @@
26
26
  "typescript": "5.3.3"
27
27
  },
28
28
  "peerDependencies": {
29
- "@graphcommerce/eslint-config-pwa": "^8.0.0-canary.75",
30
- "@graphcommerce/framer-next-pages": "^8.0.0-canary.75",
31
- "@graphcommerce/framer-scroller": "^8.0.0-canary.75",
32
- "@graphcommerce/framer-utils": "^8.0.0-canary.75",
33
- "@graphcommerce/image": "^8.0.0-canary.75",
34
- "@graphcommerce/prettier-config-pwa": "^8.0.0-canary.75",
35
- "@graphcommerce/typescript-config-pwa": "^8.0.0-canary.75",
29
+ "@graphcommerce/eslint-config-pwa": "^8.0.0-canary.77",
30
+ "@graphcommerce/framer-next-pages": "^8.0.0-canary.77",
31
+ "@graphcommerce/framer-scroller": "^8.0.0-canary.77",
32
+ "@graphcommerce/framer-utils": "^8.0.0-canary.77",
33
+ "@graphcommerce/image": "^8.0.0-canary.77",
34
+ "@graphcommerce/prettier-config-pwa": "^8.0.0-canary.77",
35
+ "@graphcommerce/typescript-config-pwa": "^8.0.0-canary.77",
36
36
  "@lingui/core": "^4.2.1",
37
37
  "@lingui/macro": "^4.2.1",
38
38
  "@lingui/react": "^4.2.1",