diff --git a/.agents/skills/generate-frontend-forms/SKILL.md b/.agents/skills/generate-frontend-forms/SKILL.md index 5e8873408eb3..221f7cb90cc9 100644 --- a/.agents/skills/generate-frontend-forms/SKILL.md +++ b/.agents/skills/generate-frontend-forms/SKILL.md @@ -65,18 +65,16 @@ function MyForm() { }); return ( - - - - {field => ( - - - - )} - - - Submit - + + + {field => ( + + + + )} + + + Submit ); } @@ -86,16 +84,15 @@ function MyForm() { ### Returned Properties -| Property | Description | -| ---------------- | ---------------------------------------------- | -| `AppForm` | Root wrapper component (provides form context) | -| `FormWrapper` | Form element wrapper (handles submit) | -| `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 | +| Property | Description | +| ---------------- | ------------------------------------------------------------------------------------------------------------- | +| `AppForm` | Root wrapper component (provides form context and renders `
` 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 | --- @@ -810,7 +807,7 @@ When creating a new form: - [ ] Use `useScrapsForm` with `...defaultFormOptions` - [ ] Set `defaultValues` matching schema shape - [ ] Set `validators: {onDynamic: schema}` -- [ ] Wrap with `` and `` +- [ ] Wrap with `` - [ ] Use `` for each field - [ ] Choose appropriate layout (Stack or Row) - [ ] Handle server errors with `setFieldErrors` diff --git a/.agents/skills/migrate-frontend-forms/SKILL.md b/.agents/skills/migrate-frontend-forms/SKILL.md index ec2f24e5f066..1a5a0d85bb9f 100644 --- a/.agents/skills/migrate-frontend-forms/SKILL.md +++ b/.agents/skills/migrate-frontend-forms/SKILL.md @@ -452,7 +452,7 @@ function SlugForm({project}: {project: Project}) { }); return ( - + {field => ( diff --git a/static/app/components/core/form/field/autoSaveField.tsx b/static/app/components/core/form/field/autoSaveField.tsx index a7663daab885..eb17d4634fa9 100644 --- a/static/app/components/core/form/field/autoSaveField.tsx +++ b/static/app/components/core/form/field/autoSaveField.tsx @@ -231,11 +231,9 @@ export function AutoSaveField< }); return ( - + - - {field => children(field as never)} - + {field => children(field as never)} ); diff --git a/static/app/components/core/form/field/baseField.spec.tsx b/static/app/components/core/form/field/baseField.spec.tsx index dde85abf5bd0..1ba6a7632d87 100644 --- a/static/app/components/core/form/field/baseField.spec.tsx +++ b/static/app/components/core/form/field/baseField.spec.tsx @@ -22,7 +22,7 @@ function TestForm({label, hintText, required, defaultValue, validator}: TestForm }); return ( - + {field => ( @@ -49,7 +49,7 @@ function CompactTestForm({label, hintText, layout = 'Row'}: CompactTestFormProps }); return ( - + {field => { const LayoutComponent = field.Layout[layout]; diff --git a/static/app/components/core/form/field/passwordField.spec.tsx b/static/app/components/core/form/field/passwordField.spec.tsx index 571de11aaf11..01c2ff10be23 100644 --- a/static/app/components/core/form/field/passwordField.spec.tsx +++ b/static/app/components/core/form/field/passwordField.spec.tsx @@ -15,7 +15,7 @@ function TestForm({defaultValue = '', disabled, label = 'Password'}: TestFormPro }); return ( - + {field => ( diff --git a/static/app/components/core/form/field/radioField.spec.tsx b/static/app/components/core/form/field/radioField.spec.tsx index e0018995bc2a..0b49c0bfb5b5 100644 --- a/static/app/components/core/form/field/radioField.spec.tsx +++ b/static/app/components/core/form/field/radioField.spec.tsx @@ -27,7 +27,7 @@ function TestForm({ }); return ( - + {field => ( + {field => ( diff --git a/static/app/components/core/form/field/selectField.spec.tsx b/static/app/components/core/form/field/selectField.spec.tsx index 48d91b8377b7..bbf03dceb099 100644 --- a/static/app/components/core/form/field/selectField.spec.tsx +++ b/static/app/components/core/form/field/selectField.spec.tsx @@ -39,7 +39,7 @@ function TestForm({ }); return ( - + {field => ( @@ -102,7 +102,7 @@ describe('SelectField', () => { }); return ( - + {field => ( { }); return ( - + {field => ( { }); return ( - + {field => ( // @ts-expect-error value should be string[] when multiple is true @@ -189,7 +189,7 @@ describe('SelectField', () => { }); return ( - + {field => ( // @ts-expect-error value should be string when multiple is false @@ -214,7 +214,7 @@ describe('SelectField', () => { }); return ( - + {field => ( { }); return ( - + {field => ( + {field => ( diff --git a/static/app/components/core/form/field/switchField.spec.tsx b/static/app/components/core/form/field/switchField.spec.tsx index 08433a865f63..140c41505dac 100644 --- a/static/app/components/core/form/field/switchField.spec.tsx +++ b/static/app/components/core/form/field/switchField.spec.tsx @@ -36,7 +36,7 @@ function TestForm({ }); return ( - + {field => ( diff --git a/static/app/components/core/form/field/textAreaField.spec.tsx b/static/app/components/core/form/field/textAreaField.spec.tsx index 9ec1fa33d52b..d4e1ed0d1fb4 100644 --- a/static/app/components/core/form/field/textAreaField.spec.tsx +++ b/static/app/components/core/form/field/textAreaField.spec.tsx @@ -31,7 +31,7 @@ function TestForm({ }); return ( - + {field => ( diff --git a/static/app/components/core/form/form.stories.tsx b/static/app/components/core/form/form.stories.tsx index 4bfcdf5a8659..ead55099ad12 100644 --- a/static/app/components/core/form/form.stories.tsx +++ b/static/app/components/core/form/form.stories.tsx @@ -314,165 +314,163 @@ function BasicForm() { } return ( - - - - - {field => ( - - - - )} - - - {field => ( - - - - )} - - - {field => ( - - - - )} - - - {field => ( - - - - )} - - - {field => ( - - - - )} - - - {field => ( - - - - )} - - - {field => ( - + + + {field => ( + + - - - Low - Medium - - High - - - - - )} - - - {field => ( - - - - )} - - state.values.age === 42}> - {showSecret => - showSecret ? ( - - {field => ( - - - - )} - - ) : null - } - - - - - - {field => ( - - - - )} - - - {field => ( - - - - )} - - - {field => ( - - + /> + + )} + + + {field => ( + + + + )} + + + {field => ( + + + + )} + + + {field => ( + + + + )} + + + {field => ( + + + + )} + + + {field => ( + + + + )} + + + {field => ( + + + + Low + Medium + + High + + - )} - - - - - - Submit - - + + )} + + + {field => ( + + + + )} + + state.values.age === 42}> + {showSecret => + showSecret ? ( + + {field => ( + + + + )} + + ) : null + } + + + + + + {field => ( + + + + )} + + + {field => ( + + + + )} + + + {field => ( + + + + )} + + + + + + Submit + ); } @@ -489,56 +487,54 @@ function CompactExample() { }); return ( - - - - - {field => ( - - - - )} - - - {field => ( - - - - )} - - - - - - {field => ( - - - - )} - - - {field => ( - - - - )} - - - + + + + {field => ( + + + + )} + + + {field => ( + + + + )} + + + + + + {field => ( + + + + )} + + + {field => ( + + + + )} + + ); } diff --git a/static/app/components/core/form/scrapsForm.tsx b/static/app/components/core/form/scrapsForm.tsx index ed8c37af585c..bda2b4aec518 100644 --- a/static/app/components/core/form/scrapsForm.tsx +++ b/static/app/components/core/form/scrapsForm.tsx @@ -3,6 +3,7 @@ import { createFormHook, formOptions, revalidateLogic, + type AnyFormApi, type DeepKeys, } from '@tanstack/react-form'; @@ -57,7 +58,7 @@ const {useAppForm} = createFormHook({ formComponents: { FieldGroup, SubmitButton, - FormWrapper, + AppForm, }, fieldContext, formContext, @@ -80,6 +81,14 @@ function SubmitButton(props: ButtonProps) { ); } +function AppForm({children, form}: {children: React.ReactNode; form: AnyFormApi}) { + return ( + + {children} + + ); +} + function FormWrapper({children}: {children: React.ReactNode}) { const form = useFormContext(); diff --git a/static/app/components/customCommitsResolutionModal.tsx b/static/app/components/customCommitsResolutionModal.tsx index a17d7f9c1400..e9f26dabc694 100644 --- a/static/app/components/customCommitsResolutionModal.tsx +++ b/static/app/components/customCommitsResolutionModal.tsx @@ -56,57 +56,55 @@ function CustomCommitsResolutionModal({ }); return ( - - -
-

{t('Resolved In')}

-
- - - {field => ( - - { - return queryOptions({ - ...apiOptions.as()( - '/projects/$organizationIdOrSlug/$projectIdOrSlug/commits/', - { - path: { - organizationIdOrSlug: orgSlug, - projectIdOrSlug: projectSlug, - }, - query: {query: debouncedInput}, - staleTime: 30_000, - } - ), - select: ({json: commits}) => - commits.map(c => ({ - value: c, - textValue: c.id, - label: , - details: ( - - {t('Created')} - - ), - })), - }); - }} - placeholder={t('e.g. d86b832')} - /> - - )} - - -
- - - {t('Resolve')} - -
-
+ +
+

{t('Resolved In')}

+
+ + + {field => ( + + { + return queryOptions({ + ...apiOptions.as()( + '/projects/$organizationIdOrSlug/$projectIdOrSlug/commits/', + { + path: { + organizationIdOrSlug: orgSlug, + projectIdOrSlug: projectSlug, + }, + query: {query: debouncedInput}, + staleTime: 30_000, + } + ), + select: ({json: commits}) => + commits.map(c => ({ + value: c, + textValue: c.id, + label: , + details: ( + + {t('Created')} + + ), + })), + }); + }} + placeholder={t('e.g. d86b832')} + /> + + )} + + +
+ + + {t('Resolve')} + +
); } diff --git a/static/app/views/settings/account/accountEmails.tsx b/static/app/views/settings/account/accountEmails.tsx index f920d7ddd2e1..9a89fd8b39aa 100644 --- a/static/app/views/settings/account/accountEmails.tsx +++ b/static/app/views/settings/account/accountEmails.tsx @@ -84,29 +84,27 @@ function AccountEmails() { - - - - - {field => ( - - - - )} - - - - {t('Add email')} - - + + + + {field => ( + + + + )} + + + + {t('Add email')} + diff --git a/static/app/views/settings/account/passwordForm.tsx b/static/app/views/settings/account/passwordForm.tsx index 19c3880390b2..2cf72b3d1c23 100644 --- a/static/app/views/settings/account/passwordForm.tsx +++ b/static/app/views/settings/account/passwordForm.tsx @@ -62,57 +62,55 @@ export function PasswordForm() { return ( - - - - - {t('Changing your password will invalidate all logged in sessions.')} - - - {field => ( - - - - )} - - - {field => ( - - - - )} - - - {field => ( - - - - )} - - - {t('Change password')} - - - + + + + {t('Changing your password will invalidate all logged in sessions.')} + + + {field => ( + + + + )} + + + {field => ( + + + + )} + + + {field => ( + + + + )} + + + {t('Change password')} + + ); diff --git a/static/app/views/settings/organizationAuthTokens/authTokenDetails.tsx b/static/app/views/settings/organizationAuthTokens/authTokenDetails.tsx index 1303e97f1c51..a386365e8c23 100644 --- a/static/app/views/settings/organizationAuthTokens/authTokenDetails.tsx +++ b/static/app/views/settings/organizationAuthTokens/authTokenDetails.tsx @@ -140,39 +140,37 @@ function AuthTokenDetailsForm({token}: {token: OrgAuthToken}) { }); return ( - - - - {field => ( - - - - )} - - - -
{tokenPreview(token.tokenLastCharacters || '****')}
-
- - -
{token.scopes.slice().sort().join(', ')}
-
- - - - {t('Save Changes')} - -
+ + + {field => ( + + + + )} + + + +
{tokenPreview(token.tokenLastCharacters || '****')}
+
+ + +
{token.scopes.slice().sort().join(', ')}
+
+ + + + {t('Save Changes')} +
); } diff --git a/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx b/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx index ca9f801f9075..419fc51eebe6 100644 --- a/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx +++ b/static/app/views/settings/organizationAuthTokens/newAuthToken.tsx @@ -97,35 +97,33 @@ function AuthTokenCreateForm({ }); return ( - - - - {field => ( - - - - )} - - - -
-
org:ci
- {t('Source Map Upload, Release Creation')} -
-
- - - - {t('Create Token')} - -
+ + + {field => ( + + + + )} + + + +
+
org:ci
+ {t('Source Map Upload, Release Creation')} +
+
+ + + + {t('Create Token')} +
); } diff --git a/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx b/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx index c22d800b14ce..fe6b8fb45cd9 100644 --- a/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx +++ b/static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx @@ -454,55 +454,50 @@ function OrganizationSettingsForm({initialData, onSave}: Props) { {/* Slug — explicit save with warning */} - - - - {field => ( - - field.handleChange(slugify(value))} - disabled={!hasWriteAccess} - /> - - )} - - state.values.slug !== initialData.slug} - > - {isDirty => - isDirty && ( - - - {tct( - 'Changing your organization slug will break organization tokens, may impact integrations, and break links to your organization. You will be redirected to the new slug after saving. [link:Learn more]', - { - link: ( - - ), - } - )} - - - - - {t('Save')} - - - - ) - } - - + + + {field => ( + + field.handleChange(slugify(value))} + disabled={!hasWriteAccess} + /> + + )} + + state.values.slug !== initialData.slug} + > + {isDirty => + isDirty && ( + + + {tct( + 'Changing your organization slug will break organization tokens, may impact integrations, and break links to your organization. You will be redirected to the new slug after saving. [link:Learn more]', + { + link: ( + + ), + } + )} + + + + + {t('Save')} + + + + ) + } + {/* Display Name */} diff --git a/static/app/views/settings/organizationSecurityAndPrivacy/index.tsx b/static/app/views/settings/organizationSecurityAndPrivacy/index.tsx index c6d07a51ba86..75540b2e82fb 100644 --- a/static/app/views/settings/organizationSecurityAndPrivacy/index.tsx +++ b/static/app/views/settings/organizationSecurityAndPrivacy/index.tsx @@ -425,80 +425,78 @@ function ScrubbingConfigurationFieldGroup({hasOrgWrite}: {hasOrgWrite: boolean}) return ( - - - - - {field => ( - - - - )} - + + + + {field => ( + + + + )} + - - {field => ( - - - - )} - - {hasOrgWrite ? ( - - - state.values.sensitiveFields !== initialSensitiveFields || - state.values.safeFields !== initialSafeFields - } - > - {hasChanged => ( - - - {t( - 'Changes to your scrubbing configuration will apply to all new events.' - )} - - - )} - - - - - {t('Save')} - - + + {field => ( + + + + )} + + {hasOrgWrite ? ( + + + state.values.sensitiveFields !== initialSensitiveFields || + state.values.safeFields !== initialSafeFields + } + > + {hasChanged => ( + + + {t( + 'Changes to your scrubbing configuration will apply to all new events.' + )} + + + )} + + + + + {t('Save')} + - ) : null} - - + + ) : null} + ); diff --git a/static/app/views/settings/organizationTeams/teamSettings/index.tsx b/static/app/views/settings/organizationTeams/teamSettings/index.tsx index b89151f4d1ab..bbc22a780caa 100644 --- a/static/app/views/settings/organizationTeams/teamSettings/index.tsx +++ b/static/app/views/settings/organizationTeams/teamSettings/index.tsx @@ -101,65 +101,63 @@ export default function TeamSettings() { )} - - - - - {field => ( - - field.handleChange(slugify(value))} - placeholder="e.g. operations, web-frontend, mobile-ios" - disabled={isDisabled} - /> - - )} - - - {field => ( - - - - )} - + + + + {field => ( + + field.handleChange(slugify(value))} + placeholder="e.g. operations, web-frontend, mobile-ios" + disabled={isDisabled} + /> + + )} + + + {field => ( + + + + )} + - {isDisabled ? null : ( - - state.values.slug !== team.slug}> - {hasChanged => ( - - - {t('You will be redirected to the new team slug after saving.')} - - - )} - - - - {t('Save')} - + {isDisabled ? null : ( + + state.values.slug !== team.slug}> + {hasChanged => ( + + + {t('You will be redirected to the new team slug after saving.')} + + + )} + + + + {t('Save')} - )} - - + + )} +