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.

239 lines (238 loc) 12.7 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.SelectBase = exports.buttonBorderWidth = void 0; const react_1 = __importStar(require("react")); const common_1 = require("@workday/canvas-kit-react/common"); const tokens_1 = require("@workday/canvas-kit-react/tokens"); const canvas_system_icons_web_1 = require("@workday/canvas-system-icons-web"); const icon_1 = require("@workday/canvas-kit-react/icon"); const SelectMenu_1 = require("./SelectMenu"); const SelectOption_1 = require("./SelectOption"); const scrolling_1 = require("./scrolling"); const utils_1 = require("./utils"); exports.buttonBorderWidth = 1; const menuIconSize = tokens_1.space.m; const SelectButton = (0, common_1.styled)('button')({ ...tokens_1.type.levels.subtext.large, border: `${exports.buttonBorderWidth}px solid ${tokens_1.inputColors.border}`, cursor: 'default', display: 'block', backgroundColor: tokens_1.inputColors.background, borderRadius: tokens_1.borderRadius.m, boxSizing: 'border-box', height: tokens_1.space.xl, outline: 'none', overflow: 'hidden', padding: `calc(${tokens_1.space.xxs} - ${exports.buttonBorderWidth}px)`, paddingRight: `calc(${tokens_1.space.xxs} + ${tokens_1.space.m} + (${tokens_1.space.xxs} + ${exports.buttonBorderWidth}px))`, textAlign: 'left', textOverflow: 'ellipsis', transition: '0.2s box-shadow, 0.2s border-color', whiteSpace: 'nowrap', // width is required (instead of minWidth) in order for the button to // be sized properly for lengthy options width: `calc((${tokens_1.space.xxxl} * 7) / 2)`, '&::placeholder': { color: tokens_1.inputColors.placeholder, }, '&:disabled': { backgroundColor: tokens_1.inputColors.disabled.background, borderColor: tokens_1.inputColors.disabled.border, color: tokens_1.inputColors.disabled.text, '&::placeholder': { color: tokens_1.inputColors.disabled.text, }, }, }, ({ error, menuVisibility, theme }) => { const themedFocusOutlineColor = theme.canvas.palette.common.focusOutline; const buttonFocusStyles = { borderColor: themedFocusOutlineColor, boxShadow: `inset 0 0 0 1px ${themedFocusOutlineColor}`, }; if (error === undefined) { // If there isn't an error, apply focus and hover styles if the menu is // closed or in the process of closing (otherwise, the menu is opened // or in the process of opening: style the button as if it had focus) return menuVisibility === 'closed' || menuVisibility === 'closing' ? { '&:focus:not([disabled])': { ...buttonFocusStyles, }, '&:hover:not([disabled]):not(:focus)': { borderColor: tokens_1.inputColors.hoverBorder, }, } : { ...buttonFocusStyles }; } return { ...(0, common_1.errorRing)(error, theme), }; }, ({ grow }) => grow && { width: '100%', }); const SelectMenuIcon = (0, common_1.styled)(icon_1.SystemIcon)({ position: 'absolute', top: tokens_1.space.xxxs, right: tokens_1.space.xxxs, padding: tokens_1.space.xxxs, pointerEvents: 'none', '& path': { transition: '100ms fill', }, }); const SelectInput = (0, common_1.styled)('input')({ display: 'none', }); const SelectWrapper = (0, common_1.styled)('div')({ position: 'relative', }, ({ grow }) => ({ display: grow ? 'block' : 'inline-block', }), ({ disabled }) => ({ '&:hover .menu-icon path': { fill: disabled ? undefined : tokens_1.colors.licorice500, }, })); const defaultRenderOption = option => { return react_1.default.createElement("div", null, defaultRenderSelected(option)); }; const defaultRenderSelected = option => { return option.label; }; /** * @deprecated ⚠️ `SelectBase` 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 SelectBase = ({ 'aria-labelledby': ariaLabelledBy, 'aria-required': ariaRequired, as, forwardedButtonRef, localButtonRef, disabled, error, focusedOptionIndex = 0, grow, inputRef, menuPlacement = 'bottom', menuRef, menuVisibility = 'closed', onChange, onKeyDown, onBlur, onClose, onOptionSelection, options, renderOption = defaultRenderOption, renderSelected = defaultRenderSelected, required, shouldMenuAutoFlip = true, shouldMenuAutoFocus = true, value, ...elemProps }) => { const focusedOptionRef = react_1.default.useRef(null); const menuId = (0, common_1.useUniqueId)(); const renderOptions = (renderOption) => { const selectedOptionIndex = (0, utils_1.getCorrectedIndexByValue)(options, value); return options.map((option, index) => { const optionProps = { 'aria-disabled': option.disabled ? true : undefined, 'aria-selected': selectedOptionIndex === index ? true : undefined, error, focused: focusedOptionIndex === index, id: option.id, interactive: menuVisibility === 'opening' || menuVisibility === 'opened', key: option.id, optionRef: focusedOptionIndex === index ? focusedOptionRef : undefined, value: option.value, ...(onOptionSelection ? { onClick: (event) => { // TODO: Figure out why this preventDefault call exists. // Removing it doesn't have any obvious consequences, // but need to do more careful testing. event.preventDefault(); onOptionSelection(index); }, } : {}), }; // Pass in additional information about the option state: focused, selected const normalizedOption = { ...option, focused: optionProps.focused, selected: !!optionProps['aria-selected'], }; return react_1.default.createElement(SelectOption_1.SelectOption, { ...optionProps }, renderOption(normalizedOption)); }); }; // If the focused option changed, scroll the newly focused option into view (if // necessary) but do NOT center it (0, react_1.useLayoutEffect)(() => { const focusedOption = focusedOptionRef.current; if (focusedOption) { // TODO: Figure out if rAF is the best approach here. I initially added // rAF to get the Select States Menu On story to render correctly in IE. // Without rAF, the menu is scrolled slightly further down than it should // be (only in IE, and only in the Menu On visual testing stories), which // triggers a visual regression. We're inside useLayoutEffect here so I // didn't expect to need rAF in order to make proper measurements, but it // seems to be necessary in IE. // // This rAF call also has the additional benefit of fixing a jarring menu // placement issue in IE (https://github.com/Workday/canvas-kit/issues/791), // so I'm leaving it in for now. const animateId = requestAnimationFrame(() => { // We cannot use the native Element.scrollIntoView() here because it // doesn't work properly with the portalled menu: when using the keyboard // to advance focus through the options, using scrollIntoView to keep the // newly focused option in view also scrolls the ENTIRE page. Instead, we // call our own scrollIntoViewIfNeeded function. (0, scrolling_1.scrollIntoViewIfNeeded)(focusedOption, false); }); return () => { cancelAnimationFrame(animateId); }; } return undefined; }, [focusedOptionIndex]); // If the menu was just opened, scroll the focused option into view (if // necessary) and center it (0, react_1.useLayoutEffect)(() => { const focusedOption = focusedOptionRef.current; // We need to scroll if the menu is either opening or opened in case we decide to // bypass the opening state and jump straight to opened (like in visual testing, // for instance) if (focusedOption && (menuVisibility === 'opening' || menuVisibility === 'opened')) { // TODO: This rAF call is also necessary for the Menu On visual testing // stories to render properly in IE (see above, both rAF calls need to be // present). It has no bearing on the menu placement issue. const animateId = requestAnimationFrame(() => { (0, scrolling_1.scrollIntoViewIfNeeded)(focusedOption, true); }); return () => { cancelAnimationFrame(animateId); }; } return undefined; }, [menuVisibility]); // Do a bit of error-checking in case options weren't provided const hasOptions = options.length > 0; const selectedOption = hasOptions ? options[(0, utils_1.getCorrectedIndexByValue)(options, value)] : null; const selectedOptionValue = selectedOption ? selectedOption.value : ''; return (react_1.default.createElement(SelectWrapper, { grow: grow, disabled: disabled }, react_1.default.createElement(SelectButton, { "aria-expanded": menuVisibility !== 'closed' ? 'true' : undefined, "aria-haspopup": "listbox", "aria-controls": menuVisibility !== 'closed' ? menuId : undefined, as: as, disabled: disabled, error: error, grow: grow, menuVisibility: menuVisibility, onKeyDown: onKeyDown, onBlur: event => { if (menuVisibility === 'closed') { onBlur === null || onBlur === void 0 ? void 0 : onBlur(event); } else { event.preventDefault(); event.stopPropagation(); } }, // Prevent Firefox from triggering click handler on spacebar during // type-ahead when the menu is closed (and, thus, incorrectly displaying // the menu) onKeyUp: e => { e.preventDefault(); }, ref: forwardedButtonRef, type: "button", value: selectedOptionValue, ...elemProps }, !!selectedOption && renderSelected(selectedOption)), react_1.default.createElement(SelectInput, { onChange: onChange, ref: inputRef, type: "text", value: selectedOptionValue }), hasOptions && menuVisibility !== 'closed' && (react_1.default.createElement(SelectMenu_1.SelectMenu, { "aria-activedescendant": options[focusedOptionIndex].id, "aria-labelledby": ariaLabelledBy, "aria-required": ariaRequired || required ? true : undefined, buttonRef: localButtonRef, id: menuId, error: error, menuRef: menuRef, onKeyDown: onKeyDown, onClose: onClose, placement: menuPlacement, shouldAutoFlip: shouldMenuAutoFlip, shouldAutoFocus: shouldMenuAutoFocus, visibility: menuVisibility }, renderOptions(renderOption))), react_1.default.createElement(SelectMenuIcon, { className: "menu-icon", icon: canvas_system_icons_web_1.caretDownSmallIcon, color: disabled ? tokens_1.colors.licorice100 : tokens_1.colors.licorice200, colorHover: disabled ? tokens_1.colors.licorice100 : tokens_1.colors.licorice500, size: `${menuIconSize}rem` }))); }; exports.SelectBase = SelectBase;