diff --git a/browser/.eslintrc.cjs b/browser/.eslintrc.cjs index ca5d3e6f..79356d65 100644 --- a/browser/.eslintrc.cjs +++ b/browser/.eslintrc.cjs @@ -38,7 +38,7 @@ module.exports = { 'create-template/tsconfig.json', ], }, - plugins: ['react', '@typescript-eslint', 'prettier', 'react-hooks', 'jsx-a11y'], + plugins: ['react', '@typescript-eslint', 'prettier', 'react-hooks', 'jsx-a11y', 'eslint-plugin-react-compiler'], settings: { react: { version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use @@ -114,5 +114,6 @@ module.exports = { "@typescript-eslint/no-shadow": ["error"], "@typescript-eslint/member-ordering": "error", "react/no-unknown-property": ["error", { "ignore": ["about"] }], + 'react-compiler/react-compiler': 'error', }, }; diff --git a/browser/CHANGELOG.md b/browser/CHANGELOG.md index eebf839d..4c9c67cf 100644 --- a/browser/CHANGELOG.md +++ b/browser/CHANGELOG.md @@ -28,7 +28,11 @@ This changelog covers all five packages, as they are (for now) updated as a whol ### @tomic/react -- Add cjs build. +- BREAKING CHANGE: `useCanWrite` now only returns a boolean. There is no longer a message returned. +- BREAKING CHANGE: `useCanWrite` does not take an agent as argument any more and only checks the agent set in the store. If you need to explicitly check a different agent, use `await resource.canWrite(agent)`. +- BREAKING CHANGE: `useDebounce` and `useDebouncedCallback` are no longer exported. +- Added `useDebouncedSave` hook. +- Add a cjs build. ### @tomic/cli diff --git a/browser/data-browser/index.html b/browser/data-browser/index.html index 1ceb99ce..d64ce0c5 100644 --- a/browser/data-browser/index.html +++ b/browser/data-browser/index.html @@ -12,6 +12,7 @@ + diff --git a/browser/data-browser/package.json b/browser/data-browser/package.json index 2ed45046..15242210 100644 --- a/browser/data-browser/package.json +++ b/browser/data-browser/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-tabs": "^1.1.1", + "@tanstack/react-router": "^1.95.1", "@tiptap/extension-image": "^2.9.1", "@tiptap/extension-link": "^2.9.1", "@tiptap/extension-placeholder": "^2.9.1", @@ -31,38 +32,36 @@ "prismjs": "^1.29.0", "query-string": "^7.1.3", "quick-score": "^0.2.0", - "react": "^18.3.1", + "react": "^19.0.0", "react-colorful": "^5.6.1", - "react-dom": "^18.3.1", + "react-dom": "^19.0.0", "react-dropzone": "^11.7.1", - "react-helmet-async": "^1.3.0", "react-hot-toast": "^2.4.1", "react-hotkeys-hook": "^3.4.7", "react-icons": "^4.12.0", "react-intersection-observer": "^9.13.1", - "react-is": "^18.3.1", - "react-markdown": "^8.0.7", + "react-is": "^19.0.0", + "react-markdown": "^9.0.3", "react-pdf": "^9.1.1", - "react-router": "^6.27.0", - "react-router-dom": "^6.27.0", "react-virtualized-auto-sizer": "^1.0.24", "react-window": "^1.8.10", "reactflow": "^11.11.4", - "remark-gfm": "^3.0.1", + "remark-gfm": "^4.0.0", "styled-components": "^6.1.13", "stylis": "4.3.0", "tippy.js": "^6.3.7", "tiptap-markdown": "^0.8.10" }, "devDependencies": { - "@swc/plugin-styled-components": "^2.0.12", + "@tanstack/router-devtools": "^1.95.1", "@types/prismjs": "^1.26.5", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", "@types/react-pdf": "^7.0.0", - "@types/react-router-dom": "^5.3.3", "@types/react-window": "^1.8.8", - "@vitejs/plugin-react-swc": "^3.7.1", + "@vitejs/plugin-react": "^4.3.4", + "babel-plugin-react-compiler": "19.0.0-beta-37ed2a7-20241206", + "babel-plugin-styled-components": "^2.1.4", "csstype": "^3.1.3", "gh-pages": "^5.0.0", "lint-staged": "^10.5.4", diff --git a/browser/data-browser/src/App.tsx b/browser/data-browser/src/App.tsx index 32755ec6..e0b8a425 100644 --- a/browser/data-browser/src/App.tsx +++ b/browser/data-browser/src/App.tsx @@ -1,33 +1,15 @@ -import { BrowserRouter } from 'react-router-dom'; -import { HelmetProvider } from 'react-helmet-async'; import { StoreContext, Store } from '@tomic/react'; -import { StyleSheetManager, type ShouldForwardProp } from 'styled-components'; -import { GlobalStyle, ThemeWrapper } from './styling'; -import { AppRoutes } from './routes/Routes'; -import { NavWrapper } from './components/Navigation'; -import { MetaSetter } from './components/MetaSetter'; -import { Toaster } from './components/Toaster'; import { isDev } from './config'; -import { initBugsnag } from './helpers/loggingHandlers'; -import HotKeysWrapper from './components/HotKeyWrapper'; -import { AppSettingsContextProvider } from './helpers/AppSettings'; -import CrashPage from './views/CrashPage'; -import { DialogGlobalContextProvider } from './components/Dialog/DialogGlobalContextProvider'; import { registerHandlers } from './handlers'; -import { ErrorBoundary } from './views/ErrorPage'; -import { NetworkIndicator } from './components/NetworkIndicator'; import { getAgentFromLocalStorage } from './helpers/agentStorage'; -import { DropdownContainer } from './components/Dropdown/DropdownContainer'; -import { PopoverContainer } from './components/Popover'; -import { SkipNav } from './components/SkipNav'; -import { ControlLockProvider } from './hooks/useControlLock'; -import { FormValidationContextProvider } from './components/forms/formValidation/FormValidationContextProvider'; import { registerCustomCreateActions } from './components/forms/NewForm/CustomCreateActions'; -import isPropValid from '@emotion/is-prop-valid'; -import { NewResourceUIProvider } from './components/forms/NewForm/useNewResourceUI'; import { serverURLStorage } from './helpers/serverURLStorage'; +import type { JSX } from 'react'; +import { RouterProvider } from '@tanstack/react-router'; +import { router } from './routes/Router'; + function fixDevUrl(url: string) { if (isDev()) { return url.replace('5173', '9883'); @@ -57,10 +39,6 @@ declare global { bugsnagApiKey: string; } } -// Setup bugsnag for error handling, but only if there's an API key -const ErrBoundary = window.bugsnagApiKey - ? initBugsnag(window.bugsnagApiKey) - : ErrorBoundary; // Fetch all the Properties and Classes - this helps speed up the app. store.preloadPropsAndClasses(); @@ -74,62 +52,11 @@ if (isDev()) { window.store = store; } -// This implements the default behavior from styled-components v5 -const shouldForwardProp: ShouldForwardProp<'web'> = (propName, target) => { - if (typeof target === 'string') { - // For HTML elements, forward the prop if it is a valid HTML attribute - return isPropValid(propName); - } - - // For other elements, forward all props - return true; -}; - /** Entrypoint of the application. This is where providers go. */ function App(): JSX.Element { return ( - - - {/* Basename is for hosting on GitHub pages */} - - - - - - - {/* @ts-ignore fallback component type too strict */} - - {/* Default form validation provider. Does not do anything on its own but will make sure useValidation works without context*/} - undefined} - > - - - - - - - - - - - - - - - - - - - - - - - - - - + ); } diff --git a/browser/data-browser/src/Providers.tsx b/browser/data-browser/src/Providers.tsx new file mode 100644 index 00000000..d8afdb88 --- /dev/null +++ b/browser/data-browser/src/Providers.tsx @@ -0,0 +1,76 @@ +import { Toaster } from 'react-hot-toast'; +import { StyleSheetManager, type ShouldForwardProp } from 'styled-components'; +import { DialogGlobalContextProvider } from './components/Dialog/DialogGlobalContextProvider'; +import { DropdownContainer } from './components/Dropdown/DropdownContainer'; +import { FormValidationContextProvider } from './components/forms/formValidation/FormValidationContextProvider'; +import { NewResourceUIProvider } from './components/forms/NewForm/useNewResourceUI'; +import HotKeysWrapper from './components/HotKeyWrapper'; +import { MetaSetter } from './components/MetaSetter'; +import { NavWrapper } from './components/Navigation'; +import { NetworkIndicator } from './components/NetworkIndicator'; +import { PopoverContainer } from './components/Popover'; +import { SkipNav } from './components/SkipNav'; +import { ControlLockProvider } from './hooks/useControlLock'; +import { ThemeWrapper, GlobalStyle } from './styling'; +import isPropValid from '@emotion/is-prop-valid'; +import { initBugsnag } from './helpers/loggingHandlers'; +import { ErrorBoundary } from './views/ErrorPage'; +import CrashPage from './views/CrashPage'; +import { AppSettingsContextProvider } from './helpers/AppSettings'; +import { NavStateProvider } from './components/NavState'; + +// Setup bugsnag for error handling, but only if there's an API key +const ErrBoundary = window.bugsnagApiKey + ? initBugsnag(window.bugsnagApiKey) + : ErrorBoundary; + +// This implements the default behavior from styled-components v5 +const shouldForwardProp: ShouldForwardProp<'web'> = (propName, target) => { + if (typeof target === 'string') { + // For HTML elements, forward the prop if it is a valid HTML attribute + return isPropValid(propName); + } + + // For other elements, forward all props + return true; +}; + +export const Providers: React.FC = ({ children }) => { + return ( + + + + + + + + + {/* Default form validation provider. Does not do anything on its own but will make sure useValidation works without context*/} + undefined} + > + + + + + + + + + {children} + + + + + + + + + + + + + + + ); +}; diff --git a/browser/data-browser/src/chunks/EmojiInput/EmojiInput.tsx b/browser/data-browser/src/chunks/EmojiInput/EmojiInput.tsx index 5610abe7..62c0b95e 100644 --- a/browser/data-browser/src/chunks/EmojiInput/EmojiInput.tsx +++ b/browser/data-browser/src/chunks/EmojiInput/EmojiInput.tsx @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { useCallback, useState, type JSX } from 'react'; import Picker from '@emoji-mart/react'; import { styled } from 'styled-components'; import * as RadixPopover from '@radix-ui/react-popover'; diff --git a/browser/data-browser/src/chunks/GraphViewer/FloatingEdge.tsx b/browser/data-browser/src/chunks/GraphViewer/FloatingEdge.tsx index 942fc25a..8df31401 100644 --- a/browser/data-browser/src/chunks/GraphViewer/FloatingEdge.tsx +++ b/browser/data-browser/src/chunks/GraphViewer/FloatingEdge.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react'; +import { useCallback, type JSX } from 'react'; import { useStore as useFlowStore, getBezierPath, diff --git a/browser/data-browser/src/chunks/GraphViewer/OntologyGraph.tsx b/browser/data-browser/src/chunks/GraphViewer/OntologyGraph.tsx index 37183124..c6cd0f8e 100644 --- a/browser/data-browser/src/chunks/GraphViewer/OntologyGraph.tsx +++ b/browser/data-browser/src/chunks/GraphViewer/OntologyGraph.tsx @@ -1,5 +1,5 @@ import { Resource, useStore } from '@tomic/react'; -import { useCallback } from 'react'; +import { useCallback, type JSX } from 'react'; import ReactFlow, { Controls, useReactFlow, diff --git a/browser/data-browser/src/chunks/PDFViewer/index.tsx b/browser/data-browser/src/chunks/PDFViewer/index.tsx index 98dbd3c1..771e0331 100644 --- a/browser/data-browser/src/chunks/PDFViewer/index.tsx +++ b/browser/data-browser/src/chunks/PDFViewer/index.tsx @@ -1,4 +1,4 @@ -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useMemo, useState, type JSX } from 'react'; import { pdfjs, Document, Page } from 'react-pdf'; import 'react-pdf/dist/esm/Page/TextLayer.css'; import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; diff --git a/browser/data-browser/src/components/AllProps.tsx b/browser/data-browser/src/components/AllProps.tsx index 8f398a18..a3b97f3c 100644 --- a/browser/data-browser/src/components/AllProps.tsx +++ b/browser/data-browser/src/components/AllProps.tsx @@ -4,6 +4,8 @@ import { styled, css } from 'styled-components'; import PropVal from './PropVal'; import { ALL_PROPS_CONTAINER } from '../helpers/containers'; +import type { JSX } from 'react'; + type Props = { resource: Resource; /** A list of property subjects (URLs) that need not be rendered */ diff --git a/browser/data-browser/src/components/AllPropsSimple.tsx b/browser/data-browser/src/components/AllPropsSimple.tsx index 0b0fc09f..1b8b8b51 100644 --- a/browser/data-browser/src/components/AllPropsSimple.tsx +++ b/browser/data-browser/src/components/AllPropsSimple.tsx @@ -7,7 +7,7 @@ import { useSubject, useTitle, } from '@tomic/react'; -import { useMemo } from 'react'; +import { useMemo, type JSX } from 'react'; import { styled } from 'styled-components'; import { InlineFormattedResourceList } from './InlineFormattedResourceList'; diff --git a/browser/data-browser/src/components/AtomicLink.tsx b/browser/data-browser/src/components/AtomicLink.tsx index cf4a608f..cec0f1c8 100644 --- a/browser/data-browser/src/components/AtomicLink.tsx +++ b/browser/data-browser/src/components/AtomicLink.tsx @@ -1,4 +1,4 @@ -import { ReactNode, forwardRef } from 'react'; +import { ReactNode, forwardRef, type JSX } from 'react'; import { styled } from 'styled-components'; import { constructOpenURL, pathToURL } from '../helpers/navigation'; import { FaExternalLinkAlt } from 'react-icons/fa'; @@ -33,7 +33,7 @@ export const AtomicLink = forwardRef( ): JSX.Element => { const navigate = useNavigateWithTransition(); - if (!subject && !href && !path) { + if (subject === undefined && href === undefined && path === undefined) { return ( No `subject`, `path` or `href` passed to this AtomicLink. diff --git a/browser/data-browser/src/components/BetaBadge.tsx b/browser/data-browser/src/components/BetaBadge.tsx index a48056e6..06686a79 100644 --- a/browser/data-browser/src/components/BetaBadge.tsx +++ b/browser/data-browser/src/components/BetaBadge.tsx @@ -1,5 +1,7 @@ import { styled } from 'styled-components'; +import type { JSX } from 'react'; + export function BetaBadge(): JSX.Element { return BETA; } diff --git a/browser/data-browser/src/components/Button.tsx b/browser/data-browser/src/components/Button.tsx index c67c7b36..9eb7ff00 100644 --- a/browser/data-browser/src/components/Button.tsx +++ b/browser/data-browser/src/components/Button.tsx @@ -1,4 +1,4 @@ -import { forwardRef, PropsWithChildren } from 'react'; +import { forwardRef, PropsWithChildren, type JSX } from 'react'; import { styled } from 'styled-components'; import { transition } from '../helpers/transition'; import { Spinner } from './Spinner'; diff --git a/browser/data-browser/src/components/ButtonGroup.tsx b/browser/data-browser/src/components/ButtonGroup.tsx index a8d3f9ec..6266a15b 100644 --- a/browser/data-browser/src/components/ButtonGroup.tsx +++ b/browser/data-browser/src/components/ButtonGroup.tsx @@ -1,4 +1,4 @@ -import { useCallback, useId, useState } from 'react'; +import { useCallback, useId, useState, type JSX } from 'react'; import { styled } from 'styled-components'; export interface ButtonGroupOption { diff --git a/browser/data-browser/src/components/ClassDetail.tsx b/browser/data-browser/src/components/ClassDetail.tsx index 1d0678e0..70942f84 100644 --- a/browser/data-browser/src/components/ClassDetail.tsx +++ b/browser/data-browser/src/components/ClassDetail.tsx @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { type JSX } from 'react'; import { Resource, useResource } from '@tomic/react'; import { Detail } from './Detail'; import { getIconForClass } from '../helpers/iconMap'; import { InlineFormattedResourceList } from './InlineFormattedResourceList'; import { AtomicLink } from './AtomicLink'; +import { Row } from './Row'; type ClassDetailProps = { resource: Resource; @@ -16,12 +17,12 @@ export const ClassDetail: React.FC = ({ resource }) => { } return ( - + - + ); }; diff --git a/browser/data-browser/src/components/ClassSelectorDialog.tsx b/browser/data-browser/src/components/ClassSelectorDialog.tsx index 7509d6b6..52047727 100644 --- a/browser/data-browser/src/components/ClassSelectorDialog.tsx +++ b/browser/data-browser/src/components/ClassSelectorDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, type JSX } from 'react'; import { Dialog, DialogContent, DialogTitle, useDialog } from './Dialog'; import { OutlinedSection } from './OutlinedSection'; import { useServerSearch, core, type Core, useResource } from '@tomic/react'; diff --git a/browser/data-browser/src/components/Collapse.tsx b/browser/data-browser/src/components/Collapse.tsx index 43d4b6aa..5326301f 100644 --- a/browser/data-browser/src/components/Collapse.tsx +++ b/browser/data-browser/src/components/Collapse.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, type JSX } from 'react'; import { styled } from 'styled-components'; import { timeoutEffect } from '../helpers/timeoutEffect'; import { animationDuration } from '../styling'; diff --git a/browser/data-browser/src/components/CommitDetail.tsx b/browser/data-browser/src/components/CommitDetail.tsx index df982f4f..5078d087 100644 --- a/browser/data-browser/src/components/CommitDetail.tsx +++ b/browser/data-browser/src/components/CommitDetail.tsx @@ -4,6 +4,8 @@ import { Detail } from './Detail'; import { DateTime } from './datatypes/DateTime'; import { AtomicLink } from './AtomicLink'; +import type { JSX } from 'react'; + type Props = { commitSubject?: string; }; @@ -22,8 +24,8 @@ export function CommitDetail({ commitSubject }: Props): JSX.Element | null { return null; } - if (!commitSubject || !resource.isReady) { - return loading...; + if (!commitSubject || resource.loading) { + return -; } return ( diff --git a/browser/data-browser/src/components/ConfirmationDialog.tsx b/browser/data-browser/src/components/ConfirmationDialog.tsx index b48a7b8f..6e73be7e 100644 --- a/browser/data-browser/src/components/ConfirmationDialog.tsx +++ b/browser/data-browser/src/components/ConfirmationDialog.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, type JSX } from 'react'; import { Dialog, DialogActions, diff --git a/browser/data-browser/src/components/Detail.tsx b/browser/data-browser/src/components/Detail.tsx index 49235632..4dab568b 100644 --- a/browser/data-browser/src/components/Detail.tsx +++ b/browser/data-browser/src/components/Detail.tsx @@ -5,10 +5,12 @@ export const Detail = styled.div` display: inline-flex; align-items: center; gap: 0.5ch; - margin-right: 2rem; `; /** A wrapper for the Detail component . */ export const Details = styled.div` font-style: italic; + display: flex; + justify-content: space-between; + flex-wrap: wrap; `; diff --git a/browser/data-browser/src/components/Details.tsx b/browser/data-browser/src/components/Details.tsx index dbebcd4c..ebbdd01a 100644 --- a/browser/data-browser/src/components/Details.tsx +++ b/browser/data-browser/src/components/Details.tsx @@ -1,4 +1,10 @@ -import { PropsWithChildren, useCallback, useEffect, useState } from 'react'; +import { + PropsWithChildren, + useCallback, + useEffect, + useState, + type JSX, +} from 'react'; import { styled } from 'styled-components'; import { FaCaretRight } from 'react-icons/fa'; import { Collapse } from './Collapse'; @@ -40,7 +46,7 @@ export function Details({ return !p; }); - }, []); + }, [onStateToggle]); return ( <> @@ -52,7 +58,7 @@ export function Details({ hide={!!disabled} aria-label={isOpen ? 'collapse' : 'expand'} > - + {title} @@ -78,7 +84,7 @@ const TitleWrapper = styled.div` } `; -const Icon = styled(FaCaretRight)<{ turn: boolean }>` +const Icon = styled(FaCaretRight)<{ $turn: boolean }>` color: ${({ theme }) => theme.colors.main}; margin-top: auto; cursor: pointer; @@ -89,7 +95,7 @@ const Icon = styled(FaCaretRight)<{ turn: boolean }>` transition: transform var(--speed) ease-in-out, background-color var(--speed) ease; - transform: rotate(${props => (props.turn ? '90deg' : '0deg')}); + transform: rotate(${props => (props.$turn ? '90deg' : '0deg')}); aspect-ratio: 1/1; display: flex; align-items: center; diff --git a/browser/data-browser/src/components/Dialog/DialogGlobalContextProvider.tsx b/browser/data-browser/src/components/Dialog/DialogGlobalContextProvider.tsx index 5f0a6ad5..56ae6dbd 100644 --- a/browser/data-browser/src/components/Dialog/DialogGlobalContextProvider.tsx +++ b/browser/data-browser/src/components/Dialog/DialogGlobalContextProvider.tsx @@ -16,10 +16,14 @@ import { styled } from 'styled-components'; interface DialogGlobalContext { openDialogs: string[]; setDialogOpen: (id: string, open: boolean) => void; - portal: RefObject; + portal: RefObject; } -export const DialogContext = createContext(null!); +export const DialogContext = createContext({ + openDialogs: [], + setDialogOpen: () => {}, + portal: { current: null }, +}); export const DialogGlobalContextProvider: FC = ({ children, diff --git a/browser/data-browser/src/components/Dialog/useDialog.tsx b/browser/data-browser/src/components/Dialog/useDialog.tsx index 7ef7d574..22de9ed1 100644 --- a/browser/data-browser/src/components/Dialog/useDialog.tsx +++ b/browser/data-browser/src/components/Dialog/useDialog.tsx @@ -16,7 +16,7 @@ export type UseDialogOptions = { bindShow?: React.Dispatch; onCancel?: () => void; onSuccess?: () => void; - triggerRef?: React.RefObject; + triggerRef?: React.RefObject; }; /** Sets up state, and functions to use with a {@link Dialog} */ diff --git a/browser/data-browser/src/components/Dropdown/DefaultTrigger.tsx b/browser/data-browser/src/components/Dropdown/DefaultTrigger.tsx index 408f319e..f560fa5f 100644 --- a/browser/data-browser/src/components/Dropdown/DefaultTrigger.tsx +++ b/browser/data-browser/src/components/Dropdown/DefaultTrigger.tsx @@ -1,7 +1,6 @@ -import { useId } from 'react'; import { IconButton } from '../IconButton/IconButton'; import { - DropdownTriggerRenderFunction, + DropdownTriggerComponent, type DropdownTriggerProps, } from './DropdownTrigger'; @@ -12,13 +11,14 @@ export const buildDefaultTrigger = ( icon: React.ReactNode, title = 'Open menu', ButtonComp: typeof IconButton = IconButton, -): DropdownTriggerRenderFunction => { - const Comp = ( - { onClick, menuId, isActive }: DropdownTriggerProps, - ref: React.Ref, - ) => { - const id = useId(); - +): DropdownTriggerComponent => { + const Comp = ({ + onClick, + menuId, + isActive, + ref, + id, + }: DropdownTriggerProps) => { return ( > = ({ const portalRef = useRef(null); return ( - + {children} - + ); }; diff --git a/browser/data-browser/src/components/Dropdown/DropdownTrigger.ts b/browser/data-browser/src/components/Dropdown/DropdownTrigger.ts index da17763a..aa52db8a 100644 --- a/browser/data-browser/src/components/Dropdown/DropdownTrigger.ts +++ b/browser/data-browser/src/components/Dropdown/DropdownTrigger.ts @@ -2,9 +2,8 @@ export interface DropdownTriggerProps { onClick: (event: React.MouseEvent) => void; menuId: string; isActive: boolean; + ref: React.Ref; + id: string; } -export type DropdownTriggerRenderFunction = React.ForwardRefRenderFunction< - HTMLButtonElement, - DropdownTriggerProps ->; +export type DropdownTriggerComponent = React.FC; diff --git a/browser/data-browser/src/components/Dropdown/dropdownContext.ts b/browser/data-browser/src/components/Dropdown/dropdownContext.ts index a35c6c13..9830d542 100644 --- a/browser/data-browser/src/components/Dropdown/dropdownContext.ts +++ b/browser/data-browser/src/components/Dropdown/dropdownContext.ts @@ -1,5 +1,7 @@ import { createContext } from 'react'; export const DropdownPortalContext = createContext< - React.RefObject ->(null!); + React.RefObject +>({ + current: null, +}); diff --git a/browser/data-browser/src/components/Dropdown/index.tsx b/browser/data-browser/src/components/Dropdown/index.tsx index 005b19c2..6677c06f 100644 --- a/browser/data-browser/src/components/Dropdown/index.tsx +++ b/browser/data-browser/src/components/Dropdown/index.tsx @@ -6,15 +6,15 @@ import { useState, useCallback, PropsWithChildren, - forwardRef, ReactNode, useEffect, + type JSX, } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { styled } from 'styled-components'; import { useClickAwayListener } from '../../hooks/useClickAwayListener'; import { Button } from '../Button'; -import { DropdownTriggerRenderFunction } from './DropdownTrigger'; +import { DropdownTriggerComponent as DropdownTriggerComponent } from './DropdownTrigger'; import { shortcuts } from '../HotKeyWrapper'; import { Shortcut } from '../Shortcut'; import { transition } from '../../helpers/transition'; @@ -41,7 +41,7 @@ export type DropdownItem = typeof DIVIDER | MenuItemMinimial; interface DropdownMenuProps { /** The list of menu items */ items: DropdownItem[]; - trigger: DropdownTriggerRenderFunction; + Trigger: DropdownTriggerComponent; /** Enables the keyboard shortcut */ isMainMenu?: boolean; bindActive?: (active: boolean) => void; @@ -108,11 +108,12 @@ function normalizeItems(items: DropdownItem[]) { */ export function DropdownMenu({ items, - trigger, + Trigger, isMainMenu, bindActive = () => undefined, }: DropdownMenuProps): JSX.Element { const menuId = useId(); + const triggerId = useId(); const dropdownRef = useRef(null); const triggerRef = useRef(null); @@ -252,8 +253,6 @@ export function DropdownMenu({ [getNewIndex], ); - const Trigger = useMemo(() => forwardRef(trigger), []); - const handleBlur = useCallback(() => { // Doesn't work without delay, maybe the browser sets document.activeElement after firering the blur event? requestAnimationFrame(() => { @@ -268,6 +267,7 @@ export function DropdownMenu({ return ( <> {normalizedItems.map((props, i) => { diff --git a/browser/data-browser/src/components/EditableTitle.tsx b/browser/data-browser/src/components/EditableTitle.tsx index a48ffc26..adc129d1 100644 --- a/browser/data-browser/src/components/EditableTitle.tsx +++ b/browser/data-browser/src/components/EditableTitle.tsx @@ -1,5 +1,5 @@ import { Resource, useCanWrite, useTitle } from '@tomic/react'; -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, type JSX } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { FaPencil } from 'react-icons/fa6'; import { styled, css } from 'styled-components'; @@ -13,7 +13,7 @@ import { UnsavedIndicator } from './UnsavedIndicator'; export interface EditableTitleProps { resource: Resource; /** Uses `name` by default */ - parentRef?: React.RefObject; + parentRef?: React.RefObject; id?: string; className?: string; } @@ -35,7 +35,7 @@ export function EditableTitle({ const innerRef = useRef(null); const ref = parentRef || innerRef; - const [canEdit] = useCanWrite(resource); + const canEdit = useCanWrite(resource); useHotkeys( 'enter', diff --git a/browser/data-browser/src/components/ErrorLook.tsx b/browser/data-browser/src/components/ErrorLook.tsx index 71c1469d..cd0025e3 100644 --- a/browser/data-browser/src/components/ErrorLook.tsx +++ b/browser/data-browser/src/components/ErrorLook.tsx @@ -3,6 +3,8 @@ import { styled, css } from 'styled-components'; import { FaExclamationTriangle } from 'react-icons/fa'; +import type { JSX } from 'react'; + export const errorLookStyle = css` color: ${props => props.theme.colors.alert}; font-family: monospace; diff --git a/browser/data-browser/src/components/ExternalLink.tsx b/browser/data-browser/src/components/ExternalLink.tsx index 68b790cf..6ee83dfc 100644 --- a/browser/data-browser/src/components/ExternalLink.tsx +++ b/browser/data-browser/src/components/ExternalLink.tsx @@ -1,6 +1,8 @@ import { styled } from 'styled-components'; import { FaExternalLinkAlt } from 'react-icons/fa'; +import type { JSX } from 'react'; + export enum ExternalLinkVariant { Plain, Button, diff --git a/browser/data-browser/src/components/FilePill.tsx b/browser/data-browser/src/components/FilePill.tsx index a3475eeb..410f3d79 100644 --- a/browser/data-browser/src/components/FilePill.tsx +++ b/browser/data-browser/src/components/FilePill.tsx @@ -3,6 +3,8 @@ import { useResource, useTitle } from '@tomic/react'; import { AtomicLink } from './AtomicLink'; import { styled } from 'styled-components'; +import type { JSX } from 'react'; + interface FilePillProps { subject: string; } diff --git a/browser/data-browser/src/components/HotKeyWrapper.tsx b/browser/data-browser/src/components/HotKeyWrapper.tsx index ab598b4b..d61cd4e1 100644 --- a/browser/data-browser/src/components/HotKeyWrapper.tsx +++ b/browser/data-browser/src/components/HotKeyWrapper.tsx @@ -1,12 +1,14 @@ import * as React from 'react'; import { dataURL, editURL } from '../helpers/navigation'; import { useHotkeys } from 'react-hotkeys-hook'; -import { useNavigate } from 'react-router-dom'; import { useCurrentSubject } from '../helpers/useCurrentSubject'; import { Client } from '@tomic/react'; import { useSettings } from '../helpers/AppSettings'; import { paths } from '../routes/paths'; +import type { JSX } from 'react'; +import { useNavigateWithTransition } from '../hooks/useNavigateWithTransition'; + type Props = { children: React.ReactNode; }; @@ -63,7 +65,7 @@ export function displayShortcut(shortcut: string): string { /** App-wide keyboard events handler. */ function HotKeysWrapper({ children }: Props): JSX.Element { - const navigate = useNavigate(); + const navigate = useNavigateWithTransition(); const [subject] = useCurrentSubject(); const { sideBarLocked, setSideBarLocked } = useSettings(); @@ -99,7 +101,7 @@ function HotKeysWrapper({ children }: Props): JSX.Element { }); useHotkeys(shortcuts.themeSettings, e => { e.preventDefault(); - navigate(paths.themeSettings); + navigate(paths.appSettings); }); useHotkeys(shortcuts.keyboardShortcuts, e => { e.preventDefault(); diff --git a/browser/data-browser/src/components/IconButton/IconButton.tsx b/browser/data-browser/src/components/IconButton/IconButton.tsx index 3446486c..bd88be63 100644 --- a/browser/data-browser/src/components/IconButton/IconButton.tsx +++ b/browser/data-browser/src/components/IconButton/IconButton.tsx @@ -1,8 +1,6 @@ import { ComponentType, ButtonHTMLAttributes, - forwardRef, - PropsWithChildren, AnchorHTMLAttributes, } from 'react'; import { styled, DefaultTheme } from 'styled-components'; @@ -30,52 +28,51 @@ type BaseProps = { }; export type IconButtonProps = BaseProps & - ButtonHTMLAttributes; + ButtonHTMLAttributes & { + ref?: React.Ref; + }; -export const IconButton = forwardRef< - HTMLButtonElement, - PropsWithChildren ->(({ variant, children, color, ...props }, ref) => { +export const IconButton: React.FC> = ({ + variant = IconButtonVariant.Simple, + color = 'inherit', + size = '1em', + children, + ref, + ...props +}) => { const Comp = ComponentMap.get(variant!) ?? SimpleIconButton; return ( - + {children} ); -}); - -IconButton.displayName = 'IconButton'; - -const defaultProps = { - variant: IconButtonVariant.Simple, - color: 'inherit', - size: '1em', -} as IconButtonProps; - -IconButton.defaultProps = defaultProps; +}; export type IconButtonLinkProps = BaseProps & AnchorHTMLAttributes & { href: string; + ref?: React.Ref; }; -export const IconButtonLink = forwardRef< - HTMLAnchorElement, - PropsWithChildren ->(({ variant, children, color, ...props }, ref) => { +export const IconButtonLink: React.FC< + React.PropsWithChildren +> = ({ + variant = IconButtonVariant.Simple, + color = 'inherit', + size = '1em', + ref, + children, + ...props +}) => { const Comp = ComponentMap.get(variant ?? IconButtonVariant.Simple)!; return ( - + {children} ); -}); - -IconButtonLink.displayName = 'IconButtonLink'; - -IconButtonLink.defaultProps = defaultProps as IconButtonLinkProps; +}; interface ButtonBaseProps { size?: string; diff --git a/browser/data-browser/src/components/ImageViewer.tsx b/browser/data-browser/src/components/ImageViewer.tsx index f86791bf..b550c2b0 100644 --- a/browser/data-browser/src/components/ImageViewer.tsx +++ b/browser/data-browser/src/components/ImageViewer.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, type JSX } from 'react'; import { createPortal } from 'react-dom'; import { useHotkeys } from 'react-hotkeys-hook'; diff --git a/browser/data-browser/src/components/InlineFormattedResourceList.tsx b/browser/data-browser/src/components/InlineFormattedResourceList.tsx index 635af1c5..4a5a6c5a 100644 --- a/browser/data-browser/src/components/InlineFormattedResourceList.tsx +++ b/browser/data-browser/src/components/InlineFormattedResourceList.tsx @@ -1,5 +1,7 @@ import { ResourceInline } from '../views/ResourceInline'; +import type { JSX } from 'react'; + interface InlineFormattedResourceListProps { subjects: string[]; /** Optional component to render items instead of an inline resource */ diff --git a/browser/data-browser/src/components/Loader.tsx b/browser/data-browser/src/components/Loader.tsx index 39461035..13cfecd0 100644 --- a/browser/data-browser/src/components/Loader.tsx +++ b/browser/data-browser/src/components/Loader.tsx @@ -1,4 +1,5 @@ import { styled, keyframes } from 'styled-components'; +import { CurrentBackgroundColor } from '../globalCssVars'; const loadingAnimation = keyframes` from { @@ -11,13 +12,16 @@ const loadingAnimation = keyframes` export const LoaderInline = styled.span` --loader-bg-from: ${p => p.theme.colors.bg1}; - --loader-bg-to: ${p => p.theme.colors.bg}; + --loader-bg-to: ${CurrentBackgroundColor.var()}; background-color: ${p => p.theme.colors.bg1}; border-radius: ${p => p.theme.radius}; animation: ${loadingAnimation} 0.8s infinite ease-in-out alternate; - width: 100%; + flex: 1; display: inline-block; - height: 1rem; + padding: 0; + padding-inline: 1ch; + margin: 0; + color: ${p => p.theme.colors.textLight}; `; export const LoaderBlock = styled.div` diff --git a/browser/data-browser/src/components/Logo.tsx b/browser/data-browser/src/components/Logo.tsx index 8b079ed3..7c738b1b 100644 --- a/browser/data-browser/src/components/Logo.tsx +++ b/browser/data-browser/src/components/Logo.tsx @@ -1,5 +1,7 @@ import { useSettings } from '../helpers/AppSettings'; +import type { JSX } from 'react'; + interface LogoProps { style: React.CSSProperties; } diff --git a/browser/data-browser/src/components/Main.tsx b/browser/data-browser/src/components/Main.tsx index 82ffac34..8af90b1c 100644 --- a/browser/data-browser/src/components/Main.tsx +++ b/browser/data-browser/src/components/Main.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren, memo } from 'react'; +import { PropsWithChildren, memo, type JSX } from 'react'; import { VisuallyHidden } from './VisuallyHidden'; import { styled } from 'styled-components'; import { diff --git a/browser/data-browser/src/components/MetaSetter.tsx b/browser/data-browser/src/components/MetaSetter.tsx index 2418ee5d..7055050a 100644 --- a/browser/data-browser/src/components/MetaSetter.tsx +++ b/browser/data-browser/src/components/MetaSetter.tsx @@ -1,45 +1,44 @@ import { - properties, + core, unknownSubject, useResource, useString, useTitle, } from '@tomic/react'; -import { Helmet } from 'react-helmet-async'; import { useSettings } from '../helpers/AppSettings'; import { useCurrentSubject } from '../helpers/useCurrentSubject'; +import type { JSX } from 'react'; + /** Sets various HTML meta tags, depending on the currently opened resource */ export function MetaSetter(): JSX.Element { const { mainColor, darkMode } = useSettings(); const [subject] = useCurrentSubject(); const resource = useResource(subject); - let [title] = useTitle(resource); - let [description] = useString(resource, properties.description); - const hasResource = - resource.isReady() && resource.getSubject() !== unknownSubject; + const [title] = useTitle(resource); + const [description] = useString(resource, core.properties.description); + const hasResource = resource.isReady() && resource.subject !== unknownSubject; - title = hasResource && title ? title : 'Atomic Data'; - description = + const displayTitle = hasResource && title ? title : 'Atomic Data'; + const displayDescription = hasResource && description ? description : 'The easiest way to create and share linked data.'; return ( - - {title} - + <> + {displayTitle} - - - + + + - + ); } diff --git a/browser/data-browser/src/components/NavBarSpacer.tsx b/browser/data-browser/src/components/NavBarSpacer.tsx index 9f928616..38273a3a 100644 --- a/browser/data-browser/src/components/NavBarSpacer.tsx +++ b/browser/data-browser/src/components/NavBarSpacer.tsx @@ -1,6 +1,8 @@ import { styled } from 'styled-components'; import { useSettings } from '../helpers/AppSettings'; +import type { JSX } from 'react'; + const NAVBAR_HEIGHT = '2rem'; const NAVBAR_CALC_PART = ` + ${NAVBAR_HEIGHT}`; diff --git a/browser/data-browser/src/components/NavState.tsx b/browser/data-browser/src/components/NavState.tsx new file mode 100644 index 00000000..643f8225 --- /dev/null +++ b/browser/data-browser/src/components/NavState.tsx @@ -0,0 +1,56 @@ +import { createContext, useContext, useEffect, useState } from 'react'; + +export type NavState = 'PUSH' | 'POP' | 'NONE'; + +const NavStateContext = createContext('PUSH'); + +/** Tracks navigation to keep a record of the last navigation type. + * Useful if you need to know if the last navigation was a pop or push. + */ +export const NavStateProvider: React.FC = ({ + children, +}) => { + const [navState, setNavState] = useState('PUSH'); + + useEffect(() => { + const handlePopState = () => { + setNavState('POP'); + }; + + const oldPushState = window.history.pushState; + + const handlePageShow = (event: PageTransitionEvent) => { + if (!event.persisted) { + setNavState('NONE'); + } + }; + + window.addEventListener('popstate', handlePopState); + window.addEventListener('pageshow', handlePageShow); + + // Modify the pushState function so we can track when it is called. + window.history.pushState = function (...args) { + setNavState('PUSH'); + oldPushState.apply(this, args); + }; + + return () => { + window.removeEventListener('popstate', handlePopState); + window.removeEventListener('pageshow', handlePageShow); + window.history.pushState = oldPushState; + }; + }, []); + + return ( + + {children} + + ); +}; + +/** + * Returns the last navigation type. + */ +export const useNavState = (): NavState => { + return useContext(NavStateContext); +}; diff --git a/browser/data-browser/src/components/NavStyleButton.tsx b/browser/data-browser/src/components/NavStyleButton.tsx index 4f3a0d12..a5f1bca6 100644 --- a/browser/data-browser/src/components/NavStyleButton.tsx +++ b/browser/data-browser/src/components/NavStyleButton.tsx @@ -1,6 +1,8 @@ import { styled } from 'styled-components'; import { useSettings } from '../helpers/AppSettings'; +import type { JSX } from 'react'; + interface NavBarButtonProps { top: boolean; floating: boolean; diff --git a/browser/data-browser/src/components/Navigation.tsx b/browser/data-browser/src/components/Navigation.tsx index 1f3830a8..9be4d92b 100644 --- a/browser/data-browser/src/components/Navigation.tsx +++ b/browser/data-browser/src/components/Navigation.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; -import { useEffect } from 'react'; +import { type JSX } from 'react'; import { FaArrowLeft, FaArrowRight, FaBars } from 'react-icons/fa'; -import { useLocation } from 'react-router-dom'; import { styled } from 'styled-components'; import { ButtonBar } from './Button'; @@ -13,7 +12,7 @@ import { shortcuts } from './HotKeyWrapper'; import { NavBarSpacer } from './NavBarSpacer'; import { Searchbar } from './Searchbar'; import { useMediaQuery } from '../hooks/useMediaQuery'; -import { useNavigateWithTransition } from '../hooks/useNavigateWithTransition'; +import { useBackForward } from '../hooks/useNavigateWithTransition'; import { NAVBAR_TRANSITION_TAG } from '../helpers/transitionName'; interface NavWrapperProps { @@ -24,11 +23,6 @@ interface NavWrapperProps { export function NavWrapper({ children }: NavWrapperProps): JSX.Element { const { navbarTop, navbarFloating } = useSettings(); const contentRef = React.useRef(null); - const location = useLocation(); - - useEffect(() => { - contentRef?.current?.scrollTo(0, 0); - }, [location]); return ( <> @@ -62,8 +56,9 @@ const Content = styled.div` /** Persistently shown navigation bar */ function NavBar(): JSX.Element { + const { back, forward } = useBackForward(); + const [subject] = useCurrentSubject(); - const navigate = useNavigateWithTransition(); const { navbarTop, navbarFloating, sideBarLocked, setSideBarLocked } = useSettings(); const [showButtons, setShowButtons] = React.useState(true); @@ -110,18 +105,10 @@ function NavBar(): JSX.Element { {isInStandaloneMode && ( <> - navigate(-1)} - > + {' '} - navigate(1)} - > + diff --git a/browser/data-browser/src/components/NewInstanceButton/Base.tsx b/browser/data-browser/src/components/NewInstanceButton/Base.tsx index 50c317d5..91823629 100644 --- a/browser/data-browser/src/components/NewInstanceButton/Base.tsx +++ b/browser/data-browser/src/components/NewInstanceButton/Base.tsx @@ -1,12 +1,12 @@ import { useStore } from '@tomic/react'; -import { useCallback } from 'react'; +import { useCallback, type JSX } from 'react'; import toast from 'react-hot-toast'; import { IconType } from 'react-icons'; import { FaPlus } from 'react-icons/fa'; -import { useNavigate } from 'react-router-dom'; import { styled } from 'styled-components'; import { paths } from '../../routes/paths'; import { Button } from '../Button'; +import { useNavigateWithTransition } from '../../hooks/useNavigateWithTransition'; export interface InstanceButtonBaseProps { onClick: () => void; @@ -31,7 +31,7 @@ export function Base({ const store = useStore(); const agent = store.getAgent(); - const navigate = useNavigate(); + const navigate = useNavigateWithTransition(); const handleClick = useCallback(() => { if (!agent) { diff --git a/browser/data-browser/src/components/NewInstanceButton/NewInstanceButton.tsx b/browser/data-browser/src/components/NewInstanceButton/NewInstanceButton.tsx index 9f2249e3..ebb85211 100644 --- a/browser/data-browser/src/components/NewInstanceButton/NewInstanceButton.tsx +++ b/browser/data-browser/src/components/NewInstanceButton/NewInstanceButton.tsx @@ -5,6 +5,8 @@ import { useNewResourceUI } from '../forms/NewForm/useNewResourceUI'; import { Base } from './Base'; import { IconType } from 'react-icons'; +import type { JSX } from 'react'; + interface NewInstanceButtonProps { /** URL of the Class to be instantiated */ klass: string; diff --git a/browser/data-browser/src/components/OutlinedSection.tsx b/browser/data-browser/src/components/OutlinedSection.tsx index 2ab5b4a0..f1b61936 100644 --- a/browser/data-browser/src/components/OutlinedSection.tsx +++ b/browser/data-browser/src/components/OutlinedSection.tsx @@ -1,4 +1,4 @@ -import { PropsWithChildren } from 'react'; +import { PropsWithChildren, type JSX } from 'react'; import { Row } from './Row'; import { styled } from 'styled-components'; import { CurrentBackgroundColor } from '../globalCssVars'; diff --git a/browser/data-browser/src/components/PalettePicker.tsx b/browser/data-browser/src/components/PalettePicker.tsx index 5ac05a91..1ab763b9 100644 --- a/browser/data-browser/src/components/PalettePicker.tsx +++ b/browser/data-browser/src/components/PalettePicker.tsx @@ -2,6 +2,8 @@ import { styled } from 'styled-components'; import { transition } from '../helpers/transition'; import { Row } from './Row'; +import type { JSX } from 'react'; + interface PalettePickerProps { palette: string[]; onChange: (color: string) => void; diff --git a/browser/data-browser/src/components/Parent.tsx b/browser/data-browser/src/components/Parent.tsx index f3031074..02328306 100644 --- a/browser/data-browser/src/components/Parent.tsx +++ b/browser/data-browser/src/components/Parent.tsx @@ -13,9 +13,11 @@ import { useNavigateWithTransition } from '../hooks/useNavigateWithTransition'; import { useSettings } from '../helpers/AppSettings'; import { Button } from './Button'; import { BREADCRUMB_BAR_TRANSITION_TAG } from '../helpers/transitionName'; -import ResourceContextMenu from './ResourceContextMenu'; +import { ResourceContextMenu } from './ResourceContextMenu'; import { MenuBarDropdownTrigger } from './ResourceContextMenu/MenuBarDropdownTrigger'; +import type { JSX } from 'react'; + type ParentProps = { resource: Resource; }; diff --git a/browser/data-browser/src/components/Popover.tsx b/browser/data-browser/src/components/Popover.tsx index 57f85bfc..cef29844 100644 --- a/browser/data-browser/src/components/Popover.tsx +++ b/browser/data-browser/src/components/Popover.tsx @@ -9,6 +9,7 @@ import { useContext, useEffect, useRef, + type JSX, } from 'react'; import * as RadixPopover from '@radix-ui/react-popover'; import { styled, keyframes } from 'styled-components'; @@ -87,11 +88,11 @@ const fadeIn = keyframes` `; const Content = styled(RadixPopover.Content)` - --popover-close-offset: ${p => p.theme.margin}rem; + --popover-close-offset: ${p => p.theme.size()}; --popover-close-size: 25px; --popover-close-safe-area: calc( var(--popover-close-size) + (var(--popover-close-offset) * 2) - - ${p => p.theme.margin}rem + ${p => p.theme.size()} ); background-color: ${p => transparentize(0.2, p.theme.colors.bgBody)}; backdrop-filter: blur(10px); @@ -110,16 +111,16 @@ const Arrow = styled(RadixPopover.Arrow)` `; const PopoverContainerContext = - createContext>(createRef()); + createContext>(createRef()); export const PopoverContainer: FC = ({ children }) => { const popoverContainerRef = useRef(null); return ( - + {children} - + ); }; diff --git a/browser/data-browser/src/components/PropVal.tsx b/browser/data-browser/src/components/PropVal.tsx index bb310898..ca0f88c2 100644 --- a/browser/data-browser/src/components/PropVal.tsx +++ b/browser/data-browser/src/components/PropVal.tsx @@ -8,6 +8,8 @@ import ValueComp from './ValueComp'; import { ALL_PROPS_CONTAINER } from '../helpers/containers'; import { LoaderInline } from './Loader'; +import type { JSX } from 'react'; + type Props = { propertyURL: string; resource: Resource; diff --git a/browser/data-browser/src/components/ResourceContextMenu/MenuBarDropdownTrigger.tsx b/browser/data-browser/src/components/ResourceContextMenu/MenuBarDropdownTrigger.tsx index 4498699d..2d0fee96 100644 --- a/browser/data-browser/src/components/ResourceContextMenu/MenuBarDropdownTrigger.tsx +++ b/browser/data-browser/src/components/ResourceContextMenu/MenuBarDropdownTrigger.tsx @@ -1,12 +1,13 @@ import { FaEllipsisV } from 'react-icons/fa'; -import { DropdownTriggerRenderFunction } from '../Dropdown/DropdownTrigger'; +import { DropdownTriggerComponent } from '../Dropdown/DropdownTrigger'; import { shortcuts } from '../HotKeyWrapper'; import { IconButton } from '../IconButton/IconButton'; -export const MenuBarDropdownTrigger: DropdownTriggerRenderFunction = ( - { onClick, menuId }, +export const MenuBarDropdownTrigger: DropdownTriggerComponent = ({ + onClick, + menuId, ref, -) => ( +}) => ( - !isItem(item) || showOnly.includes(item.id as ContextMenuOptions), + !isItem(item) || + showOnly.includes(item.id as ContextMenuOptionsUnion), ) : items; @@ -229,7 +233,7 @@ function ResourceContextMenu({ <> @@ -258,5 +262,3 @@ function ResourceContextMenu({ ); } - -export default ResourceContextMenu; diff --git a/browser/data-browser/src/components/ResourceUsage/ChildrenUsage.tsx b/browser/data-browser/src/components/ResourceUsage/ChildrenUsage.tsx index 1a38a669..1e71d651 100644 --- a/browser/data-browser/src/components/ResourceUsage/ChildrenUsage.tsx +++ b/browser/data-browser/src/components/ResourceUsage/ChildrenUsage.tsx @@ -2,6 +2,8 @@ import { Resource, properties, useCollection } from '@tomic/react'; import { UsageCard } from './UsageCard'; +import type { JSX } from 'react'; + interface ChildrenUsageProps { resource: Resource; } diff --git a/browser/data-browser/src/components/ResourceUsage/ClassUsage.tsx b/browser/data-browser/src/components/ResourceUsage/ClassUsage.tsx index 1438c336..b89e3813 100644 --- a/browser/data-browser/src/components/ResourceUsage/ClassUsage.tsx +++ b/browser/data-browser/src/components/ResourceUsage/ClassUsage.tsx @@ -4,6 +4,8 @@ import { UsageCard } from './UsageCard'; import { Column } from '../Row'; import { ChildrenUsage } from './ChildrenUsage'; +import type { JSX } from 'react'; + interface ClassUsageProps { resource: Resource; } diff --git a/browser/data-browser/src/components/ResourceUsage/PropertyUsage.tsx b/browser/data-browser/src/components/ResourceUsage/PropertyUsage.tsx index 47e4c783..47bbfc34 100644 --- a/browser/data-browser/src/components/ResourceUsage/PropertyUsage.tsx +++ b/browser/data-browser/src/components/ResourceUsage/PropertyUsage.tsx @@ -4,6 +4,8 @@ import { UsageCard } from './UsageCard'; import { Column } from '../Row'; import { ChildrenUsage } from './ChildrenUsage'; +import type { JSX } from 'react'; + interface PropertyUsageProps { resource: Resource; } diff --git a/browser/data-browser/src/components/ResourceUsage/ResourceUsage.tsx b/browser/data-browser/src/components/ResourceUsage/ResourceUsage.tsx index b78d12af..695fc472 100644 --- a/browser/data-browser/src/components/ResourceUsage/ResourceUsage.tsx +++ b/browser/data-browser/src/components/ResourceUsage/ResourceUsage.tsx @@ -6,6 +6,8 @@ import { ChildrenUsage } from './ChildrenUsage'; import { Column } from '../Row'; import { ReferenceUsage } from './ReferenceUsage'; +import type { JSX } from 'react'; + interface ResourceUsageProps { resource: Resource; } diff --git a/browser/data-browser/src/components/ResourceUsage/UsageCard.tsx b/browser/data-browser/src/components/ResourceUsage/UsageCard.tsx index e0910eae..1860df1e 100644 --- a/browser/data-browser/src/components/ResourceUsage/UsageCard.tsx +++ b/browser/data-browser/src/components/ResourceUsage/UsageCard.tsx @@ -4,7 +4,7 @@ import { styled } from 'styled-components'; import { Details } from '../Details'; import { UsageRow } from './UsageRow'; import { Column, Row } from '../Row'; -import { useState } from 'react'; +import { useState, type JSX } from 'react'; import { FaChevronLeft, FaChevronRight } from 'react-icons/fa6'; import { IconButton } from '../IconButton/IconButton'; diff --git a/browser/data-browser/src/components/ResourceUsage/UsageRow.tsx b/browser/data-browser/src/components/ResourceUsage/UsageRow.tsx index abd56541..552adba6 100644 --- a/browser/data-browser/src/components/ResourceUsage/UsageRow.tsx +++ b/browser/data-browser/src/components/ResourceUsage/UsageRow.tsx @@ -4,6 +4,8 @@ import { ResourceInline } from '../../views/ResourceInline'; import { styled } from 'styled-components'; import { ErrorLook } from '../ErrorLook'; +import type { JSX } from 'react'; + interface UsageRowProps { subject: string; } diff --git a/browser/data-browser/src/components/Row.tsx b/browser/data-browser/src/components/Row.tsx index 26f04cd0..97c83e63 100644 --- a/browser/data-browser/src/components/Row.tsx +++ b/browser/data-browser/src/components/Row.tsx @@ -2,7 +2,7 @@ import { styled } from 'styled-components'; import * as CSS from 'csstype'; import { ButtonDefault } from './Button'; -import { forwardRef } from 'react'; +import { forwardRef, type JSX } from 'react'; export interface FlexProps extends React.HTMLAttributes { gap?: CSS.Property.Gap; diff --git a/browser/data-browser/src/components/ScrollArea.tsx b/browser/data-browser/src/components/ScrollArea.tsx index dbd3d0d5..c50f1037 100644 --- a/browser/data-browser/src/components/ScrollArea.tsx +++ b/browser/data-browser/src/components/ScrollArea.tsx @@ -1,7 +1,7 @@ import * as RadixScrollArea from '@radix-ui/react-scroll-area'; import { styled } from 'styled-components'; import { transparentize } from 'polished'; -import { forwardRef } from 'react'; +import { forwardRef, type JSX } from 'react'; const SIZE = '0.8rem'; diff --git a/browser/data-browser/src/components/Searchbar.tsx b/browser/data-browser/src/components/Searchbar.tsx index f303d8e1..a26b3abc 100644 --- a/browser/data-browser/src/components/Searchbar.tsx +++ b/browser/data-browser/src/components/Searchbar.tsx @@ -1,21 +1,18 @@ import { Client, useResource, useTitle } from '@tomic/react'; import { transparentize } from 'polished'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, type JSX } from 'react'; import { useHotkeys } from 'react-hotkeys-hook'; import { FaTimes } from 'react-icons/fa'; -import { useNavigate } from 'react-router'; import { styled } from 'styled-components'; -import { - constructOpenURL, - searchURL, - useSearchQuery, -} from '../helpers/navigation'; -import { useFocus } from '../helpers/useFocus'; +import { constructOpenURL } from '../helpers/navigation'; import { useQueryScopeHandler } from '../hooks/useQueryScope'; import { shortcuts } from './HotKeyWrapper'; import { IconButton, IconButtonVariant } from './IconButton/IconButton'; import { FaMagnifyingGlass } from 'react-icons/fa6'; import { isURL } from '../helpers/isURL'; +import { useNavigate, useSearch } from '@tanstack/react-router'; +import { paths } from '../routes/paths'; +import { useCurrentSubject } from '../helpers/useCurrentSubject'; export interface SearchbarProps { onFocus?: React.FocusEventHandler; @@ -28,13 +25,37 @@ export function Searchbar({ onBlur, subject, }: SearchbarProps): JSX.Element { - const [input, setInput] = useState(''); - const [query] = useSearchQuery(); + const [currentSubject] = useCurrentSubject(); + const { query } = useSearch({ strict: false }); + const [input, setInput] = useState(currentSubject ?? query ?? ''); const { scope, clearScope } = useQueryScopeHandler(); const searchBarRef = useRef(null); - const [inputRef, setInputFocus] = useFocus(); + const inputRef = useRef(null); + const navigate = useNavigate(); + const setQuery = useDebouncedCallback((q: string) => { + try { + Client.tryValidSubject(q); + // Replace instead of push to make the back-button behavior better. + navigate({ to: constructOpenURL(q), replace: true }); + } catch (_err) { + navigate({ + to: paths.search, + search: { + query: q, + ...(scope ? { queryscope: scope } : {}), + }, + replace: true, + }); + } + }, 20); + + const handleInput = (q: string) => { + setInput(q); + setQuery(q); + }; + const handleSelect: React.MouseEventHandler = e => { if (isURL(input ?? '')) { // @ts-ignore @@ -42,49 +63,36 @@ export function Searchbar({ } }; - const handleChange: React.ChangeEventHandler = e => { - setInput(e.target.value); - - try { - Client.tryValidSubject(e.target.value); - // Replace instead of push to make the back-button behavior better. - navigate(constructOpenURL(e.target.value), { replace: true }); - } catch (_err) { - navigate(searchURL(e.target.value, scope), { replace: true }); - } - }; - const handleSubmit: React.FormEventHandler = event => { if (!subject) { return; } event.preventDefault(); - //@ts-ignore this does seem callable - inputRef.current.blur(); - //@ts-ignore this does seem callable - document.activeElement.blur(); - navigate(constructOpenURL(subject)); + + inputRef.current?.blur(); + //@ts-expect-error This should work + document.activeElement?.blur(); + navigate({ to: constructOpenURL(subject), replace: true }); }; const onSearchButtonClick = () => { - navigate(searchURL('', scope), { replace: true }); - setInputFocus(); + navigate({ to: paths.search }); + inputRef.current?.focus(); }; useHotkeys(shortcuts.search, e => { e.preventDefault(); - //@ts-ignore this does seem callable - inputRef.current.select(); - setInputFocus(); + + inputRef.current?.select(); + inputRef.current?.focus(); }); useHotkeys( 'esc', e => { e.preventDefault(); - //@ts-ignore this does seem callable - inputRef.current.blur(); + inputRef.current?.blur(); }, { enableOnTags: ['INPUT'] }, ); @@ -102,16 +110,24 @@ export function Searchbar({ ); useEffect(() => { - if (query) { - setInput(query); - } else { - setInput(subject); + if (query !== undefined) { + return; + } + + if (scope !== undefined) { + setInput(''); + + return; } - if (query || scope) { - setInputFocus(); + if (currentSubject) { + setInput(currentSubject); + + return; } - }, [query, scope, subject]); + + setInput(''); + }, [query, scope, currentSubject]); return (
@@ -126,7 +142,6 @@ export function Searchbar({ {scope && } handleInput(e.target.value)} placeholder='Enter an Atomic URL or search (press "/" )' /> ); } +function useDebouncedCallback( + callback: (query: string) => void, + timeout: number, +): (query: string) => void { + const timeoutId = useRef>(undefined); + + const cb = (query: string) => { + if (timeoutId.current) { + clearTimeout(timeoutId.current); + } + + timeoutId.current = setTimeout(async () => { + callback(query); + }, timeout); + }; + + return cb; +} + interface ParentTagProps { subject: string; onClick: () => void; @@ -161,6 +195,7 @@ function ParentTag({ subject, onClick }: ParentTagProps): JSX.Element { variant={IconButtonVariant.Fill} color='textLight' size='0.8rem' + type='button' >
diff --git a/browser/data-browser/src/components/Shortcut.tsx b/browser/data-browser/src/components/Shortcut.tsx index c642c992..71f5fdf1 100644 --- a/browser/data-browser/src/components/Shortcut.tsx +++ b/browser/data-browser/src/components/Shortcut.tsx @@ -1,4 +1,4 @@ -import { Fragment } from 'react'; +import { Fragment, type JSX } from 'react'; import { styled } from 'styled-components'; import { displayShortcut } from './HotKeyWrapper'; diff --git a/browser/data-browser/src/components/SideBar/About.tsx b/browser/data-browser/src/components/SideBar/About.tsx index bfbe6341..bc065167 100644 --- a/browser/data-browser/src/components/SideBar/About.tsx +++ b/browser/data-browser/src/components/SideBar/About.tsx @@ -4,6 +4,7 @@ import { FaGithub, FaDiscord, FaBook } from 'react-icons/fa'; import { IconButtonLink, IconButtonVariant } from '../IconButton/IconButton'; import { FaRadiation } from 'react-icons/fa6'; import { isDev } from '../../config'; +import { paths } from '../../routes/paths'; interface AboutItem { icon: React.ReactNode; @@ -49,7 +50,7 @@ export function About() { ))} {isDev() && ( } label='Settings' helper='Change client settings (t)' - path={paths.themeSettings} + path={paths.appSettings} onClick={onItemClick} /> , 'Open Drive Settings'); @@ -30,7 +30,7 @@ function dedupeAFromB(a: Map, b: Map): Map { } export function DriveSwitcher() { - const navigate = useNavigate(); + const navigate = useNavigateWithTransition(); const { drive, setDrive, agent } = useSettings(); const [savedDrives] = useSavedDrives(); const [history, addToHistory] = useDriveHistory(savedDrives, 5); @@ -94,5 +94,5 @@ export function DriveSwitcher() { [savedDrivesMap, drive, historyMap, agent], ); - return ; + return ; } diff --git a/browser/data-browser/src/components/SideBar/OntologySideBar/OntologiesPanel.tsx b/browser/data-browser/src/components/SideBar/OntologySideBar/OntologiesPanel.tsx index 006fa474..b87f7833 100644 --- a/browser/data-browser/src/components/SideBar/OntologySideBar/OntologiesPanel.tsx +++ b/browser/data-browser/src/components/SideBar/OntologySideBar/OntologiesPanel.tsx @@ -12,7 +12,7 @@ import { AtomicLink } from '../../AtomicLink'; import { getIconForClass } from '../../../helpers/iconMap'; import { ScrollArea } from '../../ScrollArea'; import { ErrorLook } from '../../ErrorLook'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState, type JSX } from 'react'; import { useSettings } from '../../../helpers/AppSettings'; export function OntologiesPanel(): JSX.Element | null { diff --git a/browser/data-browser/src/components/SideBar/OverlapSpacer.tsx b/browser/data-browser/src/components/SideBar/OverlapSpacer.tsx index 378ba9c1..61cd78c9 100644 --- a/browser/data-browser/src/components/SideBar/OverlapSpacer.tsx +++ b/browser/data-browser/src/components/SideBar/OverlapSpacer.tsx @@ -3,6 +3,8 @@ import { useSettings } from '../../helpers/AppSettings'; import { styled } from 'styled-components'; import { transition } from '../../helpers/transition'; +import type { JSX } from 'react'; + export function OverlapSpacer(): JSX.Element { const narrow = useMediaQuery('(max-width: 950px)'); const { navbarFloating } = useSettings(); diff --git a/browser/data-browser/src/components/SideBar/ResourceSideBar/DropEdge.tsx b/browser/data-browser/src/components/SideBar/ResourceSideBar/DropEdge.tsx index 8c860198..6c095fd4 100644 --- a/browser/data-browser/src/components/SideBar/ResourceSideBar/DropEdge.tsx +++ b/browser/data-browser/src/components/SideBar/ResourceSideBar/DropEdge.tsx @@ -25,7 +25,7 @@ export function DropEdge({ const parentResource = useResource(parent); - const [canWrite] = useCanWrite(parentResource); + const canWrite = useCanWrite(parentResource); useDndMonitor({ onDragStart: event => setDraggingSubject(event.active.id as string), onDragEnd: () => setDraggingSubject(undefined), diff --git a/browser/data-browser/src/components/SideBar/ResourceSideBar/FloatingActions.tsx b/browser/data-browser/src/components/SideBar/ResourceSideBar/FloatingActions.tsx index 06b35ec3..5f0077c7 100644 --- a/browser/data-browser/src/components/SideBar/ResourceSideBar/FloatingActions.tsx +++ b/browser/data-browser/src/components/SideBar/ResourceSideBar/FloatingActions.tsx @@ -1,8 +1,8 @@ -import { useState } from 'react'; +import { useState, type JSX } from 'react'; import { FaEllipsisVertical } from 'react-icons/fa6'; import { styled, css } from 'styled-components'; import { buildDefaultTrigger } from '../../Dropdown/DefaultTrigger'; -import ResourceContextMenu from '../../ResourceContextMenu'; +import { ResourceContextMenu } from '../../ResourceContextMenu'; export interface FloatingActionsProps { subject: string; diff --git a/browser/data-browser/src/components/SideBar/ResourceSideBar/ResourceSideBar.tsx b/browser/data-browser/src/components/SideBar/ResourceSideBar/ResourceSideBar.tsx index 9dda134f..33cec0a1 100644 --- a/browser/data-browser/src/components/SideBar/ResourceSideBar/ResourceSideBar.tsx +++ b/browser/data-browser/src/components/SideBar/ResourceSideBar/ResourceSideBar.tsx @@ -27,7 +27,7 @@ import { transition } from '../../../helpers/transition'; interface ResourceSideBarProps { subject: string; - renderedHierargy: string[]; + renderedHierarchy: string[]; ancestry: string[]; /** When a SideBar item is clicked, we should close the SideBar (on mobile devices) */ onClick?: () => unknown; @@ -36,19 +36,19 @@ interface ResourceSideBarProps { /** Renders a Resource as a nav item for in the sidebar. */ export const ResourceSideBar: React.FC = ({ subject, - renderedHierargy, + renderedHierarchy, ancestry, onClick, }) => { - if (renderedHierargy.length === 0) { - throw new Error('renderedHierargy should not be empty'); + if (renderedHierarchy.length === 0) { + throw new Error('renderedHierarchy should not be empty'); } const resource = useResource(subject, { allowIncomplete: true }); const [currentUrl] = useCurrentSubject(); const [title] = useTitle(resource); const [description] = useString(resource, core.properties.description); - const [canWrite] = useCanWrite(resource); + const canWrite = useCanWrite(resource); const active = currentUrl === subject; const [open, setOpen] = useState(active); @@ -58,7 +58,7 @@ export const ResourceSideBar: React.FC = ({ ); const dragData: SideBarDragData = { - renderedUnder: renderedHierargy.at(-1)!, + renderedUnder: renderedHierarchy.at(-1)!, }; const { @@ -80,8 +80,8 @@ export const ResourceSideBar: React.FC = ({ active={active} onClick={onClick} ref={setNodeRef} - listeners={listeners} - attributes={attributes} + listeners={canWrite ? listeners : undefined} + attributes={canWrite ? attributes : undefined} /> ), [subject, active, onClick, description, title, listeners, attributes], @@ -90,7 +90,7 @@ export const ResourceSideBar: React.FC = ({ const hasSubResources = subResources.length > 0; const isDragging = draggingNode?.id === subject; const isHoveringOver = over?.data.current?.parent === subject; - const hierarchyWithItself = [...renderedHierargy, subject]; + const hierarchyWithItself = [...renderedHierarchy, subject]; useEffect(() => { if (isDragging) { @@ -102,7 +102,7 @@ export const ResourceSideBar: React.FC = ({ if (ancestry.includes(subject) && ancestry[0] !== subject) { setOpen(true); } - }, [ancestry]); + }, [ancestry, subject]); if (!subject || subject === unknownSubject) { return null; @@ -150,7 +150,7 @@ export const ResourceSideBar: React.FC = ({ diff --git a/browser/data-browser/src/components/SideBar/ResourceSideBar/SidebarItemTitle.tsx b/browser/data-browser/src/components/SideBar/ResourceSideBar/SidebarItemTitle.tsx index 3c906d02..aa484b2b 100644 --- a/browser/data-browser/src/components/SideBar/ResourceSideBar/SidebarItemTitle.tsx +++ b/browser/data-browser/src/components/SideBar/ResourceSideBar/SidebarItemTitle.tsx @@ -66,6 +66,7 @@ export const SidebarItemTitle = forwardRef< title={`Rearange ${resource.title}`} {...(listeners ?? {})} {...(attributes ?? {})} + role='link' > diff --git a/browser/data-browser/src/components/SideBar/SideBarDrive.tsx b/browser/data-browser/src/components/SideBar/SideBarDrive.tsx index d952731c..56f5ec30 100644 --- a/browser/data-browser/src/components/SideBar/SideBarDrive.tsx +++ b/browser/data-browser/src/components/SideBar/SideBarDrive.tsx @@ -6,9 +6,8 @@ import { useStore, useTitle, } from '@tomic/react'; -import { Fragment, useEffect, useState } from 'react'; +import { Fragment, useEffect, useState, type JSX } from 'react'; import { FaPlus } from 'react-icons/fa6'; -import { useNavigate } from 'react-router-dom'; import { styled } from 'styled-components'; import { useSettings } from '../../helpers/AppSettings'; import { constructOpenURL } from '../../helpers/navigation'; @@ -27,6 +26,7 @@ import { SidebarItemTitle } from './ResourceSideBar/SidebarItemTitle'; import { DropEdge } from './ResourceSideBar/DropEdge'; import { createPortal } from 'react-dom'; import { transition } from '../../helpers/transition'; +import { useNavigateWithTransition } from '../../hooks/useNavigateWithTransition'; interface SideBarDriveProps { onItemClick: () => unknown; @@ -55,8 +55,8 @@ export function SideBarDrive({ dataBrowser.properties.subResources, ); const [title] = useTitle(driveResource); - const navigate = useNavigate(); - const [agentCanWrite] = useCanWrite(driveResource); + const navigate = useNavigateWithTransition(); + const agentCanWrite = useCanWrite(driveResource); const [currentSubject] = useCurrentSubject(); const currentResource = useResource(currentSubject); const [ancestry, setAncestry] = useState([]); @@ -107,7 +107,7 @@ export function SideBarDrive({ diff --git a/browser/data-browser/src/components/SideBar/SideBarPanel.tsx b/browser/data-browser/src/components/SideBar/SideBarPanel.tsx index ed7b279d..2b0f21f7 100644 --- a/browser/data-browser/src/components/SideBar/SideBarPanel.tsx +++ b/browser/data-browser/src/components/SideBar/SideBarPanel.tsx @@ -2,7 +2,7 @@ import { styled } from 'styled-components'; import { Collapse } from '../Collapse'; import { FaCaretRight } from 'react-icons/fa'; import { transition } from '../../helpers/transition'; -import { useState } from 'react'; +import { useState, type JSX } from 'react'; interface SideBarPanelProps { title: string; diff --git a/browser/data-browser/src/components/SideBar/index.tsx b/browser/data-browser/src/components/SideBar/index.tsx index d6a5c054..0c1c1823 100644 --- a/browser/data-browser/src/components/SideBar/index.tsx +++ b/browser/data-browser/src/components/SideBar/index.tsx @@ -15,6 +15,7 @@ import { OntologiesPanel } from './OntologySideBar/OntologiesPanel'; import { SideBarPanel } from './SideBarPanel'; import { Panel, usePanelList } from './usePanelList'; import { SIDEBAR_WIDTH_PROP } from './SidebarCSSVars'; +import { useRef, type JSX } from 'react'; /** Amount of pixels where the sidebar automatically shows */ export const SIDEBAR_TOGGLE_WIDTH = 600; @@ -22,6 +23,7 @@ export const SIDEBAR_TOGGLE_WIDTH = 600; const SideBarDriveMemo = React.memo(SideBarDrive); export function SideBar(): JSX.Element { + const targetRef = useRef(null); const [isRearanging, setIsRearanging] = React.useState(false); const { drive, sideBarLocked, setSideBarLocked } = useSettings(); @@ -32,12 +34,12 @@ export function SideBar(): JSX.Element { true, ); - const { size, targetRef, dragAreaRef, isDragging, dragAreaListeners } = - useResizable({ - initialSize: 300, - minSize: 200, - maxSize: 2000, - }); + const { size, dragAreaRef, isDragging, dragAreaListeners } = useResizable({ + initialSize: 300, + minSize: 200, + maxSize: 2000, + targetRef, + }); const { enabledPanels } = usePanelList(); @@ -58,8 +60,7 @@ export function SideBar(): JSX.Element { return ( - {/* @ts-ignore */} - )} - + setSideBarLocked(false)} visible={sideBarLocked && !isWideScreen} @@ -107,7 +108,7 @@ export function SideBar(): JSX.Element { ); } -interface SideBarStyledProps { +interface StyledNavProps { locked: boolean; exposed: boolean; size: string; @@ -117,11 +118,10 @@ interface SideBarOverlayProps { visible: boolean; } -//@ts-ignore -const SideBarStyled = styled.nav.attrs(p => ({ +const StyledNav = styled.nav.attrs(p => ({ style: { [SIDEBAR_WIDTH_PROP]: p.size, - }, + } as Record, }))` z-index: ${p => p.theme.zIndex.sidebar}; box-sizing: border-box; diff --git a/browser/data-browser/src/components/SignInButton.tsx b/browser/data-browser/src/components/SignInButton.tsx index 5c1b55d0..d9bf18c6 100644 --- a/browser/data-browser/src/components/SignInButton.tsx +++ b/browser/data-browser/src/components/SignInButton.tsx @@ -1,13 +1,13 @@ -import { useNavigate } from 'react-router-dom'; import { paths } from '../routes/paths'; import { Button } from './Button'; +import { useNavigateWithTransition } from '../hooks/useNavigateWithTransition'; /** * Button that currently links to the Agent Settings page. Should probably open * in a Modal. */ export function SignInButton() { - const navigate = useNavigate(); + const navigate = useNavigateWithTransition(); return (