feat: 표 열 정렬을 디자인팀 규칙(기본 가운데, 제목·긴 글만 왼쪽)으로 맞춘다
통용 관례 적용을 되돌리고(64b5dee) 디자인팀 회신대로 한다 — 목록의 기본은 시안대로 가운데, 제목 열(가맹점명·업체명·메뉴명·제목·콘텐츠명·질문·금지 키워드·코드명·학생명·이벤트/구분)과 긴 글 열(주소·설명·코드설명·질문내용·키워드·이메일)만 align=start로 왼쪽에 둔다. 머리칸은 본문 열을 따른다. Co-Authored-By: Claude Opus 5
@a53fd1f5fd3bb1f5ef8f0b78162bfe442ad947cf
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
... | ... | @@ -3069,7 +3069,7 @@ |
| 3069 | 3069 |
id: "fox-table", |
| 3070 | 3070 |
name: "FoxTable", |
| 3071 | 3071 |
description: |
| 3072 |
- "Figma 시안 9종(tbl-basic · tbl-list · tbl-form과 각각의 행·칸)을 FoxTable / FoxTableRow / FoxTableCell 셋으로 옮겼습니다. 진짜 <table>이라 스크린리더가 행·열을 읽어 주고 colSpan·rowSpan이 그대로 동작합니다. 머리행은 head만 주면 <thead>로 알아서 갈립니다. 창을 768px 아래로 좁히면 list는 카드로 접히고 form은 레이블이 입력 위로 올라갑니다.", |
|
| 3072 |
+ "Figma 시안 9종(tbl-basic · tbl-list · tbl-form과 각각의 행·칸)을 FoxTable / FoxTableRow / FoxTableCell 셋으로 옮겼습니다. 진짜 <table>이라 스크린리더가 행·열을 읽어 주고 colSpan·rowSpan이 그대로 동작합니다. 머리행은 head만 주면 <thead>로 알아서 갈립니다. 창을 768px 아래로 좁히면 list는 카드로 접히고 form은 레이블이 입력 위로 올라갑니다. 열 정렬은 디자인팀 규칙대로 가운데가 기본이고, 제목 열과 긴 글 열(주소·설명·내용)만 align=\"start\"로 왼쪽입니다.", |
|
| 3073 | 3073 |
variants: [ |
| 3074 | 3074 |
{
|
| 3075 | 3075 |
label: "list 표 예시 — 시안 699:6304 그대로. 체크·정렬·버튼·고른 줄", |
--- @fox/styles/_fox-table.scss
+++ @fox/styles/_fox-table.scss
... | ... | @@ -27,6 +27,10 @@ |
| 27 | 27 |
// 스크린리더가 행·열을 읽지 못한다. 그래서 `list`의 칸은 `label`을 함께 받아 카드에서 |
| 28 | 28 |
// 열 이름을 글자로 보여 준다. |
| 29 | 29 |
// |
| 30 |
+// 열 정렬 규약(디자인팀 확정, 2026-09-22) — 목록·기본 표는 **가운데가 기본**이고, **제목 열 |
|
| 31 |
+// (emphasis)과 긴 글 열(주소·설명·내용·키워드·이메일)만 `align="start"`** 로 왼쪽에 둔다. |
|
| 32 |
+// 머리칸은 본문 열과 같은 정렬이다(FoxListContainer가 같은 align을 머리칸에도 준다). |
|
| 33 |
+// |
|
| 30 | 34 |
// 토큰이 아닌 값: |
| 31 | 35 |
// - form 머리칸의 최소 크기 12rem×4.8rem: 시안 값이나 Figma가 변수로 export하지 |
| 32 | 36 |
// 않는다(사용자 승인). |
--- app/(protected)/(basic)/admins/_components/admin-member-list.tsx
+++ app/(protected)/(basic)/admins/_components/admin-member-list.tsx
... | ... | @@ -82,6 +82,7 @@ |
| 82 | 82 |
{
|
| 83 | 83 |
key: 'email', |
| 84 | 84 |
header: '이메일', |
| 85 |
+ align: 'start', |
|
| 85 | 86 |
width: FILL_COLUMN_WIDTH, |
| 86 | 87 |
render: (row) => formatOptionalValue(row.email), |
| 87 | 88 |
}, |
--- app/(protected)/(basic)/boards/_components/board-post-columns.tsx
+++ app/(protected)/(basic)/boards/_components/board-post-columns.tsx
... | ... | @@ -82,7 +82,7 @@ |
| 82 | 82 |
return [ |
| 83 | 83 |
numberColumn(page, pageSize), |
| 84 | 84 |
...(IS_BOARD_CATEGORY_ENABLED ? [categoryColumn()] : []), |
| 85 |
- { key: 'title', header: '제목', width: 320, emphasis: true },
|
|
| 85 |
+ { key: 'title', header: '제목', width: 320, align: 'start', emphasis: true },
|
|
| 86 | 86 |
{
|
| 87 | 87 |
key: 'attachment', |
| 88 | 88 |
header: '첨부', |
... | ... | @@ -134,7 +134,7 @@ |
| 134 | 134 |
// 서버가 조인해 준 이름을 그대로 쓴다. 코드표 조회는 이름이 안 온 행의 보루다. |
| 135 | 135 |
render: (row) => row.typeName ?? formatFaqTypeLabel(row.typeCode, typeOptions), |
| 136 | 136 |
}, |
| 137 |
- { key: 'title', header: '제목', width: 320, emphasis: true },
|
|
| 137 |
+ { key: 'title', header: '제목', width: 320, align: 'start', emphasis: true },
|
|
| 138 | 138 |
{
|
| 139 | 139 |
key: 'authorName', |
| 140 | 140 |
header: '작성자', |
... | ... | @@ -198,6 +198,7 @@ |
| 198 | 198 |
{
|
| 199 | 199 |
key: 'content', |
| 200 | 200 |
header: '질문내용', |
| 201 |
+ align: 'start', |
|
| 201 | 202 |
width: 280, |
| 202 | 203 |
emphasis: true, |
| 203 | 204 |
render: (row) => formatContentExcerpt(row.content), |
--- app/(protected)/(basic)/contents/comics/_components/comics-list.tsx
+++ app/(protected)/(basic)/contents/comics/_components/comics-list.tsx
... | ... | @@ -58,6 +58,7 @@ |
| 58 | 58 |
{
|
| 59 | 59 |
key: 'name', |
| 60 | 60 |
header: '콘텐츠명', |
| 61 |
+ align: 'start', |
|
| 61 | 62 |
width: 320, |
| 62 | 63 |
emphasis: true, |
| 63 | 64 |
render: (row) => formatContentText(row.name || row.title), |
... | ... | @@ -65,6 +66,7 @@ |
| 65 | 66 |
{
|
| 66 | 67 |
key: 'keywords', |
| 67 | 68 |
header: '키워드', |
| 69 |
+ align: 'start', |
|
| 68 | 70 |
width: 200, |
| 69 | 71 |
render: (row) => formatKeywordsLabel(row.keywords), |
| 70 | 72 |
}, |
--- app/(protected)/(basic)/contents/quizzes/_components/quizzes-list.tsx
+++ app/(protected)/(basic)/contents/quizzes/_components/quizzes-list.tsx
... | ... | @@ -59,12 +59,14 @@ |
| 59 | 59 |
{
|
| 60 | 60 |
key: 'keywords', |
| 61 | 61 |
header: '키워드', |
| 62 |
+ align: 'start', |
|
| 62 | 63 |
width: 200, |
| 63 | 64 |
render: (row) => formatContentText(formatKeywordsLabel(row.keywords)), |
| 64 | 65 |
}, |
| 65 | 66 |
{
|
| 66 | 67 |
key: 'question', |
| 67 | 68 |
header: '질문', |
| 69 |
+ align: 'start', |
|
| 68 | 70 |
width: 360, |
| 69 | 71 |
emphasis: true, |
| 70 | 72 |
render: (row) => formatContentText(row.question), |
--- app/(protected)/(basic)/contents/shorts/_components/shorts-list.tsx
+++ app/(protected)/(basic)/contents/shorts/_components/shorts-list.tsx
... | ... | @@ -58,6 +58,7 @@ |
| 58 | 58 |
{
|
| 59 | 59 |
key: 'title', |
| 60 | 60 |
header: '제목', |
| 61 |
+ align: 'start', |
|
| 61 | 62 |
width: 320, |
| 62 | 63 |
emphasis: true, |
| 63 | 64 |
render: (row) => formatContentText(row.title || row.name), |
... | ... | @@ -65,6 +66,7 @@ |
| 65 | 66 |
{
|
| 66 | 67 |
key: 'keywords', |
| 67 | 68 |
header: '키워드', |
| 69 |
+ align: 'start', |
|
| 68 | 70 |
width: 200, |
| 69 | 71 |
render: (row) => formatKeywordsLabel(row.keywords), |
| 70 | 72 |
}, |
--- app/(protected)/(basic)/points/accumulated/_components/point-account-list.tsx
+++ app/(protected)/(basic)/points/accumulated/_components/point-account-list.tsx
... | ... | @@ -81,7 +81,7 @@ |
| 81 | 81 |
render: (_row, index) => (currentPage - 1) * query.pageSize + index + 1, |
| 82 | 82 |
}, |
| 83 | 83 |
{ key: 'loginId', header: '아이디', width: 140 },
|
| 84 |
- { key: 'studentName', header: '학생명', width: 120, emphasis: true },
|
|
| 84 |
+ { key: 'studentName', header: '학생명', align: 'start', width: 120, emphasis: true },
|
|
| 85 | 85 |
{ key: 'schoolName', header: '학교명', width: 160 },
|
| 86 | 86 |
{ key: 'grade', header: '학년', width: 80, align: 'center' },
|
| 87 | 87 |
{ key: 'classNo', header: '반', width: 80, align: 'center' },
|
--- app/(protected)/(basic)/points/standards/_components/point-standard-list.tsx
+++ app/(protected)/(basic)/points/standards/_components/point-standard-list.tsx
... | ... | @@ -65,6 +65,7 @@ |
| 65 | 65 |
{
|
| 66 | 66 |
key: 'eventCode', |
| 67 | 67 |
header: '이벤트/구분', |
| 68 |
+ align: 'start', |
|
| 68 | 69 |
width: 140, |
| 69 | 70 |
emphasis: true, |
| 70 | 71 |
// 표에 없는 코드는 지어내지 않고 코드 그대로 보여준다(formatCommonCode 규칙). |
... | ... | @@ -95,7 +96,7 @@ |
| 95 | 96 |
/> |
| 96 | 97 |
), |
| 97 | 98 |
}, |
| 98 |
- { key: 'description', header: '설명', width: 320 },
|
|
| 99 |
+ { key: 'description', header: '설명', align: 'start', width: 320 },
|
|
| 99 | 100 |
{
|
| 100 | 101 |
key: 'isActive', |
| 101 | 102 |
header: '사용여부', |
--- app/(protected)/(basic)/students/_components/student-list.tsx
+++ app/(protected)/(basic)/students/_components/student-list.tsx
... | ... | @@ -95,7 +95,7 @@ |
| 95 | 95 |
width: 160, |
| 96 | 96 |
render: (row) => text(formatPhoneNumber(row.phoneNumber)), |
| 97 | 97 |
}, |
| 98 |
- { key: 'email', header: '이메일', width: 200, render: (row) => text(row.email) },
|
|
| 98 |
+ { key: 'email', header: '이메일', align: 'start', width: 200, render: (row) => text(row.email) },
|
|
| 99 | 99 |
{
|
| 100 | 100 |
key: 'role', |
| 101 | 101 |
header: '역할', |
--- app/(protected)/(basic)/system/banned-words/_components/banned-word-list.tsx
+++ app/(protected)/(basic)/system/banned-words/_components/banned-word-list.tsx
... | ... | @@ -85,7 +85,7 @@ |
| 85 | 85 |
width: 100, |
| 86 | 86 |
render: (_row, index) => (currentPage - 1) * query.pageSize + index + 1, |
| 87 | 87 |
}, |
| 88 |
- { key: 'word', header: '금지 키워드', width: 400, emphasis: true },
|
|
| 88 |
+ { key: 'word', header: '금지 키워드', align: 'start', width: 400, emphasis: true },
|
|
| 89 | 89 |
{
|
| 90 | 90 |
key: 'createdAt', |
| 91 | 91 |
header: '등록일', |
--- app/(protected)/(basic)/system/codes/_components/code-management.tsx
+++ app/(protected)/(basic)/system/codes/_components/code-management.tsx
... | ... | @@ -146,10 +146,11 @@ |
| 146 | 146 |
render: (_row, index) => (groupPage - 1) * COMMON_CODE_PAGE_SIZE + index + 1, |
| 147 | 147 |
}, |
| 148 | 148 |
{ key: 'comCd', header: '코드ID', width: 120 },
|
| 149 |
- { key: 'name', header: '코드명', width: 160, emphasis: true },
|
|
| 149 |
+ { key: 'name', header: '코드명', align: 'start', width: 160, emphasis: true },
|
|
| 150 | 150 |
{
|
| 151 | 151 |
key: 'description', |
| 152 | 152 |
header: '코드설명', |
| 153 |
+ align: 'start', |
|
| 153 | 154 |
width: 156, |
| 154 | 155 |
render: (row) => <Truncated text={row.description} />,
|
| 155 | 156 |
}, |
... | ... | @@ -189,10 +190,11 @@ |
| 189 | 190 |
render: (row) => row.sortSeq, |
| 190 | 191 |
}, |
| 191 | 192 |
{ key: 'comDtlCd', header: '상세코드ID', width: 120 },
|
| 192 |
- { key: 'name', header: '상세코드명', width: 120, emphasis: true },
|
|
| 193 |
+ { key: 'name', header: '상세코드명', align: 'start', width: 120, emphasis: true },
|
|
| 193 | 194 |
{
|
| 194 | 195 |
key: 'description', |
| 195 | 196 |
header: '상세코드설명', |
| 197 |
+ align: 'start', |
|
| 196 | 198 |
width: 196, |
| 197 | 199 |
render: (row) => <Truncated text={row.description} />,
|
| 198 | 200 |
}, |
--- app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-list.tsx
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-detail-list.tsx
... | ... | @@ -201,7 +201,7 @@ |
| 201 | 201 |
<MerchantThumbnail src={row.thumbnailUrl} alt={`${row.name} 대표이미지`} />
|
| 202 | 202 |
), |
| 203 | 203 |
}, |
| 204 |
- { key: 'name', header: '업체명', width: 220, emphasis: true },
|
|
| 204 |
+ { key: 'name', header: '업체명', align: 'start', width: 220, emphasis: true },
|
|
| 205 | 205 |
{
|
| 206 | 206 |
key: 'category', |
| 207 | 207 |
header: '카테고리', |
... | ... | @@ -219,6 +219,7 @@ |
| 219 | 219 |
{
|
| 220 | 220 |
key: 'address', |
| 221 | 221 |
header: '주소', |
| 222 |
+ align: 'start', |
|
| 222 | 223 |
width: 280, |
| 223 | 224 |
render: (row) => formatOptionalValue(row.address), |
| 224 | 225 |
}, |
--- app/(protected)/(basic)/system/merchant-details/_components/merchant-product-table.tsx
+++ app/(protected)/(basic)/system/merchant-details/_components/merchant-product-table.tsx
... | ... | @@ -59,8 +59,8 @@ |
| 59 | 59 |
<FoxTable type="list" caption="메뉴 목록"> |
| 60 | 60 |
<FoxTableRow head> |
| 61 | 61 |
<FoxTableCell head width={140} align="center">이미지</FoxTableCell>
|
| 62 |
- <FoxTableCell head width={240}>메뉴명</FoxTableCell>
|
|
| 63 |
- <FoxTableCell head>설명</FoxTableCell> |
|
| 62 |
+ <FoxTableCell head width={240} align="start">메뉴명</FoxTableCell>
|
|
| 63 |
+ <FoxTableCell head align="start">설명</FoxTableCell> |
|
| 64 | 64 |
<FoxTableCell head width={140} align="center">가격</FoxTableCell>
|
| 65 | 65 |
<FoxTableCell head width={180} align="center">배지</FoxTableCell>
|
| 66 | 66 |
<FoxTableCell head width={96} align="center">노출</FoxTableCell>
|
... | ... | @@ -79,8 +79,8 @@ |
| 79 | 79 |
<FoxTableCell align="center" label="이미지"> |
| 80 | 80 |
<MerchantThumbnail src={product.imageUrl} alt={`${product.name} 이미지`} />
|
| 81 | 81 |
</FoxTableCell> |
| 82 |
- <FoxTableCell emphasis label="메뉴명">{product.name}</FoxTableCell>
|
|
| 83 |
- <FoxTableCell label="설명"> |
|
| 82 |
+ <FoxTableCell align="start" emphasis label="메뉴명">{product.name}</FoxTableCell>
|
|
| 83 |
+ <FoxTableCell align="start" label="설명"> |
|
| 84 | 84 |
<span className={styles.description}>
|
| 85 | 85 |
{product.description ?? EMPTY_FIELD_PLACEHOLDER}
|
| 86 | 86 |
</span> |
--- app/(protected)/(basic)/system/merchants/_components/merchant-list.tsx
+++ app/(protected)/(basic)/system/merchants/_components/merchant-list.tsx
... | ... | @@ -68,7 +68,7 @@ |
| 68 | 68 |
render: (_row, index) => (currentPage - 1) * query.pageSize + index + 1, |
| 69 | 69 |
}, |
| 70 | 70 |
{ key: 'code', header: '가맹점코드', width: 140 },
|
| 71 |
- { key: 'name', header: '가맹점명', width: 240, emphasis: true },
|
|
| 71 |
+ { key: 'name', header: '가맹점명', align: 'start', width: 240, emphasis: true },
|
|
| 72 | 72 |
{
|
| 73 | 73 |
key: 'categoryCode', |
| 74 | 74 |
header: '업종', |
... | ... | @@ -84,6 +84,7 @@ |
| 84 | 84 |
{
|
| 85 | 85 |
key: 'address', |
| 86 | 86 |
header: '주소', |
| 87 |
+ align: 'start', |
|
| 87 | 88 |
width: 280, |
| 88 | 89 |
render: formatAddressSummary, |
| 89 | 90 |
}, |
Add a 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?