143 lines
5.7 KiB
TypeScript
143 lines
5.7 KiB
TypeScript
"use client";
|
|
|
|
import type { DividerBlockProps } from "@/features/editor/state/editorStore";
|
|
import { NumericPropertyControl } from "@/features/editor/components/NumericPropertyControl";
|
|
import { ColorPickerField, TEXT_COLOR_PALETTE } from "@/features/editor/components/ColorPickerField";
|
|
import { useAppLocale } from "@/features/i18n/LocaleProvider";
|
|
import { getEditorDividerPropertiesPanelMessages } from "@/features/i18n/messages/editorDividerPropertiesPanel";
|
|
|
|
export type DividerPropertiesPanelProps = {
|
|
dividerProps: DividerBlockProps;
|
|
selectedBlockId: string;
|
|
updateBlock: (id: string, partial: Partial<DividerBlockProps>) => void;
|
|
};
|
|
|
|
export function DividerPropertiesPanel({ dividerProps, selectedBlockId, updateBlock }: DividerPropertiesPanelProps) {
|
|
const locale = useAppLocale();
|
|
const m = getEditorDividerPropertiesPanelMessages(locale);
|
|
|
|
return (
|
|
<>
|
|
<div className="space-y-1">
|
|
<label className="flex flex-col gap-1 text-xs text-slate-400">
|
|
<span>{m.alignLabel}</span>
|
|
<select
|
|
className="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"
|
|
aria-label={m.alignAria}
|
|
value={dividerProps.align}
|
|
onChange={(e) => {
|
|
const value = e.target.value as DividerBlockProps["align"];
|
|
updateBlock(selectedBlockId, { align: value } as any);
|
|
}}
|
|
>
|
|
<option value="left">{m.alignOptionLeft}</option>
|
|
<option value="center">{m.alignOptionCenter}</option>
|
|
<option value="right">{m.alignOptionRight}</option>
|
|
</select>
|
|
</label>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<label className="flex flex-col gap-1 text-xs text-slate-400">
|
|
<span>{m.thicknessLabel}</span>
|
|
<select
|
|
className="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"
|
|
aria-label={m.thicknessAria}
|
|
value={dividerProps.thickness}
|
|
onChange={(e) => {
|
|
const value = e.target.value as DividerBlockProps["thickness"];
|
|
updateBlock(selectedBlockId, { thickness: value } as any);
|
|
}}
|
|
>
|
|
<option value="thin">{m.thicknessOptionThin}</option>
|
|
<option value="medium">{m.thicknessOptionMedium}</option>
|
|
</select>
|
|
</label>
|
|
</div>
|
|
|
|
<div className="mt-3 space-y-2 border-t border-slate-800 pt-3 text-xs text-slate-400">
|
|
<h4 className="text-[11px] font-semibold text-slate-800 dark:text-slate-200">{m.styleSectionTitle}</h4>
|
|
|
|
{/* 길이/너비 모드 */}
|
|
<label className="flex flex-col gap-1">
|
|
<span>{m.lengthModeLabel}</span>
|
|
<select
|
|
className="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"
|
|
aria-label={m.lengthModeAria}
|
|
value={dividerProps.widthMode ?? "full"}
|
|
onChange={(e) => {
|
|
const value = e.target.value as NonNullable<DividerBlockProps["widthMode"]>;
|
|
updateBlock(selectedBlockId, { widthMode: value } as any);
|
|
}}
|
|
>
|
|
<option value="auto">{m.lengthModeOptionAuto}</option>
|
|
<option value="full">{m.lengthModeOptionFull}</option>
|
|
<option value="fixed">{m.lengthModeOptionFixed}</option>
|
|
</select>
|
|
</label>
|
|
|
|
{(dividerProps.widthMode ?? "full") === "fixed" && (
|
|
<NumericPropertyControl
|
|
label={m.fixedLengthLabel}
|
|
unitLabel={m.fixedLengthUnitLabel}
|
|
value={dividerProps.widthPx ?? 320}
|
|
min={40}
|
|
max={1200}
|
|
step={10}
|
|
presets={[
|
|
{ id: "sm", label: m.fixedLengthPresetShort, value: 240 },
|
|
{ id: "md", label: m.fixedLengthPresetNormal, value: 320 },
|
|
{ id: "lg", label: m.fixedLengthPresetLong, value: 480 },
|
|
]}
|
|
onChangeValue={(v) => {
|
|
updateBlock(selectedBlockId, { widthPx: v } as any);
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{/* 색상 */}
|
|
<ColorPickerField
|
|
label={m.colorLabel}
|
|
ariaLabelColorInput={m.colorPickerAria}
|
|
ariaLabelHexInput={m.colorHexAria}
|
|
value={
|
|
dividerProps.colorHex && dividerProps.colorHex.trim() !== ""
|
|
? dividerProps.colorHex
|
|
: ""
|
|
}
|
|
onChange={(hex) => {
|
|
updateBlock(selectedBlockId, { colorHex: hex } as any);
|
|
}}
|
|
palette={TEXT_COLOR_PALETTE}
|
|
onPaletteSelect={(item) => {
|
|
updateBlock(selectedBlockId, { colorHex: item.color } as any);
|
|
}}
|
|
/>
|
|
|
|
{/* 상하 여백 (슬라이더) */}
|
|
<NumericPropertyControl
|
|
label={m.marginLabel}
|
|
unitLabel={m.marginUnitLabel}
|
|
value={(() => {
|
|
if (typeof dividerProps.marginYPx === "number") {
|
|
return dividerProps.marginYPx;
|
|
}
|
|
const y = dividerProps.marginY ?? "md";
|
|
return y === "sm" ? 8 : y === "lg" ? 24 : 16;
|
|
})()}
|
|
min={0}
|
|
max={50}
|
|
step={2}
|
|
presets={[
|
|
{ id: "sm", label: m.marginPresetSmall, value: 8 },
|
|
{ id: "md", label: m.marginPresetNormal, value: 16 },
|
|
{ id: "lg", label: m.marginPresetLarge, value: 24 },
|
|
]}
|
|
onChangeValue={(v) => {
|
|
updateBlock(selectedBlockId, { marginYPx: v } as any);
|
|
}}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|