@itfin/components 1.3.96 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/package.json +1 -1
  2. package/src/assets/scss/_css_variables.scss +2 -2
  3. package/src/assets/scss/_variables.scss +18 -7
  4. package/src/assets/scss/components/_button.scss +10 -0
  5. package/src/assets/scss/components/_pagination.scss +4 -1
  6. package/src/assets/scss/components/_select.scss +1 -3
  7. package/src/assets/scss/components/_text-field.scss +17 -7
  8. package/src/assets/scss/main.scss +36 -2
  9. package/src/components/app/message.js +1 -1
  10. package/src/components/button/Button.vue +4 -2
  11. package/src/components/filter/FilterAmountRange.vue +50 -42
  12. package/src/components/filter/FilterBadge.vue +25 -22
  13. package/src/components/filter/FilterFacetsList.vue +1 -1
  14. package/src/components/filter/FilterPanel.vue +82 -27
  15. package/src/components/filter/index.stories.js +0 -2
  16. package/src/components/icon/Icon.vue +3 -1
  17. package/src/components/icon/components/fi_fingerprint.vue +4 -0
  18. package/src/components/icon/components/nomi-arrow-down.vue +4 -0
  19. package/src/components/icon/components/nomi-arrow-right-top.vue +4 -0
  20. package/src/components/icon/components/nomi-arrow-up.vue +4 -0
  21. package/src/components/icon/components/nomi-arrows.vue +7 -0
  22. package/src/components/icon/components/nomi-calendar-alt.vue +4 -0
  23. package/src/components/icon/components/nomi-calendar.vue +11 -0
  24. package/src/components/icon/components/nomi-card.vue +4 -0
  25. package/src/components/icon/components/nomi-close.vue +5 -0
  26. package/src/components/icon/components/nomi-eye-close.vue +4 -0
  27. package/src/components/icon/components/nomi-eye-open.vue +4 -0
  28. package/src/components/icon/components/nomi-filter.vue +4 -0
  29. package/src/components/icon/components/nomi-hide.vue +4 -0
  30. package/src/components/icon/components/nomi-money.vue +4 -0
  31. package/src/components/icon/components/nomi-move-left.vue +4 -0
  32. package/src/components/icon/components/nomi-move-right.vue +4 -0
  33. package/src/components/icon/components/nomi-person.vue +5 -0
  34. package/src/components/icon/components/nomi-pin.vue +7 -0
  35. package/src/components/icon/components/nomi-sort-asc.vue +7 -0
  36. package/src/components/icon/components/nomi-sort-desc.vue +7 -0
  37. package/src/components/icon/components/nomi-table-view.vue +4 -0
  38. package/src/components/icon/components/nomi-tag.vue +4 -0
  39. package/src/components/icon/components/nomi-target.vue +4 -0
  40. package/src/components/icon/components/nomi-text.vue +6 -0
  41. package/src/components/icon/components/nomi-unpin.vue +7 -0
  42. package/src/components/icon/convert-icons.js +11 -0
  43. package/src/components/icon/icons.js +302 -277
  44. package/src/components/icon/new-icons/arrow-down.svg +3 -0
  45. package/src/components/icon/new-icons/arrow-right-top.svg +3 -0
  46. package/src/components/icon/new-icons/arrow-up.svg +3 -0
  47. package/src/components/icon/new-icons/arrows.svg +6 -0
  48. package/src/components/icon/new-icons/calendar-alt.svg +3 -0
  49. package/src/components/icon/new-icons/calendar.svg +10 -0
  50. package/src/components/icon/new-icons/card.svg +3 -0
  51. package/src/components/icon/new-icons/clear.svg +3 -0
  52. package/src/components/icon/new-icons/close.svg +4 -0
  53. package/src/components/icon/new-icons/eye-close.svg +3 -0
  54. package/src/components/icon/new-icons/eye-open.svg +3 -0
  55. package/src/components/icon/new-icons/filter.svg +3 -0
  56. package/src/components/icon/new-icons/hide.svg +3 -0
  57. package/src/components/icon/new-icons/money.svg +3 -0
  58. package/src/components/icon/new-icons/move-left.svg +3 -0
  59. package/src/components/icon/new-icons/move-right.svg +3 -0
  60. package/src/components/icon/new-icons/person.svg +4 -0
  61. package/src/components/icon/new-icons/pin.svg +6 -0
  62. package/src/components/icon/new-icons/sort-asc.svg +6 -0
  63. package/src/components/icon/new-icons/sort-desc.svg +6 -0
  64. package/src/components/icon/new-icons/table-view.svg +3 -0
  65. package/src/components/icon/new-icons/tag.svg +3 -0
  66. package/src/components/icon/new-icons/target.svg +3 -0
  67. package/src/components/icon/new-icons/text.svg +5 -0
  68. package/src/components/icon/new-icons/unpin.svg +6 -0
  69. package/src/components/pagination/Pagination.vue +3 -2
  70. package/src/components/pagination/Pagination2.vue +176 -0
  71. package/src/components/sortable/draggable.js +2 -1
  72. package/src/components/table/Table2.vue +24 -1
  73. package/src/components/table/TableBody.vue +7 -2
  74. package/src/components/table/TableGroup.vue +8 -4
  75. package/src/components/table/TableHeader.vue +101 -24
  76. package/src/components/table/TableRows.vue +3 -1
  77. package/src/components/table/index.stories.js +22 -200
  78. package/src/components/table/table2.scss +178 -49
  79. package/src/components/text-field/TextField.vue +12 -8
  80. package/src/components/view/View.vue +119 -0
  81. package/src/components/view/index.stories.js +588 -0
  82. package/src/helpers/formatters.js +14 -1
  83. package/src/locales/en.js +13 -0
  84. package/src/locales/uk.js +11 -0
  85. package/src/components/table/TableRow.vue +0 -221
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itfin/components",
3
- "version": "1.3.96",
3
+ "version": "2.0.1",
4
4
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -1,8 +1,8 @@
1
1
  @import './variables.scss';
