Share Dialog

Modal dialog for sharing a page across social platforms with a copy-link action and overridable labels.

Report a bug

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/share-dialog.json

Storybook

Explore all variants, controls, and accessibility checks in the interactive Storybook playground.

View in Storybook

Code

"use client"; import * as React from "react"; import { cn } from "../../lib/utils"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "../dialog/dialog"; export type SharePlatform = { buildUrl: (pageUrl: string, pageTitle: string) => string; key: string; label: string; }; export type ShareDialogLabels = { copied: string; copyLink: string; }; export type ShareDialogProps = { /** Function to build the URL for copying (should add UTM params) */ buildCopyUrl?: (url: string) => string; description?: string; labels?: ShareDialogLabels; onCopy?: () => void; onOpenChange?: (open: boolean) => void; onShare?: (platformKey: string) => void; open?: boolean; platforms: SharePlatform[]; title?: string; }; function PlatformButton({ onClick, platform, }: { onClick: (key: string) => void; platform: SharePlatform; }) { const handleSelectPlatform = () => { onClick(platform.key); }; return ( <button className={cn( "w-full rounded-md border border-border bg-background px-4 py-3", "text-sm font-medium text-foreground", "transition-colors duration-150", "hover:bg-accent hover:text-accent-foreground", "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", )} onClick={handleSelectPlatform} type="button" > {platform.label} </button> ); } function CopyButton({ buildCopyUrl, labels, onCopy, }: { buildCopyUrl?: (url: string) => string; labels: ShareDialogLabels; onCopy?: () => void; }) { const [copied, setCopied] = React.useState(false); const handleCopy = React.useCallback(async () => { const url = buildCopyUrl ? buildCopyUrl(window.location.href) : window.location.href; const text = `${document.title} - ${url}`; await navigator.clipboard.writeText(text); setCopied(true); onCopy?.(); setTimeout(() => { setCopied(false); }, 2000); }, [buildCopyUrl, onCopy]); return ( <button className={cn( "col-span-2 w-full rounded-md border px-4 py-3", "text-sm font-medium", "transition-colors duration-150", "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", copied ? "border-green-500 bg-green-500/10 text-green-600 dark:text-green-400" : "border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground", )} onClick={handleCopy} type="button" > {copied ? labels.copied : labels.copyLink} </button> ); } const defaultLabels: ShareDialogLabels = { copied: "Copied!", copyLink: "Copy link", }; export function ShareDialog({ buildCopyUrl, description, labels = defaultLabels, onCopy, onOpenChange, onShare, open, platforms, title = "Share", }: ShareDialogProps) { const handlePlatformClick = React.useCallback( (key: string) => { const platform = platforms.find((p) => p.key === key); if (platform) { const url = platform.buildUrl(window.location.href, document.title); window.open(url, "_blank", "noopener,noreferrer"); onShare?.(key); onOpenChange?.(false); } }, [platforms, onShare, onOpenChange], ); return ( <Dialog onOpenChange={onOpenChange} open={open}> <DialogContent className="sm:max-w-sm"> <DialogHeader> <DialogTitle>{title}</DialogTitle> {description ? ( <DialogDescription>{description}</DialogDescription> ) : null} </DialogHeader> <div className="grid grid-cols-2 gap-2"> {platforms.map((platform) => ( <PlatformButton key={platform.key} onClick={handlePlatformClick} platform={platform} /> ))} <CopyButton buildCopyUrl={buildCopyUrl} labels={labels} onCopy={onCopy} /> </div> </DialogContent> </Dialog> ); }

Dependencies

  • @vllnt/ui@^0.2.1