오류 수정
This commit is contained in:
@@ -99,10 +99,10 @@ export function FormCheckboxPropertiesPanel({ block, selectedBlockId, updateBloc
|
||||
max={80}
|
||||
step={2}
|
||||
presets={[
|
||||
{ id: "tight", label: "타이트", value: 4 },
|
||||
{ id: "normal", label: "보통", value: 8 },
|
||||
{ id: "relaxed", label: "느슨", value: 12 },
|
||||
{ id: "extra", label: "넓게", value: 16 },
|
||||
{ id: "tight", label: "Tight", value: 4 },
|
||||
{ id: "normal", label: "Normal", value: 8 },
|
||||
{ id: "relaxed", label: "Relaxed", value: 12 },
|
||||
{ id: "extra", label: "Extra", value: 16 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -235,7 +235,7 @@ export function FormCheckboxPropertiesPanel({ block, selectedBlockId, updateBloc
|
||||
? [...(checkboxProps as any).options]
|
||||
: [];
|
||||
next.push({
|
||||
label: "새 옵션",
|
||||
label: "New option",
|
||||
value: `option_${next.length + 1}`,
|
||||
});
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -442,9 +442,9 @@ export function FormCheckboxPropertiesPanel({ block, selectedBlockId, updateBloc
|
||||
max={60}
|
||||
step={1}
|
||||
presets={[
|
||||
{ id: "tight", label: "타이트", value: 18 },
|
||||
{ id: "normal", label: "보통", value: 24 },
|
||||
{ id: "loose", label: "루즈", value: 32 },
|
||||
{ id: "tight", label: "Tight", value: 18 },
|
||||
{ id: "normal", label: "Normal", value: 24 },
|
||||
{ id: "loose", label: "Loose", value: 32 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -466,9 +466,9 @@ export function FormCheckboxPropertiesPanel({ block, selectedBlockId, updateBloc
|
||||
max={20}
|
||||
step={0.5}
|
||||
presets={[
|
||||
{ id: "tight", label: "좁게", value: -1 },
|
||||
{ id: "normal", label: "보통", value: 0 },
|
||||
{ id: "wide", label: "넓게", value: 2 },
|
||||
{ id: "tight", label: "Tight", value: -1 },
|
||||
{ id: "normal", label: "Normal", value: 0 },
|
||||
{ id: "wide", label: "Wide", value: 2 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -501,9 +501,9 @@ export function FormCheckboxPropertiesPanel({ block, selectedBlockId, updateBloc
|
||||
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, {
|
||||
@@ -520,11 +520,11 @@ export function FormCheckboxPropertiesPanel({ block, selectedBlockId, updateBloc
|
||||
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 thin", value: 6 },
|
||||
{ id: "sm", label: "Thin", value: 10 },
|
||||
{ id: "md", label: "Normal", value: 12 },
|
||||
{ id: "lg", label: "Wide", value: 16 },
|
||||
{ id: "xl", label: "Extra wide", value: 20 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -540,11 +540,11 @@ export function FormCheckboxPropertiesPanel({ block, selectedBlockId, updateBloc
|
||||
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 thin", value: 6 },
|
||||
{ id: "sm", label: "Thin", value: 8 },
|
||||
{ id: "md", label: "Normal", value: 10 },
|
||||
{ id: "lg", label: "Wide", value: 12 },
|
||||
{ id: "xl", label: "Extra wide", value: 16 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -560,10 +560,10 @@ export function FormCheckboxPropertiesPanel({ block, selectedBlockId, updateBloc
|
||||
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, {
|
||||
|
||||
@@ -216,6 +216,7 @@ export function FormControllerPanel({ block, blocks, selectedBlockId, updateBloc
|
||||
<option value="fixed">{m.formWidthModeOptionFixed}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{(formProps.formWidthMode ?? "auto") === "fixed" && (
|
||||
<NumericPropertyControl
|
||||
label={m.formFixedWidthLabel}
|
||||
@@ -225,9 +226,9 @@ export function FormControllerPanel({ block, blocks, selectedBlockId, updateBloc
|
||||
max={1200}
|
||||
step={10}
|
||||
presets={[
|
||||
{ id: "sm", label: "좁게", value: 320 },
|
||||
{ id: "md", label: "보통", value: 480 },
|
||||
{ id: "lg", label: "넓게", value: 640 },
|
||||
{ id: "sm", label: m.formFixedWidthPresetNarrow, value: 320 },
|
||||
{ id: "md", label: m.formFixedWidthPresetNormal, value: 480 },
|
||||
{ id: "lg", label: m.formFixedWidthPresetWide, value: 640 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -236,6 +237,7 @@ export function FormControllerPanel({ block, blocks, selectedBlockId, updateBloc
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<NumericPropertyControl
|
||||
label={m.marginYLabel}
|
||||
unitLabel="(px)"
|
||||
@@ -352,7 +354,7 @@ export function FormControllerPanel({ block, blocks, selectedBlockId, updateBloc
|
||||
} as any);
|
||||
}}
|
||||
/>
|
||||
<span>필수</span>
|
||||
<span>{m.requiredLabel}</span>
|
||||
</label>
|
||||
</label>
|
||||
);
|
||||
@@ -371,7 +373,7 @@ export function FormControllerPanel({ block, blocks, selectedBlockId, updateBloc
|
||||
} as any)
|
||||
}
|
||||
>
|
||||
<option value="">선택 안 함</option>
|
||||
<option value="">{m.submitButtonNoneOptionLabel}</option>
|
||||
{blocks
|
||||
.filter((b) => b.type === "button")
|
||||
.map((buttonBlock) => {
|
||||
|
||||
@@ -92,10 +92,10 @@ export function FormInputPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
max={80}
|
||||
step={2}
|
||||
presets={[
|
||||
{ id: "tight", label: "타이트", value: 4 },
|
||||
{ id: "normal", label: "보통", value: 8 },
|
||||
{ id: "relaxed", label: "느슨", value: 12 },
|
||||
{ id: "extra", label: "넓게", value: 16 },
|
||||
{ id: "tight", label: "Tight", value: 4 },
|
||||
{ id: "normal", label: "Normal", value: 8 },
|
||||
{ id: "relaxed", label: "Relaxed", value: 12 },
|
||||
{ id: "extra", label: "Extra", value: 16 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -218,9 +218,9 @@ export function FormInputPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
max={60}
|
||||
step={1}
|
||||
presets={[
|
||||
{ id: "tight", label: "타이트", value: 18 },
|
||||
{ id: "normal", label: "보통", value: 24 },
|
||||
{ id: "loose", label: "루즈", value: 32 },
|
||||
{ id: "tight", label: "Tight", value: 18 },
|
||||
{ id: "normal", label: "Normal", value: 24 },
|
||||
{ id: "loose", label: "Loose", value: 32 },
|
||||
]}
|
||||
onChangeValue={(v) => {
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -242,9 +242,9 @@ export function FormInputPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
max={20}
|
||||
step={0.5}
|
||||
presets={[
|
||||
{ id: "tight", label: "좁게", value: -1 },
|
||||
{ id: "normal", label: "보통", value: 0 },
|
||||
{ id: "wide", label: "넓게", value: 2 },
|
||||
{ id: "tight", label: "Tight", value: -1 },
|
||||
{ id: "normal", label: "Normal", value: 0 },
|
||||
{ id: "wide", label: "Wide", value: 2 },
|
||||
]}
|
||||
onChangeValue={(v) => {
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -293,9 +293,9 @@ export function FormInputPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
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, {
|
||||
@@ -311,11 +311,11 @@ export function FormInputPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
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 thin", value: 6 },
|
||||
{ id: "sm", label: "Thin", value: 10 },
|
||||
{ id: "md", label: "Normal", value: 12 },
|
||||
{ id: "lg", label: "Wide", value: 16 },
|
||||
{ id: "xl", label: "Extra wide", value: 20 },
|
||||
]}
|
||||
onChangeValue={(v) => {
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -331,11 +331,11 @@ export function FormInputPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
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 thin", value: 6 },
|
||||
{ id: "sm", label: "Thin", value: 8 },
|
||||
{ id: "md", label: "Normal", value: 10 },
|
||||
{ id: "lg", label: "Wide", value: 12 },
|
||||
{ id: "xl", label: "Extra wide", value: 16 },
|
||||
]}
|
||||
onChangeValue={(v) => {
|
||||
updateBlock(selectedBlockId, {
|
||||
|
||||
@@ -25,7 +25,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
<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>
|
||||
<span className="text-slate-500 dark:text-slate-400">{m.groupTitleTypeLabel}</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.groupLabelMode ?? "text"}
|
||||
@@ -35,13 +35,13 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
} as any)
|
||||
}
|
||||
>
|
||||
<option value="text">텍스트</option>
|
||||
<option value="image">이미지</option>
|
||||
<option value="text">{m.groupTitleTypeOptionText}</option>
|
||||
<option value="image">{m.groupTitleTypeOptionImage}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-slate-500 dark:text-slate-400">그룹 타이틀 표시 방식</span>
|
||||
<span className="text-slate-500 dark:text-slate-400">{m.groupTitleDisplayLabel}</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={groupLabelDisplay}
|
||||
@@ -51,14 +51,14 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
} as any)
|
||||
}
|
||||
>
|
||||
<option value="visible">표시 (기본)</option>
|
||||
<option value="hidden">숨김</option>
|
||||
<option value="visible">{m.groupTitleDisplayOptionVisible}</option>
|
||||
<option value="hidden">{m.groupTitleDisplayOptionHidden}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{groupLabelDisplay === "visible" && (
|
||||
<label className="flex flex-col gap-1 text-[11px] text-slate-500 dark:text-slate-400">
|
||||
<span>그룹 타이틀 레이아웃</span>
|
||||
<span>{m.layoutLabel}</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.labelLayout ?? "stacked"}
|
||||
@@ -68,14 +68,14 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
} as any)
|
||||
}
|
||||
>
|
||||
<option value="stacked">세로 (기본)</option>
|
||||
<option value="inline">가로 (인라인)</option>
|
||||
<option value="stacked">{m.layoutOptionStacked}</option>
|
||||
<option value="inline">{m.layoutOptionInline}</option>
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
|
||||
<label className="flex flex-col gap-1 text-[11px] text-slate-500 dark:text-slate-400">
|
||||
<span>옵션 레이아웃</span>
|
||||
<span>{m.optionLayoutLabel}</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.optionLayout ?? "stacked"}
|
||||
@@ -85,24 +85,24 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
} as any)
|
||||
}
|
||||
>
|
||||
<option value="stacked">세로 (기본)</option>
|
||||
<option value="inline">가로 (인라인)</option>
|
||||
<option value="stacked">{m.optionLayoutOptionStacked}</option>
|
||||
<option value="inline">{m.optionLayoutOptionInline}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{groupLabelDisplay === "visible" && (radioProps.labelLayout ?? "stacked") === "inline" && (
|
||||
<NumericPropertyControl
|
||||
label="라벨/필드 간격 (px)"
|
||||
label={m.labelGapLabel}
|
||||
unitLabel="(px)"
|
||||
value={typeof radioProps.labelGapPx === "number" ? radioProps.labelGapPx : 8}
|
||||
min={0}
|
||||
max={80}
|
||||
step={2}
|
||||
presets={[
|
||||
{ id: "tight", label: "타이트", value: 4 },
|
||||
{ id: "normal", label: "보통", value: 8 },
|
||||
{ id: "relaxed", label: "느슨", value: 12 },
|
||||
{ id: "extra", label: "넓게", value: 16 },
|
||||
{ id: "tight", label: "Tight", value: 4 },
|
||||
{ id: "normal", label: "Normal", value: 8 },
|
||||
{ id: "relaxed", label: "Relaxed", value: 12 },
|
||||
{ id: "extra", label: "Extra", value: 16 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -113,7 +113,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
)}
|
||||
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-slate-500 dark:text-slate-400">그룹 타이틀</span>
|
||||
<span className="text-slate-500 dark:text-slate-400">{m.groupTitleLabel}</span>
|
||||
<input
|
||||
className="w-full rounded border border-slate-300 bg-white px-2 py-1 text-xs text-slate-900 outline-none focus:border-sky-500 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
|
||||
value={radioProps.groupLabel ?? ""}
|
||||
@@ -135,7 +135,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-slate-400">그룹 타이틀 이미지 소스</span>
|
||||
<span className="text-slate-400">{m.groupTitleImageSourceLabel}</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={source}
|
||||
@@ -145,14 +145,14 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
} as any)
|
||||
}
|
||||
>
|
||||
<option value="url">URL</option>
|
||||
<option value="upload">파일 업로드</option>
|
||||
<option value="url">{m.groupTitleImageSourceOptionUrl}</option>
|
||||
<option value="upload">{m.groupTitleImageSourceOptionUpload}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
{source === "url" && (
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-slate-400">그룹 타이틀 이미지 URL</span>
|
||||
<span className="text-slate-400">{m.groupTitleImageUrlLabel}</span>
|
||||
<input
|
||||
className="w-full rounded border border-slate-300 bg-white px-2 py-1 text-xs text-slate-900 outline-none focus:border-sky-500 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
|
||||
value={radioProps.groupLabelImageUrl ?? ""}
|
||||
@@ -167,12 +167,12 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
|
||||
{source === "upload" && (
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-slate-400">그룹 타이틀 이미지 파일 업로드</span>
|
||||
<span className="text-slate-400">{m.groupTitleImageUploadLabel}</span>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="w-full text-[11px] text-slate-300 file:mr-2 file:rounded file:border file:border-slate-700 file:bg-slate-800 file:px-2 file:py-1 file:text-[11px] file:text-slate-100 hover:file:bg-slate-700"
|
||||
aria-label="그룹 타이틀 이미지 파일 업로드"
|
||||
aria-label={m.groupTitleImageUploadAria}
|
||||
onChange={async (event) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
@@ -212,7 +212,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
})()}
|
||||
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-slate-500 dark:text-slate-400">전송 키</span>
|
||||
<span className="text-slate-500 dark:text-slate-400">{m.submitKeyLabel}</span>
|
||||
<input
|
||||
className="w-full rounded border border-slate-300 bg-white px-2 py-1 text-xs text-slate-900 outline-none focus:border-sky-500 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100"
|
||||
value={radioProps.formFieldName ?? ""}
|
||||
@@ -226,7 +226,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-slate-500 dark:text-slate-400">옵션</span>
|
||||
<span className="text-slate-500 dark:text-slate-400">{m.optionsLabel}</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-red-900/60 dark:hover:border-red-700"
|
||||
@@ -235,7 +235,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
? [...(radioProps as any).options]
|
||||
: [];
|
||||
next.push({
|
||||
label: "새 옵션",
|
||||
label: "New option",
|
||||
value: `option_${next.length + 1}`,
|
||||
});
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -243,12 +243,12 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
} as any);
|
||||
}}
|
||||
>
|
||||
옵션 추가
|
||||
{m.addOptionButtonLabel}
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-slate-500 dark:text-slate-400">옵션 이미지 소스</span>
|
||||
<span className="text-slate-500 dark:text-slate-400">{m.optionImageSourceLabel}</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={(() => {
|
||||
@@ -263,8 +263,8 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
} as any)
|
||||
}
|
||||
>
|
||||
<option value="url">URL</option>
|
||||
<option value="upload">파일 업로드</option>
|
||||
<option value="url">{m.optionImageSourceOptionUrl}</option>
|
||||
<option value="upload">{m.optionImageSourceOptionUpload}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
@@ -273,7 +273,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
<div className="grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto] items-center gap-1">
|
||||
<input
|
||||
className="flex-1 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"
|
||||
placeholder="라벨"
|
||||
placeholder={m.optionLabelPlaceholder}
|
||||
value={opt.label ?? ""}
|
||||
onChange={(e) => {
|
||||
const next = [...(((radioProps as any).options ?? []) as any[])];
|
||||
@@ -288,7 +288,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
/>
|
||||
<input
|
||||
className="flex-1 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"
|
||||
placeholder="값(value)"
|
||||
placeholder={m.optionValuePlaceholder}
|
||||
value={opt.value ?? ""}
|
||||
onChange={(e) => {
|
||||
const next = [...(((radioProps as any).options ?? []) as any[])];
|
||||
@@ -325,7 +325,7 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
{source === "url" && (
|
||||
<input
|
||||
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"
|
||||
placeholder="옵션 이미지 URL (선택)"
|
||||
placeholder={m.optionImageUrlPlaceholder}
|
||||
value={opt.labelImageUrl ?? ""}
|
||||
onChange={(e) => {
|
||||
const next = [...(((radioProps as any).options ?? []) as any[])];
|
||||
@@ -341,12 +341,12 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
)}
|
||||
{source === "upload" && (
|
||||
<label className="flex flex-col gap-1">
|
||||
<span className="text-slate-500 dark:text-slate-400">옵션 이미지 파일 업로드</span>
|
||||
<span className="text-slate-500 dark:text-slate-400">{m.optionImageUploadLabel}</span>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="w-full text-[11px] text-slate-300 file:mr-2 file:rounded file:border file:border-slate-700 file:bg-slate-800 file:px-2 file:py-1 file:text-[11px] file:text-slate-100 hover:file:bg-slate-700"
|
||||
aria-label="옵션 이미지 파일 업로드"
|
||||
aria-label={m.optionImageUploadAria}
|
||||
onChange={async (event) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
@@ -442,9 +442,9 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
max={60}
|
||||
step={1}
|
||||
presets={[
|
||||
{ id: "tight", label: "타이트", value: 18 },
|
||||
{ id: "normal", label: "보통", value: 24 },
|
||||
{ id: "loose", label: "루즈", value: 32 },
|
||||
{ id: "tight", label: "Tight", value: 18 },
|
||||
{ id: "normal", label: "Normal", value: 24 },
|
||||
{ id: "loose", label: "Loose", value: 32 },
|
||||
]}
|
||||
onChangeValue={(v) => {
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -466,9 +466,9 @@ export function FormRadioPropertiesPanel({ block, selectedBlockId, updateBlock }
|
||||
max={20}
|
||||
step={0.5}
|
||||
presets={[
|
||||
{ id: "tight", label: "좁게", value: -1 },
|
||||
{ id: "normal", label: "보통", value: 0 },
|
||||
{ id: "wide", label: "넓게", value: 2 },
|
||||
{ id: "tight", label: "Tight", value: -1 },
|
||||
{ id: "normal", label: "Normal", value: 0 },
|
||||
{ id: "wide", label: "Wide", value: 2 },
|
||||
]}
|
||||
onChangeValue={(v) => {
|
||||
updateBlock(selectedBlockId, {
|
||||
|
||||
@@ -83,10 +83,10 @@ export function FormSelectPropertiesPanel({ block, selectedBlockId, updateBlock
|
||||
max={80}
|
||||
step={2}
|
||||
presets={[
|
||||
{ id: "tight", label: "타이트", value: 4 },
|
||||
{ id: "normal", label: "보통", value: 8 },
|
||||
{ id: "relaxed", label: "느슨", value: 12 },
|
||||
{ id: "extra", label: "넓게", value: 16 },
|
||||
{ id: "tight", label: "Tight", value: 4 },
|
||||
{ id: "normal", label: "Normal", value: 8 },
|
||||
{ id: "relaxed", label: "Relaxed", value: 12 },
|
||||
{ id: "extra", label: "Extra", value: 16 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -133,7 +133,7 @@ export function FormSelectPropertiesPanel({ block, selectedBlockId, updateBlock
|
||||
? [...(selectProps as any).options]
|
||||
: [];
|
||||
next.push({
|
||||
label: "새 옵션",
|
||||
label: "New option",
|
||||
value: `option_${next.length + 1}`,
|
||||
});
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -240,9 +240,9 @@ export function FormSelectPropertiesPanel({ block, selectedBlockId, updateBlock
|
||||
max={60}
|
||||
step={1}
|
||||
presets={[
|
||||
{ id: "tight", label: "타이트", value: 18 },
|
||||
{ id: "normal", label: "보통", value: 24 },
|
||||
{ id: "loose", label: "루즈", value: 32 },
|
||||
{ id: "tight", label: "Tight", value: 18 },
|
||||
{ id: "normal", label: "Normal", value: 24 },
|
||||
{ id: "loose", label: "Loose", value: 32 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -264,9 +264,9 @@ export function FormSelectPropertiesPanel({ block, selectedBlockId, updateBlock
|
||||
max={20}
|
||||
step={0.5}
|
||||
presets={[
|
||||
{ id: "tight", label: "좁게", value: -1 },
|
||||
{ id: "normal", label: "보통", value: 0 },
|
||||
{ id: "wide", label: "넓게", value: 2 },
|
||||
{ id: "tight", label: "Tight", value: -1 },
|
||||
{ id: "normal", label: "Normal", value: 0 },
|
||||
{ id: "wide", label: "Wide", value: 2 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -285,9 +285,9 @@ export function FormSelectPropertiesPanel({ 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</option>
|
||||
<option value="fixed">Fixed</option>
|
||||
</select>
|
||||
</label>
|
||||
{(selectProps.widthMode ?? "full") === "fixed" && (
|
||||
@@ -299,9 +299,9 @@ export function FormSelectPropertiesPanel({ block, selectedBlockId, updateBlock
|
||||
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, {
|
||||
@@ -318,11 +318,11 @@ export function FormSelectPropertiesPanel({ block, selectedBlockId, updateBlock
|
||||
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 thin", value: 6 },
|
||||
{ id: "sm", label: "Thin", value: 10 },
|
||||
{ id: "md", label: "Normal", value: 12 },
|
||||
{ id: "lg", label: "Wide", value: 16 },
|
||||
{ id: "xl", label: "Extra wide", value: 20 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -338,11 +338,11 @@ export function FormSelectPropertiesPanel({ block, selectedBlockId, updateBlock
|
||||
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 thin", value: 6 },
|
||||
{ id: "sm", label: "Thin", value: 8 },
|
||||
{ id: "md", label: "Normal", value: 10 },
|
||||
{ id: "lg", label: "Wide", value: 12 },
|
||||
{ id: "xl", label: "Extra wide", value: 16 },
|
||||
]}
|
||||
onChangeValue={(v) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
|
||||
+13
-8
@@ -206,7 +206,7 @@ function EditorPageInner() {
|
||||
}
|
||||
|
||||
updateProjectConfig({
|
||||
title: "새 페이지",
|
||||
title: "New page",
|
||||
slug: "my-landing",
|
||||
canvasPreset: "full",
|
||||
canvasBgColorHex: "#020617",
|
||||
@@ -1084,6 +1084,7 @@ function EditorPageInner() {
|
||||
listItemToolbarMoveDownLabel={m.listItemToolbarMoveDownLabel}
|
||||
listItemToolbarIndentLabel={m.listItemToolbarIndentLabel}
|
||||
listItemToolbarOutdentLabel={m.listItemToolbarOutdentLabel}
|
||||
imageBlockEmptyPreviewPlaceholder={m.imageBlockEmptyPreviewPlaceholder}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
@@ -1425,6 +1426,7 @@ interface SortableEditorBlockProps {
|
||||
listItemToolbarMoveDownLabel: string;
|
||||
listItemToolbarIndentLabel: string;
|
||||
listItemToolbarOutdentLabel: string;
|
||||
imageBlockEmptyPreviewPlaceholder: string;
|
||||
}
|
||||
|
||||
function SortableEditorBlock({
|
||||
@@ -1449,6 +1451,7 @@ function SortableEditorBlock({
|
||||
listItemToolbarMoveDownLabel,
|
||||
listItemToolbarIndentLabel,
|
||||
listItemToolbarOutdentLabel,
|
||||
imageBlockEmptyPreviewPlaceholder,
|
||||
}: SortableEditorBlockProps) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
|
||||
id: block.id,
|
||||
@@ -1560,7 +1563,7 @@ function SortableEditorBlock({
|
||||
<button
|
||||
type="button"
|
||||
className="mt-0.5 h-4 w-4 rounded border border-slate-300 bg-slate-50 text-[10px] flex items-center justify-center text-slate-500 hover:bg-slate-100 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-400 dark:hover:bg-slate-800"
|
||||
aria-label="블록 드래그 핸들"
|
||||
aria-label="Block drag handle"
|
||||
{...listeners}
|
||||
{...attributes}
|
||||
>
|
||||
@@ -1571,7 +1574,7 @@ function SortableEditorBlock({
|
||||
isEditing ? (
|
||||
<textarea
|
||||
className="w-full bg-transparent outline-none resize-none text-sm"
|
||||
aria-label="텍스트 블록 내용 편집"
|
||||
aria-label="Edit text block content"
|
||||
value={editingText}
|
||||
onChange={(e) => setEditingText(e.target.value)}
|
||||
onBlur={commitEditing}
|
||||
@@ -1715,8 +1718,8 @@ function SortableEditorBlock({
|
||||
const options = Array.isArray(selectProps.options) && selectProps.options.length > 0
|
||||
? selectProps.options
|
||||
: [
|
||||
{ label: "옵션 1", value: "option_1" },
|
||||
{ label: "옵션 2", value: "option_2" },
|
||||
{ label: "Option 1", value: "option_1" },
|
||||
{ label: "Option 2", value: "option_2" },
|
||||
];
|
||||
|
||||
const selectTokens = computeFormSelectEditorTokens(selectProps);
|
||||
@@ -2080,7 +2083,7 @@ function SortableEditorBlock({
|
||||
/>
|
||||
) : (
|
||||
<span className="text-[10px] text-slate-900 dark:text-slate-500 px-2 py-4">
|
||||
이미지 URL 을 입력하거나 파일을 업로드하면 여기에서 미리보기가 표시됩니다.
|
||||
{imageBlockEmptyPreviewPlaceholder}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -2147,7 +2150,7 @@ function SortableEditorBlock({
|
||||
: [
|
||||
{
|
||||
id: `${block.id}_item_1`,
|
||||
text: "리스트 아이템 1",
|
||||
text: "List item 1",
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
@@ -2350,6 +2353,8 @@ interface ColumnDroppableProps {
|
||||
function ColumnDroppable({ id, sectionId, columnId, basis, blocks, renderBlocks, span }: ColumnDroppableProps) {
|
||||
const { setNodeRef } = useDroppable({ id });
|
||||
const { over } = useDndContext();
|
||||
const locale = useAppLocale();
|
||||
const editorMessages = getEditorMessages(locale);
|
||||
|
||||
// 현재 드롭 대상이 이 컬럼이거나, 이 컬럼 안 블록들의 dropzone-before / dropzone-after 인 경우
|
||||
// 컬럼 박스를 강조해서 사용자가 어느 컬럼으로 이동 중인지 명확히 보여준다.
|
||||
@@ -2400,7 +2405,7 @@ function ColumnDroppable({ id, sectionId, columnId, basis, blocks, renderBlocks,
|
||||
>
|
||||
{blocks.length === 0 ? (
|
||||
<span className="text-[11px] px-2 text-center text-slate-900 dark:text-slate-300">
|
||||
{`컬럼 영역 (span ${span}/12)`}
|
||||
{`${editorMessages.columnAreaLabel} (span ${span}/12)`}
|
||||
</span>
|
||||
) : (
|
||||
<SortableContext items={blocks.map((b) => b.id)} strategy={verticalListSortingStrategy}>
|
||||
|
||||
@@ -50,43 +50,58 @@ export function BlocksSidebar() {
|
||||
const addFooterTemplateSection = useEditorStore((state) => state.addFooterTemplateSection);
|
||||
|
||||
// 버튼 핸들러를 useCallback으로 래핑해 불필요한 재생성을 줄인다.
|
||||
const handleAddText = useCallback(() => addTextBlock(), [addTextBlock]);
|
||||
const handleAddButton = useCallback(() => addButtonBlock(), [addButtonBlock]);
|
||||
const handleAddText = useCallback(() => addTextBlock(locale), [addTextBlock, locale]);
|
||||
const handleAddButton = useCallback(() => addButtonBlock(locale), [addButtonBlock, locale]);
|
||||
const handleAddImage = useCallback(() => addImageBlock(), [addImageBlock]);
|
||||
const handleAddVideo = useCallback(() => addVideoBlock(), [addVideoBlock]);
|
||||
const handleAddDivider = useCallback(() => addDividerBlock(), [addDividerBlock]);
|
||||
const handleAddList = useCallback(() => addListBlock(), [addListBlock]);
|
||||
const handleAddList = useCallback(() => addListBlock(locale), [addListBlock, locale]);
|
||||
const handleAddSection = useCallback(() => addSectionBlock(), [addSectionBlock]);
|
||||
|
||||
const handleAddFormBlock = useCallback(() => addFormBlock(), [addFormBlock]);
|
||||
const handleAddFormInput = useCallback(() => addFormInputBlock(), [addFormInputBlock]);
|
||||
const handleAddFormSelect = useCallback(() => addFormSelectBlock(), [addFormSelectBlock]);
|
||||
const handleAddFormRadio = useCallback(() => addFormRadioBlock(), [addFormRadioBlock]);
|
||||
const handleAddFormCheckbox = useCallback(() => addFormCheckboxBlock(), [addFormCheckboxBlock]);
|
||||
const handleAddFormBlock = useCallback(() => addFormBlock(locale), [addFormBlock, locale]);
|
||||
const handleAddFormInput = useCallback(() => addFormInputBlock(locale), [addFormInputBlock, locale]);
|
||||
const handleAddFormSelect = useCallback(() => addFormSelectBlock(locale), [addFormSelectBlock, locale]);
|
||||
const handleAddFormRadio = useCallback(() => addFormRadioBlock(locale), [addFormRadioBlock, locale]);
|
||||
const handleAddFormCheckbox = useCallback(
|
||||
() => addFormCheckboxBlock(locale),
|
||||
[addFormCheckboxBlock, locale],
|
||||
);
|
||||
|
||||
const handleAddHeroTemplate = useCallback(
|
||||
() => addHeroTemplateSection(),
|
||||
[addHeroTemplateSection],
|
||||
() => addHeroTemplateSection(locale),
|
||||
[addHeroTemplateSection, locale],
|
||||
);
|
||||
const handleAddFeaturesTemplate = useCallback(
|
||||
() => addFeaturesTemplateSection(),
|
||||
[addFeaturesTemplateSection],
|
||||
() => addFeaturesTemplateSection(locale),
|
||||
[addFeaturesTemplateSection, locale],
|
||||
);
|
||||
const handleAddCtaTemplate = useCallback(
|
||||
() => addCtaTemplateSection(locale),
|
||||
[addCtaTemplateSection, locale],
|
||||
);
|
||||
const handleAddFaqTemplate = useCallback(
|
||||
() => addFaqTemplateSection(locale),
|
||||
[addFaqTemplateSection, locale],
|
||||
);
|
||||
const handleAddCtaTemplate = useCallback(() => addCtaTemplateSection(), [addCtaTemplateSection]);
|
||||
const handleAddFaqTemplate = useCallback(() => addFaqTemplateSection(), [addFaqTemplateSection]);
|
||||
const handleAddPricingTemplate = useCallback(
|
||||
() => addPricingTemplateSection(),
|
||||
[addPricingTemplateSection],
|
||||
() => addPricingTemplateSection(locale),
|
||||
[addPricingTemplateSection, locale],
|
||||
);
|
||||
const handleAddTestimonialsTemplate = useCallback(
|
||||
() => addTestimonialsTemplateSection(),
|
||||
[addTestimonialsTemplateSection],
|
||||
() => addTestimonialsTemplateSection(locale),
|
||||
[addTestimonialsTemplateSection, locale],
|
||||
);
|
||||
const handleAddBlogTemplate = useCallback(
|
||||
() => addBlogTemplateSection(locale),
|
||||
[addBlogTemplateSection, locale],
|
||||
);
|
||||
const handleAddTeamTemplate = useCallback(
|
||||
() => addTeamTemplateSection(locale),
|
||||
[addTeamTemplateSection, locale],
|
||||
);
|
||||
const handleAddBlogTemplate = useCallback(() => addBlogTemplateSection(), [addBlogTemplateSection]);
|
||||
const handleAddTeamTemplate = useCallback(() => addTeamTemplateSection(), [addTeamTemplateSection]);
|
||||
const handleAddFooterTemplate = useCallback(
|
||||
() => addFooterTemplateSection(),
|
||||
[addFooterTemplateSection],
|
||||
() => addFooterTemplateSection(locale),
|
||||
[addFooterTemplateSection, locale],
|
||||
);
|
||||
|
||||
const [isBlocksOpen, setIsBlocksOpen] = useState(true);
|
||||
|
||||
@@ -88,9 +88,9 @@ export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBl
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="none">{m.backgroundVideoSourceOptionNone}</option>
|
||||
<option value="url">{m.backgroundVideoSourceOptionUrl}</option>
|
||||
<option value="upload">{m.backgroundVideoSourceOptionUpload}</option>
|
||||
<option value="none">{m.backgroundImageSourceOptionNone}</option>
|
||||
<option value="url">{m.backgroundImageSourceOptionUrl}</option>
|
||||
<option value="upload">{m.backgroundImageSourceOptionUpload}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
@@ -229,9 +229,9 @@ export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBl
|
||||
max={100}
|
||||
step={1}
|
||||
presets={[
|
||||
{ id: "left", label: "왼쪽", value: 0 },
|
||||
{ id: "center", label: "가운데", value: 50 },
|
||||
{ id: "right", label: "오른쪽", value: 100 },
|
||||
{ id: "left", label: m.backgroundImagePositionOptionLeft, value: 0 },
|
||||
{ id: "center", label: m.backgroundImagePositionOptionCenter, value: 50 },
|
||||
{ id: "right", label: m.backgroundImagePositionOptionRight, value: 100 },
|
||||
]}
|
||||
onChangeValue={(next) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -253,9 +253,9 @@ export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBl
|
||||
max={100}
|
||||
step={1}
|
||||
presets={[
|
||||
{ id: "top", label: "위", value: 0 },
|
||||
{ id: "center", label: "가운데", value: 50 },
|
||||
{ id: "bottom", label: "아래", value: 100 },
|
||||
{ id: "top", label: m.backgroundImagePositionOptionTop, value: 0 },
|
||||
{ id: "center", label: m.backgroundImagePositionOptionCenter, value: 50 },
|
||||
{ id: "bottom", label: m.backgroundImagePositionOptionBottom, value: 100 },
|
||||
]}
|
||||
onChangeValue={(next) =>
|
||||
updateBlock(selectedBlockId, {
|
||||
@@ -317,9 +317,9 @@ export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBl
|
||||
}
|
||||
}}
|
||||
>
|
||||
<option value="none">없음</option>
|
||||
<option value="url">URL</option>
|
||||
<option value="upload">파일 업로드</option>
|
||||
<option value="none">{m.backgroundVideoSourceOptionNone}</option>
|
||||
<option value="url">{m.backgroundVideoSourceOptionUrl}</option>
|
||||
<option value="upload">{m.backgroundVideoSourceOptionUpload}</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
@@ -387,7 +387,7 @@ export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBl
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 세로 패딩 슬라이더 (px) - 프리셋 + 자유 슬라이더 */}
|
||||
{/* 세로 패딩 슬라이더 - 프리셋 + 자유 슬라이더 */}
|
||||
<div className="mt-3 space-y-1">
|
||||
<NumericPropertyControl
|
||||
label={m.paddingYLabel}
|
||||
@@ -397,11 +397,11 @@ export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBl
|
||||
max={80}
|
||||
step={2}
|
||||
presets={[
|
||||
{ id: "x-tight", label: "매우 좁게", value: 16 },
|
||||
{ id: "tight", label: "좁게", value: 32 },
|
||||
{ id: "normal", label: "보통", value: 48 },
|
||||
{ id: "relaxed", label: "넓게", value: 64 },
|
||||
{ id: "x-relaxed", label: "아주 넓게", value: 80 },
|
||||
{ id: "x-tight", label: m.paddingYPresetExtraTight, value: 16 },
|
||||
{ id: "tight", label: m.paddingYPresetTight, value: 32 },
|
||||
{ id: "normal", label: m.paddingYPresetNormal, value: 48 },
|
||||
{ id: "relaxed", label: m.paddingYPresetRelaxed, value: 64 },
|
||||
{ id: "x-relaxed", label: m.paddingYPresetExtraRelaxed, value: 80 },
|
||||
]}
|
||||
onChangeValue={(next) => {
|
||||
const safe = Number.isFinite(next) ? next : 48;
|
||||
@@ -682,7 +682,7 @@ export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBl
|
||||
});
|
||||
})()}
|
||||
|
||||
{/* 최대 폭 슬라이더 (px) - 프리셋 + 자유 슬라이더 */}
|
||||
{/* 최대 폭 슬라이더 - 프리셋 + 자유 슬라이더 */}
|
||||
<NumericPropertyControl
|
||||
label={m.maxWidthLabel}
|
||||
unitLabel={m.maxWidthUnitLabel}
|
||||
@@ -691,11 +691,11 @@ export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBl
|
||||
max={1440}
|
||||
step={16}
|
||||
presets={[
|
||||
{ id: "x-narrow", label: "매우 좁게", value: 640 },
|
||||
{ id: "narrow", label: "좁게", value: 800 },
|
||||
{ id: "normal", label: "보통", value: 960 },
|
||||
{ id: "wide", label: "넓게", value: 1200 },
|
||||
{ id: "x-wide", label: "아주 넓게", value: 1440 },
|
||||
{ id: "x-narrow", label: m.maxWidthPresetExtraNarrow, value: 640 },
|
||||
{ id: "narrow", label: m.maxWidthPresetNarrow, value: 800 },
|
||||
{ id: "normal", label: m.maxWidthPresetNormal, value: 960 },
|
||||
{ id: "wide", label: m.maxWidthPresetWide, value: 1200 },
|
||||
{ id: "x-wide", label: m.maxWidthPresetExtraWide, value: 1440 },
|
||||
]}
|
||||
onChangeValue={(next) => {
|
||||
const safe = Number.isFinite(next) ? next : 960;
|
||||
@@ -703,7 +703,7 @@ export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBl
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 컬럼 간 간격 슬라이더 (px) - 프리셋 + 자유 슬라이더 */}
|
||||
{/* 컬럼 간 간격 슬라이더 - 프리셋 + 자유 슬라이더 */}
|
||||
<NumericPropertyControl
|
||||
label={m.gapXLabel}
|
||||
unitLabel={m.gapXUnitLabel}
|
||||
@@ -712,12 +712,12 @@ export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBl
|
||||
max={64}
|
||||
step={2}
|
||||
presets={[
|
||||
{ id: "zero", label: "0", value: 0 },
|
||||
{ id: "tight", label: "좁게", value: 16 },
|
||||
{ id: "normal", label: "보통", value: 24 },
|
||||
{ id: "relaxed", label: "넓게", value: 32 },
|
||||
{ id: "x-relaxed", label: "아주 넓게", value: 48 },
|
||||
{ id: "max", label: "최대", value: 64 },
|
||||
{ id: "zero", label: m.gapXPresetZero, value: 0 },
|
||||
{ id: "tight", label: m.gapXPresetTight, value: 16 },
|
||||
{ id: "normal", label: m.gapXPresetNormal, value: 24 },
|
||||
{ id: "relaxed", label: m.gapXPresetRelaxed, value: 32 },
|
||||
{ id: "x-relaxed", label: m.gapXPresetExtraRelaxed, value: 48 },
|
||||
{ id: "max", label: m.gapXPresetMax, value: 64 },
|
||||
]}
|
||||
onChangeValue={(next) => {
|
||||
const safe = Number.isFinite(next) ? next : 24;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import type { Block, SectionBlockProps, TextBlockProps, ButtonBlockProps } from "@/features/editor/state/editorStore";
|
||||
import type { EditorPricingTemplateMessages } from "@/features/i18n/messages/editorTemplates";
|
||||
|
||||
export function createPricingTemplateBlocks(opts: {
|
||||
sectionId: string;
|
||||
createId: () => string;
|
||||
messages: EditorPricingTemplateMessages;
|
||||
}): { blocks: Block[]; lastSelectedId: string } {
|
||||
const { sectionId, createId } = opts;
|
||||
const { sectionId, createId, messages } = opts;
|
||||
|
||||
const columns: SectionBlockProps["columns"] = [
|
||||
{ id: `${sectionId}_col_1`, span: 4 },
|
||||
@@ -33,11 +35,7 @@ export function createPricingTemplateBlocks(opts: {
|
||||
columnId: null,
|
||||
};
|
||||
|
||||
const planDefinitions: Array<{ name: string; price: string; popular?: boolean }> = [
|
||||
{ name: "Basic", price: "₩9,900/월" },
|
||||
{ name: "Pro", price: "₩29,900/월", popular: true },
|
||||
{ name: "Enterprise", price: "문의" },
|
||||
];
|
||||
const planDefinitions = messages.plans;
|
||||
|
||||
const pricingBlocks: Block[] = columns.flatMap((col, index) => {
|
||||
const plan = planDefinitions[index] ?? planDefinitions[0];
|
||||
@@ -96,7 +94,7 @@ export function createPricingTemplateBlocks(opts: {
|
||||
// Features list (simple text for now)
|
||||
const featuresId = createId();
|
||||
const featuresProps: TextBlockProps = {
|
||||
text: "• 모든 기본 기능\n• 이메일 지원\n• 1GB 스토리지",
|
||||
text: messages.featuresText,
|
||||
align: "center",
|
||||
size: "sm",
|
||||
color: "muted",
|
||||
@@ -112,7 +110,7 @@ export function createPricingTemplateBlocks(opts: {
|
||||
// Button
|
||||
const buttonId = createId();
|
||||
const buttonProps: ButtonBlockProps = {
|
||||
label: isPopular ? "시작하기" : "선택하기",
|
||||
label: isPopular ? messages.primaryButtonLabel : messages.secondaryButtonLabel,
|
||||
href: "#",
|
||||
align: "center",
|
||||
size: "md",
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import type { Block, SectionBlockProps, TextBlockProps } from "@/features/editor/state/editorStore";
|
||||
import type { EditorTestimonialsTemplateMessages } from "@/features/i18n/messages/editorTemplates";
|
||||
|
||||
export function createTestimonialsTemplateBlocks(opts: {
|
||||
sectionId: string;
|
||||
createId: () => string;
|
||||
messages: EditorTestimonialsTemplateMessages;
|
||||
}): { blocks: Block[]; lastSelectedId: string } {
|
||||
const { sectionId, createId } = opts;
|
||||
const { sectionId, createId, messages } = opts;
|
||||
|
||||
const columns: SectionBlockProps["columns"] = [
|
||||
{ id: `${sectionId}_col_1`, span: 4 },
|
||||
@@ -33,11 +35,7 @@ export function createTestimonialsTemplateBlocks(opts: {
|
||||
columnId: null,
|
||||
};
|
||||
|
||||
const testimonialDefinitions: Array<{ body: string; author: string }> = [
|
||||
{ body: "이 서비스 덕분에 랜딩 페이지 제작 시간이 크게 줄었습니다.", author: "홍길동" },
|
||||
{ body: "디자인을 잘 못해도 깔끔한 페이지를 만들 수 있어요.", author: "김영희" },
|
||||
{ body: "팀 전체가 만족하는 빌더입니다.", author: "이철수" },
|
||||
];
|
||||
const testimonialDefinitions = messages.items;
|
||||
|
||||
const testimonialBlocks: Block[] = columns.flatMap((col, index) => {
|
||||
const t = testimonialDefinitions[index] ?? testimonialDefinitions[0];
|
||||
|
||||
+7
-2
@@ -1,7 +1,7 @@
|
||||
import "../styles/globals.css";
|
||||
import "../styles/builder.css";
|
||||
import type { ReactNode } from "react";
|
||||
import { headers } from "next/headers";
|
||||
import { headers, cookies } from "next/headers";
|
||||
import { LocaleProvider } from "@/features/i18n/LocaleProvider";
|
||||
import { LocaleSwitcher } from "@/features/i18n/LocaleSwitcher";
|
||||
import { resolveLocaleFromAcceptLanguage, DEFAULT_LOCALE } from "@/features/i18n/locale";
|
||||
@@ -12,9 +12,14 @@ export const metadata = {
|
||||
};
|
||||
|
||||
export default async function RootLayout({ children }: { children: ReactNode }) {
|
||||
const cookieStore = await cookies();
|
||||
const cookieLocale = cookieStore.get("pb-locale")?.value;
|
||||
|
||||
const headerList = await headers();
|
||||
const acceptLanguage = headerList.get("accept-language");
|
||||
const locale = resolveLocaleFromAcceptLanguage(acceptLanguage ?? undefined) ?? DEFAULT_LOCALE;
|
||||
|
||||
const localeFromCookie = cookieLocale === "en" || cookieLocale === "ko" ? cookieLocale : null;
|
||||
const locale = (localeFromCookie ?? resolveLocaleFromAcceptLanguage(acceptLanguage ?? undefined)) ?? DEFAULT_LOCALE;
|
||||
|
||||
return (
|
||||
<html lang={locale} suppressHydrationWarning>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { getPublicPageRendererMessages } from "@/features/i18n/messages/publicPageRenderer";
|
||||
|
||||
interface PublicProjectPageClientProps {
|
||||
html: string;
|
||||
}
|
||||
|
||||
export default function PublicProjectPageClient({ html }: PublicProjectPageClientProps) {
|
||||
const m = getPublicPageRendererMessages(null);
|
||||
const [toastMessage, setToastMessage] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -58,21 +60,18 @@ export default function PublicProjectPageClient({ html }: PublicProjectPageClien
|
||||
const ok = data && data.ok;
|
||||
const message = data && data.message;
|
||||
if (ok) {
|
||||
const success =
|
||||
message || (config && config.successMessage) || "성공적으로 전송되었습니다.";
|
||||
const success = message || (config && config.successMessage) || m.submitSuccessDefault;
|
||||
showToast(success);
|
||||
try {
|
||||
form.reset();
|
||||
} catch {}
|
||||
} else {
|
||||
const errorMsg =
|
||||
message || (config && config.errorMessage) || "전송 중 오류가 발생했습니다.";
|
||||
const errorMsg = message || (config && config.errorMessage) || m.submitErrorDefault;
|
||||
showToast(errorMsg);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
const errorMsg =
|
||||
(config && config.errorMessage) || "전송 중 오류가 발생했습니다.";
|
||||
const errorMsg = (config && config.errorMessage) || m.submitErrorDefault;
|
||||
showToast(errorMsg);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,6 +4,9 @@ import { useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter, useParams } from "next/navigation";
|
||||
import { FolderKanban, LayoutDashboard, ListChecks, SunMoon } from "lucide-react";
|
||||
import { useAppLocale } from "@/features/i18n/LocaleProvider";
|
||||
import { getDashboardMessages } from "@/features/i18n/messages/dashboard";
|
||||
import { getSubmissionsMessages } from "@/features/i18n/messages/submissions";
|
||||
|
||||
// 프로젝트별 폼 제출 내역을 조회해서 보여주는 페이지 컴포넌트.
|
||||
// - URL 의 slug 파라미터를 기준으로 /api/projects/[slug]/submissions 에 요청을 보낸다.
|
||||
@@ -25,6 +28,10 @@ export default function ProjectSubmissionsPage() {
|
||||
const params = useParams() as { slug?: string } | null;
|
||||
const slug = (params?.slug ?? "").toString();
|
||||
|
||||
const locale = useAppLocale();
|
||||
const t = getDashboardMessages(locale);
|
||||
const m = getSubmissionsMessages(locale);
|
||||
|
||||
const [submissions, setSubmissions] = useState<SubmissionItem[]>([]);
|
||||
const [status, setStatus] = useState<PageStatus>("idle");
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
@@ -48,21 +55,19 @@ export default function ProjectSubmissionsPage() {
|
||||
|
||||
if (res.status === 401) {
|
||||
setStatus("error");
|
||||
setErrorMessage("폼 제출 내역을 조회하려면 로그인이 필요합니다. 다시 로그인해 주세요.");
|
||||
setErrorMessage(m.errorUnauthorized);
|
||||
router.push("/login");
|
||||
return;
|
||||
}
|
||||
|
||||
if (res.status === 404) {
|
||||
setStatus("error");
|
||||
setErrorMessage(
|
||||
"프로젝트를 찾을 수 없거나 접근 권한이 없습니다. 프로젝트 소유자 계정으로 다시 로그인해 주세요.",
|
||||
);
|
||||
setErrorMessage(m.errorGeneric);
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus("error");
|
||||
setErrorMessage("폼 제출 내역을 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
|
||||
setErrorMessage(m.errorGeneric);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,7 +81,7 @@ export default function ProjectSubmissionsPage() {
|
||||
} catch {
|
||||
if (!cancelled) {
|
||||
setStatus("error");
|
||||
setErrorMessage("폼 제출 내역을 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
|
||||
setErrorMessage(m.errorGeneric);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -112,13 +117,13 @@ export default function ProjectSubmissionsPage() {
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
setErrorMessage("로그아웃 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
|
||||
setErrorMessage(m.errorLogout);
|
||||
return;
|
||||
}
|
||||
|
||||
router.push("/login");
|
||||
} catch {
|
||||
setErrorMessage("로그아웃 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.");
|
||||
setErrorMessage(m.errorLogout);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,8 +144,8 @@ export default function ProjectSubmissionsPage() {
|
||||
<main className="min-h-screen flex flex-col bg-slate-100 text-slate-900 dark:bg-slate-950 dark:text-slate-50">
|
||||
<header className="border-b border-slate-200 px-6 py-4 flex items-center justify-between bg-white/80 backdrop-blur dark:border-slate-800 dark:bg-slate-950/80">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight">폼 제출 내역</h1>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">프로젝트에 연결된 폼으로 수집된 제출 데이터를 확인할 수 있습니다.</p>
|
||||
<h1 className="text-2xl font-bold tracking-tight">{m.headerTitle}</h1>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">{m.headerDescription}</p>
|
||||
<p className="mt-1 text-[11px] font-mono text-slate-500 dark:text-slate-400">{slug}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
@@ -150,21 +155,21 @@ export default function ProjectSubmissionsPage() {
|
||||
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium text-slate-700 hover:bg-slate-100 hover:text-sky-700 dark:text-slate-200 dark:hover:bg-slate-800 dark:hover:text-sky-200"
|
||||
>
|
||||
<LayoutDashboard className="w-4 h-4" aria-hidden="true" />
|
||||
<span>대시보드</span>
|
||||
<span>{t.navDashboard}</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/projects"
|
||||
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium text-slate-700 hover:bg-slate-100 hover:text-sky-700 dark:text-slate-200 dark:hover:bg-slate-800 dark:hover:text-sky-200"
|
||||
>
|
||||
<FolderKanban className="w-4 h-4" aria-hidden="true" />
|
||||
<span>프로젝트 목록</span>
|
||||
<span>{t.navProjects}</span>
|
||||
</Link>
|
||||
<Link
|
||||
href="/projects/submissions"
|
||||
className="inline-flex items-center gap-1 px-3 py-1.5 rounded-full text-sm font-medium text-emerald-700 hover:bg-emerald-50 hover:text-emerald-800 dark:text-emerald-200 dark:hover:bg-emerald-900/60 dark:hover:text-emerald-100"
|
||||
>
|
||||
<ListChecks className="w-4 h-4" aria-hidden="true" />
|
||||
<span>전체 제출 내역</span>
|
||||
<span>{t.navSubmissions}</span>
|
||||
</Link>
|
||||
</nav>
|
||||
<button
|
||||
@@ -173,7 +178,7 @@ export default function ProjectSubmissionsPage() {
|
||||
onClick={handleToggleTheme}
|
||||
>
|
||||
<SunMoon className="w-4 h-4" aria-hidden="true" />
|
||||
<span>테마 전환</span>
|
||||
<span>{t.themeToggleLabel}</span>
|
||||
</button>
|
||||
<div className="relative">
|
||||
<button
|
||||
@@ -183,7 +188,7 @@ export default function ProjectSubmissionsPage() {
|
||||
setIsMenuOpen((prev) => !prev);
|
||||
}}
|
||||
>
|
||||
메뉴
|
||||
{t.menuLabel}
|
||||
</button>
|
||||
{isMenuOpen && (
|
||||
<div className="absolute right-0 mt-1 w-32 rounded border border-slate-200 bg-white shadow-lg z-20 dark:border-slate-700 dark:bg-slate-900/95">
|
||||
@@ -195,7 +200,7 @@ export default function ProjectSubmissionsPage() {
|
||||
void handleLogout();
|
||||
}}
|
||||
>
|
||||
로그아웃
|
||||
{t.logoutLabel}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
@@ -209,23 +214,23 @@ export default function ProjectSubmissionsPage() {
|
||||
)}
|
||||
|
||||
{status === "loading" && (
|
||||
<p className="text-xs text-slate-500 mb-3 dark:text-slate-400">폼 제출 내역을 불러오는 중입니다...</p>
|
||||
<p className="text-xs text-slate-500 mb-3 dark:text-slate-400">{m.loadingText}</p>
|
||||
)}
|
||||
|
||||
{submissions.length === 0 && status === "idle" && !errorMessage && (
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">아직 수집된 폼 제출 내역이 없습니다.</p>
|
||||
<p className="text-xs text-slate-500 dark:text-slate-400">{m.emptyText}</p>
|
||||
)}
|
||||
|
||||
{submissions.length > 0 && (
|
||||
<table className="w-full text-xs text-left border-collapse mt-2">
|
||||
<thead>
|
||||
<tr className="border-b border-slate-200 text-slate-600 dark:border-slate-800 dark:text-slate-400">
|
||||
<th className="py-2 pr-4">제출 시각</th>
|
||||
<th className="py-2 pr-4">이름</th>
|
||||
<th className="py-2 pr-4">이메일</th>
|
||||
<th className="py-2 pr-4">전화번호</th>
|
||||
<th className="py-2 pr-4">생년월일</th>
|
||||
<th className="py-2 pr-4">기타 필드</th>
|
||||
<th className="py-2 pr-4">{m.tableHeaderCreatedAt}</th>
|
||||
<th className="py-2 pr-4">{m.tableHeaderName}</th>
|
||||
<th className="py-2 pr-4">{m.tableHeaderEmail}</th>
|
||||
<th className="py-2 pr-4">{m.tableHeaderPhone}</th>
|
||||
<th className="py-2 pr-4">{m.tableHeaderBirthdate}</th>
|
||||
<th className="py-2 pr-4">{m.tableHeaderOtherFields}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -84,7 +84,7 @@ export function NumericPropertyControl({
|
||||
<PropertySliderField
|
||||
label=""
|
||||
ariaLabelSlider={`${label} 슬라이더`}
|
||||
ariaLabelInput={`${label} 커스텀${unitLabel ? ` ${unitLabel}` : ""}`}
|
||||
ariaLabelInput={`${label} 커스텀`}
|
||||
value={Number.isFinite(value) ? value : 0}
|
||||
min={min}
|
||||
max={max}
|
||||
|
||||
@@ -847,7 +847,7 @@ export function PublicPageRenderer({ blocks, projectSlug }: PublicPageRendererPr
|
||||
: [
|
||||
{
|
||||
id: `${block.id}_item_1`,
|
||||
text: "리스트 아이템 1",
|
||||
text: "List item 1",
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -10,7 +10,8 @@ import { createFaqTemplateBlocks } from "@/app/editor/templates/faqTemplate";
|
||||
import { createPricingTemplateBlocks } from "@/app/editor/templates/pricingTemplate";
|
||||
import { createTestimonialsTemplateBlocks } from "@/app/editor/templates/testimonialsTemplate";
|
||||
import { getEditorTemplatesMessages } from "@/features/i18n/messages/editorTemplates";
|
||||
import { DEFAULT_LOCALE } from "@/features/i18n/locale";
|
||||
import { getEditorBlockDefaultsMessages } from "@/features/i18n/messages/editorBlockDefaults";
|
||||
import { DEFAULT_LOCALE, type AppLocale } from "@/features/i18n/locale";
|
||||
|
||||
// 블록 타입 정의: 텍스트/버튼/이미지/비디오/섹션/구분선/리스트/폼 블록/폼 요소 블록
|
||||
export type BlockType =
|
||||
@@ -739,27 +740,27 @@ export interface EditorState {
|
||||
future: Block[][];
|
||||
projectConfig: ProjectConfig;
|
||||
updateProjectConfig: (partial: Partial<ProjectConfig>) => void;
|
||||
addTextBlock: () => void;
|
||||
addButtonBlock: () => void;
|
||||
addTextBlock: (locale?: AppLocale | null) => void;
|
||||
addButtonBlock: (locale?: AppLocale | null) => void;
|
||||
addImageBlock: () => void;
|
||||
addVideoBlock: () => void;
|
||||
addDividerBlock: () => void;
|
||||
addListBlock: () => void;
|
||||
addListBlock: (locale?: AppLocale | null) => void;
|
||||
addSectionBlock: () => void;
|
||||
addFormBlock: () => void;
|
||||
addFormInputBlock: () => void;
|
||||
addFormSelectBlock: () => void;
|
||||
addFormCheckboxBlock: () => void;
|
||||
addFormRadioBlock: () => void;
|
||||
addHeroTemplateSection: () => void;
|
||||
addFeaturesTemplateSection: () => void;
|
||||
addCtaTemplateSection: () => void;
|
||||
addFaqTemplateSection: () => void;
|
||||
addPricingTemplateSection: () => void;
|
||||
addTestimonialsTemplateSection: () => void;
|
||||
addBlogTemplateSection: () => void;
|
||||
addTeamTemplateSection: () => void;
|
||||
addFooterTemplateSection: () => void;
|
||||
addFormBlock: (locale?: AppLocale | null) => void;
|
||||
addFormInputBlock: (locale?: AppLocale | null) => void;
|
||||
addFormSelectBlock: (locale?: AppLocale | null) => void;
|
||||
addFormCheckboxBlock: (locale?: AppLocale | null) => void;
|
||||
addFormRadioBlock: (locale?: AppLocale | null) => void;
|
||||
addHeroTemplateSection: (locale?: AppLocale | null) => void;
|
||||
addFeaturesTemplateSection: (locale?: AppLocale | null) => void;
|
||||
addCtaTemplateSection: (locale?: AppLocale | null) => void;
|
||||
addFaqTemplateSection: (locale?: AppLocale | null) => void;
|
||||
addPricingTemplateSection: (locale?: AppLocale | null) => void;
|
||||
addTestimonialsTemplateSection: (locale?: AppLocale | null) => void;
|
||||
addBlogTemplateSection: (locale?: AppLocale | null) => void;
|
||||
addTeamTemplateSection: (locale?: AppLocale | null) => void;
|
||||
addFooterTemplateSection: (locale?: AppLocale | null) => void;
|
||||
selectListItem: (itemId: string | null) => void;
|
||||
indentSelectedListItem: (blockId: string) => void;
|
||||
outdentSelectedListItem: (blockId: string) => void;
|
||||
@@ -819,6 +820,8 @@ const getNextFormFieldName = (blocks: Block[], prefix: string): string => {
|
||||
return `${prefix}-${max + 1}`;
|
||||
};
|
||||
|
||||
const isKoLocale = (locale?: AppLocale | null): boolean => (locale ?? DEFAULT_LOCALE) === "ko";
|
||||
|
||||
// 에디터 스토어 생성 함수 (테스트 및 앱에서 공유 사용)
|
||||
// set/get 은 zustand 내부 구현에 의해 주입되므로, 여기서는 any 로 완화해 사용한다.
|
||||
const createEditorState = (set: any, get: any): EditorState => {
|
||||
@@ -837,7 +840,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
history: [],
|
||||
future: [],
|
||||
projectConfig: {
|
||||
title: "새 페이지",
|
||||
title: "New page",
|
||||
slug: "my-landing",
|
||||
canvasPreset: "full",
|
||||
canvasBgColorHex: "#020617",
|
||||
@@ -862,7 +865,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// 텍스트 블록 추가: 기본 텍스트와 함께 생성 후 선택 상태로 만든다
|
||||
addTextBlock: () => {
|
||||
addTextBlock: (locale?: AppLocale | null) => {
|
||||
const id = createId();
|
||||
|
||||
const { selectedBlockId, blocks } = get();
|
||||
@@ -883,11 +886,12 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const blockDefaults = getEditorBlockDefaultsMessages(locale);
|
||||
const newBlock: Block = {
|
||||
id,
|
||||
type: "text",
|
||||
props: {
|
||||
text: "새 텍스트",
|
||||
text: blockDefaults.text.defaultText,
|
||||
align: "left",
|
||||
size: "base",
|
||||
},
|
||||
@@ -903,7 +907,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// Hero 템플릿 섹션 추가: 섹션 1개와 기본 텍스트/버튼 블록들을 첫 컬럼에 배치한다
|
||||
addHeroTemplateSection: () => {
|
||||
addHeroTemplateSection: (locale?: AppLocale | null) => {
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
@@ -916,7 +920,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
|
||||
const tpl = getEditorTemplatesMessages(locale);
|
||||
const { blocks: templateBlocks, lastSelectedId } = createHeroTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
@@ -942,7 +946,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// Features 템플릿 섹션 추가: 3컬럼(4/4/4) 섹션과 각 컬럼의 제목/설명 텍스트를 생성한다
|
||||
addFeaturesTemplateSection: () => {
|
||||
addFeaturesTemplateSection: (locale?: AppLocale | null) => {
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
@@ -955,7 +959,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
|
||||
const tpl = getEditorTemplatesMessages(locale);
|
||||
const { blocks: templateBlocks, lastSelectedId } = createFeaturesTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
@@ -981,7 +985,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// Blog 템플릿 섹션 추가: 3컬럼 포스트 카드(제목/요약)를 생성한다
|
||||
addBlogTemplateSection: () => {
|
||||
addBlogTemplateSection: (locale?: AppLocale | null) => {
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
@@ -994,7 +998,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
|
||||
const tpl = getEditorTemplatesMessages(locale);
|
||||
const { blocks: templateBlocks, lastSelectedId } = createBlogTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
@@ -1020,7 +1024,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// Team 템플릿 섹션 추가: 3컬럼 팀 카드(이름/역할/소개)를 생성한다
|
||||
addTeamTemplateSection: () => {
|
||||
addTeamTemplateSection: (locale?: AppLocale | null) => {
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
@@ -1033,7 +1037,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
|
||||
const tpl = getEditorTemplatesMessages(locale);
|
||||
const { blocks: templateBlocks, lastSelectedId } = createTeamTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
@@ -1059,7 +1063,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// Footer 템플릿 섹션 추가: 링크/카피라이트 텍스트가 포함된 1컬럼 섹션을 생성한다
|
||||
addFooterTemplateSection: () => {
|
||||
addFooterTemplateSection: (locale?: AppLocale | null) => {
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
@@ -1072,7 +1076,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
|
||||
const tpl = getEditorTemplatesMessages(locale);
|
||||
const { blocks: templateBlocks, lastSelectedId } = createFooterTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
@@ -1098,7 +1102,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// CTA 템플릿 섹션 추가: 텍스트와 버튼이 포함된 1컬럼 섹션을 생성한다
|
||||
addCtaTemplateSection: () => {
|
||||
addCtaTemplateSection: (locale?: AppLocale | null) => {
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
@@ -1111,7 +1115,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
|
||||
const tpl = getEditorTemplatesMessages(locale);
|
||||
const { blocks: templateBlocks, lastSelectedId } = createCtaTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
@@ -1137,7 +1141,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// FAQ 템플릿 섹션 추가: 질문/답변 쌍이 수직으로 나열된 섹션을 생성한다
|
||||
addFaqTemplateSection: () => {
|
||||
addFaqTemplateSection: (locale?: AppLocale | null) => {
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
@@ -1150,7 +1154,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const tpl = getEditorTemplatesMessages(DEFAULT_LOCALE);
|
||||
const tpl = getEditorTemplatesMessages(locale);
|
||||
const { blocks: templateBlocks, lastSelectedId } = createFaqTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
@@ -1176,7 +1180,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// Pricing 템플릿 섹션 추가: 3컬럼 요금제 카드(플랜 이름/가격/설명)를 생성한다
|
||||
addPricingTemplateSection: () => {
|
||||
addPricingTemplateSection: (locale?: AppLocale | null) => {
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
@@ -1189,9 +1193,11 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const tpl = getEditorTemplatesMessages(locale);
|
||||
const { blocks: templateBlocks, lastSelectedId } = createPricingTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
messages: tpl.pricing,
|
||||
});
|
||||
|
||||
pushHistoryImpl(set, get);
|
||||
@@ -1213,7 +1219,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// Testimonials 템플릿 섹션 추가: 3컬럼 후기 카드(본문/작성자)를 생성한다
|
||||
addTestimonialsTemplateSection: () => {
|
||||
addTestimonialsTemplateSection: (locale?: AppLocale | null) => {
|
||||
const { selectedBlockId, blocks } = get();
|
||||
let sectionId = createId();
|
||||
let replacingSectionId: string | null = null;
|
||||
@@ -1226,9 +1232,11 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const tpl = getEditorTemplatesMessages(locale);
|
||||
const { blocks: templateBlocks, lastSelectedId } = createTestimonialsTemplateBlocks({
|
||||
sectionId,
|
||||
createId,
|
||||
messages: tpl.testimonials,
|
||||
});
|
||||
|
||||
pushHistoryImpl(set, get);
|
||||
@@ -1276,7 +1284,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
type: "image",
|
||||
props: {
|
||||
src: "",
|
||||
alt: "이미지 설명",
|
||||
alt: "Image description",
|
||||
align: "center",
|
||||
widthMode: "auto",
|
||||
borderRadius: "md",
|
||||
@@ -1340,11 +1348,12 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// 폼 입력 블록 추가: 루트에 기본 label/formFieldName 과 함께 생성 후 선택 상태로 만든다
|
||||
addFormInputBlock: () => {
|
||||
addFormInputBlock: (locale?: AppLocale | null) => {
|
||||
const id = createId();
|
||||
const { blocks } = get();
|
||||
|
||||
const label = "입력 필드";
|
||||
const blockDefaults = getEditorBlockDefaultsMessages(locale);
|
||||
const label = blockDefaults.formInput.label;
|
||||
const formFieldName = getNextFormFieldName(blocks, "text");
|
||||
|
||||
const newBlock: Block = {
|
||||
@@ -1380,15 +1389,17 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// 폼 셀렉트 블록 추가: 루트에 기본 label/formFieldName/options 와 함께 생성 후 선택 상태로 만든다
|
||||
addFormSelectBlock: () => {
|
||||
addFormSelectBlock: (locale?: AppLocale | null) => {
|
||||
const id = createId();
|
||||
const { blocks } = get();
|
||||
|
||||
const label = "선택 필드";
|
||||
const blockDefaults = getEditorBlockDefaultsMessages(locale);
|
||||
const selectDefaults = blockDefaults.formSelect;
|
||||
const label = selectDefaults.label;
|
||||
const formFieldName = getNextFormFieldName(blocks, "select");
|
||||
const options: FormSelectOption[] = [
|
||||
{ label: "옵션 1", value: "option_1" },
|
||||
{ label: "옵션 2", value: "option_2" },
|
||||
{ label: selectDefaults.option1Label, value: "option_1" },
|
||||
{ label: selectDefaults.option2Label, value: "option_2" },
|
||||
];
|
||||
|
||||
const newBlock: Block = {
|
||||
@@ -1422,15 +1433,17 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// 폼 체크박스 블록 추가: 루트에 기본 groupLabel/formFieldName/options 와 함께 생성 후 선택 상태로 만든다
|
||||
addFormCheckboxBlock: () => {
|
||||
addFormCheckboxBlock: (locale?: AppLocale | null) => {
|
||||
const id = createId();
|
||||
const { blocks } = get();
|
||||
|
||||
const groupLabel = "체크박스";
|
||||
const blockDefaults = getEditorBlockDefaultsMessages(locale);
|
||||
const checkboxDefaults = blockDefaults.formCheckbox;
|
||||
const groupLabel = checkboxDefaults.groupLabel;
|
||||
const formFieldName = getNextFormFieldName(blocks, "checkbox");
|
||||
const options: FormCheckboxOption[] = [
|
||||
{ label: "체크 1", value: "check_1" },
|
||||
{ label: "체크 2", value: "check_2" },
|
||||
{ label: checkboxDefaults.option1Label, value: "check_1" },
|
||||
{ label: checkboxDefaults.option2Label, value: "check_2" },
|
||||
];
|
||||
|
||||
const newBlock: Block = {
|
||||
@@ -1465,15 +1478,17 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// 폼 라디오 그룹 블록 추가: 루트에 기본 groupLabel/formFieldName/options 와 함께 생성 후 선택 상태로 만든다
|
||||
addFormRadioBlock: () => {
|
||||
addFormRadioBlock: (locale?: AppLocale | null) => {
|
||||
const id = createId();
|
||||
const { blocks } = get();
|
||||
|
||||
const groupLabel = "라디오 그룹";
|
||||
const blockDefaults = getEditorBlockDefaultsMessages(locale);
|
||||
const radioDefaults = blockDefaults.formRadio;
|
||||
const groupLabel = radioDefaults.groupLabel;
|
||||
const formFieldName = getNextFormFieldName(blocks, "radio");
|
||||
const options: FormRadioOption[] = [
|
||||
{ label: "옵션 A", value: "option_a" },
|
||||
{ label: "옵션 B", value: "option_b" },
|
||||
{ label: radioDefaults.optionALabel, value: "option_a" },
|
||||
{ label: radioDefaults.optionBLabel, value: "option_b" },
|
||||
];
|
||||
|
||||
const newBlock: Block = {
|
||||
@@ -1549,7 +1564,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// 리스트 블록 추가: 기본 항목/정렬과 함께 생성 후 선택 상태로 만든다
|
||||
addListBlock: () => {
|
||||
addListBlock: (locale?: AppLocale | null) => {
|
||||
const id = createId();
|
||||
|
||||
const { selectedBlockId, blocks } = get();
|
||||
@@ -1570,15 +1585,18 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
|
||||
const blockDefaults = getEditorBlockDefaultsMessages(locale);
|
||||
const itemText = blockDefaults.list.firstItemText;
|
||||
|
||||
const newBlock: Block = {
|
||||
id,
|
||||
type: "list",
|
||||
props: {
|
||||
items: ["리스트 아이템 1"],
|
||||
items: [itemText],
|
||||
itemsTree: [
|
||||
{
|
||||
id: `${id}_item_1`,
|
||||
text: "리스트 아이템 1",
|
||||
text: itemText,
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
@@ -1624,28 +1642,31 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// 폼 블록 추가: 기본 contact 폼 설정과 함께 생성 후 선택 상태로 만든다
|
||||
addFormBlock: () => {
|
||||
addFormBlock: (locale?: AppLocale | null) => {
|
||||
const id = createId();
|
||||
|
||||
const blockDefaults = getEditorBlockDefaultsMessages(locale);
|
||||
const formDefaults = blockDefaults.form;
|
||||
|
||||
const fields: FormFieldConfig[] = [
|
||||
{
|
||||
id: `${id}_field_name`,
|
||||
name: "name",
|
||||
label: "이름",
|
||||
label: formDefaults.nameLabel,
|
||||
type: "text",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: `${id}_field_email`,
|
||||
name: "email",
|
||||
label: "이메일",
|
||||
label: formDefaults.emailLabel,
|
||||
type: "email",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
id: `${id}_field_message`,
|
||||
name: "message",
|
||||
label: "메시지",
|
||||
label: formDefaults.messageLabel,
|
||||
type: "textarea",
|
||||
required: true,
|
||||
},
|
||||
@@ -1657,8 +1678,8 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
props: {
|
||||
kind: "contact",
|
||||
submitTarget: "internal",
|
||||
successMessage: "성공적으로 전송되었습니다.",
|
||||
errorMessage: "전송 중 오류가 발생했습니다.",
|
||||
successMessage: formDefaults.successMessage,
|
||||
errorMessage: formDefaults.errorMessage,
|
||||
fields,
|
||||
fieldIds: [],
|
||||
submitButtonId: null,
|
||||
@@ -1675,7 +1696,7 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
},
|
||||
|
||||
// 버튼 블록 추가: 기본 라벨/링크와 함께 생성 후 선택 상태로 만든다
|
||||
addButtonBlock: () => {
|
||||
addButtonBlock: (locale?: AppLocale | null) => {
|
||||
const id = createId();
|
||||
|
||||
const { selectedBlockId, blocks } = get();
|
||||
@@ -1695,11 +1716,14 @@ const createEditorState = (set: any, get: any): EditorState => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const blockDefaults = getEditorBlockDefaultsMessages(locale);
|
||||
|
||||
const newBlock: Block = {
|
||||
id,
|
||||
type: "button",
|
||||
props: {
|
||||
label: "버튼",
|
||||
label: blockDefaults.button.label,
|
||||
href: "#",
|
||||
align: "left",
|
||||
size: "md",
|
||||
|
||||
@@ -10,6 +10,13 @@ export function LocaleSwitcher() {
|
||||
const handleToggle = () => {
|
||||
const nextLocale: AppLocale = locale === "en" ? "ko" : "en";
|
||||
setLocale(nextLocale);
|
||||
|
||||
try {
|
||||
const maxAgeSeconds = 60 * 60 * 24 * 365; // 1 year
|
||||
document.cookie = `pb-locale=${nextLocale}; path=/; max-age=${maxAgeSeconds}`;
|
||||
} catch {
|
||||
// 쿠키 저장 실패는 무시 (로케일 상태는 여전히 컨텍스트에서 유지된다)
|
||||
}
|
||||
};
|
||||
|
||||
const labelText = locale === "en" ? "EN" : "KO";
|
||||
|
||||
@@ -46,6 +46,9 @@ export type EditorMessages = {
|
||||
listItemToolbarMoveDownLabel: string;
|
||||
listItemToolbarIndentLabel: string;
|
||||
listItemToolbarOutdentLabel: string;
|
||||
|
||||
imageBlockEmptyPreviewPlaceholder: string;
|
||||
columnAreaLabel: string;
|
||||
};
|
||||
|
||||
const EDITOR_MESSAGES: Record<AppLocale, EditorMessages> = {
|
||||
@@ -96,6 +99,10 @@ const EDITOR_MESSAGES: Record<AppLocale, EditorMessages> = {
|
||||
listItemToolbarMoveDownLabel: "Move item down",
|
||||
listItemToolbarIndentLabel: "Indent item",
|
||||
listItemToolbarOutdentLabel: "Outdent item",
|
||||
|
||||
imageBlockEmptyPreviewPlaceholder:
|
||||
"Enter an image URL or upload a file to see the preview here.",
|
||||
columnAreaLabel: "Column area",
|
||||
},
|
||||
ko: {
|
||||
headerTitle: "페이지 에디터",
|
||||
@@ -144,6 +151,10 @@ const EDITOR_MESSAGES: Record<AppLocale, EditorMessages> = {
|
||||
listItemToolbarMoveDownLabel: "아이템 아래로 이동",
|
||||
listItemToolbarIndentLabel: "아이템 들여쓰기",
|
||||
listItemToolbarOutdentLabel: "아이템 내어쓰기",
|
||||
|
||||
imageBlockEmptyPreviewPlaceholder:
|
||||
"이미지 URL 을 입력하거나 파일을 업로드하면 여기에서 미리보기가 표시됩니다.",
|
||||
columnAreaLabel: "컬럼 영역",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
import type { AppLocale } from "../locale";
|
||||
import { DEFAULT_LOCALE } from "../locale";
|
||||
|
||||
export type EditorBlockDefaultsMessages = {
|
||||
text: {
|
||||
defaultText: string;
|
||||
};
|
||||
list: {
|
||||
firstItemText: string;
|
||||
};
|
||||
button: {
|
||||
label: string;
|
||||
};
|
||||
form: {
|
||||
nameLabel: string;
|
||||
emailLabel: string;
|
||||
messageLabel: string;
|
||||
successMessage: string;
|
||||
errorMessage: string;
|
||||
};
|
||||
formInput: {
|
||||
label: string;
|
||||
};
|
||||
formSelect: {
|
||||
label: string;
|
||||
option1Label: string;
|
||||
option2Label: string;
|
||||
};
|
||||
formCheckbox: {
|
||||
groupLabel: string;
|
||||
option1Label: string;
|
||||
option2Label: string;
|
||||
};
|
||||
formRadio: {
|
||||
groupLabel: string;
|
||||
optionALabel: string;
|
||||
optionBLabel: string;
|
||||
};
|
||||
};
|
||||
|
||||
const EDITOR_BLOCK_DEFAULTS_MESSAGES: Record<AppLocale, EditorBlockDefaultsMessages> = {
|
||||
en: {
|
||||
text: {
|
||||
defaultText: "New text",
|
||||
},
|
||||
list: {
|
||||
firstItemText: "List item 1",
|
||||
},
|
||||
button: {
|
||||
label: "Button",
|
||||
},
|
||||
form: {
|
||||
nameLabel: "Name",
|
||||
emailLabel: "Email",
|
||||
messageLabel: "Message",
|
||||
successMessage: "Your message has been sent successfully.",
|
||||
errorMessage: "An error occurred while submitting.",
|
||||
},
|
||||
formInput: {
|
||||
label: "Input field",
|
||||
},
|
||||
formSelect: {
|
||||
label: "Select field",
|
||||
option1Label: "Option 1",
|
||||
option2Label: "Option 2",
|
||||
},
|
||||
formCheckbox: {
|
||||
groupLabel: "Checkbox group",
|
||||
option1Label: "Check 1",
|
||||
option2Label: "Check 2",
|
||||
},
|
||||
formRadio: {
|
||||
groupLabel: "Radio group",
|
||||
optionALabel: "Option A",
|
||||
optionBLabel: "Option B",
|
||||
},
|
||||
},
|
||||
ko: {
|
||||
text: {
|
||||
defaultText: "새 텍스트",
|
||||
},
|
||||
list: {
|
||||
firstItemText: "리스트 아이템 1",
|
||||
},
|
||||
button: {
|
||||
label: "버튼",
|
||||
},
|
||||
form: {
|
||||
nameLabel: "이름",
|
||||
emailLabel: "이메일",
|
||||
messageLabel: "메시지",
|
||||
successMessage: "성공적으로 전송되었습니다.",
|
||||
errorMessage: "전송 중 오류가 발생했습니다.",
|
||||
},
|
||||
formInput: {
|
||||
label: "입력 필드",
|
||||
},
|
||||
formSelect: {
|
||||
label: "셀렉트 필드",
|
||||
option1Label: "옵션 1",
|
||||
option2Label: "옵션 2",
|
||||
},
|
||||
formCheckbox: {
|
||||
groupLabel: "체크박스 그룹",
|
||||
option1Label: "옵션 1",
|
||||
option2Label: "옵션 2",
|
||||
},
|
||||
formRadio: {
|
||||
groupLabel: "라디오 그룹",
|
||||
optionALabel: "옵션 A",
|
||||
optionBLabel: "옵션 B",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export function getEditorBlockDefaultsMessages(
|
||||
locale: AppLocale | null | undefined,
|
||||
): EditorBlockDefaultsMessages {
|
||||
const key = (locale ?? DEFAULT_LOCALE) as AppLocale;
|
||||
return EDITOR_BLOCK_DEFAULTS_MESSAGES[key] ?? EDITOR_BLOCK_DEFAULTS_MESSAGES[DEFAULT_LOCALE];
|
||||
}
|
||||
@@ -120,8 +120,8 @@ const EDITOR_BUTTON_PANEL_MESSAGES: Record<AppLocale, EditorButtonPanelMessages>
|
||||
imagePlacementOptionTop: "Text top",
|
||||
imagePlacementOptionBottom: "Text bottom",
|
||||
|
||||
paddingXLabel: "Horizontal padding (px)",
|
||||
paddingYLabel: "Vertical padding (px)",
|
||||
paddingXLabel: "Horizontal padding",
|
||||
paddingYLabel: "Vertical padding",
|
||||
|
||||
linkLabel: "Button link",
|
||||
linkAria: "Button link",
|
||||
@@ -211,8 +211,8 @@ const EDITOR_BUTTON_PANEL_MESSAGES: Record<AppLocale, EditorButtonPanelMessages>
|
||||
imagePlacementOptionTop: "텍스트 위쪽",
|
||||
imagePlacementOptionBottom: "텍스트 아래쪽",
|
||||
|
||||
paddingXLabel: "가로 패딩 (px)",
|
||||
paddingYLabel: "세로 패딩 (px)",
|
||||
paddingXLabel: "가로 패딩",
|
||||
paddingYLabel: "세로 패딩",
|
||||
|
||||
linkLabel: "버튼 링크",
|
||||
linkAria: "버튼 링크",
|
||||
|
||||
@@ -57,9 +57,9 @@ const EDITOR_DIVIDER_PROPERTIES_PANEL_MESSAGES: Record<AppLocale, EditorDividerP
|
||||
lengthModeAria: "Divider length mode",
|
||||
lengthModeOptionAuto: "Fit content",
|
||||
lengthModeOptionFull: "Full width",
|
||||
lengthModeOptionFixed: "Fixed length (px)",
|
||||
lengthModeOptionFixed: "Fixed length",
|
||||
|
||||
fixedLengthLabel: "Fixed length (px)",
|
||||
fixedLengthLabel: "Fixed length",
|
||||
fixedLengthUnitLabel: "(px)",
|
||||
fixedLengthPresetShort: "Short",
|
||||
fixedLengthPresetNormal: "Normal",
|
||||
@@ -94,9 +94,9 @@ const EDITOR_DIVIDER_PROPERTIES_PANEL_MESSAGES: Record<AppLocale, EditorDividerP
|
||||
lengthModeAria: "구분선 길이 모드",
|
||||
lengthModeOptionAuto: "내용에 맞춤",
|
||||
lengthModeOptionFull: "전체 폭",
|
||||
lengthModeOptionFixed: "고정 길이 (px)",
|
||||
lengthModeOptionFixed: "고정 길이",
|
||||
|
||||
fixedLengthLabel: "고정 길이 (px)",
|
||||
fixedLengthLabel: "고정 길이",
|
||||
fixedLengthUnitLabel: "(px)",
|
||||
fixedLengthPresetShort: "짧게",
|
||||
fixedLengthPresetNormal: "보통",
|
||||
|
||||
@@ -117,7 +117,7 @@ const EDITOR_FORM_CHECKBOX_PANEL_MESSAGES: Record<AppLocale, EditorFormCheckboxP
|
||||
optionLayoutOptionStacked: "Vertical (default)",
|
||||
optionLayoutOptionInline: "Horizontal (inline)",
|
||||
|
||||
labelGapLabel: "Label/field gap (px)",
|
||||
labelGapLabel: "Label/field gap",
|
||||
|
||||
groupTitleLabel: "Group title",
|
||||
|
||||
@@ -148,13 +148,13 @@ const EDITOR_FORM_CHECKBOX_PANEL_MESSAGES: Record<AppLocale, EditorFormCheckboxP
|
||||
|
||||
styleSectionTitle: "Field style",
|
||||
|
||||
textSizeLabel: "Checkbox text size (px)",
|
||||
textSizeLabel: "Checkbox text size",
|
||||
textSizeUnitLabel: "(px)",
|
||||
|
||||
lineHeightLabel: "Checkbox line height (px)",
|
||||
lineHeightLabel: "Checkbox line height",
|
||||
lineHeightUnitLabel: "(px)",
|
||||
|
||||
letterSpacingLabel: "Checkbox letter spacing (px)",
|
||||
letterSpacingLabel: "Checkbox letter spacing",
|
||||
letterSpacingUnitLabel: "(px)",
|
||||
|
||||
widthModeLabel: "Field width",
|
||||
@@ -165,13 +165,13 @@ const EDITOR_FORM_CHECKBOX_PANEL_MESSAGES: Record<AppLocale, EditorFormCheckboxP
|
||||
fixedWidthLabel: "Field fixed width",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
|
||||
paddingXLabel: "Checkbox horizontal padding (px)",
|
||||
paddingXLabel: "Checkbox horizontal padding",
|
||||
paddingXUnitLabel: "(px)",
|
||||
|
||||
paddingYLabel: "Checkbox vertical padding (px)",
|
||||
paddingYLabel: "Checkbox vertical padding",
|
||||
paddingYUnitLabel: "(px)",
|
||||
|
||||
optionGapLabel: "Checkbox option gap (px)",
|
||||
optionGapLabel: "Checkbox option gap",
|
||||
optionGapUnitLabel: "(px)",
|
||||
|
||||
textColorLabel: "Text color",
|
||||
@@ -212,7 +212,7 @@ const EDITOR_FORM_CHECKBOX_PANEL_MESSAGES: Record<AppLocale, EditorFormCheckboxP
|
||||
optionLayoutOptionStacked: "세로 (기본)",
|
||||
optionLayoutOptionInline: "가로 (인라인)",
|
||||
|
||||
labelGapLabel: "라벨/필드 간격 (px)",
|
||||
labelGapLabel: "라벨/필드 간격",
|
||||
|
||||
groupTitleLabel: "그룹 타이틀",
|
||||
|
||||
@@ -243,13 +243,13 @@ const EDITOR_FORM_CHECKBOX_PANEL_MESSAGES: Record<AppLocale, EditorFormCheckboxP
|
||||
|
||||
styleSectionTitle: "필드 스타일",
|
||||
|
||||
textSizeLabel: "체크박스 텍스트 크기 (px)",
|
||||
textSizeLabel: "체크박스 텍스트 크기",
|
||||
textSizeUnitLabel: "(px)",
|
||||
|
||||
lineHeightLabel: "체크박스 줄간격 (px)",
|
||||
lineHeightLabel: "체크박스 줄간격",
|
||||
lineHeightUnitLabel: "(px)",
|
||||
|
||||
letterSpacingLabel: "체크박스 자간 (px)",
|
||||
letterSpacingLabel: "체크박스 자간",
|
||||
letterSpacingUnitLabel: "(px)",
|
||||
|
||||
widthModeLabel: "필드 너비",
|
||||
@@ -260,13 +260,13 @@ const EDITOR_FORM_CHECKBOX_PANEL_MESSAGES: Record<AppLocale, EditorFormCheckboxP
|
||||
fixedWidthLabel: "필드 고정 너비",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
|
||||
paddingXLabel: "체크박스 가로 패딩 (px)",
|
||||
paddingXLabel: "체크박스 가로 패딩",
|
||||
paddingXUnitLabel: "(px)",
|
||||
|
||||
paddingYLabel: "체크박스 세로 패딩 (px)",
|
||||
paddingYLabel: "체크박스 세로 패딩",
|
||||
paddingYUnitLabel: "(px)",
|
||||
|
||||
optionGapLabel: "체크박스 옵션 간격 (px)",
|
||||
optionGapLabel: "체크박스 옵션 간격",
|
||||
optionGapUnitLabel: "(px)",
|
||||
|
||||
textColorLabel: "텍스트 색상",
|
||||
|
||||
@@ -32,6 +32,10 @@ export type EditorFormControllerPanelMessages = {
|
||||
|
||||
formFixedWidthLabel: string;
|
||||
|
||||
formFixedWidthPresetNarrow: string;
|
||||
formFixedWidthPresetNormal: string;
|
||||
formFixedWidthPresetWide: string;
|
||||
|
||||
marginYLabel: string;
|
||||
marginYPresetNone: string;
|
||||
marginYPresetCompact: string;
|
||||
@@ -52,6 +56,8 @@ export type EditorFormControllerPanelMessages = {
|
||||
submitButtonLabel: string;
|
||||
submitButtonAriaLabel: string;
|
||||
|
||||
submitButtonNoneOptionLabel: string;
|
||||
|
||||
sheetsGuideButtonLabel: string;
|
||||
sheetsGuideHeading: string;
|
||||
sheetsGuideCloseAriaLabel: string;
|
||||
@@ -94,9 +100,13 @@ const EDITOR_FORM_CONTROLLER_PANEL_MESSAGES: Record<AppLocale, EditorFormControl
|
||||
formWidthModeOptionFull: "Full width",
|
||||
formWidthModeOptionFixed: "Fixed value",
|
||||
|
||||
formFixedWidthLabel: "Form fixed width (px)",
|
||||
formFixedWidthLabel: "Form fixed width",
|
||||
|
||||
marginYLabel: "Form top/bottom margin (px)",
|
||||
formFixedWidthPresetNarrow: "Narrow",
|
||||
formFixedWidthPresetNormal: "Normal",
|
||||
formFixedWidthPresetWide: "Wide",
|
||||
|
||||
marginYLabel: "Form top/bottom margin",
|
||||
marginYPresetNone: "None",
|
||||
marginYPresetCompact: "Compact",
|
||||
marginYPresetNormal: "Normal",
|
||||
@@ -116,6 +126,8 @@ const EDITOR_FORM_CONTROLLER_PANEL_MESSAGES: Record<AppLocale, EditorFormControl
|
||||
submitButtonLabel: "Submit button",
|
||||
submitButtonAriaLabel: "Submit button",
|
||||
|
||||
submitButtonNoneOptionLabel: "(None)",
|
||||
|
||||
sheetsGuideButtonLabel: "Google Sheets integration guide",
|
||||
sheetsGuideHeading: "Google Sheets integration guide",
|
||||
sheetsGuideCloseAriaLabel: "Close Google Sheets integration guide",
|
||||
@@ -160,9 +172,13 @@ const EDITOR_FORM_CONTROLLER_PANEL_MESSAGES: Record<AppLocale, EditorFormControl
|
||||
formWidthModeOptionFull: "전체 폭",
|
||||
formWidthModeOptionFixed: "고정 값",
|
||||
|
||||
formFixedWidthLabel: "폼 고정 너비 (px)",
|
||||
formFixedWidthLabel: "폼 고정 너비",
|
||||
|
||||
marginYLabel: "폼 위/아래 여백 (px)",
|
||||
formFixedWidthPresetNarrow: "좁게",
|
||||
formFixedWidthPresetNormal: "보통",
|
||||
formFixedWidthPresetWide: "넓게",
|
||||
|
||||
marginYLabel: "폼 위/아래 여백",
|
||||
marginYPresetNone: "없음",
|
||||
marginYPresetCompact: "좁게",
|
||||
marginYPresetNormal: "보통",
|
||||
@@ -182,6 +198,8 @@ const EDITOR_FORM_CONTROLLER_PANEL_MESSAGES: Record<AppLocale, EditorFormControl
|
||||
submitButtonLabel: "Submit 버튼",
|
||||
submitButtonAriaLabel: "Submit 버튼",
|
||||
|
||||
submitButtonNoneOptionLabel: "선택 안 함",
|
||||
|
||||
sheetsGuideButtonLabel: "Google Sheets 연동 가이드",
|
||||
sheetsGuideHeading: "Google Sheets 연동 가이드",
|
||||
sheetsGuideCloseAriaLabel: "Google Sheets 연동 가이드 닫기",
|
||||
|
||||
@@ -22,6 +22,12 @@ export type EditorFormInputPanelMessages = {
|
||||
labelGapLabel: string;
|
||||
labelGapUnitLabel: string;
|
||||
|
||||
// Presets for label gap
|
||||
labelGapPresetTight: string;
|
||||
labelGapPresetNormal: string;
|
||||
labelGapPresetRelaxed: string;
|
||||
labelGapPresetExtra: string;
|
||||
|
||||
labelImageUrlLabel: string;
|
||||
labelImageAltLabel: string;
|
||||
|
||||
@@ -44,9 +50,19 @@ export type EditorFormInputPanelMessages = {
|
||||
lineHeightLabel: string;
|
||||
lineHeightUnitLabel: string;
|
||||
|
||||
// Presets for line height
|
||||
lineHeightPresetTight: string;
|
||||
lineHeightPresetNormal: string;
|
||||
lineHeightPresetLoose: string;
|
||||
|
||||
letterSpacingLabel: string;
|
||||
letterSpacingUnitLabel: string;
|
||||
|
||||
// Presets for letter spacing
|
||||
letterSpacingPresetTight: string;
|
||||
letterSpacingPresetNormal: string;
|
||||
letterSpacingPresetWide: string;
|
||||
|
||||
textAlignLabel: string;
|
||||
textAlignOptionLeft: string;
|
||||
textAlignOptionCenter: string;
|
||||
@@ -61,12 +77,31 @@ export type EditorFormInputPanelMessages = {
|
||||
fixedWidthLabel: string;
|
||||
fixedWidthUnitLabel: string;
|
||||
|
||||
// Presets for fixed width
|
||||
fixedWidthPresetSmall: string;
|
||||
fixedWidthPresetMedium: string;
|
||||
fixedWidthPresetLarge: string;
|
||||
|
||||
paddingXLabel: string;
|
||||
paddingXUnitLabel: string;
|
||||
|
||||
// Presets for horizontal padding
|
||||
paddingXPresetXs: string;
|
||||
paddingXPresetSm: string;
|
||||
paddingXPresetMd: string;
|
||||
paddingXPresetLg: string;
|
||||
paddingXPresetXl: string;
|
||||
|
||||
paddingYLabel: string;
|
||||
paddingYUnitLabel: string;
|
||||
|
||||
// Presets for vertical padding
|
||||
paddingYPresetXs: string;
|
||||
paddingYPresetSm: string;
|
||||
paddingYPresetMd: string;
|
||||
paddingYPresetLg: string;
|
||||
paddingYPresetXl: string;
|
||||
|
||||
textColorLabel: string;
|
||||
textColorPickerAria: string;
|
||||
textColorHexAria: string;
|
||||
@@ -106,9 +141,14 @@ const EDITOR_FORM_INPUT_PANEL_MESSAGES: Record<AppLocale, EditorFormInputPanelMe
|
||||
layoutLabel: "Layout",
|
||||
layoutOptionStacked: "Vertical (default)",
|
||||
layoutOptionInline: "Horizontal (inline)",
|
||||
labelGapLabel: "Label/field gap (px)",
|
||||
labelGapLabel: "Label/field gap",
|
||||
labelGapUnitLabel: "(px)",
|
||||
|
||||
labelGapPresetTight: "Tight",
|
||||
labelGapPresetNormal: "Normal",
|
||||
labelGapPresetRelaxed: "Relaxed",
|
||||
labelGapPresetExtra: "Extra",
|
||||
|
||||
labelImageUrlLabel: "Label image URL",
|
||||
labelImageAltLabel: "Label image alt text",
|
||||
|
||||
@@ -125,15 +165,23 @@ const EDITOR_FORM_INPUT_PANEL_MESSAGES: Record<AppLocale, EditorFormInputPanelMe
|
||||
|
||||
requiredNoticeText: "Required state is configured in the form controller.",
|
||||
|
||||
textSizeLabel: "Field text size (px)",
|
||||
textSizeLabel: "Field text size",
|
||||
textSizeUnitLabel: "(px)",
|
||||
|
||||
lineHeightLabel: "Field line height (px)",
|
||||
lineHeightLabel: "Field line height",
|
||||
lineHeightUnitLabel: "(px)",
|
||||
|
||||
letterSpacingLabel: "Field letter spacing (px)",
|
||||
lineHeightPresetTight: "Tight",
|
||||
lineHeightPresetNormal: "Normal",
|
||||
lineHeightPresetLoose: "Loose",
|
||||
|
||||
letterSpacingLabel: "Field letter spacing",
|
||||
letterSpacingUnitLabel: "(px)",
|
||||
|
||||
letterSpacingPresetTight: "Tight",
|
||||
letterSpacingPresetNormal: "Normal",
|
||||
letterSpacingPresetWide: "Wide",
|
||||
|
||||
textAlignLabel: "Text alignment",
|
||||
textAlignOptionLeft: "Left",
|
||||
textAlignOptionCenter: "Center",
|
||||
@@ -148,12 +196,28 @@ const EDITOR_FORM_INPUT_PANEL_MESSAGES: Record<AppLocale, EditorFormInputPanelMe
|
||||
fixedWidthLabel: "Field fixed width",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
|
||||
paddingXLabel: "Field horizontal padding (px)",
|
||||
fixedWidthPresetSmall: "Small",
|
||||
fixedWidthPresetMedium: "Medium",
|
||||
fixedWidthPresetLarge: "Large",
|
||||
|
||||
paddingXLabel: "Field horizontal padding",
|
||||
paddingXUnitLabel: "(px)",
|
||||
|
||||
paddingYLabel: "Field vertical padding (px)",
|
||||
paddingXPresetXs: "Extra thin",
|
||||
paddingXPresetSm: "Thin",
|
||||
paddingXPresetMd: "Normal",
|
||||
paddingXPresetLg: "Wide",
|
||||
paddingXPresetXl: "Extra wide",
|
||||
|
||||
paddingYLabel: "Field vertical padding",
|
||||
paddingYUnitLabel: "(px)",
|
||||
|
||||
paddingYPresetXs: "Extra thin",
|
||||
paddingYPresetSm: "Thin",
|
||||
paddingYPresetMd: "Normal",
|
||||
paddingYPresetLg: "Wide",
|
||||
paddingYPresetXl: "Extra wide",
|
||||
|
||||
textColorLabel: "Field text color",
|
||||
textColorPickerAria: "Field text color picker",
|
||||
textColorHexAria: "Field text color HEX",
|
||||
@@ -191,9 +255,14 @@ const EDITOR_FORM_INPUT_PANEL_MESSAGES: Record<AppLocale, EditorFormInputPanelMe
|
||||
layoutLabel: "레이아웃",
|
||||
layoutOptionStacked: "세로 (기본)",
|
||||
layoutOptionInline: "가로 (인라인)",
|
||||
labelGapLabel: "라벨/필드 간격 (px)",
|
||||
labelGapLabel: "라벨/필드 간격",
|
||||
labelGapUnitLabel: "(px)",
|
||||
|
||||
labelGapPresetTight: "타이트",
|
||||
labelGapPresetNormal: "보통",
|
||||
labelGapPresetRelaxed: "느슨",
|
||||
labelGapPresetExtra: "넓게",
|
||||
|
||||
labelImageUrlLabel: "라벨 이미지 URL",
|
||||
labelImageAltLabel: "라벨 이미지 대체 텍스트",
|
||||
|
||||
@@ -210,15 +279,23 @@ const EDITOR_FORM_INPUT_PANEL_MESSAGES: Record<AppLocale, EditorFormInputPanelMe
|
||||
|
||||
requiredNoticeText: "필수 여부는 폼 컨트롤러에서 설정합니다.",
|
||||
|
||||
textSizeLabel: "필드 텍스트 크기 (px)",
|
||||
textSizeLabel: "필드 텍스트 크기",
|
||||
textSizeUnitLabel: "(px)",
|
||||
|
||||
lineHeightLabel: "필드 줄간격 (px)",
|
||||
lineHeightLabel: "필드 줄간격",
|
||||
lineHeightUnitLabel: "(px)",
|
||||
|
||||
letterSpacingLabel: "필드 자간 (px)",
|
||||
lineHeightPresetTight: "타이트",
|
||||
lineHeightPresetNormal: "보통",
|
||||
lineHeightPresetLoose: "루즈",
|
||||
|
||||
letterSpacingLabel: "필드 자간",
|
||||
letterSpacingUnitLabel: "(px)",
|
||||
|
||||
letterSpacingPresetTight: "좁게",
|
||||
letterSpacingPresetNormal: "보통",
|
||||
letterSpacingPresetWide: "넓게",
|
||||
|
||||
textAlignLabel: "텍스트 정렬",
|
||||
textAlignOptionLeft: "왼쪽",
|
||||
textAlignOptionCenter: "가운데",
|
||||
@@ -233,12 +310,28 @@ const EDITOR_FORM_INPUT_PANEL_MESSAGES: Record<AppLocale, EditorFormInputPanelMe
|
||||
fixedWidthLabel: "필드 고정 너비",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
|
||||
paddingXLabel: "필드 가로 패딩 (px)",
|
||||
fixedWidthPresetSmall: "작게",
|
||||
fixedWidthPresetMedium: "보통",
|
||||
fixedWidthPresetLarge: "넓게",
|
||||
|
||||
paddingXLabel: "필드 가로 패딩",
|
||||
paddingXUnitLabel: "(px)",
|
||||
|
||||
paddingYLabel: "필드 세로 패딩 (px)",
|
||||
paddingXPresetXs: "아주 얇게",
|
||||
paddingXPresetSm: "얇게",
|
||||
paddingXPresetMd: "보통",
|
||||
paddingXPresetLg: "넓게",
|
||||
paddingXPresetXl: "아주 넓게",
|
||||
|
||||
paddingYLabel: "필드 세로 패딩",
|
||||
paddingYUnitLabel: "(px)",
|
||||
|
||||
paddingYPresetXs: "아주 얇게",
|
||||
paddingYPresetSm: "얇게",
|
||||
paddingYPresetMd: "보통",
|
||||
paddingYPresetLg: "넓게",
|
||||
paddingYPresetXl: "아주 넓게",
|
||||
|
||||
textColorLabel: "필드 텍스트 색상",
|
||||
textColorPickerAria: "필드 텍스트 색상 피커",
|
||||
textColorHexAria: "필드 텍스트 색상 HEX",
|
||||
|
||||
@@ -4,6 +4,50 @@ import { DEFAULT_LOCALE } from "../locale";
|
||||
export type EditorFormRadioPanelMessages = {
|
||||
sectionTitle: string;
|
||||
|
||||
// Group title & option configuration
|
||||
groupTitleTypeLabel: string;
|
||||
groupTitleTypeOptionText: string;
|
||||
groupTitleTypeOptionImage: string;
|
||||
|
||||
groupTitleDisplayLabel: string;
|
||||
groupTitleDisplayOptionVisible: string;
|
||||
groupTitleDisplayOptionHidden: string;
|
||||
|
||||
layoutLabel: string;
|
||||
layoutOptionStacked: string;
|
||||
layoutOptionInline: string;
|
||||
|
||||
optionLayoutLabel: string;
|
||||
optionLayoutOptionStacked: string;
|
||||
optionLayoutOptionInline: string;
|
||||
|
||||
labelGapLabel: string;
|
||||
|
||||
groupTitleLabel: string;
|
||||
|
||||
submitKeyLabel: string;
|
||||
|
||||
optionsLabel: string;
|
||||
addOptionButtonLabel: string;
|
||||
optionLabelPlaceholder: string;
|
||||
optionValuePlaceholder: string;
|
||||
|
||||
optionImageSourceLabel: string;
|
||||
optionImageSourceOptionUrl: string;
|
||||
optionImageSourceOptionUpload: string;
|
||||
|
||||
optionImageUrlPlaceholder: string;
|
||||
optionImageUploadLabel: string;
|
||||
optionImageUploadAria: string;
|
||||
|
||||
groupTitleImageSourceLabel: string;
|
||||
groupTitleImageSourceOptionUrl: string;
|
||||
groupTitleImageSourceOptionUpload: string;
|
||||
|
||||
groupTitleImageUrlLabel: string;
|
||||
groupTitleImageUploadLabel: string;
|
||||
groupTitleImageUploadAria: string;
|
||||
|
||||
requiredNoticeText: string;
|
||||
|
||||
styleSectionTitle: string;
|
||||
@@ -53,29 +97,72 @@ const EDITOR_FORM_RADIO_PANEL_MESSAGES: Record<AppLocale, EditorFormRadioPanelMe
|
||||
en: {
|
||||
sectionTitle: "Radio field",
|
||||
|
||||
groupTitleTypeLabel: "Group title type",
|
||||
groupTitleTypeOptionText: "Text",
|
||||
groupTitleTypeOptionImage: "Image",
|
||||
|
||||
groupTitleDisplayLabel: "Group title display mode",
|
||||
groupTitleDisplayOptionVisible: "Visible (default)",
|
||||
groupTitleDisplayOptionHidden: "Hidden",
|
||||
|
||||
layoutLabel: "Group title layout",
|
||||
layoutOptionStacked: "Vertical (default)",
|
||||
layoutOptionInline: "Horizontal (inline)",
|
||||
|
||||
optionLayoutLabel: "Option layout",
|
||||
optionLayoutOptionStacked: "Vertical (default)",
|
||||
optionLayoutOptionInline: "Horizontal (inline)",
|
||||
|
||||
labelGapLabel: "Label/field gap",
|
||||
|
||||
groupTitleLabel: "Group title",
|
||||
|
||||
submitKeyLabel: "Submit key",
|
||||
|
||||
optionsLabel: "Options",
|
||||
addOptionButtonLabel: "Add option",
|
||||
optionLabelPlaceholder: "Label",
|
||||
optionValuePlaceholder: "Value (value)",
|
||||
|
||||
optionImageSourceLabel: "Option image source",
|
||||
optionImageSourceOptionUrl: "URL",
|
||||
optionImageSourceOptionUpload: "File upload",
|
||||
|
||||
optionImageUrlPlaceholder: "Option image URL (optional)",
|
||||
optionImageUploadLabel: "Option image file upload",
|
||||
optionImageUploadAria: "Option image file upload",
|
||||
|
||||
groupTitleImageSourceLabel: "Group title image source",
|
||||
groupTitleImageSourceOptionUrl: "URL",
|
||||
groupTitleImageSourceOptionUpload: "File upload",
|
||||
|
||||
groupTitleImageUrlLabel: "Group title image URL",
|
||||
groupTitleImageUploadLabel: "Group title image file upload",
|
||||
groupTitleImageUploadAria: "Group title image file upload",
|
||||
|
||||
requiredNoticeText: "Required state is configured in the form controller.",
|
||||
|
||||
styleSectionTitle: "Field style",
|
||||
|
||||
textSizeLabel: "Radio text size (px)",
|
||||
textSizeLabel: "Radio text size",
|
||||
textSizeUnitLabel: "(px)",
|
||||
|
||||
lineHeightLabel: "Radio line height (px)",
|
||||
lineHeightLabel: "Radio line height",
|
||||
lineHeightUnitLabel: "(px)",
|
||||
|
||||
letterSpacingLabel: "Radio letter spacing (px)",
|
||||
letterSpacingLabel: "Radio letter spacing",
|
||||
letterSpacingUnitLabel: "(px)",
|
||||
|
||||
fixedWidthLabel: "Field fixed width",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
|
||||
paddingXLabel: "Radio horizontal padding (px)",
|
||||
paddingXLabel: "Radio horizontal padding",
|
||||
paddingXUnitLabel: "(px)",
|
||||
|
||||
paddingYLabel: "Radio vertical padding (px)",
|
||||
paddingYLabel: "Radio vertical padding",
|
||||
paddingYUnitLabel: "(px)",
|
||||
|
||||
optionGapLabel: "Radio option gap (px)",
|
||||
optionGapLabel: "Radio option gap",
|
||||
optionGapUnitLabel: "(px)",
|
||||
|
||||
textColorLabel: "Text color",
|
||||
@@ -100,29 +187,72 @@ const EDITOR_FORM_RADIO_PANEL_MESSAGES: Record<AppLocale, EditorFormRadioPanelMe
|
||||
ko: {
|
||||
sectionTitle: "라디오 필드",
|
||||
|
||||
groupTitleTypeLabel: "그룹 타이틀 타입",
|
||||
groupTitleTypeOptionText: "텍스트",
|
||||
groupTitleTypeOptionImage: "이미지",
|
||||
|
||||
groupTitleDisplayLabel: "그룹 타이틀 표시 방식",
|
||||
groupTitleDisplayOptionVisible: "표시 (기본)",
|
||||
groupTitleDisplayOptionHidden: "숨김",
|
||||
|
||||
layoutLabel: "그룹 타이틀 레이아웃",
|
||||
layoutOptionStacked: "세로 (기본)",
|
||||
layoutOptionInline: "가로 (인라인)",
|
||||
|
||||
optionLayoutLabel: "옵션 레이아웃",
|
||||
optionLayoutOptionStacked: "세로 (기본)",
|
||||
optionLayoutOptionInline: "가로 (인라인)",
|
||||
|
||||
labelGapLabel: "라벨/필드 간격",
|
||||
|
||||
groupTitleLabel: "그룹 타이틀",
|
||||
|
||||
submitKeyLabel: "전송 키",
|
||||
|
||||
optionsLabel: "옵션",
|
||||
addOptionButtonLabel: "옵션 추가",
|
||||
optionLabelPlaceholder: "라벨",
|
||||
optionValuePlaceholder: "값(value)",
|
||||
|
||||
optionImageSourceLabel: "옵션 이미지 소스",
|
||||
optionImageSourceOptionUrl: "URL",
|
||||
optionImageSourceOptionUpload: "파일 업로드",
|
||||
|
||||
optionImageUrlPlaceholder: "옵션 이미지 URL (선택)",
|
||||
optionImageUploadLabel: "옵션 이미지 파일 업로드",
|
||||
optionImageUploadAria: "옵션 이미지 파일 업로드",
|
||||
|
||||
groupTitleImageSourceLabel: "그룹 타이틀 이미지 소스",
|
||||
groupTitleImageSourceOptionUrl: "URL",
|
||||
groupTitleImageSourceOptionUpload: "파일 업로드",
|
||||
|
||||
groupTitleImageUrlLabel: "그룹 타이틀 이미지 URL",
|
||||
groupTitleImageUploadLabel: "그룹 타이틀 이미지 파일 업로드",
|
||||
groupTitleImageUploadAria: "그룹 타이틀 이미지 파일 업로드",
|
||||
|
||||
requiredNoticeText: "필수 여부는 폼 컨트롤러에서 설정합니다.",
|
||||
|
||||
styleSectionTitle: "필드 스타일",
|
||||
|
||||
textSizeLabel: "라디오 텍스트 크기 (px)",
|
||||
textSizeLabel: "라디오 텍스트 크기",
|
||||
textSizeUnitLabel: "(px)",
|
||||
|
||||
lineHeightLabel: "라디오 줄간격 (px)",
|
||||
lineHeightLabel: "라디오 줄간격",
|
||||
lineHeightUnitLabel: "(px)",
|
||||
|
||||
letterSpacingLabel: "라디오 자간 (px)",
|
||||
letterSpacingLabel: "라디오 자간",
|
||||
letterSpacingUnitLabel: "(px)",
|
||||
|
||||
fixedWidthLabel: "필드 고정 너비",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
|
||||
paddingXLabel: "라디오 가로 패딩 (px)",
|
||||
paddingXLabel: "라디오 가로 패딩",
|
||||
paddingXUnitLabel: "(px)",
|
||||
|
||||
paddingYLabel: "라디오 세로 패딩 (px)",
|
||||
paddingYLabel: "라디오 세로 패딩",
|
||||
paddingYUnitLabel: "(px)",
|
||||
|
||||
optionGapLabel: "라디오 옵션 간격 (px)",
|
||||
optionGapLabel: "라디오 옵션 간격",
|
||||
optionGapUnitLabel: "(px)",
|
||||
|
||||
textColorLabel: "텍스트 색상",
|
||||
|
||||
@@ -89,7 +89,7 @@ const EDITOR_FORM_SELECT_PANEL_MESSAGES: Record<AppLocale, EditorFormSelectPanel
|
||||
layoutOptionStacked: "Vertical (default)",
|
||||
layoutOptionInline: "Horizontal (inline)",
|
||||
|
||||
labelGapLabel: "Label/field gap (px)",
|
||||
labelGapLabel: "Label/field gap",
|
||||
|
||||
fieldLabelLabel: "Field label",
|
||||
submitKeyLabel: "Submit key",
|
||||
@@ -103,13 +103,13 @@ const EDITOR_FORM_SELECT_PANEL_MESSAGES: Record<AppLocale, EditorFormSelectPanel
|
||||
|
||||
styleSectionTitle: "Field style",
|
||||
|
||||
textSizeLabel: "Select text size (px)",
|
||||
textSizeLabel: "Select text size",
|
||||
textSizeUnitLabel: "(px)",
|
||||
|
||||
lineHeightLabel: "Select line height (px)",
|
||||
lineHeightLabel: "Select line height",
|
||||
lineHeightUnitLabel: "(px)",
|
||||
|
||||
letterSpacingLabel: "Select letter spacing (px)",
|
||||
letterSpacingLabel: "Select letter spacing",
|
||||
letterSpacingUnitLabel: "(px)",
|
||||
|
||||
widthModeLabel: "Field width",
|
||||
@@ -120,10 +120,10 @@ const EDITOR_FORM_SELECT_PANEL_MESSAGES: Record<AppLocale, EditorFormSelectPanel
|
||||
fixedWidthLabel: "Field fixed width",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
|
||||
paddingXLabel: "Select horizontal padding (px)",
|
||||
paddingXLabel: "Select horizontal padding",
|
||||
paddingXUnitLabel: "(px)",
|
||||
|
||||
paddingYLabel: "Select vertical padding (px)",
|
||||
paddingYLabel: "Select vertical padding",
|
||||
paddingYUnitLabel: "(px)",
|
||||
|
||||
textColorLabel: "Field text color",
|
||||
@@ -160,7 +160,7 @@ const EDITOR_FORM_SELECT_PANEL_MESSAGES: Record<AppLocale, EditorFormSelectPanel
|
||||
layoutOptionStacked: "세로 (기본)",
|
||||
layoutOptionInline: "가로 (인라인)",
|
||||
|
||||
labelGapLabel: "라벨/필드 간격 (px)",
|
||||
labelGapLabel: "라벨/필드 간격",
|
||||
|
||||
fieldLabelLabel: "필드 라벨",
|
||||
submitKeyLabel: "전송 키",
|
||||
@@ -174,13 +174,13 @@ const EDITOR_FORM_SELECT_PANEL_MESSAGES: Record<AppLocale, EditorFormSelectPanel
|
||||
|
||||
styleSectionTitle: "필드 스타일",
|
||||
|
||||
textSizeLabel: "셀렉트 텍스트 크기 (px)",
|
||||
textSizeLabel: "셀렉트 텍스트 크기",
|
||||
textSizeUnitLabel: "(px)",
|
||||
|
||||
lineHeightLabel: "셀렉트 줄간격 (px)",
|
||||
lineHeightLabel: "셀렉트 줄간격",
|
||||
lineHeightUnitLabel: "(px)",
|
||||
|
||||
letterSpacingLabel: "셀렉트 자간 (px)",
|
||||
letterSpacingLabel: "셀렉트 자간",
|
||||
letterSpacingUnitLabel: "(px)",
|
||||
|
||||
widthModeLabel: "필드 너비",
|
||||
@@ -191,10 +191,10 @@ const EDITOR_FORM_SELECT_PANEL_MESSAGES: Record<AppLocale, EditorFormSelectPanel
|
||||
fixedWidthLabel: "필드 고정 너비",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
|
||||
paddingXLabel: "셀렉트 가로 패딩 (px)",
|
||||
paddingXLabel: "셀렉트 가로 패딩",
|
||||
paddingXUnitLabel: "(px)",
|
||||
|
||||
paddingYLabel: "셀렉트 세로 패딩 (px)",
|
||||
paddingYLabel: "셀렉트 세로 패딩",
|
||||
paddingYUnitLabel: "(px)",
|
||||
|
||||
textColorLabel: "필드 텍스트 색상",
|
||||
|
||||
@@ -78,9 +78,9 @@ const EDITOR_IMAGE_PANEL_MESSAGES: Record<AppLocale, EditorImagePanelMessages> =
|
||||
widthModeLabel: "Width mode",
|
||||
widthModeAria: "Image width mode",
|
||||
widthModeOptionAuto: "Fit to content",
|
||||
widthModeOptionFixed: "Fixed width (px)",
|
||||
widthModeOptionFixed: "Fixed width",
|
||||
|
||||
fixedWidthLabel: "Fixed width (px)",
|
||||
fixedWidthLabel: "Fixed width",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
fixedWidthPresetSmall: "Small",
|
||||
fixedWidthPresetMedium: "Medium",
|
||||
@@ -123,9 +123,9 @@ const EDITOR_IMAGE_PANEL_MESSAGES: Record<AppLocale, EditorImagePanelMessages> =
|
||||
widthModeLabel: "너비 모드",
|
||||
widthModeAria: "이미지 너비 모드",
|
||||
widthModeOptionAuto: "내용에 맞춤",
|
||||
widthModeOptionFixed: "고정 너비 (px)",
|
||||
widthModeOptionFixed: "고정 너비",
|
||||
|
||||
fixedWidthLabel: "고정 너비 (px)",
|
||||
fixedWidthLabel: "고정 너비",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
fixedWidthPresetSmall: "작게",
|
||||
fixedWidthPresetMedium: "보통",
|
||||
|
||||
@@ -64,7 +64,7 @@ const EDITOR_LIST_PANEL_MESSAGES: Record<AppLocale, EditorListPanelMessages> = {
|
||||
|
||||
styleSectionTitle: "List style",
|
||||
|
||||
fontSizeLabel: "Font size (px)",
|
||||
fontSizeLabel: "Font size",
|
||||
fontSizeUnitLabel: "(px)",
|
||||
fontSizePresetSmall: "Small",
|
||||
fontSizePresetMedium: "Medium",
|
||||
@@ -95,7 +95,7 @@ const EDITOR_LIST_PANEL_MESSAGES: Record<AppLocale, EditorListPanelMessages> = {
|
||||
bulletStyleOptionUpperRoman: "Upper roman (I.)",
|
||||
bulletStyleOptionNone: "None",
|
||||
|
||||
gapLabel: "Item gap (px)",
|
||||
gapLabel: "Item gap",
|
||||
gapUnitLabel: "(px)",
|
||||
gapPresetTight: "Tight",
|
||||
gapPresetNormal: "Normal",
|
||||
@@ -113,7 +113,7 @@ const EDITOR_LIST_PANEL_MESSAGES: Record<AppLocale, EditorListPanelMessages> = {
|
||||
|
||||
styleSectionTitle: "리스트 스타일",
|
||||
|
||||
fontSizeLabel: "글자 크기 (px)",
|
||||
fontSizeLabel: "글자 크기",
|
||||
fontSizeUnitLabel: "(px)",
|
||||
fontSizePresetSmall: "작게",
|
||||
fontSizePresetMedium: "보통",
|
||||
@@ -144,7 +144,7 @@ const EDITOR_LIST_PANEL_MESSAGES: Record<AppLocale, EditorListPanelMessages> = {
|
||||
bulletStyleOptionUpperRoman: "로마 숫자 대문자 (I.)",
|
||||
bulletStyleOptionNone: "없음",
|
||||
|
||||
gapLabel: "아이템 간 여백 (px)",
|
||||
gapLabel: "아이템 간 여백",
|
||||
gapUnitLabel: "(px)",
|
||||
gapPresetTight: "좁게",
|
||||
gapPresetNormal: "보통",
|
||||
|
||||
@@ -117,12 +117,12 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"The label that appears on the button. For example: 'Get started now', 'Contact us'. Short, action-oriented phrases usually work best.",
|
||||
},
|
||||
{
|
||||
label: "Horizontal padding (px)",
|
||||
label: "Horizontal padding",
|
||||
description:
|
||||
"Controls the left and right inner spacing of the button. Larger values create a wider button that stands out more.",
|
||||
},
|
||||
{
|
||||
label: "Vertical padding (px)",
|
||||
label: "Vertical padding",
|
||||
description:
|
||||
"Controls the top and bottom inner spacing. Increasing this value makes the button taller and more prominent.",
|
||||
},
|
||||
@@ -210,7 +210,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"Controls whether the image is aligned to the left, center, or right inside the container.",
|
||||
},
|
||||
{
|
||||
label: "Width mode / fixed width (px)",
|
||||
label: "Width mode / fixed width",
|
||||
description:
|
||||
"Choose between auto width (fit content) and a fixed width in px. For thumbnail grids, fixed widths often create a more stable layout.",
|
||||
},
|
||||
@@ -238,7 +238,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"Align the whole list to the left, center, or right. Checklists are typically left-aligned, while short feature summaries often use center alignment.",
|
||||
},
|
||||
{
|
||||
label: "Font size (px)",
|
||||
label: "Font size",
|
||||
description:
|
||||
"Controls the font size of list text. Slightly smaller than the main body copy can make it feel like supporting information.",
|
||||
},
|
||||
@@ -263,7 +263,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"Choose bullet shapes (●/○/■) or numbered styles (1., a., i.). Number/letter/Roman styles automatically become ordered lists.",
|
||||
},
|
||||
{
|
||||
label: "Item gap (px)",
|
||||
label: "Item gap",
|
||||
description:
|
||||
"Controls the vertical space between list items. For longer descriptions, use a larger gap for better readability.",
|
||||
},
|
||||
@@ -286,7 +286,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"Controls line thickness. A thin line is good for subtle separators, while a normal thickness clearly divides sections.",
|
||||
},
|
||||
{
|
||||
label: "Length mode / fixed length (px)",
|
||||
label: "Length mode / fixed length",
|
||||
description:
|
||||
"Choose whether the divider spans the full width, auto width, or a fixed length in px.",
|
||||
},
|
||||
@@ -362,7 +362,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"Controls whether the video is left, center, or right aligned inside its container.",
|
||||
},
|
||||
{
|
||||
label: "Width mode / fixed width (px)",
|
||||
label: "Width mode / fixed width",
|
||||
description:
|
||||
"Choose auto width, full width, or a fixed width in px for the video.",
|
||||
},
|
||||
@@ -405,12 +405,12 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"Add extra key/value pairs that are always included in the submission. Use one `key=value` per line. Example: `source=landing`.",
|
||||
},
|
||||
{
|
||||
label: "Form width mode / fixed width (px)",
|
||||
label: "Form width mode / fixed width",
|
||||
description:
|
||||
"Choose automatic width, full width, or a fixed px width. Contact forms often work well around 480–640px.",
|
||||
},
|
||||
{
|
||||
label: "Form vertical margin (px)",
|
||||
label: "Form vertical margin",
|
||||
description:
|
||||
"Controls the vertical space above and below the form block.",
|
||||
},
|
||||
@@ -480,7 +480,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
{
|
||||
label: "Layout / label-field gap",
|
||||
description:
|
||||
"Choose stacked or inline layout for label and field, and adjust the gap between them (px) in inline mode.",
|
||||
"Choose stacked or inline layout for label and field, and adjust the gap between them in inline mode.",
|
||||
},
|
||||
{
|
||||
label: "Width / fixed width",
|
||||
@@ -719,12 +719,12 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"버튼 위에 표시될 문구입니다. 예: '지금 시작하기', '문의 남기기'. 짧고 행동을 유도하는 문장을 사용하는 것이 좋습니다.",
|
||||
},
|
||||
{
|
||||
label: "가로 패딩 (px)",
|
||||
label: "가로 패딩",
|
||||
description:
|
||||
"버튼 양 옆의 여백을 조절합니다. 값이 클수록 버튼이 가로로 넓어져 더 강조됩니다.",
|
||||
},
|
||||
{
|
||||
label: "세로 패딩 (px)",
|
||||
label: "세로 패딩",
|
||||
description:
|
||||
"버튼 위·아래 여백을 조절합니다. 값이 클수록 버튼 높이가 높아져 더 눈에 띕니다.",
|
||||
},
|
||||
@@ -812,7 +812,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"이미지가 포함된 영역 안에서 왼쪽/가운데/오른쪽 중 어디에 위치할지 결정합니다.",
|
||||
},
|
||||
{
|
||||
label: "너비 모드 / 고정 너비 (px)",
|
||||
label: "너비 모드 / 고정 너비",
|
||||
description:
|
||||
"이미지를 내용 크기에 맞출지(auto) 또는 px 단위로 고정된 너비를 사용할지 선택합니다. 썸네일 그리드에서는 고정 너비를 사용하는 편이 레이아웃이 안정적입니다.",
|
||||
},
|
||||
@@ -840,7 +840,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"리스트 전체를 왼쪽/가운데/오른쪽 중 어디에 정렬할지 결정합니다. 체크리스트는 왼쪽 정렬, 특징 요약은 가운데 정렬이 자주 사용됩니다.",
|
||||
},
|
||||
{
|
||||
label: "글자 크기 (px)",
|
||||
label: "글자 크기",
|
||||
description:
|
||||
"리스트 텍스트의 폰트 크기를 조절합니다. 본문보다 약간 작게 설정하면 보조 정보 느낌을 줄 수 있습니다.",
|
||||
},
|
||||
@@ -865,7 +865,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"불릿(●/○/■) 또는 번호(1., a., i.) 형식을 선택합니다. 숫자/알파벳/로마 숫자 스타일을 선택하면 자동으로 순서형 리스트(ordered)가 됩니다.",
|
||||
},
|
||||
{
|
||||
label: "아이템 간 여백 (px)",
|
||||
label: "아이템 간 여백",
|
||||
description:
|
||||
"각 리스트 항목 사이의 세로 간격입니다. 설명이 긴 항목이 많다면 여백을 넉넉히 두어 가독성을 확보하세요.",
|
||||
},
|
||||
@@ -888,7 +888,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"선의 두께입니다. '얇게'는 가벼운 보조 구분선, '보통'은 섹션 경계를 확실히 나눌 때 사용합니다.",
|
||||
},
|
||||
{
|
||||
label: "길이 모드 / 고정 길이 (px)",
|
||||
label: "길이 모드 / 고정 길이",
|
||||
description:
|
||||
"구분선을 전체 폭으로 쓸지, 내용에 맞출지, 또는 px 단위의 고정 길이를 사용할지 선택합니다.",
|
||||
},
|
||||
@@ -964,7 +964,7 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"비디오를 컨테이너 안에서 왼쪽/가운데/오른쪽 중 어디에 위치시킬지 결정합니다.",
|
||||
},
|
||||
{
|
||||
label: "비디오 너비 모드 / 고정 너비 (px)",
|
||||
label: "비디오 너비 모드 / 고정 너비",
|
||||
description:
|
||||
"비디오를 내용 너비에 맞출지, 가로 전체를 채울지, 특정 px 너비로 고정할지 선택합니다.",
|
||||
},
|
||||
@@ -1007,12 +1007,12 @@ const EDITOR_PROPERTIES_SIDEBAR_MESSAGES: Record<AppLocale, EditorPropertiesSide
|
||||
"`key=value` 형식으로 줄바꿈해 작성하면, 모든 폼 제출에 공통으로 포함될 추가 파라미터를 지정할 수 있습니다. 예: `source=landing`.",
|
||||
},
|
||||
{
|
||||
label: "폼 너비 모드 / 폼 고정 너비 (px)",
|
||||
label: "폼 너비 모드 / 폼 고정 너비",
|
||||
description:
|
||||
"폼 전체의 가로 폭을 자동/전체 폭/고정 px 값 중에서 선택합니다. 문의 폼은 보통 480~640px 정도의 고정 폭이 읽기 좋습니다.",
|
||||
},
|
||||
{
|
||||
label: "폼 위/아래 여백 (px)",
|
||||
label: "폼 위/아래 여백",
|
||||
description:
|
||||
"폼 블록 위·아래에 들어가는 공백입니다. 페이지 내 다른 섹션과의 간격을 조절할 때 사용합니다.",
|
||||
},
|
||||
|
||||
@@ -172,7 +172,7 @@ const EDITOR_SECTION_PANEL_MESSAGES: Record<AppLocale, EditorSectionPanelMessage
|
||||
columnLayoutOptionThreeEqual: "3 columns (1/3 - 1/3 - 1/3)",
|
||||
columnLayoutOptionCustom: "Custom",
|
||||
|
||||
maxWidthLabel: "Max width (px)",
|
||||
maxWidthLabel: "Max width",
|
||||
maxWidthUnitLabel: "(px)",
|
||||
maxWidthPresetExtraNarrow: "Very narrow",
|
||||
maxWidthPresetNarrow: "Narrow",
|
||||
@@ -180,7 +180,7 @@ const EDITOR_SECTION_PANEL_MESSAGES: Record<AppLocale, EditorSectionPanelMessage
|
||||
maxWidthPresetWide: "Wide",
|
||||
maxWidthPresetExtraWide: "Very wide",
|
||||
|
||||
gapXLabel: "Column gap (px)",
|
||||
gapXLabel: "Column gap",
|
||||
gapXUnitLabel: "(px)",
|
||||
gapXPresetZero: "0",
|
||||
gapXPresetTight: "Tight",
|
||||
@@ -268,7 +268,7 @@ const EDITOR_SECTION_PANEL_MESSAGES: Record<AppLocale, EditorSectionPanelMessage
|
||||
columnLayoutOptionThreeEqual: "3열 (1/3 - 1/3 - 1/3)",
|
||||
columnLayoutOptionCustom: "직접 조정",
|
||||
|
||||
maxWidthLabel: "최대 폭 (px)",
|
||||
maxWidthLabel: "최대 폭",
|
||||
maxWidthUnitLabel: "(px)",
|
||||
maxWidthPresetExtraNarrow: "매우 좁게",
|
||||
maxWidthPresetNarrow: "좁게",
|
||||
@@ -276,7 +276,7 @@ const EDITOR_SECTION_PANEL_MESSAGES: Record<AppLocale, EditorSectionPanelMessage
|
||||
maxWidthPresetWide: "넓게",
|
||||
maxWidthPresetExtraWide: "아주 넓게",
|
||||
|
||||
gapXLabel: "컬럼 간 간격 (px)",
|
||||
gapXLabel: "컬럼 간 간격",
|
||||
gapXUnitLabel: "(px)",
|
||||
gapXPresetZero: "0",
|
||||
gapXPresetTight: "좁게",
|
||||
|
||||
@@ -69,7 +69,7 @@ const EDITOR_TEXT_PANEL_MESSAGES: Record<AppLocale, EditorTextPanelMessages> = {
|
||||
letterSpacingLabel: "Letter spacing",
|
||||
letterSpacingPresetAria: "Letter spacing preset",
|
||||
letterSpacingSliderAria: "Letter spacing slider",
|
||||
letterSpacingInputAria: "Letter spacing custom (px)",
|
||||
letterSpacingInputAria: "Letter spacing custom",
|
||||
letterSpacingPresetTighter: "Very tight",
|
||||
letterSpacingPresetTight: "Tight",
|
||||
letterSpacingPresetNormal: "Normal",
|
||||
@@ -118,7 +118,7 @@ const EDITOR_TEXT_PANEL_MESSAGES: Record<AppLocale, EditorTextPanelMessages> = {
|
||||
letterSpacingLabel: "글자 간격",
|
||||
letterSpacingPresetAria: "글자 간격 프리셋",
|
||||
letterSpacingSliderAria: "글자 간격 슬라이더",
|
||||
letterSpacingInputAria: "글자 간격 커스텀 (px)",
|
||||
letterSpacingInputAria: "글자 간격 커스텀",
|
||||
letterSpacingPresetTighter: "아주 좁게",
|
||||
letterSpacingPresetTight: "좁게",
|
||||
letterSpacingPresetNormal: "보통",
|
||||
|
||||
@@ -111,9 +111,9 @@ const EDITOR_VIDEO_PANEL_MESSAGES: Record<AppLocale, EditorVideoPanelMessages> =
|
||||
widthModeAria: "Video width mode",
|
||||
widthModeOptionAuto: "Fit to content",
|
||||
widthModeOptionFull: "Full width",
|
||||
widthModeOptionFixed: "Fixed width (px)",
|
||||
widthModeOptionFixed: "Fixed width",
|
||||
|
||||
fixedWidthLabel: "Fixed width (px)",
|
||||
fixedWidthLabel: "Fixed width",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
fixedWidthPresetSmall: "Small",
|
||||
fixedWidthPresetMedium: "Medium",
|
||||
@@ -179,9 +179,9 @@ const EDITOR_VIDEO_PANEL_MESSAGES: Record<AppLocale, EditorVideoPanelMessages> =
|
||||
widthModeAria: "비디오 너비 모드",
|
||||
widthModeOptionAuto: "내용에 맞춤",
|
||||
widthModeOptionFull: "가로 전체",
|
||||
widthModeOptionFixed: "고정 너비 (px)",
|
||||
widthModeOptionFixed: "고정 너비",
|
||||
|
||||
fixedWidthLabel: "고정 너비 (px)",
|
||||
fixedWidthLabel: "고정 너비",
|
||||
fixedWidthUnitLabel: "(px)",
|
||||
fixedWidthPresetSmall: "작게",
|
||||
fixedWidthPresetMedium: "보통",
|
||||
|
||||
Reference in New Issue
Block a user