Compare commits

...

12 Commits

Author SHA1 Message Date
Alex van Andel 52dbd3d0a8 Cleanup 2022-10-05 19:42:44 +01:00
Alex van Andel 9e627a3e21 // intentially empty no longer needed 2022-10-05 19:40:29 +01:00
Alex van Andel b2aeb1873d Revert irrelevant changes 2022-10-05 19:36:48 +01:00
Alex van Andel 302e62b020 Merged with main 2022-10-05 19:17:37 +01:00
Alex van Andel 5f38cd1732 Improve responsive behaviour 2022-10-05 19:02:28 +01:00
Alex van Andel 3f11fd1f2f Do not allow disabling a schedule when the schedule is default 2022-10-05 18:31:22 +01:00
Alex van Andel 46d5e49c24 Updates all event types now, further testing needed 2022-10-04 00:16:34 +01:00
Alex van Andel c56620aa18 Allow overload of ItemIndicator 2022-10-03 23:10:36 +01:00
Alex van Andel 5eca49bb94 tRPC v10 merge 2022-10-03 22:05:27 +01:00
Alex van Andel 7adb52b172 Merge branch 'main' into feature/active-on-toggler 2022-10-03 21:53:00 +01:00
Alex van Andel b49d7d335d Fixed merge conflicts 2022-09-29 16:26:39 +01:00
Alex van Andel 8d111cede5 Starting to take shape 2022-09-28 23:54:41 +01:00
9 changed files with 3364 additions and 4975 deletions

View File

@ -1,193 +0,0 @@
/**
* @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";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
import { DEFAULT_SCHEDULE, availabilityAsString } from "@calcom/lib/availability";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { stringOrNumber } from "@calcom/prisma/zod-utils";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import Button from "@calcom/ui/Button";
import { BadgeCheckIcon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";
import Switch from "@calcom/ui/Switch";
import TimezoneSelect from "@calcom/ui/form/TimezoneSelect";
import { Form } from "@calcom/ui/form/fields";
import { QueryCell } from "@lib/QueryCell";
import { HttpError } from "@lib/core/http/error";
import Schedule from "@components/availability/Schedule";
import EditableHeading from "@components/ui/EditableHeading";
/**
* @deprecated modifications to this file should be v2 only
* Use `/apps/web/pages/v2/availability/[schedule].tsx` instead
*/
export function AvailabilityForm(props: inferQueryOutput<"viewer.availability.schedule">) {
const { t } = useLocale();
const router = useRouter();
const utils = trpc.useContext();
const form = useForm({
defaultValues: {
schedule: props.availability || DEFAULT_SCHEDULE,
isDefault: !!props.isDefault,
timeZone: props.timeZone,
},
});
const updateMutation = trpc.useMutation("viewer.availability.schedule.update", {
onSuccess: async ({ schedule }) => {
await utils.invalidateQueries(["viewer.availability.schedule"]);
await router.push("/availability");
showToast(
t("availability_updated_successfully", {
scheduleName: schedule.name,
}),
"success"
);
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
},
});
return (
<Form
form={form}
handleSubmit={async (values) => {
updateMutation.mutate({
scheduleId: parseInt(router.query.schedule as string, 10),
name: props.schedule.name,
...values,
});
}}
className="grid grid-cols-3 gap-2">
<div className="col-span-3 space-y-2 lg:col-span-2">
<div className="divide-y rounded-sm border border-gray-200 bg-white px-4 py-5 sm:p-6">
<h3 className="mb-5 text-base font-medium leading-6 text-gray-900">{t("change_start_end")}</h3>
<Schedule name="schedule" />
</div>
<div className="space-x-2 text-right">
<Button color="secondary" href="/availability" tabIndex={-1}>
{t("cancel")}
</Button>
<Button>{t("save")}</Button>
</div>
</div>
<div className="min-w-40 col-span-3 ml-2 space-y-2 lg:col-span-1">
{props.isDefault ? (
<div className="inline-block cursor-default rounded border border-gray-300 bg-gray-200 px-2 py-0.5 pl-1.5 text-sm font-medium text-neutral-800">
<span className="flex items-center">
<BadgeCheckIcon className="mr-1 h-4 w-4" /> {t("default")}
</span>
</div>
) : (
<Controller
name="isDefault"
render={({ field: { onChange, value } }) => (
<Switch label={t("set_to_default")} onCheckedChange={onChange} checked={value} />
)}
/>
)}
<div>
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
{t("timezone")}
</label>
<div className="mt-1">
<Controller
name="timeZone"
render={({ field: { onChange, value } }) => (
<TimezoneSelect
value={value}
className="focus:border-brand mt-1 block w-full rounded-md border-gray-300 text-sm"
onChange={(timezone) => onChange(timezone.value)}
/>
)}
/>
</div>
</div>
<div className="mt-2 rounded-sm border border-gray-200 px-4 py-5 sm:p-6 ">
<h3 className="text-base font-medium leading-6 text-gray-900">
{t("something_doesnt_look_right")}
</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500">
<p>{t("troubleshoot_availability")}</p>
</div>
<div className="mt-5">
<Button href="/availability/troubleshoot" color="secondary">
{t("launch_troubleshooter")}
</Button>
</div>
</div>
</div>
</Form>
);
}
const querySchema = z.object({
schedule: stringOrNumber,
});
export default function Availability() {
const router = useRouter();
const { i18n } = useLocale();
const { schedule: scheduleId } = router.isReady ? querySchema.parse(router.query) : { schedule: -1 };
const query = trpc.useQuery(["viewer.availability.schedule", { scheduleId }], { enabled: router.isReady });
const [name, setName] = useState<string>();
return (
<div>
<QueryCell
query={query}
success={({ data }) => {
return (
<Shell
heading={<EditableHeading title={name || data.schedule.name} onChange={setName} />}
subtitle={data.schedule.availability.map((availability) => (
<span key={availability.id}>
{availabilityAsString(availability, { locale: i18n.language })}
<br />
</span>
))}>
<AvailabilityForm
{...{ ...data, schedule: { ...data.schedule, name: name || data.schedule.name } }}
/>
</Shell>
);
}}
/>
</div>
);
}
export const getStaticProps: GetStaticProps = (ctx) => {
const params = querySchema.safeParse(ctx.params);
if (!params.success) return { notFound: true };
return {
props: {
schedule: params.data.schedule,
},
revalidate: 10, // seconds
};
};
export const getStaticPaths: GetStaticPaths = () => {
return {
paths: [],
fallback: "blocking",
};
};

