From 67e0c8dfd74c3ac9a5344fd46ad50a4c975ffd4a Mon Sep 17 00:00:00 2001
From: "Vink, S.A. (Sjoerd)" <s.a.vink@uu.nl>
Date: Wed, 25 Oct 2023 09:26:06 +0000
Subject: [PATCH] feat(onboarding): basic product walkthrough

solves #DEV-230
---
 apps/web/package.json                         |   1 +
 apps/web/src/app/app.tsx                      |   2 +
 apps/web/src/app/panels/Visualization.tsx     |   2 +-
 apps/web/src/components/navbar/navbar.tsx     |   2 +-
 .../components/navbar/search/SearchBar.tsx    |   2 +-
 .../src/components/onboarding/onboarding.tsx  |  89 ++++++++++++
 .../components/onboarding/use-cases/fraud.tsx |  11 ++
 .../onboarding/use-cases/general.tsx          |  55 ++++++++
 .../components/onboarding/use-cases/index.tsx |   7 +
 .../lib/querybuilder/panel/querybuilder.tsx   |   4 +-
 libs/shared/lib/schema/panel/schema.tsx       |  11 +-
 libs/shared/package.json                      |   1 +
 pnpm-lock.yaml                                | 132 +++++++++++++++++-
 13 files changed, 307 insertions(+), 12 deletions(-)
 create mode 100644 apps/web/src/components/onboarding/onboarding.tsx
 create mode 100644 apps/web/src/components/onboarding/use-cases/fraud.tsx
 create mode 100644 apps/web/src/components/onboarding/use-cases/general.tsx
 create mode 100644 apps/web/src/components/onboarding/use-cases/index.tsx

diff --git a/apps/web/package.json b/apps/web/package.json
index 71a76d8b0..8ed2832f6 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -22,6 +22,7 @@
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-grid-layout": "^1.3.4",
+    "react-joyride": "^2.6.1",
     "react-redux": "^8.0.5",
     "react-router-dom": "^6.8.1",
     "reactflow": "11.4.0-next.1",
diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx
index 4f787f5b9..67b3237ba 100644
--- a/apps/web/src/app/app.tsx
+++ b/apps/web/src/app/app.tsx
@@ -37,6 +37,7 @@ import { LinkPredictionInstance, setMLResult, allMLTypes } from '@graphpolaris/s
 import { Resizable } from '@graphpolaris/shared/lib/components/Resizable';
 import { DashboardAlerts } from '@graphpolaris/shared/lib/data-access/authorization/dashboardAlerts';
 import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice';
+import Onboarding from '../components/onboarding/onboarding';
 
 export interface App {}
 
