You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Tabs.js 3.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. // @flow
  2. import React, { useCallback, useEffect, useState } from 'react';
  3. /**
  4. * The type of the React {@code Component} props of {@link Tabs}.
  5. */
  6. type Props = {
  7. /**
  8. * Accessibility label for the tabs container.
  9. *
  10. */
  11. accessibilityLabel: string,
  12. /**
  13. * Tabs information.
  14. */
  15. tabs: Object
  16. };
  17. /**
  18. * A React component that implements tabs.
  19. *
  20. * @returns {ReactElement} The component.
  21. */
  22. const Tabs = ({ accessibilityLabel, tabs }: Props) => {
  23. const [ current, setCurrent ] = useState(0);
  24. const onClick = useCallback(index => event => {
  25. event.preventDefault();
  26. setCurrent(index);
  27. }, []);
  28. const onKeyDown = useCallback(index => event => {
  29. let newIndex = null;
  30. if (event.key === 'ArrowLeft') {
  31. event.preventDefault();
  32. newIndex = index === 0 ? tabs.length - 1 : index - 1;
  33. }
  34. if (event.key === 'ArrowRight') {
  35. event.preventDefault();
  36. newIndex = index === tabs.length - 1 ? 0 : index + 1;
  37. }
  38. if (newIndex !== null) {
  39. setCurrent(newIndex);
  40. }
  41. }, [ tabs ]);
  42. useEffect(() => {
  43. // this test is needed to make sure the effect is triggered because of user actually changing tab
  44. if (document.activeElement?.getAttribute('role') === 'tab') {
  45. document.querySelector(`#${`${tabs[current].id}-tab`}`)?.focus();
  46. }
  47. }, [ current, tabs ]);
  48. return (
  49. <div className = 'tab-container'>
  50. { tabs.length > 1
  51. ? (
  52. <>
  53. <div
  54. aria-label = { accessibilityLabel }
  55. className = 'tab-buttons'
  56. role = 'tablist'>
  57. {tabs.map((tab, index) => (
  58. <button
  59. aria-controls = { `${tab.id}-panel` }
  60. aria-selected = { current === index ? 'true' : 'false' }
  61. id = { `${tab.id}-tab` }
  62. key = { tab.id }
  63. onClick = { onClick(index) }
  64. onKeyDown = { onKeyDown(index) }
  65. role = 'tab'
  66. tabIndex = { current === index ? undefined : -1 }>
  67. {tab.label}
  68. </button>
  69. ))}
  70. </div>
  71. {tabs.map((tab, index) => (
  72. <div
  73. aria-labelledby = { `${tab.id}-tab` }
  74. className = { current === index ? 'tab-content' : 'hide' }
  75. id = { `${tab.id}-panel` }
  76. key = { tab.id }
  77. role = 'tabpanel'
  78. tabIndex = { 0 }>
  79. {tab.content}
  80. </div>
  81. ))}
  82. </>
  83. )
  84. : (
  85. <>
  86. <h2 className = 'sr-only'>{accessibilityLabel}</h2>
  87. <div className = 'tab-content'>{tabs[0].content}</div>
  88. </>
  89. )
  90. }
  91. </div>
  92. );
  93. };
  94. export default Tabs;