diff --git a/package.json b/package.json index a92b459fd..819e1a697 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "@microlink/mql": "~0.9.5", "@microlink/react": "~5.5.6", "@microlink/recipes": "~1.0.0", + "@radix-ui/react-popper": "0.0.17", "@stripe/react-stripe-js": "~1.4.1", "@stripe/stripe-js": "~1.15.0", "@styled-system/prop-types": "~5.1.5", diff --git a/src/components/pages/home/hero.js b/src/components/pages/home/hero.js index 4f73b629a..563aa375c 100644 --- a/src/components/pages/home/hero.js +++ b/src/components/pages/home/hero.js @@ -1,17 +1,17 @@ import { Link, Flex, Subhead, Container, Heading } from 'components/elements' -import { Caption, ArrowLink } from 'components/patterns' +import { Cursor, Caption, ArrowLink } from 'components/patterns' import React, { useEffect, useState } from 'react' import { fadeIn } from 'components/keyframes' import { layout } from 'theme' const SENTENCES = [ - { href: '/recipes/get-html', text: 'Get HTML markup', color: '#850BA7' }, - { href: '/screenshot', text: 'Take a screenshot', color: '#FD494A' }, - { href: '/pdf', text: 'Generate a PDF', color: '#e000ac' }, { href: '/meta', text: 'Normalize metadata', color: '#3e55ff' }, - { href: '/insights', text: 'Run Lighthouse', color: '#B500ED' }, + { href: '/recipes', text: 'Get HTML markup', color: '#850BA7' }, + { href: '/screenshot', text: 'Take a screenshot', color: '#FD494A' }, { href: '/insights', text: 'Identify tech stack', color: '#A31B90' }, - { href: '/recipes', text: 'Automate scrapping', color: '#DF3A61' } + { href: '/pdf', text: 'Generate a PDF', color: '#e000ac' }, + { href: '/recipes', text: 'Automate scrapping', color: '#DF3A61' }, + { href: '/insights', text: 'Run Lighthouse', color: '#B500ED' } ] const SENTENCES_INTERVAL = 3500 @@ -29,30 +29,37 @@ const Hero = props => { const { color, text, href } = SENTENCES[index] - const cursor = `url("data:image/svg+xml,%3Csvg shape-rendering='geometricPrecision' xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='none'%3E%3Cg filter='url(%23filter0_d)'%3E%3Cpath fill='%23${color.slice( - 1 - )}' d='M9.63 6.9a1 1 0 011.27-1.27l11.25 3.75a1 1 0 010 1.9l-4.68 1.56a1 1 0 00-.63.63l-1.56 4.68a1 1 0 01-1.9 0L9.63 6.9z'/%3E%3Cpath stroke='%23fff' stroke-width='1.5' d='M11.13 4.92a1.75 1.75 0 00-2.2 2.21l3.74 11.26a1.75 1.75 0 003.32 0l1.56-4.68a.25.25 0 01.16-.16L22.4 12a1.75 1.75 0 000-3.32L11.13 4.92z'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='filter0_d' width='32.26' height='32.26' x='.08' y='.08' filterUnits='userSpaceOnUse'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeColorMatrix in='SourceAlpha' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='4'/%3E%3CfeGaussianBlur stdDeviation='4'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0'/%3E%3CfeBlend in2='BackgroundImageFix' mode='normal' result='effect1_dropShadow'/%3E%3CfeBlend in='SourceGraphic' in2='effect1_dropShadow' mode='normal' result='shape'/%3E%3C/filter%3E%3C/defs%3E%3C/svg%3E") 6 2, default` - return ( Browser as API - - - {text} - - + + + + {text} + + + diff --git a/src/components/patterns/Cursor/Cursor.js b/src/components/patterns/Cursor/Cursor.js new file mode 100644 index 000000000..b6c043c1a --- /dev/null +++ b/src/components/patterns/Cursor/Cursor.js @@ -0,0 +1,66 @@ +import { Popper, PopperAnchor, PopperContent } from '@radix-ui/react-popper' +import React, { useState, useRef, useEffect } from 'react' +import { Box, Text } from 'components/elements' +import styled from 'styled-components' +import { shadows } from 'theme' + +const CursorContent = styled(Text)` + border-radius: 9999px; + border: ${props => `1.8px solid ${props.color}`}; + box-shadow: ${shadows[0]}; +` + +const Cursor = ({ color = 'white', style: innerStyle, bg, text, children }) => { + const [isActive, setIsActive] = useState(false) + const mousePosRef = useRef({ x: 0, y: 0 }) + + const handleMouseMove = event => + (mousePosRef.current = { x: event.pageX, y: event.pageY }) + + const virtualRef = useRef({ + getBoundingClientRect: () => + window.DOMRect.fromRect({ width: 0, height: 0, ...mousePosRef.current }) + }) + + useEffect(() => { + document.addEventListener('mousemove', handleMouseMove) + return () => document.removeEventListener('mousemove', handleMouseMove) + }, []) + + const onMouseEnter = () => setIsActive(true) + const onMouseLeave = () => setIsActive(false) + + const cursor = `url("data:image/svg+xml,%3Csvg shape-rendering='geometricPrecision' xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='none'%3E%3Cg filter='url(%23filter0_d)'%3E%3Cpath fill='%23${bg.slice( + 1 + )}' d='M9.63 6.9a1 1 0 011.27-1.27l11.25 3.75a1 1 0 010 1.9l-4.68 1.56a1 1 0 00-.63.63l-1.56 4.68a1 1 0 01-1.9 0L9.63 6.9z'/%3E%3Cpath stroke='%23fff' stroke-width='1.5' d='M11.13 4.92a1.75 1.75 0 00-2.2 2.21l3.74 11.26a1.75 1.75 0 003.32 0l1.56-4.68a.25.25 0 01.16-.16L22.4 12a1.75 1.75 0 000-3.32L11.13 4.92z'/%3E%3C/g%3E%3Cdefs%3E%3Cfilter id='filter0_d' width='32.26' height='32.26' x='.08' y='.08' filterUnits='userSpaceOnUse'%3E%3CfeFlood flood-opacity='0' result='BackgroundImageFix'/%3E%3CfeColorMatrix in='SourceAlpha' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0'/%3E%3CfeOffset dy='4'/%3E%3CfeGaussianBlur stdDeviation='4'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0'/%3E%3CfeBlend in2='BackgroundImageFix' mode='normal' result='effect1_dropShadow'/%3E%3CfeBlend in='SourceGraphic' in2='effect1_dropShadow' mode='normal' result='shape'/%3E%3C/filter%3E%3C/defs%3E%3C/svg%3E") 6 2, default` + + return ( + <> + + {children} + + {isActive && ( + + + + + {text} + + + + )} + + ) +} + +export default Cursor diff --git a/src/components/patterns/Cursor/Cursor.stories.js b/src/components/patterns/Cursor/Cursor.stories.js new file mode 100644 index 000000000..2129cafe9 --- /dev/null +++ b/src/components/patterns/Cursor/Cursor.stories.js @@ -0,0 +1,33 @@ +import { Subhead } from 'components/elements' +import { Cursor } from 'components/patterns' +import { storiesOf } from '@storybook/react' +import { Story } from 'story' +import React from 'react' + +const storyName = 'Cursor' + +const code = ` +import { Subhead } from 'components/elements' +import { Cursor } from 'components/patterns' + +export default () => ( + + + Hover on me + + +)` + +const CursorStory = () => { + return ( + + + + Hover on me + + + + ) +} + +storiesOf('Patterns', module).add(storyName, () => ) diff --git a/src/components/patterns/index.js b/src/components/patterns/index.js index 1e38bb403..fd6d0c5be 100644 --- a/src/components/patterns/index.js +++ b/src/components/patterns/index.js @@ -14,6 +14,7 @@ import Footer from './Footer/Footer' import Grid from './Grid' import Layout from './Layout' import Legend from './Legend/Legend' +import Cursor from './Cursor/Cursor' import List from './List/List' import Average from './Average/Average' import Microlink from './Microlink/Microlink' @@ -37,6 +38,7 @@ export { ClusterMonitor, CookiesPolicy, CubeBackground, + Cursor, DemoLinks, DotsBackground, Faq,