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

Bulk-select toolkit for admin tables, **client-side**. The selection
lives in the browser; the server only learns about it at action time
(when the user clicks an action button) via the `BulkSelectScope`
JS hook. This makes per-checkbox toggles feel instant — no LV
round-trip on every click.

Three function components compose with `<.table_default>`, plus a
wrapper element with the hook attached.

  * `<.bulk_select_scope>` — opens an inline-styled wrapper with
    `phx-hook="BulkSelectScope"`. Everything inside (header cell,
    row cells, toolbar buttons) participates in the same selection
    set. Pass `total_count` so the hook knows when "all" is checked.

  * `<.bulk_select_header_cell>` — the header checkbox. Tri-state
    (unchecked / indeterminate / checked) is managed by the hook.

  * `<.bulk_select_cell>` — per-row checkbox bound to a UUID value.

  * `<.bulk_actions_toolbar>` — the toolbar above the table. Buttons
    with `data-bulk-action` dispatch LV events with the selected
    UUIDs in the `{uuids: [...]}` payload.

## Example

    <.bulk_select_scope id="projects-bulk" total_count={length(@projects)}>
      <.bulk_actions_toolbar
        on_open_reorder="open_reorder_modal"
        on_clear_selection="clear"
        noun_singular={gettext("project")}
        noun_plural={gettext("projects")}
      />

      <.table_default id="projects-list" size="sm">
        <.table_default_header>
          <.table_default_row>
            <.bulk_select_header_cell
              id="projects-select-all"
              aria_label={gettext("Select all projects")}
            />
            <.table_default_header_cell>Name</.table_default_header_cell>
            ...
          </.table_default_row>
        </.table_default_header>
        <tbody>
          <.table_default_row :for={p <- @projects}>
            <.bulk_select_cell value={p.uuid} />
            <.table_default_cell>{p.name}</.table_default_cell>
            ...
          </.table_default_row>
        </tbody>
      </.table_default>
    </.bulk_select_scope>

The consumer LV handles `on_open_reorder` (etc.) with a payload of
`%{"uuids" => uuids}`:

    def handle_event("open_reorder_modal", %{"uuids" => uuids}, socket) do
      {:noreply, assign(socket, show_reorder_modal: true, captured_uuids: uuids)}
    end

## Why client-side

Server-side selection (each click is a `phx-click` round-trip)
forces a re-render with every toggle, which feels laggy at any
meaningful network latency. Bulk-select is a high-frequency UI
interaction where the server only needs to know the selection at
the moment it acts on it — making the client the natural owner.

# `bulk_actions_toolbar`

Floating toolbar above the table. Built-in actions: Reorder, Delete,
Clear. Each button is opt-in via flags / event-name attrs. Toolbar
always renders; the count text + button labels + visibility update
live as the user toggles checkboxes.

Reorder is mandatory (`on_open_reorder` is required). Delete and
Clear are optional.

## Attributes

* `on_open_reorder` (`:string`) (required) - Event pushed when the Reorder button is clicked. Receives `%{"uuids" => uuids}`.
* `on_bulk_delete` (`:string`) - Event pushed when Delete is clicked. Required if `allow_delete` is true. Defaults to `nil`.
* `noun_singular` (`:string`) - Defaults to `"item"`.
* `noun_plural` (`:string`) - Defaults to `"items"`.
* `allow_delete` (`:boolean`) - Defaults to `true`.
* `reorder_gate` (`:atom`) - When `:always`, the Reorder button is always visible — label is 'Reorder all' when 0–1 rows are selected, 'Reorder N selected' when 2+. (A one-row reorder is a no-op, so we keep the 'Reorder all' label there; the consumer LV normalises 1-uuid scopes to :all when applying.) When `:multi`, hidden unless count > 1 — useful when the surrounding context has no meaningful 'reorder all' interpretation (e.g. the list is currently sorted by name, not the manual position field). Defaults to `:always`. Must be one of `:always`, or `:multi`.
* `reorder_dialog_id` (`:string`) - Optional id of a kept-in-DOM reorder modal (e.g. `"reorder-modal"`). When set, the Reorder button opens that dialog locally via the BulkSelectScope hook BEFORE pushing `on_open_reorder`, so the modal appears instantly without waiting for the LV round-trip. Pair with `<.reorder_modal>` (which sets `keep_in_dom={true}` on its inner `<.modal>`). Omit to fall back to the round-trip-open flow used by conditionally-rendered modals. Defaults to `nil`.
## Slots

* `leading` - Content rendered on the left of the toolbar before the action buttons. Common use: tuck a sort selector in here so the toolbar reads as one widget.

# `bulk_select_cell`

Per-row checkbox cell. The `value` is captured into the selection
set when checked; it's the identifier the server receives in
`{uuids: [...]}` when an action fires.

## Attributes

* `value` (`:string`) (required)
* `class` (`:string`) - Defaults to `"w-8"`.

# `bulk_select_header_cell`

Header checkbox cell — drop-in replacement for `<.table_default_header_cell>`
in the bulk-select column. The `BulkSelectScope` hook drives its
checked / indeterminate state based on the current selection.

## Attributes

* `id` (`:string`) (required)
* `aria_label` (`:string`) - Defaults to `"Toggle select all"`.
* `class` (`:string`) - Defaults to `"w-8"`.

# `bulk_select_scope`

Opens a bulk-select scope. Everything inside this wrapper
participates in the same selection set.

## Attributes

* `id` (`:string`) (required)
* `total_count` (`:integer`) (required)
* `class` (`:string`) - Defaults to `""`.
## Slots

* `inner_block` (required)

---

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