]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commitdiff
refactor(webui): css quality overhaul with scoped styles, class selectors, design...
authorJérôme Benoit <jerome.benoit@sap.com>
Sat, 21 Mar 2026 15:46:55 +0000 (16:46 +0100)
committerJérôme Benoit <jerome.benoit@sap.com>
Sat, 21 Mar 2026 15:46:55 +0000 (16:46 +0100)
- add scoped to all 12 Vue component style blocks
- move body/app global styles to index.html
- replace all 33 ID CSS selectors with class selectors
- add :deep() for parent-child table style propagation
- add spacing scale and typography tokens to all themes
- replace magic number percentages with spacing variables
- add :focus-visible styles for buttons and select
- replace hardcoded form widths with max-width constraints
- use gap for flex toolbar spacing
- remove dead CSS

16 files changed:
ui/web/index.html
ui/web/src/App.vue
ui/web/src/assets/themes/catppuccin-latte.css
ui/web/src/assets/themes/sap-horizon.css
ui/web/src/assets/themes/tokyo-night-storm.css
ui/web/src/components/Container.vue
ui/web/src/components/actions/AddChargingStations.vue
ui/web/src/components/actions/SetSupervisionUrl.vue
ui/web/src/components/actions/StartTransaction.vue
ui/web/src/components/buttons/Button.vue
ui/web/src/components/buttons/ReloadButton.vue
ui/web/src/components/buttons/ToggleButton.vue
ui/web/src/components/charging-stations/CSData.vue
ui/web/src/components/charging-stations/CSTable.vue
ui/web/src/views/ChargingStationsView.vue
ui/web/src/views/NotFoundView.vue

index 877bf4458f57f358cbe4b1fa7e0558ee1c011579..034b547a504e06895ecc3c6e21c5f6de33a9c1bb 100644 (file)
@@ -5,6 +5,24 @@
     <meta name="viewport" content="width=device-width,initial-scale=1.0" />
     <link rel="icon" href="/favicon.ico" />
     <title>Simulator Web UI</title>
+    <style>
+      body {
+        margin: 0;
+        padding: 0;
+      }
+
+      #app {
+        height: fit-content;
+        width: 100%;
+        font-family: var(--font-family);
+        -webkit-font-smoothing: antialiased;
+        -moz-osx-font-smoothing: grayscale;
+        display: flex;
+        flex-direction: row;
+        color: var(--color-text);
+        background-color: var(--color-bg);
+      }
+    </style>
   </head>
 
   <body>
index 8bf915f2eeb266d4961bda38082579610215d24a..7883d52ba78f6f86a45190631e8a0cf4a14619d2 100644 (file)
@@ -3,6 +3,7 @@
   <Container
     v-show="$route.name !== 'charging-stations' && $route.name !== 'not-found'"
     id="action-container"
+    class="action-container"
   >
     <router-view name="action" />
   </Container>
 import Container from '@/components/Container.vue'
 </script>
 
-<style>
-#app {
-  height: fit-content;
-  width: 100%;
-  font-family: Tahoma, 'Arial Narrow', Arial, Helvetica, sans-serif;
-  -webkit-font-smoothing: antialiased;
-  -moz-osx-font-smoothing: grayscale;
-  display: flex;
-  flex-direction: row;
-  color: var(--color-text);
-  background-color: var(--color-bg);
-}
-
-#action-container {
+<style scoped>
+.action-container {
   min-width: max-content;
   height: fit-content;
   display: flex;
   position: sticky;
-  top: 0.008%;
+  top: 0;
   flex-direction: column;
   justify-content: center;
   align-items: center;
   text-align: center;
-  margin-right: 0.2%;
-  margin-left: 0.2%;
-  padding: 0.4%;
+  margin-inline: var(--spacing-sm);
+  padding: var(--spacing-md);
   border: solid 0.25px var(--color-border);
 }
