diff --git a/packages/visx-demo/.happo.js b/packages/visx-demo/.happo.js index 965a998be..05bc2f2f2 100644 --- a/packages/visx-demo/.happo.js +++ b/packages/visx-demo/.happo.js @@ -28,6 +28,7 @@ module.exports = { targets: { 'chrome-desktop': new RemoteBrowserTarget('chrome', { viewport: '800x552', + prefersReducedMotion: true, }), }, diff --git a/packages/visx-demo/happo/gallery.tsx b/packages/visx-demo/happo/gallery.tsx index 9f0a7ae7f..b17a0b513 100644 --- a/packages/visx-demo/happo/gallery.tsx +++ b/packages/visx-demo/happo/gallery.tsx @@ -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; @@ -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: () => }, - })); +const snapshots: HappoSnapshot[] = examples.map(Example => ({ + // note: this (reasonably) asserts Examples have unique names + component: getComponentName(Example), + variants: { default: () => }, +})); -export default snapshots.concat([ - // needs timeout for animated axes - { - component: '@visx/demo-axis', - variants: { - default: renderWithTimeout(), - }, - }, - // needs timeout for animated axes - { - component: '@visx/demo-xychart', - variants: { - default: renderWithTimeout(), - }, - }, -]); +export default snapshots; diff --git a/packages/visx-demo/src/sandboxes/visx-axis/Example.tsx b/packages/visx-demo/src/sandboxes/visx-axis/Example.tsx index 81fca7cf3..da3c3d804 100644 --- a/packages/visx-demo/src/sandboxes/visx-axis/Example.tsx +++ b/packages/visx-demo/src/sandboxes/visx-axis/Example.tsx @@ -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'; @@ -40,23 +43,51 @@ export type AxisProps = { showControls?: boolean; }; +type AnimationTrajectory = 'outside' | 'center' | 'min' | 'max' | undefined; + +type AxisComponent = React.FC< + SharedAxisProps & { + animationTrajectory: AnimationTrajectory; + } +>; +type GridRowsComponent = React.FC< + GridRowsProps & { + animationTrajectory: AnimationTrajectory; + } +>; +type GridColumnsComponent = React.FC< + GridColumnsProps & { + 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('center'); + // define some types interface AxisDemoProps extends SharedAxisProps { values: ScaleInput[]; } + const AxisComponent: AxisComponent = useAnimatedComponents ? AnimatedAxis : Axis; + const GridRowsComponent: GridRowsComponent = useAnimatedComponents ? AnimatedGridRows : GridRows; + const GridColumnsComponent: GridColumnsComponent = useAnimatedComponents + ? AnimatedGridColumns + : GridColumns; + const axes: AxisDemoProps>[] = useMemo(() => { // toggle between two value ranges to demo animation const linearValues = dataToggle ? [0, 2, 4, 6, 8, 10] : [6, 8, 10, 12]; @@ -144,7 +175,7 @@ export default function Example({ {axes.map(({ scale, values, label, tickFormat }, i) => ( - - - {showControls && ( <> -
- animation trajectory - - - +
+     + {useAnimatedComponents && ( + <> + animation trajectory + + + + + + )}
- + {useAnimatedComponents && ( + + )} )} diff --git a/packages/visx-demo/src/sandboxes/visx-pattern/Example.tsx b/packages/visx-demo/src/sandboxes/visx-pattern/Example.tsx index 644ef17e3..ace500aaa 100644 --- a/packages/visx-demo/src/sandboxes/visx-pattern/Example.tsx +++ b/packages/visx-demo/src/sandboxes/visx-pattern/Example.tsx @@ -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 }) => , - ({ id }) => ( + ({ id, prefersReducedMotion }) => ( - + {!prefersReducedMotion && ( + + )} ), @@ -78,21 +80,23 @@ const Patterns: React.FC<{ id: string }>[] = [ /> ), ({ id }) => , - ({ id }) => { + ({ id, prefersReducedMotion }) => { const width = 10; const height = 10; return ( - + {!prefersReducedMotion && ( + + )} [] = [ ]; 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); @@ -132,7 +140,7 @@ export default function Example({ width, height, margin = defaultMargin }: Patte return ( {/** Like SVG , Patterns are rendered with an id */} - + {/** And are then referenced for a style attribute. */} ( - {renderBarStack && ( - - + - - - + )} {renderBarGroup && ( - - + - - - + )} {renderBarSeries && ( - - - - )} {renderAreaStack && ( - - + - - - - - + )} {renderLineSeries && ( <> - {!renderBarSeries && ( - )} - )} {renderGlyphSeries && ( - )} - - '' : undefined} /> {annotationDataKey && annotationDatum && ( - - + )} {showTooltip && ( diff --git a/packages/visx-demo/src/sandboxes/visx-xychart/ExampleControls.tsx b/packages/visx-demo/src/sandboxes/visx-xychart/ExampleControls.tsx index 1c8505793..a37e6c258 100644 --- a/packages/visx-demo/src/sandboxes/visx-xychart/ExampleControls.tsx +++ b/packages/visx-demo/src/sandboxes/visx-xychart/ExampleControls.tsx @@ -9,6 +9,8 @@ import cityTemperature, { CityTemperature } from '@visx/mock-data/lib/mocks/city import { GlyphCross, GlyphDot, GlyphStar } from '@visx/glyph'; import { curveLinear, curveStep, curveCardinal } from '@visx/curve'; import customTheme from './customTheme'; +import userPrefersReducedMotion from './userPrefersReducedMotion'; +import getAnimatedOrUnanimatedComponents from './getAnimatedOrUnanimatedComponents'; const dateScaleConfig = { type: 'band', paddingInner: 0.3 } as const; const temperatureScaleConfig = { type: 'linear' } as const; @@ -47,7 +49,7 @@ type ProvidedProps = { y: Accessors; date: Accessor; }; - animationTrajectory: AnimationTrajectory; + animationTrajectory?: AnimationTrajectory; annotationDataKey: DataKey | null; annotationDatum?: CityTemperature; annotationLabelPosition: { dx: number; dy: number }; @@ -85,15 +87,18 @@ type ProvidedProps = { theme: XYChartTheme; xAxisOrientation: 'top' | 'bottom'; yAxisOrientation: 'left' | 'right'; -}; +} & ReturnType; type ControlsProps = { children: (props: ProvidedProps) => React.ReactNode; }; export default function ExampleControls({ children }: ControlsProps) { + const [useAnimatedComponents, setUseAnimatedComponents] = useState(!userPrefersReducedMotion()); const [theme, setTheme] = useState(darkTheme); - const [animationTrajectory, setAnimationTrajectory] = useState('center'); + const [animationTrajectory, setAnimationTrajectory] = useState( + 'center', + ); const [gridProps, setGridProps] = useState<[boolean, boolean]>([false, false]); const [showGridRows, showGridColumns] = gridProps; const [xAxisOrientation, setXAxisOrientation] = useState<'top' | 'bottom'>('bottom'); @@ -240,6 +245,7 @@ export default function ExampleControls({ children }: ControlsProps) { theme, xAxisOrientation, yAxisOrientation, + ...getAnimatedOrUnanimatedComponents(useAnimatedComponents), })} {/** This style is used for annotated elements via colorAccessor. */} @@ -743,39 +749,53 @@ export default function ExampleControls({ children }: ControlsProps) {
{/** animation trajectory */}
- axis + grid animation - - - + + {useAnimatedComponents && ( + <> +     + axis + grid animation + + + + + + )}