임동욱 임동욱 07-28
feat: 아이콘 버튼 공용 컴포넌트 추가
Co-Authored-By: Claude Opus 5 
@33c5b718718a9abdc939e10d969b32f2920cb50c
 
components/ui/icon-button.tsx (added)
+++ components/ui/icon-button.tsx
@@ -0,0 +1,48 @@
+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>
+  );
+}
Add a comment
List