Improve modal accessibility

Implement initial focus for modals. Change all modals including forms to put initial focus on the first input. When Enter is pressed on any input (CTRL + Enter for Textareas), the form is submitted if it is valid.

Co-authored-by: Sebastian Sdorra <sebastian.sdorra@cloudogu.com>
Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
This commit is contained in:
Konstantin Schaper
2022-01-21 14:25:19 +01:00
committed by GitHub
parent d8fcb12402
commit d0cf976a54
37 changed files with 1848 additions and 570 deletions

View File

@@ -21,112 +21,78 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import React, { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import InputField from "./InputField";
type State = {
password: string;
confirmedPassword: string;
passwordValid: boolean;
passwordConfirmationFailed: boolean;
};
type Props = WithTranslation & {
type BaseProps = {
passwordChanged: (p1: string, p2: boolean) => void;
passwordValidator?: (p: string) => boolean;
onReturnPressed?: () => void;
};
class PasswordConfirmation extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
password: "",
confirmedPassword: "",
passwordValid: true,
passwordConfirmationFailed: false,
};
}
type InnerProps = BaseProps & {
innerRef: React.Ref<HTMLInputElement>;
};
componentDidMount() {
this.setState({
password: "",
confirmedPassword: "",
passwordValid: true,
passwordConfirmationFailed: false,
});
}
const PasswordConfirmation: FC<InnerProps> = ({ passwordChanged, passwordValidator, onReturnPressed, innerRef }) => {
const [t] = useTranslation("commons");
const [password, setPassword] = useState("");
const [confirmedPassword, setConfirmedPassword] = useState("");
const [passwordValid, setPasswordValid] = useState(true);
const [passwordConfirmationFailed, setPasswordConfirmationFailed] = useState(false);
const isValid = passwordValid && !passwordConfirmationFailed;
render() {
const { t, onReturnPressed } = this.props;
return (
<div className="columns is-multiline">
<div className="column is-half">
<InputField
label={t("password.newPassword")}
type="password"
onChange={this.handlePasswordChange}
value={this.state.password ? this.state.password : ""}
validationError={!this.state.passwordValid}
errorMessage={t("password.passwordInvalid")}
/>
</div>
<div className="column is-half">
<InputField
label={t("password.confirmPassword")}
type="password"
onChange={this.handlePasswordValidationChange}
value={this.state ? this.state.confirmedPassword : ""}
validationError={this.state.passwordConfirmationFailed}
errorMessage={t("password.passwordConfirmFailed")}
onReturnPressed={onReturnPressed}
/>
</div>
</div>
);
}
useEffect(() => passwordChanged(password, isValid), [password, isValid]);
validatePassword = (password: string) => {
const { passwordValidator } = this.props;
const validatePassword = (newPassword: string) => {
if (passwordValidator) {
return passwordValidator(password);
return passwordValidator(newPassword);
}
return password.length >= 6 && password.length < 32;
return newPassword.length >= 6 && newPassword.length < 32;
};
handlePasswordValidationChange = (confirmedPassword: string) => {
const passwordConfirmed = this.state.password === confirmedPassword;
this.setState(
{
confirmedPassword,
passwordConfirmationFailed: !passwordConfirmed,
},
this.propagateChange
);
const handlePasswordValidationChange = (newConfirmedPassword: string) => {
setConfirmedPassword(newConfirmedPassword);
setPasswordConfirmationFailed(password !== newConfirmedPassword);
};
handlePasswordChange = (password: string) => {
const passwordConfirmationFailed = password !== this.state.confirmedPassword;
this.setState(
{
passwordValid: this.validatePassword(password),
passwordConfirmationFailed,
password: password,
},
this.propagateChange
);
const handlePasswordChange = (newPassword: string) => {
setPasswordConfirmationFailed(newPassword !== confirmedPassword);
setPassword(newPassword);
setPasswordValid(validatePassword(newPassword));
};
isValid = () => {
return this.state.passwordValid && !this.state.passwordConfirmationFailed;
};
return (
<div className="columns is-multiline">
<div className="column is-half">
<InputField
label={t("password.newPassword")}
type="password"
onChange={event => handlePasswordChange(event.target.value)}
value={password}
validationError={!passwordValid}
errorMessage={t("password.passwordInvalid")}
ref={innerRef}
onReturnPressed={onReturnPressed}
/>
</div>
<div className="column is-half">
<InputField
label={t("password.confirmPassword")}
type="password"
onChange={handlePasswordValidationChange}
value={confirmedPassword}
validationError={passwordConfirmationFailed}
errorMessage={t("password.passwordConfirmFailed")}
onReturnPressed={onReturnPressed}
/>
</div>
</div>
);
};
propagateChange = () => {
this.props.passwordChanged(this.state.password, this.isValid());
};
}
export default withTranslation("commons")(PasswordConfirmation);
export default React.forwardRef<HTMLInputElement, BaseProps>((props, ref) => (
<PasswordConfirmation {...props} innerRef={ref} />
));