adata-ui 2.1.41-beta.0 → 2.1.41-beta.10

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 (63) hide show
  1. package/-error.vue +1 -1
  2. package/CLAUDE.md +3 -2
  3. package/assets/styles/main.css +4 -3
  4. package/assets/styles/theme.css +20 -6
  5. package/assets/styles/ui.css +77 -0
  6. package/components/elements/button/AButtonV2.vue +60 -15
  7. package/components/elements/count-up/ACountUp.vue +54 -0
  8. package/components/elements/curve-block-v2/ACurveBlockV2.vue +56 -18
  9. package/components/elements/empty-box/empty-box-V2.vue +1 -1
  10. package/components/elements/error-template/index.vue +8 -3
  11. package/components/elements/infinite-carousel/AInfiniteCarousel.vue +46 -8
  12. package/components/elements/section-heading/ASectionHeading.vue +35 -0
  13. package/components/elements/select/ACascadeSelectV2.vue +710 -0
  14. package/components/elements/select/ASelectV2.vue +70 -143
  15. package/components/elements/select/cascade-utils.ts +115 -0
  16. package/components/elements/select/types.ts +27 -0
  17. package/components/elements/select/ui/cascade-drawer.vue +146 -0
  18. package/components/elements/select/ui/cascade-panel.vue +237 -0
  19. package/components/elements/select/ui/select-trigger.vue +167 -0
  20. package/components/elements/step-slider/AStepSliderColCard.vue +75 -42
  21. package/components/elements/step-slider/AStepSliderColNumber.vue +74 -53
  22. package/components/features/color-mode/AColorMode.client.vue +1 -23
  23. package/components/features/go-top/GoTop.vue +4 -0
  24. package/components/forms/feedback-form/FeedbackFormV2.vue +18 -18
  25. package/components/forms/input/AInputV2.vue +69 -47
  26. package/components/forms/input/textarea/ATextareaV2.vue +10 -5
  27. package/components/modals/ReportBugModal.vue +1 -1
  28. package/components/modals/id/IdModals.vue +14 -10
  29. package/components/navigation/bottom-navigation/ABottomNavigation.vue +1 -1
  30. package/components/navigation/footer/AFooter.vue +2 -2
  31. package/components/navigation/header/AHeader.vue +8 -4
  32. package/components/navigation/pill-tabs/APillTabsV2.vue +29 -14
  33. package/components/overlays/drawer/ADrawerV2.vue +9 -2
  34. package/components/overlays/modal/AModalV2.vue +1 -0
  35. package/icons/ai/ai-diamond-unfilled.vue +26 -0
  36. package/icons/ai/ai-diamond.vue +11 -0
  37. package/icons/arrow/arrow-down-left.vue +6 -0
  38. package/icons/arrow/arrow-up-right.vue +6 -0
  39. package/icons/clock-rotate-ccw.vue +7 -0
  40. package/icons/crown.vue +6 -0
  41. package/icons/letter/letter-a.vue +4 -2
  42. package/icons/letter/letter-b.vue +2 -2
  43. package/icons/letter/letter-c.vue +2 -2
  44. package/icons/letter/letter-d.vue +2 -2
  45. package/icons/letter/letter-e.vue +2 -2
  46. package/icons/list-checks.vue +3 -0
  47. package/icons/log-out.vue +21 -0
  48. package/icons/main-filter.vue +6 -1
  49. package/icons/refresh-cw.vue +8 -0
  50. package/icons/shield-check.vue +20 -0
  51. package/icons/shuffle.vue +3 -0
  52. package/icons/user-plus.vue +3 -0
  53. package/icons/wallet.vue +6 -0
  54. package/illustrations/notes-search.vue +20 -0
  55. package/illustrations/notes-sticky.vue +20 -0
  56. package/illustrations/scheme-notes.vue +21 -0
  57. package/illustrations/scheme-waypoints.vue +25 -0
  58. package/lang/en.ts +2 -1
  59. package/lang/kk.ts +3 -2
  60. package/lang/ru.ts +2 -1
  61. package/nuxt.config.ts +7 -4
  62. package/package.json +2 -2
  63. package/utils/toCssSize.ts +11 -0
