@zap-js/client 0.0.2 → 0.0.5

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.
Files changed (115) hide show
  1. package/README.md +310 -24
  2. package/bin/zap +0 -0
  3. package/bin/zap-codegen +0 -0
  4. package/dist/cli/commands/build.d.ts +11 -0
  5. package/dist/cli/commands/build.js +282 -0
  6. package/dist/cli/commands/codegen.d.ts +8 -0
  7. package/dist/cli/commands/codegen.js +95 -0
  8. package/dist/cli/commands/dev.d.ts +20 -0
  9. package/dist/cli/commands/dev.js +78 -0
  10. package/dist/cli/commands/new.d.ts +9 -0
  11. package/dist/cli/commands/new.js +307 -0
  12. package/dist/cli/commands/routes-old.d.ts +9 -0
  13. package/dist/cli/commands/routes-old.js +106 -0
  14. package/dist/cli/commands/routes.d.ts +11 -0
  15. package/dist/cli/commands/routes.js +280 -0
  16. package/dist/cli/commands/serve.d.ts +17 -0
  17. package/dist/cli/commands/serve.js +386 -0
  18. package/dist/cli/index.d.ts +2 -0
  19. package/dist/cli/index.js +76 -0
  20. package/dist/cli/utils/index.d.ts +2 -0
  21. package/dist/cli/utils/index.js +2 -0
  22. package/dist/cli/utils/logger.d.ts +84 -0
  23. package/dist/cli/utils/logger.js +181 -0
  24. package/dist/cli/utils/port-finder.d.ts +8 -0
  25. package/dist/cli/utils/port-finder.js +48 -0
  26. package/dist/dev-server/codegen-runner.d.ts +41 -0
  27. package/dist/dev-server/codegen-runner.js +172 -0
  28. package/dist/dev-server/hot-reload.d.ts +72 -0
  29. package/dist/dev-server/hot-reload.js +280 -0
  30. package/dist/dev-server/index.d.ts +8 -0
  31. package/dist/dev-server/index.js +8 -0
  32. package/dist/dev-server/route-scanner.d.ts +84 -0
  33. package/dist/dev-server/route-scanner.js +113 -0
  34. package/dist/dev-server/rust-builder.d.ts +66 -0
  35. package/dist/dev-server/rust-builder.js +286 -0
  36. package/dist/dev-server/server.d.ts +147 -0
  37. package/dist/dev-server/server.js +660 -0
  38. package/dist/dev-server/vite-proxy.d.ts +56 -0
  39. package/dist/dev-server/vite-proxy.js +212 -0
  40. package/dist/dev-server/watcher.d.ts +48 -0
  41. package/dist/dev-server/watcher.js +127 -0
  42. package/dist/router/codegen-enhanced.d.ts +5 -0
  43. package/dist/router/codegen-enhanced.js +275 -0
  44. package/dist/router/codegen.d.ts +17 -0
  45. package/dist/router/codegen.js +654 -0
  46. package/dist/router/index.d.ts +16 -0
  47. package/dist/router/index.js +19 -0
  48. package/dist/router/scanner.d.ts +86 -0
  49. package/dist/router/scanner.js +689 -0
  50. package/dist/router/ssg.d.ts +115 -0
  51. package/dist/router/ssg.js +202 -0
  52. package/dist/router/types.d.ts +124 -0
  53. package/dist/router/types.js +9 -0
  54. package/dist/router/watch.d.ts +38 -0
  55. package/dist/router/watch.js +135 -0
  56. package/dist/runtime/csrf.d.ts +146 -0
  57. package/dist/runtime/csrf.js +166 -0
  58. package/dist/runtime/error-boundary.d.ts +129 -0
  59. package/dist/runtime/error-boundary.js +287 -0
  60. package/dist/runtime/hooks.d.ts +83 -0
  61. package/dist/runtime/hooks.js +96 -0
  62. package/dist/runtime/index.d.ts +229 -0
  63. package/dist/runtime/index.js +449 -0
  64. package/dist/runtime/ipc-client.d.ts +144 -0
  65. package/dist/runtime/ipc-client.js +621 -0
  66. package/dist/runtime/logger.d.ts +71 -0
  67. package/dist/runtime/logger.js +164 -0
  68. package/dist/runtime/middleware.d.ts +66 -0
  69. package/dist/runtime/middleware.js +114 -0
  70. package/dist/runtime/process-manager.d.ts +51 -0
  71. package/dist/runtime/process-manager.js +207 -0
  72. package/dist/runtime/router-simple.d.ts +98 -0
  73. package/dist/runtime/router-simple.js +330 -0
  74. package/dist/runtime/router.d.ts +103 -0
  75. package/dist/runtime/router.js +435 -0
  76. package/dist/runtime/rpc-client.d.ts +35 -0
  77. package/dist/runtime/rpc-client.js +140 -0
  78. package/dist/runtime/streaming-utils.d.ts +86 -0
  79. package/dist/runtime/streaming-utils.js +150 -0
  80. package/dist/runtime/types.d.ts +465 -0
  81. package/dist/runtime/types.js +60 -0
  82. package/dist/runtime/websockets-utils.d.ts +50 -0
  83. package/dist/runtime/websockets-utils.js +92 -0
  84. package/package.json +30 -20
  85. package/index.js +0 -29
  86. package/internal/cli/package.json +0 -46
  87. package/internal/cli/tsconfig.tsbuildinfo +0 -1
  88. package/internal/dev-server/node_modules/ora/index.d.ts +0 -332
  89. package/internal/dev-server/node_modules/ora/index.js +0 -416
  90. package/internal/dev-server/node_modules/ora/license +0 -9
  91. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.d.ts +0 -36
  92. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.js +0 -65
  93. package/internal/dev-server/node_modules/ora/node_modules/string-width/license +0 -9
  94. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  95. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/README.md +0 -107
  96. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.d.ts +0 -3
  97. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.js +0 -4
  98. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.mjs +0 -4
  99. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/package.json +0 -46
  100. package/internal/dev-server/node_modules/ora/node_modules/string-width/package.json +0 -60
  101. package/internal/dev-server/node_modules/ora/node_modules/string-width/readme.md +0 -62
  102. package/internal/dev-server/node_modules/ora/package.json +0 -66
  103. package/internal/dev-server/node_modules/ora/readme.md +0 -325
  104. package/internal/dev-server/package.json +0 -41
  105. package/internal/router/package.json +0 -28
  106. package/internal/runtime/package.json +0 -41
  107. package/internal/runtime/src/error-boundary.tsx +0 -476
  108. package/internal/runtime/src/router-simple.tsx +0 -640
  109. package/internal/runtime/src/router.tsx +0 -771
  110. package/internal/runtime/tsconfig.tsbuildinfo +0 -1
  111. package/src/errors.js +0 -33
  112. package/src/logger.js +0 -10
  113. package/src/middleware.js +0 -32
  114. package/src/router.js +0 -41
  115. package/src/types.js +0 -39
