|
7 | 7 | type LoaderFunction, |
8 | 8 | type MetaFunction, |
9 | 9 | type NonIndexRouteObject, |
10 | | - createRoutesStub, |
| 10 | + RouterProvider, |
| 11 | + createMemoryRouter, |
11 | 12 | } from 'react-router-dom'; |
12 | 13 |
|
13 | 14 | export type StubRouteObject = StubIndexRouteObject | StubNonIndexRouteObject; |
@@ -36,21 +37,49 @@ interface StubIndexRouteObject |
36 | 37 |
|
37 | 38 | interface RemixStubOptions { |
38 | 39 | routes: StubRouteObject[]; |
| 40 | + initialPath?: string; |
39 | 41 | } |
40 | 42 |
|
41 | 43 | export const withReactRouterStubDecorator = (options: RemixStubOptions): Decorator => { |
42 | | - const { routes } = options; |
43 | | - return (Story) => { |
44 | | - // Map routes to include Story component as fallback if no Component provided |
| 44 | + const { routes, initialPath = '/' } = options; |
| 45 | + // This outer function runs once when Storybook loads the story meta |
| 46 | + |
| 47 | + return (Story, context) => { |
| 48 | + // This inner function runs when the story component actually renders |
45 | 49 | const mappedRoutes = routes.map((route) => ({ |
46 | 50 | ...route, |
47 | | - Component: route.Component ?? (() => <Story />), |
| 51 | + Component: route.Component ?? (() => <Story {...context.args} />), |
48 | 52 | })); |
49 | 53 |
|
50 | | - // Use more specific type assertion to fix the incompatibility |
51 | | - // @ts-ignore - Types from createRoutesStub are incompatible but the code works at runtime |
52 | | - const RemixStub = createRoutesStub(mappedRoutes); |
| 54 | + // Get the base path (without existing query params from options) |
| 55 | + const basePath = initialPath.split('?')[0]; |
| 56 | + |
| 57 | + // Get the current search string from the actual browser window, if available |
| 58 | + // If not available, use a default search string with parameters needed for the data table |
| 59 | + const currentWindowSearch = typeof window !== 'undefined' |
| 60 | + ? window.location.search |
| 61 | + : '?page=0&pageSize=10'; |
| 62 | + |
| 63 | + // Combine them for the initial entry |
| 64 | + const actualInitialPath = `${basePath}${currentWindowSearch}`; |
| 65 | + |
| 66 | + // Create a memory router, initializing it with the path derived from the window's search params |
| 67 | + // biome-ignore lint/suspicious/noExplicitAny: <explanation> |
| 68 | + const router = createMemoryRouter(mappedRoutes as any, { |
| 69 | + initialEntries: [actualInitialPath], // Use the path combined with window.location.search |
| 70 | + }); |
53 | 71 |
|
54 | | - return <RemixStub initialEntries={['/']} />; |
| 72 | + return <RouterProvider router={router} />; |
55 | 73 | }; |
56 | 74 | }; |
| 75 | + |
| 76 | +/** |
| 77 | + * A decorator that provides URL state management for stories |
| 78 | + * Use this when you need URL query parameters in your stories |
| 79 | + */ |
| 80 | +export const withURLState = (initialPath = '/'): Decorator => { |
| 81 | + return withReactRouterStubDecorator({ |
| 82 | + routes: [{ path: '/' }], |
| 83 | + initialPath, |
| 84 | + }); |
| 85 | +}; |
0 commit comments