refactor: add themed form components for consistent shop styling

Adds reusable Phoenix components (shop_input, shop_textarea, shop_select,
shop_button, shop_card) backed by semantic CSS classes (.themed-input,
.themed-button, etc.) to eliminate repeated inline styles across templates.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jamey Greenwood 2026-01-25 19:09:49 +00:00
parent 1b12dc3e7f
commit 7d5896a1e3
7 changed files with 380 additions and 178 deletions

View File

@ -240,6 +240,57 @@
color: var(--t-text-inverse); color: var(--t-text-inverse);
} }
} }
/* Themed Form Controls */
& .themed-input {
background-color: var(--t-surface-base);
color: var(--t-text-primary);
border: 1px solid var(--t-border-default);
border-radius: var(--t-radius-input);
&:focus {
outline: none;
border-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
}
&::placeholder {
color: var(--t-text-tertiary);
}
}
& .themed-select {
background-color: var(--t-surface-raised);
color: var(--t-text-primary);
border: 1px solid var(--t-border-default);
border-radius: var(--t-radius-input);
&:focus {
outline: none;
border-color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));
}
}
& .themed-button {
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;
}
& .themed-button-outline {
background-color: transparent;
color: var(--t-text-primary);
border: 1px solid var(--t-border-default);
border-radius: var(--t-radius-button);
cursor: pointer;
}
& .themed-card {
background-color: var(--t-surface-raised);
border: 1px solid var(--t-border-default);
border-radius: var(--t-radius-card);
}
} }
/* Shop nav display - hidden on mobile, flex on md+ (via Tailwind classes in component) */ /* Shop nav display - hidden on mobile, flex on md+ (via Tailwind classes in component) */

View File

@ -63,7 +63,7 @@ defmodule SimpleshopThemeWeb.Layouts do
</header> </header>
<main class="px-4 py-20 sm:px-6 lg:px-8"> <main class="px-4 py-20 sm:px-6 lg:px-8">
<div class="mx-auto max-w-2xl space-y-4"> <div class="mx-auto max-w-2xl flex flex-col gap-4">
{render_slot(@inner_block)} {render_slot(@inner_block)}
</div> </div>
</main> </main>

View File

