kopia lustrzana https://github.com/wagtail/wagtail
Content checks design upgrade: help text and separate cards (#12090)
Co-authored-by: Thibaud Colas <thibaudcolas@gmail.com>pull/12160/head
rodzic
62674d3fbb
commit
2d568dd825
|
@ -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)
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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<string, string>;
|
||||
messages: Record<string, ErrorMessage>;
|
||||
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<HTMLDivElement>(
|
||||
'[data-a11y-result-row]',
|
||||
)[violationIndex];
|
||||
let nodeCounter = 0;
|
||||
sortedViolations.forEach((violation) => {
|
||||
violation.nodes.forEach((node) => {
|
||||
container.appendChild(a11yRowTemplate.content.cloneNode(true));
|
||||
|
||||
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 currentA11yRow = container.querySelectorAll<HTMLDivElement>(
|
||||
'[data-a11y-result-row]',
|
||||
)[nodeCounter];
|
||||
nodeCounter += 1;
|
||||
|
||||
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<HTMLButtonElement>(
|
||||
'[data-a11y-result-selector]',
|
||||
)[nodeIndex];
|
||||
|
||||
currentA11ySelector.setAttribute('aria-describedby', a11yErrorName.id);
|
||||
const currentA11ySelectorText = currentA11ySelector.querySelector(
|
||||
'[data-a11y-result-selector-text]',
|
||||
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),
|
||||
);
|
||||
|
|
|
@ -362,21 +362,12 @@ export class Userbar extends HTMLElement {
|
|||
const a11yRowTemplate = this.shadowRoot.querySelector<HTMLTemplateElement>(
|
||||
'#w-a11y-result-row-template',
|
||||
);
|
||||
const a11ySelectorTemplate =
|
||||
this.shadowRoot.querySelector<HTMLTemplateElement>(
|
||||
'#w-a11y-result-selector-template',
|
||||
);
|
||||
const a11yOutlineTemplate =
|
||||
this.shadowRoot.querySelector<HTMLTemplateElement>(
|
||||
'#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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,20 +1,18 @@
|
|||
{% load i18n wagtailadmin_tags %}
|
||||
|
||||
<template id="w-a11y-result-row-template">
|
||||
<div class="w-a11y-result__row" data-a11y-result-row>
|
||||
<h3 class="w-a11y-result__header">
|
||||
<span class="w-a11y-result__name" data-a11y-result-name></span>
|
||||
<span class="w-sr-only">{% trans 'Issues found' %}</span><span class="w-a11y-result__subtotal_count" data-a11y-result-count></span>
|
||||
</h3>
|
||||
<div class="w-a11y-result__container" data-a11y-result-container></div>
|
||||
<div>
|
||||
<h3 class="w-a11y-result__header">
|
||||
<span class="w-a11y-result__name" data-a11y-result-name></span>
|
||||
</h3>
|
||||
<div class="w-a11y-result__help" data-a11y-result-help></div>
|
||||
</div>
|
||||
<button class="w-a11y-result__selector"
|
||||
data-a11y-result-selector
|
||||
aria-label="{% trans 'Show issue' %}"
|
||||
type="button">{% icon name="crosshairs" classname="w-a11y-result__icon" %}</button>
|
||||
</div>
|
||||
</template>
|
||||
<template id="w-a11y-result-selector-template">
|
||||
<button class="w-a11y-result__selector" data-a11y-result-selector type="button">
|
||||
{% icon name="link-external" classname="w-a11y-result__icon" %}
|
||||
<span data-a11y-result-selector-text></span>
|
||||
</button>
|
||||
</template>
|
||||
<div class="w-divide-y w-divide-border-furniture w-py-6 w-pl-2 lg:w-pl-8">
|
||||
<div>
|
||||
<h2 class="w-my-5 w-text-16 w-font-bold w-text-text-label">
|
||||
|
@ -31,8 +29,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="w-flex w-items-center w-gap-2 w-my-5 w-text-16 w-font-bold w-text-text-label">
|
||||
<div class="w-mt-3">
|
||||
<h2 class="w-flex w-items-center w-gap-2 w-my-5 w-text-16 w-font-bold">
|
||||
<span>{% trans 'Issues found' %}</span><span class="w-a11y-result__count" data-a11y-result-count>0</span>
|
||||
</h2>
|
||||
<div class="w-flex w-flex-col w-gap-2.5" data-checks-panel></div>
|
||||
|
|
|
@ -17,19 +17,17 @@
|
|||
{# Contents of the dialog created in JS based on these templates. #}
|
||||
<template id="w-a11y-result-row-template">
|
||||
<div class="w-a11y-result__row" data-a11y-result-row>
|
||||
<h3 class="w-a11y-result__header">
|
||||
<span class="w-a11y-result__name" data-a11y-result-name></span>
|
||||
<span class="w-sr-only">{% trans 'Issues found' %}</span><span class="w-a11y-result__subtotal_count" data-a11y-result-count></span>
|
||||
</h3>
|
||||
<div class="w-a11y-result__container" data-a11y-result-container></div>
|
||||
<div>
|
||||
<h3 class="w-a11y-result__header">
|
||||
<span class="w-a11y-result__name" data-a11y-result-name></span>
|
||||
</h3>
|
||||
<div class="w-a11y-result__help" data-a11y-result-help></div>
|
||||
</div>
|
||||
<button class="w-a11y-result__selector" data-a11y-result-selector aria-label="{% trans 'Show issue' %}" type="button">
|
||||
{% icon name="crosshairs" classname="w-a11y-result__icon" %}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<template id="w-a11y-result-selector-template">
|
||||
<button class="w-a11y-result__selector" data-a11y-result-selector type="button">
|
||||
{% icon name="crosshairs" classname="w-a11y-result__icon" %}
|
||||
<span data-a11y-result-selector-text></span>
|
||||
</button>
|
||||
</template>
|
||||
<template id="w-a11y-result-outline-template">
|
||||
<div class="w-a11y-result__outline" data-a11y-result-outline></div>
|
||||
</template>
|
||||
|
|
|
@ -208,22 +208,32 @@ class TestAccessibilityCheckerConfig(WagtailTestUtils, TestCase):
|
|||
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 @@ class TestAccessibilityCheckerConfig(WagtailTestUtils, TestCase):
|
|||
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",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
Ładowanie…
Reference in New Issue