Skip to content

Commit

Permalink
feat(react): complete controller integrations and navigation (#16849)
Browse files Browse the repository at this point in the history
* fix(react): correct controller types and reexport AlertOptions.

* feat(): add Ion Stack and Tabs navigation items based on React Router.

* rework tabs and add router outlet

* fixes to the outlet rendering.

* add direction as state

* fixed transitions

* Update to core rc2.
  • Loading branch information
jthoms1 authored Jan 22, 2019
1 parent 3612651 commit f46cd50
Show file tree
Hide file tree
Showing 16 changed files with 479 additions and 55 deletions.
21 changes: 15 additions & 6 deletions react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,22 @@
"@types/node": "10.12.9",
"@types/react": "16.7.6",
"@types/react-dom": "16.0.9",
"react": "latest",
"react-dom": "latest",
"typescript": "3.1.1"
"@types/react-router": "^4.4.3",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
"typescript": "^3.2.2",
"np": "^3.1.0"
},
"dependencies": {
"@ionic/core": "4.0.0-rc.0",
"ionicons": "^4.5.0",
"np": "^3.1.0"
"@ionic/core": "4.0.0-rc.2",
"ionicons": "^4.5.0"
},
"peerDependencies": {
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1"
}
}
3 changes: 1 addition & 2 deletions react/src/components/IonActionSheet.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Components } from '@ionic/core';
import { createOverlayComponent } from './createOverlayComponent';
import { Omit } from './types';

export type ActionSheetOptions = Omit<Components.IonActionSheetAttributes, 'overlayIndex'>;
export type ActionSheetOptions = Components.IonActionSheetAttributes;

const IonActionSheet = createOverlayComponent<ActionSheetOptions, HTMLIonActionSheetElement, HTMLIonActionSheetControllerElement>('ion-action-sheet', 'ion-action-sheet-controller')
export default IonActionSheet;
3 changes: 1 addition & 2 deletions react/src/components/IonAlert.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Components } from '@ionic/core';
import { createControllerComponent } from './createControllerComponent';
import { Omit } from './types';

export type AlertOptions = Omit<Components.IonAlertAttributes, 'overlayIndex'>;
export type AlertOptions = Components.IonAlertAttributes;

const IonAlert = createControllerComponent<AlertOptions, HTMLIonAlertElement, HTMLIonAlertControllerElement>('ion-alert', 'ion-alert-controller')
export default IonAlert;
3 changes: 1 addition & 2 deletions react/src/components/IonLoading.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Components } from '@ionic/core';
import { createControllerComponent } from './createControllerComponent';
import { Omit } from './types';

export type LoadingOptions = Omit<Components.IonLoadingAttributes, 'overlayIndex'>;
export type LoadingOptions = Components.IonLoadingAttributes;

const IonActionSheet = createControllerComponent<LoadingOptions, HTMLIonLoadingElement, HTMLIonLoadingControllerElement>('ion-loading', 'ion-loading-controller')
export default IonActionSheet;
2 changes: 1 addition & 1 deletion react/src/components/IonModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Components } from '@ionic/core';
import { createOverlayComponent } from './createOverlayComponent';
import { Omit } from './types';

export type ModalOptions = Omit<Components.IonModalAttributes, 'delegate' | 'overlayIndex' | 'component' | 'componentProps'> & {
export type ModalOptions = Omit<Components.IonModalAttributes, 'component' | 'componentProps'> & {
children: React.ReactNode;
};

Expand Down
2 changes: 1 addition & 1 deletion react/src/components/IonPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Components } from '@ionic/core';
import { createOverlayComponent } from './createOverlayComponent';
import { Omit } from './types';

export type PopoverOptions = Omit<Components.IonPopoverAttributes, 'delegate' | 'overlayIndex' | 'component' | 'componentProps'> & {
export type PopoverOptions = Omit<Components.IonPopoverAttributes, 'component' | 'componentProps'> & {
children: React.ReactNode;
};

Expand Down
3 changes: 1 addition & 2 deletions react/src/components/IonToast.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Components } from '@ionic/core';
import { createControllerComponent } from './createControllerComponent';
import { Omit } from './types';

export type ToastOptions = Omit<Components.IonToastAttributes, 'overlayIndex'>;
export type ToastOptions = Components.IonToastAttributes;

const IonToast = createControllerComponent<ToastOptions, HTMLIonToastElement, HTMLIonToastControllerElement>('ion-toast', 'ion-toast-controller')
export default IonToast;
21 changes: 14 additions & 7 deletions react/src/components/createComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { dashToPascalCase, attachEventProps } from './utils';

