@spaced-out/ui-design-system
Version:
Sense UI components library
202 lines (184 loc) • 5.11 kB
Flow
// @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>;
};