From b031ce9a0fe8b3afa997655bf3e6acf11effc338 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Adrian=20Or=C5=82=C3=B3w?= <adrian.orlow@fishbrain.com>
Date: Mon, 5 Feb 2024 14:39:15 +0100
Subject: [PATCH] feat: add plugins drawer tabs

---
 .../MapNavigation/MapNavigation.component.tsx |   8 +-
 .../NavBar/NavBar.component.tsx               |   2 +
 .../AvailablePluginsDrawer.component.test.tsx |  29 ++--
 .../LoadPlugin/LoadPlugin.component.test.tsx  |  17 ++-
 .../LoadPlugin/hooks/useLoadPlugin.ts         |  21 ++-
 src/components/Map/Map.component.tsx          |   2 +
 .../PluginContent.component.test.tsx          |  44 ++++++
 .../PluginContent/PluginContent.component.tsx |  19 +++
 .../PluginContent/PluginContent.constants.ts  |  13 ++
 .../Map/PluginsDrawer/PluginContent/index.ts  |   1 +
 .../utils/getPluginContentStyle.test.ts       |  26 ++++
 .../utils/getPluginContentStyle.ts            |  16 ++
 .../PluginsDrawer.component.test.tsx          |  88 +++++++++++
 .../PluginsDrawer/PluginsDrawer.component.tsx |  25 ++++
 .../PluginsDrawer/PluginsDrawer.constants.ts  |   2 +
 .../PluginHeaderInfo.component.test.tsx       | 104 +++++++++++++
 .../PluginHeaderInfo.component.tsx            |  25 ++++
 .../PluginHeaderInfo.constants.ts             |   1 +
 .../PluginsHeader/PluginHeaderInfo/index.ts   |   1 +
 .../LoadPluginButton.component.test.tsx       |  82 +++++++++++
 .../LoadPluginButton.component.tsx            |  28 ++++
 .../LoadPluginButton/index.ts                 |   1 +
 .../PluginOpenButton.component.test.tsx       |  76 ++++++++++
 .../PluginOpenButton.component.tsx            |  37 +++++
 .../PluginsHeader/PluginOpenButton/index.ts   |   1 +
 .../PluginsHeader.component.test.tsx          | 124 ++++++++++++++++
 .../PluginsHeader/PluginsHeader.component.tsx |  38 +++++
 .../Map/PluginsDrawer/PluginsHeader/index.ts  |   1 +
 .../PluginSingleTab.component.test.tsx        | 138 ++++++++++++++++++
 .../PluginSingleTab.component.tsx             |  61 ++++++++
 .../PluginsTabs/PluginSingleTab/index.ts      |   1 +
 .../PluginsTabs.component.test.tsx            |  86 +++++++++++
 .../PluginsTabs/PluginsTabs.component.tsx     |  29 ++++
 .../Map/PluginsDrawer/PluginsTabs/index.ts    |   1 +
 src/components/Map/PluginsDrawer/index.ts     |   1 +
 src/constants/plugins.ts                      |   2 +
 src/models/mocks/pluginsMock.ts               |   4 +-
 src/redux/plugins/plugins.constants.ts        |   4 +
 src/redux/plugins/plugins.mock.ts             |   8 +-
 src/redux/plugins/plugins.reducers.ts         |  23 ++-
 src/redux/plugins/plugins.selectors.ts        |  33 ++++-
 src/redux/plugins/plugins.slice.ts            |  11 +-
 src/redux/plugins/plugins.thunks.ts           |  15 +-
 src/redux/plugins/plugins.types.ts            |   9 ++
 .../pluginsManager/pluginsManager.test.ts     |  58 +++++++-
 src/services/pluginsManager/pluginsManager.ts |  29 +++-
 .../pluginsManager/pluginsManager.types.ts    |   3 +
 src/shared/Icon/Icon.component.tsx            |  14 +-
 src/shared/Icon/Icons/ReloadIcon.tsx          |  19 +++
 src/types/iconTypes.ts                        |   5 +-
 50 files changed, 1328 insertions(+), 58 deletions(-)
 create mode 100644 src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.test.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginContent/PluginContent.constants.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginContent/index.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginContent/utils/getPluginContentStyle.test.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginContent/utils/getPluginContentStyle.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginsDrawer.component.test.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsDrawer.component.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsDrawer.constants.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.test.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.constants.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/index.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/LoadPluginButton.component.test.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/LoadPluginButton.component.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/index.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.test.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/index.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.test.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsHeader/index.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.test.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/index.ts
 create mode 100644 src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.test.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.tsx
 create mode 100644 src/components/Map/PluginsDrawer/PluginsTabs/index.ts
 create mode 100644 src/components/Map/PluginsDrawer/index.ts
 create mode 100644 src/constants/plugins.ts
 create mode 100644 src/shared/Icon/Icons/ReloadIcon.tsx

diff --git a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx
index 135deed5..293fba3a 100644
--- a/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx
+++ b/src/components/FunctionalArea/MapNavigation/MapNavigation.component.tsx
@@ -1,12 +1,12 @@
-import { MouseEvent } from 'react';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { MAIN_MAP } from '@/redux/map/map.constants';
 import { mapModelIdSelector, mapOpenedMapsSelector } from '@/redux/map/map.selectors';
 import { closeMap, closeMapAndSetMainMapActive, setActiveMap } from '@/redux/map/map.slice';
 import { OppenedMap } from '@/redux/map/map.types';
 import { Button } from '@/shared/Button';
 import { Icon } from '@/shared/Icon';
-import { MAIN_MAP } from '@/redux/map/map.constants';
+import { MouseEvent } from 'react';
 import { twMerge } from 'tailwind-merge';
 
 export const MapNavigation = (): JSX.Element => {
@@ -51,10 +51,6 @@ export const MapNavigation = (): JSX.Element => {
           )}
         </Button>
       ))}
-      {/* TODO: REMOVE WHEN PLUGIN DRAWER IS IMPLEMENTED */}
-      <div className="fixed bottom-0 right-0 top-[104px] w-96 bg-white">
-        <div id="plugins" />
-      </div>
     </div>
   );
 };
diff --git a/src/components/FunctionalArea/NavBar/NavBar.component.tsx b/src/components/FunctionalArea/NavBar/NavBar.component.tsx
index 830131c1..4c0be4e1 100644
--- a/src/components/FunctionalArea/NavBar/NavBar.component.tsx
+++ b/src/components/FunctionalArea/NavBar/NavBar.component.tsx
@@ -4,6 +4,7 @@ import { openDrawer } from '@/redux/drawer/drawer.slice';
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { openLegend } from '@/redux/legend/legend.slice';
 import { openLoginModal } from '@/redux/modal/modal.slice';
+import { openPluginsDrawer } from '@/redux/plugins/plugins.slice';
 import { IconButton } from '@/shared/IconButton';
 import Image from 'next/image';
 
@@ -16,6 +17,7 @@ export const NavBar = (): JSX.Element => {
 
   const openDrawerPlugins = (): void => {
     dispatch(openDrawer('available-plugins'));
+    dispatch(openPluginsDrawer());
   };
 
   const openDrawerExport = (): void => {
diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.component.test.tsx b/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.component.test.tsx
index 06d70d35..436f89c6 100644
--- a/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.component.test.tsx
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/AvailablePluginsDrawer.component.test.tsx
@@ -34,20 +34,23 @@ describe('AvailablePluginsDrawer - component', () => {
       expect(loadPluginFromUrlInput).toBeInTheDocument();
     });
 
-    it.each(PLUGINS_MOCK)('should render render all public plugins', currentPlugin => {
-      renderComponent({
-        ...INITIAL_STORE_STATE_MOCK,
-        plugins: {
-          ...INITIAL_STORE_STATE_MOCK.plugins,
-          list: {
-            ...INITIAL_STORE_STATE_MOCK.plugins.list,
-            data: PLUGINS_MOCK,
+    it.each(PLUGINS_MOCK.filter(p => p.isPublic))(
+      'should render render all public plugins',
+      currentPlugin => {
+        renderComponent({
+          ...INITIAL_STORE_STATE_MOCK,
+          plugins: {
+            ...INITIAL_STORE_STATE_MOCK.plugins,
+            list: {
+              ...INITIAL_STORE_STATE_MOCK.plugins.list,
+              data: PLUGINS_MOCK,
+            },
           },
-        },
-      });
+        });
 
-      const pluginLabel = screen.getByText(currentPlugin.name);
-      expect(pluginLabel).toBeInTheDocument();
-    });
+        const pluginLabel = screen.getByText(currentPlugin.name);
+        expect(pluginLabel).toBeInTheDocument();
+      },
+    );
   });
 });
diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/LoadPlugin.component.test.tsx b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/LoadPlugin.component.test.tsx
index ba8ea94c..ca74841a 100644
--- a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/LoadPlugin.component.test.tsx
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/LoadPlugin.component.test.tsx
@@ -1,16 +1,19 @@
 /* eslint-disable no-magic-numbers */
 import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
 import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
-import { render, screen } from '@testing-library/react';
-import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
-import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener';
+import { apiPath } from '@/redux/apiPath';
+import {
+  PLUGINS_INITIAL_STATE_LIST_MOCK,
+  PLUGINS_INITIAL_STATE_MOCK,
+} from '@/redux/plugins/plugins.mock';
 import { StoreType } from '@/redux/store';
 import { mockNetworkResponse } from '@/utils/mockNetworkResponse';
-import MockAdapter from 'axios-mock-adapter';
+import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener';
+import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
+import { render, screen } from '@testing-library/react';
 import axios, { HttpStatusCode } from 'axios';
-import { apiPath } from '@/redux/apiPath';
+import MockAdapter from 'axios-mock-adapter';
 import { act } from 'react-dom/test-utils';
-import { PLUGINS_INITIAL_STATE_LIST_MOCK } from '@/redux/plugins/plugins.mock';
 import { LoadPlugin, Props } from './LoadPlugin.component';
 
 const mockedAxiosApiClient = mockNetworkResponse();
