@vc-shell/framework 1.0.231 → 1.0.233

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 (88) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/core/types/index.ts +14 -1
  3. package/dist/core/types/index.d.ts +12 -2
  4. package/dist/core/types/index.d.ts.map +1 -1
  5. package/dist/framework.js +14875 -14580
  6. package/dist/index.css +1 -1
  7. package/dist/locales/en.json +8 -2
  8. package/dist/shared/components/notifications/components/notification-container/index.d.ts +1 -1
  9. package/dist/shared/components/user-dropdown-button/user-dropdown-button.vue.d.ts.map +1 -1
  10. package/dist/shared/modules/dynamic/components/SchemaRender.d.ts.map +1 -1
  11. package/dist/shared/modules/dynamic/components/fields/GalleryField.d.ts.map +1 -1
  12. package/dist/shared/modules/dynamic/components/fields/StatusField.d.ts.map +1 -1
  13. package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts.map +1 -1
  14. package/dist/shared/modules/dynamic/types/index.d.ts +10 -5
  15. package/dist/shared/modules/dynamic/types/index.d.ts.map +1 -1
  16. package/dist/tsconfig.tsbuildinfo +1 -1
  17. package/dist/ui/components/atoms/vc-badge/vc-badge.stories.d.ts +187 -17
  18. package/dist/ui/components/atoms/vc-badge/vc-badge.stories.d.ts.map +1 -1
  19. package/dist/ui/components/atoms/vc-badge/vc-badge.vue.d.ts +19 -3
  20. package/dist/ui/components/atoms/vc-badge/vc-badge.vue.d.ts.map +1 -1
  21. package/dist/ui/components/atoms/vc-button/vc-button.stories.d.ts +91 -91
  22. package/dist/ui/components/atoms/vc-button/vc-button.vue.d.ts +1 -1
  23. package/dist/ui/components/atoms/vc-icon/vc-icon.stories.d.ts +9 -9
  24. package/dist/ui/components/atoms/vc-icon/vc-icon.vue.d.ts +1 -1
  25. package/dist/ui/components/atoms/vc-image/index.d.ts +12 -3
  26. package/dist/ui/components/atoms/vc-image/index.d.ts.map +1 -1
  27. package/dist/ui/components/atoms/vc-image/vc-image.stories.d.ts +12 -3
  28. package/dist/ui/components/atoms/vc-image/vc-image.stories.d.ts.map +1 -1
  29. package/dist/ui/components/atoms/vc-image/vc-image.vue.d.ts +5 -1
  30. package/dist/ui/components/atoms/vc-image/vc-image.vue.d.ts.map +1 -1
  31. package/dist/ui/components/atoms/vc-status/vc-status.stories.d.ts +7 -7
  32. package/dist/ui/components/atoms/vc-status/vc-status.vue.d.ts +2 -2
  33. package/dist/ui/components/atoms/vc-status/vc-status.vue.d.ts.map +1 -1
  34. package/dist/ui/components/atoms/vc-widget/index.d.ts +6 -0
  35. package/dist/ui/components/atoms/vc-widget/index.d.ts.map +1 -1
  36. package/dist/ui/components/atoms/vc-widget/vc-widget.stories.d.ts +6 -0
  37. package/dist/ui/components/atoms/vc-widget/vc-widget.stories.d.ts.map +1 -1
  38. package/dist/ui/components/atoms/vc-widget/vc-widget.vue.d.ts +1 -0
  39. package/dist/ui/components/atoms/vc-widget/vc-widget.vue.d.ts.map +1 -1
  40. package/dist/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.stories.d.ts +3 -3
  41. package/dist/ui/components/molecules/vc-breadcrumbs/vc-breadcrumbs.vue.d.ts +1 -1
  42. package/dist/ui/components/molecules/vc-editor/vc-editor.vue.d.ts.map +1 -1
  43. package/dist/ui/components/molecules/vc-input/vc-input.vue.d.ts.map +1 -1
  44. package/dist/ui/components/molecules/vc-input-currency/vc-input-currency.vue.d.ts.map +1 -1
  45. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue.d.ts +1 -0
  46. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue.d.ts.map +1 -1
  47. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue.d.ts +1 -0
  48. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue.d.ts.map +1 -1
  49. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue.d.ts.map +1 -1
  50. package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
  51. package/dist/ui/components/organisms/vc-blade/_internal/vc-blade-toolbar/_internal/vc-blade-toolbar-button/vc-blade-toolbar-button.vue.d.ts +1 -1
  52. package/dist/ui/components/organisms/vc-blade/vc-blade.stories.d.ts +2 -0
  53. package/dist/ui/components/organisms/vc-blade/vc-blade.stories.d.ts.map +1 -1
  54. package/dist/ui/components/organisms/vc-blade/vc-blade.vue.d.ts +2 -0
  55. package/dist/ui/components/organisms/vc-blade/vc-blade.vue.d.ts.map +1 -1
  56. package/dist/ui/components/organisms/vc-table/_internal/vc-table-cell/vc-table-cell.vue.d.ts.map +1 -1
  57. package/dist/ui/components/organisms/vc-table/_internal/vc-table-mobile-item/vc-table-mobile-item.vue.d.ts +3 -0
  58. package/dist/ui/components/organisms/vc-table/_internal/vc-table-mobile-item/vc-table-mobile-item.vue.d.ts.map +1 -1
  59. package/dist/ui/components/organisms/vc-table/vc-table.vue.d.ts.map +1 -1
  60. package/package.json +4 -4
  61. package/shared/components/app-switcher/components/vc-app-switcher/vc-app-switcher.vue +1 -1
  62. package/shared/components/user-dropdown-button/user-dropdown-button.vue +11 -3
  63. package/shared/modules/dynamic/components/SchemaRender.ts +0 -2
  64. package/shared/modules/dynamic/components/fields/Card.ts +1 -1
  65. package/shared/modules/dynamic/components/fields/GalleryField.ts +1 -0
  66. package/shared/modules/dynamic/components/fields/StatusField.ts +39 -3
  67. package/shared/modules/dynamic/pages/dynamic-blade-form.vue +17 -22
  68. package/shared/modules/dynamic/types/index.ts +15 -7
  69. package/ui/components/atoms/vc-badge/vc-badge.stories.ts +3 -3
  70. package/ui/components/atoms/vc-badge/vc-badge.vue +58 -10
  71. package/ui/components/atoms/vc-container/vc-container.vue +2 -2
  72. package/ui/components/atoms/vc-image/vc-image.vue +3 -1
  73. package/ui/components/atoms/vc-status/vc-status.vue +5 -2
  74. package/ui/components/atoms/vc-tooltip/vc-tooltip.vue +1 -1
  75. package/ui/components/atoms/vc-widget/vc-widget.vue +42 -22
  76. package/ui/components/molecules/vc-editor/vc-editor.vue +5 -1
  77. package/ui/components/molecules/vc-input/vc-input.vue +17 -2
  78. package/ui/components/molecules/vc-input-currency/vc-input-currency.vue +20 -0
  79. package/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue +1 -1
  80. package/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/_internal/vc-app-menu-link.vue +21 -10
  81. package/ui/components/organisms/vc-app/_internal/vc-app-menu/_internal/vc-app-menu-item/vc-app-menu-item.vue +3 -0
  82. package/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue +59 -9
  83. package/ui/components/organisms/vc-app/vc-app.vue +0 -1
  84. package/ui/components/organisms/vc-blade/vc-blade.vue +89 -2
  85. package/ui/components/organisms/vc-table/_internal/vc-table-cell/vc-table-cell.vue +72 -56
  86. package/ui/components/organisms/vc-table/_internal/vc-table-mobile-item/vc-table-mobile-item.vue +27 -7
  87. package/ui/components/organisms/vc-table/vc-table.stories.ts +1 -1
  88. package/ui/components/organisms/vc-table/vc-table.vue +138 -62
