Skip to content

Commit f082c77

Browse files
camchenrypksjce
andauthored
Allow changing initially focused button in ConfirmationDialog (#6843)
Co-authored-by: Pavithra Kodmad <[email protected]>
1 parent 59ee3f0 commit f082c77

File tree

5 files changed

+41
-3
lines changed

5 files changed

+41
-3
lines changed

.changeset/wet-terms-argue.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': minor
3+
---
4+
5+
Allow changing initially focused button in ConfirmationDialog

packages/react/src/ConfirmationDialog/ConfirmationDialog.docs.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@
3737
"defaultValue": "normal",
3838
"description": "The type of button to use for the confirm button."
3939
},
40+
{
41+
"name": "overrideButtonFocus",
42+
"type": "'confirm' | 'cancel'",
43+
"description": "The button that should be initially focused when the dialog is opened. By default, the initial button focus is the confirm button, unless the confirm button is dangerous, in which case the cancel button is focused. This prop should be used rarely, as it can allow dangerous actions to be taken accidentally."
44+
},
4045
{
4146
"name": "className",
4247
"type": "string",

packages/react/src/ConfirmationDialog/ConfirmationDialog.test.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import theme from '../theme'
1111
import {ThemeProvider} from '../ThemeProvider'
1212
import {Stack} from '../Stack'
1313

14-
const Basic = ({confirmButtonType}: Pick<React.ComponentProps<typeof ConfirmationDialog>, 'confirmButtonType'>) => {
14+
const Basic = ({
15+
confirmButtonType,
16+
overrideButtonFocus,
17+
}: Pick<React.ComponentProps<typeof ConfirmationDialog>, 'confirmButtonType' | 'overrideButtonFocus'>) => {
1518
const [isOpen, setIsOpen] = useState(false)
1619
const buttonRef = useRef<HTMLButtonElement>(null)
1720
const onDialogClose = useCallback(() => setIsOpen(false), [])
@@ -28,6 +31,7 @@ const Basic = ({confirmButtonType}: Pick<React.ComponentProps<typeof Confirmatio
2831
cancelButtonContent="Secondary"
2932
confirmButtonContent="Primary"
3033
confirmButtonType={confirmButtonType}
34+
overrideButtonFocus={overrideButtonFocus}
3135
>
3236
Lorem ipsum dolor sit Pippin good dog.
3337
</ConfirmationDialog>
@@ -187,6 +191,15 @@ describe('ConfirmationDialog', () => {
187191
expect(dialog.getAttribute('data-height')).toBe('small')
188192
})
189193

194+
it('focuses the confirm button even when dangerous if initialButtonFocus is confirm', async () => {
195+
const {getByText, getByRole} = render(<Basic confirmButtonType="danger" overrideButtonFocus="confirm" />)
196+
197+
fireEvent.click(getByText('Show dialog'))
198+
199+
expect(getByRole('button', {name: 'Primary'})).toEqual(document.activeElement)
200+
expect(getByRole('button', {name: 'Secondary'})).not.toEqual(document.activeElement)
201+
})
202+
190203
describe('loading states', () => {
191204
it('applies loading state to confirm button when confirmButtonLoading is true', async () => {
192205
const {getByText, getByRole} = render(<LoadingStates confirmButtonLoading={true} />)

packages/react/src/ConfirmationDialog/ConfirmationDialog.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ export interface ConfirmationDialogProps {
5151
*/
5252
confirmButtonLoading?: boolean
5353

54+
/**
55+
* Overrides the button that should be initially focused when the dialog is opened. By default, the confirm button
56+
* is focused initially unless it is a dangerous action, in which case the cancel button is focused. This should
57+
* rarely be overridden, in order to ensure that the user does not accidentally confirm a dangerous action.
58+
*/
59+
overrideButtonFocus?: 'cancel' | 'confirm'
60+
5461
/**
5562
* Additional class names to apply to the dialog
5663
*/
@@ -125,6 +132,7 @@ export const ConfirmationDialog: React.FC<React.PropsWithChildren<ConfirmationDi
125132
className,
126133
width = 'medium',
127134
height,
135+
overrideButtonFocus,
128136
} = props
129137

130138
const onCancelButtonClick = useCallback(() => {
@@ -134,17 +142,19 @@ export const ConfirmationDialog: React.FC<React.PropsWithChildren<ConfirmationDi
134142
onClose('confirm')
135143
}, [onClose])
136144
const isConfirmationDangerous = confirmButtonType === 'danger'
145+
const buttonToFocus =
146+
overrideButtonFocus !== undefined ? overrideButtonFocus : isConfirmationDangerous ? 'cancel' : 'confirm'
137147
const cancelButton: DialogButtonProps = {
138148
content: cancelButtonContent,
139149
onClick: onCancelButtonClick,
140-
autoFocus: isConfirmationDangerous,
150+
autoFocus: buttonToFocus === 'cancel',
141151
loading: cancelButtonLoading,
142152
}
143153
const confirmButton: DialogButtonProps = {
144154
content: confirmButtonContent,
145155
buttonType: confirmButtonType,
146156
onClick: onConfirmButtonClick,
147-
autoFocus: !isConfirmationDangerous,
157+
autoFocus: buttonToFocus === 'confirm',
148158
loading: confirmButtonLoading,
149159
}
150160
const footerButtons = [cancelButton, confirmButton]

packages/react/src/ConfirmationDialog/useConfirm.hookDocs.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@
4646
"type": "\"normal\" | \"primary\" | \"danger\"",
4747
"defaultValue": "normal",
4848
"description": "The type of button to use for the confirm button."
49+
},
50+
{
51+
"name": "overrideButtonFocus",
52+
"type": "'confirm' | 'cancel'",
53+
"description": "The button that should be initially focused when the dialog is opened. By default, the initial button focus is the confirm button, unless the confirm button is dangerous, in which case the cancel button is focused. This prop should be used rarely, as it can allow dangerous actions to be taken accidentally."
4954
}
5055
]
5156
}

0 commit comments

Comments
 (0)