@it-enterprise/forcebpm-ui-kit 1.0.2-beta.21 → 1.0.2-beta.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/index.js CHANGED
@@ -10,6 +10,21 @@ export { default as FNoData } from './src/FNoData.vue'
10
10
  export { default as FNotify } from './src/FNotify.vue'
11
11
  export { default as FShare } from './src/FShare.vue'
12
12
  export { default as FContextMenu } from './src/FContextMenu.vue'
13
+ export { default as FSearchPanel } from './src/FSearchPanel.vue'
14
+ export { default as FDialog } from './src/FDialog.vue'
15
+ export { default as FToolbar } from './src/f-toolbar/FToolbar.vue'
16
+ export { default as FTabPanel } from './src/f-toolbar/FTabPanel.vue'
17
+ export { default as FViewerPanel } from './src/f-toolbar/FViewerPanel.vue'
18
+ export { default as FFilterPanel } from './src/f-toolbar/FFilterPanel.vue'
19
+ export { default as FSortPanel } from './src/f-toolbar/FSortPanel.vue'
20
+ export { default as FTruncate } from './src/FTruncate.vue'
21
+ export { default as FUserRoles } from './src/FUserRoles.vue'
22
+ export { default as FDatePicker } from './src/f-date-picker/FDatePicker.vue'
23
+ export { default as FMenuDatePicker } from './src/f-date-picker/FMenuDatePicker.vue'
24
+ export { default as FTextFieldDate } from './src/f-date-picker/FTextFieldDate.vue'
25
+
26
+ // Icon components
27
+ export { FIcon, forcebpmIcon } from './src/forcebpmIcon.js'
13
28
 
14
29
  // Plugin and translations
15
30
  export { default as ForceBPMUiKit, defaultMessages } from './plugin.js'
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
+ "type": "module",
2
3
  "name": "@it-enterprise/forcebpm-ui-kit",
3
- "version": "1.0.2-beta.21",
4
+ "version": "1.0.2-beta.22",
4
5
  "description": "FBPM UI Kit",
5
6
  "author": "it-enterprise",
6
7
  "license": "MIT",
@@ -37,20 +38,42 @@
37
38
  ],
38
39
  "dependencies": {
39
40
  "dompurify": "^3.0.0",
41
+ "moment": "^2.30.1",
40
42
  "vue": "^3.0.0"
41
43
  },
42
44
  "peerDependencies": {
43
- "vue": "^3.0.0"
45
+ "@vueuse/core": "^14.1.0",
46
+ "vue": "^3.0.0",
47
+ "vue-i18n": "^11.2.8",
48
+ "vuetify": "^3.0.0"
44
49
  },
45
50
  "devDependencies": {
46
- "@vitejs/plugin-vue": "^5.0.0",
51
+ "@eslint/js": "^9.39.2",
52
+ "@vitejs/plugin-vue": "^6.0.3",
53
+ "@vitest/coverage-v8": "^4.0.18",
54
+ "@vitest/ui": "^4.0.18",
47
55
  "@vue/compiler-sfc": "^3.0.0",
56
+ "@vue/eslint-config-prettier": "^10.2.0",
57
+ "@vue/test-utils": "^2.4.6",
58
+ "eslint": "^9.39.2",
59
+ "eslint-config-prettier": "^10.1.8",
60
+ "eslint-plugin-vue": "^10.7.0",
61
+ "jsdom": "^27.4.0",
62
+ "prettier": "^3.8.1",
48
63
  "sass-embedded": "^1.97.2",
49
- "vite": "^5.0.0"
64
+ "vite": "^7.3.1",
65
+ "vitest": "^4.0.18"
50
66
  },
51
67
  "scripts": {
68
+ "lint": "eslint --ext .js,.vue src",
69
+ "lint:fix": "eslint --ext .js,.vue src --fix",
70
+ "format": "prettier --write \"src/**/*.{js,vue,json,scss}\"",
71
+ "preview": "vite preview",
52
72
  "build": "vite build",
53
- "test": "echo 'Test script placeholder'"
73
+ "test": "vitest",
74
+ "test:run": "vitest run",
75
+ "test:coverage": "vitest run --coverage",
76
+ "test:ui": "vitest --ui"
54
77
  },
55
78
  "repository": {
56
79
  "type": "git",
@@ -60,4 +83,4 @@
60
83
  "url": "https://gitlab.com/it-enterprise/npm-packages/forcebpm-ui-kit/-/issues"
61
84
  },
62
85
  "homepage": "https://gitlab.com/it-enterprise/npm-packages/forcebpm-ui-kit#readme"
63
- }
86
+ }
package/plugin.js CHANGED
@@ -11,32 +11,32 @@ export { defaultMessages }
11
11
 
12
12
  /**
13
13
  * ForceBPM UI Kit Plugin
14
- *
14
+ *
15
15
  * @example
16
16
  * // Basic usage with global registration
17
17
  * import { createApp } from 'vue'
18
18
  * import { ForceBPMUiKit } from '@it-enterprise/forcebpm-ui-kit'
19
- *
19
+ *
20
20
  * const app = createApp(App)
21
21
  * app.use(ForceBPMUiKit)
22
- *
22
+ *
23
23
  * @example
24
24
  * // With i18n integration
25
25
  * import { createApp } from 'vue'
26
26
  * import { createI18n } from 'vue-i18n'
27
27
  * import { ForceBPMUiKit } from '@it-enterprise/forcebpm-ui-kit'
28
- *
28
+ *
29
29
  * const i18n = createI18n({ locale: 'uk', messages: {} })
30
30
  * const app = createApp(App)
31
- *
31
+ *
32
32
  * app.use(i18n)
33
33
  * app.use(ForceBPMUiKit, { i18n })
34
- *
34
+ *
35
35
  * @example
36
36
  * // Without global registration (tree-shaking friendly)
37
37
  * import { createApp } from 'vue'
38
38
  * import { ForceBPMUiKit } from '@it-enterprise/forcebpm-ui-kit'
39
- *
39
+ *
40
40
  * const app = createApp(App)
41
41
  * app.use(ForceBPMUiKit, { registerGlobal: false })
42
42
  */
