Skip to main content

generate-frontend-forms

Guide for creating forms using Sentry's new form system. Use when implementing forms, form fields, validation, or auto-save functionality.

معلومات المصدر

المستودع
getsentry/sentry
آخر نشاط في المصدر
٨ أكتوبر ٢٠٢٦ في ١٢:١٨
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٤٥٬٥٢٨
التفرعات
٤٬٩١٥

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
generate-frontend-forms
description
Guide for creating forms using Sentry's new form system. Use when implementing forms, form fields, validation, or auto-save functionality.
# Form System Guide This skill provides patterns for building forms using Sentry's new form system built on TanStack React Form and Zod validation. ## Core Principle - Always use the new form system (`useScrapsForm`, `AutoSaveForm`) for new forms. Never create new forms with the legacy JsonForm or Reflux-based systems. - All forms should be schema based. DO NOT create a form without schema validation. ## Imports All form components are exported from `@sentry/scraps/form`: ```tsx import {z} from 'zod'; import { AutoSaveForm, defaultFormOptions, setFieldErrors, useScrapsForm, } from '@sentry/scraps/form'; ``` > **Important**: DO NOT import from deeper paths, like '@sentry/scraps/form/field'. You can only use what is part of the PUBLIC interface in the index file in @sentry/scraps/form. --- ## Form Hook: `useScrapsForm` The main hook for creating forms with validation and submission handling. ### Basic Usage ```tsx import {z} from 'zod'; import {defaultFormOptions, useScrapsForm} from '@sentry/scraps/form'; const schema = z.object({ email: z.email('Invalid email'), name: z.string().min(2, 'Name must be at least 2 characters'), }); function MyForm() { const form = useScrapsForm({ ...defaultFormOptions, defaultValues: { email: '', name: '', }, validators: { onDynamic: schema, }, onSubmit: ({value, formApi}) => { // Handle submission console.log(value); }, }); return ( <form.AppForm form={form}> <form.AppField name="email"> {field => ( <field.Layout.Stack label="Email" required> <field.Input value={field.state.value} onChange={field.handleChange} /> </field.Layout.Stack> )} </form.AppField> <form.SubmitButton>Submit</form.SubmitButton> </form.AppForm> ); } ``` > **Important**: Always spread `defaultFormOptions` first. It configures validation to run on submit initially, then on every change after the first submission. This is why validators are defined as `onDynamic`, and it's what provides a consistent UX. ### Returned Properties | Property | Description | | ---------------- | ------------------------------------------------------------------------------------------------------------- | | `AppForm` | Root wrapper component (provides form context and renders `<form>` element). Must receive `form={form}` prop. | | `AppField` | Field renderer component | | `FieldGroup` | Section grouping with title | | `SubmitButton` | Pre-wired submit button | | `Subscribe` | Subscribe to form state changes | | `reset()` | Reset form to default values | | `handleSubmit()` | Manually trigger submission | --- ## Field Components All fields are accessed via the `field` render prop and follow consistent patterns. ### Input Field (Text) ```tsx <form.AppField name="firstName"> {field => ( <field.Layout.Stack label="First Name" required> <field.Input value={field.state.value} onChange={field.handleChange} placeholder="Enter your name" /> </field.Layout.Stack> )} </form.AppField> ``` ### Number Field ```tsx <form.AppField name="age"> {field => ( <field.Layout.Stack label="Age" required> <field.Number value={field.state.value} onChange={field.handleChange} min={0} max={120} step={1} /> </field.Layout.Stack> )} </form.AppField> ``` ### Select Field (Single) ```tsx <form.AppField name="country"> {field => ( <field.Layout.Stack label="Country"> <field.Select value={field.state.value} onChange={field.handleChange} options={[ {value: 'us', label: 'United States'}, {value: 'uk', label: 'United Kingdom'}, ]} /> </field.Layout.Stack> )} </form.AppField> ``` ### Select Field (Multiple) ```tsx <form.AppField name="tags"> {field => ( <field.Layout.Stack label="Tags"> <field.Select multiple value={field.state.value} onChange={field.handleChange} options={[ {value: 'bug', label: 'Bug'}, {value: 'feature', label: 'Feature'}, ]} clearable /> </field.Layout.Stack> )} </form.AppField> ``` ### Switch Field (Boolean) ```tsx <form.AppField name="notifications"> {field => ( <field.Layout.Stack label="Enable notifications"> <field.Switch checked={field.state.value} onChange={field.handleChange} /> </field.Layout.Stack> )} </form.AppField> ``` ### TextArea Field ```tsx <form.AppField name="bio"> {field => ( <field.Layout.Stack label="Bio"> <field.TextArea value={field.state.value} onChange={field.handleChange} rows={4} placeholder="Tell us about yourself" /> </field.Layout.Stack> )} </form.AppField> ``` ### Range Field (Slider) ```tsx <form.AppField name="volume"> {field => ( <field.Layout.Stack label="Volume"> <field.Range value={field.state.value} onChange={field.handleChange} min={0} max={100} step={10} /> </field.Layout.Stack> )} </form.AppField> ``` ### Radio Field Radio fields use a composable API with `Radio.Group` and `Radio.Item`. `Radio.Group` provides group context that changes how the label is rendered for proper accessibility semantics. > **Important**: The layout (and its label) **must** be rendered _inside_ `Radio.Group`. The group context is provided by `Radio.Group`, so placing the layout outside will result in incorrect accessibility semantics. ```tsx <form.AppField name="priority"> {field => ( <field.Radio.Group value={field.state.value} onChange={field.handleChange}> <field.Layout.Stack label="Priority"> <field.Radio.Item value="low">Low</field.Radio.Item> <field.Radio.Item value="medium">Medium</field.Radio.Item> <field.Radio.Item value="high" description="Urgent issues"> High </field.Radio.Item> </field.Layout.Stack> </field.Radio.Group> )} </form.AppField> ``` For horizontal arrangement of radio items, use a `Flex` or `Stack` wrapper inside the layout: ```tsx import {Flex} from '@sentry/scraps/layout'; <field.Radio.Group value={field.state.value} onChange={field.handleChange}> <field.Layout.Row label="Priority"> <Flex gap="lg"> <field.Radio.Item value="low">Low</field.Radio.Item> <field.Radio.Item value="high">High</field.Radio.Item> </Flex> </field.Layout.Row> </field.Radio.Group>; ``` ### Custom Fields with BaseField For one-off fields that don't have a built-in component (e.g. a color picker, or any custom input), use `field.Base`. It provides a render prop with all the necessary accessibility and form integration props (`ref`, `disabled`, `aria-invalid`, `aria-describedby`, `onBlur`, `name`, `id`) that you spread onto your native element. ```tsx <form.AppField name="color"> {field => ( <field.Layout.Row label="Brand Color"> <field.Base<HTMLInputElement>> {(baseProps, {indicator}) => ( <Flex flexGrow={1}> <input {...baseProps} type="color" value={field.state.value} onChange={e => field.handleChange(e.target.value)} /> {indicator} </Flex> )} </field.Base> </field.Layout.Row> )} </form.AppField> ``` The render prop receives two arguments: 1. **`baseProps`** — accessibility and form integration props (`ref`, `disabled`, `aria-invalid`, `aria-describedby`, `onBlur`, `name`, `id`) to spread onto your element 2. **`{indicator}`** — the auto-save status indicator (spinner/checkmark) as a React node, which you can place wherever makes sense in your custom layout The element type is inferred from the passed `ref`, so if you don't pass one, you have to manually annotate it with `<field.Base<HTMLInputElement>>`. `field.Base` automatically handles: - Merging refs (for scroll-to-hash and external ref forwarding) - Disabling the field when auto-save is pending - Setting `aria-invalid` based on validation state - Linking to hint text via `aria-describedby` Use `field.Base` instead of building custom wrappers that duplicate this logic. It works with any native HTML element or third-party component that accepts standard props. --- ## Layouts Two layout options are available for positioning labels and fields. ### Stack Layout (Vertical) Label above, field below. Best for forms with longer labels or mobile layouts. ```tsx <field.Layout.Stack label="Email Address" hintText="We'll never share your email" required > <field.Input value={field.state.value} onChange={field.handleChange} /> </field.Layout.Stack> ``` ### Row Layout (Horizontal) Label on left (~50%), field on right. Compact layout for settings pages. ```tsx <field.Layout.Row label="Email Address" hintText="We'll never share your email" required> <field.Input value={field.state.value} onChange={field.handleChange} /> </field.Layout.Row> ``` ### Compact Variant Both Stack and Row layouts support a `variant="compact"` prop. In compact mode, the hint text appears as a tooltip on the label instead of being displayed below. This saves vertical space while still providing the hint information. ```tsx // Default: hint text appears below the label <field.Layout.Row label="Email" hintText="We'll never share your email"> <field.Input ... /> </field.Layout.Row> // Compact: hint text appears in tooltip when hovering the label <field.Layout.Row label="Email" hintText="We'll never share your email" variant="compact"> <field.Input ... /> </field.Layout.Row> // Also works with Stack layout <field.Layout.Stack label="Email" hintText="We'll never share your email" variant="compact"> <field.Input ... /> </field.Layout.Stack> ``` **When to Use Compact**: - Settings pages with many fields where vertical space is limited - Forms where hint text is supplementary, not essential - Dashboards or panels with constrained height ### Custom Layouts You are allowed to create new layouts if necessary, or not use any layouts at all. Without a layout, you _should_ render `field.meta.Label` and optionally `field.meta.HintText` for a11y. ```tsx <form.AppField name="firstName"> {field => ( <Flex gap="md"> <field.Meta.Label required>First Name:</field.Meta.Label> <field.Input value={field.state.value ?? ''} onChange={field.handleChange} /> </Flex> )} </form.AppField> ``` ### Layout Props | Prop | Type | Description |
عرض على GitHub
ملف SKILL.md هذا كبير جدا، لذلك يعرض SkillsMP القسم الاول فقط هنا. عرض على GitHub