@styloviz/skeleton-loader 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 StyloViz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @styloviz/skeleton-loader
2
+
3
+ > Loading placeholders with wave/pulse animations and preset layouts.
4
+
5
+ Part of the **StyloViz UI Kit** — a premium Angular 21 + Tailwind CSS 4 dashboard component library. Standalone, `OnPush`, strongly typed, dark-mode ready.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @styloviz/core @styloviz/skeleton-loader
11
+ ```
12
+
13
+ Requires `@angular/core` and `@angular/common` >= 21. `@styloviz/core` is a peer dependency shared by every component. Prefer everything at once? `@styloviz/all` installs the whole free tier in one command.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { SvSkeletonLoaderComponent } from '@styloviz/skeleton-loader';
19
+
20
+ @Component({
21
+ standalone: true,
22
+ imports: [SvSkeletonLoaderComponent],
23
+ template: `
24
+ <sv-skeleton-loader preset="card" animation="wave" />
25
+ `,
26
+ })
27
+ export class DemoComponent {}
28
+ ```
29
+
30
+ ## Inputs
31
+
32
+ | Input | Type | Default | Description |
33
+ | --- | --- | --- | --- |
34
+ | `preset` | `SkeletonPreset` | `'custom'` | High-level composed preset. Set to `'custom'` (or omit) to control individual bone inputs. |
35
+ | `shape` | `SkeletonShape` | `'line'` | Shape of the skeleton bone. |
36
+ | `width` | `string` | `'100%'` | CSS width applied to the bone element. Accepts any valid CSS value: `'100%'`, `'8rem'`, `'w-1/2'` (Tailwind class). |
37
+ | `height` | `string` | `'1rem'` | CSS height applied to the bone element. |
38
+ | `rounded` | `string` | `''` | Border-radius override. Accepts Tailwind rounded-* class suffix: `'none'` `'sm'` `'md'` `'lg'` `'xl'` `'full'`. Shape defaults apply when left empty. |
39
+ | `lines` | `number` | `1` | Repeat this bone N times stacked vertically. Gaps between repeated lines are automatic. |
40
+ | `shortLastLine` | `boolean` | `true` | When `lines > 1`, make the last line shorter (like a real paragraph ending). |
41
+ | `animation` | `SkeletonAnimation` | `'wave'` | Shimmer animation style. |
42
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Overall size multiplier that scales preset bone heights. |
43
+ | `customClass` | `string` | `''` | Additional CSS classes merged onto the root wrapper. |
44
+ | `ariaLabel` | `string` | `'Loading content…'` | Accessible label announced to screen readers while content loads. |
45
+
46
+ ## Documentation
47
+
48
+ Full API reference and live demos: https://styloviz.dev/docs/skeleton-loader
49
+
50
+ ## License
51
+
52
+ MIT
@@ -0,0 +1,145 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NgClass } from '@angular/common';
4
+
5
+ // ─── Component ───────────────────────────────────────────────────────────────
6
+ /**
7
+ * SkeletonLoader — Lightweight, composable content placeholder.
8
+ *
9
+ * Two usage modes:
10
+ *
11
+ * 1. **Preset** — pass a `preset` input and get a fully composed layout
12
+ * (stat-card, table, form, dashboard, …) with a single tag.
13
+ *
14
+ * 2. **Single bone** — omit `preset` (or use `preset="custom"`) and
15
+ * control `shape`, `width`, `height`, `rounded`, `lines` individually
16
+ * to build bespoke skeletons via composition.
17
+ *
18
+ * Animation is driven purely by Tailwind utilities — no extra CSS file needed.
19
+ */
20
+ class SvSkeletonLoaderComponent {
21
+ // ── Mode ──────────────────────────────────────────────────────────────────
22
+ /**
23
+ * High-level composed preset.
24
+ * Set to `'custom'` (or omit) to control individual bone inputs.
25
+ * @default 'custom'
26
+ */
27
+ preset = input('custom', ...(ngDevMode ? [{ debugName: "preset" }] : /* istanbul ignore next */ []));
28
+ // ── Single-bone controls (used when preset === 'custom') ──────────────────
29
+ /** Shape of the skeleton bone. @default 'line' */
30
+ shape = input('line', ...(ngDevMode ? [{ debugName: "shape" }] : /* istanbul ignore next */ []));
31
+ /**
32
+ * CSS width applied to the bone element.
33
+ * Accepts any valid CSS value: `'100%'`, `'8rem'`, `'w-1/2'` (Tailwind class).
34
+ * @default '100%'
35
+ */
36
+ width = input('100%', ...(ngDevMode ? [{ debugName: "width" }] : /* istanbul ignore next */ []));
37
+ /**
38
+ * CSS height applied to the bone element.
39
+ * @default '1rem'
40
+ */
41
+ height = input('1rem', ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
42
+ /**
43
+ * Border-radius override.
44
+ * Accepts Tailwind rounded-* class suffix: `'none'` `'sm'` `'md'` `'lg'` `'xl'` `'full'`.
45
+ * Shape defaults apply when left empty.
46
+ * @default ''
47
+ */
48
+ rounded = input('', ...(ngDevMode ? [{ debugName: "rounded" }] : /* istanbul ignore next */ []));
49
+ /**
50
+ * Repeat this bone N times stacked vertically.
51
+ * Gaps between repeated lines are automatic.
52
+ * @default 1
53
+ */
54
+ lines = input(1, ...(ngDevMode ? [{ debugName: "lines" }] : /* istanbul ignore next */ []));
55
+ /**
56
+ * When `lines > 1`, make the last line shorter (like a real paragraph ending).
57
+ * @default true
58
+ */
59
+ shortLastLine = input(true, ...(ngDevMode ? [{ debugName: "shortLastLine" }] : /* istanbul ignore next */ []));
60
+ // ── Appearance ────────────────────────────────────────────────────────────
61
+ /** Shimmer animation style. @default 'wave' */
62
+ animation = input('wave', ...(ngDevMode ? [{ debugName: "animation" }] : /* istanbul ignore next */ []));
63
+ /**
64
+ * Overall size multiplier that scales preset bone heights.
65
+ * @default 'md'
66
+ */
67
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
68
+ /** Additional CSS classes merged onto the root wrapper. */
69
+ customClass = input('', ...(ngDevMode ? [{ debugName: "customClass" }] : /* istanbul ignore next */ []));
70
+ /** Accessible label announced to screen readers while content loads. @default 'Loading content…' */
71
+ ariaLabel = input('Loading content…', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
72
+ // ── Computed helpers ──────────────────────────────────────────────────────
73
+ isPreset = computed(() => this.preset() !== 'custom', ...(ngDevMode ? [{ debugName: "isPreset" }] : /* istanbul ignore next */ []));
74
+ /** Base class applied to every individual bone element. */
75
+ boneBase = computed(() => {
76
+ const anim = {
77
+ pulse: 'animate-pulse motion-reduce:animate-none',
78
+ wave: '', // wave uses a pseudo-element driven by CSS (reduced-motion handled there)
79
+ none: '',
80
+ };
81
+ return [
82
+ 'bg-gray-200 dark:bg-gray-700',
83
+ anim[this.animation()],
84
+ ].filter(Boolean).join(' ');
85
+ }, ...(ngDevMode ? [{ debugName: "boneBase" }] : /* istanbul ignore next */ []));
86
+ /** Additional class added when animation === 'wave' to enable the shimmer sweep. */
87
+ waveClass = computed(() => this.animation() === 'wave' ? 'skeleton-wave' : '', ...(ngDevMode ? [{ debugName: "waveClass" }] : /* istanbul ignore next */ []));
88
+ /** Lines array for the repeater. Last line gets a width reduction. */
89
+ lineItems = computed(() => {
90
+ const count = Math.max(1, this.lines());
91
+ const baseW = this.width();
92
+ const shortLast = this.shortLastLine();
93
+ return Array.from({ length: count }, (_, i) => ({
94
+ width: shortLast && i === count - 1 && count > 1 ? '65%' : baseW,
95
+ }));
96
+ }, ...(ngDevMode ? [{ debugName: "lineItems" }] : /* istanbul ignore next */ []));
97
+ /** Rounded class derived from shape or `rounded` override. */
98
+ roundedClass = computed(() => {
99
+ const override = this.rounded();
100
+ if (override)
101
+ return `rounded-${override}`;
102
+ switch (this.shape()) {
103
+ case 'circle': return 'rounded-full';
104
+ case 'badge': return 'rounded-full';
105
+ case 'button': return 'rounded-lg';
106
+ case 'line': return 'rounded-md';
107
+ default: return 'rounded-lg';
108
+ }
109
+ }, ...(ngDevMode ? [{ debugName: "roundedClass" }] : /* istanbul ignore next */ []));
110
+ // ── Preset line-height helper ─────────────────────────────────────────────
111
+ lineH = computed(() => {
112
+ switch (this.size()) {
113
+ case 'sm': return '0.75rem';
114
+ case 'lg': return '1.125rem';
115
+ default: return '0.875rem';
116
+ }
117
+ }, ...(ngDevMode ? [{ debugName: "lineH" }] : /* istanbul ignore next */ []));
118
+ avatarSz = computed(() => {
119
+ switch (this.size()) {
120
+ case 'sm': return '2rem';
121
+ case 'lg': return '3rem';
122
+ default: return '2.5rem';
123
+ }
124
+ }, ...(ngDevMode ? [{ debugName: "avatarSz" }] : /* istanbul ignore next */ []));
125
+ iconSz = computed(() => {
126
+ switch (this.size()) {
127
+ case 'sm': return '2rem';
128
+ case 'lg': return '3rem';
129
+ default: return '2.5rem';
130
+ }
131
+ }, ...(ngDevMode ? [{ debugName: "iconSz" }] : /* istanbul ignore next */ []));
132
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvSkeletonLoaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
133
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.13", type: SvSkeletonLoaderComponent, isStandalone: true, selector: "sv-skeleton-loader", inputs: { preset: { classPropertyName: "preset", publicName: "preset", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, rounded: { classPropertyName: "rounded", publicName: "rounded", isSignal: true, isRequired: false, transformFunction: null }, lines: { classPropertyName: "lines", publicName: "lines", isSignal: true, isRequired: false, transformFunction: null }, shortLastLine: { classPropertyName: "shortLastLine", publicName: "shortLastLine", isSignal: true, isRequired: false, transformFunction: null }, animation: { classPropertyName: "animation", publicName: "animation", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, customClass: { classPropertyName: "customClass", publicName: "customClass", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n Root wrapper \u2014 provides accessible semantics.\n aria-busy + aria-label signal to screen readers that content is loading.\n-->\n<div\n [ngClass]=\"['w-full', customClass()]\"\n aria-busy=\"true\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"status\"\n>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: text-block \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'text-block') {\n <div class=\"space-y-2.5\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-[65%] rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: paragraph \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'paragraph') {\n <div class=\"flex gap-3\">\n <div\n [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\"\n [style.height]=\"avatarSz()\"\n ></div>\n <div class=\"flex-1 space-y-2.5 pt-1\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-3/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-[55%] rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: card \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'card') {\n <div class=\"overflow-hidden rounded-xl border border-gray-200 dark:border-gray-800\">\n <!-- Image area -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-40 w-full rounded-none']\"></div>\n <div class=\"space-y-3 p-4\">\n <!-- Title -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-4 w-3/4 rounded-md']\"></div>\n <!-- Body lines -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-[70%] rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <!-- Footer -->\n <div class=\"flex items-center justify-between pt-1\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-20 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-8 w-24 rounded-lg']\"></div>\n </div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: stat-card \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'stat-card') {\n <div class=\"rounded-xl border border-t-2 border-gray-200 bg-white p-5 shadow-sm\n dark:border-gray-800 dark:bg-gray-900 space-y-4\">\n <!-- Icon + label -->\n <div class=\"flex items-start justify-between\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-28 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-10 rounded-lg']\"\n [style.width]=\"iconSz()\" [style.height]=\"iconSz()\"></div>\n </div>\n <!-- Big number -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-8 w-36 rounded-md']\"></div>\n <!-- Trend chip + label -->\n <div class=\"flex items-center gap-2\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-full']\"></div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: table-row \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'table-row') {\n <div class=\"flex items-center gap-4 px-4 py-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 flex-1 rounded-md']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-md hidden sm:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-16 rounded-full hidden md:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-20 rounded-md hidden lg:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-7 w-7 rounded-md']\"></div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: table \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'table') {\n <div class=\"overflow-hidden rounded-xl border border-gray-200 dark:border-gray-800\">\n <!-- Header -->\n <div class=\"flex items-center gap-4 border-b border-gray-200 bg-gray-50 px-4 py-3\n dark:border-gray-800 dark:bg-gray-800/50\">\n <div [ngClass]=\"[boneBase(), 'h-3 w-4 rounded opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 flex-1 rounded opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-24 rounded hidden sm:block opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-16 rounded hidden md:block opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-20 rounded hidden lg:block opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-7 rounded opacity-50']\"></div>\n </div>\n <!-- Rows -->\n <div class=\"divide-y divide-gray-100 dark:divide-gray-800\">\n @for (_ of [0,1,2,3,4]; track $index) {\n <div class=\"flex items-center gap-4 px-4 py-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 flex-1 rounded-md']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-md hidden sm:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-16 rounded-full hidden md:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-20 rounded-md hidden lg:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-7 w-7 rounded-md']\"></div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: list-item \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'list-item') {\n <div class=\"flex items-center gap-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div class=\"flex flex-1 flex-col gap-2 min-w-0\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-2/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-3/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 shrink-0 rounded-full']\"></div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: list \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'list') {\n <div class=\"divide-y divide-gray-100 dark:divide-gray-800\">\n @for (_ of [0,1,2,3,4]; track $index) {\n <div class=\"flex items-center gap-3 py-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div class=\"flex flex-1 flex-col gap-2 min-w-0\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-2/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-3/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 shrink-0 rounded-full']\"></div>\n </div>\n }\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: profile-header \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'profile-header') {\n <div class=\"flex flex-col items-center gap-4 py-6 sm:flex-row sm:items-start sm:gap-6\">\n <!-- Large avatar -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n style=\"width: 5rem; height: 5rem;\"></div>\n <div class=\"flex-1 space-y-2.5 text-center sm:text-left\">\n <!-- Name -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'mx-auto h-5 w-48 rounded-md sm:mx-0']\"></div>\n <!-- Role -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'mx-auto h-3.5 w-32 rounded-full sm:mx-0']\"></div>\n <!-- Meta chips -->\n <div class=\"flex justify-center gap-2 pt-1 sm:justify-start\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-20 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-24 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-16 rounded-full']\"></div>\n </div>\n </div>\n <!-- Action buttons -->\n <div class=\"flex gap-2 shrink-0\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-9 w-24 rounded-lg']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-9 w-9 rounded-lg']\"></div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: form \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'form') {\n <div class=\"space-y-5\">\n @for (_ of [0,1,2]; track $index) {\n <div class=\"space-y-1.5\">\n <!-- Label -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3 w-24 rounded-full']\"></div>\n <!-- Input field -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-full rounded-lg']\"></div>\n </div>\n }\n <!-- Submit button -->\n <div class=\"flex justify-end pt-2\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-32 rounded-lg']\"></div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: dashboard \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'dashboard') {\n <div class=\"space-y-6\">\n <!-- Stat cards row -->\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n @for (_ of [0,1,2]; track $index) {\n <div class=\"rounded-xl border border-t-2 border-gray-200 bg-white p-5 shadow-sm\n dark:border-gray-800 dark:bg-gray-900 space-y-4\">\n <div class=\"flex items-start justify-between\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-28 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-10 rounded-lg']\"></div>\n </div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-8 w-36 rounded-md']\"></div>\n <div class=\"flex items-center gap-2\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-full']\"></div>\n </div>\n </div>\n }\n </div>\n <!-- Chart placeholder -->\n <div class=\"rounded-xl border border-gray-200 bg-white p-5 shadow-sm\n dark:border-gray-800 dark:bg-gray-900 space-y-4\">\n <div class=\"flex items-center justify-between\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-4 w-40 rounded-md']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-7 w-24 rounded-lg']\"></div>\n </div>\n <!-- Y-axis labels + bars -->\n <div class=\"flex items-end gap-2 pt-2\" style=\"height: 10rem;\">\n <div class=\"flex w-8 shrink-0 flex-col justify-between py-1\">\n @for (_ of [0,1,2,3,4]; track $index) {\n <div [ngClass]=\"[boneBase(), 'h-2.5 w-6 rounded opacity-50']\"></div>\n }\n </div>\n <div class=\"flex flex-1 items-end gap-1.5 h-full\">\n @for (h of [55,72,48,88,65,94,70,58,82,76,90,68]; track $index) {\n <div\n [ngClass]=\"[boneBase(), waveClass(), 'flex-1 rounded-t-sm']\"\n [style.height.%]=\"h\"\n ></div>\n }\n </div>\n </div>\n <!-- X-axis labels -->\n <div class=\"flex justify-around pl-8\">\n @for (_ of [0,1,2,3,4,5,6,7,8,9,10,11]; track $index) {\n <div [ngClass]=\"[boneBase(), 'h-2.5 w-4 rounded opacity-40']\"></div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 CUSTOM / single-bone \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'custom') {\n <div\n class=\"flex flex-col\"\n [class.gap-2]=\"lines() > 1\"\n >\n @for (item of lineItems(); track $index) {\n <div\n [ngClass]=\"[boneBase(), waveClass(), roundedClass()]\"\n [style.width]=\"item.width\"\n [style.height]=\"height()\"\n ></div>\n }\n </div>\n }\n\n</div>\n\n<!--\n Wave shimmer \u2014 a pure CSS technique using an overlay gradient that sweeps\n left-to-right. Implemented as a global @layer utility so no extra .scss is needed.\n Tailwind's @layer utilities picks this up at build time.\n-->\n", styles: [".skeleton-wave{position:relative;overflow:hidden}.skeleton-wave:after{content:\"\";position:absolute;inset:0;background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.35) 50%,transparent 100%);background-size:200% 100%;animation:sk-wave 1.6s ease-in-out infinite}:where(.dark) .skeleton-wave:after{background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.06) 50%,transparent 100%);background-size:200% 100%}@keyframes sk-wave{0%{background-position:200% 0}to{background-position:-200% 0}}@media(prefers-reduced-motion:reduce){.skeleton-wave:after{animation:none}}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
134
+ }
135
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: SvSkeletonLoaderComponent, decorators: [{
136
+ type: Component,
137
+ args: [{ selector: 'sv-skeleton-loader', standalone: true, imports: [NgClass], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n Root wrapper \u2014 provides accessible semantics.\n aria-busy + aria-label signal to screen readers that content is loading.\n-->\n<div\n [ngClass]=\"['w-full', customClass()]\"\n aria-busy=\"true\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"status\"\n>\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: text-block \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'text-block') {\n <div class=\"space-y-2.5\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-[65%] rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: paragraph \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'paragraph') {\n <div class=\"flex gap-3\">\n <div\n [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\"\n [style.height]=\"avatarSz()\"\n ></div>\n <div class=\"flex-1 space-y-2.5 pt-1\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-3/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-[55%] rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: card \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'card') {\n <div class=\"overflow-hidden rounded-xl border border-gray-200 dark:border-gray-800\">\n <!-- Image area -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-40 w-full rounded-none']\"></div>\n <div class=\"space-y-3 p-4\">\n <!-- Title -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-4 w-3/4 rounded-md']\"></div>\n <!-- Body lines -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-[70%] rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <!-- Footer -->\n <div class=\"flex items-center justify-between pt-1\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-20 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-8 w-24 rounded-lg']\"></div>\n </div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: stat-card \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'stat-card') {\n <div class=\"rounded-xl border border-t-2 border-gray-200 bg-white p-5 shadow-sm\n dark:border-gray-800 dark:bg-gray-900 space-y-4\">\n <!-- Icon + label -->\n <div class=\"flex items-start justify-between\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-28 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-10 rounded-lg']\"\n [style.width]=\"iconSz()\" [style.height]=\"iconSz()\"></div>\n </div>\n <!-- Big number -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-8 w-36 rounded-md']\"></div>\n <!-- Trend chip + label -->\n <div class=\"flex items-center gap-2\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-full']\"></div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: table-row \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'table-row') {\n <div class=\"flex items-center gap-4 px-4 py-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 flex-1 rounded-md']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-md hidden sm:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-16 rounded-full hidden md:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-20 rounded-md hidden lg:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-7 w-7 rounded-md']\"></div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: table \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'table') {\n <div class=\"overflow-hidden rounded-xl border border-gray-200 dark:border-gray-800\">\n <!-- Header -->\n <div class=\"flex items-center gap-4 border-b border-gray-200 bg-gray-50 px-4 py-3\n dark:border-gray-800 dark:bg-gray-800/50\">\n <div [ngClass]=\"[boneBase(), 'h-3 w-4 rounded opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 flex-1 rounded opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-24 rounded hidden sm:block opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-16 rounded hidden md:block opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-20 rounded hidden lg:block opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-7 rounded opacity-50']\"></div>\n </div>\n <!-- Rows -->\n <div class=\"divide-y divide-gray-100 dark:divide-gray-800\">\n @for (_ of [0,1,2,3,4]; track $index) {\n <div class=\"flex items-center gap-4 px-4 py-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 flex-1 rounded-md']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-md hidden sm:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-16 rounded-full hidden md:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-20 rounded-md hidden lg:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-7 w-7 rounded-md']\"></div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: list-item \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'list-item') {\n <div class=\"flex items-center gap-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div class=\"flex flex-1 flex-col gap-2 min-w-0\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-2/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-3/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 shrink-0 rounded-full']\"></div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: list \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'list') {\n <div class=\"divide-y divide-gray-100 dark:divide-gray-800\">\n @for (_ of [0,1,2,3,4]; track $index) {\n <div class=\"flex items-center gap-3 py-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div class=\"flex flex-1 flex-col gap-2 min-w-0\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-2/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-3/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 shrink-0 rounded-full']\"></div>\n </div>\n }\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: profile-header \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'profile-header') {\n <div class=\"flex flex-col items-center gap-4 py-6 sm:flex-row sm:items-start sm:gap-6\">\n <!-- Large avatar -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n style=\"width: 5rem; height: 5rem;\"></div>\n <div class=\"flex-1 space-y-2.5 text-center sm:text-left\">\n <!-- Name -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'mx-auto h-5 w-48 rounded-md sm:mx-0']\"></div>\n <!-- Role -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'mx-auto h-3.5 w-32 rounded-full sm:mx-0']\"></div>\n <!-- Meta chips -->\n <div class=\"flex justify-center gap-2 pt-1 sm:justify-start\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-20 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-24 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-16 rounded-full']\"></div>\n </div>\n </div>\n <!-- Action buttons -->\n <div class=\"flex gap-2 shrink-0\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-9 w-24 rounded-lg']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-9 w-9 rounded-lg']\"></div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: form \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'form') {\n <div class=\"space-y-5\">\n @for (_ of [0,1,2]; track $index) {\n <div class=\"space-y-1.5\">\n <!-- Label -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3 w-24 rounded-full']\"></div>\n <!-- Input field -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-full rounded-lg']\"></div>\n </div>\n }\n <!-- Submit button -->\n <div class=\"flex justify-end pt-2\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-32 rounded-lg']\"></div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 PRESET: dashboard \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'dashboard') {\n <div class=\"space-y-6\">\n <!-- Stat cards row -->\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n @for (_ of [0,1,2]; track $index) {\n <div class=\"rounded-xl border border-t-2 border-gray-200 bg-white p-5 shadow-sm\n dark:border-gray-800 dark:bg-gray-900 space-y-4\">\n <div class=\"flex items-start justify-between\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-28 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-10 rounded-lg']\"></div>\n </div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-8 w-36 rounded-md']\"></div>\n <div class=\"flex items-center gap-2\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-full']\"></div>\n </div>\n </div>\n }\n </div>\n <!-- Chart placeholder -->\n <div class=\"rounded-xl border border-gray-200 bg-white p-5 shadow-sm\n dark:border-gray-800 dark:bg-gray-900 space-y-4\">\n <div class=\"flex items-center justify-between\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-4 w-40 rounded-md']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-7 w-24 rounded-lg']\"></div>\n </div>\n <!-- Y-axis labels + bars -->\n <div class=\"flex items-end gap-2 pt-2\" style=\"height: 10rem;\">\n <div class=\"flex w-8 shrink-0 flex-col justify-between py-1\">\n @for (_ of [0,1,2,3,4]; track $index) {\n <div [ngClass]=\"[boneBase(), 'h-2.5 w-6 rounded opacity-50']\"></div>\n }\n </div>\n <div class=\"flex flex-1 items-end gap-1.5 h-full\">\n @for (h of [55,72,48,88,65,94,70,58,82,76,90,68]; track $index) {\n <div\n [ngClass]=\"[boneBase(), waveClass(), 'flex-1 rounded-t-sm']\"\n [style.height.%]=\"h\"\n ></div>\n }\n </div>\n </div>\n <!-- X-axis labels -->\n <div class=\"flex justify-around pl-8\">\n @for (_ of [0,1,2,3,4,5,6,7,8,9,10,11]; track $index) {\n <div [ngClass]=\"[boneBase(), 'h-2.5 w-4 rounded opacity-40']\"></div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 CUSTOM / single-bone \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 -->\n @if (preset() === 'custom') {\n <div\n class=\"flex flex-col\"\n [class.gap-2]=\"lines() > 1\"\n >\n @for (item of lineItems(); track $index) {\n <div\n [ngClass]=\"[boneBase(), waveClass(), roundedClass()]\"\n [style.width]=\"item.width\"\n [style.height]=\"height()\"\n ></div>\n }\n </div>\n }\n\n</div>\n\n<!--\n Wave shimmer \u2014 a pure CSS technique using an overlay gradient that sweeps\n left-to-right. Implemented as a global @layer utility so no extra .scss is needed.\n Tailwind's @layer utilities picks this up at build time.\n-->\n", styles: [".skeleton-wave{position:relative;overflow:hidden}.skeleton-wave:after{content:\"\";position:absolute;inset:0;background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.35) 50%,transparent 100%);background-size:200% 100%;animation:sk-wave 1.6s ease-in-out infinite}:where(.dark) .skeleton-wave:after{background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.06) 50%,transparent 100%);background-size:200% 100%}@keyframes sk-wave{0%{background-position:200% 0}to{background-position:-200% 0}}@media(prefers-reduced-motion:reduce){.skeleton-wave:after{animation:none}}\n"] }]
138
+ }], propDecorators: { preset: [{ type: i0.Input, args: [{ isSignal: true, alias: "preset", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], rounded: [{ type: i0.Input, args: [{ isSignal: true, alias: "rounded", required: false }] }], lines: [{ type: i0.Input, args: [{ isSignal: true, alias: "lines", required: false }] }], shortLastLine: [{ type: i0.Input, args: [{ isSignal: true, alias: "shortLastLine", required: false }] }], animation: [{ type: i0.Input, args: [{ isSignal: true, alias: "animation", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], customClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "customClass", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
139
+
140
+ /**
141
+ * Generated bundle index. Do not edit.
142
+ */
143
+
144
+ export { SvSkeletonLoaderComponent };
145
+ //# sourceMappingURL=styloviz-skeleton-loader.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styloviz-skeleton-loader.mjs","sources":["../../../../projects/skeleton-loader/src/lib/skeleton-loader.component.ts","../../../../projects/skeleton-loader/src/lib/skeleton-loader.component.html","../../../../projects/skeleton-loader/src/styloviz-skeleton-loader.ts"],"sourcesContent":["import {\n Component,\n input,\n computed,\n ChangeDetectionStrategy,\n} from '@angular/core';\nimport { NgClass } from '@angular/common';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\n/**\n * Primitive shape rendered by a single skeleton bone.\n * - `line` — full-width text line (variable height)\n * - `circle` — circular avatar / icon placeholder\n * - `rect` — arbitrary rectangle (image, card, banner)\n * - `badge` — small pill-shaped inline chip\n * - `button` — rounded-lg rectangle mimicking a CTA\n */\nexport type SkeletonShape = 'line' | 'circle' | 'rect' | 'badge' | 'button';\n\n/**\n * Animation style for the shimmer effect.\n * - `pulse` — fade in/out (CSS `animate-pulse`)\n * - `wave` — left-to-right sweep shimmer\n * - `none` — static, no animation (reduced-motion-friendly)\n */\nexport type SkeletonAnimation = 'pulse' | 'wave' | 'none';\n\n/**\n * High-level preset that renders a complete composed skeleton layout.\n * When set, all individual bone inputs are ignored.\n *\n * - `text-block` — 3–4 lines of text\n * - `paragraph` — avatar + multi-line text block\n * - `card` — image placeholder + title + 2 body lines\n * - `stat-card` — icon + big number + label + trend chip\n * - `table-row` — one full table row (avatar + 4 cells)\n * - `table` — 5 stacked table-rows\n * - `list-item` — avatar + two text lines side-by-side\n * - `list` — 5 stacked list-items\n * - `profile-header` — large avatar + name/role text stack\n * - `form` — label + input × 3 + button\n * - `dashboard` — 3 stat-cards + a chart placeholder\n * - `custom` — fall-through: renders the single bone inputs\n */\nexport type SkeletonPreset =\n | 'text-block'\n | 'paragraph'\n | 'card'\n | 'stat-card'\n | 'table-row'\n | 'table'\n | 'list-item'\n | 'list'\n | 'profile-header'\n | 'form'\n | 'dashboard'\n | 'custom';\n\n// ─── Component ───────────────────────────────────────────────────────────────\n\n/**\n * SkeletonLoader — Lightweight, composable content placeholder.\n *\n * Two usage modes:\n *\n * 1. **Preset** — pass a `preset` input and get a fully composed layout\n * (stat-card, table, form, dashboard, …) with a single tag.\n *\n * 2. **Single bone** — omit `preset` (or use `preset=\"custom\"`) and\n * control `shape`, `width`, `height`, `rounded`, `lines` individually\n * to build bespoke skeletons via composition.\n *\n * Animation is driven purely by Tailwind utilities — no extra CSS file needed.\n */\n@Component({\n selector: 'sv-skeleton-loader',\n standalone: true,\n imports: [NgClass],\n templateUrl: './skeleton-loader.component.html',\n styleUrl: './skeleton-loader.component.css',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SvSkeletonLoaderComponent {\n // ── Mode ──────────────────────────────────────────────────────────────────\n\n /**\n * High-level composed preset.\n * Set to `'custom'` (or omit) to control individual bone inputs.\n * @default 'custom'\n */\n preset = input<SkeletonPreset>('custom');\n\n // ── Single-bone controls (used when preset === 'custom') ──────────────────\n\n /** Shape of the skeleton bone. @default 'line' */\n shape = input<SkeletonShape>('line');\n\n /**\n * CSS width applied to the bone element.\n * Accepts any valid CSS value: `'100%'`, `'8rem'`, `'w-1/2'` (Tailwind class).\n * @default '100%'\n */\n width = input<string>('100%');\n\n /**\n * CSS height applied to the bone element.\n * @default '1rem'\n */\n height = input<string>('1rem');\n\n /**\n * Border-radius override.\n * Accepts Tailwind rounded-* class suffix: `'none'` `'sm'` `'md'` `'lg'` `'xl'` `'full'`.\n * Shape defaults apply when left empty.\n * @default ''\n */\n rounded = input<string>('');\n\n /**\n * Repeat this bone N times stacked vertically.\n * Gaps between repeated lines are automatic.\n * @default 1\n */\n lines = input<number>(1);\n\n /**\n * When `lines > 1`, make the last line shorter (like a real paragraph ending).\n * @default true\n */\n shortLastLine = input<boolean>(true);\n\n // ── Appearance ────────────────────────────────────────────────────────────\n\n /** Shimmer animation style. @default 'wave' */\n animation = input<SkeletonAnimation>('wave');\n\n /**\n * Overall size multiplier that scales preset bone heights.\n * @default 'md'\n */\n size = input<'sm' | 'md' | 'lg'>('md');\n\n /** Additional CSS classes merged onto the root wrapper. */\n customClass = input<string>('');\n\n /** Accessible label announced to screen readers while content loads. @default 'Loading content…' */\n ariaLabel = input<string>('Loading content…');\n\n // ── Computed helpers ──────────────────────────────────────────────────────\n\n isPreset = computed(() => this.preset() !== 'custom');\n\n /** Base class applied to every individual bone element. */\n boneBase = computed<string>(() => {\n const anim: Record<SkeletonAnimation, string> = {\n pulse: 'animate-pulse motion-reduce:animate-none',\n wave: '', // wave uses a pseudo-element driven by CSS (reduced-motion handled there)\n none: '',\n };\n return [\n 'bg-gray-200 dark:bg-gray-700',\n anim[this.animation()],\n ].filter(Boolean).join(' ');\n });\n\n /** Additional class added when animation === 'wave' to enable the shimmer sweep. */\n waveClass = computed<string>(() =>\n this.animation() === 'wave' ? 'skeleton-wave' : ''\n );\n\n /** Lines array for the repeater. Last line gets a width reduction. */\n lineItems = computed<{ width: string }[]>(() => {\n const count = Math.max(1, this.lines());\n const baseW = this.width();\n const shortLast = this.shortLastLine();\n return Array.from({ length: count }, (_, i) => ({\n width: shortLast && i === count - 1 && count > 1 ? '65%' : baseW,\n }));\n });\n\n /** Rounded class derived from shape or `rounded` override. */\n roundedClass = computed<string>(() => {\n const override = this.rounded();\n if (override) return `rounded-${override}`;\n switch (this.shape()) {\n case 'circle': return 'rounded-full';\n case 'badge': return 'rounded-full';\n case 'button': return 'rounded-lg';\n case 'line': return 'rounded-md';\n default: return 'rounded-lg';\n }\n });\n\n // ── Preset line-height helper ─────────────────────────────────────────────\n\n lineH = computed<string>(() => {\n switch (this.size()) {\n case 'sm': return '0.75rem';\n case 'lg': return '1.125rem';\n default: return '0.875rem';\n }\n });\n\n avatarSz = computed<string>(() => {\n switch (this.size()) {\n case 'sm': return '2rem';\n case 'lg': return '3rem';\n default: return '2.5rem';\n }\n });\n\n iconSz = computed<string>(() => {\n switch (this.size()) {\n case 'sm': return '2rem';\n case 'lg': return '3rem';\n default: return '2.5rem';\n }\n });\n}\n","<!--\n Root wrapper — provides accessible semantics.\n aria-busy + aria-label signal to screen readers that content is loading.\n-->\n<div\n [ngClass]=\"['w-full', customClass()]\"\n aria-busy=\"true\"\n [attr.aria-label]=\"ariaLabel()\"\n role=\"status\"\n>\n\n <!-- ════════════════════ PRESET: text-block ════════════════════════════ -->\n @if (preset() === 'text-block') {\n <div class=\"space-y-2.5\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-[65%] rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n }\n\n <!-- ════════════════════ PRESET: paragraph ═════════════════════════════ -->\n @if (preset() === 'paragraph') {\n <div class=\"flex gap-3\">\n <div\n [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\"\n [style.height]=\"avatarSz()\"\n ></div>\n <div class=\"flex-1 space-y-2.5 pt-1\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-3/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-[55%] rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n </div>\n }\n\n <!-- ════════════════════ PRESET: card ══════════════════════════════════ -->\n @if (preset() === 'card') {\n <div class=\"overflow-hidden rounded-xl border border-gray-200 dark:border-gray-800\">\n <!-- Image area -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-40 w-full rounded-none']\"></div>\n <div class=\"space-y-3 p-4\">\n <!-- Title -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-4 w-3/4 rounded-md']\"></div>\n <!-- Body lines -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-full rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-[70%] rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <!-- Footer -->\n <div class=\"flex items-center justify-between pt-1\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-20 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-8 w-24 rounded-lg']\"></div>\n </div>\n </div>\n </div>\n }\n\n <!-- ════════════════════ PRESET: stat-card ═════════════════════════════ -->\n @if (preset() === 'stat-card') {\n <div class=\"rounded-xl border border-t-2 border-gray-200 bg-white p-5 shadow-sm\n dark:border-gray-800 dark:bg-gray-900 space-y-4\">\n <!-- Icon + label -->\n <div class=\"flex items-start justify-between\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-28 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-10 rounded-lg']\"\n [style.width]=\"iconSz()\" [style.height]=\"iconSz()\"></div>\n </div>\n <!-- Big number -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-8 w-36 rounded-md']\"></div>\n <!-- Trend chip + label -->\n <div class=\"flex items-center gap-2\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-full']\"></div>\n </div>\n </div>\n }\n\n <!-- ════════════════════ PRESET: table-row ═════════════════════════════ -->\n @if (preset() === 'table-row') {\n <div class=\"flex items-center gap-4 px-4 py-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 flex-1 rounded-md']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-md hidden sm:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-16 rounded-full hidden md:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-20 rounded-md hidden lg:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-7 w-7 rounded-md']\"></div>\n </div>\n }\n\n <!-- ════════════════════ PRESET: table ════════════════════════════════ -->\n @if (preset() === 'table') {\n <div class=\"overflow-hidden rounded-xl border border-gray-200 dark:border-gray-800\">\n <!-- Header -->\n <div class=\"flex items-center gap-4 border-b border-gray-200 bg-gray-50 px-4 py-3\n dark:border-gray-800 dark:bg-gray-800/50\">\n <div [ngClass]=\"[boneBase(), 'h-3 w-4 rounded opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 flex-1 rounded opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-24 rounded hidden sm:block opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-16 rounded hidden md:block opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-20 rounded hidden lg:block opacity-50']\"></div>\n <div [ngClass]=\"[boneBase(), 'h-3 w-7 rounded opacity-50']\"></div>\n </div>\n <!-- Rows -->\n <div class=\"divide-y divide-gray-100 dark:divide-gray-800\">\n @for (_ of [0,1,2,3,4]; track $index) {\n <div class=\"flex items-center gap-4 px-4 py-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 flex-1 rounded-md']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-md hidden sm:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-16 rounded-full hidden md:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-20 rounded-md hidden lg:block']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-7 w-7 rounded-md']\"></div>\n </div>\n }\n </div>\n </div>\n }\n\n <!-- ════════════════════ PRESET: list-item ═════════════════════════════ -->\n @if (preset() === 'list-item') {\n <div class=\"flex items-center gap-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div class=\"flex flex-1 flex-col gap-2 min-w-0\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-2/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-3/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 shrink-0 rounded-full']\"></div>\n </div>\n }\n\n <!-- ════════════════════ PRESET: list ══════════════════════════════════ -->\n @if (preset() === 'list') {\n <div class=\"divide-y divide-gray-100 dark:divide-gray-800\">\n @for (_ of [0,1,2,3,4]; track $index) {\n <div class=\"flex items-center gap-3 py-3\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n [style.width]=\"avatarSz()\" [style.height]=\"avatarSz()\"></div>\n <div class=\"flex flex-1 flex-col gap-2 min-w-0\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-2/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'w-3/5 rounded-md']\"\n [style.height]=\"lineH()\"></div>\n </div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 shrink-0 rounded-full']\"></div>\n </div>\n }\n </div>\n }\n\n <!-- ════════════════════ PRESET: profile-header ════════════════════════ -->\n @if (preset() === 'profile-header') {\n <div class=\"flex flex-col items-center gap-4 py-6 sm:flex-row sm:items-start sm:gap-6\">\n <!-- Large avatar -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'shrink-0 rounded-full']\"\n style=\"width: 5rem; height: 5rem;\"></div>\n <div class=\"flex-1 space-y-2.5 text-center sm:text-left\">\n <!-- Name -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'mx-auto h-5 w-48 rounded-md sm:mx-0']\"></div>\n <!-- Role -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'mx-auto h-3.5 w-32 rounded-full sm:mx-0']\"></div>\n <!-- Meta chips -->\n <div class=\"flex justify-center gap-2 pt-1 sm:justify-start\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-20 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-24 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-6 w-16 rounded-full']\"></div>\n </div>\n </div>\n <!-- Action buttons -->\n <div class=\"flex gap-2 shrink-0\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-9 w-24 rounded-lg']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-9 w-9 rounded-lg']\"></div>\n </div>\n </div>\n }\n\n <!-- ════════════════════ PRESET: form ══════════════════════════════════ -->\n @if (preset() === 'form') {\n <div class=\"space-y-5\">\n @for (_ of [0,1,2]; track $index) {\n <div class=\"space-y-1.5\">\n <!-- Label -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3 w-24 rounded-full']\"></div>\n <!-- Input field -->\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-full rounded-lg']\"></div>\n </div>\n }\n <!-- Submit button -->\n <div class=\"flex justify-end pt-2\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-32 rounded-lg']\"></div>\n </div>\n </div>\n }\n\n <!-- ════════════════════ PRESET: dashboard ══════════════════════════════ -->\n @if (preset() === 'dashboard') {\n <div class=\"space-y-6\">\n <!-- Stat cards row -->\n <div class=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n @for (_ of [0,1,2]; track $index) {\n <div class=\"rounded-xl border border-t-2 border-gray-200 bg-white p-5 shadow-sm\n dark:border-gray-800 dark:bg-gray-900 space-y-4\">\n <div class=\"flex items-start justify-between\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-28 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-10 w-10 rounded-lg']\"></div>\n </div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-8 w-36 rounded-md']\"></div>\n <div class=\"flex items-center gap-2\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-5 w-14 rounded-full']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-3.5 w-24 rounded-full']\"></div>\n </div>\n </div>\n }\n </div>\n <!-- Chart placeholder -->\n <div class=\"rounded-xl border border-gray-200 bg-white p-5 shadow-sm\n dark:border-gray-800 dark:bg-gray-900 space-y-4\">\n <div class=\"flex items-center justify-between\">\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-4 w-40 rounded-md']\"></div>\n <div [ngClass]=\"[boneBase(), waveClass(), 'h-7 w-24 rounded-lg']\"></div>\n </div>\n <!-- Y-axis labels + bars -->\n <div class=\"flex items-end gap-2 pt-2\" style=\"height: 10rem;\">\n <div class=\"flex w-8 shrink-0 flex-col justify-between py-1\">\n @for (_ of [0,1,2,3,4]; track $index) {\n <div [ngClass]=\"[boneBase(), 'h-2.5 w-6 rounded opacity-50']\"></div>\n }\n </div>\n <div class=\"flex flex-1 items-end gap-1.5 h-full\">\n @for (h of [55,72,48,88,65,94,70,58,82,76,90,68]; track $index) {\n <div\n [ngClass]=\"[boneBase(), waveClass(), 'flex-1 rounded-t-sm']\"\n [style.height.%]=\"h\"\n ></div>\n }\n </div>\n </div>\n <!-- X-axis labels -->\n <div class=\"flex justify-around pl-8\">\n @for (_ of [0,1,2,3,4,5,6,7,8,9,10,11]; track $index) {\n <div [ngClass]=\"[boneBase(), 'h-2.5 w-4 rounded opacity-40']\"></div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- ════════════════════ CUSTOM / single-bone ═══════════════════════════ -->\n @if (preset() === 'custom') {\n <div\n class=\"flex flex-col\"\n [class.gap-2]=\"lines() > 1\"\n >\n @for (item of lineItems(); track $index) {\n <div\n [ngClass]=\"[boneBase(), waveClass(), roundedClass()]\"\n [style.width]=\"item.width\"\n [style.height]=\"height()\"\n ></div>\n }\n </div>\n }\n\n</div>\n\n<!--\n Wave shimmer — a pure CSS technique using an overlay gradient that sweeps\n left-to-right. Implemented as a global @layer utility so no extra .scss is needed.\n Tailwind's @layer utilities picks this up at build time.\n-->\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@styloviz/skeleton-loader",
3
+ "version": "0.1.1",
4
+ "description": "Loading placeholders with wave/pulse animations and preset layouts.",
5
+ "license": "MIT",
6
+ "author": "sazzad-bs23",
7
+ "homepage": "https://styloviz.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/rhossain/angular-tailwind-dashboard-components.git",
11
+ "directory": "projects/skeleton-loader"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/rhossain/angular-tailwind-dashboard-components/issues"
15
+ },
16
+ "peerDependencies": {
17
+ "@angular/common": ">=21.0.0",
18
+ "@angular/core": ">=21.0.0"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "engines": {
24
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
25
+ },
26
+ "sideEffects": false,
27
+ "module": "fesm2022/styloviz-skeleton-loader.mjs",
28
+ "typings": "types/styloviz-skeleton-loader.d.ts",
29
+ "exports": {
30
+ "./package.json": {
31
+ "default": "./package.json"
32
+ },
33
+ ".": {
34
+ "types": "./types/styloviz-skeleton-loader.d.ts",
35
+ "default": "./fesm2022/styloviz-skeleton-loader.mjs"
36
+ }
37
+ },
38
+ "type": "module",
39
+ "dependencies": {
40
+ "tslib": "^2.3.0"
41
+ }
42
+ }
@@ -0,0 +1,119 @@
1
+ import * as _angular_core from '@angular/core';
2
+
3
+ /**
4
+ * Primitive shape rendered by a single skeleton bone.
5
+ * - `line` — full-width text line (variable height)
6
+ * - `circle` — circular avatar / icon placeholder
7
+ * - `rect` — arbitrary rectangle (image, card, banner)
8
+ * - `badge` — small pill-shaped inline chip
9
+ * - `button` — rounded-lg rectangle mimicking a CTA
10
+ */
11
+ type SkeletonShape = 'line' | 'circle' | 'rect' | 'badge' | 'button';
12
+ /**
13
+ * Animation style for the shimmer effect.
14
+ * - `pulse` — fade in/out (CSS `animate-pulse`)
15
+ * - `wave` — left-to-right sweep shimmer
16
+ * - `none` — static, no animation (reduced-motion-friendly)
17
+ */
18
+ type SkeletonAnimation = 'pulse' | 'wave' | 'none';
19
+ /**
20
+ * High-level preset that renders a complete composed skeleton layout.
21
+ * When set, all individual bone inputs are ignored.
22
+ *
23
+ * - `text-block` — 3–4 lines of text
24
+ * - `paragraph` — avatar + multi-line text block
25
+ * - `card` — image placeholder + title + 2 body lines
26
+ * - `stat-card` — icon + big number + label + trend chip
27
+ * - `table-row` — one full table row (avatar + 4 cells)
28
+ * - `table` — 5 stacked table-rows
29
+ * - `list-item` — avatar + two text lines side-by-side
30
+ * - `list` — 5 stacked list-items
31
+ * - `profile-header` — large avatar + name/role text stack
32
+ * - `form` — label + input × 3 + button
33
+ * - `dashboard` — 3 stat-cards + a chart placeholder
34
+ * - `custom` — fall-through: renders the single bone inputs
35
+ */
36
+ type SkeletonPreset = 'text-block' | 'paragraph' | 'card' | 'stat-card' | 'table-row' | 'table' | 'list-item' | 'list' | 'profile-header' | 'form' | 'dashboard' | 'custom';
37
+ /**
38
+ * SkeletonLoader — Lightweight, composable content placeholder.
39
+ *
40
+ * Two usage modes:
41
+ *
42
+ * 1. **Preset** — pass a `preset` input and get a fully composed layout
43
+ * (stat-card, table, form, dashboard, …) with a single tag.
44
+ *
45
+ * 2. **Single bone** — omit `preset` (or use `preset="custom"`) and
46
+ * control `shape`, `width`, `height`, `rounded`, `lines` individually
47
+ * to build bespoke skeletons via composition.
48
+ *
49
+ * Animation is driven purely by Tailwind utilities — no extra CSS file needed.
50
+ */
51
+ declare class SvSkeletonLoaderComponent {
52
+ /**
53
+ * High-level composed preset.
54
+ * Set to `'custom'` (or omit) to control individual bone inputs.
55
+ * @default 'custom'
56
+ */
57
+ preset: _angular_core.InputSignal<SkeletonPreset>;
58
+ /** Shape of the skeleton bone. @default 'line' */
59
+ shape: _angular_core.InputSignal<SkeletonShape>;
60
+ /**
61
+ * CSS width applied to the bone element.
62
+ * Accepts any valid CSS value: `'100%'`, `'8rem'`, `'w-1/2'` (Tailwind class).
63
+ * @default '100%'
64
+ */
65
+ width: _angular_core.InputSignal<string>;
66
+ /**
67
+ * CSS height applied to the bone element.
68
+ * @default '1rem'
69
+ */
70
+ height: _angular_core.InputSignal<string>;
71
+ /**
72
+ * Border-radius override.
73
+ * Accepts Tailwind rounded-* class suffix: `'none'` `'sm'` `'md'` `'lg'` `'xl'` `'full'`.
74
+ * Shape defaults apply when left empty.
75
+ * @default ''
76
+ */
77
+ rounded: _angular_core.InputSignal<string>;
78
+ /**
79
+ * Repeat this bone N times stacked vertically.
80
+ * Gaps between repeated lines are automatic.
81
+ * @default 1
82
+ */
83
+ lines: _angular_core.InputSignal<number>;
84
+ /**
85
+ * When `lines > 1`, make the last line shorter (like a real paragraph ending).
86
+ * @default true
87
+ */
88
+ shortLastLine: _angular_core.InputSignal<boolean>;
89
+ /** Shimmer animation style. @default 'wave' */
90
+ animation: _angular_core.InputSignal<SkeletonAnimation>;
91
+ /**
92
+ * Overall size multiplier that scales preset bone heights.
93
+ * @default 'md'
94
+ */
95
+ size: _angular_core.InputSignal<"sm" | "md" | "lg">;
96
+ /** Additional CSS classes merged onto the root wrapper. */
97
+ customClass: _angular_core.InputSignal<string>;
98
+ /** Accessible label announced to screen readers while content loads. @default 'Loading content…' */
99
+ ariaLabel: _angular_core.InputSignal<string>;
100
+ isPreset: _angular_core.Signal<boolean>;
101
+ /** Base class applied to every individual bone element. */
102
+ boneBase: _angular_core.Signal<string>;
103
+ /** Additional class added when animation === 'wave' to enable the shimmer sweep. */
104
+ waveClass: _angular_core.Signal<string>;
105
+ /** Lines array for the repeater. Last line gets a width reduction. */
106
+ lineItems: _angular_core.Signal<{
107
+ width: string;
108
+ }[]>;
109
+ /** Rounded class derived from shape or `rounded` override. */
110
+ roundedClass: _angular_core.Signal<string>;
111
+ lineH: _angular_core.Signal<string>;
112
+ avatarSz: _angular_core.Signal<string>;
113
+ iconSz: _angular_core.Signal<string>;
114
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SvSkeletonLoaderComponent, never>;
115
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SvSkeletonLoaderComponent, "sv-skeleton-loader", never, { "preset": { "alias": "preset"; "required": false; "isSignal": true; }; "shape": { "alias": "shape"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "height": { "alias": "height"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "lines": { "alias": "lines"; "required": false; "isSignal": true; }; "shortLastLine": { "alias": "shortLastLine"; "required": false; "isSignal": true; }; "animation": { "alias": "animation"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "customClass": { "alias": "customClass"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
116
+ }
117
+
118
+ export { SvSkeletonLoaderComponent };
119
+ export type { SkeletonAnimation, SkeletonPreset, SkeletonShape };