You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ScreenPositionFixer.tsx 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import { throttle } from 'lodash';
  21. import * as React from 'react';
  22. import { findDOMNode } from 'react-dom';
  23. import { Theme, withTheme } from '../theme';
  24. interface Props {
  25. /**
  26. * First time `children` are rendered with `undefined` fixes to measure the offset.
  27. * Second time it renders with the computed fixes.
  28. */
  29. children: (props: Fixes) => React.ReactNode;
  30. /**
  31. * Use this flag to force re-positioning.
  32. * Use cases:
  33. * - when you need to measure `children` size first
  34. * - when you load content asynchronously
  35. */
  36. ready?: boolean;
  37. theme: Theme;
  38. }
  39. interface Fixes {
  40. leftFix?: number;
  41. topFix?: number;
  42. }
  43. export class ScreenPositionFixer extends React.Component<Props, Fixes> {
  44. throttledPosition: () => void;
  45. constructor(props: Props) {
  46. super(props);
  47. this.state = {};
  48. this.throttledPosition = throttle(this.position, 50);
  49. }
  50. componentDidMount() {
  51. this.addEventListeners();
  52. this.position();
  53. }
  54. componentDidUpdate(prevProps: Props) {
  55. if (!prevProps.ready && this.props.ready) {
  56. this.position();
  57. } else if (prevProps.ready && !this.props.ready) {
  58. this.reset();
  59. }
  60. }
  61. componentWillUnmount() {
  62. this.removeEventListeners();
  63. }
  64. addEventListeners = () => {
  65. window.addEventListener('resize', this.throttledPosition);
  66. };
  67. removeEventListeners = () => {
  68. window.removeEventListener('resize', this.throttledPosition);
  69. };
  70. reset = () => {
  71. this.setState({ leftFix: undefined, topFix: undefined });
  72. };
  73. position = () => {
  74. const edgeMargin = 0.5 * this.props.theme.rawSizes.grid;
  75. // eslint-disable-next-line react/no-find-dom-node
  76. const node = findDOMNode(this);
  77. if (node && node instanceof Element) {
  78. const { width, height, left, top } = node.getBoundingClientRect();
  79. const { clientHeight, clientWidth } = document.documentElement;
  80. let leftFix = 0;
  81. if (left < edgeMargin) {
  82. leftFix = edgeMargin - left;
  83. } else if (left + width > clientWidth - edgeMargin) {
  84. leftFix = clientWidth - edgeMargin - left - width;
  85. }
  86. let topFix = 0;
  87. if (top < edgeMargin) {
  88. topFix = edgeMargin - top;
  89. } else if (top + height > clientHeight - edgeMargin) {
  90. topFix = clientHeight - edgeMargin - top - height;
  91. }
  92. this.setState({ leftFix, topFix });
  93. }
  94. };
  95. render() {
  96. return this.props.children(this.state);
  97. }
  98. }
  99. export default withTheme(ScreenPositionFixer);