spoko-design-system 0.2.35 → 0.2.36
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/.astro/settings.json +1 -1
- package/package.json +27 -26
- package/src/components/Button.vue +1 -1
- package/src/components/Product/ProductEngineType.vue +43 -0
- package/src/components/Product/ProductImage.astro +42 -0
- package/src/components/Product/ProductLinkInfo.astro +38 -0
- package/src/components/Product/ProductNumber.astro +105 -0
- package/src/components/ProductNumber.astro +86 -95
- package/src/components/ProductTile.astro +1 -1
- package/src/index.ts +23 -0
- package/src/pages/components/details-list.mdx +1 -1
- package/src/pages/components/product-number.mdx +1 -1
- package/src/types/Product.ts +32 -0
- package/src/types/astro.d.ts +5 -0
- package/src/utils/text.ts +43 -0
package/.astro/settings.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spoko-design-system",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.36",
|
|
4
4
|
"private": false,
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "astro dev",
|
|
@@ -27,21 +27,21 @@
|
|
|
27
27
|
"spoko design system"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@algolia/client-search": "^5.
|
|
31
|
-
"@astrojs/mdx": "^3.1.
|
|
30
|
+
"@algolia/client-search": "^5.13.0",
|
|
31
|
+
"@astrojs/mdx": "^3.1.9",
|
|
32
32
|
"@astrojs/node": "^8.3.4",
|
|
33
33
|
"@astrojs/sitemap": "^3.2.1",
|
|
34
34
|
"@astrojs/vue": "^4.5.2",
|
|
35
|
-
"@docsearch/css": "^3.
|
|
35
|
+
"@docsearch/css": "^3.7.0",
|
|
36
36
|
"@iconify-json/ant-design": "^1.2.2",
|
|
37
37
|
"@iconify-json/bi": "^1.2.1",
|
|
38
38
|
"@iconify-json/bx": "^1.2.1",
|
|
39
|
-
"@iconify-json/carbon": "^1.2.
|
|
39
|
+
"@iconify-json/carbon": "^1.2.4",
|
|
40
40
|
"@iconify-json/circle-flags": "^1.2.1",
|
|
41
41
|
"@iconify-json/el": "^1.2.0",
|
|
42
42
|
"@iconify-json/eos-icons": "^1.2.1",
|
|
43
43
|
"@iconify-json/flowbite": "^1.2.2",
|
|
44
|
-
"@iconify-json/fluent": "^1.2.
|
|
44
|
+
"@iconify-json/fluent": "^1.2.6",
|
|
45
45
|
"@iconify-json/fluent-emoji": "1.2.1",
|
|
46
46
|
"@iconify-json/icon-park-outline": "^1.2.1",
|
|
47
47
|
"@iconify-json/la": "^1.2.0",
|
|
@@ -49,41 +49,42 @@
|
|
|
49
49
|
"@iconify-json/noto-v1": "^1.2.0",
|
|
50
50
|
"@iconify-json/octicon": "^1.2.1",
|
|
51
51
|
"@iconify-json/ph": "^1.2.1",
|
|
52
|
-
"@iconify-json/simple-icons": "^1.2.
|
|
52
|
+
"@iconify-json/simple-icons": "^1.2.11",
|
|
53
53
|
"@iconify-json/uil": "^1.2.1",
|
|
54
|
-
"@iconify/json": "^2.2.
|
|
54
|
+
"@iconify/json": "^2.2.268",
|
|
55
55
|
"@iconify/vue": "^4.1.2",
|
|
56
|
-
"@playform/compress": "^0.1.
|
|
56
|
+
"@playform/compress": "^0.1.6",
|
|
57
57
|
"@playform/inline": "github:playform/inline",
|
|
58
|
-
"@types/node": "^22.
|
|
59
|
-
"@unocss/astro": "^0.
|
|
60
|
-
"@unocss/preset-attributify": "^0.
|
|
61
|
-
"@unocss/preset-typography": "^0.
|
|
62
|
-
"@unocss/preset-uno": "^0.
|
|
63
|
-
"@unocss/preset-web-fonts": "^0.
|
|
64
|
-
"@unocss/preset-wind": "^0.
|
|
65
|
-
"@unocss/reset": "^0.
|
|
58
|
+
"@types/node": "^22.9.0",
|
|
59
|
+
"@unocss/astro": "^0.64.0",
|
|
60
|
+
"@unocss/preset-attributify": "^0.64.0",
|
|
61
|
+
"@unocss/preset-typography": "^0.64.0",
|
|
62
|
+
"@unocss/preset-uno": "^0.64.0",
|
|
63
|
+
"@unocss/preset-web-fonts": "^0.64.0",
|
|
64
|
+
"@unocss/preset-wind": "^0.64.0",
|
|
65
|
+
"@unocss/reset": "^0.64.0",
|
|
66
66
|
"@vite-pwa/astro": "^0.4.3",
|
|
67
|
-
"@vueuse/core": "^11.
|
|
67
|
+
"@vueuse/core": "^11.2.0",
|
|
68
68
|
"astro-i18next": "1.0.0-beta.21",
|
|
69
|
-
"astro-icon": "^1.1.
|
|
69
|
+
"astro-icon": "^1.1.2",
|
|
70
70
|
"astro-meta-tags": "^0.3.1",
|
|
71
|
-
"astro-navbar": "^2.3.
|
|
71
|
+
"astro-navbar": "^2.3.6",
|
|
72
72
|
"astro-pagefind": "^1.6.0",
|
|
73
73
|
"astro-remote": "^0.3.3",
|
|
74
|
-
"i18next": "^23.16.
|
|
74
|
+
"i18next": "^23.16.4",
|
|
75
75
|
"i18next-browser-languagedetector": "^8.0.0",
|
|
76
76
|
"i18next-fs-backend": "^2.3.2",
|
|
77
77
|
"i18next-http-backend": "^2.6.2",
|
|
78
78
|
"i18next-vue": "^5.0.0",
|
|
79
79
|
"swiper": "^11.1.14",
|
|
80
|
-
"unocss": "^0.
|
|
81
|
-
"vite": "^5.4.
|
|
80
|
+
"unocss": "^0.64.0",
|
|
81
|
+
"vite": "^5.4.10",
|
|
82
82
|
"vue": "^3.5.12"
|
|
83
83
|
},
|
|
84
84
|
"devDependencies": {
|
|
85
|
-
"@unocss/transformer-variant-group": "^0.
|
|
86
|
-
"
|
|
85
|
+
"@unocss/transformer-variant-group": "^0.64.0",
|
|
86
|
+
"@vitejs/plugin-vue": "^5.1.4",
|
|
87
|
+
"astro": "^4.16.10",
|
|
87
88
|
"unocss": "^0.60.0"
|
|
88
89
|
},
|
|
89
90
|
"packageManager": "pnpm@9.8.0",
|
|
@@ -94,4 +95,4 @@
|
|
|
94
95
|
"sharp@<0.30.5": ">=0.30.5"
|
|
95
96
|
}
|
|
96
97
|
}
|
|
97
|
-
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
interface Engine {
|
|
3
|
+
id: number | string;
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<{
|
|
8
|
+
engines: Engine[];
|
|
9
|
+
}>(), {
|
|
10
|
+
engines: () => []
|
|
11
|
+
});
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<ul v-if="engines.length" class="engine-types">
|
|
16
|
+
<li
|
|
17
|
+
v-for="(engine, index) in engines"
|
|
18
|
+
:key="engine.id"
|
|
19
|
+
class="engine-type"
|
|
20
|
+
:class="{ 'with-comma': index !== engines.length - 1 }"
|
|
21
|
+
>
|
|
22
|
+
{{ engine.name }}
|
|
23
|
+
</li>
|
|
24
|
+
</ul>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<style lang="scss">
|
|
28
|
+
.engine-types {
|
|
29
|
+
@apply flex flex-wrap items-center;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.engine-type {
|
|
33
|
+
@apply inline-block;
|
|
34
|
+
|
|
35
|
+
&.with-comma {
|
|
36
|
+
@apply mr-1;
|
|
37
|
+
|
|
38
|
+
&:after {
|
|
39
|
+
content: ',';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Image } from 'astro:assets'
|
|
3
|
+
|
|
4
|
+
interface ImageObject {
|
|
5
|
+
src: string;
|
|
6
|
+
alt?: string;
|
|
7
|
+
height?: string | number;
|
|
8
|
+
width?: string | number;
|
|
9
|
+
loading?: 'lazy' | 'eager';
|
|
10
|
+
srcset?: string[];
|
|
11
|
+
class?: string;
|
|
12
|
+
index?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { imageObject, imagesApiUrl } = Astro.props as { imageObject: ImageObject; imagesApiUrl: string };
|
|
16
|
+
|
|
17
|
+
let inputProps = {};
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if (imageObject.index && imageObject.index === 1) {
|
|
21
|
+
inputProps['data-pagefind-meta'] = 'image[src], image_alt[alt]';
|
|
22
|
+
imageObject.loading = 'eager'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (imageObject.srcset && imageObject.srcset.length) {
|
|
26
|
+
inputProps['widths'] = imageObject.srcset
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
<Image
|
|
31
|
+
src={`${imagesApiUrl}${imageObject.src}`}
|
|
32
|
+
alt={imageObject.alt}
|
|
33
|
+
height={imageObject.height}
|
|
34
|
+
width={imageObject.width}
|
|
35
|
+
loading={imageObject.loading ? imageObject.loading : 'lazy'}
|
|
36
|
+
itemprop="image"
|
|
37
|
+
format="avif"
|
|
38
|
+
data-pagefind-index-attrs={imageObject.alt}
|
|
39
|
+
onerror="this.style.display='none';"
|
|
40
|
+
class={`product-image ${imageObject.class || ''}`}
|
|
41
|
+
{ ...inputProps}
|
|
42
|
+
/>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { Product } from '../../types/Product';
|
|
3
|
+
import ProductNumber from "@components/Product/ProductNumber.astro"
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
product: Product;
|
|
7
|
+
nameFormatted: string;
|
|
8
|
+
price?: string;
|
|
9
|
+
url: string;
|
|
10
|
+
index?: number | null;
|
|
11
|
+
bigTile?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { product, nameFormatted, price, url, index, bigTile } = Astro.props;
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<div class={`flex flex-col ${bigTile ? '' : 'sm:pl-4'}`}>
|
|
19
|
+
{price && (
|
|
20
|
+
<p class="block mb-2 font-600 font-headbold text-5">
|
|
21
|
+
{price}
|
|
22
|
+
</p>
|
|
23
|
+
)}
|
|
24
|
+
<a
|
|
25
|
+
class="product-link--url"
|
|
26
|
+
href={url}
|
|
27
|
+
itemprop="url"
|
|
28
|
+
title={product.number}
|
|
29
|
+
set:html={nameFormatted}
|
|
30
|
+
/>
|
|
31
|
+
<ProductNumber productNumber={product.number} copyDisabled={true} />
|
|
32
|
+
{index !== null && (
|
|
33
|
+
<>
|
|
34
|
+
<meta itemprop="position" content={index} />
|
|
35
|
+
<meta itemprop="name" content={nameFormatted} />
|
|
36
|
+
</>
|
|
37
|
+
)}
|
|
38
|
+
</div>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { t } from "i18next";
|
|
3
|
+
import ButtonCopy from "./../ButtonCopy.vue";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
productNumber: string | null;
|
|
7
|
+
copyDisabled?: boolean;
|
|
8
|
+
isPdp?: boolean;
|
|
9
|
+
small?: boolean;
|
|
10
|
+
big?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
productNumber,
|
|
16
|
+
copyDisabled,
|
|
17
|
+
isPdp,
|
|
18
|
+
small,
|
|
19
|
+
big,
|
|
20
|
+
class: className
|
|
21
|
+
} = Astro.props;
|
|
22
|
+
|
|
23
|
+
const buttonTexts = {
|
|
24
|
+
copy: t('copy'),
|
|
25
|
+
copied: t('copied')
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
// Regex patterns:
|
|
29
|
+
const LIQUIDS_PATTERN = /^(\w{1})(\w{3})(\w{3})(.*)$/;
|
|
30
|
+
const WHEELS_EMBLEMS_PATTERN = /^(\w{3})(\w{3})(\w{3})(.*)(\w{3})$/;
|
|
31
|
+
const ACCESSORIES_MATS_PATTERN = /^(\w{3})(\w{3})(\w{3})(\w{1})(.*)$/;
|
|
32
|
+
const OTHER_PARTS_PATTERN = /(\w{3})/g;
|
|
33
|
+
|
|
34
|
+
const isLetter = (char: string): boolean =>
|
|
35
|
+
char.toLowerCase() !== char.toUpperCase();
|
|
36
|
+
|
|
37
|
+
const formatProductNumber = (number: string, separator: string): string => {
|
|
38
|
+
if (!number) return '';
|
|
39
|
+
|
|
40
|
+
let formatted = number;
|
|
41
|
+
|
|
42
|
+
if (isLetter(number[0]) && !isLetter(number[1]) && number.length === 9) {
|
|
43
|
+
formatted = formatted.replace(LIQUIDS_PATTERN, `$1${separator}$2${separator}$3${separator}$4`);
|
|
44
|
+
} else if (number.length >= 13) {
|
|
45
|
+
formatted = formatted.replace(WHEELS_EMBLEMS_PATTERN, `$1${separator}$2${separator}$3${separator}$4${separator}$5`);
|
|
46
|
+
} else if (number.length > 12) {
|
|
47
|
+
formatted = formatted.replace(ACCESSORIES_MATS_PATTERN, `$1${separator}$2${separator}$3${separator}$4${separator}$5`);
|
|
48
|
+
} else {
|
|
49
|
+
formatted = formatted.replace(OTHER_PARTS_PATTERN, `$1${separator}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return formatted
|
|
53
|
+
.replace(' ', separator)
|
|
54
|
+
.replace(/[^a-zA-Z0-9]$/, ''); // Remove the end trailing separator
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Memoization of formatted numbers
|
|
58
|
+
const formattedNumbers = productNumber ? {
|
|
59
|
+
standard: formatProductNumber(productNumber, '\u00A0'),
|
|
60
|
+
dot: formatProductNumber(productNumber, '.'),
|
|
61
|
+
dash: formatProductNumber(productNumber, '-')
|
|
62
|
+
} : null;
|
|
63
|
+
|
|
64
|
+
const classNames = [
|
|
65
|
+
'product-number',
|
|
66
|
+
big ? 'text-4.5' : 'number-big',
|
|
67
|
+
className
|
|
68
|
+
].filter(Boolean).join(' ');
|
|
69
|
+
|
|
70
|
+
const trackingClass = small ? 'tracking-wide' : 'tracking-tight';
|
|
71
|
+
|
|
72
|
+
const ProductWrapper = isPdp ? 'h2' : 'div';
|
|
73
|
+
const FormattedWrapper = isPdp ? 'h3' : 'div';
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
{productNumber && formattedNumbers && (
|
|
77
|
+
<div class={classNames}>
|
|
78
|
+
<div class={`p-number ${small ? "w-full" : ""}`} itemprop="identifier">
|
|
79
|
+
<ProductWrapper id={productNumber} class="product-code">
|
|
80
|
+
{productNumber}
|
|
81
|
+
</ProductWrapper>
|
|
82
|
+
|
|
83
|
+
{big && (
|
|
84
|
+
<ButtonCopy
|
|
85
|
+
productNumber={productNumber}
|
|
86
|
+
copyDisabled={!big}
|
|
87
|
+
texts={buttonTexts}
|
|
88
|
+
client:idle
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class={`code-formatted ${trackingClass}`}>
|
|
94
|
+
<div class="relative inset-0" data-pagefind-ignore>
|
|
95
|
+
{formattedNumbers.dot}
|
|
96
|
+
</div>
|
|
97
|
+
<div class="absolute inset-0" data-pagefind-ignore>
|
|
98
|
+
{formattedNumbers.dash}
|
|
99
|
+
</div>
|
|
100
|
+
<FormattedWrapper class="number-secondary">
|
|
101
|
+
{formattedNumbers.standard}
|
|
102
|
+
</FormattedWrapper>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
|
@@ -1,114 +1,105 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { t } from "i18next";
|
|
3
3
|
import ButtonCopy from "./ButtonCopy.vue";
|
|
4
|
-
import PartNumber from "./PartNumber.vue";
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} = Astro.props;
|
|
15
|
-
|
|
16
|
-
const butonTexts = {
|
|
17
|
-
copy: t("copy"),
|
|
18
|
-
copied: t("copied"),
|
|
19
|
-
};
|
|
5
|
+
interface Props {
|
|
6
|
+
productNumber: string | null;
|
|
7
|
+
copyDisabled?: boolean;
|
|
8
|
+
isPdp?: boolean;
|
|
9
|
+
small?: boolean;
|
|
10
|
+
big?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
20
13
|
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
const {
|
|
15
|
+
productNumber,
|
|
16
|
+
copyDisabled,
|
|
17
|
+
isPdp,
|
|
18
|
+
small,
|
|
19
|
+
big,
|
|
20
|
+
class: className
|
|
21
|
+
} = Astro.props;
|
|
24
22
|
|
|
25
|
-
const
|
|
26
|
-
|
|
23
|
+
const buttonTexts = {
|
|
24
|
+
copy: t('copy'),
|
|
25
|
+
copied: t('copied')
|
|
26
|
+
} as const;
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// mainly liquids
|
|
34
|
-
word = string
|
|
35
|
-
.replace(
|
|
36
|
-
/^(\w{1})(\w{3})(\w{3})(.*)$/,
|
|
37
|
-
`$1${separator}$2${separator}$3${separator}$4`,
|
|
38
|
-
)
|
|
39
|
-
.replace(/(^\s+|\s+$)/, "");
|
|
40
|
-
}
|
|
28
|
+
// Regex patterns:
|
|
29
|
+
const LIQUIDS_PATTERN = /^(\w{1})(\w{3})(\w{3})(.*)$/;
|
|
30
|
+
const WHEELS_EMBLEMS_PATTERN = /^(\w{3})(\w{3})(\w{3})(.*)(\w{3})$/;
|
|
31
|
+
const ACCESSORIES_MATS_PATTERN = /^(\w{3})(\w{3})(\w{3})(\w{1})(.*)$/;
|
|
32
|
+
const OTHER_PARTS_PATTERN = /(\w{3})/g;
|
|
41
33
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// console.log('part number: wheels / emblems', word)
|
|
45
|
-
word = string
|
|
46
|
-
.replace(
|
|
47
|
-
/^(\w{3})(\w{3})(\w{3})(.*)(\w{3})$/,
|
|
48
|
-
`$1${separator}$2${separator}$3${separator}$4${separator}$5`,
|
|
49
|
-
)
|
|
50
|
-
.replace(/(^\s+|\s+$)/, "");
|
|
51
|
-
} else if (string.length > 12) {
|
|
52
|
-
// accessories / mats
|
|
53
|
-
word = string
|
|
54
|
-
.replace(
|
|
55
|
-
/^(\w{3})(\w{3})(\w{3})(\w{1})(.*)$/,
|
|
56
|
-
`$1${separator}$2${separator}$3${separator}$4${separator}$5`,
|
|
57
|
-
)
|
|
58
|
-
.replace(/(^\s+|\s+$)/, "");
|
|
59
|
-
} else {
|
|
60
|
-
// other parts
|
|
61
|
-
word = string
|
|
62
|
-
.replace(/(\w{3})/g, `$1${separator}`)
|
|
63
|
-
.replace(/(^\s+|\s+$)/, "");
|
|
64
|
-
}
|
|
34
|
+
const isLetter = (char: string): boolean =>
|
|
35
|
+
char.toLowerCase() !== char.toUpperCase();
|
|
65
36
|
|
|
66
|
-
|
|
67
|
-
|
|
37
|
+
const formatProductNumber = (number: string, separator: string): string => {
|
|
38
|
+
if (!number) return '';
|
|
68
39
|
|
|
69
|
-
|
|
70
|
-
|
|
40
|
+
let formatted = number;
|
|
41
|
+
|
|
42
|
+
if (isLetter(number[0]) && !isLetter(number[1]) && number.length === 9) {
|
|
43
|
+
formatted = formatted.replace(LIQUIDS_PATTERN, `$1${separator}$2${separator}$3${separator}$4`);
|
|
44
|
+
} else if (number.length >= 13) {
|
|
45
|
+
formatted = formatted.replace(WHEELS_EMBLEMS_PATTERN, `$1${separator}$2${separator}$3${separator}$4${separator}$5`);
|
|
46
|
+
} else if (number.length > 12) {
|
|
47
|
+
formatted = formatted.replace(ACCESSORIES_MATS_PATTERN, `$1${separator}$2${separator}$3${separator}$4${separator}$5`);
|
|
48
|
+
} else {
|
|
49
|
+
formatted = formatted.replace(OTHER_PARTS_PATTERN, `$1${separator}`);
|
|
71
50
|
}
|
|
72
51
|
|
|
73
|
-
return
|
|
52
|
+
return formatted
|
|
53
|
+
.replace(' ', separator)
|
|
54
|
+
.replace(/[^a-zA-Z0-9]$/, ''); // Remove the end trailing separator
|
|
74
55
|
};
|
|
75
56
|
|
|
76
|
-
|
|
57
|
+
// Memoization of formatted numbers
|
|
58
|
+
const formattedNumbers = productNumber ? {
|
|
59
|
+
standard: formatProductNumber(productNumber, '\u00A0'),
|
|
60
|
+
dot: formatProductNumber(productNumber, '.'),
|
|
61
|
+
dash: formatProductNumber(productNumber, '-')
|
|
62
|
+
} : null;
|
|
77
63
|
|
|
64
|
+
const classNames = [
|
|
65
|
+
'product-number',
|
|
66
|
+
big ? 'text-4.5' : 'number-big',
|
|
67
|
+
className
|
|
68
|
+
].filter(Boolean).join(' ');
|
|
78
69
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<!-- <PartNumber number={productNumber} as="div"></PartNumber> -->
|
|
82
|
-
|
|
70
|
+
const trackingClass = small ? 'tracking-wide' : 'tracking-tight';
|
|
83
71
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<PartNumber class={`${classes} product-code`} number={productNumber} as={isPdp ? 'h2' : 'div'} />
|
|
72
|
+
const ProductWrapper = isPdp ? 'h2' : 'div';
|
|
73
|
+
const FormattedWrapper = isPdp ? 'h3' : 'div';
|
|
74
|
+
---
|
|
89
75
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
76
|
+
{productNumber && formattedNumbers && (
|
|
77
|
+
<div class={classNames}>
|
|
78
|
+
<div class={`p-number ${small ? "w-full" : ""}`} itemprop="identifier">
|
|
79
|
+
<ProductWrapper id={productNumber} class="product-code">
|
|
80
|
+
{productNumber}
|
|
81
|
+
</ProductWrapper>
|
|
82
|
+
|
|
83
|
+
{big && (
|
|
84
|
+
<ButtonCopy
|
|
85
|
+
productNumber={productNumber}
|
|
86
|
+
copyDisabled={!big}
|
|
87
|
+
texts={buttonTexts}
|
|
88
|
+
client:idle
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class={`code-formatted ${trackingClass}`}>
|
|
94
|
+
<div class="relative inset-0" data-pagefind-ignore>
|
|
95
|
+
{formattedNumbers.dot}
|
|
100
96
|
</div>
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
texts={butonTexts}
|
|
111
|
-
tooltipClasses=""
|
|
112
|
-
client:only
|
|
113
|
-
/>
|
|
114
|
-
<!-- )} -->
|
|
97
|
+
<div class="absolute inset-0" data-pagefind-ignore>
|
|
98
|
+
{formattedNumbers.dash}
|
|
99
|
+
</div>
|
|
100
|
+
<FormattedWrapper class="number-secondary">
|
|
101
|
+
{formattedNumbers.standard}
|
|
102
|
+
</FormattedWrapper>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
)}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
|
|
3
|
+
// Vue Components
|
|
4
|
+
export { default as FuckRussia } from './components/FuckRussia.vue';
|
|
5
|
+
export { default as FlagPL } from './components/flags/FlagPL.vue';
|
|
6
|
+
export { default as Badges } from './components/Badges.vue';
|
|
7
|
+
export { default as SlimBanner } from './components/SlimBanner.vue';
|
|
8
|
+
export { default as Jumbatron } from './components/Jumbatron.vue';
|
|
9
|
+
export { default as Button } from './components/Button.vue';
|
|
10
|
+
export { default as Breadcrumbs } from './components/Breadcrumbs.vue';
|
|
11
|
+
export { default as ProductDetailsList } from './components/ProductDetailsList.vue';
|
|
12
|
+
export { default as FeaturesList } from './components/FeaturesList.vue';
|
|
13
|
+
export { default as ProductCodes } from './components/ProductCodes.vue';
|
|
14
|
+
export { default as ProductEngineType } from './components/Product/ProductEngineType.vue';
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
// Astro Components
|
|
19
|
+
export { default as Copyright } from './components/Copyright.astro';
|
|
20
|
+
export { default as HandDrive } from './components/HandDrive.astro';
|
|
21
|
+
export { default as Faq } from './components/Faq.astro';
|
|
22
|
+
export { default as ProductNumber } from './components/Product/ProductNumber.astro';
|
|
23
|
+
export { default as ProductImage } from './components/Product/ProductImage.astro';
|
|
@@ -4,7 +4,7 @@ layout: "../../layouts/MainLayout.astro"
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
import ProductDetailsList from '../../components/ProductDetailsList.vue'
|
|
7
|
-
import ProductNumber from "../../components/ProductNumber.astro"
|
|
7
|
+
import ProductNumber from "../../components/Product/ProductNumber.astro"
|
|
8
8
|
import ProductCodes from '../../components/ProductCodes.vue'
|
|
9
9
|
|
|
10
10
|
export const prcodesArray = ["PJ4", "CA2", "C4E", "2JZ"]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface ProductImage {
|
|
2
|
+
path: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface BaseProduct {
|
|
6
|
+
id: string | number;
|
|
7
|
+
number: string;
|
|
8
|
+
photo: string | null;
|
|
9
|
+
price_pln?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ShopProduct extends BaseProduct {
|
|
13
|
+
images: ProductImage[];
|
|
14
|
+
slug: string;
|
|
15
|
+
name_pl: string;
|
|
16
|
+
name_en: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CatalogProduct extends BaseProduct {
|
|
20
|
+
photo: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type Product = ShopProduct | CatalogProduct;
|
|
24
|
+
|
|
25
|
+
export interface ProductLinkProps {
|
|
26
|
+
productId: string;
|
|
27
|
+
bigTile?: boolean;
|
|
28
|
+
locale: string;
|
|
29
|
+
index?: number | null;
|
|
30
|
+
loading?: 'lazy' | 'eager';
|
|
31
|
+
isShopProduct?: boolean;
|
|
32
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
import i18next, { t } from "i18next";
|
|
3
|
+
|
|
4
|
+
// import { t } from "i18next";
|
|
5
|
+
|
|
6
|
+
export const getTranslation = (translationKey:string) => {
|
|
7
|
+
// console.log('search translation for', translationKey, t(translationKey))
|
|
8
|
+
return i18next.exists(translationKey) ? t(translationKey) : `missing ${translationKey}`
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const text2paragraphs = (text:string, firstLineBottomMargin:boolean=false) => {
|
|
12
|
+
// return '<p class="mb-2">' + text.split(/[\n\r]+/g).join('</p><p>') + '</p>'
|
|
13
|
+
let out = '<p' + (firstLineBottomMargin ? 'class="mb-3"' : '') + '>' + text.split("\n").join('</p><p>') + '<\/p>';
|
|
14
|
+
return out.split('<p><\/p><p>').join('<p class="mt-3">');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export const getShorterDescription = (description: string) => {
|
|
19
|
+
function cutString(s:string, n:number) {
|
|
20
|
+
if (s && s.length > 150) {
|
|
21
|
+
const text = s.replace(/(\n)/g," ")
|
|
22
|
+
const cut = text.indexOf('. ', n);
|
|
23
|
+
if (cut == -1) return text;
|
|
24
|
+
return `${text.substring(0, cut)}.`
|
|
25
|
+
} else {
|
|
26
|
+
return s || ''
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return cutString(description, 150) || ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const countWords = (str: string) => {
|
|
34
|
+
return str.trim().split(/\s+/).length;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const firstSentence = (str: string) => {
|
|
38
|
+
return str.split('\.');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const removeSemicolon = (name: string) => {
|
|
42
|
+
return name.replace(';', '')
|
|
43
|
+
}
|