UNPKG

@sap-ux/ui-components

Version:

SAP UI Components Library

249 lines 11 kB
import React from 'react'; import { Dropdown, DropdownMenuItemType, ResponsiveMode } from '@fluentui/react'; import { UIIcon } from '../UIIcon/index.js'; import { getMessageInfo, MESSAGE_TYPES_CLASSNAME_MAP } from '../../helper/ValidationMessage/index.js'; import { labelGlobalStyle } from '../UILabel/index.js'; import { isDropdownEmpty, getCalloutCollisionTransformationPropsForDropdown } from './utils.js'; import { CalloutCollisionTransform } from '../UICallout/index.js'; import { REQUIRED_LABEL_INDICATOR } from '../types.js'; import './UIDropdown.scss'; export { DropdownMenuItemType as UIDropdownMenuItemType }; const ERROR_BORDER_COLOR = 'var(--vscode-inputValidation-errorBorder)'; /** * UIDropdown component * based on https://developer.microsoft.com/en-us/fluentui#/controls/web/dropdown * * @exports * @class UIDropdown * @extends {React.Component<UIDropdownProps, UIDropdownState>} */ export class UIDropdown extends React.Component { /** * Initializes component properties. * * @param {UIDropdownProps} props */ constructor(props) { super(props); this.dropdownDomRef = React.createRef(); this.menuDomRef = React.createRef(); this.calloutCollisionTransform = new CalloutCollisionTransform(this.dropdownDomRef, this.menuDomRef); this.onRenderCaretDown = () => { return React.createElement(UIIcon, { iconName: "ArrowDown", style: { height: '100%' } }); }; this.onRenderTitle = (items) => { if (this.props.multiSelect && this.props.onHandleRenderTitle) { return this.props.onHandleRenderTitle(items); } else { const { multiSelectDelimiter = ', ' } = this.props; if (items) { const title = items.map((i) => i.text).join(multiSelectDelimiter); return React.createElement(React.Fragment, null, title); } else { return React.createElement(React.Fragment, null, this.props.title); } } }; this.onClick = ( /* event: React.FormEvent<HTMLDivElement>, option?: IDropdownOption, index?: number */) => { this.setState((prevState) => ({ isOpen: !prevState.isOpen }), () => { if (this.props.multiSelect && this.props.onHandleOpen) { if (this.state.isOpen) { this.setState((prevState) => ({ isOpen: !prevState.isOpen })); this.props.onHandleOpen(); } } }); }; this.onChange = (event, option, index) => { if (this.props.multiSelect && this.props.onHandleChange) { if (option && index) { this.props.onHandleChange(option, index); } } }; /** * Method stops event propagation. * * @param {React.MouseEvent<HTMLDivElement>} event Mouse event. */ this.stopEventPropagation = (event) => { event.stopPropagation(); }; /** * Method used as workaround to separate focus and hover. * Default behaviour of fluent ui is that focus follows hover, but we need separe them. * * @param {IDropdownOption} [props] Dropdown props. * @param {(props?: IDropdownOption) => JSX.Element | null} [defaultRender] Default option renderer. * @returns {JSX.Element | null} Returns dropdown option element. */ this._onRenderOption = (props, defaultRender) => { return (React.createElement(React.Fragment, null, defaultRender?.(props), props?.itemType !== DropdownMenuItemType.Header && (React.createElement("div", { onMouseEnter: this.stopEventPropagation.bind(this), onMouseLeave: this.stopEventPropagation.bind(this), onMouseMove: this.stopEventPropagation.bind(this), className: "ts-dropdown-item-blocker" })))); }; /** * Render dropdown menu option. * * @param {IDropdownOption} [props] Dropdown props. * @param {(props?: IDropdownOption) => JSX.Element | null} [defaultRender] Default option renderer. * @returns {JSX.Element | null} Returns dropdown option element. */ this.onRenderOption = (props, defaultRender) => { if (this.props.onRenderOption) { return this.props.onRenderOption(props, this._onRenderOption.bind(this, props, defaultRender)); } return this._onRenderOption(props, defaultRender); }; /** * Method called on combobox item render. * We should pass query to it and avoid rendering if it is hidden. * * @param {IComboBoxOption} props Combobox item props. * @param {Function} defaultRender Combobox item default renderer. * @returns {JSX.Element | null} Element to render. */ this._onRenderItem = (props, defaultRender) => { if (defaultRender && props) { if (props.title === undefined) { // Apply title by default if property not provided // In older fluent-ui versions it was applied by default, but behavior changed in version '8.66.2' props.title = props.text; } return defaultRender(props); } return null; }; /** * Render dropdown menu item. * * @param {IComboBoxOption} props Combobox item props. * @param {Function} defaultRender Combobox item default renderer. * @returns {JSX.Element | null} Element to render. */ this.onRenderItem = (props, defaultRender) => { if (this.props.onRenderItem) { return this.props.onRenderItem(props, this._onRenderItem.bind(this, props, defaultRender)); } return this._onRenderItem(props, defaultRender); }; this.state = { options: [], isOpen: false }; } /** * Method returns styles for callout to support property 'useDropdownAsMenuMinWidth'. * States: * 1. Min width of callout is equals to width of droipdown input box; * 2. Max width equals to windows size minus 10px; * 3. Width is auto - it allows to make callout wider if menu option size exceeds size of dropdown input(min-width). * * @param {ICalloutContentStyleProps} calloutStyleProps Current callout styles. * @returns {Partial<ICalloutContentStyles>} Styles for callout. */ getCalloutStylesForUseAsMinWidth(calloutStyleProps) { return { root: { minWidth: calloutStyleProps.calloutWidth, width: 'auto', maxWidth: 'calc(100% - 10px)' } }; } /** * Method returns class names string depending on props and component state. * * @param {InputValidationMessageInfo} messageInfo Error/warning message if applied * @returns {string} Class names of root dropdown element. */ getClassNames(messageInfo) { const { className, readOnly, disabled } = this.props; const errorSuffix = messageInfo.message ? MESSAGE_TYPES_CLASSNAME_MAP.get(messageInfo.type) : undefined; let classNames = `ts-SelectBox${messageInfo.message ? ' ts-SelectBox--' + errorSuffix : ''}`; // Readonly if (readOnly && !disabled) { classNames += ' ts-SelectBox--readonly'; } // Disabled if (disabled) { classNames += ' ts-SelectBox--disabled'; } // Custom external classes if (className) { classNames += ` ${className}`; } // Empty value if (isDropdownEmpty(this.props)) { classNames += ' ts-SelectBox--empty'; } return classNames; } /** * Method returns additional component properties for accessibility. * * @returns {AccessibilityProps} Additional properties. */ getAccessibilityProps() { const { readOnly, disabled } = this.props; const additionalProps = {}; if (readOnly && !disabled) { // Make dropdown focusable additionalProps.tabIndex = 0; additionalProps['data-is-focusable'] = true; // Adjust aria attributes for readonly additionalProps['aria-disabled'] = undefined; additionalProps['aria-readonly'] = true; } else if (disabled) { additionalProps.tabIndex = 0; additionalProps['data-is-focusable'] = true; } return additionalProps; } /** * @returns {JSX.Element} */ render() { const messageInfo = getMessageInfo(this.props); const additionalProps = this.getAccessibilityProps(); const dropdownStyles = () => ({ ...{ label: { ...labelGlobalStyle, ...(this.props.disabled && { opacity: '0.4' }), ...(this.props.required && { selectors: { '::after': { content: REQUIRED_LABEL_INDICATOR, color: ERROR_BORDER_COLOR, paddingRight: 12 } } }) }, errorMessage: [messageInfo.style], callout: { boxShadow: 'var(--ui-box-shadow-small)' } } }); return (React.createElement(Dropdown, { ref: this.dropdownDomRef, onRenderCaretDown: this.onRenderCaretDown, onClick: this.onClick, onChange: this.onChange, onRenderTitle: this.onRenderTitle, // Use default responsiveMode as xxxLarge, which does not enter mobile mode. responsiveMode: ResponsiveMode.xxxLarge, disabled: this.props.readOnly, ...additionalProps, ...this.props, calloutProps: { calloutMaxHeight: 200, styles: this.props.useDropdownAsMenuMinWidth ? this.getCalloutStylesForUseAsMinWidth : undefined, className: 'ts-Callout ts-Callout-Dropdown', popupProps: { ref: this.menuDomRef }, ...this.props.calloutProps, ...getCalloutCollisionTransformationPropsForDropdown(this, this.calloutCollisionTransform) }, onRenderOption: this.onRenderOption.bind(this), onRenderItem: this.onRenderItem.bind(this), styles: dropdownStyles, className: this.getClassNames(messageInfo), errorMessage: messageInfo.message })); } } //# sourceMappingURL=UIDropdown.js.map