adata-ui 2.1.40-beta → 2.1.40-beta.1

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.
@@ -1,16 +1,10 @@
1
1
  <script setup lang="ts">
2
- import { PAGES } from '#adata-ui/shared/constans/pages'
3
2
  import { usePkServicesLinks } from '#adata-ui/composables/useHeaderNavigationLinks'
4
- import { useUrls } from '#adata-ui/composables/useUrls'
3
+ import { useActiveNavigation } from '#adata-ui/composables/useActiveNavigation'
5
4
  import { NuxtLinkLocale } from '#components'
6
5
 
7
6
  const services = usePkServicesLinks()
8
- const route = useRoute()
9
- const localePath = useLocalePath()
10
- const { landing } = useUrls()
11
- const { locale } = useI18n()
12
-
13
- const pageUrl = useRequestURL()
7
+ const { isActiveService } = useActiveNavigation()
14
8
 
15
9
  const blockStyles = [
16
10
  'first-border-gradient',
@@ -23,22 +17,6 @@ const blockStyles = [
23
17
  'eighth-border-gradient',
24
18
  'ninth-border-gradient',
25
19
  ]
26
- const linkByIndex = [
27
- PAGES.pk.main,
28
- PAGES.pk.employees,
29
- PAGES.pk.connections,
30
- PAGES.pk.offshore,
31
- PAGES.pk.foreign,
32
- PAGES.pk.unload,
33
- PAGES.pk.compare,
34
- PAGES.pk.sanctions,
35
- buildLocalizedUrl(locale, landing, '/all-services'),
36
- ]
37
-
38
- const normalize = (path: string) => {
39
- const cleaned = path.replace(/\/+$/, '')
40
- return cleaned === '' ? '/' : cleaned
41
- }
42
20
  </script>
43
21
 
44
22
  <template>
@@ -46,15 +24,15 @@ const normalize = (path: string) => {
46
24
  <component
47
25
  v-for="(service, index) in services"
48
26
  :key="index"
49
- :is="normalize(localePath(service.short)) === normalize(route.path) && pageUrl.hostname.startsWith('pk') ? 'div' : NuxtLinkLocale"
50
- :to="normalize(localePath(service.short)) === normalize(route.path) ? '' : service.to"
27
+ :is="isActiveService(service.to) ? 'div' : NuxtLinkLocale"
28
+ :to="isActiveService(service.to) ? '' : service.to"
51
29
  :class="['flex flex-col items-center gap-2 p-2', blockStyles[index]]"
52
30
  >
53
31
  <div
54
32
  class="size-10 p-2 rounded-lg"
55
33
  :class="[
56
34
  'bg-deepblue-900/5 dark:bg-gray-200/5',
57
- {'!bg-blue-700 text-white dark:!bg-blue-500 ': route.path.replace(/\/+$/, '') === localePath(linkByIndex[index]).replace(/\/+$/, '')}
35
+ {'!bg-blue-700 text-white dark:!bg-blue-500 ': isActiveService(service.to)}
58
36
  ]"
59
37
  >
60
38
  <component
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { PAGES } from '#adata-ui/shared/constans/pages'
2
+ import { useActiveNavigation } from '#adata-ui/composables/useActiveNavigation'
3
3
 
4
4
  interface NavList {
5
5
  title: string
@@ -18,61 +18,28 @@ const props = defineProps<{
18
18
  currentModule: boolean
19
19
  }>()
20
20
 
21
+ const { isActiveService } = useActiveNavigation()
22
+
21
23
  function isActive(itemPath: string) {
22
24
  if (!props.currentModule) return false
23
-
24
- const currentUrl = window.location.href.split('/').filter(item => !['kk', 'en'].includes(item)).join('/')
25
- const section = PAGES[props.id]
26
-
27
- const currentPath = itemPath.split('/').filter(item => !['kk', 'en'].includes(item)).join('/')
28
-
29
- if (currentUrl === currentPath) return true
30
-
31
- if (currentPath.endsWith('/')) {
32
- let atLeastOne = false
33
-
34
- for (const key in section) {
35
- const path = section[key]
36
- if (key !== 'main') {
37
- const includes = currentUrl.includes(path) || currentUrl.includes('car-result')
38
- if (includes) {
39
- atLeastOne = true
40
- break
41
- }
42
- }
43
- }
44
-
45
- return !atLeastOne
46
- }
47
-
48
- if (currentPath.includes('check-car')) {
49
- if (currentUrl.includes('car-result')) {
50
- return true
51
- }
52
- }
53
-
54
- for (const key in section) {
55
- const path = section[key]
56
- if (key !== 'main') {
57
- const includesBoth = currentUrl.includes(path) && currentUrl.startsWith(currentPath)
58
- if (includesBoth) {
59
- return true
60
- }
61
- }
62
- }
63
-
64
- return false
25
+ return isActiveService(itemPath)
65
26
  }
66
27
  </script>
67
28
 
68
29
  <template>
69
- <section class="group/section overflow-hidden rounded-xl border border-gray-200 bg-white p-3 shadow-sm transition-colors hover:border-blue-700/40 dark:border-gray-800 dark:bg-gray-950 dark:hover:border-blue-500/40">
30
+ <section
31
+ class="group/section overflow-hidden rounded-xl border p-3 shadow-sm transition-colors"
32
+ :class="currentModule
33
+ ? 'border-blue-700/20 bg-blue-50 hover:border-blue-700/40 dark:border-blue-500/20 dark:bg-blue-500/5 dark:hover:border-blue-500/40'
34
+ : 'border-gray-200 bg-white hover:border-blue-700/40 dark:border-gray-800 dark:bg-gray-950 dark:hover:border-blue-500/40'"
35
+ >
70
36
  <div class="mb-3 flex items-start justify-between gap-2">
71
37
  <nuxt-link-locale
72
38
  :to="link"
73
39
  class="group/title flex min-w-0 items-center gap-2 text-sm font-bold text-deepblue-900 transition-colors hover:text-blue-700 dark:text-gray-100 dark:hover:text-blue-500"
74
40
  >
75
- <span class="flex size-8 shrink-0 items-center justify-center rounded-lg bg-blue-700/10 text-blue-700 transition-colors group-hover/title:bg-blue-700 group-hover/title:text-white dark:bg-blue-500/10 dark:text-blue-500">
41
+ <span
42
+ class="flex size-8 shrink-0 items-center justify-center rounded-lg bg-blue-700/10 text-blue-700 transition-colors group-hover/title:bg-blue-700 group-hover/title:text-white dark:bg-blue-500/10 dark:text-blue-500">
76
43
  <component
77
44
  :is="icon"
78
45
  class="size-4"
@@ -86,7 +53,8 @@ function isActive(itemPath: string) {
86
53
  v-if="badge"
87
54
  class="rounded-full bg-blue-700/10 px-1.5 py-0.5 text-[10px] font-bold uppercase leading-none text-blue-700 dark:bg-blue-500/10 dark:text-blue-500"
88
55
  >NEW</span>
89
- <a-icon-arrow-side-up class="size-4 text-gray-500 transition-colors group-hover/section:text-blue-700 dark:group-hover/section:text-blue-500" />
56
+ <a-icon-arrow-side-up
57
+ class="size-4 text-gray-500 transition-colors group-hover/section:text-blue-700 dark:group-hover/section:text-blue-500"/>
90
58
  </div>
91
59
  </div>
92
60
 
@@ -102,13 +70,25 @@ function isActive(itemPath: string) {
102
70
  class="group/item flex items-center gap-2 rounded-lg px-2 py-1.5 transition-colors hover:bg-blue-100 dark:hover:bg-gray-200/10"
103
71
  :class="{ 'bg-blue-100 dark:bg-gray-200/10': isActive(item.to) }"
104
72
  >
105
- <span class="flex size-7 shrink-0 items-center justify-center rounded-md bg-gray-100 text-gray-600 transition-colors group-hover/item:bg-blue-700/10 group-hover/item:text-blue-700 dark:bg-gray-800 dark:text-gray-300 dark:group-hover/item:text-blue-500">
73
+ <span
74
+ class="flex size-7 shrink-0 items-center justify-center rounded-md transition-colors group-hover/item:bg-blue-700/10 group-hover/item:text-blue-700 dark:group-hover/item:text-blue-500"
75
+ :class="{
76
+ 'bg-blue-700/10 text-blue-700 dark:bg-blue-500/10 dark:text-blue-500': isActive(item.to),
77
+ 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-300': !isActive(item.to),
78
+ // 'bg-white text-gray-600 dark:bg-white dark:text-gray-300': currentModule,
79
+ }"
80
+ >
106
81
  <component
107
82
  :is="item.icon"
108
83
  class="size-[14px]"
109
84
  />
110
85
  </span>
111
- <span class="truncate text-sm font-semibold normal-case text-gray-600 transition-colors group-hover/item:text-deepblue-900 dark:text-gray-300 dark:group-hover/item:text-gray-100">{{ item.title }}</span>
86
+ <span
87
+ class="truncate text-sm font-semibold normal-case transition-colors group-hover/item:text-deepblue-900 dark:group-hover/item:text-gray-100"
88
+ :class="isActive(item.to)
89
+ ? 'text-blue-700 dark:text-blue-500'
90
+ : 'text-gray-600 dark:text-gray-300'"
91
+ >{{ item.title }}</span>
112
92
  </nuxt-link-locale>
113
93
  </li>
114
94
  </ul>
@@ -1,6 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import CardGallery from '#adata-ui/components/navigation/header/CardGallery.vue'
3
3
  import NavList from '#adata-ui/components/navigation/header/NavList.vue'
4
+ import { useActiveNavigation } from '#adata-ui/composables/useActiveNavigation'
4
5
  import { useHeaderNavigationLinks } from '#adata-ui/composables/useHeaderNavigationLinks'
5
6
 
6
7
  defineProps<{ url?: string }>()
@@ -8,6 +9,7 @@ defineEmits(['outerClick', 'mouseOver'])
8
9
 
9
10
  const { t } = useI18n()
10
11
  const modules = useHeaderNavigationLinks()
12
+ const { isActiveModule } = useActiveNavigation()
11
13
 
12
14
  // Fixed EDO promo card on the left (like edo-editor); EDO is excluded from the catalog grid below.
13
15
  const edoModule = computed(() => modules.value.find(module => module.key === 'edo') ?? null)
@@ -32,35 +34,39 @@ const catalogColumns = computed(() =>
32
34
  <nuxt-link-locale
33
35
  v-if="edoModule"
34
36
  :to="edoModule.link"
35
- class="group block overflow-hidden rounded-xl border border-blue-700/20 bg-blue-50 p-4 shadow-sm transition-colors hover:border-blue-700/40 hover:bg-blue-100 dark:border-blue-500/20 dark:bg-blue-500/5 dark:hover:border-blue-500/40 dark:hover:bg-blue-500/10"
37
+ class="group block overflow-hidden rounded-xl border border-blue-700/20 p-4 shadow-sm transition-colors hover:border-blue-700/40 hover:bg-blue-100 dark:border-blue-500/20 dark:hover:border-blue-500/40 dark:hover:bg-blue-500/10"
36
38
  :data-test-id="edoModule.data_attribute"
37
39
  >
38
40
  <div class="flex items-start gap-3">
39
- <div class="flex size-10 shrink-0 items-center justify-center rounded-lg bg-blue-700 text-white shadow-sm">
41
+ <div
42
+ class="flex size-10 shrink-0 items-center justify-center rounded-lg bg-blue-700 text-white shadow-sm">
40
43
  <component
41
44
  :is="edoModule.icon"
42
45
  class="size-5"
43
46
  />
44
47
  </div>
45
48
  <div class="min-w-0">
46
- <div class="flex items-center gap-2">
49
+ <div class="flex items-center justify-between gap-2">
47
50
  <h3 class="text-base font-bold normal-case text-deepblue-900 dark:text-gray-100">
48
51
  {{ edoModule.name }}
49
52
  </h3>
50
- <a-icon-arrow-side-up class="size-4 text-blue-700 transition-transform group-hover:-translate-y-0.5 group-hover:translate-x-0.5 dark:text-blue-500" />
53
+ <div class="flex items-center gap-1 justify-end">
54
+ <span
55
+ v-if="edoModule.is_new"
56
+ class="rounded-full bg-blue-700/10 px-1.5 py-0.5 text-[10px] font-bold uppercase leading-none text-blue-700 dark:bg-blue-500/10 dark:text-blue-500"
57
+ >NEW</span>
58
+ <a-icon-arrow-side-up
59
+ class="size-4 text-gray-500 transition group-hover:-translate-y-0.5 group-hover:translate-x-0.5 group-hover:text-blue-700 dark:text-gray-400 dark:group-hover:text-blue-500"/>
60
+ </div>
51
61
  </div>
52
62
  <p class="mt-1 text-sm normal-case leading-snug text-gray-600 dark:text-gray-300">
53
63
  {{ t('header.products.edo.heroSubtitle') }}
54
64
  </p>
55
65
  </div>
56
66
  </div>
57
- <div class="mt-4 inline-flex items-center gap-1.5 text-sm font-semibold normal-case text-blue-700 dark:text-blue-500">
58
- {{ t('header.products.edo.heroCta') }}
59
- <a-icon-arrow-side-up class="size-3.5" />
60
- </div>
61
67
  </nuxt-link-locale>
62
68
 
63
- <card-gallery class="hidden lg:block" />
69
+ <card-gallery class="hidden lg:block"/>
64
70
  </aside>
65
71
 
66
72
  <!-- Catalog grid: products as bordered section cards (EDO lives in the hero). -->
@@ -74,7 +80,7 @@ const catalogColumns = computed(() =>
74
80
  v-for="module in column"
75
81
  :id="module.key"
76
82
  :key="module.key"
77
- :current-module="false"
83
+ :current-module="isActiveModule(module)"
78
84
  :title="module.name"
79
85
  :link="module.link"
80
86
  :nav-list="module.items"
@@ -0,0 +1,84 @@
1
+ // Active-state detection for the products mega-menu.
2
+ //
3
+ // Two levels of "active":
4
+ // 1. activeModule — the current host matches one of the module's hosts
5
+ // (host ≠ module key: e.g. the `fines` module lives on avto.*.kz,
6
+ // and `tenders` spans both zakupki.*.kz and tender.*.kz).
7
+ // 2. activeService — within the active module, the current page matches a
8
+ // concrete item link (`/`, `/unload`, `/foreign`, …).
9
+
10
+ const LOCALE_SEGMENTS = new Set(['kk', 'en'])
11
+
12
+ interface NavTarget { to: string }
13
+ interface NavModule { link: string, items: NavTarget[] }
14
+ interface NormalizedUrl { host: string, key: string }
15
+
16
+ function safeUrl(href: string): URL | null {
17
+ try {
18
+ return new URL(href)
19
+ }
20
+ catch {
21
+ return null
22
+ }
23
+ }
24
+
25
+ // Reduce a URL to a comparable identity: host + locale-stripped path + query.
26
+ // The query is kept because it can be significant (fines main is `/?check_fines=true`).
27
+ function normalize(href: string): NormalizedUrl | null {
28
+ const url = safeUrl(href)
29
+ if (!url) return null
30
+
31
+ const segments = url.pathname
32
+ .split('/')
33
+ .filter(segment => segment && !LOCALE_SEGMENTS.has(segment))
34
+
35
+ const path = `/${segments.join('/')}`
36
+ return { host: url.host, key: path + url.search }
37
+ }
38
+
39
+ // A "root" target has no path segments (e.g. `/` or `/?check_fines=true`).
40
+ // Roots must match exactly, otherwise their prefix would light up every sub-page.
41
+ function isRoot(key: string): boolean {
42
+ return key === '/' || key.startsWith('/?')
43
+ }
44
+
45
+ export function useActiveNavigation() {
46
+ const route = useRoute()
47
+
48
+ // Recompute on every SPA navigation. Host stays constant within an app, so we
49
+ // read it from the request URL (SSR-safe); the path/query come from the route.
50
+ const current = computed<NormalizedUrl | null>(() => {
51
+ const host = useRequestURL().host
52
+ return normalize(`https://${host}${route.fullPath}`)
53
+ })
54
+
55
+ function isActiveModule(module: NavModule): boolean {
56
+ if (!current.value) return false
57
+
58
+ const hosts = new Set<string>()
59
+ const link = normalize(module.link)
60
+ if (link) hosts.add(link.host)
61
+ for (const item of module.items) {
62
+ const item_url = normalize(item.to)
63
+ if (item_url) hosts.add(item_url.host)
64
+ }
65
+
66
+ return hosts.has(current.value.host)
67
+ }
68
+
69
+ function isActiveService(itemTo: string): boolean {
70
+ const cur = current.value
71
+ const item = normalize(itemTo)
72
+ if (!cur || !item || cur.host !== item.host) return false
73
+
74
+ // The car-check page redirects to a `car-result` URL; keep its item lit.
75
+ if (item.key.includes('check-car') && cur.key.includes('car-result')) return true
76
+
77
+ if (cur.key === item.key) return true
78
+ if (isRoot(item.key)) return false
79
+
80
+ return cur.key.startsWith(item.key)
81
+ }
82
+
83
+ return { isActiveModule, isActiveService }
84
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "adata-ui",
3
3
  "type": "module",
4
- "version": "2.1.40-beta",
4
+ "version": "2.1.40-beta.1",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground",