spoko-design-system 0.2.41 → 0.2.43

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 (158) hide show
  1. package/.env.example +2 -0
  2. package/.github/dependabot.yml +11 -11
  3. package/.github/todo.yml +3 -3
  4. package/.github/workflows/deploy.yml +39 -39
  5. package/.stackblitzrc +5 -5
  6. package/.vscode/extensions.json +5 -5
  7. package/.vscode/launch.json +11 -11
  8. package/.vscode/settings.json +5 -5
  9. package/LICENSE +21 -21
  10. package/README.md +113 -113
  11. package/astro-i18next.config.mjs +17 -17
  12. package/astro-i18next.config.ts +10 -10
  13. package/astro.config.mjs +147 -147
  14. package/dev-dist/sw.js +91 -91
  15. package/dev-dist/workbox-c676b6d3.js +3391 -3391
  16. package/index.ts +51 -58
  17. package/package.json +3 -2
  18. package/public/fonts/lg.svg +53 -53
  19. package/public/fonts/vwhead-bold-demo.html +549 -549
  20. package/public/fonts/vwhead-regular-demo.html +549 -549
  21. package/public/fonts/vwtext-bold-demo.html +549 -549
  22. package/public/fonts/vwtext-regular-demo.html +549 -549
  23. package/public/github.svg +3 -3
  24. package/public/grid_dot.svg +4 -4
  25. package/public/linkedin.svg +44 -44
  26. package/public/locales/en/translation.json +8 -8
  27. package/public/locales/pl/translation.json +8 -8
  28. package/public/make-scrollable-code-focusable.js +3 -3
  29. package/public/pagefind.yml +3 -3
  30. package/public/polo.blue.svg +29 -29
  31. package/public/spoko.space.svg +71 -71
  32. package/public/twitter.svg +46 -46
  33. package/renovate.json +6 -6
  34. package/sandbox.config.json +11 -11
  35. package/src/MyComponent.astro +8 -8
  36. package/src/components/Badge.vue +19 -19
  37. package/src/components/Badges.vue +21 -21
  38. package/src/components/Breadcrumbs.vue +110 -110
  39. package/src/components/Button.vue +55 -55
  40. package/src/components/ButtonCopy.vue +47 -47
  41. package/src/components/Card.astro +27 -27
  42. package/src/components/Carousel.astro +26 -26
  43. package/src/components/Category/CategoriesCarousel.astro +101 -101
  44. package/src/components/Category/CategoriesSidebar.astro +186 -186
  45. package/src/components/Category/CategoryDetails.astro +82 -82
  46. package/src/components/Category/CategoryLink.vue +23 -23
  47. package/src/components/Category/CategorySection.astro +69 -69
  48. package/src/components/Category/CategorySidebarToggler.vue +9 -9
  49. package/src/components/Category/SubCategoryLink.vue +29 -29
  50. package/src/components/CategoryLink.astro +18 -18
  51. package/src/components/Copyright.astro +12 -12
  52. package/src/components/Date.astro +7 -7
  53. package/src/components/Faq.astro +33 -33
  54. package/src/components/FaqItem.astro +96 -96
  55. package/src/components/FeaturesList.vue +41 -41
  56. package/src/components/FuckRussia.vue +80 -80
  57. package/src/components/HandDrive.astro +29 -29
  58. package/src/components/Header/Header.astro +214 -214
  59. package/src/components/Header/SkipToContent.astro +1 -1
  60. package/src/components/Headline.vue +48 -48
  61. package/src/components/Image.astro +30 -30
  62. package/src/components/Jumbatron.vue +40 -40
  63. package/src/components/LeftSidebar.astro +53 -53
  64. package/src/components/MainColors.vue +23 -23
  65. package/src/components/MainInput.vue +15 -15
  66. package/src/components/Modal.astro +27 -27
  67. package/src/components/PageContent.astro +5 -5
  68. package/src/components/PartNumber.vue +27 -27
  69. package/src/components/PostHeader.astro +103 -103
  70. package/src/components/PrCode.vue +156 -156
  71. package/src/components/Product/ProductButton.vue +18 -18
  72. package/src/components/Product/ProductCodes.vue +167 -167
  73. package/src/components/Product/ProductEngineType.vue +42 -42
  74. package/src/components/Product/ProductImage.astro +41 -41
  75. package/src/components/Product/ProductLinkInfo.astro +37 -37
  76. package/src/components/Product/ProductNumber.astro +104 -104
  77. package/src/components/ProductCarousel.astro +38 -38
  78. package/src/components/ProductCodes.vue +39 -39
  79. package/src/components/ProductDetailName.vue +52 -52
  80. package/src/components/ProductDetailsList.vue +65 -65
  81. package/src/components/ProductNumber copy.astro +116 -116
  82. package/src/components/ProductNumber.astro +104 -104
  83. package/src/components/ProductTile.astro +48 -48
  84. package/src/components/Quote.vue +23 -23
  85. package/src/components/ReloadPrompt.astro +50 -50
  86. package/src/components/SlimBanner.vue +72 -72
  87. package/src/components/Table.vue +32 -32
  88. package/src/components/TableOfContents.astro +15 -15
  89. package/src/components/Translations.vue +23 -23
  90. package/src/components/flags/FlagPL.vue +3 -3
  91. package/src/components/flags/FlagUA.vue +2 -2
  92. package/src/components/layout/Container.astro +7 -7
  93. package/src/components/layout/Header.astro +80 -80
  94. package/src/config.ts +56 -56
  95. package/src/design.config.ts +81 -81
  96. package/src/env.d.ts +1 -1
  97. package/src/layouts/Layout.astro +60 -60
  98. package/src/layouts/MainLayout.astro +81 -81
  99. package/src/layouts/partials/FooterCommon.astro +4 -4
  100. package/src/layouts/partials/HeadCommon.astro +44 -44
  101. package/src/layouts/partials/HeadSEO.astro +41 -41
  102. package/src/pages/components/badges.mdx +57 -57
  103. package/src/pages/components/breadcrumbs.mdx +139 -139
  104. package/src/pages/components/buttons.mdx +236 -236
  105. package/src/pages/components/card.mdx +294 -294
  106. package/src/pages/components/carousel.mdx +62 -62
  107. package/src/pages/components/copyright.mdx +42 -42
  108. package/src/pages/components/details-list.mdx +115 -115
  109. package/src/pages/components/features-list.mdx +37 -37
  110. package/src/pages/components/flags.mdx +49 -49
  111. package/src/pages/components/fuck-russia.mdx +39 -39
  112. package/src/pages/components/hand-drive.mdx +38 -38
  113. package/src/pages/components/headline.mdx +152 -152
  114. package/src/pages/components/icons.astro +48 -48
  115. package/src/pages/components/image.mdx +513 -513
  116. package/src/pages/components/input.mdx +45 -45
  117. package/src/pages/components/jumbatron.mdx +95 -95
  118. package/src/pages/components/modal.mdx +64 -64
  119. package/src/pages/components/post-header.mdx +60 -60
  120. package/src/pages/components/pr-code.mdx +65 -65
  121. package/src/pages/components/product-number.mdx +66 -66
  122. package/src/pages/components/product-tile.mdx +51 -51
  123. package/src/pages/components/quote.mdx +33 -33
  124. package/src/pages/components/slimbanner.mdx +35 -35
  125. package/src/pages/components/table.mdx +108 -108
  126. package/src/pages/core/colors.mdx +10 -10
  127. package/src/pages/core/grid.mdx +89 -89
  128. package/src/pages/core/introduction.mdx +77 -77
  129. package/src/pages/core/shadows.astro +20 -20
  130. package/src/pages/core/typography.astro +47 -47
  131. package/src/pages/index.astro +126 -126
  132. package/src/pages/patterns/introduction.mdx +60 -60
  133. package/src/pwa.ts +12 -12
  134. package/src/styles/_variables.scss +70 -70
  135. package/src/styles/base/base.css +184 -184
  136. package/src/styles/base/grid.css +92 -92
  137. package/src/styles/base/typography.css +70 -70
  138. package/src/styles/content.css +73 -73
  139. package/src/styles/main.css +7 -7
  140. package/src/types/Product.ts +31 -31
  141. package/src/types/astro.d.ts +4 -5
  142. package/src/types/index.ts +237 -0
  143. package/src/utils/api/getCategories.ts +3 -0
  144. package/src/utils/category/getMainCategoryList.ts +31 -0
  145. package/src/utils/category/getSortedCategories.ts +11 -0
  146. package/src/utils/getData.ts +52 -0
  147. package/src/utils/product/getPriceFormatted.ts +16 -14
  148. package/src/utils/product/getProductChecklist.ts +17 -15
  149. package/src/utils/seo/getShorterDescription.ts +14 -12
  150. package/src/utils/text/formatDate.ts +5 -5
  151. package/src/utils/text/formatLocaleNumber.ts +6 -6
  152. package/src/utils/text/formatPad.ts +12 -11
  153. package/src/utils/text/getNumberFormatted.ts +33 -32
  154. package/src/utils/text/getTranslatedLink.ts +7 -5
  155. package/src/utils/text.ts +42 -42
  156. package/tailwind.config.cjs +8 -8
  157. package/tsconfig.json +28 -11
  158. package/uno.config.ts +256 -256
