GitHub

Popover

Click-triggered floating panel for contextual actions and details.

01 Live Demo

02 Code Snippet

src/ui/Popover.tsx
import { Popover } from "@/ui/Popover";

<Popover.Root>
  <Popover.Trigger>Open profile card</Popover.Trigger>
  <Popover.Content>
    <h4 className="text-sm font-semibold">Project Access</h4>
    <p className="mt-1 text-xs text-slate-500">Invite teammates and set role permissions.</p>
    <div className="mt-3">
      <Popover.Close>Done</Popover.Close>
    </div>
  </Popover.Content>
</Popover.Root>

03 Copy-Paste (Single File)

Popover.tsx
import { useState, type ReactNode } from "react";

function PopoverRoot({ children }: { children: ReactNode }) {
  return <div className="relative inline-block">{children}</div>;
}

function PopoverTrigger({ onClick, children }: { onClick?: () => void; children: ReactNode }) {
  return <button type="button" onClick={onClick} className="rounded-lg border border-slate-200 px-3 py-2 text-sm">{children}</button>;
}

function PopoverContent({ open, children }: { open: boolean; children: ReactNode }) {
  if (!open) return null;
  return <div className="absolute left-0 top-[calc(100%+8px)] z-50 w-72 rounded-xl border border-slate-200 bg-white p-4 shadow-xl">{children}</div>;
}

type PopoverCompound = typeof PopoverRoot & {
  Root: typeof PopoverRoot;
  Trigger: typeof PopoverTrigger;
  Content: typeof PopoverContent;
};

export const Popover = Object.assign(PopoverRoot, { Root: PopoverRoot, Trigger: PopoverTrigger, Content: PopoverContent }) as PopoverCompound;

// usage
function Example() {
  const [open, setOpen] = useState(false);
  return (
    <Popover.Root>
      <Popover.Trigger onClick={() => setOpen((v) => !v)}>Toggle</Popover.Trigger>
      <Popover.Content open={open}>Popover content</Popover.Content>
    </Popover.Root>
  );
}
react-principles