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
@@ -0,0 +1,223 @@
1
+ <script setup lang="ts">
2
+ import type { TooltipPlacement, TooltipTrigger } from './types'
3
+ import {
4
+ arrow as arrowMiddleware,
5
+ autoUpdate,
6
+ flip,
7
+ offset as offsetMiddleware,
8
+ shift,
9
+ useFloating,
10
+ } from '@floating-ui/vue'
11
+ import { onClickOutside, onKeyStroke } from '@vueuse/core'
12
+ import { twMerge } from '#adata-ui/utils/twMerge'
13
+ import { useTooltipTrigger } from './useTooltipTrigger'
14
+
15
+ interface Props {
16
+ placement?: TooltipPlacement
17
+ trigger?: TooltipTrigger
18
+ arrow?: boolean
19
+ offset?: number
20
+ openDelay?: number
21
+ closeDelay?: number
22
+ teleport?: boolean
23
+ disabled?: boolean
24
+ zIndex?: number
25
+ text?: string
26
+ contentClass?: string
27
+ }
28
+
29
+ defineOptions({ name: 'ATooltipV2', inheritAttrs: false })
30
+
31
+ const props = withDefaults(defineProps<Props>(), {
32
+ placement: 'top',
33
+ trigger: 'hover',
34
+ arrow: false,
35
+ offset: 8,
36
+ openDelay: 0,
37
+ closeDelay: 0,
38
+ teleport: true,
39
+ disabled: false,
40
+ zIndex: 9999,
41
+ text: '',
42
+ contentClass: '',
43
+ })
44
+
45
+ const open = defineModel<boolean>('open', { default: false })
46
+
47
+ const reference = ref<HTMLElement | null>(null)
48
+ const floating = ref<HTMLElement | null>(null)
49
+ const arrowRef = ref<HTMLElement | null>(null)
50
+
51
+ const tooltipId = `a-tooltip-v2-${useId()}`
52
+
53
+ const { listeners, close, clearTimers } = useTooltipTrigger({
54
+ open,
55
+ trigger: toRef(props, 'trigger'),
56
+ disabled: toRef(props, 'disabled'),
57
+ openDelay: toRef(props, 'openDelay'),
58
+ closeDelay: toRef(props, 'closeDelay'),
59
+ })
60
+
61
+ const { x, y, strategy, middlewareData, placement: resolvedPlacement } = useFloating(reference, floating, {
62
+ placement: computed(() => props.placement),
63
+ strategy: 'fixed',
64
+ whileElementsMounted: autoUpdate,
65
+ middleware: computed(() => [
66
+ offsetMiddleware(props.offset),
67
+ flip(),
68
+ shift({ padding: 8 }),
69
+ ...(props.arrow ? [arrowMiddleware({ element: arrowRef, padding: 6 })] : []),
70
+ ]),
71
+ })
72
+
73
+ const side = computed(() => resolvedPlacement.value.split('-')[0])
74
+ const opposite: Record<string, string> = { top: 'bottom', bottom: 'top', left: 'right', right: 'left' }
75
+
76
+ const transformOrigin = computed(() => {
77
+ const [main, align] = resolvedPlacement.value.split('-')
78
+ const anchor = opposite[main] ?? 'center'
79
+ if (main === 'left' || main === 'right') {
80
+ const v = align === 'start' ? 'top' : align === 'end' ? 'bottom' : 'center'
81
+ return `${anchor} ${v}`
82
+ }
83
+ const h = align === 'start' ? 'left' : align === 'end' ? 'right' : 'center'
84
+ return `${h} ${anchor}`
85
+ })
86
+
87
+ const arrowBorderClass = computed(() => {
88
+ switch (side.value) {
89
+ case 'top': return 'border-b border-r'
90
+ case 'bottom': return 'border-t border-l'
91
+ case 'left': return 'border-t border-r'
92
+ case 'right': return 'border-b border-l'
93
+ default: return ''
94
+ }
95
+ })
96
+
97
+ const arrowStyle = computed<Record<string, string>>(() => {
98
+ const data = middlewareData.value.arrow
99
+ const staticSide = opposite[side.value]
100
+ const style: Record<string, string> = {}
101
+ if (data?.x != null) style.left = `${data.x}px`
102
+ if (data?.y != null) style.top = `${data.y}px`
103
+ if (staticSide) style[staticSide] = '-4px'
104
+ return style
105
+ })
106
+
107
+ const interactive = computed(() => props.trigger !== 'hover')
108
+
109
+ const boxClass = computed(() =>
110
+ twMerge(
111
+ 'rounded-md bg-white px-2.5 py-1.5 shadow-lg ring-1 ring-gray-200 dark:bg-gray-900 dark:ring-gray-700',
112
+ props.contentClass,
113
+ ),
114
+ )
115
+
116
+ onClickOutside(
117
+ floating,
118
+ () => {
119
+ if (open.value && props.trigger === 'click') close()
120
+ },
121
+ { ignore: [reference] },
122
+ )
123
+
124
+ onKeyStroke('Escape', () => {
125
+ if (open.value && props.trigger === 'click') close()
126
+ })
127
+
128
+ onUnmounted(() => clearTimers())
129
+ </script>
130
+
131
+ <template>
132
+ <span
133
+ ref="reference"
134
+ v-bind="$attrs"
135
+ class="inline-flex"
136
+ :aria-describedby="open ? tooltipId : undefined"
137
+ v-on="listeners"
138
+ >
139
+ <slot :open="open" />
140
+ </span>
141
+
142
+ <teleport
143
+ to="body"
144
+ :disabled="!teleport"
145
+ >
146
+ <transition
147
+ enter-active-class="a-tooltip-v2__enter-active"
148
+ enter-from-class="a-tooltip-v2__enter-from"
149
+ enter-to-class="a-tooltip-v2__enter-to"
150
+ leave-active-class="a-tooltip-v2__leave-active"
151
+ leave-from-class="a-tooltip-v2__leave-from"
152
+ leave-to-class="a-tooltip-v2__leave-to"
153
+ >
154
+ <div
155
+ v-if="open && !disabled"
156
+ :id="tooltipId"
157
+ ref="floating"
158
+ role="tooltip"
159
+ :class="[boxClass, interactive ? 'pointer-events-auto' : 'pointer-events-none']"
160
+ :style="{
161
+ position: strategy,
162
+ left: x != null ? `${x}px` : '0',
163
+ top: y != null ? `${y}px` : '0',
164
+ zIndex,
165
+ transformOrigin,
166
+ }"
167
+ >
168
+ <slot
169
+ name="content"
170
+ :close="close"
171
+ >
172
+ {{ text }}
173
+ </slot>
174
+
175
+ <span
176
+ v-if="arrow"
177
+ ref="arrowRef"
178
+ class="absolute size-2 rotate-45 border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900"
179
+ :class="arrowBorderClass"
180
+ :style="arrowStyle"
181
+ />
182
+ </div>
183
+ </transition>
184
+ </teleport>
185
+ </template>
186
+
187
+ <style scoped>
188
+ .a-tooltip-v2__enter-active {
189
+ transition:
190
+ opacity 160ms ease,
191
+ transform 200ms cubic-bezier(0.22, 1, 0.36, 1);
192
+ }
193
+
194
+ .a-tooltip-v2__leave-active {
195
+ transition:
196
+ opacity 120ms ease,
197
+ transform 140ms cubic-bezier(0.4, 0, 1, 1);
198
+ }
199
+
200
+ .a-tooltip-v2__enter-from,
201
+ .a-tooltip-v2__leave-to {
202
+ opacity: 0;
203
+ transform: translateY(-4px) scale(0.96);
204
+ }
205
+
206
+ .a-tooltip-v2__enter-to,
207
+ .a-tooltip-v2__leave-from {
208
+ opacity: 1;
209
+ transform: translateY(0) scale(1);
210
+ }
211
+
212
+ @media (prefers-reduced-motion: reduce) {
213
+ .a-tooltip-v2__enter-active,
214
+ .a-tooltip-v2__leave-active {
215
+ transition: opacity 120ms ease;
216
+ }
217
+
218
+ .a-tooltip-v2__enter-from,
219
+ .a-tooltip-v2__leave-to {
220
+ transform: none;
221
+ }
222
+ }
223
+ </style>
@@ -0,0 +1,26 @@
1
+ import type { Placement } from '@floating-ui/vue'
2
+
3
+ export type { Placement }
4
+
5
+ /**
6
+ * Placement options spelled out as literals (mirrors Floating UI's `Placement`).
7
+ * Importing the package's `Placement` alias directly makes Vue's runtime prop
8
+ * inference fall back to `Object` and warn; a locally-resolvable union infers
9
+ * `String` correctly.
10
+ */
11
+ export type TooltipPlacement
12
+ = | 'top'
13
+ | 'top-start'
14
+ | 'top-end'
15
+ | 'bottom'
16
+ | 'bottom-start'
17
+ | 'bottom-end'
18
+ | 'left'
19
+ | 'left-start'
20
+ | 'left-end'
21
+ | 'right'
22
+ | 'right-start'
23
+ | 'right-end'
24
+
25
+ /** How the tooltip is opened. `manual` is fully driven by `v-model:open`. */
26
+ export type TooltipTrigger = 'hover' | 'click' | 'manual'
@@ -0,0 +1,101 @@
1
+ import type { Ref } from 'vue'
2
+ import type { TooltipTrigger } from './types'
3
+ import { computed, watch } from 'vue'
4
+
5
+ interface UseTooltipTriggerOptions {
6
+ /** Visibility ref (the component's `v-model:open`). Mutated here. */
7
+ open: Ref<boolean>
8
+ trigger: Ref<TooltipTrigger>
9
+ disabled: Ref<boolean>
10
+ openDelay: Ref<number>
11
+ closeDelay: Ref<number>
12
+ }
13
+
14
+ /**
15
+ * Owns the open/close state machine for {@link ATooltipV2}: debounced show/hide
16
+ * and the per-mode DOM listeners. Kept separate from the SFC so each trigger
17
+ * style (hover/focus, click, manual) stays small and independently testable.
18
+ */
19
+ export function useTooltipTrigger(options: UseTooltipTriggerOptions) {
20
+ const { open, trigger, disabled, openDelay, closeDelay } = options
21
+
22
+ let openTimer: ReturnType<typeof setTimeout> | null = null
23
+ let closeTimer: ReturnType<typeof setTimeout> | null = null
24
+
25
+ function clearOpenTimer() {
26
+ if (openTimer) {
27
+ clearTimeout(openTimer)
28
+ openTimer = null
29
+ }
30
+ }
31
+
32
+ function clearCloseTimer() {
33
+ if (closeTimer) {
34
+ clearTimeout(closeTimer)
35
+ closeTimer = null
36
+ }
37
+ }
38
+
39
+ function clearTimers() {
40
+ clearOpenTimer()
41
+ clearCloseTimer()
42
+ }
43
+
44
+ function show() {
45
+ if (disabled.value || open.value) return
46
+ clearCloseTimer()
47
+ if (openDelay.value > 0) {
48
+ openTimer = setTimeout(() => {
49
+ open.value = true
50
+ openTimer = null
51
+ }, openDelay.value)
52
+ }
53
+ else {
54
+ open.value = true
55
+ }
56
+ }
57
+
58
+ function hide() {
59
+ clearOpenTimer()
60
+ if (!open.value) return
61
+ if (closeDelay.value > 0) {
62
+ closeTimer = setTimeout(() => {
63
+ open.value = false
64
+ closeTimer = null
65
+ }, closeDelay.value)
66
+ }
67
+ else {
68
+ open.value = false
69
+ }
70
+ }
71
+
72
+ function toggle() {
73
+ if (open.value) hide()
74
+ else show()
75
+ }
76
+
77
+ function close() {
78
+ clearTimers()
79
+ open.value = false
80
+ }
81
+
82
+ // Force-close if disabled while visible.
83
+ watch(disabled, (value) => {
84
+ if (value) close()
85
+ })
86
+
87
+ // Listeners bound on the trigger wrapper via `v-on`, selected by mode.
88
+ const listeners = computed<Record<string, () => void>>(() => {
89
+ if (disabled.value) return {}
90
+ switch (trigger.value) {
91
+ case 'hover':
92
+ return { mouseenter: show, mouseleave: hide, focusin: show, focusout: hide }
93
+ case 'click':
94
+ return { click: toggle }
95
+ default:
96
+ return {}
97
+ }
98
+ })
99
+
100
+ return { show, hide, toggle, close, clearTimers, listeners }
101
+ }
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
  import ui from "#adata-ui/components/elements/accordion/AAccordionGroup/ui.config";
