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:
parent
14a6c5a03f
commit
58dd5308ac
|
@ -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} </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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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 || [];
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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" />
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
|
Loading…
Reference in New Issue
Block a user