UNPKG

@atlaskit/motion

Version:

A set of utilities to apply motion in your application.

123 lines (119 loc) 5.85 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _typeof = require("@babel/runtime/helpers/typeof"); Object.defineProperty(exports, "__esModule", { value: true }); exports.useStaggeredEntrance = exports.default = void 0; var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _react = _interopRequireWildcard(require("react")); var _noop = _interopRequireDefault(require("@atlaskit/ds-lib/noop")); var _useLayoutEffect = require("../utils/use-layout-effect"); var _useUniqueId = require("../utils/use-unique-id"); function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } var StaggeredEntranceContext = /*#__PURE__*/(0, _react.createContext)(function () { return { isReady: true, delay: 0, ref: _noop.default }; }); var useStaggeredEntrance = exports.useStaggeredEntrance = function useStaggeredEntrance() { var indentifier = (0, _useUniqueId.useUniqueId)(); var context = (0, _react.useContext)(StaggeredEntranceContext); return context(indentifier); }; /** * For a list of elements that need to animate in, * this should be used in conjunction with entering components. * This does not need Javascript to execute so it will run immediately for any SSR rendered React apps before the JS has executed. * * Will dynamically add delay to each child entering component. * Unfortunately all entering components _NEED_ to be a direct descendant. */ var StaggeredEntrance = function StaggeredEntrance(_ref) { var children = _ref.children, column = _ref.column, _ref$columns = _ref.columns, columns = _ref$columns === void 0 ? 'responsive' : _ref$columns, _ref$delayStep = _ref.delayStep, delayStep = _ref$delayStep === void 0 ? 50 : _ref$delayStep; var elementRefs = (0, _react.useRef)([]); var indexes = []; var _useState = (0, _react.useState)(function () { if (typeof columns === 'number') { // A hardcoded columns is set so bail out and set it to that! return columns; } if (typeof column === 'number') { // A hardcoded column is set so we will set actualColumns to be 1. return 1; } // We are in "responsive" mode. // So we will be calculating when the Javascript executes on the client how many columns there will be. return 0; }), _useState2 = (0, _slicedToArray2.default)(_useState, 2), actualColumns = _useState2[0], setActualColumns = _useState2[1]; (0, _useLayoutEffect.useLayoutEffect)(function () { // We want to only run this code when we are in "responsive" mode. // It is assumed we are in responsive mode if `columns` is "responsive", // we have children element refs ready to be read (i.e. if there are no children this won't run as well) // and finally that `actualColumns` is `0` - this is because for the first render cycle `actualColumns` will be `0` (set above) // and then after this layout effect runs the value for `actualColumns` will then be calculated and set. if (columns === 'responsive' && elementRefs.current.length && actualColumns === 0) { var currentTop = 0; var numberColumns = 0; if (elementRefs.current.length <= 1) { setActualColumns(1); return; } // We set the current top to the first elements. // We will be comparing this and incrementing the column count // until we hit an element that has a different offset top (or we run out of elements). currentTop = elementRefs.current[0] ? elementRefs.current[0].offsetTop : 0; for (var i = 0; i < elementRefs.current.length; i++) { var child = elementRefs.current[i]; if (!child) { break; } if (currentTop === child.offsetTop) { numberColumns += 1; if (elementRefs.current.length - 1 === i) { setActualColumns(numberColumns); } continue; } setActualColumns(numberColumns); break; } } // We only want this effect to run once - on initial mount. // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return /*#__PURE__*/_react.default.createElement(StaggeredEntranceContext.Provider, { value: function value(id) { if (!indexes.includes(id)) { indexes.push(id); } var isReady = actualColumns > 0; var index = indexes.indexOf(id); var currentColumn = column || index % actualColumns; var currentRow = Math.floor(index / actualColumns); var distanceFromTopLeftElement = currentRow + currentColumn; // We don't want loads of elements to have the same staggered delay as it ends up looking slow for users. // To get around that we calculate the logarithm using `distanceFromTopLeftElement` which ends making // elements appear faster the further away from the top left element. var delay = Math.ceil(Math.log(distanceFromTopLeftElement + 1) * delayStep * 1.5) || 0; return { delay: delay, isReady: isReady, ref: function ref(element) { return elementRefs.current[index] = element; } }; } }, children); }; var _default = exports.default = StaggeredEntrance;