mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Merge pull request #22475 from opf/71063-create-a-pagination-component-based-on-the-primer-specification
[71063] Update PVC with new Pagination component and Banner styles
This commit is contained in:
committed by
GitHub
parent
5c6b584946
commit
7014e13d3e
@@ -432,4 +432,4 @@ end
|
||||
|
||||
gem "openproject-octicons", "~>19.32.0"
|
||||
gem "openproject-octicons_helper", "~>19.32.0"
|
||||
gem "openproject-primer_view_components", "~>0.83.0"
|
||||
gem "openproject-primer_view_components", "~>0.84.1"
|
||||
|
||||
+3
-3
@@ -902,7 +902,7 @@ GEM
|
||||
actionview
|
||||
openproject-octicons (= 19.32.1)
|
||||
railties
|
||||
openproject-primer_view_components (0.83.0)
|
||||
openproject-primer_view_components (0.84.1)
|
||||
actionview (>= 7.2.0)
|
||||
activesupport (>= 7.2.0)
|
||||
openproject-octicons (>= 19.30.1)
|
||||
@@ -1687,7 +1687,7 @@ DEPENDENCIES
|
||||
openproject-octicons (~> 19.32.0)
|
||||
openproject-octicons_helper (~> 19.32.0)
|
||||
openproject-openid_connect!
|
||||
openproject-primer_view_components (~> 0.83.0)
|
||||
openproject-primer_view_components (~> 0.84.1)
|
||||
openproject-recaptcha!
|
||||
openproject-reporting!
|
||||
openproject-storages!
|
||||
@@ -2070,7 +2070,7 @@ CHECKSUMS
|
||||
openproject-octicons (19.32.1) sha256=32253f3256ad4e1aec36442558ce140623c01e5241d9b90f6eb6d317f462781e
|
||||
openproject-octicons_helper (19.32.1) sha256=7676059927ae940170fb13d62f88b885985a3f0d483e1bb246475afcffd90f8f
|
||||
openproject-openid_connect (1.0.0)
|
||||
openproject-primer_view_components (0.83.0) sha256=5e0b89f6467f5c69f017dd14723be28dcc34c842312999c954184ef5b82b83b9
|
||||
openproject-primer_view_components (0.84.1) sha256=ea0a8da1bc45c8f0ddc13ab279535297ca63f974da72449fbb3a9e4b4e9753d7
|
||||
openproject-recaptcha (1.0.0)
|
||||
openproject-reporting (1.0.0)
|
||||
openproject-storages (1.0.0)
|
||||
|
||||
@@ -39,7 +39,7 @@ module PaginationHelper
|
||||
return unless paginator.total_entries > 0
|
||||
|
||||
content_tag(:div, class: "op-pagination") do
|
||||
concat pagination_pages_section(paginator, renderer: OpenProject::LinkRenderer, params:, allowed_params:, **)
|
||||
concat pagination_pages_section(paginator, params:, allowed_params:, **)
|
||||
concat pagination_options_section(paginator, params:, allowed_params:) if per_page_links
|
||||
end
|
||||
end
|
||||
@@ -113,12 +113,57 @@ module PaginationHelper
|
||||
|
||||
private
|
||||
|
||||
def pagination_pages_section(paginator, **)
|
||||
content_tag(:nav, class: "op-pagination--pages", aria: { label: I18n.t(:"js.pagination.page_navigation") }) do
|
||||
pagination_entries(paginator, **)
|
||||
def pagination_pages_section(paginator, params:, allowed_params:, **)
|
||||
content_tag(:div, class: "op-pagination--pages") do
|
||||
safe_join(
|
||||
[
|
||||
render_primer_pagination(paginator, params:, allowed_params:, **),
|
||||
pagination_range(paginator)
|
||||
]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def render_primer_pagination(paginator, params:, allowed_params:, turbo: false, turbo_action: nil, **)
|
||||
link_arguments = {}
|
||||
link_arguments[:data] = {}
|
||||
link_arguments[:data][:turbo_stream] = true if turbo
|
||||
link_arguments[:data][:turbo_action] = turbo_action if turbo_action.present?
|
||||
link_arguments.delete(:data) if link_arguments[:data].empty?
|
||||
|
||||
render(
|
||||
Primer::OpenProject::Pagination.new(
|
||||
page_count: paginator.total_pages,
|
||||
current_page: paginator.current_page,
|
||||
href_builder: ->(page) { pagination_href(page, params:, allowed_params:) },
|
||||
link_arguments:
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def pagination_href(page, params:, allowed_params:)
|
||||
allowed_params ||= %w[filters sortBy]
|
||||
|
||||
url_for(
|
||||
params
|
||||
.merge(page:)
|
||||
.merge(safe_query_params(allowed_params))
|
||||
)
|
||||
end
|
||||
|
||||
def pagination_range(paginator)
|
||||
page_first = paginator.offset + 1
|
||||
page_last = paginator.offset + paginator.length
|
||||
total = paginator.total_entries
|
||||
|
||||
content_tag(
|
||||
:div,
|
||||
"(#{page_first} - #{page_last}/#{total})",
|
||||
class: "op-pagination--range",
|
||||
aria: { live: "polite" }
|
||||
)
|
||||
end
|
||||
|
||||
def pagination_options_section(paginator, params:, allowed_params:)
|
||||
per_page_options = Setting.per_page_options_array
|
||||
return "" if per_page_options.empty?
|
||||
@@ -135,31 +180,11 @@ module PaginationHelper
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Builds the pagination nav with pages and range
|
||||
def pagination_entries(paginator, **)
|
||||
page_first = paginator.offset + 1
|
||||
page_last = paginator.offset + paginator.length
|
||||
total = paginator.total_entries
|
||||
|
||||
content_tag(:ul, class: "op-pagination--items op-pagination--items_start", role: "presentation") do
|
||||
concat will_paginate(paginator, **, container: false)
|
||||
concat content_tag(
|
||||
:li,
|
||||
"(#{page_first} - #{page_last}/#{total})",
|
||||
class: "op-pagination--range",
|
||||
aria: { live: "polite" }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def pagination_options_list(per_pages, current_per_page:, **)
|
||||
content_tag(:ul, class: "op-pagination--items op-pagination--items_end", role: "presentation") do
|
||||
safe_join [
|
||||
content_tag(:li, I18n.t(:label_per_page), class: "op-pagination--label"),
|
||||
per_pages.map { |per_page| pagination_options_item(per_page, current: per_page == current_per_page, **) }
|
||||
]
|
||||
end
|
||||
safe_join [
|
||||
content_tag(:span, I18n.t(:label_per_page), class: "op-pagination--label"),
|
||||
per_pages.map { |per_page| pagination_options_item(per_page, current: per_page == current_per_page, **) }
|
||||
]
|
||||
end
|
||||
|
||||
##
|
||||
@@ -167,15 +192,15 @@ module PaginationHelper
|
||||
# determined from available options in the settings.
|
||||
def pagination_options_item(per_page, current:, **options)
|
||||
label = I18n.t("js.pagination.pages.show_per_page", number: per_page)
|
||||
content_tag(:li, class: ["op-pagination--item", { "op-pagination--item_current": current }]) do
|
||||
link_to_unless(
|
||||
current,
|
||||
per_page,
|
||||
options.merge(page: 1, per_page:),
|
||||
class: "op-pagination--item-link", aria: { label: }, target: "_top"
|
||||
) do
|
||||
content_tag(:span, per_page, aria: { label:, current: "page" }, tabindex: 0)
|
||||
end
|
||||
end
|
||||
aria_props = { label: }
|
||||
aria_props[:current] = "page" if current
|
||||
|
||||
link_to(
|
||||
per_page,
|
||||
options.merge(page: 1, per_page:),
|
||||
class: "Page",
|
||||
aria: aria_props,
|
||||
target: "_top"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
[data-color-mode][data-dark-theme=dark] {
|
||||
--fgColor-accent: var(--accent-color) !important;
|
||||
--fgColor-link: var(--accent-color) !important;
|
||||
--bgColor-accent-emphasis: var(--accent-color) !important;
|
||||
--control-checked-bgColor-rest: var(--control-checked-color) !important;
|
||||
--control-checked-bgColor-active: var(--control-checked-color) !important;
|
||||
--control-checked-bgColor-hover: var(--control-checked-color--major1) !important;
|
||||
|
||||
@@ -716,6 +716,15 @@ en:
|
||||
confirmation_live_message_checked: "The button to proceed is now active."
|
||||
confirmation_live_message_unchecked: "The button to proceed is now inactive. You need to tick the checkbox to continue."
|
||||
|
||||
pagination:
|
||||
label: "Pagination"
|
||||
prev: "Previous"
|
||||
prev_page: "Previous Page"
|
||||
next: "Next"
|
||||
next_page: "Next Page"
|
||||
page: "Page %{number}"
|
||||
page_with_more: "Page %{number}..."
|
||||
|
||||
mcp_configurations:
|
||||
server_url_component:
|
||||
caption: "The URL at which the OpenProject MCP server will be reachable. Required for setting up MCP clients."
|
||||
|
||||
Generated
+14
-14
@@ -57,12 +57,12 @@
|
||||
"@ng-select/ng-select": "^20.1.0",
|
||||
"@ngneat/content-loader": "^7.0.0",
|
||||
"@openproject/octicons-angular": "^19.32.0",
|
||||
"@openproject/primer-view-components": "^0.83.0",
|
||||
"@openproject/primer-view-components": "^0.84.1",
|
||||
"@openproject/reactivestates": "^3.0.1",
|
||||
"@primer/css": "^22.1.0",
|
||||
"@primer/live-region-element": "^0.8.0",
|
||||
"@primer/primitives": "^11.5.1",
|
||||
"@primer/view-components": "npm:@openproject/primer-view-components@^0.83.0",
|
||||
"@primer/view-components": "npm:@openproject/primer-view-components@^0.84.1",
|
||||
"@rails/request.js": "^0.0.13",
|
||||
"@stimulus-components/auto-submit": "^6.0.0",
|
||||
"@stimulus-components/reveal": "^5.0.0",
|
||||
@@ -7428,9 +7428,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@openproject/primer-view-components": {
|
||||
"version": "0.83.0",
|
||||
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.83.0.tgz",
|
||||
"integrity": "sha512-7u34bIvgsSMey1ju4rriISsUSVhCwxFHH6jInSO85YtucdlTvcKaPVxAtLCGubUl+NbnW2x9fCDNrajha7G7/g==",
|
||||
"version": "0.84.1",
|
||||
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.84.1.tgz",
|
||||
"integrity": "sha512-ouSnwxqn78MxfcnLo+twd2W15cOJnsC+nTEqwLb76tGpW5HNF0R1gVdgRcmym/3SmcP+4eA8PPMIJOtGsieJCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@github/auto-check-element": "^6.0.0",
|
||||
@@ -7832,9 +7832,9 @@
|
||||
},
|
||||
"node_modules/@primer/view-components": {
|
||||
"name": "@openproject/primer-view-components",
|
||||
"version": "0.83.0",
|
||||
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.83.0.tgz",
|
||||
"integrity": "sha512-7u34bIvgsSMey1ju4rriISsUSVhCwxFHH6jInSO85YtucdlTvcKaPVxAtLCGubUl+NbnW2x9fCDNrajha7G7/g==",
|
||||
"version": "0.84.1",
|
||||
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.84.1.tgz",
|
||||
"integrity": "sha512-ouSnwxqn78MxfcnLo+twd2W15cOJnsC+nTEqwLb76tGpW5HNF0R1gVdgRcmym/3SmcP+4eA8PPMIJOtGsieJCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@github/auto-check-element": "^6.0.0",
|
||||
@@ -30413,9 +30413,9 @@
|
||||
}
|
||||
},
|
||||
"@openproject/primer-view-components": {
|
||||
"version": "0.83.0",
|
||||
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.83.0.tgz",
|
||||
"integrity": "sha512-7u34bIvgsSMey1ju4rriISsUSVhCwxFHH6jInSO85YtucdlTvcKaPVxAtLCGubUl+NbnW2x9fCDNrajha7G7/g==",
|
||||
"version": "0.84.1",
|
||||
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.84.1.tgz",
|
||||
"integrity": "sha512-ouSnwxqn78MxfcnLo+twd2W15cOJnsC+nTEqwLb76tGpW5HNF0R1gVdgRcmym/3SmcP+4eA8PPMIJOtGsieJCQ==",
|
||||
"requires": {
|
||||
"@github/auto-check-element": "^6.0.0",
|
||||
"@github/auto-complete-element": "^3.8.0",
|
||||
@@ -30608,9 +30608,9 @@
|
||||
"integrity": "sha512-NB9uYfJ01FVY6zp+33EoUbJ0paS3JrWY+PqdHPebTvyRtQgL3sX8//3jWqjt3/jL81UMEulJRM2A0hPj0/vFpQ=="
|
||||
},
|
||||
"@primer/view-components": {
|
||||
"version": "npm:@openproject/primer-view-components@0.83.0",
|
||||
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.83.0.tgz",
|
||||
"integrity": "sha512-7u34bIvgsSMey1ju4rriISsUSVhCwxFHH6jInSO85YtucdlTvcKaPVxAtLCGubUl+NbnW2x9fCDNrajha7G7/g==",
|
||||
"version": "npm:@openproject/primer-view-components@0.84.1",
|
||||
"resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.84.1.tgz",
|
||||
"integrity": "sha512-ouSnwxqn78MxfcnLo+twd2W15cOJnsC+nTEqwLb76tGpW5HNF0R1gVdgRcmym/3SmcP+4eA8PPMIJOtGsieJCQ==",
|
||||
"requires": {
|
||||
"@github/auto-check-element": "^6.0.0",
|
||||
"@github/auto-complete-element": "^3.8.0",
|
||||
|
||||
@@ -112,12 +112,12 @@
|
||||
"@ng-select/ng-select": "^20.1.0",
|
||||
"@ngneat/content-loader": "^7.0.0",
|
||||
"@openproject/octicons-angular": "^19.32.0",
|
||||
"@openproject/primer-view-components": "^0.83.0",
|
||||
"@openproject/primer-view-components": "^0.84.1",
|
||||
"@openproject/reactivestates": "^3.0.1",
|
||||
"@primer/css": "^22.1.0",
|
||||
"@primer/live-region-element": "^0.8.0",
|
||||
"@primer/primitives": "^11.5.1",
|
||||
"@primer/view-components": "npm:@openproject/primer-view-components@^0.83.0",
|
||||
"@primer/view-components": "npm:@openproject/primer-view-components@^0.84.1",
|
||||
"@rails/request.js": "^0.0.13",
|
||||
"@stimulus-components/auto-submit": "^6.0.0",
|
||||
"@stimulus-components/reveal": "^5.0.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@if (pagination && isVisible && pagination.total) {
|
||||
<div class="op-pagination">
|
||||
<nav class="op-pagination--pages" [attr.aria-label]="text.page_navigation">
|
||||
<ul class="op-pagination--items op-pagination--items_start" role="presentation">
|
||||
<ul class="op-pagination--items" role="presentation">
|
||||
<li [hidden]="pagination.page === 1 || !showPageSelections" class="op-pagination--item">
|
||||
<button
|
||||
class="op-pagination--item-link op-pagination--item-link_prev"
|
||||
@@ -109,7 +109,7 @@
|
||||
<div class="op-pagination--options">
|
||||
<nav [attr.aria-label]="text.per_page_navigation">
|
||||
<ul
|
||||
class="op-pagination--items op-pagination--items_end"
|
||||
class="op-pagination--items"
|
||||
role="presentation">
|
||||
<li
|
||||
class="op-pagination--label"
|
||||
|
||||
@@ -35,17 +35,19 @@ $pagination--font-size: 0.8125rem
|
||||
min-height: 45px
|
||||
|
||||
&--pages
|
||||
display: flex
|
||||
flex-grow: 2
|
||||
flex-shrink: 2
|
||||
margin: 10px 5px 10px 0
|
||||
|
||||
&--options
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: end
|
||||
flex-grow: 1
|
||||
flex-shrink: 1
|
||||
margin: 10px 0 0 5px
|
||||
|
||||
@media screen and (max-width: $breakpoint-sm)
|
||||
display: none
|
||||
display: none
|
||||
|
||||
&--items
|
||||
list-style-type: none
|
||||
@@ -55,12 +57,6 @@ $pagination--font-size: 0.8125rem
|
||||
padding: 0
|
||||
font-size: $pagination--font-size
|
||||
|
||||
&_start
|
||||
justify-content: flex-start
|
||||
|
||||
&_end
|
||||
justify-content: flex-end
|
||||
|
||||
&--item
|
||||
min-width: 25px
|
||||
margin: 0 5px 0 0
|
||||
@@ -126,11 +122,15 @@ $pagination--font-size: 0.8125rem
|
||||
|
||||
&--range,
|
||||
&--info
|
||||
display: flex
|
||||
align-items: center
|
||||
flex: 1
|
||||
margin: 0 0 0 5px
|
||||
padding: 3px 0
|
||||
display: block
|
||||
@include text-shortener
|
||||
|
||||
&--info
|
||||
flex-basis: auto
|
||||
|
||||
&--range,
|
||||
&--label
|
||||
color: var(--fgColor-muted)
|
||||
|
||||
@@ -166,3 +166,19 @@ ul.SegmentedControl,
|
||||
|
||||
.Box-row:is(.Box-row--draggable)
|
||||
padding-left: 0
|
||||
|
||||
// Apply the mobile styles as soon as the banner itself is small
|
||||
// Styles are copied from the PVC repo
|
||||
.op-primer-flash
|
||||
x-banner
|
||||
container-type: inline-size
|
||||
display: block
|
||||
|
||||
.Banner
|
||||
@container (max-width: 543.98px)
|
||||
grid-template-areas: 'visual message close' '. actions actions'
|
||||
grid-template-columns: min-content 1fr min-content
|
||||
grid-template-rows: min-content min-content
|
||||
|
||||
& .Banner-actions
|
||||
margin: var(--base-size-8) 0 0 var(--base-size-8)
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License version 3.
|
||||
#
|
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
# Copyright (C) 2010-2013 the ChiliProject Team
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# See COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
module OpenProject
|
||||
class LinkRenderer < ::WillPaginate::ActionView::LinkRenderer
|
||||
include ActionView::Helpers::OutputSafetyHelper
|
||||
|
||||
protected
|
||||
|
||||
def merge_get_params(url_params)
|
||||
params = super
|
||||
allowed_params ? params.slice(*allowed_params) : params
|
||||
end
|
||||
|
||||
def page_number(page)
|
||||
label = I18n.t("js.pagination.pages.page_number", number: page)
|
||||
if page == current_page
|
||||
tag(:li,
|
||||
tag(:em, page, "aria-label": label, "aria-current": "page", tabindex: 0),
|
||||
class: "op-pagination--item op-pagination--item_current")
|
||||
else
|
||||
tag(:li,
|
||||
link(page, page, class: "op-pagination--item-link", "aria-label": label),
|
||||
class: "op-pagination--item")
|
||||
end
|
||||
end
|
||||
|
||||
def gap
|
||||
tag(:li,
|
||||
tag(:span, "…", "aria-hidden": "true") +
|
||||
tag(:span, I18n.t(:"js.pagination.pages_skipped"), class: "sr-only"),
|
||||
class: "op-pagination--space")
|
||||
end
|
||||
|
||||
def previous_page
|
||||
num = @collection.current_page > 1 && (@collection.current_page - 1)
|
||||
previous_or_next_page(
|
||||
num,
|
||||
safe_join_components(
|
||||
render_octicon(:"chevron-left", class_suffix: "prev"),
|
||||
I18n.t(:label_previous)
|
||||
),
|
||||
"prev"
|
||||
)
|
||||
end
|
||||
|
||||
def next_page
|
||||
num = @collection.current_page < total_pages && (@collection.current_page + 1)
|
||||
previous_or_next_page(
|
||||
num,
|
||||
safe_join_components(
|
||||
I18n.t(:label_next),
|
||||
render_octicon(:"chevron-right", class_suffix: "next")
|
||||
),
|
||||
"next"
|
||||
)
|
||||
end
|
||||
|
||||
def previous_or_next_page(page, text, class_suffix)
|
||||
if page
|
||||
tag(:li,
|
||||
link(text, page, { class: "op-pagination--item-link op-pagination--item-link_#{class_suffix}" }),
|
||||
class: "op-pagination--item")
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def link(text, target, attributes)
|
||||
new_attributes = attributes.dup
|
||||
new_attributes["data-turbo-stream"] = true if turbo?
|
||||
new_attributes["data-turbo-action"] = turbo_action if turbo_action.present?
|
||||
|
||||
super(text, target, new_attributes)
|
||||
end
|
||||
|
||||
def allowed_params
|
||||
@options[:allowed_params]
|
||||
end
|
||||
|
||||
# Customize the Turbo visit action for pagination links. Can be set to "advance" or "replace".
|
||||
# "advance" - push a new entry onto the history stack.
|
||||
# "replace" - replace the current history entry.
|
||||
# See: https://turbo.hotwired.dev/reference/attributes
|
||||
#
|
||||
# Example: Promoting a Frame Navigation to a Page Visit
|
||||
# By default navigation within a turbo frame does not change the rest of the browser's state,
|
||||
# but you can promote a frame navigation a "Visit" by setting the turbo-action attribute to "advance".
|
||||
# See: https://turbo.hotwired.dev/handbook/frames#promoting-a-frame-navigation-to-a-page-visit
|
||||
#
|
||||
def turbo_action
|
||||
@options[:turbo_action]
|
||||
end
|
||||
|
||||
def turbo?
|
||||
@options[:turbo]
|
||||
end
|
||||
|
||||
def safe_join_components(*components)
|
||||
safe_join(components, " ")
|
||||
end
|
||||
|
||||
def render_octicon(icon_name, class_suffix:, **)
|
||||
@template.render(
|
||||
Primer::Beta::Octicon.new(
|
||||
icon_name,
|
||||
size: :xsmall,
|
||||
classes: ["op-pagination--item-link-icon", "op-pagination--item-link-icon_#{class_suffix}"],
|
||||
**
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,27 +0,0 @@
|
||||
<div class="op-pagination">
|
||||
<nav class="op-pagination--pages">
|
||||
<ul class="op-pagination--items op-pagination--items_start">
|
||||
<li class="op-pagination--item -prev"><a class="op-pagination--item-link">Previous</a></li>
|
||||
<li class="op-pagination--item"><a class="op-pagination--item-link">1</a></li>
|
||||
<li class="op-pagination--item"><a class="op-pagination--item-link">2</a></li>
|
||||
<li class="op-pagination--item op-pagination--item op-pagination--item_current">3</li>
|
||||
<li class="op-pagination--item"><a class="op-pagination--item-link">4</a></li>
|
||||
<li class="op-pagination--item"><a class="op-pagination--item-link">5</a></li>
|
||||
<li class="op-pagination--space">…</li>
|
||||
<li class="op-pagination--item"><a class="op-pagination--item-link">34</a></li>
|
||||
<li class="op-pagination--item"><a class="op-pagination--item-link">35</a></li>
|
||||
<li class="op-pagination--item -next"><a class="op-pagination--item-link">Next</a></li>
|
||||
<li class="op-pagination--range" title="(1 - 50/820)">(1 - 50/820)</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="op-pagination--options">
|
||||
<ul class="op-pagination--items op-pagination--items_end">
|
||||
<li class="op-pagination--label" title="Per page:">Per page:</li>
|
||||
<li class="op-pagination--item"><a class="op-pagination--item-link">10</a></li>
|
||||
<li class="op-pagination--item"><a class="op-pagination--item-link">20</a></li>
|
||||
<li class="op-pagination--item op-pagination--item op-pagination--item_current">50</li>
|
||||
<li class="op-pagination--item"><a class="op-pagination--item-link">100</a></li>
|
||||
<li class="op-pagination--item"><a class="op-pagination--item-link">200</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,7 +98,9 @@ module Pages
|
||||
end
|
||||
|
||||
def expect_to_be_on_page(number)
|
||||
expect(page).to have_css(".op-pagination--item_current", text: number)
|
||||
within ".PaginationContainer" do
|
||||
expect(page).to have_css("a.Page", text: number, aria: { current: "page" })
|
||||
end
|
||||
end
|
||||
|
||||
def to_page(number)
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
end
|
||||
end
|
||||
|
||||
component.with_row do
|
||||
component.with_row(py: 0) do
|
||||
helpers.pagination_links_full(documents)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -167,7 +167,7 @@ RSpec.describe "Admin lists project mappings for a storage",
|
||||
|
||||
aggregate_failures "pagination links maintain the correct url" do
|
||||
within ".op-pagination" do
|
||||
pagination_links = page.all(".op-pagination--item-link")
|
||||
pagination_links = page.all("a.Page")
|
||||
expect(pagination_links.size).to be_positive
|
||||
|
||||
pagination_links.each do |pagination_link|
|
||||
|
||||
@@ -74,11 +74,18 @@ RSpec.describe PaginationHelper do
|
||||
end
|
||||
|
||||
it "renders a labelled nav element" do
|
||||
expect(pagination).to have_element "nav", aria: { label: "Pagination navigation" }
|
||||
expect(pagination).to have_element "nav", aria: { label: "Pagination" }
|
||||
end
|
||||
|
||||
it "renders 2 presentational lists" do
|
||||
expect(pagination).to have_css "ul.op-pagination--items[role=presentation]", count: 2
|
||||
it "renders the main pagination nav" do
|
||||
expect(pagination).to have_css("nav[aria-label='Pagination']")
|
||||
end
|
||||
|
||||
it "renders the per-page options list" do
|
||||
expect(pagination).to have_css(".op-pagination--options")
|
||||
expect(pagination).to have_css("nav", accessible_name: "Items per page selection")
|
||||
expect(pagination).to have_css(".op-pagination--label", text: "Per page")
|
||||
expect(pagination).to have_css("a.Page", minimum: 1)
|
||||
end
|
||||
|
||||
it "has a next page link" do
|
||||
@@ -95,8 +102,9 @@ RSpec.describe PaginationHelper do
|
||||
it "has links to every page except the current one" do
|
||||
pages.excluding(current_page).each do |page|
|
||||
path = work_packages_path(page:)
|
||||
expect(pagination).to have_link text: page, exact_text: true, href: path, current: nil do |link|
|
||||
expect(pagination).to have_link(page.to_s, href: path) do |link|
|
||||
expect(link["aria-label"]).to eq "Page #{page}"
|
||||
expect(link["aria-current"]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -105,11 +113,9 @@ RSpec.describe PaginationHelper do
|
||||
expect(pagination).to have_no_link text: current_page, exact_text: true, current: "page"
|
||||
end
|
||||
|
||||
it "has a focusable element for the current page" do
|
||||
expect(pagination).to have_css ".op-pagination--item_current", text: current_page, exact_text: true
|
||||
expect(pagination).to have_element text: current_page, exact_text: true, aria: { current: "page" } do |element|
|
||||
it "renders the current page with aria-current" do
|
||||
expect(pagination).to have_element(aria: { current: "page" }, text: current_page.to_s) do |element|
|
||||
expect(element["aria-label"]).to eq "Page #{current_page}"
|
||||
expect(element["tabindex"]).to eq "0"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,7 +135,10 @@ RSpec.describe PaginationHelper do
|
||||
|
||||
href = work_packages_path({ page: i }.merge(params))
|
||||
|
||||
expect(pagination).to have_link text: i, exact_text: true, href:, current: nil
|
||||
expect(pagination).to have_link(i.to_s, href:) do |link|
|
||||
expect(link["aria-label"]).to eq "Page #{i}"
|
||||
expect(link["aria-current"]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -143,29 +152,23 @@ RSpec.describe PaginationHelper do
|
||||
expect(pagination).to have_element "nav", aria: { label: "Items per page selection" }
|
||||
end
|
||||
|
||||
it "has a link to every per page option except the current one" do
|
||||
it "has a link to every per page option" do
|
||||
expect(pagination).to have_css(".op-pagination--options") do |pagination_options|
|
||||
[50, 100].each do |other_per_page|
|
||||
[10, 50, 100].each do |other_per_page|
|
||||
path = work_packages_path(page: current_page, per_page: other_per_page)
|
||||
expect(pagination_options).to have_link href: path, current: nil do |link|
|
||||
expect(pagination_options).to have_link href: path do |link|
|
||||
expect(link["aria-label"]).to eq "Show #{other_per_page} per page"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "does not have a link to the current per page option" do
|
||||
it "marks the current per page option with aria-current" do
|
||||
expect(pagination).to have_css(".op-pagination--options") do |pagination_options|
|
||||
expect(pagination_options).to have_no_link text: per_page, exact_text: true, current: "page"
|
||||
end
|
||||
end
|
||||
|
||||
it "has a focusable element for the current per page option" do
|
||||
expect(pagination).to have_css(".op-pagination--options") do |pagination_options|
|
||||
expect(pagination_options).to have_css(".op-pagination--item_current", text: per_page)
|
||||
expect(pagination_options).to have_element text: per_page, exact_text: true, aria: { current: "page" } do |element|
|
||||
expect(element["aria-label"]).to eq "Show #{per_page} per page"
|
||||
expect(element["tabindex"]).to eq "0"
|
||||
expect(pagination_options).to have_link(per_page.to_s) do |link|
|
||||
expect(link["aria-label"]).to eq "Show #{per_page} per page"
|
||||
expect(link["aria-current"]).to eq "page"
|
||||
expect(link["class"]).to include("Page")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -220,7 +223,7 @@ RSpec.describe PaginationHelper do
|
||||
end
|
||||
|
||||
it "does not render a labelled nav element" do
|
||||
expect(pagination).to have_no_element "nav", aria: { label: "Pagination navigation" }
|
||||
expect(pagination).to have_no_element "nav", aria: { label: "Pagination" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -107,49 +107,56 @@ module Pages
|
||||
end
|
||||
|
||||
def expect_current_page_number(number)
|
||||
within ".op-pagination--pages" do
|
||||
expect(page).to have_css(".op-pagination--item_current", text: number)
|
||||
within ".PaginationContainer" do
|
||||
expect(page).to have_css("a.Page", text: number.to_s, aria: { current: "page" })
|
||||
end
|
||||
end
|
||||
|
||||
def expect_total_pages(number)
|
||||
within ".op-pagination--pages" do
|
||||
expect(page).to have_css(".op-pagination--item", text: number)
|
||||
expect(page).to have_no_css(".op-pagination--item", text: number + 1)
|
||||
expect(page).to have_css(".Page", text: number)
|
||||
expect(page).to have_no_css(".Page", text: number + 1)
|
||||
end
|
||||
end
|
||||
|
||||
def expect_page_link(text)
|
||||
within ".op-pagination--pages" do
|
||||
expect(page).to have_css("a.op-pagination--item-link", text:)
|
||||
expect(page).to have_css("a.Page", text:)
|
||||
end
|
||||
end
|
||||
|
||||
def expect_page_links(model:, current_page: 1)
|
||||
within ".op-pagination--pages" do
|
||||
pagination_links = page.all(".op-pagination--item-link")
|
||||
within ".PaginationContainer" do
|
||||
pagination_links = page.all("a.Page")
|
||||
expect(pagination_links.size).to be_positive
|
||||
|
||||
page_number_links = pagination_links.reject { |link| link.text =~ /previous|next/i }
|
||||
page_number_links.each.with_index(1) do |pagination_link, page_number|
|
||||
page_number_links = pagination_links.select { |link| link["aria-label"]&.match?(/\APage \d+\z/) }
|
||||
|
||||
page_number_links.each do |pagination_link|
|
||||
page_number = pagination_link.text.to_i
|
||||
uri = URI.parse(pagination_link["href"])
|
||||
expect(uri.path).to eq(path(model))
|
||||
expect(uri.query).to include("page=#{page_number}")
|
||||
end
|
||||
|
||||
if current_page > 1
|
||||
expect(page).to have_link("Previous", href: "#{path(model)}?#{{ page: current_page - 1 }.to_query}")
|
||||
previous_link = find("a[rel='prev']", visible: true)
|
||||
previous_uri = URI.parse(previous_link["href"])
|
||||
expect(previous_uri.path).to eq(path(model))
|
||||
expect(previous_uri.query).to include("page=#{current_page - 1}")
|
||||
else
|
||||
expect(page).to have_link("Next", href: "#{path(model)}?#{{ page: current_page + 1 }.to_query}")
|
||||
next_link = find("a[rel='next']", visible: true)
|
||||
next_uri = URI.parse(next_link["href"])
|
||||
expect(next_uri.path).to eq(path(model))
|
||||
expect(next_uri.query).to include("page=#{current_page + 1}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def expect_page_sizes(model:)
|
||||
within ".op-pagination--options" do
|
||||
pagination_links = page.all(".op-pagination--item-link")
|
||||
pagination_links = page.all("a.Page")
|
||||
expect(pagination_links.size).to be_positive
|
||||
expect(page).to have_css(".op-pagination--item_current")
|
||||
|
||||
pagination_links.each do |pagination_link|
|
||||
uri = URI.parse(pagination_link["href"])
|
||||
@@ -443,19 +450,19 @@ module Pages
|
||||
|
||||
def set_page_size(size)
|
||||
within ".op-pagination--options" do
|
||||
find(".op-pagination--item", text: size).click
|
||||
find("a.Page", text: size.to_s).click
|
||||
end
|
||||
end
|
||||
|
||||
def expect_page_size(size)
|
||||
within ".op-pagination--options" do
|
||||
expect(page).to have_css(".op-pagination--item_current", text: size)
|
||||
expect(page).to have_css("a.Page", text: /\A#{size}\z/, aria: { current: "page" })
|
||||
end
|
||||
end
|
||||
|
||||
def go_to_page(page_number)
|
||||
within ".op-pagination--pages" do
|
||||
find(".op-pagination--item-link", text: page_number).click
|
||||
within ".PaginationContainer" do
|
||||
click_link accessible_name: "Page #{page_number}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Reference in New Issue
Block a user