adata-ui 2.1.40-beta.18 → 2.1.40-beta.19

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 (211) hide show
  1. package/.playground/app.vue +102 -0
  2. package/assets/styles/index.scss +67 -5
  3. package/components/elements/a-select-row/ASelectRowV2.vue +214 -0
  4. package/components/elements/a-select-row/index.vue +3 -5
  5. package/components/elements/accordion/AAccordion.vue +5 -3
  6. package/components/elements/accordion/AAccordionGroup/AAccordionGroup.vue +3 -3
  7. package/components/elements/alert/AAlert.vue +34 -47
  8. package/components/elements/bg-block/ABgBlock.vue +1 -1
  9. package/components/elements/button/AButton.vue +3 -3
  10. package/components/elements/button/AButtonV2.vue +133 -0
  11. package/components/elements/button-login/index.vue +6 -10
  12. package/components/elements/chip-wrapper/AChipWrapper.vue +1 -1
  13. package/components/elements/chips/AChips.vue +2 -2
  14. package/components/elements/companies/AOurClients.vue +3 -1
  15. package/components/elements/curve-block-v2/ACurveBlockV2.vue +85 -0
  16. package/components/elements/digit-badge/ADigitBadge.vue +24 -22
  17. package/components/elements/feature-description/AFeatureDescription.vue +1 -1
  18. package/components/elements/leave-note/ALeaveNote.vue +39 -18
  19. package/components/elements/pagination/APagination.vue +7 -7
  20. package/components/elements/segmented/ASegmentedV2.vue +151 -0
  21. package/components/elements/select/ASelect.vue +4 -4
  22. package/components/elements/select/ASelectMobile.vue +2 -2
  23. package/components/elements/select/ASelectV2.vue +588 -0
  24. package/components/elements/select/ui/select-button.vue +1 -1
  25. package/components/elements/show-more/AShowMoreV2.vue +26 -0
  26. package/components/elements/slider/ASlider.vue +174 -0
  27. package/components/elements/slider/ASliderItem.vue +31 -0
  28. package/components/elements/slider/types.ts +21 -0
  29. package/components/elements/star-rating/AStarRating.vue +12 -4
  30. package/components/elements/status-badge/AStatusBadge.vue +91 -44
  31. package/components/elements/step-slider/AStepSliderColCard.vue +232 -0
  32. package/components/elements/step-slider/AStepSliderColNumber.vue +178 -0
  33. package/components/elements/table/ATable.vue +1 -1
  34. package/components/elements/table/table.config.ts +2 -2
  35. package/components/elements/tree-select/ATreeSelect.vue +97 -113
  36. package/components/elements/tree-select/TreeService.ts +142 -207
  37. package/components/elements/tree-select/components/ATreeSelectNode.vue +104 -0
  38. package/components/elements/tree-select/types.ts +40 -11
  39. package/components/elements/tree-select/utils.ts +30 -0
  40. package/components/elements/tree-select-modal/ATreeSelectModal.vue +217 -0
  41. package/components/features/color-mode/AColorMode.client.vue +74 -32
  42. package/components/features/dropdown/ADropdownV2.vue +140 -0
  43. package/components/features/lang-switcher/lang-switcher.vue +125 -40
  44. package/components/features/payment/banner/BasicPlusLimitBanner.vue +3 -3
  45. package/components/features/payment/banner/PaymentBanner.vue +3 -3
  46. package/components/features/payment/banner/UpSellBanner.vue +3 -3
  47. package/components/features/payment/process/PaymentKaspiQrSidePanel.vue +45 -27
  48. package/components/features/payment/process/PaymentKaspiRedirectSidePanel.vue +34 -23
  49. package/components/features/payment/process/PaymentMethodSidePanel.vue +66 -49
  50. package/components/features/payment/process/PaymentProcess.vue +6 -6
  51. package/components/features/payment/process/PaymentTopUpSidePanel.vue +49 -63
  52. package/components/features/pk-mobile-services/APkMobileServices.vue +6 -28
  53. package/components/features//321/201hange-version/AChangeVersion.vue +2 -2
  54. package/components/forms/checkbox/ACheckbox.vue +1 -1
  55. package/components/forms/checkbox/ACheckboxV2.vue +229 -0
  56. package/components/forms/feedback-form/FeedbackForm.vue +8 -8
  57. package/components/forms/feedback-form/FeedbackFormV2.vue +182 -0
  58. package/components/forms/input/AInputV2.vue +542 -0
  59. package/components/forms/input/date/AInputDate.vue +2 -2
  60. package/components/forms/input/password/AInputPassword.vue +8 -2
  61. package/components/forms/input/standard/AInputStandard.vue +3 -3
  62. package/components/forms/input/textarea/ATextarea.vue +2 -2
  63. package/components/forms/input/textarea/ATextareaV2.vue +248 -0
  64. package/components/forms/toggle/AToggle.vue +2 -3
  65. package/components/forms/toggle/AToggleV2.vue +71 -0
  66. package/components/modals/AnotherDeviceModal.vue +22 -18
  67. package/components/modals/ApplicationAcceptedModal.vue +12 -15
  68. package/components/modals/ConnectingTariffModal.vue +13 -20
  69. package/components/modals/ContactsMobileModel.vue +1 -1
  70. package/components/modals/ContentNavigationModal.vue +77 -333
  71. package/components/modals/Insufficient-funds-modal.vue +18 -33
  72. package/components/modals/LimitReachedModal.vue +17 -14
  73. package/components/modals/NoAccessModal.vue +9 -11
  74. package/components/modals/PaymentMethodModal.vue +1 -1
  75. package/components/modals/ReportBugConfirmModal.vue +12 -10
  76. package/components/modals/ReportBugModal.vue +46 -38
  77. package/components/modals/Resend.vue +9 -10
  78. package/components/modals/SubmitApplicationModal.vue +27 -24
  79. package/components/modals/id/IdBanner.vue +3 -3
  80. package/components/modals/id/IdConfirmAccountOtpModal.vue +4 -9
  81. package/components/modals/id/IdLoginModal.vue +3 -8
  82. package/components/modals/id/IdModals.vue +23 -10
  83. package/components/modals/id/IdRecoveryModal.vue +1 -1
  84. package/components/modals/id/IdTwoFactorModal.vue +3 -8
  85. package/components/navigation/bottom-navigation/ABottomNavigation.vue +1 -1
  86. package/components/navigation/breadcrumbs/ABreadcrumbs.vue +106 -45
  87. package/components/navigation/breadcrumbs/types.ts +3 -0
  88. package/components/navigation/footer/AFooter.vue +39 -40
  89. package/components/navigation/footer/ui/a-footer-accordion.vue +9 -9
  90. package/components/navigation/header/AHeader.vue +68 -51
  91. package/components/navigation/header/AlmatyContacts.vue +78 -78
  92. package/components/navigation/header/CardGallery.vue +9 -7
  93. package/components/navigation/header/ContactMenu.vue +26 -92
  94. package/components/navigation/header/HeaderLink.vue +181 -213
  95. package/components/navigation/header/HeaderUsage.vue +139 -0
  96. package/components/navigation/header/ListItem.vue +2 -2
  97. package/components/navigation/header/NavCard.vue +1 -1
  98. package/components/navigation/header/NavList.vue +33 -92
  99. package/components/navigation/header/NotificationsMenu.vue +2 -3
  100. package/components/navigation/header/ProductMenu.vue +81 -127
  101. package/components/navigation/header/ProfileMenu.vue +122 -151
  102. package/components/navigation/header/SystemNotification.vue +108 -0
  103. package/components/navigation/mobile-navigation/AMobileNavigation.vue +23 -15
  104. package/components/navigation/pill-tabs/APillTabs.vue +2 -2
  105. package/components/navigation/pill-tabs/APillTabsV2.vue +226 -0
  106. package/components/overlays/drawer/ADrawerV2.vue +335 -0
  107. package/components/overlays/modal/AModal.vue +4 -4
  108. package/components/overlays/modal/AModalV2.vue +397 -0
  109. package/components/overlays/side-panel/ASidePanel.vue +10 -7
  110. package/components/overlays/sideover/ASlideover.vue +6 -6
  111. package/components/overlays/tooltip/ATooltip.vue +1 -1
  112. package/components/overlays/tooltip/ATooltipV2.vue +223 -0
  113. package/components/overlays/tooltip/types.ts +26 -0
  114. package/components/overlays/tooltip/useTooltipTrigger.ts +101 -0
  115. package/components/transitions/ATransitionHeight.vue +1 -1
  116. package/composables/useAccessTokenCookie.ts +25 -0
  117. package/composables/useActiveNavigation.ts +84 -0
  118. package/composables/useChipOverflow.ts +92 -0
  119. package/composables/useHeaderNavigationLinks.ts +140 -98
  120. package/icons/archive.vue +16 -0
  121. package/icons/arrow/arrow-left-right.vue +8 -0
  122. package/icons/arrow/arrow-right.vue +14 -0
  123. package/icons/arrow/arrows-in.vue +11 -0
  124. package/icons/arrow/arrows-out.vue +11 -0
  125. package/icons/arrow/arrows-resize-in.vue +38 -0
  126. package/icons/arrow/arrows-resize-out.vue +17 -0
  127. package/icons/bill.vue +3 -0
  128. package/icons/bot.vue +52 -0
  129. package/icons/briefcase.vue +14 -0
  130. package/icons/building.vue +9 -0
  131. package/icons/car-side.vue +16 -0
  132. package/icons/cart.vue +15 -0
  133. package/icons/chart-no-axes-column.vue +3 -0
  134. package/icons/clock-time.vue +24 -0
  135. package/icons/cloud/cloud-upload.vue +3 -0
  136. package/icons/code.vue +9 -0
  137. package/icons/compare-outlined.vue +8 -0
  138. package/icons/dots-horizontal.vue +31 -0
  139. package/icons/download-line.vue +13 -0
  140. package/icons/download-rounded.vue +15 -0
  141. package/icons/egov.vue +41 -0
  142. package/icons/elicense.vue +38 -0
  143. package/icons/favourite-bookmark-outline.vue +17 -0
  144. package/icons/file/file-check-corner.vue +3 -0
  145. package/icons/file/file-spreadsheet.vue +3 -0
  146. package/icons/file-pen.vue +31 -0
  147. package/icons/file-stack.vue +31 -0
  148. package/icons/file-types/excel.vue +131 -0
  149. package/icons/file-types/html.vue +13 -0
  150. package/icons/file-types/jpeg.vue +16 -0
  151. package/icons/file-types/pdf.vue +11 -0
  152. package/icons/file-types/png.vue +11 -0
  153. package/icons/file-types/rar.vue +11 -0
  154. package/icons/file-types/word.vue +50 -0
  155. package/icons/file-types/zip.vue +11 -0
  156. package/icons/gauge.vue +17 -0
  157. package/icons/git-compare.vue +38 -0
  158. package/icons/git-fork.vue +3 -0
  159. package/icons/graduation-cap.vue +31 -0
  160. package/icons/graph-loading.vue +34 -0
  161. package/icons/headset.vue +6 -0
  162. package/icons/help-question-circle.vue +26 -0
  163. package/icons/in-row-type.vue +46 -0
  164. package/icons/in-table-type.vue +29 -0
  165. package/icons/landmark.vue +3 -0
  166. package/icons/layers.vue +3 -0
  167. package/icons/letter/letter-a.vue +6 -0
  168. package/icons/letter/letter-b.vue +6 -0
  169. package/icons/letter/letter-c.vue +6 -0
  170. package/icons/letter/letter-d.vue +6 -0
  171. package/icons/letter/letter-e.vue +6 -0
  172. package/icons/link-url.vue +3 -0
  173. package/icons/list-tree.vue +3 -0
  174. package/icons/mail-question-mark.vue +8 -0
  175. package/icons/message/message-square.vue +3 -0
  176. package/icons/message-dotted.vue +30 -0
  177. package/icons/network.vue +3 -0
  178. package/icons/newspaper.vue +3 -0
  179. package/icons/notification-error.vue +17 -0
  180. package/icons/person.vue +22 -0
  181. package/icons/phone-call.vue +6 -0
  182. package/icons/phone-reversed.vue +3 -0
  183. package/icons/route.vue +7 -0
  184. package/icons/scan-search.vue +3 -0
  185. package/icons/settings.vue +26 -0
  186. package/icons/shield-alert.vue +31 -0
  187. package/icons/shield-verified.vue +14 -0
  188. package/icons/socials/facebook-color.vue +16 -0
  189. package/icons/socials/globe-color.vue +16 -0
  190. package/icons/socials/instagram-color.vue +46 -0
  191. package/icons/socials/linkedin-color.vue +14 -0
  192. package/icons/socials/telegram-color.vue +16 -0
  193. package/icons/socials/tiktok-color.vue +14 -0
  194. package/icons/socials/vk-color.vue +16 -0
  195. package/icons/socials/youtube-color.vue +16 -0
  196. package/icons/sparkles.vue +3 -0
  197. package/icons/sun.vue +13 -3
  198. package/icons/users-round.vue +3 -0
  199. package/icons/view-eye-open.vue +13 -0
  200. package/icons/workflow.vue +3 -0
  201. package/icons/zap.vue +3 -0
  202. package/lang/en.ts +49 -5
  203. package/lang/kk.ts +50 -6
  204. package/lang/ru.ts +51 -7
  205. package/package.json +1 -1
  206. package/plugins/toast.client.ts +38 -55
  207. package/shared/constans/pages.ts +8 -2
  208. package/tailwind.config.ts +147 -116
  209. package/utils/twMerge.ts +34 -0
  210. package/components/elements/tree-select/components/tree-select-nodes.vue +0 -91
  211. package/components/navigation/header/TopHeader.vue +0 -196
