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
|
// Custom Components
|
||||||
export { IonPage } from './IonPage';
|
export { IonPage } from './IonPage';
|
||||||
|
export { IonTabsContext, IonTabsContextState } from './navigation/IonTabsContext';
|
||||||
export { IonTabs } from './navigation/IonTabs';
|
export { IonTabs } from './navigation/IonTabs';
|
||||||
export { IonTabBar } from './navigation/IonTabBar';
|
export { IonTabBar } from './navigation/IonTabBar';
|
||||||
export { IonBackButton } from './navigation/IonBackButton';
|
export { IonBackButton } from './navigation/IonBackButton';
|
||||||
|
@ -5,28 +5,33 @@ import { NavContext } from '../../contexts/NavContext';
|
|||||||
import { IonicReactProps } from '../IonicReactProps';
|
import { IonicReactProps } from '../IonicReactProps';
|
||||||
import { IonTabBarInner } from '../inner-proxies';
|
import { IonTabBarInner } from '../inner-proxies';
|
||||||
import { IonTabButton } from '../proxies';
|
import { IonTabButton } from '../proxies';
|
||||||
|
import { createForwardRef } from '../utils';
|
||||||
|
|
||||||
type Props = LocalJSX.IonTabBar & IonicReactProps & {
|
type IonTabBarProps = LocalJSX.IonTabBar & IonicReactProps & {
|
||||||
onIonTabsDidChange?: (event: CustomEvent<{ tab: string }>) => void;
|
onIonTabsDidChange?: (event: CustomEvent<{ tab: string; }>) => void;
|
||||||
onIonTabsWillChange?: (event: CustomEvent<{ tab: string }>) => void;
|
onIonTabsWillChange?: (event: CustomEvent<{ tab: string; }>) => void;
|
||||||
currentPath?: string;
|
currentPath?: string;
|
||||||
slot?: 'bottom' | 'top';
|
slot?: 'bottom' | 'top';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface InternalProps extends IonTabBarProps {
|
||||||
|
forwardedRef?: React.RefObject<HTMLIonIconElement>;
|
||||||
|
}
|
||||||
|
|
||||||
interface TabUrls {
|
interface TabUrls {
|
||||||
originalHref: string;
|
originalHref: string;
|
||||||
currentHref: string;
|
currentHref: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface IonTabBarState {
|
||||||
activeTab: string | undefined;
|
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>;
|
context!: React.ContextType<typeof NavContext>;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: InternalProps) {
|
||||||
super(props);
|
super(props);
|
||||||
const tabs: { [key: string]: TabUrls; } = {};
|
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 = {
|
this.state = {
|
||||||
activeTab: undefined,
|
activeTab,
|
||||||
tabs
|
tabs
|
||||||
};
|
};
|
||||||
|
|
||||||
this.onTabButtonClick = this.onTabButtonClick.bind(this);
|
this.onTabButtonClick = this.onTabButtonClick.bind(this);
|
||||||
this.renderTabButton = this.renderTabButton.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 tabs = { ...state.tabs };
|
||||||
const activeTab = Object.keys(state.tabs)
|
const activeTab = Object.keys(state.tabs)
|
||||||
@ -101,6 +132,7 @@ class IonTabBarUnwrapped extends React.PureComponent<Props, State> {
|
|||||||
if (this.props.onIonTabsDidChange) {
|
if (this.props.onIonTabsDidChange) {
|
||||||
this.props.onIonTabsDidChange(new CustomEvent('ionTabDidChange', { detail: { tab: e.detail.tab } }));
|
this.props.onIonTabsDidChange(new CustomEvent('ionTabDidChange', { detail: { tab: e.detail.tab } }));
|
||||||
}
|
}
|
||||||
|
this.setActiveTabOnContext(e.detail.tab);
|
||||||
this.context.navigate(currentHref, 'none');
|
this.context.navigate(currentHref, 'none');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,9 +152,7 @@ class IonTabBarUnwrapped extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
||||||
const { activeTab } = this.state;
|
const { activeTab } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IonTabBarInner {...this.props} selectedTab={activeTab}>
|
<IonTabBarInner {...this.props} selectedTab={activeTab}>
|
||||||
{React.Children.map(this.props.children as any, this.renderTabButton(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);
|
const context = useContext(NavContext);
|
||||||
return (
|
return (
|
||||||
<IonTabBarUnwrapped
|
<IonTabBarUnwrapped
|
||||||
|
ref={forwardedRef}
|
||||||
{...props as any}
|
{...props as any}
|
||||||
currentPath={props.currentPath || context.currentPath}
|
currentPath={props.currentPath || context.currentPath}
|
||||||
>
|
>
|
||||||
@ -146,3 +177,5 @@ export const IonTabBar: React.FC<Props> = React.memo<Props>(props => {
|
|||||||
</IonTabBarUnwrapped>
|
</IonTabBarUnwrapped>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const IonTabBar = createForwardRef<IonTabBarProps, HTMLIonTabBarElement>(IonTabBarContainer, 'IonTabBar');
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import { JSX as LocalJSX } from '@ionic/core';
|
import { JSX as LocalJSX } from '@ionic/core';
|
||||||
import React from 'react';
|
import React, { Fragment } from 'react';
|
||||||
|
|
||||||
import { NavContext } from '../../contexts/NavContext';
|
import { NavContext } from '../../contexts/NavContext';
|
||||||
import { IonRouterOutlet } from '../IonRouterOutlet';
|
import { IonRouterOutlet } from '../IonRouterOutlet';
|
||||||
|
|
||||||
import { IonTabBar } from './IonTabBar';
|
import { IonTabBar } from './IonTabBar';
|
||||||
|
import { IonTabsContext, IonTabsContextState } from './IonTabsContext';
|
||||||
|
|
||||||
|
type ChildFunction = (ionTabContext: IonTabsContextState) => React.ReactNode;
|
||||||
|
|
||||||
interface Props extends LocalJSX.IonTabs {
|
interface Props extends LocalJSX.IonTabs {
|
||||||
children: React.ReactNode;
|
children: ChildFunction | React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hostStyles: React.CSSProperties = {
|
const hostStyles: React.CSSProperties = {
|
||||||
@ -32,25 +35,60 @@ const tabsInner: React.CSSProperties = {
|
|||||||
export class IonTabs extends React.Component<Props> {
|
export class IonTabs extends React.Component<Props> {
|
||||||
context!: React.ContextType<typeof NavContext>;
|
context!: React.ContextType<typeof NavContext>;
|
||||||
routerOutletRef: React.Ref<HTMLIonRouterOutletElement> = React.createRef();
|
routerOutletRef: React.Ref<HTMLIonRouterOutletElement> = React.createRef();
|
||||||
|
selectTabHandler?: (tag: string) => boolean;
|
||||||
|
tabBarRef = React.createRef<any>();
|
||||||
|
|
||||||
|
ionTabContextState: IonTabsContextState = {
|
||||||
|
activeTab: undefined,
|
||||||
|
selectTab: () => false
|
||||||
|
};
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(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() {
|
render() {
|
||||||
let outlet: React.ReactElement<{}> | undefined;
|
let outlet: React.ReactElement<{}> | undefined;
|
||||||
let tabBar: 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')) {
|
if (child == null || typeof child !== 'object' || !child.hasOwnProperty('type')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (child.type === IonRouterOutlet) {
|
if (child.type === IonRouterOutlet) {
|
||||||
outlet = child;
|
outlet = child;
|
||||||
|
} else if (child.type === Fragment && child.props.children[0].type === IonRouterOutlet) {
|
||||||
|
outlet = child.props.children[0];
|
||||||
}
|
}
|
||||||
if (child.type === IonTabBar) {
|
if (child.type === IonTabBar) {
|
||||||
const { onIonTabsDidChange, onIonTabsWillChange } = this.props;
|
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 (
|
return (
|
||||||
|
<IonTabsContext.Provider
|
||||||
|
value={this.ionTabContextState}
|
||||||
|
>
|
||||||
<div style={hostStyles}>
|
<div style={hostStyles}>
|
||||||
{tabBar.props.slot === 'top' ? tabBar : null}
|
{tabBar.props.slot === 'top' ? tabBar : null}
|
||||||
<div style={tabsInner} className="tabs-inner">
|
<div style={tabsInner} className="tabs-inner">
|
||||||
@ -70,6 +111,7 @@ export class IonTabs extends React.Component<Props> {
|
|||||||
</div>
|
</div>
|
||||||
{tabBar.props.slot === 'bottom' ? tabBar : null}
|
{tabBar.props.slot === 'bottom' ? tabBar : null}
|
||||||
</div>
|
</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-lambda": false,
|
||||||
"jsx-no-multiline-js": false,
|
"jsx-no-multiline-js": false,
|
||||||
"jsx-wrap-multiline": false,
|
"jsx-wrap-multiline": false,
|
||||||
|
"no-empty": false,
|
||||||
"no-empty-interface": false,
|
"no-empty-interface": false,
|
||||||
"no-unbound-method": false,
|
"no-unbound-method": false,
|
||||||
|
"no-unused-expression": false,
|
||||||
"prefer-conditional-expression": false
|
"prefer-conditional-expression": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user