View File

@ -1,74 +0,0 @@
/**
* @deprecated modifications to this file should be v2 only
* Use `/apps/web/pages/v2/availability/index.tsx` instead
*/
import { ScheduleListItem } from "@calcom/features/schedules/components/ScheduleListItem";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import EmptyScreen from "@calcom/ui/EmptyScreen";
import { Icon } from "@calcom/ui/Icon";
import Shell from "@calcom/ui/Shell";
import { withQuery } from "@lib/QueryCell";
import { HttpError } from "@lib/core/http/error";
import { NewScheduleButton } from "@components/availability/NewScheduleButton";
import SkeletonLoader from "@components/availability/SkeletonLoader";
/**
* @deprecated modifications to this file should be v2 only
* Use `/apps/web/pages/v2/availability/index.tsx` instead
*/
export function AvailabilityList({ schedules }: inferQueryOutput<"viewer.availability.list">) {
const { t } = useLocale();
const utils = trpc.useContext();
const deleteMutation = trpc.useMutation("viewer.availability.schedule.delete", {
onSuccess: async () => {
await utils.invalidateQueries(["viewer.availability.list"]);
showToast(t("schedule_deleted_successfully"), "success");
},
onError: (err) => {
if (err instanceof HttpError) {
const message = `${err.statusCode}: ${err.message}`;
showToast(message, "error");
}
},
});
return (
<>
{schedules.length === 0 ? (
<EmptyScreen
Icon={Icon.FiClock}
headline={t("new_schedule_heading")}
description={t("new_schedule_description")}
/>
) : (
<div className="-mx-4 mb-16 overflow-hidden rounded-sm border border-gray-200 bg-white sm:mx-0">
<ul className="divide-y divide-neutral-200" data-testid="schedules">
{schedules.map((schedule) => (
<ScheduleListItem
key={schedule.id}
schedule={schedule}
deleteFunction={deleteMutation.mutate}
/>
))}
</ul>
</div>
)}
</>
);
}
const WithQuery = withQuery(["viewer.availability.list"]);
export default function AvailabilityPage() {
const { t } = useLocale();
return (
<div>
<Shell heading={t("availability")} subtitle={t("configure_availability")} CTA={<NewScheduleButton />}>
<WithQuery success={({ data }) => <AvailabilityList {...data} />} customLoader={<SkeletonLoader />} />
</Shell>
</div>
);
}

