StickyStretchableScroller.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import React, {
  2. useEffect, useCallback, useRef, useState, useMemo, RefObject,
  3. } from 'react';
  4. import SimpleBar from 'simplebar-react';
  5. import { debounce } from 'throttle-debounce';
  6. import { useSticky } from '~/client/services/side-effects/use-sticky';
  7. import loggerFactory from '~/utils/logger';
  8. const logger = loggerFactory('growi:cli:StickyStretchableScroller');
  9. export type StickyStretchableScrollerProps = {
  10. stickyElemSelector: string,
  11. simplebarRef?: (ref: RefObject<SimpleBar>) => void,
  12. calcViewHeight?: (scrollElement: HTMLElement) => number,
  13. children?: JSX.Element,
  14. }
  15. /**
  16. * USAGE:
  17. *
  18. const calcViewHeight = useCallback(() => {
  19. const containerElem = document.querySelector('#sticky-elem');
  20. const containerTop = containerElem.getBoundingClientRect().top;
  21. // stretch to the bottom of window
  22. return window.innerHeight - containerTop;
  23. });
  24. return (
  25. <StickyStretchableScroller
  26. stickyElemSelector="#sticky-elem"
  27. calcViewHeight={calcViewHeight}
  28. >
  29. <div>
  30. ...
  31. </div>
  32. </StickyStretchableScroller>
  33. );
  34. */
  35. export const StickyStretchableScroller = (props: StickyStretchableScrollerProps): JSX.Element => {
  36. const {
  37. children, stickyElemSelector, calcViewHeight, simplebarRef: setSimplebarRef,
  38. } = props;
  39. const simplebarRef = useRef<SimpleBar>(null);
  40. const [simplebarMaxHeight, setSimplebarMaxHeight] = useState<number|undefined>();
  41. // Get sticky status
  42. const isSticky = useSticky(stickyElemSelector);
  43. /**
  44. * Reset scrollbar
  45. */
  46. const resetScrollbar = useCallback(() => {
  47. if (simplebarRef.current == null || calcViewHeight == null) {
  48. return;
  49. }
  50. const scrollElement = simplebarRef.current.getScrollElement();
  51. const newHeight = calcViewHeight(scrollElement);
  52. logger.debug('Set new height to simplebar', newHeight);
  53. // set new height
  54. setSimplebarMaxHeight(newHeight);
  55. // reculculate
  56. simplebarRef.current.recalculate();
  57. }, [calcViewHeight]);
  58. const resetScrollbarDebounced = useMemo(() => debounce(100, resetScrollbar), [resetScrollbar]);
  59. useEffect(() => {
  60. resetScrollbarDebounced();
  61. }, [isSticky, resetScrollbarDebounced]);
  62. // setup effect by resizing event
  63. useEffect(() => {
  64. const resizeHandler = () => {
  65. resetScrollbarDebounced();
  66. };
  67. window.addEventListener('resize', resizeHandler);
  68. // return clean up handler
  69. return () => {
  70. window.removeEventListener('resize', resizeHandler);
  71. };
  72. }, [resetScrollbarDebounced]);
  73. // setup effect on init
  74. useEffect(() => {
  75. resetScrollbarDebounced();
  76. }, [resetScrollbarDebounced]);
  77. // update ref
  78. useEffect(() => {
  79. if (setSimplebarRef != null) {
  80. setSimplebarRef(simplebarRef);
  81. }
  82. }, [setSimplebarRef]);
  83. return (
  84. <SimpleBar style={{ maxHeight: simplebarMaxHeight }} ref={simplebarRef}>
  85. { children }
  86. </SimpleBar>
  87. );
  88. };