Dialogs
Confirmations, forms, sharing, scheduling, and multi-step dialogs.
Fromblocks.so
Dialog Confirmation
"use client"
import { Check } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
export default function Dialog01() {
return (
<Dialog defaultOpen>
<DialogTrigger render={<Button variant="outline" />}>
Show Dialog
</DialogTrigger>
<DialogContent className="sm:max-w-sm">
<div className="flex justify-center">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
<Check className="h-6 w-6 text-green-600" />
</div>
</div>
<DialogHeader className="gap-0 text-center">
<DialogTitle className="text-balance text-center">
Payment successful
</DialogTitle>
<DialogDescription className="mx-auto mt-2 text-pretty text-center sm:max-w-[90%]">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
amet labore.
</DialogDescription>
</DialogHeader>
<DialogFooter className="sm:justify-center">
<DialogClose render={<Button className="w-full" />}>
Go back to dashboard
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-01.jsonDialog with Warning Icon
"use client"
import { Check } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
export default function Dialog04() {
return (
<Dialog defaultOpen>
<DialogTrigger render={<Button variant="outline" />}>
Show Dialog
</DialogTrigger>
<DialogContent className="sm:max-w-sm">
<div className="flex justify-center">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100">
<Check className="h-6 w-6 text-green-600" />
</div>
</div>
<DialogHeader className="gap-0 text-center">
<DialogTitle className="text-balance text-center">
Payment successful
</DialogTitle>
<DialogDescription className="mx-auto mt-2 text-pretty text-center sm:max-w-[90%]">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequatur
amet labore.
</DialogDescription>
</DialogHeader>
<DialogFooter className="flex-col gap-2 sm:flex-row sm:gap-3">
<DialogClose
render={<Button className="w-full sm:flex-1" variant="default" />}
>
Deactivate
</DialogClose>
<DialogClose
render={<Button className="w-full sm:flex-1" variant="outline" />}
>
Cancel
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-02.jsonDialog with Input Field
"use client"
import { AlertTriangleIcon } from "lucide-react"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
export default function Dialog02() {
const [open, setOpen] = useState(true)
return (
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger render={<Button variant="destructive" />}>
Deactivate
</DialogTrigger>
<DialogContent className="sm:max-w-lg">
<div className="flex items-start space-x-4">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-red-100">
<AlertTriangleIcon className="h-6 w-6 text-red-600" />
</div>
<DialogHeader>
<DialogTitle>Deactivate account</DialogTitle>
<DialogDescription>
Are you sure you want to deactivate your account? All of your data
will be permanently removed. This action cannot be undone.
</DialogDescription>
</DialogHeader>
</div>
<DialogFooter>
<DialogClose render={<Button variant="outline" />}>
Cancel
</DialogClose>
<Button onClick={() => setOpen(false)} variant="destructive">
Deactivate
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-03.jsonDialog with Two Buttons
"use client"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export default function Dialog03() {
return (
<Dialog defaultOpen>
<div className="flex items-center justify-center py-36">
<DialogTrigger render={<Button />}>Edit Profile</DialogTrigger>
</div>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Edit Profile</DialogTitle>
<DialogDescription>
Make changes to your profile here. Click save when you're done.
</DialogDescription>
</DialogHeader>
<div className="space-y-2">
<Label className="font-medium text-sm" htmlFor="username">
Username
</Label>
<Input
id="username"
name="username"
placeholder="Your username"
type="text"
/>
</div>
<DialogFooter>
<Button className="w-full" type="submit">
Save Changes
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-04.jsonDialog with Password Confirmation
"use client"
import { EyeIcon, EyeOffIcon } from "lucide-react"
import { type FormEvent, useState } from "react"
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export default function Dialog05() {
const [isOpen, setIsOpen] = useState(true)
const [isVisible, setIsVisible] = useState<boolean>(false)
const toggleVisibility = () => setIsVisible((prevState) => !prevState)
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
setIsOpen(false)
}
return (
<AlertDialog onOpenChange={setIsOpen} open={isOpen}>
<AlertDialogTrigger render={<Button variant="destructive" />}>
Delete Workspace
</AlertDialogTrigger>
<AlertDialogContent className="sm:max-w-md">
<AlertDialogHeader>
<AlertDialogTitle className="text-balance">
Delete workspace
</AlertDialogTitle>
<AlertDialogDescription className="text-pretty">
All workspace data will be permanently deleted. There is no coming
back after you press delete.
</AlertDialogDescription>
</AlertDialogHeader>
<form onSubmit={handleSubmit}>
<div>
<Label className="font-medium text-sm" htmlFor="delete-workspace">
Confirm password
</Label>
<div className="relative mt-2">
<Input
className="pe-9"
id="delete-workspace"
name="delete-workspace"
placeholder="Password"
required
type={isVisible ? "text" : "password"}
/>
<button
aria-controls="delete-workspace"
aria-label={isVisible ? "Hide password" : "Show password"}
aria-pressed={isVisible}
className="absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md"
onClick={toggleVisibility}
type="button"
>
{isVisible ? (
<EyeOffIcon aria-hidden="true" size={16} />
) : (
<EyeIcon aria-hidden="true" size={16} />
)}
</button>
</div>
</div>
<AlertDialogFooter className="mt-4">
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button type="submit" variant="destructive">
Delete workspace permanently
</Button>
</AlertDialogFooter>
</form>
</AlertDialogContent>
</AlertDialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-05.jsonDialog with Privacy Toggle
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
export default function Dialog06() {
const [open, setOpen] = useState(true)
return (
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger render={<Button onClick={() => setOpen(true)} />}>
Create Workspace
</DialogTrigger>
<DialogContent className="p-0 sm:max-w-lg">
<DialogHeader className="px-6 pt-4">
<DialogTitle className="text-balance font-semibold text-foreground text-lg">
Create workspace
</DialogTitle>
<DialogDescription className="mt-2 text-pretty text-muted-foreground text-sm leading-6">
Workspaces are shared environments where teams can connect to data
sources, run queries and create reports.
</DialogDescription>
</DialogHeader>
<form action="#" method="POST">
<div className="px-6 pb-4">
<Label className="font-medium text-sm" htmlFor="workspace-name">
Workspace Name<span className="text-destructive">*</span>
</Label>
<Input
className="mt-2"
id="workspace-name"
name="workspace-name"
placeholder="My workspace"
required
type="text"
/>
<Button className="mt-4 w-full" type="submit">
Create workspace
</Button>
</div>
<div className="rounded-b-md border-t bg-muted px-6 py-4">
<div className="flex items-start space-x-3">
<div className="mt-1 pt-0.5">
<Switch
id="enable-private-workspace"
name="enable-private-workspace"
/>
</div>
<div>
<Label
className="font-medium text-sm"
htmlFor="enable-private-workspace"
>
Set workspace to private
</Label>
<p className="text-pretty text-muted-foreground text-sm">
Only those invited can access or view
</p>
</div>
</div>
</div>
</form>
</DialogContent>
</Dialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-06.jsonDialog with Member List
"use client"
import { EyeIcon, EyeOffIcon } from "lucide-react"
import { type FormEvent, useState } from "react"
import {
AlertDialog,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export default function Dialog08() {
const [open, setOpen] = useState(true)
const [isVisible, setIsVisible] = useState<boolean>(false)
const toggleVisibility = () => setIsVisible((prevState) => !prevState)
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault()
setOpen(false)
}
return (
<AlertDialog onOpenChange={setOpen} open={open}>
<AlertDialogTrigger render={<Button variant="destructive" />}>
Deactivate 2FA
</AlertDialogTrigger>
<AlertDialogContent className="sm:max-w-lg">
<AlertDialogHeader>
<AlertDialogTitle className="text-balance">
Deactivate two-step authentication
</AlertDialogTitle>
<AlertDialogDescription className="text-pretty">
Enter your password to deactivate the two-step authentication login.
Make sure to have your smartphone ready.
</AlertDialogDescription>
</AlertDialogHeader>
<form className="space-y-4" onSubmit={handleSubmit}>
<div>
<Label className="font-medium text-sm" htmlFor="email">
Email
</Label>
<Input
className="mt-2"
disabled
id="email"
name="email"
placeholder="ephraim@blocks.so"
type="email"
/>
</div>
<div>
<Label className="font-medium text-sm" htmlFor="password">
Confirm password
</Label>
<div className="relative mt-2">
<Input
className="pe-9"
id="password"
name="password"
placeholder="Password"
required
type={isVisible ? "text" : "password"}
/>
<button
aria-controls="password"
aria-label={isVisible ? "Hide password" : "Show password"}
aria-pressed={isVisible}
className="absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md"
onClick={toggleVisibility}
type="button"
>
{isVisible ? (
<EyeOffIcon aria-hidden="true" size={16} />
) : (
<EyeIcon aria-hidden="true" size={16} />
)}
</button>
</div>
</div>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button type="submit" variant="destructive">
Deactivate
</Button>
</AlertDialogFooter>
</form>
</AlertDialogContent>
</AlertDialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-07.jsonDialog with Email and Password Fields
"use client"
import { UserPlus } from "lucide-react"
import { useState } from "react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
const members = [
{
name: "Ephraim Duncan",
email: "ephraim@documenso.com",
avatarUrl: "https://blocks.so/avatar-01.png",
initials: "ED",
status: "member",
},
{
name: "Lucas Smith",
email: "lucas@documenso.com",
avatarUrl: "https://blocks.so/avatar-03.png",
initials: "LS",
status: "member",
},
{
name: "Timur Ercan",
email: "timur@documenso.com",
avatarUrl: "https://blocks.so/avatar-02.jpg",
initials: "TE",
status: "member",
},
{
name: "Catalin Pit",
email: "catalin@documenso.com",
avatarUrl: "https://blocks.so/avatar-04.jpg",
initials: "CP",
status: "member",
},
]
export default function Dialog07() {
const [open, setOpen] = useState(true)
return (
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger render={<Button onClick={() => setOpen(true)} />}>
Invite members
</DialogTrigger>
<DialogContent className="sm:max-w-2xl">
<DialogHeader>
<DialogTitle className="text-balance font-semibold text-foreground">
Invite members
</DialogTitle>
<DialogDescription className="text-pretty text-muted-foreground text-sm leading-6">
Add new team members to your workspace. Please consider your
organization's policies when adding external people.
</DialogDescription>
</DialogHeader>
<form>
<div className="flex w-full items-center space-x-2">
<div className="relative flex-1">
<UserPlus className="-translate-y-1/2 absolute top-1/2 left-3 h-4 w-4 text-muted-foreground" />
<Input
className="h-10 pl-9"
id="inviteEmail"
placeholder="Add email..."
type="email"
/>
</div>
<Button className="h-10" type="submit">
Invite
</Button>
</div>
</form>
<h4 className="mt-4 text-balance font-medium text-foreground text-sm">
People with existing access
</h4>
<ul className="divide-y">
{members.map((member) => (
<li
className="flex items-center justify-between py-2.5"
key={member.name}
>
<div className="flex items-center space-x-3">
<Avatar className="h-9 w-9">
<AvatarImage alt={member.name} src={member.avatarUrl} />
<AvatarFallback>{member.initials}</AvatarFallback>
</Avatar>
<span className="font-medium text-foreground">
{member.name}
</span>
</div>
<Badge
className="bg-background font-medium text-xs"
variant="outline"
>
{member.status}
</Badge>
</li>
))}
</ul>
</DialogContent>
</Dialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-08.jsonDialog with Link Share and Toggles
"use client"
import { CheckIcon, CopyIcon, ExternalLink, Link, Share2 } from "lucide-react"
import { useRef, useState } from "react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Switch } from "@/components/ui/switch"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
import { cn } from "@/lib/utils"
export default function Dialog09() {
const [copied, setCopied] = useState<boolean>(false)
const inputRef = useRef<HTMLInputElement>(null)
const handleCopy = () => {
if (inputRef.current) {
navigator.clipboard.writeText(inputRef.current.value)
setCopied(true)
setTimeout(() => setCopied(false), 1500)
}
}
return (
<Dialog defaultOpen>
<DialogTrigger render={<Button />}>
<Share2 className="mr-2 h-4 w-4" />
Share
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Share & Collaborate</DialogTitle>
<p className="text-pretty text-muted-foreground text-sm">
Share this project with your team to collaborate on it.
</p>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="flex items-center justify-between space-x-2">
<div className="flex items-center space-x-2">
<span>Enable comments and suggestions</span>
</div>
<Switch id="comments" />
</div>
<div className="space-y-1.5">
<Label className="sr-only" htmlFor="share-link">
Share Link
</Label>
<div className="relative">
<Input
className="pe-9"
id="share-link"
readOnly
ref={inputRef}
value="https://writer.so/app/projects/123?share=true"
/>
<TooltipProvider delay={0}>
<Tooltip>
<TooltipTrigger
render={
<button
aria-label={copied ? "Copied" : "Copy to clipboard"}
className="absolute inset-y-0 end-0 flex h-full w-9 items-center justify-center rounded-e-md text-muted-foreground/80 outline-none transition-[color,box-shadow] hover:text-foreground focus:z-10 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed"
disabled={copied}
onClick={handleCopy}
/>
}
>
<div
className={cn(
"transition-[transform,opacity] duration-200 ease-out",
copied ? "scale-100 opacity-100" : "scale-0 opacity-0"
)}
>
<CheckIcon
aria-hidden="true"
className="text-primary"
size={16}
/>
</div>
<div
className={cn(
"absolute transition-[transform,opacity] duration-200 ease-out",
copied ? "scale-0 opacity-0" : "scale-100 opacity-100"
)}
>
<CopyIcon aria-hidden="true" size={16} />
</div>
</TooltipTrigger>
<TooltipContent className="px-2 py-1 text-xs">
{copied ? "Copied!" : "Copy to clipboard"}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
<div className="flex space-x-2">
<Button className="flex-1 gap-2" onClick={handleCopy}>
<Link className="h-4 w-4" />
Copy Link
</Button>
<Button className="flex-1 gap-2" variant="outline">
<ExternalLink className="h-4 w-4" />
Preview
</Button>
</div>
</div>
</DialogContent>
</Dialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-09.jsonDialog with Calendar and Time Picker
"use client"
import { format } from "date-fns"
import { CalendarIcon } from "lucide-react"
import { useMemo, useState } from "react"
import { Button } from "@/components/ui/button"
import { Calendar } from "@/components/ui/calendar"
import {
Dialog,
DialogClose,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Textarea } from "@/components/ui/textarea"
import { cn } from "@/lib/utils"
export default function Dialog10() {
const [date, setDate] = useState<Date | undefined>(new Date())
const [startTime, setStartTime] = useState("09:00")
const timeOptions = useMemo(() => {
const options = []
for (let hour = 0; hour <= 23; hour++) {
for (let minute = 0; minute < 60; minute += 30) {
const formattedHour = hour.toString().padStart(2, "0")
const formattedMinute = minute.toString().padStart(2, "0")
const value = `${formattedHour}:${formattedMinute}`
const tempDate = new Date(2000, 0, 1, hour, minute)
const label = format(tempDate, "h:mm a")
options.push({ value, label })
}
}
if (!options.find((opt) => opt.value === "23:59")) {
const endOfDay = new Date(2000, 0, 1, 23, 59)
options.push({ value: "23:59", label: format(endOfDay, "h:mm a") })
}
return options
}, [])
return (
<Dialog defaultOpen>
<DialogTrigger render={<Button />}>Show dialog</DialogTrigger>
<DialogContent className="gap-0 p-0 sm:max-w-lg">
<DialogHeader className="border-b px-6 py-4 pt-5">
<DialogTitle>Schedule a Meeting</DialogTitle>
</DialogHeader>
<form action="#" method="POST">
<div className="space-y-6 p-6">
<div className="space-y-2">
<Label htmlFor="title">Meeting Title</Label>
<Input
id="title"
name="title"
placeholder="e.g., Project Kickoff"
/>
</div>
<div className="space-y-2">
<Label htmlFor="attendees">Attendees</Label>
<Input
id="attendees"
name="attendees"
placeholder="user1@example.com, user2@example.com"
/>
<p className="text-pretty text-muted-foreground text-xs">
Enter email addresses separated by commas.
</p>
</div>
<div className="grid grid-cols-3 gap-4">
<div className="col-span-2 space-y-2">
<Label htmlFor="date">Date</Label>
<Popover>
<PopoverTrigger
render={
<Button
className={cn(
"w-full justify-start text-left font-normal",
!date && "text-muted-foreground"
)}
id="date"
variant={"outline"}
/>
}
>
<CalendarIcon className="mr-1 h-4 w-4 shrink-0" />{" "}
{date ? format(date, "PPP") : <span>Pick a date</span>}
</PopoverTrigger>
<PopoverContent align="start" className="w-auto p-0">
<Calendar
autoFocus
mode="single"
onSelect={setDate}
selected={date}
/>
</PopoverContent>
</Popover>
</div>
<div className="space-y-2">
<Label htmlFor="time">Time</Label>
<Select
items={timeOptions}
onValueChange={(value) => {
if (value) {
setStartTime(value)
}
}}
value={startTime}
>
<SelectTrigger className="w-full" id="time">
<SelectValue placeholder="Select time" />
</SelectTrigger>
<SelectContent>
{timeOptions.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="location">Location / Conference Link</Label>
<Input
id="location"
name="location"
placeholder="e.g., Conference Room B or https://meet.example.com/..."
/>
</div>
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea
className="min-h-[100px]"
id="description"
name="description"
placeholder="Optional: Add an agenda or notes..."
/>
</div>
</div>
<div className="flex items-center justify-end space-x-2 border-t p-4">
<DialogClose render={<Button type="button" variant="ghost" />}>
Cancel
</DialogClose>
<Button size="sm" type="submit">
Schedule
</Button>
</div>
</form>
</DialogContent>
</Dialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-10.jsonDialog Multi-Step Wizard
"use client"
import { AppWindowIcon as Apps } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Label } from "@/components/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Separator } from "@/components/ui/separator"
export default function Dialog11() {
return (
<Dialog defaultOpen>
<DialogTrigger render={<Button />}>Initialize New Project</DialogTrigger>
<DialogContent className="gap-0 overflow-visible p-0 sm:max-w-2xl">
<DialogHeader className="mb-0 border-b px-6 py-4">
<DialogTitle>Initialize New Project</DialogTitle>
</DialogHeader>
<form action="#" method="POST">
<div className="flex flex-col-reverse md:flex-row">
<div className="flex flex-col justify-between md:w-80 md:border-r">
<div className="flex-1 grow">
<div className="border-t p-6 md:border-none">
<div className="flex items-center space-x-3">
<div className="inline-flex shrink-0 items-center justify-center rounded-sm bg-muted p-3">
<Apps
aria-hidden={true}
className="size-5 text-foreground"
/>
</div>
<div className="space-y-0.5">
<h3 className="text-balance font-medium text-foreground text-sm">
Project Starter
</h3>
<p className="text-pretty text-muted-foreground text-sm">
Configure your new codebase
</p>
</div>
</div>
<Separator className="my-4" />
<h4 className="text-balance font-medium text-foreground text-sm">
Description
</h4>
<p className="mt-1 text-pretty text-muted-foreground text-sm leading-6">
Quickly set up the foundational tools for your project.
</p>
<h4 className="mt-6 text-balance font-medium text-foreground text-sm">
Info
</h4>
<p className="mt-1 text-pretty text-muted-foreground text-sm leading-6">
Select your preferred stack and configurations.
</p>
</div>
</div>
<div className="flex items-center justify-between border-t p-4">
<DialogClose render={<Button type="button" variant="ghost" />}>
Cancel
</DialogClose>
<Button size="sm" type="submit">
Initialize
</Button>
</div>
</div>
<div className="flex-1 space-y-6 p-6 md:px-6 md:pt-6 md:pb-8">
<div className="space-y-2">
<div className="flex items-center space-x-3">
<div className="inline-flex size-6 items-center justify-center rounded-sm bg-muted text-foreground text-sm">
1
</div>
<Label
className="font-medium text-foreground text-sm"
htmlFor="framework"
>
Select Framework
</Label>
</div>
<Select
defaultValue="react"
items={{
react: "Next.js",
vue: "React Router",
angular: "Tanstack Start",
svelte: "SvelteKit",
vanilla: "SolidStart",
}}
>
<SelectTrigger
className="w-full"
id="framework"
name="framework"
>
<SelectValue placeholder="Select framework" />
</SelectTrigger>
<SelectContent>
<SelectItem value="react">Next.js</SelectItem>
<SelectItem value="vue">React Router</SelectItem>
<SelectItem value="angular">Tanstack Start</SelectItem>
<SelectItem value="svelte">SvelteKit</SelectItem>
<SelectItem value="vanilla">SolidStart</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<div className="flex items-center space-x-3">
<div className="inline-flex size-6 items-center justify-center rounded-sm bg-muted text-foreground text-sm">
2
</div>
<Label
className="font-medium text-foreground text-sm"
htmlFor="package-manager"
>
Choose Package Manager
</Label>
</div>
<Select
defaultValue="npm"
items={{ npm: "npm", yarn: "yarn", pnpm: "pnpm", bun: "bun" }}
>
<SelectTrigger
className="w-full"
id="package-manager"
name="package-manager"
>
<SelectValue placeholder="Select manager" />
</SelectTrigger>
<SelectContent>
<SelectItem value="npm">npm</SelectItem>
<SelectItem value="yarn">yarn</SelectItem>
<SelectItem value="pnpm">pnpm</SelectItem>
<SelectItem value="bun">bun</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<div className="flex items-center space-x-3">
<div className="inline-flex size-6 items-center justify-center rounded-sm bg-muted text-foreground text-sm">
3
</div>
<Label
className="font-medium text-foreground text-sm"
htmlFor="linter"
>
Configure Linter/Formatter
</Label>
</div>
<p className="mt-1 text-pretty text-muted-foreground text-xs">
Ensure code quality and consistency.
</p>
<Select
defaultValue="eslint-prettier"
items={{
"eslint-prettier": "ESLint + Prettier",
eslint: "ESLint Only",
prettier: "Prettier Only",
biome: "Biome",
oxlint: "Oxlint",
none: "None",
}}
>
<SelectTrigger
className="mt-4 w-full"
id="linter"
name="linter"
>
<SelectValue placeholder="Select tools" />
</SelectTrigger>
<SelectContent>
<SelectItem value="eslint-prettier">
ESLint + Prettier
</SelectItem>
<SelectItem value="eslint">ESLint Only</SelectItem>
<SelectItem value="prettier">Prettier Only</SelectItem>
<SelectItem value="biome">Biome</SelectItem>
<SelectItem value="oxlint">Oxlint</SelectItem>
<SelectItem value="none">None</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<div className="flex items-center space-x-3">
<div className="inline-flex size-6 items-center justify-center rounded-sm bg-muted text-foreground text-sm">
4
</div>
<Label
className="font-medium text-foreground text-sm"
htmlFor="testing-tool"
>
Select Testing Tool
</Label>
</div>
<p className="mt-1 text-pretty text-muted-foreground text-xs">
Choose a framework for unit/integration tests.
</p>
<Select
defaultValue="jest"
items={{
jest: "Jest",
vitest: "Vitest",
cypress: "Cypress",
none: "None",
}}
>
<SelectTrigger
className="mt-4 w-full"
id="testing-tool"
name="testing-tool"
>
<SelectValue placeholder="Select tool" />
</SelectTrigger>
<SelectContent>
<SelectItem value="jest">Jest</SelectItem>
<SelectItem value="vitest">Vitest</SelectItem>
<SelectItem value="cypress">Cypress</SelectItem>
<SelectItem value="none">None</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</div>
</form>
</DialogContent>
</Dialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-11.jsonDialog with Avatar Upload
"use client"
import { Plus, UserRoundIcon, X } from "lucide-react"
import { useRef, useState } from "react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export default function Dialog12() {
const [open, setOpen] = useState(true)
const [authorName, setAuthorName] = useState("Ephraim Duncan")
const [title, setTitle] = useState("Design Engineer")
const [image, setImage] = useState<string | null>(null)
const fileInputRef = useRef<HTMLInputElement>(null)
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file) {
if (file.size > 1_048_576) {
alert("File size exceeds 1MB limit")
return
}
const reader = new FileReader()
reader.onload = (event) => {
setImage(event.target?.result as string)
}
reader.readAsDataURL(file)
}
}
const triggerFileInput = () => {
fileInputRef.current?.click()
}
return (
<Dialog onOpenChange={setOpen} open={open}>
<DialogTrigger render={<Button />}>Open Dialog</DialogTrigger>
<DialogContent className="gap-0 rounded-3xl p-0 sm:max-w-lg">
<DialogHeader className="border-b px-6 py-4">
<DialogTitle className="text-balance font-medium">
Add a writer
</DialogTitle>
</DialogHeader>
<div className="grid grid-cols-1 px-6 pt-4 pb-6 md:grid-cols-5">
<div className="flex flex-col items-center justify-center md:col-span-2">
<div className="relative mb-2">
<Avatar className="h-24 w-24 border-2 border-muted">
<AvatarImage alt="Profile" src={image || undefined} />
<AvatarFallback>
<UserRoundIcon
aria-hidden="true"
className="text-muted-foreground"
size={52}
/>
</AvatarFallback>
</Avatar>
<Button
className="-top-0.5 -right-0.5 absolute rounded-full border-[3px] border-background bg-accent hover:bg-accent"
onClick={() => {
if (image) {
setImage(null)
if (fileInputRef.current) {
fileInputRef.current.value = ""
}
} else {
triggerFileInput()
}
}}
size="icon-sm"
variant="ghost"
>
{image ? (
<X className="h-4 w-4 text-muted-foreground" />
) : (
<Plus className="h-3 w-3 text-muted-foreground" />
)}
<span className="sr-only">
{image ? "Remove image" : "Upload image"}
</span>
</Button>
</div>
<p className="text-pretty text-center font-medium">Upload Image</p>
<p className="text-pretty text-center text-muted-foreground text-sm">
Max file size: 1MB
</p>
<input
accept="image/*"
className="hidden"
onChange={handleFileChange}
ref={fileInputRef}
type="file"
/>
<Button
className="mt-2"
onClick={triggerFileInput}
size="sm"
variant="outline"
>
Add Image
</Button>
</div>
<div className="flex flex-col justify-between md:col-span-3">
<div className="space-y-4">
<div className="space-y-1">
<Label className="flex items-center" htmlFor="author-name">
Author name <span className="text-primary">*</span>
</Label>
<Input
id="author-name"
onChange={(e) => setAuthorName(e.target.value)}
required
value={authorName}
/>
</div>
<div className="space-y-1">
<div className="flex items-center">
<Label htmlFor="title">Title</Label>
</div>
<Input
id="title"
onChange={(e) => setTitle(e.target.value)}
value={title}
/>
</div>
</div>
<div className="flex justify-end gap-2">
<Button onClick={() => setOpen(false)} variant="outline">
Cancel
</Button>
<Button className="bg-foreground text-background hover:bg-foreground/90">
Save Changes
</Button>
</div>
</div>
</div>
</DialogContent>
</Dialog>
)
}npx shadcn@latest add https://blocks.so/r/dialog-12.json