# `PhoenixKitWeb.Components.Core.DraggableList`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v1.7.164/lib/phoenix_kit_web/components/core/draggable_list.ex#L1)

A reusable drag-and-drop sortable component supporting both grid and list layouts.

Uses SortableJS (auto-loaded from CDN) to enable drag-and-drop reordering of items.
The component sends a LiveView event when items are reordered.

## Design Philosophy

This component provides minimal opinionated styling - it handles drag-drop behavior
while you control the appearance through classes and slot content.

## Usage - Grid Layout (default)

Perfect for image galleries, card grids, etc:

    <.draggable_list
      id="post-images"
      items={@images}
      on_reorder="reorder_images"
      cols={4}
    >
      <:item :let={img}>
        <img src={img.url} class="w-full aspect-square object-cover rounded" />
      </:item>
      <:add_button>
        <button phx-click="add_image" class="btn">Add</button>
      </:add_button>
    </.draggable_list>

## Usage - List Layout

Perfect for column selectors, ordered lists, etc:

    <.draggable_list
      id="table-columns"
      items={@columns}
      on_reorder="reorder_columns"
      layout={:list}
      item_class="flex items-center p-3 bg-base-100 border rounded-lg hover:bg-base-200"
    >
      <:item :let={col}>
        <div class="mr-3 text-base-content/40">
          <.icon name="hero-bars-3" class="w-5 h-5" />
        </div>
        <span class="flex-1 font-medium">{col.label}</span>
        <button phx-click="remove_column" phx-value-id={col.id} class="btn btn-ghost btn-xs">
          <.icon name="hero-x-mark" class="w-4 h-4" />
        </button>
      </:item>
    </.draggable_list>

## Event Handler

The `on_reorder` event receives `%{"ordered_ids" => [id1, id2, ...]}` with the new order:

    def handle_event("reorder_items", %{"ordered_ids" => ordered_ids}, socket) do
      # ordered_ids is a list of item IDs in the new order
      {:noreply, socket}
    end

## Inside a LiveComponent

The `on_reorder` event is pushed by the `SortableGrid` JS hook. By default the
hook uses `pushEvent`, which delivers the event to the host **LiveView**. When
`<.draggable_list>` is rendered inside a **LiveComponent** that handles the
event itself, pass `target` — a CSS selector for the component's root element
— so the hook routes via `pushEventTo` and the event reaches the component:

    <.draggable_list id="my-gallery-grid" items={@items}
      on_reorder="reorder_images" target={"##{@id}"}>

Without `target`, a LiveComponent's `handle_event("reorder_…")` never fires and
the event lands on the host LiveView instead.

# `draggable_list`

## Attributes

* `id` (`:string`) (required) - Unique ID for the container.
* `items` (`:list`) (required) - List of items to display.
* `item_id` (`:any`) - Function to extract ID from item, defaults to &(&1.id). Defaults to `nil`.
* `on_reorder` (`:string`) (required) - Event name to send on reorder.
* `layout` (`:atom`) - Layout mode. Defaults to `:grid`. Must be one of `:grid`, or `:list`.
* `cols` (`:any`) - Grid columns (only for layout={:grid}). Either an integer 1..6 (mapped to a static `grid-cols-N`), or a string of Tailwind grid-column classes used verbatim — e.g. `"grid-cols-4 lg:grid-cols-6 2xl:grid-cols-8"` — for a responsive grid. The string form must be a literal in a Tailwind-scanned source so the classes are compiled. Any other value (out-of-range integer, atom, etc.) falls back to `grid-cols-4`. Defaults to `4`.
* `gap` (`:string`) - Gap between items (Tailwind class). Defaults to `"gap-2"`.
* `class` (`:string`) - Additional CSS classes for the container. Defaults to `""`.
* `item_class` (`:string`) - Additional CSS classes for each item wrapper. Defaults to `""`.
* `hide_source` (`:boolean`) - Hide source element on drag start. Defaults to `false`.
* `sortable_handle` (`:string`) - Optional CSS selector (e.g. `".pk-drag-handle"`) that restricts drag initiation to elements matching the selector inside each item. When set, the item wrapper no longer carries the `cursor-grab` styling — the caller is responsible for rendering the handle. Mirrors the workspace's `<.table_default>` `:on_reorder` + `.pk-drag-handle` convention. Defaults to `nil`.
* `target` (`:string`) - Optional CSS selector for the LiveComponent root that should receive the `on_reorder` event (e.g. `"#my-gallery"`). When set, the `SortableGrid` hook uses `pushEventTo` instead of `pushEvent`. Required when `<.draggable_list>` is rendered inside a LiveComponent that handles the reorder event itself. Defaults to `nil`.
* `draggable` (`:boolean`) - When false, the container renders without the SortableGrid hook and items skip the grab-cursor styling — useful when the list is too short to reorder (length <= 1). `data-id` is still emitted on each item so click-to-select handlers and test selectors work in both modes. Defaults to `true`.
## Slots

* `item` (required) - Slot to render each item, receives the item as let.
* `add_button` - Optional slot for add button at end of container.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
