From 2d568dd825e4b087d7cea1c4d1fa49d916b395c2 Mon Sep 17 00:00:00 2001 From: Albina <51043550+albinazs@users.noreply.github.com> Date: Wed, 17 Jul 2024 00:27:27 +0300 Subject: [PATCH] Content checks design upgrade: help text and separate cards (#12090) Co-authored-by: Thibaud Colas --- CHANGELOG.txt | 1 + client/scss/components/_a11y-result.scss | 46 +++++----- client/src/entrypoints/admin/preview-panel.js | 12 +-- client/src/includes/a11y-result.ts | 83 +++++++++---------- client/src/includes/userbar.ts | 12 +-- docs/releases/6.2.md | 2 +- .../shared/side_panels/checks.html | 26 +++--- .../userbar/item_accessibility.html | 20 ++--- wagtail/admin/tests/test_userbar.py | 28 +++++-- wagtail/admin/userbar.py | 59 ++++++++----- 10 files changed, 146 insertions(+), 143 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ab3cb2938996..40116ffb7ee5 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -23,6 +23,7 @@ Changelog * Make `routable_resolver_match` attribute available on RoutablePageMixin responses (Andy Chosak) * Support customizations to `UserViewSet` via the app config (Sage Abdullah) * Add word count and reading time metrics within the page editor (Albina Starykova. Sponsored by The Motley Fool) + * Implement a new design for accessibility checks (Albina Starykova) * Fix: Make `WAGTAILIMAGES_CHOOSER_PAGE_SIZE` setting functional again (Rohit Sharma) * Fix: Enable `richtext` template tag to convert lazy translation values (Benjamin Bach) * Fix: Ensure permission labels on group permissions page are translated where available (Matt Westcott) diff --git a/client/scss/components/_a11y-result.scss b/client/scss/components/_a11y-result.scss index caf97c80b6a7..a76488b17e6e 100644 --- a/client/scss/components/_a11y-result.scss +++ b/client/scss/components/_a11y-result.scss @@ -1,10 +1,12 @@ .w-a11y-result__row { @include box; padding: theme('spacing.4'); + display: flex; + justify-content: space-between; } .w-a11y-result__header { - margin: 0; + margin: 0 0 theme('spacing.[0.5]'); width: 100%; display: flex; justify-content: space-between; @@ -21,40 +23,34 @@ font-weight: theme('fontWeight.semibold'); } -.w-a11y-result__container { - display: flex; - flex-wrap: wrap; - gap: theme('spacing.[2.5]'); - padding-top: theme('spacing.3'); -} - -.w-a11y-result__subtotal_count { - color: theme('colors.icon-primary'); - width: theme('spacing.5'); - text-align: center; - font-size: theme('fontSize.11'); - font-weight: theme('fontWeight.normal'); +.w-a11y-result__help { + color: theme('colors.text-placeholder'); + font-size: theme('fontSize.14'); .w-dialog--userbar & { - font-size: theme('fontSize.14'); + font-size: theme('fontSize.16'); } } .w-a11y-result__selector { display: flex; align-items: center; - background: theme('colors.surface-field-inactive'); - color: theme('colors.text-context'); + justify-content: center; + background: theme('colors.surface-page'); border-radius: theme('borderRadius.DEFAULT'); - padding: theme('spacing.[1.5]'); + margin-top: calc(theme('spacing.[2.5]') * -1); + margin-inline-end: calc(theme('spacing.[2.5]') * -1); + height: theme('spacing.[7.5]'); + width: theme('spacing.[7.5]'); &:hover, &:focus { - background: theme('colors.surface-button-default'); - color: theme('colors.text-button'); + background: theme('colors.surface-header'); .w-a11y-result__icon { - fill: theme('colors.text-button'); + @apply w-scale-110; + + fill: theme('colors.text-context'); } } @@ -64,11 +60,12 @@ } .w-a11y-result__icon { + @apply w-transition hover:w-transform; + flex-shrink: 0; fill: theme('colors.surface-button-default'); height: theme('spacing.[3.5]'); width: theme('spacing.[3.5]'); - margin-inline-end: theme('spacing.[1.5]'); } .w-a11y-result__count { @@ -79,7 +76,6 @@ background-color: theme('colors.positive.100'); border-radius: theme('borderRadius.full'); font-size: theme('fontSize.11'); - line-height: theme('lineHeight.none'); height: theme('spacing.4'); width: theme('spacing.4'); color: theme('colors.text-button'); @@ -91,4 +87,8 @@ @media (forced-colors: active) { border: theme('spacing.px') solid ButtonText; } + + .w-userbar & { + line-height: theme('lineHeight.none'); + } } diff --git a/client/src/entrypoints/admin/preview-panel.js b/client/src/entrypoints/admin/preview-panel.js index 9c0ddce22a68..4d36c64e9ac8 100644 --- a/client/src/entrypoints/admin/preview-panel.js +++ b/client/src/entrypoints/admin/preview-panel.js @@ -29,9 +29,6 @@ const runContentChecks = async () => { const runAccessibilityChecks = async (onClickSelector) => { const a11yRowTemplate = document.querySelector('#w-a11y-result-row-template'); - const a11ySelectorTemplate = document.querySelector( - '#w-a11y-result-selector-template', - ); const checksPanel = document.querySelector('[data-checks-panel]'); const config = getAxeConfiguration(document.body); const toggleCounter = document.querySelector( @@ -41,13 +38,7 @@ const runAccessibilityChecks = async (onClickSelector) => { '[data-side-panel="checks"] [data-a11y-result-count]', ); - if ( - !a11yRowTemplate || - !a11ySelectorTemplate || - !config || - !toggleCounter || - !panelCounter - ) { + if (!a11yRowTemplate || !config || !toggleCounter || !panelCounter) { return; } @@ -75,7 +66,6 @@ const runAccessibilityChecks = async (onClickSelector) => { results, config, a11yRowTemplate, - a11ySelectorTemplate, onClickSelector, ); }; diff --git a/client/src/includes/a11y-result.ts b/client/src/includes/a11y-result.ts index cff531b2bc3e..06971b9ccc5e 100644 --- a/client/src/includes/a11y-result.ts +++ b/client/src/includes/a11y-result.ts @@ -40,10 +40,15 @@ export const sortAxeViolations = (violations: Result[]) => * Wagtail's Axe configuration object. This should reflect what's returned by * `wagtail.admin.userbar.AccessibilityItem.get_axe_configuration()`. */ + +interface ErrorMessage { + error_name: string; + help_text: string; +} export interface WagtailAxeConfiguration { context: ElementContext; options: RunOptions; - messages: Record; + messages: Record; spec: Spec; } @@ -151,7 +156,6 @@ export const renderA11yResults = ( results: AxeResults, config: WagtailAxeConfiguration, a11yRowTemplate: HTMLTemplateElement, - a11ySelectorTemplate: HTMLTemplateElement, onClickSelector: (selectorName: string, event: MouseEvent) => void, ) => { // Reset contents ahead of rendering new results. @@ -160,54 +164,47 @@ export const renderA11yResults = ( if (results.violations.length) { const sortedViolations = sortAxeViolations(results.violations); - sortedViolations.forEach((violation, violationIndex) => { - container.appendChild(a11yRowTemplate.content.cloneNode(true)); - const currentA11yRow = container.querySelectorAll( - '[data-a11y-result-row]', - )[violationIndex]; - - const a11yErrorName = currentA11yRow.querySelector( - '[data-a11y-result-name]', - ) as HTMLSpanElement; - a11yErrorName.id = `w-a11y-result__name-${violationIndex}`; - // Display custom error messages supplied by Wagtail if available, - // fallback to default error message from Axe - a11yErrorName.textContent = - config.messages[violation.id] || violation.help; - const a11yErrorCount = currentA11yRow.querySelector( - '[data-a11y-result-count]', - ) as HTMLSpanElement; - a11yErrorCount.textContent = `${violation.nodes.length}`; - - const a11yErrorContainer = currentA11yRow.querySelector( - '[data-a11y-result-container]', - ) as HTMLDivElement; - - violation.nodes.forEach((node, nodeIndex) => { - a11yErrorContainer.appendChild( - a11ySelectorTemplate.content.cloneNode(true), - ); - const currentA11ySelector = - a11yErrorContainer.querySelectorAll( - '[data-a11y-result-selector]', - )[nodeIndex]; - - currentA11ySelector.setAttribute('aria-describedby', a11yErrorName.id); - const currentA11ySelectorText = currentA11ySelector.querySelector( - '[data-a11y-result-selector-text]', + let nodeCounter = 0; + sortedViolations.forEach((violation) => { + violation.nodes.forEach((node) => { + container.appendChild(a11yRowTemplate.content.cloneNode(true)); + + const currentA11yRow = container.querySelectorAll( + '[data-a11y-result-row]', + )[nodeCounter]; + nodeCounter += 1; + + const a11yErrorName = currentA11yRow.querySelector( + '[data-a11y-result-name]', ) as HTMLSpanElement; + const a11yErrorHelp = currentA11yRow.querySelector( + '[data-a11y-result-help]', + ) as HTMLDivElement; + a11yErrorName.id = `w-a11y-result__name-${nodeCounter}`; + + // Display custom error messages supplied by Wagtail if available, + // fallback to default error message from Axe + const messages = config.messages[violation.id]; + + const name = + (typeof messages === 'string' ? messages : messages?.error_name) || + violation.help; + a11yErrorName.textContent = name; + a11yErrorHelp.textContent = + messages?.help_text || violation.description; + // Special-case when displaying accessibility results within the admin interface. const selectorName = toSelector( node.target[0] === '#preview-iframe' ? node.target[1] : node.target[0], ); - // Remove unnecessary details before displaying selectors to the user - currentA11ySelectorText.textContent = selectorName.replace( - /\[data-block-key="\w{5}"\]/, - '', - ); - currentA11ySelector.addEventListener( + + const a11ySelector = currentA11yRow.querySelector( + '[data-a11y-result-selector]', + ) as HTMLButtonElement; + a11ySelector.setAttribute('aria-describedby', a11yErrorName.id); + a11ySelector?.addEventListener( 'click', onClickSelector.bind(null, selectorName), ); diff --git a/client/src/includes/userbar.ts b/client/src/includes/userbar.ts index 10c2f7984b73..965ae1cf8977 100644 --- a/client/src/includes/userbar.ts +++ b/client/src/includes/userbar.ts @@ -362,21 +362,12 @@ export class Userbar extends HTMLElement { const a11yRowTemplate = this.shadowRoot.querySelector( '#w-a11y-result-row-template', ); - const a11ySelectorTemplate = - this.shadowRoot.querySelector( - '#w-a11y-result-selector-template', - ); const a11yOutlineTemplate = this.shadowRoot.querySelector( '#w-a11y-result-outline-template', ); - if ( - !accessibilityResultsBox || - !a11yRowTemplate || - !a11ySelectorTemplate || - !a11yOutlineTemplate - ) { + if (!accessibilityResultsBox || !a11yRowTemplate || !a11yOutlineTemplate) { return; } @@ -460,7 +451,6 @@ export class Userbar extends HTMLElement { results, config, a11yRowTemplate, - a11ySelectorTemplate, onClickSelector, ); } else { diff --git a/docs/releases/6.2.md b/docs/releases/6.2.md index 4cd3f07d8b22..1b09cade9adf 100644 --- a/docs/releases/6.2.md +++ b/docs/releases/6.2.md @@ -43,7 +43,7 @@ This feature was developed by Albina Starykova and sponsored by The Motley Fool. * Implement universal listings UI for report views (Sage Abdullah) * Make `routable_resolver_match` attribute available on RoutablePageMixin responses (Andy Chosak) * Support customizations to `UserViewSet` via the app config (Sage Abdullah) - + * Implement a new design for accessibility checks (Albina Starykova, sponsored by The Motley Fool) ### Bug fixes diff --git a/wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html b/wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html index 7ed0d988814b..6d5634a94410 100644 --- a/wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html +++ b/wagtail/admin/templates/wagtailadmin/shared/side_panels/checks.html @@ -1,20 +1,18 @@ {% load i18n wagtailadmin_tags %} - -

@@ -31,8 +29,8 @@

{% trans 'Reading time' %}<

-
-

+
+

{% trans 'Issues found' %}0

diff --git a/wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html b/wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html index 75e4c0d91a78..b735f859a233 100644 --- a/wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html +++ b/wagtail/admin/templates/wagtailadmin/userbar/item_accessibility.html @@ -17,19 +17,17 @@ {# Contents of the dialog created in JS based on these templates. #} - diff --git a/wagtail/admin/tests/test_userbar.py b/wagtail/admin/tests/test_userbar.py index 54c0d206a449..f8fb0f5c14fe 100644 --- a/wagtail/admin/tests/test_userbar.py +++ b/wagtail/admin/tests/test_userbar.py @@ -208,22 +208,32 @@ def test_messages(self): config = self.get_config() self.assertIsInstance(config.get("messages"), dict) self.assertEqual( - config["messages"]["empty-heading"], - "Empty heading found. Use meaningful text for screen reader users.", + config["messages"]["empty-heading"]["error_name"], + "Empty heading found", + ) + self.assertEqual( + config["messages"]["empty-heading"]["help_text"], + "Use meaningful text for screen reader users", ) def test_custom_message(self): class CustomMessageAccessibilityItem(AccessibilityItem): # Override via class attribute axe_messages = { - "empty-heading": "Headings should not be empty!", + "empty-heading": { + "error_name": "Headings should not be empty!", + "help_text": "Use meaningful text!", + }, } # Override via method def get_axe_messages(self, request): return { **super().get_axe_messages(request), - "color-contrast-enhanced": "Increase colour contrast!", + "color-contrast-enhanced": { + "error_name": "Insufficient colour contrast!", + "help_text": "Ensure contrast ratio of at least 4.5:1", + }, } with hooks.register_temporarily( @@ -234,8 +244,14 @@ def get_axe_messages(self, request): self.assertEqual( config["messages"], { - "empty-heading": "Headings should not be empty!", - "color-contrast-enhanced": "Increase colour contrast!", + "empty-heading": { + "error_name": "Headings should not be empty!", + "help_text": "Use meaningful text!", + }, + "color-contrast-enhanced": { + "error_name": "Insufficient colour contrast!", + "help_text": "Ensure contrast ratio of at least 4.5:1", + }, }, ) diff --git a/wagtail/admin/userbar.py b/wagtail/admin/userbar.py index ca4b670d2fdc..889234b3cd86 100644 --- a/wagtail/admin/userbar.py +++ b/wagtail/admin/userbar.py @@ -92,29 +92,42 @@ class AccessibilityItem(BaseItem): #: to use as the error messages. If an enabled rule does not exist in this #: dictionary, Axe's error message for the rule will be used as fallback. axe_messages = { - "button-name": _( - "Button text is empty. Use meaningful text for screen reader users." - ), - "empty-heading": _( - "Empty heading found. Use meaningful text for screen reader users." - ), - "empty-table-header": _( - "Table header text is empty. Use meaningful text for screen reader users." - ), - "frame-title": _( - "Empty frame title found. Use a meaningful title for screen reader users." - ), - "heading-order": _("Incorrect heading hierarchy. Avoid skipping levels."), - "input-button-name": _( - "Input button text is empty. Use meaningful text for screen reader users." - ), - "link-name": _( - "Link text is empty. Use meaningful text for screen reader users." - ), - "p-as-heading": _("Misusing paragraphs as headings. Use proper heading tags."), - "alt-text-quality": _( - "Image alt text has inappropriate pattern. Use meaningful text." - ), + "button-name": { + "error_name": _("Button text is empty"), + "help_text": _("Use meaningful text for screen reader users"), + }, + "empty-heading": { + "error_name": _("Empty heading found"), + "help_text": _("Use meaningful text for screen reader users"), + }, + "empty-table-header": { + "error_name": _("Table header text is empty"), + "help_text": _("Use meaningful text for screen reader users"), + }, + "frame-title": { + "error_name": _("Empty frame title found"), + "help_text": _("Use a meaningful title for screen reader users"), + }, + "heading-order": { + "error_name": _("Incorrect heading hierarchy"), + "help_text": _("Avoid skipping levels"), + }, + "input-button-name": { + "error_name": _("Input button text is empty"), + "help_text": _("Use meaningful text for screen reader users"), + }, + "link-name": { + "error_name": _("Link text is empty"), + "help_text": _("Use meaningful text for screen reader users"), + }, + "p-as-heading": { + "error_name": _("Misusing paragraphs as headings"), + "help_text": _("Use proper heading tags"), + }, + "alt-text-quality": { + "error_name": _("Image alt text has inappropriate pattern"), + "help_text": _("Use meaningful text"), + }, } def get_axe_include(self, request):