UNPKG

@atlaskit/motion

Version:

A set of utilities to apply motion in your application.

105 lines (102 loc) 4.44 kB
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; var _excluded = ["children"]; import React, { Fragment, useRef } from 'react'; import { isReducedMotion } from '../utils/accessibility'; import { easeInOut } from '../utils/curves'; import { durations } from '../utils/durations'; import { useRequestAnimationFrame, useSetTimeout } from '../utils/timer-hooks'; import { useElementRef } from '../utils/use-element-ref'; import { useLayoutEffect } from '../utils/use-layout-effect'; import { useSnapshotBeforeUpdate } from '../utils/use-snapshot-before-update'; /** * `useResizingHeight` animates height changes over state changes. If the height hasn't changed nothing will happen. * * __WARNING__: Potentially janky. This hook animates height which is * [notoriously unperformant](https://firefox-source-docs.mozilla.org/performance/bestpractices.html#Get_familiar_with_the_pipeline_that_gets_pixels_to_the_screen). * Test your app over low powered devices, you may want to avoid this if you can see it impacting FPS. * * See [examples](https://atlaskit.atlassian.com/packages/design-system/motion/docs/resizing-motions). */ export var useResizingHeight = function useResizingHeight() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref$duration = _ref.duration, calcDuration = _ref$duration === void 0 ? function () { return durations.medium; } : _ref$duration, _ref$timingFunction = _ref.timingFunction, calcTimingFunction = _ref$timingFunction === void 0 ? function () { return easeInOut; } : _ref$timingFunction; var prevDimensions = useRef(); var _useElementRef = useElementRef(), _useElementRef2 = _slicedToArray(_useElementRef, 2), element = _useElementRef2[0], setElementRef = _useElementRef2[1]; // We cleanup on the next effect to prevent the previous timeout being called during // the next motion - as now the timeout has essentially been extended! var setTimeout = useSetTimeout({ cleanup: 'next-effect' }); var requestAnimationFrame = useRequestAnimationFrame(); useSnapshotBeforeUpdate(function () { if (isReducedMotion() || !element) { return; } prevDimensions.current = element.getBoundingClientRect(); }); useLayoutEffect(function () { if (isReducedMotion() || !element || !prevDimensions.current) { return; } // We might already be animating. // Because of that we need to expand to the destination height first. element.setAttribute('style', ''); var nextDimensions = element.getBoundingClientRect(); if (nextDimensions.height === prevDimensions.current.height) { return; } var duration = calcDuration(prevDimensions.current.height, nextDimensions.height); var newStyles = { height: "".concat(prevDimensions.current.height, "px"), willChange: 'height', transitionProperty: 'height', transitionDuration: "".concat(duration, "ms"), boxSizing: 'border-box', transitionTimingFunction: calcTimingFunction(prevDimensions.current.height, nextDimensions.height, duration) }; Object.assign(element.style, newStyles); // We split this over two animation frames so the DOM has enough time to flush the changes. // We are deliberately not skipping this frame if another render happens - if we do the motion doesn't finish properly. requestAnimationFrame(function () { requestAnimationFrame(function () { if (!element) { return; } element.style.height = "".concat(nextDimensions.height, "px"); setTimeout(function () { if (!element) { return; } element.setAttribute('style', ''); }, duration); }); }); }); return { ref: setElementRef }; }; /** * __ResizingHeight__ * * Component which consumes the useResizingHook() under-the-hood. Its props are the same as the hooks opts. * * See [examples](https://atlaskit.atlassian.com/packages/design-system/motion/docs/resizing-motions). */ export var ResizingHeight = function ResizingHeight(_ref2) { var children = _ref2.children, props = _objectWithoutProperties(_ref2, _excluded); var resizing = useResizingHeight(props); return /*#__PURE__*/React.createElement(Fragment, null, children(resizing)); };