@@ -53,14 +53,7 @@ export const ForceBPMUiKit = {
53
53
  * @param {Object} [options.events={}] - Events map for modal windows
54
54
  */
55
55
  install(app, options = {}) {
56
- const {
57
- registerGlobal = true,
58
- i18n = null,
59
- prefix = 'F',
60
- messages = null,
61
- emitter = null,
62
- events = {}
63
- } = options
56
+ const { registerGlobal = true, i18n = null, prefix = 'F', messages = null, emitter = null, events = {} } = options
64
57
 
65
58
  // Provide emitter and events to components
66
59
  if (emitter) {
@@ -70,9 +63,7 @@ export const ForceBPMUiKit = {
70
63
 
71
64
  // Merge translations into i18n if provided
72
65
  if (i18n) {
73
- const messagesToMerge = messages
74
- ? mergeDeep(defaultMessages, messages)
75
- : defaultMessages
66
+ const messagesToMerge = messages ? mergeDeep(defaultMessages, messages) : defaultMessages
76
67
 
77
68
  Object.entries(messagesToMerge).forEach(([locale, localeMessages]) => {
78
69
  if (i18n.global) {
@@ -91,10 +82,8 @@ export const ForceBPMUiKit = {
91
82
  // Skip special exports
92
83
  if (name === 'default' || name === 'ForceBPMUiKit' || name === 'defaultMessages') return
93
84
  // Allow custom prefix (e.g., 'Fbpm' instead of 'F')
94
- const componentName = prefix === 'F'
95
- ? name
96
- : name.replace(/^F/, prefix)
97
-
85
+ const componentName = prefix === 'F' ? name : name.replace(/^F/, prefix)
86
+
98
87
  app.component(componentName, component)
99
88
  })
100
89
  }
@@ -112,7 +101,7 @@ export const ForceBPMUiKit = {
112
101
  */
113
102
  function mergeDeep(target, source) {
114
103
  const output = { ...target }
115
-
104
+
116
105
  for (const key in source) {
117
106
  if (source[key] instanceof Object && key in target) {
118
107
  output[key] = mergeDeep(target[key], source[key])
@@ -120,7 +109,7 @@ function mergeDeep(target, source) {
120
109
  output[key] = source[key]
121
110
  }
122
111
  }
123
-
112
+
124
113
  return output
125
114
  }
126
115
 
package/src/FAvatar.vue CHANGED
@@ -1,6 +1,6 @@
1
1
  <script setup>
2
2
  // FAvatar
3
- import { computed, defineEmits } from 'vue'
3
+ import { computed } from 'vue'
4
4
 
5
5
  const props = defineProps({
6
6
  activeUserId: {
@@ -9,7 +9,8 @@ const props = defineProps({
9
9
  },
10
10
  fullUser: {
11
11
  type: Object,
12
- default: null
12
+ default: null,
13
+ validator: value => value && 'userId' in value && 'name' in value
13
14
  },
14
15
  group: {
15
16
  type: Object,
@@ -37,12 +38,12 @@ const props = defineProps({
37
38
  const emit = defineEmits(['clearUserPhoto'])
38
39
 
39
40
  const getInitialsByName = name => {
40
- const splitter = name.split(' ')
41
- const result = splitter.reduce((sum, current) => {
42
- return sum + current.substr(0, 1)
43
- }, '')
44
- return result.slice(0, 2)
45
- }
41
+ const splitter = name.split(' ')
42
+ const result = splitter.reduce((sum, current) => {
43
+ return sum + current.substr(0, 1)
44
+ }, '')
45
+ return result.slice(0, 2)
46
+ }
46
47
 
47
48
  const userPhoto = computed(() => props.fullUser?.photo)
48
49
 
@@ -71,18 +72,10 @@ const errorHandler = () => {
71
72
  <template>
72
73
  <div class="f-avatar cursor-pointer" :style="sizeStyle">
73
74
  <!-- GROUP -->
74
- <v-tooltip
75
- v-if="group"
76
- :text="group.name"
77
- location="top right"
78
- :disabled="disabled"
79
- >
75
+ <v-tooltip v-if="group" :text="group.name" location="top right" :disabled="disabled">
80
76
  <template #activator="{ props: propsTooltip }">
81
77
  <div v-bind="propsTooltip" :style="sizeStyle" :class="avatarClass">
82
- <i
83
- class="f-icon group-star bg-secondary text-center"
84
- :style="sizeStyle"
85
- >
78
+ <i class="f-icon group-star bg-secondary text-center" :style="sizeStyle">
86
79
  <span class="text-subTitle" :style="fontStyle">
87
80
  {{ getInitialsByName(group.name) }}
88
81
  </span>
@@ -104,13 +97,7 @@ const errorHandler = () => {
104
97
  >
105
98
  <template #activator="{ props: propsMenu }">
106
99
  <!-- User -->
107
- <div
108
- v-if="fullUser"
109
- v-bind="propsMenu"
110
- class="f-avatar-user"
111
- :class="avatarClass"
112
- :style="sizeStyle"
113
- >
100
+ <div v-if="fullUser" v-bind="propsMenu" class="f-avatar-user" :class="avatarClass" :style="sizeStyle">
114
101
  <!-- Image -->
115
102
  <v-img
116
103
  v-if="userPhoto"
@@ -124,17 +111,8 @@ const errorHandler = () => {
124
111
  @error="errorHandler"
125
112
  >
126
113
  <template #placeholder>
127
- <v-skeleton-loader
128
- v-if="!fullUser?.name"
129
- :height="size"
130
- :width="size"
131
- type="avatar"
132
- />
133
- <span
134
- v-else
135
- class="f-avatar-initials text-subTitle d-flex align-center justify-center"
136
- :style="[sizeStyle, fontStyle]"
137
- >
114
+ <v-skeleton-loader v-if="!fullUser?.name" :height="size" :width="size" type="avatar" />
115
+ <span v-else class="f-avatar-initials text-subTitle d-flex align-center justify-center" :style="[sizeStyle, fontStyle]">
138
116
  {{ getInitialsByName(fullUser.name) }}
139
117
  </span>
140
118
  </template>
@@ -142,12 +120,7 @@ const errorHandler = () => {
142
120
 
143
121
  <!-- No avatar -->
144
122
  <template v-else>
145
- <v-skeleton-loader
146
- v-if="!fullUser?.name"
147
- :height="size"
148
- :width="size"
149
- type="avatar"
150
- />
123
+ <v-skeleton-loader v-if="!fullUser?.name" :height="size" :width="size" type="avatar" />
151
124
  <span
152
125
  v-else
153
126
  :style="[sizeStyle, fontStyle]"
@@ -161,18 +134,8 @@ const errorHandler = () => {
161
134
  </template>
162
135
 
163
136
  <!-- User info -->
164
- <div
165
- v-if="fullUser"
166
- class="d-flex flex-column align-center f-avatar-user-info"
167
- >
168
- <v-img
169
- cover
170
- height="120px"
171
- width="120px"
172
- :src="userPhoto"
173
- :class="{ 'f-avatar-me': activeUserId === fullUser.userId }"
174
- class="f-avatar-img"
175
- >
137
+ <div v-if="fullUser" class="d-flex flex-column align-center f-avatar-user-info">
138
+ <v-img cover height="120px" width="120px" :src="userPhoto" :class="{ 'f-avatar-me': activeUserId === fullUser.userId }" class="f-avatar-img">
176
139
  <template #placeholder>
177
140
  <FIcon icon="user-line" color="disabled-light" size="55" />
178
141
  </template>
@@ -180,42 +143,36 @@ const errorHandler = () => {
180
143
 
181
144
  <div class="selectable-text mt-5">
182
145
  <!-- Name -->
183
- <div
184
- class="text-title text-center font-weight-semibold f-avatar-user-info-name"
185
- style="word-break: break-all"
186
- >
187
- {{ fullUser.name || "No Name" }}
146
+ <div class="text-title text-center font-weight-semibold f-avatar-user-info-name" style="word-break: break-all">
147
+ {{ fullUser.name || 'No Name' }}
188
148
  </div>
189
149
 
190
150
  <v-table class="f-avatar-user-info-table mt-5">
191
151
  <tbody>
192
152
  <!-- Phone -->
193
153
  <tr>
194
- <td class="text-text fs-09">{{ $t("user.telephone") }}</td>
195
- <td class="text-subTitle">{{ fullUser.phone || "-" }}</td>
154
+ <td class="text-text fs-09">{{ $t('user.telephone') }}</td>
155
+ <td class="text-subTitle">{{ fullUser.phone || '-' }}</td>
196
156
  </tr>
197
157
  <!-- E-mail -->
198
158
  <tr>
199
159
  <td class="text-text fs-09">E-mail</td>
200
- <td
201
- class="text-subTitle text-lowercase"
202
- style="word-break: break-all"
203
- >
204
- {{ fullUser.email || "-" }}
160
+ <td class="text-subTitle text-lowercase" style="word-break: break-all">
161
+ {{ fullUser.email || '-' }}
205
162
  </td>
206
163
  </tr>
207
164
  <!-- Position -->
208
165
  <tr>
209
- <td class="text-text fs-09">{{ $t("user.position") }}</td>
166
+ <td class="text-text fs-09">{{ $t('user.position') }}</td>
210
167
  <td class="text-subTitle">
211
- {{ fullUser.positionTitle || fullUser.positionCode || "-" }}
168
+ {{ fullUser.positionTitle || fullUser.positionCode || '-' }}
212
169
  </td>
213
170
  </tr>
214
171
  <!-- Department -->
215
172
  <tr>
216
- <td class="text-text fs-09">{{ $t("user.department") }}</td>
173
+ <td class="text-text fs-09">{{ $t('user.department') }}</td>
217
174
  <td class="text-subTitle">
218
- {{ fullUser.department || "-" }}
175
+ {{ fullUser.department || '-' }}
219
176
  </td>
220
177
  </tr>
221
178
  </tbody>
@@ -50,14 +50,7 @@ onBeforeUnmount(() => {
50
50
  })
51
51
  </script>
52
52
  <template>
53
- <v-dialog
54
- v-model="show"
55
- content-class="f-confirm-modal"
56
- max-width="500"
57
- persistent
58
- @keydown.esc="declineHandler"
59
- @keydown.enter="approveHandler"
60
- >
53
+ <v-dialog v-model="show" content-class="f-confirm-modal" max-width="500" persistent @keydown.esc="declineHandler" @keydown.enter="approveHandler">
61
54
  <!-- Header -->
62
55
  <div class="f-confirm-modal-header">{{ title }}</div>
63
56
 
@@ -66,20 +59,11 @@ onBeforeUnmount(() => {
66
59
  <div class="f-confirm-modal-content mt-5 mx-5 mb-10">{{ subTitle }}</div>
67
60
 
68
61
  <!-- Actions -->
69
- <v-btn
70
- class="f-confirm-modal-btn"
71
- variant="outlined"
72
- :disabled="loading"
73
- @click="declineHandler"
74
- >
75
- {{ $t("buttons.no") }}
62
+ <v-btn class="f-confirm-modal-btn" variant="outlined" :disabled="loading" @click="declineHandler">
63
+ {{ $t('buttons.no') }}
76
64
  </v-btn>
77
- <v-btn
78
- class="f-confirm-modal-btn mt-2 mb-10"
79
- :loading="loading"
80
- @click="approveHandler"
81
- >
82
- {{ $t("buttons.yes") }}
65
+ <v-btn class="f-confirm-modal-btn mt-2 mb-10" :loading="loading" @click="approveHandler">
66
+ {{ $t('buttons.yes') }}
83
67
  </v-btn>
84
68
  </div>
85
69
  </v-dialog>
@@ -46,18 +46,9 @@ const isEmpty = computed(() => !props.list.length || props.list.every(el => el.h
46
46
  </script>
47
47
 
48
48
  <template>
49
- <v-menu
50
- v-if="!isEmpty"
51
- v-model="menu"
52
- v-bind="menuProps"
53
- content-class="f-context-menu-content"
54
- >
49
+ <v-menu v-if="!isEmpty" v-model="menu" v-bind="menuProps" content-class="f-context-menu-content">
55
50
  <template #activator="{ props: propsMenu }">
56
- <v-tooltip
57
- :disabled="menu || disabled"
58
- :text="$t('tooltip.actions')"
59
- v-bind="tooltipProps"
60
- >
51
+ <v-tooltip :disabled="menu || disabled" :text="$t('tooltip.actions')" v-bind="tooltipProps">
61
52
  <template #activator="{ props: propsTooltip }">
62
53
  <v-btn
63
54
  size="25"
@@ -67,11 +58,7 @@ const isEmpty = computed(() => !props.list.length || props.list.every(el => el.h
67
58
  :disabled="disabled"
68
59
  v-bind="mergeProps(propsMenu, propsTooltip)"
69
60
  >
70
- <FIcon
71
- icon="dots"
72
- size="18"
73
- :color="disabled ? 'disabled' : 'secondary'"
74
- />
61
+ <FIcon icon="dots" size="18" :color="disabled ? 'disabled' : 'secondary'" />
75
62
  </v-btn>
76
63
  </template>
77
64
  </v-tooltip>
@@ -81,31 +68,18 @@ const isEmpty = computed(() => !props.list.length || props.list.every(el => el.h
81
68
  <template v-for="(item, idx) in list">
82
69
  <template v-if="!item.hide">
83
70
  <!-- Divider top -->
84
- <v-divider
85
- v-if="item.dividerTop"
86
- :key="`divider-top-${idx}`"
87
- class="bg-disabled my-3"
88
- />
71
+ <v-divider v-if="item.dividerTop" :key="`divider-top-${idx}`" class="bg-disabled my-3" />
89
72
 
90
73
  <!-- List item -->
91
74
  <v-list-item :key="idx" @click="item.action">
92
75
  <v-list-item-title :class="`text-${item.color || 'subTitle'}`">
93
- <FIcon
94
- v-if="item.icon"
95
- :icon="item.icon"
96
- :color="item.color || 'text'"
97
- size="18"
98
- />
76
+ <FIcon v-if="item.icon" :icon="item.icon" :color="item.color || 'text'" size="18" />
99
77
  <span :class="item.icon ? 'ml-1' : 'ml-5'">{{ item.name }}</span>
100
78
  </v-list-item-title>
101
79
  </v-list-item>
102
80
 
103
81
  <!-- Divider bottom -->
104
- <v-divider
105
- v-if="item.dividerBottom"
106
- :key="`divider-bottom-${idx}`"
107
- class="bg-disabled my-3"
108
- />
82
+ <v-divider v-if="item.dividerBottom" :key="`divider-bottom-${idx}`" class="bg-disabled my-3" />
109
83
  </template>
110
84
  </template>
111
85
  </v-list>
@@ -113,35 +87,37 @@ const isEmpty = computed(() => !props.list.length || props.list.every(el => el.h
113
87
  </template>
114
88
 
115
89
  <style lang="scss" scoped>
116
- .f-context-menu-content {
117
- .v-list {
118
- padding: 12px 6px;
119
- overflow-x: hidden;
120
- .v-list-item {
121
- padding: 4px 6px;
122
- min-height: auto;
123
- :deep(.v-list-item__overlay) {
124
- border-radius: 5.8px;
125
- background: rgb(var(--v-theme-primary));
126
- }
127
- &:hover {
90
+ .f-menu {
91
+ .f-context-menu-content {
92
+ .v-list {
93
+ padding: 12px 6px;
94
+ overflow-x: hidden;
95
+ .v-list-item {
96
+ padding: 4px 6px;
97
+ min-height: auto;
128
98
  :deep(.v-list-item__overlay) {
129
- opacity: 0.15;
99
+ border-radius: 5.8px;
100
+ background: rgb(var(--v-theme-primary));
130
101
  }
131
- }
132
- .v-list-item__content {
133
- .v-list-item-title {
134
- display: flex;
135
- align-items: center;
136
- font-weight: 600;
137
- font-size: 1em;
102
+ &:hover {
103
+ :deep(.v-list-item__overlay) {
104
+ opacity: 0.15;
105
+ }
106
+ }
107
+ .v-list-item__content {
108
+ .v-list-item-title {
109
+ display: flex;
110
+ align-items: center;
111
+ font-weight: 600;
112
+ font-size: 1em;
113
+ }
138
114
  }
139
115
  }
140
- }
141
- .v-divider {
142
- position: relative;
143
- min-width: calc(100% + 12px);
144
- left: -6px;
116
+ .v-divider {
117
+ position: relative;
118
+ min-width: calc(100% + 12px);
119
+ left: -6px;
120
+ }
145
121
  }
146
122
  }
147
123
  }