From 557172a75d0a30bad6826e13adb861fb61b01ed9 Mon Sep 17 00:00:00 2001 From: Eugene Lim Date: Wed, 3 Jan 2018 18:24:43 +0800 Subject: [PATCH 01/12] Make ModulesSelect remains open after selection --- www/src/js/views/timetable/ModulesSelect.jsx | 22 +++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/www/src/js/views/timetable/ModulesSelect.jsx b/www/src/js/views/timetable/ModulesSelect.jsx index fa4222af55..19bcca0a32 100644 --- a/www/src/js/views/timetable/ModulesSelect.jsx +++ b/www/src/js/views/timetable/ModulesSelect.jsx @@ -1,6 +1,5 @@ // @flow import React, { Component } from 'react'; -import _ from 'lodash'; import Downshift from 'downshift'; import classnames from 'classnames'; @@ -24,6 +23,7 @@ type Props = { type State = { isModalOpen: boolean, isOpen: boolean, + inputValue: string, }; const RESULTS_LIMIT = 500; @@ -33,12 +33,25 @@ class ModulesSelect extends Component { state = { isOpen: false, isModalOpen: false, + inputValue: '', }; onChange = (selection: any) => { // Refocus after choosing a module if (this.input) this.input.focus(); - if (selection) this.props.onChange(selection); + if (selection) { + this.props.onChange(selection); + } + }; + + onStateChange = (changes: any) => { + if (Object.prototype.hasOwnProperty.call(changes, 'inputValue')) { + if (!Object.prototype.hasOwnProperty.call(changes, 'selectedItem')) { + this.setState({ + inputValue: changes.inputValue, + }); + } + } }; onFocus = () => this.setState({ isOpen: true }); @@ -152,9 +165,8 @@ class ModulesSelect extends Component { onChange={this.onChange} render={this.renderDropdown} defaultHighlightedIndex={0} - /* Hack to force item selection to be empty */ - itemToString={_.stubString} - selectedItem="" + inputValue={this.state.inputValue} + onStateChange={this.onStateChange} /> ); return matchBreakpoint ? ( From 4e95facb9bbd2cbf7bdb7d17a9e8a245716d43dc Mon Sep 17 00:00:00 2001 From: Eugene Lim Date: Wed, 3 Jan 2018 23:02:27 +0800 Subject: [PATCH 02/12] Fix cursor jumping and refactor ModulesSelect - Fix cursor jumping to the end of the input - Refactor `ModulesSelect` --- www/src/js/views/timetable/ModulesSelect.jsx | 106 +++++++++++-------- 1 file changed, 61 insertions(+), 45 deletions(-) diff --git a/www/src/js/views/timetable/ModulesSelect.jsx b/www/src/js/views/timetable/ModulesSelect.jsx index 19bcca0a32..273ee6f287 100644 --- a/www/src/js/views/timetable/ModulesSelect.jsx +++ b/www/src/js/views/timetable/ModulesSelect.jsx @@ -1,5 +1,6 @@ // @flow import React, { Component } from 'react'; +import _ from 'lodash'; import Downshift from 'downshift'; import classnames from 'classnames'; @@ -21,51 +22,63 @@ type Props = { }; type State = { - isModalOpen: boolean, isOpen: boolean, + isModalOpen: boolean, inputValue: string, + selectedItem: any, }; const RESULTS_LIMIT = 500; class ModulesSelect extends Component { input: ?HTMLInputElement; + state = { isOpen: false, - isModalOpen: false, + isModalOpen: true, inputValue: '', + selectedItem: null, }; - onChange = (selection: any) => { - // Refocus after choosing a module - if (this.input) this.input.focus(); - if (selection) { - this.props.onChange(selection); + onStateChange = (changes: any) => { + if (_.has(changes, 'selectedItem')) { + this.props.onChange(changes.selectedItem); } }; - onStateChange = (changes: any) => { - if (Object.prototype.hasOwnProperty.call(changes, 'inputValue')) { - if (!Object.prototype.hasOwnProperty.call(changes, 'selectedItem')) { - this.setState({ - inputValue: changes.inputValue, - }); - } + onBlur = () => { + if (!this.state.inputValue && this.state.isModalOpen) { + this.closeSelect(); } }; - onFocus = () => this.setState({ isOpen: true }); - onOuterClick = () => this.setState({ isOpen: false }); - toggleModal = () => this.setState({ isModalOpen: !this.state.isModalOpen }); + onInputChange = (event) => { + this.setState({ inputValue: event.target.value }); + }; - getFilteredModules = (inputValue: string) => { - if (!inputValue) { - return []; - } + onFocus = () => this.openSelect(); + onOuterClick = () => this.closeSelect(); + + closeSelect = () => { + this.setState({ + isOpen: false, + isModalOpen: false, + inputValue: '', + selectedItem: null, + }); + }; + + openSelect = () => { + this.setState({ + isOpen: true, + isModalOpen: true, + }); + }; + getFilteredModules = (inputValue: string) => { + if (!inputValue) return []; const predicate = createSearchPredicate(inputValue); const results = this.props.moduleList.filter(predicate); - return sortModules(inputValue, results.slice(0, RESULTS_LIMIT)); }; @@ -93,20 +106,20 @@ class ModulesSelect extends Component { {placeholder} { - this.input = input; - }} - autoFocus={isModalOpen} - className={styles.input} - {...getInputProps({ placeholder })} - disabled={disabled} - onFocus={this.onFocus} - /* Also prevents iOS "Done" button from resetting input */ - onBlur={() => { - if (!inputValue && isModalOpen) this.toggleModal(); - }} + {...getInputProps({ + ref: (input) => { + this.input = input; + }, + className: styles.input, + autoFocus: isModalOpen, + placeholder, + disabled, + onFocus: this.onFocus, + onBlur: this.onBlur, + onChange: this.onInputChange, + })} /> - {isModalOpen && } + {isModalOpen && } {showResults && (
    {results.map( @@ -156,29 +169,32 @@ class ModulesSelect extends Component { }; render() { - const { isModalOpen, isOpen } = this.state; + const { isModalOpen } = this.state; const { matchBreakpoint, disabled } = this.props; + const downshiftComponent = ( ); - return matchBreakpoint ? ( - downshiftComponent - ) : ( + + if (matchBreakpoint) { + return downshiftComponent; + } + + return (
    - {downshiftComponent} From 7b04ffd80a22fc4dc9a1d9718dc44c6c2d0ab821 Mon Sep 17 00:00:00 2001 From: Eugene Lim Date: Sun, 7 Jan 2018 21:04:31 +0800 Subject: [PATCH 03/12] Fix modal opening by default --- www/src/js/views/timetable/ModulesSelect.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/src/js/views/timetable/ModulesSelect.jsx b/www/src/js/views/timetable/ModulesSelect.jsx index 273ee6f287..c22bde8068 100644 --- a/www/src/js/views/timetable/ModulesSelect.jsx +++ b/www/src/js/views/timetable/ModulesSelect.jsx @@ -35,7 +35,7 @@ class ModulesSelect extends Component { state = { isOpen: false, - isModalOpen: true, + isModalOpen: false, inputValue: '', selectedItem: null, }; From 8897161c257b0374646be10e5eedd965d04866df Mon Sep 17 00:00:00 2001 From: Eugene Lim Date: Sun, 7 Jan 2018 22:30:06 +0800 Subject: [PATCH 04/12] Change selectedItem type to ModuleCode --- www/src/js/views/timetable/ModulesSelect.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/www/src/js/views/timetable/ModulesSelect.jsx b/www/src/js/views/timetable/ModulesSelect.jsx index c22bde8068..3e8e262d9e 100644 --- a/www/src/js/views/timetable/ModulesSelect.jsx +++ b/www/src/js/views/timetable/ModulesSelect.jsx @@ -5,6 +5,7 @@ import Downshift from 'downshift'; import classnames from 'classnames'; import type { ModuleSelectList } from 'types/reducers'; +import type { ModuleCode } from 'types/modules'; import { createSearchPredicate, sortModules } from 'utils/moduleSearch'; import { breakpointUp } from 'utils/css'; import makeResponsive from 'views/hocs/makeResponsive'; @@ -25,7 +26,7 @@ type State = { isOpen: boolean, isModalOpen: boolean, inputValue: string, - selectedItem: any, + selectedItem: ?ModuleCode, }; const RESULTS_LIMIT = 500; From 285e8352856d1b091bd38e0f1aefcb47aca6e4c4 Mon Sep 17 00:00:00 2001 From: Zhang Yi Jiang Date: Mon, 8 Jan 2018 14:24:26 +0800 Subject: [PATCH 05/12] Remove unused ref and move Close button into Modal --- www/src/js/views/timetable/ModulesSelect.jsx | 7 +------ www/src/js/views/timetable/ModulesSelect.scss | 5 +++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/www/src/js/views/timetable/ModulesSelect.jsx b/www/src/js/views/timetable/ModulesSelect.jsx index 3e8e262d9e..6c706b751c 100644 --- a/www/src/js/views/timetable/ModulesSelect.jsx +++ b/www/src/js/views/timetable/ModulesSelect.jsx @@ -32,8 +32,6 @@ type State = { const RESULTS_LIMIT = 500; class ModulesSelect extends Component { - input: ?HTMLInputElement; - state = { isOpen: false, isModalOpen: false, @@ -108,9 +106,6 @@ class ModulesSelect extends Component { { - this.input = input; - }, className: styles.input, autoFocus: isModalOpen, placeholder, @@ -120,7 +115,6 @@ class ModulesSelect extends Component { onChange: this.onInputChange, })} /> - {isModalOpen && } {showResults && (
      {results.map( @@ -198,6 +192,7 @@ class ModulesSelect extends Component { onRequestClose={this.closeSelect} className={styles.modal} > + {downshiftComponent}
    diff --git a/www/src/js/views/timetable/ModulesSelect.scss b/www/src/js/views/timetable/ModulesSelect.scss index 66793cdd21..bd9c3fb98d 100644 --- a/www/src/js/views/timetable/ModulesSelect.scss +++ b/www/src/js/views/timetable/ModulesSelect.scss @@ -31,8 +31,9 @@ $module-list-height: 13.5rem; .close { position: absolute; - top: 0; - right: 0; + top: 1rem; + right: 0.5rem; + z-index: 1; width: $input-height; height: $input-height; } From 619b2d4f3cf80d692b56851f7f3381ef947b7b88 Mon Sep 17 00:00:00 2001 From: Eugene Lim Date: Mon, 8 Jan 2018 15:50:57 +0800 Subject: [PATCH 06/12] Fix incorrect isModalOpen state --- www/src/js/views/timetable/ModulesSelect.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/src/js/views/timetable/ModulesSelect.jsx b/www/src/js/views/timetable/ModulesSelect.jsx index 6c706b751c..ac7ae720b0 100644 --- a/www/src/js/views/timetable/ModulesSelect.jsx +++ b/www/src/js/views/timetable/ModulesSelect.jsx @@ -70,7 +70,7 @@ class ModulesSelect extends Component { openSelect = () => { this.setState({ isOpen: true, - isModalOpen: true, + isModalOpen: !this.props.matchBreakpoint, }); }; @@ -96,7 +96,7 @@ class ModulesSelect extends Component { const { isModalOpen } = this.state; const results = this.getFilteredModules(inputValue); const showResults = isOpen && results.length > 0; - const showTip = isModalOpen && !results.length; + const showTip = isOpen && !results.length; const showNoResultMessage = isOpen && inputValue && !results.length; return ( From a26006f274ed899e5e8c20ff27f983088c5bd4ef Mon Sep 17 00:00:00 2001 From: Eugene Lim Date: Mon, 8 Jan 2018 16:02:19 +0800 Subject: [PATCH 07/12] Fix ModulesSelect not close after Escape is pressed --- www/src/js/views/timetable/ModulesSelect.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/www/src/js/views/timetable/ModulesSelect.jsx b/www/src/js/views/timetable/ModulesSelect.jsx index ac7ae720b0..993804edda 100644 --- a/www/src/js/views/timetable/ModulesSelect.jsx +++ b/www/src/js/views/timetable/ModulesSelect.jsx @@ -58,6 +58,13 @@ class ModulesSelect extends Component { onFocus = () => this.openSelect(); onOuterClick = () => this.closeSelect(); + onKeyDown = (event) => { + if (event.key === 'Escape') { + this.closeSelect(); + event.target.blur(); + } + }; + closeSelect = () => { this.setState({ isOpen: false, @@ -113,6 +120,7 @@ class ModulesSelect extends Component { onFocus: this.onFocus, onBlur: this.onBlur, onChange: this.onInputChange, + onKeyDown: this.onKeyDown, })} /> {showResults && ( From 1f17e990a72c6fc4f566a32da936c6e9112429a4 Mon Sep 17 00:00:00 2001 From: Eugene Lim Date: Mon, 8 Jan 2018 16:10:51 +0800 Subject: [PATCH 08/12] Fix position of close button --- www/src/js/views/timetable/ModulesSelect.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/src/js/views/timetable/ModulesSelect.scss b/www/src/js/views/timetable/ModulesSelect.scss index bd9c3fb98d..7f8376f8ce 100644 --- a/www/src/js/views/timetable/ModulesSelect.scss +++ b/www/src/js/views/timetable/ModulesSelect.scss @@ -31,8 +31,8 @@ $module-list-height: 13.5rem; .close { position: absolute; - top: 1rem; - right: 0.5rem; + top: 0; + right: 0; z-index: 1; width: $input-height; height: $input-height; From 30a8d4bc3e81443ca6062a19a639ffd87b5cf989 Mon Sep 17 00:00:00 2001 From: Eugene Lim Date: Mon, 8 Jan 2018 19:48:49 +0800 Subject: [PATCH 09/12] Set default highlighted index --- www/src/js/views/timetable/ModulesSelect.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/www/src/js/views/timetable/ModulesSelect.jsx b/www/src/js/views/timetable/ModulesSelect.jsx index 993804edda..3abdfe665e 100644 --- a/www/src/js/views/timetable/ModulesSelect.jsx +++ b/www/src/js/views/timetable/ModulesSelect.jsx @@ -183,6 +183,7 @@ class ModulesSelect extends Component { inputValue={this.state.inputValue} isOpen={this.state.isOpen} selectedItem={this.state.selectedItem} + defaultHighlightedIndex={0} /> ); From 19487289f0ea7ee16bd2f6adc3c4e3d82a7facd1 Mon Sep 17 00:00:00 2001 From: Zhang Yi Jiang Date: Tue, 9 Jan 2018 12:37:35 +0800 Subject: [PATCH 10/12] Minor tweaks --- www/src/js/views/timetable/ModulesSelect.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/www/src/js/views/timetable/ModulesSelect.jsx b/www/src/js/views/timetable/ModulesSelect.jsx index 3abdfe665e..d9cb0c2b90 100644 --- a/www/src/js/views/timetable/ModulesSelect.jsx +++ b/www/src/js/views/timetable/ModulesSelect.jsx @@ -1,6 +1,6 @@ // @flow import React, { Component } from 'react'; -import _ from 'lodash'; +import { has } from 'lodash'; import Downshift from 'downshift'; import classnames from 'classnames'; @@ -40,7 +40,7 @@ class ModulesSelect extends Component { }; onStateChange = (changes: any) => { - if (_.has(changes, 'selectedItem')) { + if (has(changes, 'selectedItem')) { this.props.onChange(changes.selectedItem); } }; @@ -103,7 +103,7 @@ class ModulesSelect extends Component { const { isModalOpen } = this.state; const results = this.getFilteredModules(inputValue); const showResults = isOpen && results.length > 0; - const showTip = isOpen && !results.length; + const showTip = isModalOpen && !results.length; const showNoResultMessage = isOpen && inputValue && !results.length; return ( @@ -134,7 +134,7 @@ class ModulesSelect extends Component { [styles.optionSelected]: highlightedIndex === index, })} > - {`${module.ModuleCode} ${module.ModuleTitle}`} + {module.ModuleCode} {module.ModuleTitle}
    Added
    @@ -150,7 +150,7 @@ class ModulesSelect extends Component { [styles.optionSelected]: highlightedIndex === index, })} > - {`${module.ModuleCode} ${module.ModuleTitle}`} + {module.ModuleCode} {module.ModuleTitle} ), )} From 9691dc0645e158070866fc4c5d3da8239bd34310 Mon Sep 17 00:00:00 2001 From: Zhang Yi Jiang Date: Tue, 9 Jan 2018 14:14:15 +0800 Subject: [PATCH 11/12] Revert template update and add comment on iOS bug --- www/src/js/views/timetable/ModulesSelect.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/www/src/js/views/timetable/ModulesSelect.jsx b/www/src/js/views/timetable/ModulesSelect.jsx index d9cb0c2b90..0b2b59aee7 100644 --- a/www/src/js/views/timetable/ModulesSelect.jsx +++ b/www/src/js/views/timetable/ModulesSelect.jsx @@ -134,7 +134,9 @@ class ModulesSelect extends Component { [styles.optionSelected]: highlightedIndex === index, })} > - {module.ModuleCode} {module.ModuleTitle} + {/* Using interpolated string instead of JSX because of iOS Safari + bug that drops the whitespace between the module code and title */} + {`${module.ModuleCode} ${module.ModuleTitle}`}
    Added
    @@ -150,7 +152,9 @@ class ModulesSelect extends Component { [styles.optionSelected]: highlightedIndex === index, })} > - {module.ModuleCode} {module.ModuleTitle} + {/* Using interpolated string instead of JSX because of iOS Safari + bug that drops the whitespace between the module code and title */} + {`${module.ModuleCode} ${module.ModuleTitle}`} ), )} From 058995cb0d7f2d47ac4d6deb812a8310f89cd422 Mon Sep 17 00:00:00 2001 From: Zhang Yi Jiang Date: Tue, 9 Jan 2018 18:00:32 +0800 Subject: [PATCH 12/12] Add background color and replace styles with list-unstyled from BS --- www/src/js/views/timetable/ModulesSelect.scss | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/www/src/js/views/timetable/ModulesSelect.scss b/www/src/js/views/timetable/ModulesSelect.scss index 7f8376f8ce..3afda1e306 100644 --- a/www/src/js/views/timetable/ModulesSelect.scss +++ b/www/src/js/views/timetable/ModulesSelect.scss @@ -54,11 +54,10 @@ div > .modal { } .selectList { - composes: scrollable-y from global; + composes: scrollable-y list-unstyled from global; max-height: calc(100% - #{$input-height}); - padding: 0; - margin: 0; - list-style: none; + // Background color so that elements behind this won't peek through for iOS overscroll + background: var(--body-bg); } @include media-breakpoint-up(md) {