Fix flash of untranslated content by adding inline Skeleton Loaders on EventType (#4519)

* Add support for easy use of inline skeleton

* Fix user being taken to /v2 path

* Fixes

* Fix TS

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
Hariom Balhara
2022-09-21 16:01:47 +00:00
committed by GitHub
co-authored by Peer Richelsen
parent 42c128b790
commit 631e974769
6 changed files with 112 additions and 30 deletions
@@ -19,6 +19,7 @@ import {
DialogContent,
Label,
showToast,
Skeleton,
Switch,
TextField,
Tooltip,
@@ -37,7 +38,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
const connectedCalendarsQuery = trpc.useQuery(["viewer.connectedCalendars"]);
const formMethods = useFormContext<FormValues>();
const { t } = useLocale();
const utils = trpc.useContext();
const [showEventNameTip, setShowEventNameTip] = useState(false);
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!eventType.hashedLink);
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
@@ -138,8 +138,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
}}
/>
<div className="flex flex-col">
<Label className="text-sm font-semibold leading-none text-black">{t("additional_inputs")}</Label>
<p className="-mt-2 text-sm leading-normal text-gray-600">{t("additional_input_description")}</p>
<Skeleton as={Label} className="text-sm font-semibold leading-none text-black">
{t("additional_inputs")}
</Skeleton>
<Skeleton as="p" className="-mt-2 text-sm leading-normal text-gray-600">
{t("additional_input_description")}
</Skeleton>
</div>
</div>
<ul className="my-4" ref={animationRef}>
@@ -184,12 +188,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
fitToHeight={true}
/>
<div className="flex flex-col">
<Label className="text-sm font-semibold leading-none text-black">
<Skeleton as={Label} className="text-sm font-semibold leading-none text-black">
{t("requires_confirmation")}
</Label>
<p className="-mt-2 text-sm leading-normal text-gray-600">
</Skeleton>
<Skeleton as="p" className="-mt-2 text-sm leading-normal text-gray-600">
{t("requires_confirmation_description")}
</p>
</Skeleton>
</div>
</div>
)}
@@ -209,8 +213,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
disabled={seatsEnabled}
/>
<div className="flex flex-col">
<Label className="text-sm font-semibold leading-none text-black">{t("disable_guests")}</Label>
<p className="-mt-2 text-sm leading-normal text-gray-600">{t("disable_guests_description")}</p>
<Skeleton as={Label} className="text-sm font-semibold leading-none text-black">
{t("disable_guests")}
</Skeleton>
<Skeleton as="p" className="-mt-2 text-sm leading-normal text-gray-600">
{t("disable_guests_description")}
</Skeleton>
</div>
</div>
)}
@@ -230,8 +238,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
onCheckedChange={(e) => onChange(e)}
/>
<div className="flex flex-col">
<Label className="text-sm font-semibold leading-none text-black">{t("disable_notes")}</Label>
<p className="-mt-2 text-sm leading-normal text-gray-600">{t("disable_notes_description")}</p>
<Skeleton as={Label} className="text-sm font-semibold leading-none text-black">
{t("disable_notes")}
</Skeleton>
<Skeleton as="p" className="-mt-2 text-sm leading-normal text-gray-600">
{t("disable_notes_description")}
</Skeleton>
</div>
</div>
)}
@@ -255,8 +267,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
}}
/>
<div className="flex flex-col">
<Label className="text-sm font-semibold leading-none text-black">{t("private_link")}</Label>
<p className="-mt-2 text-sm leading-normal text-gray-600">{t("private_link_description")}</p>
<Skeleton as={Label} className="text-sm font-semibold leading-none text-black">
{t("private_link")}
</Skeleton>
<Skeleton as="p" className="-mt-2 text-sm leading-normal text-gray-600">
{t("private_link_description")}
</Skeleton>
</div>
</div>
@@ -10,7 +10,7 @@ import { getEventLocationType, EventLocationType } from "@calcom/app-store/locat
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Icon } from "@calcom/ui/Icon";
import { Select, Label, TextField, Button } from "@calcom/ui/v2";
import { Select, Label, TextField, Button, Skeleton } from "@calcom/ui/v2";
import { slugify } from "@lib/slugify";
@@ -222,7 +222,9 @@ export const EventSetupTab = (
}}
/>
<div>
<Label>{t("location")}</Label>
<Skeleton as={Label} loadingClassName="w-16">
{t("location")}
</Skeleton>
<Controller
name="locations"
control={formMethods.control}
@@ -29,6 +29,7 @@ import Dropdown, {
} from "@calcom/ui/v2/core/Dropdown";
import Shell from "@calcom/ui/v2/core/Shell";
import VerticalDivider from "@calcom/ui/v2/core/VerticalDivider";
import { Skeleton } from "@calcom/ui/v2/core/skeleton";
import { ClientSuspense } from "@components/ClientSuspense";
import { EmbedButton, EmbedDialog } from "@components/Embed";
@@ -159,9 +160,12 @@ function EventTypeSingleLayout({
CTA={
<div className="flex items-center justify-end">
<div className="flex items-center rounded-md px-2 sm:hover:bg-gray-100">
<Label htmlFor="hiddenSwitch" className="mt-2 hidden cursor-pointer self-center pr-2 sm:inline">
<Skeleton
as={Label}
htmlFor="hiddenSwitch"
className="mt-2 hidden cursor-pointer self-center pr-2 sm:inline">
{t("hide_from_profile")}
</Label>
</Skeleton>
<Switch
id="hiddenSwitch"
defaultChecked={formMethods.getValues("hidden")}
+13 -10
View File
@@ -1,5 +1,5 @@
import { useId } from "@radix-ui/react-id";
import React, { forwardRef, ReactElement, ReactNode, Ref, useCallback, useMemo, useState } from "react";
import React, { forwardRef, ReactElement, ReactNode, Ref, useCallback, useState } from "react";
import { Check, Circle, Info, X, Eye, EyeOff } from "react-feather";
import {
FieldErrors,
@@ -13,7 +13,7 @@ import {
import classNames from "@calcom/lib/classNames";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Tooltip } from "@calcom/ui/v2";
import { Skeleton, Tooltip } from "@calcom/ui/v2";
import showToast from "@calcom/ui/v2/core/notifications";
import { Alert } from "../../../Alert";
@@ -37,7 +37,7 @@ export function Label(props: JSX.IntrinsicElements["label"]) {
return (
<label
{...props}
className={classNames("block pb-2 text-sm font-medium leading-none text-gray-700", props.className)}>
className={classNames("mb-2 block text-sm font-medium leading-none text-gray-700", props.className)}>
{props.children}
</label>
);
@@ -166,15 +166,14 @@ type InputFieldProps = {
const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputField(props, ref) {
const id = useId();
const { t: _t } = useLocale();
const { t: _t, isLocaleReady } = useLocale();
const t = props.t || _t;
const name = props.name || "";
const {
label = t(name),
labelProps,
labelClassName,
/** Prevents displaying untranslated placeholder keys */
placeholder = t(name + "_placeholder") !== name + "_placeholder" ? t(name + "_placeholder") : "",
placeholder = isLocaleReady ? t(name + "_placeholder") : "",
className,
addOnLeading,
addOnSuffix,
@@ -188,15 +187,19 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
...passThrough
} = props;
const translatedPlaceholder = isLocaleReady ? placeholder : "";
return (
<div className={classNames(containerClassName)}>
{!!name && (
<Label
<Skeleton
as={Label}
htmlFor={id}
loadingClassName="w-16"
{...labelProps}
className={classNames(labelClassName, labelSrOnly && "sr-only", props.error && "text-red-900")}>
{label}
</Label>
</Skeleton>
)}
{addOnLeading || addOnSuffix ? (
<div
@@ -221,7 +224,7 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
</div>
<Input
id={id}
placeholder={placeholder}
placeholder={translatedPlaceholder}
className={classNames(
className,
addOnLeading && "rounded-l-none",
@@ -233,7 +236,7 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
/>
</div>
) : (
<Input id={id} placeholder={placeholder} className={className} {...passThrough} ref={ref} />
<Input id={id} placeholder={translatedPlaceholder} className={className} {...passThrough} ref={ref} />
)}
<HintsOrErrors hintErrors={hintErrors} fieldName={name} t={t} />
{hint && <div className="text-gray mt-2 flex items-center text-sm text-gray-700">{hint}</div>}
@@ -7,6 +7,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SVGComponent } from "@calcom/types/SVGComponent";
import { Icon } from "@calcom/ui/Icon";
import { Skeleton } from "../../skeleton";
export type VerticalTabItemProps = {
name: string;
info?: string;
@@ -55,8 +57,12 @@ const VerticalTabItem = function ({
<props.icon className="mr-[10px] h-[16px] w-[16px] self-start stroke-[2px] md:mt-0" />
)}
<div>
<p>{t(name)}</p>
{info && <p className="pt-1 text-xs font-normal">{t(info)}</p>}
<Skeleton as="p">{t(name)}</Skeleton>
{info && (
<Skeleton as="p" className="mt-1 text-xs font-normal">
{t(info)}
</Skeleton>
)}
</div>
{!disableChevron && isCurrent && (
<div className="ml-auto self-center">
+52 -1
View File
@@ -1,4 +1,7 @@
import React from "react";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
type SkeletonBaseProps = {
className?: string;
@@ -14,6 +17,54 @@ 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,
@@ -42,4 +93,4 @@ const SkeletonContainer: React.FC<SkeletonContainer> = ({ children, as, classNam
return <Component className={classNames("animate-pulse", className)}>{children}</Component>;
};
export { SkeletonAvatar, SkeletonText, SkeletonButton, SkeletonContainer };
export { Skeleton, SkeletonAvatar, SkeletonText, SkeletonButton, SkeletonContainer };