@atlaskit/drawer
Version:
A drawer is a panel that slides in from the left side of the screen.
228 lines (221 loc) • 8.84 kB
JavaScript
/* drawer-top-layer.tsx generated by @compiled/babel-plugin v3.0.2 */
import _extends from "@babel/runtime/helpers/extends";
import _typeof from "@babel/runtime/helpers/typeof";
import "./drawer-top-layer.compiled.css";
import * as React from 'react';
import { ax, ix } from "@compiled/react/runtime";
import { useCallback, useEffect, useRef } from 'react';
import { cx } from '@compiled/react';
import { bind } from 'bind-event-listener';
import { usePlatformLeafEventHandler } from '@atlaskit/analytics-next/usePlatformLeafEventHandler';
import { createCloseEvent } from '@atlaskit/top-layer/create-close-event';
import { Dialog } from '@atlaskit/top-layer/dialog-content';
import { DialogScrollLock } from '@atlaskit/top-layer/dialog-scroll-lock';
import { EnsureIsInsideDrawerContext } from '../ensure-is-inside-drawer-context';
import { OnCloseContext } from '../on-close-context';
var LOCAL_CURRENT_SURFACE_CSS_VAR = '--ds-elevation-surface-current';
var slideInKeyframes = null;
var slideOutKeyframes = null;
var styles = {
root: "_19pkidpf _2hwxidpf _otyridpf _18u0idpf _152tidpf _1e02idpf _rjxp1wug _4t3i1496 _p12fauwl",
enter: "_j7hqntq4",
exit: "_j7hqcljl",
surface: "_1reo15vq _18m915vq _1e0c1txw _1bsb1osq _4t3i1osq _bfhk1bhr _1q1l1bhr _ect4ttxp"
};
var widthStyles = {
narrow: "_1bsb1gwv",
medium: "_1bsb1y37",
wide: "_1bsb1ogm",
extended: "_1bsb1mwr",
full: "_1bsbauwl"
};
var enterFromStyles = {
left: "_1s8jpwvu _1c2rf1ug",
right: "_1s8j1osq _1c2rf1ug",
top: "_1s8jf1ug _1c2rpwvu",
bottom: "_1s8jf1ug _1c2r1osq"
};
/**
* Resolve the native `<dialog>` accessible name props. Prefer the consumer's
* `label`, then their `titleId`. If neither is supplied (legacy allowed this),
* fall back to a generic name so the dialog is never unlabelled. Note:
* `@atlaskit/drawer` has no i18n setup, so this fallback is English only;
* consumers should pass a localised `label` or `titleId`.
*/
function getAccessibleName(_ref) {
var label = _ref.label,
titleId = _ref.titleId;
if (label) {
return {
label: label
};
}
if (titleId) {
return {
labelledBy: titleId
};
}
return {
label: 'Drawer'
};
}
/**
* **DrawerTopLayer**
*
* Top-layer (`platform-dst-top-layer`) implementation of `Drawer`. Renders a
* native `<dialog>` via `@atlaskit/top-layer`, replacing Portal, Blanket,
* react-focus-lock, react-scrolllock and `@atlaskit/layering` with native
* modality, `::backdrop`, focus trap and return, and `DialogScrollLock`.
*
* The `Dialog` primitive owns the entry and exit animation lifecycle (it keeps
* the host element mounted through the exit transition, then fires
* `onExitFinish`), so no `ExitingPersistence` wrapper is needed: the drawer
* renders `<Dialog isOpen={isOpen}>` directly. `useDrawerStack` tracks stack
* depth so only the foreground drawer shows a `::backdrop`.
*
* `isFocusLockEnabled` is intentionally unsupported: a native modal `<dialog>`
* always traps focus, so `isFocusLockEnabled={false}` is a no-op under this gate.
*/
export function DrawerTopLayer(_ref2) {
var _ref2$width = _ref2.width,
width = _ref2$width === void 0 ? 'narrow' : _ref2$width,
isOpen = _ref2.isOpen,
_ref2$shouldReturnFoc = _ref2.shouldReturnFocus,
shouldReturnFocus = _ref2$shouldReturnFoc === void 0 ? true : _ref2$shouldReturnFoc,
onKeyDown = _ref2.onKeyDown,
testId = _ref2.testId,
children = _ref2.children,
onClose = _ref2.onClose,
onCloseComplete = _ref2.onCloseComplete,
onOpenComplete = _ref2.onOpenComplete,
label = _ref2.label,
titleId = _ref2.titleId,
_ref2$enterFrom = _ref2.enterFrom,
enterFrom = _ref2$enterFrom === void 0 ? 'left' : _ref2$enterFrom;
/**
* Points to the panel surface. Passed to `onOpenComplete` / `onCloseComplete`.
*/
var contentRef = useRef(null);
// Cache the last content element so `onCloseComplete` still receives a node
// after children unmount (with reduced motion `contentRef` can clear before
// `onExitFinish` fires).
var lastContentElRef = useRef(null);
// Callback ref runs at commit (not during render), so both refs stay
// populated without a render-time side effect. `lastContentElRef` only
// overwrites with a non-null node, preserving it across the unmount.
var setContentEl = useCallback(function (el) {
contentRef.current = el;
if (el) {
lastContentElRef.current = el;
}
}, []);
// Analytics-wrapped close handlers, one per legacy trigger.
var handleEscapeClose = usePlatformLeafEventHandler({
fn: function fn(evt, analyticsEvent) {
return onClose === null || onClose === void 0 ? void 0 : onClose(evt, analyticsEvent);
},
action: 'dismissed',
componentName: 'drawer',
packageName: "@atlaskit/drawer",
packageVersion: "14.3.5",
analyticsData: {
trigger: 'escKey'
}
});
var handleBlanketClose = usePlatformLeafEventHandler({
fn: function fn(evt, analyticsEvent) {
return onClose === null || onClose === void 0 ? void 0 : onClose(evt, analyticsEvent);
},
action: 'dismissed',
componentName: 'drawer',
packageName: "@atlaskit/drawer",
packageVersion: "14.3.5",
analyticsData: {
trigger: 'blanket'
}
});
var handleBackButtonClose = usePlatformLeafEventHandler({
fn: function fn(evt, analyticsEvent) {
return onClose === null || onClose === void 0 ? void 0 : onClose(evt, analyticsEvent);
},
action: 'dismissed',
componentName: 'drawer',
packageName: "@atlaskit/drawer",
packageVersion: "14.3.5",
analyticsData: {
trigger: 'backButton'
}
});
// Bridge the Dialog primitive's `onClose({ reason })` to the legacy
// `onClose(event, analyticsEvent)` contract via a synthetic event. Drawer
// has no `shouldCloseOn*` props, so both reasons always forward.
var handleDialogClose = useCallback(function (_ref3) {
var reason = _ref3.reason;
var event = createCloseEvent({
reason: reason
});
if (reason === 'escape') {
handleEscapeClose(event);
} else {
handleBlanketClose(event);
}
}, [handleEscapeClose, handleBlanketClose]);
// Mirror the legacy window `keydown` listener so `onKeyDown` still fires
// while the drawer is open.
var handleKeyDown = useCallback(function (evt) {
onKeyDown === null || onKeyDown === void 0 || onKeyDown(evt);
}, [onKeyDown]);
useEffect(function () {
if (!isOpen) {
return;
}
return bind(window, {
type: 'keydown',
listener: handleKeyDown
});
}, [isOpen, handleKeyDown]);
// `onOpenComplete` once the entry animation settles (via `Dialog`'s
// `onEnterFinish`, which the underlying hook fires for animated,
// non-animated and reduced-motion paths).
var handleEnterFinish = useCallback(function () {
onOpenComplete === null || onOpenComplete === void 0 || onOpenComplete(contentRef.current);
}, [onOpenComplete]);
// `onCloseComplete` once the exit animation settles (via `Dialog`'s
// `onExitFinish`). This is also where a custom `shouldReturnFocus={ref}` is
// honoured: native `<dialog>` restores focus to the trigger at the start of
// close, so the consumer's ref is focused now that the exit is done.
// `shouldReturnFocus={false}` is a documented best-effort limitation; native
// always restores focus to the trigger.
var handleExitFinish = useCallback(function () {
var _contentRef$current;
onCloseComplete === null || onCloseComplete === void 0 || onCloseComplete((_contentRef$current = contentRef.current) !== null && _contentRef$current !== void 0 ? _contentRef$current : lastContentElRef.current);
lastContentElRef.current = null;
if (_typeof(shouldReturnFocus) === 'object' && shouldReturnFocus !== null && shouldReturnFocus !== void 0 && shouldReturnFocus.current) {
shouldReturnFocus.current.focus();
}
}, [onCloseComplete, shouldReturnFocus]);
var accessibleName = getAccessibleName({
label: label,
titleId: titleId
});
return /*#__PURE__*/React.createElement(Dialog, _extends({
isOpen: isOpen,
onClose: handleDialogClose,
onEnterFinish: handleEnterFinish,
onExitFinish: handleExitFinish,
shouldAnimate: true,
xcss: cx(styles.root, widthStyles[width], enterFromStyles[enterFrom]),
enteringAnimationXcss: styles.enter,
exitingAnimationXcss: styles.exit,
testId: testId
}, accessibleName), /*#__PURE__*/React.createElement(DialogScrollLock, {
isOpen: true
}), /*#__PURE__*/React.createElement("div", {
ref: setContentEl,
className: ax([styles.surface])
}, /*#__PURE__*/React.createElement(EnsureIsInsideDrawerContext.Provider, {
value: true
}, /*#__PURE__*/React.createElement(OnCloseContext.Provider, {
value: handleBackButtonClose
}, children))));
}