mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 03:45:06 +03:00
## What kind of change does this PR introduce? Bug fix and design-system documentation update. ## What is the current behavior? Invite acceptance failures only appear in a transient toast. ## What is the new behavior? Invite failures remain visible beside the actions. The design-system guidance now distinguishes field, action, state, and toast feedback. | Before | After | | --- | --- | | <img width="759" height="619" alt="Join Organization Supabase" src="https://github.com/user-attachments/assets/ed8e974c-5da3-477a-81da-628d3f847131" /> | <img width="741" height="768" alt="Join Organization Supabase" src="https://github.com/user-attachments/assets/4c3f6bcd-4ed9-40b2-8280-e8c8a44ecbd6" /> | ## To test With local Studio running at `http://localhost:8082`: 1. Open `apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.utils.ts`. 2. At line 37, immediately inside `getOrganizationInviteStatus`, add: ```tsx return 'ready' ``` This deliberately bypasses invite lookup and account checks for the visual test. 3. Open `apps/studio/components/interfaces/OrganizationInvite/OrganizationInvite.tsx`. 4. At line 30, change: ```tsx const [joinError, setJoinError] = useState<string>() ``` to: ```tsx const [joinError, setJoinError] = useState<string>('Invite token can only be accepted via an SSO account') ``` 5. Open `http://localhost:8082/join?token=test&slug=test` while signed in. 6. Confirm the card says **Join an organization** and shows the error below **Decline**, separated from the actions by a divider. 7. Revert both temporary edits before committing anything. ## Additional context First PR in a five-PR stack. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a new connect interstitial example showcasing an inline action-error state with clear retry guidance. - **Bug Fixes** - Invitation acceptance failures now show inline destructive feedback under “Accept invite,” keeping the button enabled for retry (and removing prior toast-based failure behavior). - Updated the invalid-invitation title to “Invalid invitation.” - Changed the “Decline” link destination to `/organizations`. - **Documentation** - Expanded Sonner toast “When to use” guidance. - Refined form and connect interstitial action-feedback patterns (inline vs toast usage). - **Tests** - Updated and added coverage for the inline error rendering and “Invalid invitation” text. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
65 lines
3.2 KiB
Plaintext
65 lines
3.2 KiB
Plaintext
---
|
|
title: Forms
|
|
description: Common form patterns used in Studio settings pages and side panels.
|
|
---
|
|
|
|
Forms in Supabase Studio should follow consistent patterns to ensure a cohesive user experience across settings pages and side panels. This guide covers the most common form patterns and field types.
|
|
|
|
## Page Layout
|
|
|
|
Forms in page layouts typically use `PageSection` components with `Card` containers. Fields use `FormItemLayout` with `layout="flex-row-reverse"` for horizontal alignment.
|
|
|
|
<ComponentPreview
|
|
name="form-patterns-pagelayout"
|
|
description="Complete form example with all field types in a PageLayout pattern"
|
|
peekCode
|
|
wide
|
|
/>
|
|
|
|
## Side Panel
|
|
|
|
Forms in side panels (Sheets) use `FormItemLayout` with `layout="horizontal"` on wider panels and `layout="vertical"` on panels with a size of `sm` or below. The form is typically wrapped in a `Sheet` component.
|
|
|
|
<ComponentPreview
|
|
name="form-patterns-sidepanel"
|
|
description="Complete form example with all field types in a SidePanel/Sheet pattern"
|
|
peekCode
|
|
wide
|
|
/>
|
|
|
|
## Field Arrays
|
|
|
|
The form previews above include both repeated-field patterns used across Studio:
|
|
|
|
- **Field Array** for repeated single-value rows such as redirect URIs.
|
|
- **Key/Value Field Array** for repeated text pairs such as headers, parameters, and config entries.
|
|
|
|
Use the shared [Single Value Field Array](../fragments/single-value-field-array) fragment when each row is one text input managed by `react-hook-form`.
|
|
|
|
Use the shared [Key/Value Field Array](../fragments/key-value-field-array) fragment when each row is two text inputs managed by `react-hook-form`.
|
|
|
|
Keep repeated-row validation in the form schema or shared validation helper, not in the fragment component itself.
|
|
|
|
Build a custom row when the cells are mixed controls, such as an input paired with a `Select`.
|
|
|
|
## Best practices
|
|
|
|
1. **Always use FormItemLayout**: Use `FormItemLayout` instead of manually composing `FormItem`, `FormLabel`, `FormMessage`, and `FormDescription`.
|
|
|
|
2. **Layout selection**:
|
|
- Use `layout="flex-row-reverse"` for page layouts (horizontal alignment)
|
|
- Use `layout="horizontal"` for side panels with more width
|
|
- Use `layout="vertical"` for side panels with limited width
|
|
|
|
3. **Wrap inputs in FormControl*Shadcn***: Always wrap form inputs with `FormControl` to ensure proper form integration.
|
|
|
|
4. **Use Cards for grouping**: Wrap form sections in `Card` components with `CardContent` and `CardFooter` for actions.
|
|
|
|
5. **Handle dirty state**: Show cancel buttons and disable save buttons based on `form.formState.isDirty`. Make sure you destructure `isDirty` from `form.formState` (see https://react-hook-form.com/docs/useform/formstate)
|
|
|
|
6. **Error handling**: Match feedback to its scope. Use `FormMessage` or `FieldError` for field validation. Show submission failures inline near the form actions when the user needs to retry or change something. Reserve toasts for non-blocking feedback or completed operations whose originating surface is no longer visible.
|
|
|
|
7. **Loading states**: Show loading states on submit buttons using the `loading` prop.
|
|
|
|
8. **Form IDs**: When submit buttons are outside the form, use a form ID and reference it with the `form` prop on the button.
|