"use client"; import { FoxInput, FoxInputState, FoxSelect, FoxSelectItem, FoxSelectOption, FoxSelectOptionItem } from "@/@fox/core/components"; import { useEffect, useState, type CSSProperties, type ReactNode } from "react"; import { FoxAddress, type FoxAddressSearchResult, type FoxAddressValue } from "../core/components/fox-address"; import { FoxAlert } from "../core/components/fox-alert"; import { FoxBreadcrumb } from "../core/components/fox-breadcrumb"; import { FoxButton } from "../core/components/fox-button"; import { FoxButtonGroup } from "../core/components/fox-button-group"; import { FoxButtonPanel } from "../core/components/fox-button-panel"; import { FoxCarouselArea } from "../core/components/fox-carousel-area"; import { FoxCarouselButton } from "../core/components/fox-carousel-button"; import { FoxCarouselIndicator } from "../core/components/fox-carousel-indicator"; import { FoxCertified } from "../core/components/fox-certified"; import { FoxCheckIcon } from "../core/components/fox-check-icon"; import { FoxCheckbox } from "../core/components/fox-checkbox"; import { FoxCheckboxGroup } from "../core/components/fox-checkbox-group"; import { FoxConditionalOption } from "../core/components/fox-conditional-option"; import { FoxContextualTrigger } from "../core/components/fox-contextual-trigger"; import { FoxEmail } from "../core/components/fox-email"; import { FoxFileUpload, type FoxFileItem } from "../core/components/fox-file-upload"; import { FoxFormLabel } from "../core/components/fox-form-label"; import { FoxHelperText } from "../core/components/fox-helper-text"; import { FoxIconButton } from "../core/components/fox-icon-button"; import { FoxLinkButton } from "../core/components/fox-link-button"; import { FoxPagination } from "../core/components/fox-pagination"; import { FoxPhoneNumber, type FoxPhoneNumberState } from "../core/components/fox-phone-number"; import { FoxPopover } from "../core/components/fox-popover"; import { FoxRadio } from "../core/components/fox-radio"; import { FoxRadioGroup } from "../core/components/fox-radio-group"; import { FoxRadioIcon } from "../core/components/fox-radio-icon"; import { FoxTag } from "../core/components/fox-tag"; import { FoxTagList } from "../core/components/fox-tag-list"; import { FoxToast } from "../core/components/fox-toast"; import { FoxTextArea, type FoxTextAreaState } from "../core/components/fox-text-area"; import { FoxTextButton } from "../core/components/fox-text-button"; import { FoxToggleSwitch } from "../core/components/fox-toggle-switch"; import { FoxBulletList } from "../core/components/fox-bullet-list"; import { FoxBulletListItem } from "../core/components/fox-bullet-list-item"; import { FoxBadge } from "../core/components/fox-badge"; import { FoxBadgeDot } from "../core/components/fox-badge-dot"; import { FoxBadgeGroup } from "../core/components/fox-badge-group"; import { FoxTable } from "../core/components/fox-table"; import { FoxTableCell } from "../core/components/fox-table-cell"; import { FoxTableRow } from "../core/components/fox-table-row"; import { FoxTab } from "../core/components/fox-tab"; import { FoxTabItem } from "../core/components/fox-tab-item"; import { FoxSpinner } from "../core/components/fox-spinner"; import { FoxSnackbar } from "../core/components/fox-snackbar"; import { FoxSnackbarList } from "../core/components/fox-snackbar-list"; import { FoxStatusIndicator } from "../core/components/fox-status-indicator"; import { FoxStepIndicatorItem } from "../core/components/fox-step-indicator-item"; import { FoxStepIndicator } from "../core/components/fox-step-indicator"; import { FoxChip, type FoxChipItem } from "../core/components/fox-chip"; import { FoxChipArea } from "../core/components/fox-chip-area"; import { FoxSelectText } from "../core/components/fox-select-text"; import { FoxSegmentedControl } from "../core/components/fox-segmented-control"; import { FoxSegmentedControlItem } from "../core/components/fox-segmented-control-item"; import { FoxTooltip } from "../core/components/fox-tooltip"; import { FoxTooltipRich } from "../core/components/fox-tooltip-rich"; import { FoxToggleQuantity } from "../core/components/fox-toggle-quantity"; import { FoxPageHeader } from "../core/components/fox-page-header"; import { FoxModal } from "../core/components/fox-modal"; import { FoxAccordion } from "../core/components/fox-accordion"; import { FoxCard } from "../core/components/fox-card"; import { FoxDisclosure } from "../core/components/fox-disclosure"; import styles from "./dev-test.module.scss"; export interface ComponentExample { /** 해시 링크에 쓰이는 kebab-case 식별자. */ id: string; name: string; description?: string; /** 눈으로 비교해야 하는 조합을 빠짐없이 넣는다 — 예제가 곧 회귀 확인 수단이다. */ variants: { label: string; node: ReactNode }[]; } // 자리 확인용 도형. `currentColor`로 그려야 계열별 색이 입혀진다. style은 FoxSelect // 힌트 아이콘처럼 크기를 줄여 써야 하는 자리에 쓴다. function DemoIcon({ style }: { style?: CSSProperties } = {}) { return ( ); } function DemoStar() { return ( ); } // overlay 계열은 어두운 반투명 배경이라 밝은 바탕 위에서는 대비를 볼 수 없다. function OnPhoto({ children }: { children: ReactNode }) { return ( {children} ); } // 입력 예제는 폭이 있어야 시안(360px)처럼 보인다. function InputStage({ children, wide = false }: { children: ReactNode; wide?: boolean }) { return
{children}
; } // 지우기 버튼과 자동 상태 판정을 실제로 눌러 보려면 예제가 값을 들고 있어야 한다. function InputLiveDemo() { const [text, setText] = useState("지우기를 눌러보세요"); return ( <> ); } // 카운터가 실제로 올라가는 걸 보려면 예제가 값을 들고 있어야 한다. function TextAreaLiveDemo() { const [text, setText] = useState("입력하면 아래 숫자가 따라 올라갑니다."); return ( <> ); } const PHONE_PREFIXES = ["010", "011", "016", "017", "018", "019", "02", "031", "051"]; // 값이 숫자만 담긴 문자열 하나라는 걸 보려면 예제가 그 값을 그대로 보여줘야 한다. function PhoneNumberLiveDemo() { const [unit, setUnit] = useState("01012345678"); const [combine, setCombine] = useState("0212345678"); return ( <> ); } const EMAIL_DOMAINS = ["naver.com", "gmail.com", "daum.net", "kakao.com"]; // 값이 합쳐진 문자열 하나라는 걸 보려면 예제가 그 값을 그대로 보여줘야 한다. function EmailLiveDemo() { const [email, setEmail] = useState("hong@naver.com"); return ( ); } // 어떤 우편번호 서비스를 쓰든 이 모양으로 돌려주면 된다 — 여기서는 지연만 흉내낸다. const FAKE_SEARCH_RESULT: FoxAddressSearchResult = { zipCode: "06236", address: "서울 강남구 테헤란로 152", detail: "강남파이낸스센터", }; function AddressLiveDemo({ cancel = false }) { const [value, setValue] = useState({ zipCode: "", address: "", detail: "" }); return ( <> { await new Promise((resolve) => setTimeout(resolve, 400)); return cancel ? null : FAKE_SEARCH_RESULT; }} />
{JSON.stringify(value, null, 2)}
); } // 목록은 업로드 결과에 따라 바뀌므로, 오류 줄까지 보려면 예제가 값을 들고 있어야 한다. const SAMPLE_FILES: FoxFileItem[] = [ { id: "a", name: "사업자등록증", meta: ".PDF (20KB)" }, { id: "b", name: "아주 긴 파일 이름이라 가운데에서 말줄임되어야 하는 첨부물", meta: ".XLSX (1.2MB)" }, { id: "c", name: "용량초과파일", meta: ".ZIP (32MB)", error: true, message: "등록 가능한 파일 용량을 초과하였습니다. 20MB 미만의 파일만 등록할 수 있습니다.", }, ]; function FileUploadLiveDemo({ mode }: { mode: "default" | "area" }) { const [files, setFiles] = useState(SAMPLE_FILES); return ( setFiles((prev) => [ ...prev, ...picked.map((file) => ({ id: file.name, name: file.name, meta: `(${file.size}B)` })), ]) } onRemove={(id) => setFiles((prev) => prev.filter((item) => item.id !== id))} description={mode === "default" ? "JPG, PNG 파일만 가능 (최대 5MB)" : undefined} multiple /> ); } // 이벤트가 실제로 도는 걸 보려면 예제가 결과를 적어 둬야 한다. 서버 대신 지연만 흉내낸다. function CertifiedLiveDemo({ failSend = false, failVerify = false }) { const [log, setLog] = useState([]); const write = (line: string) => setLog((prev) => [line, ...prev].slice(0, 5)); const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); return ( <> { await wait(400); if (failSend) throw new Error("발송 한도를 넘었습니다"); write(`보내기 요청 → ${phone}`); }} onVerify={async (_phone, code) => { await wait(400); if (failVerify || code !== "123456") throw new Error("번호가 맞지 않습니다"); }} onSendSuccess={() => write("onSendSuccess")} onSendFail={(error) => write(`onSendFail — ${(error as Error).message}`)} onVerifySuccess={() => write("onVerifySuccess — 이제 전부 잠깁니다")} onVerifyFail={(error) => write(`onVerifyFail — ${(error as Error).message}`)} onExpire={() => write("onExpire")} />
{log.length ? log.join("\n") : "이벤트가 여기에 쌓입니다 (인증번호는 123456)"}
); } // active는 호출부가 소유하므로 켜고 끄는 걸 보려면 예제가 상태를 들고 있어야 한다. function IconToggleDemo() { const [on, setOn] = useState(false); return ( <> } active={on} onAction={() => setOn((prev) => !prev)} /> } active={on} onAction={() => setOn((prev) => !prev)} /> } active={on} onAction={() => setOn((prev) => !prev)} /> ); } const SELECT_ITEMS: FoxSelectItem[] = [ { value: "a", label: "옵션 A" }, { value: "b", label: "옵션 B" }, { value: "c", label: "옵션 C" }, ]; // 옵션 아이콘은 별도 목록으로 둔다 — 위 목록에 섞으면 크기·상태 예제가 전부 같이 바뀌어 // 무엇을 보는 예제인지 흐려진다. 아이콘은 시안 미정이라 자리 확인용 도형이다. const SELECT_ITEMS_RICH: FoxSelectItem[] = [ { value: "a", label: "아이콘 있는 옵션", icon: }, { value: "b", label: "아이콘 없는 옵션" }, { value: "c", label: "아주 긴 옵션 라벨 — 트리거에서는 말줄임으로 잘린다" }, ]; const BREADCRUMB_ITEMS = [ { label: "홈", href: "#" }, { label: "1Depth", href: "#" }, { label: "2Depth", href: "#" }, { label: "3Depth" }, ]; function PaginationLiveDemo() { const [page, setPage] = useState(1); return (

현재 {page} / 12 페이지

); } function CarouselDotPickDemo() { const [page, setPage] = useState(1); return ; } function CarouselLiveDemo() { const total = 5; const [page, setPage] = useState(1); const [playing, setPlaying] = useState(true); const move = (step: number) => setPage((prev) => ((prev - 1 + step + total) % total) + 1); return (