GrowiNavbar.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import React, {
  2. FC, memo, useMemo, useRef,
  3. } from 'react';
  4. import { isServer } from '@growi/core';
  5. import { useTranslation } from 'next-i18next';
  6. import dynamic from 'next/dynamic';
  7. import Image from 'next/image';
  8. import Link from 'next/link';
  9. import { useRipple } from 'react-use-ripple';
  10. import { UncontrolledTooltip } from 'reactstrap';
  11. import {
  12. useIsSearchPage, useIsGuestUser, useIsSearchServiceConfigured, useAppTitle, useConfidential, useCustomizedLogoSrc,
  13. } from '~/stores/context';
  14. import { usePageCreateModal } from '~/stores/modal';
  15. import { useCurrentPagePath } from '~/stores/page';
  16. import { useIsDeviceSmallerThanMd } from '~/stores/ui';
  17. import { HasChildren } from '../../interfaces/common';
  18. import GrowiLogo from '../Icons/GrowiLogo';
  19. import { GlobalSearchProps } from './GlobalSearch';
  20. import styles from './GrowiNavbar.module.scss';
  21. const PersonalDropdown = dynamic(() => import('./PersonalDropdown'), { ssr: false });
  22. const InAppNotificationDropdown = dynamic(() => import('../InAppNotification/InAppNotificationDropdown')
  23. .then(mod => mod.InAppNotificationDropdown), { ssr: false });
  24. const AppearanceModeDropdown = dynamic(() => import('./AppearanceModeDropdown').then(mod => mod.AppearanceModeDropdown), { ssr: false });
  25. const NavbarRight = memo((): JSX.Element => {
  26. const { t } = useTranslation();
  27. const { data: currentPagePath } = useCurrentPagePath();
  28. const { data: isGuestUser } = useIsGuestUser();
  29. // ripple
  30. const newButtonRef = useRef(null);
  31. useRipple(newButtonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
  32. const { open: openCreateModal } = usePageCreateModal();
  33. const isAuthenticated = isGuestUser === false;
  34. const authenticatedNavItem = useMemo(() => {
  35. return (
  36. <>
  37. <li className="nav-item">
  38. <InAppNotificationDropdown />
  39. </li>
  40. <li className="nav-item d-none d-md-block">
  41. <button
  42. className="px-md-3 nav-link btn-create-page border-0 bg-transparent"
  43. type="button"
  44. ref={newButtonRef}
  45. data-testid="newPageBtn"
  46. onClick={() => openCreateModal(currentPagePath || '')}
  47. >
  48. <i className="icon-pencil mr-2"></i>
  49. <span className="d-none d-lg-block">{ t('commons:New') }</span>
  50. </button>
  51. </li>
  52. <li className="grw-apperance-mode-dropdown nav-item dropdown">
  53. <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
  54. </li>
  55. <li className="grw-personal-dropdown nav-item dropdown dropdown-toggle dropdown-toggle-no-caret" data-testid="grw-personal-dropdown">
  56. <PersonalDropdown />
  57. </li>
  58. </>
  59. );
  60. }, [t, isAuthenticated, openCreateModal, currentPagePath]);
  61. const notAuthenticatedNavItem = useMemo(() => {
  62. return (
  63. <>
  64. <li className="grw-apperance-mode-dropdown nav-item dropdown">
  65. <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
  66. </li>
  67. <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>;
  68. </>
  69. );
  70. }, [isAuthenticated]);
  71. return (
  72. <>
  73. {isAuthenticated ? authenticatedNavItem : notAuthenticatedNavItem}
  74. </>
  75. );
  76. });
  77. NavbarRight.displayName = 'NavbarRight';
  78. type ConfidentialProps = {
  79. confidential?: string,
  80. }
  81. const Confidential: FC<ConfidentialProps> = memo((props: ConfidentialProps): JSX.Element => {
  82. const { confidential } = props;
  83. if (confidential == null || confidential.length === 0) {
  84. return <></>;
  85. }
  86. return (
  87. <li className="nav-item confidential text-light">
  88. <i id="confidentialTooltip" className="icon-info d-md-none" />
  89. <span className="d-none d-md-inline">
  90. {confidential}
  91. </span>
  92. <UncontrolledTooltip
  93. placement="bottom"
  94. target="confidentialTooltip"
  95. className="d-md-none"
  96. >
  97. {confidential}
  98. </UncontrolledTooltip>
  99. </li>
  100. );
  101. });
  102. Confidential.displayName = 'Confidential';
  103. interface NavbarLogoProps {
  104. logoSrc?: string,
  105. }
  106. const GrowiNavbarLogo: FC<NavbarLogoProps> = memo((props: NavbarLogoProps) => {
  107. const { logoSrc } = props;
  108. return logoSrc != null
  109. // eslint-disable-next-line @next/next/no-img-element
  110. ? (<img src={logoSrc} alt="custom logo" className="picture picture-lg p-2 mx-2" id="settingBrandLogo" width="32" />)
  111. : <GrowiLogo />;
  112. });
  113. GrowiNavbarLogo.displayName = 'GrowiNavbarLogo';
  114. export const GrowiNavbar = (): JSX.Element => {
  115. const GlobalSearch = dynamic<GlobalSearchProps>(() => import('./GlobalSearch').then(mod => mod.GlobalSearch), { ssr: false });
  116. const { data: appTitle } = useAppTitle();
  117. const { data: confidential } = useConfidential();
  118. const { data: isSearchServiceConfigured } = useIsSearchServiceConfigured();
  119. const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
  120. const { data: isSearchPage } = useIsSearchPage();
  121. const { data: customizedLogoSrc } = useCustomizedLogoSrc();
  122. return (
  123. <nav id="grw-navbar" className={`navbar grw-navbar ${styles['grw-navbar']} navbar-expand navbar-dark sticky-top mb-0 px-0`}>
  124. {/* Brand Logo */}
  125. <div className="navbar-brand mr-0">
  126. <Link href="/" prefetch={false}>
  127. <a className="grw-logo d-block">
  128. <GrowiNavbarLogo logoSrc={customizedLogoSrc}/>
  129. </a>
  130. </Link>
  131. </div>
  132. <div className="grw-app-title d-none d-md-block">
  133. {appTitle}
  134. </div>
  135. {/* Navbar Right */}
  136. <ul className="navbar-nav ml-auto">
  137. <NavbarRight />
  138. <Confidential confidential={confidential} />
  139. </ul>
  140. <div className="grw-global-search-container position-absolute">
  141. { isSearchServiceConfigured && !isDeviceSmallerThanMd && !isSearchPage && (
  142. <GlobalSearch />
  143. ) }
  144. </div>
  145. </nav>
  146. );
  147. };