From 89b8cee87d6de58fb02c8dca4eb266ea4f3fbd0b Mon Sep 17 00:00:00 2001
From: Leonardo Christino <leomilho@gmail.com>
Date: Tue, 23 Apr 2024 16:50:19 +0200
Subject: [PATCH] fix: schema tooltips working and with copy

---
 apps/web/src/app/{app.tsx => App.tsx}         |  54 +----
 apps/web/src/app/app.spec.tsx                 |   2 +-
 apps/web/src/app/app.stories.tsx              |   2 +-
 .../DatabaseManagement/forms/settings.tsx     |   2 +-
 apps/web/src/main.tsx                         |   2 +-
 apps/web/tsconfig.json                        |   2 +-
 libs/shared/lib/components/index.ts           |   3 +-
 .../lib/components/{ => layout}/Dialog.tsx    |   0
 libs/shared/lib/components/layout/Panel.tsx   |  26 +++
 .../lib/components/{ => layout}/Resizable.tsx |   0
 libs/shared/lib/components/layout/index.ts    |   3 +
 .../lib/querybuilder/panel/querybuilder.tsx   |   2 +-
 .../panel/querysidepanel/queryMLDialog.tsx    |   2 +-
 .../querysidepanel/querySettingsDialog.tsx    |   2 +-
 .../schema/panel/{schema.tsx => Schema.tsx}   | 139 +++++++----
 .../{schemaDialog.tsx => SchemaDialog.tsx}    |   2 +-
 libs/shared/lib/schema/panel/index.ts         |   2 +-
 .../lib/schema/panel/schema.stories.tsx       |   2 +-
 libs/shared/lib/sidebar/index.tsx             |   6 +-
 libs/shared/lib/sidebar/search/SearchBar.tsx  | 216 ++++++++++++++++++
 libs/shared/lib/sidebar/search/searchbar.tsx  | 182 ---------------
 21 files changed, 360 insertions(+), 291 deletions(-)
 rename apps/web/src/app/{app.tsx => App.tsx} (58%)
 rename libs/shared/lib/components/{ => layout}/Dialog.tsx (100%)
 create mode 100644 libs/shared/lib/components/layout/Panel.tsx
 rename libs/shared/lib/components/{ => layout}/Resizable.tsx (100%)
 create mode 100644 libs/shared/lib/components/layout/index.ts
 rename libs/shared/lib/schema/panel/{schema.tsx => Schema.tsx} (53%)
 rename libs/shared/lib/schema/panel/{schemaDialog.tsx => SchemaDialog.tsx} (96%)
 create mode 100644 libs/shared/lib/sidebar/search/SearchBar.tsx
 delete mode 100644 libs/shared/lib/sidebar/search/searchbar.tsx

diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/App.tsx
similarity index 58%
rename from apps/web/src/app/app.tsx
rename to apps/web/src/app/App.tsx
index 15401c5fd..23d49ba3c 100644
--- a/apps/web/src/app/app.tsx
+++ b/apps/web/src/app/App.tsx
@@ -5,12 +5,13 @@ import {
   useML,
   useQuerybuilderGraph,
   useQuerybuilderSettings,
+  useSchemaGraph,
   useSessionCache,
 } from '@graphpolaris/shared/lib/data-access';
 import { resetGraphQueryResults, queryingBackend } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice';
 import { Query2BackendQuery, QueryMultiGraph } from '@graphpolaris/shared/lib/querybuilder';
 import { Navbar } from '../components/navbar/navbar';
-import { Resizable } from '@graphpolaris/shared/lib/components/Resizable';
+import { Resizable } from '@graphpolaris/shared/lib/components/layout';
 import { DashboardAlerts } from '@graphpolaris/shared/lib/data-access/authorization/dashboardAlerts';
 import { EventBus } from '@graphpolaris/shared/lib/data-access/api/eventBus';
 import { Onboarding } from '../components/onboarding/onboarding';
@@ -21,11 +22,8 @@ import { QueryBuilder } from '@graphpolaris/shared/lib/querybuilder';
 import { SideNavTab, Sidebar } from '@graphpolaris/shared/lib/sidebar';
 import { VisualizationManager } from '@graphpolaris/shared/lib/vis/manager';
 import { ConfigPanel } from '@graphpolaris/shared/lib/vis/components/config';
-import { Tooltip, TooltipTrigger, Button, TooltipContent, TooltipProvider } from '@graphpolaris/shared';
-import { ControlContainer } from '@graphpolaris/shared/lib/components/controls';
-import { Searchbar } from '@graphpolaris/shared/lib/sidebar/search/searchbar';
+import { SearchBar } from '@graphpolaris/shared/lib/sidebar/search/SearchBar';
 import { Schema } from '@graphpolaris/shared/lib/schema/panel';
