@citizenplane/pimp 8.9.5 → 8.10.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.
Files changed (54) hide show
  1. package/dist/pimp.es.js +5057 -4628
  2. package/dist/pimp.umd.js +9 -2
  3. package/dist/style.css +1 -1
  4. package/package.json +15 -16
  5. package/src/App.vue +9 -9
  6. package/src/assets/styles/base/_base.scss +4 -4
  7. package/src/assets/styles/helpers/_keyframes.scss +0 -25
  8. package/src/assets/styles/helpers/_mixins.scss +23 -2
  9. package/src/assets/styles/main.scss +2 -16
  10. package/src/assets/styles/variables/_colors.scss +2 -0
  11. package/src/assets/styles/variables/_sizing.scss +3 -3
  12. package/src/assets/styles/variables/_spacing.scss +2 -2
  13. package/src/components/atomic-elements/CpBadge.vue +33 -33
  14. package/src/components/atomic-elements/CpDialog.vue +19 -19
  15. package/src/components/atomic-elements/CpTooltip.vue +6 -6
  16. package/src/components/buttons/CpButton.vue +53 -57
  17. package/src/components/core/{BaseInputLabel/index.vue → BaseInputLabel.vue} +3 -3
  18. package/src/components/core/playground-sections/SectionAtomicElements.vue +1 -1
  19. package/src/components/core/playground-sections/SectionButtons.vue +2 -2
  20. package/src/components/core/playground-sections/SectionContainer.vue +5 -5
  21. package/src/components/core/playground-sections/SectionDatePickers.vue +3 -3
  22. package/src/components/core/playground-sections/SectionDialog.vue +1 -1
  23. package/src/components/core/playground-sections/SectionFeedbackIndicators.vue +2 -2
  24. package/src/components/core/playground-sections/SectionInputs.vue +2 -2
  25. package/src/components/core/playground-sections/SectionListsAndTables.vue +9 -9
  26. package/src/components/core/playground-sections/SectionSelects.vue +2 -2
  27. package/src/components/core/playground-sections/SectionSimpleInputs.vue +2 -2
  28. package/src/components/core/playground-sections/SectionToggles.vue +4 -4
  29. package/src/components/date-pickers/CpCalendar.vue +14 -14
  30. package/src/components/date-pickers/{CpDate/index.vue → CpDate.vue} +165 -1
  31. package/src/components/date-pickers/CpDatepicker.vue +1 -1
  32. package/src/components/feedback-indicators/CpAlert.vue +22 -22
  33. package/src/components/feedback-indicators/CpToaster.vue +36 -38
  34. package/src/components/index.js +7 -7
  35. package/src/components/inputs/CpInput.vue +55 -55
  36. package/src/components/inputs/CpTextarea.vue +20 -20
  37. package/src/components/lists-and-table/CpTable.vue +718 -0
  38. package/src/components/lists-and-table/{CpTable/CpTableEmptyState/index.vue → CpTableEmptyState.vue} +9 -9
  39. package/src/components/selects/CpSelect.vue +29 -28
  40. package/src/components/selects/{CpSelectMenu/index.vue → CpSelectMenu.vue} +40 -41
  41. package/src/components/toggles/{CpCheckbox/index.vue → CpCheckbox.vue} +133 -1
  42. package/src/components/toggles/CpRadio.vue +253 -0
  43. package/src/components/toggles/{CpSwitch/index.vue → CpSwitch.vue} +19 -19
  44. package/src/components/typography/{CpHeading/index.vue → CpHeading.vue} +26 -26
  45. package/src/constants/index.js +1 -0
  46. package/src/constants/src/CpTableConfig.js +14 -0
  47. package/src/libs/CoreDatepicker.vue +383 -308
  48. package/src/assets/styl/colors.styl +0 -39
  49. package/src/components/date-pickers/CpDate/index.scss +0 -165
  50. package/src/components/lists-and-table/CpTable/index.scss +0 -325
  51. package/src/components/lists-and-table/CpTable/index.vue +0 -438
  52. package/src/components/toggles/CpCheckbox/index.scss +0 -136
  53. package/src/components/toggles/CpRadio/index.scss +0 -160
  54. package/src/components/toggles/CpRadio/index.vue +0 -97
