# Dropdown Component

## Introduction

The Dropdown component provides an interactive dropdown menu that toggles open or closed when clicking a trigger button. It handles positioning, automatic closing behavior, and keyboard navigation out of the box.

**Key features:**

- Toggle open/close on button click
- Automatic close when clicking outside
- Keyboard support (ESC to close)
- Configurable vertical position (above or below the button)
- Configurable horizontal alignment (left, right, or center)
- Session-based state management
- HTMX-powered updates without page reload

**Common use cases:**

- Navigation menus
- User account menus
- Action menus (edit, delete, share)
- Filter or sort options
- Context-sensitive toolbars
- Settings quick access

## Quick Start

Here's a minimal example showing a dropdown menu with navigation links:

```python
from fasthtml.common import *
from myfasthtml.controls.Dropdown import Dropdown
from myfasthtml.core.instances import RootInstance

# Create root instance and dropdown
root = RootInstance(session)

dropdown = Dropdown(
    parent=root,
    button=Button("Menu", cls="btn"),
    content=Ul(
        Li(A("Home", href="/")),
        Li(A("Settings", href="/settings")),
        Li(A("Logout", href="/logout"))
    )
)

# Render the dropdown
return dropdown
```

This creates a complete dropdown with:

- A "Menu" button that toggles the dropdown
- A list of navigation links displayed below the button
- Automatic closing when clicking outside the dropdown
- ESC key support to close the dropdown

**Note:** The dropdown opens below the button and aligns to the left by default. Users can click anywhere outside the dropdown to close it, or press ESC on the keyboard.

## Basic Usage

### Visual Structure

The Dropdown component consists of a trigger button and a content panel:

```
Closed state:
┌──────────────┐
│   Button ▼   │
└──────────────┘

Open state (position="below", align="left"):
┌──────────────┐
│   Button ▼   │
├──────────────┴─────────┐
│  Dropdown Content      │
│  - Option 1            │
│  - Option 2            │
│  - Option 3            │
└────────────────────────┘

Open state (position="above", align="right"):
       ┌────────────────────────┐
       │  Dropdown Content      │
       │  - Option 1            │
       │  - Option 2            │
       ├──────────────┬─────────┘
       │   Button ▲   │
       └──────────────┘
```

**Component details:**

| Element   | Description                                    |
|-----------|------------------------------------------------|
| Button    | Trigger element that toggles the dropdown      |
| Content   | Panel containing the dropdown menu items       |
| Wrapper   | Container with relative positioning for anchor |

### Creating a Dropdown

The Dropdown is a `MultipleInstance`, meaning you can create multiple independent dropdowns in your application. Create it by providing a parent instance:

```python
dropdown = Dropdown(parent=root_instance, button=my_button, content=my_content)

# Or with a custom ID
dropdown = Dropdown(parent=root_instance, button=my_button, content=my_content, _id="my-dropdown")
```

### Button and Content

The dropdown requires two main elements:

**Button:** The trigger element that users click to toggle the dropdown.

```python
# Simple text button
dropdown = Dropdown(
    parent=root,
    button=Button("Click me", cls="btn btn-primary"),
    content=my_content
)

# Button with icon
dropdown = Dropdown(
    parent=root,
    button=Div(
        icon_svg,
        Span("Options"),
        cls="flex items-center gap-2"
    ),
    content=my_content
)

# Just an icon
dropdown = Dropdown(
    parent=root,
    button=icon_svg,
    content=my_content
)
```

**Content:** Any FastHTML element to display in the dropdown panel.

```python
# Simple list
content = Ul(
    Li("Option 1"),
    Li("Option 2"),
    Li("Option 3"),
    cls="menu"
)

# Complex content with sections
content = Div(
    Div("User Actions", cls="font-bold p-2"),
    Hr(),
    Button("Edit Profile", cls="btn btn-ghost w-full"),
    Button("Settings", cls="btn btn-ghost w-full"),
    Hr(),
    Button("Logout", cls="btn btn-error w-full")
)
```

