Packages

phoenix_kit

2.8.0
2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.0 2.0.1 2.0.0 1.7.236 1.7.235 1.7.234 1.7.233 1.7.232 1.7.231 1.7.230 1.7.229 1.7.228 1.7.227 1.7.226 1.7.225 1.7.224 1.7.223 1.7.222 1.7.221 1.7.220 1.7.219 1.7.218 1.7.217 1.7.216 1.7.215 1.7.214 1.7.213 1.7.212 1.7.211 1.7.210 1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib phoenix_kit_web components theme_bootstrap.ex
Raw

lib/phoenix_kit_web/components/theme_bootstrap.ex

defmodule PhoenixKitWeb.Components.ThemeBootstrap do
@moduledoc """
The pre-paint theme setup — palettes plus the stamp script — renders in
`<head>`, before (or with) the stylesheets.
Every page class had its own first-paint story and most of them flashed:
the dashboard's `<html>` carried no `data-theme` and applied the saved one
from a script at the end of `<body>`; the standalone admin hardcoded
`data-theme="light"` and fixed it up on DOMContentLoaded; the kit's own
root layout hardcoded light and ran no theme JS at all. A dark-mode user
got a white flash on the first two and permanent light on the third.
This component is the one shared answer: the custom-theme `<style>`
(phoenix-* plus host `:theme_definitions`) plus a synchronous script that
reads the saved choice, resolves `"system"` from `ThemeConfig.system_pair/0`
— the CONFIGURED pair, not hardcoded `phoenix-*` names — and stamps
`data-theme` plus `color-scheme` on `<html>` before the first paint. The
palettes travel with the stamp: a script-only bootstrap on a host layout
(or the standalone admin shell) used to paint `phoenix-dark` +
`color-scheme: dark` over daisyUI's light variables until a later `<style>`
in the body arrived.
It also subscribes to the `storage` event, so a theme picked in one tab
follows into the others.
Host root layouts can render it too (`PhoenixKitWeb.Components.ThemeBootstrap`
is public API); a host with its own equivalent script loses nothing by
keeping it.
"""
use Phoenix.Component
alias PhoenixKit.ThemeConfig
@doc """
The blocking head setup (palettes + stamp script). Render in `<head>`.
"""
def theme_bootstrap(assigns) do
{light, dark} = ThemeConfig.system_pair()
assigns =
assigns
|> assign(:light, light)
|> assign(:dark, dark)
|> assign(:custom_css, ThemeConfig.custom_theme_css())
|> assign(
:base_map_json,
# :html_safe escapes < and > so no config-derived name can close
# this <script> tag. Names are validated upstream too; this is the
# sink-side half of that defense.
Jason.encode!(ThemeConfig.base_map(), escape: :html_safe)
)
~H"""
<style data-phoenix-kit-themes>
<%= Phoenix.HTML.raw(@custom_css) %>
</style>
<script>
(function () {
// One instance per page — a host layout and a kit layout can
// both render this; a second copy would double the storage listener.
if (window.__pkThemeBootstrap) return;
window.__pkThemeBootstrap = true;
var KEY = 'phx:theme';
var LEGACY_KEY = 'phoenix_kit_theme';
var LIGHT = '<%= @light %>';
var DARK = '<%= @dark %>';
var BASES = <%= Phoenix.HTML.raw(@base_map_json) %>;
function resolve(theme) {
if (!theme || theme === 'system') {
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? DARK
: LIGHT;
}
return theme;
}
function apply(theme) {
var resolved = resolve(theme);
var el = document.documentElement;
el.setAttribute('data-theme', resolved);
el.style.colorScheme = BASES[resolved] || 'light';
}
var saved = null;
try {
saved = localStorage.getItem(KEY);
// The deleted static phoenix_kit_themes.js wrote this older key.
// Promote it once so an upgrade does not reset the saved choice.
if (!saved) {
var legacy = localStorage.getItem(LEGACY_KEY);
if (legacy) {
saved = legacy;
localStorage.setItem(KEY, legacy);
}
}
} catch (e) {
/* storage blocked: fall through to system */
}
apply(saved);
// A theme picked in another tab follows into this one.
window.addEventListener('storage', function (e) {
if (e.key === KEY || e.key === LEGACY_KEY) apply(e.newValue);
});
})();
</script>
"""
end
end