Kaynağa Gözat

Merge pull request #8543 from weseek/imprv/133910-141426-replace-spinner-pulse

imprv: replace spinner pulse
Tatsuya 2 yıl önce
ebeveyn
işleme
4d56ec08e4
28 değiştirilmiş dosya ile 103 ekleme ve 59 silme
  1. 2 1
      apps/app/src/components/Admin/App/QuestionnaireSettings.tsx
  2. 6 6
      apps/app/src/components/Admin/AuditLogManagement.tsx
  3. 3 2
      apps/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx
  4. 3 1
      apps/app/src/components/Admin/ElasticsearchManagement/ReconnectControls.tsx
  5. 2 1
      apps/app/src/components/Admin/SlackIntegration/SlackIntegration.jsx
  6. 2 1
      apps/app/src/components/Admin/Users/PasswordResetModal.jsx
  7. 2 1
      apps/app/src/components/Common/Dropdown/PageItemControl.tsx
  8. 5 4
      apps/app/src/components/DescendantsPageList.tsx
  9. 5 2
      apps/app/src/components/InAppNotification/InAppNotificationList.tsx
  10. 4 4
      apps/app/src/components/InAppNotification/InAppNotificationPage.tsx
  11. 5 4
      apps/app/src/components/InfiniteScroll.tsx
  12. 11 4
      apps/app/src/components/InstallerForm.tsx
  13. 9 1
      apps/app/src/components/InvitedForm.tsx
  14. 1 2
      apps/app/src/components/LoginForm.tsx
  15. 3 1
      apps/app/src/components/Me/QuestionnaireSettings.tsx
  16. 4 1
      apps/app/src/components/Page/RevisionLoader.tsx
  17. 2 1
      apps/app/src/components/PageAccessoriesModal/PageAttachment.tsx
  18. 2 1
      apps/app/src/components/PageControls/BookmarkButtons.tsx
  19. 3 1
      apps/app/src/components/PageEditor/DrawioModal.tsx
  20. 4 3
      apps/app/src/components/PageList/PageList.tsx
  21. 2 1
      apps/app/src/components/PagePresentationModal.tsx
  22. 11 9
      apps/app/src/components/PrivateLegacyPages.tsx
  23. 2 2
      apps/app/src/components/SavePageControls.tsx
  24. 2 1
      apps/app/src/components/SavePageControls/GrantSelector/GrantSelector.tsx
  25. 2 1
      apps/app/src/components/SearchPage/SearchPageBase.tsx
  26. 2 1
      apps/app/src/components/TemplateModal/TemplateModal.tsx
  27. 2 1
      apps/app/src/components/TreeItem/SimpleItem.tsx
  28. 2 1
      apps/app/src/pages/tags.page.tsx

+ 2 - 1
apps/app/src/components/Admin/App/QuestionnaireSettings.tsx

