Compare commits

...

5 Commits

Author SHA1 Message Date
Matt Hughes
43266546e3 tighten animation to avoid inconsistent motion b/t drawer & page 2019-09-04 08:58:48 -04:00
Matt Hughes
dc5652d336 Add separate state field to hide noninteractible components
this is important for a11y/keyboard interaction
2019-09-04 08:57:36 -04:00
Matt Hughes
f4c40eaffc Add icon to filter drawer-launching button 2019-09-04 08:55:59 -04:00
Matt Hughes
3e405b06db Move drawer off of modal styles and filters into drawer 2019-09-03 15:57:34 -04:00
Matt Hughes
11d9c9eb3e WIP first stab at an animated drawer for GB 2019-08-23 17:25:06 -04:00
2 changed files with 207 additions and 148 deletions

View File

@@ -1,3 +1,5 @@
$drawer-width: 350px;
.spinner-overlay {
position: fixed;
height: 100%;
@@ -17,18 +19,11 @@
color: black;
}
.gradebook-container{
width: 500px;
@media only screen and (min-width: 640px) {
width: 630px;
.gradebook-contents {
transition: transform 300ms cubic-bezier(0.4,0,0.2,1);
.drawer.open + & {
transform: translateX($drawer-width);
}
@media only screen and (min-width: 992px) {
width: 900px;
}
@media only screen and (min-width: 1200px) {
width: 1024px;
}
.search-help-text {
margin-left: 20px;
}
@@ -38,6 +33,32 @@
}
}
.drawer-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
padding: 15px;
}
.drawer-container {
overflow-x: hidden;
.collapsible {
margin-bottom: 1em;
}
}
.drawer {
height: 100%;
width: $drawer-width;
position: absolute;
transform: translateX(-$drawer-width);
flex-direction: column;
transition: transform 300ms cubic-bezier(0.4,0,0.2,1);
&.open {
transform: translateX(0%);
}
}
.student-filters{
display: flex;
.label{

View File

@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {
Button,
Collapsible,
@@ -41,6 +42,8 @@ export default class Gradebook extends React.Component {
assignmentGradeMax: '100',
isMinCourseGradeFilterValid: true,
isMaxCourseGradeFilterValid: true,
drawerOpen: true,
drawerTransitioning: false,
};
this.fileFormRef = React.createRef();
this.fileInputRef = React.createRef();
@@ -471,10 +474,166 @@ export default class Gradebook extends React.Component {
return valueAsInt >= 0 && valueAsInt <= 100;
};
closeFilterDrawer = () => {
this.setState({ drawerOpen: false, drawerTransitioning: true });
};
toggleFilterDrawer = () => {
this.setState({ drawerTransitioning: true });
// defer the transition to the next repaint so we can be sure that
// opening drawer is visible before it transitions
// (the start state of the opening animation doesn't work if the element starts hidden)
window.requestAnimationFrame(() =>
this.setState(prevState => ({ drawerOpen: !prevState.drawerOpen })));
};
handleDrawerSlideDone = () => {
this.setState({ drawerTransitioning: false });
};
render() {
// todo: add aria label for close button (is this broken for modal buttons?)
return (
<div className="d-flex justify-content-center">
<div className="gradebook-container">
<div className="d-flex drawer-container">
<aside
className={classNames(
'drawer',
{
open: this.state.drawerOpen,
'd-none': !this.state.drawerTransitioning && !this.state.drawerOpen,
},
)}
onTransitionEnd={this.handleDrawerSlideDone}
>
<div className="drawer-header">
<h2><Icon className="fa fa-filter" />Filter By...</h2>
<Button
className="p-1"
onClick={this.closeFilterDrawer}
inputRef={this.setFirstFocusableElement}
onKeyDown={this.handleKeyDown}
>
<Icon className="fa fa-times" />
</Button>
</div>
<Collapsible title="Assignments" isOpen className="filter-group">
<div>
<div className="student-filters">
<span className="label">
Assignment Types:
</span>
<InputSelect
name="assignment-types"
aria-label="Assignment Types"
value={this.props.selectedAssignmentType}
options={this.mapAssignmentTypeEntries(this.props.assignmentTypes)}
onChange={this.updateAssignmentTypes}
disabled={this.props.assignmentFilterOptions.length === 0}
/>
</div>
<div className="student-filters">
<span className="label">
Assignment:
</span>
<InputSelect
name="assignment"
aria-label="Assignment"
value={this.props.selectedAssignment}
options={this.getAssignmentFilterOptions()}
onChange={this.handleAssignmentFilterChange}
disabled={this.props.assignmentFilterOptions.length === 0}
/>
</div>
<p>Grade Range (0% - 100%)</p>
<form className="d-flex justify-content-between align-items-center" onSubmit={this.handleSubmitAssignmentGrade}>
<InputText
label="Min Grade"
name="assignmentGradeMin"
type="number"
min={0}
max={100}
step={1}
value={this.state.assignmentGradeMin}
disabled={!this.props.selectedAssignment}
onChange={this.handleMinAssigGradeChange}
/>
<span className="input-percent-label">%</span>
<InputText
label="Max Grade"
name="assignmentGradeMax"
type="number"
min={0}
max={100}
step={1}
value={this.state.assignmentGradeMax}
disabled={!this.props.selectedAssignment}
onChange={this.handleMaxAssigGradeChange}
/>
<span className="input-percent-label">%</span>
<div className="d-flex align-items-center">
<Button
type="submit"
className="btn-outline-secondary"
name="assignmentGradeMinMax"
disabled={!this.props.selectedAssignment}
>
Apply
</Button>
</div>
</form>
</div>
</Collapsible>
<Collapsible title="Overall Grade" isOpen className="filter-group">
<div className="d-flex justify-content-between align-items-center">
<InputText
value={this.state.courseGradeMin}
name="minimum-grade"
label="Min Grade"
onChange={value => this.handleCourseGradeFilterChange('min', value)}
type="number"
min={0}
max={100}
/>
<span className="input-percent-label">%</span>
<InputText
value={this.state.courseGradeMax}
name="max-grade"
label="Max Grade"
onChange={value => this.handleCourseGradeFilterChange('max', value)}
type="number"
min={0}
max={100}
/>
<span className="input-percent-label">%</span>
<Button
buttonType="outline-secondary"
className="align-self-center"
onClick={this.handleCourseGradeFilterApplyButtonClick}
>
Apply
</Button>
</div>
</Collapsible>
<Collapsible title="Student Groups" isOpen className="filter-group">
<InputSelect
name="Tracks"
aria-label="Tracks"
disabled={this.props.tracks.length === 0}
value={this.mapSelectedTrackEntry(this.props.selectedTrack)}
options={this.mapTracksEntries(this.props.tracks)}
onChange={this.updateTracks}
/>
<InputSelect
name="Cohorts"
aria-label="Cohorts"
disabled={this.props.cohorts.length === 0}
value={this.mapSelectedCohortEntry(this.props.selectedCohort)}
options={this.mapCohortsEntries(this.props.cohorts)}
onChange={this.updateCohorts}
/>
</Collapsible>
</aside>
<div className="px-3 mw-100 gradebook-contents">
<div>
<a
href={this.lmsInstructorDashboardUrl(this.props.courseId)}
@@ -499,130 +658,7 @@ export default class Gradebook extends React.Component {
<h4>Step 1: Filter the Grade Report</h4>
<div className="d-flex justify-content-between" >
{this.props.showSpinner && <div className="spinner-overlay"><Icon className="fa fa-spinner fa-spin fa-5x color-black" /></div>}
<div>
<Collapsible title="Assignments" isOpen>
<div>
<div className="student-filters">
<span className="label">
Assignment Types:
</span>
<InputSelect
name="assignment-types"
aria-label="Assignment Types"
value={this.props.selectedAssignmentType}
options={this.mapAssignmentTypeEntries(this.props.assignmentTypes)}
onChange={this.updateAssignmentTypes}
disabled={this.props.assignmentFilterOptions.length === 0}
/>
</div>
<div className="student-filters">
<span className="label">
Assignment:
</span>
<InputSelect
name="assignment"
aria-label="Assignment"
value={this.props.selectedAssignment}
options={this.getAssignmentFilterOptions()}
onChange={this.handleAssignmentFilterChange}
disabled={this.props.assignmentFilterOptions.length === 0}
/>
</div>
<p>Grade Range (0% - 100%)</p>
<form className="d-flex justify-content-between align-items-center" onSubmit={this.handleSubmitAssignmentGrade}>
<InputText
label="Min Grade"
name="assignmentGradeMin"
type="number"
min={0}
max={100}
step={1}
value={this.state.assignmentGradeMin}
disabled={!this.props.selectedAssignment}
onChange={this.handleMinAssigGradeChange}
/>
<span className="input-percent-label">%</span>
<InputText
label="Max Grade"
name="assignmentGradeMax"
type="number"
min={0}
max={100}
step={1}
value={this.state.assignmentGradeMax}
disabled={!this.props.selectedAssignment}
onChange={this.handleMaxAssigGradeChange}
/>
<span className="input-percent-label">%</span>
<div className="d-flex align-items-center">
<Button
type="submit"
className="btn-outline-secondary"
name="assignmentGradeMinMax"
disabled={!this.props.selectedAssignment}
>
Apply
</Button>
</div>
</form>
</div>
</Collapsible>
<div className="student-filters">
<span className="label">
Student Groups:
</span>
<InputSelect
name="Tracks"
aria-label="Tracks"
disabled={this.props.tracks.length === 0}
value={this.mapSelectedTrackEntry(this.props.selectedTrack)}
options={this.mapTracksEntries(this.props.tracks)}
onChange={this.updateTracks}
/>
<InputSelect
name="Cohorts"
aria-label="Cohorts"
disabled={this.props.cohorts.length === 0}
value={this.mapSelectedCohortEntry(this.props.selectedCohort)}
options={this.mapCohortsEntries(this.props.cohorts)}
onChange={this.updateCohorts}
/>
</div>
<div>
<span className="label">
Course Grade (0%-100%)
</span>
<div className="d-flex justify-content-between align-items-center">
<InputText
value={this.state.courseGradeMin}
name="minimum-grade"
label="Min Grade"
onChange={value => this.handleCourseGradeFilterChange('min', value)}
type="number"
min={0}
max={100}
/>
<span className="input-percent-label">%</span>
<InputText
value={this.state.courseGradeMax}
name="max-grade"
label="Max Grade"
onChange={value => this.handleCourseGradeFilterChange('max', value)}
type="number"
min={0}
max={100}
/>
<span className="input-percent-label">%</span>
<Button
buttonType="outline-secondary"
className="align-self-center"
onClick={this.handleCourseGradeFilterApplyButtonClick}
>
Apply
</Button>
</div>
</div>
</div>
<Button className="btn-primary align-self-start" onClick={this.toggleFilterDrawer}><Icon className="fa fa-filter" /> Edit Filters</Button>
<div>
<SearchField
onSubmit={value =>
@@ -686,7 +722,7 @@ export default class Gradebook extends React.Component {
{this.props.showBulkManagement && (
<div>
<StatefulButton
buttonType="primary"
buttonType="outline-primary"
onClick={this.handleClickExportGrades}
state={this.props.showSpinner ? 'pending' : 'default'}
labels={{
@@ -700,7 +736,7 @@ export default class Gradebook extends React.Component {
disabledStates={['pending']}
/>
<StatefulButton
buttonType="primary"
buttonType="outline-primary"
onClick={this.handleClickDownloadInterventions}
state={this.props.showSpinner ? 'pending' : 'default'}
className="ml-2"
@@ -717,15 +753,17 @@ export default class Gradebook extends React.Component {
</div>
)}
</div>
<div className="gbook">
<Table
columns={this.formatHeadings()}
data={this.formatter[this.props.format](
this.props.grades,
this.props.areGradesFrozen,
)}
rowHeaderColumnKey="username"
/>
<div className="gradebook-container">
<div className="gbook">
<Table
columns={this.formatHeadings()}
data={this.formatter[this.props.format](
this.props.grades,
this.props.areGradesFrozen,
)}
rowHeaderColumnKey="username"
/>
</div>
</div>
{PageButtons(this.props)}
<Modal