feat: add react-scan in dev

This commit is contained in:
Aarnav Tale
2025-01-24 08:17:12 -05:00
parent 665509e710
commit 0f75636342
8 changed files with 577 additions and 133 deletions
+133 -74
View File
@@ -1,97 +1,156 @@
import React, { Dispatch, ReactNode, SetStateAction } from 'react';
import Button, { ButtonProps } from '~/components/Button';
import Title from '~/components/Title';
import Text from '~/components/Text';
import Card from '~/components/Card';
import React, { cloneElement, useRef } from 'react';
import {
Dialog as AriaDialog,
DialogTrigger,
Modal,
ModalOverlay,
} from 'react-aria-components';
type AriaDialogProps,
type AriaModalOverlayProps,
Overlay,
useDialog,
useModalOverlay,
useOverlayTrigger,
} from 'react-aria';
import { Form, type HTMLFormMethod } from 'react-router';
import {
type OverlayTriggerProps,
type OverlayTriggerState,
useOverlayTriggerState,
} from 'react-stately';
import Button, { ButtonProps } from '~/components/Button';
import Card from '~/components/Card';
import IconButton, { IconButtonProps } from '~/components/IconButton';
import Text from '~/components/Text';
import Title from '~/components/Title';
import { cn } from '~/utils/cn';
interface ActionProps extends Omit<ButtonProps, 'variant'> {
variant: 'cancel' | 'confirm';
export interface DialogProps extends OverlayTriggerProps {
children: [
React.ReactElement<ButtonProps> | React.ReactElement<IconButtonProps>,
React.ReactElement<DialogPanelProps>,
];
}
function Action(props: ActionProps) {
return (
<Button
{...props}
type={props.variant === 'confirm' ? 'submit' : 'button'}
variant={props.variant === 'cancel' ? 'light' : 'heavy'}
/>
function Dialog(props: DialogProps) {
const state = useOverlayTriggerState(props);
const { triggerProps, overlayProps } = useOverlayTrigger(
{
type: 'dialog',
},
state,
);
}
interface GutterProps {
children: ReactNode;
}
function Gutter({ children }: GutterProps) {
const [button, panel] = props.children;
return (
<div className="mt-6 flex justify-end gap-4 mt-6">
{children}
</div>
)
}
interface PanelProps {
children: (close: () => void) => ReactNode;
control?: [boolean, Dispatch<SetStateAction<boolean>>];
className?: string;
}
function Panel({ children, control, className }: PanelProps) {
return (
<ModalOverlay
aria-hidden="true"
className={cn(
'fixed inset-0 h-screen w-screen z-50',
'flex items-center justify-center',
'bg-headplane-900/15 dark:bg-headplane-900/30',
'entering:animate-in exiting:animate-out',
'entering:fade-in entering:duration-100 entering:ease-out',
'exiting:fade-out exiting:duration-50 exiting:ease-in',
className,
<>
{cloneElement(button, triggerProps)}
{state.isOpen && (
<DModal state={state}>
{cloneElement(panel, {
...overlayProps,
close: () => state.close(),
})}
</DModal>
)}
isOpen={control ? control[0] : undefined}
onOpenChange={control ? control[1] : undefined}
>
<Modal className={cn(
'bg-white dark:bg-headplane-900 rounded-3xl w-full max-w-lg',
'entering:animate-in exiting:animate-out',
'entering:zoom-in-95 entering:ease-out entering:duration-100',
'exiting:zoom-out-95 exiting:ease-in exiting:duration-50',
)}>
<Card variant="flat" className="w-full max-w-lg">
<AriaDialog role="alertdialog" className="outline-none">
{({ close }) => children(close)}
</AriaDialog>
</Card>
</Modal>
</ModalOverlay>
</>
);
}
interface DialogProps {
children: ReactNode;
control?: [boolean, Dispatch<SetStateAction<boolean>>];
export interface DialogPanelProps extends AriaDialogProps {
children: React.ReactNode;
variant?: 'normal' | 'destructive';
onSubmit?: React.FormEventHandler<HTMLFormElement>;
method?: HTMLFormMethod;
// Anonymous (passed by parent)
close?: () => void;
}
function Dialog({ children, control }: DialogProps) {
if (control) {
return children;
function Panel(props: DialogPanelProps) {
const { children, onSubmit, close, variant, method = 'POST' } = props;
const ref = useRef<HTMLFormElement | null>(null);
const { dialogProps } = useDialog(
{
...props,
role: 'alertdialog',
},
ref,
);
return (
<Form
{...dialogProps}
onSubmit={(event) => {
if (onSubmit) {
onSubmit(event);
}
close?.();
}}
method={method ?? 'POST'}
ref={ref}
className={cn(
'outline-none rounded-3xl w-full max-w-lg',
'bg-white dark:bg-headplane-900',
)}
>
<Card className="w-full max-w-lg">
{children}
<div className="mt-6 flex justify-end gap-4">
<Button onPress={close}>Cancel</Button>
<Button
type="submit"
variant={variant === 'destructive' ? 'danger' : 'heavy'}
isDisabled={!(ref.current?.checkVisibility() ?? false)}
>
Confirm
</Button>
</div>
</Card>
</Form>
);
}
interface DModalProps extends AriaModalOverlayProps {
children: React.ReactNode;
state: OverlayTriggerState;
}
function DModal(props: DModalProps) {
const { children, state } = props;
const ref = useRef<HTMLDivElement>(null);
const { modalProps, underlayProps } = useModalOverlay(props, state, ref);
if (!state.isOpen) {
return null;
}
return <DialogTrigger>{children}</DialogTrigger>;
return (
<Overlay>
<div
{...underlayProps}
aria-hidden="true"
className={cn(
'fixed inset-0 h-screen w-screen z-50',
'flex items-center justify-center',
'bg-headplane-900/15 dark:bg-headplane-900/30',
'entering:animate-in exiting:animate-out',
'entering:fade-in entering:duration-100 entering:ease-out',
'exiting:fade-out exiting:duration-50 exiting:ease-in',
)}
/>
<div
{...modalProps}
className={cn(
'fixed inset-0 h-screen w-screen z-50',
'flex items-center justify-center',
)}
>
{children}
</div>
</Overlay>
);
}
export default Object.assign(Dialog, {
Action,
Button,
Gutter,
IconButton,
Panel,
Title,
Text,