import * as React from "react"; import * as smoothscroll from 'smoothscroll-polyfill'; export interface ISliderProps { /** * Apply a `width: 100%` style to the .slider */ full?: boolean; /** * Center the around a child `Element`. This is contrained * by the bounds of the containing `Element`. * * @see `Slider#calculateCenteringTransform` */ centerOnChild?: number; children?: JSX.Element | JSX.Element[]; } export class Slider extends React.Component { public centerChildRef: React.RefObject; public sliderRef: React.RefObject; constructor(props: ISliderProps) { super(props); if (props.centerOnChild !== undefined) { this.centerChildRef = React.createRef(); } this.sliderRef = React.createRef(); // We need to add a 'smoothscroll' polyfill to support Safari smoothscroll.polyfill(); } public componentDidMount() { if (this.props.centerOnChild !== undefined) { this.centerSliderAroundChild(this.props.centerOnChild); } } public componentDidUpdate(prevProps: ISliderProps) { if (prevProps.centerOnChild !== this.props.centerOnChild && this.props.centerOnChild !== undefined) { this.centerSliderAroundChild(this.props.centerOnChild); } } public render() { const { full, children, centerOnChild } = this.props; // Create a reference to be applied to the child that needs centering. this.centerChildRef = React.createRef(); return (
{React.Children.map(children, (child: JSX.Element, index: number) => { return React.cloneElement(child, { ...index === this.props.centerOnChild && { innerRef: this.centerChildRef, }, }); })}
); } /** * Scroll to to a specific x-offset. We calculate this offset * by finding the center line of the containing screen and calculating the difference * between that and the center of the target Element. */ private centerSliderAroundChild = (childIndex: number) => { // The index must be valid if (!(childIndex >= 0 && childIndex < React.Children.count(this.props.children) - 1)) { return; } const sliderNode = this.sliderRef.current; const centerChildNode = this.centerChildRef.current; if (sliderNode && centerChildNode) { // 1. Get the center point on the x-axis for the container of the `sliderNode` const centerXPositionPoint = Math.round(sliderNode.getBoundingClientRect().width / 2); // 2. Get half of the width of the child to be centered const halfWidthOfCenterChild = Math.round(centerChildNode.getBoundingClientRect().width / 2); // The translation is the distance from the center point on the x-axis to the offset of the child while // accounting for the width of the child. const translate = -(centerXPositionPoint - centerChildNode.offsetLeft - halfWidthOfCenterChild); sliderNode.scroll({ left: translate, behavior: "smooth", }); } } }