View File

@ -1,121 +0,0 @@
/**
* @deprecated modifications to this file should be v2 only
* Use `/apps/web/pages/v2/availability/troubleshoot.tsx` instead
*/
import type { IBusySlot } from "pages/v2/availability/troubleshoot";
import { useState } from "react";
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
import Shell from "@calcom/ui/Shell";
import { QueryCell } from "@lib/QueryCell";
import Loader from "@components/Loader";
type User = inferQueryOutput<"viewer.me">;
/**
* @deprecated modifications to this file should be v2 only
* Use `/apps/web/pages/v2/availability/troubleshoot.tsx` instead
*/
const AvailabilityView = ({ user }: { user: User }) => {
const { t } = useLocale();
const [selectedDate, setSelectedDate] = useState(dayjs());
const { data, isLoading } = trpc.useQuery(
[
"viewer.availability.user",
{
username: user.username!,
dateFrom: selectedDate.startOf("day").utc().format(),
dateTo: selectedDate.endOf("day").utc().format(),
withSource: true,
},
],
{
enabled: !!user.username,
}
);
return (
<div className="max-w-xl overflow-hidden rounded-sm bg-white shadow">
<div className="px-4 py-5 sm:p-6">
{t("overview_of_day")}{" "}
<input
type="date"
className="inline h-8 border-none p-0"
defaultValue={selectedDate.format("YYYY-MM-DD")}
onChange={(e) => {
if (e.target.value) setSelectedDate(dayjs(e.target.value));
}}
/>
<small className="block text-neutral-400">{t("hover_over_bold_times_tip")}</small>
<div className="mt-4 space-y-4">
<div className="bg-brand dark:bg-darkmodebrand overflow-hidden rounded-sm">
<div className="text-brandcontrast dark:text-darkmodebrandcontrast px-4 py-2 sm:px-6">
{t("your_day_starts_at")} {convertMinsToHrsMins(user.startTime)}
</div>
</div>
{isLoading ? (
<Loader />
) : data && data.busy.length > 0 ? (
data.busy
.sort((a: IBusySlot, b: IBusySlot) => (a.start > b.start ? -1 : 1))
.map((slot: IBusySlot) => (
<div
key={`${slot.start}-${slot.title ?? "untitled"}`}
className="overflow-hidden rounded-sm bg-neutral-100">
<div className="px-4 py-5 text-black sm:p-6">
{t("calendar_shows_busy_between")}{" "}
<span className="font-medium text-neutral-800" title={slot.start}>
{dayjs(slot.start).format("HH:mm")}
</span>{" "}
{t("and")}{" "}
<span className="font-medium text-neutral-800" title={slot.end}>
{dayjs(slot.end).format("HH:mm")}
</span>{" "}
{t("on")} {dayjs(slot.start).format("D")}{" "}
{t(dayjs(slot.start).format("MMMM").toLowerCase())} {dayjs(slot.start).format("YYYY")}
{slot.title && ` - (${slot.title})`}
{slot.source && <small>{` - (source: ${slot.source})`}</small>}
</div>
</div>
))
) : (
<div className="overflow-hidden rounded-sm bg-neutral-100">
<div className="px-4 py-5 text-black sm:p-6">{t("calendar_no_busy_slots")}</div>
</div>
)}
<div className="bg-brand dark:bg-darkmodebrand overflow-hidden rounded-sm">
<div className="text-brandcontrast dark:text-darkmodebrandcontrast px-4 py-2 sm:px-6">
{t("your_day_ends_at")} {convertMinsToHrsMins(user.endTime)}
</div>
</div>
</div>
</div>
</div>
);
};
export default function Troubleshoot() {
const query = trpc.useQuery(["viewer.me"]);
const { t } = useLocale();
return (
<div>
<Shell heading={t("troubleshoot")} subtitle={t("troubleshoot_description")}>
<QueryCell query={query} success={({ data }) => <AvailabilityView user={data} />} />
</Shell>
</div>
);
}
function convertMinsToHrsMins(mins: number) {
const h = Math.floor(mins / 60);
const m = mins % 60;
const hs = h < 10 ? "0" + h : h;
const ms = m < 10 ? "0" + m : m;
return `${hs}:${ms}`;
}

