@aminnairi/react-router 1.1.0 → 2.0.1

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/.eslintrc.cjs ADDED
@@ -0,0 +1,13 @@
1
+ module.exports = {
2
+ root: true,
3
+ env: { browser: true, es2020: true },
4
+ extends: [
5
+ 'eslint:recommended',
6
+ 'plugin:@typescript-eslint/recommended',
7
+ 'plugin:react-hooks/recommended',
8
+ ],
9
+ ignorePatterns: ['dist', '.eslintrc.cjs', 'index.js'],
10
+ parser: '@typescript-eslint/parser',
11
+ plugins: [],
12
+ rules: {},
13
+ }
package/README.md CHANGED
@@ -17,6 +17,7 @@ cd project
17
17
  ```
18
18
 
19
19
  ### Dependencies installation
20
+
20
21
  ```bash
21
22
  npm install
22
23
  ```
@@ -41,10 +42,8 @@ import { createPage } from "@aminnairi/react-router";
41
42
  export const home = createPage({
42
43
  path: "/",
43
44
  element: function Home() {
44
- return (
45
- <h1>Home page</h1>
46
- );
47
- }
45
+ return <h1>Home page</h1>;
46
+ },
48
47
  });
49
48
  ```
50
49
 
@@ -59,12 +58,8 @@ import { home } from "./pages/home";
59
58
  export const Fallback = () => {
60
59
  const navigateToHomePage = useNavigateToPage(home);
61
60
 
62
- return (
63
- <button onClick={navigateToHomePage}>
64
- Go back home
65
- </button>
66
- );
67
- }
61
+ return <button onClick={() => navigateToHomePage({})}>Go back home</button>;
62
+ };
68
63
  ```
69
64
 
70
65
  ```bash
@@ -82,30 +77,26 @@ export const Issue = () => {
82
77
  return (
83
78
  <Fragment>
84
79
  <h1>An issue occurred</h1>
85
- <button onClick={home.navigate}>
86
- Go back home
87
- </button>
80
+ <button onClick={() => navigateToHomePage({})}>Go back home</button>
88
81
  </Fragment>
89
82
  );
90
- }
83
+ };
91
84
  ```
92
85
 
93
86
  ```bash
94
87
  touch src/router/index.ts
95
88
  ```
96
89
 
97
- ```tsx
90
+ ```ts
98
91
  import { createRouter } from "@aminnairi/react-router";
99
- import { Fallback } from "./router/fallback";
100
- import { Issue } from "./router/issue";
101
- import { home } from "./router/pages/home";
92
+ import { Fallback } from "./fallback";
93
+ import { Issue } from "./issue";
94
+ import { home } from "./pages/home";
102
95
 
103
96
  export const router = createRouter({
104
97
  fallback: Fallback,
105
98
  issue: Issue,
106
- pages: [
107
- home
108
- ]
99
+ pages: [home],
109
100
  });
110
101
  ```
111
102
 
@@ -117,9 +108,7 @@ touch src/App.tsx
117
108
  import { router } from "./router";
118
109
 
119
110
  export default function App() {
120
- return (
121
- <router.View />
122
- );
111
+ return <router.View />;
123
112
  }
124
113
  ```
125
114
 
@@ -144,7 +133,7 @@ createRoot(rootElement).render(
144
133
  <router.Provider>
145
134
  <App />
146
135
  </router.Provider>
147
- </StrictMode>
136
+ </StrictMode>,
148
137
  );
149
138
  ```
150
139
 
@@ -166,10 +155,8 @@ import { createPage } from "@aminnairi/react-router";
166
155
  createPage({
167
156
  path: "/",
168
157
  element: function Home() {
169
- return (
170
- <h1>Home</h1>
171
- );
172
- }
158
+ return <h1>Home</h1>;
159
+ },
173
160
  });
174
161
  ```
175
162
 
@@ -181,24 +168,14 @@ import { createPage, createRouter } from "@aminnairi/react-router";
181
168
  const home = createPage({
182
169
  path: "/",
183
170
  element: function Home() {
184
- return (
185
- <h1>Home</h1>
186
- );
187
- }
171
+ return <h1>Home</h1>;
172
+ },
188
173
  });
189
174
 
190
175
  createRouter({
191
- fallback: () => (
192
- <h1>
193
- Not found
194
- </h1>
195
- ),
196
- issue: () => (
197
- <h1>
198
- An error occurred
199
- </h1>
200
- ),
201
- pages: [home]
176
+ fallback: () => <h1>Not found</h1>,
177
+ issue: () => <h1>An error occurred</h1>,
178
+ pages: [home],
202
179
  });
203
180
  ```
204
181
 
@@ -209,11 +186,9 @@ import { createPage } from "@aminnairi/react-router";
209
186
 
210
187
  createPage({
211
188
  path: "/users/:user",
212
- element: function User({ parameters: { user }}) {
213
- return (
214
- <h1>User#{user}</h1>
215
- );
216
- }
189
+ element: function User({ parameters: { user } }) {
190
+ return <h1>User#{user}</h1>;
191
+ },
217
192
  });