-
-body {
-  margin: 0.008%;
-  padding: 0.008%;
-}
 </style>
index c8da8ff7347eb074eb0450749eea1095f969de60..44b06b802810c3c9e3a2165249b95171d693bfcd 100644 (file)
   --color-border-row: var(--ctp-surface0);
   --color-accent: var(--ctp-lavender);
   --color-shadow-inset: rgba(0, 0, 0, 0.1);
+
+  /* Spacing */
+  --spacing-xs: 0.125rem;
+  --spacing-sm: 0.25rem;
+  --spacing-md: 0.5rem;
+  --spacing-lg: 1rem;
+  --spacing-xl: 1.5rem;
+
+  /* Typography */
+  --font-family: Tahoma, 'Arial Narrow', Arial, Helvetica, sans-serif;
+  --font-size-sm: 0.875rem;
 }
 
 body {
index fd0c824d764880e7cce03c65df7d0a56375bfe5a..4365d68acf7fad9d5d199127b2ffb14ac266d7e5 100644 (file)
   --color-border-row: var(--sap-border);
   --color-accent: var(--sap-brand);
   --color-shadow-inset: rgba(34, 53, 72, 0.15);
+
+  /* Spacing */
+  --spacing-xs: 0.125rem;
+  --spacing-sm: 0.25rem;
+  --spacing-md: 0.5rem;
+  --spacing-lg: 1rem;
+  --spacing-xl: 1.5rem;
+
+  /* Typography */
+  --font-family: Tahoma, 'Arial Narrow', Arial, Helvetica, sans-serif;
+  --font-size-sm: 0.875rem;
 }
 
 body {
index 6095f9e395582d5730f71bb63c0b0c82da60ebc7..0b223b5466d4ad5d9d49ffbc6b8ffed3a9dee531 100644 (file)
   --color-border-row: var(--tn-bg-hover);
   --color-accent: var(--tn-accent);
   --color-shadow-inset: rgba(0, 0, 0, 0.4);
+
+  /* Spacing */
+  --spacing-xs: 0.125rem;
+  --spacing-sm: 0.25rem;
+  --spacing-md: 0.5rem;
+  --spacing-lg: 1rem;
+  --spacing-xl: 1.5rem;
+
+  /* Typography */
+  --font-family: Tahoma, 'Arial Narrow', Arial, Helvetica, sans-serif;
+  --font-size-sm: 0.875rem;
 }
 
 body {
index 0bc72b01a60da5457c18a981daa8517063469cbc..bd6ebf46ffc74e0beee3b4e5890385b53d3de54e 100644 (file)
@@ -4,7 +4,7 @@
   </div>
 </template>
 
-<style>
+<style scoped>
 .container {
   flex: auto;
 }
index 7b8e43fdd9af08849dcc0ac9345a2ccc7e366c31..0a8745996b20512636c460843659844990d8cfa9 100644 (file)
@@ -1,5 +1,5 @@
 <template>
-  <h1 id="action">
+  <h1 class="action">
     Add Charging Stations
   </h1>
   <p>Template:</p>
   <input
     id="number-of-stations"
     v-model="state.numberOfStations"
+    class="number-of-stations"
     min="1"
     name="number-of-stations"
     placeholder="number of stations"
     type="number"
   >
   <p>Template options overrides:</p>
-  <ul id="template-options">
+  <ul class="template-options">
     <li>
       Supervision url:
       <input
         id="supervision-url"
         v-model.trim="state.supervisionUrl"
+        class="supervision-url"
         name="supervision-url"
         placeholder="wss://"
         type="url"
@@ -146,18 +148,27 @@ if (templates != null) {
 }
 </script>
 
-<style>
-#number-of-stations {
-  width: 15%;
+<style scoped>
+.action {
+  min-width: max-content;
+  color: var(--color-text-strong);
+  background-color: var(--color-bg-caption);
+  padding: var(--spacing-lg);
+}
+
+.number-of-stations {
+  width: auto;
+  max-width: 6rem;
   text-align: center;
 }
 
-#supervision-url {
-  width: 90%;
+.supervision-url {
+  width: 100%;
+  max-width: 40rem;
   text-align: left;
 }
 
-#template-options {
+.template-options {
   list-style: circle inside;
   text-align: left;
 }
index c7affb90eff57eef990c77a4b58452fb7339dd1b..4cbbd905fd9b8e1042856e2f685fbfd6dbefd9ac 100644 (file)
@@ -1,5 +1,5 @@
 <template>
-  <h1 id="action">
+  <h1 class="action">
     Set Supervision Url
   </h1>
   <h2>{{ chargingStationId }}</h2>
@@ -7,6 +7,7 @@
   <input
     id="supervision-url"
     v-model.trim="state.supervisionUrl"
+    class="supervision-url"
     name="supervision-url"
     placeholder="wss://"
     type="url"
@@ -52,9 +53,17 @@ const state = ref<{ supervisionUrl: string }>({
 })
 </script>
 
-<style>
-#supervision-url {
-  width: 90%;
+<style scoped>
+.action {
+  min-width: max-content;
+  color: var(--color-text-strong);
+  background-color: var(--color-bg-caption);
+  padding: var(--spacing-lg);
+}
+
+.supervision-url {
+  width: 100%;
+  max-width: 40rem;
   text-align: left;
 }
 </style>
index c16401d206ea1b3549310364bdad99eeb17a80e5..ac7f92309ea7ddac44dafb14766fe7df76822a9e 100644 (file)
@@ -1,5 +1,5 @@
 <template>
-  <h1 id="action">
+  <h1 class="action">
     Start Transaction
   </h1>
   <h2>{{ chargingStationId }}</h2>
@@ -14,6 +14,7 @@
     <input
       id="idtag"
       v-model.trim="state.idTag"
+      class="idtag"
       name="idtag"
       placeholder="RFID tag"
       type="text"
@@ -108,8 +109,15 @@ const handleStartTransaction = async (): Promise<void> => {
 }
 </script>
 
-<style>
-#idtag {
+<style scoped>
+.action {
+  min-width: max-content;
+  color: var(--color-text-strong);
+  background-color: var(--color-bg-caption);
+  padding: var(--spacing-lg);
+}
+
+.idtag {
   text-align: center;
 }
 </style>
index b84c142301825d463ee242258243363c4c316958..1762c36ec2a9ea6592698cc4bdc6a6e0067fc80a 100644 (file)
@@ -7,12 +7,17 @@
   </button>
 </template>
 
-<style>
+<style scoped>
 .button {
   display: block;
-  flex: auto;
   width: 100%;
   text-align: center;
-  font: small-caption;
+  white-space: nowrap;
+  font-size: var(--font-size-sm);
+}
+
+.button:focus-visible {
+  outline: 2px solid var(--color-accent);
+  outline-offset: -2px;
 }
 </style>
index 385e145462d369744e1757b04f9c48abca4a82c0..7126ffbf0e323fa3a143d177112cc51d6035f53c 100644 (file)
@@ -12,8 +12,8 @@ defineProps<{
 }>()
 </script>
 
-<style>
-@keyframes rotation {
+<style scoped>
+@keyframes spin {
   from {
     transform: rotate(0deg);
   }
@@ -23,6 +23,6 @@ defineProps<{
 }
 
 .spin {
-  animation: rotation 2s linear infinite;
+  animation: spin 2s linear infinite;
 }
 </style>
index 7fe7d641ec92129996598766b6f6376394abc0cb..694d7502f3213346056864e0e551d75aad8ac129 100644 (file)
@@ -49,7 +49,7 @@ const click = (): void => {
 }
 </script>
 
-<style>
+<style scoped>
 button.on {
   color: var(--color-text);
   background-color: var(--color-bg-active);
index 53703a64ec4c6b191ab944b8ff242ccab4eec441..216e227dd5b49ceafd6af0d8680e7872cac6308e 100644 (file)
@@ -71,8 +71,8 @@
       </Button>
     </td>
     <td class="cs-table__connectors-column">
-      <table id="connectors-table">
-        <thead id="connectors-table__head">
+      <table class="connectors-table">
+        <thead class="connectors-table__head">
           <tr class="connectors-table__row">
             <th
               class="connectors-table__column"
             </th>
           </tr>
         </thead>
-        <tbody id="connectors-table__body">
+        <tbody class="connectors-table__body">
           <CSConnector
             v-for="entry in getConnectorEntries()"
             :key="entry.evseId != null ? `${entry.evseId}-${entry.connectorId}` : entry.connectorId"
@@ -262,27 +262,27 @@ const deleteChargingStation = (): void => {
 }
 </script>
 
-<style>
-#connectors-table {
+<style scoped>
+.connectors-table {
   width: 100%;
   background-color: var(--color-bg-surface);
   border-collapse: collapse;
   empty-cells: show;
 }
 
-.connectors-table__row {
-  border: solid 0.25px var(--color-border-row);
+.connectors-table__head .connectors-table__row {
+  background-color: var(--color-bg-header);
 }
 
-.connectors-table__row:nth-of-type(even) {
-  background-color: var(--color-bg-hover);
+:deep(.connectors-table__row) {
+  border: solid 0.25px var(--color-border-row);
 }
 
-#connectors-table__head .connectors-table__row {
-  background-color: var(--color-bg-header);
+:deep(.connectors-table__row:nth-of-type(even)) {
+  background-color: var(--color-bg-hover);
 }
 
-.connectors-table__column {
+:deep(.connectors-table__column) {
   text-align: center;
   vertical-align: middle;
   padding: 0.25rem;
index bc887dfe1557422f270b31879f25555d92e38017..1fb4031e8340991b6c01099ed6c007018c6621c4 100644 (file)
@@ -1,7 +1,7 @@
 <template>
   <div class="cs-table__wrapper">
-    <table id="cs-table">
-      <caption id="cs-table__caption">
+    <table class="cs-table">
+      <caption class="cs-table__caption">
         Charging Stations
       </caption>
       <colgroup>
@@ -18,7 +18,7 @@
         <col>
         <col class="cs-table__col--connectors">
       </colgroup>
-      <thead id="cs-table__head">
+      <thead class="cs-table__head">
         <tr class="cs-table__row">
           <th
             class="cs-table__column"
@@ -94,7 +94,7 @@
           </th>
         </tr>
       </thead>
-      <tbody id="cs-table__body">
+      <tbody class="cs-table__body">
         <CSData
           v-for="chargingStation in chargingStations"
           :key="chargingStation.stationInfo.hashId"
@@ -118,12 +118,12 @@ defineProps<{
 const $emit = defineEmits(['need-refresh'])
 </script>
 
-<style>
+<style scoped>
 .cs-table__wrapper {
   overflow-x: auto;
 }
 
-#cs-table {
+.cs-table {
   width: 100%;
   min-width: 1280px;
   background-color: var(--color-bg-surface);
@@ -132,7 +132,7 @@ const $emit = defineEmits(['need-refresh'])
   empty-cells: show;
 }
 
-#cs-table__caption {
+.cs-table__caption {
   color: var(--color-text-strong);
   background-color: var(--color-bg-caption);
   font-size: 1.5rem;
@@ -140,30 +140,30 @@ const $emit = defineEmits(['need-refresh'])
   padding: 0.5rem;
 }
 
-.cs-table__row {
-  border: solid 0.25px var(--color-border-row);
+.cs-table__head .cs-table__row {
+  background-color: var(--color-bg-header);
 }
 
-.cs-table__row:nth-of-type(even) {
-  background-color: var(--color-bg-hover);
+:deep(.cs-table__row) {
+  border: solid 0.25px var(--color-border-row);
 }
 
-#cs-table__head .cs-table__row {
-  background-color: var(--color-bg-header);
+:deep(.cs-table__row:nth-of-type(even)) {
+  background-color: var(--color-bg-hover);
 }
 
-.cs-table__column {
+:deep(.cs-table__column) {
   text-align: center;
   vertical-align: middle;
   padding: 0.25rem;
-  overflow-wrap: anywhere;
+  overflow-wrap: break-word;
 }
 
 .cs-table__col--connectors {
   width: 33%;
 }
 
-.cs-table__connectors-column {
+:deep(.cs-table__connectors-column) {
   vertical-align: top;
   padding: 0;
 }
index ecf937ba601c6c6230dabafc5ae6df15141033bc..585e98690f13a3d4f7aa2bf3b6173b5a80a1970a 100644 (file)
@@ -1,13 +1,15 @@
 <template>
-  <Container id="charging-stations-container">
-    <Container id="buttons-container">
+  <Container class="charging-stations-container">
+    <Container class="buttons-container">
       <Container
         v-show="Array.isArray(uiServerConfigurations) && uiServerConfigurations.length > 1"
         id="ui-server-container"
+        class="ui-server-container"
       >
         <select
           id="ui-server-selector"
           v-model="state.uiServerIndex"
+          class="ui-server-selector"
           @change="
             () => {
               if (
@@ -93,7 +95,7 @@
         Add Charging Stations
       </ToggleButton>
       <ReloadButton
-        id="reload-button"
+        class="reload-button"
         :loading="state.gettingChargingStations"
         @click="getChargingStations()"
       />
@@ -340,55 +342,56 @@ const stopSimulator = (): void => {
 }
 </script>
 
-<style>
-#charging-stations-container {
+<style scoped>
+.charging-stations-container {
   height: fit-content;
   width: 100%;
   display: flex;
   flex-direction: column;
 }
 
-#ui-server-container {
+.ui-server-container {
   display: flex;
+  flex: 3 1 0;
+  min-width: 0;
   justify-content: center;
   border: 1px solid var(--color-border-row);
 }
 
-#ui-server-selector {
+.ui-server-selector {
   width: 100%;
   background-color: var(--color-bg-input);
   color: var(--color-text);
-  font: small-caption;
+  font-size: var(--font-size-sm);
   text-align: center;
 }
 
-#ui-server-selector:hover {
+.ui-server-selector:hover {
   background-color: var(--color-bg-hover);
 }
 
-#buttons-container {
+.ui-server-selector:focus-visible {
+  outline: 2px solid var(--color-accent);
+  outline-offset: -2px;
+}
+
+.buttons-container {
   display: flex;
   flex-direction: row;
+  gap: var(--spacing-xs);
   position: sticky;
   top: 0;
 }
 
-#action-button {
-  flex: none;
+.buttons-container > * {
+  flex: 1 1 0;
 }
 
-#reload-button {
+.reload-button {
   font-size: 1.5rem;
 }
 
-#reload-button:active {
+.reload-button:active {
   background-color: var(--color-primary);
 }
-
-#action {
-  min-width: max-content;
-  color: var(--color-text-strong);
-  background-color: var(--color-bg-caption);
-  padding: 0.8%;
-}
 </style>
index 7f800e80fd5bce248f73b37b249879a1aa8f9487..8880d06aaa018036819281183921b57e84f4fe84 100644 (file)
@@ -1,5 +1,5 @@
 <template>
-  <Container id="not-found">
+  <Container class="not-found">
     404 - Not found
   </Container>
 </template>
@@ -8,8 +8,8 @@
 import Container from '@/components/Container.vue'
 </script>
 
-<style>
-#not-found {
+<style scoped>
+.not-found {
   display: flex;
   justify-content: center;
   align-items: center;