Skip to content

Commit

Permalink
Add support for dark mode
Browse files Browse the repository at this point in the history
  • Loading branch information
rowanhogan committed Oct 2, 2019
1 parent 2191c6a commit fee8d17
Show file tree
Hide file tree
Showing 16 changed files with 412 additions and 182 deletions.
Binary file modified public/favicon.ico
Binary file not shown.
4 changes: 1 addition & 3 deletions src/components/footer/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import Settings from '../settings'

class Footer extends Component {
render() {
return window === window.top ? (
<footer className="footer">
<Settings />
<Link className="logo" to="/">
Wikipedia
Wikipadia
</Link>
</footer>
) : null
Expand Down
3 changes: 3 additions & 0 deletions src/components/header/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { Component } from 'react'

import Search from '../search'
import Settings from '../settings'
import { Link } from 'react-router-dom'

class Header extends Component {
Expand Down Expand Up @@ -46,6 +48,7 @@ class Header extends Component {
Home
</Link>
<Search />
<Settings />
</header>
) : null
}
Expand Down
54 changes: 54 additions & 0 deletions src/components/modal/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import React, { Component } from "react";
import { createPortal } from "react-dom";
import withClickOutside from "react-click-outside";

class Modal extends Component {
constructor() {
super();
this.el = null;
}

componentDidMount() {
this.el = document.createElement("div");
document.querySelector(".settings-wrapper").appendChild(this.el);
}

componentWillUnmount() {
if (this.el) {
this.el.remove();
}
}

handleClickOutside(event) {
const { closeOnOutsideClick, handleClose } = this.props;

if (closeOnOutsideClick) {
event.preventDefault();
return handleClose();
}
}

render() {
const { children, handleClose, isOpen, title } = this.props;

if (!isOpen) return null;

return createPortal(
<div className="modal">
{title && <h3 className="modal-title">{title}</h3>}
{children}
<button className="close" onClick={handleClose}>
Close
</button>
</div>,
this.el
);
}
}

Modal.defaultProps = {
closeOnOutsideClick: true,
isOpen: false
};

export default withClickOutside(Modal);
87 changes: 46 additions & 41 deletions src/components/settings/index.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,71 @@
import React, { Component } from 'react'
import { compose } from 'redux'
import { connect } from 'react-redux'
import { updateSetting } from '../../store/settings'
import withClickOutside from 'react-click-outside'
import React, { Component, Fragment } from "react";
import { connect } from "react-redux";
import { updateSetting } from "../../store/settings";

import Toggle from '../toggle'
import Modal from "../modal";
import Toggle from "../toggle";

class Settings extends Component {
constructor() {
super()
super();
this.state = {
open: false
}
};
}

handleClickOutside(event) {
if (this.state.open) {
event.preventDefault()
this.setState({ open: false })
event.preventDefault();
this.setState({ open: false });
}
}

render() {
const { settings } = this.props
const { open } = this.state
const { settings } = this.props;
const { open } = this.state;

return (
<div className="settings">
<Fragment>
<button
className="settings-button"
onClick={() => this.setState({ open: true })}>
onClick={() => this.setState({ open: true })}
>
Settings
</button>
{open && (
<div className="modal settings-modal">
<button
className="close"
onClick={() => this.setState({ open: false })}>
Close
</button>
<h3>Settings</h3>
<Modal
isOpen={open}
title="Settings"
handleClose={() => this.setState({ open: false })}
>
<Toggle
label="Dark Mode"
name="darkMode"
checked={settings.darkMode}
onChange={this.props.updateSetting}
/>
<p className="settings-hint">
Light text on dark background (defaults to OS preference)
</p>

<Toggle
label="Low contrast"
name="lowContrast"
checked={settings.lowContrast}
onChange={this.props.updateSetting}
/>
<p className="settings-hint">
Reduce eye strain for low-light reading.
</p>
</div>
)}
</div>
)
<Toggle
label="Low contrast"
name="lowContrast"
checked={settings.lowContrast}
onChange={this.props.updateSetting}
/>
<p className="settings-hint">
Reduce eye strain for low-light reading.
</p>
</Modal>
</Fragment>
);
}
}

