From 9949f57cc7af45859276eb45eca3e145f94c8326 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Thu, 18 May 2023 17:46:17 +0100 Subject: [PATCH 01/12] Remove unnecessary gl-button classes The `app/views/shared/issuable/_search_bar.html.haml` template renders the deprecated, jQuery-based filtered search (which should eventually be replaced with the `GlFilteredSearch` Vue component). It's used in places like the Merge Requests list and Issue Analytics. These buttons should never have had the `gl-button` class applied to them, since their styles are fully described by the (deprecated) filtered search styles anyway. Due to the existence of the class, they are false positive findings detected by the [Pajamas Adoption Scanner][1]. Removing the class will prevent the Scanner from detecting them. [1]: https://gitlab-org.gitlab.io/frontend/pajamas-adoption-scanner/?search=extra%5Bmetadata.componentLabel%20eq%20%22component%3Abutton%22%20and%20severity%20ne%20%22INFO%22%20and%20lines%20co%20%22%25button%22%20and%20lines%20co%20%22btn-link%22%5D%20and%20path%20co%20%22%2F_search_bar.html%22&groupBy=file-type --- .../shared/issuable/_search_bar.html.haml | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index b8f98c285745cd..d590c859945d27 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -34,7 +34,7 @@ #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item{ data: {hint: "#{'{{hint}}'}", tag: "#{'{{tag}}'}", action: "#{'{{hint === \'search\' ? \'submit\' : \'\' }}'}" } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } -# Encapsulate static class name `{{icon}}` inside #{} to bypass -# haml lint's ClassAttributeWithStaticValue %svg @@ -44,7 +44,7 @@ #js-dropdown-operator.filtered-search-input-dropdown-menu.dropdown-menu %ul.filter-dropdown{ data: { dropdown: true, dynamic: true } } %li.filter-dropdown-item{ data: { value: "{{ title }}" } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } {{ title }} %span.btn-helptext {{ help }} @@ -60,10 +60,10 @@ #js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'None' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('None') %li.filter-dropdown-item{ data: { value: 'Any' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Any') %li.divider.droplab-item-ignore - if current_user @@ -76,10 +76,10 @@ #js-dropdown-reviewer.filtered-search-input-dropdown-menu.dropdown-menu %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'None' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('None') %li.filter-dropdown-item{ data: { value: 'Any' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Any') %li.divider.droplab-item-ignore - if current_user @@ -94,101 +94,101 @@ #js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'None' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('None') %li.filter-dropdown-item{ data: { value: 'Any' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Any') %li.filter-dropdown-item{ data: { value: 'Upcoming' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Upcoming') %li.filter-dropdown-item{ data: { value: 'Started' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Started') %li.divider.droplab-item-ignore %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item - %button.gl-button.btn.btn-link.js-data-value{ type: 'button' } + %button.btn.btn-link.js-data-value{ type: 'button' } {{title}} = render_if_exists 'shared/issuable/filter_iteration', type: type #js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'None' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('None') %li.filter-dropdown-item{ data: { value: 'Any' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Any') %li.divider.droplab-item-ignore %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item - %button.gl-button.btn.btn-link.js-data-value{ type: 'button' } + %button.btn.btn-link.js-data-value{ type: 'button' } {{title}} #js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'None' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('None') %li.filter-dropdown-item{ data: { value: 'Any' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Any') %li.divider.droplab-item-ignore %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } %span.dropdown-label-box{ style: 'background: {{color}}' } %span.label-title.js-data-value {{title}} #js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'None' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('None') %li.filter-dropdown-item{ data: { value: 'Any' } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Any') %li.divider.droplab-item-ignore %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } %gl-emoji %span.js-data-value.gl-ml-3 {{name}} #js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu %ul.filter-dropdown{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Yes') %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('No') - if ::Feature.enabled?(:mr_approved_filter, type: :ops) #js-dropdown-approved.filtered-search-input-dropdown-menu.dropdown-menu %ul.filter-dropdown{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Yes') %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('No') #js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu %ul.filter-dropdown{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('Yes') %li.filter-dropdown-item{ data: { value: 'no', capitalize: true } } - %button.gl-button.btn.btn-link{ type: 'button' } + %button.btn.btn-link{ type: 'button' } = _('No') - unless disable_target_branch #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item - %button.gl-button.btn.btn-link.js-data-value.monospace + %button.btn.btn-link.js-data-value.monospace {{title}} #js-dropdown-environment.filtered-search-input-dropdown-menu.dropdown-menu %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } %li.filter-dropdown-item - %button.gl-button.btn.btn-link.js-data-value{ type: 'button' } + %button.btn.btn-link.js-data-value{ type: 'button' } {{title}} = render_if_exists 'shared/issuable/filter_weight', type: type -- GitLab From 1b7cfadcf2828af438fa91001721c1bee3cd342b Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Mon, 22 May 2023 17:56:44 +0100 Subject: [PATCH 02/12] Remove unnecessary gl-btn classes This class doesn't do anything. These should also not have the `.gl-button` class, since they're part of dropdown's options, which are styled differently anyway. --- app/views/admin/labels/_label.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml index f4f64eadf210e6..19460ddb0e5973 100644 --- a/app/views/admin/labels/_label.html.haml +++ b/app/views/admin/labels/_label.html.haml @@ -10,8 +10,8 @@ .dropdown-menu.dropdown-menu-right %ul %li - = link_to edit_admin_label_path(label), class: 'btn gl-btn label-action dropdown-item btn-link' do + = link_to edit_admin_label_path(label), class: 'btn label-action dropdown-item btn-link' do = _('Edit') %li - = link_to admin_label_path(label), class: 'btn gl-btn js-remove-label dropdown-item btn-link gl-text-red-500!', data: { confirm: _('Are you sure you want to delete this label?'), confirm_btn_variant: 'danger' }, aria: { label: _('Delete label') }, method: :delete, remote: true do + = link_to admin_label_path(label), class: 'btn js-remove-label dropdown-item btn-link gl-text-red-500!', data: { confirm: _('Are you sure you want to delete this label?'), confirm_btn_variant: 'danger' }, aria: { label: _('Delete label') }, method: :delete, remote: true do = _('Delete') -- GitLab From c96bc7fed60c6887a4e51bf59e57140a12ed0ef3 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Tue, 23 May 2023 17:49:27 +0100 Subject: [PATCH 03/12] Manually rewrite disabled button --- app/views/projects/ci/builds/_build.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index ecdd43a54f9f33..6a9f40343e13bb 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -110,8 +110,7 @@ = link_to cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: _('Cancel'), class: 'gl-button btn btn-default btn-icon' do = sprite_icon('cancel', css_class: 'gl-icon') - elsif job.scheduled? - .gl-button.btn.btn-default.btn-icon.disabled{ disabled: true } - = sprite_icon('planning', css_class: 'gl-icon') + = render Pajamas::ButtonComponent.new(disabled: true, icon: 'planning') do %time.js-remaining-time{ datetime: job.scheduled_at.utc.iso8601 } = duration_in_numbers(job.execute_in) - confirmation_message = s_("DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes.") % { job_name: job.name } -- GitLab From 2e57b4fb715be1b1e44be7279e8a256e6ce09f26 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Wed, 24 May 2023 17:46:23 +0100 Subject: [PATCH 04/12] Replace pluralize calls with n_ For improved localisation. --- app/views/shared/milestones/_labels_tab.html.haml | 4 ++-- locale/gitlab.pot | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml index d2bee57992deb2..b8296736c53dbd 100644 --- a/app/views/shared/milestones/_labels_tab.html.haml +++ b/app/views/shared/milestones/_labels_tab.html.haml @@ -9,6 +9,6 @@ .float-right.d-none.d-lg-block = link_to milestones_issues_path(options.merge(state: 'opened')), class: 'btn gl-button btn-default-tertiary' do - - pluralize milestone_issues_by_label_count(@milestone, label, state: :opened), _('open issue') + = n_('open issue', 'open issues', milestone_issues_by_label_count(@milestone, label, state: :opened)) = link_to milestones_issues_path(options.merge(state: 'closed')), class: 'btn gl-button btn-default-tertiary' do - - pluralize milestone_issues_by_label_count(@milestone, label, state: :closed), _('closed issue') + = n_('closed issue', 'closed issues', milestone_issues_by_label_count(@milestone, label, state: :closed)) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e04b4de3e14578..fd89a556095f69 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -54137,7 +54137,9 @@ msgid "closed %{timeago}" msgstr "" msgid "closed issue" -msgstr "" +msgid_plural "closed issues" +msgstr[0] "" +msgstr[1] "" msgid "collect usage information" msgstr "" @@ -55195,7 +55197,9 @@ msgid "only supports valid HTTP(S) URLs" msgstr "" msgid "open issue" -msgstr "" +msgid_plural "open issues" +msgstr[0] "" +msgstr[1] "" msgid "or" msgstr "" -- GitLab From 33f495fce0c9fbde253ac79eb450f00d5e9e4760 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Wed, 24 May 2023 18:32:42 +0100 Subject: [PATCH 05/12] Add link_button_to helper This provides an easier migration path for links that look like buttons than using `Pajamas::ButtonComponent` itself. This is because the method signature is much more similar to `link_to` than `Pajamas::ButtonComponent`. It's also slightly less verbose, which seems reasonable for such a common use case. --- app/helpers/application_helper.rb | 67 +++++++++++++ spec/helpers/application_helper_spec.rb | 119 ++++++++++++++++++++++++ 2 files changed, 186 insertions(+) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3aca413ed45c4a..c662b364e589a1 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -464,6 +464,73 @@ def gitlab_ui_form_with(**args, &block) form_with(**args.merge({ builder: ::Gitlab::FormBuilders::GitlabUiFormBuilder }), &block) end + # Creates a link that looks like a button. + # + # It renders a Pajamas::ButtonComponent. + # + # It has the same API as `link_to`, but with some additional options + # specific to button rendering. + # + # Examples: + # # Default button + # link_button_to _('Foo'), some_path + # + # # Default button using a block + # link_button_to some_path do + # _('Foo') + # end + # + # # Confirm variant + # link_button_to _('Foo'), some_path, variant: :confirm + # + # # With icon + # link_button_to _('Foo'), some_path, icon: 'pencil' + # + # # Icon-only + # # NOTE: The content must be `nil` in order to correctly render. Use aria-label + # # to ensure the link is accessible. + # link_button_to nil, some_path, icon: 'pencil', 'aria-label': _('Foo') + # + # # Small button + # link_button_to _('Foo'), some_path, size: :small + # + # # Secondary category danger button + # link_button_to _('Foo'), some_path, variant: :danger, category: :secondary + # + # For accessibility, ensure that icon-only links have aria-label set. + def link_button_to(name = nil, options = nil, html_options = nil, &block) + if block + html_options = options + options = name + name = block + end + + html_options ||= {} + + # Ignore args that don't make sense for links, like disabled, loading, etc. + options_for_button = %i[ + category + variant + size + block + selected + icon + target + method + ] + + args = html_options.slice(*options_for_button) + html_options = html_options.except(*options_for_button) + + if block + render Pajamas::ButtonComponent.new(href: options, **args, button_options: html_options), &block + else + render Pajamas::ButtonComponent.new(href: options, **args, button_options: html_options) do + name + end + end + end + private def browser_id diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index f82b4146643ca9..7a35ebb1a7116f 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -619,6 +619,125 @@ def stub_controller_method(method_name, value) end end + describe '#link_button_to', feature_category: :design_system do + let(:content) { 'Button content' } + let(:options) { '#' } + let(:html_options) { {} } + + RSpec.shared_examples 'basic behavior' do + it 'renders a basic link button' do + expect(subject.name).to eq('a') + expect(subject.classes).to include(*%w[gl-button btn btn-md btn-default]) + expect(subject.attr('href')).to eq(options) + expect(subject.content.strip).to eq(content) + end + + describe 'variant option' do + let(:html_options) { { variant: :danger } } + + it 'renders the variant class' do + expect(subject.classes).to include('btn-danger') + end + end + + describe 'category option' do + let(:html_options) { { category: :tertiary } } + + it 'renders the category class' do + expect(subject.classes).to include('btn-default-tertiary') + end + end + + describe 'size option' do + let(:html_options) { { size: :small } } + + it 'renders the small class' do + expect(subject.classes).to include('btn-sm') + end + end + + describe 'block option' do + let(:html_options) { { block: true } } + + it 'renders the block class' do + expect(subject.classes).to include('btn-block') + end + end + + describe 'selected option' do + let(:html_options) { { selected: true } } + + it 'renders the selected class' do + expect(subject.classes).to include('selected') + end + end + + describe 'target option' do + let(:html_options) { { target: '_blank' } } + + it 'renders the target attribute' do + expect(subject.attr('target')).to eq('_blank') + end + end + + describe 'method option' do + let(:html_options) { { method: :post } } + + it 'renders the data-method attribute' do + expect(subject.attr('data-method')).to eq('post') + end + end + + describe 'icon option' do + let(:html_options) { { icon: 'remove' } } + + it 'renders the icon' do + icon = subject.at_css('svg.gl-icon') + expect(icon.attr('data-testid')).to eq('remove-icon') + end + end + + describe 'icon only' do + let(:content) { nil } + let(:html_options) { { icon: 'remove' } } + + it 'renders the icon-only class' do + expect(subject.classes).to include('btn-icon') + end + end + + describe 'arbitrary html options' do + let(:content) { nil } + let(:html_options) { { data: { foo: true }, aria: { labelledby: 'foo' } } } + + it 'renders the attributes' do + expect(subject.attr('data-foo')).to eq('true') + expect(subject.attr('aria-labelledby')).to eq('foo') + end + end + end + + describe 'without block' do + subject do + tag = helper.link_button_to content, options, **html_options + Nokogiri::HTML.fragment(tag).first_element_child + end + + include_examples 'basic behavior' + end + + describe 'with block' do + subject do + tag = helper.link_button_to options, **html_options do + content + end + Nokogiri::HTML.fragment(tag).first_element_child + end + + include_examples 'basic behavior' + end + end + describe '#page_class' do subject(:page_class) do helper.page_class.flatten -- GitLab From 5a0b9c031ef3875f57a8914ac41eec0c105f882c Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Wed, 24 May 2023 20:26:52 +0100 Subject: [PATCH 06/12] Mass button migration Executed with: comby \ -config ~/dev/pajamas-adoption-scanner/comby/button.toml \ -custom-matcher ~/dev/pajamas-adoption-scanner/comby/haml.json \ -matcher .generic -in-place \ -f {ee/,}app/views/**/*.haml Using the `button-migration` branch of the Pajamas Adoption Scanner: https://gitlab.com/gitlab-org/frontend/pajamas-adoption-scanner/-/commits/button-migration --- app/views/admin/users/projects.html.haml | 6 ++---- app/views/admin/users/show.html.haml | 3 +-- app/views/profiles/emails/index.html.haml | 4 +--- app/views/profiles/gpg_keys/_key.html.haml | 6 ++---- app/views/projects/_activity.html.haml | 3 +-- app/views/projects/_find_file_link.html.haml | 2 +- app/views/projects/_import_project_pane.html.haml | 2 +- app/views/projects/artifacts/browse.html.haml | 4 +--- app/views/projects/branches/index.html.haml | 4 ++-- app/views/projects/buttons/_fork.html.haml | 12 +++++------- app/views/projects/buttons/_star.html.haml | 9 ++++----- app/views/projects/ci/builds/_build.html.haml | 12 ++++-------- app/views/projects/commits/show.html.haml | 3 +-- app/views/projects/confluences/show.html.haml | 2 +- app/views/projects/diffs/_file.html.haml | 3 +-- app/views/projects/environments/terminal.html.haml | 3 +-- app/views/projects/forks/index.html.haml | 10 ++++------ .../projects/integrations/shimos/show.html.haml | 2 +- app/views/projects/milestones/index.html.haml | 6 +++--- app/views/projects/no_repo.html.haml | 8 ++++---- .../pipeline_schedules/_pipeline_schedule.html.haml | 9 +++------ .../projects/pipeline_schedules/index.html.haml | 4 ++-- app/views/projects/runners/_group_runners.html.haml | 4 ++-- app/views/projects/runners/_runner.html.haml | 9 +++------ app/views/projects/tags/index.html.haml | 5 ++--- app/views/projects/tags/show.html.haml | 6 ++---- .../shared/_remote_mirror_update_button.html.haml | 3 +-- ...two_factor_auth_recovery_settings_check.html.haml | 2 +- .../shared/doorkeeper/applications/_index.html.haml | 5 +---- .../gitlab_slack_application/_slack_button.html.haml | 2 +- .../shared/members/_manage_access_button.html.haml | 6 ++---- app/views/shared/milestones/_labels_tab.html.haml | 4 ++-- app/views/shared/projects/_search_form.html.haml | 2 +- app/views/shared/wikis/_main_links.html.haml | 4 ++-- app/views/shared/wikis/_sidebar.html.haml | 2 +- app/views/shared/wikis/diff.html.haml | 2 +- app/views/shared/wikis/pages.html.haml | 3 +-- ee/app/views/admin/geo/projects/_all.html.haml | 4 ++-- ee/app/views/admin/geo/projects/_failed.html.haml | 4 ++-- ee/app/views/admin/geo/projects/_pending.html.haml | 4 ++-- ee/app/views/admin/geo/projects/_synced.html.haml | 4 ++-- .../admin/projects/_geo_status_widget.html.haml | 4 ++-- .../views/admin/users/_admin_email_users.html.haml | 3 +-- .../users/_admin_export_user_permissions.html.haml | 3 +-- .../projects/_blank_state_ee_trial.html.haml | 2 +- .../ldap_group_links/_ldap_group_link.html.haml | 5 ++--- .../views/projects/mirrors/_table_pull_row.html.haml | 3 +-- .../views/projects/path_locks/_path_lock.html.haml | 2 +- .../settings/subscriptions/_project.html.haml | 3 +-- ee/app/views/shared/_mirror_update_button.html.haml | 3 +-- 50 files changed, 88 insertions(+), 132 deletions(-) diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index 1f3e8f4bba289b..fa89c3d4b4f34d 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -18,8 +18,7 @@ .float-right %span.light.vertical-align-middle= group_member.human_access - unless group_member.owner? - = link_to group_group_member_path(group, group_member), data: { confirm: remove_member_message(group_member), confirm_btn_variant: 'danger', testid: 'remove-user' }, aria: { label: _('Remove') }, method: :delete, remote: true, class: "btn btn-sm btn-danger gl-button btn-icon gl-ml-3", title: _('Remove user from group') do - = sprite_icon('remove', size: 16, css_class: 'gl-icon') + = link_button_to nil, group_group_member_path(group, group_member), data: { confirm: remove_member_message(group_member), confirm_btn_variant: 'danger', testid: 'remove-user' }, aria: { label: _('Remove') }, method: :delete, remote: true, class: 'gl-ml-3', title: _('Remove user from group'), variant: :danger, size: :small, icon: 'remove' .row .col-md-6 @@ -50,5 +49,4 @@ %span.light.vertical-align-middle= member.human_access - if member.respond_to? :project - = link_to project_project_member_path(project, member), data: { confirm: remove_member_message(member), confirm_btn_variant: 'danger' }, aria: { label: _('Remove') }, remote: true, method: :delete, class: "btn btn-sm btn-danger gl-button btn-icon gl-ml-3", title: _('Remove user from project') do - = sprite_icon('remove', size: 16, css_class: 'gl-icon') + = link_button_to nil, project_project_member_path(project, member), data: { confirm: remove_member_message(member), confirm_btn_variant: 'danger' }, aria: { label: _('Remove') }, remote: true, method: :delete, class: 'gl-ml-3', title: _('Remove user from project'), variant: :danger, size: :small, icon: 'remove' diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index ea6525e1b96a9b..a1b0c6c612b332 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -42,8 +42,7 @@ %span.light= _('Secondary email:') %strong = render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? } - = link_to remove_email_admin_user_path(@user, email), data: { confirm: _("Are you sure you want to remove %{email}?") % { email: email.email }, 'confirm-btn-variant': 'danger' }, method: :delete, class: "btn btn-sm btn-danger gl-button btn-icon float-right", title: _('Remove secondary email'), id: "remove_email_#{email.id}" do - = sprite_icon('close', size: 16, css_class: 'gl-icon') + = link_button_to nil, remove_email_admin_user_path(@user, email), data: { confirm: _("Are you sure you want to remove %{email}?") % { email: email.email }, 'confirm-btn-variant': 'danger' }, method: :delete, class: 'float-right', title: _('Remove secondary email'), id: "remove_email_#{email.id}", variant: :danger, size: :small, icon: 'close' %li %span.light ID: %strong{ data: { qa_selector: 'user_id_content' } } diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index c16f3c3b12b6e9..5686adaa3f5b70 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -61,6 +61,4 @@ - confirm_title = "#{email.confirmation_sent_at ? _('Resend confirmation email') : _('Send confirmation email')}" = link_to confirm_title, resend_confirmation_instructions_profile_email_path(email), method: :put, class: 'gl-button btn btn-sm btn-default' - = link_to profile_email_path(email), data: { confirm: _('Are you sure?'), qa_selector: 'delete_email_link'}, method: :delete, class: 'gl-button btn btn-sm btn-danger' do - %span.sr-only= _('Remove') - = sprite_icon('remove') + = link_button_to nil, profile_email_path(email), data: { confirm: _('Are you sure?'), qa_selector: 'delete_email_link'}, method: :delete, variant: :danger, size: :small, icon: 'remove', 'aria-label': _('Remove') diff --git a/app/views/profiles/gpg_keys/_key.html.haml b/app/views/profiles/gpg_keys/_key.html.haml index d52b16814c03be..c866523476873b 100644 --- a/app/views/profiles/gpg_keys/_key.html.haml +++ b/app/views/profiles/gpg_keys/_key.html.haml @@ -19,9 +19,7 @@ .float-right %span.key-created-at = html_escape(s_('Profiles|Created %{time_ago}')) % { time_ago: time_ago_with_tooltip(key.created_at) } - = link_to profile_gpg_key_path(key), data: { confirm: _('Are you sure? Removing this GPG key does not affect already signed commits.') }, method: :delete, class: "gl-button btn btn-icon btn-danger gl-ml-3" do - %span.sr-only= _('Remove') - = sprite_icon('remove') - = link_to revoke_profile_gpg_key_path(key), data: { confirm: _('Are you sure? All commits that were signed with this GPG key will be unverified.') }, method: :put, class: "gl-button btn btn-danger gl-ml-3" do + = link_button_to nil, profile_gpg_key_path(key), data: { confirm: _('Are you sure? Removing this GPG key does not affect already signed commits.') }, method: :delete, class: 'gl-ml-3', variant: :danger, icon: 'remove', 'aria-label': _('Remove') + = link_button_to revoke_profile_gpg_key_path(key), data: { confirm: _('Are you sure? All commits that were signed with this GPG key will be unverified.') }, method: :put, class: 'gl-ml-3', variant: :danger do %span.sr-only= _('Revoke') = _('Revoke') diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml index 118f6fb129682b..00da6c73081cf4 100644 --- a/app/views/projects/_activity.html.haml +++ b/app/views/projects/_activity.html.haml @@ -3,8 +3,7 @@ .nav-block.d-none.d-sm-flex.activities.gl-static = render 'shared/event_filter' .controls.gl-display-flex - = link_to project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'btn gl-button btn-default btn-icon d-none d-sm-inline-flex has-tooltip' do - = sprite_icon('rss', css_class: 'gl-icon') + = link_button_to nil, project_path(@project, rss_url_options), title: s_("ProjectActivityRSS|Subscribe"), class: 'd-none d-sm-inline-flex has-tooltip', icon: 'rss' - if is_project_overview && can?(current_user, :download_code, @project) .project-clone-holder.d-none.d-md-inline-flex.gl-ml-2 = render "projects/buttons/clone", dropdown_class: 'dropdown-menu-right' diff --git a/app/views/projects/_find_file_link.html.haml b/app/views/projects/_find_file_link.html.haml index a4bf72edf12251..4ad2c339bcc1d6 100644 --- a/app/views/projects/_find_file_link.html.haml +++ b/app/views/projects/_find_file_link.html.haml @@ -1,2 +1,2 @@ -= link_to project_find_file_path(@project, @ref), class: 'gl-button btn btn-default shortcuts-find-file', rel: 'nofollow' do += link_button_to project_find_file_path(@project, @ref), class: 'shortcuts-find-file', rel: 'nofollow' do = _('Find file') diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index 947a1007fd513b..668c4a6250838c 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -43,7 +43,7 @@ - if gitea_import_enabled? %div - = link_to new_import_gitea_path(namespace_id: namespace_id), class: 'gl-button btn-default btn import_gitea js-import-project-btn', data: { platform: 'gitea', **tracking_attrs_data(track_label, 'click_button', 'gitea') } do + = link_button_to new_import_gitea_path(namespace_id: namespace_id), class: 'import_gitea js-import-project-btn', data: { platform: 'gitea', **tracking_attrs_data(track_label, 'click_button', 'gitea') } do .gl-button-icon = custom_icon('gitea_logo') Gitea diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index ccda06c7e4cbe2..947e49a2e5f88e 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -18,9 +18,7 @@ = link_to truncate(title, length: 40), browse_project_job_artifacts_path(@project, @build, path) .tree-controls< - = link_to download_project_job_artifacts_path(@project, @build), - rel: 'nofollow', download: '', class: 'gl-button btn btn-default download' do - = sprite_icon('download', css_class: 'gl-mr-2') + = link_button_to nil, download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'download', icon: 'download' Download artifacts archive .tree-content-holder diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 3e98383f13ecd6..c03de6646cf4be 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -23,11 +23,11 @@ } - if can_view_branch_rules? - = link_to project_settings_repository_path(@project, anchor: 'js-branch-rules'), class: 'gl-button btn btn-default' do + = link_button_to project_settings_repository_path(@project, anchor: 'js-branch-rules') do = s_('Branches|View branch rules') - if can_push_code? - = link_to new_project_branch_path(@project), class: 'gl-button btn btn-confirm' do + = link_button_to new_project_branch_path(@project), variant: :confirm do = s_('Branches|New branch') .js-delete-merged-branches.gl-w-7{ data: { default_branch: @project.repository.root_ref, diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 6d05f1dc955e63..e191c1327d7946 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -2,17 +2,15 @@ - if current_user .count-badge.btn-group - if current_user.already_forked?(@project) && current_user.forkable_namespaces.size < 2 - = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: s_('ProjectOverview|Go to your fork'), class: 'gl-button btn btn-default has-tooltip fork-btn' do - = sprite_icon('fork', css_class: 'icon') - %span= s_('ProjectOverview|Fork') + = link_button_to namespace_project_path(current_user, current_user.fork_of(@project)), title: s_('ProjectOverview|Go to your fork'), class: 'has-tooltip fork-btn', icon: 'fork' do + = s_('ProjectOverview|Fork') - else - disabled_tooltip = fork_button_disabled_tooltip(@project) - count_class = 'disabled' unless can?(current_user, :read_code, @project) - button_class = 'disabled' if disabled_tooltip %span.btn-group{ class: ('has-tooltip' if disabled_tooltip), title: disabled_tooltip } - = link_to new_project_fork_path(@project), class: "gl-button btn btn-default fork-btn #{button_class}", data: { qa_selector: 'fork_button' } do - = sprite_icon('fork', css_class: 'icon') - %span= s_('ProjectOverview|Fork') - = link_to project_forks_path(@project), title: n_(s_('ProjectOverview|Forks'), s_('ProjectOverview|Forks'), @project.forks_count), class: "gl-button btn btn-default count has-tooltip fork-count #{count_class}" do + = link_button_to new_project_fork_path(@project), class: 'fork-btn #{button_class}', data: { qa_selector: 'fork_button' }, icon: 'fork' do + = s_('ProjectOverview|Fork') + = link_button_to project_forks_path(@project), title: n_(s_('ProjectOverview|Forks'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count has-tooltip fork-count #{count_class}' do = @project.forks_count diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index d4dcfbdff5419e..35318f68f572e3 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -6,12 +6,11 @@ .count-badge.d-inline-flex.align-item-stretch.btn-group = render Pajamas::ButtonComponent.new(size: :medium, icon: icon, button_text_classes: button_text_classes, button_options: { class: 'star-btn toggle-star', data: { endpoint: toggle_star_project_path(@project, :json) } }) do - button_text - = link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'gl-button btn btn-default has-tooltip star-count count' do + = link_button_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'has-tooltip star-count count' do = @project.star_count - else .count-badge.d-inline-flex.align-item-stretch.btn-group - = link_to new_user_session_path, class: 'gl-button btn btn-default has-tooltip star-btn', title: s_('ProjectOverview|You must sign in to star a project') do - = sprite_icon('star-o', css_class: 'icon') - %span= s_('ProjectOverview|Star') - = link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'gl-button btn btn-default has-tooltip star-count count' do + = link_button_to new_user_session_path, class: 'has-tooltip star-btn', title: s_('ProjectOverview|You must sign in to star a project'), icon: 'star-o' do + = s_('ProjectOverview|Star') + = link_button_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'has-tooltip star-count count' do = @project.star_count diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 6a9f40343e13bb..ca9af9c1c65760 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -103,12 +103,10 @@ .gl-text-right .btn-group - if can?(current_user, :read_job_artifacts, job) && job.artifacts? - = link_to download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: _('Download artifacts'), class: 'gl-button btn btn-default btn-icon' do - = sprite_icon('download', css_class: 'gl-icon') + = link_button_to nil, download_project_job_artifacts_path(job.project, job), rel: 'nofollow', download: '', title: _('Download artifacts'), icon: 'download' - if can?(current_user, :update_build, job) - if job.active? - = link_to cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: _('Cancel'), class: 'gl-button btn btn-default btn-icon' do - = sprite_icon('cancel', css_class: 'gl-icon') + = link_button_to nil, cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: _('Cancel'), icon: 'cancel' - elsif job.scheduled? = render Pajamas::ButtonComponent.new(disabled: true, icon: 'planning') do %time.js-remaining-time{ datetime: job.scheduled_at.utc.iso8601 } @@ -127,8 +125,6 @@ = sprite_icon('time-out', css_class: 'gl-icon') - elsif allow_retry - if job.playable? && !admin && can?(current_user, :update_build, job) - = link_to play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Play'), class: 'gl-button btn btn-default btn-icon' do - = sprite_icon('play', css_class: 'gl-icon') + = link_button_to nil, play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Play'), icon: 'play' - elsif job.retryable? - = link_to retry_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Retry'), class: 'gl-button btn btn-default btn-icon' do - = sprite_icon('retry', css_class: 'gl-icon') + = link_button_to nil, retry_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Retry'), icon: 'retry' diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 4c5a9acdf83532..19a2dd5bdef40a 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -28,8 +28,7 @@ = form_tag(project_commits_path(@project, @id, ref_type: @ref_type), method: :get, class: 'commits-search-form js-signature-container', data: { 'signatures-path' => namespace_project_signatures_path(ref_type: @ref_type)}) do = search_field_tag :search, params[:search], { placeholder: _('Search by message'), id: 'commits-search', class: 'form-control gl-form-input input-short gl-mt-3 gl-sm-mt-0 gl-min-w-full', spellcheck: false } .control.d-none.d-md-block - = link_to project_commits_path(@project, @id, rss_url_options), title: _("Commits feed"), class: 'btn gl-button btn-default btn-icon' do - = sprite_icon('rss') + = link_button_to nil, project_commits_path(@project, @id, rss_url_options), title: _("Commits feed"), icon: 'rss' = render_if_exists 'projects/commits/mirror_status' diff --git a/app/views/projects/confluences/show.html.haml b/app/views/projects/confluences/show.html.haml index 6fec9b501ea6b9..283408ffa63a80 100644 --- a/app/views/projects/confluences/show.html.haml +++ b/app/views/projects/confluences/show.html.haml @@ -8,6 +8,6 @@ - wiki_confluence_epic_link_url = 'https://gitlab.com/groups/gitlab-org/-/epics/3629' - wiki_confluence_epic_link_start = ''.html_safe % { url: wiki_confluence_epic_link_url } = html_escape(s_("WikiEmpty|You've enabled the Confluence Workspace integration. Your wiki will be viewable directly within Confluence. We are hard at work integrating Confluence more seamlessly into GitLab. If you'd like to stay up to date, follow our %{wiki_confluence_epic_link_start}Confluence epic%{wiki_confluence_epic_link_end}.")) % { wiki_confluence_epic_link_start: wiki_confluence_epic_link_start, wiki_confluence_epic_link_end: ''.html_safe } - = link_to @project.confluence_integration.confluence_url, target: '_blank', rel: 'noopener noreferrer', class: 'gl-button btn btn-confirm external-url', title: s_('WikiEmpty|Go to Confluence') do + = link_button_to @project.confluence_integration.confluence_url, target: '_blank', rel: 'noopener noreferrer', class: 'external-url', title: s_('WikiEmpty|Go to Confluence'), variant: :confirm do = s_('WikiEmpty|Go to Confluence') = sprite_icon('external-link') diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 5ec95c3095da08..fe951e37706604 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -19,8 +19,7 @@ - if diff_file.blob&.readable_text? - unless @diff_notes_disabled %span.has-tooltip{ title: _("Toggle comments for this file") } - = link_to '#', class: 'js-toggle-diff-comments btn gl-button btn-default btn-icon selected' do - = sprite_icon('comment') + = link_button_to nil, '#', class: 'js-toggle-diff-comments selected', icon: 'comment' \ - if editable_diff?(diff_file) - link_opts = @merge_request.persisted? ? { from_merge_request_iid: @merge_request.iid } : {} diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml index 7c837d4ded0685..c2ad91918002b2 100644 --- a/app/views/projects/environments/terminal.html.haml +++ b/app/views/projects/environments/terminal.html.haml @@ -13,8 +13,7 @@ .col-sm-6 .nav-controls - if @environment.external_url.present? - = link_to @environment.external_url, class: 'gl-button btn btn-default', target: '_blank', rel: 'noopener noreferrer nofollow' do - = sprite_icon('external-link') + = link_button_to nil, @environment.external_url, target: '_blank', rel: 'noopener noreferrer nofollow', icon: 'external-link' = render 'projects/deployments/actions', deployment: @environment.last_deployment .terminal-container{ class: container_class } diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml index d28ee30b6f9556..49047749b71e31 100644 --- a/app/views/projects/forks/index.html.haml +++ b/app/views/projects/forks/index.html.haml @@ -20,12 +20,10 @@ - if current_user && can?(current_user, :fork_project, @project) - if current_user.already_forked?(@project) && current_user.forkable_namespaces.size < 2 - = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn gl-button btn-confirm gl-md-ml-3' do - = sprite_icon('fork', size: 12) - %span= _('Fork') + = link_button_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'gl-md-ml-3', variant: :confirm, icon: 'fork' do + = _('Fork') - else - = link_to new_project_fork_path(@project), title: _("Fork project"), class: 'btn gl-button btn-confirm gl-md-ml-3 gl-mt-3 gl-md-mt-0' do - = sprite_icon('fork', size: 12) - %span= _('Fork') + = link_button_to new_project_fork_path(@project), title: _("Fork project"), class: 'gl-md-ml-3 gl-mt-3 gl-md-mt-0', variant: :confirm, icon: 'fork' do + = _('Fork') = render 'projects', projects: @forks diff --git a/app/views/projects/integrations/shimos/show.html.haml b/app/views/projects/integrations/shimos/show.html.haml index 92b9e03d5bda21..e6cd8c15809ada 100644 --- a/app/views/projects/integrations/shimos/show.html.haml +++ b/app/views/projects/integrations/shimos/show.html.haml @@ -6,5 +6,5 @@ = s_('Shimo|Shimo Workspace integration is enabled') %p = s_("Shimo|You've enabled the Shimo Workspace integration. You can view your wiki directly in Shimo.") - = link_to @project.shimo_integration.external_wiki_url, target: '_blank', rel: 'noopener noreferrer', class: 'gl-button btn btn-confirm', title: s_('Shimo|Go to Shimo Workspace') do + = link_button_to @project.shimo_integration.external_wiki_url, target: '_blank', rel: 'noopener noreferrer', title: s_('Shimo|Go to Shimo Workspace'), variant: :confirm do = s_('Shimo|Go to Shimo Workspace') diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index 326a7c4027f049..a7a21ef0440b39 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -9,14 +9,14 @@ = render 'shared/milestones/search_form' = render 'shared/milestones_sort_dropdown' - if can?(current_user, :admin_milestone, @project) - = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm gl-ml-3', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do + = link_button_to new_project_milestone_path(@project), class: 'gl-ml-3', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone'), variant: :confirm do = _('New milestone') - if @milestones.blank? = render 'shared/empty_states/milestones_tab' do - if can?(current_user, :admin_milestone, @project) .text-center - = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do + = link_button_to new_project_milestone_path(@project), data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone'), variant: :confirm do = _('New milestone') - else @@ -32,5 +32,5 @@ = render 'shared/empty_states/milestones' do - if can?(current_user, :admin_milestone, @project) .text-center - = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do + = link_button_to new_project_milestone_path(@project), data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone'), variant: :confirm do = _('New milestone') diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml index e3f46d601a382d..5cc909741f6aa8 100644 --- a/app/views/projects/no_repo.html.haml +++ b/app/views/projects/no_repo.html.haml @@ -13,13 +13,13 @@ %hr .no-repo-actions - = link_to project_repository_path(@project), method: :post, class: 'btn gl-button btn-confirm' do - #{ _('Create empty repository') } + = link_button_to project_repository_path(@project), method: :post, variant: :confirm do + = _('Create empty repository') %strong.gl-ml-3.gl-mr-3 or - = link_to new_project_import_path(@project), class: 'btn gl-button btn-default' do - #{ _('Import repository') } + = link_button_to new_project_import_path(@project) do + = _('Import repository') - if can? current_user, :remove_project, @project .prepend-top-20 diff --git a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml index 37b2b3ecfdeb29..a050808f13c819 100644 --- a/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml +++ b/app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml @@ -35,14 +35,11 @@ %td{ role: 'cell', data: { label: _('Actions') } } .float-right.btn-group - if can?(current_user, :play_pipeline_schedule, pipeline_schedule) - = link_to play_pipeline_schedule_path(pipeline_schedule), method: :post, title: _('Play'), class: 'btn gl-button btn-default btn-icon' do - = sprite_icon('play') + = link_button_to nil, play_pipeline_schedule_path(pipeline_schedule), method: :post, title: _('Play'), icon: 'play' - if can?(current_user, :admin_pipeline_schedule, pipeline_schedule) && pipeline_schedule.owner != current_user = render Pajamas::ButtonComponent.new(button_options: { class: 'js-take-ownership-button has-tooltip', title: s_('PipelineSchedule|Take ownership to edit'), data: { url: take_ownership_pipeline_schedule_path(pipeline_schedule) } }) do = s_('PipelineSchedules|Take ownership') - if can?(current_user, :update_pipeline_schedule, pipeline_schedule) - = link_to edit_pipeline_schedule_path(pipeline_schedule), title: _('Edit'), class: 'btn gl-button btn-default btn-icon' do - = sprite_icon('pencil') + = link_button_to nil, edit_pipeline_schedule_path(pipeline_schedule), title: _('Edit'), icon: 'pencil' - if can?(current_user, :admin_pipeline_schedule, pipeline_schedule) - = link_to pipeline_schedule_path(pipeline_schedule), title: _('Delete'), method: :delete, class: 'btn gl-button btn-danger btn-icon', aria: { label: _('Delete pipeline schedule') }, data: { confirm: _("Are you sure you want to delete this pipeline schedule?"), confirm_btn_variant: 'danger' } do - = sprite_icon('remove') + = link_button_to nil, pipeline_schedule_path(pipeline_schedule), title: _('Delete'), method: :delete, aria: { label: _('Delete pipeline schedule') }, data: { confirm: _("Are you sure you want to delete this pipeline schedule?"), confirm_btn_variant: 'danger' }, variant: :danger, icon: 'remove' diff --git a/app/views/projects/pipeline_schedules/index.html.haml b/app/views/projects/pipeline_schedules/index.html.haml index f2cefa402672a3..88a60b1fb06803 100644 --- a/app/views/projects/pipeline_schedules/index.html.haml +++ b/app/views/projects/pipeline_schedules/index.html.haml @@ -14,8 +14,8 @@ - if can?(current_user, :create_pipeline_schedule, @project) .nav-controls - = link_to new_project_pipeline_schedule_path(@project), class: 'btn gl-button btn-confirm' do - %span= _('New schedule') + = link_button_to new_project_pipeline_schedule_path(@project), variant: :confirm do + = _('New schedule') - if @schedules.present? %ul.content-list diff --git a/app/views/projects/runners/_group_runners.html.haml b/app/views/projects/runners/_group_runners.html.haml index d71bcd12e64751..32a2e36c779e70 100644 --- a/app/views/projects/runners/_group_runners.html.haml +++ b/app/views/projects/runners/_group_runners.html.haml @@ -13,10 +13,10 @@ %br %br - if @project.group_runners_enabled? - = link_to toggle_group_runners_project_runners_path(@project), class: 'btn gl-button btn-default', method: :post do + = link_button_to toggle_group_runners_project_runners_path(@project), method: :post do = _('Disable group runners') - else - = link_to toggle_group_runners_project_runners_path(@project), class: 'btn gl-button btn-confirm-secondary', method: :post do + = link_button_to toggle_group_runners_project_runners_path(@project), method: :post, variant: :confirm, category: :secondary do = _('Enable group runners')   = _('for this project') diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index e517b37aae951c..6d60370d2e05d9 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -13,14 +13,11 @@ .gl-ml-2 .btn-group.btn-group-sm - if @project_runners.include?(runner) - = link_to edit_project_runner_path(@project, runner), class: 'btn gl-button btn-icon', title: _('Edit'), aria: { label: _('Edit') }, data: { testid: 'edit-runner-link', toggle: 'tooltip', placement: 'top', container: 'body' } do - = sprite_icon('pencil') + = link_button_to nil, edit_project_runner_path(@project, runner), title: _('Edit'), aria: { label: _('Edit') }, data: { testid: 'edit-runner-link', toggle: 'tooltip', placement: 'top', container: 'body' }, icon: 'pencil' - if runner.active? - = link_to pause_project_runner_path(@project, runner), method: :post, class: 'btn gl-button btn-icon', title: s_('Runners|Pause from accepting jobs'), aria: { label: _('Pause') }, data: { toggle: 'tooltip', container: 'body', confirm: _("Are you sure?") } do - = sprite_icon('pause') + = link_button_to nil, pause_project_runner_path(@project, runner), method: :post, title: s_('Runners|Pause from accepting jobs'), aria: { label: _('Pause') }, data: { toggle: 'tooltip', container: 'body', confirm: _("Are you sure?") }, icon: 'pause' - else - = link_to resume_project_runner_path(@project, runner), method: :post, class: 'btn gl-button btn-icon', title: s_('Runners|Resume accepting jobs'), aria: { label: _('Resume') }, data: { toggle: 'tooltip', container: 'body' } do - = sprite_icon('play') + = link_button_to nil, resume_project_runner_path(@project, runner), method: :post, title: s_('Runners|Resume accepting jobs'), aria: { label: _('Resume') }, data: { toggle: 'tooltip', container: 'body' }, icon: 'play' - if runner.belongs_to_one_project? = link_to _('Remove runner'), project_runner_path(@project, runner), aria: { label: _('Remove') }, data: { confirm: _("Are you sure?"), 'confirm-btn-variant': 'danger' }, method: :delete, class: 'btn gl-button btn-danger' - else diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index fda797f3228cae..b0be748eb36625 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -9,10 +9,9 @@ .nav-controls #js-tags-sort-dropdown{ data: { filter_tags_path: filter_tags_path(search: @search, sort: @sort), sort_options: tags_sort_options_hash.to_json } } - = link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn gl-button btn-default btn-icon has-tooltip gl-ml-auto' do - = sprite_icon('rss', css_class: 'gl-icon') + = link_button_to nil, project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'has-tooltip gl-ml-auto', icon: 'rss' - if can?(current_user, :admin_tag, @project) - = link_to new_project_tag_path(@project), class: 'btn gl-button btn-confirm', data: { qa_selector: "new_tag_button" } do + = link_button_to new_project_tag_path(@project), data: { qa_selector: "new_tag_button" }, variant: :confirm do = s_('TagsPage|New tag') = render_if_exists 'projects/commits/mirror_status' diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index 5127972c40673f..1649e56043e81d 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -45,10 +45,8 @@ = render partial: 'projects/commit/signature', object: @tag.signature - if can?(current_user, :admin_tag, @project) = render 'edit_release_button', tag: @tag, project: @project, release: @release - = link_to project_tree_path(@project, @tag.name), class: 'btn btn-icon gl-button btn-default has-tooltip', title: s_('TagsPage|Browse files') do - = sprite_icon('folder-open', css_class: 'gl-icon') - = link_to project_commits_path(@project, @tag.name), class: 'btn btn-icon gl-button btn-default has-tooltip', title: s_('TagsPage|Browse commits') do - = sprite_icon('history', css_class: 'gl-icon') + = link_button_to nil, project_tree_path(@project, @tag.name), class: 'has-tooltip', title: s_('TagsPage|Browse files'), icon: 'folder-open' + = link_button_to nil, project_commits_path(@project, @tag.name), class: 'has-tooltip', title: s_('TagsPage|Browse commits'), icon: 'history' = render 'projects/buttons/download', project: @project, ref: @tag.name - if can?(current_user, :admin_tag, @project) = render 'projects/buttons/remove_tag', project: @project, tag: @tag diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml index bc80ebe39506fc..fa5c862b768a37 100644 --- a/app/views/shared/_remote_mirror_update_button.html.haml +++ b/app/views/shared/_remote_mirror_update_button.html.haml @@ -3,5 +3,4 @@ button_options: { class: 'disabled', title: _('Updating'), data: { toggle: 'tooltip', container: 'body' } }, icon_classes: 'spin') - elsif remote_mirror.enabled? - = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn btn-icon gl-button rspec-update-now-button", data: { toggle: 'tooltip', container: 'body', qa_selector: 'update_now_button' }, title: _('Update now') do - = sprite_icon("retry") + = link_button_to nil, update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: 'rspec-update-now-button', data: { toggle: 'tooltip', container: 'body', qa_selector: 'update_now_button' }, title: _('Update now'), icon: 'retry' diff --git a/app/views/shared/_two_factor_auth_recovery_settings_check.html.haml b/app/views/shared/_two_factor_auth_recovery_settings_check.html.haml index 290152d5803372..e372dbd983ce92 100644 --- a/app/views/shared/_two_factor_auth_recovery_settings_check.html.haml +++ b/app/views/shared/_two_factor_auth_recovery_settings_check.html.haml @@ -8,5 +8,5 @@ = s_('Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place.') = link_to _('Learn more.'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'recovery-codes'), target: '_blank', rel: 'noopener noreferrer' - c.with_actions do - = link_to profile_two_factor_auth_path, class: 'deferred-link btn gl-alert-action btn-confirm btn-md gl-button' do + = link_button_to profile_two_factor_auth_path, class: 'deferred-link gl-alert-action', variant: :confirm do = s_('Profiles|Manage two-factor authentication') diff --git a/app/views/shared/doorkeeper/applications/_index.html.haml b/app/views/shared/doorkeeper/applications/_index.html.haml index cffe645d698e97..31407c60e845b7 100644 --- a/app/views/shared/doorkeeper/applications/_index.html.haml +++ b/app/views/shared/doorkeeper/applications/_index.html.haml @@ -43,10 +43,7 @@ %div= uri %td= application.access_tokens.count %td.gl-display-flex - = link_to edit_application_url.call(application), class: "gl-button btn btn-default btn-icon gl-mr-3" do - %span.sr-only - = _('Edit') - = sprite_icon('pencil') + = link_button_to nil, edit_application_url.call(application), class: 'gl-mr-3', icon: 'pencil', 'aria-label': _('Edit') = render 'shared/doorkeeper/applications/delete_form', path: application_url.call(application), small: true - else .settings-message diff --git a/app/views/shared/integrations/gitlab_slack_application/_slack_button.html.haml b/app/views/shared/integrations/gitlab_slack_application/_slack_button.html.haml index b22a6eeca908e2..1ec669d5745fbf 100644 --- a/app/views/shared/integrations/gitlab_slack_application/_slack_button.html.haml +++ b/app/views/shared/integrations/gitlab_slack_application/_slack_button.html.haml @@ -1,4 +1,4 @@ -= link_to add_to_slack_link(project, slack_app_id), class: 'btn btn-default gl-button gl-pr-6!' do += link_button_to add_to_slack_link(project, slack_app_id), class: 'gl-pr-6!' do = image_tag 'illustrations/slack_logo.svg', class: 'gl-icon gl-button-icon gl-w-9! gl-h-9! gl-my-n3! gl-mr-0!' %strong.gl-button-text = label diff --git a/app/views/shared/members/_manage_access_button.html.haml b/app/views/shared/members/_manage_access_button.html.haml index c88198ec3809fc..910d62d4dc42b5 100644 --- a/app/views/shared/members/_manage_access_button.html.haml +++ b/app/views/shared/members/_manage_access_button.html.haml @@ -1,7 +1,5 @@ - path = local_assigns.fetch(:path, nil) .gl-float-right - = link_to path, class: 'btn btn-default btn-sm gl-button' do - = sprite_icon('pencil', css_class: 'gl-icon gl-button-icon') - %span.gl-button-text - = _('Manage access') + = link_button_to path, size: :small, icon: 'pencil' do + = _('Manage access') diff --git a/app/views/shared/milestones/_labels_tab.html.haml b/app/views/shared/milestones/_labels_tab.html.haml index b8296736c53dbd..1e856bf435519c 100644 --- a/app/views/shared/milestones/_labels_tab.html.haml +++ b/app/views/shared/milestones/_labels_tab.html.haml @@ -8,7 +8,7 @@ = markdown_field(label, :description) .float-right.d-none.d-lg-block - = link_to milestones_issues_path(options.merge(state: 'opened')), class: 'btn gl-button btn-default-tertiary' do + = link_button_to milestones_issues_path(options.merge(state: 'opened')), category: :tertiary do = n_('open issue', 'open issues', milestone_issues_by_label_count(@milestone, label, state: :opened)) - = link_to milestones_issues_path(options.merge(state: 'closed')), class: 'btn gl-button btn-default-tertiary' do + = link_button_to milestones_issues_path(options.merge(state: 'closed')), category: :tertiary do = n_('closed issue', 'closed issues', milestone_issues_by_label_count(@milestone, label, state: :closed)) diff --git a/app/views/shared/projects/_search_form.html.haml b/app/views/shared/projects/_search_form.html.haml index 72709b3ed2fbe8..2388bf2f0be0b2 100644 --- a/app/views/shared/projects/_search_form.html.haml +++ b/app/views/shared/projects/_search_form.html.haml @@ -51,5 +51,5 @@ .gl-display-flex.gl-w-full.gl-md-w-auto{ class: 'gl-m-0!' } .js-namespace-select{ data: { field_name: 'namespace_id', selected_id: namespace&.id, selected_text: selected_text, update_location: 'true' } } - = link_to new_project_path, class: 'gl-button btn btn-confirm gl-display-inline gl-mb-0!' do + = link_button_to new_project_path, class: 'gl-display-inline gl-mb-0!', variant: :confirm do = _('New Project') diff --git a/app/views/shared/wikis/_main_links.html.haml b/app/views/shared/wikis/_main_links.html.haml index c1fd8c48c602e5..41831c95198cde 100644 --- a/app/views/shared/wikis/_main_links.html.haml +++ b/app/views/shared/wikis/_main_links.html.haml @@ -1,6 +1,6 @@ - if @page&.persisted? - = link_to wiki_page_path(@wiki, @page, action: :history), class: "btn gl-button btn-default", role: "button", data: { qa_selector: 'page_history_button' } do + = link_button_to wiki_page_path(@wiki, @page, action: :history), role: "button", data: { qa_selector: 'page_history_button' } do = s_("Wiki|Page history") - if can?(current_user, :create_wiki, @wiki.container) - = link_to wiki_path(@wiki, action: :new), class: "btn gl-button btn-confirm-secondary", role: "button", data: { qa_selector: 'new_page_button' } do + = link_button_to wiki_path(@wiki, action: :new), role: "button", data: { qa_selector: 'new_page_button' }, variant: :confirm, category: :secondary do = s_("Wiki|New page") diff --git a/app/views/shared/wikis/_sidebar.html.haml b/app/views/shared/wikis/_sidebar.html.haml index 8b8c981da96d81..a34827602abcba 100644 --- a/app/views/shared/wikis/_sidebar.html.haml +++ b/app/views/shared/wikis/_sidebar.html.haml @@ -32,5 +32,5 @@ = render partial: entry.to_partial_path, object: entry, locals: { context: 'sidebar' } .block.w-100 - if @sidebar_limited - = link_to wiki_path(@wiki, action: :pages), class: 'btn gl-button btn-block', data: { qa_selector: 'view_all_pages_button' } do + = link_button_to wiki_path(@wiki, action: :pages), data: { qa_selector: 'view_all_pages_button' }, block: true do = s_("Wiki|View All Pages") diff --git a/app/views/shared/wikis/diff.html.haml b/app/views/shared/wikis/diff.html.haml index ee6c7f307a7d21..67772ec40c1fdb 100644 --- a/app/views/shared/wikis/diff.html.haml +++ b/app/views/shared/wikis/diff.html.haml @@ -12,7 +12,7 @@ = _('Changes') .nav-controls.pb-md-3.pb-lg-0 - = link_to wiki_page_path(@wiki, @page, action: :history), class: 'btn gl-button', role: 'button', data: { qa_selector: 'page_history_button' } do + = link_button_to wiki_page_path(@wiki, @page, action: :history), role: 'button', data: { qa_selector: 'page_history_button' } do = s_('Wiki|Page history') .page-content-header diff --git a/app/views/shared/wikis/pages.html.haml b/app/views/shared/wikis/pages.html.haml index f35649d031cf79..4656bb8d453a70 100644 --- a/app/views/shared/wikis/pages.html.haml +++ b/app/views/shared/wikis/pages.html.haml @@ -8,8 +8,7 @@ = s_("Wiki|Wiki Pages") .nav-controls.pb-md-3.pb-lg-0 - = link_to wiki_path(@wiki, action: :git_access), class: 'btn gl-button' do - = sprite_icon('download') + = link_button_to wiki_path(@wiki, action: :git_access), icon: 'download' do = _("Clone repository") .dropdown.inline.wiki-sort-dropdown diff --git a/ee/app/views/admin/geo/projects/_all.html.haml b/ee/app/views/admin/geo/projects/_all.html.haml index 334a27e6aa91ab..08630cfd9dea7e 100644 --- a/ee/app/views/admin/geo/projects/_all.html.haml +++ b/ee/app/views/admin/geo/projects/_all.html.haml @@ -9,10 +9,10 @@ %strong.text-truncate.flex-fill = link_to project_registry.project.full_name, admin_namespace_project_path(project_registry.project.namespace, project_registry.project) - unless project_registry.pending_verification? - = link_to(reverify_admin_geo_project_path(project_registry), method: :post, class: 'gl-button btn btn-default btn-sm gl-mr-3') do + = link_button_to reverify_admin_geo_project_path(project_registry), method: :post, class: 'gl-mr-3', size: :small do = s_('Geo|Reverify') - unless project_registry.resync_repository? - = link_to(resync_admin_geo_project_path(project_registry), method: :post, class: 'gl-button btn btn-default-primary btn-sm') do + = link_button_to resync_admin_geo_project_path(project_registry), method: :post, size: :small do = s_('Geo|Resync') = render partial: "registry_#{project_registry.synchronization_state}", locals: { project_registry: project_registry } diff --git a/ee/app/views/admin/geo/projects/_failed.html.haml b/ee/app/views/admin/geo/projects/_failed.html.haml index d1d858c4f30096..70a5d9e1a91f9c 100644 --- a/ee/app/views/admin/geo/projects/_failed.html.haml +++ b/ee/app/views/admin/geo/projects/_failed.html.haml @@ -9,9 +9,9 @@ %strong.text-truncate.flex-fill = link_to project_registry.project.full_name, admin_namespace_project_path(project_registry.project.namespace, project_registry.project) - if project_registry.candidate_for_redownload? - = link_to(force_redownload_admin_geo_project_path(project_registry), method: :post, class: 'gl-button btn btn-default btn-sm gl-mr-3') do + = link_button_to force_redownload_admin_geo_project_path(project_registry), method: :post, class: 'gl-mr-3', size: :small do = s_('Geo|Redownload') - = link_to(resync_admin_geo_project_path(project_registry), method: :post, class: 'gl-button btn btn-default-primary btn-sm') do + = link_button_to resync_admin_geo_project_path(project_registry), method: :post, size: :small do = s_('Geo|Resync') = render partial: 'registry_failed', locals: { project_registry: project_registry } diff --git a/ee/app/views/admin/geo/projects/_pending.html.haml b/ee/app/views/admin/geo/projects/_pending.html.haml index 611eccccdf65f9..239d0df71486f0 100644 --- a/ee/app/views/admin/geo/projects/_pending.html.haml +++ b/ee/app/views/admin/geo/projects/_pending.html.haml @@ -9,10 +9,10 @@ %strong.text-truncate.flex-fill = link_to project_registry.project.full_name, admin_namespace_project_path(project_registry.project.namespace, project_registry.project) - unless project_registry.pending_verification? - = link_to(reverify_admin_geo_project_path(project_registry), method: :post, class: 'gl-button btn btn-default btn-sm gl-mr-3') do + = link_button_to reverify_admin_geo_project_path(project_registry), method: :post, class: 'gl-mr-3', size: :small do = s_('Geo|Reverify') - unless project_registry.resync_repository? - = link_to(resync_admin_geo_project_path(project_registry), method: :post, class: 'gl-button btn btn-default-primary btn-sm') do + = link_button_to resync_admin_geo_project_path(project_registry), method: :post, size: :small do = s_('Geo|Resync') = render partial: 'registry_pending', locals: { project_registry: project_registry } diff --git a/ee/app/views/admin/geo/projects/_synced.html.haml b/ee/app/views/admin/geo/projects/_synced.html.haml index 913d3c7e8b211c..182b9391010144 100644 --- a/ee/app/views/admin/geo/projects/_synced.html.haml +++ b/ee/app/views/admin/geo/projects/_synced.html.haml @@ -8,9 +8,9 @@ - else %strong.text-truncate.flex-fill = link_to project_registry.project.full_name, admin_namespace_project_path(project_registry.project.namespace, project_registry.project) - = link_to(reverify_admin_geo_project_path(project_registry), method: :post, class: 'gl-button btn btn-default btn-sm gl-mr-3') do + = link_button_to reverify_admin_geo_project_path(project_registry), method: :post, class: 'gl-mr-3', size: :small do = s_('Geo|Reverify') - = link_to(resync_admin_geo_project_path(project_registry), method: :post, class: 'gl-button btn btn-default-primary btn-sm') do + = link_button_to resync_admin_geo_project_path(project_registry), method: :post, size: :small do = s_('Geo|Resync') = render partial: 'registry_synced', locals: { project_registry: project_registry } diff --git a/ee/app/views/admin/projects/_geo_status_widget.html.haml b/ee/app/views/admin/projects/_geo_status_widget.html.haml index fb837d4babcae2..3cfb1c789bb272 100644 --- a/ee/app/views/admin/projects/_geo_status_widget.html.haml +++ b/ee/app/views/admin/projects/_geo_status_widget.html.haml @@ -3,8 +3,8 @@ .card-header = s_('Geo|Geo Status') .float-right - = link_to(reverify_admin_geo_project_path(@project.project_registry), method: :post, class: 'gl-button btn btn-default btn-sm mr-2') do + = link_button_to reverify_admin_geo_project_path(@project.project_registry), method: :post, class: 'mr-2', size: :small do = s_('Geo|Reverify') - = link_to(resync_admin_geo_project_path(@project.project_registry), method: :post, class: 'gl-button btn btn-default-primary btn-sm') do + = link_button_to resync_admin_geo_project_path(@project.project_registry), method: :post, size: :small do = s_('Geo|Resync') = render partial: "admin/geo/projects/registry_#{@project.project_registry.synchronization_state}", locals: { project_registry: @project.project_registry } diff --git a/ee/app/views/admin/users/_admin_email_users.html.haml b/ee/app/views/admin/users/_admin_email_users.html.haml index d15efafe4ce737..5e26370964ffdb 100644 --- a/ee/app/views/admin/users/_admin_email_users.html.haml +++ b/ee/app/views/admin/users/_admin_email_users.html.haml @@ -1,4 +1,3 @@ - return unless send_emails_from_admin_area_feature_available? -= link_to admin_email_path, { class: 'gl-button btn btn-default btn-icon', data: { toggle: "tooltip", placement: "top", container: "body" }, title: s_("AdminUsers|Send email to users") } do - = sprite_icon('mail') += link_button_to nil, admin_email_path, { class: '', data: { toggle: "tooltip", placement: "top", container: "body" }, title: s_("AdminUsers|Send email to users") }, icon: 'mail' diff --git a/ee/app/views/admin/users/_admin_export_user_permissions.html.haml b/ee/app/views/admin/users/_admin_export_user_permissions.html.haml index cd23ce0997708f..18df57314586e1 100644 --- a/ee/app/views/admin/users/_admin_export_user_permissions.html.haml +++ b/ee/app/views/admin/users/_admin_export_user_permissions.html.haml @@ -1,4 +1,3 @@ - return unless current_user&.can?(:export_user_permissions) -= link_to admin_user_permission_exports_path(format: :csv), { class: 'gl-button btn btn-default btn-icon', data: { toggle: "tooltip", placement: "top", container: "body" }, title: s_("AdminUsers|Export permissions as CSV (max 100,000 users)") } do - = sprite_icon('upload') += link_button_to nil, admin_user_permission_exports_path(format: :csv), { class: '', data: { toggle: "tooltip", placement: "top", container: "body" }, title: s_("AdminUsers|Export permissions as CSV (max 100,000 users)") }, icon: 'upload' diff --git a/ee/app/views/dashboard/projects/_blank_state_ee_trial.html.haml b/ee/app/views/dashboard/projects/_blank_state_ee_trial.html.haml index af491402ef8c10..bd02844a684ce0 100644 --- a/ee/app/views/dashboard/projects/_blank_state_ee_trial.html.haml +++ b/ee/app/views/dashboard/projects/_blank_state_ee_trial.html.haml @@ -7,5 +7,5 @@ = _('Unlock more features with GitLab Ultimate') %p = _('GitLab is free to use. Many features for larger teams are part of our %{link_start}paid products%{link_end}. You can try Ultimate for free without any obligation or payment details.').html_safe % { link_start: ''.html_safe, link_end: ''.html_safe } - = link_to new_trial_url, class: "gl-button btn btn-confirm" do + = link_button_to new_trial_url, variant: :confirm do = _('Start free trial') diff --git a/ee/app/views/ldap_group_links/_ldap_group_link.html.haml b/ee/app/views/ldap_group_links/_ldap_group_link.html.haml index 9505f997ee78cf..209fc017cff64d 100644 --- a/ee/app/views/ldap_group_links/_ldap_group_link.html.haml +++ b/ee/app/views/ldap_group_links/_ldap_group_link.html.haml @@ -1,8 +1,7 @@ %li .float-right - = link_to group_ldap_group_link_path(group, ldap_group_link), method: :delete, class: 'gl-button btn btn-danger btn-sm' do - = sprite_icon('unlink', size: 12, css_class: 'gl-m-0!') - %span= _('Remove') + = link_button_to group_ldap_group_link_path(group, ldap_group_link), method: :delete, variant: :danger, size: :small, icon: 'unlink' do + = _('Remove') %strong= ldap_group_link.cn ? "Group: #{ldap_group_link.cn}" : "Filter: #{truncate(ldap_group_link.filter, length: 70)}" - if ldap_group_link.config diff --git a/ee/app/views/projects/mirrors/_table_pull_row.html.haml b/ee/app/views/projects/mirrors/_table_pull_row.html.haml index 8fe6e2e9c361e5..09e7a5509e7217 100644 --- a/ee/app/views/projects/mirrors/_table_pull_row.html.haml +++ b/ee/app/views/projects/mirrors/_table_pull_row.html.haml @@ -31,8 +31,7 @@ button_options: { class: 'disabled', title: _('This Project is currently archived and read-only. Please unarchive the project first if you want to resume Pull mirroring'), data: { container: 'body', toggle: 'tooltip', qa_selector: 'update_now_button', testid: 'update-now-button' } }, icon_classes: 'spin') - else - = link_to update_now_project_mirror_path(@project), method: :post, class: 'btn gl-button btn-icon btn-default js-force-update-mirror', data: { container: 'body', toggle: 'tooltip', qa_selector: 'update_now_button', testid: 'update-now-button' }, title: _('Update now') do - = sprite_icon("retry") + = link_button_to nil, update_now_project_mirror_path(@project), method: :post, class: 'js-force-update-mirror', data: { container: 'body', toggle: 'tooltip', qa_selector: 'update_now_button', testid: 'update-now-button' }, title: _('Update now'), icon: 'retry' = render Pajamas::ButtonComponent.new(variant: :danger, icon: 'remove', button_options: { class: 'js-delete-mirror js-delete-pull-mirror', title: _('Remove'), data: { toggle: 'tooltip', container: 'body' } }) diff --git a/ee/app/views/projects/path_locks/_path_lock.html.haml b/ee/app/views/projects/path_locks/_path_lock.html.haml index d0b4272b540d96..d7ec21a7acfd27 100644 --- a/ee/app/views/projects/path_locks/_path_lock.html.haml +++ b/ee/app/views/projects/path_locks/_path_lock.html.haml @@ -6,7 +6,7 @@ .controls - if can_unlock?(path_lock) - = link_to project_path_lock_path(@project, path_lock), class: 'btn gl-button btn-default js-remove-row', title: _("Unlock"), method: :delete, data: { confirm: _("Are you sure you want to unlock %{path_lock_path}?") % { path_lock_path: path_lock.path }, container: 'body', qa_selector: 'unlock_button' }, remote: true do + = link_button_to project_path_lock_path(@project, path_lock), class: 'js-remove-row', title: _("Unlock"), method: :delete, data: { confirm: _("Are you sure you want to unlock %{path_lock_path}?") % { path_lock_path: path_lock.path }, container: 'body', qa_selector: 'unlock_button' }, remote: true do = _('Unlock') = _("locked by %{path_lock_user_name} %{created_at}").html_safe % { path_lock_user_name: path_lock.user.name, created_at: time_ago_with_tooltip(path_lock.created_at) } diff --git a/ee/app/views/projects/settings/subscriptions/_project.html.haml b/ee/app/views/projects/settings/subscriptions/_project.html.haml index d42913d116b8f2..31f9effc2ff562 100644 --- a/ee/app/views/projects/settings/subscriptions/_project.html.haml +++ b/ee/app/views/projects/settings/subscriptions/_project.html.haml @@ -9,7 +9,6 @@ = project.namespace.name - if is_upstream_mode %td.gl-text-right - = link_to project_subscription_path(@project, subscription.id), method: :delete, data: { toggle: 'tooltip', title: tooltip, container: 'body', testid: 'delete-subscription' }, class: "gl-button btn btn-danger" do - = sprite_icon('close', size: 16, css_class: 'gl-icon') + = link_button_to nil, project_subscription_path(@project, subscription.id), method: :delete, data: { toggle: 'tooltip', title: tooltip, container: 'body', testid: 'delete-subscription' }, variant: :danger, icon: 'close' - else %td diff --git a/ee/app/views/shared/_mirror_update_button.html.haml b/ee/app/views/shared/_mirror_update_button.html.haml index 7aaa4a99f972fa..f154b7500d48be 100644 --- a/ee/app/views/shared/_mirror_update_button.html.haml +++ b/ee/app/views/shared/_mirror_update_button.html.haml @@ -9,8 +9,7 @@ = sprite_icon('retry', css_class: 'spin gl-mr-3') = _('Updating…') - elsif can?(current_user, :admin_project, @project) - = link_to update_now_project_mirror_path(@project), method: :post, class: 'gl-button btn btn-default' do - = sprite_icon('retry', css_class: 'gl-mr-3') + = link_button_to update_now_project_mirror_path(@project), method: :post, icon: 'retry' do = _('Update Now') - else %span.btn.gl-button.btn-default.disabled -- GitLab From 4914f7bbdd833c76f39aa11f04c906d1f8174bc8 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Wed, 24 May 2023 21:01:12 +0100 Subject: [PATCH 07/12] Button post-migration manual fixes - Translate untranslated button text - Use double quotes for interpolation of classes - Migrate some link buttons in the vicinity not caught by migration - Clean up html_options hashes, empty class attributes - Unwrap unnecessary span element surrounding link - Use keyword arguments rather than an explicit hash argument --- .../active_sessions/_active_session.html.haml | 5 +---- app/views/projects/artifacts/browse.html.haml | 4 ++-- app/views/projects/buttons/_fork.html.haml | 4 ++-- app/views/projects/ci/builds/_build.html.haml | 14 +++++++------- app/views/projects/commits/show.html.haml | 2 +- app/views/projects/diffs/_file.html.haml | 3 +-- .../views/admin/users/_admin_email_users.html.haml | 2 +- .../users/_admin_export_user_permissions.html.haml | 2 +- locale/gitlab.pot | 3 +++ 9 files changed, 19 insertions(+), 20 deletions(-) diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml index 9ec8d694dac235..40785d823a3f3b 100644 --- a/app/views/profiles/active_sessions/_active_session.html.haml +++ b/app/views/profiles/active_sessions/_active_session.html.haml @@ -27,9 +27,6 @@ - unless is_current_session .float-right - = link_to(revoke_session_path(active_session), - { data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.') }, - method: :delete, - class: "gl-button btn btn-danger gl-ml-3" }) do + = link_button_to revoke_session_path(active_session), data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.'), confirm_btn_variant: :danger }, method: :delete, class: 'gl-ml-3', variant: :danger do %span.sr-only= _('Revoke') = _('Revoke') diff --git a/app/views/projects/artifacts/browse.html.haml b/app/views/projects/artifacts/browse.html.haml index 947e49a2e5f88e..ebeeaed7ae9b2d 100644 --- a/app/views/projects/artifacts/browse.html.haml +++ b/app/views/projects/artifacts/browse.html.haml @@ -18,8 +18,8 @@ = link_to truncate(title, length: 40), browse_project_job_artifacts_path(@project, @build, path) .tree-controls< - = link_button_to nil, download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'download', icon: 'download' - Download artifacts archive + = link_button_to download_project_job_artifacts_path(@project, @build), rel: 'nofollow', download: '', class: 'download', icon: 'download' do + = _('Download artifacts archive') .tree-content-holder %table.table.tree-table diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index e191c1327d7946..c9dcfaff8c6b8d 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -10,7 +10,7 @@ - button_class = 'disabled' if disabled_tooltip %span.btn-group{ class: ('has-tooltip' if disabled_tooltip), title: disabled_tooltip } - = link_button_to new_project_fork_path(@project), class: 'fork-btn #{button_class}', data: { qa_selector: 'fork_button' }, icon: 'fork' do + = link_button_to new_project_fork_path(@project), class: "fork-btn #{button_class}", data: { qa_selector: 'fork_button' }, icon: 'fork' do = s_('ProjectOverview|Fork') - = link_button_to project_forks_path(@project), title: n_(s_('ProjectOverview|Forks'), s_('ProjectOverview|Forks'), @project.forks_count), class: 'count has-tooltip fork-count #{count_class}' do + = link_button_to project_forks_path(@project), title: n_(s_('ProjectOverview|Forks'), s_('ProjectOverview|Forks'), @project.forks_count), class: "count has-tooltip fork-count #{count_class}" do = @project.forks_count diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index ca9af9c1c65760..4017db459a9bf6 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -112,17 +112,17 @@ %time.js-remaining-time{ datetime: job.scheduled_at.utc.iso8601 } = duration_in_numbers(job.execute_in) - confirmation_message = s_("DelayedJobs|Are you sure you want to run %{job_name} immediately? This job will run automatically after it's timer finishes.") % { job_name: job.name } - = link_to play_project_job_path(job.project, job, return_to: request.original_url), + = link_button_to nil, play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: s_('DelayedJobs|Start now'), - class: 'gl-button btn btn-default btn-icon has-tooltip', - data: { confirm: confirmation_message } do - = sprite_icon('play', css_class: 'gl-icon') - = link_to unschedule_project_job_path(job.project, job, return_to: request.original_url), + class: 'has-tooltip', + data: { confirm: confirmation_message }, + icon: 'play' + = link_button_to nil, unschedule_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: s_('DelayedJobs|Unschedule'), - class: 'gl-button btn btn-default btn-icon has-tooltip' do - = sprite_icon('time-out', css_class: 'gl-icon') + class: 'has-tooltip', + icon: 'time-out' - elsif allow_retry - if job.playable? && !admin && can?(current_user, :update_build, job) = link_button_to nil, play_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: _('Play'), icon: 'play' diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 19a2dd5bdef40a..8afc9ade3e14c5 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -18,7 +18,7 @@ .tree-controls - if @merge_request.present? .control.d-none.d-md-block - = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'btn gl-button' + = link_button_to _("View open merge request"), project_merge_request_path(@project, @merge_request) - elsif create_mr_button?(from: @ref, source_project: @project) .control.d-none.d-md-block = render Pajamas::ButtonComponent.new(variant: :confirm, href: create_mr_path(from: @ref, source_project: @project)) do diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index fe951e37706604..3db1467df60f4e 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -18,8 +18,7 @@ #js-diff-stats{ data: diff_file_stats_data(diff_file) } - if diff_file.blob&.readable_text? - unless @diff_notes_disabled - %span.has-tooltip{ title: _("Toggle comments for this file") } - = link_button_to nil, '#', class: 'js-toggle-diff-comments selected', icon: 'comment' + = link_button_to nil, '#', class: 'js-toggle-diff-comments has-tooltip', icon: 'comment', title: _("Toggle comments for this file") \ - if editable_diff?(diff_file) - link_opts = @merge_request.persisted? ? { from_merge_request_iid: @merge_request.iid } : {} diff --git a/ee/app/views/admin/users/_admin_email_users.html.haml b/ee/app/views/admin/users/_admin_email_users.html.haml index 5e26370964ffdb..c74676c1d2cf22 100644 --- a/ee/app/views/admin/users/_admin_email_users.html.haml +++ b/ee/app/views/admin/users/_admin_email_users.html.haml @@ -1,3 +1,3 @@ - return unless send_emails_from_admin_area_feature_available? -= link_button_to nil, admin_email_path, { class: '', data: { toggle: "tooltip", placement: "top", container: "body" }, title: s_("AdminUsers|Send email to users") }, icon: 'mail' += link_button_to nil, admin_email_path, data: { toggle: "tooltip", placement: "top", container: "body" }, title: s_("AdminUsers|Send email to users"), icon: 'mail' diff --git a/ee/app/views/admin/users/_admin_export_user_permissions.html.haml b/ee/app/views/admin/users/_admin_export_user_permissions.html.haml index 18df57314586e1..e8e9592e85070b 100644 --- a/ee/app/views/admin/users/_admin_export_user_permissions.html.haml +++ b/ee/app/views/admin/users/_admin_export_user_permissions.html.haml @@ -1,3 +1,3 @@ - return unless current_user&.can?(:export_user_permissions) -= link_button_to nil, admin_user_permission_exports_path(format: :csv), { class: '', data: { toggle: "tooltip", placement: "top", container: "body" }, title: s_("AdminUsers|Export permissions as CSV (max 100,000 users)") }, icon: 'upload' += link_button_to nil, admin_user_permission_exports_path(format: :csv), data: { toggle: "tooltip", placement: "top", container: "body" }, title: s_("AdminUsers|Export permissions as CSV (max 100,000 users)"), icon: 'upload' diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fd89a556095f69..26e76f23d9f295 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16529,6 +16529,9 @@ msgstr "" msgid "Download artifacts" msgstr "" +msgid "Download artifacts archive" +msgstr "" + msgid "Download codes" msgstr "" -- GitLab From 7b622d6426a891950f0b2bfe43d01e7a4817eeb3 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Mon, 19 Jun 2023 12:47:52 +0100 Subject: [PATCH 08/12] Fix custom icon rendering The Gitea logo isn't available as a regular icon, so cannot be set via the `icon` argument. It must be handled specially. The styling here ensures that the icon and the text are displayed on the same line. --- app/views/projects/_import_project_pane.html.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index 668c4a6250838c..acb401fc3684d9 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -43,10 +43,11 @@ - if gitea_import_enabled? %div - = link_button_to new_import_gitea_path(namespace_id: namespace_id), class: 'import_gitea js-import-project-btn', data: { platform: 'gitea', **tracking_attrs_data(track_label, 'click_button', 'gitea') } do - .gl-button-icon - = custom_icon('gitea_logo') - Gitea + = render Pajamas::ButtonComponent.new(href: new_import_gitea_path(namespace_id: namespace_id), button_options: { class: 'import_gitea js-import-project-btn', data: { platform: 'gitea', **tracking_attrs_data(track_label, 'click_button', 'gitea') } }) do + %span.gl-display-flex + .gl-button-icon + = custom_icon('gitea_logo') + Gitea - if git_import_enabled? %div -- GitLab From bd6f4a6609e711a9f839910d6cf99f2c46500db7 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Mon, 26 Jun 2023 12:32:35 +0100 Subject: [PATCH 09/12] Replace .sr-only with aria-label While the screen-reader-only label seems redundant given the link already has accessible text, it exists so that the confirmation modal picks up the text for its confirm button. For some reason, [it explicitly looks][1] for screen-reader-only text. The [original MR][2] does not explain why. There is an [issue][3] to explore changing this. Why switch from .sr-only to aria-label: - To be consistent with link button above it. - The `sr-only` class is from Bootstrap, and generally we're trying to move away from that. [1]: https://gitlab.com/gitlab-org/gitlab/-/blob/59409bf02df839aabb2cfee70975458b82260f68/app/assets/javascripts/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal.js#L6-9 [2]: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/73167 [3]: https://gitlab.com/gitlab-org/gitlab/-/issues/413033 --- app/views/profiles/active_sessions/_active_session.html.haml | 3 +-- app/views/profiles/gpg_keys/_key.html.haml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/views/profiles/active_sessions/_active_session.html.haml b/app/views/profiles/active_sessions/_active_session.html.haml index 40785d823a3f3b..e91c28e6e84ddb 100644 --- a/app/views/profiles/active_sessions/_active_session.html.haml +++ b/app/views/profiles/active_sessions/_active_session.html.haml @@ -27,6 +27,5 @@ - unless is_current_session .float-right - = link_button_to revoke_session_path(active_session), data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.'), confirm_btn_variant: :danger }, method: :delete, class: 'gl-ml-3', variant: :danger do - %span.sr-only= _('Revoke') + = link_button_to revoke_session_path(active_session), data: { confirm: _('Are you sure? The device will be signed out of GitLab and all remember me tokens revoked.'), confirm_btn_variant: :danger }, method: :delete, class: 'gl-ml-3', variant: :danger, 'aria-label': _('Revoke') do = _('Revoke') diff --git a/app/views/profiles/gpg_keys/_key.html.haml b/app/views/profiles/gpg_keys/_key.html.haml index c866523476873b..d8b8dda29dce51 100644 --- a/app/views/profiles/gpg_keys/_key.html.haml +++ b/app/views/profiles/gpg_keys/_key.html.haml @@ -20,6 +20,5 @@ %span.key-created-at = html_escape(s_('Profiles|Created %{time_ago}')) % { time_ago: time_ago_with_tooltip(key.created_at) } = link_button_to nil, profile_gpg_key_path(key), data: { confirm: _('Are you sure? Removing this GPG key does not affect already signed commits.') }, method: :delete, class: 'gl-ml-3', variant: :danger, icon: 'remove', 'aria-label': _('Remove') - = link_button_to revoke_profile_gpg_key_path(key), data: { confirm: _('Are you sure? All commits that were signed with this GPG key will be unverified.') }, method: :put, class: 'gl-ml-3', variant: :danger do - %span.sr-only= _('Revoke') + = link_button_to revoke_profile_gpg_key_path(key), data: { confirm: _('Are you sure? All commits that were signed with this GPG key will be unverified.') }, method: :put, class: 'gl-ml-3', variant: :danger, 'aria-label': _('Revoke') do = _('Revoke') -- GitLab From 84f1738edbdbded4e94dfbe05568cfdb97523671 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Wed, 28 Jun 2023 19:07:41 +0100 Subject: [PATCH 10/12] Rename parameters and call block with yield --- app/helpers/application_helper.rb | 21 +++++++---------- spec/helpers/application_helper_spec.rb | 30 ++++++++++++------------- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index c662b364e589a1..90218d42c377dc 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -498,14 +498,13 @@ def gitlab_ui_form_with(**args, &block) # link_button_to _('Foo'), some_path, variant: :danger, category: :secondary # # For accessibility, ensure that icon-only links have aria-label set. - def link_button_to(name = nil, options = nil, html_options = nil, &block) + def link_button_to(name = nil, href = nil, options = nil, &block) if block - html_options = options - options = name - name = block + options = href + href = name end - html_options ||= {} + options ||= {} # Ignore args that don't make sense for links, like disabled, loading, etc. options_for_button = %i[ @@ -519,15 +518,11 @@ def link_button_to(name = nil, options = nil, html_options = nil, &block) method ] - args = html_options.slice(*options_for_button) - html_options = html_options.except(*options_for_button) + args = options.slice(*options_for_button) + button_options = options.except(*options_for_button) - if block - render Pajamas::ButtonComponent.new(href: options, **args, button_options: html_options), &block - else - render Pajamas::ButtonComponent.new(href: options, **args, button_options: html_options) do - name - end + render Pajamas::ButtonComponent.new(href: href, **args, button_options: button_options) do + block.present? ? yield : name end end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 7a35ebb1a7116f..f8797109401409 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -621,19 +621,19 @@ def stub_controller_method(method_name, value) describe '#link_button_to', feature_category: :design_system do let(:content) { 'Button content' } - let(:options) { '#' } - let(:html_options) { {} } + let(:href) { '#' } + let(:options) { {} } RSpec.shared_examples 'basic behavior' do it 'renders a basic link button' do expect(subject.name).to eq('a') expect(subject.classes).to include(*%w[gl-button btn btn-md btn-default]) - expect(subject.attr('href')).to eq(options) + expect(subject.attr('href')).to eq(href) expect(subject.content.strip).to eq(content) end describe 'variant option' do - let(:html_options) { { variant: :danger } } + let(:options) { { variant: :danger } } it 'renders the variant class' do expect(subject.classes).to include('btn-danger') @@ -641,7 +641,7 @@ def stub_controller_method(method_name, value) end describe 'category option' do - let(:html_options) { { category: :tertiary } } + let(:options) { { category: :tertiary } } it 'renders the category class' do expect(subject.classes).to include('btn-default-tertiary') @@ -649,7 +649,7 @@ def stub_controller_method(method_name, value) end describe 'size option' do - let(:html_options) { { size: :small } } + let(:options) { { size: :small } } it 'renders the small class' do expect(subject.classes).to include('btn-sm') @@ -657,7 +657,7 @@ def stub_controller_method(method_name, value) end describe 'block option' do - let(:html_options) { { block: true } } + let(:options) { { block: true } } it 'renders the block class' do expect(subject.classes).to include('btn-block') @@ -665,7 +665,7 @@ def stub_controller_method(method_name, value) end describe 'selected option' do - let(:html_options) { { selected: true } } + let(:options) { { selected: true } } it 'renders the selected class' do expect(subject.classes).to include('selected') @@ -673,7 +673,7 @@ def stub_controller_method(method_name, value) end describe 'target option' do - let(:html_options) { { target: '_blank' } } + let(:options) { { target: '_blank' } } it 'renders the target attribute' do expect(subject.attr('target')).to eq('_blank') @@ -681,7 +681,7 @@ def stub_controller_method(method_name, value) end describe 'method option' do - let(:html_options) { { method: :post } } + let(:options) { { method: :post } } it 'renders the data-method attribute' do expect(subject.attr('data-method')).to eq('post') @@ -689,7 +689,7 @@ def stub_controller_method(method_name, value) end describe 'icon option' do - let(:html_options) { { icon: 'remove' } } + let(:options) { { icon: 'remove' } } it 'renders the icon' do icon = subject.at_css('svg.gl-icon') @@ -699,7 +699,7 @@ def stub_controller_method(method_name, value) describe 'icon only' do let(:content) { nil } - let(:html_options) { { icon: 'remove' } } + let(:options) { { icon: 'remove' } } it 'renders the icon-only class' do expect(subject.classes).to include('btn-icon') @@ -708,7 +708,7 @@ def stub_controller_method(method_name, value) describe 'arbitrary html options' do let(:content) { nil } - let(:html_options) { { data: { foo: true }, aria: { labelledby: 'foo' } } } + let(:options) { { data: { foo: true }, aria: { labelledby: 'foo' } } } it 'renders the attributes' do expect(subject.attr('data-foo')).to eq('true') @@ -719,7 +719,7 @@ def stub_controller_method(method_name, value) describe 'without block' do subject do - tag = helper.link_button_to content, options, **html_options + tag = helper.link_button_to content, href, options Nokogiri::HTML.fragment(tag).first_element_child end @@ -728,7 +728,7 @@ def stub_controller_method(method_name, value) describe 'with block' do subject do - tag = helper.link_button_to options, **html_options do + tag = helper.link_button_to href, options do content end Nokogiri::HTML.fragment(tag).first_element_child -- GitLab From 8cdf2e157c7eb7d1b877ba9b9588780f98070db0 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Wed, 28 Jun 2023 20:38:12 +0100 Subject: [PATCH 11/12] Move helper to more appropriate file --- app/helpers/application_helper.rb | 62 ------------ app/helpers/button_helper.rb | 62 ++++++++++++ spec/helpers/application_helper_spec.rb | 119 ------------------------ spec/helpers/button_helper_spec.rb | 119 ++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 181 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 90218d42c377dc..3aca413ed45c4a 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -464,68 +464,6 @@ def gitlab_ui_form_with(**args, &block) form_with(**args.merge({ builder: ::Gitlab::FormBuilders::GitlabUiFormBuilder }), &block) end - # Creates a link that looks like a button. - # - # It renders a Pajamas::ButtonComponent. - # - # It has the same API as `link_to`, but with some additional options - # specific to button rendering. - # - # Examples: - # # Default button - # link_button_to _('Foo'), some_path - # - # # Default button using a block - # link_button_to some_path do - # _('Foo') - # end - # - # # Confirm variant - # link_button_to _('Foo'), some_path, variant: :confirm - # - # # With icon - # link_button_to _('Foo'), some_path, icon: 'pencil' - # - # # Icon-only - # # NOTE: The content must be `nil` in order to correctly render. Use aria-label - # # to ensure the link is accessible. - # link_button_to nil, some_path, icon: 'pencil', 'aria-label': _('Foo') - # - # # Small button - # link_button_to _('Foo'), some_path, size: :small - # - # # Secondary category danger button - # link_button_to _('Foo'), some_path, variant: :danger, category: :secondary - # - # For accessibility, ensure that icon-only links have aria-label set. - def link_button_to(name = nil, href = nil, options = nil, &block) - if block - options = href - href = name - end - - options ||= {} - - # Ignore args that don't make sense for links, like disabled, loading, etc. - options_for_button = %i[ - category - variant - size - block - selected - icon - target - method - ] - - args = options.slice(*options_for_button) - button_options = options.except(*options_for_button) - - render Pajamas::ButtonComponent.new(href: href, **args, button_options: button_options) do - block.present? ? yield : name - end - end - private def browser_id diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 64d6ba155cd147..6e0ba748d85a8c 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -98,6 +98,68 @@ def dropdown_item_with_description(title, description, href: nil, data: nil, def href: href, data: data end + + # Creates a link that looks like a button. + # + # It renders a Pajamas::ButtonComponent. + # + # It has the same API as `link_to`, but with some additional options + # specific to button rendering. + # + # Examples: + # # Default button + # link_button_to _('Foo'), some_path + # + # # Default button using a block + # link_button_to some_path do + # _('Foo') + # end + # + # # Confirm variant + # link_button_to _('Foo'), some_path, variant: :confirm + # + # # With icon + # link_button_to _('Foo'), some_path, icon: 'pencil' + # + # # Icon-only + # # NOTE: The content must be `nil` in order to correctly render. Use aria-label + # # to ensure the link is accessible. + # link_button_to nil, some_path, icon: 'pencil', 'aria-label': _('Foo') + # + # # Small button + # link_button_to _('Foo'), some_path, size: :small + # + # # Secondary category danger button + # link_button_to _('Foo'), some_path, variant: :danger, category: :secondary + # + # For accessibility, ensure that icon-only links have aria-label set. + def link_button_to(name = nil, href = nil, options = nil, &block) + if block + options = href + href = name + end + + options ||= {} + + # Ignore args that don't make sense for links, like disabled, loading, etc. + options_for_button = %i[ + category + variant + size + block + selected + icon + target + method + ] + + args = options.slice(*options_for_button) + button_options = options.except(*options_for_button) + + render Pajamas::ButtonComponent.new(href: href, **args, button_options: button_options) do + block.present? ? yield : name + end + end end ButtonHelper.prepend_mod_with('ButtonHelper') diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index f8797109401409..f82b4146643ca9 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -619,125 +619,6 @@ def stub_controller_method(method_name, value) end end - describe '#link_button_to', feature_category: :design_system do - let(:content) { 'Button content' } - let(:href) { '#' } - let(:options) { {} } - - RSpec.shared_examples 'basic behavior' do - it 'renders a basic link button' do - expect(subject.name).to eq('a') - expect(subject.classes).to include(*%w[gl-button btn btn-md btn-default]) - expect(subject.attr('href')).to eq(href) - expect(subject.content.strip).to eq(content) - end - - describe 'variant option' do - let(:options) { { variant: :danger } } - - it 'renders the variant class' do - expect(subject.classes).to include('btn-danger') - end - end - - describe 'category option' do - let(:options) { { category: :tertiary } } - - it 'renders the category class' do - expect(subject.classes).to include('btn-default-tertiary') - end - end - - describe 'size option' do - let(:options) { { size: :small } } - - it 'renders the small class' do - expect(subject.classes).to include('btn-sm') - end - end - - describe 'block option' do - let(:options) { { block: true } } - - it 'renders the block class' do - expect(subject.classes).to include('btn-block') - end - end - - describe 'selected option' do - let(:options) { { selected: true } } - - it 'renders the selected class' do - expect(subject.classes).to include('selected') - end - end - - describe 'target option' do - let(:options) { { target: '_blank' } } - - it 'renders the target attribute' do - expect(subject.attr('target')).to eq('_blank') - end - end - - describe 'method option' do - let(:options) { { method: :post } } - - it 'renders the data-method attribute' do - expect(subject.attr('data-method')).to eq('post') - end - end - - describe 'icon option' do - let(:options) { { icon: 'remove' } } - - it 'renders the icon' do - icon = subject.at_css('svg.gl-icon') - expect(icon.attr('data-testid')).to eq('remove-icon') - end - end - - describe 'icon only' do - let(:content) { nil } - let(:options) { { icon: 'remove' } } - - it 'renders the icon-only class' do - expect(subject.classes).to include('btn-icon') - end - end - - describe 'arbitrary html options' do - let(:content) { nil } - let(:options) { { data: { foo: true }, aria: { labelledby: 'foo' } } } - - it 'renders the attributes' do - expect(subject.attr('data-foo')).to eq('true') - expect(subject.attr('aria-labelledby')).to eq('foo') - end - end - end - - describe 'without block' do - subject do - tag = helper.link_button_to content, href, options - Nokogiri::HTML.fragment(tag).first_element_child - end - - include_examples 'basic behavior' - end - - describe 'with block' do - subject do - tag = helper.link_button_to href, options do - content - end - Nokogiri::HTML.fragment(tag).first_element_child - end - - include_examples 'basic behavior' - end - end - describe '#page_class' do subject(:page_class) do helper.page_class.flatten diff --git a/spec/helpers/button_helper_spec.rb b/spec/helpers/button_helper_spec.rb index 8a5669867bf049..a59f172061e73e 100644 --- a/spec/helpers/button_helper_spec.rb +++ b/spec/helpers/button_helper_spec.rb @@ -223,4 +223,123 @@ def element(data = {}) end end end + + describe '#link_button_to', feature_category: :design_system do + let(:content) { 'Button content' } + let(:href) { '#' } + let(:options) { {} } + + RSpec.shared_examples 'basic behavior' do + it 'renders a basic link button' do + expect(subject.name).to eq('a') + expect(subject.classes).to include(*%w[gl-button btn btn-md btn-default]) + expect(subject.attr('href')).to eq(href) + expect(subject.content.strip).to eq(content) + end + + describe 'variant option' do + let(:options) { { variant: :danger } } + + it 'renders the variant class' do + expect(subject.classes).to include('btn-danger') + end + end + + describe 'category option' do + let(:options) { { category: :tertiary } } + + it 'renders the category class' do + expect(subject.classes).to include('btn-default-tertiary') + end + end + + describe 'size option' do + let(:options) { { size: :small } } + + it 'renders the small class' do + expect(subject.classes).to include('btn-sm') + end + end + + describe 'block option' do + let(:options) { { block: true } } + + it 'renders the block class' do + expect(subject.classes).to include('btn-block') + end + end + + describe 'selected option' do + let(:options) { { selected: true } } + + it 'renders the selected class' do + expect(subject.classes).to include('selected') + end + end + + describe 'target option' do + let(:options) { { target: '_blank' } } + + it 'renders the target attribute' do + expect(subject.attr('target')).to eq('_blank') + end + end + + describe 'method option' do + let(:options) { { method: :post } } + + it 'renders the data-method attribute' do + expect(subject.attr('data-method')).to eq('post') + end + end + + describe 'icon option' do + let(:options) { { icon: 'remove' } } + + it 'renders the icon' do + icon = subject.at_css('svg.gl-icon') + expect(icon.attr('data-testid')).to eq('remove-icon') + end + end + + describe 'icon only' do + let(:content) { nil } + let(:options) { { icon: 'remove' } } + + it 'renders the icon-only class' do + expect(subject.classes).to include('btn-icon') + end + end + + describe 'arbitrary html options' do + let(:content) { nil } + let(:options) { { data: { foo: true }, aria: { labelledby: 'foo' } } } + + it 'renders the attributes' do + expect(subject.attr('data-foo')).to eq('true') + expect(subject.attr('aria-labelledby')).to eq('foo') + end + end + end + + describe 'without block' do + subject do + tag = helper.link_button_to content, href, options + Nokogiri::HTML.fragment(tag).first_element_child + end + + include_examples 'basic behavior' + end + + describe 'with block' do + subject do + tag = helper.link_button_to href, options do + content + end + Nokogiri::HTML.fragment(tag).first_element_child + end + + include_examples 'basic behavior' + end + end end -- GitLab From 69c454bd2d30872841f91184ad875be58a631c88 Mon Sep 17 00:00:00 2001 From: Mark Florian Date: Thu, 29 Jun 2023 13:16:41 +0100 Subject: [PATCH 12/12] Remove Gitea icon button hack It turns out the Gitea logo _is_ in our sprite icon set, so the `icon` option can be used. --- app/views/projects/_import_project_pane.html.haml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index acb401fc3684d9..6315c6dc52d7e0 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -43,11 +43,8 @@ - if gitea_import_enabled? %div - = render Pajamas::ButtonComponent.new(href: new_import_gitea_path(namespace_id: namespace_id), button_options: { class: 'import_gitea js-import-project-btn', data: { platform: 'gitea', **tracking_attrs_data(track_label, 'click_button', 'gitea') } }) do - %span.gl-display-flex - .gl-button-icon - = custom_icon('gitea_logo') - Gitea + = render Pajamas::ButtonComponent.new(href: new_import_gitea_path(namespace_id: namespace_id), icon: 'gitea', button_options: { class: 'import_gitea js-import-project-btn', data: { platform: 'gitea', **tracking_attrs_data(track_label, 'click_button', 'gitea') } }) do + Gitea - if git_import_enabled? %div -- GitLab