-import { Fullscreen, FitScreen, Remove, Schema as SchemaIcon, Search as SearchIcon } from '@mui/icons-material';
 
 export type App = {
   load?: string;
@@ -39,6 +37,7 @@ export function App(props: App) {
   const dispatch = useAppDispatch();
   const queryBuilderSettings = useQuerybuilderSettings();
   const manager = VisualizationManager();
+  const schema = useSchemaGraph();
 
   const runQuery = () => {
     if (session?.currentSaveState && query) {
@@ -79,51 +78,12 @@ export function App(props: App) {
                 <Navbar />
               </aside>
               <main className="grow flex flex-row h-screen pt-12">
-                <Sidebar onTab={(tab) => setTab(tab)} />
+                <Sidebar onTab={(tab) => setTab(tab)} tab={tab} />
                 <Resizable divisorSize={3} horizontal={true} defaultProportion={0.33}>
                   {tab !== undefined ? (
                     <div className="flex flex-col border w-full h-full bg-light">
-                      <div className="sticky shrink-0 top-0 flex items-stretch justify-between h-7 bg-secondary-100 border-b border-secondary-200 max-w-full">
-                        <div className="flex items-center">
-                          <h1 className="text-xs font-semibold text-secondary-600 px-2 truncate">{tab}</h1>
-                        </div>
-                        <div className="shrink-0 sticky right-0 px-0.5 ml-auto items-center flex">
-                          <ControlContainer>
-                            <TooltipProvider delayDuration={100}>
-                              <Tooltip>
-                                <TooltipTrigger asChild>
-                                  <Button type="secondary" variant="ghost" size="xs" iconComponent={<Remove />} onClick={() => {}} />
-                                </TooltipTrigger>
-                                <TooltipContent side={'top'}>
-                                  <p>Hide</p>
-                                </TooltipContent>
-                              </Tooltip>
-                              {tab === 'Schema' && (
-                                <Tooltip>
-                                  <TooltipTrigger asChild>
-                                    <Button type="secondary" variant="ghost" size="xs" iconComponent={<FitScreen />} onClick={() => {}} />
-                                  </TooltipTrigger>
-                                  <TooltipContent side={'top'}>
-                                    <p>Fit to screen</p>
-                                  </TooltipContent>
-                                </Tooltip>
-                              )}
-                              {tab === 'Search' && (
-                                <Tooltip>
-                                  <TooltipTrigger asChild>
-                                    <Button type="secondary" variant="ghost" size="xs" iconComponent={<Fullscreen />} onClick={() => {}} />
-                                  </TooltipTrigger>
-                                  <TooltipContent side={'top'}>
-                                    <p>Mock icon</p>
-                                  </TooltipContent>
-                                </Tooltip>
-                              )}
-                            </TooltipProvider>
-                          </ControlContainer>
-                        </div>
-                      </div>
-                      {tab === 'Search' && <Searchbar />}
-                      {tab === 'Schema' && <Schema auth={authCheck} />}
+                      {tab === 'Search' && <SearchBar onRemove={() => setTab(undefined)} />}
+                      {tab === 'Schema' && <Schema auth={authCheck} onRemove={() => setTab(undefined)} />}
                     </div>
                   ) : null}
 
diff --git a/apps/web/src/app/app.spec.tsx b/apps/web/src/app/app.spec.tsx
index 5bb3582e5..45f220412 100644
--- a/apps/web/src/app/app.spec.tsx
+++ b/apps/web/src/app/app.spec.tsx
@@ -1,7 +1,7 @@
 import { render } from '@testing-library/react';
 import { assert, describe, expect, it, test } from 'vitest';
 
-import App from './app';
+import App from './App';
 
 describe('App', () => {
   it('should render successfully', () => {
diff --git a/apps/web/src/app/app.stories.tsx b/apps/web/src/app/app.stories.tsx
index 74396173e..13e418060 100644
--- a/apps/web/src/app/app.stories.tsx
+++ b/apps/web/src/app/app.stories.tsx
@@ -1,6 +1,6 @@
 import React from 'react';
 import { Meta } from '@storybook/react';
-import { App } from './app';
+import { App } from './App';
 import { Provider } from 'react-redux';
 import { store } from '@graphpolaris/shared/lib/data-access/store';
 import { Route, BrowserRouter as Router, Routes } from 'react-router-dom';
diff --git a/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx b/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx
index 233899f09..f9bb74087 100644
--- a/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx
+++ b/apps/web/src/components/navbar/DatabaseManagement/forms/settings.tsx
@@ -8,7 +8,7 @@ import {
   useAuthorizationCache,
 } from '@graphpolaris/shared/lib/data-access';
 import { ErrorOutline } from '@mui/icons-material';
-import { Dialog } from '@graphpolaris/shared/lib/components/Dialog';
+import { Dialog } from '@graphpolaris/shared/lib/components/layout';
 import { Button } from '@graphpolaris/shared/lib/components/buttons';
 import { useImmer } from 'use-immer';
 import { addSaveState, testedSaveState } from '@graphpolaris/shared/lib/data-access/store/sessionSlice';
diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx
index 8c0776767..11ca09fff 100644
--- a/apps/web/src/main.tsx
+++ b/apps/web/src/main.tsx
@@ -3,7 +3,7 @@ import React from 'react';
 import { Provider } from 'react-redux';
 
 import { store } from '@graphpolaris/shared/lib/data-access/store';
-import App from './app/app';
+import App from './app/App';
 import { createRoot } from 'react-dom/client';
 import './main.css';
 
diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json
index 5b58d4e9d..493b19469 100644
--- a/apps/web/tsconfig.json
+++ b/apps/web/tsconfig.json
@@ -45,7 +45,7 @@
     "postcss.config.js", // excludes PostCSS configuration file
     "tsconfig.tsbuildinfo" // excludes TypeScript build info file
   ],
-  "include": ["vite.config.ts", "src/**/*"],
+  "include": ["vite.config.ts", "src/**/*", "../../libs/shared/lib/components/layout/Panel.tsx"],
   "files": ["vite.config.ts"],
   "references": []
 }
diff --git a/libs/shared/lib/components/index.ts b/libs/shared/lib/components/index.ts
index 46862ade6..3a41c2845 100644
--- a/libs/shared/lib/components/index.ts
+++ b/libs/shared/lib/components/index.ts
@@ -10,9 +10,8 @@ export * from './info';
 export * from './inputs';
 export * from './pagination';
 export * from './tooltip';
-export * from './Dialog';
 export * from './Legend';
 export * from './LoadingSpinner';
 export * from './Popup';
-export * from './Resizable';
+export * from './layout';
 export * from './pills';
diff --git a/libs/shared/lib/components/Dialog.tsx b/libs/shared/lib/components/layout/Dialog.tsx
similarity index 100%
rename from libs/shared/lib/components/Dialog.tsx
rename to libs/shared/lib/components/layout/Dialog.tsx
diff --git a/libs/shared/lib/components/layout/Panel.tsx b/libs/shared/lib/components/layout/Panel.tsx
new file mode 100644
index 000000000..ad77898ff
--- /dev/null
+++ b/libs/shared/lib/components/layout/Panel.tsx
@@ -0,0 +1,26 @@
+import React, { useEffect, useState } from 'react';
+import { ControlContainer } from '../controls';
+
+export type Panel = {
+  title: string;
+  tooltips: React.ReactNode;
+  children: React.ReactNode;
+};
+
+export function Panel(props: Panel) {
+  return (
+    <div className="flex flex-col border w-full h-full bg-light">
+      <div className="sticky shrink-0 top-0 flex items-stretch justify-between h-7 bg-secondary-100 border-b border-secondary-200 max-w-full">
+        <div className="flex items-center">
+          <h1 className="text-xs font-semibold text-secondary-600 px-2 truncate">{props.title}</h1>
+        </div>
+        <div className="shrink-0 sticky right-0 px-0.5 ml-auto items-center flex">
+          <ControlContainer>{props.tooltips}</ControlContainer>
+        </div>
+      </div>
+      {props.children}
+    </div>
+  );
+}
+
+export default Panel;
diff --git a/libs/shared/lib/components/Resizable.tsx b/libs/shared/lib/components/layout/Resizable.tsx
similarity index 100%
rename from libs/shared/lib/components/Resizable.tsx
rename to libs/shared/lib/components/layout/Resizable.tsx
diff --git a/libs/shared/lib/components/layout/index.ts b/libs/shared/lib/components/layout/index.ts
new file mode 100644
index 000000000..93c40e523
--- /dev/null
+++ b/libs/shared/lib/components/layout/index.ts
@@ -0,0 +1,3 @@
+export * from './Dialog';
+export * from './Panel';
+export * from './Resizable';
diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
index e8224d3cc..3e118b5dc 100644
--- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx
+++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
@@ -24,7 +24,7 @@ import ReactFlow, {
   isNode,
   useReactFlow,
 } from 'reactflow';
-import { Dialog } from '../../components/Dialog';
+import { Dialog } from '../../components/layout/Dialog';
 import { Button } from '../../components/buttons';
 import { ControlContainer } from '../../components/controls';
 import { addError } from '../../data-access/store/configSlice';
diff --git a/libs/shared/lib/querybuilder/panel/querysidepanel/queryMLDialog.tsx b/libs/shared/lib/querybuilder/panel/querysidepanel/queryMLDialog.tsx
index 0c409aa29..ff724e1ca 100644
--- a/libs/shared/lib/querybuilder/panel/querysidepanel/queryMLDialog.tsx
+++ b/libs/shared/lib/querybuilder/panel/querysidepanel/queryMLDialog.tsx
@@ -1,5 +1,5 @@
 import React from 'react';
-import { DialogProps } from '@graphpolaris/shared/lib/components/Dialog';
+import { DialogProps } from '@graphpolaris/shared/lib/components/layout';
 import { useAppDispatch, useML } from '@graphpolaris/shared/lib/data-access';
 import {
   setCentralityEnabled,
diff --git a/libs/shared/lib/querybuilder/panel/querysidepanel/querySettingsDialog.tsx b/libs/shared/lib/querybuilder/panel/querysidepanel/querySettingsDialog.tsx
index b5103ac70..81e7567c1 100644
--- a/libs/shared/lib/querybuilder/panel/querysidepanel/querySettingsDialog.tsx
+++ b/libs/shared/lib/querybuilder/panel/querysidepanel/querySettingsDialog.tsx
@@ -1,5 +1,5 @@
 import { useEffect } from 'react';
-import { DialogProps } from '../../../components/Dialog';
+import { DialogProps } from '../../../components/layout/Dialog';
 import React from 'react';
 import { useAppDispatch, useQuerybuilderSettings } from '../../../data-access';
 import { QueryBuilderSettings, setQuerybuilderSettings } from '../../../data-access/store/querybuilderSlice';
diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/Schema.tsx
similarity index 53%
rename from libs/shared/lib/schema/panel/schema.tsx
rename to libs/shared/lib/schema/panel/Schema.tsx
index 04fe939b2..2e29acd26 100644
--- a/libs/shared/lib/schema/panel/schema.tsx
+++ b/libs/shared/lib/schema/panel/Schema.tsx
@@ -9,15 +9,17 @@ import { NodeEdge } from '../pills/edges/node-edge';
 import { SelfEdge } from '../pills/edges/self-edge';
 import { SchemaEntityPill } from '../pills/nodes/entity/SchemaEntityPill';
 import { SchemaRelationPill } from '../pills/nodes/relation/SchemaRelationPill';
-import { SchemaDialog } from './schemaDialog';
-import { KeyboardArrowDown, KeyboardArrowRight } from '@mui/icons-material';
+import { SchemaDialog } from './SchemaDialog';
+import { ContentCopy, FitScreen, Fullscreen, KeyboardArrowDown, KeyboardArrowRight, Remove } from '@mui/icons-material';
 import { AlgorithmToLayoutProvider, AllLayoutAlgorithms, LayoutFactory } from '../../graph-layout';
 import { ConnectionLine, ConnectionDragLine } from '../../querybuilder';
 import { schemaExpandRelation, schemaGraphology2Reactflow } from '../schema-utils';
+import { Panel, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components';
 
 interface Props {
   content?: string;
   auth?: boolean;
+  onRemove?: () => void;
 }
 
 const onInit = (reactFlowInstance: ReactFlowInstance) => {
@@ -108,50 +110,99 @@ export const Schema = (props: Props) => {
   }, [searchResults]);
 
   return (
-    <div className="schema-panel w-full h-full flex flex-col justify-between" ref={reactFlowRef}>
-      {nodes.length === 0 ? (
-        <p className="m-3 text-xl font-bold">No Elements</p>
-      ) : (
-        <ReactFlowProvider>
-          <ReactFlow
-            snapGrid={[10, 10]}
-            snapToGrid
-            onlyRenderVisibleElements={false}
-            nodesDraggable={true}
-            nodeTypes={nodeTypes}
-            edgeTypes={edgeTypes}
-            connectionLineComponent={ConnectionDragLine}
-            onNodesChange={onNodesChange}
-            onEdgesChange={onEdgesChange}
-            nodes={nodes}
-            edges={edges}
-            onInit={(reactFlowInstance) => {
-              reactFlowInstanceRef.current = reactFlowInstance;
-              onInit(reactFlowInstance);
-            }}
-            proOptions={{ hideAttribution: true }}
-          ></ReactFlow>
-        </ReactFlowProvider>
-      )}
-      <div>
-        <div
-          className="w-full py-0 px-2 bg-secondary-50 cursor-pointer border-y flex items-center gap-1"
-          onClick={() => setExpanded(!expanded)}
-        >
-          <Button
-            size="xs"
-            variant="ghost"
-            iconComponent={expanded ? <KeyboardArrowDown /> : <KeyboardArrowRight />}
+    <Panel
+      title="Schema"
+      tooltips={
+        <TooltipProvider delayDuration={10}>
+          <Tooltip>
+            <TooltipTrigger asChild>
+              <Button
+                type="secondary"
+                variant="ghost"
+                size="xs"
+                iconComponent={<Remove />}
+                onClick={() => {
+                  if (props.onRemove) props.onRemove();
+                }}
+              />
+            </TooltipTrigger>
+            <TooltipContent side={'top'}>
+              <p>Hide</p>
+            </TooltipContent>
+          </Tooltip>
+          <Tooltip>
+            <TooltipTrigger asChild>
+              <Button
+                type="secondary"
+                variant="ghost"
+                size="xs"
+                iconComponent={<ContentCopy />}
+                onClick={() => {
+                  // Copy the schema to the clipboard
+                  navigator.clipboard.writeText(JSON.stringify(schemaGraph, null, 2));
+                }}
+              />
+            </TooltipTrigger>
+            <TooltipContent side={'top'}>
+              <p>Copy Schema to Clipboard</p>
+            </TooltipContent>
+          </Tooltip>
+          <Tooltip>
+            <TooltipTrigger asChild>
+              <Button type="secondary" variant="ghost" size="xs" iconComponent={<FitScreen />} onClick={() => {}} />
+            </TooltipTrigger>
+            <TooltipContent side={'top'}>
+              <p>Fit to screen</p>
+            </TooltipContent>
+          </Tooltip>
+        </TooltipProvider>
+      }
+    >
+      <div className="schema-panel w-full h-full flex flex-col justify-between" ref={reactFlowRef}>
+        {nodes.length === 0 ? (
+          <p className="m-3 text-xl font-bold">No Elements</p>
+        ) : (
+          <ReactFlowProvider>
+            <ReactFlow
+              snapGrid={[10, 10]}
+              snapToGrid
+              onlyRenderVisibleElements={false}
+              nodesDraggable={true}
+              nodeTypes={nodeTypes}
+              edgeTypes={edgeTypes}
+              connectionLineComponent={ConnectionDragLine}
+              onNodesChange={onNodesChange}
+              onEdgesChange={onEdgesChange}
+              nodes={nodes}
+              edges={edges}
+              onInit={(reactFlowInstance) => {
+                reactFlowInstanceRef.current = reactFlowInstance;
+                onInit(reactFlowInstance);
+              }}
+              proOptions={{ hideAttribution: true }}
+            ></ReactFlow>
+          </ReactFlowProvider>
+        )}
+        <div>
+          <div
+            className="w-full py-0 px-2 bg-secondary-50 cursor-pointer border-y flex items-center gap-1"
             onClick={() => setExpanded(!expanded)}
-          />
-          <span className="text-xs font-semibold text-secondary-600 truncate">Schema settings</span>
-        </div>
-        {expanded && (
-          <div className="h-full w-full overflow-y-auto">
-            <SchemaDialog open={toggleSchemaSettings} onClose={() => setToggleSchemaSettings(false)} />
+          >
+            <Button
+              size="xs"
+              variant="ghost"
+              iconComponent={expanded ? <KeyboardArrowDown /> : <KeyboardArrowRight />}
+              onClick={() => setExpanded(!expanded)}
+            />
+            <span className="text-xs font-semibold text-secondary-600 truncate">Schema settings</span>
           </div>
-        )}
+          {expanded && (
+            <div className="h-full w-full overflow-y-auto">
+              <SchemaDialog open={toggleSchemaSettings} onClose={() => setToggleSchemaSettings(false)} />
+            </div>
+          )}
+        </div>
       </div>
-    </div>
+    </Panel>
   );
 };
diff --git a/libs/shared/lib/schema/panel/schemaDialog.tsx b/libs/shared/lib/schema/panel/SchemaDialog.tsx
similarity index 96%
rename from libs/shared/lib/schema/panel/schemaDialog.tsx
rename to libs/shared/lib/schema/panel/SchemaDialog.tsx
index 3d6771997..492e7be6e 100644
--- a/libs/shared/lib/schema/panel/schemaDialog.tsx
+++ b/libs/shared/lib/schema/panel/SchemaDialog.tsx
@@ -1,5 +1,5 @@
 import { useEffect, useState } from 'react';
-import { Dialog, DialogProps } from '../../components/Dialog';
+import { Dialog, DialogProps } from '../../components/layout/Dialog';
 import React from 'react';
 import { useAppDispatch, useSchemaSettings } from '../../data-access';
 import { SchemaConnectionTypes, SchemaSettings, schemaConnectionTypeArray, setSchemaSettings } from '../../data-access/store/schemaSlice';
diff --git a/libs/shared/lib/schema/panel/index.ts b/libs/shared/lib/schema/panel/index.ts
index e27a6e2f5..5adcc326e 100644
--- a/libs/shared/lib/schema/panel/index.ts
+++ b/libs/shared/lib/schema/panel/index.ts
@@ -1 +1 @@
-export * from './schema';
+export * from './Schema';
diff --git a/libs/shared/lib/schema/panel/schema.stories.tsx b/libs/shared/lib/schema/panel/schema.stories.tsx
index 051acd2f7..17679da8a 100644
--- a/libs/shared/lib/schema/panel/schema.stories.tsx
+++ b/libs/shared/lib/schema/panel/schema.stories.tsx
@@ -6,7 +6,7 @@ import { schemaSlice, setSchema } from '@graphpolaris/shared/lib/data-access/sto
 
 import { configureStore } from '@reduxjs/toolkit';
 import { Provider } from 'react-redux';
-import { Schema } from './schema';
+import { Schema } from './Schema';
 
 import {
   movieSchemaRaw,
diff --git a/libs/shared/lib/sidebar/index.tsx b/libs/shared/lib/sidebar/index.tsx
index 6b2a03c0c..aa1fb99bd 100644
--- a/libs/shared/lib/sidebar/index.tsx
+++ b/libs/shared/lib/sidebar/index.tsx
@@ -15,9 +15,7 @@ const tabs: Array<{
   { name: 'Schema', icon: <SchemaIcon /> },
 ];
 
-export function Sidebar({ onTab }: { onTab: (tab: SideNavTab) => void }) {
-  const [tab, setTab] = useState<SideNavTab>('Schema');
-
+export function Sidebar({ onTab, tab }: { onTab: (tab: SideNavTab) => void; tab: SideNavTab }) {
   return (
     <div className="side-bar w-fit h-full flex shrink">
       <TooltipProvider delayDuration={100}>
@@ -33,10 +31,8 @@ export function Sidebar({ onTab }: { onTab: (tab: SideNavTab) => void }) {
                   onClick={() => {
                     if (tab === t.name) {
                       onTab(undefined);
-                      setTab(undefined);
                     } else {
                       onTab(t.name);
-                      setTab(t.name);
                     }
                   }}
                   additionalClasses={tab === t.name ? 'bg-secondary-100' : ''}
diff --git a/libs/shared/lib/sidebar/search/SearchBar.tsx b/libs/shared/lib/sidebar/search/SearchBar.tsx
new file mode 100644
index 000000000..04e24282f
--- /dev/null
+++ b/libs/shared/lib/sidebar/search/SearchBar.tsx
@@ -0,0 +1,216 @@
+import React, { useRef, useState, useEffect } from 'react';
+import {
+  AppDispatch,
+  useAppDispatch,
+  useGraphQueryResult,
+  useQuerybuilderGraph,
+  useRecentSearches,
+  useSchemaGraph,
+  useSearchResult,
+} from '../../data-access';
+import { QueryMultiGraph } from '../../querybuilder';
+import {
+  CATEGORY_KEYS,
+  addRecentSearch,
+  addSearchResultData,
+  addSearchResultQueryBuilder,
+  addSearchResultSchema,
+} from '../../data-access/store/searchResultSlice';
+import { filterData } from './similarity';
+import { Button, Panel, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components';
+import { Remove, Fullscreen } from '@mui/icons-material';
+
+const SIMILARITY_THRESHOLD = 0.7;
+
+const CATEGORY_ACTIONS: {
+  [key in CATEGORY_KEYS]: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => void;
+} = {
+  data: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => {
+    dispatch(addSearchResultData(payload));
+  },
+  schema: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => {
+    dispatch(addSearchResultSchema(payload));
+  },
+  querybuilder: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => {
+    dispatch(addSearchResultQueryBuilder(payload));
+  },
+};
+
+const SEARCH_CATEGORIES: CATEGORY_KEYS[] = Object.keys(CATEGORY_ACTIONS) as CATEGORY_KEYS[];
+
+export function SearchBar(props: { onRemove?: () => void }) {
+  const inputRef = useRef<HTMLInputElement>(null);
+  const [search, setSearch] = useState<string>('');
+  const searchbarRef = useRef<HTMLDivElement>(null);
+  const dispatch = useAppDispatch();
+  const results = useSearchResult();
+  const recentSearches = useRecentSearches();
+  const schema = useSchemaGraph();
+  const graphData = useGraphQueryResult();
+  const querybuilderData = useQuerybuilderGraph();
+
+  const dataSources: {
+    [key: string]: { nodes: Record<string, any>[]; edges: Record<string, any>[] };
+  } = {
+    data: graphData,
+    schema: schema,
+    querybuilder: querybuilderData as QueryMultiGraph,
+  };
+
+  useEffect(() => {
+    const handleKeyPress = (event: KeyboardEvent) => {
+      if (event.key === 'Enter') {
+        if (search !== '') {
+          dispatch(addRecentSearch(search));
+        }
+      }
+    };
+    window.addEventListener('keydown', handleKeyPress);
+    return () => window.removeEventListener('keydown', handleKeyPress);
+  }, [search]);
+
+  useEffect(() => {
+    handleSearch();
+  }, [search]);
+
+  const handleSearch = () => {
+    let query = search.toLowerCase();
+    const categories = search.match(/@[^ ]+/g);
+
+    if (categories) {
+      categories.map((category) => {
+        query = query.replace(category, '').trim();
+        const cat = category.substring(1);
+
+        if (cat in CATEGORY_ACTIONS) {
+          const categoryAction = CATEGORY_ACTIONS[cat as CATEGORY_KEYS];
+          const data = dataSources[cat];
+
+          const payload = {
+            nodes: filterData(query, data.nodes, SIMILARITY_THRESHOLD),
+            edges: filterData(query, data.edges, SIMILARITY_THRESHOLD),
+          };
+          categoryAction(payload, dispatch);
+        }
+      });
+    } else {
+      for (const category of SEARCH_CATEGORIES) {
+        const categoryAction = CATEGORY_ACTIONS[category];
+        const data = dataSources[category];
+
+        const payload = {
+          nodes: filterData(query, data.nodes, SIMILARITY_THRESHOLD),
+          edges: filterData(query, data.edges, SIMILARITY_THRESHOLD),
+        };
+
+        categoryAction(payload, dispatch);
+      }
+    }
+  };
+
+  return (
+    <Panel
+      title="Search"
+      tooltips={
+        <TooltipProvider delayDuration={10}>
+          <Tooltip>
+            <TooltipTrigger asChild>
+              <Button
+                type="secondary"
+                variant="ghost"
+                size="xs"
+                iconComponent={<Remove />}
+                onClick={() => {
+                  if (props.onRemove) props.onRemove();
+                }}
+              />
+            </TooltipTrigger>
+            <TooltipContent side={'top'}>
+              <p>Hide</p>
+            </TooltipContent>
+          </Tooltip>
+          <Tooltip>
+            <TooltipTrigger asChild>
+              <Button type="secondary" variant="ghost" size="xs" iconComponent={<Fullscreen />} onClick={() => {}} />
+            </TooltipTrigger>
+            <TooltipContent side={'top'}>
+              <p>Mock icon</p>
+            </TooltipContent>
+          </Tooltip>
+        </TooltipProvider>
+      }
+    >
+      <div className="flex flex-col w-full p-2">
+        <div className="w-full">
+          <input
+            type="text"
+            ref={inputRef}
+            value={search}
+            onChange={(e) => setSearch(e.target.value)}
+            id="input-group-search"
+            className="block w-full p-2 ps-2 text-sm text-secondary-900 border border-secondary-300 rounded bg-secondary-50 focus:ring-blue-500 focus:border-blue-500 focus:ring-0"
+            placeholder="Search database"
+          ></input>
+        </div>
+        <div>
+          {recentSearches.length !== 0 && (
+            <div className="px-3 pb-3">
+              <p className="text-sm">Recent searches</p>
+              {recentSearches.slice(0, 3).map((term) => (
+                <p key={term} className="ml-1 text-sm text-secondary-500 cursor-pointer" onClick={() => setSearch(term)}>
+                  {term}
+                </p>
+              ))}
+            </div>
+          )}
+          {search !== '' && (
+            <div className="z-10 w-full overflow-y-auto scroll h-full px-2 pb-2">
+              {SEARCH_CATEGORIES.every((category) => results[category].nodes.length === 0 && results[category].edges.length === 0) ? (
+                <div className="ml-1 text-sm">
+                  <p className="text-secondary-500">Found no matches...</p>
+                </div>
+              ) : (
+                SEARCH_CATEGORIES.map((category, index) => {
+                  if (results[category].nodes.length > 0 || results[category].edges.length > 0) {
+                    return (
+                      <div key={index}>
+                        <div className="flex justify-between p-2 text-lg">
+                          <p className="font-bold text-sm">{category.charAt(0).toUpperCase() + category.slice(1)}</p>
+                          <p className="font-bold text-sm">{results[category].nodes.length + results[category].edges.length} results</p>
+                        </div>
+                        <div className="h-[1px] w-full bg-secondary-200"></div>
+                        {Object.values(Object.values(results[category]))
+                          .flat()
+                          .map((item, index) => (
+                            <div
+                              key={index}
+                              className="flex flex-col hover:bg-secondary-300 px-2 py-1 cursor-pointer rounded ml-2"
+                              title={JSON.stringify(item)}
+                              onClick={() => {
+                                CATEGORY_ACTIONS[category](
+                                  {
+                                    nodes: results[category].nodes.includes(item) ? [item] : [],
+                                    edges: results[category].edges.includes(item) ? [item] : [],
+                                  },
+                                  dispatch,
+                                );
+                              }}
+                            >
+                              <div className="font-bold text-sm">
+                                {item?.key?.slice(0, 18) || item?.id?.slice(0, 18) || Object.values(item)?.[0]?.slice(0, 18)}
+                              </div>
+                              <div className="font-light text-secondary-800 text-xs">{JSON.stringify(item).substring(0, 40)}...</div>
+                            </div>
+                          ))}
+                      </div>
+                    );
+                  } else return <></>;
+                })
+              )}
+            </div>
+          )}
+        </div>
+      </div>
+    </Panel>
+  );
+}
diff --git a/libs/shared/lib/sidebar/search/searchbar.tsx b/libs/shared/lib/sidebar/search/searchbar.tsx
deleted file mode 100644
index 837085911..000000000
--- a/libs/shared/lib/sidebar/search/searchbar.tsx
+++ /dev/null
@@ -1,182 +0,0 @@
-import React, { useRef, useState, useEffect } from 'react';
-import {
-  AppDispatch,
-  useAppDispatch,
-  useGraphQueryResult,
-  useQuerybuilderGraph,
-  useRecentSearches,
-  useSchemaGraph,
-  useSearchResult,
-} from '../../data-access';
-import { QueryMultiGraph } from '../../querybuilder';
-import {
-  CATEGORY_KEYS,
-  addRecentSearch,
-  addSearchResultData,
-  addSearchResultQueryBuilder,
-  addSearchResultSchema,
-} from '../../data-access/store/searchResultSlice';
-import { filterData } from './similarity';
-
-const SIMILARITY_THRESHOLD = 0.7;
-
-const CATEGORY_ACTIONS: {
-  [key in CATEGORY_KEYS]: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => void;
-} = {
-  data: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => {
-    dispatch(addSearchResultData(payload));
-  },
-  schema: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => {
-    dispatch(addSearchResultSchema(payload));
-  },
-  querybuilder: (payload: { nodes: Record<string, any>[]; edges: Record<string, any>[] }, dispatch: AppDispatch) => {
-    dispatch(addSearchResultQueryBuilder(payload));
-  },
-};
-
-const SEARCH_CATEGORIES: CATEGORY_KEYS[] = Object.keys(CATEGORY_ACTIONS) as CATEGORY_KEYS[];
-
-export function Searchbar() {
-  const inputRef = useRef<HTMLInputElement>(null);
-  const [search, setSearch] = useState<string>('');
-  const searchbarRef = useRef<HTMLDivElement>(null);
-  const dispatch = useAppDispatch();
-  const results = useSearchResult();
-  const recentSearches = useRecentSearches();
-  const schema = useSchemaGraph();
-  const graphData = useGraphQueryResult();
-  const querybuilderData = useQuerybuilderGraph();
-
-  const dataSources: {
-    [key: string]: { nodes: Record<string, any>[]; edges: Record<string, any>[] };
-  } = {
-    data: graphData,
-    schema: schema,
-    querybuilder: querybuilderData as QueryMultiGraph,
-  };
-
-  useEffect(() => {
-    const handleKeyPress = (event: KeyboardEvent) => {
-      if (event.key === 'Enter') {
-        if (search !== '') {
-          dispatch(addRecentSearch(search));
-        }
-      }
-    };
-    window.addEventListener('keydown', handleKeyPress);
-    return () => window.removeEventListener('keydown', handleKeyPress);
-  }, [search]);
-
-  useEffect(() => {
-    handleSearch();
-  }, [search]);
-
-  const handleSearch = () => {
-    let query = search.toLowerCase();
-    const categories = search.match(/@[^ ]+/g);
-
-    if (categories) {
-      categories.map((category) => {
-        query = query.replace(category, '').trim();
-        const cat = category.substring(1);
-
-        if (cat in CATEGORY_ACTIONS) {
-          const categoryAction = CATEGORY_ACTIONS[cat as CATEGORY_KEYS];
-          const data = dataSources[cat];
-
-          const payload = {
-            nodes: filterData(query, data.nodes, SIMILARITY_THRESHOLD),
-            edges: filterData(query, data.edges, SIMILARITY_THRESHOLD),
-          };
-          categoryAction(payload, dispatch);
-        }
-      });
-    } else {
-      for (const category of SEARCH_CATEGORIES) {
-        const categoryAction = CATEGORY_ACTIONS[category];
-        const data = dataSources[category];
-
-        const payload = {
-          nodes: filterData(query, data.nodes, SIMILARITY_THRESHOLD),
-          edges: filterData(query, data.edges, SIMILARITY_THRESHOLD),
-        };
-
-        categoryAction(payload, dispatch);
-      }
-    }
-  };
-
-  return (
-    <div className="flex flex-col w-full p-2">
-      <div className="w-full">
-        <input
-          type="text"
-          ref={inputRef}
-          value={search}
-          onChange={(e) => setSearch(e.target.value)}
-          id="input-group-search"
-          className="block w-full p-2 ps-2 text-sm text-secondary-900 border border-secondary-300 rounded bg-secondary-50 focus:ring-blue-500 focus:border-blue-500 focus:ring-0"
-          placeholder="Search database"
-        ></input>
-      </div>
-      <div>
-        {recentSearches.length !== 0 && (
-          <div className="px-3 pb-3">
-            <p className="text-sm">Recent searches</p>
-            {recentSearches.slice(0, 3).map((term) => (
-              <p key={term} className="ml-1 text-sm text-secondary-500 cursor-pointer" onClick={() => setSearch(term)}>
-                {term}
-              </p>
-            ))}
-          </div>
-        )}
-        {search !== '' && (
-          <div className="z-10 w-full overflow-y-auto scroll h-full px-2 pb-2">
-            {SEARCH_CATEGORIES.every((category) => results[category].nodes.length === 0 && results[category].edges.length === 0) ? (
-              <div className="ml-1 text-sm">
-                <p className="text-secondary-500">Found no matches...</p>
-              </div>
-            ) : (
-              SEARCH_CATEGORIES.map((category, index) => {
-                if (results[category].nodes.length > 0 || results[category].edges.length > 0) {
-                  return (
-                    <div key={index}>
-                      <div className="flex justify-between p-2 text-lg">
-                        <p className="font-bold text-sm">{category.charAt(0).toUpperCase() + category.slice(1)}</p>
-                        <p className="font-bold text-sm">{results[category].nodes.length + results[category].edges.length} results</p>
-                      </div>
-                      <div className="h-[1px] w-full bg-secondary-200"></div>
-                      {Object.values(Object.values(results[category]))
-                        .flat()
-                        .map((item, index) => (
-                          <div
-                            key={index}
-                            className="flex flex-col hover:bg-secondary-300 px-2 py-1 cursor-pointer rounded ml-2"
-                            title={JSON.stringify(item)}
-                            onClick={() => {
-                              CATEGORY_ACTIONS[category](
-                                {
-                                  nodes: results[category].nodes.includes(item) ? [item] : [],
-                                  edges: results[category].edges.includes(item) ? [item] : [],
-                                },
-                                dispatch,
-                              );
-                            }}
-                          >
-                            <div className="font-bold text-sm">
-                              {item?.key?.slice(0, 18) || item?.id?.slice(0, 18) || Object.values(item)?.[0]?.slice(0, 18)}
-                            </div>
-                            <div className="font-light text-secondary-800 text-xs">{JSON.stringify(item).substring(0, 40)}...</div>
-                          </div>
-                        ))}
-                    </div>
-                  );
-                } else return <></>;
-              })
-            )}
-          </div>
-        )}
-      </div>
-    </div>
-  );
-}
-- 
GitLab