> ## Documentation Index
> Fetch the complete documentation index at: https://spreecommerce.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Admin Components

Spree Admin provides a set of reusable UI components that you can use in your custom admin views. These components are implemented as view helpers and integrate with Stimulus controllers for interactivity.

## Dropdown

The dropdown component creates accessible dropdown menus with automatic positioning using Floating UI.

### Basic Usage

```erb theme={"theme":"night-owl"}
<%= dropdown do %>
  <%= dropdown_toggle class: 'btn-light btn-sm' do %>
    <%= icon('dots-vertical', class: 'mr-0') %>
  <% end %>

  <%= dropdown_menu do %>
    <%= link_to_with_icon 'pencil', Spree.t(:edit), edit_path, class: 'dropdown-item' %>
    <%= link_to_with_icon 'trash', Spree.t(:delete), delete_path,
        class: 'dropdown-item text-danger',
        data: { turbo_method: :delete, turbo_confirm: Spree.t(:are_you_sure) } %>
  <% end %>
<% end %>
```

### Features

* **Automatic positioning** - Uses Floating UI to position the menu optimally
* **Auto-flip** - Menu flips to stay within viewport
* **Click outside to close** - Menu closes when clicking outside
* **Escape to close** - Menu closes when pressing Escape key
* **Keyboard navigation** - Full keyboard accessibility

### `dropdown`

Creates the dropdown container with Stimulus controller.

```erb theme={"theme":"night-owl"}
<%= dropdown do %>
  <!-- toggle and menu -->
<% end %>

<%= dropdown placement: 'top-end' do %>
  <!-- menu opens above, aligned to end -->
<% end %>
```

| Option      | Type    | Default        | Description                                                                       |
| ----------- | ------- | -------------- | --------------------------------------------------------------------------------- |
| `class`     | String  | -              | Additional CSS classes                                                            |
| `placement` | String  | `bottom-start` | Menu placement: `bottom-start`, `bottom-end`, `top-start`, `top-end`              |
| `direction` | String  | -              | Legacy option: `left` → `bottom-end`, `top` → `top-start`, `top-left` → `top-end` |
| `portal`    | Boolean | -              | Whether to portal the dropdown to body                                            |
| `data`      | Hash    | -              | Additional data attributes                                                        |

### `dropdown_toggle`

Creates the button that toggles the dropdown menu.

```erb theme={"theme":"night-owl"}
<%= dropdown_toggle class: 'btn-primary' do %>
  Actions <%= icon('chevron-down', class: 'ml-2') %>
<% end %>
```

| Option  | Type   | Default | Description                                        |
| ------- | ------ | ------- | -------------------------------------------------- |
| `class` | String | -       | Additional CSS classes (added to `btn` base class) |
| `data`  | Hash   | -       | Additional data attributes                         |

### `dropdown_menu`

Creates the menu container for dropdown items.

```erb theme={"theme":"night-owl"}
<%= dropdown_menu do %>
  <%= link_to 'Option 1', '#', class: 'dropdown-item' %>
  <div class="dropdown-divider"></div>
  <%= link_to 'Option 2', '#', class: 'dropdown-item text-danger' %>
<% end %>
```

| Option  | Type   | Default | Description                |
| ------- | ------ | ------- | -------------------------- |
| `class` | String | -       | Additional CSS classes     |
| `data`  | Hash   | -       | Additional data attributes |

### Complete Example

```erb theme={"theme":"night-owl"}
<%= content_for :page_actions do %>
  <%= dropdown do %>
    <%= dropdown_toggle class: 'btn-primary' do %>
      <%= icon('settings', class: 'mr-2') %>
      Actions
      <%= icon('chevron-down', class: 'ml-2') %>
    <% end %>

    <%= dropdown_menu do %>
      <%= link_to_with_icon 'download', 'Export CSV', export_path(format: :csv), class: 'dropdown-item' %>
      <%= link_to_with_icon 'file-export', 'Export Excel', export_path(format: :xlsx), class: 'dropdown-item' %>
      <div class="dropdown-divider"></div>
      <%= link_to_with_icon 'upload', 'Import', import_path, class: 'dropdown-item' %>
      <div class="dropdown-divider"></div>
      <%= link_to_with_icon 'trash', 'Delete All', bulk_delete_path,
          class: 'dropdown-item text-danger',
          data: { turbo_method: :delete, turbo_confirm: Spree.t(:are_you_sure) } %>
    <% end %>
  <% end %>
<% end %>
```

## Dialog

Dialogs (modals) are used for focused interactions that require user attention. They overlay the page content and must be dismissed before continuing.

### Basic Usage