@@ -1,286 +1,254 @@
1
1
  <script setup lang="ts">
2
- import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock'
3
-
4
- import { PAGES } from '#adata-ui/shared/constans/pages'
5
- import ProductMenu from '#adata-ui/components/navigation/header/ProductMenu.vue'
6
-
2
+ import type { Component } from 'vue'
7
3
  import ContactMenu from '#adata-ui/components/navigation/header/ContactMenu.vue'
8
- import { buildLocalizedUrl } from '#adata-ui/utils/localizedNavigation'
4
+ import ProductMenu from '#adata-ui/components/navigation/header/ProductMenu.vue'
9
5
  import { useUrls } from '#adata-ui/composables/useUrls'
6
+ import { PAGES } from '#adata-ui/shared/constans/pages'
7
+ import { buildLocalizedUrl, navigateToLocalizedPage } from '#adata-ui/utils/localizedNavigation'
8
+ import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock'
10
9
 
11
- const { landing, pk } = useUrls()
10
+ withDefaults(defineProps<{ menuTop?: number }>(), {
11
+ menuTop: 64,
12
+ })
13
+
14
+ const { landing } = useUrls()
12
15
  const { t, locale } = useI18n()
13
16
 
14
- const navs: any = [
15
- {
16
- label: 'header.navs.products',
17
- to: landing + PAGES.totalServices,
18
- data_attribute: 'header-products-and-solutions-button',
19
- },
20
- {
21
- label: 'header.navs.contacts',
22
- to: landing + PAGES.contacts,
23
- data_attribute: 'header-contacts-button',
24
- },
25
- {
26
- label: 'header.navs.tariffs',
27
- to: landing + PAGES.tariffs,
28
- data_attribute: 'header-tariffs-button',
29
- },
30
- // {
31
- // label: 'header.navs.api',
32
- // to: landing + PAGES.api
33
- // },
17
+ interface HeaderNav {
18
+ label: string
19
+ dataAttribute: string
20
+ menu?: Component
21
+ }
22
+
23
+ const navs: HeaderNav[] = [
24
+ { label: 'header.navs.products', dataAttribute: 'header-products-and-solutions-button', menu: ProductMenu },
25
+ { label: 'header.navs.contacts', dataAttribute: 'header-contacts-button' },
26
+ { label: 'header.navs.tariffs', dataAttribute: 'header-tariffs-button' },
34
27
  ]
35
28
 
36
- // const pageUrl = useRequestURL()
37
- // if (pageUrl.hostname.startsWith('pk')) {
38
- // navs.push(
39
- // {
40
- // label: 'header.navs.services',
41
- // to: pk + PAGES.services + '/kgd'
42
- // }
43
- // )
44
- // }
29
+ const OPEN_DELAY = 500
30
+ const SWITCH_DELAY = 80
45
31
 
46
32
  const menu = ref<HTMLElement | null>(null)
47
- const isBodyAlreadyLocked = ref<boolean>(false)
48
- const currentIndex = ref(0)
49
- const forward = ref(true)
50
33
  const show = ref(false)
51
- const isMenuLocked = ref(false)
52
- const animation = ref('slide-fade')
53
- const currentMenu = shallowRef(null)
54
- const delayTimer = ref<ReturnType<typeof setTimeout> | null>(null)
34
+ const locked = ref(false)
35
+ const currentIndex = ref(0)
36
+ const swapName = ref('swap-fade')
37
+ const hoverTimer = ref<ReturnType<typeof setTimeout> | null>(null)
55
38
 
56
- const currentMenuToShow = (index) => {
57
- switch (index) {
58
- case 0:
59
- return ProductMenu
60
- case 1:
61
- return ContactMenu
62
- case 2:
63
- return null
64
- }
39
+ const currentMenu = computed(() => navs[currentIndex.value]?.menu ?? null)
40
+
41
+ function isActive(index: number) {
42
+ return show.value && currentIndex.value === index
65
43
  }
66
- const toggleMenu = (index: number) => {
67
- if (index === 2) {
68
- const pageUrl = useRequestURL()
69
44
 
70
- let code = ''
45
+ function openMenu(index: number) {
46
+ swapName.value = show.value && index !== currentIndex.value
47
+ ? (index > currentIndex.value ? 'slide-next' : 'slide-prev')
48
+ : 'swap-fade'
49
+ currentIndex.value = index
50
+ show.value = true
51
+ }
71
52
 
72
- if (pageUrl.hostname.includes('pk')) code = '?code=pk'
73
- if (pageUrl.hostname.includes('avto')) code = '?code=auto'
74
- if (pageUrl.hostname.includes('tnved')) code = '?code=ved'
75
- if (pageUrl.hostname.includes('analytics')) code = '?code=analytics'
76
- if (pageUrl.hostname.includes('ac')) code = '?code=compliance'
77
- if (pageUrl.hostname.includes('zakupki')) code = '?code=procurement'
78
- if (pageUrl.hostname.includes('edo')) code = '?code=edo'
53
+ function closeMenu() {
54
+ show.value = false
55
+ locked.value = false
56
+ }
79
57
 
80
- navigateToLocalizedPage({ locale, projectUrl: landing, path: PAGES.tariffs + code, target: '_blank' })
81
- }
82
- // else if (index === 3) {
83
- // navigateToLocalizedPage({ locale, projectUrl: landing, path: PAGES.api, target: '_blank' })
84
- // }
85
- // else if (index === 4) {
86
- // navigateToLocalizedPage({ locale, projectUrl: pk, path: PAGES.services + '/kgd', target: '_blank' })
87
- // }
88
- else {
89
- currentMenu.value = currentMenuToShow(index)
90
- const prev = currentIndex.value
91
- if (prev < index) {
92
- forward.value = false
93
- } else if (prev > index) {
94
- forward.value = true
95
- }
96
- animation.value = forward.value ? ' prev' : 'next'
97
- if (!show.value) animation.value = 'slide-fade'
98
- currentIndex.value = index
99
- show.value = true
100
- }
58
+ function goToContacts() {
59
+ navigateToLocalizedPage({ locale, projectUrl: landing, path: PAGES.contacts, target: '_blank' })
101
60
  }
102
- const closeMenu = (forceClose = false) => {
103
- if (isMenuLocked.value && !forceClose) {
104
- return
105
- } else {
106
- animation.value = 'slide-fade'
107
- show.value = false
108
61
 
109
- setTimeout(() => {
110
- isMenuLocked.value = false
111
- currentMenu.value = null
112
- }, 200)
62
+ function goToTariffs() {
63
+ const host = useRequestURL().hostname
64
+ const codeMap: Record<string, string> = {
65
+ pk: 'pk',
66
+ avto: 'auto',
67
+ tnved: 'ved',
68
+ analytics: 'analytics',
69
+ ac: 'compliance',
70
+ zakupki: 'procurement',
71
+ edo: 'edo',
113
72
  }
73
+ const key = Object.keys(codeMap).find(item => host.includes(item))
74
+ const code = key ? `?code=${codeMap[key]}` : ''
75
+ navigateToLocalizedPage({ locale, projectUrl: landing, path: PAGES.tariffs + code, target: '_blank' })
114
76
  }
115
- const onMenuMouseEnter = (index: number) => {
116
- if (![2, 3, 4].includes(index)) {
117
- if (show.value === false) {
118
- delayTimer.value = setTimeout(() => {
119
- toggleMenu(index)
120
- }, 600)
121
- } else {
122
- delayTimer.value = setTimeout(() => {
123
- toggleMenu(index)
124
- }, 100)
77
+
78
+ function toggleMenu(index: number) {
79
+ const nav = navs[index]
80
+ if (!nav?.menu) {
81
+ if (index === 1) {
82
+ goToContacts()
125
83
  }
126
- } else show.value = false
127
- }
128
- const onMenuMouseLeave = () => {
129
- if (delayTimer.value) {
130
- clearTimeout(delayTimer.value)
131
- delayTimer.value = null
84
+ else if (index === 2) {
85
+ goToTariffs()
86
+ }
87
+ return
132
88
  }
133
- }
134
- const lockMenu = () => {
135
- isMenuLocked.value = true
136
- }
137
- const toggleBodyScroll = (el: HTMLBodyElement | HTMLDivElement, lock: boolean) => {
138
- if (lock) {
139
- setTimeout(() => {
140
- disableBodyScroll(el, { reserveScrollBarGap: true })
141
- }, 0)
89
+ if (show.value && currentIndex.value === index) {
90
+ closeMenu()
142
91
  return
143
92
  }
144
- enableBodyScroll(el)
93
+ openMenu(index)
94
+ locked.value = true
145
95
  }
146
96
 
147
- onBeforeUnmount(() => {
148
- if (show.value) toggleBodyScroll(menu.value, false)
149
- })
150
-
151
- watch(
152
- () => [show.value, menu.value],
153
- (newVal, oldVal) => {
154
- const wasShown = oldVal ? oldVal[0] : false
155
- const [isShown, menuEl] = newVal
156
- const isOpening = isShown
157
- const isClosing = wasShown && !isShown
158
-
159
- if (!menuEl) return
160
- if (isOpening) isBodyAlreadyLocked.value = !!document.body.style.overflow
97
+ function clearHoverTimer() {
98
+ if (hoverTimer.value) {
99
+ clearTimeout(hoverTimer.value)
100
+ hoverTimer.value = null
101
+ }
102
+ }
161
103
 
162
- if (isShown) {
163
- toggleBodyScroll(menuEl, true)
164
- return
165
- }
104
+ function scheduleOpen(index: number) {
105
+ clearHoverTimer()
106
+ hoverTimer.value = setTimeout(() => {
107
+ if (navs[index]?.menu) openMenu(index)
108
+ else if (show.value && !locked.value) closeMenu()
109
+ }, show.value ? SWITCH_DELAY : OPEN_DELAY)
110
+ }
166
111
 
167
- if (!isClosing || isBodyAlreadyLocked.value) return
112
+ function onOverlayHover() {
113
+ if (!locked.value) closeMenu()
114
+ }
168
115
 
169
- setTimeout(() => {
170
- if (menuEl) toggleBodyScroll(menuEl, false)
171
- }, 100)
116
+ watch(show, async (open) => {
117
+ if (open) {
118
+ await nextTick()
119
+ if (menu.value) disableBodyScroll(menu.value, { reserveScrollBarGap: false })
172
120
  }
173
- )
121
+ else if (menu.value) {
122
+ enableBodyScroll(menu.value)
123
+ }
124
+ })
125
+
126
+ onBeforeUnmount(() => {
127
+ clearHoverTimer()
128
+ if (menu.value) enableBodyScroll(menu.value)
129
+ })
174
130
  </script>
175
131
 
176
132
  <template>
177
133
  <div class="relative">
178
- <ul>
134
+ <ul class="flex items-center">
179
135
  <li
180
136
  v-for="(nav, index) in navs"
181
137
  :key="nav.label"
182
- class="group inline-block border-r border-[#2C3E501A] px-2 last-of-type:border-none dark:border-[#E3E5E81A]"
183
- :data-test-id="nav.data_attribute"
184
- @click="lockMenu"
138
+ class="relative after:absolute after:right-0 after:top-1/2 after:h-6 after:w-px after:-translate-y-1/2 after:bg-gray-300 last:after:hidden dark:after:bg-gray-700"
139
+ :data-test-id="nav.dataAttribute"
185
140
  role="listitem"
186
141
  >
187
- <div
188
- class="relative cursor-pointer font-bold uppercase text-gray-800 hover:text-gray-600 dark:text-gray-200 hover:dark:text-gray-600"
189
- @mouseenter="onMenuMouseEnter(index)"
142
+ <button
143
+ type="button"
144
+ class="group relative cursor-pointer px-3 py-2 font-bold uppercase tracking-tight transition-colors duration-150"
145
+ :class="isActive(index)
146
+ ? 'text-blue-700 dark:text-blue-400'
147
+ : 'text-deepblue-900 hover:text-blue-700 dark:text-gray-200 dark:hover:text-blue-400'"
148
+ @mouseenter="scheduleOpen(index)"
149
+ @mouseleave="clearHoverTimer"
190
150
  @click="toggleMenu(index)"
191
- @mouseleave="onMenuMouseLeave"
192
151
  >
193
152
  {{ t(nav.label) }}
194
- </div>
153
+ <span
154
+ class="absolute inset-x-3 -bottom-0.5 h-0.5 origin-left rounded-full bg-blue-600 transition-transform duration-200 dark:bg-blue-400"
155
+ :class="isActive(index) ? 'scale-x-100' : 'scale-x-0 group-hover:scale-x-100'"
156
+ />
157
+ </button>
195
158
  </li>
196
159
  </ul>
197
- <div
198
- v-show="show"
199
- ref="menu"
200
- class="top-[104px] fixed left-0 z-20 h-full w-full"
201
- >
202
- <div class="h-full w-full">
203
- <transition :name="animation">
204
- <keep-alive>
205
- <component
206
- :is="currentMenu"
207
- v-show="show"
208
- :animation="forward ? 'next' : 'prev'"
209
- :index="currentIndex"
210
- :url="buildLocalizedUrl(locale, landing, PAGES.contacts)"
211
- @outer-click="closeMenu(true)"
212
- @mouse-over="closeMenu"
213
- />
214
- </keep-alive>
215
- </transition>
160
+
161
+ <transition name="menu">
162
+ <div
163
+ v-if="show"
164
+ ref="menu"
165
+ class="fixed inset-x-0 z-20"
166
+ :style="{ top: `${menuTop}px`, height: `calc(100dvh - ${menuTop}px)` }"
167
+ >
216
168
  <div
217
- v-show="show"
218
- class="absolute left-[-25%] h-full w-[150%] bg-[#E3E5E833] backdrop-blur transition"
219
- @click="closeMenu(true)"
220
- @mouseover="closeMenu(false)"
169
+ class="absolute inset-0 bg-gray-900/30 backdrop-blur dark:bg-black/50"
170
+ @click="closeMenu"
171
+ @mouseover="onOverlayHover"
221
172
  />
173
+
174
+ <transition
175
+ :name="swapName"
176
+ mode="out-in"
177
+ >
178
+ <component
179
+ :is="currentMenu"
180
+ :key="currentIndex"
181
+ class="relative"
182
+ />
183
+ </transition>
222
184
  </div>
223
- </div>
185
+ </transition>
224
186
  </div>
225
187
  </template>
226
188
 
227
189
  <style scoped lang="scss">
228
- $percent: 30%;
229
- .next-enter-from,
230
- .next-leave-to {
231
- opacity: 0;
232
- transform: translateX($percent);
233
- position: absolute;
234
- }
190
+ $ease: cubic-bezier(0.22, 1, 0.36, 1);
191
+ $shift: 24px;
235
192
 
236
- .next-enter-active {
237
- transition: all 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
193
+ .menu-enter-active,
194
+ .menu-leave-active {
195
+ transition:
196
+ opacity 0.25s $ease,
197
+ transform 0.25s $ease;
238
198
  }
239
199
 
240
- .next-leave-active {
241
- transition: all 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
200
+ .menu-enter-from,
201
+ .menu-leave-to {
242
202
  opacity: 0;
243
- transform: translateX(-$percent);
203
+ transform: translateY(-12px);
244
204
  }
245
205
 
246
- // Backwards transition
247
-
248
- .prev-enter-from,
249
- .prev-leave-to {
250
- position: absolute;
251
-
252
- opacity: 0;
253
- transform: translateX(-$percent);
206
+ .slide-next-enter-active,
207
+ .slide-next-leave-active,
208
+ .slide-prev-enter-active,
209
+ .slide-prev-leave-active,
210
+ .swap-fade-enter-active,
211
+ .swap-fade-leave-active {
212
+ transition:
213
+ opacity 0.2s $ease,
214
+ transform 0.2s $ease;
254
215
  }
255
216
 
256
- .prev-enter-active {
257
- transition: all 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
217
+ .slide-next-enter-from {
218
+ opacity: 0;
219
+ transform: translateX($shift);
258
220
  }
259
221
 
260
- .prev-leave-active {
261
- transition: all 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
222
+ .slide-next-leave-to {
262
223
  opacity: 0;
263
- transform: translateX($percent);
224
+ transform: translateX(-$shift);
264
225
  }
265
226
 
266
- .slide-fade-enter-active,
267
- .slide-fade-leave-active {
268
- transition: all 0.2s ease-in-out;
227
+ .slide-prev-enter-from {
228
+ opacity: 0;
229
+ transform: translateX(-$shift);
269
230
  }
270
231
 
271
- .slide-fade-enter-from,
272
- .slide-fade-leave-to {
273
- transform: translateY(-12px);
232
+ .slide-prev-leave-to {
274
233
  opacity: 0;
234
+ transform: translateX($shift);
275
235
  }
276
236
 
277
- .slide-change-enter-active,
278
- .slide-change-leave-active {
279
- transition: all 0.2s ease-in-out;
237
+ .swap-fade-enter-from,
238
+ .swap-fade-leave-to {
239
+ opacity: 0;
280
240
  }
281
241
 
282
- .slide-change-enter-from,
283
- .slide-change-leave-to {
284
- opacity: 0;
242
+ @media (prefers-reduced-motion: reduce) {
243
+ .menu-enter-active,
244
+ .menu-leave-active,
245
+ .slide-next-enter-active,
246
+ .slide-next-leave-active,
247
+ .slide-prev-enter-active,
248
+ .slide-prev-leave-active,
249
+ .swap-fade-enter-active,
250
+ .swap-fade-leave-active {
251
+ transition: none;
252
+ }
285
253
  }
286
254
  </style>
@@ -0,0 +1,139 @@
1
+ <script setup lang="ts">
2
+ import Calendar from '#adata-ui/icons/calendar.vue'
3
+ import Gauge from '#adata-ui/icons/gauge.vue'
4
+
5
+ defineOptions({ name: 'HeaderUsage' })
6
+
7
+ const props = withDefaults(defineProps<Props>(), {
8
+ limitRemaining: 0,
9
+ maxLimit: 300,
10
+ daysRemaining: 0,
11
+ })
12
+
13
+ interface Props {
14
+ limitRemaining?: number
15
+ maxLimit?: number
16
+ daysRemaining?: number
17
+ }
18
+
19
+ const percent = computed(() => {
20
+ if (props.maxLimit <= 0) return 0
21
+ const ratio = (props.limitRemaining / props.maxLimit) * 100
22
+ return Math.min(100, Math.max(0, ratio))
23
+ })
24
+
25
+ const displayPercent = ref(0)
26
+ onMounted(() => {
27
+ displayPercent.value = percent.value
28
+ })
29
+ watch(percent, (value) => {
30
+ displayPercent.value = value
31
+ })
32
+
33
+ const usageLevel = computed<'normal' | 'warning' | 'critical'>(() => {
34
+ if (percent.value < 20) return 'critical'
35
+ if (percent.value < 40) return 'warning'
36
+ return 'normal'
37
+ })
38
+
39
+ const barColorClass = computed(() => {
40
+ switch (usageLevel.value) {
41
+ case 'critical':
42
+ return 'bg-red-500 dark:bg-red-400'
43
+ case 'warning':
44
+ return 'bg-yellow-500 dark:bg-yellow-400'
45
+ default:
46
+ return 'bg-blue-600 dark:bg-blue-500'
47
+ }
48
+ })
49
+
50
+ const valueColorClass = computed(() => {
51
+ switch (usageLevel.value) {
52
+ case 'critical':
53
+ return 'text-red-600 dark:text-red-400'
54
+ case 'warning':
55
+ return 'text-yellow-600 dark:text-yellow-400'
56
+ default:
57
+ return 'text-gray-900 dark:text-gray-100'
58
+ }
59
+ })
60
+ </script>
61
+
62
+ <template>
63
+ <client-only>
64
+ <div
65
+ class="inline-flex items-center gap-2.5"
66
+ data-test-id="header-usage"
67
+ >
68
+ <a-tooltip-v2
69
+ class="shrink-0 items-center gap-2.5"
70
+ placement="bottom"
71
+ arrow
72
+ :z-index="10001"
73
+ content-class="py-0.5"
74
+ >
75
+ <gauge class="size-4 shrink-0 text-gray-600 dark:text-gray-300" />
76
+
77
+ <div class="h-1.5 w-20 rounded-full bg-deepblue-900/30 dark:bg-white/30">
78
+ <div
79
+ class="usage-bar h-full rounded-full"
80
+ :class="barColorClass"
81
+ :style="{ width: `${displayPercent}%` }"
82
+ />
83
+ </div>
84
+
85
+ <span
86
+ class="text-body-md tabular-nums leading-none"
87
+ data-test-id="header-usage-requests"
88
+ >
89
+ <span
90
+ class="font-bold transition-colors duration-300"
91
+ :class="valueColorClass"
92
+ >{{ limitRemaining }}</span><span class="font-medium text-gray-600 dark:text-gray-300">/{{ maxLimit }}</span>
93
+ </span>
94
+
95
+ <template #content>
96
+ <span class="whitespace-nowrap text-body-sm leading-none text-deepblue-900 dark:text-gray-100">
97
+ Лимит запросов: {{ limitRemaining }}
98
+ </span>
99
+ </template>
100
+ </a-tooltip-v2>
101
+
102
+ <span class="h-4 w-px shrink-0 bg-deepblue-900/15 dark:bg-white/15" />
103
+
104
+ <a-tooltip-v2
105
+ class="shrink-0"
106
+ placement="bottom"
107
+ arrow
108
+ :z-index="10001"
109
+ content-class="py-0.5"
110
+ >
111
+ <div
112
+ class="flex shrink-0 items-center gap-1.5"
113
+ data-test-id="header-usage-days"
114
+ >
115
+ <calendar class="size-4 shrink-0 text-gray-600 dark:text-gray-300" />
116
+ <span class="text-body-md font-bold tabular-nums text-gray-900 dark:text-gray-100">{{ daysRemaining }}</span>
117
+ </div>
118
+
119
+ <template #content>
120
+ <span class="whitespace-nowrap text-body-sm leading-none text-deepblue-900 dark:text-gray-100">
121
+ Остаток дней: {{ daysRemaining }}
122
+ </span>
123
+ </template>
124
+ </a-tooltip-v2>
125
+ </div>
126
+ </client-only>
127
+ </template>
128
+
129
+ <style scoped>
130
+ .usage-bar {
131
+ transition: width 700ms cubic-bezier(0.22, 1, 0.36, 1);
132
+ }
133
+
134
+ @media (prefers-reduced-motion: reduce) {
135
+ .usage-bar {
136
+ transition: none;
137
+ }
138
+ }
139
+ </style>
@@ -4,9 +4,9 @@
4
4
  @mouseenter="hover = true"
5
5
  @mouseleave="hover = false"
6
6
  :class="{'opacity-30' : !hover && someHovered}"
7
- class="flex items-center gap-4 px-2.5 py-2 hover:bg-gray-50 rounded-lg dark:hover:bg-gray-800 normal-case transition-all "
7
+ class="flex items-center gap-4 px-2.5 py-2 hover:bg-gray-50 rounded-md dark:hover:bg-gray-800 normal-case transition-all "
8
8
  >
9
- <div class="min-w-8 h-8 rounded-lg flex items-center justify-center dark:text-gray-900 text-white bg-gradient-blue">
9
+ <div class="min-w-8 h-8 rounded-md flex items-center justify-center dark:text-gray-900 text-white bg-gradient-blue">
10
10
  <component
11
11
  :is="item.icon"
12
12
  class="h-5 w-5"
@@ -29,7 +29,7 @@ const hover = ref(false)
29
29
  </p>
30
30
  </div>
31
31
  <div
32
- class="flex items-center justify-center self-end rounded-lg bg-gradient-blue dark:text-gray-900 text-white"
32
+ class="flex items-center justify-center self-end rounded-md bg-gradient-blue dark:text-gray-900 text-white"
33
33
  :class="{'h-8 min-w-8' : size==='sm', 'h-16 min-w-16' : size==='lg'}"
34
34
  >
35
35
  <component