From 794607919a881390fc2aaa42615f2d8f71c83545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Mon, 28 Oct 2019 17:52:24 +0000 Subject: [PATCH] feat(ui): experimental dark theme Very ugly, might be buggy, but need to start somewhere --- ui/src/App.test.js | 12 ++ ui/src/App.tsx | 7 + .../Grid/AlertGrid/AlertGroup/index.js | 2 +- .../Configuration/AlertGroupTitleBarColor.js | 2 +- .../Configuration/ThemeConfiguration.js | 57 +++++++ .../Configuration/ThemeConfiguration.test.js | 54 ++++++ .../AlertGroupTitleBarColor.test.js.snap | 2 +- .../ThemeConfiguration.test.js.snap | 25 +++ .../__snapshots__/index.test.js.snap | 23 ++- .../MainModal/Configuration/index.js | 10 +- .../MainModalContent.test.js.snap | 23 ++- ui/src/DarkTheme.scss | 159 ++++++++++++++++++ ui/src/Stores/Settings.js | 16 ++ ui/src/__mocks__/Defaults.js | 3 +- 14 files changed, 385 insertions(+), 10 deletions(-) create mode 100644 ui/src/Components/MainModal/Configuration/ThemeConfiguration.js create mode 100644 ui/src/Components/MainModal/Configuration/ThemeConfiguration.test.js create mode 100644 ui/src/Components/MainModal/Configuration/__snapshots__/ThemeConfiguration.test.js.snap create mode 100644 ui/src/DarkTheme.scss diff --git a/ui/src/App.test.js b/ui/src/App.test.js index 36f95d8d4..03fe95049 100644 --- a/ui/src/App.test.js +++ b/ui/src/App.test.js @@ -154,4 +154,16 @@ describe("", () => { let event = new PopStateEvent("popstate"); window.onpopstate(event); }); + + it("appends 'dark-theme' class to #root if dark mode is enabled", () => { + const tree = shallow( + + ); + tree.instance().componentWillUnmount(); + + expect(document.body.className.split(" ")).toContain("dark-theme"); + }); }); diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 00f962665..f3e7abd5f 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -12,11 +12,13 @@ import { FaviconBadge } from "Components/FaviconBadge"; import { ErrorBoundary } from "./ErrorBoundary"; import "./App.scss"; +import "./DarkTheme.scss"; interface UIDefaults { Refresh: number; HideFiltersWhenIdle: boolean; ColorTitlebar: boolean; + DarkTheme: boolean; MinimalGroupWidth: number; AlertsPerGroup: number; CollapseGroups: "expanded" | "collapsed" | "collapsedOnMobile"; @@ -70,6 +72,11 @@ class App extends Component { componentDidMount() { window.onpopstate = this.onPopState; + + document.body.classList.toggle( + "dark-theme", + this.settingsStore.themeConfig.config.darkTheme + ); } componentWillUnmount() { diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js index 5441f759e..410edd3e3 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js @@ -224,7 +224,7 @@ const AlertGroup = observer( setIsMenuOpen={this.renderConfig.setIsMenuOpen} /> {this.collapse.value ? null : ( -
+
    {group.alerts .slice(0, this.renderConfig.alertsToRender) diff --git a/ui/src/Components/MainModal/Configuration/AlertGroupTitleBarColor.js b/ui/src/Components/MainModal/Configuration/AlertGroupTitleBarColor.js index 8bf71aca2..9270cec1a 100644 --- a/ui/src/Components/MainModal/Configuration/AlertGroupTitleBarColor.js +++ b/ui/src/Components/MainModal/Configuration/AlertGroupTitleBarColor.js @@ -22,7 +22,7 @@ const AlertGroupTitleBarColor = observer( const { settingsStore } = this.props; return ( -
    +
    { + const { settingsStore } = this.props; + settingsStore.themeConfig.config.darkTheme = event.target.checked; + + document.body.classList.toggle( + "dark-theme", + settingsStore.themeConfig.config.darkTheme + ); + }); + + render() { + const { settingsStore } = this.props; + + return ( +
    +
    + + + + + Experimental + + +
    +
    + ); + } + } +); + +export { ThemeConfiguration }; diff --git a/ui/src/Components/MainModal/Configuration/ThemeConfiguration.test.js b/ui/src/Components/MainModal/Configuration/ThemeConfiguration.test.js new file mode 100644 index 000000000..6e73f4117 --- /dev/null +++ b/ui/src/Components/MainModal/Configuration/ThemeConfiguration.test.js @@ -0,0 +1,54 @@ +import React from "react"; + +import { mount } from "enzyme"; + +import toDiffableHtml from "diffable-html"; + +import { Settings } from "Stores/Settings"; +import { ThemeConfiguration } from "./ThemeConfiguration"; + +let settingsStore; +beforeEach(() => { + settingsStore = new Settings(); +}); + +const FakeConfiguration = () => { + return mount(); +}; + +describe("", () => { + it("matches snapshot with default values", () => { + const tree = FakeConfiguration(); + expect(toDiffableHtml(tree.html())).toMatchSnapshot(); + }); + + it("darkTheme is 'false' by default", () => { + expect(settingsStore.themeConfig.config.darkTheme).toBe(false); + }); + + it("unchecking the checkbox sets stored darkTheme value to 'false'", done => { + const tree = FakeConfiguration(); + const checkbox = tree.find("#configuration-theme"); + + settingsStore.themeConfig.config.darkTheme = true; + expect(settingsStore.themeConfig.config.darkTheme).toBe(true); + checkbox.simulate("change", { target: { checked: false } }); + setTimeout(() => { + expect(settingsStore.themeConfig.config.darkTheme).toBe(false); + done(); + }, 200); + }); + + it("checking the checkbox sets stored darkTheme value to 'true'", done => { + const tree = FakeConfiguration(); + const checkbox = tree.find("#configuration-theme"); + + settingsStore.themeConfig.config.darkTheme = false; + expect(settingsStore.themeConfig.config.darkTheme).toBe(false); + checkbox.simulate("change", { target: { checked: true } }); + setTimeout(() => { + expect(settingsStore.themeConfig.config.darkTheme).toBe(true); + done(); + }, 200); + }); +}); diff --git a/ui/src/Components/MainModal/Configuration/__snapshots__/AlertGroupTitleBarColor.test.js.snap b/ui/src/Components/MainModal/Configuration/__snapshots__/AlertGroupTitleBarColor.test.js.snap index 1dc05774d..db92ecc3f 100644 --- a/ui/src/Components/MainModal/Configuration/__snapshots__/AlertGroupTitleBarColor.test.js.snap +++ b/ui/src/Components/MainModal/Configuration/__snapshots__/AlertGroupTitleBarColor.test.js.snap @@ -2,7 +2,7 @@ exports[` matches snapshot with default values 1`] = ` " -
    +
    matches snapshot with default values 1`] = ` +" +
    +
    + + + + + Experimental + + +
    +
    +" +`; diff --git a/ui/src/Components/MainModal/Configuration/__snapshots__/index.test.js.snap b/ui/src/Components/MainModal/Configuration/__snapshots__/index.test.js.snap index aa5924138..d6542ef7d 100644 --- a/ui/src/Components/MainModal/Configuration/__snapshots__/index.test.js.snap +++ b/ui/src/Components/MainModal/Configuration/__snapshots__/index.test.js.snap @@ -126,7 +126,7 @@ exports[` matches snapshot 1`] = `
    - Alert group titlebar configuration + Theme
    matches snapshot 1`] = ` style=\\"height:0;-webkit-transition:height 50ms linear;-ms-transition:height 50ms linear;transition:height 50ms linear;overflow:hidden\\" >
    -
    +
    matches snapshot 1`] = `
    +
    +
    + + + + + Experimental + + +
    +
    diff --git a/ui/src/Components/MainModal/Configuration/index.js b/ui/src/Components/MainModal/Configuration/index.js index 75c2014aa..9f7065dcb 100644 --- a/ui/src/Components/MainModal/Configuration/index.js +++ b/ui/src/Components/MainModal/Configuration/index.js @@ -10,6 +10,7 @@ import { AlertGroupWidthConfiguration } from "./AlertGroupWidthConfiguration"; import { AlertGroupSortConfiguration } from "./AlertGroupSortConfiguration"; import { AlertGroupCollapseConfiguration } from "./AlertGroupCollapseConfiguration"; import { AlertGroupTitleBarColor } from "./AlertGroupTitleBarColor"; +import { ThemeConfiguration } from "./ThemeConfiguration"; const Configuration = ({ settingsStore }) => (
    @@ -23,8 +24,13 @@ const Configuration = ({ settingsStore }) => ( content={} /> } + text="Theme" + content={ + + + + + } /> matches snapshot 1`] = `
    - Alert group titlebar configuration + Theme
    matches snapshot 1`] = ` style=\\"height: 0px; transition: height 50ms linear; overflow: hidden;\\" >
    -
    +
    matches snapshot 1`] = `
    +
    +
    + + + + + Experimental + + +
    +
    diff --git a/ui/src/DarkTheme.scss b/ui/src/DarkTheme.scss new file mode 100644 index 000000000..69abd4616 --- /dev/null +++ b/ui/src/DarkTheme.scss @@ -0,0 +1,159 @@ +@import "src/App.scss"; + +$alertgroup-body-bg: #5a6e7b; +$alertgroup-header-bg: #515658; +$alertgroup-footer-bg: #728998; +$alertgroup-border-inside: #485862; + +$silence-bg: #c3cdd5; +$silence-progress-bg: #eaeef0; + +$badge-light-bg: #d2dae0; + +.dark-theme .card.bg-light { + border-color: #3c3e3e !important; +} +.dark-theme .components-grid-alertgrid-card { + &.card-body, + & > .list-group, + & > .list-group > .list-group-item { + background-color: $alertgroup-body-bg !important; + } +} +.dark-theme .card.bg-light > .card-header { + background-color: $alertgroup-header-bg !important; +} +.dark-theme .card > .card-header { + border-bottom-color: $alertgroup-border-inside !important; +} +.dark-theme .bg-card-footer-default { + background-color: $alertgroup-footer-bg !important; + border-top-color: $alertgroup-border-inside !important; +} + +.dark-theme .btn.bg-white { + background-color: $alertgroup-body-bg !important; +} + +.dark-theme { + & .components-grid-annotation .text-muted, + & .components-managed-silence-cite .text-muted, + & .components-managed-silence svg.text-muted, + & .text-muted.fa-bell-slash { + color: $dark !important; + } + & .text-muted { + color: $white !important; + } +} + +.dark-theme .components-managed-silence { + border-left-color: $dark; + + & > .card-header, + & > .card-body { + background-color: $silence-bg !important; + } +} + +.dark-theme { + & .modal-content { + background-color: $alertgroup-body-bg; + + & .list-group-item { + background-color: $alertgroup-body-bg; + } + } + & .modal-header { + border-bottom-color: $alertgroup-border-inside; + } + & .modal-footer { + border-top-color: $alertgroup-border-inside; + } + + & .components-grid-annotation.bg-light { + background-color: $silence-bg !important; + } + + & .badge.badge-light { + background-color: $badge-light-bg !important; + } + & .components-filteredinputlabel > .badge-pill.badge-light { + background-color: $badge-light-bg !important; + filter: none; + } + + & .dropdown-menu { + background-color: $silence-bg !important; + } + & .dropdown-header { + color: $dark !important; + } + + & .progress.bg-white { + background-color: $silence-progress-bg !important; + } +} + +.dark-theme { + & .react-select__control, + & .react-select__indicators, + & .react-select__value-container { + background-color: $alertgroup-body-bg !important; + } + & .react-select__menu { + background-color: $silence-bg !important; + } + + & .tab-content, + & .tab-content .text-muted, + & .nav-item:not(.components-tab-inactive), + & .custom-control-label, + & .react-select__placeholder { + color: $white !important; + } + + & .nav-link.active { + color: $black !important; + background-color: $silence-bg !important; + } + + & .form-control { + color: $gray-200 !important; + background-color: $alertgroup-body-bg !important; + } + & .input-group-text { + background-color: $silence-bg !important; + } + + & .react-datepicker, + & .react-datepicker__header, + & .react-datepicker__today-button { + background-color: $silence-bg !important; + } +} + +.dark-theme { + & .jumbotron.bg-white { + background-color: $alertgroup-body-bg !important; + } +} + +.dark-theme { + & .collapse, + & .collapse > .card-body { + color: $white; + background-color: $alertgroup-body-bg !important; + } + & .Collapsible > .card-header { + background-color: $alertgroup-footer-bg !important; + border-bottom-color: $alertgroup-border-inside !important; + } + & .input-range__label, + & .Collapsible__trigger.bg-light { + color: $white; + } + & .Collapsible__trigger.is-closed { + color: $black; + } +} diff --git a/ui/src/Stores/Settings.js b/ui/src/Stores/Settings.js index b9ac01396..4a714617c 100644 --- a/ui/src/Stores/Settings.js +++ b/ui/src/Stores/Settings.js @@ -110,6 +110,20 @@ class FilterBarConfig { } } +class ThemeConfig { + constructor(darkTheme) { + this.config = localStored( + "themeConfig", + { + darkTheme: darkTheme + }, + { + delay: 100 + } + ); + } +} + class Settings { constructor(defaults) { let defaultSettings; @@ -118,6 +132,7 @@ class Settings { Refresh: 30 * 1000 * 1000 * 1000, HideFiltersWhenIdle: true, ColorTitlebar: false, + DarkTheme: false, MinimalGroupWidth: 420, AlertsPerGroup: 5, CollapseGroups: "collapsedOnMobile" @@ -140,6 +155,7 @@ class Settings { this.filterBarConfig = new FilterBarConfig( defaultSettings.HideFiltersWhenIdle ); + this.themeConfig = new ThemeConfig(defaultSettings.DarkTheme); } } diff --git a/ui/src/__mocks__/Defaults.js b/ui/src/__mocks__/Defaults.js index d647135c3..381458357 100644 --- a/ui/src/__mocks__/Defaults.js +++ b/ui/src/__mocks__/Defaults.js @@ -1,9 +1,10 @@ const DefaultsBase64 = - "eyJSZWZyZXNoIjo0NTAwMDAwMDAwMCwiSGlkZUZpbHRlcnNXaGVuSWRsZSI6ZmFsc2UsIkNvbG9yVGl0bGViYXIiOmZhbHNlLCJNaW5pbWFsR3JvdXBXaWR0aCI6NTU1LCJBbGVydHNQZXJHcm91cCI6MTUsIkNvbGxhcHNlR3JvdXBzIjoiZXhwYW5kZWQifQo="; + "eyJSZWZyZXNoIjo0NTAwMDAwMDAwMCwiSGlkZUZpbHRlcnNXaGVuSWRsZSI6ZmFsc2UsIkNvbG9yVGl0bGViYXIiOmZhbHNlLCJEYXJrTW9kZSI6ZmFsc2UsIk1pbmltYWxHcm91cFdpZHRoIjo1NTUsIkFsZXJ0c1Blckdyb3VwIjoxNSwiQ29sbGFwc2VHcm91cHMiOiJleHBhbmRlZCJ9Cg=="; const DefaultsObject = { Refresh: 45000000000, HideFiltersWhenIdle: false, ColorTitlebar: false, + DarkMode: false, MinimalGroupWidth: 555, AlertsPerGroup: 15, CollapseGroups: "expanded"