### Positioning Options

The Dropdown supports two positioning parameters:

**`position`** - Vertical position relative to the button:
- `"below"` (default): Dropdown appears below the button
- `"above"`: Dropdown appears above the button

**`align`** - Horizontal alignment relative to the button:
- `"left"` (default): Dropdown aligns to the left edge of the button
- `"right"`: Dropdown aligns to the right edge of the button
- `"center"`: Dropdown is centered relative to the button

```python
# Default: below + left
dropdown = Dropdown(parent=root, button=btn, content=menu)

# Above the button, aligned right
dropdown = Dropdown(parent=root, button=btn, content=menu, position="above", align="right")

# Below the button, centered
dropdown = Dropdown(parent=root, button=btn, content=menu, position="below", align="center")
```

**Visual examples of all combinations:**

```
position="below", align="left"     position="below", align="center"     position="below", align="right"
┌────────┐                              ┌────────┐                                      ┌────────┐
│ Button │                              │ Button │                                      │ Button │
├────────┴────┐                    ┌────┴────────┴────┐                    ┌────────────┴────────┤
│ Content     │                    │     Content      │                    │           Content   │
└─────────────┘                    └──────────────────┘                    └─────────────────────┘

position="above", align="left"     position="above", align="center"     position="above", align="right"
┌─────────────┐                    ┌──────────────────┐                    ┌─────────────────────┐
│ Content     │                    │     Content      │                    │           Content   │
├────────┬────┘                    └────┬────────┬────┘                    └────────────┬────────┤
│ Button │                              │ Button │                                      │ Button │
└────────┘                              └────────┘                                      └────────┘
```

## Advanced Features

### Automatic Close Behavior

The Dropdown automatically closes in two scenarios:

**Click outside:** When the user clicks anywhere outside the dropdown, it closes automatically. This is handled by the Mouse component listening for global click events.

**ESC key:** When the user presses the ESC key, the dropdown closes. This is handled by the Keyboard component.

```python
# Both behaviors are enabled by default - no configuration needed
dropdown = Dropdown(parent=root, button=btn, content=menu)
```

**How it works internally:**

- The `Mouse` component detects clicks and sends `is_inside` and `is_button` parameters
- If `is_button` is true, the dropdown toggles
- If `is_inside` is false (clicked outside), the dropdown closes
- The `Keyboard` component listens for ESC and triggers the close command

### Programmatic Control

You can control the dropdown programmatically using its methods and commands:

```python
# Toggle the dropdown state
dropdown.toggle()

# Close the dropdown
dropdown.close()

# Access commands for use with other controls
close_cmd = dropdown.commands.close()
click_cmd = dropdown.commands.click()
```

**Using commands with buttons:**

```python
from myfasthtml.controls.helpers import mk

# Create a button that closes the dropdown
close_button = mk.button("Close", command=dropdown.commands.close())

# Add it to the dropdown content
dropdown = Dropdown(
    parent=root,
    button=Button("Menu"),
    content=Div(
        Ul(Li("Option 1"), Li("Option 2")),
        close_button
    )
)
```

### CSS Customization

The Dropdown uses CSS classes that you can customize:

| Class                 | Element                               |
|-----------------------|---------------------------------------|
| `mf-dropdown-wrapper` | Container with relative positioning   |
| `mf-dropdown-btn`     | Button wrapper                        |
| `mf-dropdown`         | Dropdown content panel                |
| `mf-dropdown-below`   | Applied when position="below"         |
| `mf-dropdown-above`   | Applied when position="above"         |
| `mf-dropdown-left`    | Applied when align="left"             |
| `mf-dropdown-right`   | Applied when align="right"            |
| `mf-dropdown-center`  | Applied when align="center"           |
| `is-visible`          | Applied when dropdown is open         |

**Example customization:**

