Working on ColumnsManager. Added CycleStateControl and DataGridColumnsManager.
This commit is contained in:
557
docs/Dropdown.md
Normal file
557
docs/Dropdown.md
Normal file
@@ -0,0 +1,557 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user