diff --git a/examples/with-fallback-element/ice.config.mts b/examples/with-fallback-element/ice.config.mts new file mode 100644 index 0000000000..63f3c0bcab --- /dev/null +++ b/examples/with-fallback-element/ice.config.mts @@ -0,0 +1,5 @@ +import { defineConfig } from '@ice/app'; + +export default defineConfig(() => ({ + ssg: false, +})); diff --git a/examples/with-fallback-element/package.json b/examples/with-fallback-element/package.json new file mode 100644 index 0000000000..d77d0fdb84 --- /dev/null +++ b/examples/with-fallback-element/package.json @@ -0,0 +1,20 @@ +{ + "name": "@ice/example-with-fallback-element", + "private": true, + "version": "1.0.0", + "description": "Example demonstrating custom fallbackElement configuration in app.tsx", + "dependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0", + "@ice/runtime": "workspace:*" + }, + "devDependencies": { + "@ice/app": "workspace:*", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0" + }, + "scripts": { + "start": "ice start", + "build": "ice build" + } +} diff --git a/examples/with-fallback-element/src/app.tsx b/examples/with-fallback-element/src/app.tsx new file mode 100644 index 0000000000..b65ceaa975 --- /dev/null +++ b/examples/with-fallback-element/src/app.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { defineAppConfig } from 'ice'; + +// Custom fallback element. +const CustomFallback = () => ( +
+
+
🔄 Loading...
+
+ Custom fallback element from app.tsx configuration +
+
+
+); + +export default defineAppConfig({ + app: { + rootId: 'app', + }, + router: { + type: 'hash', + fallbackElement: , + }, +}); diff --git a/examples/with-fallback-element/src/pages/home.tsx b/examples/with-fallback-element/src/pages/home.tsx new file mode 100644 index 0000000000..b49ca46911 --- /dev/null +++ b/examples/with-fallback-element/src/pages/home.tsx @@ -0,0 +1,14 @@ +import React from 'react'; + +export default function Home() { + return ( +
+

+ Home Page +

+
+ Welcome to the Home Page! +
+
+ ); +} diff --git a/examples/with-fallback-element/src/pages/index.tsx b/examples/with-fallback-element/src/pages/index.tsx new file mode 100644 index 0000000000..457352fd03 --- /dev/null +++ b/examples/with-fallback-element/src/pages/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Link } from 'ice'; + +export default function Home() { + return ( +
+

+ Test Page +

+
+ Go to Home Page +
+
+ ); +} diff --git a/examples/with-fallback-element/src/pages/layout.tsx b/examples/with-fallback-element/src/pages/layout.tsx new file mode 100644 index 0000000000..444ff1d4b2 --- /dev/null +++ b/examples/with-fallback-element/src/pages/layout.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { useNavigation, Outlet } from 'ice'; + +export default function Home() { + const navigation = useNavigation(); + // Use navigation state to determine loading status, and show loading indicator. + const isLoading = navigation.state === 'loading'; + return ( +
+ {isLoading && (
loading
)} + +
+ ); +} diff --git a/packages/runtime/src/ClientRouter.tsx b/packages/runtime/src/ClientRouter.tsx index b11ac3aa32..d60335a709 100644 --- a/packages/runtime/src/ClientRouter.tsx +++ b/packages/runtime/src/ClientRouter.tsx @@ -25,7 +25,7 @@ function createRouterHistory(history: History, router: Router) { let router: Router = null; function ClientRouter(props: ClientAppRouterProps) { const { Component, routerContext } = props; - const { revalidate } = useAppContext(); + const { revalidate, appConfig } = useAppContext(); function clearRouter() { if (router) { @@ -57,7 +57,8 @@ function ClientRouter(props: ClientAppRouterProps) { let element: React.ReactNode; if (process.env.ICE_CORE_ROUTER === 'true') { - element = ; + const fallbackElement = appConfig?.router?.fallbackElement ?? null; + element = ; } else { element = ; } diff --git a/packages/runtime/src/appConfig.ts b/packages/runtime/src/appConfig.ts index be66c3212c..7b55c43029 100644 --- a/packages/runtime/src/appConfig.ts +++ b/packages/runtime/src/appConfig.ts @@ -7,6 +7,7 @@ const defaultAppConfig: AppConfig = { }, router: { type: 'browser', + fallbackElement: null, }, }; diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index cd32ee4ab9..23caf34855 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -69,6 +69,7 @@ export interface AppConfig { type?: 'hash' | 'browser' | 'memory'; basename?: string; initialEntries?: InitialEntry[]; + fallbackElement?: React.ReactNode; }; } diff --git a/packages/runtime/tests/appConfig.test.ts b/packages/runtime/tests/appConfig.test.ts index bc3c9daee3..3937fadc74 100644 --- a/packages/runtime/tests/appConfig.test.ts +++ b/packages/runtime/tests/appConfig.test.ts @@ -17,6 +17,7 @@ describe('AppConfig', () => { strict: false, }, router: { + fallbackElement: null, type: 'browser', }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 115f1ef456..efab792227 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -802,6 +802,28 @@ importers: specifier: ^18.0.6 version: 18.0.11 + examples/with-fallback-element: + dependencies: + '@ice/runtime': + specifier: workspace:* + version: link:../../packages/runtime + react: + specifier: ^18.0.0 + version: 18.2.0 + react-dom: + specifier: ^18.0.0 + version: 18.2.0(react@18.2.0) + devDependencies: + '@ice/app': + specifier: workspace:* + version: link:../../packages/ice + '@types/react': + specifier: ^18.0.0 + version: 18.0.34 + '@types/react-dom': + specifier: ^18.0.0 + version: 18.0.11 + examples/with-fallback-entry: dependencies: '@ice/app':