defmodule BrightWeb.SVGIcon do @moduledoc """ This package adds a convenient way of using svg icons with your Phoenix, Phoenix LiveView and Surface applications. greets https://github.com/miguel-s/ex_heroicons/blob/main/lib/heroicons.ex ## Usage ## Config Defaults can be set in the application configuration. config :bright, :icons_type: "outline" """ use Phoenix.Component alias BrightWeb.SVGIcon.Icon svg_icons_path = "priv/static/assets/icons" unless File.exists?(svg_icons_path) do raise """ SVG icons not found. Expected to load them from #{svg_icons_path}. """ end icon_paths = svg_icons_path |> Path.join("**/*.svg") |> Path.wildcard() icons = for icon_path <- icon_paths do @external_resource Path.relative_to_cwd(icon_path) Icon.parse!(icon_path) end types = icons |> Enum.map(& &1.type) |> Enum.uniq() names = icons |> Enum.map(& &1.name) |> Enum.uniq() default_type = case Application.compile_env(:bright, :icons_type) do nil -> "solid" type when is_binary(type) -> if type in types do type else raise ArgumentError, "expected default type to be one of #{inspect(types)}, got: #{inspect(type)}" end type -> raise ArgumentError, "expected default type to be one of #{inspect(types)}, got: #{inspect(type)}" end @names names def names, do: @names @types types def types, do: @types attr :name, :string, values: @names, required: true, doc: "the name of the icon" attr :type, :string, values: @types, default: default_type, doc: "the type of the icon" attr :class, :string, default: nil, doc: "the css classes to add to the svg container" attr :rest, :global, doc: "the arbitrary HTML attributes to add to the svg container" def icon(assigns) do name = assigns[:name] if name == nil or name not in @names do raise ArgumentError, "expected icon name to be one of #{inspect(unquote(@names))}, got: #{inspect(name)}" end type = assigns[:type] if type == nil or type not in @types do raise ArgumentError, "expected icon type to be one of #{inspect(unquote(@types))}, got: #{inspect(type)}" end ~H""" <.svg_container focusable="false" type={@type} class={@class} {@rest}> <%= {:safe, svg_body(@name, @type)} %> """ end attr :type, :string, values: @types, default: default_type, doc: "the type of the icon" attr :class, :string, default: nil, doc: "the css classes to add to the svg container" attr :rest, :global, doc: "the arbitrary HTML attributes to add to the svg container" slot :inner_block, required: true, doc: "the svg to render" defp svg_container(assigns) do ~H""" <%= render_slot(@inner_block) %> """ end defp svg_viewbox(type) do case type do "micro" -> "0 0 16 16" "mini" -> "0 0 20 20" "solid" -> "0 0 24 24" "outline" -> "0 0 24 24" end end for %Icon{name: name, type: type, file: file} <- icons do defp svg_body(unquote(name), unquote(type)) do unquote(file) end end end