refactor: expand hero_section with page and error variants

Add variant support to hero_section component:
- :default - Standard hero with section wrapper (home, about)
- :page - Page header style with larger title, more spacing (contact)
- :error - Error page with pre-title (404), dual buttons (error)

Now used in: home, about, contact, error pages

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jamey Greenwood 2026-01-17 14:57:31 +00:00
parent f5b7693b96
commit 1589ebaeca
3 changed files with 164 additions and 73 deletions

View File

@ -861,77 +861,186 @@ defmodule SimpleshopThemeWeb.ShopComponents do
end end
@doc """ @doc """
Renders a centered hero section with title, description, and optional CTA. Renders a centered hero section with title, description, and optional CTAs.
## Attributes ## Attributes
* `title` - Required. The main heading text. * `title` - Required. The main heading text.
* `description` - Required. The description paragraph text. * `description` - Required. The description paragraph text.
* `variant` - The visual variant:
- `:default` - Standard hero with section wrapper and padding
- `:page` - Page header style, larger title, more spacing, no section wrapper
- `:error` - Error page with pre-title (404), two buttons
* `background` - Background style. Either `:base` (default) or `:sunken`. * `background` - Background style. Either `:base` (default) or `:sunken`.
* `cta_text` - Optional. Text for the CTA button. * `pre_title` - Optional. Text shown above title (e.g., "404" for error pages).
* `cta_text` - Optional. Text for the primary CTA button.
* `cta_page` - Optional. Page to navigate to on click (for preview mode). * `cta_page` - Optional. Page to navigate to on click (for preview mode).
* `cta_href` - Optional. URL for live mode navigation.
* `secondary_cta_text` - Optional. Text for secondary button (error variant).
* `secondary_cta_page` - Optional. Page for secondary button (preview mode).
* `secondary_cta_href` - Optional. URL for secondary button (live mode).
* `mode` - Either `:live` (default) or `:preview`. * `mode` - Either `:live` (default) or `:preview`.
## Examples ## Examples
<.hero_section <.hero_section
title="Original designs, printed on demand" title="Original designs, printed on demand"
description="From art prints to apparel unique products created by independent artists." description="From art prints to apparel..."
cta_text="Shop the collection" cta_text="Shop the collection"
cta_page="collection" cta_page="collection"
mode={:preview} mode={:preview}
/> />
<.hero_section <.hero_section
title="About the studio" variant={:page}
description="Nature photography, printed with care" title="Contact Us"
background={:sunken} description="Questions about your order?"
/>
<.hero_section
variant={:error}
pre_title="404"
title="Page Not Found"
description="Sorry, we couldn't find the page..."
cta_text="Go to Homepage"
secondary_cta_text="Browse Products"
mode={:preview}
/> />
""" """
attr :title, :string, required: true attr :title, :string, required: true
attr :description, :string, required: true attr :description, :string, required: true
attr :variant, :atom, default: :default
attr :background, :atom, default: :base attr :background, :atom, default: :base
attr :pre_title, :string, default: nil
attr :cta_text, :string, default: nil attr :cta_text, :string, default: nil
attr :cta_page, :string, default: nil attr :cta_page, :string, default: nil
attr :cta_href, :string, default: nil attr :cta_href, :string, default: nil
attr :secondary_cta_text, :string, default: nil
attr :secondary_cta_page, :string, default: nil
attr :secondary_cta_href, :string, default: nil
attr :mode, :atom, default: :live attr :mode, :atom, default: :live
def hero_section(assigns) do def hero_section(assigns) do
~H""" ~H"""
<section <%= case @variant do %>
class="text-center" <% :default -> %>
style={"padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-#{@background});"} <section
> class="text-center"
<h1 style={"padding: var(--space-2xl) var(--space-lg); background-color: var(--t-surface-#{@background});"}
class="text-3xl md:text-4xl mb-4" >
style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);" <h1
> class="text-3xl md:text-4xl mb-4"
<%= @title %> style="font-family: var(--t-font-heading); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking); color: var(--t-text-primary);"
</h1>
<p class="text-lg max-w-lg mx-auto mb-8" style="color: var(--t-text-secondary); line-height: 1.6;">
<%= @description %>
</p>
<%= if @cta_text do %>
<%= if @mode == :preview do %>
<button
phx-click="change_preview_page"
phx-value-page={@cta_page}
class="px-6 py-3 font-medium transition-all"
style="background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); color: var(--t-text-inverse); border-radius: var(--t-radius-button); cursor: pointer; border: none;"
> >
<%= @cta_text %> <%= @title %>
</button> </h1>
<% else %> <p class="text-lg max-w-lg mx-auto mb-8" style="color: var(--t-text-secondary); line-height: 1.6;">
<a <%= @description %>
href={@cta_href || "/products"} </p>
class="inline-block px-6 py-3 font-medium transition-all" <.hero_cta
style="background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); color: var(--t-text-inverse); border-radius: var(--t-radius-button); text-decoration: none;" :if={@cta_text}
text={@cta_text}
page={@cta_page}
href={@cta_href}
mode={@mode}
variant={:primary}
/>
</section>
<% :page -> %>
<div class="text-center">
<h1
class="text-4xl md:text-5xl font-bold mb-6"
style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"
> >
<%= @cta_text %> <%= @title %>
</a> </h1>
<% end %> <p class="text-lg mb-12 max-w-2xl mx-auto" style="color: var(--t-text-secondary);">
<% end %> <%= @description %>
</section> </p>
</div>
<% :error -> %>
<div class="text-center">
<%= if @pre_title do %>
<h1
class="text-8xl md:text-9xl font-bold mb-4"
style="font-family: var(--t-font-heading); color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); font-weight: var(--t-heading-weight);"
>
<%= @pre_title %>
</h1>
<% end %>
<h2
class="text-3xl md:text-4xl font-bold mb-6"
style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"
>
<%= @title %>
</h2>
<p class="text-lg mb-8 max-w-md mx-auto" style="color: var(--t-text-secondary);">
<%= @description %>
</p>
<%= if @cta_text || @secondary_cta_text do %>
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<.hero_cta
:if={@cta_text}
text={@cta_text}
page={@cta_page}
href={@cta_href}
mode={@mode}
variant={:primary}
/>
<.hero_cta
:if={@secondary_cta_text}
text={@secondary_cta_text}
page={@secondary_cta_page}
href={@secondary_cta_href}
mode={@mode}
variant={:secondary}
/>
</div>
<% end %>
</div>
<% end %>
""" """
end end
attr :text, :string, required: true
attr :page, :string, default: nil
attr :href, :string, default: nil
attr :mode, :atom, required: true
attr :variant, :atom, required: true
defp hero_cta(assigns) do
~H"""
<%= if @mode == :preview do %>
<button
phx-click="change_preview_page"
phx-value-page={@page}
class={hero_cta_classes(@variant)}
style={hero_cta_style(@variant)}
>
<%= @text %>
</button>
<% else %>
<a
href={@href || "/"}
class={["inline-block", hero_cta_classes(@variant)]}
style={hero_cta_style(@variant) <> " text-decoration: none;"}
>
<%= @text %>
</a>
<% end %>
"""
end
defp hero_cta_classes(:primary), do: "px-8 py-3 font-semibold transition-all"
defp hero_cta_classes(:secondary), do: "px-8 py-3 font-semibold transition-all"
defp hero_cta_style(:primary) do
"background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); color: var(--t-text-inverse); border-radius: var(--t-radius-button); border: none; cursor: pointer;"
end
defp hero_cta_style(:secondary) do
"border: 2px solid var(--t-border-default); color: var(--t-text-primary); border-radius: var(--t-radius-button); background: transparent; cursor: pointer;"
end
end end

