chore: migrate docs feedback modal to react-hook-form (#44739)

## Problem

The Docs feedback modal still uses the old `Form` component with
`formik`

## Solution

Migrate it to `react-hook-form`

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Improvements**
* Enhanced feedback form with schema-driven validation requiring title
and comment before submission.
* Submission buttons disable while submitting; cancel now also resets
the form state.

* **Chores**
* Added form handling and validation libraries to support the improved
feedback experience.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Gildas Garcia authored and GitHub committed 2026-04-10 12:11:30 +02:00
1 parent b8a246f2d7
commit a9fdb09c66
3 files changed
+120 -80

No files matched your search

+99 -71
View File
@@ -1,10 +1,24 @@
import { Button, Form, Input, Modal } from 'ui'
import { zodResolver } from '@hookform/resolvers/zod'
import { SubmitHandler, useForm } from 'react-hook-form'
import {
Button,
Form_Shadcn_,
FormControl_Shadcn_,
FormField_Shadcn_,
Input_Shadcn_,
Modal,
Textarea,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import * as z from 'zod'
export type FeedbackFields = {
page: string
title: string
comment: string
}
const formSchema = z.object({
page: z.string(),
title: z.string().min(1, 'Required'),
comment: z.string().min(1, 'Required'),
})
export type FeedbackFields = z.infer<typeof formSchema>
type FeedbackModalProps = {
visible: boolean
@@ -14,78 +28,92 @@ type FeedbackModalProps = {
}
function FeedbackModal({ visible, page, onCancel, onSubmit }: FeedbackModalProps) {
const form = useForm<FeedbackFields>({
defaultValues: { page, title: '', comment: '' },
resolver: zodResolver(formSchema),
})
const { reset } = form
const { isSubmitting } = form.formState
const handleCancel = () => {
reset()
onCancel()
}
const handleSubmit: SubmitHandler<FeedbackFields> = (values) => {
onSubmit(values)
reset()
}
return (
<Modal
hideFooter
header="Leave a comment"
visible={visible}
onCancel={onCancel}
onEscapeKeyDown={onCancel}
onCancel={handleCancel}
onEscapeKeyDown={handleCancel}
>
<Form
initialValues={{ page, comment: '' }}
validateOnBlur
validate={(vals) => {
const errors: Partial<FeedbackFields> = {}
if (!vals.title) {
errors.title = 'Required'
}
if (!vals.comment) {
errors.comment = 'Required'
}
return errors
}}
onReset={onCancel}
onSubmit={onSubmit}
>
{({ isSubmitting }: { isSubmitting: boolean }) => (
<>
<Modal.Content className="pt-4 pb-2 flex flex-col gap-2">
<Input type="hidden" id="page" name="page" value={page} />
<Input type="text" id="title" name="title" label="Title" className="mb-2" />
<Input.TextArea
label="Comment"
id="comment"
name="comment"
size="medium"
className="mb-2"
textAreaClassName="resize-none"
afterLabel=" (not anonymous)"
/>
<div className="flex gap-2 text-xs text-foreground-light leading-relaxed">
<span className="flex-shrink-0 mt-0.5">💡</span>
<div>
<strong>Need help or support?</strong> This feedback form is for documentation
improvements only. For technical support, please submit a{' '}
<a
href="https://supabase.com/dashboard/support/new"
target="_blank"
rel="noopener noreferrer"
className="text-brand-link hover:underline"
>
support request
</a>
.
</div>
<Form_Shadcn_ {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)}>
<Modal.Content className="pt-4 pb-2 flex flex-col gap-2">
<input type="hidden" id="page" {...form.register('page')} />
<FormField_Shadcn_
control={form.control}
name="title"
render={({ field }) => (
<FormItemLayout layout="vertical" label="Title">
<FormControl_Shadcn_ className="col-span-6">
<Input_Shadcn_ {...field} />
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
<FormField_Shadcn_
control={form.control}
name="comment"
render={({ field }) => (
<FormItemLayout layout="vertical" label="Comment" afterLabel="(not anonymous)">
<FormControl_Shadcn_ className="col-span-6">
<Textarea {...field} rows={4} className="resize-none" />
</FormControl_Shadcn_>
</FormItemLayout>
)}
/>
<div className="flex gap-2 text-xs text-foreground-light leading-relaxed">
<span className="flex-shrink-0 mt-0.5">💡</span>
<div>
<strong>Need help or support?</strong> This feedback form is for documentation
improvements only. For technical support, please submit a{' '}
<a
href="https://supabase.com/dashboard/support/new"
target="_blank"
rel="noopener noreferrer"
className="text-brand-link hover:underline"
>
support request
</a>
.
</div>
</Modal.Content>
<Modal.Separator />
<Modal.Content className="pt-2 pb-4">
<div className="flex items-center justify-end gap-2">
<Button htmlType="reset" type="default" onClick={onCancel} disabled={isSubmitting}>
Cancel
</Button>
<Button htmlType="submit" loading={isSubmitting} disabled={isSubmitting}>
Submit feedback
</Button>
</div>
</Modal.Content>
</>
)}
</Form>
</div>
</Modal.Content>
<Modal.Separator />
<Modal.Content className="pt-2 pb-4">
<div className="flex items-center justify-end gap-2">
<Button
htmlType="reset"
type="default"
onClick={handleCancel}
disabled={isSubmitting}
>
Cancel
</Button>
<Button htmlType="submit" loading={isSubmitting} disabled={isSubmitting}>
Submit feedback
</Button>
</div>
</Modal.Content>
</form>
</Form_Shadcn_>
</Modal>
)
}
+2
View File
@@ -44,6 +44,7 @@
},
"dependencies": {
"@har-sdk/openapi-sampler": "^2.2.0",
"@hookform/resolvers": "^3.1.1",
"@mdx-js/loader": "^3.0.0",
"@mdx-js/react": "^3.0.0",
"@next/bundle-analyzer": "15.3.1",
@@ -103,6 +104,7 @@
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "catalog:",
"react-error-boundary": "^4.0.12",
"react-hook-form": "^7.45.0",
"react-intersection-observer": "^9.5.3",
"react-markdown": "^8.0.3",
"react-use": "^17.5.0",
+19 -9
View File
@@ -301,6 +301,9 @@ importers:
'@har-sdk/openapi-sampler':
specifier: ^2.2.0
version: 2.2.0
'@hookform/resolvers':
specifier: ^3.1.1
version: 3.3.1(react-hook-form@7.47.0(react@18.3.1))
'@mdx-js/loader':
specifier: ^3.0.0
version: 3.1.1(supports-color@8.1.1)(webpack@5.105.4)
@@ -478,6 +481,9 @@ importers:
react-error-boundary:
specifier: ^4.0.12
version: 4.0.13(react@18.3.1)
react-hook-form:
specifier: ^7.45.0
version: 7.47.0(react@18.3.1)
react-intersection-observer:
specifier: ^9.5.3
version: 9.8.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -2538,7 +2544,7 @@ importers:
version: 2.30.0
formik:
specifier: ^2.2.9
version: 2.4.5(react@18.3.1)
version: 2.4.9(@types/react@18.3.3)(react@18.3.1)
framer-motion:
specifier: ^11.0.3
version: 11.11.17(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -9297,8 +9303,10 @@ packages:
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
'@types/hoist-non-react-statics@3.3.2':
resolution: {integrity: sha512-YIQtIg4PKr7ZyqNPZObpxfHsHEmuB8dXCxd6qVcGuQVDK2bpsF7bYNnBJ4Nn7giuACZg+WewExgrtAJ3XnA4Xw==}
'@types/hoist-non-react-statics@3.3.7':
resolution: {integrity: sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==}
peerDependencies:
'@types/react': '*'
'@types/js-cookie@2.2.7':
resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==}
@@ -12359,8 +12367,8 @@ packages:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
formik@2.4.5:
resolution: {integrity: sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ==}
formik@2.4.9:
resolution: {integrity: sha512-5nI94BMnlFDdQRBY4Sz39WkhxajZJ57Fzs8wVbtsQlm5ScKIR1QLYqv/ultBnobObtlUyxpxoLodpixrsf36Og==}
peerDependencies:
react: '>=16.8.0'
@@ -26967,7 +26975,7 @@ snapshots:
dependencies:
'@types/unist': 2.0.8
'@types/hoist-non-react-statics@3.3.2':
'@types/hoist-non-react-statics@3.3.7(@types/react@18.3.3)':
dependencies:
'@types/react': 18.3.3
hoist-non-react-statics: 3.3.2
@@ -30595,9 +30603,9 @@ snapshots:
dependencies:
fetch-blob: 3.2.0
formik@2.4.5(react@18.3.1):
formik@2.4.9(@types/react@18.3.3)(react@18.3.1):
dependencies:
'@types/hoist-non-react-statics': 3.3.2
'@types/hoist-non-react-statics': 3.3.7(@types/react@18.3.3)
deepmerge: 2.2.1
hoist-non-react-statics: 3.3.2
lodash: 4.18.1
@@ -30605,7 +30613,9 @@ snapshots:
react: 18.3.1
react-fast-compare: 2.0.4
tiny-warning: 1.0.3
tslib: 2.6.2
tslib: 2.8.1
transitivePeerDependencies:
- '@types/react'
forwarded-parse@2.1.2: {}