```css
/* Change dropdown background and border */
.mf-dropdown {
    background-color: #1f2937;
    border: 1px solid #374151;
    border-radius: 0.5rem;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
}

/* Add animation */
.mf-dropdown {
    opacity: 0;
    transform: translateY(-10px);
    transition: opacity 0.2s ease, transform 0.2s ease;
}

.mf-dropdown.is-visible {
    opacity: 1;
    transform: translateY(0);
}

/* Style for above position */
.mf-dropdown-above {
    transform: translateY(10px);
}

.mf-dropdown-above.is-visible {
    transform: translateY(0);
}
```

## Examples

### Example 1: Navigation Menu

A simple navigation dropdown menu:

```python
from fasthtml.common import *
from myfasthtml.controls.Dropdown import Dropdown

dropdown = Dropdown(
    parent=root,
    button=Button("Navigation", cls="btn btn-ghost"),
    content=Ul(
        Li(A("Dashboard", href="/dashboard", cls="block p-2 hover:bg-base-200")),
        Li(A("Projects", href="/projects", cls="block p-2 hover:bg-base-200")),
        Li(A("Tasks", href="/tasks", cls="block p-2 hover:bg-base-200")),
        Li(A("Reports", href="/reports", cls="block p-2 hover:bg-base-200")),
        cls="menu p-2"
    )
)

return dropdown
```

### Example 2: User Account Menu

A user menu aligned to the right, typically placed in a header:

```python
from fasthtml.common import *
from myfasthtml.controls.Dropdown import Dropdown

# User avatar button
user_button = Div(
    Img(src="/avatar.png", cls="w-8 h-8 rounded-full"),
    Span("John Doe", cls="ml-2"),
    cls="flex items-center gap-2 cursor-pointer"
)

# Account menu content
account_menu = Div(
    Div(
        Div("John Doe", cls="font-bold"),
        Div("john@example.com", cls="text-sm opacity-60"),
        cls="p-3 border-b"
    ),
    Ul(
        Li(A("Profile", href="/profile", cls="block p-2 hover:bg-base-200")),
        Li(A("Settings", href="/settings", cls="block p-2 hover:bg-base-200")),
        Li(A("Billing", href="/billing", cls="block p-2 hover:bg-base-200")),
        cls="menu p-2"
    ),
    Div(
        A("Sign out", href="/logout", cls="block p-2 text-error hover:bg-base-200"),
        cls="border-t"
    ),
    cls="w-56"
)

# Align right so it doesn't overflow the viewport
dropdown = Dropdown(
    parent=root,
    button=user_button,
    content=account_menu,
    align="right"
)

return dropdown
```

### Example 3: Action Menu Above Button

A dropdown that opens above the trigger, useful when the button is at the bottom of the screen:

```python
from fasthtml.common import *
from myfasthtml.controls.Dropdown import Dropdown

# Action button with icon
action_button = Button(
    Span("+", cls="text-xl"),
    cls="btn btn-circle btn-primary"
)

# Quick actions menu
actions_menu = Div(
    Button("New Document", cls="btn btn-ghost btn-sm w-full justify-start"),
    Button("Upload File", cls="btn btn-ghost btn-sm w-full justify-start"),
    Button("Create Folder", cls="btn btn-ghost btn-sm w-full justify-start"),
    Button("Import Data", cls="btn btn-ghost btn-sm w-full justify-start"),
    cls="flex flex-col p-2 w-40"
)

# Open above and center-aligned
dropdown = Dropdown(
    parent=root,
    button=action_button,
    content=actions_menu,
    position="above",
    align="center"
)

return dropdown
```

### Example 4: Dropdown with Commands

A dropdown containing action buttons that execute commands:

