adata-ui 2.1.40-beta.11 → 2.1.40-beta.12
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/assets/styles/index.scss +67 -5
- package/components/elements/a-select-row/ASelectRowV2.vue +8 -8
- package/components/elements/a-select-row/index.vue +3 -5
- package/components/elements/accordion/AAccordion.vue +5 -3
- package/components/elements/accordion/AAccordionGroup/AAccordionGroup.vue +3 -3
- package/components/elements/alert/AAlert.vue +34 -47
- package/components/elements/bg-block/ABgBlock.vue +1 -1
- package/components/elements/button/AButton.vue +3 -3
- package/components/elements/button/AButtonV2.vue +7 -5
- package/components/elements/button-login/index.vue +1 -1
- package/components/elements/chip-wrapper/AChipWrapper.vue +1 -1
- package/components/elements/chips/AChips.vue +2 -2
- package/components/elements/digit-badge/ADigitBadge.vue +21 -19
- package/components/elements/feature-description/AFeatureDescription.vue +1 -1
- package/components/elements/leave-note/ALeaveNote.vue +1 -1
- package/components/elements/pagination/APagination.vue +7 -7
- package/components/elements/segmented/ASegmentedV2.vue +103 -10
- package/components/elements/select/ASelect.vue +4 -4
- package/components/elements/select/ASelectMobile.vue +2 -2
- package/components/elements/select/ASelectV2.vue +10 -11
- package/components/elements/select/ui/select-button.vue +1 -1
- package/components/elements/status-badge/AStatusBadge.vue +22 -19
- package/components/elements/status-badge-v2/StatusBadgeV2.vue +1 -3
- package/components/elements/table/ATable.vue +1 -1
- package/components/elements/table/table.config.ts +2 -2
- package/components/elements/tree-select/ATreeSelect.vue +97 -113
- package/components/elements/tree-select/TreeService.ts +142 -207
- package/components/elements/tree-select/components/ATreeSelectNode.vue +104 -0
- package/components/elements/tree-select/types.ts +40 -11
- package/components/elements/tree-select/utils.ts +30 -0
- package/components/elements/tree-select-modal/ATreeSelectModal.vue +217 -0
- package/components/features/color-mode/AColorMode.client.vue +1 -1
- package/components/features/dropdown/ADropdownV2.vue +1 -2
- package/components/features/lang-switcher/lang-switcher.vue +9 -4
- package/components/features/payment/banner/BasicPlusLimitBanner.vue +3 -3
- package/components/features/payment/banner/PaymentBanner.vue +3 -3
- package/components/features/payment/banner/UpSellBanner.vue +3 -3
- package/components/features/payment/process/PaymentKaspiQrSidePanel.vue +3 -3
- package/components/features/payment/process/PaymentKaspiRedirectSidePanel.vue +1 -1
- package/components/features/payment/process/PaymentMethodSidePanel.vue +3 -3
- package/components/features/payment/process/PaymentTopUpSidePanel.vue +2 -2
- package/components/features/pk-mobile-services/APkMobileServices.vue +1 -1
- package/components/features//321/201hange-version/AChangeVersion.vue +2 -2
- package/components/forms/checkbox/ACheckbox.vue +1 -1
- package/components/forms/checkbox/ACheckboxV2.vue +5 -5
- package/components/forms/input/AInputV2.vue +4 -4
- package/components/forms/input/date/AInputDate.vue +2 -2
- package/components/forms/input/password/AInputPassword.vue +8 -2
- package/components/forms/input/standard/AInputStandard.vue +3 -3
- package/components/forms/input/textarea/ATextarea.vue +2 -2
- package/components/forms/toggle/AToggle.vue +2 -3
- package/components/forms/toggle/AToggleV2.vue +3 -3
- package/components/modals/AnotherDeviceModal.vue +1 -1
- package/components/modals/ContactsMobileModel.vue +1 -1
- package/components/modals/ContentNavigationModal.vue +3 -3
- package/components/modals/Insufficient-funds-modal.vue +1 -1
- package/components/modals/PaymentMethodModal.vue +1 -1
- package/components/modals/id/IdBanner.vue +3 -3
- package/components/modals/id/IdRecoveryModal.vue +1 -1
- package/components/navigation/breadcrumbs/ABreadcrumbs.vue +106 -45
- package/components/navigation/breadcrumbs/types.ts +3 -0
- package/components/navigation/footer/AFooter.vue +39 -40
- package/components/navigation/footer/ui/a-footer-accordion.vue +9 -9
- package/components/navigation/header/AHeader.vue +2 -4
- package/components/navigation/header/AlmatyContacts.vue +78 -78
- package/components/navigation/header/CardGallery.vue +4 -4
- package/components/navigation/header/HeaderUsage.vue +4 -6
- package/components/navigation/header/ListItem.vue +2 -2
- package/components/navigation/header/NavCard.vue +1 -1
- package/components/navigation/header/NavList.vue +2 -2
- package/components/navigation/header/NotificationsMenu.vue +2 -3
- package/components/navigation/header/ProductMenu.vue +1 -1
- package/components/navigation/header/ProfileMenu.vue +26 -35
- package/components/navigation/header/SystemNotification.vue +1 -3
- package/components/navigation/pill-tabs/APillTabs.vue +2 -2
- package/components/navigation/pill-tabs/APillTabsV2.vue +5 -5
- package/components/overlays/drawer/ADrawerV2.vue +335 -0
- package/components/overlays/modal/AModal.vue +4 -4
- package/components/overlays/modal/AModalV2.vue +4 -4
- package/components/overlays/side-panel/ASidePanel.vue +6 -6
- package/components/overlays/sideover/ASlideover.vue +6 -6
- package/components/overlays/tooltip/ATooltip.vue +1 -1
- package/components/overlays/tooltip/ATooltipV2.vue +2 -12
- package/components/transitions/ATransitionHeight.vue +1 -1
- package/composables/useHeaderNavigationLinks.ts +100 -94
- package/icons/archive.vue +16 -0
- package/icons/arrow/arrows-in.vue +11 -0
- package/icons/arrow/arrows-out.vue +11 -0
- package/icons/arrow/arrows-resize-in.vue +38 -0
- package/icons/arrow/arrows-resize-out.vue +17 -0
- package/icons/bot.vue +52 -0
- package/icons/clock-time.vue +24 -0
- package/icons/compare-outlined.vue +8 -0
- package/icons/dots-horizontal.vue +31 -0
- package/icons/download-line.vue +13 -0
- package/icons/egov.vue +41 -0
- package/icons/elicense.vue +38 -0
- package/icons/favourite-bookmark-outline.vue +17 -0
- package/icons/file-pen.vue +31 -0
- package/icons/file-stack.vue +31 -0
- package/icons/file-types/excel.vue +131 -0
- package/icons/file-types/html.vue +13 -0
- package/icons/file-types/jpeg.vue +16 -0
- package/icons/file-types/pdf.vue +11 -0
- package/icons/file-types/png.vue +11 -0
- package/icons/file-types/rar.vue +11 -0
- package/icons/file-types/word.vue +50 -0
- package/icons/file-types/zip.vue +11 -0
- package/icons/git-compare.vue +38 -0
- package/icons/graduation-cap.vue +31 -0
- package/icons/graph-loading.vue +34 -0
- package/icons/help-question-circle.vue +26 -0
- package/icons/in-row-type.vue +46 -0
- package/icons/in-table-type.vue +29 -0
- package/icons/message-dotted.vue +30 -0
- package/icons/notification-error.vue +17 -0
- package/icons/person.vue +22 -0
- package/icons/settings.vue +26 -0
- package/icons/shield-alert.vue +31 -0
- package/icons/socials/facebook-color.vue +16 -0
- package/icons/socials/globe-color.vue +16 -0
- package/icons/socials/instagram-color.vue +46 -0
- package/icons/socials/linkedin-color.vue +14 -0
- package/icons/socials/telegram-color.vue +16 -0
- package/icons/socials/tiktok-color.vue +14 -0
- package/icons/socials/vk-color.vue +16 -0
- package/icons/socials/youtube-color.vue +16 -0
- package/icons/view-eye-open.vue +13 -0
- package/package.json +1 -1
- package/plugins/toast.client.ts +38 -55
- package/tailwind.config.ts +147 -116
- package/utils/twMerge.ts +34 -0
- package/components/elements/tree-select/components/tree-select-nodes.vue +0 -91
|
@@ -62,7 +62,7 @@ function handleExportClick() {
|
|
|
62
62
|
v-if="module.key === 'edo'"
|
|
63
63
|
#bottom
|
|
64
64
|
>
|
|
65
|
-
<div class="mt-4 flex flex-col gap-4 rounded-
|
|
65
|
+
<div class="mt-4 flex flex-col gap-4 rounded-lg bg-blue-700 p-4 normal-case text-white dark:bg-blue-500">
|
|
66
66
|
<div class="flex items-center gap-3">
|
|
67
67
|
<span class="rounded-[10px] bg-white p-[10px] dark:bg-gray-950">
|
|
68
68
|
<a-icon-download-rounded class="size-5 text-blue-700 dark:text-blue-500" />
|
|
@@ -39,23 +39,20 @@ function onReplenish() {
|
|
|
39
39
|
</script>
|
|
40
40
|
|
|
41
41
|
<template>
|
|
42
|
-
<div class="w-full overflow-hidden bg-white text-deepblue-900 dark:bg-gray-900 dark:text-gray-200 lg:w-[320px] lg:rounded-
|
|
43
|
-
<!-- Subscription & balance card -->
|
|
42
|
+
<div class="w-full overflow-hidden bg-white text-deepblue-900 dark:bg-gray-900 dark:text-gray-200 lg:w-[320px] lg:rounded-lg lg:border lg:border-gray-200 lg:dark:border-gray-800">
|
|
44
43
|
<div class="p-2">
|
|
45
|
-
<div class="relative overflow-hidden rounded-
|
|
46
|
-
<!-- decorative glow for a "living" feel -->
|
|
44
|
+
<div class="relative overflow-hidden rounded-lg bg-gradient-to-br from-blue-700/95 to-blue-500/85 p-3 text-white">
|
|
47
45
|
<div class="pointer-events-none absolute -right-8 -top-10 size-28 rounded-full bg-white/20 blur-2xl" aria-hidden="true" />
|
|
48
46
|
|
|
49
47
|
<div class="relative">
|
|
50
|
-
|
|
51
|
-
<p class="text-[11px] font-medium uppercase tracking-wide text-white/70">
|
|
48
|
+
<p class="text-overline-medium uppercase text-white/70">
|
|
52
49
|
{{ t('header.profile.tariff') }}
|
|
53
50
|
</p>
|
|
54
51
|
<div class="mt-0.5 flex items-center justify-between gap-2">
|
|
55
|
-
<p class="truncate text-
|
|
52
|
+
<p class="truncate text-h4">
|
|
56
53
|
{{ rate }}
|
|
57
54
|
</p>
|
|
58
|
-
<span class="inline-flex shrink-0 items-center gap-1.5 rounded-full px-2 py-0.5 text-
|
|
55
|
+
<span class="inline-flex shrink-0 items-center gap-1.5 rounded-full px-2 py-0.5 text-body-sm-medium text-white ring-1 ring-inset ring-white/20">
|
|
59
56
|
<span class="relative flex size-1.5 items-center justify-center">
|
|
60
57
|
<span class="absolute inline-flex size-full animate-ping rounded-full bg-green-400/80" />
|
|
61
58
|
<span class="relative inline-flex size-1.5 rounded-full bg-green-400" />
|
|
@@ -64,10 +61,9 @@ function onReplenish() {
|
|
|
64
61
|
</span>
|
|
65
62
|
</div>
|
|
66
63
|
|
|
67
|
-
<!-- Email (mobile only — desktop shows it in the header trigger) -->
|
|
68
64
|
<p
|
|
69
65
|
v-if="email"
|
|
70
|
-
class="mt-1 truncate text-
|
|
66
|
+
class="mt-1 truncate text-body-sm text-white/75 lg:hidden"
|
|
71
67
|
data-test-id="profile-menu-email"
|
|
72
68
|
>
|
|
73
69
|
{{ email }}
|
|
@@ -75,42 +71,39 @@ function onReplenish() {
|
|
|
75
71
|
|
|
76
72
|
<div class="my-2 h-px bg-white/15" />
|
|
77
73
|
|
|
78
|
-
<!-- Balance -->
|
|
79
74
|
<div class="flex items-center justify-between gap-2">
|
|
80
|
-
<span class="text-
|
|
81
|
-
<span class="text-
|
|
75
|
+
<span class="text-body-sm text-white/80">{{ t('header.profile.currentBalance') }}</span>
|
|
76
|
+
<span class="text-body-lg font-bold tabular-nums">{{ formattedBalance }} ₸</span>
|
|
82
77
|
</div>
|
|
83
78
|
|
|
84
|
-
<!-- Usage stats (mobile only — desktop shows them in the header bar) -->
|
|
85
79
|
<div class="mt-2 grid grid-cols-2 gap-2 lg:hidden">
|
|
86
|
-
<div class="flex items-center gap-2 rounded-
|
|
80
|
+
<div class="flex items-center gap-2 rounded-md px-2 py-1.5 ring-1 ring-inset ring-white/15">
|
|
87
81
|
<gauge class="size-4 shrink-0 text-white/80" />
|
|
88
82
|
<div class="min-w-0">
|
|
89
|
-
<p class="truncate text-
|
|
83
|
+
<p class="truncate text-body-sm uppercase leading-none text-white/70">
|
|
90
84
|
{{ t('header.profile.requests') }}
|
|
91
85
|
</p>
|
|
92
|
-
<p class="mt-1 text-
|
|
86
|
+
<p class="mt-1 text-body-md font-bold tabular-nums leading-none">
|
|
93
87
|
{{ limitRemaining }}
|
|
94
88
|
</p>
|
|
95
89
|
</div>
|
|
96
90
|
</div>
|
|
97
|
-
<div class="flex items-center gap-2 rounded-
|
|
91
|
+
<div class="flex items-center gap-2 rounded-md px-2 py-1.5 ring-1 ring-inset ring-white/15">
|
|
98
92
|
<calendar class="size-4 shrink-0 text-white/80" />
|
|
99
93
|
<div class="min-w-0">
|
|
100
|
-
<p class="truncate text-
|
|
94
|
+
<p class="truncate text-body-sm uppercase leading-none text-white/70">
|
|
101
95
|
{{ t('header.profile.daysLeft') }}
|
|
102
96
|
</p>
|
|
103
|
-
<p class="mt-1 text-
|
|
97
|
+
<p class="mt-1 text-body-md font-bold tabular-nums leading-none">
|
|
104
98
|
{{ daysRemaining }}
|
|
105
99
|
</p>
|
|
106
100
|
</div>
|
|
107
101
|
</div>
|
|
108
102
|
</div>
|
|
109
103
|
|
|
110
|
-
<!-- Top up -->
|
|
111
104
|
<button
|
|
112
105
|
type="button"
|
|
113
|
-
class="mt-2.5 inline-flex h-8 w-full cursor-pointer items-center justify-center gap-1.5 rounded-full bg-white px-3 text-
|
|
106
|
+
class="mt-2.5 inline-flex h-8 w-full cursor-pointer items-center justify-center gap-1.5 rounded-full bg-white px-3 text-body-md-strong text-blue-700 shadow-sm transition-all duration-150 hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70 focus-visible:ring-offset-2 focus-visible:ring-offset-blue-600 active:scale-[0.98]"
|
|
114
107
|
@click="onReplenish"
|
|
115
108
|
>
|
|
116
109
|
<a-icon-plus-circle class="size-4" />
|
|
@@ -122,31 +115,29 @@ function onReplenish() {
|
|
|
122
115
|
</div>
|
|
123
116
|
</div>
|
|
124
117
|
|
|
125
|
-
<!-- Menu links -->
|
|
126
118
|
<nav class="flex flex-col px-2 pb-2">
|
|
127
119
|
<nuxt-link-locale
|
|
128
120
|
v-for="item in items"
|
|
129
121
|
:key="item.title"
|
|
130
|
-
class="group flex items-center gap-3 rounded-
|
|
122
|
+
class="group flex items-center gap-3 rounded-md px-2.5 py-2 text-body-md transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/60"
|
|
131
123
|
:to="item.to"
|
|
132
124
|
:data-test-id="item?.dataTestId"
|
|
133
125
|
target="_blank"
|
|
134
126
|
>
|
|
135
|
-
<!-- <span-->
|
|
136
|
-
<!-- class="flex size-8 shrink-0 items-center justify-center rounded-
|
|
137
|
-
<!--
|
|
138
|
-
<!-- <component-->
|
|
139
|
-
<!-- :is="item.icon"-->
|
|
140
|
-
<!-- class="size-4"-->
|
|
141
|
-
<!--
|
|
142
|
-
<!-- </span
|
|
127
|
+
<!-- <span -->
|
|
128
|
+
<!-- class="flex size-8 shrink-0 items-center justify-center rounded-md border border-blue-100 bg-blue-50 text-blue-700 dark:border-blue-900/40 dark:bg-blue-900/30 dark:text-blue-400" -->
|
|
129
|
+
<!-- > -->
|
|
130
|
+
<!-- <component -->
|
|
131
|
+
<!-- :is="item.icon" -->
|
|
132
|
+
<!-- class="size-4" -->
|
|
133
|
+
<!-- /> -->
|
|
134
|
+
<!-- </span> -->
|
|
143
135
|
<span class="truncate text-deepblue-900 group-hover:text-blue-700 dark:text-gray-200 dark:group-hover:text-blue-400">
|
|
144
136
|
{{ item.title }}
|
|
145
137
|
</span>
|
|
146
138
|
</nuxt-link-locale>
|
|
147
139
|
</nav>
|
|
148
140
|
|
|
149
|
-
<!-- Footer: old version, theme (mobile), logout -->
|
|
150
141
|
<div class="border-t border-gray-100 p-2 dark:border-gray-800">
|
|
151
142
|
<a-change-version
|
|
152
143
|
v-if="oldVersion"
|
|
@@ -156,7 +147,7 @@ function onReplenish() {
|
|
|
156
147
|
|
|
157
148
|
<div
|
|
158
149
|
v-if="!isLargeScreen"
|
|
159
|
-
class="flex items-center justify-between rounded-
|
|
150
|
+
class="flex items-center justify-between rounded-md px-2.5 py-2 text-body-md text-deepblue-900 dark:text-gray-200"
|
|
160
151
|
>
|
|
161
152
|
<span>{{ t('header.profile.colorScheme') }}</span>
|
|
162
153
|
<a-color-mode />
|
|
@@ -164,7 +155,7 @@ function onReplenish() {
|
|
|
164
155
|
|
|
165
156
|
<button
|
|
166
157
|
type="button"
|
|
167
|
-
class="flex w-full cursor-pointer items-center gap-3 rounded-
|
|
158
|
+
class="flex w-full cursor-pointer items-center gap-3 rounded-md px-2.5 py-2 text-body-md-medium text-red-600 transition-colors hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
|
|
168
159
|
data-test-id="logout-button"
|
|
169
160
|
@click="$emit('logout')"
|
|
170
161
|
>
|
|
@@ -19,7 +19,6 @@ const message = useCookie('message')
|
|
|
19
19
|
const systemMessage = ref<string | null>(null)
|
|
20
20
|
const isOpen = ref(false)
|
|
21
21
|
|
|
22
|
-
// API expects the legacy "counterparty" code instead of "pk".
|
|
23
22
|
const moduleName = computed(() => (currentModule.value === 'pk' ? 'counterparty' : currentModule.value))
|
|
24
23
|
|
|
25
24
|
function localizedMessage(detail: SystemMessageDetail): string {
|
|
@@ -34,7 +33,6 @@ function localizedMessage(detail: SystemMessageDetail): string {
|
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
async function fetchNotification() {
|
|
37
|
-
// The cookie is set once the user dismisses the banner — skip the request afterwards.
|
|
38
36
|
if (message.value !== undefined) return
|
|
39
37
|
|
|
40
38
|
try {
|
|
@@ -74,7 +72,7 @@ onMounted(fetchNotification)
|
|
|
74
72
|
<a-icon-info-circle
|
|
75
73
|
class="max-h-[16px] w-full max-w-[16px] shrink-0 stroke-orange-500"
|
|
76
74
|
/>
|
|
77
|
-
<span class="text-
|
|
75
|
+
<span class="text-body-md-strong text-orange-500">{{ systemMessage }}</span>
|
|
78
76
|
</div>
|
|
79
77
|
<a-icon-x-mark
|
|
80
78
|
class="max-h-[16px] w-full max-w-[16px] shrink-0 cursor-pointer stroke-orange-500"
|
|
@@ -115,14 +115,14 @@ const viewTypeValues: ViewType = {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
const defaultClasses = (isDisabled: boolean) => [
|
|
118
|
-
'text-sm rounded-
|
|
118
|
+
'text-sm rounded-sm outline-none select-none focus-visible:shadow-[0_0_0_4px#1B64B3]',
|
|
119
119
|
isDisabled ? 'bg-deepblue-900/5 dark:bg-gray-200/5 text-gray-500 dark:text-gray-500' : '',
|
|
120
120
|
props.view === 'transparent' && isDisabled ? 'bg-transparent' : '',
|
|
121
121
|
viewTypeValues[props.view]['default'],
|
|
122
122
|
props.block ? 'w-full' : ''
|
|
123
123
|
]
|
|
124
124
|
const selectedClasses = () => [
|
|
125
|
-
'text-sm rounded-
|
|
125
|
+
'text-sm rounded-sm select-none focus-visible:shadow-[0_0_0_4px#1B64B3]',
|
|
126
126
|
viewTypeValues[props.view]['selected'],
|
|
127
127
|
props.block ? 'w-full' : ''
|
|
128
128
|
]
|
|
@@ -32,8 +32,8 @@ const emit = defineEmits<{ (e: 'update:modelValue', value: string): void }>()
|
|
|
32
32
|
const listRef = ref<HTMLElement | null>(null)
|
|
33
33
|
|
|
34
34
|
const SIZE_CLASS: Record<'sm' | 'md', string> = {
|
|
35
|
-
sm: 'text-
|
|
36
|
-
md: 'text-
|
|
35
|
+
sm: 'text-body-sm',
|
|
36
|
+
md: 'text-body-md',
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const COUNT_COLOR_CLASS: Record<CountColor, string> = {
|
|
@@ -74,7 +74,7 @@ onMounted(scrollActiveIntoView)
|
|
|
74
74
|
|
|
75
75
|
<template>
|
|
76
76
|
<div
|
|
77
|
-
class="pill-tabs-v2 min-w-0 max-w-full rounded-
|
|
77
|
+
class="pill-tabs-v2 min-w-0 max-w-full rounded-lg border border-gray-200 bg-gray-50 p-1 dark:border-white/10 dark:bg-white/[0.03]"
|
|
78
78
|
:class="[
|
|
79
79
|
block ? 'w-full' : 'w-fit',
|
|
80
80
|
disabled ? 'pill-tabs-v2--disabled opacity-60' : '',
|
|
@@ -96,7 +96,7 @@ onMounted(scrollActiveIntoView)
|
|
|
96
96
|
:aria-disabled="isDisabled(option) || undefined"
|
|
97
97
|
:disabled="isDisabled(option)"
|
|
98
98
|
:tabindex="modelValue === option.key ? 0 : -1"
|
|
99
|
-
class="inline-flex items-center gap-1.5 whitespace-nowrap rounded-
|
|
99
|
+
class="inline-flex items-center gap-1.5 whitespace-nowrap rounded-md px-3 py-1.5 font-medium transition-colors duration-150"
|
|
100
100
|
:class="[
|
|
101
101
|
SIZE_CLASS[size],
|
|
102
102
|
isDisabled(option)
|
|
@@ -113,7 +113,7 @@ onMounted(scrollActiveIntoView)
|
|
|
113
113
|
|
|
114
114
|
<span
|
|
115
115
|
v-if="showCount && !option.hideCount"
|
|
116
|
-
class="inline-flex min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 text-
|
|
116
|
+
class="inline-flex min-w-5 shrink-0 items-center justify-center rounded-full px-1.5 text-body-sm font-semibold leading-5"
|
|
117
117
|
:class="COUNT_COLOR_CLASS[option.countColor ?? 'gray']"
|
|
118
118
|
>
|
|
119
119
|
{{ option.count }}
|
|
@@ -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 '
|
|
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-
|
|
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-
|
|
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="
|
|
300
|
+
class="text-h2 text-center"
|
|
301
301
|
@touchmove="touchmove"
|
|
302
302
|
@touchstart="touchstart"
|
|
303
303
|
@touchend="touchend"
|
|
@@ -98,10 +98,10 @@ const contentBaseClass = computed(() => {
|
|
|
98
98
|
const base = ['modal-v2-content flex flex-col overflow-hidden border bg-white shadow-2xl dark:bg-gray-900']
|
|
99
99
|
|
|
100
100
|
if (isSheet.value) {
|
|
101
|
-
base.push('w-full max-w-none max-h-[100dvh] rounded-t-
|
|
101
|
+
base.push('w-full max-w-none max-h-[100dvh] rounded-t-xl rounded-b-none border-x-0 border-b-0 border-t border-gray-200 dark:border-gray-800')
|
|
102
102
|
}
|
|
103
103
|
else {
|
|
104
|
-
const radius = props.size === 'fullscreen' ? 'rounded-none' : 'rounded-
|
|
104
|
+
const radius = props.size === 'fullscreen' ? 'rounded-none' : 'rounded-xl'
|
|
105
105
|
const widthCls = props.size === 'container'
|
|
106
106
|
? ' 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'
|
|
107
107
|
: ''
|
|
@@ -308,14 +308,14 @@ function onTouchEnd() {
|
|
|
308
308
|
@touchcancel="onTouchEnd"
|
|
309
309
|
>
|
|
310
310
|
<slot name="header">
|
|
311
|
-
<h2 v-if="title" class="text-
|
|
311
|
+
<h2 v-if="title" class="text-h5 text-gray-900 dark:text-gray-100">
|
|
312
312
|
{{ title }}
|
|
313
313
|
</h2>
|
|
314
314
|
</slot>
|
|
315
315
|
<button
|
|
316
316
|
v-if="!hideClose"
|
|
317
317
|
type="button"
|
|
318
|
-
class="ml-auto rounded-
|
|
318
|
+
class="ml-auto rounded-sm p-1 transition hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-gray-200"
|
|
319
319
|
@click="closeIfAllowed"
|
|
320
320
|
>
|
|
321
321
|
<a-icon-x-mark class="size-4" />
|
|
@@ -128,10 +128,10 @@ watch(() => [modelValue.value, panel.value], (newP, oldP) => {
|
|
|
128
128
|
|
|
129
129
|
const roundedClass = computed(() => {
|
|
130
130
|
const classes = {
|
|
131
|
-
left: "rounded-r-
|
|
132
|
-
right: "rounded-l-
|
|
133
|
-
top: "rounded-b-
|
|
134
|
-
bottom: "rounded-t-
|
|
131
|
+
left: "rounded-r-lg",
|
|
132
|
+
right: "rounded-l-lg",
|
|
133
|
+
top: "rounded-b-lg",
|
|
134
|
+
bottom: "rounded-t-lg",
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
return classes[panelSide.value]
|
|
@@ -170,7 +170,7 @@ const modalHeight = ref(0)
|
|
|
170
170
|
const touchstart = (event: TouchEvent) => {
|
|
171
171
|
isClose.value = false
|
|
172
172
|
positionStart.value = event.touches[0].clientY
|
|
173
|
-
modalHeight.value = panel.value?.offsetHeight || 0
|
|
173
|
+
modalHeight.value = panel.value?.offsetHeight || 0
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
const touchmove = (event: TouchEvent) => {
|
|
@@ -179,7 +179,7 @@ const touchmove = (event: TouchEvent) => {
|
|
|
179
179
|
const touchPosition = event.touches[0].clientY
|
|
180
180
|
const step = touchPosition - positionStart.value
|
|
181
181
|
|
|
182
|
-
if (step < 0) return
|
|
182
|
+
if (step < 0) return
|
|
183
183
|
|
|
184
184
|
const newHeight = modalHeight.value - step
|
|
185
185
|
panel.value.style.height = `${newHeight}px`
|