Skip to content

Instantly share code, notes, and snippets.

@silviogutierrez
Created May 30, 2020 15:39
Show Gist options
  • Save silviogutierrez/ad2049cf8feee0749d85fccb1854d675 to your computer and use it in GitHub Desktop.
Save silviogutierrez/ad2049cf8feee0749d85fccb1854d675 to your computer and use it in GitHub Desktop.
import * as React from "react";
import {Link} from "react-router";
import {motion} from "framer-motion";
import {reverse} from "@client/generated";
import * as style from "@client/style";
import {Icon} from "@client/components/Icon";
import {Platform, Icon as IconLiterals} from "@client/constants";
import {Profile} from "@client/models";
const variants = {
menu: {
open: {
opacity: 1,
display: "block",
transition: {duration: 0.3, delayChildren: 0.1},
},
closed: {
opacity: 0,
transition: {delay: 1, delayChildren: 0.3},
transitionEnd: {
display: "none",
},
},
},
logo: {
open: {
opacity: 1,
transition: {
when: "beforeChildren",
delay: 0.3,
duration: 0.1,
staggerChildren: 0.5,
},
},
closed: {
opacity: 0,
},
},
menuContent: {
open: (height = 1000) => ({
clipPath: `circle(${
height * 2 + 200
}px at 20px calc(env(safe-area-inset-top) + 20px))`,
transition: {
type: "spring",
stiffness: 40,
restDelta: 2,
},
}),
closed: {
clipPath: "circle(0px at 20px calc(env(safe-area-inset-top) + 20px))",
transition: {
delay: 0.5,
type: "spring",
stiffness: 400,
damping: 40,
},
},
},
menuItems: {
open: {
transition: {staggerChildren: 0.07, delayChildren: 0.4},
},
closed: {
transition: {staggerChildren: 0.05, staggerDirection: -1},
},
},
menuItemComplex: {
open: {
y: 0,
opacity: 1,
transition: {
y: {stiffness: 1000, velocity: -100},
},
},
closed: {
y: 50,
opacity: 0,
transition: {
y: {stiffness: 1000},
},
},
},
menuItemSimple: {
open: {
opacity: 1,
},
closed: {
opacity: 0,
},
},
} as const;
const colors = ["#EF8165", "#50BD9C", "#EAAE63", "#9582C4", "#488EA5"];
namespace styles {
export const menu = style.style({
zIndex: style.zIndexes.menu,
position: "absolute",
width: "100vw",
height: "100vh",
backgroundColor: "rgba(0,0,0,0.3)",
});
export const isMenuOpen = style.style({});
export const menuContent = style.style({
paddingTop: ["0px", "calc(env(safe-area-inset-top))"],
minWidth: 250,
width: "20%",
backgroundColor: "#FFF",
height: "100%",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
overflow: "hidden",
});
export const logo = style.style({
height: 27,
paddingTop: 10,
paddingRight: 10,
alignSelf: "flex-end",
});
export const menuItems = style.style(style.csstips.verticallySpaced(15), {
paddingLeft: 25,
paddingTop: 20,
paddingBottom: 20,
flex: 1,
overflowY: "scroll",
// We want the scrollbar to appear on the items and not the whole menu.
// And all the way to the right.
width: "100%",
// But we want the children not to take up the full width so that on
// very large screens the scaling isn't huge. Size them to fit content.
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
});
export const menuItem = style.style({});
export const menuItemLink = style.style({
display: "flex",
alignItems: "center",
cursor: "pointer",
});
export const menuItemIcon = style.style({
width: 40,
height: 40,
borderRadius: 10,
border: "1.5px solid currentColor",
flex: "40px 0",
marginRight: 10,
display: "flex",
justifyContent: "center",
alignItems: "center",
});
export const menuItemTitle = style.style({
flex: 1,
textAlign: "left",
fontWeight: 500,
});
}
type MenuLink = {
icon: IconLiterals;
link: string;
title: string;
showIf?: (profile: Profile | null, platform: Platform) => boolean;
external?: boolean;
};
const homePage = {
icon: "home",
link: reverse("home_page"),
title: "Home Page",
showIf: () => window.cordova == null,
external: true,
} as const;
const AUTHENTICATED_LINKS: MenuLink[] = [
{
icon: "custom-0283-contacts",
link: "/journal/",
title: "Journal",
},
{
icon: "0393-calendar-31",
link: "/planner/",
title: "Planner",
},
{
icon: "leaf",
link: "/foods/",
title: "My Foods",
},
{
icon: "star",
link: "/recipes/",
title: "Recipes",
},
{
icon: "user",
link: "/profile/",
title: "Profile",
},
{
icon: "bar-chart-o",
link: "/reports/",
title: "Reports",
},
{
icon: "0291-users",
link: "/manager/",
title: "Manager",
showIf: (profile) => profile != null && profile.is_manager,
},
{
icon: "send",
link: "/invite/",
title: "Invite",
},
{
icon: "envelope-o",
link: "/contact/",
title: "Contact",
},
homePage,
{
icon: "custom-0767-exit-right",
link: "/logout/",
title: "Logout",
},
];
export const ANONYMOUS_LINKS: MenuLink[] = [
{
icon: "custom-0763-enter-right",
link: "/login/",
title: "Login",
},
{
icon: "pencil-square-o",
link: "/register/",
title: "Register",
},
{
icon: "calculator",
link: "/calculator/",
title: "Calculator",
},
{
icon: "envelope-o",
link: "/contact/",
title: "Contact",
},
homePage,
];
interface Props {
isAuthenticated: boolean;
toggleMenu: () => void;
isMenuOpen: boolean;
profile: Profile | null;
platform: Platform;
}
export const Menu = (props: Props) => {
const handleClick = () => props.toggleMenu();
const swallowClick = (event: React.MouseEvent<HTMLDivElement>) =>
event.stopPropagation();
return (
<motion.div
initial={false}
animate={props.isMenuOpen === true ? "open" : "closed"}
variants={variants.menu}
className={style.classes(
styles.menu,
props.isMenuOpen && styles.isMenuOpen,
)}
onClick={handleClick}
>
<motion.div
className={styles.menuContent}
variants={variants.menuContent}
onClick={swallowClick}
>
<Logo isMenuOpen={props.isMenuOpen} />
<motion.ul className={styles.menuItems} variants={variants.menuItems}>
{(props.isAuthenticated ? AUTHENTICATED_LINKS : ANONYMOUS_LINKS)
.filter(
(link) =>
link.showIf == null ||
link.showIf(props.profile, props.platform) === true,
)
.map((item, index) => {
const i = index % colors.length;
const itemStyle = {
color: colors[i],
// backgroundColor: bgs[i],
};
const content = (
<>
<div
style={itemStyle}
className={styles.menuItemIcon}
>
<Icon large="relative" icon={item.icon} />
</div>
<div className={styles.menuItemTitle}>
{item.title}
</div>
</>
);
const commonProps = {
className: styles.menuItemLink,
onClick: handleClick,
};
const link =
item.external === true ? (
<a href={item.link} {...commonProps}>
{content}
</a>
) : (
<Link to={item.link} {...commonProps}>
{content}
</Link>
);
return (
<motion.li
key={item.link}
className={styles.menuItem}
variants={
variants.menuItemComplex
}
whileHover={{scale: 1.1}}
whileTap={{scale: 0.95}}
>
{link}
</motion.li>
);
})}
</motion.ul>
</motion.div>
</motion.div>
);
};
interface LogoProps {
isMenuOpen: boolean;
}
const Logo = (props: LogoProps) => (
<motion.svg
initial={false}
className={styles.logo}
animate={props.isMenuOpen === true ? "open" : "closed"}
variants={variants.logo}
viewBox="0 0 181 61"
>
<motion.path
initial="closed"
d="M113.1,26 C113.386288,27.4487218 113.52035,28.9234021 113.5,30.4 C113.472355,42.7435186 103.443556,52.7275127 91.1000375,52.6998882 C78.7565188,52.6722638 68.7725081,42.6434817 68.8001119,30.299963 C68.8277158,17.9564442 78.8564812,7.97241675 91.2,8 C96.1576181,8.02719484 100.96977,9.67807899 104.9,12.7 L110.6,7 C98.8399439,-2.61729776 81.7710663,-2.02053704 70.711382,8.39458264 C59.6516976,18.8097023 58.0322912,35.8120618 66.9269572,48.1277531 C75.8216232,60.4434445 92.470938,64.2517275 105.83509,57.0274096 C119.199243,49.8030917 125.132224,33.7873984 119.7,19.6 L113.1,26 Z"
variants={{
open: {
rotate: 0,
},
closed: {
rotate: 180,
originX: "center",
originY: "center",
},
}}
fill="#373A3C"
/>
<motion.path
initial="closed"
variants={{
open: {
scale: 1,
},
closed: {
originX: "center",
originY: "center",
scale: 0,
},
}}
d="M113.1,9.3 L92.6,29.7 L88,25.1 L82.1,31 L92.6,41.5 L118.1,16.2 C116.782176,13.6663504 115.097326,11.3412568 113.1,9.3 Z"
id="Path"
fill="#F57E5F"
/>
<polygon
fill="#373A3C"
points="180.9 0.3 159.2 41.1 159.2 60.2 151.1 60.2 151.1 41.1 129.6 0.1 138.8 0.1 155.2 29.3 171.7 0.2"
/>
<path
d="M19.9,60.9 C6.2,60.9 0,51 0,41.1 L8.1,41.1 C8.1,51.8 17,52.9 19.7,52.9 C25.538796,52.9171573 30.3069981,48.2380805 30.4,42.4 L30.4,0.3 L38.5,0.3 L38.5,41.6 C38.6889161,46.5507122 36.8949236,51.372218 33.5158418,54.9953447 C30.1367599,58.6184713 25.451852,60.7437865 20.5,60.9 L19.9,60.9 Z"
fill="#373A3C"
/>
</motion.svg>
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment