feat: 표에 고른 줄·강조 칸 추가하고 list 표 예시 등록
시안 tbl-list(699:6304)를 카탈로그에 넣으려 보니 두 가지가 빠져 있었다. - FoxTableRow에 `selected` — 고른 줄의 바탕이 action/primary-hover, 글자가 font/theme/primary가 된다. `aria-selected`도 함께 나간다. - FoxTableCell에 `emphasis` — 그 줄을 대표하는 칸. 시안 list 표의 제목 열이 heading/xsm(15px ExtraBold)를 쓴다. 고른 줄 규칙은 자식 결합자로 자기 칸만 고른다 — 후손 선택자를 쓰면 칸 안에 든 표까지 파랗게 물든다(글머리 목록에서 겪은 것과 같은 문제다). 예시는 시안 그대로 체크 열·정렬 머리·제목 강조·버튼 묶음·더보기·고른 줄을 담았고, 전체 선택 체크박스와 줄 선택이 실제로 동작한다. 정렬 아이콘과 더보기 아이콘은 이미 이식된 Phosphor 세트의 CaretUpDown·DotsThreeVertical을 그대로 쓴다. 검증: 고른 줄 바탕 #ecf2fe·글자 #0b50d0, 강조 칸 15px w800 lh 21px이 시안과 일치. sass · tsc · eslint 통과. Co-Authored-By: Claude Opus 5
@45d01e0e2ae42d58e867ff4efc7f9668e1ecde79
--- @fox/core/components/fox-table-cell/fox-table-cell.tsx
+++ @fox/core/components/fox-table-cell/fox-table-cell.tsx
... | ... | @@ -10,6 +10,11 @@ |
| 10 | 10 |
/** 참이면 `<th>`가 된다. 머리행 안에서는 주지 않아도 행이 채운다. */ |
| 11 | 11 |
head?: boolean; |
| 12 | 12 |
align?: FoxTableCellAlign; |
| 13 |
+ /** |
|
| 14 |
+ * 그 줄을 대표하는 칸. 글자가 굵고 커진다 — 시안 list 표의 제목 열이다. |
|
| 15 |
+ * 누를 수 있게 하려면 안에 링크나 버튼을 넣는다. |
|
| 16 |
+ */ |
|
| 17 |
+ emphasis?: boolean; |
|
| 13 | 18 |
colSpan?: number; |
| 14 | 19 |
rowSpan?: number; |
| 15 | 20 |
/** 열 너비. `<col>` 없이 한 칸만 잡을 때 쓴다. */ |
... | ... | @@ -47,6 +52,7 @@ |
| 47 | 52 |
children, |
| 48 | 53 |
head, |
| 49 | 54 |
align, |
| 55 |
+ emphasis = false, |
|
| 50 | 56 |
colSpan, |
| 51 | 57 |
rowSpan, |
| 52 | 58 |
width, |
... | ... | @@ -67,6 +73,7 @@ |
| 67 | 73 |
className={cx(
|
| 68 | 74 |
"fox-table-cell", |
| 69 | 75 |
isHead ? "fox-table-cell--head" : "fox-table-cell--body", |
| 76 |
+ emphasis && "fox-table-cell--emphasis", |
|
| 70 | 77 |
align && ALIGN_CLASS[align], |
| 71 | 78 |
className |
| 72 | 79 |
)} |
--- @fox/core/components/fox-table-row/fox-table-row.tsx
+++ @fox/core/components/fox-table-row/fox-table-row.tsx
... | ... | @@ -14,6 +14,8 @@ |
| 14 | 14 |
children?: ReactNode; |
| 15 | 15 |
/** 참이면 머리행이다. 안의 칸이 모두 `<th scope="col">`이 된다. */ |
| 16 | 16 |
head?: boolean; |
| 17 |
+ /** 참이면 고른 줄이 된다 — 바탕과 글자가 파랗게 바뀐다(시안 list 표). */ |
|
| 18 |
+ selected?: boolean; |
|
| 17 | 19 |
/** 참이면 렌더하지 않는다(DOM에 남지 않는다). */ |
| 18 | 20 |
hidden?: boolean; |
| 19 | 21 |
/** 배치 조정용. */ |
... | ... | @@ -46,13 +48,27 @@ |
| 46 | 48 |
* ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"` |
| 47 | 49 |
* (또는 개별 파티셜)로 한 번 불러와야 한다. |
| 48 | 50 |
*/ |
| 49 |
-export function FoxTableRow({ children, head = false, hidden = false, className }: FoxTableRowProps) {
|
|
| 51 |
+export function FoxTableRow({
|
|
| 52 |
+ children, |
|
| 53 |
+ head = false, |
|
| 54 |
+ selected = false, |
|
| 55 |
+ hidden = false, |
|
| 56 |
+ className, |
|
| 57 |
+}: FoxTableRowProps) {
|
|
| 50 | 58 |
if (hidden) {
|
| 51 | 59 |
return null; |
| 52 | 60 |
} |
| 53 | 61 |
|
| 54 | 62 |
return ( |
| 55 |
- <tr className={cx("fox-table-row", head && "fox-table-row--head", className)}>
|
|
| 63 |
+ <tr |
|
| 64 |
+ className={cx(
|
|
| 65 |
+ "fox-table-row", |
|
| 66 |
+ head && "fox-table-row--head", |
|
| 67 |
+ selected && "fox-table-row--selected", |
|
| 68 |
+ className |
|
| 69 |
+ )} |
|
| 70 |
+ aria-selected={selected || undefined}
|
|
| 71 |
+ > |
|
| 56 | 72 |
{head ? markCells(children) : children}
|
| 57 | 73 |
</tr> |
| 58 | 74 |
); |
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
... | ... | @@ -32,6 +32,8 @@ |
| 32 | 32 |
import { FoxToggleSwitch } from "../core/components/fox-toggle-switch";
|
| 33 | 33 |
import {
|
| 34 | 34 |
FoxCaretLeftIcon, |
| 35 |
+ FoxCaretUpDownIcon, |
|
| 36 |
+ FoxDotsThreeVerticalIcon, |
|
| 35 | 37 |
FoxCaretRightIcon, |
| 36 | 38 |
FoxPauseIcon, |
| 37 | 39 |
FoxPlayIcon, |
... | ... | @@ -470,6 +472,76 @@ |
| 470 | 472 |
</div> |
| 471 | 473 |
))} |
| 472 | 474 |
</div> |
| 475 |
+ ); |
|
| 476 |
+} |
|
| 477 |
+ |
|
| 478 |
+/** 시안 tbl-list(699:6304) 그대로 — 체크 열·정렬 머리·제목 강조·버튼·더보기·고른 줄. */ |
|
| 479 |
+function TableListSample() {
|
|
| 480 |
+ const [selected, setSelected] = useState<number[]>([2]); |
|
| 481 |
+ const rows = [1, 2, 3]; |
|
| 482 |
+ const allOn = selected.length === rows.length; |
|
| 483 |
+ |
|
| 484 |
+ const toggle = (n: number) => |
|
| 485 |
+ setSelected((prev) => (prev.includes(n) ? prev.filter((v) => v !== n) : [...prev, n])); |
|
| 486 |
+ |
|
| 487 |
+ return ( |
|
| 488 |
+ <FoxTable type="list" caption="목록 표 예시"> |
|
| 489 |
+ <FoxTableRow head> |
|
| 490 |
+ <FoxTableCell width={64}>
|
|
| 491 |
+ <FoxCheckbox |
|
| 492 |
+ size="md" |
|
| 493 |
+ className={styles.checkboxOnly}
|
|
| 494 |
+ label="전체 선택" |
|
| 495 |
+ checked={allOn}
|
|
| 496 |
+ indeterminate={selected.length > 0 && !allOn}
|
|
| 497 |
+ onChange={(on) => setSelected(on ? rows : [])}
|
|
| 498 |
+ /> |
|
| 499 |
+ </FoxTableCell> |
|
| 500 |
+ <FoxTableCell align="start"> |
|
| 501 |
+ <span className={styles.tableSortHead}>
|
|
| 502 |
+ 제목 |
|
| 503 |
+ <FoxCaretUpDownIcon /> |
|
| 504 |
+ </span> |
|
| 505 |
+ </FoxTableCell> |
|
| 506 |
+ <FoxTableCell width={96}>제목</FoxTableCell>
|
|
| 507 |
+ <FoxTableCell width={96}>제목</FoxTableCell>
|
|
| 508 |
+ <FoxTableCell width={160}>제목</FoxTableCell>
|
|
| 509 |
+ <FoxTableCell width={64} />
|
|
| 510 |
+ </FoxTableRow> |
|
| 511 |
+ |
|
| 512 |
+ {rows.map((n) => (
|
|
| 513 |
+ <FoxTableRow key={n} selected={selected.includes(n)}>
|
|
| 514 |
+ <FoxTableCell label="선택"> |
|
| 515 |
+ <FoxCheckbox |
|
| 516 |
+ size="md" |
|
| 517 |
+ className={styles.checkboxOnly}
|
|
| 518 |
+ label={`${n}번째 줄 선택`}
|
|
| 519 |
+ checked={selected.includes(n)}
|
|
| 520 |
+ onChange={() => toggle(n)}
|
|
| 521 |
+ /> |
|
| 522 |
+ </FoxTableCell> |
|
| 523 |
+ <FoxTableCell label="제목" align="start" emphasis> |
|
| 524 |
+ 내용 |
|
| 525 |
+ </FoxTableCell> |
|
| 526 |
+ <FoxTableCell label="제목">내용</FoxTableCell> |
|
| 527 |
+ <FoxTableCell label="제목">내용</FoxTableCell> |
|
| 528 |
+ <FoxTableCell label="제목"> |
|
| 529 |
+ <FoxButtonGroup size="sm"> |
|
| 530 |
+ <FoxButton size="sm" label="버튼" /> |
|
| 531 |
+ <FoxButton size="sm" label="버튼" /> |
|
| 532 |
+ </FoxButtonGroup> |
|
| 533 |
+ </FoxTableCell> |
|
| 534 |
+ <FoxTableCell label="더보기"> |
|
| 535 |
+ <FoxIconButton |
|
| 536 |
+ size="sm" |
|
| 537 |
+ type="ghost" |
|
| 538 |
+ label={`${n}번째 줄 더보기`}
|
|
| 539 |
+ icon={<FoxDotsThreeVerticalIcon />}
|
|
| 540 |
+ /> |
|
| 541 |
+ </FoxTableCell> |
|
| 542 |
+ </FoxTableRow> |
|
| 543 |
+ ))} |
|
| 544 |
+ </FoxTable> |
|
| 473 | 545 |
); |
| 474 | 546 |
} |
| 475 | 547 |
|
... | ... | @@ -2555,6 +2627,10 @@ |
| 2555 | 2627 |
"Figma 시안 9종(tbl-basic · tbl-list · tbl-form과 각각의 행·칸)을 FoxTable / FoxTableRow / FoxTableCell 셋으로 옮겼습니다. 진짜 <table>이라 스크린리더가 행·열을 읽어 주고 colSpan·rowSpan이 그대로 동작합니다. 머리행은 head만 주면 <thead>로 알아서 갈립니다. 창을 768px 아래로 좁히면 list는 카드로 접히고 form은 레이블이 입력 위로 올라갑니다.", |
| 2556 | 2628 |
variants: [ |
| 2557 | 2629 |
{
|
| 2630 |
+ label: "list 표 예시 — 시안 699:6304 그대로. 체크·정렬·버튼·고른 줄", |
|
| 2631 |
+ node: <TableListSample />, |
|
| 2632 |
+ }, |
|
| 2633 |
+ {
|
|
| 2558 | 2634 |
label: "basic — 제목 열이 있는 상세표", |
| 2559 | 2635 |
node: ( |
| 2560 | 2636 |
<FoxTable caption="basic 표 예시"> |
--- @fox/dev-test/dev-test.module.scss
+++ @fox/dev-test/dev-test.module.scss
... | ... | @@ -496,3 +496,27 @@ |
| 496 | 496 |
align-items: center; |
| 497 | 497 |
gap: fox.gap(3); |
| 498 | 498 |
} |
| 499 |
+ |
|
| 500 |
+// 정렬 가능한 머리칸 — 글자 옆에 위아래 화살표가 붙는다. |
|
| 501 |
+.tableSortHead {
|
|
| 502 |
+ display: inline-flex; |
|
| 503 |
+ align-items: center; |
|
| 504 |
+ gap: fox.gap(2); |
|
| 505 |
+ white-space: nowrap; |
|
| 506 |
+ |
|
| 507 |
+ > svg {
|
|
| 508 |
+ inline-size: fox.icon(3); |
|
| 509 |
+ block-size: fox.icon(3); |
|
| 510 |
+ color: fox.color(icon-neutral-default); |
|
| 511 |
+ } |
|
| 512 |
+} |
|
| 513 |
+ |
|
| 514 |
+// 표의 체크 열처럼 상자만 필요한 자리. FoxCheckbox에 라벨을 감추는 프롭이 없어 |
|
| 515 |
+// 카탈로그에서만 이렇게 가린다 — 이름은 남으므로 스크린리더는 그대로 읽는다. |
|
| 516 |
+.checkboxOnly :global(.fox-checkbox__body) {
|
|
| 517 |
+ position: absolute; |
|
| 518 |
+ inline-size: fox.border(1); |
|
| 519 |
+ block-size: fox.border(1); |
|
| 520 |
+ overflow: hidden; |
|
| 521 |
+ clip-path: inset(50%); |
|
| 522 |
+} |
--- @fox/styles/_fox-table-cell.scss
+++ @fox/styles/_fox-table-cell.scss
... | ... | @@ -46,6 +46,14 @@ |
| 46 | 46 |
background: fox.color(surface-neutral-default); |
| 47 | 47 |
} |
| 48 | 48 |
|
| 49 |
+ // 그 줄을 대표하는 칸. 시안 list 표의 제목 열이 이렇다. |
|
| 50 |
+ &--emphasis {
|
|
| 51 |
+ font-family: fox.font-family(title); |
|
| 52 |
+ font-size: fox.font-size(heading-xsm); |
|
| 53 |
+ font-weight: fox.font-weight(bold); |
|
| 54 |
+ line-height: 1.4; |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 49 | 57 |
// 칸이 직접 정한 정렬은 표 기본값을 이긴다. |
| 50 | 58 |
&--start { text-align: start; }
|
| 51 | 59 |
&--center { text-align: center; }
|
--- @fox/styles/_fox-table-row.scss
+++ @fox/styles/_fox-table-row.scss
... | ... | @@ -11,4 +11,11 @@ |
| 11 | 11 |
.fox-table-row {
|
| 12 | 12 |
// 마지막 줄도 아래 선을 갖는다(시안). 칸이 그리므로 여기서는 둘 것이 없다. |
| 13 | 13 |
border: none; |
| 14 |
+ |
|
| 15 |
+ // 고른 줄. 칸이 바탕과 글자를 그리므로 여기서 자식만 골라 덮는다 — 후손 선택자를 쓰면 |
|
| 16 |
+ // 칸 안에 든 표까지 파랗게 물든다. |
|
| 17 |
+ &--selected > .fox-table-cell {
|
|
| 18 |
+ background: fox.color(action-primary-hover); |
|
| 19 |
+ color: fox.color(font-theme-primary); |
|
| 20 |
+ } |
|
| 14 | 21 |
} |
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?