2
2
  @import './bootstrap.scss';
3
3
 
4
- $color-income: #0d935b;
5
- $color-outcome: #b91e1e;
4
+ $color-income: #46A856;
5
+ $color-outcome: #DC5C2E;
6
6
 
7
7
  :root {
8
8
  --color-income: #{$color-income};
@@ -26,21 +26,21 @@ $success: #10834e;
26
26
  $warning: #cda277;
27
27
  $danger: #cb4839;
28
28
 
29
- $primary: #0B314F !default;
30
- $link-color: #0B314F !default;
29
+ $primary: #1A4A97 !default;
30
+ $link-color: #1A4A97 !default;
31
31
  $input-btn-focus-width: .125rem;
32
32
 
33
- $input-bg: #f3f3f3 !default;
34
- $input-border-color: rgba(#000, .08);
33
+ $input-bg: #F2F4F7 !default;
34
+ $input-border-color: #F2F4F7;
35
35
  $input-border-radius: 10px;
36
36
  $input-font-size: 0.875rem;
37
37
  $input-font-family: "Fira Mono", "Courier New", monospace;
38
38
 
39
- $input-focus-bg: #fff;
40
- $input-focus-border-color: #fff;
39
+ $input-focus-bg: #F2F4F7;
40
+ $input-focus-border-color: #F2F4F7;
41
41
 
42
42
  $form-label-margin-bottom: .1rem;
43
- $input-focus-border: rgb(11 49 79 / 25%);
43
+ $input-focus-border: #F2F4F7;//rgb(11 49 79 / 25%);
44
44
 
45
45
  $form-check-input-border: 1px solid rgba(#000, .08);
46
46
  $form-switch-focus-color: tint-color($primary, 50%);
@@ -87,3 +87,14 @@ $primaryColor15: #DAE0E5;
87
87
  $baseText: #1e1e1e;
88
88
  $gray5: #F0F0F1;
89
89
  $gray50: #787885;
90
+
91
+ // згідно з дизайном
92
+ $body-color: #22222B;
93
+ $dropdown-border-radius: .75rem;
94
+ $dropdown-padding-x: .5rem;
95
+ $dropdown-item-padding-x: .5rem;
96
+ $dropdown-link-hover-bg: #F1F2F4;
97
+ $border-color-translucent: #00000014;
98
+ $dropdown-header-color: #A5A5A9;
99
+ $form-check-input-checked-bg-color: #22222B;
100
+ $form-check-input-indeterminate-bg-color: #22222B;
@@ -139,4 +139,14 @@
139
139
  }
140
140
  }
141
141
  }
142
+
143
+ &.btn-default {
144
+ border: 1px solid #0000001A;
145
+ color: #8E97A5;
146
+
147
+ &.active, &:hover, &.show {
148
+ border: 1px solid #1A4A974D;
149
+ color: #1A4A97;
150
+ }
151
+ }
142
152
  }