218
193
  ```
219
194
 
@@ -224,11 +199,13 @@ import { createPage } from "@aminnairi/react-router";
224
199
 
225
200
  createPage({
226
201
  path: "/users/:user/articles/:article",
227
- element: function UserArticle({ parameters: { user, article }}) {
202
+ element: function UserArticle({ parameters: { user, article } }) {
228
203
  return (
229
- <h1>Article#{article } of user#{user}</h1>
204
+ <h1>
205
+ Article#{article} of user#{user}
206
+ </h1>
230
207
  );
231
- }
208
+ },
232
209
  });
233
210
  ```
234
211
 
@@ -243,10 +220,8 @@ import { createPage, useNavigateToPage } from "@aminnairi/react-router";
243
220
  const login = createPage({
244
221
  path: "/login",
245
222
  element: function Login() {
246
- return (
247
- <h1>Login</h1>
248
- );
249
- }
223
+ return <h1>Login</h1>;
224
+ },
250
225
  });
251
226
 
252
227
  const about = createPage({
@@ -256,15 +231,11 @@ const about = createPage({
256
231
 
257
232
  return (
258
233
  <Fragment>
259
- <h1>
260
- About Us
261
- </h1>
262
- <button onClick={navigateToLoginPage}>
263
- Login
264
- </button>
234
+ <h1>About Us</h1>
235
+ <button onClick={() => navigateToLoginPage({})}>Login</button>
265
236
  </Fragment>
266
237
  );
267
- }
238
+ },
268
239
  });
269
240
 
270
241
  createPage({
@@ -274,15 +245,11 @@ createPage({
274
245
 
275
246
  return (
276
247
  <Fragment>
277
- <h1>
278
- Home
279
- </h1>
280
- <button onClick={navigateToAboutPage}>
281
- About Us
282
- </button>
248
+ <h1>Home</h1>
249
+ <button onClick={() => navigateToAboutPage({})}>About Us</button>
283
250
  </Fragment>
284
251
  );
285
- }
252
+ },
286
253
  });
287
254
  ```
288
255
 
@@ -294,11 +261,9 @@ import { createPage, useNavigateToPage } from "@aminnairi/react-router";
294
261
 
295
262
  const user = createPage({
296
263
  path: "/users/:user",
297
- element: function User({ parameters: { user }}) {
298
- return (
299
- <h1>User#{user}</h1>
300
- );
301
- }
264
+ element: function User({ parameters: { user } }) {
265
+ return <h1>User#{user}</h1>;
266
+ },
302
267
  });
303
268
 
304
269
  createPage({
@@ -308,15 +273,13 @@ createPage({
308
273
 
309
274
  return (
310
275
  <Fragment>
311
- <h1>
312
- Home
313
- </h1>
276
+ <h1>Home</h1>
314
277
  <button onClick={() => navigateToUserPage({ user: "123" })}>
315
278
  User#123
316
279
  </button>
317
280
  </Fragment>
318
281
  );
319
- }
282
+ },
320
283
  });
321
284
  ```
322
285
 
@@ -332,22 +295,14 @@ import { createRouter, createPage } from "@aminnairi/react-router";
332
295
  const home = createPage({
333
296
  path: "/",
334
297
  element: function Home() {
335
- return (
336
- <h1>Home</h1>
337
- );
338
- }
298
+ return <h1>Home</h1>;
299
+ },
339
300
  });
340
301
 
341
302
  const router = createRouter({
342
- fallback: () => (
343
- <h1>Not found</h1>
344
- ),
345
- issue: () => (
346
- <h1>An error occurred</h1>
347
- ),
348
- pages: [
349
- home
350
- ]
303
+ fallback: () => <h1>Not found</h1>,
304
+ issue: () => <h1>An error occurred</h1>,
305
+ pages: [home],
351
306
  });
352
307
 
353
308
  const rootElement = document.getElementById("root");
@@ -367,51 +322,47 @@ const App = () => {
367
322
  <main>
368
323
  <router.View />
369
324
  </main>
370
- <footer>
371
- Credit © Yourself 2025
372
- </footer>
325
+ <footer>Credit © Yourself 2025</footer>
373
326
  </Fragment>
374
327
  );
375
- }
328
+ };
376
329
 
377
330
  root.render(
378
331
  <StrictMode>
379
332
  <router.Provider>
380
333
  <App />
381
334
  </router.Provider>
382
- </StrictMode>
335
+ </StrictMode>,
383
336
  );
384
337
  ```
385
338
 
386
339
  You can also activate the View Transition Web API if you want before each page renders. This is nice because by default, the browser already has some styling that allows for a smooth and simple transition between pages.
387
340
 
388
- All you have to do is to set the `withViewTransition` property to `true` in the arguments of the `createRouter` function. By default, its value is set to `false` if not provided in the arguments of the `createRouter` function.
341
+ All you have to do is to provide a `transition` function in the arguments of the `createRouter` function. This function receives the navigation direction (`"pushstate"` or `"popstate"`) and a `next` callback to render the next page.
342
+
343
+ This library also exports a `slideFadeTransition` that you can use out-of-the-box.
389
344
 
390
345
  ```tsx
