您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

Tabs.tsx 4.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. import React, { useCallback, useEffect } from 'react';
  2. import { makeStyles } from 'tss-react/mui';
  3. import { isMobileBrowser } from '../../../environment/utils';
  4. import { withPixelLineHeight } from '../../../styles/functions.web';
  5. interface ITabProps {
  6. accessibilityLabel: string;
  7. className?: string;
  8. onChange: (id: string) => void;
  9. selected: string;
  10. tabs: Array<{
  11. accessibilityLabel: string;
  12. controlsId: string;
  13. countBadge?: number;
  14. disabled?: boolean;
  15. id: string;
  16. label: string;
  17. }>;
  18. }
  19. const useStyles = makeStyles()(theme => {
  20. return {
  21. container: {
  22. display: 'flex'
  23. },
  24. tab: {
  25. ...withPixelLineHeight(theme.typography.bodyShortBold),
  26. color: theme.palette.text02,
  27. flex: 1,
  28. padding: '14px',
  29. background: 'none',
  30. border: 0,
  31. appearance: 'none',
  32. borderBottom: `2px solid ${theme.palette.ui05}`,
  33. transition: 'color, border-color 0.2s',
  34. display: 'flex',
  35. alignItems: 'center',
  36. justifyContent: 'center',
  37. borderRadius: 0,
  38. '&:hover': {
  39. color: theme.palette.text01,
  40. borderColor: theme.palette.ui10
  41. },
  42. '&.focus-visible': {
  43. outline: 0,
  44. boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`,
  45. border: 0,
  46. color: theme.palette.text01
  47. },
  48. '&.selected': {
  49. color: theme.palette.text01,
  50. borderColor: theme.palette.action01
  51. },
  52. '&:disabled': {
  53. color: theme.palette.text03,
  54. borderColor: theme.palette.ui05
  55. },
  56. '&.is-mobile': {
  57. ...withPixelLineHeight(theme.typography.bodyShortBoldLarge)
  58. }
  59. },
  60. badge: {
  61. ...withPixelLineHeight(theme.typography.labelBold),
  62. color: theme.palette.text04,
  63. padding: `0 ${theme.spacing(1)}`,
  64. borderRadius: '100%',
  65. backgroundColor: theme.palette.warning01,
  66. marginLeft: theme.spacing(2)
  67. }
  68. };
  69. });
  70. const Tabs = ({
  71. accessibilityLabel,
  72. className,
  73. onChange,
  74. selected,
  75. tabs
  76. }: ITabProps) => {
  77. const { classes, cx } = useStyles();
  78. const isMobile = isMobileBrowser();
  79. const onClick = useCallback(id => () => {
  80. onChange(id);
  81. }, []);
  82. const onKeyDown = useCallback((index: number) => (event: React.KeyboardEvent<HTMLButtonElement>) => {
  83. let newIndex: number | null = null;
  84. if (event.key === 'ArrowLeft') {
  85. event.preventDefault();
  86. newIndex = index === 0 ? tabs.length - 1 : index - 1;
  87. }
  88. if (event.key === 'ArrowRight') {
  89. event.preventDefault();
  90. newIndex = index === tabs.length - 1 ? 0 : index + 1;
  91. }
  92. if (newIndex !== null) {
  93. onChange(tabs[newIndex].id);
  94. }
  95. }, [ tabs ]);
  96. useEffect(() => {
  97. // this test is needed to make sure the effect is triggered because of user actually changing tab
  98. if (document.activeElement?.getAttribute('role') === 'tab') {
  99. document.querySelector<HTMLButtonElement>(`#${selected}`)?.focus();
  100. }
  101. }, [ selected ]);
  102. return (
  103. <div
  104. aria-label = { accessibilityLabel }
  105. className = { cx(classes.container, className) }
  106. role = 'tablist'>
  107. {tabs.map((tab, index) => (
  108. <button
  109. aria-controls = { tab.controlsId }
  110. aria-label = { tab.accessibilityLabel }
  111. aria-selected = { selected === tab.id }
  112. className = { cx(classes.tab, selected === tab.id && 'selected', isMobile && 'is-mobile') }
  113. disabled = { tab.disabled }
  114. id = { tab.id }
  115. key = { tab.id }
  116. onClick = { onClick(tab.id) }
  117. onKeyDown = { onKeyDown(index) }
  118. role = 'tab'
  119. tabIndex = { selected === tab.id ? undefined : -1 }>
  120. {tab.label}
  121. {tab.countBadge && <span className = { classes.badge }>{tab.countBadge}</span>}
  122. </button>
  123. ))}
  124. </div>
  125. );
  126. };
  127. export default Tabs;