import { useRef, useState } from "react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogCloseTrigger,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
export default function Component() {
const [hasReadToBottom, setHasReadToBottom] = useState(false)
const contentRef = useRef<HTMLDivElement>(null)
const handleScroll = () => {
const content = contentRef.current
if (!content) return
const scrollPercentage = content.scrollTop / (content.scrollHeight - content.clientHeight)
if (scrollPercentage >= 0.99 && !hasReadToBottom) {
setHasReadToBottom(true)
}
}
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Terms & Conditions</Button>
</DialogTrigger>
<DialogContent className="flex flex-col gap-0 p-0 sm:max-h-[min(640px,80vh)] sm:max-w-lg [&>button:last-child]:top-3.5">
<DialogHeader className="contents space-y-0 text-left">
<DialogTitle className="border-b px-6 py-4 text-base">Terms & Conditions</DialogTitle>
<div ref={contentRef} onScroll={handleScroll} className="overflow-y-auto">
<DialogDescription asChild>
<div className="px-6 py-4">
<div className="space-y-4 [&_strong]:font-semibold [&_strong]:text-foreground">
<div className="space-y-4">
<div className="space-y-1">
<p>
<strong>Acceptance of Terms</strong>
</p>
<p>
By accessing and using this website, users agree to comply with and be bound
by these Terms of Service. Users who do not agree with these terms should
discontinue use of the website immediately.
</p>
</div>
<div className="space-y-1">
<p>
<strong>User Account Responsibilities</strong>
</p>
<p>
Users are responsible for maintaining the confidentiality of their account
credentials. Any activities occurring under a user‘s account are the
sole responsibility of the account holder. Users must notify the website
administrators immediately of any unauthorized account access.
</p>
</div>
<div className="space-y-1">
<p>
<strong>Content Usage and Restrictions</strong>
</p>
<p>
The website and its original content are protected by intellectual property
laws. Users may not reproduce, distribute, modify, create derivative works,
or commercially exploit any content without explicit written permission from
the website owners.
</p>
</div>
<div className="space-y-1">
<p>
<strong>Limitation of Liability</strong>
</p>
<p>
The website provides content “as is“ without any warranties. The
website owners shall not be liable for direct, indirect, incidental,
consequential, or punitive damages arising from user interactions with the
platform.
</p>
</div>
<div className="space-y-1">
<p>
<strong>User Conduct Guidelines</strong>
</p>
<ul className="list-disc pl-6">
<li>Not upload harmful or malicious content</li>
<li>Respect the rights of other users</li>
<li>Avoid activities that could disrupt website functionality</li>
<li>Comply with applicable local and international laws</li>
</ul>
</div>
<div className="space-y-1">
<p>
<strong>Modifications to Terms</strong>
</p>
<p>
The website reserves the right to modify these terms at any time. Continued
use of the website after changes constitutes acceptance of the new terms.
</p>
</div>
<div className="space-y-1">
<p>
<strong>Termination Clause</strong>
</p>
<p>
The website may terminate or suspend user access without prior notice for
violations of these terms or for any other reason deemed appropriate by the
administration.
</p>
</div>
<div className="space-y-1">
<p>
<strong>Governing Law</strong>
</p>
<p>
These terms are governed by the laws of the jurisdiction where the website
is primarily operated, without regard to conflict of law principles.
</p>
</div>
</div>
</div>
</div>
</DialogDescription>
</div>
</DialogHeader>
<DialogFooter className="border-t px-6 py-4 sm:items-center">
{!hasReadToBottom && (
<span className="grow text-muted-foreground text-xs max-sm:text-center">
Read all terms before accepting.
</span>
)}
<DialogCloseTrigger asChild>
<Button type="button" variant="outline">
Cancel
</Button>
</DialogCloseTrigger>
<DialogCloseTrigger asChild>
<Button type="button" disabled={!hasReadToBottom}>
I agree
</Button>
</DialogCloseTrigger>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
Not supported yet Not supported yet Not supported yet Installation
Copy and paste the following code into your project.
"use client"
import * as React from "react"
import { Dialog as DialogPrimitive, dialogAnatomy } from "@ark-ui/react/dialog"
import { type HTMLArkProps, ark } from "@ark-ui/react/factory"
import { Portal } from "@ark-ui/react/portal"
import { XIcon } from "lucide-react"
import { cn } from "@/lib/utils"
const parts = dialogAnatomy.extendWith("header").build()
const Dialog = DialogPrimitive.Root
const DialogBackdrop = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Backdrop>,
DialogPrimitive.BackdropProps
>(({ className, ...props }, ref) => (
<DialogPrimitive.Backdrop
ref={ref}
className={cn(
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-[--z-index] bg-black/80 data-[state=closed]:animate-out data-[state=open]:animate-in",
className,
)}
{...props}
/>
))
DialogBackdrop.displayName = "DialogBackdrop"
const DialogCloseTrigger = DialogPrimitive.CloseTrigger
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
DialogPrimitive.ContentProps
>(({ className, children, ...props }, ref) => (
<Portal>
<DialogBackdrop />
<DialogPrimitive.Positioner>
<DialogPrimitive.Content
ref={ref}
className={cn(
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 -translate-x-1/2 -translate-y-1/2 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-1/2 left-1/2 z-[--z-index] grid max-h-[calc(100%-2rem)] w-full max-w-[calc(100%-2rem)] gap-4 overflow-y-auto rounded-xl border bg-background p-6 shadow-lg data-[state=closed]:animate-out data-[state=open]:animate-in sm:max-w-100",
className,
)}
{...props}
>
{children}
<DialogPrimitive.CloseTrigger className="group absolute top-3 right-3 flex size-7 items-center justify-center rounded outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none">
<XIcon className="size-4 opacity-60 transition-opacity group-hover:opacity-100" />
<span className="sr-only">Close</span>
</DialogPrimitive.CloseTrigger>
</DialogPrimitive.Content>
</DialogPrimitive.Positioner>
</Portal>
))
DialogContent.displayName = "DialogContent"
const DialogContext = DialogPrimitive.Context
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
DialogPrimitive.DescriptionProps
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
))
DialogDescription.displayName = "DialogDescription"
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn("flex flex-col-reverse gap-3 sm:flex-row sm:justify-end", className)}
{...props}
/>
)
const DialogHeader = React.forwardRef<HTMLDivElement, HTMLArkProps<"div">>(
({ className, ...props }, ref) => (
<ark.div
ref={ref}
{...parts.header.attrs}
className={cn("flex flex-col gap-1 text-center sm:text-left", className)}
{...props}
/>
),
)
DialogHeader.displayName = "DialogHeader"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
DialogPrimitive.TitleProps
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn("font-semibold text-lg leading-none", className)}
{...props}
/>
))
DialogTitle.displayName = "DialogTitle"
const DialogTrigger = DialogPrimitive.Trigger
export {
Dialog,
DialogBackdrop,
DialogCloseTrigger,
DialogContent,
DialogContext,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
}
Not supported yet Not supported yet Not supported yet Update the import paths to match your project setup.
bunx --bun shadcn@latest add @shipbase/dialog npx shadcn@latest add @shipbase/dialog pnpm dlx shadcn@latest add @shipbase/dialog yarn dlx shadcn@latest add @shipbase/dialog Usage
import {
Dialog,
DialogCloseTrigger,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog" <Dialog>
<DialogTrigger>Open</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Are you absolutely sure?</DialogTitle>
<DialogDescription>
This action cannot be undone. This will permanently delete your account and remove your data
from our servers.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<DialogCloseTrigger>Close</DialogCloseTrigger>
</DialogFooter>
</DialogContent>
</Dialog>