Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sticky donate link button #485

Open
wants to merge 2 commits into
base: wordpress
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion components/AppHeader/AppHeaderNav/AppHeaderNav.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Styles for AppHeaderNav.
*/

import { keyframes } from 'tss-react';
import { makeStyles } from 'tss-react/mui';
import { alpha, createTheme, Theme } from '@mui/material/styles';

export const appHeaderNavTheme = (theme: Theme) =>
Expand All @@ -13,11 +15,77 @@ export const appHeaderNavTheme = (theme: Theme) =>
text: {
color: theme.palette.primary.contrastText,
fontWeight: theme.typography.fontWeightBold,
'&:hover, &:focus': {
'&:hover, &:focus-visible': {
backgroundColor: alpha(theme.palette.primary.contrastText, 0.1)
}
}
}
}
}
});

export const appHeaderNavStyles = makeStyles()((theme) => {
const stickyOffsetY = theme.spacing(2);
const stickyOffsetX = theme.spacing(2);
const headerNavStickyConstantProps = `
--sticky-box-shadow: ${theme.shadows[4]};
--sticky-icon-button-scale: 2;
position: fixed;
top: 0;
right: 0;
padding-inline: ${stickyOffsetX};
padding-block: ${stickyOffsetY};
z-index: ${theme.zIndex.fab};
`;
const headerNavSticky = keyframes`
1% {
${headerNavStickyConstantProps}
--sticky-bg-opacity: 0;
translate: 0 calc((300% + ${stickyOffsetY} * 2) * -1);
}

100% {
${headerNavStickyConstantProps}
--sticky-bg-opacity: 1;
translate: 0 0;
}
`;

return {
root: {
'@property --sticky-bg-opacity': {
syntax: '"<number>"',
inherits: 'true',
initialValue: 0
}
},
sticky: {
'--sticky-bg-opacity': 0,
'--sticky-box-shadow': 'none',
'--sticky-icon-button-scale': 1,
isolation: 'isolate',
animation: `${headerNavSticky} ease both`,
'animation-timeline': 'view()',
'animation-range-start': 'exit 100px',
'animation-range-end': 'exit 400px',
'& > :not(.MuiIconButton-root)': {
boxShadow: 'var(--sticky-box-shadow)'
},
'& > .MuiIconButton-root': {
scale: 'var(--sticky-icon-button-scale)'
},
'&::before': {
pointerEvents: 'none',
content: "''",
opacity: 'var(--sticky-bg-opacity)',
position: 'absolute',
inset: '0 0 -200% -200%',
zIndex: -1,
backgroundImage: `radial-gradient(farthest-side at top right, ${alpha(
theme.palette.background.paper,
0.6
)} 30%, transparent)`
}
}
};
});
189 changes: 103 additions & 86 deletions components/AppHeader/AppHeaderNav/AppHeaderNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
IconButtonColors,
RootState
} from '@interfaces';
import { Fragment } from 'react';
import { useEffect, useState } from 'react';
import { useStore } from 'react-redux';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
Expand All @@ -18,7 +18,7 @@ import { FavoriteSharp } from '@mui/icons-material';
import { isLocalUrl } from '@lib/parse/url';
import { handleButtonClick } from '@lib/routing';
import { getAppDataMenu } from '@store/reducers';
import { appHeaderNavTheme } from './AppHeaderNav.styles';
import { appHeaderNavTheme, appHeaderNavStyles } from './AppHeaderNav.styles';

