UNPKG

@spaced-out/ui-design-system

Version:
167 lines (155 loc) 5.37 kB
// @flow strict import * as React from 'react'; import { // $FlowFixMe[untyped-import] autoUpdate, // $FlowFixMe[untyped-import] flip, // $FlowFixMe[untyped-import] FloatingFocusManager, // $FlowFixMe[untyped-import] FloatingPortal, // $FlowFixMe[untyped-import] offset, // $FlowFixMe[untyped-import] shift, // $FlowFixMe[untyped-import] useFloating, } from '@floating-ui/react'; import {useReferenceElementWidth} from '../../hooks'; import {spaceNone, spaceXXSmall} from '../../styles/variables/_space'; import type {ClickAwayRefType} from '../../utils'; import {ClickAway, mergeRefs} from '../../utils'; import classify from '../../utils/classify'; import type {ButtonProps, ButtonSize} from '../Button'; import {Button} from '../Button'; import type {AnchorType, Strategy} from '../ButtonDropdown'; import {ANCHOR_POSITION_TYPE, STRATEGY_TYPE} from '../ButtonDropdown'; import type {ElevationType} from '../Tooltip'; import {getElevationValue} from '../Tooltip'; import css from './FilterButtonOverlay.module.css'; type ClassNames = $ReadOnly<{wrapper?: string, overlayContainer?: string}>; export type FilterButtonOverlaySizeTypes = 'medium' | 'small'; export type NewButtonProps = $Diff<ButtonProps, {children?: React.Node}>; export type FilterButtonOverlayProps = { ...NewButtonProps, classNames?: ClassNames, children: React.Node, positionStrategy?: Strategy, anchorPosition?: AnchorType, clickAwayRef?: ClickAwayRefType, isFluid?: boolean, size?: FilterButtonOverlaySizeTypes, buttonLabel?: React.Node, buttonSize?: ButtonSize, elevation?: ElevationType, buttonIsFluid?: boolean, ... }; export const FilterButtonOverlay: React$AbstractComponent< FilterButtonOverlayProps, HTMLDivElement, > = React.forwardRef<FilterButtonOverlayProps, HTMLDivElement>( ( { classNames, anchorPosition = ANCHOR_POSITION_TYPE.bottomStart, positionStrategy = STRATEGY_TYPE.absolute, clickAwayRef, size = 'medium', children, isFluid, buttonLabel, buttonIsFluid, buttonSize, elevation = 'modal', ...restProps }: FilterButtonOverlayProps, ref, ) => { const {x, y, refs, strategy, context} = useFloating({ open: true, strategy: positionStrategy, placement: anchorPosition, whileElementsMounted: autoUpdate, middleware: [shift(), flip(), offset(parseInt(spaceXXSmall))], }); const dropdownWidth = useReferenceElementWidth(refs.reference?.current); return ( <ClickAway clickAwayRef={clickAwayRef} closeOnEscapeKeypress containsNestedFloatingPortals > {({isOpen, onOpen, boundaryRef, triggerRef}) => ( <div data-testid="FilterButtonOverlay" className={classify( { [css.isFluid]: buttonIsFluid, }, classNames?.wrapper, )} ref={ref} > <Button {...restProps} ref={mergeRefs([refs.setReference, triggerRef])} onClick={(e) => { e.stopPropagation(); onOpen(); }} isFluid={buttonIsFluid} size={buttonSize} > {buttonLabel} </Button> {isOpen && ( <FloatingPortal> <FloatingFocusManager modal={false} context={context} initialFocus={refs.reference} > <div ref={mergeRefs([refs.setFloating, boundaryRef])} className={css.overlayContainer} style={{ display: 'flex', position: strategy, top: y ?? spaceNone, left: x ?? spaceNone, /* NOTE(Sharad): The FloatingPortal renders the menu outside the normal DOM structure, so its parent is effectively the <body> element. This means the menu would otherwise default to the body's width. To support fluid width, we must manually set the dropdown width here; otherwise, it uses a fixed width. Also, Only treat menu as non-fluid if isFluid is strictly false, since default is true in menu and undefined means fluid. */ ...(isFluid !== false && { '--dropdown-width': dropdownWidth, }), '--menu-elevation': getElevationValue(elevation), }} > <div className={classify( css.overlayWrapper, { [css.fluid]: isFluid, [css.small]: size === 'small', [css.medium]: size === 'medium', }, classNames?.overlayContainer, )} > {children} </div> </div> </FloatingFocusManager> </FloatingPortal> )} </div> )} </ClickAway> ); }, );