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:
Jeroen Reumkens
2022-11-14 10:31:36 +00:00
committed by GitHub
co-authored by zomars kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
parent d83883e7e4
commit f513ce8687
16 changed files with 31 additions and 913 deletions
-309
View File
@@ -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>
);
}
+1
View File
@@ -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 (
-56
View File
@@ -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>
);
}
-269
View File
@@ -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;
+5 -5
View File
@@ -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>
-181
View File
@@ -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
View File
@@ -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";
-66
View File
@@ -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 };