mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
ready to test
Testing Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -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>
|
||||
//
|
||||
// ===========================================================================
|
||||
Reference in New Issue
Block a user