export function createReactComponent<T, E>(tagName: string) {
export function createReactComponent<T extends object, E>(tagName: string) {
const displayName = dashToPascalCase(tagName);

type IonicReactInternalProps = {
forwardedRef?: React.RefObject<E>;
children?: React.ReactNode;
}
type InternalProps = T & IonicReactInternalProps;

type IonicReactExternalProps = {
ref?: React.RefObject<E>;
children?: React.ReactNode;
}

class ReactComponent extends React.Component<T & IonicReactInternalProps> {
class ReactComponent extends React.Component<InternalProps> {
componentRef: React.RefObject<E>;

constructor(props: T & IonicReactInternalProps) {
Expand All @@ -31,7 +32,7 @@ export function createReactComponent<T, E>(tagName: string) {
this.componentWillReceiveProps(this.props);
}

componentWillReceiveProps(props: any) {
componentWillReceiveProps(props: InternalProps) {
const node = ReactDOM.findDOMNode(this);

if (!(node instanceof HTMLElement)) {
Expand All @@ -42,14 +43,20 @@ export function createReactComponent<T, E>(tagName: string) {
}

render() {
const { children, forwardedRef, ...cProps } = this.props as any;
cProps.ref = forwardedRef;
const { children, forwardedRef, ...cProps } = this.props;

return React.createElement(tagName, cProps, children);
return React.createElement(
tagName,
{
...cProps,
ref: forwardedRef
},
children
);
}
}

function forwardRef(props: T & IonicReactInternalProps, ref: React.RefObject<E>) {
function forwardRef(props: InternalProps, ref: React.RefObject<E>) {
return <ReactComponent {...props} forwardedRef={ref} />;
}
forwardRef.displayName = displayName;
Expand Down
26 changes: 13 additions & 13 deletions react/src/components/createControllerComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import React from 'react';
import { attachEventProps } from './utils'
import { ensureElementInBody, dashToPascalCase } from './utils';
import { OverlayComponentElement, OverlayControllerComponentElement } from './types';

export function createControllerComponent<T, E extends HTMLElement, C extends HTMLElement>(tagName: string, controllerTagName: string) {
export function createControllerComponent<T extends object, E extends OverlayComponentElement, C extends OverlayControllerComponentElement<E>>(tagName: string, controllerTagName: string) {
const displayName = dashToPascalCase(tagName);

type IonicReactInternalProps = {
forwardedRef?: React.RefObject<E>;
children?: React.ReactNode;
show: boolean
type ReactProps = {
show: boolean;
}
type Props = T & ReactProps;

return class ReactControllerComponent extends React.Component<T & IonicReactInternalProps> {
return class ReactControllerComponent extends React.Component<Props> {
element: E;
controllerElement: C;

constructor(props: T & IonicReactInternalProps) {
constructor(props: Props) {
super(props);
}

Expand All @@ -25,20 +25,20 @@ export function createControllerComponent<T, E extends HTMLElement, C extends HT

async componentDidMount() {
this.controllerElement = ensureElementInBody<C>(controllerTagName);
await (this.controllerElement as any).componentOnReady();
await this.controllerElement.componentOnReady();
}

async componentDidUpdate(prevProps: T & IonicReactInternalProps) {
async componentDidUpdate(prevProps: Props) {
if (prevProps.show !== this.props.show && this.props.show === true) {
const { children, show, ...cProps} = this.props as any;
const { show, ...cProps} = this.props;

this.element = await (this.controllerElement as any).create(cProps);
await (this.element as any).present();
this.element = await this.controllerElement.create(cProps);
await this.element.present();

attachEventProps(this.element, cProps);
}
if (prevProps.show !== this.props.show && this.props.show === false) {
return await (this.element as any).dismiss();
await this.element.dismiss();
}
}

Expand Down
29 changes: 16 additions & 13 deletions react/src/components/createOverlayComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { attachEventProps } from './utils'
import { ensureElementInBody, dashToPascalCase } from './utils';
import { OverlayComponentElement, OverlayControllerComponentElement } from './types';

export function createOverlayComponent<T, E extends HTMLElement, C extends HTMLElement>(tagName: string, controllerTagName: string) {
export function createOverlayComponent<T extends object, E extends OverlayComponentElement, C extends OverlayControllerComponentElement<E>>(tagName: string, controllerTagName: string) {
const displayName = dashToPascalCase(tagName);

type IonicReactInternalProps = {
forwardedRef?: React.RefObject<E>;
type ReactProps = {
children: React.ReactNode;
show: boolean;
}
type Props = T & ReactProps;

return class ReactControllerComponent extends React.Component<T & IonicReactInternalProps> {
return class ReactControllerComponent extends React.Component<Props> {
element: E;
controllerElement: C;
el: HTMLDivElement;

constructor(props: T & IonicReactInternalProps) {
constructor(props: Props) {
super(props);

this.el = document.createElement('div');
Expand All @@ -29,22 +30,24 @@ export function createOverlayComponent<T, E extends HTMLElement, C extends HTMLE

async componentDidMount() {
this.controllerElement = ensureElementInBody<C>(controllerTagName);
await (this.controllerElement as any).componentOnReady();
await this.controllerElement.componentOnReady();
}

async componentDidUpdate(prevProps: T & IonicReactInternalProps) {
async componentDidUpdate(prevProps: Props) {
if (prevProps.show !== this.props.show && this.props.show === true) {
const { children, show, ...cProps} = this.props as any;
cProps.component = this.el;
cProps.componentProps = {};
const { children, show, ...cProps} = this.props;

this.element = await (this.controllerElement as any).create(cProps);
await (this.element as any).present();
this.element = await this.controllerElement.create({
...cProps,
component: this.el,
componentProps: {}
});
await this.element.present();

attachEventProps(this.element, cProps);
}
if (prevProps.show !== this.props.show && this.props.show === false) {
return await (this.element as any).dismiss();
await this.element.dismiss();
}
}

Expand Down
19 changes: 13 additions & 6 deletions react/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ import { Components as IoniconsComponents } from 'ionicons';
import { Components } from '@ionic/core';
import { createReactComponent } from './createComponent';

export { AlertButton, AlertInput } from '@ionic/core';

export { default as IonActionSheet } from './IonActionSheet';
export { default as IonAlert } from './IonAlert';
export { default as IonLoading } from './IonLoading';
export { default as IonModal } from './IonModal';
export { default as IonPopover } from './IonPopover';
export { default as IonToast } from './IonToast';
export { default as IonTabs } from './navigation/IonTabs';
export { default as IonTabBar } from './navigation/IonTabBar';
export { IonRouterOutlet, IonBackButton } from './navigation/IonRouterOutlet';

export const IonTabBarInner = createReactComponent<Components.IonTabBarAttributes, HTMLIonTabBarElement>('ion-tab-bar');
export const IonRouterOutletInner = createReactComponent<Components.IonRouterOutletAttributes, HTMLIonRouterOutletElement>('ion-router-outlet');
export const IonBackButtonInner = createReactComponent<Components.IonBackButtonAttributes, HTMLIonBackButtonElement>('ion-back-button');
export const IonTab = createReactComponent<Components.IonTabAttributes, HTMLIonTabElement>('ion-tab');
export const IonTabButton = createReactComponent<Components.IonTabButtonAttributes, HTMLIonTabButtonElement>('ion-tab-button');

export const IonAnchor = createReactComponent<Components.IonAnchorAttributes, HTMLIonAnchorElement>('ion-anchor');
export const IonApp = createReactComponent<Components.IonAppAttributes, HTMLIonAppElement>('ion-app');
Expand Down Expand Up @@ -56,8 +67,8 @@ export const IonProgressBar = createReactComponent<Components.IonProgressBarAttr
export const IonRadio = createReactComponent<Components.IonRadioAttributes, HTMLIonRadioElement>('ion-radio');
export const IonRadioGroup = createReactComponent<Components.IonRadioGroupAttributes, HTMLIonRadioGroupElement>('ion-radio-group');
export const IonRange = createReactComponent<Components.IonRangeAttributes, HTMLIonRangeElement>('ion-range');
export const IonRefresher= createReactComponent<Components.IonRefresherAttributes, HTMLIonRefresherElement>('ion-refresher');
export const IonRefresherContent= createReactComponent<Components.IonRefresherContentAttributes, HTMLIonRefresherContentElement>('ion-refresher-content');
export const IonRefresher = createReactComponent<Components.IonRefresherAttributes, HTMLIonRefresherElement>('ion-refresher');
export const IonRefresherContent = createReactComponent<Components.IonRefresherContentAttributes, HTMLIonRefresherContentElement>('ion-refresher-content');
export const IonReorder = createReactComponent<Components.IonReorderAttributes, HTMLIonReorderElement>('ion-reorder');
export const IonReorderGroup = createReactComponent<Components.IonReorderGroupAttributes, HTMLIonReorderGroupElement>('ion-reorder-group');
export const IonRippleEffect = createReactComponent<Components.IonRippleEffectAttributes, HTMLIonRippleEffectElement>('ion-ripple-effect');
Expand All @@ -73,10 +84,6 @@ export const IonSlide = createReactComponent<Components.IonSlideAttributes, HTML
export const IonSlides = createReactComponent<Components.IonSlidesAttributes, HTMLIonSlidesElement>('ion-slides');
export const IonSpinner = createReactComponent<Components.IonSpinnerAttributes, HTMLIonSpinnerElement>('ion-spinner');
export const IonSplitPane = createReactComponent<Components.IonSplitPaneAttributes, HTMLIonSplitPaneElement>('ion-split-pane');
export const IonTab = createReactComponent<Components.IonTabAttributes, HTMLIonTabElement>('ion-tab');
export const IonTabBar = createReactComponent<Components.IonTabBarAttributes, HTMLIonTabBarElement>('ion-tab-bar');
export const IonTabButton = createReactComponent<Components.IonTabButtonAttributes, HTMLIonTabButtonElement>('ion-tab-button');
export const IonTabs = createReactComponent<Components.IonTabsAttributes, HTMLIonTabsElement>('ion-tabs');
export const IonText = createReactComponent<Components.IonTextAttributes, HTMLIonTextElement>('ion-text');
export const IonTextarea = createReactComponent<Components.IonTextareaAttributes, HTMLIonTextareaElement>('ion-textarea');
export const IonThumbnail = createReactComponent<Components.IonThumbnailAttributes, HTMLIonThumbnailElement>('ion-thumbnail');
Expand Down
Loading

0 comments on commit f46cd50

Please sign in to comment.