From 7014e13d3e6799faeaf4e048c44b6d50416207d2 Mon Sep 17 00:00:00 2001 From: Behrokh Satarnejad <62008897+bsatarnejad@users.noreply.github.com> Date: Wed, 25 Mar 2026 14:02:04 +0100 Subject: [PATCH] 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 --- Gemfile | 2 +- Gemfile.lock | 6 +- app/helpers/pagination_helper.rb | 101 ++++++++----- .../custom_styles/_primer_color_mapping.erb | 1 + config/locales/en.yml | 9 ++ frontend/package-lock.json | 28 ++-- frontend/package.json | 4 +- .../table-pagination.component.html | 4 +- .../global_styles/content/_pagination.sass | 22 +-- .../src/global_styles/primer/_overrides.sass | 16 ++ lib/open_project/link_renderer.rb | 143 ------------------ .../pagination_preview/default.html.erb | 27 ---- .../spec/features/support/board_list_page.rb | 4 +- .../documents/list_component.html.erb | 2 +- .../storages/admin/project_storages_spec.rb | 2 +- spec/helpers/pagination_helper_spec.rb | 51 ++++--- spec/support/pages/projects/index.rb | 41 ++--- 17 files changed, 178 insertions(+), 285 deletions(-) delete mode 100644 lib/open_project/link_renderer.rb delete mode 100644 lookbook/previews/open_project/deprecated/pagination_preview/default.html.erb diff --git a/Gemfile b/Gemfile index a12fd69e898..548a87c64e5 100644 --- a/Gemfile +++ b/Gemfile @@ -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" diff --git a/Gemfile.lock b/Gemfile.lock index ecb24a88f51..ec987c85987 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/helpers/pagination_helper.rb b/app/helpers/pagination_helper.rb index 808b6bc1d54..84921efd886 100644 --- a/app/helpers/pagination_helper.rb +++ b/app/helpers/pagination_helper.rb @@ -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 diff --git a/app/views/custom_styles/_primer_color_mapping.erb b/app/views/custom_styles/_primer_color_mapping.erb index 9ede993ef8a..37ad04283f5 100644 --- a/app/views/custom_styles/_primer_color_mapping.erb +++ b/app/views/custom_styles/_primer_color_mapping.erb @@ -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; diff --git a/config/locales/en.yml b/config/locales/en.yml index af9036130b8..03c290cd36d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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." diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0404217de42..f011d32b7a0 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index 90e41e3b33c..041f430a450 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/app/shared/components/table-pagination/table-pagination.component.html b/frontend/src/app/shared/components/table-pagination/table-pagination.component.html index 96f12a403df..8657bf4bdcc 100644 --- a/frontend/src/app/shared/components/table-pagination/table-pagination.component.html +++ b/frontend/src/app/shared/components/table-pagination/table-pagination.component.html @@ -1,7 +1,7 @@ @if (pagination && isVisible && pagination.total) {