Co-Authored-By: Claude Opus 5
+import type { ReactNode } from "react";
+
+interface IconButtonProps {
+ /** 접근성 이름(`aria-label`)과 네이티브 툴팁(`title`)으로 쓰인다. 생략하면 두
+ * 속성을 렌더하지 않는다 — 이 경우 children에 포함된 `sr-only` 텍스트가 접근성
+ * 이름이 되어야 한다(예: 아이콘 여러 개를 CSS로 전환 노출하며 각각 옆에 개별
+ * `sr-only` 라벨을 두는 구성). */
+ label?: string;
+ /** 아이콘(들). 장식용 아이콘은 자체적으로 `aria-hidden`을 갖는다 — 접근성 이름이
+ * 필요하면 `label`을 전달하거나 children에 `sr-only` 텍스트를 포함해야 한다. */
+ children: ReactNode;
+ type?: "button" | "submit";
+ /** 2-상태 토글일 때만 전달 → `aria-pressed` 렌더. 3-상태 순환 등에서는 생략 권장
+ * (`aria-pressed`는 이진 개념이라 의미가 어긋난다). */
+ isPressed?: boolean;
+ onClick?: () => void;
+ disabled?: boolean;
+ /** 배치(레이아웃) 조정용 클래스 패스스루만 허용 — 디자인 값 주입 금지. */
+ className?: string;
+}
+/**
+ * 아이콘 전용 버튼 비주얼. 상태를 가지지 않는 프레젠테이션 컴포넌트 — 클릭 동작·상태
+ * 관리는 호출부(feature) 책임이다.
+ */
+export function IconButton({
+ label,
+ children,
+ type = "button",
+ isPressed,
+ onClick,
+ disabled,
+ className,
+}: IconButtonProps) {
+ return (
+ <button
+ type={type}
+ onClick={onClick}
+ disabled={disabled}
+ aria-label={label}
+ title={label}
+ aria-pressed={isPressed}
+ className={`inline-flex size-9 items-center justify-center rounded-md border border-border text-foreground-muted transition-colors duration-200 hover:bg-surface-muted hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:pointer-events-none disabled:opacity-50 ${className ?? ""}`}
+ >
+ {children}
+ </button>
+ );
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?
Delete comment
Once you delete this comment, you won't be able to recover it. Are you sure you want to delete this comment?