Files
coder/site/src/components/Dialog/Dialog.tsx
T

197 lines
4.6 KiB
TypeScript

/**
* Copied from shadc/ui on 11/13/2024
* @see {@link https://ui.shadcn.com/docs/components/dialog}
*/
import { cva, type VariantProps } from "class-variance-authority";
import { Dialog as DialogPrimitive } from "radix-ui";
import { Button } from "#/components/Button/Button";
import { Spinner } from "#/components/Spinner/Spinner";
import { cn } from "#/utils/cn";
export const Dialog = DialogPrimitive.Root;
export const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
export const DialogClose = DialogPrimitive.Close;
const DialogOverlay: React.FC<
React.ComponentPropsWithRef<typeof DialogPrimitive.Overlay>
> = ({ className, ...props }) => {
return (
<DialogPrimitive.Overlay
className={cn(
`fixed inset-0 z-50 bg-overlay
data-[state=open]:animate-in data-[state=closed]:animate-out
data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0`,
className,
)}
{...props}
/>
);
};
const dialogVariants = cva(
`fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg gap-6
border border-solid bg-surface-primary p-8 shadow-lg duration-200 sm:rounded-lg
translate-x-[-50%] translate-y-[-50%] outline-none
data-[state=open]:animate-in data-[state=closed]:animate-out
data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]
data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]`,
{
variants: {
variant: {
default: "border-border-primary",
destructive: "border-border-destructive",
},
},
defaultVariants: {
variant: "default",
},
},
);
type DialogContentProps = React.ComponentPropsWithRef<
typeof DialogPrimitive.Content
> &
VariantProps<typeof dialogVariants>;
export const DialogContent: React.FC<DialogContentProps> = ({
className,
variant,
children,
...props
}) => {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
className={cn(dialogVariants({ variant }), className)}
{...props}
>
{children}
</DialogPrimitive.Content>
</DialogPortal>
);
};
export const DialogHeader: React.FC<React.ComponentPropsWithRef<"div">> = ({
className,
...props
}) => {
return (
<div
className={cn(
"flex flex-col space-y-5 text-center sm:text-left",
className,
)}
{...props}
/>
);
};
export const DialogFooter: React.FC<React.ComponentPropsWithRef<"div">> = ({
className,
...props
}) => {
return (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className,
)}
{...props}
/>
);
};
type DialogActionsProps = {
/** Text to display in the confirm button */
confirmText?: React.ReactNode;
/** Whether or not confirm is loading, also disables cancel when true */
confirmLoading?: boolean;
/** Whether or not the submit button is disabled */
confirmDisabled?: boolean;
/** Whether the confirm button triggers a destructive action or not */
confirmVariant?: React.ComponentProps<typeof Button>["variant"];
/** Called when confirm is clicked */
onConfirm?: () => void;
/** Text to display in the cancel button */
cancelText?: string;
/** Called when cancel is clicked */
onCancel?: () => void;
};
/**
* Quickly handles most modals actions, some combination of a cancel and confirm button
*/
export const DialogActions: React.FC<DialogActionsProps> = ({
confirmText = "Confirm",
confirmLoading = false,
confirmDisabled = false,
confirmVariant,
onConfirm,
cancelText = "Cancel",
onCancel,
}) => {
return (
<>
{onCancel && (
<Button
disabled={confirmLoading}
onClick={(e) => {
e.stopPropagation();
onCancel();
}}
variant="outline"
>
{cancelText}
</Button>
)}
{onConfirm && (
<Button
variant={confirmVariant}
disabled={confirmLoading || confirmDisabled}
onClick={onConfirm}
data-testid="confirm-button"
type="submit"
>
<Spinner loading={confirmLoading} />
{confirmText}
</Button>
)}
</>
);
};
export const DialogTitle: React.FC<
React.ComponentPropsWithRef<typeof DialogPrimitive.Title>
> = ({ className, ...props }) => {
return (
<DialogPrimitive.Title
className={cn(
"text-xl m-0 text-content-primary font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
);
};
export const DialogDescription: React.FC<
React.ComponentPropsWithRef<typeof DialogPrimitive.Description>
> = ({ className, ...props }) => {
return (
<DialogPrimitive.Description
className={cn("text-sm text-content-secondary font-medium", className)}
{...props}
/>
);
};