Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/wet-terms-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

Allow changing initially focused button in ConfirmationDialog
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@
"defaultValue": "normal",
"description": "The type of button to use for the confirm button."
},
{
"name": "overrideButtonFocus",
"type": "'confirm' | 'cancel'",
"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."
},
{
"name": "className",
"type": "string",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ import theme from '../theme'
import {ThemeProvider} from '../ThemeProvider'
import {Stack} from '../Stack'

const Basic = ({confirmButtonType}: Pick<React.ComponentProps<typeof ConfirmationDialog>, 'confirmButtonType'>) => {
const Basic = ({
confirmButtonType,
overrideButtonFocus,
}: Pick<React.ComponentProps<typeof ConfirmationDialog>, 'confirmButtonType' | 'overrideButtonFocus'>) => {
const [isOpen, setIsOpen] = useState(false)
const buttonRef = useRef<HTMLButtonElement>(null)
const onDialogClose = useCallback(() => setIsOpen(false), [])
Expand All @@ -28,6 +31,7 @@ const Basic = ({confirmButtonType}: Pick<React.ComponentProps<typeof Confirmatio
cancelButtonContent="Secondary"
confirmButtonContent="Primary"
confirmButtonType={confirmButtonType}
overrideButtonFocus={overrideButtonFocus}
>
Lorem ipsum dolor sit Pippin good dog.
</ConfirmationDialog>
Expand Down Expand Up @@ -187,6 +191,15 @@ describe('ConfirmationDialog', () => {
expect(dialog.getAttribute('data-height')).toBe('small')
})

it('focuses the confirm button even when dangerous if initialButtonFocus is confirm', async () => {
const {getByText, getByRole} = render(<Basic confirmButtonType="danger" overrideButtonFocus="confirm" />)

fireEvent.click(getByText('Show dialog'))

expect(getByRole('button', {name: 'Primary'})).toEqual(document.activeElement)
expect(getByRole('button', {name: 'Secondary'})).not.toEqual(document.activeElement)
})

describe('loading states', () => {
it('applies loading state to confirm button when confirmButtonLoading is true', async () => {
const {getByText, getByRole} = render(<LoadingStates confirmButtonLoading={true} />)
Expand Down
14 changes: 12 additions & 2 deletions packages/react/src/ConfirmationDialog/ConfirmationDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ export interface ConfirmationDialogProps {
*/
confirmButtonLoading?: boolean

/**
* Overrides the button that should be initially focused when the dialog is opened. By default, the confirm button
* is focused initially unless it is a dangerous action, in which case the cancel button is focused. This should
* rarely be overridden, in order to ensure that the user does not accidentally confirm a dangerous action.
*/
overrideButtonFocus?: 'cancel' | 'confirm'

/**
* Additional class names to apply to the dialog
*/
Expand Down Expand Up @@ -125,6 +132,7 @@ export const ConfirmationDialog: React.FC<React.PropsWithChildren<ConfirmationDi
className,
width = 'medium',
height,
overrideButtonFocus,
} = props

const onCancelButtonClick = useCallback(() => {
Expand All @@ -134,17 +142,19 @@ export const ConfirmationDialog: React.FC<React.PropsWithChildren<ConfirmationDi
onClose('confirm')
}, [onClose])
const isConfirmationDangerous = confirmButtonType === 'danger'
const buttonToFocus =
overrideButtonFocus !== undefined ? overrideButtonFocus : isConfirmationDangerous ? 'cancel' : 'confirm'
const cancelButton: DialogButtonProps = {
content: cancelButtonContent,
onClick: onCancelButtonClick,
autoFocus: isConfirmationDangerous,
autoFocus: buttonToFocus === 'cancel',
loading: cancelButtonLoading,
}
const confirmButton: DialogButtonProps = {
content: confirmButtonContent,
buttonType: confirmButtonType,
onClick: onConfirmButtonClick,
autoFocus: !isConfirmationDangerous,
autoFocus: buttonToFocus === 'confirm',
loading: confirmButtonLoading,
}
const footerButtons = [cancelButton, confirmButton]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@
"type": "\"normal\" | \"primary\" | \"danger\"",
"defaultValue": "normal",
"description": "The type of button to use for the confirm button."
},
{
"name": "overrideButtonFocus",
"type": "'confirm' | 'cancel'",
"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."
}
]
}
Expand Down
Loading