```erb theme={"theme":"night-owl"}
<div class="dialog" data-controller="dialog">
  <%= dialog_header('Edit Product') %>

  <div class="dialog-body">
    <!-- Dialog content -->
  </div>

  <div class="dialog-footer">
    <%= dialog_discard_button %>
    <%= turbo_save_button_tag %>
  </div>
</div>
```

### `dialog_header`

Creates a dialog header with title and close button.

```erb theme={"theme":"night-owl"}
<%= dialog_header('Confirm Action') %>
<%= dialog_header('Custom Dialog', 'my-dialog') %>
```

| Parameter         | Type   | Default  | Description                                   |
| ----------------- | ------ | -------- | --------------------------------------------- |
| `title`           | String | required | The dialog title                              |
| `controller_name` | String | `dialog` | Stimulus controller name for the close action |

**Renders:**

```html theme={"theme":"night-owl"}
<div class="dialog-header">
  <h5 class="dialog-title">Confirm Action</h5>
  <button type="button" class="btn-close" data-action="dialog#close" data-dismiss="dialog"></button>
</div>
```

### `dialog_close_button`

Creates a standalone close button for dialogs.

```erb theme={"theme":"night-owl"}
<%= dialog_close_button %>
<%= dialog_close_button('custom-controller') %>
```

| Parameter         | Type   | Default  | Description              |
| ----------------- | ------ | -------- | ------------------------ |
| `controller_name` | String | `dialog` | Stimulus controller name |

### `dialog_discard_button`

Creates a "Discard" button that closes the dialog.

```erb theme={"theme":"night-owl"}
<%= dialog_discard_button %>
```

| Parameter         | Type   | Default  | Description              |
| ----------------- | ------ | -------- | ------------------------ |
| `controller_name` | String | `dialog` | Stimulus controller name |

**Renders:**

```html theme={"theme":"night-owl"}
<button type="button" class="btn btn-light" data-action="dialog#close" data-dismiss="dialog">
  Discard
</button>
```

### Complete Dialog Example

```erb theme={"theme":"night-owl"}
<%= turbo_frame_tag 'main-dialog' do %>
  <div class="dialog-backdrop" data-controller="dialog" data-action="click->dialog#backdropClose keydown.esc@window->dialog#close">
    <div class="dialog">
      <%= dialog_header('Add New Item') %>

      <%= form_with model: @item, url: items_path, data: { turbo_frame: '_top' } do |f| %>
        <div class="dialog-body">
          <%= f.spree_text_field :name, required: true %>
          <%= f.spree_text_area :description %>
        </div>

        <div class="dialog-footer">
          <%= dialog_discard_button %>
          <%= turbo_save_button_tag 'Create Item' %>
        </div>
      <% end %>
    </div>
  </div>
<% end %>
```

## Drawer

Drawers are slide-out panels typically used for filters, secondary forms, or detailed views without leaving the current page.

### Basic Usage

```erb theme={"theme":"night-owl"}
<div class="drawer" data-controller="drawer">
  <%= drawer_header('Filter Options') %>

  <div class="drawer-body">
    <!-- Drawer content -->
  </div>

  <div class="drawer-footer">
    <%= drawer_discard_button %>
    <button type="submit" class="btn btn-primary">Apply Filters</button>
  </div>
</div>
```

### `drawer_header`

Creates a drawer header with title and close button.

```erb theme={"theme":"night-owl"}
<%= drawer_header('Filters') %>
<%= drawer_header('Details', 'custom-drawer') %>
```

| Parameter         | Type   | Default  | Description                                   |
| ----------------- | ------ | -------- | --------------------------------------------- |
| `title`           | String | required | The drawer title                              |
| `controller_name` | String | `drawer` | Stimulus controller name for the close action |

**Renders:**

```html theme={"theme":"night-owl"}
<div class="drawer-header">
  <h5 class="drawer-title">Filters</h5>
  <button type="button" class="btn-close" data-action="drawer#close" data-dismiss="drawer"></button>
</div>
```

### `drawer_close_button`

Creates a standalone close button for drawers.

```erb theme={"theme":"night-owl"}
<%= drawer_close_button %>
<%= drawer_close_button('custom-drawer') %>
```

| Parameter         | Type   | Default  | Description              |
| ----------------- | ------ | -------- | ------------------------ |
| `controller_name` | String | `drawer` | Stimulus controller name |

### `drawer_discard_button`

Creates a "Discard" button that closes the drawer.

```erb theme={"theme":"night-owl"}
<%= drawer_discard_button %>
```