```python
from fasthtml.common import *
from myfasthtml.controls.Dropdown import Dropdown
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command

# Define actions
def edit_item():
    return "Editing..."

def delete_item():
    return "Deleted!"

def share_item():
    return "Shared!"

# Create commands
edit_cmd = Command("edit", "Edit item", edit_item)
delete_cmd = Command("delete", "Delete item", delete_item)
share_cmd = Command("share", "Share item", share_item)

# Build menu with command buttons
actions_menu = Div(
    mk.button("Edit", command=edit_cmd, cls="btn btn-ghost btn-sm w-full justify-start"),
    mk.button("Share", command=share_cmd, cls="btn btn-ghost btn-sm w-full justify-start"),
    Hr(cls="my-1"),
    mk.button("Delete", command=delete_cmd, cls="btn btn-ghost btn-sm w-full justify-start text-error"),
    cls="flex flex-col p-2"
)

dropdown = Dropdown(
    parent=root,
    button=Button("Actions", cls="btn btn-sm"),
    content=actions_menu
)

return dropdown
```

---

## Developer Reference

This section contains technical details for developers working on the Dropdown component itself.

### State

The Dropdown component maintains its state via `DropdownState`:

| Name     | Type    | Description                  | Default |
|----------|---------|------------------------------|---------|
| `opened` | boolean | Whether dropdown is open     | `False` |

### Commands

Available commands for programmatic control:

| Name      | Description                                     |
|-----------|-------------------------------------------------|
| `close()` | Closes the dropdown                             |
| `click()` | Handles click events (toggle or close behavior) |

**Command details:**

- `close()`: Sets `opened` to `False` and returns updated content
- `click()`: Receives `combination`, `is_inside`, and `is_button` parameters
    - If `is_button` is `True`: toggles the dropdown
    - If `is_inside` is `False`: closes the dropdown

### Public Methods

| Method     | Description                | Returns              |
|------------|----------------------------|----------------------|
| `toggle()` | Toggles open/closed state  | Content tuple        |
| `close()`  | Closes the dropdown        | Content tuple        |
| `render()` | Renders complete component | `Div`                |

### Constructor Parameters

| Parameter  | Type        | Description                        | Default   |
|------------|-------------|------------------------------------|-----------|
| `parent`   | Instance    | Parent instance (required)         | -         |
| `content`  | Any         | Content to display in dropdown     | `None`    |
| `button`   | Any         | Trigger element                    | `None`    |
| `_id`      | str         | Custom ID for the instance         | `None`    |
| `position` | str         | Vertical position: "below"/"above" | `"below"` |
| `align`    | str         | Horizontal align: "left"/"right"/"center" | `"left"` |

### High Level Hierarchical Structure

```
Div(id="{id}")
├── Div(cls="mf-dropdown-wrapper")
│   ├── Div(cls="mf-dropdown-btn")
│   │   └── [Button content]
│   └── Div(id="{id}-content", cls="mf-dropdown mf-dropdown-{position} mf-dropdown-{align} [is-visible]")
│       └── [Dropdown content]
├── Keyboard(id="{id}-keyboard")
│   └── ESC → close command
└── Mouse(id="{id}-mouse")
    └── click → click command
```

### Element IDs

| Name             | Description                    |
|------------------|--------------------------------|
| `{id}`           | Root dropdown container        |
| `{id}-content`   | Dropdown content panel         |
| `{id}-keyboard`  | Keyboard handler component     |
| `{id}-mouse`     | Mouse handler component        |

**Note:** `{id}` is the Dropdown instance ID (auto-generated or custom `_id`).

### Internal Methods

| Method          | Description                              |
|-----------------|------------------------------------------|
| `_mk_content()` | Renders the dropdown content panel       |
| `on_click()`    | Handles click events from Mouse component |

**Method details:**

- `_mk_content()`:
    - Builds CSS classes based on `position` and `align`
    - Adds `is-visible` class when `opened` is `True`
    - Returns a tuple containing the content `Div`

- `on_click(combination, is_inside, is_button)`:
    - Called by Mouse component on click events
    - `is_button`: `True` if click was on the button
    - `is_inside`: `True` if click was inside the dropdown
    - Returns updated content for HTMX swap
