@citizenplane/pimp 18.1.2 → 18.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@citizenplane/pimp",
3
- "version": "18.1.2",
3
+ "version": "18.2.0",
4
4
  "scripts": {
5
5
  "dev": "storybook dev -p 8081",
6
6
  "build-storybook": "storybook build --output-dir ./docs",
@@ -71,9 +71,11 @@ const handleClick = (event: MouseEvent) => {
71
71
  transition:
72
72
  background-color 100ms ease-out,
73
73
  box-shadow 100ms ease-out,
74
- color 100ms ease-out;
74
+ color 100ms ease-out,
75
+ transform 100ms ease;
75
76
 
76
77
  &:hover:not(:disabled) {
78
+ transform: translateY(fn.px-to-rem(-1));
77
79
  background-color: var(--cp-background-primary-hover);
78
80
  color: var(--cp-text-secondary-hover);
79
81
  box-shadow:
@@ -81,6 +83,10 @@ const handleClick = (event: MouseEvent) => {
81
83
  0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft-hover);
82
84
  }
83
85
 
86
+ &:active:not(:disabled) {
87
+ transform: translateY(fn.px-to-rem(1));
88
+ }
89
+
84
90
  &:focus-visible {
85
91
  box-shadow:
86
92
  0 0 0 var(--cp-dimensions-0_25) var(--cp-border-soft-hover),
@@ -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
- <div v-if="hasPagination" class="cpTable__footer">
97
- <div class="footer__details">
98
- <p class="footer__results">
99
- <slot v-if="!isLoading" name="footer-details">
100
- <template v-if="numberOfResults">
101
- <strong>
102
- {{ paginationLabel }}
103
- </strong>
104
- <span class="footer__resultsCount"> on {{ paginationResultsDetails }}</span>
105
- </template>
106
- <template v-else> No results </template>
107
- </slot>
108
- <template v-else> Loading <cp-loader class="cpTable__loader--isSmall" :color="LoaderColor" /> </template>
109
- </p>
110
- </div>
111
- <div class="footer__pagination">
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
- if (hasRemainingPages.value) {
484
- augmentOffset()
485
- }
437
+ augmentOffset()
486
438
  emit('onNextClick')
487
439
  return
488
440
  }
489
441
 
490
- if (hasPreviousPages.value) decreaseOffset()
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 (isNextEnabled.value) pageNumber.value++
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 (isPreviousEnabled.value) pageNumber.value--
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: 3,
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.