# 拟态数据表（Data Table）

- 类型 / 材质：`table` / `neumorphic`（新拟态）
- 状态：done ｜ 最后提交：2026-11-01
- 灵感来源：GOV.UK table / USWDS usa-table
- 依据标准：`/design#pages` ｜ 组件页：`/showcase/component/table/neumorphic` ｜ 条目：`/r/table--neumorphic.json`
- 必缺状态：悬停 / 焦点
- 整页场景：尚未接入

## 规格

- 同一份 props（caption / columns / rows / className）
- 零分隔线：表头做成凹下去的带，行悬停 = 整行按下去
- 用 border-separate 而非 collapse，塌陷模式会裁掉 tr 的阴影
- 没有线也没有斑马纹，行数一多难横向追踪——只给行数少的清单用
- 对齐、滚动与只读边界同玻璃数据表

## 参数（CSS 变量）

`--color-text` `--color-text-muted` `--neuro-bg` `--neuro-dark` `--neuro-light`

## Props 契约

| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `caption` | `string` | 必填 | — |
| `columns` | `TableColumn[]` | 必填 | — |
| `rows` | `TableRow[]` | 必填 | — |
| `framed` | `boolean` | 可选 | false = 只留滚动容器，材质底交给外层面板（嵌在卡片里时不再套一层卡） |
| `className` | `string` | 可选 | — |

## 提示词（可直接粘给智能体）

```
复现「拟态数据表」— Data Table（表格 / Neumorphic）
灵感来源：GOV.UK table / USWDS usa-table
规格：
- 同一份 props（caption / columns / rows / className）
- 零分隔线：表头做成凹下去的带，行悬停 = 整行按下去
- 用 border-separate 而非 collapse，塌陷模式会裁掉 tr 的阴影
- 没有线也没有斑马纹，行数一多难横向追踪——只给行数少的清单用
- 对齐、滚动与只读边界同玻璃数据表
参数（CSS 变量）：--color-text --color-text-muted --neuro-bg --neuro-dark --neuro-light
必缺状态：悬停 / 焦点
整页场景：（尚未接进整页场景）
无障碍：焦点环 ≥2px 等效周长（SC 2.4.13）；指针目标 ≥24×24（SC 2.5.8）；动效带 prefers-reduced-motion 降级
参考：本站实样 /showcase/component/table/neumorphic · 标准 pages /design#pages · 调研笔记 /showcase/component/table/neumorphic#notes
```

## 调研笔记

import { type ReactNode } from 'react';

export interface TableColumn {
  key: string;
  header: string;
  /** end = 数字列：表头与数值一起右对齐（GOV / Polaris 同一条） */
  align?: 'start' | 'end';
  /** 列宽只写 Tailwind 宽度类，不写 px 字面量 */
  width?: string;
}

export interface TableRow {
  id: string;
  cells: Record<string, ReactNode>;
}

interface Props {
  caption: string;
  columns: TableColumn[];
  rows: TableRow[];
  /** false = 只留滚动容器，材质底交给外层面板（嵌在卡片里时不再套一层卡） */
  framed?: boolean;
  className?: string;
}

/** 拟态数据表：材质签名 = 同色挤出、零边框——分层靠凹陷与浮起，不靠线；悬停把整行按下去 */
export function NeumorphicTable({ caption, columns, rows, framed = true, className = '' }: Props) {
  const last = columns.length - 1;

  return (
    <div
      role="region"
      aria-label={caption}
      tabIndex={0}
      className={`
        overflow-x-auto outline-offset-2 rounded-2xl
        ${framed
          ? 'p-2 bg-[var(--neuro-bg)] shadow-[6px_6px_14px_var(--neuro-dark),-6px_-6px_14px_var(--neuro-light)]'
          : ''}
        ${className}
      `}
    >
      <table className="w-full min-w-[34rem] border-separate border-spacing-0 text-sm">
        <caption className="sr-only">{caption}</caption>
        <thead>
          <tr className="bg-[var(--neuro-bg)] shadow-[inset_2px_2px_5px_var(--neuro-dark),inset_-2px_-2px_5px_var(--neuro-light)]">
            {columns.map((c, i) => (
              <th
                key={c.key}
                scope="col"
                className={`
                  px-4 py-2.5 text-xs font-medium text-[var(--color-text-muted)] whitespace-nowrap
                  ${c.align === 'end' ? 'text-right' : 'text-left'}
                  ${i === 0 ? 'rounded-l-xl' : i === last ? 'rounded-r-xl' : ''}
                  ${c.width ?? ''}
                `}
              >
                {c.header}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.map((row) => (
            <tr
              key={row.id}
              className="transition-shadow hover:shadow-[inset_2px_2px_5px_var(--neuro-dark),inset_-2px_-2px_5px_var(--neuro-light)]"
            >
              {columns.map((c, i) =>
                i === 0 ? (
                  <th
                    key={c.key}
                    scope="row"
                    className="px-4 py-3 text-left font-medium text-[var(--color-text)] break-words rounded-l-xl"
                  >
                    {row.cells[c.key]}
                  </th>
                ) : (
                  <td
                    key={c.key}
                    className={`
                      px-4 py-3 text-[var(--color-text-muted)] break-words
                      ${i === last ? 'rounded-r-xl' : ''}
                      ${c.align === 'end' ? 'text-right tabular-nums whitespace-nowrap' : ''}
                    `}
                  >
                    {row.cells[c.key]}
                  </td>
                ),
              )}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
