├── .eslintrc
├── .gitignore
├── README.md
├── menu.gif
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── AnimatedNavbar.js
├── DemoControls.js
├── DropdownContainer
│ ├── Components.js
│ ├── FadeContents.js
│ ├── index.js
│ └── utils.js
├── DropdownContents
│ ├── CompanyDropdown.js
│ ├── Components.js
│ ├── DevelopersDropdown.js
│ └── ProductsDropdown.js
├── Navbar
│ ├── NavbarItem.js
│ └── index.js
├── index.css
└── index.js
└── yarn.lock
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "react-app",
3 | "rules": {
4 | "react/prop-types": 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## A Stripe-style animated navbar menu
2 | Built with React, [Styled Components](https://www.styled-components.com/), and [React-Flip-Toolkit](https://github.com/aholachek/react-flip-toolkit).
3 |
4 |
5 |
6 |
7 |
8 | 1. [View the demo](https://aholachek.github.io/react-stripe-menu)
9 |
10 | 2. [Read the tutorial](https://css-tricks.com/building-a-complex-ui-animation-in-react-simply/)
11 |
12 |
13 | 3. [Check out the code for the main dropdown component](https://github.com/aholachek/react-stripe-menu/blob/master/src/DropdownContainer/index.js)
14 |
15 | ### Details
16 |
17 | This animation demo explores how one might recreate [Stripe's animated menu](https://stripe.com/) in React.
18 |
19 | In order to keep the example as simple as possible, it focuses mainly on the animation aspect and therefore is not WAI-ARIA compliant. (Take a look at Stripe's full implementation for what seems to be a fully accessible nav menu component.)
20 |
21 | There are multiple ways one could implement this animation, each with its own tradeoffs. This demo is particularly focused on developer ease of use.
22 |
--------------------------------------------------------------------------------
/menu.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aholachek/react-stripe-menu/7e0ef85108642e75c6a8e13660e375e13dca431c/menu.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-stripe-menu",
3 | "homepage": "http://aholachek.github.io/react-stripe-menu",
4 | "version": "0.1.0",
5 | "private": true,
6 | "dependencies": {
7 | "normalize.css": "^8.0.1",
8 | "react": "^16.8.6",
9 | "react-dom": "^16.8.6",
10 | "react-flip-toolkit": "latest",
11 | "react-scripts": "3.0.1",
12 | "styled-components": "4.3.2"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test --env=jsdom",
18 | "eject": "react-scripts eject",
19 | "predeploy": "yarn run build",
20 | "deploy": "gh-pages -d build"
21 | },
22 | "devDependencies": {
23 | "babel-core": "^6.26.3",
24 | "babel-runtime": "^6.26.0",
25 | "gh-pages": "^1.1.0"
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aholachek/react-stripe-menu/7e0ef85108642e75c6a8e13660e375e13dca431c/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | Stripe Menu
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/AnimatedNavbar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import Navbar from "./Navbar"
3 | import NavbarItem from "./Navbar/NavbarItem"
4 | import { Flipper } from "react-flip-toolkit"
5 | import DropdownContainer from "./DropdownContainer"
6 | import CompanyDropdown from "./DropdownContents/CompanyDropdown"
7 | import DevelopersDropdown from "./DropdownContents/DevelopersDropdown"
8 | import ProductsDropdown from "./DropdownContents/ProductsDropdown"
9 |
10 | const navbarConfig = [
11 | { title: "Products", dropdown: ProductsDropdown },
12 | { title: "Developers", dropdown: DevelopersDropdown },
13 | { title: "Company", dropdown: CompanyDropdown }
14 | ]
15 |
16 | export default class AnimatedNavbar extends Component {
17 | state = {
18 | activeIndices: []
19 | }
20 |
21 | resetDropdownState = i => {
22 | this.setState({
23 | activeIndices: typeof i === "number" ? [i] : [],
24 | animatingOut: false
25 | })
26 | delete this.animatingOutTimeout
27 | }
28 |
29 | onMouseEnter = i => {
30 | if (this.animatingOutTimeout) {
31 | clearTimeout(this.animatingOutTimeout)
32 | this.resetDropdownState(i)
33 | return
34 | }
35 | if (this.state.activeIndices[this.state.activeIndices.length - 1] === i)
36 | return
37 |
38 | this.setState(prevState => ({
39 | activeIndices: prevState.activeIndices.concat(i),
40 | animatingOut: false
41 | }))
42 | }
43 |
44 | onMouseLeave = () => {
45 | this.setState({
46 | animatingOut: true
47 | })
48 | this.animatingOutTimeout = setTimeout(
49 | this.resetDropdownState,
50 | this.props.duration
51 | )
52 | }
53 |
54 | render() {
55 | const { duration } = this.props
56 | let CurrentDropdown
57 | let PrevDropdown
58 | let direction
59 |
60 | const currentIndex = this.state.activeIndices[
61 | this.state.activeIndices.length - 1
62 | ]
63 | const prevIndex =
64 | this.state.activeIndices.length > 1 &&
65 | this.state.activeIndices[this.state.activeIndices.length - 2]
66 |
67 | if (typeof currentIndex === "number")
68 | CurrentDropdown = navbarConfig[currentIndex].dropdown
69 | if (typeof prevIndex === "number") {
70 | PrevDropdown = navbarConfig[prevIndex].dropdown
71 | direction = currentIndex > prevIndex ? "right" : "left"
72 | }
73 |
74 | return (
75 |
79 |
80 | {navbarConfig.map((n, index) => {
81 | return (
82 |
88 | {currentIndex === index && (
89 |
94 |
95 | {PrevDropdown && }
96 |
97 | )}
98 |
99 | )
100 | })}
101 |
102 |
103 | )
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/DemoControls.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import PropTypes from "prop-types"
3 | import styled from "styled-components"
4 |
5 | const Form = styled.form`
6 | padding: 2rem 0 1rem 0;
7 | background-color: #fff;
8 | display: flex;
9 | justify-content: center;
10 |
11 | > div {
12 | border: 0;
13 | padding: 1rem 0 1rem 0;
14 | margin-right: 3rem;
15 | display: flex;
16 | }
17 |
18 | input {
19 | margin-right: 0.5rem;
20 | }
21 | label + label input {
22 | margin-left: 1.5rem;
23 | }
24 | b {
25 | margin-right: 1.5rem;
26 | }
27 | `
28 |
29 | class DemoControls extends Component {
30 | static propTypes = {
31 | duration: PropTypes.number
32 | }
33 |
34 | render() {
35 | const { duration } = this.props
36 | return (
37 |
62 | )
63 | }
64 | }
65 |
66 | export default DemoControls
67 |
--------------------------------------------------------------------------------
/src/DropdownContainer/Components.js:
--------------------------------------------------------------------------------
1 | import styled, { keyframes } from "styled-components"
2 | import { promoteLayer } from "./utils"
3 |
4 | const getDropdownRootKeyFrame = ({ animatingOut, direction }) => {
5 | if (!animatingOut && direction) return null
6 | return keyframes`
7 | from {
8 | transform: ${animatingOut ? "rotateX(0)" : "rotateX(-15deg)"};
9 | opacity: ${animatingOut ? 1 : 0};
10 | }
11 | to {
12 | transform: ${animatingOut ? "rotateX(-15deg)" : "rotateX(0)"};
13 | opacity: ${animatingOut ? 0 : 1};
14 | }
15 | `
16 | }
17 |
18 | export const DropdownRoot = styled.div`
19 | transform-origin: 0 0;
20 | ${promoteLayer}
21 | animation-name: ${getDropdownRootKeyFrame};
22 | animation-duration: ${props => props.duration}ms;
23 | /* use 'forwards' to prevent flicker on leave animation */
24 | animation-fill-mode: forwards;
25 | /* flex styles will center the caret child component */
26 | display: flex;
27 | flex-direction: column;
28 | align-items: center;
29 | position: relative;
30 | top: -20px;
31 | `
32 |
33 | export const Caret = styled.div`
34 | width: 0;
35 | height: 0;
36 | border-width: 10px;
37 | border-style: solid;
38 | border-color: transparent transparent var(--white);
39 | /* make sure it's above the main dropdown container so now box-shadow bleeds over it */
40 | z-index: 1;
41 | position: relative;
42 | /* prevent any gap in between caret and main dropdown */
43 | top: 1px;
44 | `
45 |
46 | export const DropdownBackground = styled.div`
47 | transform-origin: 0 0;
48 | background-color: var(--white);
49 | border-radius: 4px;
50 | overflow: hidden;
51 | position: relative;
52 | box-shadow: 0 50px 100px rgba(50, 50, 93, 0.1);
53 | ${promoteLayer}
54 | `
55 |
56 | export const AltBackground = styled.div`
57 | background-color: var(--grey);
58 | width: 300%;
59 | height: 100%;
60 | position: absolute;
61 | top: 0;
62 | left: -100%;
63 | transform-origin: 0 0;
64 | z-index: 0;
65 | transition: transform ${props => props.duration}ms;
66 | `
67 |
68 | export const InvertedDiv = styled.div`
69 | ${promoteLayer}
70 | position: ${props => (props.absolute ? "absolute" : "relative")};
71 | top: 0;
72 | left: 0;
73 | &:first-of-type {
74 | z-index: 1;
75 | }
76 | &:not(:first-of-type) {
77 | z-index: -1;
78 | }
79 | `
80 |
--------------------------------------------------------------------------------
/src/DropdownContainer/FadeContents.js:
--------------------------------------------------------------------------------
1 | import React, { forwardRef } from "react"
2 | import PropTypes from "prop-types"
3 | import styled, { keyframes } from "styled-components"
4 | import { promoteLayer } from "./utils"
5 |
6 | const getFadeContainerKeyFrame = ({ animatingOut, direction }) => {
7 | if (!direction) return
8 | return keyframes`
9 | to {
10 | transform: translateX(0px);
11 | opacity: ${animatingOut ? 0 : 1};
12 | }
13 | `
14 | }
15 | const FadeContainer = styled.div`
16 | ${promoteLayer}
17 | animation-name: ${getFadeContainerKeyFrame};
18 | animation-duration: ${props => props.duration}ms;
19 | animation-fill-mode: forwards;
20 | opacity: ${props => (props.direction && !props.animatingOut ? 0 : 1)};
21 | top: 0;
22 | left: 0;
23 | `
24 |
25 | const propTypes = {
26 | duration: PropTypes.number,
27 | direction: PropTypes.oneOf(["right", "left"]),
28 | animatingOut: PropTypes.bool,
29 | children: PropTypes.node
30 | }
31 |
32 | const FadeContents = forwardRef(
33 | ({ children, duration, animatingOut, direction }, ref) => (
34 |
42 | {children}
43 |
44 | )
45 | )
46 |
47 | FadeContents.propTypes = propTypes
48 |
49 | export default FadeContents
50 |
--------------------------------------------------------------------------------
/src/DropdownContainer/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component, Children, createRef } from "react"
2 | import PropTypes from "prop-types"
3 | import { Flipped } from "react-flip-toolkit"
4 | import {
5 | DropdownRoot,
6 | Caret,
7 | DropdownBackground,
8 | AltBackground,
9 | InvertedDiv
10 | } from "./Components"
11 | import FadeContents from "./FadeContents"
12 |
13 | const getFirstDropdownSectionHeight = el => {
14 | if (
15 | !el ||
16 | !el.querySelector ||
17 | !el.querySelector("*[data-first-dropdown-section]")
18 | )
19 | return 0
20 | return el.querySelector("*[data-first-dropdown-section]").offsetHeight
21 | }
22 |
23 | const updateAltBackground = ({
24 | altBackground,
25 | prevDropdown,
26 | currentDropdown
27 | }) => {
28 | const prevHeight = getFirstDropdownSectionHeight(prevDropdown)
29 | const currentHeight = getFirstDropdownSectionHeight(currentDropdown)
30 |
31 | const immediateSetTranslateY = (el, translateY) => {
32 | el.style.transform = `translateY(${translateY}px)`
33 | el.style.transition = "transform 0s"
34 | requestAnimationFrame(() => (el.style.transitionDuration = ""))
35 | }
36 |
37 | if (prevHeight) {
38 | // transition the grey ("alt") background from its previous height to its current height
39 | immediateSetTranslateY(altBackground, prevHeight)
40 | requestAnimationFrame(() => {
41 | altBackground.style.transform = `translateY(${currentHeight}px)`
42 | })
43 | } else {
44 | // just immediately set the background to the appropriate height
45 | // since we don't have a stored value
46 | immediateSetTranslateY(altBackground, currentHeight)
47 | }
48 | }
49 |
50 | class DropdownContainer extends Component {
51 | static propTypes = {
52 | children: PropTypes.node.isRequired,
53 | animatingOut: PropTypes.bool,
54 | direction: PropTypes.oneOf(["left", "right"]),
55 | duration: PropTypes.number
56 | }
57 |
58 | currentDropdownEl = createRef()
59 | prevDropdownEl = createRef()
60 |
61 | componentDidMount() {
62 | updateAltBackground({
63 | altBackground: this.altBackgroundEl,
64 | prevDropdown: this.prevDropdownEl.current,
65 | currentDropdown: this.currentDropdownEl.current,
66 | duration: this.props.duration
67 | })
68 | }
69 |
70 | render() {
71 | const { children, direction, animatingOut, duration } = this.props
72 | const [currentDropdown, prevDropdown] = Children.toArray(children)
73 | return (
74 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | (this.altBackgroundEl = el)}
88 | duration={duration}
89 | />
90 |
95 | {currentDropdown}
96 |
97 |
98 |
99 |
100 |
101 |
102 | {prevDropdown && (
103 |
109 | {prevDropdown}
110 |
111 | )}
112 |
113 |
114 |
115 |
116 |
117 | )
118 | }
119 | }
120 |
121 | export default DropdownContainer
122 |
--------------------------------------------------------------------------------
/src/DropdownContainer/utils.js:
--------------------------------------------------------------------------------
1 | import { css } from "styled-components"
2 |
3 | // if applied to a persistent component, make sure to remove when animation is not imminent
4 | // to prevent taking up too many browser resources with `will-change`
5 | export const promoteLayer = css`
6 | will-change: transform;
7 | `
8 |
--------------------------------------------------------------------------------
/src/DropdownContents/CompanyDropdown.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import {
4 | Heading,
5 | HeadingLink,
6 | LinkList,
7 | DropdownSection,
8 | Icon
9 | } from "./Components"
10 |
11 | const CompanyDropdownEl = styled.div`
12 | width: 18.5rem;
13 | `
14 |
15 | const CompanyDropdown = () => {
16 | return (
17 |
18 |
19 |
41 |
42 |
43 |
59 |
60 |
61 | )
62 | }
63 |
64 | export default CompanyDropdown
65 |
--------------------------------------------------------------------------------
/src/DropdownContents/Components.js:
--------------------------------------------------------------------------------
1 | import styled from "styled-components"
2 |
3 | export const Heading = styled.h3`
4 | text-transform: uppercase;
5 | font-weight: bold;
6 | font-size: 1.1rem;
7 | margin-top: 0;
8 | margin-bottom: ${props => (props.noMarginBottom ? 0 : "1rem")};
9 | color: ${({ color }) => (color ? `var(--${color})` : "var(--blue)")};
10 | `
11 |
12 | export const HeadingLink = Heading.withComponent("li")
13 |
14 | export const LinkList = styled.ul`
15 | li {
16 | margin-bottom: 1rem;
17 | }
18 |
19 | li:last-of-type {
20 | margin-bottom: 0;
21 | }
22 |
23 | margin-left: ${props => (props.marginLeft ? props.marginLeft : 0)};
24 | `
25 |
26 | export const Icon = styled.div`
27 | width: 13px;
28 | height: 13px;
29 | margin-right: 13px;
30 | background-color: var(--blue);
31 | opacity: 0.8;
32 | display: inline-block;
33 | `
34 |
35 | export const DropdownSection = styled.div`
36 | padding: var(--spacer);
37 | position: relative;
38 | z-index: 1;
39 | `
40 |
--------------------------------------------------------------------------------
/src/DropdownContents/DevelopersDropdown.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import {
4 | Icon,
5 | DropdownSection,
6 | Heading,
7 | HeadingLink,
8 | LinkList
9 | } from "./Components"
10 |
11 | const DevelopersDropdownEl = styled.div`
12 | width: 25rem;
13 | `
14 |
15 | const Flex = styled.div`
16 | display: flex;
17 | > div:first-of-type {
18 | margin-right: 48px;
19 | }
20 | `
21 |
22 | const DevelopersDropdown = () => {
23 | return (
24 |
25 |
26 |
27 |
Documentation
28 |
Start integrating Stripe’s products and tools
29 |
30 |
44 |
58 |
59 |
60 |
61 |
62 |
79 |
80 |
81 | )
82 | }
83 |
84 | export default DevelopersDropdown
85 |
--------------------------------------------------------------------------------
/src/DropdownContents/ProductsDropdown.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import styled from "styled-components"
3 | import { Icon, DropdownSection, Heading } from "./Components"
4 |
5 | const ProductsDropdownEl = styled.div`
6 | width: 30rem;
7 | `
8 |
9 | const Logo = styled.div`
10 | width: 38px;
11 | height: 38px;
12 | margin-right: 16px;
13 | border-radius: 100%;
14 | opacity: 0.6;
15 | background-color: ${({ color }) => `var(--${color})`};
16 | `
17 |
18 | const SubProductsList = styled.ul`
19 | li {
20 | display: flex;
21 | margin-bottom: 1rem;
22 | }
23 | h3 {
24 | margin-right: 1rem;
25 | width: 3.2rem;
26 | display: block;
27 | }
28 | a {
29 | color: var(--dark-grey);
30 | }
31 | `
32 |
33 | const ProductsSection = styled.ul`
34 | li {
35 | display: flex;
36 | }
37 | `
38 |
39 | const WorksWithStripe = styled.div`
40 | border-top: 2px solid #fff;
41 | display:flex;
42 | justify-content: center;
43 | align-items: center;
44 | margin-top: var(--spacer);
45 | padding-top: var(--spacer);
46 | }
47 | h3 {
48 | margin-bottom: 0;
49 | }
50 | `
51 |
52 | const ProductsDropdown = () => {
53 | return (
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
Payments
63 |
A complete payments platform
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
Billing
72 |
Build and scale your recurring business model
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
Connect
81 |
82 | Everything platforms need to get sellers paid
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | Sigma
92 | Your business data at your fingertips.
93 |
94 |
95 | Atlas
96 | The best way to start an internet business.
97 |
98 |
99 | Radar
100 | Fight fraud with machine learning.
101 |
102 |
103 |
104 |
105 |
106 | Works with Stripe
107 |
108 |
109 |
110 |
111 |
112 | )
113 | }
114 |
115 | export default ProductsDropdown
116 |
--------------------------------------------------------------------------------
/src/Navbar/NavbarItem.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import PropTypes from "prop-types"
3 | import styled from "styled-components"
4 |
5 | const NavbarItemTitle = styled.button`
6 | background: transparent;
7 | border: 0;
8 | font-weight: bold;
9 | font-family: inherit;
10 | font-size: 18px;
11 | padding: 2rem 1.5rem 1.2rem 1.5rem;
12 | color: white;
13 | display: flex;
14 | justify-content: center;
15 | transition: opacity 250ms;
16 | cursor: pointer;
17 | /* position above the dropdown, otherwise the dropdown will cover up the bottom sliver of the buttons */
18 | position: relative;
19 | z-index: 2;
20 | &:hover, &:focus {
21 | opacity: 0.7;
22 | outline:none;
23 | }
24 | `
25 |
26 | const NavbarItemEl = styled.li`
27 | position: relative;
28 | `
29 |
30 | const DropdownSlot = styled.div`
31 | position: absolute;
32 | left: 50%;
33 | transform: translateX(-50%);
34 | perspective: 1500px;
35 | `
36 |
37 | export default class NavbarItem extends Component {
38 | static propTypes = {
39 | onMouseEnter: PropTypes.func.isRequired,
40 | title: PropTypes.string.isRequired,
41 | index: PropTypes.number.isRequired,
42 | children: PropTypes.node
43 | }
44 | onMouseEnter = () => {
45 | this.props.onMouseEnter(this.props.index)
46 | }
47 |
48 | render() {
49 | const { title, children } = this.props
50 | return (
51 |
52 | {title}
53 | {children}
54 |
55 | )
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Navbar/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import styled from "styled-components"
3 |
4 | const NavbarEl = styled.nav`
5 | margin: auto;
6 | `
7 |
8 | const NavbarList = styled.ul`
9 | display: flex;
10 | justify-content: center;
11 | list-style: none;
12 | margin: 0;
13 | `
14 |
15 | class Navbar extends Component {
16 | render() {
17 | const { children, onMouseLeave } = this.props
18 | return (
19 |
20 | {children}
21 |
22 | )
23 | }
24 | }
25 |
26 | export default Navbar
27 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | :root {
6 | --white: #fff;
7 | --grey: #f1f4f8b0;
8 | --dark-grey: #6b7c93;
9 | --green: #24b47e;
10 | --teal: #4F96CE;
11 | --blue: #6772e5;
12 | --dark-blue: #4F3EF5;
13 | --spacer: 28px;
14 | }
15 |
16 | body {
17 | font-family: -apple-system, system-ui, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
18 | -webkit-font-smoothing: antialiased;
19 | color: var(--dark-grey);
20 | }
21 |
22 | a {
23 | text-decoration: none;
24 | color: var(--blue);
25 | }
26 |
27 | a:hover,
28 | a:focus {
29 | color: var(--dark-blue);
30 | }
31 |
32 | ul {
33 | list-style: none;
34 | padding-left: 0;
35 | }
36 |
37 | p {
38 | margin-top: 0;
39 | margin-bottom: 1rem;
40 | }
41 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import ReactDOM from "react-dom"
3 | import AnimatedNavbar from "./AnimatedNavbar"
4 | import DemoControls from "./DemoControls"
5 | import styled from "styled-components"
6 | import "./index.css"
7 | import "normalize.css";
8 |
9 | const AppContainer = styled.div`
10 | background: linear-gradient(150deg, #53f 15%, #05d5ff);
11 | display: flex;
12 | flex-direction: column;
13 | min-height: 100vh;
14 |
15 | > div:first-of-type {
16 | flex: 1 0 70vh;
17 | }
18 | `
19 |
20 | class App extends Component {
21 | state = { duration: 300 }
22 |
23 | onChange = data => {
24 | this.setState(data)
25 | }
26 |
27 | render() {
28 | return (
29 |
30 |
31 |
35 |
36 | )
37 | }
38 | }
39 |
40 | ReactDOM.render(, document.querySelector("#root"))
41 |
--------------------------------------------------------------------------------