@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
JavaScript
/** @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;