@itfin/components 1.0.18 → 1.0.22
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 -1
- package/src/assets/scss/_variables.scss +6 -5
- package/src/assets/scss/components/_dropdown-menu.scss +3 -5
- package/src/assets/scss/components/_dropdown-toggle.scss +8 -3
- package/src/assets/scss/components/_selected.scss +15 -0
- package/src/assets/scss/components/_spinner.scss +4 -3
- package/src/assets/scss/components/_states.scss +0 -1
- package/src/assets/scss/directives/loading.scss +1 -1
- package/src/components/button/Button.vue +7 -5
- package/src/components/checkbox/Checkbox.vue +114 -0
- package/src/components/checkbox/CheckboxGroup.vue +54 -0
- package/src/components/checkbox/index.stories.js +74 -0
- package/src/components/datepicker/DatePicker.vue +11 -9
- package/src/components/datepicker/DatePickerInline.vue +3 -1
- package/src/components/datepicker/DateRangePicker.vue +11 -9
- package/src/components/datepicker/MonthPicker.vue +163 -0
- package/src/components/datepicker/index.stories.js +5 -0
- package/src/components/icon/Icon.vue +1 -1
- package/src/components/modal/Modal.vue +6 -2
- package/src/components/popover/Popover.vue +1 -1
- package/src/components/select/Select.vue +20 -16
- package/src/components/select/index.stories.js +8 -1
- package/src/components/table/Table.vue +12 -9
- package/src/components/text-field/TextField.vue +10 -2
- package/src/components/tooltip/Tooltip.vue +39 -0
- package/src/components/tooltip/index.stories.js +52 -0
- package/src/components/tree/index.stories.js +10 -6
- package/src/directives/sticky.js +298 -0
- package/src/index.stories.js +0 -25
- package/src/components/select/Deselect.vue +0 -3
- package/src/components/select/OpenIndicator.vue +0 -3
package/package.json
CHANGED
|
@@ -24,18 +24,19 @@ $link-color: #0B314F;
|
|
|
24
24
|
$input-btn-focus-width: .125rem;
|
|
25
25
|
|
|
26
26
|
$input-bg: #f3f3f3;
|
|
27
|
-
$input-border-color: #
|
|
27
|
+
$input-border-color: rgba(#000, .08);
|
|
28
28
|
$input-border-radius: 10px;
|
|
29
29
|
$input-font-family: 'Fira Mono', monospace;
|
|
30
30
|
|
|
31
|
-
$input-focus-bg: #
|
|
32
|
-
$input-focus-border-color: #
|
|
33
|
-
|
|
34
|
-
$input-focus-box-shadow: none;
|
|
31
|
+
$input-focus-bg: #fff;
|
|
32
|
+
$input-focus-border-color: #fff;
|
|
35
33
|
|
|
36
34
|
$form-label-margin-bottom: .1rem;
|
|
37
35
|
$input-focus-border: rgb(11 49 79 / 25%);
|
|
38
36
|
|
|
37
|
+
$form-check-input-border: 1px solid rgba(#000, .08);
|
|
38
|
+
$form-switch-focus-color: tint-color($primary, 50%);
|
|
39
|
+
|
|
39
40
|
$modal-backdrop-bg: #fff;
|
|
40
41
|
$modal-backdrop-opacity: .7;
|
|
41
42
|
$modal-content-border-radius: 0;
|
|
@@ -12,19 +12,17 @@ $min-width: $vs-dropdown-min-width;
|
|
|
12
12
|
$max-height: $vs-dropdown-max-height;
|
|
13
13
|
|
|
14
14
|
.vs__dropdown-menu {
|
|
15
|
+
overflow-x: hidden;
|
|
16
|
+
overflow-y: auto;
|
|
15
17
|
display: block;
|
|
16
18
|
box-sizing: border-box;
|
|
17
19
|
position: absolute;
|
|
18
|
-
top: calc(100%
|
|
20
|
+
top: calc(100% + #{$border-width * 2}); // -{#$border-width} here ensures the left and right borders of the dropdown appear flush with the toggle.
|
|
19
21
|
left: 0;
|
|
20
22
|
z-index: $z-index;
|
|
21
23
|
width: 100%;
|
|
22
24
|
max-height: $max-height;
|
|
23
25
|
min-width: $min-width;
|
|
24
|
-
overflow-y: auto;
|
|
25
|
-
border-top-style: none;
|
|
26
|
-
border-top-left-radius: 0;
|
|
27
|
-
border-top-right-radius: 0;
|
|
28
26
|
text-align: left;
|
|
29
27
|
list-style: none;
|
|
30
28
|
}
|
|
@@ -25,9 +25,17 @@ $border-radius: $vs-border-radius;
|
|
|
25
25
|
flex-wrap: wrap;
|
|
26
26
|
padding: 0 2px 0 0;
|
|
27
27
|
position: relative;
|
|
28
|
+
flex-shrink: 1;
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
min-height: 26px;
|
|
31
|
+
|
|
32
|
+
.vs--multiple & {
|
|
33
|
+
min-height: 32px;
|
|
34
|
+
}
|
|
28
35
|
}
|
|
29
36
|
|
|
30
37
|
.vs__actions {
|
|
38
|
+
flex-shrink: 0;
|
|
31
39
|
display: flex;
|
|
32
40
|
margin-right: -9px;
|
|
33
41
|
align-items: center;
|
|
@@ -46,7 +54,4 @@ $border-radius: $vs-border-radius;
|
|
|
46
54
|
cursor: pointer;
|
|
47
55
|
}
|
|
48
56
|
.vs--open .vs__dropdown-toggle {
|
|
49
|
-
border-bottom-color: transparent;
|
|
50
|
-
border-bottom-left-radius: 0;
|
|
51
|
-
border-bottom-right-radius: 0;
|
|
52
57
|
}
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
color: #222;
|
|
18
18
|
background-color: #ccc;
|
|
19
19
|
|
|
20
|
+
&.badge {
|
|
21
|
+
padding: 0.1875rem 0.5rem;
|
|
22
|
+
}
|
|
23
|
+
|
|
20
24
|
@media (prefers-color-scheme: notdark) {
|
|
21
25
|
background-color: #221;
|
|
22
26
|
color: $dark-body-color;
|
|
@@ -43,6 +47,17 @@
|
|
|
43
47
|
.vs__selected {
|
|
44
48
|
background-color: transparent;
|
|
45
49
|
border-color: transparent;
|
|
50
|
+
white-space: nowrap;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
text-overflow: ellipsis;
|
|
53
|
+
}
|
|
54
|
+
.vs__search {
|
|
55
|
+
top: 0;
|
|
56
|
+
position: absolute;
|
|
57
|
+
right: 0;
|
|
58
|
+
left: 0;
|
|
59
|
+
width: 100% !important;
|
|
60
|
+
padding-left: 0 !important;
|
|
46
61
|
}
|
|
47
62
|
&.vs--open .vs__selected,
|
|
48
63
|
&.vs--loading .vs__selected {
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
/* Loading Spinner */
|
|
2
|
-
.itf-select .itf-
|
|
2
|
+
.itf-select .itf-select__loader {
|
|
3
3
|
opacity: 0;
|
|
4
4
|
transform: translateZ(0);
|
|
5
5
|
transition: opacity .1s;
|
|
6
|
-
right:
|
|
6
|
+
right: 0.75rem;
|
|
7
|
+
position: absolute;
|
|
7
8
|
mix-blend-mode: normal;
|
|
8
9
|
width: 1rem;
|
|
9
10
|
height: 1rem;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/* Loading Spinner States */
|
|
13
|
-
.vs--loading .itf-
|
|
14
|
+
.vs--loading .itf-select__loader {
|
|
14
15
|
opacity: 1;
|
|
15
16
|
}
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
tabindex="0"
|
|
8
8
|
type="button"
|
|
9
9
|
:class="{
|
|
10
|
+
[`btn-${color}`]: color,
|
|
10
11
|
'labeled': labeled,
|
|
11
12
|
'disabled': disabled || loading,
|
|
12
13
|
'btn-primary': primary,
|
|
13
|
-
'btn-basic': !primary && !secondary,
|
|
14
|
+
'btn-basic': !primary && !secondary && !color,
|
|
14
15
|
'btn-secondary': secondary,
|
|
15
16
|
'btn-sm': small,
|
|
16
17
|
'btn-lg': large,
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
'btn-icon': icon,
|
|
21
22
|
'loading': loading
|
|
22
23
|
}">
|
|
23
|
-
<div class="itf-spinner" :class="{'
|
|
24
|
+
<div class="itf-spinner" :class="{'itf-spinner__white': primary}" v-if="loading"></div>
|
|
24
25
|
<div v-if="loading && loadingText">{{loadingText}}</div>
|
|
25
26
|
<slot v-else></slot>
|
|
26
27
|
</button>
|
|
@@ -31,11 +32,11 @@
|
|
|
31
32
|
@import '~bootstrap/scss/buttons';
|
|
32
33
|
|
|
33
34
|
.itf-button {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
|
|
34
38
|
& > div {
|
|
35
|
-
display: flex;
|
|
36
39
|
pointer-events: none;
|
|
37
|
-
align-items: center;
|
|
38
|
-
text-align: left;
|
|
39
40
|
}
|
|
40
41
|
.itf-spinner {
|
|
41
42
|
mix-blend-mode: normal;
|
|
@@ -141,6 +142,7 @@ class itfButton extends Vue {
|
|
|
141
142
|
@Prop(Boolean) large;
|
|
142
143
|
@Prop(Boolean) icon;
|
|
143
144
|
@Prop(String) loadingText;
|
|
145
|
+
@Prop(String) color;
|
|
144
146
|
@Prop(Boolean) disabled;
|
|
145
147
|
}
|
|
146
148
|
</script>
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
|
|
3
|
+
<div class="itf-checkbox form-check" :class="{ 'form-switch': this.switch, 'itf-checkbox__large': large, 'itf-checkbox__medium': medium }">
|
|
4
|
+
<input class="form-check-input" :id="id" slot="input" type="checkbox" name="checkbox" v-model="isChecked" :disabled="isDisabled" />
|
|
5
|
+
<label :for="id" slot="label" class="form-check-label">
|
|
6
|
+
{{label}}
|
|
7
|
+
</label>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
</template>
|
|
11
|
+
<style lang="scss">
|
|
12
|
+
@import '../../assets/scss/variables';
|
|
13
|
+
|
|
14
|
+
.itf-checkbox {
|
|
15
|
+
.form-check-input, .form-check-label {
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
}
|
|
18
|
+
&__large {
|
|
19
|
+
padding-left: $form-check-padding-start * 2;
|
|
20
|
+
min-height: 2.5rem;
|
|
21
|
+
|
|
22
|
+
.form-check-label {
|
|
23
|
+
padding-top: 0.25rem;
|
|
24
|
+
}
|
|
25
|
+
.form-check-input {
|
|
26
|
+
margin-top: 0;
|
|
27
|
+
width: $form-check-input-width * 2;
|
|
28
|
+
height: $form-check-input-width * 2;
|
|
29
|
+
margin-left: -$form-check-padding-start * 2;
|
|
30
|
+
border-radius: 0.75rem;
|
|
31
|
+
}
|
|
32
|
+
&.form-switch {
|
|
33
|
+
padding-left: $form-switch-padding-start * 2;
|
|
34
|
+
|
|
35
|
+
.form-check-input {
|
|
36
|
+
width: $form-switch-width * 2;
|
|
37
|
+
margin-left: -$form-switch-padding-start * 2;
|
|
38
|
+
border-radius: 100px;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
&__medium {
|
|
43
|
+
padding-left: $form-check-padding-start * 1.5;
|
|
44
|
+
min-height: 2rem;
|
|
45
|
+
|
|
46
|
+
.form-check-input {
|
|
47
|
+
margin-top: 0;
|
|
48
|
+
width: $form-check-input-width * 1.5;
|
|
49
|
+
height: $form-check-input-width * 1.5;
|
|
50
|
+
margin-left: -$form-check-padding-start * 1.5;
|
|
51
|
+
border-radius: 0.5rem;
|
|
52
|
+
}
|
|
53
|
+
&.form-switch {
|
|
54
|
+
padding-left: $form-switch-padding-start * 1.5;
|
|
55
|
+
|
|
56
|
+
.form-check-input {
|
|
57
|
+
width: $form-switch-width * 1.5;
|
|
58
|
+
margin-left: -$form-switch-padding-start * 1.5;
|
|
59
|
+
border-radius: 100px;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
65
|
+
<script>
|
|
66
|
+
import { Vue, Component, Prop, Model, Inject } from 'vue-property-decorator';
|
|
67
|
+
|
|
68
|
+
let globalCheckboxId = 0;
|
|
69
|
+
|
|
70
|
+
export default @Component({
|
|
71
|
+
name: 'itfCheckbox',
|
|
72
|
+
components: {
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
class itfCheckbox extends Vue {
|
|
76
|
+
@Inject({ default: null }) checkboxGroup;
|
|
77
|
+
|
|
78
|
+
@Model('input') value;
|
|
79
|
+
@Prop(String) label;
|
|
80
|
+
|
|
81
|
+
@Prop(Boolean) switch;
|
|
82
|
+
@Prop(Boolean) disabled;
|
|
83
|
+
@Prop(Boolean) medium;
|
|
84
|
+
@Prop(Boolean) large;
|
|
85
|
+
|
|
86
|
+
id = '';
|
|
87
|
+
|
|
88
|
+
created() {
|
|
89
|
+
globalCheckboxId += 1;
|
|
90
|
+
this.id = `checkbox${globalCheckboxId}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get isDisabled() {
|
|
94
|
+
if (this.checkboxGroup) {
|
|
95
|
+
return this.disabled || this.checkboxGroup.isDisabled(this.value);
|
|
96
|
+
}
|
|
97
|
+
return this.disabled;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get isChecked() {
|
|
101
|
+
if (this.checkboxGroup) {
|
|
102
|
+
return this.checkboxGroup.isChecked(this.value);
|
|
103
|
+
}
|
|
104
|
+
return this.value;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
set isChecked(val) {
|
|
108
|
+
if (this.checkboxGroup) {
|
|
109
|
+
this.checkboxGroup.onToggleOption(this.value, val);
|
|
110
|
+
}
|
|
111
|
+
this.$emit('input', val);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
|
|
3
|
+
<div>
|
|
4
|
+
<slot />
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
</template>
|
|
8
|
+
<style>
|
|
9
|
+
|
|
10
|
+
</style>
|
|
11
|
+
<script>
|
|
12
|
+
import { Vue, Component, Prop, Model, Provide } from 'vue-property-decorator';
|
|
13
|
+
|
|
14
|
+
export default @Component({
|
|
15
|
+
name: 'itfCheckboxGroup'
|
|
16
|
+
})
|
|
17
|
+
class itfCheckboxGroup extends Vue {
|
|
18
|
+
@Provide() checkboxGroup = this;
|
|
19
|
+
|
|
20
|
+
@Model('input', { type: Array, default: () => [] }) value;
|
|
21
|
+
|
|
22
|
+
@Prop(Boolean) disabled;
|
|
23
|
+
|
|
24
|
+
@Prop(String) itemKey;
|
|
25
|
+
|
|
26
|
+
@Prop(Number) max;
|
|
27
|
+
|
|
28
|
+
onToggleOption(value, isChecked) {
|
|
29
|
+
const index = this.getCheckedIndex(value);
|
|
30
|
+
const arr = this.value ? [ ...this.value ] : [];
|
|
31
|
+
if (index !== -1 && isChecked === false) {
|
|
32
|
+
arr.splice(index, 1);
|
|
33
|
+
} else if (isChecked === true) {
|
|
34
|
+
arr.push(value);
|
|
35
|
+
}
|
|
36
|
+
this.$emit('input', arr);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getCheckedIndex(value) {
|
|
40
|
+
if (this.itemKey) {
|
|
41
|
+
return this.value && this.value.findIndex(item => item[this.itemKey] === value[this.itemKey]);
|
|
42
|
+
}
|
|
43
|
+
return this.value.indexOf(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
isChecked(value) {
|
|
47
|
+
return this.getCheckedIndex(value) !== -1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isDisabled(value) {
|
|
51
|
+
return this.getCheckedIndex(value) === -1 && (this.max !== 'undefined' && this.value.length >= this.max);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
</script>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { storiesOf } from '@storybook/vue';
|
|
2
|
+
import itfCheckbox from './Checkbox';
|
|
3
|
+
import itfCheckboxGroup from './CheckboxGroup';
|
|
4
|
+
import itfForm from '../form/Form.vue';
|
|
5
|
+
import itfLabel from '../form/Label.vue';
|
|
6
|
+
import itfDatePicker from '../datepicker/DatePicker.vue';
|
|
7
|
+
import itfDateRangePicker from '../datepicker/DateRangePicker.vue';
|
|
8
|
+
import itfApp from '../app/App.vue';
|
|
9
|
+
|
|
10
|
+
storiesOf('Common', module)
|
|
11
|
+
.add('Checkbox', () => ({
|
|
12
|
+
components: {
|
|
13
|
+
itfApp,
|
|
14
|
+
itfCheckbox,
|
|
15
|
+
itfCheckboxGroup,
|
|
16
|
+
itfForm,
|
|
17
|
+
itfLabel,
|
|
18
|
+
itfDatePicker,
|
|
19
|
+
itfDateRangePicker,
|
|
20
|
+
},
|
|
21
|
+
data() {
|
|
22
|
+
return {
|
|
23
|
+
test: false,
|
|
24
|
+
group: []
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
methods: {
|
|
28
|
+
},
|
|
29
|
+
template: `<div>
|
|
30
|
+
<p>You need wrap whole application with this tag</p>
|
|
31
|
+
|
|
32
|
+
<h2>Usage</h2>
|
|
33
|
+
|
|
34
|
+
<pre>
|
|
35
|
+
|
|
36
|
+
<itf-datepicker></itf-datepicker>
|
|
37
|
+
|
|
38
|
+
<button @click="$showSuccess('Success')">Show success</button>
|
|
39
|
+
<button @click="$showError('Error')">Show error</button>
|
|
40
|
+
<button @click="$try(() => { throw new Error('Invalid function'); })">Try function with error</button>
|
|
41
|
+
</pre>
|
|
42
|
+
|
|
43
|
+
<h2>Example</h2>
|
|
44
|
+
|
|
45
|
+
<itf-form>
|
|
46
|
+
<itf-checkbox label="Test" v-model="test" />
|
|
47
|
+
|
|
48
|
+
<itf-checkbox switch label="Test" v-model="test" />
|
|
49
|
+
{{test}}
|
|
50
|
+
|
|
51
|
+
<itf-checkbox-group v-model="group">
|
|
52
|
+
<itf-checkbox label="Test1" value="1" />
|
|
53
|
+
<itf-checkbox label="Test2" value="2" />
|
|
54
|
+
</itf-checkbox-group>
|
|
55
|
+
|
|
56
|
+
{{group}}
|
|
57
|
+
|
|
58
|
+
<h3>Medium</h3>
|
|
59
|
+
<itf-checkbox medium label="Test" v-model="test" />
|
|
60
|
+
|
|
61
|
+
<itf-checkbox medium switch label="Test" v-model="test" />
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
<h3>Large</h3>
|
|
66
|
+
<itf-checkbox large label="Test" v-model="test" />
|
|
67
|
+
|
|
68
|
+
<itf-checkbox large switch label="Test" v-model="test" />
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
</itf-form>
|
|
73
|
+
</div>`,
|
|
74
|
+
}));
|
|
@@ -22,15 +22,17 @@
|
|
|
22
22
|
:lazy="!focused"
|
|
23
23
|
:placeholder="placeholder"
|
|
24
24
|
/>
|
|
25
|
-
<div
|
|
26
|
-
<itf-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
<div style="display: none">
|
|
26
|
+
<div ref="dropdown" class="itf-datepicker__dropdown">
|
|
27
|
+
<itf-date-picker-inline
|
|
28
|
+
:value="value"
|
|
29
|
+
:start-view="startView"
|
|
30
|
+
:only-calendar="onlyCalendar"
|
|
31
|
+
:display-format="displayFormat"
|
|
32
|
+
:value-format="valueFormat"
|
|
33
|
+
@input="selectInlineDate"
|
|
34
|
+
></itf-date-picker-inline>
|
|
35
|
+
</div>
|
|
34
36
|
</div>
|
|
35
37
|
</div>
|
|
36
38
|
</template>
|
|
@@ -74,6 +74,7 @@ class itfDatePickerInline extends Vue {
|
|
|
74
74
|
@Prop({ type: String, default: 'ISO' }) valueFormat;
|
|
75
75
|
@Prop({ type: String, default: ITFSettings.defaultDisplayDateFormat }) displayFormat;
|
|
76
76
|
@Prop({ type: String, default: 'days', validator: (value) => ['days', 'months', 'years'].includes(value) }) startView;
|
|
77
|
+
@Prop({ type: String, default: null, validator: (value) => ['days', 'months', 'years'].includes(value) }) minView;
|
|
77
78
|
@Prop({ type: Boolean, default: false }) onlyCalendar;
|
|
78
79
|
@Prop({ type: Boolean, default: false }) range;
|
|
79
80
|
@Prop({ type: Object, default: () => ({}) }) customDays;
|
|
@@ -108,7 +109,8 @@ class itfDatePickerInline extends Vue {
|
|
|
108
109
|
locale: localeEn,
|
|
109
110
|
firstDay: 1,
|
|
110
111
|
range: this.range,
|
|
111
|
-
view: this.valueAsLuxon ? 'days' : this.startView,
|
|
112
|
+
view: (this.valueAsLuxon && !this.minView) ? 'days' : this.startView,
|
|
113
|
+
minView: this.minView,
|
|
112
114
|
selectedDates: this.valueAsLuxon ? [this.valueAsLuxon.toJSDate()] : [],
|
|
113
115
|
onSelect: ({ date }) => {
|
|
114
116
|
if (this.range && !this.calendar.rangeDateTo) {
|
|
@@ -43,15 +43,17 @@
|
|
|
43
43
|
:placeholder="placeholder"
|
|
44
44
|
/>
|
|
45
45
|
</div>
|
|
46
|
-
<div
|
|
47
|
-
<itf-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
<div style="display: none">
|
|
47
|
+
<div ref="dropdown" class="itf-daterange-picker__dropdown">
|
|
48
|
+
<itf-date-range-picker-inline
|
|
49
|
+
:value="value"
|
|
50
|
+
:start-view="startView"
|
|
51
|
+
:only-calendar="onlyCalendar"
|
|
52
|
+
:display-format="displayFormat"
|
|
53
|
+
:value-format="valueFormat"
|
|
54
|
+
@input="selectInlineDate"
|
|
55
|
+
></itf-date-range-picker-inline>
|
|
56
|
+
</div>
|
|
55
57
|
</div>
|
|
56
58
|
</div>
|
|
57
59
|
</template>
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="itf-monthpicker" :class="{'with-addon addon-start': prependIcon}">
|
|
3
|
+
<div class="addon" v-if="prependIcon">
|
|
4
|
+
<slot name="addon">
|
|
5
|
+
<itf-icon :name="prependIcon" />
|
|
6
|
+
</slot>
|
|
7
|
+
</div>
|
|
8
|
+
<input
|
|
9
|
+
ref="input"
|
|
10
|
+
readonly
|
|
11
|
+
class="form-control"
|
|
12
|
+
:class="{ 'is-invalid': isInvalid(), 'is-valid': isSuccess() }"
|
|
13
|
+
@focus="onFocus"
|
|
14
|
+
@blur="onBlur"
|
|
15
|
+
:value="displayValue"
|
|
16
|
+
:placeholder="placeholder"
|
|
17
|
+
/>
|
|
18
|
+
<div style="display: none">
|
|
19
|
+
<div ref="dropdown" class="itf-monthpicker__dropdown">
|
|
20
|
+
<itf-date-picker-inline
|
|
21
|
+
:value="value"
|
|
22
|
+
start-view="months"
|
|
23
|
+
min-view="months"
|
|
24
|
+
only-calendar
|
|
25
|
+
:display-format="displayFormat"
|
|
26
|
+
:value-format="valueFormat"
|
|
27
|
+
@input="selectInlineDate"
|
|
28
|
+
></itf-date-picker-inline>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
<style lang="scss">
|
|
34
|
+
@import '../../assets/scss/variables';
|
|
35
|
+
@import '../../assets/scss/directives/tooltip';
|
|
36
|
+
@import '../../assets/scss/input-addon';
|
|
37
|
+
|
|
38
|
+
.itf-monthpicker {
|
|
39
|
+
&__dropdown {
|
|
40
|
+
width: max-content;
|
|
41
|
+
border: $input-border-width solid $input-border-color;
|
|
42
|
+
border-radius: $input-border-radius;
|
|
43
|
+
background-color: $body-bg;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
@if $enable-shadows {
|
|
46
|
+
@include box-shadow($input-box-shadow, $input-btn-focus-box-shadow);
|
|
47
|
+
} @else {
|
|
48
|
+
// Avoid using mixin so we can pass custom focus shadow properly
|
|
49
|
+
box-shadow: $input-btn-focus-box-shadow;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@media (prefers-color-scheme: notdark) {
|
|
53
|
+
background-color: $dark-body-bg;
|
|
54
|
+
border-color: $dark-border-color;
|
|
55
|
+
box-shadow: 0 0 0 2px $dark-input-focus-border;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
</style>
|
|
60
|
+
<script>
|
|
61
|
+
import { Vue, Component, Prop, Inject } from 'vue-property-decorator';
|
|
62
|
+
import { DateTime } from 'luxon';
|
|
63
|
+
import tippy from 'tippy.js';
|
|
64
|
+
import itfIcon from '../icon/Icon';
|
|
65
|
+
import itfDatePickerInline from './DatePickerInline.vue';
|
|
66
|
+
|
|
67
|
+
export default @Component({
|
|
68
|
+
name: 'itfMonthPicker',
|
|
69
|
+
components: {
|
|
70
|
+
itfIcon,
|
|
71
|
+
itfDatePickerInline
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
class itfMonthPicker extends Vue {
|
|
75
|
+
@Inject({ default: null }) itemLabel;
|
|
76
|
+
@Inject({ default: null }) appendContext;
|
|
77
|
+
|
|
78
|
+
@Prop({ type: String }) value;
|
|
79
|
+
@Prop({ type: String, default: 'ISO' }) valueFormat;
|
|
80
|
+
@Prop({ type: String, default: 'MMMM yyyy' }) displayFormat;
|
|
81
|
+
@Prop({ type: String, default: '' }) placeholder;
|
|
82
|
+
@Prop({ type: String, default: '' }) prependIcon;
|
|
83
|
+
|
|
84
|
+
focused = false;
|
|
85
|
+
|
|
86
|
+
tooltip = null;
|
|
87
|
+
|
|
88
|
+
isInvalid() {
|
|
89
|
+
return this.itemLabel && this.itemLabel.isHasError();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
isSuccess() {
|
|
93
|
+
return this.itemLabel && this.itemLabel.isHasSuccess();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
mounted() {
|
|
97
|
+
// якщо в модалці, то контекст модалки, якщо ні, то аплікейшена
|
|
98
|
+
const context = (this.appendContext && this.appendContext()) || document.body;
|
|
99
|
+
this.tooltip = tippy(this.$refs.input, {
|
|
100
|
+
interactiveBorder: 30,
|
|
101
|
+
interactiveDebounce: 75,
|
|
102
|
+
animation: 'scale',
|
|
103
|
+
arrow: true,
|
|
104
|
+
content: this.$refs.dropdown,
|
|
105
|
+
allowHTML: true,
|
|
106
|
+
trigger: 'click',
|
|
107
|
+
interactive: true,
|
|
108
|
+
placement: 'bottom-start',
|
|
109
|
+
appendTo: context
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get valueAsLuxon() {
|
|
114
|
+
if (!this.value) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
if (this.valueFormat === 'ISO') {
|
|
118
|
+
return DateTime.fromISO(this.value);
|
|
119
|
+
}
|
|
120
|
+
return DateTime.fromFormat(this.value, this.valueFormat);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
get displayValue() {
|
|
124
|
+
if (!this.valueAsLuxon) {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
return this.valueAsLuxon.toFormat(this.displayFormat);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
updateValue(value, emitEmpty = false) {
|
|
131
|
+
const val = value && DateTime.fromFormat(value, this.displayFormat);
|
|
132
|
+
if (!val || !val.isValid) {
|
|
133
|
+
if (emitEmpty) {
|
|
134
|
+
this.$emit('input', null);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
this.$emit('input', this.valueFormat === 'ISO' ? val.toISO() : val.toFormat(this.valueFormat));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
format(date) {
|
|
142
|
+
return DateTime.fromJSDate(date).toFormat(this.displayFormat);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
parse(str) {
|
|
146
|
+
return DateTime.fromFormat(str, this.displayFormat).toJSDate();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
onFocus() {
|
|
150
|
+
this.focused = true;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
onBlur(e) {
|
|
154
|
+
this.focused = false;
|
|
155
|
+
this.updateValue(e.target.value, true);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
selectInlineDate(date) {
|
|
159
|
+
this.$emit('input', date);
|
|
160
|
+
this.tooltip.hide();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
</script>
|