Removed unused ui components and updated skeleton components (#5412)
* Removed unused ui components and updated skeleton components throughout the app to use new version. * Deleted unused app component and changed destructive button variants to secondary as per design. Co-authored-by: zomars <zomars@me.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
zomars
kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
parent
d83883e7e4
commit
f513ce8687
@@ -1,309 +0,0 @@
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
||||
import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { App as AppType } from "@calcom/types/App";
|
||||
import { Button, SkeletonButton } from "@calcom/ui";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import Shell from "@calcom/ui/Shell";
|
||||
import { Badge } from "@calcom/ui/components/badge";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
|
||||
import DisconnectIntegration from "@components/integrations/DisconnectIntegration";
|
||||
|
||||
const Component = ({
|
||||
name,
|
||||
type,
|
||||
logo,
|
||||
body,
|
||||
categories,
|
||||
author,
|
||||
price = 0,
|
||||
commission,
|
||||
isGlobal = false,
|
||||
feeType,
|
||||
docs,
|
||||
website,
|
||||
email,
|
||||
tos,
|
||||
privacy,
|
||||
isProOnly,
|
||||
}: Parameters<typeof App>[0]) => {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const { data: user } = trpc.viewer.me.useQuery();
|
||||
|
||||
const utils = trpc.useContext();
|
||||
const handleOpenChange = () => {
|
||||
utils.viewer.integrations.invalidate();
|
||||
router.replace("/apps/installed");
|
||||
};
|
||||
|
||||
const mutation = useAddAppMutation(null, {
|
||||
onSuccess: () => {
|
||||
showToast("App successfully installed", "success");
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error instanceof Error) showToast(error.message || "App could not be installed", "error");
|
||||
},
|
||||
});
|
||||
|
||||
const priceInDollar = Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
useGrouping: false,
|
||||
}).format(price);
|
||||
|
||||
const [existingCredentials, setExistingCredentials] = useState<number[]>([]);
|
||||
const appCredentials = trpc.viewer.appCredentialsByType.useQuery(
|
||||
{ appType: type },
|
||||
{
|
||||
onSuccess(data) {
|
||||
setExistingCredentials(data);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const allowedMultipleInstalls = categories.indexOf("calendar") > -1;
|
||||
|
||||
return (
|
||||
<div className="-mx-4 md:-mx-8">
|
||||
<div className="px-8">
|
||||
<Link href="/apps">
|
||||
<a className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800">
|
||||
<Icon.FiChevronLeft className="h-5 w-5" /> {t("browse_apps")}
|
||||
</a>
|
||||
</Link>
|
||||
<div className="items-center justify-between py-4 sm:flex sm:py-8">
|
||||
<div className="flex">
|
||||
<img className="h-16 w-16 rounded-sm" src={logo} alt={name} />
|
||||
<header className="px-4 py-2">
|
||||
<div className="flex items-center">
|
||||
<h1 className="font-cal text-xl text-gray-900">{name}</h1>
|
||||
</div>
|
||||
<h2 className="text-sm text-gray-500">
|
||||
<span className="capitalize">{categories[0]}</span> • {t("published_by", { author })}
|
||||
</h2>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 sm:mt-0 sm:text-right">
|
||||
{!appCredentials.isLoading ? (
|
||||
isGlobal ||
|
||||
(existingCredentials.length > 0 && allowedMultipleInstalls ? (
|
||||
<div className="flex space-x-3">
|
||||
<Button StartIcon={Icon.FiCheck} color="secondary" disabled>
|
||||
{existingCredentials.length > 0
|
||||
? t("active_install", { count: existingCredentials.length })
|
||||
: t("globally_install")}
|
||||
</Button>
|
||||
{!isGlobal && (
|
||||
<InstallAppButton
|
||||
type={type}
|
||||
isProOnly={isProOnly}
|
||||
render={({ useDefaultComponent, ...props }) => {
|
||||
if (useDefaultComponent) {
|
||||
props = {
|
||||
onClick: () => {
|
||||
mutation.mutate({ type });
|
||||
},
|
||||
loading: mutation.isLoading,
|
||||
};
|
||||
}
|
||||
return (
|
||||
<Button StartIcon={Icon.FiPlus} {...props} data-testid="install-app-button">
|
||||
{t("add_another")}
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : existingCredentials.length > 0 ? (
|
||||
<DisconnectIntegration
|
||||
id={existingCredentials[0]}
|
||||
render={(btnProps) => (
|
||||
<Button {...btnProps} color="warn" data-testid={type + "-integration-disconnect-button"}>
|
||||
{t("disconnect")}
|
||||
</Button>
|
||||
)}
|
||||
onOpenChange={handleOpenChange}
|
||||
/>
|
||||
) : (
|
||||
<InstallAppButton
|
||||
type={type}
|
||||
isProOnly={isProOnly}
|
||||
render={({ useDefaultComponent, ...props }) => {
|
||||
if (useDefaultComponent) {
|
||||
props = {
|
||||
onClick: () => {
|
||||
mutation.mutate({ type });
|
||||
},
|
||||
loading: mutation.isLoading,
|
||||
};
|
||||
}
|
||||
return (
|
||||
<Button data-testid="install-app-button" {...props}>
|
||||
{t("install_app")}
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<SkeletonButton width="24" height="10" />
|
||||
)}
|
||||
{price !== 0 && (
|
||||
<small className="block text-right">
|
||||
{feeType === "usage-based" ? commission + "% + " + priceInDollar + "/booking" : priceInDollar}
|
||||
{feeType === "monthly" && "/" + t("month")}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="justify-between px-8 py-10 md:flex">
|
||||
<div className="prose-sm prose mb-6">{body}</div>
|
||||
<div className="md:max-w-80 flex-1 md:ml-8">
|
||||
<h4 className="font-medium text-gray-900 ">{t("categories")}</h4>
|
||||
<div className="space-x-2">
|
||||
{categories.map((category) => (
|
||||
<Link href={"/apps/categories/" + category} key={category}>
|
||||
<a>
|
||||
<Badge variant="success">{category}</Badge>
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<h4 className="mt-8 font-medium text-gray-900 ">{t("pricing")}</h4>
|
||||
<small>
|
||||
{price === 0 ? (
|
||||
"Free"
|
||||
) : (
|
||||
<>
|
||||
{Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
useGrouping: false,
|
||||
}).format(price)}
|
||||
{feeType === "monthly" && "/" + t("month")}
|
||||
</>
|
||||
)}
|
||||
</small>
|
||||
<h4 className="mt-8 mb-2 font-medium text-gray-900 ">{t("learn_more")}</h4>
|
||||
<ul className="prose -ml-1 -mr-1 text-xs leading-5">
|
||||
{docs && (
|
||||
<li>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 no-underline hover:underline"
|
||||
href={docs}>
|
||||
<Icon.FiBookOpen className="mr-1 -mt-1 inline h-4 w-4" />
|
||||
{t("documentation")}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{website && (
|
||||
<li>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 no-underline hover:underline"
|
||||
href={website}>
|
||||
<Icon.FiExternalLink className="mr-1 -mt-px inline h-4 w-4" />
|
||||
{website.replace("https://", "")}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{email && (
|
||||
<li>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 no-underline hover:underline"
|
||||
href={"mailto:" + email}>
|
||||
<Icon.FiMail className="mr-1 -mt-px inline h-4 w-4" />
|
||||
|
||||
{email}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{tos && (
|
||||
<li>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 no-underline hover:underline"
|
||||
href={tos}>
|
||||
<Icon.FiFile className="mr-1 -mt-px inline h-4 w-4" />
|
||||
{t("terms_of_service")}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
{privacy && (
|
||||
<li>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-blue-500 no-underline hover:underline"
|
||||
href={privacy}>
|
||||
<Icon.FiShield className="mr-1 -mt-px inline h-4 w-4" />
|
||||
{t("privacy_policy")}
|
||||
</a>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
<hr className="my-6" />
|
||||
<small className="leading-1 block text-gray-500">
|
||||
Every app published on the Cal.com App Store is open source and thoroughly tested via peer
|
||||
reviews. Nevertheless, Cal.com, Inc. does not endorse or certify these apps unless they are
|
||||
published by Cal.com. If you encounter inappropriate content or behaviour please report it.
|
||||
</small>
|
||||
<a className="mt-2 block text-xs text-red-500" href="mailto:help@cal.com">
|
||||
<Icon.FiFlag className="inline h-3 w-3" /> Report App
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function App(props: {
|
||||
name: string;
|
||||
type: AppType["type"];
|
||||
isGlobal?: AppType["isGlobal"];
|
||||
logo: string;
|
||||
body: React.ReactNode;
|
||||
categories: string[];
|
||||
author: string;
|
||||
pro?: boolean;
|
||||
price?: number;
|
||||
commission?: number;
|
||||
feeType?: AppType["feeType"];
|
||||
docs?: string;
|
||||
website?: string;
|
||||
email: string; // required
|
||||
tos?: string;
|
||||
privacy?: string;
|
||||
licenseRequired: AppType["licenseRequired"];
|
||||
isProOnly: AppType["isProOnly"];
|
||||
}) {
|
||||
return (
|
||||
<Shell large isPublic>
|
||||
{props.licenseRequired ? (
|
||||
<LicenseRequired>
|
||||
<Component {...props} />
|
||||
</LicenseRequired>
|
||||
) : (
|
||||
<Component {...props} />
|
||||
)}
|
||||
</Shell>
|
||||
);
|
||||
}
|
||||
@@ -146,6 +146,7 @@ const Component = ({
|
||||
</div>
|
||||
) : existingCredentials.length > 0 ? (
|
||||
<DisconnectIntegration
|
||||
buttonProps={{ color: "secondary" }}
|
||||
label={t("disconnect")}
|
||||
credentialId={existingCredentials[0]}
|
||||
onSuccess={() => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { nameOfDay } from "@calcom/lib/weekday";
|
||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
|
||||
import { SkeletonContainer, SkeletonText } from "@calcom/ui";
|
||||
import { SkeletonContainer, SkeletonText } from "@calcom/ui/v2";
|
||||
import { ToggleGroup } from "@calcom/ui/v2/core/form/ToggleGroup";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
@@ -152,13 +152,13 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
{isLoading && !slots.length && (
|
||||
<>
|
||||
<SkeletonContainer className="mb-2">
|
||||
<SkeletonText width="full" height="20" />
|
||||
<SkeletonText className="h-5 w-full" />
|
||||
</SkeletonContainer>
|
||||
<SkeletonContainer className="mb-2">
|
||||
<SkeletonText width="full" height="20" />
|
||||
<SkeletonText className="h-5 w-full" />
|
||||
</SkeletonContainer>
|
||||
<SkeletonContainer className="mb-2">
|
||||
<SkeletonText width="full" height="20" />
|
||||
<SkeletonText className="h-5 w-full" />
|
||||
</SkeletonContainer>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -5,8 +5,8 @@ function SkeletonLoader() {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<div className="mb-4 flex items-center">
|
||||
<SkeletonAvatar className="h-8 w-8" />
|
||||
<div className="flex flex-col space-y-1">
|
||||
<SkeletonAvatar className="h-2 w-2" />
|
||||
<div className="space-y-1">
|
||||
<SkeletonText className="h-4 w-16" />
|
||||
<SkeletonText className="h-4 w-24" />
|
||||
</div>
|
||||
@@ -27,17 +27,17 @@ function SkeletonItem() {
|
||||
<li className="group flex w-full items-center justify-between px-4 py-4 sm:px-6">
|
||||
<div className="flex-grow truncate text-sm">
|
||||
<div>
|
||||
<SkeletonText className="h-5 w-32" />
|
||||
<SkeletonText className="h-[5px] w-8" />
|
||||
</div>
|
||||
<div className="">
|
||||
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
|
||||
<li className="flex items-center whitespace-nowrap">
|
||||
<Icon.FiClock className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
|
||||
<SkeletonText className="h-4 w-12" />
|
||||
<SkeletonText className="h-1 w-3" />
|
||||
</li>
|
||||
<li className="flex items-center whitespace-nowrap">
|
||||
<Icon.FiUser className="mt-0.5 mr-1.5 inline h-4 w-4 text-gray-200" />
|
||||
<SkeletonText className="h-4 w-16" />
|
||||
<SkeletonText className="h-1 w-4" />
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,11 +5,11 @@ import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import { List } from "@calcom/ui/List";
|
||||
import { ShellSubHeading } from "@calcom/ui/Shell";
|
||||
import Switch from "@calcom/ui/Switch";
|
||||
import SkeletonLoader from "@calcom/ui/apps/SkeletonLoader";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
@@ -161,7 +161,7 @@ function ConnectedCalendarsList(props: Props) {
|
||||
id={item.credentialId}
|
||||
externalId={item.primary?.externalId}
|
||||
render={(btnProps) => (
|
||||
<Button {...btnProps} color="warn" data-testid="integration-connection-button">
|
||||
<Button {...btnProps} color="secondary" data-testid="integration-connection-button">
|
||||
{t("disconnect")}
|
||||
</Button>
|
||||
)}
|
||||
@@ -194,7 +194,7 @@ function ConnectedCalendarsList(props: Props) {
|
||||
<DisconnectIntegration
|
||||
id={item.credentialId}
|
||||
render={(btnProps) => (
|
||||
<Button {...btnProps} color="warn" data-testid="integration-connection-button">
|
||||
<Button {...btnProps} color="secondary" data-testid="integration-connection-button">
|
||||
Disconnect
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -2,9 +2,9 @@ import { useState } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { ButtonBaseProps } from "@calcom/ui/Button";
|
||||
import ConfirmationDialogContent from "@calcom/ui/ConfirmationDialogContent";
|
||||
import { Dialog } from "@calcom/ui/Dialog";
|
||||
import { ButtonBaseProps } from "@calcom/ui/components/button";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
|
||||
export default function DisconnectIntegration(props: {
|
||||
|
||||
@@ -15,8 +15,8 @@ import { useAccount, useSignMessage } from "wagmi";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { SkeletonText } from "@calcom/ui";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { SkeletonText } from "@calcom/ui/v2";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
|
||||
import { getProviders, ETH_MESSAGE, SUPPORTED_CHAINS } from "../utils/ethereum";
|
||||
@@ -118,8 +118,8 @@ const BalanceCheck: React.FC<RainbowGateProps> = ({ chainId, setToken, tokenAddr
|
||||
<h2 className="mb-2 grow font-semibold text-neutral-900 dark:text-white">Token Gate</h2>
|
||||
{isLoading && (
|
||||
<>
|
||||
<SkeletonText width="[100%]" height="5" className="mb-3" />
|
||||
<SkeletonText width="[100%]" height="5" />
|
||||
<SkeletonText className="mb-3 h-1 w-full" />
|
||||
<SkeletonText className="h-1 w-full" />
|
||||
</>
|
||||
)}
|
||||
{!isLoading && contractData && contractData.data && (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SkeletonText } from "@calcom/ui";
|
||||
import { SkeletonText } from "@calcom/ui/v2";
|
||||
|
||||
function SkeletonLoaderTeamList() {
|
||||
return (
|
||||
@@ -19,18 +19,18 @@ function SkeletonItem() {
|
||||
<li className="group flex w-full items-center justify-between px-3 py-4">
|
||||
<div className="flex-grow truncate text-sm">
|
||||
<div className="flex justify-start space-x-2 px-2">
|
||||
<SkeletonText width="10" height="10" className="rounded-full" />
|
||||
<SkeletonText className="h-3 w-3 rounded-full" />
|
||||
<div className="space-y-2">
|
||||
<SkeletonText height="4" width="32" />
|
||||
<SkeletonText height="4" width="16" />
|
||||
<SkeletonText className="h-1 w-8" />
|
||||
<SkeletonText className="h-1 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 hidden flex-shrink-0 pr-4 sm:mt-0 sm:ml-5 lg:flex">
|
||||
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
||||
<SkeletonText width="12" height="4" />
|
||||
<SkeletonText width="4" height="4" />
|
||||
<SkeletonText width="4" height="4" />
|
||||
<SkeletonText className="h-1 w-3" />
|
||||
<SkeletonText className="h-1 w-1" />
|
||||
<SkeletonText className="h-1 w-1" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SkeletonContainer, SkeletonText } from "@calcom/ui";
|
||||
import { SkeletonContainer, SkeletonText } from "@calcom/ui/v2";
|
||||
|
||||
function SkeletonLoader() {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SkeletonContainer, SkeletonText } from "@calcom/ui";
|
||||
import { SkeletonContainer, SkeletonText } from "@calcom/ui/v2";
|
||||
|
||||
function SkeletonLoader() {
|
||||
return (
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
import { Root as ToggleGroupPrimitive, Item as ToggleGroupItemPrimitive } from "@radix-ui/react-toggle-group";
|
||||
import { useState } from "react";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
|
||||
const boolean = (yesNo: "yes" | "no") => (yesNo === "yes" ? true : yesNo === "no" ? false : undefined);
|
||||
const yesNo = (boolean?: boolean) => (boolean === true ? "yes" : boolean === false ? "no" : undefined);
|
||||
|
||||
export default function BooleanToggleGroup({
|
||||
defaultValue = true,
|
||||
value,
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
onValueChange = () => {},
|
||||
}: {
|
||||
defaultValue?: boolean;
|
||||
value?: boolean;
|
||||
onValueChange?: (value?: boolean) => void;
|
||||
}) {
|
||||
// Maintain a state because it is not necessary that onValueChange the parent component would re-render. Think react-hook-form
|
||||
// Also maintain a string as boolean isn't accepted as ToggleGroupPrimitive value
|
||||
const [yesNoValue, setYesNoValue] = useState<"yes" | "no" | undefined>(yesNo(value));
|
||||
|
||||
if (!yesNoValue) {
|
||||
setYesNoValue(yesNo(defaultValue));
|
||||
onValueChange(defaultValue);
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ToggleGroupPrimitive
|
||||
value={yesNoValue}
|
||||
type="single"
|
||||
className="rounded-sm"
|
||||
onValueChange={(yesNoValue: "yes" | "no") => {
|
||||
setYesNoValue(yesNoValue);
|
||||
onValueChange(boolean(yesNoValue));
|
||||
}}>
|
||||
<ToggleGroupItemPrimitive
|
||||
className={classNames(
|
||||
boolean(yesNoValue) ? "bg-gray-200" : "",
|
||||
"border border-gray-300 py-2 px-3 text-sm"
|
||||
)}
|
||||
value="yes">
|
||||
Yes
|
||||
</ToggleGroupItemPrimitive>
|
||||
<ToggleGroupItemPrimitive
|
||||
className={classNames(
|
||||
!boolean(yesNoValue) ? "bg-gray-200" : "",
|
||||
"border border-l-0 border-gray-300 py-2 px-3 text-sm"
|
||||
)}
|
||||
value="no">
|
||||
No
|
||||
</ToggleGroupItemPrimitive>
|
||||
</ToggleGroupPrimitive>
|
||||
);
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
import Head from "next/head";
|
||||
|
||||
import { useBrandColors } from "@calcom/embed-core/embed-iframe";
|
||||
|
||||
const brandColor = "#292929";
|
||||
const brandTextColor = "#ffffff";
|
||||
const darkBrandColor = "#fafafa";
|
||||
|
||||
export function colorNameToHex(color: string) {
|
||||
const colors = {
|
||||
aliceblue: "#f0f8ff",
|
||||
antiquewhite: "#faebd7",
|
||||
aqua: "#00ffff",
|
||||
aquamarine: "#7fffd4",
|
||||
azure: "#f0ffff",
|
||||
beige: "#f5f5dc",
|
||||
bisque: "#ffe4c4",
|
||||
black: "#000000",
|
||||
blanchedalmond: "#ffebcd",
|
||||
blue: "#0000ff",
|
||||
blueviolet: "#8a2be2",
|
||||
brown: "#a52a2a",
|
||||
burlywood: "#deb887",
|
||||
cadetblue: "#5f9ea0",
|
||||
chartreuse: "#7fff00",
|
||||
chocolate: "#d2691e",
|
||||
coral: "#ff7f50",
|
||||
cornflowerblue: "#6495ed",
|
||||
cornsilk: "#fff8dc",
|
||||
crimson: "#dc143c",
|
||||
cyan: "#00ffff",
|
||||
darkblue: "#00008b",
|
||||
darkcyan: "#008b8b",
|
||||
darkgoldenrod: "#b8860b",
|
||||
darkgray: "#a9a9a9",
|
||||
darkgreen: "#006400",
|
||||
darkkhaki: "#bdb76b",
|
||||
darkmagenta: "#8b008b",
|
||||
darkolivegreen: "#556b2f",
|
||||
darkorange: "#ff8c00",
|
||||
darkorchid: "#9932cc",
|
||||
darkred: "#8b0000",
|
||||
darksalmon: "#e9967a",
|
||||
darkseagreen: "#8fbc8f",
|
||||
darkslateblue: "#483d8b",
|
||||
darkslategray: "#2f4f4f",
|
||||
darkturquoise: "#00ced1",
|
||||
darkviolet: "#9400d3",
|
||||
deeppink: "#ff1493",
|
||||
deepskyblue: "#00bfff",
|
||||
dimgray: "#696969",
|
||||
dodgerblue: "#1e90ff",
|
||||
firebrick: "#b22222",
|
||||
floralwhite: "#fffaf0",
|
||||
forestgreen: "#228b22",
|
||||
fuchsia: "#ff00ff",
|
||||
gainsboro: "#dcdcdc",
|
||||
ghostwhite: "#f8f8ff",
|
||||
gold: "#ffd700",
|
||||
goldenrod: "#daa520",
|
||||
gray: "#808080",
|
||||
green: "#008000",
|
||||
greenyellow: "#adff2f",
|
||||
honeydew: "#f0fff0",
|
||||
hotpink: "#ff69b4",
|
||||
"indianred ": "#cd5c5c",
|
||||
indigo: "#4b0082",
|
||||
ivory: "#fffff0",
|
||||
khaki: "#f0e68c",
|
||||
lavender: "#e6e6fa",
|
||||
lavenderblush: "#fff0f5",
|
||||
lawngreen: "#7cfc00",
|
||||
lemonchiffon: "#fffacd",
|
||||
lightblue: "#add8e6",
|
||||
lightcoral: "#f08080",
|
||||
lightcyan: "#e0ffff",
|
||||
lightgoldenrodyellow: "#fafad2",
|
||||
lightgrey: "#d3d3d3",
|
||||
lightgreen: "#90ee90",
|
||||
lightpink: "#ffb6c1",
|
||||
lightsalmon: "#ffa07a",
|
||||
lightseagreen: "#20b2aa",
|
||||
lightskyblue: "#87cefa",
|
||||
lightslategray: "#778899",
|
||||
lightsteelblue: "#b0c4de",
|
||||
lightyellow: "#ffffe0",
|
||||
lime: "#00ff00",
|
||||
limegreen: "#32cd32",
|
||||
linen: "#faf0e6",
|
||||
magenta: "#ff00ff",
|
||||
maroon: "#800000",
|
||||
mediumaquamarine: "#66cdaa",
|
||||
mediumblue: "#0000cd",
|
||||
mediumorchid: "#ba55d3",
|
||||
mediumpurple: "#9370d8",
|
||||
mediumseagreen: "#3cb371",
|
||||
mediumslateblue: "#7b68ee",
|
||||
mediumspringgreen: "#00fa9a",
|
||||
mediumturquoise: "#48d1cc",
|
||||
mediumvioletred: "#c71585",
|
||||
midnightblue: "#191970",
|
||||
mintcream: "#f5fffa",
|
||||
mistyrose: "#ffe4e1",
|
||||
moccasin: "#ffe4b5",
|
||||
navajowhite: "#ffdead",
|
||||
navy: "#000080",
|
||||
oldlace: "#fdf5e6",
|
||||
olive: "#808000",
|
||||
olivedrab: "#6b8e23",
|
||||
orange: "#ffa500",
|
||||
orangered: "#ff4500",
|
||||
orchid: "#da70d6",
|
||||
palegoldenrod: "#eee8aa",
|
||||
palegreen: "#98fb98",
|
||||
paleturquoise: "#afeeee",
|
||||
palevioletred: "#d87093",
|
||||
papayawhip: "#ffefd5",
|
||||
peachpuff: "#ffdab9",
|
||||
peru: "#cd853f",
|
||||
pink: "#ffc0cb",
|
||||
plum: "#dda0dd",
|
||||
powderblue: "#b0e0e6",
|
||||
purple: "#800080",
|
||||
rebeccapurple: "#663399",
|
||||
red: "#ff0000",
|
||||
rosybrown: "#bc8f8f",
|
||||
royalblue: "#4169e1",
|
||||
saddlebrown: "#8b4513",
|
||||
salmon: "#fa8072",
|
||||
sandybrown: "#f4a460",
|
||||
seagreen: "#2e8b57",
|
||||
seashell: "#fff5ee",
|
||||
sienna: "#a0522d",
|
||||
silver: "#c0c0c0",
|
||||
skyblue: "#87ceeb",
|
||||
slateblue: "#6a5acd",
|
||||
slategray: "#708090",
|
||||
snow: "#fffafa",
|
||||
springgreen: "#00ff7f",
|
||||
steelblue: "#4682b4",
|
||||
tan: "#d2b48c",
|
||||
teal: "#008080",
|
||||
thistle: "#d8bfd8",
|
||||
tomato: "#ff6347",
|
||||
turquoise: "#40e0d0",
|
||||
violet: "#ee82ee",
|
||||
wheat: "#f5deb3",
|
||||
white: "#ffffff",
|
||||
whitesmoke: "#f5f5f5",
|
||||
yellow: "#ffff00",
|
||||
yellowgreen: "#9acd32",
|
||||
};
|
||||
|
||||
return colors[color.toLowerCase() as keyof typeof colors] !== undefined
|
||||
? colors[color.toLowerCase() as keyof typeof colors]
|
||||
: false;
|
||||
}
|
||||
|
||||
function computeContrastRatio(a: number[], b: number[]) {
|
||||
const lum1 = computeLuminance(a[0], a[1], a[2]);
|
||||
const lum2 = computeLuminance(b[0], b[1], b[2]);
|
||||
const brightest = Math.max(lum1, lum2);
|
||||
const darkest = Math.min(lum1, lum2);
|
||||
return (brightest + 0.05) / (darkest + 0.05);
|
||||
}
|
||||
|
||||
function computeLuminance(r: number, g: number, b: number) {
|
||||
const a = [r, g, b].map((v) => {
|
||||
v /= 255;
|
||||
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
||||
});
|
||||
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
|
||||
}
|
||||
|
||||
function hexToRGB(hex: string) {
|
||||
const color = hex.replace("#", "");
|
||||
return [parseInt(color.slice(0, 2), 16), parseInt(color.slice(2, 4), 16), parseInt(color.slice(4, 6), 16)];
|
||||
}
|
||||
|
||||
function normalizeHexCode(hex: string | null, dark: boolean) {
|
||||
if (!hex) {
|
||||
return !dark ? brandColor : darkBrandColor;
|
||||
}
|
||||
hex = hex.replace("#", "");
|
||||
if (hex.length === 3) {
|
||||
hex = hex
|
||||
.split("")
|
||||
.map(function (hex) {
|
||||
return hex + hex;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
|
||||
function getContrastingTextColor(bgColor: string | null, dark: boolean): string {
|
||||
bgColor = bgColor == "" || bgColor == null ? (dark ? darkBrandColor : brandColor) : bgColor;
|
||||
const rgb = hexToRGB(bgColor);
|
||||
const whiteContrastRatio = computeContrastRatio(rgb, [255, 255, 255]);
|
||||
const blackContrastRatio = computeContrastRatio(rgb, [41, 41, 41]); //#292929
|
||||
return whiteContrastRatio > blackContrastRatio ? brandTextColor : brandColor;
|
||||
}
|
||||
|
||||
export function isValidHexCode(val: string | null) {
|
||||
if (val) {
|
||||
val = val.indexOf("#") === 0 ? val : "#" + val;
|
||||
const regex = new RegExp("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$");
|
||||
return regex.test(val);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function fallBackHex(val: string | null, dark: boolean): string {
|
||||
if (val) if (colorNameToHex(val)) return colorNameToHex(val) as string;
|
||||
return dark ? darkBrandColor : brandColor;
|
||||
}
|
||||
|
||||
const BrandColor = ({
|
||||
lightVal = brandColor,
|
||||
darkVal = darkBrandColor,
|
||||
}: {
|
||||
lightVal: string | undefined | null;
|
||||
darkVal: string | undefined | null;
|
||||
}) => {
|
||||
const embedBrandingColors = useBrandColors();
|
||||
lightVal = embedBrandingColors.brandColor || lightVal;
|
||||
// convert to 6 digit equivalent if 3 digit code is entered
|
||||
lightVal = normalizeHexCode(lightVal, false);
|
||||
darkVal = normalizeHexCode(darkVal, true);
|
||||
// ensure acceptable hex-code
|
||||
lightVal = isValidHexCode(lightVal)
|
||||
? lightVal?.indexOf("#") === 0
|
||||
? lightVal
|
||||
: "#" + lightVal
|
||||
: fallBackHex(lightVal, false);
|
||||
darkVal = isValidHexCode(darkVal)
|
||||
? darkVal?.indexOf("#") === 0
|
||||
? darkVal
|
||||
: "#" + darkVal
|
||||
: fallBackHex(darkVal, true);
|
||||
return (
|
||||
<Head>
|
||||
<style>
|
||||
{`body {
|
||||
/* green--500*/
|
||||
--booking-highlight-color: ${embedBrandingColors.highlightColor || "#10B981"};
|
||||
/* gray--200 */
|
||||
--booking-lightest-color: ${embedBrandingColors.lightestColor || "#E1E1E1"};
|
||||
/* gray--400 */
|
||||
--booking-lighter-color: ${embedBrandingColors.lighterColor || "#ACACAC"};
|
||||
/* gray--500 */
|
||||
--booking-light-color: ${embedBrandingColors.lightColor || "#888888"};
|
||||
/* gray--600 */
|
||||
--booking-median-color: ${embedBrandingColors.medianColor || "#494949"};
|
||||
/* gray--800 */
|
||||
--booking-dark-color: ${embedBrandingColors.darkColor || "#313131"};
|
||||
/* gray--900 */
|
||||
--booking-darker-color: ${embedBrandingColors.darkerColor || "#292929"};
|
||||
--brand-color: ${lightVal};
|
||||
--brand-text-color: ${getContrastingTextColor(lightVal, true)};
|
||||
--brand-color-dark-mode: ${darkVal};
|
||||
--brand-text-color-dark-mode: ${getContrastingTextColor(darkVal, true)};
|
||||
`}
|
||||
</style>
|
||||
</Head>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrandColor;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { SkeletonText } from "../";
|
||||
import { ShellSubHeading } from "../Shell";
|
||||
import { SkeletonText } from "../v2/core/skeleton";
|
||||
|
||||
/** @deprecated Use `packages/ui/v2/core/apps/SkeletonLoader.tsx` */
|
||||
function SkeletonLoader({ className }: { className?: string }) {
|
||||
@@ -22,16 +22,16 @@ function SkeletonItem() {
|
||||
<li className="group flex w-full items-center justify-between p-3">
|
||||
<div className="flex-grow truncate text-sm">
|
||||
<div className="flex justify-start space-x-2">
|
||||
<SkeletonText width="10" height="10" />
|
||||
<SkeletonText className="h-3 w-3" />
|
||||
<div className="space-y-2">
|
||||
<SkeletonText height="4" width="32" />
|
||||
<SkeletonText height="4" width="16" />
|
||||
<SkeletonText className="h-1 w-8" />
|
||||
<SkeletonText className="h-1 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 lg:flex">
|
||||
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
||||
<SkeletonText width="32" height="11" />
|
||||
<SkeletonText className="h-3 w-8" />
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid";
|
||||
|
||||
import dayjs, { Dayjs } from "@calcom/dayjs";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { daysInMonth, yyyymmdd } from "@calcom/lib/date-fns";
|
||||
import { weekdayNames } from "@calcom/lib/weekday";
|
||||
import { SkeletonText } from "@calcom/ui/skeleton";
|
||||
|
||||
export type DatePickerProps = {
|
||||
/** which day of the week to render the calendar. Usually Sunday (=0) or Monday (=1) - default: Sunday */
|
||||
weekStart?: 0 | 1 | 2 | 3 | 4 | 5 | 6;
|
||||
/** Fires whenever a selected date is changed. */
|
||||
onChange: (date: Dayjs) => void;
|
||||
/** Fires when the month is changed. */
|
||||
onMonthChange?: (date: Dayjs) => void;
|
||||
/** which date is currently selected (not tracked from here) */
|
||||
selected?: Dayjs;
|
||||
/** defaults to current date. */
|
||||
minDate?: Dayjs;
|
||||
/** Furthest date selectable in the future, default = UNLIMITED */
|
||||
maxDate?: Dayjs;
|
||||
/** locale, any IETF language tag, e.g. "hu-HU" - defaults to Browser settings */
|
||||
locale: string;
|
||||
/** Defaults to [], which dates are not bookable. Array of valid dates like: ["2022-04-23", "2022-04-24"] */
|
||||
excludedDates?: string[];
|
||||
/** defaults to all, which dates are bookable (inverse of excludedDates) */
|
||||
includedDates?: string[];
|
||||
/** allows adding classes to the container */
|
||||
className?: string;
|
||||
/** Shows a small loading spinner next to the month name */
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
export const Day = ({
|
||||
date,
|
||||
active,
|
||||
...props
|
||||
}: JSX.IntrinsicElements["button"] & { active: boolean; date: Dayjs }) => {
|
||||
return (
|
||||
<button
|
||||
className={classNames(
|
||||
"hover:border-brand disabled:text-bookinglighter absolute top-0 left-0 right-0 bottom-0 mx-auto w-full rounded-sm border border-transparent text-center text-sm font-medium disabled:cursor-default disabled:border-transparent disabled:font-light dark:hover:border-white disabled:dark:border-transparent",
|
||||
active
|
||||
? "bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast"
|
||||
: !props.disabled
|
||||
? " bg-gray-100 dark:bg-gray-600 dark:text-white"
|
||||
: ""
|
||||
)}
|
||||
data-testid="day"
|
||||
data-disabled={props.disabled}
|
||||
{...props}>
|
||||
{date.date()}
|
||||
{date.isToday() && <span className="absolute left-0 bottom-0 mx-auto -mb-px w-full text-4xl">.</span>}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const Days = ({
|
||||
// minDate,
|
||||
excludedDates = [],
|
||||
includedDates,
|
||||
browsingDate,
|
||||
weekStart,
|
||||
DayComponent = Day,
|
||||
selected,
|
||||
...props
|
||||
}: Omit<DatePickerProps, "locale" | "className" | "weekStart"> & {
|
||||
DayComponent?: React.FC<React.ComponentProps<typeof Day>>;
|
||||
browsingDate: Dayjs;
|
||||
weekStart: number;
|
||||
}) => {
|
||||
// Create placeholder elements for empty days in first week
|
||||
const weekdayOfFirst = browsingDate.day();
|
||||
|
||||
const days: (Dayjs | null)[] = Array((weekdayOfFirst - weekStart + 7) % 7).fill(null);
|
||||
for (let day = 1, dayCount = daysInMonth(browsingDate); day <= dayCount; day++) {
|
||||
const date = browsingDate.set("date", day);
|
||||
days.push(date);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{days.map((day, idx) => (
|
||||
<div key={day === null ? `e-${idx}` : `day-${day.format()}`} className="relative w-full pt-[100%]">
|
||||
{day === null ? (
|
||||
<div key={`e-${idx}`} />
|
||||
) : props.isLoading ? (
|
||||
<button
|
||||
className="dark:bg-darkgray-100 dark:text-darkgray-400 absolute top-0 left-0 right-0 bottom-0 mx-auto flex w-full items-center justify-center rounded-sm border-transparent bg-gray-50 text-center text-gray-400 opacity-50"
|
||||
key={`e-${idx}`}
|
||||
disabled>
|
||||
<SkeletonText width="5" height="4" />
|
||||
</button>
|
||||
) : (
|
||||
<DayComponent
|
||||
date={day}
|
||||
onClick={() => {
|
||||
props.onChange(day);
|
||||
window.scrollTo({
|
||||
top: 360,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}}
|
||||
disabled={
|
||||
(includedDates && !includedDates.includes(yyyymmdd(day))) ||
|
||||
excludedDates.includes(yyyymmdd(day))
|
||||
}
|
||||
active={selected ? yyyymmdd(selected) === yyyymmdd(day) : false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const DatePicker = ({
|
||||
weekStart = 0,
|
||||
className,
|
||||
locale,
|
||||
selected,
|
||||
onMonthChange,
|
||||
...passThroughProps
|
||||
}: DatePickerProps & Partial<React.ComponentProps<typeof Days>>) => {
|
||||
const browsingDate = passThroughProps.browsingDate || dayjs().startOf("month");
|
||||
|
||||
const changeMonth = (newMonth: number) => {
|
||||
if (onMonthChange) {
|
||||
onMonthChange(browsingDate.add(newMonth, "month"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="mb-4 flex justify-between px-4 text-xl font-light sm:px-0">
|
||||
<span className="w-1/2 dark:text-white">
|
||||
{browsingDate ? (
|
||||
<>
|
||||
<span className="text-bookingdarker text-base font-semibold dark:text-white">
|
||||
{browsingDate.format("MMMM")},
|
||||
</span>{" "}
|
||||
<span className="text-bookinglight text-base font-medium">{browsingDate.format("YYYY")}</span>
|
||||
</>
|
||||
) : (
|
||||
<SkeletonText width="24" height="8" />
|
||||
)}
|
||||
</span>
|
||||
<div className="text-black dark:text-white">
|
||||
<button
|
||||
onClick={() => changeMonth(-1)}
|
||||
className={classNames(
|
||||
"group p-1 opacity-50 hover:opacity-100 ltr:mr-2 rtl:ml-2",
|
||||
!browsingDate.isAfter(dayjs()) && "disabled:text-bookinglighter hover:opacity-50"
|
||||
)}
|
||||
disabled={!browsingDate.isAfter(dayjs())}
|
||||
data-testid="decrementMonth">
|
||||
<ChevronLeftIcon className="h-5 w-5" />
|
||||
</button>
|
||||
<button
|
||||
className="group p-1 opacity-50 hover:opacity-100"
|
||||
onClick={() => changeMonth(+1)}
|
||||
data-testid="incrementMonth">
|
||||
<ChevronRightIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="border-bookinglightest mb-2 grid grid-cols-7 gap-4 border-t border-b text-center dark:border-gray-800 md:mb-0 md:border-0">
|
||||
{weekdayNames(locale, weekStart, "short").map((weekDay) => (
|
||||
<div key={weekDay} className="text-bookinglight my-4 text-xs uppercase tracking-widest">
|
||||
{weekDay}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-7 gap-1 text-center">
|
||||
<Days weekStart={weekStart} selected={selected} {...passThroughProps} browsingDate={browsingDate} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatePicker;
|
||||
@@ -2,8 +2,6 @@ export { default as Button } from "./Button";
|
||||
export { default as EmptyScreen } from "./EmptyScreen";
|
||||
export { default as Select } from "./form/Select";
|
||||
export { default as Loader } from "./Loader";
|
||||
export * from "./skeleton";
|
||||
export { default as Switch } from "./Switch";
|
||||
export { default as Tooltip } from "./Tooltip";
|
||||
export { default as BooleanToggleGroup } from "./BooleanToggleGroup";
|
||||
export * from "./Icon";
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
|
||||
type SkeletonBaseProps = {
|
||||
/** @deprecated @see https://tailwindcss.com/docs/content-configuration#dynamic-class-names */
|
||||
width?: string;
|
||||
/** @deprecated @see https://tailwindcss.com/docs/content-configuration#dynamic-class-names */
|
||||
height?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
interface AvatarProps extends SkeletonBaseProps {
|
||||
// Limit this cause we don't use avatars bigger than this
|
||||
/** @deprecated @see https://tailwindcss.com/docs/content-configuration#dynamic-class-names */
|
||||
width?: "2" | "3" | "4" | "5" | "6" | "8" | "12";
|
||||
/** @deprecated @see https://tailwindcss.com/docs/content-configuration#dynamic-class-names */
|
||||
height?: "2" | "3" | "4" | "5" | "6" | "8" | "12";
|
||||
}
|
||||
|
||||
interface SkeletonContainer {
|
||||
as?: keyof JSX.IntrinsicElements;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
const SkeletonAvatar: React.FC<AvatarProps> = ({ width, height, className }) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
`mt-1 rounded-full bg-gray-200 ltr:mr-2 rtl:ml-2 w-${width} h-${height}`,
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
const SkeletonText: React.FC<SkeletonBaseProps> = ({ width = "", height = "", className = "" }) => {
|
||||
className = width ? `${className} w-${width}` : className;
|
||||
className = height ? `${className} h-${height}` : className;
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
`dark:white-300 animate-pulse rounded-md bg-gray-300 empty:before:inline-block empty:before:content-[''] dark:bg-gray-300/50 `,
|
||||
className
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
const SkeletonButton: React.FC<SkeletonBaseProps> = ({ width, height, className }) => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
<div className={classNames(`w-${width} h-${height} bg-gray-200`, className)} />
|
||||
</SkeletonContainer>
|
||||
);
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
const SkeletonContainer: React.FC<SkeletonContainer> = ({ children, as, className }) => {
|
||||
const Component = as || "div";
|
||||
return <Component className={classNames("animate-pulse", className)}>{children}</Component>;
|
||||
};
|
||||
|
||||
export { SkeletonAvatar, SkeletonText, SkeletonButton, SkeletonContainer };
|
||||
Reference in New Issue
Block a user