// ButtonContent.tsx — WING-OPS Button 컴포넌트 상세 페이지 (다크/라이트 테마 지원) import type { DesignTheme } from './designTheme'; // ---------- 타입 ---------- interface ButtonSizeRow { label: string; heightClass: string; heightPx: number; px: number; } interface ButtonVariantStyle { bg: string; text: string; border?: string; } interface ButtonStateRow { state: string; accent: ButtonVariantStyle; primary: ButtonVariantStyle; secondary: ButtonVariantStyle; tertiary: ButtonVariantStyle; tertiaryFilled: ButtonVariantStyle; } // ---------- 데이터 ---------- const BUTTON_SIZES: ButtonSizeRow[] = [ { label: 'XLarge (56)', heightClass: 'h-14', heightPx: 56, px: 24 }, { label: 'Large (48)', heightClass: 'h-12', heightPx: 48, px: 20 }, { label: 'Medium (44)', heightClass: 'h-11', heightPx: 44, px: 16 }, { label: 'Small (32)', heightClass: 'h-8', heightPx: 32, px: 12 }, { label: 'XSmall (24)', heightClass: 'h-6', heightPx: 24, px: 8 }, ]; const VARIANTS = ['Accent', 'Primary', 'Secondary', 'Tertiary', 'Tertiary (filled)'] as const; const getDarkStateRows = (): ButtonStateRow[] => [ { state: 'Default', accent: { bg: '#ef4444', text: '#fff' }, primary: { bg: '#1a1a2e', text: '#fff' }, secondary: { bg: '#6b7280', text: '#fff' }, tertiary: { bg: 'transparent', text: '#c2c6d6', border: '#6b7280' }, tertiaryFilled: { bg: '#374151', text: '#c2c6d6' }, }, { state: 'Hover', accent: { bg: '#dc2626', text: '#fff' }, primary: { bg: '#2d2d44', text: '#fff' }, secondary: { bg: '#7c8393', text: '#fff' }, tertiary: { bg: 'rgba(255,255,255,0.05)', text: '#c2c6d6', border: '#9ca3af' }, tertiaryFilled: { bg: '#4b5563', text: '#c2c6d6' }, }, { state: 'Pressed', accent: { bg: '#b91c1c', text: '#fff' }, primary: { bg: '#3d3d5c', text: '#fff' }, secondary: { bg: '#9ca3af', text: '#fff' }, tertiary: { bg: 'rgba(255,255,255,0.1)', text: '#c2c6d6', border: '#9ca3af' }, tertiaryFilled: { bg: '#6b7280', text: '#c2c6d6' }, }, { state: 'Disabled', accent: { bg: 'rgba(239,68,68,0.3)', text: 'rgba(255,255,255,0.5)' }, primary: { bg: 'rgba(26,26,46,0.5)', text: 'rgba(255,255,255,0.4)' }, secondary: { bg: 'rgba(107,114,128,0.3)', text: 'rgba(255,255,255,0.4)' }, tertiary: { bg: 'transparent', text: 'rgba(255,255,255,0.3)', border: 'rgba(107,114,128,0.3)' }, tertiaryFilled: { bg: 'rgba(55,65,81,0.3)', text: 'rgba(255,255,255,0.3)' }, }, ]; const getLightStateRows = (): ButtonStateRow[] => [ { state: 'Default', accent: { bg: '#ef4444', text: '#fff' }, primary: { bg: '#1a1a2e', text: '#fff' }, secondary: { bg: '#d1d5db', text: '#374151' }, tertiary: { bg: 'transparent', text: '#374151', border: '#d1d5db' }, tertiaryFilled: { bg: '#e5e7eb', text: '#374151' }, }, { state: 'Hover', accent: { bg: '#dc2626', text: '#fff' }, primary: { bg: '#2d2d44', text: '#fff' }, secondary: { bg: '#bcc0c7', text: '#374151' }, tertiary: { bg: 'rgba(0,0,0,0.03)', text: '#374151', border: '#9ca3af' }, tertiaryFilled: { bg: '#d1d5db', text: '#374151' }, }, { state: 'Pressed', accent: { bg: '#b91c1c', text: '#fff' }, primary: { bg: '#3d3d5c', text: '#fff' }, secondary: { bg: '#9ca3af', text: '#374151' }, tertiary: { bg: 'rgba(0,0,0,0.06)', text: '#374151', border: '#6b7280' }, tertiaryFilled: { bg: '#bcc0c7', text: '#374151' }, }, { state: 'Disabled', accent: { bg: 'rgba(239,68,68,0.3)', text: 'rgba(255,255,255,0.5)' }, primary: { bg: 'rgba(26,26,46,0.3)', text: 'rgba(255,255,255,0.5)' }, secondary: { bg: 'rgba(209,213,219,0.5)', text: 'rgba(55,65,81,0.4)' }, tertiary: { bg: 'transparent', text: 'rgba(55,65,81,0.3)', border: 'rgba(209,213,219,0.5)' }, tertiaryFilled: { bg: 'rgba(229,231,235,0.5)', text: 'rgba(55,65,81,0.3)' }, }, ]; // ---------- Props ---------- interface ButtonContentProps { theme: DesignTheme; } // ---------- 헬퍼 ---------- function getVariantStyle(row: ButtonStateRow, variantIndex: number): ButtonVariantStyle { const keys: (keyof Omit)[] = [ 'accent', 'primary', 'secondary', 'tertiary', 'tertiaryFilled', ]; return row[keys[variantIndex]]; } // ---------- 컴포넌트 ---------- export const ButtonContent = ({ theme }: ButtonContentProps) => { const t = theme; const isDark = t.mode === 'dark'; const sectionCardBg = isDark ? 'rgba(255,255,255,0.03)' : '#f5f5f5'; const dividerColor = isDark ? 'rgba(255,255,255,0.08)' : '#e5e7eb'; const badgeBg = isDark ? '#4a5568' : '#6b7280'; const annotationColor = isDark ? '#f87171' : '#ef4444'; const buttonDarkBg = isDark ? '#e2e8f0' : '#1a1a2e'; const buttonDarkText = isDark ? '#1a1a2e' : '#fff'; const stateRows = isDark ? getDarkStateRows() : getLightStateRows(); return (
{/* ── 섹션 1: 헤더 ── */}