391
346
  import { Fragment, StrictMode } from "react";
392
347
  import { createRoot } from "react-dom/client";
393
- import { createRouter, createPage } from "@aminnairi/react-router";
348
+ import {
349
+ createRouter,
350
+ createPage,
351
+ slideFadeTransition,
352
+ } from "@aminnairi/react-router";
394
353
 
395
354
  const home = createPage({
396
355
  path: "/",
397
356
  element: function Page() {
398
- return (
399
- <h1>Home</h1>
400
- );
401
- }
357
+ return <h1>Home</h1>;
358
+ },
402
359
  });
403
360
 
404
361
  const router = createRouter({
405
- transition: true,
406
- fallback: () => (
407
- <h1>Not found</h1>
408
- ),
409
- issue: () => (
410
- <h1>An error occurred</h1>
411
- ),
412
- pages: [
413
- home
414
- ]
362
+ transition: slideFadeTransition,
363
+ fallback: () => <h1>Not found</h1>,
364
+ issue: () => <h1>An error occurred</h1>,
365
+ pages: [home],
415
366
  });
416
367
 
417
368
  const rootElement = document.getElementById("root");
@@ -431,19 +382,17 @@ const App = () => {
431
382
  <main>
432
383
  <router.View />
433
384
  </main>
434
- <footer>
435
- Credit © Yourself 2025
436
- </footer>
385
+ <footer>Credit © Yourself 2025</footer>
437
386
  </Fragment>
438
387
  );
439
- }
388
+ };
440
389
 
441
390
  root.render(
442
391
  <StrictMode>
443
392
  <router.Provider>
444
393
  <App />
445
394
  </router.Provider>
446
- </StrictMode>
395
+ </StrictMode>,
447
396
  );
448
397
  ```
449
398
 
@@ -457,29 +406,20 @@ import { createRouter, createPage } from "@aminnairi/react-router";
457
406
  const home = createPage({
458
407
  path: "/",
459
408
  element: function Home() {
460
- return (
461
- <h1>Home</h1>
462
- );
463
- }
409
+ return <h1>Home</h1>;
410
+ },
464
411
  });
465
412
 
466
413
  const router = createRouter({
467
- transition: true,
468
- fallback: () => (
469
- <h1>Not found</h1>
470
- ),
414
+ fallback: () => <h1>Not found</h1>,
471
415
  issue: ({ error, reset }) => (
472
- return (
473
- <Fragment>
474
- <h1>Error</h1>
475
- <p>{error.message}</p>
476
- <button onClick={reset}>Reset</button>
477
- </Fragment>
478
- );
416
+ <Fragment>
417
+ <h1>Error</h1>
418
+ <p>{error.message}</p>
419
+ <button onClick={reset}>Reset</button>
420
+ </Fragment>
479
421
  ),
480
- pages: [
481
- home
482
- ]
422
+ pages: [home],
483
423
  });
484
424
 
485
425
  const rootElement = document.getElementById("root");
@@ -499,19 +439,17 @@ const App = () => {
499
439
  <main>
500
440
  <router.View />
501
441
  </main>
502
- <footer>
503
- Credit © Yourself 2025
504
- </footer>
442
+ <footer>Credit © Yourself 2025</footer>
505
443
  </Fragment>
506
444
  );
507
- }
445
+ };
508
446
 
509
447
  root.render(
510
448
  <StrictMode>
511
449
  <router.Provider>
512
450
  <App />
513
451
  </router.Provider>
514
- </StrictMode>
452
+ </StrictMode>,
515
453
  );
516
454
  ```
517
455
 
@@ -525,35 +463,26 @@ import { createRouter, createPage, createIssue } from "@aminnairi/react-router";
525
463
  const home = createPage({
526
464
  path: "/",
527
465
  element: function Home() {
528
- return (
529
- <h1>Home</h1>
530
- );
531
- }
466
+ return <h1>Home</h1>;
467
+ },
532
468
  });
533
469
 
534
470
  const Fallback = () => {
535
- return (
536
- <h1>Not found</h1>
537
- );
538
- }
471
+ return <h1>Not found</h1>;
472
+ };
539
473
 
540
474
  const Issue = createIssue(({ error, reset }) => (
541
- return (
542
- <Fragment>
543
- <h1>Error</h1>
544
- <p>{error.message}</p>
545
- <button onClick={reset}>Reset</button>
546
- </Fragment>
547
- );
475
+ <Fragment>
476
+ <h1>Error</h1>
477
+ <p>{error.message}</p>
478
+ <button onClick={reset}>Reset</button>
479
+ </Fragment>
548
480
  ));
549
481
 