@@ -58,6 +58,8 @@
58
58
  :disabled="disabled"
59
59
  :placeholder="holder"
60
60
  @blur="handleBlur"
61
+ @keydown="handleKeyDown"
62
+ @paste="handlePaste"
61
63
  />
62
64
  </template>
63
65
  <template
@@ -242,6 +244,8 @@ const { inputRef, setOptions, numberValue, setValue } = useCurrencyInput(
242
244
  currencyDisplay: props.currencyDisplay as CurrencyDisplay,
243
245
  hideGroupingSeparatorOnFocus: false,
244
246
  precision: props.precision,
247
+ hideCurrencySymbolOnFocus: false,
248
+ hideNegligibleDecimalDigitsOnFocus: false,
245
249
  },
246
250
  false,
247
251
  );
@@ -256,6 +260,9 @@ watch(
256
260
  currency: newVal,
257
261
  currencyDisplay: props.currencyDisplay as CurrencyDisplay,
258
262
  hideGroupingSeparatorOnFocus: false,
263
+ precision: props.precision,
264
+ hideCurrencySymbolOnFocus: false,
265
+ hideNegligibleDecimalDigitsOnFocus: false,
259
266
  });
260
267
  },
261
268
  );
@@ -280,4 +287,17 @@ function updateModel(value: string | number | Date | null | undefined) {
280
287
  function handleBlur(event: Event) {
281
288
  emit("blur", event);
282
289
  }
290
+
291
+ function handleKeyDown(e: KeyboardEvent) {
292
+ if (e.key === "-" || e.key === "e") {
293
+ e.preventDefault();
294
+ }
295
+ }
296
+
297
+ function handlePaste(e: ClipboardEvent) {
298
+ const pasteData = e.clipboardData?.getData("text/plain");
299
+ if (typeof pasteData !== "undefined" && parseInt(pasteData) < 0) {
300
+ e.preventDefault();
301
+ }
302
+ }
283
303
  </script>
@@ -8,7 +8,7 @@
8
8
  <template v-if="!$isMobile.value || quantity === 0">
9
9
  <!-- Logo -->
10
10
  <img
11
- class="tw-h-1/2 tw-cursor-pointer tw-mx-3"
11
+ class="tw-h-1/2 tw-cursor-pointer tw-mx-4"
12
12
  alt="logo"
13
13
  :src="logo"
14
14
  @click="$emit('logo:click')"
@@ -3,9 +3,9 @@
3
3
  class="vc-app-menu-item"
4
4
  :class="[
5
5
  {
6
- 'vc-app-menu-item_active': isActive(url ?? '') && !children?.length,
6
+ 'vc-app-menu-item_active': isMenuItemActive,
7
7
  'vc-app-menu-item_no-hover': !children?.length,
8
- 'vc-app-menu-item_child-opened': isOpened,
8
+ 'vc-app-menu-item_child-opened': expand && isOpened,
9
9
  },
10
10
  ]"
11
11
  @click="onMenuItemClick"
@@ -28,7 +28,10 @@
28
28
  size="m"
29
29
  />
30
30
  </div>
31
- <div class="vc-app-menu-item__title tw-capitalize">
31
+ <div
32
+ v-if="expand"
33
+ class="vc-app-menu-item__title tw-capitalize"
34
+ >
32
35
  {{ title }}
33
36
  <VcIcon
34
37
  v-if="!!children?.length || false"
@@ -40,7 +43,7 @@
40
43
  </div>
41
44
  <!-- Nested menu items -->
42
45
  <div
43
- v-show="isOpened"
46
+ v-show="isOpened && expand"
44
47
  class="vc-app-menu-item__child"
45
48
  >
46
49
  <template
@@ -69,7 +72,7 @@
69
72
  </div>
70
73
  </template>
71
74
  <script lang="ts" setup>
72
- import { ref, watch } from "vue";
75
+ import { ref, watch, computed } from "vue";
73
76
  import { MenuItem } from "../../../../../../../../../core/types";
74
77
  import { VcIcon } from "./../../../../../../../";
75
78
  import { useRoute } from "vue-router";
@@ -80,6 +83,7 @@ export interface Props {
80
83
  icon: string;
81
84
  title?: string;
82
85
  url?: string;
86
+ expand?: boolean;
83
87
  }
84
88
 
85
89
  export interface Emits {
@@ -96,6 +100,12 @@ const isOpened = ref(false);
96
100
  const route = useRoute();
97
101
  const params = Object.fromEntries(Object.entries(route.params).filter(([key]) => key !== "pathMatch"));
98
102
 
103
+ const isMenuItemActive = computed(
104
+ () =>
105
+ (isActive(props.url ?? "") && !props.children?.length) ||
106
+ (!props.expand && isOpened.value && props.children?.some((x) => isActive(x.url ?? ""))),
107
+ );
108
+
99
109
  watch(
100
110
  () => route.path,
101
111
  () => {
@@ -128,7 +138,9 @@ const isActive = (url: string) => {
128
138
  }
129
139
 
130
140
  return active;
131
- } else return false;
141
+ } else {
142
+ return false;
143
+ }
132
144
  };
133
145
  </script>
134
146
 
@@ -182,10 +194,9 @@ const isActive = (url: string) => {
182
194
  }
183
195
 
184
196
  &__icon {
185
- @apply tw-w-[var(--app-menu-item-icon-width)]
186
- tw-text-[color:var(--app-menu-item-icon-color)]
197
+ @apply tw-text-[color:var(--app-menu-item-icon-color)]
187
198
  tw-overflow-hidden tw-flex
188
- tw-justify-center tw-shrink-0 tw-transition-[color] tw-duration-200;
199
+ tw-justify-center tw-shrink-0 tw-transition-[color] tw-duration-200 tw-pr-[7px];
189
200
  }
190
201
 
191
202
  &_active &__icon {
@@ -196,7 +207,7 @@ const isActive = (url: string) => {
196
207
  @apply tw-truncate
197
208
  tw-text-lg
198
209
  tw-font-medium
199
- tw-px-2
210
+ tw-pr-2
200
211
  tw-text-[color:var(--app-menu-item-title-color)]
201
212
  [transition:color_0.2s_ease]
202
213
  tw-opacity-100 tw-w-full tw-flex tw-justify-between tw-items-center;
@@ -11,6 +11,7 @@
11
11
  :icon="icon ?? ''"
12
12
  :title="title ?? ''"
13
13
  :url="url"
14
+ :expand="expand"
14
15
  @on-click="$emit('click')"
15
16
  />
16
17
  </router-link>
@@ -22,6 +23,7 @@
22
23
  :sticky="sticky"
23
24
  :icon="icon ?? ''"
24
25
  :title="title ?? ''"
26
+ :expand="expand"
25
27
  @on-click="$emit('click', $event)"
26
28
  />
27
29
  </template>
@@ -39,6 +41,7 @@ export interface Props {
39
41
  icon?: string;
40
42
  title?: string;
41
43
  children?: MenuItem[];
44
+ expand?: boolean;
42
45
  }
43
46
 
44
47
  export interface Emits {
@@ -1,12 +1,16 @@
1
1
  <template>
2
2
  <div
3
3
  v-if="isMenuVisible"
4
- class="tw-relative tw-w-[var(--app-menu-width)] tw-transition tw-duration-100 tw-pt-[22px]"
4
+ class="tw-relative tw-w-[var(--app-menu-width)] [transition:width_300ms_cubic-bezier(0.2,0,0,1)_0s] tw-pt-[22px]"
5
5
  :class="{
6
6
  'vc-app-menu_mobile tw-hidden !tw-fixed !tw-left-0 !tw-top-0 !tw-w-full !tw-bottom-0 !tw-z-[9999]':
7
7
  $isMobile.value,
8
8
  '!tw-block': isMobileVisible,
9
+ '!tw-w-[63px]': $isDesktop.value && !isExpanded,
9
10
  }"
11
+ @mouseenter="!isExpanded && expandOverHandler(true)"
12
+ @mouseover="!isExpanded && expandOverHandler(true)"
13
+ @mouseleave="expandOverHandler(false)"
10
14
  >
11
15
  <!-- Show backdrop overlay on mobile devices -->
12
16
  <div
@@ -14,17 +18,40 @@
14
18
  class="tw-absolute tw-left-0 tw-top-0 tw-right-0 tw-bottom-0 tw-z-[9998] tw-bg-[#808c99] tw-opacity-60"
15
19
  @click="isMobileVisible = false"
16
20
  ></div>
17
- <div class="vc-app-menu__inner tw-flex tw-flex-col tw-h-full">
21
+ <div
22
+ class="!tw-absolute vc-app-menu__inner tw-flex tw-flex-col tw-h-full [transition:width_300ms_cubic-bezier(0.2,0,0,1)_0s] tw-z-[9999] tw-left-0 tw-top-0 tw-bottom-0 tw-bg-[color:var(--app-background)] tw-pt-[22px] tw-shadow-[inset_0px_2px_5px_0px_#3654751A]"
23
+ :class="{
24
+ '!tw-w-[63px] !tw-shadow-[inset_4px_2px_5px_0px_#3654751A]': $isDesktop.value && !isExpanded && !isExpandedOver,
25
+ 'tw-w-[var(--app-menu-width)]': $isDesktop.value && (isExpanded || isExpandedOver),
26
+ 'tw-shadow-none': $isDesktop.value && isExpanded,
27
+ }"
28
+ >
18
29
  <!-- Show menu close handler on mobile devices -->
19
30
  <div
20
31
  v-if="$isMobile.value"
21
32
  class="tw-text-[#319ed4] tw-flex tw-justify-end tw-items-center tw-p-4 tw-cursor-pointer"
22
33
  >
23
- <VcIcon
24
- icon="fas fa-times"
25
- size="xl"
26
- @click="isMobileVisible = false"
27
- ></VcIcon>
34
+ <button @click="isMobileVisible = false">
35
+ <VcIcon
36
+ icon="fas fa-times"
37
+ size="xl"
38
+ ></VcIcon>
39
+ </button>
40
+ </div>
41
+
42
+ <div
43
+ v-if="$isDesktop.value"
44
+ class="tw-pl-[21px] tw-pb-2"
45
+ >
46
+ <button
47
+ class="tw-p-[10px] tw-h-[var(--app-menu-item-height)] tw-rounded tw-text-[color:var(--app-menu-burger-color)] hover:tw-bg-[color:var(--app-menu-burger-background-color)]"
48
+ @click="toggleMenu"
49
+ >
50
+ <VcIcon
51
+ icon="fas fa-bars"
52
+ size="xl"
53
+ ></VcIcon>
54
+ </button>
28
55
  </div>
29
56
 
30
57
  <!-- Show scrollable area with menu items -->
@@ -32,7 +59,13 @@
32
59
  :no-padding="true"
33
60
  class="tw-grow tw-basis-0"
34
61
  >
35
- <div class="tw-gap-[5px] tw-flex tw-flex-col tw-px-6 tw-h-full">
62
+ <div
63
+ class="tw-gap-[5px] tw-flex tw-flex-col tw-h-full"
64
+ :class="{
65
+ 'tw-px-[21px]': ($isDesktop.value && (isExpanded || isExpandedOver)) || $isMobile.value,
66
+ 'tw-pl-[21px] tw-pr-[2px]': $isDesktop.value && !isExpanded && !isExpandedOver,
67
+ }"
68
+ >
36
69
  <slot
37
70
  v-if="!$isDesktop.value"
38
71
  name="mobile"
@@ -48,6 +81,7 @@
48
81
  :icon="item.icon"
49
82
  :title="item.title as string"
50
83
  :children="item.children"
84
+ :expand="$isDesktop.value ? isExpanded || isExpandedOver : true"
51
85
  @click="
52
86
  (event) => {
53
87
  $emit('item:click', event ? event : item);
@@ -58,7 +92,7 @@
58
92
  </div>
59
93
  </VcContainer>
60
94
  <div
61
- class="tw-text-[color:var(--app-menu-version-color)] tw-text-xs tw-mt-auto tw-self-center tw-p-1"
95
+ class="tw-text-[color:var(--app-menu-version-color)] tw-text-xs tw-mt-auto tw-self-start tw-py-1 tw-pl-4"
62
96
  @click="$emit('version:click')"
63
97
  >
64
98
  {{ version }}
@@ -73,6 +107,7 @@ import VcAppMenuItem from "./_internal/vc-app-menu-item/vc-app-menu-item.vue";
73
107
  import { VcContainer, VcIcon } from "./../../../../";
74
108
  import { useMenuService } from "../../../../../../core/composables";
75
109
  import { MenuItem } from "../../../../../../core/types";
110
+ import { useLocalStorage } from "@vueuse/core";
76
111
 
77
112
  export interface Props {
78
113
  version: string;
@@ -91,6 +126,8 @@ withDefaults(defineProps<Props>(), {
91
126
 
92
127
  defineEmits<Emits>();
93
128
  const { menuItems } = useMenuService();
129
+ const isExpanded = useLocalStorage("VC_APP_MENU_EXPANDED", true);
130
+ const isExpandedOver = ref(false);
94
131
 
95
132
  const isMobileVisible = ref(false);
96
133
 
@@ -98,6 +135,16 @@ const isMenuVisible = computed(() => {
98
135
  return !!menuItems.value.length;
99
136
  });
100
137
 
138
+ function toggleMenu() {
139
+ isExpanded.value = !isExpanded.value;
140
+ }
141
+
142
+ function expandOverHandler(state: boolean) {
143
+ if (isExpandedOver.value !== state) {
144
+ isExpandedOver.value = state;
145
+ }
146
+ }
147
+
101
148
  defineExpose({
102
149
  isMobileVisible,
103
150
  });
@@ -108,6 +155,9 @@ defineExpose({
108
155
  --app-menu-width: 230px;
109
156
  --app-menu-background-color: #ffffff;
110
157
  --app-menu-version-color: #838d9a;
158
+
159
+ --app-menu-burger-background-color: rgba(255, 255, 255, 0.5);
160
+ --app-menu-burger-color: #319ed4;
111
161
  }
112
162
 
113
163
  .vc-app-menu {
@@ -10,7 +10,6 @@
10
10
  :class="[
11
11
  {
12
12
  'vc-app_touch': $isTouch,
13
- 'vc-app_phone': $isPhone.value,
14
13
  'vc-app_mobile': $isMobile.value,
15
14
  },
16
15
  ]"
@@ -70,12 +70,64 @@
70
70
  class="tw-shrink-0"
71
71
  :items="toolbarItems"
72
72
  ></VcBladeToolbar>
73
- <slot></slot>
73
+
74
+ <div class="tw-flex-1 tw-overflow-auto">
75
+ <div
76
+ class="tw-flex tw-flex-row tw-h-full"
77
+ :class="{
78
+ 'tw-flex-col': $isMobile.value,
79
+ }"
80
+ >
81
+ <slot></slot>
82
+
83
+ <div
84
+ v-show="$slots['widgets'] && !isWidgetContainerEmpty"
85
+ ref="widgetsRef"
86
+ class="vc-blade__widgets tw-flex"
87
+ :class="{
88
+ 'tw-w-[var(--blade-widgets-width)] tw-flex-col': $isDesktop.value && !isExpanded,
89
+ 'tw-w-[var(--blade-widgets-width-expanded)] tw-flex-col': $isDesktop.value && isExpanded,
90
+ 'tw-w-auto tw-border-t tw-border-solid tw-border-t-[#eaedf3] tw-flex-row': $isMobile.value,
91
+ }"
92
+ >
93
+ <div
94
+ ref="widgetsContainerRef"
95
+ class="vc-blade__widget-container tw-flex tw-overflow-auto"
96
+ :class="{
97
+ 'tw-flex-col': $isDesktop.value,
98
+ 'tw-flex-row': $isMobile.value,
99
+ }"
100
+ >
101
+ <slot
102
+ name="widgets"
103
+ :is-expanded="isExpanded"
104
+ ></slot>
105
+ </div>
106
+
107
+ <div
108
+ class="tw-flex tw-flex-auto"
109
+ :class="{
110
+ 'tw-flex-col tw-justify-end': $isDesktop.value,
111
+ 'tw-w-12 tw-max-w-12 tw-bg-white tw-items-center tw-justify-center tw-px-4 tw-ml-auto': $isMobile.value,
112
+ }"
113
+ >
114
+ <VcIcon
115
+ class="tw-self-center tw-justify-self-center tw-text-[#a1c0d4] tw-cursor-pointer hover:tw-text-[#7ea8c4]"
116
+ :class="{
117
+ 'tw-mb-4': $isDesktop.value,
118
+ }"
119
+ :icon="`fas fa-chevron-${$isDesktop.value ? (isExpanded ? 'right' : 'left') : isExpanded ? 'up' : 'down'}`"
120
+ @click="toggleWidgets"
121
+ ></VcIcon>
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </div>
74
126
  </div>
75
127
  </template>
76
128
 
77
129
  <script lang="ts" setup>
78
- import { computed, Ref, reactive, useAttrs, toRefs, toValue } from "vue";
130
+ import { computed, Ref, reactive, useAttrs, toRefs, toValue, ref, onMounted, onUpdated } from "vue";
79
131
  import { IBladeToolbar } from "../../../../core/types";
80
132
  import { usePopup } from "./../../../../shared";
81
133
  import { useI18n } from "vue-i18n";
@@ -83,6 +135,7 @@ import VcBladeHeader from "./_internal/vc-blade-header/vc-blade-header.vue";
83
135
  import VcBladeToolbar from "./_internal/vc-blade-toolbar/vc-blade-toolbar.vue";
84
136
  import { VcButton, VcIcon } from "./../../";
85
137
  import vcPopupError from "../../../../shared/components/common/popup/vc-popup-error.vue";
138
+ import { useLocalStorage } from "@vueuse/core";
86
139
 
87
140
  export interface Props {
88
141
  icon?: string;
@@ -117,12 +170,39 @@ withDefaults(defineProps<Props>(), {
117
170
  defineSlots<{
118
171
  actions: void;
119
172
  default: void;
173
+ widgets: void;
120
174
  }>();
121
175
 
122
176
  defineEmits<Emits>();
123
177
  const attrs = useAttrs();
124
178
  const { maximized, error }: { maximized?: Ref<boolean>; error?: Ref<string> } = toRefs(reactive(attrs));
125
179
  const { t } = useI18n({ useScope: "global" });
180
+ const widgetsRef = ref();
181
+ const widgetsContainerRef = ref();
182
+
183
+ const isExpanded = useLocalStorage("VC_BLADE_WIDGETS_IS_EXPANDED", true);
184
+
185
+ const toggleWidgets = () => {
186
+ isExpanded.value = !isExpanded.value;
187
+ };
188
+ const isWidgetContainerEmpty = ref(false);
189
+
190
+ const checkEmpty = (el: HTMLElement) => {
191
+ const isEmpty = !el.innerText.trim() && Array.from(el.children).every((node) => node.nodeType === Node.COMMENT_NODE);
192
+ isWidgetContainerEmpty.value = isEmpty;
193
+ };
194
+
195
+ onMounted(() => {
196
+ if (widgetsRef.value) {
197
+ checkEmpty(widgetsContainerRef.value);
198
+ }
199
+ });
200
+
201
+ onUpdated(() => {
202
+ if (widgetsRef.value) {
203
+ checkEmpty(widgetsContainerRef.value);
204
+ }
205
+ });
126
206
 
127
207
  const { open } = usePopup({
128
208
  component: vcPopupError,
@@ -142,9 +222,16 @@ const { open } = usePopup({
142
222
  --blade-color-error: #f14e4e;
143
223
  --blade-color-unsaved-changes: #82a6bd;
144
224
  --blade-color-unsaved-changes: #82a6bd;
225
+
226
+ --blade-widgets-width: 36px;
227
+ --blade-widgets-width-expanded: 80px;
145
228
  }
146
229
 
147
230
  .vc-app_mobile .vc-blade {
148
231
  @apply tw-m-0 tw-rounded-none;
149
232
  }
233
+
234
+ .vc-app_mobile .vc-blade__widgets {
235
+ @apply tw-flex tw-flex-row;
236
+ }
150
237
  </style>
@@ -23,29 +23,34 @@
23
23
  :model-value="value"
24
24
  :rules="cell.rules"
25
25
  >
26
- <VcInputCurrency
27
- :model-value="value"
28
- :options="[]"
29
- :option="(item[cell.currencyField || 'currency'] as string) || 'USD'"
30
- currency-display="symbol"
31
- class="tw-w-full"
32
- :error="errors.length > 0"
33
- :error-message="$isMobile.value ? errorMessage : undefined"
34
- @update:model-value="$emit('update', { field: cell.id, value: $event })"
35
- @blur="onBlur({ row: index, field: cell.id, errors })"
36
- >
26
+ <VcTooltip placement="bottom">
27
+ <template #default>
28
+ <VcInputCurrency
29
+ :model-value="value"
30
+ :options="[]"
31
+ :option="(item[cell.currencyField || 'currency'] as string) || 'USD'"
32
+ currency-display="symbol"
33
+ class="tw-w-full"
34
+ :error="errors.length > 0"
35
+ :error-message="$isMobile.value ? errorMessage : undefined"
36
+ @update:model-value="$emit('update', { field: cell.id, value: $event })"
37
+ @blur="onBlur({ row: index, field: cell.id, errors })"
38
+ >
39
+ <template
40
+ v-if="$isDesktop.value && errors.length > 0"
41
+ #append-inner
42
+ >
43
+ <VcIcon icon="fas fa-exclamation-circle tw-text-[color:var(--error-color)]"></VcIcon>
44
+ </template>
45
+ </VcInputCurrency>
46
+ </template>
37
47
  <template
38
- v-if="$isDesktop.value && errors.length > 0"
39
- #append-inner
48
+ v-if="errors.length > 0"
49
+ #tooltip
40
50
  >
41
- <VcTooltip placement="bottom-end">
42
- <VcIcon icon="fas fa-exclamation-circle tw-text-[color:var(--error-color)]"></VcIcon>
43
- <template #tooltip>
44
- <div class="tw-text-[color:var(--error-color)]">{{ errorMessage }}</div>
45
- </template>
46
- </VcTooltip>
51
+ <div class="tw-text-[color:var(--error-color)]">{{ errorMessage }}</div>
47
52
  </template>
48
- </VcInputCurrency>
53
+ </VcTooltip>
49
54
  </Field>
50
55
  </template>
51
56
  <template v-else>
@@ -124,6 +129,7 @@
124
129
  size="s"
125
130
  aspect="1x1"
126
131
  :src="value as string"
132
+ :empty-icon="('emptyIcon' in cell && cell.emptyIcon) || undefined"
127
133
  background="contain"
128
134
  />
129
135
  </template>
@@ -156,27 +162,32 @@
156
162
  :model-value="value"
157
163
  :rules="cell.rules"
158
164
  >
159
- <VcInput
160
- :model-value="value"
161
- class="tw-w-full"
162
- type="number"
163
- :error="errors.length > 0"
164
- :error-message="$isMobile.value ? errorMessage : undefined"
165
- @update:model-value="$emit('update', { field: cell.id, value: $event })"
166
- @blur="onBlur({ row: index, field: cell.id, errors })"
167
- >
165
+ <VcTooltip placement="bottom">
166
+ <template #default>
167
+ <VcInput
168
+ :model-value="value"
169
+ class="tw-w-full"
170
+ type="number"
171
+ :error="errors.length > 0"
172
+ :error-message="$isMobile.value ? errorMessage : undefined"
173
+ @update:model-value="$emit('update', { field: cell.id, value: $event })"
174
+ @blur="onBlur({ row: index, field: cell.id, errors })"
175
+ >
176
+ <template
177
+ v-if="$isDesktop.value && errors.length > 0"
178
+ #append-inner
179
+ >
180
+ <VcIcon icon="fas fa-exclamation-circle tw-text-[color:var(--error-color)]"></VcIcon>
181
+ </template>
182
+ </VcInput>
183
+ </template>
168
184
  <template
169
- v-if="$isDesktop.value && errors.length > 0"
170
- #append-inner
185
+ v-if="errors.length > 0"
186
+ #tooltip
171
187
  >
172
- <VcTooltip placement="bottom-end">
173
- <VcIcon icon="fas fa-exclamation-circle tw-text-[color:var(--error-color)]"></VcIcon>
174
- <template #tooltip>
175
- <div class="tw-text-[color:var(--error-color)]">{{ errorMessage }}</div>
176
- </template>
177
- </VcTooltip>
188
+ <div class="tw-text-[color:var(--error-color)]">{{ errorMessage }}</div>
178
189
  </template>
179
- </VcInput>
190
+ </VcTooltip>
180
191
  </Field>
181
192
  </template>
182
193
  <template v-else>
@@ -220,26 +231,30 @@
220
231
  :model-value="value"
221
232
  :rules="cell.rules"
222
233
  >
223
- <VcInput
224
- :model-value="value"
225
- class="tw-w-full"
226
- :error="errors.length > 0"
227
- :error-message="$isMobile.value ? errorMessage : undefined"
228
- @update:model-value="$emit('update', { field: cell.id, value: $event })"
229
- @blur="onBlur({ row: index, field: cell.id, errors })"
230
- >
234
+ <VcTooltip placement="bottom">
235
+ <template #default>
236
+ <VcInput
237
+ :model-value="value"
238
+ class="tw-w-full"
239
+ :error="errors.length > 0"
240
+ :error-message="$isMobile.value ? errorMessage : undefined"
241
+ @update:model-value="$emit('update', { field: cell.id, value: $event })"
242
+ @blur="onBlur({ row: index, field: cell.id, errors })"
243
+ >
244
+ <template
245
+ v-if="$isDesktop.value && errors.length > 0"
246
+ #append-inner
247
+ >
248
+ <VcIcon icon="fas fa-exclamation-circle tw-text-[color:var(--error-color)]"></VcIcon> </template
249
+ ></VcInput>
250
+ </template>
231
251
  <template
232
- v-if="$isDesktop.value && errors.length > 0"
233
- #append-inner
252
+ v-if="errors.length > 0"
253
+ #tooltip
234
254
  >
235
- <VcTooltip placement="bottom-end">
236
- <VcIcon icon="fas fa-exclamation-circle tw-text-[color:var(--error-color)]"></VcIcon>
237
- <template #tooltip>
238
- <div class="tw-text-[color:var(--error-color)]">{{ errorMessage }}</div>
239
- </template>
240
- </VcTooltip>
241
- </template></VcInput
242
- >
255
+ <div class="tw-text-[color:var(--error-color)]">{{ errorMessage }}</div>
256
+ </template>
257
+ </VcTooltip>
243
258
  </Field>
244
259
  </template>
245
260
  <template v-else>
@@ -259,6 +274,7 @@ import htmlTruncate from "truncate-html";
259
274
  import * as DOMPurify from "dompurify";
260
275
  import VcInputCurrency from "../../../../molecules/vc-input-currency/vc-input-currency.vue";
261
276
  import VcInput from "../../../../molecules/vc-input/vc-input.vue";
277
+ import VcTooltip from "../../../../atoms/vc-tooltip/vc-tooltip.vue";
262
278
  import { Field } from "vee-validate";
263
279
 
264
280
  export interface Props {