react-native-skelo 0.0.2 â 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +86 -90
- package/lib/commonjs/animations/PulseAnimation.js +17 -42
- package/lib/commonjs/animations/PulseAnimation.js.map +1 -1
- package/lib/commonjs/animations/ShimmerAnimation.js +54 -65
- package/lib/commonjs/animations/ShimmerAnimation.js.map +1 -1
- package/lib/commonjs/components/Skeleton.js +22 -16
- package/lib/commonjs/components/Skeleton.js.map +1 -1
- package/lib/commonjs/core/parser/expandLists.js +67 -0
- package/lib/commonjs/core/parser/expandLists.js.map +1 -0
- package/lib/commonjs/index.js +1 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/animations/PulseAnimation.js +18 -43
- package/lib/module/animations/PulseAnimation.js.map +1 -1
- package/lib/module/animations/ShimmerAnimation.js +55 -66
- package/lib/module/animations/ShimmerAnimation.js.map +1 -1
- package/lib/module/components/Skeleton.js +22 -16
- package/lib/module/components/Skeleton.js.map +1 -1
- package/lib/module/core/parser/expandLists.js +61 -0
- package/lib/module/core/parser/expandLists.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/animations/PulseAnimation.d.ts +0 -44
- package/lib/typescript/animations/PulseAnimation.d.ts.map +1 -1
- package/lib/typescript/animations/ShimmerAnimation.d.ts +0 -44
- package/lib/typescript/animations/ShimmerAnimation.d.ts.map +1 -1
- package/lib/typescript/components/Skeleton.d.ts +1 -1
- package/lib/typescript/components/Skeleton.d.ts.map +1 -1
- package/lib/typescript/core/parser/expandLists.d.ts +3 -0
- package/lib/typescript/core/parser/expandLists.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types/skeleton.d.ts +8 -0
- package/lib/typescript/types/skeleton.d.ts.map +1 -1
- package/package.json +5 -12
- package/src/animations/PulseAnimation.tsx +17 -89
- package/src/animations/ShimmerAnimation.tsx +39 -118
- package/src/components/Skeleton.tsx +24 -18
- package/src/core/parser/expandLists.tsx +77 -0
- package/src/index.ts +4 -0
- package/src/types/skeleton.ts +9 -0
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026 Shubhanshu
|
|
3
|
+
Copyright (c) 2026 Shubhanshu Barnwal <connect@shubhanshubb.dev> (https://shubhanshubb.dev)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,134 +1,121 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
# react-native-skelo
|
|
3
|
+
# ðĶī react-native-skelo
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
### Write your UI once. Skelo builds the loading state â automatically.
|
|
6
6
|
|
|
7
|
-
[](https://www.npmjs.com/package/react-native-skelo)
|
|
8
|
-
[](https://www.npmjs.com/package/react-native-skelo)
|
|
8
|
+
[](https://www.npmjs.com/package/react-native-skelo)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
[](https://github.com/shubhanshubb/react-native-skelo)
|
|
11
|
+
|
|
12
|
+
<br />
|
|
13
|
+
|
|
14
|
+
<img src="./assets/demo.gif" alt="Skelo skeleton demo" width="270" />
|
|
15
|
+
|
|
16
|
+
<img src="./assets/grid.gif" alt="Image-grid skeleton" width="270" />
|
|
9
17
|
|
|
10
18
|
</div>
|
|
11
19
|
|
|
12
|
-
|
|
20
|
+
<br />
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
**Stop building and maintaining a second copy of your UI just for loading states.** Wrap your components in `<Skeleton>` and Skelo generates a matching skeleton â sized from the styles you already wrote.
|
|
15
23
|
|
|
16
24
|
```tsx
|
|
17
|
-
import { Skeleton } from 'react-native-skelo';
|
|
18
|
-
|
|
19
25
|
<Skeleton loading={isLoading}>
|
|
20
|
-
<
|
|
21
|
-
<Image style={styles.avatar} />
|
|
22
|
-
<Text style={styles.name}>{user.name}</Text>
|
|
23
|
-
</View>
|
|
26
|
+
<ProfileCard user={user} />
|
|
24
27
|
</Skeleton>
|
|
25
28
|
```
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
That's the whole API. No skeleton screens to hand-draw, no keeping two layouts in sync.
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
> â ïļ **Pre-release (`0.0.x`)** â evolving fast; the API may change before the stable `0.1.0`.
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
npm install react-native-skelo
|
|
33
|
-
npm install react-native-reanimated react-native-linear-gradient
|
|
34
|
-
```
|
|
34
|
+
---
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
## âĻ Features
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
- ðŠ **Zero-config** â wrap a component, get a skeleton. No setup, no separate loading screen.
|
|
39
|
+
- ð§ **Sees inside your components** â introspects custom components automatically (no inlining).
|
|
40
|
+
- ð **Lists included** â `FlatList` / `SectionList` render skeleton rows while data loads.
|
|
41
|
+
- ðĻ **Shimmer · Pulse · None** â built-in animations on the native driver.
|
|
42
|
+
- ðŠķ **No dependencies** â no `reanimated`, no gradient library. Just `react` + `react-native`.
|
|
43
|
+
- ð **Style-aware** â every skeleton is sized from your real element styles.
|
|
39
44
|
|
|
40
|
-
|
|
41
|
-
use. Install `react-reconciler` (matching your React version) and import the
|
|
42
|
-
deep entry **once** in your app's entry file:
|
|
45
|
+
---
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
# React 19:
|
|
46
|
-
npm install react-reconciler@0.31.0
|
|
47
|
-
# React 18:
|
|
48
|
-
npm install react-reconciler@0.29.2
|
|
49
|
-
```
|
|
47
|
+
## ðĶ Installation
|
|
50
48
|
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
import 'react-native-skelo/deep';
|
|
49
|
+
```bash
|
|
50
|
+
npm install react-native-skelo
|
|
54
51
|
```
|
|
55
52
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
## Usage
|
|
53
|
+
**That's it â no other dependencies.** Everything (including custom-component introspection) works out of the box.
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Skelo traverses the `View` / `Text` / `Image` tree you pass and mirrors it:
|
|
63
|
-
|
|
64
|
-
```tsx
|
|
65
|
-
<Skeleton loading={loading} animation="shimmer">
|
|
66
|
-
<View style={styles.row}>
|
|
67
|
-
<Image style={styles.avatar} /> {/* â circle / box */}
|
|
68
|
-
<Text style={styles.title}>Title</Text> {/* â text bar */}
|
|
69
|
-
</View>
|
|
70
|
-
</Skeleton>
|
|
71
|
-
```
|
|
55
|
+
> React 18 apps: add `"react-reconciler": "0.29.x"` to your deps (Skelo ships targeting React 19's `0.31`).
|
|
72
56
|
|
|
73
|
-
|
|
57
|
+
---
|
|
74
58
|
|
|
75
|
-
|
|
59
|
+
## ð Usage
|
|
76
60
|
|
|
77
|
-
|
|
61
|
+
### Just wrap it
|
|
78
62
|
|
|
79
63
|
```tsx
|
|
80
|
-
import { Skeleton
|
|
81
|
-
|
|
82
|
-
<Skeleton loading={loading} deep>
|
|
83
|
-
<ProfileScreen />
|
|
84
|
-
</Skeleton>
|
|
64
|
+
import { Skeleton } from 'react-native-skelo';
|
|
85
65
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<
|
|
66
|
+
function Screen({ loading, user }) {
|
|
67
|
+
return (
|
|
68
|
+
<Skeleton loading={loading} animation="shimmer">
|
|
69
|
+
<ProfileCard user={user} />
|
|
70
|
+
</Skeleton>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
89
73
|
```
|
|
90
74
|
|
|
91
|
-
|
|
75
|
+
Skelo renders `ProfileCard`, reads its real `View`/`Text`/`Image` tree + styles, and draws a matching skeleton while `loading` is `true`. When it's `false`, your real UI renders.
|
|
92
76
|
|
|
93
|
-
###
|
|
77
|
+
### Lists
|
|
94
78
|
|
|
95
|
-
|
|
79
|
+
Wrap a `FlatList` â Skelo fills it with skeleton rows while the data loads:
|
|
96
80
|
|
|
97
81
|
```tsx
|
|
98
|
-
<Skeleton loading={loading}
|
|
82
|
+
<Skeleton loading={loading} count={6}>
|
|
83
|
+
<FlatList data={items} renderItem={renderItem} />
|
|
84
|
+
</Skeleton>
|
|
99
85
|
```
|
|
100
86
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
### `Skeleton.Ignore` â keep something visible
|
|
87
|
+
### Keep something visible
|
|
104
88
|
|
|
105
89
|
```tsx
|
|
106
90
|
<Skeleton loading={loading}>
|
|
107
91
|
<Skeleton.Ignore>
|
|
108
|
-
<Text style={styles.heading}>Profile</Text>
|
|
92
|
+
<Text style={styles.heading}>Profile</Text> {/* stays real */}
|
|
109
93
|
</Skeleton.Ignore>
|
|
110
94
|
<ProfileBody />
|
|
111
95
|
</Skeleton>
|
|
112
96
|
```
|
|
113
97
|
|
|
114
|
-
###
|
|
98
|
+
### Generate from a StyleSheet
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<Skeleton loading={loading} styles={styles} excludeStyles={['container']} />
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Custom skeleton for a component
|
|
115
105
|
|
|
116
106
|
```tsx
|
|
117
107
|
import { Skelo } from 'react-native-skelo';
|
|
118
|
-
import { Avatar } from 'my-design-system';
|
|
119
108
|
|
|
120
109
|
Skelo.register({
|
|
121
110
|
name: 'Avatar',
|
|
122
111
|
component: Avatar,
|
|
123
|
-
strategy: (props, { primitives }) =>
|
|
124
|
-
<primitives.Circle size={props.size ?? 40} />
|
|
125
|
-
),
|
|
112
|
+
strategy: (props, { primitives }) => <primitives.Circle size={props.size ?? 40} />,
|
|
126
113
|
});
|
|
127
114
|
```
|
|
128
115
|
|
|
129
|
-
|
|
116
|
+
---
|
|
130
117
|
|
|
131
|
-
|
|
118
|
+
## âïļ `<Skeleton>` props
|
|
132
119
|
|
|
133
120
|
| Prop | Type | Default | Description |
|
|
134
121
|
|---|---|---|---|
|
|
@@ -137,33 +124,42 @@ Skelo.register({
|
|
|
137
124
|
| `duration` | `number` | `1200` | Animation duration (ms) |
|
|
138
125
|
| `baseColor` / `highlightColor` | `string` | theme | Skeleton colors |
|
|
139
126
|
| `borderRadius` | `number` | `4` | Default corner radius |
|
|
140
|
-
| `
|
|
127
|
+
| `count` | `number` | `6` | Placeholder rows for a `FlatList`/`SectionList` |
|
|
128
|
+
| `deep` | `boolean` | auto | Force component introspection on/off (auto when available) |
|
|
141
129
|
| `styles` | `StyleSheet \| style[]` | â | Generate from styles instead of children |
|
|
142
|
-
| `excludeStyles` | `string[]` | â | Style keys to skip in `styles` mode |
|
|
143
130
|
| `accessibilityLabel` | `string` | `'Loading content'` | Screen-reader label |
|
|
144
131
|
|
|
145
|
-
Also exported: `withSkeleton`, `SkeletonIgnore`, `StyleSkeleton`, the primitives (`SkeletonBox`, `SkeletonText`, `SkeletonImage`, `SkeletonCircle`), and `Skelo` (
|
|
132
|
+
Also exported: `withSkeleton`, `SkeletonIgnore`, `StyleSkeleton`, the primitives (`SkeletonBox`, `SkeletonText`, `SkeletonImage`, `SkeletonCircle`), and `Skelo` (`register` / `fromStyles`).
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## ð§Đ How it works
|
|
137
|
+
|
|
138
|
+
- **Host elements** (`View` / `Text` / `Image`) are read directly (`React.Children` + `StyleSheet.flatten`).
|
|
139
|
+
- **Custom components** are expanded by a tiny JS-only `react-reconciler` renderer (bundled) that renders them off-screen to recover their real host tree + styles â then feeds the same skeleton engine.
|
|
140
|
+
- Anything Skelo can't introspect (native content) is measured and shown as one size-matched block.
|
|
141
|
+
|
|
142
|
+
---
|
|
146
143
|
|
|
147
|
-
##
|
|
144
|
+
## â ïļ Good to know
|
|
148
145
|
|
|
149
|
-
|
|
150
|
-
- **`deep` mode** â a JS-only `react-reconciler` renderer renders the component off-screen to recover its real host tree + styles, then feeds the same skeleton renderer.
|
|
151
|
-
- Anything Skelo can't introspect (a custom component without `deep`, or native content) is measured and shown as a single size-matched block.
|
|
146
|
+
Skelo skeletonizes **what your component renders during loading**:
|
|
152
147
|
|
|
153
|
-
|
|
148
|
+
- Introspection **runs mount effects** â a screen that fetches on mount may fetch twice. Prefer it for presentational trees; register a plugin for effect-heavy screens.
|
|
149
|
+
- It renders **outside your providers** â components needing navigation/theme/redux context fall back to a measured block.
|
|
150
|
+
- **Native content** (`WebView`, `MapView`, `Video`, âĶ) lives outside React, so it becomes a single sized block.
|
|
151
|
+
- A **data-driven list/grid** that renders nothing while loading has nothing to skeletonize â keep placeholder items rendered (see the [example](./example)).
|
|
154
152
|
|
|
155
|
-
|
|
153
|
+
---
|
|
156
154
|
|
|
157
|
-
|
|
158
|
-
- **Renders outside your providers** â components that require navigation/theme/redux context may fail to expand; Skelo catches this and falls back to a measured block.
|
|
159
|
-
- Only the **React host tree** is visible. `WebView`, `MapView`, `Video`, etc. render their content natively (outside React), so they can only become a single sized block.
|
|
155
|
+
## ð Requirements
|
|
160
156
|
|
|
161
|
-
|
|
157
|
+
- React Native (New Architecture recommended)
|
|
158
|
+
- React 19 (React 18 â override `react-reconciler` to `0.29`)
|
|
159
|
+
- No other dependencies
|
|
162
160
|
|
|
163
|
-
|
|
164
|
-
- `react-native-reanimated` âĨ 3
|
|
165
|
-
- `react-reconciler` (optional, for `deep`) â must match your React version
|
|
161
|
+
---
|
|
166
162
|
|
|
167
|
-
## License
|
|
163
|
+
## ð License
|
|
168
164
|
|
|
169
165
|
MIT ÂĐ [Shubhanshu Barnwal](https://shubhanshubb.dev)
|
|
@@ -6,25 +6,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.PulseAnimation = PulseAnimation;
|
|
7
7
|
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
|
-
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
|
|
10
9
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
11
|
-
|
|
12
|
-
* Pulse animation component using Reanimated 3
|
|
13
|
-
*
|
|
14
|
-
* Creates a subtle breathing/pulsing effect using:
|
|
15
|
-
* - Shared values for performance
|
|
16
|
-
* - Worklets for 60 FPS on UI thread
|
|
17
|
-
* - Opacity animation for pulse effect
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* ```tsx
|
|
21
|
-
* <PulseAnimation
|
|
22
|
-
* width={200}
|
|
23
|
-
* height={20}
|
|
24
|
-
* borderRadius={4}
|
|
25
|
-
* />
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
10
|
+
// Breathing opacity loop using React Native's built-in Animated (native driver).
|
|
28
11
|
function PulseAnimation({
|
|
29
12
|
width,
|
|
30
13
|
height,
|
|
@@ -34,38 +17,30 @@ function PulseAnimation({
|
|
|
34
17
|
style,
|
|
35
18
|
children
|
|
36
19
|
}) {
|
|
37
|
-
|
|
38
|
-
const opacity = (0, _reactNativeReanimated.useSharedValue)(1);
|
|
20
|
+
const opacity = (0, _react.useRef)(new _reactNative.Animated.Value(1)).current;
|
|
39
21
|
(0, _react.useEffect)(() => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
opacity.value = (0, _reactNativeReanimated.withRepeat)((0, _reactNativeReanimated.withSequence)((0, _reactNativeReanimated.withTiming)(0.5, {
|
|
22
|
+
const loop = _reactNative.Animated.loop(_reactNative.Animated.sequence([_reactNative.Animated.timing(opacity, {
|
|
23
|
+
toValue: 0.5,
|
|
43
24
|
duration: duration / 2,
|
|
44
|
-
easing:
|
|
45
|
-
|
|
25
|
+
easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
|
|
26
|
+
useNativeDriver: true
|
|
27
|
+
}), _reactNative.Animated.timing(opacity, {
|
|
28
|
+
toValue: 1,
|
|
46
29
|
duration: duration / 2,
|
|
47
|
-
easing:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
);
|
|
30
|
+
easing: _reactNative.Easing.inOut(_reactNative.Easing.ease),
|
|
31
|
+
useNativeDriver: true
|
|
32
|
+
})]));
|
|
33
|
+
loop.start();
|
|
34
|
+
return () => loop.stop();
|
|
52
35
|
}, [duration, opacity]);
|
|
53
|
-
|
|
54
|
-
// Animated style for pulse effect
|
|
55
|
-
const animatedStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
|
|
56
|
-
'worklet';
|
|
57
|
-
|
|
58
|
-
return {
|
|
59
|
-
opacity: opacity.value
|
|
60
|
-
};
|
|
61
|
-
}, []);
|
|
62
|
-
return /*#__PURE__*/_react.default.createElement(_reactNativeReanimated.default.View, {
|
|
36
|
+
return /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, {
|
|
63
37
|
style: [styles.container, {
|
|
64
38
|
width,
|
|
65
39
|
height,
|
|
66
40
|
borderRadius,
|
|
67
|
-
backgroundColor: baseColor
|
|
68
|
-
|
|
41
|
+
backgroundColor: baseColor,
|
|
42
|
+
opacity
|
|
43
|
+
}, style]
|
|
69
44
|
}, children);
|
|
70
45
|
}
|
|
71
46
|
const styles = _reactNative.StyleSheet.create({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","PulseAnimation","width","height","baseColor","duration","borderRadius","style","children","opacity","useRef","Animated","Value","current","useEffect","loop","sequence","timing","toValue","easing","Easing","inOut","ease","useNativeDriver","start","stop","createElement","View","styles","container","backgroundColor","StyleSheet","create","overflow"],"sourceRoot":"../../../src","sources":["animations/PulseAnimation.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -6,26 +6,10 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.ShimmerAnimation = ShimmerAnimation;
|
|
7
7
|
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
|
-
var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
|
|
10
|
-
var _reactNativeLinearGradient = require("react-native-linear-gradient");
|
|
11
9
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
* Creates a smooth left-to-right shimmer effect using:
|
|
16
|
-
* - Shared values for performance
|
|
17
|
-
* - Worklets for 60 FPS on UI thread
|
|
18
|
-
* - Linear gradient for shimmer effect
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```tsx
|
|
22
|
-
* <ShimmerAnimation
|
|
23
|
-
* width={200}
|
|
24
|
-
* height={20}
|
|
25
|
-
* borderRadius={4}
|
|
26
|
-
* />
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
10
|
+
// Zero-dependency shimmer: a highlight band (dim/bright/dim segments to fake a
|
|
11
|
+
// soft gradient) sweeps across the base, driven by React Native's built-in
|
|
12
|
+
// Animated native driver. Plain Views only â guaranteed to render and animate.
|
|
29
13
|
function ShimmerAnimation({
|
|
30
14
|
width,
|
|
31
15
|
height,
|
|
@@ -36,68 +20,73 @@ function ShimmerAnimation({
|
|
|
36
20
|
style,
|
|
37
21
|
children
|
|
38
22
|
}) {
|
|
39
|
-
|
|
40
|
-
const
|
|
23
|
+
const progress = (0, _react.useRef)(new _reactNative.Animated.Value(0)).current;
|
|
24
|
+
const [measuredWidth, setMeasuredWidth] = (0, _react.useState)(0);
|
|
41
25
|
(0, _react.useEffect)(() => {
|
|
42
|
-
|
|
43
|
-
|
|
26
|
+
const loop = _reactNative.Animated.loop(_reactNative.Animated.timing(progress, {
|
|
27
|
+
toValue: 1,
|
|
44
28
|
duration,
|
|
45
|
-
easing:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
);
|
|
29
|
+
easing: _reactNative.Easing.linear,
|
|
30
|
+
useNativeDriver: true
|
|
31
|
+
}));
|
|
32
|
+
loop.start();
|
|
33
|
+
return () => loop.stop();
|
|
50
34
|
}, [duration, progress]);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
translateX: translateX * (typeof width === 'number' ? width : 200)
|
|
62
|
-
}]
|
|
63
|
-
};
|
|
64
|
-
}, [width]);
|
|
35
|
+
const onLayout = event => {
|
|
36
|
+
const w = event.nativeEvent.layout.width;
|
|
37
|
+
setMeasuredWidth(prev => prev === w ? prev : w);
|
|
38
|
+
};
|
|
39
|
+
const containerWidth = typeof width === 'number' ? width : measuredWidth || 200;
|
|
40
|
+
const bandWidth = Math.max(containerWidth * 0.6, 80);
|
|
41
|
+
const translateX = progress.interpolate({
|
|
42
|
+
inputRange: [0, 1],
|
|
43
|
+
outputRange: [-bandWidth, containerWidth]
|
|
44
|
+
});
|
|
65
45
|
return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
|
|
46
|
+
onLayout: onLayout,
|
|
66
47
|
style: [styles.container, {
|
|
67
48
|
width,
|
|
68
49
|
height,
|
|
69
50
|
borderRadius,
|
|
70
51
|
backgroundColor: baseColor
|
|
71
52
|
}, style]
|
|
72
|
-
}, /*#__PURE__*/_react.default.createElement(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
53
|
+
}, /*#__PURE__*/_react.default.createElement(_reactNative.Animated.View, {
|
|
54
|
+
pointerEvents: "none",
|
|
55
|
+
style: [styles.band, {
|
|
56
|
+
width: bandWidth,
|
|
57
|
+
transform: [{
|
|
58
|
+
translateX
|
|
59
|
+
}]
|
|
60
|
+
}]
|
|
61
|
+
}, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
|
|
62
|
+
style: [styles.segment, {
|
|
63
|
+
backgroundColor: highlightColor,
|
|
64
|
+
opacity: 0.25
|
|
65
|
+
}]
|
|
66
|
+
}), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
|
|
67
|
+
style: [styles.segment, {
|
|
68
|
+
backgroundColor: highlightColor,
|
|
69
|
+
opacity: 0.9
|
|
76
70
|
}]
|
|
77
|
-
}, /*#__PURE__*/_react.default.createElement(
|
|
78
|
-
style: [styles.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
colors: [baseColor, highlightColor, highlightColor, baseColor],
|
|
84
|
-
start: {
|
|
85
|
-
x: 0,
|
|
86
|
-
y: 0
|
|
87
|
-
},
|
|
88
|
-
end: {
|
|
89
|
-
x: 1,
|
|
90
|
-
y: 0
|
|
91
|
-
},
|
|
92
|
-
style: _reactNative.StyleSheet.absoluteFill
|
|
93
|
-
}))), children);
|
|
71
|
+
}), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
|
|
72
|
+
style: [styles.segment, {
|
|
73
|
+
backgroundColor: highlightColor,
|
|
74
|
+
opacity: 0.25
|
|
75
|
+
}]
|
|
76
|
+
})), children);
|
|
94
77
|
}
|
|
95
78
|
const styles = _reactNative.StyleSheet.create({
|
|
96
79
|
container: {
|
|
97
80
|
overflow: 'hidden'
|
|
98
81
|
},
|
|
99
|
-
|
|
100
|
-
position: 'absolute'
|
|
82
|
+
band: {
|
|
83
|
+
position: 'absolute',
|
|
84
|
+
top: 0,
|
|
85
|
+
bottom: 0,
|
|
86
|
+
flexDirection: 'row'
|
|
87
|
+
},
|
|
88
|
+
segment: {
|
|
89
|
+
flex: 1
|
|
101
90
|
}
|
|
102
91
|
});
|
|
103
92
|
//# sourceMappingURL=ShimmerAnimation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","ShimmerAnimation","width","height","baseColor","highlightColor","duration","borderRadius","style","children","progress","useRef","Animated","Value","current","measuredWidth","setMeasuredWidth","useState","useEffect","loop","timing","toValue","easing","Easing","linear","useNativeDriver","start","stop","onLayout","event","w","nativeEvent","layout","prev","containerWidth","bandWidth","Math","max","translateX","interpolate","inputRange","outputRange","createElement","View","styles","container","backgroundColor","pointerEvents","band","transform","segment","opacity","StyleSheet","create","overflow","position","top","bottom","flexDirection","flex"],"sourceRoot":"../../../src","sources":["animations/ShimmerAnimation.tsx"],"mappings":";;;;;;AAAA,IAAAA,MAAA,GAAAC,uBAAA,CAAAC,OAAA;AAEA,IAAAC,YAAA,GAAAD,OAAA;AAAkE,SAAAD,wBAAAG,CAAA,EAAAC,CAAA,6BAAAC,OAAA,MAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAL,uBAAA,YAAAA,CAAAG,CAAA,EAAAC,CAAA,SAAAA,CAAA,IAAAD,CAAA,IAAAA,CAAA,CAAAK,UAAA,SAAAL,CAAA,MAAAM,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAC,OAAA,EAAAV,CAAA,iBAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,SAAAQ,CAAA,MAAAF,CAAA,GAAAL,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAG,CAAA,CAAAK,GAAA,CAAAX,CAAA,UAAAM,CAAA,CAAAM,GAAA,CAAAZ,CAAA,GAAAM,CAAA,CAAAO,GAAA,CAAAb,CAAA,EAAAQ,CAAA,gBAAAP,CAAA,IAAAD,CAAA,gBAAAC,CAAA,OAAAa,cAAA,CAAAC,IAAA,CAAAf,CAAA,EAAAC,CAAA,OAAAM,CAAA,IAAAD,CAAA,GAAAU,MAAA,CAAAC,cAAA,KAAAD,MAAA,CAAAE,wBAAA,CAAAlB,CAAA,EAAAC,CAAA,OAAAM,CAAA,CAAAK,GAAA,IAAAL,CAAA,CAAAM,GAAA,IAAAP,CAAA,CAAAE,CAAA,EAAAP,CAAA,EAAAM,CAAA,IAAAC,CAAA,CAAAP,CAAA,IAAAD,CAAA,CAAAC,CAAA,WAAAO,CAAA,KAAAR,CAAA,EAAAC,CAAA;AAalE;AACA;AACA;AACO,SAASkB,gBAAgBA,CAAC;EAC/BC,KAAK;EACLC,MAAM;EACNC,SAAS,GAAG,SAAS;EACrBC,cAAc,GAAG,SAAS;EAC1BC,QAAQ,GAAG,IAAI;EACfC,YAAY,GAAG,CAAC;EAChBC,KAAK;EACLC;AACY,CAAC,EAAE;EACf,MAAMC,QAAQ,GAAG,IAAAC,aAAM,EAAC,IAAIC,qBAAQ,CAACC,KAAK,CAAC,CAAC,CAAC,CAAC,CAACC,OAAO;EACtD,MAAM,CAACC,aAAa,EAAEC,gBAAgB,CAAC,GAAG,IAAAC,eAAQ,EAAC,CAAC,CAAC;EAErD,IAAAC,gBAAS,EAAC,MAAM;IACd,MAAMC,IAAI,GAAGP,qBAAQ,CAACO,IAAI,CACxBP,qBAAQ,CAACQ,MAAM,CAACV,QAAQ,EAAE;MACxBW,OAAO,EAAE,CAAC;MACVf,QAAQ;MACRgB,MAAM,EAAEC,mBAAM,CAACC,MAAM;MACrBC,eAAe,EAAE;IACnB,CAAC,CACH,CAAC;IACDN,IAAI,CAACO,KAAK,CAAC,CAAC;IACZ,OAAO,MAAMP,IAAI,CAACQ,IAAI,CAAC,CAAC;EAC1B,CAAC,EAAE,CAACrB,QAAQ,EAAEI,QAAQ,CAAC,CAAC;EAExB,MAAMkB,QAAQ,GAAIC,KAAwB,IAAK;IAC7C,MAAMC,CAAC,GAAGD,KAAK,CAACE,WAAW,CAACC,MAAM,CAAC9B,KAAK;IACxCc,gBAAgB,CAACiB,IAAI,IAAKA,IAAI,KAAKH,CAAC,GAAGG,IAAI,GAAGH,CAAE,CAAC;EACnD,CAAC;EAED,MAAMI,cAAc,GAAG,OAAOhC,KAAK,KAAK,QAAQ,GAAGA,KAAK,GAAGa,aAAa,IAAI,GAAG;EAC/E,MAAMoB,SAAS,GAAGC,IAAI,CAACC,GAAG,CAACH,cAAc,GAAG,GAAG,EAAE,EAAE,CAAC;EAEpD,MAAMI,UAAU,GAAG5B,QAAQ,CAAC6B,WAAW,CAAC;IACtCC,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAClBC,WAAW,EAAE,CAAC,CAACN,SAAS,EAAED,cAAc;EAC1C,CAAC,CAAC;EAEF,oBACExD,MAAA,CAAAc,OAAA,CAAAkD,aAAA,CAAC7D,YAAA,CAAA8D,IAAI;IACHf,QAAQ,EAAEA,QAAS;IACnBpB,KAAK,EAAE,CAACoC,MAAM,CAACC,SAAS,EAAE;MAAE3C,KAAK;MAAEC,MAAM;MAAEI,YAAY;MAAEuC,eAAe,EAAE1C;IAAU,CAAC,EAAEI,KAAK;EAAE,gBAE9F9B,MAAA,CAAAc,OAAA,CAAAkD,aAAA,CAAC7D,YAAA,CAAA+B,QAAQ,CAAC+B,IAAI;IACZI,aAAa,EAAC,MAAM;IACpBvC,KAAK,EAAE,CAACoC,MAAM,CAACI,IAAI,EAAE;MAAE9C,KAAK,EAAEiC,SAAS;MAAEc,SAAS,EAAE,CAAC;QAAEX;MAAW,CAAC;IAAE,CAAC;EAAE,gBAExE5D,MAAA,CAAAc,OAAA,CAAAkD,aAAA,CAAC7D,YAAA,CAAA8D,IAAI;IAACnC,KAAK,EAAE,CAACoC,MAAM,CAACM,OAAO,EAAE;MAAEJ,eAAe,EAAEzC,cAAc;MAAE8C,OAAO,EAAE;IAAK,CAAC;EAAE,CAAE,CAAC,eACrFzE,MAAA,CAAAc,OAAA,CAAAkD,aAAA,CAAC7D,YAAA,CAAA8D,IAAI;IAACnC,KAAK,EAAE,CAACoC,MAAM,CAACM,OAAO,EAAE;MAAEJ,eAAe,EAAEzC,cAAc;MAAE8C,OAAO,EAAE;IAAI,CAAC;EAAE,CAAE,CAAC,eACpFzE,MAAA,CAAAc,OAAA,CAAAkD,aAAA,CAAC7D,YAAA,CAAA8D,IAAI;IAACnC,KAAK,EAAE,CAACoC,MAAM,CAACM,OAAO,EAAE;MAAEJ,eAAe,EAAEzC,cAAc;MAAE8C,OAAO,EAAE;IAAK,CAAC;EAAE,CAAE,CACvE,CAAC,EAEf1C,QACG,CAAC;AAEX;AAEA,MAAMmC,MAAM,GAAGQ,uBAAU,CAACC,MAAM,CAAC;EAC/BR,SAAS,EAAE;IACTS,QAAQ,EAAE;EACZ,CAAC;EACDN,IAAI,EAAE;IACJO,QAAQ,EAAE,UAAU;IACpBC,GAAG,EAAE,CAAC;IACNC,MAAM,EAAE,CAAC;IACTC,aAAa,EAAE;EACjB,CAAC;EACDR,OAAO,EAAE;IACPS,IAAI,EAAE;EACR;AACF,CAAC,CAAC","ignoreList":[]}
|
|
@@ -10,6 +10,7 @@ var _SkeletonRenderer = require("./SkeletonRenderer");
|
|
|
10
10
|
var _SkeletonIgnore = require("./SkeletonIgnore");
|
|
11
11
|
var _StyleSkeleton = require("../core/generator/StyleSkeleton");
|
|
12
12
|
var _deepRegistry = require("../core/deepRegistry");
|
|
13
|
+
var _expandLists = require("../core/parser/expandLists");
|
|
13
14
|
var _defaults = require("../constants/defaults");
|
|
14
15
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
15
16
|
/**
|
|
@@ -45,7 +46,8 @@ function Skeleton({
|
|
|
45
46
|
children,
|
|
46
47
|
styles,
|
|
47
48
|
excludeStyles,
|
|
48
|
-
deep
|
|
49
|
+
deep,
|
|
50
|
+
count = 6,
|
|
49
51
|
animation = _defaults.DEFAULT_CONFIG.animation,
|
|
50
52
|
duration = _defaults.DEFAULT_CONFIG.duration,
|
|
51
53
|
baseColor = _defaults.DEFAULT_CONFIG.baseColor,
|
|
@@ -66,24 +68,28 @@ function Skeleton({
|
|
|
66
68
|
console.log('[Skelo] Parsing component tree...');
|
|
67
69
|
}
|
|
68
70
|
try {
|
|
71
|
+
// Turn FlatList/SectionList into placeholder rows so lists show a
|
|
72
|
+
// skeleton even though their data (and rows) don't exist yet.
|
|
73
|
+
const prepared = (0, _expandLists.expandListPlaceholders)(children, count);
|
|
74
|
+
|
|
69
75
|
// Deep mode: expand opaque components into their real host tree via the
|
|
70
76
|
// opt-in expander (registered by importing 'react-native-skelo/deep').
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
} else if (__DEV__) {
|
|
83
|
-
console.warn("[Skelo] `deep` requires react-reconciler. Install it and add `import 'react-native-skelo/deep';` in your app entry.");
|
|
77
|
+
// When `deep` is not set explicitly, it's used automatically whenever the
|
|
78
|
+
// expander is available. Falls back to static parsing otherwise.
|
|
79
|
+
const expand = (0, _deepRegistry.getDeepExpander)();
|
|
80
|
+
const wantDeep = deep === undefined ? expand !== null : deep;
|
|
81
|
+
if (wantDeep && expand) {
|
|
82
|
+
const expanded = expand(/*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, prepared));
|
|
83
|
+
if (expanded && expanded.length > 0) {
|
|
84
|
+
return expanded;
|
|
85
|
+
}
|
|
86
|
+
if (__DEV__) {
|
|
87
|
+
console.warn('[Skelo] deep expansion returned nothing; falling back to static parsing.');
|
|
84
88
|
}
|
|
89
|
+
} else if (deep === true && !expand && __DEV__) {
|
|
90
|
+
console.warn("[Skelo] `deep` requires react-reconciler. Install it and add `import 'react-native-skelo/deep';` in your app entry.");
|
|
85
91
|
}
|
|
86
|
-
const parsed = _parser.Parser.parse(
|
|
92
|
+
const parsed = _parser.Parser.parse(prepared);
|
|
87
93
|
if (__DEV__ && debug) {
|
|
88
94
|
// Log a lightweight summary: the raw nodes hold React elements
|
|
89
95
|
// (`element`) and arbitrary props that aren't safely serializable.
|
|
@@ -101,7 +107,7 @@ function Skeleton({
|
|
|
101
107
|
}
|
|
102
108
|
return [];
|
|
103
109
|
}
|
|
104
|
-
}, [loading, styles, deep, children, debug]);
|
|
110
|
+
}, [loading, styles, deep, count, children, debug]);
|
|
105
111
|
|
|
106
112
|
// Create skeleton config
|
|
107
113
|
const config = (0, _react.useMemo)(() => ({
|