diff --git a/Gemfile b/Gemfile index 2ae9d83cd351..ac5c47da0301 100644 --- a/Gemfile +++ b/Gemfile @@ -433,5 +433,5 @@ end source "https://rubygems.org", cooldown: 0 do gem "openproject-octicons", "~>19.35.0" gem "openproject-octicons_helper", "~>19.35.0" - gem "openproject-primer_view_components", "~>0.86.2" + gem "openproject-primer_view_components", "~>0.87.0" end diff --git a/Gemfile.lock b/Gemfile.lock index c1c3ad97243c..2e92b86bde32 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -909,7 +909,7 @@ GEM actionview openproject-octicons (= 19.35.0) railties - openproject-primer_view_components (0.86.2) + openproject-primer_view_components (0.87.0) actionview (>= 7.2.0) activesupport (>= 7.2.0) openproject-octicons (>= 19.34.0) @@ -1688,7 +1688,7 @@ DEPENDENCIES openproject-octicons (~> 19.35.0)! openproject-octicons_helper (~> 19.35.0)! openproject-openid_connect! - openproject-primer_view_components (~> 0.86.2)! + openproject-primer_view_components (~> 0.87.0)! openproject-recaptcha! openproject-reporting! openproject-resource_management! @@ -2070,7 +2070,7 @@ CHECKSUMS openproject-octicons (19.35.0) sha256=a5033550d0961b4a8cb0993512a899716d633e17c2b5147bc6a9ed74f3952b38 openproject-octicons_helper (19.35.0) sha256=c32d142a4bb7fda739b16768aa8846fd88ffc1750509d8056f516056e8767361 openproject-openid_connect (1.0.0) - openproject-primer_view_components (0.86.2) sha256=1725bb2f0b636045a9bc9ff0ab1791d9a9a0341f1d3a0bd06727100120f29cba + openproject-primer_view_components (0.87.0) sha256=df8dfbe765bab12d608e86f428e5e87258ae3be351e88a8701352d7369dbe3b1 openproject-recaptcha (1.0.0) openproject-reporting (1.0.0) openproject-resource_management (1.0.0) diff --git a/app/components/header/projects/filterable_tree_view_component.html.erb b/app/components/header/projects/filterable_tree_view_component.html.erb index aa484ff99594..72decb91c72c 100644 --- a/app/components/header/projects/filterable_tree_view_component.html.erb +++ b/app/components/header/projects/filterable_tree_view_component.html.erb @@ -33,10 +33,7 @@ See COPYRIGHT and LICENSE files for more details. src: tree_src, include_sub_items_check_box_arguments: { hidden: true }, filter_mode_control_arguments: logged? ? {} : { hidden: true }, - filter_input_arguments: { name: "filter", - label: t(:label_filter), - visually_hide_label: true, - autofocus: true, + filter_input_arguments: { autofocus: true, data: { test_selector: "op-header-project-select--search" } }, no_results_node_arguments: { data: { test_selector: "op-header-project-select--no-results" }, label: I18n.t("filterable_tree_view.no_results_text") } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7f53819fbd2b..bf2bc7342d84 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -58,12 +58,12 @@ "@ng-select/ng-select": "^23.0.0", "@ngneat/content-loader": "^7.0.0", "@openproject/octicons-angular": "^19.35.0", - "@openproject/primer-view-components": "^0.86.2", + "@openproject/primer-view-components": "^0.87.0", "@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.86.2", + "@primer/view-components": "npm:@openproject/primer-view-components@^0.87.0", "@rails/request.js": "^0.0.13", "@stimulus-components/auto-submit": "^6.0.0", "@stimulus-components/reveal": "^5.0.0", @@ -5468,9 +5468,9 @@ } }, "node_modules/@openproject/primer-view-components": { - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.86.2.tgz", - "integrity": "sha512-xNkNQm0fkDuBjksGr8UaINdOn1/mjs1FEaJCAXhNWkcZrxaEXgx76mxijt7y4T7I4QNXYgNlaQj/tYlmVJjHsw==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.87.0.tgz", + "integrity": "sha512-EiDHeVjR6psumh+k/JviQuGeCUj3axgkiGrU2fToHWbUz7niWc1YqHPAlj2a2vVR+bMtmN+b1tpSsRvLnzn6FQ==", "license": "MIT", "dependencies": { "@github/auto-check-element": "^6.0.0", @@ -5882,9 +5882,9 @@ }, "node_modules/@primer/view-components": { "name": "@openproject/primer-view-components", - "version": "0.86.2", - "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.86.2.tgz", - "integrity": "sha512-xNkNQm0fkDuBjksGr8UaINdOn1/mjs1FEaJCAXhNWkcZrxaEXgx76mxijt7y4T7I4QNXYgNlaQj/tYlmVJjHsw==", + "version": "0.87.0", + "resolved": "https://registry.npmjs.org/@openproject/primer-view-components/-/primer-view-components-0.87.0.tgz", + "integrity": "sha512-EiDHeVjR6psumh+k/JviQuGeCUj3axgkiGrU2fToHWbUz7niWc1YqHPAlj2a2vVR+bMtmN+b1tpSsRvLnzn6FQ==", "license": "MIT", "dependencies": { "@github/auto-check-element": "^6.0.0", diff --git a/frontend/package.json b/frontend/package.json index db301968acdc..413a8e14ec11 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -104,12 +104,12 @@ "@ng-select/ng-select": "^23.0.0", "@ngneat/content-loader": "^7.0.0", "@openproject/octicons-angular": "^19.35.0", - "@openproject/primer-view-components": "^0.86.2", + "@openproject/primer-view-components": "^0.87.0", "@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.86.2", + "@primer/view-components": "npm:@openproject/primer-view-components@^0.87.0", "@rails/request.js": "^0.0.13", "@stimulus-components/auto-submit": "^6.0.0", "@stimulus-components/reveal": "^5.0.0", diff --git a/lookbook/docs/components/sub-header.md.erb b/lookbook/docs/components/sub-header.md.erb index fe84ba382421..24f47be2f9ad 100644 --- a/lookbook/docs/components/sub-header.md.erb +++ b/lookbook/docs/components/sub-header.md.erb @@ -13,8 +13,10 @@ The SubHeader accepts a number of additional components. **All buttons** within On the left side: - **Search filter**: (Optional) A basic [text input](https://primer.style/components/text-input) with a [search icon](https://primer.style/components/text-input#with-leading-and-trailing-visuals) which is mostly used on index pages to filter the elements below. -- **Filter button:** (Optional) A button which is shown next to the filter input (or on the very left, if there is no filter input) -- **Segmented controls** (optional) to switch two pre-filtered views +- **Sort action** (Optional) A generic slot for a sort button component. It is recommended to use IconButtons here. +- **Group action** (Optional) A generic slot for a group button component. It is recommended to use IconButtons here. +- **Quick filters** (Optional) Up to 5 QuickFilter can be rendered here. When using more then one quick filter, an `All Filter` button is required. +- **All Filter button:** (Optional) A button which is shown next to the filter input (or on the very left, if there is no filter input) At the center of the SubHeader: @@ -33,6 +35,7 @@ The SubHeader automatically adapts to mobile screens: - All actions on both sides (including Buttons, SegmentedControls, ButtonGroups, etc) always turn into icon only variants without labels. - There is a search IconButton; on click, it reveals the search input takes the full length (hiding all other actions), with a cancel button to go back to the previous state +- When using more than one QuickFilter, they are hidden on mobile. The "All filters" button remains visible to preserve functionality. ## Best practices @@ -41,6 +44,7 @@ The SubHeader automatically adapts to mobile screens: - Restrict SubHeader actions to the page's content-specific actions (e.g. filtering or creating a new object). - Use IconButtons instead of Buttons for actions only when the icon is clear enough to describe the action. - When more actions are necessary, or if a menu is needed, use an ActionMenu instead of the ActionButtons. +- Use IconButtons for the sort and group components. **Don't** @@ -122,3 +126,44 @@ We currently only allow async dialogs to be rendered from the SubHeader. So you end %> ``` + +### Sorting and grouping + +The sort and group actions are always displayed after the search but before the quick filters. + +```html + <%= + render(Primer::OpenProject::SubHeader.new) do |component| + component.with_quick_sort do + render(Foo::BarSortComponent.new(..)) + end + + component.with_quick_group do + render(Foo::BarGroupComponent.new(..)) + end + end + %> +``` + +### QuickFilters + +The `with_quick_filter` slot only offers a generic slot for a quick filter taking care of placement, responsiveness and mobile behaviour. The logic is part of the actual [QuickFilter components](../patterns/quick_filters). + +```html + <%= + render(Primer::OpenProject::SubHeader.new) do |component| + component.with_quick_filter do + render(Foo::BarFilterComponent.new(query: @query, project: @project)) + end + component.with_quick_filter do + render(Foo::OtherFilterComponent.new(query: @query, project: @project)) + end + + + # The all filters button is required when using more than one quick filter + component.with_filter_button(mobile_label: "All filters") do + render(Foo::AllFilterComponent.new(query: @query, project: @project)) + end + end + %> +``` diff --git a/lookbook/previews/open_project/common/quick_filter_preview.rb b/lookbook/previews/open_project/filter/simple_list_filter_preview.rb similarity index 89% rename from lookbook/previews/open_project/common/quick_filter_preview.rb rename to lookbook/previews/open_project/filter/simple_list_filter_preview.rb index 73f68014bac1..2847e66f1762 100644 --- a/lookbook/previews/open_project/common/quick_filter_preview.rb +++ b/lookbook/previews/open_project/filter/simple_list_filter_preview.rb @@ -29,10 +29,11 @@ # ++ module OpenProject - module Common - # @logical_path OpenProject/Common + module Filter + # @logical_path OpenProject/Filter # @display min_height 250px - class QuickFilterPreview < Lookbook::Preview + # @label Simple list filtering with stimulus + class SimpleListFilterPreview < Lookbook::Preview def default render_with_template end diff --git a/lookbook/previews/open_project/common/quick_filter_preview/default.html.erb b/lookbook/previews/open_project/filter/simple_list_filter_preview/default.html.erb similarity index 100% rename from lookbook/previews/open_project/common/quick_filter_preview/default.html.erb rename to lookbook/previews/open_project/filter/simple_list_filter_preview/default.html.erb diff --git a/modules/wikis/app/forms/wikis/create_new_wiki_page_form.rb b/modules/wikis/app/forms/wikis/create_new_wiki_page_form.rb index e0f2791a84fb..de117107c14c 100644 --- a/modules/wikis/app/forms/wikis/create_new_wiki_page_form.rb +++ b/modules/wikis/app/forms/wikis/create_new_wiki_page_form.rb @@ -52,14 +52,6 @@ class CreateNewWikiPageForm < ApplicationForm filter_mode_control_arguments: { hidden: true }, filter_input_arguments: { placeholder: I18n.t("wikis.link_existing_wiki_page_form.placeholder"), - # every other property is just refilling the default values, - # as those are not merged into custom arguments - name: :filter, - label: I18n.t(:button_filter), - type: :search, - leading_visual: { icon: :search }, - visually_hide_label: true, - show_clear_button: true }, include_sub_items_check_box_arguments: { hidden: true }, no_results_node_arguments: { label: I18n.t("wikis.link_existing_wiki_page_form.no_results") } diff --git a/modules/wikis/app/forms/wikis/link_existing_wiki_page_form.rb b/modules/wikis/app/forms/wikis/link_existing_wiki_page_form.rb index 93711b53e807..48c20b8e2042 100644 --- a/modules/wikis/app/forms/wikis/link_existing_wiki_page_form.rb +++ b/modules/wikis/app/forms/wikis/link_existing_wiki_page_form.rb @@ -42,14 +42,6 @@ class LinkExistingWikiPageForm < ApplicationForm filter_mode_control_arguments: { hidden: true }, filter_input_arguments: { placeholder: I18n.t("wikis.link_existing_wiki_page_form.placeholder"), - # every other property is just refilling the default values, - # as those are not merged into custom arguments - name: :filter, - label: I18n.t(:button_filter), - type: :search, - leading_visual: { icon: :search }, - visually_hide_label: true, - show_clear_button: true }, include_sub_items_check_box_arguments: { hidden: true }, no_results_node_arguments: { label: I18n.t("wikis.link_existing_wiki_page_form.no_results") }