File name
Commit message
Commit date
File name
Commit message
Commit date
09-03
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} />}
</>
);
}