i18n 적용
CI / test (push) Failing after 11m12s
CI / e2e (push) Has been cancelled
CI / pr_and_merge (push) Has been cancelled

This commit is contained in:
2025-12-10 15:56:51 +09:00
parent 73e9bc6a1c
commit f71207aeb5
127 changed files with 7346 additions and 2079 deletions
@@ -3,6 +3,8 @@
import type { Block, FormRadioBlockProps } from "@/features/editor/state/editorStore";
import { ColorPickerField, TEXT_COLOR_PALETTE } from "@/features/editor/components/ColorPickerField";
import { NumericPropertyControl } from "@/features/editor/components/NumericPropertyControl";
import { useAppLocale } from "@/features/i18n/LocaleProvider";
import { getEditorFormRadioPanelMessages } from "@/features/i18n/messages/editorFormRadioPanel";
interface FormRadioPropertiesPanelProps {
block: Block;
@@ -11,6 +13,8 @@ interface FormRadioPropertiesPanelProps {
}
export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }: FormRadioPropertiesPanelProps) {
const locale = useAppLocale();
const m = getEditorFormRadioPanelMessages(locale);
if (!selectedBlockId || block.id !== selectedBlockId) return null;
const radioProps = block.props as FormRadioBlockProps;
@@ -18,7 +22,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
return (
<div className="space-y-3 text-xs">
<h3 className="text-[11px] font-semibold text-slate-800 dark:text-slate-200"> </h3>
<h3 className="text-[11px] font-semibold text-slate-800 dark:text-slate-200">{m.sectionTitle}</h3>
<label className="flex flex-col gap-1">
<span className="text-slate-500 dark:text-slate-400"> </span>
@@ -225,7 +229,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
<span className="text-slate-500 dark:text-slate-400"></span>
<button
type="button"
className="rounded border border-slate-300 bg-white px-2 py-0.5 text-[10px] text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-slate-800"
className="rounded border border-slate-300 bg-white px-2 py-0.5 text-[10px] text-slate-900 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100 dark:hover:bg-red-900/60 dark:hover:border-red-700"
onClick={() => {
const next = Array.isArray((radioProps as any).options)
? [...(radioProps as any).options]
@@ -393,15 +397,15 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
</div>
<label className="inline-flex items-center gap-2">
<span className="text-slate-500"> .</span>
<span className="text-slate-500">{m.requiredNoticeText}</span>
</label>
<div className="mt-3 space-y-2 border-t border-slate-800 pt-3">
<h4 className="text-[11px] font-semibold text-slate-800 dark:text-slate-200"> </h4>
{/* 라디오 타이포 px 값을 직접 입력받아 em 스케일 변환 로직과 연동한다 */}
<h4 className="text-[11px] font-semibold text-slate-800 dark:text-slate-200">{m.styleSectionTitle}</h4>
<NumericPropertyControl
label="라디오 텍스트 크기 (px)"
unitLabel="(px)"
label={m.textSizeLabel}
unitLabel={m.textSizeUnitLabel}
value={(() => {
const raw = radioProps.fontSizeCustom ?? "";
const match = raw.match(/([0-9]+(?:\.[0-9]+)?)/);
@@ -424,10 +428,10 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
} as any);
}}
/>
{/* 줄간격 px 입력을 노출하여 preview 에서 em 변환 근거로 사용 */}
<NumericPropertyControl
label="라디오 줄간격 (px)"
unitLabel="(px)"
label={m.lineHeightLabel}
unitLabel={m.lineHeightUnitLabel}
value={(() => {
const raw = radioProps.lineHeightCustom ?? "";
const match = raw.match(/([0-9]+(?:\.[0-9]+)?)/);
@@ -448,10 +452,10 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
} as any);
}}
/>
{/* 자간 px 입력 컨트롤로 프리뷰 적용 근거 제공 */}
<NumericPropertyControl
label="라디오 자간 (px)"
unitLabel="(px)"
label={m.letterSpacingLabel}
unitLabel={m.letterSpacingUnitLabel}
value={(() => {
const raw = radioProps.letterSpacingCustom ?? "";
const match = raw.match(/(-?[0-9]+(?:\.[0-9]+)?)/);
@@ -472,8 +476,9 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
} as any);
}}
/>
<label className="flex flex-col gap-1 text-[11px] text-slate-400">
<span> </span>
<span>Field width</span>
<select
className="w-full rounded border border-slate-300 bg-white px-2 py-1 text-[11px] text-slate-900 outline-none focus:border-sky-500 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
value={radioProps.widthMode ?? "full"}
@@ -483,23 +488,24 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
} as any)
}
>
<option value="auto"></option>
<option value="full"> </option>
<option value="fixed"> </option>
<option value="auto">Auto</option>
<option value="full">Full width</option>
<option value="fixed">Fixed width</option>
</select>
</label>
{(radioProps.widthMode ?? "full") === "fixed" && (
<NumericPropertyControl
label="필드 고정 너비"
label="Field fixed width"
unitLabel="(px)"
value={radioProps.widthPx ?? 240}
min={80}
max={800}
step={10}
presets={[
{ id: "sm", label: "작게", value: 200 },
{ id: "md", label: "보통", value: 280 },
{ id: "lg", label: "넓게", value: 360 },
{ id: "sm", label: "Small", value: 200 },
{ id: "md", label: "Medium", value: 280 },
{ id: "lg", label: "Large", value: 360 },
]}
onChangeValue={(v) => {
updateBlock(selectedBlockId, {
@@ -508,19 +514,20 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
}}
/>
)}
<NumericPropertyControl
label="라디오 가로 패딩 (px)"
unitLabel="(px)"
label={m.paddingXLabel}
unitLabel={m.paddingXUnitLabel}
value={typeof radioProps.paddingX === "number" ? radioProps.paddingX : 12}
min={0}
max={60}
step={2}
presets={[
{ id: "xs", label: "아주 얇게", value: 6 },
{ id: "sm", label: "얇게", value: 10 },
{ id: "md", label: "보통", value: 12 },
{ id: "lg", label: "넓게", value: 16 },
{ id: "xl", label: "아주 넓게", value: 20 },
{ id: "xs", label: "Extra small", value: 6 },
{ id: "sm", label: "Small", value: 10 },
{ id: "md", label: "Medium", value: 12 },
{ id: "lg", label: "Large", value: 16 },
{ id: "xl", label: "Extra large", value: 20 },
]}
onChangeValue={(v) =>
updateBlock(selectedBlockId, {
@@ -528,19 +535,20 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
} as any)
}
/>
<NumericPropertyControl
label="라디오 세로 패딩 (px)"
unitLabel="(px)"
label={m.paddingYLabel}
unitLabel={m.paddingYUnitLabel}
value={typeof radioProps.paddingY === "number" ? radioProps.paddingY : 10}
min={0}
max={40}
step={1}
presets={[
{ id: "xs", label: "아주 얇게", value: 6 },
{ id: "sm", label: "얇게", value: 8 },
{ id: "md", label: "보통", value: 10 },
{ id: "lg", label: "넓게", value: 12 },
{ id: "xl", label: "아주 넓게", value: 16 },
{ id: "xs", label: "Extra small", value: 6 },
{ id: "sm", label: "Small", value: 8 },
{ id: "md", label: "Medium", value: 10 },
{ id: "lg", label: "Large", value: 12 },
{ id: "xl", label: "Extra large", value: 16 },
]}
onChangeValue={(v) =>
updateBlock(selectedBlockId, {
@@ -548,18 +556,19 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
} as any)
}
/>
<NumericPropertyControl
label="라디오 옵션 간격 (px)"
unitLabel="(px)"
label={m.optionGapLabel}
unitLabel={m.optionGapUnitLabel}
value={typeof radioProps.optionGapPx === "number" ? radioProps.optionGapPx : 4}
min={0}
max={40}
step={1}
presets={[
{ id: "tight", label: "타이트", value: 2 },
{ id: "normal", label: "보통", value: 4 },
{ id: "relaxed", label: "느슨", value: 8 },
{ id: "extra", label: "넓게", value: 12 },
{ id: "tight", label: "Tight", value: 2 },
{ id: "normal", label: "Normal", value: 4 },
{ id: "relaxed", label: "Relaxed", value: 8 },
{ id: "extra", label: "Extra", value: 12 },
]}
onChangeValue={(v) =>
updateBlock(selectedBlockId, {
@@ -567,10 +576,11 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
} as any)
}
/>
<ColorPickerField
label="텍스트 색상"
ariaLabelColorInput="라디오 텍스트 색상 피커"
ariaLabelHexInput="라디오 텍스트 색상 HEX"
label={m.textColorLabel}
ariaLabelColorInput={m.textColorPickerAria}
ariaLabelHexInput={m.textColorHexAria}
value={
radioProps.textColorCustom && radioProps.textColorCustom.trim() !== ""
? radioProps.textColorCustom
@@ -588,10 +598,11 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
} as any);
}}
/>
<ColorPickerField
label="배경 색상"
ariaLabelColorInput="라디오 배경 색상 피커"
ariaLabelHexInput="라디오 배경 색상 HEX"
label={m.fillColorLabel}
ariaLabelColorInput={m.fillColorPickerAria}
ariaLabelHexInput={m.fillColorHexAria}
value={
radioProps.fillColorCustom && radioProps.fillColorCustom.trim() !== ""
? radioProps.fillColorCustom
@@ -609,10 +620,11 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
} as any);
}}
/>
<ColorPickerField
label="테두리 색상"
ariaLabelColorInput="라디오 테두리 색상 피커"
ariaLabelHexInput="라디오 테두리 색상 HEX"
label={m.strokeColorLabel}
ariaLabelColorInput={m.strokeColorPickerAria}
ariaLabelHexInput={m.strokeColorHexAria}
value={
radioProps.strokeColorCustom && radioProps.strokeColorCustom.trim() !== ""
? radioProps.strokeColorCustom
@@ -630,8 +642,9 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
} as any);
}}
/>
<NumericPropertyControl
label="필드 모서리 둥글기"
label={m.borderRadiusLabel}
value={(() => {
const r = radioProps.borderRadius ?? "md";
return r === "none" ? 0 : r === "sm" ? 2 : r === "lg" ? 6 : r === "full" ? 8 : 4;
@@ -640,11 +653,11 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
max={8}
step={1}
presets={[
{ id: "none", label: "없음", value: 0 },
{ id: "sm", label: "작게", value: 2 },
{ id: "md", label: "보통", value: 4 },
{ id: "lg", label: "크게", value: 6 },
{ id: "full", label: "완전 둥글게", value: 8 },
{ id: "none", label: m.borderRadiusPresetNone, value: 0 },
{ id: "sm", label: m.borderRadiusPresetSmall, value: 2 },
{ id: "md", label: m.borderRadiusPresetMedium, value: 4 },
{ id: "lg", label: m.borderRadiusPresetLarge, value: 6 },
{ id: "full", label: m.borderRadiusPresetFull, value: 8 },
]}
onChangeValue={(v) => {
const next =