@workday/canvas-kit-docs
Version:
Documentation components of Canvas Kit components
66 lines (65 loc) • 3.66 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import * as React from 'react';
import { PopupStack } from '@workday/canvas-kit-popup-stack';
import { DeleteButton } from '@workday/canvas-kit-react/button';
import { changeFocus, useUniqueId } from '@workday/canvas-kit-react/common';
import { Box, Flex } from '@workday/canvas-kit-react/layout';
import { Popup, useCloseOnEscape, useCloseOnOutsideClick, usePopupModel, useReturnFocus, } from '@workday/canvas-kit-react/popup';
import { createStyles, px2rem } from '@workday/canvas-kit-styling';
import { system } from '@workday/canvas-tokens-web';
const cardStyles = createStyles({
width: px2rem(320),
});
const flexStyles = createStyles({
gap: system.gap.md,
padding: system.padding.xs,
});
const layoutStyles = createStyles({
gap: system.gap.md,
alignItems: 'flex-start',
flexDirection: 'column',
});
const bodyStyles = createStyles({
marginBlock: '0',
});
/**
* Portals popup content into a sentinel div after the trigger (via PopupStack.pushStackContext)
* so DOM reading order matches page context. Uses a two-phase open so pushStackContext runs
* before Popper mounts and registers with the stack.
*/
export const InlinePortalPopup = () => {
const messageId = useUniqueId();
const sentinelRef = React.useRef(null);
const initialFocusRef = React.useRef(null);
const model = usePopupModel({ initialFocusRef });
const visible = model.state.visibility !== 'hidden';
const [portalReady, setPortalReady] = React.useState(false);
useCloseOnOutsideClick(model);
useCloseOnEscape(model);
useReturnFocus(model);
// Defer initial focus until Popper content is mounted. useInitialFocus runs when visible while
// stackRef can still point at an empty sentinel (second open) and throws.
React.useEffect(() => {
if (!visible || !portalReady) {
return;
}
const el = initialFocusRef.current;
if (!el) {
return;
}
requestAnimationFrame(() => {
changeFocus(el);
});
}, [visible, portalReady]);
React.useLayoutEffect(() => {
if (visible && sentinelRef.current && !portalReady) {
PopupStack.pushStackContext(sentinelRef.current);
setPortalReady(true);
}
if (!visible && portalReady) {
PopupStack.popStackContext(sentinelRef.current);
setPortalReady(false);
}
}, [visible, portalReady]);
return (_jsxs(Flex, { cs: layoutStyles, children: [_jsx(Flex, { children: _jsxs(Popup, { model: model, children: [_jsx(Popup.Target, { as: DeleteButton, children: "Delete Item" }), _jsx("div", { ref: sentinelRef }), visible && portalReady ? (_jsx(Popup.Popper, { placement: "top", children: _jsxs(Popup.Card, { cs: cardStyles, "aria-describedby": messageId, children: [_jsx(Popup.Heading, { children: "Delete Item" }), _jsx(Popup.Body, { children: _jsx(Box, { as: "p", id: messageId, cs: bodyStyles, children: "Are you sure you'd like to delete the item titled 'My Item'?" }) }), _jsxs(Flex, { cs: flexStyles, children: [_jsx(Popup.CloseButton, { ref: initialFocusRef, children: "Cancel" }), _jsx(Popup.CloseButton, { as: DeleteButton, children: "Delete" })] })] }) })) : null] }) }), _jsx("p", { children: "This content should come after the popup in the reading order. When someone uses a screen reader or moves through the page with tabbing, they will read or reach this content only after the popup content is shown. This helps keep the page easy to follow and makes sure that the popup is announced before any information that comes next." })] }));
};