@@ -99,80 +99,101 @@ watch(() => [props.interval, props.autoplay, props.steps.length], () => {
99
99
 
100
100
  <template>
101
101
  <div
102
- class="grid grid-cols-1 items-center gap-4 lg:gap-16"
102
+ class="grid grid-cols-1 items-start gap-[34px] lg:gap-13"
103
103
  :class="colsClass"
104
104
  >
105
- <ol class="flex flex-col gap-4">
106
- <li
107
- v-for="(step, i) in steps"
108
- :key="i"
109
- class="group relative flex cursor-pointer items-start gap-4 px-4 py-3"
110
- @click="goTo(i)"
111
- >
105
+ <div class="flex flex-col">
106
+ <ol class="relative flex flex-col gap-1.5">
107
+ <!-- Одна непрерывная линия с прогрессивной заливкой (как в макете),
108
+ а не дискретные сегменты между шагами. Линия проходит ЗА кружками,
109
+ поэтому их фон обязан быть непрозрачным (см. ниже). -->
112
110
  <span
113
- v-if="i === active"
114
- class="ss-card-glow absolute inset-0 z-[1] rounded-2xl bg-main"
115
- />
116
- <span
117
- v-if="i < steps.length - 1"
118
- class="absolute left-[34px] top-[46px] z-0 h-[calc(100%_-_18px)] w-0.5 -translate-x-1/2 rounded transition-colors"
119
- :class="i < active ? 'bg-brand' : 'bg-badge'"
120
- />
121
- <span class="relative z-10 flex w-9 shrink-0 justify-center">
111
+ v-if="steps.length > 1"
112
+ class="absolute inset-y-9 left-[31px] z-0 w-0.5 overflow-hidden rounded bg-badge"
113
+ aria-hidden="true"
114
+ >
115
+ <span
116
+ class="ss-line-fill absolute inset-x-0 top-0 rounded bg-brand"
117
+ :style="{ height: `${(active / (steps.length - 1)) * 100}%` }"
118
+ />
119
+ </span>
120
+
121
+ <li
122
+ v-for="(step, i) in steps"
123
+ :key="i"
124
+ class="ss-step relative z-10 flex cursor-pointer gap-[15px] rounded-14 px-4 py-3.5"
125
+ :class="i === active ? 'ss-step-on bg-main' : ''"
126
+ @click="goTo(i)"
127
+ >
128
+ <!-- Фон кружка только НЕПРОЗРАЧНЫМИ токенами: bg-brand-subtle в тёмной
129
+ теме = #068DFF1A (10% альфы), и линия прогресса просвечивала
130
+ сквозь пройденный шаг. Для дарка берём непрозрачный brand-900. -->
122
131
  <span
123
- class="text-body-md flex items-center justify-center rounded-full font-bold transition-all duration-300"
132
+ class="relative z-10 flex size-[34px] shrink-0 items-center justify-center rounded-full text-body-md font-bold transition-all duration-300"
124
133
  :class="i === active
125
- ? 'ss-num-glow size-9 bg-brand text-action-primary-fg'
134
+ ? 'ss-num-glow scale-[1.08] bg-brand text-inverted'
126
135
  : i < active
127
- ? 'size-[34px] bg-brand-subtle text-brand group-hover:bg-blue-subtle'
128
- : 'size-[34px] bg-muted text-disabled group-hover:bg-badge'"
136
+ ? 'bg-brand-100 text-brand dark:bg-brand-900 dark:text-brand-300'
137
+ : 'bg-canvas text-secondary'"
129
138
  >
130
139
  {{ i + 1 }}
131
140
  </span>
132
- </span>
133
- <div class="relative z-10">
134
- <p
135
- class="text-body-lg-strong transition-colors"
136
- :class="i === active
137
- ? 'text-primary'
138
- : i < active
139
- ? 'text-quaternary'
140
- : 'text-disabled'"
141
- >
142
- {{ step.title }}
143
- </p>
144
- <p class="text-body-md text-secondary">
145
- {{ step.description }}
146
- </p>
147
- </div>
148
- </li>
149
- </ol>
141
+ <div>
142
+ <p
143
+ class="text-body-md-strong transition-colors"
144
+ :class="i === active ? 'text-primary' : 'text-secondary'"
145
+ >
146
+ {{ step.title }}
147
+ </p>
148
+ <p class="mt-[3px] text-body-sm leading-normal text-secondary">
149
+ {{ step.description }}
150
+ </p>
151
+ </div>
152
+ </li>
153
+ </ol>
154
+
155
+ <slot name="steps-footer" />
156
+ </div>
150
157
 
151
158
  <div
152
159
  @mouseenter="onMouseEnter"
153
160
  @mouseleave="onMouseLeave"
154
161
  >
155
- <div class="flex justify-center">
156
- <slot
157
- name="panel"
158
- :active="active"
159
- :step="steps[active]"
160
- :progress="progress"
161
- :count="steps.length"
162
- :fill-width="fillWidth"
163
- :go-to="goTo"
164
- />
165
- </div>
162
+ <slot
163
+ name="panel"
164
+ :active="active"
165
+ :step="steps[active]"
166
+ :progress="progress"
167
+ :count="steps.length"
168
+ :fill-width="fillWidth"
169
+ :go-to="goTo"
170
+ />
166
171
  </div>
167
172
  </div>
168
173
  </template>
169
174
 
170
175
  <style scoped>
176
+ .ss-line-fill {
177
+ transition: height .6s cubic-bezier(.4, 0, .2, 1);
178
+ }
179
+
180
+ .ss-step {
181
+ background: transparent;
182
+ transition: background .4s, box-shadow .4s;
183
+ }
184
+
185
+ .ss-step-on {
186
+ box-shadow: 0 14px 34px -18px color-mix(in srgb, var(--color-brand-500) 30%, transparent);
187
+ }
188
+
171
189
  .ss-num-glow {
172
- box-shadow: 0 4px 12px color-mix(in srgb, var(--color-brand-500) 45%, transparent);
190
+ box-shadow: 0 6px 16px -4px color-mix(in srgb, var(--color-brand-500) 55%, transparent);
173
191
  }
174
192
 
175
- .ss-card-glow {
176
- box-shadow: 0 22px 60px -18px color-mix(in srgb, var(--color-brand-500) 32%, transparent);
193
+ @media (prefers-reduced-motion: reduce) {
194
+ .ss-line-fill,
195
+ .ss-step {
196
+ transition: none;
197
+ }
177
198
  }
178
199
  </style>
@@ -1,36 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import Moon from '#adata-ui/icons/moon.vue'
3
3
  import Sun from '#adata-ui/icons/sun.vue'
4
- import { useEventListener } from '@vueuse/core'
5
4
 
6
5
  const colorMode = useColorMode()
7
- const setColorMode = useCookie('colorMode')
8
-
9
- if (setColorMode.value) {
10
- colorMode.preference = setColorMode.value
11
- }
12
-
13
- const ONE_YEAR_SECONDS = 60 * 60 * 24 * 365
14
-
15
- function cookieDomainAttr(): string {
16
- const parts = location.hostname.split('.').reverse()
17
- if (parts.length < 2) return ''
18
- return `; domain=.${parts[1]}.${parts[0]}`
19
- }
20
6
 
21
7
  const isDark = computed(() => colorMode.value === 'dark')
22
8
 
23
9
  function toggle() {
24
- const value = isDark.value ? 'light' : 'dark'
25
- colorMode.preference = value
26
- document.cookie = `colorMode=${value}; max-age=${ONE_YEAR_SECONDS}${cookieDomainAttr()}; path=/`
10
+ colorMode.preference = isDark.value ? 'light' : 'dark'
27
11
  }
28
-
29
- useEventListener(window, 'storage', (event: StorageEvent) => {
30
- if (event.key === 'nuxt-color-mode' && event.newValue) {
31
- colorMode.preference = event.newValue
32
- }
33
- })
34
12
  </script>
35
13
 
36
14
  <template>
@@ -1,6 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { AIconChevronUp } from "#components";
3
3
 
4
+ const { t } = useI18n()
5
+
4
6
  const props = defineProps<{
5
7
  bottomOffset?: number
6
8
  }>()
@@ -49,9 +51,11 @@ onUnmounted(() => {
49
51
  :class="[{ 'opacity-0': !isShow }]"
50
52
  :style="{ bottom: footerHeight + 'px' }"
51
53
  >
54
+ <!-- Кнопка только с иконкой: без aria-label скринридер читает её как "button". -->
52
55
  <a-button
53
56
  @click="goTop"
54
57
  form="icon"
58
+ :aria-label="t('reuse.goTop')"
55
59
  :icon="AIconChevronUp"
56
60
  icon-class="h-6 w-6"
57
61
  size="lg"
@@ -5,6 +5,8 @@ import MessageSquare from '#adata-ui/icons/message/message-square.vue'
5
5
  import AInputV2 from '#adata-ui/components/forms/input/AInputV2.vue'
6
6
  import ATextareaV2 from '#adata-ui/components/forms/input/textarea/ATextareaV2.vue'
7
7
  import AButtonV2 from '#adata-ui/components/elements/button/AButtonV2.vue'
8
+ import { buildLocalizedUrl } from '#adata-ui/utils/localizedNavigation'
9
+ import { PAGES } from '#adata-ui/shared/constans/pages'
8
10
 
9
11
  const props = withDefaults(defineProps<{
10
12
  userInfo: any
@@ -16,24 +18,22 @@ const props = withDefaults(defineProps<{
16
18
  })
17
19
 
18
20
  const emit = defineEmits(['submit'])
19
-
21
+ const { locale } = useI18n()
20
22
  const { t } = useI18n()
21
-
22
- const isLoading = defineModel< null | any>('isLoading', { default: false })
23
- const isModalOpen = defineModel< null | any>('isModalOpen', { default: false })
24
-
23
+ const { landing } = useUrls()
24
+ const isLoading = defineModel<null | any>('isLoading', { default: false })
25
+ const isModalOpen = defineModel<null | any>('isModalOpen', { default: false })
25
26
  const fullName = ref<string>('')
26
27
  const phone = ref<string>('')
27
28
  const mail = ref<string>('')
28
29
  const comment = ref<string>('')
29
-
30
30
  const submitted = ref(false)
31
31
 
32
32
  const scheme = z.object({
33
- fullName: z.string().nonempty(t('forms.feedback.fieldRequired')),
33
+ fullName: z.string(),
34
34
  phone: z.string().nonempty(t('forms.feedback.fieldRequired')).min(17, t('forms.feedback.phoneMinLength', { count: 10 })),
35
- mail: z.string().nonempty(t('forms.feedback.fieldRequired')).email(t('forms.feedback.emailValid')),
36
- comment: z.string().nonempty(t('forms.feedback.fieldRequired')).min(10, t('forms.feedback.commentMinLength', { count: 10 })).max(250, t('forms.feedback.commentMaxLength', { count: 250 })),
35
+ mail: z.string(),
36
+ comment: z.string().max(250, t('forms.feedback.commentMaxLength', { count: 250 })),
37
37
  })
38
38
 
39
39
  const { validation, getError } = useUIValidation(
@@ -58,7 +58,6 @@ const send = () => {
58
58
  mail.value = props.userInfo?.email || ''
59
59
  comment.value = ''
60
60
  }
61
-
62
61
  defineExpose({
63
62
  reset: () => (submitted.value = false),
64
63
  })
@@ -70,10 +69,10 @@ onMounted(() => {
70
69
 
71
70
  <template>
72
71
  <section class="feedback-v2">
73
- <!-- осознанно константный: промо-градиент brand-50/100 и shadow-brand-600/10 — константно-яркая промо-карточка, не флипается в тёмной теме -->
74
- <div class="grid grid-cols-1 items-center gap-8 rounded-[28px] border border-brand-100 bg-linear-to-br from-brand-50 to-brand-100 p-4 md:gap-10 md:p-12 lg:grid-cols-2">
72
+ <!-- градиент промо-карточки — осознанный литерал (§05 аудита), но флипается в тёмной теме -->
73
+ <div class="grid grid-cols-1 items-center gap-8 rounded-[28px] border border-default bg-linear-to-br from-[#f0f8ff] via-[#eef4ff] to-[#f0f8ff] p-4 md:gap-10 md:p-12 lg:grid-cols-2 dark:from-[#1a2231] dark:via-[#15191f] dark:to-[#121316]">
75
74
  <div>
76
- <div class="mb-4 inline-flex items-center gap-2 rounded-full border border-brand-100 bg-main px-3 py-1.5 text-body-md text-brand">
75
+ <div class="mb-4 inline-flex items-center gap-2 rounded-full border border-default bg-main h-8 px-3 text-body-md text-brand">
77
76
  <message-square class="size-3.5" />
78
77
  {{ t('forms.feedback.value') }}
79
78
  </div>
@@ -99,7 +98,6 @@ onMounted(() => {
99
98
  v-model="fullName"
100
99
  variant="gray"
101
100
  size="md"
102
- required
103
101
  :label="t('forms.demo.n')"
104
102
  :error="getError('fullName')"
105
103
  :disabled="isLoading"
@@ -121,7 +119,6 @@ onMounted(() => {
121
119
  type="email"
122
120
  variant="gray"
123
121
  size="md"
124
- required
125
122
  :label="t('forms.feedback.email')"
126
123
  :error="getError('mail')"
127
124
  :disabled="isLoading"
@@ -136,7 +133,6 @@ onMounted(() => {
136
133
  :label="t('forms.feedback.comment')"
137
134
  :error="getError('comment')"
138
135
  :disabled="isLoading"
139
- required
140
136
  />
141
137
  <a-button-v2
142
138
  type="submit"
@@ -149,9 +145,13 @@ onMounted(() => {
149
145
  </a-button-v2>
150
146
  <p class="mt-1 text-center text-body-sm text-disabled">
151
147
  {{ t('forms.feedback.consent') }}
152
- <span class="text-brand">
148
+ <nuxt-link-locale
149
+ class="cursor-pointer text-brand"
150
+ :to="buildLocalizedUrl(locale, landing, PAGES.privacy)"
151
+ target="_blank"
152
+ >
153
153
  {{ t('forms.feedback.consentAccent') }}
154
- </span>
154
+ </nuxt-link-locale>
155
155
  </p>
156
156
  </form>
157
157
  </div>
@@ -19,6 +19,7 @@ const props = withDefaults(defineProps<Props>(), {
19
19
  loading: false,
20
20
  variant: 'default',
21
21
  mask: undefined,
22
+ dropdownStrategy: 'absolute',
22
23
  autocompleteList: () => [] as T[],
23
24
  autocompleteKey: undefined,
24
25
  autocompleteOption: undefined,
@@ -43,13 +44,15 @@ interface Props {
43
44
  disabled?: boolean
44
45
  readonly?: boolean
45
46
  clearable?: boolean
46
- size?: 'sm' | 'md'
47
+ size?: 'sm' | 'md' | 'lg'
47
48
  error?: string | string[]
48
49
  startIcon?: Component
50
+ startIconClass?: string
49
51
  endIcon?: Component
50
52
  loading?: boolean
51
- variant?: 'default' | 'gray' | 'blue'
53
+ variant?: 'default' | 'gray' | 'blue' | 'canvas'
52
54
  mask?: string
55
+ dropdownStrategy?: 'absolute' | 'fixed'
53
56
  inputId?: string
54
57
  autocomplete?: string
55
58
  autocompleteList?: T[]
@@ -104,7 +107,7 @@ const fieldRef = ref<HTMLElement | null>(null)
104
107
  const floatingRef = ref<HTMLElement | null>(null)
105
108
  const { x, y, strategy, update } = useFloating(fieldRef, floatingRef, {
106
109
  placement: 'bottom-start',
107
- strategy: 'absolute',
110
+ strategy: computed(() => props.dropdownStrategy),
108
111
  middleware: [
109
112
  offset(6),
110
113
  flip({ fallbackPlacements: ['top-start'] }),
@@ -135,7 +138,7 @@ onUnmounted(() => cleanup?.())
135
138
  const wrapperRef = ref<HTMLDivElement | null>(null)
136
139
  onClickOutside(wrapperRef, () => {
137
140
  if (isFocused.value) inputRef.value?.blur()
138
- })
141
+ }, { ignore: [floatingRef] })
139
142
 
140
143
  function resolveSelectedValue(item: T): AutocompleteValue {
141
144
  if (props.autocompleteFn) return props.autocompleteFn(item) as AutocompleteValue
@@ -255,13 +258,19 @@ defineExpose({ focus, blur, inputRef })
255
258
 
256
259
  const layoutClass = computed(() => {
257
260
  if (!props.label) {
261
+ if (props.size === 'lg') return 'h-11 py-2'
258
262
  return props.size === 'md' ? 'h-10 py-1.5' : 'h-9 py-1.5'
259
263
  }
264
+ if (props.size === 'lg') return 'h-11 pt-4 pb-1'
260
265
  return props.size === 'md' ? 'h-10 pt-4 pb-0.5' : 'h-9 pt-3 pb-0.5'
261
266
  })
262
267
 
268
+ /* Компенсация асимметричных паддингов поля с label: иконки возвращаются к
269
+ визуальному центру. Величина = (pt + контент/2) - h/2:
270
+ sm 12+11-18=5, md 16+11-20=7, lg 16+12-22=6. */
263
271
  const iconOffsetClass = computed(() => {
264
272
  if (!props.label) return ''
273
+ if (props.size === 'lg') return '-translate-y-[6px]'
265
274
  return props.size === 'md' ? '-translate-y-[7px]' : '-translate-y-[5px]'
266
275
  })
267
276
 
@@ -286,11 +295,16 @@ const fieldClass = computed(() => {
286
295
  return 'border-default bg-badge/70'
287
296
  }
288
297
  if (props.variant === 'gray') {
289
- return 'border-transparent bg-muted'
298
+ /* по макету ДС-2026 у залитого поля бордер видимый, а не прозрачный */
299
+ return 'border-default bg-muted'
290
300
  }
291
301
  if (props.variant === 'blue') {
292
302
  return 'border-brand-200 bg-brand-subtle'
293
303
  }
304
+ if (props.variant === 'canvas') {
305
+ /* «утопленная» зона ДС + hero-поиск лендинга: bg-canvas глубже bg-muted в дарке */
306
+ return 'border-default bg-canvas'
307
+ }
294
308
  return 'border-default bg-main'
295
309
  })
296
310
 
@@ -308,7 +322,7 @@ function rowDisplay(item: T): unknown {
308
322
  >
309
323
  <div
310
324
  ref="fieldRef"
311
- class="input-v2__field text-primary relative flex w-full cursor-text items-center gap-2 rounded-[10px] border border-solid transition-colors duration-200 focus-within:border-focus focus-within:ring-2 focus-within:ring-brand-500/20 hover:border-brand"
325
+ class="input-v2__field relative flex w-full cursor-text items-center gap-2 rounded-10 border border-solid text-primary transition-colors duration-200 focus-within:border-focus focus-within:ring-2 focus-within:ring-brand-500/20 hover:border-brand"
312
326
  :class="[
313
327
  layoutClass,
314
328
  fieldClass,
@@ -331,11 +345,13 @@ function rowDisplay(item: T): unknown {
331
345
  {{ label }}<span v-if="required" class="text-danger">&nbsp;*</span>
332
346
  </label>
333
347
 
348
+ <!-- startIconClass ЗАМЕНЯЕТ дефолтные размер+цвет, а не дописывается:
349
+ конфликт одинаковых утилит решается порядком в stylesheet, не в class -->
334
350
  <component
335
351
  :is="startIcon"
336
352
  v-if="startIcon"
337
- class="size-4 shrink-0 text-icon-disabled"
338
- :class="iconOffsetClass"
353
+ class="shrink-0"
354
+ :class="[startIconClass || 'size-4 text-icon-disabled', iconOffsetClass]"
339
355
  />
340
356
 
341
357
  <input
@@ -348,7 +364,7 @@ function rowDisplay(item: T): unknown {
348
364
  :readonly="readonly"
349
365
  :autocomplete="autocomplete"
350
366
  :tabindex="disabled ? -1 : 0"
351
- class="input-v2__input text-primary w-full min-w-0 bg-transparent leading-tight outline-hidden placeholder:text-secondary disabled:cursor-not-allowed"
367
+ class="input-v2__input w-full min-w-0 bg-transparent leading-tight text-primary outline-hidden placeholder:text-secondary disabled:cursor-not-allowed"
352
368
  v-bind="inputAttrs"
353
369
  @input="onInput"
354
370
  @focus="onFocus"
@@ -371,7 +387,7 @@ function rowDisplay(item: T): unknown {
371
387
  @mousedown.prevent
372
388
  @click.stop="onClear"
373
389
  >
374
- <a-icon-x-mark class="!m-0 size-3.5" />
390
+ <a-icon-x-mark class="m-0! size-3.5" />
375
391
  </button>
376
392
 
377
393
  <component
@@ -384,43 +400,45 @@ function rowDisplay(item: T): unknown {
384
400
  <slot name="endButton" />
385
401
  </div>
386
402
 
387
- <transition
388
- enter-active-class="input-v2__dropdown-enter-active"
389
- enter-from-class="input-v2__dropdown-enter-from"
390
- enter-to-class="input-v2__dropdown-enter-to"
391
- leave-active-class="input-v2__dropdown-leave-active"
392
- leave-from-class="input-v2__dropdown-leave-from"
393
- leave-to-class="input-v2__dropdown-leave-to"
394
- >
395
- <div
396
- v-if="isDropdownOpen"
397
- ref="floatingRef"
398
- class="input-v2__dropdown z-[10000] flex flex-col gap-2 rounded-lg border border-default bg-main p-1.5 shadow-lg shadow-surface-dark/10 dark:shadow-surface-dark/30"
399
- :style="{
400
- position: strategy,
401
- left: x != null ? `${x}px` : '',
402
- top: y != null ? `${y}px` : '',
403
- }"
403
+ <teleport to="body">
404
+ <transition
405
+ enter-active-class="input-v2__dropdown-enter-active"
406
+ enter-from-class="input-v2__dropdown-enter-from"
407
+ enter-to-class="input-v2__dropdown-enter-to"
408
+ leave-active-class="input-v2__dropdown-leave-active"
409
+ leave-from-class="input-v2__dropdown-leave-from"
410
+ leave-to-class="input-v2__dropdown-leave-to"
404
411
  >
405
- <ul ref="autocompleteListEl" class="input-v2__list flex flex-col">
406
- <li
407
- v-for="(item, idx) in autocompleteList"
408
- :key="idx"
409
- class="cursor-pointer rounded-md px-3 py-2 text-body-md transition-colors duration-100 hover:bg-action-secondary-hover"
410
- :class="{ 'input-v2__option--active': currentItemIndex === idx }"
411
- @mousedown.prevent="onSelectOption(item)"
412
- @touchend.prevent="onTouchEndOption(item)"
413
- @touchmove="isTouchScrolling = true"
414
- >
415
- <slot name="autocomplete-option" :item="item">
416
- {{ rowDisplay(item) }}
417
- </slot>
418
- </li>
419
- </ul>
420
-
421
- <slot name="autocomplete-buttons" :value="modelValue" />
422
- </div>
423
- </transition>
412
+ <div
413
+ v-if="isDropdownOpen"
414
+ ref="floatingRef"
415
+ class="input-v2__dropdown z-10001 flex flex-col gap-2 rounded-xl border border-default bg-main p-1.5 shadow-[0_24px_50px_-24px] shadow-brand-500/50"
416
+ :style="{
417
+ position: strategy,
418
+ left: x != null ? `${x}px` : '',
419
+ top: y != null ? `${y}px` : '',
420
+ }"
421
+ >
422
+ <ul ref="autocompleteListEl" class="input-v2__list flex flex-col">
423
+ <li
424
+ v-for="(item, idx) in autocompleteList"
425
+ :key="idx"
426
+ class="cursor-pointer rounded-lg px-3.5 py-2.5 text-body-md transition-colors duration-100 hover:bg-brand-subtle"
427
+ :class="{ 'input-v2__option--active': currentItemIndex === idx }"
428
+ @mousedown.prevent="onSelectOption(item)"
429
+ @touchend.prevent="onTouchEndOption(item)"
430
+ @touchmove="isTouchScrolling = true"
431
+ >
432
+ <slot name="autocomplete-option" :item="item">
433
+ {{ rowDisplay(item) }}
434
+ </slot>
435
+ </li>
436
+ </ul>
437
+
438
+ <slot name="autocomplete-buttons" :value="modelValue" />
439
+ </div>
440
+ </transition>
441
+ </teleport>
424
442
 
425
443
  <transition
426
444
  enter-active-class="input-v2__error-enter-active"
@@ -471,6 +489,10 @@ function rowDisplay(item: T): unknown {
471
489
  transform: translateY(-14px);
472
490
  }
473
491
 
492
+ .input-v2__label--floated[data-size="lg"] {
493
+ transform: translateY(-19px);
494
+ }
495
+
474
496
  .input-v2__dropdown-enter-active,
475
497
  .input-v2__dropdown-leave-active {
476
498
  transition:
@@ -491,7 +513,7 @@ function rowDisplay(item: T): unknown {
491
513
  }
492
514
 
493
515
  .input-v2__list {
494
- max-height: 250px;
516
+ min-height: 0;
495
517
  overflow-y: auto;
496
518
  overflow-x: hidden;
497
519
  scrollbar-width: none;
@@ -30,7 +30,7 @@ interface Props {
30
30
  readonly?: boolean
31
31
  error?: string | string[]
32
32
  size?: 'sm' | 'md'
33
- variant?: 'default' | 'gray' | 'blue'
33
+ variant?: 'default' | 'gray' | 'blue' | 'canvas'
34
34
  rows?: number
35
35
  resize?: 'none' | 'vertical' | 'both'
36
36
  maxlength?: number
@@ -60,11 +60,16 @@ const fieldClass = computed(() => {
60
60
  return 'border-default bg-badge/70'
61
61
  }
62
62
  if (props.variant === 'gray') {
63
- return 'border-transparent bg-muted'
63
+ /* по макету ДС-2026 у залитого поля бордер видимый, а не прозрачный */
64
+ return 'border-default bg-muted'
64
65
  }
65
66
  if (props.variant === 'blue') {
66
67
  return 'border-brand-200 bg-brand-subtle'
67
68
  }
69
+ if (props.variant === 'canvas') {
70
+ /* «утопленная» зона ДС: bg-canvas глубже bg-muted в дарке (парно с AInputV2) */
71
+ return 'border-default bg-canvas'
72
+ }
68
73
  return 'border-default bg-main'
69
74
  })
70
75
 
@@ -124,7 +129,7 @@ defineExpose({ focus, blur, textareaRef })
124
129
  <template>
125
130
  <div class="textarea-v2 relative w-full text-body-md" v-bind="wrapperAttrs">
126
131
  <div
127
- class="textarea-v2__field relative rounded-[10px] border border-solid transition-colors duration-200 focus-within:border-focus focus-within:ring-2 focus-within:ring-brand-500/20 hover:border-brand"
132
+ class="textarea-v2__field relative rounded-10 border border-solid transition-colors duration-200 focus-within:border-focus focus-within:ring-2 focus-within:ring-brand-500/20 hover:border-brand"
128
133
  :class="[fieldClass, { 'textarea-v2__field--error': hasError, 'textarea-v2__field--disabled': disabled }]"
129
134
  >
130
135
  <label
@@ -147,7 +152,7 @@ defineExpose({ focus, blur, textareaRef })
147
152
  :disabled="disabled"
148
153
  :readonly="readonly"
149
154
  :tabindex="disabled ? -1 : 0"
150
- class="textarea-v2__input text-primary block w-full bg-transparent leading-snug outline-hidden placeholder:text-secondary disabled:cursor-not-allowed"
155
+ class="textarea-v2__input block w-full bg-transparent leading-snug text-primary outline-hidden placeholder:text-secondary disabled:cursor-not-allowed"
151
156
  :class="[resizeClass, paddingClass]"
152
157
  v-bind="inputAttrs"
153
158
  @focus="onFocus"
@@ -156,7 +161,7 @@ defineExpose({ focus, blur, textareaRef })
156
161
 
157
162
  <span
158
163
  v-if="maxlength"
159
- class="textarea-v2__counter pointer-events-none absolute bottom-1.5 right-3 text-body-sm text-icon-disabled"
164
+ class="textarea-v2__counter pointer-events-none absolute right-3 bottom-1.5 text-body-sm text-icon-disabled"
160
165
  >
161
166
  {{ modelValue?.length ?? 0 }}/{{ maxlength }}
162
167
  </span>
@@ -100,7 +100,7 @@ async function send() {
100
100
 
101
101
  <div class="flex flex-col gap-5 px-6 py-4">
102
102
  <div class="flex flex-col items-center justify-center gap-4">
103
- <a-ill-stop-sign class="size-[140px]" />
103
+ <a-ill-mail-active class="size-[140px]" />
104
104
  <slot name="title">
105
105
  <p class="text-body-md text-center">
106
106
  {{ name }}
@@ -1,16 +1,20 @@
1
1
  <script setup lang="ts">
2
- import IdAutoLogoutModal from '#adata-ui/components/modals/id/IdAutoLogoutModal.vue'
3
- import IdConfirmAccountOtpModal from '#adata-ui/components/modals/id/IdConfirmAccountOtpModal.vue'
4
- import IdConfirmSuccessfulModal from '#adata-ui/components/modals/id/IdConfirmSuccessfulModal.vue'
5
- import IdLoginModal from '#adata-ui/components/modals/id/IdLoginModal.vue'
6
- import IdNewPasswordModal from '#adata-ui/components/modals/id/IdNewPasswordModal.vue'
7
- import IdPasswordSuccessfulModal from '#adata-ui/components/modals/id/IdPasswordSuccessfulModal.vue'
8
- import IdRecoveryModal from '#adata-ui/components/modals/id/IdRecoveryModal.vue'
9
- import IdRegistrationModal from '#adata-ui/components/modals/id/IdRegistrationModal.vue'
10
- import IdResetPasswordOtpModal from '#adata-ui/components/modals/id/IdResetPasswordOtpModal.vue'
11
- import IdTwoFactorModal from '#adata-ui/components/modals/id/IdTwoFactorModal.vue'
12
2
  import { removeTrailingSlash } from '#adata-ui/components/utils/removeTrailingSlash'
13
3
  import { writeAccessTokenCookie } from '#adata-ui/composables/useAccessTokenCookie'
4
+ // Каждое из этих окон рендерится только под своим v-if, но статический import
5
+ // всё равно тащил все десять (плюс zod, который они используют для валидации)
6
+ // в стартовый чанк каждого приложения-потребителя. defineAsyncComponent грузит
7
+ // чанк в момент открытия окна: поведение то же, стартовый бандл меньше.
8
+ const IdAutoLogoutModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdAutoLogoutModal.vue'))
9
+ const IdConfirmAccountOtpModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdConfirmAccountOtpModal.vue'))
10
+ const IdConfirmSuccessfulModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdConfirmSuccessfulModal.vue'))
11
+ const IdLoginModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdLoginModal.vue'))
12
+ const IdNewPasswordModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdNewPasswordModal.vue'))
13
+ const IdPasswordSuccessfulModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdPasswordSuccessfulModal.vue'))
14
+ const IdRecoveryModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdRecoveryModal.vue'))
15
+ const IdRegistrationModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdRegistrationModal.vue'))
16
+ const IdResetPasswordOtpModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdResetPasswordOtpModal.vue'))
17
+ const IdTwoFactorModal = defineAsyncComponent(() => import('#adata-ui/components/modals/id/IdTwoFactorModal.vue'))
14
18
 
15
19
  const emit = defineEmits<{
16
20
  (e: 'closed', value: boolean): void
@@ -56,7 +56,7 @@ const profile: string = 'modals.mobile_navigation.type_profile'
56
56
  </script>
57
57
 
58
58
  <template>
59
- <header class="mx-3 mb-3 overflow-hidden rounded-full border border-default bg-subtle shadow-lg backdrop-blur-lg">
59
+ <header class="mx-3 mb-3 overflow-hidden rounded-full border border-default bg-main/10 shadow-lg backdrop-blur-lg backdrop-saturate-140">
60
60
  <div class="grid w-full grid-cols-3 justify-items-center">
61
61
  <slot>
62
62
  <button