UNPKG

@workday/canvas-kit-react

Version:

The parent module that contains all Workday Canvas Kit React components

123 lines (122 loc) 5.71 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.usePopupStack = void 0; const react_1 = __importDefault(require("react")); const canvas_kit_popup_stack_1 = require("@workday/canvas-kit-popup-stack"); const common_1 = require("@workday/canvas-kit-react/common"); const react_2 = require("@emotion/react"); /** * **Note:** If you're using {@link Popper}, you do not need to use this hook directly. * * This hook will add the `stackRef` element to the {@link PopupStack} on mount and remove on unmount. If * you use `Popper`, the popper `stackRef` is automatically added/removed from the `PopupStack`. The * `PopupStack` is required for proper z-index values to ensure Popups are rendered correct. It is * also required for global listeners like click outside or escape key closing a popup. Without the * `PopupStack`, all popups will close rather than only the topmost one. * * If `ref` is provided, it will be the same as `stackRef`. If `ref` is not provided`, * this hook will create one and return it. * * This hook should be used by all stacked UIs unless using the `Popper` component. * * ```tsx * const model = usePopupModel(); * usePopupStack(model.state.stackRef, model.state.targetRef); * * // add some popup functionality * useCloseOnOutsideClick(model); * useCloseOnEscape(model); * * return ( * <> * <button ref={model.state.targetRef}>Open Popup</button> * {model.state.visibility !== 'hidden' * ? ReactDOM.createPortal(<div>Popup Contents</div>, model.state.stackRef.current) * : null} * </> * ); * ``` * * @param ref This ref will be managed by the PopupStack and should not be managed by React. Do * not apply this stackRef to a React element. Doing so will result in an error. Instead, use this * `stackRef` directly with `ReactDOM.createPortal(<YourComponent>, stackRef.current!)`. This is * definitely strange for React code, but is necessary for the PopupStack to remain framework * agnostic and flexible to integrate with existing `PopupStack` systems. If not provided, this hook * will create one and return that `stackRef` instead. * @param target Usually the trigger of a popup. This will fix `bringToTop` and should be provided * by all ephemeral-type popups (like Tooltips, Select menus, etc). It will also add in clickOutside * detection. */ const usePopupStack = (ref, target) => { const { elementRef, localRef } = (0, common_1.useLocalRef)(ref); const isRTL = (0, common_1.useIsRTL)(); const theme = react_1.default.useContext(react_2.ThemeContext); const { className, style } = (0, common_1.useCanvasThemeToCssVars)(theme, {}); // useState function input ensures we only create a container once. const [popupRef] = react_1.default.useState(() => { const container = canvas_kit_popup_stack_1.PopupStack.createContainer(); elementRef(container); return container; }); // We useLayoutEffect to ensure proper timing of registration of the element to the popup stack. // Without this, the timing is unpredictable when mixed with other frameworks. Other frameworks // should also register as soon as the element is available react_1.default.useLayoutEffect(() => { if (popupRef !== localRef.current) { throw Error(`The 'ref' passed to usePopupStack should not be applied to a React element. This will cause a runtime error where the PopupStack and React compete for the element. Instead use ReactDOM.createPortal(<YourComponent />, ref.current)`); } const targetEl = target ? 'current' in target ? target.current || undefined : target : undefined; const element = localRef.current; canvas_kit_popup_stack_1.PopupStack.add({ element: element, owner: targetEl }); return () => { canvas_kit_popup_stack_1.PopupStack.remove(element); }; }, [localRef, target, popupRef]); // The direction will properly follow the theme via React context, but portals lose the `dir` // hierarchy, so we'll add it back here. react_1.default.useLayoutEffect(() => { var _a, _b; if (isRTL) { (_a = localRef.current) === null || _a === void 0 ? void 0 : _a.setAttribute('dir', 'rtl'); } else { (_b = localRef.current) === null || _b === void 0 ? void 0 : _b.removeAttribute('dir'); } }, [localRef, isRTL]); // theming className react_1.default.useLayoutEffect(() => { const element = localRef.current; element === null || element === void 0 ? void 0 : element.classList.add(className.trim()); return () => { element === null || element === void 0 ? void 0 : element.classList.remove(className.trim()); }; }, [localRef, className]); react_1.default.useLayoutEffect(() => { const element = localRef.current; if (element) { // eslint-disable-next-line guard-for-in for (const key in style) { // @ts-ignore element.style.setProperty(key, style[key]); } } return () => { if (element) { // eslint-disable-next-line guard-for-in for (const key in style) { // @ts-ignore element.style.removeProperty(key, style[key]); } } }; }, [localRef, style]); return localRef; }; exports.usePopupStack = usePopupStack;