adata-ui 2.1.40-beta.1 → 2.1.40-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/elements/a-select-row/ASelectRowV2.vue +214 -0
- package/components/elements/button/AButtonV2.vue +112 -0
- package/components/elements/leave-note/ALeaveNote.vue +38 -17
- package/components/elements/segmented/ASegmentedV2.vue +58 -0
- package/components/elements/select/ASelectV2.vue +589 -0
- package/components/elements/show-more/AShowMoreV2.vue +26 -0
- package/components/elements/star-rating/AStarRating.vue +12 -4
- package/components/elements/status-badge-v2/StatusBadgeV2.vue +78 -0
- package/components/forms/checkbox/ACheckboxV2.vue +229 -0
- package/components/forms/feedback-form/FeedbackForm.vue +8 -8
- package/components/forms/input/AInputV2.vue +542 -0
- package/components/forms/input/textarea/ATextareaV2.vue +248 -0
- package/components/forms/toggle/AToggleV2.vue +71 -0
- package/components/modals/AnotherDeviceModal.vue +22 -18
- package/components/modals/ApplicationAcceptedModal.vue +12 -15
- package/components/modals/ConnectingTariffModal.vue +13 -20
- package/components/modals/Insufficient-funds-modal.vue +18 -33
- package/components/modals/LimitReachedModal.vue +4 -3
- package/components/modals/NoAccessModal.vue +9 -11
- package/components/modals/PaymentMethodModal.vue +1 -1
- package/components/modals/ReportBugConfirmModal.vue +12 -10
- package/components/modals/ReportBugModal.vue +42 -36
- package/components/modals/Resend.vue +9 -10
- package/components/modals/SubmitApplicationModal.vue +27 -24
- package/components/modals/id/IdConfirmAccountOtpModal.vue +4 -9
- package/components/modals/id/IdLoginModal.vue +3 -8
- package/components/modals/id/IdModals.vue +23 -10
- package/components/modals/id/IdTwoFactorModal.vue +3 -8
- package/components/navigation/header/AHeader.vue +5 -5
- package/components/navigation/header/HeaderLink.vue +15 -21
- package/components/navigation/header/NavList.vue +8 -32
- package/components/navigation/header/ProductMenu.vue +52 -50
- package/components/navigation/pill-tabs/APillTabsV2.vue +118 -0
- package/components/overlays/modal/AModalV2.vue +388 -0
- package/composables/useAccessTokenCookie.ts +25 -0
- package/composables/useChipOverflow.ts +92 -0
- package/composables/useHeaderNavigationLinks.ts +53 -23
- package/icons/arrow/arrow-right.vue +14 -0
- package/icons/briefcase.vue +14 -0
- package/icons/car-side.vue +16 -0
- package/icons/cart.vue +15 -0
- package/icons/download-rounded.vue +15 -0
- package/icons/shield-verified.vue +14 -0
- package/lang/en.ts +34 -4
- package/lang/kk.ts +35 -5
- package/lang/ru.ts +36 -6
- package/package.json +1 -1
- package/shared/constans/pages.ts +8 -2
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { onKeyStroke, useScrollLock, useWindowSize } from '@vueuse/core'
|
|
3
|
+
|
|
4
|
+
type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | 'container' | 'fullscreen'
|
|
5
|
+
|
|
6
|
+
const props = withDefaults(defineProps<{
|
|
7
|
+
title?: string
|
|
8
|
+
size?: ModalSize
|
|
9
|
+
width?: string
|
|
10
|
+
closeOnOverlay?: boolean
|
|
11
|
+
closeOnEsc?: boolean
|
|
12
|
+
lockScroll?: boolean
|
|
13
|
+
persistent?: boolean
|
|
14
|
+
transition?: boolean
|
|
15
|
+
contentClass?: string
|
|
16
|
+
hideHeader?: boolean
|
|
17
|
+
blur?: boolean | 'sm' | 'md' | 'lg' | 'xl'
|
|
18
|
+
mobileSheet?: boolean
|
|
19
|
+
swipeToClose?: boolean
|
|
20
|
+
swipeThreshold?: number
|
|
21
|
+
showDragHandle?: boolean
|
|
22
|
+
}>(), {
|
|
23
|
+
size: 'md',
|
|
24
|
+
closeOnOverlay: true,
|
|
25
|
+
closeOnEsc: true,
|
|
26
|
+
lockScroll: true,
|
|
27
|
+
persistent: false,
|
|
28
|
+
transition: true,
|
|
29
|
+
hideHeader: false,
|
|
30
|
+
blur: false,
|
|
31
|
+
mobileSheet: true,
|
|
32
|
+
swipeToClose: true,
|
|
33
|
+
swipeThreshold: 65,
|
|
34
|
+
showDragHandle: true,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const emit = defineEmits<{
|
|
38
|
+
(e: 'close'): void
|
|
39
|
+
(e: 'open'): void
|
|
40
|
+
}>()
|
|
41
|
+
|
|
42
|
+
const open = defineModel<boolean>({ required: true })
|
|
43
|
+
|
|
44
|
+
const contentRef = ref<HTMLElement | null>(null)
|
|
45
|
+
const overlayRef = ref<HTMLElement | null>(null)
|
|
46
|
+
const swipeClosing = ref(false)
|
|
47
|
+
const bodyScrollLocked = useScrollLock(import.meta.client ? document.body : null)
|
|
48
|
+
const { width: windowWidth } = useWindowSize()
|
|
49
|
+
|
|
50
|
+
const isMobile = computed(() => windowWidth.value < 1025)
|
|
51
|
+
const isSheet = computed(() => isMobile.value && props.mobileSheet)
|
|
52
|
+
|
|
53
|
+
const sizeStyle = computed(() => {
|
|
54
|
+
if (isSheet.value) return { width: '100%' }
|
|
55
|
+
if (props.width) return { width: props.width }
|
|
56
|
+
|
|
57
|
+
switch (props.size) {
|
|
58
|
+
case 'sm': return { width: '380px' }
|
|
59
|
+
case 'md': return { width: '520px' }
|
|
60
|
+
case 'lg': return { width: '720px' }
|
|
61
|
+
case 'xl': return { width: '1100px' }
|
|
62
|
+
case 'container': return {}
|
|
63
|
+
case 'fullscreen': return { width: '100vw', height: '100vh' }
|
|
64
|
+
default: return { width: '520px' }
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const blurClass = computed(() => {
|
|
69
|
+
if (!props.blur) return ''
|
|
70
|
+
|
|
71
|
+
switch (props.blur) {
|
|
72
|
+
case 'sm': return 'backdrop-blur-sm'
|
|
73
|
+
case 'md': return 'backdrop-blur-md'
|
|
74
|
+
case 'lg': return 'backdrop-blur-lg'
|
|
75
|
+
case 'xl': return 'backdrop-blur-xl'
|
|
76
|
+
default: return 'backdrop-blur'
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const overlayClass = computed(() => {
|
|
81
|
+
const base = ['modal-v2-overlay fixed inset-0 z-[10000] flex bg-black/40 dark:bg-black/60']
|
|
82
|
+
|
|
83
|
+
if (isSheet.value) {
|
|
84
|
+
base.push('items-end justify-stretch')
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
base.push(props.size === 'container' ? 'items-center justify-center' : 'items-center justify-center p-4')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (blurClass.value) base.push(blurClass.value)
|
|
91
|
+
|
|
92
|
+
return base.join(' ')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const contentBaseClass = computed(() => {
|
|
96
|
+
const base = ['modal-v2-content flex flex-col overflow-hidden border bg-white shadow-2xl dark:bg-gray-900']
|
|
97
|
+
|
|
98
|
+
if (isSheet.value) {
|
|
99
|
+
base.push('w-full max-w-none max-h-[100dvh] rounded-t-2xl rounded-b-none border-x-0 border-b-0 border-t border-gray-200 dark:border-gray-800')
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const radius = props.size === 'fullscreen' ? 'rounded-none' : 'rounded-2xl'
|
|
103
|
+
const widthCls = props.size === 'container'
|
|
104
|
+
? ' w-full max-w-screen-sm md:max-w-screen-md lg:max-w-screen-lg xl:max-w-screen-xl 2xl:max-w-screen-2xl'
|
|
105
|
+
: ''
|
|
106
|
+
base.push(`max-h-[calc(100vh-2rem)] border-gray-200 dark:border-gray-800 ${radius}${widthCls}`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return base.join(' ')
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const overlayTransitionName = computed(() => {
|
|
113
|
+
if (swipeClosing.value) return ''
|
|
114
|
+
return props.transition ? 'modal-v2-overlay' : ''
|
|
115
|
+
})
|
|
116
|
+
const contentTransitionName = computed(() => {
|
|
117
|
+
if (swipeClosing.value || !props.transition) return ''
|
|
118
|
+
return isSheet.value ? 'modal-v2-sheet' : 'modal-v2-content'
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
function closeIfAllowed() {
|
|
122
|
+
if (props.persistent) return
|
|
123
|
+
open.value = false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function onOverlayClick(event: MouseEvent) {
|
|
127
|
+
if (!props.closeOnOverlay || props.persistent) return
|
|
128
|
+
if (event.target === event.currentTarget) {
|
|
129
|
+
open.value = false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
onKeyStroke('Escape', () => {
|
|
134
|
+
if (!open.value) return
|
|
135
|
+
if (!props.closeOnEsc || props.persistent) return
|
|
136
|
+
open.value = false
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
function setScrollbarCompensation(active: boolean) {
|
|
140
|
+
if (!import.meta.client) return
|
|
141
|
+
|
|
142
|
+
if (active) {
|
|
143
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth
|
|
144
|
+
document.body.style.paddingRight = scrollbarWidth > 0 ? `${scrollbarWidth}px` : ''
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
document.body.style.paddingRight = ''
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
watch(open, (value) => {
|
|
152
|
+
if (props.lockScroll) {
|
|
153
|
+
if (value) setScrollbarCompensation(true)
|
|
154
|
+
bodyScrollLocked.value = value
|
|
155
|
+
if (!value) setScrollbarCompensation(false)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (value) emit('open')
|
|
159
|
+
else emit('close')
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
onBeforeUnmount(() => {
|
|
163
|
+
if (bodyScrollLocked.value) {
|
|
164
|
+
bodyScrollLocked.value = false
|
|
165
|
+
}
|
|
166
|
+
setScrollbarCompensation(false)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const dragging = ref(false)
|
|
170
|
+
const dragStartY = ref(0)
|
|
171
|
+
const dragDelta = ref(0)
|
|
172
|
+
|
|
173
|
+
function canSwipe() {
|
|
174
|
+
return isSheet.value && props.swipeToClose && !props.persistent
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function setTransform(value: string) {
|
|
178
|
+
if (contentRef.value) {
|
|
179
|
+
contentRef.value.style.transform = value
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function onTouchStart(event: TouchEvent) {
|
|
184
|
+
if (!canSwipe()) return
|
|
185
|
+
dragging.value = true
|
|
186
|
+
dragStartY.value = event.touches[0].clientY
|
|
187
|
+
dragDelta.value = 0
|
|
188
|
+
|
|
189
|
+
if (contentRef.value) {
|
|
190
|
+
contentRef.value.style.transition = 'none'
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function onTouchMove(event: TouchEvent) {
|
|
195
|
+
if (!dragging.value || !canSwipe()) return
|
|
196
|
+
|
|
197
|
+
const delta = event.touches[0].clientY - dragStartY.value
|
|
198
|
+
if (delta < 0) {
|
|
199
|
+
dragDelta.value = 0
|
|
200
|
+
setTransform('')
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
dragDelta.value = delta
|
|
205
|
+
setTransform(`translateY(${delta}px)`)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function prefersReducedMotion() {
|
|
209
|
+
return import.meta.client && window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function animateClose() {
|
|
213
|
+
const content = contentRef.value
|
|
214
|
+
|
|
215
|
+
if (!content || prefersReducedMotion()) {
|
|
216
|
+
open.value = false
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
swipeClosing.value = true
|
|
221
|
+
|
|
222
|
+
let done = false
|
|
223
|
+
const finish = () => {
|
|
224
|
+
if (done) return
|
|
225
|
+
done = true
|
|
226
|
+
content.removeEventListener('transitionend', onTransitionEnd)
|
|
227
|
+
open.value = false
|
|
228
|
+
swipeClosing.value = false
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function onTransitionEnd(event: TransitionEvent) {
|
|
232
|
+
if (event.target === content && event.propertyName === 'transform') {
|
|
233
|
+
finish()
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
content.addEventListener('transitionend', onTransitionEnd)
|
|
238
|
+
setTimeout(finish, 320)
|
|
239
|
+
|
|
240
|
+
requestAnimationFrame(() => {
|
|
241
|
+
content.style.transition = 'transform 260ms cubic-bezier(0.22, 1, 0.36, 1)'
|
|
242
|
+
content.style.transform = 'translateY(100%)'
|
|
243
|
+
|
|
244
|
+
if (overlayRef.value) {
|
|
245
|
+
overlayRef.value.style.transition = 'opacity 260ms ease'
|
|
246
|
+
overlayRef.value.style.opacity = '0'
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function onTouchEnd() {
|
|
252
|
+
if (!dragging.value) return
|
|
253
|
+
dragging.value = false
|
|
254
|
+
|
|
255
|
+
const shouldClose = dragDelta.value > props.swipeThreshold && canSwipe()
|
|
256
|
+
|
|
257
|
+
if (shouldClose) {
|
|
258
|
+
animateClose()
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
if (contentRef.value) {
|
|
262
|
+
contentRef.value.style.transition = 'transform 220ms cubic-bezier(0.22, 1, 0.36, 1)'
|
|
263
|
+
contentRef.value.style.transform = ''
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
dragDelta.value = 0
|
|
268
|
+
}
|
|
269
|
+
</script>
|
|
270
|
+
|
|
271
|
+
<template>
|
|
272
|
+
<client-only>
|
|
273
|
+
<teleport to="body">
|
|
274
|
+
<transition :name="overlayTransitionName">
|
|
275
|
+
<div
|
|
276
|
+
v-if="open"
|
|
277
|
+
ref="overlayRef"
|
|
278
|
+
:class="overlayClass"
|
|
279
|
+
@mousedown="onOverlayClick"
|
|
280
|
+
>
|
|
281
|
+
<transition :name="contentTransitionName" appear>
|
|
282
|
+
<div
|
|
283
|
+
v-if="open"
|
|
284
|
+
ref="contentRef"
|
|
285
|
+
:class="contentBaseClass"
|
|
286
|
+
:style="sizeStyle"
|
|
287
|
+
@mousedown.stop
|
|
288
|
+
>
|
|
289
|
+
<div
|
|
290
|
+
v-if="isSheet && showDragHandle"
|
|
291
|
+
class="flex shrink-0 cursor-grab touch-none justify-center py-2"
|
|
292
|
+
@touchstart.passive="onTouchStart"
|
|
293
|
+
@touchmove="onTouchMove"
|
|
294
|
+
@touchend="onTouchEnd"
|
|
295
|
+
@touchcancel="onTouchEnd"
|
|
296
|
+
>
|
|
297
|
+
<div class="h-1 w-10 rounded-full bg-gray-300 dark:bg-gray-600" />
|
|
298
|
+
</div>
|
|
299
|
+
|
|
300
|
+
<header
|
|
301
|
+
v-if="!hideHeader"
|
|
302
|
+
class="flex shrink-0 items-center justify-between gap-3 border-b border-gray-200 px-5 py-4 dark:border-gray-800"
|
|
303
|
+
@touchstart.passive="onTouchStart"
|
|
304
|
+
@touchmove="onTouchMove"
|
|
305
|
+
@touchend="onTouchEnd"
|
|
306
|
+
@touchcancel="onTouchEnd"
|
|
307
|
+
>
|
|
308
|
+
<slot name="header">
|
|
309
|
+
<h2 v-if="title" class="text-base font-semibold text-gray-900 dark:text-gray-100">
|
|
310
|
+
{{ title }}
|
|
311
|
+
</h2>
|
|
312
|
+
</slot>
|
|
313
|
+
<button
|
|
314
|
+
type="button"
|
|
315
|
+
class="ml-auto rounded-md p-1 transition hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-gray-200"
|
|
316
|
+
@click="closeIfAllowed"
|
|
317
|
+
>
|
|
318
|
+
<a-icon-x-mark class="size-4" />
|
|
319
|
+
</button>
|
|
320
|
+
</header>
|
|
321
|
+
|
|
322
|
+
<div class="min-h-0 flex-1 overflow-auto" :class="contentClass">
|
|
323
|
+
<slot :close="closeIfAllowed" />
|
|
324
|
+
</div>
|
|
325
|
+
|
|
326
|
+
<footer
|
|
327
|
+
v-if="$slots.footer"
|
|
328
|
+
class="shrink-0 border-t border-gray-200 px-5 py-4 dark:border-gray-800"
|
|
329
|
+
>
|
|
330
|
+
<slot name="footer" :close="closeIfAllowed" />
|
|
331
|
+
</footer>
|
|
332
|
+
</div>
|
|
333
|
+
</transition>
|
|
334
|
+
</div>
|
|
335
|
+
</transition>
|
|
336
|
+
</teleport>
|
|
337
|
+
</client-only>
|
|
338
|
+
</template>
|
|
339
|
+
|
|
340
|
+
<style scoped lang="scss">
|
|
341
|
+
.modal-v2-overlay-enter-active {
|
|
342
|
+
transition: opacity 200ms ease-out;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.modal-v2-overlay-leave-active {
|
|
346
|
+
transition: opacity 150ms ease-in;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.modal-v2-overlay-enter-from,
|
|
350
|
+
.modal-v2-overlay-leave-to {
|
|
351
|
+
opacity: 0;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
.modal-v2-content-enter-active {
|
|
355
|
+
transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1), opacity 220ms ease-out;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.modal-v2-content-leave-active {
|
|
359
|
+
transition: transform 150ms ease-in, opacity 150ms ease-in;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.modal-v2-content-enter-from,
|
|
363
|
+
.modal-v2-content-leave-to {
|
|
364
|
+
opacity: 0;
|
|
365
|
+
transform: translateY(8px) scale(0.98);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.modal-v2-sheet-enter-active,
|
|
369
|
+
.modal-v2-sheet-leave-active {
|
|
370
|
+
transition: transform 280ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.modal-v2-sheet-enter-from,
|
|
374
|
+
.modal-v2-sheet-leave-to {
|
|
375
|
+
transform: translateY(100%);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@media (prefers-reduced-motion: reduce) {
|
|
379
|
+
.modal-v2-overlay-enter-active,
|
|
380
|
+
.modal-v2-overlay-leave-active,
|
|
381
|
+
.modal-v2-content-enter-active,
|
|
382
|
+
.modal-v2-content-leave-active,
|
|
383
|
+
.modal-v2-sheet-enter-active,
|
|
384
|
+
.modal-v2-sheet-leave-active {
|
|
385
|
+
transition: none;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
</style>
|
|
@@ -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,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
|
+
}
|
|
@@ -9,12 +9,9 @@ import IconBlock from '#adata-ui/icons/block.vue'
|
|
|
9
9
|
import IconDollar from '#adata-ui/icons/currency/currency-dollar.vue'
|
|
10
10
|
import IconWork from '#adata-ui/icons/work-bag.vue'
|
|
11
11
|
import IconDocument from '#adata-ui/icons/hdocument.vue'
|
|
12
|
-
import IconCheck from '#adata-ui/icons/hcheck.vue'
|
|
13
|
-
import IconFile from '#adata-ui/icons/file/file.vue'
|
|
14
12
|
import IconCheckCircle from '#adata-ui/icons/check/check-circle.vue'
|
|
15
13
|
import IconCar from '#adata-ui/icons/car.vue'
|
|
16
14
|
import IconTruck from '#adata-ui/icons/truck.vue'
|
|
17
|
-
import IconClipboardText from '#adata-ui/icons/clipboard-text.vue'
|
|
18
15
|
import IconArrowGraph from '#adata-ui/icons/arrow/arrow-graph-up.vue'
|
|
19
16
|
import IconMedal from '#adata-ui/icons/medal.vue'
|
|
20
17
|
import IconCompany from '#adata-ui/icons/company.vue'
|
|
@@ -26,24 +23,27 @@ import { buildLocalizedUrl } from '#adata-ui/utils/localizedNavigation'
|
|
|
26
23
|
import IconLock from '#adata-ui/icons/lock.vue'
|
|
27
24
|
import IconReceipt from '#adata-ui/icons/receipt.vue'
|
|
28
25
|
import IconFavorite from '#adata-ui/icons/bookmark/bookmark.vue'
|
|
29
|
-
// import IconReport from '#adata-ui/icons/file/file.vue'
|
|
30
26
|
import IconHistory from '#adata-ui/icons/history.vue'
|
|
31
27
|
import IconMessage from '#adata-ui/icons/message/message.vue'
|
|
28
|
+
import AIconBriefcase from '#adata-ui/icons/briefcase.vue'
|
|
29
|
+
import AIconCarSide from '#adata-ui/icons/car-side.vue'
|
|
30
|
+
import AIconShieldVerified from '#adata-ui/icons/shield-verified.vue'
|
|
31
|
+
import AIconCart from '#adata-ui/icons/cart.vue'
|
|
32
32
|
import {
|
|
33
|
-
AIconWorkBag,
|
|
34
33
|
AIconUsersThree,
|
|
35
|
-
AIconReceipt,
|
|
36
|
-
AIconCar,
|
|
37
34
|
AIconArrowGraphUp,
|
|
38
35
|
AIconGlobe,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
AIconArrowSquareDown,
|
|
37
|
+
AIconExpandWindow,
|
|
38
|
+
AIconUsers,
|
|
39
|
+
AIconWarningTriangle,
|
|
40
|
+
AIconSearch,
|
|
41
|
+
AIconAkimat,
|
|
42
|
+
AIconCurrencyTenge,
|
|
43
|
+
AIconExcelIcon, AIconFile,
|
|
42
44
|
} from '#components'
|
|
43
45
|
import MapPinRect from '#adata-ui/icons/map/map-pin-rect.vue'
|
|
44
46
|
import MapPaper from '#adata-ui/icons/map-paper.vue'
|
|
45
|
-
import ChartBar from '#adata-ui/icons/chart-bar.vue'
|
|
46
|
-
import Bank from '#adata-ui/icons/bank.vue'
|
|
47
47
|
import { useUrls } from '#adata-ui/composables/useUrls'
|
|
48
48
|
import AiAssistant from '#adata-ui/icons/ai-assistant.vue'
|
|
49
49
|
import Tasks from '#adata-ui/icons/tasks.vue'
|
|
@@ -61,7 +61,7 @@ export const useHeaderNavigationLinks = () => {
|
|
|
61
61
|
key: 'pk',
|
|
62
62
|
name: t('header.products.counterparties.label'),
|
|
63
63
|
link: buildLocalizedUrl(locale, urls.pk, PAGES.pk.main),
|
|
64
|
-
icon:
|
|
64
|
+
icon: AIconUsers,
|
|
65
65
|
to: urls.pk,
|
|
66
66
|
data_attribute: 'navigation-menu-counterparty-block',
|
|
67
67
|
items: [
|
|
@@ -115,7 +115,7 @@ export const useHeaderNavigationLinks = () => {
|
|
|
115
115
|
key: 'fines',
|
|
116
116
|
name: t('header.products.fines.label'),
|
|
117
117
|
link: buildLocalizedUrl(locale, urls.avto, PAGES.fines.main),
|
|
118
|
-
icon:
|
|
118
|
+
icon: AIconCarSide,
|
|
119
119
|
data_attribute: 'navigation-menu-fines-block',
|
|
120
120
|
items: [
|
|
121
121
|
{
|
|
@@ -142,7 +142,7 @@ export const useHeaderNavigationLinks = () => {
|
|
|
142
142
|
key: 'work',
|
|
143
143
|
name: t('header.products.jobs.label'),
|
|
144
144
|
link: urls.work,
|
|
145
|
-
icon:
|
|
145
|
+
icon: AIconBriefcase,
|
|
146
146
|
data_attribute: 'navigation-menu-work-block',
|
|
147
147
|
items: [
|
|
148
148
|
{
|
|
@@ -216,7 +216,7 @@ export const useHeaderNavigationLinks = () => {
|
|
|
216
216
|
key: 'tenders',
|
|
217
217
|
name: t('header.products.tenders.label'),
|
|
218
218
|
link: urls.zakupki,
|
|
219
|
-
icon:
|
|
219
|
+
icon: AIconCart,
|
|
220
220
|
is_new: true,
|
|
221
221
|
to: urls.tender,
|
|
222
222
|
data_attribute: 'navigation-menu-zakupki-block',
|
|
@@ -264,15 +264,39 @@ export const useHeaderNavigationLinks = () => {
|
|
|
264
264
|
is_new: true,
|
|
265
265
|
name: t('header.products.edo.label'),
|
|
266
266
|
link: buildLocalizedUrl(locale, urls.edo_docs , ''),
|
|
267
|
-
icon:
|
|
267
|
+
icon: AIconFile,
|
|
268
268
|
data_attribute: 'navigation-menu-edo-block',
|
|
269
269
|
items: [
|
|
270
270
|
{
|
|
271
|
-
title: t('header.products.edo.items.
|
|
272
|
-
subtitle: t('header.products.edo.items.
|
|
273
|
-
icon:
|
|
274
|
-
to: buildLocalizedUrl(locale, urls.edo_docs ,
|
|
275
|
-
}
|
|
271
|
+
title: t('header.products.edo.items.docSign.t'),
|
|
272
|
+
subtitle: t('header.products.edo.items.docSign.st'),
|
|
273
|
+
icon: AIconUsers,
|
|
274
|
+
to: buildLocalizedUrl(locale, urls.edo_docs , PAGES.edo.docSign)
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
title: t('header.products.edo.items.docTemplate.t'),
|
|
278
|
+
subtitle: t('header.products.edo.items.docTemplate.st'),
|
|
279
|
+
icon: AIconUsers,
|
|
280
|
+
to: buildLocalizedUrl(locale, urls.edo_docs , PAGES.edo.docTemplate)
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
title: t('header.products.edo.items.docRisk.t'),
|
|
284
|
+
subtitle: t('header.products.edo.items.docRisk.st'),
|
|
285
|
+
icon: AIconUsers,
|
|
286
|
+
to: buildLocalizedUrl(locale, urls.edo_docs , PAGES.edo.docRisk)
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
title: t('header.products.edo.items.docCompare.t'),
|
|
290
|
+
subtitle: t('header.products.edo.items.docCompare.st'),
|
|
291
|
+
icon: AIconUsers,
|
|
292
|
+
to: buildLocalizedUrl(locale, urls.edo_docs , PAGES.edo.docCompare)
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
title: t('header.products.edo.items.docAi.t'),
|
|
296
|
+
subtitle: t('header.products.edo.items.docAi.st'),
|
|
297
|
+
icon: AIconUsers,
|
|
298
|
+
to: buildLocalizedUrl(locale, urls.edo_docs , PAGES.edo.docAi)
|
|
299
|
+
},
|
|
276
300
|
]
|
|
277
301
|
},
|
|
278
302
|
{
|
|
@@ -280,7 +304,7 @@ export const useHeaderNavigationLinks = () => {
|
|
|
280
304
|
is_new: true,
|
|
281
305
|
name: t('header.products.compliance.label'),
|
|
282
306
|
link: buildLocalizedUrl(locale, urls.compliance, PAGES.compliance.l),
|
|
283
|
-
icon:
|
|
307
|
+
icon: AIconShieldVerified,
|
|
284
308
|
data_attribute: 'navigation-menu-compliance-block',
|
|
285
309
|
items: [
|
|
286
310
|
{
|
|
@@ -352,6 +376,12 @@ export const useHeaderNavigationLinks = () => {
|
|
|
352
376
|
subtitle: t('header.products.analytics.items.subjects.subtitle'),
|
|
353
377
|
icon: IconCompany,
|
|
354
378
|
to: buildLocalizedUrl(locale, urls.analytics, PAGES.analytics.business)
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
title: t('header.products.analytics.items.demography.title'),
|
|
382
|
+
subtitle: t('header.products.analytics.items.demography.subtitle'),
|
|
383
|
+
icon: IconCompany,
|
|
384
|
+
to: buildLocalizedUrl(locale, urls.analytics, PAGES.analytics.demography)
|
|
355
385
|
}
|
|
356
386
|
]
|
|
357
387
|
},
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
7
|
+
<path d="M3.125 7.5H11.875" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8
|
+
<path d="M7.5 3.125L11.875 7.5L7.5 11.875" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
|
|
9
|
+
</svg>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<style scoped>
|
|
13
|
+
|
|
14
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<template>
|
|
6
|
+
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
7
|
+
<path d="M15 5.25H3C2.17157 5.25 1.5 5.92157 1.5 6.75V14.25C1.5 15.0784 2.17157 15.75 3 15.75H15C15.8284 15.75 16.5 15.0784 16.5 14.25V6.75C16.5 5.92157 15.8284 5.25 15 5.25Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
8
|
+
<path d="M12 15.75V3.75C12 3.35218 11.842 2.97064 11.5607 2.68934C11.2794 2.40804 10.8978 2.25 10.5 2.25H7.5C7.10218 2.25 6.72064 2.40804 6.43934 2.68934C6.15804 2.97064 6 3.35218 6 3.75V15.75" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
9
|
+
</svg>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<style scoped>
|
|
13
|
+
|
|
14
|
+
</style>
|