router-kit 1.3.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -373
- package/dist/components/Link.d.ts +23 -6
- package/dist/components/Link.js +51 -6
- package/dist/components/NavLink.d.ts +44 -7
- package/dist/components/NavLink.js +111 -10
- package/dist/components/Outlet.d.ts +66 -0
- package/dist/components/Outlet.js +69 -0
- package/dist/components/Router.d.ts +69 -0
- package/dist/components/Router.js +109 -0
- package/dist/components/route.d.ts +77 -0
- package/dist/components/route.js +51 -0
- package/dist/context/OutletContext.d.ts +41 -0
- package/dist/context/OutletContext.js +31 -0
- package/dist/context/RouterContext.d.ts +9 -0
- package/dist/context/RouterContext.js +21 -1
- package/dist/context/RouterProvider.d.ts +15 -4
- package/dist/context/RouterProvider.js +321 -84
- package/dist/core/createRouter.d.ts +65 -0
- package/dist/core/createRouter.js +126 -7
- package/dist/hooks/useBlocker.d.ts +65 -0
- package/dist/hooks/useBlocker.js +152 -0
- package/dist/hooks/useDynamicComponents.d.ts +61 -2
- package/dist/hooks/useDynamicComponents.js +89 -17
- package/dist/hooks/useLoaderData.d.ts +98 -0
- package/dist/hooks/useLoaderData.js +107 -0
- package/dist/hooks/useLocation.d.ts +37 -0
- package/dist/hooks/useLocation.js +106 -1
- package/dist/hooks/useMatches.d.ts +99 -0
- package/dist/hooks/useMatches.js +114 -0
- package/dist/hooks/useNavigate.d.ts +59 -0
- package/dist/hooks/useNavigate.js +70 -0
- package/dist/hooks/useParams.d.ts +57 -2
- package/dist/hooks/useParams.js +60 -14
- package/dist/hooks/useQuery.d.ts +53 -3
- package/dist/hooks/useQuery.js +107 -8
- package/dist/hooks/useRouter.d.ts +34 -0
- package/dist/hooks/useRouter.js +35 -1
- package/dist/index.d.ts +19 -5
- package/dist/index.js +23 -4
- package/dist/ssr/StaticRouter.d.ts +65 -0
- package/dist/ssr/StaticRouter.js +292 -0
- package/dist/ssr/hydrateRouter.d.ts +44 -0
- package/dist/ssr/hydrateRouter.js +60 -0
- package/dist/ssr/index.d.ts +92 -0
- package/dist/ssr/index.js +92 -0
- package/dist/ssr/serverUtils.d.ts +107 -0
- package/dist/ssr/serverUtils.js +263 -0
- package/dist/types/index.d.ts +201 -2
- package/package.json +14 -2
|
@@ -1,8 +1,45 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import type { NavLinkProps } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* NavLink component for navigation with active state styling
|
|
4
|
+
*
|
|
5
|
+
* Extends Link with automatic active state detection and styling.
|
|
6
|
+
* Perfect for navigation menus, tabs, and breadcrumbs.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* // Basic usage with activeClassName
|
|
11
|
+
* <NavLink to="/about" activeClassName="active">About</NavLink>
|
|
12
|
+
*
|
|
13
|
+
* // With activeStyle
|
|
14
|
+
* <NavLink
|
|
15
|
+
* to="/profile"
|
|
16
|
+
* activeStyle={{ fontWeight: 'bold', color: 'blue' }}
|
|
17
|
+
* >
|
|
18
|
+
* Profile
|
|
19
|
+
* </NavLink>
|
|
20
|
+
*
|
|
21
|
+
* // Exact matching with end prop
|
|
22
|
+
* <NavLink to="/" end activeClassName="active">Home</NavLink>
|
|
23
|
+
*
|
|
24
|
+
* // Custom active detection
|
|
25
|
+
* <NavLink
|
|
26
|
+
* to="/users"
|
|
27
|
+
* isActive={(match, location) => {
|
|
28
|
+
* return location.pathname.startsWith('/users');
|
|
29
|
+
* }}
|
|
30
|
+
* >
|
|
31
|
+
* Users
|
|
32
|
+
* </NavLink>
|
|
33
|
+
*
|
|
34
|
+
* // Render prop for full control
|
|
35
|
+
* <NavLink to="/dashboard">
|
|
36
|
+
* {({ isActive }) => (
|
|
37
|
+
* <span className={isActive ? 'active' : ''}>
|
|
38
|
+
* Dashboard {isActive && '✓'}
|
|
39
|
+
* </span>
|
|
40
|
+
* )}
|
|
41
|
+
* </NavLink>
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
declare const NavLink: import("react").ForwardRefExoticComponent<NavLinkProps & import("react").RefAttributes<HTMLAnchorElement>>;
|
|
8
45
|
export default NavLink;
|
|
@@ -1,14 +1,115 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, } from "react";
|
|
3
|
+
import { useLocation } from "../hooks/useLocation";
|
|
2
4
|
import { useRouter } from "../hooks/useRouter";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Checks if a link should trigger navigation or default browser behavior
|
|
7
|
+
*/
|
|
8
|
+
const shouldNavigate = (event, target) => {
|
|
9
|
+
return (!event.defaultPrevented &&
|
|
10
|
+
event.button === 0 &&
|
|
11
|
+
(!target || target === "_self") &&
|
|
12
|
+
!event.metaKey &&
|
|
13
|
+
!event.altKey &&
|
|
14
|
+
!event.ctrlKey &&
|
|
15
|
+
!event.shiftKey);
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Default active matching logic
|
|
19
|
+
*/
|
|
20
|
+
const defaultIsActive = (pathname, to, end, caseSensitive) => {
|
|
21
|
+
const toPath = caseSensitive ? to : to.toLowerCase();
|
|
22
|
+
const currentPath = caseSensitive ? pathname : pathname.toLowerCase();
|
|
23
|
+
if (end) {
|
|
24
|
+
// Exact match (including trailing slash variations)
|
|
25
|
+
return currentPath === toPath || currentPath === `${toPath}/`;
|
|
26
|
+
}
|
|
27
|
+
// Partial match - pathname starts with to
|
|
28
|
+
return (currentPath.startsWith(toPath) &&
|
|
29
|
+
(currentPath.length === toPath.length || currentPath[toPath.length] === "/"));
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* NavLink component for navigation with active state styling
|
|
33
|
+
*
|
|
34
|
+
* Extends Link with automatic active state detection and styling.
|
|
35
|
+
* Perfect for navigation menus, tabs, and breadcrumbs.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* // Basic usage with activeClassName
|
|
40
|
+
* <NavLink to="/about" activeClassName="active">About</NavLink>
|
|
41
|
+
*
|
|
42
|
+
* // With activeStyle
|
|
43
|
+
* <NavLink
|
|
44
|
+
* to="/profile"
|
|
45
|
+
* activeStyle={{ fontWeight: 'bold', color: 'blue' }}
|
|
46
|
+
* >
|
|
47
|
+
* Profile
|
|
48
|
+
* </NavLink>
|
|
49
|
+
*
|
|
50
|
+
* // Exact matching with end prop
|
|
51
|
+
* <NavLink to="/" end activeClassName="active">Home</NavLink>
|
|
52
|
+
*
|
|
53
|
+
* // Custom active detection
|
|
54
|
+
* <NavLink
|
|
55
|
+
* to="/users"
|
|
56
|
+
* isActive={(match, location) => {
|
|
57
|
+
* return location.pathname.startsWith('/users');
|
|
58
|
+
* }}
|
|
59
|
+
* >
|
|
60
|
+
* Users
|
|
61
|
+
* </NavLink>
|
|
62
|
+
*
|
|
63
|
+
* // Render prop for full control
|
|
64
|
+
* <NavLink to="/dashboard">
|
|
65
|
+
* {({ isActive }) => (
|
|
66
|
+
* <span className={isActive ? 'active' : ''}>
|
|
67
|
+
* Dashboard {isActive && '✓'}
|
|
68
|
+
* </span>
|
|
69
|
+
* )}
|
|
70
|
+
* </NavLink>
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
const NavLink = forwardRef(({ to, children, className, activeClassName = "active", activeStyle, isActive: customIsActive, end = false, caseSensitive = false, replace = false, state, preventScrollReset = false, target, rel, title, onClick, ...rest }, ref) => {
|
|
74
|
+
const { navigate, pathname, matches } = useRouter();
|
|
75
|
+
const location = useLocation();
|
|
76
|
+
// Determine active state
|
|
77
|
+
let isActive;
|
|
78
|
+
if (customIsActive) {
|
|
79
|
+
// Use custom isActive function
|
|
80
|
+
const currentMatch = matches.length > 0 ? matches[matches.length - 1] : null;
|
|
81
|
+
isActive = customIsActive(currentMatch, location);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Use default matching
|
|
85
|
+
isActive = defaultIsActive(pathname, to, end, caseSensitive);
|
|
86
|
+
}
|
|
87
|
+
// Compute class names
|
|
88
|
+
const computedClassName = [className, isActive ? activeClassName : null]
|
|
7
89
|
.filter(Boolean)
|
|
8
|
-
.join(" ");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
90
|
+
.join(" ") || undefined;
|
|
91
|
+
// Compute styles
|
|
92
|
+
const computedStyle = isActive
|
|
93
|
+
? activeStyle
|
|
94
|
+
: undefined;
|
|
95
|
+
// External link check
|
|
96
|
+
const isExternal = /^https?:\/\//i.test(to);
|
|
97
|
+
const handleClick = (event) => {
|
|
98
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
99
|
+
if (!isExternal && shouldNavigate(event, target)) {
|
|
100
|
+
event.preventDefault();
|
|
101
|
+
navigate(to, { replace, state, preventScrollReset });
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
// Security for external links
|
|
105
|
+
const computedRel = isExternal && target === "_blank" ? rel || "noopener noreferrer" : rel;
|
|
106
|
+
// Support render prop pattern
|
|
107
|
+
const renderChildren = typeof children === "function"
|
|
108
|
+
? children({
|
|
109
|
+
isActive,
|
|
110
|
+
})
|
|
111
|
+
: children;
|
|
112
|
+
return (_jsx("a", { ref: ref, href: to, onClick: handleClick, className: computedClassName, style: computedStyle, target: target, rel: computedRel, title: title, "aria-current": isActive ? "page" : undefined, ...rest, children: renderChildren }));
|
|
113
|
+
});
|
|
114
|
+
NavLink.displayName = "NavLink";
|
|
14
115
|
export default NavLink;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { useOutletContext } from "../context/OutletContext";
|
|
3
|
+
/**
|
|
4
|
+
* Outlet component props
|
|
5
|
+
*/
|
|
6
|
+
export interface OutletProps {
|
|
7
|
+
/** Custom context to pass to child routes */
|
|
8
|
+
context?: unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Outlet Component
|
|
12
|
+
*
|
|
13
|
+
* Renders the child route's element when nested routes are used.
|
|
14
|
+
* This is the React Router-style component for rendering nested route content.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* // Parent route component
|
|
19
|
+
* function Dashboard() {
|
|
20
|
+
* return (
|
|
21
|
+
* <div className="dashboard">
|
|
22
|
+
* <Sidebar />
|
|
23
|
+
* <main>
|
|
24
|
+
* <Outlet /> {/* Renders child route content *\/}
|
|
25
|
+
* </main>
|
|
26
|
+
* </div>
|
|
27
|
+
* );
|
|
28
|
+
* }
|
|
29
|
+
*
|
|
30
|
+
* // Routes configuration
|
|
31
|
+
* const routes = createRouter([
|
|
32
|
+
* {
|
|
33
|
+
* path: "dashboard",
|
|
34
|
+
* component: <Dashboard />,
|
|
35
|
+
* children: [
|
|
36
|
+
* { path: "", component: <DashboardHome /> },
|
|
37
|
+
* { path: "settings", component: <Settings /> },
|
|
38
|
+
* { path: "profile", component: <Profile /> },
|
|
39
|
+
* ],
|
|
40
|
+
* },
|
|
41
|
+
* ]);
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```tsx
|
|
46
|
+
* // Passing context to child routes
|
|
47
|
+
* function Layout() {
|
|
48
|
+
* const user = useUser();
|
|
49
|
+
* return (
|
|
50
|
+
* <div>
|
|
51
|
+
* <Header user={user} />
|
|
52
|
+
* <Outlet context={{ user }} />
|
|
53
|
+
* </div>
|
|
54
|
+
* );
|
|
55
|
+
* }
|
|
56
|
+
*
|
|
57
|
+
* // Accessing context in child route
|
|
58
|
+
* function Profile() {
|
|
59
|
+
* const { user } = useOutletContext<{ user: User }>();
|
|
60
|
+
* return <h1>Welcome, {user.name}</h1>;
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
export declare function Outlet({ context }: OutletProps): ReactNode;
|
|
65
|
+
export { useOutletContext };
|
|
66
|
+
export default Outlet;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useOutlet, useOutletContext } from "../context/OutletContext";
|
|
2
|
+
/**
|
|
3
|
+
* Outlet Component
|
|
4
|
+
*
|
|
5
|
+
* Renders the child route's element when nested routes are used.
|
|
6
|
+
* This is the React Router-style component for rendering nested route content.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* // Parent route component
|
|
11
|
+
* function Dashboard() {
|
|
12
|
+
* return (
|
|
13
|
+
* <div className="dashboard">
|
|
14
|
+
* <Sidebar />
|
|
15
|
+
* <main>
|
|
16
|
+
* <Outlet /> {/* Renders child route content *\/}
|
|
17
|
+
* </main>
|
|
18
|
+
* </div>
|
|
19
|
+
* );
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* // Routes configuration
|
|
23
|
+
* const routes = createRouter([
|
|
24
|
+
* {
|
|
25
|
+
* path: "dashboard",
|
|
26
|
+
* component: <Dashboard />,
|
|
27
|
+
* children: [
|
|
28
|
+
* { path: "", component: <DashboardHome /> },
|
|
29
|
+
* { path: "settings", component: <Settings /> },
|
|
30
|
+
* { path: "profile", component: <Profile /> },
|
|
31
|
+
* ],
|
|
32
|
+
* },
|
|
33
|
+
* ]);
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```tsx
|
|
38
|
+
* // Passing context to child routes
|
|
39
|
+
* function Layout() {
|
|
40
|
+
* const user = useUser();
|
|
41
|
+
* return (
|
|
42
|
+
* <div>
|
|
43
|
+
* <Header user={user} />
|
|
44
|
+
* <Outlet context={{ user }} />
|
|
45
|
+
* </div>
|
|
46
|
+
* );
|
|
47
|
+
* }
|
|
48
|
+
*
|
|
49
|
+
* // Accessing context in child route
|
|
50
|
+
* function Profile() {
|
|
51
|
+
* const { user } = useOutletContext<{ user: User }>();
|
|
52
|
+
* return <h1>Welcome, {user.name}</h1>;
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
export function Outlet({ context }) {
|
|
57
|
+
const outlet = useOutlet();
|
|
58
|
+
// If context is provided, we could wrap outlet with context
|
|
59
|
+
// For now, just return the outlet content
|
|
60
|
+
if (context !== undefined) {
|
|
61
|
+
// Context is passed through OutletProvider in RouterProvider
|
|
62
|
+
console.warn("Outlet context prop is for documentation purposes. " +
|
|
63
|
+
"Use useOutletContext() in child components to access parent context.");
|
|
64
|
+
}
|
|
65
|
+
return outlet;
|
|
66
|
+
}
|
|
67
|
+
// Re-export useOutletContext for convenience
|
|
68
|
+
export { useOutletContext };
|
|
69
|
+
export default Outlet;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Router props
|
|
4
|
+
*/
|
|
5
|
+
interface RouterProps {
|
|
6
|
+
/** Route children as JSX */
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
/** Base path for all routes */
|
|
9
|
+
basename?: string;
|
|
10
|
+
/** Fallback element shown during lazy loading */
|
|
11
|
+
fallback?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Router component for declarative routing
|
|
15
|
+
*
|
|
16
|
+
* Provides an alternative JSX-based approach to defining routes.
|
|
17
|
+
* Supports all route configuration options through props.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* // Basic usage
|
|
22
|
+
* <Router>
|
|
23
|
+
* <Route path="/" component={<Home />} />
|
|
24
|
+
* <Route path="/about" component={<About />} />
|
|
25
|
+
* <Route path="/users/:id" component={<UserProfile />} />
|
|
26
|
+
* <Route path="/dashboard" component={<Dashboard />}>
|
|
27
|
+
* <Route path="settings" component={<Settings />} />
|
|
28
|
+
* <Route path="profile" component={<Profile />} />
|
|
29
|
+
* </Route>
|
|
30
|
+
* <Route path="/404" component={<NotFound />} />
|
|
31
|
+
* </Router>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // With basename
|
|
37
|
+
* <Router basename="/app">
|
|
38
|
+
* <Route path="/" component={<Home />} />
|
|
39
|
+
* </Router>
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* // With lazy loading
|
|
45
|
+
* const LazyAbout = lazy(() => import('./pages/About'));
|
|
46
|
+
*
|
|
47
|
+
* <Router fallback={<Loading />}>
|
|
48
|
+
* <Route path="/about" component={<LazyAbout />} />
|
|
49
|
+
* </Router>
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```tsx
|
|
54
|
+
* // With guards and loaders
|
|
55
|
+
* <Router>
|
|
56
|
+
* <Route
|
|
57
|
+
* path="/admin"
|
|
58
|
+
* component={<Admin />}
|
|
59
|
+
* guard={() => isAdmin() || '/login'}
|
|
60
|
+
* loader={async () => fetchAdminData()}
|
|
61
|
+
* />
|
|
62
|
+
* </Router>
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare const Router: {
|
|
66
|
+
({ children, basename, fallback }: RouterProps): import("react/jsx-runtime").JSX.Element;
|
|
67
|
+
displayName: string;
|
|
68
|
+
};
|
|
69
|
+
export default Router;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Children, isValidElement, Suspense } from "react";
|
|
3
|
+
import RouterProvider from "../context/RouterProvider";
|
|
4
|
+
import createRouter from "../core/createRouter";
|
|
5
|
+
import { Route } from "./route";
|
|
6
|
+
/**
|
|
7
|
+
* Extracts route configurations from JSX Route elements
|
|
8
|
+
* @param children React children containing Route elements
|
|
9
|
+
* @returns Array of route configurations
|
|
10
|
+
*/
|
|
11
|
+
function extractRoutesFromJSX(children) {
|
|
12
|
+
const routes = [];
|
|
13
|
+
Children.forEach(children, (child) => {
|
|
14
|
+
var _a;
|
|
15
|
+
if (isValidElement(child)) {
|
|
16
|
+
// Check if this is a Route component
|
|
17
|
+
const isRouteComponent = child.type === Route ||
|
|
18
|
+
((_a = child.type) === null || _a === void 0 ? void 0 : _a.displayName) === "Route" ||
|
|
19
|
+
(typeof child.type === "function" && child.type.name === "Route");
|
|
20
|
+
if (isRouteComponent) {
|
|
21
|
+
const props = child.props;
|
|
22
|
+
const route = {
|
|
23
|
+
path: props.path,
|
|
24
|
+
component: props.component,
|
|
25
|
+
// Pass through all route configuration options
|
|
26
|
+
index: props.index,
|
|
27
|
+
lazy: props.lazy,
|
|
28
|
+
loader: props.loader,
|
|
29
|
+
errorElement: props.errorElement,
|
|
30
|
+
redirectTo: props.redirectTo,
|
|
31
|
+
guard: props.guard,
|
|
32
|
+
meta: props.meta,
|
|
33
|
+
};
|
|
34
|
+
// Handle nested routes
|
|
35
|
+
if (props.children) {
|
|
36
|
+
const childRoutes = extractRoutesFromJSX(props.children);
|
|
37
|
+
if (childRoutes.length > 0) {
|
|
38
|
+
route.children = childRoutes;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
routes.push(route);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return routes;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Router component for declarative routing
|
|
49
|
+
*
|
|
50
|
+
* Provides an alternative JSX-based approach to defining routes.
|
|
51
|
+
* Supports all route configuration options through props.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* // Basic usage
|
|
56
|
+
* <Router>
|
|
57
|
+
* <Route path="/" component={<Home />} />
|
|
58
|
+
* <Route path="/about" component={<About />} />
|
|
59
|
+
* <Route path="/users/:id" component={<UserProfile />} />
|
|
60
|
+
* <Route path="/dashboard" component={<Dashboard />}>
|
|
61
|
+
* <Route path="settings" component={<Settings />} />
|
|
62
|
+
* <Route path="profile" component={<Profile />} />
|
|
63
|
+
* </Route>
|
|
64
|
+
* <Route path="/404" component={<NotFound />} />
|
|
65
|
+
* </Router>
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* // With basename
|
|
71
|
+
* <Router basename="/app">
|
|
72
|
+
* <Route path="/" component={<Home />} />
|
|
73
|
+
* </Router>
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```tsx
|
|
78
|
+
* // With lazy loading
|
|
79
|
+
* const LazyAbout = lazy(() => import('./pages/About'));
|
|
80
|
+
*
|
|
81
|
+
* <Router fallback={<Loading />}>
|
|
82
|
+
* <Route path="/about" component={<LazyAbout />} />
|
|
83
|
+
* </Router>
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```tsx
|
|
88
|
+
* // With guards and loaders
|
|
89
|
+
* <Router>
|
|
90
|
+
* <Route
|
|
91
|
+
* path="/admin"
|
|
92
|
+
* component={<Admin />}
|
|
93
|
+
* guard={() => isAdmin() || '/login'}
|
|
94
|
+
* loader={async () => fetchAdminData()}
|
|
95
|
+
* />
|
|
96
|
+
* </Router>
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
const Router = ({ children, basename, fallback }) => {
|
|
100
|
+
const routes = extractRoutesFromJSX(children);
|
|
101
|
+
const content = (_jsx(RouterProvider, { routes: createRouter(routes), basename: basename, fallbackElement: fallback }));
|
|
102
|
+
// Wrap in Suspense if fallback is provided
|
|
103
|
+
if (fallback) {
|
|
104
|
+
return _jsx(Suspense, { fallback: fallback, children: content });
|
|
105
|
+
}
|
|
106
|
+
return content;
|
|
107
|
+
};
|
|
108
|
+
Router.displayName = "Router";
|
|
109
|
+
export default Router;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ComponentType, LazyExoticComponent, ReactElement } from "react";
|
|
2
|
+
import type { RouteGuard, RouteLoader, RouteMeta } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Route component props for declarative routing
|
|
5
|
+
*/
|
|
6
|
+
export interface RouteProps {
|
|
7
|
+
/** Path pattern(s) for the route */
|
|
8
|
+
path: string | string[];
|
|
9
|
+
/** Component to render when route matches */
|
|
10
|
+
component: ReactElement;
|
|
11
|
+
/** Nested route children */
|
|
12
|
+
children?: ReactElement<RouteProps> | ReactElement<RouteProps>[];
|
|
13
|
+
/** Index route - renders when parent path matches exactly */
|
|
14
|
+
index?: boolean;
|
|
15
|
+
/** Lazy-loaded component */
|
|
16
|
+
lazy?: LazyExoticComponent<ComponentType<any>>;
|
|
17
|
+
/** Route loader for data fetching */
|
|
18
|
+
loader?: RouteLoader;
|
|
19
|
+
/** Error boundary element */
|
|
20
|
+
errorElement?: ReactElement;
|
|
21
|
+
/** Redirect to another path */
|
|
22
|
+
redirectTo?: string;
|
|
23
|
+
/** Route guard function */
|
|
24
|
+
guard?: RouteGuard;
|
|
25
|
+
/** Route metadata */
|
|
26
|
+
meta?: RouteMeta;
|
|
27
|
+
/** Case-sensitive matching */
|
|
28
|
+
caseSensitive?: boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Route component - used for declarative route definitions
|
|
32
|
+
* This is a placeholder component that will be processed by the Router
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* // Basic route
|
|
37
|
+
* <Route path="/users/:id" component={<UserProfile />} />
|
|
38
|
+
*
|
|
39
|
+
* // Multiple paths
|
|
40
|
+
* <Route path={["/about", "/about-us"]} component={<About />} />
|
|
41
|
+
*
|
|
42
|
+
* // Nested routes
|
|
43
|
+
* <Route path="/dashboard" component={<Dashboard />}>
|
|
44
|
+
* <Route path="settings" component={<Settings />} />
|
|
45
|
+
* <Route path="profile" component={<Profile />} />
|
|
46
|
+
* </Route>
|
|
47
|
+
*
|
|
48
|
+
* // With loader
|
|
49
|
+
* <Route
|
|
50
|
+
* path="/user/:id"
|
|
51
|
+
* component={<UserPage />}
|
|
52
|
+
* loader={async ({ params }) => fetchUser(params.id)}
|
|
53
|
+
* />
|
|
54
|
+
*
|
|
55
|
+
* // With guard
|
|
56
|
+
* <Route
|
|
57
|
+
* path="/admin"
|
|
58
|
+
* component={<AdminPanel />}
|
|
59
|
+
* guard={() => isAdmin() || '/login'}
|
|
60
|
+
* />
|
|
61
|
+
*
|
|
62
|
+
* // With metadata
|
|
63
|
+
* <Route
|
|
64
|
+
* path="/about"
|
|
65
|
+
* component={<About />}
|
|
66
|
+
* meta={{ title: 'About Us', description: 'Learn about us' }}
|
|
67
|
+
* />
|
|
68
|
+
*
|
|
69
|
+
* // Catch-all route
|
|
70
|
+
* <Route path="*" component={<NotFound />} />
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare function Route(_props: RouteProps): null;
|
|
74
|
+
export declare namespace Route {
|
|
75
|
+
var displayName: string;
|
|
76
|
+
}
|
|
77
|
+
export default Route;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route component - used for declarative route definitions
|
|
3
|
+
* This is a placeholder component that will be processed by the Router
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* // Basic route
|
|
8
|
+
* <Route path="/users/:id" component={<UserProfile />} />
|
|
9
|
+
*
|
|
10
|
+
* // Multiple paths
|
|
11
|
+
* <Route path={["/about", "/about-us"]} component={<About />} />
|
|
12
|
+
*
|
|
13
|
+
* // Nested routes
|
|
14
|
+
* <Route path="/dashboard" component={<Dashboard />}>
|
|
15
|
+
* <Route path="settings" component={<Settings />} />
|
|
16
|
+
* <Route path="profile" component={<Profile />} />
|
|
17
|
+
* </Route>
|
|
18
|
+
*
|
|
19
|
+
* // With loader
|
|
20
|
+
* <Route
|
|
21
|
+
* path="/user/:id"
|
|
22
|
+
* component={<UserPage />}
|
|
23
|
+
* loader={async ({ params }) => fetchUser(params.id)}
|
|
24
|
+
* />
|
|
25
|
+
*
|
|
26
|
+
* // With guard
|
|
27
|
+
* <Route
|
|
28
|
+
* path="/admin"
|
|
29
|
+
* component={<AdminPanel />}
|
|
30
|
+
* guard={() => isAdmin() || '/login'}
|
|
31
|
+
* />
|
|
32
|
+
*
|
|
33
|
+
* // With metadata
|
|
34
|
+
* <Route
|
|
35
|
+
* path="/about"
|
|
36
|
+
* component={<About />}
|
|
37
|
+
* meta={{ title: 'About Us', description: 'Learn about us' }}
|
|
38
|
+
* />
|
|
39
|
+
*
|
|
40
|
+
* // Catch-all route
|
|
41
|
+
* <Route path="*" component={<NotFound />} />
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function Route(_props) {
|
|
45
|
+
// This component doesn't render anything directly
|
|
46
|
+
// It's used as a declarative way to define routes that will be processed by Router
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
// Add displayName for better debugging and component recognition
|
|
50
|
+
Route.displayName = "Route";
|
|
51
|
+
export default Route;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import type { Route, RouteMatch } from "../types";
|
|
3
|
+
/**
|
|
4
|
+
* Outlet context for nested route rendering
|
|
5
|
+
*/
|
|
6
|
+
export interface OutletContextType {
|
|
7
|
+
/** Current outlet content to render */
|
|
8
|
+
outlet: ReactNode;
|
|
9
|
+
/** Remaining child routes */
|
|
10
|
+
childRoutes: Route[];
|
|
11
|
+
/** Current route matches */
|
|
12
|
+
matches: RouteMatch[];
|
|
13
|
+
/** Current depth in route tree */
|
|
14
|
+
depth: number;
|
|
15
|
+
/** Custom context data passed to outlet */
|
|
16
|
+
context?: unknown;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Context for outlet data
|
|
20
|
+
*/
|
|
21
|
+
export declare const OutletDataContext: import("react").Context<OutletContextType | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Hook to access outlet context
|
|
24
|
+
*/
|
|
25
|
+
export declare function useOutletContext<T = unknown>(): T;
|
|
26
|
+
/**
|
|
27
|
+
* Hook to check if there's an outlet available
|
|
28
|
+
*/
|
|
29
|
+
export declare function useOutlet(): ReactNode;
|
|
30
|
+
/**
|
|
31
|
+
* Provider for outlet context
|
|
32
|
+
*/
|
|
33
|
+
export declare function OutletProvider({ children, outlet, childRoutes, matches, depth, context, }: {
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
outlet: ReactNode;
|
|
36
|
+
childRoutes?: Route[];
|
|
37
|
+
matches?: RouteMatch[];
|
|
38
|
+
depth?: number;
|
|
39
|
+
context?: unknown;
|
|
40
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
export default OutletDataContext;
|