const mapStateToProps = ({ settings }) => ({ settings })
const mapDispatchToProps = { updateSetting }
const mapStateToProps = ({ settings }) => ({ settings });
const mapDispatchToProps = { updateSetting };

export default compose(
connect(mapStateToProps, mapDispatchToProps),
withClickOutside
)(Settings)
export default connect(
mapStateToProps,
mapDispatchToProps
)(Settings);
1 change: 1 addition & 0 deletions src/images/settings.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 16 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux'
import ReactDOM from 'react-dom'
import configureStore from './store'
import initReactFastclick from 'react-fastclick'
import registerServiceWorker from './registerServiceWorker'
import './styles/styles.css'
import React from "react";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import ReactDOM from "react-dom";
import configureStore from "./store";
import initReactFastclick from "react-fastclick";
import registerServiceWorker from "./registerServiceWorker";
import "./styles/styles.css";
import "./lib/favicon";

import App from './routes'
import App from "./routes";

if ('ontouchstart' in document.documentElement) {
document.body.style.cursor = 'pointer'
if ("ontouchstart" in document.documentElement) {
document.body.style.cursor = "pointer";
}

initReactFastclick()
initReactFastclick();

ReactDOM.render(
<Provider store={configureStore()}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
)
registerServiceWorker()
document.getElementById("root")
);
registerServiceWorker();
28 changes: 28 additions & 0 deletions src/lib/favicon/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 34 additions & 13 deletions src/routes/app/index.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import React from 'react'
import { connect } from 'react-redux'
import React, { Component } from "react";
import { connect } from "react-redux";

import Header from '../../components/header'
import Footer from '../../components/footer'
import Header from "../../components/header";
import Footer from "../../components/footer";

const App = ({ classNames, children }) => (
<div className={classNames.join(' ')}>
<Header />
{children}
<Footer />
</div>
)
class App extends Component {
componentDidMount() {
if (this.props.darkMode) {
document.documentElement.style.backgroundColor = "black";
}
}

componentDidUpdate({ darkMode }) {
if (this.props.darkMode !== darkMode) {
document.documentElement.style.backgroundColor = this.props.darkMode
? "black"
: "transparent";
}
}

render() {
const { children, classNames } = this.props;

return (
<div className={`settings-wrapper ${classNames.join(" ")}`}>
<Header />
{children}
<Footer />
</div>
);
}
}

const mapStateToProps = ({ settings }) => ({
darkMode: settings.darkMode,
classNames: Object.keys(settings).filter(setting => settings[setting])
})
});

export default connect(mapStateToProps)(App)
export default connect(mapStateToProps)(App);
23 changes: 13 additions & 10 deletions src/store/settings/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,29 @@ export const updateSetting = (name, value) => dispatch =>
.then(() => localStorage.setItem(name, value))
.then(() =>
dispatch({
type: 'SETTINGS/UPDATE',
type: "SETTINGS/UPDATE",
payload: { name, value }
})
)
);

const getBooleanValue = key => localStorage.getItem(key) === 'true'
const getBooleanValue = key => localStorage.getItem(key) === "true";

const initialState = {
lowContrast: getBooleanValue('lowContrast')
}
darkMode: localStorage.getItem("darkMode")
? getBooleanValue("darkMode")
: window.matchMedia("(prefers-color-scheme: dark)").matches,
lowContrast: getBooleanValue("lowContrast")
};

export default (state = initialState, action) => {
switch (action.type) {
case 'SETTINGS/UPDATE':
const { name, value } = action.payload
case "SETTINGS/UPDATE":
const { name, value } = action.payload;
return {
...state,
[name]: value
}
};
default:
return state
return state;
}
}
};
Loading

0 comments on commit fee8d17

Please sign in to comment.