Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds dark mode to entire dashboard #467

Merged
merged 33 commits into from
Dec 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b5c1305
Adds New Dark Mode Assets
Vigasaurus Dec 11, 2020
fc15757
Moves triangle for dropdown to a reasonable position
Vigasaurus Dec 11, 2020
232d733
Majority .eex dark implementation
Vigasaurus Dec 11, 2020
f89f21f
Fixes Logo Positioning
Vigasaurus Dec 12, 2020
0d3a2a8
Adds theme flag to user schema, uses it
Vigasaurus Dec 12, 2020
51ba28e
Uses correct variables for theme applicator script
Vigasaurus Dec 12, 2020
b49bb75
Minor missed theme changes/fallbacks
Vigasaurus Dec 12, 2020
9b41185
Individual Component Support + Theme Context
Vigasaurus Dec 12, 2020
10d05a4
Sources Tab Support
Vigasaurus Dec 12, 2020
bee6f4e
Partial Stats Sections Support
Vigasaurus Dec 12, 2020
03d3975
More of stats modules supported
Vigasaurus Dec 12, 2020
8b43c6c
Modal +table support
Vigasaurus Dec 12, 2020
3f1d09f
Improves some Flatpickr in light theme, supports dark theme
Vigasaurus Dec 13, 2020
a49de44
Fixes missed settings tab colors
Vigasaurus Dec 13, 2020
cc46ff1
Finishes Devices module support
Vigasaurus Dec 13, 2020
df89c0f
Fixes bar graph colors
Vigasaurus Dec 13, 2020
eb7eda8
Better colorizes maps module
Vigasaurus Dec 13, 2020
90212fd
Undoes colorized bars
Vigasaurus Dec 13, 2020
9b010c2
Fixes loading indicator
Vigasaurus Dec 13, 2020
99ed154
Finishes conversions module
Vigasaurus Dec 13, 2020
4ce2e6a
Adds changelog entry
Vigasaurus Dec 13, 2020
c0cf932
Merge branch 'master' into dark-mode
Vigasaurus Dec 13, 2020
365c4e2
Fixes missed header color
Vigasaurus Dec 13, 2020
dcb4a8f
Merge branch 'dark-mode' of github.com:Vigasaurus/plausible-analytics…
Vigasaurus Dec 13, 2020
b823d1f
Fixes naming of migration and removes static alter
Vigasaurus Dec 14, 2020
e87921e
Does migration correctly
Vigasaurus Dec 14, 2020
9a5a5d4
Adds support for spike notifications setting
Vigasaurus Dec 14, 2020
f0719d9
Improves contrast and visibility for email settings
Vigasaurus Dec 14, 2020
0fe25d0
Merge 'master' into 'dark-mode', support new onboarding dark mode
Vigasaurus Dec 15, 2020
1bfe5bb
Resolves @ukutaht's comments on #467
Vigasaurus Dec 15, 2020
3782a67
Fixes missing dark style
Vigasaurus Dec 15, 2020
d7d648c
Found one more missed dark element (shared links)
Vigasaurus Dec 16, 2020
4e09b37
Formatting fixes
Vigasaurus Dec 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
18 changes: 16 additions & 2 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -156,7 +157,7 @@ blockquote {

.dropdown-content::before {
top: -16px;
right: 64px;
right: 8px;
left: auto;
}
.dropdown-content::before {
Expand All @@ -174,7 +175,7 @@ blockquote {
}
.dropdown-content::after {
top: -14px;
right: 65px;
right: 9px;
left: auto;
}

Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
110 changes: 110 additions & 0 deletions assets/css/flatpickr.dark.css
Original file line number Diff line number Diff line change
@@ -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;
}
5 changes: 5 additions & 0 deletions assets/css/loader.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
22 changes: 11 additions & 11 deletions assets/js/dashboard/datepicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ class DatePicker extends React.Component {

renderArrow(period, prevDate, nextDate) {
return (
<div className="flex rounded shadow bg-white mr-4 cursor-pointer">
<QueryLink to={{date: prevDate}} query={this.props.query} className="flex items-center px-2 border-r border-gray-300">
<div className="flex rounded shadow bg-white dark:bg-gray-800 mr-4 cursor-pointer">
<QueryLink to={{date: prevDate}} query={this.props.query} className="flex items-center px-2 border-r border-gray-300 dark:border-gray-500 dark:text-gray-100">
<svg className="feather h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
</QueryLink>
<QueryLink to={{date: nextDate}} query={this.props.query} className="flex items-center px-2">
<QueryLink to={{date: nextDate}} query={this.props.query} className="flex items-center px-2 dark:text-gray-100">
<svg className="feather h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
</QueryLink>
</div>
Expand Down Expand Up @@ -135,7 +135,7 @@ class DatePicker extends React.Component {
renderDropDown() {
return (
<div className="relative" style={{height: '35.5px', width: '190px'}} ref={node => this.dropDownNode = node}>
<div onClick={this.open.bind(this)} className="flex items-center justify-between rounded bg-white shadow px-4 pr-3 py-2 leading-tight cursor-pointer text-sm font-medium text-gray-800 h-full">
<div onClick={this.open.bind(this)} className="flex items-center justify-between rounded bg-white dark:bg-gray-800 shadow px-4 pr-3 py-2 leading-tight cursor-pointer text-sm font-medium text-gray-800 dark:text-gray-200 h-full">
<span className="mr-2">{this.timeFrameText()}</span>
<svg className="text-pink-500 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
Expand Down Expand Up @@ -176,7 +176,7 @@ class DatePicker extends React.Component {
if (opts.date) { opts.date = formatISO(opts.date) }

return (
<QueryLink to={{period, ...opts}} onClick={this.close.bind(this)} query={this.props.query} className={boldClass + ' block px-4 py-2 text-sm leading-tight hover:bg-gray-100 hover:text-gray-900'}>
<QueryLink to={{period, ...opts}} onClick={this.close.bind(this)} query={this.props.query} className={boldClass + ' 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'}>
{text}
</QueryLink>
)
Expand All @@ -186,29 +186,29 @@ class DatePicker extends React.Component {
if (this.state.mode === 'menu') {
return (
<div className="absolute mt-2 rounded shadow-md z-10" style={{width: '235px', right: '-14px'}}>
<div className="rounded bg-white ring-1 ring-black ring-opacity-5 font-medium text-gray-800">
<div className="rounded bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5 font-medium text-gray-800 dark:text-gray-200">
<div className="py-1">
{ this.renderLink('day', 'Today') }
{ this.renderLink('realtime', 'Realtime') }
</div>
<div className="border-t border-gray-200"></div>
<div className="border-t border-gray-200 dark:border-gray-500"></div>
<div className="py-1">
{ this.renderLink('7d', 'Last 7 days') }
{ this.renderLink('30d', 'Last 30 days') }
</div>
<div className="border-t border-gray-200"></div>
<div className="border-t border-gray-200 dark:border-gray-500"></div>
<div className="py-1">
{ this.renderLink('month', 'This month') }
{ this.renderLink('month', 'Last month', {date: lastMonth(this.props.site)}) }
</div>
<div className="border-t border-gray-200"></div>
<div className="border-t border-gray-200 dark:border-gray-500"></div>
<div className="py-1">
{ this.renderLink('6mo', 'Last 6 months') }
{ this.renderLink('12mo', 'Last 12 months') }
</div>
<div className="border-t border-gray-200"></div>
<div className="border-t border-gray-200 dark:border-gray-500"></div>
<div className="py-1">
<span onClick={e => 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</span>
<span onClick={e => 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</span>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion assets/js/dashboard/error-boundary.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default class ErrorBoundary extends React.Component {
render() {
if (this.state.error) {
return (
<div className="text-center text-gray-900 mt-36">
<div className="text-center text-gray-900 dark:text-gray-100 mt-36">
<RocketIcon />
<div className="text-lg font-bold">Oops! Something went wrong</div>
<div className="text-lg">{this.state.error.name + ': ' + this.state.error.message}</div>
Expand Down
2 changes: 1 addition & 1 deletion assets/js/dashboard/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ function renderFilter(history, [key, value], query) {
}

return (
<span key={key} title={value} className="inline-flex bg-white text-gray-700 shadow text-sm rounded py-2 px-3 mr-4">
<span key={key} title={value} className="inline-flex bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 shadow text-sm rounded py-2 px-3 mr-4">
{filterText(key, value, query)} <b className="ml-1 cursor-pointer" onClick={removeFilter}>✕</b>
</span>
)
Expand Down
4 changes: 2 additions & 2 deletions assets/js/dashboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -51,4 +51,4 @@ class Dashboard extends React.Component {
}
}

export default withRouter(Dashboard)
export default withRouter(withThemeProvider(Dashboard))
18 changes: 9 additions & 9 deletions assets/js/dashboard/site-switcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<a href={`/${encodeURIComponent(domain)}`} key={domain} className={`block truncate px-4 py-2 text-sm leading-5 text-gray-700 ${extraClass}`}>
<a href={`/${encodeURIComponent(domain)}`} key={domain} className={`block truncate px-4 py-2 text-sm leading-5 text-gray-700 dark:text-gray-300 ${extraClass}`}>
<img src={`https://icons.duckduckgo.com/ip3/${domain}.ico`} referrerPolicy="no-referrer" className="inline w-4 mr-2 align-middle" />
<span>{domain}</span>
</a>
Expand All @@ -58,17 +58,17 @@ export default class SiteSwitcher extends React.Component {
if (this.state.loading) {
return <div className="px-4 py-6"><div className="loading sm mx-auto"><div></div></div></div>
} else if (this.state.error) {
return <div className="mx-auto px-4 py-6">Something went wrong, try again</div>
return <div className="mx-auto px-4 py-6 dark:text-gray-100">Something went wrong, try again</div>
} else {
return (
<React.Fragment>
<div className="py-1">
<a href={`/${encodeURIComponent(this.props.site.domain)}/settings`} className="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" role="menuitem">
<svg viewBox="0 0 20 20" fill="currentColor" className="mr-2 h-4 w-4 text-gray-500 group-hover:text-gray-600 group-focus:text-gray-500"><path d="M5 4a1 1 0 00-2 0v7.268a2 2 0 000 3.464V16a1 1 0 102 0v-1.268a2 2 0 000-3.464V4zM11 4a1 1 0 10-2 0v1.268a2 2 0 000 3.464V16a1 1 0 102 0V8.732a2 2 0 000-3.464V4zM16 3a1 1 0 011 1v7.268a2 2 0 010 3.464V16a1 1 0 11-2 0v-1.268a2 2 0 010-3.464V4a1 1 0 011-1z" /></svg>
<a href={`/${encodeURIComponent(this.props.site.domain)}/settings`} className="group flex items-center px-4 py-2 text-sm leading-5 text-gray-700 dark:text-gray-300 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" role="menuitem">
<svg viewBox="0 0 20 20" fill="currentColor" className="mr-2 h-4 w-4 text-gray-500 dark:text-gray-200 group-hover:text-gray-600 dark:group-hover:text-gray-400 group-focus:text-gray-500 dark:group-focus:text-gray-200"><path d="M5 4a1 1 0 00-2 0v7.268a2 2 0 000 3.464V16a1 1 0 102 0v-1.268a2 2 0 000-3.464V4zM11 4a1 1 0 10-2 0v1.268a2 2 0 000 3.464V16a1 1 0 102 0V8.732a2 2 0 000-3.464V4zM16 3a1 1 0 011 1v7.268a2 2 0 010 3.464V16a1 1 0 11-2 0v-1.268a2 2 0 010-3.464V4a1 1 0 011-1z" /></svg>
Site settings
</a>
</div>
<div className="border-t border-gray-100"></div>
<div className="border-t border-gray-100 dark:border-gray-900"></div>
<div className="py-1">
{ this.state.sites.map(this.renderSiteLink.bind(this)) }
</div>
Expand All @@ -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 (
<div className="relative inline-block text-left z-10 mr-8">
<button onClick={this.toggle.bind(this)} className={`inline-flex items-center text-lg w-full rounded-md py-2 leading-5 font-bold text-gray-700 focus:outline-none transition ease-in-out duration-150 ${hoverClass}`}>
<button onClick={this.toggle.bind(this)} className={`inline-flex items-center text-lg w-full rounded-md py-2 leading-5 font-bold text-gray-700 dark:text-gray-300 focus:outline-none transition ease-in-out duration-150 ${hoverClass}`}>

<img src={`https://icons.duckduckgo.com/ip3/${this.props.site.domain}.ico`} referrerPolicy="no-referrer" className="inline w-4 mr-2 align-middle" />
{this.props.site.domain}
Expand All @@ -109,7 +109,7 @@ export default class SiteSwitcher extends React.Component {
leaveTo="opacity-0 scale-95"
>
<div className="origin-top-left absolute left-0 mt-2 w-64 rounded-md shadow-lg" ref={node => this.dropDownNode = node} >
<div className="rounded-md bg-white ring-1 ring-black ring-opacity-5">
<div className="rounded-md bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5">
{ this.renderDropdown() }
</div>
</div>
Expand Down
14 changes: 7 additions & 7 deletions assets/js/dashboard/stats/conversions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ export default class Conversions extends React.Component {
return (
<div className="my-2 text-sm" key={goal.name}>
<div className="flex items-center justify-between my-2">
<div className="w-full h-8 relative" style={{maxWidth: 'calc(100% - 16rem)'}}>
<Bar count={goal.count} all={this.state.goals} bg="bg-red-50" />
<div className="w-full h-8 relative dark:text-gray-300" style={{maxWidth: 'calc(100% - 16rem)'}}>
<Bar count={goal.count} all={this.state.goals} bg="bg-red-50 dark:bg-gray-500 dark:bg-opacity-15" />
{this.renderGoalText(goal.name)}
</div>
<div>
<div className="dark:text-gray-200">
<span className="font-medium inline-block w-20 text-right">{numberFormatter(goal.count)}</span>
<span className="font-medium inline-block w-20 text-right">{numberFormatter(goal.total_count)}</span>
<span className="font-medium inline-block w-20 text-right">{goal.conversion_rate}%</span>
Expand All @@ -68,15 +68,15 @@ export default class Conversions extends React.Component {
render() {
if (this.state.loading) {
return (
<div className="w-full bg-white shadow-xl rounded p-4" style={{height: '94px'}}>
<div className="w-full bg-white dark:bg-gray-825 shadow-xl rounded p-4" style={{height: '94px'}}>
<div className="loading my-2 mx-auto"><div></div></div>
</div>
)
} else if (this.state.goals) {
return (
<div className="w-full bg-white shadow-xl rounded p-4">
<h3 className="font-bold">{this.props.title || "Goal Conversions"}</h3>
<div className="flex items-center mt-3 mb-2 justify-between text-gray-500 text-xs font-bold tracking-wide">
<div className="w-full bg-white dark:bg-gray-825 shadow-xl rounded p-4">
<h3 className="font-bold dark:text-gray-100">{this.props.title || "Goal Conversions"}</h3>
<div className="flex items-center mt-3 mb-2 justify-between text-gray-500 dark:text-gray-400 text-xs font-bold tracking-wide">
<span>Goal</span>
<div className="text-right">
<span className="inline-block w-20">Uniques</span>
Expand Down
Loading