diff --git a/.storybook/main.js b/.storybook/main.js index ddef68bcf9..7cdebbaf17 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -1,23 +1,16 @@ -const { loadConfigFromFile } = require("vite"); -const path = require("path"); - module.exports = { - stories: ["../assets/js/**/*.stories.mdx", "../assets/js/**/*.stories.@(js|jsx|ts|tsx)"], - addons: ["@storybook/addon-links", "@storybook/addon-essentials", "@storybook/addon-postcss"], + stories: ["../assets/js/**/*.stories.js"], + addons: [ + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions", + ], framework: "@storybook/vue3", core: { - builder: "storybook-builder-vite", + builder: "@storybook/builder-vite", }, staticDirs: ["../assets"], - async viteFinal(config, b) { - const { config: userConfig } = await loadConfigFromFile( - path.resolve(__dirname, "../vite.config.ts") - ); - const vuePlugin = userConfig.plugins.find((p) => p.name === "vite:vue"); - - const vuePluginIndex = config.plugins.findIndex((p) => p.name === "vite:vue"); - config.plugins[vuePluginIndex] = vuePlugin; - - return config; + features: { + storyStoreV7: true, }, }; diff --git a/.storybook/middleware.js b/.storybook/middleware.js deleted file mode 100644 index 10947f4a38..0000000000 --- a/.storybook/middleware.js +++ /dev/null @@ -1,9 +0,0 @@ -const express = require("express"); - -const expressMiddleWare = (router) => { - router.post("/update", function (req, res) { - res.status(200).send("ok"); - }); -}; - -module.exports = expressMiddleWare; diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 0000000000..05da1e9dfb --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/.storybook/preview.js b/.storybook/preview.js index bfc6cb9f24..0fdf9947bd 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -10,16 +10,22 @@ app.use(i18n); export const parameters = { actions: { argTypesRegex: "^on[A-Z].*" }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, backgrounds: { - default: "dark", + default: "background", values: [ { - name: "light", - value: "#ffffff", + name: "background", + value: "var(--evcc-background)", }, { - name: "dark", - value: "#28293e", + name: "box", + value: "var(--evcc-box)", }, ], }, diff --git a/Dockerfile b/Dockerfile index b06943cb86..58581d6674 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,8 @@ RUN make install-ui COPY assets assets COPY vite.config.js vite.config.js COPY .eslintrc.js .eslintrc.js +COPY postcss.config.js postcss.config.js + RUN make clean ui diff --git a/assets/css/app.css b/assets/css/app.css index c7063c1619..476cc418f6 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -11,24 +11,47 @@ font-style: normal; } +/* Bootstrap Breakpoints as Custom Media Queries */ +@custom-media --sm-and-up (min-width: 576px); +@custom-media --md-and-up (min-width: 768px); +@custom-media --lg-and-up (min-width: 992px); +@custom-media --xl-and-up (min-width: 1200px); +@custom-media --xxl-and-up (min-width: 1400px); + +@custom-media --light-mode (prefers-color-scheme: light); +@custom-media --dark-mode (prefers-color-scheme: dark); + :root { --evcc-green: #baffcb; --evcc-dark-green: #0fde41; + --evcc-darker-green: #0ba631; + --evcc-darkest-green: #076f20; --evcc-yellow: #faf000; + --evcc-dark-yellow: #bbb400; + --bs-gray-deep: #010322; --bs-gray-dark: #28293e; --bs-gray-medium: #93949e; --bs-gray-light: #b5b6c3; + --bs-gray-bright: #f3f3f7; --evcc-grid: var(--bs-gray-dark); --evcc-self: var(--evcc-dark-green); --evcc-export: var(--evcc-yellow); + --evcc-background: var(--bs-gray-bright); + --evcc-box: var(--bs-white); + --evcc-default-text: var(--bs-gray-dark); + --evcc-gray: var(--bs-gray-medium); + + --evcc-accent1: var(--evcc-dark-yellow); + --evcc-accent2: var(--evcc-darker-green); + --evcc-accent3: var(--evcc-darkest-green); --evcc-transition-slow: 1000ms; --evcc-transition-medium: 500ms; --evcc-transition-fast: 250ms; --evcc-transition-very-fast: 100ms; - --bs-primary: var(--evcc-dark-green); + --bs-primary: var(--evcc-darker-green); --bs-primary-rgb: 58, 186, 44; --bs-body-font-size: 14px; @@ -37,6 +60,20 @@ "Segoe UI Symbol", "Noto Color Emoji"; } +@media (--dark-mode) { + :root { + --evcc-grid: var(--bs-gray-medium); + --evcc-background: var(--bs-gray-deep); + --evcc-box: var(--bs-gray-dark); + --evcc-default-text: var(--bs-white); + --evcc-gray: var(--bs-gray-light); + --evcc-accent1: var(--evcc-yellow); + --evcc-accent2: var(--evcc-dark-green); + --evcc-accent3: var(--evcc-darker-green); + --bs-primary: var(--evcc-dark-green); + } +} + html { /* prevents content jump when scrollbar is activate */ width: 100vw; @@ -44,7 +81,8 @@ html { } body { - background-color: var(--evcc-gray-dark); + background-color: var(--evcc-background); + color: var(--evcc-default-text); } h1, @@ -70,7 +108,11 @@ h4 { } a { - color: var(--evcc-dark-green); + color: var(--bs-primary); +} + +a:hover { + color: var(--evcc-accent3); } /* reverse loading animation */ @@ -87,33 +129,50 @@ a { .btn-primary, .btn-primary:focus { - background-color: var(--bs-gray-dark); - border-color: var(--bs-gray-dark); + background-color: var(--bs-primary); + border-color: var(--bs-primary); + color: var(--evcc-background); } .btn-primary:hover, .btn-primary:active { - background-color: var(--evcc-dark-green); - border-color: var(--evcc-dark-green); + background-color: var(--evcc-accent3); + border-color: var(--evcc-accent3); } .btn-outline-primary, .btn-outline-primary:focus { - color: var(--bs-gray-dark); + color: var(--bs-primary); background-color: transparent; border-width: 2px; - border-color: var(--bs-gray-dark); + border-color: var(--bs-primary); } .btn-outline-primary:hover, .btn-outline-primary:active { - color: var(--evcc-dark-green); + color: var(--evcc-accent3); background-color: transparent; border-width: 2px; - border-color: var(--evcc-dark-green); + border-color: var(--evcc-accent3); } + .text-evcc { color: var(--evcc-dark-green); } +.text-accent1 { + color: var(--evcc-accent1); +} +.text-accent2 { + color: var(--evcc-accent2); +} +.text-accent3 { + color: var(--evcc-accent3); +} +.evcc-default-text { + color: var(--evcc-default-text) !important; +} +.evcc-gray { + color: var(--evcc-gray); +} .text-grid { color: var(--evcc-grid); } @@ -147,26 +206,39 @@ a { } .modal-header { - padding-bottom: 1.5rem; + padding: 0 0 1rem 0; + border: none; } .modal-title { font-weight: bold; - font-size: 22px; + font-size: 1.25rem; } .modal-content { border-radius: 1rem; - padding: 1rem; + padding: 1.25rem; + background-color: var(--evcc-box); + color: var(--evcc-default-text); +} + +@media (--sm-and-up) { + .modal-content { + padding: 2rem; + } } .modal-body { - padding-top: 1.5rem; - padding-bottom: 1.5rem; + padding: 1rem 0 0; } .modal-footer { - padding-top: 1.5rem; + padding: 1rem 0 0; + border: none; +} + +.modal-footer > * { + margin: 0; } .cursor-pointer { @@ -186,3 +258,25 @@ small { .btn-close { opacity: 1; } + +@media (--dark-mode) { + .btn-close { + filter: invert(1); + } +} + +.dropdown-menu { + border: none; + border-radius: 10px; + box-shadow: 0 0 8px var(--bs-gray-light); + background-color: var(--evcc-box); +} +.dropdown-item { + color: var(--evcc-default-text); +} + +@media (--dark-mode) { + .dropdown-menu { + box-shadow: 0 0 8px var(--evcc-box); + } +} diff --git a/assets/index.html b/assets/index.html index 1edbef4d2c..28e4e88ea2 100644 --- a/assets/index.html +++ b/assets/index.html @@ -5,7 +5,7 @@ - + @@ -17,7 +17,7 @@ - + evcc diff --git a/assets/js/app.js b/assets/js/app.js index e37181be8a..0d7d12d783 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -73,3 +73,11 @@ window.app = app.mount("#app"); window.setInterval(function () { api.get("health").then(window.app.setOnline).catch(window.app.setOffline); }, 5000); + +const isDarkMode = window.matchMedia("(prefers-color-scheme: dark)"); +isDarkMode.addEventListener("change", updateThemeColor); +function updateThemeColor() { + const $el = document.querySelector("meta[name=theme-color]"); + $el.setAttribute("content", isDarkMode.matches ? "#020318" : "#f3f3f7"); +} +updateThemeColor(); diff --git a/assets/js/components/Energyflow/Energyflow.stories.js b/assets/js/components/Energyflow/Energyflow.stories.js index 4bc6bcdd21..a334aa505e 100644 --- a/assets/js/components/Energyflow/Energyflow.stories.js +++ b/assets/js/components/Energyflow/Energyflow.stories.js @@ -12,11 +12,6 @@ export default { batteryPower: { control: { type: "range", min: -4000, max: 4000, step: 100 } }, batterySoC: { control: { type: "range", min: 0, max: 100, step: 1 } }, }, - parameters: { - backgrounds: { - default: "light", - }, - }, }; const Template = (args) => ({ diff --git a/assets/js/components/Energyflow/Energyflow.vue b/assets/js/components/Energyflow/Energyflow.vue index 199508405b..02156c0b8a 100644 --- a/assets/js/components/Energyflow/Energyflow.vue +++ b/assets/js/components/Energyflow/Energyflow.vue @@ -19,9 +19,6 @@ :valuesInKw="valuesInKw" /> -
- -
@@ -121,7 +118,6 @@ + diff --git a/assets/js/components/LabelAndValue.vue b/assets/js/components/LabelAndValue.vue index c9dacf504d..e8d9ea55a5 100644 --- a/assets/js/components/LabelAndValue.vue +++ b/assets/js/components/LabelAndValue.vue @@ -1,5 +1,5 @@