App Passwords: Improve accessibility.

- Make form inputs stacked instead of inline.
- Provide a visible label for the app name.
- Add screen reader text to dismiss button.
- Make "Revoke" button label more descriptive.
- Use aria-disabled instead of disabled to avoid focus loss.
- Display password in a readonly input to assist copy and paste.
- Remove large sections of italic text.
- Use `.form-wrap` and `.form-field` to give consistent form styling.
- Improve labeling and placeholder text.

Props alexstine, georgestephanis, afercia, TimothyBlynJacobs.
Fixes #51580.


git-svn-id: https://develop.svn.wordpress.org/trunk@49294 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Timothy Jacobs 2020-10-24 03:32:46 +00:00
parent 99b40eed6a
commit 5e31ccfee2
6 changed files with 93 additions and 33 deletions

View File

@ -17,6 +17,11 @@
$newAppPassButton.click( function( e ) {
e.preventDefault();
if ( $newAppPassButton.prop( 'aria-disabled' ) ) {
return;
}
var name = $newAppPassField.val();
if ( 0 === name.length ) {
@ -25,8 +30,7 @@
}
clearErrors();
$newAppPassField.prop( 'disabled', true );
$newAppPassButton.prop( 'disabled', true );
$newAppPassButton.prop( 'aria-disabled', true ).addClass( 'disabled' );
var request = {
name: name
@ -47,8 +51,7 @@
method: 'POST',
data: request
} ).always( function() {
$newAppPassField.prop( 'disabled', false );
$newAppPassButton.prop( 'disabled', false );
$newAppPassButton.removeProp( 'aria-disabled' ).removeClass( 'disabled' );
} ).done( function( response ) {
$newAppPassField.val( '' );
$newAppPassButton.prop( 'disabled', false );

View File

@ -21,13 +21,16 @@
e.preventDefault();
if ( $approveBtn.prop( 'aria-disabled' ) ) {
return;
}
if ( 0 === name.length ) {
$appNameField.focus();
return;
}
$appNameField.prop( 'disabled', true );
$approveBtn.prop( 'disabled', true );
$approveBtn.prop( 'aria-disabled', true ).addClass( 'disabled' );
var request = {
name: name
@ -82,17 +85,17 @@
message = wp.i18n.sprintf(
wp.i18n.__( 'Your new password for %1$s is: %2$s.' ),
'<strong></strong>',
'<kbd></kbd>'
'<input type="text" class="code" readonly="readonly" value="" />'
);
$notice = $( '<div></div>' )
.attr( 'role', 'alert' )
.attr( 'tabindex', 0 )
.addClass( 'notice notice-success notice-alt' )
.append( $( '<p></p>' ).html( message ) );
.append( $( '<p></p>' ).addClass( 'application-password-display' ).html( message ) );
// We're using .text() to write the variables to avoid any chance of XSS.
$( 'strong', $notice ).text( name );
$( 'kbd', $notice ).text( response.password );
$( 'input', $notice ).val( response.password );
$form.replaceWith( $notice );
$notice.focus();
@ -116,8 +119,7 @@
$( 'h1' ).after( $notice );
$appNameField.prop( 'disabled', false );
$approveBtn.prop( 'disabled', false );
$approveBtn.removeProp( 'aria-disabled', false ).removeClass( 'disabled' );
/**
* Fires when an Authorize Application Password request encountered an error when trying to approve the request.

View File

@ -127,7 +127,7 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
<div class="notice notice-error"><p><?php echo $error->get_error_message(); ?></p></div>
<?php endif; ?>
<div class="card js-auth-app-card">
<div class="card auth-app-card">
<h2 class="title"><?php __( 'An application would like to connect to your account.' ); ?></h2>
<?php if ( $app_name ) : ?>
<p>
@ -170,13 +170,13 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
<?php if ( $new_password ) : ?>
<div class="notice notice-success notice-alt below-h2">
<p class="password-display">
<p class="application-password-display">
<?php
printf(
/* translators: 1: Application name, 2: Generated password. */
__( 'Your new password for %1$s is %2$s.' ),
'<strong>' . esc_html( $app_name ) . '</strong>',
'<kbd>' . esc_html( WP_Application_Passwords::chunk_password( $new_password ) ) . '</kbd>'
sprintf( '<input type="text" class="code" readonly="readonly" value="%s" />', esc_attr( WP_Application_Passwords::chunk_password( $new_password ) ) )
);
?>
</p>
@ -195,15 +195,17 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
do_action( 'wp_authorize_application_password_form', $request, $user );
?>
<?php else : ?>
<form action="<?php echo esc_url( admin_url( 'authorize-application.php' ) ); ?>" method="post">
<form action="<?php echo esc_url( admin_url( 'authorize-application.php' ) ); ?>" method="post" class="form-wrap">
<?php wp_nonce_field( 'authorize_application_password' ); ?>
<input type="hidden" name="action" value="authorize_application_password" />
<input type="hidden" name="app_id" value="<?php echo esc_attr( $app_id ); ?>" />
<input type="hidden" name="success_url" value="<?php echo esc_url( $success_url ); ?>" />
<input type="hidden" name="reject_url" value="<?php echo esc_url( $reject_url ); ?>" />
<label for="app_name"><?php esc_html_e( 'Application Title:' ); ?></label>
<input type="text" id="app_name" name="app_name" value="<?php echo esc_attr( $app_name ); ?>" placeholder="<?php esc_attr_e( 'Name this connection&hellip;' ); ?>" required />
<div class="form-field">
<label for="app_name"><?php _e( 'New Application Password Name' ); ?></label>
<input type="text" id="app_name" name="app_name" value="<?php echo esc_attr( $app_name ); ?>" placeholder="<?php esc_attr_e( 'WordPress App on My Phone' ); ?>" required aria-required="true" />
</div>
<?php
/**
@ -223,8 +225,18 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
do_action( 'wp_authorize_application_password_form', $request, $user );
?>
<p><?php submit_button( __( 'Yes, I approve of this connection.' ), 'primary', 'approve', false ); ?>
<br /><em>
<?php
submit_button(
__( 'Yes, I approve of this connection.' ),
'primary',
'approve',
false,
array(
'aria-describedby' => 'description-approve',
)
);
?>
<p class="description" id="description-approve">
<?php
if ( $success_url ) {
printf(
@ -245,11 +257,20 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
_e( 'You will be given a password to manually enter into the application in question.' );
}
?>
</em>
</p>
<p><?php submit_button( __( 'No, I do not approve of this connection.' ), 'secondary', 'reject', false ); ?>
<br /><em>
<?php
submit_button(
__( 'No, I do not approve of this connection.' ),
'secondary',
'reject',
false,
array(
'aria-describedby' => 'description-reject',
)
);
?>
<p class="description" id="description-reject">
<?php
if ( $reject_url ) {
printf(
@ -261,7 +282,6 @@ require_once ABSPATH . 'wp-admin/admin-header.php';
_e( 'You will be returned to the WordPress Dashboard, and no changes will be made.' );
}
?>
</em>
</p>
</form>
<?php endif; ?>

View File

@ -851,11 +851,28 @@ table.form-table td .updated p {
cursor: pointer;
}
.create-application-password .form-field {
max-width: 25em;
}
.create-application-password label {
font-weight: 600;
}
.create-application-password p.submit {
margin-bottom: 0;
padding-bottom: 0;
}
.new-application-password-notice.notice {
margin-top: 20px;
margin-bottom: 0;
}
.application-password-display input.code {
width: 19em;
}
/*------------------------------------------------------------------------------
19.0 - Tools
------------------------------------------------------------------------------*/

View File

@ -104,9 +104,20 @@ class WP_Application_Passwords_List_Table extends WP_List_Table {
* Handles the revoke column output.
*
* @since 5.6.0
*
* @param array $item The current application password item.
*/
public function column_revoke() {
submit_button( __( 'Revoke' ), 'delete', 'revoke-application-password', false );
public function column_revoke( $item ) {
submit_button(
__( 'Revoke' ),
'delete',
'revoke-application-password-' . $item['uuid'],
false,
array(
/* translators: %s: the application password's given name. */
'title' => sprintf( __( 'Revoke "%s"' ), $item['name'] ),
)
);
}
/**
@ -220,7 +231,12 @@ class WP_Application_Passwords_List_Table extends WP_List_Table {
echo "{{ data.last_ip || '—' }}";
break;
case 'revoke':
echo $this->column_revoke();
printf(
'<input type="submit" class="button delete" value="%1$s" title="%2$s">',
esc_attr( __( 'Revoke' ) ),
/* translators: %s: the application password's given name. */
esc_attr( sprintf( __( 'Revoke "%s"' ), '{{ data.name }}' ) )
);
break;
default:
/**

View File

@ -739,9 +739,11 @@ endif;
}
}
?>
<div class="create-application-password">
<label for="new_application_password_name" class="screen-reader-text"><?php _e( 'New Application Password Name' ); ?></label>
<input type="text" size="30" id="new_application_password_name" name="new_application_password_name" placeholder="<?php esc_attr_e( 'New Application Password Name' ); ?>" class="input" />
<div class="create-application-password form-wrap">
<div class="form-field">
<label for="new_application_password_name"><?php _e( 'New Application Password Name' ); ?></label>
<input type="text" size="30" id="new_application_password_name" name="new_application_password_name" placeholder="<?php esc_attr_e( 'WordPress App on My Phone' ); ?>" class="input" />
</div>
<?php
/**
@ -754,7 +756,7 @@ endif;
do_action( 'wp_create_application_password_form', $profileuser );
?>
<?php submit_button( __( 'Add New' ), 'secondary', 'do_new_application_password', false ); ?>
<?php submit_button( __( 'Add New' ), 'secondary', 'do_new_application_password' ); ?>
</div>
<div class="application-passwords-list-table-wrapper">
@ -856,19 +858,19 @@ endif;
<?php if ( isset( $application_passwords_list_table ) ) : ?>
<script type="text/html" id="tmpl-new-application-password">
<div class="notice notice-success is-dismissible new-application-password-notice" role="alert" tabindex="0">
<p>
<p class="application-password-display">
<?php
printf(
/* translators: 1: Application name, 2: Generated password. */
esc_html__( 'Your new password for %1$s is: %2$s' ),
'<strong>{{ data.name }}</strong>',
'<kbd>{{ data.password }}</kbd>'
'<input type="text" class="code" readonly="readonly" value="{{ data.password }}" />'
);
?>
</p>
<p><?php esc_attr_e( 'Be sure to save this in a safe location. You will not be able to retrieve it.' ); ?></p>
<button type="button" class="notice-dismiss">
<span class="screen-reader-text"><?php __( 'Dismiss this notice.' ); ?></span>
<span class="screen-reader-text"><?php _e( 'Dismiss this notice.' ); ?></span>
</button>
</div>
</script>