component.js 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import * as React from 'react';
  2. import { styleSingleton } from 'react-style-singleton';
  3. import { fullWidthClassName, zeroRightClassName, noScrollbarsClassName, removedBarSizeVariable } from './constants';
  4. import { getGapWidth } from './utils';
  5. const Style = styleSingleton();
  6. export const lockAttribute = 'data-scroll-locked';
  7. // important tip - once we measure scrollBar width and remove them
  8. // we could not repeat this operation
  9. // thus we are using style-singleton - only the first "yet correct" style will be applied.
  10. const getStyles = ({ left, top, right, gap }, allowRelative, gapMode = 'margin', important) => `
  11. .${noScrollbarsClassName} {
  12. overflow: hidden ${important};
  13. padding-right: ${gap}px ${important};
  14. }
  15. body[${lockAttribute}] {
  16. overflow: hidden ${important};
  17. overscroll-behavior: contain;
  18. ${[
  19. allowRelative && `position: relative ${important};`,
  20. gapMode === 'margin' &&
  21. `
  22. padding-left: ${left}px;
  23. padding-top: ${top}px;
  24. padding-right: ${right}px;
  25. margin-left:0;
  26. margin-top:0;
  27. margin-right: ${gap}px ${important};
  28. `,
  29. gapMode === 'padding' && `padding-right: ${gap}px ${important};`,
  30. ]
  31. .filter(Boolean)
  32. .join('')}
  33. }
  34. .${zeroRightClassName} {
  35. right: ${gap}px ${important};
  36. }
  37. .${fullWidthClassName} {
  38. margin-right: ${gap}px ${important};
  39. }
  40. .${zeroRightClassName} .${zeroRightClassName} {
  41. right: 0 ${important};
  42. }
  43. .${fullWidthClassName} .${fullWidthClassName} {
  44. margin-right: 0 ${important};
  45. }
  46. body[${lockAttribute}] {
  47. ${removedBarSizeVariable}: ${gap}px;
  48. }
  49. `;
  50. /**
  51. * Removes page scrollbar and blocks page scroll when mounted
  52. */
  53. export const RemoveScrollBar = (props) => {
  54. const { noRelative, noImportant, gapMode = 'margin' } = props;
  55. /*
  56. gap will be measured on every component mount
  57. however it will be used only by the "first" invocation
  58. due to singleton nature of <Style
  59. */
  60. const gap = React.useMemo(() => getGapWidth(gapMode), [gapMode]);
  61. React.useEffect(() => {
  62. document.body.setAttribute(lockAttribute, '');
  63. return () => {
  64. document.body.removeAttribute(lockAttribute);
  65. };
  66. }, []);
  67. return React.createElement(Style, { styles: getStyles(gap, !noRelative, gapMode, !noImportant ? '!important' : '') });
  68. };