Skip to content

Instantly share code, notes, and snippets.

@andrewluetgers
Last active January 23, 2024 19:17
Show Gist options
  • Save andrewluetgers/c2712593de864e72cc0f56ae9860e4d1 to your computer and use it in GitHub Desktop.
Save andrewluetgers/c2712593de864e72cc0f56ae9860e4d1 to your computer and use it in GitHub Desktop.
Presentation mode for Storybook play functions
import {useEffect} from 'react'
import {fireEvent, within} from '@storybook/testing-library'
import userEvent from '@testing-library/user-event'
export const presentInteraction = storyFn => {
return (
<div>
<PresentInteraction/>
{storyFn()}
</div>
)
}
let PresentInteraction = () => {
useEffect(() => {
let remove = (elem) => {
try {
(el || elem) && document.body.removeChild(el || elem)
} catch (e) {}
},
el = null
document.addEventListener('click', e => {
if (global.test || !global.playing) {return}
let {target} = e,
{left, top, width, height} = target.getBoundingClientRect(),
sTop = Math.round(top+(Math.min(height/2, 50)))+'px',
sLeft = Math.round(left+(Math.min(width/2, 50)))+'px'
remove()
el = document.createElement('div')
el.className = 'clickEffect'
el.style.top = sTop
el.style.left = sLeft
document.body.appendChild(el)
el.addEventListener('animationend',() => {
remove(el)
})
}, {capture: true})
}, [])
return (
<div id="presentInteraction"> </div>
)
}
export async function mouseTo(target, ms = 1200) {
if (global.test || !target) {
return new Promise(resolve => resolve());
} else {
return new Promise(resolve => {
// animate mouse pointer from previous to current element
let cursorEl = document.getElementById('demoCursor')
if (!cursorEl) {
cursorEl = document.createElement('div')
cursorEl.id = 'demoCursor'
cursorEl.addEventListener('transitionend',() => {
console.log('anim ended',{cursorEl})
cursorEl.className = 'hide'
target.dispatchEvent(new MouseEvent('mouseover', {'view': window, 'bubbles': true, 'cancelable': true}))
}, {capture: true})
document.body.appendChild(cursorEl)
}
console.log({target})
let {left, top, width, height} = target.getBoundingClientRect(),
sTop = Math.round(top+(Math.min(height/2, 50)))+'px',
sLeft = Math.round(left+(Math.min(width/2, 50)))+'px'
cursorEl.className = 'moving'
cursorEl.style.top = sTop
cursorEl.style.left = sLeft
cursorEl.style.transitionDuration = `${Math.round(ms*.9)}ms`
// ^ bakes in a 10% time delay from movement ending to click event
return setTimeout(resolve, ms)
});
}
}
export async function click(target, delay = 900) {
await mouseTo(target, delay)
return fireEvent.click(target)
}
// global.playing is used to prevent click effects when manually interacting
// no effects or delays applied if global.test
let examplePlayFunction = async ({canvasElement: el, ...rest}) => {
global.playing = true
let screen = within(el),
input = await screen.getByRole('textbox'),
value = `Channel ${Math.random()}`
await click(input)
await userEvent.type(input, value, {delay: 50})
input.blur()
await click(el.querySelector('.addButton'))
await click(screen.getByText(value))
global.playing = false;
}
<!-- lives in .storybook folder-->
<style>
/* storybook present interaction */
.clickEffect {
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
/*background: red;*/
position: fixed;
box-sizing: border-box;
border-style: solid;
border-color: #FFC400D6;
border-radius: 50%;
animation: clickEffect 1s ease-out;
z-index: 99999;
}
@keyframes clickEffect {
0% {
opacity: 1;
width: 0.5em;
height: 0.5em;
margin: -0.25em;
border-width: 0.5em;
}
100% {
opacity: 0.2;
width: 15em;
height: 15em;
margin: -7.5em;
border-width: 0.03em;
}
}
#demoCursor {
transition-duration: 1000ms;
transition-timing-function: cubic-bezier(0.2, 1, 0.2, 1);
transition-property: top, left;
position: fixed;
width: 40px;
height: 40px;
border-radius: 20px;
margin: -20px 0 0 -20px;
background: rgba(0, 0, 0, 0);
top: 50%;
left: 50%;
pointer-events: none;
}
#demoCursor.moving {
background: rgba(0, 0, 0, 0.1);
}
#demoCursor.hide {
animation: cursorHide 1000ms linear;
}
@keyframes cursorHide {
0% {
background: rgba(0, 0, 0, 0.1);
}
100% {
background: rgba(0, 0, 0, 0);
}
}
</style>
@andrewluetgers
Copy link
Author

After sharing this with the Storybook community I was super excited to see @yannbf demo this at JS World https://youtu.be/kAvyulNHv6k?si=nmqU71bgWYCT_6rI&t=1041

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment