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,174 @@
1
+ <script setup lang="ts">
2
+ import { useDocumentVisibility, useElementHover } from '@vueuse/core'
3
+ import { computed, provide, ref } from 'vue'
4
+ import { SLIDER_CONTEXT } from './types'
5
+
6
+ interface Props {
7
+ /** Time each slide stays active, in ms. */
8
+ interval?: number
9
+ /** Advance automatically. */
10
+ autoplay?: boolean
11
+ /** Wrap around from the last slide back to the first. */
12
+ loop?: boolean
13
+ /** Freeze autoplay while the cursor is over the slider. */
14
+ pauseOnHover?: boolean
15
+ /** Freeze autoplay while the browser tab is hidden. */
16
+ pauseOnHidden?: boolean
17
+ /** Render the built-in progress indicators. */
18
+ indicators?: boolean
19
+ }
20
+
21
+ const props = withDefaults(defineProps<Props>(), {
22
+ interval: 5000,
23
+ autoplay: true,
24
+ loop: true,
25
+ pauseOnHover: true,
26
+ pauseOnHidden: true,
27
+ indicators: true,
28
+ })
29
+
30
+ /** Active slide index — usable as `v-model:index`. */
31
+ const index = defineModel<number>('index', { default: 0 })
32
+
33
+ const root = ref<HTMLElement>()
34
+
35
+ /* ---- item registration (drives `count` and per-item index) ---- */
36
+ const ids = ref<symbol[]>([])
37
+ const count = computed(() => ids.value.length)
38
+
39
+ function register(id: symbol) {
40
+ ids.value.push(id)
41
+ return computed(() => ids.value.indexOf(id))
42
+ }
43
+ function unregister(id: symbol) {
44
+ const i = ids.value.indexOf(id)
45
+ if (i !== -1)
46
+ ids.value.splice(i, 1)
47
+ }
48
+
49
+ /* ---- navigation ---- */
50
+ const direction = ref<1 | -1>(1)
51
+ // Bumping this key remounts the clock/progress element, restarting the timing.
52
+ const tick = ref(0)
53
+
54
+ function go(to: number, dir?: 1 | -1) {
55
+ if (count.value === 0)
56
+ return
57
+ const n = ((to % count.value) + count.value) % count.value
58
+ direction.value = dir ?? (n >= index.value ? 1 : -1)
59
+ index.value = n
60
+ tick.value++
61
+ }
62
+
63
+ function next() {
64
+ if (!props.loop && index.value >= count.value - 1)
65
+ return
66
+ go(index.value + 1, 1)
67
+ }
68
+ function prev() {
69
+ if (!props.loop && index.value <= 0)
70
+ return
71
+ go(index.value - 1, -1)
72
+ }
73
+ function goTo(i: number) {
74
+ go(i)
75
+ }
76
+
77
+ /* ---- autoplay ----
78
+ * Timing is driven by an invisible "clock" element's CSS animation.
79
+ * `animation-play-state: paused` freezes both the clock and the visible
80
+ * progress bar in one place, so hover/hidden pause stays perfectly in sync
81
+ * and resumes from where it stopped — no JS timers to keep aligned. */
82
+ const hovered = useElementHover(root)
83
+ const visibility = useDocumentVisibility()
84
+
85
+ const shouldRun = computed(() => props.autoplay && count.value > 1)
86
+ const paused = computed(() =>
87
+ (props.pauseOnHover && hovered.value)
88
+ || (props.pauseOnHidden && visibility.value === 'hidden'),
89
+ )
90
+ const timingStyle = computed(() => ({
91
+ animationDuration: `${props.interval}ms`,
92
+ animationPlayState: paused.value ? 'paused' : 'running',
93
+ }))
94
+
95
+ provide(SLIDER_CONTEXT, {
96
+ index,
97
+ count,
98
+ direction,
99
+ register,
100
+ unregister,
101
+ })
102
+
103
+ defineExpose({ next, prev, goTo, index, count })
104
+ </script>
105
+
106
+ <template>
107
+ <div
108
+ ref="root"
109
+ class="relative overflow-hidden"
110
+ role="group"
111
+ aria-roledescription="carousel"
112
+ >
113
+ <!-- Invisible clock: fires `next` when a slide's time elapses. -->
114
+ <div
115
+ v-if="shouldRun"
116
+ :key="tick"
117
+ class="a-slider-clock"
118
+ :style="timingStyle"
119
+ @animationend="next"
120
+ />
121
+
122
+ <slot />
123
+
124
+ <div
125
+ v-if="indicators && count > 1"
126
+ class="absolute bottom-0 left-1/2 z-10 flex -translate-x-1/2 gap-2"
127
+ role="tablist"
128
+ >
129
+ <button
130
+ v-for="(_, i) in count"
131
+ :key="i"
132
+ type="button"
133
+ role="tab"
134
+ :aria-selected="index === i"
135
+ :aria-label="`Слайд ${i + 1}`"
136
+ class="relative h-[6px] cursor-pointer overflow-hidden rounded-full bg-blue-200 transition-all duration-300 dark:bg-blue-900/40"
137
+ :class="index === i ? 'w-[30px]' : 'w-[20px]'"
138
+ @click="goTo(i)"
139
+ >
140
+ <span
141
+ v-if="index === i && shouldRun"
142
+ :key="tick"
143
+ class="a-slider-progress absolute inset-0 rounded-full bg-blue-500 dark:bg-blue-400"
144
+ :style="timingStyle"
145
+ />
146
+ </button>
147
+ </div>
148
+ </div>
149
+ </template>
150
+
151
+ <style scoped>
152
+ .a-slider-clock {
153
+ position: absolute;
154
+ width: 1px;
155
+ height: 1px;
156
+ opacity: 0;
157
+ pointer-events: none;
158
+ animation: a-slider-tick linear forwards;
159
+ }
160
+
161
+ .a-slider-progress {
162
+ animation: a-slider-progress linear forwards;
163
+ }
164
+
165
+ @keyframes a-slider-tick {
166
+ from { transform: scaleX(0); }
167
+ to { transform: scaleX(1); }
168
+ }
169
+
170
+ @keyframes a-slider-progress {
171
+ from { width: 0; }
172
+ to { width: 100%; }
173
+ }
174
+ </style>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import { computed, inject, onBeforeUnmount } from 'vue'
3
+ import { SLIDER_CONTEXT } from './types'
4
+
5
+ const ctx = inject(SLIDER_CONTEXT)
6
+ if (!ctx)
7
+ throw new Error('<ASliderItem> must be used inside <ASlider>')
8
+
9
+ // Registered synchronously during setup, so siblings keep their template order.
10
+ const id = Symbol('a-slider-item')
11
+ const index = ctx.register(id)
12
+ onBeforeUnmount(() => ctx.unregister(id))
13
+
14
+ const active = computed(() => ctx.index.value === index.value)
15
+ const direction = computed(() => ctx.direction.value)
16
+ </script>
17
+
18
+ <template>
19
+ <!-- Slides are stacked on the same stage so leave/enter animations overlap. -->
20
+ <div class="absolute inset-0">
21
+ <!--
22
+ The slider only tells the slide whether it's active and in which
23
+ direction. The slide owns its own <Transition> / animations.
24
+ -->
25
+ <slot
26
+ :active="active"
27
+ :index="index"
28
+ :direction="direction"
29
+ />
30
+ </div>
31
+ </template>
@@ -0,0 +1,21 @@
1
+ import type { ComputedRef, InjectionKey, Ref } from 'vue'
2
+
3
+ /**
4
+ * Context provided by <ASlider> and consumed by <ASliderItem>.
5
+ * The slider owns the mechanism (active index, count, direction);
6
+ * items only read from it and render their own animations.
7
+ */
8
+ export interface SliderContext {
9
+ /** Index of the currently active slide. */
10
+ index: Readonly<Ref<number>>
11
+ /** Total number of registered slides. */
12
+ count: Readonly<Ref<number>>
13
+ /** Direction of the last transition: 1 = forward, -1 = backward. */
14
+ direction: Readonly<Ref<1 | -1>>
15
+ /** Register an item and get back its live index. */
16
+ register: (id: symbol) => ComputedRef<number>
17
+ /** Unregister an item on unmount. */
18
+ unregister: (id: symbol) => void
19
+ }
20
+
21
+ export const SLIDER_CONTEXT: InjectionKey<SliderContext> = Symbol('a-slider')
@@ -18,6 +18,8 @@ interface Props {
18
18
  fontSize?: number;
19
19
  error?: string | boolean;
20
20
  errorSize?: 'sm' | 'lg';
21
+ hideValue?: boolean;
22
+ hideEmptyStars?: boolean;
21
23
  }
22
24
 
23
25
  const props = withDefaults(defineProps<Props>(), {
@@ -32,7 +34,9 @@ const props = withDefaults(defineProps<Props>(), {
32
34
  ratingPlacement: 'left',
33
35
  fontSize: 14,
34
36
  error: false,
35
- errorSize: 'lg'
37
+ errorSize: 'lg',
38
+ hideValue: false,
39
+ hideEmptyStars: false
36
40
  })
37
41
 
38
42
  const fillLevel = ref<number[]>([])
@@ -51,6 +55,10 @@ const roundedRating = computed(() => {
51
55
  })
52
56
  const shouldRound = computed(() => ratingSelected.value || props.roundStartRating)
53
57
 
58
+ const visibleStarsCount = computed(() => {
59
+ if (!props.hideEmptyStars) return props.max
60
+ return Math.min(props.max, Math.max(1, Math.ceil(currentRating.value)))
61
+ })
54
62
 
55
63
  watch(modelValue, (e)=>{
56
64
  currentRating.value = e
@@ -105,7 +113,7 @@ createStars(shouldRound.value)
105
113
  <div>
106
114
  <div class="w-full flex items-center gap-2">
107
115
  <span
108
- v-if="ratingPlacement === 'left'"
116
+ v-if="ratingPlacement === 'left' && !hideValue"
109
117
  class="text-gray-600 dark:text-gray-300 mt-px"
110
118
  :style="{fontSize: `${fontSize}px`}"
111
119
  >
@@ -118,7 +126,7 @@ createStars(shouldRound.value)
118
126
  @mouseleave="resetRating"
119
127
  >
120
128
  <star
121
- v-for="n in max"
129
+ v-for="n in visibleStarsCount"
122
130
  :key="n"
123
131
  :fill="fillLevel[n-1]"
124
132
  :size="starSize"
@@ -127,7 +135,7 @@ createStars(shouldRound.value)
127
135
  />
128
136
  </div>
129
137
  <span
130
- v-if="ratingPlacement === 'right'"
138
+ v-if="ratingPlacement === 'right' && !hideValue"
131
139
  class="text-sm text-gray-600 dark:text-gray-300 mt-px"
132
140
  >
133
141
  {{ modelValue }}
@@ -1,74 +1,121 @@
1
1
  <script lang="ts" setup>
2
- import {twMerge} from "tailwind-merge";
2
+ type BadgeType = 'primary' | 'success' | 'danger' | 'gray' | 'warning'
3
+ type BadgeView = 'default' | 'inverted' | 'transparent' | 'subtle'
4
+ type BadgeSize = 'xs' | 'sm' | 'md' | 'lg'
3
5
 
4
- defineOptions({ name: 'AStatusBadge' })
6
+ type StateType = {
7
+ [key in BadgeType]?: string
8
+ }
9
+
10
+ interface ViewType {
11
+ default: StateType
12
+ inverted: StateType
13
+ transparent: StateType
14
+ subtle: StateType
15
+ }
5
16
 
6
17
  interface Props {
7
- type?: 'primary' | 'success' | 'danger' | 'gray' | 'orange' | 'warning'
8
- view?: 'default' | 'inverted' | 'transparent'
9
- size?: 'xs' | 'sm' | 'lg'
18
+ type?: BadgeType
19
+ view?: BadgeView
20
+ size?: BadgeSize
21
+ point?: boolean
10
22
  customClasses?: string
11
23
  }
12
24
 
25
+ defineOptions({ name: 'AStatusBadge' })
26
+
13
27
  const props = withDefaults(defineProps<Props>(), {
14
28
  type: 'primary',
15
29
  view: 'default',
16
30
  size: 'lg',
17
- customClasses: ''
31
+ point: false,
32
+ customClasses: '',
18
33
  })
19
34
 
20
- const classes = computed(() =>
21
- [
22
- 'text-sm rounded-md select-none inline-flex gap-[5.5px] justify-center items-center',
23
- typeSwitchValues[props.view][props.type],
24
- props.size === 'xs'? 'px-4 py-[2px] text-xs font-bold' : '',
25
- props.size === 'sm'? 'text-xs font-semibold px-4 py-[2px] h-6' : '',
26
- props.size === 'lg'? 'px-4 py-[7.5px] font-semibold' : '',
27
- ])
28
-
29
-
30
35
  const typeSwitchValues: ViewType = {
31
36
  default: {
32
- primary: 'bg-blue-700 text-white dark:text-gray-900 dark:bg-blue-500',
33
- success: 'bg-green-500 text-white dark:text-gray-200 dark:bg-green-400',
34
- danger: 'bg-red-500 text-white dark:text-gray-200 ',
35
- warning: 'bg-yellow-400 text-white dark:text-gray-200 ',
36
- gray: 'bg-deepblue-900 text-white dark:text-gray-200 dark:bg-gray-200/10',
37
- orange: 'bg-orange-600 text-white dark:text-gray-200'
37
+ primary: 'bg-[#068DFF] text-white border-transparent dark:bg-[#068DFF]/30 dark:text-blue-100',
38
+ success: 'bg-[#00A63E] text-white border-transparent dark:bg-[#00A63E]/30 dark:text-green-100',
39
+ danger: 'bg-[#E7000B] text-white border-transparent dark:bg-[#E7000B]/30 dark:text-red-100',
40
+ warning: 'bg-[#E17100] text-white border-transparent dark:bg-[#E17100]/30 dark:text-orange-100',
41
+ gray: 'bg-[#4A5565] text-white border-transparent dark:bg-[#4A5565]/30 dark:text-gray-100',
38
42
  },
39
43
  inverted: {
40
- primary: 'bg-blue-700/10 text-blue-700 dark:text-blue-500 dark:bg-[#1EABFF1A]',
41
- success: 'bg-green-500/20 text-green-500 dark:text-green-400 dark:bg-green-400/20',
42
- danger: 'bg-red-500/10 text-red-500 dark:text-red-400 dark:bg-red-400/10',
43
- warning: 'bg-yellow-400/10 text-yellow-400',
44
- gray: 'bg-deepblue-900/10 text-gray-600 dark:text-gray-200 dark:bg-gray-200/10',
45
- orange: 'bg-orange-600/10 text-orange-600'
44
+ primary: 'bg-[#F0F8FF] text-[#068DFF] border-transparent dark:bg-[#068DFF]/10 dark:text-blue-400',
45
+ success: 'bg-[#DCFCE7] text-[#008236] border-transparent dark:bg-[#00A63E]/10 dark:text-green-400',
46
+ danger: 'bg-[#FFE2E2] text-[#C10007] border-transparent dark:bg-[#E7000B]/10 dark:text-red-400',
47
+ warning: 'bg-[#FEF3C6] text-[#9A5800] border-transparent dark:bg-[#E17100]/10 dark:text-orange-400',
48
+ gray: 'bg-[#F3F4F6] text-[#364153] border-transparent dark:bg-[#4A5565]/10 dark:text-gray-300',
46
49
  },
47
50
  transparent: {
48
- success: 'bg-green-500/10 text-green-500 dark:bg-green-400/10 dark:text-green-400',
49
- danger: 'bg-red-500/10 text-red-500 dark:bg-red-400/10 dark:text-red-400'
50
- }
51
+ primary: 'bg-white border-[#B3DCFF] text-[#0066BD] dark:bg-transparent dark:border-[#068DFF]/50 dark:text-blue-400',
52
+ success: 'bg-white border-[#7BF1A8] text-[#008236] dark:bg-transparent dark:border-[#00A63E]/50 dark:text-green-400',
53
+ danger: 'bg-white border-[#FFA2A2] text-[#C10007] dark:bg-transparent dark:border-[#E7000B]/50 dark:text-red-400',
54
+ warning: 'bg-white border-[#FFD230] text-[#9A5800] dark:bg-transparent dark:border-[#E17100]/50 dark:text-orange-400',
55
+ gray: 'bg-white border-[#D1D5DC] text-[#364153] dark:bg-transparent dark:border-[#4A5565]/50 dark:text-gray-300',
56
+ },
57
+ subtle: {
58
+ primary: 'bg-[#068DFF]/5 border-[#B3DCFF] text-[#068DFF] dark:bg-blue-500/10 dark:border-[#068DFF]/40 dark:text-blue-400',
59
+ success: 'bg-[#00A63E]/5 border-[#7BF1A8] text-[#00A63E] dark:bg-green-500/10 dark:border-[#00A63E]/40 dark:text-green-400',
60
+ danger: 'bg-[#E7000B]/5 border-[#FFA2A2] text-[#E7000B] dark:bg-red-500/10 dark:border-[#E7000B]/40 dark:text-red-400',
61
+ warning: 'bg-[#E17100]/5 border-[#FFD230] text-[#E17100] dark:bg-yellow-500/10 dark:border-[#E17100]/40 dark:text-orange-400',
62
+ gray: 'bg-[#4A5565]/5 border-[#D1D5DC] text-[#4A5565] dark:bg-gray-500/10 dark:border-[#4A5565]/40 dark:text-gray-300',
63
+ },
51
64
  }
52
65
 
53
-
54
-
55
-
56
- type StateType = {
57
- [key in 'primary' | 'success' | 'danger' | 'gray' | 'orange' | 'warning']?: string
66
+ const pointSwitchValues: ViewType = {
67
+ default: {
68
+ primary: 'bg-white',
69
+ success: 'bg-white',
70
+ danger: 'bg-white',
71
+ warning: 'bg-white',
72
+ gray: 'bg-white',
73
+ },
74
+ inverted: {
75
+ primary: 'bg-[#068DFF] dark:bg-blue-400',
76
+ success: 'bg-[#00A63E] dark:bg-green-400',
77
+ danger: 'bg-[#E7000B] dark:bg-red-400',
78
+ warning: 'bg-[#E17100] dark:bg-orange-400',
79
+ gray: 'bg-[#6A7282] dark:bg-gray-300',
80
+ },
81
+ transparent: {
82
+ primary: 'bg-[#068DFF] dark:bg-blue-400',
83
+ success: 'bg-[#00A63E] dark:bg-green-400',
84
+ danger: 'bg-[#E7000B] dark:bg-red-400',
85
+ warning: 'bg-[#E17100] dark:bg-orange-400',
86
+ gray: 'bg-[#6A7282] dark:bg-gray-300',
87
+ },
88
+ subtle: {
89
+ primary: 'bg-[#068DFF] dark:bg-blue-400',
90
+ success: 'bg-[#00A63E] dark:bg-green-400',
91
+ danger: 'bg-[#E7000B] dark:bg-red-400',
92
+ warning: 'bg-[#E17100] dark:bg-orange-400',
93
+ gray: 'bg-[#6A7282] dark:bg-gray-300',
94
+ },
58
95
  }
59
96
 
60
- type ViewType = {
61
- default: StateType
62
- inverted: StateType
63
- transparent: StateType
64
- };
65
-
66
-
97
+ const classes = computed(() =>
98
+ [
99
+ 'text-body-md rounded-2xl select-none inline-flex gap-[5.5px] justify-center items-center border',
100
+ typeSwitchValues[props.view][props.type],
101
+ props.size === 'xs' ? 'px-2 py-[2px] text-body-sm font-bold' : '',
102
+ props.size === 'sm' ? 'px-[10px] py-1 text-body-sm font-semibold h-6' : '',
103
+ props.size === 'md' ? 'px-3 py-[6x] text-body-sm font-semibold h-7' : '',
104
+ props.size === 'lg' ? 'px-4 py-[6px] font-semibold' : '',
105
+ ])
67
106
 
107
+ const pointClasses = computed(() =>
108
+ [
109
+ 'w-1.5 h-1.5 rounded-full shrink-0',
110
+ pointSwitchValues[props.view][props.type],
111
+ ])
68
112
  </script>
113
+
69
114
  <template>
70
115
  <div :class="[[classes], customClasses]">
71
- <slot name="point"/>
116
+ <slot name="point">
117
+ <span v-if="point" :class="pointClasses" />
118
+ </slot>
72
119
  <slot />
73
120
  </div>
74
121
  </template>
@@ -0,0 +1,232 @@
1
+ <script setup lang="ts">
2
+ import type { Component } from 'vue'
3
+
4
+ interface Step {
5
+ title: string
6
+ description: string
7
+ icon?: Component
8
+ iconBg?: string
9
+ iconColor?: string
10
+ chips?: string[]
11
+ }
12
+
13
+ const props = withDefaults(defineProps<{
14
+ steps: Step[]
15
+ interval?: number
16
+ autoplay?: boolean
17
+ pauseOnHover?: boolean
18
+ modelValue?: number
19
+ colsClass?: string
20
+ expandable?: boolean
21
+ }>(), {
22
+ interval: 5000,
23
+ autoplay: true,
24
+ pauseOnHover: true,
25
+ modelValue: undefined,
26
+ colsClass: 'lg:grid-cols-2',
27
+ expandable: true,
28
+ })
29
+
30
+ const emit = defineEmits<{
31
+ (e: 'update:modelValue', value: number): void
32
+ (e: 'change', value: number): void
33
+ }>()
34
+
35
+ const TICK = 50
36
+
37
+ const internal = ref(props.modelValue ?? 0)
38
+
39
+ const active = computed<number>({
40
+ get: () => props.modelValue ?? internal.value,
41
+ set: (value) => {
42
+ internal.value = value
43
+ emit('update:modelValue', value)
44
+ emit('change', value)
45
+ },
46
+ })
47
+
48
+ const elapsed = ref(0)
49
+ let ticker: ReturnType<typeof setInterval> | null = null
50
+ let lastTs = 0
51
+
52
+ const progress = computed(() => {
53
+ if (!props.autoplay || props.steps.length <= 1)
54
+ return 1
55
+ return Math.min(1, elapsed.value / props.interval)
56
+ })
57
+
58
+ const fillWidth = computed(() => `${progress.value * 100}%`)
59
+
60
+ function stopTicker() {
61
+ if (ticker) {
62
+ clearInterval(ticker)
63
+ ticker = null
64
+ }
65
+ lastTs = 0
66
+ }
67
+
68
+ function startTicker() {
69
+ stopTicker()
70
+ if (!props.autoplay || props.steps.length <= 1)
71
+ return
72
+ ticker = setInterval(() => {
73
+ const now = Date.now()
74
+ const dt = lastTs ? now - lastTs : TICK
75
+ lastTs = now
76
+ elapsed.value += dt
77
+ if (elapsed.value >= props.interval) {
78
+ elapsed.value = 0
79
+ active.value = (active.value + 1) % props.steps.length
80
+ }
81
+ }, TICK)
82
+ }
83
+
84
+ function goTo(index: number) {
85
+ active.value = index
86
+ elapsed.value = 0
87
+ startTicker()
88
+ }
89
+
90
+ function onMouseEnter() {
91
+ if (props.pauseOnHover)
92
+ stopTicker()
93
+ }
94
+
95
+ function onMouseLeave() {
96
+ if (props.pauseOnHover)
97
+ startTicker()
98
+ }
99
+
100
+ onMounted(startTicker)
101
+ onBeforeUnmount(stopTicker)
102
+ watch(() => [props.interval, props.autoplay, props.steps.length], () => {
103
+ elapsed.value = 0
104
+ startTicker()
105
+ })
106
+ </script>
107
+
108
+ <template>
109
+ <div
110
+ class="grid grid-cols-1 gap-4 lg:gap-10"
111
+ :class="colsClass"
112
+ >
113
+ <ol class="flex flex-col gap-3">
114
+ <li
115
+ v-for="(step, i) in steps"
116
+ :key="i"
117
+ class="ss-card group relative cursor-pointer overflow-hidden rounded-2xl border bg-white transition-all duration-300"
118
+ :class="i === active
119
+ ? 'ss-card-active border-blue-200'
120
+ : 'border-gray-200 hover:border-blue-100'"
121
+ @click="goTo(i)"
122
+ >
123
+ <div class="p-4">
124
+ <div
125
+ class="flex gap-4"
126
+ :class="expandable ? 'items-center' : 'items-start'"
127
+ >
128
+ <span
129
+ class="flex size-11 shrink-0 items-center justify-center rounded-xl"
130
+ :class="step.iconBg || 'bg-blue-50'"
131
+ >
132
+ <component
133
+ :is="step.icon"
134
+ v-if="step.icon"
135
+ class="size-5"
136
+ :class="step.iconColor || 'text-blue-600'"
137
+ />
138
+ </span>
139
+ <div class="min-w-0 flex-1">
140
+ <p class="text-body-md-strong md:text-body-lg-strong text-gray-900">
141
+ {{ step.title }}
142
+ </p>
143
+
144
+ <div v-if="!expandable">
145
+ <p class="mt-1 text-body-sm text-gray-500">
146
+ {{ step.description }}
147
+ </p>
148
+ <div
149
+ v-if="step.chips?.length"
150
+ class="mt-3 flex flex-wrap gap-2"
151
+ >
152
+ <a-status-badge
153
+ v-for="(chip, ci) in step.chips"
154
+ :key="ci"
155
+ type="primary"
156
+ view="inverted"
157
+ size="xs"
158
+ custom-classes="!text-body-sm-medium"
159
+ >
160
+ {{ chip }}
161
+ </a-status-badge>
162
+ </div>
163
+ </div>
164
+ </div>
165
+ </div>
166
+
167
+ <div
168
+ v-if="expandable"
169
+ class="grid transition-all duration-500 ease-out"
170
+ :class="i === active
171
+ ? 'mt-3 grid-rows-[1fr] opacity-100'
172
+ : 'grid-rows-[0fr] opacity-0'"
173
+ >
174
+ <div class="overflow-hidden">
175
+ <p class="text-body-sm text-gray-500">
176
+ {{ step.description }}
177
+ </p>
178
+ <div
179
+ v-if="step.chips?.length"
180
+ class="mt-3 flex flex-wrap gap-2"
181
+ >
182
+ <a-status-badge
183
+ v-for="(chip, ci) in step.chips"
184
+ :key="ci"
185
+ type="primary"
186
+ view="inverted"
187
+ size="xs"
188
+ custom-classes="!text-body-sm-medium"
189
+ >
190
+ {{ chip }}
191
+ </a-status-badge>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </div>
196
+
197
+ <span
198
+ v-if="i === active && autoplay && steps.length > 1"
199
+ class="absolute inset-x-0 bottom-0 h-1 bg-gray-100"
200
+ >
201
+ <span
202
+ class="block h-full rounded-r-full bg-gradient-to-r from-blue-600 to-blue-400 transition-[width] duration-100 ease-linear"
203
+ :style="{ width: fillWidth }"
204
+ />
205
+ </span>
206
+ </li>
207
+ </ol>
208
+
209
+ <div
210
+ @mouseenter="onMouseEnter"
211
+ @mouseleave="onMouseLeave"
212
+ >
213
+ <div class="flex justify-center">
214
+ <slot
215
+ name="panel"
216
+ :active="active"
217
+ :step="steps[active]"
218
+ :progress="progress"
219
+ :count="steps.length"
220
+ :fill-width="fillWidth"
221
+ :go-to="goTo"
222
+ />
223
+ </div>
224
+ </div>
225
+ </div>
226
+ </template>
227
+
228
+ <style scoped>
229
+ .ss-card-active {
230
+ box-shadow: 0 22px 60px -18px rgb(6 141 255 / 22%);
231
+ }
232
+ </style>