adata-ui 2.1.41-beta.2 → 2.1.41-beta.20

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 (627) hide show
  1. package/-error.vue +170 -170
  2. package/.editorconfig +12 -12
  3. package/.gitlab-ci.yml +16 -16
  4. package/.nuxtrc +1 -1
  5. package/.nvmrc +1 -1
  6. package/.playground/app.config.ts +5 -5
  7. package/.playground/app.vue +102 -102
  8. package/.playground/nuxt.config.ts +4 -4
  9. package/CLAUDE.md +63 -63
  10. package/README.md +75 -75
  11. package/app.config.ts +28 -28
  12. package/app.vue +8 -8
  13. package/assets/styles/main.css +12 -12
  14. package/assets/styles/theme.css +590 -583
  15. package/assets/styles/ui.css +247 -247
  16. package/components/elements/README.md +1 -1
  17. package/components/elements/a-select-row/ASelectRowV2.vue +210 -210
  18. package/components/elements/a-select-row/index.vue +139 -139
  19. package/components/elements/accordion/AAccordion.vue +199 -199
  20. package/components/elements/accordion/AAccordionGroup/AAccordionGroup.vue +145 -145
  21. package/components/elements/accordion/AAccordionGroup/types.ts +20 -20
  22. package/components/elements/accordion/AAccordionGroup/ui.config.ts +22 -22
  23. package/components/elements/accordion/types.ts +14 -14
  24. package/components/elements/accordion/ui.config.ts +22 -22
  25. package/components/elements/alert/AAlert.vue +52 -52
  26. package/components/elements/banner/ABanner.vue +67 -49
  27. package/components/elements/bg-block/ABgBlock.vue +11 -11
  28. package/components/elements/button/AButton.vue +191 -191
  29. package/components/elements/button/AButtonV2.vue +179 -179
  30. package/components/elements/button/types.ts +24 -24
  31. package/components/elements/button-login/index.vue +19 -19
  32. package/components/elements/calendar/ACalendar.vue +83 -83
  33. package/components/elements/carousel/ACarousel.vue +159 -159
  34. package/components/elements/carousel/config.ui.ts +15 -15
  35. package/components/elements/carousel/types.ts +18 -18
  36. package/components/elements/chip-wrapper/AChipWrapper.vue +81 -81
  37. package/components/elements/chip-wrapper/types.ts +12 -12
  38. package/components/elements/chips/AChips.vue +33 -33
  39. package/components/elements/companies/AOurClients.vue +47 -47
  40. package/components/elements/count-up/ACountUp.vue +54 -54
  41. package/components/elements/curve-block/ACurveBlock.vue +259 -259
  42. package/components/elements/curve-block-v2/ACurveBlockV2.vue +122 -122
  43. package/components/elements/digit-badge/ADigitBadge.vue +86 -86
  44. package/components/elements/empty-box/empty-box-V2.vue +92 -92
  45. package/components/elements/error-template/index.vue +121 -116
  46. package/components/elements/feature-description/AFeatureDescription.vue +112 -112
  47. package/components/elements/illustrations/noAccess.vue +71 -71
  48. package/components/elements/infinite-carousel/AInfiniteCarousel.vue +85 -85
  49. package/components/elements/infinite-scroll/AInfiniteScroll.vue +33 -33
  50. package/components/elements/leave-note/ALeaveNote.vue +102 -102
  51. package/components/elements/pagination/APagination.vue +110 -110
  52. package/components/elements/photos-animation/APhotosAnimation.vue +85 -85
  53. package/components/elements/section-heading/ASectionHeading.vue +35 -35
  54. package/components/elements/segmented/ASegmentedV2.vue +154 -154
  55. package/components/elements/select/ACascadeSelectV2.vue +710 -0
  56. package/components/elements/select/AGroupSelectV2.vue +592 -0
  57. package/components/elements/select/ASelect.vue +448 -448
  58. package/components/elements/select/ASelectMobile.vue +186 -186
  59. package/components/elements/select/ASelectV2.vue +513 -580
  60. package/components/elements/select/cascade-utils.ts +115 -0
  61. package/components/elements/select/types.ts +27 -0
  62. package/components/elements/select/ui/cascade-drawer.vue +146 -0
  63. package/components/elements/select/ui/cascade-panel.vue +237 -0
  64. package/components/elements/select/ui/select-button.vue +112 -112
  65. package/components/elements/select/ui/select-list.vue +99 -99
  66. package/components/elements/select/ui/select-trigger.vue +167 -0
  67. package/components/elements/show-more/AShowMore.vue +35 -35
  68. package/components/elements/show-more/AShowMoreV2.vue +26 -26
  69. package/components/elements/skeleton/ASkeleton.vue +44 -44
  70. package/components/elements/slider/ASlider.vue +175 -175
  71. package/components/elements/slider/ASliderItem.vue +31 -31
  72. package/components/elements/slider/ASliderV2.vue +182 -182
  73. package/components/elements/slider/types.ts +21 -21
  74. package/components/elements/star-rating/AStarRating.vue +189 -189
  75. package/components/elements/star-rating/types.ts +7 -7
  76. package/components/elements/star-rating/ui/Star.vue +74 -74
  77. package/components/elements/status-badge/AStatusBadge.vue +160 -125
  78. package/components/elements/step-slider/AStepSliderColCard.vue +266 -270
  79. package/components/elements/step-slider/AStepSliderColNumber.vue +199 -199
  80. package/components/elements/step-slider/AStepSliderRowCard.vue +197 -0
  81. package/components/elements/table/ATable.vue +93 -93
  82. package/components/elements/table/table.config.ts +32 -32
  83. package/components/elements/tree-select/ATreeSelect.vue +157 -157
  84. package/components/elements/tree-select/TreeService.ts +184 -184
  85. package/components/elements/tree-select/components/ATreeSelectNode.vue +104 -104
  86. package/components/elements/tree-select/types.ts +63 -63
  87. package/components/elements/tree-select/utils.ts +30 -30
  88. package/components/elements/tree-select-mobile/ATreeSelectMobile.vue +79 -79
  89. package/components/elements/tree-select-mobile/components/ATreeSelectNodesMobile.vue +175 -175
  90. package/components/elements/tree-select-modal/ATreeSelectModal.vue +217 -217
  91. package/components/features/color-mode/AColorMode.client.vue +74 -74
  92. package/components/features/dropdown/ADropdown.vue +127 -127
  93. package/components/features/dropdown/ADropdownV2.vue +140 -140
  94. package/components/features/go-top/GoTop.vue +67 -63
  95. package/components/features/lang-switcher/lang-switcher.vue +160 -158
  96. package/components/features/payment/banner/BasicPlusLimitBanner.vue +84 -84
  97. package/components/features/payment/banner/PaymentBanner.vue +88 -88
  98. package/components/features/payment/banner/UpSellBanner.vue +105 -105
  99. package/components/features/payment/process/PaymentKaspiQrSidePanel.vue +180 -180
  100. package/components/features/payment/process/PaymentKaspiRedirectSidePanel.vue +123 -123
  101. package/components/features/payment/process/PaymentMethodSidePanel.vue +138 -138
  102. package/components/features/payment/process/PaymentProcess.vue +162 -158
  103. package/components/features/payment/process/PaymentTopUpSidePanel.vue +134 -119
  104. package/components/features/pk-mobile-services/APkMobileServices.vue +107 -107
  105. package/components/features//321/201hange-version/AChangeVersion.vue +20 -20
  106. package/components/forms/README.md +1 -1
  107. package/components/forms/checkbox/ACheckbox.vue +149 -149
  108. package/components/forms/checkbox/ACheckboxV2.vue +229 -229
  109. package/components/forms/feedback-form/FeedbackForm.vue +177 -177
  110. package/components/forms/feedback-form/FeedbackFormV2.vue +183 -186
  111. package/components/forms/input/AInputV2.vue +580 -560
  112. package/components/forms/input/button/AInputButton.vue +53 -53
  113. package/components/forms/input/date/AInputDate.vue +217 -217
  114. package/components/forms/input/password/AInputPassword.vue +59 -59
  115. package/components/forms/input/password/AInputPasswordV2.vue +72 -0
  116. package/components/forms/input/standard/AInputStandard.vue +424 -424
  117. package/components/forms/input/textarea/ATextarea.vue +207 -207
  118. package/components/forms/input/textarea/ATextareaV2.vue +253 -253
  119. package/components/forms/password/APasswordRequirements.vue +50 -0
  120. package/components/forms/radio-button/ARadioButton.vue +95 -95
  121. package/components/forms/request-demo/ARequestDemo.vue +108 -108
  122. package/components/forms/toggle/AToggle.vue +157 -157
  123. package/components/forms/toggle/AToggleV2.vue +72 -72
  124. package/components/forms/toggle/types.ts +16 -16
  125. package/components/modals/AConfirmationEmail.vue +40 -40
  126. package/components/modals/AboutTariffModal.vue +115 -115
  127. package/components/modals/AnotherDeviceModal.vue +65 -65
  128. package/components/modals/ApplicationAcceptedModal.vue +45 -45
  129. package/components/modals/ConnectingTariffModal.vue +60 -60
  130. package/components/modals/ContactsMobileModel.vue +182 -182
  131. package/components/modals/ContentNavigationModal.vue +100 -100
  132. package/components/modals/Insufficient-funds-modal.vue +62 -62
  133. package/components/modals/LimitReachedModal.vue +44 -44
  134. package/components/modals/NoAccessModal.vue +39 -39
  135. package/components/modals/PaymentMethodModal.vue +101 -101
  136. package/components/modals/RedirectsBanksModal.vue +69 -69
  137. package/components/modals/ReplenishModal.vue +132 -132
  138. package/components/modals/ReportBugConfirmModal.vue +32 -32
  139. package/components/modals/ReportBugModal.vue +172 -172
  140. package/components/modals/Resend.vue +81 -81
  141. package/components/modals/SubmitApplicationModal.vue +126 -126
  142. package/components/modals/id/AuthModal.vue +72 -72
  143. package/components/modals/id/IdAutoLogoutModal.vue +44 -44
  144. package/components/modals/id/IdBanner.vue +41 -41
  145. package/components/modals/id/IdConfirmAccountOtpModal.vue +176 -182
  146. package/components/modals/id/IdConfirmSuccessfulModal.vue +44 -41
  147. package/components/modals/id/IdLoginModal.vue +370 -336
  148. package/components/modals/id/IdModals.vue +171 -125
  149. package/components/modals/id/IdNewPasswordModal.vue +172 -129
  150. package/components/modals/id/IdOtpInput.vue +156 -156
  151. package/components/modals/id/IdPasswordSuccessfulModal.vue +25 -25
  152. package/components/modals/id/IdRecoveryModal.vue +133 -117
  153. package/components/modals/id/IdRegistrationModal.vue +260 -215
  154. package/components/modals/id/IdResetPasswordOtpModal.vue +145 -159
  155. package/components/modals/id/IdTwoFactorModal.vue +111 -126
  156. package/components/navigation/README.md +1 -1
  157. package/components/navigation/bottom-navigation/ABottomNavigation.vue +112 -106
  158. package/components/navigation/bottom-navigation/types.ts +6 -0
  159. package/components/navigation/breadcrumbs/ABreadcrumbs.vue +128 -128
  160. package/components/navigation/breadcrumbs/types.ts +8 -8
  161. package/components/navigation/footer/AFooter.vue +283 -283
  162. package/components/navigation/footer/ui/a-footer-accordion.vue +118 -118
  163. package/components/navigation/header/AHeader.vue +241 -237
  164. package/components/navigation/header/AlmatyContacts.vue +145 -145
  165. package/components/navigation/header/AstanaContacts.vue +67 -67
  166. package/components/navigation/header/CardGallery.vue +252 -252
  167. package/components/navigation/header/ContactMenu.vue +48 -48
  168. package/components/navigation/header/HeaderLink.vue +250 -250
  169. package/components/navigation/header/HeaderUsage.vue +139 -139
  170. package/components/navigation/header/ListItem.vue +32 -32
  171. package/components/navigation/header/NavCard.vue +42 -42
  172. package/components/navigation/header/NavList.vue +73 -73
  173. package/components/navigation/header/NotificationsMenu.vue +50 -50
  174. package/components/navigation/header/ProductMenu.vue +53 -101
  175. package/components/navigation/header/ProfileMenu.vue +168 -168
  176. package/components/navigation/header/SystemNotification.vue +108 -108
  177. package/components/navigation/header/types.ts +28 -28
  178. package/components/navigation/line-tabs/ALineTabs.vue +112 -112
  179. package/components/navigation/line-tabs/types.ts +21 -21
  180. package/components/navigation/mobile-navigation/AMobileNavigation.vue +129 -112
  181. package/components/navigation/pill-tabs/APillTabs.vue +179 -179
  182. package/components/navigation/pill-tabs/APillTabsV2.vue +241 -241
  183. package/components/navigation/pill-tabs/types.ts +24 -24
  184. package/components/navigation/side-navigation/ASideNavigation.vue +406 -406
  185. package/components/overlays/README.md +1 -1
  186. package/components/overlays/drawer/ADrawerV2.vue +342 -335
  187. package/components/overlays/modal/AModal.vue +335 -335
  188. package/components/overlays/modal/AModalV2.vue +401 -400
  189. package/components/overlays/side-panel/ASidePanel.vue +445 -445
  190. package/components/overlays/sideover/ASlideover.vue +351 -351
  191. package/components/overlays/tooltip/ATooltip.vue +238 -238
  192. package/components/overlays/tooltip/ATooltipV2.vue +226 -226
  193. package/components/overlays/tooltip/types.ts +26 -26
  194. package/components/overlays/tooltip/useTooltipTrigger.ts +101 -101
  195. package/components/transitions/ATransitionHeight.vue +67 -67
  196. package/components/utils/index.ts +19 -19
  197. package/components/utils/removeTrailingSlash.ts +7 -7
  198. package/composables/highlight.ts +19 -19
  199. package/composables/modalsState.ts +8 -8
  200. package/composables/projectState.ts +2 -2
  201. package/composables/useAccessTokenCookie.ts +25 -25
  202. package/composables/useActiveNavigation.ts +84 -84
  203. package/composables/useAdaptive.ts +46 -46
  204. package/composables/useBuyTariffs.ts +91 -91
  205. package/composables/useCarouselScroll.ts +49 -49
  206. package/composables/useChipOverflow.ts +92 -92
  207. package/composables/useHeaderNavigationLinks.ts +535 -519
  208. package/composables/useIdModals.ts +36 -36
  209. package/composables/usePayment.ts +81 -78
  210. package/composables/useUIValidation.ts +16 -16
  211. package/composables/useUrls.ts +23 -23
  212. package/eslint.config.mjs +58 -58
  213. package/i18n/i18n.config.ts +21 -21
  214. package/i18n.config.ts +21 -21
  215. package/icons/adata-logo.vue +17 -17
  216. package/icons/ai/ai-diamond-unfilled.vue +26 -0
  217. package/icons/ai/ai-diamond.vue +11 -0
  218. package/icons/ai-assistant.vue +13 -13
  219. package/icons/akimat.vue +20 -20
  220. package/icons/archive.vue +16 -16
  221. package/icons/arrow/arrow-bottom-left-on-square.vue +5 -5
  222. package/icons/arrow/arrow-circle-down.vue +16 -16
  223. package/icons/arrow/arrow-circle-reset.vue +16 -16
  224. package/icons/arrow/arrow-corner.vue +9 -9
  225. package/icons/arrow/arrow-down-left.vue +6 -0
  226. package/icons/arrow/arrow-graph-down.vue +13 -13
  227. package/icons/arrow/arrow-graph-up.vue +14 -14
  228. package/icons/arrow/arrow-left-right.vue +8 -8
  229. package/icons/arrow/arrow-left.vue +14 -14
  230. package/icons/arrow/arrow-right.vue +14 -14
  231. package/icons/arrow/arrow-square-down.vue +15 -15
  232. package/icons/arrow/arrow-top-right-on-square.vue +6 -6
  233. package/icons/arrow/arrow-up-right.vue +6 -0
  234. package/icons/arrow/arrows-in.vue +11 -11
  235. package/icons/arrow/arrows-out.vue +11 -11
  236. package/icons/arrow/arrows-resize-in.vue +38 -38
  237. package/icons/arrow/arrows-resize-out.vue +17 -17
  238. package/icons/arrow-currency-gray.vue +5 -5
  239. package/icons/arrow-currency-green.vue +5 -5
  240. package/icons/arrow-currency-red.vue +5 -5
  241. package/icons/arrow-side-up.vue +6 -6
  242. package/icons/avatar.vue +12 -12
  243. package/icons/bank.vue +5 -5
  244. package/icons/bar-chart-3.vue +3 -3
  245. package/icons/bill.vue +3 -3
  246. package/icons/block.vue +16 -16
  247. package/icons/book-open.vue +3 -3
  248. package/icons/bookmark/bookmark-filled.vue +19 -19
  249. package/icons/bookmark/bookmark.vue +5 -5
  250. package/icons/bot.vue +52 -52
  251. package/icons/braces.vue +3 -3
  252. package/icons/briefcase.vue +14 -14
  253. package/icons/browsers/browser-duck.vue +65 -65
  254. package/icons/browsers/browser-google.vue +30 -30
  255. package/icons/browsers/browser-yandex.vue +13 -13
  256. package/icons/building-org.vue +15 -0
  257. package/icons/building-vector.vue +7 -7
  258. package/icons/building.vue +9 -9
  259. package/icons/calculator.vue +7 -7
  260. package/icons/calendar.vue +24 -24
  261. package/icons/calendar2.vue +23 -0
  262. package/icons/car-avto.vue +19 -0
  263. package/icons/car-front.vue +21 -0
  264. package/icons/car-side.vue +16 -16
  265. package/icons/car.vue +7 -7
  266. package/icons/cart.vue +15 -15
  267. package/icons/chart-bar.vue +5 -5
  268. package/icons/chart-no-axes-column.vue +3 -3
  269. package/icons/chart-pie-piece.vue +6 -0
  270. package/icons/chart-pie.vue +16 -16
  271. package/icons/check/check-circle.vue +6 -6
  272. package/icons/check/check-outline.vue +3 -3
  273. package/icons/check/check-shield.vue +14 -14
  274. package/icons/check/check.vue +17 -17
  275. package/icons/check/checkmark-circle.vue +6 -6
  276. package/icons/check-sb.vue +7 -7
  277. package/icons/checkbox/checkbox-active.vue +17 -17
  278. package/icons/checkbox/checkbox-empty.vue +10 -10
  279. package/icons/checkbox/checkbox-intermediate.vue +7 -7
  280. package/icons/chevron/chevron-down-outline.vue +3 -3
  281. package/icons/chevron/chevron-down.vue +5 -5
  282. package/icons/chevron/chevron-left.vue +5 -5
  283. package/icons/chevron/chevron-right.vue +5 -5
  284. package/icons/chevron/chevron-up.vue +5 -5
  285. package/icons/chevron/double-chevron-right.vue +12 -12
  286. package/icons/clipboard-check.vue +18 -0
  287. package/icons/clipboard-list.vue +3 -3
  288. package/icons/clipboard-text.vue +7 -7
  289. package/icons/clock-outline.vue +3 -3
  290. package/icons/clock-rotate-ccw.vue +7 -0
  291. package/icons/clock-time.vue +24 -24
  292. package/icons/clock.vue +25 -25
  293. package/icons/close.vue +16 -16
  294. package/icons/cloud/cloud-upload.vue +3 -3
  295. package/icons/code.vue +9 -9
  296. package/icons/cog.vue +29 -0
  297. package/icons/company/company-bazis.vue +21 -21
  298. package/icons/company/company-bereke.vue +25 -25
  299. package/icons/company/company-bigroup.vue +16 -16
  300. package/icons/company/company-erg.vue +17 -17
  301. package/icons/company/company-forte.vue +21 -21
  302. package/icons/company/company-halyk.vue +15 -15
  303. package/icons/company/company-jusan.vue +31 -31
  304. package/icons/company/company-kaspi.vue +14 -14
  305. package/icons/company/company-kazzinc.vue +22 -22
  306. package/icons/company/company-mycar.vue +13 -13
  307. package/icons/company/company-samruk.vue +21 -21
  308. package/icons/company-egov-small.vue +15 -15
  309. package/icons/company.vue +8 -8
  310. package/icons/compare-outlined.vue +8 -8
  311. package/icons/copy.vue +7 -7
  312. package/icons/credit-card.vue +17 -0
  313. package/icons/crown.vue +6 -0
  314. package/icons/currency/currency-dollar.vue +16 -16
  315. package/icons/currency/currency-down.vue +22 -22
  316. package/icons/currency/currency-eur.vue +57 -57
  317. package/icons/currency/currency-rub.vue +7 -7
  318. package/icons/currency/currency-tenge.vue +9 -9
  319. package/icons/currency/currency-usd.vue +221 -221
  320. package/icons/currency/currency-yuan.vue +25 -25
  321. package/icons/database.vue +3 -3
  322. package/icons/default-avatar.vue +20 -20
  323. package/icons/delete.vue +7 -7
  324. package/icons/document.vue +5 -5
  325. package/icons/dots-horizontal.vue +31 -31
  326. package/icons/download-line.vue +13 -13
  327. package/icons/download-outline.vue +3 -3
  328. package/icons/download-rounded.vue +15 -15
  329. package/icons/download.vue +11 -11
  330. package/icons/edit.vue +7 -7
  331. package/icons/education.vue +16 -16
  332. package/icons/egov-small.vue +7 -7
  333. package/icons/egov.vue +41 -41
  334. package/icons/elicense.vue +38 -38
  335. package/icons/excel-icon.vue +14 -14
  336. package/icons/expand-window.vue +7 -7
  337. package/icons/eye-closed.vue +26 -26
  338. package/icons/eye-open.vue +7 -7
  339. package/icons/eye-opened.vue +23 -23
  340. package/icons/factory.vue +7 -0
  341. package/icons/favourite-bookmark-outline.vue +17 -17
  342. package/icons/file/file-check-corner.vue +3 -3
  343. package/icons/file/file-scan.vue +5 -0
  344. package/icons/file/file-spreadsheet.vue +3 -3
  345. package/icons/file/file.vue +16 -16
  346. package/icons/file/files.vue +16 -16
  347. package/icons/file/folder.vue +15 -0
  348. package/icons/file-download.vue +8 -0
  349. package/icons/file-json.vue +3 -3
  350. package/icons/file-pen.vue +31 -31
  351. package/icons/file-search.vue +3 -3
  352. package/icons/file-stack.vue +31 -31
  353. package/icons/file-text.vue +3 -3
  354. package/icons/file-types/excel.vue +131 -131
  355. package/icons/file-types/html.vue +13 -13
  356. package/icons/file-types/jpeg.vue +16 -16
  357. package/icons/file-types/pdf.vue +11 -11
  358. package/icons/file-types/png.vue +11 -11
  359. package/icons/file-types/rar.vue +11 -11
  360. package/icons/file-types/word.vue +50 -50
  361. package/icons/file-types/zip.vue +11 -11
  362. package/icons/filter.vue +5 -5
  363. package/icons/filter2.vue +22 -0
  364. package/icons/flag.vue +7 -7
  365. package/icons/flask-conical.vue +3 -3
  366. package/icons/folder-open.vue +3 -3
  367. package/icons/funnel.vue +5 -0
  368. package/icons/gauge.vue +17 -17
  369. package/icons/gift-outline.vue +3 -3
  370. package/icons/gift.vue +8 -8
  371. package/icons/git-compare.vue +38 -38
  372. package/icons/git-fork.vue +3 -3
  373. package/icons/globe-2.vue +3 -3
  374. package/icons/globe.vue +16 -16
  375. package/icons/google.vue +19 -41
  376. package/icons/gradiend-icons/files-gradient.vue +22 -22
  377. package/icons/gradiend-icons/hammer-gradient.vue +62 -62
  378. package/icons/graduation-cap.vue +31 -31
  379. package/icons/graph-loading.vue +34 -34
  380. package/icons/graph-pie.vue +14 -14
  381. package/icons/hand/hand-thumb-up-filled.vue +5 -5
  382. package/icons/hand/hand-thumb-up.vue +5 -5
  383. package/icons/hand-with-phone/hand-with-phone-dark.vue +52 -52
  384. package/icons/hand-with-phone/hand-with-phone-light.vue +52 -52
  385. package/icons/handshake.vue +5 -5
  386. package/icons/hcheck.vue +14 -14
  387. package/icons/hdocument.vue +7 -7
  388. package/icons/headset.vue +6 -6
  389. package/icons/heart-pulse.vue +6 -0
  390. package/icons/help-question-circle.vue +26 -26
  391. package/icons/history.vue +5 -5
  392. package/icons/home.vue +14 -14
  393. package/icons/horizontal-more.vue +13 -13
  394. package/icons/hot-line.vue +6 -6
  395. package/icons/hummer.vue +16 -16
  396. package/icons/id.vue +7 -7
  397. package/icons/in-row-type.vue +46 -46
  398. package/icons/in-table-type.vue +29 -29
  399. package/icons/info/info-circle.vue +29 -29
  400. package/icons/invoice.vue +8 -8
  401. package/icons/judge.vue +14 -14
  402. package/icons/kaspi-qr.vue +13 -13
  403. package/icons/key-round.vue +3 -3
  404. package/icons/landmark.vue +3 -3
  405. package/icons/layers.vue +3 -3
  406. package/icons/letter/letter-a.vue +8 -8
  407. package/icons/letter/letter-b.vue +6 -6
  408. package/icons/letter/letter-c.vue +6 -6
  409. package/icons/letter/letter-d.vue +6 -6
  410. package/icons/letter/letter-e.vue +6 -6
  411. package/icons/link-chain.vue +28 -28
  412. package/icons/link-url.vue +3 -3
  413. package/icons/link.vue +7 -7
  414. package/icons/linkedin.vue +24 -24
  415. package/icons/list-checks.vue +3 -0
  416. package/icons/list-tree.vue +3 -3
  417. package/icons/loader-circle.vue +27 -27
  418. package/icons/location.vue +8 -8
  419. package/icons/lock.vue +5 -5
  420. package/icons/log-out.vue +21 -0
  421. package/icons/logo.vue +15 -15
  422. package/icons/logout.vue +16 -16
  423. package/icons/magnify/magnifying-glass-minus.vue +8 -8
  424. package/icons/magnify/magnifying-glass-plus.vue +9 -9
  425. package/icons/magnify/magnifying-glass.vue +9 -9
  426. package/icons/mail-question-mark.vue +8 -8
  427. package/icons/mail.vue +7 -7
  428. package/icons/mailru.vue +19 -34
  429. package/icons/main-filter.vue +10 -5
  430. package/icons/map/map-pin-filled.vue +7 -7
  431. package/icons/map/map-pin-rect.vue +15 -15
  432. package/icons/map/map-pin.vue +7 -7
  433. package/icons/map-marker-start.vue +7 -7
  434. package/icons/map-paper.vue +5 -5
  435. package/icons/medal.vue +7 -7
  436. package/icons/menu-filled.vue +20 -20
  437. package/icons/menu.vue +8 -8
  438. package/icons/message/message-square.vue +3 -3
  439. package/icons/message/message.vue +12 -12
  440. package/icons/message-dotted.vue +30 -30
  441. package/icons/messages-square.vue +3 -3
  442. package/icons/minus/minus-circle.vue +10 -10
  443. package/icons/money.vue +5 -5
  444. package/icons/monitoring.vue +10 -10
  445. package/icons/moon.vue +8 -8
  446. package/icons/more.vue +29 -29
  447. package/icons/network.vue +3 -3
  448. package/icons/newspaper.vue +3 -3
  449. package/icons/note-pencil.vue +14 -14
  450. package/icons/note.vue +14 -14
  451. package/icons/notification-error.vue +17 -17
  452. package/icons/notification.vue +16 -16
  453. package/icons/paperclip.vue +7 -7
  454. package/icons/payment/payment-card.vue +6 -6
  455. package/icons/payment/payment-kaspi.vue +11 -11
  456. package/icons/person-vector.vue +7 -7
  457. package/icons/person-with-briefcase.vue +10 -10
  458. package/icons/person.vue +22 -22
  459. package/icons/phone-call.vue +6 -6
  460. package/icons/phone-filled.vue +5 -5
  461. package/icons/phone-reversed.vue +3 -3
  462. package/icons/phone.vue +7 -7
  463. package/icons/plus/plus-circle.vue +8 -8
  464. package/icons/plus/plus.vue +13 -13
  465. package/icons/profile.vue +5 -5
  466. package/icons/qr-code.vue +25 -0
  467. package/icons/radio/radio-check.vue +8 -8
  468. package/icons/radio/radio-empty.vue +10 -10
  469. package/icons/receipt-text.vue +19 -0
  470. package/icons/receipt.vue +8 -8
  471. package/icons/recycle.vue +10 -0
  472. package/icons/refresh-cw.vue +8 -0
  473. package/icons/reply.vue +15 -0
  474. package/icons/robot-outline.vue +3 -3
  475. package/icons/robot.vue +14 -14
  476. package/icons/route.vue +7 -7
  477. package/icons/sanctions.vue +8 -8
  478. package/icons/scales/scale-outline.vue +3 -3
  479. package/icons/scales/scale.vue +16 -16
  480. package/icons/scales/scales.vue +5 -5
  481. package/icons/scales/standing-scales.vue +15 -15
  482. package/icons/scan-search.vue +3 -3
  483. package/icons/search-outline.vue +3 -3
  484. package/icons/search.vue +27 -27
  485. package/icons/send.vue +3 -3
  486. package/icons/settings.vue +26 -26
  487. package/icons/share/share-alt.vue +5 -5
  488. package/icons/share/share.vue +14 -14
  489. package/icons/shield-alert.vue +31 -31
  490. package/icons/shield-check.vue +20 -0
  491. package/icons/shield-verified.vue +14 -14
  492. package/icons/ship.vue +8 -8
  493. package/icons/shuffle.vue +3 -0
  494. package/icons/siren.vue +23 -0
  495. package/icons/socials/face-book.vue +15 -15
  496. package/icons/socials/facebook-color.vue +16 -16
  497. package/icons/socials/globe-color.vue +16 -16
  498. package/icons/socials/instagram-color.vue +46 -46
  499. package/icons/socials/instagram.vue +19 -19
  500. package/icons/socials/linkedin-color.vue +14 -14
  501. package/icons/socials/telegram-color.vue +16 -16
  502. package/icons/socials/telegram.vue +15 -15
  503. package/icons/socials/tik-tok.vue +15 -15
  504. package/icons/socials/tiktok-color.vue +14 -14
  505. package/icons/socials/vk-color.vue +16 -16
  506. package/icons/socials/youtube-color.vue +16 -16
  507. package/icons/socials/youtube.vue +16 -16
  508. package/icons/sort/sort-asc.vue +15 -15
  509. package/icons/sort/sort-desc.vue +15 -15
  510. package/icons/sparkles.vue +3 -3
  511. package/icons/splitting-arrows.vue +8 -8
  512. package/icons/star/star-filled.vue +41 -41
  513. package/icons/star/star-half-filled.vue +21 -21
  514. package/icons/star/star.vue +25 -25
  515. package/icons/store.vue +7 -0
  516. package/icons/sun.vue +25 -25
  517. package/icons/sviazi.vue +5 -5
  518. package/icons/tag.vue +30 -30
  519. package/icons/target.vue +7 -0
  520. package/icons/tasks.vue +10 -10
  521. package/icons/tender-search.vue +11 -11
  522. package/icons/terminal.vue +3 -3
  523. package/icons/toasts/check-circle-toast.vue +6 -6
  524. package/icons/toasts/warning-triangle-toast.vue +8 -8
  525. package/icons/trash.vue +13 -13
  526. package/icons/trees.vue +8 -0
  527. package/icons/triangle-alert.vue +3 -3
  528. package/icons/triangle.vue +10 -10
  529. package/icons/trophy.vue +10 -0
  530. package/icons/truck.vue +7 -7
  531. package/icons/upload.vue +16 -0
  532. package/icons/user-plus.vue +3 -0
  533. package/icons/user-square.vue +14 -14
  534. package/icons/user-x.vue +11 -0
  535. package/icons/user.vue +22 -22
  536. package/icons/users-focus.vue +8 -8
  537. package/icons/users-outline.vue +3 -3
  538. package/icons/users-round.vue +3 -3
  539. package/icons/users-three.vue +7 -7
  540. package/icons/users.vue +8 -8
  541. package/icons/view-eye-open.vue +13 -13
  542. package/icons/wallet.vue +6 -0
  543. package/icons/warning/warning-circle.vue +29 -29
  544. package/icons/warning/warning-triangle-filled.vue +12 -12
  545. package/icons/warning/warning-triangle.vue +28 -28
  546. package/icons/webhook.vue +3 -3
  547. package/icons/whatsapp.vue +8 -8
  548. package/icons/work-bag.vue +11 -11
  549. package/icons/work-case.vue +9 -9
  550. package/icons/work-search.vue +10 -10
  551. package/icons/work.vue +5 -5
  552. package/icons/workflow.vue +3 -3
  553. package/icons/x-mark.vue +15 -15
  554. package/icons/x-outline.vue +3 -3
  555. package/icons/yandex.vue +19 -29
  556. package/icons/zap.vue +3 -3
  557. package/illustrations/address-location.vue +38 -38
  558. package/illustrations/award-badge.vue +105 -105
  559. package/illustrations/ball-with-chain.vue +120 -120
  560. package/illustrations/bill.vue +133 -133
  561. package/illustrations/buildings.vue +82 -82
  562. package/illustrations/calendar.vue +156 -156
  563. package/illustrations/chains.vue +152 -152
  564. package/illustrations/coin-percent.vue +126 -126
  565. package/illustrations/coins-stack.vue +202 -202
  566. package/illustrations/delete-dark.vue +32 -32
  567. package/illustrations/delete.vue +33 -33
  568. package/illustrations/doc-with-stamp.vue +126 -126
  569. package/illustrations/document.vue +64 -64
  570. package/illustrations/door.vue +85 -85
  571. package/illustrations/empty-badge-check.vue +20 -20
  572. package/illustrations/empty-ban.vue +18 -18
  573. package/illustrations/empty-box.vue +77 -77
  574. package/illustrations/empty-filter.vue +20 -20
  575. package/illustrations/empty-inbox.vue +19 -19
  576. package/illustrations/empty-scale.vue +22 -22
  577. package/illustrations/empty-search-x.vue +21 -21
  578. package/illustrations/empty-shopping-cart.vue +21 -21
  579. package/illustrations/empty-users-round.vue +20 -20
  580. package/illustrations/empty-wallet.vue +127 -127
  581. package/illustrations/graph-in-coin.vue +119 -119
  582. package/illustrations/hammer.vue +156 -156
  583. package/illustrations/hand-cash.vue +108 -108
  584. package/illustrations/info.vue +52 -52
  585. package/illustrations/mail-active.vue +86 -86
  586. package/illustrations/mail.vue +68 -68
  587. package/illustrations/no-access.vue +92 -92
  588. package/illustrations/notes-search.vue +20 -0
  589. package/illustrations/notes-sticky.vue +20 -0
  590. package/illustrations/ok.vue +90 -90
  591. package/illustrations/people-group.vue +237 -237
  592. package/illustrations/person-with-phone.vue +187 -187
  593. package/illustrations/person.vue +159 -159
  594. package/illustrations/phone-check.vue +110 -110
  595. package/illustrations/phone-payment-method.vue +223 -223
  596. package/illustrations/scheme-notes.vue +21 -0
  597. package/illustrations/scheme-waypoints.vue +25 -0
  598. package/illustrations/search.vue +14 -14
  599. package/illustrations/shield-user.vue +94 -94
  600. package/illustrations/stop-hand.vue +84 -84
  601. package/illustrations/stop-sign.vue +34 -34
  602. package/illustrations/suit.vue +97 -97
  603. package/illustrations/suitcase.vue +105 -105
  604. package/illustrations/terminal.vue +100 -100
  605. package/illustrations/trash-can.vue +66 -66
  606. package/illustrations/turn-on-tariff.vue +38 -38
  607. package/illustrations/two-persons.vue +169 -169
  608. package/lang/en.ts +911 -877
  609. package/lang/kk.ts +913 -879
  610. package/lang/ru.ts +913 -879
  611. package/layouts/default.vue +13 -13
  612. package/nuxt.config.ts +119 -95
  613. package/package.json +64 -64
  614. package/plugins/maska.ts +4 -4
  615. package/plugins/toast.client.ts +41 -41
  616. package/public/kaspi/logo.svg +4 -4
  617. package/server/middleware/color-mode-cookie.ts +58 -0
  618. package/shared/constans/pages.ts +96 -96
  619. package/tsconfig.json +6 -6
  620. package/utils/apiErrorMessage.ts +22 -0
  621. package/utils/getMaxZindex.ts +25 -25
  622. package/utils/localizedNavigation.ts +36 -36
  623. package/utils/passwordRules.ts +20 -0
  624. package/utils/toCssSize.ts +11 -0
  625. package/utils/twMerge.ts +34 -34
  626. package/vitest.config.ts +14 -14
  627. package/.claude/settings.local.json +0 -19