View File

@ -11,13 +11,11 @@
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="contact" mode={:preview} cart_count={2} /> <.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="contact" mode={:preview} cart_count={2} />
<main id="main-content" class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16"> <main id="main-content" class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<h1 class="text-4xl md:text-5xl font-bold mb-6 text-center" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"> <.hero_section
Contact Us variant={:page}
</h1> title="Contact Us"
description="Questions about your order or just want to say hello? Drop us a message and we'll get back to you as soon as we can."
<p class="text-lg mb-12 text-center max-w-2xl mx-auto" style="color: var(--t-text-secondary);"> />
Questions about your order or just want to say hello? Drop us a message and we'll get back to you as soon as we can.
</p>
<div class="grid gap-8 md:grid-cols-2 mb-12"> <div class="grid gap-8 md:grid-cols-2 mb-12">
<!-- Contact Form --> <!-- Contact Form -->

View File

@ -11,34 +11,18 @@
<.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="error" mode={:preview} cart_count={2} /> <.shop_header theme_settings={@theme_settings} logo_image={@logo_image} header_image={@header_image} active_page="error" mode={:preview} cart_count={2} />
<main id="main-content" class="flex items-center justify-center" style="min-height: calc(100vh - 4rem);"> <main id="main-content" class="flex items-center justify-center" style="min-height: calc(100vh - 4rem);">
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-16 text-center"> <div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-16">
<h1 class="text-8xl md:text-9xl font-bold mb-4" style="font-family: var(--t-font-heading); color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); font-weight: var(--t-heading-weight);"> <.hero_section
404 variant={:error}
</h1> pre_title="404"
title="Page Not Found"
<h2 class="text-3xl md:text-4xl font-bold mb-6" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight); letter-spacing: var(--t-heading-tracking);"> description="Sorry, we couldn't find the page you're looking for. Perhaps you've mistyped the URL or the page has been moved."
Page Not Found cta_text="Go to Homepage"
</h2> cta_page="home"
secondary_cta_text="Browse Products"
<p class="text-lg mb-8 max-w-md mx-auto" style="color: var(--t-text-secondary);"> secondary_cta_page="collection"
Sorry, we couldn't find the page you're looking for. Perhaps you've mistyped the URL or the page has been moved. mode={:preview}
</p> />
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<button
class="px-8 py-3 font-semibold transition-all"
style="background-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); color: var(--t-text-inverse); border-radius: var(--t-radius-button);"
>
Go to Homepage
</button>
<button
class="px-8 py-3 font-semibold transition-all"
style="border: 2px solid var(--t-border-default); color: var(--t-text-primary); border-radius: var(--t-radius-button);"
>
Browse Products
</button>
</div>
<.product_grid columns={:fixed_4} gap="gap-4" class="mt-12 max-w-xl mx-auto"> <.product_grid columns={:fixed_4} gap="gap-4" class="mt-12 max-w-xl mx-auto">
<%= for product <- Enum.take(@preview_data.products, 4) do %> <%= for product <- Enum.take(@preview_data.products, 4) do %>