UNPKG

lucid-ui

Version:

A UI component library from Xandr.

178 lines 7.11 kB
import _, { omit } from 'lodash'; import React from 'react'; import PropTypes from 'prop-types'; import { Motion, spring } from 'react-motion'; import { QUICK_SLIDE_MOTION } from '../../constants/motion-spring'; import { lucidClassNames } from '../../util/style-helpers'; import { shiftChildren } from '../../util/dom-helpers'; import { findTypes } from '../../util/component-types'; const cx = lucidClassNames.bind('&-SlidePanel'); const { bool, func, node, number, string, any } = PropTypes; const modulo = (n, a) => a - n * Math.floor(a / n); class SlidePanelSlide extends React.Component { render() { return null; } } SlidePanelSlide.displayName = 'SlidePanel.Slide'; SlidePanelSlide.propName = 'Slide'; const nonPassThroughs = [ 'className', 'children', 'Slide', 'slidesToShow', 'offset', 'isAnimated', 'isLooped', 'onSwipe', 'initialState', 'callbackId', ]; class SlidePanel extends React.Component { constructor() { super(...arguments); this.rootHTMLDivElement = React.createRef(); this.slideStrip = React.createRef(); this.offsetTranslate = this.props.isLooped ? Math.floor(_.size(findTypes(this.props, SlidePanel.Slide)) / 2) : 0; this.state = { translateXPixel: 0, startX: 0, isAnimated: this.props.isAnimated, isDragging: false, }; this.handleTouchStart = (event) => { this.setState({ startX: event.touches[0].screenX, isAnimated: false, isDragging: true, }); }; this.handleTouchMove = (event) => { const dX = event.touches[0].screenX - this.state.startX; this.setState({ translateXPixel: dX, }); }; this.handleTouchEnd = (event) => { const dX = event.changedTouches[0].screenX - this.state.startX; const slideWidth = this.rootHTMLDivElement.current.getBoundingClientRect() .width / this.props.slidesToShow; const slidesSwiped = Math.round(dX / slideWidth); if (slidesSwiped !== 0) { this.props.onSwipe(-1 * slidesSwiped, { event, props: this.props }); } this.setState({ translateXPixel: 0, isDragging: false, isAnimated: this.props.isAnimated, }); }; } componentDidMount() { const slides = findTypes(this.props, SlidePanel.Slide); if (this.props.isLooped) { shiftChildren(this.slideStrip.current, Math.floor(_.size(slides) / 2)); } } componentDidUpdate(prevProps, prevState) { const slides = findTypes(this.props, SlidePanel.Slide); const offsetDiff = this.props.offset - prevProps.offset; if (offsetDiff !== 0 && this.props.isLooped) { this.offsetTranslate = modulo(_.size(slides), this.offsetTranslate - offsetDiff); _.delay(() => { shiftChildren(this.slideStrip.current, -offsetDiff); this.setState({ isAnimated: false, }, () => { this.forceUpdate(); this.setState({ isAnimated: this.props.isAnimated, }); }); }, 200); } } render() { const { className, slidesToShow, offset: realOffset, isLooped, ...passThroughs } = this.props; const offset = realOffset + this.offsetTranslate; const slides = findTypes(this.props, SlidePanel.Slide); const translateXPercentage = -1 * (100 / slidesToShow) * (isLooped ? modulo(_.size(slides), offset) : offset); return (React.createElement("div", { ...omit(passThroughs, nonPassThroughs), ref: this.rootHTMLDivElement, className: cx('&', className) }, React.createElement(Motion, { style: this.state.isAnimated ? { translateXPercentage: spring(translateXPercentage, QUICK_SLIDE_MOTION), translateXPixel: spring(this.state.translateXPixel, QUICK_SLIDE_MOTION), } : { translateXPercentage: translateXPercentage, translateXPixel: this.state.translateXPixel, } }, (tween) => (React.createElement("div", { ...omit(passThroughs, nonPassThroughs), className: cx('&-slidestrip', className), style: { transform: this.state.isDragging ? `translateX(calc(${tween.translateXPercentage}% + ${this.state.translateXPixel}px))` : `translateX(calc(${tween.translateXPercentage}% + ${tween.translateXPixel}px))`, }, ref: this.slideStrip, onTouchStart: this.handleTouchStart, onTouchMove: this.handleTouchMove, onTouchEnd: this.handleTouchEnd, onTouchCancel: _.noop }, _.map(slides, (slide, offset) => (React.createElement("div", { key: offset, ...slide.props, className: cx('&-Slide', slide.props.className), style: { flexGrow: 1, flexShrink: 0, flexBasis: `${100 / slidesToShow}%`, ...slide.props.style, } })))))))); } } SlidePanel._isPrivate = true; SlidePanel.displayName = 'SlidePanel'; SlidePanel.peek = { description: `A container for rendering a set of horizontal slides at at a particular offset. Translation between slides is controlled by passing in a new \`offset\`. Can hook into touch events to update the \`offset\`.`, categories: ['helpers'], }; SlidePanel.propTypes = { /** Appended to the component-specific class names set on the root element. */ className: string, /** SlidePanel.Slide elements are passed in as children. */ children: node, /** This is the child component that will be displayed inside the SlidePanel. */ Slide: any, /** Max number of viewable slides to show simultaneously. */ slidesToShow: number, /** The offset of the left-most rendered slide. */ offset: number, /** Animate slides transitions from changes in \`offset\`. */ isAnimated: bool, /** Slides are rendered in a continuous loop, where the first slide repeats after the last slide and vice-versa. DOM elements are re-ordered and re-used. */ isLooped: bool, /** Called when a user's swipe would change the offset. Callback passes number of slides by the user (positive for forward swipes, negative for backwards swipes). Signature: \`(slidesSwiped, { event, props }) => {}\` */ onSwipe: func, }; SlidePanel.Slide = SlidePanelSlide; SlidePanel.defaultProps = { slidesToShow: 1, offset: 0, isAnimated: true, onSwipe: _.noop, isLooped: false, }; export default SlidePanel; //# sourceMappingURL=SlidePanel.js.map