Skip to content

Commit 25c3bd9

Browse files
committed
feat: add responsive modal component and integrate with delete user functionality
1 parent e329ad2 commit 25c3bd9

File tree

6 files changed

+207
-18
lines changed

6 files changed

+207
-18
lines changed

package-lock.json

Lines changed: 13 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"tailwindcss": "^4.0.0",
5353
"tailwindcss-animate": "^1.0.7",
5454
"typescript": "^5.7.2",
55+
"vaul": "^1.1.2",
5556
"vite": "^6.0"
5657
},
5758
"optionalDependencies": {

resources/css/app.css

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,16 @@
156156
@apply bg-background text-foreground;
157157
}
158158
}
159+
160+
/*
161+
---break---
162+
*/
163+
164+
@layer base {
165+
* {
166+
@apply border-border outline-ring/50;
167+
}
168+
body {
169+
@apply bg-background text-foreground;
170+
}
171+
}

resources/js/components/delete-user.tsx

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import { useForm } from '@inertiajs/react';
2-
import { FormEventHandler, useRef } from 'react';
2+
import { FormEventHandler, useRef, useState } from 'react';
33

44
// Components...
55
import InputError from '@/components/input-error';
6+
import { ResponsiveModal } from '@/components/responsive-modal';
67
import { Button } from '@/components/ui/button';
78
import { Input } from '@/components/ui/input';
89
import { Label } from '@/components/ui/label';
910

1011
import HeadingSmall from '@/components/heading-small';
1112

12-
import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
13+
import { DialogDescription, DialogFooter, DialogTitle } from '@/components/ui/dialog';
1314

