@itfin/components 1.4.11 → 1.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@itfin/components",
3
- "version": "1.4.11",
3
+ "version": "1.4.13",
4
4
  "author": "Vitalii Savchuk <esvit666@gmail.com>",
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -60,7 +60,7 @@ body[data-theme="dark"] {
60
60
 
61
61
  .dropdown-menu {
62
62
  --bs-dropdown-link-color: #{$dark-link-color};
63
- --bs-dropdown-link-hover-color: #1e2125;
64
- --bs-dropdown-link-hover-bg: #{$dark-link-color};
63
+ --bs-dropdown-link-hover-bg: rgba(var(--bs-primary-rgb), .15);
64
+ --bs-dropdown-link-active-bg: rgba(var(--bs-primary-rgb), .5);
65
65
  }
66
66
  }
@@ -29,8 +29,8 @@
29
29
  color: $dark-body-color;
30
30
 
31
31
  &.active, &:active {
32
- background-color: $dark-primary;
33
- color: $dark-body-bg;
32
+ background-color: var(--bs-dropdown-link-active-bg);
33
+ color: var(--bs-dropdown-link-color);
34
34
  }
35
35
  }
36
36
  }
@@ -17,7 +17,6 @@ class itfButton extends Vue {
17
17
  @Prop(Boolean) large;
18
18
  @Prop(Boolean) icon;
19
19
  @Prop(Boolean) block;
20
- @Prop(Boolean) squircle;
21
20
  @Prop(String) loadingText;
22
21
  @Prop(String) color;
23
22
  @Prop(Boolean) disabled;
@@ -28,7 +27,7 @@ class itfButton extends Vue {
28
27
 
29
28
  render (createElement, { data, slots, children, props }) {
30
29
  const {
31
- to, href, target, disabled, color, block, loading, labeled, secondary, primary, small, large, icon, loadingText, squircle, default: defaultStyle,
30
+ to, href, target, disabled, color, block, loading, labeled, secondary, primary, small, large, icon, loadingText, default: defaultStyle,
32
31
  class: classNames
33
32
  } = props;
34
33
  const component = to ? 'nuxt-link' : (props.href ? 'a' : 'button');
@@ -48,7 +47,6 @@ class itfButton extends Vue {
48
47
  'btn-default': defaultStyle,
49
48
  'btn-basic': !primary && !secondary && !color && !defaultStyle,
50
49
  'btn-secondary': secondary,
51
- 'btn-squircle': squircle,
52
50
  'btn-sm': small,
53
51
  'btn-lg': large,
54
52
  // 'px-3': small && !icon,
@@ -11,85 +11,27 @@
11
11
  :placeholder="placeholder"
12
12
  />
13
13
  <div style="display: none">
14
- <div ref="dropdown" class="itf-periodpicker__dropdown">
15
- <div class="row mb-3">
16
- <div class="col">
17
- <itf-button @click="setYear(prevYear)">
18
- <itf-icon name="chevron_left" />
19
- </itf-button>
20
- </div>
21
- <div class="col">
22
- <itf-button block @click="setYear(prevYear)">
23
- {{prevYear}}
24
- </itf-button>
25
- </div>
26
- <div class="col">
27
- <itf-button color="outline-primary" block>
28
- {{year}}
29
- </itf-button>
30
- </div>
31
- <div class="col">
32
- <itf-button block @click="setYear(nextYear)">
33
- {{nextYear}}
34
- </itf-button>
35
- </div>
36
- <div class="col text-end">
37
- <itf-button @click="setYear(nextYear)">
38
- <itf-icon name="chevron_right" />
39
- </itf-button>
40
- </div>
41
- </div>
42
-
43
- <itf-button block class="mb-3" :class="{'btn-whole': !isCurrentYear()}" :primary="isCurrentYear()" @click="onYearSelect(year)">
44
- {{ $t('components.wholeYear') }}
45
- </itf-button>
46
-
47
- <div class="itf-periodpicker__quarters">
48
- <div
49
- v-for="quarter of quarters"
50
- :key="quarter.Number"
51
- class="itf-periodpicker__quarter"
52
- :class="{ 'active': isOnlyQuarter(quarter.Number), 'active-inside': isCurrentQuarter(quarter.Number) }">
53
- <div class="px-3 pt-2" @click="onQuarterSelect([quarter.Months[0], quarter.Months[2]])">
54
- <small><b>QUARTER</b></small><br>
55
- <span class="quarter-number">{{ quarter.Number }}</span>
56
- </div>
57
-
58
- <div class="itf-periodpicker__months">
59
- <itf-button
60
- class="itf-periodpicker__month px-1"
61
- v-for="month of quarter.Months"
62
- :key="month"
63
- :primary="isCurrentMonth(month)"
64
- @click="onMonthSelect(month)"
65
- >
66
- {{ month | formatMonth }}
67
- </itf-button>
68
- </div>
69
- </div>
70
- </div>
71
- </div>
14
+ <itf-period-picker-inline
15
+ ref="dropdown"
16
+ class="itf-periodpicker__dropdown"
17
+ v-model="value"
18
+ :value-format="valueFormat"
19
+ @input="onInput"
20
+ />
72
21
  </div>
73
22
  </div>
74
23
  </template>
75
24
  <script>
76
25
  import { Vue, Component, Prop, Inject } from 'vue-property-decorator';
77
- import { DateTime } from 'luxon';
78
26
  import tippy from 'tippy.js';
79
- import itfIcon from '../icon/Icon';
80
- import itfButton from '../button/Button';
27
+ import itfPeriodPickerInline from "./PeriodPickerInline.vue";
28
+ import {DateTime} from "luxon";
81
29
  import ITFSettings from '../../ITFSettings';
82
30
 
83
31
  export default @Component({
84
32
  name: 'itfPeriodPicker',
85
33
  components: {
86
- itfIcon,
87
- itfButton
88
- },
89
- filters: {
90
- formatMonth(month) {
91
- return DateTime.local().set({ month }).toFormat('MMM');
92
- }
34
+ itfPeriodPickerInline
93
35
  }
94
36
  })
95
37
  class itfPeriodPicker extends Vue {
@@ -97,51 +39,12 @@ class itfPeriodPicker extends Vue {
97
39
 
98
40
  @Prop({ type: Array }) value;
99
41
  @Prop({ type: String, default: 'ISO' }) valueFormat;
100
- @Prop({ type: String }) displayFormat;
101
42
  @Prop({ type: String, default: 'bottom-start' }) placement;
102
- @Prop({ type: String, default: 'days', validator: (value) => ['days', 'months', 'years'].includes(value) }) startView;
103
- @Prop({ type: Boolean, default: false }) onlyCalendar;
104
43
  @Prop({ type: String, default: '' }) placeholder;
105
-
106
- year = null;
44
+ @Prop({ type: String }) displayFormat;
107
45
 
108
46
  focused = false;
109
47
 
110
- tooltip = null;
111
-
112
- get quarters() {
113
- return [
114
- { Name: `${this.$t('components.quarter')} 1`, Months: [1, 2, 3], Number: 1 },
115
- { Name: `${this.$t('components.quarter')} 2`, Months: [4, 5, 6], Number: 2 },
116
- { Name: `${this.$t('components.quarter')} 3`, Months: [7, 8, 9], Number: 3 },
117
- { Name: `${this.$t('components.quarter')} 4`, Months: [10, 11, 12], Number: 4 },
118
- ];
119
- }
120
-
121
- get dateFormat() {
122
- return this.displayFormat || ITFSettings.defaultDisplayDateFormat;
123
- }
124
-
125
- get nextYear() {
126
- return this.year + 1;
127
- }
128
-
129
- get prevYear() {
130
- return this.year - 1;
131
- }
132
-
133
- setYear(year) {
134
- this.year = year;
135
- }
136
-
137
- isInvalid() {
138
- return this.itemLabel && this.itemLabel.isHasError();
139
- }
140
-
141
- isSuccess() {
142
- return this.itemLabel && this.itemLabel.isHasSuccess();
143
- }
144
-
145
48
  mounted() {
146
49
  const context = this.$el.closest('.itf-append-context') || document.body;
147
50
  this.tooltip = tippy(this.$refs.input, {
@@ -149,30 +52,17 @@ class itfPeriodPicker extends Vue {
149
52
  interactiveDebounce: 75,
150
53
  animation: 'scale',
151
54
  arrow: true,
152
- content: this.$refs.dropdown,
55
+ content: this.$refs.dropdown.$el,
153
56
  allowHTML: true,
154
57
  trigger: 'click',
155
58
  interactive: true,
156
59
  placement: this.placement,
157
60
  appendTo: context,
158
61
  });
159
- this.year = (this.value && this.value[0]) ? this.valueAsLuxon[0].year : DateTime.local().year;
160
62
  }
161
63
 
162
- get valueAsLuxon() {
163
- if (!this.value || this.value.length < 2) {
164
- return null;
165
- }
166
- if (this.valueFormat === 'ISO') {
167
- return [
168
- DateTime.fromISO(this.value[0]),
169
- DateTime.fromISO(this.value[1]),
170
- ];
171
- }
172
- return [
173
- DateTime.fromFormat(this.value[0], this.valueFormat),
174
- DateTime.fromFormat(this.value[1], this.valueFormat),
175
- ];
64
+ get dateFormat() {
65
+ return this.displayFormat || ITFSettings.defaultDisplayDateFormat;
176
66
  }
177
67
 
178
68
  get displayText() {
@@ -183,12 +73,29 @@ class itfPeriodPicker extends Vue {
183
73
  if (!this.valueAsLuxon || this.valueAsLuxon.length < 2) {
184
74
  return [];
185
75
  }
76
+ console.info(this.valueAsLuxon);
186
77
  return [
187
78
  this.valueAsLuxon[0].toFormat(this.dateFormat),
188
79
  this.valueAsLuxon[1].toFormat(this.dateFormat)
189
80
  ];
190
81
  }
191
82
 
83
+ get valueAsLuxon() {
84
+ if (!this.value || this.value.length < 2) {
85
+ return null;
86
+ }
87
+ if (this.valueFormat === 'ISO') {
88
+ return [
89
+ DateTime.fromISO(this.value[0]),
90
+ DateTime.fromISO(this.value[1]),
91
+ ];
92
+ }
93
+ return [
94
+ DateTime.fromFormat(this.value[0], this.valueFormat),
95
+ DateTime.fromFormat(this.value[1], this.valueFormat),
96
+ ];
97
+ }
98
+
192
99
  onFocus() {
193
100
  this.focused = true;
194
101
  }
@@ -197,71 +104,21 @@ class itfPeriodPicker extends Vue {
197
104
  this.focused = false;
198
105
  }
199
106
 
200
- selectInlineDate(date) {
201
- this.$emit('input', date);
202
- this.tooltip.hide();
203
- }
204
-
205
- isCurrentMonth(month) {
206
- if (!this.valueAsLuxon) {
207
- return false;
208
- }
209
- return Number(this.valueAsLuxon[0].toFormat('M')) === month && Number(this.valueAsLuxon[1].toFormat('M')) === month;
210
- }
211
-
212
- isOnlyQuarter(quarter) {
213
- if (!this.valueAsLuxon) {
214
- return false;
215
- }
216
- return this.isCurrentQuarter(quarter) && this.valueAsLuxon[0].month !== this.valueAsLuxon[1].month;
107
+ isInvalid() {
108
+ return this.itemLabel && this.itemLabel.isHasError();
217
109
  }
218
110
 
219
- isCurrentQuarter(quarter) {
220
- if (!this.valueAsLuxon) {
221
- return false;
222
- }
223
- return this.valueAsLuxon[0].quarter === quarter && this.valueAsLuxon[1].quarter === quarter;
111
+ isSuccess() {
112
+ return this.itemLabel && this.itemLabel.isHasSuccess();
224
113
  }
225
114
 
226
- isCurrentYear() {
227
- if (!this.valueAsLuxon) {
228
- return false;
229
- }
230
- return this.valueAsLuxon[0].hasSame(this.valueAsLuxon[0].startOf('year'), 'day')
231
- && this.valueAsLuxon[1].hasSame(this.valueAsLuxon[0].endOf('year'), 'day');
115
+ selectInlineDate(date) {
116
+ this.$emit('input', date);
232
117
  }
233
118
 
234
- updateValue(start, end) {
235
- if (!start) {
236
- this.$emit('input', []);
237
- return;
238
- }
239
- this.$emit('input', [
240
- (this.valueFormat === 'ISO') ? start.toISO() : start.toFormat(this.valueFormat),
241
- (this.valueFormat === 'ISO') ? end.toISO() : end.toFormat(this.valueFormat),
242
- ]);
119
+ onInput(val) {
120
+ this.$emit('input', val);
243
121
  this.tooltip.hide();
244
122
  }
245
-
246
- onYearSelect(year) {
247
- this.updateValue(
248
- DateTime.local().set({ year }).startOf('year'),
249
- DateTime.local().set({ year }).endOf('year'),
250
- );
251
- }
252
-
253
- onMonthSelect(month) {
254
- this.updateValue(
255
- DateTime.local().set({ month, year: this.year }).startOf('month'),
256
- DateTime.local().set({ month, year: this.year }).endOf('month'),
257
- );
258
- }
259
-
260
- onQuarterSelect([startMonth, endMonth]) {
261
- this.updateValue(
262
- DateTime.local().set({ month: startMonth, year: this.year }).startOf('quarter'),
263
- DateTime.local().set({ month: endMonth, year: this.year }).endOf('quarter'),
264
- );
265
- }
266
123
  }
267
124
  </script>
@@ -0,0 +1,187 @@
1
+ <template>
2
+ <div>
3
+ <div class="row mb-3">
4
+ <div class="col">
5
+ <itf-button icon @click="setYear(prevYear)">
6
+ <itf-icon name="chevron_left" />
7
+ </itf-button>
8
+ </div>
9
+ <div class="col">
10
+ <itf-button block @click="setYear(prevYear)">
11
+ {{prevYear}}
12
+ </itf-button>
13
+ </div>
14
+ <div class="col">
15
+ <itf-button color="outline-primary" block>
16
+ {{year}}
17
+ </itf-button>
18
+ </div>
19
+ <div class="col">
20
+ <itf-button block @click="setYear(nextYear)">
21
+ {{nextYear}}
22
+ </itf-button>
23
+ </div>
24
+ <div class="col text-end">
25
+ <itf-button icon @click="setYear(nextYear)">
26
+ <itf-icon name="chevron_right" />
27
+ </itf-button>
28
+ </div>
29
+ </div>
30
+
31
+ <itf-button block class="mb-3" :class="{'btn-whole': !isCurrentYear()}" :primary="isCurrentYear()" @click="onYearSelect(year)">
32
+ {{ $t('components.wholeYear') }}
33
+ </itf-button>
34
+
35
+ <div class="itf-periodpicker__quarters">
36
+ <div
37
+ v-for="quarter of quarters"
38
+ :key="quarter.Number"
39
+ class="itf-periodpicker__quarter"
40
+ :class="{ 'active': isOnlyQuarter(quarter.Number), 'active-inside': isCurrentQuarter(quarter.Number) }">
41
+ <div class="px-3 pt-2" @click="onQuarterSelect([quarter.Months[0], quarter.Months[2]])">
42
+ <small><b>{{ $t('components.quarter') }}</b></small><br>
43
+ <span class="quarter-number">{{ quarter.Number }}</span>
44
+ </div>
45
+
46
+ <div class="itf-periodpicker__months">
47
+ <itf-button
48
+ class="itf-periodpicker__month px-1"
49
+ v-for="month of quarter.Months"
50
+ :key="month"
51
+ :primary="isCurrentMonth(month)"
52
+ @click="onMonthSelect(month)"
53
+ >
54
+ {{ month | formatMonth }}
55
+ </itf-button>
56
+ </div>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ </template>
61
+ <script>
62
+ import { Vue, Component, Prop } from 'vue-property-decorator';
63
+ import { DateTime } from 'luxon';
64
+ import itfIcon from '../icon/Icon';
65
+ import itfButton from '../button/Button';
66
+
67
+ export default @Component({
68
+ name: 'itfPeriodPickerInline',
69
+ components: {
70
+ itfIcon,
71
+ itfButton
72
+ },
73
+ filters: {
74
+ formatMonth(month) {
75
+ return DateTime.local().set({ month }).toFormat('MMM');
76
+ }
77
+ }
78
+ })
79
+ class itfPeriodPickerInline extends Vue {
80
+ @Prop({ type: Array }) value;
81
+ @Prop({ type: String, default: 'ISO' }) valueFormat;
82
+
83
+ year = null;
84
+
85
+ get quarters() {
86
+ return [
87
+ { Name: `${this.$t('components.quarter')} 1`, Months: [1, 2, 3], Number: 1 },
88
+ { Name: `${this.$t('components.quarter')} 2`, Months: [4, 5, 6], Number: 2 },
89
+ { Name: `${this.$t('components.quarter')} 3`, Months: [7, 8, 9], Number: 3 },
90
+ { Name: `${this.$t('components.quarter')} 4`, Months: [10, 11, 12], Number: 4 },
91
+ ];
92
+ }
93
+
94
+ get nextYear() {
95
+ return this.year + 1;
96
+ }
97
+
98
+ get prevYear() {
99
+ return this.year - 1;
100
+ }
101
+
102
+ setYear(year) {
103
+ this.year = year;
104
+ }
105
+
106
+ mounted() {
107
+ this.year = (this.value && this.value[0]) ? this.valueAsLuxon[0].year : DateTime.local().year;
108
+ }
109
+
110
+ get valueAsLuxon() {
111
+ if (!this.value || this.value.length < 2) {
112
+ return null;
113
+ }
114
+ if (this.valueFormat === 'ISO') {
115
+ return [
116
+ DateTime.fromISO(this.value[0]),
117
+ DateTime.fromISO(this.value[1]),
118
+ ];
119
+ }
120
+ return [
121
+ DateTime.fromFormat(this.value[0], this.valueFormat),
122
+ DateTime.fromFormat(this.value[1], this.valueFormat),
123
+ ];
124
+ }
125
+
126
+ isCurrentMonth(month) {
127
+ if (!this.valueAsLuxon) {
128
+ return false;
129
+ }
130
+ return Number(this.valueAsLuxon[0].toFormat('M')) === month && Number(this.valueAsLuxon[1].toFormat('M')) === month;
131
+ }
132
+
133
+ isOnlyQuarter(quarter) {
134
+ if (!this.valueAsLuxon) {
135
+ return false;
136
+ }
137
+ return this.isCurrentQuarter(quarter) && this.valueAsLuxon[0].month !== this.valueAsLuxon[1].month;
138
+ }
139
+
140
+ isCurrentQuarter(quarter) {
141
+ if (!this.valueAsLuxon) {
142
+ return false;
143
+ }
144
+ return this.valueAsLuxon[0].quarter === quarter && this.valueAsLuxon[1].quarter === quarter;
145
+ }
146
+
147
+ isCurrentYear() {
148
+ if (!this.valueAsLuxon) {
149
+ return false;
150
+ }
151
+ return this.valueAsLuxon[0].hasSame(this.valueAsLuxon[0].startOf('year'), 'day')
152
+ && this.valueAsLuxon[1].hasSame(this.valueAsLuxon[0].endOf('year'), 'day');
153
+ }
154
+
155
+ updateValue(start, end) {
156
+ if (!start) {
157
+ this.$emit('input', []);
158
+ return;
159
+ }
160
+ this.$emit('input', [
161
+ (this.valueFormat === 'ISO') ? start.toISO() : start.toFormat(this.valueFormat),
162
+ (this.valueFormat === 'ISO') ? end.toISO() : end.toFormat(this.valueFormat),
163
+ ]);
164
+ }
165
+
166
+ onYearSelect(year) {
167
+ this.updateValue(
168
+ DateTime.local().set({ year }).startOf('year'),
169
+ DateTime.local().set({ year }).endOf('year'),
170
+ );
171
+ }
172
+
173
+ onMonthSelect(month) {
174
+ this.updateValue(
175
+ DateTime.local().set({ month, year: this.year }).startOf('month'),
176
+ DateTime.local().set({ month, year: this.year }).endOf('month'),
177
+ );
178
+ }
179
+
180
+ onQuarterSelect([startMonth, endMonth]) {
181
+ this.updateValue(
182
+ DateTime.local().set({ month: startMonth, year: this.year }).startOf('quarter'),
183
+ DateTime.local().set({ month: endMonth, year: this.year }).endOf('quarter'),
184
+ );
185
+ }
186
+ }
187
+ </script>
@@ -2,6 +2,7 @@ import { storiesOf } from '@storybook/vue';
2
2
  import itfDatePicker from './DatePicker.vue';
3
3
  import itfMonthPicker from './MonthPicker.vue';
4
4
  import itfPeriodPicker from './PeriodPicker';
5
+ import itfPeriodPickerInline from './PeriodPickerInline';
5
6
  import itfDatePickerInline from './DatePickerInline.vue';
6
7
  import itfDateRangePicker from './DateRangePicker.vue';
7
8
  import itfDateRangePickerInline from './DateRangePickerInline.vue';
@@ -13,6 +14,7 @@ storiesOf('Common', module)
13
14
  itfApp,
14
15
  itfDatePicker,
15
16
  itfPeriodPicker,
17
+ itfPeriodPickerInline,
16
18
  itfMonthPicker,
17
19
  itfDateRangePicker,
18
20
  itfDatePickerInline,
@@ -86,6 +88,8 @@ storiesOf('Common', module)
86
88
  <h2>Period</h2>
87
89
 
88
90
  <itf-period-picker :value="dateRange" v-model.lazy="dateRange"></itf-period-picker>
91
+
92
+ <itf-period-picker-inline :value="dateRange" v-model.lazy="dateRange"></itf-period-picker-inline>
89
93
  </itf-app>
90
94
  </div>`,
91
95
  }));
@@ -29,9 +29,8 @@
29
29
  @input="onFilterChange({ value: $event })"
30
30
  />
31
31
  </template>
32
- <template v-else-if="type === 'period-selector'">
33
- <itf-period-picker
34
- style="margin: -.5rem"
32
+ <template v-else-if="type === 'timeframe'">
33
+ <itf-period-picker-inline
35
34
  :value="value.value"
36
35
  value-format="yyyy-MM-dd"
37
36
  @input="onFilterChange({ value: $event })"
@@ -69,6 +68,7 @@
69
68
  <style lang="scss">
70
69
  :root {
71
70
  --filter-badge__default-color: #475266;
71
+ --filter-badge__icon-color: #A7AFBB;
72
72
  --filter-badge__default-border-color: #0000001A;
73
73
  --filter-badge__default-bg-color: transparent;
74
74
  --filter-badge__default-bg-color-hover: #1A4A970D;
@@ -83,6 +83,10 @@
83
83
  --filter-badge__padding-y: .5rem;
84
84
  --filter-badge__padding-x: .75rem;
85
85
  }
86
+ body[data-theme="dark"] {
87
+ --filter-badge__default-border-color: #FFFFFF1A;
88
+ --filter-badge__selected-color: #efd877;
89
+ }
86
90
  .filter-pill {
87
91
  align-items: center;
88
92
  font-size: 14px;
@@ -95,6 +99,7 @@
95
99
 
96
100
  .icon {
97
101
  margin: -2px;
102
+ color: var(--filter-badge__icon-color);
98
103
  }
99
104
  &:hover {
100
105
  background-color: var(--filter-badge__default-bg-color-hover);
@@ -112,7 +117,7 @@
112
117
  }
113
118
  }
114
119
  .filter-pill__label {
115
- color: var(--filter-badge__default-color);
120
+ //color: var(--filter-badge__default-color);
116
121
  padding: var(--filter-badge__padding-y) 0 var(--filter-badge__padding-y) var(--filter-badge__padding-x);
117
122
  max-width: 330px;
118
123
  text-overflow: ellipsis;
@@ -137,7 +142,7 @@
137
142
  padding: 0 calc(var(--filter-badge__padding-x) / 2) 0 calc(var(--filter-badge__padding-x) / 4);
138
143
 
139
144
  svg {
140
- color: var(--filter-badge__default-color);
145
+ //color: var(--filter-badge__default-color);
141
146
  }
142
147
  &.filter-pill__icon-invalid svg {
143
148
  color: var(--filter-badge__invalid-color);
@@ -154,7 +159,7 @@ import itfButton from '../button/Button';
154
159
  import itfDropdown from '../dropdown/Dropdown.vue';
155
160
  import itfDatePickerInline from '../datepicker/DatePickerInline.vue';
156
161
  import itfDateRangePickerInline from '../datepicker/DateRangePickerInline.vue';
157
- import itfPeriodPicker from '../datepicker/PeriodPicker.vue'
162
+ import itfPeriodPickerInline from '../datepicker/PeriodPickerInline.vue'
158
163
  import itfTextField from '../text-field/TextField.vue';
159
164
  import FilterFacetsList from './FilterFacetsList';
160
165
  import FilterAmountRange from './FilterAmountRange.vue';
@@ -166,7 +171,7 @@ export default @Component({
166
171
  itfDropdown,
167
172
  itfDatePickerInline,
168
173
  itfDateRangePickerInline,
169
- itfPeriodPicker,
174
+ itfPeriodPickerInline,
170
175
  itfTextField,
171
176
  FilterFacetsList,
172
177
  FilterAmountRange
@@ -23,9 +23,9 @@
23
23
  <div v-for="(val, n) of mappedValues" :key="n" class="dropdown-item px-2" :class="{'active': val.isSelected}" @click="onFilterClick(val)">
24
24
  <span class="facet-name text-dark d-flex align-items-center">
25
25
  <itf-checkbox ungrouped :value="val.isSelected" class="m-0" />
26
- <div class="w-100 text-truncate">{{ val.label }}</div>
26
+ <div class="w-100 text-truncate">{{ val.label }} <span v-if="val.description" class="small"><br/>{{ val.description }}</span></div>
27
27
  </span>
28
- <span v-if="val.count" class="facet-stat text-muted">
28
+ <span v-if="val.count" class="facet-stat">
29
29
  {{ val.count }}
30
30
  <span class="facet-bar"><span :style="{'--bar-width': `${getPercent(val)}%`}" class="facet-bar-progress" /></span>
31
31
  </span>
@@ -60,7 +60,7 @@
60
60
  justify-content: space-between;
61
61
  position: relative;
62
62
  box-sizing: border-box;
63
- height: 1.75rem;
63
+ min-height: 1.75rem;
64
64
  width: 100%;
65
65
  font-size: 0.875rem;
66
66
  line-height: 1.25rem;
@@ -76,16 +76,17 @@
76
76
  margin: 1px 0;
77
77
  &.active {
78
78
  .facet-bar-progress {
79
- background-color: var(--bs-blue);
79
+ background-color: var(--bs-primary);
80
80
  }
81
81
  }
82
82
  .facet-name {
83
83
  min-width: 0;
84
84
  text-align: left;
85
+ line-height: 100%;
85
86
  white-space: nowrap;
86
87
 
87
88
  .itf-checkbox {
88
- min-height: auto;
89
+ min-height: 1.25rem;
89
90
  }
90
91
  }
91
92
  .facet-stat {
@@ -104,7 +105,7 @@
104
105
  width: var(--bar-width);
105
106
  min-width: 5px;
106
107
  height: 10px;
107
- background-color: rgba(var(--bs-blue-rgb), 50%);
108
+ background-color: rgba(var(--bs-primary-rgb), 50%);
108
109
  transition: width 0.3s ease 0s;
109
110
  }
110
111
  }
@@ -11,7 +11,7 @@
11
11
  prepend-icon="search"
12
12
  :delay-input="250"
13
13
  clearable
14
- :value="filterValue.query"
14
+ :value="filterValueQuery"
15
15
  @input="(e) => onFilterChange({ type: 'text', name: 'query' }, { value: e })"
16
16
  />
17
17
  </div>
@@ -27,20 +27,29 @@
27
27
  <slot name="after-filter-btn"></slot>
28
28
  </div>
29
29
  </div>
30
- <div class="d-flex align-items-center justify-content-between w-100">
31
- <div v-if="showFilters && showFilter" class="d-flex gap-2 flex-nowrap filters-row">
30
+ <div class="d-flex align-items-start justify-content-between w-100">
31
+ <div v-show="showFilters && showFilter" class="gap-2 filters-row" ref="container" :class="{'expanded': isFilterExpanded}">
32
32
  <filter-badge
33
33
  v-for="(facet, n) in visibleFilters"
34
34
  :key="n"
35
+ class="itf-filter-panel__badge"
36
+ :ref="'item-' + n"
35
37
  v-model="filter[facet.name]"
36
38
  :is-default="filter[facet.name].isDefault"
37
39
  :text="filter[facet.name].label"
38
40
  :type="facet.type"
39
41
  :icon="facet.icon"
40
42
  :options="facet.options"
43
+ :class="{ hidden: !visibleItems.has(n) && !isFilterExpanded }"
41
44
  @change="onFilterChange(facet, $event)"
42
45
  />
43
46
  </div>
47
+ <div v-if="showFilters && showFilter && (visibleItems.size < visibleFilters.length || (isFilterExpanded && visibleItems.size === visibleFilters.length))">
48
+ <itf-button icon default small class="itf-filter-panel__filters" @click="toggleExpandFilter">
49
+ <itf-icon v-if="isFilterExpanded" name="minus" />
50
+ <itf-icon v-else name="plus" />
51
+ </itf-button>
52
+ </div>
44
53
  <slot name="after-filters"></slot>
45
54
  </div>
46
55
  <div v-if="loading">
@@ -51,6 +60,18 @@
51
60
  </template>
52
61
  <style lang="scss">
53
62
  .itf-filter-panel {
63
+ &__badge {
64
+ transition: opacity 0.3s ease-in-out;
65
+
66
+ &.hidden {
67
+ opacity: 0;
68
+ pointer-events: none;
69
+ visibility: hidden;
70
+ }
71
+ }
72
+ &__filters {
73
+ outline: 1px solid var(--filter-badge__default-border-color);
74
+ }
54
75
  .itf-text-field:not(.is-valid):not(.is-invalid) .itf-icon {
55
76
  color: #8E97A5;
56
77
  }
@@ -69,11 +90,21 @@
69
90
  }
70
91
 
71
92
  .filters-row {
72
- @media (max-width: 768px) {
73
- overflow: auto;
74
- width: 100%;
75
- padding: 2px;
76
- margin: -2px;
93
+ display: flex;
94
+ overflow: hidden;
95
+ width: 100%;
96
+ padding: 2px;
97
+ margin: -2px;
98
+ flex-wrap: nowrap;
99
+
100
+ &.expanded {
101
+ flex-wrap: wrap;
102
+
103
+ .itf-filter-panel__badge.hidden {
104
+ opacity: 1;
105
+ visibility: visible;
106
+ pointer-events: all;
107
+ }
77
108
  }
78
109
  }
79
110
  }
@@ -115,12 +146,63 @@ class FilterPanel extends Vue {
115
146
  @Prop({ type: String, default: function() { return this.$t('components.filter.search'); } }) searchPlaceholder;
116
147
 
117
148
  filter = {};
118
- filterValue = {};
149
+ filterValue = null;
119
150
  filters = [];
120
151
  loading = false;
121
152
  showFilters = true;
153
+ isFilterExpanded = false;
154
+ observer = null;
155
+
156
+ periodFilters = ['period', 'timeframe'];
157
+ visibleItems = new Set();
158
+
159
+ toggleExpandFilter() {
160
+ this.isFilterExpanded = !this.isFilterExpanded;
161
+ }
162
+
163
+ beforeDestroy() {
164
+ if (this.observer) {
165
+ this.observer.disconnect();
166
+ }
167
+ }
168
+
169
+ initObserver() {
170
+ if (this.observer) {
171
+ this.observer.disconnect();
172
+ }
173
+ if (!this.$refs.container) {
174
+ return;
175
+ }
176
+ this.observer = new IntersectionObserver(
177
+ (entries) => {
178
+ entries.forEach(entry => {
179
+ const index = parseInt(entry.target.dataset.index);
180
+ if (entry.isIntersecting) {
181
+ this.visibleItems.add(index); // Додаємо, якщо елемент у полі зору
182
+ } else {
183
+ this.visibleItems.delete(index); // Видаляємо, якщо вийшов за межі
184
+ }
185
+ this.$forceUpdate(); // Оновлюємо Vue, бо Set не є реактивним
186
+ });
187
+ },
188
+ { root: this.$refs.container, threshold: 1.0 }
189
+ );
190
+
191
+ // Спостерігаємо за кожним елементом
192
+ this.$nextTick(() => {
193
+ for (const index in this.visibleFilters) {
194
+ const item = this.$refs[`item-${index}`][0];
195
+ if (item) {
196
+ item.$el.dataset.index = index; // Зберігаємо індекс у dataset
197
+ this.observer.observe(item.$el);
198
+ }
199
+ }
200
+ });
201
+ }
122
202
 
123
- periodFilters = ['period', 'period-selector'];
203
+ get filterValueQuery() {
204
+ return this.filterValue?.query ?? '';
205
+ }
124
206
 
125
207
  get visibleFilters() {
126
208
  if (this.mini) {
@@ -194,13 +276,25 @@ class FilterPanel extends Vue {
194
276
  if (this.search) {
195
277
  filterValue.query = payload.query;
196
278
  }
197
- const prevFilter = JSON.stringify(this.filter).concat(JSON.stringify(this.filterValue));
198
- const newFilter = JSON.stringify(filter).concat(JSON.stringify(filterValue));
199
- if (prevFilter !== newFilter) {
279
+ const prevFilter = JSON.stringify(this.getVisibleFilters(this.filterValue));//.concat(JSON.stringify(this.filterValue));
280
+ const newFilter = JSON.stringify(this.getVisibleFilters(filterValue));//.concat(JSON.stringify(filterValue));
281
+ if (prevFilter !== newFilter || !this.filterValue) {
200
282
  this.filter = filter;
201
283
  this.filterValue = filterValue;
202
284
  this.$emit('input', this.filterValue);
285
+ this.initObserver();
286
+ }
287
+ }
288
+
289
+ getVisibleFilters(filter) {
290
+ const result = [];
291
+ const facets = Object.values(this.filter);
292
+ for (const facet of facets) {
293
+ if (!facet.isDefault && !facet.hidden) {
294
+ result.push(filter[facet.name]);
295
+ }
203
296
  }
297
+ return result.filter(Boolean);
204
298
  }
205
299
 
206
300
  setFilter(field, value) {
@@ -159,8 +159,8 @@ $double-an-time: $an-time * 2;
159
159
  </style>
160
160
  <script lang="ts">
161
161
  import { Vue, Component, Prop } from 'vue-property-decorator';
162
- import Panel from './Panel.vue';
163
- import {hashToStack, stackToHash} from "@itfin/components/src/components/panels/helpers";
162
+ import Panel from './Panel';
163
+ import {hashToStack, stackToHash} from "./helpers";
164
164
 
165
165
  interface VisualOptions {
166
166
  title: string;
@@ -88,7 +88,6 @@ import itfTableGroup from './TableGroup.vue';
88
88
  import itfTableHeader from './TableHeader.vue';
89
89
  import itfNoticePopout from '../popover/NoticePopout.vue';
90
90
  import './table2.scss';
91
- import itfTableBody from '@itfin/components/src/components/table/TableBody.vue';
92
91
 
93
92
  export default @Component({
94
93
  name: 'itfTable2',
@@ -96,7 +95,6 @@ export default @Component({
96
95
  return { tableEl: this }; // do not use Provide from vue-property-decorator
97
96
  },
98
97
  components: {
99
- itfTableBody,
100
98
  itfCheckboxGroup,
101
99
  itfTableHeader,
102
100
  itfButton,
@@ -95,11 +95,11 @@
95
95
 
96
96
  <!-- Лінія додати нову -->
97
97
  <div v-if="isShowTable && addNewRows"
98
- class="table-row-template d-flex align-items-stretch">
98
+ class="table-row-template table-row-template__new-row d-flex align-items-stretch">
99
99
  <div class="shadow-area"></div>
100
100
  <a href="" @click.prevent="$emit('new', title)" data-test="table-add-new-item"
101
101
  class="d-flex align-items-center flex-grow-1 table-add-new-item text-decoration-none">
102
- <span class="d-sticky d-flex align-items-center py-1">
102
+ <span class="d-sticky d-flex align-items-center py-1 px-2 small">
103
103
  <itf-icon name="plus"/>
104
104
  <span>{{ newLabel }}</span>
105
105
  </span>
@@ -268,7 +268,11 @@
268
268
  min-height: var(--table-small-row-size);
269
269
  }
270
270
 
271
+ .table-row-template.table-row-template__new-row {
272
+ min-height: 2rem;
273
+ }
271
274
  .table-add-new-item {
275
+ background-color: var(--itf-table-header-bg);
272
276
  border-right:var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
273
277
  border-left:var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
274
278
  border-bottom: var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
@@ -277,7 +281,7 @@
277
281
  border-bottom-right-radius: var(--itf-table-table-border-radius);
278
282
 
279
283
  & > span {
280
- left: var(--shadow-area-width);
284
+ left: calc(var(--shadow-area-width) + 4px);
281
285
  position: sticky;
282
286
  padding-left: var(--shadow-area-width);
283
287
  //border-left: var(--itf-table-border-base-width) solid var(--itf-table-border-base-color);
@@ -23,6 +23,8 @@
23
23
  --itf-table-summary-text: var(--bs-tertiary-color);
24
24
  --itf-table-table-border-radius: 1rem;
25
25
  --itf-table-header-height: 2.25rem;
26
+ --itf-table-divider-bg: #F7F8FA;
27
+ --itf-table-divider-border: rgba(238, 238, 238, 1);
26
28
 
27
29
  --group-title-height: 40px;
28
30
  --table-row-height: none;
@@ -43,6 +45,9 @@ body[data-theme="dark"] {
43
45
  --itf-table-selected-bg: #011534;
44
46
  --itf-table-active-bg: #022e72;
45
47
  --itf-table-summary-text: #82909d80;
48
+ --itf-table-border-base-color: var(--itf-table-header-bg);
49
+ --itf-table-divider-bg: #0f0f0f;
50
+ --itf-table-divider-border: rgb(100, 100, 100, .1);
46
51
  }
47
52
  .itf-table2 {
48
53
  font-size: var(--itf-table-content-font-size, var(--itf-table-font-size));
@@ -60,7 +65,7 @@ body[data-theme="dark"] {
60
65
  height: 100%;
61
66
  }
62
67
  .scroller {
63
- margin-bottom: .5rem;
68
+ //margin-bottom: .5rem;
64
69
  }
65
70
  .scrollable-x {
66
71
  overflow-x: scroll;
@@ -435,11 +440,11 @@ body[data-theme="dark"] {
435
440
  }
436
441
 
437
442
  &__row-divider {
438
- background-color: #F7F8FA;
443
+ background-color: var(--itf-table-divider-bg);
439
444
  height: 5px;
440
445
  padding: 0;
441
- border-top: 1px solid rgba(238, 238, 238, 1);
442
- border-bottom: 1px solid rgba(238, 238, 238, 1);
446
+ border-top: 1px solid var(--itf-table-divider-border);
447
+ border-bottom: 1px solid var(--itf-table-divider-border);
443
448
  }
444
449
  //&:hover, &.permanent-editable-border {
445
450
  // .table-view-item-value.editable {
@@ -42,12 +42,11 @@
42
42
  </itf-dropdown>
43
43
 
44
44
  <itf-segmented-control
45
- v-if="tabs.length > 1"
45
+ v-if="tabs.length"
46
46
  class="small"
47
- :value="currentTab"
47
+ v-model="currentTab"
48
48
  item-key="value"
49
49
  :items="tabs"
50
- @input="updateTabs"
51
50
  >
52
51
  <template #item="{ item }">
53
52
  <div class="d-flex align-items-center">
@@ -119,7 +118,7 @@
119
118
 
120
119
  </template>
121
120
  <script>
122
- import {Vue, ModelSync, Component, Prop, Inject, PropSync, Watch} from 'vue-property-decorator';
121
+ import { Vue, ModelSync, Component, Prop, Inject } from 'vue-property-decorator';
123
122
  import loading from '../../directives/loading';
124
123
  import itfTable from '../table/Table2.vue';
125
124
  import itfFilterPanel from '../filter/FilterPanel.vue';
@@ -127,13 +126,14 @@ import itfPagination from '../pagination/Pagination2.vue';
127
126
  import itfTableBody from "../table/TableBody.vue";
128
127
  import itfIcon from "../icon/Icon.vue";
129
128
  import itfDropdown from "../dropdown/Dropdown.vue";
130
- import itfSegmentedControl from '@itfin/components/src/components/segmented-control/SegmentedControl.vue';
129
+ import itfSegmentedControl from '../segmented-control/SegmentedControl.vue';
131
130
 
132
131
  export default @Component({
133
132
  name: 'itfView',
134
133
  components: {
135
134
  itfSegmentedControl,
136
- itfDropdown, itfIcon,
135
+ itfDropdown,
136
+ itfIcon,
137
137
  itfPagination,
138
138
  itfFilterPanel,
139
139
  itfTableBody,
@@ -149,7 +149,10 @@ class itfView extends Vue {
149
149
 
150
150
  @Prop({ type: Boolean }) loading;
151
151
  @Prop({ type: Array }) filters;
152
+ // @Prop({ type: Object, required: true }) schema;
152
153
  @Prop({ type: Object }) schema;
154
+ // @Prop({ default: 20 }) size;
155
+ // @Prop({ default: 1 }) page;
153
156
  @Prop(String) defaultSorting;
154
157
  @Prop(String) endpoint;
155
158
  @Prop(String) filtersEndpoint;
@@ -194,6 +197,10 @@ class itfView extends Vue {
194
197
  return this.tab;
195
198
  }
196
199
 
200
+ set currentTab(val) {
201
+ this.$emit('update:tab', val);
202
+ }
203
+
197
204
  get tabs() {
198
205
  const views = [];
199
206
  if (this.listViewEnabled) {
@@ -212,6 +219,7 @@ class itfView extends Vue {
212
219
  }
213
220
 
214
221
  get tableSchema() {
222
+ // return this.tableColumns || this.schema;
215
223
  if (this.tableColumns) {
216
224
  return {
217
225
  properties: this.tableColumns
@@ -224,12 +232,13 @@ class itfView extends Vue {
224
232
  }
225
233
 
226
234
  created() {
227
- const defaultSize = localStorage.getItem('sizePerPage') ?? 20;
228
-
229
- const { page, size, sorting } = this.panel.getPayload() ?? {};
230
- this.page = page ?? 1;
231
- this.size = size ?? defaultSize;
232
- this.sorting = sorting ?? this.defaultSorting;
235
+ // const defaultSize = localStorage.getItem('sizePerPage') ?? 20;
236
+ //
237
+ // const { page, size, sorting } = this.panel.getPayload() ?? {};
238
+ // this.page = page ?? 1;
239
+ // this.size = size ?? defaultSize;
240
+ // this.sorting = sorting ?? this.defaultSorting;
241
+ this.sorting = this.defaultSorting;
233
242
  }
234
243
 
235
244
  mounted() {
package/src/locales/en.js CHANGED
@@ -99,7 +99,7 @@ module.exports = {
99
99
  copyingToClipboardWasSuccessful: 'Copying to clipboard was successful',
100
100
  },
101
101
  table: {
102
- new: 'New',
102
+ new: 'New row',
103
103
  noResults: 'No items',
104
104
  sortAscending: 'Sort ascending',
105
105
  sortDescending: 'Sort descending',
package/src/locales/uk.js CHANGED
@@ -104,7 +104,7 @@ module.exports = {
104
104
  next: 'Наступна',
105
105
  },
106
106
  table: {
107
- new: 'Додати',
107
+ new: 'Додати рядок',
108
108
  noResults: 'Немає записів',
109
109
  sortAscending: 'Сортувати за зростанням',
110
110
  sortDescending: 'Сортувати за спаданням',