defmodule FlintUI.API.Classes do @moduledoc """ CSS class helpers. """ @doc """ Builds a class string, concatenates a class string, out of a list of: * Strings; * Atoms; * Keyword lists or maps where the keys are the CSS class and the values are treated as booleans for conditional inclusion of the class. All `nil`, `false` and duplicated values are filtered out. ## Examples iex> cx(["foo", "bar"]) "foo bar" iex> cx([foo: false, bar: true]) "bar" iex> cx([[a: true, b: false], [c: false, d: true]]) "a d" iex> cx(["one", :two]) "one two" iex> cx(yes: true, no: false) "yes" iex> cx(%{"a" => true, b: false}) "a" iex> cx(t: "I'm truthy", f: nil) "t" iex> cx([[[nil, false, "hello", :world]]]) "hello world" """ def cx(classes) do [classes] |> flatten_classes() |> Enum.uniq() |> Enum.join(" ") |> String.trim() |> case do "" -> nil classes -> classes end end defguardp is_class(c) when is_binary(c) or is_atom(c) # Return a list of classes in string or atom form. defp classes(nil), do: [] defp classes(false), do: [] defp classes(s) when is_class(s), do: [to_string(s)] defp classes({k, false}) when is_class(k), do: [] defp classes({k, nil}) when is_class(k), do: [] defp classes({k, _}) when is_class(k), do: [to_string(k)] defp classes(%{} = map), do: Enum.flat_map(map, &classes/1) defp classes(_), do: [] defp flatten_classes(class_list) do class_list |> List.flatten() |> Enum.flat_map(&classes/1) |> Enum.filter(&(!!&1 && &1 != "")) end end