CustomExpandIcon.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /* eslint-disable react-hooks/exhaustive-deps */
  2. import React, { useCallback } from 'react';
  3. import PropTypes from 'prop-types';
  4. import { noop } from 'lodash';
  5. import { IconChevronRight, IconChevronDown, IconTreeTriangleDown, IconTreeTriangleRight } from '@douyinfe/semi-icons';
  6. import { cssClasses } from '@douyinfe/semi-foundation/table/constants';
  7. import isEnterPress from '@douyinfe/semi-foundation/utils/isEnterPress';
  8. import CSSAnimation from "../_cssAnimation";
  9. export interface CustomExpandIconProps {
  10. expanded?: boolean;
  11. componentType?: 'tree' | 'expand';
  12. onClick?: (nextExpand: boolean, e: React.MouseEvent<HTMLDivElement>) => void;
  13. onMouseEnter?: (e: React.MouseEvent<HTMLSpanElement>) => void;
  14. onMouseLeave?: (e: React.MouseEvent<HTMLSpanElement>) => void;
  15. expandIcon?: ((expanded?: boolean) => React.ReactNode) | React.ReactNode;
  16. prefixCls?: string;
  17. motion?: boolean
  18. }
  19. /**
  20. * render expand icon
  21. */
  22. export default function CustomExpandIcon(props: CustomExpandIconProps) {
  23. const {
  24. expanded,
  25. componentType,
  26. onClick = noop,
  27. onMouseEnter = noop,
  28. onMouseLeave = noop,
  29. expandIcon,
  30. prefixCls = cssClasses.PREFIX,
  31. motion = true,
  32. } = props;
  33. let icon;
  34. if (React.isValidElement(expandIcon)) {
  35. icon = expandIcon;
  36. } else if (typeof expandIcon === 'function') {
  37. icon = expandIcon(expanded);
  38. } else if (componentType === 'tree') {
  39. icon = expanded && !motion ? <IconTreeTriangleDown size="small" /> : <IconTreeTriangleRight size="small" />;
  40. } else {
  41. icon = expanded && !motion ? <IconChevronDown /> : <IconChevronRight />;
  42. }
  43. const handleClick = useCallback(
  44. e => {
  45. if (typeof onClick === 'function') {
  46. onClick(!expanded, e);
  47. }
  48. },
  49. [expanded]
  50. );
  51. if (motion) {
  52. const originIcon = icon;
  53. icon = <CSSAnimation animationState={expanded?"enter":"leave"} startClassName={`${cssClasses.PREFIX}-expandedIcon-${expanded?'show':"hide"}`}>
  54. {({ animationClassName })=>{
  55. return React.cloneElement(originIcon, { className: (originIcon.props.className||"")+" "+animationClassName });
  56. }}
  57. </CSSAnimation>;
  58. }
  59. return (
  60. <span
  61. role="button"
  62. aria-label="Expand this row"
  63. tabIndex={-1}
  64. onClick={handleClick}
  65. onMouseEnter={onMouseEnter}
  66. onMouseLeave={onMouseLeave}
  67. className={`${prefixCls}-expand-icon`}
  68. onKeyPress={e => isEnterPress(e) && handleClick(e as any)}
  69. >
  70. {icon}
  71. </span>
  72. );
  73. }
  74. CustomExpandIcon.propTypes = {
  75. expanded: PropTypes.bool,
  76. componentType: PropTypes.oneOf(['tree', 'expand']),
  77. onClick: PropTypes.func,
  78. onMouseEnter: PropTypes.func,
  79. onMouseLeave: PropTypes.func,
  80. expandIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
  81. prefixCls: PropTypes.string,
  82. motion: PropTypes.bool,
  83. };
  84. CustomExpandIcon.defaultProps = {
  85. componentType: 'expand',
  86. onClick: noop,
  87. onMouseEnter: noop,
  88. onMouseLeave: noop,
  89. prefixCls: cssClasses.PREFIX,
  90. };