File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
08-14
08-14
08-18
08-18
08-14
08-19
08-18
08-18
08-18
08-18
File name
Commit message
Commit date
import type { ReactNode, Ref } from "react";
import { cx } from "../../utils";
/** 시안의 state. 지난 단계 · 지금 단계 · 아직 오지 않은 단계다. */
export type FoxStepIndicatorItemState = "completion" | "ongoing" | "before";
export interface FoxStepIndicatorItemProps {
state?: FoxStepIndicatorItemState;
/** 단계 번호 줄(작고 흐린 글자). 넘기지 않으면 렌더링하지 않는다. */
step?: ReactNode;
/** 단계 제목 줄. 넘기지 않으면 렌더링하지 않는다. */
title?: ReactNode;
/**
* 동그라미 오른쪽으로 뻗는 선. **마지막 단계에서는 꺼야 한다** — 켜 두면 선이 묶음 밖으로
* 삐져나간다. 선 색은 `state`가 정한다(지난 단계만 진하다).
*/
line?: boolean;
/** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
hidden?: boolean;
id?: string;
/** 배치 조정용. 시안의 폭(80px)을 바꿔야 할 때도 여기로 준다. */
className?: string;
ref?: Ref<HTMLDivElement>;
}
/** `Record`로 고정해 상태를 추가하면 항목 누락이 타입 에러가 되게 한다. */
const STATE_CLASS: Record<FoxStepIndicatorItemState, string> = {
completion: "fox-step-indicator-item--completion",
ongoing: "fox-step-indicator-item--ongoing",
before: "fox-step-indicator-item--before",
};
/**
* @fox 단계 하나. 위에 동그라미와 선, 아래에 단계 번호와 제목이 놓인다.
* `FoxStepIndicator` 안에 여러 개를 늘어놓아 쓴다.
*
* 상태를 갖지 않는다 — 어디까지 왔는지는 호출부가 각 단계의 `state`로 정한다.
*
* **모바일에서는 글자가 사라지고 동그라미와 선만 남는다.** 그 판단은 CSS가 한다(화면 폭) —
* 기기를 prop으로 받지 않는다. 조건부 렌더로 하면 창 폭이 바뀔 때마다 DOM이 들락거리고,
* React를 쓰지 않는 소비자는 같은 결과를 얻지 못한다(`FoxChipSelectOption`의 확인 버튼과
* 같은 근거).
*
* ⚠️ 진행 상태를 소리로도 전해야 하면 감싼 쪽이 알린다 — 이 조각의 동그라미·선은 장식이라
* 보조기술에 아무것도 말하지 않고, 읽히는 것은 단계 번호와 제목뿐이다. 모바일에서는 그
* 글자마저 사라지므로, 그 화면에서 순서를 전해야 하면 호출부가 이름을 따로 준다.
*
* ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
* (또는 개별 파티셜)로 한 번 불러와야 한다.
*/
export function FoxStepIndicatorItem({
state = "before",
step,
title,
line = true,
hidden = false,
id,
className,
ref,
}: FoxStepIndicatorItemProps) {
if (hidden) {
return null;
}
return (
<div
ref={ref}
id={id}
className={cx("fox-step-indicator-item", STATE_CLASS[state], className)}
>
<div className="fox-step-indicator-item__indicator" aria-hidden="true">
<span className="fox-step-indicator-item__mark">
{state === "completion" ? <CheckMark /> : null}
{state === "ongoing" ? <span className="fox-step-indicator-item__dot" /> : null}
</span>
{line ? <span className="fox-step-indicator-item__line" /> : null}
</div>
{step === undefined && title === undefined ? null : (
<div className="fox-step-indicator-item__text">
{step === undefined ? null : (
<span className="fox-step-indicator-item__step">{step}</span>
)}
{title === undefined ? null : (
<span className="fox-step-indicator-item__title">{title}</span>
)}
</div>
)}
</div>
);
}
// 지난 단계의 체크 표시. `@fox/core/icons`(Phosphor)를 쓰지 않는다 — 시안이 준 것은 선으로
// 그린 10×8 글리프이고, Phosphor의 Check는 면으로 채운 다른 그림이라 크기·굵기가 맞지 않는다.
// `stroke`를 시안의 리터럴(흰색) 대신 `currentColor`로 두면 색을 상태 규칙이 정한다.
function CheckMark() {
return (
<svg viewBox="0 0 12 10" fill="none" aria-hidden="true">
<path
d="M0.75 4.75006L4.96726 8.75006L10.75 0.750061"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}