PrimaryItems.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import {
  2. FC, memo, useCallback, useEffect,
  3. } from 'react';
  4. import { SidebarContentsType, SidebarMode } from '~/interfaces/ui';
  5. import { useSWRxInAppNotificationStatus } from '~/stores/in-app-notification';
  6. import { useDefaultSocket } from '~/stores/socket-io';
  7. import { useCollapsedContentsOpened, useCurrentSidebarContents, useSidebarMode } from '~/stores/ui';
  8. import styles from './PrimaryItems.module.scss';
  9. /**
  10. * @returns String for className to switch the indicator is active or not
  11. */
  12. const useIndicator = (sidebarMode: SidebarMode, isSelected: boolean): string => {
  13. const { data: isCollapsedContentsOpened } = useCollapsedContentsOpened();
  14. if (sidebarMode === SidebarMode.COLLAPSED && !isCollapsedContentsOpened) {
  15. return '';
  16. }
  17. return isSelected ? 'active' : '';
  18. };
  19. const NotificationIconWithCountBadge = (): JSX.Element => {
  20. const { data: socket } = useDefaultSocket();
  21. const { data: notificationCount, mutate: mutateNotificationCount } = useSWRxInAppNotificationStatus();
  22. useEffect(() => {
  23. if (socket != null) {
  24. socket.on('notificationUpdated', () => {
  25. mutateNotificationCount();
  26. });
  27. // clean up
  28. return () => {
  29. socket.off('notificationUpdated');
  30. };
  31. }
  32. }, [mutateNotificationCount, socket]);
  33. return (
  34. <div className="position-relative">
  35. { notificationCount != null && notificationCount > 0 && (
  36. <span className="badge rounded-pill bg-primary notification-count-badge">{notificationCount}</span>
  37. ) }
  38. <span className="material-symbols-outlined">notifications</span>
  39. </div>
  40. );
  41. };
  42. type PrimaryItemProps = {
  43. contents: SidebarContentsType,
  44. label: string,
  45. iconName: string,
  46. sidebarMode: SidebarMode,
  47. onHover?: (contents: SidebarContentsType) => void,
  48. }
  49. const PrimaryItem: FC<PrimaryItemProps> = (props: PrimaryItemProps) => {
  50. const {
  51. contents, label, iconName, sidebarMode,
  52. onHover,
  53. } = props;
  54. const { data: currentContents, mutateAndSave: mutateContents } = useCurrentSidebarContents();
  55. const indicatorClass = useIndicator(sidebarMode, contents === currentContents);
  56. const selectThisItem = useCallback(() => {
  57. mutateContents(contents, false);
  58. }, [contents, mutateContents]);
  59. const itemClickedHandler = useCallback(() => {
  60. // do nothing ONLY WHEN the collapse mode
  61. if (sidebarMode === SidebarMode.COLLAPSED) {
  62. return;
  63. }
  64. selectThisItem();
  65. }, [selectThisItem, sidebarMode]);
  66. const mouseEnteredHandler = useCallback(() => {
  67. // ignore other than collapsed mode
  68. if (sidebarMode !== SidebarMode.COLLAPSED) {
  69. return;
  70. }
  71. selectThisItem();
  72. onHover?.(contents);
  73. }, [contents, onHover, selectThisItem, sidebarMode]);
  74. const labelForTestId = label.toLowerCase().replace(' ', '-');
  75. return (
  76. <button
  77. type="button"
  78. data-testid={`grw-sidebar-nav-primary-${labelForTestId}`}
  79. className={`btn btn-primary ${indicatorClass}`}
  80. onClick={itemClickedHandler}
  81. onMouseEnter={mouseEnteredHandler}
  82. >
  83. { contents === SidebarContentsType.NOTIFICATION
  84. ? <NotificationIconWithCountBadge />
  85. : <span className="material-symbols-outlined">{iconName}</span>
  86. }
  87. </button>
  88. );
  89. };
  90. type Props = {
  91. onItemHover?: (contents: SidebarContentsType) => void,
  92. }
  93. export const PrimaryItems = memo((props: Props) => {
  94. const { onItemHover } = props;
  95. const { data: sidebarMode } = useSidebarMode();
  96. if (sidebarMode == null) {
  97. return <></>;
  98. }
  99. return (
  100. <div className={styles['grw-primary-items']}>
  101. <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" onHover={onItemHover} />
  102. <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.CUSTOM} label="Custom Sidebar" iconName="code" onHover={onItemHover} />
  103. <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.RECENT} label="Recent Changes" iconName="update" onHover={onItemHover} />
  104. <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.BOOKMARKS} label="Bookmarks" iconName="bookmarks" onHover={onItemHover} />
  105. <PrimaryItem sidebarMode={sidebarMode} contents={SidebarContentsType.TAG} label="Tags" iconName="local_offer" onHover={onItemHover} />
  106. <PrimaryItem
  107. sidebarMode={sidebarMode}
  108. contents={SidebarContentsType.NOTIFICATION}
  109. label="In-App Notification"
  110. iconName="notifications"
  111. onHover={onItemHover}
  112. />
  113. </div>
  114. );
  115. });