@@ -56,6 +59,7 @@ describe('LoadPlugin - component', () => {
         { plugin },
         {
           plugins: {
+            ...PLUGINS_INITIAL_STATE_MOCK,
             activePlugins: {
               data: {
                 [plugin.hash]: plugin,
@@ -81,6 +85,7 @@ describe('LoadPlugin - component', () => {
         { plugin },
         {
           plugins: {
+            ...PLUGINS_INITIAL_STATE_MOCK,
             activePlugins: {
               data: {
                 [plugin.hash]: plugin,
diff --git a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts
index d1b04f65..a6ec51dc 100644
--- a/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts
+++ b/src/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin.ts
@@ -1,14 +1,22 @@
 import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
 import { useAppSelector } from '@/redux/hooks/useAppSelector';
-import { isPluginActiveSelector, isPluginLoadingSelector } from '@/redux/plugins/plugins.selectors';
+import {
+  isPluginActiveSelector,
+  isPluginLoadingSelector,
+  isPluginSelectedSelector,
+} from '@/redux/plugins/plugins.selectors';
 import { removePlugin } from '@/redux/plugins/plugins.slice';
 import { PluginsManager } from '@/services/pluginsManager';
 import axios from 'axios';
 
 type UseLoadPluginReturnType = {
   togglePlugin: () => void;
+  loadPlugin: () => Promise<void>;
+  unloadPlugin: () => void;
+  reloadPlugin: () => void;
   isPluginActive: boolean;
   isPluginLoading: boolean;
+  isPluginSelected: boolean;
 };
 
 type UseLoadPluginProps = {
@@ -19,6 +27,7 @@ type UseLoadPluginProps = {
 export const useLoadPlugin = ({ hash, pluginUrl }: UseLoadPluginProps): UseLoadPluginReturnType => {
   const isPluginActive = useAppSelector(state => isPluginActiveSelector(state, hash));
   const isPluginLoading = useAppSelector(state => isPluginLoadingSelector(state, hash));
+  const isPluginSelected = useAppSelector(state => isPluginSelectedSelector(state, hash));
 
   const dispatch = useAppDispatch();
 
@@ -39,6 +48,12 @@ export const useLoadPlugin = ({ hash, pluginUrl }: UseLoadPluginProps): UseLoadP
 
   const handleUnloadPlugin = (): void => {
     dispatch(removePlugin({ pluginId: hash }));
+    PluginsManager.removePluginContent({ hash });
+  };
+
+  const handleReloadPlugin = async (): Promise<void> => {
+    handleUnloadPlugin();
+    await handleLoadPlugin();
   };
 
   const togglePlugin = (): void => {
@@ -50,7 +65,11 @@ export const useLoadPlugin = ({ hash, pluginUrl }: UseLoadPluginProps): UseLoadP
   };
 
   return {
+    isPluginSelected,
     togglePlugin,
+    loadPlugin: handleLoadPlugin,
+    unloadPlugin: handleUnloadPlugin,
+    reloadPlugin: handleReloadPlugin,
     isPluginActive,
     isPluginLoading,
   };
diff --git a/src/components/Map/Map.component.tsx b/src/components/Map/Map.component.tsx
index 36ac1a1f..72d18ebf 100644
--- a/src/components/Map/Map.component.tsx
+++ b/src/components/Map/Map.component.tsx
@@ -3,6 +3,7 @@ import { Legend } from '@/components/Map/Legend';
 import { MapAdditionalActions } from './MapAdditionalActions';
 import { MapAdditionalOptions } from './MapAdditionalOptions';
 import { MapViewer } from './MapViewer/MapViewer.component';
+import { PluginsDrawer } from './PluginsDrawer';
 
 export const Map = (): JSX.Element => (
   <div
@@ -11,6 +12,7 @@ export const Map = (): JSX.Element => (
   >
     <MapAdditionalOptions />
     <Drawer />
+    <PluginsDrawer />
     <MapViewer />
     <Legend />
     <MapAdditionalActions />
diff --git a/src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.test.tsx b/src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.test.tsx
new file mode 100644
index 00000000..94f0d4f0
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.test.tsx
@@ -0,0 +1,44 @@
+import { PLUGINS_CONTENT_ELEMENT_ID } from '@/constants/plugins';
+import { PLUGINS_INITIAL_STATE_MOCK } from '@/redux/plugins/plugins.mock';
+import { StoreType } from '@/redux/store';
+import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener';
+import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
+import { render, screen } from '@testing-library/react';
+import { PluginContent } from './PluginContent.component';
+
+const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
+  const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <PluginContent />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+describe('PluginContent - component', () => {
+  describe('when always', () => {
+    beforeEach(() => {
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+        },
+      });
+    });
+
+    it('should render plugins content', () => {
+      const element = screen.getByTestId('drawer-plugins-content');
+      expect(element).toBeInTheDocument();
+      expect(element.id).toBe(PLUGINS_CONTENT_ELEMENT_ID);
+    });
+  });
+
+  /*
+    NOTE:
+    Testing of <style jsx global> is not possible in Jest
+  */
+});
diff --git a/src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.tsx b/src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.tsx
new file mode 100644
index 00000000..143111c7
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginContent/PluginContent.component.tsx
@@ -0,0 +1,19 @@
+import { PLUGINS_CONTENT_ELEMENT_ID } from '@/constants/plugins';
+import { selectedDrawerPluginSelector } from '@/redux/plugins/plugins.selectors';
+import { useSelector } from 'react-redux';
+import { getPluginContentStyle } from './utils/getPluginContentStyle';
+
+export const PluginContent = (): JSX.Element => {
+  const selectedPlugin = useSelector(selectedDrawerPluginSelector);
+
+  return (
+    <>
+      <style jsx global>
+        {`
+          ${getPluginContentStyle(selectedPlugin)}
+        `}
+      </style>
+      <div id={PLUGINS_CONTENT_ELEMENT_ID} data-testid="drawer-plugins-content" />
+    </>
+  );
+};
diff --git a/src/components/Map/PluginsDrawer/PluginContent/PluginContent.constants.ts b/src/components/Map/PluginsDrawer/PluginContent/PluginContent.constants.ts
new file mode 100644
index 00000000..3ae40067
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginContent/PluginContent.constants.ts
@@ -0,0 +1,13 @@
+import { PLUGINS_CONTENT_ELEMENT_ATTR_NAME } from '@/constants/plugins';
+
+export const HIDE_ALL_ELEMENTS_STYLE = `
+    div[${PLUGINS_CONTENT_ELEMENT_ATTR_NAME}] {
+        display: none;
+    }
+`;
+
+export const SHOW_SELECTED_PLUGIN_ELEMENT_STYLE = (hash: string): string => `
+    div[${PLUGINS_CONTENT_ELEMENT_ATTR_NAME}='${hash}'] {
+        display: unset;
+    }
+`;
diff --git a/src/components/Map/PluginsDrawer/PluginContent/index.ts b/src/components/Map/PluginsDrawer/PluginContent/index.ts
new file mode 100644
index 00000000..f9abaf2a
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginContent/index.ts
@@ -0,0 +1 @@
+export { PluginContent } from './PluginContent.component';
diff --git a/src/components/Map/PluginsDrawer/PluginContent/utils/getPluginContentStyle.test.ts b/src/components/Map/PluginsDrawer/PluginContent/utils/getPluginContentStyle.test.ts
new file mode 100644
index 00000000..137c33be
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginContent/utils/getPluginContentStyle.test.ts
@@ -0,0 +1,26 @@
+import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
+import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
+import {
+  HIDE_ALL_ELEMENTS_STYLE,
+  SHOW_SELECTED_PLUGIN_ELEMENT_STYLE,
+} from '../PluginContent.constants';
+import { getPluginContentStyle } from './getPluginContentStyle';
+
+describe('getPluginContentStyle - util', () => {
+  describe('when selectedPlugin is NOT present', () => {
+    it('should return valid value', () => {
+      expect(getPluginContentStyle(undefined)).toBe(HIDE_ALL_ELEMENTS_STYLE);
+    });
+  });
+
+  describe('when selectedPlugin is present', () => {
+    const selectedPlugin = PLUGINS_MOCK[FIRST_ARRAY_ELEMENT];
+
+    it('should return valid value', () => {
+      const result = getPluginContentStyle(selectedPlugin);
+
+      expect(result.includes(HIDE_ALL_ELEMENTS_STYLE)).toBeTruthy();
+      expect(result.includes(SHOW_SELECTED_PLUGIN_ELEMENT_STYLE(selectedPlugin.hash))).toBeTruthy();
+    });
+  });
+});
diff --git a/src/components/Map/PluginsDrawer/PluginContent/utils/getPluginContentStyle.ts b/src/components/Map/PluginsDrawer/PluginContent/utils/getPluginContentStyle.ts
new file mode 100644
index 00000000..c6befb45
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginContent/utils/getPluginContentStyle.ts
@@ -0,0 +1,16 @@
+import { MinervaPlugin } from '@/types/models';
+import {
+  HIDE_ALL_ELEMENTS_STYLE,
+  SHOW_SELECTED_PLUGIN_ELEMENT_STYLE,
+} from '../PluginContent.constants';
+
+export const getPluginContentStyle = (selectedPlugin?: MinervaPlugin): string => {
+  if (!selectedPlugin) {
+    return HIDE_ALL_ELEMENTS_STYLE;
+  }
+
+  return `
+    ${HIDE_ALL_ELEMENTS_STYLE}
+    ${SHOW_SELECTED_PLUGIN_ELEMENT_STYLE(selectedPlugin.hash)}
+  `;
+};
diff --git a/src/components/Map/PluginsDrawer/PluginsDrawer.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsDrawer.component.test.tsx
new file mode 100644
index 00000000..47fde9e0
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsDrawer.component.test.tsx
@@ -0,0 +1,88 @@
+import { StoreType } from '@/redux/store';
+import { InitialStoreState } from '@/utils/testing/getReduxStoreActionsListener';
+import { getReduxWrapperWithStore } from '@/utils/testing/getReduxWrapperWithStore';
+
+import { PLUGINS_INITIAL_STATE_MOCK } from '@/redux/plugins/plugins.mock';
+import { render, screen } from '@testing-library/react';
+import { PluginsDrawer } from './PluginsDrawer.component';
+import { PLUGINS_DRAWER_ROLE } from './PluginsDrawer.constants';
+
+const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
+  const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <PluginsDrawer />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+describe('PluginsDrawer - component', () => {
+  describe('when drawer is open', () => {
+    beforeEach(() => {
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+          drawer: {
+            ...PLUGINS_INITIAL_STATE_MOCK.drawer,
+            isOpen: false,
+          },
+        },
+      });
+    });
+
+    it('should render component without show class', () => {
+      const drawer = screen.getByRole(PLUGINS_DRAWER_ROLE);
+
+      expect(drawer).not.toHaveClass('translate-x-0');
+    });
+  });
+
+  describe('when drawer is NOT open', () => {
+    beforeEach(() => {
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+          drawer: {
+            ...PLUGINS_INITIAL_STATE_MOCK.drawer,
+            isOpen: true,
+          },
+        },
+      });
+    });
+
+    it('should render component without show class', () => {
+      const drawer = screen.getByRole(PLUGINS_DRAWER_ROLE);
+      expect(drawer).toHaveClass('translate-x-0');
+    });
+  });
+
+  describe('when always', () => {
+    beforeEach(() => {
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+        },
+      });
+    });
+
+    it('should render plugins tab', () => {
+      const element = screen.getByTestId('drawer-plugins-tab');
+      expect(element).toBeInTheDocument();
+    });
+
+    it('should render plugins tab', () => {
+      const element = screen.getByTestId('drawer-plugins-header');
+      expect(element).toBeInTheDocument();
+    });
+
+    it('should render plugins content', () => {
+      const element = screen.getByTestId('drawer-plugins-content');
+      expect(element).toBeInTheDocument();
+    });
+  });
+});
diff --git a/src/components/Map/PluginsDrawer/PluginsDrawer.component.tsx b/src/components/Map/PluginsDrawer/PluginsDrawer.component.tsx
new file mode 100644
index 00000000..c06d9774
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsDrawer.component.tsx
@@ -0,0 +1,25 @@
+import { pluginsDrawerSelector } from '@/redux/plugins/plugins.selectors';
+import { useSelector } from 'react-redux';
+import { twMerge } from 'tailwind-merge';
+import { PluginContent } from './PluginContent';
+import { PLUGINS_DRAWER_ROLE } from './PluginsDrawer.constants';
+import { PluginsHeader } from './PluginsHeader';
+import { PluginsTabs } from './PluginsTabs/PluginsTabs.component';
+
+export const PluginsDrawer = (): JSX.Element => {
+  const { isOpen } = useSelector(pluginsDrawerSelector);
+
+  return (
+    <div
+      className={twMerge(
+        'absolute bottom-0 right-0 top-[104px] z-20 h-calc-drawer w-[432px] translate-x-full transform border border-divide bg-white-pearl text-font-500 transition-all duration-500',
+        isOpen && 'translate-x-0',
+      )}
+      role={PLUGINS_DRAWER_ROLE}
+    >
+      <PluginsTabs />
+      <PluginsHeader />
+      <PluginContent />
+    </div>
+  );
+};
diff --git a/src/components/Map/PluginsDrawer/PluginsDrawer.constants.ts b/src/components/Map/PluginsDrawer/PluginsDrawer.constants.ts
new file mode 100644
index 00000000..4ce466ee
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsDrawer.constants.ts
@@ -0,0 +1,2 @@
+export const PLUGINS_DRAWER_ROLE = 'plugins-drawer';
+export const CLOSE_PLUGINS_DRAWER_BUTTON_ROLE = 'close-plugins-drawer-drawer-button';
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.test.tsx
new file mode 100644
index 00000000..fa013b07
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.test.tsx
@@ -0,0 +1,104 @@
+import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
+import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
+import {
+  PLUGINS_INITIAL_STATE_LIST_MOCK,
+  PLUGINS_INITIAL_STATE_MOCK,
+} from '@/redux/plugins/plugins.mock';
+import { AppDispatch, RootState } from '@/redux/store';
+import { PluginsManager } from '@/services/pluginsManager';
+import { MinervaPlugin } from '@/types/models';
+import {
+  InitialStoreState,
+  getReduxStoreWithActionsListener,
+} from '@/utils/testing/getReduxStoreActionsListener';
+import { render, screen, waitFor } from '@testing-library/react';
+import axios, { HttpStatusCode } from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { MockStoreEnhanced } from 'redux-mock-store';
+import { PluginHeaderInfo } from './PluginHeaderInfo.component';
+import { RELOAD_PLUGIN_DRAWER_BUTTON_ROLE } from './PluginHeaderInfo.constants';
+
+const mockedAxiosClient = new MockAdapter(axios);
+
+const renderComponent = (
+  initialStore: InitialStoreState,
+  plugin: MinervaPlugin,
+): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
+  const { Wrapper, store } = getReduxStoreWithActionsListener(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <PluginHeaderInfo plugin={plugin} />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+const removePluginContentSpy = jest.spyOn(PluginsManager, 'removePluginContent');
+const setHashedPluginSpy = jest.spyOn(PluginsManager, 'setHashedPlugin');
+
+const PLUGIN = PLUGINS_MOCK[FIRST_ARRAY_ELEMENT];
+
+const STATE = {
+  plugins: {
+    ...PLUGINS_INITIAL_STATE_MOCK,
+    drawer: {
+      ...PLUGINS_INITIAL_STATE_MOCK.drawer,
+      currentPluginHash: PLUGIN.hash,
+    },
+    activePlugins: {
+      data: {
+        [PLUGIN.hash]: PLUGIN,
+      },
+      pluginsId: [PLUGIN.hash],
+    },
+    list: PLUGINS_INITIAL_STATE_LIST_MOCK,
+  },
+};
+
+describe('PluginHeaderInfo - component', () => {
+  it('renders plugin title and name', () => {
+    renderComponent(STATE, PLUGIN);
+
+    const title = screen.getByText(`Plugin:`, { exact: false });
+    const pluginName = screen.getByText(PLUGIN.name, { exact: false });
+
+    expect(title).toBeInTheDocument();
+    expect(pluginName).toBeInTheDocument();
+  });
+
+  it('renders plugin reload button', () => {
+    renderComponent(STATE, PLUGIN);
+
+    const button = screen.getByRole(RELOAD_PLUGIN_DRAWER_BUTTON_ROLE);
+    expect(button).toBeInTheDocument();
+  });
+
+  it('reload plugin on reload button', async () => {
+    const { store } = renderComponent(STATE, PLUGIN);
+    mockedAxiosClient.onGet(PLUGIN.urls[FIRST_ARRAY_ELEMENT]).reply(HttpStatusCode.Ok, '');
+
+    const button = screen.getByRole(RELOAD_PLUGIN_DRAWER_BUTTON_ROLE);
+    button.click();
+
+    const actions = store.getActions();
+
+    expect(removePluginContentSpy).toHaveBeenCalledWith({
+      hash: PLUGIN.hash,
+    });
+
+    expect(actions).toEqual([
+      { payload: { pluginId: '5e3fcb59588cc311ef9839feea6382eb' }, type: 'plugins/removePlugin' },
+    ]);
+
+    await waitFor(() => {
+      expect(setHashedPluginSpy).toHaveBeenCalledWith({
+        pluginScript: '',
+        pluginUrl: 'https://minerva-service.lcsb.uni.lu/plugins/disease-associations/plugin.js',
+      });
+    });
+  });
+});
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.tsx
new file mode 100644
index 00000000..7c77ecd9
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.component.tsx
@@ -0,0 +1,25 @@
+import { useLoadPlugin } from '@/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin';
+import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
+import { Icon } from '@/shared/Icon';
+import { MinervaPlugin } from '@/types/models';
+import { RELOAD_PLUGIN_DRAWER_BUTTON_ROLE } from './PluginHeaderInfo.constants';
+
+interface Props {
+  plugin: MinervaPlugin;
+}
+
+export const PluginHeaderInfo = ({ plugin }: Props): JSX.Element => {
+  const { reloadPlugin } = useLoadPlugin({
+    hash: plugin.hash,
+    pluginUrl: plugin.urls[FIRST_ARRAY_ELEMENT],
+  });
+
+  return (
+    <>
+      Plugin: <b>{plugin.name}</b>
+      <button type="button" onClick={reloadPlugin} role={RELOAD_PLUGIN_DRAWER_BUTTON_ROLE}>
+        <Icon name="reload" />
+      </button>
+    </>
+  );
+};
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.constants.ts b/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.constants.ts
new file mode 100644
index 00000000..5dd23022
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/PluginHeaderInfo.constants.ts
@@ -0,0 +1 @@
+export const RELOAD_PLUGIN_DRAWER_BUTTON_ROLE = 'reload-plugin-drawer-button';
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/index.ts b/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/index.ts
new file mode 100644
index 00000000..188cbbd6
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginHeaderInfo/index.ts
@@ -0,0 +1 @@
+export { PluginHeaderInfo } from './PluginHeaderInfo.component';
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/LoadPluginButton.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/LoadPluginButton.component.test.tsx
new file mode 100644
index 00000000..36a673cb
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/LoadPluginButton.component.test.tsx
@@ -0,0 +1,82 @@
+import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
+import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
+import {
+  PLUGINS_INITIAL_STATE_LIST_MOCK,
+  PLUGINS_INITIAL_STATE_MOCK,
+} from '@/redux/plugins/plugins.mock';
+import { AppDispatch, RootState } from '@/redux/store';
+import { PluginsManager } from '@/services/pluginsManager';
+import { MinervaPlugin } from '@/types/models';
+import {
+  InitialStoreState,
+  getReduxStoreWithActionsListener,
+} from '@/utils/testing/getReduxStoreActionsListener';
+import { render, screen, waitFor } from '@testing-library/react';
+import axios, { HttpStatusCode } from 'axios';
+import MockAdapter from 'axios-mock-adapter';
+import { MockStoreEnhanced } from 'redux-mock-store';
+import { LoadPluginButton } from './LoadPluginButton.component';
+
+const mockedAxiosClient = new MockAdapter(axios);
+
+const renderComponent = (
+  initialStore: InitialStoreState,
+  plugin: MinervaPlugin,
+): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
+  const { Wrapper, store } = getReduxStoreWithActionsListener(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <LoadPluginButton plugin={plugin} />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+const setHashedPluginSpy = jest.spyOn(PluginsManager, 'setHashedPlugin');
+
+const PLUGIN = PLUGINS_MOCK[FIRST_ARRAY_ELEMENT];
+
+const STATE = {
+  plugins: {
+    ...PLUGINS_INITIAL_STATE_MOCK,
+    drawer: {
+      ...PLUGINS_INITIAL_STATE_MOCK.drawer,
+      currentPluginHash: PLUGIN.hash,
+    },
+    activePlugins: {
+      data: {
+        [PLUGIN.hash]: PLUGIN,
+      },
+      pluginsId: [PLUGIN.hash],
+    },
+    list: PLUGINS_INITIAL_STATE_LIST_MOCK,
+  },
+};
+
+describe('LoadPluginButton - component', () => {
+  beforeEach(() => {
+    renderComponent(STATE, PLUGIN);
+  });
+
+  it('renders plugin button element', () => {
+    const element = screen.getByText(PLUGIN.name);
+    expect(element).toBeInTheDocument();
+  });
+
+  it('loads plugin on button click', async () => {
+    mockedAxiosClient.onGet(PLUGIN.urls[FIRST_ARRAY_ELEMENT]).reply(HttpStatusCode.Ok, '');
+    const button = screen.getByText(PLUGIN.name);
+    button.click();
+
+    await waitFor(() => {
+      expect(setHashedPluginSpy).toHaveBeenCalledWith({
+        pluginScript: '',
+        pluginUrl: 'https://minerva-service.lcsb.uni.lu/plugins/disease-associations/plugin.js',
+      });
+    });
+  });
+});
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/LoadPluginButton.component.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/LoadPluginButton.component.tsx
new file mode 100644
index 00000000..24c961ce
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/LoadPluginButton.component.tsx
@@ -0,0 +1,28 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import { useLoadPlugin } from '@/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin';
+import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
+import { MinervaPlugin } from '@/types/models';
+
+interface LoadPluginButtonProps {
+  plugin: MinervaPlugin;
+}
+
+export const LoadPluginButton = ({ plugin }: LoadPluginButtonProps): JSX.Element => {
+  const { hash, name } = plugin;
+
+  const { loadPlugin } = useLoadPlugin({
+    hash: plugin.hash,
+    pluginUrl: plugin.urls[FIRST_ARRAY_ELEMENT],
+  });
+
+  return (
+    <div
+      key={hash}
+      className="flex cursor-pointer flex-col border-t px-4 py-2 shadow-sm"
+      onClick={loadPlugin}
+    >
+      {name}
+    </div>
+  );
+};
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/index.ts b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/index.ts
new file mode 100644
index 00000000..2f91e9a4
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/LoadPluginButton/index.ts
@@ -0,0 +1 @@
+export { LoadPluginButton } from './LoadPluginButton.component';
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.test.tsx
new file mode 100644
index 00000000..01f6e7fc
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.test.tsx
@@ -0,0 +1,76 @@
+import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
+import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
+import { PLUGINS_INITIAL_STATE_MOCK } from '@/redux/plugins/plugins.mock';
+import { AppDispatch, RootState } from '@/redux/store';
+import {
+  InitialStoreState,
+  getReduxStoreWithActionsListener,
+} from '@/utils/testing/getReduxStoreActionsListener';
+import { render, screen } from '@testing-library/react';
+import { MockStoreEnhanced } from 'redux-mock-store';
+import { PluginOpenButton } from './PluginOpenButton.component';
+
+const renderComponent = (
+  initialStore: InitialStoreState,
+): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
+  const { Wrapper, store } = getReduxStoreWithActionsListener(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <PluginOpenButton />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+const PLUGIN = PLUGINS_MOCK[FIRST_ARRAY_ELEMENT];
+
+const STATE = {
+  plugins: {
+    ...PLUGINS_INITIAL_STATE_MOCK,
+    drawer: {
+      ...PLUGINS_INITIAL_STATE_MOCK.drawer,
+      currentPluginHash: PLUGIN.hash,
+    },
+    activePlugins: {
+      data: {
+        [PLUGIN.hash]: PLUGIN,
+      },
+      pluginsId: [PLUGIN.hash],
+    },
+  },
+};
+
+describe('PluginOpenButton - component', () => {
+  describe('when public plugins list is empty', () => {});
+
+  describe('when public plugins list is present', () => {
+    const publicPlugins = PLUGINS_MOCK.filter(p => p.isPublic);
+
+    beforeEach(() => {
+      renderComponent({
+        ...STATE,
+        plugins: {
+          ...STATE.plugins,
+          list: {
+            ...PLUGINS_INITIAL_STATE_MOCK.list,
+            data: PLUGINS_MOCK,
+          },
+        },
+      });
+    });
+
+    it.each(publicPlugins)('should render load plugin button for public plugins', plugin => {
+      const element = screen.getByText(plugin.name, { exact: false });
+      expect(element).toBeInTheDocument();
+    });
+
+    it('should render open new plugin button', () => {
+      const element = screen.getByText('Open new plugin', { exact: false });
+      expect(element).toBeInTheDocument();
+    });
+  });
+});
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.tsx
new file mode 100644
index 00000000..16c3032b
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/PluginOpenButton.component.tsx
@@ -0,0 +1,37 @@
+import { ZERO } from '@/constants/common';
+import { publicPluginsListWithoutActiveSelector } from '@/redux/plugins/plugins.selectors';
+import { Button } from '@/shared/Button';
+import { MinervaPlugin } from '@/types/models';
+import { useSelect } from 'downshift';
+import { useSelector } from 'react-redux';
+import { LoadPluginButton } from './LoadPluginButton';
+
+export const PluginOpenButton = (): JSX.Element | null => {
+  const publicPlugins = useSelector(publicPluginsListWithoutActiveSelector);
+
+  const { isOpen, getToggleButtonProps, getMenuProps } = useSelect({
+    items: publicPlugins,
+  });
+
+  return (
+    <div className="relative" data-testid="open-new-plugin-list">
+      {publicPlugins.length === ZERO ? null : (
+        <>
+          <Button icon="plus" isIcon isFrontIcon {...getToggleButtonProps()}>
+            Open new plugin
+          </Button>
+          <ul
+            className={`absolute left-[-50%] z-10 max-h-80 w-48 translate-x-1/4 overflow-x-hidden overflow-y-scroll rounded-sm border bg-white p-0 ps-0 ${
+              !isOpen && 'hidden'
+            }`}
+            {...getMenuProps()}
+          >
+            {publicPlugins.map((plugin: MinervaPlugin) => (
+              <LoadPluginButton key={plugin.hash} plugin={plugin} />
+            ))}
+          </ul>
+        </>
+      )}
+    </div>
+  );
+};
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/index.ts b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/index.ts
new file mode 100644
index 00000000..1de83e5a
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginOpenButton/index.ts
@@ -0,0 +1 @@
+export { PluginOpenButton } from './PluginOpenButton.component';
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.test.tsx
new file mode 100644
index 00000000..864f1d96
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.test.tsx
@@ -0,0 +1,124 @@
+import { FIRST_ARRAY_ELEMENT } from '@/constants/common';
+import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
+import {
+  PLUGINS_INITIAL_STATE_LIST_MOCK,
+  PLUGINS_INITIAL_STATE_MOCK,
+} from '@/redux/plugins/plugins.mock';
+import { AppDispatch, RootState } from '@/redux/store';
+import {
+  InitialStoreState,
+  getReduxStoreWithActionsListener,
+} from '@/utils/testing/getReduxStoreActionsListener';
+import { render, screen } from '@testing-library/react';
+import { MockStoreEnhanced } from 'redux-mock-store';
+import { CLOSE_PLUGINS_DRAWER_BUTTON_ROLE } from '../PluginsDrawer.constants';
+import { PluginsHeader } from './PluginsHeader.component';
+
+const renderComponent = (
+  initialStore?: InitialStoreState,
+): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
+  const { Wrapper, store } = getReduxStoreWithActionsListener(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <PluginsHeader />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+describe('PluginsHeader - component', () => {
+  describe('when currentPlugin is NOT present', () => {
+    beforeEach(() => {
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+          drawer: {
+            ...PLUGINS_INITIAL_STATE_MOCK.drawer,
+            currentPluginHash: undefined,
+          },
+        },
+      });
+    });
+
+    it('should render no plugin selected info', () => {
+      const element = screen.getByText('No plugin selected');
+      expect(element).toBeInTheDocument();
+    });
+  });
+
+  describe('when currentPlugin is present', () => {
+    const plugin = PLUGINS_MOCK[FIRST_ARRAY_ELEMENT];
+
+    beforeEach(() => {
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+          drawer: {
+            ...PLUGINS_INITIAL_STATE_MOCK.drawer,
+            currentPluginHash: plugin.hash,
+          },
+          activePlugins: {
+            data: {
+              [plugin.hash]: plugin,
+            },
+            pluginsId: [plugin.hash],
+          },
+          list: PLUGINS_INITIAL_STATE_LIST_MOCK,
+        },
+      });
+    });
+
+    it('should render header info', () => {
+      const title = screen.getByText(`Plugin:`, { exact: false });
+      const pluginName = screen.getByText(plugin.name, { exact: false });
+
+      expect(title).toBeInTheDocument();
+      expect(pluginName).toBeInTheDocument();
+    });
+  });
+
+  describe('when always', () => {
+    it('should render close plugins drawer button', () => {
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+        },
+      });
+
+      const element = screen.getByRole(CLOSE_PLUGINS_DRAWER_BUTTON_ROLE);
+      expect(element).toBeInTheDocument();
+    });
+
+    it('should render plugin open button', () => {
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+        },
+      });
+
+      const element = screen.getByTestId('open-new-plugin-list');
+      expect(element).toBeInTheDocument();
+    });
+
+    it('should dispatch close plugins drawer on button click', () => {
+      const { store } = renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+        },
+      });
+
+      const element = screen.getByRole(CLOSE_PLUGINS_DRAWER_BUTTON_ROLE);
+      element.click();
+
+      const actions = store.getActions();
+      expect(actions[FIRST_ARRAY_ELEMENT]).toStrictEqual({
+        payload: undefined,
+        type: 'plugins/closePluginsDrawer',
+      });
+    });
+  });
+});
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.tsx b/src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.tsx
new file mode 100644
index 00000000..b8706250
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/PluginsHeader.component.tsx
@@ -0,0 +1,38 @@
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { selectedDrawerPluginSelector } from '@/redux/plugins/plugins.selectors';
+import { closePluginsDrawer } from '@/redux/plugins/plugins.slice';
+import { IconButton } from '@/shared/IconButton';
+import { useSelector } from 'react-redux';
+import { CLOSE_PLUGINS_DRAWER_BUTTON_ROLE } from '../PluginsDrawer.constants';
+import { PluginHeaderInfo } from './PluginHeaderInfo';
+import { PluginOpenButton } from './PluginOpenButton';
+
+export const PluginsHeader = (): JSX.Element => {
+  const dispatch = useAppDispatch();
+  const currentPlugin = useSelector(selectedDrawerPluginSelector);
+
+  const onCloseButtonClick = (): void => {
+    dispatch(closePluginsDrawer());
+  };
+
+  return (
+    <div
+      className="flex flex-col items-start gap-4 border-b border-b-divide bg-white-pearl p-6"
+      data-testid="drawer-plugins-header"
+    >
+      <div className="flex w-full justify-between">
+        <div className="flex items-center gap-2 text-xl">
+          {currentPlugin ? <PluginHeaderInfo plugin={currentPlugin} /> : <>No plugin selected</>}
+        </div>
+        <IconButton
+          className="h-auto w-auto bg-white-pearl p-0"
+          classNameIcon="fill-font-500"
+          icon="close"
+          role={CLOSE_PLUGINS_DRAWER_BUTTON_ROLE}
+          onClick={onCloseButtonClick}
+        />
+      </div>
+      <PluginOpenButton />
+    </div>
+  );
+};
diff --git a/src/components/Map/PluginsDrawer/PluginsHeader/index.ts b/src/components/Map/PluginsDrawer/PluginsHeader/index.ts
new file mode 100644
index 00000000..1d6261a4
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsHeader/index.ts
@@ -0,0 +1 @@
+export { PluginsHeader } from './PluginsHeader.component';
diff --git a/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.test.tsx
new file mode 100644
index 00000000..157d4220
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.test.tsx
@@ -0,0 +1,138 @@
+import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT, THIRD_ARRAY_ELEMENT } from '@/constants/common';
+import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
+import {
+  PLUGINS_INITIAL_STATE_LIST_MOCK,
+  PLUGINS_INITIAL_STATE_MOCK,
+} from '@/redux/plugins/plugins.mock';
+import { AppDispatch, RootState } from '@/redux/store';
+import { PluginsManager } from '@/services/pluginsManager';
+import { MinervaPlugin } from '@/types/models';
+import {
+  InitialStoreState,
+  getReduxStoreWithActionsListener,
+} from '@/utils/testing/getReduxStoreActionsListener';
+import { render, screen } from '@testing-library/react';
+import { MockStoreEnhanced } from 'redux-mock-store';
+import { PluginSingleTab } from './PluginSingleTab.component';
+
+const renderComponent = (
+  initialStore: InitialStoreState,
+  plugin: MinervaPlugin,
+): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
+  const { Wrapper, store } = getReduxStoreWithActionsListener(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <PluginSingleTab plugin={plugin} />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+const removePluginContentSpy = jest.spyOn(PluginsManager, 'removePluginContent');
+
+const PLUGIN = PLUGINS_MOCK[FIRST_ARRAY_ELEMENT];
+const PLUGIN_2 = PLUGINS_MOCK[SECOND_ARRAY_ELEMENT];
+const PLUGIN_3 = PLUGINS_MOCK[THIRD_ARRAY_ELEMENT];
+
+const STATE = {
+  plugins: {
+    ...PLUGINS_INITIAL_STATE_MOCK,
+    drawer: {
+      ...PLUGINS_INITIAL_STATE_MOCK.drawer,
+      currentPluginHash: PLUGIN.hash,
+    },
+    activePlugins: {
+      data: {
+        [PLUGIN.hash]: PLUGIN,
+      },
+      pluginsId: [PLUGIN.hash],
+    },
+    list: PLUGINS_INITIAL_STATE_LIST_MOCK,
+  },
+};
+
+describe('PluginSingleTab - component', () => {
+  describe('when always', () => {
+    it('should render plugin name', () => {
+      renderComponent(STATE, PLUGIN);
+
+      const element = screen.getByText(PLUGIN.name);
+      expect(element).toBeInTheDocument();
+    });
+
+    it('should render close button', () => {
+      renderComponent(STATE, PLUGIN);
+
+      const element = screen.getByTestId('close-icon');
+      expect(element).toBeInTheDocument();
+    });
+
+    it('should dispatch close action on close btn click', () => {
+      const { store } = renderComponent(STATE, PLUGIN);
+      const element = screen.getByTestId('close-icon');
+      element.click();
+
+      const actions = store.getActions();
+
+      expect(removePluginContentSpy).toHaveBeenCalledWith({
+        hash: PLUGIN.hash,
+      });
+
+      expect(actions).toEqual([
+        { payload: { pluginId: '5e3fcb59588cc311ef9839feea6382eb' }, type: 'plugins/removePlugin' },
+      ]);
+    });
+
+    it('should dispatch close action on close btn click and new current drawer on last active plugin', () => {
+      const { store } = renderComponent(
+        {
+          ...STATE,
+          plugins: {
+            ...PLUGINS_INITIAL_STATE_MOCK,
+            activePlugins: {
+              data: {
+                [PLUGIN.hash]: PLUGIN,
+                [PLUGIN_2.hash]: PLUGIN_2,
+                [PLUGIN_3.hash]: PLUGIN_3,
+              },
+              pluginsId: [PLUGIN.hash, PLUGIN_2.hash, PLUGIN_3.hash],
+            },
+          },
+        },
+        PLUGIN,
+      );
+      const element = screen.getByTestId('close-icon');
+      element.click();
+
+      const actions = store.getActions();
+
+      expect(removePluginContentSpy).toHaveBeenCalledWith({
+        hash: PLUGIN.hash,
+      });
+
+      expect(actions).toEqual([
+        { payload: { pluginId: '5e3fcb59588cc311ef9839feea6382eb' }, type: 'plugins/removePlugin' },
+        {
+          payload: '5314b9f996e56e67f0dad65e7df8b73b',
+          type: 'plugins/setCurrentDrawerPluginHash',
+        },
+      ]);
+    });
+
+    it('should dispatch set current drawer on tab click', () => {
+      const { store } = renderComponent(STATE, PLUGIN);
+      const element = screen.getByText(PLUGIN.name);
+      element.click();
+
+      const actions = store.getActions();
+
+      expect(actions).toStrictEqual([
+        { payload: '5e3fcb59588cc311ef9839feea6382eb', type: 'plugins/setCurrentDrawerPluginHash' },
+      ]);
+    });
+  });
+});
diff --git a/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.tsx b/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.tsx
new file mode 100644
index 00000000..c1a4c58a
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/PluginSingleTab.component.tsx
@@ -0,0 +1,61 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import { useLoadPlugin } from '@/components/Map/Drawer/AvailablePluginsDrawer/LoadPlugin/hooks/useLoadPlugin';
+import { FIRST_ARRAY_ELEMENT, ZERO } from '@/constants/common';
+import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { allActivePluginsSelector } from '@/redux/plugins/plugins.selectors';
+import { setCurrentDrawerPluginHash } from '@/redux/plugins/plugins.slice';
+import { Button } from '@/shared/Button';
+import { Icon } from '@/shared/Icon';
+import { MinervaPlugin } from '@/types/models';
+import { twMerge } from 'tailwind-merge';
+
+interface Props {
+  plugin: MinervaPlugin;
+}
+
+export const PluginSingleTab = ({ plugin }: Props): JSX.Element => {
+  const dispatch = useAppDispatch();
+  const allActivePlugins = useAppSelector(allActivePluginsSelector);
+
+  const { unloadPlugin, isPluginSelected } = useLoadPlugin({
+    hash: plugin.hash,
+    pluginUrl: plugin.urls[FIRST_ARRAY_ELEMENT],
+  });
+
+  const onPluginTabClick = (): void => {
+    dispatch(setCurrentDrawerPluginHash(plugin.hash));
+  };
+
+  const onPluginUnload = (event: React.MouseEvent<HTMLDivElement>): void => {
+    event.stopPropagation();
+    unloadPlugin();
+
+    const newAllActivePlugins = allActivePlugins.filter(p => p.hash !== plugin.hash);
+    const lastActivePlugin = newAllActivePlugins.pop();
+    if (lastActivePlugin) {
+      dispatch(setCurrentDrawerPluginHash(lastActivePlugin.hash));
+    }
+  };
+
+  return (
+    <Button
+      className={twMerge(
+        'h-10 whitespace-nowrap',
+        isPluginSelected ? 'bg-[#EBF4FF]' : 'font-normal',
+      )}
+      variantStyles={isPluginSelected ? 'secondary' : 'ghost'}
+      onClick={(): void => onPluginTabClick()}
+    >
+      {plugin.name}
+      <div
+        onClick={(event): void => onPluginUnload(event)}
+        data-testid="close-icon"
+        role="button"
+        tabIndex={ZERO}
+      >
+        <Icon name="close" className="ml-3 h-5 w-5 fill-font-400" />
+      </div>
+    </Button>
+  );
+};
diff --git a/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/index.ts b/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/index.ts
new file mode 100644
index 00000000..2b6e0df2
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsTabs/PluginSingleTab/index.ts
@@ -0,0 +1 @@
+export { PluginSingleTab } from './PluginSingleTab.component';
diff --git a/src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.test.tsx b/src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.test.tsx
new file mode 100644
index 00000000..64bc4670
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.test.tsx
@@ -0,0 +1,86 @@
+import { FIRST_ARRAY_ELEMENT, SECOND_ARRAY_ELEMENT } from '@/constants/common';
+import { PLUGINS_MOCK } from '@/models/mocks/pluginsMock';
+import {
+  PLUGINS_INITIAL_STATE_LIST_MOCK,
+  PLUGINS_INITIAL_STATE_MOCK,
+} from '@/redux/plugins/plugins.mock';
+import { AppDispatch, RootState } from '@/redux/store';
+import {
+  InitialStoreState,
+  getReduxStoreWithActionsListener,
+} from '@/utils/testing/getReduxStoreActionsListener';
+import { render, screen } from '@testing-library/react';
+import { MockStoreEnhanced } from 'redux-mock-store';
+import { PluginsTabs } from './PluginsTabs.component';
+
+const renderComponent = (
+  initialStore?: InitialStoreState,
+): { store: MockStoreEnhanced<Partial<RootState>, AppDispatch> } => {
+  const { Wrapper, store } = getReduxStoreWithActionsListener(initialStore);
+  return (
+    render(
+      <Wrapper>
+        <PluginsTabs />
+      </Wrapper>,
+    ),
+    {
+      store,
+    }
+  );
+};
+
+const PLUGIN = PLUGINS_MOCK[FIRST_ARRAY_ELEMENT];
+const PLUGIN_2 = PLUGINS_MOCK[SECOND_ARRAY_ELEMENT];
+
+describe('PluginsTabs - component', () => {
+  describe('when active plugins list is empty', () => {
+    beforeEach(() => {
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+          drawer: {
+            ...PLUGINS_INITIAL_STATE_MOCK.drawer,
+            currentPluginHash: PLUGIN.hash,
+          },
+          activePlugins: {
+            data: {},
+            pluginsId: [],
+          },
+          list: PLUGINS_INITIAL_STATE_LIST_MOCK,
+        },
+      });
+    });
+
+    it('should render empty plugin info', () => {
+      const element = screen.getByText(`You don't have any opened plugin yet`);
+      expect(element).toBeInTheDocument();
+    });
+  });
+
+  describe('when active plugins list is present', () => {
+    beforeEach(() => {
+      renderComponent({
+        plugins: {
+          ...PLUGINS_INITIAL_STATE_MOCK,
+          drawer: {
+            ...PLUGINS_INITIAL_STATE_MOCK.drawer,
+            currentPluginHash: PLUGIN.hash,
+          },
+          activePlugins: {
+            data: {
+              [PLUGIN.hash]: PLUGIN,
+              [PLUGIN_2.hash]: PLUGIN_2,
+            },
+            pluginsId: [PLUGIN.hash, PLUGIN_2.hash],
+          },
+          list: PLUGINS_INITIAL_STATE_LIST_MOCK,
+        },
+      });
+    });
+
+    it.each([PLUGIN, PLUGIN_2])('should render plugins tabs', plugin => {
+      const element = screen.getByText(plugin.name);
+      expect(element).toBeInTheDocument();
+    });
+  });
+});
diff --git a/src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.tsx b/src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.tsx
new file mode 100644
index 00000000..45dd4e59
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsTabs/PluginsTabs.component.tsx
@@ -0,0 +1,29 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import { ZERO } from '@/constants/common';
+import { useAppSelector } from '@/redux/hooks/useAppSelector';
+import { allActivePluginsSelector } from '@/redux/plugins/plugins.selectors';
+import { PluginSingleTab } from './PluginSingleTab';
+
+export const PluginsTabs = (): JSX.Element => {
+  const allActivePlugins = useAppSelector(allActivePluginsSelector);
+  const isPluginsEmpty = allActivePlugins.length === ZERO;
+
+  const pluginsTabs = allActivePlugins.map(plugin => (
+    <PluginSingleTab plugin={plugin} key={plugin.hash} />
+  ));
+
+  const pluginsEmptyInfo = (
+    <div className="flex h-10 items-center px-4 text-[#979797]">
+      You don&apos;t have any opened plugin yet
+    </div>
+  );
+
+  return (
+    <div
+      className="flex h-10 w-full flex-row flex-nowrap justify-start border-b border-b-divide bg-white-pearl text-xs"
+      data-testid="drawer-plugins-tab"
+    >
+      {isPluginsEmpty ? pluginsEmptyInfo : pluginsTabs}
+    </div>
+  );
+};
diff --git a/src/components/Map/PluginsDrawer/PluginsTabs/index.ts b/src/components/Map/PluginsDrawer/PluginsTabs/index.ts
new file mode 100644
index 00000000..89b61283
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/PluginsTabs/index.ts
@@ -0,0 +1 @@
+export { PluginsTabs as PluginsList } from './PluginsTabs.component';
diff --git a/src/components/Map/PluginsDrawer/index.ts b/src/components/Map/PluginsDrawer/index.ts
new file mode 100644
index 00000000..8858fdbe
--- /dev/null
+++ b/src/components/Map/PluginsDrawer/index.ts
@@ -0,0 +1 @@
+export { PluginsDrawer } from './PluginsDrawer.component';
diff --git a/src/constants/plugins.ts b/src/constants/plugins.ts
new file mode 100644
index 00000000..8da8b37f
--- /dev/null
+++ b/src/constants/plugins.ts
@@ -0,0 +1,2 @@
+export const PLUGINS_CONTENT_ELEMENT_ATTR_NAME = 'data-hash';
+export const PLUGINS_CONTENT_ELEMENT_ID = 'plugins';
diff --git a/src/models/mocks/pluginsMock.ts b/src/models/mocks/pluginsMock.ts
index f5c91eaf..39963669 100644
--- a/src/models/mocks/pluginsMock.ts
+++ b/src/models/mocks/pluginsMock.ts
@@ -5,7 +5,7 @@ export const PLUGINS_MOCK: MinervaPlugin[] = [
     hash: '5e3fcb59588cc311ef9839feea6382eb',
     name: 'Disease-variant associations',
     version: '1.0.0',
-    isPublic: true,
+    isPublic: false,
     isDefault: false,
     urls: ['https://minerva-service.lcsb.uni.lu/plugins/disease-associations/plugin.js'],
   },
