mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 12:25:05 +03:00
## 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>
105 lines
5.2 KiB
Plaintext
105 lines
5.2 KiB
Plaintext
---
|
||
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" />
|