UNPKG

@atlaskit/modal-dialog

Version:

A modal dialog displays content that requires user interaction, in a layer above the page.

98 lines (97 loc) 3.43 kB
/** @jsx jsx */ import React, { forwardRef, useEffect, useRef, useState } from 'react'; import { css, jsx } from '@emotion/react'; import { bind } from 'bind-event-listener'; import rafSchedule from 'raf-schd'; import mergeRefs from '@atlaskit/ds-lib/merge-refs'; import noop from '@atlaskit/ds-lib/noop'; import useLazyCallback from '@atlaskit/ds-lib/use-lazy-callback'; import useStateRef from '@atlaskit/ds-lib/use-state-ref'; import FocusRing from '@atlaskit/focus-ring'; import { keylineColor, keylineHeight } from '../constants'; const baseStyles = css({ display: 'inherit', margin: "var(--ds-space-0, 0px)", flex: 'inherit', flexDirection: 'inherit', flexGrow: 1, overflowX: 'hidden', overflowY: 'auto', '@media (min-width: 480px)': { height: 'unset', overflowY: 'auto' } }); const topKeylineStyles = css({ borderTop: `${keylineHeight}px solid ${keylineColor}` }); const bottomKeylineStyles = css({ borderBottom: `${keylineHeight}px solid ${keylineColor}` }); /** * A container that shows top and bottom keylines when the * content overflows into the scrollable element. */ const ScrollContainer = /*#__PURE__*/forwardRef((props, ref) => { const { testId, children } = props; const [hasSiblings, setSiblings] = useStateRef({ previous: false, next: false }); const [showContentFocus, setContentFocus] = useState(false); const [showTopKeyline, setTopKeyline] = useState(false); const [showBottomKeyline, setBottomKeyline] = useState(false); const scrollableRef = useRef(null); const setLazySiblings = useLazyCallback(setSiblings); const setLazyContentFocus = useLazyCallback(rafSchedule(() => { const target = scrollableRef.current; target && setContentFocus(target.scrollHeight > target.clientHeight); })); const setLazyKeylines = useLazyCallback(rafSchedule(() => { const target = scrollableRef.current; if (target) { const scrollableDistance = target.scrollHeight - target.clientHeight; if (hasSiblings.current.previous) { setTopKeyline(target.scrollTop > keylineHeight); } if (hasSiblings.current.next) { setBottomKeyline(target.scrollTop <= scrollableDistance - keylineHeight); } } })); useEffect(() => { const target = scrollableRef.current; const unbindWindowEvent = bind(window, { type: 'resize', listener: setLazyKeylines }); const unbindTargetEvent = target ? bind(target, { type: 'scroll', listener: setLazyKeylines }) : noop; setLazyContentFocus(); setLazyKeylines(); setLazySiblings({ previous: Boolean(target === null || target === void 0 ? void 0 : target.previousElementSibling), next: Boolean(target === null || target === void 0 ? void 0 : target.nextElementSibling) }); return () => { unbindWindowEvent(); unbindTargetEvent(); }; }, [setLazyContentFocus, setLazyKeylines, setLazySiblings]); return jsx(FocusRing, { isInset: true }, jsx("div", { // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex tabIndex: showContentFocus ? 0 : undefined, "data-testid": testId && `${testId}--scrollable`, ref: mergeRefs([ref, scrollableRef]), css: [baseStyles, showTopKeyline && topKeylineStyles, showBottomKeyline && bottomKeylineStyles] }, children)); }); ScrollContainer.displayName = 'ScrollContainer'; export default ScrollContainer;