@sap-ux/ui-components
Version:
SAP UI Components Library
249 lines • 11 kB
JavaScript
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