@@ -0,0 +1,718 @@
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
+ </script>
393
+
394
+ <style lang="scss">
395
+ .cpTable {
396
+ position: relative;
397
+ display: flex;
398
+ flex-direction: column;
399
+
400
+ &__container {
401
+ position: relative;
402
+ display: flex;
403
+ flex-direction: column;
404
+ flex: 1;
405
+
406
+ &--hasPagination {
407
+ border-bottom: fn.px-to-rem(1) solid colors.$border-color;
408
+ }
409
+ }
410
+
411
+ /* Standard Tables */
412
+ &__container[role='region'][aria-labelledby][tabindex] {
413
+ overflow: auto;
414
+ }
415
+
416
+ &__container[role='region'][aria-labelledby][tabindex]:focus {
417
+ border-radius: fn.px-to-rem(10);
418
+ box-shadow: 0 0 0 fn.px-to-em(3) color.scale(colors.$blue, $lightness: 70%);
419
+ }
420
+
421
+ &__container[role='region'][aria-labelledby][tabindex] &__table {
422
+ margin: 0;
423
+ border: none;
424
+ }
425
+
426
+ /* Scrolling Visual Cue */
427
+ &__container[role='region'][aria-labelledby][tabindex] {
428
+ background:
429
+ linear-gradient(to right, colors.$neutral-light 30%, rgba(255, 255, 255, 0)),
430
+ linear-gradient(to right, rgba(colors.$neutral-light, 0), colors.$neutral-light 70%) 0 100%,
431
+ radial-gradient(farthest-side at 0% 50%, rgba(colors.$neutral-dark, 0.2), rgba(colors.$neutral-dark, 0)),
432
+ radial-gradient(farthest-side at 100% 50%, rgba(colors.$neutral-dark, 0.2), rgba(colors.$neutral-dark, 0)) 0 100%;
433
+ background-repeat: no-repeat;
434
+ background-color: colors.$neutral-light;
435
+ background-size:
436
+ 40px 100%,
437
+ 40px 100%,
438
+ 14px 100%,
439
+ 14px 100%;
440
+ background-position:
441
+ 0 0,
442
+ 100%,
443
+ 0 0,
444
+ 100%;
445
+ background-attachment: local, local, scroll, scroll;
446
+ }
447
+
448
+ &__caption {
449
+ position: sticky;
450
+ left: 0;
451
+ margin-bottom: sp.$space-lg;
452
+ text-align: left;
453
+ font-size: fn.px-to-em(18);
454
+
455
+ &:first-letter {
456
+ text-transform: capitalize;
457
+ }
458
+ }
459
+
460
+ &__table {
461
+ border-collapse: collapse;
462
+ width: 100%;
463
+ }
464
+
465
+ &__row {
466
+ &--body:not(:last-of-type) {
467
+ border-bottom: fn.px-to-rem(1) solid colors.$border-color;
468
+ }
469
+
470
+ &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):hover,
471
+ &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus,
472
+ &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus-within {
473
+ background-color: rgba(colors.$neutral-dark, 0.05);
474
+ transition: background-color 0.1s ease-in-out;
475
+ }
476
+
477
+ &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus,
478
+ &--body:not(#{&}--isFullWidth):not(#{&}--isSelected):focus-within {
479
+ color: colors.$primary-color;
480
+ }
481
+
482
+ &--isFullWidth td {
483
+ padding: sp.$space;
484
+ background-color: rgba(colors.$neutral-dark, 0.03);
485
+ }
486
+
487
+ &--isClickable {
488
+ cursor: pointer;
489
+ }
490
+
491
+ &--isSelected {
492
+ background-color: rgba(colors.$primary-color, 0.1);
493
+ color: colors.$primary-color;
494
+ }
495
+
496
+ &--body td {
497
+ font-size: fn.px-to-em(14);
498
+ }
499
+ }
500
+
501
+ &__column {
502
+ position: sticky;
503
+ top: 0;
504
+ z-index: 3;
505
+ background-color: colors.$neutral-light;
506
+ padding: sp.$space sp.$space-md;
507
+ text-align: left;
508
+ white-space: nowrap;
509
+ font-size: fn.px-to-em(12);
510
+ font-weight: normal;
511
+ color: colors.$neutral-dark-1;
512
+
513
+ &:first-letter {
514
+ text-transform: capitalize;
515
+ }
516
+
517
+ /* Workaround for sticky header border-bottom */
518
+ &:after {
519
+ content: '';
520
+ position: absolute;
521
+ bottom: 0;
522
+ left: 0;
523
+ width: 100%;
524
+ height: fn.px-to-rem(1);
525
+ background-color: colors.$border-color;
526
+ }
527
+ }
528
+
529
+ &__body {
530
+ vertical-align: middle;
531
+ }
532
+
533
+ &__row--body,
534
+ &__cell {
535
+ vertical-align: inherit;
536
+ }
537
+
538
+ &__cell {
539
+ &:not(#{&}--isFullWidth):not(#{&}--isOptions) {
540
+ padding: sp.$space-lg sp.$space-md;
541
+ white-space: nowrap;
542
+ }
543
+
544
+ &--isFullWidth span,
545
+ &--isOptions {
546
+ position: sticky;
547
+ }
548
+
549
+ &--isFullWidth span {
550
+ left: 50%;
551
+ transform: translateX(-50%);
552
+ display: inline-block;
553
+ }
554
+
555
+ &--isOptions {
556
+ right: 0;
557
+ padding: 0 sp.$space-md;
558
+ }
559
+
560
+ &--isOptions button {
561
+ border: fn.px-to-rem(1) solid colors.$border-color;
562
+ border-radius: fn.px-to-rem(8);
563
+ background-color: colors.$neutral-light;
564
+ display: inline-flex;
565
+ padding: sp.$space-sm;
566
+ color: colors.$neutral-dark-1;
567
+
568
+ &:hover {
569
+ background-color: color.scale(colors.$neutral-dark, $lightness: 98%);
570
+ color: colors.$neutral-dark;
571
+ }
572
+
573
+ &:focus {
574
+ outline: none !important;
575
+ box-shadow: 0 0 0 fn.px-to-em(3) color.scale(colors.$secondary-color, $lightness: 80%);
576
+ }
577
+ }
578
+
579
+ &--isOptions i {
580
+ width: fn.px-to-rem(16);
581
+ height: fn.px-to-rem(16);
582
+ }
583
+ }
584
+
585
+ // On desktop devices, display options only on row focus or hover
586
+ @media (hover: hover) and (pointer: fine) {
587
+ &__cell--isOptions {
588
+ opacity: 0;
589
+ transition: opacity 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.175);
590
+ }
591
+
592
+ &__row:focus &__cell--isOptions,
593
+ &__row:focus-within &__cell--isOptions,
594
+ &__row:hover &__cell--isOptions {
595
+ opacity: 1;
596
+ }
597
+ }
598
+
599
+ &__emptyState {
600
+ flex: 1;
601
+ }
602
+
603
+ &--isLoading &__overlay {
604
+ opacity: 0.5;
605
+ pointer-events: all;
606
+ }
607
+
608
+ &__overlay {
609
+ position: absolute;
610
+ overflow: hidden;
611
+ left: 0;
612
+ right: 0;
613
+ top: 0;
614
+ bottom: 0;
615
+ border-radius: fn.px-to-rem(10);
616
+ background-color: colors.$neutral-light;
617
+ z-index: 4;
618
+ opacity: 0;
619
+ transition: 0.15s opacity ease-in-out;
620
+ cursor: wait;
621
+ pointer-events: none;
622
+
623
+ &::after {
624
+ content: '';
625
+ position: absolute;
626
+ top: 0;
627
+ right: 0;
628
+ bottom: 0;
629
+ left: 0;
630
+ transform: translateX(-100%);
631
+ background-image: linear-gradient(
632
+ 90deg,
633
+ rgba(colors.$neutral-dark-3, 0) 0,
634
+ rgba(colors.$neutral-dark-3, 0.2) 20%,
635
+ rgba(colors.$neutral-dark-3, 0.5) 60%,
636
+ rgba(colors.$neutral-dark-3, 0)
637
+ );
638
+ animation: shimmer 2s infinite;
639
+ }
640
+
641
+ @keyframes shimmer {
642
+ 100% {
643
+ transform: translateX(100%);
644
+ }
645
+ }
646
+ }
647
+
648
+ &__loader {
649
+ width: fn.px-to-rem(32);
650
+ height: fn.px-to-rem(32);
651
+
652
+ &--isSmall {
653
+ display: inline-block;
654
+ vertical-align: middle;
655
+ width: fn.px-to-rem(24);
656
+ height: fn.px-to-rem(24);
657
+ }
658
+ }
659
+
660
+ &__footer {
661
+ padding: sp.$space-lg sp.$space-md 0;
662
+ display: flex;
663
+ align-items: center;
664
+ font-size: fn.px-to-em(14);
665
+ }
666
+
667
+ .footer {
668
+ &__details,
669
+ &__pagination {
670
+ flex: 1;
671
+ }
672
+
673
+ &__results {
674
+ font-variant-numeric: tabular-nums;
675
+ color: colors.$neutral-dark-1;
676
+ }
677
+
678
+ &__results strong {
679
+ color: colors.$neutral-dark;
680
+ }
681
+
682
+ &__pagination {
683
+ text-align: right;
684
+ }
685
+
686
+ &__pagination button {
687
+ box-shadow: 0 fn.px-to-rem(1) fn.px-to-rem(2) rgba(colors.$neutral-dark, 0.08);
688
+ border-radius: fn.px-to-rem(10);
689
+ border: fn.px-to-rem(1) solid colors.$border-color;
690
+ padding: fn.px-to-rem(6) fn.px-to-rem(10);
691
+ transition: background-color 0.15s;
692
+ background-color: colors.$neutral-light;
693
+
694
+ &:hover {
695
+ background-color: rgba(colors.$neutral-dark, 0.05);
696
+ }
697
+
698
+ &:focus {
699
+ outline: none !important;
700
+ box-shadow: 0 0 0 fn.px-to-em(3) color.scale(colors.$blue, $lightness: 70%);
701
+ }
702
+
703
+ &:disabled {
704
+ box-shadow: none;
705
+ border-color: colors.$neutral-light-1;
706
+ background-color: colors.$neutral-light-1;
707
+ color: colors.$neutral-dark-2;
708
+ cursor: not-allowed;
709
+ user-select: none;
710
+ }
711
+
712
+ &:last-of-type {
713
+ margin-left: sp.$space;
714
+ }
715
+ }
716
+ }
717
+ }
718
+ </style>