Skip to main content

lib/shadix/components/slider.ex

defmodule Shadix.Components.Slider do
  @moduledoc """
  Slider component adapted from shadcn/ui (new-york-v4) to a native
  `<input type="range">`.

  Field-aware: the current value is read from the bound `Phoenix.HTML.FormField`.
  shadcn builds its slider from Radix markup (track + range + thumb); here the
  same look is reconstructed on the native element with `appearance-none` plus
  Tailwind arbitrary variants targeting the browser's slider track/thumb
  pseudo-elements (`::-webkit-slider-runnable-track`, `::-webkit-slider-thumb`,
  `::-moz-range-track`, `::-moz-range-thumb`). No JavaScript.
  """
  use Phoenix.Component

  import Shadix.Cn
  import Shadix.Form

  @base "w-full cursor-pointer appearance-none bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none " <>
          "[&::-webkit-slider-runnable-track]:h-1.5 [&::-webkit-slider-runnable-track]:w-full [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:bg-muted " <>
          "[&::-moz-range-track]:h-1.5 [&::-moz-range-track]:w-full [&::-moz-range-track]:rounded-full [&::-moz-range-track]:bg-muted " <>
          "[&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:size-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:border [&::-webkit-slider-thumb]:border-primary [&::-webkit-slider-thumb]:bg-background [&::-webkit-slider-thumb]:shadow-sm [&::-webkit-slider-thumb]:ring-ring/50 [&::-webkit-slider-thumb]:transition-[box-shadow] hover:[&::-webkit-slider-thumb]:ring-4 focus-visible:[&::-webkit-slider-thumb]:ring-4 " <>
          "[&::-moz-range-thumb]:size-4 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:border [&::-moz-range-thumb]:border-primary [&::-moz-range-thumb]:bg-background [&::-moz-range-thumb]:shadow-sm [&::-moz-range-thumb]:ring-ring/50 [&::-moz-range-thumb]:transition-[box-shadow] hover:[&::-moz-range-thumb]:ring-4 focus-visible:[&::-moz-range-thumb]:ring-4 " <>
          "aria-invalid:[&::-webkit-slider-thumb]:border-destructive aria-invalid:[&::-moz-range-thumb]:border-destructive"

  attr(:field, Phoenix.HTML.FormField, required: true)
  attr(:min, :integer, default: 0)
  attr(:max, :integer, default: 100)
  attr(:step, :integer, default: 1)
  attr(:class, :string, default: nil)

  attr(:rest, :global, include: ~w(disabled required readonly autofocus))

  def slider(assigns) do
    %{id: id, name: name, value: value, errors: errors} = field_attrs(assigns.field)
    assigns = assign(assigns, id: id, name: name, value: value, errors: errors)
    assigns = assign(assigns, :computed_class, cn([@base, assigns.class]))

    ~H"""
    <input
      type="range"
      data-slot="slider"
      id={@id}
      name={@name}
      value={@value}
      min={@min}
      max={@max}
      step={@step}
      aria-invalid={@errors != [] && "true"}
      aria-describedby={(@errors != [] && "#{@id}-error") || nil}
      class={@computed_class}
      {@rest}
    />
    <p
      :if={@errors != []}
      id={"#{@id}-error"}
      data-slot="form-message"
      class="text-destructive text-sm mt-1"
    >
      {List.first(@errors)}
    </p>
    """
  end
end