GrowiNavbar.tsx 5.1 KB

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