- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Auth Surface
- Avatar
- Badge
- Browse Catalog Dialog
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Command
- Connector Setup Dialog
- Cookie Banner
- Dialog
- Directory Card
- Directory Detail
- Directory Skeleton
- DrawerНовое
- Token Parts Input
- Empty
- Field
- Fieldset
- File Preview Modal
- File Preview Skeleton
- Form
- Frame
- Group
- Icon
- Input
- Input Group
- Kbd
- Label
- Legal Shell
- Menu
- Mermaid Diagram
- Mind Map Diagram
- Not Found Screen
- Onboarding Frame
- Popover
- PDF Thumbnail
- Personalization Landing
- Preview Card
- Pricing Page
- Progress
- Radio Group
- Ring Spinner
- Scroll Area
- Select
- Separator
- Settings Page
- Settings Skills
- Settings Connectors
- Settings Capabilities
- Settings Usage
- Settings Account
- Settings Billing
- Sheet
- Sidebar
- Skeleton
- Skill Create Dialog
- Slider
- Spinner
- Stat
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Tooltip
- Компоненты AI
- Chat Conversation
- Chat Message
- Chat Response
- Chat Suggestion
- Chat Prompt Input
- Slash Highlighted Textarea
- Chat Search Dialog
- Chat Skill Doc
- Chat Connector Detail
- Chat Attachments
- Chat File Card
- Chat Token Chip
- Chat Code Block
- Chat Image
- Chat Inline Citation
- Chat Sources
- Chat Web Search
- Chat Research
- Chat Source
- Chat Actions
- Chat Context
- Chat Loader
- Chat Compaction
- Chat Timeline
- Chat Snippet
- Chat Terminal
- Chat Stack Trace
- Chat Test Results
- Chat File Tree
- Chat Environment Variables
- Chat Audio Player
- Chat Transcription
- Chat Speech Input
- Chat Mic Selector
- Chat Voice Selector
- Chat Agent
- Chat Persona
- Chat Connection
- Chat Connector Suggestion
- Chat Queue
- Chat Checkpoint
- Chat Confirmation
- Chat Artifact
- Chat JSX Preview
- Chat Schema Display
- Chat Package Info
- Chat Commit
- Chat Plan
- Chat Open In Chat
- Chat Sandbox
- Chat Model Selector
- Chat Canvas
- Chat Node
- Chat Edge
Table
Простой компонент таблицы для отображения табличных данных.
| Проект | Статус | Команда | Бюджет |
|---|---|---|---|
| Редизайн сайта | Оплачено | Команда фронтенда | $12,500 |
| Мобильное приложение | Не оплачено | Команда мобильной разработки | $8,750 |
| Интеграция API | В ожидании | Команда бэкенда | $5,200 |
| Миграция базы данных | Оплачено | Команда DevOps | $3,800 |
| Панель пользователя | Оплачено | Команда UX | $7,200 |
| Аудит безопасности | Ошибка | Команда безопасности | $2,100 |
| Общий бюджет | $39,550 | ||
import { Badge } from "@/components/ui/badge";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
export default function Particle() {
return (
<Table>
<TableCaption>Список текущих проектов.</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Проект</TableHead>
<TableHead>Статус</TableHead>
<TableHead>Команда</TableHead>
<TableHead className="text-right">Бюджет</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Редизайн сайта</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда фронтенда</TableCell>
<TableCell className="text-right">$12,500</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Мобильное приложение</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-muted-foreground/64"
/>
Не оплачено
</Badge>
</TableCell>
<TableCell>Команда мобильной разработки</TableCell>
<TableCell className="text-right">$8,750</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Интеграция API</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-warning"
/>
В ожидании
</Badge>
</TableCell>
<TableCell>Команда бэкенда</TableCell>
<TableCell className="text-right">$5,200</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Миграция базы данных</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда DevOps</TableCell>
<TableCell className="text-right">$3,800</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Панель пользователя</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда UX</TableCell>
<TableCell className="text-right">$7,200</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Аудит безопасности</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-destructive"
/>
Ошибка
</Badge>
</TableCell>
<TableCell>Команда безопасности</TableCell>
<TableCell className="text-right">$2,100</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Общий бюджет</TableCell>
<TableCell className="text-right">$39,550</TableCell>
</TableRow>
</TableFooter>
</Table>
);
}
Установка
pnpm dlx shadcn@latest add @oracul/table
Использование
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"<Table>
<TableCaption>Caption</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Header</TableHead>
<TableHead>Header</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell>Cell</TableCell>
<TableCell>Cell</TableCell>
</TableRow>
</TableBody>
</Table>API
Table
Основной компонент-контейнер таблицы. Установите variant="card", чтобы получить таблицу в стиле карточек: разделённые границы, скруглённые углы и фоны строк, которые воспринимаются как карточки. Это хорошо сочетается с Frame (обрамление страницы) или CardFrame (оболочка карточки с необязательными действиями в шапке). Вариант по умолчанию — более простая компоновка строк со стандартными границами.
| Свойство | Тип | По умолчанию |
|---|---|---|
className | string | |
variant | "default" | "card" | "default" |
<Table>
<TableHeader>...</TableHeader>
<TableBody>...</TableBody>
</Table><Table variant="card">
<TableHeader>...</TableHeader>
<TableBody>...</TableBody>
</Table>TableHeader
Секция шапки таблицы, содержащая заголовки столбцов.
| Свойство | Тип | По умолчанию |
|---|---|---|
className | string |
<TableHeader>
<TableRow>
<TableHead>Header</TableHead>
</TableRow>
</TableHeader>TableBody
Секция тела таблицы, содержащая строки и данные.
| Свойство | Тип | По умолчанию |
|---|---|---|
className | string |
<TableBody>
<TableRow>
<TableCell>Cell</TableCell>
</TableRow>
</TableBody>TableFooter
Секция подвала таблицы.
| Свойство | Тип | По умолчанию |
|---|---|---|
className | string |
<TableFooter>
<TableRow>
<TableCell>Footer</TableCell>
</TableRow>
</TableFooter>TableRow
Строка таблицы.
| Свойство | Тип | По умолчанию |
|---|---|---|
className | string |
<TableRow>
<TableCell>Cell</TableCell>
</TableRow>TableHead
Ячейка-заголовок таблицы.
| Свойство | Тип | По умолчанию |
|---|---|---|
className | string |
<TableHead>Header</TableHead>TableCell
Ячейка с данными в таблице.
| Свойство | Тип | По умолчанию |
|---|---|---|
className | string |
<TableCell>Cell</TableCell>TableCaption
Подпись к таблице.
| Свойство | Тип | По умолчанию |
|---|---|---|
className | string |
<TableCaption>Caption</TableCaption>Примеры
Таблица в стиле карточек
Используйте variant="card", когда сама сетка должна выглядеть как набор карточек (например, дашборды или отдельные таблицы без внешнего обрамления).
| Проект | Статус | Команда | Бюджет |
|---|---|---|---|
| Редизайн сайта | Оплачено | Команда фронтенда | $12,500 |
| Мобильное приложение | Не оплачено | Команда мобильной разработки | $8,750 |
| Интеграция с API | В ожидании | Команда бэкенда | $5,200 |
| Миграция базы данных | Оплачено | Команда DevOps | $3,800 |
| Пользовательская панель | Оплачено | Команда UX | $7,200 |
| Аудит безопасности | Ошибка | Команда безопасности | $2,100 |
| Итого по бюджету | $39,550 | ||
import { Badge } from "@/components/ui/badge";
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
export default function Particle() {
return (
<Table className="w-full" variant="card">
<TableHeader>
<TableRow>
<TableHead>Проект</TableHead>
<TableHead>Статус</TableHead>
<TableHead>Команда</TableHead>
<TableHead className="text-right">Бюджет</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Редизайн сайта</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда фронтенда</TableCell>
<TableCell className="text-right">$12,500</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Мобильное приложение</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-muted-foreground/64"
/>
Не оплачено
</Badge>
</TableCell>
<TableCell>Команда мобильной разработки</TableCell>
<TableCell className="text-right">$8,750</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Интеграция с API</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-warning"
/>
В ожидании
</Badge>
</TableCell>
<TableCell>Команда бэкенда</TableCell>
<TableCell className="text-right">$5,200</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Миграция базы данных</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда DevOps</TableCell>
<TableCell className="text-right">$3,800</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Пользовательская панель</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда UX</TableCell>
<TableCell className="text-right">$7,200</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Аудит безопасности</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-destructive"
/>
Ошибка
</Badge>
</TableCell>
<TableCell>Команда безопасности</TableCell>
<TableCell className="text-right">$2,100</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Итого по бюджету</TableCell>
<TableCell className="text-right">$39,550</TableCell>
</TableRow>
</TableFooter>
</Table>
);
}
Таблица в CardFrame
Поместите таблицу в CardFrame, чтобы сетка располагалась внутри оболочки карточки (граница, радиус, обрезка). Используйте variant="card" на Table. Пример ниже — это статическая разметка, без выбора строк и без TanStack.
| Проект | Статус | Команда | Бюджет |
|---|---|---|---|
| Редизайн сайта | Оплачено | Команда фронтенда | $12,500 |
| Мобильное приложение | Не оплачено | Команда мобильной разработки | $8,750 |
| Интеграция с API | В ожидании | Команда бэкенда | $5,200 |
| Миграция базы данных | Оплачено | Команда DevOps | $3,800 |
| Пользовательская панель | Оплачено | Команда UX | $7,200 |
| Аудит безопасности | Ошибка | Команда безопасности | $2,100 |
| Итого по бюджету | $39,550 | ||
import { Badge } from "@/components/ui/badge";
import { CardFrame } from "@/components/ui/card";
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
export default function Particle() {
return (
<CardFrame className="w-full">
<Table variant="card">
<TableHeader>
<TableRow>
<TableHead>Проект</TableHead>
<TableHead>Статус</TableHead>
<TableHead>Команда</TableHead>
<TableHead className="text-right">Бюджет</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Редизайн сайта</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда фронтенда</TableCell>
<TableCell className="text-right">$12,500</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Мобильное приложение</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-muted-foreground/64"
/>
Не оплачено
</Badge>
</TableCell>
<TableCell>Команда мобильной разработки</TableCell>
<TableCell className="text-right">$8,750</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Интеграция с API</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-warning"
/>
В ожидании
</Badge>
</TableCell>
<TableCell>Команда бэкенда</TableCell>
<TableCell className="text-right">$5,200</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Миграция базы данных</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда DevOps</TableCell>
<TableCell className="text-right">$3,800</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">
Пользовательская панель
</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда UX</TableCell>
<TableCell className="text-right">$7,200</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Аудит безопасности</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-destructive"
/>
Ошибка
</Badge>
</TableCell>
<TableCell>Команда безопасности</TableCell>
<TableCell className="text-right">$2,100</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Итого по бюджету</TableCell>
<TableCell className="text-right">$39,550</TableCell>
</TableRow>
</TableFooter>
</Table>
</CardFrame>
);
}
Таблица в Frame
Оберните таблицу в Frame для обрамления поверхности приложения границами. Используйте variant="card" на Table, чтобы строки сохраняли оформление в стиле карточек внутри обрамления.
| Проект | Статус | Команда | Бюджет |
|---|---|---|---|
| Редизайн сайта | Оплачено | Команда фронтенда | $12,500 |
| Мобильное приложение | Не оплачено | Команда мобильной разработки | $8,750 |
| Интеграция API | В ожидании | Команда бэкенда | $5,200 |
| Миграция базы данных | Оплачено | Команда DevOps | $3,800 |
| Панель пользователя | Оплачено | Команда UX | $7,200 |
| Аудит безопасности | Ошибка | Команда безопасности | $2,100 |
| Общий бюджет | $39,550 | ||
import { Badge } from "@/components/ui/badge";
import { Frame } from "@/components/ui/frame";
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
export default function Particle() {
return (
<Frame className="w-full">
<Table variant="card">
<TableHeader>
<TableRow>
<TableHead>Проект</TableHead>
<TableHead>Статус</TableHead>
<TableHead>Команда</TableHead>
<TableHead className="text-right">Бюджет</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<TableRow>
<TableCell className="font-medium">Редизайн сайта</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда фронтенда</TableCell>
<TableCell className="text-right">$12,500</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Мобильное приложение</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-muted-foreground/64"
/>
Не оплачено
</Badge>
</TableCell>
<TableCell>Команда мобильной разработки</TableCell>
<TableCell className="text-right">$8,750</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Интеграция API</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-warning"
/>
В ожидании
</Badge>
</TableCell>
<TableCell>Команда бэкенда</TableCell>
<TableCell className="text-right">$5,200</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Миграция базы данных</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда DevOps</TableCell>
<TableCell className="text-right">$3,800</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Панель пользователя</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-success"
/>
Оплачено
</Badge>
</TableCell>
<TableCell>Команда UX</TableCell>
<TableCell className="text-right">$7,200</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">Аудит безопасности</TableCell>
<TableCell>
<Badge variant="outline">
<span
aria-hidden="true"
className="size-1.5 rounded-full bg-destructive"
/>
Ошибка
</Badge>
</TableCell>
<TableCell>Команда безопасности</TableCell>
<TableCell className="text-right">$2,100</TableCell>
</TableRow>
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={3}>Общий бюджет</TableCell>
<TableCell className="text-right">$39,550</TableCell>
</TableRow>
</TableFooter>
</Table>
</Frame>
);
}
Таблица данных с TanStack
Используйте TanStack Table с variant="card", когда вам нужны определения столбцов, выбор строк и flexRender поверх тех же примитивов таблицы. Добавьте @tanstack/react-table в свой проект для безголового состояния (сортировка, пагинация, выбор), выходящего за пределы статической разметки.
| Проект | Статус | Команда | Бюджет | |
|---|---|---|---|---|
Редизайн сайта | Paid | Команда фронтенда | $12,500 | |
Мобильное приложение | Unpaid | Команда мобильной разработки | $8,750 | |
Интеграция с API | Pending | Команда бэкенда | $5,200 | |
Миграция базы данных | Paid | Команда DevOps | $3,800 | |
Пользовательская панель | Paid | Команда UX | $7,200 | |
Аудит безопасности | Failed | Команда безопасности | $2,100 | |
| Итого по бюджету | $39,550 | |||
"use client";
import {
type ColumnDef,
flexRender,
getCoreRowModel,
useReactTable,
} from "@tanstack/react-table";
import type React from "react";
import { useMemo, useState } from "react";
import { Badge } from "@/components/ui/badge";
import { CardFrame } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
type Project = {
id: string;
project: string;
status: "Paid" | "Unpaid" | "Pending" | "Failed";
team: string;
budget: number;
};
const data: Project[] = [
{
budget: 12500,
id: "1",
project: "Редизайн сайта",
status: "Paid",
team: "Команда фронтенда",
},
{
budget: 8750,
id: "2",
project: "Мобильное приложение",
status: "Unpaid",
team: "Команда мобильной разработки",
},
{
budget: 5200,
id: "3",
project: "Интеграция с API",
status: "Pending",
team: "Команда бэкенда",
},
{
budget: 3800,
id: "4",
project: "Миграция базы данных",
status: "Paid",
team: "Команда DevOps",
},
{
budget: 7200,
id: "5",
project: "Пользовательская панель",
status: "Paid",
team: "Команда UX",
},
{
budget: 2100,
id: "6",
project: "Аудит безопасности",
status: "Failed",
team: "Команда безопасности",
},
];
const getStatusColor = (status: Project["status"]) => {
switch (status) {
case "Paid":
return "bg-success";
case "Unpaid":
return "bg-muted-foreground/64";
case "Pending":
return "bg-warning";
case "Failed":
return "bg-destructive";
default:
return "bg-muted-foreground/64";
}
};
const getColumns = (): ColumnDef<Project>[] => [
{
cell: ({ row }) => {
const toggleHandler = row.getToggleSelectedHandler();
return (
<Checkbox
aria-label="Выбрать строку"
checked={row.getIsSelected()}
disabled={!row.getCanSelect()}
onCheckedChange={(value) => {
// Create a synthetic event for the handler
const syntheticEvent = {
target: { checked: !!value },
} as unknown as React.ChangeEvent<HTMLInputElement>;
toggleHandler(syntheticEvent);
}}
/>
);
},
enableSorting: false,
header: ({ table }) => {
const isAllSelected = table.getIsAllPageRowsSelected();
const isSomeSelected = table.getIsSomePageRowsSelected();
const toggleHandler = table.getToggleAllPageRowsSelectedHandler();
return (
<Checkbox
aria-label="Выбрать все"
checked={isAllSelected}
indeterminate={isSomeSelected && !isAllSelected}
onCheckedChange={(value) => {
// Create a synthetic event for the handler
const syntheticEvent = {
target: { checked: !!value },
} as unknown as React.ChangeEvent<HTMLInputElement>;
toggleHandler(syntheticEvent);
}}
/>
);
},
id: "select",
},
{
accessorKey: "project",
cell: ({ row }) => (
<div className="font-medium">{row.getValue("project")}</div>
),
header: "Проект",
},
{
accessorKey: "status",
cell: ({ row }) => {
const status = row.getValue("status") as Project["status"];
return (
<Badge variant="outline">
<span
aria-hidden="true"
className={`size-1.5 rounded-full ${getStatusColor(status)}`}
/>
{status}
</Badge>
);
},
header: "Статус",
},
{
accessorKey: "team",
header: "Команда",
},
{
accessorKey: "budget",
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue("budget"));
const formatted = new Intl.NumberFormat("en-US", {
currency: "USD",
maximumFractionDigits: 0,
minimumFractionDigits: 0,
style: "currency",
}).format(amount);
return <div className="text-right">{formatted}</div>;
},
header: () => <div className="text-right">Бюджет</div>,
},
];
export default function Particle() {
const [tableData] = useState<Project[]>(data);
const [rowSelection, setRowSelection] = useState({});
const columns = useMemo(() => getColumns(), []);
const table = useReactTable({
columns,
data: tableData,
enableRowSelection: true,
getCoreRowModel: getCoreRowModel(),
onRowSelectionChange: setRowSelection,
state: {
rowSelection,
},
});
const totalBudget = tableData.reduce(
(sum, project) => sum + project.budget,
0,
);
const formattedTotal = new Intl.NumberFormat("en-US", {
currency: "USD",
maximumFractionDigits: 0,
minimumFractionDigits: 0,
style: "currency",
}).format(totalBudget);
return (
<CardFrame className="w-full">
<Table variant="card">
<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
data-state={row.getIsSelected() && "selected"}
key={row.id}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell className="h-24 text-center" colSpan={columns.length}>
Ничего не найдено.
</TableCell>
</TableRow>
)}
</TableBody>
<TableFooter>
<TableRow>
<TableCell colSpan={4}>Итого по бюджету</TableCell>
<TableCell className="text-right">{formattedTotal}</TableCell>
</TableRow>
</TableFooter>
</Table>
</CardFrame>
);
}
Журнал изменений
- 12 апреля 2026 —
Tableполучает необязательныйvariant(defaultилиcard)