3
- import { twMerge } from 'tailwind-merge'
3
+ import { twMerge } from '#adata-ui/utils/twMerge'
4
4
 
5
5
  const props = defineProps<{
6
6
  transition?: {
@@ -0,0 +1,25 @@
1
+ import { useUrls } from '#adata-ui/composables/useUrls'
2
+
3
+ /**
4
+ * Единая точка записи куки accessToken (логин, 2FA, OTP, обмен по auth-сессии).
5
+ * Домен должен совпадать с доменом удаления в resetTokens (adata-common-auth),
6
+ * иначе кука не находится на следующей загрузке и логин-флоу зацикливается на reload.
7
+ */
8
+ export async function writeAccessTokenCookie(accessToken: string, expireIn?: number): Promise<boolean> {
9
+ const { mode } = useUrls()
10
+ const sharedDomain = `${mode}.kz`
11
+ const onSharedDomain = location.hostname === sharedDomain || location.hostname.endsWith(`.${sharedDomain}`)
12
+ const maxAge = Number(expireIn) > 0 ? Number(expireIn) : undefined
13
+
14
+ useCookie('accessToken', {
15
+ maxAge,
16
+ // на localhost/превью-хостах атрибут domain невалиден — пишем host-only куку
17
+ domain: onSharedDomain ? `.${sharedDomain}` : undefined,
18
+ path: '/',
19
+ secure: location.protocol === 'https:',
20
+ }).value = accessToken
21
+
22
+ // useCookie пишет document.cookie на следующем flush, не синхронно
23
+ await nextTick()
24
+ return document.cookie.split('; ').some(part => part.startsWith('accessToken='))
25
+ }
@@ -0,0 +1,84 @@
1
+ // Active-state detection for the products mega-menu.
2
+ //
3
+ // Two levels of "active":
4
+ // 1. activeModule — the current host matches one of the module's hosts
5
+ // (host ≠ module key: e.g. the `fines` module lives on avto.*.kz,
6
+ // and `tenders` spans both zakupki.*.kz and tender.*.kz).
7
+ // 2. activeService — within the active module, the current page matches a
8
+ // concrete item link (`/`, `/unload`, `/foreign`, …).
9
+
10
+ const LOCALE_SEGMENTS = new Set(['kk', 'en'])
11
+
12
+ interface NavTarget { to: string }
13
+ interface NavModule { link: string, items: NavTarget[] }
14
+ interface NormalizedUrl { host: string, key: string }
15
+
16
+ function safeUrl(href: string): URL | null {
17
+ try {
18
+ return new URL(href)
19
+ }
20
+ catch {
21
+ return null
22
+ }
23
+ }
24
+
25
+ // Reduce a URL to a comparable identity: host + locale-stripped path + query.
26
+ // The query is kept because it can be significant (fines main is `/?check_fines=true`).
27
+ function normalize(href: string): NormalizedUrl | null {
28
+ const url = safeUrl(href)
29
+ if (!url) return null
30
+
31
+ const segments = url.pathname
32
+ .split('/')
33
+ .filter(segment => segment && !LOCALE_SEGMENTS.has(segment))
34
+
35
+ const path = `/${segments.join('/')}`
36
+ return { host: url.host, key: path + url.search }
37
+ }
38
+
39
+ // A "root" target has no path segments (e.g. `/` or `/?check_fines=true`).
40
+ // Roots must match exactly, otherwise their prefix would light up every sub-page.
41
+ function isRoot(key: string): boolean {
42
+ return key === '/' || key.startsWith('/?')
43
+ }
44
+
45
+ export function useActiveNavigation() {
46
+ const route = useRoute()
47
+
48
+ // Recompute on every SPA navigation. Host stays constant within an app, so we
49
+ // read it from the request URL (SSR-safe); the path/query come from the route.
50
+ const current = computed<NormalizedUrl | null>(() => {
51
+ const host = useRequestURL().host
52
+ return normalize(`https://${host}${route.fullPath}`)
53
+ })
54
+
55
+ function isActiveModule(module: NavModule): boolean {
56
+ if (!current.value) return false
57
+
58
+ const hosts = new Set<string>()
59
+ const link = normalize(module.link)
60
+ if (link) hosts.add(link.host)
61
+ for (const item of module.items) {
62
+ const item_url = normalize(item.to)
63
+ if (item_url) hosts.add(item_url.host)
64
+ }
65
+
66
+ return hosts.has(current.value.host)
67
+ }
68
+
69
+ function isActiveService(itemTo: string): boolean {
70
+ const cur = current.value
71
+ const item = normalize(itemTo)
72
+ if (!cur || !item || cur.host !== item.host) return false
73
+
74
+ // The car-check page redirects to a `car-result` URL; keep its item lit.
75
+ if (item.key.includes('check-car') && cur.key.includes('car-result')) return true
76
+
77
+ if (cur.key === item.key) return true
78
+ if (isRoot(item.key)) return false
79
+
80
+ return cur.key.startsWith(item.key)
81
+ }
82
+
83
+ return { isActiveModule, isActiveService }
84
+ }
@@ -0,0 +1,92 @@
1
+ import type { ComputedRef, Ref } from 'vue'
2
+
3
+ interface UseChipOverflowOptions {
4
+ container: Ref<HTMLElement | null>
5
+ measure: Ref<HTMLElement | null>
6
+ count: Ref<number>
7
+ contentKey?: Ref<unknown>
8
+ gap?: number
9
+ }
10
+
11
+ interface UseChipOverflow {
12
+ visibleCount: Ref<number>
13
+ hiddenCount: ComputedRef<number>
14
+ }
15
+
16
+ export function useChipOverflow(options: UseChipOverflowOptions): UseChipOverflow {
17
+ const { container, measure, count, contentKey, gap = 6 } = options
18
+
19
+ const availableWidth = ref(0)
20
+ const visibleCount = ref(0)
21
+
22
+ // Ceil so a chip is only counted as fitting when it fully fits;
23
+ // sub-pixel underestimates would let flexbox squeeze visible chips into truncation.
24
+ function chipWidth(el: HTMLElement): number {
25
+ return Math.ceil(el.getBoundingClientRect().width)
26
+ }
27
+
28
+ function recompute() {
29
+ const total = count.value
30
+ if (total <= 0) {
31
+ visibleCount.value = 0
32
+ return
33
+ }
34
+
35
+ const avail = availableWidth.value
36
+ const root = measure.value
37
+
38
+ if (!avail || !root) {
39
+ visibleCount.value = total
40
+ return
41
+ }
42
+
43
+ const chips = (Array.from(root.children) as HTMLElement[]).slice(0, total)
44
+
45
+ let used = 0
46
+ let fit = 0
47
+ for (let i = 0; i < chips.length; i++) {
48
+ const width = chipWidth(chips[i]) + (i > 0 ? gap : 0)
49
+ if (used + width > avail) break
50
+ used += width
51
+ fit++
52
+ }
53
+
54
+ if (fit >= total) {
55
+ visibleCount.value = total
56
+ return
57
+ }
58
+
59
+ const badge = root.children[total] as HTMLElement | undefined
60
+ const badgeWidth = (badge ? chipWidth(badge) : 0) + gap
61
+ while (fit > 1 && used + badgeWidth > avail) {
62
+ used -= chipWidth(chips[fit - 1]) + gap
63
+ fit--
64
+ }
65
+
66
+ visibleCount.value = Math.max(1, fit)
67
+ }
68
+
69
+ let resizeObserver: ResizeObserver | null = null
70
+
71
+ onMounted(() => {
72
+ if (container.value) {
73
+ availableWidth.value = Math.floor(container.value.clientWidth)
74
+ resizeObserver = new ResizeObserver((entries) => {
75
+ availableWidth.value = Math.floor(entries[0]?.contentRect.width ?? 0)
76
+ })
77
+ resizeObserver.observe(container.value)
78
+ }
79
+ recompute()
80
+ })
81
+
82
+ onUnmounted(() => resizeObserver?.disconnect())
83
+
84
+ watch(
85
+ contentKey ? [availableWidth, count, contentKey] : [availableWidth, count],
86
+ () => nextTick(recompute),
87
+ )
88
+
89
+ const hiddenCount = computed(() => Math.max(0, count.value - visibleCount.value))
90
+
91
+ return { visibleCount, hiddenCount }
92
+ }