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 type { ReactNode, Ref } from "react";
import { cx } from "../../utils";
export interface FoxSelectOptionItemProps {
/** 옵션 목록 안에서 유일해야 한다 — 목록을 여는 요소의 `aria-activedescendant`가 가리킨다. */
id?: string;
label: ReactNode;
/** 라벨 뒤에 놓인다(시안에 앞 슬롯이 없다). 색은 라벨을 따라간다. */
icon?: ReactNode;
/** 선택된 항목. `aria-selected`가 붙고 맨 앞에 체크 아이콘이 그려진다. */
selected?: boolean;
/**
* 키보드로 이동 중인 항목. 마우스 hover와 같은 모양이지만 `:hover`로는 표현할 수 없어
* 클래스로 둔다. 호출부가 소유한다 — 항목이 스스로 켜지지 않는다.
*/
active?: boolean;
/**
* 위험한 선택지(시안의 `state=danger`). 글자·아이콘이 danger 색으로 바뀌고, 선택된 뒤에도
* 그 색을 유지한다. 시안이 hover·pressed·selected와 한 `state`로 묶어 놨지만 나머지 셋은
* 브라우저가 스스로 아는 상태라, 밖에서 받아야 하는 건 이것뿐이다.
*/
danger?: boolean;
onSelect?: () => void;
/** 포인터가 올라왔을 때. 호출부가 활성 항목을 옮기는 데 쓴다. */
onHover?: () => void;
/** 배치 조정용. 모양이 달라야 하면 여기 말고 모디파이어를 추가한다. */
className?: string;
ref?: Ref<HTMLLIElement>;
}
/**
* @fox 옵션 목록(`FoxSelectOption`)의 항목 하나. 상태를 갖지 않는다.
*
* 목록을 여는 요소가 포커스를 계속 쥐고 있어야 하므로 이 항목은 포커스를 받지 않는다 —
* 활성 표시는 `aria-activedescendant`가, 클릭은 `onSelect`가 담당한다.
*
* ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
* (또는 개별 파티셜)로 한 번 불러와야 한다.
*/
export function FoxSelectOptionItem({
id,
label,
icon,
selected = false,
active = false,
danger = false,
onSelect,
onHover,
className,
ref,
}: FoxSelectOptionItemProps) {
return (
<li
ref={ref}
id={id}
role="option"
aria-selected={selected}
className={cx(
"fox-select-option-item",
active && "fox-select-option-item--active",
danger && "fox-select-option-item--danger",
className
)}
onMouseEnter={onHover}
onClick={onSelect}
>
{selected ? (
<span className="fox-select-option-item__check" aria-hidden="true">
<CheckIcon />
</span>
) : null}
<span className="fox-select-option-item__text">{label}</span>
{icon ? (
<span className="fox-select-option-item__icon" aria-hidden="true">
{icon}
</span>
) : null}
</li>
);
}
// 시안 아이콘. 호출부가 넘기는 콘텐츠가 아니라 디자인이 정한 표시라 컴포넌트가 소유한다.
// `fill`을 `currentColor`로 두면 선택된 항목의 글자색을 물려받는데, 그 값
// (`font-theme-secondary`)이 시안의 `#1C589C`와 같아 색 규칙을 따로 둘 필요가 없다.
function CheckIcon() {
return (
<svg viewBox="0 0 16 16" fill="none" aria-hidden="true">
<path
d="M14.5306 5.03063L6.5306 13.0306C6.46092 13.1006 6.37813 13.156 6.28696 13.1939C6.1958 13.2317 6.09806 13.2512 5.99935 13.2512C5.90064 13.2512 5.8029 13.2317 5.71173 13.1939C5.62057 13.156 5.53778 13.1006 5.4681 13.0306L1.9681 9.53063C1.89833 9.46087 1.84299 9.37804 1.80524 9.28689C1.76748 9.19574 1.74805 9.09804 1.74805 8.99938C1.74805 8.90072 1.76748 8.80302 1.80524 8.71187C1.84299 8.62072 1.89833 8.53789 1.9681 8.46813C2.03786 8.39837 2.12069 8.34302 2.21184 8.30527C2.30299 8.26751 2.40069 8.24808 2.49935 8.24808C2.59801 8.24808 2.69571 8.26751 2.78686 8.30527C2.87801 8.34302 2.96083 8.39837 3.0306 8.46813L5.99997 11.4375L13.4693 3.96938C13.6102 3.82848 13.8013 3.74933 14.0006 3.74933C14.1999 3.74933 14.391 3.82848 14.5318 3.96938C14.6727 4.11028 14.7519 4.30137 14.7519 4.50063C14.7519 4.69989 14.6727 4.89098 14.5318 5.03188L14.5306 5.03063Z"
fill="currentColor"
/>
</svg>
);
}