mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-07-12 14:05:08 +10:00
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
This commit is contained in:
@@ -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<StyleIntent>) => void;
|
||||
};
|
||||
|
||||
function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleIntentEditorProps) {
|
||||
const labelStart = labelPrefix ? `${labelPrefix} ` : "";
|
||||
|
||||
function RuleIntentEditor({ idPrefix, intent, onChange }: RuleIntentEditorProps) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<ControlPanel title="Color">
|
||||
<div className={exactFourControlGridClassName}>
|
||||
<ColorField
|
||||
label={`${labelStart}Text Color`}
|
||||
label={"Text Color"}
|
||||
id={`${idPrefix}-color`}
|
||||
value={intent.color}
|
||||
placeholder="rgba(0, 0, 0, 1)"
|
||||
@@ -491,7 +489,7 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
onChange={(color) => onChange({ color })}
|
||||
/>
|
||||
<ColorField
|
||||
label={`${labelStart}Background`}
|
||||
label={"Background"}
|
||||
id={`${idPrefix}-background`}
|
||||
value={intent.backgroundColor}
|
||||
placeholder="rgba(255, 255, 255, 1)"
|
||||
@@ -499,7 +497,7 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
onChange={(backgroundColor) => onChange({ backgroundColor })}
|
||||
/>
|
||||
<ColorField
|
||||
label={`${labelStart}Text Decoration Color`}
|
||||
label={"Text Decoration Color"}
|
||||
id={`${idPrefix}-text-decoration-color`}
|
||||
value={intent.textDecorationColor}
|
||||
placeholder="rgba(0, 0, 0, 1)"
|
||||
@@ -507,7 +505,7 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
onChange={(textDecorationColor) => onChange({ textDecorationColor })}
|
||||
/>
|
||||
<NumberInput
|
||||
label={`${labelStart}Opacity`}
|
||||
label={"Opacity"}
|
||||
id={`${idPrefix}-opacity`}
|
||||
value={intent.opacity}
|
||||
min={0}
|
||||
@@ -521,7 +519,7 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
<ControlPanel title="Text">
|
||||
<div className={controlGridClassName}>
|
||||
<NumberInput
|
||||
label={`${labelStart}Font Size`}
|
||||
label={"Font Size"}
|
||||
id={`${idPrefix}-font-size`}
|
||||
value={intent.fontSize}
|
||||
min={6}
|
||||
@@ -529,20 +527,20 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
onChange={(fontSize) => onChange({ fontSize })}
|
||||
/>
|
||||
<FontWeightField
|
||||
label={`${labelStart}Font Weight`}
|
||||
label={"Font Weight"}
|
||||
id={`${idPrefix}-font-weight`}
|
||||
value={intent.fontWeight}
|
||||
onChange={(fontWeight) => onChange({ fontWeight })}
|
||||
/>
|
||||
<IntentSelectField
|
||||
label={`${labelStart}Font Style`}
|
||||
label={"Font Style"}
|
||||
id={`${idPrefix}-font-style`}
|
||||
value={intent.fontStyle}
|
||||
options={fontStyleOptions}
|
||||
onChange={(fontStyle) => onChange({ fontStyle })}
|
||||
/>
|
||||
<NumberInput
|
||||
label={`${labelStart}Line Height`}
|
||||
label={"Line Height"}
|
||||
id={`${idPrefix}-line-height`}
|
||||
value={intent.lineHeight}
|
||||
min={0.5}
|
||||
@@ -551,7 +549,7 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
onChange={(lineHeight) => onChange({ lineHeight })}
|
||||
/>
|
||||
<NumberInput
|
||||
label={`${labelStart}Letter Spacing`}
|
||||
label={"Letter Spacing"}
|
||||
id={`${idPrefix}-letter-spacing`}
|
||||
value={intent.letterSpacing}
|
||||
min={-16}
|
||||
@@ -560,28 +558,28 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
onChange={(letterSpacing) => onChange({ letterSpacing })}
|
||||
/>
|
||||
<IntentSelectField
|
||||
label={`${labelStart}Text Decoration`}
|
||||
label={"Text Decoration"}
|
||||
id={`${idPrefix}-text-decoration`}
|
||||
value={intent.textDecoration}
|
||||
options={textDecorationOptions}
|
||||
onChange={(textDecoration) => onChange({ textDecoration })}
|
||||
/>
|
||||
<IntentSelectField
|
||||
label={`${labelStart}Decoration Style`}
|
||||
label={"Decoration Style"}
|
||||
id={`${idPrefix}-text-decoration-style`}
|
||||
value={intent.textDecorationStyle}
|
||||
options={textDecorationStyleOptions}
|
||||
onChange={(textDecorationStyle) => onChange({ textDecorationStyle })}
|
||||
/>
|
||||
<IntentSelectField
|
||||
label={`${labelStart}Text Align`}
|
||||
label={"Text Align"}
|
||||
id={`${idPrefix}-text-align`}
|
||||
value={intent.textAlign}
|
||||
options={textAlignOptions}
|
||||
onChange={(textAlign) => onChange({ textAlign })}
|
||||
/>
|
||||
<IntentSelectField
|
||||
label={`${labelStart}Text Transform`}
|
||||
label={"Text Transform"}
|
||||
id={`${idPrefix}-text-transform`}
|
||||
value={intent.textTransform}
|
||||
options={textTransformOptions}
|
||||
@@ -592,11 +590,11 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
|
||||
<ControlPanel title="Spacing">
|
||||
<div className="space-y-3">
|
||||
<PaddingSideInputs idPrefix={idPrefix} intent={intent} labelPrefix={labelPrefix} onChange={onChange} />
|
||||
<MarginSideInputs idPrefix={idPrefix} intent={intent} labelPrefix={labelPrefix} onChange={onChange} />
|
||||
<PaddingSideInputs idPrefix={idPrefix} intent={intent} onChange={onChange} />
|
||||
<MarginSideInputs idPrefix={idPrefix} intent={intent} onChange={onChange} />
|
||||
<SpacingInputGroup label="Gap">
|
||||
<CompactNumberInput
|
||||
ariaLabel={`${labelStart}Row Gap`}
|
||||
ariaLabel={"Row Gap"}
|
||||
id={`${idPrefix}-row-gap`}
|
||||
placeholder="row"
|
||||
value={intent.rowGap}
|
||||
@@ -605,7 +603,7 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
onChange={(rowGap) => onChange({ rowGap })}
|
||||
/>
|
||||
<CompactNumberInput
|
||||
ariaLabel={`${labelStart}Column Gap`}
|
||||
ariaLabel={"Column Gap"}
|
||||
id={`${idPrefix}-column-gap`}
|
||||
placeholder="column"
|
||||
value={intent.columnGap}
|
||||
@@ -620,14 +618,14 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
<ControlPanel title="Border">
|
||||
<div className={exactFourControlGridClassName}>
|
||||
<IntentSelectField
|
||||
label={`${labelStart}Border Style`}
|
||||
label={"Border Style"}
|
||||
id={`${idPrefix}-border-style`}
|
||||
value={intent.borderStyle}
|
||||
options={borderStyleOptions}
|
||||
onChange={(borderStyle) => onChange({ borderStyle })}
|
||||
/>
|
||||
<NumberInput
|
||||
label={`${labelStart}Border Width`}
|
||||
label={"Border Width"}
|
||||
id={`${idPrefix}-border-width`}
|
||||
value={intent.borderWidth}
|
||||
min={0}
|
||||
@@ -635,7 +633,7 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
onChange={(borderWidth) => onChange({ borderWidth })}
|
||||
/>
|
||||
<NumberInput
|
||||
label={`${labelStart}Border Radius`}
|
||||
label={"Border Radius"}
|
||||
id={`${idPrefix}-border-radius`}
|
||||
value={intent.borderRadius}
|
||||
min={0}
|
||||
@@ -643,7 +641,7 @@ function RuleIntentEditor({ idPrefix, intent, labelPrefix, onChange }: RuleInten
|
||||
onChange={(borderRadius) => onChange({ borderRadius })}
|
||||
/>
|
||||
<ColorField
|
||||
label={`${labelStart}Border Color`}
|
||||
label={"Border Color"}
|
||||
id={`${idPrefix}-border-color`}
|
||||
value={intent.borderColor}
|
||||
placeholder="rgba(0, 0, 0, 1)"
|
||||
@@ -751,19 +749,16 @@ type MarginSideProperty = (typeof marginSideOptions)[number]["property"];
|
||||
type PaddingSideInputsProps = {
|
||||
idPrefix: string;
|
||||
intent: StyleIntent;
|
||||
labelPrefix?: string;
|
||||
onChange: (patch: Partial<StyleIntent>) => void;
|
||||
};
|
||||
|
||||
function PaddingSideInputs({ idPrefix, intent, labelPrefix, onChange }: PaddingSideInputsProps) {
|
||||
const labelStart = labelPrefix ? `${labelPrefix} ` : "";
|
||||
|
||||
function PaddingSideInputs({ idPrefix, intent, onChange }: PaddingSideInputsProps) {
|
||||
return (
|
||||
<SpacingInputGroup label="Padding">
|
||||
{paddingSideOptions.map((side) => (
|
||||
<CompactNumberInput
|
||||
key={side.property}
|
||||
ariaLabel={`${labelStart}Padding ${side.label}`}
|
||||
ariaLabel={`Padding ${side.label}`}
|
||||
id={`${idPrefix}-${side.property}`}
|
||||
placeholder={side.label.toLowerCase()}
|
||||
value={getPaddingSideValue(intent, side.property)}
|
||||
@@ -779,19 +774,16 @@ function PaddingSideInputs({ idPrefix, intent, labelPrefix, onChange }: PaddingS
|
||||
type MarginSideInputsProps = {
|
||||
idPrefix: string;
|
||||
intent: StyleIntent;
|
||||
labelPrefix?: string;
|
||||
onChange: (patch: Partial<StyleIntent>) => void;
|
||||
};
|
||||
|
||||
function MarginSideInputs({ idPrefix, intent, labelPrefix, onChange }: MarginSideInputsProps) {
|
||||
const labelStart = labelPrefix ? `${labelPrefix} ` : "";
|
||||
|
||||
function MarginSideInputs({ idPrefix, intent, onChange }: MarginSideInputsProps) {
|
||||
return (
|
||||
<SpacingInputGroup label="Margin">
|
||||
{marginSideOptions.map((side) => (
|
||||
<CompactNumberInput
|
||||
key={side.property}
|
||||
ariaLabel={`${labelStart}Margin ${side.label}`}
|
||||
ariaLabel={`Margin ${side.label}`}
|
||||
id={`${idPrefix}-${side.property}`}
|
||||
placeholder={side.label.toLowerCase()}
|
||||
value={intent[side.property]}
|
||||
@@ -1047,10 +1039,3 @@ function compactIntent(intent: Partial<StyleIntent>): 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();
|
||||
}
|
||||
|
||||
@@ -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<typeof colorDesignSchema>;
|
||||
|
||||
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<typeof useColorSectionForm>;
|
||||
|
||||
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() {
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
<form.Field name="primary">
|
||||
{(field) => (
|
||||
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
|
||||
<FormLabel>
|
||||
<Trans>Primary Color</Trans>
|
||||
</FormLabel>
|
||||
<div className="flex items-center gap-3">
|
||||
<ColorPicker
|
||||
value={field.state.value}
|
||||
onChange={(color) => {
|
||||
field.handleChange(color);
|
||||
handleAutoSave();
|
||||
}}
|
||||
/>
|
||||
<FormControl
|
||||
render={
|
||||
<Input
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => {
|
||||
field.handleChange(e.target.value);
|
||||
handleAutoSave();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<FormMessage errors={field.state.meta.errors} />
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
<form.Field name="text">
|
||||
{(field) => (
|
||||
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
|
||||
<FormLabel>
|
||||
<Trans>Text Color</Trans>
|
||||
</FormLabel>
|
||||
<div className="flex items-center gap-3">
|
||||
<ColorPicker
|
||||
defaultValue={field.state.value}
|
||||
onChange={(color) => {
|
||||
field.handleChange(color);
|
||||
handleAutoSave();
|
||||
}}
|
||||
/>
|
||||
<FormControl
|
||||
render={
|
||||
<Input
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => {
|
||||
field.handleChange(e.target.value);
|
||||
handleAutoSave();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<FormMessage errors={field.state.meta.errors} />
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
<form.Field name="background">
|
||||
{(field) => (
|
||||
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
|
||||
<FormLabel>
|
||||
<Trans>Background Color</Trans>
|
||||
</FormLabel>
|
||||
<div className="flex items-center gap-3">
|
||||
<ColorPicker
|
||||
defaultValue={field.state.value}
|
||||
onChange={(color) => {
|
||||
field.handleChange(color);
|
||||
handleAutoSave();
|
||||
}}
|
||||
/>
|
||||
<FormControl
|
||||
render={
|
||||
<Input
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => {
|
||||
field.handleChange(e.target.value);
|
||||
handleAutoSave();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<FormMessage errors={field.state.meta.errors} />
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
<ColorFormField
|
||||
form={form}
|
||||
name="primary"
|
||||
label={<Trans>Primary Color</Trans>}
|
||||
controlled
|
||||
handleAutoSave={handleAutoSave}
|
||||
/>
|
||||
<ColorFormField form={form} name="text" label={<Trans>Text Color</Trans>} handleAutoSave={handleAutoSave} />
|
||||
<ColorFormField
|
||||
form={form}
|
||||
name="background"
|
||||
label={<Trans>Background Color</Trans>}
|
||||
handleAutoSave={handleAutoSave}
|
||||
/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
type ColorFormFieldProps = {
|
||||
form: ColorSectionForm;
|
||||
name: keyof ColorValues;
|
||||
label: ReactNode;
|
||||
controlled?: boolean;
|
||||
handleAutoSave: () => void;
|
||||
};
|
||||
|
||||
function ColorFormField({ form, name, label, controlled, handleAutoSave }: ColorFormFieldProps) {
|
||||
return (
|
||||
<form.Field name={name}>
|
||||
{(field) => (
|
||||
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
<div className="flex items-center gap-3">
|
||||
<ColorPicker
|
||||
{...(controlled ? { value: field.state.value } : { defaultValue: field.state.value })}
|
||||
onChange={(color) => {
|
||||
field.handleChange(color);
|
||||
handleAutoSave();
|
||||
}}
|
||||
/>
|
||||
<FormControl
|
||||
render={
|
||||
<Input
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => {
|
||||
field.handleChange(e.target.value);
|
||||
handleAutoSave();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<FormMessage errors={field.state.meta.errors} />
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
);
|
||||
}
|
||||
|
||||
const quickColorOptions = [
|
||||
"rgba(231, 0, 11, 1)", // red-600
|
||||
"rgba(245, 73, 0, 1)", // orange-600
|
||||
|
||||
@@ -53,6 +53,19 @@ function PageSectionForm() {
|
||||
persist({ ...form.state.values, [name]: value });
|
||||
};
|
||||
|
||||
const pageNumberFields = [
|
||||
{ name: "marginX" as const, label: <Trans>Margin (Horizontal)</Trans>, min: 0, max: 100 as number | undefined },
|
||||
{ name: "marginY" as const, label: <Trans>Margin (Vertical)</Trans>, min: 0, max: 100 as number | undefined },
|
||||
{ name: "gapX" as const, label: <Trans>Spacing (Horizontal)</Trans>, min: 0, max: undefined },
|
||||
{ name: "gapY" as const, label: <Trans>Spacing (Vertical)</Trans>, min: 0, max: undefined },
|
||||
];
|
||||
|
||||
const pageSwitchFields = [
|
||||
{ name: "hideLinkUnderline" as const, label: <Trans>Hide Link Underline</Trans> },
|
||||
{ name: "hideIcons" as const, label: <Trans>Hide Icons</Trans> },
|
||||
{ name: "hideSectionIcons" as const, label: <Trans>Hide Section Icons</Trans> },
|
||||
];
|
||||
|
||||
return (
|
||||
<form
|
||||
className="grid @md:grid-cols-2 grid-cols-1 gap-4"
|
||||
@@ -120,215 +133,64 @@ function PageSectionForm() {
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
<form.Field name="marginX">
|
||||
{(field) => (
|
||||
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
|
||||
<FormLabel>
|
||||
<Trans>Margin (Horizontal)</Trans>
|
||||
</FormLabel>
|
||||
<InputGroup>
|
||||
<FormControl
|
||||
render={
|
||||
<InputGroupInput
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
type="number"
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
const marginX = value === "" ? ("" as unknown as number) : Number(value);
|
||||
field.handleChange(marginX);
|
||||
handleAutoSave("marginX", marginX);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>pt</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<FormMessage errors={field.state.meta.errors} />
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
<form.Field name="marginY">
|
||||
{(field) => (
|
||||
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
|
||||
<FormLabel>
|
||||
<Trans>Margin (Vertical)</Trans>
|
||||
</FormLabel>
|
||||
<InputGroup>
|
||||
<FormControl
|
||||
render={
|
||||
<InputGroupInput
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
type="number"
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
const marginY = value === "" ? ("" as unknown as number) : Number(value);
|
||||
field.handleChange(marginY);
|
||||
handleAutoSave("marginY", marginY);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>pt</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<FormMessage errors={field.state.meta.errors} />
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
<form.Field name="gapX">
|
||||
{(field) => (
|
||||
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
|
||||
<FormLabel>
|
||||
<Trans>Spacing (Horizontal)</Trans>
|
||||
</FormLabel>
|
||||
<InputGroup>
|
||||
<FormControl
|
||||
render={
|
||||
<InputGroupInput
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
min={0}
|
||||
step={1}
|
||||
type="number"
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
const gapX = value === "" ? ("" as unknown as number) : Number(value);
|
||||
field.handleChange(gapX);
|
||||
handleAutoSave("gapX", gapX);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>pt</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<FormMessage errors={field.state.meta.errors} />
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
<form.Field name="gapY">
|
||||
{(field) => (
|
||||
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
|
||||
<FormLabel>
|
||||
<Trans>Spacing (Vertical)</Trans>
|
||||
</FormLabel>
|
||||
<InputGroup>
|
||||
<FormControl
|
||||
render={
|
||||
<InputGroupInput
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
min={0}
|
||||
step={1}
|
||||
type="number"
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value;
|
||||
const gapY = value === "" ? ("" as unknown as number) : Number(value);
|
||||
field.handleChange(gapY);
|
||||
handleAutoSave("gapY", gapY);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>pt</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<FormMessage errors={field.state.meta.errors} />
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
<form.Field name="hideLinkUnderline">
|
||||
{(field) => (
|
||||
<FormItem
|
||||
className="col-span-full flex items-center gap-x-3 py-1"
|
||||
hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}
|
||||
>
|
||||
<FormControl
|
||||
render={
|
||||
<Switch
|
||||
checked={field.state.value}
|
||||
onCheckedChange={(checked) => {
|
||||
field.handleChange(checked);
|
||||
handleAutoSave("hideLinkUnderline", checked);
|
||||
}}
|
||||
{pageNumberFields.map(({ name, label, min, max }) => (
|
||||
<form.Field key={name} name={name}>
|
||||
{(field) => (
|
||||
<FormItem hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
<InputGroup>
|
||||
<FormControl
|
||||
render={
|
||||
<InputGroupInput
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
min={min}
|
||||
{...(max !== undefined ? { max } : {})}
|
||||
step={1}
|
||||
type="number"
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
const num = v === "" ? ("" as unknown as number) : Number(v);
|
||||
field.handleChange(num);
|
||||
handleAutoSave(name, num);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<FormLabel>
|
||||
<Trans>Hide Link Underline</Trans>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
<InputGroupAddon align="inline-end">
|
||||
<InputGroupText>pt</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<FormMessage errors={field.state.meta.errors} />
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
))}
|
||||
|
||||
<form.Field name="hideIcons">
|
||||
{(field) => (
|
||||
<FormItem
|
||||
className="col-span-full flex items-center gap-x-3 py-1"
|
||||
hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}
|
||||
>
|
||||
<FormControl
|
||||
render={
|
||||
<Switch
|
||||
checked={field.state.value}
|
||||
onCheckedChange={(checked) => {
|
||||
field.handleChange(checked);
|
||||
handleAutoSave("hideIcons", checked);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<FormLabel>
|
||||
<Trans>Hide Icons</Trans>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
|
||||
<form.Field name="hideSectionIcons">
|
||||
{(field) => (
|
||||
<FormItem
|
||||
className="col-span-full flex items-center gap-x-3 py-1"
|
||||
hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}
|
||||
>
|
||||
<FormControl
|
||||
render={
|
||||
<Switch
|
||||
checked={field.state.value}
|
||||
onCheckedChange={(checked) => {
|
||||
field.handleChange(checked);
|
||||
handleAutoSave("hideSectionIcons", checked);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<FormLabel>
|
||||
<Trans>Hide Section Icons</Trans>
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
{pageSwitchFields.map(({ name, label }) => (
|
||||
<form.Field key={name} name={name}>
|
||||
{(field) => (
|
||||
<FormItem
|
||||
className="col-span-full flex items-center gap-x-3 py-1"
|
||||
hasError={field.state.meta.isTouched && field.state.meta.errors.length > 0}
|
||||
>
|
||||
<FormControl
|
||||
render={
|
||||
<Switch
|
||||
checked={field.state.value}
|
||||
onCheckedChange={(checked) => {
|
||||
field.handleChange(checked);
|
||||
handleAutoSave(name, checked);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
</form.Field>
|
||||
))}
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user