@zendeskgarden/react-loaders
Version:
Components relating to loaders in the Garden Design System
612 lines (592 loc) • 20.8 kB
JavaScript
/**
* Copyright Zendesk, Inc.
*
* Use of this source code is governed under the Apache License, Version 2.0
* found at http://www.apache.org/licenses/LICENSE-2.0.
*/
'use strict';
var React = require('react');
var PropTypes = require('prop-types');
var styled = require('styled-components');
var containerSchedule = require('@zendeskgarden/container-schedule');
var reactTheming = require('@zendeskgarden/react-theming');
var polished = require('polished');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var React__default = /*#__PURE__*/_interopDefault(React);
var PropTypes__default = /*#__PURE__*/_interopDefault(PropTypes);
var styled__default = /*#__PURE__*/_interopDefault(styled);
const dotOneKeyframes = styled.keyframes(["0%{transform:translate(0,5px);}3%{transform:translate(1px,-5px);}6%{transform:translate(3px,-15px);}8%{transform:translate(5px,-18px);}9%{transform:translate(7px,-21px);}11%{transform:translate(8px,-22px);}13%{transform:translate(9px,-23px);}16%{transform:translate(12px,-25px);}18%{transform:translate(13px,-26px);}23%{transform:translate(18px,-26px);}24%{transform:translate(19px,-25px);}28%{transform:translate(22px,-23px);}31%{transform:translate(24px,-21px);}33%{transform:translate(26px,-18px);}34%{transform:translate(28px,-14px);}36%{transform:translate(29px,-12px);}38%{transform:translate(30px,-5px);}39%{transform:translate(31px,5px);}54%{transform:translate(31px,3px);}59%{transform:translate(33px);}61%{transform:translate(43px);}63%{transform:translate(48px);}64%{transform:translate(51px);}66%{transform:translate(53px);}68%{transform:translate(55px);}69%{transform:translate(57px);}76%{transform:translate(60px);}81%{transform:translate(61px);}83%,100%{transform:translate(62px);}"]);
const dotTwoKeyframes = styled.keyframes(["4%{transform:translate(0);}6%{transform:translate(-1px);}8%{transform:translate(-2px);}9%{transform:translate(-5px);}11%{transform:translate(-7px);}13%{transform:translate(-12px);}14%{transform:translate(-17px);}16%{transform:translate(-19px);}18%{transform:translate(-22px);}19%{transform:translate(-25px);}21%{transform:translate(-26px);}23%{transform:translate(-27px);}24%{transform:translate(-28px);}26%{transform:translate(-29px);}29%{transform:translate(-30px);}33%,89%{transform:translate(-31px);}91%{transform:translate(-31px,1px);}94%{transform:translate(-31px,2px);}98%{transform:translate(-31px,3px);}99%{transform:translate(-31px,4px);}100%{transform:translate(-31px,5px);}"]);
const dotThreeKeyframes = styled.keyframes(["39%{transform:translate(0);}44%{transform:translate(0,1px);}46%{transform:translate(0,2px);}48%{transform:translate(0,3px);}49%{transform:translate(0,4px);}51%{transform:translate(0,5px);}53%{transform:translate(-1px,-6px);}54%{transform:translate(-2px,-13px);}56%{transform:translate(-3px,-15px);}58%{transform:translate(-5px,-19px);}59%{transform:translate(-7px,-21px);}61%{transform:translate(-8px,-22px);}63%{transform:translate(-9px,-24px);}64%{transform:translate(-11px,-25px);}66%{transform:translate(-12px,-26px);}74%{transform:translate(-19px,-26px);}76%{transform:translate(-20px,-25px);}78%{transform:translate(-22px,-24px);}81%{transform:translate(-24px,-21px);}83%{transform:translate(-26px,-19px);}84%{transform:translate(-28px,-15px);}86%{transform:translate(-29px,-13px);}88%{transform:translate(-30px,-6px);}89%{transform:translate(-31px,5px);}91%{transform:translate(-31px,4px);}93%{transform:translate(-31px,3px);}94%{transform:translate(-31px,2px);}98%{transform:translate(-31px,1px);}100%{transform:translate(-31px);}"]);
const StyledDotsCircle = styled__default.default.circle.attrs({
cy: 36,
r: 9
}).withConfig({
displayName: "StyledDots__StyledDotsCircle",
componentId: "sc-1ltah7e-0"
})([""]);
const animationStyles$1 = (animationName, props) => {
return styled.css(["animation:", " ", "ms linear infinite;"], animationName, props.$duration);
};
const StyledDotsCircleOne = styled__default.default(StyledDotsCircle).attrs({
cx: 9
}).withConfig({
displayName: "StyledDots__StyledDotsCircleOne",
componentId: "sc-1ltah7e-1"
})(["", ";"], props => animationStyles$1(dotOneKeyframes, props));
const StyledDotsCircleTwo = styled__default.default(StyledDotsCircle).attrs(() => ({
cx: 40
})).withConfig({
displayName: "StyledDots__StyledDotsCircleTwo",
componentId: "sc-1ltah7e-2"
})(["", ";"], props => animationStyles$1(dotTwoKeyframes, props));
const StyledDotsCircleThree = styled__default.default(StyledDotsCircle).attrs(() => ({
cx: 71
})).withConfig({
displayName: "StyledDots__StyledDotsCircleThree",
componentId: "sc-1ltah7e-3"
})(["", ";"], props => animationStyles$1(dotThreeKeyframes, props));
const COMPONENT_ID$5 = 'loaders.loading_placeholder';
const sizeStyles$1 = _ref => {
let {
$width = '1em',
$height = '0.9em',
$fontSize
} = _ref;
const [value, unit] = polished.getValueAndUnit($fontSize);
let fontSize;
if (unit === undefined) {
fontSize = $fontSize;
} else {
fontSize = `${value}${unit === '' ? 'px' : unit}`;
}
return styled.css(["width:", ";height:", ";font-size:", ";"], $width, $height, fontSize);
};
const StyledLoadingPlaceholder = styled__default.default.div.attrs({
'data-garden-id': COMPONENT_ID$5,
'data-garden-version': '9.5.3',
role: 'progressbar'
}).withConfig({
displayName: "StyledLoadingPlaceholder",
componentId: "sc-x3bwsx-0"
})(["display:inline-block;", ";", ""], sizeStyles$1, reactTheming.componentStyles);
const sizeToHeight = ($size, theme) => {
switch ($size) {
case 'small':
return theme.space.base / 2;
case 'medium':
return theme.space.base * 1.5;
default:
return theme.space.base * 3;
}
};
const sizeToBorderRadius = ($size, theme) => sizeToHeight($size, theme) / 2;
const PROGRESS_BACKGROUND_COMPONENT_ID = 'loaders.progress_background';
const colorStyles$2 = _ref => {
let {
theme,
$color
} = _ref;
const backgroundColor = reactTheming.getColor({
theme,
hue: 'neutralHue',
transparency: theme.opacity[200],
light: {
shade: 700
},
dark: {
shade: 500
}
});
let options;
if ($color) {
options = $color.includes('.') ? {
variable: $color,
theme
} : {
hue: $color,
theme
};
} else {
options = {
variable: 'border.successEmphasis',
theme
};
}
const foregroundColor = reactTheming.getColor(options);
return styled.css(["background-color:", ";color:", ";"], backgroundColor, foregroundColor);
};
const StyledProgressBackground = styled__default.default.div.attrs({
'data-garden-id': PROGRESS_BACKGROUND_COMPONENT_ID,
'data-garden-version': '9.5.3'
}).withConfig({
displayName: "StyledProgress__StyledProgressBackground",
componentId: "sc-2g8w4s-0"
})(["margin:", "px 0;border-radius:", "px;", ";", ""], props => props.theme.space.base * 2, props => sizeToBorderRadius(props.$size, props.theme), colorStyles$2, reactTheming.componentStyles);
const PROGESS_INDICATOR_COMPONENT_ID = 'loaders.progress_indicator';
const StyledProgressIndicator = styled__default.default.div.attrs({
'data-garden-id': PROGESS_INDICATOR_COMPONENT_ID,
'data-garden-version': '9.5.3'
}).withConfig({
displayName: "StyledProgress__StyledProgressIndicator",
componentId: "sc-2g8w4s-1"
})(["transition:width 0.1s ease-in-out;border-radius:", "px;background:currentcolor;width:", "%;height:", "px;", ""], props => sizeToBorderRadius(props.$size, props.theme), props => props.$value, props => sizeToHeight(props.$size, props.theme), reactTheming.componentStyles);
const COMPONENT_ID$4 = 'loaders.skeleton';
const fadeInAnimation = styled.keyframes(["0%,60%{opacity:0;}100%{opacity:1;}"]);
const skeletonAnimation = styled.keyframes(["0%{transform:translateX(-100%);}100%{transform:translateX(100%);}"]);
const skeletonRtlAnimation = styled.keyframes(["0%{transform:translateX(100%);}100%{transform:translateX(-100%)}"]);
const getBackgroundColor = _ref => {
let {
theme,
$isLight
} = _ref;
let backgroundColor;
if ($isLight) {
backgroundColor = reactTheming.getColor({
theme,
hue: 'white',
transparency: theme.opacity[200]
});
} else {
backgroundColor = reactTheming.getColor({
theme,
hue: 'neutralHue',
transparency: theme.opacity[200],
light: {
shade: 700
},
dark: {
shade: 500
}
});
}
return backgroundColor;
};
const animationStyles = _ref2 => {
let {
theme
} = _ref2;
if (theme.rtl) {
return styled.css(["animation:", " 1.5s ease-in-out 300ms infinite;"], skeletonRtlAnimation);
}
return styled.css(["animation:", " 1.5s ease-in-out 300ms infinite;"], skeletonAnimation);
};
const gradientStyles = props => {
return styled.css(["background-image:linear-gradient( ", ",transparent,", ",transparent );"], props.theme.rtl ? '-45deg' : '45deg', getBackgroundColor);
};
const StyledSkeleton = styled__default.default.div.attrs({
'data-garden-id': COMPONENT_ID$4,
'data-garden-version': '9.5.3'
}).withConfig({
displayName: "StyledSkeleton",
componentId: "sc-1raozze-0"
})(["display:inline-block;position:relative;animation:", " 750ms linear;border-radius:", ";background-color:", ";width:", ";height:", ";overflow:hidden;line-height:", ";&::before{position:absolute;top:0;width:1000px;height:100%;content:'';", " ", "}", ";"], fadeInAnimation, props => props.theme.borderRadii.md, getBackgroundColor, props => props.$width, props => props.$height, props => reactTheming.getLineHeight(props.theme.fontSizes.sm, props.theme.space.base * 5), animationStyles, gradientStyles, reactTheming.componentStyles);
const StyledSpinnerCircle = styled__default.default.circle.attrs(props => ({
cx: 40,
cy: 40,
r: 34,
fill: 'none',
stroke: 'currentColor',
strokeLinecap: 'round',
strokeWidth: props.$strokeWidthValue,
strokeDasharray: `${props.$dasharrayValue} 250`,
transform: props.transform
})).withConfig({
displayName: "StyledSpinnerCircle",
componentId: "sc-o4kd70-0"
})([""]);
const colorStyles$1 = _ref => {
let {
theme,
$color = 'inherit'
} = _ref;
const options = $color.includes('.') ? {
variable: $color,
theme
} : {
hue: $color,
theme
};
return styled.css(["color:", ";"], reactTheming.getColor(options));
};
const sizeStyles = _ref2 => {
let {
$containerWidth = '1em',
$containerHeight = '0.9em',
$fontSize = 'inherit'
} = _ref2;
const [value, unit] = polished.getValueAndUnit($fontSize);
let fontSize;
if (unit === undefined) {
fontSize = $fontSize;
} else {
fontSize = `${value}${unit === '' ? 'px' : unit}`;
}
return styled.css(["width:", ";height:", ";font-size:", ";"], $containerWidth, $containerHeight, fontSize);
};
const StyledSVG = styled__default.default.svg.attrs(props => ({
'data-garden-version': '9.5.3',
xmlns: 'http://www.w3.org/2000/svg',
focusable: 'false',
viewBox: `0 0 ${props.$width} ${props.$height}`,
role: 'img'
})).withConfig({
displayName: "StyledSVG",
componentId: "sc-1xtc3kx-0"
})(["", ";", ";", ";"], sizeStyles, colorStyles$1, reactTheming.componentStyles);
const COMPONENT_ID$3 = 'loaders.inline';
const colorStyles = _ref => {
let {
theme,
$color
} = _ref;
const options = $color.includes('.') ? {
variable: $color,
theme
} : {
hue: $color,
theme
};
return styled.css(["color:", ";"], reactTheming.getColor(options));
};
const retrieveAnimation = _ref2 => {
let {
theme
} = _ref2;
return styled.keyframes(["0%,100%{opacity:", ";}50%{opacity:", ";}"], theme.opacity[200], theme.opacity[600]);
};
const StyledCircle = styled__default.default.circle.attrs({
fill: 'currentColor',
cy: 2,
r: 2
}).withConfig({
displayName: "StyledInline__StyledCircle",
componentId: "sc-fxsb9l-0"
})([""]);
const StyledInline = styled__default.default.svg.attrs(props => ({
'data-garden-id': COMPONENT_ID$3,
'data-garden-version': '9.5.3',
viewBox: '0 0 16 4',
width: props.$size,
height: props.$size * 0.25
})).withConfig({
displayName: "StyledInline",
componentId: "sc-fxsb9l-1"
})(["", ";", "{opacity:0.2;&:nth-child(1){animation:", " 1s infinite;animation-delay:", ";}&:nth-child(2){animation:", " 1s infinite;animation-delay:0.2s;}&:nth-child(3){animation:", " 1s infinite;animation-delay:", ";}}", ""], colorStyles, StyledCircle, retrieveAnimation, props => props.theme.rtl ? 'unset' : '0.4s', retrieveAnimation, retrieveAnimation, props => props.theme.rtl ? '0.4s' : 'unset', reactTheming.componentStyles);
const COMPONENT_ID$2 = 'loaders.dots';
const Dots = React.forwardRef((_ref, ref) => {
let {
size,
color,
duration,
delayMS,
...other
} = _ref;
const theme = React.useContext(styled.ThemeContext);
const environment = reactTheming.useDocument(theme);
const canTransformSVG = React.useRef(null);
if (environment && canTransformSVG.current === null) {
const svg = environment.createElementNS('http://www.w3.org/2000/svg', 'svg');
canTransformSVG.current = 'transform' in svg;
}
const {
delayComplete
} = containerSchedule.useSchedule({
duration,
delayMS,
loop: true
});
const dotOne = React.useRef(null);
const dotTwo = React.useRef(null);
const dotThree = React.useRef(null);
React.useEffect(() => {
if (!canTransformSVG.current && delayComplete) {
const transforms = [window.getComputedStyle(dotOne.current).getPropertyValue('transform'), window.getComputedStyle(dotTwo.current).getPropertyValue('transform'), window.getComputedStyle(dotThree.current).getPropertyValue('transform')];
dotOne.current.setAttribute('transform', transforms[0]);
dotTwo.current.setAttribute('transform', transforms[1]);
dotThree.current.setAttribute('transform', transforms[2]);
}
});
if (!delayComplete && delayMS !== 0) {
return React__default.default.createElement(StyledLoadingPlaceholder, {
$fontSize: size
}, "\xA0");
}
return React__default.default.createElement(StyledSVG, Object.assign({
"data-garden-id": COMPONENT_ID$2,
ref: ref,
$fontSize: size,
$color: color,
$width: "80",
$height: "72"
}, other), React__default.default.createElement("g", {
fill: "currentColor"
}, React__default.default.createElement(StyledDotsCircleOne, {
$duration: duration,
ref: dotOne
}), React__default.default.createElement(StyledDotsCircleTwo, {
$duration: duration,
ref: dotTwo
}), React__default.default.createElement(StyledDotsCircleThree, {
$duration: duration,
ref: dotThree
})));
});
Dots.displayName = 'Dots';
Dots.propTypes = {
size: PropTypes__default.default.any,
duration: PropTypes__default.default.number,
color: PropTypes__default.default.string,
delayMS: PropTypes__default.default.number
};
Dots.defaultProps = {
size: 'inherit',
color: 'inherit',
duration: 1250,
delayMS: 750
};
const SIZE = ['small', 'medium', 'large'];
const COMPONENT_ID$1 = 'loaders.progress';
const Progress = React__default.default.forwardRef((_ref, ref) => {
let {
color,
value,
size,
'aria-label': label,
...other
} = _ref;
const percentage = Math.max(0, Math.min(100, value));
const ariaLabel = reactTheming.useText(Progress, {
'aria-label': label
}, 'aria-label', 'Progress');
return (
React__default.default.createElement(StyledProgressBackground, Object.assign({
"data-garden-id": COMPONENT_ID$1,
"data-garden-version": '9.5.3',
"aria-valuemax": 100,
"aria-valuemin": 0,
"aria-valuenow": percentage,
role: "progressbar",
$size: size,
$color: color,
ref: ref,
"aria-label": ariaLabel
}, other), React__default.default.createElement(StyledProgressIndicator, {
$value: percentage,
$size: size
}))
);
});
Progress.displayName = 'Progress';
Progress.propTypes = {
color: PropTypes__default.default.string,
value: PropTypes__default.default.number.isRequired,
size: PropTypes__default.default.oneOf(SIZE)
};
Progress.defaultProps = {
value: 0,
size: 'medium'
};
const Skeleton = React.forwardRef((_ref, ref) => {
let {
width,
height,
isLight,
...other
} = _ref;
return React__default.default.createElement(StyledSkeleton, Object.assign({
ref: ref,
$isLight: isLight,
$width: width,
$height: height
}, other), "\xA0");
});
Skeleton.displayName = 'Skeleton';
Skeleton.propTypes = {
width: PropTypes__default.default.string,
height: PropTypes__default.default.string,
isLight: PropTypes__default.default.bool
};
Skeleton.defaultProps = {
width: '100%',
height: '100%'
};
const STROKE_WIDTH_FRAMES = {
0: 6,
14: 5,
26: 4,
36: 3,
46: 2,
58: 3,
70: 4,
80: 5,
91: 6
};
const ROTATION_FRAMES = {
0: -90,
8: -81,
36: -30,
41: -18,
44: -8,
48: 0,
55: 22,
63: 53,
64: 62,
66: 67,
68: 78,
69: 90,
71: 99,
73: 112,
74: 129,
76: 138,
78: 159,
79: 180,
81: 190,
83: 207,
84: 221,
86: 226,
88: 235,
90: 243,
99: 270
};
const DASHARRAY_FRAMES = {
0: 0,
13: 2,
26: 13,
53: 86,
58: 90,
63: 89,
64: 88,
66: 86,
68: 83,
69: 81,
71: 76,
73: 70,
74: 62,
76: 58,
78: 47,
79: 37,
81: 31,
83: 23,
84: 16,
88: 10,
89: 7,
98: 1,
99: 0
};
const COMPONENT_ID = 'loaders.spinner';
const TOTAL_FRAMES = 100;
const computeFrames = (frames, duration) => {
return Object.entries(frames).reduce((acc, item, index, arr) => {
const [frame, value] = item;
const [nextFrame, nextValue] = arr[index + 1] || [TOTAL_FRAMES, arr[0][1]];
const diff = parseInt(nextFrame, 10) - parseInt(frame, 10);
const frameHz = 1000 / 60;
let subDuration = duration / (TOTAL_FRAMES - 1) * diff;
let lastValue = value;
acc[frame] = value;
for (let idx = 0; idx < diff; idx++) {
lastValue = lastValue + (nextValue - lastValue) * (frameHz / subDuration);
subDuration = duration / (TOTAL_FRAMES - 1) * (diff - idx);
acc[parseInt(frame, 10) + idx + 1] = lastValue;
}
acc[nextFrame] = nextValue;
return acc;
}, {});
};
const Spinner = React.forwardRef((_ref, ref) => {
let {
size,
duration,
color,
delayMS,
...other
} = _ref;
const strokeWidthValues = computeFrames(STROKE_WIDTH_FRAMES, duration);
const rotationValues = computeFrames(ROTATION_FRAMES, duration);
const dasharrayValues = computeFrames(DASHARRAY_FRAMES, duration);
const {
elapsed,
delayComplete
} = containerSchedule.useSchedule({
duration,
delayMS
});
const frame = (elapsed * 100).toFixed(0);
const strokeWidthValue = strokeWidthValues[frame];
const rotationValue = rotationValues[frame];
const dasharrayValue = dasharrayValues[frame];
const WIDTH = 80;
const HEIGHT = 80;
if (!delayComplete && delayMS !== 0) {
return React__default.default.createElement(StyledLoadingPlaceholder, {
$width: "1em",
$height: "1em",
$fontSize: size
}, "\xA0");
}
return React__default.default.createElement(StyledSVG, Object.assign({
$color: color,
$containerHeight: "1em",
$containerWidth: "1em",
$fontSize: size,
"data-garden-id": COMPONENT_ID,
$height: HEIGHT,
ref: ref,
$width: WIDTH
}, other), React__default.default.createElement(StyledSpinnerCircle, {
$dasharrayValue: dasharrayValue,
$strokeWidthValue: strokeWidthValue,
transform: `rotate(${rotationValue}, ${WIDTH / 2}, ${HEIGHT / 2})`
}));
});
Spinner.displayName = 'Spinner';
Spinner.propTypes = {
size: PropTypes__default.default.any,
duration: PropTypes__default.default.number,
color: PropTypes__default.default.string,
delayMS: PropTypes__default.default.number
};
Spinner.defaultProps = {
size: 'inherit',
duration: 1250,
color: 'inherit',
delayMS: 750
};
const Inline = React.forwardRef((_ref, ref) => {
let {
size,
color,
...other
} = _ref;
const ariaLabel = reactTheming.useText(Inline, other, 'aria-label', 'loading');
return (
React__default.default.createElement(StyledInline, Object.assign({
ref: ref,
$size: size,
$color: color,
"aria-label": ariaLabel,
role: "img"
}, other), React__default.default.createElement(StyledCircle, {
cx: "14"
}), React__default.default.createElement(StyledCircle, {
cx: "8"
}), React__default.default.createElement(StyledCircle, {
cx: "2"
}))
);
});
Inline.displayName = 'Inline';
Inline.propTypes = {
size: PropTypes__default.default.number,
color: PropTypes__default.default.string
};
Inline.defaultProps = {
size: 16,
color: 'inherit'
};
exports.Dots = Dots;
exports.Inline = Inline;
exports.Progress = Progress;
exports.Skeleton = Skeleton;
exports.Spinner = Spinner;