Table
A flexible and responsive Table component for displaying structured data, supporting slots for custom headers, rows and more.
Demo
Column 1 | Column 2 | Column 3 | |
|---|---|---|---|
| John Doe | johndoe@example.com | Active | |
| Jane Smith | janesmith@example.com | Inactive | |
| Alice Johnson | alice.j@example.com | Pending | |
| Bob Brown | bob.brown@example.com | Active | |
| Charlie Davis | charlie.davis@example.com | Inactive | |
| Emma Wilson | emma.wilson@example.com | Pending | |
| Liam Taylor | liam.taylor@example.com | Active | |
| Olivia Moore | olivia.moore@example.com | Inactive | |
| Noah Miller | noah.miller@example.com | Active | |
| Ava Martin | ava.martin@example.com | Pending |
Showing 1 to 10 of 80 results
<template>
<TableWrapper v-if="exampleTableData.length">
<Table>
<TableHeader>
<TableHeaderCell>Column 1</TableHeaderCell>
<TableHeaderCell>Column 2</TableHeaderCell>
<TableHeaderCell>Column 3</TableHeaderCell>
<TableHeaderCell><!-- Cell actions --></TableHeaderCell>
</TableHeader>
<TableBody>
<TableRow v-for="(item, index) in paginatedData" :key="index">
<TableCell>{{ item.col1 }}</TableCell>
<TableCell>{{ item.col2 }}</TableCell>
<TableCell>{{ item.col3 }}</TableCell>
<TableCellActions>
<ActionIconButton
:size="ButtonSize.MD"
icon="mdi:eye-outline"
/>
</TableCellActions>
</TableRow>
</TableBody>
</Table>
<ButtonPagination
v-model:modelValue="currentPage"
v-model:items-per-page="currentItemsPerPage"
:totalItems="exampleTableData.length"
/>
</TableWrapper>
<EmptyState v-else />
</template>
<script setup lang="ts">
// States
const currentPage = ref(1)
const currentItemsPerPage = ref(10)
// Static example data
const exampleTableData = [
...
]
// Computed
const paginatedData = computed(() =>
getPaginatedData(exampleTableData, currentPage.value, currentItemsPerPage.value)
)
</script>
Architecture
To build a flexible and reusable table layout, you can structure your component as follows. This approach separates concerns clearly, supports pagination, and includes an empty state fallback to improve UX:
<template>
<TableWrapper v-if="exampleTableData.length">
<Table>
<TableHeader>
<TableHeaderCell>
<!-- Column name -->
</TableHeaderCell>
....
</TableHeader>
<TableBody>
<!-- Use the data array to render all the rows -->
<TableRow v-for="(item, index) in paginatedData" :key="index">
<TableCell>{{ item.col1 }}</TableCell>
....
<!-- In case you need to use actions, use this component for a proper layout -->
<TableCellActions>
<ActionIconButton
:size="ButtonSize.MD"
icon="mdi:eye-outline"
/>
</TableCellActions>
</TableRow>
</TableBody>
</Table>
<!-- Optional pagination -->
<ButtonPagination
v-model:modelValue="currentPage"
v-model:items-per-page="currentItemsPerPage"
:totalItems="exampleTableData.length"
/>
</TableWrapper>
<!-- Recommended empty state -->
<EmptyState v-else />
<template>
Components
This set of components are used when creating tables.
| Name | Description |
|---|---|
<TableWrapper> | Provides a container for all the table components and pagination which is usefull to set the layout correctly. |
<Table> | Represents the table. Under the hood, it provides an overflow scrollable container. |
<TableHeader> | Represents the head of the table. |
<TableHeaderCell> | Represents a cell in the table header. |
<TableBody> | Represents the body of the table. |
<TableRow> | Represents a table row. |
<TableCell> | Represents the cell inside a table row. |
<TableCellActions> | It is a predesigned and use-specific case cell, which provides an suitable layout for actions |
Usage
Paginated data
In the event of using pagined data, please follow this steps:
This function requires:
data: T[],
currentPage: number,
itemsPerPage: number,
itemsLimit?: number
Consider using itemsLimit if you want to limit the amount of items you want to handle.
<script setup lang="ts">
// States
const currentPage = ref(1)
const currentItemsPerPage = ref(10)
// Static example data
const exampleTableData = [
...
]
// Computed
const paginatedData = computed(() =>
getPaginatedData(exampleTableData, currentPage.value, currentItemsPerPage.value)
)
</script>
TableCell
The TableCell components accept some props:
| Props | Default | Type |
|---|---|---|
fitToContent | false | boolean |
to | — | string |
noWrap | false | boolean |
fitToContent
Forces the cell and the column to fit is width to the content of the cell. Usefull when using badges for instance.
<template>
...
<TableCell fitToContent>
...
</TableCell>
...
</template>
to
The to prop is used to set the href attribute of the a tag inside the TableCell component. It is useful when you want to link to another page or resource.
UX improvement: When using the to prop, provide the <TableRow> component the isHoverable prop to ensure a better UX experience.
<template>
...
<TableCell to="/some-page">
...
</TableCell>
...
</template>
When linking table rows to a specific route, make sure all relevant cells include the same <strong>to</strong> prop. This ensures consistent behavior. Some cells, such as action cells, should remain unlinked, which is why this approach provides more granular control.
noWrap
Prevents the cell content from wrapping to a new line. Useful for cells containing dates, IDs, or other short values that should stay on a single line.
<template>
...
<TableCell noWrap>
...
</TableCell>
...
</template>
TableHeaderCell
The TableHeaderCell component accepts some props:
| Props | Default | Type |
|---|---|---|
scope | TableHeaderCellScope.COL | TableHeaderCellScope |
sorteable | false | boolean |
columnKey | — | string |
sortKey | — | string |
sortAsc | — | boolean |
onToggleSort | — | () => void |
fitToContent | false | boolean |
to | — | string |
sortAriaLabel | 'Toggle sort' | string |
noWrap | false | boolean |
scope
The scope prop is used to set the scope of the header cell. It uses the TableHeaderCellScope enum.
With TableHeaderCellScope.COL (default), the header cell is placed inside TableHeader, as a column heading:
<template>
...
<TableHeader>
<TableHeaderCell :scope="TableHeaderCellScope.COL">
Column 1
</TableHeaderCell>
...
</TableHeader>
...
</template>
With TableHeaderCellScope.ROW, the header cell is placed inside TableRow, alongside TableCell, as a row heading:
<template>
...
<TableRow>
<TableHeaderCell :scope="TableHeaderCellScope.ROW">
Row header
</TableHeaderCell>
<TableCell>...</TableCell>
...
</TableRow>
...
</template>
Options
| Value | Description |
|---|---|
COL | Indicates that the header cell is a column header. |
ROW | Indicates that the header cell is a row header. |
Note about scope
When scope is set to <strong>COL</strong>, the fitToContent and to props are ignored and have no effect on the header cell. When scope is set to <strong>ROW</strong>, sorting is disabled. In this case, the following props should not be used, as they will not apply: sorteable, columnKey, sortKey, sortAsc, and onToggleSort.
sorteable
When true, it indicates that the column is sortable.
It is recommended to use this prop together with columnKey, sortKey, sortAsc and onToggleSort props.
<template>
...
<TableHeaderCell
sorteable
columnKey="col1"
:sortKey
:sortAsc
:onToggleSort="() => toggleSort('col1')"
>
Column 1
</TableHeaderCell>
...
</template>
columnKey
The columnKey prop is used to set the key of the column. It is useful when you want to sort the column.
<template>
...
<TableHeaderCell
sorteable
columnKey="col1"
:sortKey
:sortAsc
:onToggleSort="() => toggleSort('col1')"
>
Column 1
</TableHeaderCell>
...
</template>
sortKey
The sortKey prop is used to set the current sort key. It is useful when you want to sort the column.
<template>
...
<TableHeaderCell
sorteable
columnKey="col1"
:sortKey
:sortAsc
:onToggleSort="() => toggleSort('col1')"
>
Column 1
</TableHeaderCell>
...
</template>
sortAsc
The sortAsc prop is used to set the current sort direction. It is useful when you want to sort the column.
<template>
...
<TableHeaderCell
sorteable
columnKey="col1"
:sortKey
:sortAsc
:onToggleSort="() => toggleSort('col1')"
>
Column 1
</TableHeaderCell>
...
</template>
onToggleSort
The onToggleSort prop is used to set the function that will be called when the user clicks on the header cell to toggle the sort direction. It is useful when you want to sort the column.
<template>
...
<TableHeaderCell
sorteable
columnKey="col1"
:sortKey
:sortAsc
:onToggleSort="() => toggleSort('col1')"
>
Column 1
</TableHeaderCell>
...
</template>
fitToContent / to
These props have the same behavior as in the TableCell component, but in this case they will be applied to the header cell.
<template>
...
<TableHeaderCell
:scope="TableHeaderCellScope.ROW"
fitToContent
to="/some-page"
>
Row header
</TableHeaderCell>
...
</template>
noWrap
Prevents the header cell content from wrapping to a new line. Applies when scope is ROW; COL header cells already prevent wrapping by default.
<template>
...
<TableHeaderCell :scope="TableHeaderCellScope.ROW" noWrap>
Row header
</TableHeaderCell>
...
</template>
sortAriaLabel
The sortAriaLabel prop sets the accessible label for the sort toggle button. Override it for i18n.
<template>
<TableHeaderCell
sorteable
columnKey="col1"
sortAriaLabel="Alternar orden"
>
Column 1
</TableHeaderCell>
</template>
TableRow
The TableRow component is used to render a table row. It accepts the following props:
| Props | Default | Type |
|---|---|---|
isHoverable | false | boolean |
isStripped | false | boolean |
isHoverable
Displays a hover effect on the row if it is true.
<template>
...
<TableRow isHoverable>
...
</TableRow>
...
</template>
isStripped
When true, it applies a different background color to even rows, creating a stripped effect.
<template>
...
<TableRow isStripped>
...
</TableRow>
...
</template>
Table examples
Stripped
Column 1 | Column 2 | Column 3 | |
|---|---|---|---|
| John Doe | johndoe@example.com | Active | |
| Jane Smith | janesmith@example.com | Inactive | |
| Alice Johnson | alice.j@example.com | Pending | |
| Bob Brown | bob.brown@example.com | Active | |
| Charlie Davis | charlie.davis@example.com | Inactive | |
| Emma Wilson | emma.wilson@example.com | Pending | |
| Liam Taylor | liam.taylor@example.com | Active | |
| Olivia Moore | olivia.moore@example.com | Inactive | |
| Noah Miller | noah.miller@example.com | Active | |
| Ava Martin | ava.martin@example.com | Pending |
Showing 1 to 10 of 17 results
<template>
<TableWrapper v-if="exampleTableData.length">
<Table>
<TableHeader>
<TableHeaderCell>Column 1</TableHeaderCell>
<TableHeaderCell>Column 2</TableHeaderCell>
<TableHeaderCell>Column 3</TableHeaderCell>
<TableHeaderCell><!-- Cell actions --></TableHeaderCell>
</TableHeader>
<TableBody>
<TableRow v-for="(item, index) in paginatedData" :key="index" isStripped>
<TableCell>{{ item.col1 }}</TableCell>
<TableCell>{{ item.col2 }}</TableCell>
<TableCell>{{ item.col3 }}</TableCell>
<TableCellActions>
<ActionIconButton
:size="ButtonSize.MD"
icon="mdi:eye-outline"
/>
</TableCellActions>
</TableRow>
</TableBody>
</Table>
<ButtonPagination
v-model:modelValue="currentPage"
v-model:items-per-page="currentItemsPerPage"
:totalItems="exampleTableData.length"
/>
</TableWrapper>
<EmptyState v-else />
</template>
<script setup lang="ts">
// States
const currentPage = ref(1)
const currentItemsPerPage = ref(10)
// Static example data
const exampleTableData = [
{ col1: 'John Doe', col2: 'johndoe@example.com', col3: 'Active' },
...
]
// Computed
const paginatedData = computed(() =>
getPaginatedData(exampleTableData, currentPage.value, currentItemsPerPage.value)
)
</script>
Sorteable
Column 1 | Column 2 | Column 3 | |
|---|---|---|---|
| John Doe | johndoe@example.com | Active | |
| Jane Smith | janesmith@example.com | Inactive | |
| Alice Johnson | alice.j@example.com | Pending | |
| Bob Brown | bob.brown@example.com | Active | |
| Charlie Davis | charlie.davis@example.com | Inactive | |
| Emma Wilson | emma.wilson@example.com | Pending | |
| Liam Taylor | liam.taylor@example.com | Active | |
| Olivia Moore | olivia.moore@example.com | Inactive | |
| Noah Miller | noah.miller@example.com | Active | |
| Ava Martin | ava.martin@example.com | Pending |
Showing 1 to 10 of 17 results
<template>
<TableWrapper v-if="exampleTableData.length">
<Table>
<TableHeader>
<TableHeaderCell
sorteable
columnKey="col1"
:sortKey
:sortAsc
:onToggleSort="() => toggleSort('col1')"
>
Column 1
</TableHeaderCell>
<TableHeaderCell
sorteable
columnKey="col2"
:sortKey
:sortAsc
:onToggleSort="() => toggleSort('col2')"
>
Column 2
</TableHeaderCell>
<TableHeaderCell
sorteable
columnKey="col3"
:sortKey
:sortAsc
:onToggleSort="() => toggleSort('col3')"
>
Column 3
</TableHeaderCell>
<TableHeaderCell><!-- Actions --></TableHeaderCell>
</TableHeader>
<TableBody>
<TableRow v-for="(item, index) in paginatedData" :key="index">
<TableCell>{{ item.col1 }}</TableCell>
<TableCell>{{ item.col2 }}</TableCell>
<TableCell>{{ item.col3 }}</TableCell>
<TableCellActions>
<ActionIconButton
:size="ButtonSize.MD"
icon="mdi:eye-outline"
/>
</TableCellActions>
</TableRow>
</TableBody>
</Table>
<ButtonPagination
v-model:modelValue="currentPage"
v-model:items-per-page="currentItemsPerPage"
:totalItems="exampleTableData.length"
/>
</TableWrapper>
<EmptyState v-else />
</template>
<script setup lang="ts">
// States
const currentPage = ref(1)
const currentItemsPerPage = ref(10)
// Static example data
type ExampleTableDataItem = {
col1: string
col2: string
col3: string
}
const exampleTableData = ref<ExampleTableDataItem[]>([
{ col1: 'John Doe', col2: 'johndoe@example.com', col3: 'Active' },
{ col1: 'Jane Smith', col2: 'janesmith@example.com', col3: 'Inactive' },
{ col1: 'Alice Johnson', col2: 'alice.j@example.com', col3: 'Pending' },
{ col1: 'Bob Brown', col2: 'bob.brown@example.com', col3: 'Active' },
{ col1: 'Charlie Davis', col2: 'charlie.davis@example.com', col3: 'Inactive' },
{ col1: 'Emma Wilson', col2: 'emma.wilson@example.com', col3: 'Pending' },
{ col1: 'Liam Taylor', col2: 'liam.taylor@example.com', col3: 'Active' },
{ col1: 'Olivia Moore', col2: 'olivia.moore@example.com', col3: 'Inactive' },
{ col1: 'Noah Miller', col2: 'noah.miller@example.com', col3: 'Active' },
{ col1: 'Ava Martin', col2: 'ava.martin@example.com', col3: 'Pending' },
{ col1: 'Sophia Clark', col2: 'sophia.clark@example.com', col3: 'Active' },
{ col1: 'Mason Lewis', col2: 'mason.lewis@example.com', col3: 'Inactive' },
{ col1: 'Isabella Young', col2: 'isabella.young@example.com', col3: 'Active' },
{ col1: 'Logan Hall', col2: 'logan.hall@example.com', col3: 'Pending' },
{ col1: 'Mia Allen', col2: 'mia.allen@example.com', col3: 'Active' },
{ col1: 'Ethan Wright', col2: 'ethan.wright@example.com', col3: 'Inactive' },
{ col1: 'Amelia Scott', col2: 'amelia.scott@example.com', col3: 'Pending' },
])
// Composables
const {
sortKey,
sortAsc,
toggleSort,
sortedData,
} = useTable(exampleTableData)
// Computed
const paginatedData = computed(() =>
getPaginatedData(sortedData.value, currentPage.value, currentItemsPerPage.value)
)
</script>
Row header cell
Col1 | John Doe | johndoe@example.com | Active | |
|---|---|---|---|---|
Col2 | Jane Smith | janesmith@example.com | Inactive | |
Col3 | Alice Johnson | alice.j@example.com | Pending | |
Col4 | Bob Brown | bob.brown@example.com | Active | |
Col5 | Charlie Davis | charlie.davis@example.com | Inactive | |
Col6 | Emma Wilson | emma.wilson@example.com | Pending | |
Col7 | Liam Taylor | liam.taylor@example.com | Active |
Showing 7 results
<template>
<TableWrapper v-if="exampleTableData.length">
<Table>
<TableBody>
<TableRow v-for="(item, index) in paginatedData" :key="index">
<TableHeaderCell :scope="TableHeaderCellScope.ROW">
{{ item.colHeader }}
</TableHeaderCell>
<TableCell>{{ item.col1 }}</TableCell>
<TableCell>{{ item.col2 }}</TableCell>
<TableCell>{{ item.col3 }}</TableCell>
<TableCellActions>
<ActionIconButton
:size="ButtonSize.MD"
icon="mdi:eye-outline"
/>
</TableCellActions>
</TableRow>
</TableBody>
</Table>
<ButtonPagination
v-model:modelValue="currentPage"
v-model:items-per-page="currentItemsPerPage"
:totalItems="exampleTableData.length"
/>
</TableWrapper>
<EmptyState v-else />
</template>
<script setup lang="ts">
// States
const currentPage = ref(1)
const currentItemsPerPage = ref(10)
// Static example data
const exampleTableData = [
{ colHeader: 'Col1', col1: 'John Doe', col2: 'johndoe@example.com', col3: 'Active' },
{ colHeader: 'Col2', col1: 'Jane Smith', col2: 'janesmith@example.com', col3: 'Inactive' },
{ colHeader: 'Col3', col1: 'Alice Johnson', col2: 'alice.j@example.com', col3: 'Pending' },
{ colHeader: 'Col4', col1: 'Bob Brown', col2: 'bob.brown@example.com', col3: 'Active' },
{ colHeader: 'Col5', col1: 'Charlie Davis', col2: 'charlie.davis@example.com', col3: 'Inactive' },
{ colHeader: 'Col6', col1: 'Emma Wilson', col2: 'emma.wilson@example.com', col3: 'Pending' },
{ colHeader: 'Col7', col1: 'Liam Taylor', col2: 'liam.taylor@example.com', col3: 'Active' },
]
// Computed
const paginatedData = computed(() =>
getPaginatedData(exampleTableData, currentPage.value, currentItemsPerPage.value)
)
</script>
Checkbox row selection
Column 1 | Column 2 | Column 3 | ||
|---|---|---|---|---|
| John Doe | johndoe@example.com | Active | ||
| Jane Smith | janesmith@example.com | Inactive | ||
| Alice Johnson | alice.j@example.com | Pending | ||
| Bob Brown | bob.brown@example.com | Active | ||
| Charlie Davis | charlie.davis@example.com | Inactive | ||
| Emma Wilson | emma.wilson@example.com | Pending | ||
| Liam Taylor | liam.taylor@example.com | Active | ||
| Olivia Moore | olivia.moore@example.com | Inactive | ||
| Noah Miller | noah.miller@example.com | Active | ||
| Ava Martin | ava.martin@example.com | Pending |
Showing 1 to 10 of 22 results
<template>
<TableWrapper v-if="exampleTableData.length">
<!-- Batch Actions Form -->
<Form>
<FormRow class="gap-2">
<SelectField
id="batch-action"
v-model="selectedAction"
:options="batchActions"
placeholder="Select an action"
:disabled="selectedRows.size === 0"
selectBoxClass="!max-w-[200px]"
/>
<ActionButton
v-if="selectedRows.size > 0"
text="Apply"
:styleType="ButtonStyleType.NEUTRAL_FILLED"
@click="handleBatchAction"
/>
</FormRow>
</Form>
<!-- Table -->
<Table>
<TableHeader>
<TableHeaderCell>
<Checkbox
id="header-select-all"
:modelValue="isAllSelected"
:size="ControlFieldSize.SM"
@update:modelValue="toggleSelectAll"
/>
</TableHeaderCell>
<TableHeaderCell>Column 1</TableHeaderCell>
<TableHeaderCell>Column 2</TableHeaderCell>
<TableHeaderCell>Column 3</TableHeaderCell>
<TableHeaderCell><!-- Cell actions --></TableHeaderCell>
</TableHeader>
<TableBody>
<TableRow v-for="(item, index) in paginatedData" :key="index">
<TableCell fitToContent>
<Checkbox
:id="`row-checkbox-${index}`"
:modelValue="selectedRows.has(getItemKey(item))"
:size="ControlFieldSize.SM"
@update:modelValue="toggleRowSelection(getItemKey(item), $event)"
/>
</TableCell>
<TableCell>{{ item.col1 }}</TableCell>
<TableCell>{{ item.col2 }}</TableCell>
<TableCell>{{ item.col3 }}</TableCell>
<TableCellActions>
<ActionIconButton
:size="ButtonSize.MD"
icon="mdi:eye-outline"
/>
</TableCellActions>
</TableRow>
</TableBody>
</Table>
<ButtonPagination
v-model:modelValue="currentPage"
v-model:items-per-page="currentItemsPerPage"
:totalItems="exampleTableData.length"
/>
</TableWrapper>
<EmptyState v-else />
</template>
<script setup lang="ts">
// States
const currentPage = ref(1)
const currentItemsPerPage = ref(10)
const selectedRows = ref<Set<string>>(new Set())
const selectedAction = ref<string | null>(null)
// Batch actions
const batchActions = [
{ label: 'Archive', value: 'archive', text: 'Archive' },
{ label: 'Delete', value: 'delete', text: 'Delete' },
{ label: 'Move to folder', value: 'move', text: 'Move to folder' },
{ label: 'Change status', value: 'status', text: 'Change status' },
]
// Static example data
const exampleTableData = [
{ col1: 'John Doe', col2: 'johndoe@example.com', col3: 'Active' },
{ col1: 'Jane Smith', col2: 'janesmith@example.com', col3: 'Inactive' },
{ col1: 'Alice Johnson', col2: 'alice.j@example.com', col3: 'Pending' },
{ col1: 'Bob Brown', col2: 'bob.brown@example.com', col3: 'Active' },
{ col1: 'Charlie Davis', col2: 'charlie.davis@example.com', col3: 'Inactive' },
{ col1: 'Emma Wilson', col2: 'emma.wilson@example.com', col3: 'Pending' },
{ col1: 'Liam Taylor', col2: 'liam.taylor@example.com', col3: 'Active' },
{ col1: 'Olivia Moore', col2: 'olivia.moore@example.com', col3: 'Inactive' },
{ col1: 'Noah Miller', col2: 'noah.miller@example.com', col3: 'Active' },
{ col1: 'Ava Martin', col2: 'ava.martin@example.com', col3: 'Pending' },
{ col1: 'Sophia Clark', col2: 'sophia.clark@example.com', col3: 'Active' },
{ col1: 'Mason Lewis', col2: 'mason.lewis@example.com', col3: 'Inactive' },
{ col1: 'Isabella Young', col2: 'isabella.young@example.com', col3: 'Active' },
{ col1: 'Logan Hall', col2: 'logan.hall@example.com', col3: 'Pending' },
{ col1: 'Mia Allen', col2: 'mia.allen@example.com', col3: 'Active' },
{ col1: 'Ethan Wright', col2: 'ethan.wright@example.com', col3: 'Inactive' },
{ col1: 'Amelia Scott', col2: 'amelia.scott@example.com', col3: 'Pending' },
{ col1: 'James Green', col2: 'james.green@example.com', col3: 'Active' },
{ col1: 'Harper Adams', col2: 'harper.adams@example.com', col3: 'Inactive' },
{ col1: 'Benjamin Baker', col2: 'benjamin.baker@example.com', col3: 'Active' },
{ col1: 'Ella Perez', col2: 'ella.perez@example.com', col3: 'Pending' },
{ col1: 'Lucas Campbell', col2: 'lucas.campbell@example.com', col3: 'Active' },
]
// Computed
const paginatedData = computed(() =>
getPaginatedData(exampleTableData, currentPage.value, currentItemsPerPage.value)
)
const isAllSelected = computed(() => {
if (exampleTableData.length === 0) return false
return exampleTableData.every(item => selectedRows.value.has(getItemKey(item)))
})
// Helpers
const getItemKey = (item: any) => `${item.col1}-${item.col2}`
const toggleSelectAll = (shouldSelect: boolean) => {
if (shouldSelect) {
exampleTableData.forEach(item => {
selectedRows.value.add(getItemKey(item))
})
} else {
selectedRows.value.clear()
}
}
const toggleRowSelection = (key: string, isSelected: boolean) => {
if (isSelected) {
selectedRows.value.add(key)
} else {
selectedRows.value.delete(key)
}
}
const handleBatchAction = () => {
if (!selectedAction.value) return
const actionMap: Record<string, string> = {
archive: 'archived',
delete: 'deleted',
move: 'moved',
status: 'status changed',
}
const action = actionMap[selectedAction.value] || 'processed'
const message = `${selectedRows.value.size} item(s) ${action}`
console.log(message)
console.log('Selected action:', selectedAction.value)
console.log('Selected items:', Array.from(selectedRows.value))
// Reset after action
selectedRows.value.clear()
selectedAction.value = null
}
</script>