| Parameter         | Type   | Default  | Description              |
| ----------------- | ------ | -------- | ------------------------ |
| `controller_name` | String | `drawer` | Stimulus controller name |

## Icon

Icons are rendered using the [Tabler Icons](https://tabler.io/icons) library.

### Basic Usage

```erb theme={"theme":"night-owl"}
<%= icon('package') %>
<%= icon('shopping-cart', class: 'text-primary') %>
<%= icon('check', class: 'text-success', height: 24) %>
```

### Options

| Option   | Type    | Default | Description              |
| -------- | ------- | ------- | ------------------------ |
| `class`  | String  | -       | Additional CSS classes   |
| `height` | Integer | -       | Icon size in pixels      |
| `style`  | String  | -       | Additional inline styles |

### Output

```html theme={"theme":"night-owl"}
<i class="ti ti-package"></i>
<i class="ti ti-shopping-cart text-primary"></i>
<i class="ti ti-check text-success" style="font-size: 24px !important;"></i>
```

### Legacy Icon Names

For backwards compatibility, legacy icon names are automatically translated:

| Legacy Name | Tabler Icon     |
| ----------- | --------------- |
| `save`      | `device-floppy` |
| `edit`      | `pencil`        |
| `delete`    | `trash`         |
| `add`       | `plus`          |
| `cancel`    | `x`             |

### Common Icons

| Icon                              | Name            | Usage              |
| --------------------------------- | --------------- | ------------------ |
| <i class="ti ti-pencil" />        | `pencil`        | Edit actions       |
| <i class="ti ti-trash" />         | `trash`         | Delete actions     |
| <i class="ti ti-plus" />          | `plus`          | Add/create actions |
| <i class="ti ti-eye" />           | `eye`           | View/preview       |
| <i class="ti ti-download" />      | `download`      | Download/export    |
| <i class="ti ti-upload" />        | `upload`        | Upload/import      |
| <i class="ti ti-check" />         | `check`         | Success/confirm    |
| <i class="ti ti-x" />             | `x`             | Close/cancel       |
| <i class="ti ti-dots-vertical" /> | `dots-vertical` | More options       |
| <i class="ti ti-chevron-down" />  | `chevron-down`  | Expand             |
| <i class="ti ti-chevron-right" /> | `chevron-right` | Navigate           |

## Image

Displays optimized images with automatic WebP conversion and retina support.

### Basic Usage

```erb theme={"theme":"night-owl"}
<%= spree_image_tag(product.images.first, width: 100, height: 100) %>
<%= spree_image_tag(current_store.logo, width: 200, height: 60) %>
<%= spree_image_tag(taxon.image, width: 400, height: 300, class: 'rounded') %>
```

### Options

| Option    | Type             | Default  | Description                                |
| --------- | ---------------- | -------- | ------------------------------------------ |
| `image`   | Asset/Attachment | required | `Spree::Asset` or ActiveStorage attachment |
| `width`   | Integer          | -        | Display width in pixels                    |
| `height`  | Integer          | -        | Display height in pixels                   |
| `class`   | String           | -        | CSS classes                                |
| `alt`     | String           | -        | Alt text for accessibility                 |
| `loading` | Symbol           | -        | `:lazy` or `:eager`                        |

### Features

* **Retina support** - Automatically scales dimensions by 2x for sharp display
* **WebP conversion** - Outputs optimized WebP format
* **Smart cropping** - When both dimensions provided, crops to fill
* **Aspect ratio preservation** - When one dimension provided, maintains ratio

### Examples

```erb theme={"theme":"night-owl"}
<%# Product thumbnail in index table %>
<% if product.has_images? %>
  <%= spree_image_tag product.default_image, width: 48, height: 48, class: 'rounded' %>
<% else %>
  <div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 48px; height: 48px;">
    <%= icon('photo-off', class: 'text-muted') %>
  </div>
<% end %>

<%# Store logo in header %>
<%= spree_image_tag current_store.logo, width: 120, height: 40, alt: current_store.name %>

<%# Category image %>
<%= spree_image_tag taxon.image, width: 300, height: 200, class: 'card-img-top' %>
```

### With Placeholder

```erb theme={"theme":"night-owl"}
<% if @brand.logo.attached? %>
  <%= spree_image_tag @brand.logo, width: 100, height: 100 %>
<% else %>
  <div class="placeholder-image">
    <%= icon('photo') %>
  </div>
<% end %>
```

<Info>
  For comprehensive documentation on image handling, storage configuration, and best practices, see the [Media](/developer/core-concepts/media) guide.
</Info>

## Tooltip

Tooltips provide additional context on hover.

### Basic Usage

```erb theme={"theme":"night-owl"}
<span data-controller="tooltip">
  <%= icon('info-circle') %>
  <%= tooltip('This is helpful information') %>
</span>
```

### `tooltip`

Creates a tooltip container.

```erb theme={"theme":"night-owl"}
<%= tooltip('Simple text tooltip') %>

<%= tooltip do %>
  <strong>Rich content</strong> with <em>formatting</em>
<% end %>
```

| Parameter | Type   | Description                                  |
| --------- | ------ | -------------------------------------------- |
| `text`    | String | Tooltip text (or use block for rich content) |

### `help_bubble`

Creates an info icon with a tooltip - commonly used for form field hints.

```erb theme={"theme":"night-owl"}
<%= help_bubble('This field is used for SEO optimization') %>
<%= help_bubble('Shown below the field', 'bottom') %>
<%= help_bubble('Custom styled', 'top', css: 'text-primary') %>
```

| Parameter   | Type   | Default                 | Description                                        |
| ----------- | ------ | ----------------------- | -------------------------------------------------- |
| `text`      | String | required                | Tooltip text                                       |
| `placement` | String | `top`                   | Tooltip position: `top`, `bottom`, `left`, `right` |
| `css`       | String | `text-xs text-muted...` | CSS classes for the icon                           |

**Output:**

```html theme={"theme":"night-owl"}
<span data-controller="tooltip" data-tooltip-placement-value="top">
  <i class="ti ti-info-square-rounded text-xs text-muted cursor-default opacity-75"></i>
  <span role="tooltip" data-tooltip-target="tooltip" class="tooltip-container">
    This field is used for SEO optimization
  </span>
</span>
```

## Active Badge

Displays a status badge indicating active/inactive state.

### Basic Usage

```erb theme={"theme":"night-owl"}
<%= active_badge(product.active?) %>
<%= active_badge(user.confirmed?) %>
<%= active_badge(order.paid?, label: 'Paid') %>
```

### Options

| Option      | Type    | Default  | Description               |
| ----------- | ------- | -------- | ------------------------- |
| `condition` | Boolean | required | The condition to evaluate |
| `label`     | String  | Yes/No   | Custom label text         |

### Output

When condition is `true`:

```html theme={"theme":"night-owl"}
<span class="badge badge-active">
  <i class="ti ti-check"></i> Yes
</span>
```

When condition is `false`:

```html theme={"theme":"night-owl"}
<span class="badge badge-inactive">No</span>
```

### Custom Labels

```erb theme={"theme":"night-owl"}
<%= active_badge(subscription.active?, label: subscription.active? ? 'Active' : 'Expired') %>
<%= active_badge(feature.enabled?, label: feature.enabled? ? 'Enabled' : 'Disabled') %>
```

## Avatar

Renders a user avatar with automatic fallback to initials.

### Basic Usage

```erb theme={"theme":"night-owl"}
<%= render_avatar(current_user) %>
<%= render_avatar(user, width: 48, height: 48) %>
<%= render_avatar(admin, class: 'avatar-lg') %>
```

### Options

| Option   | Type    | Default  | Description                                       |
| -------- | ------- | -------- | ------------------------------------------------- |
| `user`   | Object  | required | User object (must respond to `avatar` and `name`) |
| `width`  | Integer | 128      | Avatar width in pixels                            |
| `height` | Integer | 128      | Avatar height in pixels                           |
| `class`  | String  | `avatar` | CSS classes                                       |

### Behavior

1. If user has an attached avatar image → displays the image
2. Otherwise → displays user's initials on a colored background

```erb theme={"theme":"night-owl"}
<!-- With avatar image -->
<img src="avatar.jpg" class="avatar" style="width: 128px; height: 128px;">

<!-- Without avatar (fallback) -->
<div class="avatar" style="width: 128px; height: 128px;">JD</div>
```

## Clipboard

Copy-to-clipboard functionality with visual feedback.

### Basic Usage

```erb theme={"theme":"night-owl"}
<%= clipboard_component(product.sku) %>
<%= clipboard_component(api_key) %>
```

### `clipboard_component`

Creates a complete clipboard component with hidden input and copy button.

```erb theme={"theme":"night-owl"}
<%= clipboard_component('ABC-123-XYZ') %>
```

| Parameter | Type   | Description      |
| --------- | ------ | ---------------- |
| `text`    | String | The text to copy |

**Output:**

```html theme={"theme":"night-owl"}
<span data-controller="clipboard" data-clipboard-success-content-value="<i class='ti ti-check mr-0 font-size-sm'></i>">
  <input type="hidden" name="clipboard_source" value="ABC-123-XYZ" data-clipboard-target="source">
  <button type="button" class="btn btn-clipboard" data-action="clipboard#copy" data-clipboard-target="button">
    <i class="ti ti-copy mr-0 font-size-sm"></i>
    <span role="tooltip" data-tooltip-target="tooltip" class="tooltip-container">Copy to clipboard</span>
  </button>
</span>
```

### `clipboard_button`

Creates just the copy button (for custom layouts).

```erb theme={"theme":"night-owl"}
<div data-controller="clipboard">
  <input type="text" data-clipboard-target="source" value="custom-value">
  <%= clipboard_button %>
</div>
```

### Inline with Text

```erb theme={"theme":"night-owl"}
<div class="d-flex align-items-center gap-2">
  <code><%= product.sku %></code>
  <%= clipboard_component(product.sku) %>
</div>
```

## Progress Bar

Displays a progress bar with customizable range.

### Basic Usage

```erb theme={"theme":"night-owl"}
<%= progress_bar_component(75) %>
<%= progress_bar_component(150, max: 200) %>
<%= progress_bar_component(50, min: 0, max: 100) %>
```

### Options

| Option  | Type    | Default  | Description            |
| ------- | ------- | -------- | ---------------------- |
| `value` | Integer | required | Current progress value |
| `min`   | Integer | 0        | Minimum value          |
| `max`   | Integer | 100      | Maximum value          |

### Output

```html theme={"theme":"night-owl"}
<div class="progress">
  <div class="progress-bar"
       role="progressbar"
       style="width: 75%"
       aria-valuenow="75"
       aria-valuemin="0"
       aria-valuemax="100">
  </div>
</div>
```

### Examples

```erb theme={"theme":"night-owl"}
<!-- Inventory level -->
<%= progress_bar_component(stock_item.count_on_hand, max: stock_item.backorderable_threshold || 100) %>

<!-- Order fulfillment -->
<%= progress_bar_component(order.shipments.shipped.count, max: order.shipments.count) %>

<!-- Upload progress -->
<%= progress_bar_component(uploaded_count, max: total_count) %>
```

## Date & Time

Helpers for displaying dates and times in the user's local timezone.

### `spree_date`

Renders a date in the user's local format.

```erb theme={"theme":"night-owl"}
<%= spree_date(order.created_at) %>
<%= spree_date(product.available_on) %>
```

### `spree_time`

Renders a date and time in the user's local format.

```erb theme={"theme":"night-owl"}
<%= spree_time(order.completed_at) %>
<%= spree_time(shipment.shipped_at) %>
```

### `spree_time_ago`

Renders a relative time (e.g., "2 hours ago") with a tooltip showing the full timestamp.

```erb theme={"theme":"night-owl"}
<%= spree_time_ago(order.completed_at) %>
<%= spree_time_ago(comment.created_at) %>
```

**Output:**

```html theme={"theme":"night-owl"}
<span data-controller="tooltip">
  <time datetime="2024-01-15T10:30:00Z" data-local="time-ago">2 hours ago</time>
  <span role="tooltip" data-tooltip-target="tooltip" class="tooltip-container">
    January 15, 2024 10:30 AM
  </span>
</span>
```

### `local_time`

The underlying helper from [local\_time gem](https://github.com/basecamp/local_time) - displays time in user's browser timezone.

```erb theme={"theme":"night-owl"}
<%= local_time(order.completed_at) %>
<%= local_time(event.starts_at, format: '%B %e, %Y at %l:%M %p') %>
```

### Comparison

| Helper           | Output                      | Use Case                   |
| ---------------- | --------------------------- | -------------------------- |
| `spree_date`     | "Jan 15, 2024"              | Date-only display          |
| `spree_time`     | "Jan 15, 2024 10:30 AM"     | Full timestamp             |
| `spree_time_ago` | "2 hours ago"               | Relative time with tooltip |
| `local_time`     | "January 15, 2024 10:30 AM" | Customizable format        |

## Best Practices

<Check>
  **Use semantic components** - Choose the right component for the interaction (Dialog for focused tasks, Drawer for contextual panels)
</Check>

<Check>
  **Provide feedback** - Use tooltips and badges to give users context about their actions
</Check>

<Check>
  **Keep dropdowns focused** - Limit dropdown menus to related actions, use dividers to group items
</Check>

<Check>
  **Use appropriate icons** - Choose icons that clearly represent the action
</Check>

<Check>
  **Handle loading states** - Use `turbo_save_button_tag` for forms to show loading feedback
</Check>

<Check>
  **Consider accessibility** - Components include ARIA attributes and keyboard navigation
</Check>
