NextJs整合shadcn/ui data-table与sheet组件遇布局异常求助
问题解决思路:表格行点击弹出Sheet导致布局异常
问题根源
HTML表格对DOM结构有严格要求:<tbody>的直接子元素必须是<tr>。你当前把<Sheet>作为<TableBody>的子元素,相当于在表格行外层套了额外容器节点,浏览器无法正确解析表格结构,最终导致所有单元格挤入第一列的布局异常。
解决方案
不要用SheetTrigger直接包裹<TableRow>,而是通过状态控制Sheet的显隐,将Sheet组件移出表格结构之外,避免破坏表格的DOM层级。
修改后的代码示例
"use client"; import { useState } from "react"; import { ColumnDef, flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetClose, } from "@/components/ui/sheet"; interface DataTableProps<TData, TValue> { columns: ColumnDef<TData, TValue>[]; data: TData[]; } export function DataTable<TData, TValue>({ columns, data, }: DataTableProps<TData, TValue>) { const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel(), }); // 维护当前打开Sheet的行ID const [openSheetId, setOpenSheetId] = useState<string | null>(null); // 根据ID获取当前行的数据 const currentRowData = data.find(row => row.id === openSheetId); return ( <div className="rounded-md border"> <Table> <TableHeader> {table.getHeaderGroups().map((headerGroup) => ( <TableRow key={headerGroup.id}> {headerGroup.headers.map((header) => { return ( <TableHead key={header.id}> {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} </TableHead> ); })} </TableRow> ))} </TableHeader> <TableBody> {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( <TableRow key={row.id} data-state={row.getIsSelected() && "selected"} // 点击行时设置要打开的Sheet ID onClick={() => setOpenSheetId(row.id)} // 添加鼠标悬停效果,提示可点击 className="cursor-pointer hover:bg-muted/50" > {row.getVisibleCells().map((cell) => ( <TableCell key={cell.id}> {flexRender( cell.column.columnDef.cell, cell.getContext() )} </TableCell> ))} </TableRow> )) ) : ( <TableRow> <TableCell colSpan={columns.length} className="h-24 text-center" > No results. </TableCell> </TableRow> )} </TableBody> </Table> {/* 将Sheet放在表格之外,避免破坏表格结构 */} {openSheetId && ( <Sheet open={!!openSheetId} onOpenChange={(open) => !open && setOpenSheetId(null)}> <SheetContent> <SheetHeader> <SheetTitle>行详情:{currentRowData?.name}</SheetTitle> <SheetDescription> 这是ID为{openSheetId}的行数据详情,可根据需求自定义内容。 </SheetDescription> </SheetHeader> <div className="mt-4"> {/* 这里渲染当前行的具体数据 */} {columns.map(col => ( <div key={col.id} className="mb-2"> <span className="font-medium">{col.columnDef.header as string}:</span> <span className="ml-2">{currentRowData?.[col.id as keyof TData]}</span> </div> ))} </div> <SheetClose className="mt-6">关闭</SheetClose> </SheetContent> </Sheet> )} </div> ); }
关键修改说明
- 状态管理:通过
openSheetId记录当前要打开的行ID,控制Sheet的显示与隐藏。 - 结构调整:Sheet组件从
<TableBody>中移出,放在表格外层,完全不影响表格的DOM结构。 - 点击事件:直接在
<TableRow>上绑定onClick事件触发Sheet打开,替代SheetTrigger的包裹方式。 - 数据匹配:根据
openSheetId从数据源中找到对应行的数据,渲染到Sheet中展示详情。
额外优化建议
- 如果需要支持多行选中或批量操作,可调整状态为数组存储选中ID,但单Sheet场景下单个ID足够。
- 为可点击的表格行添加
cursor-pointer和悬停样式,提升用户体验。 - 可以为Sheet添加关闭按钮或点击遮罩层关闭的逻辑,增强交互性。
内容的提问来源于stack exchange,提问作者Simo D.
相关产品推荐
相关产品推荐

