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.
Files changed (3) hide show
  1. package/AGENTS.md +24 -18
  2. package/README.md +130 -52
  3. 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 } 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
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 } from 'shimmer-trace'; // true when inside ShimmerSuspense fallback
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:** inner `<Shimmer>` without `loading` prop inside an outer `<Shimmer>` auto-becomes a Reporter. It measures its own subtree and sends rects to the Master. One overlay covers everything.
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 `&nbsp;` width padding.
330
+
326
331
  ```tsx
327
332
  import { ShimmerSuspense } from 'shimmer-trace';
328
333
 
329
- // Skeleton template: same structure, no real data
330
- const UserCardTemplate = () => (
331
- <div className="profile-card">
332
- <img className="avatar" src="" alt="" />
333
- <div className="info">
334
- <h3>&nbsp;</h3>
335
- <span>&nbsp;</span>
336
- <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</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={<UserCardTemplate />} animation="wave">
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
  [![TypeScript](https://img.shields.io/badge/TypeScript-ready-3178c6)](https://www.typescriptlang.org)
11
11
  [![license](https://img.shields.io/npm/l/shimmer-trace)](./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
- - **List mode** — `dummyLength` clones your list items for skeleton lists, with template caching.
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
- | `dummyLength` | `number` | — | Enables list mode (see below) |
123
- | `stopPropagation` | `boolean` | `false` | Force master renderer, ignore parent context |
124
- | `className` | `string` | — | Class on the container `<div>` |
125
- | `style` | `CSSProperties` | | Inline styles on the container `<div>` |
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. List Skeleton with `dummyLength`
196
+ ### 3. Skeleton Shape with `dummyData`
175
197
 
176
- Loading a list from an API? `dummyLength` clones your list item template to show the right number of skeleton rows even when the array is empty.
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
- <Shimmer loading={loading} dummyLength={10}>
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
- <div className="post-row" key={post.id}>
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 list item, clones it `dummyLength` times, and shimmers it
197
- - If your array is empty during loading (e.g., `posts = []`), shimmer-trace uses a **cached template** from the previous render — the skeleton always matches your real layout
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
- ### 4. Synchronized Flex Layout
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
- ### 5. Custom Colors (Dark Mode)
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
- Pass the same component without data as `template`. Zero shimmer-awareness needed in your component.
326
+ Reuse the same component as its own skeleton — pass it through `template` with template props. No duplicate skeleton component, no `&nbsp;` 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
- // Same shape, no data — used as skeleton template
276
- const UserCardSkeleton = () => (
277
- <div className="card">
278
- <img src="" alt="" />
279
- <h3>&nbsp;</h3>
280
- <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>
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={<UserCardSkeleton />}>
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' | 'breathe'
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 | react-loading-skeleton | MUI Skeleton |
411
- | ---------------------- | ---------------- | ---------------------- | -------------- |
412
- | Manual skeleton code | ❌ None | ✅ Required | ✅ Required |
413
- | Matches real layout | ✅ Automatically | ⚠️ Manual | ⚠️ Manual |
414
- | List mode | ✅ `dummyLength` | ❌ | ❌ |
415
- | Suspense support | ✅ Native | ❌ | ❌ |
416
- | Synchronized animation | ✅ One overlay | ⚠️ Per-element | ⚠️ Per-element |
417
- | Zero layout shift | ✅ | ⚠️ | ⚠️ |
418
- | Bundle size | ~3kb | ~5kb | ~12kb |
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.2",
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.com/jeetvora331/shimmer-trace#readme",
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": "^5.8.3"
62
+ "typescript": "^6.0.0"
63
63
  }
64
64
  }