@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
@@ -1,438 +0,0 @@
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>
95
- import { camelize, decamelize } from '@/helpers/string'
96
- import { randomString } from '@/helpers'
97
-
98
- import CpTableEmptyState from '@/components/lists-and-table/CpTable/CpTableEmptyState/index.vue'
99
-
100
- const VISIBLE_ROWS_MAX = 100
101
- const RESERVED_KEYS = {
102
- GROUP_BY: 'groupBy',
103
- FULL_WIDTH: 'fullWidth',
104
- IS_SELECTED: 'isSelected',
105
- }
106
- const PAGINATION_FORMATS = {
107
- RESULTS: 'results',
108
- PAGES: 'pages',
109
- }
110
- const LoaderColor = '#5341F9'
111
-
112
- export default {
113
- components: {
114
- CpTableEmptyState,
115
- },
116
- props: {
117
- caption: {
118
- type: String,
119
- default: '',
120
- required: false,
121
- },
122
- columns: {
123
- type: Array,
124
- default: () => [],
125
- required: false,
126
- },
127
- data: {
128
- type: Array,
129
- required: true,
130
- },
131
- pagination: {
132
- type: [Boolean, Object],
133
- default: false,
134
- required: false,
135
- validator: (value) => {
136
- const isLimitValid = value.limit ? value.limit <= VISIBLE_ROWS_MAX : true
137
- const isFooterDetailsFormatValid = value.format
138
- ? Object.values(PAGINATION_FORMATS).includes(value.format)
139
- : true
140
-
141
- return isLimitValid && isFooterDetailsFormatValid
142
- },
143
- },
144
- areRowsClickable: {
145
- type: Boolean,
146
- default: false,
147
- required: false,
148
- },
149
- emptyCellPlaceholder: {
150
- type: String,
151
- default: 'n/a',
152
- required: false,
153
- },
154
- noResultPlaceholder: {
155
- type: String,
156
- default: 'No results found',
157
- required: false,
158
- },
159
- isLoading: {
160
- type: Boolean,
161
- default: false,
162
- required: false,
163
- },
164
- enableRowOptions: {
165
- type: Boolean,
166
- default: false,
167
- required: false,
168
- },
169
- },
170
- emits: ['on-row-click', 'on-row-right-click', 'on-next-click', 'on-previous-click'],
171
- data() {
172
- return {
173
- uniqueId: randomString(),
174
- pageNumber: 0,
175
- LoaderColor,
176
- }
177
- },
178
- computed: {
179
- containerDOMElement() {
180
- return this.$refs.cpTableContainer
181
- },
182
- mainClasses() {
183
- return {
184
- 'cpTable--isLoading': this.isLoading,
185
- }
186
- },
187
- containerClasses() {
188
- return {
189
- 'cpTable__container--hasPagination': this.hasPagination,
190
- }
191
- },
192
- normalizedColumns() {
193
- if (!this.columns) return []
194
- const columns = this.columns.length ? [...this.columns] : [...this.columnsFromRows]
195
-
196
- return columns.map((column) => {
197
- if (typeof column === 'string') {
198
- return {
199
- id: camelize(column),
200
- name: decamelize(column),
201
- }
202
- }
203
-
204
- return {
205
- id: column.id || camelize(column.name),
206
- ...column,
207
- }
208
- })
209
- },
210
- numberOfColumns() {
211
- return this.normalizedColumns.length
212
- },
213
- hasGroupBy() {
214
- if (!this.data.length) return false
215
-
216
- return this.data.some((item) => RESERVED_KEYS.GROUP_BY in item)
217
- },
218
- columnsFromRows() {
219
- if (!this.data.length) return []
220
-
221
- const firstRow = this.hasGroupBy ? this.data[0].rows[0] : this.data[0]
222
- return Object.keys(firstRow)
223
- },
224
- numberOfResults() {
225
- return this.isServerSidePagination ? this.pagination.server.total : this.flattenedRows.length
226
- },
227
- hasNoResult() {
228
- return this.numberOfResults === 0
229
- },
230
- rowsPerPageLimit() {
231
- return this.pagination.limit || VISIBLE_ROWS_MAX
232
- },
233
- flattenedRows() {
234
- if (!this.data) return []
235
-
236
- if (!this.hasGroupBy) return this.data
237
-
238
- // Transform groupBy key into a row and add it in between others
239
- return this.data.reduce((flattenedRows, groupByItem) => {
240
- const fullWidthRow = { [RESERVED_KEYS.FULL_WIDTH]: groupByItem.groupBy }
241
- return [...flattenedRows, fullWidthRow, ...groupByItem.rows]
242
- }, [])
243
- },
244
- rawVisibleRows() {
245
- const dataCopy = [...this.flattenedRows]
246
-
247
- if (this.isServerSidePagination) return dataCopy
248
- return this.hasPagination ? dataCopy.splice(this.pagesStartIndex, this.rowsPerPageLimit) : dataCopy
249
- },
250
- visibleRows() {
251
- return this.rawVisibleRows.map((rawRow) => {
252
- const normalizedRow = this.normalizeRowData({ rowPayload: rawRow })
253
- return this.mapCellToColumn({ rowPayload: normalizedRow })
254
- })
255
- },
256
- paginationState() {
257
- return typeof this.pagination === 'boolean' ? this.pagination : this.pagination.enabled
258
- },
259
- hasPagination() {
260
- return this.paginationState || this.numberOfResults > VISIBLE_ROWS_MAX
261
- },
262
- paginationFormat() {
263
- return this.pagination?.format || PAGINATION_FORMATS.PAGES
264
- },
265
- hasRemainingPages() {
266
- return this.numberOfPages > this.activePage
267
- },
268
- isNextEnabled() {
269
- return this.hasRemainingPages && !this.isLoading
270
- },
271
- hasPreviousPages() {
272
- return this.isServerSidePagination ? this.serverActivePage > 0 : this.pagesStartIndex - this.rowsPerPageLimit >= 0
273
- },
274
- isPreviousEnabled() {
275
- return this.hasPreviousPages && !this.isLoading
276
- },
277
- pagesStartIndex() {
278
- return this.pageNumber * this.rowsPerPageLimit
279
- },
280
- pagesEndIndex() {
281
- return this.rowsPerPageLimit * (1 + this.pageNumber)
282
- },
283
- numberOfPages() {
284
- return Math.ceil(this.numberOfResults / this.rowsPerPageLimit)
285
- },
286
- activePage() {
287
- return this.isServerSidePagination ? this.serverActivePage + 1 : this.pageNumber + 1
288
- },
289
- isServerSidePagination() {
290
- if (typeof this.pagination === 'boolean') return false
291
- return 'server' in this.pagination
292
- },
293
- serverActivePage() {
294
- return this.pagination.server.activePage
295
- },
296
- serverPagesStartIndex() {
297
- return this.serverActivePage * this.rowsPerPageLimit + 1
298
- },
299
- serverPagesEndIndex() {
300
- return this.rowsPerPageLimit * (1 + this.serverActivePage)
301
- },
302
- pageFirstResultIndex() {
303
- return this.isServerSidePagination ? this.serverPagesStartIndex : this.pagesStartIndex + 1
304
- },
305
- pageLastResultIndex() {
306
- const endIndex = this.isServerSidePagination ? this.serverPagesEndIndex : this.pagesEndIndex
307
-
308
- return this.hasRemainingPages ? endIndex : this.numberOfResults
309
- },
310
- paginationLabel() {
311
- if (this.paginationFormat === PAGINATION_FORMATS.PAGES) {
312
- const pluralizedCount = this.numberOfPages > 1 ? 'pages' : 'page'
313
-
314
- return `${this.activePage}/${this.numberOfPages} ${pluralizedCount}`
315
- }
316
-
317
- return `${this.pageFirstResultIndex} – ${this.pageLastResultIndex}`
318
- },
319
- paginationResultsDetails() {
320
- const formattedNumberOfResults = new Intl.NumberFormat('en-US').format(this.numberOfResults)
321
- const pluralizedCount = this.numberOfResults > 1 ? 'results' : 'result'
322
- return `${formattedNumberOfResults} ${pluralizedCount}`
323
- },
324
- },
325
- methods: {
326
- getRowPayload(rowIndex) {
327
- return this.rawVisibleRows[rowIndex]
328
- },
329
- handleRowClick(rowData, rowIndex) {
330
- if (this.isFullWidthRow(rowData)) return
331
-
332
- const data = this.getRowPayload(rowIndex)
333
- this.$emit('on-row-click', data)
334
- },
335
- handleRowRightClick({ rowData, rowIndex }, event) {
336
- if (this.isFullWidthRow(rowData)) return
337
-
338
- const data = this.getRowPayload(rowIndex)
339
- this.$emit('on-row-right-click', { data, event })
340
- },
341
- handleNavigationClick(isNext = true) {
342
- this.resetScrollPosition()
343
-
344
- if (isNext) {
345
- this.hasRemainingPages && this.augmentOffset()
346
- this.$emit('on-next-click')
347
- return
348
- }
349
-
350
- this.hasPreviousPages && this.decreaseOffset()
351
- this.$emit('on-previous-click')
352
- },
353
- normalizeRowData({ columns = this.normalizedColumns, rowPayload }) {
354
- if (!Array.isArray(rowPayload)) {
355
- return { ...rowPayload }
356
- }
357
-
358
- // from ['cell 1', 'cell 2', 'cell 3']
359
- // to { column1: 'cell 1', column2: 'cell 2', column3: 'cell 3' }
360
- return rowPayload.reduce((row, cell, cellIndex) => {
361
- const normalizedCell = {
362
- [columns[cellIndex]?.id]: cell,
363
- }
364
- return { ...row, ...normalizedCell }
365
- }, {})
366
- },
367
- mapCellToColumn({ columns = this.normalizedColumns, rowPayload }) {
368
- if (this.isFullWidthRow(rowPayload)) return rowPayload
369
- // Bind column id with each row key
370
- // and replace row missing keys with the emptyCellPlaceholder value
371
- return columns.reduce((finalRow, currentColumn) => {
372
- const correspondingColumn = currentColumn?.id || currentColumn
373
- const correspondingValue = rowPayload[correspondingColumn] || this.emptyCellPlaceholder
374
- const cellValue = { [correspondingColumn]: correspondingValue }
375
-
376
- return { ...finalRow, ...cellValue }
377
- }, {})
378
- },
379
- augmentOffset() {
380
- if (this.isNextEnabled) this.pageNumber++
381
- },
382
- decreaseOffset() {
383
- if (this.isPreviousEnabled) this.pageNumber--
384
- },
385
- resetScrollPosition() {
386
- this.containerDOMElement.scrollTop = 0
387
- },
388
- getColumnStyle(columnPayload) {
389
- const formattedWidth = columnPayload?.width && `${columnPayload.width}px`
390
-
391
- return {
392
- width: formattedWidth,
393
- textAlign: columnPayload.textAlign,
394
- }
395
- },
396
- getCellStyle(cellKey, columnIndex) {
397
- if (cellKey === RESERVED_KEYS.FULL_WIDTH) return null
398
-
399
- return {
400
- textAlign: this.normalizedColumns[columnIndex]?.textAlign,
401
- }
402
- },
403
- getRowClasses(rowData, rowIndex) {
404
- return {
405
- 'cpTable__row--isFullWidth': this.isFullWidthRow(rowData),
406
- 'cpTable__row--isClickable': !this.isFullWidthRow(rowData) && this.areRowsClickable,
407
- 'cpTable__row--isSelected': this.isRowSelected(rowIndex),
408
- }
409
- },
410
- getCellClasses(cellKey) {
411
- return {
412
- 'cpTable__cell--isFullWidth': cellKey === RESERVED_KEYS.FULL_WIDTH,
413
- }
414
- },
415
- getColspan(cellKey) {
416
- const numberOfColumns = this.enableRowOptions ? this.numberOfColumns + 1 : this.numberOfColumns
417
-
418
- return cellKey === RESERVED_KEYS.FULL_WIDTH ? numberOfColumns : null
419
- },
420
- getTabindex(rowData) {
421
- return this.isFullWidthRow(rowData) ? -1 : 0
422
- },
423
- isFullWidthRow(rowData) {
424
- return RESERVED_KEYS.FULL_WIDTH in rowData
425
- },
426
- isRowSelected(rowIndex) {
427
- return this.rawVisibleRows[rowIndex][RESERVED_KEYS.IS_SELECTED]
428
- },
429
- areRowOptionsEnabled(rowData) {
430
- return this.enableRowOptions && !this.isFullWidthRow(rowData)
431
- },
432
- },
433
- }
434
- </script>
435
-
436
- <style lang="scss">
437
- @import 'index';
438
- </style>
@@ -1,136 +0,0 @@
1
- @use 'sass:math';
2
- @import '/src/assets/styles/main';
3
-
4
- $cp-checkbox-base-width: px-to-rem(16);
5
- $cp-checkbox-tick-base-width: px-to-rem(12);
6
-
7
- @mixin cp-checkbox-style($color, $className) {
8
- &--is#{$className} input:checked {
9
- background-color: $color;
10
- border-color: $color;
11
- }
12
-
13
- &--is#{$className}:hover input {
14
- background-color: scale-color($color, $lightness: 95%);
15
- }
16
-
17
- &--is#{$className}:hover input:checked {
18
- background-color: darken($color, 10%);
19
- }
20
-
21
- &--is#{$className} input:focus-visible {
22
- position: relative;
23
- z-index: 1;
24
- outline: px-to-rem(2) solid $color;
25
- outline-offset: px-to-rem(2);
26
- }
27
- }
28
-
29
- .cpCheckbox {
30
- position: relative;
31
- display: flex;
32
- align-items: flex-start;
33
- cursor: pointer;
34
-
35
- @include cp-checkbox-style($secondary-color, 'Blue');
36
- @include cp-checkbox-style($primary-color, 'Purple');
37
-
38
-
39
- &:not(&--isEmpty) {
40
- gap: $space-md;
41
- }
42
-
43
- &__wrapper {
44
- display: flex;
45
- align-items: center;
46
- justify-content: center;
47
- padding: $space-sm;
48
- flex-shrink: 0;
49
- }
50
-
51
- input {
52
- -webkit-appearance: none;
53
- -moz-appearance: none;
54
- appearance: none;
55
- border: px-to-rem(1) solid $neutral-dark-4;
56
- border-radius: px-to-rem(4);
57
- width: $cp-checkbox-base-width;
58
- height: $cp-checkbox-base-width;
59
- flex-shrink: 0;
60
- cursor: pointer;
61
- transition:
62
- background-color 0.1s ease-out,
63
- transform 0.1s linear;
64
- background-color: $neutral-light;
65
-
66
- & + i {
67
- position: absolute;
68
- z-index: 2;
69
- width: $cp-checkbox-tick-base-width;
70
- height: $cp-checkbox-tick-base-width;
71
- color: $neutral-light;
72
- opacity: 0;
73
- transition: opacity 0.2s linear 0.1s;
74
- }
75
-
76
- & + i svg {
77
- stroke-width: 3;
78
- }
79
-
80
- &:checked + i {
81
- visibility: visible;
82
- opacity: 1;
83
- }
84
-
85
- &:disabled {
86
- background-color: $neutral-light-1;
87
- cursor: not-allowed;
88
-
89
- & ~ span {
90
- color: $neutral-dark-1;
91
- }
92
- }
93
-
94
- &:checked:disabled {
95
- border-color: $neutral-dark-3;
96
- background-color: $neutral-dark-3;
97
-
98
- & ~ span {
99
- color: $neutral-dark-3;
100
- }
101
- }
102
-
103
- &:active:not(:disabled) {
104
- transform: scale(0.8);
105
- }
106
- }
107
-
108
- &--isDisabled {
109
- cursor: not-allowed;
110
- }
111
-
112
- &--isReversed {
113
- flex-direction: row-reverse;
114
- }
115
-
116
- &__content {
117
- flex: 1;
118
- }
119
-
120
- &__label, &__helper {
121
- display: block;
122
- line-height: px-to-rem(24);
123
- }
124
-
125
- &__label {
126
- font-weight: 500;
127
-
128
- &--isCapitalized::first-letter {
129
- text-transform: capitalize;
130
- }
131
- }
132
-
133
- &__helper {
134
- color: $neutral-dark-1;
135
- }
136
- }