diff --git a/client/assets/moon.svg b/client/assets/moon.svg new file mode 100644 index 00000000..68640757 --- /dev/null +++ b/client/assets/moon.svg @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/client/assets/sun.svg b/client/assets/sun.svg new file mode 100644 index 00000000..c85378f3 --- /dev/null +++ b/client/assets/sun.svg @@ -0,0 +1,8 @@ + + + + diff --git a/client/components/AutocompleteInput/AutocompleteInput.scss b/client/components/AutocompleteInput/AutocompleteInput.scss index 19101cae..adf251a7 100644 --- a/client/components/AutocompleteInput/AutocompleteInput.scss +++ b/client/components/AutocompleteInput/AutocompleteInput.scss @@ -179,3 +179,34 @@ } } } + +.dark-theme { + .dummy-input__package-name { + color: $ghost-white; + } + + .dummy-input__package-version { + color: $spun-pearl; + } + + .autocomplete-input__suggestions-menu { + background: $black-russian; + border-color: $autocomplete-border-color-dark; + } + + .autocomplete-input__suggestion { + color: $spun-pearl; + + &:not(:last-of-type) { + border-bottom: 1px solid transparentize($spun-pearl, 0.6); + } + + em { + color: $ghost-white; + } + } + + .autocomplete-input__suggestion--highlight { + background-color: lighten($black-russian, 5%); + } +} diff --git a/client/components/AutocompleteInputBox/AutocompleteInputBox.scss b/client/components/AutocompleteInputBox/AutocompleteInputBox.scss index 1da86418..283569f1 100644 --- a/client/components/AutocompleteInputBox/AutocompleteInputBox.scss +++ b/client/components/AutocompleteInputBox/AutocompleteInputBox.scss @@ -32,3 +32,9 @@ height: 1px; } } + +.dark-theme { + .autocomplete-input-box { + border-color: $autocomplete-border-color-dark; + } +} diff --git a/client/components/ProgressHex/ProgressHex.scss b/client/components/ProgressHex/ProgressHex.scss index 2b3eb048..75b95fa4 100644 --- a/client/components/ProgressHex/ProgressHex.scss +++ b/client/components/ProgressHex/ProgressHex.scss @@ -1,3 +1,5 @@ +@import '../../../stylesheets/colors'; + .progress-hex { width: 8rem; height: 8rem; @@ -14,3 +16,11 @@ .progress-hex__trail { stroke-width: 1px; } + +.dark-theme { + .progress-hex { + circle { + fill: $ghost-white; + } + } +} diff --git a/client/components/QuickStatsBar/QuickStatsBar.scss b/client/components/QuickStatsBar/QuickStatsBar.scss index 0f06af4b..27444819 100644 --- a/client/components/QuickStatsBar/QuickStatsBar.scss +++ b/client/components/QuickStatsBar/QuickStatsBar.scss @@ -36,11 +36,6 @@ margin: auto; left: 0; } - - &:first-of-type, - &:last-of-type { - //padding-left: $global-spacing; - } } .quick-stats-bar__stat--optional { @@ -106,3 +101,26 @@ } } } + +.dark-theme { + .quick-stats-bar { + background: lighten($black-russian, 10%); + color: $spun-pearl; + } + + .quick-stats-bar__stat { + &:not(:first-of-type)::before { + background: darken($spun-pearl, 30%); + } + } + + .quick-stats-bar__link { + &:hover { + .quick-stats-bar__logo-icon--github { + path { + fill: $ghost-white; + } + } + } + } +} diff --git a/client/components/ResultLayout/ResultLayout.js b/client/components/ResultLayout/ResultLayout.js index 128d2304..b8c5c2dc 100644 --- a/client/components/ResultLayout/ResultLayout.js +++ b/client/components/ResultLayout/ResultLayout.js @@ -3,6 +3,7 @@ import cx from 'classnames' import Link from 'next/link' import Layout from 'client/components/Layout' +import ThemeToggle from 'client/components/ThemeToggle' import GithubLogo from '../../assets/github-logo.svg' import './ResultLayout.scss' @@ -58,6 +59,7 @@ export default class ResultLayout extends Component { > +
{children}
diff --git a/client/components/ResultLayout/ResultLayout.scss b/client/components/ResultLayout/ResultLayout.scss index 1be5808d..1b4aab1d 100644 --- a/client/components/ResultLayout/ResultLayout.scss +++ b/client/components/ResultLayout/ResultLayout.scss @@ -58,7 +58,7 @@ flex-direction: column; min-height: 100vh; min-height: calc(100vh - 6px); - flex-gorw: 1; + flex-grow: 1; } .page-content { @@ -105,3 +105,23 @@ overflow: scroll; } } + +.dark-theme { + .github-logo { + &:hover { + path { + fill: $ghost-white; + } + } + } + + .logo-small { + color: $ghost-white; + } + + .page-header__quicklinks { + a { + color: $spun-pearl; + } + } +} diff --git a/client/components/Separator.js b/client/components/Separator/Separator.js similarity index 87% rename from client/components/Separator.js rename to client/components/Separator/Separator.js index 3f00d98f..d763a96c 100644 --- a/client/components/Separator.js +++ b/client/components/Separator/Separator.js @@ -1,4 +1,5 @@ import React from 'react' +import './Separator.scss' export default function Separator({ text = 'or' }) { const commonStyles = { @@ -24,7 +25,7 @@ export default function Separator({ text = 'or' }) { >
- + @@ -34,37 +35,21 @@ export default function Separator({ text = 'or' }) {
- +
- - {text} - + {text} ) } diff --git a/client/components/Separator/Separator.scss b/client/components/Separator/Separator.scss new file mode 100644 index 00000000..d2a40426 --- /dev/null +++ b/client/components/Separator/Separator.scss @@ -0,0 +1,32 @@ +@import '../../../stylesheets/colors.scss'; + +.separator-flourish, +.separator-line { + fill: #211915; +} + +.separator-text { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: auto; + width: 42px; + height: 20px; + background: white; + padding: 0 15px; + border-radius: 50%; +} + +.dark-theme { + .separator-flourish, + .separator-line { + fill: $spun-pearl; + } + + .separator-text { + background: $black-russian; + color: $spun-pearl; + } +} diff --git a/client/components/Separator/index.js b/client/components/Separator/index.js new file mode 100644 index 00000000..8384c36b --- /dev/null +++ b/client/components/Separator/index.js @@ -0,0 +1,3 @@ +import Separator from './Separator.js' + +export default Separator diff --git a/client/components/SimilarPackageCard/SimilarPackageCard.scss b/client/components/SimilarPackageCard/SimilarPackageCard.scss index 897f4f8a..d1dfe664 100644 --- a/client/components/SimilarPackageCard/SimilarPackageCard.scss +++ b/client/components/SimilarPackageCard/SimilarPackageCard.scss @@ -155,3 +155,27 @@ fill: transparentize($raven, 0.7); } } + +.dark-theme { + .similar-package-card { + background: lighten($black-russian, 10%); + } + + .similar-package-card__name { + color: lighten($spun-pearl, 10%); + } + + .similar-package-card__description { + color: darken($spun-pearl, 15%); + } + + .similar-package-card__footer { + background: $black-russian; + } + + .similar-package-card__github-icon { + path { + fill: $ghost-white; + } + } +} diff --git a/client/components/Stat/Stat.scss b/client/components/Stat/Stat.scss index 08727375..297bed9f 100644 --- a/client/components/Stat/Stat.scss +++ b/client/components/Stat/Stat.scss @@ -30,6 +30,7 @@ color: #212121; background: inherit; position: relative; + transition: none; .stat-container--compact & { @include font-size-lg; @@ -118,3 +119,9 @@ display: none; } } + +.dark-theme { + .stat-container__value { + color: $spun-pearl; + } +} diff --git a/client/components/ThemeContext/ThemeContext.js b/client/components/ThemeContext/ThemeContext.js new file mode 100644 index 00000000..78ce4b54 --- /dev/null +++ b/client/components/ThemeContext/ThemeContext.js @@ -0,0 +1,25 @@ +import React, { createContext, useState } from 'react' + +export const Themes = { + light: 'light', + dark: 'dark', +} + +export const ThemeContext = createContext({ + theme: Themes.light, + toggleTheme: () => {}, +}) + +const ThemeContextProvider = props => { + const [theme, setTheme] = useState(Themes.light) + const toggleTheme = () => { + setTheme(theme === Themes.light ? Themes.dark : Themes.light) + } + + return ( + + {props.children} + + ) +} +export default ThemeContextProvider diff --git a/client/components/ThemeContext/index.js b/client/components/ThemeContext/index.js new file mode 100644 index 00000000..d1a2563c --- /dev/null +++ b/client/components/ThemeContext/index.js @@ -0,0 +1,4 @@ +import ThemeContextProvider from './ThemeContext' +export { Themes, ThemeContext } from './ThemeContext' + +export default ThemeContextProvider diff --git a/client/components/ThemeToggle/ThemeToggle.js b/client/components/ThemeToggle/ThemeToggle.js new file mode 100644 index 00000000..19212385 --- /dev/null +++ b/client/components/ThemeToggle/ThemeToggle.js @@ -0,0 +1,28 @@ +import React, { useContext, useCallback } from 'react' +import cx from 'classnames' +import { Themes, ThemeContext } from 'client/components/ThemeContext' +import SunIcon from '../../assets/sun.svg' +import MoonIcon from '../../assets/moon.svg' + +import './ThemeToggle.scss' + +const ThemeToggle = ({ className }) => { + const { theme, toggleTheme } = useContext(ThemeContext) + const onToggleTheme = useCallback(() => { + if (theme === Themes.dark) { + document.body.classList.remove('dark-theme') + } else { + document.body.classList.add('dark-theme') + } + + toggleTheme() + }) + + return ( + + ) +} + +export default ThemeToggle diff --git a/client/components/ThemeToggle/ThemeToggle.scss b/client/components/ThemeToggle/ThemeToggle.scss new file mode 100644 index 00000000..555ea8e4 --- /dev/null +++ b/client/components/ThemeToggle/ThemeToggle.scss @@ -0,0 +1,25 @@ +@import '../../../stylesheets/variables'; +@import '../../../stylesheets/colors'; + +.theme-toggle { + background: none; + border: none; + margin-left: $global-spacing * 2; + outline: none; + cursor: pointer; + + .sun-icon, + .moon-icon { + height: 30px; + width: 30px; + + @media screen and (max-width: 40em) { + width: 20px; + height: 20px; + } + } + + .sun-icon { + fill: $dandelion; + } +} diff --git a/client/components/ThemeToggle/index.js b/client/components/ThemeToggle/index.js new file mode 100644 index 00000000..21f405f3 --- /dev/null +++ b/client/components/ThemeToggle/index.js @@ -0,0 +1,3 @@ +import ThemeToggle from './ThemeToggle.js' + +export default ThemeToggle diff --git a/client/components/Warning/Warning.scss b/client/components/Warning/Warning.scss index 57ce0d64..0cd1b87f 100644 --- a/client/components/Warning/Warning.scss +++ b/client/components/Warning/Warning.scss @@ -18,3 +18,10 @@ text-transform: uppercase; } } + +.dark-theme { + .warning-bar { + background: $black-russian; + border: 1px solid $dandelion; + } +} diff --git a/pages/_app.js b/pages/_app.js new file mode 100644 index 00000000..0f0ceb0d --- /dev/null +++ b/pages/_app.js @@ -0,0 +1,17 @@ +import React from 'react' +import App from 'next/app' + +import ThemeContextProvider from 'client/components/ThemeContext' + +class MyApp extends App { + render() { + const { Component, pageProps } = this.props + return ( + + + + ) + } +} + +export default MyApp diff --git a/pages/index.js b/pages/index.js index bff583d3..dc0af0b6 100644 --- a/pages/index.js +++ b/pages/index.js @@ -4,8 +4,10 @@ import Layout from 'client/components/Layout' import Router from 'next/router' import Link from 'next/link' import Analytics from 'react-ga' -import './index.scss' import AutocompleteInputBox from 'client/components/AutocompleteInputBox/AutocompleteInputBox' +import ThemeToggle from 'client/components/ThemeToggle' + +import './index.scss' export default class Home extends PureComponent { componentDidMount() { @@ -33,10 +35,15 @@ export default class Home extends PureComponent { viewBox="0 0 137 157" xmlns="http://www.w3.org/2000/svg" > - + + ) } diff --git a/pages/index.scss b/pages/index.scss index e104c72c..22247a8a 100644 --- a/pages/index.scss +++ b/pages/index.scss @@ -42,6 +42,14 @@ color: #888; } +.logo__skeleton-base { + stroke: black; +} + +.logo__skeleton--filled-circle-mouth { + fill: #c0c0c0; +} + .logo__skeleton { animation: move 2s alternate infinite; @@ -141,3 +149,31 @@ transform: translate(0.55px, -1px); } } + +.homepage__theme-toggle { + position: absolute; + top: $global-spacing * 3; + right: $global-spacing * 3; +} + +.dark-theme { + .logo { + color: $ghost-white; + } + + .logo__skeleton-base { + stroke: $spun-pearl; + } + + .logo__skeleton--filled-circle-mouth { + fill: darken($spun-pearl, 25%); + } + + .logo__skeleton-group { + stroke: $spun-pearl; + } + + .logo__skeleton { + stroke: $ghost-white; + } +} diff --git a/pages/result/ResultPage.js b/pages/result/ResultPage.js index e41ecef4..227eee4b 100644 --- a/pages/result/ResultPage.js +++ b/pages/result/ResultPage.js @@ -53,7 +53,7 @@ class ResultPage extends PureComponent { router: { query }, } = this.props const { - url: { query: nextQuery }, + router: { query: nextQuery }, } = nextProps if (!nextQuery || !nextQuery.p.trim()) { @@ -242,7 +242,7 @@ class ResultPage extends PureComponent { } getMetaTags = () => { - const { url } = this.props + const { query } = this.props.router const { resultsPromiseState, results } = this.state let name, version @@ -250,8 +250,8 @@ class ResultPage extends PureComponent { name = results.name version = results.version } else { - name = parsePackageString(url.query.p).name - version = parsePackageString(url.query.p).version + name = parsePackageString(query.p).name + version = parsePackageString(query.p).version } const packageString = version ? `${name}@${version}` : name diff --git a/pages/result/ResultPage.scss b/pages/result/ResultPage.scss index 4a139477..40afbac2 100644 --- a/pages/result/ResultPage.scss +++ b/pages/result/ResultPage.scss @@ -248,6 +248,7 @@ background: #ffbc40; border-radius: 2px; line-height: 1.2; + color: black; } } @@ -328,3 +329,13 @@ margin: $global-spacing * 3 0 0 0; line-height: 1.2; } + +.dark-theme { + .result-error__img { + fill: $spun-pearl; + } + + .time-container { + border-color: darken($spun-pearl, 25%); + } +} diff --git a/pages/result/components/ExportAnalysisSection/ExportAnalysisSection.scss b/pages/result/components/ExportAnalysisSection/ExportAnalysisSection.scss index fa9d6c3c..5f48fae1 100644 --- a/pages/result/components/ExportAnalysisSection/ExportAnalysisSection.scss +++ b/pages/result/components/ExportAnalysisSection/ExportAnalysisSection.scss @@ -232,3 +232,32 @@ color: $raven; } } + +.dark-theme { + .export-analysis-section__list { + background-image: none; + } + + .export-analysis-section__filter-input { + background-color: $black-russian; + color: $spun-pearl; + border-color: $autocomplete-border-color-dark; + + &:focus { + background: lighten($black-russian, 5%); + border-color: lighten($autocomplete-border-color-dark, 48%); + } + } + + .export-analysis-section__pill { + color: $black-russian; + + &::after { + background: $black-russian; + } + } + + .export-analysis-section__pill-spinner { + background-color: $spun-pearl; + } +} diff --git a/pages/scan-results/ScanResults.scss b/pages/scan-results/ScanResults.scss index 321ac1bb..e1baea29 100644 --- a/pages/scan-results/ScanResults.scss +++ b/pages/scan-results/ScanResults.scss @@ -221,3 +221,22 @@ $index-width: 4rem; text-decoration: line-through; } } + +.dark-theme { + .scan-results__container { + border-color: $spun-pearl; + box-shadow: 0 0 4px lighten($black-russian, 20%); + } + + .scan-results__item { + background: $black-russian; + } + + .scan-results__item--total { + background: lighten($black-russian, 5%); + } + + .scan-results__index { + color: lighten($black-russian, 10%); + } +} diff --git a/pages/scan/Scan.scss b/pages/scan/Scan.scss index c3d25cb1..4472c7e2 100644 --- a/pages/scan/Scan.scss +++ b/pages/scan/Scan.scss @@ -103,3 +103,22 @@ } } } + +.dark-theme { + .scan__btn { + background-color: $spun-pearl; + color: $black-russian; + + &:hover { + background: lighten($spun-pearl, 20%); + } + } + + .scan__selection-header { + .scan__btn { + &:hover { + color: $black-russian; + } + } + } +} diff --git a/stylesheets/base.scss b/stylesheets/base.scss index 17f07165..c284968b 100644 --- a/stylesheets/base.scss +++ b/stylesheets/base.scss @@ -16,6 +16,11 @@ html { color: #212121; } +body.dark-theme { + background-color: $black-russian; + color: $spun-pearl; +} + code { font-family: $font-family-code; } diff --git a/stylesheets/colors.scss b/stylesheets/colors.scss index 29db39c0..68f0ab3d 100644 --- a/stylesheets/colors.scss +++ b/stylesheets/colors.scss @@ -8,4 +8,9 @@ $dandelion: #fff3cf; $carrot-orange: #eb841f; $raven: #666e78; +$black-russian: #1c1c1e; +$spun-pearl: #a9a9b3; +$ghost-white: #f1f1f2; + $autocomplete-border-color: transparentize(black, 0.93); +$autocomplete-border-color-dark: transparentize($spun-pearl, 0.6);