@aminnairi/react-router 1.1.0 → 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.
Files changed (3) hide show
  1. package/README.md +72 -139
  2. package/index.tsx +71 -13
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -60,7 +60,7 @@ export const Fallback = () => {
60
60
  const navigateToHomePage = useNavigateToPage(home);
61
61
 
62
62
  return (
63
- <button onClick={navigateToHomePage}>
63
+ <button onClick={() => navigateToHomePage({})}>
64
64
  Go back home
65
65
  </button>
66
66
  );
@@ -82,7 +82,7 @@ export const Issue = () => {
82
82
  return (
83
83
  <Fragment>
84
84
  <h1>An issue occurred</h1>
85
- <button onClick={home.navigate}>
85
+ <button onClick={() => navigateToHomePage({})}>
86
86
  Go back home
87
87
  </button>
88
88
  </Fragment>
@@ -94,11 +94,11 @@ export const Issue = () => {
94
94
  touch src/router/index.ts
95
95
  ```
96
96
 
97
- ```tsx
97
+ ```ts
98
98
  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";
99
+ import { Fallback } from "./fallback";
100
+ import { Issue } from "./issue";
101
+ import { home } from "./pages/home";
102
102
 
103
103
  export const router = createRouter({
104
104
  fallback: Fallback,
@@ -259,7 +259,7 @@ const about = createPage({
259
259
  <h1>
260
260
  About Us
261
261
  </h1>
262
- <button onClick={navigateToLoginPage}>
262
+ <button onClick={() => navigateToLoginPage({})}>
263
263
  Login
264
264
  </button>
265
265
  </Fragment>
@@ -277,7 +277,7 @@ createPage({
277
277
  <h1>
278
278
  Home
279
279
  </h1>
280
- <button onClick={navigateToAboutPage}>
280
+ <button onClick={() => navigateToAboutPage({})}>
281
281
  About Us
282
282
  </button>
283
283
  </Fragment>
@@ -385,12 +385,14 @@ root.render(
385
385
 
386
386
  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
387
 
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.
388
+ 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.
389
+
390
+ This library also exports a `slideFadeTransition` that you can use out-of-the-box.
389
391
 
390
392
  ```tsx
391
393
  import { Fragment, StrictMode } from "react";
392
394
  import { createRoot } from "react-dom/client";
393
- import { createRouter, createPage } from "@aminnairi/react-router";
395
+ import { createRouter, createPage, slideFadeTransition } from "@aminnairi/react-router";
394
396
 
395
397
  const home = createPage({
396
398
  path: "/",
@@ -402,7 +404,7 @@ const home = createPage({
402
404
  });
403
405
 
404
406
  const router = createRouter({
405
- transition: true,
407
+ transition: slideFadeTransition,
406
408
  fallback: () => (
407
409
  <h1>Not found</h1>
408
410
  ),
@@ -464,18 +466,15 @@ const home = createPage({
464
466
  });
465
467
 
466
468
  const router = createRouter({
467
- transition: true,
468
469
  fallback: () => (
469
470
  <h1>Not found</h1>
470
471
  ),
471
472
  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
- );
473
+ <Fragment>
474
+ <h1>Error</h1>
475
+ <p>{error.message}</p>
476
+ <button onClick={reset}>Reset</button>
477
+ </Fragment>
479
478
  ),
480
479
  pages: [
481
480
  home
@@ -538,17 +537,14 @@ const Fallback = () => {
538
537
  }
539
538
 
540
539
  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
- );
540
+ <Fragment>
541
+ <h1>Error</h1>
542
+ <p>{error.message}</p>
543
+ <button onClick={reset}>Reset</button>
544
+ </Fragment>
548
545
  ));
549
546
 
550
547
  const router = createRouter({
551
- transition: true,
552
548
  fallback: Fallback,
553
549
  issue: Issue,
554
550
  pages: [
@@ -582,7 +578,9 @@ const App = () => {
582
578
 
583
579
  root.render(
584
580
  <StrictMode>
585
- <App />
581
+ <router.Provider>
582
+ <App />
583
+ </router.Provider>
586
584
  </StrictMode>
587
585
  );
588
586
  ```
@@ -611,7 +609,7 @@ const Fallback = () => {
611
609
  return (
612
610
  <Fragment>
613
611
  <h1>Not found</h1>
614
- <button onClick={navigateToHomePage}>
612
+ <button onClick={() => navigateToHomePage({})}>
615
613
  Go Back Home
616
614
  </button>
617
615
  </Fragment>
@@ -619,18 +617,15 @@ const Fallback = () => {
619
617
  }
620
618
 
621
619
  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
- );
620
+ <Fragment>
621
+ <h1>Error</h1>
622
+ <p>{error.message}</p>
623
+ <button onClick={reset}>Reset</button>
624
+ </Fragment>
629
625
  ));
630
626
 
631
627
  const router = createRouter({
632
628
  prefix: "/portfolio",
633
- transition: true,
634
629
  fallback: Fallback,
635
630
  issue: Issue,
636
631
  pages: [
@@ -664,7 +659,9 @@ const App = () => {
664
659
 
665
660
  root.render(
666
661
  <StrictMode>
667
- <App />
662
+ <router.Provider>
663
+ <App />
664
+ </router.Provider>
668
665
  </StrictMode>
669
666
  );
670
667
  ```
@@ -679,7 +676,7 @@ It accepts a page that has been created using `createPage`.
679
676
  import { Fragment } from "react";
680
677
  import { createPage, useNavigateToPage } from "@aminnairi/react-router";
681
678
 
682
- const home = createPath({
679
+ const home = createPage({
683
680
  path: "/",
684
681
  element: function Home() {
685
682
  return (
@@ -696,7 +693,7 @@ createPage({
696
693
  return (
697
694
  <Fragment>
698
695
  <h1>About</h1>
699
- <button onClick={navigateToHomePage}>Home</button>
696
+ <button onClick={() => navigateToHomePage({})}>Home</button>
700
697
  </Fragment>
701
698
  );
702
699
  }
@@ -711,7 +708,7 @@ The parameters should always be provided as string, as they are the only data ty
711
708
  import { Fragment } from "react";
712
709
  import { createPage, useNavigateToPage } from "@aminnairi/react-router";
713
710
 
714
- const user = createPath({
711
+ const user = createPage({
715
712
  path: "/users/:user",
716
713
  element: function User({ parameters: { user }}) {
717
714
  return (
@@ -745,7 +742,7 @@ The created component is simply a `<a href="...">{children}</a>` under the hood
745
742
  import { Fragment } from "react";
746
743
  import { createPage, useLink } from "@aminnairi/react-router";
747
744
 
748
- const user = createPath({
745
+ const user = createPage({
749
746
  path: "/users/:user",
750
747
  element: function User({ parameters: { user }}) {
751
748
  return (
@@ -782,7 +779,7 @@ import { createPage, useSearch } from "@aminnairi/react-router";
782
779
  createPage({
783
780
  path: "/users",
784
781
  element: function Home() {
785
- const [search] = useSearch();
782
+ const search = useSearch();
786
783
  const sortedByDate = useMemo(() => search.get("sort-by") === "date", [search]);
787
784
 
788
785
  return (
@@ -825,13 +822,13 @@ This function is mainly used in the internals of the `createRouter` and in most
825
822
  ```typescript
826
823
  import { doesRouteMatchPath } from "@aminnairi/react-router";
827
824
 
828
- doesRoutePatchPath("/", "/"); // true
825
+ doesRouteMatchPath("/", "/"); // true
829
826
 
830
- doesRoutePatchPath("/", "/about"); // false
827
+ doesRouteMatchPath("/", "/about"); // false
831
828
 
832
- doesRoutePatchPath("/users/:user", "/users/123"); // true
829
+ doesRouteMatchPath("/users/:user", "/users/123"); // true
833
830
 
834
- doesRoutePatchPath("/users/:user", "/users/123/articles"); // false
831
+ doesRouteMatchPath("/users/:user", "/users/123/articles"); // false
835
832
  ```
836
833
 
837
834
  You can also optionally provide a prefix.
@@ -839,13 +836,13 @@ You can also optionally provide a prefix.
839
836
  ```typescript
840
837
  import { doesRouteMatchPath } from "@aminnairi/react-router";
841
838
 
842
- doesRoutePatchPath("/", "/github", "/github"); // true
839
+ doesRouteMatchPath("/", "/github", "/github"); // true
843
840
 
844
- doesRoutePatchPath("/", "/github/about", "/github"); // false
841
+ doesRouteMatchPath("/", "/github/about", "/github"); // false
845
842
 
846
- doesRoutePatchPath("/users/:user", "/github/users/123", "/github"); // true
843
+ doesRouteMatchPath("/users/:user", "/github/users/123", "/github"); // true
847
844
 
848
- doesRoutePatchPath("/users/:user", "/github/users/123/articles", "/github"); // false
845
+ doesRouteMatchPath("/users/:user", "/github/users/123/articles", "/github"); // false
849
846
  ```
850
847
 
851
848
  ### getParameters
@@ -857,13 +854,13 @@ This function is mainly used in the internals of the `createRouter` and in most
857
854
  ```typescript
858
855
  import { getParameters } from "@aminnairi/react-router";
859
856
 
860
- getParameters("/", "/"); // object
857
+ getParameters("/", "/"); // {}
861
858
 
862
- getParameters("/", "/about"); // object
859
+ getParameters("/", "/about"); // {}
863
860
 
864
861
  getParameters("/users/:user", "/users/123"); // { user: "123" }
865
862
 
866
- getParameters("/users/:user", "/users/123/articles"); // { user: "123" }
863
+ getParameters("/users/:user", "/users/123/articles"); // {}
867
864
  ```
868
865
 
869
866
  You can also provide an optional prefix.
@@ -871,91 +868,13 @@ You can also provide an optional prefix.
871
868
  ```typescript
872
869
  import { getParameters } from "@aminnairi/react-router";
873
870
 
874
- getParameters("/", "/github", "/github"); // object
871
+ getParameters("/", "/github", "/github"); // {}
875
872
 
876
- getParameters("/", "/github/about", "/github"); // object
873
+ getParameters("/", "/github/about", "/github"); // {}
877
874
 
878
875
  getParameters("/users/:user", "/github/users/123", "/github"); // { user: "123" }
879
876
 
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
- }
877
+ getParameters("/users/:user", "/github/users/123/articles", "/github"); // {}
959
878
  ```
960
879
 
961
880
  ### sanitizePath
@@ -994,9 +913,7 @@ This means that you can use this library with other popular solutions for handli
994
913
 
995
914
  ### Transition
996
915
 
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.
916
+ 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
917
 
1001
918
  ### Error handling
1002
919
 
@@ -1010,12 +927,28 @@ See [`LICENSE`](./LICENSE).
1010
927
 
1011
928
  ### Versions
1012
929
 
930
+ - [`2.0.0`](#200)
1013
931
  - [`1.1.0`](#110)
1014
932
  - [`1.0.1`](#101)
1015
933
  - [`1.0.0`](#100)
1016
934
  - [`0.1.1`](#011)
1017
935
  - [`0.1.0`](#010)
1018
936
 
937
+ ### 2.0.0
938
+
939
+ #### Major changes
940
+
941
+ - 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.
942
+ - A `slideFadeTransition` is now exported and can be used directly.
943
+
944
+ #### Minor changes
945
+
946
+ None.
947
+
948
+ #### Bug & security fixes
949
+
950
+ None.
951
+
1019
952
  ### 1.1.0
1020
953
 
1021
954
  #### Major changes
@@ -1088,4 +1021,4 @@ None.
1088
1021
 
1089
1022
  #### Bug & security fixes
1090
1023
 
1091
- None.
1024
+ None.
package/index.tsx CHANGED
@@ -25,8 +25,10 @@ export interface Page<Path extends string> {
25
25
  element: FunctionComponent<PageComponentProps<Path>>
26
26
  }
27
27
 
28
+ export type Transition = (direction: NavigationDirection, next: () => void) => void;
29
+
28
30
  export interface CreateRouterOptions<Path extends string> {
29
- transition?: boolean,
31
+ transition?: Transition,
30
32
  prefix?: string,
31
33
  pages: Array<Page<Path>>
32
34
  fallback: FunctionComponent
@@ -99,7 +101,7 @@ export interface IssueProps {
99
101
 
100
102
  export interface ErrorBoundaryProps {
101
103
  fallback: FunctionComponent<IssueProps>,
102
- transition: boolean
104
+ transition?: Transition
103
105
  }
104
106
 
105
107
  export interface ErrorBoundaryState {
@@ -177,6 +179,11 @@ const Context = createContext<ContextInterface>({
177
179
  setHash: () => { }
178
180
  });
179
181
 
182
+ enum NavigationDirection {
183
+ Forward = "pushstate",
184
+ Backward = "popstate"
185
+ }
186
+
180
187
  export const useNavigateToPage = <Path extends string>(page: Page<Path>) => {
181
188
  const { prefix } = useContext(Context);
182
189
 
@@ -193,10 +200,16 @@ export const useNavigateToPage = <Path extends string>(page: Page<Path>) => {
193
200
  window.history.pushState(null, pathWithParameters, pathWithParameters);
194
201
  }
195
202
 
196
- window.dispatchEvent(new CustomEvent("popstate"));
203
+ window.dispatchEvent(new CustomEvent(NavigationDirection.Forward));
197
204
  }, [page]);
198
205
  };
199
206
 
207
+ export const useNavigateBack = () => {
208
+ return useCallback(() => {
209
+ window.dispatchEvent(new CustomEvent(NavigationDirection.Backward));
210
+ }, []);
211
+ }
212
+
200
213
  export const useIsActivePage = (page: Page<string>) => {
201
214
  const { pathname, prefix } = useContext(Context);
202
215
 
@@ -243,12 +256,45 @@ export const useLink = <Path extends string>(page: Page<Path>) => {
243
256
  return Link;
244
257
  };
245
258
 
246
- export const createRouter = <Path extends string>({ pages, fallback, transition: withViewTransition, issue, prefix }: CreateRouterOptions<Path>) => {
259
+ export const slideFadeTransition: Transition = async (direction: NavigationDirection, next) => {
260
+ const transition = document.startViewTransition(() => {
261
+ next();
262
+ });
263
+
264
+ await transition.ready;
265
+
266
+ document.documentElement.animate(
267
+ [
268
+ { transform: 'translateX(0)', opacity: 1 },
269
+ { transform: `translateX(${direction === NavigationDirection.Forward ? '100%' : '-100%'})`, opacity: 0 }
270
+ ],
271
+ {
272
+ duration: 250,
273
+ easing: "ease-in-out",
274
+ fill: "both",
275
+ pseudoElement: "::view-transition-old(root)",
276
+ }
277
+ );
278
+
279
+ document.documentElement.animate(
280
+ [
281
+ { transform: `translateX(${direction === NavigationDirection.Forward ? '-100%' : '100%'})`, opacity: 0 },
282
+ { transform: 'translateX(0)', opacity: 1 }
283
+ ],
284
+ {
285
+ duration: 250,
286
+ easing: "ease-in-out",
287
+ fill: "both",
288
+ pseudoElement: "::view-transition-new(root)",
289
+ }
290
+ );
291
+ }
292
+
293
+ export const createRouter = <Path extends string>({ pages, fallback, transition, issue, prefix }: CreateRouterOptions<Path>) => {
247
294
  const Provider = ({ children }: ProviderProps) => {
248
295
  const [pathname, setPathname] = useState(sanitizePath(window.location.pathname));
249
296
  const [search, setSearch] = useState(new URLSearchParams(sanitizePath(window.location.search)));
250
297
  const [hash, setHash] = useState(window.location.hash);
251
- const shouldTransitionBetweenPages = useMemo(() => typeof document.startViewTransition === "function" && withViewTransition ? true : false, [withViewTransition]);
252
298
 
253
299
  const value = useMemo(() => {
254
300
  return {
@@ -263,9 +309,9 @@ export const createRouter = <Path extends string>({ pages, fallback, transition:
263
309
  }, [prefix, pathname, search, hash]);
264
310
 
265
311
  useEffect(() => {
266
- const onWindowPopstate = () => {
267
- if (shouldTransitionBetweenPages) {
268
- document.startViewTransition(() => {
312
+ const onNavigation = async (direction: NavigationDirection) => {
313
+ if (transition) {
314
+ transition(direction, () => {
269
315
  setPathname(sanitizePath(window.location.pathname));
270
316
  });
271
317
 
@@ -273,18 +319,28 @@ export const createRouter = <Path extends string>({ pages, fallback, transition:
273
319
  }
274
320
 
275
321
  setPathname(sanitizePath(window.location.pathname));
322
+ }
323
+
324
+ const onNavigationForward = () => {
325
+ onNavigation(NavigationDirection.Forward);
276
326
  };
277
327
 
278
- window.addEventListener("popstate", onWindowPopstate);
328
+ const onNavigationBackward = () => {
329
+ onNavigation(NavigationDirection.Backward);
330
+ };
331
+
332
+ window.addEventListener(NavigationDirection.Forward, onNavigationForward);
333
+ window.addEventListener(NavigationDirection.Backward, onNavigationBackward);
279
334
 
280
335
  return () => {
281
- window.removeEventListener("popstate", onWindowPopstate);
336
+ window.removeEventListener(NavigationDirection.Forward, onNavigationForward);
337
+ window.removeEventListener(NavigationDirection.Backward, onNavigationBackward);
282
338
  }
283
339
  }, []);
284
340
 
285
341
  return (
286
342
  <Context.Provider value={value}>
287
- <ErrorBoundary fallback={issue} transition={shouldTransitionBetweenPages}>
343
+ <ErrorBoundary fallback={issue}>
288
344
  {children}
289
345
  </ErrorBoundary>
290
346
  </Context.Provider>
@@ -306,7 +362,9 @@ export const createRouter = <Path extends string>({ pages, fallback, transition:
306
362
 
307
363
  if (page) {
308
364
  return (
309
- <page.element parameters={parameters} />
365
+ <div >
366
+ <page.element parameters={parameters} />
367
+ </div>
310
368
  );
311
369
  }
312
370
 
@@ -319,4 +377,4 @@ export const createRouter = <Path extends string>({ pages, fallback, transition:
319
377
  View,
320
378
  Provider,
321
379
  };
322
- }
380
+ }
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.0",
6
6
  "homepage": "https://github.com/aminnairi/react-router#readme",
7
7
  "license": "MIT",
8
8
  "bugs": {
@@ -27,4 +27,4 @@
27
27
  "peerDependencies": {
28
28
  "react": ">=18.0.0"
29
29
  }
30
- }
30
+ }