@shimmer-from-structure/angular 2.2.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/README.md ADDED
@@ -0,0 +1,911 @@
1
+ # ✨ Shimmer From Structure
2
+
3
+ A **React, Vue, Svelte & Angular** shimmer/skeleton library that **automatically adapts to your component's runtime structure**. Unlike traditional shimmer libraries that require pre-defined skeleton structures, this library analyzes your actual component's DOM at runtime and generates a shimmer effect that perfectly matches its layout.
4
+
5
+ ![React](https://img.shields.io/badge/React-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB)
6
+ ![Vue](https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D)
7
+ ![Svelte](https://img.shields.io/badge/Svelte-ff3e00?style=for-the-badge&logo=svelte&logoColor=white)
8
+ ![Angular](https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white)
9
+
10
+ ![Shimmer From Structure Demo](https://github.com/darula-hpp/shimmer-from-structure/raw/main/example/preview.gif)
11
+
12
+ ## Why This Library?
13
+
14
+ Traditional shimmer libraries require you to:
15
+
16
+ - Manually create skeleton components that mirror your real components
17
+ - Maintain two versions of each component (real + skeleton)
18
+ - Update skeletons every time your layout changes
19
+
20
+ **Shimmer From Structure** eliminates all of that:
21
+
22
+ - ✅ **Works with React, Vue, Svelte & Angular** - Simple, framework-specific adapters
23
+ - ✅ Automatically measures your component's structure at runtime
24
+ - ✅ Generates shimmer effects that match actual dimensions
25
+ - ✅ Zero maintenance - works with any layout changes
26
+ - ✅ Works with complex nested structures
27
+ - ✅ Supports dynamic data with `templateProps`
28
+ - ✅ Preserves container backgrounds during loading
29
+ - ✅ Auto-detects border-radius from your CSS
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ npm install shimmer-from-structure
35
+ # or
36
+ yarn add shimmer-from-structure
37
+ # or
38
+ pnpm add shimmer-from-structure
39
+ ```
40
+
41
+ ## 🎯 Framework Support
42
+
43
+ Shimmer From Structure provides dedicated packages for **React and Vue**.
44
+
45
+ ### React
46
+
47
+ React support is built-in to the main package for backward compatibility:
48
+
49
+ ```javascript
50
+ // React projects (or @shimmer-from-structure/react)
51
+ import { Shimmer } from 'shimmer-from-structure';
52
+ ```
53
+
54
+ ### Vue 3
55
+
56
+ Vue support requires importing from the specific adapter:
57
+
58
+ ```javascript
59
+ // Vue 3 projects
60
+ import { Shimmer } from '@shimmer-from-structure/vue';
61
+ ```
62
+
63
+ ### Svelte
64
+
65
+ Svelte support is provided via its own adapter:
66
+
67
+ ```javascript
68
+ // Svelte projects
69
+ import { Shimmer } from '@shimmer-from-structure/svelte';
70
+ ```
71
+
72
+ ### Angular
73
+
74
+ Angular support requires importing from the specific adapter:
75
+
76
+ ```typescript
77
+ // Angular projects
78
+ import { ShimmerComponent } from '@shimmer-from-structure/angular';
79
+ ```
80
+
81
+ ---
82
+
83
+ # 📖 Basic Usage
84
+
85
+ ## React
86
+
87
+ ### Static Content
88
+
89
+ For components with hardcoded/static content:
90
+
91
+ ```tsx
92
+ import { Shimmer } from 'shimmer-from-structure';
93
+
94
+ function UserCard() {
95
+ return (
96
+ <Shimmer loading={isLoading}>
97
+ <div className="card">
98
+ <img src="avatar.jpg" className="avatar" />
99
+ <h2>John Doe</h2>
100
+ <p>Software Engineer</p>
101
+ </div>
102
+ </Shimmer>
103
+ );
104
+ }
105
+ ```
106
+
107
+ ## Vue
108
+
109
+ ### Static Content
110
+
111
+ ```vue
112
+ <script setup>
113
+ import { ref } from 'vue';
114
+ import { Shimmer } from '@shimmer-from-structure/vue';
115
+
116
+ const isLoading = ref(true);
117
+ </script>
118
+
119
+ <template>
120
+ <Shimmer :loading="isLoading">
121
+ <div class="card">
122
+ <img src="avatar.jpg" class="avatar" />
123
+ <h2>John Doe</h2>
124
+ <p>Software Engineer</p>
125
+ </div>
126
+ </Shimmer>
127
+ </template>
128
+ ```
129
+
130
+ ## Svelte
131
+
132
+ ### Static Content
133
+
134
+ ```svelte
135
+ <script>
136
+ import { Shimmer } from '@shimmer-from-structure/svelte';
137
+
138
+ let isLoading = $state(true);
139
+ </script>
140
+
141
+ <Shimmer loading={isLoading}>
142
+ <div class="card">
143
+ <img src="avatar.jpg" class="avatar" />
144
+ <h2>John Doe</h2>
145
+ <p>Software Engineer</p>
146
+ </div>
147
+ </Shimmer>
148
+ ```
149
+
150
+ ## Angular
151
+
152
+ ### Static Content
153
+
154
+ ```typescript
155
+ import { Component, signal } from '@angular/core';
156
+ import { ShimmerComponent } from '@shimmer-from-structure/angular';
157
+
158
+ @Component({
159
+ selector: 'app-user-card',
160
+ standalone: true,
161
+ imports: [ShimmerComponent],
162
+ template: `
163
+ <shimmer [loading]="isLoading()">
164
+ <div class="card">
165
+ <img src="avatar.jpg" class="avatar" />
166
+ <h2>John Doe</h2>
167
+ <p>Software Engineer</p>
168
+ </div>
169
+ </shimmer>
170
+ `,
171
+ })
172
+ export class UserCardComponent {
173
+ isLoading = signal(true);
174
+ }
175
+ ```
176
+
177
+ ---
178
+
179
+ ### Dynamic Content with `templateProps`
180
+
181
+ For components that receive dynamic data via props, use `templateProps` to provide mock data for skeleton generation:
182
+
183
+ **React**
184
+
185
+ ```tsx
186
+ import { Shimmer } from 'shimmer-from-structure';
187
+
188
+ // Your component that accepts props
189
+ const UserCard = ({ user }) => (
190
+ <div className="card">
191
+ <img src={user.avatar} className="avatar" />
192
+ <h2>{user.name}</h2>
193
+ <p>{user.role}</p>
194
+ </div>
195
+ );
196
+
197
+ // Template data for the skeleton
198
+ const userTemplate = {
199
+ name: 'Loading...',
200
+ role: 'Loading role...',
201
+ avatar: 'placeholder.jpg',
202
+ };
203
+
204
+ function App() {
205
+ const [loading, setLoading] = useState(true);
206
+ const [user, setUser] = useState(null);
207
+
208
+ return (
209
+ <Shimmer loading={loading} templateProps={{ user: userTemplate }}>
210
+ <UserCard user={user || userTemplate} />
211
+ </Shimmer>
212
+ );
213
+ }
214
+ ```
215
+
216
+ **Vue**
217
+
218
+ ```vue
219
+ <script setup>
220
+ import { ref } from 'vue';
221
+ import { Shimmer } from '@shimmer-from-structure/vue';
222
+ import UserCard from './UserCard.vue';
223
+
224
+ const loading = ref(true);
225
+ const userTemplate = {
226
+ name: 'Loading...',
227
+ role: 'Loading role...',
228
+ avatar: 'placeholder.jpg',
229
+ };
230
+ </script>
231
+
232
+ <template>
233
+ <Shimmer :loading="loading" :templateProps="{ user: userTemplate }">
234
+ <UserCard :user="user || userTemplate" />
235
+ </Shimmer>
236
+ </template>
237
+ ```
238
+
239
+ **Svelte**
240
+
241
+ ```svelte
242
+ <script>
243
+ import { Shimmer } from '@shimmer-from-structure/svelte';
244
+ import UserCard from './UserCard.svelte';
245
+
246
+ let { user } = $props();
247
+ let loading = $state(true);
248
+
249
+ const userTemplate = {
250
+ name: 'Loading...',
251
+ role: 'Loading role...',
252
+ avatar: 'placeholder.jpg',
253
+ };
254
+ </script>
255
+
256
+ <Shimmer loading={loading} templateProps={{ user: userTemplate }}>
257
+ <UserCard user={user || userTemplate} />
258
+ </Shimmer>
259
+ ```
260
+
261
+ **Angular**
262
+
263
+ ```typescript
264
+ import { Component, signal } from '@angular/core';
265
+ import { ShimmerComponent } from '@shimmer-from-structure/angular';
266
+ import { UserCardComponent } from './user-card.component';
267
+
268
+ @Component({
269
+ selector: 'app-root',
270
+ standalone: true,
271
+ imports: [ShimmerComponent, UserCardComponent],
272
+ template: `
273
+ <shimmer [loading]="loading()" [templateProps]="{ user: userTemplate }">
274
+ <app-user-card [user]="user() || userTemplate" />
275
+ </shimmer>
276
+ `,
277
+ })
278
+ export class AppComponent {
279
+ loading = signal(true);
280
+ user = signal<User | null>(null);
281
+
282
+ userTemplate = {
283
+ name: 'Loading...',
284
+ role: 'Loading role...',
285
+ avatar: 'placeholder.jpg',
286
+ };
287
+ }
288
+ ```
289
+
290
+ The `templateProps` object is spread onto the first child component when loading, allowing it to render with mock data for measurement.
291
+
292
+ ## 🎨 API Reference
293
+
294
+ ### `<Shimmer>` Props
295
+
296
+ | Prop | Type | Default | Description |
297
+ | ---------------------- | ------------------------- | -------------------------- | --------------------------------------------------------- |
298
+ | `loading` | `boolean` | `true` | Whether to show shimmer effect or actual content |
299
+ | `children` | `React.ReactNode` | required | The content to render/measure |
300
+ | `shimmerColor` | `string` | `'rgba(255,255,255,0.15)'` | Color of the shimmer wave |
301
+ | `backgroundColor` | `string` | `'rgba(255,255,255,0.08)'` | Background color of shimmer blocks |
302
+ | `duration` | `number` | `1.5` | Animation duration in seconds |
303
+ | `fallbackBorderRadius` | `number` | `4` | Border radius (px) for elements with no CSS border-radius |
304
+ | `templateProps` | `Record<string, unknown>` | - | Props to inject into first child for skeleton rendering |
305
+
306
+ ### Example with All Props
307
+
308
+ **React**
309
+
310
+ ```tsx
311
+ <Shimmer
312
+ loading={isLoading}
313
+ shimmerColor="rgba(255, 255, 255, 0.2)"
314
+ backgroundColor="rgba(255, 255, 255, 0.1)"
315
+ duration={2}
316
+ fallbackBorderRadius={8}
317
+ templateProps={{
318
+ user: userTemplate,
319
+ settings: settingsTemplate,
320
+ }}
321
+ >
322
+ <MyComponent user={user} settings={settings} />
323
+ </Shimmer>
324
+ ```
325
+
326
+ **Vue**
327
+
328
+ ```vue
329
+ <Shimmer
330
+ :loading="isLoading"
331
+ shimmerColor="rgba(255, 255, 255, 0.2)"
332
+ backgroundColor="rgba(255, 255, 255, 0.1)"
333
+ :duration="2"
334
+ :fallbackBorderRadius="8"
335
+ :templateProps="{
336
+ user: userTemplate,
337
+ settings: settingsTemplate,
338
+ }"
339
+ >
340
+ <MyComponent :user="user" :settings="settings" />
341
+ </Shimmer>
342
+ ```
343
+
344
+ **Svelte**
345
+
346
+ ```svelte
347
+ <Shimmer
348
+ loading={isLoading}
349
+ shimmerColor="rgba(255, 255, 255, 0.2)"
350
+ backgroundColor="rgba(255, 255, 255, 0.1)"
351
+ duration={2}
352
+ fallbackBorderRadius={8}
353
+ templateProps={{
354
+ user: userTemplate,
355
+ settings: settingsTemplate,
356
+ }}
357
+ >
358
+ <MyComponent {user} {settings} />
359
+ </Shimmer>
360
+ ```
361
+
362
+ **Angular**
363
+
364
+ ```typescript
365
+ <shimmer
366
+ [loading]="isLoading()"
367
+ shimmerColor="rgba(255, 255, 255, 0.2)"
368
+ backgroundColor="rgba(255, 255, 255, 0.1)"
369
+ [duration]="2"
370
+ [fallbackBorderRadius]="8"
371
+ [templateProps]="{
372
+ user: userTemplate,
373
+ settings: settingsTemplate
374
+ }">
375
+ <app-my-component
376
+ [user]="user()"
377
+ [settings]="settings()" />
378
+ </shimmer>
379
+ ```
380
+
381
+ ## 🔧 How It Works
382
+
383
+ 1. **Visible Container Rendering**: When `loading={true}`, your component renders with transparent text but **visible container backgrounds**
384
+ 2. **Template Props Injection**: If `templateProps` is provided, it's spread onto the first child so dynamic components can render
385
+ 3. **DOM Measurement**: Uses `useLayoutEffect` to synchronously measure all leaf elements via `getBoundingClientRect()`
386
+ 4. **Border Radius Detection**: Automatically captures each element's computed `border-radius` from CSS
387
+ 5. **Shimmer Generation**: Creates absolutely-positioned shimmer blocks matching measured dimensions
388
+ 6. **Animation**: Applies smooth gradient animation that sweeps across each block
389
+
390
+ ### Key Features
391
+
392
+ - **Container backgrounds visible**: Unlike `opacity: 0`, we use `color: transparent` so card backgrounds/borders show during loading
393
+ - **Auto border-radius**: Circular avatars get circular shimmer blocks automatically
394
+ - **Fallback radius**: Text elements (which have `border-radius: 0`) use `fallbackBorderRadius` to avoid sharp rectangles
395
+ - **Dark-mode friendly**: Default colors use semi-transparent whites that work on any background
396
+
397
+ ## Examples
398
+
399
+ ### Dashboard with Multiple Sections
400
+
401
+ Each section can have its own independent loading state:
402
+
403
+ **React**
404
+
405
+ ```tsx
406
+ function Dashboard() {
407
+ const [loadingUser, setLoadingUser] = useState(true);
408
+ const [loadingStats, setLoadingStats] = useState(true);
409
+
410
+ return (
411
+ <>
412
+ {/* User profile section */}
413
+ <Shimmer loading={loadingUser} templateProps={{ user: userTemplate }}>
414
+ <UserProfile user={user} />
415
+ </Shimmer>
416
+
417
+ {/* Stats section - with custom colors */}
418
+ <Shimmer
419
+ loading={loadingStats}
420
+ templateProps={{ stats: statsTemplate }}
421
+ shimmerColor="rgba(20, 184, 166, 0.2)"
422
+ >
423
+ <StatsGrid stats={stats} />
424
+ </Shimmer>
425
+ </>
426
+ );
427
+ }
428
+ ```
429
+
430
+ **Vue**
431
+
432
+ ```vue
433
+ <template>
434
+ <!-- User profile section -->
435
+ <Shimmer :loading="loadingUser" :templateProps="{ user: userTemplate }">
436
+ <UserProfile :user="user" />
437
+ </Shimmer>
438
+
439
+ <!-- Stats section - with custom colors -->
440
+ <Shimmer
441
+ :loading="loadingStats"
442
+ :templateProps="{ stats: statsTemplate }"
443
+ shimmerColor="rgba(20, 184, 166, 0.2)"
444
+ >
445
+ <StatsGrid :stats="stats" />
446
+ </Shimmer>
447
+ </template>
448
+ ```
449
+
450
+ **Svelte**
451
+
452
+ ```svelte
453
+ <Shimmer loading={loadingUser} templateProps={{ user: userTemplate }}>
454
+ <UserProfile {user} />
455
+ </Shimmer>
456
+
457
+ <Shimmer
458
+ loading={loadingStats}
459
+ templateProps={{ stats: statsTemplate }}
460
+ shimmerColor="rgba(20, 184, 166, 0.2)"
461
+ >
462
+ <StatsGrid {stats} />
463
+ </Shimmer>
464
+ ```
465
+
466
+ **Angular**
467
+
468
+ ```typescript
469
+ @Component({
470
+ template: `
471
+ <!-- User profile section -->
472
+ <shimmer [loading]="loadingUser()" [templateProps]="{ user: userTemplate }">
473
+ <app-user-profile [user]="user()" />
474
+ </shimmer>
475
+
476
+ <!-- Stats section - with custom colors -->
477
+ <shimmer
478
+ [loading]="loadingStats()"
479
+ [templateProps]="{ stats: statsTemplate }"
480
+ shimmerColor="rgba(20, 184, 166, 0.2)"
481
+ >
482
+ <app-stats-grid [stats]="stats()" />
483
+ </shimmer>
484
+ `,
485
+ })
486
+ export class DashboardComponent {
487
+ loadingUser = signal(true);
488
+ loadingStats = signal(true);
489
+ // ...
490
+ }
491
+ ```
492
+
493
+ ### Transactions List
494
+
495
+ **React**
496
+
497
+ ```tsx
498
+ <Shimmer loading={loadingTransactions} templateProps={{ transactions: transactionsTemplate }}>
499
+ <TransactionsList transactions={transactions} />
500
+ </Shimmer>
501
+ ```
502
+
503
+ **Vue**
504
+
505
+ ```vue
506
+ <Shimmer :loading="loadingTransactions" :templateProps="{ transactions: transactionsTemplate }">
507
+ <TransactionsList :transactions="transactions" />
508
+ </Shimmer>
509
+ ```
510
+
511
+ **Svelte**
512
+
513
+ ```svelte
514
+ <Shimmer loading={loadingTransactions} templateProps={{ transactions: transactionsTemplate }}>
515
+ <TransactionsList {transactions} />
516
+ </Shimmer>
517
+ ```
518
+
519
+ **Angular**
520
+
521
+ ```typescript
522
+ <shimmer
523
+ [loading]="loadingTransactions()"
524
+ [templateProps]="{ transactions: transactionsTemplate }">
525
+ <app-transactions-list [transactions]="transactions()" />
526
+ </shimmer>
527
+ ```
528
+
529
+ ### Team Members Grid
530
+
531
+ **React**
532
+
533
+ ```tsx
534
+ <Shimmer loading={loadingTeam} templateProps={{ members: teamTemplate }}>
535
+ <TeamMembers members={team} />
536
+ </Shimmer>
537
+ ```
538
+
539
+ **Vue**
540
+
541
+ ```vue
542
+ <Shimmer :loading="loadingTeam" :templateProps="{ members: teamTemplate }">
543
+ <TeamMembers :members="team" />
544
+ </Shimmer>
545
+ ```
546
+
547
+ **Svelte**
548
+
549
+ ```svelte
550
+ <Shimmer loading={loadingTeam} templateProps={{ members: teamTemplate }}>
551
+ <TeamMembers members={team} />
552
+ </Shimmer>
553
+ ```
554
+
555
+ **Angular**
556
+
557
+ ```typescript
558
+ <shimmer
559
+ [loading]="loadingTeam()"
560
+ [templateProps]="{ members: teamTemplate }">
561
+ <app-team-members [members]="team()" />
562
+ </shimmer>
563
+ ```
564
+
565
+ ## 🔄 Using with React Suspense
566
+
567
+ Shimmer works seamlessly as a Suspense fallback. When used this way, `loading` is always `true` because React automatically unmounts the fallback and replaces it with the resolved component.
568
+
569
+ ### Basic Suspense Pattern
570
+
571
+ ```tsx
572
+ import { Suspense, lazy } from 'react';
573
+ import { Shimmer } from 'shimmer-from-structure';
574
+
575
+ const UserProfile = lazy(() => import('./UserProfile'));
576
+
577
+ function App() {
578
+ return (
579
+ <Suspense
580
+ fallback={
581
+ <Shimmer loading={true} templateProps={{ user: userTemplate }}>
582
+ <UserProfile />
583
+ </Shimmer>
584
+ }
585
+ >
586
+ <UserProfile userId="123" />
587
+ </Suspense>
588
+ );
589
+ }
590
+ ```
591
+
592
+ ### Why `loading={true}` is Always Set
593
+
594
+ When using Shimmer as a Suspense fallback:
595
+
596
+ 1. **Suspend**: React renders the fallback → Shimmer shows with `loading={true}`
597
+ 2. **Resolve**: React **replaces** the entire fallback with the real component
598
+ 3. The Shimmer is **unmounted**, not updated — so you never need to toggle `loading`
599
+
600
+ ### Performance Tips for Suspense
601
+
602
+ **Memoize the fallback** to prevent re-renders:
603
+
604
+ ```tsx
605
+ const ShimmerFallback = React.memo(() => (
606
+ <Shimmer loading={true} templateProps={{ user: userTemplate }}>
607
+ <UserProfile />
608
+ </Shimmer>
609
+ ));
610
+
611
+ // Usage
612
+ <Suspense fallback={<ShimmerFallback />}>
613
+ <UserProfile userId="123" />
614
+ </Suspense>;
615
+ ```
616
+
617
+ **Keep templates lightweight** — the DOM is measured synchronously via `useLayoutEffect`, so avoid complex logic in your template.
618
+
619
+ ## Global Configuration
620
+
621
+ You can set default configuration for your entire app (or specific sections) using the context/provider pattern. This is perfect for maintaining consistent themes without repeating props.
622
+
623
+ ### React (Context API)
624
+
625
+ ```tsx
626
+ import { Shimmer, ShimmerProvider } from '@shimmer-from-structure/react';
627
+
628
+ function App() {
629
+ return (
630
+ // Set global defaults
631
+ <ShimmerProvider
632
+ config={{
633
+ shimmerColor: 'rgba(56, 189, 248, 0.4)', // Blue shimmer
634
+ backgroundColor: 'rgba(56, 189, 248, 0.1)', // Blue background
635
+ duration: 2.5,
636
+ fallbackBorderRadius: 8,
637
+ }}
638
+ >
639
+ <Dashboard />
640
+ </ShimmerProvider>
641
+ );
642
+ }
643
+ ```
644
+
645
+ ### Vue (Provide/Inject)
646
+
647
+ ```vue
648
+ <!-- App.vue -->
649
+ <script setup>
650
+ import { provideShimmerConfig } from '@shimmer-from-structure/vue';
651
+
652
+ provideShimmerConfig({
653
+ shimmerColor: 'rgba(56, 189, 248, 0.4)',
654
+ backgroundColor: 'rgba(56, 189, 248, 0.1)',
655
+ duration: 2.5,
656
+ fallbackBorderRadius: 8,
657
+ });
658
+ </script>
659
+
660
+ <template>
661
+ <router-view />
662
+ </template>
663
+ ```
664
+
665
+ ### Svelte (setShimmerConfig)
666
+
667
+ ```svelte
668
+ <!-- App.svelte or any parent component -->
669
+ <script>
670
+ import { setShimmerConfig } from '@shimmer-from-structure/svelte';
671
+ import Dashboard from './Dashboard.svelte';
672
+
673
+ // Must be called at the top level during component initialization
674
+ setShimmerConfig({
675
+ shimmerColor: 'rgba(56, 189, 248, 0.4)',
676
+ backgroundColor: 'rgba(56, 189, 248, 0.1)',
677
+ duration: 2.5,
678
+ fallbackBorderRadius: 8,
679
+ });
680
+ </script>
681
+
682
+ <Dashboard />
683
+ ```
684
+
685
+ ### Angular (Dependency Injection)
686
+
687
+ ```typescript
688
+ // main.ts or bootstrapApplication
689
+ import { bootstrapApplication } from '@angular/platform-browser';
690
+ import { provideShimmerConfig } from '@shimmer-from-structure/angular';
691
+ import { AppComponent } from './app/app.component';
692
+
693
+ bootstrapApplication(AppComponent, {
694
+ providers: [
695
+ provideShimmerConfig({
696
+ shimmerColor: 'rgba(56, 189, 248, 0.4)',
697
+ backgroundColor: 'rgba(56, 189, 248, 0.1)',
698
+ duration: 2.5,
699
+ fallbackBorderRadius: 8,
700
+ }),
701
+ ],
702
+ });
703
+ ```
704
+
705
+ ---
706
+
707
+ Components inside the provider automatically inherit values. You can still override them locally:
708
+
709
+ **React**
710
+
711
+ ```tsx
712
+ // Inherits blue theme from provider
713
+ <Shimmer loading={true}><UserCard /></Shimmer>
714
+
715
+ // Overrides provider settings
716
+ <Shimmer loading={true} duration={0.5}><FastCard /></Shimmer>
717
+ ```
718
+
719
+ **Vue**
720
+
721
+ ```vue
722
+ <!-- Inherits blue theme from provider -->
723
+ <Shimmer :loading="true"><UserCard /></Shimmer>
724
+
725
+ <!-- Overrides provider settings -->
726
+ <Shimmer :loading="true" :duration="0.5"><FastCard /></Shimmer>
727
+ ```
728
+
729
+ **Svelte**
730
+
731
+ ```svelte
732
+ <!-- Inherits blue theme from provider -->
733
+ <Shimmer loading={true}><UserCard /></Shimmer>
734
+
735
+ <!-- Overrides provider settings -->
736
+ <Shimmer loading={true} duration={0.5}><FastCard /></Shimmer>
737
+ ```
738
+
739
+ **Angular**
740
+
741
+ ```typescript
742
+ <!-- Inherits blue theme from injected config -->
743
+ <shimmer [loading]="true"><app-user-card /></shimmer>
744
+
745
+ <!-- Overrides injected settings -->
746
+ <shimmer [loading]="true" [duration]="0.5"><app-fast-card /></shimmer>
747
+ ```
748
+
749
+ ### Accessing Config in Hooks/Composables
750
+
751
+ If you need to access the current configuration in your own components:
752
+
753
+ **React**
754
+
755
+ ```tsx
756
+ import { useShimmerConfig } from 'shimmer-from-structure';
757
+
758
+ function MyComponent() {
759
+ const config = useShimmerConfig();
760
+ return <div style={{ background: config.backgroundColor }}>...</div>;
761
+ }
762
+ ```
763
+
764
+ **Vue**
765
+
766
+ ```javascript
767
+ import { useShimmerConfig } from '@shimmer-from-structure/vue';
768
+
769
+ const config = useShimmerConfig();
770
+ console.log(config.value.backgroundColor);
771
+ ```
772
+
773
+ **Svelte**
774
+
775
+ ```javascript
776
+ import { getShimmerConfig } from '@shimmer-from-structure/svelte';
777
+
778
+ const config = getShimmerConfig();
779
+ console.log(config.backgroundColor);
780
+ ```
781
+
782
+ **Angular**
783
+
784
+ ```typescript
785
+ import { Component, inject } from '@angular/core';
786
+ import { injectShimmerConfig } from '@shimmer-from-structure/angular';
787
+
788
+ @Component({
789
+ selector: 'app-my-component',
790
+ template: `<div [style.background]="config.backgroundColor">...</div>`,
791
+ })
792
+ export class MyComponent {
793
+ config = injectShimmerConfig();
794
+ }
795
+ ```
796
+
797
+ ## Best Practices
798
+
799
+ ### 1. Use `templateProps` for Dynamic Data
800
+
801
+ When your component receives data via props, always provide `templateProps` with mock data that matches the expected structure.
802
+
803
+ ### 2. Match Template Structure to Real Data
804
+
805
+ Ensure your template data has the same array length and property structure as real data for accurate shimmer layout.
806
+
807
+ ### 3. Use Individual Shimmer Components
808
+
809
+ Wrap each section in its own Shimmer for independent loading states:
810
+
811
+ ```tsx
812
+ // ✅ Good - independent loading
813
+ <Shimmer loading={loadingUsers}><UserList /></Shimmer>
814
+ <Shimmer loading={loadingPosts}><PostList /></Shimmer>
815
+
816
+ // ❌ Avoid - all-or-nothing loading
817
+ <Shimmer loading={loadingUsers || loadingPosts}>
818
+ <UserList />
819
+ <PostList />
820
+ </Shimmer>
821
+ ```
822
+
823
+ ### 4. Consider Element Widths
824
+
825
+ Block elements like `<h1>`, `<p>` take full container width. If you want shimmer to match text width:
826
+
827
+ ```css
828
+ .title {
829
+ width: fit-content;
830
+ }
831
+ ```
832
+
833
+ ### 5. Provide Container Dimensions
834
+
835
+ For async components (like charts), ensure containers have explicit dimensions so shimmer has something to measure.
836
+
837
+ ## ⚡ Performance Considerations
838
+
839
+ - Measurement happens only when `loading` changes to `true`
840
+ - Uses `useLayoutEffect` for synchronous measurement (no flicker)
841
+ - Minimal re-renders - only updates when loading state or children change
842
+ - Lightweight DOM measurements using native browser APIs
843
+
844
+ - Lightweight DOM measurements using native browser APIs
845
+
846
+ ## 🛠️ Development
847
+
848
+ This is a monorepo managed with npm workspaces. Each package can be built independently:
849
+
850
+ ```bash
851
+ # Install dependencies
852
+ npm install
853
+
854
+ # Build all packages
855
+ npm run build
856
+
857
+ # Build individual packages
858
+ npm run build:core
859
+ npm run build:react
860
+ npm run build:vue
861
+ npm run build:svelte
862
+ npm run build:main
863
+
864
+ # Run tests
865
+ npm test
866
+ ```
867
+
868
+ ## 📝 License
869
+
870
+ MIT
871
+
872
+ ## 🤝 Contributing
873
+
874
+ Contributions are welcome! Please feel free to submit a Pull Request.
875
+
876
+ ## 🐛 Known Limitations
877
+
878
+ - **Async components**: Components that render asynchronously (like charts using `ResponsiveContainer`) may need explicit container dimensions
879
+ - **Zero-dimension elements**: Elements with `display: none` or zero dimensions won't be captured
880
+ - **SVG internals**: Only the outer `<svg>` element is captured, not internal paths/shapes
881
+
882
+ ## 🏗️ Monorepo Structure
883
+
884
+ This library is organized as a monorepo with four packages:
885
+
886
+ | Package | Description | Size |
887
+ | --------------------------------- | ------------------------------------------- | -------- |
888
+ | `@shimmer-from-structure/core` | Framework-agnostic DOM utilities | 1.44 kB |
889
+ | `@shimmer-from-structure/react` | React adapter | 12.84 kB |
890
+ | `@shimmer-from-structure/vue` | Vue 3 adapter | 3.89 kB |
891
+ | `@shimmer-from-structure/svelte` | Svelte adapter | 4.60 kB |
892
+ | `@shimmer-from-structure/angular` | Angular adapter | 6.83 kB |
893
+ | `shimmer-from-structure` | Main package (React backward compatibility) | 0.93 kB |
894
+
895
+ The core package contains all DOM measurement logic, while React, Vue, Svelte and Angular packages are thin wrappers that provide framework-specific APIs.
896
+
897
+ ## 🚧 Roadmap
898
+
899
+ - [x] Dynamic data support via `templateProps`
900
+ - [x] Auto border-radius detection
901
+ - [x] Container background visibility
902
+ - [x] **Vue.js adapter**
903
+ - [x] **Svelte adapter**
904
+ - [x] **Angular adapter**
905
+ - [ ] Better async component support
906
+ - [ ] Customizable shimmer direction (vertical, diagonal)
907
+ - [ ] React Native support
908
+
909
+ ---
910
+
911
+ Made with ❤️ for developers tired of maintaining skeleton screens