임동욱 임동욱 08-18
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
+++ @fox/core/components/fox-table-cell/fox-table-cell.tsx
@@ -10,6 +10,11 @@
   /** 참이면 `<th>`가 된다. 머리행 안에서는 주지 않아도 행이 채운다. */
   head?: boolean;
   align?: FoxTableCellAlign;
+  /**
+   * 그 줄을 대표하는 칸. 글자가 굵고 커진다 — 시안 list 표의 제목 열이다.
+   * 누를 수 있게 하려면 안에 링크나 버튼을 넣는다.
+   */
+  emphasis?: boolean;
   colSpan?: number;
   rowSpan?: number;
   /** 열 너비. `<col>` 없이 한 칸만 잡을 때 쓴다. */
@@ -47,6 +52,7 @@
   children,
   head,
   align,
+  emphasis = false,
   colSpan,
   rowSpan,
   width,
@@ -67,6 +73,7 @@
       className={cx(
         "fox-table-cell",
         isHead ? "fox-table-cell--head" : "fox-table-cell--body",
+        emphasis && "fox-table-cell--emphasis",
         align && ALIGN_CLASS[align],
         className
       )}
@fox/core/components/fox-table-row/fox-table-row.tsx
--- @fox/core/components/fox-table-row/fox-table-row.tsx
+++ @fox/core/components/fox-table-row/fox-table-row.tsx
@@ -14,6 +14,8 @@
   children?: ReactNode;
   /** 참이면 머리행이다. 안의 칸이 모두 `<th scope="col">`이 된다. */
   head?: boolean;
+  /** 참이면 고른 줄이 된다 — 바탕과 글자가 파랗게 바뀐다(시안 list 표). */
+  selected?: boolean;
   /** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
   hidden?: boolean;
   /** 배치 조정용. */
@@ -46,13 +48,27 @@
  * ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
  * (또는 개별 파티셜)로 한 번 불러와야 한다.
  */
-export function FoxTableRow({ children, head = false, hidden = false, className }: FoxTableRowProps) {
+export function FoxTableRow({
+  children,
+  head = false,
+  selected = false,
+  hidden = false,
+  className,
+}: FoxTableRowProps) {
   if (hidden) {
     return null;
   }
 
   return (
-    <tr className={cx("fox-table-row", head && "fox-table-row--head", className)}>
+    <tr
+      className={cx(
+        "fox-table-row",
+        head && "fox-table-row--head",
+        selected && "fox-table-row--selected",
+        className
+      )}
+      aria-selected={selected || undefined}
+    >
       {head ? markCells(children) : children}
     </tr>
   );
@fox/dev-test/component-registry.tsx
--- @fox/dev-test/component-registry.tsx
+++ @fox/dev-test/component-registry.tsx
@@ -32,6 +32,8 @@
 import { FoxToggleSwitch } from "../core/components/fox-toggle-switch";
 import {
   FoxCaretLeftIcon,
+  FoxCaretUpDownIcon,
+  FoxDotsThreeVerticalIcon,
   FoxCaretRightIcon,
   FoxPauseIcon,
   FoxPlayIcon,
@@ -470,6 +472,76 @@
         </div>
       ))}
     </div>
+  );
+}
+
+/** 시안 tbl-list(699:6304) 그대로 — 체크 열·정렬 머리·제목 강조·버튼·더보기·고른 줄. */
+function TableListSample() {
+  const [selected, setSelected] = useState<number[]>([2]);
+  const rows = [1, 2, 3];
+  const allOn = selected.length === rows.length;
+
+  const toggle = (n: number) =>
+    setSelected((prev) => (prev.includes(n) ? prev.filter((v) => v !== n) : [...prev, n]));
+
+  return (
+    <FoxTable type="list" caption="목록 표 예시">
+      <FoxTableRow head>
+        <FoxTableCell width={64}>
+          <FoxCheckbox
+            size="md"
+            className={styles.checkboxOnly}
+            label="전체 선택"
+            checked={allOn}
+            indeterminate={selected.length > 0 && !allOn}
+            onChange={(on) => setSelected(on ? rows : [])}
+          />
+        </FoxTableCell>
+        <FoxTableCell align="start">
+          <span className={styles.tableSortHead}>
+            제목
+            <FoxCaretUpDownIcon />
+          </span>
+        </FoxTableCell>
+        <FoxTableCell width={96}>제목</FoxTableCell>
+        <FoxTableCell width={96}>제목</FoxTableCell>
+        <FoxTableCell width={160}>제목</FoxTableCell>
+        <FoxTableCell width={64} />
+      </FoxTableRow>
+
+      {rows.map((n) => (
+        <FoxTableRow key={n} selected={selected.includes(n)}>
+          <FoxTableCell label="선택">
+            <FoxCheckbox
+              size="md"
+              className={styles.checkboxOnly}
+              label={`${n}번째 줄 선택`}
+              checked={selected.includes(n)}
+              onChange={() => toggle(n)}
+            />
+          </FoxTableCell>
+          <FoxTableCell label="제목" align="start" emphasis>
+            내용
+          </FoxTableCell>
+          <FoxTableCell label="제목">내용</FoxTableCell>
+          <FoxTableCell label="제목">내용</FoxTableCell>
+          <FoxTableCell label="제목">
+            <FoxButtonGroup size="sm">
+              <FoxButton size="sm" label="버튼" />
+              <FoxButton size="sm" label="버튼" />
+            </FoxButtonGroup>
+          </FoxTableCell>
+          <FoxTableCell label="더보기">
+            <FoxIconButton
+              size="sm"
+              type="ghost"
+              label={`${n}번째 줄 더보기`}
+              icon={<FoxDotsThreeVerticalIcon />}
+            />
+          </FoxTableCell>
+        </FoxTableRow>
+      ))}
+    </FoxTable>
   );
 }
 
