UNPKG

4.62 kBJavaScriptView Raw
1'use client';
2import React from 'react';
3
4/**
5 * @internal
6 */
7const SkeletonThemeContext = React.createContext({});
8
9/* eslint-disable react/no-array-index-key */
10const defaultEnableAnimation = true;
11// For performance & cleanliness, don't add any inline styles unless we have to
12function styleOptionsToCssProperties({ baseColor, highlightColor, width, height, borderRadius, circle, direction, duration, enableAnimation = defaultEnableAnimation, customHighlightBackground, }) {
13 const style = {};
14 if (direction === 'rtl')
15 style['--animation-direction'] = 'reverse';
16 if (typeof duration === 'number')
17 style['--animation-duration'] = `${duration}s`;
18 if (!enableAnimation)
19 style['--pseudo-element-display'] = 'none';
20 if (typeof width === 'string' || typeof width === 'number')
21 style.width = width;
22 if (typeof height === 'string' || typeof height === 'number')
23 style.height = height;
24 if (typeof borderRadius === 'string' || typeof borderRadius === 'number')
25 style.borderRadius = borderRadius;
26 if (circle)
27 style.borderRadius = '50%';
28 if (typeof baseColor !== 'undefined')
29 style['--base-color'] = baseColor;
30 if (typeof highlightColor !== 'undefined')
31 style['--highlight-color'] = highlightColor;
32 if (typeof customHighlightBackground === 'string')
33 style['--custom-highlight-background'] = customHighlightBackground;
34 return style;
35}
36function Skeleton({ count = 1, wrapper: Wrapper, className: customClassName, containerClassName, containerTestId, circle = false, style: styleProp, ...originalPropsStyleOptions }) {
37 var _a, _b, _c;
38 const contextStyleOptions = React.useContext(SkeletonThemeContext);
39 const propsStyleOptions = { ...originalPropsStyleOptions };
40 // DO NOT overwrite style options from the context if `propsStyleOptions`
41 // has properties explicity set to undefined
42 for (const [key, value] of Object.entries(originalPropsStyleOptions)) {
43 if (typeof value === 'undefined') {
44 delete propsStyleOptions[key];
45 }
46 }
47 // Props take priority over context
48 const styleOptions = {
49 ...contextStyleOptions,
50 ...propsStyleOptions,
51 circle,
52 };
53 // `styleProp` has the least priority out of everything
54 const style = {
55 ...styleProp,
56 ...styleOptionsToCssProperties(styleOptions),
57 };
58 let className = 'react-loading-skeleton';
59 if (customClassName)
60 className += ` ${customClassName}`;
61 const inline = (_a = styleOptions.inline) !== null && _a !== void 0 ? _a : false;
62 const elements = [];
63 const countCeil = Math.ceil(count);
64 for (let i = 0; i < countCeil; i++) {
65 let thisStyle = style;
66 if (countCeil > count && i === countCeil - 1) {
67 // count is not an integer and we've reached the last iteration of
68 // the loop, so add a "fractional" skeleton.
69 //
70 // For example, if count is 3.5, we've already added 3 full
71 // skeletons, so now we add one more skeleton that is 0.5 times the
72 // original width.
73 const width = (_b = thisStyle.width) !== null && _b !== void 0 ? _b : '100%'; // 100% is the default since that's what's in the CSS
74 const fractionalPart = count % 1;
75 const fractionalWidth = typeof width === 'number'
76 ? width * fractionalPart
77 : `calc(${width} * ${fractionalPart})`;
78 thisStyle = { ...thisStyle, width: fractionalWidth };
79 }
80 const skeletonSpan = (React.createElement("span", { className: className, style: thisStyle, key: i }, "\u200C"));
81 if (inline) {
82 elements.push(skeletonSpan);
83 }
84 else {
85 // Without the <br />, the skeleton lines will all run together if
86 // `width` is specified
87 elements.push(React.createElement(React.Fragment, { key: i },
88 skeletonSpan,
89 React.createElement("br", null)));
90 }
91 }
92 return (React.createElement("span", { className: containerClassName, "data-testid": containerTestId, "aria-live": "polite", "aria-busy": (_c = styleOptions.enableAnimation) !== null && _c !== void 0 ? _c : defaultEnableAnimation }, Wrapper
93 ? elements.map((el, i) => React.createElement(Wrapper, { key: i }, el))
94 : elements));
95}
96
97function SkeletonTheme({ children, ...styleOptions }) {
98 return (React.createElement(SkeletonThemeContext.Provider, { value: styleOptions }, children));
99}
100
101export { SkeletonTheme, Skeleton as default };