컴포넌트는 className을 안 만든다 — prop을 그대로 DOM 어트리뷰트로 흘리고, variant 분기는 CSS 어트리뷰트 셀렉터가 담당한다.
// 컴포넌트 전체 = h(tag, { ...props, k: name }) 한 줄
export const Badge = createSimpleComponent<'span', BadgeOwnProps>(
'badge',
'span',
)
// → <span k="badge" variant="secondary"> (variant가 class가 아니라 attr로 그대로 나감)
[k="badge"] { /* default */ }
[k="badge"][variant="secondary"] { … }
[k="badge"][variant="destructive"]:hover { … }
createSimpleComponent 가 하는 일
/**
* - defaults를 props 아래에 깔고
* - k={name} 할당하고
* - h(tag, normalizedProps)
*/
createSimpleComponent<T, P>(name, tag)
k?: never로 소비자가 셀렉터 키k를 덮어쓰는 걸 타입 레벨에서 차단.Props = Omit<JSX.IntrinsicElements[T], keyof P> & P→ own prop이 동명 intrinsic 어트리뷰트를 덮어씀 (Badge의variant가 우선).tag를(props) => T함수로도 받아 폴리모픽 (href있으면<a>, 없으면<button>). 단 분기에 들어가는 건 defaults 적용 전 원본 props.
대가: 런타임 동적 variant 계산·조건부 로직은 포기 (CSS 셀렉터로 표현 가능한 범위만).