@ -17,7 +17,7 @@
<div class="grid gap-8 md:grid-cols-2 mb-12"> <div class="grid gap-8 md:grid-cols-2 mb-12">
<.contact_form email="hello@example.com" /> <.contact_form email="hello@example.com" />
<div class="space-y-6"> <div class="flex flex-col gap-6">
<.order_tracking_card /> <.order_tracking_card />
<.info_card title="Handy to know" items={[ <.info_card title="Handy to know" items={[

View File

@ -53,6 +53,246 @@ defmodule SimpleshopThemeWeb.ShopComponents do
""" """
end end
# =============================================================================
# Themed Form Components
# =============================================================================
# These components wrap common form elements with the themed CSS classes.
# They provide a consistent, theme-aware appearance across shop pages.
@doc """
Renders a themed text input.
This component applies the `.themed-input` CSS class which inherits
colors, borders, and radii from the current theme's CSS variables.
## Attributes
* `type` - Optional. Input type. Defaults to "text".
* `class` - Optional. Additional CSS classes.
* All other attributes are passed through to the input element.
## Examples
<.shop_input type="email" placeholder="your@email.com" />
<.shop_input type="text" name="name" class="flex-1" />
"""
attr :type, :string, default: "text"
attr :class, :string, default: nil
attr :rest, :global, include: ~w(name value placeholder required disabled autocomplete readonly)
def shop_input(assigns) do
~H"""
<input type={@type} class={["themed-input", @class]} {@rest} />
"""
end
@doc """
Renders a themed textarea.
## Attributes
* `class` - Optional. Additional CSS classes.
* All other attributes are passed through to the textarea element.
## Examples
<.shop_textarea placeholder="Your message..." rows="5" />
"""
attr :class, :string, default: nil
attr :rest, :global, include: ~w(name rows placeholder required disabled readonly)
def shop_textarea(assigns) do
~H"""
<textarea class={["themed-input", @class]} {@rest}></textarea>
"""
end
@doc """
Renders a themed select dropdown.
## Attributes
* `class` - Optional. Additional CSS classes.
* `options` - Required. List of options (strings or {value, label} tuples).
* `selected` - Optional. Currently selected value.
* All other attributes are passed through to the select element.
## Examples
<.shop_select options={["Option 1", "Option 2"]} />
<.shop_select options={[{"value", "Label"}]} selected="value" />
"""
attr :class, :string, default: nil
attr :options, :list, required: true
attr :selected, :any, default: nil
attr :rest, :global, include: ~w(name required disabled aria-label)
def shop_select(assigns) do
~H"""
<select class={["themed-select", @class]} {@rest}>
<%= for option <- @options do %>
<%= case option do %>
<% {value, label} -> %>
<option value={value} selected={@selected == value}>{label}</option>
<% label -> %>
<option selected={@selected == label}>{label}</option>
<% end %>
<% end %>
</select>
"""
end
@doc """
Renders a themed primary button (accent color background).
## Attributes
* `class` - Optional. Additional CSS classes.
* `type` - Optional. Button type. Defaults to "button".
* All other attributes are passed through to the button element.
## Slots
* `inner_block` - Required. Button content.
## Examples
<.shop_button>Send Message</.shop_button>
<.shop_button type="submit" class="w-full">Subscribe</.shop_button>
"""
attr :class, :string, default: nil
attr :type, :string, default: "button"
attr :rest, :global, include: ~w(disabled name value phx-click phx-value-page)
slot :inner_block, required: true
def shop_button(assigns) do
~H"""
<button type={@type} class={["themed-button", @class]} {@rest}>
{render_slot(@inner_block)}
</button>
"""
end
@doc """
Renders a themed outline/secondary button.
## Attributes
* `class` - Optional. Additional CSS classes.
* `type` - Optional. Button type. Defaults to "button".
* All other attributes are passed through to the button element.
## Slots
* `inner_block` - Required. Button content.
## Examples
<.shop_button_outline>Continue Shopping</.shop_button_outline>
"""
attr :class, :string, default: nil
attr :type, :string, default: "button"
attr :rest, :global, include: ~w(disabled name value phx-click phx-value-page)
slot :inner_block, required: true
def shop_button_outline(assigns) do
~H"""
<button type={@type} class={["themed-button-outline", @class]} {@rest}>
{render_slot(@inner_block)}
</button>
"""
end
@doc """
Renders a themed link styled as a primary button.
## Attributes
* `href` - Required. Link destination.
* `class` - Optional. Additional CSS classes.
## Slots
* `inner_block` - Required. Link content.
## Examples
<.shop_link_button href="/checkout">Checkout</.shop_link_button>
"""
attr :href, :string, required: true
attr :class, :string, default: nil
slot :inner_block, required: true
def shop_link_button(assigns) do
~H"""
<a href={@href} class={["themed-button", @class]} style="text-decoration: none; display: inline-block;">
{render_slot(@inner_block)}
</a>
"""
end
@doc """
Renders a themed link styled as an outline button.
## Attributes
* `href` - Required. Link destination.
* `class` - Optional. Additional CSS classes.
## Slots
* `inner_block` - Required. Link content.
## Examples
<.shop_link_outline href="/collections/all">Continue Shopping</.shop_link_outline>
"""
attr :href, :string, required: true
attr :class, :string, default: nil
slot :inner_block, required: true
def shop_link_outline(assigns) do
~H"""
<a href={@href} class={["themed-button-outline", @class]} style="text-decoration: none; display: inline-block;">
{render_slot(@inner_block)}
</a>
"""
end
@doc """
Renders a themed card container.
## Attributes
* `class` - Optional. Additional CSS classes.
## Slots
* `inner_block` - Required. Card content.
## Examples
<.shop_card class="p-6">
<h3>Card Title</h3>
<p>Card content...</p>
</.shop_card>
"""
attr :class, :string, default: nil
slot :inner_block, required: true
def shop_card(assigns) do
~H"""
<div class={["themed-card", @class]}>
{render_slot(@inner_block)}
</div>
"""
end
@doc """ @doc """
Renders a mobile bottom navigation bar. Renders a mobile bottom navigation bar.
@ -313,7 +553,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
<h4 class="font-semibold mb-4 text-sm" style="font-family: var(--t-font-heading); color: var(--t-text-primary);"> <h4 class="font-semibold mb-4 text-sm" style="font-family: var(--t-font-heading); color: var(--t-text-primary);">
Shop Shop
</h4> </h4>
<ul class="space-y-2 text-sm"> <ul class="flex flex-col gap-2 text-sm">
<%= if @mode == :preview do %> <%= if @mode == :preview do %>
<li><a href="#" phx-click="change_preview_page" phx-value-page="collection" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary); cursor: pointer;">All products</a></li> <li><a href="#" phx-click="change_preview_page" phx-value-page="collection" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary); cursor: pointer;">All products</a></li>
<li><a href="#" phx-click="change_preview_page" phx-value-page="collection" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary); cursor: pointer;">New arrivals</a></li> <li><a href="#" phx-click="change_preview_page" phx-value-page="collection" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary); cursor: pointer;">New arrivals</a></li>
@ -329,7 +569,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
<h4 class="font-semibold mb-4 text-sm" style="font-family: var(--t-font-heading); color: var(--t-text-primary);"> <h4 class="font-semibold mb-4 text-sm" style="font-family: var(--t-font-heading); color: var(--t-text-primary);">
Help Help
</h4> </h4>
<ul class="space-y-2 text-sm"> <ul class="flex flex-col gap-2 text-sm">
<%= if @mode == :preview do %> <%= if @mode == :preview do %>
<li><a href="#" phx-click="change_preview_page" phx-value-page="about" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary); cursor: pointer;">Delivery</a></li> <li><a href="#" phx-click="change_preview_page" phx-value-page="about" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary); cursor: pointer;">Delivery</a></li>
<li><a href="#" phx-click="change_preview_page" phx-value-page="about" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary); cursor: pointer;">Returns</a></li> <li><a href="#" phx-click="change_preview_page" phx-value-page="about" class="transition-colors hover:opacity-80" style="color: var(--t-text-secondary); cursor: pointer;">Returns</a></li>
@ -1302,16 +1542,11 @@ defmodule SimpleshopThemeWeb.ShopComponents do
""" """
end end
defp hero_cta_classes(:primary), do: "px-8 py-3 font-semibold transition-all" defp hero_cta_classes(:primary), do: "themed-button 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_classes(:secondary), do: "themed-button-outline px-8 py-3 font-semibold transition-all"
defp hero_cta_style(:primary) do 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;" defp hero_cta_style(:secondary), do: ""
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
@doc """ @doc """
Renders a row of category circles for navigation. Renders a row of category circles for navigation.
@ -1601,20 +1836,13 @@ defmodule SimpleshopThemeWeb.ShopComponents do
</button> </button>
<%= for category <- @categories do %> <%= for category <- @categories do %>
<button class={"filter-pill#{if @active_category == category.name, do: " filter-pill-active", else: ""}"}> <button class={"filter-pill#{if @active_category == category.name, do: " filter-pill-active", else: ""}"}>
<%= category.name %> {category.name}
</button> </button>
<% end %> <% end %>
</div> </div>
<!-- Sort Dropdown --> <!-- Sort Dropdown -->
<select <.shop_select options={@sort_options} class="px-4 py-2" />
class="px-4 py-2"
style="background-color: var(--t-surface-raised); color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-input);"
>
<%= for option <- @sort_options do %>
<option><%= option %></option>
<% end %>
</select>
</div> </div>
""" """
end end
@ -1677,84 +1905,57 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def contact_form(assigns) do def contact_form(assigns) do
~H""" ~H"""
<div <.shop_card class="p-8">
class="p-8"
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<h2 class="text-xl font-bold mb-2" style="font-family: var(--t-font-heading); color: var(--t-text-primary);"> <h2 class="text-xl font-bold mb-2" style="font-family: var(--t-font-heading); color: var(--t-text-primary);">
<%= @title %> {@title}
</h2> </h2>
<%= if @email || @response_time do %> <%= if @email || @response_time do %>
<div class="mb-6 text-sm space-y-1" style="color: var(--t-text-secondary);"> <div class="flex flex-col gap-1 mb-6 text-sm" style="color: var(--t-text-secondary);">
<%= if @email do %> <%= if @email do %>
<p>Email me: <a href={"mailto:#{@email}"} style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));"><%= @email %></a></p> <p>Email me: <a href={"mailto:#{@email}"} style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l));">{@email}</a></p>
<% end %> <% end %>
<%= if @response_time do %> <%= if @response_time do %>
<p><%= @response_time %></p> <p>{@response_time}</p>
<% end %> <% end %>
</div> </div>
<% else %> <% else %>
<div class="mb-4"></div> <div class="mb-4"></div>
<% end %> <% end %>
<form class="space-y-4"> <form class="flex flex-col gap-4">
<div> <div>
<label class="block font-medium mb-2" style="color: var(--t-text-primary);"> <label class="block font-medium mb-2" style="color: var(--t-text-primary);">
Name Name
</label> </label>
<input <.shop_input type="text" placeholder="Your name" class="w-full px-4 py-2" />
type="text"
placeholder="Your name"
class="w-full px-4 py-2"
style="background-color: var(--t-surface-base); color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-input);"
/>
</div> </div>
<div> <div>
<label class="block font-medium mb-2" style="color: var(--t-text-primary);"> <label class="block font-medium mb-2" style="color: var(--t-text-primary);">
Email Email
</label> </label>
<input <.shop_input type="email" placeholder="your@email.com" class="w-full px-4 py-2" />
type="email"
placeholder="your@email.com"
class="w-full px-4 py-2"
style="background-color: var(--t-surface-base); color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-input);"
/>
</div> </div>
<div> <div>
<label class="block font-medium mb-2" style="color: var(--t-text-primary);"> <label class="block font-medium mb-2" style="color: var(--t-text-primary);">
Subject Subject
</label> </label>
<input <.shop_input type="text" placeholder="How can I help?" class="w-full px-4 py-2" />
type="text"
placeholder="How can I help?"
class="w-full px-4 py-2"
style="background-color: var(--t-surface-base); color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-input);"
/>
</div> </div>
<div> <div>
<label class="block font-medium mb-2" style="color: var(--t-text-primary);"> <label class="block font-medium mb-2" style="color: var(--t-text-primary);">
Message Message
</label> </label>
<textarea <.shop_textarea rows="5" placeholder="Your message..." class="w-full px-4 py-2" />
rows="5"
placeholder="Your message..."
class="w-full px-4 py-2"
style="background-color: var(--t-surface-base); color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-input);"
></textarea>
</div> </div>
<button <.shop_button type="submit" class="w-full px-6 py-3 font-semibold transition-all">
type="submit"
class="w-full px-6 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);"
>
Send Message Send Message
</button> </.shop_button>
</form> </form>
</div> </.shop_card>
""" """
end end
@ -1767,29 +1968,23 @@ defmodule SimpleshopThemeWeb.ShopComponents do
""" """
def order_tracking_card(assigns) do def order_tracking_card(assigns) do
~H""" ~H"""
<div <.shop_card class="p-6">
class="p-6"
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<h3 class="font-bold mb-3" style="color: var(--t-text-primary);">Track your order</h3> <h3 class="font-bold mb-3" style="color: var(--t-text-primary);">Track your order</h3>
<p class="text-sm mb-3" style="color: var(--t-text-secondary);"> <p class="text-sm mb-3" style="color: var(--t-text-secondary);">
Enter your email and I'll send you a link to check your order status. Enter your email and I'll send you a link to check your order status.
</p> </p>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<input <.shop_input
type="email" type="email"
placeholder="your@email.com" placeholder="your@email.com"
class="flex-1 min-w-0 px-3 py-2 text-sm" class="flex-1 min-w-0 px-3 py-2 text-sm"
style="background-color: var(--t-surface-base); color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-input); min-width: 150px;" style="min-width: 150px;"
/> />
<button <.shop_button class="px-4 py-2 text-sm font-medium whitespace-nowrap">
class="px-4 py-2 text-sm font-medium whitespace-nowrap"
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);"
>
Send Send
</button> </.shop_button>
</div>
</div> </div>
</.shop_card>
""" """
end end
@ -1813,20 +2008,17 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def info_card(assigns) do def info_card(assigns) do
~H""" ~H"""
<div <.shop_card class="p-6">
class="p-6" <h3 class="font-bold mb-3" style="color: var(--t-text-primary);">{@title}</h3>
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);" <ul class="flex flex-col gap-2 text-sm" style="color: var(--t-text-secondary);">
>
<h3 class="font-bold mb-3" style="color: var(--t-text-primary);"><%= @title %></h3>
<ul class="space-y-2 text-sm" style="color: var(--t-text-secondary);">
<%= for item <- @items do %> <%= for item <- @items do %>
<li class="flex items-start gap-2"> <li class="flex items-start gap-2">
<span style="color: var(--t-text-tertiary);"></span> <span style="color: var(--t-text-tertiary);"></span>
<span><strong style="color: var(--t-text-primary);"><%= item.label %>:</strong> <%= item.value %></span> <span><strong style="color: var(--t-text-primary);">{item.label}:</strong> {item.value}</span>
</li> </li>
<% end %> <% end %>
</ul> </ul>
</div> </.shop_card>
""" """
end end
@ -1849,21 +2041,18 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def contact_info_card(assigns) do def contact_info_card(assigns) do
~H""" ~H"""
<div <.shop_card class="p-6">
class="p-6" <h3 class="font-bold mb-3" style="color: var(--t-text-primary);">{@title}</h3>
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<h3 class="font-bold mb-3" style="color: var(--t-text-primary);"><%= @title %></h3>
<a href={"mailto:#{@email}"} class="flex items-center gap-2 mb-2" style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); text-decoration: none;"> <a href={"mailto:#{@email}"} class="flex items-center gap-2 mb-2" style="color: hsl(var(--t-accent-h) var(--t-accent-s) var(--t-accent-l)); text-decoration: none;">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg> </svg>
<%= @email %> {@email}
</a> </a>
<p class="text-sm" style="color: var(--t-text-secondary);"> <p class="text-sm" style="color: var(--t-text-secondary);">
<%= @response_text %> {@response_text}
</p> </p>
</div> </.shop_card>
""" """
end end
@ -1892,25 +2081,21 @@ defmodule SimpleshopThemeWeb.ShopComponents do
~H""" ~H"""
<div> <div>
<h3 class="text-xl font-bold mb-2" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);"> <h3 class="text-xl font-bold mb-2" style="font-family: var(--t-font-heading); color: var(--t-text-primary); font-weight: var(--t-heading-weight);">
<%= @title %> {@title}
</h3> </h3>
<p class="text-sm mb-4" style="color: var(--t-text-secondary);"> <p class="text-sm mb-4" style="color: var(--t-text-secondary);">
<%= @description %> {@description}
</p> </p>
<form class="flex flex-wrap gap-2"> <form class="flex flex-wrap gap-2">
<input <.shop_input
type="email" type="email"
placeholder="your@email.com" placeholder="your@email.com"
class="flex-1 min-w-0 px-4 py-2 text-sm" class="flex-1 min-w-0 px-4 py-2 text-sm"
style="background-color: var(--t-surface-base); color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-input); min-width: 150px;" style="min-width: 150px;"
/> />
<button <.shop_button type="submit" class="px-6 py-2 text-sm font-medium whitespace-nowrap">
type="submit" {@button_text}
class="px-6 py-2 text-sm font-medium whitespace-nowrap" </.shop_button>
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);"
>
<%= @button_text %>
</button>
</form> </form>
</div> </div>
""" """
@ -1918,30 +2103,23 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def newsletter_card(assigns) do def newsletter_card(assigns) do
~H""" ~H"""
<div <.shop_card class="p-6">
class="p-6" <h3 class="font-bold mb-2" style="color: var(--t-text-primary);">{@title}</h3>
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<h3 class="font-bold mb-2" style="color: var(--t-text-primary);"><%= @title %></h3>
<p class="text-sm mb-4" style="color: var(--t-text-secondary);"> <p class="text-sm mb-4" style="color: var(--t-text-secondary);">
<%= @description %> {@description}
</p> </p>
<form class="flex flex-wrap gap-2"> <form class="flex flex-wrap gap-2">
<input <.shop_input
type="email" type="email"
placeholder="your@email.com" placeholder="your@email.com"
class="flex-1 min-w-0 px-3 py-2 text-sm" class="flex-1 min-w-0 px-3 py-2 text-sm"
style="background-color: var(--t-surface-base); color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-input); min-width: 150px;" style="min-width: 150px;"
/> />
<button <.shop_button type="submit" class="px-4 py-2 text-sm font-medium whitespace-nowrap">
type="submit" {@button_text}
class="px-4 py-2 text-sm font-medium whitespace-nowrap" </.shop_button>
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);"
>
<%= @button_text %>
</button>
</form> </form>
</div> </.shop_card>
""" """
end end
@ -1967,26 +2145,23 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def social_links_card(assigns) do def social_links_card(assigns) do
~H""" ~H"""
<div <.shop_card class="p-6">
class="p-6" <h3 class="font-bold mb-4" style="color: var(--t-text-primary);">{@title}</h3>
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<h3 class="font-bold mb-4" style="color: var(--t-text-primary);"><%= @title %></h3>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<%= for link <- @links do %> <%= for link <- @links do %>
<a <a
href={link.url} href={link.url}
class="flex items-center gap-2 px-3 py-2 text-sm transition-all hover:opacity-80" class="themed-button-outline flex items-center gap-2 px-3 py-2 text-sm transition-all hover:opacity-80"
style="background-color: var(--t-surface-base); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-button); color: var(--t-text-primary); text-decoration: none;" style="text-decoration: none;"
> >
<span style="color: var(--t-text-secondary);"> <span style="color: var(--t-text-secondary);">
<.social_icon platform={link.platform} /> <.social_icon platform={link.platform} />
</span> </span>
<span><%= link.label %></span> <span>{link.label}</span>
</a> </a>
<% end %> <% end %>
</div> </div>
</div> </.shop_card>
""" """
end end
@ -2294,10 +2469,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def cart_item(assigns) do def cart_item(assigns) do
~H""" ~H"""
<div <.shop_card class="flex gap-4 p-4">
class="flex gap-4 p-4"
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<div class="w-24 h-24 flex-shrink-0 bg-gray-200 overflow-hidden" style="border-radius: var(--t-radius-image);"> <div class="w-24 h-24 flex-shrink-0 bg-gray-200 overflow-hidden" style="border-radius: var(--t-radius-image);">
<img <img
src={@item.product.image_url} src={@item.product.image_url}
@ -2334,10 +2506,10 @@ defmodule SimpleshopThemeWeb.ShopComponents do
<div class="text-right"> <div class="text-right">
<p class="font-bold text-lg" style="color: var(--t-text-primary);"> <p class="font-bold text-lg" style="color: var(--t-text-primary);">
<%= @currency %><%= @item.product.price / 100 * @item.quantity %> {@currency}{@item.product.price / 100 * @item.quantity}
</p> </p>
</div> </div>
</div> </.shop_card>
""" """
end end
@ -2368,65 +2540,50 @@ defmodule SimpleshopThemeWeb.ShopComponents do
assigns = assign(assigns, :total, total) assigns = assign(assigns, :total, total)
~H""" ~H"""
<div <.shop_card class="p-6 sticky top-4">
class="p-6 sticky top-4"
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<h2 class="text-xl font-bold mb-6" style="font-family: var(--t-font-heading); color: var(--t-text-primary);"> <h2 class="text-xl font-bold mb-6" style="font-family: var(--t-font-heading); color: var(--t-text-primary);">
Order Summary Order Summary
</h2> </h2>
<div class="space-y-3 mb-6"> <div class="flex flex-col gap-3 mb-6">
<div class="flex justify-between"> <div class="flex justify-between">
<span style="color: var(--t-text-secondary);">Subtotal</span> <span style="color: var(--t-text-secondary);">Subtotal</span>
<span style="color: var(--t-text-primary);"> <span style="color: var(--t-text-primary);">
<%= @currency %><%= Float.round(@subtotal / 100, 2) %> {@currency}{Float.round(@subtotal / 100, 2)}
</span> </span>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<span style="color: var(--t-text-secondary);">Delivery</span> <span style="color: var(--t-text-secondary);">Delivery</span>
<span style="color: var(--t-text-primary);"><%= @currency %><%= Float.round(@delivery / 100, 2) %></span> <span style="color: var(--t-text-primary);">{@currency}{Float.round(@delivery / 100, 2)}</span>
</div> </div>
<div class="flex justify-between"> <div class="flex justify-between">
<span style="color: var(--t-text-secondary);">VAT (20%)</span> <span style="color: var(--t-text-secondary);">VAT (20%)</span>
<span style="color: var(--t-text-primary);"><%= @currency %><%= Float.round(@vat / 100, 2) %></span> <span style="color: var(--t-text-primary);">{@currency}{Float.round(@vat / 100, 2)}</span>
</div> </div>
<div class="border-t pt-3" style="border-color: var(--t-border-default);"> <div class="border-t pt-3" style="border-color: var(--t-border-default);">
<div class="flex justify-between text-lg"> <div class="flex justify-between text-lg">
<span class="font-semibold" style="color: var(--t-text-primary);">Total</span> <span class="font-semibold" style="color: var(--t-text-primary);">Total</span>
<span class="font-bold" style="color: var(--t-text-primary);"> <span class="font-bold" style="color: var(--t-text-primary);">
<%= @currency %><%= Float.round(@total / 100, 2) %> {@currency}{Float.round(@total / 100, 2)}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<button <.shop_button class="w-full px-6 py-3 font-semibold transition-all mb-3">
class="w-full px-6 py-3 font-semibold transition-all mb-3"
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);"
>
Checkout Checkout
</button> </.shop_button>
<%= if @mode == :preview do %> <%= if @mode == :preview do %>
<button <.shop_button_outline phx-click="change_preview_page" phx-value-page="collection" class="w-full px-6 py-3 font-semibold transition-all">
phx-click="change_preview_page"
phx-value-page="collection"
class="w-full px-6 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); background: transparent; cursor: pointer;"
>
Continue Shopping Continue Shopping
</button> </.shop_button_outline>
<% else %> <% else %>
<a <.shop_link_outline href="/collections/all" class="block w-full px-6 py-3 font-semibold transition-all text-center">
href="/collections/all"
class="block w-full px-6 py-3 font-semibold transition-all text-center"
style="border: 2px solid var(--t-border-default); color: var(--t-text-primary); border-radius: var(--t-radius-button); text-decoration: none;"
>
Continue Shopping Continue Shopping
</a> </.shop_link_outline>
<% end %> <% end %>
</div> </.shop_card>
""" """
end end
@ -2578,7 +2735,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def trust_badges(assigns) do def trust_badges(assigns) do
~H""" ~H"""
<div class="p-4 space-y-3" style="background-color: var(--t-surface-sunken); border-radius: var(--t-radius-card);"> <div class="flex flex-col gap-3 p-4" style="background-color: var(--t-surface-sunken); border-radius: var(--t-radius-card);">
<%= for item <- @items do %> <%= for item <- @items do %>
<div class="flex items-start gap-3"> <div class="flex items-start gap-3">
<.trust_badge_icon icon={item.icon} /> <.trust_badge_icon icon={item.icon} />
@ -2666,18 +2823,15 @@ defmodule SimpleshopThemeWeb.ShopComponents do
</summary> </summary>
<div class="pb-8"> <div class="pb-8">
<div class="space-y-6"> <div class="flex flex-col gap-6">
<%= for review <- @reviews do %> <%= for review <- @reviews do %>
<.review_card review={review} /> <.review_card review={review} />
<% end %> <% end %>
</div> </div>
<button <.shop_button_outline class="mt-6 px-6 py-2 text-sm font-medium transition-all mx-auto block">
class="mt-6 px-6 py-2 text-sm font-medium transition-all mx-auto block"
style="background-color: transparent; color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-button);"
>
Load more reviews Load more reviews
</button> </.shop_button_outline>
</div> </div>
</details> </details>
""" """
@ -3116,7 +3270,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
<% end %> <% end %>
<.accordion_item title="Shipping & Returns"> <.accordion_item title="Shipping & Returns">
<div class="space-y-3"> <div class="flex flex-col gap-3">
<div> <div>
<p class="font-semibold mb-1" style="color: var(--t-text-primary);">Delivery</p> <p class="font-semibold mb-1" style="color: var(--t-text-primary);">Delivery</p>
<p class="text-sm">Free UK delivery on orders over £40. Standard delivery 3-5 working days. Express delivery available at checkout.</p> <p class="text-sm">Free UK delivery on orders over £40. Standard delivery 3-5 working days. Express delivery available at checkout.</p>
@ -3178,7 +3332,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
~H""" ~H"""
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div class="lg:col-span-2"> <div class="lg:col-span-2">
<div class="space-y-4"> <div class="flex flex-col gap-4">
<%= for item <- @items do %> <%= for item <- @items do %>
<.cart_item item={item} currency={@currency} /> <.cart_item item={item} currency={@currency} />
<% end %> <% end %>

