My first elixir module: a css classname joiner

Hello elixir community :slight_smile:

As a pretty much newcomer to elixir, I was thinking how I could get started with some practical but straightforward work and I got the idea to build a simple css classname join utility à la https://github.com/lukeed/clsx

I don’t know if sharing code via forum is something seen in this community, but I would really love to get feedback how I can improve on my code :nerd_face:

First and foremost I was curious to hear if there already exist some recommended, best practice helpers regarding classname handling in eex.

This is what I came up with:

defmodule KatyWeb.Css do
  @var %{
    :some_global => "bg-blue-500 hover:bg-blue-300"
  }
  def get(key) do
    @var[key]
  end

  defp convertNameToClass(name) when is_map_key(@var, name) do
    [@var[name]]
  end

  defp convertNameToClass(name) when is_list(name) do
    Enum.flat_map(name, &convertNameToClass/1)
  end

  defp convertNameToClass(name) do
    [name]
  end

  @type names :: %{any: boolean}

  @spec join(names) :: String.t()
  def join(names) do
    names
      |> Enum.filter(fn({_key, value}) -> value end)
      |> Enum.map(fn({key, _value}) -> key end)
      |> Enum.flat_map(&convertNameToClass/1)
      |> IO.inspect
      |> Enum.uniq
      |> Enum.join(" ")
  end
end

Usage Example: (in liveview with a little counter button):

<button
  class="<%= Css.join(%{
    "my-24 mx-6 p-6 text-4xl font-light tracking-tight" => 1,
    "bg-green-500 hover:bg-green-300" => @count == 0,
    "bg-orange-500 hover:bg-orange-300" => @count == 1,
    [:some_global, "font-bold"] => @count == 2,
    "bg-red-500 hover:bg-red-300" => @count > 2
  }) %>"
  phx-click="count"
>
  Count: <%= @count %>
</button>

Demo:

Thanks a lot in advance for your feedback and time :rainbow:

3 Likes

Hello!

After my first attempt to find something similar I found this → GitHub - rzane/classnames: A simple utility for conditionally joining class names in Elixir
Take a look how it is implemented.

Now about your code. These 3 calls

can be merged into 1 flat_map

|> Enum.flat_map(fn {key, value} ->
  if value do
    convertNameToClass(key)
  else
    []
  end
end)

It is recommended to name functions in snake_case. convertNameToClassconvert_name_to_class.

This type means map with key :any. Perhaps, you want %{any() => boolean()}.

3 Likes

You can also use this form :slight_smile:

|> Enum.flat_map(fn 
  {_key, nil} -> []
  {key, _value} -> convertNameToClass(key)
end)
4 Likes

false should be handled too :wink:

I wanted to avoid this…

|> Enum.flat_map(fn 
  {_key, value} when not value -> []
  {key, _value} -> convertNameToClass(key)
end)

…but now i have too :slight_smile:

1 Like

@kokolegorille / @fuelen You guys are awesome! Thanks a lot for the reviews and hints :star_struck: