From 13439ee5e60872ed6528cab7f962a1af4bc95aa6 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Fri, 18 Oct 2024 11:37:25 -0700 Subject: [PATCH 01/19] Add heading role to Header text --- packages/components/src/components/shared/FormText.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/components/shared/FormText.tsx b/packages/components/src/components/shared/FormText.tsx index 4f248500..60730b1e 100644 --- a/packages/components/src/components/shared/FormText.tsx +++ b/packages/components/src/components/shared/FormText.tsx @@ -72,7 +72,7 @@ export const Header: FC = ({ text, required }) => { : getA11yLabel(text) return ( - + {getDisplayText(text)} {required && {` (*${t('required')})`}} From 66eb2497777bce85785b469166219e057e26d85b Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Fri, 18 Oct 2024 11:37:56 -0700 Subject: [PATCH 02/19] Add accessibilityValue prop to Checkbox. Provide list position in CheckboxGroup map --- .../components/src/components/Checkbox/Checkbox.tsx | 2 ++ .../src/components/CheckboxGroup/CheckboxGroup.tsx | 10 ++++++++++ packages/components/src/types/forms.ts | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/packages/components/src/components/Checkbox/Checkbox.tsx b/packages/components/src/components/Checkbox/Checkbox.tsx index 7b26121e..0e0123bd 100644 --- a/packages/components/src/components/Checkbox/Checkbox.tsx +++ b/packages/components/src/components/Checkbox/Checkbox.tsx @@ -31,6 +31,7 @@ export type CheckboxProps = FormElementProps & } export const Checkbox: FC = ({ + accessibilityValue, checked, label, description, @@ -130,6 +131,7 @@ export const Checkbox: FC = ({ {_icon} diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index 281f93e1..3d9f7cfa 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -12,6 +12,7 @@ import { } from '../../types' import { Spacer } from '../Spacer/Spacer' import { useTheme } from '../../utils' +import { useTranslation } from 'react-i18next' type TextWithA11yAndValue = TextWithA11y & { /** Description for checkbox item */ @@ -98,6 +99,7 @@ export const CheckboxGroup: FC = ({ testID, tile, }) => { + const { t } = useTranslation() const theme = useTheme() const handleCheckboxChange = (value: string | number) => { @@ -142,10 +144,18 @@ export const CheckboxGroup: FC = ({ const isObject = typeof item === 'object' const value = isObject ? item.value || item.text : item + const accessibilityValue = { + text: t('listPosition', { + position: index + 1, + total: items.length, + }), + } + return ( handleCheckboxChange(value)} diff --git a/packages/components/src/types/forms.ts b/packages/components/src/types/forms.ts index 804ed477..795f21e2 100644 --- a/packages/components/src/types/forms.ts +++ b/packages/components/src/types/forms.ts @@ -1,3 +1,5 @@ +import { AccessibilityValue } from 'react-native' + import { StringOrTextWithA11y } from './common' /** @@ -24,6 +26,8 @@ export type CheckboxRadioProps = { label: StringOrTextWithA11y /** OnPress logic to alter `checked` state or other behavior associated with the checkbox */ onPress: () => void + /** Textual description of position within list of checkboxes */ + accessibilityValue?: AccessibilityValue /** Description that appears below label */ description?: StringOrTextWithA11y /** True to apply tile styling */ From d40c9d45229afc2bf0a5aa7fb5823a740de4593f Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Fri, 18 Oct 2024 11:52:40 -0700 Subject: [PATCH 03/19] Create useListPosition hook --- .../components/CheckboxGroup/CheckboxGroup.tsx | 9 ++------- .../SegmentedControl/SegmentedControl.tsx | 10 ++-------- packages/components/src/utils/accessibility.ts | 1 + .../src/utils/hooks/useListPosition.ts | 17 +++++++++++++++++ 4 files changed, 22 insertions(+), 15 deletions(-) create mode 100644 packages/components/src/utils/hooks/useListPosition.ts diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index 3d9f7cfa..a28eb48c 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -11,6 +11,7 @@ import { TextWithA11y, } from '../../types' import { Spacer } from '../Spacer/Spacer' +import { useListPosition } from '../../utils/accessibility' import { useTheme } from '../../utils' import { useTranslation } from 'react-i18next' @@ -99,7 +100,6 @@ export const CheckboxGroup: FC = ({ testID, tile, }) => { - const { t } = useTranslation() const theme = useTheme() const handleCheckboxChange = (value: string | number) => { @@ -144,12 +144,7 @@ export const CheckboxGroup: FC = ({ const isObject = typeof item === 'object' const value = isObject ? item.value || item.text : item - const accessibilityValue = { - text: t('listPosition', { - position: index + 1, - total: items.length, - }), - } + const accessibilityValue = useListPosition(index + 1, items.length) return ( diff --git a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx index f443a032..e366d350 100644 --- a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx +++ b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx @@ -1,11 +1,11 @@ import { Pressable, Text, TextStyle, View, ViewStyle } from 'react-native' import { spacing } from '@department-of-veterans-affairs/mobile-tokens' -import { useTranslation } from 'react-i18next' import React, { FC, useEffect } from 'react' import styled from 'styled-components/native' import { ComponentWrapper } from '../../wrapper' import { PressableOpacityStyle, useTheme } from '../../utils' +import { useListPosition } from '../../utils/accessibility' /** * Props for {@link SegmentedControl} @@ -52,7 +52,6 @@ export const SegmentedControl: FC = ({ a11yHints, testIDs, }) => { - const { t } = useTranslation() const theme = useTheme() useEffect(() => { @@ -83,12 +82,7 @@ export const SegmentedControl: FC = ({ const accessibilityLabel = a11yLabels ? a11yLabels[index] || labels[index] : labels[index] - const accessibilityValue = { - text: t('listPosition', { - position: index + 1, - total: labels.length, - }), - } + const accessibilityValue = useListPosition(index + 1, labels.length) // TODO: Replace with typography tokens const font: TextStyle = { diff --git a/packages/components/src/utils/accessibility.ts b/packages/components/src/utils/accessibility.ts index 3d056711..c2e09f9a 100644 --- a/packages/components/src/utils/accessibility.ts +++ b/packages/components/src/utils/accessibility.ts @@ -2,6 +2,7 @@ import { StringOrTextWithA11y } from '../types' // Export related hooks export { useIsScreenReaderEnabled } from './hooks/useIsScreenReaderEnabled' +export { useListPosition } from './hooks/useListPosition' /** * Returns text that should be displayed on the screen diff --git a/packages/components/src/utils/hooks/useListPosition.ts b/packages/components/src/utils/hooks/useListPosition.ts new file mode 100644 index 00000000..af067523 --- /dev/null +++ b/packages/components/src/utils/hooks/useListPosition.ts @@ -0,0 +1,17 @@ +import { useTranslation } from 'react-i18next' +import { AccessibilityValue } from 'react-native' + +/** Returns list position used for accessibilityValues */ +export function useListPosition( + position: number, + total: number, +): AccessibilityValue { + const { t } = useTranslation() + + return { + text: t('listPosition', { + position, + total, + }), + } +} From c461c44b3ab76e1383a932ba5d2e4fed2fe4b8d7 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Fri, 18 Oct 2024 11:53:12 -0700 Subject: [PATCH 04/19] Remove unused import --- .../components/src/components/CheckboxGroup/CheckboxGroup.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index a28eb48c..7083651c 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -13,7 +13,6 @@ import { import { Spacer } from '../Spacer/Spacer' import { useListPosition } from '../../utils/accessibility' import { useTheme } from '../../utils' -import { useTranslation } from 'react-i18next' type TextWithA11yAndValue = TextWithA11y & { /** Description for checkbox item */ From 4c0835cae1afd05c714a8815f2827eaf4a6b7f60 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Fri, 18 Oct 2024 11:54:33 -0700 Subject: [PATCH 05/19] Consolidate imports --- .../components/src/components/CheckboxGroup/CheckboxGroup.tsx | 3 +-- .../src/components/SegmentedControl/SegmentedControl.tsx | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index 7083651c..e637bc66 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -11,8 +11,7 @@ import { TextWithA11y, } from '../../types' import { Spacer } from '../Spacer/Spacer' -import { useListPosition } from '../../utils/accessibility' -import { useTheme } from '../../utils' +import { useListPosition, useTheme } from '../../utils' type TextWithA11yAndValue = TextWithA11y & { /** Description for checkbox item */ diff --git a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx index e366d350..e616982a 100644 --- a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx +++ b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx @@ -4,8 +4,7 @@ import React, { FC, useEffect } from 'react' import styled from 'styled-components/native' import { ComponentWrapper } from '../../wrapper' -import { PressableOpacityStyle, useTheme } from '../../utils' -import { useListPosition } from '../../utils/accessibility' +import { PressableOpacityStyle, useListPosition, useTheme } from '../../utils' /** * Props for {@link SegmentedControl} From ea670e314ff4e01fafe47f7bd01dc0d6e858c2c3 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Fri, 18 Oct 2024 11:57:45 -0700 Subject: [PATCH 06/19] Rename useListPosition to useA11yListPosition --- .../src/components/CheckboxGroup/CheckboxGroup.tsx | 7 +++++-- .../src/components/SegmentedControl/SegmentedControl.tsx | 8 ++++++-- packages/components/src/utils/accessibility.ts | 2 +- .../hooks/{useListPosition.ts => useA11yListPosition.ts} | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) rename packages/components/src/utils/hooks/{useListPosition.ts => useA11yListPosition.ts} (90%) diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index e637bc66..30f32947 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -11,7 +11,7 @@ import { TextWithA11y, } from '../../types' import { Spacer } from '../Spacer/Spacer' -import { useListPosition, useTheme } from '../../utils' +import { useA11yListPosition, useTheme } from '../../utils' type TextWithA11yAndValue = TextWithA11y & { /** Description for checkbox item */ @@ -142,7 +142,10 @@ export const CheckboxGroup: FC = ({ const isObject = typeof item === 'object' const value = isObject ? item.value || item.text : item - const accessibilityValue = useListPosition(index + 1, items.length) + const accessibilityValue = useA11yListPosition( + index + 1, + items.length, + ) return ( diff --git a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx index e616982a..cbe7f960 100644 --- a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx +++ b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx @@ -4,7 +4,11 @@ import React, { FC, useEffect } from 'react' import styled from 'styled-components/native' import { ComponentWrapper } from '../../wrapper' -import { PressableOpacityStyle, useListPosition, useTheme } from '../../utils' +import { + PressableOpacityStyle, + useA11yListPosition, + useTheme, +} from '../../utils' /** * Props for {@link SegmentedControl} @@ -81,7 +85,7 @@ export const SegmentedControl: FC = ({ const accessibilityLabel = a11yLabels ? a11yLabels[index] || labels[index] : labels[index] - const accessibilityValue = useListPosition(index + 1, labels.length) + const accessibilityValue = useA11yListPosition(index + 1, labels.length) // TODO: Replace with typography tokens const font: TextStyle = { diff --git a/packages/components/src/utils/accessibility.ts b/packages/components/src/utils/accessibility.ts index c2e09f9a..b67f73de 100644 --- a/packages/components/src/utils/accessibility.ts +++ b/packages/components/src/utils/accessibility.ts @@ -2,7 +2,7 @@ import { StringOrTextWithA11y } from '../types' // Export related hooks export { useIsScreenReaderEnabled } from './hooks/useIsScreenReaderEnabled' -export { useListPosition } from './hooks/useListPosition' +export { useA11yListPosition } from './hooks/useA11yListPosition' /** * Returns text that should be displayed on the screen diff --git a/packages/components/src/utils/hooks/useListPosition.ts b/packages/components/src/utils/hooks/useA11yListPosition.ts similarity index 90% rename from packages/components/src/utils/hooks/useListPosition.ts rename to packages/components/src/utils/hooks/useA11yListPosition.ts index af067523..957b28a3 100644 --- a/packages/components/src/utils/hooks/useListPosition.ts +++ b/packages/components/src/utils/hooks/useA11yListPosition.ts @@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next' import { AccessibilityValue } from 'react-native' /** Returns list position used for accessibilityValues */ -export function useListPosition( +export function useA11yListPosition( position: number, total: number, ): AccessibilityValue { From e5c889b6bbd543ffa5cfe85b33a39ea105af1144 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Fri, 18 Oct 2024 11:59:47 -0700 Subject: [PATCH 07/19] Sort imports --- packages/components/src/utils/hooks/useA11yListPosition.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/utils/hooks/useA11yListPosition.ts b/packages/components/src/utils/hooks/useA11yListPosition.ts index 957b28a3..f732ce61 100644 --- a/packages/components/src/utils/hooks/useA11yListPosition.ts +++ b/packages/components/src/utils/hooks/useA11yListPosition.ts @@ -1,5 +1,5 @@ -import { useTranslation } from 'react-i18next' import { AccessibilityValue } from 'react-native' +import { useTranslation } from 'react-i18next' /** Returns list position used for accessibilityValues */ export function useA11yListPosition( From de7d7c1b6b4b98e2ddbaed9ecde4404ae3042444 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Tue, 22 Oct 2024 10:16:43 -0700 Subject: [PATCH 08/19] Update useA11yListPosition to accept index and calculate position. Update components to use hook for accessibilityValue inline --- .../src/components/CheckboxGroup/CheckboxGroup.tsx | 7 +------ .../src/components/SegmentedControl/SegmentedControl.tsx | 3 +-- packages/components/src/utils/hooks/useA11yListPosition.ts | 4 ++-- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index 30f32947..1894b58d 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -142,16 +142,11 @@ export const CheckboxGroup: FC = ({ const isObject = typeof item === 'object' const value = isObject ? item.value || item.text : item - const accessibilityValue = useA11yListPosition( - index + 1, - items.length, - ) - return ( handleCheckboxChange(value)} diff --git a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx index cbe7f960..b55f5574 100644 --- a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx +++ b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx @@ -85,7 +85,6 @@ export const SegmentedControl: FC = ({ const accessibilityLabel = a11yLabels ? a11yLabels[index] || labels[index] : labels[index] - const accessibilityValue = useA11yListPosition(index + 1, labels.length) // TODO: Replace with typography tokens const font: TextStyle = { @@ -109,7 +108,7 @@ export const SegmentedControl: FC = ({ widthPct={`${100 / labels.length}%`} aria-label={accessibilityLabel} accessibilityHint={a11yHints ? a11yHints[index] : ''} - accessibilityValue={accessibilityValue} + accessibilityValue={useA11yListPosition(index, labels.length)} role={'tab'} accessibilityState={{ selected: isSelected }} style={PressableOpacityStyle()} diff --git a/packages/components/src/utils/hooks/useA11yListPosition.ts b/packages/components/src/utils/hooks/useA11yListPosition.ts index f732ce61..89aafaa6 100644 --- a/packages/components/src/utils/hooks/useA11yListPosition.ts +++ b/packages/components/src/utils/hooks/useA11yListPosition.ts @@ -3,14 +3,14 @@ import { useTranslation } from 'react-i18next' /** Returns list position used for accessibilityValues */ export function useA11yListPosition( - position: number, + index: number, total: number, ): AccessibilityValue { const { t } = useTranslation() return { text: t('listPosition', { - position, + position: index + 1, total, }), } From 6bbf3b150ba863e21321bcfa9c5007d29af30064 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Tue, 22 Oct 2024 10:29:16 -0700 Subject: [PATCH 09/19] Rename accessibilityValue prop to a11yListPosition. Update prop to be of type string --- .../src/components/Checkbox/Checkbox.tsx | 4 ++-- .../components/CheckboxGroup/CheckboxGroup.tsx | 2 +- .../SegmentedControl/SegmentedControl.tsx | 2 +- packages/components/src/types/forms.ts | 2 +- .../src/utils/hooks/useA11yListPosition.ts | 16 +++++----------- 5 files changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/components/src/components/Checkbox/Checkbox.tsx b/packages/components/src/components/Checkbox/Checkbox.tsx index 0e0123bd..732edfe0 100644 --- a/packages/components/src/components/Checkbox/Checkbox.tsx +++ b/packages/components/src/components/Checkbox/Checkbox.tsx @@ -31,7 +31,7 @@ export type CheckboxProps = FormElementProps & } export const Checkbox: FC = ({ - accessibilityValue, + a11yListPosition, checked, label, description, @@ -131,7 +131,7 @@ export const Checkbox: FC = ({ {_icon} diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index 1894b58d..4ac96bdb 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -146,7 +146,7 @@ export const CheckboxGroup: FC = ({ handleCheckboxChange(value)} diff --git a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx index b55f5574..501c79ec 100644 --- a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx +++ b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx @@ -108,7 +108,7 @@ export const SegmentedControl: FC = ({ widthPct={`${100 / labels.length}%`} aria-label={accessibilityLabel} accessibilityHint={a11yHints ? a11yHints[index] : ''} - accessibilityValue={useA11yListPosition(index, labels.length)} + aria-valuetext={useA11yListPosition(index, labels.length)} role={'tab'} accessibilityState={{ selected: isSelected }} style={PressableOpacityStyle()} diff --git a/packages/components/src/types/forms.ts b/packages/components/src/types/forms.ts index 795f21e2..fd4a71ad 100644 --- a/packages/components/src/types/forms.ts +++ b/packages/components/src/types/forms.ts @@ -27,7 +27,7 @@ export type CheckboxRadioProps = { /** OnPress logic to alter `checked` state or other behavior associated with the checkbox */ onPress: () => void /** Textual description of position within list of checkboxes */ - accessibilityValue?: AccessibilityValue + a11yListPosition?: string /** Description that appears below label */ description?: StringOrTextWithA11y /** True to apply tile styling */ diff --git a/packages/components/src/utils/hooks/useA11yListPosition.ts b/packages/components/src/utils/hooks/useA11yListPosition.ts index 89aafaa6..d26ac947 100644 --- a/packages/components/src/utils/hooks/useA11yListPosition.ts +++ b/packages/components/src/utils/hooks/useA11yListPosition.ts @@ -1,17 +1,11 @@ -import { AccessibilityValue } from 'react-native' import { useTranslation } from 'react-i18next' /** Returns list position used for accessibilityValues */ -export function useA11yListPosition( - index: number, - total: number, -): AccessibilityValue { +export function useA11yListPosition(index: number, total: number): string { const { t } = useTranslation() - return { - text: t('listPosition', { - position: index + 1, - total, - }), - } + return t('listPosition', { + position: index + 1, + total, + }) } From 96fefca62bcba6f416d0b1ecb26444f1e363c462 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Tue, 22 Oct 2024 10:32:48 -0700 Subject: [PATCH 10/19] Revert back to AccessibiltyValue type --- .../src/components/Checkbox/Checkbox.tsx | 4 ++-- .../components/CheckboxGroup/CheckboxGroup.tsx | 2 +- .../SegmentedControl/SegmentedControl.tsx | 2 +- packages/components/src/types/forms.ts | 2 +- .../src/utils/hooks/useA11yListPosition.ts | 16 +++++++++++----- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/components/src/components/Checkbox/Checkbox.tsx b/packages/components/src/components/Checkbox/Checkbox.tsx index 732edfe0..0e0123bd 100644 --- a/packages/components/src/components/Checkbox/Checkbox.tsx +++ b/packages/components/src/components/Checkbox/Checkbox.tsx @@ -31,7 +31,7 @@ export type CheckboxProps = FormElementProps & } export const Checkbox: FC = ({ - a11yListPosition, + accessibilityValue, checked, label, description, @@ -131,7 +131,7 @@ export const Checkbox: FC = ({ {_icon} diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index 4ac96bdb..1894b58d 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -146,7 +146,7 @@ export const CheckboxGroup: FC = ({ handleCheckboxChange(value)} diff --git a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx index 501c79ec..b55f5574 100644 --- a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx +++ b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx @@ -108,7 +108,7 @@ export const SegmentedControl: FC = ({ widthPct={`${100 / labels.length}%`} aria-label={accessibilityLabel} accessibilityHint={a11yHints ? a11yHints[index] : ''} - aria-valuetext={useA11yListPosition(index, labels.length)} + accessibilityValue={useA11yListPosition(index, labels.length)} role={'tab'} accessibilityState={{ selected: isSelected }} style={PressableOpacityStyle()} diff --git a/packages/components/src/types/forms.ts b/packages/components/src/types/forms.ts index fd4a71ad..795f21e2 100644 --- a/packages/components/src/types/forms.ts +++ b/packages/components/src/types/forms.ts @@ -27,7 +27,7 @@ export type CheckboxRadioProps = { /** OnPress logic to alter `checked` state or other behavior associated with the checkbox */ onPress: () => void /** Textual description of position within list of checkboxes */ - a11yListPosition?: string + accessibilityValue?: AccessibilityValue /** Description that appears below label */ description?: StringOrTextWithA11y /** True to apply tile styling */ diff --git a/packages/components/src/utils/hooks/useA11yListPosition.ts b/packages/components/src/utils/hooks/useA11yListPosition.ts index d26ac947..89aafaa6 100644 --- a/packages/components/src/utils/hooks/useA11yListPosition.ts +++ b/packages/components/src/utils/hooks/useA11yListPosition.ts @@ -1,11 +1,17 @@ +import { AccessibilityValue } from 'react-native' import { useTranslation } from 'react-i18next' /** Returns list position used for accessibilityValues */ -export function useA11yListPosition(index: number, total: number): string { +export function useA11yListPosition( + index: number, + total: number, +): AccessibilityValue { const { t } = useTranslation() - return t('listPosition', { - position: index + 1, - total, - }) + return { + text: t('listPosition', { + position: index + 1, + total, + }), + } } From b66cba2080f57274186aa2240fc9005f4775aa79 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Tue, 22 Oct 2024 10:33:12 -0700 Subject: [PATCH 11/19] Alphabetize exports --- packages/components/src/utils/accessibility.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/components/src/utils/accessibility.ts b/packages/components/src/utils/accessibility.ts index b67f73de..a98c0d6b 100644 --- a/packages/components/src/utils/accessibility.ts +++ b/packages/components/src/utils/accessibility.ts @@ -1,8 +1,8 @@ import { StringOrTextWithA11y } from '../types' // Export related hooks -export { useIsScreenReaderEnabled } from './hooks/useIsScreenReaderEnabled' export { useA11yListPosition } from './hooks/useA11yListPosition' +export { useIsScreenReaderEnabled } from './hooks/useIsScreenReaderEnabled' /** * Returns text that should be displayed on the screen From 7fd793a1ec74d641e452ad082acdea636195901a Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Tue, 22 Oct 2024 10:41:42 -0700 Subject: [PATCH 12/19] Replace accessibilityValue with aria-valuetext for list positions --- .../src/components/Checkbox/Checkbox.tsx | 4 ++-- .../components/CheckboxGroup/CheckboxGroup.tsx | 2 +- .../SegmentedControl/SegmentedControl.tsx | 2 +- packages/components/src/types/forms.ts | 4 +--- .../src/utils/hooks/useA11yListPosition.ts | 18 ++++++------------ 5 files changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/components/src/components/Checkbox/Checkbox.tsx b/packages/components/src/components/Checkbox/Checkbox.tsx index 0e0123bd..586ea70b 100644 --- a/packages/components/src/components/Checkbox/Checkbox.tsx +++ b/packages/components/src/components/Checkbox/Checkbox.tsx @@ -31,7 +31,7 @@ export type CheckboxProps = FormElementProps & } export const Checkbox: FC = ({ - accessibilityValue, + a11yListPosition, checked, label, description, @@ -131,8 +131,8 @@ export const Checkbox: FC = ({ {_icon} diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index 1894b58d..4ac96bdb 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -146,7 +146,7 @@ export const CheckboxGroup: FC = ({ handleCheckboxChange(value)} diff --git a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx index b55f5574..32c168e2 100644 --- a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx +++ b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx @@ -107,8 +107,8 @@ export const SegmentedControl: FC = ({ key={index} widthPct={`${100 / labels.length}%`} aria-label={accessibilityLabel} + aria-valuetext={useA11yListPosition(index, labels.length)} accessibilityHint={a11yHints ? a11yHints[index] : ''} - accessibilityValue={useA11yListPosition(index, labels.length)} role={'tab'} accessibilityState={{ selected: isSelected }} style={PressableOpacityStyle()} diff --git a/packages/components/src/types/forms.ts b/packages/components/src/types/forms.ts index 795f21e2..03a7cd39 100644 --- a/packages/components/src/types/forms.ts +++ b/packages/components/src/types/forms.ts @@ -1,5 +1,3 @@ -import { AccessibilityValue } from 'react-native' - import { StringOrTextWithA11y } from './common' /** @@ -27,7 +25,7 @@ export type CheckboxRadioProps = { /** OnPress logic to alter `checked` state or other behavior associated with the checkbox */ onPress: () => void /** Textual description of position within list of checkboxes */ - accessibilityValue?: AccessibilityValue + a11yListPosition?: string /** Description that appears below label */ description?: StringOrTextWithA11y /** True to apply tile styling */ diff --git a/packages/components/src/utils/hooks/useA11yListPosition.ts b/packages/components/src/utils/hooks/useA11yListPosition.ts index 89aafaa6..20abefb1 100644 --- a/packages/components/src/utils/hooks/useA11yListPosition.ts +++ b/packages/components/src/utils/hooks/useA11yListPosition.ts @@ -1,17 +1,11 @@ -import { AccessibilityValue } from 'react-native' import { useTranslation } from 'react-i18next' -/** Returns list position used for accessibilityValues */ -export function useA11yListPosition( - index: number, - total: number, -): AccessibilityValue { +/** Returns list position used for a11yListPositions */ +export function useA11yListPosition(index: number, total: number): string { const { t } = useTranslation() - return { - text: t('listPosition', { - position: index + 1, - total, - }), - } + return t('listPosition', { + position: index + 1, + total, + }) } From dac9614bcb3c9f82c2ce4271268bea8875e70d7e Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Tue, 22 Oct 2024 11:34:18 -0700 Subject: [PATCH 13/19] Delete useA11yListPosition hook and revert to using translation directly --- .../components/CheckboxGroup/CheckboxGroup.tsx | 10 ++++++++-- .../SegmentedControl/SegmentedControl.tsx | 15 +++++++++------ packages/components/src/utils/accessibility.ts | 1 - .../src/utils/hooks/useA11yListPosition.ts | 11 ----------- 4 files changed, 17 insertions(+), 20 deletions(-) delete mode 100644 packages/components/src/utils/hooks/useA11yListPosition.ts diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index 4ac96bdb..ed081054 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -11,7 +11,8 @@ import { TextWithA11y, } from '../../types' import { Spacer } from '../Spacer/Spacer' -import { useA11yListPosition, useTheme } from '../../utils' +import { useTheme } from '../../utils' +import { useTranslation } from 'react-i18next' type TextWithA11yAndValue = TextWithA11y & { /** Description for checkbox item */ @@ -99,6 +100,7 @@ export const CheckboxGroup: FC = ({ tile, }) => { const theme = useTheme() + const { t } = useTranslation() const handleCheckboxChange = (value: string | number) => { if (selectedItems.includes(value)) { @@ -141,12 +143,16 @@ export const CheckboxGroup: FC = ({ {items.map((item, index) => { const isObject = typeof item === 'object' const value = isObject ? item.value || item.text : item + const a11yListPosition = t('listPosition', { + position: index + 1, + total: items.length, + }) return ( handleCheckboxChange(value)} diff --git a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx index 32c168e2..5eee07d2 100644 --- a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx +++ b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx @@ -4,11 +4,8 @@ import React, { FC, useEffect } from 'react' import styled from 'styled-components/native' import { ComponentWrapper } from '../../wrapper' -import { - PressableOpacityStyle, - useA11yListPosition, - useTheme, -} from '../../utils' +import { PressableOpacityStyle, useTheme } from '../../utils' +import { useTranslation } from 'react-i18next' /** * Props for {@link SegmentedControl} @@ -56,6 +53,7 @@ export const SegmentedControl: FC = ({ testIDs, }) => { const theme = useTheme() + const { t } = useTranslation() useEffect(() => { onChange(selected) @@ -99,6 +97,11 @@ export const SegmentedControl: FC = ({ textAlign: 'center', } + const a11yListPosition = t('listPosition', { + position: index + 1, + total: labels.length, + }) + return ( onChange(index)} @@ -107,7 +110,7 @@ export const SegmentedControl: FC = ({ key={index} widthPct={`${100 / labels.length}%`} aria-label={accessibilityLabel} - aria-valuetext={useA11yListPosition(index, labels.length)} + aria-valuetext={a11yListPosition} accessibilityHint={a11yHints ? a11yHints[index] : ''} role={'tab'} accessibilityState={{ selected: isSelected }} diff --git a/packages/components/src/utils/accessibility.ts b/packages/components/src/utils/accessibility.ts index a98c0d6b..3d056711 100644 --- a/packages/components/src/utils/accessibility.ts +++ b/packages/components/src/utils/accessibility.ts @@ -1,7 +1,6 @@ import { StringOrTextWithA11y } from '../types' // Export related hooks -export { useA11yListPosition } from './hooks/useA11yListPosition' export { useIsScreenReaderEnabled } from './hooks/useIsScreenReaderEnabled' /** diff --git a/packages/components/src/utils/hooks/useA11yListPosition.ts b/packages/components/src/utils/hooks/useA11yListPosition.ts deleted file mode 100644 index 20abefb1..00000000 --- a/packages/components/src/utils/hooks/useA11yListPosition.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useTranslation } from 'react-i18next' - -/** Returns list position used for a11yListPositions */ -export function useA11yListPosition(index: number, total: number): string { - const { t } = useTranslation() - - return t('listPosition', { - position: index + 1, - total, - }) -} From 4091982c53113a789ef17215baa90590988c2a4f Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Tue, 22 Oct 2024 11:43:12 -0700 Subject: [PATCH 14/19] Add heading and accessibilityValue tests --- .../components/CheckboxGroup/CheckboxGroup.test.tsx | 11 +++++++++++ .../src/components/shared/FormText.test.tsx | 1 + 2 files changed, 12 insertions(+) diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.test.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.test.tsx index 00c85340..7498090f 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.test.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.test.tsx @@ -104,6 +104,17 @@ describe('CheckboxGroup', () => { expect(checkboxes[4].props.accessibilityState.checked).toBe(false) expect(checkboxes[5].props.accessibilityState.checked).toBe(false) }) + + it('should have accessibilityValues on checkboxes', () => { + render() + const checkboxes = screen.queryAllByRole('checkbox') + expect(checkboxes[0].props.accessibilityValue.text).toBe('1 of 6') + expect(checkboxes[1].props.accessibilityValue.text).toBe('2 of 6') + expect(checkboxes[2].props.accessibilityValue.text).toBe('3 of 6') + expect(checkboxes[3].props.accessibilityValue.text).toBe('4 of 6') + expect(checkboxes[4].props.accessibilityValue.text).toBe('5 of 6') + expect(checkboxes[5].props.accessibilityValue.text).toBe('6 of 6') + }) }) describe('onPress behavior', () => { diff --git a/packages/components/src/components/shared/FormText.test.tsx b/packages/components/src/components/shared/FormText.test.tsx index 40d45b76..08d407fd 100644 --- a/packages/components/src/components/shared/FormText.test.tsx +++ b/packages/components/src/components/shared/FormText.test.tsx @@ -36,6 +36,7 @@ describe('Form Text', () => { it('should render text object and a11y label', () => { render(
) expect(screen.root).toHaveTextContent('Header text object') + expect(screen.getByRole('heading')).toBeOnTheScreen() expect(screen.getByLabelText('Header a11y')).toBeOnTheScreen() }) From 4d5334d5629ff5c334d00e67d562770192833d9a Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Wed, 23 Oct 2024 10:11:08 -0700 Subject: [PATCH 15/19] Consolidate a11y label on checkbox pressable --- .../components/src/components/Checkbox/Checkbox.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/components/src/components/Checkbox/Checkbox.tsx b/packages/components/src/components/Checkbox/Checkbox.tsx index 586ea70b..44e884b2 100644 --- a/packages/components/src/components/Checkbox/Checkbox.tsx +++ b/packages/components/src/components/Checkbox/Checkbox.tsx @@ -20,7 +20,8 @@ import { } from '../shared/FormText' import { Icon, IconProps } from '../Icon/Icon' import { Spacer } from '../Spacer/Spacer' -import { useTheme } from '../../utils' +import { getA11yLabel, useTheme } from '../../utils' +import { useTranslation } from 'react-i18next' export type CheckboxProps = FormElementProps & CheckboxRadioProps & { @@ -45,6 +46,7 @@ export const Checkbox: FC = ({ tile, }) => { const theme = useTheme() + const { t } = useTranslation() const fontScale = useWindowDimensions().fontScale /** @@ -116,6 +118,11 @@ export const Checkbox: FC = ({ ) + const a11yLabel = + getA11yLabel(label) + + (required ? ', ' + t('required') : '') + + (description ? `, ${getA11yLabel(description)}` : '') + return ( @@ -133,6 +140,7 @@ export const Checkbox: FC = ({ style={tile ? tileStyle : pressableBaseStyle} aria-checked={indeterminate ? 'mixed' : checked} aria-valuetext={a11yListPosition} + aria-label={a11yLabel} role="checkbox"> {_icon} From 5511b53fd6ca51afe6204d0fd8e2b0513816d16b Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Thu, 24 Oct 2024 11:23:44 -0700 Subject: [PATCH 16/19] Remove aria-labels from label and description --- .../components/src/components/shared/FormText.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/components/src/components/shared/FormText.tsx b/packages/components/src/components/shared/FormText.tsx index 60730b1e..75dbe933 100644 --- a/packages/components/src/components/shared/FormText.tsx +++ b/packages/components/src/components/shared/FormText.tsx @@ -145,12 +145,8 @@ export const Label: FC = ({ text, error, required }) => { color: theme.vadsColorForegroundError, } - const ariaLabel = required - ? getA11yLabel(text) + ', ' + t('required') - : getA11yLabel(text) - return ( - + {getDisplayText(text)} {required && {` (*${t('required')})`}} @@ -170,9 +166,5 @@ export const Description: FC = ({ text }) => { color: theme.vadsColorForegroundDefault, } - return ( - - {getDisplayText(text)} - - ) + return {getDisplayText(text)} } From 5e53cdff4da93dcb40fc72ba8e0f8030705f0156 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Thu, 24 Oct 2024 11:24:00 -0700 Subject: [PATCH 17/19] Add comment regarding combined a11yLabel on Pressable --- packages/components/src/components/Checkbox/Checkbox.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/components/src/components/Checkbox/Checkbox.tsx b/packages/components/src/components/Checkbox/Checkbox.tsx index 44e884b2..7c9d2ee7 100644 --- a/packages/components/src/components/Checkbox/Checkbox.tsx +++ b/packages/components/src/components/Checkbox/Checkbox.tsx @@ -118,6 +118,9 @@ export const Checkbox: FC = ({ ) + /** + * Combined a11yLabel on Pressable required for Android Talkback + */ const a11yLabel = getA11yLabel(label) + (required ? ', ' + t('required') : '') + From 24d40842c87c6ab8633b20d21661356b44263ce2 Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Sun, 27 Oct 2024 21:24:37 -0700 Subject: [PATCH 18/19] Fixed import sorts. Regroup accessibility variables in SegmentedControl --- .../components/src/components/Checkbox/Checkbox.tsx | 2 +- .../src/components/CheckboxGroup/CheckboxGroup.tsx | 2 +- .../components/SegmentedControl/SegmentedControl.tsx | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/components/src/components/Checkbox/Checkbox.tsx b/packages/components/src/components/Checkbox/Checkbox.tsx index 7c9d2ee7..a0d74383 100644 --- a/packages/components/src/components/Checkbox/Checkbox.tsx +++ b/packages/components/src/components/Checkbox/Checkbox.tsx @@ -6,6 +6,7 @@ import { useWindowDimensions, } from 'react-native' import { spacing } from '@department-of-veterans-affairs/mobile-tokens' +import { useTranslation } from 'react-i18next' import React, { FC } from 'react' import { CheckboxRadioProps, FormElementProps } from '../../types/forms' @@ -21,7 +22,6 @@ import { import { Icon, IconProps } from '../Icon/Icon' import { Spacer } from '../Spacer/Spacer' import { getA11yLabel, useTheme } from '../../utils' -import { useTranslation } from 'react-i18next' export type CheckboxProps = FormElementProps & CheckboxRadioProps & { diff --git a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx index ed081054..68eb6326 100644 --- a/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx +++ b/packages/components/src/components/CheckboxGroup/CheckboxGroup.tsx @@ -1,5 +1,6 @@ import { View, ViewStyle } from 'react-native' import { spacing } from '@department-of-veterans-affairs/mobile-tokens' +import { useTranslation } from 'react-i18next' import React, { FC, Fragment } from 'react' import { Checkbox } from '../Checkbox/Checkbox' @@ -12,7 +13,6 @@ import { } from '../../types' import { Spacer } from '../Spacer/Spacer' import { useTheme } from '../../utils' -import { useTranslation } from 'react-i18next' type TextWithA11yAndValue = TextWithA11y & { /** Description for checkbox item */ diff --git a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx index 5eee07d2..908b7e45 100644 --- a/packages/components/src/components/SegmentedControl/SegmentedControl.tsx +++ b/packages/components/src/components/SegmentedControl/SegmentedControl.tsx @@ -1,11 +1,11 @@ import { Pressable, Text, TextStyle, View, ViewStyle } from 'react-native' import { spacing } from '@department-of-veterans-affairs/mobile-tokens' +import { useTranslation } from 'react-i18next' import React, { FC, useEffect } from 'react' import styled from 'styled-components/native' import { ComponentWrapper } from '../../wrapper' import { PressableOpacityStyle, useTheme } from '../../utils' -import { useTranslation } from 'react-i18next' /** * Props for {@link SegmentedControl} @@ -84,6 +84,11 @@ export const SegmentedControl: FC = ({ ? a11yLabels[index] || labels[index] : labels[index] + const a11yListPosition = t('listPosition', { + position: index + 1, + total: labels.length, + }) + // TODO: Replace with typography tokens const font: TextStyle = { fontFamily: isSelected ? 'SourceSansPro-Bold' : 'SourceSansPro-Regular', @@ -97,11 +102,6 @@ export const SegmentedControl: FC = ({ textAlign: 'center', } - const a11yListPosition = t('listPosition', { - position: index + 1, - total: labels.length, - }) - return ( onChange(index)} From d3a44d0b56451994d3b4831392912ac51093503d Mon Sep 17 00:00:00 2001 From: Narin Ratana Date: Sun, 27 Oct 2024 21:41:36 -0700 Subject: [PATCH 19/19] Fix tests for combined a11y label --- .../src/components/Checkbox/Checkbox.test.tsx | 49 +++++++++++++++++++ .../src/components/shared/FormText.test.tsx | 10 +--- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/packages/components/src/components/Checkbox/Checkbox.test.tsx b/packages/components/src/components/Checkbox/Checkbox.test.tsx index 411bee0d..993597e6 100644 --- a/packages/components/src/components/Checkbox/Checkbox.test.tsx +++ b/packages/components/src/components/Checkbox/Checkbox.test.tsx @@ -25,6 +25,12 @@ describe('Checkbox', () => { onPress: onPressSpy, } + const labelObject = { text: 'Label text object', a11yLabel: 'Label a11y' } + const descriptionObject = { + text: 'Description text object', + a11yLabel: 'Description a11y', + } + const errorMsg = 'Error text' describe('Basic tests', () => { @@ -75,6 +81,49 @@ describe('Checkbox', () => { }) }) + describe('Accessibility', () => { + it('should have a11y labels when label and description are strings', () => { + render() + expect( + screen.getByLabelText('Label text, Description text'), + ).toBeOnTheScreen() + }) + + it('should include required in a11y label', () => { + render() + expect( + screen.getByLabelText('Label text, Required, Description text'), + ).toBeOnTheScreen() + }) + + it('should have a11y labels when label and description are TextWithA11y objects', () => { + render( + , + ) + expect( + screen.getByLabelText('Label a11y, Required, Description a11y'), + ).toBeOnTheScreen() + }) + + it('should have a11y labels when label and description are objects without a11y', () => { + render( + , + ) + expect( + screen.getByLabelText('Label without a11y, Description without a11y'), + ).toBeOnTheScreen() + }) + }) + describe('Styling', () => { describe('Light mode', () => { it('icon color (unchecked)', async () => { diff --git a/packages/components/src/components/shared/FormText.test.tsx b/packages/components/src/components/shared/FormText.test.tsx index 08d407fd..f048f679 100644 --- a/packages/components/src/components/shared/FormText.test.tsx +++ b/packages/components/src/components/shared/FormText.test.tsx @@ -154,10 +154,9 @@ describe('Form Text', () => { }) describe('Label', () => { - it('should render text and a11y label', () => { + it('should render text', () => { render(