From 85f3fca5fbbaf8ebdc0d7f5271344899aedb716e Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy Date: Wed, 11 Jan 2017 21:51:52 -0800 Subject: [PATCH 01/24] moved topoptions to navbar drawer --- src/components/Ayah/index.js | 29 +++++---- src/components/ContentDropdown/index.js | 13 ++-- src/components/FontSizeDropdown/index.js | 28 +++++---- src/components/FontSizeDropdown/style.scss | 8 ++- src/components/Home/LastVisit/index.js | 5 +- src/components/InformationToggle/index.js | 46 ++++++-------- src/components/Line/index.js | 25 +++++--- src/components/NightModeToggle/index.js | 33 +++------- src/components/ReadingModeToggle/index.js | 2 +- src/components/ReciterDropdown/index.js | 10 +--- src/components/Sidebar/style.scss | 2 +- src/components/SwitchToggle/index.js | 2 +- src/components/TooltipDropdown/index.js | 70 +++++++++------------- src/components/TopOptions/index.js | 49 ++------------- src/components/VersesDropdown/index.js | 6 +- src/containers/Surah/index.js | 50 ++++++++++++---- src/containers/Surah/style.scss | 13 ++++ src/locale/en.js | 2 +- src/types/segmentType.js | 5 +- 19 files changed, 189 insertions(+), 209 deletions(-) diff --git a/src/components/Ayah/index.js b/src/components/Ayah/index.js index 68885911f..0ce91e113 100644 --- a/src/components/Ayah/index.js +++ b/src/components/Ayah/index.js @@ -99,7 +99,7 @@ export default class Ayah extends Component {

{content.name || content.resource.name}

@@ -148,20 +148,19 @@ export default class Ayah extends Component { } renderText() { - const { ayah, tooltip, currentAyah, isPlaying, audioActions, isSearched} = this.props; - - const text = ayah.words.map(word => { - return( - - ) - }); + const { ayah, tooltip, currentAyah, isPlaying, audioActions, isSearched } = this.props; + + const text = ayah.words.map(word => ( + + )); return (

diff --git a/src/components/ContentDropdown/index.js b/src/components/ContentDropdown/index.js index 2cca52725..4f3aa8f0f 100644 --- a/src/components/ContentDropdown/index.js +++ b/src/components/ContentDropdown/index.js @@ -3,7 +3,6 @@ import React, { Component, PropTypes } from 'react'; import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import MenuItem from 'react-bootstrap/lib/MenuItem'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; -import { optionsType } from 'types'; const style = require('./style.scss'); @@ -450,7 +449,7 @@ export const slugs = [ export default class ContentDropdown extends Component { static propTypes = { onOptionChange: PropTypes.func.isRequired, - options: optionsType.isRequired, + content: PropTypes.arrayOf(PropTypes.number).isRequired, className: PropTypes.string }; @@ -458,10 +457,6 @@ export default class ContentDropdown extends Component { className: 'col-md-3' } - shouldComponentUpdate(nextProps) { - return this.props.options !== nextProps.options; - } - handleRemoveContent = () => { const { onOptionChange } = this.props; @@ -469,7 +464,7 @@ export default class ContentDropdown extends Component { } handleOptionSelected(id) { - const { onOptionChange, options: { content } } = this.props; + const { onOptionChange, content } = this.props; if (content.find(option => option === id)) { onOptionChange({ content: content.filter(option => option !== id) }); @@ -479,7 +474,7 @@ export default class ContentDropdown extends Component { } renderItems(items) { - const { options: { content } } = this.props; + const { content } = this.props; return items.map((slug) => { const checked = content.find(option => option === slug.id); @@ -515,7 +510,7 @@ export default class ContentDropdown extends Component { } render() { - const { className, options: { content } } = this.props; + const { className, content } = this.props; return ( { - const { onOptionChange, options: { fontSize } } = this.props; + const { onOptionChange, fontSize } = this.props; const changeFactor = { translation: 0.5, arabic: 0.5 @@ -94,15 +95,16 @@ export default class FontSizeDropdown extends Component { render() { return ( - - - - - +
+ + + + + +
); } } diff --git a/src/components/FontSizeDropdown/style.scss b/src/components/FontSizeDropdown/style.scss index 298b6cf11..4f19f8725 100644 --- a/src/components/FontSizeDropdown/style.scss +++ b/src/components/FontSizeDropdown/style.scss @@ -1,6 +1,6 @@ @import '../../styles/variables.scss'; -:local .popover{ +.popover{ :global(.popover-title){ font-family: $font-montserrat; text-transform: uppercase; @@ -16,3 +16,9 @@ } } } + +.link{ + position: relative; + display: block; + cursor: pointer; +} diff --git a/src/components/Home/LastVisit/index.js b/src/components/Home/LastVisit/index.js index 55c9ae68e..36b5a074a 100644 --- a/src/components/Home/LastVisit/index.js +++ b/src/components/Home/LastVisit/index.js @@ -1,6 +1,7 @@ import React, { PropTypes } from 'react'; import debug from 'helpers/debug'; import Link from 'react-router/lib/Link'; +import { surahType } from 'types'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; const styles = require('containers/Home/style.scss'); @@ -10,7 +11,7 @@ const LastVisit = (props) => { return (

- + {props.surah.name.simple} ({props.surah.id}:{props.ayah}) @@ -22,7 +23,7 @@ const LastVisit = (props) => { }; LastVisit.propTypes = { - surah: PropTypes.object.isRequired, + surah: surahType.isRequired, ayah: PropTypes.number.isRequired }; diff --git a/src/components/InformationToggle/index.js b/src/components/InformationToggle/index.js index 3ecbd885d..5da9be52e 100644 --- a/src/components/InformationToggle/index.js +++ b/src/components/InformationToggle/index.js @@ -1,33 +1,23 @@ -import React, { Component, PropTypes } from 'react'; +import React, { PropTypes } from 'react'; +import SwitchToggle from 'components/SwitchToggle'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; -export default class InformationToggle extends Component { - static propTypes = { - isShowingSurahInfo: PropTypes.bool.isRequired, - onToggle: PropTypes.func.isRequired - }; +const InformationToggle = ({ isShowingSurahInfo, onToggle }) => ( +
+ + onToggle({ isShowingSurahInfo: !isShowingSurahInfo })} + id="info-toggle" + flat + /> +
+); - toggleInformationMode = (event) => { - const { isShowingSurahInfo } = this.props; +InformationToggle.propTypes = { + isShowingSurahInfo: PropTypes.bool.isRequired, + onToggle: PropTypes.func.isRequired +}; - event.preventDefault(); - - this.props.onToggle({ isShowingSurahInfo: !isShowingSurahInfo }); - }; - - render() { - const { isShowingSurahInfo } = this.props; - - return ( - - - - ); - } -} +export default InformationToggle; diff --git a/src/components/Line/index.js b/src/components/Line/index.js index 372e1fc71..c2600ef7d 100644 --- a/src/components/Line/index.js +++ b/src/components/Line/index.js @@ -11,8 +11,12 @@ export default class Line extends React.Component { line: PropTypes.arrayOf(wordType).isRequired, tooltip: PropTypes.string, currentAyah: PropTypes.string.isRequired, - audioActions: PropTypes.object.isRequired, - currentWord: PropTypes.any, + audioActions: PropTypes.shape({ + pause: PropTypes.func.isRequired, + setAyah: PropTypes.func.isRequired, + play: PropTypes.func.isRequired, + setCurrentWord: PropTypes.func.isRequired, + }), isPlaying: PropTypes.bool }; @@ -29,11 +33,16 @@ export default class Line extends React.Component { renderText() { const { tooltip, currentAyah, audioActions, isPlaying, line } = this.props; - const text = line.map(word => { - return ( - - ) - }); + const text = line.map(word => ( + + )); return ( @@ -52,7 +61,7 @@ export default class Line extends React.Component { return (
-
+
{this.renderText()}
diff --git a/src/components/NightModeToggle/index.js b/src/components/NightModeToggle/index.js index 545ca4366..dd14b442b 100644 --- a/src/components/NightModeToggle/index.js +++ b/src/components/NightModeToggle/index.js @@ -1,16 +1,12 @@ /* global document */ import React, { Component } from 'react'; -import { intlShape, injectIntl } from 'react-intl'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; +import SwitchToggle from 'components/SwitchToggle'; import bindTooltip from 'utils/bindTooltip'; class NightModeToggle extends Component { - static propTypes = { - intl: intlShape.isRequired - }; - state = { isNightMode: false, }; @@ -26,29 +22,18 @@ class NightModeToggle extends Component { } render() { - const { intl } = this.props; - - const title = intl.formatMessage({ - id: this.state.isNightMode ? 'setting.nightMode.dayTip' : 'setting.nightMode.nightTip', - defaultMessage: this.state.isNightMode ? 'Switch to day mode' : 'switch to night mode' - }); - return ( ); } } -export default injectIntl(NightModeToggle); +export default NightModeToggle; diff --git a/src/components/ReadingModeToggle/index.js b/src/components/ReadingModeToggle/index.js index a27db924d..77f44f5f3 100644 --- a/src/components/ReadingModeToggle/index.js +++ b/src/components/ReadingModeToggle/index.js @@ -5,7 +5,7 @@ import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; const ReadingModeToggle = ({ onReadingModeToggle, isToggled }) => (
- :{' '} + ( onOptionChange({ audio: slug.id })} > {slug.name.english} diff --git a/src/components/Sidebar/style.scss b/src/components/Sidebar/style.scss index de965550f..4ee05e3c2 100644 --- a/src/components/Sidebar/style.scss +++ b/src/components/Sidebar/style.scss @@ -8,7 +8,7 @@ $width: 20%; top: 0px; bottom: 0px; background: #fff; - z-index: 9999; + z-index: 10; box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15); background: #fff; diff --git a/src/components/SwitchToggle/index.js b/src/components/SwitchToggle/index.js index 8c6bd9c6d..26451b0e0 100644 --- a/src/components/SwitchToggle/index.js +++ b/src/components/SwitchToggle/index.js @@ -3,7 +3,7 @@ import React, { PropTypes } from 'react'; const styles = require('./style.scss'); const SwitchToggle = ({ id, flat, checked, onToggle }) => ( -
+
{ + handleOptionSelected = (type) => { const { onOptionChange } = this.props; return onOptionChange({ - tooltip: checked ? 'transliteration' : 'translation' + tooltip: type }); } - renderPopup() { - const { options: { tooltip } } = this.props; - const tooltipTitle = ; - - return ( - -
- - - {' '} - - {' '} - - -
-
- ); + renderList() { + const { tooltip } = this.props; + + return ['translation', 'transliteration'].map(type => ( + this.handleOptionSelected(type)} + active={tooltip === type} + key={type} + > + {' '} + + + )); } render() { + const { className } = this.props; + return ( - - - - - + } + > + {this.renderList()} + ); } } diff --git a/src/components/TopOptions/index.js b/src/components/TopOptions/index.js index 0e7ca3fc6..d8602431f 100644 --- a/src/components/TopOptions/index.js +++ b/src/components/TopOptions/index.js @@ -1,53 +1,17 @@ -import React, { PropTypes } from 'react'; +import React from 'react'; import Col from 'react-bootstrap/lib/Col'; -import InformationToggle from 'components/InformationToggle'; -import FontSizeDropdown from 'components/FontSizeDropdown'; -import TooltipDropdown from 'components/TooltipDropdown'; -import ReadingModeToggle from 'components/ReadingModeToggle'; -import NightModeToggle from 'components/NightModeToggle'; import Title from 'containers/Surah/Title'; import Share from 'components/Share'; -import { surahType, optionsType } from 'types'; +import { surahType } from 'types'; -const TopOptions = ({ options, surah, actions }) => ( +const TopOptions = ({ surah }) => (
</Col> <Col md={8} className="text-right"> <ul className="list-inline"> - <li> - <InformationToggle - onToggle={actions.setOption} - isShowingSurahInfo={options.isShowingSurahInfo} - /> - </li> - <li>|</li> - <li> - <FontSizeDropdown - options={options} - onOptionChange={actions.setOption} - /> - </li> - <li>|</li> - <li> - <TooltipDropdown - options={options} - onOptionChange={actions.setOption} - /> - </li> - <li>|</li> - <li> - <ReadingModeToggle - isToggled={options.isReadingMode} - onReadingModeToggle={actions.toggleReadingMode} - /> - </li> - <li>|</li> - <li> - <NightModeToggle /> - </li> <li><Share surah={surah} /></li> </ul> </Col> @@ -55,12 +19,7 @@ const TopOptions = ({ options, surah, actions }) => ( ); TopOptions.propTypes = { - options: optionsType.isRequired, - surah: surahType.isRequired, - actions: PropTypes.shape({ - toggleReadingMode: PropTypes.func.isRequired, - setOption: PropTypes.func.isRequired, - }).isRequired + surah: surahType.isRequired }; export default TopOptions; diff --git a/src/components/VersesDropdown/index.js b/src/components/VersesDropdown/index.js index e6a0c8cb7..4f77bc5b2 100644 --- a/src/components/VersesDropdown/index.js +++ b/src/components/VersesDropdown/index.js @@ -63,7 +63,11 @@ export default class VersesDropdown extends Component { ); return ( - <DropdownButton className={`dropdown ${className} ${style.dropdown}`} title={title}> + <DropdownButton + className={`dropdown ${className} ${style.dropdown}`} + title={title} + id="verses-dropdown" + > {this.renderMenu()} </DropdownButton> ); diff --git a/src/containers/Surah/index.js b/src/containers/Surah/index.js index b774e3c8c..719acad32 100644 --- a/src/containers/Surah/index.js +++ b/src/containers/Surah/index.js @@ -29,6 +29,11 @@ import Line from 'components/Line'; import SearchInput from 'components/SearchInput'; import Bismillah from 'components/Bismillah'; import TopOptions from 'components/TopOptions'; +import ReadingModeToggle from 'components/ReadingModeToggle'; +import NightModeToggle from 'components/NightModeToggle'; +import TooltipDropdown from 'components/TooltipDropdown'; +import FontSizeDropdown from 'components/FontSizeDropdown'; +import InformationToggle from 'components/InformationToggle'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; @@ -347,13 +352,12 @@ class Surah extends Component { audioActions={actions.audio} isPlaying={isPlaying} /> - ) - + ); }); } renderSidebar() { - const { surah, surahs, ayahIds, options } = this.props; + const { surah, surahs, ayahIds, options, actions } = this.props; return ( <div> @@ -369,7 +373,7 @@ class Surah extends Component { /> <SurahsDropdown surahs={surahs} - className={`${style.dropdown}`} + className={style.dropdown} /> <VersesDropdown ayat={surah.ayat} @@ -377,18 +381,44 @@ class Surah extends Component { isReadingMode={options.isReadingMode} onClick={this.handleVerseDropdownClick} surah={surah} - className={`${style.dropdown}`} + className={style.dropdown} /> <ReciterDropdown onOptionChange={this.handleOptionChange} - options={options} - className={`${style.dropdown}`} + audio={options.audio} + className={style.dropdown} /> <ContentDropdown onOptionChange={this.handleOptionChange} - options={options} - className={`${style.dropdown}`} + content={options.content} + className={style.dropdown} + /> + <TooltipDropdown + tooltip={options.tooltip} + onOptionChange={actions.options.setOption} + className={style.dropdown} /> + <div className={style.sidebarItem}> + <FontSizeDropdown + fontSize={options.fontSize} + onOptionChange={actions.options.setOption} + /> + </div> + <div className={style.sidebarItem}> + <InformationToggle + onToggle={actions.options.setOption} + isShowingSurahInfo={options.isShowingSurahInfo} + /> + </div> + <div className={style.sidebarItem}> + <ReadingModeToggle + isToggled={options.isReadingMode} + onReadingModeToggle={actions.options.toggleReadingMode} + /> + </div> + <div className={style.sidebarItem}> + <NightModeToggle /> + </div> </div> ); } @@ -455,7 +485,7 @@ class Surah extends Component { onClose={this.handleSurahInfoToggle} /> <Col md={10} mdOffset={1}> - <TopOptions options={options} actions={actions.options} surah={surah} /> + <TopOptions surah={surah} /> <Bismillah surah={surah} /> {options.isReadingMode ? this.renderLines() : this.renderAyahs()} </Col> diff --git a/src/containers/Surah/style.scss b/src/containers/Surah/style.scss index d417d56fa..209876a51 100644 --- a/src/containers/Surah/style.scss +++ b/src/containers/Surah/style.scss @@ -35,3 +35,16 @@ padding-top: 70px; } } + +.sidebarItem{ + padding: 10px 12px; + color: #333; + font-weight: 300; + + :global(.switch){ + float: right; + } + :global(a){ + color: #333; + } +} diff --git a/src/locale/en.js b/src/locale/en.js index b00905a3c..d0260b4bc 100644 --- a/src/locale/en.js +++ b/src/locale/en.js @@ -16,7 +16,7 @@ export default { 'setting.fontSize': 'Font size', 'setting.fontSize.arabic': 'Arabic', 'setting.reading': 'Reading', - 'setting.tooltip': 'tooltip', + 'setting.tooltip': 'Tooltip content', 'setting.tooltip.title': 'TOOLTIP DISPLAY', 'setting.tooltip.translation': 'Translation', 'setting.tooltip.transliteration': 'Transliteration', diff --git a/src/types/segmentType.js b/src/types/segmentType.js index ca76f885b..d564817fb 100644 --- a/src/types/segmentType.js +++ b/src/types/segmentType.js @@ -6,5 +6,8 @@ export default PropTypes.shape({ endTime: PropTypes.number.isRequired, duration: PropTypes.number.isRequired, })), - intervals: PropTypes.any // TODO: This should be done a better way. + intervals: PropTypes.oneOfType([ + PropTypes.array, + PropTypes.object + ]) // TODO: This should be done a better way. }); From 2a899d6c3828664afeb6933d39f81a5ec768100f Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Wed, 11 Jan 2017 21:54:30 -0800 Subject: [PATCH 02/24] fix lint --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 53e271315..1272fbd08 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,14 @@ "scripts": { "test": "npm run test:dev:unit", "test:ci:unit": "karma start --browsers PhantomJS --single-run", - "posttest:ci:unit": "npm run test:ci:lint", + "pretest:ci:unit": "npm run test:ci:lint", "test:ci:functional": "BROWSER=phantomjs ./tests/functional/test.sh start-ci", "posttest:ci:functional": "./tests/functional/test.sh stop", "test:dev:unit": "karma start", "test:ci:lint": "./node_modules/eslint/bin/eslint.js ./src/**/*.js", "test:dev:functional": "BROWSER=chrome ./tests/functional/test.sh start", "posttest:dev:functional": "./tests/functional/test.sh stop", - "test:dev:lint": "eslint ./src/scripts/**/*.js", + "test:dev:lint": "./node_modules/eslint/bin/eslint.js ./src/**/*.js", "test:stylelint": "stylelint './src/**/*.scss' --config ./webpack/.stylelintrc", "dev": "env NODE_PATH='./src' PORT=8000 UV_THREADPOOL_SIZE=100 node ./webpack/webpack-dev-server.js & env NODE_PATH='./src' PORT=8000 node ./bin/server.js", "start": "NODE_PATH='src' node ./start", From b5d5398ad05ef69bd12f35b7a8e2c3b223a71515 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Wed, 11 Jan 2017 22:28:36 -0800 Subject: [PATCH 03/24] fix tests --- src/components/ContentDropdown/spec.js | 2 +- src/components/TopOptions/spec.js | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/ContentDropdown/spec.js b/src/components/ContentDropdown/spec.js index 34b375361..90000612c 100644 --- a/src/components/ContentDropdown/spec.js +++ b/src/components/ContentDropdown/spec.js @@ -12,7 +12,7 @@ describe('<ContentDropdown />', () => { onOptionChange = sinon.stub(); wrapper = mountWithIntl( <ContentDropdown - options={{ content: [defaultOption] }} + content={defaultOption} onOptionChange={onOptionChange} /> ); diff --git a/src/components/TopOptions/spec.js b/src/components/TopOptions/spec.js index eb8a7654c..facd92a94 100644 --- a/src/components/TopOptions/spec.js +++ b/src/components/TopOptions/spec.js @@ -36,9 +36,5 @@ describe('<TopOptions />', () => { expect(component).to.be.ok; // eslint-disable-line expect(component.find(Share).length).to.eql(1); - expect(component.find(InformationToggle).length).to.eql(1); - expect(component.find(FontSizeDropdown).length).to.eql(1); - expect(component.find(TooltipDropdown).length).to.eql(1); - expect(component.find(ReadingModeToggle).length).to.eql(1); }); }); From 72a3c3aca5606995f01beb7af4a85ea0c8a53f30 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Wed, 11 Jan 2017 22:28:58 -0800 Subject: [PATCH 04/24] break eslint --- src/containers/Surah/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/Surah/index.js b/src/containers/Surah/index.js index 719acad32..21084256a 100644 --- a/src/containers/Surah/index.js +++ b/src/containers/Surah/index.js @@ -27,7 +27,7 @@ import SurahInfo from 'components/SurahInfo'; import Ayah from 'components/Ayah'; import Line from 'components/Line'; import SearchInput from 'components/SearchInput'; -import Bismillah from 'components/Bismillah'; +import Bismillah from 'components/Bismillah' import TopOptions from 'components/TopOptions'; import ReadingModeToggle from 'components/ReadingModeToggle'; import NightModeToggle from 'components/NightModeToggle'; From 54bdb1bb1a811448f13afe0163aff96392f895a0 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Wed, 11 Jan 2017 22:39:54 -0800 Subject: [PATCH 05/24] fix eslint --- package.json | 4 +-- src/components/Home/QuickSurahs/index.js | 5 ++-- src/components/Word/index.js | 35 +++++++++++------------- src/containers/Home/index.js | 5 +++- src/containers/Surah/index.js | 2 +- src/redux/actions/ayahs.js | 2 +- src/redux/modules/audioplayer.js | 2 +- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 1272fbd08..0d4313466 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "test:ci:functional": "BROWSER=phantomjs ./tests/functional/test.sh start-ci", "posttest:ci:functional": "./tests/functional/test.sh stop", "test:dev:unit": "karma start", - "test:ci:lint": "./node_modules/eslint/bin/eslint.js ./src/**/*.js", + "test:ci:lint": "./node_modules/eslint/bin/eslint.js ./src", "test:dev:functional": "BROWSER=chrome ./tests/functional/test.sh start", "posttest:dev:functional": "./tests/functional/test.sh stop", - "test:dev:lint": "./node_modules/eslint/bin/eslint.js ./src/**/*.js", + "test:dev:lint": "./node_modules/eslint/bin/eslint.js ./src", "test:stylelint": "stylelint './src/**/*.scss' --config ./webpack/.stylelintrc", "dev": "env NODE_PATH='./src' PORT=8000 UV_THREADPOOL_SIZE=100 node ./webpack/webpack-dev-server.js & env NODE_PATH='./src' PORT=8000 node ./bin/server.js", "start": "NODE_PATH='src' node ./start", diff --git a/src/components/Home/QuickSurahs/index.js b/src/components/Home/QuickSurahs/index.js index 17521f1ab..f1f7d7bba 100644 --- a/src/components/Home/QuickSurahs/index.js +++ b/src/components/Home/QuickSurahs/index.js @@ -14,7 +14,7 @@ export default () => { return ( <div className=""> <h4 className={`text-muted ${styles.title} ${styles.items}`}> - <LocaleFormattedMessage id='surah.index.quickLinks' defaultMessage={'Quick links'}/> + <LocaleFormattedMessage id="surah.index.quickLinks" defaultMessage="Quick links" /> { isFriday && <span> @@ -65,5 +65,6 @@ export default () => { </Link> </span> </h4> - </div>); + </div> + ); }; diff --git a/src/components/Word/index.js b/src/components/Word/index.js index b5cb53bc0..2da2801c6 100644 --- a/src/components/Word/index.js +++ b/src/components/Word/index.js @@ -1,29 +1,25 @@ import React, { PropTypes } from 'react'; -const styles = require('../Ayah/style.scss'); - /* eslint-disable no-unused-vars */ const CHAR_TYPE_WORD = 1; const CHAR_TYPE_END = 2; const CHAR_TYPE_PAUSE = 3; const CHAR_TYPE_RUB = 4; const CHAR_TYPE_SAJDAH = 5; -/* eslint-enable no-unused-vars */ export default class Line extends React.Component { static propTypes = { - word: PropTypes.object.isRequired, + word: PropTypes.object.isRequired, // eslint-disable-line tooltip: PropTypes.string, - audioActions: PropTypes.object.isRequired, - word: PropTypes.object.isRequired, - currentAyah: PropTypes.object.isRequired, + audioActions: PropTypes.object.isRequired, // eslint-disable-line + currentAyah: PropTypes.object.isRequired, // eslint-disable-line isPlaying: PropTypes.bool, isSearched: PropTypes.bool }; - buildTooltip(word, tooltip){ + buildTooltip = (word, tooltip) => { let title; - if (!word.wordId && word.charTypeId == CHAR_TYPE_END) { + if (!word.wordId && word.charTypeId === CHAR_TYPE_END) { title = `Verse ${word.ayahKey.split(':')[1]}`; } else { title = word[tooltip]; @@ -31,12 +27,13 @@ export default class Line extends React.Component { return title; } - handleWordClick(word){ + handleWordClick = (word) => { const { currentAyah, audioActions, isPlaying, isSearched } = this.props; - if(isSearched) return; - - if(currentAyah == word.ayahKey && isPlaying) { - audioActions.setCurrentWord(word.dataset.key)
; + if (isSearched) { + return; + } + if ((currentAyah === word.ayahKey) && isPlaying) { + audioActions.setCurrentWord(word.data.key); } else { audioActions.pause(); audioActions.setAyah(word.dataset.ayah); @@ -48,8 +45,8 @@ export default class Line extends React.Component { const { tooltip, word, currentAyah, isPlaying } = this.props; let id = null; - const position = word.position - 1; - const highlight = currentAyah == word.ayahKey && isPlaying ? 'highlight' : ''; + const position = word.position - 1; + const highlight = currentAyah === word.ayahKey && isPlaying ? 'highlight' : ''; const className = `${word.className} ${highlight} ${word.highlight ? word.highlight : ''}`; if (word.charTypeId === CHAR_TYPE_WORD) { @@ -57,16 +54,16 @@ export default class Line extends React.Component { } return ( - <b + <b // eslint-disable-line key={word.code} id={id} rel="tooltip" - onClick={(event) => this.handleWordClick(event.target)} + onClick={event => this.handleWordClick(event.target)} data-key={`${word.ayahKey}:${position}`} data-ayah={word.ayahKey} className={`${className} pointer`} title={this.buildTooltip(word, tooltip)} - dangerouslySetInnerHTML={{__html: word.code}} + dangerouslySetInnerHTML={{ __html: word.code }} /> ); } diff --git a/src/containers/Home/index.js b/src/containers/Home/index.js index b671266f6..e64a55d02 100644 --- a/src/containers/Home/index.js +++ b/src/containers/Home/index.js @@ -27,7 +27,10 @@ const Home = (props) => { <div className={`container ${styles.list}`}> <div className="row"> <div className="col-md-10 col-md-offset-1"> - {lastVisit && <LastVisit surah={props.surahs[lastVisit.surahId]} ayah={lastVisit.ayahId}/>} + { + lastVisit && + <LastVisit surah={props.surahs[lastVisit.surahId]} ayah={lastVisit.ayahId} /> + } <QuickSurahs /> <h4 className={`text-muted ${styles.title}`}> <LocaleFormattedMessage id="surah.index.heading" defaultMessage="SURAHS (CHAPTERS)" /> diff --git a/src/containers/Surah/index.js b/src/containers/Surah/index.js index 21084256a..719acad32 100644 --- a/src/containers/Surah/index.js +++ b/src/containers/Surah/index.js @@ -27,7 +27,7 @@ import SurahInfo from 'components/SurahInfo'; import Ayah from 'components/Ayah'; import Line from 'components/Line'; import SearchInput from 'components/SearchInput'; -import Bismillah from 'components/Bismillah' +import Bismillah from 'components/Bismillah'; import TopOptions from 'components/TopOptions'; import ReadingModeToggle from 'components/ReadingModeToggle'; import NightModeToggle from 'components/NightModeToggle'; diff --git a/src/redux/actions/ayahs.js b/src/redux/actions/ayahs.js index bf8830862..42ea2dda7 100644 --- a/src/redux/actions/ayahs.js +++ b/src/redux/actions/ayahs.js @@ -21,7 +21,7 @@ const defaultOptions = { export function load(id, from, to, options = defaultOptions) { const { audio, quran, content } = options; - cookie.save('lastVisit', JSON.stringify({surahId: id, ayahId: from})); + cookie.save('lastVisit', JSON.stringify({ surahId: id, ayahId: from })); return { types: [LOAD, LOAD_SUCCESS, LOAD_FAIL], diff --git a/src/redux/modules/audioplayer.js b/src/redux/modules/audioplayer.js index f79017ef2..cc67511cb 100644 --- a/src/redux/modules/audioplayer.js +++ b/src/redux/modules/audioplayer.js @@ -281,7 +281,7 @@ export default function reducer(state = initialState, action = {}) { const endTime = state.segments[surahId][nextId].words[word].endTime; currentFile.currentTime = currentTime; - const int = setInterval(function() { + const int = setInterval(() => { if (currentFile.currentTime > endTime) { currentFile.pause(); clearInterval(int); From 6c7223963a2999fc2b58e8fe7f88a468e99f80e8 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Wed, 11 Jan 2017 22:45:18 -0800 Subject: [PATCH 06/24] fix test --- src/components/ContentDropdown/spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ContentDropdown/spec.js b/src/components/ContentDropdown/spec.js index 90000612c..b6a377767 100644 --- a/src/components/ContentDropdown/spec.js +++ b/src/components/ContentDropdown/spec.js @@ -12,7 +12,7 @@ describe('<ContentDropdown />', () => { onOptionChange = sinon.stub(); wrapper = mountWithIntl( <ContentDropdown - content={defaultOption} + content={[defaultOption]} onOptionChange={onOptionChange} /> ); From b9a0b43e594d07bf5891452ea4683a684a49812d Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Wed, 11 Jan 2017 22:50:59 -0800 Subject: [PATCH 07/24] verse dropdown max height --- src/components/VersesDropdown/style.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/VersesDropdown/style.scss b/src/components/VersesDropdown/style.scss index 8bd923b10..52101888d 100644 --- a/src/components/VersesDropdown/style.scss +++ b/src/components/VersesDropdown/style.scss @@ -1,6 +1,8 @@ .dropdown{ - :global(ul) { + & + :global(.dropdown-menu){ max-height: 400px; + max-height: 60vh; overflow-y: scroll; + overflow-x: hidden; } } From 3754224ab420abb12c40ea2f2a1aa83f4e87bd02 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Thu, 12 Jan 2017 09:33:30 -0800 Subject: [PATCH 08/24] Global Nav --- src/components/GlobalNav/Surah/index.js | 35 ++++++++++++++++ src/components/GlobalNav/index.js | 51 +++++++++++++++++++++++ src/components/Home/LastVisit/index.js | 2 +- src/components/InformationToggle/index.js | 19 ++++----- src/components/SurahsDropdown/index.js | 19 ++++----- src/components/SurahsDropdown/style.scss | 2 +- src/containers/App/index.js | 9 ++-- src/containers/Surah/index.js | 2 - src/routes.js | 3 +- src/styles/partials/_search-input.scss | 2 - src/styles/variables.scss | 3 +- 11 files changed, 114 insertions(+), 33 deletions(-) create mode 100644 src/components/GlobalNav/Surah/index.js create mode 100644 src/components/GlobalNav/index.js diff --git a/src/components/GlobalNav/Surah/index.js b/src/components/GlobalNav/Surah/index.js new file mode 100644 index 000000000..7db364a01 --- /dev/null +++ b/src/components/GlobalNav/Surah/index.js @@ -0,0 +1,35 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; + +import { surahType, optionsType } from 'types'; +import * as OptionsActions from 'redux/actions/options.js'; + +import SurahsDropdown from 'components/SurahsDropdown'; +import InformationToggle from 'components/InformationToggle'; +import GlobalNav from '../index'; + +const GlobalNavSurah = ({ surah, surahs, setOption, options }) => ( + <GlobalNav + leftControls={[<SurahsDropdown title={surah.name.simple} surahs={surahs} />]} + rightControls={[ + <li> + <InformationToggle + onToggle={setOption} + isShowingSurahInfo={options.isShowingSurahInfo} + /> + </li> + ]} + /> +); + +GlobalNavSurah.propTypes = { + surah: surahType.isRequired, + surahs: PropTypes.objectOf(surahType).isRequired, + options: optionsType.isRequired +}; + +export default connect((state, ownProps) => ({ + surah: state.surahs.entities[parseInt(ownProps.params.surahId, 10)], + surahs: state.surahs.entities, + options: state.options +}), OptionsActions)(GlobalNavSurah); diff --git a/src/components/GlobalNav/index.js b/src/components/GlobalNav/index.js new file mode 100644 index 000000000..cca43ad76 --- /dev/null +++ b/src/components/GlobalNav/index.js @@ -0,0 +1,51 @@ +import React, { PropTypes } from 'react'; +import { Link } from 'react-router'; +import Col from 'react-bootstrap/lib/Col'; +import Navbar from 'react-bootstrap/lib/Navbar'; +import Nav from 'react-bootstrap/lib/Nav'; + +import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; +import SearchInput from 'components/SearchInput'; + +import debug from 'helpers/debug'; + +const Header = Navbar.Header; +const Form = Navbar.Form; + +// const styles = require('./style.scss'); + +const GlobalNav = ({ leftControls, rightControls }) => { + debug('component:GlobalNav', 'Render'); + + return ( + <Navbar className="montserrat" fixedTop fluid> + <button type="button" className="navbar-toggle collapsed"> + <span className="sr-only">Toggle navigation</span> + <span className="icon-bar" /> + <span className="icon-bar" /> + <span className="icon-bar" /> + </button> + <Nav> + {leftControls && leftControls.map(control => control)} + <Form pullLeft> + <SearchInput + className="search-input" + /> + </Form> + </Nav> + {rightControls && + <Nav pullRight> + {rightControls.map(control => control)} + </Nav> + } + </Navbar> + ); +}; + +GlobalNav.propTypes = { + // handleToggleSidebar: PropTypes.func.isRequired, + leftControls: PropTypes.arrayOf(PropTypes.element), + rightControls: PropTypes.arrayOf(PropTypes.element), +}; + +export default GlobalNav; diff --git a/src/components/Home/LastVisit/index.js b/src/components/Home/LastVisit/index.js index 36b5a074a..cacb4ab74 100644 --- a/src/components/Home/LastVisit/index.js +++ b/src/components/Home/LastVisit/index.js @@ -11,7 +11,7 @@ const LastVisit = (props) => { return ( <div> <h4 className={`text-muted ${styles.title}`}> - <LocaleFormattedMessage id="surah.index.continue" defaultMessage="Continue" /> + <LocaleFormattedMessage id="surah.index.continue" defaultMessage="Continue" />{' '} <Link to={`/${props.surah.id}/${props.ayah}`}> <span> {props.surah.name.simple} ({props.surah.id}:{props.ayah}) diff --git a/src/components/InformationToggle/index.js b/src/components/InformationToggle/index.js index 5da9be52e..a83600465 100644 --- a/src/components/InformationToggle/index.js +++ b/src/components/InformationToggle/index.js @@ -1,18 +1,15 @@ import React, { PropTypes } from 'react'; -import SwitchToggle from 'components/SwitchToggle'; -import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; - const InformationToggle = ({ isShowingSurahInfo, onToggle }) => ( - <div> - <LocaleFormattedMessage id="surah.info" defaultMessage="Surah Info" /> - <SwitchToggle - checked={isShowingSurahInfo} - onToggle={() => onToggle({ isShowingSurahInfo: !isShowingSurahInfo })} - id="info-toggle" - flat + <a + tabIndex="-1" + className={`pointer ${isShowingSurahInfo && 'active'}`} + onClick={() => onToggle({ isShowingSurahInfo: !isShowingSurahInfo })} + > + <i + className="ss-icon ss-info" /> - </div> + </a> ); InformationToggle.propTypes = { diff --git a/src/components/SurahsDropdown/index.js b/src/components/SurahsDropdown/index.js index 87bc18bbf..adc482cb2 100644 --- a/src/components/SurahsDropdown/index.js +++ b/src/components/SurahsDropdown/index.js @@ -2,7 +2,7 @@ import React, { Component, PropTypes } from 'react'; import LinkContainer from 'react-router-bootstrap/lib/LinkContainer'; import Col from 'react-bootstrap/lib/Col'; -import DropdownButton from 'react-bootstrap/lib/DropdownButton'; +import NavDropdown from 'react-bootstrap/lib/NavDropdown'; import MenuItem from 'react-bootstrap/lib/MenuItem'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; @@ -13,11 +13,8 @@ const styles = require('./style.scss'); export default class SurahsDropdown extends Component { static propTypes = { surahs: PropTypes.objectOf(surahType).isRequired, - className: PropTypes.string - }; - - static defaultProps = { - className: 'col-md-3' + className: PropTypes.string, + title: PropTypes.string, }; shouldComponentUpdate(nextProps) { @@ -51,17 +48,17 @@ export default class SurahsDropdown extends Component { } render() { - const { className } = this.props; + const { className, title } = this.props; return ( - <DropdownButton + <NavDropdown link - className={`${className} ${styles.dropdown}`} + className={styles.dropdown} id="surahs-dropdown" - title={<LocaleFormattedMessage id="setting.surahs" defaultMessage="Surahs" />} + title={title || <LocaleFormattedMessage id="setting.surahs" defaultMessage="Surahs" />} > {this.renderList()} - </DropdownButton> + </NavDropdown> ); } } diff --git a/src/components/SurahsDropdown/style.scss b/src/components/SurahsDropdown/style.scss index d27531488..a76bcd025 100644 --- a/src/components/SurahsDropdown/style.scss +++ b/src/components/SurahsDropdown/style.scss @@ -1,5 +1,5 @@ .dropdown{ - & + :global(.dropdown-menu){ + :global(.dropdown-menu){ max-height: 400px; max-height: 60vh; overflow-y: scroll; diff --git a/src/containers/App/index.js b/src/containers/App/index.js index ea3007145..198abf249 100644 --- a/src/containers/App/index.js +++ b/src/containers/App/index.js @@ -29,7 +29,9 @@ class App extends Component { content: PropTypes.string }).isRequired, removeMedia: PropTypes.func.isRequired, - children: PropTypes.element + children: PropTypes.element, + main: PropTypes.element, + nav: PropTypes.element, }; static contextTypes = { @@ -37,7 +39,7 @@ class App extends Component { }; render() { - const { children, media, removeMedia } = this.props; // eslint-disable-line no-shadow + const { main, nav, children, media, removeMedia } = this.props; // eslint-disable-line no-shadow debug('component:APPLICATION', 'Render'); return ( @@ -58,7 +60,8 @@ class App extends Component { </Col> </div> </NoScript> - {children} + {nav} + {children || main} <SmartBanner title="The Noble Quran - القرآن الكريم" button="Install" /> <Footer /> <Modal bsSize="large" show={!!media.content} onHide={removeMedia}> diff --git a/src/containers/Surah/index.js b/src/containers/Surah/index.js index 719acad32..7dfbb1b4a 100644 --- a/src/containers/Surah/index.js +++ b/src/containers/Surah/index.js @@ -54,7 +54,6 @@ import * as MediaActions from 'redux/actions/media.js'; import { surahsConnect, surahInfoConnect, ayahsConnect } from './connect'; -import Header from './Header'; const NavbarHeader = Navbar.Header; @@ -469,7 +468,6 @@ class Surah extends Component { } ]} /> - <Header surah={surah} handleToggleSidebar={() => this.setState({ sidebarOpen: true })} /> <Sidebar open={this.state.sidebarOpen} onSetOpen={open => this.setState({ sidebarOpen: open })} diff --git a/src/routes.js b/src/routes.js index 943563890..5ed4a22cc 100644 --- a/src/routes.js +++ b/src/routes.js @@ -3,6 +3,7 @@ import React from 'react'; import IndexRoute from 'react-router/lib/IndexRoute'; import Route from 'react-router/lib/Route'; import Redirect from 'react-router/lib/Redirect'; +import GlobalNavSurah from 'components/GlobalNav/Surah'; import { isLoaded as isAuthLoaded, load as loadAuth, hasAccessToken } from 'redux/actions/auth'; @@ -64,7 +65,7 @@ export default (store) => { <Route path="/:surahId(/:range)" - getComponent={(nextState, cb) => System.import('./containers/Surah').then(module => cb(null, module)).catch(err => console.trace(err))} + getComponents={(nextState, cb) => System.import('./containers/Surah').then(module => cb(null, { main: module, nav: GlobalNavSurah })).catch(err => console.trace(err))} onEnter={checkValidSurah} /> </Route> diff --git a/src/styles/partials/_search-input.scss b/src/styles/partials/_search-input.scss index 130bc127d..3567a92a2 100644 --- a/src/styles/partials/_search-input.scss +++ b/src/styles/partials/_search-input.scss @@ -1,6 +1,5 @@ .right-inner-addon.search-input{ padding: 0px; - height: 40px; margin-bottom: 0px; input{ @@ -87,4 +86,3 @@ .search-status{ span span{color: #2CA4AB} } - diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 173c5e8e8..5808c5b71 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -28,8 +28,9 @@ $text-muted: #d1d0d0; $text-color: #939598; $navbar-height: 50px; -$navbar-default-bg: $beige; +$navbar-default-bg: #fff; $navbar-default-link-active-bg: transparent; +$navbar-default-link-active-color: $brand-primary; $navbar-default-border: transparent; $navbar-inverse-bg: $brand-primary; From 98f0b45874de84044abfd2161bbe5ff51eb4d592 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Thu, 12 Jan 2017 09:38:32 -0800 Subject: [PATCH 09/24] small bug --- src/components/GlobalNav/Surah/index.js | 3 ++- src/components/GlobalNav/index.js | 4 ---- src/components/SurahsDropdown/index.js | 3 +-- src/routes.js | 3 ++- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/GlobalNav/Surah/index.js b/src/components/GlobalNav/Surah/index.js index 7db364a01..ac50223e5 100644 --- a/src/components/GlobalNav/Surah/index.js +++ b/src/components/GlobalNav/Surah/index.js @@ -25,7 +25,8 @@ const GlobalNavSurah = ({ surah, surahs, setOption, options }) => ( GlobalNavSurah.propTypes = { surah: surahType.isRequired, surahs: PropTypes.objectOf(surahType).isRequired, - options: optionsType.isRequired + options: optionsType.isRequired, + setOption: PropTypes.func.isRequired, }; export default connect((state, ownProps) => ({ diff --git a/src/components/GlobalNav/index.js b/src/components/GlobalNav/index.js index cca43ad76..4abb32638 100644 --- a/src/components/GlobalNav/index.js +++ b/src/components/GlobalNav/index.js @@ -1,15 +1,11 @@ import React, { PropTypes } from 'react'; -import { Link } from 'react-router'; -import Col from 'react-bootstrap/lib/Col'; import Navbar from 'react-bootstrap/lib/Navbar'; import Nav from 'react-bootstrap/lib/Nav'; -import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; import SearchInput from 'components/SearchInput'; import debug from 'helpers/debug'; -const Header = Navbar.Header; const Form = Navbar.Form; // const styles = require('./style.scss'); diff --git a/src/components/SurahsDropdown/index.js b/src/components/SurahsDropdown/index.js index adc482cb2..2b50dc1d3 100644 --- a/src/components/SurahsDropdown/index.js +++ b/src/components/SurahsDropdown/index.js @@ -13,7 +13,6 @@ const styles = require('./style.scss'); export default class SurahsDropdown extends Component { static propTypes = { surahs: PropTypes.objectOf(surahType).isRequired, - className: PropTypes.string, title: PropTypes.string, }; @@ -48,7 +47,7 @@ export default class SurahsDropdown extends Component { } render() { - const { className, title } = this.props; + const { title } = this.props; return ( <NavDropdown diff --git a/src/routes.js b/src/routes.js index 5ed4a22cc..db6d23ca1 100644 --- a/src/routes.js +++ b/src/routes.js @@ -4,6 +4,7 @@ import IndexRoute from 'react-router/lib/IndexRoute'; import Route from 'react-router/lib/Route'; import Redirect from 'react-router/lib/Redirect'; import GlobalNavSurah from 'components/GlobalNav/Surah'; +import GlobalNav from 'components/GlobalNav'; import { isLoaded as isAuthLoaded, load as loadAuth, hasAccessToken } from 'redux/actions/auth'; @@ -39,7 +40,7 @@ export default (store) => { return ( <Route path="/" component={App} onEnter={shouldAuth}> - <IndexRoute component={Home} /> + <IndexRoute components={{ main: Home, nav: GlobalNav }} /> <Route path="/donations" getComponent={(nextState, cb) => System.import('./containers/Donations').then(module => cb(null, module))} /> <Route path="/contributions" getComponent={(nextState, cb) => System.import('./containers/Donations').then(module => cb(null, module))} /> From 496019b25fa496fa502010d562ded8a11b014e62 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Thu, 12 Jan 2017 09:44:26 -0800 Subject: [PATCH 10/24] change again --- src/containers/App/index.js | 3 ++- src/routes.js | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/containers/App/index.js b/src/containers/App/index.js index 198abf249..f7a9026db 100644 --- a/src/containers/App/index.js +++ b/src/containers/App/index.js @@ -6,6 +6,7 @@ import { asyncConnect } from 'redux-connect'; import Helmet from 'react-helmet'; import Modal from 'react-bootstrap/lib/Modal'; import SmartBanner from 'components/SmartBanner'; +import GlobalNav from 'components/GlobalNav'; import Col from 'react-bootstrap/lib/Col'; import debug from 'helpers/debug'; @@ -60,7 +61,7 @@ class App extends Component { </Col> </div> </NoScript> - {nav} + {nav || <GlobalNav />} {children || main} <SmartBanner title="The Noble Quran - القرآن الكريم" button="Install" /> <Footer /> diff --git a/src/routes.js b/src/routes.js index db6d23ca1..ba681d3ed 100644 --- a/src/routes.js +++ b/src/routes.js @@ -4,7 +4,6 @@ import IndexRoute from 'react-router/lib/IndexRoute'; import Route from 'react-router/lib/Route'; import Redirect from 'react-router/lib/Redirect'; import GlobalNavSurah from 'components/GlobalNav/Surah'; -import GlobalNav from 'components/GlobalNav'; import { isLoaded as isAuthLoaded, load as loadAuth, hasAccessToken } from 'redux/actions/auth'; @@ -40,7 +39,7 @@ export default (store) => { return ( <Route path="/" component={App} onEnter={shouldAuth}> - <IndexRoute components={{ main: Home, nav: GlobalNav }} /> + <IndexRoute components={Home} /> <Route path="/donations" getComponent={(nextState, cb) => System.import('./containers/Donations').then(module => cb(null, module))} /> <Route path="/contributions" getComponent={(nextState, cb) => System.import('./containers/Donations').then(module => cb(null, module))} /> From 1a1524cad2bd72711e7e55ba0a502389110ef777 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Thu, 12 Jan 2017 09:48:13 -0800 Subject: [PATCH 11/24] placeholder for search --- src/locale/en.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locale/en.js b/src/locale/en.js index d0260b4bc..6f248f471 100644 --- a/src/locale/en.js +++ b/src/locale/en.js @@ -28,7 +28,7 @@ export default { 'ayah.bookmarked': 'Bookmarked', 'ayah.bookmark': 'Bookmark', - 'search.placeholder': 'Search', + 'search.placeholder': 'Search "Noah"', 'search.resultHeading': '{from}-{to} OF <span> {total} </span> SEARCH RESULTS FOR: <span>{query}</span>', // eslint-disable-line max-len 'search.error': 'Sorry, there was an error with your search.', 'search.noResult': 'No results found.', From 9d4a39eae41b60d873615bee678eeca702553871 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Mon, 16 Jan 2017 17:07:06 -0800 Subject: [PATCH 12/24] global nav edits and to merge so one pr --- src/components/GlobalNav/Surah/index.js | 55 +++++++++--- src/components/GlobalNav/index.js | 104 ++++++++++++++-------- src/components/GlobalNav/style.scss | 3 + src/components/InformationToggle/index.js | 24 ++--- src/components/NightModeToggle/index.js | 23 +++-- src/components/ReadingModeToggle/index.js | 27 +++--- src/redux/actions/options.js | 8 +- src/redux/constants/options.js | 1 - src/redux/modules/options.js | 8 +- 9 files changed, 154 insertions(+), 99 deletions(-) create mode 100644 src/components/GlobalNav/style.scss diff --git a/src/components/GlobalNav/Surah/index.js b/src/components/GlobalNav/Surah/index.js index ac50223e5..76d0e4789 100644 --- a/src/components/GlobalNav/Surah/index.js +++ b/src/components/GlobalNav/Surah/index.js @@ -5,19 +5,27 @@ import { surahType, optionsType } from 'types'; import * as OptionsActions from 'redux/actions/options.js'; import SurahsDropdown from 'components/SurahsDropdown'; +import ReadingModeToggle from 'components/ReadingModeToggle'; +import NightModeToggle from 'components/NightModeToggle'; +// TODO: import VersesDropdown from 'components/VersesDropdown'; import InformationToggle from 'components/InformationToggle'; import GlobalNav from '../index'; const GlobalNavSurah = ({ surah, surahs, setOption, options }) => ( <GlobalNav - leftControls={[<SurahsDropdown title={surah.name.simple} surahs={surahs} />]} + leftControls={[ + <SurahsDropdown title={surah.name.simple} surahs={surahs} />, + ]} rightControls={[ - <li> - <InformationToggle - onToggle={setOption} - isShowingSurahInfo={options.isShowingSurahInfo} - /> - </li> + <InformationToggle + onToggle={setOption} + isToggled={options.isShowingSurahInfo} + />, + <ReadingModeToggle + isToggled={options.isReadingMode} + onToggle={setOption} + />, + <NightModeToggle /> ]} /> ); @@ -29,8 +37,31 @@ GlobalNavSurah.propTypes = { setOption: PropTypes.func.isRequired, }; -export default connect((state, ownProps) => ({ - surah: state.surahs.entities[parseInt(ownProps.params.surahId, 10)], - surahs: state.surahs.entities, - options: state.options -}), OptionsActions)(GlobalNavSurah); +function mapStateToProps(state, ownProps) { + const surahId = parseInt(ownProps.params.surahId, 10); + const surah: Object = state.surahs.entities[surahId]; + const ayahs: Object = state.ayahs.entities[surahId]; + const ayahArray = ayahs ? Object.keys(ayahs).map(key => parseInt(key.split(':')[1], 10)) : []; + const ayahIds = new Set(ayahArray); + const lastAyahInArray = ayahArray.slice(-1)[0]; + + return { + surah, + ayahs, + ayahIds, + isStarted: state.audioplayer.isStarted, + isPlaying: state.audioplayer.isPlaying, + currentAyah: state.audioplayer.currentAyah, + isAuthenticated: state.auth.loaded, + currentWord: state.ayahs.currentWord, + isEndOfSurah: lastAyahInArray === surah.ayat, + surahs: state.surahs.entities, + bookmarks: state.bookmarks.entities, + isLoading: state.ayahs.loading, + isLoaded: state.ayahs.loaded, + lines: state.lines.lines, + options: state.options + }; +} + +export default connect(mapStateToProps, OptionsActions)(GlobalNavSurah); diff --git a/src/components/GlobalNav/index.js b/src/components/GlobalNav/index.js index 4abb32638..0b9b5620b 100644 --- a/src/components/GlobalNav/index.js +++ b/src/components/GlobalNav/index.js @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react'; +/* global window */ +import React, { PropTypes, Component } from 'react'; import Navbar from 'react-bootstrap/lib/Navbar'; import Nav from 'react-bootstrap/lib/Nav'; @@ -8,40 +9,73 @@ import debug from 'helpers/debug'; const Form = Navbar.Form; -// const styles = require('./style.scss'); - -const GlobalNav = ({ leftControls, rightControls }) => { - debug('component:GlobalNav', 'Render'); - - return ( - <Navbar className="montserrat" fixedTop fluid> - <button type="button" className="navbar-toggle collapsed"> - <span className="sr-only">Toggle navigation</span> - <span className="icon-bar" /> - <span className="icon-bar" /> - <span className="icon-bar" /> - </button> - <Nav> - {leftControls && leftControls.map(control => control)} - <Form pullLeft> - <SearchInput - className="search-input" - /> - </Form> - </Nav> - {rightControls && - <Nav pullRight> - {rightControls.map(control => control)} - </Nav> +const styles = require('./style.scss'); + +class GlobalNav extends Component { + static propTypes = { + // handleToggleSidebar: PropTypes.func.isRequired, + leftControls: PropTypes.arrayOf(PropTypes.element), + rightControls: PropTypes.arrayOf(PropTypes.element), + }; + + state = { + scrolled: false + } + + componentDidMount() { + window.addEventListener('scroll', this.handleNavbar, true); + } + + componentWillUnmount() { + window.removeEventListener('scroll', this.handleNavbar, true); + } + + handleNavbar = () => { + const { scrolled } = this.state; + + if (window.pageYOffset > 50) { + if (!scrolled) { + this.setState({ scrolled: true }); } - </Navbar> - ); -}; - -GlobalNav.propTypes = { - // handleToggleSidebar: PropTypes.func.isRequired, - leftControls: PropTypes.arrayOf(PropTypes.element), - rightControls: PropTypes.arrayOf(PropTypes.element), -}; + } else if (scrolled) { + this.setState({ scrolled: false }); + } + + return false; + } + + render() { + const { leftControls, rightControls } = this.props; + debug('component:GlobalNav', 'Render'); + + return ( + <Navbar + className={`montserrat ${this.state.scrolled && styles.scrolled}`} + fixedTop + fluid + > + <button type="button" className="navbar-toggle collapsed"> + <span className="sr-only">Toggle navigation</span> + <span className="icon-bar" /> + <span className="icon-bar" /> + <span className="icon-bar" /> + </button> + <Nav> + {leftControls && leftControls.map(control => control)} + <Form pullLeft> + <SearchInput + className="search-input" + /> + </Form> + </Nav> + {rightControls && + <Nav pullRight> + {rightControls.map(control => control)} + </Nav> + } + </Navbar> + ); + } +} export default GlobalNav; diff --git a/src/components/GlobalNav/style.scss b/src/components/GlobalNav/style.scss new file mode 100644 index 000000000..74e24ab3d --- /dev/null +++ b/src/components/GlobalNav/style.scss @@ -0,0 +1,3 @@ +.scrolled{ + box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.2); +} diff --git a/src/components/InformationToggle/index.js b/src/components/InformationToggle/index.js index a83600465..8021be197 100644 --- a/src/components/InformationToggle/index.js +++ b/src/components/InformationToggle/index.js @@ -1,19 +1,21 @@ import React, { PropTypes } from 'react'; -const InformationToggle = ({ isShowingSurahInfo, onToggle }) => ( - <a - tabIndex="-1" - className={`pointer ${isShowingSurahInfo && 'active'}`} - onClick={() => onToggle({ isShowingSurahInfo: !isShowingSurahInfo })} - > - <i - className="ss-icon ss-info" - /> - </a> +const InformationToggle = ({ isToggled, onToggle }) => ( + <li className={isToggled && 'active'}> + <a + tabIndex="-1" + className="pointer" + onClick={() => onToggle({ isShowingSurahInfo: !isToggled })} + > + <i + className="ss-icon ss-info" + /> + </a> + </li> ); InformationToggle.propTypes = { - isShowingSurahInfo: PropTypes.bool.isRequired, + isToggled: PropTypes.bool.isRequired, onToggle: PropTypes.func.isRequired }; diff --git a/src/components/NightModeToggle/index.js b/src/components/NightModeToggle/index.js index dd14b442b..0175da6e4 100644 --- a/src/components/NightModeToggle/index.js +++ b/src/components/NightModeToggle/index.js @@ -1,9 +1,6 @@ /* global document */ import React, { Component } from 'react'; -import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; -import SwitchToggle from 'components/SwitchToggle'; - import bindTooltip from 'utils/bindTooltip'; class NightModeToggle extends Component { @@ -23,15 +20,17 @@ class NightModeToggle extends Component { render() { return ( - <div> - <LocaleFormattedMessage id="setting.nightMode" defaultMessage="Night Mode" /> - <SwitchToggle - checked={this.state.isNightMode} - onToggle={this.toggleNightMode} - id="night-mode-toggle" - flat - /> - </div> + <li className={this.state.isNightMode && 'active'}> + <a + tabIndex="-1" + className="pointer" + onClick={this.toggleNightMode} + > + <i + className="ss-icon ss-lightbulb" + /> + </a> + </li> ); } } diff --git a/src/components/ReadingModeToggle/index.js b/src/components/ReadingModeToggle/index.js index 77f44f5f3..6e236c004 100644 --- a/src/components/ReadingModeToggle/index.js +++ b/src/components/ReadingModeToggle/index.js @@ -1,22 +1,21 @@ import React, { PropTypes } from 'react'; -import SwitchToggle from 'components/SwitchToggle'; -import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; - -const ReadingModeToggle = ({ onReadingModeToggle, isToggled }) => ( - <div> - <LocaleFormattedMessage id="setting.reading" defaultMessage="Reading" /> - <SwitchToggle - checked={isToggled} - onToggle={onReadingModeToggle} - id="reading-mode-toggle" - flat - /> - </div> +const ReadingModeToggle = ({ onToggle, isToggled }) => ( + <li className={isToggled && 'active'}> + <a + tabIndex="-1" + className="pointer" + onClick={() => onToggle({ isReadingMode: !isToggled })} + > + <i + className="ss-icon ss-openbook" + /> + </a> + </li> ); ReadingModeToggle.propTypes = { - onReadingModeToggle: PropTypes.func.isRequired, + onToggle: PropTypes.func.isRequired, isToggled: PropTypes.bool.isRequired }; diff --git a/src/redux/actions/options.js b/src/redux/actions/options.js index 1f327915d..443623cb1 100644 --- a/src/redux/actions/options.js +++ b/src/redux/actions/options.js @@ -1,5 +1,5 @@ import cookie from 'react-cookie'; -import { TOGGLE_READING_MODE, SET_OPTION } from 'redux/constants/options.js'; +import { SET_OPTION } from 'redux/constants/options.js'; export function isReadingMode(globalState) { return globalState.options.isReadingMode; @@ -15,9 +15,3 @@ export function setOption(payload) { payload }; } - -export function toggleReadingMode() { - return { - type: TOGGLE_READING_MODE - }; -} diff --git a/src/redux/constants/options.js b/src/redux/constants/options.js index 1e33b4619..339433c66 100644 --- a/src/redux/constants/options.js +++ b/src/redux/constants/options.js @@ -1,3 +1,2 @@ -export const TOGGLE_READING_MODE = '@@quran/options/TOGGLE_READING_MODE'; export const SET_OPTION = '@@quran/options/SET_OPTION'; export const TOGGLE_NIGHT_MODE = '@@quran/options/TOGGLE_NIGHT_MODE'; diff --git a/src/redux/modules/options.js b/src/redux/modules/options.js index 48b3ce4a4..e275ae80d 100644 --- a/src/redux/modules/options.js +++ b/src/redux/modules/options.js @@ -1,5 +1,5 @@ -import { TOGGLE_READING_MODE, SET_OPTION } from 'redux/constants/options.js'; +import { SET_OPTION } from 'redux/constants/options.js'; const initialState = { isReadingMode: false, @@ -16,12 +16,6 @@ const initialState = { export default function reducer(state = initialState, action = {}) { switch (action.type) { - case TOGGLE_READING_MODE: { - return { - ...state, - isReadingMode: !state.isReadingMode - }; - } case SET_OPTION: { const payload = action.payload; return { From 5cf9c24cde59825a2ea55a1a1cc9b1abc0d499e9 Mon Sep 17 00:00:00 2001 From: Mohamed El Mahallawy <mmahalwy@gmail.com> Date: Tue, 17 Jan 2017 01:16:58 -0800 Subject: [PATCH 13/24] a ton of changes and i want to sleep now --- src/components/ContentDropdown/index.js | 50 +- src/components/FontSizeDropdown/index.js | 9 +- src/components/Footer/index.js | 1 - src/components/GlobalNav/Surah/index.js | 47 +- src/components/GlobalNav/index.js | 93 ++- src/components/GlobalNav/style.scss | 17 + src/components/GlobalSidebar/Surah/index.js | 29 + src/components/GlobalSidebar/index.js | 116 +++ src/components/GlobalSidebar/style.scss | 61 ++ src/components/IndexHeader/index.js | 3 - src/components/InformationToggle/index.js | 4 +- .../LocaleFormattedMessage/index.js | 9 +- src/components/LocaleSwitcher/index.js | 44 +- src/components/NightModeToggle/index.js | 5 +- src/components/Radio/index.js | 30 + src/components/Radio/style.scss | 130 +++ src/components/ReadingModeToggle/index.js | 4 +- src/components/ReciterDropdown/index.js | 26 +- src/components/SettingsModal/index.js | 86 ++ src/components/Sidebar/index.js | 50 -- src/components/Sidebar/style.scss | 38 - src/components/TooltipDropdown/index.js | 75 +- src/containers/App/index.js | 34 +- src/containers/Search/index.js | 7 +- src/containers/Search/style.scss | 2 +- src/containers/Surah/index.js | 99 --- src/containers/Surah/style.scss | 2 +- src/locale/ar.js | 3 + src/locale/en.js | 5 +- src/locale/ur.js | 3 + src/redux/actions/search.js | 2 +- src/routes.js | 7 +- src/styles/main.scss | 27 +- src/styles/variables.scss | 1 + static/fonts/ss-standard/documentation.html | 4 +- static/fonts/ss-standard/ss-standard.css | 784 ++++++++++++++++++ 36 files changed, 1544 insertions(+), 363 deletions(-) create mode 100644 src/components/GlobalSidebar/Surah/index.js create mode 100644 src/components/GlobalSidebar/index.js create mode 100644 src/components/GlobalSidebar/style.scss create mode 100644 src/components/Radio/index.js create mode 100644 src/components/Radio/style.scss create mode 100644 src/components/SettingsModal/index.js delete mode 100644 src/components/Sidebar/index.js delete mode 100644 src/components/Sidebar/style.scss create mode 100644 static/fonts/ss-standard/ss-standard.css diff --git a/src/components/ContentDropdown/index.js b/src/components/ContentDropdown/index.js index 4f3aa8f0f..62eb06fc9 100644 --- a/src/components/ContentDropdown/index.js +++ b/src/components/ContentDropdown/index.js @@ -1,5 +1,6 @@ import React, { Component, PropTypes } from 'react'; +import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar'; import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import MenuItem from 'react-bootstrap/lib/MenuItem'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; @@ -453,10 +454,6 @@ export default class ContentDropdown extends Component { className: PropTypes.string }; - static defaultProps = { - className: 'col-md-3' - } - handleRemoveContent = () => { const { onOptionChange } = this.props; @@ -511,28 +508,33 @@ export default class ContentDropdown extends Component { render() { const { className, content } = this.props; + const title = slugs.filter(slug => content.includes(slug.id)).map(slug => slug.name).join(', '); return ( - <DropdownButton - className={`dropdown ${className} ${style.dropdown}`} - title={<LocaleFormattedMessage id="setting.translations.title" defaultMessage="Translations" />} - > - { - content.length && - <MenuItem onClick={this.handleRemoveContent}> - <LocaleFormattedMessage id="setting.translations.removeAll" defaultMessage="Remove all" /> - </MenuItem> - } - <MenuItem header> - <LocaleFormattedMessage id="setting.translations.english" defaultMessage="English" /> - </MenuItem> - {this.renderEnglishList()} - <MenuItem divider /> - <MenuItem header> - <LocaleFormattedMessage id="setting.translations.other" defaultMessage="Other Languages" /> - </MenuItem> - {this.renderLanguagesList()} - </DropdownButton> + <ButtonToolbar> + <DropdownButton + block + id="content-dropdown" + className={`dropdown ${className} ${style.dropdown}`} + title={title} + > + { + content.length && + <MenuItem onClick={this.handleRemoveContent}> + <LocaleFormattedMessage id="setting.translations.removeAll" defaultMessage="Remove all" /> + </MenuItem> + } + <MenuItem header> + <LocaleFormattedMessage id="setting.translations.english" defaultMessage="English" /> + </MenuItem> + {this.renderEnglishList()} + <MenuItem divider /> + <MenuItem header> + <LocaleFormattedMessage id="setting.translations.other" defaultMessage="Other Languages" /> + </MenuItem> + {this.renderLanguagesList()} + </DropdownButton> + </ButtonToolbar> ); } } diff --git a/src/components/FontSizeDropdown/index.js b/src/components/FontSizeDropdown/index.js index 8b2d283fb..5fae9cd44 100644 --- a/src/components/FontSizeDropdown/index.js +++ b/src/components/FontSizeDropdown/index.js @@ -95,16 +95,17 @@ export default class FontSizeDropdown extends Component { render() { return ( - <div className={style.link}> - <OverlayTrigger trigger="click" placement="bottom" overlay={this.renderPopup()} rootClose container={this} positionLeft={0}> + <li className={style.link}> + <OverlayTrigger trigger="click" placement="bottom" overlay={this.renderPopup()} rootClose> <a tabIndex="-1" data-metrics-event-name="FontSizeDropdown" > - <LocaleFormattedMessage id="setting.fontSize" defaultMessage="Font size" /> + <i className="ss-icon ss-font vertical-align-middle" /> + {' '}<LocaleFormattedMessage id="setting.fontSize" defaultMessage="Font Size" className="visible-xs-inline-block" /> </a> </OverlayTrigger> - </div> + </li> ); } } diff --git a/src/components/Footer/index.js b/src/components/Footer/index.js index 6641a7fdd..20920b1ba 100644 --- a/src/components/Footer/index.js +++ b/src/components/Footer/index.js @@ -144,7 +144,6 @@ const Footer = () => ( </p> <div className={styles.list}> - <LocaleSwitcher /> <p className="monserrat"> <LocaleFormattedMessage id="nav.aboutQuranProject" diff --git a/src/components/GlobalNav/Surah/index.js b/src/components/GlobalNav/Surah/index.js index 76d0e4789..e07c4bde5 100644 --- a/src/components/GlobalNav/Surah/index.js +++ b/src/components/GlobalNav/Surah/index.js @@ -1,5 +1,6 @@ import React, { PropTypes } from 'react'; import { connect } from 'react-redux'; +import NavDropdown from 'react-bootstrap/lib/NavDropdown'; import { surahType, optionsType } from 'types'; import * as OptionsActions from 'redux/actions/options.js'; @@ -7,16 +8,44 @@ import * as OptionsActions from 'redux/actions/options.js'; import SurahsDropdown from 'components/SurahsDropdown'; import ReadingModeToggle from 'components/ReadingModeToggle'; import NightModeToggle from 'components/NightModeToggle'; +import FontSizeDropdown from 'components/FontSizeDropdown'; +import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; // TODO: import VersesDropdown from 'components/VersesDropdown'; import InformationToggle from 'components/InformationToggle'; import GlobalNav from '../index'; -const GlobalNavSurah = ({ surah, surahs, setOption, options }) => ( +const styles = require('../style.scss'); + +const GlobalNavSurah = ({ surah, surahs, setOption, options, ...props }) => ( <GlobalNav + {...props} leftControls={[ <SurahsDropdown title={surah.name.simple} surahs={surahs} />, + <NavDropdown + id="hidden-dropdown" + className={`visible-xs-inline-block ${styles.optionsDropdown}`} + title={<LocaleFormattedMessage id="settings.options" defaultMessage="Options" />} + > + <FontSizeDropdown + fontSize={options.fontSize} + onOptionChange={setOption} + /> + <InformationToggle + onToggle={setOption} + isToggled={options.isShowingSurahInfo} + /> + <ReadingModeToggle + isToggled={options.isReadingMode} + onToggle={setOption} + /> + <NightModeToggle /> + </NavDropdown> ]} rightControls={[ + <FontSizeDropdown + fontSize={options.fontSize} + onOptionChange={setOption} + />, <InformationToggle onToggle={setOption} isToggled={options.isShowingSurahInfo} @@ -40,26 +69,10 @@ GlobalNavSurah.propTypes = { function mapStateToProps(state, ownProps) { const surahId = parseInt(ownProps.params.surahId, 10); const surah: Object = state.surahs.entities[surahId]; - const ayahs: Object = state.ayahs.entities[surahId]; - const ayahArray = ayahs ? Object.keys(ayahs).map(key => parseInt(key.split(':')[1], 10)) : []; - const ayahIds = new Set(ayahArray); - const lastAyahInArray = ayahArray.slice(-1)[0]; return { surah, - ayahs, - ayahIds, - isStarted: state.audioplayer.isStarted, - isPlaying: state.audioplayer.isPlaying, - currentAyah: state.audioplayer.currentAyah, - isAuthenticated: state.auth.loaded, - currentWord: state.ayahs.currentWord, - isEndOfSurah: lastAyahInArray === surah.ayat, surahs: state.surahs.entities, - bookmarks: state.bookmarks.entities, - isLoading: state.ayahs.loading, - isLoaded: state.ayahs.loaded, - lines: state.lines.lines, options: state.options }; } diff --git a/src/components/GlobalNav/index.js b/src/components/GlobalNav/index.js index 0b9b5620b..5b5e50bb6 100644 --- a/src/components/GlobalNav/index.js +++ b/src/components/GlobalNav/index.js @@ -1,13 +1,15 @@ /* global window */ import React, { PropTypes, Component } from 'react'; +import { connect } from 'react-redux'; +import Link from 'react-router/lib/Link'; import Navbar from 'react-bootstrap/lib/Navbar'; import Nav from 'react-bootstrap/lib/Nav'; -import SearchInput from 'components/SearchInput'; +import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; +import LocaleSwitcher from 'components/LocaleSwitcher'; import debug from 'helpers/debug'; - -const Form = Navbar.Form; +import { userType } from 'types'; const styles = require('./style.scss'); @@ -16,6 +18,13 @@ class GlobalNav extends Component { // handleToggleSidebar: PropTypes.func.isRequired, leftControls: PropTypes.arrayOf(PropTypes.element), rightControls: PropTypes.arrayOf(PropTypes.element), + handleSidebarToggle: PropTypes.func.isRequired, + isStatic: PropTypes.bool.isRequired, + user: userType + }; + + static defaultProps = { + isStatic: false }; state = { @@ -31,10 +40,11 @@ class GlobalNav extends Component { } handleNavbar = () => { + const { isStatic } = this.props; const { scrolled } = this.state; if (window.pageYOffset > 50) { - if (!scrolled) { + if (!scrolled && !isStatic) { this.setState({ scrolled: true }); } } else if (scrolled) { @@ -44,38 +54,81 @@ class GlobalNav extends Component { return false; } + renderRightControls() { + const { user, rightControls } = this.props; + + return rightControls || [ + <LocaleSwitcher />, + <li> + <a + href="https://quran.zendesk.com/hc/en-us/articles/210090626-Development-help" + target="_blank" + rel="noopener noreferrer" + data-metrics-event-name="IndexHeader:Link:Developer" + > + <LocaleFormattedMessage + id="nav.developers" + defaultMessage="Developers" + /> + </a> + </li>, + <li> + <a href="https://quran.zendesk.com/hc/en-us" data-metrics-event-name="IndexHeader:Link:Contact"> + <LocaleFormattedMessage + id="nav.contactUs" + defaultMessage="Contact Us" + /> + </a> + </li>, + user ? + <li> + <Link to="/profile" data-metrics-event-name="IndexHeader:Link:Profile"> + {user.firstName || user.name} + </Link> + </li> : + <noscript /> + ]; + } + render() { - const { leftControls, rightControls } = this.props; + const { leftControls, handleSidebarToggle, isStatic } = this.props; debug('component:GlobalNav', 'Render'); return ( <Navbar className={`montserrat ${this.state.scrolled && styles.scrolled}`} - fixedTop + fixedTop={!isStatic} fluid + static={isStatic} > - <button type="button" className="navbar-toggle collapsed"> + <button type="button" className="navbar-toggle collapsed" onClick={handleSidebarToggle}> <span className="sr-only">Toggle navigation</span> <span className="icon-bar" /> <span className="icon-bar" /> <span className="icon-bar" /> </button> - <Nav> - {leftControls && leftControls.map(control => control)} - <Form pullLeft> - <SearchInput - className="search-input" - /> - </Form> + <Nav className={styles.nav}> + <li> + <Link to="/"><i className="ss-icon ss-home" /></Link> + </li> + { + leftControls && + leftControls.map(((control, index) => React.cloneElement(control, { key: index }))) + } + </Nav> + <Nav pullRight className="hidden-xs"> + { + this.renderRightControls() + .map(((control, index) => React.cloneElement(control, { key: index }))) + } </Nav> - {rightControls && - <Nav pullRight> - {rightControls.map(control => control)} - </Nav> - } </Navbar> ); } } -export default GlobalNav; +export default connect( + state => ({ + user: state.auth.user + }) +)(GlobalNav); diff --git a/src/components/GlobalNav/style.scss b/src/components/GlobalNav/style.scss index 74e24ab3d..1fff5993b 100644 --- a/src/components/GlobalNav/style.scss +++ b/src/components/GlobalNav/style.scss @@ -1,3 +1,20 @@ +@import '../../styles/variables.scss'; + .scrolled{ box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.2); } + +.nav{ + @media(max-width: $screen-sm){ + // display: inline-block; + :global(& > li){ + display: inline-block; + } + } +} + +.optionsDropdown{ + :global(.ss-icon){ + margin-right: 5px; + } +} diff --git a/src/components/GlobalSidebar/Surah/index.js b/src/components/GlobalSidebar/Surah/index.js new file mode 100644 index 000000000..82c843399 --- /dev/null +++ b/src/components/GlobalSidebar/Surah/index.js @@ -0,0 +1,29 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; + +import { surahType } from 'types'; +import GlobalSidebar from '../index.js'; + +const GlobalSidebarSurah = ({ ...props, surah, ayahIds }) => ( + <GlobalSidebar {...props} settingsModalProps={{ surah, ayahIds }} /> +); + +GlobalSidebarSurah.propTypes = { + surah: surahType, + ayahIds: PropTypes.instanceOf(Set) +}; + +function mapStateToProps(state, ownProps) { + const surahId = parseInt(ownProps.params.surahId, 10); + const surah: Object = state.surahs.entities[surahId]; + const ayahs: Object = state.ayahs.entities[surahId]; + const ayahArray = ayahs ? Object.keys(ayahs).map(key => parseInt(key.split(':')[1], 10)) : []; + const ayahIds = new Set(ayahArray); + + return { + surah, + ayahIds + }; +} + +export default connect(mapStateToProps)(GlobalSidebarSurah); diff --git a/src/components/GlobalSidebar/index.js b/src/components/GlobalSidebar/index.js new file mode 100644 index 000000000..fab83c343 --- /dev/null +++ b/src/components/GlobalSidebar/index.js @@ -0,0 +1,116 @@ +/* global document */ +import React, { PropTypes, Component } from 'react'; +import Link from 'react-router/lib/Link'; +import Navbar from 'react-bootstrap/lib/Navbar'; +import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; +import SettingsModal from 'components/SettingsModal'; + +const styles = require('./style.scss'); + +const NavbarHeader = Navbar.Header; + +class GlobalSidebar extends Component { + static propTypes = { + open: PropTypes.bool.isRequired, + handleOpen: PropTypes.func, + settingsModalProps: PropTypes.object, // eslint-disable-line + children: PropTypes.node + }; + + static defaultProps = { + open: false + }; + + state = { + settingsModalOpen: false + } + + componentDidMount() { + document.body.addEventListener('click', this.onBodyClick.bind(this), true); + } + + componentWillUnmount() { + document.body.removeEventListener('click', this.onBodyClick.bind(this), true); + } + + onBodyClick = (event) => { + const { open, handleOpen } = this.props; + + if (open && !this.container.contains(event.target)) { + return handleOpen(false); + } + + return false; + } + + render() { + const { open, handleOpen, settingsModalProps, children } = this.props; + + return ( + <div + ref={(container) => { this.container = container; }} + className={`${styles.container} sidebar ${open && styles.open}`} + > + <Navbar static fluid> + <NavbarHeader> + <p className="navbar-text"> + <LocaleFormattedMessage id="setting.title" defaultMessage="Quran" /> + </p> + </NavbarHeader> + </Navbar> + <ul className={styles.list}> + {children} + <li> + <a tabIndex="-1" className="pointer" onClick={() => this.setState({ settingsModalOpen: true }, handleOpen(false))}> + <i className="ss-icon ss-settings vertical-align-middle" />{' '}Settings + </a> + </li> + <li> + <a href="https://quran.zendesk.com/hc/en-us" data-metrics-event-name="Sidebar:Link:Help"> + <i className="ss-icon ss-help vertical-align-middle" />{' '} + <LocaleFormattedMessage + id="nav.help" + defaultMessage="Help & feedback" + /> + </a> + </li> + <hr /> + <li> + <Link to="/apps" data-metrics-event-name="Sidebar:Link:Mobile"> + <i className="ss-icon ss-cell vertical-align-middle" />{' '} + <LocaleFormattedMessage + id="nav.mobile" + defaultMessage="Mobile" + /> + </Link> + </li> + <li> + <Link to="/donations" data-metrics-event-name="Sidebar:Link:Contribute"> + <i className="ss-icon ss-dollarsign vertical-align-middle" />{' '} + <LocaleFormattedMessage + id="nav.contribute" + defaultMessage="Contribute" + /> + </Link> + </li> + <li> + <a href="http://legacy.quran.com" data-metrics-event-name="Sidebar:Link:Legacy"> + <i className="ss-icon ss-alert vertical-align-middle" />{' '} + <LocaleFormattedMessage + id="nav.legacySite" + defaultMessage="Legacy Quran.com" + /> + </a> + </li> + </ul> + <SettingsModal + {...settingsModalProps} + open={this.state.settingsModalOpen} + handleHide={() => this.setState({ settingsModalOpen: false })} + /> + </div> + ); + } +} + +export default GlobalSidebar; diff --git a/src/components/GlobalSidebar/style.scss b/src/components/GlobalSidebar/style.scss new file mode 100644 index 000000000..abef4da9f --- /dev/null +++ b/src/components/GlobalSidebar/style.scss @@ -0,0 +1,61 @@ +@import '../../styles/variables.scss'; + +$width: 18%; + +.container{ + position: fixed; + right: 110%; + top: 0px; + bottom: 0px; + background: #fff; + z-index: 1031; + box-shadow: 0 16px 24px 2px rgba(0,0,0,0.14), 0 6px 30px 5px rgba(0,0,0,0.12), 0 8px 10px -5px rgba(0,0,0,0.2); + + background: #fff; + width: $width; + transition: right 0.35s cubic-bezier(0.24,1,0.32,1); + + &.open{ + right: 100% - $width; + } + + :global(.navbar-text){ + margin-left: 0px; + } + + @media(max-width: $screen-sm) { + width: 100% - $width; + + &.open{ + right: $width; + } + + :global(.navbar-text){ + padding-left: 15px; + } + } +} + +.list{ + padding-left: 0px; + + :global(li){ + color: #777; + + :global(a){ + color: #777; + padding: 10px 15px; + display: block; + + :global(.ss-icon){ + font-size: 18px; + margin-right: 20px; + } + + &:hover{ + background: #f5f5f5; + color: #333; + } + } + } +} diff --git a/src/components/IndexHeader/index.js b/src/components/IndexHeader/index.js index d58ffb1ce..adbb0af15 100644 --- a/src/components/IndexHeader/index.js +++ b/src/components/IndexHeader/index.js @@ -4,8 +4,6 @@ import Link from 'react-router/lib/Link'; import SearchInput from 'components/SearchInput'; import debug from 'helpers/debug'; -import Nav from './Nav'; - const logo = require('../../../static/images/logo-lg-w.png'); export default class IndexHeader extends Component { @@ -28,7 +26,6 @@ export default class IndexHeader extends Component { return ( <div className="index-header" style={{ backgroundColor: '#2CA4AB' }}> - <Nav /> <div className="container"> <div className="row"> <div className="col-md-10 col-md-offset-1 text-center"> diff --git a/src/components/InformationToggle/index.js b/src/components/InformationToggle/index.js index 8021be197..929b5cb30 100644 --- a/src/components/InformationToggle/index.js +++ b/src/components/InformationToggle/index.js @@ -1,4 +1,5 @@ import React, { PropTypes } from 'react'; +import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; const InformationToggle = ({ isToggled, onToggle }) => ( <li className={isToggled && 'active'}> @@ -8,8 +9,9 @@ const InformationToggle = ({ isToggled, onToggle }) => ( onClick={() => onToggle({ isShowingSurahInfo: !isToggled })} > <i - className="ss-icon ss-info" + className="ss-icon ss-info vertical-align-middle" /> + {' '}<LocaleFormattedMessage id="surah.info" defaultMessage="Surah Info" className="visible-xs-inline-block" /> </a> </li> ); diff --git a/src/components/LocaleFormattedMessage/index.js b/src/components/LocaleFormattedMessage/index.js index 23144529e..d2898c07f 100644 --- a/src/components/LocaleFormattedMessage/index.js +++ b/src/components/LocaleFormattedMessage/index.js @@ -1,17 +1,22 @@ import React, { PropTypes } from 'react'; import { intlShape, injectIntl, FormattedMessage } from 'react-intl'; -const LocaleFormattedMessage = ({ id, defaultMessage, intl, values }) => ( - <span className={intl.messages.local}> +const LocaleFormattedMessage = ({ id, defaultMessage, intl, values, className }) => ( + <span className={`${intl.messages.local} ${className}`}> <FormattedMessage id={id} defaultMessage={defaultMessage} values={values} /> </span> ); LocaleFormattedMessage.propTypes = { id: PropTypes.string.isRequired, + className: PropTypes.string, defaultMessage: PropTypes.string, intl: intlShape.isRequired, values: PropTypes.object // eslint-disable-line }; +LocaleFormattedMessage.defaultPropTypes = { + className: '' +}; + export default injectIntl(LocaleFormattedMessage); diff --git a/src/components/LocaleSwitcher/index.js b/src/components/LocaleSwitcher/index.js index 5c743f1f2..6995b8f45 100644 --- a/src/components/LocaleSwitcher/index.js +++ b/src/components/LocaleSwitcher/index.js @@ -1,6 +1,8 @@ /* global window */ import React, { Component } from 'react'; import cookie from 'react-cookie'; +import NavDropdown from 'react-bootstrap/lib/NavDropdown'; +import MenuItem from 'react-bootstrap/lib/MenuItem'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; import { locales, defaultLocale } from '../../config'; @@ -31,36 +33,30 @@ export default class LocaleSwitcher extends Component { window.location.reload(); } - renderLocaleLink = (locale) => { - let className = 'local-switch-link'; - if (locale === this.state.currentLocale) { - className = `btn ${className} ${className}-active`; - } + renderList() { + const keys = Object.keys(locales); - return ( - <a - key={locale} - className={className} - onClick={() => this.handleLocaleClick(locale)} - href={`?local=${locale}`} + return keys.map(key => ( + <MenuItem + key={key} + className={key === this.state.currentLocale && 'active'} // NOTE: if you use key `active` it will make all dropdown active + onClick={() => this.handleLocaleClick(key)} + href={`?local=${key}`} > - {locales[locale]} - </a> - ); + {locales[key]} + </MenuItem> + )); } render() { - const keys = Object.keys(locales); - return ( - <div className="local-switcher"> - <p> - <LocaleFormattedMessage id="local.changeLocal" defaultMessage="Choose language " /> - </p> - - {keys.map(this.renderLocaleLink, this)} - </div> + <NavDropdown + active={false} + id="site-language-dropdown" + title={<LocaleFormattedMessage id="local.siteLocale" defaultMessage="Site language " />} + > + {this.renderList()} + </NavDropdown> ); } - } diff --git a/src/components/NightModeToggle/index.js b/src/components/NightModeToggle/index.js index 0175da6e4..ffa982286 100644 --- a/src/components/NightModeToggle/index.js +++ b/src/components/NightModeToggle/index.js @@ -1,6 +1,6 @@ /* global document */ import React, { Component } from 'react'; - +import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; import bindTooltip from 'utils/bindTooltip'; class NightModeToggle extends Component { @@ -27,8 +27,9 @@ class NightModeToggle extends Component { onClick={this.toggleNightMode} > <i - className="ss-icon ss-lightbulb" + className="ss-icon ss-lightbulb vertical-align-middle" /> + {' '}<LocaleFormattedMessage id="settings.nightMode" defaultMessage="Night Mode" className="visible-xs-inline-block" /> </a> </li> ); diff --git a/src/components/Radio/index.js b/src/components/Radio/index.js new file mode 100644 index 000000000..1d94fb551 --- /dev/null +++ b/src/components/Radio/index.js @@ -0,0 +1,30 @@ +import React, { PropTypes } from 'react'; + +const styles = require('./style.scss'); + +const Radio = ({ id, name, checked, handleChange, children }) => ( + <label className={styles.radio} htmlFor={id}> + <input + id={id} + className={styles.input} + type="radio" + name={name} + checked={checked} + onChange={handleChange} + /> + <span className={styles.outer}> + <span className={styles.inner} /> + </span> + {children} + </label> +); + +Radio.propTypes = { + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + checked: PropTypes.bool.isRequired, + handleChange: PropTypes.func.isRequired, + children: PropTypes.element.isRequired, +}; + +export default Radio; diff --git a/src/components/Radio/style.scss b/src/components/Radio/style.scss new file mode 100644 index 000000000..a00b1fdee --- /dev/null +++ b/src/components/Radio/style.scss @@ -0,0 +1,130 @@ +@import '../../styles/variables.scss'; + +@-webkit-keyframes cardEnter { + 0%, 20%, 40%, 60%, 80%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + 0% { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + } + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + } + 40% { + -webkit-transform: scale3d(0.9, 0.9, 0.9); + } + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + } + 80% { + -webkit-transform: scale3d(0.97, 0.97, 0.97); + } + 100% { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + } +} + +@keyframes cardEnter { + 0%, 20%, 40%, 60%, 80%, 100% { + -webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + } + 0% { + opacity: 0; + -webkit-transform: scale3d(0.3, 0.3, 0.3); + transform: scale3d(0.3, 0.3, 0.3); + } + 20% { + -webkit-transform: scale3d(1.1, 1.1, 1.1); + transform: scale3d(1.1, 1.1, 1.1); + } + 40% { + -webkit-transform: scale3d(0.9, 0.9, 0.9); + transform: scale3d(0.9, 0.9, 0.9); + } + 60% { + opacity: 1; + -webkit-transform: scale3d(1.03, 1.03, 1.03); + transform: scale3d(1.03, 1.03, 1.03); + } + 80% { + -webkit-transform: scale3d(0.97, 0.97, 0.97); + transform: scale3d(0.97, 0.97, 0.97); + } + 100% { + opacity: 1; + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.radio { + display: inline-block; + padding-right: 20px; + font-weight: 500; + color: #777; + line-height: 40px; + cursor: pointer; +} + +.radio:hover .inner { + -webkit-transform: scale(0.5); + -ms-transform: scale(0.5); + transform: scale(0.5); + opacity: .5; +} + +.radio .input { + width: 1px; + height: 1px; + opacity: 0; +} + +.radio .input:checked + .outer .inner { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + opacity: 1; +} + +.radio .input:checked + .outer { + border: 2px solid $brand-primary; +} + +.radio .input:focus + .outer .inner { + -webkit-transform: scale(1); + -ms-transform: scale(1); + transform: scale(1); + opacity: 1; + background-color: $brand-primary; +} + +.radio .outer { + width: 20px; + height: 20px; + display: block; + float: left; + margin: 10px 9px 10px 10px; + border: 2px solid $brand-primary; + border-radius: 50%; + background-color: #fff; +} + +.radio .inner { + -webkit-transition: all 0.25s ease-in-out; + transition: all 0.25s ease-in-out; + width: 10px; + height: 10px; + -webkit-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); + display: block; + margin: 3px; + border-radius: 50%; + background-color: $brand-primary; + opacity: 0; +} diff --git a/src/components/ReadingModeToggle/index.js b/src/components/ReadingModeToggle/index.js index 6e236c004..ff75185c0 100644 --- a/src/components/ReadingModeToggle/index.js +++ b/src/components/ReadingModeToggle/index.js @@ -1,4 +1,5 @@ import React, { PropTypes } from 'react'; +import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; const ReadingModeToggle = ({ onToggle, isToggled }) => ( <li className={isToggled && 'active'}> @@ -8,8 +9,9 @@ const ReadingModeToggle = ({ onToggle, isToggled }) => ( onClick={() => onToggle({ isReadingMode: !isToggled })} > <i - className="ss-icon ss-openbook" + className="ss-icon ss-openbook vertical-align-middle" /> + {' '}<LocaleFormattedMessage id="settings.reading" defaultMessage="Reading" className="visible-xs-inline-block" /> </a> </li> ); diff --git a/src/components/ReciterDropdown/index.js b/src/components/ReciterDropdown/index.js index 083377feb..3f11060ff 100644 --- a/src/components/ReciterDropdown/index.js +++ b/src/components/ReciterDropdown/index.js @@ -1,9 +1,8 @@ import React, { Component, PropTypes } from 'react'; +import ButtonToolbar from 'react-bootstrap/lib/ButtonToolbar'; import DropdownButton from 'react-bootstrap/lib/DropdownButton'; import MenuItem from 'react-bootstrap/lib/MenuItem'; -import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; - const style = require('./style.scss'); // To save API calls. @@ -197,11 +196,6 @@ export default class ReciterDropdown extends Component { className: PropTypes.string }; - static defaultProps = { - className: 'col-md-3' - }; - - renderMenu() { const { audio, onOptionChange } = this.props; @@ -217,15 +211,19 @@ export default class ReciterDropdown extends Component { } render() { - const { className } = this.props; + const { className, audio } = this.props; return ( - <DropdownButton - className={`${className} ${style.dropdown}`} - title={<LocaleFormattedMessage id="setting.reciters" defaultMessage="Reciters" />} - > - {this.renderMenu()} - </DropdownButton> + <ButtonToolbar> + <DropdownButton + block + id="reciter-dropdown" + className={`${className} ${style.dropdown}`} + title={slugs.find(slug => slug.id === audio).name.english} + > + {this.renderMenu()} + </DropdownButton> + </ButtonToolbar> ); } } diff --git a/src/components/SettingsModal/index.js b/src/components/SettingsModal/index.js new file mode 100644 index 000000000..b42242e62 --- /dev/null +++ b/src/components/SettingsModal/index.js @@ -0,0 +1,86 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import Modal from 'react-bootstrap/lib/Modal'; + +import ReciterDropdown from 'components/ReciterDropdown'; +import ContentDropdown from 'components/ContentDropdown'; +import TooltipDropdown from 'components/TooltipDropdown'; + +import { setOption } from 'redux/actions/options.js'; +import { load } from 'redux/actions/ayahs.js'; +import { optionsType, surahType } from 'types'; + +const ModalHeader = Modal.Header; +const ModalTitle = Modal.Title; +const ModalBody = Modal.Body; + +const SettingsModal = ({ + surah, + ayahIds, + open, + handleHide, + options, + setOption, // eslint-disable-line no-shadow + load // eslint-disable-line no-shadow +}) => { + const handleOptionChange = (payload) => { + setOption(payload); + + if (surah) { + const first = [...ayahIds][0]; + const last = [...ayahIds][[...ayahIds].length - 1]; + load(surah.id, first, last, { ...options, ...payload }); + } + }; + + return ( + <Modal show={open} onHide={handleHide}> + <ModalHeader closeButton> + <ModalTitle className="montserrat"> + Settings + </ModalTitle> + </ModalHeader> + <ModalBody> + <div className="form-group"> + <h5 className="text-black">Reciters</h5> + <ReciterDropdown + onOptionChange={handleOptionChange} + audio={options.audio} + /> + </div> + <div className="form-group"> + <h5 className="text-black">Translations</h5> + <ContentDropdown + onOptionChange={handleOptionChange} + content={options.content} + /> + </div> + <div className="form-group"> + <h5 className="text-black">Tooltip Content</h5> + <TooltipDropdown + tooltip={options.tooltip} + onOptionChange={setOption} + /> + </div> + </ModalBody> + </Modal> + ); +}; + +SettingsModal.propTypes = { + surah: surahType, + ayahIds: PropTypes.instanceOf(Set), + open: PropTypes.bool, + handleHide: PropTypes.func.isRequired, + options: optionsType, + setOption: PropTypes.func.isRequired, + load: PropTypes.func.isRequired, +}; + +SettingsModal.defaultProps = { + open: false +}; + +export default connect(state => ({ + options: state.options +}), { setOption, load })(SettingsModal); diff --git a/src/components/Sidebar/index.js b/src/components/Sidebar/index.js deleted file mode 100644 index c7aba296f..000000000 --- a/src/components/Sidebar/index.js +++ /dev/null @@ -1,50 +0,0 @@ -/* global document */ -import React, { PropTypes, Component } from 'react'; - -const styles = require('./style.scss'); - -class Sidebar extends Component { - static propTypes = { - open: PropTypes.bool.isRequired, - onSetOpen: PropTypes.func.isRequired, - children: PropTypes.node - }; - - static defaultProps = { - open: false - }; - - componentWillReceiveProps(nextProps) { - if (__CLIENT__ && nextProps.open) { - document.body.removeEventListener('click', this.onBodyClick.bind(this), true); - document.body.addEventListener('click', this.onBodyClick.bind(this), true); - } - - return false; - } - - onBodyClick = (event) => { - const { onSetOpen } = this.props; - - if (!this.container.contains(event.target)) { - return onSetOpen(); - } - - return false; - } - - render() { - const { open, children } = this.props; - - return ( - <div - ref={(container) => { this.container = container; }} - className={`${styles.container} sidebar ${open && styles.open}`} - > - {children} - </div> - ); - } -} - -export default Sidebar; diff --git a/src/components/Sidebar/style.scss b/src/components/Sidebar/style.scss deleted file mode 100644 index 4ee05e3c2..000000000 --- a/src/components/Sidebar/style.scss +++ /dev/null @@ -1,38 +0,0 @@ -@import '../../styles/variables.scss'; - -$width: 20%; - -.container{ - position: fixed; - right: 100%; - top: 0px; - bottom: 0px; - background: #fff; - z-index: 10; - box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.15); - - background: #fff; - width: $width; - transition: right 0.3s; - - - &.open{ - right: 100% - $width; - } - - :global(.navbar-text){ - margin-left: 0px; - } - - @media(max-width: $screen-sm) { - width: 100% - $width; - - &.open{ - right: $width; - } - - :global(.navbar-text){ - padding-left: 15px; - } - } -} diff --git a/src/components/TooltipDropdown/index.js b/src/components/TooltipDropdown/index.js index 7b4b059d6..91ba5a59f 100644 --- a/src/components/TooltipDropdown/index.js +++ b/src/components/TooltipDropdown/index.js @@ -1,52 +1,29 @@ -import React, { Component, PropTypes } from 'react'; - -import DropdownButton from 'react-bootstrap/lib/DropdownButton'; -import MenuItem from 'react-bootstrap/lib/MenuItem'; +import React, { PropTypes } from 'react'; +import Radio from 'components/Radio'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; -export default class TooltipDropdown extends Component { - static propTypes = { - onOptionChange: PropTypes.func, - tooltip: PropTypes.string.isRequired, - className: PropTypes.string.isRequired - } - - handleOptionSelected = (type) => { - const { onOptionChange } = this.props; - - return onOptionChange({ - tooltip: type - }); - } - - renderList() { - const { tooltip } = this.props; - - return ['translation', 'transliteration'].map(type => ( - <MenuItem - onClick={() => this.handleOptionSelected(type)} - active={tooltip === type} - key={type} - > - <i className={`fa fa-check ${tooltip !== type && 'invisible'}`} />{' '} - <LocaleFormattedMessage id={`setting.tooltip.${type}`} defaultMessage={type.toUpperCase()} /> - </MenuItem> - )); - } - - render() { - const { className } = this.props; - - return ( - <DropdownButton - link - className={className} - id="tooltip-dropdown" - title={<LocaleFormattedMessage id="setting.tooltip" defaultMessage="Tooltip content" />} - > - {this.renderList()} - </DropdownButton> - ); - } -} +const TooltipDropdown = ({ tooltip, onOptionChange }) => { + const handleOptionChange = type => onOptionChange({ + tooltip: type + }); + + const list = ['translation', 'transliteration'].map(type => ( + <Radio key={type} id={type} name="type" checked={type === tooltip} handleChange={() => handleOptionChange(type)}> + <LocaleFormattedMessage id={`setting.tooltip.${type}`} defaultMessage={type.toUpperCase()} /> + </Radio> + )); + + return ( + <div> + {list} + </div> + ); +}; + +TooltipDropdown.propTypes = { + onOptionChange: PropTypes.func, + tooltip: PropTypes.string.isRequired, +}; + +export default TooltipDropdown; diff --git a/src/containers/App/index.js b/src/containers/App/index.js index f7a9026db..5bad50ffc 100644 --- a/src/containers/App/index.js +++ b/src/containers/App/index.js @@ -7,6 +7,7 @@ import Helmet from 'react-helmet'; import Modal from 'react-bootstrap/lib/Modal'; import SmartBanner from 'components/SmartBanner'; import GlobalNav from 'components/GlobalNav'; +import GlobalSidebar from 'components/GlobalSidebar'; import Col from 'react-bootstrap/lib/Col'; import debug from 'helpers/debug'; @@ -23,7 +24,6 @@ const ModalHeader = Modal.Header; const ModalTitle = Modal.Title; const ModalBody = Modal.Body; - class App extends Component { static propTypes = { media: PropTypes.shape({ @@ -33,14 +33,26 @@ class App extends Component { children: PropTypes.element, main: PropTypes.element, nav: PropTypes.element, + sidebar: PropTypes.element, }; static contextTypes = { store: PropTypes.object.isRequired }; + state = { + sidebarOpen: false + } + render() { - const { main, nav, children, media, removeMedia } = this.props; // eslint-disable-line no-shadow + const { + main, + nav, + sidebar, + children, + media, + removeMedia // eslint-disable-line no-shadow + } = this.props; debug('component:APPLICATION', 'Render'); return ( @@ -61,7 +73,23 @@ class App extends Component { </Col> </div> </NoScript> - {nav || <GlobalNav />} + { + React.cloneElement( + nav || <GlobalNav isStatic />, + { + handleSidebarToggle: () => this.setState({ sidebarOpen: !this.state.sidebarOpen }) + } + ) + } + { + React.cloneElement( + sidebar || <GlobalSidebar />, + { + open: this.state.sidebarOpen, + handleOpen: open => this.setState({ sidebarOpen: open }) + } + ) + } {children || main} <SmartBanner title="The Noble Quran - القرآن الكريم" button="Install" /> <Footer /> diff --git a/src/containers/Search/index.js b/src/containers/Search/index.js index acfede5fe..a2162989d 100644 --- a/src/containers/Search/index.js +++ b/src/containers/Search/index.js @@ -86,7 +86,7 @@ class Search extends Component { values={values} /> </Col> - <Col className="text-right"> + <Col md={6} className="text-right"> <ReactPaginate previousLabel={ <span aria-hidden="true"> @@ -98,15 +98,16 @@ class Search extends Component { <i className="ss-icon ss-directright" /> </span> } - breakLabel={<li className="break"><a href="">...</a></li>} + breakLabel={<a href="">...</a>} pageNum={pageNum} marginPagesDisplayed={2} pageRangeDisplayed={5} initialSelected={page - 1} forceSelected={page - 1} - clickCallback={this.handlePageChange} + onPageChange={this.handlePageChange} containerClassName="pagination" subContainerClassName="pages pagination" + pageLinkClassName="pointer:" activeClass={style.active} /> </Col> diff --git a/src/containers/Search/style.scss b/src/containers/Search/style.scss index 3d129a3bd..223ef23c0 100644 --- a/src/containers/Search/style.scss +++ b/src/containers/Search/style.scss @@ -1,6 +1,6 @@ @import '../../styles/variables.scss'; -:local .header{ +.header{ background-color: #E7E6E6; min-height: 50px; padding: 15px 0px; diff --git a/src/containers/Surah/index.js b/src/containers/Surah/index.js index 7dfbb1b4a..688ee107f 100644 --- a/src/containers/Surah/index.js +++ b/src/containers/Surah/index.js @@ -9,34 +9,21 @@ import { push } from 'react-router-redux'; // bootstrap import Col from 'react-bootstrap/lib/Col'; -import Navbar from 'react-bootstrap/lib/Navbar'; import Helmet from 'react-helmet'; -import Sidebar from 'components/Sidebar'; // components import Loader from 'components/Loader'; import LazyLoad from 'components/LazyLoad'; import PageBreak from 'components/PageBreak'; import Audioplayer from 'components/Audioplayer'; -import ContentDropdown from 'components/ContentDropdown'; -import ReciterDropdown from 'components/ReciterDropdown'; -import SurahsDropdown from 'components/SurahsDropdown'; -import VersesDropdown from 'components/VersesDropdown'; import SurahInfo from 'components/SurahInfo'; import Ayah from 'components/Ayah'; import Line from 'components/Line'; -import SearchInput from 'components/SearchInput'; import Bismillah from 'components/Bismillah'; import TopOptions from 'components/TopOptions'; -import ReadingModeToggle from 'components/ReadingModeToggle'; -import NightModeToggle from 'components/NightModeToggle'; -import TooltipDropdown from 'components/TooltipDropdown'; -import FontSizeDropdown from 'components/FontSizeDropdown'; -import InformationToggle from 'components/InformationToggle'; import LocaleFormattedMessage from 'components/LocaleFormattedMessage'; - // utils import scroller from 'utils/scroller'; @@ -54,9 +41,6 @@ import * as MediaActions from 'redux/actions/media.js'; import { surahsConnect, surahInfoConnect, ayahsConnect } from './connect'; - -const NavbarHeader = Navbar.Header; - const style = require('./style.scss'); class Surah extends Component { @@ -136,16 +120,6 @@ class Surah extends Component { return Object.keys(this.props.ayahs).length; } - handleOptionChange = (payload) => { - const { surah, options, actions } = this.props; // eslint-disable-line no-shadow, max-len - const from = this.getFirst(); - const to = this.getLast(); - - actions.options.setOption(payload); - - return actions.ayah.load(surah.id, from, to, Object.assign({}, options, payload)); - } - handleVerseDropdownClick = (ayahNum) => { const { ayahIds, surah, actions } = this.props; // eslint-disable-line no-shadow @@ -355,73 +329,6 @@ class Surah extends Component { }); } - renderSidebar() { - const { surah, surahs, ayahIds, options, actions } = this.props; - - return ( - <div> - <Navbar static fluid> - <NavbarHeader> - <p className={`navbar-text ${style.sidebarTitle}`}> - <LocaleFormattedMessage id="setting.title" defaultMessage="Options" /> - </p> - </NavbarHeader> - </Navbar> - <SearchInput - className="search-input" - /> - <SurahsDropdown - surahs={surahs} - className={style.dropdown} - /> - <VersesDropdown - ayat={surah.ayat} - loadedAyahs={ayahIds} - isReadingMode={options.isReadingMode} - onClick={this.handleVerseDropdownClick} - surah={surah} - className={style.dropdown} - /> - <ReciterDropdown - onOptionChange={this.handleOptionChange} - audio={options.audio} - className={style.dropdown} - /> - <ContentDropdown - onOptionChange={this.handleOptionChange} - content={options.content} - className={style.dropdown} - /> - <TooltipDropdown - tooltip={options.tooltip} - onOptionChange={actions.options.setOption} - className={style.dropdown} - /> - <div className={style.sidebarItem}> - <FontSizeDropdown - fontSize={options.fontSize} - onOptionChange={actions.options.setOption} - /> - </div> - <div className={style.sidebarItem}> - <InformationToggle - onToggle={actions.options.setOption} - isShowingSurahInfo={options.isShowingSurahInfo} - /> - </div> - <div className={style.sidebarItem}> - <ReadingModeToggle - isToggled={options.isReadingMode} - onReadingModeToggle={actions.options.toggleReadingMode} - /> - </div> - <div className={style.sidebarItem}> - <NightModeToggle /> - </div> - </div> - ); - } - render() { const { surah, options, actions } = this.props; // eslint-disable-line no-shadow debug('component:Surah', 'Render'); @@ -468,12 +375,6 @@ class Surah extends Component { } ]} /> - <Sidebar - open={this.state.sidebarOpen} - onSetOpen={open => this.setState({ sidebarOpen: open })} - > - {this.renderSidebar()} - </Sidebar> <div className={`container-fluid ${style.container}`}> <div className="row"> <SurahInfo diff --git a/src/containers/Surah/style.scss b/src/containers/Surah/style.scss index 209876a51..6cf886556 100644 --- a/src/containers/Surah/style.scss +++ b/src/containers/Surah/style.scss @@ -29,7 +29,7 @@ .container { padding-top: 70px; - min-height: 80vh; + min-height: 100vh; @media(max-width: $screen-xs-max) { padding-top: 70px; diff --git a/src/locale/ar.js b/src/locale/ar.js index 069f26a42..6f06ca92c 100644 --- a/src/locale/ar.js +++ b/src/locale/ar.js @@ -2,9 +2,11 @@ export default { messages: { local: 'arabic', + 'local.siteLocale': 'اختر لغة الموقع', 'local.changeLocal': 'اختر اللغة التي تود عرض الموقع بها', 'local.navtiveName': 'العربية', 'local.selectLabel': 'اللغة', + 'setting.options': 'خيارات', 'setting.title': 'خيارات', 'setting.surahs': 'السورة', 'setting.verses': 'الذهاب إلى الآية', @@ -65,6 +67,7 @@ export default { 'nav.usefulSites': 'مواقع مفيدة', 'nav.otherLinks': 'روابط اخرى', 'nav.contactUs': 'اتصل بنا', + 'nav.help': 'مساعدة وتعليقات', 'nav.aboutQuranProject': 'Quran.com المعروف أيضا باسم القرآن الكريم، قناة القرآن الكريم، القرآن الكريم، القرآن الكريم) هو مشروع خيري)', // eslint-disable-line max-len 'nav.mobile': 'التليفون المحمول', 'nav.navigate': 'انتقل', diff --git a/src/locale/en.js b/src/locale/en.js index 6f248f471..35f215564 100644 --- a/src/locale/en.js +++ b/src/locale/en.js @@ -2,10 +2,12 @@ export default { messages: { local: 'english', + 'local.siteLocale': 'Site Language', 'local.changeLocal': 'Choose the language you want the site to display with', 'local.navtiveName': 'English', 'local.selectLabel': 'Language', - 'setting.title': 'Options', + 'setting.options': 'Options', + 'setting.title': 'Quran', 'setting.surahs': 'Surahs', 'setting.verses': 'Go to verse', 'setting.reciters': 'Reciters', @@ -65,6 +67,7 @@ export default { 'nav.usefulSites': 'Useful sites', 'nav.otherLinks': 'Other links', 'nav.contactUs': 'Contact us', + 'nav.help': 'Help & feedback', 'nav.aboutQuranProject': 'Quran.com (also known as The Noble Quran, Al Quran, Holy Quran, Koran) is a pro bono project.', // eslint-disable-line max-len 'nav.mobile': 'Mobile', 'nav.navigate': 'Navigate', diff --git a/src/locale/ur.js b/src/locale/ur.js index 8b76a3f4e..2fcf4f4a4 100644 --- a/src/locale/ur.js +++ b/src/locale/ur.js @@ -2,9 +2,11 @@ export default { messages: { local: 'urdu', + 'local.siteLocale': 'سائٹ کی زبان', 'local.changeLocal': 'جس زبان میں سایٹ دیکہنا چاہتے ہیں اس زبان کا انتخاب کریں', 'local.navtiveName': 'اردو', 'local.selectLabel': 'زبان', + 'setting.options': 'اختیارات', 'setting.title': 'اختیارات', 'setting.surahs': 'سورة', 'setting.verses': 'آیت منتخب کریں', @@ -65,6 +67,7 @@ export default { 'nav.usefulSites': 'مفید سائٹس', 'nav.otherLinks': 'دیگر لنکس', 'nav.contactUs': 'ہم سے رابطہ', + 'nav.help': 'مدد اور تاثرات', 'nav.aboutQuranProject': 'قرآن کریم ایک فلاں عامہ (عوامی بہبود کے لئے) منصوبہ ہے', 'nav.mobile': 'موبائل', 'nav.navigate': 'تشریف لے جائیں', diff --git a/src/redux/actions/search.js b/src/redux/actions/search.js index 9333c3034..c9b80601e 100644 --- a/src/redux/actions/search.js +++ b/src/redux/actions/search.js @@ -11,7 +11,7 @@ export function search(params) { types: [SEARCH, SEARCH_SUCCESS, SEARCH_FAIL], schema: { results: [{ ayah: ayahsSchema }] }, // TODO: We are doing this because of a weird obj.hasOwnProperty method missing on `params` - promise: client => client.get('/v2/search', { params: { q: params.q } }), + promise: client => client.get('/v2/search', { params: { q: params.q, p: params.p } }), params }; } diff --git a/src/routes.js b/src/routes.js index ba681d3ed..00856a662 100644 --- a/src/routes.js +++ b/src/routes.js @@ -4,6 +4,7 @@ import IndexRoute from 'react-router/lib/IndexRoute'; import Route from 'react-router/lib/Route'; import Redirect from 'react-router/lib/Redirect'; import GlobalNavSurah from 'components/GlobalNav/Surah'; +import GlobalSidebarSurah from 'components/GlobalSidebar/Surah'; import { isLoaded as isAuthLoaded, load as loadAuth, hasAccessToken } from 'redux/actions/auth'; @@ -65,7 +66,11 @@ export default (store) => { <Route path="/:surahId(/:range)" - getComponents={(nextState, cb) => System.import('./containers/Surah').then(module => cb(null, { main: module, nav: GlobalNavSurah })).catch(err => console.trace(err))} + getComponents={(nextState, cb) => + System.import('./containers/Surah') + .then(module => cb(null, { main: module, nav: GlobalNavSurah, sidebar: GlobalSidebarSurah })) + .catch(err => console.trace(err)) + } onEnter={checkValidSurah} /> </Route> diff --git a/src/styles/main.scss b/src/styles/main.scss index 159913400..3e2cae34d 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss @@ -133,6 +133,9 @@ li em{ .pointer{ cursor: pointer; } +.vertical-align-middle{ + vertical-align: middle; +} .no-outline{ outline: none !important; @@ -143,6 +146,12 @@ li em{ .text-color{ color: $text-color; } +.text-black{ + color: #333; +} +.text-black-light{ + color: #777; +} .ss-icon.text-align{ vertical-align: middle; @@ -162,7 +171,6 @@ li em{ .navbar-toggle{ display: inline-block !important; float: left; - margin-left: 15px; margin-right: 0px; } @@ -178,3 +186,20 @@ li em{ direction: rtl; font-family: 'Nafees'; } + + +// NOTE: This is for dropdown in navbar on mobile +@media(max-width: $screen-sm){ + .navbar-nav .open .dropdown-menu{ + position: absolute;; + float: left; + width: auto; + margin-top: 0px; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.15); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + } + .navbar-nav .open .dropdown-menu > li > a, .navbar-nav .open .dropdown-menu .dropdown-header{ + padding: 5px 15px; + } +} diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 5808c5b71..376198682 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -28,6 +28,7 @@ $text-muted: #d1d0d0; $text-color: #939598; $navbar-height: 50px; +$navbar-margin-bottom: 0px; $navbar-default-bg: #fff; $navbar-default-link-active-bg: transparent; $navbar-default-link-active-color: $brand-primary; diff --git a/static/fonts/ss-standard/documentation.html b/static/fonts/ss-standard/documentation.html index 9dd55e58c..1d4a8db63 100644 --- a/static/fonts/ss-standard/documentation.html +++ b/static/fonts/ss-standard/documentation.html @@ -3,7 +3,7 @@ <head> <meta charset='UTF-8' /> <title>SS Standard v1.005 - +