@citizenplane/pimp 8.9.5 → 8.11.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/dist/pimp.es.js +5051 -4622
- package/dist/pimp.umd.js +9 -2
- package/dist/style.css +1 -1
- package/package.json +15 -16
- package/src/App.vue +9 -9
- package/src/assets/styles/base/_base.scss +4 -4
- package/src/assets/styles/helpers/_keyframes.scss +0 -25
- package/src/assets/styles/helpers/_mixins.scss +23 -2
- package/src/assets/styles/main.scss +2 -16
- package/src/assets/styles/variables/_colors.scss +2 -0
- package/src/assets/styles/variables/_sizing.scss +3 -3
- package/src/assets/styles/variables/_spacing.scss +2 -2
- package/src/components/atomic-elements/CpBadge.vue +33 -33
- package/src/components/atomic-elements/CpDialog.vue +19 -19
- package/src/components/atomic-elements/CpTooltip.vue +6 -6
- package/src/components/buttons/CpButton.vue +53 -57
- package/src/components/core/{BaseInputLabel/index.vue → BaseInputLabel.vue} +3 -3
- package/src/components/core/playground-sections/SectionAtomicElements.vue +1 -1
- package/src/components/core/playground-sections/SectionButtons.vue +2 -2
- package/src/components/core/playground-sections/SectionContainer.vue +5 -5
- package/src/components/core/playground-sections/SectionDatePickers.vue +3 -3
- package/src/components/core/playground-sections/SectionDialog.vue +1 -1
- package/src/components/core/playground-sections/SectionFeedbackIndicators.vue +2 -2
- package/src/components/core/playground-sections/SectionInputs.vue +2 -2
- package/src/components/core/playground-sections/SectionListsAndTables.vue +9 -9
- package/src/components/core/playground-sections/SectionSelects.vue +2 -2
- package/src/components/core/playground-sections/SectionSimpleInputs.vue +2 -2
- package/src/components/core/playground-sections/SectionToggles.vue +4 -4
- package/src/components/date-pickers/CpCalendar.vue +14 -14
- package/src/components/date-pickers/{CpDate/index.vue → CpDate.vue} +165 -1
- package/src/components/date-pickers/CpDatepicker.vue +1 -1
- package/src/components/feedback-indicators/CpAlert.vue +22 -22
- package/src/components/feedback-indicators/CpToaster.vue +36 -38
- package/src/components/index.js +7 -7
- package/src/components/inputs/CpInput.vue +55 -55
- package/src/components/inputs/CpTextarea.vue +20 -20
- package/src/components/lists-and-table/CpTable.vue +722 -0
- package/src/components/lists-and-table/{CpTable/CpTableEmptyState/index.vue → CpTableEmptyState.vue} +9 -9
- package/src/components/selects/CpSelect.vue +29 -28
- package/src/components/selects/{CpSelectMenu/index.vue → CpSelectMenu.vue} +40 -41
- package/src/components/toggles/{CpCheckbox/index.vue → CpCheckbox.vue} +133 -1
- package/src/components/toggles/CpRadio.vue +253 -0
- package/src/components/toggles/{CpSwitch/index.vue → CpSwitch.vue} +19 -19
- package/src/components/typography/{CpHeading/index.vue → CpHeading.vue} +26 -26
- package/src/constants/index.js +1 -0
- package/src/constants/src/CpTableConfig.js +14 -0
- package/src/libs/CoreDatepicker.vue +383 -308
- package/src/assets/styl/colors.styl +0 -39
- package/src/components/date-pickers/CpDate/index.scss +0 -165
- package/src/components/lists-and-table/CpTable/index.scss +0 -325
- package/src/components/lists-and-table/CpTable/index.vue +0 -438
- package/src/components/toggles/CpCheckbox/index.scss +0 -136
- package/src/components/toggles/CpRadio/index.scss +0 -160
- package/src/components/toggles/CpRadio/index.vue +0 -97
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="cpTable" :class="mainClasses">
|
|
3
|
+
<div
|
|
4
|
+
ref="cpTableContainer"
|
|
5
|
+
role="region"
|
|
6
|
+
:aria-labelledby="uniqueId"
|
|
7
|
+
tabindex="0"
|
|
8
|
+
class="cpTable__container"
|
|
9
|
+
:class="containerClasses"
|
|
10
|
+
>
|
|
11
|
+
<table class="cpTable__table">
|
|
12
|
+
<caption v-if="caption" :id="uniqueId" class="cpTable__caption">
|
|
13
|
+
{{
|
|
14
|
+
caption
|
|
15
|
+
}}
|
|
16
|
+
</caption>
|
|
17
|
+
<thead class="cpTable__header">
|
|
18
|
+
<tr class="cpTable__row cpTable__row--header">
|
|
19
|
+
<th
|
|
20
|
+
v-for="column in normalizedColumns"
|
|
21
|
+
:key="column.id"
|
|
22
|
+
class="cpTable__column"
|
|
23
|
+
:style="getColumnStyle(column)"
|
|
24
|
+
>
|
|
25
|
+
<slot name="column" :column="column">
|
|
26
|
+
{{ column.name }}
|
|
27
|
+
</slot>
|
|
28
|
+
</th>
|
|
29
|
+
<th v-show="enableRowOptions" class="cpTable__column cpTable__column--isOptions"><span /></th>
|
|
30
|
+
</tr>
|
|
31
|
+
</thead>
|
|
32
|
+
<tbody class="cpTable__body">
|
|
33
|
+
<tr
|
|
34
|
+
v-for="(rowData, rowIndex) in visibleRows"
|
|
35
|
+
:key="rowIndex"
|
|
36
|
+
class="cpTable__row cpTable__row--body"
|
|
37
|
+
:class="getRowClasses(rowData, rowIndex)"
|
|
38
|
+
:tabindex="getTabindex(rowData)"
|
|
39
|
+
@click="handleRowClick(rowData, rowIndex)"
|
|
40
|
+
@click.right="handleRowRightClick({ rowData, rowIndex }, $event)"
|
|
41
|
+
@keydown.enter="handleRowClick(rowData, rowIndex)"
|
|
42
|
+
>
|
|
43
|
+
<slot name="row" :row="rowData">
|
|
44
|
+
<td
|
|
45
|
+
v-for="(cellValue, cellKey, cellIndex) in rowData"
|
|
46
|
+
:key="`${cellKey}_${rowIndex}`"
|
|
47
|
+
class="cpTable__cell"
|
|
48
|
+
:class="getCellClasses(cellKey)"
|
|
49
|
+
:style="getCellStyle(cellKey, cellIndex)"
|
|
50
|
+
:colspan="getColspan(cellKey)"
|
|
51
|
+
>
|
|
52
|
+
<slot :name="cellKey" :cell="cellValue">
|
|
53
|
+
<span v-if="isFullWidthRow(rowData)">{{ cellValue }}</span>
|
|
54
|
+
<template v-else>{{ cellValue }}</template>
|
|
55
|
+
</slot>
|
|
56
|
+
</td>
|
|
57
|
+
<td v-show="areRowOptionsEnabled(rowData)" class="cpTable__cell cpTable__cell--isOptions">
|
|
58
|
+
<slot name="row-quick-actions" :row="rowData">
|
|
59
|
+
<button type="button" @click.stop="handleRowRightClick({ rowData, rowIndex }, $event)">
|
|
60
|
+
<cp-icon type="more-vertical" />
|
|
61
|
+
</button>
|
|
62
|
+
</slot>
|
|
63
|
+
</td>
|
|
64
|
+
</slot>
|
|
65
|
+
</tr>
|
|
66
|
+
</tbody>
|
|
67
|
+
</table>
|
|
68
|
+
<cp-table-empty-state v-if="hasNoResult" :placeholder="noResultPlaceholder" class="cpTable__emptyState" />
|
|
69
|
+
</div>
|
|
70
|
+
<div v-if="hasPagination" class="cpTable__footer">
|
|
71
|
+
<div class="footer__details">
|
|
72
|
+
<p class="footer__results">
|
|
73
|
+
<slot v-if="!isLoading" name="footer-details">
|
|
74
|
+
<template v-if="numberOfResults">
|
|
75
|
+
<strong>
|
|
76
|
+
{{ paginationLabel }}
|
|
77
|
+
</strong>
|
|
78
|
+
<span class="footer__resultsCount"> on {{ paginationResultsDetails }}</span>
|
|
79
|
+
</template>
|
|
80
|
+
<template v-else> No results </template>
|
|
81
|
+
</slot>
|
|
82
|
+
<template v-else> Loading <cp-loader :color="LoaderColor" class="cpTable__loader--isSmall" /> </template>
|
|
83
|
+
</p>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="footer__pagination">
|
|
86
|
+
<button :disabled="!isPreviousEnabled" type="button" @click="handleNavigationClick(false)">Prev.</button>
|
|
87
|
+
<button :disabled="!isNextEnabled" type="button" @click="handleNavigationClick()">Next</button>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="cpTable__overlay" />
|
|
91
|
+
</div>
|
|
92
|
+
</template>
|
|
93
|
+
|
|
94
|
+
<script setup>
|
|
95
|
+
import { ref, computed } from 'vue'
|
|
96
|
+
|
|
97
|
+
import { camelize, decamelize } from '@/helpers/string'
|
|
98
|
+
import { randomString } from '@/helpers'
|
|
99
|
+
|
|
100
|
+
import CpTableEmptyState from '@/components/lists-and-table/CpTableEmptyState.vue'
|
|
101
|
+
|
|
102
|
+
import { CpTableConfig } from '@/constants'
|
|
103
|
+
|
|
104
|
+
const LoaderColor = '#5341F9'
|
|
105
|
+
|
|
106
|
+
const props = defineProps({
|
|
107
|
+
caption: {
|
|
108
|
+
type: String,
|
|
109
|
+
default: '',
|
|
110
|
+
required: false,
|
|
111
|
+
},
|
|
112
|
+
columns: {
|
|
113
|
+
type: Array,
|
|
114
|
+
default: () => [],
|
|
115
|
+
required: false,
|
|
116
|
+
},
|
|
117
|
+
data: {
|
|
118
|
+
type: Array,
|
|
119
|
+
required: true,
|
|
120
|
+
},
|
|
121
|
+
pagination: {
|
|
122
|
+
type: [Boolean, Object],
|
|
123
|
+
default: false,
|
|
124
|
+
required: false,
|
|
125
|
+
validator: (value) => {
|
|
126
|
+
const isLimitValid = value.limit ? value.limit <= CpTableConfig.VISIBLE_ROWS_MAX : true
|
|
127
|
+
const isFooterDetailsFormatValid = value.format
|
|
128
|
+
? Object.values(CpTableConfig.PAGINATION_FORMATS).includes(value.format)
|
|
129
|
+
: true
|
|
130
|
+
|
|
131
|
+
return isLimitValid && isFooterDetailsFormatValid
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
areRowsClickable: {
|
|
135
|
+
type: Boolean,
|
|
136
|
+
default: false,
|
|
137
|
+
required: false,
|
|
138
|
+
},
|
|
139
|
+
emptyCellPlaceholder: {
|
|
140
|
+
type: String,
|
|
141
|
+
default: 'n/a',
|
|
142
|
+
required: false,
|
|
143
|
+
},
|
|
144
|
+
noResultPlaceholder: {
|
|
145
|
+
type: String,
|
|
146
|
+
default: 'No results found',
|
|
147
|
+
required: false,
|
|
148
|
+
},
|
|
149
|
+
isLoading: {
|
|
150
|
+
type: Boolean,
|
|
151
|
+
default: false,
|
|
152
|
+
required: false,
|
|
153
|
+
},
|
|
154
|
+
enableRowOptions: {
|
|
155
|
+
type: Boolean,
|
|
156
|
+
default: false,
|
|
157
|
+
required: false,
|
|
158
|
+
},
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
const emit = defineEmits(['onRowClick', 'onRowRightClick', 'onNextClick', 'onPreviousClick'])
|
|
162
|
+
|
|
163
|
+
const uniqueId = ref(randomString())
|
|
164
|
+
const pageNumber = ref(0)
|
|
165
|
+
const cpTableContainer = ref(null)
|
|
166
|
+
|
|
167
|
+
const containerDOMElement = computed(() => cpTableContainer.value)
|
|
168
|
+
|
|
169
|
+
const mainClasses = computed(() => ({ 'cpTable--isLoading': props.isLoading }))
|
|
170
|
+
const containerClasses = computed(() => ({ 'cpTable__container--hasPagination': hasPagination.value }))
|
|
171
|
+
|
|
172
|
+
const normalizedColumns = computed(() => {
|
|
173
|
+
if (!props.columns) return []
|
|
174
|
+
const columns = props.columns.length ? [...props.columns] : [...columnsFromRows.value]
|
|
175
|
+
|
|
176
|
+
return columns.map((column) => {
|
|
177
|
+
if (typeof column === 'string') {
|
|
178
|
+
return {
|
|
179
|
+
id: camelize(column),
|
|
180
|
+
name: decamelize(column),
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
id: column.id || camelize(column.name),
|
|
186
|
+
...column,
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const numberOfColumns = computed(() => normalizedColumns.value.length)
|
|
192
|
+
|
|
193
|
+
const hasGroupBy = computed(() => {
|
|
194
|
+
if (!props.data.length) return false
|
|
195
|
+
return props.data.some((item) => CpTableConfig.RESERVED_KEYS.GROUP_BY in item)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
const columnsFromRows = computed(() => {
|
|
199
|
+
if (!props.data.length) return []
|
|
200
|
+
const firstRow = hasGroupBy.value ? props.data[0].rows[0] : props.data[0]
|
|
201
|
+
return Object.keys(firstRow)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
const numberOfResults = computed(() => {
|
|
205
|
+
return isServerSidePagination.value ? props.pagination.server.total : flattenedRows.value.length
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const hasNoResult = computed(() => numberOfResults.value === 0)
|
|
209
|
+
const rowsPerPageLimit = computed(() => props.pagination.limit || CpTableConfig.VISIBLE_ROWS_MAX)
|
|
210
|
+
|
|
211
|
+
const flattenedRows = computed(() => {
|
|
212
|
+
if (!props.data) return []
|
|
213
|
+
if (!hasGroupBy.value) return props.data
|
|
214
|
+
|
|
215
|
+
// Transform groupBy key into a row and add it in between others
|
|
216
|
+
return props.data.reduce((flattenedRows, groupByItem) => {
|
|
217
|
+
const fullWidthRow = { [CpTableConfig.RESERVED_KEYS.FULL_WIDTH]: groupByItem.groupBy }
|
|
218
|
+
return [...flattenedRows, fullWidthRow, ...groupByItem.rows]
|
|
219
|
+
}, [])
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
const rawVisibleRows = computed(() => {
|
|
223
|
+
const dataCopy = [...flattenedRows.value]
|
|
224
|
+
if (isServerSidePagination.value) return dataCopy
|
|
225
|
+
return hasPagination.value ? dataCopy.splice(pagesStartIndex.value, rowsPerPageLimit.value) : dataCopy
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
const visibleRows = computed(() =>
|
|
229
|
+
rawVisibleRows.value.map((rawRow) => {
|
|
230
|
+
const normalizedRow = normalizeRowData({ rowPayload: rawRow })
|
|
231
|
+
return mapCellToColumn({ rowPayload: normalizedRow })
|
|
232
|
+
}),
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
const paginationState = computed(() => {
|
|
236
|
+
return typeof props.pagination === 'boolean' ? props.pagination : props.pagination.enabled
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
const hasPagination = computed(() => paginationState.value || numberOfResults.value > CpTableConfig.VISIBLE_ROWS_MAX)
|
|
240
|
+
const paginationFormat = computed(() => props.pagination?.format || CpTableConfig.PAGINATION_FORMATS.PAGES)
|
|
241
|
+
const hasRemainingPages = computed(() => numberOfPages.value > activePage.value)
|
|
242
|
+
const isNextEnabled = computed(() => hasRemainingPages.value && !props.isLoading)
|
|
243
|
+
|
|
244
|
+
const hasPreviousPages = computed(() => {
|
|
245
|
+
return isServerSidePagination.value ? serverActivePage.value > 0 : pagesStartIndex.value - rowsPerPageLimit.value >= 0
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const isPreviousEnabled = computed(() => hasPreviousPages.value && !props.isLoading)
|
|
249
|
+
const pagesStartIndex = computed(() => pageNumber.value * rowsPerPageLimit.value)
|
|
250
|
+
const pagesEndIndex = computed(() => rowsPerPageLimit.value * (1 + pageNumber.value))
|
|
251
|
+
const numberOfPages = computed(() => Math.ceil(numberOfResults.value / rowsPerPageLimit.value))
|
|
252
|
+
const activePage = computed(() => {
|
|
253
|
+
return isServerSidePagination.value ? serverActivePage.value + 1 : pageNumber.value + 1
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
const isServerSidePagination = computed(() => {
|
|
257
|
+
if (typeof props.pagination === 'boolean') return false
|
|
258
|
+
return 'server' in props.pagination
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const serverActivePage = computed(() => props.pagination.server.activePage)
|
|
262
|
+
const serverPagesStartIndex = computed(() => serverActivePage.value * rowsPerPageLimit.value + 1)
|
|
263
|
+
const serverPagesEndIndex = computed(() => rowsPerPageLimit.value * (1 + serverActivePage.value))
|
|
264
|
+
|
|
265
|
+
const pageFirstResultIndex = computed(() => {
|
|
266
|
+
return isServerSidePagination.value ? serverPagesStartIndex.value : pagesStartIndex.value + 1
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const pageLastResultIndex = computed(() => {
|
|
270
|
+
const endIndex = isServerSidePagination.value ? serverPagesEndIndex.value : pagesEndIndex.value
|
|
271
|
+
return hasRemainingPages.value ? endIndex : numberOfResults.value
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
const paginationLabel = computed(() => {
|
|
275
|
+
if (paginationFormat.value === CpTableConfig.PAGINATION_FORMATS.PAGES) {
|
|
276
|
+
const pluralizedCount = numberOfPages.value > 1 ? 'pages' : 'page'
|
|
277
|
+
return `${activePage.value}/${numberOfPages.value} ${pluralizedCount}`
|
|
278
|
+
}
|
|
279
|
+
return `${pageFirstResultIndex.value} – ${pageLastResultIndex.value}`
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
const paginationResultsDetails = computed(() => {
|
|
283
|
+
const formattedNumberOfResults = new Intl.NumberFormat('en-US').format(numberOfResults.value)
|
|
284
|
+
const pluralizedCount = numberOfResults.value > 1 ? 'results' : 'result'
|
|
285
|
+
return `${formattedNumberOfResults} ${pluralizedCount}`
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
const getRowPayload = (rowIndex) => rawVisibleRows.value[rowIndex]
|
|
289
|
+
|
|
290
|
+
const handleRowClick = (rowData, rowIndex) => {
|
|
291
|
+
if (isFullWidthRow(rowData)) return
|
|
292
|
+
|
|
293
|
+
const data = getRowPayload(rowIndex)
|
|
294
|
+
emit('onRowClick', data)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const handleRowRightClick = ({ rowData, rowIndex }, event) => {
|
|
298
|
+
if (isFullWidthRow(rowData)) return
|
|
299
|
+
|
|
300
|
+
const data = getRowPayload(rowIndex)
|
|
301
|
+
emit('onRowRightClick', { data, event })
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const handleNavigationClick = (isNext = true) => {
|
|
305
|
+
resetScrollPosition()
|
|
306
|
+
|
|
307
|
+
if (isNext) {
|
|
308
|
+
hasRemainingPages.value && augmentOffset()
|
|
309
|
+
emit('onNextClick')
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
hasPreviousPages.value && decreaseOffset()
|
|
314
|
+
emit('onPreviousClick')
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const normalizeRowData = ({ columns = normalizedColumns.value, rowPayload }) => {
|
|
318
|
+
if (!Array.isArray(rowPayload)) {
|
|
319
|
+
return { ...rowPayload }
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// from ['cell 1', 'cell 2', 'cell 3']
|
|
323
|
+
// to { column1: 'cell 1', column2: 'cell 2', column3: 'cell 3' }
|
|
324
|
+
return rowPayload.reduce((row, cell, cellIndex) => {
|
|
325
|
+
const normalizedCell = {
|
|
326
|
+
[columns[cellIndex]?.id]: cell,
|
|
327
|
+
}
|
|
328
|
+
return { ...row, ...normalizedCell }
|
|
329
|
+
}, {})
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const mapCellToColumn = ({ columns = normalizedColumns.value, rowPayload }) => {
|
|
333
|
+
if (isFullWidthRow(rowPayload)) return rowPayload
|
|
334
|
+
// Bind column id with each row key
|
|
335
|
+
// and replace row missing keys with the emptyCellPlaceholder value
|
|
336
|
+
return columns.reduce((finalRow, currentColumn) => {
|
|
337
|
+
const correspondingColumn = currentColumn?.id || currentColumn
|
|
338
|
+
const correspondingValue = rowPayload[correspondingColumn] || props.emptyCellPlaceholder
|
|
339
|
+
const cellValue = { [correspondingColumn]: correspondingValue }
|
|
340
|
+
|
|
341
|
+
return { ...finalRow, ...cellValue }
|
|
342
|
+
}, {})
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const augmentOffset = () => {
|
|
346
|
+
if (isNextEnabled.value) pageNumber.value++
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const decreaseOffset = () => {
|
|
350
|
+
if (isPreviousEnabled.value) pageNumber.value--
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const resetScrollPosition = () => (containerDOMElement.value.scrollTop = 0)
|
|
354
|
+
|
|
355
|
+
const getColumnStyle = (columnPayload) => {
|
|
356
|
+
const formattedWidth = columnPayload?.width && `${columnPayload.width}px`
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
width: formattedWidth,
|
|
360
|
+
textAlign: columnPayload.textAlign,
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const getCellStyle = (cellKey, columnIndex) => {
|
|
365
|
+
if (cellKey === CpTableConfig.RESERVED_KEYS.FULL_WIDTH) return null
|
|
366
|
+
return {
|
|
367
|
+
textAlign: normalizedColumns.value[columnIndex]?.textAlign,
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const getRowClasses = (rowData, rowIndex) => {
|
|
372
|
+
return {
|
|
373
|
+
'cpTable__row--isFullWidth': isFullWidthRow(rowData),
|
|
374
|
+
'cpTable__row--isClickable': !isFullWidthRow(rowData) && props.areRowsClickable,
|
|
375
|
+
'cpTable__row--isSelected': isRowSelected(rowIndex),
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const getCellClasses = (cellKey) => {
|
|
380
|
+
return { 'cpTable__cell--isFullWidth': cellKey === CpTableConfig.RESERVED_KEYS.FULL_WIDTH }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const getColspan = (cellKey) => {
|
|
384
|
+
const numberOfColumnsValue = props.enableRowOptions ? numberOfColumns.value + 1 : numberOfColumns.value
|
|
385
|
+
return cellKey === CpTableConfig.RESERVED_KEYS.FULL_WIDTH ? numberOfColumnsValue : null
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const getTabindex = (rowData) => (isFullWidthRow(rowData) ? -1 : 0)
|
|
389
|
+
const isFullWidthRow = (rowData) => CpTableConfig.RESERVED_KEYS.FULL_WIDTH in rowData
|
|
390
|
+
const isRowSelected = (rowIndex) => rawVisibleRows.value[rowIndex][CpTableConfig.RESERVED_KEYS.IS_SELECTED]
|
|
391
|
+
const areRowOptionsEnabled = (rowData) => props.enableRowOptions && !isFullWidthRow(rowData)
|
|
392
|
+
|
|
393
|
+
const resetPagination = () => (pageNumber.value = 0)
|
|
394
|
+
|
|
395
|
+
defineExpose({ resetPagination })
|
|
396
|
+
</script>
|
|
397
|
+
|
|
398
|
+
<style lang="scss">
|
|
399
|
+
.cpTable {
|
|
400
|
+
position: relative;
|
|
401
|
+
display: flex;
|
|
402
|
+
flex-direction: column;
|
|
403
|
+
|
|
404
|
+
&__container {
|
|
405
|
+
position: relative;
|
|
406
|
+
display: flex;
|
|
407
|
+
flex-direction: column;
|
|
408
|
+
flex: 1;
|
|
409
|
+
|
|
410
|
+
&--hasPagination {
|
|
411
|
+
border-bottom: fn.px-to-rem(1) solid colors.$border-color;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/* Standard Tables */
|
|
416
|
+
&__container[role='region'][aria-labelledby][tabindex] {
|
|
417
|
+
overflow: auto;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
&__container[role='region'][aria-labelledby][tabindex]:focus {
|
|
421
|
+
border-radius: fn.px-to-rem(10);
|
|
422
|
+
box-shadow: 0 0 0 fn.px-to-em(3) color.scale(colors.$blue, $lightness: 70%);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
&__container[role='region'][aria-labelledby][tabindex] &__table {
|
|
426
|
+
margin: 0;
|
|
427
|
+
border: none;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/* Scrolling Visual Cue */
|
|
431
|
+
&__container[role='region'][aria-labelledby][tabindex] {
|
|
432
|
+
background:
|
|
433
|
+
linear-gradient(to right, colors.$neutral-light 30%, rgba(255, 255, 255, 0)),
|
|
434
|
+
linear-gradient(to right, rgba(colors.$neutral-light, 0), colors.$neutral-light 70%) 0 100%,
|
|
435
|
+
radial-gradient(farthest-side at 0% 50%, rgba(colors.$neutral-dark, 0.2), rgba(colors.$neutral-dark, 0)),
|
|
436
|
+
radial-gradient(farthest-side at 100% 50%, rgba(colors.$neutral-dark, 0.2), rgba(colors.$neutral-dark, 0)) 0 100%;
|
|
437
|
+
background-repeat: no-repeat;
|
|
438
|
+
background-color: colors.$neutral-light;
|
|
439
|
+
background-size:
|
|
440
|
+
40px 100%,
|
|
441
|
+
40px 100%,
|
|
442
|
+
14px 100%,
|
|
443
|
+
14px 100%;
|
|
444
|
+
background-position:
|
|
445
|
+
0 0,
|
|
446
|
+
100%,
|
|
447
|
+
0 0,
|
|
448
|
+
100%;
|
|
449
|
+
background-attachment: local, local, scroll, scroll;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
&__caption {
|
|
453
|
+
position: sticky;
|
|
454
|
+
left: 0;
|
|
455
|
+
margin-bottom: sp.$space-lg;
|
|
456
|
+
text-align: left;
|
|
457
|
+
font-size: fn.px-to-em(18);
|
|
458
|
+
|
|
459
|
+
&:first-letter {
|
|
460
|
+
text-transform: capitalize;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
&__table {
|
|
465
|
+
border-collapse: collapse;
|
|
466
|
+
width: 100%;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
&__row {
|
|
470
|
+
&--body:not(:last-of-type) {
|
|
471
|
+
border-bottom: fn.px-to-rem(1) solid colors.$border-color;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
&--body:not(#{&}--isFullWidth):not(#{&}--isSelected):hover,
|
|
475
|
+
&--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus,
|
|
476
|
+
&--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus-within {
|
|
477
|
+
background-color: rgba(colors.$neutral-dark, 0.05);
|
|
478
|
+
transition: background-color 0.1s ease-in-out;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
&--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus,
|
|
482
|
+
&--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus-within {
|
|
483
|
+
color: colors.$primary-color;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
&--isFullWidth td {
|
|
487
|
+
padding: sp.$space;
|
|
488
|
+
background-color: rgba(colors.$neutral-dark, 0.03);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
&--isClickable {
|
|
492
|
+
cursor: pointer;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
&--isSelected {
|
|
496
|
+
background-color: rgba(colors.$primary-color, 0.1);
|
|
497
|
+
color: colors.$primary-color;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
&--body td {
|
|
501
|
+
font-size: fn.px-to-em(14);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
&__column {
|
|
506
|
+
position: sticky;
|
|
507
|
+
top: 0;
|
|
508
|
+
z-index: 3;
|
|
509
|
+
background-color: colors.$neutral-light;
|
|
510
|
+
padding: sp.$space sp.$space-md;
|
|
511
|
+
text-align: left;
|
|
512
|
+
white-space: nowrap;
|
|
513
|
+
font-size: fn.px-to-em(12);
|
|
514
|
+
font-weight: normal;
|
|
515
|
+
color: colors.$neutral-dark-1;
|
|
516
|
+
|
|
517
|
+
&:first-letter {
|
|
518
|
+
text-transform: capitalize;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/* Workaround for sticky header border-bottom */
|
|
522
|
+
&:after {
|
|
523
|
+
content: '';
|
|
524
|
+
position: absolute;
|
|
525
|
+
bottom: 0;
|
|
526
|
+
left: 0;
|
|
527
|
+
width: 100%;
|
|
528
|
+
height: fn.px-to-rem(1);
|
|
529
|
+
background-color: colors.$border-color;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
&__body {
|
|
534
|
+
vertical-align: middle;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
&__row--body,
|
|
538
|
+
&__cell {
|
|
539
|
+
vertical-align: inherit;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
&__cell {
|
|
543
|
+
&:not(#{&}--isFullWidth):not(#{&}--isOptions) {
|
|
544
|
+
padding: sp.$space-lg sp.$space-md;
|
|
545
|
+
white-space: nowrap;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
&--isFullWidth span,
|
|
549
|
+
&--isOptions {
|
|
550
|
+
position: sticky;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
&--isFullWidth span {
|
|
554
|
+
left: 50%;
|
|
555
|
+
transform: translateX(-50%);
|
|
556
|
+
display: inline-block;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
&--isOptions {
|
|
560
|
+
right: 0;
|
|
561
|
+
padding: 0 sp.$space-md;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
&--isOptions button {
|
|
565
|
+
border: fn.px-to-rem(1) solid colors.$border-color;
|
|
566
|
+
border-radius: fn.px-to-rem(8);
|
|
567
|
+
background-color: colors.$neutral-light;
|
|
568
|
+
display: inline-flex;
|
|
569
|
+
padding: sp.$space-sm;
|
|
570
|
+
color: colors.$neutral-dark-1;
|
|
571
|
+
|
|
572
|
+
&:hover {
|
|
573
|
+
background-color: color.scale(colors.$neutral-dark, $lightness: 98%);
|
|
574
|
+
color: colors.$neutral-dark;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
&:focus {
|
|
578
|
+
outline: none !important;
|
|
579
|
+
box-shadow: 0 0 0 fn.px-to-em(3) color.scale(colors.$secondary-color, $lightness: 80%);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
&--isOptions i {
|
|
584
|
+
width: fn.px-to-rem(16);
|
|
585
|
+
height: fn.px-to-rem(16);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// On desktop devices, display options only on row focus or hover
|
|
590
|
+
@media (hover: hover) and (pointer: fine) {
|
|
591
|
+
&__cell--isOptions {
|
|
592
|
+
opacity: 0;
|
|
593
|
+
transition: opacity 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.175);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
&__row:focus &__cell--isOptions,
|
|
597
|
+
&__row:focus-within &__cell--isOptions,
|
|
598
|
+
&__row:hover &__cell--isOptions {
|
|
599
|
+
opacity: 1;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
&__emptyState {
|
|
604
|
+
flex: 1;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
&--isLoading &__overlay {
|
|
608
|
+
opacity: 0.5;
|
|
609
|
+
pointer-events: all;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
&__overlay {
|
|
613
|
+
position: absolute;
|
|
614
|
+
overflow: hidden;
|
|
615
|
+
left: 0;
|
|
616
|
+
right: 0;
|
|
617
|
+
top: 0;
|
|
618
|
+
bottom: 0;
|
|
619
|
+
border-radius: fn.px-to-rem(10);
|
|
620
|
+
background-color: colors.$neutral-light;
|
|
621
|
+
z-index: 4;
|
|
622
|
+
opacity: 0;
|
|
623
|
+
transition: 0.15s opacity ease-in-out;
|
|
624
|
+
cursor: wait;
|
|
625
|
+
pointer-events: none;
|
|
626
|
+
|
|
627
|
+
&::after {
|
|
628
|
+
content: '';
|
|
629
|
+
position: absolute;
|
|
630
|
+
top: 0;
|
|
631
|
+
right: 0;
|
|
632
|
+
bottom: 0;
|
|
633
|
+
left: 0;
|
|
634
|
+
transform: translateX(-100%);
|
|
635
|
+
background-image: linear-gradient(
|
|
636
|
+
90deg,
|
|
637
|
+
rgba(colors.$neutral-dark-3, 0) 0,
|
|
638
|
+
rgba(colors.$neutral-dark-3, 0.2) 20%,
|
|
639
|
+
rgba(colors.$neutral-dark-3, 0.5) 60%,
|
|
640
|
+
rgba(colors.$neutral-dark-3, 0)
|
|
641
|
+
);
|
|
642
|
+
animation: shimmer 2s infinite;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
@keyframes shimmer {
|
|
646
|
+
100% {
|
|
647
|
+
transform: translateX(100%);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
&__loader {
|
|
653
|
+
width: fn.px-to-rem(32);
|
|
654
|
+
height: fn.px-to-rem(32);
|
|
655
|
+
|
|
656
|
+
&--isSmall {
|
|
657
|
+
display: inline-block;
|
|
658
|
+
vertical-align: middle;
|
|
659
|
+
width: fn.px-to-rem(24);
|
|
660
|
+
height: fn.px-to-rem(24);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
&__footer {
|
|
665
|
+
padding: sp.$space-lg sp.$space-md 0;
|
|
666
|
+
display: flex;
|
|
667
|
+
align-items: center;
|
|
668
|
+
font-size: fn.px-to-em(14);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
.footer {
|
|
672
|
+
&__details,
|
|
673
|
+
&__pagination {
|
|
674
|
+
flex: 1;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
&__results {
|
|
678
|
+
font-variant-numeric: tabular-nums;
|
|
679
|
+
color: colors.$neutral-dark-1;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
&__results strong {
|
|
683
|
+
color: colors.$neutral-dark;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
&__pagination {
|
|
687
|
+
text-align: right;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
&__pagination button {
|
|
691
|
+
box-shadow: 0 fn.px-to-rem(1) fn.px-to-rem(2) rgba(colors.$neutral-dark, 0.08);
|
|
692
|
+
border-radius: fn.px-to-rem(10);
|
|
693
|
+
border: fn.px-to-rem(1) solid colors.$border-color;
|
|
694
|
+
padding: fn.px-to-rem(6) fn.px-to-rem(10);
|
|
695
|
+
transition: background-color 0.15s;
|
|
696
|
+
background-color: colors.$neutral-light;
|
|
697
|
+
|
|
698
|
+
&:hover {
|
|
699
|
+
background-color: rgba(colors.$neutral-dark, 0.05);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
&:focus {
|
|
703
|
+
outline: none !important;
|
|
704
|
+
box-shadow: 0 0 0 fn.px-to-em(3) color.scale(colors.$blue, $lightness: 70%);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
&:disabled {
|
|
708
|
+
box-shadow: none;
|
|
709
|
+
border-color: colors.$neutral-light-1;
|
|
710
|
+
background-color: colors.$neutral-light-1;
|
|
711
|
+
color: colors.$neutral-dark-2;
|
|
712
|
+
cursor: not-allowed;
|
|
713
|
+
user-select: none;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
&:last-of-type {
|
|
717
|
+
margin-left: sp.$space;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
</style>
|