Skip to content

Commit

Permalink
Merge pull request #459 from MetaPhase-Consulting/feature/dark-mode
Browse files Browse the repository at this point in the history
dark mode
  • Loading branch information
burgwyn committed Oct 15, 2019
2 parents 4f4f409 + 57dd486 commit 7c4afca
Show file tree
Hide file tree
Showing 23 changed files with 258 additions and 10 deletions.
7 changes: 7 additions & 0 deletions config/jest/setupTests.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import chalk from 'chalk';
Object.entries = require('object.entries'); // because jest doesn't import babel
import config from '../../public/config/config.json';

// Override console.error() for invalid or failed propTypes by throwing an Error
Expand Down Expand Up @@ -30,6 +31,12 @@ global.window.URL.createObjectURL = () => {};
// Stub msSaveBlob
global.window.navigator.msSaveBlob = () => {};

global.MutationObserver = class {
constructor(callback) {}
disconnect() {}
observe(element, initObject) {}
}

beforeEach(() => {
// mock sessionStorage - feature flags config
sessionStorage.setItem('config', JSON.stringify(config));
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"body-parser": "^1.18.2",
"bunyan": "^1.8.12",
"css-box-shadow": "^1.0.0-3",
"darkreader": "^4.7.15",
"date-fns": "^1.29.0",
"draft-js": "^0.10.4",
"draft-js-plugins-editor": "^2.1.1",
Expand Down Expand Up @@ -104,6 +105,7 @@
"node-mocks-http": "^1.7.6",
"node-sass": "^4.7.2",
"object-assign": "4.1.1",
"object.entries": "^1.1.0",
"pa11y-ci": "^1.3.1",
"postcss-flexbugs-fixes": "4.1.0",
"postcss-loader": "3.0.0",
Expand Down
1 change: 1 addition & 0 deletions public/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"complete_bidding": true,
"data_sync_admin": true,
"available_positions": false,
"personalization": true,
"persona_auth": true
}
}
1 change: 1 addition & 0 deletions public/config/config_dev.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"complete_bidding": true,
"data_sync_admin": true,
"available_positions": false,
"personalization": true,
"persona_auth": true
}
}
8 changes: 8 additions & 0 deletions src/Components/AccountDropdown/AccountDropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdow
import PropTypes from 'prop-types';
import { get, compact, values } from 'lodash';
import Avatar from '../Avatar';
import DarkModeToggle from './DarkModeToggle';
import { EMPTY_FUNCTION, USER_PROFILE } from '../../Constants/PropTypes';
import { checkFlag } from '../../flags';

const getUseDarkMode = () => checkFlag('flags.personalization');

