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>