adata-ui 2.1.40-beta.4 → 2.1.40-beta.6

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 (35) hide show
  1. package/components/elements/a-select-row/ASelectRowV2.vue +5 -4
  2. package/components/elements/button/AButtonV2.vue +29 -6
  3. package/components/elements/select/ASelectV2.vue +17 -9
  4. package/components/elements/status-badge-v2/StatusBadgeV2.vue +78 -0
  5. package/components/forms/feedback-form/FeedbackForm.vue +8 -8
  6. package/components/forms/input/AInputV2.vue +1 -1
  7. package/components/forms/input/textarea/ATextareaV2.vue +248 -0
  8. package/components/modals/AnotherDeviceModal.vue +16 -17
  9. package/components/modals/ApplicationAcceptedModal.vue +12 -15
  10. package/components/modals/ConnectingTariffModal.vue +13 -20
  11. package/components/modals/Insufficient-funds-modal.vue +18 -33
  12. package/components/modals/LimitReachedModal.vue +4 -3
  13. package/components/modals/NoAccessModal.vue +9 -11
  14. package/components/modals/PaymentMethodModal.vue +1 -1
  15. package/components/modals/ReportBugConfirmModal.vue +12 -10
  16. package/components/modals/ReportBugModal.vue +42 -36
  17. package/components/modals/Resend.vue +9 -10
  18. package/components/modals/SubmitApplicationModal.vue +27 -24
  19. package/components/navigation/header/HeaderLink.vue +15 -21
  20. package/components/navigation/header/NavList.vue +8 -32
  21. package/components/navigation/header/ProductMenu.vue +52 -50
  22. package/components/overlays/modal/AModalV2.vue +10 -9
  23. package/composables/useChipOverflow.ts +17 -7
  24. package/composables/useHeaderNavigationLinks.ts +53 -23
  25. package/icons/arrow/arrow-right.vue +14 -0
  26. package/icons/briefcase.vue +14 -0
  27. package/icons/car-side.vue +16 -0
  28. package/icons/cart.vue +15 -0
  29. package/icons/download-rounded.vue +15 -0
  30. package/icons/shield-verified.vue +14 -0
  31. package/lang/en.ts +34 -4
  32. package/lang/kk.ts +34 -4
  33. package/lang/ru.ts +36 -6
  34. package/package.json +1 -1
  35. package/shared/constans/pages.ts +8 -2
@@ -42,6 +42,7 @@ const { visibleCount, hiddenCount } = useChipOverflow({
42
42
  container: chipsRow,
43
43
  measure: measureRow,
44
44
  count: itemCount,
45
+ contentKey: computed(() => props.selectedItems?.map(item => item.name).join(' ') ?? ''),
45
46
  gap: 6,
46
47
  })
47
48
 
@@ -101,7 +102,7 @@ defineExpose({ onClear })
101
102
  <span
102
103
  v-for="item in visibleItems"
103
104
  :key="item.id"
104
- class="select-row-v2__chip inline-flex min-w-0 max-w-[180px] items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs dark:border-gray-700 dark:bg-white/[0.06]"
105
+ class="select-row-v2__chip inline-flex min-w-0 items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs dark:border-gray-700 dark:bg-white/[0.06]"
105
106
  @click.stop="deletable ? deleteItem(item) : (isOpen = true)"
106
107
  >
107
108
  <span class="truncate">{{ item.name }}</span>
@@ -123,14 +124,14 @@ defineExpose({ onClear })
123
124
  <span
124
125
  ref="measureRow"
125
126
  aria-hidden="true"
126
- class="pointer-events-none invisible absolute left-0 top-0 flex h-0 items-center gap-1.5"
127
+ class="pointer-events-none invisible absolute left-0 top-0 flex h-0 w-max items-center gap-1.5"
127
128
  >
128
129
  <span
129
130
  v-for="item in selectedItems"
130
131
  :key="`measure-${item.id}`"
131
- class="inline-flex max-w-[180px] items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs"
132
+ class="inline-flex items-center gap-1 whitespace-nowrap rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs"
132
133
  >
133
- <span class="truncate">{{ item.name }}</span>
134
+ <span>{{ item.name }}</span>
134
135
  <span
135
136
  v-if="deletable"
136
137
  class="flex shrink-0 items-center justify-center rounded-full p-0.5"
