# Popover

Import: `import { Popover } from '@neo4j-ndl/react'`

## Props

### Popover

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `anchorElement` | `Element \| null` |  |  | Can be used if the anchor should be separated from the trigger. Note: to visually separate the anchor from the trigger, use the Popover.Anchor component instead. |
| `anchorPosition` | `{ x: number; y: number; }` |  |  | If the anchorPosition is provided the anchorElement will be ignored |
| `children` | `ReactNode` |  |  | The content of the popover |
| `closeOnClickOutside` | `boolean` |  | `true` | If the popover should close when the user clicks outside of it |
| `hasAnchorPortal` | `boolean` |  |  | Use anchorEl for portal's container. Should be used if the popover is portaled and is inside a Dialog for example. |
| `initialFocus` | `number \| RefObject<HTMLElement \| null>` |  |  | Which element to initially focus. * |
| `isOpen` | `boolean` |  |  | if the popover is open |
| `isPortaled` | `boolean` |  |  | Whether the content is rendered in a portal |
| `offset` | `OffsetOptions` |  |  | can take either a number, an object or a function. Full documentation here:  https://floating-ui.com/docs/offset |
| `onOpenChange` | `((isOpen: boolean, event?: Event) => void)` |  |  | Called when an internal state change is triggered, when the floating element either opens or closes. It is not called when `isOpen` is updated by the consumer. |
| `placement` | `'bottom-end-bottom-start' \| 'bottom-end-top-end' \| 'bottom-middle-top-middle' \| 'bottom-start-bottom-end' \| 'bottom-start-top-start' \| 'middle-end-middle-start' \| 'middle-start-middle-end' \| 'top-end-bottom-end' \| 'top-end-top-start' \| 'top-middle-bottom-middle' \| 'top-start-bottom-start' \| 'top-start-top-end'` |  |  | The placement of the floating element is determined by two sets of words. The first set of words specifies the point on the anchor element where the floating element will be attached. The second set of coordinates specifies the point on the floating element that will attach to the anchor element. |
| `role` | `'alertdialog' \| 'dialog' \| 'grid' \| 'listbox' \| 'menu' \| 'tooltip' \| 'tree'` |  |  | The role of the popover. @deprecated - This prop will be removed in v5. |
| `shouldCaptureFocus` | `boolean` |  | `false` | If the popover should constrain the focus inside the popover while open |
| `shouldConstrainSize` | `boolean` |  |  | When true, limits the popover's max-height and max-width to the available viewport space. The consumer is responsible for adding overflow handling (e.g. overflow-y: auto) to the content. |
| `strategy` | `'absolute' \| 'fixed'` |  |  | The strategy of the popover. Fixed should be used when the popover is inside a Dialog. |

### Popover.Anchor

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` |  |  |  |
| `ref` | `Ref<HTMLDivElement>` |  |  | A ref to apply to the root element. |

### Popover.Content

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `as` | `ElementType<any, keyof IntrinsicElements>` |  |  | An override of the default HTML tag of the root of the component. Can also be another React component. |
| `children` | `ReactNode` | ✅ |  |  |
| `ref` | `any` |  |  | A ref to apply to the root element. |

### Popover.Trigger

| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| `children` | `ReactNode` | ✅ |  | The content of the trigger |
| `hasButtonWrapper` | `boolean` |  | `false` | Whether the trigger should render a button element, or pass its props to the child element |
| `ref` | `Ref<HTMLButtonElement>` |  |  | A ref to apply to the root element. |

## Accessibility

## Implementation guidelines

When used with a toggle button, the button should have aria-expanded (true when open, false when closed), aria-haspopup set to the role of the popover, and aria-controls set to the id of the popover.

## Examples

### Default

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { FilledButton, Popover } from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Popover>
      <Popover.Trigger hasButtonWrapper>
        <FilledButton>Open Popover</FilledButton>
      </Popover.Trigger>
      <Popover.Content className="n-p-token-16">
        Popover content
      </Popover.Content>
    </Popover>
  );
};

export default Component;
```

### Controlled

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { FilledButton, Popover } from '@neo4j-ndl/react';
import { useState } from 'react';

const Component = () => {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <Popover
      isOpen={isOpen}
      onOpenChange={(open) => {
        setIsOpen(open);
      }}
    >
      <Popover.Trigger hasButtonWrapper>
        <FilledButton onClick={() => setIsOpen((prev) => !prev)}>
          Open Popover
        </FilledButton>
      </Popover.Trigger>
      <Popover.Content className="n-p-token-16">
        Popover content
      </Popover.Content>
    </Popover>
  );
};

