@it-enterprise/forcebpm-ui-kit 1.0.2-beta.3 → 1.0.2-beta.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +81 -65
  2. package/index.js +48 -1
  3. package/package.json +35 -15
  4. package/plugin.js +117 -0
  5. package/src/FActionSnackbar.vue +107 -0
  6. package/src/FAvatar.vue +10 -8
  7. package/src/{FConfirmModal.vue → FConfirmDialog.vue} +38 -28
  8. package/src/FContextMenu.vue +87 -0
  9. package/src/FDialog.vue +264 -0
  10. package/src/FLangSwitcher.vue +47 -0
  11. package/src/FNoData.vue +80 -0
  12. package/src/FNotify.vue +257 -0
  13. package/src/FPagination.vue +163 -0
  14. package/src/FPreLoader.vue +105 -0
  15. package/src/FSearchPanel.vue +191 -0
  16. package/src/FShare.vue +134 -0
  17. package/src/FTruncate.vue +121 -0
  18. package/src/FUserGroupPicker.vue +634 -0
  19. package/src/FUserRoles.vue +195 -0
  20. package/src/assets/fonts/ProximaNova-Bold.woff +0 -0
  21. package/src/assets/fonts/ProximaNova-Bold.woff2 +0 -0
  22. package/src/assets/fonts/ProximaNova-Regular.woff +0 -0
  23. package/src/assets/fonts/ProximaNova-Regular.woff2 +0 -0
  24. package/src/assets/fonts/ProximaNova-Semibold.woff +0 -0
  25. package/src/assets/fonts/ProximaNova-Semibold.woff2 +0 -0
  26. package/src/assets/fonts/loadFonts.js +55 -0
  27. package/src/assets/icons/bell-mute.svg +4 -0
  28. package/src/assets/icons/bell.svg +3 -0
  29. package/src/assets/icons/check.svg +3 -0
  30. package/src/assets/icons/checkbox-false.svg +3 -0
  31. package/src/assets/icons/checkbox-true.svg +4 -0
  32. package/src/assets/icons/chevron.svg +3 -0
  33. package/src/assets/icons/color-radio-active.svg +4 -0
  34. package/src/assets/icons/color-radio.svg +3 -0
  35. package/src/assets/icons/ctx-arrow.svg +3 -0
  36. package/src/assets/icons/ctx-delete.svg +6 -0
  37. package/src/assets/icons/ctx-link.svg +5 -0
  38. package/src/assets/icons/ctx-more.svg +5 -0
  39. package/src/assets/icons/ctx-pen.svg +4 -0
  40. package/src/assets/icons/delete.svg +6 -0
  41. package/src/assets/icons/dots.svg +5 -0
  42. package/src/assets/icons/filter.svg +3 -0
  43. package/src/assets/icons/folder.svg +3 -0
  44. package/src/assets/icons/open-envelope.svg +6 -0
  45. package/src/assets/icons/pass-eye-close.svg +3 -0
  46. package/src/assets/icons/pass-eye-open.svg +4 -0
  47. package/src/assets/icons/plus-solid.svg +4 -0
  48. package/src/assets/icons/plus.svg +4 -0
  49. package/src/assets/icons/radio-false.svg +3 -0
  50. package/src/assets/icons/radio-true.svg +4 -0
  51. package/src/assets/icons/search.svg +3 -0
  52. package/src/assets/icons/sort-solid.svg +6 -0
  53. package/src/assets/icons/sort.svg +6 -0
  54. package/src/assets/icons/star-line.svg +3 -0
  55. package/src/assets/icons/star.svg +3 -0
  56. package/src/assets/icons/times.svg +4 -0
  57. package/src/assets/images/0.gif +0 -0
  58. package/src/assets/images/0.svg +4 -0
  59. package/src/assets/images/3.svg +4 -0
  60. package/src/assets/images/4.svg +4 -0
  61. package/src/assets/images/5.svg +4 -0
  62. package/src/assets/scss/buttons.scss +60 -0
  63. package/src/assets/scss/card.scss +11 -0
  64. package/src/assets/scss/expansion.scss +45 -0
  65. package/src/assets/scss/icons.scss +273 -0
  66. package/src/assets/scss/index.scss +10 -0
  67. package/src/assets/scss/input.scss +361 -0
  68. package/src/assets/scss/overlay.scss +69 -0
  69. package/src/assets/scss/skeleton.scss +22 -0
  70. package/src/assets/scss/tables.scss +143 -0
  71. package/src/assets/scss/tabs.scss +42 -0
  72. package/src/assets/scss/utilities.scss +167 -0
  73. package/src/f-date-picker/FDatePicker.vue +322 -0
  74. package/src/f-date-picker/FMenuDatePicker.vue +337 -0
  75. package/src/f-date-picker/FTextFieldDate.vue +498 -0
  76. package/src/f-toolbar/FFilterPanel.vue +62 -0
  77. package/src/f-toolbar/FSortPanel.vue +55 -0
  78. package/src/f-toolbar/FTabPanel.vue +123 -0
  79. package/src/f-toolbar/FToolbar.vue +131 -0
  80. package/src/f-toolbar/FViewerPanel.vue +71 -0
  81. package/src/forcebpmIcon.js +69 -0
  82. package/src/locales/en.json +115 -0
  83. package/src/locales/index.js +7 -0
  84. package/src/locales/ru.json +116 -0
  85. package/src/locales/uk.json +116 -0
  86. package/src/utils/color.js +38 -0
