import noop from 'lodash/noop'; import React, { useState, useRef, useCallback, useMemo, memo, isValidElement, Children, cloneElement, useImperativeHandle, ReactElement, forwardRef, } from 'react'; import { ScrollView, Text, TouchableOpacity, View } from 'react-native'; import RNPopoverView, { PopoverPlacement } from 'react-native-popover-view'; import Theme from '../../core/theme'; import type { PopoverProps, PopoverItemProps } from './interface'; import { varCreator, styleCreator } from './styles/popover.style'; import { PopoverAction, PopoverInstance } from './interface'; // 无箭头,需设置为0 const arrowSize = { width: 0, height: 0 }; // 默认箭头大小 const defaultArrowSize = { width: 8, height: 4 }; const PlacementMap: Record< Required>['placement'], PopoverPlacement > = { auto: PopoverPlacement.AUTO, top: PopoverPlacement.TOP, bottom: PopoverPlacement.BOTTOM, left: PopoverPlacement.LEFT, right: PopoverPlacement.RIGHT, center: PopoverPlacement.CENTER, floating: PopoverPlacement.FLOATING, }; type SelectAction = PopoverAction & RNPopoverView; export interface P extends SelectAction {} const Popover = forwardRef>((props, ref) => { const { children, content, actions = [], mode = 'dark', shadow = false, arrow = true, placement = 'auto', offset = 5, triggerStyle, onSelect = noop, closeOnClickPopover = true, closeOnClickOverlay = true, renderContentComponent, duration = 300, backgroundStyle, popoverStyle, statusBarTranslucent = true, ...restProps } = props; const TOKENS = Theme.useThemeTokens(); const CV = Theme.createVar(TOKENS, varCreator); // @ts-ignore const STYLES = Theme.createStyle(CV, styleCreator); const anchorRef = useRef(null); const [showPopover, setShowPopover] = useState(false); const animationConfig = useMemo(() => ({ duration }), [duration]); const openPopover = useCallback(() => { setShowPopover(true); }, []); const closePopover = useCallback(() => { setShowPopover(false); }, []); const handleClose = () => { closeOnClickOverlay && closePopover(); }; useImperativeHandle(ref, () => ({ show: () => openPopover(), hide: () => closePopover(), })); const _onSelect = (value: PopoverAction, index: number) => { if (onSelect) { onSelect(value, index); } closeOnClickPopover && closePopover(); }; const renderContent = () => { const items = Children.map(content, (child, index) => { if (!isValidElement(child)) { return child; } return cloneElement( child as React.ReactElement>, { // @ts-ignore onSelect: (v: PopoverAction) => _onSelect(v, index), dark: mode === 'dark', } ); }); if (typeof renderContentComponent === 'function') { return renderContentComponent(items, closePopover); } return {items}; }; const renderAction = (action: PopoverAction, index: number) => { const { icon, text, color, disabled: actionDisabled, divider = false, style: actionStyle, } = action; const actionTextStyle = mode === 'dark' ? CV.popover_text_color_dark : CV.popover_text_color; const textColor = color || (actionDisabled ? CV.popover_text_color_disabled : actionTextStyle); return ( _onSelect(action, index)} activeOpacity={CV.button_active_opacity} > {icon && ( {cloneElement(icon as ReactElement, { color: textColor, size: CV.popover_action_text_font_size, })} )} {text} ); }; return ( <> {isValidElement(children) ? cloneElement(children, { // @ts-ignore onPress: openPopover, }) : children} {content ? renderContent() : actions.length > 0 ? actions.map(renderAction) : null} ); }); export default memo(Popover);