micro/packages/web/src/components/skeleton.tsx

61 lines
1.8 KiB
TypeScript

import clsx from 'clsx';
import type { FC, ReactNode } from 'react';
import { Fragment, memo } from 'react';
import { BASE_BUTTON_CLASSES } from './button';
import { BASE_INPUT_CLASSES, BASE_INPUT_MAX_HEIGHT } from './input/container';
interface SkeletonProps {
className?: string;
}
export const Skeleton = memo<SkeletonProps>(({ className }) => {
const hasHeight = !className?.includes(' h-');
return <div className={clsx('animate-pulse bg-gray-800 rounded-md', className, hasHeight && 'h-4')} />;
});
interface SkeletonListProps {
count: number;
children: ReactNode;
className?: string;
as?: FC | FC<{ className?: string }>;
}
export const SkeletonList = memo<SkeletonListProps>(({ count, children, className, as }) => {
const Component = as || 'div';
return (
<Component className={className}>
{Array.from({ length: count }).map((_, i) => (
<Fragment key={i}>{children}</Fragment>
))}
</Component>
);
});
interface InputSkeletonProps {
maxHeight?: boolean;
className?: string;
}
export const InputSkeleton = memo<InputSkeletonProps>(({ maxHeight = true, className }) => {
const classes = clsx(BASE_INPUT_CLASSES, maxHeight && BASE_INPUT_MAX_HEIGHT, className, 'border-transparent');
return <Skeleton className={classes} />;
});
export const ButtonSkeleton = memo<SkeletonProps>(({ className }) => {
return <Skeleton className={clsx(BASE_BUTTON_CLASSES, className, 'min-h-[2.65em] min-w-[20em]')} />;
});
export const SkeletonWrap = memo<{
show: boolean;
children: ReactNode;
}>(({ show, children }) => {
if (!show) return <Fragment>{children}</Fragment>;
return (
<span className="relative">
<Skeleton className="absolute inset-0 h-full" />
<span className="opacity-0">{children}</span>
</span>
);
});