diff --git a/gradle/changelog/improve_a11y.yaml b/gradle/changelog/improve_a11y.yaml new file mode 100644 index 0000000000..bab017f3ae --- /dev/null +++ b/gradle/changelog/improve_a11y.yaml @@ -0,0 +1,2 @@ +- type: fixed + description: Improve accessibility ([#1956](https://github.com/scm-manager/scm-manager/pull/1956)) diff --git a/scm-ui/ui-components/src/OverviewPageActions.tsx b/scm-ui/ui-components/src/OverviewPageActions.tsx index 6c60e18708..18c8959975 100644 --- a/scm-ui/ui-components/src/OverviewPageActions.tsx +++ b/scm-ui/ui-components/src/OverviewPageActions.tsx @@ -37,6 +37,7 @@ type Props = { label?: string; testId?: string; searchPlaceholder?: string; + groupAriaLabelledby?: string; }; const createAbsoluteLink = (url: string) => { @@ -52,7 +53,8 @@ const OverviewPageActions: FC = ({ groupSelected, label, testId, - searchPlaceholder + searchPlaceholder, + groupAriaLabelledby }) => { const history = useHistory(); const location = useLocation(); @@ -62,6 +64,7 @@ const OverviewPageActions: FC = ({ const groupSelector = groups && (
> = ({ ariaLabelledby, ...props }) => { - const id = ariaLabelledby || createA11yId("radio"); - const helpId = createA11yId("radio"); + const id = useMemo(() => ariaLabelledby || createA11yId("radio"), [ariaLabelledby]); + const helpId = useMemo(() => createA11yId("radio"), []); const renderHelp = () => { const helpText = props.helpText; @@ -77,7 +77,7 @@ const InnerRadio: FC> = ({ } }; - const labelElement = props.label ? ({props.label}) : null; + const labelElement = props.label ? {props.label} : null; return (
@@ -98,7 +98,7 @@ const InnerRadio: FC> = ({ ref={props.innerRef} defaultChecked={defaultChecked} aria-labelledby={id} - aria-describedby={helpId} + aria-describedby={props.helpText ? helpId : undefined} />{" "} {labelElement} {renderHelp()} diff --git a/scm-ui/ui-components/src/forms/Select.tsx b/scm-ui/ui-components/src/forms/Select.tsx index 21488ffa76..62f14d17e2 100644 --- a/scm-ui/ui-components/src/forms/Select.tsx +++ b/scm-ui/ui-components/src/forms/Select.tsx @@ -124,7 +124,7 @@ const InnerSelect: FC> = ({ onChange={handleInput} onBlur={handleBlur} disabled={disabled} - aria-labelledby={label ? a11yId : undefined} + aria-labelledby={ariaLabelledby || (label ? a11yId : undefined)} aria-describedby={helpText ? helpId : undefined} {...createAttributesForTesting(testId)} > diff --git a/scm-ui/ui-styles/src/components/_main.scss b/scm-ui/ui-styles/src/components/_main.scss index c3549dede0..eff27608af 100644 --- a/scm-ui/ui-styles/src/components/_main.scss +++ b/scm-ui/ui-styles/src/components/_main.scss @@ -511,6 +511,14 @@ ul.is-separated { &.is-darker { background-color: #e1e1e1; } + // Explicitly "remove" styles from td element to use it as an empty table column header, which is necessary for + // a11y because an empty th element is not allowed. + &.has-no-style { + background-color: transparent; + &.is-darker { + background-color: transparent; + } + } } a { color: $blue; @@ -528,6 +536,13 @@ ul.is-separated { &.is-darker { background-color: whitesmoke; } + // Explicitly "remove" styles from td element to use it as an empty table column header, which is necessary for + // a11y because an empty th element is not allowed. + &.has-no-style { + padding: 0; + background-color: transparent; + border: none; + } } &.is-hoverable tbody tr:not(.is-selected):hover { background-color: whitesmoke; diff --git a/scm-ui/ui-webapp/public/locales/de/commons.json b/scm-ui/ui-webapp/public/locales/de/commons.json index 1d06c46cbd..c1bf59505d 100644 --- a/scm-ui/ui-webapp/public/locales/de/commons.json +++ b/scm-ui/ui-webapp/public/locales/de/commons.json @@ -246,5 +246,8 @@ }, "pdfViewer": { "error": "Das Dokument konnte nicht angezeigt werden. Es kann <1>hier heruntergeladen werden." + }, + "tag": { + "delete": "Löschen" } } diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json index 07dd67e09c..ddfb6a369a 100644 --- a/scm-ui/ui-webapp/public/locales/de/repos.json +++ b/scm-ui/ui-webapp/public/locales/de/repos.json @@ -60,6 +60,7 @@ "invalidNamespace": "Keine Repositories gefunden. Möglicherweise existiert der ausgewählte Namespace nicht.", "createButton": "Repository hinzufügen", "filterRepositories": "Repositories filtern", + "filterByNamespace": "Nach Namespace filtern", "allNamespaces": "Alle Namespaces", "clone": "Clone/Checkout", "contact": "E-Mail senden an {{contact}}", diff --git a/scm-ui/ui-webapp/public/locales/en/commons.json b/scm-ui/ui-webapp/public/locales/en/commons.json index 4611c1b0b7..094227b0c5 100644 --- a/scm-ui/ui-webapp/public/locales/en/commons.json +++ b/scm-ui/ui-webapp/public/locales/en/commons.json @@ -247,5 +247,8 @@ }, "pdfViewer": { "error": "Failed to display the document. Please download it from <1>here." + }, + "tag": { + "delete": "Delete" } } diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json index 9c787efc18..0cfb998e17 100644 --- a/scm-ui/ui-webapp/public/locales/en/repos.json +++ b/scm-ui/ui-webapp/public/locales/en/repos.json @@ -60,6 +60,7 @@ "invalidNamespace": "No repositories found. It's likely that the selected namespace does not exist.", "createButton": "Add Repository", "filterRepositories": "Filter repositories", + "filterByNamespace": "Filter by namespace", "allNamespaces": "All namespaces", "clone": "Clone/Checkout", "contact": "Send mail to {{contact}}", diff --git a/scm-ui/ui-webapp/src/repos/containers/Overview.tsx b/scm-ui/ui-webapp/src/repos/containers/Overview.tsx index 6acdb04d35..d2cc1fb112 100644 --- a/scm-ui/ui-webapp/src/repos/containers/Overview.tsx +++ b/scm-ui/ui-webapp/src/repos/containers/Overview.tsx @@ -191,19 +191,25 @@ const Overview: FC = () => {
{showActions ? ( - n.namespace === namespace) ? namespace : "" - } - groups={namespacesToRender} - groupSelected={namespaceSelected} - link={namespace ? `repos/${namespace}` : "repos"} - createLink="/repos/create/" - label={t("overview.createButton")} - testId="repository-overview" - searchPlaceholder={t("overview.filterRepositories")} - /> + <> + + n.namespace === namespace) ? namespace : "" + } + groups={namespacesToRender} + groupSelected={namespaceSelected} + groupAriaLabelledby="select-namespace" + link={namespace ? `repos/${namespace}` : "repos"} + createLink="/repos/create/" + label={t("overview.createButton")} + testId="repository-overview" + searchPlaceholder={t("overview.filterRepositories")} + /> + ) : null}