complete admin CSS refactor: delete utilities.css, add layout primitives
- Delete utilities.css (701 lines / 24 KB of Tailwind utility clones) - Add layout.css with admin-stack, admin-row, admin-cluster, admin-grid primitives and gap variants (sm, md, lg, xl) - Add transitions.css import and layout.css import to admin.css entry point - Replace all Tailwind utility classes across 26 admin templates with semantic admin-*/theme-*/page-specific CSS classes - Replace all non-dynamic inline styles with semantic classes - Add ~100 new semantic classes to components.css (analytics, dashboard, order detail, settings, theme editor, generic utilities) - Fix stray text-error → admin-text-error in media.ex - Add missing .truncate definition to admin CSS - Only remaining inline styles are dynamic data values (progress bars, chart dimensions) and one JS.toggle target Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -89,14 +89,11 @@ defmodule BerrypodWeb.Admin.ProductShow do
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.header>
|
||||
<.link
|
||||
navigate={~p"/admin/products"}
|
||||
class="text-sm font-normal text-base-content/60 hover:underline"
|
||||
>
|
||||
<.link navigate={~p"/admin/products"} class="admin-back-link">
|
||||
← Products
|
||||
</.link>
|
||||
<div class="flex items-center gap-3 mt-1">
|
||||
<span class="text-2xl font-bold">{@product.title}</span>
|
||||
<div class="admin-product-header">
|
||||
<span class="admin-product-title">{@product.title}</span>
|
||||
<.visibility_badge visible={@product.visible} />
|
||||
<.status_badge status={@product.status} />
|
||||
</div>
|
||||
@@ -121,22 +118,26 @@ defmodule BerrypodWeb.Admin.ProductShow do
|
||||
</.header>
|
||||
|
||||
<%!-- images + details --%>
|
||||
<div class="grid gap-6 mt-6 lg:grid-cols-3">
|
||||
<div class="lg:col-span-2">
|
||||
<div class="grid grid-cols-3 sm:grid-cols-4 gap-2">
|
||||
<div class="admin-product-grid">
|
||||
<div>
|
||||
<div class="admin-product-image-grid">
|
||||
<div
|
||||
:for={image <- sorted_images(@product)}
|
||||
class="aspect-square rounded bg-base-200 overflow-hidden"
|
||||
class="admin-product-image-tile"
|
||||
>
|
||||
<img
|
||||
src={ProductImage.url(image, 400)}
|
||||
alt={image.alt || @product.title}
|
||||
class="w-full h-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p :if={@product.images == []} class="text-base-content/40 text-sm">No images</p>
|
||||
<p
|
||||
:if={@product.images == []}
|
||||
class="admin-help-text"
|
||||
>
|
||||
No images
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="admin-card">
|
||||
@@ -163,17 +164,19 @@ defmodule BerrypodWeb.Admin.ProductShow do
|
||||
</div>
|
||||
|
||||
<%!-- storefront controls --%>
|
||||
<div class="card bg-base-100 shadow-sm border border-base-200 mt-6">
|
||||
<div class="admin-card admin-card-spaced">
|
||||
<div class="admin-card-body">
|
||||
<h3 class="admin-card-title">Storefront controls</h3>
|
||||
<.form
|
||||
for={@form}
|
||||
phx-submit="save_storefront"
|
||||
phx-change="validate_storefront"
|
||||
class="flex flex-wrap gap-4 items-end"
|
||||
class="admin-filter-row-end"
|
||||
>
|
||||
<label class="w-auto">
|
||||
<span class="text-xs mb-0.5">Visibility</span>
|
||||
<label class="admin-filter-select">
|
||||
<span class="admin-filter-label">
|
||||
Visibility
|
||||
</span>
|
||||
<select
|
||||
name="product[visible]"
|
||||
class="admin-select admin-select-sm"
|
||||
@@ -193,8 +196,8 @@ defmodule BerrypodWeb.Admin.ProductShow do
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="w-auto flex-1 min-w-48">
|
||||
<span class="text-xs mb-0.5">Category</span>
|
||||
<label class="admin-filter-select-wide">
|
||||
<span class="admin-filter-label">Category</span>
|
||||
<input
|
||||
type="text"
|
||||
name="product[category]"
|
||||
@@ -209,7 +212,7 @@ defmodule BerrypodWeb.Admin.ProductShow do
|
||||
</div>
|
||||
|
||||
<%!-- variants --%>
|
||||
<div class="card bg-base-100 shadow-sm border border-base-200 mt-6">
|
||||
<div class="admin-card admin-card-spaced">
|
||||
<div class="admin-card-body">
|
||||
<h3 class="admin-card-title">Variants ({length(@product.variants)})</h3>
|
||||
<.table id="variants" rows={@product.variants}>
|
||||
@@ -225,26 +228,25 @@ defmodule BerrypodWeb.Admin.ProductShow do
|
||||
else: "—"}
|
||||
</:col>
|
||||
<:col :let={variant} label="Available">
|
||||
<.icon
|
||||
<span
|
||||
:if={variant.is_enabled && variant.is_available}
|
||||
name="hero-check-circle-mini"
|
||||
class="size-5 text-green-600"
|
||||
/>
|
||||
<.icon
|
||||
class="admin-icon-positive"
|
||||
>
|
||||
<.icon name="hero-check-circle-mini" class="size-5" />
|
||||
</span>
|
||||
<span
|
||||
:if={!variant.is_enabled || !variant.is_available}
|
||||
name="hero-x-circle-mini"
|
||||
class="size-5 text-base-content/30"
|
||||
/>
|
||||
class="admin-icon-muted"
|
||||
>
|
||||
<.icon name="hero-x-circle-mini" class="size-5" />
|
||||
</span>
|
||||
</:col>
|
||||
</.table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%!-- provider data --%>
|
||||
<div
|
||||
:if={@product.provider_connection}
|
||||
class="card bg-base-100 shadow-sm border border-base-200 mt-6"
|
||||
>
|
||||
<div :if={@product.provider_connection} class="admin-card admin-card-spaced">
|
||||
<div class="admin-card-body">
|
||||
<h3 class="admin-card-title">Provider data</h3>
|
||||
<.list>
|
||||
@@ -255,7 +257,7 @@ defmodule BerrypodWeb.Admin.ProductShow do
|
||||
<:item title="Status">{@product.status}</:item>
|
||||
<:item title="Sync status">{@product.provider_connection.sync_status}</:item>
|
||||
</.list>
|
||||
<div class="mt-4">
|
||||
<div class="admin-section-body">
|
||||
<button phx-click="resync" class="admin-btn admin-btn-outline admin-btn-sm">
|
||||
<.icon name="hero-arrow-path" class="size-4" /> Re-sync
|
||||
</button>
|
||||
@@ -274,47 +276,32 @@ defmodule BerrypodWeb.Admin.ProductShow do
|
||||
end
|
||||
|
||||
defp visibility_badge(assigns) do
|
||||
{bg, text, ring, label} =
|
||||
{color, label} =
|
||||
if assigns.visible do
|
||||
{"bg-green-50", "text-green-700", "ring-green-600/20", "visible"}
|
||||
{"green", "visible"}
|
||||
else
|
||||
{"bg-base-200/50", "text-base-content/60", "ring-base-content/10", "hidden"}
|
||||
{"zinc", "hidden"}
|
||||
end
|
||||
|
||||
assigns = assign(assigns, bg: bg, text: text, ring: ring, label: label)
|
||||
assigns = assign(assigns, color: color, label: label)
|
||||
|
||||
~H"""
|
||||
<span class={[
|
||||
"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ring-1 ring-inset",
|
||||
@bg,
|
||||
@text,
|
||||
@ring
|
||||
]}>
|
||||
{@label}
|
||||
</span>
|
||||
<span class={["admin-status-pill", "admin-status-pill-#{@color}"]}>{@label}</span>
|
||||
"""
|
||||
end
|
||||
|
||||
defp status_badge(assigns) do
|
||||
{bg, text, ring} =
|
||||
color =
|
||||
case assigns.status do
|
||||
"active" -> {"bg-green-50", "text-green-700", "ring-green-600/20"}
|
||||
"draft" -> {"bg-amber-50", "text-amber-700", "ring-amber-600/20"}
|
||||
"archived" -> {"bg-base-200/50", "text-base-content/60", "ring-base-content/10"}
|
||||
_ -> {"bg-base-200/50", "text-base-content/60", "ring-base-content/10"}
|
||||
"active" -> "green"
|
||||
"draft" -> "amber"
|
||||
_ -> "zinc"
|
||||
end
|
||||
|
||||
assigns = assign(assigns, bg: bg, text: text, ring: ring)
|
||||
assigns = assign(assigns, :color, color)
|
||||
|
||||
~H"""
|
||||
<span class={[
|
||||
"inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ring-1 ring-inset",
|
||||
@bg,
|
||||
@text,
|
||||
@ring
|
||||
]}>
|
||||
{@status}
|
||||
</span>
|
||||
<span class={["admin-status-pill", "admin-status-pill-#{@color}"]}>{@status}</span>
|
||||
"""
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user