plugin-ui-for-kzt 0.0.21 → 0.0.23

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 (57) hide show
  1. package/dist/components/BaseBadge/BaseBadge.vue.d.ts +1 -1
  2. package/dist/components/BaseButton/BaseButton.vue.d.ts +1 -3
  3. package/dist/components/BaseCalendar/BaseCalendar.vue.d.ts +9 -1
  4. package/dist/components/BaseCheckbox/BaseCheckbox.vue.d.ts +1 -3
  5. package/dist/components/BaseDropdown/BaseDropdown.vue.d.ts +1 -3
  6. package/dist/components/BaseField/BaseField.vue.d.ts +98 -0
  7. package/dist/components/BaseInput/BaseInput.vue.d.ts +19 -17
  8. package/dist/components/BaseInputCalendar/BaseInputCalendar.vue.d.ts +15 -17
  9. package/dist/components/BaseInputCurrency/BaseInputCurrency.vue.d.ts +10 -12
  10. package/dist/components/BaseInputEmail/BaseInputEmail.vue.d.ts +9 -14
  11. package/dist/components/BaseInputPhone/BaseInputPhone.vue.d.ts +9 -14
  12. package/dist/components/BaseOpenedListItem/BaseOpenedListItem.vue.d.ts +1 -3
  13. package/dist/components/BasePagination/BasePagination.vue.d.ts +1 -1
  14. package/dist/components/BaseRadio/BaseRadio.vue.d.ts +1 -3
  15. package/dist/components/BaseSegmentedButtons/BaseSegmentedButtons.vue.d.ts +1 -3
  16. package/dist/components/BaseSelect/BaseSelect.vue.d.ts +1 -1
  17. package/dist/components/BaseSiteInput/BaseSiteInput.vue.d.ts +11 -7
  18. package/dist/components/BaseTabs/BaseTabs.vue.d.ts +25 -0
  19. package/dist/components/BaseTag/BaseTag.vue.d.ts +1 -1
  20. package/dist/components/BaseTextarea/BaseTextarea.vue.d.ts +9 -14
  21. package/dist/components/BaseToggle/BaseToggle.vue.d.ts +1 -3
  22. package/dist/components/BaseTooltip/BaseTooltip.vue.d.ts +1 -1
  23. package/dist/components/BaseUpload/BaseUpload.vue.d.ts +11 -0
  24. package/dist/components/BaseUpload/CropModal.vue.d.ts +9 -0
  25. package/dist/composables/kit/state.d.ts +1 -2
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.js +1 -1
  28. package/dist/index.js.LICENSE.txt +15 -0
  29. package/example/App.vue +37 -31
  30. package/example/TestImage.vue +6 -0
  31. package/package.json +2 -1
  32. package/src/components/BaseCheckbox/BaseCheckbox.vue +76 -46
  33. package/src/components/BaseField/BaseField.vue +184 -0
  34. package/src/components/BaseField/README.md +85 -0
  35. package/src/components/BaseInput/BaseInput.vue +162 -228
  36. package/src/components/BaseInputCalendar/BaseInputCalendar.vue +10 -7
  37. package/src/components/BaseInputCurrency/BaseInputCurrency.vue +39 -78
  38. package/src/components/BaseInputEmail/BaseInputEmail.vue +2 -4
  39. package/src/components/BaseInputPhone/BaseInputPhone.vue +29 -89
  40. package/src/components/BaseRadio/BaseRadio.vue +266 -233
  41. package/src/components/BaseSelect/BaseSelect.vue +9 -52
  42. package/src/components/BaseSiteInput/BaseSiteInput.vue +11 -63
  43. package/src/components/BaseTabs/BaseTabs.vue +193 -0
  44. package/src/components/BaseTextarea/BaseTextarea.vue +3 -30
  45. package/src/components/BaseUpload/BaseUpload.vue +35 -1
  46. package/src/components/BaseUpload/CropModal.vue +210 -0
  47. package/src/composables/kit/state.ts +1 -2
  48. package/src/index.ts +8 -2
  49. package/src/styles/index.scss +87 -2
  50. package/src/styles/root.scss +1 -0
  51. package/src/styles/variables.scss +2 -1
  52. package/src/types/calendar.d.ts +2 -0
  53. package/src/types/field.d.ts +12 -0
  54. package/src/types/input.d.ts +26 -8
  55. package/src/types/tab.d.ts +17 -0
  56. package/src/types/uploadedFile.d.ts +7 -0
  57. package/src/types/utils.d.ts +0 -1
