PrimaryItem.tsx 3.0 KB

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