@@ -1,53 +1,53 @@
1
- <script lang="ts" setup>
2
- import { PropType } from 'vue';
3
-
4
- const props = defineProps({
5
- as: {
6
- type: String as PropType< 'th'| 'td' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'div' | 'span'>,
7
- default: 'div',
8
- required: true,
9
- },
10
- text: {
11
- type: String,
12
- default: '',
13
- required: true,
14
- },
15
- styles: {
16
- type: String,
17
- default: '',
18
- required: false,
19
- }
20
- })
21
- </script>
22
-
23
- <template>
24
- <component :is="props.as" class="font-bold detail-name w-full sm:w-50 flex 2xl:w-64">
25
- <span :class="styles && styles.length ? styles : 'mt-auto'">
26
- <b class="bg-white z-1 colon-after pr-1">{{ props.text }}</b>
27
- </span>
28
- </component>
29
- </template>
30
-
31
-
32
- <style lang="scss">
33
- .detail-name {
34
- @apply overflow-hidden relative;
35
-
36
- span {
37
- @apply block bg-white relative z-10 pr-1.5 w-full;
38
-
39
- &:before {
40
- // order: 2;
41
- @apply text-gray-300 absolute select-none border-b border-gray-200 w-full -z-1 absolute content-empty left-0;
42
- height: 1em;
43
- white-space: nowrap;
44
- font-weight: 100;
45
- bottom: 2px;
46
- flex: 1;
47
- }
48
- }
49
-
50
-
51
- }
52
-
1
+ <script lang="ts" setup>
2
+ import { PropType } from 'vue';
3
+
4
+ const props = defineProps({
5
+ as: {
6
+ type: String as PropType< 'th'| 'td' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'div' | 'span'>,
7
+ default: 'div',
8
+ required: true,
9
+ },
10
+ text: {
11
+ type: String,
12
+ default: '',
13
+ required: true,
14
+ },
15
+ styles: {
16
+ type: String,
17
+ default: '',
18
+ required: false,
19
+ }
20
+ })
21
+ </script>
22
+
23
+ <template>
24
+ <component :is="props.as" class="font-bold detail-name w-full sm:w-50 flex 2xl:w-64">
25
+ <span :class="styles && styles.length ? styles : 'mt-auto'">
26
+ <b class="bg-white z-1 colon-after pr-1">{{ props.text }}</b>
27
+ </span>
28
+ </component>
29
+ </template>
30
+
31
+
32
+ <style lang="scss">
33
+ .detail-name {
34
+ @apply overflow-hidden relative;
35
+
36
+ span {
37
+ @apply block bg-white relative z-10 pr-1.5 w-full;
38
+
39
+ &:before {
40
+ // order: 2;
41
+ @apply text-gray-300 absolute select-none border-b border-gray-200 w-full -z-1 absolute content-empty left-0;
42
+ height: 1em;
43
+ white-space: nowrap;
44
+ font-weight: 100;
45
+ bottom: 2px;
46
+ flex: 1;
47
+ }
48
+ }
49
+
50
+
51
+ }
52
+
53
53
  </style>
