From dc5f7d0f2319dad4973c45ebb7e919043bdff762 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 16 Nov 2021 11:35:58 +0100 Subject: [PATCH] Feature/fix tabulator stops (#1831) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tab stops to action to increase accessibility of SCM-Manager with keyboard only usage. Also add a focus trap for modals to ensure the actions inside the modal can be used without losing the focus. Co-authored-by: René Pfeuffer --- gradle/changelog/keyboard-access.yaml | 4 + scm-ui/ui-components/package.json | 1 + scm-ui/ui-components/src/Autocomplete.tsx | 3 + scm-ui/ui-components/src/BranchSelector.tsx | 8 +- scm-ui/ui-components/src/Breadcrumb.tsx | 18 +- scm-ui/ui-components/src/CardColumn.tsx | 1 + scm-ui/ui-components/src/Help.tsx | 4 +- scm-ui/ui-components/src/Tooltip.tsx | 5 +- .../src/__snapshots__/storyshots.test.ts.snap | 1004 ++++++++++++++++- scm-ui/ui-components/src/buttons/Button.tsx | 4 +- .../src/buttons/RemoveEntryOfTableButton.tsx | 4 +- .../src/buttons/SubmitButton.tsx | 4 +- scm-ui/ui-components/src/createA11yId.tsx | 27 + .../src/forms/AddEntryToTableField.tsx | 4 +- .../AutocompleteAddEntryToTableField.tsx | 4 +- scm-ui/ui-components/src/forms/Checkbox.tsx | 10 +- scm-ui/ui-components/src/forms/FileInput.tsx | 6 +- .../ui-components/src/forms/FilterInput.tsx | 4 +- scm-ui/ui-components/src/forms/InputField.tsx | 8 +- .../src/forms/LabelWithHelpIcon.tsx | 10 +- scm-ui/ui-components/src/forms/Radio.tsx | 14 +- scm-ui/ui-components/src/forms/Select.tsx | 9 +- scm-ui/ui-components/src/forms/Textarea.tsx | 8 +- scm-ui/ui-components/src/index.ts | 1 + .../ui-components/src/layout/GroupEntry.tsx | 11 +- .../ui-components/src/modals/ConfirmAlert.tsx | 2 + scm-ui/ui-components/src/modals/Modal.tsx | 20 +- .../src/repos/RepositoryEntry.tsx | 1 + scm-ui/ui-components/src/useTrapFocus.ts | 163 +++ scm-ui/ui-webapp/public/locales/de/repos.json | 3 +- scm-ui/ui-webapp/public/locales/en/repos.json | 3 +- .../admin/components/form/GeneralSettings.tsx | 1 + .../admin/plugins/components/PluginEntry.tsx | 22 +- scm-ui/ui-webapp/src/containers/Theme.tsx | 39 +- .../src/groups/components/table/Details.tsx | 2 +- .../repos/branches/components/BranchRow.tsx | 2 +- .../branches/containers/BranchesOverview.tsx | 2 +- .../components/FileSearchButton.tsx | 2 +- .../codeSection/containers/FileSearch.tsx | 4 +- .../components/DeletePermissionButton.tsx | 8 +- .../src/repos/sources/components/FileIcon.tsx | 8 +- .../repos/sources/components/FileTreeLeaf.tsx | 4 +- .../sources/components/content/FileLink.tsx | 21 +- .../src/repos/tags/components/TagRow.tsx | 4 +- .../src/repos/tags/container/TagsOverview.tsx | 2 +- .../src/users/components/table/Details.tsx | 4 +- yarn.lock | 5 + 47 files changed, 1380 insertions(+), 118 deletions(-) create mode 100644 gradle/changelog/keyboard-access.yaml create mode 100644 scm-ui/ui-components/src/createA11yId.tsx create mode 100644 scm-ui/ui-components/src/useTrapFocus.ts diff --git a/gradle/changelog/keyboard-access.yaml b/gradle/changelog/keyboard-access.yaml new file mode 100644 index 0000000000..932f89ab9b --- /dev/null +++ b/gradle/changelog/keyboard-access.yaml @@ -0,0 +1,4 @@ +- type: changed + description: Improve keyboard access by adding tab stops ([#1831](https://github.com/scm-manager/scm-manager/pull/1831)) +- type: changed + description: Improve aria lables for better screen reader support ([#1831](https://github.com/scm-manager/scm-manager/pull/1831)) diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index 0951f33a56..7eaa742faa 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -60,6 +60,7 @@ "react-test-renderer": "^17.0.1", "sass-loader": "^12.3.0", "storybook-addon-i18next": "^1.3.0", + "tabbable": "^5.2.1", "storybook-addon-themes": "^6.1.0", "to-camel-case": "^1.0.0", "webpack": "^5.61.0", diff --git a/scm-ui/ui-components/src/Autocomplete.tsx b/scm-ui/ui-components/src/Autocomplete.tsx index a415f39802..6e4dad2475 100644 --- a/scm-ui/ui-components/src/Autocomplete.tsx +++ b/scm-ui/ui-components/src/Autocomplete.tsx @@ -81,6 +81,7 @@ class Autocomplete extends React.Component { creatable, className, } = this.props; + return (
@@ -104,6 +105,7 @@ class Autocomplete extends React.Component { }, }); }} + aria-label={helpText || label} /> ) : ( { placeholder={placeholder} loadingMessage={() => loadingMessage} noOptionsMessage={() => noOptionsMessage} + aria-label={helpText || label} /> )}
diff --git a/scm-ui/ui-components/src/BranchSelector.tsx b/scm-ui/ui-components/src/BranchSelector.tsx index c530eb7344..23934e150c 100644 --- a/scm-ui/ui-components/src/BranchSelector.tsx +++ b/scm-ui/ui-components/src/BranchSelector.tsx @@ -26,6 +26,7 @@ import classNames from "classnames"; import styled from "styled-components"; import { Branch } from "@scm-manager/ui-types"; import { Select } from "./forms"; +import { createA11yId } from "./createA11yId"; type Props = { branches: Branch[]; @@ -45,11 +46,15 @@ const MinWidthControl = styled.div` `; const BranchSelector: FC = ({ branches, onSelectBranch, selectedBranch, label, disabled }) => { + const a11yId = createA11yId("branch-select"); + if (branches) { return (
- +
@@ -61,6 +66,7 @@ const BranchSelector: FC = ({ branches, onSelectBranch, selectedBranch, l disabled={!!disabled} value={selectedBranch} addValueToOptions={true} + ariaLabelledby={a11yId} />
diff --git a/scm-ui/ui-components/src/Breadcrumb.tsx b/scm-ui/ui-components/src/Breadcrumb.tsx index 68a605917d..c7cf2e4bdd 100644 --- a/scm-ui/ui-components/src/Breadcrumb.tsx +++ b/scm-ui/ui-components/src/Breadcrumb.tsx @@ -23,11 +23,11 @@ */ import React, { FC, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useHistory, useLocation, Link } from "react-router-dom"; +import { Link, useHistory, useLocation } from "react-router-dom"; import classNames from "classnames"; import styled from "styled-components"; import { urls } from "@scm-manager/ui-api"; -import { Branch, Repository, File } from "@scm-manager/ui-types"; +import { Branch, File, Repository } from "@scm-manager/ui-types"; import { binder, ExtensionPoint, extensionPoints } from "@scm-manager/ui-extensions"; import Icon from "./Icon"; import Tooltip from "./Tooltip"; @@ -68,20 +68,25 @@ const BreadcrumbNav = styled.nav` width: 100%; /* move slash to end */ + li + li::before { content: none; } + li:not(:last-child)::after { color: #b5b5b5; //$breadcrumb-item-separator-color content: "\\0002f"; } + li:first-child { margin-left: 0.75rem; } /* sizing of each item */ + li { max-width: 375px; + a { display: initial; } @@ -94,6 +99,7 @@ const HomeIcon = styled(Icon)` const ActionBar = styled.div` /* ensure space between action bar items */ + & > * { /* * We have to use important, because plugins could use field or control classes like the editor-plugin does. @@ -117,7 +123,7 @@ const Breadcrumb: FC = ({ baseUrl, sources, permalink, - preButtons, + preButtons }) => { const location = useLocation(); const history = useHistory(); @@ -189,13 +195,13 @@ const Breadcrumb: FC = ({ {prefixButtons}
  • - +
  • {pathSection()}
- + e.key === "Enter" && copySource()}> {copying ? ( ) : ( @@ -214,7 +220,7 @@ const Breadcrumb: FC = ({ branch: branch ? branch : defaultBranch, path, sources, - repository, + repository }; const renderExtensionPoints = () => { diff --git a/scm-ui/ui-components/src/CardColumn.tsx b/scm-ui/ui-components/src/CardColumn.tsx index 01b26f1245..cdfc8af572 100644 --- a/scm-ui/ui-components/src/CardColumn.tsx +++ b/scm-ui/ui-components/src/CardColumn.tsx @@ -75,6 +75,7 @@ const CardColumn: FC = ({ e.preventDefault(); action(); }} + tabIndex={0} /> ); } diff --git a/scm-ui/ui-components/src/Help.tsx b/scm-ui/ui-components/src/Help.tsx index 82f7238e6d..e2808b8cbb 100644 --- a/scm-ui/ui-components/src/Help.tsx +++ b/scm-ui/ui-components/src/Help.tsx @@ -31,16 +31,18 @@ type Props = { message: string; multiline?: boolean; className?: string; + id?: string; }; const AbsolutePositionTooltip = styled(Tooltip)` position: absolute; `; -const Help: FC = ({ message, multiline, className }) => ( +const Help: FC = ({ message, multiline, className, id }) => ( diff --git a/scm-ui/ui-components/src/Tooltip.tsx b/scm-ui/ui-components/src/Tooltip.tsx index 6285055014..2fcd539909 100644 --- a/scm-ui/ui-components/src/Tooltip.tsx +++ b/scm-ui/ui-components/src/Tooltip.tsx @@ -29,6 +29,7 @@ type Props = { location: TooltipLocation; multiline?: boolean; children: ReactNode; + id?: string; }; export type TooltipLocation = "bottom" | "right" | "top" | "left"; @@ -39,7 +40,7 @@ class Tooltip extends React.Component { }; render() { - const { className, message, location, multiline, children } = this.props; + const { className, message, location, multiline, children, id } = this.props; let classes = `tooltip has-tooltip-${location}`; if (multiline) { classes += " has-tooltip-multiline"; @@ -49,7 +50,7 @@ class Tooltip extends React.Component { } return ( - + {children} ); diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index d940101f3e..9f547601a8 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -12,6 +12,7 @@ exports[`Storyshots BranchSelector Default 1`] = ` > @@ -33,6 +34,8 @@ exports[`Storyshots BranchSelector Default 1`] = ` className="control select is-fullwidth" > - Value + + Value +
- Key + + Key +
- Value + + Value +
- Upload File + + Upload File + @@ -2500,6 +2572,7 @@ exports[`Storyshots Forms/FileInput Default 1`] = ` className="file-label" > - Field with AutoFocus + + Field with AutoFocus +
- Field with Default Value + + Field with Default Value +
- Readonly + + Readonly +
- Disabled + + Disabled +
- First Name + + First Name +
- Last Name + + Last Name +
- Not checked + + Not checked +
- Checked + + Checked +
@@ -2883,6 +3012,8 @@ exports[`Storyshots Forms/Radio Disabled 1`] = ` disabled={true} > - Checked but disabled + + Checked but disabled +
@@ -2905,6 +3040,8 @@ Array [ className="radio mr-2" > - Remember Me + + Remember Me +
- Dont Remember Me + + Dont Remember Me +
@@ -2971,6 +3120,8 @@ exports[`Storyshots Forms/Radio ReactHookForm 1`] = ` className="radio mr-2 ml-2" > - Scramble Password + + Scramble Password +
- Disabled wont be submitted + + Disabled wont be submitted +
- Readonly will be submitted + + Readonly will be submitted +
- Ref Radio Button + + Ref Radio Button + ,