List of groups in student view is more accessible

fixes CNVS-17365

Test Plan:
* As a teacher create a course and several groups sets with several
  groups in them
* As a student use the keyboard to navigate the groups tab in the People
  section of the course you created
* Ensure that lock icons can be reached and that appropriate tootips are
  provided when the lock receives focus
* Ensure that the expand/collapse arrow icon can be clicked with the
  keyboard and that the list of group members is shown accordingly
* Ensure that when using a screen reader appropriate announcements are
  made for each of the controls as you navigate the list of groups
* Ensure that when you focus the arrow icon that expands the group
  member list it announces whether the list is expanded/collapsed
* Ensure as a student when on the People page that when the Groups
  tab has focus it has a visible outline
* Ensure as student when on the Groups tab of the People page that the
  Everyone tab has a visible outline when it has focus

Change-Id: I9246c5716a424979585d8e4cd4396bab138b093c
Reviewed-on: https://gerrit.instructure.com/47355
Tested-by: Jenkins
Reviewed-by: Colleen Palmer <colleen@instructure.com>
Reviewed-by: Brayden Lopez <blopez@instructure.com>
QA-Review: Derek Hansen <dhansen@instructure.com>
Product-Review: Aaron Cannon <acannon@instructure.com>
This commit is contained in:
Andrew Butterfield 2015-01-13 15:31:04 -07:00
parent 82c7d5342c
commit e308e2ffac
8 changed files with 57 additions and 26 deletions

View File

