/
router.tsx
148 lines (127 loc) · 4.01 KB
/
router.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import global from 'global';
import React, { ReactNode, useCallback } from 'react';
import {
Link,
BrowserRouter,
useNavigate,
useLocation,
NavigateOptions,
Router,
} from 'react-router-dom';
import { ToggleVisibility } from './visibility';
import { queryFromString, parsePath, getMatch, StoryData } from './utils';
const { document } = global;
interface Other extends StoryData {
path: string;
singleStory?: boolean;
}
export type RouterData = {
location: Partial<Location>;
navigate: ReturnType<typeof useQueryNavigate>;
} & Other;
export type RenderData = Pick<RouterData, 'location'> & Other;
interface MatchingData {
match: null | { path: string };
}
interface QueryLocationProps {
children: (renderData: RenderData) => ReactNode;
}
interface QueryMatchProps {
path: string;
startsWith: boolean;
children: (matchingData: MatchingData) => ReactNode;
}
interface RouteProps {
path: string;
startsWith?: boolean;
hideOnly?: boolean;
children: ReactNode;
}
export interface QueryLinkProps {
to: string;
children: ReactNode;
}
const getBase = () => `${document.location.pathname}?`;
type ExpandedNavigateOptions = NavigateOptions & { plain?: boolean };
// const queryNavigate: NavigateFn = (to: string | number, options?: NavigateOptions<{}>) =>
// typeof to === 'number' ? navigate(to) : navigate(`${getBase()}path=${to}`, options);
const useQueryNavigate = () => {
const navigate = useNavigate();
return useCallback((to: string | number, options?: ExpandedNavigateOptions) => {
if (typeof to === 'string' && to.startsWith('#')) {
document.location.hash = to;
return undefined;
}
if (typeof to === 'string') {
const target = options?.plain ? to : `?path=${to}`;
return navigate(target, options);
}
if (typeof to === 'number') {
return navigate(to);
}
return undefined;
}, []);
};
// A component that will navigate to a new location/path when clicked
const QueryLink = ({ to, children, ...rest }: QueryLinkProps) => (
<Link to={`${getBase()}path=${to}`} {...rest}>
{children}
</Link>
);
QueryLink.displayName = 'QueryLink';
// A render-prop component where children is called with a location
// and will be called whenever it changes when it changes
const QueryLocation = ({ children }: QueryLocationProps) => {
const location = useLocation();
const { path, singleStory } = queryFromString(location.search);
const { viewMode, storyId, refId } = parsePath(path);
return (
<>
{children({
path,
location,
viewMode,
storyId,
refId,
singleStory: singleStory === 'true',
})}
</>
);
};
QueryLocation.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.
const QueryMatch = ({ children, path: targetPath, startsWith = false }: QueryMatchProps) => (
<QueryLocation>
{({ path: urlPath, ...rest }) =>
children({
match: getMatch(urlPath, targetPath, startsWith),
...rest,
})
}
</QueryLocation>
);
QueryMatch.displayName = 'QueryMatch';
// A component to conditionally render children based on matching a target path
const Route = ({ path, children, startsWith = false, hideOnly = false }: RouteProps) => (
<QueryMatch path={path} startsWith={startsWith}>
{({ match }) => {
if (hideOnly) {
return <ToggleVisibility hidden={!match}>{children}</ToggleVisibility>;
}
return match ? children : null;
}}
</QueryMatch>
);
Route.displayName = 'Route';
export { QueryLink as Link };
export { QueryMatch as Match };
export { QueryLocation as Location };
export { Route };
export { useQueryNavigate as useNavigate };
export { BrowserRouter as LocationProvider };
export { Router as BaseLocationProvider };
export { useNavigate as usePlainNavigate };
// eslint-disable-next-line no-undef
export type { ExpandedNavigateOptions as NavigateOptions };