diff --git a/apps/fluent-tester/src/TestComponents/Callout/CalloutTest.tsx b/apps/fluent-tester/src/TestComponents/Callout/CalloutTest.tsx index 2bbdc65960..fb7ec08c30 100644 --- a/apps/fluent-tester/src/TestComponents/Callout/CalloutTest.tsx +++ b/apps/fluent-tester/src/TestComponents/Callout/CalloutTest.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import type { KeyboardMetrics } from 'react-native'; -import { Text, View, Switch, ScrollView, Platform } from 'react-native'; +import { AccessibilityInfo, Platform, ScrollView, Switch, Text, View } from 'react-native'; import { ButtonV1 as Button, Separator, Pressable } from '@fluentui/react-native'; import type { IFocusable, RestoreFocusEvent, DismissBehaviors } from '@fluentui/react-native'; @@ -423,19 +423,45 @@ const StandardCallout: React.FunctionComponent = () => { const CustomCallout: React.FunctionComponent = () => { const [showCustomizedCallout, setShowCustomizedCallout] = React.useState(false); const [isCustomizedCalloutVisible, setIsCustomizedCalloutVisible] = React.useState(false); + const [shouldFocusCloseButton, setShouldFocusCloseButton] = React.useState(false); - const toggleShowCustomizedCallout = React.useCallback(() => { - setShowCustomizedCallout(!showCustomizedCallout); + const a11yOnShowAnnouncement = 'Be informed that a customized callout has been opened.'; + const calloutMessage = 'This is an example message.'; + const closeButtonRef = React.useRef(null); + + const openCallout = React.useCallback((focusCloseButton: boolean) => { + setShowCustomizedCallout(true); // Unmounting a callout does not invoke onDismiss; onDismiss is only invoked // for dismissals generated by the native app. When toggling to 'show', // the isVisible state will be corrected to 'true' by the onShow callback. setIsCustomizedCalloutVisible(false); - }, [showCustomizedCallout, setIsCustomizedCalloutVisible, setShowCustomizedCallout]); + setShouldFocusCloseButton(focusCloseButton); + }, []); + + const onClick = React.useCallback(() => { + openCallout(false /*focusCloseButton*/); + }, [openCallout]); + + const onKeyDown = React.useCallback( + (e) => { + if (e.nativeEvent.key === 'Enter' || e.nativeEvent.key === ' ') { + openCallout(true /*focusCloseButton*/); + } + }, + [openCallout], + ); + + React.useEffect(() => { + if (isCustomizedCalloutVisible && shouldFocusCloseButton) { + closeButtonRef.current?.focus?.(); + } + }, [isCustomizedCalloutVisible, shouldFocusCloseButton]); const onShowCustomizedCallout = React.useCallback(() => { setIsCustomizedCalloutVisible(true); - }, [setIsCustomizedCalloutVisible]); + AccessibilityInfo.announceForAccessibility(a11yOnShowAnnouncement + ' ' + calloutMessage); + }, []); const onDismissCustomizedCallout = React.useCallback(() => { setIsCustomizedCalloutVisible(false); @@ -443,14 +469,17 @@ const CustomCallout: React.FunctionComponent = () => { // setting the internal state to false will instigate unmounting the // zombie Callout control. setShowCustomizedCallout(false); - }, [setIsCustomizedCalloutVisible, setShowCustomizedCallout]); + setShouldFocusCloseButton(false); + }, []); const myRect: KeyboardMetrics = { screenX: 10, screenY: 10, width: 100, height: 100 }; return ( - + Visibility: {isCustomizedCalloutVisible ? Visible : Not Visible} @@ -464,10 +493,12 @@ const CustomCallout: React.FunctionComponent = () => { onShow={onShowCustomizedCallout} accessibilityLabel="Customized Callout" accessibilityRole="alert" - accessibilityOnShowAnnouncement="Be informed that a customized callout has been opened." > - just some text so it does not take focus and is not empty. + {calloutMessage} + )} diff --git a/change/@fluentui-react-native-tester-78450674-4274-4fca-a61c-3ae7e8f4d826.json b/change/@fluentui-react-native-tester-78450674-4274-4fca-a61c-3ae7e8f4d826.json new file mode 100644 index 0000000000..f67bf53dc7 --- /dev/null +++ b/change/@fluentui-react-native-tester-78450674-4274-4fca-a61c-3ae7e8f4d826.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "Fix custom callout example", + "packageName": "@fluentui-react-native/tester", + "email": "krsiler@microsoft.com", + "dependentChangeType": "patch" +}