├── .formatter.exs ├── package.json ├── test ├── test_helper.exs ├── support │ └── conn_case.ex ├── fontawesome_test.exs ├── surface │ └── icon_test.exs └── live_view_test.exs ├── .gitignore ├── LICENSE ├── lib ├── fontawesome │ ├── live_view.ex │ ├── icon.ex │ └── surface │ │ └── icon.ex └── fontawesome.ex ├── README.md ├── .github └── workflows │ └── ci.yml ├── CHANGELOG.md ├── mix.exs └── mix.lock /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | import_deps: [:surface], 4 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 5 | ] 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ex_fontawesome", 3 | "version": "0.7.2", 4 | "description": "ex_fontawesome", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": "lib", 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@fortawesome/fontawesome-free": "^6.2.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | 3 | defmodule Router do 4 | use Phoenix.Router 5 | end 6 | 7 | defmodule Endpoint do 8 | use Phoenix.Endpoint, otp_app: :surface 9 | plug(Router) 10 | end 11 | 12 | Application.put_env(:surface, Endpoint, 13 | secret_key_base: "J4lTFt000ENUVhu3dbIB2P2vRVl2nDBH6FLefnPUImL8mHYNX8Kln/N9J0HH19Mq", 14 | live_view: [ 15 | signing_salt: "LfCCMxfkGME8S8P8XU3Z6/7+ZlD9611u" 16 | ] 17 | ) 18 | 19 | Endpoint.start_link() 20 | -------------------------------------------------------------------------------- /test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule FontAwesome.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | It also imports other functionality to make it easier 7 | to test components. 8 | """ 9 | 10 | use ExUnit.CaseTemplate 11 | 12 | using do 13 | quote do 14 | # Import conveniences for testing 15 | use Surface.LiveViewTest 16 | 17 | # The default endpoint for testing 18 | @endpoint Endpoint 19 | end 20 | end 21 | 22 | setup _tags do 23 | {:ok, conn: Phoenix.ConnTest.build_conn()} 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | ex_fontawesome-*.tar 24 | 25 | 26 | # Temporary files for e.g. tests 27 | /tmp 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Miguel Serrano 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/fontawesome/live_view.ex: -------------------------------------------------------------------------------- 1 | if Code.ensure_loaded?(Phoenix.LiveView) do 2 | defmodule FontAwesome.LiveView do 3 | @moduledoc """ 4 | A LiveView component for rendering Font Awesome icons. 5 | 6 | ## Examples 7 | 8 | 9 | """ 10 | 11 | use Phoenix.Component 12 | 13 | def icon(assigns) do 14 | opts = assigns[:opts] || [] 15 | type_opts = type_to_opts(assigns) 16 | class_opts = class_to_opts(assigns) 17 | 18 | opts = 19 | opts 20 | |> Keyword.merge(type_opts) 21 | |> Keyword.merge(class_opts) 22 | 23 | assigns = assign(assigns, opts: opts) 24 | 25 | ~H""" 26 | <%= FontAwesome.icon(@name, @opts) %> 27 | """ 28 | end 29 | 30 | defp type_to_opts(assigns) do 31 | type = assigns[:type] || FontAwesome.default_type() 32 | 33 | unless type do 34 | raise ArgumentError, 35 | "type prop is required if default type is not configured." 36 | end 37 | 38 | [type: type] 39 | end 40 | 41 | defp class_to_opts(assigns) do 42 | if assigns[:class] do 43 | [class: assigns.class] 44 | else 45 | [] 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FontAwesome 2 | 3 | ![CI](https://github.com/miguel-s/ex_fontawesome/actions/workflows/ci.yml/badge.svg) 4 | 5 | This package adds a convenient way of using [Font Awesome](https://fontawesome.com) SVGs with your Phoenix, Phoenix LiveView and Surface applications. 6 | 7 | You can find the original docs [here](https://fontawesome.com) and repo [here](https://github.com/FortAwesome/Font-Awesome). 8 | 9 | Current FontAwesome version: 6.2.0 10 | 11 | ## Installation 12 | 13 | Add `ex_fontawesome` to the list of dependencies in `mix.exs`: 14 | 15 | def deps do 16 | [ 17 | {:ex_fontawesome, "~> 0.7.2"} 18 | ] 19 | end 20 | 21 | Then run `mix deps.get`. 22 | 23 | ## Usage 24 | 25 | #### With Eex or Leex 26 | 27 | ```elixir 28 | <%= FontAwesome.icon("address-book", type: "regular", class: "h-4 w-4") %> 29 | ``` 30 | 31 | #### With Heex 32 | 33 | ```elixir 34 | 35 | ``` 36 | 37 | #### With Surface 38 | 39 | ```elixir 40 | 41 | ``` 42 | 43 | ## Config 44 | 45 | Defaults can be set in the `FontAwesome` application configuration. 46 | 47 | ```elixir 48 | config :ex_fontawesome, type: "regular" 49 | ``` 50 | 51 | ## License 52 | 53 | MIT. See [LICENSE](https://github.com/miguel-s/ex_fontawesome/blob/master/LICENSE) for more details. 54 | -------------------------------------------------------------------------------- /lib/fontawesome/icon.ex: -------------------------------------------------------------------------------- 1 | defmodule FontAwesome.Icon do 2 | @moduledoc """ 3 | This module defines the data structure and functions for working with icons stored as SVG files. 4 | """ 5 | 6 | alias __MODULE__ 7 | 8 | @doc """ 9 | Defines the FontAwesome.Icon struct. 10 | 11 | Its fields are: 12 | 13 | * `:type` - the type of the icon 14 | 15 | * `:name` - the name of the icon 16 | 17 | * `:file` - the binary of the icon 18 | 19 | """ 20 | defstruct [:type, :name, :file] 21 | 22 | @type t :: %Icon{type: String.t(), name: String.t(), file: binary} 23 | 24 | @doc "Parses a SVG file and returns structured data" 25 | @spec parse!(String.t()) :: Icon.t() 26 | def parse!(filename) do 27 | [type, name] = filename |> Path.split() |> Enum.take(-2) 28 | name = Path.rootname(name) 29 | file = File.read!(filename) 30 | struct!(__MODULE__, type: type, name: name, file: file) 31 | end 32 | 33 | @doc "Converts opts to HTML attributes" 34 | @spec opts_to_attrs(keyword) :: list 35 | def opts_to_attrs(opts) do 36 | for {key, value} <- opts do 37 | key = 38 | key 39 | |> Atom.to_string() 40 | |> String.replace("_", "-") 41 | |> Phoenix.HTML.Safe.to_iodata() 42 | 43 | value = Phoenix.HTML.Safe.to_iodata(value) 44 | 45 | [?\s, key, ?=, ?", value, ?"] 46 | end 47 | end 48 | 49 | @doc "Inserts HTML attributes into an SVG icon" 50 | @spec insert_attrs(binary, keyword) :: Phoenix.HTML.safe() 51 | def insert_attrs(" rest, attrs) do 52 | Phoenix.HTML.raw([" 9 | """ 10 | 11 | use Surface.Component 12 | 13 | @doc "The name of the icon" 14 | prop name, :string, required: true 15 | 16 | @doc """ 17 | The type of the icon 18 | 19 | Required if default type is not configured. 20 | """ 21 | prop type, :string 22 | 23 | @doc "The class of the icon" 24 | prop class, :css_class 25 | 26 | @doc "All options are forwarded to the underlying SVG tag as HTML attributes" 27 | prop opts, :keyword, default: [] 28 | 29 | def render(assigns) do 30 | type_opts = type_to_opts(assigns) 31 | class_opts = class_to_opts(assigns) 32 | 33 | opts = 34 | assigns.opts 35 | |> Keyword.merge(type_opts) 36 | |> Keyword.merge(class_opts) 37 | 38 | assigns = assign(assigns, opts: opts) 39 | 40 | ~F""" 41 | { FontAwesome.icon(@name, @opts) } 42 | """ 43 | end 44 | 45 | defp type_to_opts(assigns) do 46 | type = assigns[:type] || FontAwesome.default_type() 47 | 48 | unless type do 49 | raise ArgumentError, 50 | "type prop is required if default type is not configured." 51 | end 52 | 53 | [type: type] 54 | end 55 | 56 | defp class_to_opts(assigns) do 57 | if assigns[:class] do 58 | [class: Surface.css_class(assigns.class)] 59 | else 60 | [] 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v0.7.2 (2022-09-06) 2 | 3 | - Upgraded to FontAwesome v6.2.0 4 | 5 | ## v0.7.1 (2022-03-24) 6 | 7 | - Upgraded to FontAwesome v6.1.1 8 | 9 | ## v0.7.0 (2022-02-21) 10 | 11 | - Upgraded to FontAwesome v6.0.0 12 | 13 | ## v0.6.0 (2022-01-13) 14 | 15 | - Upgraded to Phoenix HTML v3.2 16 | - Upgraded to Phoenix LiveView v0.17 17 | - Upgraded to Surface v0.7 18 | 19 | ## v0.5.1 (2021-11-16) 20 | 21 | - Added helper function `types/0` for listing available icon types 22 | - Added helper function `names/0` for listing available icon names 23 | 24 | ## v0.5.0 (2021-10-21) 25 | 26 | - Upgraded to Surface v0.6.0 27 | - Removed support for Phoenix HTML v2 28 | 29 | ## v0.4.0 (2021-09-05) 30 | 31 | - Upgraded to Phoenix HTML v3.0 32 | - Upgraded to Phoenix LiveView v0.16 33 | - Upgraded to Surface v0.5.2 34 | 35 | #### Breaking Changes 36 | 37 | - Renamed `FontAwesome.Components.Icon` to `FontAwesome.Surface.Icon` 38 | 39 | #### Enhancements 40 | 41 | - Added support for heex templates with a new Phoenix LiveView component `FontAwesome.LiveView.icon` 42 | 43 | ## v0.3.1 (2021-09-03) 44 | 45 | - Added support for Phoenix HTML v0.3 46 | 47 | ## v0.3.0 (2021-06-18) 48 | 49 | #### Breaking Changes 50 | 51 | - Upgraded to Surface v0.5.0 52 | 53 | ## v0.2.0 (2021-06-07) 54 | 55 | #### Breaking Changes 56 | 57 | - Replaced `icon/3` with `icon/2`, the `type` argument should be passed as an option instead 58 | 59 | #### Enhancements 60 | 61 | - Added `:type` to config for setting the default icon type 62 | 63 | ## v0.1.1 (2021-05-29) 64 | 65 | - All options passed to `icon/3` will be added to the SVG tag as HTML attributes 66 | - Added `opts` prop to the Surface Icon component, which will be added to the SVG tag as HTML attributes 67 | 68 | ## v0.1.0 (2021-05-24) 69 | 70 | - Initial release 71 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ExFontawesome.MixProject do 2 | use Mix.Project 3 | 4 | @version "0.7.2" 5 | 6 | def project do 7 | [ 8 | app: :ex_fontawesome, 9 | version: @version, 10 | elixir: "~> 1.11", 11 | elixirc_paths: elixirc_paths(Mix.env()), 12 | start_permanent: Mix.env() == :prod, 13 | deps: deps(), 14 | docs: docs(), 15 | description: description(), 16 | package: package(), 17 | source_url: "https://github.com/miguel-s/ex_fontawesome" 18 | ] 19 | end 20 | 21 | def application do 22 | [ 23 | extra_applications: [:logger] 24 | ] 25 | end 26 | 27 | defp elixirc_paths(:test), do: ["lib", "test/support"] 28 | defp elixirc_paths(_), do: ["lib"] 29 | 30 | defp deps do 31 | [ 32 | {:phoenix_html, "~> 3.2"}, 33 | {:ex_doc, "~> 0.27", only: :dev, runtime: false}, 34 | {:floki, ">= 0.32.0", only: :test}, 35 | {:phoenix_live_view, "~> 0.17", optional: true}, 36 | {:surface, "~> 0.7", optional: true} 37 | ] 38 | end 39 | 40 | defp docs do 41 | [ 42 | main: "FontAwesome", 43 | source_ref: "v#{@version}", 44 | source_url: "https://github.com/miguel-s/ex_fontawesome", 45 | groups_for_modules: [ 46 | Liveview: ~r/FontAwesome.LiveView/, 47 | Surface: ~r/FontAwesome.Surface/ 48 | ], 49 | nest_modules_by_prefix: [ 50 | FontAwesome.LiveView, 51 | FontAwesome.Surface 52 | ], 53 | extras: ["README.md"] 54 | ] 55 | end 56 | 57 | defp description() do 58 | """ 59 | This package adds a convenient way of using Font Awesome SVGs with your Phoenix, Phoenix LiveView and Surface applications. 60 | """ 61 | end 62 | 63 | defp package do 64 | %{ 65 | files: ~w(lib node_modules .formatter.exs mix.exs README* LICENSE* CHANGELOG*), 66 | licenses: ["MIT"], 67 | links: %{"GitHub" => "https://github.com/miguel-s/ex_fontawesome"} 68 | } 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /test/fontawesome_test.exs: -------------------------------------------------------------------------------- 1 | defmodule FontAwesomeTest do 2 | use ExUnit.Case, async: true 3 | doctest FontAwesome 4 | 5 | test "renders icon" do 6 | assert FontAwesome.icon("address-book", type: "regular") 7 | |> Phoenix.HTML.safe_to_string() =~ " Phoenix.HTML.safe_to_string() =~ ~s( Phoenix.HTML.safe_to_string() =~ ~s(