index.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import React from 'react';
  2. import classnames from 'classnames';
  3. import PropTypes from 'prop-types';
  4. import { cssClasses, strings, numbers } from '@douyinfe/semi-foundation/dropdown/constants';
  5. import BaseComponent from '../_base/baseComponent';
  6. import Tooltip, { Position, TooltipProps, Trigger } from '../tooltip/index';
  7. import { numbers as tooltipNumbers } from '@douyinfe/semi-foundation/tooltip/constants';
  8. import Foundation from '@douyinfe/semi-foundation/dropdown/foundation';
  9. import DropdownMenu from './dropdownMenu';
  10. import DropdownItem, { DropdownItemProps } from './dropdownItem';
  11. import DropdownDivider, { DropdownDividerProps } from './dropdownDivider';
  12. import DropdownTitle, { DropdownTitleProps } from './dropdownTitle';
  13. import DropdownContext from './context';
  14. import '@douyinfe/semi-foundation/dropdown/dropdown.scss';
  15. import { noop, get } from 'lodash';
  16. import { Motion } from '../_base/base';
  17. const positionSet = strings.POSITION_SET;
  18. const triggerSet = strings.TRIGGER_SET;
  19. export { DropdownDividerProps } from './dropdownDivider';
  20. export { DropdownItemProps, Type } from './dropdownItem';
  21. export { DropdownMenuProps } from './dropdownMenu';
  22. export { DropdownTitleProps } from './dropdownTitle';
  23. export interface DropDownMenuItemItem extends DropdownItemProps {
  24. node: 'item';
  25. name?: string;
  26. }
  27. export interface DropDownMenuItemDivider extends DropdownDividerProps {
  28. node: 'divider'
  29. }
  30. export interface DropDownMenuItemTitle extends DropdownTitleProps {
  31. node: 'title';
  32. name?: string
  33. }
  34. export type DropDownMenuItem = DropDownMenuItemItem | DropDownMenuItemDivider | DropDownMenuItemTitle;
  35. export interface DropdownProps extends TooltipProps {
  36. render?: React.ReactNode;
  37. children?: React.ReactNode;
  38. visible?: boolean;
  39. position?: Position;
  40. getPopupContainer?: () => HTMLElement;
  41. mouseEnterDelay?: number;
  42. mouseLeaveDelay?: number;
  43. menu?: DropDownMenuItem[];
  44. trigger?: Trigger;
  45. zIndex?: number;
  46. motion?: Motion;
  47. className?: string;
  48. contentClassName?: string | any[];
  49. style?: React.CSSProperties;
  50. onVisibleChange?: (visible: boolean) => void;
  51. rePosKey?: string | number;
  52. showTick?: boolean;
  53. }
  54. interface DropdownState {
  55. popVisible: boolean;
  56. }
  57. class Dropdown extends BaseComponent<DropdownProps, DropdownState> {
  58. static Menu = DropdownMenu;
  59. static Item = DropdownItem;
  60. static Divider = DropdownDivider;
  61. static Title = DropdownTitle;
  62. static contextType = DropdownContext;
  63. static propTypes = {
  64. render: PropTypes.node,
  65. children: PropTypes.node,
  66. visible: PropTypes.bool,
  67. position: PropTypes.oneOf(positionSet),
  68. getPopupContainer: PropTypes.func,
  69. mouseEnterDelay: PropTypes.number,
  70. mouseLeaveDelay: PropTypes.number,
  71. trigger: PropTypes.oneOf(triggerSet),
  72. zIndex: PropTypes.number,
  73. motion: PropTypes.oneOfType([PropTypes.bool, PropTypes.func, PropTypes.object]),
  74. className: PropTypes.string,
  75. contentClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
  76. style: PropTypes.object,
  77. onVisibleChange: PropTypes.func,
  78. rePosKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  79. showTick: PropTypes.bool,
  80. prefixCls: PropTypes.string,
  81. spacing: PropTypes.number,
  82. menu: PropTypes.array,
  83. };
  84. static defaultProps = {
  85. onVisibleChange: noop,
  86. prefixCls: cssClasses.PREFIX,
  87. zIndex: tooltipNumbers.DEFAULT_Z_INDEX,
  88. motion: true,
  89. trigger: 'hover',
  90. position: 'bottom',
  91. mouseLeaveDelay: strings.DEFAULT_LEAVE_DELAY,
  92. showTick: false,
  93. };
  94. constructor(props: DropdownProps) {
  95. super(props);
  96. this.state = {
  97. popVisible: props.visible,
  98. };
  99. this.foundation = new Foundation(this.adapter);
  100. }
  101. get adapter() {
  102. return {
  103. ...super.adapter,
  104. setPopVisible: (popVisible: boolean) => this.setState({ popVisible }),
  105. notifyVisibleChange: (visible: boolean) => this.props.onVisibleChange(visible),
  106. };
  107. }
  108. handleVisibleChange = (visible: boolean) => this.foundation.handleVisibleChange(visible);
  109. renderContent() {
  110. const { render, menu, contentClassName, style, showTick, prefixCls } = this.props;
  111. const className = classnames(prefixCls, contentClassName);
  112. const { level = 0 } = this.context;
  113. const contextValue = { showTick, level: level + 1 };
  114. let content = null;
  115. if (React.isValidElement(render)) {
  116. content = render;
  117. } else if (Array.isArray(menu)) {
  118. content = this.renderMenu();
  119. }
  120. return (
  121. <DropdownContext.Provider value={contextValue}>
  122. <div className={className} style={style}>
  123. <div className={`${prefixCls}-content`}>{content}</div>
  124. </div>
  125. </DropdownContext.Provider>
  126. );
  127. }
  128. renderMenu() {
  129. const { menu } = this.props;
  130. const content = menu.map((m, index) => {
  131. switch (m.node) {
  132. case 'title': {
  133. const { name, node, ...rest } = m;
  134. return (
  135. <Dropdown.Title {...rest} key={node + name + index}>
  136. {name}
  137. </Dropdown.Title>
  138. );
  139. }
  140. case 'item': {
  141. const { node, name, ...rest } = m;
  142. return (
  143. <Dropdown.Item {...rest} key={node + name + index}>
  144. {name}
  145. </Dropdown.Item>
  146. );
  147. }
  148. case 'divider': {
  149. return <Dropdown.Divider key={m.node + index} />;
  150. }
  151. default:
  152. return null;
  153. }
  154. });
  155. return <Dropdown.Menu>{content}</Dropdown.Menu>;
  156. }
  157. renderPopCard() {
  158. const { render, contentClassName, style, showTick, prefixCls } = this.props;
  159. const className = classnames(prefixCls, contentClassName);
  160. const { level = 0 } = this.context;
  161. const contextValue = { showTick, level: level + 1 };
  162. return (
  163. <DropdownContext.Provider value={contextValue}>
  164. <div className={className} style={style}>
  165. <div className={`${prefixCls}-content`}>{render}</div>
  166. </div>
  167. </DropdownContext.Provider>
  168. );
  169. }
  170. render() {
  171. const {
  172. children,
  173. position,
  174. trigger,
  175. onVisibleChange,
  176. zIndex,
  177. className,
  178. motion,
  179. style,
  180. prefixCls,
  181. ...attr
  182. } = this.props;
  183. let { spacing } = this.props;
  184. const { level } = this.context;
  185. const { popVisible } = this.state;
  186. const pop = this.renderContent();
  187. if (level > 0) {
  188. spacing = typeof spacing === 'number' ? spacing : numbers.NESTED_SPACING;
  189. } else if (spacing === null || typeof spacing === 'undefined') {
  190. spacing = numbers.SPACING;
  191. }
  192. return (
  193. <Tooltip
  194. zIndex={zIndex}
  195. motion={motion}
  196. content={pop}
  197. className={className}
  198. prefixCls={prefixCls}
  199. spacing={spacing}
  200. position={position}
  201. trigger={trigger}
  202. onVisibleChange={this.handleVisibleChange}
  203. showArrow={false}
  204. {...attr}
  205. >
  206. {React.isValidElement(children) ?
  207. React.cloneElement(children, {
  208. className: classnames(get(children, 'props.className'), {
  209. [`${prefixCls}-showing`]: popVisible,
  210. }),
  211. }) :
  212. children}
  213. </Tooltip>
  214. );
  215. }
  216. }
  217. export default Dropdown;