From acc7caa81670ae86f66cf30db9f49fccda808ddd Mon Sep 17 00:00:00 2001 From: Kris Date: Tue, 19 Nov 2024 15:38:13 -0500 Subject: [PATCH] A11Y: make the uppy image uploader keyboard navigable (#29807) --- .../app/components/uppy-image-uploader.hbs | 5 +++- .../app/components/uppy-image-uploader.js | 18 +++++++++++++++ spec/system/admin_about_config_area_spec.rb | 23 +++++++++++++++++++ .../components/uppy_image_uploader.rb | 11 +++++++++ 4 files changed, 56 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/components/uppy-image-uploader.hbs b/app/assets/javascripts/discourse/app/components/uppy-image-uploader.hbs index d331d6d9a11..5b9e65d368f 100644 --- a/app/assets/javascripts/discourse/app/components/uppy-image-uploader.hbs +++ b/app/assets/javascripts/discourse/app/components/uppy-image-uploader.hbs @@ -9,13 +9,16 @@ diff --git a/app/assets/javascripts/discourse/app/components/uppy-image-uploader.js b/app/assets/javascripts/discourse/app/components/uppy-image-uploader.js index 6167ff41b61..fe9ef9e3b0b 100644 --- a/app/assets/javascripts/discourse/app/components/uppy-image-uploader.js +++ b/app/assets/javascripts/discourse/app/components/uppy-image-uploader.js @@ -1,6 +1,7 @@ import Component from "@ember/component"; import { action } from "@ember/object"; import { or } from "@ember/object/computed"; +import { guidFor } from "@ember/object/internals"; import { getOwner } from "@ember/owner"; import { next } from "@ember/runloop"; import { htmlSafe } from "@ember/template"; @@ -58,6 +59,12 @@ export default class UppyImageUploader extends Component { }); } + @discourseComputed("id") + computedId(id) { + // without a fallback ID this will not be accessible + return id ? `${id}__input` : `${guidFor(this)}__input`; + } + @discourseComputed("siteSettings.enable_experimental_lightbox") experimentalLightboxEnabled(experimentalLightboxEnabled) { return experimentalLightboxEnabled; @@ -155,4 +162,15 @@ export default class UppyImageUploader extends Component { this.setProperties({ imageUrl: null }); } } + + @action + handleKeyboardActivation(event) { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); // avoid space scrolling the page + const input = document.getElementById(this.computedId); + if (input && !this.disabled) { + input.click(); + } + } + } } diff --git a/spec/system/admin_about_config_area_spec.rb b/spec/system/admin_about_config_area_spec.rb index dcebb9040f1..6946794c6e1 100644 --- a/spec/system/admin_about_config_area_spec.rb +++ b/spec/system/admin_about_config_area_spec.rb @@ -116,6 +116,29 @@ describe "Admin About Config Area Page", type: :system do expect(config_area.general_settings_section).to have_saved_successfully expect(SiteSetting.about_banner_image).to eq(nil) end + + it "can upload an image using keyboard nav" do + config_area.visit + + image_file = file_from_fixtures("logo.png", "images") + config_area.general_settings_section.banner_image_uploader.select_image_with_keyboard( + image_file.path, + ) + + expect(config_area.general_settings_section.banner_image_uploader).to have_uploaded_image + end + + it "can remove the uploaded image using keyboard nav" do + SiteSetting.about_banner_image = image_upload + + config_area.visit + + config_area.general_settings_section.banner_image_uploader.remove_image_with_keyboard + + config_area.general_settings_section.submit + expect(config_area.general_settings_section).to have_saved_successfully + expect(SiteSetting.about_banner_image).to eq(nil) + end end end diff --git a/spec/system/page_objects/components/uppy_image_uploader.rb b/spec/system/page_objects/components/uppy_image_uploader.rb index c428e299965..ff5cc5effc8 100644 --- a/spec/system/page_objects/components/uppy_image_uploader.rb +++ b/spec/system/page_objects/components/uppy_image_uploader.rb @@ -11,6 +11,12 @@ module PageObjects attach_file(path) { @element.find("label.btn-default").click } end + def select_image_with_keyboard(path) + label = @element.find("label.btn-default") + label.send_keys(:enter) + attach_file(path) { label.click } + end + def has_uploaded_image? # if there's a delete button (.btn-danger), then there must be an # uploaded image. @@ -22,6 +28,11 @@ module PageObjects def remove_image @element.find(".btn-danger").click end + + def remove_image_with_keyboard + delete_button = @element.find(".btn-danger") + delete_button.send_keys(:enter) + end end end end