Repository: manusa/electronim
Pull request #202 (2022-11-13 16:29:32)

💄 settings uses Material 3 top-app-bar

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!&nbsp; &nbsp; [![Quality Gate passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/passed-16px.png 'Quality Gate passed')](https://sonarcloud.io/dashboard?id=manusa_electronim&pullRequest=202)

[![Bug](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug-16px.png 'Bug')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=202&resolved=false&types=BUG) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=202&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=202&resolved=false&types=BUG)  
[![Vulnerability](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability-16px.png 'Vulnerability')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=202&resolved=false&types=VULNERABILITY) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=202&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=202&resolved=false&types=VULNERABILITY)  
[![Security Hotspot](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot-16px.png 'Security Hotspot')](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=202&resolved=false&types=SECURITY_HOTSPOT) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=202&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=manusa_electronim&pullRequest=202&resolved=false&types=SECURITY_HOTSPOT)  
[![Code Smell](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell-16px.png 'Code Smell')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=202&resolved=false&types=CODE_SMELL) [![A](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A-16px.png 'A')](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=202&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=202&resolved=false&types=CODE_SMELL)

[![81.8%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/60-16px.png '81.8%')](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=202&metric=new_coverage&view=list) [81.8% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=202&metric=new_coverage&view=list)  
[![0.0%](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3-16px.png '0.0%')](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=202&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=202&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/src/components/icon-button.mjs b/src/components/icon-button.mjs
index 1d5b9fe..4a0cfe9 100644
--- a/src/components/icon-button.mjs
+++ b/src/components/icon-button.mjs
@@ -24,5 +24,6 @@ export const IconButton = ({
 }) => html`
   <a href='#' ...${properties}
     class=${`material3 icon-button ${disabled ? 'disabled ' : ''}${className}`}
-    onClick=${disabled ? null : onClick}>${icon}</a>
+    disabled=${disabled} onClick=${disabled ? null : onClick}
+  >${icon}</a>
 `;
diff --git a/src/components/index.mjs b/src/components/index.mjs
index e51195a..951fa76 100644
--- a/src/components/index.mjs
+++ b/src/components/index.mjs
@@ -22,4 +22,4 @@ export {DropDown} from './drop-down.mjs';
 export {Icon} from './icon.mjs';
 export {IconButton} from './icon-button.mjs';
 export {Panel} from './panel.mjs';
-export {TopBar, TopAppBar} from './top-bar.mjs';
+export {TopAppBar} from './top-app-bar.mjs';
diff --git a/src/components/top-bar.mjs b/src/components/top-app-bar.mjs
similarity index 63%
rename from src/components/top-bar.mjs
rename to src/components/top-app-bar.mjs
index d816818..298ed7d 100644
--- a/src/components/top-bar.mjs
+++ b/src/components/top-app-bar.mjs
@@ -15,49 +15,27 @@
  */
 import {html, IconButton} from './index.mjs';
 
-/**
- * A TopBar partially based on Bulma's Navbar component.
- */
-export const TopBar = ({
-  title,
-  titleClass = 'navbar-item is-size-5 has-text-info has-text-weight-bold',
-  containerClass = '',
-  endComponents = '',
-  endComponentsClass = '',
-  fixed = false
-}) => {
-  if (fixed) {
-    document.body.classList.add('has-navbar-fixed-top');
-  }
-  return html`
-    <nav class=${`top-bar navbar ${fixed ? 'is-fixed-top' : ''}`}>
-      <div class=${`not-navbar-brand is-flex is-flex-grow-1 is-align-content-center ${containerClass}`}>
-        <div class=${`not-navbar-brand is-flex is-flex-grow-1 ${titleClass}`}>${title}</div>
-        <div class=${endComponentsClass}>${endComponents}</div>
-      </div>
-    </nav>
-  `;
-};
-
 /**
  * A top app bar based on Material design (3) guidelines.
  *
  * @param icon represented using a Material Icon codepoint.
  * @param iconClick callback function to be executed when the icon is clicked.
  * @param headline the headline of the app bar.
+ * @param components or component containing an icon displayed at the end of the app bar.
  * @see https://m3.material.io/components/top-app-bar
  */
 export const TopAppBar = ({
   icon,
   iconClick = () => {},
-  headline
+  headline,
+  trailingIcon
 }) => {
   document.body.classList.add('has-top-app-bar');
   return html`
     <div class='material3 top-app-bar small elevation-0 surface'>
       <${IconButton} className='leading-navigation-icon title-large' icon=${icon} onClick=${iconClick}/>
       <div class='top-app-bar__headline title-large'>${headline}</div>
-      <div class='trailing-icon title-large'></div>
+      <div class='trailing-icon title-large'>${trailingIcon}</div>
     </div>
   `;
 };
diff --git a/src/settings/settings.browser.mjs b/src/settings/settings.browser.mjs
index 12f472d..93d6cf7 100644
--- a/src/settings/settings.browser.mjs
+++ b/src/settings/settings.browser.mjs
@@ -15,7 +15,7 @@
  */
 const {ipcRenderer} = window;
 
-import {APP_EVENTS, Checkbox, TopBar, html, render, useReducer} from '../components/index.mjs';
+import {APP_EVENTS, html, render, useReducer, Checkbox, IconButton, TopAppBar} from '../components/index.mjs';
 import {
   ACTIONS, reducer, dictionariesEnabled, setTabProperty, toggleTabProperty
 } from './settings.reducer.browser.mjs';
@@ -121,18 +121,10 @@ const Settings = ({initialState}) => {
   });
   const cancel = () => ipcRenderer.send(APP_EVENTS.closeDialog);
   return html`
-  <${TopBar} fixed=${true} title='Settings'
-    endComponents=${html`
-      <div class="navbar-item field is-grouped">
-        <div class="control">
-          <button class="settings__submit button is-link"
-            disabled=${!state.canSave || state.invalidTabs.size !== 0} onClick=${save}>Ok</button>
-        </div>
-        <div class="control">
-          <button class="settings__cancel button is-link is-light" onClick=${cancel}>Cancel</button>
-        </div>
-      </div>
-    `}
+  <${TopAppBar} headline='Settings' icon='\uE5C4' iconClick=${cancel}
+    trailingIcon=${html`<${IconButton}
+        className='settings__submit' icon='\ue161' onClick=${save}
+        disabled=${!state.canSave || state.invalidTabs.size !== 0}/>`}
   />
   <div class="container">
     <div class="form">
diff --git a/src/styles/base/material3/palette.css b/src/styles/base/material3/palette.css
index 00b3ed9..3284244 100644
--- a/src/styles/base/material3/palette.css
+++ b/src/styles/base/material3/palette.css
@@ -14,6 +14,7 @@
    limitations under the License.
  */
 
+/* https://m3.material.io/theme-builder */
 /* https://github.com/material-foundation/material-tokens/blob/main/css/palette.css */
 
 :root {
diff --git a/src/styles/components/icon-button.css b/src/styles/components/icon-button.css
index 294d458..e1ced0c 100644
--- a/src/styles/components/icon-button.css
+++ b/src/styles/components/icon-button.css
@@ -13,6 +13,10 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
+:root {
+  --material3-icon-button-size: 24px;
+}
+
 .electronim .material3.icon-button,
 .electronim .material3.icon-button:active,
 .electronim .material3.icon-button:focus,
@@ -23,10 +27,10 @@
   color: var(--md-sys-color-on-surface-variant);
   outline: none;
   text-decoration: none;
-  width: 24px;
-  height: 24px;
-  font-size: 24px;
-  line-height: 24px;
+  width: var(--material3-icon-button-size);
+  height: var(--material3-icon-button-size);
+  font-size: var(--material3-icon-button-size);
+  line-height: var(--material3-icon-button-size);
   text-align: center;
 }
 
@@ -47,3 +51,16 @@
 .electronim .material3.icon-button:hover::before {
   opacity: var(--md-sys-state-hover-state-layer-opacity);
 }
+
+.electronim .material3.icon-button.disabled {
+  opacity: 38%;
+  color: var(--md-sys-color-on-surface);
+}
+
+.electronim .material3.icon-button.disabled:hover {
+  cursor: not-allowed;
+}
+
+.electronim .material3.icon-button.disabled:hover::before {
+  opacity: 0;
+}
diff --git a/src/styles/components/top-bar.css b/src/styles/components/top-bar.css
index e52a47a..1eaa124 100644
--- a/src/styles/components/top-bar.css
+++ b/src/styles/components/top-bar.css
@@ -13,10 +13,6 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
-.electronim .top-bar.navbar {
-  background: var(--color-canvas-default);
-}
-
 :root {
   --material3-top-app-bar-height: 64px;
 }
@@ -30,6 +26,7 @@
   top: 0;
   left: 0;
   right: 0;
+  z-index: 1;
 }
 
 .electronim .material3.top-app-bar.small {
@@ -46,5 +43,13 @@
 
 .electronim .material3.top-app-bar.small .leading-navigation-icon {
   width: 24px;
-  text-align: center;
+}
+
+.electronim .material3.top-app-bar .top-app-bar__headline {
+  flex: 1;
+}
+
+.electronim .material3.top-app-bar .trailing-icon {
+  display: flex;
+  align-items: center;
 }


Test patch:
diff --git a/src/settings/__tests__/settings.browser.test.mjs b/src/settings/__tests__/settings.browser.test.mjs
index 610b8bb..19dafe5 100644
--- a/src/settings/__tests__/settings.browser.test.mjs
+++ b/src/settings/__tests__/settings.browser.test.mjs
@@ -47,7 +47,7 @@ describe('Settings in Browser test suite', () => {
     });
     test('Cancel should send close dialog event', () => {
       // When
-      fireEvent.click(document.querySelector('.settings__cancel'));
+      fireEvent.click(document.querySelector('.top-app-bar .leading-navigation-icon'));
       // Then
       expect(mockIpcRenderer.send).toHaveBeenCalledTimes(1);
       expect(mockIpcRenderer.send).toHaveBeenCalledWith('closeDialog');