@ -191,8 +191,8 @@ define([
var newGroupButton = null
if (ENV.STUDENT_CAN_ORGANIZE_GROUPS_FOR_COURSE) {
newGroupButton = (
<button className="btn btn-primary add_group_link" onClick={this.openNewGroupDialog}>
<i className="icon-plus" aria-label={I18n.t('new')} />
<button aria-label={I18n.t('Add new group')} className="btn btn-primary add_group_link" onClick={this.openNewGroupDialog}>
<i className="icon-plus" />
&nbsp;{I18n.t('Group')}
</button>);
}
@ -208,7 +208,7 @@ define([
<a href={`/courses/${ENV.course_id}/users`}>{I18n.t('Everyone')}</a>
</li>
<li className="ui-state-default ui-corner-top ui-tabs-active ui-state-active">
<a href="#">{I18n.t('Groups')}</a>
<a href="#" tabIndex="-1">{I18n.t('Groups')}</a>
</li>
</ul>
<div className="roster-tab tab-panel">

View File

@ -8,10 +8,8 @@ define([
render() {
return (
<div className="form-inline clearfix content-box">
<label htmlFor="search_field" className="screenreader-only">
{I18n.t('As you type in this field, the list of groups will be automatically filtered to only include those whose names match your input.')}
</label>
<input id="search_field" placeholder={I18n.t('Search Groups or People')} type="search" onChange={this.props.onChange}/>
<input id="search_field" placeholder={I18n.t('Search Groups or People')} type="search" onChange={this.props.onChange}
aria-label={I18n.t('As you type in this field, the list of groups will be automatically filtered to only include those whose names match your input.')} />
</div>);
}
});

View File

@ -21,6 +21,27 @@ define([
});
},
handleKeyDown(e) {
switch(e.which)
{
case 13:
this.toggleOpen();
break;
case 32:
e.preventDefault();
break;
}
},
handleKeyUp(e) {
switch(e.which)
{
case 32:
this.toggleOpen();
break;
}
},
_onManage(e) {
e.stopPropagation();
e.preventDefault();
@ -51,6 +72,7 @@ define([
this.props.group.group_category.self_signup === 'restricted');
var isFull = (this.props.group.max_membership != null) && this.props.group.users.length >= this.props.group.max_membership;
var isAllowedToJoin = this.props.group.permissions.join;
var hasUsers = this.props.group.users.length > 0;
var shouldSwitch = this.props.group.group_category.is_member && this.props.group.group_category.role !== 'student_organized';
var visitLink = isMember ? <a href={`/groups/${this.props.group.id}`}
@ -62,14 +84,18 @@ define([
aria-label={I18n.t('Manage group %{group_name}', {group_name: groupName})}
onClick={this._onManage}>Manage</a> : null;
var arrowDown = this.state.open || this.props.group.users.length == 0;
var arrow = <i className={`icon-mini-arrow-${arrowDown ? 'down' : 'right'}`}
aria-hidden="true" />;
var showBody = this.state.open && this.props.group.users.length > 0;
var arrowDown = this.state.open || this.props.group.users.length == 0;
var studentGroupId = "student-group-body-" + this.props.group.id;
var arrow = <i className={`icon-mini-arrow-${arrowDown ? 'down' : 'right'}`}
role="button" onKeyDown={this.handleKeyDown} onKeyUp={this.handleKeyUp}
aria-label={arrowDown ? I18n.t("Collapse list of group members for %{groupName}", {groupName: groupName}) :
I18n.t("Expand list of group members for %{groupName}", {groupName: groupName})}
aria-expanded={showBody} aria-controls={studentGroupId} tabIndex={hasUsers ? "0" : "-1"} />;
var body = (
<div className={`student-group-body${showBody ? '' : ' hidden'}`} aria-expanded={showBody}>
<ul ref="memberList" className="student-group-list clearfix" aria-label={I18n.t('group members')} tabIndex="0" role="region">
<div id={studentGroupId} className={`student-group-body${showBody ? '' : ' hidden'}`}>
<ul ref="memberList" className="student-group-list clearfix" aria-label={I18n.t('group members')} tabIndex="0" role="list">
{this.props.group.users.map(u => {
var isLeader = u.id == leaderId;
var name = u.name || u.display_name;
@ -102,22 +128,24 @@ define([
toolTip = I18n.t('Group is joined by invitation only');
ariaLabel = I18n.t('Group %{group_name} is joined by invitation only', {group_name: groupName});
}
membershipAction = <i className="icon-lock" aria-label={ariaLabel} title={toolTip} data-tooltip="left" tabIndex="0"></i>;
membershipAction = <span id="membership-action" tabIndex="0" title={toolTip} data-tooltip="left" aria-label={ariaLabel}>
<i className="icon-lock"></i>
</span>;
};
return (
<div className={`accordion student-groups content-box${showBody ? ' show-body' : ''}`} onClick={this.toggleOpen}>
<div role="listitem" className={`accordion student-groups content-box${showBody ? ' show-body' : ''}`} onClick={this.toggleOpen}>
<div className="student-group-header clearfix">
{arrow}
<div ref="groupTitle" className="student-group-title">
<h3 aria-expanded={showBody} aria-controls="student-group-body" role="button" aria-label={groupName}>
<h3 aria-label={groupName}>
{this.props.group.name}
<small>&nbsp;{this.props.group.group_category.name}</small>
</h3>
{arrow}
{visitLink}&nbsp;{manageLink}
</div>
<span className="student-group-join">&nbsp;{membershipAction}</span>
<span className="student-group-students">{leaderBadge}{I18n.t('student', {count: this.props.group.users.length})}</span>
<span className="student-group-join">&nbsp;{membershipAction}</span>
</div>
{body}
</div>);

View File

@ -21,7 +21,7 @@ define([
onJoin={() => this.props.onJoin(g)}
onManage={() => this.props.onManage(g)} />);
return (
<div>
<div role="list" aria-label={I18n.t("Groups")}>
{groups}
</div>);
}

View File

@ -39,6 +39,7 @@
padding: 16px 16px 16px 10px
.icon-mini-arrow-right, .icon-mini-arrow-down
float: left
order: 1
.icon-mini-arrow-down
display: none
&.show-body
@ -46,18 +47,21 @@
background-color: #f5f5f5
border-bottom: 1px solid #c1c7cf
cursor: pointer
position: relative
.icon-mini-arrow-right
display: none
.icon-mini-arrow-down
display: block
.student-group-title
float: left
display: inline-flex
h3
margin: 0 7px 0 5px
font-weight: bold
font-size: 14px
float: left
line-height: 18px
order: 2
small
font-size: 12px
@ -68,17 +72,19 @@
position: relative
top: -1px
padding-right: 5px
order: 2
.student-group-students
i
padding-right: 5px
float: right
position: absolute
right: 175px
color: #555
font-weight: normal
.student-group-join
float: right
position: absolute
text-transform: uppercase
font-weight: bold
width: 175px
right: 10px
text-align: right
.student-group-body
padding: 16px 16px 16px 36px

View File

@ -33,7 +33,6 @@
text-decoration: none;
font-weight: bold;
text-shadow: 0 1px 0 rgba(255,255,255,0.5);
&:focus { outline: none; }
}
&.ui-tabs-active { margin-bottom: 0; padding-bottom: 1px;
background: $bgColorContent;
@ -71,4 +70,4 @@
padding-right: 0;
padding-bottom: 0;
}
}
}

View File

@ -1,5 +1,5 @@
<li class="static ui-state-default ui-corner-top ui-tabs-active ui-state-active" role="tab" >
<a href="#" class="ui-tabs-anchor" role="presentation">{{#t 'everyone_tab'}}Everyone{{/t}}</a>
<a href="#" class="ui-tabs-anchor" role="presentation" tabindex="-1">{{#t 'everyone_tab'}}Everyone{{/t}}</a>
</li>
{{#each collection}}
<li role="tab" class="ui-state-default ui-corner-top" >

View File

@ -1 +1 @@
<a href="#tab-{{id}}" class='group-category-tab-link' title="{{name}}">{{name}}</a>
<a href="#tab-{{id}}" class='group-category-tab-link' title="{{name}}" tabindex="0">{{name}}</a>