@@ -13,7 +13,7 @@ export const PLUGINS_MOCK: MinervaPlugin[] = [
     hash: '20df86476c311824bbfe73d1034af89e',
     name: 'GSEA',
     version: '0.9.2',
-    isPublic: true,
+    isPublic: false,
     isDefault: false,
     urls: ['https://minerva-service.lcsb.uni.lu/plugins/gsea/plugin.js'],
   },
diff --git a/src/redux/plugins/plugins.constants.ts b/src/redux/plugins/plugins.constants.ts
index c3365278..cdd616c5 100644
--- a/src/redux/plugins/plugins.constants.ts
+++ b/src/redux/plugins/plugins.constants.ts
@@ -10,4 +10,8 @@ export const PLUGINS_INITIAL_STATE: PluginsState = {
     data: {},
     pluginsId: [],
   },
+  drawer: {
+    isOpen: false,
+    currentPluginHash: undefined,
+  },
 };
diff --git a/src/redux/plugins/plugins.mock.ts b/src/redux/plugins/plugins.mock.ts
index 9b6b9c8f..11091195 100644
--- a/src/redux/plugins/plugins.mock.ts
+++ b/src/redux/plugins/plugins.mock.ts
@@ -1,5 +1,5 @@
 import { DEFAULT_ERROR } from '@/constants/errors';
