refactor: extract styling field components into separate files

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
TheodorTomas
2026-02-06 16:17:35 +08:00
parent 7563793643
commit 5c6f1e998e
5 changed files with 177 additions and 167 deletions
@@ -0,0 +1,30 @@
"use client";
import { ColorPicker } from "@/modules/ui/components/color-picker";
import { FormControl, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
interface ColorFieldProps {
form: any;
name: string;
label: string;
containerClass?: string;
}
export const ColorField = ({ form, name, label, containerClass }: ColorFieldProps) => (
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel className="text-xs">{label}</FormLabel>
<FormControl>
<ColorPicker
color={field.value}
onChange={(color) => field.onChange(color)}
containerClass={containerClass || "w-full"}
/>
</FormControl>
</FormItem>
)}
/>
);
@@ -0,0 +1,74 @@
"use client";
import { FormControl, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
import { Input } from "@/modules/ui/components/input";
interface DimensionInputProps {
form: any;
name: string;
label: string;
placeholder?: string;
}
export const DimensionInput = ({ form, name, label, placeholder }: DimensionInputProps) => (
<FormField
control={form.control}
name={name}
render={({ field }) => {
const value = field.value;
let unit = "px";
if (typeof value === "string") {
if (value.endsWith("%")) unit = "%";
else if (value.endsWith("rem")) unit = "rem";
else if (value.endsWith("em")) unit = "em";
}
const numericValue = typeof value === "string" ? Number.parseFloat(value) : value;
return (
<FormItem className="space-y-1">
<FormLabel className="text-xs">{label}</FormLabel>
<FormControl>
<div className="flex rounded-md shadow-xs">
<Input
type="number"
{...field}
value={numericValue ?? ""}
onChange={(e) => {
const valStr = e.target.value;
if (valStr === "") {
field.onChange(null);
return;
}
const newVal = Number.parseFloat(valStr);
if (Number.isNaN(newVal)) {
return;
}
field.onChange(unit === "px" ? newVal : `${newVal}${unit}`);
}}
className="flex-1 rounded-r-none border-r-0 text-xs focus-visible:ring-0"
placeholder={placeholder}
/>
<select
value={unit}
onChange={(e) => {
const newUnit = e.target.value;
const currentVal = numericValue ?? 0;
if (newUnit === "px") {
field.onChange(currentVal);
} else {
field.onChange(`${currentVal}${newUnit}`);
}
}}
className="ring-offset-background placeholder:text-muted-foreground focus:border-brand-dark h-10 items-center justify-between rounded-r-md border border-slate-300 bg-white pr-8 pl-3 text-xs font-medium focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50">
<option value="px">px</option>
<option value="%">%</option>
<option value="rem">rem</option>
<option value="em">em</option>
</select>
</div>
</FormControl>
</FormItem>
);
}}
/>
);
@@ -0,0 +1,39 @@
"use client";
import { FormControl, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
import { Input } from "@/modules/ui/components/input";
interface NumberFieldProps {
form: any;
name: string;
label: string;
step?: number;
max?: number;
placeholder?: string;
}
export const NumberField = ({ form, name, label, step = 1, max, placeholder }: NumberFieldProps) => (
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel className="text-xs">{label}</FormLabel>
<FormControl>
<Input
type="number"
{...field}
onChange={(e) => {
const val = e.target.valueAsNumber;
field.onChange(Number.isNaN(val) ? null : val);
}}
step={step}
max={max}
className="text-xs"
placeholder={placeholder}
/>
</FormControl>
</FormItem>
)}
/>
);
@@ -0,0 +1,25 @@
"use client";
import { FormControl, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
import { Input } from "@/modules/ui/components/input";
interface TextFieldProps {
form: any;
name: string;
label: string;
}
export const TextField = ({ form, name, label }: TextFieldProps) => (
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel className="text-xs">{label}</FormLabel>
<FormControl>
<Input type="text" {...field} className="text-xs" />
</FormControl>
</FormItem>
)}
/>
);
@@ -3,21 +3,20 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { ChevronDown, ChevronRight } from "lucide-react";
import React from "react";
import { ColorPicker } from "@/modules/ui/components/color-picker";
import { FormControl, FormField, FormItem, FormLabel } from "@/modules/ui/components/form";
import { Input } from "@/modules/ui/components/input";
export const StylingSection = ({
title,
open,
setOpen,
children,
}: {
export { ColorField } from "./components/color-field";
export { DimensionInput } from "./components/dimension-input";
export { NumberField } from "./components/number-field";
export { TextField } from "./components/text-field";
interface StylingSectionProps {
title: string;
open: boolean;
setOpen: (o: boolean) => void;
children: React.ReactNode;
}) => {
}
export const StylingSection = ({ title, open, setOpen, children }: StylingSectionProps) => {
const [parent] = useAutoAnimate();
return (
@@ -33,160 +32,3 @@ export const StylingSection = ({
</div>
);
};
export const ColorField = ({
form,
name,
label,
containerClass,
}: {
form: any;
name: string;
label: string;
containerClass?: string;
}) => (
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel className="text-xs">{label}</FormLabel>
<FormControl>
<ColorPicker
color={field.value}
onChange={(color) => field.onChange(color)}
containerClass={containerClass || "w-full"}
/>
</FormControl>
</FormItem>
)}
/>
);
export const NumberField = ({
form,
name,
label,
step = 1,
max,
placeholder,
}: {
form: any;
name: string;
label: string;
step?: number;
max?: number;
placeholder?: string;
}) => (
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel className="text-xs">{label}</FormLabel>
<FormControl>
<Input
type="number"
{...field}
onChange={(e) => {
const val = e.target.valueAsNumber;
field.onChange(Number.isNaN(val) ? null : val);
}}
step={step}
max={max}
className="text-xs"
placeholder={placeholder}
/>
</FormControl>
</FormItem>
)}
/>
);
export const DimensionInput = ({
form,
name,
label,
placeholder,
}: {
form: any;
name: string;
label: string;
placeholder?: string;
}) => (
<FormField
control={form.control}
name={name}
render={({ field }) => {
const value = field.value;
let unit = "px";
if (typeof value === "string") {
if (value.endsWith("%")) unit = "%";
else if (value.endsWith("rem")) unit = "rem";
else if (value.endsWith("em")) unit = "em";
}
const numericValue = typeof value === "string" ? Number.parseFloat(value) : value;
return (
<FormItem className="space-y-1">
<FormLabel className="text-xs">{label}</FormLabel>
<FormControl>
<div className="flex rounded-md shadow-xs">
<Input
type="number"
{...field}
value={numericValue ?? ""}
onChange={(e) => {
const valStr = e.target.value;
if (valStr === "") {
field.onChange(null);
return;
}
const newVal = Number.parseFloat(valStr);
if (Number.isNaN(newVal)) {
return;
}
field.onChange(unit === "px" ? newVal : `${newVal}${unit}`);
}}
className="flex-1 rounded-r-none border-r-0 text-xs focus-visible:ring-0"
placeholder={placeholder}
/>
<select
value={unit}
onChange={(e) => {
const newUnit = e.target.value;
const currentVal = numericValue ?? 0;
if (newUnit === "px") {
field.onChange(currentVal);
} else {
field.onChange(`${currentVal}${newUnit}`);
}
}}
className="ring-offset-background placeholder:text-muted-foreground focus:border-brand-dark h-10 items-center justify-between rounded-r-md border border-slate-300 bg-white pr-8 pl-3 text-xs font-medium focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 focus:outline-hidden disabled:cursor-not-allowed disabled:opacity-50">
<option value="px">px</option>
<option value="%">%</option>
<option value="rem">rem</option>
<option value="em">em</option>
</select>
</div>
</FormControl>
</FormItem>
);
}}
/>
);
export const TextField = ({ form, name, label }: { form: any; name: string; label: string }) => (
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItem className="space-y-1">
<FormLabel className="text-xs">{label}</FormLabel>
<FormControl>
<Input type="text" {...field} className="text-xs" />
</FormControl>
</FormItem>
)}
/>
);