separate editor FAB and panel for cleaner animation
All checks were successful
deploy / deploy (push) Successful in 1m32s

Split the editor sheet into two distinct elements:
- .editor-fab: floating action button, always a pill in the corner
- .editor-panel: sliding panel that animates in/out independently

This enables proper CSS keyframe animations (slide-up/down on mobile,
slide-in/out on desktop) with a closing class for exit transitions.
Simplified the JS hook to only handle close behaviour.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey
2026-03-07 19:01:32 +00:00
parent 3f96769840
commit bd07c9c7d9
6 changed files with 230 additions and 249 deletions

View File

@@ -1066,77 +1066,61 @@ defmodule BerrypodWeb.ShopComponents.Layout do
def editor_sheet(assigns) do
~H"""
<%!-- Floating action button: always visible when panel is closed --%>
<button
:if={@editor_sheet_state == :collapsed}
type="button"
phx-click={if @editing, do: "editor_set_sheet_state", else: "editor_toggle_editing"}
phx-value-state={if @editing, do: "open", else: nil}
class="editor-fab"
aria-label={if @editing, do: "Show editor", else: "Edit page"}
>
<.edit_pencil_svg />
<span>{if @editing, do: "Show editor", else: "Edit page"}</span>
<span :if={@editing && @editor_dirty} class="editor-fab-dirty" aria-label="Unsaved changes" />
</button>
<%!-- Editor panel: slides in/out --%>
<aside
id="editor-sheet"
class="editor-sheet"
id="editor-panel"
class="editor-panel"
role="region"
aria-label="Page editor"
aria-expanded={to_string(@editor_sheet_state != :collapsed)}
aria-hidden={to_string(@editor_sheet_state == :collapsed)}
data-state={@editor_sheet_state}
data-editing={to_string(@editing)}
phx-hook="EditorSheet"
>
<%!-- Header: content varies by state and editing mode --%>
<div class="editor-sheet-header">
<%= if @editor_sheet_state == :collapsed and not @editing do %>
<%!-- Not editing, collapsed: show Edit button to enter edit mode --%>
<button
type="button"
phx-click="editor_toggle_editing"
class="editor-sheet-edit-btn"
>
<.edit_pencil_svg />
<span>Edit page</span>
</button>
<% end %>
<%= if @editor_sheet_state == :collapsed and @editing do %>
<%!-- Editing but collapsed: show button to expand sheet (for previewing) --%>
<button
type="button"
phx-click="editor_set_sheet_state"
phx-value-state="open"
class="editor-sheet-edit-btn"
>
<.edit_pencil_svg />
<span>Show editor</span>
</button>
<span :if={@editor_dirty} class="editor-sheet-dirty" aria-live="polite">
<span class="editor-sheet-dirty-dot" aria-hidden="true" />
<div class="editor-panel-header">
<div class="editor-panel-header-left">
<span class="editor-panel-title">Page editor</span>
<span :if={@editor_dirty} class="editor-panel-dirty" aria-live="polite">
<span class="editor-panel-dirty-dot" aria-hidden="true" />
<span>Unsaved</span>
</span>
<% end %>
<%= if @editor_sheet_state != :collapsed do %>
<div class="editor-sheet-header-left">
<span class="editor-sheet-title">Page editor</span>
<span :if={@editor_dirty} class="editor-sheet-dirty" aria-live="polite">
<span class="editor-sheet-dirty-dot" aria-hidden="true" />
<span>Unsaved</span>
</span>
</div>
<div class="editor-sheet-header-actions">
<button
:if={@editor_save_status == :saved}
type="button"
class="admin-btn admin-btn-sm admin-btn-ghost"
disabled
>
Saved ✓
</button>
<button
:if={@editor_save_status != :saved}
type="button"
phx-click="editor_save"
class={["admin-btn admin-btn-sm", @editor_dirty && "admin-btn-primary"]}
disabled={!@editor_dirty}
>
Save
</button>
</div>
<% end %>
</div>
<div class="editor-panel-header-actions">
<button
:if={@editor_save_status == :saved}
type="button"
class="admin-btn admin-btn-sm admin-btn-ghost"
disabled
>
Saved ✓
</button>
<button
:if={@editor_save_status != :saved}
type="button"
phx-click="editor_save"
class={["admin-btn admin-btn-sm", @editor_dirty && "admin-btn-primary"]}
disabled={!@editor_dirty}
>
Save
</button>
</div>
</div>
<%!-- Content area (hidden when collapsed) --%>
<div class="editor-sheet-content">
<div class="editor-panel-content">
{render_slot(@inner_block)}
</div>
</aside>