From 8c60699ac4e1d00e1a877698d470f83f1c6b93c6 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 12 Apr 2023 12:30:40 +0200 Subject: [PATCH 01/47] create move focus functions for web and native --- src/libs/Accessibility/index.js | 10 ++++++++++ src/libs/Accessibility/index.native.js | 12 ++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/libs/Accessibility/index.js create mode 100644 src/libs/Accessibility/index.native.js diff --git a/src/libs/Accessibility/index.js b/src/libs/Accessibility/index.js new file mode 100644 index 000000000000..5257a2b4f4e3 --- /dev/null +++ b/src/libs/Accessibility/index.js @@ -0,0 +1,10 @@ +const moveAccessibilityFocus = (ref) => { + if (!ref || !ref.current) { + return; + } + ref.current.focus(); +}; + +export default { + moveAccessibilityFocus, +}; diff --git a/src/libs/Accessibility/index.native.js b/src/libs/Accessibility/index.native.js new file mode 100644 index 000000000000..9b07fd6ff40d --- /dev/null +++ b/src/libs/Accessibility/index.native.js @@ -0,0 +1,12 @@ +import {AccessibilityInfo} from 'react-native'; + +const moveAccessibilityFocus = (ref) => { + if (!ref) { + return; + } + AccessibilityInfo.sendAccssibilityEvent(ref, 'focus'); +}; + +export default { + moveAccessibilityFocus, +}; From 8c823daccde2ef036fa068fa4d939604d2e571e6 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 12 Apr 2023 14:04:10 +0200 Subject: [PATCH 02/47] create useScreenReaderStatus hook --- src/libs/Accessibility/index.js | 23 +++++++++++++++---- .../moveAccessibilityFocus/index.js | 8 +++++++ .../index.native.js | 4 +--- 3 files changed, 27 insertions(+), 8 deletions(-) create mode 100644 src/libs/Accessibility/moveAccessibilityFocus/index.js rename src/libs/Accessibility/{ => moveAccessibilityFocus}/index.native.js (80%) diff --git a/src/libs/Accessibility/index.js b/src/libs/Accessibility/index.js index 5257a2b4f4e3..0e866d4543f9 100644 --- a/src/libs/Accessibility/index.js +++ b/src/libs/Accessibility/index.js @@ -1,10 +1,23 @@ -const moveAccessibilityFocus = (ref) => { - if (!ref || !ref.current) { - return; - } - ref.current.focus(); +import {useEffect, useState} from 'react'; +import {AccessibilityInfo} from 'react-native'; +import moveAccessibilityFocus from './moveAccessibilityFocus'; + +const useScreenReaderStatus = () => { + const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false); + useEffect(() => { + const unsubscribe = AccessibilityInfo.addEventListener('screenReaderChanged', (isEnabled) => { + setIsScreenReaderEnabled(isEnabled); + }); + + return () => { + unsubscribe(); + }; + }, []); + + return isScreenReaderEnabled; }; export default { moveAccessibilityFocus, + useScreenReaderStatus, }; diff --git a/src/libs/Accessibility/moveAccessibilityFocus/index.js b/src/libs/Accessibility/moveAccessibilityFocus/index.js new file mode 100644 index 000000000000..290576f01ba4 --- /dev/null +++ b/src/libs/Accessibility/moveAccessibilityFocus/index.js @@ -0,0 +1,8 @@ +const moveAccessibilityFocus = (ref) => { + if (!ref || !ref.current) { + return; + } + ref.current.focus(); +}; + +export default moveAccessibilityFocus; \ No newline at end of file diff --git a/src/libs/Accessibility/index.native.js b/src/libs/Accessibility/moveAccessibilityFocus/index.native.js similarity index 80% rename from src/libs/Accessibility/index.native.js rename to src/libs/Accessibility/moveAccessibilityFocus/index.native.js index 9b07fd6ff40d..748084278095 100644 --- a/src/libs/Accessibility/index.native.js +++ b/src/libs/Accessibility/moveAccessibilityFocus/index.native.js @@ -7,6 +7,4 @@ const moveAccessibilityFocus = (ref) => { AccessibilityInfo.sendAccssibilityEvent(ref, 'focus'); }; -export default { - moveAccessibilityFocus, -}; +export default moveAccessibilityFocus; \ No newline at end of file From 737f6dc82690c00cff7d0d4b493aca9a8f93af4c Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 12 Apr 2023 19:49:48 +0200 Subject: [PATCH 03/47] create base pressable files structure --- .../GenericPressable/GenericPressable.js | 21 +++++++++++++++++++ .../Pressable/GenericPressable/PropTypes.js | 9 ++++++++ .../Pressable/GenericPressable/index.js | 3 +++ .../GenericPressable/index.native.js | 9 ++++++++ .../Pressable/PressableWithFeedback.js | 3 +++ .../Pressable/PressableWithoutFeedback.js | 0 src/components/Pressable/index.js | 3 +++ 7 files changed, 48 insertions(+) create mode 100644 src/components/Pressable/GenericPressable/GenericPressable.js create mode 100644 src/components/Pressable/GenericPressable/PropTypes.js create mode 100644 src/components/Pressable/GenericPressable/index.js create mode 100644 src/components/Pressable/GenericPressable/index.native.js create mode 100644 src/components/Pressable/PressableWithFeedback.js create mode 100644 src/components/Pressable/PressableWithoutFeedback.js create mode 100644 src/components/Pressable/index.js diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js new file mode 100644 index 000000000000..2560fec1d53f --- /dev/null +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -0,0 +1,21 @@ +import React from 'react'; +import {Pressable} from 'react-native'; +import _ from 'lodash'; +const GenericPressable = (props) => { + const rest = _.omit(props, ['children']); + + return ( + + {props.children} + +); +}; + +GenericPressable.displayName = 'GenericPressable'; +GenericPressable.propTypes = genericPressablePropTypes.propTypes; +GenericPressable.defaultProps = genericPressablePropTypes.defaultProps; + +export default GenericPressable; diff --git a/src/components/Pressable/GenericPressable/PropTypes.js b/src/components/Pressable/GenericPressable/PropTypes.js new file mode 100644 index 000000000000..ddd9062e9b74 --- /dev/null +++ b/src/components/Pressable/GenericPressable/PropTypes.js @@ -0,0 +1,9 @@ +import PropTypes from 'prop-types'; +const propTypes = { +const defaultProps = { +}; + +export default { + propTypes, + defaultProps, +}; diff --git a/src/components/Pressable/GenericPressable/index.js b/src/components/Pressable/GenericPressable/index.js new file mode 100644 index 000000000000..92716994214b --- /dev/null +++ b/src/components/Pressable/GenericPressable/index.js @@ -0,0 +1,3 @@ +import GenericPressable from './GenericPressable'; + +export default GenericPressable; diff --git a/src/components/Pressable/GenericPressable/index.native.js b/src/components/Pressable/GenericPressable/index.native.js new file mode 100644 index 000000000000..1de00205362f --- /dev/null +++ b/src/components/Pressable/GenericPressable/index.native.js @@ -0,0 +1,9 @@ +import GenericPressable from './GenericPressable'; + +const WebGenericPressable = (props) => { + const accessibilityMappedProps = props; + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +}; + +export default WebGenericPressable; diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js new file mode 100644 index 000000000000..5306c0abd9b2 --- /dev/null +++ b/src/components/Pressable/PressableWithFeedback.js @@ -0,0 +1,3 @@ +import GenericPressable from './GenericPressable'; + +export default GenericPressable; \ No newline at end of file diff --git a/src/components/Pressable/PressableWithoutFeedback.js b/src/components/Pressable/PressableWithoutFeedback.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/components/Pressable/index.js b/src/components/Pressable/index.js new file mode 100644 index 000000000000..83f597b3de8d --- /dev/null +++ b/src/components/Pressable/index.js @@ -0,0 +1,3 @@ +export {default as GenericPressable} from './GenericPressable'; +export {default as PressableWithFeedback} from './PressableWithFeedback'; +export {default as PressableWithoutFeedback} from './PressableWithoutFeedback'; From ecec5eb44d812c37f0b0de1f89ae520ba910580a Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 13 Apr 2023 19:27:49 +0200 Subject: [PATCH 04/47] create base GenericPressable component --- .../GenericPressable/GenericPressable.js | 124 +++++++++++++++++- .../Pressable/GenericPressable/PropTypes.js | 36 +++++ .../Pressable/GenericPressable/index.js | 28 +++- .../GenericPressable/index.native.js | 7 +- src/styles/styles.js | 4 + 5 files changed, 188 insertions(+), 11 deletions(-) diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js index 2560fec1d53f..209df8f1203e 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -1,21 +1,137 @@ import React from 'react'; import {Pressable} from 'react-native'; import _ from 'lodash'; +import Accessibility from '../../../libs/Accessibility'; +import HapticFeedback from '../../../libs/HapticFeedback'; +import KeyboardShortcut from '../../../libs/KeyboardShortcut'; +import styles from '../../../styles/styles'; +import genericPressablePropTypes from './propTypes'; + +const parseStyleFromFunction = (style, state) => (_.isFunction(style) ? style(state) : style); + +const getCursorStyle = (isDisabled, isText) => { + if (isDisabled) { + return styles.cursorDisabled; + } + if (isText) { + return styles.cursorText; + } + return styles.cursorPointer; +}; + const GenericPressable = (props) => { - const rest = _.omit(props, ['children']); + // eslint-disable-next-line react/destructuring-assignment + const { + children, + onPress, + onLongPress, + onKeyPress, + disabled, + nativeID, + style, + accessibilityHint, + shouldUseHapticsOnLongPress, + shouldUseHapticsOnPress, + nextFocusRef, + keyboardShortcut, + forwardedRef, + ...rest + } = props; + + const isScreenReaderActive = Accessibility.useScreenReaderStatus(); + const shouldScreenReaderDisableComponent = React.useMemo(() => { + switch (props.screenReaderActive) { + case 'always_active': + return true; + case 'disabled': + return !isScreenReaderActive; + case 'active': + return isScreenReaderActive; + default: + return false; + } + }, [isScreenReaderActive, props.screenReaderActive]); + + const onLongPressHandler = () => { + if (shouldUseHapticsOnLongPress) { + HapticFeedback.longPress(); + } + const pressFunction = onLongPress || onPress; + pressFunction(); + Accessibility.moveAccessibilityFocus(props.nextFocusRef); + }; + + const onPressHandler = React.useCallback(() => { + if (shouldUseHapticsOnPress) { + HapticFeedback.press(); + } + onPress(); + Accessibility.moveAccessibilityFocus(nextFocusRef); + }, [shouldUseHapticsOnPress, onPress, nextFocusRef]); + + const onKeyPressHandler = (event) => { + if (event.key !== 'Enter') { + return; + } + onPressHandler(); + }; + + React.useEffect(() => { + if (!keyboardShortcut) { + return; + } + const {shortcutKey, descriptionKey, modifiers} = keyboardShortcut; + const unsubscribe = KeyboardShortcut.subscribe(shortcutKey, onPressHandler, descriptionKey, modifiers, true, false, 0, false); + return () => { + if (!unsubscribe) { + return; + } + unsubscribe(); + }; + }, [keyboardShortcut, onPressHandler]); return ( [ + getCursorStyle(props.disabled, props.accessibilityRole === 'text'), + props.style, + isScreenReaderActive && props.screenReaderActiveStyle, + state.focused && parseStyleFromFunction(props.focusStyle, state), + state.hovered && parseStyleFromFunction(props.hoverStyle, state), + state.pressed && parseStyleFromFunction(props.pressedStyle, state), + props.disabled && [parseStyleFromFunction(props.disabledStyle, state), styles.cursorDisabled, styles.noSelect], + ]} + + // accessibility props + accessibilityHint={props.accessibilityHint || props.accessibilityLabel} + accessibilityState={{ + disabled: props.disabled, + ...props.accessibilityState, + }} + + // ios-only form of inputs + onMagicTap={onPressHandler} + onAccessibilityTap={onPressHandler} // eslint-disable-next-line react/jsx-props-no-spreading {...rest} > - {props.children} + {state => (_.isFunction(props.children) ? props.children({...state, isScreenReaderActive}) : props.children) } -); + ); }; GenericPressable.displayName = 'GenericPressable'; GenericPressable.propTypes = genericPressablePropTypes.propTypes; GenericPressable.defaultProps = genericPressablePropTypes.defaultProps; -export default GenericPressable; +export default React.forwardRef((props, ref) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + +)); diff --git a/src/components/Pressable/GenericPressable/PropTypes.js b/src/components/Pressable/GenericPressable/PropTypes.js index ddd9062e9b74..171b052a3cf2 100644 --- a/src/components/Pressable/GenericPressable/PropTypes.js +++ b/src/components/Pressable/GenericPressable/PropTypes.js @@ -1,6 +1,42 @@ import PropTypes from 'prop-types'; +import {PressableProps} from 'react-native'; +import * as StyleUtils from '../../../styles/StyleUtils'; + +const stylePropTypeWithFunction = PropTypes.oneOfType([ + StyleUtils.stylePropType, + PropTypes.func, +]); + const propTypes = { + ...PressableProps, + onPress: PropTypes.func.isRequired, + keyboardShortcut: PropTypes.shape({ + descriptionKey: PropTypes.string.isRequired, + shortcutKey: PropTypes.string.isRequired, + modifiers: PropTypes.arrayOf(PropTypes.string), + }), + shouldUseHapticsOnPress: PropTypes.bool, + shouldUseHapticsOnLongPress: PropTypes.bool, + disabledStyle: stylePropTypeWithFunction, + hoverStyle: stylePropTypeWithFunction, + focusStyle: stylePropTypeWithFunction, + pressedStyle: stylePropTypeWithFunction, + screenReaderActiveStyle: stylePropTypeWithFunction, + enableInScreenReaderStates: PropTypes.oneOf(['all', 'active', 'disabled']), + nextFocusRef: PropTypes.func, + accessibilityLabel: PropTypes.string.isRequired, +}; + const defaultProps = { + keyboardShortcut: undefined, + shouldUseHapticsOnPress: false, + shouldUseHapticsOnLongPress: false, + disabledStyle: {}, + hoverStyle: {}, + focusStyle: {}, + pressedStyle: {}, + screenReaderActiveStyle: {}, + screenReaderActive: 'always_active', }; export default { diff --git a/src/components/Pressable/GenericPressable/index.js b/src/components/Pressable/GenericPressable/index.js index 92716994214b..63603de23a26 100644 --- a/src/components/Pressable/GenericPressable/index.js +++ b/src/components/Pressable/GenericPressable/index.js @@ -1,3 +1,29 @@ +import _ from 'underscore'; import GenericPressable from './GenericPressable'; +import GenericPressablePropTypes from './propTypes'; -export default GenericPressable; +const WebGenericPressable = (props) => { + // change all props with accessibility* to aria-* + const accessibilityMappedProps = _.mapKeys(props, (value, key) => { + if (key === 'nativeID') { + return 'id'; + } + if (key === 'accessibilityRole') { + return 'role'; + } + if (_.startsWith(key, 'accessibility')) { + return `aria-${key.slice(13).toLowerCase()}`; + } + return key; + }); + if (!props.accessible || !props.focusable) { + accessibilityMappedProps.tabIndex = -1; + } + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +}; + +WebGenericPressable.propTypes = GenericPressablePropTypes.propTypes; +WebGenericPressable.defaultProps = GenericPressablePropTypes.defaultProps; + +export default WebGenericPressable; diff --git a/src/components/Pressable/GenericPressable/index.native.js b/src/components/Pressable/GenericPressable/index.native.js index 1de00205362f..cba6984fd96d 100644 --- a/src/components/Pressable/GenericPressable/index.native.js +++ b/src/components/Pressable/GenericPressable/index.native.js @@ -1,9 +1,4 @@ import GenericPressable from './GenericPressable'; -const WebGenericPressable = (props) => { - const accessibilityMappedProps = props; - // eslint-disable-next-line react/jsx-props-no-spreading - return ; -}; +export default GenericPressable; -export default WebGenericPressable; diff --git a/src/styles/styles.js b/src/styles/styles.js index 30fc7144ac8a..5bcef8ba7cf6 100644 --- a/src/styles/styles.js +++ b/src/styles/styles.js @@ -2516,6 +2516,10 @@ const styles = { cursor: 'pointer', }, + cursorText: { + cursor: 'text', + }, + fullscreenCard: { position: 'absolute', left: 0, From d3b843ecf0bbc878e68cbbf55b3c77e8e4bcf33c Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 13 Apr 2023 19:37:24 +0200 Subject: [PATCH 05/47] add hitSlop calculation for pressable component --- .../GenericPressable/GenericPressable.js | 6 +++++ .../Pressable/GenericPressable/PropTypes.js | 5 +++- src/libs/Accessibility/index.js | 27 ++++++++++++++++++- .../moveAccessibilityFocus/index.js | 2 +- .../moveAccessibilityFocus/index.native.js | 2 +- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js index 209df8f1203e..551e5a8ad541 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -35,10 +35,12 @@ const GenericPressable = (props) => { nextFocusRef, keyboardShortcut, forwardedRef, + shouldUseAutoHitSlop, ...rest } = props; const isScreenReaderActive = Accessibility.useScreenReaderStatus(); + const [hitslop, onLayout] = Accessibility.useAutoHitSlop(); const shouldScreenReaderDisableComponent = React.useMemo(() => { switch (props.screenReaderActive) { case 'always_active': @@ -92,6 +94,9 @@ const GenericPressable = (props) => { return ( { // ios-only form of inputs onMagicTap={onPressHandler} onAccessibilityTap={onPressHandler} + // eslint-disable-next-line react/jsx-props-no-spreading {...rest} > diff --git a/src/components/Pressable/GenericPressable/PropTypes.js b/src/components/Pressable/GenericPressable/PropTypes.js index 171b052a3cf2..152092b34672 100644 --- a/src/components/Pressable/GenericPressable/PropTypes.js +++ b/src/components/Pressable/GenericPressable/PropTypes.js @@ -25,6 +25,7 @@ const propTypes = { enableInScreenReaderStates: PropTypes.oneOf(['all', 'active', 'disabled']), nextFocusRef: PropTypes.func, accessibilityLabel: PropTypes.string.isRequired, + shouldUseAutoHitSlop: PropTypes.bool, }; const defaultProps = { @@ -36,7 +37,9 @@ const defaultProps = { focusStyle: {}, pressedStyle: {}, screenReaderActiveStyle: {}, - screenReaderActive: 'always_active', + enableInScreenReaderStates: 'all', + nextFocusRef: undefined, + shouldUseAutoHitSlop: true, }; export default { diff --git a/src/libs/Accessibility/index.js b/src/libs/Accessibility/index.js index 0e866d4543f9..cf15721f1d9c 100644 --- a/src/libs/Accessibility/index.js +++ b/src/libs/Accessibility/index.js @@ -1,5 +1,6 @@ -import {useEffect, useState} from 'react'; +import {useEffect, useState, useCallback} from 'react'; import {AccessibilityInfo} from 'react-native'; +import _ from 'lodash'; import moveAccessibilityFocus from './moveAccessibilityFocus'; const useScreenReaderStatus = () => { @@ -17,7 +18,31 @@ const useScreenReaderStatus = () => { return isScreenReaderEnabled; }; +const getHitSlopForSize = ({x, y}) => { + const minimumSize = 44; + const hitSlopVertical = _.max([minimumSize - x, 0]); + const hitSlopHorizontal = _.max([minimumSize - y, 0]); + return { + top: hitSlopVertical, + bottom: hitSlopVertical, + left: hitSlopHorizontal, + right: hitSlopHorizontal, + }; +}; + +const useAutoHitSlop = () => { + const [frameSize, setFrameSize] = useState({x: 0, y: 0}); + const onLayout = useCallback((event) => { + const {layout} = event.nativeEvent; + if (layout.width !== frameSize.x && layout.height !== frameSize.y) { + setFrameSize({frameSize: {x: layout.width, y: layout.height}}); + } + }, [frameSize]); + return [getHitSlopForSize(frameSize), onLayout]; +}; + export default { moveAccessibilityFocus, useScreenReaderStatus, + useAutoHitSlop, }; diff --git a/src/libs/Accessibility/moveAccessibilityFocus/index.js b/src/libs/Accessibility/moveAccessibilityFocus/index.js index 290576f01ba4..c9130c7e34be 100644 --- a/src/libs/Accessibility/moveAccessibilityFocus/index.js +++ b/src/libs/Accessibility/moveAccessibilityFocus/index.js @@ -5,4 +5,4 @@ const moveAccessibilityFocus = (ref) => { ref.current.focus(); }; -export default moveAccessibilityFocus; \ No newline at end of file +export default moveAccessibilityFocus; diff --git a/src/libs/Accessibility/moveAccessibilityFocus/index.native.js b/src/libs/Accessibility/moveAccessibilityFocus/index.native.js index 748084278095..73225b48e7e5 100644 --- a/src/libs/Accessibility/moveAccessibilityFocus/index.native.js +++ b/src/libs/Accessibility/moveAccessibilityFocus/index.native.js @@ -7,4 +7,4 @@ const moveAccessibilityFocus = (ref) => { AccessibilityInfo.sendAccssibilityEvent(ref, 'focus'); }; -export default moveAccessibilityFocus; \ No newline at end of file +export default moveAccessibilityFocus; From d8eec9858ffe9b02058162a9e7f8273b8ac669a6 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 13 Apr 2023 20:52:20 +0200 Subject: [PATCH 06/47] calculate disabled state before GenericPressable return, wrap press handlers in useCallback --- .../GenericPressable/GenericPressable.js | 54 +++++++++++-------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js index 551e5a8ad541..64a5aac7acc2 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useCallback, useEffect, useMemo} from 'react'; import {Pressable} from 'react-native'; import _ from 'lodash'; import Accessibility from '../../../libs/Accessibility'; @@ -36,49 +36,61 @@ const GenericPressable = (props) => { keyboardShortcut, forwardedRef, shouldUseAutoHitSlop, + enableInScreenReaderStates, ...rest } = props; const isScreenReaderActive = Accessibility.useScreenReaderStatus(); const [hitslop, onLayout] = Accessibility.useAutoHitSlop(); - const shouldScreenReaderDisableComponent = React.useMemo(() => { - switch (props.screenReaderActive) { - case 'always_active': - return true; + const isDisabled = useMemo(() => { + let shouldBeDisabledByScreenReader = false; + switch (enableInScreenReaderStates) { + case 'all': + shouldBeDisabledByScreenReader = true; + break; case 'disabled': - return !isScreenReaderActive; + shouldBeDisabledByScreenReader = !isScreenReaderActive; + break; case 'active': - return isScreenReaderActive; + shouldBeDisabledByScreenReader = isScreenReaderActive; + break; default: - return false; + break; } - }, [isScreenReaderActive, props.screenReaderActive]); + return props.disabled || shouldBeDisabledByScreenReader; + }, [isScreenReaderActive, enableInScreenReaderStates, props.disabled]); - const onLongPressHandler = () => { + const onLongPressHandler = useCallback(() => { + if (isDisabled) { + return; + } if (shouldUseHapticsOnLongPress) { HapticFeedback.longPress(); } const pressFunction = onLongPress || onPress; pressFunction(); - Accessibility.moveAccessibilityFocus(props.nextFocusRef); - }; + Accessibility.moveAccessibilityFocus(nextFocusRef); + }, [shouldUseHapticsOnLongPress, onLongPress, onPress, nextFocusRef, isDisabled]); - const onPressHandler = React.useCallback(() => { + const onPressHandler = useCallback(() => { + if (isDisabled) { + return; + } if (shouldUseHapticsOnPress) { HapticFeedback.press(); } onPress(); Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnPress, onPress, nextFocusRef]); + }, [shouldUseHapticsOnPress, onPress, nextFocusRef, isDisabled]); - const onKeyPressHandler = (event) => { + const onKeyPressHandler = useCallback((event) => { if (event.key !== 'Enter') { return; } onPressHandler(); - }; + }, [onPressHandler]); - React.useEffect(() => { + useEffect(() => { if (!keyboardShortcut) { return; } @@ -103,15 +115,15 @@ const GenericPressable = (props) => { onPress={onPressHandler} onLongPress={onLongPressHandler} onKeyPress={onKeyPressHandler} - disabled={props.disabled || shouldScreenReaderDisableComponent} + disabled={isDisabled} style={state => [ getCursorStyle(props.disabled, props.accessibilityRole === 'text'), props.style, - isScreenReaderActive && props.screenReaderActiveStyle, + isScreenReaderActive && parseStyleFromFunction(props.screenReaderActiveStyle, state), state.focused && parseStyleFromFunction(props.focusStyle, state), state.hovered && parseStyleFromFunction(props.hoverStyle, state), state.pressed && parseStyleFromFunction(props.pressedStyle, state), - props.disabled && [parseStyleFromFunction(props.disabledStyle, state), styles.cursorDisabled, styles.noSelect], + isDisabled && [parseStyleFromFunction(props.disabledStyle, state), styles.cursorDisabled, styles.noSelect], ]} // accessibility props @@ -128,7 +140,7 @@ const GenericPressable = (props) => { // eslint-disable-next-line react/jsx-props-no-spreading {...rest} > - {state => (_.isFunction(props.children) ? props.children({...state, isScreenReaderActive}) : props.children) } + {state => (_.isFunction(props.children) ? props.children({...state, isScreenReaderActive, isDisabled}) : props.children) } ); }; From dba339f21c79c23bdc0bc49335d424f195419cce Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 13 Apr 2023 21:00:29 +0200 Subject: [PATCH 07/47] fix wrong import of propTypes to GenericPressable --- src/components/Pressable/GenericPressable/GenericPressable.js | 2 +- src/components/Pressable/GenericPressable/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js index 64a5aac7acc2..f6d413e79109 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -5,7 +5,7 @@ import Accessibility from '../../../libs/Accessibility'; import HapticFeedback from '../../../libs/HapticFeedback'; import KeyboardShortcut from '../../../libs/KeyboardShortcut'; import styles from '../../../styles/styles'; -import genericPressablePropTypes from './propTypes'; +import genericPressablePropTypes from './PropTypes'; const parseStyleFromFunction = (style, state) => (_.isFunction(style) ? style(state) : style); diff --git a/src/components/Pressable/GenericPressable/index.js b/src/components/Pressable/GenericPressable/index.js index 63603de23a26..83410387d64b 100644 --- a/src/components/Pressable/GenericPressable/index.js +++ b/src/components/Pressable/GenericPressable/index.js @@ -1,6 +1,6 @@ import _ from 'underscore'; import GenericPressable from './GenericPressable'; -import GenericPressablePropTypes from './propTypes'; +import GenericPressablePropTypes from './PropTypes'; const WebGenericPressable = (props) => { // change all props with accessibility* to aria-* From 47035c30efd2f4b36ec0c13acf2516f07ad7c690 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 17 Apr 2023 11:32:53 +0200 Subject: [PATCH 08/47] update OpacityView to use reanimated and enable dimming to multiple opacity values --- src/components/OpacityView.js | 75 +++++++++++++---------------------- 1 file changed, 28 insertions(+), 47 deletions(-) diff --git a/src/components/OpacityView.js b/src/components/OpacityView.js index 9b9d24861323..75216a6f426c 100644 --- a/src/components/OpacityView.js +++ b/src/components/OpacityView.js @@ -1,6 +1,7 @@ import React from 'react'; -import {Animated} from 'react-native'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import PropTypes from 'prop-types'; +import * as StyleUtils from '../styles/StyleUtils'; const propTypes = { // Should we dim the view @@ -12,60 +13,40 @@ const propTypes = { // Array of style objects // eslint-disable-next-line react/forbid-prop-types style: PropTypes.arrayOf(PropTypes.object), + + // The value to use for the opacity when the view is dimmed + dimmingValue: PropTypes.number, }; const defaultProps = { style: [], + dimmingValue: 0.5, }; -class OpacityView extends React.Component { - constructor(props) { - super(props); - this.opacity = new Animated.Value(1); - this.undim = this.undim.bind(this); - } - - componentDidUpdate(prevProps) { - if (!prevProps.shouldDim && this.props.shouldDim) { - Animated.timing(this.opacity, { - toValue: 0.5, - duration: 50, - useNativeDriver: true, - }).start(); - } - - if (prevProps.shouldDim && !this.props.shouldDim) { - this.undim(); +const OpacityView = (props) => { + const opacity = useSharedValue(1); + const opacityStyle = useAnimatedStyle(() => ({ + opacity: opacity.value, + })); + + React.useEffect(() => { + if (props.shouldDim) { + opacity.value = withTiming(props.dimmingValue, {duration: 50}); + } else { + opacity.value = 1; } - } - - undim() { - Animated.timing(this.opacity, { - toValue: 1, - duration: 50, - useNativeDriver: true, - }).start(({finished}) => { - // If animation doesn't finish because Animation.stop was called - // (e.g. because it was interrupted by a gesture or another animation), - // restart animation so we always make sure the component gets completely shown. - if (finished) { - return; - } - this.undim(); - }); - } - - render() { - return ( - - {this.props.children} - - ); - } -} + }, [props.shouldDim, props.dimmingValue, opacity]); + + return ( + + {props.children} + + ); +}; +OpacityView.displayName = 'OpacityView'; OpacityView.propTypes = propTypes; OpacityView.defaultProps = defaultProps; export default OpacityView; From 46241f1c6de498822a2af37faa7e0546bea5f48b Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 17 Apr 2023 20:55:07 +0200 Subject: [PATCH 09/47] =?UTF-8?q?fix=20GenericPressable=20implementation?= =?UTF-8?q?=20=E2=80=93=20passing=20disabled=20prop=20to=20Pressable=20com?= =?UTF-8?q?ponent=20prohibited=20cursor=20change?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GenericPressable/GenericPressable.js | 50 ++++++++----------- .../Pressable/GenericPressable/index.js | 16 ++++-- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js index f6d413e79109..6e95a0cb4687 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo} from 'react'; +import React, {useCallback, useEffect, useMemo, forwardRef} from 'react'; import {Pressable} from 'react-native'; import _ from 'lodash'; import Accessibility from '../../../libs/Accessibility'; @@ -19,7 +19,7 @@ const getCursorStyle = (isDisabled, isText) => { return styles.cursorPointer; }; -const GenericPressable = (props) => { +const GenericPressable = forwardRef((props, ref) => { // eslint-disable-next-line react/destructuring-assignment const { children, @@ -27,14 +27,12 @@ const GenericPressable = (props) => { onLongPress, onKeyPress, disabled, - nativeID, style, accessibilityHint, shouldUseHapticsOnLongPress, shouldUseHapticsOnPress, nextFocusRef, keyboardShortcut, - forwardedRef, shouldUseAutoHitSlop, enableInScreenReaderStates, ...rest @@ -42,11 +40,12 @@ const GenericPressable = (props) => { const isScreenReaderActive = Accessibility.useScreenReaderStatus(); const [hitslop, onLayout] = Accessibility.useAutoHitSlop(); + const isDisabled = useMemo(() => { let shouldBeDisabledByScreenReader = false; switch (enableInScreenReaderStates) { case 'all': - shouldBeDisabledByScreenReader = true; + shouldBeDisabledByScreenReader = false; break; case 'disabled': shouldBeDisabledByScreenReader = !isScreenReaderActive; @@ -61,27 +60,23 @@ const GenericPressable = (props) => { }, [isScreenReaderActive, enableInScreenReaderStates, props.disabled]); const onLongPressHandler = useCallback(() => { - if (isDisabled) { + if (!onLongPress) { return; } if (shouldUseHapticsOnLongPress) { HapticFeedback.longPress(); } - const pressFunction = onLongPress || onPress; - pressFunction(); + onLongPress(); Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnLongPress, onLongPress, onPress, nextFocusRef, isDisabled]); + }, [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef]); const onPressHandler = useCallback(() => { - if (isDisabled) { - return; - } if (shouldUseHapticsOnPress) { HapticFeedback.press(); } onPress(); Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnPress, onPress, nextFocusRef, isDisabled]); + }, [shouldUseHapticsOnPress, onPress, nextFocusRef]); const onKeyPressHandler = useCallback((event) => { if (event.key !== 'Enter') { @@ -108,16 +103,14 @@ const GenericPressable = (props) => { [ - getCursorStyle(props.disabled, props.accessibilityRole === 'text'), + getCursorStyle(isDisabled, [props.accessibilityRole, props.role].includes('text')), props.style, isScreenReaderActive && parseStyleFromFunction(props.screenReaderActiveStyle, state), state.focused && parseStyleFromFunction(props.focusStyle, state), @@ -129,13 +122,17 @@ const GenericPressable = (props) => { // accessibility props accessibilityHint={props.accessibilityHint || props.accessibilityLabel} accessibilityState={{ - disabled: props.disabled, + disabled: isDisabled, ...props.accessibilityState, }} // ios-only form of inputs - onMagicTap={onPressHandler} - onAccessibilityTap={onPressHandler} + onMagicTap={!isDisabled && onPressHandler} + onAccessibilityTap={!isDisabled && onPressHandler} + + //react-native-web exclusive props + aria-disabled={isDisabled} + aria-keyshortcuts={keyboardShortcut && `${keyboardShortcut.modifiers}+${keyboardShortcut.shortcutKey}`} // eslint-disable-next-line react/jsx-props-no-spreading {...rest} @@ -143,13 +140,10 @@ const GenericPressable = (props) => { {state => (_.isFunction(props.children) ? props.children({...state, isScreenReaderActive, isDisabled}) : props.children) } ); -}; +}); GenericPressable.displayName = 'GenericPressable'; GenericPressable.propTypes = genericPressablePropTypes.propTypes; GenericPressable.defaultProps = genericPressablePropTypes.defaultProps; -export default React.forwardRef((props, ref) => ( - // eslint-disable-next-line react/jsx-props-no-spreading - -)); +export default GenericPressable; diff --git a/src/components/Pressable/GenericPressable/index.js b/src/components/Pressable/GenericPressable/index.js index 83410387d64b..1be711d94c9d 100644 --- a/src/components/Pressable/GenericPressable/index.js +++ b/src/components/Pressable/GenericPressable/index.js @@ -1,8 +1,9 @@ -import _ from 'underscore'; +import React, {forwardRef} from 'react'; +import _ from 'lodash'; import GenericPressable from './GenericPressable'; import GenericPressablePropTypes from './PropTypes'; -const WebGenericPressable = (props) => { +const WebGenericPressable = forwardRef((props, ref) => { // change all props with accessibility* to aria-* const accessibilityMappedProps = _.mapKeys(props, (value, key) => { if (key === 'nativeID') { @@ -19,9 +20,14 @@ const WebGenericPressable = (props) => { if (!props.accessible || !props.focusable) { accessibilityMappedProps.tabIndex = -1; } - // eslint-disable-next-line react/jsx-props-no-spreading - return ; -}; + + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + {props.children} + + ); +}); WebGenericPressable.propTypes = GenericPressablePropTypes.propTypes; WebGenericPressable.defaultProps = GenericPressablePropTypes.defaultProps; From d270158319dd983efead7c6423ea7fc38fcc8d65 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 17 Apr 2023 20:56:22 +0200 Subject: [PATCH 10/47] correctly set up hitslop for useAutoHitSlop --- src/libs/Accessibility/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Accessibility/index.js b/src/libs/Accessibility/index.js index cf15721f1d9c..74e2ab2f93e7 100644 --- a/src/libs/Accessibility/index.js +++ b/src/libs/Accessibility/index.js @@ -35,7 +35,7 @@ const useAutoHitSlop = () => { const onLayout = useCallback((event) => { const {layout} = event.nativeEvent; if (layout.width !== frameSize.x && layout.height !== frameSize.y) { - setFrameSize({frameSize: {x: layout.width, y: layout.height}}); + setFrameSize({x: layout.width, y: layout.height}); } }, [frameSize]); return [getHitSlopForSize(frameSize), onLayout]; From ee2e3b08c47956252effa35467035d23b9a4a2f8 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 17 Apr 2023 20:57:06 +0200 Subject: [PATCH 11/47] set up PressableWithFeedback component --- .../Pressable/PressableWithFeedback.js | 50 ++++++++++++++++++- src/styles/variables.js | 2 + 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index 5306c0abd9b2..372dc59a65df 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -1,3 +1,51 @@ +import React, {forwardRef} from 'react'; +import _ from 'lodash'; +import propTypes from 'prop-types'; import GenericPressable from './GenericPressable'; +import GenericPressablePropTypes from './GenericPressable/PropTypes'; +import OpacityView from '../OpacityView'; +import variables from '../../styles/variables'; -export default GenericPressable; \ No newline at end of file +const omitedProps = ['style', 'pressStyle', 'hoverStyle', 'focusStyle', 'wrapperStyle']; + +const PressableWithFeedbackPropTypes = { + ..._.omit(GenericPressablePropTypes.propTypes, omitedProps), + pressDimmingValue: propTypes.number, + hoverDimmingValue: propTypes.number, +}; + +const PressableWithFeedbackDefaultProps = { + ..._.omit(GenericPressablePropTypes.defaultProps, omitedProps), + pressDimmingValue: variables.pressDimValue, + hoverDimmingValue: variables.hoverDimValue, + wrapperStyle: [], +}; + +const PressableWithFeedback = forwardRef((props, ref) => { + const propsWithoutStyling = _.omit(props, omitedProps); + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + {state => ( + + {props.children} + + )} + + ); +}); + +PressableWithFeedback.displayName = 'PressableWithFeedback'; +PressableWithFeedback.propTypes = PressableWithFeedbackPropTypes; +PressableWithFeedback.defaultProps = PressableWithFeedbackDefaultProps; + +export default PressableWithFeedback; diff --git a/src/styles/variables.js b/src/styles/variables.js index cd0f593f176c..1242219db7c7 100644 --- a/src/styles/variables.js +++ b/src/styles/variables.js @@ -131,4 +131,6 @@ export default { // The height of the empty list is 14px (2px for borders and 12px for vertical padding) // This is calculated based on the values specified in the 'getGoogleListViewStyle' function of the 'StyleUtils' utility googleEmptyListViewHeight: 14, + hoverDimValue: 0.5, + pressDimValue: 0.2, }; From fd21d4e6bca4a3e69c543bb2a097a62878ad3c5e Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 17 Apr 2023 20:57:28 +0200 Subject: [PATCH 12/47] set up PressableWithoutFeedback component --- .../Pressable/PressableWithoutFeedback.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/components/Pressable/PressableWithoutFeedback.js b/src/components/Pressable/PressableWithoutFeedback.js index e69de29bb2d1..dea4c8461cd4 100644 --- a/src/components/Pressable/PressableWithoutFeedback.js +++ b/src/components/Pressable/PressableWithoutFeedback.js @@ -0,0 +1,26 @@ +import _ from 'lodash'; +import GenericPressable from './GenericPressable'; +import GenericPressableProps from './GenericPressable/PropTypes'; + +const omitedProps = [ + 'pressStyle', + 'hoverStyle', + 'focusStyle', + 'activeStyle', + 'disabledStyle', + 'screenReaderActiveStyle', + 'shouldUseHapticsOnPress', + 'shouldUseHapticsOnLongPress', +]; + +const PressableWithoutFeedback = (props) => { + const propsWithoutStyling = _.omit(props, omitedProps); + // eslint-disable-next-line react/jsx-props-no-spreading + return ; +}; + +PressableWithoutFeedback.displayName = 'PressableWithoutFeedback'; +PressableWithoutFeedback.propTypes = _.omit(GenericPressableProps.propTypes, omitedProps); +PressableWithoutFeedback.defaultProps = _.omit(GenericPressableProps.defaultProps, omitedProps); + +export default PressableWithoutFeedback; From 3dbea099bdaa165e59a0379ef8ffebed6edd44f4 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 18 Apr 2023 22:40:19 +0200 Subject: [PATCH 13/47] comment GenericPressable propTypes --- .../Pressable/GenericPressable/PropTypes.js | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/components/Pressable/GenericPressable/PropTypes.js b/src/components/Pressable/GenericPressable/PropTypes.js index 152092b34672..9873dfc98aae 100644 --- a/src/components/Pressable/GenericPressable/PropTypes.js +++ b/src/components/Pressable/GenericPressable/PropTypes.js @@ -9,22 +9,94 @@ const stylePropTypeWithFunction = PropTypes.oneOfType([ const propTypes = { ...PressableProps, + + /** + * onPress callback + */ onPress: PropTypes.func.isRequired, + + /** + * Specifies keyboard shortcut to trigger onPressHandler + * @example {shortcutKey: 'a', modifiers: ['ctrl', 'shift'], descriptionKey: 'keyboardShortcut.description'} + */ keyboardShortcut: PropTypes.shape({ descriptionKey: PropTypes.string.isRequired, shortcutKey: PropTypes.string.isRequired, modifiers: PropTypes.arrayOf(PropTypes.string), }), + + /** + * Specifies if haptic feedback should be used on press + * @default false + */ shouldUseHapticsOnPress: PropTypes.bool, + + /** + * Specifies if haptic feedback should be used on long press + * @default false + */ shouldUseHapticsOnLongPress: PropTypes.bool, + + /** + * style for when the component is disabled. Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) + * @default {} + * @example {backgroundColor: 'red'} + * @example state => ({backgroundColor: state.active ? 'red' : 'blue'}) + */ disabledStyle: stylePropTypeWithFunction, + + /** + * style for when the component is hovered. Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) + * @default {} + * @example {backgroundColor: 'red'} + * @example state => ({backgroundColor: state.active ? 'red' : 'blue'}) + */ hoverStyle: stylePropTypeWithFunction, + + /** + * style for when the component is focused. Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) + * @default {} + * @example {backgroundColor: 'red'} + * @example state => ({backgroundColor: state.active ? 'red' : 'blue'}) + */ focusStyle: stylePropTypeWithFunction, + + /** + * style for when the component is pressed. Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) + * @default {} + * @example {backgroundColor: 'red'} + * @example state => ({backgroundColor: state.active ? 'red' : 'blue'}) + */ pressedStyle: stylePropTypeWithFunction, + + /** + * style for when the component is active and the screen reader is on. + * Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) + * @default {} + * @example {backgroundColor: 'red'} + * @example state => ({backgroundColor: state.active ? 'red' : 'blue'}) + */ screenReaderActiveStyle: stylePropTypeWithFunction, + + /** + * Specifies if the component should be accessible when the screen reader is on + * @default 'all' + * @example 'all' - the component is accessible regardless of screen reader state + * @example 'active' - the component is accessible only when the screen reader is on + * @example 'disabled' - the component is not accessible when the screen reader is on + */ enableInScreenReaderStates: PropTypes.oneOf(['all', 'active', 'disabled']), + + /** + * Specifies which component should be focused after interacting with this component + */ nextFocusRef: PropTypes.func, accessibilityLabel: PropTypes.string.isRequired, + + /** + * Specifies if the component should calculate its hitSlop automatically + * @default true + */ shouldUseAutoHitSlop: PropTypes.bool, }; From 784889b25a35228327426fb76cd12d4e40e418b6 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 18 Apr 2023 22:41:10 +0200 Subject: [PATCH 14/47] fix unsubscribing screen reader listener --- src/libs/Accessibility/index.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libs/Accessibility/index.js b/src/libs/Accessibility/index.js index 74e2ab2f93e7..cd5c32f06b88 100644 --- a/src/libs/Accessibility/index.js +++ b/src/libs/Accessibility/index.js @@ -6,12 +6,15 @@ import moveAccessibilityFocus from './moveAccessibilityFocus'; const useScreenReaderStatus = () => { const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false); useEffect(() => { - const unsubscribe = AccessibilityInfo.addEventListener('screenReaderChanged', (isEnabled) => { + const subscription = AccessibilityInfo.addEventListener('screenReaderChanged', (isEnabled) => { setIsScreenReaderEnabled(isEnabled); }); return () => { - unsubscribe(); + if (!subscription) { + return; + } + subscription.remove(); }; }, []); From 283092313891b7258d49cbb572f9af46188aad38 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 18 Apr 2023 22:41:29 +0200 Subject: [PATCH 15/47] update GenericPresssable implementation --- .../GenericPressable/GenericPressable.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js index 6e95a0cb4687..1e33d5c5a935 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -13,9 +13,11 @@ const getCursorStyle = (isDisabled, isText) => { if (isDisabled) { return styles.cursorDisabled; } + if (isText) { return styles.cursorText; } + return styles.cursorPointer; }; @@ -104,8 +106,6 @@ const GenericPressable = forwardRef((props, ref) => { hitSlop={shouldUseAutoHitSlop && hitslop} onLayout={onLayout} ref={ref} - focusable - accessible onPress={!isDisabled && onPressHandler} onLongPress={!isDisabled && onLongPressHandler} onKeyPress={!isDisabled && onKeyPressHandler} @@ -116,24 +116,25 @@ const GenericPressable = forwardRef((props, ref) => { state.focused && parseStyleFromFunction(props.focusStyle, state), state.hovered && parseStyleFromFunction(props.hoverStyle, state), state.pressed && parseStyleFromFunction(props.pressedStyle, state), - isDisabled && [parseStyleFromFunction(props.disabledStyle, state), styles.cursorDisabled, styles.noSelect], + isDisabled && [parseStyleFromFunction(props.disabledStyle, state), styles.noSelect], ]} // accessibility props + focusable + accessible + tabIndex={0} accessibilityHint={props.accessibilityHint || props.accessibilityLabel} accessibilityState={{ disabled: isDisabled, ...props.accessibilityState, }} + aria-disabled={isDisabled} + aria-keyshortcuts={keyboardShortcut && `${keyboardShortcut.modifiers}+${keyboardShortcut.shortcutKey}`} // ios-only form of inputs onMagicTap={!isDisabled && onPressHandler} onAccessibilityTap={!isDisabled && onPressHandler} - //react-native-web exclusive props - aria-disabled={isDisabled} - aria-keyshortcuts={keyboardShortcut && `${keyboardShortcut.modifiers}+${keyboardShortcut.shortcutKey}`} - // eslint-disable-next-line react/jsx-props-no-spreading {...rest} > From 038960a97b63df05501d1952c63fffe7e07e10c8 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 19 Apr 2023 14:59:11 +0200 Subject: [PATCH 16/47] undim opacityView with timing --- src/components/OpacityView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/OpacityView.js b/src/components/OpacityView.js index 75216a6f426c..2298a490812b 100644 --- a/src/components/OpacityView.js +++ b/src/components/OpacityView.js @@ -33,7 +33,7 @@ const OpacityView = (props) => { if (props.shouldDim) { opacity.value = withTiming(props.dimmingValue, {duration: 50}); } else { - opacity.value = 1; + opacity.value = withTiming(1, {duration: 50}); } }, [props.shouldDim, props.dimmingValue, opacity]); From 0d81a560d7ca476d4b345c12e1730061447ae8f8 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 19 Apr 2023 15:01:20 +0200 Subject: [PATCH 17/47] update comments on OpacityView propTypes --- src/components/OpacityView.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/components/OpacityView.js b/src/components/OpacityView.js index 2298a490812b..ff3a14df61a3 100644 --- a/src/components/OpacityView.js +++ b/src/components/OpacityView.js @@ -4,17 +4,27 @@ import PropTypes from 'prop-types'; import * as StyleUtils from '../styles/StyleUtils'; const propTypes = { - // Should we dim the view + /** + * Should we dim the view + */ shouldDim: PropTypes.bool.isRequired, - // Content to render + /** + * Content to render + */ children: PropTypes.node.isRequired, - // Array of style objects + /** + * Array of style objects + * @default [] + */ // eslint-disable-next-line react/forbid-prop-types style: PropTypes.arrayOf(PropTypes.object), - // The value to use for the opacity when the view is dimmed + /** + * The value to use for the opacity when the view is dimmed + * @default 0.5 + */ dimmingValue: PropTypes.number, }; From 7b1fe9adc33f2fb21d7c216e4755ba389b771084 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 19 Apr 2023 15:09:53 +0200 Subject: [PATCH 18/47] simplify useScreenReaderStatus useEffect hook --- src/libs/Accessibility/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/libs/Accessibility/index.js b/src/libs/Accessibility/index.js index cd5c32f06b88..e27da2dffe99 100644 --- a/src/libs/Accessibility/index.js +++ b/src/libs/Accessibility/index.js @@ -6,9 +6,7 @@ import moveAccessibilityFocus from './moveAccessibilityFocus'; const useScreenReaderStatus = () => { const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false); useEffect(() => { - const subscription = AccessibilityInfo.addEventListener('screenReaderChanged', (isEnabled) => { - setIsScreenReaderEnabled(isEnabled); - }); + const subscription = AccessibilityInfo.addEventListener('screenReaderChanged', setIsScreenReaderEnabled); return () => { if (!subscription) { From d5204e998fa7510ac216b44e6b89167c86157504 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 19 Apr 2023 15:14:01 +0200 Subject: [PATCH 19/47] extract possible screen reader states to const file --- src/CONST.js | 5 +++++ .../Pressable/GenericPressable/GenericPressable.js | 7 ++++--- src/components/Pressable/GenericPressable/PropTypes.js | 5 +++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index 568aa09ebe6c..f4f482c2f763 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -2282,6 +2282,11 @@ const CONST = { 'PLN', 'GBP', 'RUB', 'SGD', 'SEK', 'CHF', 'THB', 'USD', ], CONCIERGE_TRAVEL_URL: 'https://community.expensify.com/discussion/7066/introducing-concierge-travel', + SCREEN_READER_STATES: { + ALL: 'all', + ACTIVE: 'active', + DISABLED: 'disabled', + } }; export default CONST; diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js index 1e33d5c5a935..9f98b48954e3 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -6,6 +6,7 @@ import HapticFeedback from '../../../libs/HapticFeedback'; import KeyboardShortcut from '../../../libs/KeyboardShortcut'; import styles from '../../../styles/styles'; import genericPressablePropTypes from './PropTypes'; +import CONST from '../../../CONST'; const parseStyleFromFunction = (style, state) => (_.isFunction(style) ? style(state) : style); @@ -46,13 +47,13 @@ const GenericPressable = forwardRef((props, ref) => { const isDisabled = useMemo(() => { let shouldBeDisabledByScreenReader = false; switch (enableInScreenReaderStates) { - case 'all': + case CONST.SCREEN_READER_STATES.ALL: shouldBeDisabledByScreenReader = false; break; - case 'disabled': + case CONST.SCREEN_READER_STATES.DISABLED: shouldBeDisabledByScreenReader = !isScreenReaderActive; break; - case 'active': + case CONST.SCREEN_READER_STATES.ACTIVE: shouldBeDisabledByScreenReader = isScreenReaderActive; break; default: diff --git a/src/components/Pressable/GenericPressable/PropTypes.js b/src/components/Pressable/GenericPressable/PropTypes.js index 9873dfc98aae..32c547a88a5d 100644 --- a/src/components/Pressable/GenericPressable/PropTypes.js +++ b/src/components/Pressable/GenericPressable/PropTypes.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import {PressableProps} from 'react-native'; import * as StyleUtils from '../../../styles/StyleUtils'; +import CONST from '../../../CONST'; const stylePropTypeWithFunction = PropTypes.oneOfType([ StyleUtils.stylePropType, @@ -85,7 +86,7 @@ const propTypes = { * @example 'active' - the component is accessible only when the screen reader is on * @example 'disabled' - the component is not accessible when the screen reader is on */ - enableInScreenReaderStates: PropTypes.oneOf(['all', 'active', 'disabled']), + enableInScreenReaderStates: PropTypes.oneOf([CONST.SCREEN_READER_STATES.ALL, CONST.SCREEN_READER_STATES.ACTIVE, CONST.SCREEN_READER_STATES.DISABLED]), /** * Specifies which component should be focused after interacting with this component @@ -109,7 +110,7 @@ const defaultProps = { focusStyle: {}, pressedStyle: {}, screenReaderActiveStyle: {}, - enableInScreenReaderStates: 'all', + enableInScreenReaderStates: CONST.SCREEN_READER_STATES.ALL, nextFocusRef: undefined, shouldUseAutoHitSlop: true, }; From 049151542859344c918976c73530db6f5e45e7ff Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 19 Apr 2023 15:41:29 +0200 Subject: [PATCH 20/47] fix typo --- src/libs/Accessibility/moveAccessibilityFocus/index.native.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Accessibility/moveAccessibilityFocus/index.native.js b/src/libs/Accessibility/moveAccessibilityFocus/index.native.js index 73225b48e7e5..91605e06243d 100644 --- a/src/libs/Accessibility/moveAccessibilityFocus/index.native.js +++ b/src/libs/Accessibility/moveAccessibilityFocus/index.native.js @@ -4,7 +4,7 @@ const moveAccessibilityFocus = (ref) => { if (!ref) { return; } - AccessibilityInfo.sendAccssibilityEvent(ref, 'focus'); + AccessibilityInfo.sendAccessibilityEvent(ref, 'focus'); }; export default moveAccessibilityFocus; From 83718b70d2f41b56f5b96cb6f4e2ae7c5fded518 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 19 Apr 2023 16:07:05 +0200 Subject: [PATCH 21/47] simplify switch statment to two if's --- .../GenericPressable/GenericPressable.js | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js index 9f98b48954e3..142f3a811a27 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -46,19 +46,14 @@ const GenericPressable = forwardRef((props, ref) => { const isDisabled = useMemo(() => { let shouldBeDisabledByScreenReader = false; - switch (enableInScreenReaderStates) { - case CONST.SCREEN_READER_STATES.ALL: - shouldBeDisabledByScreenReader = false; - break; - case CONST.SCREEN_READER_STATES.DISABLED: - shouldBeDisabledByScreenReader = !isScreenReaderActive; - break; - case CONST.SCREEN_READER_STATES.ACTIVE: - shouldBeDisabledByScreenReader = isScreenReaderActive; - break; - default: - break; + if (enableInScreenReaderStates === CONST.SCREEN_READER_STATES.ACTIVE) { + shouldBeDisabledByScreenReader = !isScreenReaderActive; } + + if (enableInScreenReaderStates === CONST.SCREEN_READER_STATES.DISABLED) { + shouldBeDisabledByScreenReader = isScreenReaderActive; + } + return props.disabled || shouldBeDisabledByScreenReader; }, [isScreenReaderActive, enableInScreenReaderStates, props.disabled]); From 6cc7fd7b58235b78d8b7e5c3298a5edf4ff2073e Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 19 Apr 2023 16:13:53 +0200 Subject: [PATCH 22/47] hoist pareStyleFromFunction to StyleUtils file --- .../GenericPressable/GenericPressable.js | 23 +++++++------------ src/styles/StyleUtils.js | 12 ++++++++++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js index 142f3a811a27..b5ddac35a7f7 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -7,8 +7,7 @@ import KeyboardShortcut from '../../../libs/KeyboardShortcut'; import styles from '../../../styles/styles'; import genericPressablePropTypes from './PropTypes'; import CONST from '../../../CONST'; - -const parseStyleFromFunction = (style, state) => (_.isFunction(style) ? style(state) : style); +import * as StyleUtils from '../../../styles/StyleUtils'; const getCursorStyle = (isDisabled, isText) => { if (isDisabled) { @@ -85,16 +84,10 @@ const GenericPressable = forwardRef((props, ref) => { useEffect(() => { if (!keyboardShortcut) { - return; + return () => {}; } const {shortcutKey, descriptionKey, modifiers} = keyboardShortcut; - const unsubscribe = KeyboardShortcut.subscribe(shortcutKey, onPressHandler, descriptionKey, modifiers, true, false, 0, false); - return () => { - if (!unsubscribe) { - return; - } - unsubscribe(); - }; + return KeyboardShortcut.subscribe(shortcutKey, onPressHandler, descriptionKey, modifiers, true, false, 0, false); }, [keyboardShortcut, onPressHandler]); return ( @@ -108,11 +101,11 @@ const GenericPressable = forwardRef((props, ref) => { style={state => [ getCursorStyle(isDisabled, [props.accessibilityRole, props.role].includes('text')), props.style, - isScreenReaderActive && parseStyleFromFunction(props.screenReaderActiveStyle, state), - state.focused && parseStyleFromFunction(props.focusStyle, state), - state.hovered && parseStyleFromFunction(props.hoverStyle, state), - state.pressed && parseStyleFromFunction(props.pressedStyle, state), - isDisabled && [parseStyleFromFunction(props.disabledStyle, state), styles.noSelect], + isScreenReaderActive && StyleUtils.parseStyleFromFunction(props.screenReaderActiveStyle, state), + state.focused && StyleUtils.parseStyleFromFunction(props.focusStyle, state), + state.hovered && StyleUtils.parseStyleFromFunction(props.hoverStyle, state), + state.pressed && StyleUtils.parseStyleFromFunction(props.pressedStyle, state), + isDisabled && [...StyleUtils.parseStyleFromFunction(props.disabledStyle, state), styles.noSelect], ]} // accessibility props diff --git a/src/styles/StyleUtils.js b/src/styles/StyleUtils.js index 46d1c19c3e04..64aedbaa98d2 100644 --- a/src/styles/StyleUtils.js +++ b/src/styles/StyleUtils.js @@ -651,6 +651,17 @@ function parseStyleAsArray(styleParam) { return _.isArray(styleParam) ? styleParam : [styleParam]; } +/** + * Parse style function and return Styles object + * @param {Object|Object[]|Function} style + * @param {Object} state + * @returns {Object[]} + */ +function parseStyleFromFunction(style, state) { + const functionAppliedStyle = _.isFunction(style) ? style(state) : style; + return parseStyleAsArray(functionAppliedStyle); +} + /** * Receives any number of object or array style objects and returns them all as an array * @param {Object|Object[]} allStyles @@ -1036,6 +1047,7 @@ export { getPaymentMethodMenuWidth, getThemeBackgroundColor, parseStyleAsArray, + parseStyleFromFunction, combineStyles, getPaddingLeft, convertToLTR, From 19ebe883d0f276c5345a1f763d1f1c8b9f7372f7 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 19 Apr 2023 16:15:04 +0200 Subject: [PATCH 23/47] add JSDoc for getCursorStyle function --- .../Pressable/GenericPressable/GenericPressable.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/GenericPressable.js index b5ddac35a7f7..b04622ce1703 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/GenericPressable.js @@ -9,6 +9,12 @@ import genericPressablePropTypes from './PropTypes'; import CONST from '../../../CONST'; import * as StyleUtils from '../../../styles/StyleUtils'; +/** + * Returns the cursor style based on the state of Pressable + * @param {Boolean} isDisabled + * @param {Boolean} isText + * @returns {Object} + */ const getCursorStyle = (isDisabled, isText) => { if (isDisabled) { return styles.cursorDisabled; From bd9bbbb83b84bfdc5897aaae8973146f69ebff45 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 20 Apr 2023 20:50:58 +0200 Subject: [PATCH 24/47] rename GenericPressable to BaseGenericPressable --- .../{GenericPressable.js => BaseGenericPressable.js} | 7 ++++++- src/components/Pressable/GenericPressable/index.js | 2 +- src/components/Pressable/GenericPressable/index.native.js | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) rename src/components/Pressable/GenericPressable/{GenericPressable.js => BaseGenericPressable.js} (98%) diff --git a/src/components/Pressable/GenericPressable/GenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js similarity index 98% rename from src/components/Pressable/GenericPressable/GenericPressable.js rename to src/components/Pressable/GenericPressable/BaseGenericPressable.js index b04622ce1703..df20a545fcc4 100644 --- a/src/components/Pressable/GenericPressable/GenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -1,4 +1,9 @@ -import React, {useCallback, useEffect, useMemo, forwardRef} from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + forwardRef, +} from 'react'; import {Pressable} from 'react-native'; import _ from 'lodash'; import Accessibility from '../../../libs/Accessibility'; diff --git a/src/components/Pressable/GenericPressable/index.js b/src/components/Pressable/GenericPressable/index.js index 1be711d94c9d..29b834136f9c 100644 --- a/src/components/Pressable/GenericPressable/index.js +++ b/src/components/Pressable/GenericPressable/index.js @@ -1,6 +1,6 @@ import React, {forwardRef} from 'react'; import _ from 'lodash'; -import GenericPressable from './GenericPressable'; +import GenericPressable from './BaseGenericPressable'; import GenericPressablePropTypes from './PropTypes'; const WebGenericPressable = forwardRef((props, ref) => { diff --git a/src/components/Pressable/GenericPressable/index.native.js b/src/components/Pressable/GenericPressable/index.native.js index cba6984fd96d..5d4b5d7639b8 100644 --- a/src/components/Pressable/GenericPressable/index.native.js +++ b/src/components/Pressable/GenericPressable/index.native.js @@ -1,4 +1,4 @@ -import GenericPressable from './GenericPressable'; +import GenericPressable from './BaseGenericPressable'; export default GenericPressable; From 92c826ed73c8a79525b5a6961c3a7d2c1310d4fb Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 24 Apr 2023 14:19:01 +0200 Subject: [PATCH 25/47] split accessibility props to native and web implementations --- src/CONST.js | 2 +- .../GenericPressable/BaseGenericPressable.js | 4 -- .../Pressable/GenericPressable/index.js | 39 +++++++------------ .../GenericPressable/index.native.js | 17 +++++++- 4 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/CONST.js b/src/CONST.js index f4f482c2f763..d8b0c3895ec6 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -2286,7 +2286,7 @@ const CONST = { ALL: 'all', ACTIVE: 'active', DISABLED: 'disabled', - } + }, }; export default CONST; diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index df20a545fcc4..ae215f61e55a 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -120,10 +120,6 @@ const GenericPressable = forwardRef((props, ref) => { ]} // accessibility props - focusable - accessible - tabIndex={0} - accessibilityHint={props.accessibilityHint || props.accessibilityLabel} accessibilityState={{ disabled: isDisabled, ...props.accessibilityState, diff --git a/src/components/Pressable/GenericPressable/index.js b/src/components/Pressable/GenericPressable/index.js index 29b834136f9c..24080c195970 100644 --- a/src/components/Pressable/GenericPressable/index.js +++ b/src/components/Pressable/GenericPressable/index.js @@ -1,33 +1,22 @@ import React, {forwardRef} from 'react'; -import _ from 'lodash'; import GenericPressable from './BaseGenericPressable'; import GenericPressablePropTypes from './PropTypes'; -const WebGenericPressable = forwardRef((props, ref) => { - // change all props with accessibility* to aria-* - const accessibilityMappedProps = _.mapKeys(props, (value, key) => { - if (key === 'nativeID') { - return 'id'; - } - if (key === 'accessibilityRole') { - return 'role'; - } - if (_.startsWith(key, 'accessibility')) { - return `aria-${key.slice(13).toLowerCase()}`; - } - return key; - }); - if (!props.accessible || !props.focusable) { - accessibilityMappedProps.tabIndex = -1; - } - - return ( +const WebGenericPressable = forwardRef((props, ref) => ( + - {props.children} - - ); -}); + {...props} + ref={ref} + + // change all props with accessibility* to aria-* + tabIndex={(!props.accessible || !props.focusable) ? -1 : 0} + role={props.accessibilityRole} + id={props.nativeID} + aria-label={props.accessibilityLabel} + aria-labelledby={props.accessibilityLabelledBy} + aria-valuenow={props.accessibilityValue} + /> +)); WebGenericPressable.propTypes = GenericPressablePropTypes.propTypes; WebGenericPressable.defaultProps = GenericPressablePropTypes.defaultProps; diff --git a/src/components/Pressable/GenericPressable/index.native.js b/src/components/Pressable/GenericPressable/index.native.js index 5d4b5d7639b8..f18e02ce4dc0 100644 --- a/src/components/Pressable/GenericPressable/index.native.js +++ b/src/components/Pressable/GenericPressable/index.native.js @@ -1,4 +1,19 @@ +import React, {forwardRef} from 'react'; import GenericPressable from './BaseGenericPressable'; -export default GenericPressable; +const NativeGenericPressable = forwardRef((props, ref) => ( + +)); + +NativeGenericPressable.propTypes = GenericPressable.propTypes; +NativeGenericPressable.defaultProps = GenericPressable.defaultProps; + +export default NativeGenericPressable; From f95fc73243161a6736d72d14867fb733cc65cf12 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 24 Apr 2023 15:02:38 +0200 Subject: [PATCH 26/47] rename proptypes object passed to different components --- .../Pressable/GenericPressable/BaseGenericPressable.js | 2 +- src/components/Pressable/GenericPressable/PropTypes.js | 4 ++-- src/components/Pressable/GenericPressable/index.js | 2 +- src/components/Pressable/GenericPressable/index.native.js | 5 +++-- src/components/Pressable/PressableWithFeedback.js | 2 +- src/components/Pressable/PressableWithoutFeedback.js | 2 +- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index ae215f61e55a..6ebbd398be3b 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -140,7 +140,7 @@ const GenericPressable = forwardRef((props, ref) => { }); GenericPressable.displayName = 'GenericPressable'; -GenericPressable.propTypes = genericPressablePropTypes.propTypes; +GenericPressable.propTypes = genericPressablePropTypes.pressablePropTypes; GenericPressable.defaultProps = genericPressablePropTypes.defaultProps; export default GenericPressable; diff --git a/src/components/Pressable/GenericPressable/PropTypes.js b/src/components/Pressable/GenericPressable/PropTypes.js index 32c547a88a5d..79d280abc1f7 100644 --- a/src/components/Pressable/GenericPressable/PropTypes.js +++ b/src/components/Pressable/GenericPressable/PropTypes.js @@ -8,7 +8,7 @@ const stylePropTypeWithFunction = PropTypes.oneOfType([ PropTypes.func, ]); -const propTypes = { +const pressablePropTypes = { ...PressableProps, /** @@ -116,6 +116,6 @@ const defaultProps = { }; export default { - propTypes, + pressablePropTypes, defaultProps, }; diff --git a/src/components/Pressable/GenericPressable/index.js b/src/components/Pressable/GenericPressable/index.js index 24080c195970..e50cece9fd02 100644 --- a/src/components/Pressable/GenericPressable/index.js +++ b/src/components/Pressable/GenericPressable/index.js @@ -18,7 +18,7 @@ const WebGenericPressable = forwardRef((props, ref) => ( /> )); -WebGenericPressable.propTypes = GenericPressablePropTypes.propTypes; +WebGenericPressable.propTypes = GenericPressablePropTypes.pressablePropTypes; WebGenericPressable.defaultProps = GenericPressablePropTypes.defaultProps; export default WebGenericPressable; diff --git a/src/components/Pressable/GenericPressable/index.native.js b/src/components/Pressable/GenericPressable/index.native.js index f18e02ce4dc0..02f06dad8a02 100644 --- a/src/components/Pressable/GenericPressable/index.native.js +++ b/src/components/Pressable/GenericPressable/index.native.js @@ -1,5 +1,6 @@ import React, {forwardRef} from 'react'; import GenericPressable from './BaseGenericPressable'; +import GenericPressablePropTypes from './PropTypes'; const NativeGenericPressable = forwardRef((props, ref) => ( ( /> )); -NativeGenericPressable.propTypes = GenericPressable.propTypes; -NativeGenericPressable.defaultProps = GenericPressable.defaultProps; +NativeGenericPressable.propTypes = GenericPressablePropTypes.pressablePropTypes; +NativeGenericPressable.defaultProps = GenericPressablePropTypes.defaultProps; export default NativeGenericPressable; diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index 372dc59a65df..92f079189d63 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -9,7 +9,7 @@ import variables from '../../styles/variables'; const omitedProps = ['style', 'pressStyle', 'hoverStyle', 'focusStyle', 'wrapperStyle']; const PressableWithFeedbackPropTypes = { - ..._.omit(GenericPressablePropTypes.propTypes, omitedProps), + ..._.omit(GenericPressablePropTypes.pressablePropTypes, omitedProps), pressDimmingValue: propTypes.number, hoverDimmingValue: propTypes.number, }; diff --git a/src/components/Pressable/PressableWithoutFeedback.js b/src/components/Pressable/PressableWithoutFeedback.js index dea4c8461cd4..f0adb25543a1 100644 --- a/src/components/Pressable/PressableWithoutFeedback.js +++ b/src/components/Pressable/PressableWithoutFeedback.js @@ -20,7 +20,7 @@ const PressableWithoutFeedback = (props) => { }; PressableWithoutFeedback.displayName = 'PressableWithoutFeedback'; -PressableWithoutFeedback.propTypes = _.omit(GenericPressableProps.propTypes, omitedProps); +PressableWithoutFeedback.propTypes = _.omit(GenericPressableProps.pressablePropTypes, omitedProps); PressableWithoutFeedback.defaultProps = _.omit(GenericPressableProps.defaultProps, omitedProps); export default PressableWithoutFeedback; From 85b7d025dd5bb4a999b8a6939b481afa4ecf51e5 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 24 Apr 2023 15:02:56 +0200 Subject: [PATCH 27/47] ensure blurring after interaction --- .../GenericPressable/BaseGenericPressable.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 6ebbd398be3b..25795eda625b 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -75,16 +75,24 @@ const GenericPressable = forwardRef((props, ref) => { HapticFeedback.longPress(); } onLongPress(); + + if (ref.current) { + ref.current.blur(); + } + Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef]); + }, [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef, ref]); const onPressHandler = useCallback(() => { if (shouldUseHapticsOnPress) { HapticFeedback.press(); } onPress(); + if (ref.current) { + ref.current.blur(); + } Accessibility.moveAccessibilityFocus(nextFocusRef); - }, [shouldUseHapticsOnPress, onPress, nextFocusRef]); + }, [shouldUseHapticsOnPress, onPress, nextFocusRef, ref]); const onKeyPressHandler = useCallback((event) => { if (event.key !== 'Enter') { @@ -140,7 +148,7 @@ const GenericPressable = forwardRef((props, ref) => { }); GenericPressable.displayName = 'GenericPressable'; -GenericPressable.propTypes = genericPressablePropTypes.pressablePropTypes; +GenericPressable.propTypes = genericPressablePropTypes.propTypes; GenericPressable.defaultProps = genericPressablePropTypes.defaultProps; export default GenericPressable; From 7ef33d02bceae58971080df78761744342eb80b1 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 24 Apr 2023 18:12:50 +0200 Subject: [PATCH 28/47] fix wrong propTypes import --- .../Pressable/GenericPressable/BaseGenericPressable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 25795eda625b..2ac45361eab7 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -148,7 +148,7 @@ const GenericPressable = forwardRef((props, ref) => { }); GenericPressable.displayName = 'GenericPressable'; -GenericPressable.propTypes = genericPressablePropTypes.propTypes; +GenericPressable.propTypes = genericPressablePropTypes.pressablePropTypes; GenericPressable.defaultProps = genericPressablePropTypes.defaultProps; export default GenericPressable; From 90482d1d8a31eb0bed22c682901511569816c93f Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Mon, 24 Apr 2023 21:28:46 +0200 Subject: [PATCH 29/47] patch propTypes related warnings --- src/components/Pressable/GenericPressable/PropTypes.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/Pressable/GenericPressable/PropTypes.js b/src/components/Pressable/GenericPressable/PropTypes.js index 79d280abc1f7..a6852ecca23a 100644 --- a/src/components/Pressable/GenericPressable/PropTypes.js +++ b/src/components/Pressable/GenericPressable/PropTypes.js @@ -1,16 +1,13 @@ import PropTypes from 'prop-types'; -import {PressableProps} from 'react-native'; -import * as StyleUtils from '../../../styles/StyleUtils'; +import stylePropType from '../../../styles/stylePropTypes'; import CONST from '../../../CONST'; const stylePropTypeWithFunction = PropTypes.oneOfType([ - StyleUtils.stylePropType, + stylePropType, PropTypes.func, ]); const pressablePropTypes = { - ...PressableProps, - /** * onPress callback */ From c7141dbaee83ed36ddceefaef00b0190a6d7e1b9 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 25 Apr 2023 00:30:37 +0200 Subject: [PATCH 30/47] adjust action order of haptic, blur, onPress as specified in design doc --- .../Pressable/GenericPressable/BaseGenericPressable.js | 6 +++--- src/components/Pressable/PressableWithFeedback.js | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 2ac45361eab7..5ffe907f2f6a 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -74,12 +74,11 @@ const GenericPressable = forwardRef((props, ref) => { if (shouldUseHapticsOnLongPress) { HapticFeedback.longPress(); } - onLongPress(); - if (ref.current) { ref.current.blur(); } + onLongPress(); Accessibility.moveAccessibilityFocus(nextFocusRef); }, [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef, ref]); @@ -87,10 +86,11 @@ const GenericPressable = forwardRef((props, ref) => { if (shouldUseHapticsOnPress) { HapticFeedback.press(); } - onPress(); if (ref.current) { ref.current.blur(); } + onPress(); + Accessibility.moveAccessibilityFocus(nextFocusRef); }, [shouldUseHapticsOnPress, onPress, nextFocusRef, ref]); diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index 92f079189d63..00122bc941c7 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -5,6 +5,7 @@ import GenericPressable from './GenericPressable'; import GenericPressablePropTypes from './GenericPressable/PropTypes'; import OpacityView from '../OpacityView'; import variables from '../../styles/variables'; +import * as StyleUtils from '../../styles/StyleUtils'; const omitedProps = ['style', 'pressStyle', 'hoverStyle', 'focusStyle', 'wrapperStyle']; @@ -31,10 +32,10 @@ const PressableWithFeedback = forwardRef((props, ref) => { shouldDim={state.pressed || state.hovered} dimmingValue={state.pressed ? props.pressDimmingValue : props.hoverDimmingValue} style={[ - props.style, - state.pressed && props.pressStyle, - state.hovered && props.hoverStyle, - state.focused && props.focusStyle, + StyleUtils.parseStyleFromFunction(props.style, state), + state.pressed && StyleUtils.parseStyleFromFunction(props.pressStyle, state), + state.hovered && StyleUtils.parseStyleAsArray(props.hoverStyle, state), + state.focused && StyleUtils.parseStyleAsArray(props.focusStyle, state), ]} > {props.children} From cf76372ca246d98302973c374f095dc1bf985b95 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Tue, 25 Apr 2023 13:27:03 +0200 Subject: [PATCH 31/47] prevent onPressIn and onPressOut from fire when component is disabled --- .../Pressable/GenericPressable/BaseGenericPressable.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 5ffe907f2f6a..2eefca5c0161 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -48,6 +48,8 @@ const GenericPressable = forwardRef((props, ref) => { keyboardShortcut, shouldUseAutoHitSlop, enableInScreenReaderStates, + onPressIn, + onPressOut, ...rest } = props; @@ -117,6 +119,8 @@ const GenericPressable = forwardRef((props, ref) => { onPress={!isDisabled && onPressHandler} onLongPress={!isDisabled && onLongPressHandler} onKeyPress={!isDisabled && onKeyPressHandler} + onPressIn={!isDisabled && onPressIn} + onPressOut={!isDisabled && onPressOut} style={state => [ getCursorStyle(isDisabled, [props.accessibilityRole, props.role].includes('text')), props.style, From a19c8e0b634ea7ddb57831911de9acbc006d4c9d Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 26 Apr 2023 10:52:53 +0200 Subject: [PATCH 32/47] change to underscore library --- .../Pressable/GenericPressable/BaseGenericPressable.js | 2 +- src/components/Pressable/PressableWithFeedback.js | 2 +- src/components/Pressable/PressableWithoutFeedback.js | 2 +- src/libs/Accessibility/index.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 2eefca5c0161..c217ada33e0d 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -5,7 +5,7 @@ import React, { forwardRef, } from 'react'; import {Pressable} from 'react-native'; -import _ from 'lodash'; +import _ from 'underscore'; import Accessibility from '../../../libs/Accessibility'; import HapticFeedback from '../../../libs/HapticFeedback'; import KeyboardShortcut from '../../../libs/KeyboardShortcut'; diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index 00122bc941c7..81516593e5b3 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -1,5 +1,5 @@ import React, {forwardRef} from 'react'; -import _ from 'lodash'; +import _ from 'underscore'; import propTypes from 'prop-types'; import GenericPressable from './GenericPressable'; import GenericPressablePropTypes from './GenericPressable/PropTypes'; diff --git a/src/components/Pressable/PressableWithoutFeedback.js b/src/components/Pressable/PressableWithoutFeedback.js index f0adb25543a1..ea0b7c9bde00 100644 --- a/src/components/Pressable/PressableWithoutFeedback.js +++ b/src/components/Pressable/PressableWithoutFeedback.js @@ -1,4 +1,4 @@ -import _ from 'lodash'; +import _ from 'underscore'; import GenericPressable from './GenericPressable'; import GenericPressableProps from './GenericPressable/PropTypes'; diff --git a/src/libs/Accessibility/index.js b/src/libs/Accessibility/index.js index e27da2dffe99..4fd670d73d0f 100644 --- a/src/libs/Accessibility/index.js +++ b/src/libs/Accessibility/index.js @@ -1,6 +1,6 @@ import {useEffect, useState, useCallback} from 'react'; import {AccessibilityInfo} from 'react-native'; -import _ from 'lodash'; +import _ from 'underscore'; import moveAccessibilityFocus from './moveAccessibilityFocus'; const useScreenReaderStatus = () => { From 8e5547e0e9d73f4c7216c4bd364d407cc4e8c092 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 26 Apr 2023 10:53:28 +0200 Subject: [PATCH 33/47] fix camelCase issue --- .../Pressable/GenericPressable/BaseGenericPressable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index c217ada33e0d..7adb54710933 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -54,7 +54,7 @@ const GenericPressable = forwardRef((props, ref) => { } = props; const isScreenReaderActive = Accessibility.useScreenReaderStatus(); - const [hitslop, onLayout] = Accessibility.useAutoHitSlop(); + const [hitSlop, onLayout] = Accessibility.useAutoHitSlop(); const isDisabled = useMemo(() => { let shouldBeDisabledByScreenReader = false; @@ -113,7 +113,7 @@ const GenericPressable = forwardRef((props, ref) => { return ( Date: Wed, 26 Apr 2023 10:54:06 +0200 Subject: [PATCH 34/47] return remove subscription from addEventListener immediately --- src/libs/Accessibility/index.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libs/Accessibility/index.js b/src/libs/Accessibility/index.js index 4fd670d73d0f..d10d3f1a7137 100644 --- a/src/libs/Accessibility/index.js +++ b/src/libs/Accessibility/index.js @@ -8,12 +8,7 @@ const useScreenReaderStatus = () => { useEffect(() => { const subscription = AccessibilityInfo.addEventListener('screenReaderChanged', setIsScreenReaderEnabled); - return () => { - if (!subscription) { - return; - } - subscription.remove(); - }; + return subscription.remove; }, []); return isScreenReaderEnabled; From fe9611cfb59a2f70539f5f90a7ed0c8021e23653 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 26 Apr 2023 23:38:16 +0200 Subject: [PATCH 35/47] fix typo in omittedProps --- src/components/Pressable/PressableWithFeedback.js | 8 ++++---- src/components/Pressable/PressableWithoutFeedback.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/Pressable/PressableWithFeedback.js b/src/components/Pressable/PressableWithFeedback.js index 81516593e5b3..66401fc1d974 100644 --- a/src/components/Pressable/PressableWithFeedback.js +++ b/src/components/Pressable/PressableWithFeedback.js @@ -7,23 +7,23 @@ import OpacityView from '../OpacityView'; import variables from '../../styles/variables'; import * as StyleUtils from '../../styles/StyleUtils'; -const omitedProps = ['style', 'pressStyle', 'hoverStyle', 'focusStyle', 'wrapperStyle']; +const omittedProps = ['style', 'pressStyle', 'hoverStyle', 'focusStyle', 'wrapperStyle']; const PressableWithFeedbackPropTypes = { - ..._.omit(GenericPressablePropTypes.pressablePropTypes, omitedProps), + ..._.omit(GenericPressablePropTypes.pressablePropTypes, omittedProps), pressDimmingValue: propTypes.number, hoverDimmingValue: propTypes.number, }; const PressableWithFeedbackDefaultProps = { - ..._.omit(GenericPressablePropTypes.defaultProps, omitedProps), + ..._.omit(GenericPressablePropTypes.defaultProps, omittedProps), pressDimmingValue: variables.pressDimValue, hoverDimmingValue: variables.hoverDimValue, wrapperStyle: [], }; const PressableWithFeedback = forwardRef((props, ref) => { - const propsWithoutStyling = _.omit(props, omitedProps); + const propsWithoutStyling = _.omit(props, omittedProps); return ( // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/Pressable/PressableWithoutFeedback.js b/src/components/Pressable/PressableWithoutFeedback.js index ea0b7c9bde00..4afddfbf3079 100644 --- a/src/components/Pressable/PressableWithoutFeedback.js +++ b/src/components/Pressable/PressableWithoutFeedback.js @@ -2,7 +2,7 @@ import _ from 'underscore'; import GenericPressable from './GenericPressable'; import GenericPressableProps from './GenericPressable/PropTypes'; -const omitedProps = [ +const omittedProps = [ 'pressStyle', 'hoverStyle', 'focusStyle', @@ -14,13 +14,13 @@ const omitedProps = [ ]; const PressableWithoutFeedback = (props) => { - const propsWithoutStyling = _.omit(props, omitedProps); + const propsWithoutStyling = _.omit(props, omittedProps); // eslint-disable-next-line react/jsx-props-no-spreading return ; }; PressableWithoutFeedback.displayName = 'PressableWithoutFeedback'; -PressableWithoutFeedback.propTypes = _.omit(GenericPressableProps.pressablePropTypes, omitedProps); -PressableWithoutFeedback.defaultProps = _.omit(GenericPressableProps.defaultProps, omitedProps); +PressableWithoutFeedback.propTypes = _.omit(GenericPressableProps.pressablePropTypes, omittedProps); +PressableWithoutFeedback.defaultProps = _.omit(GenericPressableProps.defaultProps, omittedProps); export default PressableWithoutFeedback; From c0e77c6129fcc99162a7878b0f6802879bf3c711 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 26 Apr 2023 23:38:49 +0200 Subject: [PATCH 36/47] remove redundant eslint disable comment --- .../Pressable/GenericPressable/BaseGenericPressable.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 7adb54710933..5d2279887152 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -33,7 +33,6 @@ const getCursorStyle = (isDisabled, isText) => { }; const GenericPressable = forwardRef((props, ref) => { - // eslint-disable-next-line react/destructuring-assignment const { children, onPress, @@ -79,8 +78,8 @@ const GenericPressable = forwardRef((props, ref) => { if (ref.current) { ref.current.blur(); } - onLongPress(); + Accessibility.moveAccessibilityFocus(nextFocusRef); }, [shouldUseHapticsOnLongPress, onLongPress, nextFocusRef, ref]); From 924382991544c5f174bcadda7e980421c2c2a57a Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Wed, 26 Apr 2023 23:40:03 +0200 Subject: [PATCH 37/47] make GenericPressable comment more precise --- src/components/Pressable/GenericPressable/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Pressable/GenericPressable/index.js b/src/components/Pressable/GenericPressable/index.js index e50cece9fd02..0ce58e3487d6 100644 --- a/src/components/Pressable/GenericPressable/index.js +++ b/src/components/Pressable/GenericPressable/index.js @@ -8,7 +8,7 @@ const WebGenericPressable = forwardRef((props, ref) => ( {...props} ref={ref} - // change all props with accessibility* to aria-* + // change native accessibily props to web accessibility props tabIndex={(!props.accessible || !props.focusable) ? -1 : 0} role={props.accessibilityRole} id={props.nativeID} From e20f56235b49b3124a0266feec186574f6970744 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 27 Apr 2023 11:56:09 +0200 Subject: [PATCH 38/47] update GenericPressable propTypes --- .../Pressable/GenericPressable/PropTypes.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/Pressable/GenericPressable/PropTypes.js b/src/components/Pressable/GenericPressable/PropTypes.js index a6852ecca23a..7224946a8a16 100644 --- a/src/components/Pressable/GenericPressable/PropTypes.js +++ b/src/components/Pressable/GenericPressable/PropTypes.js @@ -39,7 +39,7 @@ const pressablePropTypes = { * style for when the component is disabled. Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) * @default {} * @example {backgroundColor: 'red'} - * @example state => ({backgroundColor: state.active ? 'red' : 'blue'}) + * @example state => ({backgroundColor: state.isDisabled ? 'red' : 'blue'}) */ disabledStyle: stylePropTypeWithFunction, @@ -47,7 +47,7 @@ const pressablePropTypes = { * style for when the component is hovered. Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) * @default {} * @example {backgroundColor: 'red'} - * @example state => ({backgroundColor: state.active ? 'red' : 'blue'}) + * @example state => ({backgroundColor: state.hover ? 'red' : 'blue'}) */ hoverStyle: stylePropTypeWithFunction, @@ -55,7 +55,7 @@ const pressablePropTypes = { * style for when the component is focused. Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) * @default {} * @example {backgroundColor: 'red'} - * @example state => ({backgroundColor: state.active ? 'red' : 'blue'}) + * @example state => ({backgroundColor: state.focused ? 'red' : 'blue'}) */ focusStyle: stylePropTypeWithFunction, @@ -63,16 +63,16 @@ const pressablePropTypes = { * style for when the component is pressed. Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) * @default {} * @example {backgroundColor: 'red'} - * @example state => ({backgroundColor: state.active ? 'red' : 'blue'}) + * @example state => ({backgroundColor: state.pressed ? 'red' : 'blue'}) */ - pressedStyle: stylePropTypeWithFunction, + pressStyle: stylePropTypeWithFunction, /** * style for when the component is active and the screen reader is on. * Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) * @default {} * @example {backgroundColor: 'red'} - * @example state => ({backgroundColor: state.active ? 'red' : 'blue'}) + * @example state => ({backgroundColor: state.isScreenReaderActive ? 'red' : 'blue'}) */ screenReaderActiveStyle: stylePropTypeWithFunction, From 1cdea8a356c6a1bbf265a73ec3feaff4c08eefef Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 27 Apr 2023 12:00:02 +0200 Subject: [PATCH 39/47] use hoverDimValue from variables as default OpacityView value --- src/components/OpacityView.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/OpacityView.js b/src/components/OpacityView.js index ff3a14df61a3..2b0958ae7db5 100644 --- a/src/components/OpacityView.js +++ b/src/components/OpacityView.js @@ -2,6 +2,7 @@ import React from 'react'; import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import PropTypes from 'prop-types'; import * as StyleUtils from '../styles/StyleUtils'; +import variables from '../styles/variables'; const propTypes = { /** @@ -30,7 +31,7 @@ const propTypes = { const defaultProps = { style: [], - dimmingValue: 0.5, + dimmingValue: variables.hoverDimValue, }; const OpacityView = (props) => { From 9e221e2313a1ad9f5759c431b867c0d42674fac1 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 27 Apr 2023 12:00:33 +0200 Subject: [PATCH 40/47] fix wrong style name in GenericPressable --- .../Pressable/GenericPressable/BaseGenericPressable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 5d2279887152..e4684e8a63d1 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -126,7 +126,7 @@ const GenericPressable = forwardRef((props, ref) => { isScreenReaderActive && StyleUtils.parseStyleFromFunction(props.screenReaderActiveStyle, state), state.focused && StyleUtils.parseStyleFromFunction(props.focusStyle, state), state.hovered && StyleUtils.parseStyleFromFunction(props.hoverStyle, state), - state.pressed && StyleUtils.parseStyleFromFunction(props.pressedStyle, state), + state.pressed && StyleUtils.parseStyleFromFunction(props.pressStyle, state), isDisabled && [...StyleUtils.parseStyleFromFunction(props.disabledStyle, state), styles.noSelect], ]} From 8ad2923a43a89edba87547adf472afb864db9627 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 27 Apr 2023 12:01:50 +0200 Subject: [PATCH 41/47] address the arbitrary minimum value of tappable area inside getHitSlopForSize func --- src/libs/Accessibility/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/Accessibility/index.js b/src/libs/Accessibility/index.js index d10d3f1a7137..607c01836405 100644 --- a/src/libs/Accessibility/index.js +++ b/src/libs/Accessibility/index.js @@ -15,6 +15,8 @@ const useScreenReaderStatus = () => { }; const getHitSlopForSize = ({x, y}) => { + /* according to https://developer.apple.com/design/human-interface-guidelines/ios/visual-design/adaptivity-and-layout/ + the minimum tappable area is 44x44 points */ const minimumSize = 44; const hitSlopVertical = _.max([minimumSize - x, 0]); const hitSlopHorizontal = _.max([minimumSize - y, 0]); From 7bfc422ba23f1f182fadf9db65c6c13b7a2c57cc Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 27 Apr 2023 12:35:33 +0200 Subject: [PATCH 42/47] fix typo --- src/components/Pressable/GenericPressable/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Pressable/GenericPressable/index.js b/src/components/Pressable/GenericPressable/index.js index 0ce58e3487d6..a5125653b949 100644 --- a/src/components/Pressable/GenericPressable/index.js +++ b/src/components/Pressable/GenericPressable/index.js @@ -8,7 +8,7 @@ const WebGenericPressable = forwardRef((props, ref) => ( {...props} ref={ref} - // change native accessibily props to web accessibility props + // change native accessibility props to web accessibility props tabIndex={(!props.accessible || !props.focusable) ? -1 : 0} role={props.accessibilityRole} id={props.nativeID} From 2006a89e953a398101a8f54399220c16f43e834d Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 27 Apr 2023 12:52:41 +0200 Subject: [PATCH 43/47] fix wrong prop name inside default props --- .../Pressable/GenericPressable/PropTypes.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/Pressable/GenericPressable/PropTypes.js b/src/components/Pressable/GenericPressable/PropTypes.js index 7224946a8a16..3690f49884cf 100644 --- a/src/components/Pressable/GenericPressable/PropTypes.js +++ b/src/components/Pressable/GenericPressable/PropTypes.js @@ -47,7 +47,7 @@ const pressablePropTypes = { * style for when the component is hovered. Can be a function that receives the component's state (active, disabled, hover, focus, pressed, isScreenReaderActive) * @default {} * @example {backgroundColor: 'red'} - * @example state => ({backgroundColor: state.hover ? 'red' : 'blue'}) + * @example state => ({backgroundColor: state.hovered ? 'red' : 'blue'}) */ hoverStyle: stylePropTypeWithFunction, @@ -89,8 +89,20 @@ const pressablePropTypes = { * Specifies which component should be focused after interacting with this component */ nextFocusRef: PropTypes.func, + + /** + * Specifies the accessibility label for the component + * @example 'Press me' + * @example 'Close' + */ accessibilityLabel: PropTypes.string.isRequired, + /** + * Specifies the accessibility hint for the component + * @example 'Double tap to open' + */ + accessibilityHint: PropTypes.string, + /** * Specifies if the component should calculate its hitSlop automatically * @default true @@ -105,7 +117,7 @@ const defaultProps = { disabledStyle: {}, hoverStyle: {}, focusStyle: {}, - pressedStyle: {}, + pressStyle: {}, screenReaderActiveStyle: {}, enableInScreenReaderStates: CONST.SCREEN_READER_STATES.ALL, nextFocusRef: undefined, From 27ba2fab45dcd1b93e83de578fbfdf8ef22bb90b Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 27 Apr 2023 13:57:25 +0200 Subject: [PATCH 44/47] use platform specific cursor utility in BaseGenericPressable --- .../Pressable/GenericPressable/BaseGenericPressable.js | 7 ++++--- src/styles/utilities/cursor/index.js | 3 +++ src/styles/utilities/cursor/index.native.js | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index e4684e8a63d1..616fd3beff07 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -10,6 +10,7 @@ import Accessibility from '../../../libs/Accessibility'; import HapticFeedback from '../../../libs/HapticFeedback'; import KeyboardShortcut from '../../../libs/KeyboardShortcut'; import styles from '../../../styles/styles'; +import cursor from '../../../styles/utilities/cursor'; import genericPressablePropTypes from './PropTypes'; import CONST from '../../../CONST'; import * as StyleUtils from '../../../styles/StyleUtils'; @@ -22,14 +23,14 @@ import * as StyleUtils from '../../../styles/StyleUtils'; */ const getCursorStyle = (isDisabled, isText) => { if (isDisabled) { - return styles.cursorDisabled; + return cursor.cursorDisabled; } if (isText) { - return styles.cursorText; + return cursor.cursorText; } - return styles.cursorPointer; + return cursor.cursorPointer; }; const GenericPressable = forwardRef((props, ref) => { diff --git a/src/styles/utilities/cursor/index.js b/src/styles/utilities/cursor/index.js index 85aef855d2d9..00f7f9a7b1e3 100644 --- a/src/styles/utilities/cursor/index.js +++ b/src/styles/utilities/cursor/index.js @@ -29,4 +29,7 @@ export default { cursorInitial: { cursor: 'initial', }, + cursorText: { + cursor: 'text', + }, }; diff --git a/src/styles/utilities/cursor/index.native.js b/src/styles/utilities/cursor/index.native.js index 99c7070fb477..95704e55b07a 100644 --- a/src/styles/utilities/cursor/index.native.js +++ b/src/styles/utilities/cursor/index.native.js @@ -9,4 +9,5 @@ export default { cursorGrabbing: {}, cursorZoomOut: {}, cursorInitial: {}, + cursorText: {}, }; From 0e853643f3b00891d3d5072566a3f2d399bd7714 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 27 Apr 2023 18:20:23 +0200 Subject: [PATCH 45/47] Change order of imports Co-authored-by: Amy Evans --- src/components/Pressable/PressableWithoutFeedback.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Pressable/PressableWithoutFeedback.js b/src/components/Pressable/PressableWithoutFeedback.js index 4afddfbf3079..a12862bcd0fd 100644 --- a/src/components/Pressable/PressableWithoutFeedback.js +++ b/src/components/Pressable/PressableWithoutFeedback.js @@ -1,3 +1,4 @@ +import React from 'react'; import _ from 'underscore'; import GenericPressable from './GenericPressable'; import GenericPressableProps from './GenericPressable/PropTypes'; From 72af27ba741bea22e40dc91e0e1c74cf374b3eae Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 27 Apr 2023 18:21:11 +0200 Subject: [PATCH 46/47] Remove unintended whitespace Co-authored-by: Amy Evans --- .../Pressable/GenericPressable/BaseGenericPressable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.js b/src/components/Pressable/GenericPressable/BaseGenericPressable.js index 616fd3beff07..6caba1c39cd0 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.js +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.js @@ -146,7 +146,7 @@ const GenericPressable = forwardRef((props, ref) => { // eslint-disable-next-line react/jsx-props-no-spreading {...rest} > - {state => (_.isFunction(props.children) ? props.children({...state, isScreenReaderActive, isDisabled}) : props.children) } + {state => (_.isFunction(props.children) ? props.children({...state, isScreenReaderActive, isDisabled}) : props.children)} ); }); From 66782e55cee1c8e6ddae5bb6464db53d575472b5 Mon Sep 17 00:00:00 2001 From: Robert Kozik Date: Thu, 27 Apr 2023 18:24:20 +0200 Subject: [PATCH 47/47] update Pressable proppType example --- src/components/Pressable/GenericPressable/PropTypes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Pressable/GenericPressable/PropTypes.js b/src/components/Pressable/GenericPressable/PropTypes.js index 3690f49884cf..a87f8d83c02a 100644 --- a/src/components/Pressable/GenericPressable/PropTypes.js +++ b/src/components/Pressable/GenericPressable/PropTypes.js @@ -92,7 +92,7 @@ const pressablePropTypes = { /** * Specifies the accessibility label for the component - * @example 'Press me' + * @example 'Search' * @example 'Close' */ accessibilityLabel: PropTypes.string.isRequired,