Improved load state (#4696)

* Improved load state

* Don't use editable heading if it is readonly

* Fix ts errors

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Alex van Andel 2022-09-28 19:05:28 +01:00 committed by GitHub
parent 14a6c5a03f
commit 58dd5308ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 74 additions and 49 deletions

View File

@ -1,50 +1,48 @@
import { useState } from "react";
import classNames from "classnames";
import React, { useState } from "react";
import { ControllerRenderProps } from "react-hook-form";
import { Icon } from "@calcom/ui/Icon";
const EditableHeading = ({
title,
const EditableHeading = function EditableHeading({
value,
onChange,
placeholder = "",
readOnly = false,
isReady,
...passThroughProps
}: {
title: string;
onChange?: (value: string) => void;
placeholder?: string;
readOnly?: boolean;
}) => {
isReady?: boolean;
} & Omit<JSX.IntrinsicElements["input"], "name" | "onChange"> &
ControllerRenderProps) {
const [isEditing, setIsEditing] = useState(false);
const enableEditing = () => !readOnly && setIsEditing(true);
const enableEditing = () => setIsEditing(true);
return (
<div className="group relative cursor-pointer" onClick={enableEditing}>
{!isEditing ? (
<>
<h1
style={{ fontSize: 22, letterSpacing: "-0.0009em" }}
className="inline pl-0 normal-case text-gray-900 focus:text-black group-hover:text-gray-500">
{title}
</h1>
{!readOnly ? (
<Icon.FiEdit2 className="ml-1 -mt-1 inline h-4 w-4 text-gray-700 group-hover:text-gray-500" />
) : null}
</>
) : (
<div style={{ marginBottom: -11 }}>
<div className="flex items-center">
<label className="min-w-8 relative inline-block">
<span className="whitespace-pre text-xl tracking-normal text-transparent">{value}&nbsp;</span>
{!isEditing && isReady && (
<Icon.FiEdit2 className="ml-1 inline h-4 w-4 align-top text-gray-700 group-hover:text-gray-500" />
)}
<input
{...passThroughProps}
type="text"
autoFocus
style={{ top: -6, fontSize: 22 }}
value={value}
required
className="relative h-10 w-full cursor-pointer border-none bg-transparent pl-0 text-gray-900 hover:text-gray-700 focus:text-black focus:outline-none focus:ring-0"
placeholder={placeholder}
defaultValue={title}
className={classNames(
"absolute top-0 left-0 w-full cursor-pointer border-none bg-transparent p-0 align-top text-xl text-gray-900 hover:text-gray-700 focus:text-black focus:outline-none focus:ring-0"
)}
onFocus={(e) => {
setIsEditing(true);
passThroughProps.onFocus && passThroughProps.onFocus(e);
}}
onBlur={(e) => {
setIsEditing(false);
onChange && onChange(e.target.value);
passThroughProps.onBlur && passThroughProps.onBlur(e);
}}
onChange={(e) => onChange && onChange(e.target.value)}
/>
</div>
)}
</label>
</div>
</div>
);
};

View File

@ -1,3 +1,4 @@
import classNames from "classnames";
import React from "react";
import { SkeletonText } from "@calcom/ui/v2";
@ -32,9 +33,13 @@ function SkeletonItem() {
);
}
export const AvailabilitySelectSkeletonLoader = () => {
export const SelectSkeletonLoader = ({ className }: { className?: string }) => {
return (
<li className="group flex w-full items-center justify-between rounded-sm border border-gray-200 px-[10px] py-3">
<li
className={classNames(
"group flex w-full items-center justify-between rounded-sm border border-gray-200 px-[10px] py-3",
className
)}>
<div className="flex-grow truncate text-sm">
<div className="flex justify-between">
<SkeletonText className="h-4 w-32" />

View File

@ -11,7 +11,7 @@ import Button from "@calcom/ui/v2/core/Button";
import Select from "@calcom/ui/v2/core/form/Select";
import { SkeletonText } from "@calcom/ui/v2/core/skeleton";
import { AvailabilitySelectSkeletonLoader } from "@components/v2/availability/SkeletonLoader";
import { SelectSkeletonLoader } from "@components/v2/availability/SkeletonLoader";
type AvailabilityOption = {
label: string;
@ -30,7 +30,7 @@ const AvailabilitySelect = ({
}) => {
const { data, isLoading } = trpc.useQuery(["viewer.availability.list"]);
if (isLoading) {
return <AvailabilitySelectSkeletonLoader />;
return <SelectSkeletonLoader />;
}
const schedules = data?.schedules || [];

View File

@ -2,6 +2,8 @@
* @deprecated modifications to this file should be v2 only
* Use `/apps/web/pages/v2/availability/[schedule].tsx` instead
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { GetStaticPaths, GetStaticProps } from "next";
import { useRouter } from "next/router";
import { useState } from "react";

View File

@ -1,3 +1,9 @@
/**
* @deprecated modifications to this file should be v2 only
* Use `apps/web/pages/v2/event-types/[type].tsx` instead
*/
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import { zodResolver } from "@hookform/resolvers/zod";
import { EventTypeCustomInput, MembershipRole, PeriodType, Prisma, SchedulingType } from "@prisma/client";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";

View File

@ -19,11 +19,12 @@ import Switch from "@calcom/ui/v2/core/Switch";
import VerticalDivider from "@calcom/ui/v2/core/VerticalDivider";
import { Form, Label } from "@calcom/ui/v2/core/form/fields";
import showToast from "@calcom/ui/v2/core/notifications";
import { SkeletonText } from "@calcom/ui/v2/core/skeleton";
import { Skeleton, SkeletonText } from "@calcom/ui/v2/core/skeleton";
import { HttpError } from "@lib/core/http/error";
import EditableHeading from "@components/ui/EditableHeading";
import { SelectSkeletonLoader } from "@components/v2/availability/SkeletonLoader";
const querySchema = z.object({
schedule: stringOrNumber,
@ -45,7 +46,7 @@ export default function Availability({ schedule }: { schedule: number }) {
const { data, isLoading } = trpc.useQuery(["viewer.availability.schedule", { scheduleId: schedule }]);
const form = useForm<AvailabilityFormValues>();
const { control, reset, setValue } = form;
const { control, reset } = form;
useEffect(() => {
if (!isLoading && data) {
@ -83,20 +84,33 @@ export default function Availability({ schedule }: { schedule: number }) {
backPath="/availability"
title={t("availability_title", { availabilityTitle: data?.schedule.name })}
heading={
<EditableHeading title={data?.schedule.name || ""} onChange={(name) => setValue("name", name)} />
<Controller
control={form.control}
name="name"
render={({ field }) => <EditableHeading isReady={!isLoading} {...field} />}
/>
}
subtitle={
data ? (
data.schedule.availability.map((availability) => (
<span key={availability.id}>
{availabilityAsString(availability, { locale: i18n.language })}
<br />
</span>
))
) : (
<SkeletonText className="h-4 w-48" />
)
}
subtitle={data?.schedule.availability.map((availability) => (
<span key={availability.id}>
{availabilityAsString(availability, { locale: i18n.language })}
<br />
</span>
))}
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("set_to_default")}
</Label>
</Skeleton>
<Switch
id="hiddenSwitch"
disabled={isLoading}
@ -159,11 +173,11 @@ export default function Availability({ schedule }: { schedule: number }) {
value ? (
<TimezoneSelect
value={value}
className="focus:border-brand mt-1 block rounded-md border-gray-300 text-sm"
className="focus:border-brand mt-1 block w-72 rounded-md border-gray-300 text-sm"
onChange={(timezone) => onChange(timezone.value)}
/>
) : (
<SkeletonText className="h-6 w-full" />
<SelectSkeletonLoader className="w-72" />
)
}
/>