View File

@ -205,16 +205,13 @@ defmodule SimpleshopThemeWeb.ShopLive.Collection do
</nav> </nav>
<form phx-change="sort_changed"> <form phx-change="sort_changed">
<select <.shop_select
name="sort" name="sort"
options={@sort_options}
selected={@current_sort}
class="px-4 py-2 text-sm" class="px-4 py-2 text-sm"
style="background-color: var(--t-surface-raised); color: var(--t-text-primary); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-input);"
aria-label="Sort products" aria-label="Sort products"
> />
<%= for {value, label} <- @sort_options do %>
<option value={value} selected={@current_sort == value}>{label}</option>
<% end %>
</select>
</form> </form>
</div> </div>
""" """

View File

@ -252,7 +252,7 @@
</div> </div>
<!-- Header Image Controls --> <!-- Header Image Controls -->
<div class="mt-3 space-y-3"> <div class="mt-3 flex flex-col gap-3">
<form phx-change="update_setting" phx-value-field="header_zoom"> <form phx-change="update_setting" phx-value-field="header_zoom">
<div class="flex justify-between items-center mb-2"> <div class="flex justify-between items-center mb-2">
<span class="text-xs font-medium text-base-content/70">Zoom</span> <span class="text-xs font-medium text-base-content/70">Zoom</span>

View File

@ -7,7 +7,7 @@ defmodule SimpleshopThemeWeb.UserLive.Login do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}> <Layouts.app flash={@flash} current_scope={@current_scope}>
<div class="mx-auto max-w-sm space-y-4"> <div class="mx-auto max-w-sm flex flex-col gap-4">
<div class="text-center"> <div class="text-center">
<.header> <.header>
<p>Log in</p> <p>Log in</p>