Expandable rows
The expandedRowRender slot renders the content of an expanded row — the column with the chevron appears automatically.
vue
<script setup lang="ts">
import type { NTable } from '@central-design-system/components';
import { CdsTable } from '@central-design-system/components';
type TDataType = { key: number; name: string; age: number; phone: string; address: string; description: string };
const columns: NTable.NColumn.TColumns<TDataType> = [
{ title: 'Имя', dataIndex: 'name', key: 'name' },
{ title: 'Возраст', dataIndex: 'age', key: 'age' },
{ title: 'Адрес', dataIndex: 'address', key: 'address' },
{ title: 'Телефон', dataIndex: 'phone', key: 'phone' }
];
const data: TDataType[] = [
{
key: 1,
name: 'Иванов Иван',
age: 20,
phone: '79999999999',
address: 'г. Москва, ул. Ленина д. 1',
description: 'Меня зовут Иванов Иван, мне 20 лет и я живу по адресу г. Москва, ул. Ленина д. 1'
},
{
key: 2,
name: 'Иванов Василий',
age: 32,
phone: '79999999999',
address: 'г. Москва, ул. Ленина д. 2',
description: 'Меня зовут Иванов Василий, мне 32 года и я живу по адресу г. Москва, ул. Ленина д. 2'
},
{
key: 3,
name: 'Иванов Кирилл',
age: 41,
phone: '79999999999',
address: 'г. Москва, ул. Ленина д. 3',
description: 'Меня зовут Иванов Кирилл, мне 41 год и я живу по адресу г. Москва, ул. Ленина д. 3'
}
];
</script>
<template>
<CdsTable :columns="columns" :data="data" :pagination="false">
<template #expandedRowRender="{ record }">
<p>{{ record.description }}</p>
</template>
</CdsTable>
</template>Nested tables
Anything can live inside expandedRowRender — including another CdsTable with its own columns and data.
vue
<script setup lang="ts">
import type { NTable } from '@central-design-system/components';
import { CdsFunctionalButton, CdsStatus, CdsTable, CdsToggle } from '@central-design-system/components';
const columns: NTable.NColumn.TColumns = [
{ title: 'Название', dataIndex: 'name', key: 'name' },
{ title: 'Платформа', dataIndex: 'platform', key: 'platform' },
{ title: 'Версия', dataIndex: 'version', key: 'version' },
{ title: 'Покрытие', dataIndex: 'coverage', key: 'coverage' },
{ title: 'Автор', dataIndex: 'creator', key: 'creator' },
{ title: 'Дата', dataIndex: 'createdAt', key: 'createdAt' },
{ title: 'Действия', key: 'operation' }
];
const data = [...Array(3)].map((_, i) => ({
key: i,
name: `Компонент ${i + 1}`,
platform: 'iOS',
version: '3.2.1',
coverage: 100,
creator: 'i.ivanov',
createdAt: new Date('2023-02-07 23:12:00').toLocaleDateString()
}));
const innerColumns: NTable.NColumn.TColumns = [
{ title: 'Дата', dataIndex: 'date', key: 'date' },
{ title: 'Название', dataIndex: 'name', key: 'name' },
{ title: 'Статус', key: 'state' },
{ title: 'Покрытие', dataIndex: 'coverage', key: 'coverage' },
{ title: 'Действие', dataIndex: 'operation', key: 'operation' }
];
const innerData = [...Array(3)].map((_, i) => ({
key: i,
date: new Date('2023-02-07 23:12:00').toLocaleDateString(),
name: `Это компонент ${i + 1}`,
coverage: 'Покрыто: 56'
}));
</script>
<template>
<CdsTable :columns="columns" :data="data" :pagination="false">
<template #bodyCell="{ column }">
<template v-if="column.key === 'operation'">
<CdsToggle hide-messages />
</template>
</template>
<template #expandedRowRender>
<CdsTable :columns="innerColumns" :data="innerData" :pagination="false">
<template #bodyCell="{ column }">
<template v-if="column.key === 'state'">
<span>
<CdsStatus status="success" text="Готово" />
</span>
</template>
<template v-else-if="column.key === 'operation'">
<span class="flex gap-2">
<CdsFunctionalButton icon="edit" />
<CdsFunctionalButton icon="delete" />
</span>
</template>
</template>
</CdsTable>
</template>
</CdsTable>
</template>Tree data
Records with a children field expand as a tree. Together with row-selection, the checkStrictly: false option turns on cascading selection: checking a parent selects its children.
vue
<script setup lang="ts">
import type { NTable } from '@central-design-system/components';
import { CdsCheckbox, CdsTable } from '@central-design-system/components';
import { ref } from 'vue';
type TDataItem = {
key: number;
name: string;
price: number;
description: string;
children?: TDataItem[];
};
const columns: NTable.NColumn.TColumns<TDataItem> = [
{ title: 'Название', dataIndex: 'name', key: 'name' },
{ title: 'Цена, $', dataIndex: 'price', key: 'price', width: '12%' },
{ title: 'Описание', dataIndex: 'description', width: '30%', key: 'description' }
];
const data: TDataItem[] = [
{
key: 1,
name: 'Apple iPhone',
price: 3250,
description: 'Описание товара',
children: [
{ key: 11, name: 'iPhone 14 Pro', price: 2000, description: 'Описание товара' },
{
key: 12,
name: 'Power Bank',
price: 50,
description: 'Описание товара',
children: [{ key: 121, name: 'MagSafe Battery Pack', price: 50, description: 'Описание товара' }]
},
{
key: 13,
name: 'Headphones',
price: 1200,
description: 'Описание товара',
children: [
{
key: 131,
name: 'AirPods',
price: 1200,
description: 'Описание товара',
children: [
{ key: 1311, name: 'AirPods Pro', price: 200, description: 'Описание товара' },
{ key: 1312, name: 'AirPods Max', price: 1000, description: 'Описание товара' }
]
}
]
}
]
},
{ key: 2, name: 'Xiaomi Mi', price: 200, description: 'Описание товара' }
];
const rowSelection = ref({
checkStrictly: false,
onChange: (selectedRowKeys: NTable.TKey[], selectedRows: TDataItem[]) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
},
onSelect: (record: TDataItem, selected: boolean, selectedRows: TDataItem[]) => {
console.log('onSelect', record, selected, selectedRows);
}
});
const rowClassName = (record: TDataItem) => (!record.children?.length ? 'cds-table__row--no-children' : '');
</script>
<template>
<div class="flex flex-col gap-4">
<CdsCheckbox v-model="rowSelection.checkStrictly" label="Строгий выбор" hide-messages />
<CdsTable :columns="columns" :data="data" :row-selection="rowSelection" :row-class-name="rowClassName" />
</div>
</template>Expanding every node
The expanded-row-keys prop makes expansion controlled: collect the keys of every parent record — and an "Expand all" button unfolds the whole tree.
vue
<script setup lang="ts">
import type { NTable } from '@central-design-system/components';
import { CdsFunctionalButton, CdsTable } from '@central-design-system/components';
import { computed, ref } from 'vue';
type TDataItem = {
key: number;
name: string;
price: number;
description: string;
children?: TDataItem[];
};
const columns: NTable.NColumn.TColumns<TDataItem> = [
{ title: 'Название', dataIndex: 'name', key: 'name' },
{ title: 'Цена, $', dataIndex: 'price', key: 'price', width: '12%' },
{ title: 'Описание', dataIndex: 'description', width: '30%', key: 'description' }
];
const data = ref<TDataItem[]>([
{
key: 1,
name: 'Apple iPhone',
price: 3250,
description: 'Описание товара',
children: [
{ key: 11, name: 'iPhone 14 Pro', price: 2000, description: 'Описание товара' },
{
key: 12,
name: 'Power Bank',
price: 50,
description: 'Описание товара',
children: [{ key: 121, name: 'MagSafe Battery Pack', price: 50, description: 'Описание товара' }]
},
{
key: 13,
name: 'Headphones',
price: 1200,
description: 'Описание товара',
children: [
{
key: 131,
name: 'AirPods',
price: 1200,
description: 'Описание товара',
children: [
{ key: 1311, name: 'AirPods Pro', price: 200, description: 'Описание товара' },
{ key: 1312, name: 'AirPods Max', price: 1000, description: 'Описание товара' }
]
}
]
}
]
},
{ key: 2, name: 'Xiaomi Mi', price: 200, description: 'Описание товара' }
]);
const rowClassName = (record: TDataItem) => (!record.children?.length ? 'cds-table__row--no-children' : '');
// Массив для хранения ключей развернутых узлов
const expandedRowKeys = ref<NTable.TKey[]>([]);
// Рекурсивная функция для сбора всех ключей из данных
const getAllKeys = (items: TDataItem[]): NTable.TKey[] => {
let keys: NTable.TKey[] = [];
items.forEach((item) => {
keys.push(item.key);
if (item.children && item.children.length > 0) {
keys = keys.concat(getAllKeys(item.children));
}
});
return keys;
};
const allRowKeys = computed(() => getAllKeys(data.value));
const allExpanded = computed(() => allRowKeys.value.every((key) => expandedRowKeys.value.includes(key)));
const toggleAll = () => {
expandedRowKeys.value = allExpanded.value ? [] : allRowKeys.value;
};
const handleExpand = (expanded: boolean, record: TDataItem) => {
if (expanded) {
expandedRowKeys.value.push(record.key);
} else {
expandedRowKeys.value = expandedRowKeys.value.filter((key) => key !== record.key);
}
};
</script>
<template>
<CdsTable
:columns="columns"
:data="data"
:expanded-row-keys="expandedRowKeys"
:row-class-name="rowClassName"
@expand="handleExpand"
>
<template #headerCell="{ title, column }">
<template v-if="column.key === 'name'">
<CdsFunctionalButton :icon="allExpanded ? 'chevron-down' : 'chevron-right'" @click="toggleAll" />
</template>
{{ title }}
</template>
</CdsTable>
</template>