@@ -3,9 +3,11 @@ import type { Component } from 'vue'
3
3
 
4
4
  type View = 'solid' | 'outline' | 'ghost' | 'soft'
5
5
  type Size = 'sm' | 'md' | 'lg'
6
+ type Color = 'blue' | 'green' | 'red' | 'gray'
6
7
 
7
8
  const props = withDefaults(defineProps<{
8
9
  view?: View
10
+ color?: Color
9
11
  size?: Size
10
12
  icon?: Component
11
13
  iconRight?: boolean
@@ -15,6 +17,7 @@ const props = withDefaults(defineProps<{
15
17
  type?: 'button' | 'submit' | 'reset'
16
18
  }>(), {
17
19
  view: 'solid',
20
+ color: 'blue',
18
21
  size: 'md',
19
22
  iconRight: false,
20
23
  loading: false,
@@ -25,11 +28,31 @@ const props = withDefaults(defineProps<{
25
28
 
26
29
  const emit = defineEmits<{ click: [event: MouseEvent] }>()
27
30
 
28
- const VIEW_CLASS: Record<View, string> = {
29
- solid: 'bg-blue-600 text-white shadow-sm hover:bg-blue-700 hover:shadow-md active:bg-blue-800 dark:bg-blue-500 dark:hover:bg-blue-400',
30
- outline: 'border border-gray-300 bg-white text-deepblue-900 hover:border-gray-400 hover:bg-gray-50 active:bg-gray-100 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:hover:border-gray-600 dark:hover:bg-white/[0.06]',
31
- ghost: 'text-deepblue-900 hover:bg-gray-100 active:bg-gray-200 dark:text-gray-100 dark:hover:bg-white/[0.06] dark:active:bg-white/[0.1]',
32
- soft: 'bg-blue-50 text-blue-700 hover:bg-blue-100 active:bg-blue-200/70 dark:bg-blue-950/40 dark:text-blue-300 dark:hover:bg-blue-900/40',
31
+ const VIEW_CLASS: Record<Color, Record<View, string>> = {
32
+ blue: {
33
+ solid: 'bg-blue-600 text-white shadow-sm hover:bg-blue-700 hover:shadow-md active:bg-blue-800 dark:bg-blue-500 dark:hover:bg-blue-400',
34
+ outline: 'border border-blue-500 bg-white text-blue-700 hover:bg-blue-50 active:bg-blue-100 dark:border-blue-500 dark:bg-gray-900 dark:text-blue-300 dark:hover:bg-blue-950/40',
35
+ ghost: 'text-deepblue-900 hover:bg-gray-100 active:bg-gray-200 dark:text-gray-100 dark:hover:bg-white/[0.06] dark:active:bg-white/[0.1]',
36
+ soft: 'bg-blue-50 text-blue-700 hover:bg-blue-100 active:bg-blue-200/70 dark:bg-blue-950/40 dark:text-blue-300 dark:hover:bg-blue-900/40',
37
+ },
38
+ green: {
39
+ solid: 'bg-green-600 text-white shadow-sm hover:bg-green-700 hover:shadow-md active:bg-green-800 dark:bg-green-500 dark:hover:bg-green-400',
40
+ outline: 'border border-green-500 bg-white text-green-700 hover:bg-green-50 active:bg-green-100 dark:border-green-500 dark:bg-gray-900 dark:text-green-300 dark:hover:bg-green-950/40',
41
+ ghost: 'text-green-700 hover:bg-green-50 active:bg-green-100 dark:text-green-300 dark:hover:bg-green-950/40',
42
+ soft: 'bg-green-50 text-green-700 hover:bg-green-100 active:bg-green-200/70 dark:bg-green-950/40 dark:text-green-300 dark:hover:bg-green-900/40',
43
+ },
44
+ red: {
45
+ solid: 'bg-red-600 text-white shadow-sm hover:bg-red-700 hover:shadow-md active:bg-red-800 dark:bg-red-500 dark:hover:bg-red-400',
46
+ outline: 'border border-red-500 bg-white text-red-700 hover:bg-red-50 active:bg-red-100 dark:border-red-500 dark:bg-gray-900 dark:text-red-300 dark:hover:bg-red-950/40',
47
+ ghost: 'text-red-700 hover:bg-red-50 active:bg-red-100 dark:text-red-300 dark:hover:bg-red-950/40',
48
+ soft: 'bg-red-50 text-red-700 hover:bg-red-100 active:bg-red-200/70 dark:bg-red-950/40 dark:text-red-300 dark:hover:bg-red-900/40',
49
+ },
50
+ gray: {
51
+ solid: 'bg-gray-600 text-white shadow-sm hover:bg-gray-700 hover:shadow-md active:bg-gray-800 dark:bg-gray-500 dark:hover:bg-gray-400',
52
+ outline: 'border border-gray-300 bg-white text-deepblue-900 hover:border-gray-400 hover:bg-gray-50 active:bg-gray-100 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-100 dark:hover:border-gray-600 dark:hover:bg-white/[0.06]',
53
+ ghost: 'text-deepblue-900 hover:bg-gray-100 active:bg-gray-200 dark:text-gray-100 dark:hover:bg-white/[0.06] dark:active:bg-white/[0.1]',
54
+ soft: 'bg-gray-100 text-gray-700 hover:bg-gray-200 active:bg-gray-200/70 dark:bg-gray-800/60 dark:text-gray-200 dark:hover:bg-gray-700/60',
55
+ },
33
56
  }
34
57
 
35
58
  const SIZE_CLASS: Record<Size, string> = {
@@ -56,7 +79,7 @@ function onClick(event: MouseEvent) {
56
79
  :disabled="disabled || loading"
57
80
  class="button-v2 inline-flex select-none items-center justify-center rounded-xl font-semibold transition-all duration-150 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
58
81
  :class="[
59
- VIEW_CLASS[view],
82
+ VIEW_CLASS[color][view],
60
83
  SIZE_CLASS[size],
61
84
  block ? 'w-full' : '',
62
85
  iconRight ? 'flex-row-reverse' : '',
@@ -182,9 +182,10 @@ const valueToShow = computed<T | T[] | null>(() => {
182
182
  if (!Array.isArray(modelValue.value) || modelValue.value.length === 0) return null
183
183
  if (props.getOnlyKey) {
184
184
  const keys = modelValue.value as any[]
185
- const found = props.options.filter(opt =>
186
- keys.some(k => isEqual(k, opt[props.keyToSelect as keyof T])),
187
- )
185
+ // map over model keys (not options) so chips keep the selection order
186
+ const found = keys
187
+ .map(k => props.options.find(opt => isEqual(k, opt[props.keyToSelect as keyof T])))
188
+ .filter((opt): opt is T => opt != null)
188
189
  return found.length ? found : null
189
190
  }
190
191
  return modelValue.value as T[]
@@ -249,8 +250,11 @@ function onClear() {
249
250
  emits('onClear')
250
251
  }
251
252
 
252
- function onClearFromMulti(index: number) {
253
+ function onClearFromMulti(item: T) {
253
254
  if (!Array.isArray(modelValue.value)) return
255
+ const key = selectedKey(item)
256
+ const index = modelValue.value.findIndex(el => isEqual(el, key))
257
+ if (index === -1) return
254
258
  const next = [...modelValue.value]
255
259
  next.splice(index, 1)
256
260
  modelValue.value = next
@@ -267,6 +271,10 @@ const { visibleCount: visibleChipCount, hiddenCount: hiddenChipCount } = useChip
267
271
  container: valueColumn,
268
272
  measure: measureRow,
269
273
  count: selectedCount,
274
+ contentKey: computed(() => {
275
+ if (!Array.isArray(valueToShow.value)) return ''
276
+ return (valueToShow.value as any[]).map(item => item?.[props.keyToShow]).join(' ')
277
+ }),
270
278
  gap: 6,
271
279
  })
272
280
 
@@ -332,8 +340,8 @@ const isDefaultSelection = computed(() => {
332
340
  <span
333
341
  v-for="(item, index) in (valueToShow as any[]).slice(0, visibleChipCount)"
334
342
  :key="index"
335
- class="select-v2__chip inline-flex min-w-0 max-w-[180px] items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs dark:border-gray-700 dark:bg-white/[0.06]"
336
- @click.stop="canChipRemove ? onClearFromMulti(index) : (isOpen = true)"
343
+ class="select-v2__chip inline-flex min-w-0 items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs dark:border-gray-700 dark:bg-white/[0.06]"
344
+ @click.stop="canChipRemove ? onClearFromMulti(item) : (isOpen = true)"
337
345
  >
338
346
  <span class="truncate">{{ item[keyToShow] }}</span>
339
347
  <button
@@ -354,14 +362,14 @@ const isDefaultSelection = computed(() => {
354
362
  <span
355
363
  ref="measureRow"
356
364
  aria-hidden="true"
357
- class="pointer-events-none invisible absolute left-0 top-0 flex h-0 items-center gap-1.5"
365
+ class="pointer-events-none invisible absolute left-0 top-0 flex h-0 w-max items-center gap-1.5"
358
366
  >
359
367
  <span
360
368
  v-for="(item, index) in (valueToShow as any[])"
361
369
  :key="`measure-${index}`"
362
- class="inline-flex max-w-[180px] items-center gap-1 rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs"
370
+ class="inline-flex items-center gap-1 whitespace-nowrap rounded-md border border-gray-200 bg-gray-50 px-2 py-0.5 text-xs"
363
371
  >
364
- <span class="truncate">{{ item[keyToShow] }}</span>
372
+ <span>{{ item[keyToShow] }}</span>
365
373
  <span
366
374
  v-if="canChipRemove"
367
375
  class="flex shrink-0 items-center justify-center rounded-full p-0.5"
@@ -0,0 +1,78 @@
1
+ <script lang="ts" setup>
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ defineOptions({ name: 'StatusBadgeV2', inheritAttrs: false })
5
+
6
+ type BadgeType = 'primary' | 'success' | 'danger' | 'gray' | 'orange' | 'warning'
7
+ type BadgeView = 'default' | 'inverted' | 'transparent'
8
+ type BadgeSize = 'xs' | 'sm' | 'md' | 'lg'
9
+
10
+ interface Props {
11
+ type?: BadgeType
12
+ view?: BadgeView
13
+ size?: BadgeSize
14
+ }
15
+
16
+ const props = withDefaults(defineProps<Props>(), {
17
+ type: 'primary',
18
+ view: 'inverted',
19
+ size: 'sm',
20
+ })
21
+
22
+ const base
23
+ = 'inline-flex w-fit items-center justify-center gap-1.5 rounded-full font-semibold select-none'
24
+
25
+ const sizeClasses: Record<BadgeSize, string> = {
26
+ xs: 'px-2 py-0.5 text-[11px] leading-none',
27
+ sm: 'px-2.5 py-1 text-xs leading-none',
28
+ md: 'px-3 py-1.5 text-sm',
29
+ lg: 'px-4 py-2 text-base',
30
+ }
31
+
32
+ const typeClasses: Record<BadgeView, Partial<Record<BadgeType, string>>> = {
33
+ default: {
34
+ primary: 'bg-blue-700 text-white dark:bg-blue-500 dark:text-gray-900',
35
+ success: 'bg-green-500 text-white dark:bg-green-400 dark:text-gray-900',
36
+ danger: 'bg-red-500 text-white dark:text-gray-100',
37
+ warning: 'bg-yellow-400 text-white dark:text-gray-900',
38
+ gray: 'bg-deepblue-900 text-white dark:bg-gray-200/10 dark:text-gray-200',
39
+ orange: 'bg-orange-600 text-white dark:text-gray-100',
40
+ },
41
+ inverted: {
42
+ primary: 'bg-blue-700/10 text-blue-700 dark:bg-blue-500/10 dark:text-blue-500',
43
+ success: 'bg-green-500/15 text-green-600 dark:bg-green-400/15 dark:text-green-400',
44
+ danger: 'bg-red-500/10 text-red-500 dark:bg-red-400/10 dark:text-red-400',
45
+ warning: 'bg-yellow-400/15 text-yellow-500 dark:bg-yellow-400/15 dark:text-yellow-400',
46
+ gray: 'bg-deepblue-900/10 text-gray-600 dark:bg-gray-200/10 dark:text-gray-300',
47
+ orange: 'bg-orange-600/10 text-orange-600 dark:text-orange-500',
48
+ },
49
+ transparent: {
50
+ primary: 'bg-transparent text-blue-700 dark:text-blue-500',
51
+ success: 'bg-transparent text-green-600 dark:text-green-400',
52
+ danger: 'bg-transparent text-red-500 dark:text-red-400',
53
+ warning: 'bg-transparent text-yellow-500 dark:text-yellow-400',
54
+ gray: 'bg-transparent text-gray-600 dark:text-gray-300',
55
+ orange: 'bg-transparent text-orange-600 dark:text-orange-500',
56
+ },
57
+ }
58
+
59
+ const attrs = useAttrs()
60
+
61
+ // twMerge resolves Tailwind conflicts: any class passed via `class` wins over
62
+ // the defaults below, so padding / rounded / bg / text can all be overridden.
63
+ const classes = computed(() =>
64
+ twMerge(
65
+ base,
66
+ sizeClasses[props.size],
67
+ typeClasses[props.view][props.type],
68
+ attrs.class as string,
69
+ ),
70
+ )
71
+ </script>
72
+
73
+ <template>
74
+ <div :class="classes" v-bind="{ ...$attrs, class: undefined }">
75
+ <slot name="point" />
76
+ <slot />
77
+ </div>
78
+ </template>
@@ -152,26 +152,26 @@ onMounted(() => {
152
152
  </AButton>
153
153
  </section>
154
154
 
155
- <a-modal
155
+ <a-modal-v2
156
156
  v-model="isModalOpen"
157
157
  width="424px"
158
158
  @close="isModalOpen = false"
159
159
  >
160
- <div class="flex flex-col items-center gap-5">
161
- <h4 class="heading-04">
160
+ <div class="flex flex-col items-center gap-4 px-6 py-4">
161
+ <a-ill-mail />
162
+ <h4 class="text-[18px] font-semibold">
162
163
  {{ t('forms.feedback.modalTitle') }}
163
164
  </h4>
164
- <a-ill-mail />
165
165
  <p class="body-400 max-[352xp]">
166
166
  {{ t('forms.feedback.modalSubtitle') }}
167
167
  </p>
168
- <AButton
169
- block
168
+ <a-button-v2
170
169
  view="outline"
170
+ block
171
171
  @click="isModalOpen = false"
172
172
  >
173
173
  {{ t('forms.feedback.close') }}
174
- </AButton>
174
+ </a-button-v2>
175
175
  </div>
176
- </a-modal>
176
+ </a-modal-v2>
177
177
  </template>
@@ -257,7 +257,7 @@ const layoutClass = computed(() => {
257
257
  if (!props.label) {
258
258
  return props.size === 'md' ? 'h-10 py-1.5' : 'h-9 py-1.5'
259
259
  }
260
- return props.size === 'md' ? 'h-10 pt-3 pb-1' : 'h-9 pt-2 pb-1'
260
+ return props.size === 'md' ? 'h-10 pt-4 pb-0.5' : 'h-9 pt-3 pb-0.5'
261
261
  })
262
262
 
263
263
  const iconOffsetClass = computed(() => {
@@ -0,0 +1,248 @@
1
+ <script lang="ts" setup>
2
+ defineOptions({ inheritAttrs: false })
3
+
4
+ const props = withDefaults(defineProps<Props>(), {
5
+ label: '',
6
+ placeholder: '',
7
+ required: false,
8
+ disabled: false,
9
+ readonly: false,
10
+ error: undefined,
11
+ size: 'md',
12
+ variant: 'default',
13
+ rows: 4,
14
+ resize: 'vertical',
15
+ maxlength: undefined,
16
+ autoResize: false,
17
+ inputId: undefined,
18
+ })
19
+
20
+ const emit = defineEmits<{
21
+ (e: 'focus', ev: FocusEvent): void
22
+ (e: 'blur', ev: FocusEvent): void
23
+ }>()
24
+
25
+ interface Props {
26
+ label?: string
27
+ placeholder?: string
28
+ required?: boolean
29
+ disabled?: boolean
30
+ readonly?: boolean
31
+ error?: string | string[]
32
+ size?: 'sm' | 'md'
33
+ variant?: 'default' | 'gray' | 'blue'
34
+ rows?: number
35
+ resize?: 'none' | 'vertical' | 'both'
36
+ maxlength?: number
37
+ autoResize?: boolean
38
+ inputId?: string
39
+ }
40
+
41
+ const modelValue = defineModel<string>({ default: '' })
42
+
43
+ const textareaRef = ref<HTMLTextAreaElement | null>(null)
44
+ const isFocused = ref(false)
45
+
46
+ const internalId = useId()
47
+ const inputId = computed(() => props.inputId ?? `textarea-v2-${internalId}`)
48
+
49
+ const hasValue = computed(() => modelValue.value != null && modelValue.value !== '')
50
+ const hasError = computed(() => Array.isArray(props.error) ? props.error.length > 0 : !!props.error)
51
+ const isLabelFloated = computed(() => hasValue.value || isFocused.value)
52
+
53
+ const errorMessages = computed(() => {
54
+ if (!props.error) return [] as string[]
55
+ return Array.isArray(props.error) ? props.error : [props.error]
56
+ })
57
+
58
+ const fieldClass = computed(() => {
59
+ if (props.disabled) {
60
+ return 'border-gray-200 bg-gray-100/70 dark:border-gray-700 dark:bg-white/[0.03]'
61
+ }
62
+ if (props.variant === 'gray') {
63
+ return 'border-transparent bg-gray-50 dark:border-transparent dark:bg-white/[0.04]'
64
+ }
65
+ if (props.variant === 'blue') {
66
+ return 'border-blue-200 bg-blue-50 dark:border-blue-900/50 dark:bg-blue-900/20'
67
+ }
68
+ return 'border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900'
69
+ })
70
+
71
+ const resizeClass = computed(() => {
72
+ if (props.autoResize) return 'resize-none'
73
+ switch (props.resize) {
74
+ case 'none': return 'resize-none'
75
+ case 'both': return 'resize'
76
+ case 'vertical':
77
+ default: return 'resize-y'
78
+ }
79
+ })
80
+
81
+ const paddingClass = computed(() => {
82
+ // Паддинг постоянный (не зависит от фокуса/значения) — textarea не меняет размер при фокусе.
83
+ const pt = !props.label ? 'pt-2.5' : (props.size === 'md' ? 'pt-4' : 'pt-3.5')
84
+ const pb = props.maxlength ? 'pb-6' : (!props.label ? 'pb-2.5' : (props.size === 'md' ? 'pb-2' : 'pb-1.5'))
85
+ return `px-4 ${pt} ${pb}`
86
+ })
87
+
88
+ const attrs = useAttrs()
89
+ const wrapperAttrs = computed(() => ({ class: attrs.class, style: attrs.style }))
90
+ const inputAttrs = computed(() => {
91
+ const { class: _c, style: _s, ...rest } = attrs as Record<string, unknown>
92
+ return rest
93
+ })
94
+
95
+ function adjustHeight() {
96
+ const el = textareaRef.value
97
+ if (!props.autoResize || !el) return
98
+ el.style.height = 'auto'
99
+ el.style.height = `${el.scrollHeight}px`
100
+ }
101
+
102
+ function onFocus(ev: FocusEvent) {
103
+ isFocused.value = true
104
+ emit('focus', ev)
105
+ }
106
+ function onBlur(ev: FocusEvent) {
107
+ isFocused.value = false
108
+ emit('blur', ev)
109
+ }
110
+
111
+ function focus() {
112
+ textareaRef.value?.focus()
113
+ }
114
+ function blur() {
115
+ textareaRef.value?.blur()
116
+ }
117
+
118
+ watch(modelValue, () => nextTick(adjustHeight))
119
+ onMounted(adjustHeight)
120
+
121
+ defineExpose({ focus, blur, textareaRef })
122
+ </script>
123
+
124
+ <template>
125
+ <div class="textarea-v2 relative w-full text-sm" v-bind="wrapperAttrs">
126
+ <div
127
+ class="textarea-v2__field relative rounded-[10px] border border-solid transition-colors duration-200 focus-within:border-blue-600 focus-within:ring-2 focus-within:ring-blue-600/20 hover:border-blue-500 dark:focus-within:border-blue-400 dark:focus-within:ring-blue-400/20 dark:hover:border-blue-400"
128
+ :class="[fieldClass, { 'textarea-v2__field--error': hasError, 'textarea-v2__field--disabled': disabled }]"
129
+ >
130
+ <label
131
+ v-if="label"
132
+ :for="inputId"
133
+ :data-size="size"
134
+ class="textarea-v2__label pointer-events-none absolute left-4 max-w-[calc(100%-2rem)] truncate text-gray-500 dark:text-gray-400"
135
+ :class="{ 'textarea-v2__label--floated': isLabelFloated }"
136
+ >
137
+ {{ label }}<span v-if="required" class="text-red-500 dark:text-red-400">&nbsp;*</span>
138
+ </label>
139
+
140
+ <textarea
141
+ :id="inputId"
142
+ ref="textareaRef"
143
+ v-model="modelValue"
144
+ :rows="rows"
145
+ :maxlength="maxlength"
146
+ :placeholder="(isLabelFloated || !label) ? placeholder : ''"
147
+ :disabled="disabled"
148
+ :readonly="readonly"
149
+ :tabindex="disabled ? -1 : 0"
150
+ class="textarea-v2__input text-deepblue-900 block w-full bg-transparent leading-snug outline-none placeholder:text-gray-500 disabled:cursor-not-allowed dark:text-gray-200 dark:placeholder:text-gray-400"
151
+ :class="[resizeClass, paddingClass]"
152
+ v-bind="inputAttrs"
153
+ @focus="onFocus"
154
+ @blur="onBlur"
155
+ />
156
+
157
+ <span
158
+ v-if="maxlength"
159
+ class="textarea-v2__counter pointer-events-none absolute bottom-1.5 right-3 text-[10px] text-gray-400 dark:text-gray-500"
160
+ >
161
+ {{ modelValue?.length ?? 0 }}/{{ maxlength }}
162
+ </span>
163
+ </div>
164
+
165
+ <transition
166
+ enter-active-class="textarea-v2__error-enter-active"
167
+ enter-from-class="textarea-v2__error-enter-from"
168
+ enter-to-class="textarea-v2__error-enter-to"
169
+ leave-active-class="textarea-v2__error-leave-active"
170
+ leave-from-class="textarea-v2__error-leave-from"
171
+ leave-to-class="textarea-v2__error-leave-to"
172
+ >
173
+ <p v-if="hasError" class="mt-1 flex flex-col text-xs text-red-500 dark:text-red-400">
174
+ <span v-for="(err, idx) in errorMessages" :key="idx">{{ err }}</span>
175
+ </p>
176
+ </transition>
177
+ </div>
178
+ </template>
179
+
180
+ <style scoped>
181
+ .textarea-v2__field--error {
182
+ border-color: theme('colors.red.500');
183
+ }
184
+
185
+ .textarea-v2__field--disabled {
186
+ pointer-events: none;
187
+ cursor: not-allowed;
188
+ opacity: 0.7;
189
+ }
190
+
191
+ .textarea-v2__field--disabled .textarea-v2__input {
192
+ cursor: not-allowed;
193
+ }
194
+
195
+ .textarea-v2__input {
196
+ scrollbar-width: thin;
197
+ }
198
+
199
+ .textarea-v2__label {
200
+ top: 26px;
201
+ transform: translateY(-50%);
202
+ font-size: 14px;
203
+ line-height: 1.3;
204
+ transition:
205
+ transform 300ms cubic-bezier(0.22, 1, 0.36, 1),
206
+ font-size 300ms cubic-bezier(0.22, 1, 0.36, 1),
207
+ color 300ms ease-out;
208
+ }
209
+
210
+ .textarea-v2__label[data-size="sm"] {
211
+ top: 24px;
212
+ }
213
+
214
+ .textarea-v2__label--floated {
215
+ font-size: 10px;
216
+ transform: translateY(-21px);
217
+ }
218
+
219
+ .textarea-v2__label--floated[data-size="sm"] {
220
+ transform: translateY(-19px);
221
+ }
222
+
223
+ .textarea-v2__error-enter-active {
224
+ transition: transform 0.3s, opacity 0.3s;
225
+ }
226
+
227
+ .textarea-v2__error-enter-from {
228
+ opacity: 0;
229
+ transform: translateY(-4px);
230
+ }
231
+
232
+ .textarea-v2__error-leave-active {
233
+ transition: transform 0.2s, opacity 0.2s;
234
+ }
235
+
236
+ .textarea-v2__error-leave-to {
237
+ opacity: 0;
238
+ transform: translateY(-4px);
239
+ }
240
+
241
+ @media (prefers-reduced-motion: reduce) {
242
+ .textarea-v2__label,
243
+ .textarea-v2__error-enter-active,
244
+ .textarea-v2__error-leave-active {
245
+ transition: none;
246
+ }
247
+ }
248
+ </style>
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { useAnotherDeviceModal } from '#adata-ui/composables/modalsState'
3
2
  import { useUrls } from '#adata-ui/composables/useUrls'
3
+
4
4
  const { t, locale } = useI18n()
5
5
  const localePath = useLocalePath()
6
6
 
@@ -14,47 +14,46 @@ function onClose() {
14
14
  </script>
15
15
 
16
16
  <template>
17
- <a-modal
17
+ <a-modal-v2
18
18
  v-model="isOpen"
19
- :title="t('modals.another_device_modal.title')"
19
+ width="424px"
20
20
  @close="onClose"
21
21
  >
22
- <div class="flex flex-col items-center justify-center gap-5">
22
+ <div class="flex flex-col items-center justify-center gap-4 px-6 pb-4">
23
23
  <a-ill-phone-check />
24
24
  <p class="body-400 text-center">
25
25
  {{ t('modals.another_device_modal.info') }}
26
26
  </p>
27
27
  <div
28
- class="body-400 flex gap-2 rounded-lg border border-red-500 dark:border-red-400 bg-[#FDECEB] dark:bg-red-400/10 p-4 text-red-500 dark:text-red-400"
28
+ class="body-400 flex gap-2 rounded-lg border border-red-500 bg-[#FDECEB] p-3 text-red-500 dark:border-red-400 dark:bg-red-400/10 dark:text-red-400"
29
29
  >
30
30
  <a-icon-warning-triangle
31
- class="shrink-0"
32
- width="24"
33
- height="24"
31
+ class="size-4 shrink-0"
34
32
  />
35
- <p>
33
+ <span class="text-xs">
36
34
  {{ t('modals.another_device_modal.alert') }}
37
- </p>
35
+ </span>
38
36
  </div>
39
37
  </div>
40
38
  <template #footer>
41
39
  <div class="flex w-full gap-2">
42
- <a-button
40
+ <a-button-v2
43
41
  view="outline"
44
- class="w-full"
42
+ color="blue"
43
+ block
45
44
  @click="onClose"
46
45
  >
47
46
  {{ t('modals.buttons.close') }}
48
- </a-button>
49
- <a-button
50
- class="w-full"
47
+ </a-button-v2>
48
+ <a-button-v2
49
+ block
51
50
  :to="buildLocalizedUrl(locale, landing)"
52
51
  >
53
52
  {{ t('modals.buttons.toMain') }}
54
- </a-button>
53
+ </a-button-v2>
55
54
  </div>
56
55
  </template>
57
- </a-modal>
56
+ </a-modal-v2>
58
57
  </template>
59
58
 
60
59
  <style scoped></style>
@@ -1,10 +1,9 @@
1
1
  <script setup lang="ts">
2
-
3
2
  const { t } = useI18n()
4
3
 
5
- const isOpen = defineModel<boolean>({default: false})
4
+ const isOpen = defineModel<boolean>({ default: false })
6
5
 
7
- const cancel = () => {
6
+ function cancel() {
8
7
  isOpen.value = false
9
8
  }
10
9
 
@@ -16,30 +15,28 @@ const modalText = {
16
15
  </script>
17
16
 
18
17
  <template>
19
- <a-modal
18
+ <a-modal-v2
20
19
  v-model="isOpen"
21
- name="application-accepted"
22
- :prevent-close="true"
20
+ width="424px"
21
+ :persistent="true"
23
22
  >
24
- <div class="flex flex-col gap-5">
25
- <p class="text-center heading-02">
26
- {{ modalText.title }}
27
- </p>
28
- <a-ill-ok class="w-full" />
29
- <p class="text-center body-400">
23
+ <div class="flex flex-col items-center justify-center gap-4 px-6 py-4">
24
+ <a-ill-ok />
25
+ <p class="body-400 text-center">
30
26
  {{ modalText.info }}
31
27
  </p>
32
28
  </div>
33
29
  <template #footer>
34
- <a-button
30
+ <a-button-v2
35
31
  view="outline"
32
+ color="blue"
36
33
  block
37
34
  @click="cancel"
38
35
  >
39
36
  {{ modalText.close }}
40
- </a-button>
37
+ </a-button-v2>
41
38
  </template>
42
- </a-modal>
39
+ </a-modal-v2>
43
40
  </template>
44
41
 
45
42
  <style scoped>