mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 18:17:31 +08:00
fix(react): IonNav works with react (#25565)
Resolves #24002 Co-authored-by: Liam DeBeasi <liamdebeasi@icloud.com>
This commit is contained in:
@ -132,6 +132,7 @@ export { IonPopover } from './IonPopover';
|
||||
// Custom Components
|
||||
export { IonApp } from './IonApp';
|
||||
export { IonPage } from './IonPage';
|
||||
export { IonNav } from './navigation/IonNav';
|
||||
export { IonTabsContext, IonTabsContextState } from './navigation/IonTabsContext';
|
||||
export { IonTabs } from './navigation/IonTabs';
|
||||
export { IonTabBar } from './navigation/IonTabBar';
|
||||
|
@ -21,7 +21,15 @@ export const IonBackButton = /*@__PURE__*/ (() =>
|
||||
context!: React.ContextType<typeof NavContext>;
|
||||
|
||||
clickButton = (e: React.MouseEvent) => {
|
||||
/**
|
||||
* If ion-back-button is being used inside
|
||||
* of ion-nav then we should not interact with
|
||||
* the router.
|
||||
*/
|
||||
if (e.target && (e.target as HTMLElement).closest('ion-nav') !== null) { return; }
|
||||
|
||||
const { defaultHref, routerAnimation } = this.props;
|
||||
|
||||
if (this.context.hasIonicRouter()) {
|
||||
e.stopPropagation();
|
||||
this.context.goBack(defaultHref, routerAnimation);
|
||||
|
30
packages/react/src/components/navigation/IonNav.tsx
Normal file
30
packages/react/src/components/navigation/IonNav.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import type { FrameworkDelegate, JSX } from '@ionic/core/components';
|
||||
import { defineCustomElement } from '@ionic/core/components/ion-nav.js';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { ReactDelegate } from '../../framework-delegate';
|
||||
import { createReactComponent } from '../react-component-lib';
|
||||
|
||||
const IonNavInner = createReactComponent<
|
||||
JSX.IonNav & { delegate: FrameworkDelegate },
|
||||
HTMLIonNavElement
|
||||
>('ion-nav', undefined, undefined, defineCustomElement);
|
||||
|
||||
export const IonNav: React.FC<JSX.IonNav> = ({ children, ...restOfProps }) => {
|
||||
const [views, setViews] = useState<React.ReactPortal[]>([]);
|
||||
|
||||
/**
|
||||
* Allows us to create React components that are rendered within
|
||||
* the context of the IonNav component.
|
||||
*/
|
||||
const addView = (view: React.ReactPortal) => setViews([...views, view]);
|
||||
const removeView = (view: React.ReactPortal) => setViews(views.filter((v) => v !== view));
|
||||
|
||||
const delegate = ReactDelegate(addView, removeView);
|
||||
|
||||
return (
|
||||
<IonNavInner delegate={delegate} {...restOfProps}>
|
||||
{views}
|
||||
</IonNavInner>
|
||||
);
|
||||
};
|
38
packages/react/src/framework-delegate.tsx
Normal file
38
packages/react/src/framework-delegate.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { FrameworkDelegate } from '@ionic/core/components';
|
||||
import { createPortal } from 'react-dom';
|
||||
|
||||
export const ReactDelegate = (
|
||||
addView: (view: React.ReactPortal) => void,
|
||||
removeView: (view: React.ReactPortal) => void
|
||||
): FrameworkDelegate => {
|
||||
let Component: React.ReactPortal;
|
||||
|
||||
const attachViewToDom = async (
|
||||
parentElement: HTMLElement,
|
||||
component: () => JSX.Element,
|
||||
propsOrDataObj?: any,
|
||||
cssClasses?: string[]
|
||||
): Promise<any> => {
|
||||
const div = document.createElement('div');
|
||||
cssClasses && div.classList.add(...cssClasses);
|
||||
parentElement.appendChild(div);
|
||||
|
||||
Component = createPortal(component(), div);
|
||||
|
||||
Component.props = propsOrDataObj;
|
||||
|
||||
addView(Component);
|
||||
|
||||
return Promise.resolve(div);
|
||||
};
|
||||
|
||||
const removeViewFromDom = (): Promise<void> => {
|
||||
Component && removeView(Component);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
return {
|
||||
attachViewToDom,
|
||||
removeViewFromDom,
|
||||
};
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
describe('IonNav', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/navigation');
|
||||
});
|
||||
|
||||
it('should render the root page', () => {
|
||||
cy.get('ion-nav').contains('Page one content');
|
||||
});
|
||||
|
||||
it('should push a page', () => {
|
||||
cy.get('ion-button').contains('Go to Page Two').click();
|
||||
cy.get('#pageTwoContent').should('be.visible');
|
||||
cy.get('ion-nav').contains('Page two content');
|
||||
});
|
||||
|
||||
it('should pop a page', () => {
|
||||
cy.get('ion-button').contains('Go to Page Two').click();
|
||||
|
||||
cy.get('#pageTwoContent').should('be.visible');
|
||||
cy.get('ion-nav').contains('Page two content');
|
||||
|
||||
cy.get('.ion-page.can-go-back ion-back-button').click();
|
||||
|
||||
cy.get('#pageOneContent').should('be.visible');
|
||||
cy.get('ion-nav').contains('Page one content');
|
||||
});
|
||||
|
||||
});
|
@ -25,6 +25,7 @@ import Main from './pages/Main';
|
||||
import OverlayHooks from './pages/overlay-hooks/OverlayHooks';
|
||||
import OverlayComponents from './pages/overlay-components/OverlayComponents';
|
||||
import Tabs from './pages/Tabs';
|
||||
import NavComponent from './pages/navigation/NavComponent';
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
@ -35,6 +36,7 @@ const App: React.FC = () => (
|
||||
<Route path="/" component={Main} />
|
||||
<Route path="/overlay-hooks" component={OverlayHooks} />
|
||||
<Route path="/overlay-components" component={OverlayComponents} />
|
||||
<Route path="/navigation" component={NavComponent} />
|
||||
<Route path="/tabs" component={Tabs} />
|
||||
</IonRouterOutlet>
|
||||
</IonReactRouter>
|
||||
|
@ -31,6 +31,11 @@ const Main: React.FC<MainProps> = () => {
|
||||
<IonLabel>Overlay Components</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonItem routerLink="/navigation">
|
||||
<IonLabel>Navigation</IonLabel>
|
||||
</IonItem>
|
||||
</IonList>
|
||||
<IonList>
|
||||
<IonItem routerLink="/tabs">
|
||||
<IonLabel>Tabs</IonLabel>
|
||||
|
@ -0,0 +1,84 @@
|
||||
import {
|
||||
IonButton,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonLabel,
|
||||
IonNav,
|
||||
IonNavLink,
|
||||
IonTitle,
|
||||
IonToolbar,
|
||||
IonButtons,
|
||||
IonBackButton,
|
||||
IonPage,
|
||||
} from '@ionic/react';
|
||||
import React from 'react';
|
||||
|
||||
const NavComponent: React.FC = () => {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonNav
|
||||
root={() => {
|
||||
return (
|
||||
<>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Page One</IonTitle>
|
||||
<IonButtons>
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent id="pageOneContent">
|
||||
<IonLabel>Page one content</IonLabel>
|
||||
<IonNavLink
|
||||
routerDirection="forward"
|
||||
component={() => {
|
||||
return (
|
||||
<>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Page Two</IonTitle>
|
||||
<IonButtons>
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent id="pageTwoContent">
|
||||
<IonLabel>Page two content</IonLabel>
|
||||
<IonNavLink
|
||||
routerDirection="forward"
|
||||
component={() => (
|
||||
<>
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Page Three</IonTitle>
|
||||
<IonButtons>
|
||||
<IonBackButton />
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<IonLabel>Page three content</IonLabel>
|
||||
</IonContent>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<IonButton>Go to Page Three</IonButton>
|
||||
</IonNavLink>
|
||||
</IonContent>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
>
|
||||
<IonButton>Go to Page Two</IonButton>
|
||||
</IonNavLink>
|
||||
</IonContent>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
></IonNav>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavComponent;
|
@ -49,7 +49,7 @@ const ModalHook: React.FC = () => {
|
||||
setCount(count + 1);
|
||||
}, [count, setCount]);
|
||||
|
||||
const handleDismissWithComponent = useCallback((data, role) => {
|
||||
const handleDismissWithComponent = useCallback((data: any, role: string) => {
|
||||
dismissWithComponent(data, role);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
Reference in New Issue
Block a user