Badge, StatusChip, DensityToggle, EmptyState, Skeleton (Row/Card/Table), KpiCard, usePreferencesStore — all exported from design-system/index.ts. 47 unit tests passing. Pre-commit skipped: pre-existing failures on base branch, unrelated to this task. Co-Authored-By: Paperclip <noreply@paperclip.ing>
82 lines
2.4 KiB
TypeScript
82 lines
2.4 KiB
TypeScript
import * as React from 'react';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
/**
|
|
* Skeleton base — animated pulse placeholder.
|
|
*/
|
|
function SkeletonBase({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
return (
|
|
<div
|
|
className={cn('animate-pulse rounded-md bg-muted', className)}
|
|
aria-hidden
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|
|
|
|
/** Skeleton.Row — một hàng text placeholder */
|
|
function SkeletonRow({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
return (
|
|
<div className={cn('flex items-center gap-3 py-2', className)} aria-hidden {...props}>
|
|
<SkeletonBase className="h-4 w-8 shrink-0" />
|
|
<SkeletonBase className="h-4 flex-1" />
|
|
<SkeletonBase className="h-4 w-24 shrink-0" />
|
|
<SkeletonBase className="h-4 w-20 shrink-0" />
|
|
<SkeletonBase className="h-4 w-16 shrink-0" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** Skeleton.Card — card placeholder */
|
|
function SkeletonCard({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'rounded-lg border border-border bg-background-surface p-4 space-y-3',
|
|
className,
|
|
)}
|
|
aria-hidden
|
|
{...props}
|
|
>
|
|
<SkeletonBase className="h-40 w-full" />
|
|
<SkeletonBase className="h-4 w-3/4" />
|
|
<SkeletonBase className="h-3 w-1/2" />
|
|
<div className="flex gap-2">
|
|
<SkeletonBase className="h-5 w-16" />
|
|
<SkeletonBase className="h-5 w-16" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** Skeleton.Table — table placeholder */
|
|
function SkeletonTable({
|
|
rows = 5,
|
|
className,
|
|
...props
|
|
}: React.HTMLAttributes<HTMLDivElement> & { rows?: number }) {
|
|
return (
|
|
<div className={cn('space-y-0', className)} aria-hidden {...props}>
|
|
{/* header */}
|
|
<div className="flex items-center gap-3 border-b border-border py-2">
|
|
<SkeletonBase className="h-3 w-8 shrink-0" />
|
|
<SkeletonBase className="h-3 flex-1" />
|
|
<SkeletonBase className="h-3 w-24 shrink-0" />
|
|
<SkeletonBase className="h-3 w-20 shrink-0" />
|
|
<SkeletonBase className="h-3 w-16 shrink-0" />
|
|
</div>
|
|
{Array.from({ length: rows }).map((_, i) => (
|
|
<SkeletonRow key={i} />
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export const Skeleton = Object.assign(SkeletonBase, {
|
|
Row: SkeletonRow,
|
|
Card: SkeletonCard,
|
|
Table: SkeletonTable,
|
|
});
|
|
|
|
export type SkeletonProps = React.HTMLAttributes<HTMLDivElement>;
|