defmodule PhiaUi.Components.InlineEditTable do @moduledoc """ Inline-editable table row components for in-place data editing. All state (which rows are in edit mode, saving state) lives in the LiveView. These components are purely presentational — they switch between a read view and an edit view based on `@editing` assigns. ## Sub-components | Function | HTML element | Purpose | |------------------------|--------------|---------------------------------------------| | `editable_cell/1` | `` | Single cell toggling view ↔ edit mode | | `editable_row/1` | `` | Row wrapper that applies edit-mode styling | | `editable_row_actions/1`| `` | Save + Cancel action cell for editing rows | ## Example defmodule MyAppWeb.ProductsLive do use MyAppWeb, :live_view def handle_event("start_edit", %{"id" => id}, socket) do {:noreply, assign(socket, editing_id: id)} end def handle_event("save_row", %{"id" => id}, socket) do # persist changes, then: {:noreply, assign(socket, editing_id: nil)} end def handle_event("cancel_edit", _params, socket) do {:noreply, assign(socket, editing_id: nil)} end def render(assigns) do ~H\"\"\" <.table> <.table_body> <%= for product <- @products do %> <% editing = to_string(product.id) == @editing_id %> <.editable_row editing={editing}> <.editable_cell editing={editing}> <:view>{product.name} <:edit> <.editable_cell editing={editing}> <:view>{product.price} <:edit> <%= if editing do %> <.editable_row_actions row_id={to_string(product.id)} on_save="save_row" on_cancel="cancel_edit" /> <% else %> <.table_cell> <.button variant={:ghost} size={:sm} phx-click="start_edit" phx-value-id={to_string(product.id)}>Edit <% end %> <% end %> \"\"\" end end """ use Phoenix.Component import PhiaUi.ClassMerger, only: [cn: 1] # --------------------------------------------------------------------------- # editable_cell/1 # --------------------------------------------------------------------------- attr(:editing, :boolean, default: false, doc: """ When `true`, the `:edit` slot is displayed and `:view` is hidden. When `false`, the `:view` slot is displayed and `:edit` is hidden. """ ) attr(:class, :string, default: nil, doc: "Additional CSS classes for the ``") attr(:rest, :global, doc: "HTML attributes forwarded to the `` element") slot(:view, required: true, doc: "Content shown when the row is NOT in edit mode (read view)" ) slot(:edit, required: true, doc: "Input or select shown when the row IS in edit mode (edit view)" ) @doc """ Renders a `` that switches between a read view and an edit view. Only one slot is rendered at a time — determined by `@editing`. This keeps the server-rendered HTML minimal while making the transition seamless from the user's perspective. ## Example <.editable_cell editing={@editing_id == product.id}> <:view>{product.sku} <:edit> """ def editable_cell(assigns) do ~H"""
{render_slot(@view)}
{render_slot(@edit)}
""" end # --------------------------------------------------------------------------- # editable_row/1 # --------------------------------------------------------------------------- attr(:editing, :boolean, default: false, doc: """ When `true`, applies an edit-mode background tint and a primary ring outline to the row. Set to `false` for the default read view. """ ) attr(:class, :string, default: nil, doc: "Additional CSS classes for the ``") attr(:rest, :global, doc: "HTML attributes forwarded to the `` element (e.g. `id`)") slot(:inner_block, required: true, doc: "`editable_cell/1` and `editable_row_actions/1` children" ) @doc """ Renders a `` wrapper that applies edit-mode visual styling. When `editing={true}`: - Adds a subtle `bg-muted/20` background - Adds `ring-1 ring-inset ring-primary/30` to signal an active edit session - Sets `data-editing="true"` for CSS or JavaScript targeting ## Example <.editable_row editing={@editing_id == user.id}> <.editable_cell editing={@editing_id == user.id}> <:view>{user.name} <:edit> <.editable_row_actions row_id={to_string(user.id)} /> """ def editable_row(assigns) do ~H""" {render_slot(@inner_block)} """ end # --------------------------------------------------------------------------- # editable_row_actions/1 # --------------------------------------------------------------------------- attr(:on_save, :string, default: "save_row", doc: """ `phx-click` event fired when the Save button is clicked. Receives `phx-value-id` set to `row_id`. """ ) attr(:on_cancel, :string, default: "cancel_edit", doc: """ `phx-click` event fired when the Cancel button is clicked. Receives `phx-value-id` set to `row_id`. """ ) attr(:row_id, :string, required: true, doc: "Row identifier sent as `phx-value-id` on both Save and Cancel clicks" ) attr(:saving, :boolean, default: false, doc: "When `true`, the Save button is dimmed and pointer-events are disabled" ) attr(:class, :string, default: nil, doc: "Additional CSS classes for the ``") @doc """ Renders a `` containing Save and Cancel buttons for an editing row. The Save button is styled as a primary action. When `saving={true}` it becomes dimmed to signal an in-flight request. The Cancel button is a ghost action that discards changes. ## Example <.editable_row_actions row_id={to_string(user.id)} saving={@saving_id == user.id} on_save="save_row" on_cancel="cancel_edit" /> """ def editable_row_actions(assigns) do ~H"""
""" end end