# dropdown

Accessible dropdown components with expand/collapse behaviour, group coordination, and position-aware expand panels.

<iframe src="examples/dropdown/" class="example-frame" title="Dropdown example"></iframe>

```typescript
import Dropdown, {
    DropdownGroup,
    DropdownExpand,
} from '@yaoc1996/js-tools/react-utils'
```

---

## `Dropdown({ children?, autoExpand? })`

A single dropdown container. Manages its own expanded state and registers an outside-click handler to collapse automatically. When placed inside a `<DropdownGroup>`, it participates in group-level expand coordination instead.

### Props

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `children` | `ReactNode` | — | Label and `<DropdownExpand>` content. |
| `autoExpand` | `boolean` | `false` | Expand on `mouseenter`, collapse on `mouseleave`. |

### Example

```tsx
<Dropdown>
    <button>Open</button>
    <DropdownExpand>
        <ul>
            <li>Item A</li>
            <li>Item B</li>
        </ul>
    </DropdownExpand>
</Dropdown>
```

---

## `DropdownGroup({ children })`

Wraps multiple `<Dropdown>` components so that only one is visible at a time. Hovering a sibling shifts the expanded panel; clicking outside the group collapses all.

### Props

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `children` | `ReactNode` | — | One or more `<Dropdown>` elements. |

### Example

```tsx
<DropdownGroup>
    <Dropdown>
        <button>File</button>
        <DropdownExpand>…</DropdownExpand>
    </Dropdown>
    <Dropdown>
        <button>Edit</button>
        <DropdownExpand>…</DropdownExpand>
    </Dropdown>
</DropdownGroup>
```

### Notes

- Inside a group, clicking a label toggles the group's `expanded` flag (not individual Dropdown state).
- Hovering a label sets that Dropdown as `focusedId`; the expand panel tracks focus, so only the hovered Dropdown's panel is visible when the group is expanded.

---

## `DropdownExpand({ children?, expandPos?, minClientMargin?, persistOnClick?, className? })`

The panel that appears when its parent `<Dropdown>` is expanded. Positioned absolutely relative to the nearest `position: relative` ancestor. Hidden via `display: none` and `aria-hidden` when not expanded.

### Props

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `children` | `ReactNode` | — | Panel content. |
| `expandPos` | `ExpandPosition` | `"bottom-left"` | Which edge the panel opens from. |
| `minClientMargin` | `number \| string` | `"16px"` | Minimum gap between the panel edge and the viewport edge. Used to cap `maxWidth` / `maxHeight`. |
| `persistOnClick` | `boolean` | `false` | When `true`, clicks inside the panel are stopped before reaching the Dropdown toggle handler, keeping the panel open. |
| `className` | `string` | `""` | Extra CSS class on the panel div. |

### Example

```tsx
<Dropdown>
    <button>Menu</button>
    <DropdownExpand expandPos="bottom-right" persistOnClick>
        <form onSubmit={…}>…</form>
    </DropdownExpand>
</Dropdown>
```

### Notes

- `expandPos="bottom-left"` anchors `left: 0; top: 100%` (aligns with the left edge of the label).
- `expandPos="bottom-right"` anchors `right: 0; top: 100%` (aligns with the right edge).
- `expandPos="right"` / `"left"` opens a side panel.
- `maxWidth` and `maxHeight` are computed from the label's bounding rect and `clientSize` so the panel stays within the viewport.

---

## Types

```typescript
type ExpandPosition = "bottom-left" | "bottom-right" | "right" | "left";
```
