lib/fluint_ui/api/attribute.ex

defmodule FlintUI.API.Attribute do
  @moduledoc """
  Phoenix LiveView attribute helpers.
  """

  alias FlintUI.Theme

  @doc """
  Defines a class attribute for the component trying to
  fetch the default value from the theme if no default value
  is provided.
  """
  defmacro color(opts \\ []) do
    name = Keyword.get(opts, :name) |> component_name(__CALLER__)
    default = Keyword.get(opts, :default, Theme.get_in_theme(name, [:default, :color]) || :dark)
    doc = Keyword.get(opts, :doc, "The color of the #{humanize_name(name)}.")

    quote do
      attr(:color, :atom,
        default: unquote(default),
        doc: unquote(doc)
      )
    end
  end

  @doc """
  Defines a size attribute for the component trying to
  fetch the default value and values list from the theme
  if no values are provided for those.
  """
  defmacro size(opts \\ []) do
    name = Keyword.get(opts, :name) |> component_name(__CALLER__)
    values = Keyword.get(opts, :values, Theme.size_variants())
    default = Keyword.get(opts, :default, Theme.get_in_theme(name, [:default, :size]) || :md)
    doc = Keyword.get(opts, :doc, "The size of the #{humanize_name(name)}.")

    quote do
      attr(:size, :atom,
        values: unquote(values),
        default: unquote(default),
        doc: unquote(doc)
      )
    end
  end

  @doc """
  Defines a radius attribute for the component trying to
  fetch the default value and values list from the theme
  if no values are provided for those.
  """
  defmacro radius(opts \\ []) do
    name = Keyword.get(opts, :name) |> component_name(__CALLER__)
    values = Keyword.get(opts, :values, Theme.radius_variants())
    default = Keyword.get(opts, :default, Theme.get_in_theme(name, [:default, :radius]) || :full)
    doc = Keyword.get(opts, :doc, "The radius of the #{humanize_name(name)}.")

    quote do
      attr(:radius, :atom,
        values: unquote(values),
        default: unquote(default),
        doc: unquote(doc)
      )
    end
  end

  defp component_name(nil, caller) do
    FlintUI.Component.module_to_component_name(caller.module)
  end

  defp component_name(name, _caller) when is_binary(name) do
    FlintUI.Component.string_to_component_name(name)
  end

  defp component_name(name, _caller) when is_atom(name), do: name

  defp humanize_name(name) when is_atom(name) do
    FlintUI.Component.humanize_component_name(name)
  end

  @doc """
  Tries to convert an user input value value to an integer value
  and returns a default value if the conversion fails.
  """
  def as_integer(value, default \\ nil)
  def as_integer(value, _default) when is_integer(value), do: value
  def as_integer(value, _default) when is_float(value), do: trunc(value)

  def as_integer(value, default) when is_binary(value) do
    try do
      String.to_integer(value)
    rescue
      ArgumentError -> default
    end
  end

  def as_integer(_value, default), do: default

  @doc """
  Tries to convert an user input value to a boolean value
  and returns a default value if the conversion fails.
  """
  def as_boolean(value, default \\ false)
  def as_boolean(value, _default) when is_boolean(value), do: value

  def as_boolean("true", _default), do: true
  def as_boolean("1", _default), do: true
  def as_boolean("false", _default), do: false
  def as_boolean("0", _default), do: false

  def as_boolean(value, default) when is_binary(value) do
    case Integer.parse(value) do
      {int, _rest} -> as_boolean(int, default)
      _ -> default
    end
  end

  def as_boolean(value, _default) when is_number(value) do
    if value > 0, do: true, else: false
  end

  def as_boolean(_value, default), do: default
end