diff --git a/apps/web/src/components/navbar/DatabaseManagement/dbConnectionSelector.tsx b/apps/web/src/components/navbar/DatabaseManagement/dbConnectionSelector.tsx index 820d0d462c994d867f15d0e04355482f3f6afad0..0a68e06a10a9e1243440fef80c8fce7a5924fe68 100644 --- a/apps/web/src/components/navbar/DatabaseManagement/dbConnectionSelector.tsx +++ b/apps/web/src/components/navbar/DatabaseManagement/dbConnectionSelector.tsx @@ -9,6 +9,7 @@ import { DropdownButton, DropdownContainer, DropdownItemContainer } from '@graph import { clearQB } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; import { clearSchema } from '@graphpolaris/shared/lib/data-access/store/schemaSlice'; import { DatabaseStatus, SaveStateI, nilUUID, wsDeleteState } from '@graphpolaris/shared/lib/data-access/broker'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@graphpolaris/shared/lib/components/tooltip'; export default function DatabaseSelector({}) { const dispatch = useAppDispatch(); @@ -70,156 +71,181 @@ export default function DatabaseSelector({}) { return ( <div className="menu-walkthrough"> - {settingsMenuOpen !== undefined && ( - <SettingsForm - open={settingsMenuOpen} - saveState={settingsMenuOpen === 'update' ? selectedSaveState : null} - disableCancel={ - (session.saveStates && Object.keys(session.saveStates).length === 0) || - session.currentSaveState === '00000000-0000-0000-0000-000000000000' - } - onClose={() => { - setSettingsMenuOpen(undefined); - }} - /> - )} - <DropdownContainer ref={dbSelectionMenuRef} className="w-[20rem]"> - <DropdownButton - disabled={connecting || authCache.authorized === false || !!authCache.roomID} - title={ - <div className="flex items-center"> - {connecting && session.currentSaveState && session.currentSaveState in session.saveStates ? ( - <> - <LoadingSpinner /> - <p className="ml-2 truncate">Connecting to {session.saveStates[session.currentSaveState].name}</p> - </> - ) : session.currentSaveState && session.currentSaveState in session.saveStates && session.currentSaveState !== nilUUID ? ( - <> - <div - className={`h-2 w-2 rounded-full ${ - session.testedSaveState[session.currentSaveState] === DatabaseStatus.tested ? 'bg-success-500' : 'bg-danger-500' - }`} - /> - <p className="ml-2 truncate">Connected DB: {session.saveStates[session.currentSaveState].name}</p> - </> - ) : session.saveStates === undefined ? ( - <> - <LoadingSpinner /> - <p className="ml-2">Retrieving databases</p> - </> - ) : Object.keys(session.saveStates).length === 0 || session.currentSaveState === nilUUID ? ( - <> - <p className="ml-2">Add your first Database</p> - </> - ) : ( - <> - <div className="h-2 w-2 rounded-full bg-secondary-500" /> - <p className="ml-2">Select a database</p> - </> - )} - </div> - } - onClick={() => { - if (session.saveStates && Object.keys(session.saveStates).length === 0) setSettingsMenuOpen('add'); - else setDbSelectionMenuOpen(!dbSelectionMenuOpen); - }} - /> + <TooltipProvider delayDuration={1000}> + {settingsMenuOpen !== undefined && ( + <SettingsForm + open={settingsMenuOpen} + saveState={settingsMenuOpen === 'update' ? selectedSaveState : null} + disableCancel={ + (session.saveStates && Object.keys(session.saveStates).length === 0) || + session.currentSaveState === '00000000-0000-0000-0000-000000000000' + } + onClose={() => { + setSettingsMenuOpen(undefined); + }} + /> + )} + <DropdownContainer ref={dbSelectionMenuRef} className="w-[20rem]"> + <DropdownButton + disabled={connecting || authCache.authorized === false || !!authCache.roomID} + title={ + <div className="flex items-center"> + {connecting && session.currentSaveState && session.currentSaveState in session.saveStates ? ( + <> + <LoadingSpinner /> + <p className="ml-2 truncate">Connecting to {session.saveStates[session.currentSaveState].name}</p> + </> + ) : session.currentSaveState && session.currentSaveState in session.saveStates && session.currentSaveState !== nilUUID ? ( + <> + <div + className={`h-2 w-2 rounded-full ${ + session.testedSaveState[session.currentSaveState] === DatabaseStatus.tested ? 'bg-success-500' : 'bg-danger-500' + }`} + /> + <p className="ml-2 truncate">Connected DB: {session.saveStates[session.currentSaveState].name}</p> + </> + ) : session.saveStates === undefined ? ( + <> + <LoadingSpinner /> + <p className="ml-2">Retrieving databases</p> + </> + ) : Object.keys(session.saveStates).length === 0 || session.currentSaveState === nilUUID ? ( + <> + <p className="ml-2">Add your first Database</p> + </> + ) : ( + <> + <div className="h-2 w-2 rounded-full bg-secondary-500" /> + <p className="ml-2">Select a database</p> + </> + )} + </div> + } + onClick={() => { + if (session.saveStates && Object.keys(session.saveStates).length === 0) setSettingsMenuOpen('add'); + else setDbSelectionMenuOpen(!dbSelectionMenuOpen); + }} + /> - {dbSelectionMenuOpen && session.saveStates !== undefined && ( - <DropdownItemContainer align="top-10 w-full"> - <li - className="flex items-center p-2 hover:bg-secondary-50 cursor-pointer" - onClick={(e) => { - e.preventDefault(); - setDbSelectionMenuOpen(false); - setConnecting(false); - setSettingsMenuOpen('add'); - }} - title="Add new database" - > - {session.saveStates && Object.keys(session.saveStates).length === 0 ? ( - <> - <Add /> - <p className="ml-2">Add your first database</p> - </> - ) : ( - <> - <Add /> - <p className="ml-2">Add database</p> - </> - )} - </li> - {Object.values(session.saveStates) - .filter((save) => save.id !== nilUUID) - .map((save) => ( - <li - key={save.id} - className="flex justify-between items-center px-4 py-2 hover:bg-primary-100 gap-2 cursor-pointer" - onClick={(e) => { - if (save.id !== session.currentSaveState) { - e.preventDefault(); - setDbSelectionMenuOpen(false); - setConnecting(true); - dispatch(selectSaveState(save.id)); - dispatch(clearQB()); - dispatch(clearSchema()); - } else { - setDbSelectionMenuOpen(false); - } - }} - onMouseEnter={() => setHovered(save.id)} - onMouseLeave={() => setHovered(null)} - title={`Connect to ${save.name}`} - > - <div - className={`h-[8px] w-[8px] rounded-full shrink-0 ${ - session.testedSaveState[save.id] === DatabaseStatus.tested ? 'bg-success-500' : 'bg-danger-500' - }`} - /> - <div className="w-full shrink min-w-0 flex flex-col"> - <p className="truncate w-full shrink-0 min-w-0">{save.name}</p> - <p className="bg-light text-2xs text-secondary-500 truncate w-fit shrink-0 min-w-0 max-w-full h-full border border-secondary-200 rounded-sm p-0.5"> - {save.db.protocol} - {save.db.url} - </p> - </div> - {hovered === save.id && ( - <div className="flex items-center ml-2"> - <div - className="text-secondary-700 hover:text-secondary-400 transition-colors duration-300" - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - setSettingsMenuOpen('update'); - setSelectedSaveState(save); - }} - > - <Settings /> - </div> - <div - className="text-secondary-700 hover:text-secondary-400 transition-colors duration-300" - onClick={(e) => { - e.preventDefault(); - e.stopPropagation(); - setDbSelectionMenuOpen(false); - if (session.currentSaveState === save.id) { - dispatch(clearQB()); - dispatch(clearSchema()); - } - wsDeleteState(save.id); - dispatch(deleteSaveState(save.id)); - }} - title="Delete database connection" - > - <Delete /> - </div> + {dbSelectionMenuOpen && session.saveStates !== undefined && ( + <DropdownItemContainer align="top-10 w-full"> + <li + className="flex items-center p-2 hover:bg-secondary-50 cursor-pointer" + onClick={(e) => { + e.preventDefault(); + setDbSelectionMenuOpen(false); + setConnecting(false); + setSettingsMenuOpen('add'); + }} + title="Add new database" + > + {session.saveStates && Object.keys(session.saveStates).length === 0 ? ( + <> + <Add /> + <p className="ml-2">Add your first database</p> + </> + ) : ( + <> + <Add /> + <p className="ml-2">Add database</p> + </> + )} + </li> + {Object.values(session.saveStates) + .filter((save) => save.id !== nilUUID) + .map((save) => ( + <li + key={save.id} + className="flex justify-between items-center px-4 py-2 hover:bg-primary-100 gap-2 cursor-pointer" + onClick={(e) => { + if (save.id !== session.currentSaveState) { + e.preventDefault(); + setDbSelectionMenuOpen(false); + setConnecting(true); + dispatch(selectSaveState(save.id)); + dispatch(clearQB()); + dispatch(clearSchema()); + } else { + setDbSelectionMenuOpen(false); + } + }} + onMouseEnter={() => setHovered(save.id)} + onMouseLeave={() => setHovered(null)} + > + <Tooltip> + <TooltipTrigger> + <div + className={`h-[8px] w-[8px] rounded-full shrink-0 ${ + session.testedSaveState[save.id] === DatabaseStatus.tested ? 'bg-success-500' : 'bg-danger-500' + }`} + /> + </TooltipTrigger> + <TooltipContent side={'left'}> + <p> + {session.testedSaveState[save.id] === DatabaseStatus.tested + ? 'Database connection tested' + : 'Something went wrong when trying to connect'} + </p> + </TooltipContent> + </Tooltip> + <div className="w-full shrink min-w-0 flex flex-col"> + <p className="truncate w-full shrink-0 min-w-0">{save.name}</p> + <p className="bg-light text-2xs text-secondary-500 truncate w-fit shrink-0 min-w-0 max-w-full h-full border border-secondary-200 rounded-sm p-0.5"> + {save.db.protocol} + {save.db.url} + </p> </div> - )} - </li> - ))} - </DropdownItemContainer> - )} - </DropdownContainer> + {hovered === save.id && ( + <div className="flex items-center ml-2"> + <div + className="text-secondary-700 hover:text-secondary-400 transition-colors duration-300" + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + setSettingsMenuOpen('update'); + setSelectedSaveState(save); + }} + > + <Tooltip> + <TooltipTrigger> + <Settings /> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>Change the connection details</p> + </TooltipContent> + </Tooltip> + </div> + <div + className="text-secondary-700 hover:text-secondary-400 transition-colors duration-300" + onClick={(e) => { + e.preventDefault(); + e.stopPropagation(); + setDbSelectionMenuOpen(false); + if (session.currentSaveState === save.id) { + dispatch(clearQB()); + dispatch(clearSchema()); + } + wsDeleteState(save.id); + dispatch(deleteSaveState(save.id)); + }} + > + <Tooltip> + <TooltipTrigger> + <Delete /> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>Delete the database</p> + </TooltipContent> + </Tooltip> + </div> + </div> + )} + </li> + ))} + </DropdownItemContainer> + )} + </DropdownContainer> + </TooltipProvider> </div> ); } diff --git a/apps/web/src/components/navbar/DatabaseManagement/forms/databaseForm.tsx b/apps/web/src/components/navbar/DatabaseManagement/forms/databaseForm.tsx index 8a11c9ab9add17f74c82140589de367c488f40b7..4eeba3be4be2c36cf016879a3bd9191996086186 100644 --- a/apps/web/src/components/navbar/DatabaseManagement/forms/databaseForm.tsx +++ b/apps/web/src/components/navbar/DatabaseManagement/forms/databaseForm.tsx @@ -97,6 +97,7 @@ export const DatabaseForm = (props: { data: SaveStateI; onChange: (data: SaveSta required value={formData.db.protocol} options={databaseProtocolMapping} + info="Protocol via which the database connection will be established" onChange={(value: string | number) => { setFormData((draft) => { draft.db.protocol = value.toString(); @@ -113,6 +114,7 @@ export const DatabaseForm = (props: { data: SaveStateI; onChange: (data: SaveSta placeholder="neo4j" required errorText="This field is required" + info="Connection URL of your database" validate={(v) => { setHasError({ ...hasError, url: v.length === 0 }); return v.length > 0; @@ -131,6 +133,7 @@ export const DatabaseForm = (props: { data: SaveStateI; onChange: (data: SaveSta placeholder="neo4j" required errorText="Must be between 1 and 9999" + info="The port is endpoint of service for communication purposes" validate={(v) => { setHasError({ ...hasError, port: !(v <= 9999 && v > 0) }); return v <= 9999 && v > 0; @@ -151,6 +154,7 @@ export const DatabaseForm = (props: { data: SaveStateI; onChange: (data: SaveSta placeholder="username" required errorText="This field is required" + info="Username of your database instance" validate={(v) => { setHasError({ ...hasError, username: v.length === 0 }); return v.length > 0; @@ -170,6 +174,7 @@ export const DatabaseForm = (props: { data: SaveStateI; onChange: (data: SaveSta placeholder="password" required errorText="This field is required" + info="Password of your database instance" validate={(v) => { setHasError({ ...hasError, password: v.length === 0 }); return v.length > 0; diff --git a/libs/shared/lib/components/buttons/buttons.module.scss.d.ts b/libs/shared/lib/components/buttons/buttons.module.scss.d.ts index 3165183bcc43cd371b85d32e546d2904b856a67e..090a2928f1558d1bcecbdbfd73ac9089eea3458f 100644 --- a/libs/shared/lib/components/buttons/buttons.module.scss.d.ts +++ b/libs/shared/lib/components/buttons/buttons.module.scss.d.ts @@ -1,7 +1,6 @@ declare const classNames: { readonly btn: 'btn'; readonly 'btn-lg': 'btn-lg'; - readonly '5': '5'; readonly 'btn-icon-only': 'btn-icon-only'; readonly 'btn-md': 'btn-md'; readonly 'btn-sm': 'btn-sm'; diff --git a/libs/shared/lib/components/color-mode/index.tsx b/libs/shared/lib/components/color-mode/index.tsx index 18ffc7c8f3c52efa8da33c980ec038a7c46b8d97..4376dd0aa1feee1ff207cf8e2a6e54bf38f74d13 100644 --- a/libs/shared/lib/components/color-mode/index.tsx +++ b/libs/shared/lib/components/color-mode/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; import { Button } from '../buttons'; // Adjust the import path according to your project structure -import { DarkMode, LightMode } from '@mui/icons-material'; -import Tooltip from '@graphpolaris/shared/lib/components/tooltip'; +import { Apps as AppsIcon, DarkMode, LightMode, Settings as SettingsIcon } from '@mui/icons-material'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip'; const ColorMode = () => { // Initialize theme state without setting a default yet @@ -42,9 +42,16 @@ const ColorMode = () => { const iconComponent = theme === 'dark-mode' ? <DarkMode /> : <LightMode />; return ( - <Tooltip tooltip={`Switch to ${theme === 'dark-mode' ? 'light' : 'dark'}-mode`}> - <Button variant="ghost" iconComponent={iconComponent} onClick={toggleTheme} /> - </Tooltip> + <TooltipProvider delayDuration={0}> + <Tooltip> + <TooltipTrigger> + <Button variant="ghost" iconComponent={iconComponent} onClick={toggleTheme} /> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>{`Switch to ${theme === 'dark-mode' ? 'light' : 'dark'}-mode`}</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> ); }; diff --git a/libs/shared/lib/components/info/index.tsx b/libs/shared/lib/components/info/index.tsx index a9a5f485e0774f40d21215699f9ee6ffeea18571..d99e13ecb3156c65507d92bebf4cee7244ad0300 100644 --- a/libs/shared/lib/components/info/index.tsx +++ b/libs/shared/lib/components/info/index.tsx @@ -1,16 +1,24 @@ import React from 'react'; import Icon from '../icon'; -import Tooltip from '../tooltip'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../tooltip'; import { InfoOutlined } from '@mui/icons-material'; type Props = { tooltip: string; + side?: 'top' | 'bottom' | 'left' | 'right'; }; -export default function Info({ tooltip }: Props) { +export default function Info({ tooltip, side = 'left' }: Props) { return ( - <Tooltip position="top" tooltip={tooltip}> - <Icon component={<InfoOutlined />} size={14} /> - </Tooltip> + <TooltipProvider delayDuration={0}> + <Tooltip> + <TooltipTrigger> + <Icon component={<InfoOutlined />} size={14} /> + </TooltipTrigger> + <TooltipContent side={side}> + <p>{tooltip}</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> ); } diff --git a/libs/shared/lib/components/inputs/index.tsx b/libs/shared/lib/components/inputs/index.tsx index b06e1cdbb46672fdea7a29cdecca7fb5ed71cf95..e52a53dd7861867fda9c6bd4d0b10c149566978b 100644 --- a/libs/shared/lib/components/inputs/index.tsx +++ b/libs/shared/lib/components/inputs/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import styles from './inputs.module.scss'; import { DropdownButton, DropdownContainer, DropdownItem, DropdownItemContainer } from '../dropdowns'; +import Info from '../info'; type SliderProps = { label: string; @@ -25,6 +26,7 @@ type TextProps = { visible?: boolean; disabled?: boolean; tooltip?: string; + info?: string; validate?: (value: any) => boolean; onChange?: (value: string) => void; }; @@ -64,6 +66,7 @@ type DropdownProps = { tooltip?: string; onChange?: (value: string | number) => void; required?: boolean; + info?: string; disabled?: boolean; }; @@ -129,6 +132,7 @@ export const TextInput = ({ disabled = false, onChange, tooltip, + info, }: TextProps) => { const [isValid, setIsValid] = React.useState<boolean>(true); @@ -139,6 +143,7 @@ export const TextInput = ({ {label} </span> {required && isValid ? null : <span className="label-text-alt text-error">{errorText}</span>} + {info && <Info tooltip={info} side={'left'} />} </label> <input type={visible ? 'text' : 'password'} @@ -237,7 +242,7 @@ export const BooleanInput = ({ label, value, onChange, tooltip }: BooleanProps) ); }; -export const DropDownInput = ({ label, value, options, onChange, required = false, tooltip, disabled = false }: DropdownProps) => { +export const DropDownInput = ({ label, value, options, onChange, required = false, tooltip, disabled = false, info }: DropdownProps) => { const dropdownRef = React.useRef<HTMLDivElement>(null); const [isDropdownOpen, setIsDropdownOpen] = React.useState<boolean>(false); @@ -262,6 +267,7 @@ export const DropDownInput = ({ label, value, options, onChange, required = fals > {label} </span> + {info && <Info tooltip={info} />} </label> )} <DropdownContainer className="w-full" ref={dropdownRef}> diff --git a/libs/shared/lib/components/tooltip/index.tsx b/libs/shared/lib/components/tooltip/index.tsx index 51b3591fc7b9c7b9243a19a06279721a749bcac9..79e5bfe8837fc3746903db753518aa3c339ff5aa 100644 --- a/libs/shared/lib/components/tooltip/index.tsx +++ b/libs/shared/lib/components/tooltip/index.tsx @@ -1,4 +1,7 @@ +// https://www.radix-ui.com/primitives/docs/components/tooltip + import React, { ReactNode } from 'react'; +import * as TooltipPrimitive from '@radix-ui/react-tooltip'; export interface BarTooltipProps { x: number; @@ -14,19 +17,59 @@ export const BarplotTooltip: React.FC<BarTooltipProps> = ({ x, y, content }) => ); }; +const TooltipProvider = TooltipPrimitive.Provider; +const TooltipTrigger = TooltipPrimitive.Trigger; + export type TooltipProps = { + children: ReactNode; + disabled?: boolean; +}; + +const Tooltip: React.FC<TooltipProps> = ({ disabled, children }) => { + if (disabled) { + return null; + } + + return <TooltipPrimitive.Root>{children}</TooltipPrimitive.Root>; +}; + +const TooltipContent = React.forwardRef< + React.ElementRef<typeof TooltipPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> & TooltipProps +>(({ className, sideOffset = 4, disabled = false, ...props }, ref) => { + if (disabled) { + return null; + } + + return ( + <TooltipPrimitive.Content + ref={ref} + sideOffset={sideOffset} + className={ + 'z-50 overflow-hidden rounded bg-light px-2 py-1 shadow text-xs border border-secondary-200 text-dark animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2' + } + {...props} + /> + ); +}); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; + +// Old Tooltip + +export type TooltipProps2 = { position?: 'top' | 'bottom' | 'left' | 'right'; children: ReactNode; tooltip: string; disabled?: boolean; }; - -export default function Tooltip({ position = 'bottom', children, tooltip, disabled = false }: TooltipProps) { +export default function Tooltip2({ position = 'bottom', children, tooltip, disabled = false }: TooltipProps2) { return ( - <div className="group relative cursor-pointer z-50"> + <div className="group relative cursor-pointer"> <div className="">{children}</div> {!disabled && ( - <> + <div className="z-50"> <span className={`absolute hidden group-hover:inline-block bg-neutral-900 text-white text-xs p-2 whitespace-nowrap rounded ${ position === 'top' ? 'left-1/2 -translate-x-1/2 bottom-[calc(100%+5px)]' : '' @@ -55,7 +98,7 @@ export default function Tooltip({ position = 'bottom', children, tooltip, disabl : '' } `} ></span> - </> + </div> )} </div> ); diff --git a/libs/shared/lib/components/tooltip/tooltip.stories.tsx b/libs/shared/lib/components/tooltip/tooltip.stories.tsx index 1004f713a26c44dff67e895546454a5741c9ee20..955eb37c764949e70ca75cbd6fad1b992715e915 100644 --- a/libs/shared/lib/components/tooltip/tooltip.stories.tsx +++ b/libs/shared/lib/components/tooltip/tooltip.stories.tsx @@ -1,68 +1,46 @@ import React from 'react'; import { Meta } from '@storybook/react'; -import Tooltip, { TooltipProps } from '.'; +import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './index'; +import { delay } from 'lodash-es'; export default { title: 'Components/Tooltip', component: Tooltip, + argTypes: { + delay: { + control: 'number', + name: 'delay (in ms)', + defaultValue: 0, + }, + side: { + control: 'select', + options: ['top', 'bottom', 'left', 'right'], + }, + }, } as Meta<typeof Tooltip>; -export const TooltipBottom = (args: TooltipProps) => { +const TooltipStory = (args: any) => { return ( - <div className="flex m-5"> - <Tooltip {...args}> - <h1>Hover over me</h1> - </Tooltip> - </div> + <TooltipProvider delayDuration={args.delay}> + <div className="flex justify-center items-center py-16"> + <Tooltip {...args}> + <TooltipTrigger> + <button className={'mx-auto'}>Hover over me</button> + </TooltipTrigger> + <TooltipContent side={args.side}> + <p>This is a tooltip</p> + </TooltipContent> + </Tooltip> + </div> + </TooltipProvider> ); }; -TooltipBottom.args = { - position: 'bottom', - tooltip: 'This is a tooltip', +TooltipStory.args = { + side: 'bottom', }; -export const TooltipTop = (args: TooltipProps) => { - return ( - <div className="flex m-5"> - <Tooltip {...args}> - <h1>Hover over me</h1> - </Tooltip> - </div> - ); -}; - -TooltipTop.args = { - position: 'top', - tooltip: 'This is a tooltip', -}; - -export const TooltipLeft = (args: TooltipProps) => { - return ( - <div className="flex m-5"> - <Tooltip {...args}> - <h1>Hover over me</h1> - </Tooltip> - </div> - ); -}; - -TooltipLeft.args = { - position: 'left', - tooltip: 'This is a tooltip', -}; - -export const TooltipRight = (args: TooltipProps) => { - return ( - <div className="flex m-5"> - <Tooltip {...args}> - <h1>Hover over me</h1> - </Tooltip> - </div> - ); -}; - -TooltipRight.args = { - position: 'right', - tooltip: 'This is a tooltip', +export const TooltipPosition = TooltipStory.bind({}); +TooltipPosition.args = { + side: 'bottom', }; diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx index 019850fd5c6b96b9fbec7ef06043d682b351853b..5a2d623a32a89a8ba036934ba331e006a6864fe7 100644 --- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx +++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx @@ -41,7 +41,7 @@ import { QueryMLDialog } from './querysidepanel/queryMLDialog'; import { QuerySettingsDialog } from './querysidepanel/querySettingsDialog'; import { ConnectingNodeDataI } from './utils/connectorDrop'; import { CameraAlt, Cached, Difference, ImportExport, Lightbulb, Settings, Fullscreen, Delete } from '@mui/icons-material'; -import Tooltip from '../../components/tooltip'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip'; export type QueryBuilderProps = { onRunQuery?: () => void; @@ -441,87 +441,129 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { <div className="relative flex items-center justify-between z-[2] py-0 px-2 bg-secondary-100 border-b border-secondary-200"> <h1 className="text-xs font-semibold text-secondary-800">Query builder</h1> <ControlContainer> - <Tooltip tooltip="Fit to screen"> - <Button type="secondary" variant="ghost" size="xs" iconComponent={<Fullscreen />} onClick={fitView} /> - </Tooltip> - <Tooltip tooltip="Clear query panel"> - <Button type="secondary" variant="ghost" size="xs" iconComponent={<Delete />} onClick={() => clearAllNodes()} /> - </Tooltip> - <Tooltip tooltip="Capture screen"> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<CameraAlt />} - onClick={(event) => { - event.stopPropagation(); - }} - /> - </Tooltip> - <Tooltip tooltip="Layouts"> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<ImportExport />} - onClick={(event) => { - event.stopPropagation(); - applyLayout(); - }} - /> - </Tooltip> - <Tooltip tooltip="Query builder settings" disabled={toggleSettings === 'settings'}> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Settings />} - additionalClasses="query-settings" - onClick={(event) => { - event.stopPropagation(); - if (toggleSettings === 'settings') setToggleSettings(undefined); - else setToggleSettings('settings'); - }} - /> - </Tooltip> - <Tooltip tooltip="Rerun query"> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Cached />} - onClick={(event) => { - event.stopPropagation(); - if (props.onRunQuery) props.onRunQuery(); - }} - /> - </Tooltip> - <Tooltip tooltip="Logic settings" disabled={toggleSettings === 'logic'}> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Difference />} - onClick={(event) => { - event.stopPropagation(); - if (toggleSettings === 'logic') setToggleSettings(undefined); - else setToggleSettings('logic'); - }} - /> - </Tooltip> - <Tooltip tooltip="Machine learning" disabled={toggleSettings === 'ml'}> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Lightbulb />} - onClick={(event) => { - event.stopPropagation(); - if (toggleSettings === 'ml') setToggleSettings(undefined); - else setToggleSettings('ml'); - }} - /> - </Tooltip> + <TooltipProvider delayDuration={0}> + <Tooltip> + <TooltipTrigger> + <Button type="secondary" variant="ghost" size="xs" iconComponent={<Fullscreen />} onClick={fitView} /> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>Fit to screen</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button type="secondary" variant="ghost" size="xs" iconComponent={<Delete />} onClick={() => clearAllNodes()} /> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>Clear query panel</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<CameraAlt />} + onClick={(event) => { + event.stopPropagation(); + }} + /> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>Capture screen</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<ImportExport />} + onClick={(event) => { + event.stopPropagation(); + applyLayout(); + }} + /> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>Layouts</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Settings />} + additionalClasses="query-settings" + onClick={(event) => { + event.stopPropagation(); + if (toggleSettings === 'settings') setToggleSettings(undefined); + else setToggleSettings('settings'); + }} + /> + </TooltipTrigger> + <TooltipContent side={'bottom'} disabled={toggleSettings === 'settings'}> + <p>Query builder settings</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Cached />} + onClick={(event) => { + event.stopPropagation(); + if (props.onRunQuery) props.onRunQuery(); + }} + /> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>Rerun query</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Difference />} + onClick={(event) => { + event.stopPropagation(); + if (toggleSettings === 'logic') setToggleSettings(undefined); + else setToggleSettings('logic'); + }} + /> + </TooltipTrigger> + <TooltipContent side={'bottom'} disabled={toggleSettings === 'logic'}> + <p>Logic settings</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Lightbulb />} + onClick={(event) => { + event.stopPropagation(); + if (toggleSettings === 'ml') setToggleSettings(undefined); + else setToggleSettings('ml'); + }} + /> + </TooltipTrigger> + <TooltipContent side={'bottom'} disabled={toggleSettings === 'ml'}> + <p>Machine learning</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> </ControlContainer> </div> diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx index c8e2f7cc29c140c12cb0f8236ce89dd77eacab84..e9fcbff627892795fb0ce930e75dbd86639f8a5c 100644 --- a/libs/shared/lib/schema/panel/schema.tsx +++ b/libs/shared/lib/schema/panel/schema.tsx @@ -20,7 +20,7 @@ import { EntityNode } from '../pills/nodes/entity/entity-node'; import { RelationNode } from '../pills/nodes/relation/relation-node'; import { SchemaDialog } from './schemaDialog'; import { Fullscreen, Cached, Settings } from '@mui/icons-material'; -import Tooltip from '../../components/tooltip'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../components/tooltip'; interface Props { content?: string; @@ -115,35 +115,52 @@ export const Schema = (props: Props) => { <div className="relative flex items-center justify-between z-[2] py-0 px-2 bg-secondary-100 border-b border-secondary-200"> <h1 className="text-xs font-semibold text-secondary-800">Schema</h1> <ControlContainer> - <Tooltip tooltip="Fit to screen"> - <Button type="secondary" variant="ghost" size="xs" iconComponent={<Fullscreen />} onClick={fitView} /> - </Tooltip> - <Tooltip tooltip="Refresh schema"> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Cached />} - onClick={(e) => { - e.stopPropagation(); - if (session.currentSaveState) wsSchemaRequest(session.currentSaveState); - else wsGetStates(); - }} - /> - </Tooltip> - <Tooltip tooltip="Schema settings"> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<Settings />} - additionalClasses="schema-settings" - onClick={(e) => { - e.stopPropagation(); - setToggleSchemaSettings(!toggleSchemaSettings); - }} - /> - </Tooltip> + <TooltipProvider delayDuration={0}> + <Tooltip> + <TooltipTrigger> + <Button type="secondary" variant="ghost" size="xs" iconComponent={<Fullscreen />} onClick={fitView} /> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>Fit to screen</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Cached />} + onClick={(e) => { + e.stopPropagation(); + if (session.currentSaveState) wsSchemaRequest(session.currentSaveState); + else wsGetStates(); + }} + /> + </TooltipTrigger> + <TooltipContent side={'bottom'}> + <p>Refresh schema</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<Settings />} + additionalClasses="schema-settings" + onClick={(e) => { + e.stopPropagation(); + setToggleSchemaSettings(!toggleSchemaSettings); + }} + /> + </TooltipTrigger> + <TooltipContent side={'bottom'} disabled={toggleSchemaSettings}> + <p>Schema settings</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> </ControlContainer> </div> {nodes.length === 0 ? ( diff --git a/libs/shared/lib/vis/configuration/panel/tab-item.tsx b/libs/shared/lib/vis/configuration/panel/tab-item.tsx index 72d055dcd2caeca56da61b753d2e914b9cea8ccb..575cece096579f162ab1298c9e1967e81386d4ef 100644 --- a/libs/shared/lib/vis/configuration/panel/tab-item.tsx +++ b/libs/shared/lib/vis/configuration/panel/tab-item.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import Tooltip from '@graphpolaris/shared/lib/components/tooltip'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@graphpolaris/shared/lib/components/tooltip'; type TabItemProps = { active: boolean; @@ -14,7 +14,14 @@ export default function TabItem({ active, onClick, icon, tooltip }: TabItemProps className={`me-2 inline-block bg-secondary-100 cursor-pointer ${active && 'border-b-2 border-primary-200 text-primary-200'}`} onClick={onClick} > - <Tooltip tooltip={tooltip}>{icon}</Tooltip> + <TooltipProvider delayDuration={0}> + <Tooltip> + <TooltipTrigger>{icon}</TooltipTrigger> + <TooltipContent side={'top'}> + <p>{tooltip}</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> </li> ); } diff --git a/libs/shared/lib/vis/visualizationPanel.tsx b/libs/shared/lib/vis/visualizationPanel.tsx index 42cdf0cc1129c9f73ad84179a524e14fa3847429..d71f7191bb075c1df8132656bbd5b8661c787e99 100644 --- a/libs/shared/lib/vis/visualizationPanel.tsx +++ b/libs/shared/lib/vis/visualizationPanel.tsx @@ -13,9 +13,9 @@ import ControlContainer from '@graphpolaris/shared/lib/components/controls'; import { Button } from '@graphpolaris/shared/lib/components/buttons'; import { VisualizationDialog } from './configuration'; -import { Settings as SettingsIcon, Apps as AppsIcon } from '@mui/icons-material'; +import { Settings as SettingsIcon, Apps as AppsIcon, Fullscreen } from '@mui/icons-material'; import { VisualizationManager, Visualizations } from './visualizationManager'; -import Tooltip from '../components/tooltip'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../components/tooltip'; export const VisualizationPanel = () => { const graphQueryResult = useGraphQueryResult(); @@ -44,29 +44,42 @@ export const VisualizationPanel = () => { <div className="sticky top-0 flex items-center justify-between z-[2] py-0 px-2 bg-secondary-100 border-b border-secondary-200"> <h1 className="text-xs font-semibold text-secondary-800">{vis.activeVisualization} visualization</h1> <ControlContainer> - <Tooltip tooltip="Visualization settings" disabled={showVisSettings}> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<SettingsIcon />} - onClick={() => { - // TODO - // setShowVisSettings(!showVisSettings); - }} - /> - </Tooltip> - <Tooltip tooltip="Change visualization" disabled={visDropdownOpen}> - <Button - type="secondary" - variant="ghost" - size="xs" - iconComponent={<AppsIcon />} - onClick={() => { - setVisDropdownOpen(!visDropdownOpen); - }} - /> - </Tooltip> + <TooltipProvider delayDuration={0}> + <Tooltip disabled={showVisSettings}> + <TooltipTrigger> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<SettingsIcon />} + onClick={() => { + // TODO + // setShowVisSettings(!showVisSettings); + }} + /> + </TooltipTrigger> + <TooltipContent side={'bottom'} disabled={showVisSettings}> + <p>Visualization settings</p> + </TooltipContent> + </Tooltip> + <Tooltip> + <TooltipTrigger> + <Button + type="secondary" + variant="ghost" + size="xs" + iconComponent={<AppsIcon />} + onClick={() => { + setVisDropdownOpen(!visDropdownOpen); + }} + /> + </TooltipTrigger> + <TooltipContent side={'bottom'} disabled={visDropdownOpen}> + <p>Change visualization</p> + </TooltipContent> + </Tooltip> + </TooltipProvider> + {visDropdownOpen && ( <div ref={visDropdownRef}> <DropdownItemContainer align="top-6 right-6"> diff --git a/libs/shared/package.json b/libs/shared/package.json index c59615c5a882bf1a6d43931d3b5d22d2011e3aa0..33aeb4f4e133e2c5ef1d26f39d1f934e64245516 100644 --- a/libs/shared/package.json +++ b/libs/shared/package.json @@ -25,6 +25,7 @@ "@mui/icons-material": "^5.15.11", "@nebula.gl/layers": "^1.0.4", "@pixi-essentials/cull": "^2.0.0", + "@radix-ui/react-tooltip": "^1.0.7", "@reactflow/node-resizer": "^2.2.9", "@reduxjs/toolkit": "^2.2.1", "@tisoap/react-flow-smart-edge": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a8312fdf62a96d1daa907cfe00929ff8e5007dca..2a6d1b96e112b59f42a5d3104c78ef08cde1abff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -244,6 +244,9 @@ importers: '@pixi-essentials/cull': specifier: ^2.0.0 version: 2.0.0(@pixi/display@7.4.0)(@pixi/math@7.4.0) + '@radix-ui/react-tooltip': + specifier: ^1.0.7 + version: 1.0.7(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) '@reactflow/node-resizer': specifier: ^2.2.9 version: 2.2.9(@types/react@18.2.60)(immer@10.0.3)(react-dom@18.2.0)(react@18.2.0) @@ -4828,7 +4831,7 @@ packages: /@math.gl/web-mercator@3.6.3: resolution: {integrity: sha512-UVrkSOs02YLehKaehrxhAejYMurehIHPfFQvPFZmdJHglHOU4V2cCUApTVEwOksvCp161ypEqVp+9H6mGhTTcw==} dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.24.0 gl-matrix: 3.4.3 dev: false @@ -5782,7 +5785,6 @@ packages: resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} dependencies: '@babel/runtime': 7.24.0 - dev: true /@radix-ui/react-arrow@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==} @@ -5803,7 +5805,6 @@ packages: '@types/react-dom': 18.2.19 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==} @@ -5841,7 +5842,6 @@ packages: '@babel/runtime': 7.24.0 '@types/react': 18.2.60 react: 18.2.0 - dev: true /@radix-ui/react-context@1.0.1(@types/react@18.2.60)(react@18.2.0): resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} @@ -5855,7 +5855,6 @@ packages: '@babel/runtime': 7.24.0 '@types/react': 18.2.60 react: 18.2.0 - dev: true /@radix-ui/react-direction@1.0.1(@types/react@18.2.60)(react@18.2.0): resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} @@ -5896,6 +5895,31 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.60)(react@18.2.0) + '@types/react': 18.2.60 + '@types/react-dom': 18.2.19 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.60)(react@18.2.0): resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -5946,7 +5970,6 @@ packages: '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.60)(react@18.2.0) '@types/react': 18.2.60 react: 18.2.0 - dev: true /@radix-ui/react-popper@1.1.2(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==} @@ -5978,6 +6001,36 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-popper@1.1.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-use-rect': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-use-size': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/rect': 1.0.1 + '@types/react': 18.2.60 + '@types/react-dom': 18.2.19 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-portal@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==} peerDependencies: @@ -5999,6 +6052,49 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.60 + '@types/react-dom': 18.2.19 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@types/react': 18.2.60 + '@types/react-dom': 18.2.19 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-primitive@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} peerDependencies: @@ -6018,7 +6114,6 @@ packages: '@types/react-dom': 18.2.19 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /@radix-ui/react-roving-focus@1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==} @@ -6124,7 +6219,6 @@ packages: '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.60)(react@18.2.0) '@types/react': 18.2.60 react: 18.2.0 - dev: true /@radix-ui/react-toggle-group@1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==} @@ -6203,6 +6297,38 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true + /@radix-ui/react-tooltip@1.0.7(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-lPh5iKNFVQ/jav/j6ZrWq3blfDJ0OH9R6FlNUHPMqdLuQ9vwDgFsRxvl8b7Asuy5c8xmoojHUxKHQSOAvMHxyw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-popper': 1.1.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.60)(react@18.2.0) + '@radix-ui/react-visually-hidden': 1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.60 + '@types/react-dom': 18.2.19 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.60)(react@18.2.0): resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: @@ -6215,7 +6341,6 @@ packages: '@babel/runtime': 7.24.0 '@types/react': 18.2.60 react: 18.2.0 - dev: true /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.60)(react@18.2.0): resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} @@ -6230,7 +6355,6 @@ packages: '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.60)(react@18.2.0) '@types/react': 18.2.60 react: 18.2.0 - dev: true /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.60)(react@18.2.0): resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} @@ -6245,7 +6369,6 @@ packages: '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.60)(react@18.2.0) '@types/react': 18.2.60 react: 18.2.0 - dev: true /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.60)(react@18.2.0): resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} @@ -6259,7 +6382,6 @@ packages: '@babel/runtime': 7.24.0 '@types/react': 18.2.60 react: 18.2.0 - dev: true /@radix-ui/react-use-previous@1.0.1(@types/react@18.2.60)(react@18.2.0): resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==} @@ -6288,7 +6410,6 @@ packages: '@radix-ui/rect': 1.0.1 '@types/react': 18.2.60 react: 18.2.0 - dev: true /@radix-ui/react-use-size@1.0.1(@types/react@18.2.60)(react@18.2.0): resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==} @@ -6303,7 +6424,6 @@ packages: '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.60)(react@18.2.0) '@types/react': 18.2.60 react: 18.2.0 - dev: true /@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@18.2.19)(@types/react@18.2.60)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==} @@ -6324,13 +6444,11 @@ packages: '@types/react-dom': 18.2.19 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - dev: true /@radix-ui/rect@1.0.1: resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} dependencies: '@babel/runtime': 7.24.0 - dev: true /@reactflow/background@11.3.9(@types/react@18.2.60)(immer@10.0.3)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-byj/G9pEC8tN0wT/ptcl/LkEP/BBfa33/SvBkqE4XwyofckqF87lKp573qGlisfnsijwAbpDlf81PuFL41So4Q==} @@ -10350,7 +10468,7 @@ packages: engines: {node: '>=10.0.0'} requiresBuild: true dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.24.0 '@types/raf': 3.4.0 core-js: 3.36.0 raf: 3.4.1 @@ -17496,6 +17614,7 @@ packages: react: 18.2.0 scheduler: 0.19.1 dev: false + bundledDependencies: false /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} @@ -18029,7 +18148,7 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: - '@babel/runtime': 7.21.0 + '@babel/runtime': 7.24.0 react: 18.2.0 use-composed-ref: 1.3.0(react@18.2.0) use-latest: 1.2.1(@types/react@18.2.60)(react@18.2.0)