UNPKG

@salesforce/design-system-react

Version:

Salesforce Lightning Design System for React

234 lines (222 loc) 6.55 kB
/* Copyright (c) 2015-present, salesforce.com, inc. All rights reserved */ /* Licensed under BSD 3-Clause - see LICENSE.txt or git.io/sfdc-license */ /* eslint-disable jsx-a11y/interactive-supports-focus */ import React from 'react'; import PropTypes from 'prop-types'; import isEqual from 'lodash.isequal'; import classNames from 'classnames'; import { shape } from 'airbnb-prop-types'; import Icon from '../../icon'; const propTypes = { /* * Active descendant in menu */ activeOption: PropTypes.object, /* * Index of active descendant in menu */ activeOptionIndex: PropTypes.number, /** * CSS classes to be added to container `div` tag. Uses `classNames` [API](https://github.com/JedWatson/classnames). */ className: PropTypes.oneOfType([ PropTypes.array, PropTypes.object, PropTypes.string ]), /** * CSS classes to be added to tag with `.slds-dropdown`. Uses `classNames` [API](https://github.com/JedWatson/classnames). */ classNameMenu: PropTypes.oneOfType([ PropTypes.array, PropTypes.object, PropTypes.string ]), /* * Id used for assistive technology */ inputId: PropTypes.string, /** * Determines the height of the menu based on SLDS CSS classes. */ itemVisibleLength: PropTypes.oneOf([5, 7, 10]), /** * **Text labels for internationalization** * This object is merged with the default props object on every render. * * `noOptionsFound`: Custom message that renders when no matches found. The default empty state is just text that says, 'No matches found.'. */ labels: shape({ noOptionsFound: PropTypes.string.isRequired }), /** * Accepts a custom menu item rendering function that becomes a custom component and is passed in the following props: * * `assistiveText`: Object, `assistiveText` prop that is passed into Combobox * * `option`: Object, option data for item being rendered that is passed into Combobox * * `selected`: Boolean, allows rendering of `assistiveText.optionSelectedInMenu` in Readonly Combobox * * _Tested with snapshot testing._ */ menuItem: PropTypes.func, /* * Menu options */ options: PropTypes.array, /* * Callback to remove active descendent */ resetActiveOption: PropTypes.func, /* * Callback when option is selected with keyboard or mouse */ onSelect: PropTypes.func, /* * Selected options */ selection: PropTypes.array, /** * Changes styles of the menu option */ variant: PropTypes.oneOf(['icon-title-subtitle', 'checkbox']), isSelected: PropTypes.func, assistiveText: PropTypes.object }; const defaultProps = {}; const Menu = (props) => { const menuOptions = props.options.map((optionData, index) => { const active = index === props.activeOptionIndex && isEqual(optionData, props.activeOption); const selected = props.isSelected({ selection: props.selection, option: optionData }); const MenuItem = props.menuItem; return ( <li className="slds-listbox__item" key={`menu-option-${optionData.id}`} role="presentation" > { { 'icon-title-subtitle': ( <span // eslint-disable-line jsx-a11y/no-static-element-interactions aria-selected={active} id={`${props.inputId}-listbox-option-${optionData.id}`} className={classNames( 'slds-media slds-listbox__option', 'slds-listbox__option_entity slds-listbox__option_has-meta', { 'slds-has-focus': active } )} onClick={(event) => { props.onSelect(event, { option: optionData }); }} role="option" > {optionData.icon && !props.menuItem ? ( <span className="slds-media__figure">{optionData.icon}</span> ) : null} {props.menuItem ? ( <MenuItem assistiveText={props.assistiveText} selected={selected} option={optionData} /> ) : ( <span className="slds-media__body"> <span className="slds-listbox__option-text slds-listbox__option-text_entity"> {optionData.label} </span> <span className="slds-listbox__option-meta slds-listbox__option-meta_entity"> {optionData.subTitle} </span> </span> )} </span> ), checkbox: ( <span // eslint-disable-line jsx-a11y/no-static-element-interactions aria-selected={selected} id={`${props.inputId}-listbox-option-${optionData.id}`} className={classNames( 'slds-media slds-listbox__option', ' slds-listbox__option_plain slds-media_small slds-media_center', { 'slds-has-focus': active, 'slds-is-selected': selected } )} onClick={(event) => { props.onSelect(event, { selection: props.selection, option: optionData }); }} role="option" > <span className="slds-media__figure"> <Icon className="slds-listbox__icon-selected" category="utility" name="check" size="x-small" /> </span> <span className="slds-media__body"> {props.menuItem ? ( <MenuItem assistiveText={props.assistiveText} selected={selected} option={optionData} /> ) : ( <span className="slds-truncate" title={optionData.label}> {selected ? ( <span className="slds-assistive-text"> {props.assistiveText.optionSelectedInMenu} </span> ) : null}{' '} {optionData.label} </span> )} </span> </span> ) }[props.variant] } </li> ); }); return ( <ul className={classNames( 'slds-listbox slds-listbox_vertical slds-dropdown slds-dropdown_fluid', { 'slds-dropdown_length-with-icon-5': props.itemVisibleLength === 5, 'slds-dropdown_length-with-icon-7': props.itemVisibleLength === 7, 'slds-dropdown_length-with-icon-10': props.itemVisibleLength === 10 }, props.classNameMenu )} role="presentation" > {menuOptions.length ? ( menuOptions ) : ( <li className="slds-listbox__item slds-listbox__status" role="status" aria-live="polite" > <span className="slds-m-left--x-large slds-p-vertical--medium"> {props.labels.noOptionsFound} </span> </li> )} </ul> ); }; Menu.displayName = 'Menu'; Menu.propTypes = propTypes; Menu.defaultProps = defaultProps; export default Menu;