diff --git a/README.md b/README.md index 2506494..b057d31 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ The below props allow modification of the Android ActionSheet. They have no effe | `separatorStyle` | ViewStyle | Modify the look of the separators rather than use the default look. | | `useModal` | boolean | Defaults to `false` (`true` if autoFocus is also `true`) Wraps the ActionSheet with a Modal, in order to show in front of other Modals that were already opened ([issue reference](https://github.com/expo/react-native-action-sheet/issues/164)). | | `destructiveColor` | string | Modify color for text of destructive option. Defaults to `#d32f2f`. | +| `stickyCancel` | boolean | Moves the option specified by `cancelButtonIndex` to the bottom of the action sheet and outside of the underlying `ScrollView`. Defaults to `false`. | ## ActionSheetProvider Props diff --git a/example/App.tsx b/example/App.tsx index 51bb77d..66161ac 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -207,6 +207,14 @@ class App extends React.Component { onSelection={this._updateSelectionText} showActionSheetWithOptions={showActionSheetWithOptions} /> + {this._renderSectionHeader('Special Cases')} Open Modal diff --git a/example/ShowActionSheetButton.tsx b/example/ShowActionSheetButton.tsx index d4e0fd5..a1aee27 100644 --- a/example/ShowActionSheetButton.tsx +++ b/example/ShowActionSheetButton.tsx @@ -21,7 +21,9 @@ interface Props { withCustomStyles?: boolean; withCancelButtonTintColor?: boolean; withAnchor?: boolean; + withExtendedOptions?: boolean; useModal?: boolean; + stickyCancel?: boolean; } // A custom button that shows examples of different share sheet configurations @@ -34,8 +36,10 @@ export default class ShowActionSheetButton extends React.PureComponent { withCustomStyles: false, withAnchor: false, withCancelButtonTintColor: false, + withExtendedOptions: false, onSelection: null, useModal: false, + stickyCancel: false, }; _anchorRef = React.createRef(); @@ -52,10 +56,33 @@ export default class ShowActionSheetButton extends React.PureComponent { onSelection, showActionSheetWithOptions, useModal, + stickyCancel, + withExtendedOptions, } = this.props; - // Same interface as https://facebook.github.io/react-native/docs/actionsheetios.html const options = ['Delete', 'Disabled', 'Save', 'Cancel']; + const extendedOptions = [ + 'Option 1', + 'Option 2', + 'Option 3', + 'Option 4', + 'Option 5', + 'Option 6', + 'Option 7', + 'Option 8', + 'Option 9', + 'Option 10', + 'Option 11', + 'Option 12', + 'Option 13', + 'Option 14', + 'Option 15', + 'Option 16', + 'Option 17', + 'Option 18', + 'Option 19', + 'Option 20', + ]; const icons = withIcons ? [icon('delete'), icon('save'), icon('share'), icon('cancel')] : undefined; @@ -92,14 +119,17 @@ export default class ShowActionSheetButton extends React.PureComponent { ? { backgroundColor: 'lightgrey', } - : undefined; + : {}; + if (withExtendedOptions) { + containerStyle.maxHeight = 500; + } const anchor: number | null = this._anchorRef.current ? findNodeHandle(this._anchorRef.current) : null; showActionSheetWithOptions( { - options, + options: withExtendedOptions ? [...options, ...extendedOptions] : options, cancelButtonIndex, cancelButtonTintColor: withCancelButtonTintColor ? '#D93F0B' : undefined, destructiveButtonIndex, @@ -123,6 +153,8 @@ export default class ShowActionSheetButton extends React.PureComponent { containerStyle, // Android only, useModal, + // Android only + stickyCancel, }, (buttonIndex?: number) => { // Do something here depending on the button index selected diff --git a/src/ActionSheet/ActionGroup.tsx b/src/ActionSheet/ActionGroup.tsx index 5f93b0c..49e79c7 100644 --- a/src/ActionSheet/ActionGroup.tsx +++ b/src/ActionSheet/ActionGroup.tsx @@ -24,6 +24,7 @@ type Props = ActionSheetOptions & { const BLACK_54PC_TRANSPARENT = '#0000008a'; const BLACK_87PC_TRANSPARENT = '#000000de'; const DESTRUCTIVE_COLOR = '#d32f2f'; +const RIPPLE_COLOR = 'rgba(180, 180, 180, 1)'; /** * Can be used as a React ref for a component to auto-focus for accessibility on render. @@ -66,6 +67,7 @@ export default class ActionGroup extends React.Component { showSeparators: false, tintIcons: true, textStyle: {}, + stickyCancel: false, }; render() { @@ -73,6 +75,7 @@ export default class ActionGroup extends React.Component { {this._renderTitleContent()} {this._renderOptionViews()} + {this._renderCancelButton()} ); } @@ -131,12 +134,10 @@ export default class ActionGroup extends React.Component { tintColor, autoFocus, showSeparators, + stickyCancel, } = this.props; const optionViews: React.ReactNode[] = []; - const nativeFeedbackBackground = TouchableNativeFeedbackSafe.Ripple( - 'rgba(180, 180, 180, 1)', - false - ); + const nativeFeedbackBackground = TouchableNativeFeedbackSafe.Ripple(RIPPLE_COLOR, false); for (let i = startIndex; i < startIndex + length; i++) { const defaultColor = tintColor @@ -144,6 +145,9 @@ export default class ActionGroup extends React.Component { : (textStyle || {}).color || BLACK_87PC_TRANSPARENT; const disabled = isIndexDisabled(i, disabledButtonIndices); const isCancelButton = i === cancelButtonIndex; + if (isCancelButton && stickyCancel) { + continue; + } const color = isIndexDestructive(i, destructiveButtonIndex) ? destructiveColor : isCancelButton @@ -174,6 +178,46 @@ export default class ActionGroup extends React.Component { return optionViews; }; + + _renderCancelButton = () => { + if (!this.props.stickyCancel || this.props.cancelButtonIndex === undefined) { + return null; + } + const { + options, + icons, + cancelButtonIndex, + cancelButtonTintColor, + disabledButtonIndices, + onSelect, + textStyle, + tintColor, + showSeparators, + } = this.props; + const disabled = isIndexDisabled(cancelButtonIndex, disabledButtonIndices); + const defaultColor = tintColor ? tintColor : (textStyle || {}).color || BLACK_87PC_TRANSPARENT; + const color = cancelButtonTintColor || defaultColor; + const nativeFeedbackBackground = TouchableNativeFeedbackSafe.Ripple(RIPPLE_COLOR, false); + const iconSource = icons != null ? icons[cancelButtonIndex] : null; + const cancelOption = options[cancelButtonIndex]; + + return ( + <> + {showSeparators && this._renderRowSeparator('cancel')} + onSelect(cancelButtonIndex)} + style={[styles.button, disabled && styles.disabledButton]} + accessibilityRole="button" + accessibilityLabel={cancelOption}> + {this._renderIconElement(iconSource, color)} + {cancelOption} + + + ); + }; } const styles = StyleSheet.create({ diff --git a/src/ActionSheet/CustomActionSheet.tsx b/src/ActionSheet/CustomActionSheet.tsx index e88906c..f1790d8 100644 --- a/src/ActionSheet/CustomActionSheet.tsx +++ b/src/ActionSheet/CustomActionSheet.tsx @@ -140,6 +140,7 @@ export default class CustomActionSheet extends React.Component { separatorStyle, cancelButtonIndex, cancelButtonTintColor, + stickyCancel, } = options; return ( @@ -182,6 +183,7 @@ export default class CustomActionSheet extends React.Component { showSeparators={showSeparators} containerStyle={containerStyle} separatorStyle={separatorStyle} + stickyCancel={stickyCancel} /> diff --git a/src/types.ts b/src/types.ts index 44ad7ca..814b84d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -42,4 +42,5 @@ export interface ActionSheetOptions extends ActionSheetIOSOptions { separatorStyle?: ViewStyle; useModal?: boolean; destructiveColor?: string; + stickyCancel?: boolean; }