@@ -1,8 +1,6 @@
1
1
  <template>
2
2
  <div class="base-select" :class="[classList]">
3
3
  <div class="base-select__wrapper">
4
- <label v-if="label" :for="id" class="base-select__label">{{ label }}</label>
5
-
6
4
  <base-dropdown
7
5
  v-model="dropdownVisible"
8
6
  transition-name="top"
@@ -17,7 +15,7 @@
17
15
  >
18
16
  <slot
19
17
  name="header"
20
- :value="actualOption"
18
+ :value="actualOption ? [actualOption] : undefined"
21
19
  >
22
20
  <div v-if="$slots.headerIcon" class="base-select__header_prefix_icon">
23
21
  <slot name="headerIcon" />
@@ -63,7 +61,7 @@
63
61
  key-field="id"
64
62
  class="base-select__list"
65
63
  >
66
- <template #default="{ item, index, active } : SlotProps">
64
+ <template #default="{ item, index, active } : ISelectSlotProps">
67
65
  <dynamic-scroller-item
68
66
  :item="item"
69
67
  :active="active"
@@ -90,10 +88,6 @@
90
88
  </div>
91
89
  </template>
92
90
  </base-dropdown>
93
-
94
- <div v-if="(error && typeof error === 'string') || hint" class="base-select__hint">
95
- {{ error || hint }}
96
- </div>
97
91
  </div>
98
92
  </div>
99
93
  </template>
@@ -102,20 +96,13 @@
102
96
  import { computed, ref, watch } from 'vue';
103
97
  import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
104
98
  import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller';
105
- import type { ICoreSelectProps, TSelectValue, ICoreSelectBaseProps } from '../../types/input';
99
+ import type { ICoreSelectProps, TSelectValue, ICoreSelectBaseProps, ICoreSelectOption, ISelectSlotProps } from '../../types/input';
106
100
  import BaseDropdown from '../BaseDropdown/BaseDropdown.vue';
107
101
  import BaseOpenedListItem from '../BaseOpenedListItem/BaseOpenedListItem.vue';
108
102
  import { useKitSize } from '../../composables/kit/size';
109
103
  import { useKitState } from '../../composables/kit/state';
110
104
  import { useKitStyle } from '../../composables/kit/style';
111
105
 
