123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- import React from 'react';
- import classnames from 'classnames';
- import PropTypes from 'prop-types';
- import { cssClasses, strings, numbers } from '@douyinfe/semi-foundation/dropdown/constants';
- import BaseComponent from '../_base/baseComponent';
- import Tooltip, { Position, TooltipProps, Trigger } from '../tooltip/index';
- import { numbers as tooltipNumbers } from '@douyinfe/semi-foundation/tooltip/constants';
- import Foundation from '@douyinfe/semi-foundation/dropdown/foundation';
- import DropdownMenu from './dropdownMenu';
- import DropdownItem, { DropdownItemProps } from './dropdownItem';
- import DropdownDivider, { DropdownDividerProps } from './dropdownDivider';
- import DropdownTitle, { DropdownTitleProps } from './dropdownTitle';
- import DropdownContext, { DropdownContextType } from './context';
- import '@douyinfe/semi-foundation/dropdown/dropdown.scss';
- import { noop, get } from 'lodash';
- const positionSet = strings.POSITION_SET;
- const triggerSet = strings.TRIGGER_SET;
- export type { DropdownDividerProps } from './dropdownDivider';
- export type { DropdownItemProps, Type } from './dropdownItem';
- export type { DropdownMenuProps } from './dropdownMenu';
- export type { DropdownTitleProps } from './dropdownTitle';
- export interface DropDownMenuItemItem extends DropdownItemProps {
- node: 'item';
- name?: string
- }
- export interface DropDownMenuItemDivider extends DropdownDividerProps {
- node: 'divider'
- }
- export interface DropDownMenuItemTitle extends DropdownTitleProps {
- node: 'title';
- name?: string
- }
- export type DropDownMenuItem = DropDownMenuItemItem | DropDownMenuItemDivider | DropDownMenuItemTitle;
- export interface DropdownProps extends TooltipProps {
- render?: React.ReactNode;
- children?: React.ReactNode;
- visible?: boolean;
- position?: Position;
- getPopupContainer?: () => HTMLElement;
- mouseEnterDelay?: number;
- mouseLeaveDelay?: number;
- menu?: DropDownMenuItem[];
- trigger?: Trigger;
- zIndex?: number;
- motion?: boolean;
- className?: string;
- contentClassName?: string | any[];
- style?: React.CSSProperties;
- onVisibleChange?: (visible: boolean) => void;
- rePosKey?: string | number;
- showTick?: boolean;
- closeOnEsc?: TooltipProps['closeOnEsc'];
- onEscKeyDown?: TooltipProps['onEscKeyDown']
- }
- interface DropdownState {
- popVisible: boolean
- }
- class Dropdown extends BaseComponent<DropdownProps, DropdownState> {
- static Menu = DropdownMenu;
- static Item = DropdownItem;
- static Divider = DropdownDivider;
- static Title = DropdownTitle;
- static contextType = DropdownContext;
- static propTypes = {
- render: PropTypes.node,
- children: PropTypes.node,
- visible: PropTypes.bool,
- position: PropTypes.oneOf(positionSet),
- getPopupContainer: PropTypes.func,
- mouseEnterDelay: PropTypes.number,
- mouseLeaveDelay: PropTypes.number,
- trigger: PropTypes.oneOf(triggerSet),
- zIndex: PropTypes.number,
- motion: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.object]),
- className: PropTypes.string,
- contentClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
- style: PropTypes.object,
- onVisibleChange: PropTypes.func,
- rePosKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
- showTick: PropTypes.bool,
- prefixCls: PropTypes.string,
- spacing: PropTypes.number,
- menu: PropTypes.array,
- };
- static defaultProps = {
- onVisibleChange: noop,
- prefixCls: cssClasses.PREFIX,
- zIndex: tooltipNumbers.DEFAULT_Z_INDEX,
- motion: true,
- trigger: 'hover',
- position: 'bottom',
- mouseLeaveDelay: strings.DEFAULT_LEAVE_DELAY,
- showTick: false,
- closeOnEsc: true,
- onEscKeyDown: noop,
- };
- tooltipRef: React.RefObject<Tooltip>
- constructor(props: DropdownProps) {
- super(props);
- this.state = {
- popVisible: props.visible,
- };
- this.foundation = new Foundation(this.adapter);
- this.tooltipRef = React.createRef();
- }
- context: DropdownContextType;
- get adapter() {
- return {
- ...super.adapter,
- setPopVisible: (popVisible: boolean) => this.setState({ popVisible }),
- notifyVisibleChange: (visible: boolean) => this.props.onVisibleChange(visible),
- getPopupId: () => this.tooltipRef.current.getPopupId()
- };
- }
- handleVisibleChange = (visible: boolean) => this.foundation.handleVisibleChange(visible);
- renderContent() {
- const { render, menu, contentClassName, style, showTick, prefixCls, trigger } = this.props;
- const className = classnames(prefixCls, contentClassName);
- const { level = 0 } = this.context;
- const contextValue = { showTick, level: level + 1, trigger };
- let content = null;
- if (React.isValidElement(render)) {
- content = render;
- } else if (Array.isArray(menu)) {
- content = this.renderMenu();
- }
- return (
- <DropdownContext.Provider value={contextValue}>
- <div className={className} style={style}>
- <div className={`${prefixCls}-content`} x-semi-prop="render">{content}</div>
- </div>
- </DropdownContext.Provider>
- );
- }
- renderMenu() {
- const { menu } = this.props;
- const content = menu.map((m, index) => {
- switch (m.node) {
- case 'title': {
- const { name, node, ...rest } = m;
- return (
- <Dropdown.Title {...rest} key={node + name + index}>
- {name}
- </Dropdown.Title>
- );
- }
- case 'item': {
- const { node, name, ...rest } = m;
- return (
- <Dropdown.Item {...rest} key={node + name + index}>
- {name}
- </Dropdown.Item>
- );
- }
- case 'divider': {
- return <Dropdown.Divider key={m.node + index} />;
- }
- default:
- return null;
- }
- });
- return <Dropdown.Menu>{content}</Dropdown.Menu>;
- }
- renderPopCard() {
- const { render, contentClassName, style, showTick, prefixCls } = this.props;
- const className = classnames(prefixCls, contentClassName);
- const { level = 0 } = this.context;
- const contextValue = { showTick, level: level + 1 };
- return (
- <DropdownContext.Provider value={contextValue}>
- <div className={className} style={style}>
- <div className={`${prefixCls}-content`}>{render}</div>
- </div>
- </DropdownContext.Provider>
- );
- }
- render() {
- const {
- children,
- position,
- trigger,
- onVisibleChange,
- zIndex,
- className,
- motion,
- style,
- prefixCls,
- ...attr
- } = this.props;
- let { spacing } = this.props;
- const { level } = this.context;
- const { popVisible } = this.state;
- const pop = this.renderContent();
- if (level > 0) {
- spacing = typeof spacing === 'number' ? spacing : numbers.NESTED_SPACING;
- } else if (spacing === null || typeof spacing === 'undefined') {
- spacing = numbers.SPACING;
- }
- return (
- <Tooltip
- zIndex={zIndex}
- motion={motion}
- content={pop}
- className={className}
- prefixCls={prefixCls}
- spacing={spacing}
- position={position}
- trigger={trigger}
- onVisibleChange={this.handleVisibleChange}
- showArrow={false}
- returnFocusOnClose={true}
- ref={this.tooltipRef}
- {...attr}
- >
- {React.isValidElement(children) ?
- React.cloneElement(children, {
- //@ts-ignore
- className: classnames(get(children, 'props.className'), {
- [`${prefixCls}-showing`]: popVisible,
- }),
- 'aria-haspopup': true,
- 'aria-expanded': popVisible,
- onKeyDown: (e: React.KeyboardEvent) => {
- this.foundation.handleKeyDown(e);
- const childrenKeyDown: (e: React.KeyboardEvent) => void = get(children, 'props.onKeyDown');
- childrenKeyDown && childrenKeyDown(e);
- }
- }) :
- children}
- </Tooltip>
- );
- }
- }
- export default Dropdown;
|