@graphcommerce/next-ui 3.0.1
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/.babelrc +3 -0
- package/AnimatedRow/index.tsx +20 -0
- package/ApolloError/ApolloErrorAlert.tsx +59 -0
- package/ApolloError/ApolloErrorFullPage.tsx +25 -0
- package/AppShell/AppShellHeader/appShellHeaderContext.tsx +11 -0
- package/AppShell/AppShellHeader/index.tsx +433 -0
- package/AppShell/AppShellHeader/useAppShellHeaderContext.tsx +6 -0
- package/AppShell/AppShellProvider/index.tsx +18 -0
- package/AppShell/AppShellSticky/index.tsx +66 -0
- package/AppShell/AppShellTitle/index.tsx +45 -0
- package/AppShell/DesktopNavActions.tsx +28 -0
- package/AppShell/DesktopNavBar.tsx +110 -0
- package/AppShell/FixedFab.tsx +41 -0
- package/AppShell/ForwardButton.tsx +53 -0
- package/AppShell/FullPageShellBase.tsx +59 -0
- package/AppShell/Menu.tsx +7 -0
- package/AppShell/MenuFab.tsx +132 -0
- package/AppShell/MenuFabSecondaryItem.tsx +32 -0
- package/AppShell/MinimalPageShellBase.tsx +22 -0
- package/AppShell/PageShellHeader/index.tsx +14 -0
- package/AppShell/SheetShellBase/index.tsx +107 -0
- package/AppShell/SheetShellBase/useSheetStyles.ts +11 -0
- package/AppShell/SheetShellDragIndicator/index.tsx +48 -0
- package/AppShell/SheetShellHeader/index.tsx +28 -0
- package/AppShell/ShellBase.tsx +45 -0
- package/AppShell/useFabAnimation.tsx +19 -0
- package/AppShell/useFixedFabAnimation.tsx +18 -0
- package/AspectRatioContainer/index.tsx +27 -0
- package/Blog/BlogAuthor/index.tsx +60 -0
- package/Blog/BlogContent/index.tsx +24 -0
- package/Blog/BlogHeader/index.tsx +64 -0
- package/Blog/BlogList/index.tsx +27 -0
- package/Blog/BlogListItem/index.tsx +83 -0
- package/Blog/BlogTags/index.tsx +34 -0
- package/Blog/BlogTitle/index.tsx +29 -0
- package/Button/index.tsx +164 -0
- package/ButtonLink/index.tsx +45 -0
- package/CHANGELOG.md +1095 -0
- package/ChipMenu/index.tsx +130 -0
- package/ContainerWithHeader/index.tsx +49 -0
- package/Debug/DebugSpacer.tsx +51 -0
- package/FlagAvatar/index.tsx +28 -0
- package/Form/FormActions.tsx +15 -0
- package/Form/FormDivider.tsx +14 -0
- package/Form/FormHeader.tsx +27 -0
- package/Form/FormRow.tsx +14 -0
- package/Form/InputCheckmark.tsx +19 -0
- package/Form/index.tsx +62 -0
- package/FramerNextPagesSlider/Slide.tsx +71 -0
- package/FramerNextPagesSlider/Slider.tsx +39 -0
- package/FramerNextPagesSlider/index.ts +1 -0
- package/FramerNextPagesSlider/types.ts +3 -0
- package/FramerScroller/components/SidebarGallery.tsx +246 -0
- package/FramerScroller/components/SidebarSlider.tsx +103 -0
- package/FullPageMessage/index.tsx +68 -0
- package/Highlight/index.tsx +14 -0
- package/IconHeader/index.tsx +109 -0
- package/JsonLd/index.tsx +18 -0
- package/Page/App.tsx +15 -0
- package/Page/Document.tsx +23 -0
- package/Page/framerFeatures.ts +4 -0
- package/Page/types.ts +19 -0
- package/PageLoadIndicator/index.tsx +46 -0
- package/PageMeta/index.tsx +40 -0
- package/Pagination/index.tsx +123 -0
- package/RenderType/index.tsx +40 -0
- package/Row/ButtonLinkList/index.tsx +53 -0
- package/Row/ColumnOne/index.tsx +11 -0
- package/Row/ColumnOneBoxed/index.tsx +27 -0
- package/Row/ColumnOneCentered/index.tsx +22 -0
- package/Row/ColumnThree/index.tsx +66 -0
- package/Row/ColumnTwo/index.tsx +44 -0
- package/Row/ColumnTwoSpread/index.tsx +41 -0
- package/Row/ColumnTwoWithTop/index.tsx +53 -0
- package/Row/ContentLinks/index.tsx +48 -0
- package/Row/HeroBanner/index.tsx +102 -0
- package/Row/IconBlocks/IconBlock/index.tsx +56 -0
- package/Row/IconBlocks/index.tsx +57 -0
- package/Row/ParagraphWithSidebarSlide/index.tsx +114 -0
- package/Row/Quote/index.tsx +13 -0
- package/Row/RowImageText/index.tsx +67 -0
- package/Row/RowImageTextBoxed/index.tsx +75 -0
- package/Row/SpecialBanner/index.tsx +107 -0
- package/Row/index.tsx +13 -0
- package/SectionContainer/index.tsx +39 -0
- package/SectionHeader/index.tsx +69 -0
- package/Separator/index.tsx +33 -0
- package/Snackbar/ErrorSnackbar.tsx +9 -0
- package/Snackbar/MessageSnackbar.tsx +5 -0
- package/Snackbar/MessageSnackbarImpl.tsx +202 -0
- package/StarRatingField/index.tsx +58 -0
- package/Stepper/Stepper.tsx +45 -0
- package/StyledBadge/index.tsx +21 -0
- package/Styles/index.tsx +3 -0
- package/Styles/responsiveVal.tsx +20 -0
- package/SvgImage/SvgImageSimple.tsx +66 -0
- package/SvgImage/index.tsx +76 -0
- package/TextInputNumber/index.tsx +169 -0
- package/Theme/types.ts +63 -0
- package/TimeAgo/index.tsx +29 -0
- package/Title/index.tsx +71 -0
- package/ToggleButton/index.tsx +100 -0
- package/ToggleButtonGroup/index.tsx +112 -0
- package/UspList/UspListItem.tsx +46 -0
- package/UspList/index.tsx +32 -0
- package/icons/icon_addresses.svg +17 -0
- package/icons/icon_arrow_back.svg +1 -0
- package/icons/icon_arrow_forward.svg +1 -0
- package/icons/icon_box.svg +6 -0
- package/icons/icon_chat.svg +1 -0
- package/icons/icon_checkmark.svg +1 -0
- package/icons/icon_checkmark_green.svg +1 -0
- package/icons/icon_chevron_back.svg +8 -0
- package/icons/icon_chevron_down.svg +8 -0
- package/icons/icon_chevron_left.svg +8 -0
- package/icons/icon_chevron_right.svg +8 -0
- package/icons/icon_chevron_up.svg +8 -0
- package/icons/icon_close.svg +6 -0
- package/icons/icon_close_circle.svg +1 -0
- package/icons/icon_collapse_vertical.svg +11 -0
- package/icons/icon_customer_service.svg +6 -0
- package/icons/icon_email.svg +1 -0
- package/icons/icon_email_outline.svg +6 -0
- package/icons/icon_expand_vertical.svg +11 -0
- package/icons/icon_heart.svg +15 -0
- package/icons/icon_home.svg +6 -0
- package/icons/icon_id.svg +6 -0
- package/icons/icon_invoice_red.svg +7 -0
- package/icons/icon_location_red.svg +7 -0
- package/icons/icon_lock.svg +6 -0
- package/icons/icon_menu.svg +1 -0
- package/icons/icon_min.svg +1 -0
- package/icons/icon_newspaper.svg +6 -0
- package/icons/icon_party.svg +7 -0
- package/icons/icon_person.svg +6 -0
- package/icons/icon_person_alt.svg +6 -0
- package/icons/icon_person_alt_big.svg +15 -0
- package/icons/icon_phone.svg +1 -0
- package/icons/icon_plus.svg +1 -0
- package/icons/icon_sad_face.svg +11 -0
- package/icons/icon_sad_shoppingbag.svg +16 -0
- package/icons/icon_search.svg +1 -0
- package/icons/icon_shopping_bag.svg +7 -0
- package/icons/icon_shutdown.svg +6 -0
- package/icons/icon_star.svg +6 -0
- package/icons/icon_star_filled_muted.svg +6 -0
- package/icons/icon_star_yellow.svg +6 -0
- package/icons/index.tsx +42 -0
- package/index.ts +163 -0
- package/package.json +61 -0
- package/tsconfig.json +5 -0
- package/types.d.ts +13 -0
- package/useIntersectionObserver/index.tsx +31 -0
package/.babelrc
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { HTMLMotionProps, m } from 'framer-motion'
|
|
2
|
+
import { ReactHTML } from 'react'
|
|
3
|
+
|
|
4
|
+
export type AnimatedRowProps = Omit<
|
|
5
|
+
ReactHTML['div'] & HTMLMotionProps<'div'>,
|
|
6
|
+
'layout' | 'initial' | 'animate' | 'exit' | 'transition'
|
|
7
|
+
>
|
|
8
|
+
|
|
9
|
+
export default function AnimatedRow(props: AnimatedRowProps) {
|
|
10
|
+
return (
|
|
11
|
+
<m.div
|
|
12
|
+
{...props}
|
|
13
|
+
layout
|
|
14
|
+
initial={{ opacity: 0, height: 0 }}
|
|
15
|
+
animate={{ opacity: 1, height: 'auto' }}
|
|
16
|
+
exit={{ opacity: 0, height: 0 }}
|
|
17
|
+
transition={{ type: 'tween' }}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ApolloError } from '@apollo/client'
|
|
2
|
+
import { makeStyles, Theme } from '@material-ui/core'
|
|
3
|
+
import { Alert, AlertProps } from '@material-ui/lab'
|
|
4
|
+
import { AnimatePresence } from 'framer-motion'
|
|
5
|
+
import React from 'react'
|
|
6
|
+
import AnimatedRow from '../AnimatedRow'
|
|
7
|
+
|
|
8
|
+
export const useStyles = makeStyles(
|
|
9
|
+
(theme: Theme) => ({
|
|
10
|
+
alerts: {},
|
|
11
|
+
alert: {
|
|
12
|
+
paddingTop: `calc(${theme.spacings.xxs} / 2)`,
|
|
13
|
+
paddingBottom: `calc(${theme.spacings.xxs} / 2)`,
|
|
14
|
+
},
|
|
15
|
+
}),
|
|
16
|
+
{ name: 'ApolloErrorAlert' },
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
export type ApolloErrorAlertProps = {
|
|
20
|
+
error?: ApolloError
|
|
21
|
+
graphqlErrorAlertProps?: Omit<AlertProps, 'severity'>
|
|
22
|
+
networkErrorAlertProps?: Omit<AlertProps, 'severity'>
|
|
23
|
+
}
|
|
24
|
+
export default function ApolloErrorAlert(props: ApolloErrorAlertProps) {
|
|
25
|
+
const classes = useStyles()
|
|
26
|
+
const { error, graphqlErrorAlertProps, networkErrorAlertProps } = props
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<AnimatePresence initial={false}>
|
|
30
|
+
{error && (
|
|
31
|
+
<AnimatedRow key='alerts'>
|
|
32
|
+
<div className={classes.alerts}>
|
|
33
|
+
<AnimatePresence initial={false}>
|
|
34
|
+
{error.graphQLErrors.map((e, index) => (
|
|
35
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
36
|
+
<AnimatedRow key={index}>
|
|
37
|
+
<div className={classes.alert}>
|
|
38
|
+
<Alert severity='error' {...graphqlErrorAlertProps}>
|
|
39
|
+
{e.message}
|
|
40
|
+
</Alert>
|
|
41
|
+
</div>
|
|
42
|
+
</AnimatedRow>
|
|
43
|
+
))}
|
|
44
|
+
{error.networkError && (
|
|
45
|
+
<AnimatedRow key='networkError'>
|
|
46
|
+
<div className={classes.alert} key='networkError'>
|
|
47
|
+
<Alert severity='error' {...networkErrorAlertProps}>
|
|
48
|
+
Network Error: {error.networkError.message}
|
|
49
|
+
</Alert>
|
|
50
|
+
</div>
|
|
51
|
+
</AnimatedRow>
|
|
52
|
+
)}
|
|
53
|
+
</AnimatePresence>
|
|
54
|
+
</div>
|
|
55
|
+
</AnimatedRow>
|
|
56
|
+
)}
|
|
57
|
+
</AnimatePresence>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ApolloError } from '@apollo/client'
|
|
2
|
+
import { AlertProps } from '@material-ui/lab'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import FullPageMessage, { FullPageMessageProps } from '../FullPageMessage'
|
|
5
|
+
import ApolloErrorAlert from './ApolloErrorAlert'
|
|
6
|
+
|
|
7
|
+
export type ApolloErrorFullPageProps = {
|
|
8
|
+
error?: ApolloError
|
|
9
|
+
graphqlErrorAlertProps?: Omit<AlertProps, 'severity'>
|
|
10
|
+
networkErrorAlertProps?: Omit<AlertProps, 'severity'>
|
|
11
|
+
} & Omit<FullPageMessageProps, 'title' | 'description'>
|
|
12
|
+
|
|
13
|
+
export default function ApolloErrorFullPage(props: ApolloErrorFullPageProps) {
|
|
14
|
+
const { error, graphqlErrorAlertProps, networkErrorAlertProps, ...fullPageMessageProps } = props
|
|
15
|
+
|
|
16
|
+
const singleError = error?.graphQLErrors.length === 1
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<FullPageMessage
|
|
20
|
+
title={singleError ? error?.graphQLErrors[0].message : 'Several errors occured'}
|
|
21
|
+
description={singleError ? undefined : <ApolloErrorAlert error={error} />}
|
|
22
|
+
{...fullPageMessageProps}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
export type AppShellHeaderContext = {
|
|
4
|
+
titleRef: React.RefObject<HTMLDivElement>
|
|
5
|
+
contentHeaderRef: React.RefObject<HTMLDivElement>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const appShellHeaderContext = React.createContext(undefined as unknown as AppShellHeaderContext)
|
|
9
|
+
appShellHeaderContext.displayName = 'appShellHeaderContext'
|
|
10
|
+
|
|
11
|
+
export default appShellHeaderContext
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
import { Fab, makeStyles, Theme } from '@material-ui/core'
|
|
2
|
+
import { useHistoryLink, usePageContext, usePageRouter } from '@graphcommerce/framer-next-pages'
|
|
3
|
+
import clsx from 'clsx'
|
|
4
|
+
import { m, MotionValue, useMotionValue, useTransform } from 'framer-motion'
|
|
5
|
+
import PageLink from 'next/link'
|
|
6
|
+
import React, { useCallback, useEffect } from 'react'
|
|
7
|
+
import Button from '../../Button'
|
|
8
|
+
import { UseStyles } from '../../Styles'
|
|
9
|
+
import SvgImage from '../../SvgImage'
|
|
10
|
+
import { iconChevronLeft, iconClose } from '../../icons'
|
|
11
|
+
import useAppShellHeaderContext from './useAppShellHeaderContext'
|
|
12
|
+
|
|
13
|
+
export type AppShellHeaderProps = {
|
|
14
|
+
children?: React.ReactNode
|
|
15
|
+
primary?: React.ReactNode
|
|
16
|
+
secondary?: React.ReactNode
|
|
17
|
+
divider?: React.ReactNode
|
|
18
|
+
/* When a logo is given, children should be given too */
|
|
19
|
+
logo?: React.ReactNode
|
|
20
|
+
scrollY: MotionValue<number>
|
|
21
|
+
hideClose?: boolean
|
|
22
|
+
scrolled?: boolean
|
|
23
|
+
dragIndicator?: React.ReactNode
|
|
24
|
+
additional?: React.ReactNode
|
|
25
|
+
backFallbackHref?: string | null
|
|
26
|
+
backFallbackTitle?: string | null
|
|
27
|
+
fill?: 'both' | 'mobile-only'
|
|
28
|
+
sheet?: boolean
|
|
29
|
+
} & UseStyles<typeof useStyles>
|
|
30
|
+
|
|
31
|
+
// minHeight: x
|
|
32
|
+
// to reserve space for back & primary buttons,
|
|
33
|
+
// even when there is no app shell header on scroll (e.g. on full page shell)
|
|
34
|
+
const minHeight = 40
|
|
35
|
+
const useStyles = makeStyles(
|
|
36
|
+
(theme: Theme) => ({
|
|
37
|
+
divider: {
|
|
38
|
+
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
39
|
+
},
|
|
40
|
+
dividerSpacer: {
|
|
41
|
+
minHeight: 2,
|
|
42
|
+
},
|
|
43
|
+
dividerSheetShell: {
|
|
44
|
+
marginTop: `calc((${theme.page.headerInnerHeight.md} * 0.15))`,
|
|
45
|
+
},
|
|
46
|
+
sheetHeaderContainer: {
|
|
47
|
+
position: 'sticky',
|
|
48
|
+
top: 0,
|
|
49
|
+
zIndex: 98,
|
|
50
|
+
// reserve space in the container even without any buttons added
|
|
51
|
+
minHeight: 58,
|
|
52
|
+
},
|
|
53
|
+
sheetHeaderContainerSheetShell: {
|
|
54
|
+
marginBottom: `calc((${theme.page.headerInnerHeight.md} * 0.15) * -1)`,
|
|
55
|
+
},
|
|
56
|
+
sheetHeader: {
|
|
57
|
+
background: theme.palette.background.default,
|
|
58
|
+
paddingTop: 8,
|
|
59
|
+
paddingBottom: 8,
|
|
60
|
+
minHeight,
|
|
61
|
+
[theme.breakpoints.up('md')]: {
|
|
62
|
+
minHeight: `calc(${minHeight}px + ${theme.spacings.xxs} * 2)`,
|
|
63
|
+
paddingTop: theme.spacings.xxs,
|
|
64
|
+
paddingBottom: theme.spacings.xxs,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
sheetHeaderSheetShell: {
|
|
68
|
+
// The bottom sheets offset top is x% short compared to the page headers height,
|
|
69
|
+
// so we add x% of the header height to padding top of bottomsheet actions bar
|
|
70
|
+
// to keep consistency between app shell buttons.
|
|
71
|
+
paddingTop: `calc(${theme.spacings.xxs} + (${theme.page.headerInnerHeight.md} * 0.15))`,
|
|
72
|
+
paddingBottom: `calc(${theme.spacings.xxs} + (${theme.page.headerInnerHeight.md} * 0.15))`,
|
|
73
|
+
[theme.breakpoints.down('sm')]: {
|
|
74
|
+
paddingTop: 8,
|
|
75
|
+
paddingBottom: 8,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
sheetHeaderScrolled: {
|
|
79
|
+
[theme.breakpoints.up('md')]: {
|
|
80
|
+
marginTop: -60,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
sheetHeaderActions: {
|
|
84
|
+
display: 'grid',
|
|
85
|
+
gridTemplateColumns: `1fr auto 1fr`,
|
|
86
|
+
gridAutoFlow: 'column',
|
|
87
|
+
alignItems: 'center',
|
|
88
|
+
justifyContent: 'space-between',
|
|
89
|
+
padding: `0 calc(${theme.page.horizontal} + 2px) 0`,
|
|
90
|
+
width: '100%',
|
|
91
|
+
minHeight,
|
|
92
|
+
[theme.breakpoints.up('md')]: {
|
|
93
|
+
'& * > a, & * > button': {
|
|
94
|
+
height: minHeight,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
[theme.breakpoints.down('sm')]: {
|
|
98
|
+
'& div > .MuiFab-sizeSmall': {
|
|
99
|
+
marginLeft: -8,
|
|
100
|
+
marginRight: -8,
|
|
101
|
+
},
|
|
102
|
+
'& div > .MuiButtonBase-root': {
|
|
103
|
+
minWidth: 'unset',
|
|
104
|
+
marginRight: -8,
|
|
105
|
+
marginLeft: -8,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
sheetHeaderActionRight: {
|
|
110
|
+
justifySelf: 'flex-end',
|
|
111
|
+
'& > .Mui-disabled': {
|
|
112
|
+
opacity: 0.25,
|
|
113
|
+
color: `${theme.palette.primary.contrastText} !important`,
|
|
114
|
+
[theme.breakpoints.up('md')]: {
|
|
115
|
+
color: `${theme.palette.secondary.contrastText} !important`,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
sheetHeaderNoTitle: {
|
|
120
|
+
pointerEvents: 'none',
|
|
121
|
+
background: 'transparent',
|
|
122
|
+
},
|
|
123
|
+
sheetHeaderNoTitleFillMobileOnly: {
|
|
124
|
+
[theme.breakpoints.up('md')]: {
|
|
125
|
+
pointerEvents: 'none',
|
|
126
|
+
background: 'transparent',
|
|
127
|
+
top: 98,
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
sheetHeaderFillMobileOnly: {
|
|
131
|
+
[theme.breakpoints.up('md')]: {
|
|
132
|
+
pointerEvents: 'none',
|
|
133
|
+
background: 'transparent',
|
|
134
|
+
marginBottom: `calc((${theme.page.headerInnerHeight.md} * -1) + ${theme.spacings.xxs} * 2)`,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
innerContainer: {
|
|
138
|
+
display: 'grid',
|
|
139
|
+
textAlign: 'center',
|
|
140
|
+
pointerEvents: 'all',
|
|
141
|
+
},
|
|
142
|
+
fab: {
|
|
143
|
+
maxWidth: 38,
|
|
144
|
+
maxHeight: 38,
|
|
145
|
+
[theme.breakpoints.down('sm')]: {
|
|
146
|
+
boxShadow: 'none',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
childs: {
|
|
150
|
+
marginLeft: 12,
|
|
151
|
+
marginRight: 12,
|
|
152
|
+
whiteSpace: 'nowrap',
|
|
153
|
+
overflow: 'hidden',
|
|
154
|
+
textOverflow: 'ellipsis',
|
|
155
|
+
},
|
|
156
|
+
fillMobileOnly: {
|
|
157
|
+
[theme.breakpoints.up('md')]: {
|
|
158
|
+
display: 'none',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
dividerFillMobileOnly: {
|
|
162
|
+
[theme.breakpoints.up('md')]: {
|
|
163
|
+
visibility: 'hidden',
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
logoContainer: {
|
|
167
|
+
position: 'absolute',
|
|
168
|
+
top: 0,
|
|
169
|
+
left: 0,
|
|
170
|
+
right: 0,
|
|
171
|
+
paddingTop: 8,
|
|
172
|
+
paddingBottom: 8,
|
|
173
|
+
},
|
|
174
|
+
logoInnerContainer: {
|
|
175
|
+
minHeight,
|
|
176
|
+
display: 'flex',
|
|
177
|
+
alignItems: 'center',
|
|
178
|
+
[theme.breakpoints.up('md')]: {
|
|
179
|
+
display: 'none',
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
backButton: {
|
|
183
|
+
background: theme.palette.background.default,
|
|
184
|
+
color: theme.palette.text.primary,
|
|
185
|
+
'&:hover': {
|
|
186
|
+
background: theme.palette.background.highlight,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
sheetShellActionsFullPage: {
|
|
190
|
+
'& * > a, & * > button': {
|
|
191
|
+
pointerEvents: 'all',
|
|
192
|
+
},
|
|
193
|
+
'& * > button': {
|
|
194
|
+
boxShadow: theme.shadows[3],
|
|
195
|
+
},
|
|
196
|
+
[theme.breakpoints.up('md')]: {
|
|
197
|
+
position: 'fixed',
|
|
198
|
+
top: `calc(${theme.page.headerInnerHeight.md} + ${theme.spacings.xxs})`,
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
sheetShellActionsNoButtonShadow: {
|
|
202
|
+
[theme.breakpoints.down('sm')]: {
|
|
203
|
+
'& * > button': {
|
|
204
|
+
boxShadow: 'none',
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
{ name: 'AppShellHeader' },
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
export default function AppShellHeader(props: AppShellHeaderProps) {
|
|
213
|
+
const {
|
|
214
|
+
children,
|
|
215
|
+
logo,
|
|
216
|
+
divider,
|
|
217
|
+
primary = null,
|
|
218
|
+
secondary = null,
|
|
219
|
+
hideClose,
|
|
220
|
+
scrollY,
|
|
221
|
+
additional,
|
|
222
|
+
dragIndicator,
|
|
223
|
+
scrolled,
|
|
224
|
+
backFallbackHref,
|
|
225
|
+
backFallbackTitle,
|
|
226
|
+
fill = 'both',
|
|
227
|
+
sheet,
|
|
228
|
+
} = props
|
|
229
|
+
const router = usePageRouter()
|
|
230
|
+
const { closeSteps, backSteps } = usePageContext()
|
|
231
|
+
const classes = useStyles(props)
|
|
232
|
+
|
|
233
|
+
const { titleRef, contentHeaderRef } = useAppShellHeaderContext()
|
|
234
|
+
|
|
235
|
+
const noChildren = typeof children === 'undefined' || !children
|
|
236
|
+
|
|
237
|
+
const fillMobileOnly = fill === 'mobile-only'
|
|
238
|
+
|
|
239
|
+
const sheetHeaderHeight = useMotionValue<number>(0)
|
|
240
|
+
const titleOffset = useMotionValue<number>(100)
|
|
241
|
+
const titleHeight = useMotionValue<number>(100)
|
|
242
|
+
|
|
243
|
+
const { href: historyHref, onClick: historyOnClick } = useHistoryLink({
|
|
244
|
+
href: backFallbackHref ?? '',
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
const setOffset = useCallback(
|
|
248
|
+
(offsetTop: number, offsetParent: Element | null, clientHeight: number) => {
|
|
249
|
+
titleHeight.set(clientHeight)
|
|
250
|
+
titleOffset.set(offsetTop)
|
|
251
|
+
},
|
|
252
|
+
[titleHeight, titleOffset],
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
if (titleRef.current) {
|
|
256
|
+
setOffset(
|
|
257
|
+
titleRef.current.offsetTop,
|
|
258
|
+
titleRef.current.offsetParent,
|
|
259
|
+
titleRef.current.clientHeight,
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Measure the title sizes so we can animate the opacity
|
|
264
|
+
useEffect(() => {
|
|
265
|
+
if (!titleRef.current) return () => {}
|
|
266
|
+
|
|
267
|
+
const ro = new ResizeObserver(([entry]) => {
|
|
268
|
+
const { offsetTop, offsetParent, clientHeight } = entry.target as HTMLDivElement
|
|
269
|
+
|
|
270
|
+
setOffset(offsetTop, offsetParent, clientHeight)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
ro.observe(titleRef.current)
|
|
274
|
+
return () => ro.disconnect()
|
|
275
|
+
}, [setOffset, titleHeight, titleOffset, titleRef])
|
|
276
|
+
|
|
277
|
+
// Measure the sheetHeight sizes so we can animate the opacity
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (!contentHeaderRef.current) return () => {}
|
|
280
|
+
|
|
281
|
+
const ro = new ResizeObserver(([entry]) =>
|
|
282
|
+
sheetHeaderHeight.set((entry.target as HTMLDivElement).clientHeight),
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
ro.observe(contentHeaderRef.current)
|
|
286
|
+
|
|
287
|
+
return () => ro.disconnect()
|
|
288
|
+
}, [contentHeaderRef, sheetHeaderHeight])
|
|
289
|
+
|
|
290
|
+
const opacityTitle = useTransform(
|
|
291
|
+
[scrollY, sheetHeaderHeight, titleOffset, titleHeight] as MotionValue[],
|
|
292
|
+
([scrollYV, sheetHeaderHeightV, titleOffsetV, titleHeigthV]: number[]) =>
|
|
293
|
+
Math.min(
|
|
294
|
+
Math.max(
|
|
295
|
+
0,
|
|
296
|
+
scrolled
|
|
297
|
+
? 1
|
|
298
|
+
: (scrollYV - Math.max(titleOffsetV, 80) + sheetHeaderHeightV) / titleHeigthV,
|
|
299
|
+
),
|
|
300
|
+
1,
|
|
301
|
+
),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
const pointerEvents = useTransform(opacityTitle, (o) => (o < 0.2 ? 'none' : 'all'))
|
|
305
|
+
const opacityLogo = useTransform(opacityTitle, [0, 1], [1, fillMobileOnly && primary ? 1 : 0])
|
|
306
|
+
const pointerEventsLogo = useTransform(opacityLogo, (o) => (o < 0.2 ? 'none' : 'all'))
|
|
307
|
+
|
|
308
|
+
const close =
|
|
309
|
+
!hideClose &&
|
|
310
|
+
(closeSteps > 0 ? (
|
|
311
|
+
<Fab
|
|
312
|
+
size='small'
|
|
313
|
+
type='button'
|
|
314
|
+
classes={{ root: classes.fab }}
|
|
315
|
+
onClick={() => router.go(closeSteps * -1)}
|
|
316
|
+
>
|
|
317
|
+
<SvgImage src={iconClose} mobileSize={20} size={20} alt='Close overlay' loading='eager' />
|
|
318
|
+
</Fab>
|
|
319
|
+
) : (
|
|
320
|
+
<PageLink href='/' passHref>
|
|
321
|
+
<Fab size='small' classes={{ root: classes.fab }}>
|
|
322
|
+
<SvgImage src={iconClose} alt='Close overlay' size={20} mobileSize={20} loading='eager' />
|
|
323
|
+
</Fab>
|
|
324
|
+
</PageLink>
|
|
325
|
+
))
|
|
326
|
+
|
|
327
|
+
const backIcon = (
|
|
328
|
+
<SvgImage src={iconChevronLeft} alt='chevron back' loading='eager' size={26} mobileSize={30} />
|
|
329
|
+
)
|
|
330
|
+
let back = backSteps > 0 && (
|
|
331
|
+
<Button
|
|
332
|
+
onClick={() => router.back()}
|
|
333
|
+
variant='pill-link'
|
|
334
|
+
className={classes.backButton}
|
|
335
|
+
startIcon={backIcon}
|
|
336
|
+
>
|
|
337
|
+
{historyOnClick ? backFallbackTitle : 'Back'}
|
|
338
|
+
</Button>
|
|
339
|
+
)
|
|
340
|
+
if (!back && backFallbackHref) {
|
|
341
|
+
back = (
|
|
342
|
+
<PageLink href={backFallbackHref} passHref>
|
|
343
|
+
<Button variant='pill-link' className={classes.backButton} startIcon={backIcon}>
|
|
344
|
+
{backFallbackTitle ?? 'Back'}
|
|
345
|
+
</Button>
|
|
346
|
+
</PageLink>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let leftAction: React.ReactNode = secondary ?? back
|
|
351
|
+
const rightAction: React.ReactNode = primary ?? close
|
|
352
|
+
if (rightAction !== close && !leftAction) leftAction = close
|
|
353
|
+
if (!leftAction) leftAction = <div />
|
|
354
|
+
|
|
355
|
+
const showDivider = children || (fillMobileOnly && primary)
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
<div
|
|
359
|
+
className={clsx(
|
|
360
|
+
classes.sheetHeaderContainer,
|
|
361
|
+
noChildren && !primary && classes.sheetHeaderNoTitle,
|
|
362
|
+
fillMobileOnly && classes.sheetHeaderFillMobileOnly,
|
|
363
|
+
sheet && classes.sheetHeaderContainerSheetShell,
|
|
364
|
+
)}
|
|
365
|
+
>
|
|
366
|
+
<div
|
|
367
|
+
className={clsx(
|
|
368
|
+
classes?.sheetHeader,
|
|
369
|
+
sheet && classes.sheetHeaderSheetShell,
|
|
370
|
+
scrolled && classes?.sheetHeaderScrolled,
|
|
371
|
+
noChildren && !primary && classes.sheetHeaderNoTitle,
|
|
372
|
+
fillMobileOnly && noChildren && classes.sheetHeaderNoTitleFillMobileOnly,
|
|
373
|
+
fillMobileOnly && classes.sheetHeaderFillMobileOnly,
|
|
374
|
+
)}
|
|
375
|
+
ref={contentHeaderRef}
|
|
376
|
+
>
|
|
377
|
+
<div className={classes.logoContainer}>
|
|
378
|
+
{logo && (
|
|
379
|
+
<m.div
|
|
380
|
+
style={{
|
|
381
|
+
opacity: opacityLogo,
|
|
382
|
+
pointerEvents: pointerEventsLogo,
|
|
383
|
+
}}
|
|
384
|
+
className={classes.logoInnerContainer}
|
|
385
|
+
>
|
|
386
|
+
{logo}
|
|
387
|
+
</m.div>
|
|
388
|
+
)}
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
{dragIndicator}
|
|
392
|
+
|
|
393
|
+
<div
|
|
394
|
+
className={clsx(
|
|
395
|
+
classes.sheetHeaderActions,
|
|
396
|
+
(noChildren || fillMobileOnly) && classes.sheetShellActionsFullPage,
|
|
397
|
+
fillMobileOnly && showDivider && classes.sheetShellActionsNoButtonShadow,
|
|
398
|
+
)}
|
|
399
|
+
>
|
|
400
|
+
{leftAction && <div>{leftAction}</div>}
|
|
401
|
+
<div className={classes.innerContainer}>
|
|
402
|
+
{children && (
|
|
403
|
+
<m.div
|
|
404
|
+
style={{ opacity: opacityTitle, pointerEvents }}
|
|
405
|
+
className={clsx(classes.childs, fillMobileOnly && classes.fillMobileOnly)}
|
|
406
|
+
>
|
|
407
|
+
{children}
|
|
408
|
+
</m.div>
|
|
409
|
+
)}
|
|
410
|
+
</div>
|
|
411
|
+
<div className={classes?.sheetHeaderActionRight}>{rightAction}</div>
|
|
412
|
+
</div>
|
|
413
|
+
{additional && (
|
|
414
|
+
<div className={clsx(fillMobileOnly && classes.fillMobileOnly)}>
|
|
415
|
+
<>{additional}</>
|
|
416
|
+
</div>
|
|
417
|
+
)}
|
|
418
|
+
</div>
|
|
419
|
+
{showDivider &&
|
|
420
|
+
(divider ?? (
|
|
421
|
+
<m.div
|
|
422
|
+
className={clsx(
|
|
423
|
+
classes.dividerSpacer,
|
|
424
|
+
classes.divider,
|
|
425
|
+
fillMobileOnly && classes.dividerFillMobileOnly,
|
|
426
|
+
)}
|
|
427
|
+
style={{ opacity: opacityTitle }}
|
|
428
|
+
/>
|
|
429
|
+
))}
|
|
430
|
+
{!showDivider && <div className={classes.dividerSpacer} />}
|
|
431
|
+
</div>
|
|
432
|
+
)
|
|
433
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useRef } from 'react'
|
|
2
|
+
import appShellHeaderContext, {
|
|
3
|
+
AppShellHeaderContext,
|
|
4
|
+
} from '../AppShellHeader/appShellHeaderContext'
|
|
5
|
+
|
|
6
|
+
type AppShellProviderProps = {
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function AppShellProvider(props: AppShellProviderProps) {
|
|
11
|
+
const { children } = props
|
|
12
|
+
const context: AppShellHeaderContext = {
|
|
13
|
+
titleRef: useRef<HTMLDivElement>(null),
|
|
14
|
+
contentHeaderRef: useRef<HTMLDivElement>(null),
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return <appShellHeaderContext.Provider value={context}>{children}</appShellHeaderContext.Provider>
|
|
18
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Container, makeStyles, Theme } from '@material-ui/core'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
import { useMotionValue } from 'framer-motion'
|
|
4
|
+
import React, { useEffect } from 'react'
|
|
5
|
+
import { useMotionValueValue } from '../../../framer-utils'
|
|
6
|
+
import { UseStyles } from '../../Styles'
|
|
7
|
+
import useAppShellHeaderContext from '../AppShellHeader/useAppShellHeaderContext'
|
|
8
|
+
|
|
9
|
+
const useStyles = makeStyles(
|
|
10
|
+
(theme: Theme) => ({
|
|
11
|
+
root: {
|
|
12
|
+
position: 'sticky',
|
|
13
|
+
zIndex: 96,
|
|
14
|
+
},
|
|
15
|
+
fillMobileOnly: {
|
|
16
|
+
[theme.breakpoints.up('md')]: {
|
|
17
|
+
top: `${theme.page.vertical} !important`,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
{
|
|
22
|
+
name: 'AppShellSticky',
|
|
23
|
+
},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
type AppShellStickyBaseProps = {
|
|
27
|
+
children: React.ReactNode
|
|
28
|
+
headerFill?: 'mobile-only' | 'both'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type AppShellStickyProps = AppShellStickyBaseProps & UseStyles<typeof useStyles>
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
- makes the children sticky to the parent container
|
|
35
|
+
- determines top offset based on header height dynamically
|
|
36
|
+
*/
|
|
37
|
+
export default function AppShellSticky(props: AppShellStickyProps) {
|
|
38
|
+
const { children, headerFill = 'both' } = props
|
|
39
|
+
const classes = useStyles(props)
|
|
40
|
+
|
|
41
|
+
const { contentHeaderRef } = useAppShellHeaderContext()
|
|
42
|
+
const offsetTop = useMotionValue<number>(0)
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!contentHeaderRef?.current) return () => {}
|
|
46
|
+
|
|
47
|
+
const ro = new ResizeObserver(([entry]) =>
|
|
48
|
+
offsetTop.set(contentHeaderRef?.current?.clientHeight ?? 0),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
ro.observe(contentHeaderRef.current)
|
|
52
|
+
return () => ro.disconnect()
|
|
53
|
+
}, [contentHeaderRef, offsetTop])
|
|
54
|
+
|
|
55
|
+
const top = useMotionValueValue(offsetTop, (v) => v)
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Container
|
|
59
|
+
maxWidth={false}
|
|
60
|
+
className={clsx(classes.root, headerFill === 'mobile-only' && classes.fillMobileOnly)}
|
|
61
|
+
style={{ top }}
|
|
62
|
+
>
|
|
63
|
+
<>{children}</>
|
|
64
|
+
</Container>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { makeStyles, Theme, TypographyProps } from '@material-ui/core'
|
|
2
|
+
import clsx from 'clsx'
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { UseStyles } from '../../Styles'
|
|
5
|
+
import Title, { TitleProps } from '../../Title'
|
|
6
|
+
import useAppShellHeaderContext from '../AppShellHeader/useAppShellHeaderContext'
|
|
7
|
+
|
|
8
|
+
type AppShellTitleProps = {
|
|
9
|
+
children: React.ReactNode
|
|
10
|
+
bare?: boolean
|
|
11
|
+
variant?: TypographyProps['variant']
|
|
12
|
+
} & Pick<TitleProps, 'icon'> &
|
|
13
|
+
UseStyles<typeof useStyles>
|
|
14
|
+
|
|
15
|
+
const useStyles = makeStyles(
|
|
16
|
+
(theme: Theme) => ({
|
|
17
|
+
title: {},
|
|
18
|
+
margin: {
|
|
19
|
+
marginTop: theme.spacings.md,
|
|
20
|
+
marginBottom: theme.spacings.lg,
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
{
|
|
24
|
+
name: 'AppShellTitle',
|
|
25
|
+
},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
export default function AppShellTitle(props: AppShellTitleProps) {
|
|
29
|
+
const { children, icon, bare, variant } = props
|
|
30
|
+
const { titleRef } = useAppShellHeaderContext()
|
|
31
|
+
const classes = useStyles(props)
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<Title
|
|
35
|
+
ref={titleRef}
|
|
36
|
+
component='h2'
|
|
37
|
+
size='medium'
|
|
38
|
+
variant={variant}
|
|
39
|
+
icon={icon ?? undefined}
|
|
40
|
+
classes={{ container: clsx(classes.title, !bare && classes.margin) }}
|
|
41
|
+
>
|
|
42
|
+
{children}
|
|
43
|
+
</Title>
|
|
44
|
+
)
|
|
45
|
+
}
|