38 lines
1.2 KiB
TypeScript
38 lines
1.2 KiB
TypeScript
|
|
import { useTranslation } from 'react-i18next';
|
||
|
|
|
||
|
|
interface SkeletonProps {
|
||
|
|
className?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Одна полоса-скелетон с бегущим блеском (см. `.gl-skeleton` в index.css). */
|
||
|
|
export function Skeleton({ className = '' }: SkeletonProps) {
|
||
|
|
return <span aria-hidden="true" className={`gl-skeleton ${className}`} />;
|
||
|
|
}
|
||
|
|
|
||
|
|
interface SkeletonLinesProps {
|
||
|
|
/** Сколько строк-полос нарисовать. */
|
||
|
|
rows?: number;
|
||
|
|
className?: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Набор полос вместо текста «Загрузка…»: используется в списках и секциях
|
||
|
|
* настроек, чтобы интерфейс не «прыгал» после прихода данных.
|
||
|
|
*/
|
||
|
|
export function SkeletonLines({ rows = 3, className = '' }: SkeletonLinesProps) {
|
||
|
|
const { t } = useTranslation();
|
||
|
|
return (
|
||
|
|
<div
|
||
|
|
role="status"
|
||
|
|
aria-busy="true"
|
||
|
|
aria-label={t('common.loading')}
|
||
|
|
data-testid="skeleton"
|
||
|
|
className={`flex flex-col gap-2 ${className}`}
|
||
|
|
>
|
||
|
|
{Array.from({ length: Math.max(rows, 1) }, (_, index) => (
|
||
|
|
<Skeleton key={index} className="h-4 w-full" />
|
||
|
|
))}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|