spoko-design-system 1.9.0 → 1.9.2

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 (86) hide show
  1. package/.claude/settings.json +1 -1
  2. package/.husky/pre-commit +17 -1
  3. package/.prettierrc +8 -4
  4. package/CHANGELOG.md +35 -0
  5. package/index.ts +8 -2
  6. package/package.json +4 -3
  7. package/src/components/Badge.vue +1 -4
  8. package/src/components/Badges.vue +1 -4
  9. package/src/components/Breadcrumbs.vue +10 -44
  10. package/src/components/Button.vue +1 -5
  11. package/src/components/ButtonCopy.astro +2 -7
  12. package/src/components/ButtonCopy.vue +2 -9
  13. package/src/components/Card.astro +1 -4
  14. package/src/components/Category/CategoriesCarousel.astro +3 -11
  15. package/src/components/Category/CategoryDetails.astro +7 -32
  16. package/src/components/Category/CategoryLink.vue +1 -5
  17. package/src/components/Category/CategorySidebarToggler.vue +1 -5
  18. package/src/components/Category/CategoryTile.astro +2 -9
  19. package/src/components/Category/CategoryViewToggler.astro +3 -16
  20. package/src/components/Copyright.astro +1 -4
  21. package/src/components/Date.astro +1 -4
  22. package/src/components/Faq.astro +1 -5
  23. package/src/components/FaqItem.astro +3 -14
  24. package/src/components/FeaturesList.vue +2 -9
  25. package/src/components/FuckRussia.vue +9 -36
  26. package/src/components/HandDrive.astro +2 -12
  27. package/src/components/Header/Header.astro +3 -14
  28. package/src/components/Header/SkipToContent.astro +1 -5
  29. package/src/components/Input.vue +19 -31
  30. package/src/components/Jumbotron/index.astro +7 -41
  31. package/src/components/Jumbotron/variants/Default.astro +2 -17
  32. package/src/components/Jumbotron/variants/Hero.astro +3 -17
  33. package/src/components/Jumbotron/variants/Post.astro +3 -13
  34. package/src/components/Jumbotron/variants/PostSplit.astro +3 -13
  35. package/src/components/Jumbotron.astro +3 -12
  36. package/src/components/LanguageSuggestion.astro +3 -14
  37. package/src/components/MainColors.vue +7 -25
  38. package/src/components/MainInput.vue +2 -1
  39. package/src/components/Modal.astro +43 -41
  40. package/src/components/PageContent.astro +1 -4
  41. package/src/components/PartNumber.vue +1 -4
  42. package/src/components/PostHeader.astro +3 -13
  43. package/src/components/PrCode.vue +2 -2
  44. package/src/components/Product/ProductButton.vue +1 -4
  45. package/src/components/Product/ProductDetailName.vue +1 -4
  46. package/src/components/Product/ProductDetails.vue +19 -65
  47. package/src/components/Product/ProductDoc.vue +1 -4
  48. package/src/components/Product/ProductEngine.astro +67 -0
  49. package/src/components/Product/ProductEngineType.vue +1 -4
  50. package/src/components/Product/ProductEngines.astro +43 -0
  51. package/src/components/Product/ProductLink.astro +8 -32
  52. package/src/components/Product/ProductLink.vue +8 -36
  53. package/src/components/Product/ProductLinkInfo.astro +4 -19
  54. package/src/components/Product/ProductModel.vue +3 -5
  55. package/src/components/Product/ProductModels.vue +2 -10
  56. package/src/components/Product/ProductNumber.astro +6 -26
  57. package/src/components/Product/ProductPositions.vue +1 -5
  58. package/src/components/ProductCodes.vue +6 -14
  59. package/src/components/ProductDetailName.vue +2 -7
  60. package/src/components/ProductDetailsList.vue +7 -33
  61. package/src/components/ProductTile.astro +3 -13
  62. package/src/components/ReloadPrompt.astro +1 -5
  63. package/src/components/SlimBanner.vue +10 -15
  64. package/src/components/Table.vue +3 -15
  65. package/src/components/Translations.vue +1 -4
  66. package/src/components/layout/CallToAction.astro +2 -7
  67. package/src/components/layout/Container.astro +1 -3
  68. package/src/components/layout/Header.astro +2 -12
  69. package/src/layouts/Layout.astro +2 -9
  70. package/src/layouts/MainLayout.astro +4 -17
  71. package/src/layouts/partials/HeadCommon.astro +7 -24
  72. package/src/layouts/partials/HeadSEO.astro +12 -48
  73. package/src/pages/components/icons.astro +1 -4
  74. package/src/pages/components/product-engine.mdx +75 -31
  75. package/src/pages/core/typography.astro +2 -6
  76. package/src/pages/index.astro +16 -63
  77. package/src/scripts/tooltips.ts +33 -28
  78. package/src/styles/main.css +4 -0
  79. package/src/styles/tippy-theme.css +4 -2
  80. package/src/utils/product/getEngineTooltipContent.ts +158 -0
  81. package/src/utils/product/getPriceFormatted.ts +2 -6
  82. package/src/utils/product/useFormatProductNumber.ts +1 -4
  83. package/src/utils/seo/getShorterDescription.ts +1 -4
  84. package/uno-config/index.ts +1 -1
  85. package/src/components/Product/ProductEngine.vue +0 -240
  86. package/src/components/Product/ProductEngines.vue +0 -116
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Generates HTML content for engine tooltip
3
+ * Used by both SSR (Astro) and CSR (Tippy.js delegation)
4
+ */
5
+
6
+ export interface Engine {
7
+ id?: number;
8
+ code: string;
9
+ name?: string;
10
+ info?: string | null;
11
+ serie?: {
12
+ value: string;
13
+ label: string;
14
+ };
15
+ type?: {
16
+ value: string;
17
+ translated: string;
18
+ label: string;
19
+ };
20
+ power?: {
21
+ kw: number;
22
+ ps: number;
23
+ ps_label: string;
24
+ label: string;
25
+ };
26
+ date?: {
27
+ value: string;
28
+ label: string;
29
+ };
30
+ displacement?: {
31
+ value: number;
32
+ label: string;
33
+ };
34
+ compression_ratio?: {
35
+ value: string | null;
36
+ label: string;
37
+ };
38
+ valves?: {
39
+ value: number | null;
40
+ label: string;
41
+ };
42
+ euro?: {
43
+ value: number;
44
+ label: string;
45
+ };
46
+ pivot?: any;
47
+
48
+ // Backward compatibility - old flat structure
49
+ kw?: number;
50
+ ps?: number;
51
+ cc?: number;
52
+ c_ratio?: string | null;
53
+ }
54
+
55
+ export interface EngineTranslations {
56
+ power?: string;
57
+ cc?: string;
58
+ compressionRatio?: string;
59
+ valves?: string;
60
+ euro?: string;
61
+ horsepowerUnit?: string;
62
+ }
63
+
64
+ const defaultTranslations: EngineTranslations = {
65
+ power: 'Power',
66
+ cc: 'CC',
67
+ compressionRatio: 'C. Ratio',
68
+ valves: 'Valves',
69
+ euro: 'Euro',
70
+ horsepowerUnit: 'PS',
71
+ };
72
+
73
+ // Helper to get series value (supports both old and new API)
74
+ function getSerieValue(engine: Engine): string {
75
+ if (engine.serie && typeof engine.serie === 'object') {
76
+ return engine.serie.value;
77
+ }
78
+ // Backward compatibility - old API
79
+ const serie = engine.serie as any;
80
+ if (!serie) return '';
81
+ return serie === 3 ? 'EA288' : serie === 2 ? 'EA189' : `Serie ${serie}`;
82
+ }
83
+
84
+ export function getEngineTooltipContent(engine: Engine, translations: EngineTranslations = {}): string {
85
+ const t = { ...defaultTranslations, ...translations };
86
+
87
+ // Header section
88
+ let headerContent = `<strong>${engine.name || engine.code}</strong>`;
89
+ if (engine.info) {
90
+ headerContent += ` <span class="info">${engine.info}</span>`;
91
+ }
92
+ const serieValue = getSerieValue(engine);
93
+ if (serieValue) {
94
+ headerContent += `<div class="series-badge">${serieValue}</div>`;
95
+ }
96
+
97
+ const header = `<div class="tooltip-header">${headerContent}</div>`;
98
+
99
+ // Specs rows
100
+ const rows: string[] = [];
101
+
102
+ // Power (supports both new and old API structure)
103
+ const power = engine.power;
104
+ const oldKw = engine.kw;
105
+ const oldPs = engine.ps;
106
+
107
+ if (power || oldKw || oldPs) {
108
+ const powerValues: string[] = [];
109
+ const kw = power?.kw || oldKw;
110
+ const ps = power?.ps || oldPs;
111
+ const psLabel = power?.ps_label || t.horsepowerUnit;
112
+ const powerLabel = power?.label || t.power;
113
+
114
+ if (kw) powerValues.push(`${kw} kW`);
115
+ if (ps) powerValues.push(`${ps} ${psLabel}`);
116
+
117
+ if (powerValues.length) {
118
+ rows.push(
119
+ `<div class="tooltip-row"><span class="tooltip-label">${powerLabel}:</span><span class="tooltip-value">${powerValues.join(' / ')}</span></div>`
120
+ );
121
+ }
122
+ }
123
+
124
+ // Displacement (CC)
125
+ const displacement = engine.displacement;
126
+ const oldCc = engine.cc;
127
+
128
+ if (displacement || oldCc) {
129
+ const ccValue = displacement?.value || oldCc;
130
+ const ccLabel = displacement?.label || t.cc;
131
+
132
+ if (ccValue) {
133
+ rows.push(
134
+ `<div class="tooltip-row"><span class="tooltip-label">${ccLabel}:</span><span class="tooltip-value">${ccValue} cm³</span></div>`
135
+ );
136
+ }
137
+ }
138
+
139
+ // Euro standard
140
+ const euro = engine.euro;
141
+
142
+ if (euro && typeof euro === 'object') {
143
+ if (euro.value) {
144
+ rows.push(
145
+ `<div class="tooltip-row"><span class="tooltip-label">${euro.label}:</span><span class="tooltip-value">Euro ${euro.value}</span></div>`
146
+ );
147
+ }
148
+ } else if (euro) {
149
+ // Backward compatibility - old API
150
+ rows.push(
151
+ `<div class="tooltip-row"><span class="tooltip-label">${t.euro}:</span><span class="tooltip-value">Euro ${euro}</span></div>`
152
+ );
153
+ }
154
+
155
+ const specsContent = rows.length ? `<div class="tooltip-specs">${rows.join('')}</div>` : '';
156
+
157
+ return header + specsContent;
158
+ }
@@ -6,14 +6,10 @@ interface Product {
6
6
  export const getPriceFormatted = (product: Product) => {
7
7
  // Default to EUR formatting for English-only design system
8
8
  if (product.price_eur) {
9
- return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(
10
- product.price_eur
11
- );
9
+ return new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(product.price_eur);
12
10
  }
13
11
  if (product.price_pln) {
14
- return new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'PLN' }).format(
15
- product.price_pln
16
- );
12
+ return new Intl.NumberFormat('pl-PL', { style: 'currency', currency: 'PLN' }).format(product.price_pln);
17
13
  }
