defmodule Classes do
@moduledoc """
A pure function for generating CSS classes.
"""
@doc """
Returns a string containing CSS.
The argument can be any of the following:
* String or atom containing a CSS class or classes.
* Keyword list or map with string or atom keys. The key is a CSS class and the value is
treated as a boolean indicating whether or not the class should be included.
* `nil` and `false` are filtered.
* Lists, including deeply nested lists, containing any of the above types.
This function does not dedupe classes.
## Examples
iex> classes("my-class")
"my-class"
iex> classes(:myclass)
"myclass"
iex> classes(["one", :two])
"one two"
iex> classes(yes: true, no: false)
"yes"
iex> classes(%{"string-key" => true, atomkey: false})
"string-key"
iex> classes(yes: "I'm truthy", no: nil)
"yes"
iex> classes([%{"one" => true}, [two: false, three: true]])
"one three"
iex> classes([{"string-key-tuple", true}, {"also-happens-to_work", nil}])
"string-key-tuple"
iex> classes([[[nil, false, "hello", :world]]])
"hello world"
iex> classes([{"invalid argument"}])
** (FunctionClauseError) no function clause matching in Classes.class_list/1
"""
def classes(class) do
[class]
|> List.flatten()
|> Enum.flat_map(&class_list/1)
|> Enum.join(" ")
end
defguardp is_class(c) when is_binary(c) or is_atom(c)
# Returns list of strings and atoms
defp class_list(false), do: []
defp class_list(nil), do: []
defp class_list(s) when is_class(s), do: [s]
defp class_list({k, false}) when is_class(k), do: []
defp class_list({k, nil}) when is_class(k), do: []
defp class_list({k, _}) when is_class(k), do: [k]
defp class_list(%{} = map), do: Enum.flat_map(map, &class_list/1)
end