@itfin/components 1.0.52 → 1.0.56
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 +1 -3
- package/src/assets/scss/_variables.scss +2 -0
- package/src/components/app/App.vue +0 -3
- package/src/components/avatar/Avatar.vue +1 -1
- package/src/components/breadcrumbs/Breadcrumbs.vue +1 -1
- package/src/components/button/Button.vue +1 -1
- package/src/components/datepicker/DatePicker.vue +2 -3
- package/src/components/datepicker/DatePickerInline.vue +6 -4
- package/src/components/datepicker/DateRangePicker.vue +1 -2
- package/src/components/datepicker/MonthPicker.vue +1 -2
- package/src/components/datepicker/PeriodPicker.vue +2 -1
- package/src/components/modal/Modal.vue +0 -3
- package/src/components/overlay/SensitiveOverlay.vue +3 -3
- package/src/components/pagination/Pagination.vue +127 -0
- package/src/components/pagination/index.stories.js +41 -0
- package/src/components/popover/Popover.vue +6 -6
- package/src/components/segmented-control/SegmentedControl.vue +197 -108
- package/src/components/select/Select.vue +1 -1
- package/src/directives/appendToBody.js +1 -2
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itfin/components",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
|
|
3
|
+
"version": "1.0.56",
|
|
5
4
|
"main": "dist/itfin-components.umd.js",
|
|
6
5
|
"unpkg": "dist/itfin-components.common.js",
|
|
7
6
|
"author": "Vitalii Savchuk <esvit666@gmail.com>",
|
|
@@ -31,7 +30,6 @@
|
|
|
31
30
|
"lodash": "^4.17.20",
|
|
32
31
|
"luxon": "^2.0.2",
|
|
33
32
|
"pdfjs-dist": "^2.10.377",
|
|
34
|
-
"ramjet": "^0.5.0",
|
|
35
33
|
"tippy.js": "^6.3.2",
|
|
36
34
|
"vue": "^2.6.12",
|
|
37
35
|
"vue-imask": "^6.2.2",
|
|
@@ -67,6 +67,8 @@ $dark-btn-secondary-color: $dark-secondary;
|
|
|
67
67
|
$dark-input-box-shadow: #3d3d3d;
|
|
68
68
|
$dark-input-focus-border: rgb(244 206 176 / 25%);
|
|
69
69
|
|
|
70
|
+
$pagination-border-width: 0;
|
|
71
|
+
|
|
70
72
|
// 3. Include remainder of required Bootstrap stylesheets
|
|
71
73
|
@import "~bootstrap/scss/variables.scss";
|
|
72
74
|
@import "~bootstrap/scss/mixins.scss";
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
:class="{'active': n === items.length - 1}"
|
|
10
10
|
:aria-current="n === items.length - 1 ? 'page' : null"
|
|
11
11
|
>
|
|
12
|
-
<span v-if="crumb.disabled">{{crumb.text}}</span>
|
|
12
|
+
<span v-if="crumb.disabled || !crumb.to">{{crumb.text}}</span>
|
|
13
13
|
<nuxt-link v-else :to="crumb.to" :exact="crumb.exact">{{crumb.text}}</nuxt-link>
|
|
14
14
|
</li>
|
|
15
15
|
</ol>
|
|
@@ -74,7 +74,7 @@ import { IMask, IMaskComponent } from 'vue-imask';
|
|
|
74
74
|
import { DateTime } from 'luxon';
|
|
75
75
|
import { debounce } from '../../helpers/debounce';
|
|
76
76
|
import tippy from 'tippy.js';
|
|
77
|
-
import itfIcon from '../icon/Icon';
|
|
77
|
+
import itfIcon from '../icon/Icon.vue';
|
|
78
78
|
import itfDatePickerInline from './DatePickerInline.vue';
|
|
79
79
|
import ITFSettings from '../../ITFSettings';
|
|
80
80
|
|
|
@@ -88,7 +88,6 @@ export default @Component({
|
|
|
88
88
|
})
|
|
89
89
|
class itfDatePicker extends Vue {
|
|
90
90
|
@Inject({ default: null }) itemLabel;
|
|
91
|
-
@Inject({ default: null }) appendContext;
|
|
92
91
|
|
|
93
92
|
@Prop({ type: String }) value;
|
|
94
93
|
@Prop({ type: [Number, String], default: 300 }) delayInput;
|
|
@@ -131,7 +130,7 @@ class itfDatePicker extends Vue {
|
|
|
131
130
|
|
|
132
131
|
mounted() {
|
|
133
132
|
// якщо в модалці, то контекст модалки, якщо ні, то аплікейшена
|
|
134
|
-
const context =
|
|
133
|
+
const context = this.$el.closest('.itf-append-context') || document.body;
|
|
135
134
|
this.tooltip = tippy(this.$refs.input.$el, {
|
|
136
135
|
interactiveBorder: 30,
|
|
137
136
|
interactiveDebounce: 75,
|
|
@@ -118,9 +118,8 @@ class itfDatePickerInline extends Vue {
|
|
|
118
118
|
}
|
|
119
119
|
if (this.range) {
|
|
120
120
|
this.updateValue(
|
|
121
|
-
DateTime.fromJSDate(this.calendar.rangeDateFrom).toFormat(this.displayFormat)
|
|
122
|
-
|
|
123
|
-
DateTime.fromJSDate(this.calendar.rangeDateFrom).toFormat(this.displayFormat)
|
|
121
|
+
DateTime.fromJSDate(this.calendar.rangeDateFrom).toFormat(this.displayFormat),
|
|
122
|
+
false
|
|
124
123
|
);
|
|
125
124
|
} else {
|
|
126
125
|
this.updateValue(DateTime.fromJSDate(date).toFormat(this.displayFormat));
|
|
@@ -131,7 +130,10 @@ class itfDatePickerInline extends Vue {
|
|
|
131
130
|
if (!this.customDays[strDate]) {
|
|
132
131
|
return { html: false, classes: false };
|
|
133
132
|
}
|
|
134
|
-
return {
|
|
133
|
+
return {
|
|
134
|
+
html: this.customDays[strDate].text || false,
|
|
135
|
+
classes: this.customDays[strDate].class || false
|
|
136
|
+
};
|
|
135
137
|
},
|
|
136
138
|
});
|
|
137
139
|
if (this.valueAsLuxon) {
|
|
@@ -146,7 +146,6 @@ export default @Component({
|
|
|
146
146
|
})
|
|
147
147
|
class itfDateRangePicker extends Vue {
|
|
148
148
|
@Inject({ default: null }) itemLabel;
|
|
149
|
-
@Inject({ default: null }) appendContext;
|
|
150
149
|
|
|
151
150
|
@Prop({ default: () => ([]) }) value;
|
|
152
151
|
@Prop({ type: String, default: 'ISO' }) valueFormat;
|
|
@@ -189,7 +188,7 @@ class itfDateRangePicker extends Vue {
|
|
|
189
188
|
|
|
190
189
|
mounted() {
|
|
191
190
|
// якщо в модалці, то контекст модалки, якщо ні, то аплікейшена
|
|
192
|
-
const context =
|
|
191
|
+
const context = this.$el.closest('.itf-append-context') || document.body;
|
|
193
192
|
this.tooltip = tippy(this.$refs.group, {
|
|
194
193
|
interactiveBorder: 30,
|
|
195
194
|
interactiveDebounce: 75,
|
|
@@ -80,7 +80,6 @@ export default @Component({
|
|
|
80
80
|
})
|
|
81
81
|
class itfMonthPicker extends Vue {
|
|
82
82
|
@Inject({ default: null }) itemLabel;
|
|
83
|
-
@Inject({ default: null }) appendContext;
|
|
84
83
|
|
|
85
84
|
@Prop({ type: String }) value;
|
|
86
85
|
@Prop({ type: String, default: 'ISO' }) valueFormat;
|
|
@@ -102,7 +101,7 @@ class itfMonthPicker extends Vue {
|
|
|
102
101
|
|
|
103
102
|
mounted() {
|
|
104
103
|
// якщо в модалці, то контекст модалки, якщо ні, то аплікейшена
|
|
105
|
-
const context =
|
|
104
|
+
const context = this.$el.closest('.itf-append-context') || document.body;
|
|
106
105
|
this.tooltip = tippy(this.$refs.input, {
|
|
107
106
|
interactiveBorder: 30,
|
|
108
107
|
interactiveDebounce: 75,
|
|
@@ -233,6 +233,7 @@ class itfPeriodPicker extends Vue {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
mounted() {
|
|
236
|
+
const context = this.$el.closest('.itf-append-context') || document.body;
|
|
236
237
|
this.tooltip = tippy(this.$refs.input, {
|
|
237
238
|
interactiveBorder: 30,
|
|
238
239
|
interactiveDebounce: 75,
|
|
@@ -243,7 +244,7 @@ class itfPeriodPicker extends Vue {
|
|
|
243
244
|
trigger: 'click',
|
|
244
245
|
interactive: true,
|
|
245
246
|
placement: this.placement,
|
|
246
|
-
appendTo:
|
|
247
|
+
appendTo: context,
|
|
247
248
|
});
|
|
248
249
|
}
|
|
249
250
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
|
|
3
|
-
<div class="
|
|
3
|
+
<div class="itf-sensitive-overlay">
|
|
4
4
|
<slot></slot>
|
|
5
5
|
|
|
6
|
-
<div class="
|
|
6
|
+
<div class="itf-sensitive-overlay__cover">
|
|
7
7
|
<slot name="hint">
|
|
8
8
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="48" height="48" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 104.8 122.88" style="enable-background:new 0 0 104.8 122.88" xml:space="preserve"><g><path fill="currentColor" d="M39.92,0c11.02,0,21,4.47,28.23,11.69c7.22,7.22,11.69,17.2,11.69,28.23c0,3.8-0.53,7.47-1.52,10.95l-3.7-2.53 c0.65-2.7,1-5.52,1-8.42c0-9.86-4-18.78-10.46-25.24C58.7,8.21,49.78,4.22,39.92,4.22c-9.86,0-18.78,4-25.24,10.46 C8.21,21.13,4.22,30.06,4.22,39.92c0,9.86,4,18.78,10.46,25.24c4.79,4.79,10.93,8.22,17.8,9.68l0.34,4.36 c-8.17-1.47-15.48-5.43-21.11-11.06C4.47,60.92,0,50.94,0,39.92c0-11.02,4.47-21,11.69-28.23C18.91,4.47,28.89,0,39.92,0L39.92,0z M81.84,122.5c-0.89,0.41-1.88,0.48-2.76,0.25c-0.97-0.26-1.83-0.87-2.36-1.78l-9.91-17.08l-9.42,10.57 c-1.31,1.47-2.8,2.58-4.24,3.16c-1.11,0.45-2.2,0.59-3.22,0.37c-1.13-0.24-2.09-0.89-2.8-2c-0.56-0.89-0.93-2.07-1.05-3.59 l-5.25-68.42c-0.01-0.05-0.01-0.11-0.01-0.15c-0.01-0.43,0.07-0.85,0.25-1.23c0.19-0.44,0.51-0.84,0.91-1.13 c0.17-0.13,0.36-0.22,0.56-0.27c0.39-0.13,0.8-0.18,1.19-0.13c0.38,0.04,0.75,0.15,1.09,0.35c0.11,0.05,0.23,0.12,0.33,0.2 l56.52,38.7c1.25,0.86,2.09,1.77,2.58,2.7c0.62,1.17,0.69,2.32,0.33,3.42c-0.32,0.99-1,1.87-1.93,2.6 c-1.21,0.95-2.92,1.69-4.86,2.09c-0.02,0.01-0.05,0.01-0.07,0.01L83.92,94l9.84,17.13c0.52,0.91,0.62,1.96,0.36,2.92 c-0.25,0.94-0.85,1.8-1.72,2.37c-0.03,0.03-0.07,0.05-0.11,0.07l-10.21,5.9C81.99,122.43,81.92,122.47,81.84,122.5L81.84,122.5 L81.84,122.5z M79.99,119.28c0.1,0.03,0.2,0.03,0.29-0.01c0.03-0.02,0.07-0.04,0.11-0.05l10.08-5.82c0.09-0.07,0.15-0.17,0.19-0.27 c0.02-0.09,0.02-0.17-0.01-0.22L79.11,93.16h0.01c-0.09-0.16-0.16-0.34-0.2-0.53c-0.2-0.97,0.43-1.91,1.39-2.11l16.22-2.88 c0.02-0.01,0.04-0.01,0.07-0.01c1.4-0.29,2.58-0.79,3.38-1.41c0.39-0.31,0.65-0.61,0.74-0.9c0.06-0.18,0.03-0.4-0.1-0.65 c-0.23-0.43-0.69-0.89-1.43-1.41L45.01,46.02l5.12,65.4c0.07,0.91,0.25,1.54,0.5,1.94c0.15,0.23,0.33,0.37,0.51,0.41 c0.3,0.07,0.69-0.01,1.15-0.19c0.93-0.37,1.94-1.15,2.9-2.22l10.11-12.18l0,0c0.13-0.14,0.27-0.26,0.44-0.36 c0.85-0.49,1.95-0.21,2.44,0.65l11.63,19.69C79.85,119.22,79.91,119.26,79.99,119.28L79.99,119.28L79.99,119.28z M80.39,119.22 c0.23-0.11,0.5-0.18,0.78-0.18L80.39,119.22L80.39,119.22L80.39,119.22z M39.92,27.34c3.47,0,6.62,1.41,8.89,3.69 c0.23,0.23,0.46,0.48,0.67,0.73c-0.12-0.04-0.24-0.08-0.36-0.12c-0.65-0.21-1.35-0.37-2.09-0.48c-0.23-0.04-0.47-0.08-0.71-0.1 c-1.05-0.12-2.14-0.12-3.23,0c-0.15,0.02-0.3,0.04-0.46,0.06c-0.86-0.27-1.78-0.41-2.73-0.41c-2.55,0-4.86,1.03-6.52,2.69 l-0.01,0.01c-1.66,1.66-2.69,3.97-2.69,6.52c0,1.3,0.11,1.34-0.16,2.69c-0.14,0.68-0.22,1.44-0.24,2.28 c-0.04,0.56-0.04,1.13,0,1.71l0.12,1.58c-1.92-2.21-3.08-5.09-3.08-8.25c0-3.46,1.41-6.61,3.69-8.89l0.01-0.01 C33.31,28.74,36.45,27.34,39.92,27.34L39.92,27.34z M39.92,13.68c7.24,0,13.8,2.94,18.55,7.68c4.75,4.75,7.68,11.31,7.68,18.55 c0,0.86-0.04,1.7-0.12,2.54l-3.85-2.64c-0.03-6.11-2.51-11.64-6.52-15.64c-4.03-4.03-9.59-6.52-15.74-6.52 c-6.15,0-11.71,2.49-15.74,6.52c-4.03,4.03-6.52,9.59-6.52,15.74c0,6.15,2.49,11.71,6.52,15.74c2.04,2.04,4.48,3.69,7.19,4.82 l0.34,4.37c-3.94-1.3-7.47-3.51-10.34-6.37c-4.75-4.75-7.68-11.31-7.68-18.55c0-7.24,2.94-13.8,7.68-18.55 C26.11,16.62,32.67,13.68,39.92,13.68L39.92,13.68z"/></g></svg>
|
|
9
9
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
</template>
|
|
20
20
|
<style lang="scss">
|
|
21
|
-
.
|
|
21
|
+
.itf-sensitive-overlay {
|
|
22
22
|
position: relative;
|
|
23
23
|
min-height: 100px;
|
|
24
24
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
|
|
3
|
+
<nav aria-label="Page navigation example">
|
|
4
|
+
<ul class="pagination itf-pagination">
|
|
5
|
+
<li
|
|
6
|
+
v-for="(page, n) in pagesArr"
|
|
7
|
+
:key="n"
|
|
8
|
+
class="page-item"
|
|
9
|
+
:class="{'active': page.current, 'disabled': !page.active && !page.current }"
|
|
10
|
+
>
|
|
11
|
+
<a v-if="page.type === 'prev'" href="" class="page-link" aria-label="Previous" @click.prevent="onPage(page.number)">
|
|
12
|
+
<itf-icon name="chevron_left" aria-hidden="true" />
|
|
13
|
+
<span class="sr-only">Previous</span>
|
|
14
|
+
</a>
|
|
15
|
+
<a v-else-if="page.type === 'first'" class="page-link" href="" @click.prevent="onPage(page.number)">{{page.number}}</a>
|
|
16
|
+
<a v-else-if="page.type === 'page'" class="page-link" href="" @click.prevent="onPage(page.number)">{{page.number}}</a>
|
|
17
|
+
<a v-else-if="page.type === 'last'" class="page-link" href="" @click.prevent="onPage(page.number)">{{page.number}}</a>
|
|
18
|
+
<span v-else-if="page.type === 'more'" class="page-link">…</span>
|
|
19
|
+
<a v-if="page.type === 'next'" href="" class="page-link" aria-label="Next" @click.prevent="onPage(page.number)">
|
|
20
|
+
<span class="sr-only">Next</span>
|
|
21
|
+
<itf-icon name="chevron_right" aria-hidden="true" />
|
|
22
|
+
</a>
|
|
23
|
+
</li>
|
|
24
|
+
</ul>
|
|
25
|
+
</nav>
|
|
26
|
+
|
|
27
|
+
</template>
|
|
28
|
+
<style lang="scss">
|
|
29
|
+
@import '../../assets/scss/variables';
|
|
30
|
+
@import '~bootstrap/scss/pagination';
|
|
31
|
+
|
|
32
|
+
.itf-pagination.pagination {
|
|
33
|
+
padding-left: 0;
|
|
34
|
+
|
|
35
|
+
.page-item {
|
|
36
|
+
margin-right: 3px;
|
|
37
|
+
.page-link {
|
|
38
|
+
border-radius: .5rem;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
</style>
|
|
43
|
+
<script>
|
|
44
|
+
import { Vue, Component, Model, Prop } from 'vue-property-decorator';
|
|
45
|
+
import itfIcon from '../icon/Icon.vue';
|
|
46
|
+
|
|
47
|
+
const MIN_PAGES_BLOCKS = 2;
|
|
48
|
+
const MAX_PAGES_BLOCKS = 6;
|
|
49
|
+
|
|
50
|
+
export default @Component({
|
|
51
|
+
name: 'itfPagination',
|
|
52
|
+
components: {
|
|
53
|
+
itfIcon
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
class itfPagination extends Vue {
|
|
57
|
+
@Model('input') value;
|
|
58
|
+
@Prop({ type: [String, Number], default: 0 }) length;
|
|
59
|
+
@Prop({ type: [String, Number], default: 20 }) size;
|
|
60
|
+
@Prop({ type: [String, Number] }) pages;
|
|
61
|
+
@Prop({ type: [String, Number], default: MIN_PAGES_BLOCKS }) minBlocks;
|
|
62
|
+
@Prop({ type: [String, Number], default: MAX_PAGES_BLOCKS }) maxBlocks;
|
|
63
|
+
|
|
64
|
+
onPage(page) {
|
|
65
|
+
this.$emit('input', page);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get pagesArr () {
|
|
69
|
+
const pageSize = Number(this.size);
|
|
70
|
+
const totalItems = Number(this.length);
|
|
71
|
+
const currentPage = this.value;
|
|
72
|
+
let maxBlocks = Number(this.maxBlocks);
|
|
73
|
+
maxBlocks = maxBlocks && maxBlocks < MAX_PAGES_BLOCKS ? MAX_PAGES_BLOCKS : maxBlocks;
|
|
74
|
+
const minBlocks = Number(this.minBlocks);
|
|
75
|
+
|
|
76
|
+
const pages = [];
|
|
77
|
+
const numPages = this.pages ? Number(this.pages) : Math.ceil(totalItems / pageSize);
|
|
78
|
+
|
|
79
|
+
if (numPages > 1) {
|
|
80
|
+
pages.push({
|
|
81
|
+
type: 'prev',
|
|
82
|
+
number: Math.max(1, currentPage - 1),
|
|
83
|
+
active: currentPage > 1
|
|
84
|
+
});
|
|
85
|
+
pages.push({
|
|
86
|
+
type: 'first',
|
|
87
|
+
number: 1,
|
|
88
|
+
active: currentPage > 1,
|
|
89
|
+
current: currentPage === 1
|
|
90
|
+
});
|
|
91
|
+
const maxPivotPages = Math.round((maxBlocks - minBlocks) / 2);
|
|
92
|
+
let minPage = Math.max(2, currentPage - maxPivotPages);
|
|
93
|
+
const maxPage = Math.min(numPages - 1, currentPage + maxPivotPages * 2 - (currentPage - minPage));
|
|
94
|
+
minPage = Math.max(2, minPage - (maxPivotPages * 2 - (maxPage - minPage)));
|
|
95
|
+
let i = minPage;
|
|
96
|
+
while (i <= maxPage) {
|
|
97
|
+
if ((i === minPage && i !== 2) || (i === maxPage && i !== numPages - 1)) {
|
|
98
|
+
pages.push({
|
|
99
|
+
type: 'more',
|
|
100
|
+
active: false
|
|
101
|
+
});
|
|
102
|
+
} else {
|
|
103
|
+
pages.push({
|
|
104
|
+
type: 'page',
|
|
105
|
+
number: i,
|
|
106
|
+
active: currentPage !== i,
|
|
107
|
+
current: currentPage === i
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
i++;
|
|
111
|
+
}
|
|
112
|
+
pages.push({
|
|
113
|
+
type: 'last',
|
|
114
|
+
number: numPages,
|
|
115
|
+
active: currentPage !== numPages,
|
|
116
|
+
current: currentPage === numPages
|
|
117
|
+
});
|
|
118
|
+
pages.push({
|
|
119
|
+
type: 'next',
|
|
120
|
+
number: Math.min(numPages, currentPage + 1),
|
|
121
|
+
active: currentPage < numPages
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return pages;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
</script>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { storiesOf } from '@storybook/vue';
|
|
2
|
+
import itfPagination from './Pagination.vue';
|
|
3
|
+
import itfApp from '../app/App.vue';
|
|
4
|
+
|
|
5
|
+
storiesOf('Common', module)
|
|
6
|
+
.add('Pagination', () => ({
|
|
7
|
+
components: {
|
|
8
|
+
itfApp,
|
|
9
|
+
itfPagination
|
|
10
|
+
},
|
|
11
|
+
data() {
|
|
12
|
+
return {
|
|
13
|
+
page: 1,
|
|
14
|
+
customDays: {
|
|
15
|
+
'2021-10-21': { text: '🎉', class: 'test' }
|
|
16
|
+
},
|
|
17
|
+
dateRange: null,
|
|
18
|
+
month: '09-2021'
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
template: `<div>
|
|
22
|
+
<p>You need wrap whole application with this tag</p>
|
|
23
|
+
|
|
24
|
+
<h2>Usage</h2>
|
|
25
|
+
|
|
26
|
+
<pre>
|
|
27
|
+
|
|
28
|
+
</pre>
|
|
29
|
+
|
|
30
|
+
<h2>Example</h2>
|
|
31
|
+
|
|
32
|
+
<itf-app ref="app">
|
|
33
|
+
|
|
34
|
+
{{page}}
|
|
35
|
+
|
|
36
|
+
<br />
|
|
37
|
+
<itf-pagination :length="500" v-model.lazy="page"></itf-pagination>
|
|
38
|
+
|
|
39
|
+
</itf-app>
|
|
40
|
+
</div>`,
|
|
41
|
+
}));
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
.itf-popover {
|
|
18
18
|
display: none;
|
|
19
19
|
|
|
20
|
-
.popover-body & {
|
|
20
|
+
.popover-body > & {
|
|
21
21
|
display: block;
|
|
22
22
|
}
|
|
23
23
|
.border-right {
|
|
@@ -57,8 +57,6 @@ export default @Component({
|
|
|
57
57
|
},
|
|
58
58
|
})
|
|
59
59
|
class itfPopover extends Vue {
|
|
60
|
-
@Inject({ default: null }) appendContext;
|
|
61
|
-
|
|
62
60
|
@PropSync('visible') value;
|
|
63
61
|
@Prop({ type: String, default: 'bottom', validator: (value) => ['bottom', 'left', 'right', 'top'].includes(value) }) placement;
|
|
64
62
|
@Prop({ type: String, default: 'click', validator: (value) => ['click', 'focus', 'hover', 'manual'].includes(value) }) trigger;
|
|
@@ -95,23 +93,25 @@ class itfPopover extends Vue {
|
|
|
95
93
|
const el = this.$refs.popover;
|
|
96
94
|
const { default: Popover } = await import('bootstrap/js/src/popover.js');
|
|
97
95
|
|
|
98
|
-
const
|
|
96
|
+
const context = this.$el.closest('.itf-append-context') || document.body;
|
|
99
97
|
this.modalEl = new Popover(this.$el, {
|
|
100
98
|
content: el,
|
|
101
|
-
container:
|
|
99
|
+
container: context,
|
|
102
100
|
sanitize: false,
|
|
103
101
|
html: true,
|
|
104
102
|
customClass: this.customClass,
|
|
105
103
|
placement: this.placement,
|
|
106
|
-
trigger: this.trigger
|
|
104
|
+
trigger: this.trigger,
|
|
107
105
|
});
|
|
108
106
|
this.onVisibleChanged(this.value);
|
|
109
107
|
this.$el.addEventListener('shown.bs.popover', () => {
|
|
110
108
|
document.body.addEventListener('click', this.clickOutside);
|
|
109
|
+
this.$emit('update:visible', true);
|
|
111
110
|
});
|
|
112
111
|
this.$el.addEventListener('hidden.bs.popover', () => {
|
|
113
112
|
this.value = false;
|
|
114
113
|
document.body.removeEventListener('click', this.clickOutside);
|
|
114
|
+
this.$emit('update:visible', false);
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -1,149 +1,238 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
2
|
<div class="itf-segmeneted-control">
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<span
|
|
15
|
-
|
|
3
|
+
<span v-if="!isUndefined" class="selection" :class="{'elevation-1': !disabled}"></span>
|
|
4
|
+
|
|
5
|
+
<div class="option" v-for="(item, n) in itemsWithNames" :key="n">
|
|
6
|
+
<label>
|
|
7
|
+
<input
|
|
8
|
+
type="radio"
|
|
9
|
+
:name="name"
|
|
10
|
+
:value="n"
|
|
11
|
+
:checked="isChecked(item)"
|
|
12
|
+
@change="onItemChanged(item)" />
|
|
13
|
+
<span>
|
|
14
|
+
<slot name="item" :item="item" :itemKey="n">{{item[itemText]}}</slot>
|
|
15
|
+
</span>
|
|
16
|
+
</label>
|
|
16
17
|
</div>
|
|
17
18
|
</div>
|
|
18
|
-
|
|
19
19
|
</template>
|
|
20
|
-
<style lang="scss"
|
|
20
|
+
<style lang="scss">
|
|
21
21
|
@import '../../assets/scss/variables';
|
|
22
22
|
|
|
23
23
|
.itf-segmeneted-control {
|
|
24
|
-
--
|
|
24
|
+
--itf-segmeneted-control--backgroung: #{$input-bg};
|
|
25
|
+
--itf-segmeneted-control--border-radius: #{$border-radius};
|
|
26
|
+
--itf-segmeneted-control--shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
background: var(--itf-segmeneted-control--backgroung);
|
|
30
|
+
border-radius: var(--itf-segmeneted-control--border-radius);
|
|
29
31
|
margin: 0;
|
|
30
|
-
padding:
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
padding: 2px;
|
|
33
|
+
border: none;
|
|
34
|
+
outline: none;
|
|
35
|
+
display: inline-grid;
|
|
36
|
+
grid-auto-flow: column;
|
|
37
|
+
grid-auto-columns: 1fr;
|
|
38
|
+
-webkit-user-select: none;
|
|
39
|
+
-moz-user-select: none;
|
|
40
|
+
-ms-user-select: none;
|
|
41
|
+
user-select: none;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
|
|
44
|
+
.option {
|
|
33
45
|
position: relative;
|
|
34
|
-
|
|
35
|
-
line-height: 1;
|
|
36
|
-
}
|
|
46
|
+
cursor: pointer;
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
background: transparent;
|
|
44
|
-
border: none;
|
|
45
|
-
outline: none;
|
|
46
|
-
|
|
47
|
-
&:hover,
|
|
48
|
-
&:focus {
|
|
49
|
-
cursor: pointer;
|
|
48
|
+
&:hover input:not(:checked) + label span,
|
|
49
|
+
&:active input:not(:checked) + label span,
|
|
50
|
+
&:focus input:not(:checked) + label span {
|
|
51
|
+
/*opacity: .2;*/
|
|
52
|
+
color: var(--v-primary-base);
|
|
50
53
|
}
|
|
51
|
-
&:
|
|
52
|
-
|
|
53
|
-
top: 15%;
|
|
54
|
-
right: -0.5px;
|
|
55
|
-
display: block;
|
|
56
|
-
width: 1px;
|
|
57
|
-
height: 70%;
|
|
58
|
-
background-color: rgba(0, 0, 0, 0.08);
|
|
59
|
-
-webkit-transition: opacity .2s ease-out;
|
|
60
|
-
transition: opacity .2s ease-out;
|
|
61
|
-
content: "";
|
|
54
|
+
&:active input:not(:checked) + label span {
|
|
55
|
+
transform: scale(.95);
|
|
62
56
|
}
|
|
57
|
+
&:first-of-type {
|
|
58
|
+
grid-column: 1;
|
|
59
|
+
grid-row: 1;
|
|
60
|
+
box-shadow: none;
|
|
63
61
|
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
label::before {
|
|
63
|
+
opacity: 0;
|
|
64
|
+
}
|
|
65
|
+
label::after {
|
|
66
|
+
opacity: 0;
|
|
67
|
+
}
|
|
66
68
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
69
|
+
label {
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
position: relative;
|
|
72
|
+
display: block;
|
|
73
|
+
text-align: center;
|
|
74
|
+
padding: .25rem 1.5rem;
|
|
75
|
+
background: rgba(255, 255, 255, 0);
|
|
76
|
+
color: rgba(0, 0, 0, 1);
|
|
77
|
+
font-size: 14px;
|
|
78
|
+
|
|
79
|
+
&::before,
|
|
80
|
+
&::after {
|
|
81
|
+
content: '';
|
|
82
|
+
width: 1px;
|
|
83
|
+
background: rgba(142, 142, 147, .15);
|
|
84
|
+
position: absolute;
|
|
85
|
+
top: 14%;
|
|
86
|
+
bottom: 14%;
|
|
87
|
+
border-radius: var(--itf-segmeneted-control--border-radius);
|
|
88
|
+
will-change: background;
|
|
89
|
+
-webkit-transition: background .2s ease;
|
|
90
|
+
transition: background .2s ease;
|
|
91
|
+
}
|
|
92
|
+
&::before {
|
|
93
|
+
left: 0;
|
|
94
|
+
transform: translateX(-.5px);
|
|
95
|
+
}
|
|
96
|
+
&::after {
|
|
97
|
+
right: 0;
|
|
98
|
+
transform: translateX(.5px);
|
|
99
|
+
}
|
|
100
|
+
span {
|
|
101
|
+
display: block;
|
|
102
|
+
position: relative;
|
|
103
|
+
z-index: 2;
|
|
104
|
+
-webkit-transition: all .2s ease;
|
|
105
|
+
transition: all .2s ease;
|
|
106
|
+
will-change: transform;
|
|
107
|
+
}
|
|
94
108
|
}
|
|
95
|
-
}
|
|
96
|
-
&__button.active &__slider {
|
|
97
|
-
opacity: 1;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
@media (prefers-color-scheme: notdark) {
|
|
101
|
-
background-color: $dark-input-bg;
|
|
102
109
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
110
|
+
input {
|
|
111
|
+
position: absolute;
|
|
112
|
+
top: 0;
|
|
113
|
+
left: 0;
|
|
114
|
+
right: 0;
|
|
115
|
+
bottom: 0;
|
|
116
|
+
width: 100%;
|
|
117
|
+
height: 100%;
|
|
118
|
+
padding: 0;
|
|
119
|
+
margin: 0;
|
|
120
|
+
-webkit-appearance: none;
|
|
121
|
+
-moz-appearance: none;
|
|
122
|
+
appearance: none;
|
|
123
|
+
outline: none;
|
|
124
|
+
border: none;
|
|
125
|
+
opacity: 0;
|
|
108
126
|
|
|
109
|
-
|
|
110
|
-
|
|
127
|
+
&:checked + label {
|
|
128
|
+
cursor: default;
|
|
111
129
|
}
|
|
112
|
-
&:
|
|
113
|
-
|
|
130
|
+
&:checked + label::before,
|
|
131
|
+
&:checked + label::after {
|
|
132
|
+
background: var(--itf-segmeneted-control--backgroung);
|
|
133
|
+
z-index: 1;
|
|
114
134
|
}
|
|
115
135
|
}
|
|
116
136
|
}
|
|
137
|
+
.selection {
|
|
138
|
+
background: rgba(255, 255, 255, 1);
|
|
139
|
+
border: .5px solid rgba(0, 0, 0, 0.04);
|
|
140
|
+
box-shadow: var(--itf-segmeneted-control--shadow);
|
|
141
|
+
/*box-shadow: 0 3px 8px 0 rgba(0,0,0,0.12), 0 3px 1px 0 rgba(0,0,0,0.04);*/
|
|
142
|
+
border-radius: var(--itf-segmeneted-control--border-radius);
|
|
143
|
+
grid-column: 1;
|
|
144
|
+
grid-row: 1;
|
|
145
|
+
z-index: 2;
|
|
146
|
+
will-change: transform;
|
|
147
|
+
-webkit-transition: transform .2s ease;
|
|
148
|
+
transition: transform .2s ease;
|
|
149
|
+
}
|
|
117
150
|
}
|
|
118
151
|
</style>
|
|
119
152
|
<script>
|
|
120
|
-
import { Vue,
|
|
153
|
+
import { Vue, Component, Model, Prop, Watch } from 'vue-property-decorator';
|
|
121
154
|
|
|
122
155
|
export default @Component({
|
|
123
156
|
name: 'itfSegmentedControl',
|
|
124
|
-
components: {
|
|
125
|
-
}
|
|
157
|
+
components: {}
|
|
126
158
|
})
|
|
127
159
|
class itfSegmentedControl extends Vue {
|
|
128
160
|
@Model('input') value;
|
|
129
|
-
@Prop() items;
|
|
130
|
-
@Prop(String) itemKey;
|
|
131
|
-
@Prop(String) itemText;
|
|
161
|
+
@Prop({ type: Array, required: true }) items;
|
|
162
|
+
@Prop({ type: String, default: 'Id' }) itemKey;
|
|
163
|
+
@Prop({ type: String, default: 'Name' }) itemText;
|
|
164
|
+
@Prop({ type: Boolean, default: false }) returnObject;
|
|
165
|
+
@Prop({ type: Boolean, default: false }) disabled;
|
|
166
|
+
|
|
167
|
+
get name () {
|
|
168
|
+
return `sc${this._uid}`;
|
|
169
|
+
}
|
|
132
170
|
|
|
133
|
-
get
|
|
134
|
-
return this.items.
|
|
171
|
+
get itemsWithNames() {
|
|
172
|
+
return this.items.map((item, n) => {
|
|
173
|
+
return {
|
|
174
|
+
...item,
|
|
175
|
+
$itemName: this.disabled ? null : `item${this.name}${n}`
|
|
176
|
+
};
|
|
177
|
+
});
|
|
135
178
|
}
|
|
136
179
|
|
|
137
|
-
mounted() {
|
|
180
|
+
mounted () {
|
|
181
|
+
this.$nextTick(() => this.init());
|
|
138
182
|
}
|
|
139
183
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
184
|
+
get itemValue () {
|
|
185
|
+
if (typeof this.value === 'undefined' || this.value === null) {
|
|
186
|
+
return this.value;
|
|
187
|
+
}
|
|
188
|
+
return typeof this.value === 'object' ? this.value[this.itemKey] : this.value;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
isChecked (item) {
|
|
192
|
+
return this.itemValue === item[this.itemKey];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@Watch('value')
|
|
196
|
+
@Watch('items')
|
|
197
|
+
onItemsChanged() {
|
|
198
|
+
this.$nextTick(() => this.init());
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
get isUndefined () {
|
|
202
|
+
return typeof this.value === 'undefined';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
init() {
|
|
206
|
+
const INDIVIDUAL_SEGMENT_SELECTOR = '.option input';
|
|
207
|
+
const BACKGROUND_PILL_SELECTOR = '.selection';
|
|
208
|
+
|
|
209
|
+
this.$el.addEventListener('change', () => updatePillPosition(this.$el));
|
|
210
|
+
window.addEventListener('resize', () => updatePillPosition(this.$el)); // Prevent pill from detaching from element when window resized. Becuase this is rare I haven't bothered with throttling the event
|
|
211
|
+
|
|
212
|
+
updatePillPosition(this.$el);
|
|
213
|
+
|
|
214
|
+
function updatePillPosition (el) {
|
|
215
|
+
forEachElement(el, INDIVIDUAL_SEGMENT_SELECTOR, (elem, index) => {
|
|
216
|
+
if (elem.checked) {
|
|
217
|
+
moveBackgroundPillToElement(el, elem, index);
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function moveBackgroundPillToElement (el, elem, index) {
|
|
223
|
+
const slider = el.querySelector(BACKGROUND_PILL_SELECTOR);
|
|
224
|
+
if (slider) {
|
|
225
|
+
slider.style.transform = 'translateX(' + (elem.offsetWidth * index) + 'px)';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function forEachElement(el, className, fn) {
|
|
230
|
+
Array.from(el.querySelectorAll(className)).forEach(fn);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
145
233
|
|
|
146
|
-
|
|
234
|
+
onItemChanged (item) {
|
|
235
|
+
this.$emit('input', this.returnObject ? item : item[this.itemKey]);
|
|
147
236
|
}
|
|
148
237
|
}
|
|
149
238
|
</script>
|
|
@@ -729,7 +729,7 @@ export default {
|
|
|
729
729
|
_value: [], // Internal value managed by Vue Select if no `value` prop is passed
|
|
730
730
|
}
|
|
731
731
|
},
|
|
732
|
-
inject: { itemLabel: { default: null }
|
|
732
|
+
inject: { itemLabel: { default: null } },
|
|
733
733
|
computed: {
|
|
734
734
|
/**
|
|
735
735
|
* Determine if the component needs to
|
|
@@ -9,8 +9,7 @@ export default {
|
|
|
9
9
|
} = context.$refs.toggle.getBoundingClientRect();
|
|
10
10
|
|
|
11
11
|
const { calculatePosition } = context;
|
|
12
|
-
const
|
|
13
|
-
const rootEl = appendContext && appendContext();
|
|
12
|
+
const rootEl = el.closest('.itf-append-context') || document.body;
|
|
14
13
|
if (rootEl) {
|
|
15
14
|
let {
|
|
16
15
|
top: parentTop,
|