@@ -1,65 +1,65 @@
1
- <script setup lang="ts">
2
- import { PropType } from "vue";
3
- import ProductDetailName from "./ProductDetailName.vue";
4
-
5
- interface TableItem {
6
- id: string,
7
- name: string
8
- value: unknown
9
- }
10
-
11
- const props = defineProps({
12
- items: {type: Array as PropType<TableItem[]>, default: () => []},
13
- caption: { type: String, default: null }
14
- })
15
-
16
- </script>
17
-
18
- <template>
19
- <table class="details table-auto text-left bg-white">
20
- <caption v-if="!!$slots.caption || caption">
21
- <slot name="caption">{{ caption }}</slot>
22
- </caption>
23
- <colgroup>
24
- <col>
25
- <col>
26
- </colgroup>
27
- <tbody>
28
- <tr v-for="row, index in props.items" :key="index">
29
- <ProductDetailName as="th" :text="row.name" />
30
- <slot :name="row.id">
31
- <td>{{ row.value }}</td>
32
- </slot>
33
- </tr>
34
- </tbody>
35
- </table>
36
- </template>
37
-
38
- <style lang="scss" scoped>
39
- .details {
40
- @apply border-none shadow-none w-full md:w-auto
41
- box-shadow: none;
42
-
43
- col {
44
- @apply w-1/2 md:w-auto;
45
-
46
- }
47
-
48
- tr {
49
- @apply border-none;
50
- }
51
-
52
- tr,
53
- th {
54
- @apply leading-none text-3.5 py-2 border-none xl:(py-4) 3xl:(text-4);
55
- }
56
-
57
- th {
58
- @apply px-0;
59
- }
60
-
61
- td {
62
- @apply relative;
63
- }
64
- }
65
- </style>
1
+ <script setup lang="ts">
2
+ import { PropType } from "vue";
3
+ import ProductDetailName from "./ProductDetailName.vue";
4
+
5
+ interface TableItem {
6
+ id: string,
7
+ name: string
8
+ value: unknown
9
+ }
10
+
11
+ const props = defineProps({
12
+ items: {type: Array as PropType<TableItem[]>, default: () => []},
13
+ caption: { type: String, default: null }
14
+ })
15
+
16
+ </script>
17
+
18
+ <template>
19
+ <table class="details table-auto text-left bg-white">
20
+ <caption v-if="!!$slots.caption || caption">
21
+ <slot name="caption">{{ caption }}</slot>
22
+ </caption>
23
+ <colgroup>
24
+ <col>
25
+ <col>
26
+ </colgroup>
27
+ <tbody>
28
+ <tr v-for="row, index in props.items" :key="index">
29
+ <ProductDetailName as="th" :text="row.name" />
30
+ <slot :name="row.id">
31
+ <td>{{ row.value }}</td>
32
+ </slot>
33
+ </tr>
34
+ </tbody>
35
+ </table>
36
+ </template>
37
+
38
+ <style lang="scss" scoped>
39
+ .details {
40
+ @apply border-none shadow-none w-full md:w-auto
41
+ box-shadow: none;
42
+
43
+ col {
44
+ @apply w-1/2 md:w-auto;
45
+
46
+ }
47
+
48
+ tr {
49
+ @apply border-none;
50
+ }
51
+
52
+ tr,
53
+ th {
54
+ @apply leading-none text-3.5 py-2 border-none xl:(py-4) 3xl:(text-4);
55
+ }
56
+
57
+ th {
58
+ @apply px-0;
59
+ }
60
+
61
+ td {
62
+ @apply relative;
63
+ }
64
+ }
65
+ </style>
@@ -1,116 +1,116 @@
1
- ---
2
- import { t } from "i18next";
3
- import ButtonCopy from "./ButtonCopy.vue";
4
-
5
- const {
6
- copyDisabled,
7
- productNumber,
8
- isPdp,
9
- small,
10
- big,
11
- class: className,
12
- } = Astro.props;
13
-
14
- const butonTexts = {
15
- copy: t("copy"),
16
- copied: t("copied"),
17
- };
18
-
19
- const isLetter = (string: string) => {
20
- return string.toLowerCase() !== string.toUpperCase();
21
- };
22
-
23
- const niceName = (string: string, separator = "\u00A0") => {
24
- let word = string; //e.g. to 6Q0947106EY20 .split('+')
25
-
26
- if (
27
- isLetter(string[0]) === true &&
28
- isLetter(string[1]) === false &&
29
- string.length === 9
30
- ) {
31
- // mainly liquids
32
- word = string
33
- .replace(
34
- /^(\w{1})(\w{3})(\w{3})(.*)$/,
35
- `$1${separator}$2${separator}$3${separator}$4`,
36
- )
37
- .replace(/(^\s+|\s+$)/, "");
38
- }
39
-
40
- // wheels / emblems e.g. 6R0601025AK8Z8 --> 6R0 601 025 AB 8Z8 || 6C0601025DFZZ --> 6C0 601 025 D FZZ || 6R0853433ADA1 -> 6R0 853 433 A DA1
41
- else if (string.length >= 13) {
42
- // console.log('part number: wheels / emblems', word)
43
- word = string
44
- .replace(
45
- /^(\w{3})(\w{3})(\w{3})(.*)(\w{3})$/,
46
- `$1${separator}$2${separator}$3${separator}$4${separator}$5`,
47
- )
48
- .replace(/(^\s+|\s+$)/, "");
49
- } else if (string.length > 12) {
50
- // accessories / mats
51
- word = string
52
- .replace(
53
- /^(\w{3})(\w{3})(\w{3})(\w{1})(.*)$/,
54
- `$1${separator}$2${separator}$3${separator}$4${separator}$5`,
55
- )
56
- .replace(/(^\s+|\s+$)/, "");
57
- } else {
58
- // other parts
59
- word = string
60
- .replace(/(\w{3})/g, `$1${separator}`)
61
- .replace(/(^\s+|\s+$)/, "");
62
- }
63
-
64
- return word.replace(" ", separator);
65
- };
66
- ---
67
-
68
- {
69
- productNumber !== null && (
70
- <div
71
- class={`product-number ${
72
- big ? "text-lg" : `text-sm ${className ? className : "mt-2 sm:mt-0"}`
73
- }`}
74
- >
75
- <div
76
- class={`inline-flex leading-none relative ${small ? "w-full" : ""}`}
77
- itemprop="identifier"
78
- >
79
- {isPdp ? (
80
- <h2 id={productNumber} class="product-code">
81
- {productNumber}
82
- </h2>
83
- ) : (
84
- <div id={productNumber} class="product-code">
85
- {productNumber}
86
- </div>
87
- )}
88
-
89
- {copyDisabled && (
90
- <ButtonCopy
91
- productNumber={String(productNumber)}
92
- texts={butonTexts}
93
- tooltipClasses=""
94
- client:only
95
- />
96
- )}
97
- </div>
98
-
99
- <div
100
- class={`code-formatted ${small ? "tracking-wide" : "tracking-tight"}`}
101
- >
102
- <div class="relative inset-0" data-pagefind-ignore>
103
- {niceName(productNumber, ".")}
104
- </div>
105
- <div class="absolute inset-0" data-pagefind-ignore>
106
- {niceName(productNumber, "-")}
107
- </div>
108
- {isPdp ? (
109
- <h3 class="number-secondary">{niceName(productNumber)}</h3>
110
- ) : (
111
- <div class="number-secondary">{niceName(productNumber)}</div>
112
- )}
113
- </div>
114
- </div>
115
- )
116
- }
1
+ ---
2
+ import { t } from "i18next";
3
+ import ButtonCopy from "./ButtonCopy.vue";
4
+
5
+ const {
6
+ copyDisabled,
7
+ productNumber,
8
+ isPdp,
9
+ small,
10
+ big,
11
+ class: className,
12
+ } = Astro.props;
13
+
14
+ const butonTexts = {
15
+ copy: t("copy"),
16
+ copied: t("copied"),
17
+ };
18
+
19
+ const isLetter = (string: string) => {
20
+ return string.toLowerCase() !== string.toUpperCase();
21
+ };
22
+
23
+ const niceName = (string: string, separator = "\u00A0") => {
24
+ let word = string; //e.g. to 6Q0947106EY20 .split('+')
25
+
26
+ if (
27
+ isLetter(string[0]) === true &&
28
+ isLetter(string[1]) === false &&
29
+ string.length === 9
30
+ ) {
31
+ // mainly liquids
32
+ word = string
33
+ .replace(
34
+ /^(\w{1})(\w{3})(\w{3})(.*)$/,
35
+ `$1${separator}$2${separator}$3${separator}$4`,
36
+ )
37
+ .replace(/(^\s+|\s+$)/, "");
38
+ }
39
+
40
+ // wheels / emblems e.g. 6R0601025AK8Z8 --> 6R0 601 025 AB 8Z8 || 6C0601025DFZZ --> 6C0 601 025 D FZZ || 6R0853433ADA1 -> 6R0 853 433 A DA1
41
+ else if (string.length >= 13) {
42
+ // console.log('part number: wheels / emblems', word)
43
+ word = string
44
+ .replace(
45
+ /^(\w{3})(\w{3})(\w{3})(.*)(\w{3})$/,
46
+ `$1${separator}$2${separator}$3${separator}$4${separator}$5`,
47
+ )
48
+ .replace(/(^\s+|\s+$)/, "");
49
+ } else if (string.length > 12) {
50
+ // accessories / mats
51
+ word = string
52
+ .replace(
53
+ /^(\w{3})(\w{3})(\w{3})(\w{1})(.*)$/,
54
+ `$1${separator}$2${separator}$3${separator}$4${separator}$5`,
55
+ )
56
+ .replace(/(^\s+|\s+$)/, "");
57
+ } else {
58
+ // other parts
59
+ word = string
60
+ .replace(/(\w{3})/g, `$1${separator}`)
61
+ .replace(/(^\s+|\s+$)/, "");
62
+ }
63
+
64
+ return word.replace(" ", separator);
65
+ };
66
+ ---
67
+
68
+ {
69
+ productNumber !== null && (
70
+ <div
71
+ class={`product-number ${
72
+ big ? "text-lg" : `text-sm ${className ? className : "mt-2 sm:mt-0"}`
73
+ }`}
74
+ >
75
+ <div
76
+ class={`inline-flex leading-none relative ${small ? "w-full" : ""}`}
77
+ itemprop="identifier"
78
+ >
79
+ {isPdp ? (
80
+ <h2 id={productNumber} class="product-code">
81
+ {productNumber}
82
+ </h2>
83
+ ) : (
84
+ <div id={productNumber} class="product-code">
85
+ {productNumber}
86
+ </div>
87
+ )}
88
+
89
+ {copyDisabled && (
90
+ <ButtonCopy
91
+ productNumber={String(productNumber)}
92
+ texts={butonTexts}
93
+ tooltipClasses=""
94
+ client:only
95
+ />
96
+ )}
97
+ </div>
98
+
99
+ <div
100
+ class={`code-formatted ${small ? "tracking-wide" : "tracking-tight"}`}
101
+ >
102
+ <div class="relative inset-0" data-pagefind-ignore>
103
+ {niceName(productNumber, ".")}
104
+ </div>
105
+ <div class="absolute inset-0" data-pagefind-ignore>
106
+ {niceName(productNumber, "-")}
107
+ </div>
108
+ {isPdp ? (
109
+ <h3 class="number-secondary">{niceName(productNumber)}</h3>
110
+ ) : (
111
+ <div class="number-secondary">{niceName(productNumber)}</div>
112
+ )}
113
+ </div>
114
+ </div>
115
+ )
116
+ }
@@ -1,105 +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>
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
105
  )}