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,226 @@
1
+ <script lang="ts" setup>
2
+ type CountColor = 'gray' | 'blue' | 'yellow' | 'red' | 'green' | 'neutral'
3
+ type PillTabsType = 'pill' | 'underline'
4
+
5
+ export interface PillTab {
6
+ key: string
7
+ name: string
8
+ count?: number | string
9
+ countColor?: CountColor
10
+ disabled?: boolean
11
+ hideCount?: boolean
12
+ }
13
+
14
+ const props = withDefaults(defineProps<{
15
+ modelValue: string
16
+ options: PillTab[]
17
+ showCount?: boolean
18
+ defaultCountColor?: CountColor
19
+ ariaLabel?: string
20
+ block?: boolean
21
+ disabled?: boolean
22
+ size?: 'sm' | 'md'
23
+ scrollIntoView?: boolean
24
+ type?: PillTabsType
25
+ }>(), {
26
+ showCount: false,
27
+ block: false,
28
+ disabled: false,
29
+ size: 'sm',
30
+ scrollIntoView: false,
31
+ type: 'pill',
32
+ })
33
+
34
+ const emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>()
35
+
36
+ const listRef = ref<HTMLElement | null>(null)
37
+ const indicatorStyle = ref<Record<string, string>>({ width: '0px', transform: 'translateX(0px)', opacity: '0' })
38
+ const indicatorAnimate = ref(false)
39
+ let resizeObserver: ResizeObserver | null = null
40
+
41
+ const SIZE_CLASS: Record<'sm' | 'md', string> = {
42
+ sm: 'text-body-sm',
43
+ md: 'text-body-md',
44
+ }
45
+
46
+ const COUNT_COLOR_CLASS: Record<CountColor, string> = {
47
+ gray: 'bg-gray-200/70 text-gray-600 dark:bg-white/[0.08] dark:text-gray-300',
48
+ blue: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
49
+ yellow: 'bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-300',
50
+ red: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300',
51
+ green: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300',
52
+ neutral: 'bg-gray-100 text-gray-500 dark:bg-white/[0.06] dark:text-gray-400',
53
+ }
54
+
55
+ const { t, te } = useI18n()
56
+
57
+ const isUnderline = computed(() => props.type === 'underline')
58
+
59
+ function hasCount(option: PillTab) {
60
+ if (typeof option.count === 'string') {
61
+ return !!Number(String(option.count).replace(/\s/g, ''))
62
+ }
63
+ return !!option.count
64
+ }
65
+
66
+ function formatCount(option: PillTab) {
67
+ if (typeof option.count === 'number') {
68
+ return option.count.toLocaleString('ru-RU')
69
+ }
70
+ return option.count
71
+ }
72
+
73
+ function resolveCountColor(option: PillTab): CountColor {
74
+ if (!hasCount(option)) return 'gray'
75
+ return option.countColor ?? props.defaultCountColor ?? 'gray'
76
+ }
77
+
78
+ function localize(label: string) {
79
+ return te(label) ? t(label) : label
80
+ }
81
+
82
+ function isDisabled(option: PillTab) {
83
+ return props.disabled || option.disabled === true
84
+ }
85
+
86
+ function select(option: PillTab) {
87
+ if (isDisabled(option)) return
88
+ emit('update:modelValue', option.key)
89
+ }
90
+
91
+ function scrollActiveIntoView() {
92
+ if (!props.scrollIntoView) return
93
+ nextTick(() => {
94
+ const active = listRef.value?.querySelector<HTMLElement>('[aria-selected="true"]')
95
+ active?.scrollIntoView({ behavior: 'smooth', inline: 'center', block: 'nearest' })
96
+ })
97
+ }
98
+
99
+ function updateIndicator() {
100
+ if (!isUnderline.value) return
101
+ nextTick(() => {
102
+ const list = listRef.value
103
+ const active = list?.querySelector<HTMLElement>('[role="tab"][aria-selected="true"]')
104
+ if (!list || !active) {
105
+ indicatorStyle.value = { ...indicatorStyle.value, opacity: '0' }
106
+ return
107
+ }
108
+ indicatorStyle.value = {
109
+ width: `${active.offsetWidth}px`,
110
+ transform: `translateX(${active.offsetLeft}px)`,
111
+ opacity: '1',
112
+ }
113
+ })
114
+ }
115
+
116
+ function onActiveChange() {
117
+ scrollActiveIntoView()
118
+ updateIndicator()
119
+ }
120
+
121
+ watch(() => props.modelValue, onActiveChange)
122
+ watch(() => props.options, updateIndicator, { deep: true })
123
+ watch(isUnderline, updateIndicator)
124
+
125
+ onMounted(() => {
126
+ scrollActiveIntoView()
127
+ updateIndicator()
128
+
129
+ if (typeof ResizeObserver !== 'undefined' && listRef.value) {
130
+ resizeObserver = new ResizeObserver(() => updateIndicator())
131
+ resizeObserver.observe(listRef.value)
132
+ }
133
+
134
+ nextTick(() => requestAnimationFrame(() => { indicatorAnimate.value = true }))
135
+ })
136
+
137
+ onBeforeUnmount(() => {
138
+ resizeObserver?.disconnect()
139
+ resizeObserver = null
140
+ })
141
+ </script>
142
+
143
+ <template>
144
+ <div
145
+ class="pill-tabs-v2 min-w-0 max-w-full"
146
+ :class="[
147
+ isUnderline
148
+ ? 'border-b border-gray-200 dark:border-white/10'
149
+ : 'rounded-lg border border-gray-200 bg-gray-50 p-1 dark:border-white/10 dark:bg-white/[0.03]',
150
+ block ? 'w-full' : 'w-fit',
151
+ disabled ? 'pill-tabs-v2--disabled opacity-60' : '',
152
+ ]"
153
+ :aria-disabled="disabled || undefined"
154
+ >
155
+ <div
156
+ ref="listRef"
157
+ role="tablist"
158
+ :aria-label="ariaLabel"
159
+ class="pill-tabs-v2__list relative flex w-full flex-nowrap items-center overflow-x-auto"
160
+ :class="isUnderline ? 'gap-0' : 'gap-1'"
161
+ >
162
+ <button
163
+ v-for="option in options"
164
+ :key="option.key"
165
+ type="button"
166
+ role="tab"
167
+ :aria-selected="modelValue === option.key"
168
+ :aria-disabled="isDisabled(option) || undefined"
169
+ :disabled="isDisabled(option)"
170
+ :tabindex="modelValue === option.key ? 0 : -1"
171
+ class="inline-flex items-center gap-1.5 whitespace-nowrap font-medium transition-colors duration-150"
172
+ :class="[
173
+ SIZE_CLASS[size],
174
+ isUnderline
175
+ ? [
176
+ 'px-4 py-2',
177
+ isDisabled(option)
178
+ ? 'cursor-not-allowed text-gray-300 dark:text-gray-600'
179
+ : modelValue === option.key
180
+ ? 'text-blue-600 dark:text-blue-400'
181
+ : 'text-gray-500 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200',
182
+ ]
183
+ : [
184
+ 'rounded-md px-3 py-1.5',
185
+ isDisabled(option)
186
+ ? 'cursor-not-allowed text-gray-300 dark:text-gray-600'
187
+ : modelValue === option.key
188
+ ? 'bg-white text-deepblue-900 shadow-sm dark:bg-gray-700 dark:text-gray-100 dark:shadow-none dark:ring-1 dark:ring-inset dark:ring-white/10'
189
+ : 'text-gray-700 hover:bg-white/70 hover:text-gray-900 dark:text-gray-200 dark:hover:bg-white/[0.06] dark:hover:text-gray-100',
190
+ ],
191
+ block ? 'flex-1 basis-0 justify-center' : 'shrink-0',
192
+ ]"
193
+ @click="select(option)"
194
+ >
195
+ <slot name="option" :option="option">
196
+ <span>{{ localize(option.name) }}</span>
197
+
198
+ <span
199
+ v-if="showCount && !option.hideCount"
200
+ class="inline-flex min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 text-body-sm font-semibold leading-5"
201
+ :class="COUNT_COLOR_CLASS[resolveCountColor(option)]"
202
+ >
203
+ {{ formatCount(option) }}
204
+ </span>
205
+ </slot>
206
+ </button>
207
+
208
+ <span
209
+ v-if="isUnderline"
210
+ aria-hidden="true"
211
+ class="pill-tabs-v2__indicator pointer-events-none absolute -bottom-px left-0 h-0.5 rounded-full bg-blue-600 dark:bg-blue-400"
212
+ :class="indicatorAnimate ? 'transition-[transform,width,opacity] duration-300 ease-out' : ''"
213
+ :style="indicatorStyle"
214
+ />
215
+ </div>
216
+ </div>
217
+ </template>
218
+
219
+ <style scoped>
220
+ .pill-tabs-v2__list {
221
+ scrollbar-width: none;
222
+ }
223
+ .pill-tabs-v2__list::-webkit-scrollbar {
224
+ display: none;
225
+ }
226
+ </style>
@@ -0,0 +1,335 @@
1
+ <script setup lang="ts">
2
+ import { onKeyStroke, useScrollLock } from '@vueuse/core'
3
+
4
+ const props = withDefaults(defineProps<{
5
+ title?: string
6
+ closeOnOverlay?: boolean
7
+ closeOnEsc?: boolean
8
+ lockScroll?: boolean
9
+ persistent?: boolean
10
+ transition?: boolean
11
+ contentClass?: string
12
+ hideHeader?: boolean
13
+ blur?: boolean | 'sm' | 'md' | 'lg' | 'xl'
14
+ swipeToClose?: boolean
15
+ swipeThreshold?: number
16
+ showDragHandle?: boolean
17
+ maxHeight?: string
18
+ }>(), {
19
+ closeOnOverlay: true,
20
+ closeOnEsc: true,
21
+ lockScroll: true,
22
+ persistent: false,
23
+ transition: true,
24
+ hideHeader: false,
25
+ blur: false,
26
+ swipeToClose: true,
27
+ swipeThreshold: 65,
28
+ showDragHandle: true,
29
+ maxHeight: '100dvh',
30
+ })
31
+
32
+ const emit = defineEmits<{
33
+ (e: 'close'): void
34
+ (e: 'open'): void
35
+ }>()
36
+
37
+ const open = defineModel<boolean>({ required: true })
38
+
39
+ const contentRef = ref<HTMLElement | null>(null)
40
+ const overlayRef = ref<HTMLElement | null>(null)
41
+ const swipeClosing = ref(false)
42
+ const bodyScrollLocked = useScrollLock(import.meta.client ? document.body : null)
43
+
44
+ const blurClass = computed(() => {
45
+ if (!props.blur) return ''
46
+
47
+ switch (props.blur) {
48
+ case 'sm': return 'backdrop-blur-sm'
49
+ case 'md': return 'backdrop-blur-md'
50
+ case 'lg': return 'backdrop-blur-lg'
51
+ case 'xl': return 'backdrop-blur-xl'
52
+ default: return 'backdrop-blur'
53
+ }
54
+ })
55
+
56
+ const overlayClass = computed(() => {
57
+ const base = ['drawer-v2-overlay fixed inset-0 z-[10000] flex items-end justify-stretch bg-black/40 dark:bg-black/60']
58
+ if (blurClass.value) base.push(blurClass.value)
59
+ return base.join(' ')
60
+ })
61
+
62
+ const contentStyle = computed(() => ({ maxHeight: props.maxHeight }))
63
+
64
+ const overlayTransitionName = computed(() => {
65
+ if (swipeClosing.value) return ''
66
+ return props.transition ? 'drawer-v2-overlay' : ''
67
+ })
68
+ const contentTransitionName = computed(() => {
69
+ if (swipeClosing.value || !props.transition) return ''
70
+ return 'drawer-v2-sheet'
71
+ })
72
+
73
+ function closeIfAllowed() {
74
+ if (props.persistent) return
75
+ open.value = false
76
+ }
77
+
78
+ function onOverlayClick(event: MouseEvent) {
79
+ if (!props.closeOnOverlay || props.persistent) return
80
+ if (event.target === event.currentTarget) {
81
+ open.value = false
82
+ }
83
+ }
84
+
85
+ onKeyStroke('Escape', () => {
86
+ if (!open.value) return
87
+ if (!props.closeOnEsc || props.persistent) return
88
+ open.value = false
89
+ })
90
+
91
+ function setScrollbarCompensation(active: boolean) {
92
+ if (!import.meta.client) return
93
+
94
+ if (active) {
95
+ const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth
96
+ document.body.style.paddingRight = scrollbarWidth > 0 ? `${scrollbarWidth}px` : ''
97
+ }
98
+ else {
99
+ document.body.style.paddingRight = ''
100
+ }
101
+ }
102
+
103
+ watch(open, (value) => {
104
+ if (props.lockScroll) {
105
+ if (value) setScrollbarCompensation(true)
106
+ bodyScrollLocked.value = value
107
+ if (!value) setScrollbarCompensation(false)
108
+ }
109
+
110
+ if (value) emit('open')
111
+ else emit('close')
112
+ })
113
+
114
+ onBeforeUnmount(() => {
115
+ if (bodyScrollLocked.value) {
116
+ bodyScrollLocked.value = false
117
+ }
118
+ setScrollbarCompensation(false)
119
+ })
120
+
121
+ const dragging = ref(false)
122
+ const dragStartY = ref(0)
123
+ const dragDelta = ref(0)
124
+
125
+ function canSwipe() {
126
+ return props.swipeToClose && !props.persistent
127
+ }
128
+
129
+ function setTransform(value: string) {
130
+ if (contentRef.value) {
131
+ contentRef.value.style.transform = value
132
+ }
133
+ }
134
+
135
+ function onTouchStart(event: TouchEvent) {
136
+ if (!canSwipe()) return
137
+ dragging.value = true
138
+ dragStartY.value = event.touches[0].clientY
139
+ dragDelta.value = 0
140
+
141
+ if (contentRef.value) {
142
+ contentRef.value.style.transition = 'none'
143
+ }
144
+ }
145
+
146
+ function onTouchMove(event: TouchEvent) {
147
+ if (!dragging.value || !canSwipe()) return
148
+
149
+ const delta = event.touches[0].clientY - dragStartY.value
150
+ if (delta < 0) {
151
+ dragDelta.value = 0
152
+ setTransform('')
153
+ return
154
+ }
155
+
156
+ dragDelta.value = delta
157
+ setTransform(`translateY(${delta}px)`)
158
+ }
159
+
160
+ function prefersReducedMotion() {
161
+ return import.meta.client && window.matchMedia('(prefers-reduced-motion: reduce)').matches
162
+ }
163
+
164
+ function animateClose() {
165
+ const content = contentRef.value
166
+
167
+ if (!content || prefersReducedMotion()) {
168
+ open.value = false
169
+ return
170
+ }
171
+
172
+ swipeClosing.value = true
173
+
174
+ let done = false
175
+ const finish = () => {
176
+ if (done) return
177
+ done = true
178
+ content.removeEventListener('transitionend', onTransitionEnd)
179
+ open.value = false
180
+ swipeClosing.value = false
181
+ }
182
+
183
+ function onTransitionEnd(event: TransitionEvent) {
184
+ if (event.target === content && event.propertyName === 'transform') {
185
+ finish()
186
+ }
187
+ }
188
+
189
+ content.addEventListener('transitionend', onTransitionEnd)
190
+ setTimeout(finish, 320)
191
+
192
+ requestAnimationFrame(() => {
193
+ content.style.transition = 'transform 260ms cubic-bezier(0.22, 1, 0.36, 1)'
194
+ content.style.transform = 'translateY(100%)'
195
+
196
+ if (overlayRef.value) {
197
+ overlayRef.value.style.transition = 'opacity 260ms ease'
198
+ overlayRef.value.style.opacity = '0'
199
+ }
200
+ })
201
+ }
202
+
203
+ function onTouchEnd() {
204
+ if (!dragging.value) return
205
+ dragging.value = false
206
+
207
+ const shouldClose = dragDelta.value > props.swipeThreshold && canSwipe()
208
+
209
+ if (shouldClose) {
210
+ animateClose()
211
+ }
212
+ else if (contentRef.value) {
213
+ contentRef.value.style.transition = 'transform 220ms cubic-bezier(0.22, 1, 0.36, 1)'
214
+ contentRef.value.style.transform = ''
215
+ }
216
+
217
+ dragDelta.value = 0
218
+ }
219
+ </script>
220
+
221
+ <template>
222
+ <client-only>
223
+ <teleport to="body">
224
+ <transition :name="overlayTransitionName">
225
+ <div
226
+ v-if="open"
227
+ ref="overlayRef"
228
+ :class="overlayClass"
229
+ @mousedown="onOverlayClick"
230
+ >
231
+ <transition :name="contentTransitionName" appear>
232
+ <div
233
+ v-if="open"
234
+ ref="contentRef"
235
+ class="drawer-v2-content flex w-full max-w-none flex-col overflow-hidden rounded-t-xl border-t border-gray-200 bg-white shadow-2xl dark:border-gray-800 dark:bg-gray-900"
236
+ :style="contentStyle"
237
+ @mousedown.stop
238
+ >
239
+ <div
240
+ v-if="showDragHandle"
241
+ class="flex shrink-0 cursor-grab touch-none justify-center py-2"
242
+ @touchstart.passive="onTouchStart"
243
+ @touchmove="onTouchMove"
244
+ @touchend="onTouchEnd"
245
+ @touchcancel="onTouchEnd"
246
+ >
247
+ <div class="h-1 w-10 rounded-full bg-gray-300 dark:bg-gray-600" />
248
+ </div>
249
+
250
+ <header
251
+ v-if="!hideHeader && (title || $slots.header)"
252
+ class="flex shrink-0 items-center justify-between gap-3 border-b border-gray-200 px-5 py-4 dark:border-gray-800"
253
+ @touchstart.passive="onTouchStart"
254
+ @touchmove="onTouchMove"
255
+ @touchend="onTouchEnd"
256
+ @touchcancel="onTouchEnd"
257
+ >
258
+ <slot name="header">
259
+ <h2 class="text-base font-semibold text-gray-900 dark:text-gray-100">
260
+ {{ title }}
261
+ </h2>
262
+ <button
263
+ type="button"
264
+ class="rounded-sm p-1 text-gray-500 transition hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-800 dark:hover:text-gray-200"
265
+ @click="closeIfAllowed"
266
+ >
267
+ <a-icon-x-mark class="size-4" />
268
+ </button>
269
+ </slot>
270
+ </header>
271
+
272
+ <div class="min-h-0 flex-1 overflow-auto" :class="contentClass">
273
+ <slot :close="closeIfAllowed" />
274
+ </div>
275
+
276
+ <footer
277
+ v-if="$slots.footer"
278
+ class="shrink-0 border-t border-gray-200 px-5 py-4 dark:border-gray-800"
279
+ >
280
+ <slot name="footer" :close="closeIfAllowed" />
281
+ </footer>
282
+ </div>
283
+ </transition>
284
+ </div>
285
+ </transition>
286
+ </teleport>
287
+ </client-only>
288
+ </template>
289
+
290
+ <style scoped lang="scss">
291
+ .drawer-v2-overlay {
292
+ will-change: opacity;
293
+ }
294
+
295
+ .drawer-v2-content {
296
+ will-change: transform;
297
+ transform: translateZ(0);
298
+ backface-visibility: hidden;
299
+ }
300
+
301
+ .drawer-v2-overlay-enter-active {
302
+ transition: opacity 240ms ease-out;
303
+ }
304
+
305
+ .drawer-v2-overlay-leave-active {
306
+ transition: opacity 220ms ease-in;
307
+ }
308
+
309
+ .drawer-v2-overlay-enter-from,
310
+ .drawer-v2-overlay-leave-to {
311
+ opacity: 0;
312
+ }
313
+
314
+ .drawer-v2-sheet-enter-active {
315
+ transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
316
+ }
317
+
318
+ .drawer-v2-sheet-leave-active {
319
+ transition: transform 240ms cubic-bezier(0.4, 0, 1, 1);
320
+ }
321
+
322
+ .drawer-v2-sheet-enter-from,
323
+ .drawer-v2-sheet-leave-to {
324
+ transform: translate3d(0, 100%, 0);
325
+ }
326
+
327
+ @media (prefers-reduced-motion: reduce) {
328
+ .drawer-v2-overlay-enter-active,
329
+ .drawer-v2-overlay-leave-active,
330
+ .drawer-v2-sheet-enter-active,
331
+ .drawer-v2-sheet-leave-active {
332
+ transition: none;
333
+ }
334
+ }
335
+ </style>
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
2
 
