DFE-Digital/govuk-components

View on GitHub
app/components/govuk_component/pagination_component.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
class GovukComponent::PaginationComponent < GovukComponent::Base
  include Pagy::UrlHelpers

  attr_reader :pagy,
              :next_text,
              :previous_text,
              :page_items,
              :previous_content,
              :next_content,
              :block_mode,
              :landmark_label

  alias_method :block_mode?, :block_mode

  renders_many :items, "GovukComponent::PaginationComponent::Item"

  renders_one :next_page, ->(href:, text: default_adjacent_text(:next), label_text: nil, classes: [], html_attributes: {}) do
    GovukComponent::PaginationComponent::NextPage.new(
      text:,
      href:,
      label_text:,
      block_mode: block_mode?,
      classes:,
      html_attributes:
    )
  end

  renders_one :previous_page, ->(href:, text: default_adjacent_text(:prev), label_text: nil, classes: [], html_attributes: {}) do
    GovukComponent::PaginationComponent::PreviousPage.new(
      text:,
      href:,
      label_text:,
      block_mode: block_mode?,
      classes:,
      html_attributes:
    )
  end

  def initialize(pagy: nil,
                 next_text: nil,
                 previous_text: nil,
                 block_mode: false,
                 landmark_label: config.default_pagination_landmark_label,
                 classes: [],
                 html_attributes: {})
    @pagy                          = pagy
    @next_text                     = next_text
    @previous_text                 = previous_text
    @block_mode                    = block_mode
    @landmark_label                = landmark_label

    super(classes:, html_attributes:)
  end

  def before_render
    @page_items = if pagy.present?
                    build_items
                  elsif items.any?
                    items
                  else
                    []
                  end

    @previous_content = previous_page || build_previous
    @next_content     = next_page || build_next
  end

  def call
    attributes = html_attributes.tap { |ha| (ha[:class] << "#{brand}-pagination--block") if items.empty? }

    tag.nav(**attributes) do
      safe_join([
        previous_content,
        tag.ul(class: "#{brand}-pagination__list") { safe_join(page_items) },
        next_content
      ])
    end
  end

  def render?
    # probably isn't any point rendering if there's only one page
    (pagy.present? && pagy.series.size > 1) || @previous_content.present? || @next_content.present?
  end

private

  def default_attributes
    { aria: { label: landmark_label }, class: "#{brand}-pagination" }
  end

  def build_previous
    return unless pagy&.prev

    kwargs = {
      href: pagy_url_for(pagy, pagy.prev),
      text: @previous_text,
    }

    with_previous_page(**kwargs.compact)
  end

  def build_next
    return unless pagy&.next

    kwargs = {
      href: pagy_url_for(pagy, pagy.next),
      text: @next_text,
    }

    with_next_page(**kwargs.compact)
  end

  def build_items
    pagy.series.map { |i| with_item(number: i, href: pagy_url_for(pagy, i), from_pagy: true) }
  end

  def default_adjacent_text(side)
    visible, hidden = *case side
                       when :next
                         config.default_pagination_next_text
                       when :prev
                         config.default_pagination_previous_text
                       end

    return visible if hidden.blank?

    (visible + tag.span(" #{hidden}", class: "#{brand}-visually-hidden")).html_safe
  end
end