Components

Button

사용자의 의도를 명확하게 전달하고, 행동을 유도합니다.

버튼의 형태와 색은 우선순위를 시각적으로 구분합니다.

{/* ── 섹션 2: Anatomy ── */}

Anatomy

{/* Anatomy 카드 */}
{/* 왼쪽: 텍스트 + 아이콘 버튼 */}
{/* 버튼 본체 */}
{/* Container 번호 — 테두리 점선 */} 레이블 + {/* 번호 뱃지 — Container (1) */} 1 {/* 번호 뱃지 — Label (2) */} 2 {/* 번호 뱃지 — Icon (3) */} 3
텍스트 + 아이콘 버튼
{/* 오른쪽: 아이콘 전용 버튼 */}
♥ {/* 번호 뱃지 — Container (1) */} 1 {/* 번호 뱃지 — Icon (3) */} 3
아이콘 전용 버튼
{/* 번호 목록 */}
    {[ { label: 'Container', desc: '버튼의 외곽 영역. 클릭 가능한 전체 영역을 정의합니다.' }, { label: 'Label', desc: '버튼의 텍스트 레이블.' }, { label: 'Icon (Optional)', desc: '선택적으로 추가되는 아이콘 요소.' }, ].map((item) => (
  1. {item.label} {' '}— {item.desc}
  2. ))}
{/* ── 섹션 3: Spec ── */}

Spec

{/* 3-1. Size */}

1. Size

{BUTTON_SIZES.map((size) => (
{/* 라벨 */} {size.label} {/* 실제 크기 버튼 */}
))}
{/* 3-2. Container */}

2. Container

{/* Flexible */}
Flexible
{/* 좌측 padding 치수선 */}
20px
{/* 우측 padding 치수선 */}
20px
콘텐츠에 맞게 너비가 자동으로 조정됩니다.
{/* Fixed */}
Fixed
{/* 고정 너비 표시 */}
Fixed Width
너비가 고정된 버튼입니다.
{/* 3-3. Label */}

3. Label

{[ { resolution: '해상도 430', width: '100%', maxWidth: '390px', padding: 16 }, { resolution: '해상도 360', width: '100%', maxWidth: '328px', padding: 16 }, { resolution: '해상도 320', width: '248px', maxWidth: '248px', padding: 16 }, ].map((item) => (
{item.resolution}
{/* 패딩 주석 */} padding {item.padding}
))}
{/* ── 섹션 4: Style (변형 × 상태 매트릭스) ── */}

Style

{/* 열 헤더 */} {/* 빈 셀 (상태 열) */} ))} {stateRows.map((row, rowIdx) => ( {/* 상태 라벨 */} {/* 각 변형별 버튼 셀 */} {VARIANTS.map((_, vIdx) => { const style = getVariantStyle(row, vIdx); return ( ); })} ))}
{VARIANTS.map((variant) => ( {variant}
{row.state}
); }; export default ButtonContent;