@atlaskit/motion
Version:
A set of utilities to apply motion in your application.
105 lines (102 loc) • 4.44 kB
JavaScript
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));
};