@graphcommerce/next-ui 4.12.0 → 4.14.0

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.
@@ -0,0 +1,106 @@
1
+ import { useMotionValueValue } from '@graphcommerce/framer-utils'
2
+ import { Fab, styled, Box, SxProps, Theme, FabProps } from '@mui/material'
3
+ import { m } from 'framer-motion'
4
+ import { useRouter } from 'next/router'
5
+ import React, { useEffect } from 'react'
6
+ import { IconSvg } from '../../IconSvg'
7
+ import { useScrollY } from '../../Layout/hooks/useScrollY'
8
+ import { useFabAnimation } from '../../LayoutParts/useFabAnimation'
9
+ import { extendableComponent } from '../../Styles/extendableComponent'
10
+ import { useFabSize } from '../../Theme'
11
+ import { iconMenu, iconClose } from '../../icons'
12
+
13
+ const MotionDiv = styled(m.div)({})
14
+
15
+ export type NavigationFabProps = {
16
+ menuIcon?: React.ReactNode
17
+ closeIcon?: React.ReactNode
18
+ sx?: SxProps<Theme>
19
+ } & Pick<FabProps, 'color' | 'size' | 'variant' | 'onClick'>
20
+
21
+ const name = 'MenuFab'
22
+ const parts = ['wrapper', 'fab', 'shadow', 'menu'] as const
23
+ type OwnerState = {
24
+ scrolled: boolean
25
+ }
26
+
27
+ const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
28
+
29
+ export function NavigationFab(props: NavigationFabProps) {
30
+ const { menuIcon, closeIcon, sx = [], ...fabProps } = props
31
+ const router = useRouter()
32
+ const [openEl, setOpenEl] = React.useState<null | HTMLElement>(null)
33
+
34
+ const { opacity, shadowOpacity } = useFabAnimation()
35
+ const scrollY = useScrollY()
36
+ const scrolled = useMotionValueValue(scrollY, (y) => y > 10)
37
+
38
+ useEffect(() => {
39
+ const clear = () => setOpenEl(null)
40
+ router.events.on('routeChangeStart', clear)
41
+ return () => router.events.off('routeChangeStart', clear)
42
+ }, [router.events])
43
+
44
+ const fabIconSize = useFabSize('responsive')
45
+
46
+ const classes = withState({ scrolled })
47
+
48
+ return (
49
+ <Box
50
+ sx={[
51
+ { position: 'relative', width: fabIconSize, height: fabIconSize },
52
+ ...(Array.isArray(sx) ? sx : [sx]),
53
+ ]}
54
+ >
55
+ <MotionDiv
56
+ className={classes.wrapper}
57
+ sx={(theme) => ({
58
+ [theme.breakpoints.down('md')]: {
59
+ opacity: '1 !important',
60
+ transform: 'none !important',
61
+ },
62
+ })}
63
+ style={{ opacity }}
64
+ >
65
+ <Fab
66
+ color='inherit'
67
+ aria-label='Open Menu'
68
+ size='responsive'
69
+ sx={(theme) => ({
70
+ boxShadow: 'none',
71
+ '&:hover, &:focus': {
72
+ boxShadow: 'none',
73
+ background: theme.palette.text.primary,
74
+ },
75
+ background: theme.palette.text.primary,
76
+ pointerEvents: 'all',
77
+ color: theme.palette.background.paper,
78
+ })}
79
+ className={classes.fab}
80
+ {...fabProps}
81
+ >
82
+ {closeIcon ?? (
83
+ <IconSvg src={iconClose} size='large' sx={{ display: openEl ? 'block' : 'none' }} />
84
+ )}
85
+ {menuIcon ?? (
86
+ <IconSvg src={iconMenu} size='large' sx={{ display: openEl ? 'none' : 'block' }} />
87
+ )}
88
+ </Fab>
89
+ <MotionDiv
90
+ sx={(theme) => ({
91
+ pointerEvents: 'none',
92
+ borderRadius: '99em',
93
+ position: 'absolute',
94
+ height: '100%',
95
+ width: '100%',
96
+ boxShadow: theme.shadows[6],
97
+ top: 0,
98
+ [theme.breakpoints.down('md')]: { opacity: '1 !important' },
99
+ })}
100
+ className={classes.shadow}
101
+ style={{ opacity: shadowOpacity }}
102
+ />
103
+ </MotionDiv>
104
+ </Box>
105
+ )
106
+ }
@@ -0,0 +1,147 @@
1
+ /* eslint-disable @typescript-eslint/no-use-before-define */
2
+ import { Box, ListItemButton, styled, useEventCallback } from '@mui/material'
3
+ import PageLink from 'next/link'
4
+ import { IconSvg } from '../../IconSvg'
5
+ import { extendableComponent } from '../../Styles/extendableComponent'
6
+ import { iconChevronRight } from '../../icons'
7
+ import {
8
+ isNavigationButton,
9
+ isNavigationComponent,
10
+ isNavigationHref,
11
+ NavigationNode,
12
+ NavigationPath,
13
+ useNavigation,
14
+ } from '../hooks/useNavigation'
15
+ import type { NavigationList } from './NavigationList'
16
+
17
+ type OwnerState = {
18
+ first?: boolean
19
+ last?: boolean
20
+ // It is actually used.
21
+ // eslint-disable-next-line react/no-unused-prop-types
22
+ column: number
23
+ }
24
+
25
+ type NavigationItemProps = NavigationNode & {
26
+ parentPath: NavigationPath
27
+ idx: number
28
+ NavigationList: typeof NavigationList
29
+ } & OwnerState
30
+
31
+ const componentName = 'NavigationItem'
32
+ const parts = ['li', 'ul', 'item'] as const
33
+
34
+ const { withState } = extendableComponent<OwnerState, typeof componentName, typeof parts>(
35
+ componentName,
36
+ parts,
37
+ )
38
+
39
+ const NavigationLI = styled('li')({ display: 'contents' })
40
+
41
+ export function NavigationItem(props: NavigationItemProps) {
42
+ const { id, parentPath, idx, first, last, NavigationList } = props
43
+
44
+ const row = idx + 1
45
+ const { selected, select, hideRootOnNavigate, onClose } = useNavigation()
46
+
47
+ const itemPath = [...parentPath, id]
48
+ const isSelected = selected.slice(0, itemPath.length).join('/') === itemPath.join('/')
49
+
50
+ const hidingRoot = hideRootOnNavigate && selected.length > 0
51
+ const hideItem = hidingRoot && itemPath.length === 1
52
+
53
+ const column = hidingRoot ? itemPath.length - 1 : itemPath.length
54
+ const classes = withState({ first, last, column: itemPath.length })
55
+
56
+ const onCloseHandler: React.MouseEventHandler<HTMLAnchorElement> = useEventCallback((e) => {
57
+ if (!isNavigationHref(props)) return
58
+ const { href } = props
59
+ onClose?.(e, href)
60
+ })
61
+
62
+ if (isNavigationButton(props)) {
63
+ const { childItems, name } = props
64
+ return (
65
+ <NavigationLI className={classes.li}>
66
+ <ListItemButton
67
+ className={classes.item}
68
+ role='button'
69
+ sx={{
70
+ gridRowStart: row,
71
+ gridColumnStart: column,
72
+ gap: (theme) => theme.spacings.xxs,
73
+ display: hideItem ? 'none' : 'flex',
74
+ }}
75
+ disabled={isSelected}
76
+ tabIndex={selected.join(',').includes(parentPath.join(',')) ? undefined : -1}
77
+ onClick={(e) => {
78
+ e.preventDefault()
79
+ if (!isSelected) select(itemPath)
80
+ }}
81
+ >
82
+ <Box
83
+ component='span'
84
+ sx={{
85
+ whiteSpace: 'nowrap',
86
+ overflowX: 'hidden',
87
+ textOverflow: 'ellipsis',
88
+ flexGrow: 1,
89
+ }}
90
+ >
91
+ {name}
92
+ </Box>
93
+ <IconSvg src={iconChevronRight} sx={{ flexShrink: 0 }} />
94
+ </ListItemButton>
95
+
96
+ <NavigationList items={childItems} selected={isSelected} parentPath={itemPath} />
97
+ </NavigationLI>
98
+ )
99
+ }
100
+
101
+ if (isNavigationHref(props)) {
102
+ const { name, href } = props
103
+ return (
104
+ <NavigationLI sx={[hideItem && { display: 'none' }]} className={classes.li}>
105
+ <PageLink href={href} passHref>
106
+ <ListItemButton
107
+ className={classes.item}
108
+ component='a'
109
+ sx={(theme) => ({
110
+ gridRowStart: row,
111
+ gridColumnStart: column,
112
+ gap: theme.spacings.xxs,
113
+ })}
114
+ tabIndex={selected.join(',').includes(parentPath.join(',')) ? undefined : -1}
115
+ onClick={onCloseHandler}
116
+ >
117
+ <Box
118
+ component='span'
119
+ sx={{
120
+ whiteSpace: 'nowrap',
121
+ overflowX: 'hidden',
122
+ textOverflow: 'ellipsis',
123
+ }}
124
+ >
125
+ {name}
126
+ </Box>
127
+ </ListItemButton>
128
+ </PageLink>
129
+ </NavigationLI>
130
+ )
131
+ }
132
+
133
+ if (isNavigationComponent(props)) {
134
+ const { component } = props
135
+ return (
136
+ <NavigationLI sx={[hideItem && { display: 'none' }]} className={classes.li}>
137
+ <Box sx={{ gridRowStart: row, gridColumnStart: column }} className={classes.item}>
138
+ {component}
139
+ </Box>
140
+ </NavigationLI>
141
+ )
142
+ }
143
+
144
+ if (process.env.NODE_ENV !== 'production') throw Error('NavigationItem: unknown type')
145
+
146
+ return null
147
+ }
@@ -0,0 +1,50 @@
1
+ import { styled } from '@mui/material'
2
+ import { extendableComponent } from '../../Styles/extendableComponent'
3
+ import { NavigationNode, NavigationPath } from '../hooks/useNavigation'
4
+ import { NavigationItem } from './NavigationItem'
5
+
6
+ const NavigationUList = styled('ul')({})
7
+
8
+ type NavigationItemsProps = {
9
+ parentPath?: NavigationPath
10
+ items: NavigationNode[]
11
+ selected?: boolean
12
+ }
13
+
14
+ type OwnerState = {
15
+ column: number
16
+ }
17
+
18
+ const name = 'NavigationList'
19
+ const parts = ['root'] as const
20
+ const { withState } = extendableComponent<OwnerState, typeof name, typeof parts>(name, parts)
21
+
22
+ // const componentName = 'NavigationItem'
23
+ // const parts = ['li', 'ul', 'item'] as const
24
+
25
+ export function NavigationList(props: NavigationItemsProps) {
26
+ const { items, parentPath = [], selected = false } = props
27
+
28
+ return (
29
+ <NavigationUList
30
+ sx={[
31
+ { display: 'block', position: 'absolute', left: '-10000px', top: '-10000px' },
32
+ selected && { display: 'contents' },
33
+ ]}
34
+ className={withState({ column: 0 }).root}
35
+ >
36
+ {items.map((item, idx) => (
37
+ <NavigationItem
38
+ NavigationList={NavigationList}
39
+ key={item.id}
40
+ {...item}
41
+ parentPath={parentPath}
42
+ idx={idx}
43
+ first={idx === 0}
44
+ last={idx === items.length - 1}
45
+ column={0}
46
+ />
47
+ ))}
48
+ </NavigationUList>
49
+ )
50
+ }
@@ -0,0 +1,237 @@
1
+ import styled from '@emotion/styled'
2
+ import { i18n } from '@lingui/core'
3
+ import { Trans } from '@lingui/react'
4
+ import { Box, Fab, SxProps, Theme, useEventCallback, useMediaQuery } from '@mui/material'
5
+ import { m } from 'framer-motion'
6
+ import { IconSvg, useIconSvgSize } from '../../IconSvg'
7
+ import { LayoutHeaderContent } from '../../Layout/components/LayoutHeaderContent'
8
+ import { LayoutTitle } from '../../Layout/components/LayoutTitle'
9
+ import { Overlay } from '../../Overlay/components/Overlay'
10
+ import { extendableComponent } from '../../Styles/extendableComponent'
11
+ import { useFabSize } from '../../Theme'
12
+ import { iconClose, iconChevronLeft } from '../../icons'
13
+ import {
14
+ isNavigationButton,
15
+ isNavigationComponent,
16
+ NavigationContextType,
17
+ NavigationNodeButton,
18
+ NavigationNodeHref,
19
+ useNavigation,
20
+ } from '../hooks/useNavigation'
21
+ import { NavigationList } from './NavigationList'
22
+
23
+ type NavigationOverlayProps = {
24
+ active: boolean
25
+ sx?: SxProps<Theme>
26
+ stretchColumns?: boolean
27
+ itemWidth: string
28
+ }
29
+
30
+ function findCurrent(
31
+ items: NavigationContextType['items'],
32
+ selected: NavigationContextType['selected'],
33
+ ): NavigationNodeHref | NavigationNodeButton | undefined {
34
+ const lastItem = selected.slice(-1)[0]
35
+
36
+ if (!lastItem) return undefined
37
+
38
+ for (const item of items) {
39
+ // eslint-disable-next-line no-continue
40
+ if (isNavigationComponent(item)) continue
41
+
42
+ // If the item is the current one, return it
43
+ if (item.id === lastItem) return item
44
+
45
+ // Recursively find item
46
+ if (isNavigationButton(item)) return findCurrent(item.childItems, selected)
47
+ }
48
+ return undefined
49
+ }
50
+
51
+ const MotionDiv = styled(m.div)()
52
+
53
+ const componentName = 'Navigation'
54
+ const parts = ['root', 'navigation', 'header', 'column'] as const
55
+ const { classes } = extendableComponent(componentName, parts)
56
+
57
+ export function NavigationOverlay(props: NavigationOverlayProps) {
58
+ const { active, sx, stretchColumns, itemWidth } = props
59
+ const { selected, select, items, onClose } = useNavigation()
60
+
61
+ const fabSize = useFabSize('responsive')
62
+ const svgSize = useIconSvgSize('large')
63
+
64
+ const isMobile = useMediaQuery<Theme>((theme) => theme.breakpoints.down('md'))
65
+ const handleOnBack = useEventCallback(() => {
66
+ if (isMobile) select(selected.slice(0, -1))
67
+ else select([])
68
+ })
69
+
70
+ const showBack = selected.length > 0
71
+
72
+ return (
73
+ <Overlay
74
+ className={classes.root}
75
+ active={active}
76
+ onClosed={onClose}
77
+ variantSm='left'
78
+ sizeSm='floating'
79
+ justifySm='start'
80
+ variantMd='left'
81
+ sizeMd='floating'
82
+ justifyMd='start'
83
+ sx={{
84
+ zIndex: 'drawer',
85
+ '& .LayoutOverlayBase-overlayPane': {
86
+ minWidth: 'auto !important',
87
+ width: 'max-content',
88
+ overflow: 'hidden',
89
+ display: 'grid',
90
+ gridTemplateRows: 'auto 1fr',
91
+ },
92
+ }}
93
+ >
94
+ <MotionDiv layout style={{ display: 'grid' }}>
95
+ <Box
96
+ className={classes.header}
97
+ sx={(theme) => ({
98
+ top: 0,
99
+ position: 'sticky',
100
+ height: { xs: theme.appShell.headerHeightSm, md: theme.appShell.appBarHeightMd },
101
+ zIndex: 1,
102
+ })}
103
+ >
104
+ <LayoutHeaderContent
105
+ floatingMd={false}
106
+ floatingSm={false}
107
+ switchPoint={0}
108
+ layout='position'
109
+ left={
110
+ showBack && (
111
+ <Fab
112
+ color='inherit'
113
+ onClick={handleOnBack}
114
+ sx={{
115
+ boxShadow: 'none',
116
+ marginLeft: `calc((${fabSize} - ${svgSize}) * -0.5)`,
117
+ marginRight: `calc((${fabSize} - ${svgSize}) * -0.5)`,
118
+ }}
119
+ size='responsive'
120
+ aria-label={i18n._(/* i18n */ 'Back')}
121
+ >
122
+ <IconSvg src={iconChevronLeft} size='large' aria-hidden />
123
+ </Fab>
124
+ )
125
+ }
126
+ right={
127
+ <Fab
128
+ color='inherit'
129
+ onClick={() => onClose()}
130
+ sx={{
131
+ boxShadow: 'none',
132
+ marginLeft: `calc((${fabSize} - ${svgSize}) * -0.5)`,
133
+ marginRight: `calc((${fabSize} - ${svgSize}) * -0.5)`,
134
+ }}
135
+ size='responsive'
136
+ aria-label={i18n._(/* i18n */ 'Close')}
137
+ >
138
+ <IconSvg src={iconClose} size='large' aria-hidden />
139
+ </Fab>
140
+ }
141
+ >
142
+ <LayoutTitle size='small' component='span'>
143
+ {findCurrent(items, selected)?.name ?? <Trans id='Menu' />}
144
+ </LayoutTitle>
145
+ </LayoutHeaderContent>
146
+ </Box>
147
+ </MotionDiv>
148
+
149
+ <MotionDiv layout='position' style={{ display: 'grid' }}>
150
+ <Box
151
+ sx={(theme) => ({
152
+ display: 'grid',
153
+ alignItems: !stretchColumns ? 'start' : undefined,
154
+
155
+ [theme.breakpoints.down('md')]: {
156
+ overflow: 'hidden',
157
+ scrollSnapType: 'x mandatory',
158
+ width: `calc(${theme.spacings.md} + ${theme.spacings.md} + ${itemWidth})`,
159
+ },
160
+ '& .NavigationItem-item': {
161
+ width: itemWidth,
162
+ },
163
+ })}
164
+ >
165
+ <Box
166
+ className={classes.navigation}
167
+ sx={[
168
+ (theme) => ({
169
+ py: theme.spacings.md,
170
+ display: 'grid',
171
+ gridAutoFlow: 'column',
172
+ scrollSnapAlign: 'end',
173
+ '& > ul > li > a, & > ul > li > [role=button]': {
174
+ '& span': {
175
+ typography: 'h2',
176
+ },
177
+ // '& svg': { display: 'none' },
178
+ },
179
+ '& .Navigation-column': {},
180
+ '& .NavigationItem-item': {
181
+ mx: theme.spacings.md,
182
+ whiteSpace: 'nowrap',
183
+ },
184
+ '& .NavigationItem-item.first': {
185
+ // mt: theme.spacings.md,
186
+ },
187
+ '& .Navigation-column:first-of-type': {
188
+ boxShadow: 'none',
189
+ },
190
+ }),
191
+ ...(Array.isArray(sx) ? sx : [sx]),
192
+ ]}
193
+ >
194
+ {selected.length >= 0 && (
195
+ <Box
196
+ sx={(theme) => ({
197
+ gridArea: '1 / 1 / 999 / 2',
198
+ boxShadow: `inset 1px 0 ${theme.palette.divider}`,
199
+ })}
200
+ className={classes.column}
201
+ />
202
+ )}
203
+ {selected.length >= 1 && (
204
+ <Box
205
+ sx={(theme) => ({
206
+ gridArea: '1 / 2 / 999 / 3',
207
+ boxShadow: `inset 1px 0 ${theme.palette.divider}`,
208
+ })}
209
+ className={classes.column}
210
+ />
211
+ )}
212
+ {selected.length >= 2 && (
213
+ <Box
214
+ sx={(theme) => ({
215
+ gridArea: '1 / 3 / 999 / 4',
216
+ boxShadow: `inset 1px 0 ${theme.palette.divider}`,
217
+ })}
218
+ className={classes.column}
219
+ />
220
+ )}
221
+ {selected.length >= 3 && (
222
+ <Box
223
+ sx={(theme) => ({
224
+ gridArea: '1 / 4 / 999 / 5',
225
+ boxShadow: `inset 1px 0 ${theme.palette.divider}`,
226
+ })}
227
+ className={classes.column}
228
+ />
229
+ )}
230
+
231
+ <NavigationList items={items} selected />
232
+ </Box>
233
+ </Box>
234
+ </MotionDiv>
235
+ </Overlay>
236
+ )
237
+ }
@@ -0,0 +1,66 @@
1
+ import { useEventCallback } from '@mui/material'
2
+ import { MotionConfig } from 'framer-motion'
3
+ import { useState, useMemo } from 'react'
4
+ import { isElement } from 'react-is'
5
+ import {
6
+ NavigationNode,
7
+ NavigationPath,
8
+ NavigationContextType,
9
+ NavigationContext,
10
+ NavigationSelect,
11
+ } from '../hooks/useNavigation'
12
+
13
+ export type NavigationProviderProps = {
14
+ items: (NavigationNode | React.ReactElement)[]
15
+ hideRootOnNavigate?: boolean
16
+ closeAfterNavigate?: boolean
17
+ children?: React.ReactNode
18
+ animationDuration?: number
19
+ onChange?: NavigationSelect
20
+ onClose?: NavigationContextType['onClose']
21
+ }
22
+
23
+ const nonNullable = <T,>(value: T): value is NonNullable<T> => value !== null && value !== undefined
24
+
25
+ export function NavigationProvider(props: NavigationProviderProps) {
26
+ const {
27
+ items,
28
+ onChange,
29
+ hideRootOnNavigate = true,
30
+ closeAfterNavigate = false,
31
+ animationDuration = 0.275,
32
+ children,
33
+ onClose: onCloseUnstable,
34
+ } = props
35
+
36
+ const [selected, setSelected] = useState<NavigationPath>([])
37
+
38
+ const select = useEventCallback((incomming: NavigationPath) => {
39
+ setSelected(incomming)
40
+ onChange?.(incomming)
41
+ })
42
+
43
+ const onClose: NavigationContextType['onClose'] = useEventCallback((e, href) => {
44
+ onCloseUnstable?.(e, href)
45
+ setTimeout(() => select([]), animationDuration * 1000)
46
+ })
47
+
48
+ const value = useMemo<NavigationContextType>(
49
+ () => ({
50
+ hideRootOnNavigate,
51
+ selected,
52
+ select,
53
+ items: items
54
+ .map((item, index) => (isElement(item) ? { id: item.key ?? index, component: item } : item))
55
+ .filter(nonNullable),
56
+ onClose,
57
+ }),
58
+ [hideRootOnNavigate, selected, select, items, onClose],
59
+ )
60
+
61
+ return (
62
+ <MotionConfig transition={{ duration: animationDuration }}>
63
+ <NavigationContext.Provider value={value}>{children}</NavigationContext.Provider>
64
+ </MotionConfig>
65
+ )
66
+ }
@@ -0,0 +1,58 @@
1
+ import { createContext, useContext } from 'react'
2
+
3
+ export type NavigationId = string | number
4
+ export type NavigationPath = NavigationId[]
5
+ export type NavigationSelect = (selected: NavigationPath) => void
6
+ export type NavigationRender = React.FC<
7
+ (NavigationNodeComponent | NavigationNodeHref) & { children?: React.ReactNode }
8
+ >
9
+
10
+ export type NavigationOnClose = (
11
+ event?: React.MouseEvent<HTMLAnchorElement>,
12
+ href?: string | undefined,
13
+ ) => void
14
+ export type NavigationContextType = {
15
+ selected: NavigationPath
16
+ select: NavigationSelect
17
+ items: NavigationNode[]
18
+ hideRootOnNavigate: boolean
19
+ onClose: NavigationOnClose
20
+ }
21
+
22
+ type NavigationNodeBase = {
23
+ id: NavigationId
24
+ }
25
+
26
+ export type NavigationNodeHref = NavigationNodeBase & {
27
+ name: string
28
+ href: string
29
+ }
30
+
31
+ export type NavigationNodeButton = NavigationNodeBase & {
32
+ name: string
33
+ childItems: NavigationNode[]
34
+ }
35
+
36
+ export type NavigationNodeComponent = NavigationNodeBase & {
37
+ component: React.ReactNode
38
+ }
39
+
40
+ export type NavigationNode = NavigationNodeHref | NavigationNodeButton | NavigationNodeComponent
41
+
42
+ export function isNavigationHref(node: NavigationNodeBase): node is NavigationNodeHref {
43
+ return 'href' in node
44
+ }
45
+
46
+ export function isNavigationButton(node: NavigationNodeBase): node is NavigationNodeButton {
47
+ return (node as NavigationNodeButton).childItems?.length > 0
48
+ }
49
+
50
+ export function isNavigationComponent(node: NavigationNodeBase): node is NavigationNodeComponent {
51
+ return 'component' in node
52
+ }
53
+
54
+ export const NavigationContext = createContext(undefined as unknown as NavigationContextType)
55
+
56
+ export function useNavigation() {
57
+ return useContext(NavigationContext)
58
+ }
@@ -0,0 +1,4 @@
1
+ export * from './components/NavigationOverlay'
2
+ export * from './components/NavigationFab'
3
+ export * from './hooks/useNavigation'
4
+ export * from './components/NavigationProvider'