Skip to content

Commit 13a56d0

Browse files
committed
New approach with passing the react-router-dom version into RouterContextProvider
1 parent 2e9e07c commit 13a56d0

File tree

6 files changed

+113
-96
lines changed

6 files changed

+113
-96
lines changed

apps/design-system/src/AppRouterProvider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const AppRouterProvider: FC = () => {
4949
useSearchParams={useSearchParams}
5050
useMatches={useMatches}
5151
useParams={useParams}
52+
routerVersion=">v5"
5253
>
5354
<ComponentProvider components={{ RbacButton, RbacSplitButton: SplitButton }}>
5455
<Outlet />

apps/gitness/src/framework/context/AppRouterProvider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const AppRouterProvider: FC<AppRouterProviderProps> = ({ children }) => {
3030
useSearchParams={useSearchParams}
3131
useMatches={useMatches}
3232
useParams={useParams}
33+
routerVersion=">v5"
3334
>
3435
{children}
3536
</RouterContextProvider>

apps/portal/src/components/docs-page/example.tsx

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -74,52 +74,55 @@ const Example: FC<ExampleProps> = ({
7474
{
7575
path: "*",
7676
element: (
77-
<RouterContextProvider
78-
Link={Link}
79-
NavLink={NavLink}
80-
Outlet={Outlet}
81-
useMatches={useMatches}
82-
>
83-
<TranslationProvider>
84-
<LivePreview />
85-
</TranslationProvider>
86-
</RouterContextProvider>
77+
<TranslationProvider>
78+
<LivePreview />
79+
</TranslationProvider>
8780
),
8881
},
8982
]);
9083

9184
return (
92-
<TooltipProvider>
93-
<div className="bg-cn-1 not-content my-12 overflow-hidden rounded-md border">
94-
<LiveProvider
95-
code={currentCode}
96-
scope={scopeWithLayout}
97-
enableTypeScript
98-
>
99-
<div className={cn("grid place-items-center p-12", contentClassName)}>
100-
<RouterProvider router={router} />
101-
</div>
102-
{!hideCode && (
103-
<details className="relative example-expand bg-cn-2 border-t p-3">
104-
<CopyButton
105-
buttonVariant="transparent"
106-
className="absolute top-3 right-3"
107-
name={currentCode}
108-
/>
109-
<summary className="flex cursor-pointer select-none items-center gap-1 text-sm">
110-
<IconV2 name="nav-arrow-right" className="disclosure-icon" />
111-
Show code
112-
</summary>
113-
<LiveEditor
114-
theme={isLightTheme ? themes.vsLight : themes.vsDark}
115-
className="font-body-code line-numbers p-1 text-sm leading-6"
116-
onChange={setCurrentCode}
117-
/>
118-
</details>
119-
)}
120-
</LiveProvider>
121-
</div>
122-
</TooltipProvider>
85+
<RouterContextProvider
86+
Link={Link}
87+
NavLink={NavLink}
88+
Outlet={Outlet}
89+
useMatches={useMatches}
90+
routerVersion=">v5"
91+
>
92+
<TooltipProvider>
93+
<div className="bg-cn-1 not-content my-12 overflow-hidden rounded-md border">
94+
<LiveProvider
95+
code={currentCode}
96+
scope={scopeWithLayout}
97+
enableTypeScript
98+
>
99+
<div
100+
className={cn("grid place-items-center p-12", contentClassName)}
101+
>
102+
<RouterProvider router={router} />
103+
</div>
104+
{!hideCode && (
105+
<details className="relative example-expand bg-cn-2 border-t p-3">
106+
<CopyButton
107+
buttonVariant="transparent"
108+
className="absolute top-3 right-3"
109+
name={currentCode}
110+
/>
111+
<summary className="flex cursor-pointer select-none items-center gap-1 text-sm">
112+
<IconV2 name="nav-arrow-right" className="disclosure-icon" />
113+
Show code
114+
</summary>
115+
<LiveEditor
116+
theme={isLightTheme ? themes.vsLight : themes.vsDark}
117+
className="font-body-code line-numbers p-1 text-sm leading-6"
118+
onChange={setCurrentCode}
119+
/>
120+
</details>
121+
)}
122+
</LiveProvider>
123+
</div>
124+
</TooltipProvider>
125+
</RouterContextProvider>
123126
);
124127
};
125128

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { FC } from "react";
22

33
import { Table } from "@harnessio/ui/components";
4+
import { Link, NavLink, Outlet, useMatches } from "react-router-dom";
5+
import { RouterContextProvider } from "@harnessio/ui/context";
46

57
interface PropDescription {
68
name: string;
@@ -15,26 +17,34 @@ export interface PropsTableProps {
1517
}
1618

1719
const PropsTable: FC<PropsTableProps> = ({ props }) => (
18-
<Table.Root className="not-content">
19-
<Table.Header>
20-
<Table.Row>
21-
<Table.Head className="font-black">Prop</Table.Head>
22-
<Table.Head className="font-black">Required</Table.Head>
23-
<Table.Head className="font-black">Default</Table.Head>
24-
<Table.Head className="font-black">Type</Table.Head>
25-
</Table.Row>
26-
</Table.Header>
27-
<Table.Body>
28-
{props.map(({ name, required, defaultValue, description, value }) => (
29-
<Table.Row key={name}>
30-
<Table.Cell title={description}>{name}</Table.Cell>
31-
<Table.Cell>{required ? "true" : "false"}</Table.Cell>
32-
<Table.Cell className="font-body-code">{defaultValue}</Table.Cell>
33-
<Table.Cell className="font-body-code">{value}</Table.Cell>
20+
<RouterContextProvider
21+
Link={Link}
22+
NavLink={NavLink}
23+
Outlet={Outlet}
24+
useMatches={useMatches}
25+
routerVersion=">v5"
26+
>
27+
<Table.Root className="not-content">
28+
<Table.Header>
29+
<Table.Row>
30+
<Table.Head className="font-black">Prop</Table.Head>
31+
<Table.Head className="font-black">Required</Table.Head>
32+
<Table.Head className="font-black">Default</Table.Head>
33+
<Table.Head className="font-black">Type</Table.Head>
3434
</Table.Row>
35-
))}
36-
</Table.Body>
37-
</Table.Root>
35+
</Table.Header>
36+
<Table.Body>
37+
{props.map(({ name, required, defaultValue, description, value }) => (
38+
<Table.Row key={name}>
39+
<Table.Cell title={description}>{name}</Table.Cell>
40+
<Table.Cell>{required ? "true" : "false"}</Table.Cell>
41+
<Table.Cell className="font-body-code">{defaultValue}</Table.Cell>
42+
<Table.Cell className="font-body-code">{value}</Table.Cell>
43+
</Table.Row>
44+
))}
45+
</Table.Body>
46+
</Table.Root>
47+
</RouterContextProvider>
3848
);
3949

4050
export default PropsTable;

packages/ui/src/components/tabs.tsx

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,14 @@ import {
1313
useRef,
1414
useState
1515
} from 'react'
16-
import type { UIMatch } from 'react-router-dom'
16+
import type { NavLinkRenderProps } from 'react-router-dom'
1717

1818
import { CounterBadge } from '@/components/counter-badge'
1919
import { IconPropsV2, IconV2 } from '@/components/icon-v2'
2020
import { LogoPropsV2, LogoV2 } from '@/components/logo-v2'
2121
import { NavLinkProps, useRouterContext } from '@/context'
22-
import { afterFrames, getShadowActiveElement, useMergeRefs } from '@/utils'
22+
import { afterFrames, cn, getShadowActiveElement, useMergeRefs } from '@/utils'
2323
import * as TabsPrimitive from '@radix-ui/react-tabs'
24-
import { cn } from '@utils/cn'
2524
import { cva, type VariantProps } from 'class-variance-authority'
2625

2726
const tabsListVariants = cva('cn-tabs-list', {
@@ -262,33 +261,11 @@ interface TabsTriggerComponent {
262261
displayName?: string
263262
}
264263

265-
const normalize = (path: string) => {
266-
return path !== '/' && path.endsWith('/') ? path.slice(0, -1) : path
267-
}
268-
269-
/**
270-
* A method for determining the active tab for NavLink,
271-
* since we can’t use the native methods from react-router-dom due to different versions being used
272-
*/
273-
const isActivePath = (value: TabsTriggerBaseProps['value'], matches: UIMatch[]) => {
274-
const currentPath = normalize(
275-
matches.length
276-
? matches[matches.length - 1].pathname
277-
: typeof window !== 'undefined'
278-
? window.location.pathname
279-
: '/'
280-
)
281-
const targetPath = normalize(value)
282-
283-
return currentPath === targetPath || currentPath.endsWith(targetPath)
284-
}
285-
286264
const TabsTrigger = forwardRef<HTMLButtonElement | HTMLAnchorElement, TabsTriggerProps>((props, ref) => {
287265
const { className, children, value, icon, logo, counter, ...restProps } = props
288266
const { variant, activeClassName } = useContext(TabsListContext)
289267
const { type, activeTabValue, onValueChange } = useContext(TabsContext)
290-
const { NavLink, useMatches } = useRouterContext()
291-
const matches = useMatches()
268+
const { NavLink, routerVersion } = useRouterContext()
292269

293270
const iconSize = variant === 'ghost' || variant === 'outlined' ? 'xs' : 'sm'
294271
const logoSize: LogoPropsV2['size'] = 'xs'
@@ -303,7 +280,6 @@ const TabsTrigger = forwardRef<HTMLButtonElement | HTMLAnchorElement, TabsTrigge
303280
)
304281

305282
if (type === 'tabsnav') {
306-
const isActive = isActivePath(value, matches)
307283
const { linkProps, disabled, ..._restProps } = restProps as TabsTriggerLinkProps
308284

309285
const handleClick = (e: MouseEvent) => {
@@ -315,17 +291,29 @@ const TabsTrigger = forwardRef<HTMLButtonElement | HTMLAnchorElement, TabsTrigge
315291
onValueChange?.(value)
316292
}
317293

294+
const versionsProps =
295+
routerVersion === 'v5'
296+
? {
297+
activeClassName: `cn-tabs-trigger-active ${activeClassName}`,
298+
className: cn(tabsTriggerVariants({ variant }), className)
299+
}
300+
: {
301+
className: ({ isActive }: NavLinkRenderProps) => {
302+
return cn(
303+
tabsTriggerVariants({ variant }),
304+
{ 'cn-tabs-trigger-active': isActive, [activeClassName ?? '']: isActive },
305+
className
306+
)
307+
}
308+
}
309+
318310
return (
319311
<NavLink
320312
role="tab"
321313
to={value}
322314
onClick={handleClick}
323315
aria-disabled={disabled}
324-
className={cn(
325-
tabsTriggerVariants({ variant }),
326-
{ 'cn-tabs-trigger-active': isActive, [activeClassName ?? '']: isActive },
327-
className
328-
)}
316+
{...versionsProps}
329317
{...(linkProps as Omit<NavLinkProps, 'to' | 'className'>)}
330318
{...(_restProps as Omit<ComponentPropsWithoutRef<'a'>, 'href' | 'className'>)}
331319
ref={ref as Ref<HTMLAnchorElement>}

packages/ui/src/context/router-context.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,12 @@ interface RouterContextType {
138138
useSearchParams: typeof useSearchParamsDefault
139139
useMatches: typeof useMatchesDefault
140140
useParams: typeof useParamsDefault
141+
routerVersion?: 'v5' | '>v5'
141142
}
142143

144+
type RouterProviderType = Partial<Omit<RouterContextType, 'routerVersion'>> &
145+
Required<Pick<RouterContextType, 'routerVersion'>>
146+
143147
const RouterContext = createContext<RouterContextType>({
144148
Link: LinkDefault,
145149
NavLink: NavLinkDefault,
@@ -153,7 +157,15 @@ const RouterContext = createContext<RouterContextType>({
153157
useParams: useParamsDefault
154158
})
155159

156-
export const useRouterContext = () => useContext(RouterContext)
160+
export const useRouterContext = () => {
161+
const ctx = useContext(RouterContext)
162+
163+
if (!ctx?.routerVersion) {
164+
throw new Error('routerVersion must be provided via RouterContextProvider')
165+
}
166+
167+
return ctx as Required<RouterProviderType>
168+
}
157169

158170
export const RouterContextProvider = ({
159171
children,
@@ -166,10 +178,11 @@ export const RouterContextProvider = ({
166178
navigate = navigateFnDefault,
167179
useSearchParams = useSearchParamsDefault,
168180
useMatches = useMatchesDefault,
169-
useParams = useParamsDefault
181+
useParams = useParamsDefault,
182+
routerVersion
170183
}: {
171184
children: ReactNode
172-
} & Partial<RouterContextType>) => {
185+
} & RouterProviderType) => {
173186
return (
174187
<RouterContext.Provider
175188
value={{
@@ -182,7 +195,8 @@ export const RouterContextProvider = ({
182195
navigate,
183196
useSearchParams,
184197
useMatches,
185-
useParams
198+
useParams,
199+
routerVersion
186200
}}
187201
>
188202
<FiltersRouterContextProvider location={location} navigate={navigate}>

0 commit comments

Comments
 (0)