PrimaryItem.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. import { useCallback, type JSX } from 'react';
  2. import { useTranslation } from 'next-i18next';
  3. import { UncontrolledTooltip } from 'reactstrap';
  4. import type { SidebarContentsType } from '~/interfaces/ui';
  5. import { SidebarMode } from '~/interfaces/ui';
  6. import { useIsMobile } from '~/states/ui/device';
  7. import { useCollapsedContentsOpened, useCurrentSidebarContents } from '~/states/ui/sidebar';
  8. const useIndicator = (sidebarMode: SidebarMode, isSelected: boolean): string => {
  9. const [isCollapsedContentsOpened] = useCollapsedContentsOpened();
  10. if (sidebarMode === SidebarMode.COLLAPSED && !isCollapsedContentsOpened) {
  11. return '';
  12. }
  13. return isSelected ? 'active' : '';
  14. };
  15. export type PrimaryItemProps = {
  16. contents: SidebarContentsType,
  17. label: string,
  18. iconName: string,
  19. sidebarMode: SidebarMode,
  20. isCustomIcon?: boolean,
  21. badgeContents?: number,
  22. onHover?: (contents: SidebarContentsType) => void,
  23. onClick?: () => void,
  24. }
  25. export const PrimaryItem = (props: PrimaryItemProps): JSX.Element => {
  26. const {
  27. contents, label, iconName, sidebarMode, badgeContents, isCustomIcon,
  28. onClick, onHover,
  29. } = props;
  30. const [currentContents, setCurrentContents] = useCurrentSidebarContents();
  31. const indicatorClass = useIndicator(sidebarMode, contents === currentContents);
  32. const [isMobile] = useIsMobile();
  33. const { t } = useTranslation();
  34. const selectThisItem = useCallback(() => {
  35. setCurrentContents(contents);
  36. }, [contents, setCurrentContents]);
  37. const itemClickedHandler = useCallback(() => {
  38. // do nothing ONLY WHEN the collapse mode
  39. if (sidebarMode === SidebarMode.COLLAPSED) {
  40. return;
  41. }
  42. selectThisItem();
  43. onClick?.();
  44. }, [onClick, selectThisItem, sidebarMode]);
  45. const mouseEnteredHandler = useCallback(() => {
  46. // ignore other than collapsed mode
  47. if (sidebarMode !== SidebarMode.COLLAPSED) {
  48. return;
  49. }
  50. selectThisItem();
  51. onHover?.(contents);
  52. }, [contents, onHover, selectThisItem, sidebarMode]);
  53. const labelForTestId = label.toLowerCase().replace(' ', '-');
  54. return (
  55. <>
  56. <button
  57. type="button"
  58. data-testid={`grw-sidebar-nav-primary-${labelForTestId}`}
  59. className={`btn btn-primary ${indicatorClass}`}
  60. onClick={itemClickedHandler}
  61. onMouseEnter={mouseEnteredHandler}
  62. id={labelForTestId}
  63. >
  64. <div className="position-relative">
  65. {badgeContents != null && (
  66. <span className="position-absolute badge rounded-pill bg-primary">{badgeContents}</span>
  67. )}
  68. {isCustomIcon
  69. ? (<span className="growi-custom-icons fs-4 align-middle">{iconName}</span>)
  70. : (<span className="material-symbols-outlined">{iconName}</span>)
  71. }
  72. </div>
  73. </button>
  74. {
  75. isMobile === false ? (
  76. <UncontrolledTooltip
  77. autohide
  78. placement="right"
  79. target={labelForTestId}
  80. fade={false}
  81. >
  82. {t(label)}
  83. </UncontrolledTooltip>
  84. ) : <></>
  85. }
  86. </>
  87. );
  88. };
  89. PrimaryItem.displayName = 'PrimaryItem';