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:
co-authored by
Peer Richelsen
parent
42c128b790
commit
631e974769
@@ -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")}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 };
|
||||
|
||||
Reference in New Issue
Block a user