optimizely-oui
Version:
Optimizely's Component Library.
188 lines (173 loc) • 5.69 kB
JavaScript
import PropTypes from "prop-types";
import React from "react";
import ButtonRow from "../ButtonRow";
import classNames from "classnames";
class DockedFooter extends React.Component {
constructor(props) {
super(props);
this.state = {
isDocked: true,
listenersSet: false,
atBottom: true,
};
this.dockedFooterRef = React.createRef();
}
componentDidUpdate(prevProps, prevState) {
if (this.props.scrollRef !== prevProps.scrollRef) {
this.setIsDocked();
this.setEventListeners();
}
}
domNodesPresent = () => {
return !!this.dockedFooterRef.current && !!this.props.scrollRef;
};
setEventListeners = () => {
if (!this.state.listenersSet && this.domNodesPresent()) {
let scrollContainer = this.props.scrollRef;
window.addEventListener(
"resize",
this.throttle(3, () => this.setIsDocked()),
{ passive: false },
{ useCapture: true }
);
scrollContainer.addEventListener(
"scroll",
this.throttle(3, () => this.setAtBottom()),
{ passive: true }
);
scrollContainer.addEventListener(
"wheel",
this.throttle(3, () => this.setAtBottom()),
{ passive: true }
);
scrollContainer.addEventListener("click", () => this.setIsDocked(), {
passive: true,
});
scrollContainer.addEventListener("keydown", () => this.setIsDocked(), {
passive: true,
});
this.setState({ listenersSet: true });
}
};
throttle = (delay, fn) => {
let lastCall = 0;
return function (...args) {
const now = new Date().getTime();
if (now - lastCall < delay) {
return;
}
lastCall = now;
return fn(...args);
};
};
setIsDocked = callback => {
if (this.domNodesPresent()) {
const shouldDockByHeight =
this.dockedFooterRef.current.offsetTop >=
this.props.scrollRef.offsetHeight - this.dockedFooterRef.current.offsetHeight;
const newState = {
isDocked: shouldDockByHeight,
};
return this.setState(newState, () => {
this.setAtBottom();
});
}
this.setAtBottom();
};
setAtBottom = () => {
if (this.domNodesPresent()) {
const atBottom =
this.props.scrollRef.scrollHeight - this.props.scrollRef.scrollTop ===
this.props.scrollRef.clientHeight;
let newState = {
atBottom: atBottom,
isDocked: !atBottom,
};
this.setState(newState);
}
};
componentWillUnmount() {
window.removeEventListener("resize", this.throttle(3, this.shouldDock));
window.removeEventListener(
"mousemove",
this.throttle(5, this.setIsDocked),
{ passive: false },
{ useCapture: true }
);
this.props.scrollRef.removeEventListener("scroll", this.throttle(3, this.setAtBottom), { passive: true });
this.props.scrollRef.removeEventListener("wheel", this.throttle(3, this.setAtBottom), {
passive: true,
});
this.props.scrollRef.removeEventListener("click", this.shouldDock, {
passive: true,
});
this.props.scrollRef.removeEventListener("keydown", this.shouldDock, {
passive: true,
});
}
render() {
const {
centerGroup,
className,
includesMargin,
leftGroup,
rightGroup,
scrollRef,
testSection,
...props
} = this.props;
return (
<div
data-test-section={testSection}
ref={this.dockedFooterRef}
className={classNames({
"oui-docked-footer": true,
"oui-docked-footer--is-docked": this.state.isDocked,
"push-double--top": includesMargin,
}, className)}
{...props}
>
<ButtonRow
leftGroup={leftGroup}
centerGroup={centerGroup}
rightGroup={rightGroup}
testSection={testSection}
/>
</div>
);
}
}
DockedFooter.propTypes = {
/**
* Any components to be included in the DockedFooter
*/
centerGroup: PropTypes.arrayOf(PropTypes.element),
/** CSS class names. */
className: PropTypes.string,
/**
* Used to determine if there should be top margin
*/
includesMargin: PropTypes.bool,
/**
* Array of buttons for left side
*/
leftGroup: PropTypes.arrayOf(PropTypes.element),
/**
* Array of buttons for right side
*/
rightGroup: PropTypes.arrayOf(PropTypes.element),
/**
* Ref from parent element for DockedFooter to set listeners
*/
scrollRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({ current: PropTypes.elementType })]),
/**
* Identifier used to create data-test-section attributes for testing.
*/
testSection: PropTypes.string.isRequired,
};
DockedFooter.defaultProps = {
scrollRef: null,
includesMargin: false,
testSection: "",
};
export default DockedFooter;