18
14
  return 'no price';
19
15
  };
@@ -20,10 +20,7 @@ export default function useFormatProductNumber(productNumber: string | null): {
20
20
  let formatted = number;
21
21
 
22
22
  if (isLetter(number[0]) && !isLetter(number[1]) && number.length === 9) {
23
- formatted = formatted.replace(
24
- LIQUIDS_PATTERN,
25
- `$1${separator}$2${separator}$3${separator}$4`
26
- );
23
+ formatted = formatted.replace(LIQUIDS_PATTERN, `$1${separator}$2${separator}$3${separator}$4`);
27
24
  } else if (number.length >= 13) {
28
25
  formatted = formatted.replace(
29
26
  WHEELS_EMBLEMS_PATTERN,
@@ -1,9 +1,6 @@
1
1
  const MAX_DESCRIPTION_LENGTH = 150;
2
2
 
3
- export const getShorterDescription = (
4
- description: string | null,
5
- limit = MAX_DESCRIPTION_LENGTH
6
- ) => {
3
+ export const getShorterDescription = (description: string | null, limit = MAX_DESCRIPTION_LENGTH) => {
7
4
  function cutString(s: string, n: number) {
8
5
  const text = s.replaceAll('\n', ' ');
9
6
  const cut = text.indexOf('. ', n);
@@ -207,7 +207,7 @@ export function createSdsConfig(customConfig: CustomConfig = {}) {
207
207
  presetAttributify(),
208
208
  presetIcons({
209
209
  scale: 1.2,
210
- warn: true, // Show warnings for actual missing icons
210
+ warn: false, // Disabled to prevent false positives from JS code scanning
211
211
  prefix: 'i-',
212
212
  extraProperties: {
213
213
  'display': 'inline-block',
@@ -1,240 +0,0 @@
1
- <script lang="ts" setup>
2
- import { ref, onMounted, onUnmounted } from 'vue';
3
- import type { PropType } from 'vue';
4
- import tippy, { type Instance } from 'tippy.js';
5
- import 'tippy.js/dist/tippy.css';
6
- import '../../styles/tippy-theme.css';
7
-
8
- /*
9
- VAG group (VW/Audi/Skoda/Seat/Porsche/Bentley/Lamborghini/Ducati/Cupra/Scania/MAN) manufacturer Engine Code
10
- Displays engine code with detailed tooltip showing: name, power, displacement, dates, etc.
11
- */
12
-
13
- interface Engine {
14
- id?: number;
15
- code: string;
16
- name?: string;
17
- info?: string | null;
18
- serie?: {
19
- value: string;
20
- label: string;
21
- };
22
- type?: {
23
- value: string;
24
- translated: string;
25
- label: string;
26
- };
27
- power?: {
28
- kw: number;
29
- ps: number;
30
- ps_label: string;
31
- label: string;
32
- };
33
- date?: {
34
- value: string;
35
- label: string;
36
- };
37
- displacement?: {
38
- value: number;
39
- label: string;
40
- };
41
- compression_ratio?: {
42
- value: string | null;
43
- label: string;
44
- };
45
- valves?: {
46
- value: number | null;
47
- label: string;
48
- };
49
- euro?: {
50
- value: number;
51
- label: string;
52
- };
53
- pivot?: any;
54
-
55
- // Backward compatibility - old flat structure
56
- kw?: number;
57
- ps?: number;
58
- cc?: number;
59
- c_ratio?: string | null;
60
- }
61
-
62
- const props = defineProps({
63
- engine: {
64
- type: Object as PropType<Engine>,
65
- required: true,
66
- },
67
- showComma: {
68
- type: Boolean,
69
- default: false,
70
- required: false,
71
- },
72
- translations: {
73
- type: Object as PropType<{
74
- power?: string;
75
- cc?: string;
76
- compressionRatio?: string;
77
- valves?: string;
78
- euro?: string;
79
- horsepowerUnit?: string; // 'PS' for German, 'KM' for Polish, 'HP' for English
80
- }>,
81
- default: () => ({
82
- power: 'Power',
83
- cc: 'CC',
84
- compressionRatio: 'C. Ratio',
85
- valves: 'Valves',
86
- euro: 'Euro',
87
- horsepowerUnit: 'PS',
88
- }),
89
- required: false,
90
- },
91
- });
92
-
93
- const engineRef = ref<HTMLElement | null>(null);
94
- let tippyInstance: Instance | null = null;
95
-
96
- // Helper to get series value (supports both old and new API)
97
- const getSerieValue = (): string => {
98
- if (props.engine.serie && typeof props.engine.serie === 'object') {
99
- return props.engine.serie.value;
100
- }
101
- // Backward compatibility - old API
102
- const serie = props.engine.serie as any;
103
- if (!serie) return '';
104
- return serie === 3 ? 'EA288' : serie === 2 ? 'EA189' : `Serie ${serie}`;
105
- };
106
-
107
- // Generate tooltip HTML content
108
- const getTooltipContent = () => {
109
- // Header section
110
- let headerContent = `<strong>${props.engine.name || props.engine.code}</strong>`;
111
- if (props.engine.info) {
112
- headerContent += ` <span class="info">${props.engine.info}</span>`;
113
- }
114
- const serieValue = getSerieValue();
115
- if (serieValue) {
116
- headerContent += `<div class="series-badge">${serieValue}</div>`;
117
- }
118
-
119
- const header = `<div class="tooltip-header">${headerContent}</div>`;
120
-
121
- // Specs rows
122
- const rows = [];
123
-
124
- // Power (supports both new and old API structure)
125
- const power = props.engine.power;
126
- const oldKw = props.engine.kw;
127
- const oldPs = props.engine.ps;
128
-
129
- if (power || oldKw || oldPs) {
130
- const powerValues = [];
131
- const kw = power?.kw || oldKw;
132
- const ps = power?.ps || oldPs;
133
- const psLabel = power?.ps_label || props.translations.horsepowerUnit;
134
- const powerLabel = power?.label || props.translations.power;
135
-
136
- if (kw) powerValues.push(`${kw} kW`);
137
- if (ps) powerValues.push(`${ps} ${psLabel}`);
138
-
139
- if (powerValues.length) {
140
- rows.push(`<div class="tooltip-row"><span class="tooltip-label">${powerLabel}:</span><span class="tooltip-value">${powerValues.join(' / ')}</span></div>`);
141
- }
142
- }
143
-
144
- // Displacement (CC)
145
- const displacement = props.engine.displacement;
146
- const oldCc = props.engine.cc;
147
-
148
- if (displacement || oldCc) {
149
- const ccValue = displacement?.value || oldCc;
150
- const ccLabel = displacement?.label || props.translations.cc;
151
-
152
- if (ccValue) {
153
- rows.push(`<div class="tooltip-row"><span class="tooltip-label">${ccLabel}:</span><span class="tooltip-value">${ccValue} cm³</span></div>`);
154
- }
155
- }
156
-
157
- // Euro standard
158
- const euro = props.engine.euro;
159
-
160
- if (euro && typeof euro === 'object') {
161
- if (euro.value) {
162
- rows.push(`<div class="tooltip-row"><span class="tooltip-label">${euro.label}:</span><span class="tooltip-value">Euro ${euro.value}</span></div>`);
163
- }
164
- } else if (euro) {
165
- // Backward compatibility - old API
166
- rows.push(`<div class="tooltip-row"><span class="tooltip-label">${props.translations.euro}:</span><span class="tooltip-value">Euro ${euro}</span></div>`);
167
- }
168
-
169
- const specsContent = rows.length
170
- ? `<div class="tooltip-specs">${rows.join('')}</div>`
171
- : '';
172
-
173
- return header + specsContent;
174
- };
175
-
176
- onMounted(() => {
177
- if (engineRef.value) {
178
- tippyInstance = tippy(engineRef.value, {
179
- content: getTooltipContent(),
180
- allowHTML: true,
181
- theme: 'sds',
182
- placement: 'top',
183
- arrow: true,
184
- animation: 'shift-away',
185
- duration: [200, 150],
186
- maxWidth: 280,
187
- });
188
- }
189
- });
190
-
191
- onUnmounted(() => {
192
- if (tippyInstance) {
193
- tippyInstance.destroy();
194
- }
195
- });
196
- </script>
197
-
198
- <template>
199
- <span
200
- ref="engineRef"
201
- class="engine-code"
202
- :class="`engine-code-${engine.code}`"
203
- >
204
- {{ engine.code }}<span v-if="showComma">,</span>
205
- </span>
206
- </template>
207
-
208
- <style scoped>
209
- /* Engine Code Styles */
210
- .engine-code {
211
- @apply inline-block mr-1;
212
- @apply underline decoration-dotted underline-offset-4 py-0.5;
213
- @apply decoration-neutral-light cursor-default;
214
- @apply transition-colors duration-200;
215
- }
216
-
217
- .engine-code:hover {
218
- @apply decoration-blue-darker dark:decoration-blue-light;
219
- }
220
-
221
- /* Semantic Engine Code Colors */
222
- /* GTI Engines - Red */
223
- .engine-code-CAVE,
224
- .engine-code-CTHE,
225
- .engine-code-DAJA,
226
- .engine-code-DAYB {
227
- @apply text-red-600 dark:text-red-500;
228
- }
229
-
230
- /* WRC R Engine - Blue */
231
- .engine-code-CDLJ {
232
- @apply text-blue-600 dark:text-blue-500;
233
- }
234
-
235
- /* Special Blue Engines */
236
- .engine-code-CPTA,
237
- .engine-code-CZEA {
238
- @apply text-blue-700 dark:text-blue-600;
239
- }
240
- </style>
@@ -1,116 +0,0 @@
1
- <script lang="ts" setup>
2
- import { computed } from 'vue';
3
- import type { PropType } from 'vue';
4
- import ProductEngine from './ProductEngine.vue';
5
-
6
- /*
7
- ProductEngines wrapper component
8
- Displays a list of engine codes with tooltips
9
- */
10
-
11
- interface Engine {
12
- id?: number;
13
- code: string;
14
- name?: string;
15
- info?: string | null;
16
- serie?: {
17
- value: string;
18
- label: string;
19
- } | number;
20
- type?: {
21
- value: string;
22
- translated: string;
23
- label: string;
24
- };
25
- power?: {
26
- kw: number;
27
- ps: number;
28
- ps_label: string;
29
- label: string;
30
- };
31
- date?: {
32
- value: string;
33
- label: string;
34
- };
35
- displacement?: {
36
- value: number;
37
- label: string;
38
- };
39
- compression_ratio?: {
40
- value: string | null;
41
- label: string;
42
- };
43
- valves?: {
44
- value: number | null;
45
- label: string;
46
- };
47
- euro?: {
48
- value: number;
49
- label: string;
50
- } | number;
51
- pivot?: any;
52
-
53
- // Backward compatibility
54
- kw?: number;
55
- ps?: number;
56
- cc?: number;
57
- }
58
-
59
- const props = defineProps({
60
- engines: {
61
- type: Array as PropType<Engine[]>,
62
- default: () => [],
63
- required: true,
64
- },
65
- isPdp: {
66
- type: Boolean,
67
- default: false,
68
- required: false,
69
- },
70
- translations: {
71
- type: Object as PropType<{
72
- power?: string;
73
- cc?: string;
74
- compressionRatio?: string;
75
- valves?: string;
76
- euro?: string;
77
- horsepowerUnit?: string;
78
- }>,
79
- default: () => ({
80
- power: 'Power',
81
- cc: 'CC',
82
- compressionRatio: 'C. Ratio',
83
- valves: 'Valves',
84
- euro: 'Euro',
85
- horsepowerUnit: 'PS',
86
- }),
87
- required: false,
88
- },
89
- });
90
-
91
- // Sort engines by code
92
- const sortedEngines = computed(() => {
93
- if (!props.engines || !props.engines.length) return [];
94
- return [...props.engines].sort((a, b) => {
95
- return (a.code || '').localeCompare(b.code || '');
96
- });
97
- });
98
- </script>
99
-
100
- <template>
101
- <div v-if="sortedEngines.length" class="engines-list inline-flex flex-wrap items-center gap-x-0.5">
102
- <ProductEngine
103
- v-for="(engine, index) in sortedEngines"
104
- :key="engine.id || index"
105
- :engine="engine"
106
- :show-comma="index !== sortedEngines.length - 1"
107
- :translations="translations"
108
- />
109
- </div>
110
- </template>
111
-
112
- <style scoped>
113
- .engines-list {
114
- @apply leading-none;
115
- }
116
- </style>