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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "_variables": {
3
- "lastUpdateCheck": 1727944915761
3
+ "lastUpdateCheck": 1730933717958
4
4
  }
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoko-design-system",
3
- "version": "0.2.35",
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.9.1",
31
- "@astrojs/mdx": "^3.1.8",
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.6.2",
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.3",
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.4",
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.9",
52
+ "@iconify-json/simple-icons": "^1.2.11",
53
53
  "@iconify-json/uil": "^1.2.1",
54
- "@iconify/json": "^2.2.262",
54
+ "@iconify/json": "^2.2.268",
55
55
  "@iconify/vue": "^4.1.2",
56
- "@playform/compress": "^0.1.4",
56
+ "@playform/compress": "^0.1.6",
57
57
  "@playform/inline": "github:playform/inline",
58
- "@types/node": "^22.7.7",
59
- "@unocss/astro": "^0.63.4",
60
- "@unocss/preset-attributify": "^0.63.4",
61
- "@unocss/preset-typography": "^0.63.4",
62
- "@unocss/preset-uno": "^0.63.4",
63
- "@unocss/preset-web-fonts": "^0.63.4",
64
- "@unocss/preset-wind": "^0.63.4",
65
- "@unocss/reset": "^0.63.4",
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.1.0",
67
+ "@vueuse/core": "^11.2.0",
68
68
  "astro-i18next": "1.0.0-beta.21",
69
- "astro-icon": "^1.1.1",
69
+ "astro-icon": "^1.1.2",
70
70
  "astro-meta-tags": "^0.3.1",
71
- "astro-navbar": "^2.3.4",
71
+ "astro-navbar": "^2.3.6",
72
72
  "astro-pagefind": "^1.6.0",
73
73
  "astro-remote": "^0.3.3",
74
- "i18next": "^23.16.2",
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.63.4",
81
- "vite": "^5.4.9",
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.63.4",
86
- "astro": "^4.16.6",
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
+ }
@@ -39,7 +39,7 @@ const classes = {
39
39
  </component>
40
40
  </template>
41
41
 
42
- <style lang="scss">
42
+ <style>
43
43
  .btn-primary,
44
44
  .btn-secondary {
45
45
  svg {
@@ -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
- const {
7
- copyDisabled,
8
- productNumber,
9
- as,
10
- isPdp,
11
- small,
12
- big,
13
- class: className,
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 isLetter = (string: string) => {
22
- return string.toLowerCase() !== string.toUpperCase();
23
- };
14
+ const {
15
+ productNumber,
16
+ copyDisabled,
17
+ isPdp,
18
+ small,
19
+ big,
20
+ class: className
21
+ } = Astro.props;
24
22
 
25
- const niceName = (string: string, separator = "\u00A0") => {
26
- let word = string; //e.g. to 6Q0947106EY20 .split('+')
23
+ const buttonTexts = {
24
+ copy: t('copy'),
25
+ copied: t('copied')
26
+ } as const;
27
27
 
28
- if (
29
- isLetter(string[0]) === true &&
30
- isLetter(string[1]) === false &&
31
- string.length === 9
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
- // wheels / emblems e.g. 6R0601025AK8Z8 --> 6R0 601 025 AB 8Z8 || 6C0601025DFZZ --> 6C0 601 025 D FZZ || 6R0853433ADA1 -> 6R0 853 433 A DA1
43
- else if (string.length >= 13) {
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
- // remove single separator from the end of string:
67
- let lastCharacter = word.split('').pop() || '';
37
+ const formatProductNumber = (number: string, separator: string): string => {
38
+ if (!number) return '';
68
39
 
69
- if (!Boolean(lastCharacter.match(/[a-zA-Z0-9]/))) {
70
- word = word.slice(0, -1)
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 word.replace(" ", separator);
52
+ return formatted
53
+ .replace(' ', separator)
54
+ .replace(/[^a-zA-Z0-9]$/, ''); // Remove the end trailing separator
74
55
  };
75
56
 
76
- const classes = `product-number ${big ? "text-5.5 leading-none" : "text-sm"} ${className}` //mt-2 sm:mt-0
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
- productNumber !== null && (
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
- <div
91
- class={`code-formatted ${small ? "tracking-wide" : "tracking-tight"}`}
92
- >
93
- <div class="relative inset-0" data-pagefind-ignore>
94
- {niceName(productNumber, ".")}
95
- </div>
96
- <div class="absolute inset-0" data-pagefind-ignore>
97
- {niceName(productNumber, "-")}
98
- </div>
99
- <PartNumber class="number-secondary" number={niceName(productNumber)} as={isPdp ? 'h3' : 'div'} />
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
- <!-- {!copyDisabled && ( -->
107
- <ButtonCopy
108
- class="top-1 right-0"
109
- productNumber={String(productNumber)}
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
+ )}
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  const { productObject, locale, index } = Astro.props;
3
3
  import Image from "./Image.astro"
4
- import ProductNumber from "./ProductNumber.astro"
4
+ import ProductNumber from "./Product/ProductNumber.astro"
5
5
 
6
6
  ---
7
7
 
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"]
@@ -2,7 +2,7 @@
2
2
  title: "ProductNumber"
3
3
  layout: "../../layouts/MainLayout.astro"
4
4
  ---
5
- import ProductNumber from '../../components/ProductNumber.astro'
5
+ import ProductNumber from '../../components/Product/ProductNumber.astro'
6
6
 
7
7
  # ProductNumber
8
8
 
@@ -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,5 @@
1
+
2
+ declare module '*.astro' {
3
+ const component: any;
4
+ export default component;
5
+ }
@@ -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
+ }