export default Component;
```

### Custom Offset

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { FilledButton, Popover, Select } from '@neo4j-ndl/react';
import { useState } from 'react';

const offsetOptions = [
  {
    label: 'crossAxis-anchor-element-width',
    value: 'crossAxis-anchor-element-width',
  },
  {
    label: 'crossAxis-anchor-element-negative-width',
    value: 'crossAxis-anchor-element-negative-width',
  },
  {
    label: 'crossAxis-floating-element-negative-width',
    value: 'crossAxis-floating-element-negative-width',
  },
  {
    label: 'crossAxis-floating-element-width',
    value: 'crossAxis-floating-element-width',
  },
];

const offsetTranslation: Record<
  string,
  React.ComponentProps<typeof Popover>['offset']
> = {
  'crossAxis-anchor-element-negative-width': ({ rects }) => ({
    crossAxis: -rects.reference.width,
  }),
  'crossAxis-anchor-element-width': ({ rects }) => ({
    crossAxis: rects.reference.width,
  }),
  'crossAxis-floating-element-negative-width': ({ rects }) => ({
    crossAxis: -rects.floating.width,
  }),
  'crossAxis-floating-element-width': ({ rects }) => ({
    crossAxis: rects.floating.width,
  }),
};

const Component = () => {
  const [selectedPosition, setSelectedPosition] = useState(
    offsetOptions[0].value,
  );

  return (
    <div className="n-flex n-flex-col n-gap-token-32 n-items-center">
      <Popover offset={offsetTranslation[selectedPosition]}>
        <Popover.Trigger hasButtonWrapper>
          <FilledButton>Open Popover</FilledButton>
        </Popover.Trigger>
        <Popover.Content className="n-p-token-16">
          Popover content
        </Popover.Content>
      </Popover>

      <Select
        type="select"
        label="Example positions"
        selectProps={{
          defaultValue: offsetOptions[0],
          onChange: (e) => {
            if (e) {
              setSelectedPosition(e.value);
            }
          },
          options: offsetOptions,
        }}
      />
    </div>
  );
};

export default Component;
```

### In Dialog

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { Dialog, FilledButton, Popover } from '@neo4j-ndl/react';
import { useState } from 'react';

const Component = () => {
  const [isDialogOpen, setDialogOpen] = useState(false);
  const closeDialog = () => setDialogOpen(false);
  return (
    <div className="n-flex n-justify-center">
      <FilledButton onClick={() => setDialogOpen(true)}>
        Open Dialog
      </FilledButton>
      <Dialog isOpen={isDialogOpen} onClose={closeDialog}>
        <Dialog.Header>Dialog</Dialog.Header>
        <Dialog.Content>
          <Popover>
            <Popover.Trigger hasButtonWrapper>
              <FilledButton className="n-mx-auto">Open Popover</FilledButton>
            </Popover.Trigger>
            <Popover.Content className="n-p-token-16">
              Custom Popover content
            </Popover.Content>
          </Popover>
        </Dialog.Content>
      </Dialog>
    </div>
  );
};

export default Component;
```

### No Button Wrapper

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { Popover } from '@neo4j-ndl/react';

const Component = () => {
  return (
    <Popover>
      <Popover.Trigger>
        <div>Open Popover</div>
      </Popover.Trigger>
      <Popover.Content className="n-p-token-16">
        Popover content
      </Popover.Content>
    </Popover>
  );
};

export default Component;
```

### Separate Anchor

```tsx
import '@neo4j-ndl/base/lib/neo4j-ds-styles.css';

import { FilledButton, Popover } from '@neo4j-ndl/react';
import { useState } from 'react';

const Component = () => {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div className="n-flex n-justify-between n-w-full">
      <Popover isOpen={isOpen} onOpenChange={(open) => setIsOpen(open)}>
        <Popover.Anchor>
          <div>Anchor Element</div>
        </Popover.Anchor>
        <Popover.Trigger hasButtonWrapper>
          <FilledButton onClick={() => setIsOpen(true)}>
            Click to open popover
          </FilledButton>
        </Popover.Trigger>
        <Popover.Content className="n-p-token-16">
          Popover content
        </Popover.Content>
      </Popover>
    </div>
  );
};

export default Component;
```
