@citizenplane/pimp 18.1.3 → 18.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CpMenu.vue.d.ts +7 -1
- package/dist/components/CpMenu.vue.d.ts.map +1 -1
- package/dist/components/CpTable.vue.d.ts +4 -5
- package/dist/components/CpTable.vue.d.ts.map +1 -1
- package/dist/components/CpTableFooter.vue.d.ts +29 -0
- package/dist/components/CpTableFooter.vue.d.ts.map +1 -0
- package/dist/components/CpTableFooterDesktop.vue.d.ts +23 -0
- package/dist/components/CpTableFooterDesktop.vue.d.ts.map +1 -0
- package/dist/components/CpTableFooterDetails.vue.d.ts +8 -0
- package/dist/components/CpTableFooterDetails.vue.d.ts.map +1 -0
- package/dist/components/CpTableFooterMobile.vue.d.ts +23 -0
- package/dist/components/CpTableFooterMobile.vue.d.ts.map +1 -0
- package/dist/pimp.es.js +5535 -5356
- package/dist/pimp.umd.js +51 -51
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CpMenu.vue +8 -1
- package/src/components/CpTable.vue +28 -143
- package/src/components/CpTableFooter.vue +143 -0
- package/src/components/CpTableFooterDesktop.vue +74 -0
- package/src/components/CpTableFooterDetails.vue +63 -0
- package/src/components/CpTableFooterMobile.vue +65 -0
- package/src/stories/CpTable.stories.ts +26 -2
package/package.json
CHANGED
|
@@ -86,6 +86,11 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
86
86
|
keepOpenOnClick: false,
|
|
87
87
|
})
|
|
88
88
|
|
|
89
|
+
const emits = defineEmits<{
|
|
90
|
+
hide: []
|
|
91
|
+
show: []
|
|
92
|
+
}>()
|
|
93
|
+
|
|
89
94
|
const MOBILE_BREAKPOINT_PX = 640
|
|
90
95
|
|
|
91
96
|
const trigger = ref<HTMLElement | null>(null)
|
|
@@ -122,11 +127,13 @@ const drawerPt = {
|
|
|
122
127
|
const show = (event: Event) => {
|
|
123
128
|
if (isDrawer.value) isOpen.value = true
|
|
124
129
|
else popover.value?.show(event, trigger.value)
|
|
130
|
+
emits('show')
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
const hide = () => {
|
|
128
134
|
if (isDrawer.value) isOpen.value = false
|
|
129
135
|
else popover.value?.hide()
|
|
136
|
+
emits('hide')
|
|
130
137
|
}
|
|
131
138
|
|
|
132
139
|
const onItemClick = () => {
|
|
@@ -167,7 +174,7 @@ defineExpose({ show, hide, toggle })
|
|
|
167
174
|
|
|
168
175
|
&__overlay {
|
|
169
176
|
position: absolute;
|
|
170
|
-
margin-top: var(--cp-spacing-
|
|
177
|
+
margin-top: var(--cp-spacing-sm-md);
|
|
171
178
|
min-width: calc(var(--cp-dimensions-1) * 62.5);
|
|
172
179
|
border-radius: var(--cp-radius-md);
|
|
173
180
|
background-color: var(--cp-background-primary);
|
|
@@ -93,26 +93,22 @@
|
|
|
93
93
|
</table>
|
|
94
94
|
<cp-table-empty-state v-if="hasNoResult" class="cpTable__emptyState" :placeholder="noResultPlaceholder" />
|
|
95
95
|
</div>
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
</
|
|
111
|
-
|
|
112
|
-
<button :disabled="!isPreviousEnabled" type="button" @click="handleNavigationClick(false)">Prev.</button>
|
|
113
|
-
<button :disabled="!isNextEnabled" type="button" @click="handleNavigationClick()">Next</button>
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
96
|
+
<cp-table-footer
|
|
97
|
+
v-if="hasPagination"
|
|
98
|
+
:is-loading="isLoading"
|
|
99
|
+
:is-server-side-pagination="isServerSidePagination"
|
|
100
|
+
:number-of-results="numberOfResults"
|
|
101
|
+
:page-number="pageNumber"
|
|
102
|
+
:pagination="pagination"
|
|
103
|
+
:rows-per-page-limit="rowsPerPageLimit"
|
|
104
|
+
:server-active-page="serverActivePage"
|
|
105
|
+
@on-next-click="handleNavigationClick()"
|
|
106
|
+
@on-previous-click="handleNavigationClick(false)"
|
|
107
|
+
>
|
|
108
|
+
<template v-if="hasFooterDetailsSlot" #footer-details>
|
|
109
|
+
<slot name="footer-details" />
|
|
110
|
+
</template>
|
|
111
|
+
</cp-table-footer>
|
|
116
112
|
<div class="cpTable__overlay" />
|
|
117
113
|
<cp-contextual-menu
|
|
118
114
|
v-if="hasRowOptions"
|
|
@@ -124,7 +120,7 @@
|
|
|
124
120
|
</template>
|
|
125
121
|
|
|
126
122
|
<script setup lang="ts">
|
|
127
|
-
import { ref, computed, useId, watch } from 'vue'
|
|
123
|
+
import { ref, computed, useId, watch, useSlots } from 'vue'
|
|
128
124
|
|
|
129
125
|
import type { MenuItemCommandEvent } from 'primevue/menuitem'
|
|
130
126
|
|
|
@@ -132,6 +128,7 @@ import { CpTableColumnObject } from '@/constants/CpTableColumn'
|
|
|
132
128
|
|
|
133
129
|
import CpContextualMenu from '@/components/CpContextualMenu.vue'
|
|
134
130
|
import CpTableEmptyState from '@/components/CpTableEmptyState.vue'
|
|
131
|
+
import CpTableFooter from '@/components/CpTableFooter.vue'
|
|
135
132
|
|
|
136
133
|
import { camelize, decamelize } from '@/helpers/string'
|
|
137
134
|
|
|
@@ -151,7 +148,6 @@ interface PaginationServer {
|
|
|
151
148
|
|
|
152
149
|
interface Pagination {
|
|
153
150
|
enabled?: boolean
|
|
154
|
-
format?: 'results' | 'pages'
|
|
155
151
|
limit?: number
|
|
156
152
|
server?: PaginationServer
|
|
157
153
|
}
|
|
@@ -202,11 +198,10 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
202
198
|
})
|
|
203
199
|
|
|
204
200
|
const emit = defineEmits<Emits>()
|
|
201
|
+
const slots = useSlots()
|
|
205
202
|
|
|
206
203
|
const FULL_WIDTH_SIZE = 1000
|
|
207
204
|
|
|
208
|
-
const LoaderColor = '#5341F9'
|
|
209
|
-
|
|
210
205
|
const uniqueId = useId()
|
|
211
206
|
const pageNumber = ref(0)
|
|
212
207
|
const cpTableContainer = ref<HTMLElement | null>(null)
|
|
@@ -225,6 +220,7 @@ const quickOptions = computed(() => {
|
|
|
225
220
|
return props.rowOptions
|
|
226
221
|
})
|
|
227
222
|
|
|
223
|
+
const hasFooterDetailsSlot = computed(() => !!slots.footerDetails)
|
|
228
224
|
const displayOptionsColumn = computed(() => props.enableRowOptions || props.enableColumnEdition)
|
|
229
225
|
|
|
230
226
|
const currentRowData = ref<Record<string, unknown>>({})
|
|
@@ -395,24 +391,7 @@ const paginationState = computed(() => {
|
|
|
395
391
|
})
|
|
396
392
|
|
|
397
393
|
const hasPagination = computed(() => paginationState.value || numberOfResults.value > VISIBLE_ROWS_MAX)
|
|
398
|
-
const paginationFormat = computed(() => {
|
|
399
|
-
if (typeof props.pagination === 'object' && props.pagination.format) return props.pagination.format
|
|
400
|
-
return 'pages'
|
|
401
|
-
})
|
|
402
|
-
const hasRemainingPages = computed(() => numberOfPages.value > activePage.value)
|
|
403
|
-
const isNextEnabled = computed(() => hasRemainingPages.value && !props.isLoading)
|
|
404
|
-
|
|
405
|
-
const hasPreviousPages = computed(() => {
|
|
406
|
-
return isServerSidePagination.value ? serverActivePage.value > 0 : pagesStartIndex.value - rowsPerPageLimit.value >= 0
|
|
407
|
-
})
|
|
408
|
-
|
|
409
|
-
const isPreviousEnabled = computed(() => hasPreviousPages.value && !props.isLoading)
|
|
410
394
|
const pagesStartIndex = computed(() => pageNumber.value * rowsPerPageLimit.value)
|
|
411
|
-
const pagesEndIndex = computed(() => rowsPerPageLimit.value * (1 + pageNumber.value))
|
|
412
|
-
const numberOfPages = computed(() => Math.ceil(numberOfResults.value / rowsPerPageLimit.value))
|
|
413
|
-
const activePage = computed(() => {
|
|
414
|
-
return isServerSidePagination.value ? serverActivePage.value + 1 : pageNumber.value + 1
|
|
415
|
-
})
|
|
416
395
|
|
|
417
396
|
const isServerSidePagination = computed(() => {
|
|
418
397
|
if (!props.pagination) return false
|
|
@@ -420,31 +399,6 @@ const isServerSidePagination = computed(() => {
|
|
|
420
399
|
})
|
|
421
400
|
|
|
422
401
|
const serverActivePage = computed(() => props.pagination?.server?.activePage || 0)
|
|
423
|
-
const serverPagesStartIndex = computed(() => serverActivePage.value * rowsPerPageLimit.value + 1)
|
|
424
|
-
const serverPagesEndIndex = computed(() => rowsPerPageLimit.value * (1 + serverActivePage.value))
|
|
425
|
-
|
|
426
|
-
const pageFirstResultIndex = computed(() => {
|
|
427
|
-
return isServerSidePagination.value ? serverPagesStartIndex.value : pagesStartIndex.value + 1
|
|
428
|
-
})
|
|
429
|
-
|
|
430
|
-
const pageLastResultIndex = computed(() => {
|
|
431
|
-
const endIndex = isServerSidePagination.value ? serverPagesEndIndex.value : pagesEndIndex.value
|
|
432
|
-
return hasRemainingPages.value ? endIndex : numberOfResults.value
|
|
433
|
-
})
|
|
434
|
-
|
|
435
|
-
const paginationLabel = computed(() => {
|
|
436
|
-
if (paginationFormat.value === 'pages') {
|
|
437
|
-
const pluralizedCount = numberOfPages.value > 1 ? 'pages' : 'page'
|
|
438
|
-
return `${activePage.value}/${numberOfPages.value} ${pluralizedCount}`
|
|
439
|
-
}
|
|
440
|
-
return `${pageFirstResultIndex.value} – ${pageLastResultIndex.value}`
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
const paginationResultsDetails = computed(() => {
|
|
444
|
-
const formattedNumberOfResults = new Intl.NumberFormat('en-US').format(numberOfResults.value)
|
|
445
|
-
const pluralizedCount = numberOfResults.value > 1 ? 'results' : 'result'
|
|
446
|
-
return `${formattedNumberOfResults} ${pluralizedCount}`
|
|
447
|
-
})
|
|
448
402
|
|
|
449
403
|
const handleContextMenu = (
|
|
450
404
|
{ rowData, rowIndex }: { rowData: Record<string, unknown>; rowIndex: number },
|
|
@@ -480,14 +434,12 @@ const handleNavigationClick = (isNext = true) => {
|
|
|
480
434
|
resetScrollPosition()
|
|
481
435
|
|
|
482
436
|
if (isNext) {
|
|
483
|
-
|
|
484
|
-
augmentOffset()
|
|
485
|
-
}
|
|
437
|
+
augmentOffset()
|
|
486
438
|
emit('onNextClick')
|
|
487
439
|
return
|
|
488
440
|
}
|
|
489
441
|
|
|
490
|
-
|
|
442
|
+
decreaseOffset()
|
|
491
443
|
emit('onPreviousClick')
|
|
492
444
|
}
|
|
493
445
|
|
|
@@ -532,11 +484,15 @@ const mapCellToColumn = ({
|
|
|
532
484
|
}
|
|
533
485
|
|
|
534
486
|
const augmentOffset = () => {
|
|
535
|
-
if (
|
|
487
|
+
if (isServerSidePagination.value) return
|
|
488
|
+
|
|
489
|
+
const numberOfPages = Math.ceil(numberOfResults.value / rowsPerPageLimit.value)
|
|
490
|
+
if (pageNumber.value + 1 < numberOfPages) pageNumber.value++
|
|
536
491
|
}
|
|
537
492
|
|
|
538
493
|
const decreaseOffset = () => {
|
|
539
|
-
if (
|
|
494
|
+
if (isServerSidePagination.value) return
|
|
495
|
+
if (pageNumber.value > 0) pageNumber.value--
|
|
540
496
|
}
|
|
541
497
|
|
|
542
498
|
const resetScrollPosition = () => {
|
|
@@ -974,76 +930,5 @@ defineExpose({ hideContextualMenu, resetPagination, currentRowData })
|
|
|
974
930
|
}
|
|
975
931
|
}
|
|
976
932
|
}
|
|
977
|
-
|
|
978
|
-
&__loader {
|
|
979
|
-
width: var(--cp-dimensions-8);
|
|
980
|
-
height: var(--cp-dimensions-8);
|
|
981
|
-
|
|
982
|
-
&--isSmall {
|
|
983
|
-
display: inline-block;
|
|
984
|
-
vertical-align: middle;
|
|
985
|
-
width: var(--cp-dimensions-6);
|
|
986
|
-
height: var(--cp-dimensions-6);
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
&__footer {
|
|
991
|
-
padding: var(--cp-spacing-xl) var(--cp-spacing-lg) 0;
|
|
992
|
-
display: flex;
|
|
993
|
-
align-items: center;
|
|
994
|
-
font-size: var(--cp-text-size-sm);
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
.footer {
|
|
998
|
-
&__details,
|
|
999
|
-
&__pagination {
|
|
1000
|
-
flex: 1;
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
&__results {
|
|
1004
|
-
font-variant-numeric: tabular-nums;
|
|
1005
|
-
color: var(--cp-text-secondary);
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
&__results strong {
|
|
1009
|
-
color: var(--cp-text-primary);
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
&__pagination {
|
|
1013
|
-
text-align: right;
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
&__pagination button {
|
|
1017
|
-
box-shadow: var(--cp-shadows-3xs);
|
|
1018
|
-
border-radius: var(--cp-radius-md-lg);
|
|
1019
|
-
border: var(--cp-dimensions-0_25) solid var(--cp-border-soft);
|
|
1020
|
-
padding: var(--cp-spacing-sm-md) var(--cp-dimensions-2_5);
|
|
1021
|
-
transition: background-color 0.15s;
|
|
1022
|
-
background-color: var(--cp-background-primary);
|
|
1023
|
-
|
|
1024
|
-
&:hover {
|
|
1025
|
-
background-color: var(--cp-background-primary-hover);
|
|
1026
|
-
border: var(--cp-dimensions-0_25) solid var(--cp-border-soft-hover);
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
&:focus {
|
|
1030
|
-
outline: none !important;
|
|
1031
|
-
box-shadow: 0 0 0 calc(var(--cp-dimensions-0_5) * 1.5) var(--cp-focus);
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
&:disabled {
|
|
1035
|
-
box-shadow: none;
|
|
1036
|
-
border-color: var(--cp-border-disabled);
|
|
1037
|
-
background-color: var(--cp-background-disabled);
|
|
1038
|
-
color: var(--cp-foreground-disabled);
|
|
1039
|
-
cursor: not-allowed;
|
|
1040
|
-
user-select: none;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
&:last-of-type {
|
|
1044
|
-
margin-left: var(--cp-spacing-md);
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
933
|
}
|
|
1049
934
|
</style>
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpTableFooter">
|
|
3
|
+
<cp-table-footer-desktop
|
|
4
|
+
class="cpTableFooter__desktop"
|
|
5
|
+
:is-next-enabled="isNextEnabled"
|
|
6
|
+
:is-previous-enabled="isPreviousEnabled"
|
|
7
|
+
@on-next-click="emit('onNextClick')"
|
|
8
|
+
@on-previous-click="emit('onPreviousClick')"
|
|
9
|
+
>
|
|
10
|
+
<template #footer-details>
|
|
11
|
+
<slot v-if="!isLoading" name="footer-details">
|
|
12
|
+
<cp-table-footer-details
|
|
13
|
+
:number-of-results="numberOfResults"
|
|
14
|
+
:page-first-result-index="pageFirstResultIndex"
|
|
15
|
+
:page-last-result-index="pageLastResultIndex"
|
|
16
|
+
/>
|
|
17
|
+
</slot>
|
|
18
|
+
<span v-else class="cpTableFooter__loader">
|
|
19
|
+
Loading
|
|
20
|
+
<cp-loader color="neutral" size="xs" />
|
|
21
|
+
</span>
|
|
22
|
+
</template>
|
|
23
|
+
</cp-table-footer-desktop>
|
|
24
|
+
|
|
25
|
+
<cp-table-footer-mobile
|
|
26
|
+
class="cpTableFooter__mobile"
|
|
27
|
+
:is-next-enabled="isNextEnabled"
|
|
28
|
+
:is-previous-enabled="isPreviousEnabled"
|
|
29
|
+
@on-next-click="emit('onNextClick')"
|
|
30
|
+
@on-previous-click="emit('onPreviousClick')"
|
|
31
|
+
>
|
|
32
|
+
<template #footer-details>
|
|
33
|
+
<slot v-if="!isLoading" name="footer-details">
|
|
34
|
+
<cp-table-footer-details
|
|
35
|
+
:number-of-results="numberOfResults"
|
|
36
|
+
:page-first-result-index="pageFirstResultIndex"
|
|
37
|
+
:page-last-result-index="pageLastResultIndex"
|
|
38
|
+
/>
|
|
39
|
+
</slot>
|
|
40
|
+
<span v-else class="cpTableFooter__loader">
|
|
41
|
+
Loading
|
|
42
|
+
<cp-loader color="neutral" size="xs" />
|
|
43
|
+
</span>
|
|
44
|
+
</template>
|
|
45
|
+
</cp-table-footer-mobile>
|
|
46
|
+
</div>
|
|
47
|
+
</template>
|
|
48
|
+
|
|
49
|
+
<script setup lang="ts">
|
|
50
|
+
import { computed } from 'vue'
|
|
51
|
+
|
|
52
|
+
import CpTableFooterDesktop from '@/components/CpTableFooterDesktop.vue'
|
|
53
|
+
import CpTableFooterDetails from '@/components/CpTableFooterDetails.vue'
|
|
54
|
+
import CpTableFooterMobile from '@/components/CpTableFooterMobile.vue'
|
|
55
|
+
|
|
56
|
+
interface Props {
|
|
57
|
+
isLoading: boolean
|
|
58
|
+
isServerSidePagination: boolean
|
|
59
|
+
numberOfResults: number
|
|
60
|
+
pageNumber: number
|
|
61
|
+
rowsPerPageLimit: number
|
|
62
|
+
serverActivePage: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface Emits {
|
|
66
|
+
(evt: 'onNextClick' | 'onPreviousClick'): void
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const props = defineProps<Props>()
|
|
70
|
+
|
|
71
|
+
const emit = defineEmits<Emits>()
|
|
72
|
+
|
|
73
|
+
const numberOfPages = computed(() => Math.ceil(props.numberOfResults / props.rowsPerPageLimit))
|
|
74
|
+
const activePage = computed(() => (props.isServerSidePagination ? props.serverActivePage + 1 : props.pageNumber + 1))
|
|
75
|
+
const hasRemainingPages = computed(() => numberOfPages.value > activePage.value)
|
|
76
|
+
const hasPreviousPages = computed(() =>
|
|
77
|
+
props.isServerSidePagination ? props.serverActivePage > 0 : props.pageNumber > 0,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
const isNextEnabled = computed(() => hasRemainingPages.value && !props.isLoading)
|
|
81
|
+
const isPreviousEnabled = computed(() => hasPreviousPages.value && !props.isLoading)
|
|
82
|
+
|
|
83
|
+
const pagesStartIndex = computed(() => props.pageNumber * props.rowsPerPageLimit)
|
|
84
|
+
const pagesEndIndex = computed(() => props.rowsPerPageLimit * (1 + props.pageNumber))
|
|
85
|
+
const serverPagesStartIndex = computed(() => props.serverActivePage * props.rowsPerPageLimit + 1)
|
|
86
|
+
const serverPagesEndIndex = computed(() => props.rowsPerPageLimit * (1 + props.serverActivePage))
|
|
87
|
+
|
|
88
|
+
const pageFirstResultIndex = computed(() =>
|
|
89
|
+
props.isServerSidePagination ? serverPagesStartIndex.value : pagesStartIndex.value + 1,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
const pageLastResultIndex = computed(() => {
|
|
93
|
+
const endIndex = props.isServerSidePagination ? serverPagesEndIndex.value : pagesEndIndex.value
|
|
94
|
+
return hasRemainingPages.value ? endIndex : props.numberOfResults
|
|
95
|
+
})
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<style scoped lang="scss">
|
|
99
|
+
.cpTableFooter {
|
|
100
|
+
position: sticky;
|
|
101
|
+
bottom: var(--cp-spacing-md);
|
|
102
|
+
z-index: 3;
|
|
103
|
+
|
|
104
|
+
&__loader {
|
|
105
|
+
display: inline-flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: var(--cp-spacing-sm);
|
|
108
|
+
color: var(--cp-text-secondary);
|
|
109
|
+
font-size: var(--cp-text-size-sm);
|
|
110
|
+
line-height: var(--cp-line-height-sm);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
&__desktop {
|
|
114
|
+
display: none;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&__mobile {
|
|
118
|
+
display: flex;
|
|
119
|
+
margin-top: var(--cp-spacing-md);
|
|
120
|
+
|
|
121
|
+
.cpTableFooter__loader {
|
|
122
|
+
font-size: var(--cp-text-size-xs);
|
|
123
|
+
line-height: var(--cp-line-height-xs);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@media (min-width: 650px) {
|
|
129
|
+
.cpTableFooter {
|
|
130
|
+
position: static;
|
|
131
|
+
z-index: auto;
|
|
132
|
+
padding: var(--cp-spacing-xl) var(--cp-spacing-lg) 0;
|
|
133
|
+
|
|
134
|
+
&__desktop {
|
|
135
|
+
display: flex;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
&__mobile {
|
|
139
|
+
display: none;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpTableFooterDesktop">
|
|
3
|
+
<div class="cpTableFooterDesktop__details">
|
|
4
|
+
<p class="cpTableFooterDesktop__results">
|
|
5
|
+
<slot name="footer-details" />
|
|
6
|
+
</p>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="cpTableFooterDesktop__pagination">
|
|
9
|
+
<cp-button
|
|
10
|
+
appearance="secondary"
|
|
11
|
+
color="neutral"
|
|
12
|
+
:disabled="!isPreviousEnabled"
|
|
13
|
+
is-square
|
|
14
|
+
size="xs"
|
|
15
|
+
@click="emit('onPreviousClick')"
|
|
16
|
+
>
|
|
17
|
+
Prev.
|
|
18
|
+
</cp-button>
|
|
19
|
+
<cp-button
|
|
20
|
+
appearance="secondary"
|
|
21
|
+
color="neutral"
|
|
22
|
+
:disabled="!isNextEnabled"
|
|
23
|
+
is-square
|
|
24
|
+
size="xs"
|
|
25
|
+
@click="emit('onNextClick')"
|
|
26
|
+
>
|
|
27
|
+
Next
|
|
28
|
+
</cp-button>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
interface Props {
|
|
35
|
+
isNextEnabled: boolean
|
|
36
|
+
isPreviousEnabled: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface Emits {
|
|
40
|
+
(evt: 'onNextClick' | 'onPreviousClick'): void
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
defineProps<Props>()
|
|
44
|
+
|
|
45
|
+
const emit = defineEmits<Emits>()
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style scoped lang="scss">
|
|
49
|
+
.cpTableFooterDesktop {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
font-size: var(--cp-text-size-sm);
|
|
53
|
+
|
|
54
|
+
&__details,
|
|
55
|
+
&__pagination {
|
|
56
|
+
flex: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&__results {
|
|
60
|
+
font-variant-numeric: tabular-nums;
|
|
61
|
+
color: var(--cp-text-secondary);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&__results strong {
|
|
65
|
+
color: var(--cp-text-primary);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&__pagination {
|
|
69
|
+
justify-content: flex-end;
|
|
70
|
+
display: flex;
|
|
71
|
+
gap: var(--cp-spacing-md);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
</style>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpTableFooterDetails">
|
|
3
|
+
<p v-if="numberOfResults" class="cpTableFooterDetails__label">
|
|
4
|
+
<span class="cpTableFooterDetails__paginationLabel">
|
|
5
|
+
{{ paginationLabel }}
|
|
6
|
+
</span>
|
|
7
|
+
<span class="cpTableFooterDetails__resultsCount"> / {{ paginationResultsDetails }}</span>
|
|
8
|
+
</p>
|
|
9
|
+
<p v-else class="cpTableFooterDetails__noResults">No results</p>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import { computed } from 'vue'
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
numberOfResults: number
|
|
18
|
+
pageFirstResultIndex: number
|
|
19
|
+
pageLastResultIndex: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const props = defineProps<Props>()
|
|
23
|
+
|
|
24
|
+
const paginationLabel = computed(() => {
|
|
25
|
+
return `${props.pageFirstResultIndex} – ${props.pageLastResultIndex}`
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const paginationResultsDetails = computed(() => {
|
|
29
|
+
const formattedNumberOfResults = new Intl.NumberFormat('en-US').format(props.numberOfResults)
|
|
30
|
+
const pluralizedCount = props.numberOfResults > 1 ? 'results' : 'result'
|
|
31
|
+
return `${formattedNumberOfResults} ${pluralizedCount}`
|
|
32
|
+
})
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<style lang="scss">
|
|
36
|
+
.cpTableFooterDetails {
|
|
37
|
+
&__paginationLabel {
|
|
38
|
+
font-weight: 600;
|
|
39
|
+
color: var(--cp-text-primary);
|
|
40
|
+
font-size: var(--cp-text-size-xs);
|
|
41
|
+
line-height: var(--cp-line-height-xs);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&__resultsCount,
|
|
45
|
+
&__noResults {
|
|
46
|
+
font-weight: 500;
|
|
47
|
+
color: var(--cp-text-secondary);
|
|
48
|
+
font-size: var(--cp-text-size-xs);
|
|
49
|
+
line-height: var(--cp-line-height-xs);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@media (min-width: 650px) {
|
|
54
|
+
.cpTableFooterDetails {
|
|
55
|
+
&__paginationLabel,
|
|
56
|
+
&__resultsCount,
|
|
57
|
+
&__noResults {
|
|
58
|
+
font-size: var(--cp-text-size-sm);
|
|
59
|
+
line-height: var(--cp-line-height-sm);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
</style>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpTableFooterMobile">
|
|
3
|
+
<cp-button
|
|
4
|
+
appearance="tertiary"
|
|
5
|
+
color="neutral"
|
|
6
|
+
:disabled="!isPreviousEnabled"
|
|
7
|
+
is-square
|
|
8
|
+
size="sm"
|
|
9
|
+
@click="emit('onPreviousClick')"
|
|
10
|
+
>
|
|
11
|
+
<template #leading-icon>
|
|
12
|
+
<cp-icon size="16" type="arrow-left" />
|
|
13
|
+
</template>
|
|
14
|
+
</cp-button>
|
|
15
|
+
<p class="cpTableFooterMobile__results">
|
|
16
|
+
<slot name="footer-details" />
|
|
17
|
+
</p>
|
|
18
|
+
<cp-button
|
|
19
|
+
appearance="tertiary"
|
|
20
|
+
color="neutral"
|
|
21
|
+
:disabled="!isNextEnabled"
|
|
22
|
+
is-square
|
|
23
|
+
size="sm"
|
|
24
|
+
@click="emit('onNextClick')"
|
|
25
|
+
>
|
|
26
|
+
<template #leading-icon>
|
|
27
|
+
<cp-icon size="16" type="arrow-right" />
|
|
28
|
+
</template>
|
|
29
|
+
</cp-button>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
interface Props {
|
|
35
|
+
isNextEnabled: boolean
|
|
36
|
+
isPreviousEnabled: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface Emits {
|
|
40
|
+
(evt: 'onNextClick' | 'onPreviousClick'): void
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
defineProps<Props>()
|
|
44
|
+
|
|
45
|
+
const emit = defineEmits<Emits>()
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<style scoped lang="scss">
|
|
49
|
+
.cpTableFooterMobile {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
justify-content: space-between;
|
|
53
|
+
gap: var(--cp-spacing-md);
|
|
54
|
+
font-size: var(--cp-text-size-sm);
|
|
55
|
+
padding: var(--cp-spacing-xs) var(--cp-spacing-xl);
|
|
56
|
+
box-shadow: var(--cp-shadows-overlay);
|
|
57
|
+
border-radius: var(--cp-radius-md);
|
|
58
|
+
background: var(--cp-background-primary);
|
|
59
|
+
z-index: 3;
|
|
60
|
+
|
|
61
|
+
.cpButton--isDisabled {
|
|
62
|
+
background: var(--cp-background-primary);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
@@ -128,14 +128,38 @@ export const Empty: Story = {
|
|
|
128
128
|
export const WithPagination: Story = {
|
|
129
129
|
args: {
|
|
130
130
|
...Default.args,
|
|
131
|
+
data: [...sampleData, ...sampleData, ...sampleData],
|
|
131
132
|
pagination: {
|
|
132
133
|
enabled: true,
|
|
133
|
-
limit:
|
|
134
|
-
format: 'pages',
|
|
134
|
+
limit: 12,
|
|
135
135
|
},
|
|
136
136
|
},
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Override the default footer details through the `#footer-details` slot.
|
|
141
|
+
*/
|
|
142
|
+
export const WithCustomFooterDetails: Story = {
|
|
143
|
+
args: {
|
|
144
|
+
...WithPagination.args,
|
|
145
|
+
},
|
|
146
|
+
render: (args: TableStoryArgs) => ({
|
|
147
|
+
components: { CpTable },
|
|
148
|
+
setup() {
|
|
149
|
+
return { args }
|
|
150
|
+
},
|
|
151
|
+
template: `
|
|
152
|
+
<CpTable v-bind="args">
|
|
153
|
+
<template #footer-details>
|
|
154
|
+
<span :style="{ color: '#334155' }">
|
|
155
|
+
Custom footer: showing paginated users list.
|
|
156
|
+
</span>
|
|
157
|
+
</template>
|
|
158
|
+
</CpTable>
|
|
159
|
+
`,
|
|
160
|
+
}),
|
|
161
|
+
}
|
|
162
|
+
|
|
139
163
|
/**
|
|
140
164
|
* Group rows by providing an array of `{ groupBy, rows }` objects. A
|
|
141
165
|
* custom `#groupBy` slot renders the section header.
|