Renders a three-part entity-context picker UI for the chat composer: a `+` trigger button, a two-level popover for browsing and searching entity types/items, and a chip strip for displaying selected items. ## Key Components ### `ChatComposerPlusMenu` The `+` button rendered inside the composer input. Toggles the context picker open/closed, with visual states (grey → white → accent-yellow) reflecting hover, press, and open states. **Props:** | Prop | Type | Description | |---|---|---| | `onToggle` | `() => void` | Opens/closes the picker | | `open` | `boolean` | Drives the accent-yellow active state | | `disabled` | `boolean` | Disables the trigger during streaming | | `dropdown` | `React.ReactNode` | The `` anchored to this button | ### `ChatContextPicker` A two-level popover (`bottom-full` positioned above the input). Level 1 lists entity types; Level 2 shows a debounced-search, multi-select list of items with checkmarks. Supports `@`-mention query seeding. **Props:** | Prop | Type | Description | |---|---|---| | `open` | `boolean` | Controls render | | `config` | `ChatContextPickerConfig` | Host-provided entity types and `search()` resolver | | `selectedItems` | `ChatContextItem[]` | Drives ✓ state | | `onToggleItem` | `(item) => void` | Add/remove selection | | `onClose` | `() => void` | Escape, outside-click, or X | | `mentionQuery` | `string \| null` | Pre-filters type list from `@` trigger | ### Constants | Constant | Value | Purpose | |---|---|---| | `CHAT_CONTEXT_ITEMS_DEFAULT_MAX` | `10` | Max selectable items | | `SEARCH_DEBOUNCE_MS` | `300` | Keystroke debounce before calling `config.search()` | | `ITEMS_SKELETON_ROWS` | `10` | Suspense fallback row count | ## Usage Example ```typescript import { ChatComposerPlusMenu, ChatContextPicker, } from './chat-context-picker' function ComposerToolbar() { const [open, setOpen] = useState(false) const [selected, setSelected] = useState([]) const config: ChatContextPickerConfig = { entityTypes: [ { id: 'device', label: 'Device' }, { id: 'script', label: 'Script' }, ], search: async (type, query, signal) => fetchItems(type, query, signal), } const toggleItem = (item: ChatContextItem) => setSelected((prev) => prev.some((i) => i.id === item.id) ? prev.filter((i) => i.id !== item.id) : [...prev, item], ) return ( setOpen((v) => !v)} dropdown={ setOpen(false)} /> } /> ) } ``` ## Behavioral Notes - **Height measurement** (`useLayoutEffect`): caps the popover to the real visible space above the button by walking up to the nearest `overflow` clip ancestor — handles drawer/panel layouts without hard-coded pixel values. - **Outside-click handling**: ignores clicks on `[data-context-trigger]` so the `+` button can toggle cleanly without immediately re-closing. - **Library boundary**: the component owns only UI and selection state. All data fetching is delegated to `config.search()` — the library has no knowledge of specific entity types. **Source:** [`chat-context-picker.tsx`](https://github.com/flamingo-stack/openframe-oss-lib/blob/main/chat-context-picker.tsx)