File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
'use client';
import { useState } from 'react';
import { FoxFileUpload } from '@fox/core/components/fox-file-upload';
import { FoxHelperText } from '@fox/core/components/fox-helper-text';
import { FoxTextButton } from '@fox/core/components/fox-text-button';
import { MIN_COMIC_CUTS } from '@/lib/domain/content';
import { FILE_MODULE, fileModuleAccept } from '@/lib/domain/file-module';
import styles from './content-form.module.scss';
interface ContentCutsFieldProps {
/** 수정 화면에서 이미 등록된 컷 이미지 URL(1컷부터 순서대로). */
defaultCutUrls?: readonly string[];
/** 새로 고르지 않은 컷이 잃어버리지 않도록 들고 있는 기존 첨부 ID. */
retainedFileId?: string | null;
error?: string;
}
type Cut = { key: number; url: string | null };
/**
* 만화 이미지 — 시안 tbl-form(5449:10857)의 컷 업로드.
*
* 회색 판 위에 흰 컷 카드 4개가 깔리고 「컷 추가」로 계속 늘린다(기획 "4컷 이상 구성도 가능").
* 컷마다 점선 상자 자체가 파일선택 버튼이고, 아래에 「N컷 · 초기화 · 삭제」가 붙는다.
*
* 초기화는 그 컷의 선택만 비운다 — React key를 갈아 끼워 새로 마운트한다. 비제어
* `FoxFileUpload`가 들고 있는 파일과 네이티브 입력을 밖에서 비울 방법이 그것뿐이다.
*/
export function ContentCutsField({
defaultCutUrls = [],
retainedFileId,
error,
}: ContentCutsFieldProps) {
const [cuts, setCuts] = useState<Cut[]>(() => {
const count = Math.max(MIN_COMIC_CUTS, defaultCutUrls.length);
return Array.from({ length: count }, (_, index) => ({
key: index,
url: defaultCutUrls[index] ?? null,
}));
});
const [nextKey, setNextKey] = useState(cuts.length);
function addCut() {
setCuts((current) => [...current, { key: nextKey, url: null }]);
setNextKey((current) => current + 1);
}
function resetCut(key: number) {
setCuts((current) =>
current.map((cut) =>
cut.key === key ? { key: nextKey, url: null } : cut
)
);
setNextKey((current) => current + 1);
}
function removeCut(key: number) {
setCuts((current) =>
current.length <= MIN_COMIC_CUTS
? current
: current.filter((cut) => cut.key !== key)
);
}
return (
<>
<div className={styles.cutBox}>
<div className={styles.cutBody}>
{cuts.map((cut, index) => (
<div key={cut.key} className={styles.cutCard}>
<FoxFileUpload
mode="image"
name="cutFile"
accept={fileModuleAccept(FILE_MODULE.toonContent)}
thumbnailPicker
selectLabel={`${index + 1}컷 이미지 선택`}
thumbnailWidth="100%"
thumbnailHeight={172}
defaultFiles={
cut.url
? [{ id: String(cut.key), name: `${index + 1}컷`, previewUrl: cut.url }]
: undefined
}
/>
<input
type="hidden"
name="cutFileId"
defaultValue={cut.url ? (retainedFileId ?? '') : ''}
/>
<div className={styles.cutFooter}>
<span className={styles.cutLabel}>{index + 1}컷</span>
<span className={styles.cutActions}>
<FoxTextButton
size="md"
type="secondary"
leadingIcon={<i className="fox-ico fox-ico-ArrowClockwise" aria-hidden="true" />}
label="초기화"
onAction={() => resetCut(cut.key)}
/>
<FoxTextButton
size="md"
type="secondary"
leadingIcon={<i className="fox-ico fox-ico-Trash" aria-hidden="true" />}
label="삭제"
disabled={cuts.length <= MIN_COMIC_CUTS}
onAction={() => removeCut(cut.key)}
/>
</span>
</div>
</div>
))}
</div>
<div className={styles.cutBoxFooter}>
<FoxTextButton
size="md"
leadingIcon={<i className="fox-ico fox-ico-Plus" aria-hidden="true" />}
label="컷 추가"
onAction={addCut}
/>
</div>
</div>
{error && <FoxHelperText type="danger" message={error} />}
</>
);
}