Pregenerate the icons with a mix task

This commit is contained in:
Max Veytsman
2022-08-31 16:41:05 -04:00
parent 9c6dafbd81
commit 093383a73a
12 changed files with 35681 additions and 197 deletions

View File

@ -16,71 +16,4 @@ defmodule Heroicons do
Heroicons are designed by [Steve Schoger](https://twitter.com/steveschoger)
"""
defmodule Outline do
@moduledoc """
Outline style icons drawn with a stroke, packaged as Phoenix Components.
For primary navigation and marketing sections, with an outlined appearance,
designed to be rendered at 24x24.
"""
use Heroicons.Generator,
icon_dir: "outline/",
# Following https://github.com/tailwindlabs/heroicons/blob/b933d51df1f27c35414389fea185e9bac0097481/svgo.24.outline.yaml
svg_opts: [
remove_dimensions: true,
sort_attributes: true,
remove_attributes: ["stroke", "path:stroke-width"],
add_attributes: [
{"stroke-widt", "1.5"},
{"stroke", "currentColor"},
{"aria-hidden", "true"}
]
]
end
defmodule Solid do
@moduledoc """
Solid style icons drawn with fills, packaged as Phoenix Components.
For primary navigation and marketing sections, with a filled appearance,
designed to be rendered at 24x24.
"""
use Heroicons.Generator,
icon_dir: "solid/",
# Following https://github.com/tailwindlabs/heroicons/blob/b933d51df1f27c35414389fea185e9bac0097481/svgo.24.solid.yaml
svg_opts: [
remove_dimensions: true,
sort_attributes: true,
remove_attributes: ["fill"],
add_attributes: [
{"fill", "currentColor"},
{"aria-hidden", "true"}
]
]
end
defmodule Mini do
@moduledoc """
Solid style icons drawn with fills, packaged as Phoenix Components.
For smaller elements like buttons, form elements, and to support text,
designed to be rendered at 20x20.
"""
use Heroicons.Generator,
icon_dir: "mini/",
# Following https://github.com/tailwindlabs/heroicons/blob/b933d51df1f27c35414389fea185e9bac0097481/svgo.20.solid.yaml
svg_opts: [
remove_dimensions: true,
sort_attributes: true,
remove_attributes: ["fill"],
add_attributes: [
{"fill", "currentColor"},
{"aria-hidden", "true"}
]
]
end
end

View File

@ -1,91 +0,0 @@
defmodule Heroicons.Generator do
alias Heroicons.SvgProcessor
defmacro __using__(icon_dir: icon_dir, svg_opts: svg_opts) do
icon_paths =
Path.absname(icon_dir, :code.priv_dir(:heroicons))
|> Path.join("*.svg")
|> Path.wildcard()
require Phoenix.Component
if function_exported?(Phoenix.Component, :assigns_to_attributes, 2) do
Module.put_attribute(__CALLER__.module, :assign_mod, Phoenix.Component)
Module.put_attribute(__CALLER__.module, :assigns_to_attrs_mod, Phoenix.Component)
else
Module.put_attribute(__CALLER__.module, :assign_mod, Phoenix.LiveView)
Module.put_attribute(__CALLER__.module, :assigns_to_attrs_mod, Phoenix.LiveView.Helpers)
end
for path <- icon_paths do
generate(path, svg_opts)
end
end
@doc false
def generate(path, svg_opts) do
name =
Path.basename(path, ".svg")
|> String.replace("-", "_")
|> String.to_atom()
icon =
File.read!(path)
|> SvgProcessor.process(svg_opts)
<<"<svg", body::binary>> = icon
head = "<svg "
doc = """
![](assets/#{Path.relative_to(path, :code.priv_dir(:heroicons))}) {: width=24px}
## Examples
Use as a `Phoenix.Component`
<.#{name} />
<.#{name} class="h-6 w-6 text-gray-500" />
or as a function
<%= #{name}() %>
<%= #{name}(class: "h-6 w-6 text-gray-500") %>
"""
quote do
@doc unquote(doc)
def unquote(name)(assigns_or_opts \\ [])
def unquote(name)(var!(assigns)) when is_map(var!(assigns)) do
var!(attrs) = @assigns_to_attrs_mod.assigns_to_attributes(var!(assigns))
var!(assigns) = @assign_mod.assign(var!(assigns), :attrs, var!(attrs))
unquote(
EEx.compile_string(head <> "{@attrs}" <> body,
engine: Phoenix.LiveView.HTMLEngine,
file: __ENV__.file,
line: __ENV__.line + 1,
module: __ENV__.module,
indentation: 0
)
)
end
def unquote(name)(opts) when is_list(opts) do
attrs =
for {k, v} <- opts do
safe_k =
k |> Atom.to_string() |> String.replace("_", "-") |> Phoenix.HTML.Safe.to_iodata()
safe_v = v |> Phoenix.HTML.Safe.to_iodata()
{:safe, [?\s, safe_k, ?=, ?", safe_v, ?"]}
end
{:safe, [unquote(head), Phoenix.HTML.Safe.to_iodata(attrs), unquote(body)]}
end
end
end
end

11871
lib/heroicons/mini.ex Normal file

File diff suppressed because it is too large Load Diff

11716
lib/heroicons/outline.ex Normal file

File diff suppressed because it is too large Load Diff

11874
lib/heroicons/solid.ex Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,19 @@
defmodule Mix.Heroicons.GeneratorHelpers do
alias Mix.Heroicons.SvgProcessor
def icon_name(path) do
Path.basename(path, ".svg")
|> String.replace("-", "_")
|> String.to_atom()
end
def icon_body(path, svg_opts) do
icon =
File.read!(path)
|> SvgProcessor.process(svg_opts)
<<"<svg ", body::binary>> = icon
body
end
end

View File

@ -1,4 +1,6 @@
defmodule Heroicons.SvgProcessor do
defmodule Mix.Heroicons.SvgProcessor do
alias Mix.Heroicons.SvgProcessor.Handler
@moduledoc """
An SVG parser loosly based on https://github.com/svg/svgo
@ -12,7 +14,7 @@ defmodule Heroicons.SvgProcessor do
"""
def process(svg, opts \\ []) do
{:ok, stack} = Saxy.parse_string(svg, Heroicons.SvgProcessor.Handler, {[], opts})
{:ok, stack} = Saxy.parse_string(svg, Handler, {[], opts})
Saxy.encode!(stack)
end
end

View File

@ -1,4 +1,4 @@
defmodule Heroicons.SvgProcessor.Handler do
defmodule Mix.Heroicons.SvgProcessor.Handler do
@moduledoc false
@behaviour Saxy.Handler
@ -11,14 +11,21 @@ defmodule Heroicons.SvgProcessor.Handler do
@impl Saxy.Handler
def handle_event(:start_element, {"svg", attributes}, {stack, opts}) do
attributes =
filter_attributes(attributes, opts)
|> add_attributes(opts)
|> sort_attributes(opts)
remove_dimensions(attributes, Keyword.get(opts, :remove_dimensions))
|> remove_attributes(Keyword.get(opts, :remove_attributes))
|> add_attributes(Keyword.get(opts, :add_attributes))
|> sort_attributes(Keyword.get(opts, :sort_attributes))
tag = {"svg", attributes, []}
{:ok, {[tag | stack], opts}}
end
def handle_event(:start_element, {"path", attributes}, {stack, opts}) do
attributes = remove_attributes(attributes, Keyword.get(opts, :remove_path_attributes, []))
tag = {"path", attributes, []}
{:ok, {[tag | stack], opts}}
end
def handle_event(:start_element, {tag_name, attributes}, {stack, opts}) do
tag = {tag_name, attributes, []}
{:ok, {[tag | stack], opts}}
@ -62,25 +69,39 @@ defmodule Heroicons.SvgProcessor.Handler do
{:ok, stack}
end
defp filter_attributes(attributes, opts) do
remove_dimensions = Keyword.get(opts, :remove_dimensions)
remove_attrs = Keyword.get(opts, :remove_attributes, [])
defp remove_dimensions(attributes, true) do
Enum.reject(attributes, fn {attr, _value} ->
(remove_dimensions && (attr == "width" || attr == "height")) ||
attr in remove_attrs
attr == "width" || attr == "height"
end)
end
defp add_attributes(attributes, opts) do
attributes ++ Keyword.get(opts, :add_attributes, [])
defp remove_dimensions(attributes, _) do
attributes
end
defp sort_attributes(attributes, opts) do
if Keyword.get(opts, :sort_attributes) do
Enum.sort_by(attributes, fn {attr, _value} -> attr end)
else
attributes
end
defp remove_attributes(attributes, nil) do
attributes
end
defp remove_attributes(attributes, remove_attrs) do
Enum.reject(attributes, fn {attr, _value} ->
attr in remove_attrs
end)
end
defp add_attributes(attributes, nil) do
attributes
end
defp add_attributes(attributes, add_attrs) do
attributes ++ add_attrs
end
defp sort_attributes(attributes, true) do
Enum.sort_by(attributes, fn {attr, _value} -> attr end)
end
defp sort_attributes(attributes, nil) do
attributes
end
end

View File

@ -0,0 +1,85 @@
defmodule Mix.Tasks.Heroicons.Generate do
use Mix.Task
import Mix.Heroicons.GeneratorHelpers
@icon_sets [
%{
module: Heroicons.Outline,
path: "lib/heroicons/outline.ex",
moduledoc:
"Outline style icons drawn with a stroke, packaged as Phoenix Components.\n\n For primary navigation and marketing sections, with an outlined appearance,\n designed to be rendered at 24x24.",
icon_dir: "icons/outline/",
# Following https://github.com/tailwindlabs/heroicons/blob/b933d51df1f27c35414389fea185e9bac0097481/svgo.24.outline.yaml
svg_opts: [
remove_dimensions: true,
sort_attributes: true,
remove_attributes: ["stroke"],
remove_path_attributes: ["stroke-width"],
add_attributes: [
{"stroke-width", "1.5"},
{"stroke", "currentColor"},
{"aria-hidden", "true"}
]
]
},
%{
module: Heroicons.Solid,
path: "lib/heroicons/solid.ex",
moduledoc:
"Solid style icons drawn with fills, packaged as Phoenix Components.\n\n For primary navigation and marketing sections, with a filled appearance,\n designed to be rendered at 24x24.",
icon_dir: "icons/solid/",
# Following https://github.com/tailwindlabs/heroicons/blob/b933d51df1f27c35414389fea185e9bac0097481/svgo.24.solid.yaml
svg_opts: [
remove_dimensions: true,
sort_attributes: true,
remove_attributes: ["fill"],
add_attributes: [
{"fill", "currentColor"},
{"aria-hidden", "true"}
]
]
},
%{
module: Heroicons.Mini,
path: "lib/heroicons/mini.ex",
moduledoc: "Solid style icons drawn with fills, packaged as Phoenix Components.\n\n For smaller elements like buttons, form elements, and to support text,\n designed to be rendered at 20x20.",
icon_dir: "icons/mini/",
# Following https://github.com/tailwindlabs/heroicons/blob/b933d51df1f27c35414389fea185e9bac0097481/svgo.20.solid.yaml
svg_opts: [
remove_dimensions: true,
sort_attributes: true,
remove_attributes: ["fill"],
add_attributes: [
{"fill", "currentColor"},
{"aria-hidden", "true"}
]
]
}
]
@impl Mix.Task
def run(_args) do
for %{module: module, path: path, moduledoc: moduledoc, icon_dir: icon_dir, svg_opts: svg_opts} <-
@icon_sets do
icon_paths =
Path.absname(icon_dir, :code.priv_dir(:heroicons))
|> Path.join("*.svg")
|> Path.wildcard()
Mix.Generator.create_file(
path,
EEx.eval_file(
"priv/templates/icon_set.ex",
[
module: module,
moduledoc: moduledoc,
icon_paths: icon_paths,
svg_opts: svg_opts
],
functions: __ENV__.functions
)
)
end
end
end