UNPKG

@atlaskit/drawer

Version:

A drawer is a panel that slides in from the left side of the screen.

221 lines (214 loc) • 8.17 kB
/* drawer-top-layer.tsx generated by @compiled/babel-plugin v3.0.2 */ import _extends from "@babel/runtime/helpers/extends"; 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'; const LOCAL_CURRENT_SURFACE_CSS_VAR = '--ds-elevation-surface-current'; const slideInKeyframes = null; const slideOutKeyframes = null; const styles = { root: "_19pkidpf _2hwxidpf _otyridpf _18u0idpf _152tidpf _1e02idpf _rjxp1wug _4t3i1496 _p12fauwl", enter: "_j7hqntq4", exit: "_j7hqcljl", surface: "_1reo15vq _18m915vq _1e0c1txw _1bsb1osq _4t3i1osq _bfhk1bhr _1q1l1bhr _ect4ttxp" }; const widthStyles = { narrow: "_1bsb1gwv", medium: "_1bsb1y37", wide: "_1bsb1ogm", extended: "_1bsb1mwr", full: "_1bsbauwl" }; const 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({ label, titleId }) { if (label) { return { 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({ width = 'narrow', isOpen, shouldReturnFocus = true, onKeyDown, testId, children, onClose, onCloseComplete, onOpenComplete, label, titleId, enterFrom = 'left' }) { /** * Points to the panel surface. Passed to `onOpenComplete` / `onCloseComplete`. */ const 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). const 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. const setContentEl = useCallback(el => { contentRef.current = el; if (el) { lastContentElRef.current = el; } }, []); // Analytics-wrapped close handlers, one per legacy trigger. const handleEscapeClose = usePlatformLeafEventHandler({ fn: (evt, analyticsEvent) => onClose === null || onClose === void 0 ? void 0 : onClose(evt, analyticsEvent), action: 'dismissed', componentName: 'drawer', packageName: "@atlaskit/drawer", packageVersion: "14.3.5", analyticsData: { trigger: 'escKey' } }); const handleBlanketClose = usePlatformLeafEventHandler({ fn: (evt, analyticsEvent) => onClose === null || onClose === void 0 ? void 0 : onClose(evt, analyticsEvent), action: 'dismissed', componentName: 'drawer', packageName: "@atlaskit/drawer", packageVersion: "14.3.5", analyticsData: { trigger: 'blanket' } }); const handleBackButtonClose = usePlatformLeafEventHandler({ fn: (evt, analyticsEvent) => 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. const handleDialogClose = useCallback(({ reason }) => { const event = createCloseEvent({ 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. const handleKeyDown = useCallback(evt => { onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(evt); }, [onKeyDown]); useEffect(() => { 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). const handleEnterFinish = useCallback(() => { onOpenComplete === null || onOpenComplete === void 0 ? 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. const handleExitFinish = useCallback(() => { var _contentRef$current; onCloseComplete === null || onCloseComplete === void 0 ? 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]); const accessibleName = getAccessibleName({ label, 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)))); }