@@ -119,6 +120,7 @@ export function App(props: App) {
 
   return (
     <div className="h-screen w-screen overflow-clip">
+      <Onboarding />
       <DashboardAlerts />
       <div className={'h-screen w-screen ' + (!auth.authorized ? 'blur-sm pointer-events-none ' : '')}>
         <div className="flex flex-col h-screen max-h-screen relative">
diff --git a/apps/web/src/app/panels/Visualization.tsx b/apps/web/src/app/panels/Visualization.tsx
index c13d286da..b41d5d881 100644
--- a/apps/web/src/app/panels/Visualization.tsx
+++ b/apps/web/src/app/panels/Visualization.tsx
@@ -5,7 +5,7 @@ import { useVisualizationCache } from '@graphpolaris/shared/lib/data-access';
 export const VisualizationPanel = () => {
   const vis = useVisualizationCache()
   return (
-    <div className="h-full w-full overflow-y-auto">
+    <div className="vis-panel h-full w-full overflow-y-auto">
       <h1>Visualization Panel</h1>
       <p className='text-sm'>{vis.visual}</p>
       {/* <div>
diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx
index 76534f601..ce43ad98f 100644
--- a/apps/web/src/components/navbar/navbar.tsx
+++ b/apps/web/src/components/navbar/navbar.tsx
@@ -190,7 +190,7 @@ export const Navbar = (props: NavbarComponentProps) => {
         </div>
 
         <div className="w-fit">
-          <div className="">
+          <div className="menu-walkthrough">
             <button
               tabIndex={0}
               className="btn btn-circle btn-ghost hover:bg-gray-200"
diff --git a/apps/web/src/components/navbar/search/SearchBar.tsx b/apps/web/src/components/navbar/search/SearchBar.tsx
index a92202f37..969774e74 100644
--- a/apps/web/src/components/navbar/search/SearchBar.tsx
+++ b/apps/web/src/components/navbar/search/SearchBar.tsx
@@ -108,7 +108,7 @@ export function SearchBar({}) {
   }, []);
 
   return (
-    <div className={`relative form-control mr-4 ${inputActive || search !== '' ? 'w-96' : 'w-56'} transition-width duration-300`}>
+    <div className={`searchbar relative form-control mr-4 ${inputActive || search !== '' ? 'w-96' : 'w-56'} transition-width duration-300`}>
       <div className="relative rounded-md shadow-sm w-full">
         <input
           ref={inputRef}
diff --git a/apps/web/src/components/onboarding/onboarding.tsx b/apps/web/src/components/onboarding/onboarding.tsx
new file mode 100644
index 000000000..534379ac7
--- /dev/null
+++ b/apps/web/src/components/onboarding/onboarding.tsx
@@ -0,0 +1,89 @@
+import React, { useState, useEffect } from 'react';
+import Joyride, { ACTIONS, EVENTS, STATUS } from 'react-joyride';
+import { useLocation } from 'react-router-dom';
+import CloseIcon from '@mui/icons-material/Close';
+import { useCases } from './use-cases';
+import { useAuthorizationCache } from '@graphpolaris/shared/lib/data-access';
+
+interface OnboardingState {
+  run?: boolean;
+  stepIndex?: number;
+}
+
+export default function Onboarding({}) {
+  const location = useLocation();
+  const auth = useAuthorizationCache();
+  const [showWalkthrough, setShowWalkthrough] = useState<boolean>(true);
+  const [onboarding, setOnboarding] = useState<OnboardingState>({
+    run: false,
+    stepIndex: 0,
+  });
+
+  useEffect(() => {
+    // Check whether walkthrough cookie exists and if user is logged in
+    const isWalkthroughCompleted = document.cookie.includes('walkthroughCompleted=true');
+    if (isWalkthroughCompleted || auth.userID) {
+      setShowWalkthrough(false);
+    }
+  }, []);
+
+  const handleJoyrideCallback = (data: { action: any; index: any; status: any; type: any }) => {
+    const { action, index, status, type } = data;
+    if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) {
+      setOnboarding({ stepIndex: index + (action === ACTIONS.PREV ? -1 : 1) });
+    } else if ([STATUS.FINISHED, STATUS.SKIPPED].includes(status)) {
+      setOnboarding({ run: false });
+      addWalkthroughCookie();
+    }
+  };
+
+  const startWalkThrough = () => {
+    setOnboarding({ ...onboarding, run: true });
+    setShowWalkthrough(false);
+  };
+
+  const addWalkthroughCookie = () => {
+    setShowWalkthrough(false);
+    const date = new Date();
+    date.setTime(date.getTime() + 24 * 60 * 60 * 1000 * 365);
+    document.cookie = `walkthroughCompleted=true; expires=${date.toUTCString()};`;
+  };
+
+  const script = useCases[location.pathname as keyof typeof useCases] || useCases.general;
+
+  return (
+    <div>
+      {showWalkthrough && (
+        <div className="alert absolute bottom-5 left-5 w-fit cursor-pointer z-50 bg-entity-400">
+          <span className="btn btn-ghost" onClick={startWalkThrough}>
+            Start a Tour
+          </span>
+          <button className="btn btn-circle btn-sm btn-ghost" onClick={() => addWalkthroughCookie()}>
+            <CloseIcon fontSize="small" />
+          </button>
+        </div>
+      )}
+      <Joyride
+        run={onboarding.run}
+        continuous={true}
+        stepIndex={onboarding.stepIndex}
+        steps={script}
+        showProgress={true}
+        showSkipButton={true}
+        hideCloseButton={true}
+        callback={handleJoyrideCallback}
+        styles={{
+          options: {
+            primaryColor: '#FF7D00',
+          },
+          tooltip: {
+            borderRadius: 0,
+          },
+          buttonNext: {
+            borderRadius: 0,
+          },
+        }}
+      />
+    </div>
+  );
+}
diff --git a/apps/web/src/components/onboarding/use-cases/fraud.tsx b/apps/web/src/components/onboarding/use-cases/fraud.tsx
new file mode 100644
index 000000000..048c55455
--- /dev/null
+++ b/apps/web/src/components/onboarding/use-cases/fraud.tsx
@@ -0,0 +1,11 @@
+export const fraudScript = [
+  // TODO: make script for fraud detection
+
+  {
+    target: '.schema-panel',
+    title: 'Schema Panel',
+    content: 'The Schema Panel displays the structure of your database. You can drag pills from here to the query builder',
+    placement: 'right',
+    disableBeacon: true,
+  },
+];
diff --git a/apps/web/src/components/onboarding/use-cases/general.tsx b/apps/web/src/components/onboarding/use-cases/general.tsx
new file mode 100644
index 000000000..2f8f61fe6
--- /dev/null
+++ b/apps/web/src/components/onboarding/use-cases/general.tsx
@@ -0,0 +1,55 @@
+export const generalScript = [
+  {
+    target: '.schema-panel',
+    title: 'Schema Panel',
+    content:
+      'The schema panel displays the structure of your database. The orange pills are nodes in your database, and the blue pills are edges. You can drag pills from here to the query builder to construct queries.',
+    placement: 'right',
+    disableBeacon: true,
+  },
+  {
+    target: '.schema-settings',
+    title: 'Schema Settings',
+    content: 'Access schema settings to customize the style, structure and layout of the schema panel.',
+    placement: 'right',
+    disableBeacon: true,
+  },
+  {
+    target: '.query-panel',
+    title: 'Query Builder',
+    content:
+      'Use the query builder to create queries for your graph database. You can drag pills from the schema panel into here, and connect pills based on your needs.',
+    placement: 'top',
+    disableBeacon: true,
+  },
+  {
+    target: '.query-settings',
+    title: 'Query Builder Settings',
+    content:
+      'Adjust query builder settings to fine-tune your query-building experience. You can also incorporate machine-learning options into your query.',
+    placement: 'left',
+    disableBeacon: true,
+  },
+  {
+    target: '.vis-panel',
+    title: 'Visualization Panel',
+    content: "The visualization panel is where you'll see your data visualizations, with the data from the executed query.",
+    placement: 'left',
+    disableBeacon: true,
+  },
+  {
+    target: '.searchbar',
+    title: 'Searching',
+    content:
+      'Utilize the search bar to search through GraphPolaris efficiently. You can use keywords like @schema, @data or @querybuilder to refine search scope.',
+    placement: 'bottom',
+    disableBeacon: true,
+  },
+  {
+    target: '.menu-walkthrough',
+    title: 'Menu',
+    content: 'In the menu you can manage databases, switch between them, and perform other actions.',
+    placement: 'bottom',
+    disableBeacon: true,
+  },
+];
diff --git a/apps/web/src/components/onboarding/use-cases/index.tsx b/apps/web/src/components/onboarding/use-cases/index.tsx
new file mode 100644
index 000000000..1805d31db
--- /dev/null
+++ b/apps/web/src/components/onboarding/use-cases/index.tsx
@@ -0,0 +1,7 @@
+import { generalScript } from './general';
+import { fraudScript } from './fraud';
+
+export const useCases = {
+  general: generalScript,
+  fraud: fraudScript,
+};
diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
index 215be6b72..4063edd4f 100644
--- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx
+++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx
@@ -485,7 +485,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
       >
         <Background gap={10} size={0.7} />
 
-        <Controls showZoom={false} showInteractive={false} className={styles.controls}>
+        <Controls showZoom={false} showInteractive={false} className={`${styles.controls} query-settings`}>
           <ControlButton className={styles.buttons} title={'Remove all elements'} onClick={() => clearAllNodes()}>
             <DeleteIcon />
           </ControlButton>
@@ -559,7 +559,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => {
 
 export const QueryBuilder = (props: QueryBuilderProps) => {
   return (
-    <div className="flex w-full h-full">
+    <div className="query-panel flex w-full h-full">
       <ReactFlowProvider>
         <QueryBuilderInner {...props} />
       </ReactFlowProvider>
diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx
index d4c678d2c..91743e06a 100644
--- a/libs/shared/lib/schema/panel/schema.tsx
+++ b/libs/shared/lib/schema/panel/schema.tsx
@@ -153,9 +153,14 @@ export const Schema = (props: Props) => {
   }, [settingsIconRef, dialogRef, toggleSchemaSettings]);
 
   return (
-    <div className="w-full h-full">
+    <div className="schema-panel w-full h-full">
       <SchemaDialog open={toggleSchemaSettings} onClose={() => setToggleSchemaSettings(false)} ref={dialogRef} />
-      <h1 className="h-[1rem]">Schema</h1>
+      <div className="flex justify-between h-[1rem]">
+        <h1>Schema</h1>
+        <p className="text-slate-400" title={JSON.stringify(session)}>
+          Connected to: {session.currentDatabase}
+        </p>
+      </div>
       {nodes.length === 0 && <p>No Elements</p>}
       <ReactFlowProvider>
         <div className="h-[calc(100%-.8rem)] w-full">
@@ -174,7 +179,7 @@ export const Schema = (props: Props) => {
             onInit={onInit}
             proOptions={{ hideAttribution: true }}
           >
-            <Controls showInteractive={false} showZoom={false} showFitView={true} className={styles.controls}>
+            <Controls showInteractive={false} showZoom={false} showFitView={true} className={`${styles.controls} schema-settings`}>
               {/* <ControlButton
               className={styles.exportButton}
               title={'Export graph schema'}
diff --git a/libs/shared/package.json b/libs/shared/package.json
index f654e515f..bb16b0fab 100644
--- a/libs/shared/package.json
+++ b/libs/shared/package.json
@@ -56,6 +56,7 @@
     "react-cookie": "^4.1.1",
     "react-draggable": "^4.4.5",
     "react-grid-layout": "^1.3.4",
+    "react-joyride": "^2.6.1",
     "react-json-view": "^1.21.3",
     "react-palm": "^3.3.8",
     "react-router-dom": "^6.8.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3449e4b30..edb6c7e1f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -99,6 +99,9 @@ importers:
       react-grid-layout:
         specifier: ^1.3.4
         version: 1.3.4(react-dom@18.2.0)(react@18.2.0)
+      react-joyride:
+        specifier: ^2.6.1
+        version: 2.6.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
       react-redux:
         specifier: ^8.0.5
         version: 8.0.5(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1)
@@ -319,6 +322,9 @@ importers:
       react-grid-layout:
         specifier: ^1.3.4
         version: 1.3.4(react-dom@18.2.0)(react@18.2.0)
+      react-joyride:
+        specifier: ^2.6.1
+        version: 2.6.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
       react-json-view:
         specifier: ^1.21.3
         version: 1.21.3(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
@@ -3546,6 +3552,27 @@ packages:
     deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package
     dev: false
 
+  /@gilbarbara/deep-equal@0.1.2:
+    resolution: {integrity: sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==}
+    dev: false
+
+  /@gilbarbara/deep-equal@0.2.0:
+    resolution: {integrity: sha512-dkjEAjjsoPUthQHYENjmgd453IBWLNGqFPolcmbbyKrHrGWj3AayQz7CYGN45OljDOTaFSmyb0sWgDtzpaxWjw==}
+    dev: false
+
+  /@gilbarbara/helpers@0.8.7:
+    resolution: {integrity: sha512-DL3btZpWnS3ZMkGdQ9sVQgVj/WlabUFbRoP6sg2iOjEFImq+QDqFgEDZn4Uf8LF3thGuNgj9EtsWlNCbvJYTqg==}
+    dependencies:
+      '@gilbarbara/types': 0.2.2
+      is-lite: 0.9.3
+    dev: false
+
+  /@gilbarbara/types@0.2.2:
+    resolution: {integrity: sha512-QuQDBRRcm1Q8AbSac2W1YElurOhprj3Iko/o+P1fJxUWS4rOGKMVli98OXS7uo4z+cKAif6a+L9bcZFSyauQpQ==}
+    dependencies:
+      type-fest: 4.5.0
+    dev: false
+
   /@humanwhocodes/config-array@0.5.0:
     resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==}
     engines: {node: '>=10.10.0'}
@@ -11327,6 +11354,10 @@ packages:
       - react-dom
     dev: false
 
+  /deep-diff@1.0.2:
+    resolution: {integrity: sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==}
+    dev: false
+
   /deep-eql@4.1.3:
     resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
     engines: {node: '>=6'}
@@ -11379,7 +11410,6 @@ packages:
   /deepmerge@4.3.1:
     resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
     engines: {node: '>=0.10.0'}
-    dev: true
 
   /default-browser-id@3.0.0:
     resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==}
@@ -13962,6 +13992,14 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /is-lite@0.8.2:
+    resolution: {integrity: sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==}
+    dev: false
+
+  /is-lite@0.9.3:
+    resolution: {integrity: sha512-lbyynwsRRUMh1fHEinXkde/thdjj8OpW/okyGAVgmW4r/FkCEP966oSEg0B8ON5+mm73MJjFXB4ZViuaAldw4g==}
+    dev: false
+
   /is-map@2.0.2:
     resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==}
 
@@ -16158,6 +16196,11 @@ packages:
       splaytree: 3.1.2
     dev: false
 
+  /popper.js@1.16.1:
+    resolution: {integrity: sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==}
+    deprecated: You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1
+    dev: false
+
   /postcss-import@14.1.0(postcss@8.4.21):
     resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
     engines: {node: '>=10.0.0'}
@@ -16216,7 +16259,7 @@ packages:
     dependencies:
       lilconfig: 2.1.0
       postcss: 8.4.21
-      ts-node: 10.9.1(@types/node@18.13.0)(typescript@4.9.5)
+      ts-node: 10.9.1(@types/node@17.0.12)(typescript@4.9.5)
       yaml: 1.10.2
     dev: true
 
@@ -16874,6 +16917,23 @@ packages:
       react: 18.2.0
     dev: true
 
+  /react-floater@0.7.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-tt/15k/HpaShbtvWCwsQYLR+ebfUuYbl+oAUJ3DcEDkgYKeUcSkDey2PdAIERdVwzdFZANz47HbwoET2/Rduxg==}
+    peerDependencies:
+      react: 15 - 18
+      react-dom: 15 - 18
+    dependencies:
+      deepmerge: 4.3.1
+      exenv: 1.2.2
+      is-lite: 0.8.2
+      popper.js: 1.16.1
+      prop-types: 15.8.1
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      react-proptype-conditional-require: 1.0.4
+      tree-changes: 0.9.3
+    dev: false
+
   /react-grid-layout@1.3.4(react-dom@18.2.0)(react@18.2.0):
     resolution: {integrity: sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==}
     peerDependencies:
@@ -16889,6 +16949,16 @@ packages:
       react-resizable: 3.0.4(react-dom@18.2.0)(react@18.2.0)
     dev: false
 
+  /react-innertext@1.1.5(@types/react@18.0.28)(react@18.2.0):
+    resolution: {integrity: sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==}
+    peerDependencies:
+      '@types/react': '>=0.0.0 <=99'
+      react: '>=0.0.0 <=99'
+    dependencies:
+      '@types/react': 18.0.28
+      react: 18.2.0
+    dev: false
+
   /react-inspector@6.0.1(react@18.2.0):
     resolution: {integrity: sha512-cxKSeFTf7jpSSVddm66sKdolG90qURAX3g1roTeaN6x0YEbtWc8JpmFN9+yIqLNH2uEkYerWLtJZIXRIFuBKrg==}
     peerDependencies:
@@ -16930,6 +17000,29 @@ packages:
   /react-is@18.2.0:
     resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
 
+  /react-joyride@2.6.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-JgGxWcg3fhQx3DrSTfC7nBmexH1N1UTy8hKi+U5QAObo+ZqACi3/X62RWbauZDxloshCNX2c6Zpui14+6NYeow==}
+    peerDependencies:
+      react: 15 - 18
+      react-dom: 15 - 18
+    dependencies:
+      '@gilbarbara/deep-equal': 0.2.0
+      '@gilbarbara/helpers': 0.8.7
+      deep-diff: 1.0.2
+      deepmerge: 4.3.1
+      is-lite: 0.9.3
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
+      react-floater: 0.7.6(react-dom@18.2.0)(react@18.2.0)
+      react-innertext: 1.1.5(@types/react@18.0.28)(react@18.2.0)
+      react-is: 16.13.1
+      scroll: 3.0.1
+      scrollparent: 2.1.0
+      tree-changes: 0.10.0
+    transitivePeerDependencies:
+      - '@types/react'
+    dev: false
+
   /react-json-pretty@2.2.0(react-dom@18.2.0)(react@18.2.0):
     resolution: {integrity: sha512-3UMzlAXkJ4R8S4vmkRKtvJHTewG4/rn1Q18n0zqdu/ipZbUPLVZD+QwC7uVcD/IAY3s8iNVHlgR2dMzIUS0n1A==}
     peerDependencies:
@@ -17072,6 +17165,10 @@ packages:
       react-test-renderer: 18.2.0(react@18.2.0)
     dev: false
 
+  /react-proptype-conditional-require@1.0.4:
+    resolution: {integrity: sha512-nopsRn7KnGgazBe2c3H2+Kf+Csp6PGDRLiBkYEDMKY8o/EIgft/WnIm/OnAKTawZiLnJXHAqhpFBddvs6NiXlw==}
+    dev: false
+
   /react-reconciler@0.12.0(react@18.2.0):
     resolution: {integrity: sha512-BBaE+asD1HdzS35GLhvOEUGFwFKBNN/Jj9b+VlCt9JjF+jDnmIij4SbulNpqccYxPE/Eeup3/ciouo9YmhSgbg==}
     engines: {node: '>=0.10.0'}
@@ -18009,6 +18106,14 @@ packages:
       ajv: 6.12.6
       ajv-keywords: 3.5.2(ajv@6.12.6)
 
+  /scroll@3.0.1:
+    resolution: {integrity: sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==}
+    dev: false
+
+  /scrollparent@2.1.0:
+    resolution: {integrity: sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==}
+    dev: false
+
   /scss@0.2.4:
     resolution: {integrity: sha512-4u8V87F+Q/upVhUmhPnB4C1R11xojkRkWjExL2v0CX2EXTg18VrKd+9JWoeyCp2VEMdSpJsyAvVU+rVjogh51A==}
     engines: {node: '>= 0.2.0'}
@@ -18993,6 +19098,20 @@ packages:
       punycode: 2.3.0
     dev: true
 
+  /tree-changes@0.10.0:
+    resolution: {integrity: sha512-Hu1ElozbPrc8/zvDfazlnbOQxepXVpy0IRrNrZkUB1aDyyJ+yColKKzGmvO8KE5AH8xvW6z9aChFQfDJGlDdKA==}
+    dependencies:
+      '@gilbarbara/deep-equal': 0.1.2
+      is-lite: 0.9.3
+    dev: false
+
+  /tree-changes@0.9.3:
+    resolution: {integrity: sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==}
+    dependencies:
+      '@gilbarbara/deep-equal': 0.1.2
+      is-lite: 0.8.2
+    dev: false
+
   /trim-newlines@3.0.1:
     resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
     engines: {node: '>=8'}
@@ -19254,6 +19373,11 @@ packages:
     engines: {node: '>=12.20'}
     dev: true
 
+  /type-fest@4.5.0:
+    resolution: {integrity: sha512-diLQivFzddJl4ylL3jxSkEc39Tpw7o1QeEHIPxVwryDK2lpB7Nqhzhuo6v5/Ls08Z0yPSAhsyAWlv1/H0ciNmw==}
+    engines: {node: '>=16'}
+    dev: false
+
   /type-is@1.6.18:
     resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
     engines: {node: '>= 0.6'}
@@ -20243,8 +20367,8 @@ packages:
       tinybench: 2.4.0
       tinypool: 0.4.0
       tinyspy: 1.1.1
-      vite: 4.2.1(@types/node@17.0.12)(sass@1.64.2)
-      vite-node: 0.29.4(@types/node@17.0.12)(sass@1.59.3)
+      vite: 4.2.1(@types/node@17.0.12)(sass@1.59.3)
+      vite-node: 0.29.4(@types/node@17.0.12)(sass@1.64.2)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - less
-- 
GitLab