mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 01:52:19 +08:00
This commit is contained in:
@ -18,6 +18,7 @@ export { IonPopover } from './IonPopover';
|
||||
|
||||
// Custom Components
|
||||
export { IonPage } from './IonPage';
|
||||
export { IonTabsContext, IonTabsContextState } from './navigation/IonTabsContext';
|
||||
export { IonTabs } from './navigation/IonTabs';
|
||||
export { IonTabBar } from './navigation/IonTabBar';
|
||||
export { IonBackButton } from './navigation/IonBackButton';
|
||||
|
@ -5,28 +5,33 @@ import { NavContext } from '../../contexts/NavContext';
|
||||
import { IonicReactProps } from '../IonicReactProps';
|
||||
import { IonTabBarInner } from '../inner-proxies';
|
||||
import { IonTabButton } from '../proxies';
|
||||
import { createForwardRef } from '../utils';
|
||||
|
||||
type Props = LocalJSX.IonTabBar & IonicReactProps & {
|
||||
onIonTabsDidChange?: (event: CustomEvent<{ tab: string }>) => void;
|
||||
onIonTabsWillChange?: (event: CustomEvent<{ tab: string }>) => void;
|
||||
type IonTabBarProps = LocalJSX.IonTabBar & IonicReactProps & {
|
||||
onIonTabsDidChange?: (event: CustomEvent<{ tab: string; }>) => void;
|
||||
onIonTabsWillChange?: (event: CustomEvent<{ tab: string; }>) => void;
|
||||
currentPath?: string;
|
||||
slot?: 'bottom' | 'top';
|
||||
};
|
||||
|
||||
interface InternalProps extends IonTabBarProps {
|
||||
forwardedRef?: React.RefObject<HTMLIonIconElement>;
|
||||
}
|
||||
|
||||
interface TabUrls {
|
||||
originalHref: string;
|
||||
currentHref: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
interface IonTabBarState {
|
||||
activeTab: string | undefined;
|
||||
tabs: { [key: string]: TabUrls };
|
||||
tabs: { [key: string]: TabUrls; };
|
||||
}
|
||||
|
||||
class IonTabBarUnwrapped extends React.PureComponent<Props, State> {
|
||||
class IonTabBarUnwrapped extends React.PureComponent<InternalProps, IonTabBarState> {
|
||||
context!: React.ContextType<typeof NavContext>;
|
||||
|
||||
constructor(props: Props) {
|
||||
constructor(props: InternalProps) {
|
||||
super(props);
|
||||
const tabs: { [key: string]: TabUrls; } = {};
|
||||
|
||||
@ -39,16 +44,42 @@ class IonTabBarUnwrapped extends React.PureComponent<Props, State> {
|
||||
}
|
||||
});
|
||||
|
||||
const tabKeys = Object.keys(tabs);
|
||||
const activeTab = tabKeys
|
||||
.find(key => {
|
||||
const href = tabs[key].originalHref;
|
||||
return props.currentPath!.startsWith(href);
|
||||
}) || tabKeys[0];
|
||||
|
||||
this.state = {
|
||||
activeTab: undefined,
|
||||
activeTab,
|
||||
tabs
|
||||
};
|
||||
|
||||
this.onTabButtonClick = this.onTabButtonClick.bind(this);
|
||||
this.renderTabButton = this.renderTabButton.bind(this);
|
||||
this.setActiveTabOnContext = this.setActiveTabOnContext.bind(this);
|
||||
this.selectTab = this.selectTab.bind(this);
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: Props, state: State) {
|
||||
setActiveTabOnContext = (_tab: string) => { };
|
||||
|
||||
selectTab(tab: string) {
|
||||
const tabUrl = this.state.tabs[tab];
|
||||
if (tabUrl) {
|
||||
this.onTabButtonClick(new CustomEvent('ionTabButtonClick', {
|
||||
detail: {
|
||||
href: tabUrl.currentHref,
|
||||
tab,
|
||||
selected: tab === this.state.activeTab
|
||||
}
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props: IonTabBarProps, state: IonTabBarState) {
|
||||
|
||||
const tabs = { ...state.tabs };
|
||||
const activeTab = Object.keys(state.tabs)
|
||||
@ -101,6 +132,7 @@ class IonTabBarUnwrapped extends React.PureComponent<Props, State> {
|
||||
if (this.props.onIonTabsDidChange) {
|
||||
this.props.onIonTabsDidChange(new CustomEvent('ionTabDidChange', { detail: { tab: e.detail.tab } }));
|
||||
}
|
||||
this.setActiveTabOnContext(e.detail.tab);
|
||||
this.context.navigate(currentHref, 'none');
|
||||
}
|
||||
}
|
||||
@ -120,9 +152,7 @@ class IonTabBarUnwrapped extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const { activeTab } = this.state;
|
||||
|
||||
return (
|
||||
<IonTabBarInner {...this.props} selectedTab={activeTab}>
|
||||
{React.Children.map(this.props.children as any, this.renderTabButton(activeTab))}
|
||||
@ -135,10 +165,11 @@ class IonTabBarUnwrapped extends React.PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
export const IonTabBar: React.FC<Props> = React.memo<Props>(props => {
|
||||
const IonTabBarContainer: React.FC<InternalProps> = React.memo<InternalProps>(({ forwardedRef, ...props }) => {
|
||||
const context = useContext(NavContext);
|
||||
return (
|
||||
<IonTabBarUnwrapped
|
||||
ref={forwardedRef}
|
||||
{...props as any}
|
||||
currentPath={props.currentPath || context.currentPath}
|
||||
>
|
||||
@ -146,3 +177,5 @@ export const IonTabBar: React.FC<Props> = React.memo<Props>(props => {
|
||||
</IonTabBarUnwrapped>
|
||||
);
|
||||
});
|
||||
|
||||
export const IonTabBar = createForwardRef<IonTabBarProps, HTMLIonTabBarElement>(IonTabBarContainer, 'IonTabBar');
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { JSX as LocalJSX } from '@ionic/core';
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
import { NavContext } from '../../contexts/NavContext';
|
||||
import { IonRouterOutlet } from '../IonRouterOutlet';
|
||||
|
||||
import { IonTabBar } from './IonTabBar';
|
||||
import { IonTabsContext, IonTabsContextState } from './IonTabsContext';
|
||||
|
||||
type ChildFunction = (ionTabContext: IonTabsContextState) => React.ReactNode;
|
||||
|
||||
interface Props extends LocalJSX.IonTabs {
|
||||
children: React.ReactNode;
|
||||
children: ChildFunction | React.ReactNode;
|
||||
}
|
||||
|
||||
const hostStyles: React.CSSProperties = {
|
||||
@ -32,25 +35,60 @@ const tabsInner: React.CSSProperties = {
|
||||
export class IonTabs extends React.Component<Props> {
|
||||
context!: React.ContextType<typeof NavContext>;
|
||||
routerOutletRef: React.Ref<HTMLIonRouterOutletElement> = React.createRef();
|
||||
selectTabHandler?: (tag: string) => boolean;
|
||||
tabBarRef = React.createRef<any>();
|
||||
|
||||
ionTabContextState: IonTabsContextState = {
|
||||
activeTab: undefined,
|
||||
selectTab: () => false
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.tabBarRef.current) {
|
||||
// Grab initial value
|
||||
this.ionTabContextState.activeTab = this.tabBarRef.current.state.activeTab;
|
||||
// Override method
|
||||
this.tabBarRef.current.setActiveTabOnContext = (tab: string) => {
|
||||
this.ionTabContextState.activeTab = tab;
|
||||
};
|
||||
this.ionTabContextState.selectTab = this.tabBarRef.current.selectTab;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let outlet: React.ReactElement<{}> | undefined;
|
||||
let tabBar: React.ReactElement | undefined;
|
||||
|
||||
React.Children.forEach(this.props.children, (child: any) => {
|
||||
const children = typeof this.props.children === 'function' ?
|
||||
(this.props.children as ChildFunction)(this.ionTabContextState) : this.props.children;
|
||||
|
||||
React.Children.forEach(children, (child: any) => {
|
||||
if (child == null || typeof child !== 'object' || !child.hasOwnProperty('type')) {
|
||||
return;
|
||||
}
|
||||
if (child.type === IonRouterOutlet) {
|
||||
outlet = child;
|
||||
} else if (child.type === Fragment && child.props.children[0].type === IonRouterOutlet) {
|
||||
outlet = child.props.children[0];
|
||||
}
|
||||
if (child.type === IonTabBar) {
|
||||
const { onIonTabsDidChange, onIonTabsWillChange } = this.props;
|
||||
tabBar = React.cloneElement(child, { onIonTabsDidChange, onIonTabsWillChange });
|
||||
tabBar = React.cloneElement(child, {
|
||||
onIonTabsDidChange,
|
||||
onIonTabsWillChange,
|
||||
ref: this.tabBarRef
|
||||
});
|
||||
} else if (child.type === Fragment && child.props.children[1].type === IonTabBar) {
|
||||
const { onIonTabsDidChange, onIonTabsWillChange } = this.props;
|
||||
tabBar = React.cloneElement(child.props.children[1], {
|
||||
onIonTabsDidChange,
|
||||
onIonTabsWillChange,
|
||||
ref: this.tabBarRef
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -63,6 +101,9 @@ export class IonTabs extends React.Component<Props> {
|
||||
}
|
||||
|
||||
return (
|
||||
<IonTabsContext.Provider
|
||||
value={this.ionTabContextState}
|
||||
>
|
||||
<div style={hostStyles}>
|
||||
{tabBar.props.slot === 'top' ? tabBar : null}
|
||||
<div style={tabsInner} className="tabs-inner">
|
||||
@ -70,6 +111,7 @@ export class IonTabs extends React.Component<Props> {
|
||||
</div>
|
||||
{tabBar.props.slot === 'bottom' ? tabBar : null}
|
||||
</div>
|
||||
</IonTabsContext.Provider >
|
||||
);
|
||||
}
|
||||
|
||||
|
11
packages/react/src/components/navigation/IonTabsContext.tsx
Normal file
11
packages/react/src/components/navigation/IonTabsContext.tsx
Normal file
@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
export interface IonTabsContextState {
|
||||
activeTab: string | undefined;
|
||||
selectTab: (tab: string) => boolean;
|
||||
}
|
||||
|
||||
export const IonTabsContext = React.createContext<IonTabsContextState>({
|
||||
activeTab: undefined,
|
||||
selectTab: () => false
|
||||
});
|
@ -27,8 +27,10 @@
|
||||
"jsx-no-lambda": false,
|
||||
"jsx-no-multiline-js": false,
|
||||
"jsx-wrap-multiline": false,
|
||||
"no-empty": false,
|
||||
"no-empty-interface": false,
|
||||
"no-unbound-method": false,
|
||||
"no-unused-expression": false,
|
||||
"prefer-conditional-expression": false
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user