amelioration(menu): extraction des menu dans un composant ruby pour ne pas dupliquer les changements aria partout ds la codebase
This commit is contained in:
parent
7b5aad5f47
commit
2be0f5aa99
5 changed files with 102 additions and 63 deletions
43
app/components/dropdown/menu_component.rb
Normal file
43
app/components/dropdown/menu_component.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
class Dropdown::MenuComponent < ApplicationComponent
|
||||
renders_one :button_inner_html
|
||||
# beware, items elements like button_to/link_to must include role: 'menuitem' for aria reason
|
||||
renders_many :items, -> (options = {}, &block) do
|
||||
tag.li({ role: 'none' }.merge(options), &block)
|
||||
end
|
||||
renders_many :forms
|
||||
|
||||
def initialize(wrapper:,
|
||||
wrapper_options: {},
|
||||
button_options: {},
|
||||
menu_options: {})
|
||||
@wrapper = wrapper
|
||||
@wrapper_options = wrapper_options
|
||||
@button_options = button_options
|
||||
@menu_options = menu_options
|
||||
end
|
||||
|
||||
def wrapper_class_names
|
||||
['dropdown'] + Array(@wrapper_options[:class])
|
||||
end
|
||||
|
||||
def button_id
|
||||
"#{menu_id}_button"
|
||||
end
|
||||
|
||||
def menu_id
|
||||
@menu_options[:id] ||= SecureRandom.uuid
|
||||
@menu_options[:id]
|
||||
end
|
||||
|
||||
def menu_role
|
||||
forms? ? :region : :menu
|
||||
end
|
||||
|
||||
def menu_class_names
|
||||
['dropdown-content'] + Array(@menu_options[:class])
|
||||
end
|
||||
|
||||
def button_class_names
|
||||
['fr-btn', 'dropdown-button'] + Array(@button_options[:class])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
= content_tag(@wrapper, class: wrapper_class_names, data: { controller: 'menu-button' }) do
|
||||
|
||||
%button{ class: button_class_names, id: button_id, data: { menu_button_target: 'button' }, "aria-expanded": "false", 'aria-haspopup': 'true', 'aria-controls': menu_id }
|
||||
= button_inner_html
|
||||
|
||||
%div{ data: { menu_button_target: 'menu' }, id: menu_id, 'aria-labelledby': button_id, role: menu_role, 'tab-index': -1, class: menu_class_names }
|
||||
|
||||
-# the dropdown can be a menu with a list of item
|
||||
- if items?
|
||||
%ul.dropdown-items.fr-pl-0{ role: 'none' }
|
||||
- items.each do |dropdown_item|
|
||||
= dropdown_item
|
||||
-# the dropdown can be a menu with forms
|
||||
- if forms?
|
||||
- forms.each do |form|
|
||||
= form
|
|
@ -21,38 +21,17 @@ export class MenuButtonController extends ApplicationController {
|
|||
}
|
||||
|
||||
private get isMenu() {
|
||||
return !(this.element as HTMLElement).dataset.popover;
|
||||
return this.menuTarget.getAttribute('role') == 'menu';
|
||||
}
|
||||
|
||||
private setup() {
|
||||
this.buttonTarget.setAttribute(
|
||||
'aria-haspopup',
|
||||
this.isMenu ? 'menu' : 'true'
|
||||
);
|
||||
this.buttonTarget.setAttribute('aria-controls', this.menuTarget.id);
|
||||
if (!this.buttonTarget.id) {
|
||||
this.buttonTarget.id = `${this.menuTarget.id}_button`;
|
||||
}
|
||||
|
||||
this.menuTarget.setAttribute('aria-labelledby', this.buttonTarget.id);
|
||||
this.menuTarget.setAttribute('role', this.isMenu ? 'menu' : 'region');
|
||||
// see:
|
||||
// To progressively enhance this navigation widget that is by default accessible,
|
||||
// the class to hide the menu and the inclusion of tabindex="-1" on the interactive menuitem
|
||||
// content should be added with JavaScript on load.
|
||||
this.menuTarget.classList.add('fade-in-down');
|
||||
this.menuTarget.setAttribute('tab-index', '-1');
|
||||
|
||||
if (this.isMenu) {
|
||||
for (const menuItem of this.menuTarget.querySelectorAll('a')) {
|
||||
menuItem.setAttribute('role', 'menuitem');
|
||||
}
|
||||
for (const dropdownItems of this.menuTarget.querySelectorAll(
|
||||
'.dropdown-items'
|
||||
)) {
|
||||
dropdownItems.setAttribute('role', 'none');
|
||||
}
|
||||
for (const dropdownItems of this.menuTarget.querySelectorAll(
|
||||
'.dropdown-items > li'
|
||||
)) {
|
||||
dropdownItems.setAttribute('role', 'none');
|
||||
}
|
||||
this.menuItems.map((menuItem) => menuItem.setAttribute('tabindex', '-1'));
|
||||
}
|
||||
|
||||
this.on('click', (event) => {
|
||||
|
@ -106,7 +85,7 @@ export class MenuButtonController extends ApplicationController {
|
|||
}
|
||||
|
||||
private close() {
|
||||
this.buttonTarget.removeAttribute('aria-expanded');
|
||||
this.buttonTarget.setAttribute('aria-expanded', 'false');
|
||||
this.menuTarget.parentElement?.classList.remove('open');
|
||||
this.#teardown?.();
|
||||
this.setFocusToMenuitem(null);
|
||||
|
|
|
@ -41,42 +41,43 @@
|
|||
%li
|
||||
= link_to admin_procedure_path(procedure), class: 'fr-btn fr-icon-draft-line fr-btn--tertiary' do
|
||||
Modifier
|
||||
%li.dropdown{ data: { controller: 'menu-button' } }
|
||||
%button.fr-btn.fr-btn--tertiary.dropdown-button.procedures-actions-btn{ data: { menu_button_target: 'button' } }
|
||||
|
||||
= render Dropdown::MenuComponent.new(wrapper: :li, button_options: { class: ['fr-btn--tertiary'] }, menu_options: { id: dom_id(procedure, :actions_menu)}) do |menu|
|
||||
- menu.with_button_inner_html do
|
||||
Actions
|
||||
.dropdown-content.fade-in-down{ data: { menu_button_target: 'menu' }, id: dom_id(procedure, :actions_menu) }
|
||||
%ul.dropdown-items.pl-0
|
||||
- if !procedure.close? && !procedure.discarded?
|
||||
%li
|
||||
= link_to sanitize_url(procedure.brouillon? ? commencer_test_url(path: procedure.path) : commencer_url(path: procedure.path)), target: :blank, rel: :noopener do
|
||||
%span.icon.in-progress
|
||||
.dropdown-description
|
||||
%h4= t('administrateurs.dropdown_actions.to_test')
|
||||
|
||||
- unless procedure.discarded?
|
||||
%li
|
||||
= link_to admin_procedure_clone_path(procedure.id), class: 'clone-btn', data: { method: :put } do
|
||||
%span.icon.new-folder
|
||||
.dropdown-description
|
||||
%h4= t('administrateurs.dropdown_actions.to_clone')
|
||||
- if !procedure.close? && !procedure.discarded?
|
||||
- menu.with_item do
|
||||
= link_to(sanitize_url(procedure.brouillon? ? commencer_test_url(path: procedure.path) : commencer_url(path: procedure.path)), target: :blank, rel: :noopener, role: 'menuitem') do
|
||||
%span.icon.in-progress
|
||||
.dropdown-description
|
||||
%h4= t('administrateurs.dropdown_actions.to_test')
|
||||
|
||||
- if procedure.publiee?
|
||||
%li
|
||||
= link_to admin_procedure_close_path(procedure_id: procedure.id) do
|
||||
%span.icon.archive
|
||||
.dropdown-description
|
||||
%h4= t('administrateurs.dropdown_actions.to_close')
|
||||
- if !procedure.discarded?
|
||||
- menu.with_item do
|
||||
= link_to(admin_procedure_clone_path(procedure.id), role: 'menuitem', class: 'clone-btn', data: { method: :put }) do
|
||||
%span.icon.new-folder
|
||||
.dropdown-description
|
||||
%h4= t('administrateurs.dropdown_actions.to_clone')
|
||||
|
||||
- if procedure.can_be_deleted_by_administrateur? && !procedure.discarded?
|
||||
%li
|
||||
= link_to admin_procedure_path(procedure), method: :delete, data: { confirm: "Voulez-vous vraiment supprimer la démarche ? \nToute suppression est définitive et s'appliquera aux éventuels autres administrateurs de cette démarche !" } do
|
||||
%span.icon.refuse
|
||||
.dropdown-description
|
||||
%h4= t('administrateurs.dropdown_actions.delete')
|
||||
- if procedure.publiee?
|
||||
- menu.with_item do
|
||||
= link_to(admin_procedure_close_path(procedure_id: procedure.id), role: 'menuitem') do
|
||||
%span.icon.archive
|
||||
.dropdown-description
|
||||
%h4= t('administrateurs.dropdown_actions.to_close')
|
||||
|
||||
- if procedure.can_be_deleted_by_administrateur? && !procedure.discarded?
|
||||
- menu.with_item do
|
||||
= link_to admin_procedure_path(procedure), role: 'menuitem', method: :delete, data: { confirm: "Voulez-vous vraiment supprimer la démarche ? \nToute suppression est définitive et s'appliquera aux éventuels autres administrateurs de cette démarche !" } do
|
||||
%span.icon.refuse
|
||||
.dropdown-description
|
||||
%h4= t('administrateurs.dropdown_actions.delete')
|
||||
|
||||
- if procedure.discarded?
|
||||
- menu.with_item do
|
||||
= link_to restore_admin_procedure_path(procedure), role: 'menuitem', method: :put do
|
||||
%span.icon.unarchive
|
||||
.dropdown-description
|
||||
%h4= t('administrateurs.dropdown_actions.restore')
|
||||
|
||||
- if procedure.discarded?
|
||||
%li
|
||||
= link_to restore_admin_procedure_path(procedure), method: :put do
|
||||
%span.icon.unarchive
|
||||
.dropdown-description
|
||||
%h4= t('administrateurs.dropdown_actions.restore')
|
||||
|
|
|
@ -18,7 +18,7 @@ describe 'As an administrateur I wanna clone a procedure', js: true do
|
|||
scenario do
|
||||
visit admin_procedures_path
|
||||
expect(page.find_by_id('procedures')['data-item-count']).to eq('1')
|
||||
page.all('.procedures-actions-btn').first.click
|
||||
page.all('.admin-procedures-list-row .dropdown .fr-btn').first.click
|
||||
page.all('.clone-btn').first.click
|
||||
visit admin_procedures_path(statut: "brouillons")
|
||||
expect(page.find_by_id('procedures')['data-item-count']).to eq('1')
|
||||
|
|
Loading…
Reference in a new issue