refactor(web): finding 1 - extract TypographyGroupFields component

Extract useTypographyForm helper (captures form creation + sync) and
TypographyForm type. Extract TypographyGroupFields component with
prefix "body" | "heading" to replace 2x4 duplicated form.Field blocks.
Font Weight label ("Font Weights" vs "Font Weight") is preserved per
prefix.

Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa
This commit is contained in:
Amruth Pillai
2026-07-04 21:29:57 +02:00
parent 975cea84e3
commit e93a56d753
@@ -29,6 +29,21 @@ const formSchema = typographySchema;
type FormValues = z.infer<typeof formSchema>;
type FontWeight = FormValues["body"]["fontWeights"][number];
type TypographyPrefix = "body" | "heading";
function useTypographyForm(typography: FormValues | undefined, persist: (data: FormValues) => void) {
const form = useAppForm({
defaultValues: typography,
validators: { onChange: formSchema },
onSubmit: ({ value }) => {
persist(value);
},
});
useSyncFormValues(form, typography);
return form;
}
type TypographyForm = ReturnType<typeof useTypographyForm>;
function TypographySectionForm() {
const resume = useResume();
@@ -42,22 +57,12 @@ function TypographySectionForm() {
});
};
const form = useAppForm({
defaultValues: typography,
validators: { onChange: formSchema },
onSubmit: ({ value }) => {
persist(value);
},
});
useSyncFormValues(form, typography);
const form = useTypographyForm(typography, persist);
const handleAutoSave = () => {
persist(form.state.values);
};
const bodyFontFamily = useStore(form.store, (s) => s.values.body.fontFamily);
const headingFontFamily = useStore(form.store, (s) => s.values.heading.fontFamily);
return (
<form
className="grid @md:grid-cols-2 grid-cols-1 gap-4"
@@ -68,134 +73,25 @@ function TypographySectionForm() {
}}
>
<TypographyFieldGroup label={<Trans context="Body Text (paragraphs, lists, etc.)">Body</Trans>} />
<form.Field name="body.fontFamily">
{(field) => (
<FormItem
className="col-span-full"
hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}
>
<FormLabel>
<Trans>Font Family</Trans>
</FormLabel>
<FormControl
render={
<FontFamilyCombobox
value={field.state.value}
className="text-base"
onValueChange={(value: string | null) => {
if (value === null) return;
field.handleChange(value);
const nextWeights = getNextWeights(value);
if (nextWeights) form.setFieldValue("body.fontWeights", nextWeights);
handleAutoSave();
}}
/>
}
/>
<FormMessage errors={field.state.meta.errors} />
</FormItem>
)}
</form.Field>
<form.Field name="body.fontWeights">
{(field) => (
<FormItem
className="col-span-full"
hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}
>
<FormLabel>
<Trans>Font Weights</Trans>
</FormLabel>
<FormControl
render={
<FontWeightCombobox
value={field.state.value}
fontFamily={bodyFontFamily}
onValueChange={(value) => {
if (value?.length === 0) return;
field.handleChange(value as FontWeight[]);
handleAutoSave();
}}
/>
}
/>
<FormMessage errors={field.state.meta.errors} />
</FormItem>
)}
</form.Field>
<form.Field name="body.fontSize">
{(field) => (
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
<FormLabel>
<Trans>Font Size</Trans>
</FormLabel>
<InputGroup>
<FormControl
render={
<InputGroupInput
name={field.name}
value={field.state.value}
min={6}
max={24}
step={0.1}
type="number"
onBlur={field.handleBlur}
onChange={(e) => {
const value = e.target.value;
if (value === "") field.handleChange("" as unknown as number);
else field.handleChange(Number(value));
handleAutoSave();
}}
/>
}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>pt</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormItem>
)}
</form.Field>
<form.Field name="body.lineHeight">
{(field) => (
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
<FormLabel>
<Trans>Line Height</Trans>
</FormLabel>
<InputGroup>
<FormControl
render={
<InputGroupInput
name={field.name}
value={field.state.value}
min={0.5}
max={4}
step={0.05}
type="number"
onBlur={field.handleBlur}
onChange={(e) => {
const value = e.target.value;
if (value === "") field.handleChange("" as unknown as number);
else field.handleChange(Number(value));
handleAutoSave();
}}
/>
}
/>
<InputGroupAddon align="inline-end">
<InputGroupText>x</InputGroupText>
</InputGroupAddon>
</InputGroup>
</FormItem>
)}
</form.Field>
<TypographyGroupFields form={form} prefix="body" handleAutoSave={handleAutoSave} />
<TypographyFieldGroup label={<Trans context="Headings or Titles (H1, H2, H3, H4, H5, H6)">Heading</Trans>} />
<TypographyGroupFields form={form} prefix="heading" handleAutoSave={handleAutoSave} />
</form>
);
}
<form.Field name="heading.fontFamily">
type TypographyGroupFieldsProps = {
form: TypographyForm;
prefix: TypographyPrefix;
handleAutoSave: () => void;
};
function TypographyGroupFields({ form, prefix, handleAutoSave }: TypographyGroupFieldsProps) {
const fontFamily = useStore(form.store, (s) => s.values[prefix].fontFamily);
return (
<>
<form.Field name={`${prefix}.fontFamily`}>
{(field) => (
<FormItem
className="col-span-full"
@@ -213,7 +109,7 @@ function TypographySectionForm() {
if (value === null) return;
field.handleChange(value);
const nextWeights = getNextWeights(value);
if (nextWeights) form.setFieldValue("heading.fontWeights", nextWeights);
if (nextWeights) form.setFieldValue(`${prefix}.fontWeights`, nextWeights);
handleAutoSave();
}}
/>
@@ -224,20 +120,18 @@ function TypographySectionForm() {
)}
</form.Field>
<form.Field name="heading.fontWeights">
<form.Field name={`${prefix}.fontWeights`}>
{(field) => (
<FormItem
className="col-span-full"
hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}
>
<FormLabel>
<Trans>Font Weight</Trans>
</FormLabel>
<FormLabel>{prefix === "body" ? <Trans>Font Weights</Trans> : <Trans>Font Weight</Trans>}</FormLabel>
<FormControl
render={
<FontWeightCombobox
value={field.state.value}
fontFamily={headingFontFamily}
fontFamily={fontFamily}
onValueChange={(value) => {
if (value?.length === 0) return;
field.handleChange(value as FontWeight[]);
@@ -251,7 +145,7 @@ function TypographySectionForm() {
)}
</form.Field>
<form.Field name="heading.fontSize">
<form.Field name={`${prefix}.fontSize`}>
{(field) => (
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
<FormLabel>
@@ -285,7 +179,7 @@ function TypographySectionForm() {
)}
</form.Field>
<form.Field name="heading.lineHeight">
<form.Field name={`${prefix}.lineHeight`}>
{(field) => (
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
<FormLabel>
@@ -318,7 +212,7 @@ function TypographySectionForm() {
</FormItem>
)}
</form.Field>
</form>
</>
);
}