dark mode with css variables instead

This commit is contained in:
keyan 2021-11-09 16:43:56 -06:00
parent c6e6ddfa65
commit a3544fb67f
12 changed files with 219 additions and 205 deletions

View File

@ -3,7 +3,6 @@
"next/babel"
],
"plugins": [
"styled-components",
[
"inline-react-svg",
{

View File

@ -13,7 +13,6 @@ import { useMe } from './me'
import CommentEdit from './comment-edit'
import Countdown from './countdown'
import { NOFOLLOW_LIMIT } from '../lib/constants'
import styled from 'styled-components'
function Parent ({ item, rootText }) {
const ParentFrag = () => (
@ -40,10 +39,6 @@ function Parent ({ item, rootText }) {
)
}
const StyledComment = styled.div`
background-color: ${({ theme, includeParent }) => includeParent ? undefined : theme.commentBg};
`
export default function Comment ({
item, children, replyOpen, includeParent,
rootText, noComments, noReply
@ -69,8 +64,7 @@ export default function Comment ({
const op = item.root.user.name === item.user.name
return (
<StyledComment
includeParent={includeParent}
<div
ref={ref} className={includeParent ? '' : `${styles.comment} ${collapse ? styles.collapsed : ''}`}
>
<div className={`${itemStyles.item} ${styles.item}`}>
@ -162,7 +156,7 @@ export default function Comment ({
: null}
</div>
</div>
</StyledComment>
</div>
)
}

View File

@ -72,6 +72,7 @@
border-radius: .4rem;
padding-top: .5rem;
padding-left: .2rem;
background-color: var(--theme-commentBg);
}
.comment:not(:last-child) {

View File

@ -8,9 +8,9 @@ import Github from '../svgs/github-fill.svg'
import Twitter from '../svgs/twitter-fill.svg'
import Link from 'next/link'
import useDarkMode from 'use-dark-mode'
import styled from 'styled-components'
import Sun from '../svgs/sun-fill.svg'
import Moon from '../svgs/moon-fill.svg'
import { handleThemeChange } from '../public/darkmode'
const ChatPopover = (
<Popover>
@ -32,16 +32,6 @@ const ChatPopover = (
</Popover>
)
const ContrastLink = styled.a`
color: ${({ theme }) => theme.color};
&:hover {
color: ${({ theme }) => theme.color};
}
& svg {
fill: ${({ theme }) => theme.color};
}
`
export default function Footer ({ noLinks }) {
const query = gql`
{
@ -53,7 +43,7 @@ export default function Footer ({ noLinks }) {
const darkMode = useDarkMode(false, {
// set this so it doesn't try to use classes
onChange: () => { }
onChange: handleThemeChange
})
return (
@ -108,9 +98,9 @@ export default function Footer ({ noLinks }) {
/>
</div>}
<small>
<ContrastLink className='d-inline-block' href='https://github.com/stackernews/stacker.news'>
<a className={`d-inline-block ${styles.contrastLink}`} href='https://github.com/stackernews/stacker.news'>
This is free open source software<Github width={20} height={20} className='mx-1' />
</ContrastLink>
</a>
<span className='d-inline-block text-muted'>
made with sound love in Austin<Texas className='mx-1' width={20} height={20} />
by<a href='https://twitter.com/k00bideh' className='text-twitter d-inline-block'><Twitter width={20} height={20} className='ml-1' />@k00bideh</a>

View File

@ -8,4 +8,14 @@
.connect:hover {
opacity: 1;
}
.contrastLink {
color: var(--theme-color);
}
.contrastLink:hover {
color: var(--theme-color);
}
.contrastLink svg {
fill: var(--theme-color);
}

View File

@ -11,48 +11,6 @@ import { signOut, signIn, useSession } from 'next-auth/client'
import { useLightning } from './lightning'
import { useEffect, useState } from 'react'
import { randInRange } from '../lib/rand'
import styled from 'styled-components'
const Brand = styled(Navbar.Brand)`
color: ${({ theme }) => theme.brandColor}
`
export const StyledNavbar = styled(Navbar).attrs(({ theme }) => ({
variant: theme.navbarVariant,
className: styles.navbar
}))`
& .dropdown-menu {
background-color: ${({ theme }) => theme.body};
border: 1px solid ${({ theme }) => theme.borderColor};
}
& .dropdown-item {
color: ${({ theme }) => theme.dropdownItemColor};
}
& .dropdown-item:hover {
color: ${({ theme }) => theme.dropdownItemColorHover};
}
& .dropdown-item.active {
color: ${({ theme }) => theme.brandColor};
text-shadow: 0 0 10px var(--primary);
}
& .dropdown-divider {
border-top: 1px solid ${({ theme }) => theme.borderColor};
}
& .theme {
margin-right: 1rem;
cursor: pointer;
fill: ${({ theme }) => theme.dropdownItemColor};
}
& .theme:hover {
fill: ${({ theme }) => theme.dropdownItemColorHover};
}
`
function WalletSummary ({ me }) {
return `${me?.sats} \\ ${me?.stacked}`
@ -172,16 +130,16 @@ export default function Header () {
return (
<>
<Container className='px-sm-0'>
<StyledNavbar>
<Navbar>
<Nav
className={styles.navbarNav}
activeKey={path}
>
<Link href='/' passHref>
<Brand className={`${styles.brand} d-none d-sm-block`}>STACKER NEWS</Brand>
<Navbar.Brand className={`${styles.brand} d-none d-sm-block`}>STACKER NEWS</Navbar.Brand>
</Link>
<Link href='/' passHref>
<Brand className={`${styles.brand} d-block d-sm-none`}>SN</Brand>
<Navbar.Brand className={`${styles.brand} d-block d-sm-none`}>SN</Navbar.Brand>
</Link>
<Nav.Item className='d-md-flex d-none nav-dropdown-toggle'>
<SplitButton
@ -217,7 +175,7 @@ export default function Header () {
</Nav.Item>
<Corner />
</Nav>
</StyledNavbar>
</Navbar>
</Container>
</>
)
@ -227,6 +185,7 @@ export function HeaderPreview () {
return (
<>
<Container className='px-sm-0'>
{/* still need to set variant */}
<Navbar className={styles.navbar}>
<Nav className='w-100 justify-content-between flex-wrap align-items-center'>
<Link href='/' passHref>

View File

@ -6,6 +6,7 @@
margin-bottom: -.3rem;
margin-right: 0;
text-shadow: 0 0 10px #FADA5E;
color: var(--theme-brandColor) !important;
}
.navLink {

View File

@ -7,92 +7,10 @@ import PlausibleProvider from 'next-plausible'
import { LightningProvider } from '../components/lightning'
import { ItemActModal, ItemActProvider } from '../components/item-act'
import getApolloClient from '../lib/apollo'
import { createGlobalStyle, ThemeProvider } from 'styled-components'
import useDarkMode from 'use-dark-mode'
const GlobalStyle = createGlobalStyle`
body {
background: ${({ theme }) => theme.body};
color: ${({ theme }) => theme.color};
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
color: inherit;
background-color: ${({ theme }) => theme.inputBg};
border-color: ${({ theme }) => theme.borderColor};
border-bottom-color: ${({ theme }) => theme.inputBg};
}
.form-control {
background-color: ${({ theme }) => theme.inputBg};
color: ${({ theme }) => theme.color};
border-color: ${({ theme }) => theme.borderColor};
}
.form-control:focus {
background-color: ${({ theme }) => theme.inputBg};
color: ${({ theme }) => theme.color};
}
.form-control:disabled, .form-control[readonly] {
background-color: ${({ theme }) => theme.inputBg};
border-color: ${({ theme }) => theme.borderColor};
opacity: 1;
}
.clickToContext {
border-radius: .4rem;
padding: .2rem 0;
cursor: pointer;
}
.clickToContext:hover {
background-color: ${({ theme }) => theme.clickToContextColor};
}
.fresh {
background-color: ${({ theme }) => theme.clickToContextColor};
border-radius: .4rem;
}
.modal-content {
background-color: ${({ theme }) => theme.body};
border-color: ${({ theme }) => theme.borderColor};
}
`
const lightTheme = {
body: '#f5f5f5',
color: '#212529',
navbarVariant: 'light',
borderColor: '#ced4da',
inputBg: '#ffffff',
dropdownItemColor: 'inherit',
dropdownItemColorHover: 'rgba(0, 0, 0, 0.9)',
commentBg: 'rgba(0, 0, 0, 0.03)',
clickToContextColor: 'rgba(0, 0, 0, 0.05)',
brandColor: 'rgba(0, 0, 0, 0.9)'
}
const darkTheme = {
body: '#000000',
inputBg: '#000000',
navbarVariant: 'dark',
borderColor: 'rgb(255 255 255 / 50%)',
dropdownItemColor: 'rgba(255, 255, 255, 0.7)',
dropdownItemColorHover: 'rgba(255, 255, 255, 0.9)',
commentBg: 'rgba(255, 255, 255, 0.04)',
clickToContextColor: 'rgba(255, 255, 255, 0.08)',
color: '#f8f9fa',
brandColor: 'var(--primary) !important'
}
function MyApp ({ Component, pageProps: { session, ...props } }) {
const client = getApolloClient()
const darkMode = useDarkMode(false, {
// set this so it doesn't try to use clas
onChange: (e) => { console.log(e) }
})
/*
If we are on the client, we populate the apollo cache with the
ssr data
@ -113,18 +31,15 @@ function MyApp ({ Component, pageProps: { session, ...props } }) {
<Provider session={session}>
<ApolloProvider client={client}>
<MeProvider>
<ThemeProvider theme={darkMode.value ? darkTheme : lightTheme}>
<GlobalStyle />
<LightningProvider>
<FundErrorProvider>
<FundErrorModal />
<ItemActProvider>
<ItemActModal />
<Component {...props} />
</ItemActProvider>
</FundErrorProvider>
</LightningProvider>
</ThemeProvider>
<LightningProvider>
<FundErrorProvider>
<FundErrorModal />
<ItemActProvider>
<ItemActModal />
<Component {...props} />
</ItemActProvider>
</FundErrorProvider>
</LightningProvider>
</MeProvider>
</ApolloProvider>
</Provider>

View File

@ -1,30 +1,18 @@
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
import Document, { Html, Head, Main, NextScript } from 'next/document'
export default class MyDocument extends Document {
static async getInitialProps (ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />)
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
)
}
} finally {
sheet.seal()
}
class MyDocument extends Document {
render () {
return (
<Html>
<Head />
<body>
<script src='darkmode.js' />
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument

View File

@ -3,10 +3,9 @@ import Items from '../../components/items'
import { useRouter } from 'next/router'
import { getGetServerSideProps } from '../../api/ssrApollo'
import { MORE_ITEMS } from '../../fragments/items'
import { Nav } from 'react-bootstrap'
import { Nav, Navbar } from 'react-bootstrap'
import styles from '../../components/header.module.css'
import Link from 'next/link'
import { StyledNavbar } from '../../components/header'
export const getServerSideProps = getGetServerSideProps(MORE_ITEMS, { sort: 'top'})
@ -16,7 +15,7 @@ export default function Index ({ data: { moreItems: { items, cursor } } }) {
return (
<Layout>
<StyledNavbar>
<Navbar>
<Nav
className={styles.navbarNav}
activeKey={path}
@ -67,7 +66,7 @@ export default function Index ({ data: { moreItems: { items, cursor } } }) {
</Link>
</Nav.Item>
</Nav>
</StyledNavbar>
</Navbar>
<Items
items={items} cursor={cursor}
variables={{ sort: 'top', within: router.query?.within }} rank

71
public/darkmode.js Normal file
View File

@ -0,0 +1,71 @@
// Insert this script in your index.html right after the <body> tag.
// This will help to prevent a flash if dark mode is the default.
export const COLORS = {
light: {
body: '#f5f5f5',
color: '#212529',
navbarVariant: 'light',
navLink: 'rgba(0, 0, 0, 0.5)',
navLinkFocus: 'rgba(0, 0, 0, 0.7)',
navLinkActive: 'rgba(0, 0, 0, 0.9)',
borderColor: '#ced4da',
inputBg: '#ffffff',
dropdownItemColor: 'inherit',
dropdownItemColorHover: 'rgba(0, 0, 0, 0.9)',
commentBg: 'rgba(0, 0, 0, 0.03)',
clickToContextColor: 'rgba(0, 0, 0, 0.05)',
brandColor: 'rgba(0, 0, 0, 0.9)'
},
dark: {
body: '#000000',
inputBg: '#000000',
navLink: 'rgba(255, 255, 255, 0.5)',
navLinkFocus: 'rgba(255, 255, 255, 0.75)',
navLinkActive: 'rgba(255, 255, 255, 0.9)',
borderColor: 'rgba(255, 255, 255, 0.5)',
dropdownItemColor: 'rgba(255, 255, 255, 0.7)',
dropdownItemColorHover: 'rgba(255, 255, 255, 0.9)',
commentBg: 'rgba(255, 255, 255, 0.04)',
clickToContextColor: 'rgba(255, 255, 255, 0.08)',
color: '#f8f9fa',
brandColor: 'var(--primary)'
}
}
export const handleThemeChange = (dark) => {
const root = window.document.documentElement
const colors = COLORS[dark ? 'dark' : 'light']
Object.entries(colors).forEach(([varName, value]) => {
const cssVarName = `--theme-${varName}`
root.style.setProperty(cssVarName, value)
})
}
if (typeof window !== 'undefined') {
(function () {
// Change these if you use something different in your hook.
const storageKey = 'darkMode'
const preferDarkQuery = '(prefers-color-scheme: dark)'
const mql = window.matchMedia(preferDarkQuery)
const supportsColorSchemeQuery = mql.media === preferDarkQuery
let localStorageTheme = null
try {
localStorageTheme = localStorage.getItem(storageKey)
} catch (err) {}
const localStorageExists = localStorageTheme !== null
if (localStorageExists) {
localStorageTheme = JSON.parse(localStorageTheme)
}
// Determine the source of truth
if (localStorageExists) {
// source of truth from localStorage
handleThemeChange(localStorageTheme)
} else if (supportsColorSchemeQuery) {
// source of truth from system
handleThemeChange(mql.matches)
localStorage.setItem(storageKey, mql.matches)
}
})()
}

View File

@ -60,12 +60,103 @@ $tooltip-bg: #5c8001;
}
body {
background-image: linear-gradient(180deg, #f5f5f5, #f5f5f5, white);
background-attachment:fixed;
background: var(--theme-body);
color: var(--theme-color);
min-height: 100vh;
height: 100%;
}
.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link {
color: inherit;
background-color: var(--theme-inputBg);
border-color: var(--theme-borderColor);
border-bottom-color: var(--theme-inputBg);
}
.form-control {
background-color: var(--theme-inputBg);
color: var(--theme-color);
border-color: var(--theme-borderColor);
}
.form-control:focus {
background-color: var(--theme-inputBg);
color: var(--theme-color);
}
.form-control:disabled, .form-control[readonly] {
background-color: var(--theme-inputBg);
border-color: var(--theme-borderColor);
opacity: 1;
}
.clickToContext {
border-radius: .4rem;
padding: .2rem 0;
cursor: pointer;
}
.clickToContext:hover {
background-color: var(--theme-clickToContextColor);
}
.fresh {
background-color: var(--theme-clickToContextColor);
border-radius: .4rem;
}
.modal-content {
background-color: var(--theme-body);
border-color: var(--theme-borderColor);
}
.navbar-nav .nav-link:not(.text-success) {
color: var(--theme-navLink) !important;
}
.navbar-nav .nav-link:not(.text-success):hover, .navbar-nav .nav-link:not(.text-success):focus {
color: var(--theme-navLinkFocus) !important;
}
.nav-dropdown-toggle .btn-link {
color: var(--theme-navLink) !important;
}
.nav-dropdown-toggle .btn-link[aria-expanded="true"] {
color: var(--theme-navLinkFocus) !important;
}
.navbar-nav .dropdown-menu {
background-color: var(--theme-body);
border: 1px solid var(--theme-borderColor);
}
.navbar-nav .dropdown-item {
color: var(--theme-dropdownItemColor);
}
.navbar-nav .dropdown-item:hover {
color: var(--theme-dropdownItemColorHover);
}
.navbar-nav .dropdown-item.active {
color: var(--theme-brandColor);
text-shadow: 0 0 10px var(--primary);
}
.navbar-nav .dropdown-divider {
border-top: 1px solid var(--theme-borderColor);
}
.theme {
cursor: pointer;
fill: var(--theme-dropdownItemColor);
}
.theme:hover {
fill: var(--theme-dropdownItemColorHover);
}
#__next {
display: flex;
flex-direction: column;
@ -143,14 +234,6 @@ footer {
display: inline-block;
}
.nav-dropdown-toggle .btn-link {
color: rgba(0, 0, 0, 0.5) !important;
}
.nav-dropdown-toggle .btn-link[aria-expanded="true"] {
color: rgba(0, 0, 0, 0.9) !important;
}
.nav-dropdown-toggle .dropdown .btn {
padding: 0;
}
@ -200,6 +283,10 @@ footer {
font-weight: bold;
}
.nav-link.active:not(.text-success) {
color: var(--theme-navLinkActive) !important;
}
.fill-grey {
fill: grey;
}