UNPKG

@spaced-out/ui-design-system

Version:
202 lines (184 loc) 5.11 kB
// @flow strict import * as React from 'react'; import {classify} from '../../utils/classify'; import {UnstyledButton} from '../Button'; import {Checkbox} from '../Checkbox'; import {Icon} from '../Icon'; import {RadioButton} from '../RadioButton'; import {Truncate} from '../Truncate'; import {TruncatedTextWithTooltip} from '../TruncatedTextWithTooltip'; import type {BaseMenuProps, MenuOption} from './Menu'; import css from './Menu.module.css'; export type MenuOptionProps = { ...BaseMenuProps, option: MenuOption, isLastItem?: boolean, style?: mixed, }; export const MenuOptionButton = (props: MenuOptionProps): React.Node => { const lastMenuItemRef: {current: HTMLButtonElement | null} = React.useRef(null); const { option, size = 'medium', onSelect, selectedOption, menuDisabled, classNames, optionsVariant = 'normal', selectedKeys, isLastItem, onTabOut, resolveLabel, resolveSecondaryLabel, style, showLabelTooltip, allowWrap = false, } = props; const { key, label, secondaryLabel, customComponent, iconLeft, iconLeftType, classNames: optionClassNames, iconRight, iconRightType, disabled, optionSize, optionVariant = optionsVariant, indeterminate = false, } = option; const [buttonSize, setButtonSize] = React.useState(optionSize || size); const resolvedLabel = resolveLabel ? resolveLabel(option) : label; const resolvedSecondaryLabel = resolveSecondaryLabel ? resolveSecondaryLabel(option) : secondaryLabel; const isSelected = () => { if (!selectedKeys || !Array.isArray(selectedKeys) || !selectedKeys.length) { return false; } return selectedKeys.includes(option.key); }; React.useEffect(() => { setButtonSize(optionSize || size); }, [optionSize, size]); React.useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Tab' && !event.shiftKey) { // Tab pressed without shift key, calling tab out callback onTabOut?.(); } }; lastMenuItemRef.current?.addEventListener('keydown', handleKeyDown); return () => { lastMenuItemRef.current?.removeEventListener('keydown', handleKeyDown); }; }, [isLastItem]); return ( <UnstyledButton className={classify( css.option, { [css.selected]: isSelected() || key === selectedOption?.key, [css.optionSmall]: buttonSize === 'small', [css.optionMedium]: buttonSize === 'medium', [css.disabled]: menuDisabled || disabled, [css.withIconLeft]: !!iconLeft, [css.withIconRight]: !!iconRight, }, classNames?.option, optionClassNames?.wrapper, )} style={style} disabled={menuDisabled || disabled} onClick={(e) => onSelect && onSelect(option, e)} autoFocus={selectedOption?.key === key} {...(isLastItem ? {ref: lastMenuItemRef} : {})} > {optionVariant === 'checkbox' && ( <Checkbox tabIndex={-1} disabled={menuDisabled || disabled} checked={isSelected()} indeterminate={indeterminate} /> )} {optionVariant === 'radio' && ( <RadioButton disabled={menuDisabled || disabled} value={option.key} selectedValue={selectedKeys?.[0]} tabIndex={-1} /> )} {!!iconLeft && ( <Icon name={iconLeft} type={iconLeftType} size="small" className={css.icon} /> )} <div className={classify( css.optionTextContainer, classNames?.optionTextContainer, )} > {React.isValidElement(customComponent) ? ( customComponent ) : ( <div className={classify( css.optionTextLabel, classNames?.optionTextLabel, )} > {renderLabel(resolvedLabel, allowWrap, showLabelTooltip)} </div> )} {!!secondaryLabel && ( <div className={css.optionTextSecondaryLabel}> <Truncate>{resolvedSecondaryLabel}</Truncate> </div> )} </div> {!!iconRight && ( <Icon name={iconRight} type={iconRightType} size="small" className={classify(css.icon, css.rightIcon)} /> )} </UnstyledButton> ); }; const renderLabel = (label, allowWrap, showLabelTooltip) => { if (showLabelTooltip) { return ( <TruncatedTextWithTooltip tooltip={{ bodyMaxLines: showLabelTooltip.maxLines ?? 10, delayMotionDuration: 'normal', }} line={3} > {label} </TruncatedTextWithTooltip> ); } if (allowWrap) { return ( <TruncatedTextWithTooltip tooltip={{bodyMaxLines: 10, delayMotionDuration: 'normal'}} line={3} > {label} </TruncatedTextWithTooltip> ); } return <Truncate>{label}</Truncate>; };