-
Notifications
You must be signed in to change notification settings - Fork 4k
[A11y] Add Save button to Report settings single-select pages (Write capability, Notification preference, Visibility) #94557
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d85eb1a
7e12cbc
0f72407
d0544b4
c555d0b
3f2079c
8fe8710
a3649dd
d9fed54
d8abb61
d347e01
bae8c56
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,7 +23,7 @@ import CONST from '@src/CONST'; | |||||||
| import {DYNAMIC_ROUTES} from '@src/ROUTES'; | ||||||||
| import type {RoomVisibility} from '@src/types/onyx/Report'; | ||||||||
|
|
||||||||
| import React, {useMemo} from 'react'; | ||||||||
| import React, {useMemo, useState} from 'react'; | ||||||||
|
|
||||||||
| type DynamicVisibilityProps = WithReportOrNotFoundProps; | ||||||||
|
|
||||||||
|
|
@@ -35,6 +35,9 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { | |||||||
|
|
||||||||
| const {showConfirmModal} = useConfirmModal(); | ||||||||
|
|
||||||||
| const [draftVisibility, setDraftVisibility] = useState<RoomVisibility | undefined>(undefined); | ||||||||
| const selectedVisibility = draftVisibility ?? report?.visibility; | ||||||||
|
|
||||||||
| const visibilityOptions = useMemo( | ||||||||
| () => | ||||||||
| Object.values(CONST.REPORT.VISIBILITY) | ||||||||
|
|
@@ -44,36 +47,43 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { | |||||||
| value: visibilityOption, | ||||||||
| alternateText: translate(`newRoomPage.${visibilityOption}Description`), | ||||||||
| keyForList: visibilityOption, | ||||||||
| isSelected: visibilityOption === report?.visibility, | ||||||||
| isSelected: visibilityOption === selectedVisibility, | ||||||||
| })), | ||||||||
| [translate, report?.visibility], | ||||||||
| [translate, selectedVisibility], | ||||||||
| ); | ||||||||
|
|
||||||||
| const goBack = () => { | ||||||||
| Navigation.goBack(backPath); | ||||||||
| }; | ||||||||
|
|
||||||||
| const changeVisibility = (newVisibility: RoomVisibility) => { | ||||||||
| if (!report) { | ||||||||
| const saveVisibility = async () => { | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Public confirmation modal triggers when nothing changed If a room is already public and the user taps Save without selecting a different option,
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed by the same change in d8abb61. Save is now disabled while |
||||||||
| if (!report || !selectedVisibility) { | ||||||||
| return; | ||||||||
| } | ||||||||
| updateRoomVisibility(report.reportID, report.visibility, newVisibility); | ||||||||
|
|
||||||||
| // Selecting Public is a sensitive change, so it still has to be confirmed before we persist it. | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 0f72407 — added the blank line before the comment. |
||||||||
| if (selectedVisibility === CONST.REPORT.VISIBILITY.PUBLIC) { | ||||||||
| const result = await showConfirmModal({ | ||||||||
| title: translate('common.areYouSure'), | ||||||||
| prompt: translate('newRoomPage.publicDescription'), | ||||||||
| confirmText: translate('common.yes'), | ||||||||
| cancelText: translate('common.no'), | ||||||||
| shouldShowCancelButton: true, | ||||||||
| danger: true, | ||||||||
| }); | ||||||||
| if (result.action !== ModalActions.CONFIRM) { | ||||||||
| return; | ||||||||
| } | ||||||||
| } | ||||||||
| updateRoomVisibility(report.reportID, report.visibility, selectedVisibility); | ||||||||
| setNavigationActionToMicrotaskQueue(goBack); | ||||||||
| }; | ||||||||
|
|
||||||||
| const showPublicVisibilityModal = async () => { | ||||||||
| const result = await showConfirmModal({ | ||||||||
| title: translate('common.areYouSure'), | ||||||||
| prompt: translate('newRoomPage.publicDescription'), | ||||||||
| confirmText: translate('common.yes'), | ||||||||
| cancelText: translate('common.no'), | ||||||||
| shouldShowCancelButton: true, | ||||||||
| danger: true, | ||||||||
| }); | ||||||||
| if (result.action !== ModalActions.CONFIRM) { | ||||||||
| return; | ||||||||
| } | ||||||||
| changeVisibility(CONST.REPORT.VISIBILITY.PUBLIC); | ||||||||
| const confirmButtonOptions = { | ||||||||
| showButton: true, | ||||||||
| text: translate('common.save'), | ||||||||
| onConfirm: saveVisibility, | ||||||||
| isDisabled: selectedVisibility === report?.visibility, | ||||||||
| }; | ||||||||
|
|
||||||||
| return ( | ||||||||
|
|
@@ -89,16 +99,12 @@ function DynamicVisibilityPage({report}: DynamicVisibilityProps) { | |||||||
| <SelectionList | ||||||||
| shouldPreventDefaultFocusOnSelectRow | ||||||||
| data={visibilityOptions} | ||||||||
| onSelectRow={(option) => { | ||||||||
| if (option.value === CONST.REPORT.VISIBILITY.PUBLIC) { | ||||||||
| showPublicVisibilityModal(); | ||||||||
| return; | ||||||||
| } | ||||||||
| changeVisibility(option.value); | ||||||||
| }} | ||||||||
| onSelectRow={(option) => setDraftVisibility(option.value)} | ||||||||
| confirmButtonOptions={confirmButtonOptions} | ||||||||
| shouldSingleExecuteRowSelect | ||||||||
| initiallyFocusedItemKey={visibilityOptions.find((visibility) => visibility.isSelected)?.keyForList} | ||||||||
| initiallyFocusedItemKey={report?.visibility} | ||||||||
| ListItem={SingleSelectListItem} | ||||||||
| addBottomSafeAreaPadding | ||||||||
| /> | ||||||||
| </FullPageNotFoundView> | ||||||||
| </ScreenWrapper> | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,37 +17,43 @@ import type {WithReportOrNotFoundProps} from '@pages/inbox/report/withReportOrNo | |
|
|
||
| import CONST from '@src/CONST'; | ||
| import {DYNAMIC_ROUTES} from '@src/ROUTES'; | ||
| import type {WriteCapability} from '@src/types/onyx/Report'; | ||
|
|
||
| import type {ValueOf} from 'type-fest'; | ||
|
|
||
| import React, {useCallback} from 'react'; | ||
| import React, {useState} from 'react'; | ||
|
|
||
| type DynamicWriteCapabilityPageProps = WithReportOrNotFoundProps; | ||
|
|
||
| function DynamicWriteCapabilityPage({report, policy}: DynamicWriteCapabilityPageProps) { | ||
| const backPath = useDynamicBackPath(DYNAMIC_ROUTES.REPORT_SETTINGS_WRITE_CAPABILITY.path); | ||
| const {translate} = useLocalize(); | ||
| const currentWriteCapability = report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL; | ||
|
|
||
| const [draftWriteCapability, setDraftWriteCapability] = useState<WriteCapability | undefined>(undefined); | ||
| const selectedWriteCapability = draftWriteCapability ?? currentWriteCapability; | ||
| const writeCapabilityOptions = Object.values(CONST.REPORT.WRITE_CAPABILITIES).map((value) => ({ | ||
| value, | ||
| text: translate(`writeCapabilityPage.writeCapability.${value}`), | ||
| keyForList: value, | ||
| isSelected: value === (report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL), | ||
| isSelected: value === selectedWriteCapability, | ||
| })); | ||
| const selectedOptionKey = writeCapabilityOptions.find((locale) => locale.isSelected)?.keyForList; | ||
| const isReportArchived = useReportIsArchived(report.reportID); | ||
| const isAbleToEdit = canEditWriteCapability(report, policy, isReportArchived); | ||
|
|
||
| const goBack = () => { | ||
| Navigation.goBack(backPath); | ||
| }; | ||
|
|
||
| const updateWriteCapability = useCallback( | ||
| (newValue: ValueOf<typeof CONST.REPORT.WRITE_CAPABILITIES>) => { | ||
| updateWriteCapabilityUtil(report, newValue); | ||
| goBack(); | ||
| }, | ||
| [report, goBack], | ||
| ); | ||
| const saveWriteCapability = () => { | ||
| updateWriteCapabilityUtil(report, selectedWriteCapability); | ||
| goBack(); | ||
| }; | ||
|
|
||
| const confirmButtonOptions = { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Save enabled with no changes All three pages let the user tap Save without selecting a different option, which fires the update API with old === new. Would it make sense to disable the button while
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call — done in d8abb61. All three Save buttons are now disabled whenever the selected value equals the current live value, i.e.
|
||
| showButton: true, | ||
| text: translate('common.save'), | ||
| onConfirm: saveWriteCapability, | ||
| isDisabled: selectedWriteCapability === currentWriteCapability, | ||
| }; | ||
|
|
||
| return ( | ||
| <ScreenWrapper | ||
|
|
@@ -63,9 +69,11 @@ function DynamicWriteCapabilityPage({report, policy}: DynamicWriteCapabilityPage | |
| <SelectionList | ||
| data={writeCapabilityOptions} | ||
| ListItem={SingleSelectListItem} | ||
| onSelectRow={(option) => updateWriteCapability(option.value)} | ||
| onSelectRow={(option) => setDraftWriteCapability(option.value)} | ||
| confirmButtonOptions={confirmButtonOptions} | ||
| shouldSingleExecuteRowSelect | ||
| initiallyFocusedItemKey={selectedOptionKey} | ||
| initiallyFocusedItemKey={currentWriteCapability} | ||
| addBottomSafeAreaPadding | ||
| /> | ||
| </FullPageNotFoundView> | ||
| </ScreenWrapper> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On devices with a bottom gesture/home-indicator inset, this new fixed Save footer receives only its regular
pb5spacing because the surroundingScreenWrapperexplicitly disables bottom safe-area padding and theSelectionListdoes not setaddBottomSafeAreaPadding. The same combination occurs in all three changed pages, so their Save buttons can intrude into the unsafe area; passaddBottomSafeAreaPaddingto each list or let the wrapper provide the inset.Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MelvinBot it is a valid concern. Please resolve it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed — fixed in 8fe8710. Each of the three pages sets
includeSafeAreaPaddingBottom={false}on itsScreenWrapper, so theSelectionList's fixed Save footer (FixedFooter) was getting only its basepb5spacing and no bottom inset. PassingaddBottomSafeAreaPaddingto each list makesFixedFooteradd the device bottom inset (viauseBottomSafeSafeAreaPaddingStyle→ rawuseSafeAreaPaddings), so the Save button no longer intrudes into the home-indicator/gesture area. Because the wrapper's bottom padding is disabled, there's no double-count, and since a footer is present the list's own container padding is skipped — the inset lands exactly on the footer. This matches the established pattern (e.g.CardTypeStep).DynamicNotificationPreferencePage.tsx:84DynamicWriteCapabilityPage.tsx:77DynamicVisibilityPage.tsx:108