fix search modal closing on keypress and add admin header icon
Track search_open as server state so morphdom doesn't reset display to none on re-render. Move admin bar from layout banner to a gear icon in the header actions. Extract layout_assigns/1 helper so page templates use a spread instead of listing every attr explicitly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
edcbc596e3
commit
994f6fe0d6
@ -380,6 +380,7 @@ const SearchModal = {
|
|||||||
|
|
||||||
open() {
|
open() {
|
||||||
this.el.style.display = "flex"
|
this.el.style.display = "flex"
|
||||||
|
this.pushEvent("open_search", {})
|
||||||
const input = this.el.querySelector("#search-input")
|
const input = this.el.querySelector("#search-input")
|
||||||
if (input) {
|
if (input) {
|
||||||
input.focus()
|
input.focus()
|
||||||
|
|||||||
@ -1,13 +1,2 @@
|
|||||||
<div
|
|
||||||
:if={assigns[:current_scope]}
|
|
||||||
style="background-color: var(--t-surface-raised, #f5f5f5); border-bottom: 1px solid var(--t-border-default, #e5e5e5); padding: 0.25rem 1rem; font-size: 0.75rem; text-align: right;"
|
|
||||||
>
|
|
||||||
<.link
|
|
||||||
href={~p"/admin"}
|
|
||||||
style="color: var(--t-text-secondary, #666); text-decoration: none;"
|
|
||||||
>
|
|
||||||
Admin
|
|
||||||
</.link>
|
|
||||||
</div>
|
|
||||||
<.shop_flash_group flash={@flash} />
|
<.shop_flash_group flash={@flash} />
|
||||||
{@inner_content}
|
{@inner_content}
|
||||||
|
|||||||
@ -1,17 +1,4 @@
|
|||||||
<.shop_layout
|
<.shop_layout {layout_assigns(assigns)} active_page="cart">
|
||||||
theme_settings={@theme_settings}
|
|
||||||
logo_image={@logo_image}
|
|
||||||
header_image={@header_image}
|
|
||||||
mode={@mode}
|
|
||||||
cart_items={@cart_items}
|
|
||||||
cart_count={@cart_count}
|
|
||||||
cart_subtotal={@cart_subtotal}
|
|
||||||
cart_drawer_open={assigns[:cart_drawer_open] || false}
|
|
||||||
cart_status={assigns[:cart_status]}
|
|
||||||
active_page="cart"
|
|
||||||
search_query={assigns[:search_query] || ""}
|
|
||||||
search_results={assigns[:search_results] || []}
|
|
||||||
>
|
|
||||||
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<.page_title text="Your basket" />
|
<.page_title text="Your basket" />
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,4 @@
|
|||||||
<.shop_layout
|
<.shop_layout {layout_assigns(assigns)} active_page="checkout">
|
||||||
theme_settings={@theme_settings}
|
|
||||||
logo_image={@logo_image}
|
|
||||||
header_image={@header_image}
|
|
||||||
mode={@mode}
|
|
||||||
cart_items={@cart_items}
|
|
||||||
cart_count={@cart_count}
|
|
||||||
cart_subtotal={@cart_subtotal}
|
|
||||||
cart_drawer_open={assigns[:cart_drawer_open] || false}
|
|
||||||
cart_status={assigns[:cart_status]}
|
|
||||||
active_page="checkout"
|
|
||||||
search_query={assigns[:search_query] || ""}
|
|
||||||
search_results={assigns[:search_results] || []}
|
|
||||||
>
|
|
||||||
<main id="main-content" class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
<main id="main-content" class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
|
||||||
<%= if @order && @order.payment_status == "paid" do %>
|
<%= if @order && @order.payment_status == "paid" do %>
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
|
|||||||
@ -1,17 +1,4 @@
|
|||||||
<.shop_layout
|
<.shop_layout {layout_assigns(assigns)} active_page="collection">
|
||||||
theme_settings={@theme_settings}
|
|
||||||
logo_image={@logo_image}
|
|
||||||
header_image={@header_image}
|
|
||||||
mode={@mode}
|
|
||||||
cart_items={@cart_items}
|
|
||||||
cart_count={@cart_count}
|
|
||||||
cart_subtotal={@cart_subtotal}
|
|
||||||
cart_drawer_open={assigns[:cart_drawer_open] || false}
|
|
||||||
cart_status={assigns[:cart_status]}
|
|
||||||
active_page="collection"
|
|
||||||
search_query={assigns[:search_query] || ""}
|
|
||||||
search_results={assigns[:search_results] || []}
|
|
||||||
>
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
<.collection_header title="All Products" product_count={length(assigns[:products] || [])} />
|
<.collection_header title="All Products" product_count={length(assigns[:products] || [])} />
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +1,4 @@
|
|||||||
<.shop_layout
|
<.shop_layout {layout_assigns(assigns)} active_page="contact">
|
||||||
theme_settings={@theme_settings}
|
|
||||||
logo_image={@logo_image}
|
|
||||||
header_image={@header_image}
|
|
||||||
mode={@mode}
|
|
||||||
cart_items={@cart_items}
|
|
||||||
cart_count={@cart_count}
|
|
||||||
cart_subtotal={@cart_subtotal}
|
|
||||||
cart_drawer_open={assigns[:cart_drawer_open] || false}
|
|
||||||
cart_status={assigns[:cart_status]}
|
|
||||||
active_page="contact"
|
|
||||||
search_query={assigns[:search_query] || ""}
|
|
||||||
search_results={assigns[:search_results] || []}
|
|
||||||
>
|
|
||||||
<main id="main-content" class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-16">
|
<main id="main-content" class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-16">
|
||||||
<.hero_section
|
<.hero_section
|
||||||
variant={:page}
|
variant={:page}
|
||||||
|
|||||||
@ -1,17 +1,4 @@
|
|||||||
<.shop_layout
|
<.shop_layout {layout_assigns(assigns)}>
|
||||||
theme_settings={@theme_settings}
|
|
||||||
logo_image={@logo_image}
|
|
||||||
header_image={@header_image}
|
|
||||||
mode={@mode}
|
|
||||||
cart_items={@cart_items}
|
|
||||||
cart_count={@cart_count}
|
|
||||||
cart_subtotal={@cart_subtotal}
|
|
||||||
cart_drawer_open={assigns[:cart_drawer_open] || false}
|
|
||||||
cart_status={assigns[:cart_status]}
|
|
||||||
active_page={@active_page}
|
|
||||||
search_query={assigns[:search_query] || ""}
|
|
||||||
search_results={assigns[:search_results] || []}
|
|
||||||
>
|
|
||||||
<main id="main-content" class="content-page" style="background-color: var(--t-surface-base);">
|
<main id="main-content" class="content-page" style="background-color: var(--t-surface-base);">
|
||||||
<%= if assigns[:hero_background] do %>
|
<%= if assigns[:hero_background] do %>
|
||||||
<.hero_section
|
<.hero_section
|
||||||
|
|||||||
@ -1,18 +1,4 @@
|
|||||||
<.shop_layout
|
<.shop_layout {layout_assigns(assigns)} active_page="error" error_page>
|
||||||
theme_settings={@theme_settings}
|
|
||||||
logo_image={@logo_image}
|
|
||||||
header_image={@header_image}
|
|
||||||
mode={@mode}
|
|
||||||
cart_items={@cart_items}
|
|
||||||
cart_count={@cart_count}
|
|
||||||
cart_subtotal={@cart_subtotal}
|
|
||||||
cart_drawer_open={assigns[:cart_drawer_open] || false}
|
|
||||||
cart_status={assigns[:cart_status]}
|
|
||||||
active_page="error"
|
|
||||||
error_page
|
|
||||||
search_query={assigns[:search_query] || ""}
|
|
||||||
search_results={assigns[:search_results] || []}
|
|
||||||
>
|
|
||||||
<main
|
<main
|
||||||
id="main-content"
|
id="main-content"
|
||||||
class="flex items-center justify-center"
|
class="flex items-center justify-center"
|
||||||
|
|||||||
@ -1,17 +1,4 @@
|
|||||||
<.shop_layout
|
<.shop_layout {layout_assigns(assigns)} active_page="home">
|
||||||
theme_settings={@theme_settings}
|
|
||||||
logo_image={@logo_image}
|
|
||||||
header_image={@header_image}
|
|
||||||
mode={@mode}
|
|
||||||
cart_items={@cart_items}
|
|
||||||
cart_count={@cart_count}
|
|
||||||
cart_subtotal={@cart_subtotal}
|
|
||||||
cart_drawer_open={assigns[:cart_drawer_open] || false}
|
|
||||||
cart_status={assigns[:cart_status]}
|
|
||||||
active_page="home"
|
|
||||||
search_query={assigns[:search_query] || ""}
|
|
||||||
search_results={assigns[:search_results] || []}
|
|
||||||
>
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
<.hero_section
|
<.hero_section
|
||||||
title="Original designs, printed on demand"
|
title="Original designs, printed on demand"
|
||||||
|
|||||||
@ -1,17 +1,4 @@
|
|||||||
<.shop_layout
|
<.shop_layout {layout_assigns(assigns)} active_page="pdp">
|
||||||
theme_settings={@theme_settings}
|
|
||||||
logo_image={@logo_image}
|
|
||||||
header_image={@header_image}
|
|
||||||
mode={@mode}
|
|
||||||
cart_items={@cart_items}
|
|
||||||
cart_count={@cart_count}
|
|
||||||
cart_subtotal={@cart_subtotal}
|
|
||||||
cart_drawer_open={assigns[:cart_drawer_open] || false}
|
|
||||||
cart_status={assigns[:cart_status]}
|
|
||||||
active_page="pdp"
|
|
||||||
search_query={assigns[:search_query] || ""}
|
|
||||||
search_results={assigns[:search_results] || []}
|
|
||||||
>
|
|
||||||
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
<main id="main-content" class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<.breadcrumb
|
<.breadcrumb
|
||||||
items={
|
items={
|
||||||
|
|||||||
@ -49,6 +49,25 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do
|
|||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Keys accepted by shop_layout — used by layout_assigns/1 so page templates
|
||||||
|
# can spread assigns without listing each one explicitly.
|
||||||
|
@layout_keys ~w(theme_settings logo_image header_image mode cart_items cart_count
|
||||||
|
cart_subtotal cart_drawer_open cart_status active_page error_page is_admin
|
||||||
|
search_query search_results search_open categories)a
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Extracts the assigns relevant to `shop_layout` from a full assigns map.
|
||||||
|
|
||||||
|
Page templates can use this instead of listing every attr explicitly:
|
||||||
|
|
||||||
|
<.shop_layout {layout_assigns(assigns)} active_page="home">
|
||||||
|
...
|
||||||
|
</.shop_layout>
|
||||||
|
"""
|
||||||
|
def layout_assigns(assigns) do
|
||||||
|
Map.take(assigns, @layout_keys)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Wraps page content in the standard shop shell: container, header, footer,
|
Wraps page content in the standard shop shell: container, header, footer,
|
||||||
cart drawer, search modal, and mobile bottom nav.
|
cart drawer, search modal, and mobile bottom nav.
|
||||||
@ -67,8 +86,10 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do
|
|||||||
attr :cart_status, :string, default: nil
|
attr :cart_status, :string, default: nil
|
||||||
attr :active_page, :string, required: true
|
attr :active_page, :string, required: true
|
||||||
attr :error_page, :boolean, default: false
|
attr :error_page, :boolean, default: false
|
||||||
|
attr :is_admin, :boolean, default: false
|
||||||
attr :search_query, :string, default: ""
|
attr :search_query, :string, default: ""
|
||||||
attr :search_results, :list, default: []
|
attr :search_results, :list, default: []
|
||||||
|
attr :search_open, :boolean, default: false
|
||||||
|
|
||||||
slot :inner_block, required: true
|
slot :inner_block, required: true
|
||||||
|
|
||||||
@ -93,6 +114,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do
|
|||||||
active_page={@active_page}
|
active_page={@active_page}
|
||||||
mode={@mode}
|
mode={@mode}
|
||||||
cart_count={@cart_count}
|
cart_count={@cart_count}
|
||||||
|
is_admin={@is_admin}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{render_slot(@inner_block)}
|
{render_slot(@inner_block)}
|
||||||
@ -116,6 +138,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do
|
|||||||
hint_text={~s(Try a search – e.g. "mountain" or "notebook")}
|
hint_text={~s(Try a search – e.g. "mountain" or "notebook")}
|
||||||
search_query={@search_query}
|
search_query={@search_query}
|
||||||
search_results={@search_results}
|
search_results={@search_results}
|
||||||
|
search_open={@search_open}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<.mobile_bottom_nav :if={!@error_page} active_page={@active_page} mode={@mode} />
|
<.mobile_bottom_nav :if={!@error_page} active_page={@active_page} mode={@mode} />
|
||||||
@ -336,6 +359,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do
|
|||||||
attr :hint_text, :string, default: nil
|
attr :hint_text, :string, default: nil
|
||||||
attr :search_query, :string, default: ""
|
attr :search_query, :string, default: ""
|
||||||
attr :search_results, :list, default: []
|
attr :search_results, :list, default: []
|
||||||
|
attr :search_open, :boolean, default: false
|
||||||
|
|
||||||
def search_modal(assigns) do
|
def search_modal(assigns) do
|
||||||
alias SimpleshopTheme.Cart
|
alias SimpleshopTheme.Cart
|
||||||
@ -357,7 +381,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do
|
|||||||
<div
|
<div
|
||||||
id="search-modal"
|
id="search-modal"
|
||||||
class="search-modal"
|
class="search-modal"
|
||||||
style="position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1001; display: none; align-items: flex-start; justify-content: center; padding-top: 10vh;"
|
style={"position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1001; display: #{if @search_open, do: "flex", else: "none"}; align-items: flex-start; justify-content: center; padding-top: 10vh;"}
|
||||||
phx-hook="SearchModal"
|
phx-hook="SearchModal"
|
||||||
phx-click={
|
phx-click={
|
||||||
Phoenix.LiveView.JS.hide(to: "#search-modal")
|
Phoenix.LiveView.JS.hide(to: "#search-modal")
|
||||||
@ -714,6 +738,7 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do
|
|||||||
attr :active_page, :string, default: nil
|
attr :active_page, :string, default: nil
|
||||||
attr :mode, :atom, default: :live
|
attr :mode, :atom, default: :live
|
||||||
attr :cart_count, :integer, default: 0
|
attr :cart_count, :integer, default: 0
|
||||||
|
attr :is_admin, :boolean, default: false
|
||||||
|
|
||||||
def shop_header(assigns) do
|
def shop_header(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
@ -764,12 +789,41 @@ defmodule SimpleshopThemeWeb.ShopComponents.Layout do
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="shop-actions flex items-center" style="position: relative; z-index: 1;">
|
<div class="shop-actions flex items-center" style="position: relative; z-index: 1;">
|
||||||
|
<.link
|
||||||
|
:if={@is_admin}
|
||||||
|
href="/admin"
|
||||||
|
class="header-icon-btn w-9 h-9 flex items-center justify-center transition-all"
|
||||||
|
style="color: var(--t-text-secondary); border-radius: var(--t-radius-button);"
|
||||||
|
aria-label="Admin"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-5 h-5"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.5"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</.link>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="header-icon-btn w-9 h-9 flex items-center justify-center transition-all"
|
class="header-icon-btn w-9 h-9 flex items-center justify-center transition-all"
|
||||||
style="color: var(--t-text-secondary); background: none; border: none; cursor: pointer; border-radius: var(--t-radius-button);"
|
style="color: var(--t-text-secondary); background: none; border: none; cursor: pointer; border-radius: var(--t-radius-button);"
|
||||||
phx-click={
|
phx-click={
|
||||||
Phoenix.LiveView.JS.show(to: "#search-modal", display: "flex")
|
Phoenix.LiveView.JS.push("open_search")
|
||||||
|
|> Phoenix.LiveView.JS.show(to: "#search-modal", display: "flex")
|
||||||
|> Phoenix.LiveView.JS.focus(to: "#search-input")
|
|> Phoenix.LiveView.JS.focus(to: "#search-input")
|
||||||
}
|
}
|
||||||
aria-label="Search"
|
aria-label="Search"
|
||||||
|
|||||||
@ -81,20 +81,7 @@ defmodule SimpleshopThemeWeb.Shop.Collection do
|
|||||||
@impl true
|
@impl true
|
||||||
def render(assigns) do
|
def render(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<.shop_layout
|
<.shop_layout {layout_assigns(assigns)} active_page="collection">
|
||||||
theme_settings={@theme_settings}
|
|
||||||
logo_image={@logo_image}
|
|
||||||
header_image={@header_image}
|
|
||||||
mode={@mode}
|
|
||||||
cart_items={@cart_items}
|
|
||||||
cart_count={@cart_count}
|
|
||||||
cart_subtotal={@cart_subtotal}
|
|
||||||
cart_drawer_open={@cart_drawer_open}
|
|
||||||
cart_status={assigns[:cart_status]}
|
|
||||||
active_page="collection"
|
|
||||||
search_query={assigns[:search_query] || ""}
|
|
||||||
search_results={assigns[:search_results] || []}
|
|
||||||
>
|
|
||||||
<main id="main-content">
|
<main id="main-content">
|
||||||
<.collection_header
|
<.collection_header
|
||||||
title={@collection_title}
|
title={@collection_title}
|
||||||
|
|||||||
@ -20,11 +20,16 @@ defmodule SimpleshopThemeWeb.SearchHook do
|
|||||||
socket
|
socket
|
||||||
|> assign(:search_query, "")
|
|> assign(:search_query, "")
|
||||||
|> assign(:search_results, [])
|
|> assign(:search_results, [])
|
||||||
|
|> assign(:search_open, false)
|
||||||
|> attach_hook(:search_events, :handle_event, &handle_search_event/3)
|
|> attach_hook(:search_events, :handle_event, &handle_search_event/3)
|
||||||
|
|
||||||
{:cont, socket}
|
{:cont, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_search_event("open_search", _params, socket) do
|
||||||
|
{:halt, assign(socket, :search_open, true)}
|
||||||
|
end
|
||||||
|
|
||||||
defp handle_search_event("search", %{"value" => query}, socket) do
|
defp handle_search_event("search", %{"value" => query}, socket) do
|
||||||
results = Search.search(query)
|
results = Search.search(query)
|
||||||
|
|
||||||
@ -41,6 +46,7 @@ defmodule SimpleshopThemeWeb.SearchHook do
|
|||||||
socket
|
socket
|
||||||
|> assign(:search_query, "")
|
|> assign(:search_query, "")
|
||||||
|> assign(:search_results, [])
|
|> assign(:search_results, [])
|
||||||
|
|> assign(:search_open, false)
|
||||||
|
|
||||||
{:halt, socket}
|
{:halt, socket}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -39,6 +39,10 @@ defmodule SimpleshopThemeWeb.ThemeHook do
|
|||||||
|> assign(:header_image, Media.get_header())
|
|> assign(:header_image, Media.get_header())
|
||||||
|> assign(:categories, Products.list_categories())
|
|> assign(:categories, Products.list_categories())
|
||||||
|> assign(:mode, :shop)
|
|> assign(:mode, :shop)
|
||||||
|
|> assign(
|
||||||
|
:is_admin,
|
||||||
|
!!(socket.assigns[:current_scope] && socket.assigns.current_scope.user)
|
||||||
|
)
|
||||||
|
|
||||||
{:cont, socket}
|
{:cont, socket}
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user