Skip to content

Instantly share code, notes, and snippets.

@yaboynoem
Created July 6, 2022 23:21
Show Gist options
  • Save yaboynoem/cfbc17a2939f24da8cc30b863ff0f677 to your computer and use it in GitHub Desktop.
Save yaboynoem/cfbc17a2939f24da8cc30b863ff0f677 to your computer and use it in GitHub Desktop.
animated sidebar on clicking hamburger using react and framer motion
import React, { ReactElement } from "react"
import { motion } from "framer-motion"
import { useSelector } from "react-redux"
//Components
import SideBar from "../SideBar/SideBar"
//Constants
import { EColor } from "Constants/Colors"
//Stores
import { store } from "Stores"
import { setIsSideBarOpen } from "Stores/App/Creators"
//Styles
import { SHamburgerBox, SHamburgerContainer, SHamburgerPatty } from "./Hamburger.styles"
//Types
import { TRootState } from "Types/GlobalTypes"
const Hamburger: React.FC = (): ReactElement => {
const { darkMode, isSideBarOpen } = useSelector((state: TRootState) => state.app)
return (
<SHamburgerContainer>
<SHamburgerBox
as={motion.div}
animate={{
backgroundColor: darkMode ? EColor.CONTAINER_DARK : EColor.CONTAINER,
border: `2px solid ${darkMode ? EColor.CONTAINER : EColor.CONTAINER_DARK}`,
}}
onClick={() => store.dispatch(setIsSideBarOpen(!isSideBarOpen))}
>
<SHamburgerPatty
as={motion.div}
style={{
originX: "right",
originY: "center",
}}
animate={{
backgroundColor: darkMode ? EColor.CONTAINER : EColor.CONTAINER_DARK,
y: isSideBarOpen ? 8 : 0,
width: isSideBarOpen ? 12 : 22.5,
marginLeft: isSideBarOpen ? 11.5 : 0,
rotate: isSideBarOpen ? 45 : 0,
transition: {
y: {
duration: 0.25,
type: "spring",
delay: isSideBarOpen ? 0 : 0.25,
},
width: {
duration: 0.25,
type: "spring",
delay: isSideBarOpen ? 0.25 : 0,
},
marginLeft: {
duration: 0.25,
type: "spring",
delay: isSideBarOpen ? 0.25 : 0,
},
rotate: {
duration: 0.25,
type: "spring",
delay: isSideBarOpen ? 0.25 : 0,
},
},
}}
/>
<SHamburgerPatty
as={motion.div}
animate={{
backgroundColor: darkMode ? EColor.CONTAINER : EColor.CONTAINER_DARK,
}}
/>
<SHamburgerPatty
as={motion.div}
style={{
originX: "right",
originY: "center",
}}
animate={{
backgroundColor: darkMode ? EColor.CONTAINER : EColor.CONTAINER_DARK,
y: isSideBarOpen ? -8 : 0,
width: isSideBarOpen ? 12 : 22.5,
marginLeft: isSideBarOpen ? 11.5 : 0,
rotate: isSideBarOpen ? -45 : 0,
transition: {
y: {
duration: 0.25,
type: "spring",
delay: isSideBarOpen ? 0 : 0.25,
},
width: {
duration: 0.25,
type: "spring",
delay: isSideBarOpen ? 0.25 : 0,
},
marginLeft: {
duration: 0.25,
type: "spring",
delay: isSideBarOpen ? 0.25 : 0,
},
rotate: {
duration: 0.25,
type: "spring",
delay: isSideBarOpen ? 0.25 : 0,
},
},
}}
/>
</SHamburgerBox>
//Add this SideBar in the SHamburgerContainer
<SideBar />
</SHamburgerContainer>
)
}
export default Hamburger
import { motion } from "framer-motion"
import { useSelector } from "react-redux"
//Constants
import { menuList } from "./SideBar"
import { EColor } from "Constants/Colors"
//Styles
import { SMenu, SMenuItem, SMenuText } from "./SideBar.styles"
//Types
import { TRootState } from "Types/GlobalTypes"
const Menu = (props: any) => {
return <SMenu>{props.children}</SMenu>
}
const Item = (props: any) => {
const { index, children } = props
const { darkMode, isSideBarOpen } = useSelector((state: TRootState) => state.app)
return (
<SMenuItem
as={motion.div}
animate={{
background: darkMode ? EColor.CONTAINER_DARK : EColor.CONTAINER,
color: darkMode ? EColor.CONTAINER : EColor.CONTAINER_DARK,
border: `2px solid ${darkMode ? EColor.CONTAINER : EColor.CONTAINER_DARK}`,
//Moves the menu item to the left if isSideBarOpen is true
x: isSideBarOpen ? 0 : 50,
opacity: isSideBarOpen ? 1 : 0,
//Display none is for hiding the menu as opacity 0 will not remove the element
display: isSideBarOpen ? "block" : "none",
transition: {
x: {
duration: 0.5,
type: "spring",
//This delay logic will stagger each menu item based on the index * 0.1s
//(index is the current number of menu)
delay: (isSideBarOpen ? index : menuList.length - index) * 0.1,
},
opacity: {
duration: 0.5,
type: "spring",
delay: (isSideBarOpen ? index : menuList.length - index) * 0.1,
},
display: {
//Display will be delayed when closing the sidebar to prevent the element from
//disappearing way too early
delay: isSideBarOpen ? 0 : menuList.length * 0.1 + 0.5,
},
},
}}
>
<SMenuText
as={motion.p}
//Hovering the menu will move the text to the right
whileHover={{
x: "5%",
}}
>
{children}
</SMenuText>
</SMenuItem>
)
}
Menu.Item = Item
export default Menu
import styled from "styled-components"
export const SMenu = styled.ul`
position: absolute;
top: 40px;
right: 0;
padding: 0;
margin-top: 10px;
`
export const SMenuItem = styled.li`
list-style-type: none;
white-space: nowrap;
padding: 5px 10px;
margin-bottom: -2px;
cursor: pointer;
//The first child will have top left right border radius
&:nth-child(1) {
border-top-right-radius: 5px;
border-top-left-radius: 5px;
}
//The first child will have bottom left right border radius
&:nth-last-child(1) {
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
margin-bottom: 0;
}
`
export const SMenuText = styled.p`
margin: 0;
`
import React, { ReactElement } from "react"
import Menu from "./Menu"
export const menuList = ["About", "Works", "Blog", "Source Code"]
const SideBar: React.FC = (): ReactElement => {
return (
<Menu>
{menuList.map((item, index) => (
<Menu.Item index={index}>{item}</Menu.Item>
))}
</Menu>
)
}
export default SideBar
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment