feat(design-system): add reusable Skeleton loading primitive
Standalone, OnPush, aria-hidden. Shapes text/circle/rect, configurable width/height. Shimmer respects prefers-reduced-motion. Colors via existing tenant CSS vars with fallbacks.
This commit is contained in:
1
src/app/shared/ui/skeleton/skeleton.component.html
Normal file
1
src/app/shared/ui/skeleton/skeleton.component.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<!-- purely visual placeholder, no projected content -->
|
||||||
40
src/app/shared/ui/skeleton/skeleton.component.scss
Normal file
40
src/app/shared/ui/skeleton/skeleton.component.scss
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
var(--bg-secondary, #f4f4f5) 25%,
|
||||||
|
var(--border-color, #e4e4e7) 50%,
|
||||||
|
var(--bg-secondary, #f4f4f5) 75%
|
||||||
|
);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: app-skeleton-shimmer 1.4s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host.app-skeleton--text {
|
||||||
|
height: 1em;
|
||||||
|
border-radius: var(--radius-sm, 4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host.app-skeleton--circle {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host.app-skeleton--rect {
|
||||||
|
border-radius: var(--radius-md, 6px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes app-skeleton-shimmer {
|
||||||
|
0% {
|
||||||
|
background-position: 200% 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-position: -200% 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
:host {
|
||||||
|
animation: none;
|
||||||
|
background: var(--bg-secondary, #f4f4f5);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/app/shared/ui/skeleton/skeleton.component.ts
Normal file
24
src/app/shared/ui/skeleton/skeleton.component.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, input } from '@angular/core';
|
||||||
|
|
||||||
|
export type SkeletonShape = 'text' | 'circle' | 'rect';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-skeleton',
|
||||||
|
standalone: true,
|
||||||
|
templateUrl: './skeleton.component.html',
|
||||||
|
styleUrl: './skeleton.component.scss',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
host: {
|
||||||
|
'[class.app-skeleton--text]': "shape() === 'text'",
|
||||||
|
'[class.app-skeleton--circle]': "shape() === 'circle'",
|
||||||
|
'[class.app-skeleton--rect]': "shape() === 'rect'",
|
||||||
|
'[style.width]': 'width()',
|
||||||
|
'[style.height]': 'height()',
|
||||||
|
'aria-hidden': 'true'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export class SkeletonComponent {
|
||||||
|
readonly shape = input<SkeletonShape>('text');
|
||||||
|
readonly width = input<string>('100%');
|
||||||
|
readonly height = input<string | null>(null);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user