Fab.jsx 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import React, {
  2. useState, useCallback, useEffect, useRef,
  3. } from 'react';
  4. import { useRipple } from 'react-use-ripple';
  5. import StickyEvents from 'sticky-events';
  6. import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
  7. import { useCurrentPagePath, useCurrentUser } from '~/stores/context';
  8. import { usePageCreateModal } from '~/stores/modal';
  9. import loggerFactory from '~/utils/logger';
  10. import CreatePageIcon from './Icons/CreatePageIcon';
  11. import ReturnTopIcon from './Icons/ReturnTopIcon';
  12. const logger = loggerFactory('growi:cli:Fab');
  13. const Fab = () => {
  14. const { data: currentUser } = useCurrentUser();
  15. const { open: openCreateModal } = usePageCreateModal();
  16. const { data: currentPath = '' } = useCurrentPagePath();
  17. const [animateClasses, setAnimateClasses] = useState('invisible');
  18. const [buttonClasses, setButtonClasses] = useState('');
  19. // ripple
  20. const createBtnRef = useRef(null);
  21. useRipple(createBtnRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
  22. const stickyChangeHandler = useCallback((event) => {
  23. logger.debug('StickyEvents.CHANGE detected');
  24. const newAnimateClasses = event.detail.isSticky ? 'animated fadeInUp faster' : 'animated fadeOut faster';
  25. const newButtonClasses = event.detail.isSticky ? '' : 'disabled grw-pointer-events-none';
  26. setAnimateClasses(newAnimateClasses);
  27. setButtonClasses(newButtonClasses);
  28. }, []);
  29. // setup effect by sticky event
  30. useEffect(() => {
  31. // sticky
  32. // See: https://github.com/ryanwalters/sticky-events
  33. const stickyEvents = new StickyEvents({ stickySelector: '#grw-fav-sticky-trigger' });
  34. const { stickySelector } = stickyEvents;
  35. const elem = document.querySelector(stickySelector);
  36. elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
  37. // return clean up handler
  38. return () => {
  39. elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
  40. };
  41. }, [stickyChangeHandler]);
  42. function renderPageCreateButton() {
  43. return (
  44. <>
  45. <div className={`rounded-circle position-absolute ${animateClasses}`} style={{ bottom: '2.3rem', right: '4rem' }}>
  46. <button
  47. type="button"
  48. className={`btn btn-lg btn-create-page btn-primary rounded-circle p-0 ${buttonClasses}`}
  49. ref={createBtnRef}
  50. onClick={() => openCreateModal(currentPath)}
  51. >
  52. <CreatePageIcon />
  53. </button>
  54. </div>
  55. </>
  56. );
  57. }
  58. return (
  59. <div className="grw-fab d-none d-md-block d-edit-none" data-testid="grw-fab">
  60. {currentUser != null && renderPageCreateButton()}
  61. <div data-testid="grw-fab-return-to-top" className={`rounded-circle position-absolute ${animateClasses}`} style={{ bottom: 0, right: 0 }}>
  62. <button
  63. type="button"
  64. className={`btn btn-light btn-scroll-to-top rounded-circle p-0 ${buttonClasses}`}
  65. onClick={() => smoothScrollIntoView()}
  66. >
  67. <ReturnTopIcon />
  68. </button>
  69. </div>
  70. </div>
  71. );
  72. };
  73. export default Fab;