@@ -1,24 +1,30 @@
1
1
  <script setup>
2
- // FConfirmModal
3
- import { onBeforeMount, onBeforeUnmount, ref, defineEmits } from 'vue'
4
- import events from '@/plugins/eventBus/events'
5
- import emitter from '@/plugins/eventBus'
2
+ // FConfirmDialog
3
+ import { onBeforeMount, onBeforeUnmount, ref, inject } from 'vue'
6
4
 
7
- const show = ref(false)
5
+ const props = defineProps({
6
+ name: {
7
+ type: String,
8
+ default: 'confirm-dialog'
9
+ }
10
+ })
11
+
12
+ const state = ref(false)
8
13
  const loading = ref(false)
9
14
  const title = ref('')
10
15
  const subTitle = ref('')
11
16
  const approve = ref(null)
12
- const decline = ref(null)
17
+ const reject = ref(null)
13
18
 
14
- const emit = defineEmits(['confirmModalMount', 'confirmModalUnmount'])
19
+ // Inject emitter and events from plugin
20
+ const emitter = inject('fbpm-emitter', null)
15
21
 
16
- const showModal = props => {
22
+ const open = props => {
17
23
  title.value = props.title
18
24
  subTitle.value = props.subTitle
19
25
  approve.value = props.approve
20
- decline.value = props.decline
21
- show.value = true
26
+ reject.value = props.reject
27
+ state.value = true
22
28
  }
23
29
 
24
30
  const approveHandler = async () => {
@@ -27,38 +33,42 @@ const approveHandler = async () => {
27
33
  await approve.value()
28
34
  }
29
35
  loading.value = false
30
- show.value = false
36
+ state.value = false
31
37
  }
32
38
 
33
- const declineHandler = () => {
34
- if (typeof decline.value === 'function') {
35
- decline.value()
39
+ const rejectHandler = () => {
40
+ if (typeof reject.value === 'function') {
41
+ reject.value()
36
42
  }
37
- show.value = false
43
+ state.value = false
38
44
  }
39
45
 
40
46
  onBeforeMount(() => {
41
- emitter.on(events.confirmModal, showModal)
47
+ if (emitter && props.name) {
48
+ emitter.on(props.name, open)
49
+ }
42
50
  })
43
51
 
44
52
  onBeforeUnmount(() => {
45
- emitter.off(events.confirmModal, showModal)
53
+ if (emitter && props.name) {
54
+ emitter.off(props.name, open)
55
+ }
46
56
  })
47
57
  </script>
48
58
  <template>
49
- <v-dialog v-model="show" content-class="f-confirm-modal" max-width="500" persistent @keydown.esc="declineHandler" @keydown.enter="approveHandler">
59
+ <v-dialog v-model="state" content-class="f-confirm-dialog" max-width="500" persistent @keydown.esc="rejectHandler" @keydown.enter="approveHandler">
50
60
  <!-- Header -->
51
- <div class="f-confirm-modal-header">{{ title }}</div>
61
+ <div class="f-confirm-dialog-header">{{ title }}</div>
52
62
 
53
63
  <!-- Body -->
54
64
  <div class="d-flex flex-column">
55
- <div class="f-confirm-modal-content mt-5 mx-5 mb-10">{{ subTitle }}</div>
65
+ <div class="f-confirm-dialog-content mt-5 mx-5 mb-10">{{ subTitle }}</div>
56
66
 
57
67
  <!-- Actions -->
58
- <v-btn class="f-confirm-modal-btn" variant="outlined" :disabled="loading" @click="declineHandler">
68
+ <v-btn class="f-confirm-dialog-btn" variant="outlined" :disabled="loading" @click="rejectHandler">
59
69
  {{ $t('buttons.no') }}
60
70
  </v-btn>
61
- <v-btn class="f-confirm-modal-btn mt-2 mb-10" :loading="loading" @click="approveHandler">
71
+ <v-btn class="f-confirm-dialog-btn mt-2 mb-10" :loading="loading" @click="approveHandler">
62
72
  {{ $t('buttons.yes') }}
63
73
  </v-btn>
64
74
  </div>
@@ -66,14 +76,14 @@ onBeforeUnmount(() => {
66
76
  </template>
67
77
 
68
78
  <style lang="scss" scoped>
69
- :deep(.f-confirm-modal) {
79
+ :deep(.f-confirm-dialog) {
70
80
  background: rgb(var(--v-theme-background));
71
81
  box-shadow: 0px 4px 20px 0px rgb(var(--v-theme-title));
72
82
  border-radius: 5.8px;
73
83
  display: flex;
74
84
  flex-direction: column;
75
85
  max-height: 90vh;
76
- .f-confirm-modal-header {
86
+ .f-confirm-dialog-header {
77
87
  display: flex;
78
88
  justify-content: space-between;
79
89
  background: rgb(var(--v-theme-background-dark));
@@ -85,20 +95,20 @@ onBeforeUnmount(() => {
85
95
  color: rgb(var(--v-theme-fields-light));
86
96
  padding: 12px 0px 10px 20px;
87
97
  }
88
- .f-confirm-modal-content {
98
+ .f-confirm-dialog-content {
89
99
  font-size: 1em;
90
100
  line-height: 1.5em;
91
101
  color: rgb(var(--v-theme-title));
92
102
  }
93
- .f-confirm-modal-btn {
103
+ .f-confirm-dialog-btn {
94
104
  margin: 0 34px;
95
105
  }
96
106
  }
97
107
 
98
108
  .v-theme--dark {
99
- :deep(.f-confirm-modal) {
109
+ :deep(.f-confirm-dialog) {
100
110
  box-shadow: var(--f-box-shadow);
101
- .f-confirm-modal-header {
111
+ .f-confirm-dialog-header {
102
112
  border-bottom: 1px solid rgb(var(--v-theme-line));
103
113
  }
104
114
  }
@@ -0,0 +1,87 @@
1
+ <script setup>
2
+ // FContextMenu
3
+ import { computed, mergeProps } from 'vue'
4
+ const props = defineProps({
5
+ list: {
6
+ type: Array,
7
+ required: true
8
+ // Example of list item:
9
+ // {
10
+ // name: 'Button Name',
11
+ // action: () => {},
12
+ // color: 'text',
13
+ // icon: 'fire',
14
+ // dividerTop: false, // optional setting to add top visual separation of list blocks
15
+ // dividerBottom: false // optional setting to add bottom visual separation of list blocks
16
+ // }
17
+ },
18
+ menuProps: {
19
+ type: Object,
20
+ default: () => ({
21
+ location: 'start'
22
+ })
23
+ },
24
+ tooltipProps: {
25
+ type: Object,
26
+ default: () => ({
27
+ location: 'bottom'
28
+ })
29
+ },
30
+ disabled: {
31
+ type: Boolean,
32
+ default: false
33
+ },
34
+ btnClass: {
35
+ type: String,
36
+ default: ''
37
+ }
38
+ })
39
+
40
+ const menu = defineModel({
41
+ type: Boolean,
42
+ default: false
43
+ })
44
+
45
+ const isEmpty = computed(() => !props.list.length || props.list.every(el => el.hide))
46
+ </script>
47
+
48
+ <template>
49
+ <v-menu v-if="!isEmpty" v-model="menu" v-bind="menuProps" content-class="f-context-menu-content">
50
+ <template #activator="{ props: propsMenu }">
51
+ <v-tooltip :disabled="menu || disabled" :text="$t('tooltip.actions')" v-bind="tooltipProps">
52
+ <template #activator="{ props: propsTooltip }">
53
+ <v-btn
54
+ size="25"
55
+ variant="text"
56
+ class="f-context-menu-activator"
57
+ :class="btnClass"
58
+ :disabled="disabled"
59
+ v-bind="mergeProps(propsMenu, propsTooltip)"
60
+ >
61
+ <FIcon icon="dots" size="18" :color="disabled ? 'disabled' : 'secondary'" />
62
+ </v-btn>
63
+ </template>
64
+ </v-tooltip>
65
+ </template>
66
+
67
+ <v-list class="f-context-menu-list">
68
+ <template v-for="(item, idx) in list">
69
+ <template v-if="!item.hide">
70
+ <!-- Divider top -->
71
+ <v-divider v-if="item.dividerTop" :key="`divider-top-${idx}`" class="bg-disabled my-3" />
72
+
73
+ <!-- List item -->
74
+ <v-list-item :key="idx" @click="item.action">
75
+ <v-list-item-title :class="`text-${item.color || 'subTitle'}`">
76
+ <FIcon v-if="item.icon" :icon="item.icon" :color="item.color || 'text'" size="18" />
77
+ <span :class="item.icon ? 'ml-1' : 'ml-5'">{{ item.name }}</span>
78
+ </v-list-item-title>
79
+ </v-list-item>
80
+
81
+ <!-- Divider bottom -->
82
+ <v-divider v-if="item.dividerBottom" :key="`divider-bottom-${idx}`" class="bg-disabled my-3" />
83
+ </template>
84
+ </template>
85
+ </v-list>
86
+ </v-menu>
87
+ </template>
@@ -0,0 +1,264 @@
1
+ <script setup>
2
+ // FDialog
3
+ import { onBeforeMount, onBeforeUnmount, watch, inject } from 'vue'
4
+
5
+ const props = defineProps({
6
+ disableCloseBtn: {
7
+ type: Boolean,
8
+ default: false
9
+ },
10
+ name: {
11
+ type: String,
12
+ required: true
13
+ },
14
+ title: {
15
+ type: String,
16
+ default: 'Modal title'
17
+ },
18
+ backAction: {
19
+ type: Function,
20
+ default: null
21
+ },
22
+ noSpace: {
23
+ type: Boolean,
24
+ default: false
25
+ },
26
+ isSearch: {
27
+ type: Boolean,
28
+ default: false
29
+ },
30
+ headerActions: {
31
+ type: Array,
32
+ default: null,
33
+ validator: value => {
34
+ return value.every(
35
+ action =>
36
+ 'label' in action &&
37
+ 'icon' in action &&
38
+ 'action' in action &&
39
+ typeof action.label === 'string' &&
40
+ typeof action.icon === 'string' &&
41
+ typeof action.action === 'function'
42
+ )
43
+ }
44
+ },
45
+ actions: {
46
+ type: Array,
47
+ default: null,
48
+ validator: value => {
49
+ return value.every(
50
+ action =>
51
+ 'name' in action &&
52
+ 'action' in action &&
53
+ typeof action.name === 'string' &&
54
+ typeof action.action === 'function' &&
55
+ (action.outlined === undefined || typeof action.outlined === 'boolean')
56
+ )
57
+ }
58
+ }
59
+ })
60
+
61
+ const emit = defineEmits(['open', 'close'])
62
+
63
+ const emitter = inject('fbpm-emitter', null)
64
+ const events = inject('fbpm-events', {})
65
+
66
+ const modelValue = defineModel({
67
+ type: Boolean,
68
+ default: false
69
+ })
70
+
71
+ const search = defineModel('search', {
72
+ type: String,
73
+ default: null
74
+ })
75
+
76
+ onBeforeMount(() => {
77
+ emitter.on(events.modal[props.name], open)
78
+ })
79
+
80
+ onBeforeUnmount(() => {
81
+ emitter.off(events.modal[props.name], open)
82
+ })
83
+
84
+ const open = props => {
85
+ modelValue.value = true
86
+ emit('open', props)
87
+ }
88
+
89
+ watch(modelValue, val => {
90
+ if (!val) {
91
+ search.value = null
92
+ emit('close')
93
+ } else {
94
+ emit('open')
95
+ }
96
+ })
97
+ </script>
98
+ <template>
99
+ <v-dialog v-model="modelValue" min-width="350" content-class="f-dialog">
100
+ <!-- Header -->
101
+ <div class="f-dialog-header">
102
+ <!-- Title -->
103
+ <div class="f-dialog-header-title">{{ title }}</div>
104
+
105
+ <div class="d-flex align-center f-dialog-header-actions-wrapper">
106
+ <!-- Search -->
107
+ <FSearchPanel
108
+ v-if="isSearch"
109
+ v-model="search"
110
+ ctrl-f
111
+ outlined
112
+ color="fields-light"
113
+ class="f-dialog-header-action"
114
+ :tooltip-props="{ location: 'top', offset: 16 }"
115
+ />
116
+
117
+ <!-- Header actions -->
118
+ <div v-for="(btn, index) in headerActions" :key="index" class="d-flex f-dialog-header-actions">
119
+ <v-tooltip offset="10" :text="btn.label">
120
+ <template #activator="{ props: tooltipProps }">
121
+ <v-btn
122
+ v-bind="tooltipProps"
123
+ size="32"
124
+ variant="text"
125
+ class="f-dialog-header-action"
126
+ :disabled="Boolean(btn.disabled)"
127
+ @click="btn.action"
128
+ >
129
+ <FIcon :icon="btn.icon" size="18" color="fields-light" />
130
+ </v-btn>
131
+ </template>
132
+ </v-tooltip>
133
+ </div>
134
+
135
+ <!-- Close btn -->
136
+ <v-tooltip v-if="!disableCloseBtn" offset="10" :text="$t('buttons.close')">
137
+ <template #activator="{ props: tooltipProps }">
138
+ <v-btn
139
+ v-bind="tooltipProps"
140
+ variant="text"
141
+ size="32"
142
+ class="f-dialog-header-action f-dialog-header-action-close"
143
+ @click="modelValue = false"
144
+ >
145
+ <FIcon icon="times" size="18" color="fields-light" />
146
+ </v-btn>
147
+ </template>
148
+ </v-tooltip>
149
+ </div>
150
+ </div>
151
+
152
+ <!-- Body -->
153
+ <div class="f-dialog-body">
154
+ <!-- Back btn -->
155
+ <v-btn
156
+ v-if="typeof backAction === 'function'"
157
+ height="20"
158
+ variant="text"
159
+ color="secondary"
160
+ class="f-dialog-back-btn pa-0 font-weight-semibold align-self-start"
161
+ :class="noSpace ? 'ml-n1' : 'mt-5 ml-4'"
162
+ @click="
163
+ () => {
164
+ modelValue = false
165
+ backAction()
166
+ }
167
+ "
168
+ >
169
+ <FIcon icon="chevron-left" size="16" color="secondary" />
170
+ {{ $t('buttons.toBack') }}
171
+ </v-btn>
172
+
173
+ <!-- Default slot | browser scroll -->
174
+ <div class="f-dialog-content f-scrollbar-y" :class="{ 'mt-5 pl-5 mr-2 pr-3': !noSpace }">
175
+ <slot></slot>
176
+ </div>
177
+ </div>
178
+
179
+ <!-- Actions -->
180
+ <div v-if="actions || $slots.actions" class="f-dialog-actions d-flex justify-center flex-wrap my-10 mx-5">
181
+ <slot name="actions">
182
+ <v-btn
183
+ v-for="btn in actions"
184
+ :key="btn.name"
185
+ min-width="320"
186
+ class="f-dialog-action"
187
+ :variant="btn.outlined ? 'outlined' : 'elevated'"
188
+ @click="btn.action"
189
+ >
190
+ {{ btn.name }}
191
+ </v-btn>
192
+ </slot>
193
+ </div>
194
+ </v-dialog>
195
+ </template>
196
+
197
+ <style lang="scss" scoped>
198
+ :deep(.f-dialog) {
199
+ background: rgb(var(--v-theme-background));
200
+ box-shadow: 0px 4px 20px 0px rgb(var(--v-theme-title));
201
+ border-radius: 5.8px;
202
+ display: flex;
203
+ flex-direction: column;
204
+ max-height: 90vh; // Limit for the entire dialog
205
+ .f-dialog-header {
206
+ flex-shrink: 0; // Header does not shrink
207
+ display: flex;
208
+ justify-content: space-between;
209
+ background: rgb(var(--v-theme-title-dark));
210
+ border-top-left-radius: 5px;
211
+ border-top-right-radius: 5px;
212
+
213
+ .f-dialog-header-title {
214
+ font-weight: 600;
215
+ color: rgb(var(--v-theme-fields-light));
216
+ padding: 12px 0px 10px 20px;
217
+ }
218
+
219
+ .f-dialog-header-actions-wrapper {
220
+ display: flex;
221
+ align-items: center;
222
+ margin: 0 10px 0 20px;
223
+
224
+ .f-dialog-header-actions {
225
+ display: flex;
226
+ align-items: center;
227
+
228
+ .f-dialog-header-action {
229
+ margin-left: 8px;
230
+ }
231
+ }
232
+ .f-dialog-header-action {
233
+ .v-btn__overlay {
234
+ background-color: currentColor;
235
+ }
236
+ }
237
+ }
238
+ }
239
+ .f-dialog-body {
240
+ display: flex;
241
+ flex-direction: column;
242
+ flex: 1;
243
+ min-height: 0; // Important for flexbox to work with overflow
244
+ .f-dialog-content {
245
+ flex: 1;
246
+ overflow: auto;
247
+ }
248
+ }
249
+
250
+ .f-dialog-actions {
251
+ flex-shrink: 0; // Action section does not shrink
252
+ gap: 8px 24px;
253
+ }
254
+ }
255
+
256
+ .v-theme--dark {
257
+ :deep(.f-dialog) {
258
+ box-shadow: var(--f-box-shadow);
259
+ .f-dialog-header {
260
+ border-bottom: 1px solid rgb(var(--v-theme-line));
261
+ }
262
+ }
263
+ }
264
+ </style>
@@ -0,0 +1,47 @@
1
+ <script setup>
2
+ // FLangSwitcher
3
+ import { computed } from 'vue'
4
+ import { useI18n } from 'vue-i18n'
5
+
6
+ defineProps({
7
+ background: {
8
+ type: String,
9
+ default: 'transparent'
10
+ },
11
+ color: {
12
+ type: String,
13
+ default: 'text'
14
+ }
15
+ })
16
+
17
+ const emit = defineEmits(['change'])
18
+
19
+ const { locale } = useI18n()
20
+
21
+ const locales = [
22
+ { code: 'uk', name: 'Українська', flag: 'UA' },
23
+ { code: 'ru', name: 'Русский', flag: 'RU' },
24
+ { code: 'en', name: 'English', flag: 'EN' }
25
+ ]
26
+
27
+ const lang = computed(() => {
28
+ return locales.find(i => i.code === locale.value) || locales[0]
29
+ })
30
+ </script>
31
+
32
+ <template>
33
+ <v-menu offset="0, -6" location="bottom right" content-class="f-lang-switcher-content">
34
+ <template #activator="{ props }">
35
+ <v-btn v-bind="props" variant="text" height="32px" width="54px" class="pa-0 f-lang-switcher-btn" :class="`bg-${background}`" :color="color">
36
+ {{ lang.flag }}
37
+ <FIcon icon="chevron-down" :color="color" size="0.9em" />
38
+ </v-btn>
39
+ </template>
40
+
41
+ <v-list>
42
+ <v-list-item v-for="item in locales" :key="item.code" @click="emit('change', item.code)">
43
+ <v-list-item-title :class="item.code === lang.code ? 'text-primary' : `text-${color}`"> {{ item.flag }} ({{ item.name }}) </v-list-item-title>
44
+ </v-list-item>
45
+ </v-list>
46
+ </v-menu>
47
+ </template>
@@ -0,0 +1,80 @@
1
+ <script setup>
2
+ // FNoData
3
+ import { computed } from 'vue'
4
+ import { useTheme } from 'vuetify'
5
+ import { createRadialGradient } from './utils/color'
6
+ import { useI18n } from 'vue-i18n'
7
+
8
+ const { t } = useI18n()
9
+
10
+ const props = defineProps({
11
+ text: {
12
+ type: String,
13
+ default: null
14
+ },
15
+ isSearch: {
16
+ type: Boolean,
17
+ default: false
18
+ },
19
+ height: {
20
+ type: String,
21
+ default: '100%',
22
+ validator: value => {
23
+ return /^(calc\([^()]+\)|\d+(\.\d+)?(px|em|rem|vh|vw|%)|auto)$/.test(value)
24
+ }
25
+ },
26
+ width: {
27
+ type: String,
28
+ default: 'auto',
29
+ validator: value => {
30
+ return /^(calc\([^()]+\)|\d+(\.\d+)?(px|em|rem|vh|vw|%)|auto)$/.test(value)
31
+ }
32
+ }
33
+ })
34
+
35
+ const { current } = useTheme()
36
+ const gradient = computed(() => {
37
+ const primary = current.value.colors.primary
38
+ const spread = current.value.colors.disabled
39
+ return createRadialGradient(primary, spread)
40
+ })
41
+
42
+ const dataText = computed(() => {
43
+ if (props.text) {
44
+ return props.text
45
+ }
46
+ return props.isSearch ? t('noData.matches') : t('noData.title')
47
+ })
48
+ </script>
49
+
50
+ <template>
51
+ <div class="f-no-data" :style="`height: ${height}; width: ${width}`">
52
+ <div class="f-no-data-overlay" :style="`background: ${gradient}`">
53
+ <span class="f-no-data-text">{{ dataText }}</span>
54
+ </div>
55
+ </div>
56
+ </template>
57
+
58
+ <style lang="scss" scoped>
59
+ .f-no-data {
60
+ min-height: 150px;
61
+ position: relative;
62
+ .f-no-data-overlay {
63
+ position: absolute;
64
+ display: flex;
65
+ flex-direction: column;
66
+ align-items: center;
67
+ justify-content: center;
68
+ text-align: center;
69
+ height: 100%;
70
+ width: 100%;
71
+ color: rgb(var(--v-theme-primary));
72
+ top: calc(50% - 100% / 2);
73
+ left: calc(50% - 100% / 2);
74
+ font-size: 1em;
75
+ .f-no-data-text {
76
+ width: 250px;
77
+ }
78
+ }
79
+ }
80
+ </style>