spoko-design-system 0.2.40 → 0.2.41
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/index.ts +25 -0
- package/package.json +1 -1
- package/src/components/Category/CategoriesCarousel.astro +101 -0
- package/src/components/Category/CategoriesSidebar.astro +187 -0
- package/src/components/Category/CategoryDetails.astro +82 -0
- package/src/components/Category/CategoryLink.vue +23 -0
- package/src/components/Category/CategorySection.astro +70 -0
- package/src/components/Category/CategorySidebarToggler.vue +10 -0
- package/src/components/Category/SubCategoryLink.vue +29 -0
- package/src/components/Product/ProductButton.vue +18 -0
- package/src/components/Product/ProductCodes.vue +167 -0
- package/src/utils/product/getPriceFormatted.ts +14 -0
- package/src/utils/product/getProductChecklist.ts +15 -0
- package/src/utils/seo/getShorterDescription.ts +12 -0
- package/src/utils/text/formatDate.ts +6 -0
- package/src/utils/text/formatLocaleNumber.ts +7 -0
- package/src/utils/text/formatPad.ts +12 -0
- package/src/utils/text/getNumberFormatted.ts +33 -0
- package/src/utils/text/getTranslatedLink.ts +5 -0
package/index.ts
CHANGED
|
@@ -22,7 +22,11 @@ export { default as ProductDetailsList } from './src/components/ProductDetailsLi
|
|
|
22
22
|
export { default as FeaturesList } from './src/components/FeaturesList.vue';
|
|
23
23
|
export { default as ProductCodes } from './src/components/ProductCodes.vue';
|
|
24
24
|
export { default as ProductEngineType } from './src/components/Product/ProductEngineType.vue';
|
|
25
|
+
export { default as ProductButton } from './src/components/Product/ProductButton.vue';
|
|
25
26
|
|
|
27
|
+
export { default as CategoryLink } from './src/components/Category/CategoryLink.vue';
|
|
28
|
+
export { default as CategorySidebarToggler } from './src/components/Category/CategorySidebarToggler.vue';
|
|
29
|
+
export { default as SubCategoryLink } from './src/components/Category/SubCategoryLink.vue';
|
|
26
30
|
|
|
27
31
|
// Astro Components
|
|
28
32
|
export { default as Copyright } from './src/components/Copyright.astro';
|
|
@@ -31,3 +35,24 @@ export { default as Faq } from './src/components/Faq.astro';
|
|
|
31
35
|
export { default as FaqItem } from './src/components/FaqItem.astro';
|
|
32
36
|
export { default as ProductNumber } from './src/components/Product/ProductNumber.astro';
|
|
33
37
|
export { default as ProductImage } from './src/components/Product/ProductImage.astro';
|
|
38
|
+
|
|
39
|
+
export { default as CategoriesCarousel } from './src/components/Category/CategoriesCarousel.astro';
|
|
40
|
+
export { default as CategoriesSidebar } from './src/components/Category/CategoriesSidebar.astro';
|
|
41
|
+
export { default as CategoryDetails } from './src/components/Category/CategoryDetails.astro';
|
|
42
|
+
export { default as CategorySection } from './src/components/Category/CategorySection.astro';
|
|
43
|
+
|
|
44
|
+
// Utils: Product
|
|
45
|
+
export { default as getPriceFormatted } from './src/utils/product/getPriceFormatted';
|
|
46
|
+
export { default as getProductChecklist } from './src/utils/product/getProductChecklist';
|
|
47
|
+
|
|
48
|
+
// Utils: SEO
|
|
49
|
+
export { default as getShorterDescription } from './src/utils/seo/getShorterDescription';
|
|
50
|
+
|
|
51
|
+
// Utils: Text
|
|
52
|
+
export { default as formatDate } from './src/utils/text/formatDate';
|
|
53
|
+
export { default as formatLocaleNumber } from './src/utils/text/formatLocaleNumber';
|
|
54
|
+
export { default as formatPad } from './src/utils/text/formatPad';
|
|
55
|
+
export { default as getNumberFormatted } from './src/utils/text/getNumberFormatted';
|
|
56
|
+
export { default as getTranslatedLink } from './src/utils/text/getTranslatedLink';
|
|
57
|
+
|
|
58
|
+
|
package/package.json
CHANGED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { activeCategorySlug, locale, class: className } = Astro.props;
|
|
3
|
+
|
|
4
|
+
import { getTranslatedLink } from "@utils/text/getTranslatedLink"
|
|
5
|
+
import { getMainCategoryList } from "@utils/category/getMainCategoryList"
|
|
6
|
+
import { Image } from 'astro:assets'
|
|
7
|
+
|
|
8
|
+
import i18next, { t } from "i18next";
|
|
9
|
+
const categories = await getMainCategoryList()
|
|
10
|
+
const imgDomain = 'https://api.polo.blue/img/';
|
|
11
|
+
|
|
12
|
+
const activeIndex = activeCategorySlug && categories ? categories.map(a => a.slug).findIndex(e => e === activeCategorySlug) : 0
|
|
13
|
+
---
|
|
14
|
+
<!-- <div class={`cat-menu ${className ? className : ''}`}
|
|
15
|
+
data-pagefind-ignore
|
|
16
|
+
itemscope itemtype="https://schema.org/SiteNavigationElement"
|
|
17
|
+
transition:persist={`catcarousel${activeIndex}`}
|
|
18
|
+
transition:animate="none"
|
|
19
|
+
> -->
|
|
20
|
+
<swiper-container
|
|
21
|
+
class={`categories-carousel flex pb-1 sm:pb-0 bg-white cat-menu ${className ? className : ''}`}
|
|
22
|
+
data-pagefind-ignore
|
|
23
|
+
itemscope itemtype="https://schema.org/SiteNavigationElement"
|
|
24
|
+
transition:persist={`catcarousel${activeIndex}`}
|
|
25
|
+
transition:animate="none"
|
|
26
|
+
data-active={activeIndex}
|
|
27
|
+
initial-slide={activeIndex}
|
|
28
|
+
space-between="0"
|
|
29
|
+
slides-per-view="auto"
|
|
30
|
+
scrollbar="true"
|
|
31
|
+
draggable="true"
|
|
32
|
+
keyboard
|
|
33
|
+
free-mode
|
|
34
|
+
data-off-observer
|
|
35
|
+
run-callbacks-on-init="false"
|
|
36
|
+
>
|
|
37
|
+
{
|
|
38
|
+
categories.map((category, index) => (
|
|
39
|
+
<swiper-slide itemprop="hasPart" role="presentation"
|
|
40
|
+
class={`swiper-slide cats-slide group ${category.slug === activeCategorySlug ? 'active': ''}`}
|
|
41
|
+
>
|
|
42
|
+
<a href={getTranslatedLink(`/${category.slug}/`)}
|
|
43
|
+
class="carousel-item"
|
|
44
|
+
>
|
|
45
|
+
{
|
|
46
|
+
// itemprop="url"
|
|
47
|
+
// role="menuitem"
|
|
48
|
+
// aria-label={ t(`cat.${category.slug}.name`) }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
<Image
|
|
52
|
+
src={`${imgDomain}${category.photo}`}
|
|
53
|
+
alt={t(`cat.${category.slug}.desc`).split('. ', 1)[0]}
|
|
54
|
+
height="70"
|
|
55
|
+
width="70"
|
|
56
|
+
format="avif"
|
|
57
|
+
loading="eager"
|
|
58
|
+
onerror="this.style.display='none';"
|
|
59
|
+
class="cats-img"
|
|
60
|
+
/>
|
|
61
|
+
<div class="swiper-lazy-preloader"></div>
|
|
62
|
+
|
|
63
|
+
<div class="cat-name"
|
|
64
|
+
// itemprop="name"
|
|
65
|
+
>
|
|
66
|
+
{ t(`cat.${category.slug}.name`) }
|
|
67
|
+
</div>
|
|
68
|
+
</a>
|
|
69
|
+
</swiper-slide>
|
|
70
|
+
))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
</swiper-container>
|
|
74
|
+
<!-- </div> -->
|
|
75
|
+
|
|
76
|
+
<style lang="scss">
|
|
77
|
+
.active {
|
|
78
|
+
@apply bg-blue-700 text-white bg-opacity-100;
|
|
79
|
+
|
|
80
|
+
&:not(:hover) .cats-img {
|
|
81
|
+
filter: invert(100%);
|
|
82
|
+
// @apply filter-invert;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.categories-carousel {
|
|
88
|
+
@apply overflow-hidden;
|
|
89
|
+
|
|
90
|
+
&.swiper-initialized {
|
|
91
|
+
.img-preloader {
|
|
92
|
+
display: none;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.swiper-slide {
|
|
98
|
+
@apply transition w-[calc(100%/3.55)] min-w-[calc(100%/3.55)] sm:w-35 sm:min-w-35 3xl:min-w-[calc(100%/12.5-4px)];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
</style>
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
// import { Category } from 'types/index';
|
|
3
|
+
import CategoryLink from "@components/Category/CategoryLink.vue"
|
|
4
|
+
import SubCategoryLink from "@components/Category/SubCategoryLink.vue"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
// import { useTranslation } from "i18next-vue";
|
|
8
|
+
// const { i18next, t } = useTranslation();
|
|
9
|
+
|
|
10
|
+
import i18next, { t } from 'i18next';
|
|
11
|
+
import { Icon } from 'astro-icon/components';
|
|
12
|
+
import { isSidebarOpen } from '../../stores/info.js';
|
|
13
|
+
import { useStore } from '@nanostores/vue';
|
|
14
|
+
import { getCategoryList } from "@utils/category/getCategoryList"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
import { createSubCatLink, createSubSubCatLink} from "@utils/db"
|
|
18
|
+
// import type { SubCategory } from 'types/index'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
// read the store value with the `useStore` hook
|
|
22
|
+
const $isSidebarOpen = useStore(isSidebarOpen);
|
|
23
|
+
const { catParameters, activeSubCategorySlug, activeSubSubCategorySlug, locale } = Astro.props;
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const isActiveSubCategory = (subCategorySlug: string) => {
|
|
28
|
+
getCategoryList(i18next.language) // hack to create translations
|
|
29
|
+
return categories.activeSubCategory !== null && categories.activeSubCategory === subCategorySlug
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// const hideSidebar = () => {
|
|
33
|
+
// sidebar.setMode(false)
|
|
34
|
+
// }
|
|
35
|
+
|
|
36
|
+
// const isActiveSubCategory = null
|
|
37
|
+
|
|
38
|
+
// const sidebar = reactive({
|
|
39
|
+
// isShow: true
|
|
40
|
+
// })
|
|
41
|
+
|
|
42
|
+
const categories = {
|
|
43
|
+
activeCategory: catParameters.category,
|
|
44
|
+
activeSubCategory: activeSubCategorySlug ? activeSubCategorySlug : catParameters.category.children ? catParameters.category.children[0] : null,
|
|
45
|
+
activeSubSubCategory: null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const info = {
|
|
49
|
+
isMobile: false,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const isShowCatMobileMenu = false
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
<div data-pagefind-ignore id="sidebar" transition:name="sidebar" transition:animate="fade"
|
|
57
|
+
|
|
58
|
+
class="
|
|
59
|
+
sidebar mb-4 fixed top-0 ease delay-100 flex-col ml-0 overflow-hidden z-40 sm:z-0 md:(relative transition-all w-1/4 max-w-64 min-w-56 overflow-visible)" >
|
|
60
|
+
<!-- :class="showSidebar ? 'w-screen h-full-mobile md:h-auto' : 'h-0 w-0 fixed md:(h-auto -ml-64 static)'" -->
|
|
61
|
+
{ categories.activeSubCategory !== null && categories.activeCategory && categories.activeCategory.children && (
|
|
62
|
+
<div
|
|
63
|
+
class="subcat-menu bg-white sm:border box-border sm:border-gray-200 dark:sm:border-gray-500 sm:border-l-0 md:sticky md:top-14 bg-white dark:bg-blue-901 z-10 sm:mb-2 h-full md:h-auto pb-3 h-sidebar max-h-sidebar sm:h-auto sm:max-h-auto"
|
|
64
|
+
>
|
|
65
|
+
<div class={`subcategories flex-col pb-3 ${categories.activeSubSubCategory !== null ? 'additional-subcategories' : ''}`} >
|
|
66
|
+
<div class="sidebar-title">
|
|
67
|
+
<span>{ t('heading.subcategories') }</span>
|
|
68
|
+
<br>
|
|
69
|
+
</div>
|
|
70
|
+
<ul>
|
|
71
|
+
{
|
|
72
|
+
categories.activeCategory.children.map((subcategory) => (
|
|
73
|
+
<li class="border-b-1 border-gray-100 sm:border-none">
|
|
74
|
+
<CategoryLink
|
|
75
|
+
active={isActiveSubCategory(subcategory.slug)}
|
|
76
|
+
text={t(`cat.${categories.activeCategory.slug}.${subcategory.slug}.name`)}
|
|
77
|
+
href={createSubCatLink(locale, catParameters.category.slug, subcategory.slug)}
|
|
78
|
+
/>
|
|
79
|
+
{
|
|
80
|
+
isActiveSubCategory(subcategory.slug) && (
|
|
81
|
+
<div>
|
|
82
|
+
{ catParameters.category && catParameters.subcategory && catParameters.subcategory && catParameters.subcategory && catParameters.subcategory.children && (
|
|
83
|
+
<ul
|
|
84
|
+
class="subcategories sm:pb-2"
|
|
85
|
+
>
|
|
86
|
+
{
|
|
87
|
+
catParameters.subcategory.children.map((subsubcategory) => (
|
|
88
|
+
<li
|
|
89
|
+
class="border-t-1 border-gray-100 sm:border-none"
|
|
90
|
+
>
|
|
91
|
+
<SubCategoryLink
|
|
92
|
+
active={catParameters.subcategory && activeSubSubCategorySlug === subsubcategory.slug}
|
|
93
|
+
text={subsubcategory.name || `missing subsubcat name ${subsubcategory.slug}`}
|
|
94
|
+
href={createSubSubCatLink(locale, catParameters.category.slug, subcategory.slug, subsubcategory.slug)}
|
|
95
|
+
/>
|
|
96
|
+
</li>
|
|
97
|
+
))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
</ul>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
</div>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
</li>
|
|
107
|
+
))
|
|
108
|
+
}
|
|
109
|
+
</ul>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
<button
|
|
114
|
+
class="absolute top-2.5 left-2 text-3xl w-9 h-9 hidden md:hidden"
|
|
115
|
+
onclick="toggleCatMenu()"
|
|
116
|
+
aria-label="Menu"
|
|
117
|
+
>
|
|
118
|
+
<Icon name="octicon:chevron-left-24" class="" />
|
|
119
|
+
</button>
|
|
120
|
+
<button
|
|
121
|
+
class="absolute top-2.5 right-2 text-3xl w-9 h-9 md:hidden"
|
|
122
|
+
onclick="hideSidebar()"
|
|
123
|
+
aria-label="X"
|
|
124
|
+
>
|
|
125
|
+
<Icon name="octicon:x-24" class="" />
|
|
126
|
+
</button>
|
|
127
|
+
|
|
128
|
+
</div>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
<div id="ga-sidebar"></div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<style lang="scss" scoped>
|
|
139
|
+
|
|
140
|
+
.sidebar {
|
|
141
|
+
|
|
142
|
+
@media (max-width: 768px) {
|
|
143
|
+
&.show {
|
|
144
|
+
@apply w-screen h-full-mobile md:h-auto;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
&:not(.show){
|
|
148
|
+
@apply h-0 w-0 fixed md:(h-auto -ml-64 static);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
&.collapsed {
|
|
153
|
+
@apply md:max-w-0 md:min-w-0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.subcategories {
|
|
159
|
+
|
|
160
|
+
.active {
|
|
161
|
+
// @apply bg-blue-700 text-white;
|
|
162
|
+
@apply bg-gray-200 sm:bg-white dark:(bg-blue-901 text-white) text-blue-600 font-600;
|
|
163
|
+
|
|
164
|
+
a {
|
|
165
|
+
color: inherit;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.s-active {
|
|
170
|
+
@apply bg-gray-100 sm:bg-white dark:(bg-blue-901 text-white) text-blue-600 border-gray-400;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.form-check-input {
|
|
175
|
+
@apply appearance-none h-4 w-4 border border-gray-300 bg-white checked:bg-lightBlue-500 checked:border-lightBlue-500 focus:outline-none transition duration-200 mt-1 align-top bg-no-repeat bg-center bg-contain float-left mr-2 cursor-pointer;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.form-check-input:checked[type=checkbox] {
|
|
179
|
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
|
|
180
|
+
}
|
|
181
|
+
</style>
|
|
182
|
+
<script is:inline>
|
|
183
|
+
function hideSidebar() {
|
|
184
|
+
document.getElementById('sidebar').classList.remove("show");
|
|
185
|
+
document.body.classList.remove('overflow-hidden')
|
|
186
|
+
}
|
|
187
|
+
</script>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
import CategorySidebarToggler from "@components/Category/CategorySidebarToggler.vue";
|
|
3
|
+
import { Icon } from 'astro-icon/components';
|
|
4
|
+
const { category, subcategory, subtitle, subsubtitle, titleSmall, locale } = Astro.props;
|
|
5
|
+
import { t } from "i18next";
|
|
6
|
+
|
|
7
|
+
// Compute base URL for localization
|
|
8
|
+
const baseURL = locale === 'en' ? '' : `/${locale}`;
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
<div ref="el"
|
|
12
|
+
class="flex flex-nowrap pr-3 sm:pb-3 sm:pt-4 md:pl-4 relative z-10-off bg-gray-100 md:bg-white"
|
|
13
|
+
transition:name="category-details"
|
|
14
|
+
transition:animate="fade"
|
|
15
|
+
>
|
|
16
|
+
<CategorySidebarToggler onclick="toggleSidebar()">
|
|
17
|
+
<Icon name="ant-design:menu-fold-outlined" class="toggler-btn hidden md:block" />
|
|
18
|
+
<Icon name="ant-design:menu-unfold-outlined" class="toggler-btn hidden" />
|
|
19
|
+
<Icon name="ant-design:menu-outlined" class="toggler-btn md:hidden" />
|
|
20
|
+
</CategorySidebarToggler>
|
|
21
|
+
|
|
22
|
+
<div class="overflow-x-auto overflow-y-hidden flex max-w-full items-center">
|
|
23
|
+
{subtitle ? (
|
|
24
|
+
<>
|
|
25
|
+
<a class="text-lg font-vw-headregular whitespace-nowrap block" href={`${baseURL}/${category.slug}/`}>
|
|
26
|
+
{category.name}
|
|
27
|
+
{titleSmall && <small>{titleSmall}</small>}
|
|
28
|
+
</a>
|
|
29
|
+
<span class="text-gray-200 text-lg inline-block px-1 font-headlight">/</span>
|
|
30
|
+
{!subsubtitle ? (
|
|
31
|
+
<h1 class="text-lg py-2.5 sm:py-0 whitespace-nowrap underline underline-offset-6 decoration-blue-300 decoration-0.5">
|
|
32
|
+
{subtitle} <span class="sr-only"> {t('catalog.extra-short')}</span>
|
|
33
|
+
</h1>
|
|
34
|
+
) : (
|
|
35
|
+
<>
|
|
36
|
+
<div class="text-lg py-2.5 sm:py-0 whitespace-nowrap ">
|
|
37
|
+
<a href={`${baseURL}/${category.slug}/${subcategory.slug}/`}>
|
|
38
|
+
{subtitle}
|
|
39
|
+
</a>
|
|
40
|
+
</div>
|
|
41
|
+
<span class="text-gray-200 text-lg inline-block px-1 font-headlight">/</span>
|
|
42
|
+
<h1 class="text-lg py-2.5 sm:py-0 whitespace-nowrap underline underline-offset-6 decoration-blue-300 decoration-0.5">
|
|
43
|
+
{subsubtitle} <span class="sr-only"> {t('catalog.extra-short')}</span>
|
|
44
|
+
</h1>
|
|
45
|
+
</>
|
|
46
|
+
)}
|
|
47
|
+
</>
|
|
48
|
+
) : (
|
|
49
|
+
<h1 class="text-lg py-2.5 sm:py-0 whitespace-nowrap">
|
|
50
|
+
{category.name}
|
|
51
|
+
{titleSmall && <small>{titleSmall}</small>}
|
|
52
|
+
<span class="sr-only"> {t('catalog.extra-short')}</span>
|
|
53
|
+
</h1>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<script is:inline>
|
|
59
|
+
function toggleSidebar() {
|
|
60
|
+
function handleToggle(x) {
|
|
61
|
+
const sidebar = document.getElementById('sidebar');
|
|
62
|
+
if (x.matches) { // Mobile view
|
|
63
|
+
document.body.classList.toggle('overflow-hidden');
|
|
64
|
+
sidebar.classList.toggle('show');
|
|
65
|
+
} else {
|
|
66
|
+
document.body.classList.remove('overflow-hidden');
|
|
67
|
+
sidebar.classList.toggle('collapsed');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
var x = window.matchMedia("(max-width: 768px)");
|
|
72
|
+
handleToggle(x); // Call listener at runtime
|
|
73
|
+
x.addListener(handleToggle); // Attach listener for state changes
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<style>
|
|
78
|
+
.category-toggler {
|
|
79
|
+
right: -1px;
|
|
80
|
+
bottom: -1px;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
text: {
|
|
4
|
+
type: String,
|
|
5
|
+
default: null,
|
|
6
|
+
required: true,
|
|
7
|
+
},
|
|
8
|
+
active: {
|
|
9
|
+
type: Boolean,
|
|
10
|
+
default: false,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<a
|
|
18
|
+
class="category-link"
|
|
19
|
+
:class="!props.active ? '' : 'active'"
|
|
20
|
+
>
|
|
21
|
+
{{ props.text }}
|
|
22
|
+
</a>
|
|
23
|
+
</template>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { category, subcategory, subsubcategory, locale} = Astro.props;
|
|
3
|
+
import { t } from "i18next";
|
|
4
|
+
|
|
5
|
+
import ProductLink from "@components/Product/ProductLink.astro";
|
|
6
|
+
import { Icon } from 'astro-icon/components';
|
|
7
|
+
|
|
8
|
+
import { createSubCatLink, createSubSubCatLink } from "@utils/db";
|
|
9
|
+
import { getCategoryProducts } from "@utils/category/getCategoryProducts"
|
|
10
|
+
|
|
11
|
+
const category2search = subsubcategory ? subsubcategory : (subcategory ? subcategory : category)
|
|
12
|
+
const mainItemsCount = category2search.children ? 4 : 8
|
|
13
|
+
const products = await getCategoryProducts(category2search.id, mainItemsCount);
|
|
14
|
+
|
|
15
|
+
const getArray = async () => {
|
|
16
|
+
const arr: { name: string; id: number }[] = [];
|
|
17
|
+
|
|
18
|
+
if (category2search.children) {
|
|
19
|
+
for (const childCat of category2search.children) {
|
|
20
|
+
const output = await getCategoryProducts(childCat.id, 3);
|
|
21
|
+
arr.push(output);
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
null
|
|
25
|
+
}
|
|
26
|
+
return arr.flat(1)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const childrenProducts = await getArray()
|
|
30
|
+
|
|
31
|
+
const categoryLink = subsubcategory ? createSubSubCatLink(locale, category.slug, subcategory.slug, subsubcategory.slug) : createSubCatLink(locale, category.slug, subcategory.slug);
|
|
32
|
+
---
|
|
33
|
+
<div class="category-section">
|
|
34
|
+
<div class="px-4 md:px-1.5 py-3 text-xl flex">
|
|
35
|
+
<a href={categoryLink} class="hover:underline">
|
|
36
|
+
<h2>{category2search.name}</h2>
|
|
37
|
+
</a>
|
|
38
|
+
</div>
|
|
39
|
+
<swiper-container slides-per-view="auto" space-between="0" class="flex w-full max-w-full"
|
|
40
|
+
itemscope itemtype="https://schema.org/ItemList"
|
|
41
|
+
>
|
|
42
|
+
{childrenProducts && childrenProducts.length > 0 && (
|
|
43
|
+
childrenProducts.map((product, index) => <swiper-slide class="category-tile product-link"><ProductLink class="w-full" productId={product.id} locale={locale} index={index+1} /></swiper-slide>)
|
|
44
|
+
)}
|
|
45
|
+
{products && products.length > 0 && (
|
|
46
|
+
products.map((product, index) =>
|
|
47
|
+
<swiper-slide class="category-tile product-link"><ProductLink class="w-full h-full" productId={product.id} locale={locale} index={index+1}/></swiper-slide>)
|
|
48
|
+
)}
|
|
49
|
+
<swiper-slide class="category-tile product-link pr-4 pb-4 sm:pb-1">
|
|
50
|
+
<a
|
|
51
|
+
href={categoryLink}
|
|
52
|
+
title={category2search.name}
|
|
53
|
+
class="uppercase flex items-center bg-white w-full sm:w-auto sm:h-full mt-0 p-8 aspect-ratio-[4/3] sm:aspect-auto mb-auto sm:mb-0 sm:text-sm">
|
|
54
|
+
{t("more")}
|
|
55
|
+
<Icon name="la:arrow-right" class="h-4 ml-2 text-blue-500" />
|
|
56
|
+
</a>
|
|
57
|
+
</swiper-slide>
|
|
58
|
+
</swiper-container>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<style lang="scss">
|
|
62
|
+
.category-section {
|
|
63
|
+
content-visibility: auto;
|
|
64
|
+
|
|
65
|
+
&:hover {
|
|
66
|
+
content-visibility: visible;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
</style>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
text: {
|
|
4
|
+
type: String,
|
|
5
|
+
default: null,
|
|
6
|
+
required: true,
|
|
7
|
+
},
|
|
8
|
+
active: {
|
|
9
|
+
type: Boolean,
|
|
10
|
+
default: false,
|
|
11
|
+
required: true,
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<a
|
|
18
|
+
v-if="!props.active"
|
|
19
|
+
class="w-full block text-base hover:(bg-gray-50 sm:bg-inherit) font-medium text-gray-300 pr-2 py-2 text-left pl-6 sm:(pr-3 text-sm w-auto) md:(py-0.5 pr-4)"
|
|
20
|
+
>
|
|
21
|
+
{{ props.text }}
|
|
22
|
+
</a>
|
|
23
|
+
<div
|
|
24
|
+
v-else
|
|
25
|
+
class="w-full text-base hover:(bg-gray-50 sm:bg-inherit) font-medium pr-2 py-2 pl-6 sm:(w-auto pr-3 text-sm) md:(py-0.5 pr-4) s-active"
|
|
26
|
+
>
|
|
27
|
+
{{ props.text }}
|
|
28
|
+
</div>
|
|
29
|
+
</template>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
shadow: {
|
|
4
|
+
type: Boolean,
|
|
5
|
+
default: false,
|
|
6
|
+
required: false,
|
|
7
|
+
},
|
|
8
|
+
})
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<button
|
|
13
|
+
class="product-button"
|
|
14
|
+
:class="props.shadow ? 'drop-shadow hover:(drop-shadow-md)' : ''"
|
|
15
|
+
>
|
|
16
|
+
<slot />
|
|
17
|
+
</button>
|
|
18
|
+
</template>
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<script lang="ts" setup>
|
|
2
|
+
import type { PropType } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
prcodes: {
|
|
6
|
+
type: Object as PropType<string[] | null>,
|
|
7
|
+
default: null,
|
|
8
|
+
required: true,
|
|
9
|
+
},
|
|
10
|
+
isPdp: {
|
|
11
|
+
type: Boolean,
|
|
12
|
+
default: false,
|
|
13
|
+
required: false,
|
|
14
|
+
},
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const codes = props.prcodes || []
|
|
18
|
+
const decodedCodes = codes ? codes.sort() : []
|
|
19
|
+
|
|
20
|
+
const settings = {
|
|
21
|
+
prcodes: decodedCodes,
|
|
22
|
+
}
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
|
|
27
|
+
<span
|
|
28
|
+
v-for="(prcode, index) in settings.prcodes"
|
|
29
|
+
:key="index"
|
|
30
|
+
class="not-last:mr-1"
|
|
31
|
+
>
|
|
32
|
+
<span data-pagefind-filter="PR-Code"
|
|
33
|
+
v-if="!String(prcode).includes('+')"
|
|
34
|
+
class="btn-prcode "
|
|
35
|
+
:class="`btn-prcode--${prcode}`"
|
|
36
|
+
>
|
|
37
|
+
{{ prcode }}
|
|
38
|
+
</span>
|
|
39
|
+
<span v-else >
|
|
40
|
+
<span v-for="(splittedCode, index2) in String(prcode).split('+')" :key="index2" class="btn-prcode" :class="`btn-prcode--${splittedCode} ${isPdp ? ' btn-prcode--pdp' : ''}` " data-pagefind-filter="PR-Code">
|
|
41
|
+
{{ splittedCode }}
|
|
42
|
+
</span>
|
|
43
|
+
</span>
|
|
44
|
+
</span>
|
|
45
|
+
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<style lang="scss" scoped>
|
|
49
|
+
.btn-prcode {
|
|
50
|
+
@apply relative inline-block leading-none px-1 py-0.5 mr-1 cursor-pointer font-mono border-solid border-1 border-gray-200 select-none text-gray-500 last:mr-0 dark:border-white dark:border-opacity-10 dark:text-gray-300 dark:bg-white dark:bg-opacity-30 not-last:mr-2 not-last:after:content-[+] dark:after:text-white/50 after:pl-0.5 after:text-blue-700 after:absolute after:w-4 text-center;
|
|
51
|
+
|
|
52
|
+
&--pdp {
|
|
53
|
+
@apply mb-1
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&:before {
|
|
57
|
+
@apply rounded-2 shadow-sm py-0.5 px-2 bg-gray-100 whitespace-nowrap text-xs dark:text-black dark:bg-lightBlue-500;
|
|
58
|
+
|
|
59
|
+
display: none;
|
|
60
|
+
position: absolute;
|
|
61
|
+
text-align: center;
|
|
62
|
+
// bottom: 100%;
|
|
63
|
+
top: -10px;
|
|
64
|
+
transform: translateY(-50%) translateX(-50%);
|
|
65
|
+
left: 50%;
|
|
66
|
+
z-index: 50;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&:hover:before {
|
|
70
|
+
display:block;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
&--2JK {
|
|
74
|
+
color: #f3881d;
|
|
75
|
+
&:before {
|
|
76
|
+
content: 'CROSS';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&--1LR,
|
|
81
|
+
&--1ZG,
|
|
82
|
+
&--1ZJ {
|
|
83
|
+
&:before {
|
|
84
|
+
content: '⌀ 256 mm';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&--1KD,
|
|
89
|
+
&--1ZP,
|
|
90
|
+
&--1ZR {
|
|
91
|
+
&:before {
|
|
92
|
+
content: '⌀ 310 mm';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
&--1ZD,
|
|
97
|
+
&--1ZC,
|
|
98
|
+
&--1LN {
|
|
99
|
+
&:before {
|
|
100
|
+
content: '⌀ 288 mm; LUCAS';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&--2JZ {
|
|
105
|
+
@apply: text-lightBlue-500;
|
|
106
|
+
&:before {
|
|
107
|
+
content: 'Bluemotion';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&--7L6 {
|
|
112
|
+
@apply: text-lightBlue-500;
|
|
113
|
+
&:before {
|
|
114
|
+
content: 'Bluemotion (CFWA + start-stop)';
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&--1KK,
|
|
119
|
+
&--1KT,
|
|
120
|
+
&--1KV,
|
|
121
|
+
&--1LV,
|
|
122
|
+
&--2EJ {
|
|
123
|
+
&:before {
|
|
124
|
+
content: '⌀ 230 mm';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
&--2JE {
|
|
129
|
+
@apply text-lightBlue-700;
|
|
130
|
+
|
|
131
|
+
&:before {
|
|
132
|
+
content: 'BlueGT';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
&--2JP {
|
|
137
|
+
&:before {
|
|
138
|
+
content: 'R-Line';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// WRC Street R
|
|
143
|
+
&--E5M, // emblems/stickers
|
|
144
|
+
&--1KD, //brakes
|
|
145
|
+
&--1ZP, //brakes
|
|
146
|
+
&--2JQ,//bumpers
|
|
147
|
+
&--TA2 { //engine parts
|
|
148
|
+
color: blue;
|
|
149
|
+
&:before {
|
|
150
|
+
content: 'R WRC Street';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// GTI
|
|
155
|
+
&--1KV,
|
|
156
|
+
&--1ZD,
|
|
157
|
+
&--1ZR,
|
|
158
|
+
&--0NH,
|
|
159
|
+
&--2JD {
|
|
160
|
+
color: red;
|
|
161
|
+
&:before {
|
|
162
|
+
content: 'GTI';
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import i18next from "i18next";
|
|
2
|
+
|
|
3
|
+
export const getPriceFormatted = (product: any) => {
|
|
4
|
+
if (i18next.language === 'en') {
|
|
5
|
+
return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(product.price_eur, )
|
|
6
|
+
}
|
|
7
|
+
if (i18next.language === 'pl') {
|
|
8
|
+
return new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'PLN' }).format(product.price_pln, )
|
|
9
|
+
}
|
|
10
|
+
else {
|
|
11
|
+
return 'no price'
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { t } from "i18next";
|
|
2
|
+
|
|
3
|
+
export const getProductCheckList = (productDetails) => {
|
|
4
|
+
if (!productDetails || !productDetails.length) {
|
|
5
|
+
return null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const list = productDetails.filter(item => item.icon);
|
|
9
|
+
|
|
10
|
+
if (!list.length) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return list.map(detail => t(`detail.value.${detail.value}`));
|
|
15
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const MAX_DESCRIPTION_LENGTH = 150;
|
|
2
|
+
|
|
3
|
+
export const getShorterDescription = (description: string | null, limit = MAX_DESCRIPTION_LENGTH) => {
|
|
4
|
+
function cutString(s: string, n: number) {
|
|
5
|
+
const text = s.replace(/(\n)/g, " ");
|
|
6
|
+
const cut = text.indexOf('. ', n);
|
|
7
|
+
return cut === -1 ? text : `${text.substring(0, cut)}.`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return description ? cutString(description, limit) || '' : '';
|
|
11
|
+
|
|
12
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const formatPad = (num: number, size: number) => {
|
|
2
|
+
return String(num).padStart(size, '0');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
// export const formatPad = (num: number, size: number) => {
|
|
6
|
+
// let productId = String(num)
|
|
7
|
+
|
|
8
|
+
// if (size < 10)
|
|
9
|
+
// while (productId.length < size) productId = `0${productId}`
|
|
10
|
+
|
|
11
|
+
// return productId
|
|
12
|
+
// }
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export const getNumberFormatted = (num, digits) => {
|
|
2
|
+
const lookup = [
|
|
3
|
+
{ value: 1e18, symbol: "E" },
|
|
4
|
+
{ value: 1e15, symbol: "P" },
|
|
5
|
+
{ value: 1e12, symbol: "T" },
|
|
6
|
+
{ value: 1e9, symbol: "G" },
|
|
7
|
+
{ value: 1e6, symbol: "M" },
|
|
8
|
+
{ value: 1e3, symbol: "k" },
|
|
9
|
+
{ value: 1, symbol: "" }
|
|
10
|
+
];
|
|
11
|
+
const item = lookup.find(item => num >= item.value) || lookup[lookup.length - 1];
|
|
12
|
+
return (num / item.value).toFixed(digits).replace(/\.0+$|(\.[0-9]*[1-9])0+$/, "$1") + item.symbol;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* Tests
|
|
17
|
+
*/
|
|
18
|
+
// const tests = [
|
|
19
|
+
// { num: 0, digits: 1 },
|
|
20
|
+
// { num: 12, digits: 1 },
|
|
21
|
+
// { num: 1234, digits: 1 },
|
|
22
|
+
// { num: 100000000, digits: 1 },
|
|
23
|
+
// { num: 299792458, digits: 1 },
|
|
24
|
+
// { num: 759878, digits: 1 },
|
|
25
|
+
// { num: 759878, digits: 0 },
|
|
26
|
+
// { num: 123, digits: 1 },
|
|
27
|
+
// { num: 123.456, digits: 1 },
|
|
28
|
+
// { num: 123.456, digits: 2 },
|
|
29
|
+
// { num: 123.456, digits: 4 }
|
|
30
|
+
// ];
|
|
31
|
+
// tests.forEach(test => {
|
|
32
|
+
// console.log("getNumberFormatted(%f, %i) = %s", test.num, test.digits, getNumberFormatted(test.num, test.digits));
|
|
33
|
+
// });
|