97 lines
2.5 KiB
TypeScript
97 lines
2.5 KiB
TypeScript
import React from "react";
|
|
|
|
import classNames from "@calcom/lib/classNames";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
|
|
type SkeletonBaseProps = {
|
|
className?: string;
|
|
};
|
|
|
|
interface SkeletonContainer {
|
|
as?: keyof JSX.IntrinsicElements;
|
|
children?: React.ReactNode;
|
|
className?: string;
|
|
}
|
|
|
|
const SkeletonAvatar: React.FC<SkeletonBaseProps> = ({ className }) => {
|
|
return <div className={classNames(`mt-1 rounded-full bg-gray-200 ltr:mr-2 rtl:ml-2`, className)} />;
|
|
};
|
|
|
|
type SkeletonProps<T> = {
|
|
as: keyof JSX.IntrinsicElements | React.FC;
|
|
className?: string;
|
|
children: React.ReactNode;
|
|
loading?: boolean;
|
|
waitForTranslation?: boolean;
|
|
loadingClassName?: string;
|
|
} & (T extends React.FC<infer P>
|
|
? P
|
|
: T extends keyof JSX.IntrinsicElements
|
|
? JSX.IntrinsicElements[T]
|
|
: never);
|
|
|
|
const Skeleton = <T extends keyof JSX.IntrinsicElements | React.FC>({
|
|
as,
|
|
className = "",
|
|
children,
|
|
loading = false,
|
|
/**
|
|
* Assumes that the text needs translation by default and wait for it.
|
|
*/
|
|
waitForTranslation = true,
|
|
/**
|
|
* Classes that you need only in loading state
|
|
*/
|
|
loadingClassName = "",
|
|
...rest
|
|
}: SkeletonProps<T>) => {
|
|
const { isLocaleReady } = useLocale();
|
|
loading = (waitForTranslation ? !isLocaleReady : false) || loading;
|
|
const Component = as;
|
|
return (
|
|
<Component
|
|
className={classNames(
|
|
loading
|
|
? classNames(
|
|
"font-size-0 dark:white-300 animate-pulse rounded-md bg-gray-300 text-transparent",
|
|
loadingClassName
|
|
)
|
|
: "",
|
|
className
|
|
)}
|
|
{...rest}>
|
|
{children}
|
|
</Component>
|
|
);
|
|
};
|
|
|
|
const SkeletonText: React.FC<SkeletonBaseProps & { invisible?: boolean }> = ({
|
|
className = "",
|
|
invisible = false,
|
|
}) => {
|
|
return (
|
|
<span
|
|
className={classNames(
|
|
`font-size-0 dark:white-300 inline-block animate-pulse rounded-md bg-gray-300 empty:before:inline-block empty:before:content-['']`,
|
|
className,
|
|
invisible ? "invisible" : ""
|
|
)}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const SkeletonButton: React.FC<SkeletonBaseProps> = ({ className }) => {
|
|
return (
|
|
<SkeletonContainer>
|
|
<div className={classNames(`rounded-md bg-gray-200`, className)} />
|
|
</SkeletonContainer>
|
|
);
|
|
};
|
|
|
|
const SkeletonContainer: React.FC<SkeletonContainer> = ({ children, as, className }) => {
|
|
const Component = as || "div";
|
|
return <Component className={classNames("animate-pulse", className)}>{children}</Component>;
|
|
};
|
|
|
|
export { Skeleton, SkeletonAvatar, SkeletonText, SkeletonButton, SkeletonContainer };
|