From 83959c595d52ceb7aa4e3f68441c5ac41c389ebc Mon Sep 17 00:00:00 2001
From: Ollie H <renahlee@outlook.com>
Date: Mon, 1 May 2023 18:38:47 -0700
Subject: React Native accessibility (#539)

* React Native accessibility

* First round of changes

* Latest update

* Checkpoint

* Wrap up

* Lint

* Remove unhelpful image hints

* Fix navigation

* Fix rebase and lint

* Mitigate an known issue with the password entry in login

* Fix composer dismiss

* Remove focus on input elements for web

* Remove i and npm

* pls work

* Remove stray declaration

* Regenerate yarn.lock

---------

Co-authored-by: Paul Frazee <pfrazee@gmail.com>
---
 src/view/com/util/forms/DropdownButton.tsx | 62 ++++++++++++++++++++++--------
 1 file changed, 46 insertions(+), 16 deletions(-)

(limited to 'src/view/com/util/forms/DropdownButton.tsx')

diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx
index 725d45c1b..04346d91f 100644
--- a/src/view/com/util/forms/DropdownButton.tsx
+++ b/src/view/com/util/forms/DropdownButton.tsx
@@ -1,4 +1,4 @@
-import React, {useRef} from 'react'
+import React, {PropsWithChildren, useMemo, useRef} from 'react'
 import {
   Dimensions,
   StyleProp,
@@ -39,6 +39,19 @@ type MaybeDropdownItem = DropdownItem | false | undefined
 
 export type DropdownButtonType = ButtonType | 'bare'
 
+interface DropdownButtonProps {
+  testID?: string
+  type?: DropdownButtonType
+  style?: StyleProp<ViewStyle>
+  items: MaybeDropdownItem[]
+  label?: string
+  menuWidth?: number
+  children?: React.ReactNode
+  openToRight?: boolean
+  rightOffset?: number
+  bottomOffset?: number
+}
+
 export function DropdownButton({
   testID,
   type = 'bare',
@@ -50,18 +63,7 @@ export function DropdownButton({
   openToRight = false,
   rightOffset = 0,
   bottomOffset = 0,
-}: {
-  testID?: string
-  type?: DropdownButtonType
-  style?: StyleProp<ViewStyle>
-  items: MaybeDropdownItem[]
-  label?: string
-  menuWidth?: number
-  children?: React.ReactNode
-  openToRight?: boolean
-  rightOffset?: number
-  bottomOffset?: number
-}) {
+}: PropsWithChildren<DropdownButtonProps>) {
   const ref1 = useRef<TouchableOpacity>(null)
   const ref2 = useRef<View>(null)
 
@@ -105,6 +107,18 @@ export function DropdownButton({
     )
   }
 
+  const numItems = useMemo(
+    () =>
+      items.filter(item => {
+        if (item === undefined || item === false) {
+          return false
+        }
+
+        return isBtn(item)
+      }).length,
+    [items],
+  )
+
   if (type === 'bare') {
     return (
       <TouchableOpacity
@@ -112,7 +126,10 @@ export function DropdownButton({
         style={style}
         onPress={onPress}
         hitSlop={HITSLOP}
-        ref={ref1}>
+        ref={ref1}
+        accessibilityRole="button"
+        accessibilityLabel={`Opens ${numItems} options`}
+        accessibilityHint={`Opens ${numItems} options`}>
         {children}
       </TouchableOpacity>
     )
@@ -283,9 +300,20 @@ const DropdownItems = ({
   const separatorColor =
     theme.colorScheme === 'dark' ? pal.borderDark : pal.border
 
+  const numItems = items.filter(isBtn).length
+
   return (
     <>
-      <TouchableWithoutFeedback onPress={onOuterPress}>
+      <TouchableWithoutFeedback
+        onPress={onOuterPress}
+        // TODO: Refactor dropdown components to:
+        // - (On web, if not handled by React Native) use semantic <select />
+        // and <option /> elements for keyboard navigation out of the box
+        // - (On mobile) be buttons by default, accept `label` and `nativeID`
+        // props, and always have an explicit label
+        accessibilityRole="button"
+        accessibilityLabel="Toggle dropdown"
+        accessibilityHint="">
         <View style={[styles.bg]} />
       </TouchableWithoutFeedback>
       <View
@@ -301,7 +329,9 @@ const DropdownItems = ({
                 testID={item.testID}
                 key={index}
                 style={[styles.menuItem]}
-                onPress={() => onPressItem(index)}>
+                onPress={() => onPressItem(index)}
+                accessibilityLabel={item.label}
+                accessibilityHint={`Option ${index + 1} of ${numItems}`}>
                 {item.icon && (
                   <FontAwesomeIcon
                     style={styles.icon}
-- 
cgit 1.4.1

