fix: add data attributes and Google Fonts to enable theme visual changes

- Add Google Fonts link to root layout for typography presets
- Add data-mood, data-typography, data-shape, data-density attributes to preview-frame
- Create theme-layer2-attributes.css with attribute-based CSS from demo
- Move theme CSS files from priv/static/css to assets/css for proper compilation
- Update CSS import order (primitives → layer2 → semantic)
- Add 'css' to static_paths to serve theme CSS files

This fixes the issue where theme controls updated the database but didn't
visually affect the preview. The demo's attribute-based CSS system is now
properly integrated with the Phoenix LiveView implementation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-31 00:24:53 +00:00
parent 6a3069f854
commit 476ec9667a
9 changed files with 470 additions and 34 deletions

View File

@@ -17,7 +17,7 @@ defmodule SimpleshopThemeWeb do
those modules here.
"""
def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)
def static_paths, do: ~w(assets css fonts images favicon.ico robots.txt)
def router do
quote do

View File

@@ -8,6 +8,9 @@
{assigns[:page_title]}
</.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/css/app.css"} />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&family=Libre+Baskerville:wght@400;700&family=Nunito:wght@400;600;700&family=Nunito+Sans:opsz,wght@6..12,300;6..12,400;6..12,500;6..12,600&family=Outfit:wght@300;400;500;600&family=Source+Sans+3:wght@400;500;600&family=Space+Grotesk:wght@400;500;600&display=swap" rel="stylesheet">
<script defer phx-track-static type="text/javascript" src={~p"/assets/js/app.js"}>
</script>
<script>

View File

@@ -53,6 +53,76 @@ defmodule SimpleshopThemeWeb.ThemeLive.Index do
{:noreply, assign(socket, :preview_page, page_atom)}
end
@impl true
def handle_event("update_setting", %{"field" => field, "setting_value" => value}, socket) do
field_atom = String.to_existing_atom(field)
attrs = %{field_atom => value}
case Settings.update_theme_settings(attrs) do
{:ok, theme_settings} ->
generated_css = CSSGenerator.generate(theme_settings)
socket =
socket
|> assign(:theme_settings, theme_settings)
|> assign(:generated_css, generated_css)
{:noreply, socket}
{:error, _} ->
{:noreply, socket}
end
end
@impl true
def handle_event("update_setting", %{"field" => field} = params, socket) do
# For phx-change events from select/input elements, the value comes from the name attribute
value = params[field] || params["#{field}_text"] || params["value"]
if value do
field_atom = String.to_existing_atom(field)
attrs = %{field_atom => value}
case Settings.update_theme_settings(attrs) do
{:ok, theme_settings} ->
generated_css = CSSGenerator.generate(theme_settings)
socket =
socket
|> assign(:theme_settings, theme_settings)
|> assign(:generated_css, generated_css)
{:noreply, socket}
{:error, _} ->
{:noreply, socket}
end
else
{:noreply, socket}
end
end
@impl true
def handle_event("update_color", %{"field" => field, "value" => value}, socket) do
field_atom = String.to_existing_atom(field)
attrs = %{field_atom => value}
case Settings.update_theme_settings(attrs) do
{:ok, theme_settings} ->
generated_css = CSSGenerator.generate(theme_settings)
socket =
socket
|> assign(:theme_settings, theme_settings)
|> assign(:generated_css, generated_css)
{:noreply, socket}
{:error, _} ->
{:noreply, socket}
end
end
@impl true
def handle_event("save_theme", _params, socket) do
socket = put_flash(socket, :info, "Theme saved successfully")

View File

@@ -35,38 +35,137 @@
</div>
</div>
<!-- Current Settings Display -->
<!-- Customization Controls -->
<div class="divider"></div>
<!-- Mood -->
<div>
<h2 class="text-lg font-semibold mb-4">Current Settings</h2>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="font-medium">Mood:</span>
<span class="capitalize"><%= @theme_settings.mood %></span>
</div>
<div class="flex justify-between">
<span class="font-medium">Typography:</span>
<span class="capitalize"><%= @theme_settings.typography %></span>
</div>
<div class="flex justify-between">
<span class="font-medium">Shape:</span>
<span class="capitalize"><%= @theme_settings.shape %></span>
</div>
<div class="flex justify-between">
<span class="font-medium">Density:</span>
<span class="capitalize"><%= @theme_settings.density %></span>
</div>
<div class="flex justify-between">
<span class="font-medium">Accent Color:</span>
<span>
<span
class="inline-block w-4 h-4 rounded-sm border border-base-300"
style={"background-color: #{@theme_settings.accent_color}"}
>
</span>
<%= @theme_settings.accent_color %>
</span>
</div>
<h3 class="font-semibold mb-3">Mood</h3>
<div class="grid grid-cols-2 gap-2">
<%= for mood <- ["neutral", "warm", "cool", "dark"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="mood"
phx-value-setting_value={mood}
class={"btn btn-sm #{if @theme_settings.mood == mood, do: "btn-primary", else: "btn-outline"} capitalize"}
>
<%= mood %>
</button>
<% end %>
</div>
</div>
<!-- Typography -->
<div>
<h3 class="font-semibold mb-3">Typography</h3>
<form phx-change="update_setting" phx-value-field="typography">
<select
name="typography"
class="select select-bordered select-sm w-full capitalize"
>
<%= for typo <- ["clean", "editorial", "modern", "classic", "friendly", "minimal", "impulse"] do %>
<option value={typo} selected={@theme_settings.typography == typo}>
<%= typo %>
</option>
<% end %>
</select>
</form>
</div>
<!-- Shape -->
<div>
<h3 class="font-semibold mb-3">Shape</h3>
<div class="grid grid-cols-2 gap-2">
<%= for shape <- ["sharp", "soft", "round", "pill"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="shape"
phx-value-setting_value={shape}
class={"btn btn-sm #{if @theme_settings.shape == shape, do: "btn-primary", else: "btn-outline"} capitalize"}
>
<%= shape %>
</button>
<% end %>
</div>
</div>
<!-- Density -->
<div>
<h3 class="font-semibold mb-3">Density</h3>
<div class="grid grid-cols-3 gap-2">
<%= for density <- ["spacious", "balanced", "compact"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="density"
phx-value-setting_value={density}
class={"btn btn-sm #{if @theme_settings.density == density, do: "btn-primary", else: "btn-outline"} capitalize text-xs"}
>
<%= density %>
</button>
<% end %>
</div>
</div>
<!-- Grid Columns -->
<div>
<h3 class="font-semibold mb-3">Grid Columns</h3>
<div class="grid grid-cols-3 gap-2">
<%= for cols <- ["2", "3", "4"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="grid_columns"
phx-value-setting_value={cols}
class={"btn btn-sm #{if @theme_settings.grid_columns == cols, do: "btn-primary", else: "btn-outline"}"}
>
<%= cols %>
</button>
<% end %>
</div>
</div>
<!-- Colors -->
<div>
<h3 class="font-semibold mb-3">Accent Color</h3>
<div class="flex gap-2 items-center">
<input
type="color"
phx-change="update_color"
phx-value-field="accent_color"
value={@theme_settings.accent_color}
class="w-12 h-10 rounded cursor-pointer"
phx-debounce="300"
/>
<input
type="text"
phx-change="update_color"
phx-value-field="accent_color"
value={@theme_settings.accent_color}
class="input input-bordered input-sm flex-1 font-mono text-xs"
placeholder="#000000"
phx-debounce="blur"
/>
</div>
</div>
<!-- Header Layout -->
<div>
<h3 class="font-semibold mb-3">Header Layout</h3>
<div class="grid grid-cols-3 gap-2">
<%= for layout <- ["standard", "centered", "minimal"] do %>
<button
type="button"
phx-click="update_setting"
phx-value-field="header_layout"
phx-value-setting_value={layout}
class={"btn btn-sm #{if @theme_settings.header_layout == layout, do: "btn-primary", else: "btn-outline"} capitalize text-xs"}
>
<%= layout %>
</button>
<% end %>
</div>
</div>
</div>
@@ -103,7 +202,14 @@
</div>
<!-- Preview Frame -->
<div class="preview-frame bg-white overflow-auto" style="min-height: 600px; max-height: calc(100vh - 200px);">
<div class="preview-frame bg-white overflow-auto"
data-mood={@theme_settings.mood}
data-typography={@theme_settings.typography}
data-shape={@theme_settings.shape}
data-density={@theme_settings.density}
data-grid={@theme_settings.grid_columns}
data-header={@theme_settings.header_layout}
style="min-height: 600px; max-height: calc(100vh - 200px);">
<style>
<%= Phoenix.HTML.raw(@generated_css) %>
</style>