Pote.Theme (Pote v2.2.0)

Copy Markdown View Source

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}
    }
end

That'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

color_resolver()

@type color_resolver() :: (String.t() -> {:ok, Pote.Theme.Theme.rgb()} | :not_found)

A function that resolves a theme key to an RGB tuple.

Functions

__using__(opts)

(macro)

use Pote.Theme — the entry point for host apps.

Accepts these options:

  • :config_app (required) — the OTP application name whose :theme_active env 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/0

And automatically registers the resolver with Pote.

list_themes(storage_dir)

@spec list_themes(String.t() | nil) :: [String.t()]

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.

load_theme(name, storage_dir)

@spec load_theme(String.t(), String.t() | nil) :: {:ok, map()} | :not_found

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.

lookup(key, opts)

@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).

resolver(opts)

@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.

save_theme(theme, storage_dir)

@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.