Table
A flexible and powerful data table component that supports sorting, filtering, pagination, row selection, and customizable columns.
Basic Usage
Using cellFormatter
we can render custom content inside the table cells.
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLDataTableWrapper id="basic-table-wrapper" max-width="100%">
<HLDataTable
id="basic-table"
ref="tableInstance"
:columns="columns"
:page-size="6"
:data="data"
:horizontal-borders="false"
:vertical-borders="false"
max-height="900px"
/>
</HLDataTableWrapper>
import { HLDataTable, HLDataTableWrapper, HLProgress, HLSpace, HLIcon } from '@platform-ui/highrise'
import { Star01Icon } from '@gohighlevel/ghl-icons/24/outline'
const columns = [
{
id: 'id',
header: 'ID',
accessorKey: 'id',
size: 100,
meta: {
headerAlign: 'start',
},
},
{
id: 'firstName',
header: 'First Name',
sortingFn: 'alphanumeric',
accessorKey: 'firstName',
size: 150,
meta: {
align: 'left',
headerAlign: 'left',
},
},
{
id: 'age',
header: 'Age',
accessorKey: 'age',
sortingFn: 'alphanumeric',
meta: {
align: 'right',
headerAlign: 'right',
},
},
{
id: 'progress',
header: 'Progress',
accessorKey: 'progress',
sortingFn: 'alphanumeric',
size: 250,
cellFormatter: ({ row }) => {
return h(HLProgress, {
id: 'progress',
percentage: row.original.progress,
type: 'line',
dashboardSize: 'sm',
valuePlacement: 'outside',
})
},
meta: {
headerAlign: 'center',
},
},
{
id: 'rating',
accessorKey: 'rating',
size: 200,
header: 'Rating',
meta: {
headerAlign: 'start',
},
cellFormatter: ({ row }) => {
const rating = row.original.rating
const stars = []
for (let i = 0; i < 5; i++) {
const fill = i < rating ? 'var(--primary-600)' : 'var(--gray-400)'
stars.push(h(HLIcon, { size: 16, color: fill }, Star01Icon as any))
}
return h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, stars)
},
},
{
id: 'DOB',
accessorKey: 'DOB',
meta: {
align: 'right',
},
header: 'DOB',
},
]
const data = ref([
{
id: 1,
firstName: 'John',
lastName: 'Doe',
age: 30,
progress: 50,
rating: 4,
DOB: '2014-01-01',
},
{
id: 2,
firstName: 'Jane',
lastName: 'Doe',
age: 25,
progress: 75,
rating: 3,
DOB: '2024-01-01',
},
{
id: 3,
firstName: 'Jim',
lastName: 'Beam',
age: 40,
progress: 25,
rating: 2,
DOB: '2024-01-01',
},
])
Table with Breadcrumb and Pagination
Pagination can also be used in the table header by passing the header
slot to the HLDataTableWrapper
component. The breadcrumb can be removed if not needed.
ID | First Name | Age | Progress | Rating | DOB |
---|
<div class="h-[40vh]">
<HLDataTableWrapper id="data-table-wrapper-default" max-width="100%" :fillParentHeight="true">
<template #footer>
<div class="flex justify-between items-center">
<HLBreadcrumb size="md" :breadcrumbs="breadcrumbs" />
<HLPagination
v-if="tableInstanceBreadcrumbPagination"
id="searchable-table-pagination"
:item-count="tableInstanceBreadcrumbPagination.table.getFilteredRowModel().rows.length"
:per-page="50"
:current-page="tableInstanceBreadcrumbPagination.table?.getState().pagination.pageIndex + 1"
:pages-to-display="7"
:per-page-dropdown-options="paginationOptions"
size="sm"
per-page-text="Rows per page"
@update:per-page="updatePageSizeBreadcrumbPagination"
@update:page="
page => {
tableInstanceBreadcrumbPagination.table?.setPageIndex(page - 1)
}
"
>
<template #prev> Previous </template>
<template #next> Next </template>
</HLPagination>
</div>
</template>
<HLDataTable
id="data-table-default"
:columns="columns"
:data="data"
max-height="auto"
:page-size="50"
ref="tableInstanceBreadcrumbPagination"
>
</HLDataTable>
</HLDataTableWrapper>
</div>
const breadcrumbs = [
{
label: 'BC1',
href: 'https://google.com',
},
{
label: 'BC2',
href: 'https://google.com',
},
{
label: 'BC3',
href: 'https://google.com',
},
]
const paginationOptions = [
{
key: 50,
label: 50,
},
{
key: 100,
label: 100,
},
{
key: 200,
label: 200,
},
{
key: 300,
label: 300,
},
]
const updatePageSize = value => {
tableInstance.value.table?.setPageSize(value)
}
Responsive Columns
Make the table columns have same % of width, by setting the responsive-column-width
prop to true
.
First Name | Age | Rating | DOB |
---|
<HLDataTableWrapper id="basic-table-wrapper" max-width="800px" responsive-column-width>
<HLDataTable
id="responsive-table"
ref="tableInstance"
:columns="columnsResponsive"
:data="data"
:column-hover="true"
:horizontal-borders="false"
:vertical-borders="false"
max-height="900px"
>
</HLDataTable>
</HLDataTableWrapper>
Table with Search
Search and filter the table data by setting the show-global-search
,show-header
and prop to true
. Also, handle the @update:global-filter
event to filter the table data.
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLButton class="mb-2" @click="updateGlobalFilterForTableWithSearch('')">Clear Filter</HLButton>
<HLDataTableWrapper
id="row-expand-table-wrapper"
max-width="1000px"
:show-header="true"
:show-global-search="true"
:global-filter="filterString"
@update:global-filter="updateGlobalFilter"
>
<HLDataTable ref="tableInstance" id="table-with-search" :columns="columns" :data="data" max-height="auto" :page-size="6"> </HLDataTable>
</HLDataTableWrapper>
const filterString = ref('')
const tableInstance = ref<any>(null)
const updateGlobalFilter = globalFilter => {
filterString.value = globalFilter
tableInstance.value.table?.setGlobalFilter(globalFilter) // set global filter to the table in current rows or make API call to get the filtered data from the server
}
Column Hover
Highlight the entire column when hovering over it.
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLDataTableWrapper id="basic-table-wrapper" max-width="1000px">
<HLDataTable
id="basic-table"
ref="tableInstance"
:columns="columns"
:data="data"
:column-hover="true"
:horizontal-borders="false"
:vertical-borders="false"
max-height="900px"
>
</HLDataTable>
</HLDataTableWrapper>
Column Resize
Drag the column header to adjust the column width.
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLDataTableWrapper id="basic-table-wrapper" max-width="1000px">
<HLDataTable id="basic-table" ref="tableInstance" :columns="columns" :data="data" :column-resizing="true" max-height="900px">
</HLDataTable>
</HLDataTableWrapper>
Infinite Scroll
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLDataTableWrapper id="infinite-scroll-table-wrapper" max-width="1000px">
<HLDataTable
ref="infiniteScrollInstance"
:columns="columns"
:data="infiniteScrollData"
:loading="infiniteScrollLoading"
row-unique-id="id"
@scroll="handleScroll"
max-height="500px"
/>
</HLDataTableWrapper>
import { ref } from 'vue'
import { HLDataTableWrapper, HLDataTable } from '@platform-ui/highrise'
const infiniteScrollLoading = ref(false)
const infiniteScrollInstance = ref(null)
const infiniteScrollData = ref([]) // your initial data
const handleScroll = async event => {
const scrollPosition = event.target.scrollTop + event.target.clientHeight
if (scrollPosition >= event.target.scrollHeight - 10) {
infiniteScrollLoading.value = true
const result = await fetchMoreData() // your API call to fetch more data
infiniteScrollData.value = [...infiniteScrollData.value, ...result]
infiniteScrollInstance.value.table?.setPageSize(infiniteScrollData.value.length)
infiniteScrollLoading.value = false
}
}
const columns = [
{
id: 'id',
header: 'ID',
accessorKey: 'id',
size: 100,
cellFormatter: ({ row }) => {
return row.original.id
},
meta: {
headerAlign: 'start',
},
},
{
id: 'firstName',
header: 'First Name',
sortingFn: 'alphanumeric',
accessorKey: 'firstName',
size: 150,
meta: {
align: 'left',
headerAlign: 'start',
},
},
{
id: 'age',
header: 'Age',
accessorKey: 'age',
filterFn: 'greaterThan',
sortingFn: 'alphanumeric',
meta: {
align: 'right',
headerAlign: 'end',
},
},
{
id: 'progress',
header: 'Progress',
accessorKey: 'progress',
sortingFn: 'alphanumeric',
size: 300,
cellFormatter: ({ row }) => {
return h(HLProgress, {
id: 'progress',
percentage: row.original.progress,
type: 'line',
dashboardSize: 'sm',
valuePlacement: 'outside',
})
},
},
{
id: 'rating',
accessorKey: 'rating',
size: 200,
header: 'Rating',
meta: {
headerAlign: 'start',
},
cellFormatter: ({ row }) => {
const rating = row.original.rating
const stars = []
for (let i = 0; i < 5; i++) {
const fill = i < rating ? 'var(--primary-600)' : 'var(--gray-400)'
stars.push(h(HLIcon, { size: '16', color: fill }, Star01Icon as any))
}
return h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, { default: () => stars })
},
},
{
id: 'DOB',
accessorKey: 'DOB',
meta: {
align: 'right',
headerAlign: 'end',
},
size: 200,
header: 'DOB',
},
]
Column Re-Order
Reorder columns by dragging and dropping the column headers.
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLDataTableWrapper id="basic-table-wrapper" max-width="1000px">
<HLDataTable id="basic-table" ref="tableInstance" :columns="columns" :data="data" :column-reordering="true" max-height="900px">
</HLDataTable>
</HLDataTableWrapper>
Sticky Columns
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLDataTableWrapper id="sticky-column-table-wrapper" max-width="1000px">
<HLDataTable
ref="tableInstance"
:columns="columns"
:data="data"
:striped="true"
:horizontal-borders="true"
:vertical-borders="true"
max-height="900px"
:freezed-columns="{ left: ['id', 'firstName'] }"
>
</HLDataTable>
</HLDataTableWrapper>
Column Sort
By clicking on the column header, you can sort the data in ascending or descending order.
Id | First Name | Age | Progress | Rating | DOB |
---|
<template>
<HLDataTableWrapper id="data-table-wrapper" max-width="1000px">
<HLDataTable
id="data-table"
ref="tableInstanceColumnSort"
:columns="sortTableColumns"
:data="data"
:striped="true"
:horizontal-borders="true"
:vertical-borders="true"
max-height="900px"
row-height="40px"
:page-size="6"
header-row-height="40px"
@update:column-clicked="handleColumnClickSort"
@no-data="handleNoData"
:row-hover="true"
:column-hover="true"
>
<template #no-data>
<HLEmpty
id="empty-state"
size="md"
title="No data available to display. This is a placeholder slot"
description="This is a placeholder"
positive-text="Refresh"
negative-text="Try again"
icon="info"
/>
</template>
</HLDataTable>
</HLDataTableWrapper>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { HLDataTableWrapper, HLDataTable, HLEmpty } from '@platform-ui/highrise'
import SimpleSort from './SimpleSort.vue'
import data from './data.ts'
const sortTableSortedColumn = ref(null)
const sortTableSortDir = ref(null)
const tableInstanceColumnSort = ref<any>(null)
const sortTableColumns = [
{
id: 'id',
header: 'Id',
accessorKey: 'id',
size: 50,
meta: {
align: 'center',
},
},
{
id: 'firstName',
header: {
text: 'First Name',
icon: UserCircleIcon,
filterComponent: h(SimpleSort, {
sortDir: sortTableSortDir,
sortedColumn: sortTableSortedColumn,
columnName: 'firstName',
}),
},
sortingFn: 'alphanumeric',
accessorKey: 'firstName',
size: 150,
cellFormatter: ({ row }) => {
if (typeof row.original.firstName === 'string') {
return row.original.firstName
} else {
return h(row.original.firstName)
}
},
meta: {
align: 'left',
},
},
{
id: 'age',
header: {
text: 'Age',
icon: BarChartCircle01Icon,
filterComponent: h(SimpleSort, {
sortDir: sortTableSortDir,
sortedColumn: sortTableSortedColumn,
columnName: 'age',
}),
},
accessorKey: 'age',
sortingFn: 'alphanumeric',
meta: {
align: 'right',
headerAlign: 'right',
},
},
{
id: 'progress',
header: { text: 'Progress', icon: UserCircleIcon },
accessorKey: 'progress',
sortingFn: 'alphanumeric',
size: 250,
cellFormatter: ({ row }) => {
return h(HLProgress, {
id: 'progress',
percentage: row.original.progress,
type: 'line',
dashboardSize: 'sm',
valuePlacement: 'outside',
})
},
meta: {
headerAlign: 'left',
},
},
{
id: 'rating',
accessorKey: 'rating',
header: { text: 'Rating', icon: Star01Icon },
cellFormatter: ({ row }) => {
const rating = row.original.rating
const stars = []
for (let i = 0; i < 5; i++) {
const fill = i < rating ? 'var(--yellow-500)' : 'var(--gray-400)'
stars.push(h(HLIcon, { size: '16', color: fill }, Star01Icon))
}
return h(
HLTooltip,
{ trigger: 'hover', variant: 'dark', placement: 'top-start' },
{
trigger: () => h('div', { class: 'flex items-center gap-2 cursor-pointer' }, stars),
default: () =>
h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, () => [
h(HLIcon, { size: '20', color: 'white' }, InfoCircleIcon),
h(HLText, { size: 'sm', weight: 'semibold' }, row.original.rating),
]),
}
)
},
meta: {
headerAlign: 'left',
},
},
{
id: 'DOB',
accessorKey: 'DOB',
meta: {
align: 'right',
headerAlign: 'right',
},
header: {
text: 'DOB',
icon: CalendarIcon,
filterComponent: h(SimpleSort, {
sortDir: sortTableSortDir,
sortedColumn: sortTableSortedColumn,
columnName: 'DOB',
}),
},
},
]
const handleColumnClickSort = columnId => {
if (columnId === 'firstName' || columnId === 'DOB' || columnId === 'age') {
if (tableInstanceColumnSort.value.table.getState()?.sorting[0]?.id === columnId) {
const desc = tableInstanceColumnSort.value.table.getState()?.sorting[0]?.desc
if (desc) {
sortTableSortedColumn.value = ''
sortTableSortDir.value = null
tableInstanceColumnSort.value.table.setSorting([])
} else {
sortTableSortedColumn.value = columnId
sortTableSortDir.value = 'desc'
tableInstanceColumnSort.value.table.setSorting([{ id: columnId, desc: true }])
}
} else {
sortTableSortedColumn.value = columnId
sortTableSortDir.value = 'asc'
tableInstanceColumnSort.value.table.setSorting([{ id: columnId, desc: false }])
}
}
}
function setSortTableSort(id, desc) {
sortTableSortedColumn.value = id
sortTableSortDir.value = desc ? 'desc' : 'asc'
tableInstanceColumnSort.value.table.setSorting([{ id, desc }])
}
function resetSortTableSort() {
sortTableSortedColumn.value = null
sortTableSortDir.value = null
tableInstanceColumnSort.value.table.setSorting([])
}
</script>
<script setup lang="ts">
import { ArrowDownIcon } from '@gohighlevel/ghl-icons/24/outline'
import { Ref } from 'vue'
import { HLIcon } from '@platform-ui/highrise'
interface Props {
sortDir: Ref<'asc' | 'desc' | null>
sortedColumn: Ref<string | null>
columnName: string
}
const props = defineProps<Props>()
</script>
<template>
<HLIcon
v-if="sortedColumn.value === columnName"
size="14"
color="var(--primary-600)"
:class="[sortDir.value === 'asc' ? 'rotate-180' : '']"
>
<ArrowDownIcon />
</HLIcon>
</template>
const data = [
{
id: 1,
firstName: 'Felton',
lastName: 'Ratke',
age: 32,
rating: 2,
status: 'curatio',
progress: 59,
toggle: false,
DOB: '2025/06/28',
},
{
id: 2,
firstName: 'Marlon',
lastName: 'Graham',
age: 54,
rating: 2,
status: 'adiuvo',
progress: 32,
toggle: false,
DOB: '2024/05/01',
},
{
id: 3,
firstName: 'Woodrow',
lastName: 'Cronin',
age: 49,
rating: 1,
status: 'solio',
progress: 8,
toggle: false,
DOB: '2025/05/31',
},
{
id: 4,
firstName: 'Abe',
lastName: 'Padberg',
age: 61,
rating: 2,
status: 'trans',
progress: 41,
toggle: false,
DOB: '2024/10/26',
},
{
id: 5,
firstName: 'Stephany',
lastName: 'Berge',
age: 69,
rating: 5,
status: 'eius',
progress: 30,
toggle: true,
DOB: '2026/02/18',
},
]
Column Filter and Sort
NameFilter and AgeFilter are custom components used for filtering and sorting the data.
Id | First Name | Age | Progress | Rating | DOB |
---|
<script setup lang="ts">
import { BarChartCircle01Icon, Star01Icon, UserCircleIcon } from '@gohighlevel/ghl-icons/24/outline'
import { DataTableColumn, HLDataTable, HLDataTableWrapper, HLIcon, HLProgress, HLSpace } from '@platform-ui/highrise'
import { h, ref } from 'vue'
import NameFilter from './NameFilter.vue'
const sortedColumn = ref<string | null>(null)
const sortDir = ref<'asc' | 'desc' | null>(null)
const tableInstance = ref<any>(null)
const data = ref([]) // TODO: replace with actual data
const columns: DataTableColumn[] = [
{
id: 'id',
header: 'ID',
accessorKey: 'id',
size: 100,
meta: {
headerAlign: 'start',
},
},
{
id: 'firstName',
header: {
text: 'First Name',
icon: h(UserCircleIcon),
filterComponent: h(NameFilter, {
onSetFilter: setFilter,
onClearFilter: clearFilter,
onSetSort: setSort,
sortedColumn,
sortDir,
id: 'first-name-filter',
onResetSort: resetSort,
}),
},
sortingFn: 'alphanumeric',
accessorKey: 'firstName',
size: 150,
meta: {
align: 'start',
},
},
{
id: 'age',
header: {
text: 'Age',
icon: h(BarChartCircle01Icon),
filterComponent: h(AgeFilter, {
onSetFilter: setFilter,
onClearFilter: clearFilter,
onSetSort: setSort,
sortedColumn,
sortDir,
id: 'age-filter',
onResetSort: resetSort,
}),
},
accessorKey: 'age',
sortingFn: 'alphanumeric',
meta: {
align: 'end',
headerAlign: 'end',
},
},
{
id: 'progress',
header: 'Progress',
accessorKey: 'progress',
sortingFn: 'alphanumeric',
size: 250,
cellFormatter: ({ row }: any) => {
return h(HLProgress, {
id: 'progress',
percentage: row.original.progress,
type: 'line',
dashboardSize: 'sm',
valuePlacement: 'outside',
})
},
meta: {
headerAlign: 'center',
},
},
{
id: 'rating',
accessorKey: 'rating',
size: 200,
header: 'Rating',
meta: {
headerAlign: 'start',
},
cellFormatter: ({ row }: any) => {
const rating = row.original.rating
const stars = []
for (let i = 0; i < 5; i++) {
const fill = i < rating ? 'var(--primary-600)' : 'var(--gray-400)'
stars.push(h(HLIcon, { size: 16, color: fill }, Star01Icon as any))
}
return h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, stars)
},
},
{
id: 'DOB',
accessorKey: 'DOB',
meta: {
align: 'end',
headerAlign: 'end',
},
header: 'DOB',
},
]
function setSort(id: string, dir: 'asc' | 'desc') {
sortedColumn.value = id
sortDir.value = dir ? 'desc' : 'asc'
tableInstance.value?.table.setSorting([{ id, dir }])
}
function resetSort() {
sortedColumn.value = null
sortDir.value = null
tableInstance.value?.table.setSorting([])
}
function setFilter(id: string, filterFn: any, value: any) {
const column = tableInstance.value?.table.getColumn(id)
column.columnDef.filterFn = filterFn
column.setFilterValue(value)
}
function clearFilter(id: string) {
const column = tableInstance.value?.table.getColumn(id)
column.setFilterValue(undefined)
}
</script>
<template>
<HLDataTableWrapper id="column-filter-table-wrapper">
<HLDataTable ref="tableInstance" id="column-filter-table" :columns="columns" :data="data" />
</HLDataTableWrapper>
</template>
<script setup lang="ts">
import { CheckIcon, SearchSmIcon } from '@gohighlevel/ghl-icons/24/outline'
import {
HLAccordion,
HLAccordionItem,
HLButton,
HLCheckbox,
HLCheckboxGroup,
HLIcon,
HLInput,
HLSelect,
HLSpace,
HLText,
} from '@platform-ui/highrise'
import { nextTick, Ref, ref } from 'vue'
import FilterCompoent from './FilterComponent.vue'
const emit = defineEmits(['setSort', 'setFilter', 'clearFilter', 'resetSort'])
const filterIconColor = ref('var(--gray-400)')
const nameInputValue = ref('')
const isFilterApplied = ref(false)
defineProps<{
id: string
sortedColumn: Ref<string | null>
sortDir: Ref<'asc' | 'desc' | null>
}>()
const dropdownOptions = [
{
value: 'none',
label: 'None',
inputCount: 0,
},
{
value: 'isEmpty',
label: 'Is Empty',
inputCount: 0,
},
{
value: 'isNotEmpty',
label: 'Is Not Empty',
inputCount: 0,
},
{
value: 'contains',
label: 'Text contains',
inputCount: 1,
},
{
value: 'notContains',
label: 'Text does not contain',
inputCount: 1,
},
{
value: 'startsWith',
label: 'Text starts with',
inputCount: 1,
},
{
value: 'endsWith',
label: 'Text ends with',
inputCount: 1,
},
{
value: 'equals',
label: 'Text is exactly',
inputCount: 1,
},
]
const selectedDropdownOption = ref(dropdownOptions[0])
const handleSort = (dir: 'asc' | 'desc') => {
emit('setSort', 'firstName', dir == 'desc')
}
function handleResetSort() {
emit('resetSort', 'firstName')
}
function handleClear() {
nameInputValue.value = ''
isFilterApplied.value = false
filterIconColor.value = 'var(--gray-400)'
checkboxValue.value = []
emit('clearFilter', 'firstName')
}
function handleApply() {
if (checkboxValue.value.length) {
emit('setFilter', 'firstName', 'containsInArray', checkboxValue.value)
isFilterApplied.value = true
filterIconColor.value = 'var(--primary-600)'
return
}
if (selectedDropdownOption.value.value === 'none') {
handleClear()
return
}
isFilterApplied.value = true
filterIconColor.value = 'var(--primary-600)'
emit('setFilter', 'firstName', selectedDropdownOption.value.value, nameInputValue.value)
}
const checkboxOptions = [
{ label: '(Blank)', value: '' },
{ label: 'Mark', value: 'Mark' },
{ label: 'Jhon', value: 'Jhon' },
{ label: 'Abdul', value: 'Abdul' },
{ label: 'Victor', value: 'Victor' },
]
const checkboxValue = ref<string[]>([])
const checkboxInputValue = ref('')
const nameInputValueRef = ref<any>(null)
const updateSelectedDropdownOption = (key: string, option: any) => {
selectedDropdownOption.value = option
nextTick(() => {
if (option.inputCount) {
nameInputValueRef.value?.focus()
}
})
}
</script>
<template>
<FilterCompoent
:id="`${id}-name-filter`"
column-name="firstName"
:sorted-column="sortedColumn"
:is-filter-applied="isFilterApplied"
:sort-dir="sortDir"
@reset-sort="handleResetSort"
@set-sort="handleSort"
@clear-filter="handleClear"
@apply-click="handleApply"
>
<div class="p-2 flex flex-col gap-2">
<div
class="data-table-filter-item flex"
:sort-selected="
sortedColumn.value == 'firstName' && sortDir.value == 'asc'
"
:class="{
'sort-selected':
sortedColumn.value == 'firstName' && sortDir.value == 'asc',
}"
@click="handleSort('asc')"
>
<HLText size="md" :weight="'medium'">Sort Ascending A-Z </HLText>
<HLIcon v-if="sortedColumn.value == 'firstName' && sortDir.value == 'asc'" size="16" color="var(--primary-700)">
<CheckIcon />
</HLIcon>
</div>
<div
class="data-table-filter-item flex"
:class="{
'sort-selected':
sortedColumn.value == 'firstName' && sortDir.value == 'desc',
}"
@click="handleSort('desc')"
>
<HLText size="md" :weight="'medium'">Sort Descending Z-A </HLText>
<HLIcon v-if="sortedColumn.value == 'firstName' && sortDir.value == 'desc'" size="16" color="var(--primary-700)">
<CheckIcon />
</HLIcon>
</div>
<HLAccordion size="sm" :border="false" :zero-padding="true">
<HLAccordionItem id="1" title="Filter by Condition" name="1" size="sm" :hover-effect="false">
<div class="flex flex-col gap-1 pt-1">
<HLSelect
id="select-default"
size="xs"
:options="dropdownOptions"
:value="selectedDropdownOption.value"
:option-height="26"
@update:value="updateSelectedDropdownOption"
></HLSelect>
<HLInput
v-if="selectedDropdownOption.inputCount"
id="name-filter"
ref="nameInputValueRef"
v-model:model-value="nameInputValue"
placeholder="Filter by name"
size="xs"
clearable
@update:value="nameInputValue = $event"
/>
</div>
</HLAccordionItem>
</HLAccordion>
<HLAccordion size="sm" :border="false" :zero-padding="true" :default-expanded-names="['1']">
<HLAccordionItem id="1" title="Filter by Values" name="1" :hover-effect="false">
<div class="flex flex-col gap-2 pt-2">
<HLSpace :size="12" align="center" :wrap-item="false">
<HLButton
id="select-all-checkbox"
size="3xs"
variant="text"
color="blue"
@click="checkboxValue = checkboxOptions.map(i => i.value)"
>
Select All {{ checkboxOptions.length }}
</HLButton>
<HLButton id="select-all-checkbox" size="3xs" variant="text" color="gray" @click="checkboxValue = []"> Clear </HLButton>
<HLText size="xs" :weight="'medium'" style="margin-left: auto">Displaying {{ checkboxOptions.length }}</HLText>
</HLSpace>
<HLInput
id="name-filter"
v-model:model-value="checkboxInputValue"
:prefix-icon="SearchSmIcon as any"
placeholder="Filter by placeholder"
size="2xs"
clearable
@update:value="checkboxInputValue = $event"
/>
<HLCheckboxGroup
id="checkbox-group-1"
:value="checkboxValue"
size="xs"
style="padding: 4px"
@update:value="e => (checkboxValue = e as string[])"
>
<HLSpace :vertical="true">
<HLCheckbox
v-for="(i, index) in checkboxOptions.filter(i =>
i.label.includes(checkboxInputValue)
)"
:id="`${id}-checkbox-${index}`"
:key="index"
:value="i.value"
size="xs"
>
{{ i.label }}
</HLCheckbox>
</HLSpace>
</HLCheckboxGroup>
</div>
</HLAccordionItem>
</HLAccordion>
</div>
</FilterCompoent>
</template>
<script setup lang="ts">
import { ArrowDownIcon, ArrowUpIcon, FilterLinesIcon } from '@gohighlevel/ghl-icons/24/outline'
import { HLButton, HLIcon, HLPopover } from '@platform-ui/highrise'
import { Ref, ref } from 'vue'
const emit = defineEmits(['setSort', 'clearFilter', 'applyClick', 'resetSort'])
const filterIconColor = ref('var(--gray-400)')
const props = withDefaults(
defineProps<{
id: string
sortedColumn: Ref<string | null>
columnName: string
isFilterApplied: boolean
showSort?: boolean
sortDir: Ref<'asc' | 'desc' | null>
}>(),
{
showSort: true,
}
)
function handleClear() {
filterIconColor.value = 'var(--gray-400)'
emit('clearFilter', props.columnName)
}
function handleApply() {
filterIconColor.value = 'var(--primary-600)'
emit('applyClick', props.columnName)
}
function handleShow(show: boolean) {
if (show) {
filterIconColor.value = 'var(--primary-600)'
} else if (!props.isFilterApplied) {
filterIconColor.value = 'var(--gray-400)'
}
}
function toggleSort() {
if (props.sortDir.value == 'asc') {
emit('setSort', props.columnName, true)
} else if (props.sortDir.value == 'desc') {
emit('resetSort', props.columnName)
}
}
</script>
<template>
<div>
<HLIcon v-if="sortedColumn.value == columnName" size="14" color="var(--primary-600)" style="cursor: pointer" @click="toggleSort()">
<ArrowUpIcon v-if="sortDir.value == 'asc'" />
<ArrowDownIcon v-if="sortDir.value == 'desc'" />
</HLIcon>
<HLPopover :id="id" trigger="click" placement="bottom-end" :show-arrow="false" @show="handleShow">
<template #trigger>
<HLIcon size="14" :color="filterIconColor">
<FilterLinesIcon />
</HLIcon>
</template>
<template #default>
<slot></slot>
</template>
<template #footer>
<div class="flex gap-2 justify-end p-2">
<HLButton :id="`${id}-filter-clear`" variant="ghost" color="gray" size="3xs" @click="handleClear"> Clear </HLButton>
<HLButton :id="`${id}-filter-apply`" variant="ghost" color="blue" size="3xs" @click="handleApply"> Apply </HLButton>
</div>
</template>
</HLPopover>
</div>
</template>
Empty State
Display an empty state when the table has no data using the no-data
slot.
ID | First Name | Progress | Rating | DOB |
---|---|---|---|---|
No data available to display. This is a placeholder slot This is a placeholder |
<HLDataTableWrapper>
<HLDataTable
ref="tableInstance"
:columns="columns"
:data="data"
:striped="true"
:horizontal-borders="true"
:vertical-borders="true"
max-height="900px"
>
<template #no-data>
<HLEmpty
id="empty-state"
size="md"
title="No data available to display. This is a placeholder slot"
description="This is a placeholder"
positive-text="Refresh"
negative-text="Try again"
/>
</template>
</HLDataTable>
</HLDataTableWrapper>
import { HLDataTable, HLDataTableWrapper, HLEmpty } from '@platform-ui/highrise'
Error State
Display an error state when the table encounters an error using the no-data
slot.
ID | First Name | Progress | Rating | DOB |
---|---|---|---|---|
Something went wrong while fetching your appointments You can try again now or after 10 minutes |
<HLDataTableWrapper id="error-state-table-wrapper" max-width="1000px">
<HLDataTable
ref="tableInstance"
:columns="columns"
:data="data"
:striped="true"
:horizontal-borders="true"
:vertical-borders="true"
max-height="900px"
>
<template #no-data>
<HLEmpty
id="empty-state"
size="md"
title="No data available to display. This is a placeholder slot"
description="This is a placeholder"
positive-text="Refresh"
negative-text="Try again"
/>
</template>
</HLDataTable>
</HLDataTableWrapper>
import { HLDataTable, HLDataTableWrapper, HLEmpty } from '@platform-ui/highrise'
Highlighted Rows
Highlight specific rows in the table using the highlighted-rows
prop.
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLContentWrap>
<HLDataTableWrapper id="highlighted-rows-table-wrapper" max-width="1000px">
<HLDataTable
ref="tableInstance"
:columns="columns"
:data="data"
:horizontal-borders="true"
:vertical-borders="true"
:row-hover="false"
:column-hover="false"
max-height="900px"
:highlighted-rows="[1,5,8]"
>
</HLDataTable>
</HLDataTableWrapper>
</HLContentWrap>
Row Expand
Expand a row to show more information by handling the @table-row-clicked
event.
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLContentWrap>
<HLDataTableWrapper>
<HLDataTable
ref="tableInstance"
:columns="columns"
:data="data"
:horizontal-borders="true"
:vertical-borders="true"
:row-hover="false"
:column-hover="false"
max-height="900px"
@table-row-clicked="handleTableRowClick"
:expanded-row-renderer="ExpandedRow"
>
</HLDataTable>
</HLDataTableWrapper>
</HLContentWrap>
const columns = [
{
id: 'expand',
header: '',
cellFormatter: ({ row }) => {
return h(
HLButton,
{
variant: 'ghost',
size: '3xs',
label: 'Expand',
},
{ icon: row.getIsExpanded() ? ChevronUpIcon : ChevronDownIcon }
)
},
size: 32,
meta: {
align: 'center',
},
},
{
id: 'id',
header: 'ID',
accessorKey: 'id',
size: 100,
cell: ({ row }) => {
return row.original.id
},
},
{
id: 'firstName',
header: 'First Name',
sortingFn: 'alphanumeric',
accessorKey: 'firstName',
size: 150,
meta: {
align: 'left',
sticky: true,
},
},
]
const data = ref([
{
id: 1,
firstName: 'John',
lastName: 'Doe',
age: 30,
progress: 50,
DOB: '2014-01-01',
expandData: ['This is expanded content for John Doe'],
},
{
id: 2,
firstName: 'Jane',
lastName: 'Doe',
age: 25,
progress: 75,
DOB: '2024-01-01',
expandData: ['This is expanded content for Jane Doe'],
},
{
id: 3,
firstName: 'Jim',
lastName: 'Beam',
age: 40,
progress: 25,
DOB: '2024-01-01',
expandData: ['This is expanded content for Jim Beam'],
},
])
const handleTableRowClick = (row, rowInfo) => {
rowInfo.getToggleExpandedHandler()()
}
Row Reorder
Reorder rows by dragging and dropping them.
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLContentWrap>
<HLDataTableWrapper id="row-re-reorder-example">
<HLDataTable
id="row-re-reorder-table"
ref="tableInstance"
:columns="columns"
:data="data"
:horizontal-borders="true"
:vertical-borders="true"
max-height="900px"
:row-reordering="true"
>
</HLDataTable>
</HLDataTableWrapper>
</HLContentWrap>
Row Selection
Select rows in the table by checking and can be used when there is CTAs to perform actions on the selected rows.
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLDataTableWrapper>
<HLDataTable
ref="tableInstance"
:columns="columns"
:data="data"
:highlight-selected-row="true"
:selected-rows="selectedRows"
:row-hover="true"
max-height="900px"
row-reordering="true"
>
</HLDataTable>
</HLDataTableWrapper>
const selectedRows = ref<string[]>([])
const toggleRowSelection = (rowId: string, isChecked: boolean) => {
if (isChecked) {
selectedRows.value.push(rowId)
} else {
selectedRows.value = selectedRows.value.filter(id => id !== rowId)
}
}
const toggleAllRowsSelection = (isChecked: boolean) => {
if (isChecked) {
selectedRows.value = data.value.map(row => row.id)
} else {
selectedRows.value = []
}
}
const columns = [
{
id: 'id',
header: 'ID',
accessorKey: 'id',
size: 50,
meta: {
align: 'center',
},
},
{
id: 'checkbox',
header: ({ table }) => {
return h(HLCheckbox, {
checked: selectedRows.value.length === data.value.length,
indeterminate: selectedRows.value.length > 0 && selectedRows.value.length < data.value.length,
id: 'jkl',
onUpdateChecked: e => toggleAllRowsSelection(e),
style: {
justifyContent: 'center',
},
})
},
cellFormatter: ({ row }) => {
return h(HLCheckbox, {
checked: selectedRows.value.includes(row.original.id),
id: `select-${row.original.id}`,
onUpdateChecked: () => toggleRowSelection(row.original.id, !selectedRows.value.includes(row.original.id)),
})
},
size: 50,
meta: {
align: 'center',
},
},
{
id: 'firstName',
header: 'First Name',
sortingFn: 'alphanumeric',
accessorKey: 'firstName',
size: 150,
meta: {
align: 'left',
},
},
]
Row Striped
Add striped rows to the table to improve readability.
ID | First Name | Age | Progress | Rating | DOB |
---|
<HLDataTableWrapper>
<HLDataTable
ref="tableInstance"
:columns="columns"
:data="data"
:striped="true"
:horizontal-borders="true"
:vertical-borders="true"
max-height="900px"
>
</HLDataTable>
</HLDataTableWrapper>
Table Cell Borders
ID | First Name | Age | Progress | Rating | DOB |
---|
const data = ref([
{
id: 1,
firstName: { text: 'John', state: 'var(--success-500)' },
lastName: 'Doe',
age: 30,
progress: 50,
DOB: '2014-01-01',
},
{
id: 2,
firstName: { text: 'Jane', state: 'var(--error-500)' },
lastName: 'Doe',
age: 25,
progress: 75,
DOB: '2024-01-01',
},
{
id: 3,
firstName: { text: 'Jim', state: 'var(--warning-500)' },
lastName: 'Beam',
age: 40,
progress: 25,
DOB: '2024-01-01',
},
])
Table Cell Icon
Add an icon to the table cell by setting the prefixIcon
in data
.
Last Name | ID | First Name | Age | Progress | Rating | DOB |
---|
const data = ref([
{
id: 1,
firstName: 'John',
lastName: { text: 'Doe', prefixIcon: Wallet01Icon },
age: 30,
progress: 50,
DOB: '2014-01-01',
},
{
id: 2,
firstName: 'Jane',
lastName: { text: 'Doe', prefixIcon: Wallet02Icon },
age: 25,
progress: 75,
DOB: '2024-01-01',
},
{
id: 3,
firstName: 'Jim',
lastName: { text: 'Beam', prefixIcon: Wallet03Icon },
age: 40,
progress: 25,
DOB: '2024-01-01',
},
])
import { Wallet01Icon, Wallet02Icon, Wallet03Icon } from '@gohighlevel/ghl-icons/24/outline'
Advanced Usage of Table
Id | Toggle | First Name | Age | Progress | Radio | Rating | DOB |
---|
<div v-if="showAlert" class="hr-table-header-alerts pb-2">
<HLAlert
id="hr-table-header-alert"
closable
:title="alertTitle"
role="alert"
height="auto"
width="fit-content"
color="green"
@close="showAlert = false"
>
</HLAlert>
</div>
<HLDataTableWrapper
id="data-table-wrapper"
:column-options="columnOptions"
:page-size="pageSize"
:no-of-rows-selected="selectedRows?.length"
:layout-options="preSetLayouts"
:freezed-column="freezedColumn"
:table-height-selected-option="tableHeightSelectedOption"
:user-driven-value="userDrivenValue"
:show-global-search="true"
:show-layout-options="true"
:show-header="true"
:selected-table-layout="tableLayout"
:show-row-layout-option="true"
:show-column-layout-option="!selectedRows?.length"
:show-table-height-layout-option="!selectedRows?.length"
@update:column-reorder="handleReorder"
@update:column-checked="handleChecked"
@update:column-freeze="handleFreeze"
@update:column-unfreeze="handleUnfreeze"
@update:page-size="updateCustomPageSize"
@update:table-height-selected-option="setTableHeightSelectedOption"
@update:row-deselect-all="handleDeselectAll"
@update:global-filter="handleGlobalFilterChange"
@update:table-layout="handleSaveLayout"
@update:table-layout-as="handleSaveAsLayout"
@update:cancel="handleCancelLayout"
@update:switch-table-layout="handleSwitchLayout"
@update:delete-table-layout="handleDeleteLayout"
@update:current-layout="updateCurrentLayout"
>
<template #header-content-right>
<HLButton v-if="selectedRows?.length" id="header-button-cta" size="2xs" variant="secondary" color="gray">
Download
<template #iconLeft>
<Download01Icon />
</template>
</HLButton>
<HLButton v-if="selectedRows?.length" id="header-button-cta" size="2xs" variant="secondary" color="gray">
Share
<template #iconLeft>
<Share01Icon />
</template>
</HLButton>
<HLButton v-if="selectedRows?.length" id="header-button-cta" size="2xs" variant="secondary" color="red">
Delete
<template #iconLeft>
<Trash01Icon />
</template>
</HLButton>
<HLDropdown
v-if="selectedRows?.length"
id="header-button-cta"
width="32px"
trigger="click"
placement="bottom"
:options="[{
label: 'Download',
icon: Download01Icon,
},
{
label: 'Share',
icon: Share01Icon,
}]"
:show-arrow="false"
>
<HLButton id="header-button-cta" size="2xs" variant="text" color="gray">
<template #icon>
<DotsVerticalIcon />
</template>
</HLButton>
</HLDropdown>
<HLButton v-if="!selectedRows?.length" id="header-button-cta" size="2xs" variant="primary" color="blue"> Button CTA </HLButton>
</template>
<template v-if="selectedRows?.length" #header-content-left>
<HLText size="sm" weight="regular"> {{selectedRows?.length}} rows selected </HLText>
<HLButton id="header-button-select-all" size="2xs" variant="text" color="blue" @click="handleSelectAll">
Select All ({{data?.length}})
</HLButton>
<HLButton id="header-button-deselect-all" size="2xs" variant="ghost" @click="handleDeselectAll" style="font-size: 14px !important;">
<template #icon>
<CloseIcon />
</template>
</HLButton>
</template>
<HLDataTable
id="data-table"
ref="tableInstance"
:columns="SearchTableColumns"
:data="searchTableData"
:striped="true"
:horizontal-borders="true"
:vertical-borders="true"
max-height="900px"
row-height="40px"
header-row-height="40px"
:current-layout="currentLayout"
@update:column-checked="handleChecked"
@update:column-order="handleColumnOrder"
@update:column-clicked="handleColumnClick"
:selected-rows="selectedRows"
@no-data="handleNoData"
:column-order="columnOrder"
:freezed-columns="{left: freezedColumns}"
:row-hover="true"
:column-hover="true"
:highlight-selected-row="true"
:row-reordering="true"
:column-reordering="true"
>
<template #no-data>
<HLEmpty
id="empty-state"
size="md"
title="No data available to display. This is a placeholder slot"
description="This is a placeholder"
positive-text="Refresh"
negative-text="Try again"
icon="info"
/>
</template>
</HLDataTable>
<template #footer v-if="!noData">
<HLSpace v-if="table && tableHeightSelectedOption !== 'all'" :size="4" align="center" justify="end">
<HLPagination
id="searchable-table-pagination"
:item-count="table.getFilteredRowModel().rows?.length"
:per-page="table.getState().pagination.pageSize"
:current-page="table.getState().pagination.pageIndex + 1"
:pages-to-display="7"
:per-page-dropdown-options="paginationOptions"
size="sm"
per-page-text="Rows per page"
@update:per-page="updatePageSize"
@update:page="
page => {
table.setPageIndex(page - 1)
}
"
>
<template #prev> Previous </template>
<template #next> Next </template>
</HLPagination>
</HLSpace>
<HLSpace v-if="table && tableHeightSelectedOption === 'all'" :size="4" align="center" justify="center">
<HLButton size="2xs" variant="secondary" color="gray" @click="handleLoadMore">Load More </HLButton>
</HLSpace>
</template>
</HLDataTableWrapper>
import {
HLDataTable,
HLDataTableWrapper,
HLAlert,
HLSpace,
HLPagination,
HLButton,
HLText,
HLEmpty,
HLDropdown,
HLCheckbox,
HLProgress,
HLRadio,
HLToggle,
HLTooltip,
} from '@platform-ui/highrise'
import { Download01Icon, Share01Icon, Trash01Icon, DotsVerticalIcon } from '@gohighlevel/ghl-icons/24/outline'
const searchTableData = ref([
{
id: 1,
firstName: 'John',
lastName: 'Doe',
age: 25,
rating: 4,
status: 'Active',
progress: 75,
toggle: true,
DOB: '2024-01-01',
},
])
const sortedColumn = ref(null)
const sortDir = ref(null)
const SearchTableColumns = [
{
id: 'checkbox',
header: () => {
return h(HLCheckbox, {
checked: selectedRows.value?.length === data.value?.length,
indeterminate: selectedRows.value?.length > 0 && selectedRows.value?.length < data.value?.length,
id: 'header-checkbox',
size: 'xs',
onUpdateChecked: e => toggleAllRowsSelection(e),
style: {
justifyContent: 'center',
},
})
},
cellFormatter: ({ row }) => {
return h(HLCheckbox, {
checked: selectedRows.value.includes(row.original.id),
id: `select-${row.original.id}`,
size: 'xs',
style: {
justifyContent: 'center',
},
onUpdateChecked: () => toggleRowSelection(row.original.id, !selectedRows.value.includes(row.original.id)),
})
},
size: 32,
meta: {
align: 'center',
},
},
{
id: 'id',
header: 'Id',
accessorKey: 'id',
size: 50,
meta: {
align: 'center',
},
},
{
id: 'toggle',
accessorKey: 'toggle',
header: 'Toggle',
size: 70,
cellFormatter: ({ row }) =>
h(HLSpace, { align: 'center', wrapItem: false, justify: 'center' }, () =>
h(HLToggle, {
id: 'toggle',
'onUpdate:value': value => {
row.original.toggle = value
},
value: row.original.toggle,
})
),
meta: {
align: 'center',
},
},
{
id: 'firstName',
header: {
text: 'First Name',
icon: UserCircleIcon,
filterComponent: h(NameFilter, {
onSetFilter: setFilter,
onClearFilter: clearFilter,
onSetSort: setSort,
sortedColumn,
sortDir,
id: 'first-name-filter',
onResetSort: resetSort,
}),
},
sortingFn: 'alphanumeric',
accessorKey: 'firstName',
size: 150,
cellFormatter: ({ row }) => {
if (typeof row.original.firstName === 'string') {
return row.original.firstName
} else {
return h(row.original.firstName)
}
},
meta: {
align: 'left',
},
},
{
id: 'age',
header: {
text: 'Age',
icon: BarChartCircle01Icon,
filterComponent: h(AgeFilter, {
onSetFilter: setFilter,
onClearFilter: clearFilter,
onSetSort: setSort,
sortedColumn,
sortDir,
id: 'age-filter',
onResetSort: resetSort,
}),
},
accessorKey: 'age',
sortingFn: 'alphanumeric',
meta: {
align: 'right',
headerAlign: 'right',
},
},
{
id: 'progress',
header: { text: 'Progress', icon: UserCircleIcon },
accessorKey: 'progress',
sortingFn: 'alphanumeric',
size: 250,
cellFormatter: ({ row }) => {
return h(HLProgress, {
id: 'progress',
percentage: row.original.progress,
type: 'line',
dashboardSize: 'sm',
valuePlacement: 'outside',
})
},
meta: {
headerAlign: 'left',
},
},
{
id: 'radio',
header: 'Radio',
size: 70,
cellFormatter: ({ row }) => {
return h(HLRadio, {
id: 'radio',
value: row.original.id,
size: 'xs',
onChange: () => {
selectedRadio.value = row.original.id
},
checked: selectedRadio.value === row.original.id,
})
},
meta: {
align: 'center',
},
},
{
id: 'rating',
accessorKey: 'rating',
header: { text: 'Rating', icon: Star01Icon },
cellFormatter: ({ row }) => {
const rating = row.original.rating
const stars = []
for (let i = 0; i < 5; i++) {
const fill = i < rating ? 'var(--yellow-500)' : 'var(--gray-400)'
stars.push(h(HLIcon, { size: '16', color: fill }, Star01Icon))
}
return h(
HLTooltip,
{ trigger: 'hover', variant: 'dark', placement: 'top-start' },
{
trigger: h('div', { class: 'flex items-center gap-2 cursor-pointer' }, stars),
default: h(HLSpace, { align: 'center', wrapItem: false, size: 4 }, [
h(HLIcon, { size: '20', color: 'white' }, InfoCircleIcon),
h(HLText, { size: 'sm', weight: 'semibold' }, row.original.rating),
]),
}
)
},
meta: {
headerAlign: 'left',
},
},
{
id: 'DOB',
accessorKey: 'DOB',
meta: {
align: 'right',
headerAlign: 'right',
},
header: {
text: 'DOB',
icon: CalendarIcon,
filterComponent: h(DateFilter, {
onSetFilter: setFilter,
onClearFilter: clearFilter,
onSetSort: setSort,
sortedColumn,
sortDir,
id: 'date-filter',
onResetSort: resetSort,
}),
},
},
]
const freezedColumns = ref(['checkbox'])
const columnOptions = ref([
{
value: 'id',
label: 'Id',
checked: true,
},
{
value: 'toggle',
label: 'Toggle',
checked: true,
icon: Wallet01Icon,
},
{
value: 'firstName',
label: 'First Name',
checked: true,
icon: Wallet01Icon,
},
{
value: 'age',
label: 'Age',
checked: true,
icon: Wallet01Icon,
},
{
value: 'progress',
label: 'Progress',
checked: true,
icon: BarChartCircle01Icon,
},
{
value: 'radio',
label: 'Radio',
checked: true,
},
{
value: 'rating',
label: 'Rating',
checked: true,
icon: Star01Icon,
},
{
value: 'DOB',
label: 'DOB',
checked: true,
icon: CalendarDateIcon,
},
])
const paginationOptions = ref([
{
key: 50,
label: 50,
},
{
key: 100,
label: 100,
},
{
key: 200,
label: 200,
},
{
key: 300,
label: 300,
},
])
const preSetLayouts = ref([
{
value: 'custom',
label: 'Custom',
},
{
value: 'all',
label: 'All',
},
])
const selectedRadio = ref(null)
function handleSaveAsLayout(value) {
preSetLayouts.value.push({
value: value,
label: value,
})
showAlert.value = true
alertTitle.value = `${value} Layout saved successfully`
}
function handleSaveLayout() {
showAlert.value = true
alertTitle.value = `${tableLayout.value.label || 'Default'} Layout saved successfully`
}
let backUpLayoutColumnData = {
columnOptions: [],
freezedColumn: {},
}
let backUpLayoutTableHeight = {
tableHeightSelectedOption: '',
pageSize: '',
userDrivenValue: '',
}
function handleCancelLayout(value) {
if (previousLayout === 'columns') {
columnOptions.value = [...backUpLayoutColumnData.columnOptions]
// Handle visibility for each column
columnOptions.value.forEach(option => {
const tableColumn = tableInstance.value.table?.getColumn(option.value)
if (tableColumn?.getIsVisible() !== option.checked) {
tableColumn?.toggleVisibility(option.checked)
}
})
// Handle column order
const columnIdsInOrder = columnOptions.value.map(option => option.value)
tableInstance.value.table?.setColumnOrder(columnIdsInOrder)
// Handle frozen columns
if (backUpLayoutColumnData?.freezedColumn?.value && backUpLayoutColumnData?.freezedColumn?.value !== 'None') {
const columnIds = ['checkbox', ...columnOptions.value.map(option => option.value)]
freezedColumn.value = { ...backUpLayoutColumnData.freezedColumn }
const columnIndex = columnIds.indexOf(backUpLayoutColumnData.freezedColumn.value)
tableInstance.value.table?.setColumnPinning({
left: columnIds.slice(0, columnIndex + 1),
right: [],
})
} else {
freezedColumn.value = {
value: 'None',
label: 'None',
}
tableInstance.value.table?.setColumnPinning({
left: ['checkbox'],
right: [],
})
}
}
if (previousLayout === 'tableHeight') {
tableHeightSelectedOption.value = backUpLayoutTableHeight.tableHeightSelectedOption
pageSize.value = backUpLayoutTableHeight.pageSize
userDrivenValue.value = backUpLayoutTableHeight.userDrivenValue
if (backUpLayoutTableHeight.tableHeightSelectedOption === 'userDriven') {
tableInstance.value.table?.setPageSize(backUpLayoutTableHeight.userDrivenValue)
} else {
tableInstance.value.table?.setPageSize(pageSize.value)
}
}
}
function handleGlobalFilterChange(value) {
table.value.setGlobalFilter(String(value))
}
const handleColumnClick = columnId => {
if (columnId === 'firstName' || columnId === 'DOB' || columnId === 'age') {
if (table.value.getState()?.sorting[0]?.id === columnId) {
const desc = table.value.getState()?.sorting[0]?.desc
if (desc) {
sortedColumn.value = ''
sortDir.value = null
table.value.setSorting([])
} else {
sortedColumn.value = columnId
sortDir.value = 'desc'
table.value.setSorting([{ id: columnId, desc: true }])
}
} else {
sortedColumn.value = columnId
sortDir.value = 'asc'
table.value.setSorting([{ id: columnId, desc: false }])
}
}
}
function setSort(id, desc) {
sortedColumn.value = id
sortDir.value = desc ? 'desc' : 'asc'
table.value.setSorting([{ id, desc }])
}
function resetSort() {
sortedColumn.value = null
sortDir.value = null
table.value.setSorting([])
}
function setFilter(id, filterFn, value) {
const column = table.value.getColumn(id)
column.columnDef.filterFn = filterFn
column.setFilterValue(value)
}
function clearFilter(id) {
const column = table.value.getColumn(id)
column.setFilterValue(undefined)
}
const handleColumnOrder = columnIds => {
columnIds.shift()
columnOptions.value = columnIds.map(id => columnOptions.value.find(option => option.value === id))
}
const handleReorder = (...args) => {
columnOptions.value = args[0]
const columnIds = columnOptions.value.filter(option => option.checked !== false).map(option => option.value)
tableInstance.value.table?.setColumnOrder(columnIds)
}
const handleChecked = (...args) => {
const [field, checked] = args
const column = columnOptions.value.find(option => option.value === field)
column.checked = checked
const tableColumn = tableInstance.value.table?.getColumn(field)
tableColumn?.toggleVisibility(checked)
}
const freezedColumn = ref()
const handleFreeze = item => {
freezedColumn.value = item
const columnIds = ['checkbox', ...columnOptions.value.map(option => option.value)]
const columnIndex = columnIds.indexOf(item.value)
tableInstance.value.table?.setColumnPinning({
left: columnIds.slice(0, columnIndex + 1),
right: [],
})
}
const handleUnfreeze = () => {
columnOptions.value.forEach(option => {
option.frozen = false
})
tableInstance.value.table?.setColumnPinning({
left: ['checkbox'],
right: [],
})
}
const pageSize = ref(20)
const tableHeightSelectedOption = ref('default')
const userDrivenValue = ref(null)
const updateCustomPageSize = userDrivenNumber => {
userDrivenValue.value = userDrivenNumber
tableInstance.value.table?.setPageSize(userDrivenNumber)
// add in pagination options with ascending order
paginationOptions.value = paginationOptions.value.filter(option => option.key !== userDrivenNumber)
paginationOptions.value.push({
key: userDrivenNumber,
label: userDrivenNumber,
})
paginationOptions.value.sort((a, b) => a.key - b.key)
}
const setTableHeightSelectedOption = value => {
tableHeightSelectedOption.value = value
if (tableHeightSelectedOption.value === 'default') {
tableInstance.value.table?.setPageSize(pageSize.value)
}
}
const updatePageSize = value => {
tableHeightSelectedOption.value = 'default'
pageSize.value = Number(value)
tableInstance.value.table?.setPageSize(pageSize.value)
}
const handleLoadMore = () => {
tableInstance.value.table?.setPageSize(table.value.getState().pagination.pageSize + 50)
}
const handleDeselectAll = () => {
selectedRows.value = []
}
const handleSelectAll = () => {
selectedRows.value = data.value.map(row => row.id)
}
const currentLayout = ref('')
let previousLayout = ''
const updateCurrentLayout = value => {
if (value) previousLayout = value
currentLayout.value = value
if (value === 'columns') {
backUpLayoutColumnData = {
columnOptions: columnOptions.value.map(option => ({
...option,
icon: option.icon, // Preserve the icon reference directly
})),
freezedColumn: freezedColumn.value
? {
...freezedColumn.value,
icon: freezedColumn.value.icon, // Preserve the icon reference if it exists
}
: null,
}
} else if (value === 'tableHeight') {
backUpLayoutTableHeight = {
tableHeightSelectedOption: tableHeightSelectedOption.value,
pageSize: pageSize.value,
userDrivenValue: userDrivenValue.value,
}
}
}
const noData = ref(false)
const handleNoData = value => {
noData.value = value
}
const tableLayout = ref({
value: 'default',
label: 'Default',
})
const handleSwitchLayout = value => {
tableLayout.value = value
handleReorder(columnOptions.value.reverse())
}
const columnOrder = computed(() => ['checkbox', ...columnOptions.value.map(option => option.value)])
const handleDeleteLayout = value => {
if (value.value === tableLayout.value.value) {
tableLayout.value = {
value: 'default',
label: 'Default',
}
}
preSetLayouts.value = preSetLayouts.value.filter(option => option.value !== value.value)
showAlert.value = true
alertTitle.value = `${value.value} Layout deleted successfully`
}
const alertTitle = ref('')
const showAlert = ref(false)
Imports
import { HLDataTable, HLDataTableWrapper } from '@platform-ui/highrise'
Props
DataTable
Name | Type | Default | Description |
---|---|---|---|
id * | string | undefined | undefined | Unique identifier for the table |
columns * | DataTableColumn[] | [] | Columns (headers) |
data * | any[] | [] | Data (rows) |
maxHeight | string | '800px' | Maximum height of the table |
rowHeight | string | '40px' | Height of the row |
headerRowHeight | string | '40px' | Height of the header row |
striped | boolean | false | Applies alternating background colors to rows |
horizontalBorders | boolean | false | Show horizontal borders |
verticalBorders | boolean | false | Show vertical borders |
rowHover | boolean | true | Applies hover effect to table rows |
columnHover | boolean | true | Applies hover effect to table columns |
highlightSelectedRow | boolean | false | Highlight selected row |
highlightedRows | string[] | [] | Predefined highlighted rows |
rowReordering | boolean | false | Enable row reordering |
columnReordering | boolean | true | Enable column reordering |
freezedColumns | { left: string[], right: string[] } | { left: [], right: [] } | Pins columns to the left or right side of the table |
columnResizing | boolean | true | Enable column resizing |
selectedRows | string[] | [] | Array of selected row IDs |
currentLayout | 'rows' | 'columns' | 'tableHeight' | undefined | undefined | Current layout mode |
columnOrder | string[] | undefined | undefined | Column order |
rowClickable | boolean | false | Enable row clickable |
rowUniqueId | string | 'id' | Unique identifier for the row |
expandedRowRenderer | (row: DataTableRow) => VNodeChild | null | Renderer for expanded rows |
loading | boolean | { status: boolean; skeletonRows: number } | false | Show loading state, default skeletonRows is 10 |
pageSize | number | 20 | Number of rows per page |
tableBodyMinHeight | string | undefined | undefined | Minimum height of the table body |
DataTableWrapper
Name | Type | Default | Description |
---|---|---|---|
id * | string | undefined | undefined | Unique identifier for the table |
showHeader | boolean | false | Show header |
showLayoutOptions | boolean | false | Show layout options |
showRowLayoutOption | boolean | false | Show row layout option |
showColumnLayoutOption | boolean | false | Show column layout option |
showTableHeightLayoutOption | boolean | false | Show table height layout option |
showGlobalSearch | boolean | false | Show global search |
columnOptions | ColumnOption[] | [] | DataTableColumn options |
freezedColumn | ColumnOption | undefined | undefined | Freezed column |
layoutOptions | LayoutOption[] | [] | Layout options |
selectedTableLayout | LayoutOption | undefined | undefined | Selected table layout |
noOfRowsSelected | number | undefined | undefined | Number of rows selected |
pageSize | number | undefined | undefined | Number of rows per page |
tableHeightSelectedOption | string | 'default' | Table height selected option |
userDrivenValue | number | null | null | User driven value |
maxWidth | string | '800px' | Maximum width of the table |
responsiveColumnWidth | boolean | false | Enable responsive column width |
globalFilter | string | undefined | undefined | Global filter value |
fillParentHeight | boolean | undefined | undefined | Occupy the full height of the parent container |
Types
export interface DataTableColumn {
id: string
header:
| string
| {
text: string
icon?: VNodeChild
filterComponent?: VNodeChild
}
| VNodeChild
accessorKey?: string
size?: number
minSize?: number // min width of the column in pixels
maxSize?: number // max width of the column in pixels
filterFn?: FilterFns
sortingFn?: SortingFn
cellFormatter?: (row: DataTableRow<any>) => VNodeChild
meta?: {
align?: 'start' | 'end' | 'center' | 'space-around' | 'space-between' | 'space-evenly'
headerAlign?: 'start' | 'end' | 'center' | 'space-around' | 'space-between' | 'space-evenly'
}
}
export interface ColumnOption {
value: string
label: string
checked?: boolean
frozen?: boolean
}
export interface LayoutOption {
value: string
label: string
}
Emits
DataTable
Name | Parameters | Description |
---|---|---|
@no-data | (isEmpty: boolean) | Emitted when table has no data |
@table-row-clicked | (row: DataTableRow, rowInfo: RowInfo) | Emitted when row is clicked |
@update:column-checked | (columnId: string, checked: boolean) | Emitted when column visibility changes |
@update:column-clicked | (columnId: string) | Emitted when column is clicked |
@update:column-order | (order: string[]) | Emitted when column order changes |
DataTableWrapper
Name | Parameters | Description |
---|---|---|
@update:cancel | (layout: string) | Emitted when table layout is cancelled |
@update:column-checked | (columnId: string, checked: boolean) | Emitted when column visibility changes |
@update:column-freeze | (columnId: string) | Emitted when column is frozen |
@update:column-reorder | (columnIds: string[]) | Emitted when columns are reordered |
@update:column-unfreeze | (columnId: string) | Emitted when columns are unfrozen |
@update:current-layout | (layout: string) | Emitted when current layout changes |
@update:delete-table-layout | (layout: string) | Emitted when table layout is deleted |
@update:global-filter | (filter: string) | Emitted when global filter changes |
@update:page-size | (pageSize: number) | Emitted when page size changes |
@update:row-deselect-all | (rowIds: string[]) | Emitted when all rows are deselected |
@update:switch-table-layout | (layout: string) | Emitted when table layout is switched |
@update:table-height-selected-option | (option: string) | Emitted when table height selected option changes |
@update:table-layout-as | (layout: string) | Emitted when table layout is saved as |
@update:table-layout | (layout: string) | Emitted when table layout changes |
Slots
DataTable
Name | Description |
---|---|
no-data | When table has no data |
DataTableWrapper
Name | Description |
---|---|
header | Header |
footer | Footer |
header-content-left | Header content left (left side of the header) |
header-content-right | Header content right (right side of the header) |
default | The default content slot for table content |