nishant-design-system
Version:
Sense UI components library
162 lines (150 loc) • 4.42 kB
Flow
// @flow strict
import * as React from 'react';
import {
// $FlowFixMe[untyped-import]
autoUpdate,
// $FlowFixMe[untyped-import]
flip,
// $FlowFixMe[untyped-import]
offset,
// $FlowFixMe[untyped-import]
shift,
// $FlowFixMe[untyped-import]
useFloating,
} from '@floating-ui/react-dom';
import {sizeFluid} from '../../styles/variables/_size';
import {spaceNone, spaceXXSmall} from '../../styles/variables/_space';
import {classify} from '../../utils/classify';
import {ClickAway} from '../../utils/click-away';
import type {ButtonProps} from '../Button';
import {Button} from '../Button';
import type {MenuOption, MenuProps} from '../Menu';
import {Menu} from '../Menu';
import css from './ButtonDropdown.module.css';
export const ANCHOR_POSITION_TYPE = Object.freeze({
top: 'top',
topStart: 'top-start',
topEnd: 'top-end',
bottom: 'bottom',
bottomStart: 'bottom-start',
bottomEnd: 'bottom-end',
});
export type AnchorType = $Values<typeof ANCHOR_POSITION_TYPE>;
type ClassNames = $ReadOnly<{
buttonWrapper?: string,
dropdownContainer?: string,
buttonIcon?: string,
}>;
export type ButtonDropdownProps = {
...ButtonProps,
classNames?: ClassNames,
menu?: MenuProps,
anchorPosition?: AnchorType,
onOptionSelect?: (option: MenuOption) => mixed,
onMenuOpen?: () => mixed,
onMenuClose?: () => mixed,
...
};
export const ButtonDropdown: React$AbstractComponent<
ButtonDropdownProps,
HTMLDivElement,
> = React.forwardRef<ButtonDropdownProps, HTMLDivElement>(
(
{
anchorPosition = 'bottom-start',
size = 'medium',
onOptionSelect,
menu,
classNames,
disabled,
onMenuOpen,
onMenuClose,
children,
iconRightName,
iconRightType = 'solid',
isFluid,
...restButtonProps
}: ButtonDropdownProps,
forwardRef,
) => {
const menuBtnRef = React.useRef(null);
React.useImperativeHandle(forwardRef, () => menuBtnRef.current);
const {x, y, reference, floating, strategy} = useFloating({
strategy: 'absolute',
placement: anchorPosition,
whileElementsMounted: autoUpdate,
middleware: [shift(), flip(), offset(parseInt(spaceXXSmall))],
});
const onMenuToggle = (isOpen: boolean) => {
isOpen ? onMenuOpen && onMenuOpen() : onMenuClose && onMenuClose();
};
return (
<ClickAway onChange={onMenuToggle}>
{({isOpen, onOpen, cancelNext, clickAway}) => (
<div
data-testid="ButtonDropdown"
className={classify(
css.buttonDropdownContainer,
{
[css.isFluid]: isFluid === true,
},
classNames?.dropdownContainer,
)}
ref={menuBtnRef}
>
<Button
{...restButtonProps}
iconRightName={
children
? iconRightName || (isOpen ? 'caret-up' : 'caret-down')
: iconRightName
}
iconRightType={iconRightType}
disabled={disabled}
size={size}
ref={reference}
onClick={(e) => {
e.stopPropagation();
onOpen();
}}
isFluid={isFluid}
classNames={{
wrapper: classNames?.buttonWrapper,
icon: classNames?.buttonIcon,
}}
>
{children}
</Button>
{isOpen && menu && (
<div
onClickCapture={cancelNext}
ref={floating}
style={{
display: 'flex',
position: strategy,
top: y ?? spaceNone,
left: x ?? spaceNone,
...(isFluid && {width: sizeFluid}),
}}
>
<Menu
{...menu}
onSelect={(option) => {
onOptionSelect && onOptionSelect(option);
if (
!menu.optionsVariant ||
menu.optionsVariant === 'normal'
) {
clickAway();
}
}}
size={menu.size || size}
/>
</div>
)}
</div>
)}
</ClickAway>
);
},
);