diff --git a/front/apps/mobile/app/(app)/search/index.tsx b/front/apps/mobile/app/(app)/search/index.tsx
index 4c0d1c88..ad3064f3 100644
--- a/front/apps/mobile/app/(app)/search/index.tsx
+++ b/front/apps/mobile/app/(app)/search/index.tsx
@@ -22,7 +22,7 @@ import { SearchPage } from "@kyoo/ui";
import { Stack, useLocalSearchParams } from "expo-router";
import { useTranslation } from "react-i18next";
import { createParam } from "solito";
-import { useRouter } from "solito/router";
+import { useRouter } from "@kyoo/primitives";
import { useTheme } from "yoshiki/native";
const { useParam } = createParam<{ q?: string }>();
diff --git a/front/packages/primitives/package.json b/front/packages/primitives/package.json
index b51482b6..a75c4a39 100644
--- a/front/packages/primitives/package.json
+++ b/front/packages/primitives/package.json
@@ -54,8 +54,7 @@
},
"dependencies": {
"@expo/html-elements": "^0.9.1",
- "@tanstack/react-query": "^5.17.19",
- "solito": "^4.2.0"
+ "@tanstack/react-query": "^5.17.19"
},
"optionalDependencies": {
"@radix-ui/react-select": "^2.0.0",
diff --git a/front/packages/primitives/src/index.ts b/front/packages/primitives/src/index.ts
index daec9bd5..c163e34f 100644
--- a/front/packages/primitives/src/index.ts
+++ b/front/packages/primitives/src/index.ts
@@ -42,3 +42,4 @@ export * from "./chip";
export * from "./utils";
export * from "./constants";
+export * from "./navigation/router";
diff --git a/front/packages/primitives/src/links.tsx b/front/packages/primitives/src/links.tsx
index 466463fa..b20c6f73 100644
--- a/front/packages/primitives/src/links.tsx
+++ b/front/packages/primitives/src/links.tsx
@@ -18,19 +18,10 @@
* along with Kyoo. If not, see .
*/
-import type { UrlObject } from "node:url";
-import { type ReactNode, forwardRef } from "react";
-import {
- Linking,
- Platform,
- Pressable,
- type PressableProps,
- type TextProps,
- type View,
-} from "react-native";
-import { TextLink, useLink } from "solito/link";
-import { parseNextPath } from "solito/router";
+import { forwardRef, type ReactNode } from "react";
+import { Platform, Pressable, type TextProps, type View, type PressableProps } from "react-native";
import { useTheme, useYoshiki } from "yoshiki/native";
+import type { UrlObject } from "node:url";
import { alpha } from "./themes";
export const A = ({
@@ -122,9 +113,7 @@ export const Link = ({
{...props}
onPress={(e?: any) => {
props?.onPress?.(e);
- if (e?.defaultPrevented) return;
- if (Platform.OS !== "web" && href?.includes("://")) Linking.openURL(href);
- else linkProps.onPress(e);
+ linkProps.onPress(e);
}}
>
{children}
diff --git a/front/packages/primitives/src/menu.tsx b/front/packages/primitives/src/menu.tsx
index 9e255dba..08fb17af 100644
--- a/front/packages/primitives/src/menu.tsx
+++ b/front/packages/primitives/src/menu.tsx
@@ -39,6 +39,7 @@ import { useRouter } from "solito/router";
import { percent, px, sm, useYoshiki, vh, xl } from "yoshiki/native";
import { Icon, IconButton } from "./icons";
import { PressableFeedback } from "./links";
+import { useRouter } from "./navigation/router";
import { P } from "./text";
import { ContrastArea, SwitchVariant } from "./themes";
import { ts } from "./utils";
diff --git a/front/packages/primitives/src/navigation/link.ts b/front/packages/primitives/src/navigation/link.ts
new file mode 100644
index 00000000..3825671f
--- /dev/null
+++ b/front/packages/primitives/src/navigation/link.ts
@@ -0,0 +1,44 @@
+import { type UrlObject, format } from "node:url";
+import type { MouseEvent } from "react";
+import { Platform, type PressableProps, Linking } from "react-native";
+import { useRouter } from "./router";
+
+export const useLink = (
+ route: string | UrlObject,
+ opts: { replace?: boolean; target?: "_blank"; isNested?: boolean } = {},
+) => {
+ const router = useRouter();
+ const href = typeof route === "object" ? format(route) : route;
+
+ return {
+ accessibilityRole: "link",
+ href,
+ onPress: (e) => {
+ if (e?.defaultPrevented) return;
+ if (Platform.OS !== "web" && href.includes("://")) {
+ Linking.openURL(href);
+ return;
+ }
+
+ if (Platform.OS === "web" && e) {
+ // Web event
+ const we = e as unknown as MouseEvent;
+ if (
+ // ignore clicks with modifier keys
+ we.metaKey ||
+ we.altKey ||
+ we.ctrlKey ||
+ we.shiftKey ||
+ // ignore everything but left clicks
+ we.button !== null ||
+ we.button !== 0
+ )
+ return;
+ }
+
+ e.preventDefault();
+ if (opts.replace === true) router.replace(href, { isNested: opts.isNested });
+ else router.push(href);
+ },
+ } satisfies PressableProps & { href?: string };
+};
diff --git a/front/packages/primitives/src/navigation/router.ts b/front/packages/primitives/src/navigation/router.ts
new file mode 100644
index 00000000..b70e4c69
--- /dev/null
+++ b/front/packages/primitives/src/navigation/router.ts
@@ -0,0 +1,18 @@
+import { navigate } from "vike/client/router";
+import { type UrlObject, format } from "node:url";
+
+export const useRouter = () => {
+ return {
+ push: (route: string | UrlObject) => {
+ if (typeof route === "object") route = format(route);
+ navigate(route);
+ },
+ replace: (route: string | UrlObject, opts: {isNested?: boolean} = {}) => {
+ if (typeof route === "object") route = format(route);
+ navigate(route, { overwriteLastHistoryEntry: opts.isNested });
+ },
+ back: () => {
+ window.history.back();
+ },
+ };
+};
diff --git a/front/packages/primitives/src/navigation/router.web.ts b/front/packages/primitives/src/navigation/router.web.ts
new file mode 100644
index 00000000..b683eae0
--- /dev/null
+++ b/front/packages/primitives/src/navigation/router.web.ts
@@ -0,0 +1,18 @@
+import { navigate } from "vike/client/router";
+import { type UrlObject, format } from "node:url";
+
+export const useRouter = () => {
+ return {
+ push: (route: string | UrlObject) => {
+ if (typeof route === "object") route = format(route);
+ navigate(route);
+ },
+ replace: (route: string | UrlObject, isNested = true) => {
+ if (typeof route === "object") route = format(route);
+ navigate(route, { overwriteLastHistoryEntry: true });
+ },
+ back: () => {
+ window.history.back();
+ },
+ };
+};
diff --git a/front/packages/ui/src/login/login.tsx b/front/packages/ui/src/login/login.tsx
index 9a53710c..cfdbea09 100644
--- a/front/packages/ui/src/login/login.tsx
+++ b/front/packages/ui/src/login/login.tsx
@@ -44,10 +44,7 @@ export const LoginPage: QueryPage<{ apiUrl?: string; error?: string }> = ({
const { css } = useYoshiki();
useEffect(() => {
- if (!apiUrl && Platform.OS !== "web")
- router.replace("/server-url", undefined, {
- experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
- });
+ if (!apiUrl && Platform.OS !== "web") router.replace("/server-url", false);
}, [apiUrl, router]);
return (
@@ -73,9 +70,7 @@ export const LoginPage: QueryPage<{ apiUrl?: string; error?: string }> = ({
});
setError(error);
if (error) return;
- router.replace("/", undefined, {
- experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
- });
+ router.replace("/", false);
}}
{...css({
m: ts(1),
diff --git a/front/packages/ui/src/login/oidc.tsx b/front/packages/ui/src/login/oidc.tsx
index 1f06ad5a..44b31e3e 100644
--- a/front/packages/ui/src/login/oidc.tsx
+++ b/front/packages/ui/src/login/oidc.tsx
@@ -26,12 +26,11 @@ import {
oidcLogin,
useFetch,
} from "@kyoo/models";
-import { Button, HR, Link, P, Skeleton, ts } from "@kyoo/primitives";
-import { useEffect, useRef } from "react";
-import { useTranslation } from "react-i18next";
-import { ImageBackground, View } from "react-native";
-import { useRouter } from "solito/router";
+import { useRouter, Button, HR, Link, P, Skeleton, ts } from "@kyoo/primitives";
+import { View, ImageBackground } from "react-native";
import { percent, rem, useYoshiki } from "yoshiki/native";
+import { useTranslation } from "react-i18next";
+import { useEffect, useRef } from "react";
import { ErrorView } from "../errors";
export const OidcLogin = ({ apiUrl }: { apiUrl?: string }) => {
@@ -105,18 +104,12 @@ export const OidcCallbackPage: QueryPage<{
hasRun.current = true;
function onError(error: string) {
- router.replace({ pathname: "/login", query: { error, apiUrl } }, undefined, {
- experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
- });
+ router.replace({ pathname: "/login", query: { error, apiUrl } }, false);
}
async function run() {
const { error: loginError } = await oidcLogin(provider, code, apiUrl);
if (loginError) onError(loginError);
- else {
- router.replace("/", undefined, {
- experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
- });
- }
+ else router.replace("/", false);
}
if (error) onError(error);
diff --git a/front/packages/ui/src/login/register.tsx b/front/packages/ui/src/login/register.tsx
index 23d2c772..efca0d1e 100644
--- a/front/packages/ui/src/login/register.tsx
+++ b/front/packages/ui/src/login/register.tsx
@@ -18,13 +18,11 @@
* along with Kyoo. If not, see .
*/
-import { type QueryPage, login } from "@kyoo/models";
-import { A, Button, H1, Input, P, ts } from "@kyoo/primitives";
+import { login, type QueryPage } from "@kyoo/models";
+import { Button, P, Input, ts, H1, A, useRouter } from "@kyoo/primitives";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Trans } from "react-i18next";
-import { Platform } from "react-native";
-import { useRouter } from "solito/router";
import { percent, px, useYoshiki } from "yoshiki/native";
import { DefaultLayout } from "../layout";
import { FormPage } from "./form";
@@ -43,10 +41,7 @@ export const RegisterPage: QueryPage<{ apiUrl?: string }> = ({ apiUrl }) => {
const { css } = useYoshiki();
useEffect(() => {
- if (!apiUrl && Platform.OS !== "web")
- router.replace("/server-url", undefined, {
- experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
- });
+ if (!apiUrl && Platform.OS !== "web") router.replace("/server-url", false);
}, [apiUrl, router]);
return (
@@ -84,9 +79,7 @@ export const RegisterPage: QueryPage<{ apiUrl?: string }> = ({ apiUrl }) => {
const { error } = await login("register", { email, username, password, apiUrl });
setError(error);
if (error) return;
- router.replace("/", undefined, {
- experimental: { nativeBehavior: "stack-replace", isNestedNavigator: false },
- });
+ router.replace("/", false);
}}
{...css({
m: ts(1),
diff --git a/front/packages/ui/src/login/server-url.tsx b/front/packages/ui/src/login/server-url.tsx
index a2b6ad33..73625081 100644
--- a/front/packages/ui/src/login/server-url.tsx
+++ b/front/packages/ui/src/login/server-url.tsx
@@ -25,11 +25,10 @@ import {
ServerInfoP,
useFetch,
} from "@kyoo/models";
-import { Button, H1, HR, Input, Link, P, ts } from "@kyoo/primitives";
+import { useRouter, Button, P, Link, Input, ts, H1, HR } from "@kyoo/primitives";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { ImageBackground, Platform, View } from "react-native";
-import { useRouter } from "solito/router";
import { type Theme, percent, useYoshiki } from "yoshiki/native";
import { DefaultLayout } from "../layout";
diff --git a/front/packages/ui/src/player/components/hover.tsx b/front/packages/ui/src/player/components/hover.tsx
index db0f8b45..716f0033 100644
--- a/front/packages/ui/src/player/components/hover.tsx
+++ b/front/packages/ui/src/player/components/hover.tsx
@@ -18,7 +18,6 @@
* along with Kyoo. If not, see .
*/
-import type { Audio, Chapter, KyooImage, Subtitle } from "@kyoo/models";
import {
CircularProgress,
ContrastArea,
@@ -35,15 +34,16 @@ import {
tooltip,
ts,
useIsTouch,
+ useRouter,
} from "@kyoo/primitives";
-import ArrowBack from "@material-symbols/svg-400/rounded/arrow_back-fill.svg";
-import { useAtom, useAtomValue, useSetAtom } from "jotai";
-import { atom } from "jotai";
-import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
-import { useTranslation } from "react-i18next";
+import type { Chapter, KyooImage, Subtitle, Audio } from "@kyoo/models";
+import { useAtomValue, useSetAtom, useAtom } from "jotai";
import { type ImageStyle, Platform, Pressable, View, type ViewProps } from "react-native";
-import { useRouter } from "solito/router";
+import { useTranslation } from "react-i18next";
import { percent, rem, useYoshiki } from "yoshiki/native";
+import ArrowBack from "@material-symbols/svg-400/rounded/arrow_back-fill.svg";
+import { type ReactNode, useCallback, useEffect, useRef, useState } from "react";
+import { atom } from "jotai";
import {
bufferedAtom,
durationAtom,
diff --git a/front/packages/ui/src/player/index.tsx b/front/packages/ui/src/player/index.tsx
index bd496e29..01a1fdad 100644
--- a/front/packages/ui/src/player/index.tsx
+++ b/front/packages/ui/src/player/index.tsx
@@ -28,12 +28,11 @@ import {
WatchInfoP,
useFetch,
} from "@kyoo/models";
-import { Head } from "@kyoo/primitives";
-import { useSetAtom } from "jotai";
-import { type ComponentProps, useEffect, useState } from "react";
-import { useTranslation } from "react-i18next";
+import { Head, useRouter } from "@kyoo/primitives";
+import { useState, useEffect, type ComponentProps } from "react";
import { Platform, StyleSheet, View } from "react-native";
-import { useRouter } from "solito/router";
+import { useTranslation } from "react-i18next";
+import { useSetAtom } from "jotai";
import { useYoshiki } from "yoshiki/native";
import { episodeDisplayNumber } from "../details/episode";
import { ErrorView } from "../errors";
@@ -159,14 +158,8 @@ export const Player = ({
startTime={startTime}
onEnd={() => {
if (!data) return;
- if (data.type === "movie")
- router.replace(`/movie/${data.slug}`, undefined, {
- experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
- });
- else
- router.replace(next ?? `/show/${data.show!.slug}`, undefined, {
- experimental: { nativeBehavior: "stack-replace", isNestedNavigator: true },
- });
+ if (data.type === "movie") router.replace(`/movie/${data.slug}`, true);
+ else router.replace(next ?? `/show/${data.show!.slug}`, true);
}}
{...css(StyleSheet.absoluteFillObject)}
/>
diff --git a/front/packages/ui/src/player/keyboard.tsx b/front/packages/ui/src/player/keyboard.tsx
index 5cd828b9..6fa18483 100644
--- a/front/packages/ui/src/player/keyboard.tsx
+++ b/front/packages/ui/src/player/keyboard.tsx
@@ -20,9 +20,9 @@
import type { Subtitle } from "@kyoo/models";
import { atom, useSetAtom } from "jotai";
+import { useRouter } from "@kyoo/primitives";
import { useEffect } from "react";
import { Platform } from "react-native";
-import { useRouter } from "solito/router";
import {
durationAtom,
fullscreenAtom,
diff --git a/front/packages/ui/src/player/media-session.tsx b/front/packages/ui/src/player/media-session.tsx
index 07f3f588..58b90eb6 100644
--- a/front/packages/ui/src/player/media-session.tsx
+++ b/front/packages/ui/src/player/media-session.tsx
@@ -19,8 +19,8 @@
*/
import { useAtom, useAtomValue, useSetAtom } from "jotai";
+import { useRouter } from "@kyoo/primitives";
import { useEffect } from "react";
-import { useRouter } from "solito/router";
import { reducerAtom } from "./keyboard";
import { durationAtom, playAtom, progressAtom } from "./state";