# MaterialSymbols

Phoenix components for Google
[Material Symbols](https://fonts.google.com/icons) with a few conveniences and an Apache-2.0 license.

## Why this package

- Uses the current [**Material Symbols**](https://fonts.google.com/icons) set: 3,945 icons in outlined, rounded,
  and sharp styles, with filled variants. The package contains all three
  styles; the default build compiles **outlined only**. Material Symbols is the
  current set, introduced in April 2022; Google describes the legacy Material
  Icons set as no longer updated.
- Ships under **Apache-2.0** and contains no AGPL-3.0 code. The existing Elixir
  [`material_icons`](https://hex.pm/packages/material_icons) package is AGPL-3.0, which carries copyleft and network-use
  obligations.
- Vendors the SVGs at release time. There is no HTTP client and no icon fetch
  during `mix deps.get` or compilation, so CI needs no extra network step.
- Adds no runtime dependencies beyond Phoenix LiveView and `phoenix_html`.

## Installation

Add [`material_symbols`](https://hex.pm/packages/material_symbols) to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:material_symbols, "~> 1.0.0"}
  ]
end
```

## Requirements

- Elixir `~> 1.15`
- Phoenix LiveView `~> 1.0`
- `phoenix_html ~> 4.0`

## Usage

```heex
<MaterialSymbols.home />
<MaterialSymbols.home style="outlined" />
<MaterialSymbols.home fill />
<MaterialSymbols.home size={32} class="text-blue-600" />
<MaterialSymbols.render name="home" style="outlined" />
<MaterialSymbols.icon name={@icon} size={20} class="size-5" />
```

`style` selects the icon style and must be one of the compiled styles. `fill`
selects the filled variant. The SVG uses `fill="currentColor"`, so set the
color with `class` or with the surrounding CSS. The `style` HTML attribute is
reserved for the icon style; use `class` for styling.

`fill` is a boolean, `size` is a positive integer, and `style` is a string.
Use `fill` and `size={32}`; string values such as `fill="true"` and
`size="32"` raise, and so does an atom style such as `style={:rounded}`.
Explicit `width`, `height`, or `viewBox` attributes override the generated
defaults.

Per-icon components pass arbitrary attributes to the `<svg>` element.
`render/1` and `icon/1` accept global attributes such as `aria-*`, `data-*`,
`id`, and `role`, plus `width`, `height`, `viewBox`, `xmlns`,
`preserveAspectRatio`, and `focusable`. The default size is 24 pixels; `size`
sets both `width` and `height`.

The `<svg>` defaults to `aria-hidden="true"`. For an icon that carries meaning
on its own, override it:

```heex
<MaterialSymbols.home aria-hidden="false" aria-label="Home" role="img" />
```

### Dynamic names

Both `MaterialSymbols.render/1` and `MaterialSymbols.icon/1` take a `name` plus
the same style, fill, size, class, and global attributes as the per-icon
components. They differ in what happens when the name is not compiled:

| Component                  | Unknown name                        |
| -------------------------- | ----------------------------------- |
| `MaterialSymbols.render/1` | renders `<!-- icon x not found -->` |
| `MaterialSymbols.icon/1`   | raises `ArgumentError`              |

Use `render/1` when the name is optional or user-controlled and the page should
still render. Use `icon/1` when the name comes from controlled data and an
unknown name is a bug.

```heex
<MaterialSymbols.render name={@icon} />
<MaterialSymbols.icon name={@icon} size={20} class="size-5" />
```

Use `MaterialSymbols.icon_names/0` to list every compiled component name and
`MaterialSymbols.search_icons/1` to filter names. Both return component-name
atoms. Use `MaterialSymbols.icon_name/1` for the upstream name;
`Atom.to_string/1` is not that name, because `9k` compiles to `:icon_9k`. The per-icon
components are intentionally omitted from the ExDoc function list; browse the
upstream set at [fonts.google.com/icons](https://fonts.google.com/icons) or use
`icon_names/0`. Icon names that start with a digit get an `icon_` prefix in the
generated component name, so `9k` is `<MaterialSymbols.icon_9k />`. The
original name still works with both dynamic components:
`<MaterialSymbols.render name="9k" />` or
`<MaterialSymbols.icon name="9k" />`. The component name also works:
`<MaterialSymbols.icon name="icon_9k" />` or `name={:icon_9k}`.

`MaterialSymbols.icon_name/1` maps a generated component atom back to the
upstream name: `MaterialSymbols.icon_name(:icon_9k)` returns `"9k"`.

## Compile-time configuration

By default, all 3,945 icons are available and only the `outlined` style is
compiled. Include more styles:

```elixir
config :material_symbols, styles: [:outlined, :rounded, :sharp]
```

Compile only selected icons:

```elixir
config :material_symbols, icons: [:home, :menu, :settings]
```

`styles` defaults to `[:outlined]`; `:all` includes all three. `icons` defaults
to `:all`. Use the upstream icon name in `icons`; the digit-leading icon `9k`
is configured as `"9k"` even though its component is `icon_9k`. Component names
such as `:icon_9k` are also accepted. Unknown icon or style names fail the
build. This is compile-time configuration, so it
belongs in `config/config.exs` or the environment config, not `runtime.exs`;
changing it recompiles the dependency. If `outlined` is not configured, the
first configured style becomes the default. The build also fails if a selected
icon lacks one of the requested style/fill variants.

## Finding used icons

The package includes a static usage scanner:

```sh
mix material_symbols.used
mix material_symbols.used --paths lib,test
mix material_symbols.used lib web
```

It scans `lib` by default, prints the icons it found with `file:line`
locations, and suggests `icons` and `styles` configuration. It detects
qualified components, aliases in `.ex`/`.exs` files, `render/1` and `icon/1`
literals, function captures, and `apply/3`. Template files are scanned for
fully qualified `MaterialSymbols.` names. Comments, documentation heredocs,
ordinary string literals, and sigils other than `~H` are ignored. An unstyled
call counts as the configured default style (`outlined` when compiled,
otherwise the first configured style). Dynamic names and
`icon_names/0`/`raw_names/0`/`icon_name/1`/`search_icons/1` produce warnings,
and `import MaterialSymbols` is flagged because imported `<.icon>` usage is not
detected. The task does not edit configuration.

Run it before deployment to trim the production binary. The scan is static;
review the reported locations before applying the allowlist.

## Vendored icons

The SVGs are copied from `@material-symbols/svg-400@0.49.0`: 23,670 files
covering 3,945 icons in three styles with filled and unfilled variants. The
repository keeps them under `priv/icons/`; the Hex package ships them as
`priv/icons.tar.gz` because Hex limits `metadata.config` to 1 MB. The archive
always contains all three styles; the compile-time configuration above decides
what is generated. See `priv/SOURCE.md` for the source URL, checksums, license,
and rebuild command.

## Provenance

See [NOTICE.md](NOTICE.md) for the full provenance review.

## Performance

The icon path data is compiled into `MaterialSymbols`, so rendering does no file
I/O. The compiled size and compile time depend on the configuration:

| Build                              | Approximate BEAM |
| ---------------------------------- | ---------------- |
| Default: outlined, all 3,945 icons | ~7.8 MB          |
| All three styles, all icons        | ~17 MB           |
| Allowlist: 2 icons, outlined       | ~119 KB          |

The all-styles build takes about 14 seconds to compile in a consumer; the
two-icon allowlist takes about 1.4 seconds. The narrow allowlist still carries
the full `raw_names/0` list, which the usage task needs to recognize icons that
are vendored but not compiled.

## Development

```sh
mix deps.get
mix test
mix docs
```

The package's own test build compiles all three styles through
`config/config.exs`; the dev build uses the default outlined-only build. The
icon archive is rebuilt from `priv/icons/` with the command in
`priv/SOURCE.md`.

## License

The package code is original and licensed under the **Apache License 2.0**. See
[LICENSE](LICENSE).

The Material Symbols icons are designed by Google and distributed under the
Apache License 2.0. The vendored copy includes the upstream license at
`priv/icons/LICENSE`; provenance, version, and checksums are in
`priv/SOURCE.md`.

Apache-2.0 is permissive: it allows commercial and closed-source use,
modification, and redistribution, provided the license and notices are
retained. This package is not affiliated with or endorsed by Google.

See [NOTICE.md](NOTICE.md) for the provenance review and the relationship to
the AGPL-3.0 `material_icons` package.