1415
export default function DeleteUser() {
1516
const passwordInput = useRef<HTMLInputElement>(null);
1617
const { data, setData, delete: destroy, processing, reset, errors, clearErrors } = useForm({ password: '' });
18+
const [isOpen, setIsOpen] = useState<boolean>(false);
1719

1820
const deleteUser: FormEventHandler = (e) => {
1921
e.preventDefault();
@@ -27,6 +29,7 @@ export default function DeleteUser() {
2729
};
2830

2931
const closeModal = () => {
32+
setIsOpen(false);
3033
clearErrors();
3134
reset();
3235
};
@@ -40,13 +43,14 @@ export default function DeleteUser() {
4043
<p className="text-sm">Please proceed with caution, this cannot be undone.</p>
4144
</div>
4245

43-
<Dialog>
44-
<DialogTrigger asChild>
45-
<Button variant="destructive">Delete account</Button>
46-
</DialogTrigger>
47-
<DialogContent>
46+
<Button variant="destructive" onClick={() => setIsOpen(true)}>
47+
Delete account
48+
</Button>
49+
50+
<ResponsiveModal open={isOpen} onOpenChange={setIsOpen}>
51+
<div className="p-4 sm:p-6">
4852
<DialogTitle>Are you sure you want to delete your account?</DialogTitle>
49-
<DialogDescription>
53+
<DialogDescription className="mt-2 mb-4">
5054
Once your account is deleted, all of its resources and data will also be permanently deleted. Please enter your password
5155
to confirm you would like to permanently delete your account.
5256
</DialogDescription>
@@ -71,19 +75,17 @@ export default function DeleteUser() {
7175
</div>
7276

7377
<DialogFooter>
74-
<DialogClose asChild>
75-
<Button variant="secondary" onClick={closeModal}>
76-
Cancel
77-
</Button>
78-
</DialogClose>
78+
<Button variant="secondary" onClick={closeModal}>
79+
Cancel
80+
</Button>
7981

80-
<Button variant="destructive" disabled={processing} asChild>
81-
<button type="submit">Delete account</button>
82+
<Button variant="destructive" disabled={processing} type="submit">
83+
Delete account
8284
</Button>
8385
</DialogFooter>
8486
</form>
85-
</DialogContent>
86-
</Dialog>
87+
</div>
88+
</ResponsiveModal>
8789
</div>
8890
</div>
8991
);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useIsMobile } from '@/hooks/use-mobile';
2+
3+
import { Dialog, DialogContent } from '@/components/ui/dialog';
4+
import { Drawer, DrawerContent } from '@/components/ui/drawer';
5+
import React from 'react';
6+
7+
interface ResponsiveModalProps {
8+
children: React.ReactNode;
9+
open: boolean;
10+
onOpenChange: (open: boolean) => void;
11+
}
12+
13+
export const ResponsiveModal = ({ children, onOpenChange, open }: ResponsiveModalProps) => {
14+
const isMobile = useIsMobile();
15+
16+
if (isMobile) {
17+
return (
18+
<Drawer open={open} onOpenChange={onOpenChange}>
19+
<DrawerContent>
20+
<div className="hide-scrollbar max-h-[85vh] overflow-y-auto">{children}</div>
21+
</DrawerContent>
22+
</Drawer>
23+
);
24+
}
25+
26+
return (
27+
<Dialog open={open} onOpenChange={onOpenChange}>
28+
<DialogContent className="hide-scrollbar max-h-[85vh] w-full overflow-y-auto border-none sm:max-w-lg">{children}</DialogContent>
29+
</Dialog>
30+
);
31+
};
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import * as React from "react"
2+
import { Drawer as DrawerPrimitive } from "vaul"
3+
4+
import { cn } from "@/lib/utils"
5+
6+
function Drawer({
7+
...props
8+
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
9+
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
10+
}
11+
12+
function DrawerTrigger({
13+
...props
14+
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
15+
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
16+
}
17+
18+
function DrawerPortal({
19+
...props
20+
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
21+
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
22+
}
23+
24+
function DrawerClose({
25+
...props
26+
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
27+
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
28+
}
29+
30+
function DrawerOverlay({
31+
className,
32+
...props
33+
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
34+
return (
35+
<DrawerPrimitive.Overlay
36+
data-slot="drawer-overlay"
37+
className={cn(
38+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80",
39+
className
40+
)}
41+
{...props}
42+
/>
43+
)
44+
}
45+
46+
function DrawerContent({
47+
className,
48+
children,
49+
...props
50+
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
51+
return (
52+
<DrawerPortal data-slot="drawer-portal">
53+
<DrawerOverlay />
54+
<DrawerPrimitive.Content
55+
data-slot="drawer-content"
56+
className={cn(
57+
"group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
58+
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg",
59+
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg",
60+
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:sm:max-w-sm",
61+
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:sm:max-w-sm",
62+
className
63+
)}
64+
{...props}
65+
>
66+
<div className="bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
67+
{children}
68+
</DrawerPrimitive.Content>
69+
</DrawerPortal>
70+
)
71+
}
72+
73+
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
74+
return (
75+
<div
76+
data-slot="drawer-header"
77+
className={cn("flex flex-col gap-1.5 p-4", className)}
78+
{...props}
79+
/>
80+
)
81+
}
82+
83+
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
84+
return (
85+
<div
86+
data-slot="drawer-footer"
87+
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
88+
{...props}
89+
/>
90+
)
91+
}
92+
93+
function DrawerTitle({
94+
className,
95+
...props
96+
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
97+
return (
98+
<DrawerPrimitive.Title
99+
data-slot="drawer-title"
100+
className={cn("text-foreground font-semibold", className)}
101+
{...props}
102+
/>
103+
)
104+
}
105+
106+
function DrawerDescription({
107+
className,
108+
...props
109+
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
110+
return (
111+
<DrawerPrimitive.Description
112+
data-slot="drawer-description"
113+
className={cn("text-muted-foreground text-sm", className)}
114+
{...props}
115+
/>
116+
)
117+
}
118+
119+
export {
120+
Drawer,
121+
DrawerPortal,
122+
DrawerOverlay,
123+
DrawerTrigger,
124+
DrawerClose,
125+
DrawerContent,
126+
DrawerHeader,
127+
DrawerFooter,
128+
DrawerTitle,
129+
DrawerDescription,
130+
}

0 commit comments

Comments
 (0)