11<template >
2- <div class =" flex flex-1 flex-col" >
3- <div v-if =" pagination" class =" mb-3 flex items-center justify-end" >
4- <HoppButtonSecondary
5- outline
6- filled
7- :icon =" IconLeft"
8- :disabled =" page === 1"
9- @click =" changePage(PageDirection.Previous)"
10- />
11-
12- <span class =" flex h-full w-10 items-center justify-center" >{{
13- page
14- }}</span >
15-
16- <HoppButtonSecondary
17- outline
18- filled
19- :icon =" IconRight"
20- :disabled =" page === pagination.totalPages"
21- @click =" changePage(PageDirection.Next)"
22- />
23- </div >
24-
25- <div class =" overflow-auto rounded-md border border-dividerDark shadow-md" >
26- <!-- An Extension Slot to extend the table functionality such as search -->
27- <slot name =" extension" ></slot >
28-
29- <table class =" w-full table-fixed" >
30- <thead >
2+ <div class =" overflow-auto rounded-md border border-dividerDark shadow-md" >
3+ <!-- An Extension Slot to extend the table functionality such as search -->
4+ <slot name =" extension" ></slot >
5+
6+ <table class =" w-full table-fixed" >
7+ <thead >
8+ <tr
9+ class =" border-b border-dividerDark bg-primaryLight text-left text-sm text-secondary"
10+ >
11+ <th v-if =" selectedRows" class =" w-24" >
12+ <input
13+ ref =" selectAllCheckbox"
14+ type =" checkbox"
15+ :checked =" areAllRowsSelected"
16+ :disabled =" loading"
17+ class =" flex h-full w-full items-center justify-center"
18+ @click.stop =" toggleAllRows"
19+ />
20+ </th >
21+ <slot name =" head" >
22+ <th
23+ v-for =" th in headings"
24+ :key =" th.key"
25+ scope =" col"
26+ class =" px-6 py-3"
27+ >
28+ {{ th.label ?? th.key }}
29+ </th >
30+ </slot >
31+ </tr >
32+ </thead >
33+
34+ <tbody class =" divide-y divide-divider" >
35+ <tr v-if =" loading" >
36+ <slot name =" loading-state" >
37+ <td :colspan =" columnSpan" >
38+ <div class =" mx-auto my-3 h-5 w-5 text-center" >
39+ <HoppSmartSpinner />
40+ </div >
41+ </td >
42+ </slot >
43+ </tr >
44+
45+ <tr v-else-if =" !list.length" >
46+ <slot name =" empty-state" >
47+ <td :colspan =" columnSpan" class =" py-3 text-center" >
48+ <p >No data available</p >
49+ </td >
50+ </slot >
51+ </tr >
52+
53+ <template v-else >
3154 <tr
32- class =" border-b border-dividerDark bg-primaryLight text-left text-sm text-secondary"
55+ v-for =" (rowData, rowIndex) in workingList"
56+ :key =" rowIndex"
57+ class =" rounded-xl text-secondaryDark hover:cursor-pointer hover:bg-divider"
58+ :class =" { 'divide-x divide-divider': showYBorder }"
59+ @click =" onRowClicked(rowData)"
3360 >
34- <th v-if =" selectedRows" class = " w-24 " >
61+ <td v-if =" selectedRows" >
3562 <input
36- ref =" selectAllCheckbox"
3763 type =" checkbox"
38- :checked =" areAllRowsSelected"
39- :disabled =" loading"
64+ :checked =" isRowSelected(rowData)"
4065 class =" flex h-full w-full items-center justify-center"
41- @click.stop =" toggleAllRows "
66+ @click.stop =" toggleRow(rowData) "
4267 />
43- </th >
44- <slot name =" head " >
45- <th
46- v-for =" th in headings"
47- :key =" th .key"
48- scope = " col "
49- class = " px-6 py-3 "
68+ </td >
69+ <slot name =" body " :row = " rowData " >
70+ <td
71+ v-for =" cellHeading in headings"
72+ :key =" cellHeading .key"
73+ class = " px-4 py-2 "
74+ @click = " !cellHeading.preventClick && onRowClicked(rowData) "
5075 >
51- {{ th.label ?? th.key }}
52- </th >
53- </slot >
54- </tr >
55- </thead >
56-
57- <tbody class =" divide-y divide-divider" >
58- <tr v-if =" loading" >
59- <slot name =" loading-state" >
60- <td :colspan =" columnSpan" >
61- <div class =" mx-auto my-3 h-5 w-5 text-center" >
62- <HoppSmartSpinner />
63- </div >
64- </td >
65- </slot >
66- </tr >
67-
68- <tr v-else-if =" !list.length" >
69- <slot name =" empty-state" >
70- <td :colspan =" columnSpan" class =" py-3 text-center" >
71- <p >No data available</p >
76+ <!-- Dynamic column slot -->
77+ <slot :name =" cellHeading.key" :item =" rowData" >
78+ <!-- Generic implementation of the column -->
79+ <div class =" flex flex-col truncate" >
80+ <span class =" truncate" >
81+ {{ rowData[cellHeading.key] ?? "-" }}
82+ </span >
83+ </div >
84+ </slot >
7285 </td >
7386 </slot >
7487 </tr >
75-
76- <template v-else >
77- <tr
78- v-for =" (rowData, rowIndex) in workingList"
79- :key =" rowIndex"
80- class =" rounded-xl text-secondaryDark hover:cursor-pointer hover:bg-divider"
81- :class =" { 'divide-x divide-divider': showYBorder }"
82- @click =" onRowClicked(rowData)"
83- >
84- <td v-if =" selectedRows" >
85- <input
86- type =" checkbox"
87- :checked =" isRowSelected(rowData)"
88- class =" flex h-full w-full items-center justify-center"
89- @click.stop =" toggleRow(rowData)"
90- />
91- </td >
92- <slot name =" body" :row =" rowData" >
93- <td
94- v-for =" cellHeading in headings"
95- :key =" cellHeading.key"
96- class =" px-4 py-2"
97- @click =" !cellHeading.preventClick && onRowClicked(rowData)"
98- >
99- <!-- Dynamic column slot -->
100- <slot :name =" cellHeading.key" :item =" rowData" >
101- <!-- Generic implementation of the column -->
102- <div class =" flex flex-col truncate" >
103- <span class =" truncate" >
104- {{ rowData[cellHeading.key] ?? "-" }}
105- </span >
106- </div >
107- </slot >
108- </td >
109- </slot >
110- </tr >
111- </template >
112- </tbody >
113- </table >
114- </div >
88+ </template >
89+ </tbody >
90+ </table >
11591 </div >
11692</template >
11793
11894<script lang="ts" setup>
11995import { useVModel } from " @vueuse/core"
12096import { isEqual } from " lodash-es"
121- import { computed , ref , watch } from " vue"
122-
123- import IconLeft from " ~icons/lucide/arrow-left"
124- import IconRight from " ~icons/lucide/arrow-right"
125-
97+ import { computed , ref , watch , watchEffect } from " vue"
12698import { HoppSmartSpinner } from " .."
127- import { HoppButtonSecondary } from " ../button"
12899
129100export type CellHeading = {
130101 key: string
@@ -143,19 +114,16 @@ const props = withDefaults(
143114 /** The headings of the table */
144115 headings? : CellHeading []
145116
117+ /** Contains the rows selected */
146118 selectedRows? : Item []
119+
147120 /** Whether to enable sorting */
148121 sort? : {
149122 /** The key to sort the list by */
150123 key: string
151124 direction: Direction
152125 }
153126
154- /** Whether to enable pagination */
155- pagination? : {
156- totalPages: number
157- }
158-
159127 /** Whether to show a loading spinner */
160128 loading? : boolean
161129 }>(),
@@ -171,31 +139,8 @@ const emit = defineEmits<{
171139 (event : " onRowClicked" , item : Item ): void
172140 (event : " update:list" , list : Item []): void
173141 (event : " update:selectedRows" , selectedRows : Item []): void
174- (event : " pageNumber" , page : number ): void
175142}>()
176143
177- // Pagination functionality
178- const page = ref (1 )
179-
180- enum PageDirection {
181- Previous ,
182- Next ,
183- }
184-
185- const changePage = (direction : PageDirection ) => {
186- const isPrevious = direction === PageDirection .Previous
187-
188- const isValidPreviousAction = isPrevious && page .value > 1
189- const isValidNextAction =
190- ! isPrevious && page .value < props .pagination ! .totalPages
191-
192- if (isValidNextAction || isValidPreviousAction ) {
193- page .value += isPrevious ? - 1 : 1
194-
195- emit (" pageNumber" , page .value )
196- }
197- }
198-
199144// The working version of the list that is used to perform operations upon
200145const workingList = useVModel (props , " list" , emit )
201146
@@ -259,6 +204,14 @@ const areAllRowsSelected = computed(() => {
259204 })
260205})
261206
207+ watchEffect (() => {
208+ if (selectedRows .value ?.length === 0 ) {
209+ workingList .value .forEach ((item ) => {
210+ item .selected = false
211+ })
212+ }
213+ })
214+
262215// Sort List by key and direction which can set to ascending or descending
263216export type Direction = " ascending" | " descending"
264217
0 commit comments