UNPKG

@atlaskit/page-layout

Version:

A collection of components which let you compose an application's page layout.

253 lines (242 loc) 10.4 kB
import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { bind } from 'bind-event-listener'; import noop from '@atlaskit/ds-lib/noop'; import { isReducedMotion } from '@atlaskit/motion'; import { UNSAFE_useMediaQuery as useMediaQuery } from '@atlaskit/primitives/responsive'; import { COLLAPSED_LEFT_SIDEBAR_WIDTH, DEFAULT_LEFT_SIDEBAR_WIDTH, IS_SIDEBAR_COLLAPSING } from '../common/constants'; import { getPageLayoutSlotCSSSelector } from '../common/utils'; import { SidebarResizeContext } from './sidebar-resize-context'; var handleDataAttributesAndCb = function handleDataAttributesAndCb() { var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : noop; var leftSidebarState = arguments.length > 1 ? arguments[1] : undefined; document.documentElement.removeAttribute(IS_SIDEBAR_COLLAPSING); callback(leftSidebarState); }; var leftSidebarSelector = getPageLayoutSlotCSSSelector('left-sidebar'); // eslint-disable-next-line @repo/internal/react/require-jsdoc export var SidebarResizeController = function SidebarResizeController(_ref) { var children = _ref.children, onExpand = _ref.onLeftSidebarExpand, onCollapse = _ref.onLeftSidebarCollapse; var _useState = useState({ isFlyoutOpen: false, isResizing: false, isLeftSidebarCollapsed: false, leftSidebarWidth: 0, lastLeftSidebarWidth: 0, flyoutLockCount: 0, isFixed: true, hasInit: false }), _useState2 = _slicedToArray(_useState, 2), leftSidebarState = _useState2[0], setLeftSidebarState = _useState2[1]; var leftSidebarWidth = leftSidebarState.leftSidebarWidth, lastLeftSidebarWidth = leftSidebarState.lastLeftSidebarWidth, isResizing = leftSidebarState.isResizing, flyoutLockCount = leftSidebarState.flyoutLockCount, isFixed = leftSidebarState.isFixed, isLeftSidebarCollapsed = leftSidebarState.isLeftSidebarCollapsed, isFlyoutOpen = leftSidebarState.isFlyoutOpen, hasInit = leftSidebarState.hasInit; // We put the latest callbacks into a ref so we can always have the latest // functions in our transitionend listeners var stableRef = useRef({ onExpand: onExpand, onCollapse: onCollapse }); useEffect(function () { stableRef.current = { onExpand: onExpand, onCollapse: onCollapse }; }, [onExpand, onCollapse]); var transition = useRef(null); var mobileMediaQuery = useMediaQuery('below.sm'); var isOpen = mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches ? isFlyoutOpen : !isLeftSidebarCollapsed; var expandLeftSidebar = useCallback(function () { var _transition$current2, _transition$current3; if (isOpen) { return; } // If the user is at a mobile viewport when this runs, we handle it differently // We don't expand at mobile widths; instead we use a flyout which is treated the same otherwise if (mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches) { var _transition$current; var flyoutOpenSidebarState = { isResizing: false, isLeftSidebarCollapsed: true, leftSidebarWidth: COLLAPSED_LEFT_SIDEBAR_WIDTH, lastLeftSidebarWidth: leftSidebarWidth, isFlyoutOpen: true, flyoutLockCount: 0, isFixed: isFixed, hasInit: hasInit }; setLeftSidebarState(flyoutOpenSidebarState); // Flush the desktop transitions, cleanup, and call the `onExpand` still (_transition$current = transition.current) === null || _transition$current === void 0 || _transition$current.complete(); handleDataAttributesAndCb(stableRef.current.onExpand, flyoutOpenSidebarState); return; } if (isResizing || !isLeftSidebarCollapsed || // already expanding ((_transition$current2 = transition.current) === null || _transition$current2 === void 0 ? void 0 : _transition$current2.action) === 'expand') { return; } // flush existing transition (_transition$current3 = transition.current) === null || _transition$current3 === void 0 || _transition$current3.complete(); var width = Math.max(lastLeftSidebarWidth, DEFAULT_LEFT_SIDEBAR_WIDTH); var updatedLeftSidebarState = { isLeftSidebarCollapsed: false, isFlyoutOpen: false, leftSidebarWidth: width, lastLeftSidebarWidth: lastLeftSidebarWidth, isResizing: isResizing, flyoutLockCount: flyoutLockCount, isFixed: isFixed, hasInit: hasInit }; setLeftSidebarState(updatedLeftSidebarState); function finish() { handleDataAttributesAndCb(stableRef.current.onExpand, updatedLeftSidebarState); } var sidebar = document.querySelector(leftSidebarSelector); // onTransitionEnd isn't triggered when a user prefers reduced motion if (isReducedMotion() || !sidebar) { finish(); return; } var unbindEvent = bind(sidebar, { type: 'transitionend', listener: function listener(event) { if (event.target === sidebar && event.propertyName === 'width') { var _transition$current4; (_transition$current4 = transition.current) === null || _transition$current4 === void 0 || _transition$current4.complete(); } } }); var value = { action: 'expand', complete: function complete() { value.abort(); finish(); }, abort: function abort() { unbindEvent(); transition.current = null; } }; transition.current = value; }, [isOpen, mobileMediaQuery, isResizing, isLeftSidebarCollapsed, lastLeftSidebarWidth, flyoutLockCount, isFixed, leftSidebarWidth, hasInit]); var collapseLeftSidebar = useCallback(function (event, collapseWithoutTransition) { var _transition$current6, _transition$current7; if (!isOpen) { return; } // If the user is at a mobile viewport when this runs, we handle it differently // We don't collapse at mobile widths; instead we close the flyout. if (mobileMediaQuery !== null && mobileMediaQuery !== void 0 && mobileMediaQuery.matches) { var _transition$current5; var flyoutCloseSidebarState = { isResizing: false, isLeftSidebarCollapsed: true, leftSidebarWidth: COLLAPSED_LEFT_SIDEBAR_WIDTH, lastLeftSidebarWidth: lastLeftSidebarWidth, isFlyoutOpen: false, flyoutLockCount: 0, isFixed: isFixed, hasInit: hasInit }; setLeftSidebarState(flyoutCloseSidebarState); // Flush the desktop transitions, cleanup, and call the `onCollapse` still (_transition$current5 = transition.current) === null || _transition$current5 === void 0 || _transition$current5.complete(); handleDataAttributesAndCb(stableRef.current.onCollapse, flyoutCloseSidebarState); return; } if (isResizing || isLeftSidebarCollapsed || // already collapsing ((_transition$current6 = transition.current) === null || _transition$current6 === void 0 ? void 0 : _transition$current6.action) === 'collapse') { return; } // flush existing transition (_transition$current7 = transition.current) === null || _transition$current7 === void 0 || _transition$current7.complete(); // data-attribute is used as a CSS selector to sync the hiding/showing // of the nav contents with expand/collapse animation document.documentElement.setAttribute(IS_SIDEBAR_COLLAPSING, 'true'); var updatedLeftSidebarState = { isLeftSidebarCollapsed: true, isFlyoutOpen: false, leftSidebarWidth: COLLAPSED_LEFT_SIDEBAR_WIDTH, lastLeftSidebarWidth: leftSidebarWidth, isResizing: isResizing, flyoutLockCount: flyoutLockCount, isFixed: isFixed, hasInit: hasInit }; setLeftSidebarState(updatedLeftSidebarState); function finish() { handleDataAttributesAndCb(stableRef.current.onCollapse, updatedLeftSidebarState); } var sidebar = document.querySelector(leftSidebarSelector); // onTransitionEnd isn't triggered when a user prefers reduced motion if (collapseWithoutTransition || isReducedMotion() || !sidebar) { finish(); return; } var unbindEvent = bind(sidebar, { type: 'transitionend', listener: function listener(event) { if (sidebar === event.target && event.propertyName === 'width') { var _transition$current8; (_transition$current8 = transition.current) === null || _transition$current8 === void 0 || _transition$current8.complete(); } } }); var value = { action: 'collapse', complete: function complete() { value.abort(); finish(); }, abort: function abort() { unbindEvent(); transition.current = null; } }; transition.current = value; }, [isOpen, mobileMediaQuery, isResizing, isLeftSidebarCollapsed, leftSidebarWidth, flyoutLockCount, isFixed, lastLeftSidebarWidth, hasInit]); /** * Conditionally toggle the expanding or collapsing the sidebars. * This supports our mobile flyout mode as well. */ var toggleLeftSidebar = useCallback(function (event, collapseWithoutTransition) { if (isOpen) { collapseLeftSidebar(event, collapseWithoutTransition); } else { expandLeftSidebar(); } }, [isOpen, expandLeftSidebar, collapseLeftSidebar]); // Make sure we finish any lingering transitions when unmounting useEffect(function mount() { return function unmount() { var _transition$current9; (_transition$current9 = transition.current) === null || _transition$current9 === void 0 || _transition$current9.abort(); }; }, []); var context = useMemo(function () { return { isLeftSidebarCollapsed: !isOpen, // Technically this isn't quite true, but with mobile it's a bit safer if products are using this to roll their own collapse/expand expandLeftSidebar: expandLeftSidebar, collapseLeftSidebar: collapseLeftSidebar, leftSidebarState: leftSidebarState, setLeftSidebarState: setLeftSidebarState, toggleLeftSidebar: toggleLeftSidebar }; }, [isOpen, expandLeftSidebar, collapseLeftSidebar, leftSidebarState, toggleLeftSidebar]); return /*#__PURE__*/React.createElement(SidebarResizeContext.Provider, { value: context }, children); };