diff --git a/static/app/components/core/link/link.tsx b/static/app/components/core/link/link.tsx index 96aac7c230967a..5184dd7ce78d0d 100644 --- a/static/app/components/core/link/link.tsx +++ b/static/app/components/core/link/link.tsx @@ -1,15 +1,10 @@ -import { - Link as RouterLink, - type LinkProps as ReactRouterLinkProps, -} from 'react-router-dom'; +import {type LinkProps as ReactRouterLinkProps} from 'react-router-dom'; import isPropValid from '@emotion/is-prop-valid'; import {css, type Theme} from '@emotion/react'; import styled from '@emotion/styled'; import type {LocationDescriptor} from 'history'; -import {locationDescriptorToTo} from 'sentry/utils/reactRouter6Compat/location'; -import normalizeUrl from 'sentry/utils/url/normalizeUrl'; -import {useLocation} from 'sentry/utils/useLocation'; +import {useLinkBehavior} from './linkBehaviorContext'; export interface LinkProps extends React.RefAttributes, @@ -61,26 +56,19 @@ const getLinkStyles = ({ `; const Anchor = styled('a', { - shouldForwardProp: prop => - typeof prop === 'string' && isPropValid(prop) && prop !== 'disabled', + shouldForwardProp: prop => isPropValid(prop) && prop !== 'disabled', })<{disabled?: LinkProps['disabled']}>` ${getLinkStyles} `; -/** - * A context-aware version of Link (from react-router) that falls - * back to if there is no router present - */ -export const Link = styled(({disabled, to, ...props}: LinkProps) => { - const location = useLocation(); +export const Link = styled((props: LinkProps) => { + const {Component, behavior} = useLinkBehavior(props); - if (disabled || !location) { + if (props.disabled || Component === 'a') { return ; } - return ( - - ); + return ; })` ${getLinkStyles} `; diff --git a/static/app/components/core/link/linkBehaviorContext.tsx b/static/app/components/core/link/linkBehaviorContext.tsx new file mode 100644 index 00000000000000..d11f8d9d2fd56a --- /dev/null +++ b/static/app/components/core/link/linkBehaviorContext.tsx @@ -0,0 +1,19 @@ +import {createContext, useContext, type FunctionComponent} from 'react'; + +import type {LinkProps} from './link'; + +const LinkBehaviorContext = createContext<{ + behavior: (props: LinkProps) => LinkProps; + component: FunctionComponent | 'a'; +}>({ + component: 'a', + behavior: props => props, +}); + +export const LinkBehaviorContextProvider = LinkBehaviorContext.Provider; + +export const useLinkBehavior = (props: LinkProps) => { + const {component, behavior} = useContext(LinkBehaviorContext); + + return {Component: component, behavior: () => behavior(props)}; +}; diff --git a/static/app/main.tsx b/static/app/main.tsx index 6b20c680bc9e70..48c11ec7a6c044 100644 --- a/static/app/main.tsx +++ b/static/app/main.tsx @@ -10,7 +10,6 @@ import {FrontendVersionProvider} from 'sentry/components/frontendVersionContext' import {ThemeAndStyleProvider} from 'sentry/components/themeAndStyleProvider'; import {SENTRY_RELEASE_VERSION, USE_REACT_QUERY_DEVTOOL} from 'sentry/constants'; import {routes} from 'sentry/routes'; -import {SentryTrackingProvider} from 'sentry/tracking'; import {DANGEROUS_SET_REACT_ROUTER_6_HISTORY} from 'sentry/utils/browserHistory'; function buildRouter() { @@ -28,13 +27,11 @@ function Main() { - - - - - - - + + + + + {USE_REACT_QUERY_DEVTOOL && ( )} diff --git a/static/app/routes.tsx b/static/app/routes.tsx index 9354b9bed5a6e9..1ce1aa33d0748b 100644 --- a/static/app/routes.tsx +++ b/static/app/routes.tsx @@ -3,6 +3,7 @@ import memoize from 'lodash/memoize'; import {EXPERIMENTAL_SPA} from 'sentry/constants'; import {t} from 'sentry/locale'; +import {ScrapsProviders} from 'sentry/scrapsProviders'; import HookStore from 'sentry/stores/hookStore'; import type {HookName} from 'sentry/types/hooks'; import errorHandler from 'sentry/utils/errorHandler'; @@ -3066,7 +3067,13 @@ function buildRoutes(): RouteObject[] { }; const appRoutes: SentryRouteObject = { - component: ProvideAriaRouter, + component: ({children}: {children: React.ReactNode}) => { + return ( + + {children} + + ); + }, deprecatedRouteProps: true, children: [ experimentalSpaRoutes, diff --git a/static/app/scrapsProviders/index.tsx b/static/app/scrapsProviders/index.tsx new file mode 100644 index 00000000000000..1e4013d968e02e --- /dev/null +++ b/static/app/scrapsProviders/index.tsx @@ -0,0 +1,10 @@ +import {SentryLinkBehaviorProvider} from './link'; +import {SentryTrackingProvider} from './tracking'; + +export function ScrapsProviders({children}: {children: React.ReactNode}) { + return ( + + {children} + + ); +} diff --git a/static/app/scrapsProviders/link.tsx b/static/app/scrapsProviders/link.tsx new file mode 100644 index 00000000000000..36bb61fc0165ee --- /dev/null +++ b/static/app/scrapsProviders/link.tsx @@ -0,0 +1,34 @@ +import {useMemo} from 'react'; +import {Link as RouterLink} from 'react-router-dom'; + +import type {LinkProps} from '@sentry/scraps/link'; +import {LinkBehaviorContextProvider} from '@sentry/scraps/link/linkBehaviorContext'; + +import {locationDescriptorToTo} from 'sentry/utils/reactRouter6Compat/location'; +import normalizeUrl from 'sentry/utils/url/normalizeUrl'; +import {useLocation} from 'sentry/utils/useLocation'; + +export function SentryLinkBehaviorProvider({children}: {children: React.ReactNode}) { + const location = useLocation(); + + return ( + ({ + component: RouterLink, + behavior: ({to, ...props}: LinkProps) => { + const normalizedTo = locationDescriptorToTo(normalizeUrl(to, location)); + + return { + to: normalizedTo, + ...props, + }; + }, + }), + [location] + )} + > + {children} + + ); +} diff --git a/static/app/tracking.tsx b/static/app/scrapsProviders/tracking.tsx similarity index 100% rename from static/app/tracking.tsx rename to static/app/scrapsProviders/tracking.tsx diff --git a/tests/js/sentry-test/reactTestingLibrary.tsx b/tests/js/sentry-test/reactTestingLibrary.tsx index 9f2766b7965a2f..ff6258db8bcf4a 100644 --- a/tests/js/sentry-test/reactTestingLibrary.tsx +++ b/tests/js/sentry-test/reactTestingLibrary.tsx @@ -23,8 +23,6 @@ import * as qs from 'query-string'; import {LocationFixture} from 'sentry-fixture/locationFixture'; import {ThemeFixture} from 'sentry-fixture/theme'; -import {makeTestQueryClient} from 'sentry-test/queryClient'; - import {CommandPaletteProvider} from 'sentry/components/commandPalette/context'; import {GlobalDrawer} from 'sentry/components/globalDrawer'; import GlobalModal from 'sentry/components/globalModal'; @@ -43,6 +41,8 @@ import {instrumentUserEvent} from '../instrumentedEnv/userEventIntegration'; import {initializeOrg} from './initializeOrg'; import {SentryNuqsTestingAdapter} from './nuqsTestingAdapter'; +import {makeTestQueryClient} from './queryClient'; +import {ScrapsTestingProviders} from './scrapsTestingProviders'; interface ProviderOptions { /** @@ -218,9 +218,11 @@ function makeAllTheProviders(options: ProviderOptions) { - - {wrappedContent} - + + + {wrappedContent} + + diff --git a/tests/js/sentry-test/scrapsTestingProviders.tsx b/tests/js/sentry-test/scrapsTestingProviders.tsx new file mode 100644 index 00000000000000..f1116a22dde428 --- /dev/null +++ b/tests/js/sentry-test/scrapsTestingProviders.tsx @@ -0,0 +1,5 @@ +import {SentryLinkBehaviorProvider} from 'sentry/scrapsProviders/link'; + +export function ScrapsTestingProviders({children}: {children: React.ReactNode}) { + return {children}; +}