@@ -6,6 +6,7 @@ import { useTranslation } from 'next-i18next';
 
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
+import { LoadingSpinner } from '~/components/LoadingSpinner';
 import { useSWRxAppSettings } from '~/stores/admin/app-settings';
 
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
@@ -65,7 +66,7 @@ const QuestionnaireSettings = (): JSX.Element => {
 
       {isLoading && (
         <div className="text-muted text-center mb-5">
-          <i className="fa fa-2x fa-spinner fa-pulse me-1" />
+          <LoadingSpinner className="me-1 fs-3" />
         </div>
       )}
 

+ 6 - 6
apps/app/src/components/Admin/AuditLogManagement.tsx

@@ -1,16 +1,16 @@
-import React, {
-  FC, useState, useCallback, useRef,
-} from 'react';
+import type { FC } from 'react';
+import React, { useState, useCallback, useRef } from 'react';
 
 import { format } from 'date-fns';
 import { useTranslation } from 'react-i18next';
 
-import { IClearable } from '~/client/interfaces/clearable';
+import type { IClearable } from '~/client/interfaces/clearable';
 import { toastError } from '~/client/util/toastr';
-import { SupportedActionType } from '~/interfaces/activity';
+import type { SupportedActionType } from '~/interfaces/activity';
 import { useSWRxActivity } from '~/stores/activity';
 import { useAuditLogEnabled, useAuditLogAvailableActions } from '~/stores/context';
 
+import { LoadingSpinner } from '../LoadingSpinner';
 import PaginationWrapper from '../PaginationWrapper';
 
 import { ActivityTable } from './AuditLog/ActivityTable';
@@ -213,7 +213,7 @@ export const AuditLogManagement: FC = () => {
           { isLoading
             ? (
               <div className="text-muted text-center mb-5">
-                <i className="fa fa-2x fa-spinner fa-pulse me-1" />
+                <LoadingSpinner className="me-1 fs-3" />
               </div>
             )
             : (

+ 3 - 2
apps/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx

@@ -5,6 +5,7 @@ import React, {
 import { useTranslation } from 'next-i18next';
 
 import { toastSuccess, toastError } from '~/client/util/toastr';
+import { LoadingSpinner } from '~/components/LoadingSpinner';
 import { useSWRxLayoutSetting } from '~/stores/admin/customize';
 import { useNextThemes } from '~/stores/use-next-themes';
 
@@ -44,8 +45,8 @@ const CustomizeLayoutSetting = (): JSX.Element => {
 
   if (isContainerFluid == null) {
     return (
-      <div className="text-muted text-center">
-        <i className="fa fa-2x fa-spinner fa-pulse"></i>
+      <div className="text-muted text-center fs-3">
+        <LoadingSpinner />
       </div>
     );
   }

+ 3 - 1
apps/app/src/components/Admin/ElasticsearchManagement/ReconnectControls.tsx

@@ -2,6 +2,8 @@ import React from 'react';
 
 import { useTranslation } from 'next-i18next';
 
+import { LoadingSpinner } from '~/components/LoadingSpinner';
+
 type Props = {
   isEnabled?: boolean,
   isProcessing?: boolean,
@@ -21,7 +23,7 @@ const ReconnectControls = (props: Props): JSX.Element => {
         onClick={() => { props.onReconnectingRequested() }}
         disabled={!isEnabled}
       >
-        { isProcessing && <i className="fa fa-spinner fa-pulse me-2"></i> }
+        { isProcessing && <LoadingSpinner className="me-2" /> }
         { t('full_text_search_management.reconnect_button') }
       </button>
 

+ 2 - 1
apps/app/src/components/Admin/SlackIntegration/SlackIntegration.jsx

@@ -8,6 +8,7 @@ import {
   apiv3Delete, apiv3Get, apiv3Post, apiv3Put,
 } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
+import { LoadingSpinner } from '~/components/LoadingSpinner';
 
 import BotTypeCard from './BotTypeCard';
 import ConfirmBotChangeModal from './ConfirmBotChangeModal';
@@ -187,7 +188,7 @@ const SlackIntegration = () => {
   if (isLoading) {
     return (
       <div className="text-muted text-center">
-        <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
+        <LoadingSpinner className="me-1 fs-3" />
       </div>
     );
   }

+ 2 - 1
apps/app/src/components/Admin/Users/PasswordResetModal.jsx

@@ -10,6 +10,7 @@ import {
 import AdminUsersContainer from '~/client/services/AdminUsersContainer';
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastError } from '~/client/util/toastr';
+import { LoadingSpinner } from '~/components/LoadingSpinner';
 import { useIsMailerSetup } from '~/stores/context';
 
 class PasswordResetModal extends React.Component {
@@ -53,7 +54,7 @@ class PasswordResetModal extends React.Component {
           onClick={this.onClickSendNewPasswordButton}
           disabled={!isMailerSetup || isEmailSending || isEmailSent}
         >
-          {isEmailSending && <i className="fa fa-spinner fa-pulse mx-2" />}
+          {isEmailSending && <LoadingSpinner className="mx-2" />}
           {!isEmailSending && (isEmailSent ? t('commons:Done') : t('commons:Send'))}
         </button>
         <button type="submit" className="btn btn-danger" onClick={this.props.onClose}>

+ 2 - 1
apps/app/src/components/Common/Dropdown/PageItemControl.tsx

@@ -10,6 +10,7 @@ import {
   Dropdown, DropdownMenu, DropdownToggle, DropdownItem,
 } from 'reactstrap';
 
+import { LoadingSpinner } from '~/components/LoadingSpinner';
 import { NotAvailableForGuest } from '~/components/NotAvailableForGuest';
 import type { IPageOperationProcessData } from '~/interfaces/page-operation';
 import { useSWRxPageInfo } from '~/stores/page';
@@ -133,7 +134,7 @@ const PageItemControlDropdownMenu = React.memo((props: DropdownMenuProps): JSX.E
   if (isLoading) {
     contents = (
       <div className="text-muted text-center my-2">
-        <i className="fa fa-spinner fa-pulse"></i>
+        <LoadingSpinner />
       </div>
     );
   }

+ 5 - 4
apps/app/src/components/DescendantsPageList.tsx

@@ -8,15 +8,16 @@ import type {
 import { useTranslation } from 'next-i18next';
 
 import { toastSuccess } from '~/client/util/toastr';
-import { IPagingResult } from '~/interfaces/paging-result';
-import { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
+import type { IPagingResult } from '~/interfaces/paging-result';
+import type { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
 import { useIsGuestUser, useIsReadOnlyUser, useIsSharedUser } from '~/stores/context';
 import {
   mutatePageTree,
   useSWRxPageInfoForList, useSWRxPageList,
 } from '~/stores/page-listing';
 
-import { ForceHideMenuItems } from './Common/Dropdown/PageItemControl';
+import type { ForceHideMenuItems } from './Common/Dropdown/PageItemControl';
+import { LoadingSpinner } from './LoadingSpinner';
 import PageList from './PageList/PageList';
 import PaginationWrapper from './PaginationWrapper';
 
@@ -86,7 +87,7 @@ const DescendantsPageListSubstance = (props: SubstanceProps): JSX.Element => {
     return (
       <div className="wiki">
         <div className="text-muted text-center">
-          <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
+          <LoadingSpinner className="me-1 fs-3" />
         </div>
       </div>
     );

+ 5 - 2
apps/app/src/components/InAppNotification/InAppNotificationList.tsx

@@ -1,9 +1,12 @@
-import React, { FC } from 'react';
+import type { FC } from 'react';
+import React from 'react';
 
 import type { HasObjectId } from '@growi/core';
 
 import type { IInAppNotification, PaginateResult } from '~/interfaces/in-app-notification';
 
+import { LoadingSpinner } from '../LoadingSpinner';
+
 import InAppNotificationElm from './InAppNotificationElm';
 
 
@@ -19,7 +22,7 @@ const InAppNotificationList: FC<Props> = (props: Props) => {
     return (
       <div className="wiki">
         <div className="text-muted text-center">
-          <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
+          <LoadingSpinner className="me-1 fs-3" />
         </div>
       </div>
     );

+ 4 - 4
apps/app/src/components/InAppNotification/InAppNotificationPage.tsx

@@ -1,6 +1,5 @@
-import React, {
-  FC, useState, useEffect, useCallback,
-} from 'react';
+import type { FC } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
@@ -11,6 +10,7 @@ import loggerFactory from '~/utils/logger';
 
 import { useSWRxInAppNotifications, useSWRxInAppNotificationStatus } from '../../stores/in-app-notification';
 import CustomNavAndContents from '../CustomNavigation/CustomNavAndContents';
+import { LoadingSpinner } from '../LoadingSpinner';
 import PaginationWrapper from '../PaginationWrapper';
 
 import InAppNotificationList from './InAppNotificationList';
@@ -66,7 +66,7 @@ export const InAppNotificationPage: FC = () => {
       return (
         <div className="wiki" data-testid="grw-in-app-notification-page-spinner">
           <div className="text-muted text-center">
-            <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
+            <LoadingSpinner className="me-1 fs-3" />
           </div>
         </div>
       );

+ 5 - 4
apps/app/src/components/InfiniteScroll.tsx

@@ -1,9 +1,10 @@
-import React, {
-  Ref, useEffect, useState,
-} from 'react';
+import type { Ref } from 'react';
+import React, { useEffect, useState } from 'react';
 
 import type { SWRInfiniteResponse } from 'swr/infinite';
 
+import { LoadingSpinner } from './LoadingSpinner';
+
 type Props<T> = {
   swrInifiniteResponse: SWRInfiniteResponse<T>
   children: React.ReactNode,
@@ -32,7 +33,7 @@ const useIntersection = <E extends HTMLElement>(): [boolean, Ref<E>] => {
 const LoadingIndicator = (): React.ReactElement => {
   return (
     <div className="text-muted text-center">
-      <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
+      <LoadingSpinner className="me-1 fs-3" />
     </div>
   );
 };

+ 11 - 4
apps/app/src/components/InstallerForm.tsx

@@ -1,6 +1,5 @@
-import {
-  FormEventHandler, memo, useCallback, useState,
-} from 'react';
+import type { FormEventHandler } from 'react';
+import { memo, useCallback, useState } from 'react';
 
 import { Lang, AllLang } from '@growi/core';
 import { useTranslation } from 'next-i18next';
@@ -11,6 +10,8 @@ import { i18n as i18nConfig } from '^/config/next-i18next.config';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastError } from '~/client/util/toastr';
 
+import { LoadingSpinner } from './LoadingSpinner';
+
 const InstallerForm = memo((): JSX.Element => {
   const { t, i18n } = useTranslation();
 
@@ -203,7 +204,13 @@ const InstallerForm = memo((): JSX.Element => {
               disabled={isLoading}
             >
               <div className="eff"></div>
-              <span className="btn-label"><i className={isLoading ? 'fa fa-spinner fa-pulse me-1' : 'icon-user-follow'} /></span>
+              <span className="btn-label">
+                {isLoading ? (
+                  <LoadingSpinner />
+                ) : (
+                  <i className="icon-user-follow" />
+                )}
+              </span>
               <span className="btn-label-text">{ t('Create') }</span>
             </button>
           </div>

+ 9 - 1
apps/app/src/components/InvitedForm.tsx

@@ -7,6 +7,8 @@ import { apiv3Post } from '~/client/util/apiv3-client';
 
 import { useCurrentUser } from '../stores/context';
 
+import { LoadingSpinner } from './LoadingSpinner';
+
 
 export type InvitedFormProps = {
   invitedFormUsername: string,
@@ -141,7 +143,13 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
         <div className="input-group justify-content-center d-flex mt-4">
           <button type="submit" className="btn btn-fill" id="register" disabled={isLoading}>
             <div className="eff"></div>
-            <span className="btn-label"><i className={isLoading ? 'fa fa-spinner fa-pulse me-1' : 'icon-user-follow'} /></span>
+            <span className="btn-label">
+              {isLoading ? (
+                <LoadingSpinner />
+              ) : (
+                <i className="icon-user-follow" />
+              )}
+            </span>
             <span className="btn-label-text">{t('Create')}</span>
           </button>
         </div>

+ 1 - 2
apps/app/src/components/LoginForm.tsx

@@ -181,8 +181,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
         {/* !! - DO NOT DELETE HIDDEN ELEMENT - !! -- 7.12 ryoji-s */}
         {/* Import font-awesome to prevent MongoStore.js "Unable to find the session to touch" error */}
         <div className="visually-hidden">
-          {/* Unsettled 11.17 meiri-k */}
-          <i className="fa fa-spinner fa-pulse" />
+          <LoadingSpinner />
         </div>
         {/* !! - END OF HIDDEN ELEMENT - !! */}
         {isLdapSetupFailed && (

+ 3 - 1
apps/app/src/components/Me/QuestionnaireSettings.tsx

@@ -8,6 +8,8 @@ import { toastError, toastSuccess } from '~/client/util/toastr';
 import { useSWRxIsQuestionnaireEnabled } from '~/features/questionnaire/client/stores/questionnaire';
 import { useCurrentUser } from '~/stores/context';
 
+import { LoadingSpinner } from '../LoadingSpinner';
+
 
 export const QuestionnaireSettings = (): JSX.Element => {
   const { t } = useTranslation();
@@ -45,7 +47,7 @@ export const QuestionnaireSettings = (): JSX.Element => {
 
       {isLoadingCurrentUser && (
         <div className="text-muted text-center mb-5">
-          <i className="fa fa-2x fa-spinner fa-pulse me-1" />
+          <LoadingSpinner className="me-1 fs-3" />
         </div>
       )}
 

+ 4 - 1
apps/app/src/components/Page/RevisionLoader.tsx

@@ -7,6 +7,9 @@ import type { RendererOptions } from '~/interfaces/renderer-options';
 import { useSWRxPageRevision } from '~/stores/page';
 import loggerFactory from '~/utils/logger';
 
+
+import { LoadingSpinner } from '../LoadingSpinner';
+
 import RevisionRenderer from './RevisionRenderer';
 
 export const ROOT_ELEM_ID = 'revision-loader' as const;
@@ -64,7 +67,7 @@ export const RevisionLoader = (props: RevisionLoaderProps): JSX.Element => {
     return (
       <div className="wiki">
         <div className="text-muted text-center">
-          <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
+          <LoadingSpinner className="me-1 fs-3" />
         </div>
       </div>
     );

+ 2 - 1
apps/app/src/components/PageAccessoriesModal/PageAttachment.tsx

@@ -9,6 +9,7 @@ import { useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
 import { useDeleteAttachmentModal } from '~/stores/modal';
 import { useSWRxCurrentPage, useCurrentPageId } from '~/stores/page';
 
+import { LoadingSpinner } from '../LoadingSpinner';
 import { PageAttachmentList } from '../PageAttachment/PageAttachmentList';
 import PaginationWrapper from '../PaginationWrapper';
 
@@ -63,7 +64,7 @@ const PageAttachment = (): JSX.Element => {
     if (dataAttachments == null || inUseAttachmentsMap == null) {
       return (
         <div className="text-muted text-center">
-          <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
+          <LoadingSpinner className="me-1 fs-3" />
         </div>
       );
     }

+ 2 - 1
apps/app/src/components/PageControls/BookmarkButtons.tsx

@@ -12,6 +12,7 @@ import { useIsGuestUser } from '~/stores/context';
 
 import { BookmarkFolderMenu } from '../Bookmarks/BookmarkFolderMenu';
 import UserPictureList from '../Common/UserPictureList';
+import { LoadingSpinner } from '../LoadingSpinner';
 
 import styles from './BookmarkButtons.module.scss';
 import popoverStyles from './user-list-popover.module.scss';
@@ -94,7 +95,7 @@ export const BookmarkButtons: FC<Props> = (props: Props) => {
       </button>
       <Popover placement="bottom" isOpen={isBookmarkUsersPopoverOpen} target="po-total-bookmarks" toggle={toggleBookmarkUsersPopover} trigger="legacy">
         <PopoverBody className={`user-list-popover ${popoverStyles['user-list-popover']}`}>
-          { isLoadingBookmarkedUsers && <i className="fa fa-spinner fa-pulse"></i> }
+          { isLoadingBookmarkedUsers && <LoadingSpinner /> }
           { !isLoadingBookmarkedUsers && bookmarkedUsers != null && (
             <>
               { bookmarkedUsers.length > 0

+ 3 - 1
apps/app/src/components/PageEditor/DrawioModal.tsx

@@ -18,6 +18,8 @@ import { useDrawioModal } from '~/stores/modal';
 import { usePersonalSettings } from '~/stores/personal-settings';
 import loggerFactory from '~/utils/logger';
 
+import { LoadingSpinner } from '../LoadingSpinner';
+
 import { type DrawioConfig, DrawioCommunicationHelper } from './DrawioCommunicationHelper';
 
 const logger = loggerFactory('growi:components:DrawioModal');
@@ -133,7 +135,7 @@ export const DrawioModal = (): JSX.Element => {
         {/* Loading spinner */}
         <div className="w-100 h-100 position-absolute d-flex">
           <div className="mx-auto my-auto">
-            <i className="fa fa-3x fa-spinner fa-pulse mx-auto text-muted"></i>
+            <LoadingSpinner className="mx-auto text-muted fs-2" />
           </div>
         </div>
         {/* iframe */}

+ 4 - 3
apps/app/src/components/PageList/PageList.tsx

@@ -3,9 +3,10 @@ import React from 'react';
 import type { IPageInfoForEntity, IPageWithMeta } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 
-import { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
+import type { OnDeletedFunction, OnPutBackedFunction } from '~/interfaces/ui';
 
-import { ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
+import type { ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
+import { LoadingSpinner } from '../LoadingSpinner';
 
 import { PageListItemL } from './PageListItemL';
 
@@ -30,7 +31,7 @@ const PageList = (props: Props<IPageInfoForEntity>): JSX.Element => {
     return (
       <div className="wiki">
         <div className="text-muted text-center">
-          <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
+          <LoadingSpinner className="me-1 fs-3" />
         </div>
       </div>
     );

+ 2 - 1
apps/app/src/components/PagePresentationModal.tsx

@@ -14,6 +14,7 @@ import { useSWRxCurrentPage } from '~/stores/page';
 import { usePresentationViewOptions } from '~/stores/renderer';
 import { useNextThemes } from '~/stores/use-next-themes';
 
+import { LoadingSpinner } from './LoadingSpinner';
 
 import styles from './PagePresentationModal.module.scss';
 
@@ -21,7 +22,7 @@ import styles from './PagePresentationModal.module.scss';
 const Presentation = dynamic<PresentationProps>(() => import('./Presentation/Presentation').then(mod => mod.Presentation), {
   ssr: false,
   loading: () => (
-    <i className="fa fa-4x fa-spinner fa-pulse text-muted"></i>
+    <LoadingSpinner className="text-muted fs-1" />
   ),
 });
 

+ 11 - 9
apps/app/src/components/PrivateLegacyPages.tsx

@@ -8,28 +8,30 @@ import {
   UncontrolledButtonDropdown, DropdownToggle, DropdownMenu, DropdownItem, Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 
-import { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
+import type { ISelectableAll, ISelectableAndIndeterminatable } from '~/client/interfaces/selectable-all';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 import { V5ConversionErrCode } from '~/interfaces/errors/v5-conversion-error';
-import { V5MigrationStatus } from '~/interfaces/page-listing-results';
-import { IFormattedSearchResult } from '~/interfaces/search';
-import { PageMigrationErrorData, SocketEventName } from '~/interfaces/websocket';
+import type { V5MigrationStatus } from '~/interfaces/page-listing-results';
+import type { IFormattedSearchResult } from '~/interfaces/search';
+import type { PageMigrationErrorData } from '~/interfaces/websocket';
+import { SocketEventName } from '~/interfaces/websocket';
 import { useIsAdmin } from '~/stores/context';
-import {
-  ILegacyPrivatePage, usePrivateLegacyPagesMigrationModal,
-} from '~/stores/modal';
+import type { ILegacyPrivatePage } from '~/stores/modal';
+import { usePrivateLegacyPagesMigrationModal } from '~/stores/modal';
 import { mutatePageTree, useSWRxV5MigrationStatus } from '~/stores/page-listing';
 import {
   useSWRxSearch,
 } from '~/stores/search';
 
 import { MenuItemType } from './Common/Dropdown/PageItemControl';
+import { LoadingSpinner } from './LoadingSpinner';
 import PaginationWrapper from './PaginationWrapper';
 import { PrivateLegacyPagesMigrationModal } from './PrivateLegacyPagesMigrationModal';
 import { OperateAllControl } from './SearchPage/OperateAllControl';
 import SearchControl from './SearchPage/SearchControl';
-import { IReturnSelectedPageIds, SearchPageBase, usePageDeleteModalForBulkDeletion } from './SearchPage/SearchPageBase';
+import type { IReturnSelectedPageIds } from './SearchPage/SearchPageBase';
+import { SearchPageBase, usePageDeleteModalForBulkDeletion } from './SearchPage/SearchPageBase';
 
 
 // TODO: replace with "customize:showPageLimitationS"
@@ -61,7 +63,7 @@ const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.
   if (migrationStatus == null) {
     return (
       <div className="mw-0 flex-grow-1 flex-basis-0 m-5 text-muted text-center">
-        <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
+        <LoadingSpinner className="me-1 fs-3" />
       </div>
     );
   }

+ 2 - 2
apps/app/src/components/SavePageControls.tsx

@@ -28,7 +28,7 @@ import loggerFactory from '~/utils/logger';
 
 import { unpublish } from '../client/services/page-operation';
 
-
+import { LoadingSpinner } from './LoadingSpinner';
 import { GrantSelector } from './SavePageControls/GrantSelector';
 import { SlackNotification } from './SlackNotification';
 
@@ -101,7 +101,7 @@ const SavePageButton = (props: {slackChannels: string, isDeviceLargerThanMd?: bo
           disabled={isWaitingSaveProcessing}
         >
           {isWaitingSaveProcessing && (
-            <i className="fa fa-spinner fa-pulse me-1"></i>
+            <LoadingSpinner />
           )}
           {labelSubmitButton}
         </Button>

+ 2 - 1
apps/app/src/components/SavePageControls/GrantSelector/GrantSelector.tsx

@@ -11,6 +11,7 @@ import {
   Modal, ModalHeader, ModalBody,
 } from 'reactstrap';
 
+import { LoadingSpinner } from '~/components/LoadingSpinner';
 import type { IPageGrantData } from '~/interfaces/page';
 import { useCurrentUser } from '~/stores/context';
 
@@ -184,7 +185,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
     if (myUserGroups == null) {
       return (
         <div className="my-3 text-center">
-          <i className="fa fa-lg fa-spinner fa-pulse mx-auto text-muted"></i>
+          <LoadingSpinner className="mx-auto text-muted fs-4" />
         </div>
       );
     }

+ 2 - 1
apps/app/src/components/SearchPage/SearchPageBase.tsx

@@ -17,6 +17,7 @@ import { usePageDeleteModal } from '~/stores/modal';
 import { mutatePageTree } from '~/stores/page-listing';
 
 import type { ForceHideMenuItems } from '../Common/Dropdown/PageItemControl';
+import { LoadingSpinner } from '../LoadingSpinner';
 
 // Do not import with next/dynamic
 // see: https://github.com/weseek/growi/pull/7923
@@ -181,7 +182,7 @@ const SearchPageBaseSubstance: ForwardRefRenderFunction<ISelectableAll & IReturn
           {/* Loading */}
           { pages == null && (
             <div className="mw-0 flex-grow-1 flex-basis-0 m-5 text-muted text-center">
-              <i className="fa fa-2x fa-spinner fa-pulse me-1"></i>
+              <LoadingSpinner className="me-1 fs-3" />
             </div>
           ) }
 

+ 2 - 1
apps/app/src/components/TemplateModal/TemplateModal.tsx

@@ -26,6 +26,7 @@ import { usePersonalSettings } from '~/stores/personal-settings';
 import { usePreviewOptions } from '~/stores/renderer';
 import loggerFactory from '~/utils/logger';
 
+import { LoadingSpinner } from '../LoadingSpinner';
 import Preview from '../PageEditor/Preview';
 
 import { useFormatter } from './use-formatter';
@@ -186,7 +187,7 @@ const TemplateModalSubstance = (props: TemplateModalSubstanceProps): JSX.Element
 
             { isLoading && (
               <div className="h-100 d-flex justify-content-center align-items-center">
-                <i className="fa fa-2x fa-spinner fa-pulse text-muted mx-auto"></i>
+                <LoadingSpinner className="mx-auto text-muted fs-3" />
               </div>
             ) }
 

+ 2 - 1
apps/app/src/components/TreeItem/SimpleItem.tsx

@@ -15,6 +15,7 @@ import { usePageTreeDescCountMap } from '~/stores/ui';
 import { shouldRecoverPagePaths } from '~/utils/page-operation';
 
 import CountBadge from '../Common/CountBadge';
+import { LoadingSpinner } from '../LoadingSpinner';
 
 import { ItemNode } from './ItemNode';
 import { useNewPageInput } from './NewPageInput';
@@ -248,7 +249,7 @@ export const SimpleItem: FC<SimpleItemProps> = (props) => {
               <ItemClassFixed {...itemProps} />
               {isProcessingSubmission && (currentChildren.length - 1 === index) && (
                 <div className="text-muted text-center">
-                  <i className="fa fa-spinner fa-pulse mr-1"></i>
+                  <LoadingSpinner className="mr-1" />
                 </div>
               )}
             </div>

+ 2 - 1
apps/app/src/pages/tags.page.tsx

@@ -8,6 +8,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 
+import { LoadingSpinner } from '~/components/LoadingSpinner';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { IDataTagCount } from '~/interfaces/tag';
@@ -90,7 +91,7 @@ const TagPage: NextPageWithLayout<CommonProps> = (props: Props) => {
           { isLoading
             ? (
               <div className="text-muted text-center">
-                <i className="fa fa-2x fa-spinner fa-pulse mt-3"></i>
+                <LoadingSpinner className="mt-3 fs-3" />
               </div>
             )
             : (