-import { ActivePlugins, PluginsList, PluginsState } from './plugins.types';
+import { ActivePlugins, PluginsDrawer, PluginsList, PluginsState } from './plugins.types';
 
 export const PLUGINS_INITIAL_STATE_ACTIVE_PLUGINS_MOCK: ActivePlugins = {
   data: {},
@@ -12,7 +12,13 @@ export const PLUGINS_INITIAL_STATE_LIST_MOCK: PluginsList = {
   error: DEFAULT_ERROR,
 };
 
+export const PLUGINS_INITIAL_STATE_DRAWER_MOCK: PluginsDrawer = {
+  isOpen: false,
+  currentPluginHash: undefined,
+};
+
 export const PLUGINS_INITIAL_STATE_MOCK: PluginsState = {
   list: PLUGINS_INITIAL_STATE_LIST_MOCK,
   activePlugins: PLUGINS_INITIAL_STATE_ACTIVE_PLUGINS_MOCK,
+  drawer: PLUGINS_INITIAL_STATE_DRAWER_MOCK,
 };
diff --git a/src/redux/plugins/plugins.reducers.ts b/src/redux/plugins/plugins.reducers.ts
index f046459c..ac8ddae5 100644
--- a/src/redux/plugins/plugins.reducers.ts
+++ b/src/redux/plugins/plugins.reducers.ts
@@ -1,6 +1,10 @@
 import type { ActionReducerMapBuilder } from '@reduxjs/toolkit';
-import type { PluginsState, RemovePluginAction } from './plugins.types';
-import { registerPlugin, getAllPlugins } from './plugins.thunks';
+import { getAllPlugins, registerPlugin } from './plugins.thunks';
+import type {
+  PluginsState,
+  RemovePluginAction,
+  SetCurrentDrawerPluginHashAction,
+} from './plugins.types';
 
 export const removePluginReducer = (state: PluginsState, action: RemovePluginAction): void => {
   const { pluginId } = action.payload;
@@ -38,3 +42,18 @@ export const getAllPluginsReducer = (builder: ActionReducerMapBuilder<PluginsSta
     // TODO to discuss manage state of failure
   });
 };
