Fluxo UIFluxo UIv0.4.1

Popover

A floating panel anchored to a trigger element that displays a selectable list of items. Supports keyboard navigation, grouped items, custom rendering, built-in filtering, and mobile-responsive layout.

Basic Usage

Simple popover with selectable items

Click the button to open a popover list and select an item.

const [isOpen, setIsOpen] = useState(false);
const [selected, setSelected] = useState<ListItem | null>(null);
const triggerRef = useRef<HTMLButtonElement>(null);

<Button ref={triggerRef} onClick={() => setIsOpen(!isOpen)}>
  {selected ? selected.label : 'Pick a fruit'}
</Button>

<Popover
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  triggerElement={triggerRef.current}
  items={fruitItems}
  onSelect={(item) => {
    setSelected(item);
    setIsOpen(false);
  }}
  selectedIndex={fruitItems.findIndex(
    (i) => i.value === selected?.value
  )}
/>

Grouped Items

Grouped list items

Items organized into labeled groups with section headers.

const groups = [
  {
    label: 'Fruits',
    items: [
      { label: 'Apple', value: 'apple' },
      { label: 'Banana', value: 'banana' },
    ],
  },
  {
    label: 'Vegetables',
    items: [
      { label: 'Carrot', value: 'carrot' },
      { label: 'Broccoli', value: 'broccoli' },
    ],
  },
];

<Popover
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  triggerElement={triggerRef.current}
  items={allItems}
  groups={groups}
  onSelect={(item) => {
    setSelected(item);
    setIsOpen(false);
  }}
/>

Controlled Open/Close

Controlled open and close

Programmatically control the popover with separate open and close buttons.

const [isOpen, setIsOpen] = useState(false);
const triggerRef = useRef<HTMLButtonElement>(null);

<div className="flex gap-3">
  <Button ref={triggerRef} onClick={() => setIsOpen(true)}>
    Open Popover
  </Button>
  <Button variant="danger" layout="outlined"
    onClick={() => setIsOpen(false)}>
    Close Popover
  </Button>
</div>

<Popover
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  triggerElement={triggerRef.current}
  items={colorItems}
  onSelect={(item) => {
    setSelected(item);
    setIsOpen(false);
  }}
/>

Custom Render Item

Custom renderItem

Use the renderItem prop to fully customize how each list item is rendered.

const renderItem = (item, index, isSelected, isHighlighted) => (
  <div
    className={cn('eui-popover-item', {
      'eui-popover-item-highlighted': isHighlighted,
      'eui-popover-item-selected': isSelected,
    })}
    onClick={() => handleSelect(item, index)}
    onMouseEnter={() => setHighlighted(index)}
  >
    <span
      style={{
        width: 8, height: 8,
        borderRadius: '50%',
        backgroundColor: statusColors[item.value],
      }}
    />
    <span>{item.label}</span>
  </div>
);

<Popover
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  triggerElement={triggerRef.current}
  items={statusItems}
  onSelect={handleSelect}
  renderItem={renderItem}
  width="180px"
/>

Filterable Popover

Filterable popover with children

Use the filter prop alongside children to render a search input above the list.

const [filter, setFilter] = useState('');

<Popover
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  triggerElement={triggerRef.current}
  items={fruitItems}
  filter={filter}
  onSelect={(item) => {
    setSelected(item);
    setIsOpen(false);
  }}
  emptyMessage="No fruits match your search"
>
  <div style={{ padding: '8px' }}>
    <TextInput
      value={filter}
      onChange={setFilter}
      placeholder="Search fruits..."
    />
  </div>
</Popover>

Import

import { Popover } from 'fluxo-ui';

API Reference

isOpenreq
boolean

Controls whether the popover is visible.

onClosereq
(e?: MouseEvent) => void

Callback invoked when the popover should close (click outside, Escape key).

triggerElementreq
HTMLElement | null

The DOM element the popover is anchored to for positioning.

itemsreq
ListItem[]

Array of list items to display inside the popover.

groups
ListItemGroup[]

Optional grouping of items with labeled section headers.

onSelectreq
(item: ListItem, index: number) => void

Callback invoked when an item is selected.

selectedIndex
number"-1"

Index of the currently selected item (shows a check mark).

renderItem
(item, index, isSelected, isHighlighted) => ReactNode

Custom render function for each list item.

maxHeight
string"'300px'"

Maximum height of the popover container before scrolling.

width
string

Width of the popover. Defaults to the trigger element's width.

filter
string"''"

Filter string to narrow down displayed items by label.

loading
boolean"false"

Shows a loading spinner instead of items.

emptyMessage
string"'No items found'"

Message displayed when no items match the filter.

children
ReactNode

Content rendered above the item list (e.g., a search input).

Features

Portal Rendering

Renders in a portal so the popover is never clipped by overflow:hidden containers or stacking contexts.

Keyboard Navigation

Full keyboard support with ArrowUp, ArrowDown to navigate, Enter to select, and Escape to close.

Smart Positioning

Automatically calculates the best position relative to the trigger element using usePosition hook.

Grouped Items

Organize items into labeled groups with section headers for better categorization.

Custom Rendering

Supply a renderItem function to fully control the appearance of each item in the list.

Built-in Filtering

Pass a filter string to narrow items by label, and use the children slot to render a search input.

Mobile Responsive

Adapts to mobile viewports with a full-width bottom sheet layout using the useMobile hook.

Click Outside to Close

Clicking anywhere outside the popover automatically closes it via the useClickOutside hook.