Repository: manusa/electronim
Pull request #27 (2020-07-12 15:52:59)

✨: Tabs can be disabled
Fix: #5 

sonarcloud[bot]: SonarCloud Quality Gate failed.

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E.png' alt='E' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG) [2 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='97.1%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_coverage&view=list) [97.1% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: SonarCloud Quality Gate failed.

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/E.png' alt='E' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG) [2 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='97.1%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_coverage&view=list) [97.1% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='97.1%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_coverage&view=list) [97.1% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_duplicated_lines_density&view=list)


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=VULNERABILITY) (and [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=SECURITY_HOTSPOT) to review)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=manusa_electronim&pullRequest=27&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/90.png' alt='97.1%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_coverage&view=list) [97.1% Coverage](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_coverage&view=list)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/3.png' alt='0.0%' width='16' height='16' />](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_duplicated_lines_density&view=list) [0.0% Duplication](https://sonarcloud.io/component_measures?id=manusa_electronim&pullRequest=27&metric=new_duplicated_lines_density&view=list)



Patch:
diff --git a/src/main/index.js b/src/main/index.js
index 93bed39..4d170b6 100644
--- a/src/main/index.js
+++ b/src/main/index.js
@@ -55,18 +55,26 @@ const handleZoomOut = event => {
 
 const handleZoomReset = event => event.sender.setZoomFactor(1);
 
-const handleTabReorder = (event, {tabIds}) => {
-  const currentTabs = loadSettings().tabs.reduce((acc, tab) => {
+const handleTabReorder = (event, {tabIds: visibleTabIds}) => {
+  const currentTabs = loadSettings().tabs;
+  const hiddenTabIds = currentTabs.map(({id}) => id)
+    .filter(tabId => !visibleTabIds.includes(tabId));
+  const currentTabMap = currentTabs.reduce((acc, tab) => {
     acc[tab.id] = tab; return acc;
   }, {});
-  const tabs = tabIds.map(tabId => currentTabs[tabId]);
+  const tabs = [
+    ...visibleTabIds.map(tabId => currentTabMap[tabId]),
+    ...hiddenTabIds.map(tabId => currentTabMap[tabId])
+  ];
   updateSettings({tabs});
 };
 
 const initTabListener = () => {
   ipc.on(APP_EVENTS.tabsReady, event => {
     const currentSettings = loadSettings();
-    const tabs = currentSettings.tabs.map(tab => ({...tab, active: tab.id === currentSettings.activeTab}));
+    const tabs = currentSettings.tabs
+      .filter(tab => !tab.disabled)
+      .map(tab => ({...tab, active: tab.id === currentSettings.activeTab}));
     if (tabs.length > 0) {
       const ipcSender = event.sender;
       tabManager.addTabs(ipcSender)(tabs);
diff --git a/src/settings/browser-settings.css b/src/settings/browser-settings.css
index 180e7a6..4845301 100644
--- a/src/settings/browser-settings.css
+++ b/src/settings/browser-settings.css
@@ -13,29 +13,61 @@
    See the License for the specific language governing permissions and
    limitations under the License.
  */
+:root {
+    --default-spacing: 6px;
+    --border-color: #dbdbdb;
+}
+
 body {
     margin:0;
     padding: 15px 0;
 }
 
+.control .checkbox {
+    user-select: none;
+}
+
 .settings__button.button {
     background: none;
     border-color: transparent;
 }
 
 .settings__button.button:hover {
-    border-color: #dbdbdb;
+    border-color: var(--border-color);
 }
 
-.settings__new-tab, .settings__tab {
+.settings__button.button:not(:last-child) {
+    margin-right: var(--default-spacing);
+}
+
+.settings__tab {
+    flex-direction: column;
+}
+
+.settings__new-tab, .settings__tab-main, .settings__tab-advanced {
     display: inline-flex;
     align-items: center;
     width: 100%;
 }
 
+.settings__tab .settings__tab-advanced {
+    transform: scaleY(0);
+    height: 0;
+    margin: 0;
+    transition: margin 0.3s  ease-out, transform 0.3s ease-out;
+    overflow: hidden;
+}
+
+.settings__tab--expanded .settings__tab-advanced {
+    transform: scaleY(1);
+    height: 100%;
+    --margin: calc(2 * var(--default-spacing));
+    margin: var(--margin) var(--margin) var(--default-spacing) var(--margin);
+}
+
 .settings__new-tab .control,
 .settings__tab .control {
-    margin-right: 6px;
+    margin-right: var(--default-spacing);
     flex: 1;
 }
 
@@ -47,5 +79,5 @@ body {
 }
 
 .settings__dictionaries .control input[type="checkbox"] {
-    margin-right: 6px;
+    margin-right: var(--default-spacing);
 }
diff --git a/src/settings/browser-settings.js b/src/settings/browser-settings.js
index a519207..15db31d 100644
--- a/src/settings/browser-settings.js
+++ b/src/settings/browser-settings.js
@@ -53,6 +53,7 @@ const newId = () => (
 const initialState = {
   dictionaries: window.dictionaries,
   tabs: window.tabs,
+  expandedTabs: [],
   newTabValid: false,
   newTabValue: '',
   canSave: window.tabs.length > 0
@@ -62,7 +63,8 @@ const ACTIONS = {
   ADD: 'ADD',
   REMOVE: 'REMOVE',
   TOGGLE_DICTIONARY: 'TOGGLE_DICTIONARY',
-  TOGGLE_TAB_SANDBOX: 'TOGGLE_TAB_SANDBOX',
+  TOGGLE_TAB_EXPANDED: 'TOGGLE_TAB_EXPANDED',
+  TOGGLE_TAB_PROPERTY: 'TOGGLE_TAB_PROPERTY',
   UPDATE_NEW_TAB_VALUE: 'UPDATE_NEW_TAB_VALUE'
 };
 
@@ -102,12 +104,21 @@ const reducer = (state, action) => {
       }
       return newState;
     }
-    case ACTIONS.TOGGLE_TAB_SANDBOX: {
+    case ACTIONS.TOGGLE_TAB_EXPANDED: {
+      if (state.expandedTabs.includes(action.payload)) {
+        return {...state,
+          expandedTabs: state.expandedTabs.filter(id => id !== action.payload)};
+      }
+      return {...state,
+        expandedTabs: [...state.expandedTabs, action.payload]
+      };
+    }
+    case ACTIONS.TOGGLE_TAB_PROPERTY: {
       const newState = {...state, tabs: []};
       state.tabs.forEach(tab => {
         const newTab = {...tab};
         if (newTab.id === action.payload.id) {
-          newTab.sandboxed = !newTab.sandboxed;
+          newTab[action.payload.property] = !newTab[action.payload.property];
         }
         newState.tabs.push(newTab);
       });
@@ -124,6 +135,9 @@ const reducer = (state, action) => {
   }
 };
 
+const toggleTabProperty = (dispatch, property, id) =>
+  () => dispatch({type: ACTIONS.TOGGLE_TAB_PROPERTY, payload: {id, property}});
+
 const SettingsButton = ({icon, disabled = false, title, onclick}) => (html`
   <button class='settings__button button' disabled=${disabled} onclick=${onclick}>
     <span class='icon is-medium' title='${title}'>
@@ -131,31 +145,61 @@ const SettingsButton = ({icon, disabled = false, title, onclick}) => (html`
     </span>
   </button>
 `);
-const TabEntry = ({dispatch, id, url, sandboxed}) => (html`
-  <div class='settings__tab field '>
-    <div class='control'>
-      <input type='text' readonly class='input' name='tabs' value='${url}' />
-    </div>
-    <${SettingsButton} icon=${sandboxed ? 'fa-lock' : 'fa-lock-open'}
-      title='Use isolated session when lock is on'
-      onclick=${() => dispatch({type: ACTIONS.TOGGLE_TAB_SANDBOX, payload: {id}})}
+
+const ExpandButton = ({dispatch, id, expanded = false}) => {
+  const properties = {
+    icon: expanded ? 'fa-chevron-down' : 'fa-chevron-right',
+    title: expanded ? 'Collapse' : 'Expand',
+    onclick: () => dispatch({type: ACTIONS.TOGGLE_TAB_EXPANDED, payload: id})
+  };
+  return html`
+    <${SettingsButton} ...${properties} />
+  `;
+};
+
+const Checkbox = ({label, checked, value, onclick}) => (html`
+  <div class='control'>
+    <label class='checkbox'>
+      <input type='checkbox' checked=${checked} value=${value} onclick=${onclick} />
+      ${label}
+    </label>
+  </div>
+`);
+
+const TabAdvancedSettings = ({dispatch, id, disabled = false}) => (html`
+  <div class="settings__tab-advanced container">
+    <${Checkbox} label="Disabled" checked=${disabled} value=${id}
+      onclick=${toggleTabProperty(dispatch, 'disabled', id)}
     />
-    <${SettingsButton} icon='fa-trash' title='Delete tab'
-      onclick=${() => dispatch({type: ACTIONS.REMOVE, payload: {id}})}
+  </div>
+`);
+
+const TabEntry = ({dispatch, id, expanded, url, sandboxed, ...tab}) => (html`
+  <div class='settings__tab ${expanded && 'settings__tab--expanded'} panel-block' data-id=${id}>
+    <div class='settings__tab-main'>
+      <${ExpandButton} dispatch=${dispatch} id=${id} expanded=${expanded} />
+      <div class='control'>
+        <input type='text' readonly class='input' name='tabs' value='${url}' />
+      </div>
+      <${SettingsButton} icon=${sandboxed ? 'fa-lock' : 'fa-lock-open'}
+        title='Use isolated session when lock is on'
+        onclick=${toggleTabProperty(dispatch, 'sandboxed', id)}
+      />
+      <${SettingsButton} icon='fa-trash' title='Delete tab'
+        onclick=${() => dispatch({type: ACTIONS.REMOVE, payload: {id}})}
+      /> 
+    </div>
+    <${TabAdvancedSettings} dispatch=${dispatch} id=${id} expanded=${expanded}
+      ...${tab}
     />
   </div>
 `);
 
 const DictionaryEntry = ({dispatch, dictionaryKey, name, enabled = false}) => (html`
-  <div class='control'>
-    <label class='checkbox'>
-        <input type='checkbox' name='dictionaries'
-          value='${dictionaryKey}' checked=${enabled}
-          onclick=${() => dispatch({type: ACTIONS.TOGGLE_DICTIONARY, payload: dictionaryKey})}
-        />
-        ${name} (${dictionaryKey})
-    </label>
-  </div>
+  <${Checkbox} label=${`${name} (${dictionaryKey})`} checked=${enabled}
+    value=${dictionaryKey}
+    onclick=${() => dispatch({type: ACTIONS.TOGGLE_DICTIONARY, payload: dictionaryKey})}
+  />
 `);
 
 const Settings = () => {
@@ -181,27 +225,33 @@ const Settings = () => {
   <h1 class="title">Settings</h1>
   <div class="container">
     <div class="form">
-      <label class="label">Tabs</label>
-      <div class="settings__new-tab container field">
-        <div class="control">
-          <input type="text"
-            class="input ${newTabClass(state)}"
-            placeholder="https://web.whatsapp.com"
-            value=${state.newTabValue}
-            oninput=${onNewTabInput}
-            onkeydown=${onNewKeyDown}
-          />
+      <nav class="panel">
+        <p class="panel-heading">Tabs</p>
+        <div class="settings__new-tab panel-block">
+          <div class="control">
+            <input type="text"
+              class="input ${newTabClass(state)}"
+              placeholder="https://web.whatsapp.com"
+              value=${state.newTabValue}
+              oninput=${onNewTabInput}
+              onkeydown=${onNewKeyDown}
+            />
+          </div>
+          <${SettingsButton} icon='fa-plus' onclick=${addTab} disabled=${!state.newTabValid} /> 
         </div>
-        <${SettingsButton} icon='fa-plus' onclick=${addTab} disabled=${!state.newTabValid} />
-      </div>
-      <div class="settings__tabs container field">
-        ${state.tabs.map(tab => (html`
-          <${TabEntry} dispatch=${dispatch} ...${tab} />
-      `))}
-      </div>
-      <div class="field">
-        <label class="label">Spell checker languages</label>
-        <div class="settings__dictionaries container">${
+        <div class="settings__tabs container field">
+          ${state.tabs.map(tab => (html`
+            <${TabEntry}
+              dispatch=${dispatch} expanded=${state.expandedTabs.includes(tab.id)}
+              ...${tab}
+            />
+        `))}
+        </div>
+      </nav>
+      <nav class="panel">
+        <p class="panel-heading">Spell checker languages</p>
+        <div class="panel-block">
+          <div class="settings__dictionaries container">${
   Object.entries(availableDictionaries)
     .sort(([, {name: name1}], [, {name: name2}]) => name1.localeCompare(name2))
     .map(([key, {name}]) => (html`
@@ -209,8 +259,9 @@ const Settings = () => {
         enabled=${enabledDictionaries.includes(key)}
       />
     `))} 
+          </div>
         </div>
-      </div>
+      </nav>
       <div class="field is-grouped">
         <div class="control">
           <button class="settings__submit button is-link"


Test patch:
diff --git a/src/main/__tests__/index.test.js b/src/main/__tests__/index.test.js
index ffafb52..f68c418 100644
--- a/src/main/__tests__/index.test.js
+++ b/src/main/__tests__/index.test.js
@@ -132,7 +132,10 @@ describe('Main module test suite', () => {
       test('Previous saved tabs in loaded settings, should add tabs to manager and activate them as they are added', () => {
         // Given
         const event = {sender: {send: jest.fn()}};
-        settingsModule.loadSettings.mockImplementation(() => ({tabs: [{id: '1337', otherInfo: 'A Tab'}]}));
+        settingsModule.loadSettings.mockImplementation(() => ({tabs: [
+          {id: '1337', otherInfo: 'A Tab'},
+          {id: 'disabled-1337', disabled: true, otherInfo: 'I should be ignored'}
+        ]}));
         main.init();
         // When
         mockIpc.listeners.tabsReady(event);
@@ -269,6 +272,19 @@ describe('Main module test suite', () => {
         // Then
         expect(settingsModule.updateSettings).toHaveBeenCalledWith({tabs: [{id: '313373'}, {id: '1337'}]});
       });
+      test('Several tabs with hidden, order changed, should update settings keeping hidden tags', () => {
+        // Given
+        mockSettings = {
+          tabs: [{id: '1337'}, {id: 'hidden'}, {id: '313373'}, {id: 'hidden-too'}]
+        };
+        main.init();
+        // When
+        mockIpc.listeners.tabReorder({}, {tabIds: ['313373', '1337']});
+        // Then
+        expect(settingsModule.updateSettings).toHaveBeenCalledWith({tabs: [
+          {id: '313373'}, {id: '1337'}, {id: 'hidden'}, {id: 'hidden-too'}
+        ]});
+      });
     });
     test('settingsOpenDialog, should open settings dialog', () => {
       // Given
diff --git a/src/settings/__tests__/browser-settings.test.js b/src/settings/__tests__/browser-settings.test.js
index 4d40c8f..1e34a82 100644
--- a/src/settings/__tests__/browser-settings.test.js
+++ b/src/settings/__tests__/browser-settings.test.js
@@ -26,20 +26,22 @@ describe('Settings in Browser test suite', () => {
   let mockDictionaries;
   let mockTabs;
   let mockIpcRenderer;
+  let spyUseReducer;
   beforeEach(() => {
     mockDictionaries = {available: {
       en: {name: 'English'},
       es: {name: 'Spanish'}
     }, enabled: ['en']};
     mockTabs = [
-      {url: 'https://initial-tab.com', sandboxed: true},
-      {url: 'https://initial-tab-2.com'}
+      {id: '1', url: 'https://initial-tab.com', sandboxed: true},
+      {id: '2', url: 'https://initial-tab-2.com'}
     ];
     mockIpcRenderer = {
       send: jest.fn()
     };
     window.preact = require('preact');
     window.preactHooks = require('preact/hooks');
+    spyUseReducer = jest.spyOn(window.preactHooks, 'useReducer');
     window.htm = require('htm');
     window.APP_EVENTS = {};
     window.dictionaries = mockDictionaries;
@@ -60,8 +62,8 @@ describe('Settings in Browser test suite', () => {
       expect(mockIpcRenderer.send).toHaveBeenCalledTimes(1);
       expect(mockIpcRenderer.send).toHaveBeenCalledWith('Save my settings',
         {tabs: [
-          {url: 'https://initial-tab.com', sandboxed: true},
-          {url: 'https://initial-tab-2.com'}
+          {id: '1', url: 'https://initial-tab.com', sandboxed: true},
+          {id: '2', url: 'https://initial-tab-2.com'}
         ], enabledDictionaries: ['en']});
     });
     test('Cancel should send cancel event', () => {
@@ -80,7 +82,7 @@ describe('Settings in Browser test suite', () => {
     let $addTabButton;
     let $submitButton;
     beforeEach(() => {
-      $input = document.querySelector('.settings__new-tab .input');
+      $input = document.querySelector('.settings__new-tab input[type="text"].input');
       $addTabButton = document.querySelector('.settings__new-tab .button');
       $tabContainer = document.querySelector('.settings__tabs');
       $submitButton = document.querySelector('.settings__submit');
@@ -115,7 +117,7 @@ describe('Settings in Browser test suite', () => {
         expect($tabContainer.childElementCount).toBe(2);
         await waitFor(() =>
           expect($input.classList.contains('is-danger')).toBe(true));
-        expect($tabContainer.querySelectorAll('.settings__tab input').length).toBe(2);
+        expect($tabContainer.querySelectorAll('.settings__tab .settings__tab-main input').length).toBe(2);
         expect($input.value).toBe('invalid:1337:url');
         expect($addTabButton.hasAttribute('disabled')).toBe(true);
         expect($submitButton.hasAttribute('disabled')).toBe(true);
@@ -129,7 +131,7 @@ describe('Settings in Browser test suite', () => {
         await waitFor(() =>
           expect($tabContainer.childElementCount).toBe(3));
         expect($input.classList.contains('is-success')).toBe(false);
-        expect($tabContainer.querySelectorAll('.settings__tab input')[2].value)
+        expect($tabContainer.querySelectorAll('.settings__tab .settings__tab-main input')[2].value)
           .toBe('https://info.cern.ch');
         expect($input.value).toBe('');
         expect($addTabButton.hasAttribute('disabled')).toBe(true);
@@ -145,7 +147,7 @@ describe('Settings in Browser test suite', () => {
           expect($tabContainer.childElementCount).toBe(3));
         await waitFor(() =>
           expect($input.classList.contains('is-success')).toBe(false));
-        expect($tabContainer.querySelectorAll('.settings__tab input')[2].value)
+        expect($tabContainer.querySelectorAll('.settings__tab .settings__tab-main input')[2].value)
           .toBe('http://info.cern.ch');
         expect($input.value).toBe('');
         expect($addTabButton.hasAttribute('disabled')).toBe(true);
@@ -182,7 +184,7 @@ describe('Settings in Browser test suite', () => {
       // Then
       await waitFor(() =>
         expect($tabContainer.childElementCount).toBe(3));
-      expect($tabContainer.querySelectorAll('.settings__tab input')[2].value)
+      expect($tabContainer.querySelectorAll('.settings__tab .settings__tab-main input')[2].value)
         .toBe('https://info.cern.ch');
       expect($input.value).toBe('');
       expect($addTabButton.hasAttribute('disabled')).toBe(true);
@@ -214,11 +216,41 @@ describe('Settings in Browser test suite', () => {
       // Given
       const $tabContainer = document.querySelector('.settings__tabs');
       const $trashIcon = $tabContainer.querySelector('.icon .fa-trash');
+      const initialChildren = $tabContainer.childElementCount;
+      expect(initialChildren).toBeGreaterThan(0);
       // When
       fireEvent.click($trashIcon);
       // Then
       await waitFor(() =>
-        expect($tabContainer.childElementCount).toBe(0));
+        expect($tabContainer.childElementCount).toBe(initialChildren - 1));
+    });
+    describe('Expand/Collapse icon click', () => {
+      test('collapsed tab, should expand tab', async () => {
+        // Given
+        const $expandIcon = document.querySelector('.settings__tab[data-id="1"] .icon .fa-chevron-right');
+        const $collapsedTab = $expandIcon.closest('.settings__tab');
+        expect($collapsedTab.classList.contains('settings__tab--expanded')).toBe(false);
+        // When
+        fireEvent.click($expandIcon);
+        // Then
+        await waitFor(() =>
+          expect($expandIcon.classList.contains('fa-chevron-down')).toBe(true));
+        expect($collapsedTab.classList.contains('settings__tab--expanded')).toBe(true);
+      });
+      test('expanded tab, should collapse tab', async () => {
+        // Given
+        const dispatch = spyUseReducer.mock.results.reverse()[0].value[1];
+        await dispatch({type: 'TOGGLE_TAB_EXPANDED', payload: '2'});
+        const $collapseIcon = document.querySelector('.settings__tab[data-id="2"] .icon .fa-chevron-down');
+        const $expandedTab = $collapseIcon.closest('.settings__tab');
+        expect($expandedTab.classList.contains('settings__tab--expanded')).toBe(true);
+        // When
+        fireEvent.click($collapseIcon);
+        // Then
+        await waitFor(() =>
+          expect($collapseIcon.classList.contains('fa-chevron-right')).toBe(true));
+        expect($expandedTab.classList.contains('settings__tab--expanded')).toBe(false);
+      });
     });
   });
   describe('Dictionary events', () => {