@atlaskit/dynamic-table
Version:
A dynamic table displays rows of data with built-in pagination, sorting, and re-ordering functionality.
177 lines (176 loc) • 7.08 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
/* eslint-disable @repo/internal/dom-events/no-unsafe-event-listeners */
import React from 'react';
import Spinner from '@atlaskit/spinner';
import { LARGE, LOADING_CONTENTS_OPACITY } from '../internal/constants';
import { Container, SpinnerBackdrop, SpinnerContainer } from '../styled/loading-container-advanced';
export default class LoadingContainerAdvanced extends React.Component {
constructor(...args) {
super(...args);
_defineProperty(this, "spinnerRef", /*#__PURE__*/React.createRef());
_defineProperty(this, "containerRef", /*#__PURE__*/React.createRef());
_defineProperty(this, "componentDidMount", () => {
if (this.props.isLoading && this.hasTargetNode()) {
this.attachListeners();
this.updateTargetAppearance();
this.updateSpinnerPosition();
}
});
_defineProperty(this, "UNSAFE_componentWillReceiveProps", nextProps => {
if (!nextProps.isLoading || !this.hasTargetNode(nextProps)) {
this.detachListeners();
} else if (!this.props.isLoading) {
this.attachListeners();
}
});
_defineProperty(this, "componentDidUpdate", () => {
if (this.hasTargetNode()) {
this.updateTargetAppearance();
if (this.props.isLoading) {
this.updateSpinnerPosition();
}
}
});
_defineProperty(this, "componentWillUnmount", () => {
this.detachListeners();
});
_defineProperty(this, "getTargetNode", (nextProps = this.props) => {
const {
targetRef
} = nextProps;
const target = targetRef === null || targetRef === void 0 ? void 0 : targetRef();
return target || this.containerRef.current;
});
_defineProperty(this, "hasTargetNode", nextProps => !!this.getTargetNode(nextProps));
_defineProperty(this, "isVerticallyVisible", (elementRect, viewportHeight) => {
const {
top,
bottom
} = elementRect;
if (bottom <= 0) {
return false;
}
return top < viewportHeight;
});
_defineProperty(this, "isFullyVerticallyVisible", (elementRect, viewportHeight) => {
const {
top,
bottom
} = elementRect;
return top >= 0 && bottom <= viewportHeight;
});
_defineProperty(this, "handleResize", () => {
this.updateSpinnerPosition();
});
_defineProperty(this, "handleScroll", () => {
this.updateSpinnerPosition();
});
_defineProperty(this, "translateSpinner", (spinnerNode, transformY, isFixed) => {
spinnerNode.style.position = isFixed ? 'fixed' : '';
spinnerNode.style.transform = transformY !== 0 ? `translate3d(0, ${transformY}px, 0)` : '';
});
_defineProperty(this, "updateTargetAppearance", () => {
const targetNode = this.getTargetNode();
const {
isLoading,
contentsOpacity
} = this.props;
if (targetNode && targetNode.style && typeof targetNode.style === 'object') {
targetNode.style.pointerEvents = isLoading ? 'none' : '';
targetNode.style.opacity = isLoading ? contentsOpacity.toString() : '';
}
});
}
attachListeners() {
window.addEventListener('scroll', this.handleScroll);
window.addEventListener('resize', this.handleResize);
}
detachListeners() {
window.removeEventListener('scroll', this.handleScroll);
window.removeEventListener('resize', this.handleResize);
}
updateSpinnerPosition() {
var _this$spinnerRef, _this$containerRef;
const viewportHeight = window.innerHeight;
const targetNode = this.getTargetNode();
const spinnerNode = (_this$spinnerRef = this.spinnerRef) === null || _this$spinnerRef === void 0 ? void 0 : _this$spinnerRef.current;
if (!targetNode || typeof targetNode.getBoundingClientRect !== 'function' || !spinnerNode) {
return;
}
const targetRect = targetNode.getBoundingClientRect();
const spinnerRect = spinnerNode.getBoundingClientRect();
const spinnerHeight = spinnerRect.height;
const isInViewport = this.isVerticallyVisible(targetRect, viewportHeight);
const {
top,
bottom,
height
} = targetRect;
if (isInViewport) {
// The spinner may follow the element only if there is enough space:
// Let's say the element can fit at least three spinners (vertically)
const canFollow = height >= spinnerHeight * 3;
if (canFollow && !this.isFullyVerticallyVisible(targetRect, viewportHeight)) {
if (top >= 0) {
// Only the head of the element is visible
const viewportSpaceTakenByElement = viewportHeight - top;
const diff = viewportSpaceTakenByElement / 2 + top - spinnerHeight / 2;
const y = viewportSpaceTakenByElement < spinnerHeight * 3 ? top + spinnerHeight : diff;
this.translateSpinner(spinnerNode, y, true);
} else if (top < 0 && bottom > viewportHeight) {
// The element takes all viewport, nor its head nor tail are visible
const y = viewportHeight / 2 - spinnerHeight / 2;
this.translateSpinner(spinnerNode, y, true);
} else {
// Only the tail of the element is visible
const diff = bottom / 2 - spinnerHeight / 2;
const y = diff < spinnerHeight ? diff - (spinnerHeight - diff) : diff;
this.translateSpinner(spinnerNode, y, true);
}
return;
}
} else {
// If both the element and the spinner are off screen - quit
if (!this.isVerticallyVisible(spinnerRect, viewportHeight)) {
return;
}
}
// Three options here:
// 1) the element is fully visible
// 2) the element is too small for the spinner to follow
// 3) the spinner might still be visible while the element isn't
const containerNode = (_this$containerRef = this.containerRef) === null || _this$containerRef === void 0 ? void 0 : _this$containerRef.current;
if (containerNode && typeof containerNode.getBoundingClientRect === 'function') {
const thisTop = containerNode.getBoundingClientRect().top;
const y = (top - thisTop) / 2;
this.translateSpinner(spinnerNode, y, false);
}
}
render() {
const {
children,
isLoading,
spinnerSize,
testId,
loadingLabel
} = this.props;
return /*#__PURE__*/React.createElement(Container, {
testId: testId && `${testId}--loading--container--advanced`,
ref: this.containerRef
}, children, isLoading && /*#__PURE__*/React.createElement(SpinnerBackdrop, {
testId: testId
}, /*#__PURE__*/React.createElement(SpinnerContainer, {
ref: this.spinnerRef
}, /*#__PURE__*/React.createElement(Spinner, {
size: spinnerSize,
testId: testId && `${testId}--loadingSpinner`,
label: loadingLabel
}))));
}
}
_defineProperty(LoadingContainerAdvanced, "defaultProps", {
isLoading: true,
spinnerSize: LARGE,
contentsOpacity: `var(--ds-opacity-loading, ${`${LOADING_CONTENTS_OPACITY}`})`,
loadingLabel: 'Loading table'
});