550
482
  const router = createRouter({
551
- transition: true,
552
483
  fallback: Fallback,
553
484
  issue: Issue,
554
- pages: [
555
- home
556
- ]
485
+ pages: [home],
557
486
  });
558
487
 
559
488
  const rootElement = document.getElementById("root");
@@ -573,17 +502,17 @@ const App = () => {
573
502
  <main>
574
503
  <router.View />
575
504
  </main>
576
- <footer>
577
- Credit © Yourself 2025
578
- </footer>
505
+ <footer>Credit © Yourself 2025</footer>
579
506
  </Fragment>
580
507
  );
581
- }
508
+ };
582
509
 
583
510
  root.render(
584
511
  <StrictMode>
585
- <App />
586
- </StrictMode>
512
+ <router.Provider>
513
+ <App />
514
+ </router.Provider>
515
+ </StrictMode>,
587
516
  );
588
517
  ```
589
518
 
@@ -594,15 +523,18 @@ You don't have to manually append this prefix when creating pages, its automatic
594
523
  ```tsx
595
524
  import { Fragment, StrictMode } from "react";
596
525
  import { createRoot } from "react-dom/client";
597
- import { createRouter, createPage, createIssue, useNavigateToPage } from "@aminnairi/react-router";
526
+ import {
527
+ createRouter,
528
+ createPage,
529
+ createIssue,
530
+ useNavigateToPage,
531
+ } from "@aminnairi/react-router";
598
532
 
599
533
  const home = createPage({
600
534
  path: "/",
601
535
  element: function Home() {
602
- return (
603
- <h1>Home</h1>
604
- );
605
- }
536
+ return <h1>Home</h1>;
537
+ },
606
538
  });
607
539
 
608
540
  const Fallback = () => {
@@ -611,31 +543,24 @@ const Fallback = () => {
611
543
  return (
612
544
  <Fragment>
613
545
  <h1>Not found</h1>
614
- <button onClick={navigateToHomePage}>
615
- Go Back Home
616
- </button>
546
+ <button onClick={() => navigateToHomePage({})}>Go Back Home</button>
617
547
  </Fragment>
618
548
  );
619
- }
549
+ };
620
550
 
621
551
  const Issue = createIssue(({ error, reset }) => (
622
- return (
623
- <Fragment>
624
- <h1>Error</h1>
625
- <p>{error.message}</p>
626
- <button onClick={reset}>Reset</button>
627
- </Fragment>
628
- );
552
+ <Fragment>
553
+ <h1>Error</h1>
554
+ <p>{error.message}</p>
555
+ <button onClick={reset}>Reset</button>
556
+ </Fragment>
629
557
  ));
630
558
 
631
559
  const router = createRouter({
632
560
  prefix: "/portfolio",
633
- transition: true,
634
561
  fallback: Fallback,
635
562
  issue: Issue,
636
- pages: [
637
- home
638
- ]
563
+ pages: [home],
639
564
  });
640
565
 
641
566
  const rootElement = document.getElementById("root");
@@ -655,17 +580,17 @@ const App = () => {
655
580
  <main>
656
581
  <router.View />
657
582
  </main>
658
- <footer>
659
- Credit © Yourself 2025
660
- </footer>
583
+ <footer>Credit © Yourself 2025</footer>
661
584
  </Fragment>
662
585
  );
663
- }
586
+ };
664
587
 
665
588
  root.render(
666
589
  <StrictMode>
667
- <App />
668
- </StrictMode>
590
+ <router.Provider>
591
+ <App />
592
+ </router.Provider>
593
+ </StrictMode>,
669
594
  );
670
595
  ```
671
596
 
@@ -679,13 +604,11 @@ It accepts a page that has been created using `createPage`.
679
604
  import { Fragment } from "react";
680
605
  import { createPage, useNavigateToPage } from "@aminnairi/react-router";
681
606
 
682
- const home = createPath({
607
+ const home = createPage({
683
608
  path: "/",
684
609
  element: function Home() {
685
- return (
686
- <h1>Home</h1>
687
- );
688
- }
610
+ return <h1>Home</h1>;
611
+ },
689
612
  });
690
613
 
691
614
  createPage({
@@ -696,10 +619,10 @@ createPage({
696
619
  return (
697
620
  <Fragment>
698
621
  <h1>About</h1>
699
- <button onClick={navigateToHomePage}>Home</button>
622
+ <button onClick={() => navigateToHomePage({})}>Home</button>
700
623
  </Fragment>
701
624
  );
702
- }
625
+ },
703
626
  });
704
627
  ```
705
628
 
@@ -711,13 +634,11 @@ The parameters should always be provided as string, as they are the only data ty
711
634
  import { Fragment } from "react";
712
635
  import { createPage, useNavigateToPage } from "@aminnairi/react-router";
713
636
 
