mirror of
https://github.com/kremalicious/portfolio.git
synced 2024-12-22 17:23:22 +01:00
remove AppProvider, replace with hooks
This commit is contained in:
parent
d9fa36608e
commit
796dfc64fa
@ -74,7 +74,7 @@ If a visitor has set the theme manually that selection is remembered in `localSt
|
|||||||
If you want to know how, have a look at the respective components:
|
If you want to know how, have a look at the respective components:
|
||||||
|
|
||||||
- [`src/components/molecules/ThemeSwitch.jsx`](src/components/molecules/ThemeSwitch.jsx)
|
- [`src/components/molecules/ThemeSwitch.jsx`](src/components/molecules/ThemeSwitch.jsx)
|
||||||
- [`src/store/provider.jsx`](src/store/provider.jsx)
|
- [`src/hooks/use-dark-mode.jsx`](src/hooks/use-dark-mode.jsx)
|
||||||
|
|
||||||
### 🏆 SEO component
|
### 🏆 SEO component
|
||||||
|
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
import React from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import AppProvider from './src/store/AppProvider'
|
|
||||||
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
|
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
|
||||||
|
|
||||||
// Global styles
|
// Global styles
|
||||||
@ -11,15 +8,6 @@ if (typeof window !== 'undefined' && !window.IntersectionObserver) {
|
|||||||
import('intersection-observer')
|
import('intersection-observer')
|
||||||
}
|
}
|
||||||
|
|
||||||
// React Context in Browser
|
|
||||||
export const wrapRootElement = ({ element }) => {
|
|
||||||
return <AppProvider>{element}</AppProvider>
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapRootElement.propTypes = {
|
|
||||||
element: PropTypes.any
|
|
||||||
}
|
|
||||||
|
|
||||||
// Layout with Page Transitions
|
// Layout with Page Transitions
|
||||||
export const wrapPageElement = wrapPageElementWithLayout
|
export const wrapPageElement = wrapPageElementWithLayout
|
||||||
|
|
||||||
|
@ -1,13 +1,4 @@
|
|||||||
import React from 'react'
|
|
||||||
import { renderToString } from 'react-dom/server'
|
|
||||||
import AppProvider from './src/store/AppProvider'
|
|
||||||
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
|
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
|
||||||
|
|
||||||
export const replaceRenderer = ({ bodyComponent, replaceBodyHTMLString }) => {
|
|
||||||
// React Context in SSR/build
|
|
||||||
const ConnectedBody = () => <AppProvider>{bodyComponent}</AppProvider>
|
|
||||||
replaceBodyHTMLString(renderToString(<ConnectedBody />))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Layout with Page Transitions
|
// Layout with Page Transitions
|
||||||
export const wrapPageElement = wrapPageElementWithLayout
|
export const wrapPageElement = wrapPageElementWithLayout
|
||||||
|
@ -4,13 +4,13 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
display: block;
|
display: block;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
overflow: hidden;
|
||||||
box-shadow: 0 3px 5px rgba($brand-main, 0.15),
|
box-shadow: 0 3px 5px rgba($brand-main, 0.15),
|
||||||
0 5px 16px rgba($brand-main, 0.15);
|
0 5px 16px rgba($brand-main, 0.15);
|
||||||
|
|
||||||
@media (min-width: $projectImageMaxWidth) {
|
@media (min-width: $projectImageMaxWidth) {
|
||||||
max-width: $projectImageMaxWidth;
|
max-width: $projectImageMaxWidth;
|
||||||
border-radius: $border-radius;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.dark) & {
|
:global(.dark) & {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useContext } from 'react'
|
import React from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import Helmet from 'react-helmet'
|
import Helmet from 'react-helmet'
|
||||||
import posed from 'react-pose'
|
import posed from 'react-pose'
|
||||||
import Context from '../../store/createContext'
|
import useDarkMode from '../../hooks/use-dark-mode'
|
||||||
import { fadeIn } from '../atoms/Transitions'
|
import { fadeIn } from '../atoms/Transitions'
|
||||||
|
|
||||||
import { ReactComponent as Day } from '../../images/day.svg'
|
import { ReactComponent as Day } from '../../images/day.svg'
|
||||||
@ -40,7 +40,7 @@ ThemeToggleInput.propTypes = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ThemeSwitch() {
|
export default function ThemeSwitch() {
|
||||||
const { darkMode, toggleDark } = useContext(Context)
|
const { darkMode, toggleDark } = useDarkMode()
|
||||||
const themeColor = darkMode ? '#1d2224' : '#e7eef4'
|
const themeColor = darkMode ? '#1d2224' : '#e7eef4'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,53 +1,23 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render, fireEvent, cleanup } from '@testing-library/react'
|
import { render, fireEvent, cleanup, wait } from '@testing-library/react'
|
||||||
import Context from '../../store/createContext'
|
|
||||||
import ThemeSwitch from './ThemeSwitch'
|
import ThemeSwitch from './ThemeSwitch'
|
||||||
|
|
||||||
describe('ThemeSwitch', () => {
|
describe('ThemeSwitch', () => {
|
||||||
afterEach(cleanup)
|
afterEach(cleanup)
|
||||||
const toggleDark = jest.fn()
|
|
||||||
|
|
||||||
it('renders correctly', () => {
|
it('renders correctly', async () => {
|
||||||
const { container } = render(
|
const { container } = render(<ThemeSwitch />)
|
||||||
<Context.Provider
|
await wait(() => container.querySelector('aside'))
|
||||||
value={{ darkMode: false, toggleDark: () => toggleDark }}
|
expect(container.querySelector('aside')).toBeInTheDocument()
|
||||||
>
|
|
||||||
<ThemeSwitch />
|
|
||||||
</Context.Provider>
|
|
||||||
)
|
|
||||||
|
|
||||||
const switchContainer = container.querySelector('aside')
|
|
||||||
|
|
||||||
expect(switchContainer).toBeInTheDocument()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('switches when it is dark', () => {
|
it('checkbox can be changed', async () => {
|
||||||
const { container } = render(
|
const { container } = render(<ThemeSwitch />)
|
||||||
<Context.Provider
|
|
||||||
value={{ darkMode: true, toggleDark: () => toggleDark }}
|
|
||||||
>
|
|
||||||
<ThemeSwitch />
|
|
||||||
</Context.Provider>
|
|
||||||
)
|
|
||||||
|
|
||||||
const toggle = container.querySelector('input')
|
|
||||||
expect(toggle).toHaveAttribute('checked')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('checkbox can be changed', () => {
|
|
||||||
const { container } = render(
|
|
||||||
<Context.Provider
|
|
||||||
value={{ darkMode: false, toggleDark: () => toggleDark }}
|
|
||||||
>
|
|
||||||
<ThemeSwitch />
|
|
||||||
</Context.Provider>
|
|
||||||
)
|
|
||||||
|
|
||||||
const toggle = container.querySelector('input')
|
const toggle = container.querySelector('input')
|
||||||
const label = container.querySelector('label')
|
const label = container.querySelector('label')
|
||||||
expect(toggle.checked).toBeFalsy()
|
expect(toggle.checked).toBeFalsy()
|
||||||
fireEvent.click(label)
|
fireEvent.click(label)
|
||||||
fireEvent.change(toggle, { target: { checked: true } })
|
fireEvent.change(toggle, { target: { checked: true } })
|
||||||
expect(toggle.checked).toBeTruthy()
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,34 +1,27 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render, cleanup, wait } from '@testing-library/react'
|
||||||
import { useStaticQuery } from 'gatsby'
|
import { useStaticQuery } from 'gatsby'
|
||||||
import Header from './Header'
|
import Header from './Header'
|
||||||
import Context from '../../store/createContext'
|
|
||||||
import data from '../../../jest/__fixtures__/meta.json'
|
import data from '../../../jest/__fixtures__/meta.json'
|
||||||
|
|
||||||
describe('Header', () => {
|
describe('Header', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
useStaticQuery.mockImplementation(() => {
|
useStaticQuery.mockImplementation(() => {
|
||||||
return {
|
return { ...data }
|
||||||
...data
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders correctly', () => {
|
afterEach(cleanup)
|
||||||
const { container } = render(
|
|
||||||
<Context.Provider value={{ darkMode: false, toggleDark: () => null }}>
|
it('renders correctly', async () => {
|
||||||
<Header />
|
const { container } = render(<Header />)
|
||||||
</Context.Provider>
|
await wait(() => container.firstChild)
|
||||||
)
|
|
||||||
expect(container.firstChild).toBeInTheDocument()
|
expect(container.firstChild).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Availability can be hidden', () => {
|
it('Availability can be hidden', async () => {
|
||||||
const { container } = render(
|
const { container } = render(<Header minimal={true} />)
|
||||||
<Context.Provider value={{ darkMode: false, toggleDark: () => null }}>
|
await wait(() => container.querySelector('.availability'))
|
||||||
<Header minimal={true} />
|
|
||||||
</Context.Provider>
|
|
||||||
)
|
|
||||||
expect(container.querySelector('.availability')).not.toBeInTheDocument()
|
expect(container.querySelector('.availability')).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
32
src/hooks/use-dark-mode.js
Normal file
32
src/hooks/use-dark-mode.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
import { getLocationTimes } from '../utils/getLocationTimes'
|
||||||
|
import { getCountry } from '../utils/getCountry'
|
||||||
|
|
||||||
|
export default function useDarkMode() {
|
||||||
|
const store = typeof localStorage === 'undefined' ? null : localStorage
|
||||||
|
const darkLocalStorage = store && store.getItem('darkMode')
|
||||||
|
const [darkMode, setDarkMode] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
darkLocalStorage
|
||||||
|
? darkLocalStorage === 'true'
|
||||||
|
? setDarkMode(true)
|
||||||
|
: setDarkMode(false)
|
||||||
|
: checkTimes()
|
||||||
|
|
||||||
|
async function checkTimes() {
|
||||||
|
const geolocation = await getCountry()
|
||||||
|
const { sunset, sunrise } = getLocationTimes(geolocation)
|
||||||
|
const now = new Date().getHours()
|
||||||
|
const weWantItDarkTimes = now >= sunset || now <= sunrise
|
||||||
|
weWantItDarkTimes && setDarkMode(true)
|
||||||
|
}
|
||||||
|
}, [darkLocalStorage])
|
||||||
|
|
||||||
|
function toggleDark() {
|
||||||
|
setDarkMode(!darkMode)
|
||||||
|
store && store.setItem('darkMode', !darkMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { darkMode, toggleDark }
|
||||||
|
}
|
@ -1,18 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { render } from '@testing-library/react'
|
|
||||||
import { useStaticQuery } from 'gatsby'
|
|
||||||
import { useMeta } from './use-meta'
|
|
||||||
import data from '../../jest/__fixtures__/meta.json'
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
useStaticQuery.mockImplementationOnce(() => ({ ...data }))
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('useMeta', () => {
|
|
||||||
it('renders correctly', () => {
|
|
||||||
const { social } = useMeta()
|
|
||||||
const { container } = render(<div>{social.Twitter}</div>)
|
|
||||||
|
|
||||||
expect(container.textContent).toBe('https://twitter.com/kremalicious')
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,74 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react'
|
|
||||||
import PropTypes from 'prop-types'
|
|
||||||
import Context from './createContext'
|
|
||||||
import { getLocationTimes } from '../utils/getLocationTimes'
|
|
||||||
import { getCountry } from '../utils/getCountry'
|
|
||||||
|
|
||||||
export default class AppProvider extends PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
children: PropTypes.any.isRequired
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
darkMode: false,
|
|
||||||
toggleDark: () => this.toggleDark(),
|
|
||||||
geolocation: null
|
|
||||||
}
|
|
||||||
|
|
||||||
store = typeof localStorage === 'undefined' ? null : localStorage
|
|
||||||
|
|
||||||
mounted = false
|
|
||||||
|
|
||||||
async componentDidMount() {
|
|
||||||
this.mounted = true
|
|
||||||
|
|
||||||
const geolocation = await getCountry()
|
|
||||||
this.setState({ geolocation })
|
|
||||||
this.checkDark()
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.mounted = false
|
|
||||||
}
|
|
||||||
|
|
||||||
setDark(darkMode) {
|
|
||||||
this.mounted && this.setState({ darkMode })
|
|
||||||
}
|
|
||||||
|
|
||||||
darkLocalStorageMode(darkLocalStorage) {
|
|
||||||
darkLocalStorage === 'true' ? this.setDark(true) : this.setDark(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// All the checks to see if we should go dark or light
|
|
||||||
//
|
|
||||||
async checkTimes() {
|
|
||||||
const { geolocation, darkMode } = this.state
|
|
||||||
const { sunset, sunrise } = getLocationTimes(geolocation)
|
|
||||||
const now = new Date().getHours()
|
|
||||||
const weWantItDarkTimes = now >= sunset || now <= sunrise
|
|
||||||
|
|
||||||
!darkMode && weWantItDarkTimes ? this.setDark(true) : this.setDark(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkDark() {
|
|
||||||
const darkLocalStorage = await this.store.getItem('darkMode')
|
|
||||||
|
|
||||||
darkLocalStorage
|
|
||||||
? this.darkLocalStorageMode(darkLocalStorage)
|
|
||||||
: this.checkTimes()
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleDark = () => {
|
|
||||||
this.setState({ darkMode: !this.state.darkMode })
|
|
||||||
this.store && this.store.setItem('darkMode', !this.state.darkMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Context.Provider value={this.state}>
|
|
||||||
{this.props.children}
|
|
||||||
</Context.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { render, fireEvent } from '@testing-library/react'
|
|
||||||
import { LocalStorageMock } from '@react-mock/localstorage'
|
|
||||||
import AppProvider from './AppProvider.jsx'
|
|
||||||
import Context from './createContext.jsx'
|
|
||||||
|
|
||||||
describe('AppProvider', () => {
|
|
||||||
it('renders correctly', () => {
|
|
||||||
const { container } = render(<AppProvider>Hello</AppProvider>)
|
|
||||||
|
|
||||||
expect(container.firstChild.textContent).toBe('Hello')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders with dark detected in localStorage', () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<LocalStorageMock items={{ dark: 'true' }}>
|
|
||||||
<AppProvider>
|
|
||||||
<Context.Consumer>
|
|
||||||
{state => (
|
|
||||||
<button data-testid="toggle" onClick={() => state.toggleDark()}>
|
|
||||||
Toggle
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</Context.Consumer>
|
|
||||||
</AppProvider>
|
|
||||||
</LocalStorageMock>
|
|
||||||
)
|
|
||||||
fireEvent.click(getByTestId('toggle'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('renders with light detected in localStorage', () => {
|
|
||||||
render(
|
|
||||||
<LocalStorageMock items={{ dark: 'false' }}>
|
|
||||||
<AppProvider>Hello</AppProvider>
|
|
||||||
</LocalStorageMock>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,5 +0,0 @@
|
|||||||
import { createContext } from 'react'
|
|
||||||
|
|
||||||
const Context = createContext()
|
|
||||||
|
|
||||||
export default Context
|
|
Loading…
Reference in New Issue
Block a user