Skip to content

Commit

Permalink
apply router changes from #23292
Browse files Browse the repository at this point in the history
  • Loading branch information
ndelangen committed Jul 4, 2023
1 parent 683c480 commit f28c739
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 36 deletions.
109 changes: 74 additions & 35 deletions code/lib/router/src/router.tsx
@@ -1,6 +1,6 @@
import { global } from '@storybook/global';
import React, { useCallback } from 'react';
import type { ReactNode } from 'react';
import type { ReactNode, ReactElement, ComponentProps } from 'react';

import * as R from 'react-router-dom';
import { ToggleVisibility } from './visibility';
Expand All @@ -16,23 +16,33 @@ interface MatchingData {
interface LocationProps {
children: (renderData: RenderData) => any;
}
interface MatchProps {

interface MatchPropsStartsWith {
path: string;
startsWith: boolean;
startsWith: true;
children: (matchingData: MatchingData) => ReactNode;
}
interface MatchPropsDefault {
path: RegExp;
startsWith: false;
children: (matchingData: MatchingData) => ReactNode;
}
interface RouteProps {

interface RoutePropsStartsWith {
path: string;
startsWith?: boolean;
startsWith: true;
hideOnly?: boolean;
children: ReactNode;
}
interface RoutePropsDefault {
path: RegExp;
startsWith?: false;
hideOnly?: boolean;
children: ReactNode;
}

const getBase = () => `${document.location.pathname}?`;

// const queryNavigate: NavigateFn = (to: string | number, options?: NavigateOptions<{}>) =>
// typeof to === 'number' ? navigate(to) : navigate(`${getBase()}path=${to}`, options);

export const useNavigate = () => {
const navigate = R.useNavigate();

Expand All @@ -53,16 +63,20 @@ export const useNavigate = () => {
}, []);
};

// A component that will navigate to a new location/path when clicked
/**
* A component that will navigate to a new location/path when clicked
*/
export const Link = ({ to, children, ...rest }: LinkProps) => (
<R.Link to={`${getBase()}path=${to}`} {...rest}>
{children}
</R.Link>
);
Link.displayName = 'QueryLink';

// A render-prop component where children is called with a location
// and will be called whenever it changes when it changes
/**
* A render-prop component where children is called with a location
* and will be called whenever it changes when it changes
*/
export const Location = ({ children }: LocationProps) => {
const location = R.useLocation();
const { path, singleStory } = queryFromString(location.search);
Expand All @@ -83,33 +97,58 @@ export const Location = ({ children }: LocationProps) => {
};
Location.displayName = 'QueryLocation';

// A render-prop component for rendering when a certain path is hit.
// It's immensely similar to `Location` but it receives an addition data property: `match`.
// match has a truthy value when the path is hit.
export const Match = ({ children, path: targetPath, startsWith = false }: MatchProps) => (
<Location>
{({ path: urlPath, ...rest }) =>
children({
match: getMatch(urlPath, targetPath, startsWith),
...rest,
})
}
</Location>
);
/**
* A render-prop component for rendering when a certain path is hit.
* It's immensely similar to `Location` but it receives an addition data property: `match`.
* match has a truthy value when the path is hit.
*/
function Match(props: MatchPropsStartsWith): ReactElement;
function Match(props: MatchPropsDefault): ReactElement;
function Match({
children,
path: targetPath,
startsWith = false,
}: MatchPropsStartsWith | MatchPropsDefault) {
return (
<Location>
{({ path: urlPath, ...rest }) =>
children({
match: getMatch(urlPath, targetPath, startsWith),
...rest,
})
}
</Location>
);
}
Match.displayName = 'QueryMatch';

// A component to conditionally render children based on matching a target path
export const Route = ({ path, children, startsWith = false, hideOnly = false }: RouteProps) => (
<Match path={path} startsWith={startsWith}>
{({ match }) => {
if (hideOnly) {
return <ToggleVisibility hidden={!match}>{children}</ToggleVisibility>;
}
return match ? children : null;
}}
</Match>
);
/**
* A component to conditionally render children based on matching a target path
*/
function Route(props: RoutePropsDefault): ReactElement;
function Route(props: RoutePropsStartsWith): ReactElement;
function Route(input: RoutePropsDefault | RoutePropsStartsWith) {
const { children, hideOnly, ...rest } = input;
if (rest.startsWith === undefined) {
rest.startsWith = false;
}

const matchProps = rest as Omit<ComponentProps<typeof Match>, 'children'>;

return (
<Match {...matchProps}>
{({ match }) => {
if (hideOnly) {
return <ToggleVisibility hidden={!match}>{children}</ToggleVisibility>;
}
return match ? children : null;
}}
</Match>
);
}
Route.displayName = 'Route';

export { Route, Match };

export const LocationProvider: typeof R.BrowserRouter = (...args) => R.BrowserRouter(...args);
export const BaseLocationProvider: typeof R.Router = (...args) => R.Router(...args);
5 changes: 4 additions & 1 deletion code/lib/router/src/utils.ts
Expand Up @@ -158,8 +158,11 @@ export const stringifyQuery = (query: Query) =>
type Match = { path: string };

export const getMatch = memoize(1000)(
(current: string, target: string, startsWith = true): Match | null => {
(current: string, target: string | RegExp, startsWith = true): Match | null => {
if (startsWith) {
if (typeof target !== 'string') {
throw new Error('startsWith only works with string targets');
}
const startsWithTarget = current && current.startsWith(target);
if (startsWithTarget) {
return { path: current };
Expand Down

0 comments on commit f28c739

Please sign in to comment.