@@ -2555,6 +2627,10 @@
       "Figma 시안 9종(tbl-basic · tbl-list · tbl-form과 각각의 행·칸)을 FoxTable / FoxTableRow / FoxTableCell 셋으로 옮겼습니다. 진짜 <table>이라 스크린리더가 행·열을 읽어 주고 colSpan·rowSpan이 그대로 동작합니다. 머리행은 head만 주면 <thead>로 알아서 갈립니다. 창을 768px 아래로 좁히면 list는 카드로 접히고 form은 레이블이 입력 위로 올라갑니다.",
     variants: [
       {
+        label: "list 표 예시 — 시안 699:6304 그대로. 체크·정렬·버튼·고른 줄",
+        node: <TableListSample />,
+      },
+      {
         label: "basic — 제목 열이 있는 상세표",
         node: (
           <FoxTable caption="basic 표 예시">
@fox/dev-test/dev-test.module.scss
--- @fox/dev-test/dev-test.module.scss
+++ @fox/dev-test/dev-test.module.scss
@@ -496,3 +496,27 @@
   align-items: center;
   gap: fox.gap(3);
 }
+
+// 정렬 가능한 머리칸 — 글자 옆에 위아래 화살표가 붙는다.
+.tableSortHead {
+  display: inline-flex;
+  align-items: center;
+  gap: fox.gap(2);
+  white-space: nowrap;
+
+  > svg {
+    inline-size: fox.icon(3);
+    block-size: fox.icon(3);
+    color: fox.color(icon-neutral-default);
+  }
+}
+
+// 표의 체크 열처럼 상자만 필요한 자리. FoxCheckbox에 라벨을 감추는 프롭이 없어
+// 카탈로그에서만 이렇게 가린다 — 이름은 남으므로 스크린리더는 그대로 읽는다.
+.checkboxOnly :global(.fox-checkbox__body) {
+  position: absolute;
+  inline-size: fox.border(1);
+  block-size: fox.border(1);
+  overflow: hidden;
+  clip-path: inset(50%);
+}
@fox/styles/_fox-table-cell.scss
--- @fox/styles/_fox-table-cell.scss
+++ @fox/styles/_fox-table-cell.scss
@@ -46,6 +46,14 @@
     background: fox.color(surface-neutral-default);
   }
 
+  // 그 줄을 대표하는 칸. 시안 list 표의 제목 열이 이렇다.
+  &--emphasis {
+    font-family: fox.font-family(title);
+    font-size: fox.font-size(heading-xsm);
+    font-weight: fox.font-weight(bold);
+    line-height: 1.4;
+  }
+
   // 칸이 직접 정한 정렬은 표 기본값을 이긴다.
   &--start { text-align: start; }
   &--center { text-align: center; }
@fox/styles/_fox-table-row.scss
--- @fox/styles/_fox-table-row.scss
+++ @fox/styles/_fox-table-row.scss
@@ -11,4 +11,11 @@
 .fox-table-row {
   // 마지막 줄도 아래 선을 갖는다(시안). 칸이 그리므로 여기서는 둘 것이 없다.
   border: none;
+
+  // 고른 줄. 칸이 바탕과 글자를 그리므로 여기서 자식만 골라 덮는다 — 후손 선택자를 쓰면
+  // 칸 안에 든 표까지 파랗게 물든다.
+  &--selected > .fox-table-cell {
+    background: fox.color(action-primary-hover);
+    color: fox.color(font-theme-primary);
+  }
 }
Add a comment
List