grommet
Version:
focus on the essential experience
243 lines (240 loc) • 9.45 kB
JavaScript
var _excluded = ["activeChild", "initialChild", "onChild", "play", "children", "controls", "height", "fill", "width", "onFocus", "onBlur", "wrap"];
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
import React, { Children, useCallback, useContext, useEffect, useMemo, useState, useRef } from 'react';
import { normalizeColor } from '../../utils';
import { MessageContext } from '../../contexts/MessageContext';
import { Box } from '../Box';
import { Button } from '../Button';
import { Keyboard } from '../Keyboard';
import { StyledCarouselContainer, StyledControl } from './StyledCarousel';
import { CarouselChild } from './CarouselChild';
import { CarouselPropTypes } from './propTypes';
import { useThemeValue } from '../../utils/useThemeValue';
var Carousel = function Carousel(_ref) {
var activeChild = _ref.activeChild,
_ref$initialChild = _ref.initialChild,
initialChild = _ref$initialChild === void 0 ? 0 : _ref$initialChild,
onChild = _ref.onChild,
play = _ref.play,
children = _ref.children,
_ref$controls = _ref.controls,
controls = _ref$controls === void 0 ? true : _ref$controls,
height = _ref.height,
fill = _ref.fill,
width = _ref.width,
onFocus = _ref.onFocus,
onBlur = _ref.onBlur,
wrap = _ref.wrap,
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
var _useThemeValue = useThemeValue(),
theme = _useThemeValue.theme;
var _useContext = useContext(MessageContext),
format = _useContext.format;
var timerRef = useRef();
var animationDuration = useMemo(function () {
return play && play < theme.carousel.animation.duration ? play : theme.carousel.animation.duration;
}, [play, theme.carousel.animation.duration]);
var _useState = useState({
activeIndex: activeChild !== undefined ? activeChild : initialChild
}),
indexes = _useState[0],
setIndexes = _useState[1];
var _useState2 = useState(activeChild),
activeChildState = _useState2[0],
setActiveChildState = _useState2[1];
var _useState3 = useState(),
direction = _useState3[0],
setDirection = _useState3[1];
var _useState4 = useState(false),
inTransition = _useState4[0],
setInTransition = _useState4[1];
var activeIndex = indexes.activeIndex,
priorActiveIndex = indexes.priorActiveIndex;
var lastIndex = Children.count(children) - 1;
var onChildChange = useCallback(function (index) {
if (onChild) {
onChild(index);
}
}, [onChild]);
var onRight = useCallback(function () {
if (inTransition) return;
clearInterval(timerRef.current);
var nextActiveIndex = activeIndex < lastIndex ? activeIndex + 1 : 0;
setIndexes({
activeIndex: nextActiveIndex,
priorActiveIndex: activeIndex
});
setInTransition(true);
setDirection('left');
onChildChange(nextActiveIndex);
}, [activeIndex, inTransition, lastIndex, onChildChange]);
var onLeft = useCallback(function () {
if (inTransition) return;
clearInterval(timerRef.current);
var nextActiveIndex = activeIndex === 0 ? lastIndex : activeIndex - 1;
setIndexes({
activeIndex: nextActiveIndex,
priorActiveIndex: activeIndex
});
setInTransition(true);
setDirection('right');
onChildChange(nextActiveIndex);
}, [activeIndex, inTransition, lastIndex, onChildChange]);
var onSelect = useCallback(function (index) {
return function () {
if (!inTransition && activeIndex !== index) {
clearInterval(timerRef.current);
setIndexes({
activeIndex: index,
priorActiveIndex: activeIndex
});
setInTransition(true);
setDirection(index > activeIndex ? 'left' : 'right');
onChildChange(index);
}
};
}, [activeIndex, inTransition, onChildChange]);
var onControlledNavigation = useCallback(function () {
if (inTransition || activeChild === activeChildState || activeChild === activeIndex || activeChild === undefined || activeChild < 0 || activeChild > lastIndex) return;
setDirection(activeChild > activeIndex ? 'left' : 'right');
setInTransition(true);
setIndexes({
activeIndex: activeChild,
priorActiveIndex: activeIndex
});
setActiveChildState(activeChild);
onChildChange(activeChild);
}, [activeChild, activeChildState, activeIndex, inTransition, lastIndex, onChildChange]);
/**
* Delays the transitions between Carousel slides. This is needed to
* avoid users "spamming" the controls which results in jarring animations
* and a bad user experience.
*/
useEffect(function () {
var transitionTimer;
if (inTransition) {
transitionTimer = setTimeout(function () {
setInTransition(false);
}, animationDuration);
}
return function () {
return clearTimeout(transitionTimer);
};
}, [inTransition, setInTransition, animationDuration]);
// Handles auto-playing Carousel slides
useEffect(function () {
// stop playing if wrap is explicitly false and we're at the end
if (play && (wrap !== false || activeIndex < lastIndex)) {
var timer = setInterval(function () {
var nextActiveIndex = activeIndex < lastIndex ? activeIndex + 1 : 0;
setIndexes({
activeIndex: nextActiveIndex,
priorActiveIndex: activeIndex
});
setInTransition(true);
setDirection('left');
onChildChange(nextActiveIndex);
}, play);
timerRef.current = timer;
return function () {
clearTimeout(timer);
};
}
return function () {};
}, [activeIndex, play, children, lastIndex, onChildChange, wrap]);
// Allow Carousel slides to be controlled outside the component
useEffect(function () {
onControlledNavigation(activeIndex, activeChild, activeChildState, inTransition);
}, [onControlledNavigation, activeIndex, activeChild, activeChildState, inTransition]);
var showArrows = controls && controls !== 'selectors';
var showSelectors = controls && controls !== 'arrows';
var CurrentIcon = theme.carousel.icons.current;
var iconColor = normalizeColor(theme.carousel.icons.color || 'control', theme);
var selectors = [];
var wrappedChildren = Children.map(children, function (child, index) {
selectors.push(/*#__PURE__*/React.createElement(Button, {
a11yTitle: format({
id: 'carousel.jump',
values: {
slide: index + 1
}
})
// eslint-disable-next-line react/no-array-index-key
,
key: index,
icon: /*#__PURE__*/React.createElement(CurrentIcon, {
color: activeIndex === index ? iconColor : undefined
}),
onClick: onSelect(index)
}));
return /*#__PURE__*/React.createElement(CarouselChild, {
animationDuration: animationDuration,
fill: fill || !!height || !!width,
index: index,
activeIndex: activeIndex,
priorActiveIndex: priorActiveIndex,
direction: direction
}, child);
});
var NextIcon = theme.carousel.icons.next;
var PreviousIcon = theme.carousel.icons.previous;
var nextIconDisabled = !wrap && activeIndex >= lastIndex;
var previousIconDisabled = !wrap && activeIndex <= 0;
return /*#__PURE__*/React.createElement(Keyboard, {
onLeft: onLeft,
onRight: onRight
}, /*#__PURE__*/React.createElement(StyledCarouselContainer, _extends({
fill: fill,
height: height,
width: width
}, rest), showArrows && /*#__PURE__*/React.createElement(StyledControl, {
offsetProp: "left",
fill: "vertical"
}, /*#__PURE__*/React.createElement(Button, {
fill: "vertical",
icon: /*#__PURE__*/React.createElement(PreviousIcon, {
color: normalizeColor(previousIconDisabled ? theme.carousel.disabled.icons.color : theme.carousel.icons.color, theme)
}),
a11yTitle: format({
id: 'carousel.previous',
values: {
slide: activeIndex
}
}),
plain: true,
disabled: previousIconDisabled,
onClick: onLeft,
hoverIndicator: true
})), wrappedChildren, showSelectors && /*#__PURE__*/React.createElement(StyledControl, {
offsetProp: "bottom",
fill: "horizontal"
}, /*#__PURE__*/React.createElement(Box, {
justify: "end",
fill: !showArrows && 'horizontal'
}, /*#__PURE__*/React.createElement(Box, {
direction: "row",
justify: "center"
}, selectors))), showArrows && /*#__PURE__*/React.createElement(StyledControl, {
offsetProp: "right",
fill: "vertical"
}, /*#__PURE__*/React.createElement(Button, {
fill: "vertical",
icon: /*#__PURE__*/React.createElement(NextIcon, {
color: normalizeColor(nextIconDisabled ? theme.carousel.disabled.icons.color : theme.carousel.icons.color, theme)
}),
a11yTitle: format({
id: 'carousel.next',
values: {
slide: activeIndex + 2
}
}),
plain: true,
disabled: nextIconDisabled,
onClick: onRight,
hoverIndicator: true
}))));
};
Carousel.displayName = 'Carousel';
Carousel.propTypes = CarouselPropTypes;
export { Carousel };