+
+export const openPluginsDrawerReducer = (state: PluginsState): void => {
+  state.drawer.isOpen = true;
+};
+
+export const closePluginsDrawerReducer = (state: PluginsState): void => {
+  state.drawer.isOpen = false;
+};
+
+export const setCurrentDrawerPluginHashReducer = (
+  state: PluginsState,
+  action: SetCurrentDrawerPluginHashAction,
+): void => {
+  state.drawer.currentPluginHash = action.payload;
+};
diff --git a/src/redux/plugins/plugins.selectors.ts b/src/redux/plugins/plugins.selectors.ts
index 1bf37c63..3c829c64 100644
--- a/src/redux/plugins/plugins.selectors.ts
+++ b/src/redux/plugins/plugins.selectors.ts
@@ -1,5 +1,5 @@
-import { createSelector } from '@reduxjs/toolkit';
 import { MinervaPlugin } from '@/types/models';
+import { createSelector } from '@reduxjs/toolkit';
 import { rootSelector } from '../root/root.selectors';
 
 export const pluginsSelector = createSelector(rootSelector, state => state.plugins);
@@ -12,6 +12,10 @@ export const pluginsListDataSelector = createSelector(pluginsListSelector, plugi
   return pluginsList.data;
 });
 
+export const pluginsDrawerSelector = createSelector(pluginsSelector, plugins => {
+  return plugins.drawer;
+});
+
 export const publicPluginsListSelector = createSelector(
   pluginsListDataSelector,
   pluginsListData => {
@@ -65,3 +69,30 @@ export const isPluginLoadingSelector = createSelector(
   ({ data, pluginsId }, pluginId) =>
     pluginsId.includes(pluginId) && data[pluginId] && !Object.keys(data[pluginId]).length,
 );
+
+export const currentDrawerPluginHashSelector = createSelector(
+  pluginsDrawerSelector,
+  drawer => drawer.currentPluginHash,
+);
+
+export const selectedDrawerPluginSelector = createSelector(
+  allActivePluginsSelector,
+  currentDrawerPluginHashSelector,
+  (allActivePlugins, currentDrawerPluginHash) =>
+    allActivePlugins.find(plugin => plugin.hash === currentDrawerPluginHash),
+);
+
+export const isPluginSelectedSelector = createSelector(
+  [selectedDrawerPluginSelector, (_, pluginHash: string): string => pluginHash],
+  (selectedPlugin, pluginHash) => selectedPlugin?.hash === pluginHash,
+);
+
+export const publicPluginsListWithoutActiveSelector = createSelector(
+  publicPluginsListSelector,
+  allActivePluginsSelector,
+  (publicPlugins, allActivePlugins) => {
+    const activePluginsHashes = allActivePlugins.map(p => p.hash);
+
+    return (publicPlugins || []).filter(plugin => !activePluginsHashes.includes(plugin.hash));
+  },
+);
diff --git a/src/redux/plugins/plugins.slice.ts b/src/redux/plugins/plugins.slice.ts
index aeb40842..d5b99346 100644
--- a/src/redux/plugins/plugins.slice.ts
+++ b/src/redux/plugins/plugins.slice.ts
@@ -1,8 +1,11 @@
 import { createSlice } from '@reduxjs/toolkit';
 import {
+  closePluginsDrawerReducer,
+  getAllPluginsReducer,
+  openPluginsDrawerReducer,
   registerPluginReducer,
   removePluginReducer,
-  getAllPluginsReducer,
+  setCurrentDrawerPluginHashReducer,
 } from './plugins.reducers';
 
 import { PLUGINS_INITIAL_STATE } from './plugins.constants';
@@ -12,6 +15,9 @@ const pluginsSlice = createSlice({
   initialState: PLUGINS_INITIAL_STATE,
   reducers: {
     removePlugin: removePluginReducer,
+    openPluginsDrawer: openPluginsDrawerReducer,
+    closePluginsDrawer: closePluginsDrawerReducer,
+    setCurrentDrawerPluginHash: setCurrentDrawerPluginHashReducer,
   },
   extraReducers: builder => {
     registerPluginReducer(builder);
@@ -19,5 +25,6 @@ const pluginsSlice = createSlice({
   },
 });
 
-export const { removePlugin } = pluginsSlice.actions;
+export const { removePlugin, openPluginsDrawer, closePluginsDrawer, setCurrentDrawerPluginHash } =
+  pluginsSlice.actions;
 export default pluginsSlice.reducer;
diff --git a/src/redux/plugins/plugins.thunks.ts b/src/redux/plugins/plugins.thunks.ts
index 73c7341e..afe657f9 100644
--- a/src/redux/plugins/plugins.thunks.ts
+++ b/src/redux/plugins/plugins.thunks.ts
@@ -1,11 +1,10 @@
 /* eslint-disable no-magic-numbers */
-import axios from 'axios';
-import { createAsyncThunk } from '@reduxjs/toolkit';
-import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
 import { pluginSchema } from '@/models/pluginSchema';
-import type { MinervaPlugin } from '@/types/models';
 import { axiosInstance } from '@/services/api/utils/axiosInstance';
-import { z } from 'zod';
+import type { MinervaPlugin } from '@/types/models';
+import { validateDataUsingZodSchema } from '@/utils/validateDataUsingZodSchema';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+import axios from 'axios';
 import { apiPath } from '../apiPath';
 
 type RegisterPlugin = {
@@ -90,8 +89,10 @@ export const getAllPlugins = createAsyncThunk(
   async (): Promise<MinervaPlugin[]> => {
     const response = await axiosInstance.get<MinervaPlugin[]>(apiPath.getAllPlugins());
 
-    const isDataValid = validateDataUsingZodSchema(response.data, z.array(pluginSchema));
+    const isPluginDataValid = (pluginData: MinervaPlugin): boolean =>
+      validateDataUsingZodSchema(pluginData, pluginSchema);
+    const validPlugins = response.data.filter(isPluginDataValid);
 
-    return isDataValid ? response.data : [];
+    return validPlugins;
   },
 );
diff --git a/src/redux/plugins/plugins.types.ts b/src/redux/plugins/plugins.types.ts
index 2569f128..9a6b316a 100644
--- a/src/redux/plugins/plugins.types.ts
+++ b/src/redux/plugins/plugins.types.ts
@@ -14,7 +14,16 @@ export type ActivePlugins = {
   };
 };
 
+export type PluginsDrawer = {
+  isOpen: boolean;
+  currentPluginHash?: string;
+};
+
 export type PluginsState = {
   list: PluginsList;
   activePlugins: ActivePlugins;
+  drawer: PluginsDrawer;
 };
+
+export type SetCurrentDrawerPluginHashPayload = string;
+export type SetCurrentDrawerPluginHashAction = PayloadAction<SetCurrentDrawerPluginHashPayload>;
diff --git a/src/services/pluginsManager/pluginsManager.test.ts b/src/services/pluginsManager/pluginsManager.test.ts
index f9b55d27..f6ef08ce 100644
--- a/src/services/pluginsManager/pluginsManager.test.ts
+++ b/src/services/pluginsManager/pluginsManager.test.ts
@@ -1,11 +1,30 @@
 /* eslint-disable no-magic-numbers */
-import { store } from '@/redux/store';
+import { ZERO } from '@/constants/common';
+import { PLUGINS_CONTENT_ELEMENT_ATTR_NAME, PLUGINS_CONTENT_ELEMENT_ID } from '@/constants/plugins';
 import { configurationFixture } from '@/models/fixtures/configurationFixture';
-import { configurationMapper } from './pluginsManager.utils';
+import { store } from '@/redux/store';
 import { PluginsManager } from './pluginsManager';
+import { configurationMapper } from './pluginsManager.utils';
 
 jest.mock('../../redux/store');
 
+const createWrapperInDocument = (): HTMLDivElement => {
+  const wrapper = document.createElement('div');
+  wrapper.id = PLUGINS_CONTENT_ELEMENT_ID;
+
+  document.body.appendChild(wrapper);
+
+  return wrapper;
+};
+
+const createPluginElement = (hash: string): void => {
+  const element = document.createElement('div');
+  element.setAttribute(PLUGINS_CONTENT_ELEMENT_ATTR_NAME, hash);
+
+  const wrapper = document.querySelector(`#${PLUGINS_CONTENT_ELEMENT_ID}`);
+  wrapper?.append(element);
+};
+
 describe('PluginsManager', () => {
   const originalWindow = { ...global.window };
 
@@ -72,4 +91,39 @@ describe('PluginsManager', () => {
 
     expect(result.element).toBeDefined();
   });
+
+  it('createAndGetPluginContent creates a content in wrapper and returns element', () => {
+    const pluginName = 'TestPlugin';
+    const pluginVersion = '1.0.0';
+    const pluginUrl = 'https://example.com/test-plugin.js';
+    const hash = '128ce10ae1b46ec4bc6d7c07278b5c9e';
+    PluginsManager.hashedPlugins = {
+      [pluginUrl]: hash,
+    };
+
+    createWrapperInDocument();
+
+    const result = PluginsManager.registerPlugin({ pluginName, pluginVersion, pluginUrl });
+
+    expect(result.element).toBeDefined();
+    expect(result.element.getAttribute(PLUGINS_CONTENT_ELEMENT_ATTR_NAME)).toBe(hash);
+
+    expect(result.element.parentNode).toBeDefined();
+    expect(result.element.parentElement?.id).toBe(PLUGINS_CONTENT_ELEMENT_ID);
+  });
+
+  it('removePluginContent removes content in wrapper and returns element', () => {
+    const hash = '128ce10ae1b46ec4bc6d7c07278b5c9e';
+
+    const wrapper = createWrapperInDocument();
+    createPluginElement(hash);
+
+    PluginsManager.removePluginContent({ hash });
+
+    expect(
+      document.querySelector(`div[${PLUGINS_CONTENT_ELEMENT_ATTR_NAME}="${hash}"]`),
+    ).not.toBeInTheDocument();
+
+    expect(wrapper?.childElementCount).toEqual(ZERO);
+  });
 });
diff --git a/src/services/pluginsManager/pluginsManager.ts b/src/services/pluginsManager/pluginsManager.ts
index 25df5404..ea6ca510 100644
--- a/src/services/pluginsManager/pluginsManager.ts
+++ b/src/services/pluginsManager/pluginsManager.ts
@@ -1,8 +1,9 @@
-import md5 from 'crypto-js/md5';
-import { store } from '@/redux/store';
+import { PLUGINS_CONTENT_ELEMENT_ATTR_NAME, PLUGINS_CONTENT_ELEMENT_ID } from '@/constants/plugins';
 import { registerPlugin } from '@/redux/plugins/plugins.thunks';
-import { configurationMapper } from './pluginsManager.utils';
+import { store } from '@/redux/store';
+import md5 from 'crypto-js/md5';
 import type { PluginsManagerType } from './pluginsManager.types';
+import { configurationMapper } from './pluginsManager.utils';
 
 export const PluginsManager: PluginsManagerType = {
   hashedPlugins: {},
@@ -33,7 +34,6 @@ export const PluginsManager: PluginsManagerType = {
 
     return unsubscribe;
   },
-
   registerPlugin({ pluginName, pluginVersion, pluginUrl }) {
     const hash = PluginsManager.hashedPlugins[pluginUrl];
 
@@ -47,13 +47,26 @@ export const PluginsManager: PluginsManagerType = {
       }),
     );
 
-    // TODO: replace when plugins drawer is implemented
-    const element = document.createElement('div');
-    const wrapper = document.querySelector('#plugins');
-    wrapper?.append(element);
+    const element = PluginsManager.createAndGetPluginContent({ hash });
 
     return {
       element,
     };
   },
+  createAndGetPluginContent({ hash }) {
+    const element = document.createElement('div');
+    element.setAttribute(PLUGINS_CONTENT_ELEMENT_ATTR_NAME, hash);
+
+    const wrapper = document.querySelector(`#${PLUGINS_CONTENT_ELEMENT_ID}`);
+    wrapper?.append(element);
+
+    return element;
+  },
+  removePluginContent({ hash }) {
+    const elements = document.querySelectorAll(
+      `div[${PLUGINS_CONTENT_ELEMENT_ATTR_NAME}="${hash}"]`,
+    );
+
+    elements.forEach(element => element.remove());
+  },
 };
diff --git a/src/services/pluginsManager/pluginsManager.types.ts b/src/services/pluginsManager/pluginsManager.types.ts
index 00e247a7..59f4afac 100644
--- a/src/services/pluginsManager/pluginsManager.types.ts
+++ b/src/services/pluginsManager/pluginsManager.types.ts
@@ -1,3 +1,4 @@
+import { MinervaPlugin } from '@/types/models';
 import { Unsubscribe } from '@reduxjs/toolkit';
 import { configurationMapper } from './pluginsManager.utils';
 
@@ -18,4 +19,6 @@ export type PluginsManagerType = {
   registerPlugin({ pluginName, pluginVersion, pluginUrl }: RegisterPlugin): {
     element: HTMLDivElement;
   };
+  createAndGetPluginContent(plugin: Pick<MinervaPlugin, 'hash'>): HTMLDivElement;
+  removePluginContent(plugin: Pick<MinervaPlugin, 'hash'>): void;
 };
diff --git a/src/shared/Icon/Icon.component.tsx b/src/shared/Icon/Icon.component.tsx
index dd6d4dec..258f7a09 100644
--- a/src/shared/Icon/Icon.component.tsx
+++ b/src/shared/Icon/Icon.component.tsx
@@ -1,23 +1,24 @@
-import { ChevronRightIcon } from '@/shared/Icon/Icons/ChevronRightIcon';
-import { ChevronLeftIcon } from '@/shared/Icon/Icons/ChevronLeftIcon';
 import { AdminIcon } from '@/shared/Icon/Icons/AdminIcon';
 import { ArrowIcon } from '@/shared/Icon/Icons/ArrowIcon';
 import { ChevronDownIcon } from '@/shared/Icon/Icons/ChevronDownIcon';
+import { ChevronLeftIcon } from '@/shared/Icon/Icons/ChevronLeftIcon';
+import { ChevronRightIcon } from '@/shared/Icon/Icons/ChevronRightIcon';
 import { ChevronUpIcon } from '@/shared/Icon/Icons/ChevronUpIcon';
+import { CloseIcon } from '@/shared/Icon/Icons/CloseIcon';
 import { DotsIcon } from '@/shared/Icon/Icons/DotsIcon';
 import { ExportIcon } from '@/shared/Icon/Icons/ExportIcon';
 import { InfoIcon } from '@/shared/Icon/Icons/InfoIcon';
 import { LegendIcon } from '@/shared/Icon/Icons/LegendIcon';
 import { PageIcon } from '@/shared/Icon/Icons/PageIcon';
+import { Pin } from '@/shared/Icon/Icons/Pin';
 import { PluginIcon } from '@/shared/Icon/Icons/PluginIcon';
 import { PlusIcon } from '@/shared/Icon/Icons/PlusIcon';
-import { CloseIcon } from '@/shared/Icon/Icons/CloseIcon';
-import { Pin } from '@/shared/Icon/Icons/Pin';
 
-import type { IconTypes } from '@/types/iconTypes';
+import type { IconComponentType, IconTypes } from '@/types/iconTypes';
 import { LocationIcon } from './Icons/LocationIcon';
 import { MaginfierZoomInIcon } from './Icons/MagnifierZoomIn';
 import { MaginfierZoomOutIcon } from './Icons/MagnifierZoomOut';
+import { ReloadIcon } from './Icons/ReloadIcon';
 import { ThreeDotsIcon } from './Icons/ThreeDotsIcon';
 
 export interface IconProps {
@@ -25,7 +26,7 @@ export interface IconProps {
   name: IconTypes;
 }
 
-const icons = {
+const icons: Record<IconTypes, IconComponentType> = {
   'chevron-right': ChevronRightIcon,
   'chevron-left': ChevronLeftIcon,
   'chevron-up': ChevronUpIcon,
@@ -45,6 +46,7 @@ const icons = {
   'magnifier-zoom-in': MaginfierZoomInIcon,
   'magnifier-zoom-out': MaginfierZoomOutIcon,
   'three-dots': ThreeDotsIcon,
+  reload: ReloadIcon,
 } as const;
 
 export const Icon = ({ name, className = '', ...rest }: IconProps): JSX.Element => {
diff --git a/src/shared/Icon/Icons/ReloadIcon.tsx b/src/shared/Icon/Icons/ReloadIcon.tsx
new file mode 100644
index 00000000..71e20ae3
--- /dev/null
+++ b/src/shared/Icon/Icons/ReloadIcon.tsx
@@ -0,0 +1,19 @@
+interface ReloadIconProps {
+  className?: string;
+}
+
+export const ReloadIcon = ({ className }: ReloadIconProps): JSX.Element => (
+  <svg
+    width="14"
+    height="15"
+    viewBox="0 0 14 15"
+    fill="none"
+    xmlns="http://www.w3.org/2000/svg"
+    className={className}
+  >
+    <path
+      d="M2.64253 2.45404C3.85223 1.40581 5.39985 0.829805 7.00053 0.832038C10.6825 0.832038 13.6672 3.8167 13.6672 7.4987C13.6672 8.9227 13.2205 10.2427 12.4605 11.3254L10.3339 7.4987H12.3339C12.3339 6.45312 12.0267 5.43057 11.4503 4.55821C10.8739 3.68584 10.0538 3.00214 9.09198 2.59212C8.13015 2.1821 7.069 2.06384 6.0405 2.25205C5.01199 2.44026 4.0615 2.92664 3.3072 3.6507L2.64253 2.45404ZM11.3585 12.5434C10.1488 13.5916 8.6012 14.1676 7.00053 14.1654C3.31853 14.1654 0.333862 11.1807 0.333862 7.4987C0.333862 6.0747 0.780529 4.7547 1.54053 3.67204L3.6672 7.4987H1.6672C1.66711 8.54429 1.97436 9.56684 2.55075 10.4392C3.12714 11.3116 3.94724 11.9953 4.90908 12.4053C5.87091 12.8153 6.93205 12.9336 7.96056 12.7454C8.98906 12.5571 9.93956 12.0708 10.6939 11.3467L11.3585 12.5434Z"
+      fill="#4091F4"
+    />
+  </svg>
+);
diff --git a/src/types/iconTypes.ts b/src/types/iconTypes.ts
index 3083f716..1a7ec425 100644
--- a/src/types/iconTypes.ts
+++ b/src/types/iconTypes.ts
@@ -17,4 +17,7 @@ export type IconTypes =
   | 'magnifier-zoom-in'
   | 'magnifier-zoom-out'
   | 'pin'
-  | 'three-dots';
+  | 'three-dots'
+  | 'reload';
+
+export type IconComponentType = ({ className }: { className: string }) => JSX.Element;
-- 
GitLab