Popover
Displays rich content in a portal triggered by a button.
Preview
import { useState } from "react";
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverClose,
PopoverArrow,
PopoverAnchor,
} from "@/components/ui/Popover";
import { Button } from "@/components/ui/Button";
import { Input } from "@/components/ui/Input";
import { Label } from "@/components/ui/Label";
export function PopoverDemo() {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="menu">Open Popover</Button>
</PopoverTrigger>
<PopoverContent className="w-30 p-1">
<div className="space-y-1">
<h4 className="font-medium leading-none">Dimensions</h4>
<p className="text-sm text-gray-11">
Set the dimensions for the layer.
</p>
</div>
</PopoverContent>
</Popover>
)
}Installation
Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces
pnpm dlx shadcn@latest add @aura/popoverManual
Install the following dependencies:
pnpm install @radix-ui/react-icons radix-uiCopy and paste the class names utility into your utils/class-names.ts file.
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Copy and paste the Popover component into your components/ui/Popover.tsx file.
"use client";
/**
* @description Displays rich content in a portal triggered by a button.
*/
import * as React from "react";
import { Popover as PopoverRadix } from "radix-ui";
import { Cross2Icon } from "@radix-ui/react-icons";
import { cn } from "@/utils/class-names";
function Popover({ ...props }: React.ComponentProps<typeof PopoverRadix.Root>) {
return <PopoverRadix.Root data-slot="popover" {...props} />;
}
function PopoverTrigger({
className,
...props
}: React.ComponentProps<typeof PopoverRadix.Trigger>) {
return (
<PopoverRadix.Trigger
data-slot="popover-trigger"
className={cn(className)}
{...props}
/>
);
}
function PopoverAnchor({
...props
}: React.ComponentProps<typeof PopoverRadix.Anchor>) {
return <PopoverRadix.Anchor data-slot="popover-anchor" {...props} />;
}
function PopoverPortal({
...props
}: React.ComponentProps<typeof PopoverRadix.Portal>) {
return <PopoverRadix.Portal data-slot="popover-portal" {...props} />;
}
function PopoverContent({
className,
...props
}: React.ComponentProps<typeof PopoverRadix.Content>) {
return (
<PopoverRadix.Portal>
<PopoverRadix.Content
data-slot="popover-content"
collisionPadding={8}
className={cn(
className,
"bg-gray-1 border border-gray-a6 rounded-sm relative shadow-md"
)}
{...props}
/>
</PopoverRadix.Portal>
);
}
function PopoverArrow({
className,
...props
}: React.ComponentProps<typeof PopoverRadix.Arrow>) {
return (
<PopoverRadix.Arrow
data-slot="popover-arrow"
className={cn(className, "fill-gray-1")}
{...props}
/>
);
}
function PopoverClose({
className,
...props
}: React.ComponentProps<typeof PopoverRadix.Close>) {
return (
<PopoverRadix.Close
data-slot="popover-close"
className={cn(
className,
"absolute right-0.5 top-0.5 inline-flex items-center justify-center rounded-sm p-0.5 hover:bg-accent-3 cursor-pointer"
)}
{...props}
>
<Cross2Icon className="size-1" />
<span className="sr-only">Close</span>
</PopoverRadix.Close>
);
}
export {
Popover,
PopoverTrigger,
PopoverAnchor,
PopoverPortal,
PopoverContent,
PopoverArrow,
PopoverClose,
};Usage
WithCloseButton
export const WithCloseButton = () => (
<Popover>
<PopoverTrigger asChild>
<Button variant="menu">Open Popover</Button>
</PopoverTrigger>
<PopoverContent className="w-30 p-1">
<PopoverClose />
<div className="space-y-1">
<h4 className="font-medium leading-none">Notifications</h4>
<p className="text-sm text-gray-11">
You have 3 unread messages. Click the close button to dismiss.
</p>
</div>
</PopoverContent>
</Popover>
)WithArrow
export const WithArrow = () => (
<Popover>
<PopoverTrigger asChild>
<Button variant="menu">Open Popover</Button>
</PopoverTrigger>
<PopoverContent className="w-30 p-1">
<PopoverArrow />
<div className="space-y-1">
<h4 className="font-medium leading-none">Info</h4>
<p className="text-sm text-gray-11">
This popover includes an arrow pointing to the trigger.
</p>
</div>
</PopoverContent>
</Popover>
)Controlled
export const Controlled = () => {
const [open, setOpen] = useState(false);
return (
<div className="space-y-1">
<div className="flex items-center gap-0.5">
<span className="text-sm text-gray-11">Status:</span>
<span className="text-sm font-medium">{open ? "Open" : "Closed"}</span>
</div>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button variant="menu">Toggle Popover</Button>
</PopoverTrigger>
<PopoverContent className="w-30 p-1">
<div className="space-y-1">
<h4 className="font-medium leading-none">Controlled Popover</h4>
<p className="text-sm text-gray-11">
This popover's open state is controlled by React state.
</p>
<Button
onClick={() => setOpen(false)}
variant="link"
className="w-full mt-0.5"
>
Close
</Button>
</div>
</PopoverContent>
</Popover>
</div>
);
};WithAnchor
export const WithAnchor = () => (
<div className="space-y-1">
<p className="text-sm text-gray-11">
The popover can be anchored to a different element than the trigger.
</p>
<Popover>
<PopoverAnchor asChild>
<div className="inline-block p-1 bg-accent-3 rounded-sm">
Anchor Element
</div>
</PopoverAnchor>
<PopoverTrigger asChild>
<Button variant="menu" className="ml-1">
Open (Anchored)
</Button>
</PopoverTrigger>
<PopoverContent className="w-30 p-1">
<div className="space-y-1">
<h4 className="font-medium leading-none">Anchored Popover</h4>
<p className="text-sm text-gray-11">
This popover is positioned relative to the anchor element, not the
trigger.
</p>
</div>
</PopoverContent>
</Popover>
</div>
)Positioning
export const Positioning = () => (
<div className="grid grid-cols-2 gap-1">
{(["top", "right", "bottom", "left"] as const).map((side) => (
<Popover key={side}>
<PopoverTrigger asChild>
<Button variant="menu" className="capitalize">
{side}
</Button>
</PopoverTrigger>
<PopoverContent side={side} className="w-15 p-1">
<p className="text-sm">
Popover positioned on the <strong>{side}</strong> side.
</p>
</PopoverContent>
</Popover>
))}
</div>
)