const iconComponentMap = new Map();
iconComponentMap.set('heart', FavoriteSharp);
Expand All @@ -31,93 +31,110 @@ const renderIcon = (icon: string) => {
export const AppHeaderNav = () => {
const store = useStore<RootState>();
const state = store.getState();
const [supportsAnimationTimeline, setSupportsAnimationTimeline] =
useState(false);
const headerNav = getAppDataMenu(state, 'headerNav');
const { classes, cx } = appHeaderNavStyles();
const menuItems = headerNav
?.filter((v): v is IButtonWithUrl => !!v.url)
.map(({ url, ...other }) => ({
...other,
url: new URL(url, 'https://theworld.org')
}))
.map(({ service, ...other }) => {
if (!service) return other;

return headerNav?.length ? (
<ThemeProvider theme={appHeaderNavTheme}>
{headerNav
.filter((v): v is IButtonWithUrl => !!v.url)
.map(({ url, ...other }) => ({
...other,
url: new URL(url, 'https://theworld.org')
}))
.map(({ service, ...other }) => {
if (!service) return other;
const servicesOptions = new Map([
[
'prx:give',
{
icon: 'heart',
color: 'secondary',
sticky: supportsAnimationTimeline
}
]
]);
const options = servicesOptions.get(service);

return {
...other,
...options,
service
};
});

const renderMenuItem = ({
key,
color,
icon,
name,
url,
sticky,
attributes,
itemLinkClass = ''
}) => (
<span className={cx({ [classes.sticky]: sticky })} key={key}>
<Button
sx={{ display: { xs: 'none', sm: 'inline-flex' } }}
component="a"
href={isLocalUrl(url.href) ? url.pathname || '/' : url.href}
onClick={handleButtonClick(url)}
variant={
/\bbtn-(text|link)\b/.test(itemLinkClass) ? 'text' : 'contained'
}
color={(color as ButtonColors) || 'primary'}
disableRipple
disableElevation
{...(icon && { startIcon: renderIcon(icon) })}
{...attributes}
>
{name}
</Button>
{icon ? (
<IconButton
sx={{ display: { sm: 'none', xs: 'inline-flex' } }}
component="a"
href={url.href}
onClick={handleButtonClick(url)}
aria-label={name}
color={(color as IconButtonColors) || 'default'}
size="small"
disableRipple
{...attributes}
>
{renderIcon(icon)}
</IconButton>
) : (
<Button
sx={{ display: { sm: 'none', xs: 'inline-flex' } }}
component="a"
href={url.href}
onClick={handleButtonClick(url)}
variant="text"
color={(color as ButtonColors) || 'primary'}
size="small"
disableRipple
disableElevation
{...attributes}
{...(icon && {
startIcon: renderIcon(icon)
})}
>
{name}
</Button>
)}
</span>
);

const servicesOptions = new Map([
[
'prx:give',
{
icon: 'heart',
color: 'secondary'
}
]
]);
const options = servicesOptions.get(service);
useEffect(() => {
setSupportsAnimationTimeline(CSS.supports('animation-timeline', 'view()'));
}, []);

return {
...options,
...other,
service
};
})
.map(
({ color, icon, name, url, key, attributes, itemLinkClass = '' }) => (
<Fragment key={key}>
<Button
sx={{ display: { xs: 'none', sm: 'inline-flex' } }}
component="a"
href={isLocalUrl(url.href) ? url.pathname || '/' : url.href}
onClick={handleButtonClick(url)}
variant={
/\bbtn-(text|link)\b/.test(itemLinkClass)
? 'text'
: 'contained'
}
color={(color as ButtonColors) || 'primary'}
disableRipple
disableElevation
{...(icon && { startIcon: renderIcon(icon) })}
{...attributes}
>
{name}
</Button>
{icon ? (
<IconButton
sx={{ display: { sm: 'none', xs: 'inline-flex' } }}
component="a"
href={url.href}
onClick={handleButtonClick(url)}
aria-label={name}
color={(color as IconButtonColors) || 'default'}
size="small"
disableRipple
{...attributes}
>
{renderIcon(icon)}
</IconButton>
) : (
<Button
sx={{ display: { sm: 'none', xs: 'inline-flex' } }}
component="a"
href={url.href}
onClick={handleButtonClick(url)}
variant="text"
color={(color as ButtonColors) || 'primary'}
size="small"
disableRipple
disableElevation
{...attributes}
{...(icon && {
startIcon: renderIcon(icon)
})}
>
{name}
</Button>
)}
</Fragment>
)
)}
if (!menuItems?.length) return null;

return (
<ThemeProvider theme={appHeaderNavTheme}>
{menuItems.map(renderMenuItem)}
</ThemeProvider>
) : null;
);
};
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4244,9 +4244,9 @@ camelcase@^6.2.0:
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==

caniuse-lite@^1.0.30001406, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001591:
version "1.0.30001660"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz"
integrity sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==
version "1.0.30001667"
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001667.tgz"
integrity sha512-7LTwJjcRkzKFmtqGsibMeuXmvFDfZq/nzIjnmgCGzKKRVzjD72selLDK1oPF/Oxzmt4fNcPvTDvGqSDG4tCALw==

capital-case@^1.0.4:
version "1.0.4"
Expand Down