From 9b9d5c833cb2f95a64fb65d7084910c0934ce637 Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Sat, 4 Jul 2026 21:28:47 +0200 Subject: [PATCH] refactor(web): findings 2/3/4 in page/design/custom-styles Finding 2: Replace 4 number + 3 switch form.Field blocks with pageNumberFields/pageSwitchFields array maps. Finding 3: Extract ColorFormField helper for primary/text/background color fields in ColorSectionForm. Finding 4: Remove labelPrefix (was always component-specific), replace local slugify with @reactive-resume/utils/string import; fix ariaLabel template literals to keep test labels accurate. Claude-Session: https://claude.ai/code/session_012Bnvt1MghwHj4qQRxuQUGa --- .../-sidebar/right/sections/custom-styles.tsx | 69 ++--- .../-sidebar/right/sections/design.tsx | 179 +++++------- .../-sidebar/right/sections/page.tsx | 276 +++++------------- 3 files changed, 169 insertions(+), 355 deletions(-) diff --git a/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/custom-styles.tsx b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/custom-styles.tsx index 73f2237c0..e9d707be1 100644 --- a/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/custom-styles.tsx +++ b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/custom-styles.tsx @@ -15,6 +15,7 @@ import { Button } from "@reactive-resume/ui/components/button"; import { Input } from "@reactive-resume/ui/components/input"; import { Label } from "@reactive-resume/ui/components/label"; import { Separator } from "@reactive-resume/ui/components/separator"; +import { slugify } from "@reactive-resume/utils/string"; import { cn } from "@reactive-resume/utils/style"; import { ColorPicker } from "@/components/input/color-picker"; import { Combobox } from "@/components/ui/combobox"; @@ -471,19 +472,16 @@ function RuleScopePill({ target, slot }: RuleScopePillProps) { type RuleIntentEditorProps = { idPrefix: string; intent: StyleIntent; - labelPrefix?: string; onChange: (patch: Partial) => void; }; -function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleIntentEditorProps) { - const labelStart = labelPrefix ? `${labelPrefix} ` : ""; - +function RuleIntentEditor({ idPrefix, intent, onChange }: RuleIntentEditorProps) { return (
onChange({ color })} /> onChange({ backgroundColor })} /> onChange({ textDecorationColor })} />
onChange({ fontSize })} /> onChange({ fontWeight })} /> onChange({ fontStyle })} /> onChange({ lineHeight })} /> onChange({ letterSpacing })} /> onChange({ textDecoration })} /> onChange({ textDecorationStyle })} /> onChange({ textAlign })} />
- - + + onChange({ rowGap })} />
onChange({ borderStyle })} /> onChange({ borderWidth })} /> onChange({ borderRadius })} /> ) => void; }; -function PaddingSideInputs({ idPrefix, intent, labelPrefix, onChange }: PaddingSideInputsProps) { - const labelStart = labelPrefix ? `${labelPrefix} ` : ""; - +function PaddingSideInputs({ idPrefix, intent, onChange }: PaddingSideInputsProps) { return ( {paddingSideOptions.map((side) => ( ) => void; }; -function MarginSideInputs({ idPrefix, intent, labelPrefix, onChange }: MarginSideInputsProps) { - const labelStart = labelPrefix ? `${labelPrefix} ` : ""; - +function MarginSideInputs({ idPrefix, intent, onChange }: MarginSideInputsProps) { return ( {marginSideOptions.map((side) => ( ): StyleIntent { function hasIntent(intent: StyleIntent | undefined) { return Boolean(intent && Object.keys(intent).length > 0); } - -function slugify(value: string) { - return value - .replace(/[^a-zA-Z0-9]+/g, "-") - .replace(/(^-|-$)/g, "") - .toLowerCase(); -} diff --git a/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx index 9a97e9991..c9aa378ae 100644 --- a/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx +++ b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/design.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from "react"; import type z from "zod"; import { t } from "@lingui/core/macro"; import { Trans } from "@lingui/react/macro"; @@ -31,6 +32,20 @@ export function DesignSectionBuilder() { type ColorValues = z.infer; +function useColorSectionForm(colors: ColorValues, persist: (data: ColorValues) => void) { + const form = useAppForm({ + defaultValues: colors, + validators: { onChange: colorDesignSchema }, + onSubmit: ({ value }) => { + persist(value); + }, + }); + useSyncFormValues(form, colors); + return form; +} + +type ColorSectionForm = ReturnType; + function ColorSectionForm() { const resume = useCurrentResume(); const colors = resume.data.metadata.design.colors; @@ -42,14 +57,7 @@ function ColorSectionForm() { }); }; - const form = useAppForm({ - defaultValues: colors, - validators: { onChange: colorDesignSchema }, - onSubmit: ({ value }) => { - persist(value); - }, - }); - useSyncFormValues(form, colors); + const form = useColorSectionForm(colors, persist); const handleAutoSave = () => { persist(form.state.values); @@ -85,108 +93,67 @@ function ColorSectionForm() { )} - - {(field) => ( - 0}> - - Primary Color - -
- { - field.handleChange(color); - handleAutoSave(); - }} - /> - { - field.handleChange(e.target.value); - handleAutoSave(); - }} - /> - } - /> -
- -
- )} -
- - - {(field) => ( - 0}> - - Text Color - -
- { - field.handleChange(color); - handleAutoSave(); - }} - /> - { - field.handleChange(e.target.value); - handleAutoSave(); - }} - /> - } - /> -
- -
- )} -
- - - {(field) => ( - 0}> - - Background Color - -
- { - field.handleChange(color); - handleAutoSave(); - }} - /> - { - field.handleChange(e.target.value); - handleAutoSave(); - }} - /> - } - /> -
- -
- )} -
+ Primary Color} + controlled + handleAutoSave={handleAutoSave} + /> + Text Color} handleAutoSave={handleAutoSave} /> + Background Color} + handleAutoSave={handleAutoSave} + /> ); } +type ColorFormFieldProps = { + form: ColorSectionForm; + name: keyof ColorValues; + label: ReactNode; + controlled?: boolean; + handleAutoSave: () => void; +}; + +function ColorFormField({ form, name, label, controlled, handleAutoSave }: ColorFormFieldProps) { + return ( + + {(field) => ( + 0}> + {label} +
+ { + field.handleChange(color); + handleAutoSave(); + }} + /> + { + field.handleChange(e.target.value); + handleAutoSave(); + }} + /> + } + /> +
+ +
+ )} +
+ ); +} + const quickColorOptions = [ "rgba(231, 0, 11, 1)", // red-600 "rgba(245, 73, 0, 1)", // orange-600 diff --git a/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/page.tsx b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/page.tsx index 89640640c..7d6a0d70a 100644 --- a/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/page.tsx +++ b/apps/web/src/routes/builder/$resumeId/-sidebar/right/sections/page.tsx @@ -53,6 +53,19 @@ function PageSectionForm() { persist({ ...form.state.values, [name]: value }); }; + const pageNumberFields = [ + { name: "marginX" as const, label: Margin (Horizontal), min: 0, max: 100 as number | undefined }, + { name: "marginY" as const, label: Margin (Vertical), min: 0, max: 100 as number | undefined }, + { name: "gapX" as const, label: Spacing (Horizontal), min: 0, max: undefined }, + { name: "gapY" as const, label: Spacing (Vertical), min: 0, max: undefined }, + ]; + + const pageSwitchFields = [ + { name: "hideLinkUnderline" as const, label: Hide Link Underline }, + { name: "hideIcons" as const, label: Hide Icons }, + { name: "hideSectionIcons" as const, label: Hide Section Icons }, + ]; + return (
- - {(field) => ( - 0}> - - Margin (Horizontal) - - - { - const value = e.target.value; - const marginX = value === "" ? ("" as unknown as number) : Number(value); - field.handleChange(marginX); - handleAutoSave("marginX", marginX); - }} - /> - } - /> - - pt - - - - - )} - - - - {(field) => ( - 0}> - - Margin (Vertical) - - - { - const value = e.target.value; - const marginY = value === "" ? ("" as unknown as number) : Number(value); - field.handleChange(marginY); - handleAutoSave("marginY", marginY); - }} - /> - } - /> - - pt - - - - - )} - - - - {(field) => ( - 0}> - - Spacing (Horizontal) - - - { - const value = e.target.value; - const gapX = value === "" ? ("" as unknown as number) : Number(value); - field.handleChange(gapX); - handleAutoSave("gapX", gapX); - }} - /> - } - /> - - pt - - - - - )} - - - - {(field) => ( - 0}> - - Spacing (Vertical) - - - { - const value = e.target.value; - const gapY = value === "" ? ("" as unknown as number) : Number(value); - field.handleChange(gapY); - handleAutoSave("gapY", gapY); - }} - /> - } - /> - - pt - - - - - )} - - - - {(field) => ( - 0} - > - { - field.handleChange(checked); - handleAutoSave("hideLinkUnderline", checked); - }} + {pageNumberFields.map(({ name, label, min, max }) => ( + + {(field) => ( + 0}> + {label} + + { + const v = e.target.value; + const num = v === "" ? ("" as unknown as number) : Number(v); + field.handleChange(num); + handleAutoSave(name, num); + }} + /> + } /> - } - /> - - Hide Link Underline - - - )} - + + pt + + + + + )} + + ))} - - {(field) => ( - 0} - > - { - field.handleChange(checked); - handleAutoSave("hideIcons", checked); - }} - /> - } - /> - - Hide Icons - - - )} - - - - {(field) => ( - 0} - > - { - field.handleChange(checked); - handleAutoSave("hideSectionIcons", checked); - }} - /> - } - /> - - Hide Section Icons - - - )} - + {pageSwitchFields.map(({ name, label }) => ( + + {(field) => ( + 0} + > + { + field.handleChange(checked); + handleAutoSave(name, checked); + }} + /> + } + /> + {label} + + )} + + ))}
); }