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:
parent
82c7d5342c
commit
e308e2ffac
|
@ -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" />
|
||||
{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">
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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> {this.props.group.group_category.name}</small>
|
||||
</h3>
|
||||
{arrow}
|
||||
{visitLink} {manageLink}
|
||||
</div>
|
||||
<span className="student-group-join"> {membershipAction}</span>
|
||||
<span className="student-group-students">{leaderBadge}{I18n.t('student', {count: this.props.group.users.length})}</span>
|
||||
<span className="student-group-join"> {membershipAction}</span>
|
||||
</div>
|
||||
{body}
|
||||
</div>);
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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" >
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue