A theme system that any host application can opt into.
Pote.Theme is the core contract: it defines a behaviour that
host apps can adopt via use Pote.Theme, ... to get a fully
functional theme system without writing any boilerplate.
Quick start for host apps
defmodule MyApp.Theme do
use Pote.Theme,
config_app: :my_app,
storage_dir: "~/.config/my_app/themes",
defaults: %{
"primary" => {0, 120, 215},
"accent" => {255, 90, 50},
"background" => {24, 24, 28},
"text" => {240, 240, 245}
}
endThat's it — MyApp.Theme now exposes:
MyApp.Theme.list() # => ["default", "dracula", "monokai", ...]
MyApp.Theme.active() # => %Pote.Theme{...} (the active theme)
MyApp.Theme.activate("dracula")
MyApp.Theme.color("primary") # => {0, 120, 215}
MyApp.Theme.colors() # => %{"primary" => {0, 120, 215}, ...}
MyApp.Theme.install!(MyApp.Theme.templates().dracula)Integration with Pote's color parser
When a host app uses use Pote.Theme, the theme resolver is
automatically registered with Pote.put_theme_resolver/1 so
that Pote.parse("theme:primary") (and Pote.parse(:primary))
consult the active theme — no extra wiring needed.
This is the mechanism that makes alaja separator --color "theme:primary",
Flotilla.Components.text("hi", color: "theme:primary") and any
other Pote-aware UI all read from the same active theme.
On-disk format
Each theme is a JSON file under storage_dir:
{
"name": "dracula",
"description": "Dracula colour palette",
"colors": {
"primary": [189, 147, 249],
"accent": [255, 121, 198],
...
}
}Built-in themes
The default install (use Pote.Theme) ships with a small palette
(default, dracula, monokai, nord, light) so the system
is useful out of the box. Use MyApp.Theme.templates/0 to inspect
them and MyApp.Theme.install!/2 to write them to disk.
See Pote.Theme.Templates for the full list.
Summary
Types
A function that resolves a theme key to an RGB tuple.
Functions
use Pote.Theme — the entry point for host apps.
Lists theme names available in storage_dir.
Loads a theme JSON file from storage_dir. Returns {:ok, data}
with the parsed map, or :not_found if the file is missing or
unreadable.
Looks up a theme key in the active theme for config_app.
Returns a built-in resolver function that consults config_app +
storage_dir. This is the function passed to Pote.put_theme_resolver/1.
Writes a Pote.Theme.Theme struct to disk under storage_dir.
Types
@type color_resolver() :: (String.t() -> {:ok, Pote.Theme.Theme.rgb()} | :not_found)
A function that resolves a theme key to an RGB tuple.
Functions
use Pote.Theme — the entry point for host apps.
Accepts these options:
:config_app(required) — the OTP application name whose:theme_activeenv holds the active theme name.:storage_dir(optional) — directory where theme JSONs live. Defaults to"~/.config/<config_app>/themes".:defaults(optional) — map of default theme colours used when no theme is selected or the active theme file is missing.
Generates a module that exposes:
list/0, active/0, activate/1, color/1, colors/0,
install!/1, templates/0And automatically registers the resolver with Pote.
Lists theme names available in storage_dir.
Returns only theme names ("dracula", "monokai", ...) without
the .json extension. Themes without an on-disk file but
available via Pote.Theme.Templates are not included.
Loads a theme JSON file from storage_dir. Returns {:ok, data}
with the parsed map, or :not_found if the file is missing or
unreadable.
@spec lookup( String.t(), keyword() ) :: {:ok, Pote.Theme.Theme.rgb()} | :not_found
Looks up a theme key in the active theme for config_app.
Reads Application.get_env(config_app, :theme_active) and looks
up the corresponding theme file under storage_dir. Returns
{:ok, rgb} on hit, :not_found on miss (key or theme absent).
@spec resolver(keyword()) :: color_resolver()
Returns a built-in resolver function that consults config_app +
storage_dir. This is the function passed to Pote.put_theme_resolver/1.
The storage_dir opt can be either a string (resolved once, at
call time of resolver/1) or a 0-arity function (resolved on every
lookup, so the underlying directory can change between calls). The
generated use Pote.Theme module always passes a function so that
defoverridable storage_dir/0 overrides take effect at lookup
time, not at registration time.
@spec save_theme(Pote.Theme.Theme.t(), String.t() | nil) :: :ok | {:error, term()}
Writes a Pote.Theme.Theme struct to disk under storage_dir.
Creates storage_dir if it does not exist. Returns :ok on
success or {:error, reason} on failure.