@@ -2,11 +2,14 @@
2
2
 
3
3
  .itf-pagination.pagination {
4
4
  padding-left: 0;
5
+ display: flex;
6
+ align-items: center;
7
+ gap: .5rem;
5
8
 
6
9
  .page-item {
7
- margin-right: 3px;
8
10
  .page-link {
9
11
  border-radius: .5rem;
12
+ padding: .125rem .25rem
10
13
  }
11
14
 
12
15
  &.disabled .page-link {
@@ -41,7 +41,7 @@
41
41
  cursor: pointer;
42
42
 
43
43
  .form-control {
44
- padding: 0.3125rem 0.75rem;
44
+ padding: 0.25rem 0.75rem;
45
45
 
46
46
  &.is-valid, &.is-invalid {
47
47
  background-image: none;
@@ -66,8 +66,6 @@
66
66
  background-color: $body-bg;
67
67
  //border: 2px solid $input-focus-border;
68
68
  border: 0 none;
69
- padding-left: 0.125rem !important;
70
- padding-right: 0.125rem !important;
71
69
  left: -2px;
72
70
  right: -2px;
73
71
  width: auto;
@@ -2,10 +2,10 @@
2
2
 
3
3
  .itf-text-field {
4
4
  &__input {
5
- .addon + & {
6
- border-top-left-radius: $input-border-radius !important;
7
- border-bottom-left-radius: $input-border-radius !important;
8
- }
5
+ min-height: 2rem;
6
+ border: 0 none;
7
+ background-color: transparent !important;
8
+
9
9
  -moz-appearance: textfield; // Hide Arrows From Input Number (Firefox)
10
10
 
11
11
  &::-webkit-outer-spin-button, // Hide Arrows From Input Number (Chrome, Safari, Edge, Opera)
@@ -14,9 +14,19 @@
14
14
  margin: 0;
15
15
  }
16
16
  }
17
- .addon-end {
18
- pointer-events: all;
19
- padding-right: 0.25rem;
17
+ .input-group-text {
18
+ background: transparent;
19
+ padding: 0.375rem 0 0.375rem 0.75rem;
20
+ }
21
+ &.is-valid .input-group-text {
22
+ color: var(--bs-form-valid-border-color);
23
+ }
24
+ &.is-invalid .input-group-text {
25
+ color: var(--bs-form-invalid-border-color);
26
+ }
27
+ .was-validated &.form-control:valid, &.form-control.is-valid,
28
+ .was-validated &.form-control:invalid, &.form-control.is-invalid {
29
+ background-image: none;
20
30
  }
21
31
  }
22
32
 
@@ -28,8 +28,8 @@
28
28
  @import './directives/tooltip';
29
29
 
30
30
  .form-control {
31
- min-height: 2.5rem;
32
- box-shadow: 0 2px 10px rgba(0,0,0,.05);
31
+ //min-height: 2.5rem;
32
+ //box-shadow: 0 2px 10px rgba(0,0,0,.05);
33
33
  }
34
34
 
35
35
  .color-project-tnm {
@@ -60,3 +60,37 @@ kbd {
60
60
  color: var(--bs-primary-color) !important
61
61
  }
62
62
  }
63
+
64
+ // згідно з дизайном
65
+ .dropdown-header {
66
+ font-size: .75rem;
67
+ padding: .75rem .5rem .25rem;
68
+
69
+ &:first-child {
70
+ padding-top: .25rem;
71
+ }
72
+ }
73
+ .dropdown-item {
74
+ --bs-dropdown-link-active-bg: #F1F2F4;
75
+ --bs-dropdown-link-active-color: var(--bs-body-color);
76
+ &:not(:last-child) {
77
+ margin-bottom: 1px;
78
+ }
79
+
80
+ svg {
81
+ color: #8E97A5;
82
+ }
83
+ &:hover svg {
84
+ color: var(--bs-body-color);
85
+ }
86
+ }
87
+ .form-check-input:checked[type=checkbox] {
88
+ --bs-form-check-bg-image: url("data:image/svg+xml,%3Csvg width='8' height='5' viewBox='0 0 10 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9 1L3.5 6.5L1 4' stroke='white' stroke-width='1.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A");
89
+ box-shadow: 0px 2px 3px 0px #00000026;
90
+ }
91
+ .form-check-input:indeterminate[type=checkbox] {
92
+ box-shadow: 0px 2px 3px 0px #00000026;
93
+ }
94
+ .text-muted {
95
+ color: #666770;
96
+ }
@@ -38,7 +38,7 @@ const Message = function (options) {
38
38
  const parent = document.getElementById('itf-app');
39
39
 
40
40
  const instance = new ToastContainer({
41
- parent: $nuxt.$el.__vue__,
41
+ // parent: $nuxt.$el.__vue__,
42
42
  el: document.createElement('itf-toast-container'),
43
43
  data: options
44
44
  });
@@ -10,6 +10,7 @@ export default @Component({
10
10
  class itfButton extends Vue {
11
11
  @Prop(Boolean) labeled;
12
12
  @Prop(Boolean) primary;
13
+ @Prop(Boolean) default;
13
14
  @Prop(Boolean) secondary;
14
15
  @Prop(Boolean) loading;
15
16
  @Prop(Boolean) small;
@@ -27,7 +28,7 @@ class itfButton extends Vue {
27
28
 
28
29
  render (createElement, { data, slots, children, props }) {
29
30
  const {
30
- to, href, target, disabled, color, block, loading, labeled, secondary, primary, small, large, icon, loadingText, squircle,
31
+ to, href, target, disabled, color, block, loading, labeled, secondary, primary, small, large, icon, loadingText, squircle, default: defaultStyle,
31
32
  class: classNames
32
33
  } = props;
33
34
  const component = to ? 'nuxt-link' : (props.href ? 'a' : 'button');
@@ -44,7 +45,8 @@ class itfButton extends Vue {
44
45
  'labeled': labeled,
45
46
  'disabled': disabled || loading,
46
47
  'btn-primary': primary,
47
- 'btn-basic': !primary && !secondary && !color,
48
+ 'btn-default': defaultStyle,
49
+ 'btn-basic': !primary && !secondary && !color && !defaultStyle,
48
50
  'btn-secondary': secondary,
49
51
  'btn-squircle': squircle,
50
52
  'btn-sm': small,
@@ -1,27 +1,25 @@
1
1
  <template>
2
- <div class="py-2" @mousemove="onRefresh" @mouseup="onUpdateValue">
3
- <div class="px-1">
4
- <div v-if="histogram" class="histogram px-3">
5
- <div v-for="(item, n) in histogramPerc" :key="n" :style="`--bar-height: ${item}px`"></div>
6
- </div>
7
-
8
- <itf-range
9
- ref="range"
10
- :value="value"
11
- :width="260"
12
- @input="onChange"
13
- :min="min"
14
- :max="max"
15
- :stop-propagation="false"
16
- :use-keyboard="false"
17
- :tooltip="false"
18
- :tooltip-merge="false"
2
+ <div class="py-1 px-1 small text-muted">
3
+ <itf-label :label="$t('components.filter.filterBy')" hide-details class="w-100">
4
+ <itf-select
5
+ v-model="filterType"
6
+ :options="filterTypes"
7
+ :get-option-label="option => option.text"
8
+ :reduce="option => option.value"
9
+ @input="onChangeType"
19
10
  />
11
+ </itf-label>
20
12
 
21
- <div v-if="internalValue" class="d-flex justify-content-between gap-5">
22
- <itf-text-field small type="number" :value="internalValue[0]" @input="onChange([$event, internalValue[1]], true)" />
23
- <itf-text-field small type="number" :value="internalValue[1]" @input="onChange([internalValue[0], $event], true)" />
24
- </div>
13
+ <itf-label v-if="filterType === 'exact'" :label="$t('components.filter.value')" hide-details class="mb-0 w-100">
14
+ <itf-text-field type="number" :value="internalValue" @input="onChange($event)" />
15
+ </itf-label>
16
+ <div v-else-if="internalValue" class="d-flex justify-content-between gap-2">
17
+ <itf-label :label="$t('components.filter.from')" hide-details class="mb-0 w-100">
18
+ <itf-text-field type="number" :value="internalValue[0]" @input="onChange([$event, internalValue[1]])" />
19
+ </itf-label>
20
+ <itf-label :label="$t('components.filter.to')" hide-details class="mb-0 w-100">
21
+ <itf-text-field type="number" :value="internalValue[1]" @input="onChange([internalValue[0], $event])" />
22
+ </itf-label>
25
23
  </div>
26
24
  </div>
27
25
  </template>
@@ -42,13 +40,18 @@
42
40
  </style>
43
41
  <script>
44
42
  import { Vue, Prop, Model, Component } from 'vue-property-decorator';
43
+ import itfLabel from '../form/Label.vue';
44
+ import itfSelect from '../select/Select.vue';
45
45
  import itfTextField from '../text-field/TextField.vue';
46
46
  import itfRange from '../range/Range.vue';
47
+ import debounce from 'lodash/debounce';
47
48
 
48
49
  export default @Component({
49
50
  name: 'FilterAmountRange',
50
51
  components: {
52
+ itfSelect,
51
53
  itfTextField,
54
+ itfLabel,
52
55
  itfRange
53
56
  }
54
57
  })
@@ -58,36 +61,41 @@ class FilterAmountRange extends Vue {
58
61
  @Prop() max;
59
62
  @Prop() histogram;
60
63
 
61
- isRefreshed = false;
62
- internalValue = null;
63
-
64
- mounted() {
65
- this.internalValue = this.value;
66
- }
64
+ filterType = false;
65
+ internalValue = 'exact';
66
+ onChangeFn = null;
67
67
 
68
- get histogramPerc() {
69
- const max = Math.max(...this.histogram);
70
- return this.histogram.map(item => Math.round(item / max * 100));
68
+ get filterTypes() {
69
+ return [
70
+ { text: this.$t('components.filter.exact'), value: 'exact' },
71
+ { text: this.$t('components.filter.range'), value: 'range' }
72
+ ];
71
73
  }
72
74
 
73
- onRefresh() {
74
- if (this.isRefreshed) {
75
- return;
76
- }
77
- this.isRefreshed = true;
78
- this.$refs.range.refresh();
75
+ mounted() {
76
+ this.internalValue = this.value;
77
+ this.filterType = Array.isArray(this.internalValue) ? 'range' : 'exact';
78
+ this.onChangeFn = debounce(() => {
79
+ this.$emit('input', this.internalValue);
80
+ }, 500);
79
81
  }
80
82
 
81
- onChange(val, isSet = false) {
82
- this.isRefreshed = false;
83
+ onChange(val) {
83
84
  this.internalValue = val;
84
- if (isSet) {
85
- this.onUpdateValue();
85
+ if (!Number.isNaN(parseFloat(val))) {
86
+ this.onChangeFn()
87
+ }
88
+ if (!val) {
89
+ this.onChangeFn();
86
90
  }
87
91
  }
88
92
 
89
- onUpdateValue() {
90
- this.$emit('input', this.internalValue);
93
+ onChangeType(type) {
94
+ if (type === 'range') {
95
+ this.internalValue = [parseFloat(this.internalValue), parseFloat(this.internalValue)];
96
+ } else {
97
+ this.internalValue = Array.isArray(this.internalValue) ? parseFloat(this.internalValue[0]) : parseFloat(this.internalValue);
98
+ }
91
99
  }
92
100
  }
93
101
  </script>
@@ -1,13 +1,13 @@
1
1
  <template>
2
- <itf-dropdown text ref="dropdown" autoclose="outside">
2
+ <itf-dropdown text ref="dropdown" autoclose="outside" shadow class="h-100">
3
3
  <template #button>
4
- <div class="filter-pill" :class="{'filter-not-default-pill': !isDefault && !isInvalid, 'filter-invalid-pill': isInvalid}">
4
+ <div class="filter-pill rounded" :class="{'filter-not-default-pill': !isDefault && !isInvalid, 'filter-invalid-pill': isInvalid}">
5
5
  <div class="filter-pill__label" :class="{'filter-pill__not-default-value': !isDefault && !isInvalid, 'filter-pill__default-value': isDefault, 'filter-pill__label-invalid': isInvalid}">
6
- <itf-icon v-if="icon" :size="18" :name="icon" />
6
+ <itf-icon v-if="icon" :size="20" new :name="icon" class="icon" />
7
7
  {{ text }}
8
8
  </div>
9
9
  <div @click.stop.prevent="resetValue" v-if="!isDefault" class="filter-pill__icon" :class="{'filter-pill__icon-not-default-value': !isDefault && !isInvalid, 'filter-pill__icon-invalid': isInvalid}">
10
- <svg width="8" height="8" viewBox="0 0 8 8" fill="#788195" xmlns="http://www.w3.org/2000/svg"><path d="M8 0.94L7.06 0L4 3.06L0.94 0L0 0.94L3.06 4L0 7.06L0.94 8L4 4.94L7.06 8L8 7.06L4.94 4L8 0.94Z" fill="inherit"></path></svg>
10
+ <itf-icon new name="close" />
11
11
  </div>
12
12
  </div>
13
13
  </template>
@@ -34,7 +34,7 @@
34
34
  @input="onFilterChange({ value: $event })"
35
35
  />
36
36
  </template>
37
- <div class="px-2" v-else-if="type === 'facets-list'" style="width: 300px;">
37
+ <div v-else-if="type === 'facets-list'" style="width: 300px;">
38
38
  <filter-facets-list
39
39
  :title="options.title"
40
40
  :value="value.value"
@@ -44,7 +44,7 @@
44
44
  @input="onFilterChange({ value: $event })"
45
45
  />
46
46
  </div>
47
- <div class="px-2" v-else-if="type === 'amount'" style="width: 300px;">
47
+ <div v-else-if="type === 'amount'" style="width: 300px;">
48
48
  <filter-amount-range
49
49
  :value="value.value"
50
50
  :histogram="options.histogram"
@@ -59,29 +59,33 @@
59
59
  <style lang="scss">
60
60
  :root {
61
61
  --filter-badge__default-color: #475266;
62
- --filter-badge__default-bg-color: #4752661A;
63
- --filter-badge__default-bg-color-hover: #47526642;
62
+ --filter-badge__default-border-color: #0000001A;
63
+ --filter-badge__default-bg-color: transparent;
64
+ --filter-badge__default-bg-color-hover: #1A4A970D;
64
65
 
65
66
  --filter-badge__invalid-color: #D83C31;
66
67
  --filter-badge__invalid-bg-color: #D83C311A;
67
68
  --filter-badge__invalid-bg-color-hover: #D83C3142;
68
69
 
69
- --filter-badge__selected-color: #2f51fe; // #0d935b;
70
- --filter-badge__selected-bg-color: #2f51fe1a; // #0d935b1A;
71
- --filter-badge__selected-bg-color-hover: #2f51fe42; // #0d935b42;
72
- --filter-badge__padding-x: 6px; // 10px;
73
- --filter-badge__padding-y: 12px;// 18px;
70
+ --filter-badge__selected-color: #1A4A97; // #0d935b;
71
+ --filter-badge__selected-bg-color: #1A4A970D; // #0d935b1A;
72
+ --filter-badge__selected-bg-color-hover: rgba(26, 74, 151, 0.1); // #0d935b42;
73
+ --filter-badge__padding-y: .5rem;
74
+ --filter-badge__padding-x: .75rem;
74
75
  }
75
76
  .filter-pill {
76
77
  align-items: center;
77
- border-radius: 21px;
78
78
  font-size: 14px;
79
79
  line-height: 100%;
80
+ outline: 1px solid var(--filter-badge__default-border-color);
80
81
  background-color: var(--filter-badge__default-bg-color);
81
82
  display: flex;
82
83
  min-height: 2rem;
83
84
  transition: 0.1s;
84
85
 
86
+ .icon {
87
+ margin: -2px;
88
+ }
85
89
  &:hover {
86
90
  background-color: var(--filter-badge__default-bg-color-hover);
87
91
  cursor: pointer;
@@ -94,12 +98,12 @@
94
98
  background-color: var(--filter-badge__invalid-bg-color);
95
99
  }
96
100
  &.filter-pill__default-value {
97
- padding: var(--filter-badge__padding-x) var(--filter-badge__padding-y);
101
+ padding: var(--filter-badge__padding-y) var(--filter-badge__padding-x);
98
102
  }
99
103
  }
100
104
  .filter-pill__label {
101
105
  color: var(--filter-badge__default-color);
102
- padding: var(--filter-badge__padding-x) 0 var(--filter-badge__padding-x) var(--filter-badge__padding-y);
106
+ padding: var(--filter-badge__padding-y) 0 var(--filter-badge__padding-y) var(--filter-badge__padding-x);
103
107
  max-width: 330px;
104
108
  text-overflow: ellipsis;
105
109
  white-space: nowrap;
@@ -109,7 +113,7 @@
109
113
  align-items: center;
110
114
 
111
115
  &.filter-pill__default-value {
112
- padding: var(--filter-badge__padding-x) var(--filter-badge__padding-y);
116
+ padding: var(--filter-badge__padding-y) var(--filter-badge__padding-x);
113
117
  }
114
118
  &.filter-pill__not-default-value {
115
119
  color: var(--filter-badge__selected-color);
@@ -120,17 +124,16 @@
120
124
  }
121
125
 
122
126
  .filter-pill__icon {
123
- padding: var(--filter-badge__padding-x) calc(var(--filter-badge__padding-x) * 2) var(--filter-badge__padding-x) var(--filter-badge__padding-x);
124
- margin-top: -2px;
127
+ padding: 0 calc(var(--filter-badge__padding-x) / 2) 0 calc(var(--filter-badge__padding-x) / 4);
125
128
 
126
129
  svg {
127
- fill: var(--filter-badge__default-color);
130
+ color: var(--filter-badge__default-color);
128
131
  }
129
132
  &.filter-pill__icon-invalid svg {
130
- fill: var(--filter-badge__invalid-color);
133
+ color: var(--filter-badge__invalid-color);
131
134
  }
132
135
  &.filter-pill__icon-not-default-value svg {
133
- fill: var(--filter-badge__selected-color);
136
+ color: var(--filter-badge__selected-color);
134
137
  }
135
138
  }
136
139
  </style>
@@ -62,7 +62,7 @@
62
62
  transition: none 0s ease 0s;
63
63
  margin: 1px 0;
64
64
  &.active {
65
- background-color: rgba(var(--bs-primary-rgb), 25%);
65
+ background-color: rgba(var(--bs-primary-rgb), 10%);
66
66
 
67
67
  .facet-bar-progress {
68
68
  background-color: rgba(var(--bs-primary-rgb), 75%);
@@ -1,28 +1,40 @@
1
1
  <template>
2
- <div class="d-flex gap-2 align-items-center flex-wrap">
3
- <template v-if="search">
2
+ <div class="itf-filter-panel d-flex flex-column gap-3 align-items-start">
3
+ <div v-if="search" class="d-flex gap-2 justify-content-between w-100">
4
4
  <itf-text-field
5
- style="width: 200px"
5
+ style="width: 300px"
6
6
  small
7
- :placeholder="$t('components.filter.search')"
8
- prepend---icon="search"
7
+ :placeholder="searchPlaceholder"
8
+ prepend-icon="search"
9
9
  :delay-input="250"
10
10
  clearable
11
11
  :value="filterValue.query"
12
12
  @input="(e) => onFilterChange({ type: 'text', name: 'query' }, { value: e })"
13
13
  />
14
- </template>
15
- <filter-badge
16
- v-for="(facet, n) in filters"
17
- :key="n"
18
- v-model="filter[facet.name]"
19
- :is-default="filter[facet.name].isDefault"
20
- :text="filter[facet.name].label"
21
- :type="facet.type"
22
- :icon="facet.icon"
23
- :options="facet.options"
24
- @change="onFilterChange(facet, $event)"
25
- />
14
+ <div class="d-flex gap-2">
15
+ <itf-button default icon class="position-relative" @click="showFilters = !showFilters">
16
+ <itf-icon new name="filter" />
17
+ <span v-if="activeFiltersCount" class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-primary">
18
+ {{activeFiltersCount}}
19
+ <span class="visually-hidden">unread messages</span>
20
+ </span>
21
+ </itf-button>
22
+ <slot name="after-filter-btn"></slot>
23
+ </div>
24
+ </div>
25
+ <div v-if="showFilters" class="d-flex gap-2 flex-wrap">
26
+ <filter-badge
27
+ v-for="(facet, n) in filters"
28
+ :key="n"
29
+ v-model="filter[facet.name]"
30
+ :is-default="filter[facet.name].isDefault"
31
+ :text="filter[facet.name].label"
32
+ :type="facet.type"
33
+ :icon="facet.icon"
34
+ :options="facet.options"
35
+ @change="onFilterChange(facet, $event)"
36
+ />
37
+ </div>
26
38
  <div v-if="loading">
27
39
  <span class="itf-spinner"></span>
28
40
  {{$t('loading')}}
@@ -30,6 +42,24 @@
30
42
  </div>
31
43
  </template>
32
44
  <style lang="scss">
45
+ .itf-filter-panel {
46
+ .itf-text-field:not(.is-valid):not(.is-invalid) .itf-icon {
47
+ color: #8E97A5;
48
+ }
49
+ .itf-text-field__input {
50
+ border: 0 none;
51
+ box-shadow: none;
52
+ background-color: #F2F4F7;
53
+ border-radius: .5rem;
54
+ font-family: var(--bs-font-sans-serif);
55
+ .addon-start {
56
+ padding-left: 2rem;
57
+ }
58
+ &::placeholder {
59
+ color: #8E97A5;
60
+ }
61
+ }
62
+ }
33
63
  </style>
34
64
  <script>
35
65
  import { DateTime } from 'luxon';
@@ -56,24 +86,34 @@ export default @Component({
56
86
  })
57
87
  class FilterPanel extends Vue {
58
88
  @Model('input') value;
89
+ @Prop({ type: Array }) staticFilters;
59
90
  @Prop({ type: String }) endpoint;
60
91
  @Prop() panel;
61
92
  @Prop(Boolean) search;
93
+ @Prop({ type: String, default: function() { return this.$t('components.filter.search'); } }) searchPlaceholder;
62
94
 
63
95
  filter = {};
64
96
  filterValue = {};
65
97
  filters = [];
66
98
  loading = false;
99
+ showFilters = true;
67
100
 
68
101
  async mounted() {
69
- this.loading = true;
70
- await this.$try(async () => {
71
- const { filters } = await this.$axios.$get(this.endpoint);
72
- this.filters = filters;
102
+ this.filters = this.staticFilters ?? [];
103
+ if (this.endpoint) {
104
+ this.loading = true;
105
+ await this.$try(async () => {
106
+ const {filters} = await this.$axios.$get(this.endpoint);
107
+ this.filters = filters;
108
+ this.loadFiltersValue();
109
+ });
110
+ this.loading = false;
111
+ } else {
73
112
  this.loadFiltersValue();
74
- });
75
- this.loading = false;
76
- this.panel.on('panels.changed', () => this.loadFiltersValue());
113
+ }
114
+ if (this.panel) {
115
+ this.panel.on('panels.changed', () => this.loadFiltersValue());
116
+ }
77
117
  }
78
118
 
79
119
  loadFiltersValue() {
@@ -134,6 +174,10 @@ class FilterPanel extends Vue {
134
174
  ];
135
175
  }
136
176
 
177
+ get activeFiltersCount() {
178
+ return Object.values(this.filter).filter(facet => !facet.isDefault).length;
179
+ }
180
+
137
181
  formatValue(facet, value) {
138
182
  if (facet.type === 'period') {
139
183
  if (value.value) {
@@ -170,11 +214,22 @@ class FilterPanel extends Vue {
170
214
  }
171
215
  value.isDefault = facet.options.defaultValue ? JSON.stringify(value.value) === JSON.stringify(facet.options.defaultValue.value) : false;
172
216
  } else if (facet.type === 'amount') {
173
- if (typeof value.value[0] !== 'number' || typeof value.value[1] !== 'number') {
174
- value.value = [facet.options.min, facet.options.max];
217
+ if (value.value === null || (Array.isArray(value.value) && value.value.every(v => v === null))) {
218
+ value.value = facet.options.defaultValue.value;
219
+ } else if (Array.isArray(value.value)) {
220
+ } else {
221
+ if (Number.isNaN(value.value)) {
222
+ value.value = facet.options.defaultValue.value;
223
+ }
224
+ }
225
+ value.isDefault = facet.options.defaultValue ? JSON.stringify(value.value) === JSON.stringify(facet.options.defaultValue.value) : false;
226
+
227
+ if (Array.isArray(value.value)) {
228
+ value.label = value.isDefault ? facet.options.defaultValue.label : `${formatMoney(value.value[0], null, 2)} - ${formatMoney(value.value[1], null, 2)}`;
229
+ } else {
230
+ value.label = value.isDefault ? facet.options.defaultValue.label : formatMoney(value.value, null, 2);
175
231
  }
176
232
  value.isDefault = facet.options.defaultValue ? JSON.stringify(value.value) === JSON.stringify(facet.options.defaultValue.value) : false;
177
- value.label = value.isDefault ? facet.options.defaultValue.label : `${formatMoney(value.value[0], null, 0)} - ${formatMoney(value.value[1], null, 0)}`;
178
233
  } else if (facet.type === 'list') {
179
234
  const item = facet.options.items.find(item => item.value === value.value);
180
235
  value.isDefault = facet.options.defaultValue ? JSON.stringify(value.value) === JSON.stringify(facet.options.defaultValue.value) : false;