feat: ditch react-aria

This commit is contained in:
Aarnav Tale
2026-03-26 12:58:24 -04:00
parent 61ca6693c4
commit 333220a153
6 changed files with 62 additions and 1819 deletions
+56 -108
View File
@@ -1,100 +1,62 @@
import { AlertDialog } from "@base-ui/react/alert-dialog";
import React, { cloneElement, useEffect, useRef } from "react";
import {
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 Button, { type ButtonProps } from "~/components/button";
import cn from "~/utils/cn";
import { useLiveData } from "~/utils/live-data";
export interface DialogProps extends OverlayTriggerProps {
export interface DialogProps {
children:
| [React.ReactElement<ButtonProps>, React.ReactElement<DialogPanelProps>]
| React.ReactElement<DialogPanelProps>;
isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
}
function Dialog(props: DialogProps) {
const { pause, resume } = useLiveData();
const state = useOverlayTriggerState(props);
const { triggerProps, overlayProps } = useOverlayTrigger(
{
type: "dialog",
},
state,
);
const { isOpen, onOpenChange } = props;
useEffect(() => {
if (state.isOpen) {
if (isOpen) {
pause();
} else {
resume();
}
}, [state.isOpen]);
}, [isOpen]);
if (Array.isArray(props.children)) {
const [button, panel] = props.children;
const buttonTriggerProps = { onClick: triggerProps.onPress } as Record<string, unknown>;
return (
<>
{cloneElement(button, buttonTriggerProps)}
{state.isOpen && (
<DModal state={state}>
{cloneElement(panel, {
...overlayProps,
close: () => state.close(),
})}
</DModal>
)}
</>
<AlertDialog.Root open={isOpen} onOpenChange={(open) => onOpenChange?.(open)}>
<AlertDialog.Trigger render={cloneElement(button)} />
<DialogOverlay>{panel}</DialogOverlay>
</AlertDialog.Root>
);
}
return (
<DModal state={state}>
{cloneElement(props.children, {
...overlayProps,
close: () => state.close(),
})}
</DModal>
<AlertDialog.Root open={isOpen} onOpenChange={(open) => onOpenChange?.(open)}>
<DialogOverlay>{props.children}</DialogOverlay>
</AlertDialog.Root>
);
}
export interface DialogPanelProps extends AriaDialogProps {
export interface DialogPanelProps {
children: React.ReactNode;
variant?: "normal" | "destructive" | "unactionable";
onSubmit?: React.FormEventHandler<HTMLFormElement>;
method?: HTMLFormMethod;
isDisabled?: boolean;
// Anonymous (passed by parent)
close?: () => void;
}
function Panel(props: DialogPanelProps) {
const { children, onSubmit, isDisabled, close, variant, method = "POST" } = props;
const ref = useRef<HTMLFormElement | null>(null);
const { dialogProps } = useDialog(
{
...props,
role: "alertdialog",
},
ref,
);
const { children, onSubmit, isDisabled, variant, method = "POST" } = props;
const closeRef = useRef<HTMLButtonElement>(null);
return (
<Form
{...dialogProps}
<AlertDialog.Popup
className={cn(
"w-full max-w-lg rounded-xl p-4",
"outline-hidden",
@@ -102,67 +64,53 @@ function Panel(props: DialogPanelProps) {
"border border-mist-200 dark:border-mist-800",
"shadow-overlay",
)}
method={method ?? "POST"}
onSubmit={(event) => {
if (onSubmit) {
onSubmit(event);
}
close?.();
}}
ref={ref}
>
<div className="flex flex-col gap-4">{children}</div>
<div className="mt-5 flex justify-end gap-3">
{variant === "unactionable" ? (
<Button onClick={close}>Close</Button>
) : (
<>
<Button onClick={close}>Cancel</Button>
<Button
disabled={isDisabled}
type="submit"
variant={variant === "destructive" ? "danger" : "heavy"}
>
Confirm
</Button>
</>
)}
</div>
</Form>
<Form
method={method ?? "POST"}
onSubmit={(event) => {
if (onSubmit) {
onSubmit(event);
}
if (!event.defaultPrevented) {
closeRef.current?.click();
}
}}
>
<div className="flex flex-col gap-4">{children}</div>
<div className="mt-5 flex justify-end gap-3">
{variant === "unactionable" ? (
<AlertDialog.Close render={<Button>Close</Button>} />
) : (
<>
<AlertDialog.Close render={<Button>Cancel</Button>} />
<AlertDialog.Close ref={closeRef} className="hidden" aria-hidden tabIndex={-1} />
<Button
disabled={isDisabled}
type="submit"
variant={variant === "destructive" ? "danger" : "heavy"}
>
Confirm
</Button>
</>
)}
</div>
</Form>
</AlertDialog.Popup>
);
}
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;
}
function DialogOverlay({ children }: { children: React.ReactNode }) {
return (
<Overlay>
<div
{...underlayProps}
aria-hidden="true"
<AlertDialog.Portal>
<AlertDialog.Backdrop
className={cn(
"fixed inset-0 z-20 h-screen w-screen",
"flex items-center justify-center",
"bg-mist-900/30 dark:bg-mist-950/60",
"entering:animate-in exiting:animate-out",
"entering:fade-in entering:duration-100 entering:ease-out",
"exiting:fade-out exiting:duration-50 exiting:ease-in",
"transition-opacity duration-100",
)}
/>
<div
{...modalProps}
className={cn(
"fixed inset-0 z-20 h-screen w-screen",
"flex items-center justify-center p-4",
@@ -170,7 +118,7 @@ function DModal(props: DModalProps) {
>
{children}
</div>
</Overlay>
</AlertDialog.Portal>
);
}
+2 -2
View File
@@ -6,7 +6,7 @@ function TableList(props: HTMLProps<HTMLDivElement>) {
return (
<div
{...props}
className={cn("rounded-lg", "border border-mist-100 dark:border-mist-800", props.className)}
className={cn("rounded-lg", "border border-mist-200 dark:border-mist-800", props.className)}
>
{props.children}
</div>
@@ -19,7 +19,7 @@ function Item(props: HTMLProps<HTMLDivElement>) {
{...props}
className={cn(
"flex items-center justify-between p-2 last:border-b-0",
"border-b border-mist-100 dark:border-mist-800",
"border-b border-mist-200 dark:border-mist-800",
props.className,
)}
>
+4 -4
View File
@@ -30,7 +30,7 @@ function TabList({ children, className }: { children: ReactNode; className?: str
<BaseTabs.List
className={cn(
"flex items-center rounded-t-lg w-fit max-w-full",
"border-mist-100 dark:border-mist-800",
"border-mist-200 dark:border-mist-800",
"border-t border-x",
className,
)}
@@ -52,10 +52,10 @@ function Tab({
{...props}
className={cn(
"pl-2 pr-3 py-2.5",
"data-[selected]:bg-mist-100 dark:data-[selected]:bg-mist-950",
"data-[selected]:bg-mist-50 dark:data-[selected]:bg-mist-950",
"focus:outline-hidden focus:ring-2 focus:ring-indigo-500/40 focus:ring-offset-1 z-10",
"dark:focus:ring-indigo-400/40 dark:focus:ring-offset-mist-900",
"border-r border-mist-100 dark:border-mist-800",
"border-r border-mist-200 dark:border-mist-800",
"first:rounded-tl-lg last:rounded-tr-lg last:border-r-0",
className,
)}
@@ -77,7 +77,7 @@ function Panel({
{...props}
className={cn(
"w-full overflow-clip rounded-b-lg rounded-r-lg",
"border border-mist-100 dark:border-mist-800",
"border border-mist-200 dark:border-mist-800",
className,
)}
>
-1
View File
@@ -1,7 +1,6 @@
@import "tailwindcss";
@plugin "tailwindcss-animate";
@plugin "tailwindcss-react-aria-components";
@theme {
--blur-xs: 2px;
-3
View File
@@ -49,14 +49,12 @@
"openapi-types": "^12.1.3",
"openid-client": "6.8.2",
"react": "19.2.4",
"react-aria": "3.46.0",
"react-codemirror-merge": "4.25.5",
"react-dom": "19.2.4",
"react-error-boundary": "^6.1.1",
"react-router": "^7.13.1",
"react-router-dom": "^7.13.1",
"react-router-hono-server": "2.25.0",
"react-stately": "3.44.0",
"remix-utils": "^9.0.1",
"tailwind-merge": "3.5.0",
"ulidx": "2.4.1",
@@ -81,7 +79,6 @@
"postcss": "^8.5.6",
"tailwindcss": "^4.2.1",
"tailwindcss-animate": "^1.0.7",
"tailwindcss-react-aria-components": "^2.0.1",
"testcontainers": "^11.12.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
-1701
View File
File diff suppressed because it is too large Load Diff