VizStats UI

Share Buttons

Share Buttons for links

Installation


Add the following shadcn UI components:

Add the necessary shadcn/ui components using your preferred package manager:

npx shadcn@latest add button dialog tooltip input label

Add Environment Variables

Add the following environment variables to your .env.local file:

NEXT_PUBLIC_ROOT_URL=http://localhost:3000

Add useShareButton hook

Create a new file called useShareButton.ts in your hooks directory: hooks/useShareButton.ts

import { useState, useCallback } from "react";
import { usePathname, useSearchParams } from "next/navigation";
 
export function useShareButton() {
  const pathname = usePathname();
  const searchParams = useSearchParams();
  const [isCopied, setIsCopied] = useState(false);
 
  const rootUrl = process.env.NEXT_PUBLIC_ROOT_URL;
 
  const link =
    searchParams.toString() === ""
      ? `${rootUrl}${pathname}`
      : `${rootUrl}${pathname}?${searchParams.toString()}`;
 
  const copyToClipboard = useCallback(async () => {
    try {
      await navigator.clipboard.writeText(link);
      setIsCopied(true);
      // Reset the copied state after a short delay
      setTimeout(() => setIsCopied(false), 2000);
    } catch (error) {
      console.error("Failed to copy: ", error);
    }
  }, [link]);
 
  return {
    link,
    isCopied,
    copyToClipboard,
  };
}

Add ShareButton component

Create a new component called share-button.tsx

"use client";
import { CopyIcon } from "@radix-ui/react-icons";
import { Button, ButtonProps } from "@/components/ui/button";
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from "@/components/ui/dialog";
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Share2Icon } from "lucide-react";
import { cn } from "@/lib/utils";
import { useShareButton } from "@/hooks/useShareButton";
 
type ShareButtonProps = {
  tooltip?: string;
  className?: string;
  size?: ButtonProps["size"];
  variant?: ButtonProps["variant"];
};
 
export function ShareButton({ ...props }: ShareButtonProps) {
  const { tooltip = "Share Page", className } = props;
  const { link, isCopied, copyToClipboard } = useShareButton();
 
  return (
    <div className="z-[60]">
      <Dialog>
        <TooltipProvider>
          <Tooltip>
            <TooltipTrigger asChild>
              <DialogTrigger asChild>
                <Button
                  variant={props.variant || "default"}
                  size={props.size || "icon"}
                  className={cn(
                    className,
                    "transition-all duration-300 active:scale-90 focus:outline-none"
                  )}
                >
                  <span className="sr-only">Share</span>
                  <Share2Icon className="h-5 w-5" />
                </Button>
              </DialogTrigger>
            </TooltipTrigger>
            <TooltipContent side="top">{tooltip}</TooltipContent>
          </Tooltip>
        </TooltipProvider>
 
        <DialogContent className="sm:max-w-md">
          <DialogHeader>
            <DialogTitle>Share link</DialogTitle>
            <DialogDescription>
              Anyone who has this link will be able to view this page.
            </DialogDescription>
          </DialogHeader>
          <div className="flex items-center space-x-2">
            <div className="grid flex-1 gap-2">
              <Label htmlFor="link" className="sr-only">
                Link
              </Label>
              <Input id="link" defaultValue={link} readOnly />
            </div>
            <Button
              type="submit"
              size="sm"
              className="px-3"
              onClick={copyToClipboard}
            >
              <span className="sr-only">Copy</span>
              <CopyIcon className="h-4 w-4" />
            </Button>
          </div>
 
          <DialogFooter className="sm:justify-start">
            <DialogClose asChild>
              <div className="flex items-center space-x-2">
                <Button type="button" variant="secondary">
                  Close
                </Button>
 
                <p
                  className={`text-sm text-green-600 transition-opacity duration-500 ${
                    isCopied ? "opacity-100" : "opacity-0"
                  }`}
                >
                  Copied!
                </p>
              </div>
            </DialogClose>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}

Usage


Create a new file called page.tsx in your pages directory: pages/page.tsx

import { ShareButton } from "@/components/share-button";
 
export default function Page() {
  return (
    <div className="w-full min-h-72 flex h-full items-center justify-center">
      <ShareButton variant="outline" />
    </div>
  );
}

ShareButton Props

NameTypeDefaultDescription
tooltipstring"Share Page"Tooltip text for the share button
classNamestring""Additional classes for the share button
sizeButtonProps["size"]"icon"Size of the share button
variantButtonProps["variant"]"default"Variant of the share button

On this page