shimmer-trace 1.1.2 → 1.1.4
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/AGENTS.md +24 -18
- package/README.md +130 -52
- package/package.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -26,13 +26,16 @@ npm install shimmer-trace
|
|
|
26
26
|
|
|
27
27
|
```ts
|
|
28
28
|
// Components
|
|
29
|
-
import { Shimmer }
|
|
30
|
-
import { ShimmerSuspense }
|
|
31
|
-
import { createShimmer }
|
|
29
|
+
import { Shimmer } from 'shimmer-trace'; // main component
|
|
30
|
+
import { ShimmerSuspense } from 'shimmer-trace'; // Suspense boundary with auto-skeleton
|
|
31
|
+
import { createShimmer } from 'shimmer-trace'; // factory — bakes config into component
|
|
32
32
|
|
|
33
33
|
// Hooks
|
|
34
|
-
import { useIsShimmering }
|
|
35
|
-
import { useShimmerContext } from 'shimmer-trace'; // raw context — advanced use only
|
|
34
|
+
import { useIsShimmering } from 'shimmer-trace'; // true when inside ShimmerSuspense fallback
|
|
35
|
+
import { useShimmerContext } from 'shimmer-trace'; // raw context value — advanced use only
|
|
36
|
+
|
|
37
|
+
// Raw context (rarely needed — only if you build a custom Master/Reporter outside <Shimmer>)
|
|
38
|
+
import { ShimmerContext } from 'shimmer-trace';
|
|
36
39
|
|
|
37
40
|
// Types
|
|
38
41
|
import type {
|
|
@@ -285,7 +288,7 @@ Use when: multiple sub-sections exist but you want one synchronized shimmer acro
|
|
|
285
288
|
</Shimmer>
|
|
286
289
|
```
|
|
287
290
|
|
|
288
|
-
**Key rule:**
|
|
291
|
+
**Key rule:** Any nested `<Shimmer>` inside an outer `<Shimmer>` auto-becomes a Reporter, regardless of whether it has its own `loading` prop. The Reporter measures its own subtree, sends rects to the Master, and ignores its own `loading` prop — Master's `loading` controls everything. One overlay covers all. To break out and create an independent Master, use `stopPropagation={true}` (see Pattern 8).
|
|
289
292
|
|
|
290
293
|
---
|
|
291
294
|
|
|
@@ -323,26 +326,29 @@ Use when: component uses `use()`, `useSuspenseQuery`, or similar — throws a Pr
|
|
|
323
326
|
|
|
324
327
|
### Option A — `template` prop (component has zero shimmer awareness)
|
|
325
328
|
|
|
329
|
+
Reuse the same component as its own template — pass template props so it renders DOM without suspending. No duplicate skeleton component, no ` ` width padding.
|
|
330
|
+
|
|
326
331
|
```tsx
|
|
327
332
|
import { ShimmerSuspense } from 'shimmer-trace';
|
|
328
333
|
|
|
329
|
-
//
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
<p> </p>
|
|
337
|
-
</div>
|
|
338
|
-
</div>
|
|
339
|
-
);
|
|
334
|
+
// Template data: same shape the real component expects, no fetch
|
|
335
|
+
const userTemplate = {
|
|
336
|
+
name: 'xxxxxxxxxxxxxx',
|
|
337
|
+
role: 'xxxxxxxxxx',
|
|
338
|
+
bio: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
|
339
|
+
avatar: '',
|
|
340
|
+
};
|
|
340
341
|
|
|
341
|
-
<ShimmerSuspense template={<
|
|
342
|
+
<ShimmerSuspense template={<UserCard user={userTemplate} />} animation="wave">
|
|
342
343
|
<UserCard /> {/* throws Promise while fetching — shimmer shows automatically */}
|
|
343
344
|
</ShimmerSuspense>
|
|
344
345
|
```
|
|
345
346
|
|
|
347
|
+
**Why a `template` prop at all (not just `dummyData` like `<Shimmer>`):** the real `<UserCard />` throws a Promise during render — it never produces DOM until data resolves. `cloneElement` + props merge doesn't help because the cloned element still suspends. `template` renders a separate, non-suspending instance (same component, template data) so Shimmer gets real DOM to trace.
|
|
348
|
+
|
|
349
|
+
**Rule:** template should render synchronously. If you pass `<UserCard resource={realResource} />` as template, it'll suspend inside the fallback and skeleton goes empty. Either pass template data props (above) or use Option B.
|
|
350
|
+
|
|
351
|
+
|
|
346
352
|
### Option B — `useIsShimmering` hook (component skips fetch in shimmer mode)
|
|
347
353
|
|
|
348
354
|
```tsx
|
package/README.md
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
[](https://www.typescriptlang.org)
|
|
11
11
|
[](./LICENSE)
|
|
12
12
|
|
|
13
|
+
**[🚀 Live Demo](https://jeetvora331.github.io/shimmer-trace/)**
|
|
14
|
+
|
|
13
15
|
<p align="center">
|
|
14
16
|
<img src="https://raw.githubusercontent.com/jeetvora331/shimmer-trace/main/assets/demo.gif" alt="shimmer-trace demo" width="720" />
|
|
15
17
|
</p>
|
|
@@ -43,7 +45,9 @@ It renders your real component invisibly, traces every element's exact position
|
|
|
43
45
|
- **Zero CLS** — Container layout preserved. Default `preserveBackground` keeps card backgrounds, borders, and padding visible underneath the shimmer.
|
|
44
46
|
- **Synchronized animation** — One overlay, one wave. All skeletons animate in perfect sync.
|
|
45
47
|
- **5 animation styles** — `wave`, `pulse`, `shine`, `glow`, `gradient`.
|
|
46
|
-
- **
|
|
48
|
+
- **Dummy data injection** — `dummyData` clones children with template props so skeletons render with realistic shape, no `data || fallback` ternaries in JSX.
|
|
49
|
+
- **List mode** — `dummyLength` clones the first child N times for skeleton lists, even when your array is empty.
|
|
50
|
+
- **Component templates** — `as={MovieCard}` generates skeletons from a component + `dummyData`, no children required.
|
|
47
51
|
- **Suspense-native** — `ShimmerSuspense` wraps any suspended component with no `loading` prop.
|
|
48
52
|
- **Factory pattern** — `createShimmer` pre-bakes your config. Use it like a component everywhere.
|
|
49
53
|
- **Composable** — Nested `Shimmer` components bubble their rects up to a single master overlay.
|
|
@@ -85,6 +89,19 @@ function ProfilePage() {
|
|
|
85
89
|
|
|
86
90
|
That's it. `shimmer-trace` walks the DOM inside `<UserCard />`, finds every text node, image, input, and button, and draws a shimmer skeleton that matches it exactly.
|
|
87
91
|
|
|
92
|
+
Need realistic shape before real data arrives? Pass a template via `dummyData`:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<Shimmer
|
|
96
|
+
loading={loading}
|
|
97
|
+
dummyData={{ user: { name: "dummy_user", role: "dummy_role", avatar: "" } }}
|
|
98
|
+
>
|
|
99
|
+
<UserCard user={user} />
|
|
100
|
+
</Shimmer>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
See [Examples](#examples) for `dummyLength` (list mode) and `as` (component template) patterns.
|
|
104
|
+
|
|
88
105
|
---
|
|
89
106
|
|
|
90
107
|
## API Reference
|
|
@@ -101,7 +118,10 @@ The core component. Wrap anything with it.
|
|
|
101
118
|
highlightColor="#f5f5f5" // shimmer highlight color
|
|
102
119
|
speed={1.5} // animation duration in seconds
|
|
103
120
|
borderRadius="4px" // override auto-detected border-radius
|
|
121
|
+
preserveBackground={true} // keep card bg/borders visible under shimmer
|
|
122
|
+
dummyData={{ user: tpl }} // inject template props into children
|
|
104
123
|
dummyLength={10} // list mode: number of skeleton items
|
|
124
|
+
as={UserCard} // component template — generate skeletons from a component
|
|
105
125
|
stopPropagation={false} // force this Shimmer to be a master
|
|
106
126
|
className="my-class" // applied to the container div
|
|
107
127
|
style={{ display: "flex" }} // merged into container styles
|
|
@@ -110,19 +130,21 @@ The core component. Wrap anything with it.
|
|
|
110
130
|
</Shimmer>
|
|
111
131
|
```
|
|
112
132
|
|
|
113
|
-
| Prop | Type | Default | Description
|
|
114
|
-
| -------------------- | ------------------------------------------------------ | ----------- |
|
|
115
|
-
| `loading` | `boolean` | `false` | Enables the shimmer skeleton
|
|
116
|
-
| `animation` | `'wave' \| 'pulse' \| 'shine' \| 'glow' \| 'gradient'` | `'wave'` | Animation style
|
|
117
|
-
| `preserveBackground` | `boolean` | `true` | Keep card backgrounds/borders visible while loading
|
|
118
|
-
| `baseColor` | `string` | `'#e0e0e0'` | Base skeleton color
|
|
119
|
-
| `highlightColor` | `string` | `'#f5f5f5'` | Shimmer highlight color
|
|
120
|
-
| `speed` | `number` | `1.5` | Animation speed in seconds
|
|
121
|
-
| `borderRadius` | `string` | auto | Override border-radius on all blocks
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
133
|
+
| Prop | Type | Default | Description |
|
|
134
|
+
| -------------------- | ------------------------------------------------------ | ----------- | ------------------------------------------------------------------------ |
|
|
135
|
+
| `loading` | `boolean` | `false` | Enables the shimmer skeleton |
|
|
136
|
+
| `animation` | `'wave' \| 'pulse' \| 'shine' \| 'glow' \| 'gradient'` | `'wave'` | Animation style |
|
|
137
|
+
| `preserveBackground` | `boolean` | `true` | Keep card backgrounds/borders visible while loading |
|
|
138
|
+
| `baseColor` | `string` | `'#e0e0e0'` | Base skeleton color |
|
|
139
|
+
| `highlightColor` | `string` | `'#f5f5f5'` | Shimmer highlight color |
|
|
140
|
+
| `speed` | `number` | `1.5` | Animation speed in seconds |
|
|
141
|
+
| `borderRadius` | `string` | auto | Override border-radius on all blocks |
|
|
142
|
+
| `dummyData` | `Record<string, any>` | — | Props merged into each child while loading (template data, no real API) |
|
|
143
|
+
| `dummyLength` | `number` | — | List mode — clones first child N times (see below) |
|
|
144
|
+
| `as` | `ComponentType<any>` | — | Component template — renders `dummyLength` × `<as {...dummyData} />` |
|
|
145
|
+
| `stopPropagation` | `boolean` | `false` | Force master renderer, ignore parent context |
|
|
146
|
+
| `className` | `string` | — | Class on the container `<div>` |
|
|
147
|
+
| `style` | `CSSProperties` | — | Inline styles on the container `<div>` |
|
|
126
148
|
|
|
127
149
|
---
|
|
128
150
|
|
|
@@ -171,32 +193,74 @@ Works out of the box with inputs, labels, and buttons.
|
|
|
171
193
|
</Shimmer>
|
|
172
194
|
```
|
|
173
195
|
|
|
174
|
-
### 3.
|
|
196
|
+
### 3. Skeleton Shape with `dummyData`
|
|
175
197
|
|
|
176
|
-
|
|
198
|
+
No more `data?.name ?? 'Loading...'` ternaries scattered through your component. Pass a template object via `dummyData` and Shimmer clones each child with those props merged on top of its own.
|
|
177
199
|
|
|
178
200
|
```tsx
|
|
179
|
-
|
|
201
|
+
const userTemplate = {
|
|
202
|
+
name: "",
|
|
203
|
+
role: "",
|
|
204
|
+
avatar: "",
|
|
205
|
+
bio: "",
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
<Shimmer loading={loading} dummyData={{ user: userTemplate }}>
|
|
209
|
+
<UserCard user={user} />
|
|
210
|
+
</Shimmer>;
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
While `loading=true`, `<UserCard>` is cloned with `user={userTemplate}` — giving the shimmer realistic shape even before any data arrives. Once `loading=false`, real props pass through untouched.
|
|
214
|
+
|
|
215
|
+
### 4. List Skeleton with `dummyLength` + `dummyData`
|
|
216
|
+
|
|
217
|
+
Loading a list from an API? `dummyLength` clones a template N times so the skeleton shows the right number of rows — even when your array is empty during the first fetch.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
const postTemplate = {
|
|
221
|
+
title: "xxxxxxxxxxxxxxxxxxxx",
|
|
222
|
+
author: "xxxxxxxx",
|
|
223
|
+
category: "xxxxx",
|
|
224
|
+
thumbnail: "",
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
<Shimmer
|
|
228
|
+
loading={loading}
|
|
229
|
+
dummyLength={10}
|
|
230
|
+
dummyData={{ post: postTemplate }}
|
|
231
|
+
>
|
|
180
232
|
{posts.map((post) => (
|
|
181
|
-
<
|
|
182
|
-
<img src={post.thumbnail} alt="" />
|
|
183
|
-
<div>
|
|
184
|
-
<h4>{post.title}</h4>
|
|
185
|
-
<span>{post.author}</span>
|
|
186
|
-
</div>
|
|
187
|
-
<span className="badge">{post.category}</span>
|
|
188
|
-
</div>
|
|
233
|
+
<PostRow post={post} key={post.id} />
|
|
189
234
|
))}
|
|
190
|
-
</Shimmer
|
|
235
|
+
</Shimmer>;
|
|
191
236
|
```
|
|
192
237
|
|
|
193
238
|
**How it works:**
|
|
194
239
|
|
|
195
|
-
- `loading=false` → renders your `.map()` output normally
|
|
196
|
-
- `loading=true` → grabs the first
|
|
197
|
-
-
|
|
240
|
+
- `loading=false` → renders your `.map()` output normally.
|
|
241
|
+
- `loading=true` with children → grabs the first child, merges `dummyData` into its props, clones it `dummyLength` times.
|
|
242
|
+
- `loading=true` with empty array → use `as` (next example) so there's a component to clone even with no children.
|
|
243
|
+
|
|
244
|
+
### 5. Component Template with `as`
|
|
198
245
|
|
|
199
|
-
|
|
246
|
+
When your array is empty on first render (e.g. `posts = []` before fetch), there's no child to clone. Use `as` to point Shimmer at the component directly — it renders `dummyLength` instances of `<as {...dummyData} />`.
|
|
247
|
+
|
|
248
|
+
```tsx
|
|
249
|
+
<Shimmer
|
|
250
|
+
loading={loading}
|
|
251
|
+
as={PostRow}
|
|
252
|
+
dummyData={{ post: postTemplate }}
|
|
253
|
+
dummyLength={10}
|
|
254
|
+
>
|
|
255
|
+
{posts.map((post) => (
|
|
256
|
+
<PostRow post={post} key={post.id} />
|
|
257
|
+
))}
|
|
258
|
+
</Shimmer>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Cold-start safe — no children needed during loading. Children render normally once `loading=false`.
|
|
262
|
+
|
|
263
|
+
### 6. Synchronized Flex Layout
|
|
200
264
|
|
|
201
265
|
One `<Shimmer>` wraps multiple cards. One overlay. One perfectly synchronized wave.
|
|
202
266
|
|
|
@@ -210,7 +274,7 @@ One `<Shimmer>` wraps multiple cards. One overlay. One perfectly synchronized wa
|
|
|
210
274
|
|
|
211
275
|
No separate shimmers per card. One master overlay covers them all — the wave sweeps the entire row in sync.
|
|
212
276
|
|
|
213
|
-
###
|
|
277
|
+
### 7. Custom Colors (Dark Mode)
|
|
214
278
|
|
|
215
279
|
```tsx
|
|
216
280
|
<Shimmer loading={loading} baseColor="#1e1e3a" highlightColor="#2d2d52">
|
|
@@ -259,7 +323,7 @@ import { ShimmerSuspense } from "shimmer-trace";
|
|
|
259
323
|
|
|
260
324
|
### Option A: Explicit template (recommended)
|
|
261
325
|
|
|
262
|
-
|
|
326
|
+
Reuse the same component as its own skeleton — pass it through `template` with template props. No duplicate skeleton component, no ` ` width hacks.
|
|
263
327
|
|
|
264
328
|
```tsx
|
|
265
329
|
function UserCard({ user }) {
|
|
@@ -272,20 +336,20 @@ function UserCard({ user }) {
|
|
|
272
336
|
);
|
|
273
337
|
}
|
|
274
338
|
|
|
275
|
-
//
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
</div>
|
|
282
|
-
);
|
|
339
|
+
// Template data — same shape as real user, no fetch
|
|
340
|
+
const userTemplate = {
|
|
341
|
+
name: "xxxxxxxxxxxxxx",
|
|
342
|
+
bio: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
343
|
+
avatar: "",
|
|
344
|
+
};
|
|
283
345
|
|
|
284
|
-
<ShimmerSuspense template={<
|
|
346
|
+
<ShimmerSuspense template={<UserCard user={userTemplate} />}>
|
|
285
347
|
<UserCard resource={resource} />
|
|
286
348
|
</ShimmerSuspense>;
|
|
287
349
|
```
|
|
288
350
|
|
|
351
|
+
Why a template prop at all (not just `dummyData` like `<Shimmer>`)? Because the real `<UserCard resource={resource} />` throws a Promise during render — it never produces DOM until data resolves. The library can't merge props into a component that's mid-suspend. Rendering a separate, non-suspending instance (same component, template data) gives Shimmer real DOM to trace.
|
|
352
|
+
|
|
289
353
|
### Option B: `useIsShimmering` hook
|
|
290
354
|
|
|
291
355
|
No template needed. The component detects shimmer mode and renders an empty shape.
|
|
@@ -398,8 +462,21 @@ import type {
|
|
|
398
462
|
ShimmerProps, // Props for <Shimmer>
|
|
399
463
|
ShimmerConfig, // Config options (colors, speed, animation)
|
|
400
464
|
ShimmerRect, // Measured element rectangle
|
|
401
|
-
AnimationType, // 'wave' | 'pulse' | '
|
|
402
|
-
ShimmerSuspenseProps,
|
|
465
|
+
AnimationType, // 'wave' | 'pulse' | 'shine' | 'glow' | 'gradient'
|
|
466
|
+
ShimmerSuspenseProps, // Props for <ShimmerSuspense>
|
|
467
|
+
} from "shimmer-trace";
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Runtime exports:
|
|
471
|
+
|
|
472
|
+
```ts
|
|
473
|
+
import {
|
|
474
|
+
Shimmer,
|
|
475
|
+
createShimmer,
|
|
476
|
+
ShimmerSuspense,
|
|
477
|
+
ShimmerContext,
|
|
478
|
+
useShimmerContext,
|
|
479
|
+
useIsShimmering,
|
|
403
480
|
} from "shimmer-trace";
|
|
404
481
|
```
|
|
405
482
|
|
|
@@ -407,15 +484,16 @@ import type {
|
|
|
407
484
|
|
|
408
485
|
## Comparison
|
|
409
486
|
|
|
410
|
-
| | shimmer-trace
|
|
411
|
-
| ---------------------- |
|
|
412
|
-
| Manual skeleton code | ❌ None
|
|
413
|
-
| Matches real layout | ✅ Automatically
|
|
414
|
-
|
|
|
415
|
-
|
|
|
416
|
-
|
|
|
417
|
-
|
|
|
418
|
-
|
|
|
487
|
+
| | shimmer-trace | react-loading-skeleton | MUI Skeleton |
|
|
488
|
+
| ---------------------- | ------------------------- | ---------------------- | -------------- |
|
|
489
|
+
| Manual skeleton code | ❌ None | ✅ Required | ✅ Required |
|
|
490
|
+
| Matches real layout | ✅ Automatically | ⚠️ Manual | ⚠️ Manual |
|
|
491
|
+
| Template data | ✅ `dummyData` | ❌ | ❌ |
|
|
492
|
+
| List mode | ✅ `dummyLength` / `as` | ❌ | ❌ |
|
|
493
|
+
| Suspense support | ✅ Native | ❌ | ❌ |
|
|
494
|
+
| Synchronized animation | ✅ One overlay | ⚠️ Per-element | ⚠️ Per-element |
|
|
495
|
+
| Zero layout shift | ✅ | ⚠️ | ⚠️ |
|
|
496
|
+
| Bundle size | ~3kb | ~5kb | ~12kb |
|
|
419
497
|
|
|
420
498
|
---
|
|
421
499
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shimmer-trace",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "High-performance React skeleton loaders that automatically trace your UI dimensions. Synchronized animations, zero CLS, and one-line implementation.",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"bugs": {
|
|
48
48
|
"url": "https://github.com/jeetvora331/shimmer-trace/issues"
|
|
49
49
|
},
|
|
50
|
-
"homepage": "https://github.
|
|
50
|
+
"homepage": "https://jeetvora331.github.io/shimmer-trace/",
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": ">=18.0.0",
|
|
53
53
|
"react-dom": ">=18.0.0"
|
|
@@ -59,6 +59,6 @@
|
|
|
59
59
|
"react-dom": "^19.1.0",
|
|
60
60
|
"esbuild": "^0.25.0",
|
|
61
61
|
"tsup": "^8.5.0",
|
|
62
|
-
"typescript": "^
|
|
62
|
+
"typescript": "^6.0.0"
|
|
63
63
|
}
|
|
64
64
|
}
|