# Popover
A raised white panel with 8px corners and the floating shadow that grows out of the control that opened it.

Install: npx inkbox-ds add popover
Requires: theme, surface, utils
Packages: radix-ui
Exports: Popover, PopoverAnchor, PopoverContent, PopoverDescription, PopoverHeader, PopoverTitle, PopoverTrigger
Client boundary: yes
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


## Usage
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)
- Avoid: A dark or lilac popover panel
- Avoid: Scaling from the center instead of the trigger
- Avoid: Long forms that scroll inside a popover


## Examples
Example components: npx inkbox-ds add popover button input label
Example packages: @phosphor-icons/react
[Preview](https://inkbox-ds.vercel.app/preview/popover/Preview) | [Open](https://inkbox-ds.vercel.app/preview/popover/Open)

examples/popover.tsx

```tsx
"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

components/ui/popover.tsx

```tsx
"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 };

```
