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);
}
}
/* 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) */

View File

@ -63,7 +63,7 @@ defmodule SimpleshopThemeWeb.Layouts do
</header>
<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)}
</div>
</main>

View File

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

View File

@ -53,6 +53,246 @@ defmodule SimpleshopThemeWeb.ShopComponents do
"""
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 """
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);">
Shop
</h4>
<ul class="space-y-2 text-sm">
<ul class="flex flex-col gap-2 text-sm">
<%= 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;">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);">
Help
</h4>
<ul class="space-y-2 text-sm">
<ul class="flex flex-col gap-2 text-sm">
<%= 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;">Returns</a></li>
@ -1302,16 +1542,11 @@ defmodule SimpleshopThemeWeb.ShopComponents do
"""
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_classes(:primary), do: "themed-button 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
"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
defp hero_cta_style(:primary), do: ""
defp hero_cta_style(:secondary), do: ""
@doc """
Renders a row of category circles for navigation.
@ -1601,20 +1836,13 @@ defmodule SimpleshopThemeWeb.ShopComponents do
</button>
<%= for category <- @categories do %>
<button class={"filter-pill#{if @active_category == category.name, do: " filter-pill-active", else: ""}"}>
<%= category.name %>
{category.name}
</button>
<% end %>
</div>
<!-- Sort Dropdown -->
<select
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>
<.shop_select options={@sort_options} class="px-4 py-2" />
</div>
"""
end
@ -1677,84 +1905,57 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def contact_form(assigns) do
~H"""
<div
class="p-8"
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<.shop_card class="p-8">
<h2 class="text-xl font-bold mb-2" style="font-family: var(--t-font-heading); color: var(--t-text-primary);">
<%= @title %>
{@title}
</h2>
<%= 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 %>
<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 %>
<%= if @response_time do %>
<p><%= @response_time %></p>
<p>{@response_time}</p>
<% end %>
</div>
<% else %>
<div class="mb-4"></div>
<% end %>
<form class="space-y-4">
<form class="flex flex-col gap-4">
<div>
<label class="block font-medium mb-2" style="color: var(--t-text-primary);">
Name
</label>
<input
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);"
/>
<.shop_input type="text" placeholder="Your name" class="w-full px-4 py-2" />
</div>
<div>
<label class="block font-medium mb-2" style="color: var(--t-text-primary);">
Email
</label>
<input
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);"
/>
<.shop_input type="email" placeholder="your@email.com" class="w-full px-4 py-2" />
</div>
<div>
<label class="block font-medium mb-2" style="color: var(--t-text-primary);">
Subject
</label>
<input
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);"
/>
<.shop_input type="text" placeholder="How can I help?" class="w-full px-4 py-2" />
</div>
<div>
<label class="block font-medium mb-2" style="color: var(--t-text-primary);">
Message
</label>
<textarea
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>
<.shop_textarea rows="5" placeholder="Your message..." class="w-full px-4 py-2" />
</div>
<button
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);"
>
<.shop_button type="submit" class="w-full px-6 py-3 font-semibold transition-all">
Send Message
</button>
</.shop_button>
</form>
</div>
</.shop_card>
"""
end
@ -1767,29 +1968,23 @@ defmodule SimpleshopThemeWeb.ShopComponents do
"""
def order_tracking_card(assigns) do
~H"""
<div
class="p-6"
style="background-color: var(--t-surface-raised); border: 1px solid var(--t-border-default); border-radius: var(--t-radius-card);"
>
<.shop_card class="p-6">
<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);">
Enter your email and I'll send you a link to check your order status.
</p>
<div class="flex flex-wrap gap-2">
<input
<.shop_input
type="email"
placeholder="your@email.com"
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
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);"
>
<.shop_button class="px-4 py-2 text-sm font-medium whitespace-nowrap">
Send
</button>
</.shop_button>
</div>
</div>
</.shop_card>
"""
end
@ -1813,20 +2008,17 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def info_card(assigns) do
~H"""
<div
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);"><%= @title %></h3>
<ul class="space-y-2 text-sm" style="color: var(--t-text-secondary);">
<.shop_card class="p-6">
<h3 class="font-bold mb-3" style="color: var(--t-text-primary);">{@title}</h3>
<ul class="flex flex-col gap-2 text-sm" style="color: var(--t-text-secondary);">
<%= for item <- @items do %>
<li class="flex items-start gap-2">
<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>
<% end %>
</ul>
</div>
</.shop_card>
"""
end
@ -1849,21 +2041,18 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def contact_info_card(assigns) do
~H"""
<div
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);"><%= @title %></h3>
<.shop_card class="p-6">
<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;">
<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" />
</svg>
<%= @email %>
{@email}
</a>
<p class="text-sm" style="color: var(--t-text-secondary);">
<%= @response_text %>
{@response_text}
</p>
</div>
</.shop_card>
"""
end
@ -1892,25 +2081,21 @@ defmodule SimpleshopThemeWeb.ShopComponents do
~H"""
<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);">
<%= @title %>
{@title}
</h3>
<p class="text-sm mb-4" style="color: var(--t-text-secondary);">
<%= @description %>
{@description}
</p>
<form class="flex flex-wrap gap-2">
<input
<.shop_input
type="email"
placeholder="your@email.com"
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
type="submit"
class="px-6 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);"
>
<%= @button_text %>
</button>
<.shop_button type="submit" class="px-6 py-2 text-sm font-medium whitespace-nowrap">
{@button_text}
</.shop_button>
</form>
</div>
"""
@ -1918,30 +2103,23 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def newsletter_card(assigns) do
~H"""
<div
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-2" style="color: var(--t-text-primary);"><%= @title %></h3>
<.shop_card class="p-6">
<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);">
<%= @description %>
{@description}
</p>
<form class="flex flex-wrap gap-2">
<input
<.shop_input
type="email"
placeholder="your@email.com"
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
type="submit"
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);"
>
<%= @button_text %>
</button>
<.shop_button type="submit" class="px-4 py-2 text-sm font-medium whitespace-nowrap">
{@button_text}
</.shop_button>
</form>
</div>
</.shop_card>
"""
end
@ -1967,26 +2145,23 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def social_links_card(assigns) do
~H"""
<div
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-4" style="color: var(--t-text-primary);"><%= @title %></h3>
<.shop_card class="p-6">
<h3 class="font-bold mb-4" style="color: var(--t-text-primary);">{@title}</h3>
<div class="flex flex-wrap gap-2">
<%= for link <- @links do %>
<a
href={link.url}
class="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;"
class="themed-button-outline flex items-center gap-2 px-3 py-2 text-sm transition-all hover:opacity-80"
style="text-decoration: none;"
>
<span style="color: var(--t-text-secondary);">
<.social_icon platform={link.platform} />
</span>
<span><%= link.label %></span>
<span>{link.label}</span>
</a>
<% end %>
</div>
</div>
</.shop_card>
"""
end
@ -2294,10 +2469,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def cart_item(assigns) do
~H"""
<div
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);"
>
<.shop_card class="flex gap-4 p-4">
<div class="w-24 h-24 flex-shrink-0 bg-gray-200 overflow-hidden" style="border-radius: var(--t-radius-image);">
<img
src={@item.product.image_url}
@ -2334,10 +2506,10 @@ defmodule SimpleshopThemeWeb.ShopComponents do
<div class="text-right">
<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>
</div>
</div>
</.shop_card>
"""
end
@ -2368,65 +2540,50 @@ defmodule SimpleshopThemeWeb.ShopComponents do
assigns = assign(assigns, :total, total)
~H"""
<div
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);"
>
<.shop_card class="p-6 sticky top-4">
<h2 class="text-xl font-bold mb-6" style="font-family: var(--t-font-heading); color: var(--t-text-primary);">
Order Summary
</h2>
<div class="space-y-3 mb-6">
<div class="flex flex-col gap-3 mb-6">
<div class="flex justify-between">
<span style="color: var(--t-text-secondary);">Subtotal</span>
<span style="color: var(--t-text-primary);">
<%= @currency %><%= Float.round(@subtotal / 100, 2) %>
{@currency}{Float.round(@subtotal / 100, 2)}
</span>
</div>
<div class="flex justify-between">
<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 class="flex justify-between">
<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 class="border-t pt-3" style="border-color: var(--t-border-default);">
<div class="flex justify-between text-lg">
<span class="font-semibold" style="color: var(--t-text-primary);">Total</span>
<span class="font-bold" style="color: var(--t-text-primary);">
<%= @currency %><%= Float.round(@total / 100, 2) %>
{@currency}{Float.round(@total / 100, 2)}
</span>
</div>
</div>
</div>
<button
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);"
>
<.shop_button class="w-full px-6 py-3 font-semibold transition-all mb-3">
Checkout
</button>
</.shop_button>
<%= if @mode == :preview do %>
<button
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;"
>
<.shop_button_outline phx-click="change_preview_page" phx-value-page="collection" class="w-full px-6 py-3 font-semibold transition-all">
Continue Shopping
</button>
</.shop_button_outline>
<% else %>
<a
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;"
>
<.shop_link_outline href="/collections/all" class="block w-full px-6 py-3 font-semibold transition-all text-center">
Continue Shopping
</a>
</.shop_link_outline>
<% end %>
</div>
</.shop_card>
"""
end
@ -2578,7 +2735,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
def trust_badges(assigns) do
~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 %>
<div class="flex items-start gap-3">
<.trust_badge_icon icon={item.icon} />
@ -2666,18 +2823,15 @@ defmodule SimpleshopThemeWeb.ShopComponents do
</summary>
<div class="pb-8">
<div class="space-y-6">
<div class="flex flex-col gap-6">
<%= for review <- @reviews do %>
<.review_card review={review} />
<% end %>
</div>
<button
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);"
>
<.shop_button_outline class="mt-6 px-6 py-2 text-sm font-medium transition-all mx-auto block">
Load more reviews
</button>
</.shop_button_outline>
</div>
</details>
"""
@ -3116,7 +3270,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
<% end %>
<.accordion_item title="Shipping & Returns">
<div class="space-y-3">
<div class="flex flex-col gap-3">
<div>
<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>
@ -3178,7 +3332,7 @@ defmodule SimpleshopThemeWeb.ShopComponents do
~H"""
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<div class="lg:col-span-2">
<div class="space-y-4">
<div class="flex flex-col gap-4">
<%= for item <- @items do %>
<.cart_item item={item} currency={@currency} />
<% end %>

View File

@ -205,16 +205,13 @@ defmodule SimpleshopThemeWeb.ShopLive.Collection do
</nav>
<form phx-change="sort_changed">
<select
<.shop_select
name="sort"
options={@sort_options}
selected={@current_sort}
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"
>
<%= for {value, label} <- @sort_options do %>
<option value={value} selected={@current_sort == value}>{label}</option>
<% end %>
</select>
/>
</form>
</div>
"""

View File

@ -252,7 +252,7 @@
</div>
<!-- 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">
<div class="flex justify-between items-center mb-2">
<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
~H"""
<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">
<.header>
<p>Log in</p>