View File

@ -1,6 +1,9 @@
import { DropdownMenuCheckboxItemProps, DropdownMenuItemIndicator } from "@radix-ui/react-dropdown-menu";
import classNames from "classnames";
import { GetStaticPaths, GetStaticProps } from "next";
import { useRouter } from "next/router";
import { useEffect } from "react";
import React, { useMemo } from "react";
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
@ -14,6 +17,15 @@ import type { Schedule as ScheduleType } from "@calcom/types/schedule";
import { Icon } from "@calcom/ui";
import TimezoneSelect from "@calcom/ui/form/TimezoneSelect";
import Button from "@calcom/ui/v2/core/Button";
import Dropdown, {
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuCheckboxItem as PrimitiveDropdownMenuCheckboxItem,
DropdownMenuGroup,
DropdownMenuLabel,
DropdownMenuItem,
DropdownMenuSeparator,
} from "@calcom/ui/v2/core/Dropdown";
import Shell from "@calcom/ui/v2/core/Shell";
import Switch from "@calcom/ui/v2/core/Switch";
import VerticalDivider from "@calcom/ui/v2/core/VerticalDivider";
@ -37,6 +49,163 @@ type AvailabilityFormValues = {
isDefault: boolean;
};
type EventTypeGroup = { groupName: string; eventTypes: { title: string; isActive: boolean; id: number }[] };
export const DropdownMenuCheckboxItem = React.forwardRef<HTMLDivElement, DropdownMenuCheckboxItemProps>(
({ children, defaultChecked, disabled, ...passThroughProps }, forwardedRef) => {
const [checked, setChecked] = useState(defaultChecked);
return (
<PrimitiveDropdownMenuCheckboxItem
ref={forwardedRef}
className="flex w-full items-center justify-between space-x-4 p-1 font-normal"
checked={checked}
onCheckedChange={(checked) => {
setChecked(checked);
passThroughProps.onCheckedChange && passThroughProps.onCheckedChange(checked);
}}
disabled={defaultChecked && disabled}
ItemIndicator={() => (
<DropdownMenuItemIndicator asChild>
<span className="hidden" />
</DropdownMenuItemIndicator>
)}
onSelect={(e) => e.preventDefault()}>
{children}
<input
readOnly
disabled={defaultChecked && disabled}
type="checkbox"
checked={checked}
className="inline-block rounded-[4px] border-gray-300 text-neutral-900 focus:ring-neutral-500 disabled:text-neutral-400"
/>
</PrimitiveDropdownMenuCheckboxItem>
);
}
);
DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem";
const ActiveOnEventTypeSelect = ({ scheduleId, isDefault }: { scheduleId: number; isDefault: boolean }) => {
const { t } = useLocale();
// I was doubtful to make this RHF but this has no point: because the DropdownMenuCheckboxItem is
// controlled anyway; requiring the use of Controller regardless.
const [eventTypeIds, setEventTypeIds] = useState<{ [K: number]: boolean }>({});
const [isOpen, setOpen] = useState(false);
const { data } = trpc.useQuery(["viewer.eventTypes"]);
const mutation = trpc.useMutation("viewer.availability.switchActiveOnEventTypes");
const { data: user } = useMeQuery();
const utils = trpc.useContext();
const eventTypeGroups = useMemo(
() =>
data?.eventTypeGroups.reduce((aggregate, eventTypeGroups) => {
aggregate.push({
groupName:
eventTypeGroups.eventTypes[0].team?.name || eventTypeGroups.eventTypes[0].users[0].name || "",
eventTypes: [
...eventTypeGroups.eventTypes.map((eventType) => ({
title: eventType.title,
id: eventType.id,
isActive: eventType.scheduleId
? scheduleId === eventType.scheduleId
: scheduleId === user?.defaultScheduleId,
})),
],
});
return aggregate;
}, [] as EventTypeGroup[]),
[data?.eventTypeGroups, user?.defaultScheduleId, scheduleId]
);
useEffect(() => {
if (!data) return;
if (eventTypeGroups) {
const eventTypeIdsLocal: { [K: number]: boolean } = {};
for (const item of eventTypeGroups) {
for (const eventType of item.eventTypes) {
if (isDefault && !eventType.isActive) {
eventTypeIdsLocal[eventType.id] = eventType.isActive;
}
}
}
setEventTypeIds(eventTypeIdsLocal);
}
}, [data, eventTypeGroups, isDefault]);
return (
<Dropdown onOpenChange={setOpen} open={isOpen}>
<DropdownMenuTrigger asChild>
<Button
size="base"
color="secondary"
className="w-full px-3 !font-light sm:w-72"
EndIcon={({ className, ...props }) =>
isOpen ? (
<Icon.FiChevronUp
{...props}
className={classNames(className, "!h-5 !w-5 !font-extrabold text-gray-300")}
/>
) : (
<Icon.FiChevronDown
{...props}
className={classNames(className, "!h-5 !w-5 !font-extrabold text-gray-300")}
/>
)
}>
{t("nr_event_type", {
count: eventTypeGroups?.reduce(
(count, group) => count + group.eventTypes.filter((eventType) => eventType.isActive).length,
0
),
})}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
{(eventTypeGroups || []).map((eventTypeGroup) => (
<DropdownMenuGroup key={eventTypeGroup.groupName} className="space-y-3 p-4 px-3 sm:w-72">
<DropdownMenuLabel asChild>
<span className="h6 pb-3 pl-1 text-xs font-medium uppercase text-neutral-400">
{eventTypeGroup.groupName}
</span>
</DropdownMenuLabel>
{eventTypeGroup.eventTypes.map((eventType) => (
<DropdownMenuCheckboxItem
key={eventType.title}
disabled={isDefault}
defaultChecked={eventType.isActive}
onCheckedChange={(checked) => setEventTypeIds({ ...eventTypeIds, [eventType.id]: checked })}>
<span className="truncate">{eventType.title}</span>
</DropdownMenuCheckboxItem>
))}
</DropdownMenuGroup>
))}
<DropdownMenuSeparator asChild>
<hr />
</DropdownMenuSeparator>
<div className="flex justify-end space-x-2 px-4 pt-3 pb-2">
<Button color="minimalSecondary" onClick={() => setOpen(false)}>
{t("cancel")}
</Button>
<Button
color="primary"
type="submit"
onClick={async () => {
await mutation.mutate({
scheduleId,
eventTypeIds,
});
await utils.invalidateQueries("viewer.eventTypes");
}}>
{t("apply")}
</Button>
</div>
</DropdownMenuContent>
</Dropdown>
);
};
export default function Availability({ schedule }: { schedule: number }) {
const { t, i18n } = useLocale();
const router = useRouter();
@ -144,7 +313,7 @@ export default function Availability({ schedule }: { schedule: number }) {
}}
className="-mx-4 flex flex-col pb-16 sm:mx-0 xl:flex-row xl:space-x-6">
<div className="flex-1">
<div className="rounded-md border-gray-200 bg-white py-5 pr-4 sm:border sm:p-6">
<div className="mb-4 rounded-md border-gray-200 bg-white py-5 pr-4 sm:border sm:p-6">
<h3 className="mb-5 text-base font-medium leading-6 text-gray-900">
{t("change_start_end")}
</h3>
@ -162,25 +331,31 @@ export default function Availability({ schedule }: { schedule: number }) {
</div>
</div>
<div className="min-w-40 col-span-3 space-y-2 lg:col-span-1">
<div className="xl:max-w-80 w-full pr-4 sm:p-0">
<div>
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
{t("timezone")}
</label>
<Controller
name="timeZone"
render={({ field: { onChange, value } }) =>
value ? (
<TimezoneSelect
value={value}
className="focus:border-brand mt-1 block w-72 rounded-md border-gray-300 text-sm"
onChange={(timezone) => onChange(timezone.value)}
/>
) : (
<SelectSkeletonLoader className="w-72" />
)
}
/>
<div className="xl:max-w-80 w-full space-y-4 pr-4 sm:p-0">
<div className="xl:max-w-80 w-full space-y-4 pr-4 sm:p-0">
<div>
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
{t("timezone")}
</label>
<Controller
name="timeZone"
render={({ field: { onChange, value } }) =>
value ? (
<TimezoneSelect
value={value}
className="focus:border-brand mt-1 block w-full rounded-md border-gray-300 text-sm sm:w-72"
onChange={(timezone) => onChange(timezone.value)}
/>
) : (
<SelectSkeletonLoader className="w-full sm:w-72" />
)
}
/>
</div>
<Label className="mt-1 cursor-pointer space-y-2 sm:w-full md:w-1/2 lg:w-full">
<span>Active on</span>
<ActiveOnEventTypeSelect isDefault={form.watch("isDefault")} scheduleId={schedule} />
</Label>
</div>
<hr className="my-8" />
<div className="rounded-md">

View File

@ -175,6 +175,7 @@ const loggedInViewerRouter = createProtectedRouter()
theme: user.theme,
hideBranding: user.hideBranding,
metadata: user.metadata,
defaultScheduleId: user.defaultScheduleId,
};
},
})
@ -276,6 +277,7 @@ const loggedInViewerRouter = createProtectedRouter()
successRedirectUrl: true,
hashedLink: true,
destinationCalendar: true,
scheduleId: true,
team: true,
users: {
select: {

View File

@ -37,6 +37,24 @@ export const availabilityRouter = createProtectedRouter()
};
},
})
.mutation("switchActiveOnEventTypes", {
input: z.object({
scheduleId: z.number(),
eventTypeIds: z.record(z.boolean()),
}),
async resolve({ input }) {
return await Promise.all(
Object.entries(input.eventTypeIds).map(([eventTypeId, isActive]) =>
prisma?.eventType.update({
where: { id: Number(eventTypeId) },
data: {
scheduleId: isActive ? input.scheduleId : null,
},
})
)
);
},
})
.query("schedule", {
input: z.object({
scheduleId: z.optional(z.number()),

View File

@ -87,7 +87,7 @@ export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonPr
ref: forwardedRef,
className: classNames(
// base styles independent what type of button it is
"inline-flex items-center text-sm font-medium relative",
"inline-flex place-content-between items-center text-sm font-medium relative",
// different styles depending on size
size === "base" && "h-9 px-4 py-2.5 ",
size === "lg" && "h-[36px] px-4 py-2.5 ",

View File

@ -29,6 +29,8 @@ DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
export const DropdownMenuTriggerItem = DropdownMenuPrimitive.Trigger;
export const DropdownMenuItemIndicator = DropdownMenuPrimitive.ItemIndicator;
export const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
type DropdownMenuContentProps = ComponentProps<typeof DropdownMenuPrimitive["Content"]>;
@ -66,19 +68,31 @@ DropdownMenuItem.displayName = "DropdownMenuItem";
export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
type DropdownMenuCheckboxItemProps = ComponentProps<typeof DropdownMenuPrimitive["CheckboxItem"]>;
export const DropdownMenuCheckboxItem = forwardRef<HTMLDivElement, DropdownMenuCheckboxItemProps>(
({ children, ...props }, forwardedRef) => {
return (
<DropdownMenuPrimitive.CheckboxItem {...props} ref={forwardedRef}>
{children}
export const DropdownMenuCheckboxItem = forwardRef<
HTMLDivElement,
DropdownMenuPrimitive.DropdownMenuCheckboxItemProps & { ItemIndicator?: () => JSX.Element }
>(
(
{
children,
ItemIndicator = () => (
<DropdownMenuPrimitive.ItemIndicator>
<CheckCircleIcon />
</DropdownMenuPrimitive.ItemIndicator>
),
...props
},
forwardedRef
) => {
return (
<DropdownMenuPrimitive.CheckboxItem {...props} ref={forwardedRef}>
{children}
<ItemIndicator />
</DropdownMenuPrimitive.CheckboxItem>
);
}
);
DropdownMenuCheckboxItem.displayName = "DropdownMenuCheckboxItem";
export const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;

7686
yarn.lock

File diff suppressed because it is too large Load Diff