From 9cc3a26a490813d549b4c3c0f6e86d21957f6701 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Thu, 19 Mar 2026 00:14:13 +0100 Subject: [PATCH] feat(ui): apply Tokyo Night Storm theme with semantic CSS tokens Add theme.css with two-layer token system: primitive tokens from the official Tokyo Night Storm palette, semantic tokens mapping UI roles to primitives. All components use semantic tokens exclusively. Replace all hardcoded colors across 13 Vue components. Theme native HTML elements (button, input, select, a, headings) globally. Toggle button pressed state uses palette-semantic active bg + accent border + inset shadow for clear visual distinction. Remove dead code: simulatorButtonClass computed and associated CSS classes that duplicated global button styles. --- ui/web/src/App.vue | 6 +- ui/web/src/assets/theme.css | 84 +++++++++++++++++++ .../src/components/buttons/ToggleButton.vue | 7 +- .../components/charging-stations/CSData.vue | 8 +- .../components/charging-stations/CSTable.vue | 14 ++-- ui/web/src/main.ts | 2 + ui/web/src/views/ChargingStationsView.vue | 41 ++------- 7 files changed, 111 insertions(+), 51 deletions(-) create mode 100644 ui/web/src/assets/theme.css diff --git a/ui/web/src/App.vue b/ui/web/src/App.vue index 1a0b1d64..8bf915f2 100644 --- a/ui/web/src/App.vue +++ b/ui/web/src/App.vue @@ -21,8 +21,8 @@ import Container from '@/components/Container.vue' -moz-osx-font-smoothing: grayscale; display: flex; flex-direction: row; - color: black; - background-color: white; + color: var(--color-text); + background-color: var(--color-bg); } #action-container { @@ -38,7 +38,7 @@ import Container from '@/components/Container.vue' margin-right: 0.2%; margin-left: 0.2%; padding: 0.4%; - border: solid 0.25px black; + border: solid 0.25px var(--color-border); } body { diff --git a/ui/web/src/assets/theme.css b/ui/web/src/assets/theme.css new file mode 100644 index 00000000..2bb633b2 --- /dev/null +++ b/ui/web/src/assets/theme.css @@ -0,0 +1,84 @@ +/* Tokyo Night Storm */ + +:root { + /* Palette */ + --tn-bg-dark: #1f2335; + --tn-bg-base: #24283b; + --tn-bg-raised: #1b1e2e; + --tn-bg-hover: #292e42; + --tn-bg-active: #2c324a; + --tn-fg-base: #a9b1d6; + --tn-fg-bright: #c0caf5; + --tn-fg-muted: #8089b3; + --tn-blue: #7aa2f7; + --tn-accent: #3d59a1; + --tn-white: #ffffff; + + /* Semantic */ + --color-bg: var(--tn-bg-base); + --color-bg-surface: var(--tn-bg-dark); + --color-bg-input: var(--tn-bg-raised); + --color-bg-hover: var(--tn-bg-hover); + --color-bg-active: var(--tn-bg-active); + --color-bg-header: var(--tn-bg-dark); + --color-bg-caption: var(--tn-bg-raised); + --color-bg-button: var(--tn-accent); + --color-bg-button-hover: color-mix(in srgb, var(--tn-accent) 67%, transparent); + --color-text: var(--tn-fg-base); + --color-text-strong: var(--tn-fg-bright); + --color-text-muted: var(--tn-fg-muted); + --color-text-on-button: var(--tn-white); + --color-primary: var(--tn-blue); + --color-border: var(--tn-bg-raised); + --color-border-row: var(--tn-bg-hover); + --color-accent: var(--tn-accent); + --color-shadow-inset: rgba(0, 0, 0, 0.4); +} + +body { + color: var(--color-text); + background-color: var(--color-bg); +} + +button, +.button { + color: var(--color-text-on-button); + background-color: var(--color-bg-button); + border: 1px solid var(--color-border); + cursor: pointer; +} + +button:hover, +.button:hover { + background-color: var(--color-bg-button-hover); +} + +input, +select, +textarea { + color: var(--color-text); + background-color: var(--color-bg-input); + border: 1px solid var(--color-border-row); +} + +input::placeholder { + color: var(--color-text-muted); +} + +input:focus, +select:focus, +textarea:focus { + outline: 1px solid var(--color-primary); +} + +a { + color: var(--color-primary); +} + +h1, +h2, +h3, +p, +li { + color: var(--color-text); +} diff --git a/ui/web/src/components/buttons/ToggleButton.vue b/ui/web/src/components/buttons/ToggleButton.vue index de1d2692..04195d77 100644 --- a/ui/web/src/components/buttons/ToggleButton.vue +++ b/ui/web/src/components/buttons/ToggleButton.vue @@ -50,8 +50,9 @@ const click = (): void => { diff --git a/ui/web/src/components/charging-stations/CSData.vue b/ui/web/src/components/charging-stations/CSData.vue index 28a8b4d3..7d1f4ce3 100644 --- a/ui/web/src/components/charging-stations/CSData.vue +++ b/ui/web/src/components/charging-stations/CSData.vue @@ -267,7 +267,7 @@ const deleteChargingStation = (): void => { #connectors-table { display: flex; flex-direction: column; - background-color: white; + background-color: var(--color-bg-surface); overflow: auto hidden; border-collapse: collapse; empty-cells: show; @@ -283,15 +283,15 @@ const deleteChargingStation = (): void => { flex-direction: row; justify-content: center; align-items: center; - border: solid 0.25px black; + border: solid 0.25px var(--color-border-row); } .connectors-table__row:nth-of-type(even) { - background-color: whitesmoke; + background-color: var(--color-bg-hover); } #connectors-table__head .connectors-table__row { - background-color: lightgrey; + background-color: var(--color-bg-header); } .connectors-table__column { diff --git a/ui/web/src/components/charging-stations/CSTable.vue b/ui/web/src/components/charging-stations/CSTable.vue index 26bd9b52..3ed3b6b8 100644 --- a/ui/web/src/components/charging-stations/CSTable.vue +++ b/ui/web/src/components/charging-stations/CSTable.vue @@ -106,11 +106,11 @@ const $emit = defineEmits(['need-refresh']) #cs-table { height: fit-content; width: 100%; - background-color: white; + background-color: var(--color-bg-surface); display: flex; flex-direction: column; overflow: auto hidden; - border: solid 0.25px black; + border: solid 0.25px var(--color-border); border-collapse: collapse; empty-cells: show; } @@ -123,8 +123,8 @@ const $emit = defineEmits(['need-refresh']) } #cs-table__caption { - color: ivory; - background-color: black; + color: var(--color-text-strong); + background-color: var(--color-bg-caption); font-size: 1.5rem; font-weight: bold; padding: 0.5rem; @@ -137,11 +137,11 @@ const $emit = defineEmits(['need-refresh']) flex-direction: row; justify-content: center; align-items: center; - border: solid 0.25px black; + border: solid 0.25px var(--color-border-row); } .cs-table__row:nth-of-type(even) { - background-color: whitesmoke; + background-color: var(--color-bg-hover); } .cs-table__column { @@ -153,7 +153,7 @@ const $emit = defineEmits(['need-refresh']) } #cs-table__head .cs-table__row { - background-color: lightgrey; + background-color: var(--color-bg-header); } .cs-table__connectors-column { diff --git a/ui/web/src/main.ts b/ui/web/src/main.ts index 77eb8d1d..d40f9a25 100644 --- a/ui/web/src/main.ts +++ b/ui/web/src/main.ts @@ -9,6 +9,8 @@ import { router } from '@/router' import 'vue-toast-notification/dist/theme-bootstrap.css' +import '@/assets/theme.css' + const app = createApp(App as Component) const initializeApp = (app: AppType, config: ConfigurationData) => { diff --git a/ui/web/src/views/ChargingStationsView.vue b/ui/web/src/views/ChargingStationsView.vue index c530223d..ecf937ba 100644 --- a/ui/web/src/views/ChargingStationsView.vue +++ b/ui/web/src/views/ChargingStationsView.vue @@ -64,7 +64,6 @@ (undefined) const simulatorStarted = computed((): boolean | undefined => simulatorState.value?.started) -const simulatorButtonClass = computed((): string => - simulatorState.value?.started === true ? 'simulator-stop-button' : 'simulator-start-button' -) const simulatorButtonMessage = computed( (): string => `${simulatorState.value?.started === true ? 'Stop' : 'Start'} Simulator${ @@ -355,18 +351,19 @@ const stopSimulator = (): void => { #ui-server-container { display: flex; justify-content: center; - border-style: outset; + border: 1px solid var(--color-border-row); } #ui-server-selector { width: 100%; - background-color: rgb(239, 239, 239); + background-color: var(--color-bg-input); + color: var(--color-text); font: small-caption; text-align: center; } #ui-server-selector:hover { - background-color: rgb(229, 229, 229); + background-color: var(--color-bg-hover); } #buttons-container { @@ -376,46 +373,22 @@ const stopSimulator = (): void => { top: 0; } -.simulator-start-button { - color: ivory; - background-color: green; -} - -.simulator-start-button:hover { - background-color: rgb(0, 98, 0); -} - -.simulator-stop-button { - color: ivory; - background-color: red; -} - -.simulator-stop-button:hover { - background-color: rgb(225, 0, 0); -} - #action-button { flex: none; } #reload-button { - color: ivory; - background-color: blue; font-size: 1.5rem; } -#reload-button:hover { - background-color: rgb(0, 0, 225); -} - #reload-button:active { - background-color: darkblue; + background-color: var(--color-primary); } #action { min-width: max-content; - color: ivory; - background-color: black; + color: var(--color-text-strong); + background-color: var(--color-bg-caption); padding: 0.8%; } -- 2.43.0