@@ -1,640 +0,0 @@
1
- /**
2
- * ZapJS Production Router with Nested Layouts
3
- *
4
- * Features:
5
- * - Nested layout support
6
- * - Route-level code splitting
7
- * - Error boundaries per route
8
- * - Suspense boundaries
9
- * - Type-safe navigation
10
- */
11
-
12
- import React, {
13
- createContext,
14
- useContext,
15
- useState,
16
- useEffect,
17
- useCallback,
18
- useMemo,
19
- useTransition,
20
- Suspense,
21
- memo,
22
- type ReactNode,
23
- type ComponentType,
24
- type MouseEvent,
25
- } from 'react';
26
-
27
- // ============================================================================
28
- // Types
29
- // ============================================================================
30
-
31
- export interface LayoutDefinition {
32
- path: string;
33
- component: React.LazyExoticComponent<ComponentType<any>>;
34
- parentLayout?: string;
35
- }
36
-
37
- export interface RouteDefinition {
38
- path: string;
39
- pattern: RegExp;
40
- paramNames: string[];
41
- component: React.LazyExoticComponent<ComponentType<any>>;
42
- layoutPath?: string;
43
- errorComponent?: React.LazyExoticComponent<ComponentType<any>>;
44
- pendingComponent?: React.LazyExoticComponent<ComponentType<any>>;
45
- meta?: () => Promise<RouteMeta>;
46
- }
47
-
48
- export interface RouteMeta {
49
- title?: string;
50
- description?: string;
51
- keywords?: string[];
52
- [key: string]: any;
53
- }
54
-
55
- export interface RouteMatch {
56
- route: RouteDefinition;
57
- params: Record<string, string>;
58
- pathname: string;
59
- }
60
-
61
- export interface RouterState {
62
- pathname: string;
63
- search: string;
64
- hash: string;
65
- match: RouteMatch | null;
66
- }
67
-
68
- export interface NavigateOptions {
69
- replace?: boolean;
70
- scroll?: boolean;
71
- state?: unknown;
72
- }
73
-
74
- export interface Router {
75
- push(path: string, options?: NavigateOptions): void;
76
- replace(path: string, options?: NavigateOptions): void;
77
- back(): void;
78
- forward(): void;
79
- refresh(): void;
80
- prefetch(path: string): void;
81
- }
82
-
83
- // ============================================================================
84
- // Context
85
- // ============================================================================
86
-
87
- interface RouterContextValue {
88
- state: RouterState;
89
- router: Router;
90
- routes: RouteDefinition[];
91
- layouts: LayoutDefinition[];
92
- isPending: boolean;
93
- }
94
-
95
- const RouterContext = createContext<RouterContextValue | null>(null);
96
-
97
- // ============================================================================
98
- // Route Matching
99
- // ============================================================================
100
-
101
- function matchRoute(pathname: string, routes: RouteDefinition[]): RouteMatch | null {
102
- const normalizedPath = pathname === '' ? '/' : pathname;
103
-
104
- for (const route of routes) {
105
- const match = normalizedPath.match(route.pattern);
106
- if (match) {
107
- const params: Record<string, string> = {};
108
- route.paramNames.forEach((name, index) => {
109
- const value = match[index + 1];
110
- if (value !== undefined && value !== '') {
111
- params[name] = decodeURIComponent(value);
112
- }
113
- });
114
-
115
- return { route, params, pathname: normalizedPath };
116
- }
117
- }
118
-
119
- return null;
120
- }
121
-
122
- function parseUrl(url: string): { pathname: string; search: string; hash: string } {
123
- try {
124
- const parsed = new URL(url, window.location.origin);
125
- return {
126
- pathname: parsed.pathname,
127
- search: parsed.search,
128
- hash: parsed.hash,
129
- };
130
- } catch {
131
- const hashIndex = url.indexOf('#');
132
- const searchIndex = url.indexOf('?');
133
-
134
- let pathname = url;
135
- let search = '';
136
- let hash = '';
137
-
138
- if (hashIndex !== -1) {
139
- hash = url.slice(hashIndex);
140
- pathname = url.slice(0, hashIndex);
141
- }
142
-
143
- if (searchIndex !== -1 && (hashIndex === -1 || searchIndex < hashIndex)) {
144
- search = pathname.slice(searchIndex, hashIndex !== -1 ? hashIndex - searchIndex : undefined);
145
- pathname = pathname.slice(0, searchIndex);
146
- }
147
-
148
- return { pathname: pathname || '/', search, hash };
149
- }
150
- }
151
-
152
- // ============================================================================
153
- // Layout Wrapper Component
154
- // ============================================================================
155
-
156
- interface LayoutWrapperProps {
157
- layouts: LayoutDefinition[];
158
- layoutPath?: string;
159
- children: ReactNode;
160
- }
161
-
162
- const LayoutWrapper = memo(({ layouts, layoutPath, children }: LayoutWrapperProps) => {
163
- if (!layoutPath) {
164
- return <>{children}</>;
165
- }
166
-
167
- // Find the layout and build the chain
168
- const layoutChain: LayoutDefinition[] = [];
169
- let currentPath: string | undefined = layoutPath;
170
-
171
- while (currentPath) {
172
- const layout = layouts.find(l => l.path === currentPath);
173
- if (layout) {
174
- layoutChain.unshift(layout);
175
- currentPath = layout.parentLayout;
176
- } else {
177
- break;
178
- }
179
- }
180
-
181
- // Nest layouts
182
- return layoutChain.reduce<ReactNode>((content, layout) => {
183
- const LayoutComponent = layout.component;
184
- return (
185
- <Suspense fallback={<div>Loading layout...</div>}>
186
- <LayoutComponent>{content}</LayoutComponent>
187
- </Suspense>
188
- );
189
- }, children);
190
- });
191
-
192
- LayoutWrapper.displayName = 'LayoutWrapper';
193
-
194
- // ============================================================================
195
- // RouterProvider
196
- // ============================================================================
197
-
198
- interface RouterProviderProps {
199
- routes: RouteDefinition[];
200
- layouts?: LayoutDefinition[];
201
- children: ReactNode;
202
- notFound?: ComponentType;
203
- fallback?: ReactNode;
204
- }
205
-
206
- export function RouterProvider({
207
- routes,
208
- layouts = [],
209
- children,
210
- notFound: NotFound,
211
- fallback = null,
212
- }: RouterProviderProps): JSX.Element {
213
- const [isPending, startTransition] = useTransition();
214
-
215
- const [state, setState] = useState<RouterState>(() => {
216
- const { pathname, search, hash } = parseUrl(window.location.href);
217
- return {
218
- pathname,
219
- search,
220
- hash,
221
- match: matchRoute(pathname, routes),
222
- };
223
- });
224
-
225
- const navigate = useCallback(
226
- (path: string, options: NavigateOptions = {}) => {
227
- const { replace = false, scroll = true } = options;
228
- const { pathname, search, hash } = parseUrl(path);
229
-
230
- const url = pathname + search + hash;
231
- if (replace) {
232
- window.history.replaceState(options.state ?? null, '', url);
233
- } else {
234
- window.history.pushState(options.state ?? null, '', url);
235
- }
236
-
237
- startTransition(() => {
238
- setState({
239
- pathname,
240
- search,
241
- hash,
242
- match: matchRoute(pathname, routes),
243
- });
244
- });
245
-
246
- if (scroll) {
247
- if (hash) {
248
- const element = document.querySelector(hash);
249
- element?.scrollIntoView();
250
- } else {
251
- window.scrollTo(0, 0);
252
- }
253
- }
254
- },
255
- [routes]
256
- );
257
-
258
- const router = useMemo<Router>(
259
- () => ({
260
- push: (path, options) => navigate(path, options),
261
- replace: (path, options) => navigate(path, { ...options, replace: true }),
262
- back: () => window.history.back(),
263
- forward: () => window.history.forward(),
264
- refresh: () => {
265
- startTransition(() => {
266
- setState((prev) => ({ ...prev, match: matchRoute(prev.pathname, routes) }));
267
- });
268
- },
269
- prefetch: (path) => {
270
- const { pathname } = parseUrl(path);
271
- const match = matchRoute(pathname, routes);
272
- if (match?.route.component) {
273
- const component = match.route.component as any;
274
- if (component._payload && component._init) {
275
- try {
276
- component._init(component._payload);
277
- } catch {
278
- // Component will load when rendered
279
- }
280
- }
281
- }
282
- },
283
- }),
284
- [navigate, routes]
285
- );
286
-
287
- useEffect(() => {
288
- const handlePopState = () => {
289
- const { pathname, search, hash } = parseUrl(window.location.href);
290
- startTransition(() => {
291
- setState({
292
- pathname,
293
- search,
294
- hash,
295
- match: matchRoute(pathname, routes),
296
- });
297
- });
298
- };
299
-
300
- window.addEventListener('popstate', handlePopState);
301
- return () => window.removeEventListener('popstate', handlePopState);
302
- }, [routes]);
303
-
304
- // Update document meta on route change
305
- useEffect(() => {
306
- if (state.match?.route.meta) {
307
- state.match.route.meta().then((meta) => {
308
- if (meta.title) {
309
- document.title = meta.title;
310
- }
311
- if (meta.description) {
312
- const metaDesc = document.querySelector('meta[name="description"]');
313
- if (metaDesc) {
314
- metaDesc.setAttribute('content', meta.description);
315
- }
316
- }
317
- }).catch(() => {
318
- // Ignore meta errors
319
- });
320
- }
321
- }, [state.match]);
322
-
323
- const contextValue = useMemo<RouterContextValue>(
324
- () => ({
325
- state,
326
- router,
327
- routes,
328
- layouts,
329
- isPending,
330
- }),
331
- [state, router, routes, layouts, isPending]
332
- );
333
-
334
- return (
335
- <RouterContext.Provider value={contextValue}>
336
- <Suspense fallback={fallback}>
337
- {children}
338
- </Suspense>
339
- </RouterContext.Provider>
340
- );
341
- }
342
-
343
- // ============================================================================
344
- // Hooks
345
- // ============================================================================
346
-
347
- export function useRouter(): Router {
348
- const context = useContext(RouterContext);
349
- if (!context) {
350
- throw new Error('useRouter must be used within a RouterProvider');
351
- }
352
- return context.router;
353
- }
354
-
355
- export function useParams<T extends Record<string, string> = Record<string, string>>(): T {
356
- const context = useContext(RouterContext);
357
- if (!context) {
358
- throw new Error('useParams must be used within a RouterProvider');
359
- }
360
- return (context.state.match?.params ?? {}) as T;
361
- }
362
-
363
- export function usePathname(): string {
364
- const context = useContext(RouterContext);
365
- if (!context) {
366
- throw new Error('usePathname must be used within a RouterProvider');
367
- }
368
- return context.state.pathname;
369
- }
370
-
371
- export function useSearchParams(): [URLSearchParams, (params: Record<string, string>) => void] {
372
- const context = useContext(RouterContext);
373
- if (!context) {
374
- throw new Error('useSearchParams must be used within a RouterProvider');
375
- }
376
-
377
- const searchParams = useMemo(
378
- () => new URLSearchParams(context.state.search),
379
- [context.state.search]
380
- );
381
-
382
- const setSearchParams = useCallback(
383
- (params: Record<string, string>) => {
384
- const newParams = new URLSearchParams(params);
385
- const newSearch = newParams.toString();
386
- const path = context.state.pathname + (newSearch ? `?${newSearch}` : '') + context.state.hash;
387
- context.router.push(path, { scroll: false });
388
- },
389
- [context.router, context.state.pathname, context.state.hash]
390
- );
391
-
392
- return [searchParams, setSearchParams];
393
- }
394
-
395
- export function useRouteMatch(): RouteMatch | null {
396
- const context = useContext(RouterContext);
397
- if (!context) {
398
- throw new Error('useRouteMatch must be used within a RouterProvider');
399
- }
400
- return context.state.match;
401
- }
402
-
403
- export function useIsPending(): boolean {
404
- const context = useContext(RouterContext);
405
- if (!context) {
406
- throw new Error('useIsPending must be used within a RouterProvider');
407
- }
408
- return context.isPending;
409
- }
410
-
411
- // ============================================================================
412
- // Link Component
413
- // ============================================================================
414
-
415
- export interface LinkProps extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href'> {
416
- to: string;
417
- replace?: boolean;
418
- prefetch?: boolean;
419
- scroll?: boolean;
420
- children: ReactNode;
421
- }
422
-
423
- export function Link({
424
- to,
425
- replace = false,
426
- prefetch = true,
427
- scroll = true,
428
- children,
429
- onClick,
430
- onMouseEnter,
431
- ...props
432
- }: LinkProps): JSX.Element {
433
- const context = useContext(RouterContext);
434
-
435
- const handleClick = useCallback(
436
- (e: MouseEvent<HTMLAnchorElement>) => {
437
- onClick?.(e);
438
-
439
- if (
440
- e.defaultPrevented ||
441
- e.button !== 0 ||
442
- e.metaKey ||
443
- e.ctrlKey ||
444
- e.shiftKey ||
445
- e.altKey
446
- ) {
447
- return;
448
- }
449
-
450
- const href = to;
451
- if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//')) {
452
- return;
453
- }
454
-
455
- e.preventDefault();
456
- context?.router[replace ? 'replace' : 'push'](to, { scroll });
457
- },
458
- [context?.router, to, replace, scroll, onClick]
459
- );
460
-
461
- const handleMouseEnter = useCallback(
462
- (e: MouseEvent<HTMLAnchorElement>) => {
463
- onMouseEnter?.(e);
464
- if (prefetch && context) {
465
- context.router.prefetch(to);
466
- }
467
- },
468
- [context, to, prefetch, onMouseEnter]
469
- );
470
-
471
- return (
472
- <a
473
- href={to}
474
- onClick={handleClick}
475
- onMouseEnter={handleMouseEnter}
476
- {...props}
477
- >
478
- {children}
479
- </a>
480
- );
481
- }
482
-
483
- // ============================================================================
484
- // Route Outlet with Layout Support
485
- // ============================================================================
486
-
487
- interface OutletProps {
488
- notFound?: ComponentType;
489
- fallback?: ReactNode;
490
- }
491
-
492
- export function Outlet({ notFound: NotFound, fallback = null }: OutletProps): JSX.Element | null {
493
- const context = useContext(RouterContext);
494
- if (!context) {
495
- throw new Error('Outlet must be used within a RouterProvider');
496
- }
497
-
498
- const { match } = context.state;
499
-
500
- if (!match) {
501
- return NotFound ? <NotFound /> : null;
502
- }
503
-
504
- const { route, params } = match;
505
- const Component = route.component;
506
- const ErrorComponent = route.errorComponent;
507
- const PendingComponent = route.pendingComponent;
508
-
509
- const routeElement = (
510
- <Suspense fallback={PendingComponent ? <PendingComponent /> : fallback}>
511
- <Component params={params} />
512
- </Suspense>
513
- );
514
-
515
- const wrappedElement = (
516
- <LayoutWrapper
517
- layouts={context.layouts}
518
- layoutPath={route.layoutPath}
519
- >
520
- {ErrorComponent ? (
521
- <RouteErrorBoundary fallback={<ErrorComponent />}>
522
- {routeElement}
523
- </RouteErrorBoundary>
524
- ) : routeElement}
525
- </LayoutWrapper>
526
- );
527
-
528
- return wrappedElement;
529
- }
530
-
531
- // ============================================================================
532
- // Error Boundary
533
- // ============================================================================
534
-
535
- interface RouteErrorBoundaryProps {
536
- children: ReactNode;
537
- fallback: ReactNode;
538
- }
539
-
540
- interface RouteErrorBoundaryState {
541
- hasError: boolean;
542
- error: Error | null;
543
- }
544
-
545
- class RouteErrorBoundary extends React.Component<RouteErrorBoundaryProps, RouteErrorBoundaryState> {
546
- constructor(props: RouteErrorBoundaryProps) {
547
- super(props);
548
- this.state = { hasError: false, error: null };
549
- }
550
-
551
- static getDerivedStateFromError(error: Error): RouteErrorBoundaryState {
552
- return { hasError: true, error };
553
- }
554
-
555
- componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
556
- console.error('Route error:', error, errorInfo);
557
- }
558
-
559
- render() {
560
- if (this.state.hasError) {
561
- return this.props.fallback;
562
- }
563
- return this.props.children;
564
- }
565
- }
566
-
567
- // ============================================================================
568
- // NavLink Component
569
- // ============================================================================
570
-
571
- interface NavLinkProps extends LinkProps {
572
- activeClassName?: string;
573
- activeStyle?: React.CSSProperties;
574
- exact?: boolean;
575
- pending?: boolean;
576
- pendingClassName?: string;
577
- pendingStyle?: React.CSSProperties;
578
- }
579
-
580
- export function NavLink({
581
- to,
582
- activeClassName,
583
- activeStyle,
584
- exact = false,
585
- pending = false,
586
- pendingClassName,
587
- pendingStyle,
588
- className,
589
- style,
590
- ...props
591
- }: NavLinkProps): JSX.Element {
592
- const pathname = usePathname();
593
- const isPending = useIsPending();
594
-
595
- const isActive = exact
596
- ? pathname === to
597
- : pathname.startsWith(to) && (to === '/' ? pathname === '/' : true);
598
-
599
- const isPendingRoute = pending && isPending;
600
-
601
- const combinedClassName = [
602
- className,
603
- isActive && activeClassName,
604
- isPendingRoute && pendingClassName,
605
- ].filter(Boolean).join(' ').trim() || undefined;
606
-
607
- const combinedStyle = {
608
- ...style,
609
- ...(isActive ? activeStyle : {}),
610
- ...(isPendingRoute ? pendingStyle : {}),
611
- };
612
-
613
- return (
614
- <Link
615
- to={to}
616
- className={combinedClassName}
617
- style={combinedStyle}
618
- {...props}
619
- />
620
- );
621
- }
622
-
623
- // ============================================================================
624
- // Redirect Component
625
- // ============================================================================
626
-
627
- interface RedirectProps {
628
- to: string;
629
- replace?: boolean;
630
- }
631
-
632
- export function Redirect({ to, replace = true }: RedirectProps): null {
633
- const router = useRouter();
634
-
635
- useEffect(() => {
636
- router[replace ? 'replace' : 'push'](to);
637
- }, [router, to, replace]);
638
-
639
- return null;
640
- }