UNPKG

@workday/canvas-kit-preview-react

Version:

Canvas Kit Preview is made up of components that have the full design and a11y review, are part of the DS ecosystem and are approved for use in product. The API's could be subject to change, but not without strong communication and migration strategies.

220 lines (219 loc) 8.83 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SelectMenu = exports.menuAnimationDuration = void 0; const react_1 = __importStar(require("react")); const common_1 = require("@workday/canvas-kit-react/common"); const popup_1 = require("@workday/canvas-kit-react/popup"); const tokens_1 = require("@workday/canvas-kit-react/tokens"); exports.menuAnimationDuration = 200; const menuBorderStyles = (theme, error) => { let borderColor = theme.canvas.palette.common.focusOutline; let dividerBorderColor = borderColor; let dividerBorderWidth = 1; if (error === common_1.ErrorType.Error) { borderColor = theme.canvas.palette.error.main; dividerBorderColor = borderColor; } else if (error === common_1.ErrorType.Alert) { borderColor = theme.canvas.palette.alert.darkest; dividerBorderColor = theme.canvas.palette.alert.main; dividerBorderWidth = 2; } const dividerBorder = `${dividerBorderWidth}px solid ${dividerBorderColor}`; return { borderColor: borderColor, // Render the divider using a pseudo-element '&:before': { backgroundColor: tokens_1.colors.soap400, borderLeft: dividerBorder, borderRight: dividerBorder, boxSizing: 'border-box', content: '""', display: 'block', height: 1, position: 'absolute', width: '100%', '[data-popper-placement="bottom"] &': { top: 0, }, '[data-popper-placement="top"] &': { bottom: 0, }, }, }; }; const menuListBorderStyles = (theme, error) => { let borderColor = theme.canvas.palette.common.focusOutline; let borderWidth = 1; if (error === common_1.ErrorType.Error) { borderColor = theme.canvas.palette.error.main; } else if (error === common_1.ErrorType.Alert) { borderColor = theme.canvas.palette.alert.main; borderWidth = 2; } const border = `${borderWidth}px solid ${borderColor}`; return { borderLeft: border, borderRight: border, '[data-popper-placement="bottom"] &': { borderBottom: border, }, '[data-popper-placement="top"] &': { borderTop: border, }, }; }; const Menu = (0, common_1.styled)('div')({ backgroundColor: tokens_1.colors.frenchVanilla100, border: `1px solid ${tokens_1.inputColors.border}`, boxSizing: 'border-box', position: 'relative', transition: `opacity ${exports.menuAnimationDuration}ms`, '[data-popper-placement="bottom"] &': { borderRadius: `0 0 ${tokens_1.borderRadius.m} ${tokens_1.borderRadius.m}`, borderTop: 0, }, '[data-popper-placement="top"] &': { borderRadius: `${tokens_1.borderRadius.m} ${tokens_1.borderRadius.m} 0 0`, borderBottom: 0, }, }, ({ error, theme }) => ({ ...menuBorderStyles(theme, error), }), ({ visibility }) => ({ opacity: visibility === 'opening' || visibility === 'opened' || visibility === 'close' ? 1 : 0, }), ({ width }) => ({ width: width, })); const MenuList = (0, common_1.styled)('ul')({ listStyle: 'none', margin: 0, maxHeight: 200, outline: 'none', overflowY: 'auto', padding: 0, }, ({ error, theme }) => ({ ...menuListBorderStyles(theme, error), })); const generatePopperOptions = (props) => { const { menuRef, placement, shouldAutoFlip, shouldAutoFocus } = props; let fallbackPlacements = []; if (shouldAutoFlip) { fallbackPlacements = placement === 'top' ? ['bottom'] : ['top']; } const modifiers = [ { name: 'flip', options: { fallbackPlacements, }, }, { name: 'offset', options: { offset: () => { const skidding = 0; // Displace menu towards the button to obscure the bottom // edge of the button and to create a smooth visual // connection between the button and the menu const distance = -parseInt(tokens_1.borderRadius.m, 10); return [skidding, distance]; }, }, }, { name: 'preventOverflow', options: { // Ensure the menu stays aligned with its reference (button), // even if that means the menu is pushed out of view mainAxis: false, }, }, { // Disable the fallbackModifier as SelectMenu is properly handled by the // flip modifier through shouldAutoFlip prop name: 'fallbackModifier', enabled: false, }, ]; return { modifiers, // TODO: Consider using a more general-purpose focus function here rather // than relying on Popper's onFirstUpdate for better control over how // focus is managed onFirstUpdate: () => { if (shouldAutoFocus && menuRef && menuRef.current) { menuRef.current.focus(); } }, }; }; /** * @deprecated ⚠️ `SelectMenu` in Preview has been deprecated and will be removed in a future major version. Please use [`Select` in Main](https://workday.github.io/canvas-kit/?path=/docs/components-inputs-select--basic) instead. */ const SelectMenu = ({ buttonRef, children, error, menuRef, onClose, placement = 'bottom', shouldAutoFlip = true, shouldAutoFocus = true, visibility = 'closed', ...elemProps }) => { const model = (0, popup_1.usePopupModel)({ initialVisibility: 'visible', returnFocusRef: buttonRef, onHide: onClose, }); const [width, setWidth] = (0, react_1.useState)(0); const handleWidthChange = (0, react_1.useCallback)(() => { if (buttonRef && buttonRef.current && visibility !== 'closed') { const newMenuWidth = buttonRef.current.clientWidth + 2; setWidth(newMenuWidth); } }, [buttonRef, visibility]); (0, react_1.useLayoutEffect)(() => { handleWidthChange(); }, [handleWidthChange]); // TODO: Figure out a better way to handle width changes in the reference button. // Seems like we should resize the menu when the reference button width changes, not // necessarily when the window resizes. Resizing the menu on window resize addresses // the case when `grow = true` and the user resizes the browser window while the // menu is opened, but doesn't address cases where the reference button size changes // through other means. (0, react_1.useEffect)(() => { // Update menu width state on resize to ensure menu resizes as window resizes window.addEventListener('resize', handleWidthChange); return () => { window.removeEventListener('resize', handleWidthChange); }; }, [handleWidthChange]); (0, popup_1.useCloseOnEscape)(model); (0, popup_1.useCloseOnOutsideClick)(model); (0, popup_1.useReturnFocus)(model); (0, popup_1.useTransferOnFullscreenExit)(model); return (react_1.default.createElement(popup_1.Popper, { placement: placement, anchorElement: buttonRef, popperOptions: generatePopperOptions({ menuRef, placement, shouldAutoFlip, shouldAutoFocus, }), ref: model.state.stackRef }, react_1.default.createElement(Menu, { error: error, visibility: visibility, width: width }, react_1.default.createElement(MenuList, { error: error, ref: menuRef, role: "listbox", tabIndex: -1, ...elemProps }, children)))); }; exports.SelectMenu = SelectMenu;