import { LitElement, html, css } from 'lit';
import { property } from 'lit/decorators.js';
export type SkeletonVariant = 'text' | 'circular' | 'rectangular' | 'rounded';
export type SkeletonEffect = 'sheen' | 'pulse' | 'none';
export type SkeletonIntensity = 'light' | 'medium';
export interface SkeletonProps {
variant?: SkeletonVariant;
effect?: SkeletonEffect;
intensity?: SkeletonIntensity;
width?: string;
height?: string;
}
/** Alias required for SDUI codegen discovery (glob: SkeletonLoader/core/_*.ts → looks for SkeletonLoaderProps) */
export interface SkeletonLoaderProps extends SkeletonProps {}
export class AgSkeleton extends LitElement implements SkeletonProps {
@property({ type: String, reflect: true }) declare variant: SkeletonVariant;
@property({ type: String, reflect: true }) declare effect: SkeletonEffect;
@property({ type: String, reflect: true }) declare intensity: SkeletonIntensity;
@property({ type: String, reflect: true }) declare width?: string;
@property({ type: String, reflect: true }) declare height?: string;
constructor() {
super();
this.variant = 'text';
this.effect = 'pulse';
this.intensity = 'light';
}
connectedCallback() {
super.connectedCallback();
// Skeleton loaders are visual placeholders, hide from screen readers
if (!this.hasAttribute('aria-hidden')) {
this.setAttribute('aria-hidden', 'true');
}
}
static styles = css`
:host {
display: block;
background-color: var(--ag-background-secondary);
position: relative;
overflow: hidden;
width: 100%;
height: 1em;
}
/* Intensity variants */
:host([intensity='light']) {
background-color: var(--ag-background-secondary);
}
:host([intensity='medium']) {
background-color: var(--ag-background-tertiary);
}
/* Variants */
:host([variant='text']) {
border-radius: var(--ag-radius-sm);
}
:host([variant='circular']) {
border-radius: 50%;
}
:host([variant='rectangular']) {
border-radius: 0;
}
:host([variant='rounded']) {
border-radius: var(--ag-radius-md);
}
/* Effects */
@keyframes pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
:host([effect='pulse']) {
animation: pulse 1.5s infinite ease-in-out;
}
:host([effect='sheen'])::after {
content: "";
position: absolute;
inset: 0;
transform: translateX(-100%);
background-image: linear-gradient(
90deg,
transparent 0%,
var(--ag-background-primary) 20%,
var(--ag-background-primary) 60%,
transparent 100%
);
opacity: 0.3;
animation: sheen 1.5s infinite;
}
@keyframes sheen {
100% {
transform: translateX(100%);
}
}
:host([effect='none']) {
animation: none;
}
/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
:host([effect='pulse']),
:host([effect='sheen'])::after {
animation: none;
}
}
`;
updated(changedProperties: Map) {
super.updated(changedProperties);
// Handle dynamic width/height styling
if (changedProperties.has('width') || changedProperties.has('height') || changedProperties.has('variant')) {
this._updateDimensions();
}
}
private _updateDimensions() {
// For circular variant, ensure dimensions are equal if only one is provided
if (this.variant === 'circular') {
if (this.width && !this.height) {
this.style.height = this.width;
this.style.width = this.width;
} else if (this.height && !this.width) {
this.style.width = this.height;
this.style.height = this.height;
} else if (this.width && this.height) {
this.style.width = this.width;
this.style.height = this.height;
}
} else {
if (this.width) {
this.style.width = this.width;
}
if (this.height) {
this.style.height = this.height;
}
}
}
render() {
return html``;
}
}