diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f988bc4430b..5048fab9adc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - Display weekday on the visitor graph plausible/analytics#175 - Collect and display browser & OS versions plausible/analytics#397 - Simple notifications around traffic spikes plausible/analytics#453 +- Dark theme option/system setting follow plausible/analytics#467 ### Changed - Use alpine as base image to decrease Docker image size plausible/analytics#353 diff --git a/assets/css/app.css b/assets/css/app.css index 69a4d122d04d..c9eeda96e655 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -5,6 +5,7 @@ @import "modal.css"; @import "loader.css"; @import "tooltip.css"; +@import "flatpickr.dark.css"; .button { @apply bg-indigo-600 border border-transparent rounded-md py-2 px-4 inline-flex justify-center text-sm leading-5 font-medium text-white transition hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500; @@ -156,7 +157,7 @@ blockquote { .dropdown-content::before { top: -16px; - right: 64px; + right: 8px; left: auto; } .dropdown-content::before { @@ -174,7 +175,7 @@ blockquote { } .dropdown-content::after { top: -14px; - right: 65px; + right: 9px; left: auto; } @@ -196,6 +197,14 @@ blockquote { background-color: #f1f5f8; } +.dark .table-striped tbody tr:nth-child(odd) { + background-color: rgb(37, 47, 63); +} + +.dark .table-striped tbody tr:nth-child(even) { + background-color: rgb(26, 32, 44); +} + .twitter-icon { width: 1.25em; height: 1.25em; @@ -252,3 +261,8 @@ blockquote { .datamaps-subunit { cursor: pointer; } + +/* Only because the map handler doesn't expose an easier way to change the shadow color */ +.dark .hoverinfo { + box-shadow: 1px 1px 5px rgb(26, 32, 44); +} diff --git a/assets/css/flatpickr.dark.css b/assets/css/flatpickr.dark.css new file mode 100644 index 000000000000..a4b6ad03e2ab --- /dev/null +++ b/assets/css/flatpickr.dark.css @@ -0,0 +1,110 @@ +/* Because Flatpickr offers zero support for dynamic theming on its own (outside of third-party plugins) */ +.dark .flatpickr-calendar { + background-color: #1f2937; +} + +.dark .flatpickr-weekday { + color: #f3f4f6; +} + +.dark .flatpickr-prev-month { + fill: #f3f4f6 !important; +} + +.dark .flatpickr-next-month { + fill: #f3f4f6 !important; +} + +.dark .flatpickr-monthDropdown-months { + color: #f3f4f6 !important; +} + +.dark .numInputWrapper { + color: #f3f4f6; +} + +.dark .flatpickr-day.prevMonthDay { + color: #94a3af; +} + +.dark .flatpickr-day { + color: #E5E7EB; +} + +.dark .flatpickr-day.prevMonthDay { + color: #9CA3AF; +} + +.dark .flatpickr-day.nextMonthDay { + color: #9CA3AF; +} + +.dark .flatpickr-day:hover { + background-color: #374151; +} + +.dark :not(.startRange):not(.endRange).flatpickr-day.nextMonthDay:hover { + background-color: #374151; +} + +.dark :not(.startRange):not(.endRange).flatpickr-day.prevMonthDay:hover { + background-color: #374151; +} + +.dark .flatpickr-next-month { + fill: #f3f4f6; +} + +.dark .flatpickr-day.flatpickr-disabled { + color: #4B5563; +} + +.dark .flatpickr-day.flatpickr-disabled:hover { + color: #4B5563; +} + +.dark .flatpickr-day.today { + background-color: rgba(167, 243, 208, 0.5); +} + +.dark .flatpickr-day.today { + border-color: #34D399; +} + +.dark .flatpickr-day.inRange { + background-color: #374151; + box-shadow: -5px 0 0 #374151,5px 0 0 #374151; + border-color: #374151; +} + +.dark .flatpickr-day.prevMonthDay.inRange { + background-color: #374151; + box-shadow: -5px 0 0 #374151,5px 0 0 #374151; + border-color: #374151; +} + +.dark .flatpickr-day.nextMonthDay.inRange { + background-color: #374151; + box-shadow: -5px 0 0 #374151,5px 0 0 #374151; + border-color: #374151; +} + +.flatpickr-day.startRange { + background: #6574cd !important; + border-color: #6574cd !important; +} + +.flatpickr-day.endRange { + background: #6574cd !important; + border-color: #6574cd !important; +} + +.dark .flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)), .flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)), .flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) { + -webkit-box-shadow: -10px 0 0 #4556c3 !important; + box-shadow: -10px 0 0 #4556c3 !important; +} + +.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)), .flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)), .flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) { + -webkit-box-shadow: -10px 0 0 #4556c3 !important; + box-shadow: -10px 0 0 #4556c3 !important; +} diff --git a/assets/css/loader.css b/assets/css/loader.css index 38af0f968718..a5dc562f1cab 100644 --- a/assets/css/loader.css +++ b/assets/css/loader.css @@ -20,6 +20,11 @@ -webkit-animation: spin 1s ease-in-out infinite; } +.dark .loading div { + border: 3px solid #606f7b; + border-top-color: #dae1e7; +} + .loading.sm div { width: 25px; height: 25px; diff --git a/assets/js/dashboard/datepicker.js b/assets/js/dashboard/datepicker.js index 5f6d74726236..a4521012777d 100644 --- a/assets/js/dashboard/datepicker.js +++ b/assets/js/dashboard/datepicker.js @@ -101,11 +101,11 @@ class DatePicker extends React.Component { renderArrow(period, prevDate, nextDate) { return ( -
- +
+ - +
@@ -135,7 +135,7 @@ class DatePicker extends React.Component { renderDropDown() { return (
this.dropDownNode = node}> -
+
{this.timeFrameText()} @@ -176,7 +176,7 @@ class DatePicker extends React.Component { if (opts.date) { opts.date = formatISO(opts.date) } return ( - + {text} ) @@ -186,29 +186,29 @@ class DatePicker extends React.Component { if (this.state.mode === 'menu') { return (
-
+
{ this.renderLink('day', 'Today') } { this.renderLink('realtime', 'Realtime') }
-
+
{ this.renderLink('7d', 'Last 7 days') } { this.renderLink('30d', 'Last 30 days') }
-
+
{ this.renderLink('month', 'This month') } { this.renderLink('month', 'Last month', {date: lastMonth(this.props.site)}) }
-
+
{ this.renderLink('6mo', 'Last 6 months') } { this.renderLink('12mo', 'Last 12 months') }
-
+
- this.setState({mode: 'calendar'}, this.openCalendar.bind(this))} className="block px-4 py-2 text-sm leading-tight hover:bg-gray-100 hover:text-gray-900 cursor-pointer">Custom range + this.setState({mode: 'calendar'}, this.openCalendar.bind(this))} className="block px-4 py-2 text-sm leading-tight hover:bg-gray-100 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-100 cursor-pointer">Custom range
diff --git a/assets/js/dashboard/error-boundary.js b/assets/js/dashboard/error-boundary.js index e10fdccc5375..b09ae8b3fc24 100644 --- a/assets/js/dashboard/error-boundary.js +++ b/assets/js/dashboard/error-boundary.js @@ -14,7 +14,7 @@ export default class ErrorBoundary extends React.Component { render() { if (this.state.error) { return ( -
+
Oops! Something went wrong
{this.state.error.name + ': ' + this.state.error.message}
diff --git a/assets/js/dashboard/filters.js b/assets/js/dashboard/filters.js index 5319579d4b22..032d7852a569 100644 --- a/assets/js/dashboard/filters.js +++ b/assets/js/dashboard/filters.js @@ -68,7 +68,7 @@ function renderFilter(history, [key, value], query) { } return ( - + {filterText(key, value, query)} ) diff --git a/assets/js/dashboard/index.js b/assets/js/dashboard/index.js index 5b0e37e86e80..d5143d90f0fe 100644 --- a/assets/js/dashboard/index.js +++ b/assets/js/dashboard/index.js @@ -5,7 +5,7 @@ import Historical from './historical' import Realtime from './realtime' import {parseQuery} from './query' import * as api from './api' - +import { withThemeProvider } from './theme-provider-hoc'; const THIRTY_SECONDS = 30000 @@ -51,4 +51,4 @@ class Dashboard extends React.Component { } } -export default withRouter(Dashboard) +export default withRouter(withThemeProvider(Dashboard)) diff --git a/assets/js/dashboard/site-switcher.js b/assets/js/dashboard/site-switcher.js index 2ebf661c54f6..0ac34d3e901d 100644 --- a/assets/js/dashboard/site-switcher.js +++ b/assets/js/dashboard/site-switcher.js @@ -45,9 +45,9 @@ export default class SiteSwitcher extends React.Component { } renderSiteLink(domain) { - const extraClass = domain === this.props.site.domain ? 'font-medium text-gray-900' : 'hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900' + const extraClass = domain === this.props.site.domain ? 'font-medium text-gray-900 dark:text-gray-100' : 'hover:bg-gray-100 dark:hover:bg-gray-900 hover:text-gray-900 dark:hover:text-gray-100 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-900 dark:focus:text-gray-100' return ( - + {domain} @@ -58,17 +58,17 @@ export default class SiteSwitcher extends React.Component { if (this.state.loading) { return
} else if (this.state.error) { - return
Something went wrong, try again
+ return
Something went wrong, try again
} else { return ( -
+
{ this.state.sites.map(this.renderSiteLink.bind(this)) }
@@ -88,11 +88,11 @@ export default class SiteSwitcher extends React.Component { } render() { - const hoverClass = this.props.loggedIn ? 'hover:text-gray-500 focus:border-blue-300 focus:ring ' : 'cursor-default' + const hoverClass = this.props.loggedIn ? 'hover:text-gray-500 dark:hover:text-gray-200 focus:border-blue-300 focus:ring ' : 'cursor-default' return (
-