Files
Danny WhiteandJoshen Lim aef1d70351 chore(studio): standardise discard changes behaviour (#43201)
## What kind of change does this PR introduce?

UX consistency improvement. Updates DEPR-355.

## What is the current behavior?

Discard-confirm close behavioir is implemented inconsistently across
Studio forms:

- some sheets/dialogs used `useConfirmOnClose`
- some duplicated local `CloseConfirmationModal` components
- some (e.g. `CreateHookSheet`) closed unconditionally and could lose
unsaved changes

## What is the new behavior?

Extracts and validates a reusable discard-close pattern for
dialogs/sheets
- enhances `useConfirmOnClose` with `handleOpenChange(open)` for
`Dialog`/`Sheet` `onOpenChange`
- adds shared `DiscardChangesConfirmationDialog` (`AlertDialog`-based,
override-able copy)
- migrates:
	- `InviteMemberButton`
	- `CreateHookSheet`
	- `EditSecretSheet`

This standardizes close-guard behavior for
backdrop/escape/close-button/cancel-button flows without trying to block
route changes or arbitrary unmounts.

## Additional context

`CreateHookSheet` now also marks the generated secret action as dirty
(`setValue(..., { shouldDirty: true })`) so the discard guard behaves
correctly.

- Added tests for `useConfirmOnClose` covering:
	- clean vs dirty close
	- handleOpenChange(true|false)
	- confirm/cancel behavior
	- latest callback ref behavior

A follow-up PR is needed to migrate remaining duplicated
`CloseConfirmationModal` usages and older `useConfirmOnClose` call sites
to the shared `DiscardChangesConfirmationDialog` + `handleOpenChange`
pattern.

---------

Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
2026-03-05 11:32:39 +11:00

105 lines
5.2 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Modality
description: Present ephemeral information and demand action.
---
Modal elements interrupt the user’s current task to ask for input, a decision, or focused attention. They appear at the top of the visual stack and (by default) render everything beneath them inactive.
Given their highly interruptive nature, modal elements should be used sparingly. Common use cases include:
- Requiring confirmation from the user
- Requiring an ephemeral form submission from the user before an action can be completed
- Alerting or slowing the user down before a destructive action
We have two main ways of handling modality:
- [Dialogs](#dialogs)
- [Sheets](#sheets)
As a general rule: use dialogs for short, focused tasks and use sheets for longer forms or more detailed views.
### Dirty form dismissal pattern
When a dialog or sheet contains a form, users should generally be allowed to attempt dismissal via all normal affordances (backdrop click, Escape key, close icon, and footer `Cancel` button).
If the form is clean, close immediately. If the form has unsaved changes, show a discard-confirmation dialog instead of closing immediately.
This pattern is implemented in Studio with `useConfirmOnClose` plus `DiscardChangesConfirmationDialog`.
- **Prompt on footer `Cancel` too:** If a button is labeled `Cancel`, users expect it to stop the current action, not silently discard edits. Prompting keeps behavior consistent with backdrop/Escape dismissal and prevents accidental loss.
- **Use explicit labels for no-prompt discard:** If you intentionally want a one-click destructive exit, label the action `Discard` (or `Discard changes`) rather than `Cancel`.
- **Guard close attempts, not unmounts:** This pattern should intercept controlled modal/sheet close attempts (`onOpenChange`, close buttons, footer actions). It should not attempt to block route changes or arbitrary component unmounts.
## Dialogs
Dialogs are centered overlays used for short, focused tasks. All dialogs should follow these best practices:
- **Reiterative:** Dialog header and confirmation button text and should match the action and flow on from the entry point.
- **Simple:** No layered elements like subtitles or admonitions unless necessary. Put all the focus on the actions to get out of the dialog.
- **Accessible:** Always provide clear labels and descriptions via semantic HTML and the correct ARIA attributes. Ensure keyboard navigation works correctly.
### Components
There are quite a few dialog components, each suited to a different task or context:
- [Alert Dialog](../components/alert-dialog) contains a single, short paragraph and an explicit action.
- [Text Confirm Dialog](../fragments/text-confirm-dialog) requires a textual response before the action is enabled.
- [Confirmation Modal](../fragments/confirmation-modal) provides more flexible dialog body contents.
- [Dialog](../components/dialog) is a generalized component for bespoke purposes.
#### Alert Dialog
[Alert Dialog](../components/alert-dialog) is used to confirm or acknowledge a critical action with a single, short paragraph and a clear decision.
<ComponentPreview name="alert-dialog-demo" />
#### Discard changes confirmation dialog (pattern)
For dirty form dismissal, use a short discard-confirmation dialog after a close attempt instead of disabling dismissal entirely.
- The primary form remains in a `Dialog` or `Sheet` (dismissible).
- Closing is intercepted only when the form is dirty.
- The follow-up confirmation is an `AlertDialog` pattern (`DiscardChangesConfirmationDialog` in Studio).
Typical flow:
1. User attempts to close the dialog/sheet (backdrop, Escape, close icon, or `Cancel`)
2. If the form is clean, close immediately
3. If the form is dirty, show discard confirmation
4. `Keep editing` returns to the form
5. `Discard changes` closes and resets the form
#### Text Confirm Dialog
[Text Confirm Dialog](../fragments/text-confirm-dialog) adds a deliberate speed bump for highly destructive actions by requiring the user to type an exact confirmation string before proceeding. The confirm action remains disabled until the input matches.
<ComponentPreview name="text-confirm-dialog-demo" />
#### Confirmation Modal
[Confirmation Modal](../fragments/confirmation-modal) is a convenience wrapper for less-critical confirmations that require more than a single paragraph, such as additional context, callouts, or simple form elements.
<ComponentPreview name="confirmation-modal-demo" />
#### Dialog
[Dialog](../components/dialog) is a general-purpose modal for bespoke flows such as forms, pickers, or non-critical interactions where dismissal is acceptable.
<ComponentPreview name="dialog-demo" />
## Sheets
Sheets are dialogs presented as side panels. Use them for content that is larger than a few fields, or when a centered dialog would feel cramped.
- **Use for**: multi-field forms, editors, settings panels, and detailed views.
- **Prefer the default**: sheets slide in from the right unless you have a strong reason to use another side.
- **Group content**: use header/sections/footer so the user can scan and act quickly.
### Components
#### Sheet
[Sheet](../components/sheet) is modal by default, blocking interaction with the underlying page.
<ComponentPreview name="sheet-demo" />