mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
feat(ui): experimental dark theme
Very ugly, might be buggy, but need to start somewhere
This commit is contained in:
@@ -154,4 +154,16 @@ describe("<App />", () => {
|
||||
let event = new PopStateEvent("popstate");
|
||||
window.onpopstate(event);
|
||||
});
|
||||
|
||||
it("appends 'dark-theme' class to #root if dark mode is enabled", () => {
|
||||
const tree = shallow(
|
||||
<App
|
||||
defaultFilters={["foo=bar"]}
|
||||
uiDefaults={Object.assign({}, uiDefaults, { DarkMode: true })}
|
||||
/>
|
||||
);
|
||||
tree.instance().componentWillUnmount();
|
||||
|
||||
expect(document.body.className.split(" ")).toContain("dark-theme");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<AppProps, {}> {
|
||||
|
||||
componentDidMount() {
|
||||
window.onpopstate = this.onPopState;
|
||||
|
||||
document.body.classList.toggle(
|
||||
"dark-theme",
|
||||
this.settingsStore.themeConfig.config.darkTheme
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
@@ -224,7 +224,7 @@ const AlertGroup = observer(
|
||||
setIsMenuOpen={this.renderConfig.setIsMenuOpen}
|
||||
/>
|
||||
{this.collapse.value ? null : (
|
||||
<div className="card-body bg-white px-2 py-1">
|
||||
<div className="card-body px-2 py-1 bg-white components-grid-alertgrid-card">
|
||||
<ul className="list-group">
|
||||
{group.alerts
|
||||
.slice(0, this.renderConfig.alertsToRender)
|
||||
|
||||
@@ -22,7 +22,7 @@ const AlertGroupTitleBarColor = observer(
|
||||
const { settingsStore } = this.props;
|
||||
|
||||
return (
|
||||
<div className="form-group mb-0">
|
||||
<div className="form-group mb-2">
|
||||
<div className="form-check form-check-inline">
|
||||
<span className="custom-control custom-switch">
|
||||
<input
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { action } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
import { Settings } from "Stores/Settings";
|
||||
|
||||
const ThemeConfiguration = observer(
|
||||
class ThemeConfiguration extends Component {
|
||||
static propTypes = {
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired
|
||||
};
|
||||
|
||||
onChange = action(event => {
|
||||
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 (
|
||||
<div className="form-group mb-0">
|
||||
<div className="form-check form-check-inline">
|
||||
<span className="custom-control custom-switch">
|
||||
<input
|
||||
id="configuration-theme"
|
||||
className="custom-control-input"
|
||||
type="checkbox"
|
||||
value=""
|
||||
checked={settingsStore.themeConfig.config.darkTheme || false}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
<label
|
||||
className="custom-control-label cursor-pointer mr-3"
|
||||
htmlFor="configuration-theme"
|
||||
>
|
||||
Enable dark mode
|
||||
</label>
|
||||
<span className="ml-5 badge badge-danger align-text-bottom">
|
||||
Experimental
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export { ThemeConfiguration };
|
||||
@@ -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(<ThemeConfiguration settingsStore={settingsStore} />);
|
||||
};
|
||||
|
||||
describe("<ThemeConfiguration />", () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
exports[`<AlertGroupTitleBarColor /> matches snapshot with default values 1`] = `
|
||||
"
|
||||
<div class=\\"form-group mb-0\\">
|
||||
<div class=\\"form-group mb-2\\">
|
||||
<div class=\\"form-check form-check-inline\\">
|
||||
<span class=\\"custom-control custom-switch\\">
|
||||
<input id=\\"configuration-colortitlebar\\"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<ThemeConfiguration /> matches snapshot with default values 1`] = `
|
||||
"
|
||||
<div class=\\"form-group mb-0\\">
|
||||
<div class=\\"form-check form-check-inline\\">
|
||||
<span class=\\"custom-control custom-switch\\">
|
||||
<input id=\\"configuration-theme\\"
|
||||
class=\\"custom-control-input\\"
|
||||
type=\\"checkbox\\"
|
||||
value
|
||||
>
|
||||
<label class=\\"custom-control-label cursor-pointer mr-3\\"
|
||||
for=\\"configuration-theme\\"
|
||||
>
|
||||
Enable dark mode
|
||||
</label>
|
||||
<span class=\\"ml-5 badge badge-danger align-text-bottom\\">
|
||||
Experimental
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
"
|
||||
`;
|
||||
@@ -126,7 +126,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
|
||||
<div class=\\"Collapsible__trigger is-closed card-header cursor-pointer border-bottom-0\\">
|
||||
<div class=\\"d-flex flex-row justify-content-between\\">
|
||||
<div>
|
||||
Alert group titlebar configuration
|
||||
Theme
|
||||
</div>
|
||||
<div>
|
||||
<svg aria-hidden=\\"true\\"
|
||||
@@ -150,7 +150,7 @@ exports[`<Configuration /> matches snapshot 1`] = `
|
||||
style=\\"height:0;-webkit-transition:height 50ms linear;-ms-transition:height 50ms linear;transition:height 50ms linear;overflow:hidden\\"
|
||||
>
|
||||
<div class=\\"Collapsible__contentInner card-body my-2\\">
|
||||
<div class=\\"form-group mb-0\\">
|
||||
<div class=\\"form-group mb-2\\">
|
||||
<div class=\\"form-check form-check-inline\\">
|
||||
<span class=\\"custom-control custom-switch\\">
|
||||
<input type=\\"checkbox\\"
|
||||
@@ -166,6 +166,25 @@ exports[`<Configuration /> matches snapshot 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class=\\"form-group mb-0\\">
|
||||
<div class=\\"form-check form-check-inline\\">
|
||||
<span class=\\"custom-control custom-switch\\">
|
||||
<input type=\\"checkbox\\"
|
||||
id=\\"configuration-theme\\"
|
||||
class=\\"custom-control-input\\"
|
||||
value
|
||||
>
|
||||
<label class=\\"custom-control-label cursor-pointer mr-3\\"
|
||||
for=\\"configuration-theme\\"
|
||||
>
|
||||
Enable dark mode
|
||||
</label>
|
||||
<span class=\\"ml-5 badge badge-danger align-text-bottom\\">
|
||||
Experimental
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 }) => (
|
||||
<form className="px-3 accordion">
|
||||
@@ -23,8 +24,13 @@ const Configuration = ({ settingsStore }) => (
|
||||
content={<FilterBarConfiguration settingsStore={settingsStore} />}
|
||||
/>
|
||||
<Accordion
|
||||
text="Alert group titlebar configuration"
|
||||
content={<AlertGroupTitleBarColor settingsStore={settingsStore} />}
|
||||
text="Theme"
|
||||
content={
|
||||
<React.Fragment>
|
||||
<AlertGroupTitleBarColor settingsStore={settingsStore} />
|
||||
<ThemeConfiguration settingsStore={settingsStore} />
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
<Accordion
|
||||
text="Minimal alert group width"
|
||||
|
||||
@@ -145,7 +145,7 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
|
||||
<div class=\\"Collapsible__trigger is-closed card-header cursor-pointer border-bottom-0\\">
|
||||
<div class=\\"d-flex flex-row justify-content-between\\">
|
||||
<div>
|
||||
Alert group titlebar configuration
|
||||
Theme
|
||||
</div>
|
||||
<div>
|
||||
<svg aria-hidden=\\"true\\"
|
||||
@@ -169,7 +169,7 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
|
||||
style=\\"height: 0px; transition: height 50ms linear; overflow: hidden;\\"
|
||||
>
|
||||
<div class=\\"Collapsible__contentInner card-body my-2\\">
|
||||
<div class=\\"form-group mb-0\\">
|
||||
<div class=\\"form-group mb-2\\">
|
||||
<div class=\\"form-check form-check-inline\\">
|
||||
<span class=\\"custom-control custom-switch\\">
|
||||
<input id=\\"configuration-colortitlebar\\"
|
||||
@@ -185,6 +185,25 @@ exports[`<MainModalContent /> matches snapshot 1`] = `
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class=\\"form-group mb-0\\">
|
||||
<div class=\\"form-check form-check-inline\\">
|
||||
<span class=\\"custom-control custom-switch\\">
|
||||
<input id=\\"configuration-theme\\"
|
||||
class=\\"custom-control-input\\"
|
||||
type=\\"checkbox\\"
|
||||
value
|
||||
>
|
||||
<label class=\\"custom-control-label cursor-pointer mr-3\\"
|
||||
for=\\"configuration-theme\\"
|
||||
>
|
||||
Enable dark mode
|
||||
</label>
|
||||
<span class=\\"ml-5 badge badge-danger align-text-bottom\\">
|
||||
Experimental
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
159
ui/src/DarkTheme.scss
Normal file
159
ui/src/DarkTheme.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const DefaultsBase64 =
|
||||
"eyJSZWZyZXNoIjo0NTAwMDAwMDAwMCwiSGlkZUZpbHRlcnNXaGVuSWRsZSI6ZmFsc2UsIkNvbG9yVGl0bGViYXIiOmZhbHNlLCJNaW5pbWFsR3JvdXBXaWR0aCI6NTU1LCJBbGVydHNQZXJHcm91cCI6MTUsIkNvbGxhcHNlR3JvdXBzIjoiZXhwYW5kZWQifQo=";
|
||||
"eyJSZWZyZXNoIjo0NTAwMDAwMDAwMCwiSGlkZUZpbHRlcnNXaGVuSWRsZSI6ZmFsc2UsIkNvbG9yVGl0bGViYXIiOmZhbHNlLCJEYXJrTW9kZSI6ZmFsc2UsIk1pbmltYWxHcm91cFdpZHRoIjo1NTUsIkFsZXJ0c1Blckdyb3VwIjoxNSwiQ29sbGFwc2VHcm91cHMiOiJleHBhbmRlZCJ9Cg==";
|
||||
const DefaultsObject = {
|
||||
Refresh: 45000000000,
|
||||
HideFiltersWhenIdle: false,
|
||||
ColorTitlebar: false,
|
||||
DarkMode: false,
|
||||
MinimalGroupWidth: 555,
|
||||
AlertsPerGroup: 15,
|
||||
CollapseGroups: "expanded"
|
||||
|
||||
Reference in New Issue
Block a user