shimmer-trace 1.1.3 → 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 +128 -52
- package/package.json +1 -1
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
|
@@ -45,7 +45,9 @@ It renders your real component invisibly, traces every element's exact position
|
|
|
45
45
|
- **Zero CLS** — Container layout preserved. Default `preserveBackground` keeps card backgrounds, borders, and padding visible underneath the shimmer.
|
|
46
46
|
- **Synchronized animation** — One overlay, one wave. All skeletons animate in perfect sync.
|
|
47
47
|
- **5 animation styles** — `wave`, `pulse`, `shine`, `glow`, `gradient`.
|
|
48
|
-
- **
|
|
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.
|
|
49
51
|
- **Suspense-native** — `ShimmerSuspense` wraps any suspended component with no `loading` prop.
|
|
50
52
|
- **Factory pattern** — `createShimmer` pre-bakes your config. Use it like a component everywhere.
|
|
51
53
|
- **Composable** — Nested `Shimmer` components bubble their rects up to a single master overlay.
|
|
@@ -87,6 +89,19 @@ function ProfilePage() {
|
|
|
87
89
|
|
|
88
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.
|
|
89
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
|
+
|
|
90
105
|
---
|
|
91
106
|
|
|
92
107
|
## API Reference
|
|
@@ -103,7 +118,10 @@ The core component. Wrap anything with it.
|
|
|
103
118
|
highlightColor="#f5f5f5" // shimmer highlight color
|
|
104
119
|
speed={1.5} // animation duration in seconds
|
|
105
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
|
|
106
123
|
dummyLength={10} // list mode: number of skeleton items
|
|
124
|
+
as={UserCard} // component template — generate skeletons from a component
|
|
107
125
|
stopPropagation={false} // force this Shimmer to be a master
|
|
108
126
|
className="my-class" // applied to the container div
|
|
109
127
|
style={{ display: "flex" }} // merged into container styles
|
|
@@ -112,19 +130,21 @@ The core component. Wrap anything with it.
|
|
|
112
130
|
</Shimmer>
|
|
113
131
|
```
|
|
114
132
|
|
|
115
|
-
| Prop | Type | Default | Description
|
|
116
|
-
| -------------------- | ------------------------------------------------------ | ----------- |
|
|
117
|
-
| `loading` | `boolean` | `false` | Enables the shimmer skeleton
|
|
118
|
-
| `animation` | `'wave' \| 'pulse' \| 'shine' \| 'glow' \| 'gradient'` | `'wave'` | Animation style
|
|
119
|
-
| `preserveBackground` | `boolean` | `true` | Keep card backgrounds/borders visible while loading
|
|
120
|
-
| `baseColor` | `string` | `'#e0e0e0'` | Base skeleton color
|
|
121
|
-
| `highlightColor` | `string` | `'#f5f5f5'` | Shimmer highlight color
|
|
122
|
-
| `speed` | `number` | `1.5` | Animation speed in seconds
|
|
123
|
-
| `borderRadius` | `string` | auto | Override border-radius on all blocks
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
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>` |
|
|
128
148
|
|
|
129
149
|
---
|
|
130
150
|
|
|
@@ -173,32 +193,74 @@ Works out of the box with inputs, labels, and buttons.
|
|
|
173
193
|
</Shimmer>
|
|
174
194
|
```
|
|
175
195
|
|
|
176
|
-
### 3.
|
|
196
|
+
### 3. Skeleton Shape with `dummyData`
|
|
177
197
|
|
|
178
|
-
|
|
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.
|
|
179
199
|
|
|
180
200
|
```tsx
|
|
181
|
-
|
|
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
|
+
>
|
|
182
232
|
{posts.map((post) => (
|
|
183
|
-
<
|
|
184
|
-
<img src={post.thumbnail} alt="" />
|
|
185
|
-
<div>
|
|
186
|
-
<h4>{post.title}</h4>
|
|
187
|
-
<span>{post.author}</span>
|
|
188
|
-
</div>
|
|
189
|
-
<span className="badge">{post.category}</span>
|
|
190
|
-
</div>
|
|
233
|
+
<PostRow post={post} key={post.id} />
|
|
191
234
|
))}
|
|
192
|
-
</Shimmer
|
|
235
|
+
</Shimmer>;
|
|
193
236
|
```
|
|
194
237
|
|
|
195
238
|
**How it works:**
|
|
196
239
|
|
|
197
|
-
- `loading=false` → renders your `.map()` output normally
|
|
198
|
-
- `loading=true` → grabs the first
|
|
199
|
-
-
|
|
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`
|
|
245
|
+
|
|
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`.
|
|
200
262
|
|
|
201
|
-
###
|
|
263
|
+
### 6. Synchronized Flex Layout
|
|
202
264
|
|
|
203
265
|
One `<Shimmer>` wraps multiple cards. One overlay. One perfectly synchronized wave.
|
|
204
266
|
|
|
@@ -212,7 +274,7 @@ One `<Shimmer>` wraps multiple cards. One overlay. One perfectly synchronized wa
|
|
|
212
274
|
|
|
213
275
|
No separate shimmers per card. One master overlay covers them all — the wave sweeps the entire row in sync.
|
|
214
276
|
|
|
215
|
-
###
|
|
277
|
+
### 7. Custom Colors (Dark Mode)
|
|
216
278
|
|
|
217
279
|
```tsx
|
|
218
280
|
<Shimmer loading={loading} baseColor="#1e1e3a" highlightColor="#2d2d52">
|
|
@@ -261,7 +323,7 @@ import { ShimmerSuspense } from "shimmer-trace";
|
|
|
261
323
|
|
|
262
324
|
### Option A: Explicit template (recommended)
|
|
263
325
|
|
|
264
|
-
|
|
326
|
+
Reuse the same component as its own skeleton — pass it through `template` with template props. No duplicate skeleton component, no ` ` width hacks.
|
|
265
327
|
|
|
266
328
|
```tsx
|
|
267
329
|
function UserCard({ user }) {
|
|
@@ -274,20 +336,20 @@ function UserCard({ user }) {
|
|
|
274
336
|
);
|
|
275
337
|
}
|
|
276
338
|
|
|
277
|
-
//
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
</div>
|
|
284
|
-
);
|
|
339
|
+
// Template data — same shape as real user, no fetch
|
|
340
|
+
const userTemplate = {
|
|
341
|
+
name: "xxxxxxxxxxxxxx",
|
|
342
|
+
bio: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
343
|
+
avatar: "",
|
|
344
|
+
};
|
|
285
345
|
|
|
286
|
-
<ShimmerSuspense template={<
|
|
346
|
+
<ShimmerSuspense template={<UserCard user={userTemplate} />}>
|
|
287
347
|
<UserCard resource={resource} />
|
|
288
348
|
</ShimmerSuspense>;
|
|
289
349
|
```
|
|
290
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
|
+
|
|
291
353
|
### Option B: `useIsShimmering` hook
|
|
292
354
|
|
|
293
355
|
No template needed. The component detects shimmer mode and renders an empty shape.
|
|
@@ -400,8 +462,21 @@ import type {
|
|
|
400
462
|
ShimmerProps, // Props for <Shimmer>
|
|
401
463
|
ShimmerConfig, // Config options (colors, speed, animation)
|
|
402
464
|
ShimmerRect, // Measured element rectangle
|
|
403
|
-
AnimationType, // 'wave' | 'pulse' | '
|
|
404
|
-
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,
|
|
405
480
|
} from "shimmer-trace";
|
|
406
481
|
```
|
|
407
482
|
|
|
@@ -409,15 +484,16 @@ import type {
|
|
|
409
484
|
|
|
410
485
|
## Comparison
|
|
411
486
|
|
|
412
|
-
| | shimmer-trace
|
|
413
|
-
| ---------------------- |
|
|
414
|
-
| Manual skeleton code | ❌ None
|
|
415
|
-
| Matches real layout | ✅ Automatically
|
|
416
|
-
|
|
|
417
|
-
|
|
|
418
|
-
|
|
|
419
|
-
|
|
|
420
|
-
|
|
|
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 |
|
|
421
497
|
|
|
422
498
|
---
|
|
423
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",
|