Update card buttons dynamically

User flows when editing a card look like:
- Click "Edit" → the closure buttons are replaced by "Save changes"
- Submit card form → Saves and restores closure buttons
- Press ESC while editing → Cancels and restores closure buttons

Also, renamed for clarity:
- _title.html.erb → _content.html.erb

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mike Dalessio
2025-11-22 11:07:32 -05:00
parent 31266699cb
commit b05ecb7809
12 changed files with 57 additions and 33 deletions
-1
View File
@@ -22,7 +22,6 @@ class CardsController < ApplicationController
def update def update
@card.update! card_params @card.update! card_params
redirect_to @card
end end
def destroy def destroy
+1 -1
View File
@@ -14,7 +14,7 @@
<div class="card__body justify-space-between"> <div class="card__body justify-space-between">
<div class="card__content"> <div class="card__content">
<%= render "cards/container/title", card: card %> <%= render "cards/container/content", card: card %>
<%= render "cards/display/perma/steps", card: card %> <%= render "cards/display/perma/steps", card: card %>
</div> </div>
+1 -15
View File
@@ -7,21 +7,7 @@
<% end %> <% end %>
</div> </div>
<% else %> <% else %>
<div class="flex gap-half"> <%= render "cards/container/closure_buttons", card: card %>
<%= link_to edit_card_path(card), class: "btn btn--circle-mobile borderless", data: { controller: "hotkey", action: "keydown.e@document->hotkey#click", turbo_frame: dom_id(card, :edit) } do %>
<%= icon_tag "pencil", class: "icon--mobile-only" %>
<span>Edit</span>
<kbd class="txt-x-small hide-on-touch">e</kbd>
<% end %>
<%= button_to card_closure_path(card), class: "btn btn--circle-mobile borderless",
data: { controller: "hotkey", form_target: "submit", action: "keydown.d@document->hotkey#click" },
form: { data: { controller: "form" } } do %>
<%= icon_tag "check", class: "icon--mobile-only" %>
<span class="overflow-ellipsis">Mark as Done</span>
<kbd class="txt-x-small hide-on-touch">d</kbd>
<% end %>
</div>
<div id="<%= dom_id(card, :closure_notice) %>"> <div id="<%= dom_id(card, :closure_notice) %>">
<% if card.entropic? && card.open? && !card.postponed? %> <% if card.entropic? && card.open? && !card.postponed? %>
@@ -0,0 +1,15 @@
<div class="flex gap-half">
<%= link_to edit_card_path(card), class: "btn btn--circle-mobile borderless", data: { controller: "hotkey", action: "keydown.e@document->hotkey#click", turbo_frame: dom_id(card, :edit) } do %>
<%= icon_tag "pencil", class: "icon--mobile-only" %>
<span>Edit</span>
<kbd class="txt-x-small hide-on-touch">e</kbd>
<% end %>
<%= button_to card_closure_path(card), class: "btn btn--circle-mobile borderless",
data: { controller: "hotkey", form_target: "submit", action: "keydown.d@document->hotkey#click" },
form: { data: { controller: "form" } } do %>
<%= icon_tag "check", class: "icon--mobile-only" %>
<span class="overflow-ellipsis">Mark as Done</span>
<kbd class="txt-x-small hide-on-touch">d</kbd>
<% end %>
</div>
@@ -1,15 +1,12 @@
<% if card.published? %> <% if card.published? %>
<div data-turbo-permanent> <div data-turbo-permanent>
<%= turbo_frame_tag card, :edit do %> <%= turbo_frame_tag card, :edit do %>
<h1 class="card__title flex align-start gap-half"> <%# When canceling an edit (with the ESC key), restore the button area to show "Edit" instead of "Save changes". %>
<%= link_to card.title, edit_card_path(card), class: "card__title-link" %> <%= turbo_stream.update dom_id(card, :card_closure_toggle) do %>
</h1> <%= render "cards/container/closure_buttons", card: card %>
<% unless card.description.blank? %>
<div class="card__description rich-text-content" data-controller="syntax-highlight retarget-links">
<%= card.description %>
</div>
<% end %> <% end %>
<%= render "cards/container/content_display", card: card %>
<% end %> <% end %>
</div> </div>
<% else %> <% else %>
@@ -0,0 +1,9 @@
<h1 class="card__title flex align-start gap-half">
<%= link_to card.title, edit_card_path(card), class: "card__title-link" %>
</h1>
<% unless card.description.blank? %>
<div class="card__description rich-text-content" data-controller="syntax-highlight retarget-links">
<%= card.description %>
</div>
<% end %>
@@ -0,0 +1,8 @@
<div class="flex gap-half">
<%= button_tag type: :submit, form: dom_id(card, :edit_form), class: "btn btn--circle-mobile borderless",
data: { controller: "hotkey", action: "keydown.ctrl+enter@document->hotkey#click keydown.meta+enter@document->hotkey#click" } do %>
<%= icon_tag "check", class: "icon--mobile-only" %>
<span>Save changes</span>
<kbd class="txt-x-small hide-on-touch"><%= hotkey_label(["ctrl", "enter"]) %></kbd>
<% end %>
</div>
+8 -5
View File
@@ -1,5 +1,12 @@
<%= turbo_frame_tag @card, :edit do %> <%= turbo_frame_tag @card, :edit do %>
<%= form_with model: @card, <%# When entering edit mode, this turbo-stream replaces the button area to show
"Save changes" instead of "Edit". Turbo processes this stream as part of the
frame response. %>
<%= turbo_stream.update dom_id(@card, :card_closure_toggle) do %>
<%= render "cards/container/save_button", card: @card %>
<% end %>
<%= form_with model: @card, id: dom_id(@card, :edit_form),
data: { controller: "autoresize form local-save", local_save_key_value: "card-#{@card.id}", action: "turbo:submit-end->local-save#submit" } do |form| %> data: { controller: "autoresize form local-save", local_save_key_value: "card-#{@card.id}", action: "turbo:submit-end->local-save#submit" } do |form| %>
<h1 class="card__title flex align-start gap-half"> <h1 class="card__title flex align-start gap-half">
<%= form.label :title, class: "flex flex-column align-center autoresize__wrapper", data: { autoresize_target: "wrapper", autoresize_clone_value: "" } do %> <%= form.label :title, class: "flex flex-column align-center autoresize__wrapper", data: { autoresize_target: "wrapper", autoresize_clone_value: "" } do %>
@@ -15,10 +22,6 @@
<%= general_prompts(@card.board) %> <%= general_prompts(@card.board) %>
<% end %> <% end %>
<%= form.button type: :submit, class: "btn btn--reversed fit-content txt-small", data: { form_target: "submit" } do %>
<span>Save changes</span>
<kbd class="txt-x-small hide-on-touch"><%= hotkey_label([ "ctrl", "enter" ]) %></kbd>
<% end %>
<%= link_to "Close editor and discard changes", @card, data: { form_target: "cancel" }, hidden: true %> <%= link_to "Close editor and discard changes", @card, data: { form_target: "cancel" }, hidden: true %>
<% end %> <% end %>
<% end %> <% end %>
+7
View File
@@ -0,0 +1,7 @@
<%= turbo_stream.update dom_id(@card, :edit) do %>
<%= render "cards/container/content_display", card: @card %>
<% end %>
<%= turbo_stream.update dom_id(@card, :card_closure_toggle) do %>
<%= render "cards/container/closure_buttons", card: @card %>
<% end %>
+1 -1
View File
@@ -28,7 +28,7 @@
</header> </header>
<div class="card__body justify-space-between"> <div class="card__body justify-space-between">
<%= render "public/cards/show/title", card: @card %> <%= render "public/cards/show/content", card: @card %>
<%= render "cards/display/public_preview/columns", card: @card if @card.open? %> <%= render "cards/display/public_preview/columns", card: @card if @card.open? %>
</div> </div>
+2 -2
View File
@@ -46,13 +46,13 @@ class CardsControllerTest < ActionDispatch::IntegrationTest
end end
test "update" do test "update" do
patch card_path(cards(:logo)), params: { patch card_path(cards(:logo)), as: :turbo_stream, params: {
card: { card: {
title: "Logo needs to change", title: "Logo needs to change",
image: fixture_file_upload("moon.jpg", "image/jpeg"), image: fixture_file_upload("moon.jpg", "image/jpeg"),
description: "Something more in-depth", description: "Something more in-depth",
tag_ids: [ tags(:mobile).id ] } } tag_ids: [ tags(:mobile).id ] } }
assert_redirected_to card_path(cards(:logo)) assert_response :success
card = cards(:logo).reload card = cards(:logo).reload
assert_equal "Logo needs to change", card.title assert_equal "Logo needs to change", card.title