@itfin/components 1.2.84 → 1.2.86
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 +7 -6
- package/src/components/customize/PropertiesEditMenu.vue +53 -0
- package/src/components/customize/PropertiesItemsMenu.vue +33 -0
- package/src/components/customize/PropertiesList.vue +179 -0
- package/src/components/customize/PropertiesPopupMenu.vue +46 -0
- package/src/components/customize/PropertyInlineEdit.vue +46 -0
- package/src/components/customize/index.stories.js +65 -0
- package/src/components/dropdown/Dropdown.vue +8 -19
- package/src/components/icon/components/type_checkbox.vue +2 -0
- package/src/components/icon/components/type_date.vue +2 -0
- package/src/components/icon/components/type_email.vue +2 -0
- package/src/components/icon/components/type_file.vue +2 -0
- package/src/components/icon/components/type_formula.vue +2 -0
- package/src/components/icon/components/type_id.vue +2 -0
- package/src/components/icon/components/type_multiselect.vue +2 -0
- package/src/components/icon/components/type_number.vue +2 -0
- package/src/components/icon/components/type_person.vue +2 -0
- package/src/components/icon/components/type_phone.vue +2 -0
- package/src/components/icon/components/type_relation.vue +2 -0
- package/src/components/icon/components/type_select.vue +2 -0
- package/src/components/icon/components/type_status.vue +2 -0
- package/src/components/icon/components/type_text.vue +2 -0
- package/src/components/icon/components/type_time.vue +2 -0
- package/src/components/icon/components/type_url.vue +2 -0
- package/src/components/icon/components/type_user.vue +2 -0
- package/src/components/icon/icons/type_checkbox.svg +1 -0
- package/src/components/icon/icons/type_date.svg +1 -0
- package/src/components/icon/icons/type_email.svg +1 -0
- package/src/components/icon/icons/type_file.svg +1 -0
- package/src/components/icon/icons/type_formula.svg +1 -0
- package/src/components/icon/icons/type_id.svg +1 -0
- package/src/components/icon/icons/type_multiselect.svg +1 -0
- package/src/components/icon/icons/type_number.svg +1 -0
- package/src/components/icon/icons/type_person.svg +1 -0
- package/src/components/icon/icons/type_phone.svg +1 -0
- package/src/components/icon/icons/type_relation.svg +1 -0
- package/src/components/icon/icons/type_select.svg +1 -0
- package/src/components/icon/icons/type_status.svg +1 -0
- package/src/components/icon/icons/type_text.svg +1 -0
- package/src/components/icon/icons/type_time.svg +1 -0
- package/src/components/icon/icons/type_url.svg +1 -0
- package/src/components/icon/icons/type_user.svg +1 -0
- package/src/components/icon/icons.js +295 -278
- package/src/components/modal/DeleteConfirmModal.vue +78 -0
- package/src/components/modal/ItemEditor.vue +219 -0
- package/src/components/modal/Modal.vue +15 -13
- package/src/components/modal/index.stories.js +15 -10
- package/src/components/popover/ConfirmPopover.vue +1 -1
- package/src/components/popover/DeleteConfirmPopover.vue +39 -0
- package/src/components/popover/IconPopover.vue +136 -0
- package/src/components/popover/SelectPopover.vue +127 -0
- package/src/components/popover/index.stories.js +26 -10
- package/src/components/sortable/AutoScroll.vue +142 -0
- package/src/components/sortable/Sortable.scss +93 -0
- package/src/components/sortable/Sortable.vue +399 -0
- package/src/components/sortable/sortable-item-list/axis.js +9 -0
- package/src/components/sortable/sortable-item-list/mocked-sortable-item-list.js +415 -0
- package/src/components/sortable/sortable-item-list/original-sortable-item-list.js +105 -0
- package/src/components/sortable/utils/event-outside.js +30 -0
- package/src/components/sortable/utils/get-relative-position.js +41 -0
- package/src/components/sortable/utils/sort-item-list.js +13 -0
- package/src/components/sortable/utils/stop-event.js +16 -0
- package/src/components/sortable/utils/vibrate.js +18 -0
- package/src/components/text-field/MoneyField.vue +103 -0
- package/src/components/text-field/index.stories.js +53 -0
- package/src/directives/tooltip.js +1 -1
- package/src/locales/en.js +28 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<slot name="activator" :on="{ click: onClick }" :loading="loading">
|
|
4
|
+
<itf-button
|
|
5
|
+
icon
|
|
6
|
+
:disabled="loading || disabled"
|
|
7
|
+
:loading="loading"
|
|
8
|
+
@click.stop.prevent="onClick"
|
|
9
|
+
>
|
|
10
|
+
<itf-icon name="trash" />
|
|
11
|
+
</itf-button>
|
|
12
|
+
</slot>
|
|
13
|
+
|
|
14
|
+
<itf-modal :visible.sync="isModalShown" @keyup.native.enter="onConfirm">
|
|
15
|
+
<div slot="content" class="modal-content rounded-3 shadow itf-append-context">
|
|
16
|
+
<div class="modal-body p-4 text-center">
|
|
17
|
+
<slot>
|
|
18
|
+
<h5 class="mb-0">{{$t('components.popover.confirmDelete')}}</h5>
|
|
19
|
+
<p class="mb-0">{{$t('components.popover.areYouSureToDeleteThis')}}</p>
|
|
20
|
+
</slot>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="modal-footer flex-nowrap p-0">
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
class="btn btn-lg btn-link fs-6 text-decoration-none col-6 py-3 m-0 rounded-0"
|
|
26
|
+
data-bs-dismiss="modal"
|
|
27
|
+
@click="$emit('cancel')"
|
|
28
|
+
>
|
|
29
|
+
<span v-html="$t('components.popover.noKeepIt')"></span>
|
|
30
|
+
<kbd>Esc</kbd>
|
|
31
|
+
</button>
|
|
32
|
+
<button
|
|
33
|
+
type="button"
|
|
34
|
+
class="btn btn-lg btn-link fs-6 text-decoration-none col-6 py-3 m-0 rounded-0 border-end fw-bold"
|
|
35
|
+
:class="confirmClass"
|
|
36
|
+
@click="onConfirm"
|
|
37
|
+
>
|
|
38
|
+
<span v-html="$t('components.popover.yesDelete')"></span>
|
|
39
|
+
<kbd>↵</kbd>
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
</itf-modal>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
<script>
|
|
48
|
+
import { Vue, Component, Prop } from 'vue-property-decorator';
|
|
49
|
+
import itfIcon from '../icon/Icon';
|
|
50
|
+
import itfButton from '../button/Button';
|
|
51
|
+
import itfModal from './Modal.vue';
|
|
52
|
+
|
|
53
|
+
export default @Component({
|
|
54
|
+
components: {
|
|
55
|
+
itfIcon,
|
|
56
|
+
itfButton,
|
|
57
|
+
itfModal
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
class itfDeleteConfirmModal extends Vue {
|
|
61
|
+
@Prop(Boolean) loading;
|
|
62
|
+
@Prop({ type: Boolean, default: false }) disabled;
|
|
63
|
+
@Prop({ type: String, default: 'text-danger' }) confirmClass;
|
|
64
|
+
@Prop({ type: String, default () { return this.$t('noKeepIt'); } }) cancelCaption;
|
|
65
|
+
@Prop({ type: String, default () { return this.$t('yesDelete'); } }) deleteCaption;
|
|
66
|
+
|
|
67
|
+
isModalShown = false;
|
|
68
|
+
|
|
69
|
+
onClick() {
|
|
70
|
+
this.isModalShown = true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
onConfirm() {
|
|
74
|
+
this.isModalShown = false;
|
|
75
|
+
this.$emit('delete');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
</script>
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<itf-modal
|
|
3
|
+
ref="modal"
|
|
4
|
+
:data-test="dataTest"
|
|
5
|
+
append-to-body
|
|
6
|
+
:visible="value"
|
|
7
|
+
persistent
|
|
8
|
+
close-icon
|
|
9
|
+
:fullscreen="fullscreen"
|
|
10
|
+
:title="title"
|
|
11
|
+
content
|
|
12
|
+
:size="size"
|
|
13
|
+
@keyup.native.enter="submitSaveItem()"
|
|
14
|
+
@update:visible="$emit('input', $event)"
|
|
15
|
+
@close="onClose"
|
|
16
|
+
>
|
|
17
|
+
<itf-alert
|
|
18
|
+
v-if="showError"
|
|
19
|
+
outlined
|
|
20
|
+
type="danger"
|
|
21
|
+
dismissible
|
|
22
|
+
:message="errorMessage"
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
<itf-form v-if="showContent" ref="form">
|
|
26
|
+
<slot />
|
|
27
|
+
</itf-form>
|
|
28
|
+
|
|
29
|
+
<slot name="after-body" />
|
|
30
|
+
|
|
31
|
+
<template #footer>
|
|
32
|
+
<slot name="footer">
|
|
33
|
+
<itf-delete-confirm-popover v-if="canDelete" :loading="loading" @delete="deleteItem">
|
|
34
|
+
<template #activator="{ on }">
|
|
35
|
+
<itf-button color="outline-danger" v-on="on">
|
|
36
|
+
{{ $t('components.modal.delete') }}
|
|
37
|
+
</itf-button>
|
|
38
|
+
</template>
|
|
39
|
+
</itf-delete-confirm-popover>
|
|
40
|
+
<slot name="help"></slot>
|
|
41
|
+
|
|
42
|
+
<div class="itf-spacer" />
|
|
43
|
+
|
|
44
|
+
<itf-button
|
|
45
|
+
v-if="!readonly"
|
|
46
|
+
squircle
|
|
47
|
+
data-test="item-editor-cancel"
|
|
48
|
+
:disabled="loading"
|
|
49
|
+
secondary
|
|
50
|
+
class="me-2"
|
|
51
|
+
@click="$emit('input', false)"
|
|
52
|
+
>
|
|
53
|
+
<span class="me-1">{{ $t('components.modal.cancel') }}</span>
|
|
54
|
+
<kbd>Esc</kbd>
|
|
55
|
+
</itf-button>
|
|
56
|
+
|
|
57
|
+
<slot
|
|
58
|
+
name="save-btn"
|
|
59
|
+
:loading="loading"
|
|
60
|
+
>
|
|
61
|
+
<itf-button
|
|
62
|
+
primary
|
|
63
|
+
squircle
|
|
64
|
+
data-test="item-editor-save"
|
|
65
|
+
:disabled="loading"
|
|
66
|
+
:loading="loading"
|
|
67
|
+
@click="submitSaveItem()"
|
|
68
|
+
>
|
|
69
|
+
<span class="me-1">{{ saveBtnCaption }}</span>
|
|
70
|
+
<kbd>↵</kbd>
|
|
71
|
+
</itf-button>
|
|
72
|
+
</slot>
|
|
73
|
+
</slot>
|
|
74
|
+
</template>
|
|
75
|
+
</itf-modal>
|
|
76
|
+
</template>
|
|
77
|
+
<script>
|
|
78
|
+
import { Vue, Component, Model, Prop, Watch, Provide } from 'vue-property-decorator';
|
|
79
|
+
import itfAlert from '../alert/Alert';
|
|
80
|
+
import itfForm from '../form/Form';
|
|
81
|
+
import itfModal from '../modal/Modal';
|
|
82
|
+
import itfButton from '../button/Button';
|
|
83
|
+
import itfIcon from '../icon/Icon';
|
|
84
|
+
import itfDeleteConfirmPopover from '../popover/DeleteConfirmPopover.vue';
|
|
85
|
+
|
|
86
|
+
export default @Component({
|
|
87
|
+
components: {
|
|
88
|
+
itfIcon,
|
|
89
|
+
itfAlert,
|
|
90
|
+
itfForm,
|
|
91
|
+
itfModal,
|
|
92
|
+
itfButton,
|
|
93
|
+
itfDeleteConfirmPopover
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
class itfItemEditor extends Vue {
|
|
97
|
+
@Provide('itemEditorForm')
|
|
98
|
+
getForm () {
|
|
99
|
+
return this.$refs.form;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@Model('input') value;
|
|
103
|
+
@Prop({ type: String, default () { return this.$t('components.modal.itemHasBeenSuccessfullySaved'); } }) successMessage;
|
|
104
|
+
@Prop({ type: String, default () { return this.$t('components.modal.itemHasBeenSuccessfullyDeleted'); } }) successDeleteMessage;
|
|
105
|
+
@Prop() saveFunc;
|
|
106
|
+
@Prop() deleteFunc;
|
|
107
|
+
@Prop() dataTest;
|
|
108
|
+
@Prop(String) title;
|
|
109
|
+
@Prop({ type: String, default () { return this.$t('components.modal.save'); } }) saveBtnCaption;
|
|
110
|
+
@Prop({ type: String, default: 'lg' }) size;
|
|
111
|
+
@Prop(Boolean) canDelete;
|
|
112
|
+
@Prop(Boolean) fullscreen;
|
|
113
|
+
@Prop(Boolean) readonly; // показує форму, але кнопка Save не викликає saveFunc
|
|
114
|
+
@Prop() createdAt;
|
|
115
|
+
@Prop() creator;
|
|
116
|
+
|
|
117
|
+
loading = false;
|
|
118
|
+
showError = false;
|
|
119
|
+
showContent = false;
|
|
120
|
+
|
|
121
|
+
errorMessage = null;
|
|
122
|
+
|
|
123
|
+
details = {};
|
|
124
|
+
|
|
125
|
+
mounted () {
|
|
126
|
+
this.onShowChange();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
resetValidation () {
|
|
130
|
+
this.$refs.form.resetValidation();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
reassignFormInputs (form) {
|
|
134
|
+
const inputs = [];
|
|
135
|
+
// Copied from VForm's previous life* which had a getInputs() function
|
|
136
|
+
const search = (children, depth = 0) => {
|
|
137
|
+
for (let index = 0; index < children.length; index++) {
|
|
138
|
+
const child = children[index];
|
|
139
|
+
if (child.errorBucket !== undefined) { inputs.push(child); } else { search(child.$children, depth + 1); }
|
|
140
|
+
}
|
|
141
|
+
if (depth === 0) { return inputs; }
|
|
142
|
+
};
|
|
143
|
+
search(form.$children);
|
|
144
|
+
form.components = inputs;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async submitSaveItem (successCallback) {
|
|
148
|
+
if (!this.$refs.form) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.reassignFormInputs(this.$refs.form); // це потрібно щоб працювала валідація вкладених v-form, наприклад, кастомні поля
|
|
152
|
+
|
|
153
|
+
if (!this.readonly && !this.$refs.form.doValidation()) {
|
|
154
|
+
this.$refs.modal.scrollModalTop();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
this.loading = true;
|
|
159
|
+
this.showError = false;
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
if (!this.readonly) {
|
|
163
|
+
const data = await this.saveFunc();
|
|
164
|
+
if (this.successMessage) {
|
|
165
|
+
this.$showSuccess(this.successMessage);
|
|
166
|
+
}
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
this.$emit('saved', data);
|
|
169
|
+
if (typeof successCallback === 'function') {
|
|
170
|
+
successCallback();
|
|
171
|
+
}
|
|
172
|
+
}, 600); // modal animation 500 + 100 delay
|
|
173
|
+
}
|
|
174
|
+
this.$emit('input', false);
|
|
175
|
+
} catch (err) {
|
|
176
|
+
this.$emit('save-error');
|
|
177
|
+
this.errorMessage = err.message || this.$t('components.modal.networkProblem');
|
|
178
|
+
this.$refs.modal && this.$refs.modal.scrollModalTop();
|
|
179
|
+
this.$showError(this.$t('components.modal.pleaseFixTheErrorsBelowToProceed'));
|
|
180
|
+
|
|
181
|
+
this.showError = true;
|
|
182
|
+
}
|
|
183
|
+
this.loading = false;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async deleteItem () {
|
|
187
|
+
this.loading = true;
|
|
188
|
+
this.showError = false;
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const data = await this.deleteFunc();
|
|
192
|
+
|
|
193
|
+
this.$emit('deleted', data);
|
|
194
|
+
|
|
195
|
+
this.$emit('input', false);
|
|
196
|
+
|
|
197
|
+
this.$showSuccess(this.successDeleteMessage);
|
|
198
|
+
} catch (err) {
|
|
199
|
+
this.errorMessage = err.message || this.$t('components.modal.networkProblem');
|
|
200
|
+
|
|
201
|
+
this.showError = true;
|
|
202
|
+
}
|
|
203
|
+
this.loading = false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
@Watch('value')
|
|
207
|
+
onShowChange () {
|
|
208
|
+
this.$nextTick(() => this.$refs.form && this.$refs.form.resetValidation());
|
|
209
|
+
this.showError = false;
|
|
210
|
+
if (this.value) {
|
|
211
|
+
this.showContent = true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
onClose() {
|
|
216
|
+
this.showContent = false;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
</script>
|
|
@@ -5,20 +5,22 @@
|
|
|
5
5
|
class="modal-dialog"
|
|
6
6
|
:class="{[`modal-${size}`]: size, 'modal-fullscreen': fullscreen, 'modal-fullscreen-sm-down': !fullscreen}"
|
|
7
7
|
>
|
|
8
|
-
<
|
|
9
|
-
<div class="modal-
|
|
10
|
-
<
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
<slot name="content">
|
|
9
|
+
<div class="modal-content itf-append-context" ref="content" @click.stop>
|
|
10
|
+
<div v-if="title" class="modal-header">
|
|
11
|
+
<slot name="title">
|
|
12
|
+
<h5 class="modal-title" :id="modalId">{{title}}</h5>
|
|
13
|
+
</slot>
|
|
14
|
+
<itf-button icon @click="close" :aria-label="$t('components.close')" class="btn-close"></itf-button>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="modal-body" v-if="value">
|
|
17
|
+
<slot></slot>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="modal-footer" v-if="$slots.footer">
|
|
20
|
+
<slot name="footer"></slot>
|
|
21
|
+
</div>
|
|
14
22
|
</div>
|
|
15
|
-
|
|
16
|
-
<slot></slot>
|
|
17
|
-
</div>
|
|
18
|
-
<div class="modal-footer" v-if="$slots.footer">
|
|
19
|
-
<slot name="footer"></slot>
|
|
20
|
-
</div>
|
|
21
|
-
</div>
|
|
23
|
+
</slot>
|
|
22
24
|
</div>
|
|
23
25
|
</div>
|
|
24
26
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { storiesOf } from '@storybook/vue';
|
|
2
2
|
import itfModal from './Modal.vue';
|
|
3
|
+
import itfDeleteConfirmModal from './DeleteConfirmModal.vue';
|
|
4
|
+
import itfItemEditor from './ItemEditor.vue';
|
|
3
5
|
import itfForm from '../form/Form.vue';
|
|
6
|
+
import itfTextField from '../text-field/TextField.vue';
|
|
4
7
|
import itfLabel from '../form/Label.vue';
|
|
5
|
-
import itfDatePicker from '../datepicker/DatePicker.vue';
|
|
6
|
-
import itfDateRangePicker from '../datepicker/DateRangePicker.vue';
|
|
7
8
|
import itfApp from '../app/App.vue';
|
|
8
9
|
|
|
9
10
|
storiesOf('Common', module)
|
|
@@ -13,12 +14,14 @@ storiesOf('Common', module)
|
|
|
13
14
|
itfForm,
|
|
14
15
|
itfLabel,
|
|
15
16
|
itfModal,
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
itfTextField,
|
|
18
|
+
itfDeleteConfirmModal,
|
|
19
|
+
itfItemEditor
|
|
18
20
|
},
|
|
19
21
|
data() {
|
|
20
22
|
return {
|
|
21
23
|
isVisible: false,
|
|
24
|
+
isItemEdit: false,
|
|
22
25
|
from: '2021-01-16',
|
|
23
26
|
to: null,
|
|
24
27
|
dates: [],
|
|
@@ -29,7 +32,7 @@ storiesOf('Common', module)
|
|
|
29
32
|
},
|
|
30
33
|
methods: {
|
|
31
34
|
submit() {
|
|
32
|
-
|
|
35
|
+
console.info('saved');
|
|
33
36
|
},
|
|
34
37
|
reset() {
|
|
35
38
|
this.$refs.form.resetValidation();
|
|
@@ -42,17 +45,19 @@ storiesOf('Common', module)
|
|
|
42
45
|
|
|
43
46
|
<pre>
|
|
44
47
|
|
|
45
|
-
<itf-datepicker></itf-datepicker>
|
|
46
|
-
|
|
47
|
-
<button @click="$showSuccess('Success')">Show success</button>
|
|
48
|
-
<button @click="$showError('Error')">Show error</button>
|
|
49
|
-
<button @click="$try(() => { throw new Error('Invalid function'); })">Try function with error</button>
|
|
50
48
|
</pre>
|
|
51
49
|
|
|
52
50
|
<h2>Example</h2>
|
|
53
51
|
|
|
54
52
|
<itf-app ref="app">
|
|
55
53
|
|
|
54
|
+
<itf-delete-confirm-modal></itf-delete-confirm-modal>
|
|
55
|
+
|
|
56
|
+
<button @click="isItemEdit = !isItemEdit" class="btn btn-primary" type="button">Show item edit</button>
|
|
57
|
+
<itf-item-editor v-model="isItemEdit" :save-func="submit" title="Edit item">
|
|
58
|
+
<itf-text-field />
|
|
59
|
+
</itf-item-editor>
|
|
60
|
+
|
|
56
61
|
<button @click="isVisible = !isVisible" class="btn btn-primary" type="button">Show modal</button>
|
|
57
62
|
{{isVisible}}
|
|
58
63
|
<itf-modal title="Test" :visible.sync="isVisible">
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<itf-popover :visible.sync="value" ref="popover" :trigger="trigger" custom-class="nopadding">
|
|
2
|
+
<itf-popover :visible.sync="value" ref="popover" :trigger="trigger" custom-class="nopadding" :placement="placement">
|
|
3
3
|
<template #activator="{ on }">
|
|
4
4
|
<slot name="activator" :on="on"></slot>
|
|
5
5
|
</template>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<itf-confirm-popover :cancel-caption="cancelCaption" :confirm-class="confirmClass" :confirm-caption="deleteCaption" @confirm="$emit('delete')">
|
|
3
|
+
<template #activator="{ on }">
|
|
4
|
+
<slot name="activator" :on="on" :loading="loading">
|
|
5
|
+
<itf-button
|
|
6
|
+
icon
|
|
7
|
+
:disabled="loading || disabled"
|
|
8
|
+
:loading="loading"
|
|
9
|
+
v-on="on"
|
|
10
|
+
@click.stop.prevent
|
|
11
|
+
>
|
|
12
|
+
<itf-icon name="trash" />
|
|
13
|
+
</itf-button>
|
|
14
|
+
</slot>
|
|
15
|
+
</template>
|
|
16
|
+
<slot>{{$t('components.popover.areYouSureToDeleteThis')}}</slot>
|
|
17
|
+
</itf-confirm-popover>
|
|
18
|
+
</template>
|
|
19
|
+
<script>
|
|
20
|
+
import { Vue, Component, Prop } from 'vue-property-decorator';
|
|
21
|
+
import itfIcon from '../icon/Icon';
|
|
22
|
+
import itfButton from '../button/Button';
|
|
23
|
+
import itfConfirmPopover from './ConfirmPopover';
|
|
24
|
+
|
|
25
|
+
export default @Component({
|
|
26
|
+
components: {
|
|
27
|
+
itfIcon,
|
|
28
|
+
itfButton,
|
|
29
|
+
itfConfirmPopover
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
class itfDeletePopover extends Vue {
|
|
33
|
+
@Prop(Boolean) loading;
|
|
34
|
+
@Prop({ type: Boolean, default: false }) disabled;
|
|
35
|
+
@Prop({ type: String, default: 'text-danger' }) confirmClass;
|
|
36
|
+
@Prop({ type: String, default () { return this.$t('components.popover.noKeepIt'); } }) cancelCaption;
|
|
37
|
+
@Prop({ type: String, default () { return this.$t('components.popover.yesDelete'); } }) deleteCaption;
|
|
38
|
+
}
|
|
39
|
+
</script>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<itf-popover :visible.sync="value" ref="popover" :trigger="trigger" custom-class="nopadding" :placement="placement">
|
|
3
|
+
<template #activator="{ on }">
|
|
4
|
+
<slot name="activator" :on="on"></slot>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<div class="modal-body py-2 px-3 text-center itf-select-popover">
|
|
8
|
+
<div class="d-flex align-items-center justify-content-between">
|
|
9
|
+
<div class="mb-0" v-if="title" v-html="title"></div>
|
|
10
|
+
|
|
11
|
+
<itf-button v-if="removable" small secondary @click="onSelectIcon(null)">
|
|
12
|
+
Remove
|
|
13
|
+
</itf-button>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div class="d-flex py-2">
|
|
17
|
+
<itf-text-field
|
|
18
|
+
class="flex-grow-1 me-2"
|
|
19
|
+
v-model="search"
|
|
20
|
+
placeholder="Filter"
|
|
21
|
+
prepend-icon="search"
|
|
22
|
+
clearable
|
|
23
|
+
/>
|
|
24
|
+
|
|
25
|
+
<itf-button v-tooltip="$t('random')" @click="onRandom">
|
|
26
|
+
<itf-icon name="shuffle" />
|
|
27
|
+
</itf-button>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="itf-select-popover__scroll text-start">
|
|
31
|
+
<template v-if="!search && recentIcons.length">
|
|
32
|
+
<div class="py-1 text-muted">Recent</div>
|
|
33
|
+
<div class="d-flex align-items-start flex-wrap flex-grow-0">
|
|
34
|
+
<itf-button v-for="icon of recentIcons" :key="icon" icon v-tooltip="icon" @click="onSelectIcon(icon)">
|
|
35
|
+
<itf-icon :name="icon" />
|
|
36
|
+
</itf-button>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="py-1 text-muted">Icons</div>
|
|
40
|
+
</template>
|
|
41
|
+
<div class="d-flex align-items-start flex-wrap flex-grow-0">
|
|
42
|
+
<itf-button v-for="icon of iconsList" :key="icon" icon v-tooltip="icon" @click="onSelectIcon(icon)">
|
|
43
|
+
<itf-icon :name="icon" />
|
|
44
|
+
</itf-button>
|
|
45
|
+
</div>
|
|
46
|
+
<div v-if="!iconsList.length" class="text-muted pt-1">
|
|
47
|
+
No results
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<slot>
|
|
51
|
+
</slot>
|
|
52
|
+
</div>
|
|
53
|
+
</itf-popover>
|
|
54
|
+
</template>
|
|
55
|
+
<style lang="scss">
|
|
56
|
+
.itf-select-popover {
|
|
57
|
+
width: 280px;
|
|
58
|
+
min-width: 180px;
|
|
59
|
+
max-width: calc(100vw - 24px);
|
|
60
|
+
height: 356px;
|
|
61
|
+
max-height: 70vh;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
|
|
66
|
+
&__scroll {
|
|
67
|
+
overflow: hidden auto;
|
|
68
|
+
flex-grow: 1;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
72
|
+
<script>
|
|
73
|
+
import { Vue, Component, Prop, PropSync } from 'vue-property-decorator';
|
|
74
|
+
import itfPopover from './Popover';
|
|
75
|
+
import itfIcon from '../icon/Icon';
|
|
76
|
+
import iconsList from '../icon/icons';
|
|
77
|
+
import itfButton from '../button/Button';
|
|
78
|
+
import itfTextField from '../text-field/TextField.vue';
|
|
79
|
+
import tooltip from '../../directives/tooltip';
|
|
80
|
+
|
|
81
|
+
const RECENT_ITEMS_KEY = 'itfSelectPopoverRecent';
|
|
82
|
+
|
|
83
|
+
export default @Component({
|
|
84
|
+
name: 'itfSelectPopover',
|
|
85
|
+
directives: {
|
|
86
|
+
tooltip
|
|
87
|
+
},
|
|
88
|
+
components: {
|
|
89
|
+
itfIcon,
|
|
90
|
+
itfPopover,
|
|
91
|
+
itfButton,
|
|
92
|
+
itfTextField
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
class itfSelectPopover extends Vue {
|
|
96
|
+
@PropSync('visible') value;
|
|
97
|
+
@Prop({ type: String, default: '' }) title;
|
|
98
|
+
@Prop({ type: Boolean, default: '' }) removable;
|
|
99
|
+
@Prop({ type: String, default: 'bottom', validator: (value) => ['bottom', 'left', 'right', 'top'].includes(value) }) placement;
|
|
100
|
+
@Prop({ type: String, default: 'click', validator: (value) => ['click', 'focus', 'hover', 'manual'].includes(value) }) trigger;
|
|
101
|
+
|
|
102
|
+
search = '';
|
|
103
|
+
recentIcons = [];
|
|
104
|
+
|
|
105
|
+
created() {
|
|
106
|
+
this.recentIcons = this.getRecentIcons();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get iconsList() {
|
|
110
|
+
return Object.keys(iconsList).filter((icon) => icon.includes(this.search.toLowerCase()));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
onSelectIcon(icon) {
|
|
114
|
+
this.search = '';
|
|
115
|
+
this.$emit('input', icon);
|
|
116
|
+
this.$refs.popover.hide();
|
|
117
|
+
|
|
118
|
+
if (icon) {
|
|
119
|
+
const icons = this.getRecentIcons();
|
|
120
|
+
icons.unshift(icon);
|
|
121
|
+
this.recentIcons = [...new Set(icons.slice(0, 14))];
|
|
122
|
+
localStorage.setItem(RECENT_ITEMS_KEY, JSON.stringify(this.recentIcons));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getRecentIcons() {
|
|
127
|
+
return localStorage.getItem(RECENT_ITEMS_KEY) ? JSON.parse(localStorage.getItem(RECENT_ITEMS_KEY)) : [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
onRandom() {
|
|
131
|
+
const keys = Object.keys(iconsList);
|
|
132
|
+
const icon = keys[Math.floor(Math.random() * keys.length)];
|
|
133
|
+
this.onSelectIcon(icon);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
</script>
|