# Stencil

[![CI](https://codeberg.org/w0u7/stencil/actions/workflows/ci.yml/badge.svg)](https://codeberg.org/w0u7/stencil/actions?workflow=ci.yml)
[![Version](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fcodeberg.org%2Fapi%2Fv1%2Frepos%2Fw0u7%2Fstencil%2Ftags&query=%24%5B0%5D.name&label=version)](https://codeberg.org/w0u7/stencil/tags)

Compile-time SVG inliner for Phoenix components.

Stencil reads SVG files from a directory at compile time and embeds them into a
Phoenix component you can call from HEEx. The running app performs no disk I/O
because the raw SVG bytes are baked into the module's beam file. New or changed
files re-compile automatically when an SVG changes on disk.

This is a port of [Lucky Framework's
implementation](https://luckyframework.org/guides/frontend/rendering-html#adding-attributes-to-inline-svgs).

## Installation

Add `stencil` to your `mix.exs`:

```elixir
def deps do
  [
    {:stencil, "~> 0.1.0"}
  ]
end
```

## Usage

Put your SVGs under `priv/svg/` in your app, then create a thin wrapper module
that uses `Stencil.Component`. The conventional location is
`lib/my_app_web/components/inline_svg.ex`:

```elixir
defmodule MyAppWeb.InlineSVG do
  use Stencil.Component
end
```

To make `<.inline_svg>` available in every HEEx template, import the wrapper
inside `MyAppWeb`'s `html_helpers` (in `lib/my_app_web.ex`) alongside
`MyAppWeb.CoreComponents`:

```elixir
defp html_helpers do
  quote do
    # ...
    import MyAppWeb.CoreComponents
    import MyAppWeb.InlineSVG
    # ...
  end
end
```

Now call it from any controller view, LiveView, or component template:

```heex
<.inline_svg path="check" />
<.inline_svg path="arrow-right" class="icon" />
<.inline_svg path="icons/star" />
<.inline_svg path="logo" strip_styling={false} />
```

The `path` attribute is the file name (or nested path) under `priv/svg`,
without the `.svg` extension.

## Options

```elixir
use Stencil.Component, dir: "priv/icons"
```

- `:dir` — directory to scan, relative to the project root (or an absolute
  path). Defaults to `"priv/svg"`.

## Styling

By default, Stencil strips the SVG's own `class`, `fill`, `stroke`,
`stroke-width`, and `style` attributes so page CSS controls the visual. Any
attributes you pass to `<.inline_svg>` are forwarded onto the wrapping `<svg>`
tag:

```heex
<.inline_svg path="check" class="w-6 h-6 text-emerald-500" />
```

Pass `strip_styling={false}` to keep the file's original attributes:

```heex
<.inline_svg path="logo" strip_styling={false} />
```

Stencil also adds a marker attribute so you can target inlined SVGs from CSS:

- `data-inline-svg="<path>"` when styling is stripped
- `data-inline-svg-styled="<path>"` when it is kept

## How it works

`use Stencil.Component` walks the configured directory at macro-expansion time
and generates one function head per SVG:

```elixir
defp stencil_raw_svg("check", true), do: "<svg …>…</svg>"
```

Each file is registered as an `@external_resource`, so the module re-compiles
when any SVG changes. A `__mix_recompile__?/0` callback also detects added or
removed files.

## License

MIT.
