ready to test

Testing

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-06-22 10:07:20 +08:00
parent 56d984a26a
commit fbef7cb6e6
283 changed files with 25961 additions and 1072 deletions
+238
View File
@@ -0,0 +1,238 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { VisuallyHidden } from "radix-ui";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
DialogClose,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import {
Drawer,
DrawerContent,
DrawerHeader,
DrawerTitle,
DrawerDescription,
DrawerFooter,
DrawerClose,
} from "@/components/ui/drawer";
// ---------------------------------------------------------------------------
// useMediaQuery
// ---------------------------------------------------------------------------
function useMediaQuery(query) {
const [matches, setMatches] = React.useState(false);
React.useEffect(() => {
const mql = window.matchMedia(query);
setMatches(mql.matches);
const handler = (e) => setMatches(e.matches);
mql.addEventListener("change", handler);
return () => mql.removeEventListener("change", handler);
}, [query]);
return matches;
}
// ---------------------------------------------------------------------------
// ResponsiveModal
//
// Props:
// open boolean
// onOpenChange (open: boolean) => void
//
// title ReactNode? — rendered as DialogTitle / DrawerTitle
// description ReactNode? — rendered as DialogDescription / DrawerDescription
// footer ReactNode? — rendered as DialogFooter / DrawerFooter
// children ReactNode? — modal body
// onAction - fetch api call
//
// dialogContentProps object? — forwarded to <DialogContent>
// drawerContentProps object? — forwarded to <DrawerContent>
// drawerDirection "top"|"bottom"|"left"|"right" (default "bottom")
// hideDrawerClose boolean? — hide the default Close button in drawer
//
// ---------------------------------------------------------------------------
// Usage:
//
// <ResponsiveModal
// open={open}
// onOpenChange={setOpen}
// title="Are you absolutely sure?"
// description="This action cannot be undone."
// footer={
// <>
// <button onClick={() => setOpen(false)}>Cancel</button>
// <button>Confirm</button>
// </>
// }
// >
// <MyForm />
// </ResponsiveModal>
//
// ---------------------------------------------------------------------------
export function ResponsiveModal({
open,
onOpenChange,
title,
description,
footer,
children,
onAction,
dialogContentProps = {},
drawerContentProps = {},
drawerDirection = "bottom",
hideDrawerClose = false,
}) {
const isDesktop = useMediaQuery("(min-width: 768px)");
// ── Desktop → Dialog ──────────────────────────────────────────────────
if (isDesktop) {
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
{...dialogContentProps}
className={cn("sm:max-w-lg", dialogContentProps.className)}
>
{title || description ? (
<DialogHeader>
{title && <DialogTitle>{title}</DialogTitle>}
{description && <DialogDescription>{description}</DialogDescription>}
</DialogHeader>
) : (
<VisuallyHidden.Root>
<DialogTitle />
<DialogDescription />
</VisuallyHidden.Root>
)}
{children}
{(footer || onAction) && (
<DialogFooter>
{footer}
{onAction && (
<Button onClick={onAction}>Confirm</Button>
)}
</DialogFooter>
)}
</DialogContent>
</Dialog>
);
}
// ── Mobile → Drawer ───────────────────────────────────────────────────
return (
<Drawer open={open} onOpenChange={onOpenChange} direction={drawerDirection}>
<DrawerContent
{...drawerContentProps}
className={cn("max-h-[85svh]", drawerContentProps.className)}
>
{title || description ? (
<DrawerHeader>
{title && <DrawerTitle>{title}</DrawerTitle>}
{description ? (
<DrawerDescription>{description}</DrawerDescription>
) : (
<VisuallyHidden.Root>
<DrawerDescription />
</VisuallyHidden.Root>
)}
</DrawerHeader>
) : (
<VisuallyHidden.Root>
<DrawerTitle />
<DrawerDescription />
</VisuallyHidden.Root>
)}
<div className="overflow-y-auto px-4 pb-4">{children}</div>
{(footer || onAction || !hideDrawerClose) && (
<DrawerFooter className="pt-2">
{footer}
{onAction && (
<button
onClick={onAction}
className="w-full rounded-md bg-primary px-4 py-3 text-sm font-medium text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors"
>
Confirm
</button>
)}
{!hideDrawerClose && (
<DrawerClose asChild>
<button className="mt-1 w-full rounded-md border border-input bg-background px-4 py-2 text-sm font-medium shadow-sm hover:bg-accent hover:text-accent-foreground">
Close
</button>
</DrawerClose>
)}
</DrawerFooter>
)}
</DrawerContent>
</Drawer>
);
}
export { useMediaQuery };
export default ResponsiveModal;
// ===========================================================================
// USAGE EXAMPLES
// ===========================================================================
//
// const [open, setOpen] = useState(false);
//
//
// ── 1. Full example ───────────────────────────────────────────────────────
//
// <ResponsiveModal
// open={open}
// onOpenChange={setOpen}
// title="Are you absolutely sure?"
// description="This action cannot be undone. This will permanently delete
// your account and remove your data from our servers."
// footer={
// <>
// <Button variant="outline" onClick={() => setOpen(false)}>Cancel</Button>
// <Button>Continue</Button>
// </>
// }
// >
// <MyForm />
// </ResponsiveModal>
//
//
// ── 2. Title only (no description) ───────────────────────────────────────
//
// <ResponsiveModal
// open={open}
// onOpenChange={setOpen}
// title="Confirm Delete"
// footer={<Button>Delete</Button>}
// >
// <p>Are you sure?</p>
// </ResponsiveModal>
//
//
// ── 3. No header ─────────────────────────────────────────────────────────
//
// <ResponsiveModal open={open} onOpenChange={setOpen}>
// <p>Body only.</p>
// </ResponsiveModal>
//
//
// ── 4. Custom width ───────────────────────────────────────────────────────
//
// <ResponsiveModal
// open={open}
// onOpenChange={setOpen}
// title="Wide Modal"
// dialogContentProps={{ className: "sm:max-w-2xl" }}
// >
// <BigTable />
// </ResponsiveModal>
//
// ===========================================================================