diff --git a/docs/package-lock.json b/docs/package-lock.json index 0176e1a56..3eaedab86 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1026,7 +1026,7 @@ "dependencies": { "commander": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", "dev": true, "requires": { @@ -1344,7 +1344,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -1667,7 +1667,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -1704,7 +1704,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -1758,7 +1758,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -2490,7 +2490,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -2503,7 +2503,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -2908,7 +2908,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -4606,7 +4606,7 @@ }, "gh-pages": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-0.12.0.tgz", + "resolved": "http://registry.npmjs.org/gh-pages/-/gh-pages-0.12.0.tgz", "integrity": "sha1-2VHj7Zi4VpnUsEGOsaFbGgSYjcE=", "dev": true, "requires": { @@ -4630,7 +4630,7 @@ }, "commander": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "resolved": "http://registry.npmjs.org/commander/-/commander-2.9.0.tgz", "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", "dev": true, "requires": { @@ -5998,7 +5998,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -6534,7 +6534,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { @@ -8292,7 +8292,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8896,7 +8896,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -10267,7 +10267,7 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -11101,7 +11101,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/docs/src/structure.ts b/docs/src/structure.ts index b3451478a..2398b7696 100644 --- a/docs/src/structure.ts +++ b/docs/src/structure.ts @@ -411,6 +411,14 @@ export const structure = [ type: 'group', name: 'Modals & Overlays', }, + { + type: 'tabs', + name: 'Modal', + icon: 'dialog.svg', + source: [ + 'Modal', + ], + }, { type: 'tabs', name: 'Overflow Menu', @@ -426,14 +434,6 @@ export const structure = [ }, ], }, - { - type: 'tabs', - name: 'Modal', - icon: 'dialog.svg', - source: [ - 'Modal', - ], - }, { type: 'tabs', name: 'Popover', diff --git a/src/framework/theme/modal/modalPanel.component.tsx b/src/framework/theme/modal/modalPanel.component.tsx index e4e93aca4..56a0079e8 100644 --- a/src/framework/theme/modal/modalPanel.component.tsx +++ b/src/framework/theme/modal/modalPanel.component.tsx @@ -4,7 +4,7 @@ import { StyleSheet, ViewProps, } from 'react-native'; -import { Modal } from '../../ui/modal/modal.component'; +import { ModalResolver } from './modalResolver.component'; import { ModalService, ModalPresenting, @@ -77,7 +77,7 @@ export class ModalPanel extends React.Component this.state.components.get(item) === modal); const closeOnBackdrop: boolean = this.state.backdrops.get(identifier); return ( - {modal} - + ); } diff --git a/src/framework/theme/modal/modalResolver.component.tsx b/src/framework/theme/modal/modalResolver.component.tsx new file mode 100644 index 000000000..d6dc091d0 --- /dev/null +++ b/src/framework/theme/modal/modalResolver.component.tsx @@ -0,0 +1,129 @@ +/** + * @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, + ViewProps, + StyleSheet, + TouchableOpacity, + TouchableOpacityProps, +} from 'react-native'; + +type ChildElement = React.ReactElement; +type ChildrenProp = ChildElement | ChildElement[]; + +interface ComponentProps { + visible: boolean; + children: ChildrenProp; + isBackDropAllowed?: boolean; + identifier?: string; + onCloseModal?: (index: string) => void; +} + +export type ModalResolverProps = ViewProps & ComponentProps; + +export class ModalResolver extends React.Component { + + static defaultProps: Partial = { + visible: false, + isBackDropAllowed: false, + }; + + private closeModal = (): void => { + if (this.props.onCloseModal) { + this.props.onCloseModal(this.props.identifier); + } + }; + + private closeOnBackdrop: () => void = () => { + if (this.props.isBackDropAllowed) { + this.closeModal(); + } + }; + + private onStartShouldSetResponder = (): boolean => { + return true; + }; + + private onResponderRelease = (): void => { + return; + }; + + private onStartShouldSetResponderCapture = (): boolean => { + return false; + }; + + private renderComponentChild = (source: React.ReactElement): React.ReactElement => { + return React.cloneElement(source, { + onCloseModal: this.closeModal, + style: [source.props.style, this.props.style], + }); + }; + + private renderComponentChildren = (source: React.ReactNode): React.ReactElement[] => { + return React.Children.map(source, this.renderComponentChild); + }; + + private renderWithBackDrop = (component: React.ReactElement): + React.ReactElement => { + + return ( + + {component} + + ); + }; + + private renderWithoutBackDrop = (component: React.ReactElement): React.ReactElement => { + return ( + + + {component} + + ); + }; + + private renderComponent = (): React.ReactElement => { + const { children, isBackDropAllowed, ...derivedProps } = this.props; + const componentChildren: React.ReactElement[] = this.renderComponentChildren(children); + + const dialog: React.ReactElement = + + {componentChildren} + ; + + return isBackDropAllowed ? + this.renderWithBackDrop(dialog) : this.renderWithoutBackDrop(dialog); + }; + + public render(): React.ReactElement | null { + return this.props.visible ? this.renderComponent() : null; + } +} + +const styles = StyleSheet.create({ + container: StyleSheet.absoluteFillObject, + notVisibleWrapper: { + position: 'absolute', + width: 0, + height: 0, + }, + contentWrapper: { + alignSelf: 'flex-start', + }, +}); diff --git a/src/framework/theme/modal/modalResolver.spec.tsx b/src/framework/theme/modal/modalResolver.spec.tsx new file mode 100644 index 000000000..76ca65f5d --- /dev/null +++ b/src/framework/theme/modal/modalResolver.spec.tsx @@ -0,0 +1,181 @@ +import React from 'react'; +import { + View, + Text, + Button, + TouchableOpacity, +} from 'react-native'; +import { + fireEvent, + render, + RenderAPI, + shallow, +} from 'react-native-testing-library'; +import { ModalResolver } from './modalResolver.component'; + +jest.useFakeTimers(); + +describe('@modal resolver component checks', () => { + + const MODAL_TEST_IDENTIFIER = (substring: string): string => { + return `modal-test-identifier-${substring}`; + }; + + interface TestScreenProps { + buttonTestId: string; + } + + interface TestScreenState { + modalVisible: boolean; + } + + class TestScreen extends React.Component { + + public state: TestScreenState = { + modalVisible: false, + }; + + private setModalVisible(modalVisible: boolean): void { + this.setState({ modalVisible }); + } + + public render(): React.ReactNode { + return ( + + - Hi! I'm a Modal + {...this.props} + visible={this.state.modalVisible}> + Hi! This is Modal + ); } } -export const ModalShowcase = withStyles(ModalShowcaseComponent, (theme: ThemeType) => ({ +const styles = StyleSheet.create({ container: { - flex: 1, + justifyContent: 'space-between', }, - modal: { - ...StyleSheet.absoluteFillObject, - justifyContent: 'center', - alignItems: 'center', - width: 256, - height: 256, - backgroundColor: theme['background-basic-color-1'], - borderColor: theme['border-basic-color-2'], - borderWidth: 1, + button: { + marginTop: 12, }, -})); +}); diff --git a/src/playground/src/ui/screen/modal/type.ts b/src/playground/src/ui/screen/modal/type.ts index 957f4e28a..96bab85a0 100644 --- a/src/playground/src/ui/screen/modal/type.ts +++ b/src/playground/src/ui/screen/modal/type.ts @@ -1,34 +1,103 @@ +import { StyleSheet } from 'react-native'; import { ComponentShowcase, ComponentShowcaseItem, ComponentShowcaseSection, - ComponentShowcaseSetting, } from '../common/type'; +const styles = StyleSheet.create({ + modal: { + backgroundColor: '#636e80', + width: 200, + height: 200, + padding: 12, + alignItems: 'center', + justifyContent: 'center', + borderRadius: 8, + }, + backdropStyle: { + backgroundColor: 'black', + opacity: 0.5, + }, + customModalPosition: { + left: 100, + top: 100, + }, +}); + const defaultModal: ComponentShowcaseItem = { title: 'Default', props: {}, }; const defaultSection: ComponentShowcaseSection = { + title: 'Default Modal', items: [ defaultModal, ], }; -export const modalShowcase: ComponentShowcase = { - sections: [ - defaultSection, +const customStyledModal: ComponentShowcaseItem = { + title: 'Styled Modal', + props: { + style: styles.modal, + }, +}; + +const customStyledModalBackdrop: ComponentShowcaseItem = { + title: 'Styled Backdrop', + props: { + style: styles.modal, + backdropStyle: styles.backdropStyle, + }, +}; + +const customModalPosition: ComponentShowcaseItem = { + title: 'Custom Position', + props: { + style: [ styles.customModalPosition, styles.modal ], + }, +}; + +const customStyledSection: ComponentShowcaseSection = { + title: 'Styling', + items: [ + customStyledModal, + customStyledModalBackdrop, + customModalPosition, ], }; -export const modalSettings: ComponentShowcaseSetting[] = [ - { - propertyName: 'animationType', - value: 'fade', +const customModalBackdropAllowed: ComponentShowcaseItem = { + title: 'Close On Backdrop: true', + props: { + closeOnBackdrop: true, + style: styles.modal, + backdropStyle: styles.backdropStyle, }, - { - propertyName: 'animationType', - value: 'slideInUp', +}; + +const customModalBackdropNotAllowed: ComponentShowcaseItem = { + title: 'Close On Backdrop: false', + props: { + closeOnBackdrop: false, + style: styles.modal, + backdropStyle: styles.backdropStyle, }, -]; +}; + +const customBackdropAllowingSection: ComponentShowcaseSection = { + title: 'Backdrop Closing Permissions', + items: [ + customModalBackdropAllowed, + customModalBackdropNotAllowed, + ], +}; + +export const modalShowcase: ComponentShowcase = { + sections: [ + defaultSection, + customStyledSection, + customBackdropAllowingSection, + ], +};