Skip to content

Instantly share code, notes, and snippets.

@paulweezydesign
Created July 3, 2022 20:47
Show Gist options
  • Save paulweezydesign/81913ba7272f0f5dee143af84344200b to your computer and use it in GitHub Desktop.
Save paulweezydesign/81913ba7272f0f5dee143af84344200b to your computer and use it in GitHub Desktop.
import * as React from "react";
import { useRef } from "react";
import { motion, useCycle } from "framer-motion";
import { useDimensions } from "./use-dimensions";
import { MenuToggle } from "./MenuToggle";
import { Navigation } from "./Navigation";
const sidebar = {
open: (height = 1000) => ({
clipPath: `circle(${height * 2 + 200}px at 40px 40px)`,
transition: {
type: "spring",
stiffness: 20,
restDelta: 2
}
}),
closed: {
clipPath: "circle(30px at 40px 40px)",
transition: {
delay: 0.5,
type: "spring",
stiffness: 400,
damping: 40
}
}
};
export const Example = () => {
const [isOpen, toggleOpen] = useCycle(false, true);
const containerRef = useRef(null);
const { height } = useDimensions(containerRef);
return (
<motion.nav
initial={false}
animate={isOpen ? "open" : "closed"}
custom={height}
ref={containerRef}
>
<motion.div className="background" variants={sidebar} />
<Navigation />
<MenuToggle toggle={() => toggleOpen()} />
</motion.nav>
);
};
import * as React from "react";
import { render } from "react-dom";
import { Example } from "./Example";
import "./styles.css";
render(<Example />, document.getElementById("root"));
import * as React from "react";
import { motion } from "framer-motion";
const Path = props => (
<motion.path
fill="transparent"
strokeWidth="3"
stroke="hsl(0, 0%, 18%)"
strokeLinecap="round"
{...props}
/>
);
export const MenuToggle = ({ toggle }) => (
<button onClick={toggle}>
<svg width="23" height="23" viewBox="0 0 23 23">
<Path
variants={{
closed: { d: "M 2 2.5 L 20 2.5" },
open: { d: "M 3 16.5 L 17 2.5" }
}}
/>
<Path
d="M 2 9.423 L 20 9.423"
variants={{
closed: { opacity: 1 },
open: { opacity: 0 }
}}
transition={{ duration: 0.1 }}
/>
<Path
variants={{
closed: { d: "M 2 16.346 L 20 16.346" },
open: { d: "M 3 2.5 L 17 16.346" }
}}
/>
</svg>
</button>
);
import * as React from "react";
import { motion } from "framer-motion";
const Path = props => (
<motion.path
fill="transparent"
strokeWidth="3"
stroke="hsl(0, 0%, 18%)"
strokeLinecap="round"
{...props}
/>
);
export const MenuToggle = ({ toggle }) => (
<button onClick={toggle}>
<svg width="23" height="23" viewBox="0 0 23 23">
<Path
variants={{
closed: { d: "M 2 2.5 L 20 2.5" },
open: { d: "M 3 16.5 L 17 2.5" }
}}
/>
<Path
d="M 2 9.423 L 20 9.423"
variants={{
closed: { opacity: 1 },
open: { opacity: 0 }
}}
transition={{ duration: 0.1 }}
/>
<Path
variants={{
closed: { d: "M 2 16.346 L 20 16.346" },
open: { d: "M 3 2.5 L 17 16.346" }
}}
/>
</svg>
</button>
);
import * as React from "react";
import { motion } from "framer-motion";
import { MenuItem } from "./MenuItem";
const variants = {
open: {
transition: { staggerChildren: 0.07, delayChildren: 0.2 }
},
closed: {
transition: { staggerChildren: 0.05, staggerDirection: -1 }
}
};
export const Navigation = () => (
<motion.ul variants={variants}>
{itemIds.map(i => (
<MenuItem i={i} key={i} />
))}
</motion.ul>
);
const itemIds = [0, 1, 2, 3, 4];
body {
width: 100vw;
height: 100vh;
background: linear-gradient(180deg, #0055ff 0%, rgb(0, 153, 255) 100%);
overflow: hidden;
padding: 0;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
nav {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 300px;
}
.background {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 300px;
background: #fff;
}
button {
outline: none;
border: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
cursor: pointer;
position: absolute;
top: 18px;
left: 15px;
width: 50px;
height: 50px;
border-radius: 50%;
background: transparent;
}
ul,
li {
margin: 0;
padding: 0;
}
ul {
padding: 25px;
position: absolute;
top: 100px;
width: 230px;
}
li {
list-style: none;
margin-bottom: 20px;
display: flex;
align-items: center;
cursor: pointer;
}
.icon-placeholder {
width: 40px;
height: 40px;
border-radius: 50%;
flex: 40px 0;
margin-right: 20px;
}
.text-placeholder {
border-radius: 5px;
width: 200px;
height: 20px;
flex: 1;
}
.refresh {
padding: 10px;
position: absolute;
background: rgba(0, 0, 0, 0.4);
border-radius: 10px;
width: 20px;
height: 20px;
top: 10px;
right: 10px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
import { useEffect, useRef } from "react";
// Naive implementation - in reality would want to attach
// a window or resize listener. Also use state/layoutEffect instead of ref/effect
// if this is important to know on initial client render.
// It would be safer to return null for unmeasured states.
export const useDimensions = ref => {
const dimensions = useRef({ width: 0, height: 0 });
useEffect(() => {
dimensions.current.width = ref.current.offsetWidth;
dimensions.current.height = ref.current.offsetHeight;
}, []);
return dimensions.current;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment