mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-22 10:47:26 +02:00
♻️ Remove headlessui (#1124)
This commit is contained in:
parent
efa4f03353
commit
e9fb86516d
12 changed files with 182 additions and 327 deletions
|
@ -21,7 +21,6 @@
|
|||
"dependencies": {
|
||||
"@auth/prisma-adapter": "^1.0.3",
|
||||
"@floating-ui/react-dom-interactions": "^0.13.3",
|
||||
"@headlessui/react": "^1.7.7",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@next/bundle-analyzer": "^12.3.4",
|
||||
"@radix-ui/react-slot": "^1.0.1",
|
||||
|
|
|
@ -123,7 +123,7 @@ const Page = () => {
|
|||
if (optionsToDeleteThatHaveVotes.length > 0) {
|
||||
modalContext.render({
|
||||
title: t("areYouSure"),
|
||||
description: (
|
||||
content: (
|
||||
<Trans
|
||||
i18nKey="deletingOptionsWarning"
|
||||
components={{ b: <strong /> }}
|
||||
|
@ -131,7 +131,7 @@ const Page = () => {
|
|||
),
|
||||
onOk,
|
||||
okButtonProps: {
|
||||
type: "danger",
|
||||
variant: "destructive",
|
||||
},
|
||||
okText: t("delete"),
|
||||
cancelText: t("cancel"),
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
import * as React from "react";
|
||||
|
||||
export interface CompactButtonProps {
|
||||
icon?: React.ComponentType<{
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
}>;
|
||||
children?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const CompactButton = React.forwardRef<HTMLButtonElement, CompactButtonProps>(
|
||||
({ icon: Icon, children, onClick }, ref) => {
|
||||
return (
|
||||
<button
|
||||
ref={ref}
|
||||
type="button"
|
||||
className="inline-flex size-5 items-center justify-center rounded-full bg-gray-100 text-gray-500 transition-colors hover:bg-gray-200 active:bg-gray-300 active:text-gray-500"
|
||||
onClick={onClick}
|
||||
>
|
||||
{Icon ? <Icon className="size-3" /> : children}
|
||||
</button>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CompactButton.displayName = "CompactButton";
|
||||
|
||||
export default CompactButton;
|
|
@ -4,8 +4,6 @@ import {
|
|||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@rallly/ui/dropdown-menu";
|
||||
import { Icon } from "@rallly/ui/icon";
|
||||
|
@ -14,6 +12,7 @@ import clsx from "clsx";
|
|||
import dayjs from "dayjs";
|
||||
import {
|
||||
CalendarIcon,
|
||||
CalendarXIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
MoreHorizontalIcon,
|
||||
|
@ -34,7 +33,6 @@ import {
|
|||
getDateProps,
|
||||
removeAllOptionsForDay,
|
||||
} from "../../../../utils/date-time-utils";
|
||||
import CompactButton from "../../../compact-button";
|
||||
import DateCard from "../../../date-card";
|
||||
import { useHeadlessDatePicker } from "../../../headless-date-picker";
|
||||
import { DateTimeOption } from "..";
|
||||
|
@ -330,15 +328,20 @@ const MonthCalendar: React.FunctionComponent<DateTimePickerProps> = ({
|
|||
);
|
||||
}}
|
||||
/>
|
||||
<CompactButton
|
||||
icon={XIcon}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
onChange([
|
||||
...options.slice(0, index),
|
||||
...options.slice(index + 1),
|
||||
]);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<Icon>
|
||||
<XIcon />
|
||||
</Icon>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -380,15 +383,13 @@ const MonthCalendar: React.FunctionComponent<DateTimePickerProps> = ({
|
|||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild={true}>
|
||||
<button className="text-gray-500 hover:text-gray-800">
|
||||
<MoreHorizontalIcon className="h4 w-4" />
|
||||
</button>
|
||||
<Button variant="ghost" size="sm">
|
||||
<Icon>
|
||||
<MoreHorizontalIcon />
|
||||
</Icon>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuLabel>
|
||||
<Trans i18nKey="menu" defaults="Menu" />
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
const times = optionsForDay.map(
|
||||
|
@ -430,7 +431,9 @@ const MonthCalendar: React.FunctionComponent<DateTimePickerProps> = ({
|
|||
onChange(newOptions);
|
||||
}}
|
||||
>
|
||||
<SparklesIcon className="mr-2 size-4" />
|
||||
<Icon>
|
||||
<SparklesIcon />
|
||||
</Icon>
|
||||
<Trans i18nKey="applyToAllDates" />
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
|
@ -443,7 +446,9 @@ const MonthCalendar: React.FunctionComponent<DateTimePickerProps> = ({
|
|||
);
|
||||
}}
|
||||
>
|
||||
<XIcon className="mr-2 size-4" />
|
||||
<Icon>
|
||||
<CalendarXIcon />
|
||||
</Icon>
|
||||
<Trans i18nKey="deleteDate" />
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
import { Button } from "@rallly/ui/button";
|
||||
import {
|
||||
flip,
|
||||
FloatingPortal,
|
||||
offset,
|
||||
size,
|
||||
useFloating,
|
||||
} from "@floating-ui/react-dom-interactions";
|
||||
import { Listbox } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@rallly/ui/select";
|
||||
import dayjs from "dayjs";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { getDuration } from "@/utils/date-time-utils";
|
||||
import { stopPropagation } from "@/utils/stop-propagation";
|
||||
|
||||
import { styleMenuItem } from "../../../menu-styles";
|
||||
|
||||
export interface TimePickerProps {
|
||||
value: Date;
|
||||
value?: Date;
|
||||
after?: Date;
|
||||
className?: string;
|
||||
onChange?: (value: Date) => void;
|
||||
|
@ -29,87 +24,69 @@ const TimePicker: React.FunctionComponent<TimePickerProps> = ({
|
|||
className,
|
||||
after,
|
||||
}) => {
|
||||
const { reference, floating, x, y, strategy, refs } = useFloating({
|
||||
placement: "bottom-start",
|
||||
strategy: "fixed",
|
||||
middleware: [
|
||||
offset(5),
|
||||
flip(),
|
||||
size({
|
||||
apply: ({ rects }) => {
|
||||
if (refs.floating.current) {
|
||||
Object.assign(refs.floating.current.style, {
|
||||
minWidth: `${rects.reference.width}px`,
|
||||
});
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const getOptions = React.useCallback(() => {
|
||||
if (!open) {
|
||||
return [dayjs(value).toISOString()];
|
||||
}
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
let cursor = after
|
||||
? dayjs(after).add(15, "minutes")
|
||||
: dayjs(value).startOf("day");
|
||||
|
||||
const renderOptions = () => {
|
||||
const startFromDate = after ? dayjs(after) : dayjs(value).startOf("day");
|
||||
|
||||
const options: React.ReactNode[] = [];
|
||||
|
||||
for (let i = 1; i <= 96; i++) {
|
||||
const optionValue = startFromDate.add(i * 15, "minutes");
|
||||
options.push(
|
||||
<Listbox.Option
|
||||
key={i}
|
||||
className={styleMenuItem}
|
||||
value={optionValue.format("YYYY-MM-DDTHH:mm:ss")}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{optionValue.format("LT")}</span>
|
||||
{after ? (
|
||||
<span className="text-sm text-gray-500">
|
||||
{getDuration(dayjs(after), optionValue)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</Listbox.Option>,
|
||||
);
|
||||
const res: string[] = [];
|
||||
while (cursor.isSame(value, "day")) {
|
||||
res.push(cursor.toISOString());
|
||||
cursor = cursor.add(15, "minutes");
|
||||
}
|
||||
return options;
|
||||
};
|
||||
return res;
|
||||
}, [after, open, value]);
|
||||
|
||||
return (
|
||||
<Listbox
|
||||
value={dayjs(value).format("YYYY-MM-DDTHH:mm:ss")}
|
||||
onChange={(newValue) => {
|
||||
<Select
|
||||
value={value?.toISOString()}
|
||||
onValueChange={(newValue) => {
|
||||
if (newValue) {
|
||||
onChange?.(new Date(newValue));
|
||||
}
|
||||
}}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
>
|
||||
{(open) => (
|
||||
<>
|
||||
<div ref={reference} className={clsx("relative", className)}>
|
||||
<Listbox.Button className="btn-default text-left">
|
||||
<span className="grow truncate">{dayjs(value).format("LT")}</span>
|
||||
<span className="pointer-events-none ml-2 flex">
|
||||
<ChevronDownIcon className="size-5" />
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
</div>
|
||||
<FloatingPortal>
|
||||
<SelectTrigger asChild>
|
||||
<Button className={className}>
|
||||
<SelectValue placeholder="Select time" />
|
||||
</Button>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{open ? (
|
||||
<Listbox.Options
|
||||
style={{
|
||||
position: strategy,
|
||||
left: x ?? "",
|
||||
top: y ?? "",
|
||||
}}
|
||||
ref={floating}
|
||||
className="z-50 max-h-52 overflow-auto rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
onMouseDown={stopPropagation}
|
||||
>
|
||||
{renderOptions()}
|
||||
</Listbox.Options>
|
||||
getOptions().map((option) => {
|
||||
return (
|
||||
<SelectItem key={option} value={dayjs(option).toISOString()}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{dayjs(option).format("LT")}</span>
|
||||
{after ? (
|
||||
<span className="text-sm text-gray-500">
|
||||
{getDuration(dayjs(after), dayjs(option))}
|
||||
</span>
|
||||
) : null}
|
||||
</FloatingPortal>
|
||||
</>
|
||||
</div>
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<SelectItem value={dayjs(value).toISOString()}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span>{dayjs(value).format("LT")}</span>
|
||||
{after ? (
|
||||
<span className="text-sm text-gray-500">
|
||||
{getDuration(dayjs(after), dayjs(value))}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</SelectItem>
|
||||
)}
|
||||
</Listbox>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,14 @@ import { cn } from "@rallly/ui";
|
|||
import { Button } from "@rallly/ui/button";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@rallly/ui/card";
|
||||
import { CommandDialog } from "@rallly/ui/command";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
useDialog,
|
||||
} from "@rallly/ui/dialog";
|
||||
import { FormField, FormMessage } from "@rallly/ui/form";
|
||||
import { Label } from "@rallly/ui/label";
|
||||
import { Switch } from "@rallly/ui/switch";
|
||||
|
@ -15,7 +23,6 @@ import { useFormContext } from "react-hook-form";
|
|||
import { TimeZoneCommand } from "@/components/time-zone-picker/time-zone-select";
|
||||
|
||||
import { getBrowserTimeZone } from "../../../utils/date-time-utils";
|
||||
import { useModal } from "../../modal";
|
||||
import { NewEventData } from "../types";
|
||||
import MonthCalendar from "./month-calendar";
|
||||
import { DateTimeOption } from "./types";
|
||||
|
@ -73,38 +80,17 @@ const PollOptionsForm = ({
|
|||
options.length === 0 ||
|
||||
options.some((option) => option.type !== "timeSlot");
|
||||
|
||||
const [dateOrTimeRangeModal, openDateOrTimeRangeModal] = useModal({
|
||||
title: t("mixedOptionsTitle"),
|
||||
description: t("mixedOptionsDescription"),
|
||||
okText: t("mixedOptionsKeepTimes"),
|
||||
onOk: () => {
|
||||
setValue(
|
||||
"options",
|
||||
watchOptions.filter((option) => option.type === "timeSlot"),
|
||||
);
|
||||
if (!watchTimeZone) {
|
||||
setValue("timeZone", getBrowserTimeZone());
|
||||
}
|
||||
},
|
||||
cancelText: t("mixedOptionsKeepDates"),
|
||||
onCancel: () => {
|
||||
setValue(
|
||||
"options",
|
||||
watchOptions.filter((option) => option.type === "date"),
|
||||
);
|
||||
setValue("timeZone", "");
|
||||
},
|
||||
});
|
||||
const dateOrTimeRangeDialog = useDialog();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (watchOptions.length > 1) {
|
||||
const optionType = watchOptions[0].type;
|
||||
// all options needs to be the same type
|
||||
if (watchOptions.some((option) => option.type !== optionType)) {
|
||||
openDateOrTimeRangeModal();
|
||||
dateOrTimeRangeDialog.trigger();
|
||||
}
|
||||
}
|
||||
}, [watchOptions, openDateOrTimeRangeModal]);
|
||||
}, [watchOptions, dateOrTimeRangeDialog]);
|
||||
|
||||
const watchNavigationDate = watch("navigationDate");
|
||||
const navigationDate = new Date(watchNavigationDate ?? Date.now());
|
||||
|
@ -146,7 +132,47 @@ const PollOptionsForm = ({
|
|||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
{dateOrTimeRangeModal}
|
||||
<Dialog {...dateOrTimeRangeDialog.dialogProps}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
<Trans i18nKey="mixedOptionsTitle" />
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<p className="text-sm">
|
||||
<Trans i18nKey="mixedOptionsDescription" />
|
||||
</p>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setValue(
|
||||
"options",
|
||||
watchOptions.filter((option) => option.type === "date"),
|
||||
);
|
||||
setValue("timeZone", "");
|
||||
dateOrTimeRangeDialog.dismiss();
|
||||
}}
|
||||
>
|
||||
<Trans i18nKey="mixedOptionsKeepDates" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setValue(
|
||||
"options",
|
||||
watchOptions.filter((option) => option.type === "timeSlot"),
|
||||
);
|
||||
if (!watchTimeZone) {
|
||||
setValue("timeZone", getBrowserTimeZone());
|
||||
}
|
||||
dateOrTimeRangeDialog.dismiss();
|
||||
}}
|
||||
variant="primary"
|
||||
>
|
||||
<Trans i18nKey="mixedOptionsKeepTimes" />
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<div>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
|
|
@ -70,7 +70,6 @@ const ModalProvider: React.FunctionComponent<ModalProviderProps> = ({
|
|||
visible={true}
|
||||
{...modal.config}
|
||||
content={modal.content}
|
||||
footer={null}
|
||||
onCancel={() => {
|
||||
remove(id);
|
||||
}}
|
||||
|
|
|
@ -1,128 +1,66 @@
|
|||
import { Dialog } from "@headlessui/react";
|
||||
import { AnimatePresence, m } from "framer-motion";
|
||||
import { XIcon } from "lucide-react";
|
||||
import { Button, ButtonProps } from "@rallly/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@rallly/ui/dialog";
|
||||
import * as React from "react";
|
||||
|
||||
import { ButtonProps, LegacyButton } from "../button";
|
||||
|
||||
export interface ModalProps {
|
||||
description?: React.ReactNode;
|
||||
title?: React.ReactNode;
|
||||
okText?: string;
|
||||
cancelText?: string;
|
||||
okButtonProps?: ButtonProps;
|
||||
onOk?: () => void;
|
||||
onCancel?: () => void;
|
||||
footer?: React.ReactNode;
|
||||
content?: React.ReactNode;
|
||||
overlayClosable?: boolean;
|
||||
visible?: boolean;
|
||||
showClose?: boolean;
|
||||
}
|
||||
|
||||
const Modal: React.FunctionComponent<ModalProps> = ({
|
||||
description,
|
||||
title,
|
||||
okText,
|
||||
cancelText,
|
||||
okButtonProps,
|
||||
footer,
|
||||
content,
|
||||
overlayClosable,
|
||||
onCancel,
|
||||
cancelText,
|
||||
onOk,
|
||||
okButtonProps,
|
||||
visible,
|
||||
showClose,
|
||||
}) => {
|
||||
const initialFocusRef = React.useRef<HTMLButtonElement>(null);
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{visible ? (
|
||||
<Dialog
|
||||
open={visible}
|
||||
className="fixed inset-0 z-50 overflow-y-auto"
|
||||
initialFocus={initialFocusRef}
|
||||
onClose={() => {
|
||||
if (overlayClosable) onCancel?.();
|
||||
}}
|
||||
>
|
||||
<m.div
|
||||
transition={{ duration: 0.5 }}
|
||||
className="flex min-h-screen items-center justify-center"
|
||||
>
|
||||
<Dialog.Overlay
|
||||
as={m.div}
|
||||
transition={{ duration: 0.5 }}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-0 bg-gray-900/25"
|
||||
/>
|
||||
<m.div
|
||||
transition={{ duration: 0.1 }}
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
className="relative z-50 m-3 inline-block max-w-full transform text-left align-middle sm:m-8"
|
||||
>
|
||||
<div
|
||||
data-testid="modal"
|
||||
className="shadow-huge max-w-full overflow-hidden rounded-md bg-white"
|
||||
>
|
||||
{showClose ? (
|
||||
<button
|
||||
role="button"
|
||||
className="absolute right-2 top-2 z-10 inline-flex size-7 items-center justify-center rounded-full text-gray-500 transition-colors hover:bg-gray-500/10 hover:text-gray-500 focus:ring-0 focus:ring-offset-0 active:bg-gray-500/20"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<XIcon className="h-4" />
|
||||
</button>
|
||||
) : null}
|
||||
{content ?? (
|
||||
<div className="max-w-md p-6">
|
||||
{title ? (
|
||||
<Dialog.Title className="mb-2 font-medium">
|
||||
{title}
|
||||
</Dialog.Title>
|
||||
) : null}
|
||||
{description ? (
|
||||
<Dialog.Description className="m-0">
|
||||
{description}
|
||||
</Dialog.Description>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
{footer === undefined ? (
|
||||
<div className="flex h-14 items-center justify-end gap-3 rounded-br-lg border-t bg-gray-50 p-3">
|
||||
{cancelText ? (
|
||||
<LegacyButton
|
||||
onClick={() => {
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
onCancel?.();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{cancelText}
|
||||
</LegacyButton>
|
||||
) : null}
|
||||
{okText ? (
|
||||
<LegacyButton
|
||||
ref={initialFocusRef}
|
||||
type="primary"
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<p className="text-sm">{content}</p>
|
||||
<DialogFooter>
|
||||
<DialogClose>
|
||||
<Button>{cancelText}</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
onOk?.();
|
||||
}}
|
||||
{...okButtonProps}
|
||||
>
|
||||
{okText}
|
||||
</LegacyButton>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</m.div>
|
||||
</m.div>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -25,12 +25,3 @@ export const useModal = (
|
|||
);
|
||||
return [modal, () => setVisible(true), () => setVisible(false)];
|
||||
};
|
||||
|
||||
export const useModalState = (): [boolean, OpenModalFn, CloseModalFn] => {
|
||||
const [visible, setVisible] = React.useState(false);
|
||||
|
||||
const hide = React.useCallback(() => setVisible(false), []);
|
||||
const show = React.useCallback(() => setVisible(true), []);
|
||||
|
||||
return [visible, show, hide];
|
||||
};
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
import { Switch as HeadlessSwitch } from "@headlessui/react";
|
||||
import clsx from "clsx";
|
||||
import * as React from "react";
|
||||
|
||||
export interface SwitchProps {
|
||||
checked?: boolean;
|
||||
onChange: (checked: boolean) => void;
|
||||
srDescription?: string;
|
||||
}
|
||||
|
||||
const Switch: React.FunctionComponent<SwitchProps> = ({
|
||||
checked = false,
|
||||
onChange,
|
||||
srDescription,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<HeadlessSwitch
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
className={clsx(
|
||||
"relative inline-flex h-6 w-10 flex-shrink-0 rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out",
|
||||
{
|
||||
"bg-gray-200": !checked,
|
||||
"bg-green-500": checked,
|
||||
},
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{srDescription ? <span className="sr-only">{srDescription}</span> : null}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={clsx(
|
||||
"pointer-events-none inline-block size-5 transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out",
|
||||
{
|
||||
"translate-x-4": checked,
|
||||
"translate-x-0": !checked,
|
||||
},
|
||||
)}
|
||||
/>
|
||||
</HeadlessSwitch>
|
||||
);
|
||||
};
|
||||
|
||||
export default Switch;
|
|
@ -26,7 +26,8 @@ test.describe("edit options", () => {
|
|||
editOptionsPage.switchToSpecifyTimes();
|
||||
|
||||
await page.click("text='12:00 PM'");
|
||||
await page.click("text='1:00 PM'");
|
||||
const listbox = page.getByRole("listbox");
|
||||
listbox.getByText("1:00 PM", { exact: true }).click();
|
||||
await page.getByRole("button", { name: "Save" }).click();
|
||||
await expect(page.locator('text="Are you sure?"')).toBeVisible();
|
||||
await page.click("text='Delete'");
|
||||
|
|
|
@ -1969,13 +1969,6 @@
|
|||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@headlessui/react@^1.7.7":
|
||||
version "1.7.12"
|
||||
resolved "https://registry.npmjs.org/@headlessui/react/-/react-1.7.12.tgz"
|
||||
integrity sha512-FhSx5V+Qp0GvbTpaxyS+ymGDDNntCacClWsk/d8Upbr19g3AsPbjfPk4+m2CgJGcuCB5Dz7LpUIOAbvQTyjL2g==
|
||||
dependencies:
|
||||
client-only "^0.0.1"
|
||||
|
||||
"@heroicons/react@^1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.npmjs.org/@heroicons/react/-/react-1.0.6.tgz"
|
||||
|
@ -5750,7 +5743,7 @@ cli-spinners@^2.5.0:
|
|||
resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz"
|
||||
integrity sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==
|
||||
|
||||
client-only@0.0.1, client-only@^0.0.1:
|
||||
client-only@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz"
|
||||
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue