Skip to content

Commit

Permalink
new(demo): respect prefersReducedMotion and leverage in happo (#1037)
Browse files Browse the repository at this point in the history
* new(demo/axis,xychart): use animated components only when prefersReducedMotion=true

* test(happo): set prefersReducedMotion=true in target

* test(happo): remove special case animation timeouts

* fix(demo/xychart): remove unused props

* new(demo/xychart): remove animation trajectory control if userPrefersReducedMotion

* fix(demo/xychart): use animationTrajectory=undefined when user prefers reduced motion

* new(demo/patterns): respect prefersReducedMotion

* new(demo/xychart,axis): add animation option even if user sets prefersReducedMotion

* fix(demo/axis): fix animationTrajectory toggle

* internal(happo): remove special case references

* lint(demo/xychart)
  • Loading branch information
williaster committed Jan 27, 2021
1 parent a1da0b3 commit d717366
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 182 deletions.
1 change: 1 addition & 0 deletions packages/visx-demo/.happo.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = {
targets: {
'chrome-desktop': new RemoteBrowserTarget('chrome', {
viewport: '800x552',
prefersReducedMotion: true,
}),
},

Expand Down
44 changes: 6 additions & 38 deletions packages/visx-demo/happo/gallery.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import React from 'react';
import { tiles as examples } from '../src/components/Gallery';
import AxisTile from '../src/components/Gallery/AxisTile';
import XYChartTile from '../src/components/Gallery/XYChartTile';
import { asyncTimeout } from '../.happo-variables.js';

type HappoSnapshot = {
component: string;
Expand All @@ -13,42 +10,13 @@ type HappoSnapshot = {
};
};

const specialCases = new Set(['@visx/demo-axis', '@visx/demo-xychart']);

// renders an example with a timeout
const renderWithTimeout: (
Example: React.ReactElement,
) => HappoSnapshot['variants'][string] = Example => renderInDom => {
return new Promise(resolve => {
renderInDom(Example);
setTimeout(resolve, asyncTimeout);
});
};

const getComponentName = (Example: typeof examples[0]) =>
Example.packageJson.name || 'missing-name';

const snapshots: HappoSnapshot[] = examples
.filter(Example => !specialCases.has(getComponentName(Example)))
.map(Example => ({
// note: this (reasonably) asserts Examples have unique names
component: getComponentName(Example),
variants: { default: () => <Example.default /> },
}));
const snapshots: HappoSnapshot[] = examples.map(Example => ({
// note: this (reasonably) asserts Examples have unique names
component: getComponentName(Example),
variants: { default: () => <Example.default /> },
}));

export default snapshots.concat([
// needs timeout for animated axes
{
component: '@visx/demo-axis',
variants: {
default: renderWithTimeout(<AxisTile />),
},
},
// needs timeout for animated axes
{
component: '@visx/demo-xychart',
variants: {
default: renderWithTimeout(<XYChartTile />),
},
},
]);
export default snapshots;
122 changes: 84 additions & 38 deletions packages/visx-demo/src/sandboxes/visx-axis/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import React, { useState, useMemo } from 'react';
import AreaClosed from '@visx/shape/lib/shapes/AreaClosed';
import { curveMonotoneX } from '@visx/curve';
import { scaleUtc, scaleLinear, scaleLog, scaleBand, ScaleInput, coerceNumber } from '@visx/scale';
import { Orientation, SharedAxisProps, AxisScale } from '@visx/axis';
import { Axis, Orientation, SharedAxisProps, AxisScale } from '@visx/axis';
import { GridRows, GridColumns } from '@visx/grid';
import { AnimatedAxis, AnimatedGridRows, AnimatedGridColumns } from '@visx/react-spring';
import { getSeededRandom } from '@visx/mock-data';
import { LinearGradient } from '@visx/gradient';
import { timeFormat } from 'd3-time-format';
import { GridRowsProps } from '@visx/grid/lib/grids/GridRows';
import { GridColumnsProps } from '@visx/grid/lib/grids/GridColumns';

export const backgroundColor = '#da7cff';
const axisColor = '#fff';
Expand Down Expand Up @@ -40,23 +43,51 @@ export type AxisProps = {
showControls?: boolean;
};

type AnimationTrajectory = 'outside' | 'center' | 'min' | 'max' | undefined;

type AxisComponent = React.FC<
SharedAxisProps<AxisScale> & {
animationTrajectory: AnimationTrajectory;
}
>;
type GridRowsComponent = React.FC<
GridRowsProps<AxisScale> & {
animationTrajectory: AnimationTrajectory;
}
>;
type GridColumnsComponent = React.FC<
GridColumnsProps<AxisScale> & {
animationTrajectory: AnimationTrajectory;
}
>;

export default function Example({
width: outerWidth = 800,
height: outerHeight = 800,
showControls = true,
}: AxisProps) {
// use non-animated components if prefers-reduced-motion is set
const prefersReducedMotionQuery = window?.matchMedia('(prefers-reduced-motion: reduce)');
const prefersReducedMotion = !prefersReducedMotionQuery || !!prefersReducedMotionQuery.matches;
const [useAnimatedComponents, setUseAnimatedComponents] = useState(!prefersReducedMotion);

// in svg, margin is subtracted from total width/height
const width = outerWidth - margin.left - margin.right;
const height = outerHeight - margin.top - margin.bottom;
const [dataToggle, setDataToggle] = useState(true);
const [animationTrajectory, setAnimationTrajectory] = useState<
'outside' | 'center' | 'min' | 'max'
>('center');
const [animationTrajectory, setAnimationTrajectory] = useState<AnimationTrajectory>('center');

// define some types
interface AxisDemoProps<Scale extends AxisScale> extends SharedAxisProps<Scale> {
values: ScaleInput<Scale>[];
}

const AxisComponent: AxisComponent = useAnimatedComponents ? AnimatedAxis : Axis;
const GridRowsComponent: GridRowsComponent = useAnimatedComponents ? AnimatedGridRows : GridRows;
const GridColumnsComponent: GridColumnsComponent = useAnimatedComponents
? AnimatedGridColumns
: GridColumns;

const axes: AxisDemoProps<AxisScale<number>>[] = useMemo(() => {
// toggle between two value ranges to demo animation
const linearValues = dataToggle ? [0, 2, 4, 6, 8, 10] : [6, 8, 10, 12];
Expand Down Expand Up @@ -144,7 +175,7 @@ export default function Example({
<g transform={`translate(${margin.left},${margin.top})`}>
{axes.map(({ scale, values, label, tickFormat }, i) => (
<g key={`scale-${i}`} transform={`translate(0, ${i * (scaleHeight + scalePadding)})`}>
<AnimatedGridRows
<GridRowsComponent
// force remount when this changes to see the animation difference
key={`gridrows-${animationTrajectory}`}
scale={yScale}
Expand All @@ -153,7 +184,7 @@ export default function Example({
numTicks={dataToggle ? 1 : 3}
animationTrajectory={animationTrajectory}
/>
<AnimatedGridColumns
<GridColumnsComponent
// force remount when this changes to see the animation difference
key={`gridcolumns-${animationTrajectory}`}
scale={scale}
Expand All @@ -176,7 +207,7 @@ export default function Example({
fill={gridColor}
fillOpacity={0.2}
/>
<AnimatedAxis
<AxisComponent
// force remount when this changes to see the animation difference
key={`axis-${animationTrajectory}`}
orientation={Orientation.bottom}
Expand Down Expand Up @@ -208,42 +239,57 @@ export default function Example({
</svg>
{showControls && (
<>
<div style={{ fontSize: 11 }}>
<strong>animation trajectory</strong>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('outside')}
checked={animationTrajectory === 'outside'}
/>{' '}
outside
</label>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('center')}
checked={animationTrajectory === 'center'}
/>{' '}
center
</label>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('min')}
checked={animationTrajectory === 'min'}
/>{' '}
min
</label>
<div style={{ fontSize: 12 }}>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('max')}
checked={animationTrajectory === 'max'}
type="checkbox"
onChange={() => setUseAnimatedComponents(!useAnimatedComponents)}
checked={useAnimatedComponents}
/>{' '}
max
enable animation
</label>
&nbsp;&nbsp;&nbsp;
{useAnimatedComponents && (
<>
<strong>animation trajectory</strong>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('outside')}
checked={animationTrajectory === 'outside'}
/>{' '}
outside
</label>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('center')}
checked={animationTrajectory === 'center'}
/>{' '}
center
</label>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('min')}
checked={animationTrajectory === 'min'}
/>{' '}
min
</label>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('max')}
checked={animationTrajectory === 'max'}
/>{' '}
max
</label>
</>
)}
</div>
<button onClick={() => setDataToggle(!dataToggle)}>Update scales</button>
{useAnimatedComponents && (
<button onClick={() => setDataToggle(!dataToggle)}>Update scales</button>
)}
</>
)}
</>
Expand Down
52 changes: 30 additions & 22 deletions packages/visx-demo/src/sandboxes/visx-pattern/Example.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,21 @@ export type PatternProps = {
margin?: typeof defaultMargin;
};

const Patterns: React.FC<{ id: string }>[] = [
const Patterns: React.FC<{ id: string; prefersReducedMotion?: boolean }>[] = [
({ id }) => <PatternLines id={id} height={6} width={6} stroke="black" strokeWidth={1} />,
({ id }) => (
({ id, prefersReducedMotion }) => (
<CustomPattern id={id} width={10} height={10}>
<animateTransform
attributeType="xml"
attributeName="patternTransform"
type="translate"
from="0 0"
to="0 30"
dur="10s"
repeatCount="indefinite"
/>
{!prefersReducedMotion && (
<animateTransform
attributeType="xml"
attributeName="patternTransform"
type="translate"
from="0 0"
to="0 30"
dur="10s"
repeatCount="indefinite"
/>
)}
<circle cx={5} cy={5} r="3" stroke="none" fill="black" transform-origin="center" />
</CustomPattern>
),
Expand Down Expand Up @@ -78,21 +80,23 @@ const Patterns: React.FC<{ id: string }>[] = [
/>
),
({ id }) => <PatternCircles id={id} height={10} width={10} fill="black" complement />,
({ id }) => {
({ id, prefersReducedMotion }) => {
const width = 10;
const height = 10;

return (
<CustomPattern id={id} width={width} height={height}>
<animateTransform
attributeType="xml"
attributeName="patternTransform"
type="translate"
from="0 0"
to="50 0"
dur="10s"
repeatCount="indefinite"
/>
{!prefersReducedMotion && (
<animateTransform
attributeType="xml"
attributeName="patternTransform"
type="translate"
from="0 0"
to="50 0"
dur="10s"
repeatCount="indefinite"
/>
)}
<path
d={`M 0 ${height / 2} c ${height / 8} ${-height / 4} , ${(height * 3) / 8} ${-height /
4} , ${height / 2} 0
Expand All @@ -115,6 +119,10 @@ const Patterns: React.FC<{ id: string }>[] = [
];

export default function Example({ width, height, margin = defaultMargin }: PatternProps) {
// use non-animated components if prefers-reduced-motion is set
const prefersReducedMotionQuery = window?.matchMedia('(prefers-reduced-motion: reduce)');
const prefersReducedMotion = !prefersReducedMotionQuery || !!prefersReducedMotionQuery.matches;

const numColumns = 3;
const numRows = Patterns.length / numColumns;
const columnWidth = Math.max((width - margin.left - margin.right) / numColumns, 0);
Expand All @@ -132,7 +140,7 @@ export default function Example({ width, height, margin = defaultMargin }: Patte
return (
<React.Fragment key={id}>
{/** Like SVG <defs />, Patterns are rendered with an id */}
<Pattern id={id} />
<Pattern id={id} prefersReducedMotion={prefersReducedMotion} />

{/** And are then referenced for a style attribute. */}
<Bar
Expand Down
Loading

0 comments on commit d717366

Please sign in to comment.