Skip to content

Commit

Permalink
refactor(ui): modal-based components API refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
32penkin authored Jul 9, 2019
1 parent 97afa07 commit 6aea312
Show file tree
Hide file tree
Showing 22 changed files with 174 additions and 522 deletions.
5 changes: 1 addition & 4 deletions src/framework/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ export {
ThemeProvider,
ThemeProviderProps,
} from './theme/themeProvider.component';
export {
ModalService,
ModalComponentCloseProps,
} from './modal/modal.service';
export { ModalService } from './modal/modal.service';
export {
Interaction,
State,
Expand Down
45 changes: 27 additions & 18 deletions src/framework/theme/modal/modal.service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,40 @@
*/

import React from 'react';
import { ModalPresentingBased } from '../../ui/support/typings';

/**
* Singleton service designed to manage modal components.
*
* @type ModalServiceType
*
* @method {(element: React.ReactElement<ModalComponentCloseProps>,
* closeOnBackDrop: boolean = false) => string} show - Shows component in a modal window.
* @method {(element: React.ReactElement<ModalPresentingBased>, config: ModalPresentingConfig) => string} show -
* Shows component in a modal window. Returns its id.
*
* @method {(identifier: string) => void} hide - Hides component from a modal window.
* @method {(identifier: string) => string} hide - Hides component from a modal window and returns empty string.
*
* @example Simple Usage example
*
* ```
* import React from 'react';
* import { View, ViewProps } from 'react-native';
* import { Button, Text, ModalService } from 'react-native-ui-kitten';
* import {
* View,
* ViewProps,
* } from 'react-native';
* import {
* Button,
* Text,
* ModalService,
* } from 'react-native-ui-kitten';
*
* export const ModalServiceShowcase = (): React.ReactElement<ViewProps> => {
*
* const modalID: string = '';
*
* const showModal = () => {
* const component: React.ReactElement<ViewProps> =
* const component: React.ReactElement<ViewProps> = this.renderModalContentElement();
*
* this.modalID = ModalService.show(this.renderModalContentElement);
* this.modalID = ModalService.show(component, { allowBackdrop: true, onBackdropPress: this.hideModal });
* };
*
* const hideModal = () => {
Expand Down Expand Up @@ -71,30 +79,31 @@ class ModalServiceType {
this.panel = null;
}

public show(element: React.ReactElement<ModalComponentCloseProps>,
closeOnBackDrop: boolean = false): string {
public show(element: React.ReactElement<ModalPresentingBased>,
config: ModalPresentingConfig): string {

if (this.panel) {
return this.panel.show(element, closeOnBackDrop);
return this.panel.show(element, config);
}
}

public hide(identifier: string): void {
public hide(identifier: string): string {
if (this.panel) {
this.panel.hide(identifier);
return this.panel.hide(identifier);
}
}
}

export interface ModalComponentCloseProps {
[key: string]: any;
onRequestClose: () => void;
export interface ModalPresentingConfig {
allowBackdrop: boolean;
onBackdropPress: () => void;
}

export interface ModalPresenting {
show(element: React.ReactElement<ModalComponentCloseProps>,
closeOnBackDrop: boolean): string;
show(element: React.ReactElement<ModalPresentingBased>,
config: ModalPresentingConfig): string;

hide(identifier: string): void;
hide(identifier: string): string;
}

export const ModalService = new ModalServiceType();
85 changes: 40 additions & 45 deletions src/framework/theme/modal/modalPanel.component.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
/**
* @license
* Copyright Akveo. All Rights Reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*/

import React from 'react';
import {
View,
StyleSheet,
ViewProps,
} from 'react-native';
import { ModalResolver } from './modalResolver.component';
import {
ModalResolver,
ModalResolverProps,
} from './modalResolver.component';
import {
ModalService,
ModalPresenting,
ModalComponentCloseProps,
ModalPresentingConfig,
} from './modal.service';
import { ModalPresentingBased } from '../../ui/support/typings';

interface ModalPanelChild extends ModalPresentingConfig {
element: React.ReactElement<ModalPresentingBased>;
}

export interface ModalPanelProps {
children: React.ReactNode;
}

interface ModalPanelState {
components: Map<string, React.ReactElement<ModalComponentCloseProps>>;
backdrops: Map<string, boolean>;
components: Map<string, ModalPanelChild>;
}

export class ModalPanel extends React.Component<ModalPanelProps, ModalPanelState> implements ModalPresenting {

public state: ModalPanelState = {
components: new Map(),
backdrops: new Map(),
};

public componentDidMount(): void {
Expand All @@ -35,66 +47,49 @@ export class ModalPanel extends React.Component<ModalPanelProps, ModalPanelState
ModalService.unmount();
}

public hide = (identifier: string): void => {
const component: React.ReactElement<ModalComponentCloseProps> = this.state.components
.get(identifier);
if (component) {
component.props.onRequestClose && component.props.onRequestClose();
}
const components: Map<string, React.ReactElement<any>> = this.state.components;
public hide = (identifier: string): string => {
const components: Map<string, ModalPanelChild> = this.state.components;
components.delete(identifier);
const backdrops: Map<string, boolean> = this.state.backdrops;
backdrops.delete(identifier);
this.setState({
components: components,
backdrops: backdrops,
});
this.setState({ components });
return '';
};

public show(dialogComponent: React.ReactElement<any>, closeOnBackDrop: boolean): string {
public show(element: React.ReactElement<ModalPresentingBased>,
config: ModalPresentingConfig): string {

const key: string = this.generateUniqueComponentKey();
const componentsMap: Map<string, React.ReactElement<any>> = this.state.components
.set(key, dialogComponent);
const backdrops: Map<string, boolean> = this.state.backdrops.set(key, closeOnBackDrop);
this.setState({
components: componentsMap,
backdrops: backdrops,
});
const components: Map<string, ModalPanelChild> = this.state.components
.set(key, { ...config, element });

this.setState({ components });

return key;
}

private generateUniqueComponentKey = (): string => {
return Math.random().toString(36).substring(2);
};

private areThereAnyComponents(): boolean {
private areThereAnyComponents = (): boolean => {
return this.state.components && this.state.components.size !== 0;
}
};

private renderModal(modal: React.ReactElement<any>, index: number) {
const allModalKeys: string[] = Array.from(this.state.components.keys());
const identifier: string = allModalKeys
.find(item => this.state.components.get(item) === modal);
const closeOnBackdrop: boolean = this.state.backdrops.get(identifier);
private renderModal = (config: ModalPanelChild, index: number): React.ReactElement<ModalResolverProps> => {
return (
<ModalResolver
{...modal.props}
{...config.element.props}
visible={true}
isBackDropAllowed={closeOnBackdrop}
key={index}
identifier={identifier}
onCloseModal={this.hide}
>
{modal}
allowBackdrop={config.allowBackdrop}
onBackdropPress={config.onBackdropPress}>
{config.element}
</ModalResolver>
);
}
};

private renderModals() {
return Array.from(this.state.components.values())
.map((component: React.ReactElement<any>, i: number) =>
this.renderModal(component, i));
}
private renderModals = (): React.ReactElement<ModalResolverProps>[] => {
return Array.from(this.state.components.values()).map(this.renderModal);
};

public render(): React.ReactElement<ViewProps> {
return (
Expand Down
35 changes: 27 additions & 8 deletions src/framework/theme/modal/modalPanel.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@ import {
TouchableOpacity,
} from 'react-native';
import { ModalPanel } from './modalPanel.component';
import {
ModalService,
ModalComponentCloseProps,
} from './modal.service';
import { ModalService } from './modal.service';
import { ModalPresentingBased } from '../../ui/support/typings';

describe('@modal-service: service checks', () => {

Expand All @@ -37,6 +35,10 @@ describe('@modal-service: service checks', () => {
onCloseModal={this.props.onCloseModal}
textTestId={textId(1)}
/>,
{
allowBackdrop: false,
onBackdropPress: () => null,
},
);
};

Expand All @@ -47,6 +49,10 @@ describe('@modal-service: service checks', () => {
onCloseModal={this.props.onCloseModal}
textTestId={textId(1)}
/>,
{
allowBackdrop: false,
onBackdropPress: () => null,
},
);

ModalService.show(
Expand All @@ -55,6 +61,10 @@ describe('@modal-service: service checks', () => {
onCloseModal={this.props.onCloseModal}
textTestId={textId(2)}
/>,
{
allowBackdrop: false,
onBackdropPress: () => null,
},
);
};

Expand All @@ -64,7 +74,12 @@ describe('@modal-service: service checks', () => {
text={textId(0)}
onCloseModal={this.props.onCloseModal}
textTestId={textId(0)}
/>, true);
/>,
{
allowBackdrop: true,
onBackdropPress: () => null,
},
);
};

public render(): React.ReactNode {
Expand Down Expand Up @@ -196,7 +211,11 @@ describe('@modal panel checks', () => {

public showModal() {
this.modalId = ModalService.show(
<TestModal onRequestClose={() => 1}/>, true,
<TestModal onBackdropPress={this.hideModal}/>,
{
allowBackdrop: true,
onBackdropPress: () => null,
},
);
}

Expand Down Expand Up @@ -227,14 +246,14 @@ describe('@modal panel checks', () => {
}
}

class TestModal extends React.Component<ModalComponentCloseProps> {
class TestModal extends React.Component<ModalPresentingBased> {

public render(): React.ReactNode {
return (
<View>
<Button
title='Close Modal'
onPress={this.props.onCloseModal}
onPress={this.props.onBackdropPress}
testID={hideModalTestIdInner}
/>
</View>
Expand Down
Loading

0 comments on commit 6aea312

Please sign in to comment.