defmodule PetalComponents.Rating do use Phoenix.Component attr(:rating, :any, default: 0, doc: "The rating to display (integer or float)") attr(:round_to_nearest_half, :boolean, default: true, doc: "Whether to round the rating to the nearest half star (eg. 3.3 -> 3.5" ) attr(:total, :integer, default: 5, doc: "The total number of stars to display") attr(:class, :any, default: nil, doc: "Any additional CSS classes for the rating wrapper") attr(:star_class, :any, default: nil, doc: "Any additional CSS classes for the individual stars. Eg. you could change the size of the stars with 'h-10 w-10'" ) attr(:include_label, :boolean, default: false, doc: "Whether to include an automatically generated rating label" ) attr(:label_class, :any, default: nil, doc: "Any additional CSS classes for the rating label" ) def rating(assigns) do assigns = assigns |> assign(:rating_as_float, to_float(assigns.rating)) ~H"""
<%= for i <- 1..@total do %> <.rating_star class={@star_class} type={calculate_type(i, @rating_as_float, @round_to_nearest_half)} /> <% end %> <%= if @include_label do %> {@rating_as_float} out of {@total} <% end %>
""" end defp to_float(value) when is_integer(value), do: value + 0.0 defp to_float(value) when is_float(value), do: value defp round_to_half(number) do round(number * 2) / 2 end defp calculate_type(current_star, rating, round_to_nearest_half) do maybe_rounded = if round_to_nearest_half, do: round_to_half(rating), else: rating if current_star <= trunc(rating) do :filled else case maybe_rounded - (current_star - 1) do n when n >= 0.5 and n < 1 -> :half _ -> :empty end end end attr :class, :any, default: nil, doc: "Any additional CSS classes for the star" attr :type, :atom, default: :empty, doc: "The type of star to display" def rating_star(%{type: :empty} = assigns) do ~H""" """ end def rating_star(%{type: :filled} = assigns) do ~H""" """ end def rating_star(%{type: :half} = assigns) do ~H""" """ end end