@@ -0,0 +1,592 @@
1
+ <script generic="T" lang="ts" setup>
2
+ import type { Placement } from '@floating-ui/vue'
3
+ import { toCssSize } from '#adata-ui/utils/toCssSize'
4
+ import { autoUpdate, flip, size as floatingSize, offset, shift, useFloating } from '@floating-ui/vue'
5
+ import { onClickOutside, useToggle } from '@vueuse/core'
6
+ import { isEqual } from 'lodash-es'
7
+
8
+ /**
9
+ * Селект по двухуровневому справочнику: категория и вложенные в неё виды.
10
+ *
11
+ * Отличие от `ACascadeSelectV2` — тот показывает уровни отдельными колонками и даёт
12
+ * выбрать ТОЛЬКО лист. Здесь оба уровня лежат в одном списке и категория выбирается
13
+ * наравне с дочерним видом (`selectableParents`): справочники вроде «вида нарушения»
14
+ * допускают и общую категорию, и уточнение.
15
+ *
16
+ * На вход идёт ПЛОСКИЙ список — ровно в том виде, в каком его отдаёт бэк
17
+ * (`[{ id, name, parent_id }]`). Группировку компонент делает сам, поэтому
18
+ * консьюмерам не нужно раскладывать справочник в дерево у себя.
19
+ *
20
+ * Весь «хром» триггера — общий `a-select-trigger`, тот же, что у `ASelectV2`,
21
+ * поэтому поле выглядит идентично обычному селекту.
22
+ */
23
+ defineOptions({ name: 'AGroupSelectV2' })
24
+
25
+ const props = withDefaults(defineProps<Props>(), {
26
+ required: false,
27
+ keyToShow: 'name',
28
+ keyToSelect: 'id',
29
+ parentKey: 'parent_id',
30
+ disabledKey: undefined,
31
+ getOnlyKey: false,
32
+ selectableParents: true,
33
+ clearable: true,
34
+ disabled: false,
35
+ size: 'sm',
36
+ error: undefined,
37
+ startIcon: undefined,
38
+ buttonBg: '',
39
+ searchable: false,
40
+ searchLabel: 'Поиск...',
41
+ showPathInTrigger: false,
42
+ dropdownWidth: undefined,
43
+ dropdownMaxHeight: undefined,
44
+ dropdownAlign: 'start',
45
+ emptyLabel: 'Ничего не найдено',
46
+ })
47
+
48
+ const emits = defineEmits<Emits>()
49
+
50
+ defineSlots<{
51
+ 'option'?: (props: { option: T, isParent: boolean, selected: boolean }) => unknown
52
+ 'single-selected'?: (props: { value: T, parent: T | null }) => unknown
53
+ 'empty'?: () => unknown
54
+ }>()
55
+
56
+ interface Props {
57
+ label: string
58
+ /** Плоский список: вложенность задаётся через `parentKey`, а не массивом детей. */
59
+ options: T[]
60
+ required?: boolean
61
+ keyToShow?: string
62
+ keyToSelect?: string
63
+ /** Ключ со ссылкой на родителя. `null`/`undefined` — элемент верхнего уровня. */
64
+ parentKey?: string
65
+ /** Необязательный ключ-флаг «опция недоступна». */
66
+ disabledKey?: string
67
+ /** `true` — в v-model лежит ключ опции, `false` — сам объект. */
68
+ getOnlyKey?: boolean
69
+ /** Можно ли выбрать саму категорию, а не только вложенный вид. */
70
+ selectableParents?: boolean
71
+ clearable?: boolean
72
+ disabled?: boolean
73
+ size?: 'sm' | 'md'
74
+ error?: string | string[]
75
+ startIcon?: object
76
+ buttonBg?: string
77
+ searchable?: boolean
78
+ searchLabel?: string
79
+ /** Показывать в триггере весь путь («Категория / Вид»). */
80
+ showPathInTrigger?: boolean
81
+ dropdownWidth?: number | string
82
+ dropdownMaxHeight?: number | string
83
+ dropdownAlign?: 'start' | 'center' | 'end'
84
+ emptyLabel?: string
85
+ }
86
+
87
+ interface Emits {
88
+ (e: 'onChange', option: T, parent: T | null): void
89
+ (e: 'onClear'): void
90
+ }
91
+
92
+ const modelValue = defineModel<T | unknown | null>({ required: true })
93
+ const isOpen = defineModel<boolean>('open', { default: false })
94
+
95
+ function readKey(node: T, key: string): unknown {
96
+ return (node as Record<string, unknown>)[key]
97
+ }
98
+
99
+ function nodeKey(node: T): unknown {
100
+ return readKey(node, props.keyToSelect)
101
+ }
102
+
103
+ function nodeLabel(node: T): string {
104
+ return String(readKey(node, props.keyToShow) ?? '')
105
+ }
106
+
107
+ function nodeDisabled(node: T): boolean {
108
+ return props.disabledKey ? Boolean(readKey(node, props.disabledKey)) : false
109
+ }
110
+
111
+ // -- Группировка -----------------------------------------------------------
112
+
113
+ interface Group {
114
+ parent: T
115
+ children: T[]
116
+ }
117
+
118
+ /**
119
+ * Один проход по плоскому списку. Элемент считается дочерним, только если его
120
+ * `parent_id` реально указывает на существующий верхнеуровневый элемент —
121
+ * «сирота» со ссылкой в никуда поднимается в корень, а не теряется из списка.
122
+ */
123
+ const groups = computed<Group[]>(() => {
124
+ const items = props.options ?? []
125
+ if (!items.length) return []
126
+
127
+ const rootKeys = new Set(
128
+ items.filter(item => readKey(item, props.parentKey) == null).map(item => nodeKey(item)),
129
+ )
130
+
131
+ const childrenByParent = new Map<unknown, T[]>()
132
+ const roots: T[] = []
133
+
134
+ items.forEach((item) => {
135
+ const parentId = readKey(item, props.parentKey)
136
+ if (parentId != null && rootKeys.has(parentId)) {
137
+ const siblings = childrenByParent.get(parentId) ?? []
138
+ siblings.push(item)
139
+ childrenByParent.set(parentId, siblings)
140
+ return
141
+ }
142
+ roots.push(item)
143
+ })
144
+
145
+ return roots.map(root => ({ parent: root, children: childrenByParent.get(nodeKey(root)) ?? [] }))
146
+ })
147
+
148
+ /** Плоский индекс «ключ → узел и его родитель» — по нему решается отображение в триггере. */
149
+ const index = computed(() => {
150
+ const map = new Map<unknown, { node: T, parent: T | null }>()
151
+ groups.value.forEach(({ parent, children }) => {
152
+ map.set(nodeKey(parent), { node: parent, parent: null })
153
+ children.forEach(child => map.set(nodeKey(child), { node: child, parent }))
154
+ })
155
+ return map
156
+ })
157
+
158
+ // -- Поиск -----------------------------------------------------------------
159
+
160
+ const searchValue = ref('')
161
+
162
+ /**
163
+ * Группа остаётся в списке, если совпал сам заголовок (тогда показываем её целиком)
164
+ * или хоть один вложенный вид (тогда заголовок остаётся как контекст, даже если
165
+ * сам не совпал — иначе непонятно, к чему относится найденный вид).
166
+ */
167
+ const visibleGroups = computed<Group[]>(() => {
168
+ const needle = searchValue.value.trim().toLowerCase()
169
+ if (!needle) return groups.value
170
+
171
+ return groups.value.flatMap(({ parent, children }) => {
172
+ if (nodeLabel(parent).toLowerCase().includes(needle)) return [{ parent, children }]
173
+ const matched = children.filter(child => nodeLabel(child).toLowerCase().includes(needle))
174
+ return matched.length ? [{ parent, children: matched }] : []
175
+ })
176
+ })
177
+
178
+ const isEmpty = computed(() => visibleGroups.value.length === 0)
179
+
180
+ // -- Выбранное значение ----------------------------------------------------
181
+
182
+ const selectedKey = computed<unknown>(() => {
183
+ const value = modelValue.value
184
+ if (value == null || value === '') return undefined
185
+ return props.getOnlyKey ? value : nodeKey(value as T)
186
+ })
187
+
188
+ const selectedEntry = computed(() =>
189
+ selectedKey.value === undefined ? null : index.value.get(selectedKey.value) ?? null,
190
+ )
191
+
192
+ const displayValue = computed(() => {
193
+ const entry = selectedEntry.value
194
+ if (!entry) return ''
195
+ if (props.showPathInTrigger && entry.parent) {
196
+ return `${nodeLabel(entry.parent)} / ${nodeLabel(entry.node)}`
197
+ }
198
+ return nodeLabel(entry.node)
199
+ })
200
+
201
+ function isSelected(node: T): boolean {
202
+ return selectedKey.value !== undefined && isEqual(selectedKey.value, nodeKey(node))
203
+ }
204
+
205
+ /** Заголовок группы кликабелен, только если категории вообще разрешено выбирать. */
206
+ function isParentSelectable(parent: T): boolean {
207
+ return props.selectableParents && !nodeDisabled(parent)
208
+ }
209
+
210
+ // -- Позиционирование ------------------------------------------------------
211
+
212
+ const wrapper = ref<HTMLElement | null>(null)
213
+ const triggerRef = ref<{ el: HTMLButtonElement | null, valueEl: HTMLElement | null } | null>(null)
214
+ const reference = computed<HTMLElement | null>(() => triggerRef.value?.el ?? null)
215
+ const floating = ref<HTMLElement | null>(null)
216
+
217
+ const dropdownPlacement = computed<Placement>(() => {
218
+ if (props.dropdownAlign === 'center') return 'bottom'
219
+ if (props.dropdownAlign === 'end') return 'bottom-end'
220
+ return 'bottom-start'
221
+ })
222
+
223
+ const dropdownFallback = computed<Placement[]>(() => {
224
+ if (props.dropdownAlign === 'center') return ['top']
225
+ if (props.dropdownAlign === 'end') return ['top-end']
226
+ return ['top-start']
227
+ })
228
+
229
+ const { x, y, strategy, update } = useFloating(reference, floating, {
230
+ placement: dropdownPlacement,
231
+ strategy: 'absolute',
232
+ middleware: computed(() => [
233
+ offset(6),
234
+ flip({ fallbackPlacements: dropdownFallback.value }),
235
+ shift({ padding: 8 }),
236
+ floatingSize({
237
+ apply({ availableHeight, elements, rects }) {
238
+ const el = elements.floating as HTMLElement
239
+ el.style.width = toCssSize(props.dropdownWidth) ?? `${rects.reference.width}px`
240
+ const maxHeightOverride = toCssSize(props.dropdownMaxHeight)
241
+ el.style.maxHeight = maxHeightOverride
242
+ ? `min(${maxHeightOverride}, ${availableHeight}px)`
243
+ : `${Math.max(140, Math.min(availableHeight, 320))}px`
244
+ },
245
+ }),
246
+ ]),
247
+ })
248
+
249
+ let cleanup: (() => void) | null = null
250
+ watch(isOpen, (open) => {
251
+ if (open && reference.value && floating.value) {
252
+ cleanup = autoUpdate(reference.value, floating.value, update)
253
+ }
254
+ else {
255
+ cleanup?.()
256
+ cleanup = null
257
+ }
258
+ })
259
+ onUnmounted(() => cleanup?.())
260
+
261
+ const toggleOpen = useToggle(isOpen)
262
+
263
+ onClickOutside(wrapper, () => {
264
+ if (isOpen.value) toggleOpen(false)
265
+ })
266
+
267
+ // -- Действия --------------------------------------------------------------
268
+
269
+ function onSelect(node: T) {
270
+ if (nodeDisabled(node)) return
271
+ modelValue.value = props.getOnlyKey ? nodeKey(node) : node
272
+ emits('onChange', node, index.value.get(nodeKey(node))?.parent ?? null)
273
+ toggleOpen(false)
274
+ }
275
+
276
+ function onClear() {
277
+ modelValue.value = null
278
+ emits('onClear')
279
+ }
280
+
281
+ defineExpose({ onClear })
282
+
283
+ // -- Клавиатура ------------------------------------------------------------
284
+
285
+ /** Строки в порядке отрисовки — только те, на которые реально можно встать. */
286
+ const navigableRows = computed<T[]>(() =>
287
+ visibleGroups.value.flatMap(({ parent, children }) => [
288
+ ...(isParentSelectable(parent) ? [parent] : []),
289
+ ...children.filter(child => !nodeDisabled(child)),
290
+ ]),
291
+ )
292
+
293
+ const focusedKey = ref<unknown>(undefined)
294
+ const listRef = ref<HTMLElement | null>(null)
295
+
296
+ function focusRow(key: unknown) {
297
+ focusedKey.value = key
298
+ nextTick(() => {
299
+ listRef.value
300
+ ?.querySelector<HTMLElement>(`[data-row-key="${String(key)}"]`)
301
+ ?.scrollIntoView({ block: 'nearest' })
302
+ })
303
+ }
304
+
305
+ function moveFocus(delta: number) {
306
+ const rows = navigableRows.value
307
+ if (!rows.length) return
308
+ const current = rows.findIndex(row => isEqual(nodeKey(row), focusedKey.value))
309
+ const next = current === -1
310
+ ? (delta > 0 ? 0 : rows.length - 1)
311
+ : (current + delta + rows.length) % rows.length
312
+ focusRow(nodeKey(rows[next]))
313
+ }
314
+
315
+ function onKeydown(event: KeyboardEvent) {
316
+ if (props.disabled) return
317
+
318
+ if (!isOpen.value) {
319
+ if (event.key === 'ArrowDown') {
320
+ event.preventDefault()
321
+ toggleOpen(true)
322
+ }
323
+ return
324
+ }
325
+
326
+ switch (event.key) {
327
+ case 'Escape':
328
+ event.preventDefault()
329
+ toggleOpen(false)
330
+ nextTick(() => reference.value?.focus())
331
+ break
332
+ case 'Tab':
333
+ toggleOpen(false)
334
+ break
335
+ case 'ArrowDown':
336
+ event.preventDefault()
337
+ moveFocus(1)
338
+ break
339
+ case 'ArrowUp':
340
+ event.preventDefault()
341
+ moveFocus(-1)
342
+ break
343
+ case 'Home':
344
+ event.preventDefault()
345
+ if (navigableRows.value.length) focusRow(nodeKey(navigableRows.value[0]))
346
+ break
347
+ case 'End': {
348
+ event.preventDefault()
349
+ const rows = navigableRows.value
350
+ if (rows.length) focusRow(nodeKey(rows[rows.length - 1]))
351
+ break
352
+ }
353
+ case 'Enter': {
354
+ const row = navigableRows.value.find(item => isEqual(nodeKey(item), focusedKey.value))
355
+ if (!row) break
356
+ event.preventDefault()
357
+ onSelect(row)
358
+ break
359
+ }
360
+ }
361
+ }
362
+
363
+ /* Открытие — на выбранном значении, закрытие — сброс поиска и фокуса. */
364
+ watch(isOpen, (open) => {
365
+ if (open) {
366
+ focusedKey.value = selectedKey.value ?? nodeKey(navigableRows.value[0] ?? ({} as T))
367
+ nextTick(() => focusRow(focusedKey.value))
368
+ return
369
+ }
370
+ searchValue.value = ''
371
+ focusedKey.value = undefined
372
+ })
373
+ </script>
374
+
375
+ <template>
376
+ <div
377
+ ref="wrapper"
378
+ class="group-select relative w-full text-body-md"
379
+ @keydown="onKeydown"
380
+ >
381
+ <a-select-trigger
382
+ ref="triggerRef"
383
+ :label="label"
384
+ :required="required"
385
+ :size="size"
386
+ :disabled="disabled"
387
+ :error="error"
388
+ :is-open="isOpen"
389
+ :start-icon="startIcon"
390
+ :button-bg="buttonBg"
391
+ :clearable="clearable"
392
+ :show-clear="clearable && !!selectedEntry"
393
+ floating-label
394
+ :has-value="!!selectedEntry"
395
+ @toggle="toggleOpen()"
396
+ @clear="onClear"
397
+ >
398
+ <span v-if="selectedEntry" class="block truncate leading-tight">
399
+ <slot
400
+ name="single-selected"
401
+ :value="selectedEntry.node"
402
+ :parent="selectedEntry.parent"
403
+ >{{ displayValue }}</slot>
404
+ </span>
405
+ <span v-else class="text-secondary">&#8203;</span>
406
+ </a-select-trigger>
407
+
408
+ <transition
409
+ enter-active-class="group-select__dropdown-enter-active"
410
+ enter-from-class="group-select__dropdown-enter-from"
411
+ enter-to-class="group-select__dropdown-enter-to"
412
+ leave-active-class="group-select__dropdown-leave-active"
413
+ leave-from-class="group-select__dropdown-leave-from"
414
+ leave-to-class="group-select__dropdown-leave-to"
415
+ >
416
+ <div
417
+ v-if="isOpen"
418
+ ref="floating"
419
+ class="group-select__dropdown z-10000 rounded-lg border border-default bg-main shadow-lg shadow-surface-dark/10 dark:shadow-surface-dark/30"
420
+ :style="{
421
+ position: strategy,
422
+ left: x != null ? `${x}px` : '',
423
+ top: y != null ? `${y}px` : '',
424
+ }"
425
+ >
426
+ <div v-if="searchable" class="px-2.5 pt-2.5">
427
+ <a-input-standard
428
+ v-model="searchValue"
429
+ :label="searchLabel"
430
+ :size="size"
431
+ clearable
432
+ color="gray"
433
+ />
434
+ </div>
435
+
436
+ <ul
437
+ ref="listRef"
438
+ class="group-select__list flex flex-col gap-0.5 p-1.5"
439
+ role="listbox"
440
+ >
441
+ <li v-if="isEmpty" class="px-3 py-6 text-center text-body-sm text-quaternary">
442
+ <slot name="empty">
443
+ {{ emptyLabel }}
444
+ </slot>
445
+ </li>
446
+
447
+ <li
448
+ v-for="group in visibleGroups"
449
+ :key="String(nodeKey(group.parent))"
450
+ >
451
+ <!-- Категория: кнопка, когда её разрешено выбирать, иначе просто заголовок. -->
452
+ <component
453
+ :is="isParentSelectable(group.parent) ? 'button' : 'div'"
454
+ :data-row-key="String(nodeKey(group.parent))"
455
+ :type="isParentSelectable(group.parent) ? 'button' : undefined"
456
+ :role="isParentSelectable(group.parent) ? 'option' : 'presentation'"
457
+ :aria-selected="isParentSelectable(group.parent) ? isSelected(group.parent) : undefined"
458
+ :disabled="isParentSelectable(group.parent) ? nodeDisabled(group.parent) : undefined"
459
+ class="flex w-full items-center justify-between gap-2 rounded-md px-3 py-2 text-start"
460
+ :class="[
461
+ isParentSelectable(group.parent)
462
+ ? 'group-select__row text-body-md-medium text-primary transition-colors duration-100 hover:bg-action-secondary-hover'
463
+ : 'text-overline text-quaternary',
464
+ { 'group-select__row--selected': isSelected(group.parent) },
465
+ { 'group-select__row--focused': isEqual(focusedKey, nodeKey(group.parent)) },
466
+ ]"
467
+ @click="isParentSelectable(group.parent) ? onSelect(group.parent) : undefined"
468
+ >
469
+ <span class="min-w-0 flex-1 truncate">
470
+ <slot
471
+ name="option"
472
+ :option="group.parent"
473
+ :is-parent="true"
474
+ :selected="isSelected(group.parent)"
475
+ >{{ nodeLabel(group.parent) }}</slot>
476
+ </span>
477
+ <a-icon-check
478
+ v-if="isSelected(group.parent)"
479
+ class="size-4 shrink-0 text-brand"
480
+ />
481
+ </component>
482
+
483
+ <!-- Вложенные виды: отступ и точка-маркер, чтобы связь с категорией читалась. -->
484
+ <ul v-if="group.children.length" class="mt-0.5 flex flex-col gap-0.5">
485
+ <li v-for="child in group.children" :key="String(nodeKey(child))">
486
+ <button
487
+ type="button"
488
+ role="option"
489
+ :data-row-key="String(nodeKey(child))"
490
+ :aria-selected="isSelected(child)"
491
+ :disabled="nodeDisabled(child)"
492
+ class="group-select__row flex w-full items-center gap-2 rounded-md py-2 pr-3 pl-6 text-start text-body-md text-secondary transition-colors duration-100 hover:bg-action-secondary-hover"
493
+ :class="[
494
+ { 'group-select__row--selected': isSelected(child) },
495
+ { 'group-select__row--focused': isEqual(focusedKey, nodeKey(child)) },
496
+ ]"
497
+ @click="onSelect(child)"
498
+ >
499
+ <span class="group-select__bullet shrink-0 rounded-full" />
500
+ <span class="min-w-0 flex-1 truncate">
501
+ <slot
502
+ name="option"
503
+ :option="child"
504
+ :is-parent="false"
505
+ :selected="isSelected(child)"
506
+ >{{ nodeLabel(child) }}</slot>
507
+ </span>
508
+ <a-icon-check
509
+ v-if="isSelected(child)"
510
+ class="size-4 shrink-0 text-brand"
511
+ />
512
+ </button>
513
+ </li>
514
+ </ul>
515
+ </li>
516
+ </ul>
517
+ </div>
518
+ </transition>
519
+ </div>
520
+ </template>
521
+
522
+ <style scoped>
523
+ .group-select__dropdown-enter-active,
524
+ .group-select__dropdown-leave-active {
525
+ transition:
526
+ opacity 180ms cubic-bezier(0.22, 1, 0.36, 1),
527
+ transform 180ms cubic-bezier(0.22, 1, 0.36, 1);
528
+ }
529
+
530
+ .group-select__dropdown-enter-from,
531
+ .group-select__dropdown-leave-to {
532
+ opacity: 0;
533
+ transform: translateY(-4px);
534
+ }
535
+
536
+ .group-select__dropdown-enter-to,
537
+ .group-select__dropdown-leave-from {
538
+ opacity: 1;
539
+ transform: translateY(0);
540
+ }
541
+
542
+ .group-select__list {
543
+ max-height: 250px;
544
+ overflow-y: auto;
545
+ overflow-x: hidden;
546
+ scrollbar-width: none;
547
+ -ms-overflow-style: none;
548
+ }
549
+
550
+ .group-select__list::-webkit-scrollbar {
551
+ width: 0;
552
+ height: 0;
553
+ display: none;
554
+ }
555
+
556
+ .group-select__row {
557
+ cursor: pointer;
558
+ }
559
+
560
+ /* Размер и цвет точки — здесь, а не утилитами: consumer может тянуть CSS слоя
561
+ из npm-копии, и утилита, которую больше никто не использует, не сгенерируется. */
562
+ .group-select__bullet {
563
+ width: 6px;
564
+ height: 6px;
565
+ background-color: var(--color-icon-secondary);
566
+ }
567
+
568
+ .group-select__row:disabled {
569
+ cursor: default;
570
+ opacity: 0.5;
571
+ }
572
+
573
+ /* Фокус с клавиатуры: рамка внутрь, чтобы не спорить с соседними строками.
574
+ Цвет сильно разбавлен — сплошной --border-color-focus на узкой строке бьёт по глазам. */
575
+ .group-select__row--focused {
576
+ outline: 1px solid color-mix(in srgb, var(--border-color-focus) 35%, transparent);
577
+ outline-offset: -1px;
578
+ background-color: color-mix(in srgb, var(--border-color-focus) 6%, transparent);
579
+ }
580
+
581
+ .group-select__row--selected {
582
+ background-color: var(--background-color-brand-subtle);
583
+ font-weight: 500;
584
+ }
585
+
586
+ @media (prefers-reduced-motion: reduce) {
587
+ .group-select__dropdown-enter-active,
588
+ .group-select__dropdown-leave-active {
589
+ transition: none;
590
+ }
591
+ }
592
+ </style>