StickyStretchableScroller.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. import React, {
  2. useEffect, useCallback, FC, useRef, useState, useMemo, RefObject,
  3. } from 'react';
  4. import SimpleBar from 'simplebar-react';
  5. import StickyEvents from 'sticky-events';
  6. import { debounce } from 'throttle-debounce';
  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. }
  14. /**
  15. * USAGE:
  16. *
  17. const calcViewHeight = useCallback(() => {
  18. const containerElem = document.querySelector('#sticky-elem');
  19. const containerTop = containerElem.getBoundingClientRect().top;
  20. // stretch to the bottom of window
  21. return window.innerHeight - containerTop;
  22. });
  23. return (
  24. <StickyStretchableScroller
  25. stickyElemSelector="#sticky-elem"
  26. calcViewHeight={calcViewHeight}
  27. >
  28. <div>
  29. ...
  30. </div>
  31. </StickyStretchableScroller>
  32. );
  33. */
  34. export const StickyStretchableScroller: FC<StickyStretchableScrollerProps> = (props) => {
  35. const {
  36. children, stickyElemSelector, calcViewHeight, simplebarRef: setSimplebarRef,
  37. } = props;
  38. const simplebarRef = useRef<SimpleBar>(null);
  39. const [simplebarMaxHeight, setSimplebarMaxHeight] = useState<number|undefined>();
  40. /**
  41. * Reset scrollbar
  42. */
  43. const resetScrollbar = useCallback(() => {
  44. if (simplebarRef.current == null || calcViewHeight == null) {
  45. return;
  46. }
  47. const scrollElement = simplebarRef.current.getScrollElement();
  48. const newHeight = calcViewHeight(scrollElement);
  49. logger.debug('Set new height to simplebar', newHeight);
  50. // set new height
  51. setSimplebarMaxHeight(newHeight);
  52. // reculculate
  53. simplebarRef.current.recalculate();
  54. }, [calcViewHeight]);
  55. const resetScrollbarDebounced = useMemo(() => debounce(100, resetScrollbar), [resetScrollbar]);
  56. // const stickyChangeHandler = useCallback(() => {
  57. // logger.debug('StickyEvents.CHANGE detected');
  58. // resetScrollbarDebounced();
  59. // }, [resetScrollbarDebounced]);
  60. // // setup effect by sticky event
  61. // useEffect(() => {
  62. // // sticky
  63. // // See: https://github.com/ryanwalters/sticky-events
  64. // const stickyEvents = new StickyEvents({ stickySelector: stickyElemSelector });
  65. // stickyEvents.enableEvents();
  66. // const { stickySelector } = stickyEvents;
  67. // const elem = document.querySelector(stickySelector);
  68. // elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
  69. // // return clean up handler
  70. // return () => {
  71. // elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
  72. // };
  73. // }, [stickyElemSelector, stickyChangeHandler]);
  74. // setup effect by resizing event
  75. useEffect(() => {
  76. const resizeHandler = () => {
  77. resetScrollbarDebounced();
  78. };
  79. window.addEventListener('resize', resizeHandler);
  80. // return clean up handler
  81. return () => {
  82. window.removeEventListener('resize', resizeHandler);
  83. };
  84. }, [resetScrollbarDebounced]);
  85. // setup effect on init
  86. useEffect(() => {
  87. resetScrollbarDebounced();
  88. }, [resetScrollbarDebounced]);
  89. // update ref
  90. useEffect(() => {
  91. if (setSimplebarRef != null) {
  92. setSimplebarRef(simplebarRef);
  93. }
  94. }, [setSimplebarRef]);
  95. return (
  96. <SimpleBar style={{ maxHeight: simplebarMaxHeight }} ref={simplebarRef}>
  97. { children }
  98. </SimpleBar>
  99. );
  100. };