714
- const user = createPath({
637
+ const user = createPage({
715
638
  path: "/users/:user",
716
- element: function User({ parameters: { user }}) {
717
- return (
718
- <h1>User#{user}</h1>
719
- );
720
- }
639
+ element: function User({ parameters: { user } }) {
640
+ return <h1>User#{user}</h1>;
641
+ },
721
642
  });
722
643
 
723
644
  createPage({
@@ -728,10 +649,12 @@ createPage({
728
649
  return (
729
650
  <Fragment>
730
651
  <h1>About</h1>
731
- <button onClick={() => navigateToUserPage({ user: "123" })}>Home</button>
652
+ <button onClick={() => navigateToUserPage({ user: "123" })}>
653
+ Home
654
+ </button>
732
655
  </Fragment>
733
656
  );
734
- }
657
+ },
735
658
  });
736
659
  ```
737
660
 
@@ -745,13 +668,11 @@ The created component is simply a `<a href="...">{children}</a>` under the hood
745
668
  import { Fragment } from "react";
746
669
  import { createPage, useLink } from "@aminnairi/react-router";
747
670
 
748
- const user = createPath({
671
+ const user = createPage({
749
672
  path: "/users/:user",
750
- element: function User({ parameters: { user }}) {
751
- return (
752
- <h1>User#{user}</h1>
753
- );
754
- }
673
+ element: function User({ parameters: { user } }) {
674
+ return <h1>User#{user}</h1>;
675
+ },
755
676
  });
756
677
 
757
678
  createPage({
@@ -765,7 +686,7 @@ createPage({
765
686
  <Link parameters={{ user: "123" }}>User#123</Link>
766
687
  </Fragment>
767
688
  );
768
- }
689
+ },
769
690
  });
770
691
  ```
771
692
 
@@ -782,7 +703,7 @@ import { createPage, useSearch } from "@aminnairi/react-router";
782
703
  createPage({
783
704
  path: "/users",
784
705
  element: function Home() {
785
- const [search] = useSearch();
706
+ const search = useSearch();
786
707
  const sortedByDate = useMemo(() => search.get("sort-by") === "date", [search]);
787
708
 
788
709
  return (
@@ -807,10 +728,8 @@ createPage({
807
728
  element: function OauthCallback() {
808
729
  const token = useHash();
809
730
 
810
- return (
811
- <h1>You token is {token}</h1>
812
- );
813
- }
731
+ return <h1>You token is {token}</h1>;
732
+ },
814
733
  });
815
734
  ```
816
735
 
@@ -825,13 +744,13 @@ This function is mainly used in the internals of the `createRouter` and in most
825
744
  ```typescript
826
745
  import { doesRouteMatchPath } from "@aminnairi/react-router";
827
746
 
828
- doesRoutePatchPath("/", "/"); // true
747
+ doesRouteMatchPath("/", "/"); // true
829
748
 
830
- doesRoutePatchPath("/", "/about"); // false
749
+ doesRouteMatchPath("/", "/about"); // false
831
750
 
832
- doesRoutePatchPath("/users/:user", "/users/123"); // true
751
+ doesRouteMatchPath("/users/:user", "/users/123"); // true
833
752
 
834
- doesRoutePatchPath("/users/:user", "/users/123/articles"); // false
753
+ doesRouteMatchPath("/users/:user", "/users/123/articles"); // false
835
754
  ```
836
755
 
837
756
  You can also optionally provide a prefix.
@@ -839,13 +758,13 @@ You can also optionally provide a prefix.
839
758
  ```typescript
840
759
  import { doesRouteMatchPath } from "@aminnairi/react-router";
841
760
 
842
- doesRoutePatchPath("/", "/github", "/github"); // true
761
+ doesRouteMatchPath("/", "/github", "/github"); // true
843
762
 
844
- doesRoutePatchPath("/", "/github/about", "/github"); // false
763
+ doesRouteMatchPath("/", "/github/about", "/github"); // false
845
764
 
846
- doesRoutePatchPath("/users/:user", "/github/users/123", "/github"); // true
765
+ doesRouteMatchPath("/users/:user", "/github/users/123", "/github"); // true
847
766
 
848
- doesRoutePatchPath("/users/:user", "/github/users/123/articles", "/github"); // false
767
+ doesRouteMatchPath("/users/:user", "/github/users/123/articles", "/github"); // false
849
768
  ```
850
769
 
851
770
  ### getParameters
@@ -857,13 +776,13 @@ This function is mainly used in the internals of the `createRouter` and in most
857
776
  ```typescript
858
777
  import { getParameters } from "@aminnairi/react-router";
859
778
 
860
- getParameters("/", "/"); // object
779
+ getParameters("/", "/"); // {}
861
780
 
862
- getParameters("/", "/about"); // object
781
+ getParameters("/", "/about"); // {}
863
782
 
864
783
  getParameters("/users/:user", "/users/123"); // { user: "123" }
865
784
 
866
- getParameters("/users/:user", "/users/123/articles"); // { user: "123" }
785
+ getParameters("/users/:user", "/users/123/articles"); // {}
867
786
  ```
868
787
 
869
788
  You can also provide an optional prefix.
@@ -871,91 +790,13 @@ You can also provide an optional prefix.
871
790
  ```typescript
872
791
  import { getParameters } from "@aminnairi/react-router";
873
792
 
874
- getParameters("/", "/github", "/github"); // object
793
+ getParameters("/", "/github", "/github"); // {}
875
794
 
876
- getParameters("/", "/github/about", "/github"); // object
795
+ getParameters("/", "/github/about", "/github"); // {}
877
796
 
878
797
  getParameters("/users/:user", "/github/users/123", "/github"); // { user: "123" }
879
798
 
880
- getParameters("/users/:user", "/github/users/123/articles", "/github"); // { user: "123" }
881
- ```
882
-
883
- ### findPage
884
-
885
- Return a page that matches the `window.location.pathname` property containing the current URI of the page from an array of pages.
886
-
887
- If it does not match any pages, it returns `undefined` instead.
888
-
889
- This function is mainly used in the internals of the `createRouter` and in most case should not be necessary.
890
-
891
- ```tsx
892
- import { findPage, createPage } from "@aminnairi/react-router";
893
-
894
- const home = createPage({
895
- path: "/",
896
- element: () => <h1>Home</h1>
897
- });
898
-
899
- const about = createPage({
900
- path: "/about",
901
- element: () => <h1>About</h1>
902
- });
903
-
904
- const login = createPage({
905
- path: "/login",
906
- element: () => <h1>Login</h1>
907
- });
908
-
909
- const pages = [
910
- home.page,
911
- about.page,
912
- login.page
913
- ];
914
-
915
- const foundPage = findPage(pages, "/login");
916
-
917
- if (foundPage) {
918
- console.log("Found a page matching the current location");
919
- console.log(foundPage.path);
920
- } else {
921
- console.log("No page matching the current location.");
922
- }
923
- ```
924
-
925
- You can also provide an optional prefix.
926
-
927
- ```tsx
928
- import { findPage, createPage } from "@aminnairi/react-router";
929
-
930
- const home = createPage({
931
- path: "/",
932
- element: () => <h1>Home</h1>
933
- });
934
-
935
- const about = createPage({
936
- path: "/about",
937
- element: () => <h1>About</h1>
938
- });
939
-
940
- const login = createPage({
941
- path: "/login",
942
- element: () => <h1>Login</h1>
943
- });
944
-
945
- const pages = [
946
- home.page,
947
- about.page,
948
- login.page
949
- ];
950
-
951
- const foundPage = findPage(pages, "/github/login", "/github");
952
-
953
- if (foundPage) {
954
- console.log("Found a page matching the current location");
955
- console.log(foundPage.path);
956
- } else {
957
- console.log("No page matching the current location.");
958
- }
799
+ getParameters("/users/:user", "/github/users/123/articles", "/github"); // {}
959
800
  ```
960
801
 
961
802
  ### sanitizePath
@@ -994,9 +835,7 @@ This means that you can use this library with other popular solutions for handli
994
835
 
995
836
  ### Transition
996
837
 
997
- Support for the View Transition API is built-in and allows for painless and smooth view transition out-of-the-box without having to do anything.
998
-
999
- This can also easily be disabled if needed.
838
+ Support for the View Transition API is built-in and allows for painless and smooth view transition out-of-the-box. You can create your own transition animation, and the library also exports a `slideFadeTransition` ready to be used.
1000
839
 
1001
840
  ### Error handling
1002
841
 
@@ -1010,12 +849,43 @@ See [`LICENSE`](./LICENSE).
1010
849
 
1011
850
  ### Versions
1012
851
 
852
+ - [`2.0.1`](#201)
853
+ - [`2.0.0`](#200)
1013
854
  - [`1.1.0`](#110)
1014
855
  - [`1.0.1`](#101)
1015
856
  - [`1.0.0`](#100)
1016
857
  - [`0.1.1`](#011)
1017
858
  - [`0.1.0`](#010)
1018
859
 
860
+ ### 2.0.1
861
+
862
+ #### Major changes
863
+
864
+ None.
865
+
866
+ #### Minor changes
867
+
868
+ - Now running a linter with eslint and TypeScript and a stricter configuration to prevent type errors
869
+
870
+ #### Bug & security fixes
871
+
872
+ - Fixed an error while the error boundary was not using an override when using a stricter typescript configuration
873
+
874
+ ### 2.0.0
875
+
876
+ #### Major changes
877
+
878
+ - The `transition` property in `createRouter` is now a function instead of a boolean, which allows for more control over the animation. This is a breaking change.
879
+ - A `slideFadeTransition` is now exported and can be used directly.
880
+
881
+ #### Minor changes
882
+
883
+ None.
884
+
885
+ #### Bug & security fixes
886
+
887
+ None.
888
+
1019
889
  ### 1.1.0
1020
890
 
1021
891
  #### Major changes
@@ -1088,4 +958,4 @@ None.
1088
958
 
1089
959
  #### Bug & security fixes
1090
960
 
1091
- None.
961
+ None.
package/index.tsx CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable */
1
2
  import { useEffect, useState, FunctionComponent, useMemo, Component, PropsWithChildren, createContext, SetStateAction, Dispatch, ReactNode, useContext, useCallback, memo, MouseEvent } from "react";
2
3
 
3
4
  export type AbsolutePath<Path extends string> =
@@ -25,8 +26,10 @@ export interface Page<Path extends string> {
25
26
  element: FunctionComponent<PageComponentProps<Path>>
26
27
  }
27
28
 
29
+ export type Transition = (direction: NavigationDirection, next: () => void) => void;
30
+
28
31
  export interface CreateRouterOptions<Path extends string> {
29
- transition?: boolean,
32
+ transition?: Transition,
30
33
  prefix?: string,
31
34
  pages: Array<Page<Path>>
32
35
  fallback: FunctionComponent
@@ -99,7 +102,7 @@ export interface IssueProps {
99
102
 
100
103
  export interface ErrorBoundaryProps {
101
104
  fallback: FunctionComponent<IssueProps>,
102
- transition: boolean
105
+ transition?: Transition
103
106
  }
104
107
 
105
108
  export interface ErrorBoundaryState {
@@ -113,13 +116,13 @@ export class ErrorBoundary extends Component<PropsWithChildren<ErrorBoundaryProp
113
116
  this.state = { error: null };
114
117
  }
115
118
 
116
- static getDerivedStateFromError(error: unknown) {
119
+ public static getDerivedStateFromError(error: unknown) {
117
120
  const normalizedError = error instanceof Error ? error : new Error(String(error));
118
121
 
119
122
  return { error: normalizedError };
120
123
  }
121
124
 
122
- render() {
125
+ public override render() {
123
126
  const viewTransitionSupported = typeof document.startViewTransition === "function";
124
127
 
125
128
  if (this.state.error) {
@@ -177,6 +180,11 @@ const Context = createContext<ContextInterface>({
177
180
  setHash: () => { }
178
181
  });
179
182
 
183
+ enum NavigationDirection {
184
+ Forward = "pushstate",
185
+ Backward = "popstate"
186
+ }
187
+
180
188
  export const useNavigateToPage = <Path extends string>(page: Page<Path>) => {
181
189
  const { prefix } = useContext(Context);
182
190
 
@@ -193,10 +201,16 @@ export const useNavigateToPage = <Path extends string>(page: Page<Path>) => {
193
201
  window.history.pushState(null, pathWithParameters, pathWithParameters);
194
202
  }
195
203
 
196
- window.dispatchEvent(new CustomEvent("popstate"));
197
- }, [page]);
204
+ window.dispatchEvent(new CustomEvent(NavigationDirection.Forward));
205
+ }, [page, prefix]);
198
206
  };
199
207
 
208
+ export const useNavigateBack = () => {
209
+ return useCallback(() => {
210
+ window.dispatchEvent(new CustomEvent(NavigationDirection.Backward));
211
+ }, []);
212
+ }
213
+
200
214
  export const useIsActivePage = (page: Page<string>) => {
201
215
  const { pathname, prefix } = useContext(Context);
202
216
 
@@ -223,12 +237,12 @@ export const useLink = <Path extends string>(page: Page<Path>) => {
223
237
  return Object.entries(parameters).reduce((previousPath, [parameterName, parameterValue]) => {
224
238
  return previousPath.replace(`:${parameterName}`, parameterValue);
225
239
  }, sanitizePath(`${prefix ?? ""}/${page.path}`));
226
- }, []);
240
+ }, [prefix, page, parameters]);
227
241
 
228
242
  const navigate = useCallback((event: MouseEvent) => {
229
243
  event.preventDefault();
230
244
  navigateToPage(parameters);
231
- }, []);
245
+ }, [navigateToPage, parameters]);
232
246
 
233
247
  return (
234
248
  <a
@@ -243,12 +257,45 @@ export const useLink = <Path extends string>(page: Page<Path>) => {
243
257
  return Link;
244
258
  };
245
259
 
246
- export const createRouter = <Path extends string>({ pages, fallback, transition: withViewTransition, issue, prefix }: CreateRouterOptions<Path>) => {
260
+ export const slideFadeTransition: Transition = async (direction: NavigationDirection, next) => {
261
+ const transition = document.startViewTransition(() => {
262
+ next();
263
+ });
264
+
265
+ await transition.ready;
266
+
267
+ document.documentElement.animate(
268
+ [
269
+ { transform: 'translateX(0)', opacity: 1 },
270
+ { transform: `translateX(${direction === NavigationDirection.Forward ? '100%' : '-100%'})`, opacity: 0 }
271
+ ],
272
+ {
273
+ duration: 250,
274
+ easing: "ease-in-out",
275
+ fill: "both",
276
+ pseudoElement: "::view-transition-old(root)",
277
+ }
278
+ );
279
+
280
+ document.documentElement.animate(
281
+ [
282
+ { transform: `translateX(${direction === NavigationDirection.Forward ? '-100%' : '100%'})`, opacity: 0 },
283
+ { transform: 'translateX(0)', opacity: 1 }
284
+ ],
285
+ {
286
+ duration: 250,
287
+ easing: "ease-in-out",
288
+ fill: "both",
289
+ pseudoElement: "::view-transition-new(root)",
290
+ }
291
+ );
292
+ }
293
+
294
+ export const createRouter = <Path extends string>({ pages, fallback, transition, issue, prefix }: CreateRouterOptions<Path>) => {
247
295
  const Provider = ({ children }: ProviderProps) => {
248
296
  const [pathname, setPathname] = useState(sanitizePath(window.location.pathname));
249
297
  const [search, setSearch] = useState(new URLSearchParams(sanitizePath(window.location.search)));
250
298
  const [hash, setHash] = useState(window.location.hash);
251
- const shouldTransitionBetweenPages = useMemo(() => typeof document.startViewTransition === "function" && withViewTransition ? true : false, [withViewTransition]);
252
299
 
253
300
  const value = useMemo(() => {
254
301
  return {
@@ -263,9 +310,9 @@ export const createRouter = <Path extends string>({ pages, fallback, transition:
263
310
  }, [prefix, pathname, search, hash]);
264
311
 
265
312
  useEffect(() => {
266
- const onWindowPopstate = () => {
267
- if (shouldTransitionBetweenPages) {
268
- document.startViewTransition(() => {
313
+ const onNavigation = async (direction: NavigationDirection) => {
314
+ if (transition) {
315
+ transition(direction, () => {
269
316
  setPathname(sanitizePath(window.location.pathname));
270
317
  });
271
318
 
@@ -273,18 +320,28 @@ export const createRouter = <Path extends string>({ pages, fallback, transition:
273
320
  }
274
321
 
275
322
  setPathname(sanitizePath(window.location.pathname));
323
+ }
324
+
325
+ const onNavigationForward = () => {
326
+ onNavigation(NavigationDirection.Forward);
276
327
  };
277
328
 
278
- window.addEventListener("popstate", onWindowPopstate);
329
+ const onNavigationBackward = () => {
330
+ onNavigation(NavigationDirection.Backward);
331
+ };
332
+
333
+ window.addEventListener(NavigationDirection.Forward, onNavigationForward);
334
+ window.addEventListener(NavigationDirection.Backward, onNavigationBackward);
279
335
 
280
336
  return () => {
281
- window.removeEventListener("popstate", onWindowPopstate);
337
+ window.removeEventListener(NavigationDirection.Forward, onNavigationForward);
338
+ window.removeEventListener(NavigationDirection.Backward, onNavigationBackward);
282
339
  }
283
340
  }, []);
284
341
 
285
342
  return (
286
343
  <Context.Provider value={value}>
287
- <ErrorBoundary fallback={issue} transition={shouldTransitionBetweenPages}>
344
+ <ErrorBoundary fallback={issue}>
288
345
  {children}
289
346
  </ErrorBoundary>
290
347
  </Context.Provider>
@@ -306,7 +363,9 @@ export const createRouter = <Path extends string>({ pages, fallback, transition:
306
363
 
307
364
  if (page) {
308
365
  return (
309
- <page.element parameters={parameters} />
366
+ <div >
367
+ <page.element parameters={parameters} />
368
+ </div>
310
369
  );
311
370
  }
312
371
 
@@ -319,4 +378,4 @@ export const createRouter = <Path extends string>({ pages, fallback, transition:
319
378
  View,
320
379
  Provider,
321
380
  };
322
- }
381
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "name": "@aminnairi/react-router",
4
4
  "description": "Type-safe router for the React library",
5
- "version": "1.1.0",
5
+ "version": "2.0.1",
6
6
  "homepage": "https://github.com/aminnairi/react-router#readme",
7
7
  "license": "MIT",
8
8
  "bugs": {
@@ -26,5 +26,14 @@
26
26
  ],
27
27
  "peerDependencies": {
28
28
  "react": ">=18.0.0"
29
+ },
30
+ "scripts": {
31
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0"
32
+ },
33
+ "devDependencies": {
34
+ "@typescript-eslint/eslint-plugin": "^7.2.0",
35
+ "@typescript-eslint/parser": "^7.2.0",
36
+ "eslint": "^8.57.0",
37
+ "eslint-plugin-react-hooks": "^4.6.0"
29
38
  }
30
- }
39
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "jsx": "react-jsx",
4
+ "target": "esnext",
5
+ "module": "esnext",
6
+ "moduleResolution": "node",
7
+ "strict": true,
8
+ "forceConsistentCasingInFileNames": true,
9
+ "noFallthroughCasesInSwitch": true,
10
+ "noUncheckedIndexedAccess": true,
11
+ "noImplicitOverride": true,
12
+ "exactOptionalPropertyTypes": true,
13
+ "esModuleInterop": true,
14
+ "isolatedModules": true
15
+ }
16
+ }