{"id":1471,"date":"2024-11-26T06:26:14","date_gmt":"2024-11-26T06:26:14","guid":{"rendered":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2024\/11\/26\/how-to-make-storybook-interactions-respect-user-motion-preferences\/"},"modified":"2024-11-26T06:26:14","modified_gmt":"2024-11-26T06:26:14","slug":"how-to-make-storybook-interactions-respect-user-motion-preferences","status":"publish","type":"post","link":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2024\/11\/26\/how-to-make-storybook-interactions-respect-user-motion-preferences\/","title":{"rendered":"How to make Storybook Interactions respect user motion preferences"},"content":{"rendered":"<p>Recently, while browsing my company\u2019s <a href=\"https:\/\/github.com\/storybookjs\/storybook\">Storybook<\/a>, I came across something that seemed broken: a flickering component that appeared to be re-rendering repeatedly. The open source tool that helps designers, developers, and others build and use reusable components was behaving weirdly. As I dug in, I realized I was seeing the unintended effects of the <a href=\"https:\/\/storybook.js.org\/docs\/essentials\/interactions\">Storybook Interactions addon<\/a>, which allows developers to simulate user interactions within a story, in action.<\/p>\n<p>Storybook Interactions can be a powerful tool, enabling developers to simulate and test user behaviors quickly. But if you\u2019re unfamiliar with Interactions\u2014especially if you\u2019re just looking to explore available components\u2014the simulated tests jumping around on the screen can feel disorienting.<\/p>\n<p>This can be especially jarring for users who have the prefers-reduced-motion setting enabled in their operating system. When these users encounter a story that includes an interaction, their preferences are ignored and they have no option to disable or enable it. Instead, the Storybook Interaction immediately plays on page load, regardless. These rapid screen movements can cause disorientation for users or in some cases can even trigger a seizure.<\/p>\n<p class=\"h5-mktg gh-aside-title\">Knowledge share<\/p>\n<p>Operating systems allow users to set a motion preference. Adhering to this setting can be critical for some users. For example, for users who have photosensitive epilepsy or vertigo, even a small animation can be life-threatening.<\/p>\n<p>This explicit preference for a reduced motion experience can be used by browsers, applications, and websites to reduce unnecessary animations and motions via <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/@media\/prefers-reduced-motion\">the prefers-reduced-motion CSS media feature<\/a>.<\/p>\n<p>At this time, Storybook does not have built-in capabilities to toggle interactions on or off. Until this feature can be baked in I am hoping this blog will provide you with an alternative way to make your work environment more inclusive. Now, let\u2019s get into building an addon that respects user\u2019s motion preferences and allows users to toggle interactions on and off.<\/p>\n<h2>Goals<a href=\"https:\/\/github.blog\/engineering\/#goals\" class=\"heading-link pl-2 text-italic text-bold\"><\/a><\/h2>\n<p>Users with prefers-reduced-motion enabled <strong>MUST<\/strong> have interactions off by default.<br \/>\nUsers with prefers-reduced-motion enabled <strong>MUST<\/strong> have a way to toggle the feature on or off without altering their operating system user preferences.<br \/>\nAll users <strong>SHOULD<\/strong> have a way to toggle the feature on or off without altering their user preferences.<\/p>\n<h2>Let\u2019s get started<a href=\"https:\/\/github.blog\/engineering\/#lets-get-started\" class=\"heading-link pl-2 text-italic text-bold\"><\/a><\/h2>\n<h3>Step 1: Build a Storybook addon<a href=\"https:\/\/github.blog\/engineering\/#step-1-build-a-storybook-addon\" class=\"heading-link pl-2 text-italic text-bold\"><\/a><\/h3>\n<p><a href=\"https:\/\/storybook.js.org\/docs\/addons\/writing-addons\">Storybook allows developers to create custom addons<\/a>. In this case, we will create one that will allow users to toggle Interactions on or off, while respecting the prefers-reduced-motion setting.<\/p>\n<p>Add the following code to a file in your project\u2019s .storybook folder:<\/p>\n<p>import React, {useCallback, useEffect} from &#8216;react&#8217;<\/p>\n<p>import {IconButton} from &#8216;@storybook\/components&#8217;<br \/>\nimport {PlayIcon, StopIcon} from &#8216;@storybook\/icons&#8217;<\/p>\n<p>export const ADDON_ID = &#8216;toggle-interaction&#8217;<br \/>\nexport const TOOL_ID = `${ADDON_ID}\/tool`<\/p>\n<p>export const INTERACTION_STORAGE_KEY = &#8216;disableInteractions&#8217;<\/p>\n<p>export const InteractionToggle = () =&gt; {<br \/>\n  const [disableInteractions, setDisableInteractions] = React.useState(<br \/>\n       window?.localStorage.getItem(INTERACTION_STORAGE_KEY) === &#8216;true&#8217;,<br \/>\n  )<\/p>\n<p>  useEffect(() =&gt; {<br \/>\n    const reducedMotion = matchMedia(&#8216;(prefers-reduced-motion)&#8217;)<\/p>\n<p>    if (window?.localStorage.getItem(INTERACTION_STORAGE_KEY) === null &amp;&amp; reducedMotion.matches) {<br \/>\n      window?.localStorage?.setItem(INTERACTION_STORAGE_KEY, &#8216;true&#8217;)<br \/>\n      setDisableInteractions(true)<br \/>\n    }<br \/>\n  }, [])<\/p>\n<p>  const toggleMyTool = useCallback(() =&gt; {<br \/>\n    window?.localStorage?.setItem(INTERACTION_STORAGE_KEY, `${!disableInteractions}`)<br \/>\n    setDisableInteractions(!disableInteractions)<br \/>\n      \/\/ Refreshes the page to cause the interaction to stop\/start<br \/>\n      window.location.reload()<br \/>\n}, [disableInteractions, setDisableInteractions])<\/p>\n<p>  return (<br \/>\n    &lt;IconButton<br \/>\n      key={TOOL_ID}<br \/>\n      aria-label=&#8221;Disable Interactions&#8221;<br \/>\n      onClick={toggleMyTool}<br \/>\n      defaultChecked={disableInteractions}<br \/>\n      aria-pressed={disableInteractions}<br \/>\n    &gt;<br \/>\n      {disableInteractions ? &lt;PlayIcon \/&gt; : &lt;StopIcon \/&gt;}<br \/>\n      Interactions<br \/>\n    &lt;\/IconButton&gt;<br \/>\n  )<br \/>\n}<\/p>\n<h4>Code breakdown<a href=\"https:\/\/github.blog\/engineering\/#code-breakdown\" class=\"heading-link pl-2 text-italic text-bold\"><\/a><\/h4>\n<p>This addon stores user preferences for Interactions using <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/Window\/localStorage\">window.localStorage<\/a>. When the addon first loads, it checks whether the preference is already set and, if so, it defaults to the user\u2019s preference.<\/p>\n<p>const [disableInteractions, setDisableInteractions] = React.useState(<br \/>\n       window?.localStorage.getItem(INTERACTION_STORAGE_KEY) === &#8216;true&#8217;,<br \/>\n  )<\/p>\n<p>This <a href=\"https:\/\/react.dev\/reference\/react\/useEffect\">useEffect<\/a> hook checks if a user has their motion preferences set to prefers-reduced-motion and ensures that Interactions are turned off if the user hasn\u2019t already set a preference in Storybook.<\/p>\n<p>useEffect(() =&gt; {<br \/>\n    const reducedMotion = matchMedia(&#8216;(prefers-reduced-motion)&#8217;)<\/p>\n<p>    if (window?.localStorage.getItem(INTERACTION_STORAGE_KEY) === null &amp;&amp; reducedMotion.matches) {<br \/>\n      window?.localStorage?.setItem(INTERACTION_STORAGE_KEY, &#8216;true&#8217;)<br \/>\n      setDisableInteractions(true)<br \/>\n    }<br \/>\n  }, [])<\/p>\n<p>When a user clicks the toggle button, preferences are updated and the page is refreshed to reflect the changes.<\/p>\n<p>const toggleMyTool = useCallback(() =&gt; {<br \/>\n    window?.localStorage?.setItem(INTERACTION_STORAGE_KEY, `${!disableInteractions}`)<br \/>\n    setDisableInteractions(!disableInteractions)<br \/>\n      \/\/ Refreshes the page to cause the interaction to stop\/start<br \/>\n      window.location.reload()<br \/>\n  }, [disableInteractions, setDisableInteractions])<\/p>\n<h3>Step 2: Register your new addon with Storybook<a href=\"https:\/\/github.blog\/engineering\/#step-2-register-your-new-addon-with-storybook\" class=\"heading-link pl-2 text-italic text-bold\"><\/a><\/h3>\n<p>In your <a href=\"https:\/\/storybook.js.org\/docs\/configure#configure-storybooks-ui\">.storybook\/manager<\/a> file, register your new addon:<\/p>\n<p>addons.register(ADDON_ID, () =&gt; {<br \/>\n  addons.add(TOOL_ID, {<br \/>\n    title: &#8216;toggle interaction&#8217;,<br \/>\n    type: types.TOOL as any,<br \/>\n    match: ({ viewMode, tabId }) =&gt; viewMode === &#8216;story&#8217; &amp;&amp; !tabId,<br \/>\n    render: () =&gt; &lt;InteractionToggle \/&gt;,<br \/>\n  })<br \/>\n})<\/p>\n<p>This adds the toggle button to the Storybook toolbar, which will allow users to change their Storybook Interaction preferences.<\/p>\n<h3>Step 3: Add functionality to check user preferences<a href=\"https:\/\/github.blog\/engineering\/#step-3-add-functionality-to-check-user-preferences\" class=\"heading-link pl-2 text-italic text-bold\"><\/a><\/h3>\n<p>Finally, create a function that checks whether Interactions should be played and add it to your interaction stories:<\/p>\n<p>import {INTERACTION_STORAGE_KEY} from &#8216;.\/.storybook\/src\/InteractionToggle&#8217;<\/p>\n<p>export const shouldInteractionPlay = () =&gt; {<br \/>\n  const disableInteractions = window?.localStorage?.getItem(INTERACTION_STORAGE_KEY)<br \/>\n  return disableInteractions === &#8216;false&#8217; || disableInteractions === null<br \/>\n}<\/p>\n<p> export const SomeComponentStory = {<br \/>\n  render: SomeComponent,<br \/>\n  play: async ({context}) =&gt; {<br \/>\n    if (shouldInteractionPlay()) {<br \/>\n&#8230;<br \/>\n    }<br \/>\n  })<br \/>\n }<\/p>\n<h2>Wrap-up<a href=\"https:\/\/github.blog\/engineering\/#wrap-up\" class=\"heading-link pl-2 text-italic text-bold\"><\/a><\/h2>\n<p>With this custom addon, you can ensure your workplace remains accessible to users with motion sensitivities while benefiting from Storybook\u2019s Interactions. For those with prefers-reduced-motion enabled, motion will be turned off by default and all users will be able to toggle interactions on or off.<\/p>\n<p>The post <a href=\"https:\/\/github.blog\/engineering\/user-experience\/how-to-make-storybook-interactions-respect-user-motion-preferences\/\">How to make Storybook Interactions respect user motion preferences<\/a> appeared first on <a href=\"https:\/\/github.blog\/\">The GitHub Blog<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>Recently, while browsing my company\u2019s Storybook, I came across something that seemed broken: a flickering component that appeared to be [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[8],"tags":[],"class_list":["post-1471","post","type-post","status-publish","format-standard","hentry","category-github-engineering"],"_links":{"self":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/1471","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/comments?post=1471"}],"version-history":[{"count":0,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/1471\/revisions"}],"wp:attachment":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media?parent=1471"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/categories?post=1471"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/tags?post=1471"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}