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-18
08-18
08-18
08-18
File name
Commit message
Commit date
"use client";
import { cx } from "../../utils";
/** `dot`은 시안 carousel-dot, `number`는 carousel-number다. */
export type FoxCarouselIndicatorType = "dot" | "number";
export interface FoxCarouselIndicatorProps {
type?: FoxCarouselIndicatorType;
/** 1부터 센다. */
current: number;
total: number;
/**
* 점을 눌러 옮길 수 있게 한다. 넘기지 않으면 표시만 하는 조각이라 누를 곳이 없다.
* `number`형에는 누를 점이 없어 쓰이지 않는다.
*/
onSelect?: (page: number) => void;
/** 스크린리더가 읽을 이름. */
label?: string;
/** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
hidden?: boolean;
/** 배치 조정용. */
className?: string;
}
/**
* @fox 캐러셀 현재 위치 표시. 흰 알약 안에 점 또는 숫자가 들어간다.
*
* 시안의 carousel-dot과 carousel-number를 하나로 둔다 — 껍데기(흰 알약·높이·좌우 여백·
* 간격)가 같고 안에 든 것만 다르다.
*
* ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
* (또는 개별 파티셜)로 한 번 불러와야 한다.
*/
export function FoxCarouselIndicator({
type = "dot",
current,
total,
onSelect,
label = "현재 위치",
hidden = false,
className,
}: FoxCarouselIndicatorProps) {
if (hidden || total < 1) {
return null;
}
const safe = Math.min(Math.max(current, 1), total);
if (type === "number") {
return (
<p
className={cx("fox-carousel-indicator", "fox-carousel-indicator--number", className)}
aria-label={`${label} ${safe} / ${total}`}
>
<span className="fox-carousel-indicator__current" aria-hidden="true">
{safe}
</span>
<span className="fox-carousel-indicator__separator" aria-hidden="true" />
<span className="fox-carousel-indicator__total" aria-hidden="true">
{total}
</span>
</p>
);
}
const dots = Array.from({ length: total }, (unused, index) => index + 1);
return (
<div
className={cx("fox-carousel-indicator", "fox-carousel-indicator--dot", className)}
role="group"
aria-label={`${label} ${safe} / ${total}`}
>
{dots.map((page) => {
const active = page === safe;
const dotClass = cx(
"fox-carousel-indicator__dot",
active && "fox-carousel-indicator__dot--active"
);
// 옮길 수 없으면 버튼으로 두지 않는다 — 눌리지 않는 버튼이 초점을 먹는다.
return onSelect ? (
<button
key={page}
className={dotClass}
type="button"
aria-label={`${page}번째로 이동`}
aria-current={active || undefined}
onClick={() => onSelect(page)}
/>
) : (
<span key={page} className={dotClass} />
);
})}
</div>
);
}