♻️ refactor(print): make print-friendly pages

affected by mui/material-ui#46434
This commit is contained in:
2025-07-03 09:51:07 +02:00
parent cfa31a99a3
commit 76575ead15
6 changed files with 157 additions and 118 deletions
@@ -62,6 +62,9 @@ const CookieConsent = () => {
mb: theme.spacing(1), mb: theme.spacing(1),
p: theme.spacing(3), p: theme.spacing(3),
zIndex: 9999, zIndex: 9999,
'@media print': {
display: 'none',
},
})}> })}>
<Box> <Box>
<SectionSubheader id="cookie-consent-dialog-title" component="h2" variant="h6"> <SectionSubheader id="cookie-consent-dialog-title" component="h2" variant="h6">
@@ -32,10 +32,16 @@ const CurriculumItem = (props: CurriculumEntryProps) => {
// '& .MuiAccordionSummary-expandIconWrapper .MuiSvgIcon-root': { color: 'primary.dark' }, // use summary hover icon color // '& .MuiAccordionSummary-expandIconWrapper .MuiSvgIcon-root': { color: 'primary.dark' }, // use summary hover icon color
}, },
'&.Mui-expanded': { '&.Mui-expanded': {
bgcolor: 'background.paper', // use summary hover background
borderBottom: '1px solid', borderBottom: '1px solid',
borderTop: '1px solid', borderTop: '1px solid',
bgcolor: 'background.paper', // use summary hover background
elevation: 1, elevation: 1,
'@media print': {
bgcolor: 'background.default',
borderBottom: 0,
borderTop: 0,
elevation: 0,
},
// '& .MuiAccordionSummary-expandIconWrapper .MuiSvgIcon-root': { color: 'primary.dark' }, // use summary hover icon color // '& .MuiAccordionSummary-expandIconWrapper .MuiSvgIcon-root': { color: 'primary.dark' }, // use summary hover icon color
}, },
}}> }}>
@@ -48,6 +54,9 @@ const CurriculumItem = (props: CurriculumEntryProps) => {
}, },
'& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': { '& .MuiAccordionSummary-expandIconWrapper.Mui-expanded': {
transform: 'rotate(0deg)', transform: 'rotate(0deg)',
'@media print': {
transform: 'rotate(-90deg)',
},
}, },
'& .MuiAccordionSummary-content': { '& .MuiAccordionSummary-content': {
marginLeft: 2, marginLeft: 2,
+11 -1
View File
@@ -14,7 +14,17 @@ type FooterProps = {
const Footer: React.FC<FooterProps> = ({ showCookieConsent = true }) => { const Footer: React.FC<FooterProps> = ({ showCookieConsent = true }) => {
const year = new Date().getFullYear() const year = new Date().getFullYear()
return ( return (
<Box sx={{ bgcolor: 'background.paper', p: 6, display: 'flex', alignItems: 'center', flexDirection: 'column' }} component="footer"> <Box
id="footer"
component="footer"
sx={{
bgcolor: 'background.paper',
py: 6,
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
'@media print': { py: 3 },
}}>
<Stack direction="row" gap={2}> <Stack direction="row" gap={2}>
<LinkedInIconLink fontSize="large" href="https://www.linkedin.com/in/m-tartari/" /> <LinkedInIconLink fontSize="large" href="https://www.linkedin.com/in/m-tartari/" />
<GitHubIconLink fontSize="large" href="https://www.github.com/m-tartari/" /> <GitHubIconLink fontSize="large" href="https://www.github.com/m-tartari/" />
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import { ParallaxBanner, ParallaxProvider } from 'react-scroll-parallax' import { ParallaxBanner, ParallaxProvider } from 'react-scroll-parallax'
import { Box, Grid, IconButton, Typography, useTheme } from '@mui/material' import { Box, Grid, IconButton, Typography } from '@mui/material'
import { ExpandMore } from '@mui/icons-material' import { ExpandMore } from '@mui/icons-material'
import { useViewport } from 'components/utils/viewport/index.js' import { useViewport } from 'components/utils/viewport/index.js'
@@ -125,79 +125,91 @@ const Description = (props: Props) => {
} }
} }
const DesktopComponent = React.forwardRef<HTMLSelectElement, Props>((props, ref) => { const ProjectSpotlight = React.forwardRef<HTMLSelectElement, Props>((props, ref) => {
return ( const { width } = useViewport()
<section ref={ref}>
<ParallaxProvider>
<ParallaxBanner
layers={[
{
// translateY: [0, 30],
// scale: [1, 0.5, 'easeOutCubic'],
// shouldAlwaysCompleteAnimation: true,
image: props.image,
expanded: false,
speed: -20,
},
]}
style={{
// width: '150%',
// marginLeft: '-25%',
minHeight: '100vh',
position: 'relative',
height: '100vh',
overflow: 'hidden',
}}>
<Description {...props} />
{props.scrollTarget && (
<IconButton
size="large"
onClick={() => (props.scrollTarget as React.RefObject<HTMLSelectElement | null>).current?.scrollIntoView()}
sx={{ position: 'absolute', p: 1, bottom: 2, left: '50%', transform: 'translate(-50%, 0)' }}>
<ExpandMore fontSize="large" />
</IconButton>
)}
</ParallaxBanner>
</ParallaxProvider>
</section>
)
})
const MobileComponent = React.forwardRef<HTMLSelectElement, Props>((props, ref) => {
return ( return (
<Box component="section"> <Box ref={ref}>
<Box
ref={ref}
component="img"
sx={{
width: '100%',
height: '100%',
maxHeight: '60vh',
}}
src={props.mobileImage ?? props.image}
/>
<Box <Box
component="section"
sx={theme => ({ sx={theme => ({
mt: theme.spacing(-1.05), display: width < theme.breakpoints.values.md ? 'none' : 'inline',
borderTopStyle: 'solid', '@media print': { display: 'none' },
borderTopWidth: theme.spacing(0.5),
borderTopColor: theme.palette.primary.main,
p: `${theme.spacing(7)} ${theme.spacing(5)} ${theme.spacing(5.5)} ${theme.spacing(5)}`,
})}> })}>
<Typography variant="h4">{props.title}</Typography> <ParallaxProvider>
{props.caption} <ParallaxBanner
<Typography pt={1}>{props.children}</Typography> layers={[
{
// translateY: [0, 30],
// scale: [1, 0.5, 'easeOutCubic'],
// shouldAlwaysCompleteAnimation: true,
image: props.image,
expanded: false,
speed: -20,
},
]}
style={{
// width: '150%',
// marginLeft: '-25%',
minHeight: '100vh',
position: 'relative',
height: '100vh',
overflow: 'hidden',
}}>
<Description {...props} />
{props.scrollTarget && (
<IconButton
size="large"
onClick={() => (props.scrollTarget as React.RefObject<HTMLSelectElement | null>).current?.scrollIntoView()}
sx={{ position: 'absolute', p: 1, bottom: 2, left: '50%', transform: 'translate(-50%, 0)' }}>
<ExpandMore fontSize="large" />
</IconButton>
)}
</ParallaxBanner>
</ParallaxProvider>
</Box>
<Box
component="section"
sx={theme => ({
display: width >= theme.breakpoints.values.md ? 'none' : 'inline',
'@media print': {
display: 'inline',
height: '100%',
},
})}>
<Box
component="img"
sx={{
width: '100%',
height: '100%',
maxHeight: '60vh',
'@media print': {
maxHeight: '100%',
'-webkit-print-color-adjust': 'exact !important',
'print-color-adjust': 'exact !important',
},
}}
src={props.mobileImage ?? props.image}
/>
<Box
sx={theme => ({
mt: theme.spacing(-1.05),
borderTopStyle: 'solid',
borderTopWidth: theme.spacing(0.5),
borderTopColor: theme.palette.primary.main,
p: `${theme.spacing(7)} ${theme.spacing(5)} ${theme.spacing(5.5)} ${theme.spacing(5)}`,
'@media print': {
backgroundColor: 'background.default',
},
})}>
<Typography variant="h4">{props.title}</Typography>
{props.caption}
<Typography pt={1}>{props.children}</Typography>
</Box>
</Box> </Box>
</Box> </Box>
) )
}) })
const ProjectSpotlight = React.forwardRef<HTMLSelectElement, Props>((props, ref) => {
const { width } = useViewport()
const theme = useTheme()
return width < theme.breakpoints.values.md ? <MobileComponent {...props} ref={ref} /> : <DesktopComponent {...props} ref={ref} />
})
export default ProjectSpotlight export default ProjectSpotlight
+47 -48
View File
@@ -15,22 +15,30 @@ import {
Stack, Stack,
Toolbar, Toolbar,
Typography, Typography,
TypographyProps,
styled, styled,
useScrollTrigger, useScrollTrigger,
useTheme, useTheme,
} from '@mui/material' } from '@mui/material'
import MenuIcon from '@mui/icons-material/Menu' import MenuIcon from '@mui/icons-material/Menu'
const AppBarButton = styled(Button<typeof RouterLink>)(({ theme }) => ({ const TypographyLink = styled(Typography)(({ theme }) => ({
textTransform: 'none', role: 'link',
color: theme.palette.mode === 'dark' ? 'inherit' : 'text.secondary', textDecoration: 'none',
':hover': { ...(theme.palette.mode === 'dark' && {
backgroundColor: 'transparent', color: 'inherit',
...(theme.palette.mode === 'dark' && { ':hover': {
color: theme.palette.primary.main, color: theme.palette.primary.main,
}), },
}, }),
...(theme.palette.mode === 'light' && {
color: theme.palette.text.primary,
':hover': {
color: theme.palette.primary.dark,
},
}),
})) }))
const AppBarLink = (props: TypographyProps & { to: string }) => <TypographyLink component={RouterLink} variant="h6" {...props} />
const DrawerButton = styled(ListItemButton<typeof RouterLink>)(({ theme }) => ({ const DrawerButton = styled(ListItemButton<typeof RouterLink>)(({ theme }) => ({
textTransform: 'none', textTransform: 'none',
color: 'inherit', color: 'inherit',
@@ -102,6 +110,7 @@ const CustomToolbar: React.FC<CustomToolbarProps> = ({ drawerWidth = 240, window
{...props} {...props}
sx={{ sx={{
backgroundColor: trigger ? 'background.paper' : 'transparent', backgroundColor: trigger ? 'background.paper' : 'transparent',
'@media print': { backgroundColor: 'background.default' },
...(theme.palette.mode === 'light' && { ...(theme.palette.mode === 'light' && {
backgroundColor: 'hsla(0, 0%, 100%, 0.75)', backgroundColor: 'hsla(0, 0%, 100%, 0.75)',
backdropFilter: 'blur(8px)', backdropFilter: 'blur(8px)',
@@ -115,6 +124,7 @@ const CustomToolbar: React.FC<CustomToolbarProps> = ({ drawerWidth = 240, window
<Toolbar <Toolbar
sx={{ sx={{
display: 'flex', display: 'flex',
alignItems: 'center',
// maxWidth: '100%', // maxWidth: '100%',
// [theme.breakpoints.up('md')]: { width: theme => theme.breakpoints.values.lg, mx: 'auto' }, // [theme.breakpoints.up('md')]: { width: theme => theme.breakpoints.values.lg, mx: 'auto' },
}}> }}>
@@ -123,58 +133,47 @@ const CustomToolbar: React.FC<CustomToolbarProps> = ({ drawerWidth = 240, window
aria-label="open drawer" aria-label="open drawer"
edge="start" edge="start"
onClick={handleDrawerToggle} onClick={handleDrawerToggle}
sx={{ mr: 2, display: { md: 'none' }, position: 'absolute', backgroundColor: 'primary' }}> sx={{
mr: 2,
display: { md: 'none' },
position: 'absolute',
backgroundColor: 'primary',
'@media print': { display: 'none' },
}}>
<MenuIcon /> <MenuIcon />
</IconButton> </IconButton>
<AppBarButton
component={RouterLink} <AppBarLink
to="/" to="/"
disableRipple
sx={theme => ({ sx={theme => ({
color: theme.palette.text.primary,
[theme.breakpoints.down('md')]: { mx: 'auto' }, [theme.breakpoints.down('md')]: { mx: 'auto' },
[theme.breakpoints.up('md')]: { mr: 'auto' }, [theme.breakpoints.up('md')]: { mr: 'auto' },
})}> })}>
<Typography variant="h6">Michele Tartari</Typography> Michele Tartari
</AppBarButton> </AppBarLink>
<Stack component="div" direction="row" spacing={2} sx={{ display: { xs: 'none', md: 'flex' } }}> <Stack component="div" direction="row" spacing={2} sx={{ display: { xs: 'none', md: 'flex', alignItems: 'center' } }}>
<AppBarButton component={RouterLink} to="/" disableRipple> <AppBarLink to="/">Home</AppBarLink>
<Typography variant="h6">Home</Typography> <AppBarLink to="/projects">Projects</AppBarLink>
</AppBarButton> <AppBarLink to="/curriculum">Curriculum</AppBarLink>
<AppBarButton component={RouterLink} to="/projects" disableRipple> <Button component={RouterLink} to="/contacts" color="primary" variant="contained" sx={{ textTransform: 'none' }}>
<Typography variant="h6">Projects</Typography>
</AppBarButton>
<AppBarButton component={RouterLink} to="/curriculum" disableRipple>
<Typography variant="h6">Curriculum</Typography>
</AppBarButton>
<Button
component={RouterLink}
to="/contacts"
color="primary"
variant="contained"
sx={{
textTransform: 'none',
}}>
<Typography variant="h6">Hire me</Typography> <Typography variant="h6">Hire me</Typography>
</Button> </Button>
</Stack> </Stack>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<nav> <Drawer
<Drawer variant="temporary"
variant="temporary" open={mobileOpen}
open={mobileOpen} onClose={handleDrawerToggle}
onClose={handleDrawerToggle} ModalProps={{
ModalProps={{ keepMounted: true, // Better open performance on mobile.
keepMounted: true, // Better open performance on mobile. }}
}} sx={{
sx={{ display: { xs: 'block', md: 'none' },
display: { xs: 'block', md: 'none' }, '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth },
'& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth }, }}>
}}> {drawer}
{drawer} </Drawer>
</Drawer>
</nav>
</> </>
) )
} }
+8 -2
View File
@@ -19,11 +19,17 @@ const Page: React.FC<PageProps> = ({ title, children, showCookieConsent, ...prop
}, [title]) }, [title])
return ( return (
<Box sx={{ flexGrow: 1 }} aria-label="page"> <Box
aria-label="page"
sx={{
'@media print': { backgroundColor: 'background.default' },
flexGrow: 1,
}}>
<Toolbar /> <Toolbar />
{title && ( {title && (
<> <>
<Typography <Typography
id="page-title"
variant="h1" variant="h1"
aria-label="page-title" aria-label="page-title"
textAlign="center" textAlign="center"
@@ -53,8 +59,8 @@ const Page: React.FC<PageProps> = ({ title, children, showCookieConsent, ...prop
</> </>
)} )}
<Container <Container
id="page-content"
component="main" component="main"
maxWidth={false}
sx={{ sx={{
mt: { xs: 8, md: 12 }, mt: { xs: 8, md: 12 },
mb: 4, mb: 4,