TabItem.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import React, { ReactNode, MouseEvent, forwardRef, LegacyRef, useCallback, useMemo } from 'react';
  2. import cls from 'classnames';
  3. import { cssClasses } from '@douyinfe/semi-foundation/tabs/constants';
  4. import { IconClose } from '@douyinfe/semi-icons';
  5. import { TabType, TabSize, TabPosition } from './interface';
  6. export interface TabItemProps {
  7. tab?: ReactNode;
  8. icon?: ReactNode;
  9. size?: TabSize;
  10. type?: TabType;
  11. tabPosition?: TabPosition;
  12. selected?: boolean;
  13. closable?: boolean;
  14. disabled?: boolean;
  15. itemKey?: string;
  16. handleKeyDown?: (event: React.KeyboardEvent, itemKey: string, closable: boolean) => void;
  17. deleteTabItem?: (tabKey: string, event: MouseEvent<Element>) => void;
  18. onClick?: (itemKey: string, e: MouseEvent<Element>) => void
  19. }
  20. const TabItem = (props: TabItemProps, ref: LegacyRef<HTMLDivElement>) => {
  21. const {
  22. tab,
  23. size,
  24. type,
  25. icon,
  26. selected,
  27. closable,
  28. disabled,
  29. itemKey,
  30. deleteTabItem,
  31. tabPosition,
  32. handleKeyDown,
  33. onClick,
  34. ...restProps
  35. } = props;
  36. const closableIcon = useMemo(() => {
  37. return (type === 'card' && closable) ?
  38. <IconClose
  39. aria-label="Close"
  40. role="button"
  41. className={`${cssClasses.TABS_TAB}-icon-close`}
  42. onClick={(e: MouseEvent<HTMLSpanElement>) => deleteTabItem(itemKey, e)}
  43. /> : null;
  44. }, [type, closable, deleteTabItem, itemKey]);
  45. const renderIcon = useCallback(
  46. (icon: ReactNode) => (
  47. <span>
  48. {icon}
  49. </span>
  50. ), []);
  51. const handleKeyDownInItem = useCallback(
  52. (event: React.KeyboardEvent) => {
  53. handleKeyDown && handleKeyDown(event, itemKey, closable);
  54. },
  55. [handleKeyDown, itemKey, closable],
  56. );
  57. const handleItemClick = useCallback(
  58. (e: MouseEvent) => {
  59. !disabled && onClick && onClick(itemKey, e);
  60. },
  61. [itemKey, disabled, onClick],
  62. );
  63. const panelIcon = icon ? renderIcon(icon) : null;
  64. const className = cls(
  65. cssClasses.TABS_TAB,
  66. `${cssClasses.TABS_TAB}-${type}`,
  67. `${cssClasses.TABS_TAB}-${tabPosition}`,
  68. `${cssClasses.TABS_TAB}-single`,
  69. {
  70. [cssClasses.TABS_TAB_ACTIVE]: selected,
  71. [cssClasses.TABS_TAB_DISABLED]: disabled,
  72. [`${cssClasses.TABS_TAB}-small`]: size === 'small',
  73. [`${cssClasses.TABS_TAB}-medium`]: size === 'medium',
  74. }
  75. );
  76. return (
  77. <div
  78. role="tab"
  79. id={`semiTab${itemKey}`}
  80. data-tabkey={`semiTab${itemKey}`}
  81. aria-controls={`semiTabPanel${itemKey}`}
  82. aria-disabled={disabled ? 'true' : 'false'}
  83. aria-selected={selected ? 'true' : 'false'}
  84. tabIndex={selected ? 0 : -1}
  85. onKeyDown={handleKeyDownInItem}
  86. onClick={handleItemClick}
  87. className={className}
  88. {...restProps}
  89. ref={ref}
  90. >
  91. {panelIcon}
  92. {tab}
  93. {closableIcon}
  94. </div>
  95. );
  96. };
  97. // Why is forwardRef needed here?
  98. // Because TabItem needs to be used in OverflowList (when tabs' type is collapsible),
  99. // OverflowList will pass ref to the outermost div DOM node of TabItem
  100. const ForwardTabItem = forwardRef<HTMLDivElement, TabItemProps>(TabItem);
  101. // @ts-ignore
  102. ForwardTabItem.elementType = 'Tabs.TabItem';
  103. export default ForwardTabItem;