Component
Popover
A raised white panel with 8px corners and the floating shadow that grows out of the control that opened it.
npx -y github:anwarlemu/inkbox-brand-cli#release add popoverUsage
Use for a small form or detail tied to one control (rename a mailbox, set a phone line's state). Put a PopoverHeader with a PopoverTitle and PopoverDescription first, then the fields. For choosing one value from a list use Select or Combobox; for actions use Dropdown Menu.
Avoid
- A popover that opens on hover (use Hover Card)
- A dark or lilac popover panel
- Scaling from the center instead of the trigger
- Long forms that scroll inside a popover
Exports: Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger. This component is a client component.
Dependencies: surface, radix-ui.
Example
"use client";import * as React from "react";import { PencilSimpleIcon } from "@phosphor-icons/react/ssr";import { Button } from "@/components/ui/button";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";import { Popover, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger } from "@/components/ui/popover";function RenameForm({ onDone }: { onDone?: () => void }) { const [name, setName] = React.useState("Acme support"); return ( <form className="grid gap-3" onSubmit={(event) => { event.preventDefault(); onDone?.(); }} > <PopoverHeader> <PopoverTitle>Display name</PopoverTitle> <PopoverDescription>Shown as the sender on acme@inkboxmail.com.</PopoverDescription> </PopoverHeader> <div className="grid gap-1"> <Label htmlFor="display-name">Name</Label> <Input id="display-name" value={name} onChange={(e) => setName(e.target.value)} /> </div> <Button type="submit" className="w-full"> Save </Button> </form> );}export function Preview() { const [open, setOpen] = React.useState(false); return ( <div className="flex justify-center"> <Popover open={open} onOpenChange={setOpen}> <PopoverTrigger asChild> <Button variant="outline"> <PencilSimpleIcon weight="fill" /> Rename mailbox </Button> </PopoverTrigger> <PopoverContent align="start"> <RenameForm onDone={() => setOpen(false)} /> </PopoverContent> </Popover> </div> );}export function Open() { return ( <div className="flex min-h-80 items-start justify-center"> <Popover defaultOpen> <PopoverTrigger asChild> <Button variant="outline"> <PencilSimpleIcon weight="fill" /> Rename mailbox </Button> </PopoverTrigger> <PopoverContent> <RenameForm /> </PopoverContent> </Popover> </div> );}Source
"use client";import * as React from "react";import { Popover as PopoverPrimitive } from "radix-ui";import { usePortalContainer } from "@/lib/surface";import { cn } from "@/lib/utils";// A popover, extrapolated from the console's raised white panels (Figma 427:2715: the select and the selected tab// carry Shadow/S): white with 8px corners and the floating shadow (Shadow/S's ring with a softer, longer drop), 16px of// padding, 14px ink text. It grows out of its trigger from 96 percent with a fade (200ms, the strong ease-out) and// leaves faster than it came.function Popover(props: React.ComponentProps<typeof PopoverPrimitive.Root>) { return <PopoverPrimitive.Root data-slot="popover" {...props} />;}function PopoverTrigger(props: React.ComponentProps<typeof PopoverPrimitive.Trigger>) { return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />;}function PopoverAnchor(props: React.ComponentProps<typeof PopoverPrimitive.Anchor>) { return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />;}function PopoverContent({ className, align = "center", sideOffset = 6, ...props }: React.ComponentProps<typeof PopoverPrimitive.Content>) { const container = usePortalContainer(); return ( <PopoverPrimitive.Portal container={container}> <PopoverPrimitive.Content data-slot="popover-content" align={align} sideOffset={sideOffset} className={cn( "z-50 flex w-72 max-w-[calc(100vw-2rem)] origin-(--radix-popover-content-transform-origin) flex-col gap-3 rounded-control bg-surface p-4 text-body leading-body text-fg shadow-floating outline-none data-[state=closed]:animate-pop-out data-[state=open]:animate-pop-in", className, )} {...props} /> </PopoverPrimitive.Portal> );}/** The popover's head: its title over a short muted line. */function PopoverHeader({ className, ...props }: React.ComponentProps<"div">) { return <div data-slot="popover-header" className={cn("flex flex-col gap-0.5", className)} {...props} />;}function PopoverTitle({ className, ...props }: React.ComponentProps<"h2">) { return <h2 data-slot="popover-title" className={cn("font-sans text-body-lg leading-body font-medium text-fg", className)} {...props} />;}function PopoverDescription({ className, ...props }: React.ComponentProps<"p">) { return <p data-slot="popover-description" className={cn("text-body leading-body text-fg-muted", className)} {...props} />;}export { Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger };Reference: Extrapolated from the Inkbox console (Figma 427:2715): the raised white panel of the select and the selected tab (Shadow/S), the field and button sizes.