742 lines
27 KiB
TypeScript
742 lines
27 KiB
TypeScript
// 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<ButtonStateRow, 'state'>)[] = [
|
||
'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 (
|
||
<div className="p-12" style={{ color: t.textPrimary }}>
|
||
<div style={{ maxWidth: '64rem' }}>
|
||
|
||
{/* ── 섹션 1: 헤더 ── */}
|
||
<div
|
||
className="pb-10 mb-12 border-b border-solid"
|
||
style={{ borderColor: dividerColor }}
|
||
>
|
||
<p
|
||
className="font-mono text-sm uppercase tracking-widest mb-3"
|
||
style={{ color: t.textAccent }}
|
||
>
|
||
Components
|
||
</p>
|
||
<h1
|
||
className="text-4xl font-bold mb-4"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
Button
|
||
</h1>
|
||
<p
|
||
className="text-lg mb-1"
|
||
style={{ color: t.textSecondary }}
|
||
>
|
||
사용자의 의도를 명확하게 전달하고, 행동을 유도합니다.
|
||
</p>
|
||
<p
|
||
className="text-lg"
|
||
style={{ color: t.textSecondary }}
|
||
>
|
||
버튼의 형태와 색은 우선순위를 시각적으로 구분합니다.
|
||
</p>
|
||
</div>
|
||
|
||
{/* ── 섹션 2: Anatomy ── */}
|
||
<div className="mb-16">
|
||
<h2
|
||
className="text-2xl font-bold mb-8"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
Anatomy
|
||
</h2>
|
||
|
||
{/* Anatomy 카드 */}
|
||
<div
|
||
className="rounded-xl p-10 mb-8"
|
||
style={{ backgroundColor: sectionCardBg }}
|
||
>
|
||
<div className="flex flex-row items-start gap-16 justify-center">
|
||
|
||
{/* 왼쪽: 텍스트 + 아이콘 버튼 */}
|
||
<div className="flex flex-col items-center gap-6">
|
||
<div className="relative">
|
||
{/* 버튼 본체 */}
|
||
<div
|
||
className="relative inline-flex items-center gap-2 px-5 rounded-md"
|
||
style={{
|
||
height: '44px',
|
||
backgroundColor: buttonDarkBg,
|
||
color: buttonDarkText,
|
||
fontSize: '14px',
|
||
fontWeight: 600,
|
||
}}
|
||
>
|
||
{/* Container 번호 — 테두리 점선 */}
|
||
<span
|
||
className="absolute inset-0 rounded-md pointer-events-none"
|
||
style={{
|
||
border: `1.5px dashed ${isDark ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.20)'}`,
|
||
}}
|
||
/>
|
||
<span>레이블</span>
|
||
<span className="font-bold">+</span>
|
||
|
||
{/* 번호 뱃지 — Container (1) */}
|
||
<span
|
||
className="absolute flex items-center justify-center rounded-full text-white font-bold"
|
||
style={{
|
||
width: '22px',
|
||
height: '22px',
|
||
fontSize: '10px',
|
||
backgroundColor: badgeBg,
|
||
top: '-12px',
|
||
left: '-12px',
|
||
}}
|
||
>
|
||
1
|
||
</span>
|
||
|
||
{/* 번호 뱃지 — Label (2) */}
|
||
<span
|
||
className="absolute flex items-center justify-center rounded-full text-white font-bold"
|
||
style={{
|
||
width: '22px',
|
||
height: '22px',
|
||
fontSize: '10px',
|
||
backgroundColor: badgeBg,
|
||
top: '-12px',
|
||
left: '50%',
|
||
transform: 'translateX(-50%)',
|
||
}}
|
||
>
|
||
2
|
||
</span>
|
||
|
||
{/* 번호 뱃지 — Icon (3) */}
|
||
<span
|
||
className="absolute flex items-center justify-center rounded-full text-white font-bold"
|
||
style={{
|
||
width: '22px',
|
||
height: '22px',
|
||
fontSize: '10px',
|
||
backgroundColor: badgeBg,
|
||
top: '-12px',
|
||
right: '-12px',
|
||
}}
|
||
>
|
||
3
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<span
|
||
className="text-xs font-mono"
|
||
style={{ color: t.textMuted }}
|
||
>
|
||
텍스트 + 아이콘 버튼
|
||
</span>
|
||
</div>
|
||
|
||
{/* 오른쪽: 아이콘 전용 버튼 */}
|
||
<div className="flex flex-col items-center gap-6">
|
||
<div className="relative">
|
||
<div
|
||
className="relative inline-flex items-center justify-center rounded-md"
|
||
style={{
|
||
width: '44px',
|
||
height: '44px',
|
||
backgroundColor: buttonDarkBg,
|
||
color: buttonDarkText,
|
||
fontSize: '18px',
|
||
}}
|
||
>
|
||
<span
|
||
className="absolute inset-0 rounded-md pointer-events-none"
|
||
style={{
|
||
border: `1.5px dashed ${isDark ? 'rgba(255,255,255,0.25)' : 'rgba(0,0,0,0.20)'}`,
|
||
}}
|
||
/>
|
||
♥
|
||
|
||
{/* 번호 뱃지 — Container (1) */}
|
||
<span
|
||
className="absolute flex items-center justify-center rounded-full text-white font-bold"
|
||
style={{
|
||
width: '22px',
|
||
height: '22px',
|
||
fontSize: '10px',
|
||
backgroundColor: badgeBg,
|
||
top: '-12px',
|
||
left: '-12px',
|
||
}}
|
||
>
|
||
1
|
||
</span>
|
||
|
||
{/* 번호 뱃지 — Icon (3) */}
|
||
<span
|
||
className="absolute flex items-center justify-center rounded-full text-white font-bold"
|
||
style={{
|
||
width: '22px',
|
||
height: '22px',
|
||
fontSize: '10px',
|
||
backgroundColor: badgeBg,
|
||
top: '-12px',
|
||
right: '-12px',
|
||
}}
|
||
>
|
||
3
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<span
|
||
className="text-xs font-mono"
|
||
style={{ color: t.textMuted }}
|
||
>
|
||
아이콘 전용 버튼
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 번호 목록 */}
|
||
<ol className="flex flex-col gap-2 pl-5 list-decimal">
|
||
{[
|
||
{ label: 'Container', desc: '버튼의 외곽 영역. 클릭 가능한 전체 영역을 정의합니다.' },
|
||
{ label: 'Label', desc: '버튼의 텍스트 레이블.' },
|
||
{ label: 'Icon (Optional)', desc: '선택적으로 추가되는 아이콘 요소.' },
|
||
].map((item) => (
|
||
<li key={item.label} style={{ color: t.textSecondary }}>
|
||
<span className="font-bold" style={{ color: t.textPrimary }}>
|
||
{item.label}
|
||
</span>
|
||
{' '}— {item.desc}
|
||
</li>
|
||
))}
|
||
</ol>
|
||
</div>
|
||
|
||
{/* ── 섹션 3: Spec ── */}
|
||
<div className="mb-16">
|
||
<h2
|
||
className="text-2xl font-bold mb-10"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
Spec
|
||
</h2>
|
||
|
||
{/* 3-1. Size */}
|
||
<div className="mb-12">
|
||
<h3
|
||
className="text-xl font-semibold mb-6"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
1. Size
|
||
</h3>
|
||
<div
|
||
className="rounded-xl p-8"
|
||
style={{ backgroundColor: sectionCardBg }}
|
||
>
|
||
<div className="flex flex-col gap-5">
|
||
{BUTTON_SIZES.map((size) => (
|
||
<div key={size.label} className="flex items-center justify-between gap-8">
|
||
{/* 라벨 */}
|
||
<span
|
||
className="font-mono text-sm w-36 shrink-0"
|
||
style={{ color: t.textSecondary }}
|
||
>
|
||
{size.label}
|
||
</span>
|
||
|
||
{/* 실제 크기 버튼 */}
|
||
<div className="flex-1 flex items-center">
|
||
<button
|
||
type="button"
|
||
className="rounded-md font-semibold text-sm"
|
||
style={{
|
||
height: `${size.heightPx}px`,
|
||
paddingLeft: `${size.px}px`,
|
||
paddingRight: `${size.px}px`,
|
||
backgroundColor: buttonDarkBg,
|
||
color: buttonDarkText,
|
||
border: 'none',
|
||
cursor: 'default',
|
||
fontSize: size.heightPx <= 24 ? '11px' : size.heightPx <= 32 ? '12px' : '14px',
|
||
}}
|
||
>
|
||
레이블
|
||
</button>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 3-2. Container */}
|
||
<div className="mb-12">
|
||
<h3
|
||
className="text-xl font-semibold mb-6"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
2. Container
|
||
</h3>
|
||
<div
|
||
className="rounded-xl p-8"
|
||
style={{ backgroundColor: sectionCardBg }}
|
||
>
|
||
<div className="flex flex-col gap-8">
|
||
|
||
{/* Flexible */}
|
||
<div className="flex flex-col gap-3">
|
||
<span
|
||
className="font-mono text-sm font-bold"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
Flexible
|
||
</span>
|
||
<div className="flex items-center gap-4">
|
||
<div className="relative inline-flex">
|
||
{/* 좌측 padding 치수선 */}
|
||
<div
|
||
className="absolute top-1/2 flex items-center"
|
||
style={{
|
||
left: '0',
|
||
transform: 'translateY(-50%)',
|
||
color: annotationColor,
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
width: '20px',
|
||
height: '1px',
|
||
backgroundColor: annotationColor,
|
||
}}
|
||
/>
|
||
<span
|
||
className="font-mono absolute"
|
||
style={{
|
||
fontSize: '9px',
|
||
color: annotationColor,
|
||
top: '-14px',
|
||
left: '2px',
|
||
whiteSpace: 'nowrap',
|
||
}}
|
||
>
|
||
20px
|
||
</span>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
className="rounded-md font-semibold"
|
||
style={{
|
||
height: '44px',
|
||
paddingLeft: '20px',
|
||
paddingRight: '20px',
|
||
backgroundColor: buttonDarkBg,
|
||
color: buttonDarkText,
|
||
fontSize: '14px',
|
||
border: 'none',
|
||
cursor: 'default',
|
||
}}
|
||
>
|
||
레이블
|
||
</button>
|
||
|
||
{/* 우측 padding 치수선 */}
|
||
<div
|
||
className="absolute top-1/2 flex items-center justify-end"
|
||
style={{
|
||
right: '0',
|
||
transform: 'translateY(-50%)',
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
width: '20px',
|
||
height: '1px',
|
||
backgroundColor: annotationColor,
|
||
}}
|
||
/>
|
||
<span
|
||
className="font-mono absolute"
|
||
style={{
|
||
fontSize: '9px',
|
||
color: annotationColor,
|
||
top: '-14px',
|
||
right: '2px',
|
||
whiteSpace: 'nowrap',
|
||
}}
|
||
>
|
||
20px
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<span
|
||
className="font-mono text-xs"
|
||
style={{ color: t.textSecondary }}
|
||
>
|
||
콘텐츠에 맞게 너비가 자동으로 조정됩니다.
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Fixed */}
|
||
<div className="flex flex-col gap-3">
|
||
<span
|
||
className="font-mono text-sm font-bold"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
Fixed
|
||
</span>
|
||
<div className="flex items-center gap-4">
|
||
<div className="relative">
|
||
<button
|
||
type="button"
|
||
className="rounded-md font-semibold"
|
||
style={{
|
||
height: '44px',
|
||
width: '160px',
|
||
backgroundColor: buttonDarkBg,
|
||
color: buttonDarkText,
|
||
fontSize: '14px',
|
||
border: 'none',
|
||
cursor: 'default',
|
||
}}
|
||
>
|
||
레이블
|
||
</button>
|
||
{/* 고정 너비 표시 */}
|
||
<div
|
||
className="absolute"
|
||
style={{
|
||
bottom: '-18px',
|
||
left: '0',
|
||
right: '0',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: '4px',
|
||
}}
|
||
>
|
||
<div style={{ height: '1px', flex: 1, backgroundColor: annotationColor }} />
|
||
<span
|
||
className="font-mono"
|
||
style={{ fontSize: '9px', color: annotationColor, whiteSpace: 'nowrap' }}
|
||
>
|
||
Fixed Width
|
||
</span>
|
||
<div style={{ height: '1px', flex: 1, backgroundColor: annotationColor }} />
|
||
</div>
|
||
</div>
|
||
<span
|
||
className="font-mono text-xs ml-4"
|
||
style={{ color: t.textSecondary }}
|
||
>
|
||
너비가 고정된 버튼입니다.
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 3-3. Label */}
|
||
<div className="mb-12">
|
||
<h3
|
||
className="text-xl font-semibold mb-6"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
3. Label
|
||
</h3>
|
||
<div
|
||
className="rounded-xl p-8"
|
||
style={{ backgroundColor: sectionCardBg }}
|
||
>
|
||
<div className="flex flex-col gap-8">
|
||
{[
|
||
{ 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) => (
|
||
<div key={item.resolution} className="flex flex-col gap-3">
|
||
<span
|
||
className="font-mono text-sm"
|
||
style={{ color: t.textSecondary }}
|
||
>
|
||
{item.resolution}
|
||
</span>
|
||
<div className="flex items-center gap-6">
|
||
<div
|
||
className="relative"
|
||
style={{ width: item.width, maxWidth: item.maxWidth }}
|
||
>
|
||
<button
|
||
type="button"
|
||
className="w-full rounded-md font-semibold"
|
||
style={{
|
||
height: '44px',
|
||
paddingLeft: `${item.padding}px`,
|
||
paddingRight: `${item.padding}px`,
|
||
backgroundColor: buttonDarkBg,
|
||
color: buttonDarkText,
|
||
fontSize: '14px',
|
||
border: 'none',
|
||
cursor: 'default',
|
||
}}
|
||
>
|
||
레이블
|
||
</button>
|
||
{/* 패딩 주석 */}
|
||
<span
|
||
className="absolute font-mono"
|
||
style={{
|
||
fontSize: '9px',
|
||
color: annotationColor,
|
||
top: '-16px',
|
||
left: '0',
|
||
}}
|
||
>
|
||
padding {item.padding}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── 섹션 4: Style (변형 × 상태 매트릭스) ── */}
|
||
<div
|
||
className="pt-12 border-t border-solid"
|
||
style={{ borderColor: dividerColor }}
|
||
>
|
||
<h2
|
||
className="text-2xl font-bold mb-8"
|
||
style={{ color: t.textPrimary }}
|
||
>
|
||
Style
|
||
</h2>
|
||
|
||
<div
|
||
className="rounded-xl p-8 overflow-x-auto"
|
||
style={{ backgroundColor: sectionCardBg }}
|
||
>
|
||
<table style={{ borderCollapse: 'collapse', minWidth: '700px' }}>
|
||
{/* 열 헤더 */}
|
||
<thead>
|
||
<tr>
|
||
{/* 빈 셀 (상태 열) */}
|
||
<th style={{ width: '100px', padding: '8px 12px' }} />
|
||
{VARIANTS.map((variant) => (
|
||
<th
|
||
key={variant}
|
||
className="font-mono text-xs font-semibold text-center pb-4"
|
||
style={{
|
||
color: t.textSecondary,
|
||
padding: '8px 12px',
|
||
whiteSpace: 'nowrap',
|
||
}}
|
||
>
|
||
{variant}
|
||
</th>
|
||
))}
|
||
</tr>
|
||
</thead>
|
||
|
||
<tbody>
|
||
{stateRows.map((row, rowIdx) => (
|
||
<tr key={row.state}>
|
||
{/* 상태 라벨 */}
|
||
<td
|
||
className="font-mono text-xs font-medium"
|
||
style={{
|
||
color: t.textSecondary,
|
||
padding: rowIdx === 0 ? '8px 12px 8px 0' : '8px 12px 8px 0',
|
||
verticalAlign: 'middle',
|
||
whiteSpace: 'nowrap',
|
||
}}
|
||
>
|
||
{row.state}
|
||
</td>
|
||
|
||
{/* 각 변형별 버튼 셀 */}
|
||
{VARIANTS.map((_, vIdx) => {
|
||
const style = getVariantStyle(row, vIdx);
|
||
return (
|
||
<td
|
||
key={vIdx}
|
||
style={{ padding: '8px 12px', verticalAlign: 'middle', textAlign: 'center' }}
|
||
>
|
||
<button
|
||
type="button"
|
||
className="rounded-md font-semibold"
|
||
style={{
|
||
width: '96px',
|
||
height: '40px',
|
||
backgroundColor: style.bg,
|
||
color: style.text,
|
||
border: style.border ? `1.5px solid ${style.border}` : 'none',
|
||
fontSize: '12px',
|
||
cursor: 'default',
|
||
display: 'inline-flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
}}
|
||
>
|
||
레이블
|
||
</button>
|
||
</td>
|
||
);
|
||
})}
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default ButtonContent;
|