shimmer-trace 1.1.0
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 +657 -0
- package/LICENSE +21 -0
- package/README.md +424 -0
- package/dist/index.d.mts +224 -0
- package/dist/index.d.ts +224 -0
- package/dist/index.js +601 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +590 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +64 -0
package/README.md
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# shimmer-trace ✨
|
|
2
|
+
|
|
3
|
+
> **Automatic skeleton loaders that trace your real UI.**
|
|
4
|
+
> Zero configuration. Zero layout shift. One line of code.
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/shimmer-trace)
|
|
7
|
+
[](#bundle-size)
|
|
8
|
+
[](https://bundlephobia.com/package/shimmer-trace)
|
|
9
|
+
[](https://react.dev)
|
|
10
|
+
[](https://www.typescriptlang.org)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<img src="https://raw.githubusercontent.com/jeetvora331/shimmer-trace/main/assets/demo.gif" alt="shimmer-trace demo" width="720" />
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
Most skeleton libraries make you **hand-draw** a skeleton that matches your UI.
|
|
20
|
+
You measure heights, pick widths, match border-radii, and pray nothing changes.
|
|
21
|
+
|
|
22
|
+
**shimmer-trace does none of that.**
|
|
23
|
+
|
|
24
|
+
It renders your real component invisibly, traces every element's exact position and size from the live DOM, then paints a pixel-perfect shimmer overlay on top — automatically, on every resize.
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// Before: manual skeleton hell
|
|
28
|
+
<SkeletonRect width="100%" height={24} borderRadius={8} />
|
|
29
|
+
<SkeletonRect width="60%" height={16} borderRadius={4} />
|
|
30
|
+
<SkeletonCircle size={48} />
|
|
31
|
+
|
|
32
|
+
// After: just wrap it
|
|
33
|
+
<Shimmer loading={loading}>
|
|
34
|
+
<UserCard />
|
|
35
|
+
</Shimmer>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Features
|
|
41
|
+
|
|
42
|
+
- **Auto-tracing** — Measures real DOM layout. No manual skeleton code.
|
|
43
|
+
- **Zero CLS** — Container layout preserved. Default `preserveBackground` keeps card backgrounds, borders, and padding visible underneath the shimmer.
|
|
44
|
+
- **Synchronized animation** — One overlay, one wave. All skeletons animate in perfect sync.
|
|
45
|
+
- **5 animation styles** — `wave`, `pulse`, `shine`, `glow`, `gradient`.
|
|
46
|
+
- **List mode** — `dummyLength` clones your list items for skeleton lists, with template caching.
|
|
47
|
+
- **Suspense-native** — `ShimmerSuspense` wraps any suspended component with no `loading` prop.
|
|
48
|
+
- **Factory pattern** — `createShimmer` pre-bakes your config. Use it like a component everywhere.
|
|
49
|
+
- **Composable** — Nested `Shimmer` components bubble their rects up to a single master overlay.
|
|
50
|
+
- **ResizeObserver** — Re-traces automatically when the container resizes.
|
|
51
|
+
- **3.29 kB min+gzip** (2.95 kB brotli) — Zero runtime dependencies. Run `npm run size` to verify.
|
|
52
|
+
- **TypeScript-first** — Full types included.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install shimmer-trace
|
|
60
|
+
# or
|
|
61
|
+
yarn add shimmer-trace
|
|
62
|
+
# or
|
|
63
|
+
pnpm add shimmer-trace
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Peer dependencies:** React 18+
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Quick Start
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import { Shimmer } from "shimmer-trace";
|
|
74
|
+
|
|
75
|
+
function ProfilePage() {
|
|
76
|
+
const [loading, setLoading] = useState(true);
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Shimmer loading={loading}>
|
|
80
|
+
<UserCard />
|
|
81
|
+
</Shimmer>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
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
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## API Reference
|
|
91
|
+
|
|
92
|
+
### `<Shimmer>`
|
|
93
|
+
|
|
94
|
+
The core component. Wrap anything with it.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
<Shimmer
|
|
98
|
+
loading={boolean} // required — controls shimmer on/off
|
|
99
|
+
animation="wave" // 'wave' | 'pulse' | 'shine' | 'glow' | 'gradient'
|
|
100
|
+
baseColor="#e0e0e0" // skeleton base color
|
|
101
|
+
highlightColor="#f5f5f5" // shimmer highlight color
|
|
102
|
+
speed={1.5} // animation duration in seconds
|
|
103
|
+
borderRadius="4px" // override auto-detected border-radius
|
|
104
|
+
dummyLength={10} // list mode: number of skeleton items
|
|
105
|
+
stopPropagation={false} // force this Shimmer to be a master
|
|
106
|
+
className="my-class" // applied to the container div
|
|
107
|
+
style={{ display: "flex" }} // merged into container styles
|
|
108
|
+
>
|
|
109
|
+
{children}
|
|
110
|
+
</Shimmer>
|
|
111
|
+
```
|
|
112
|
+
|
|
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>` |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Examples
|
|
130
|
+
|
|
131
|
+
### 1. Profile Card
|
|
132
|
+
|
|
133
|
+
Wrap any component — shimmer-trace handles the rest.
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { Shimmer } from "shimmer-trace";
|
|
137
|
+
|
|
138
|
+
function App() {
|
|
139
|
+
const [loading, setLoading] = useState(true);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<Shimmer loading={loading}>
|
|
143
|
+
<div className="profile-card">
|
|
144
|
+
<img src={user.avatar} alt="Avatar" />
|
|
145
|
+
<div>
|
|
146
|
+
<h3>{user.name}</h3>
|
|
147
|
+
<span>{user.role}</span>
|
|
148
|
+
<p>{user.bio}</p>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</Shimmer>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 2. Form Skeleton
|
|
157
|
+
|
|
158
|
+
Works out of the box with inputs, labels, and buttons.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<Shimmer loading={loading}>
|
|
162
|
+
<form>
|
|
163
|
+
<label>Email</label>
|
|
164
|
+
<input type="email" placeholder="you@example.com" />
|
|
165
|
+
|
|
166
|
+
<label>Message</label>
|
|
167
|
+
<textarea placeholder="Your message..." />
|
|
168
|
+
|
|
169
|
+
<button type="submit">Send</button>
|
|
170
|
+
</form>
|
|
171
|
+
</Shimmer>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### 3. List Skeleton with `dummyLength`
|
|
175
|
+
|
|
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.
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
<Shimmer loading={loading} dummyLength={10}>
|
|
180
|
+
{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>
|
|
189
|
+
))}
|
|
190
|
+
</Shimmer>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**How it works:**
|
|
194
|
+
|
|
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
|
|
198
|
+
|
|
199
|
+
### 4. Synchronized Flex Layout
|
|
200
|
+
|
|
201
|
+
One `<Shimmer>` wraps multiple cards. One overlay. One perfectly synchronized wave.
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
<Shimmer loading={loading} style={{ display: "flex", gap: "1rem" }}>
|
|
205
|
+
<StatCard value="4,821" label="Total Users" />
|
|
206
|
+
<StatCard value="98.4%" label="Uptime" />
|
|
207
|
+
<StatCard value="142ms" label="Avg Latency" />
|
|
208
|
+
</Shimmer>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
No separate shimmers per card. One master overlay covers them all — the wave sweeps the entire row in sync.
|
|
212
|
+
|
|
213
|
+
### 5. Custom Colors (Dark Mode)
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
<Shimmer loading={loading} baseColor="#1e1e3a" highlightColor="#2d2d52">
|
|
217
|
+
<DashboardWidget />
|
|
218
|
+
</Shimmer>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## `createShimmer` — Factory Pattern
|
|
224
|
+
|
|
225
|
+
Pre-configure once, use everywhere. Great for design systems.
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
import { createShimmer } from "shimmer-trace";
|
|
229
|
+
|
|
230
|
+
// Create a pre-configured Shimmer component
|
|
231
|
+
const DarkShimmer = createShimmer({
|
|
232
|
+
baseColor: "#1e1e3a",
|
|
233
|
+
highlightColor: "#2d2d52",
|
|
234
|
+
animation: "wave",
|
|
235
|
+
speed: 1.2,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Use it like a regular component — just add `loading`
|
|
239
|
+
function App() {
|
|
240
|
+
return (
|
|
241
|
+
<DarkShimmer loading={loading}>
|
|
242
|
+
<UserCard />
|
|
243
|
+
</DarkShimmer>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The created component accepts all the same props as `<Shimmer>` — the factory defaults are just overridable.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## `ShimmerSuspense` — Suspense-Native Loading
|
|
253
|
+
|
|
254
|
+
No `loading` prop. No state. Shimmer shows automatically while children are suspended.
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
import { ShimmerSuspense } from "shimmer-trace";
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Option A: Explicit template (recommended)
|
|
261
|
+
|
|
262
|
+
Pass the same component without data as `template`. Zero shimmer-awareness needed in your component.
|
|
263
|
+
|
|
264
|
+
```tsx
|
|
265
|
+
function UserCard({ user }) {
|
|
266
|
+
return (
|
|
267
|
+
<div className="card">
|
|
268
|
+
<img src={user.avatar} alt="" />
|
|
269
|
+
<h3>{user.name}</h3>
|
|
270
|
+
<p>{user.bio}</p>
|
|
271
|
+
</div>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Same shape, no data — used as skeleton template
|
|
276
|
+
const UserCardSkeleton = () => (
|
|
277
|
+
<div className="card">
|
|
278
|
+
<img src="" alt="" />
|
|
279
|
+
<h3> </h3>
|
|
280
|
+
<p> </p>
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
<ShimmerSuspense template={<UserCardSkeleton />}>
|
|
285
|
+
<UserCard resource={resource} />
|
|
286
|
+
</ShimmerSuspense>;
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Option B: `useIsShimmering` hook
|
|
290
|
+
|
|
291
|
+
No template needed. The component detects shimmer mode and renders an empty shape.
|
|
292
|
+
|
|
293
|
+
```tsx
|
|
294
|
+
import { useIsShimmering } from "shimmer-trace";
|
|
295
|
+
|
|
296
|
+
function UserCard({ resource }) {
|
|
297
|
+
const isShimmering = useIsShimmering();
|
|
298
|
+
|
|
299
|
+
// Skip data fetching in shimmer mode (avoids nested Suspense throw)
|
|
300
|
+
const user = isShimmering ? null : resource.read();
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<div className="card">
|
|
304
|
+
<img src={user?.avatar ?? ""} alt="" />
|
|
305
|
+
<h3>{user?.name ?? " "}</h3>
|
|
306
|
+
<p>{user?.bio ?? " "}</p>
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
<ShimmerSuspense>
|
|
312
|
+
<UserCard resource={resource} />
|
|
313
|
+
</ShimmerSuspense>;
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`ShimmerSuspense` accepts all `ShimmerConfig` props too:
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
<ShimmerSuspense
|
|
320
|
+
template={<UserCardSkeleton />}
|
|
321
|
+
animation="pulse"
|
|
322
|
+
baseColor="#1e1e3a"
|
|
323
|
+
highlightColor="#2d2d52"
|
|
324
|
+
>
|
|
325
|
+
<UserCard resource={resource} />
|
|
326
|
+
</ShimmerSuspense>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## Composing Nested Shimmers
|
|
332
|
+
|
|
333
|
+
`Shimmer` components nest intelligently. Inner (Reporter) shimmers report their rects to the nearest outer (Master) shimmer — all combined into a single overlay.
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
<Shimmer loading={loading}>
|
|
337
|
+
<PageHeader>
|
|
338
|
+
{/* This nested Shimmer contributes its rects to the parent overlay */}
|
|
339
|
+
<Shimmer loading={loading}>
|
|
340
|
+
<NavigationMenu />
|
|
341
|
+
</Shimmer>
|
|
342
|
+
</PageHeader>
|
|
343
|
+
<MainContent />
|
|
344
|
+
</Shimmer>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Use `stopPropagation` to force an independent shimmer:
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
<Shimmer loading={outerLoading}>
|
|
351
|
+
<Sidebar />
|
|
352
|
+
|
|
353
|
+
{/* Independent shimmer — own overlay, own animation */}
|
|
354
|
+
<Shimmer loading={innerLoading} stopPropagation>
|
|
355
|
+
<Feed />
|
|
356
|
+
</Shimmer>
|
|
357
|
+
</Shimmer>
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## DOM Control Attributes
|
|
363
|
+
|
|
364
|
+
Fine-tune what gets traced with data attributes:
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
// Trace this specific element (overrides auto-detection)
|
|
368
|
+
<div data-shimmer>Custom block</div>
|
|
369
|
+
|
|
370
|
+
// Skip this element entirely
|
|
371
|
+
<div data-shimmer-ignore>Never shimmer this</div>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## How It Works
|
|
377
|
+
|
|
378
|
+
1. **Render real DOM** — `Shimmer` renders children normally. With `preserveBackground` (default), CSS rules hide text (`color: transparent`) and media (`opacity: 0`) on leaf elements while keeping container backgrounds, borders, and padding fully visible. Layout stays identical — zero CLS.
|
|
379
|
+
|
|
380
|
+
2. **Walk the DOM** — `useTrace` recursively traverses the container, collecting every traceable element: headings, paragraphs, images, inputs, buttons, and leaf nodes with visible dimensions.
|
|
381
|
+
|
|
382
|
+
3. **Measure everything** — Each element is measured with `getBoundingClientRect()` relative to the master container, capturing position, size, and computed `border-radius`.
|
|
383
|
+
|
|
384
|
+
4. **Build the overlay** — One absolutely-positioned `<div>` is rendered per traced rect, sized and positioned to match exactly. For sweep animations (`wave`, `shine`), each block also gets a gradient layer that spans the full container width — the highlight sweeps across all blocks in perfect sync.
|
|
385
|
+
|
|
386
|
+
5. **ResizeObserver** — Container resize triggers an automatic re-trace, so the skeleton stays pixel-perfect on responsive layouts.
|
|
387
|
+
|
|
388
|
+
6. **Re-trace on resize** — `ResizeObserver` watches the container and re-measures on every resize, keeping skeletons accurate at any screen size.
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## TypeScript
|
|
393
|
+
|
|
394
|
+
Full types exported:
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
import type {
|
|
398
|
+
ShimmerProps, // Props for <Shimmer>
|
|
399
|
+
ShimmerConfig, // Config options (colors, speed, animation)
|
|
400
|
+
ShimmerRect, // Measured element rectangle
|
|
401
|
+
AnimationType, // 'wave' | 'pulse' | 'breathe'
|
|
402
|
+
ShimmerSuspenseProps,
|
|
403
|
+
} from "shimmer-trace";
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Comparison
|
|
409
|
+
|
|
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 |
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## License
|
|
423
|
+
|
|
424
|
+
ISC — [Jeet Vora](https://github.com/jeetvora331)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import React__default, { ReactNode, RefObject } from 'react';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Represents a measured rectangle of a traced DOM element,
|
|
7
|
+
* positioned relative to the Master Shimmer container.
|
|
8
|
+
*/
|
|
9
|
+
interface ShimmerRect {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
width: number;
|
|
13
|
+
height: number;
|
|
14
|
+
borderRadius: string;
|
|
15
|
+
}
|
|
16
|
+
/** Available animation types for the shimmer effect. */
|
|
17
|
+
type AnimationType = 'wave' | 'pulse' | 'shine' | 'glow' | 'gradient';
|
|
18
|
+
/** Configuration options for the shimmer effect (all optional). */
|
|
19
|
+
interface ShimmerConfig {
|
|
20
|
+
/** Animation style. Defaults to 'wave'. */
|
|
21
|
+
animation?: AnimationType;
|
|
22
|
+
/** Base color of the shimmer blocks. Defaults to '#e0e0e0'. */
|
|
23
|
+
baseColor?: string;
|
|
24
|
+
/** Highlight color of the shimmer animation. Defaults to '#f5f5f5'. */
|
|
25
|
+
highlightColor?: string;
|
|
26
|
+
/** Animation duration in seconds. Defaults to 1.5. */
|
|
27
|
+
speed?: number;
|
|
28
|
+
/** Global border-radius override. If omitted, auto-detected from each element (defaults to 4px if detection is 0px). */
|
|
29
|
+
borderRadius?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Keep container backgrounds, borders, and padding visible while loading.
|
|
32
|
+
* When `true` (default), only text and media leaves are hidden via
|
|
33
|
+
* `color:transparent` / `opacity:0` so card backgrounds, borders, and
|
|
34
|
+
* spacing remain visible underneath the shimmer overlay.
|
|
35
|
+
*
|
|
36
|
+
* Set `false` for legacy behavior (`visibility:hidden` on whole tree).
|
|
37
|
+
*/
|
|
38
|
+
preserveBackground?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/** Props for the Shimmer component. */
|
|
41
|
+
interface ShimmerProps extends ShimmerConfig {
|
|
42
|
+
/** Whether the loading state is active. */
|
|
43
|
+
loading?: boolean;
|
|
44
|
+
/** The children to trace and render shimmer over. */
|
|
45
|
+
children: ReactNode;
|
|
46
|
+
/**
|
|
47
|
+
* Number of placeholder clones to generate for list-like loading states.
|
|
48
|
+
*
|
|
49
|
+
* When `loading=true` and `dummyLength` is set, Shimmer grabs the first
|
|
50
|
+
* available child (or a cached template from the last loaded render) and
|
|
51
|
+
* clones it `dummyLength` times to produce skeleton placeholders.
|
|
52
|
+
*
|
|
53
|
+
* When `loading=false`, children are rendered as-is.
|
|
54
|
+
*/
|
|
55
|
+
dummyLength?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Props injected into each child element while `loading=true` so the
|
|
58
|
+
* skeleton renders with realistic shape without requiring real data.
|
|
59
|
+
*
|
|
60
|
+
* Example:
|
|
61
|
+
* ```tsx
|
|
62
|
+
* <Shimmer
|
|
63
|
+
* loading={loading}
|
|
64
|
+
* dummyData={{ user: { name: 'Loading...', role: '...', avatar: '' } }}
|
|
65
|
+
* >
|
|
66
|
+
* <UserCard user={user} />
|
|
67
|
+
* </Shimmer>
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* While loading, each direct child is cloned with these props merged on top
|
|
71
|
+
* of its own props. Ignored when `loading=false`.
|
|
72
|
+
*/
|
|
73
|
+
dummyData?: Record<string, any>;
|
|
74
|
+
/**
|
|
75
|
+
* Component used to auto-generate skeleton elements while `loading=true`.
|
|
76
|
+
*
|
|
77
|
+
* When set, Shimmer ignores `children` during loading and renders
|
|
78
|
+
* `dummyLength` (defaults to 1) instances of `<as {...dummyData} />`
|
|
79
|
+
* to derive shape. Real children render once `loading=false`.
|
|
80
|
+
*
|
|
81
|
+
* ```tsx
|
|
82
|
+
* <Shimmer
|
|
83
|
+
* loading={loading}
|
|
84
|
+
* as={MovieCard}
|
|
85
|
+
* dummyData={{ movie: movieTemplate }}
|
|
86
|
+
* dummyLength={10}
|
|
87
|
+
* >
|
|
88
|
+
* {movies.map((m) => <MovieCard movie={m} key={m.id} />)}
|
|
89
|
+
* </Shimmer>
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
as?: React__default.ComponentType<any>;
|
|
93
|
+
/** Force this Shimmer to be a Master renderer even if nested inside another Shimmer. */
|
|
94
|
+
stopPropagation?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* className applied to the Master container div.
|
|
97
|
+
* Use to control layout (e.g. display:flex) without losing position:relative.
|
|
98
|
+
*/
|
|
99
|
+
className?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Inline styles merged into the Master container div.
|
|
102
|
+
* position:relative is always applied; everything else is overridable.
|
|
103
|
+
*/
|
|
104
|
+
style?: React__default.CSSProperties;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The main Shimmer component.
|
|
109
|
+
*
|
|
110
|
+
* Auto-detects **Master** (no parent Shimmer) vs **Reporter** (nested).
|
|
111
|
+
* - Master: renders children hidden, traces DOM, paints overlay.
|
|
112
|
+
* - Reporter: measures own rects, reports to parent Master.
|
|
113
|
+
*
|
|
114
|
+
* ### Skeleton shape via `dummyData`
|
|
115
|
+
*
|
|
116
|
+
* Pass `dummyData` so children render with realistic data while loading.
|
|
117
|
+
* No render-prop, no manual `data || fallback` in JSX.
|
|
118
|
+
*
|
|
119
|
+
* ```tsx
|
|
120
|
+
* const userTemplate = { name: 'Loading...', role: '...', avatar: '' };
|
|
121
|
+
*
|
|
122
|
+
* <Shimmer loading={loading} dummyData={{ user: userTemplate }}>
|
|
123
|
+
* <UserCard user={user} />
|
|
124
|
+
* </Shimmer>
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* ### List mode (`dummyLength`)
|
|
128
|
+
*
|
|
129
|
+
* Combined with `dummyData`, clones the first child N times with
|
|
130
|
+
* template props merged in:
|
|
131
|
+
*
|
|
132
|
+
* ```tsx
|
|
133
|
+
* <Shimmer
|
|
134
|
+
* loading={loading}
|
|
135
|
+
* dummyLength={5}
|
|
136
|
+
* dummyData={{ fruit: { name: 'xxxxx', price: '$0.00' } }}
|
|
137
|
+
* >
|
|
138
|
+
* <FruitCard fruit={undefined as any} />
|
|
139
|
+
* </Shimmer>
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
declare function Shimmer({ loading, children, dummyLength, dummyData, as, stopPropagation, animation, baseColor, highlightColor, speed, borderRadius, preserveBackground, className, style, }: ShimmerProps): react_jsx_runtime.JSX.Element;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Factory function to create a pre-configured Shimmer component.
|
|
146
|
+
* Avoids "Provider Hell" by baking config into the returned component.
|
|
147
|
+
*
|
|
148
|
+
* All config properties are optional — defaults are used for anything
|
|
149
|
+
* not specified.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```tsx
|
|
153
|
+
* const AppShimmer = createShimmer({
|
|
154
|
+
* animation: 'pulse',
|
|
155
|
+
* baseColor: '#1a1a2e',
|
|
156
|
+
* highlightColor: '#16213e',
|
|
157
|
+
* speed: 2,
|
|
158
|
+
* });
|
|
159
|
+
*
|
|
160
|
+
* <AppShimmer loading={isLoading}>
|
|
161
|
+
* <MyComponent />
|
|
162
|
+
* </AppShimmer>
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
declare function createShimmer(config?: ShimmerConfig): (props: Omit<ShimmerProps, keyof ShimmerConfig> & Partial<ShimmerConfig>) => react_jsx_runtime.JSX.Element;
|
|
166
|
+
|
|
167
|
+
interface ShimmerContextValue {
|
|
168
|
+
register: (id: string, rects: ShimmerRect[]) => void;
|
|
169
|
+
unregister: (id: string) => void;
|
|
170
|
+
/** Ref object (not .current) so Reporters always read a fresh value. */
|
|
171
|
+
masterRef: RefObject<HTMLElement | null>;
|
|
172
|
+
loading: boolean;
|
|
173
|
+
config: Required<ShimmerConfig>;
|
|
174
|
+
}
|
|
175
|
+
declare const ShimmerContext: React.Context<ShimmerContextValue | null>;
|
|
176
|
+
declare function useShimmerContext(): ShimmerContextValue | null;
|
|
177
|
+
declare function useIsShimmering(): boolean;
|
|
178
|
+
|
|
179
|
+
interface ShimmerSuspenseProps extends ShimmerConfig {
|
|
180
|
+
children: React__default.ReactNode;
|
|
181
|
+
/**
|
|
182
|
+
* Explicit skeleton template. Rendered hidden and traced for shimmer shape.
|
|
183
|
+
*
|
|
184
|
+
* Preferred — pass the same component with no data props:
|
|
185
|
+
* ```tsx
|
|
186
|
+
* <ShimmerSuspense template={<UserCard />}>
|
|
187
|
+
* <UserCard />
|
|
188
|
+
* </ShimmerSuspense>
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* If omitted, falls back to Option B: children are re-rendered with
|
|
192
|
+
* `useIsShimmering()=true` so they can return an empty shape themselves.
|
|
193
|
+
*/
|
|
194
|
+
template?: React__default.ReactNode;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Suspense boundary that automatically shows a shimmer skeleton while
|
|
198
|
+
* children are suspended (e.g. useSuspenseQuery, use(promise), etc).
|
|
199
|
+
*
|
|
200
|
+
* **Option A — explicit template (preferred):**
|
|
201
|
+
* ```tsx
|
|
202
|
+
* <ShimmerSuspense template={<UserCard />}>
|
|
203
|
+
* <UserCard />
|
|
204
|
+
* </ShimmerSuspense>
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* **Option B — useIsShimmering hook (no template):**
|
|
208
|
+
* ```tsx
|
|
209
|
+
* function UserCard() {
|
|
210
|
+
* const isShimmering = useIsShimmering();
|
|
211
|
+
* const data = isShimmering ? null : useSuspenseQuery(...);
|
|
212
|
+
* return <div><h3>{data?.name}</h3></div>;
|
|
213
|
+
* }
|
|
214
|
+
*
|
|
215
|
+
* <ShimmerSuspense>
|
|
216
|
+
* <UserCard />
|
|
217
|
+
* </ShimmerSuspense>
|
|
218
|
+
* ```
|
|
219
|
+
* Components must use `useIsShimmering()` to skip data fetching in shimmer mode,
|
|
220
|
+
* otherwise they will also suspend inside the fallback (causing an empty skeleton).
|
|
221
|
+
*/
|
|
222
|
+
declare function ShimmerSuspense({ children, template, ...shimmerConfig }: ShimmerSuspenseProps): react_jsx_runtime.JSX.Element;
|
|
223
|
+
|
|
224
|
+
export { type AnimationType, Shimmer, type ShimmerConfig, ShimmerContext, type ShimmerProps, type ShimmerRect, ShimmerSuspense, type ShimmerSuspenseProps, createShimmer, useIsShimmering, useShimmerContext };
|