export class AccountDropdown extends Component {

Expand Down Expand Up @@ -65,6 +69,10 @@ export class AccountDropdown extends Component {
<strong>{displayName}</strong>
</div>
<Link className="account-dropdown--identity account-dropdown--segment account-dropdown-link" to="/profile/dashboard" onClick={this.hideDropdown}>Dashboard</Link>
{
getUseDarkMode() &&
<DarkModeToggle className="unstyled-button account-dropdown--identity account-dropdown--segment account-dropdown-link account-dropdown-link--button" />
}
<Link className="account-dropdown--identity account-dropdown--segment account-dropdown-link" to="/logout" onClick={this.logout}>Logout</Link>
</DropdownContent>
</div>
Expand Down
52 changes: 52 additions & 0 deletions src/Components/AccountDropdown/DarkModeToggle.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { setDarkModePreference } from '../../actions/preferences';
import { EMPTY_FUNCTION } from '../../Constants/PropTypes';

class DarkModeToggle extends Component {

constructor(props) {
super(props);
this.onClickUnset = this.onClickUnset.bind(this);
this.onClickSet = this.onClickSet.bind(this);
}

onClickUnset() {
this.props.set(false);
}

onClickSet() {
this.props.set(true);
}

render() {
const { isDarkMode, set, ...rest } = this.props;
return (
isDarkMode ?
<button className="unstyled-button" onClick={this.onClickUnset} {...rest}>Disable Dark Mode</button>
:
<button className="unstyled-button" onClick={this.onClickSet} {...rest}>Enable Dark Mode</button>
);
}
}

DarkModeToggle.propTypes = {
isDarkMode: PropTypes.bool,
set: PropTypes.func,
};

DarkModeToggle.defaultProps = {
isDarkMode: false,
set: EMPTY_FUNCTION,
};

const mapStateToProps = state => ({
isDarkMode: state.darkModePreference,
});

export const mapDispatchToProps = dispatch => ({
set: bool => dispatch(setDarkModePreference(bool)),
});

export default connect(mapStateToProps, mapDispatchToProps)(DarkModeToggle);
51 changes: 51 additions & 0 deletions src/Components/AccountDropdown/DarkModeToggle.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { shallow } from 'enzyme';
import { testDispatchFunctions } from '../../testUtilities/testUtilities';
import DarkModeToggle, { mapDispatchToProps } from './DarkModeToggle';

describe('DarkModeToggle', () => {
const props = {
isDarkMode: false,
set: () => {},
};

it('is defined', () => {
const wrapper = shallow(
<DarkModeToggle.WrappedComponent {...props} />,
);
expect(wrapper).toBeDefined();
});

it('it displays "Disable" when isDarkMode === true', () => {
const wrapper = shallow(
<DarkModeToggle.WrappedComponent {...props} isDarkMode />,
);
expect(wrapper.find('button').text()).toBe('Disable Dark Mode');
});

it('it displays "Enable" when isDarkMode !== true', () => {
const wrapper = shallow(
<DarkModeToggle.WrappedComponent {...props} />,
);
expect(wrapper.find('button').text()).toBe('Enable Dark Mode');
});

it('calls the set function', () => {
let val = true;
const onClick = v => val = v; // eslint-disable-line
const wrapper = shallow(
<DarkModeToggle.WrappedComponent {...props} isDarkMode set={onClick} />,
);
const click = () => wrapper.find('button').simulate('click');
click();
expect(val).toBe(false);
wrapper.setProps({ isDarkMode: false });
wrapper.update();
click();
expect(val).toBe(true);
});
});

describe('mapDispatchToProps', () => {
testDispatchFunctions(mapDispatchToProps, { bool: [true] });
});
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ exports[`AccountDropdown matches snapshot 1`] = `
>
Dashboard
</Link>
<Connect(DarkModeToggle)
className="unstyled-button account-dropdown--identity account-dropdown--segment account-dropdown-link account-dropdown-link--button"
/>
<Link
className="account-dropdown--identity account-dropdown--segment account-dropdown-link"
onClick={[Function]}
Expand Down Expand Up @@ -111,6 +114,9 @@ exports[`AccountDropdown matches snapshot when shouldDisplayName is true 1`] = `
>
Dashboard
</Link>
<Connect(DarkModeToggle)
className="unstyled-button account-dropdown--identity account-dropdown--segment account-dropdown-link account-dropdown-link--button"
/>
<Link
className="account-dropdown--identity account-dropdown--segment account-dropdown-link"
onClick={[Function]}
Expand Down
55 changes: 55 additions & 0 deletions src/Containers/DarkMode/DarkMode.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
enable as enableDarkMode,
disable as disableDarkMode,
} from 'darkreader';
import { checkFlag } from '../../flags';

const getUseDarkMode = () => checkFlag('flags.personalization');

const setMode = (value) => {
if (!value || !getUseDarkMode()) {
disableDarkMode();
} else {
enableDarkMode({
brightness: 100,
contrast: 90,
sepia: 10,
});
}
};

class DarkMode extends Component {

componentWillMount() {
const { isDarkMode } = this.props;
setMode(isDarkMode);
}

componentWillReceiveProps(nextProps) {
const { isDarkMode } = nextProps;
setMode(isDarkMode);
}

render() {
return (
<div />
);
}
}

DarkMode.propTypes = {
isDarkMode: PropTypes.bool,
};

DarkMode.defaultProps = {
isDarkMode: false,
};

const mapStateToProps = state => ({
isDarkMode: state.darkModePreference,
});

export default connect(mapStateToProps)(DarkMode);
16 changes: 16 additions & 0 deletions src/Containers/DarkMode/DarkMode.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { shallow } from 'enzyme';
import DarkMode from './DarkMode';

describe('DarkMode', () => {
const props = {
isDarkMode: true,
};

it('is defined', () => {
const wrapper = shallow(
<DarkMode.WrappedComponent {...props} />,
);
expect(wrapper).toBeDefined();
});
});
1 change: 1 addition & 0 deletions src/Containers/DarkMode/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './DarkMode';
2 changes: 2 additions & 0 deletions src/Containers/Main/Main.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Header from '../../Components/Header/Header';
import Footer from '../../Components/Footer/Footer';
import Glossary from '../../Containers/Glossary';
import AuthorizedWrapper from '../../Containers/AuthorizedWrapper';
import DarkMode from '../../Containers/DarkMode';
import checkIndexAuthentication from '../../lib/check-auth';
import { store, history } from '../../store';
import PageMeta from '../../Containers/PageMeta';
Expand All @@ -24,6 +25,7 @@ const Main = props => (
<ConnectedRouter history={history}>
<ScrollContext>
<div>
<DarkMode />
<PageMeta history={history} />
<Header {...props} isAuthorized={isAuthorized} />
<main id="main-content">
Expand Down
2 changes: 1 addition & 1 deletion src/Containers/PreferenceWrapper/PreferenceWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { Component } from 'react';
import { get } from 'lodash';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { setSortPreference } from '../../actions/sortPreferences';
import { setSortPreference } from '../../actions/preferences';

export class PreferenceWrapper extends Component {
constructor(props) {
Expand Down
13 changes: 13 additions & 0 deletions src/actions/sortPreferences.js → src/actions/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,21 @@ export function sortPreference(key, value) {
};
}

export function darkModePreference(value) {
return {
type: 'SET_DARK_MODE_PREFERENCE',
value,
};
}

export function setSortPreference(key, value) {
return (dispatch) => {
dispatch(sortPreference(key, value));
};
}

export function setDarkModePreference(value) {
return (dispatch) => {
dispatch(darkModePreference(value));
};
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { setupAsyncMocks } from '../testUtilities/testUtilities';
import * as actions from './sortPreferences';
import * as actions from './preferences';

const { mockStore } = setupAsyncMocks();

Expand Down
4 changes: 2 additions & 2 deletions src/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import feedback from './feedback';
import features from './features';
import toast from './toast';
import clientView from './clientView';
import sortPreferences from './sortPreferences';
import preferences from './preferences';
import aboutContent from './aboutContent';
import homeBannerContent from './homeBannerContent';
import logs from './logs';
Expand Down Expand Up @@ -78,7 +78,7 @@ export default combineReducers({
...features,
...toast,
...clientView,
...sortPreferences,
...preferences,
...aboutContent,
...homeBannerContent,
...logs,
Expand Down
3 changes: 3 additions & 0 deletions src/reducers/preferences/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import preferences, { darkModePreference } from './preferences';

export default { preferences, darkModePreference };
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,13 @@ export default function sortPreferences(state = SORT_PREFERENCES_WITHOUT_OPTIONS
return state;
}
}

export function darkModePreference(state = false, action) {
switch (action.type) {
case 'SET_DARK_MODE_PREFERENCE': {
return action.value;
}
default:
return state;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import sortPreferences from './sortPreferences';
import preferences from './preferences';
import { POSITION_SEARCH_SORTS_TYPE } from '../../Constants/Sort';

describe('reducers', () => {
it('can set reducer SET_SORT_PREFERENCE', () => {
expect(sortPreferences({}, { type: 'SET_SORT_PREFERENCE', key: POSITION_SEARCH_SORTS_TYPE, value: 'title' }))
expect(preferences({}, { type: 'SET_SORT_PREFERENCE', key: POSITION_SEARCH_SORTS_TYPE, value: 'title' }))
.toBeDefined();
});
});
3 changes: 0 additions & 3 deletions src/reducers/sortPreferences/index.js

This file was deleted.

4 changes: 4 additions & 0 deletions src/sass/_dropdown.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
width: 100%;
}

.account-dropdown-link--button {
font-weight: bold;
}

.account-dropdown-link:hover,
.account-dropdown-link:focus {
background: $blue-primary;
Expand Down
2 changes: 1 addition & 1 deletion src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import IndexSagas from './index-sagas';
const persistConfig = {
key: 'root',
storage,
whitelist: ['sortPreferences'], // only persist sortPreferences reducer
whitelist: ['preferences', 'darkModePreference'], // only persist some reducers
};

// Setup the middleware to watch between the Reducers and the Actions
Expand Down
Loading

0 comments on commit 7c4afca

Please sign in to comment.