├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .travis.yml ├── ActionSheetContainer.js ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── package.json └── resource ├── Android-1-L.jpeg ├── Android-1-P.jpeg ├── Android-2-L.jpeg ├── Android-2-P.jpeg ├── iOS-1-L.png ├── iOS-1-P.png ├── iOS-2-L.png └── iOS-2-P.png /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | "automatic", 4 | ], 5 | extends: [ 6 | "plugin:automatic/javascript-react-native", 7 | ], 8 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /resource 2 | .travis.yml 3 | .eslintrc.js -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | os: 5 | - linux 6 | 7 | stages: 8 | - test 9 | - name: deploy 10 | if: tag IS present 11 | 12 | jobs: 13 | include: 14 | - stage: test 15 | - stage: deploy 16 | deploy: 17 | provider: npm 18 | email: gaoxiaosong06@gmail.com 19 | api_key: "$NPM_TOKEN" 20 | on: 21 | tags: true 22 | -------------------------------------------------------------------------------- /ActionSheetContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Dimensions, Modal, ScrollView, StyleSheet, Text, TouchableHighlight, TouchableWithoutFeedback, View } from 'react-native'; 3 | import { getSafeAreaInset, isLandscape } from 'react-native-safe-area-utility'; 4 | 5 | export default class extends React.PureComponent { 6 | static defaultProps = { 7 | backgroundColor: 'rgba(0, 0, 0, 0.3)', 8 | contentBackgroundColor: '#f9f9f9', 9 | separatorColor: '#d7d7d7', 10 | fontSize: 18, 11 | color: '#007aff', 12 | titleStyle: { 13 | fontSize: 15, 14 | fontWeight: 'bold', 15 | color: '#8f8f8f', 16 | }, 17 | messageStyle: { 18 | fontSize: 15, 19 | color: '#8f8f8f', 20 | }, 21 | destructiveButtonStyle: { 22 | color: '#d11f1f', 23 | }, 24 | cancelButtonStyle: { 25 | fontWeight: 'bold', 26 | }, 27 | touchableUnderlayColor: '#dddddd', 28 | supportedOrientations: ['portrait', 'landscape'], 29 | }; 30 | 31 | constructor(props) { 32 | super(props); 33 | this.onWindowChange = this._onWindowChange.bind(this); 34 | this.state = { 35 | isLandscape: isLandscape(), 36 | }; 37 | } 38 | 39 | componentDidMount() { 40 | Dimensions.addEventListener('change', this.onWindowChange); 41 | } 42 | 43 | componentWillUnmount() { 44 | Dimensions.removeEventListener('change', this.onWindowChange); 45 | } 46 | 47 | render() { 48 | const { config, backgroundColor, supportedOrientations } = this.props; 49 | const { cancelButtonIndex } = config; 50 | const closeFunc = this._click.bind(this, cancelButtonIndex); 51 | return ( 52 | 53 | 60 | 61 | 62 | 63 | {this._renderSections()} 64 | 65 | 66 | ); 67 | } 68 | 69 | _renderSections = () => { 70 | const {width, height} = Dimensions.get('window'); 71 | const inset = getSafeAreaInset(); 72 | const { config } = this.props; 73 | const { title, message, options, cancelButtonIndex } = config; 74 | const contentStyle = { 75 | paddingHorizontal: 10, 76 | marginBottom: inset.bottom > 0 ? inset.bottom : 10, 77 | marginTop: this.state.isLandscape ? inset.top + 10 : inset.top + 44, 78 | }; 79 | contentStyle.maxHeight = height - contentStyle.marginBottom - contentStyle.marginTop; 80 | if (this.state.isLandscape) { 81 | contentStyle.width = Math.max(width / 3, height - 10 * 2); 82 | contentStyle.alignSelf = 'center'; 83 | } 84 | const section = []; 85 | let cancelView = null; 86 | (title || message) && section.push(this._renderTitle(title, message)); 87 | options.forEach((item, index) => { 88 | const itemView = this._renderItem(item, index); 89 | if (index === cancelButtonIndex) { 90 | cancelView = itemView; 91 | } else { 92 | section.push(itemView); 93 | } 94 | }); 95 | const sections = cancelView ? [section, cancelView] : [section]; 96 | return ( 97 | 98 | {sections.map((sectionItem, index) => { 99 | const style = index > 0 ? { 100 | marginTop: 9, 101 | } : {}; 102 | return this._renderSection(sectionItem, index, style); 103 | })} 104 | 105 | ); 106 | }; 107 | 108 | _renderTitle = (title, message) => { 109 | const { titleStyle, messageStyle } = this.props; 110 | const style = {marginVertical: 6}; 111 | return ( 112 | 113 | {title && ( 114 | 115 | {title} 116 | 117 | )} 118 | {message && ( 119 | 120 | {message} 121 | 122 | )} 123 | 124 | ); 125 | }; 126 | 127 | _renderItem = (item, index) => { 128 | const { config, destructiveButtonStyle, cancelButtonStyle, touchableUnderlayColor, fontSize, color } = this.props; 129 | const { destructiveButtonIndex, cancelButtonIndex } = config; 130 | const isCancel = index === cancelButtonIndex; 131 | const isDestructive = index === destructiveButtonIndex; 132 | const textStyle = isCancel ? cancelButtonStyle : 133 | isDestructive ? destructiveButtonStyle : null; 134 | return ( 135 | 141 | 142 | 143 | {item} 144 | 145 | 146 | 147 | ); 148 | }; 149 | 150 | _renderSection(items, index, style) { 151 | const {contentBackgroundColor: backgroundColor} = this.props; 152 | const isArray = Array.isArray(items); 153 | const Component = isArray ? ScrollView : View; 154 | const props = isArray ? { 155 | bounces: false, 156 | } : {}; 157 | return ( 158 | 163 | {isArray ? items.map((item, innerIndex) => { 164 | const views = [item]; 165 | if (innerIndex < items.length - 1) { 166 | views.push(this._renderSeparatorLine('sepline' + innerIndex)); 167 | } 168 | return views; 169 | }) : items} 170 | 171 | ); 172 | } 173 | 174 | _renderSeparatorLine(key) { 175 | const {separatorColor: backgroundColor} = this.props; 176 | return ( 177 | 181 | ); 182 | } 183 | 184 | _click(index) { 185 | this.props.callback && this.props.callback(index); 186 | } 187 | 188 | _onWindowChange() { 189 | this.setState({isLandscape: isLandscape()}); 190 | } 191 | } 192 | 193 | const styles = StyleSheet.create({ 194 | view: { 195 | position: 'absolute', 196 | top: 0, 197 | left: 0, 198 | right: 0, 199 | bottom: 0, 200 | opacity: 1, 201 | }, 202 | touchview: { 203 | flex: 1, 204 | backgroundColor: 'transparent', 205 | }, 206 | content: { 207 | flex: 0, 208 | }, 209 | section: { 210 | borderRadius: 11, 211 | overflow: 'hidden', 212 | }, 213 | seperator: { 214 | height: StyleSheet.hairlineWidth, 215 | }, 216 | title: { 217 | justifyContent: 'center', 218 | alignItems: 'center', 219 | paddingVertical: 10, 220 | }, 221 | button: { 222 | alignItems: 'center', 223 | justifyContent: 'center', 224 | paddingHorizontal: 16, 225 | height: 57, 226 | }, 227 | buttonView: { 228 | justifyContent: 'center', 229 | alignItems: 'center', 230 | }, 231 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Xiaosong Gao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-general-actionsheet 2 | 3 | [![npm version](https://img.shields.io/npm/v/react-native-general-actionsheet.svg?style=flat)](https://www.npmjs.com/package/react-native-general-actionsheet) 4 | [![Build Status](https://travis-ci.org/gaoxiaosong/react-native-general-actionsheet.svg?branch=master)](https://travis-ci.org/gaoxiaosong/react-native-general-actionsheet) 5 | 6 | [中文说明](https://www.jianshu.com/p/2377cca9a58c) 7 | 8 | This is a general ActionSheet api. You can use [ActionSheetIOS](https://facebook.github.io/react-native/docs/actionsheetios) in iOS and use a custom view in Android. Or you can use custom view in both iOS and Android. 9 | 10 | It only support `ActionSheet.showActionSheetWithOptions` now. 11 | 12 | ## ScreenShots 13 | 14 | ### Portrait 15 | 16 |

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |

27 | 28 | ### Landscape 29 | 30 |

31 | 32 | 33 | 34 | 35 | 36 |

37 | 38 |

39 | 40 | 41 | 42 | 43 | 44 |

45 | 46 | ## Install 47 | 48 | Install by Yarn: 49 | 50 | ```shell 51 | yarn add react-native-general-actionsheet 52 | ``` 53 | 54 | Install by NPM: 55 | 56 | ```shell 57 | npm install --save react-native-general-actionsheet 58 | ``` 59 | 60 | ## Usage 61 | 62 | Use the module in file: 63 | 64 | ```javascript 65 | import ActionSheet from 'react-native-general-actionsheet'; 66 | 67 | ActionSheet.showActionSheetWithOptions(options, callback); 68 | ``` 69 | 70 | Parameters `options` and `callback` is same as [ActionSheetIOS](https://facebook.github.io/react-native/docs/actionsheetios). 71 | 72 | ## Use `ActionSheetIOS` 73 | 74 | You can change using `ActionSheetIOS` or not globally: 75 | 76 | ```javascript 77 | import ActionSheet from 'react-native-general-actionsheet'; 78 | 79 | ActionSheet.useActionSheetIOS = true/false; 80 | ``` 81 | 82 | ## Customize Style 83 | 84 | You can change style of container globally. 85 | 86 | ```javascript 87 | import ActionSheet from 'react-native-general-actionsheet'; 88 | 89 | ActionSheet.Container.defaultProps.xxx = yyy; 90 | ``` 91 | 92 | It supports following properties: 93 | 94 | | Name | Type | Description | 95 | | :-: | :-: | :- | 96 | | backgroundColor | string | Background color of whole view | 97 | | contentBackgroundColor | string | Background color of content view | 98 | | separatorColor | string | Separator line color | 99 | | fontSize | number | Button text font size | 100 | | color | string | Button text color | 101 | | titleStyle | object | Style of title text | 102 | | messageStyle | object | Style of message text | 103 | | destructiveButtonStyle | object | Style of destructive button | 104 | | cancelButtonStyle | object | Style of cancel button | 105 | | touchableUnderlayColor | string | Underlay color of button touch action | 106 | | supportedOrientations | array | Supported orientations for iOS | 107 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { StyleProp, TextStyle, ViewStyle } from 'react-native'; 2 | 3 | declare namespace ActionSheet { 4 | interface StyleProps { 5 | // 整个视图的背景色 6 | backgroundColor: string; 7 | // 内容区的背景色 8 | contentBackgroundColor: string; 9 | // 分隔线的颜色 10 | separatorColor: string; 11 | // 按钮文本的字号 12 | fontSize: number; 13 | // 按钮文本的颜色 14 | color: string; 15 | // 顶部标题的样式 16 | titleStyle: StyleProp; 17 | // 顶部消息的样式 18 | messageStyle: StyleProp; 19 | // 辅助按钮的样式 20 | destructiveButtonStyle: StyleProp; 21 | // 取消按钮的样式 22 | cancelButtonStyle: StyleProp; 23 | // 按钮点击操作的Underlay颜色 24 | touchableUnderlayColor: string; 25 | } 26 | var Container: { 27 | defaultProps: Partial; 28 | }; 29 | var useActionSheetIOS: boolean; 30 | function showActionSheetWithOptions(config: { options: string[]; title?: string; message?: string; cancelButtonIndex?: number; }, callback: (index: number) => void): void; 31 | } 32 | 33 | export = ActionSheet; 34 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ActionSheetIOS, Platform } from 'react-native'; 3 | import ActionSheetContainer from './ActionSheetContainer'; 4 | import RootSiblings from 'react-native-root-siblings'; 5 | 6 | let instance = null; 7 | 8 | const ActionSheet = { 9 | Container: ActionSheetContainer, 10 | useActionSheetIOS: true, 11 | showActionSheetWithOptions: (config, callback) => { 12 | if (Platform.OS === 'ios' && ActionSheet.useActionSheetIOS) { 13 | ActionSheetIOS.showActionSheetWithOptions(config, callback); 14 | return; 15 | } 16 | if (instance) { 17 | return; 18 | } 19 | instance = new RootSiblings( 20 | { 23 | instance && instance.destroy(() => { 24 | instance = null; 25 | setTimeout(() => { 26 | callback && callback(index); 27 | }, 0); 28 | }); 29 | }} 30 | /> 31 | ); 32 | }, 33 | }; 34 | 35 | export default ActionSheet; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-general-actionsheet", 3 | "version": "1.0.4", 4 | "private": false, 5 | "description": "ActionSheet api on iOS and Android same as ActionSheetIOS.", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "./node_modules/.bin/eslint . --ext .js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/gaoxiaosong/react-native-general-actionsheet.git" 13 | }, 14 | "keywords": [ 15 | "React Native", 16 | "ActionSheet" 17 | ], 18 | "author": "Xiaosong Gao", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/gaoxiaosong/react-native-general-actionsheet/issues", 22 | "email": "gaoxiaosong06@gmail.com" 23 | }, 24 | "homepage": "https://github.com/gaoxiaosong/react-native-general-actionsheet#readme", 25 | "dependencies": { 26 | "react-native-safe-area-utility": "^1.0.0", 27 | "react-native-root-siblings": "^3.1.6" 28 | }, 29 | "devDependencies": { 30 | "eslint-plugin-automatic": "latest", 31 | "react": "latest" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /resource/Android-1-L.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxsshallot/react-native-general-actionsheet/5b57220dfe97fa4c59ec5edf70e493b65bc50ddb/resource/Android-1-L.jpeg -------------------------------------------------------------------------------- /resource/Android-1-P.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxsshallot/react-native-general-actionsheet/5b57220dfe97fa4c59ec5edf70e493b65bc50ddb/resource/Android-1-P.jpeg -------------------------------------------------------------------------------- /resource/Android-2-L.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxsshallot/react-native-general-actionsheet/5b57220dfe97fa4c59ec5edf70e493b65bc50ddb/resource/Android-2-L.jpeg -------------------------------------------------------------------------------- /resource/Android-2-P.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxsshallot/react-native-general-actionsheet/5b57220dfe97fa4c59ec5edf70e493b65bc50ddb/resource/Android-2-P.jpeg -------------------------------------------------------------------------------- /resource/iOS-1-L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxsshallot/react-native-general-actionsheet/5b57220dfe97fa4c59ec5edf70e493b65bc50ddb/resource/iOS-1-L.png -------------------------------------------------------------------------------- /resource/iOS-1-P.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxsshallot/react-native-general-actionsheet/5b57220dfe97fa4c59ec5edf70e493b65bc50ddb/resource/iOS-1-P.png -------------------------------------------------------------------------------- /resource/iOS-2-L.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxsshallot/react-native-general-actionsheet/5b57220dfe97fa4c59ec5edf70e493b65bc50ddb/resource/iOS-2-L.png -------------------------------------------------------------------------------- /resource/iOS-2-P.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxsshallot/react-native-general-actionsheet/5b57220dfe97fa4c59ec5edf70e493b65bc50ddb/resource/iOS-2-P.png --------------------------------------------------------------------------------