Fab.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import React, {
  2. useState, useCallback, useRef, useEffect,
  3. } from 'react';
  4. import { animateScroll } from 'react-scroll';
  5. import { useRipple } from 'react-use-ripple';
  6. import { useSticky } from '~/client/services/side-effects/use-sticky';
  7. import { usePageCreateModal } from '~/stores/modal';
  8. import { useCurrentPagePath } from '~/stores/page';
  9. import { useIsAbleToChangeEditorMode } from '~/stores/ui';
  10. import loggerFactory from '~/utils/logger';
  11. import { CreatePageIcon } from './Icons/CreatePageIcon';
  12. import { ReturnTopIcon } from './Icons/ReturnTopIcon';
  13. import styles from './Fab.module.scss';
  14. const logger = loggerFactory('growi:cli:Fab');
  15. export const Fab = (): JSX.Element => {
  16. const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
  17. const { data: currentPath = '' } = useCurrentPagePath();
  18. const { open: openCreateModal } = usePageCreateModal();
  19. const [animateClasses, setAnimateClasses] = useState<string>('invisible');
  20. const [buttonClasses, setButtonClasses] = useState<string>('');
  21. const [isStickyApplied, setIsStickyApplied] = useState(false);
  22. // ripple
  23. const createBtnRef = useRef(null);
  24. useRipple(createBtnRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
  25. // Get sticky status
  26. const isSticky = useSticky('#grw-fav-sticky-trigger');
  27. // check if isSticky is already initialized then save it in isStickyApplied state
  28. useEffect(() => {
  29. if (isSticky) {
  30. setIsStickyApplied(true);
  31. }
  32. }, [isSticky]);
  33. // Apply new classes if only isSticky is initialized, otherwise no classes have changed
  34. // Prevents the Fab button from showing on first load due to the isSticky effect
  35. useEffect(() => {
  36. if (isStickyApplied) {
  37. const timer = setTimeout(() => {
  38. if (isSticky) {
  39. setAnimateClasses('visible');
  40. setButtonClasses('');
  41. }
  42. else {
  43. setAnimateClasses('invisible');
  44. }
  45. }, 500);
  46. const newAnimateClasses = isSticky ? 'animated fadeInUp faster' : 'animated fadeOut faster';
  47. const newButtonClasses = isSticky ? '' : 'disabled grw-pointer-events-none';
  48. setAnimateClasses(newAnimateClasses);
  49. setButtonClasses(newButtonClasses);
  50. return () => clearTimeout(timer);
  51. }
  52. }, [isSticky, isStickyApplied]);
  53. const PageCreateButton = useCallback(() => {
  54. return (
  55. <div
  56. className={`rounded-circle position-absolute ${animateClasses}`}
  57. style={{ bottom: '2.3rem', right: '4rem' }}
  58. data-testid="grw-fab-page-create-button"
  59. >
  60. <button
  61. type="button"
  62. className={`btn btn-lg btn-create-page btn-primary rounded-circle p-0 ${buttonClasses}`}
  63. ref={createBtnRef}
  64. onClick={currentPath != null
  65. ? () => openCreateModal(currentPath)
  66. : undefined}
  67. >
  68. <CreatePageIcon />
  69. </button>
  70. </div>
  71. );
  72. }, [animateClasses, buttonClasses, currentPath, openCreateModal]);
  73. const ScrollToTopButton = useCallback(() => {
  74. const clickHandler = () => {
  75. animateScroll.scrollToTop({ duration: 200 });
  76. };
  77. return (
  78. <div className={`rounded-circle position-absolute ${animateClasses}`} style={{ bottom: 0, right: 0 }} data-testid="grw-fab-return-to-top">
  79. <button
  80. type="button"
  81. className={`btn btn-light btn-scroll-to-top rounded-circle p-0 ${buttonClasses}`}
  82. onClick={clickHandler}
  83. >
  84. <ReturnTopIcon />
  85. </button>
  86. </div>
  87. );
  88. }, [animateClasses, buttonClasses]);
  89. if (currentPath == null) {
  90. return <></>;
  91. }
  92. return (
  93. <div className={`${styles['grw-fab']} grw-fab d-none d-md-block d-edit-none`} data-testid="grw-fab-container">
  94. {isAbleToChangeEditorMode && <PageCreateButton />}
  95. <ScrollToTopButton />
  96. </div>
  97. );
  98. };