112
- // Определяем тип для slotProps
113
- interface SlotProps {
114
- item: ICoreSelectBaseProps;
115
- index: number;
116
- active: boolean;
117
- }
118
-
119
106
  const props = withDefaults(defineProps<ICoreSelectProps & {
120
107
  modelValue?: TSelectValue
121
108
  }>(), {
@@ -130,11 +117,11 @@ const emit = defineEmits<{
130
117
 
131
118
  const actualValue = ref<TSelectValue>(props.modelValue ?? '');
132
119
  const actualOption = computed(() =>
133
- props.options?.find((item: ICoreSelectBaseProps) => item.id === actualValue.value) || null
120
+ props.options?.find((item: ICoreSelectOption) => item?.id === actualValue.value) || null
134
121
  );
135
122
 
136
123
  watch(() => props.modelValue, (val) => {
137
- actualValue.value = val;
124
+ actualValue.value = val ?? '';
138
125
  }, { immediate: true });
139
126
 
140
127
  function handleInput(value: TSelectValue) {
@@ -152,7 +139,7 @@ const { styleClassList } = useKitStyle(props);
152
139
  const listItemAttrs = computed(() => (item: ICoreSelectBaseProps) => ({
153
140
  icon: item.icon,
154
141
  active: actualValue.value === item.id,
155
- title: item.name || item.label || '',
142
+ title: item.name || '',
156
143
  style: item.style,
157
144
  disabled: item.disabled,
158
145
  class: 'base-select__item',
@@ -170,8 +157,10 @@ const classList = computed(() => [
170
157
  ]);
171
158
 
172
159
  defineSlots<{
173
- default(props: SlotProps): any;
160
+ default(props: ISelectSlotProps): any;
174
161
  iconItem(props: { item: ICoreSelectBaseProps }): any;
162
+ header(props: { value: ICoreSelectProps['options'] }): any;
163
+ headerIcon(): any;
175
164
  }>();
176
165
  </script>
177
166
 
@@ -205,14 +194,6 @@ defineSlots<{
205
194
  }
206
195
 
207
196
  &.--is-error {
208
- #{$select}__hint {
209
- color: var(--error-red);
210
- }
211
-
212
- #{$select}__label {
213
- border-color: var(--error-red-light-01);
214
- }
215
-
216
197
  #{$select}__arrow > * {
217
198
  color: var(--error-red);
218
199
  }
@@ -276,14 +257,6 @@ defineSlots<{
276
257
 
277
258
  &.--small-size {
278
259
  #{$select} {
279
- &__label {
280
- font: var(--typography-text-s-medium);
281
- }
282
-
283
- &__hint {
284
- font: var(--typography-text-s-regular);
285
- }
286
-
287
260
  &__header_value {
288
261
  font: var(--typography-text-m-regular);
289
262
  }
@@ -307,14 +280,6 @@ defineSlots<{
307
280
 
308
281
  &.--medium-size {
309
282
  #{$select} {
310
- &__label {
311
- font: var(--typography-text-s-medium);
312
- }
313
-
314
- &__hint {
315
- font: var(--typography-text-s-regular);
316
- }
317
-
318
283
  &__header_value {
319
284
  font: var(--typography-text-m-regular);
320
285
  }
@@ -333,14 +298,6 @@ defineSlots<{
333
298
 
334
299
  &.--large-size {
335
300
  #{$select} {
336
- &__label {
337
- font: var(--typography-text-m-medium);
338
- }
339
-
340
- &__hint {
341
- font: var(--typography-text-m-regular);
342
- }
343
-
344
301
  &__header_value {
345
302
  font: var(--typography-text-l-regular);
346
303
  }
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div :class="classList">
3
3
  <div class="base-site-input__wrapper">
4
- <div :class="{ '--is-error' : urlError }" class="base-site-input__url-wrapper">
4
+ <div class="base-site-input__url-wrapper">
5
5
  <span class="base-site-input__url">
6
6
  https://
7
7
  </span>
@@ -9,9 +9,10 @@
9
9
  <base-input
10
10
  v-bind="{...$props, ...$attrs}"
11
11
  v-model="modelValue"
12
- id="url"
12
+ :id="id"
13
13
  type="url"
14
14
  :error="urlError || props.error"
15
+ class="base-site-input__input"
15
16
  @update:modelValue="onUpdateModelValue"
16
17
  />
17
18
  </div>
@@ -22,30 +23,24 @@
22
23
  import { computed, ref, watch } from 'vue';
23
24
  import BaseInput from '../BaseInput/BaseInput.vue';
24
25
  import { useKitSize } from '../../composables/kit/size'
25
- import { useKitState } from '../../composables/kit/state';
26
26
  import type { ICorePropsBaseSiteInput } from '../../types/input'
27
27
 
28
28
  const props = withDefaults(defineProps<ICorePropsBaseSiteInput>(),{
29
29
  size: 'medium',
30
30
  modelValue: '',
31
- error: '',
31
+ focusable: true,
32
32
  })
33
33
 
34
34
  const emit = defineEmits<{
35
35
  (event: 'update:modelValue', value: string): void;
36
36
  }>();
37
37
 
38
- const urlError = ref('');
38
+ const urlError = ref<string>('');
39
39
  const { sizeClassList } = useKitSize(props);
40
- const { stateClassList } = useKitState(props);
41
40
 
42
41
  const classList = computed(() => [
43
42
  sizeClassList.value,
44
- stateClassList.value,
45
- 'base-site-input',
46
- {
47
- '--is-has-hint': !!props.hint,
48
- }
43
+ 'base-site-input'
49
44
  ]);
50
45
 
51
46
  const normalizedValue = computed(() => {
@@ -82,7 +77,7 @@ watch(modelValue, (value) => {
82
77
  .base-site-input {
83
78
  $input: &;
84
79
 
85
- width: fit-content;
80
+ width: 100%;
86
81
 
87
82
  &__wrapper {
88
83
  display: flex;
@@ -97,6 +92,10 @@ watch(modelValue, (value) => {
97
92
  }
98
93
  }
99
94
 
95
+ &__input {
96
+ flex-grow: 1;
97
+ }
98
+
100
99
  &__url {
101
100
  color: var(--primary-text-tertiary);
102
101
  }
@@ -114,99 +113,48 @@ watch(modelValue, (value) => {
114
113
  }
115
114
 
116
115
  &.--small-size {
117
- &.--is-has-hint, &.--is-error {
118
- #{$input} {
119
- &__url-wrapper {
120
- margin-bottom: 26px;
121
- }
122
- }
123
- }
124
-
125
116
  #{$input} {
126
117
  &__url-wrapper{
127
118
  height: 40px;
128
119
  font: var(--typography-text-m-regular);
129
120
  border-top-left-radius: var(--corner-radius-s);
130
121
  border-bottom-left-radius: var(--corner-radius-s);
131
-
132
- &.--is-error {
133
- margin-bottom: 26px;
134
- }
135
122
  }
136
123
 
137
124
  &__url {
138
125
  padding: var(--spacing-s) var(--spacing-m) var(--spacing-s) var(--spacing-2l);
139
126
  }
140
127
  }
141
-
142
- :deep(.base-input__label-text),
143
- :deep(.base-input__hint) {
144
- transform: translateX(-75px);
145
- }
146
128
  }
147
129
 
148
130
  &.--medium-size {
149
- &.--is-has-hint, &.--is-error {
150
- #{$input} {
151
- &__url-wrapper {
152
- margin-bottom: 26px;
153
- }
154
- }
155
- }
156
-
157
131
  #{$input} {
158
132
  &__url-wrapper{
159
133
  height: 48px;
160
134
  font: var(--typography-text-m-regular);
161
135
  border-top-left-radius: var(--corner-radius-m);
162
136
  border-bottom-left-radius: var(--corner-radius-m);
163
-
164
- &.--is-error {
165
- margin-bottom: 26px;
166
- }
167
137
  }
168
138
 
169
139
  &__url {
170
140
  padding: var(--spacing-m) var(--spacing-m) var(--spacing-m) var(--spacing-2l);
171
141
  }
172
142
  }
173
-
174
- :deep(.base-input__label-text),
175
- :deep(.base-input__hint) {
176
- transform: translateX(-75px);
177
- }
178
143
  }
179
144
 
180
145
  &.--large-size {
181
- &.--is-has-hint, &.--is-error {
182
- #{$input} {
183
- &__url-wrapper {
184
- margin-bottom: 30px;
185
- }
186
- }
187
- }
188
-
189
146
  #{$input} {
190
147
  &__url-wrapper{
191
148
  height: 56px;
192
149
  font: var(--typography-text-l-regular);
193
150
  border-top-left-radius: var(--corner-radius-m);
194
151
  border-bottom-left-radius: var(--corner-radius-m);
195
-
196
- &.--is-error {
197
- margin-bottom: 30px;
198
- }
199
152
  }
200
153
 
201
154
  &__url {
202
155
  padding: var(--spacing-2l) var(--spacing-m) var(--spacing-2l) var(--spacing-l);
203
156
  }
204
157
  }
205
-
206
- :deep(.base-input__label-text),
207
- :deep(.base-input__hint) {
208
- transform: translateX(-83px);
209
- }
210
158
  }
211
159
  }
212
160
  </style>
@@ -0,0 +1,193 @@
1
+ <template>
2
+ <div class="base-tabs" :class="classList">
3
+ <div class="base-tabs__header">
4
+ <base-button
5
+ v-for="tab in tabs"
6
+ :key="tab.id"
7
+ size="custom"
8
+ color="custom"
9
+ class="base-tabs__tab"
10
+ :class="{ 'base-tabs__tab--active': activeTab === tab.id }"
11
+ @click="selectTab(tab.id)"
12
+ :disabled="tab.disabled"
13
+ >
14
+ <div v-if="tab.icon" class="base-tab__icon">
15
+ <component :is="tab.icon" />
16
+ </div>
17
+ <span>{{ tab.title }}</span>
18
+ </base-button>
19
+ </div>
20
+ <div class="base-tabs__content">
21
+ <slot />
22
+ </div>
23
+ </div>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import { ref, computed, watch } from 'vue';
28
+ import type { TTabProps } from '../../types/tab.d';
29
+ import BaseButton from '../BaseButton/BaseButton.vue';
30
+
31
+ const props = withDefaults(defineProps<TTabProps>(), {
32
+ size: 'medium',
33
+ modelValue: 0,
34
+ });
35
+
36
+ const emit = defineEmits(['update:modelValue']);
37
+
38
+ const activeTab = ref(props.modelValue);
39
+
40
+ watch(() => props.modelValue, (newValue) => {
41
+ activeTab.value = newValue;
42
+ });
43
+
44
+ const selectTab = (id: number) => {
45
+ if (!props.tabs[id]?.disabled) {
46
+ activeTab.value = id;
47
+ emit('update:modelValue', id);
48
+ }
49
+ };
50
+
51
+ const classList = computed(() => [`base-tabs--${props.size}`]);
52
+ </script>
53
+
54
+ <style lang="scss" scoped>
55
+ @import '../../styles/variables';
56
+ @import '../../styles/root';
57
+
58
+ .base-tabs {
59
+ width: 100%;
60
+
61
+ &__header {
62
+ display: flex;
63
+ align-items: stretch;
64
+ position: relative;
65
+ z-index: 1;
66
+
67
+ &::before {
68
+ content: '';
69
+ width: 100%;
70
+ border-bottom: 1px solid var(--primary-black-200);
71
+ position: absolute;
72
+ bottom: 0;
73
+ }
74
+ }
75
+
76
+ &__tab {
77
+ display: flex;
78
+ align-items: center;
79
+ padding: var(--spacing-m) var(--spacing-l);
80
+ background: none;
81
+ border: none;
82
+ cursor: pointer;
83
+ color: var(--primary-black-700);
84
+ transition: color 0.2s ease, border-color 0.2s ease;
85
+ white-space: nowrap;
86
+ position: relative;
87
+
88
+ @include hover {
89
+ & {
90
+ color: var(--primary-black);
91
+ }
92
+ }
93
+
94
+ @include pressed {
95
+ color: var(--primary-black);
96
+ }
97
+
98
+ @include is-disabled-state {
99
+ color: var(--primary-black-400);
100
+ }
101
+
102
+ &--active {
103
+ color: var(--primary-blue);
104
+ border-bottom: 2px solid var(--primary-blue);
105
+ z-index: 2;
106
+
107
+ @include hover {
108
+ & {
109
+ color: var(--primary-blue-700);
110
+ border-color: transparent;
111
+ }
112
+ }
113
+
114
+ @include pressed {
115
+ color: var(--primary-blue-deep);
116
+ border-color: transparent;
117
+ }
118
+ }
119
+
120
+ .base-tab__icon {
121
+ display: flex;
122
+ align-items: center;
123
+ }
124
+ }
125
+
126
+ // Размеры
127
+ &--extra-small {
128
+ .base-tabs__header {
129
+ gap: var(--spacing-l);
130
+ }
131
+
132
+ .base-tabs__tab {
133
+ height: 38px;
134
+ padding: var(--spacing-s) var(--spacing-m);
135
+ font: var(--typography-text-s-medium);
136
+ gap: var(--spacing-s);
137
+
138
+ &--active {
139
+ font: var(--typography-text-s-semibold);
140
+ }
141
+
142
+ .base-tab__icon {
143
+ width: 20px;
144
+ height: 20px;
145
+ }
146
+ }
147
+ }
148
+
149
+ &--small {
150
+ .base-tabs__header {
151
+ gap: var(--spacing-xl);
152
+ }
153
+
154
+ .base-tabs__tab {
155
+ height: 48px;
156
+ padding: var(--spacing-m) var(--spacing-l);
157
+ font: var(--typography-text-m-medium);
158
+ gap: var(--spacing-m);
159
+
160
+ &--active {
161
+ font: var(--typography-text-m-semibold);
162
+ }
163
+
164
+ .base-tab__icon {
165
+ width: 24px;
166
+ height: 24px;
167
+ }
168
+ }
169
+ }
170
+
171
+ &--medium {
172
+ .base-tabs__header {
173
+ gap: var(--spacing-xl);
174
+ }
175
+
176
+ .base-tabs__tab {
177
+ height: 56px;
178
+ padding: var(--spacing-l) var(--spacing-xl);
179
+ font: var(--typography-text-l-medium);
180
+ gap: var(--spacing-m);
181
+
182
+ &--active {
183
+ font: var(--typography-text-l-semibold);
184
+ }
185
+
186
+ .base-tab__icon {
187
+ width: 32px;
188
+ height: 32px;
189
+ }
190
+ }
191
+ }
192
+ }
193
+ </style>
@@ -1,8 +1,6 @@
1
1
  <template>
2
2
  <div class="base-textarea" :class="classList">
3
- <label class="base-textarea__label" :for="id">
4
- <span v-if="label" class="base-textarea__label-text">{{ label }}</span>
5
-
3
+ <div class="base-textarea__wrapper">
6
4
  <div class="base-textarea__wrapper">
7
5
  <textarea
8
6
  :id="id"
@@ -17,11 +15,7 @@
17
15
  {{ modelValue.length }}/{{ maxLength }}
18
16
  </span>
19
17
  </div>
20
-
21
- <div v-if="(error && typeof error === 'string') || hint" class="base-textarea__hint">
22
- {{ error || hint }}
23
- </div>
24
- </label>
18
+ </div>
25
19
  </div>
26
20
  </template>
27
21
 
@@ -34,8 +28,6 @@ import { useKitSize } from '../../composables/kit/size';
34
28
  const props = withDefaults(defineProps<ICoreTextareaProps & { maxLength?: number }>(), {
35
29
  modelValue: '',
36
30
  placeholder: '' as string,
37
- error: '',
38
- hint: '',
39
31
  size: 'medium',
40
32
  });
41
33
 
@@ -73,11 +65,6 @@ function handleInput(event: Event) {
73
65
  resize: none;
74
66
  }
75
67
 
76
- &__hint {
77
- align-self: flex-start;
78
- font: var(--typography-text-s-regular);
79
- }
80
-
81
68
  &__wrapper {
82
69
  display: flex;
83
70
  position: relative;
@@ -113,10 +100,6 @@ function handleInput(event: Event) {
113
100
  color: var(--primary-text-tertiary);
114
101
  background: var(--primary-black-100);
115
102
  }
116
-
117
- #{$textarea}__hint {
118
- color: var(--primary-text-secondary);
119
- }
120
103
  }
121
104
  }
122
105
 
@@ -126,7 +109,7 @@ function handleInput(event: Event) {
126
109
  color: var(--primary-text-quaternary);
127
110
  }
128
111
 
129
- &__label {
112
+ &__wrapper {
130
113
  display: flex;
131
114
  flex-direction: column;
132
115
  gap: 6px;
@@ -135,17 +118,7 @@ function handleInput(event: Event) {
135
118
  height: 100%;
136
119
  }
137
120
 
138
- &__label-text {
139
- align-self: flex-start;
140
- font: var(--typography-text-s-medium);
141
- color: var(--primary-text-primary);
142
- }
143
-
144
121
  &.--is-error {
145
- #{$textarea}__hint {
146
- color: var(--error-red);
147
- }
148
-
149
122
  #{$textarea}__field {
150
123
  border-color: var(--error-red-light-01);
151
124
 
@@ -111,10 +111,12 @@ import BaseIcon from '../BaseIcon/BaseIcon.vue';
111
111
  import { useModal } from '../../composables/useModal';
112
112
  import type { UploadedFile, IpropsUpload } from '../../types/uploadedFile.d';
113
113
  import ImageModal from './ImageModal.vue';
114
+ import CropModal from './CropModal.vue';
114
115
 
115
116
  const props = withDefaults(defineProps<IpropsUpload>(), {
116
117
  multiple: true,
117
118
  maxFileSize: 25 * 1024 * 1024, // 25 MB в байтах
119
+ enableCrop: false,
118
120
  });
119
121
 
120
122
  const emit = defineEmits(['update:files']);
@@ -141,6 +143,11 @@ const triggerFileInput = () => {
141
143
  fileInput.value?.click();
142
144
  };
143
145
 
146
+ const isImageFile = (file: File) => {
147
+ const fileExtension = `.${file.name.split('.').pop()?.toLowerCase() || ''}`;
148
+ return mediaExtensions.includes(fileExtension);
149
+ };
150
+
144
151
  const handleFileUpload = (event: Event) => {
145
152
  const target = event.target as HTMLInputElement;
146
153
  const files = target.files;
@@ -161,7 +168,11 @@ const handleFileUpload = (event: Event) => {
161
168
  return;
162
169
  }
163
170
 
164
- uploadedFiles.value.push({ name: file.name, size: file.size, file });
171
+ if (props.enableCrop && isImageFile(file)) {
172
+ openCropModal(file);
173
+ } else {
174
+ uploadedFiles.value.push({ name: file.name, size: file.size, file });
175
+ }
165
176
  });
166
177
 
167
178
  isUploading.value = false;
@@ -169,6 +180,29 @@ const handleFileUpload = (event: Event) => {
169
180
  }
170
181
  };
171
182
 
183
+ const openCropModal = (file: File) => {
184
+ const imageUrl = URL.createObjectURL(file);
185
+
186
+ modal.open('crop-modal', {
187
+ closable: false,
188
+ imageUrl,
189
+ fileName: file.name,
190
+ cancelText: props.cropTexts?.cancel,
191
+ confirmText: props.cropTexts?.confirm,
192
+ onConfirm: (croppedFile: File) => {
193
+ uploadedFiles.value.push({
194
+ name: croppedFile.name,
195
+ size: croppedFile.size,
196
+ file: croppedFile
197
+ });
198
+ URL.revokeObjectURL(imageUrl);
199
+ },
200
+ onCancel: () => {
201
+ URL.revokeObjectURL(imageUrl);
202
+ }
203
+ }, CropModal);
204
+ };
205
+
172
206
  const removeFile = (index: number, isMedia: boolean) => {
173
207
  const files = isMedia ? mediaFiles.value : otherFiles.value;
174
208
  const globalIndex = uploadedFiles.value.indexOf(files[index]);