3
- import { twJoin, twMerge } from 'tailwind-merge'
3
+ import { twJoin, twMerge } from '#adata-ui/utils/twMerge'
4
4
  import { onClickOutside, useWindowSize } from '@vueuse/core'
5
5
  import { disableBodyScroll, enableBodyScroll } from 'body-scroll-lock'
6
6
 
@@ -22,13 +22,13 @@ const uiConfig = {
22
22
  position: 'top-1/2 left-1/2 translate-y-[-50%] translate-x-[-50%]',
23
23
  padding: "p-8",
24
24
  background: "bg-white dark:bg-gray-900",
25
- rounded: "rounded-lg",
25
+ rounded: "rounded-md",
26
26
  fullscreen: "w-screen h-screen rounded-none",
27
27
  mobile: {
28
28
  width: "w-screen",
29
29
  position: "bottom-0 left-0 transition-transform",
30
30
  padding: 'px-4 sm:pb-8 pb-4 pt-3',
31
- rounded: "rounded-t-2xl"
31
+ rounded: "rounded-t-xl"
32
32
  },
33
33
  transition: {
34
34
  desktop: {
@@ -297,7 +297,7 @@ watch(modelValue, async (newValue) => {
297
297
  >
298
298
  <h4
299
299
  v-if="title"
300
- class="heading-02 text-center"
300
+ class="text-h2 text-center"
301
301
  @touchmove="touchmove"
302
302
  @touchstart="touchstart"
303
303
  @touchend="touchend"