spoko-design-system 0.9.3 → 0.9.4

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 (172) hide show
  1. package/.astro/settings.json +4 -4
  2. package/.astro/types.d.ts +1 -1
  3. package/.claude/settings.local.json +2 -1
  4. package/.github/dependabot.yml +11 -11
  5. package/.github/todo.yml +3 -3
  6. package/.github/workflows/deploy.yml +39 -39
  7. package/.stackblitzrc +5 -5
  8. package/.vscode/extensions.json +5 -5
  9. package/.vscode/launch.json +11 -11
  10. package/.vscode/settings.json +5 -5
  11. package/LICENSE +21 -21
  12. package/README.md +116 -116
  13. package/astro-i18next.config.mjs +17 -17
  14. package/astro-i18next.config.ts +10 -10
  15. package/astro.config.mjs +86 -86
  16. package/dev-dist/sw.js +91 -91
  17. package/dev-dist/workbox-c676b6d3.js +3391 -3391
  18. package/icon.config.ts +310 -310
  19. package/index.ts +70 -70
  20. package/package.json +124 -124
  21. package/public/arrow-bottom.svg +7 -7
  22. package/public/fonts/lg.svg +53 -53
  23. package/public/fonts/vwhead-bold-demo.html +549 -549
  24. package/public/fonts/vwhead-regular-demo.html +549 -549
  25. package/public/fonts/vwtext-bold-demo.html +549 -549
  26. package/public/fonts/vwtext-regular-demo.html +549 -549
  27. package/public/github.svg +3 -3
  28. package/public/grid_dot.svg +4 -4
  29. package/public/linkedin.svg +44 -44
  30. package/public/locales/en/translation.json +12 -12
  31. package/public/locales/pl/translation.json +12 -12
  32. package/public/make-scrollable-code-focusable.js +3 -3
  33. package/public/pagefind.yml +3 -3
  34. package/public/polo.blue.svg +29 -29
  35. package/public/spoko.space.svg +71 -71
  36. package/public/twitter.svg +46 -46
  37. package/renovate.json +6 -6
  38. package/sandbox.config.json +11 -11
  39. package/src/MyComponent.astro +8 -8
  40. package/src/components/Badge.vue +19 -19
  41. package/src/components/Badges.vue +21 -21
  42. package/src/components/Breadcrumbs.vue +94 -94
  43. package/src/components/Button.vue +101 -101
  44. package/src/components/ButtonCopy.astro +183 -183
  45. package/src/components/ButtonCopy.vue +36 -36
  46. package/src/components/Card.astro +27 -27
  47. package/src/components/Carousel.astro +26 -26
  48. package/src/components/Category/CategoriesCarousel.astro +101 -101
  49. package/src/components/Category/CategoryDetails.astro +169 -169
  50. package/src/components/Category/CategoryLink.vue +28 -28
  51. package/src/components/Category/CategorySidebarToggler.vue +9 -9
  52. package/src/components/Category/CategoryTile.astro +37 -37
  53. package/src/components/Category/CategoryViewToggler.astro +89 -89
  54. package/src/components/Category/SubCategoryLink.vue +19 -19
  55. package/src/components/Copyright.astro +12 -12
  56. package/src/components/Date.astro +7 -7
  57. package/src/components/Faq.astro +33 -33
  58. package/src/components/FaqItem.astro +80 -80
  59. package/src/components/FeaturesList.vue +37 -37
  60. package/src/components/FuckRussia.vue +62 -62
  61. package/src/components/HandDrive.astro +29 -29
  62. package/src/components/Header/Header.astro +210 -210
  63. package/src/components/Header/SkipToContent.astro +1 -1
  64. package/src/components/Headline.vue +87 -87
  65. package/src/components/Image.astro +30 -30
  66. package/src/components/{layout → Layout}/CallToAction.astro +52 -52
  67. package/src/components/{layout → Layout}/Container.astro +7 -7
  68. package/src/components/{layout → Layout}/Header.astro +80 -80
  69. package/src/components/LeftSidebar.astro +53 -53
  70. package/src/components/MainColors.vue +22 -22
  71. package/src/components/MainInput.vue +15 -15
  72. package/src/components/Modal.astro +27 -27
  73. package/src/components/PageContent.astro +5 -5
  74. package/src/components/PartNumber.vue +27 -27
  75. package/src/components/Post/PostCategories.astro +41 -41
  76. package/src/components/Post/PostCategories.vue +30 -30
  77. package/src/components/PostHeader.astro +103 -103
  78. package/src/components/PrCode.vue +141 -141
  79. package/src/components/Product/ProductButton.vue +18 -18
  80. package/src/components/Product/ProductCarousel.astro +35 -35
  81. package/src/components/Product/ProductEngineType.vue +42 -42
  82. package/src/components/Product/ProductImage.astro +40 -40
  83. package/src/components/Product/ProductLink.astro +101 -101
  84. package/src/components/Product/ProductLink.vue +59 -59
  85. package/src/components/Product/ProductLinkInfo.astro +37 -37
  86. package/src/components/Product/ProductNumber.astro +60 -60
  87. package/src/components/ProductCarousel.astro +38 -38
  88. package/src/components/ProductCodes.vue +39 -39
  89. package/src/components/ProductDetailName.vue +52 -52
  90. package/src/components/ProductDetailsList.vue +216 -216
  91. package/src/components/ProductTile.astro +48 -48
  92. package/src/components/Quote.vue +23 -23
  93. package/src/components/ReloadPrompt.astro +50 -50
  94. package/src/components/SlimBanner.vue +72 -72
  95. package/src/components/Table.vue +32 -32
  96. package/src/components/TableOfContents.astro +15 -15
  97. package/src/components/Translations.vue +23 -23
  98. package/src/components/flags/FlagPL.vue +3 -3
  99. package/src/components/flags/FlagUA.vue +2 -2
  100. package/src/config.ts +56 -56
  101. package/src/design.config.ts +98 -98
  102. package/src/env.d.ts +6 -6
  103. package/src/layouts/Layout.astro +61 -61
  104. package/src/layouts/MainLayout.astro +81 -81
  105. package/src/layouts/partials/FooterCommon.astro +4 -4
  106. package/src/layouts/partials/HeadCommon.astro +44 -44
  107. package/src/layouts/partials/HeadSEO.astro +41 -41
  108. package/src/pages/components/badges.mdx +57 -57
  109. package/src/pages/components/breadcrumbs.mdx +139 -139
  110. package/src/pages/components/buttons.mdx +360 -360
  111. package/src/pages/components/card.mdx +294 -294
  112. package/src/pages/components/carousel.mdx +62 -62
  113. package/src/pages/components/copyright.mdx +42 -42
  114. package/src/pages/components/details-list.mdx +115 -115
  115. package/src/pages/components/features-list.mdx +37 -37
  116. package/src/pages/components/flags.mdx +49 -49
  117. package/src/pages/components/fuck-russia.mdx +39 -39
  118. package/src/pages/components/hand-drive.mdx +38 -38
  119. package/src/pages/components/headline.mdx +137 -137
  120. package/src/pages/components/icons.astro +135 -135
  121. package/src/pages/components/image.mdx +513 -513
  122. package/src/pages/components/input.mdx +367 -367
  123. package/src/pages/components/jumbotron.mdx +359 -359
  124. package/src/pages/components/modal.mdx +64 -64
  125. package/src/pages/components/post-header.mdx +64 -64
  126. package/src/pages/components/pr-code.mdx +65 -65
  127. package/src/pages/components/product-number.mdx +58 -58
  128. package/src/pages/components/product-tile.mdx +51 -51
  129. package/src/pages/components/quote.mdx +33 -33
  130. package/src/pages/components/slimbanner.mdx +35 -35
  131. package/src/pages/components/table.mdx +108 -108
  132. package/src/pages/core/colors.mdx +10 -10
  133. package/src/pages/core/grid.mdx +89 -89
  134. package/src/pages/core/introduction.mdx +77 -77
  135. package/src/pages/core/shadows.astro +20 -20
  136. package/src/pages/core/typography.astro +49 -49
  137. package/src/pages/index.astro +133 -133
  138. package/src/pages/patterns/introduction.mdx +60 -60
  139. package/src/pwa.ts +12 -12
  140. package/src/styles/_variables.scss +70 -70
  141. package/src/styles/base/base.css +184 -184
  142. package/src/styles/base/grid.css +92 -92
  143. package/src/styles/base/typography.css +70 -70
  144. package/src/styles/content.css +73 -73
  145. package/src/styles/main.css +7 -7
  146. package/src/types/Product.ts +31 -31
  147. package/src/types/astro.d.ts +3 -3
  148. package/src/utils/product/getPriceFormatted.ts +15 -15
  149. package/src/utils/product/getProductChecklist.ts +17 -17
  150. package/src/utils/product/useFormatProductNumber.ts +41 -41
  151. package/src/utils/seo/getShorterDescription.ts +14 -14
  152. package/src/utils/text/formatDate.ts +5 -5
  153. package/src/utils/text/formatLocaleNumber.ts +6 -6
  154. package/src/utils/text/formatPad.ts +12 -12
  155. package/src/utils/text/getNumberFormatted.ts +33 -33
  156. package/src/utils/text/getTranslatedLink.ts +5 -5
  157. package/src/utils/text.ts +19 -19
  158. package/tailwind.config.cjs +8 -8
  159. package/tsconfig.json +28 -28
  160. package/uno-config/index.ts +268 -268
  161. package/uno-config/theme/breakpoints.ts +9 -9
  162. package/uno-config/theme/colors.ts +64 -64
  163. package/uno-config/theme/dimensions.ts +17 -17
  164. package/uno-config/theme/effects.ts +14 -14
  165. package/uno-config/theme/grid.ts +10 -10
  166. package/uno-config/theme/index.ts +28 -28
  167. package/uno-config/theme/shortcuts/buttons.ts +53 -53
  168. package/uno-config/theme/shortcuts/components.ts +123 -123
  169. package/uno-config/theme/shortcuts/index.ts +20 -20
  170. package/uno-config/theme/shortcuts/layout.ts +74 -74
  171. package/uno-config/theme/typography.ts +29 -29
  172. package/uno.config.ts +2 -2
@@ -1,39 +1,39 @@
1
- <script lang="ts" setup>
2
- import type { PropType } from 'vue'
3
- import PrCode from './PrCode.vue';
4
-
5
- const props = defineProps({
6
- prcodes: {
7
- type: Object as PropType<string[] | null>,
8
- default: null,
9
- required: true,
10
- },
11
- isPdp: {
12
- type: Boolean,
13
- default: false,
14
- required: false,
15
- },
16
- })
17
-
18
- const codes = props.prcodes || []
19
- const decodedCodes = codes ? codes.sort() : []
20
-
21
- const settings = {
22
- prcodes: decodedCodes,
23
- }
24
- </script>
25
-
26
- <template>
27
-
28
- <span
29
- v-for="(prcode, index) in settings.prcodes"
30
- :key="index"
31
- class="not-last:mr-1"
32
- >
33
- <PrCode :prcode="prcode" v-if="!String(prcode).includes('+')" />
34
- <span v-else >
35
- <PrCode v-for="(splittedCode, index2) in String(prcode).split('+')" :key="index2" :prcode="splittedCode" />
36
- </span>
37
- </span>
38
- </template>
39
-
1
+ <script lang="ts" setup>
2
+ import type { PropType } from 'vue'
3
+ import PrCode from './PrCode.vue';
4
+
5
+ const props = defineProps({
6
+ prcodes: {
7
+ type: Object as PropType<string[] | null>,
8
+ default: null,
9
+ required: true,
10
+ },
11
+ isPdp: {
12
+ type: Boolean,
13
+ default: false,
14
+ required: false,
15
+ },
16
+ })
17
+
18
+ const codes = props.prcodes || []
19
+ const decodedCodes = codes ? codes.sort() : []
20
+
21
+ const settings = {
22
+ prcodes: decodedCodes,
23
+ }
24
+ </script>
25
+
26
+ <template>
27
+
28
+ <span
29
+ v-for="(prcode, index) in settings.prcodes"
30
+ :key="index"
31
+ class="not-last:mr-1"
32
+ >
33
+ <PrCode :prcode="prcode" v-if="!String(prcode).includes('+')" />
34
+ <span v-else >
35
+ <PrCode v-for="(splittedCode, index2) in String(prcode).split('+')" :key="index2" :prcode="splittedCode" />
36
+ </span>
37
+ </span>
38
+ </template>
39
+
@@ -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>
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>
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,216 +1,216 @@
1
- <script setup lang="ts">
2
- import { PropType, computed } from "vue";
3
- import ProductDetailName from "./ProductDetailName.vue";
4
-
5
- interface ColorCode {
6
- code: string;
7
- name: string;
8
- }
9
-
10
- interface TableItem {
11
- id: string;
12
- name: string;
13
- value: unknown; // Może być string, number, boolean, array (ColorCode[] lub string[])
14
- translated?: boolean;
15
- icon?: boolean;
16
- isArrayValue?: boolean;
17
- isColorArray?: boolean; // dla product.colors (color_ids)
18
- isPaintMarks?: boolean; // dla product.paint_marks_text
19
- isGenericArray?: boolean; // dla ogólnych tablic stringów (np. position)
20
- isForExteriorColour?: boolean; // Ta flaga będzie ustawiana przez getProductDetails na true, jeśli 'value' jest tablicą
21
- }
22
-
23
- interface GroupedLink {
24
- id: string;
25
- links: {
26
- name: string;
27
- value: string;
28
- }[];
29
- }
30
-
31
- const props = defineProps({
32
- items: { type: Array as PropType<TableItem[]>, default: () => [] },
33
- caption: { type: String, default: null }
34
- });
35
-
36
- // Function to check if a value is a link
37
- const isLink = (id: string) => {
38
- return ['blog', 'youtube', 'vimeo'].includes(id);
39
- };
40
-
41
- // Function to check if it's a color array (for 'color_ids' field from product.colors)
42
- // This will still apply to the 'color' detail if its value is an array of ColorCode objects
43
- const isColorArray = (item: TableItem) => {
44
- const colorIds = ['color', 'thread-color']; // lista ID które są kolorami
45
- return (item.isColorArray || colorIds.includes(item.id)) && Array.isArray(item.value);
46
- };
47
-
48
- // Function to check if it's paint marks (value is now a string from API)
49
- const isPaintMarks = (item: TableItem) => {
50
- return item.isPaintMarks && typeof item.value === 'string';
51
- };
52
-
53
- // Function to check if it's a generic array (e.g., for position)
54
- const isGenericArray = (item: TableItem) => {
55
- return item.isGenericArray && Array.isArray(item.value);
56
- };
57
-
58
- // ✅ Zaktualizowana funkcja: Sprawdzamy ID i czy value jest faktycznie tablicą ColorCode[]
59
- const isForExteriorColour = (item: TableItem) => {
60
- return item.id === 'for-exterior-colour' && Array.isArray(item.value);
61
- };
62
-
63
-
64
- // Function to check if value is HTML string (fallback)
65
- const isHtmlValue = (value: unknown): boolean => {
66
- return typeof value === 'string' && (value.includes('<span') || value.includes('<br>'));
67
- };
68
-
69
- // Function for specifying header text
70
- const getHeaderText = (row: TableItem | GroupedLink) => {
71
- if (row.id === 'blog') {
72
- return row.id.charAt(0).toUpperCase() + row.id.slice(1);
73
- }
74
- // Użyj `name` z obiektu `TableItem`, jeśli istnieje, w przeciwnym razie sformatuj `id`.
75
- return 'name' in row ? row.name : row.id.split('-')
76
- .map(word => word.charAt(0).toUpperCase() + word.slice(1))
77
- .join(' ');
78
- };
79
-
80
- // Function to determine the icon class for a link type
81
- const getLinkIconClass = (linkId: string) => {
82
- switch (linkId) {
83
- case 'blog':
84
- return 'i-lucide-book-text';
85
- case 'youtube':
86
- return 'i-simple-icons-youtube';
87
- case 'vimeo':
88
- return 'i-simple-icons-vimeo';
89
- default:
90
- return 'i-lucide-link';
91
- }
92
- };
93
-
94
- // Grouping of elements by id
95
- const groupedItems = computed(() => {
96
- // ✅ Add validation to ensure props.items is an array
97
- if (!Array.isArray(props.items)) {
98
- console.warn('ProductDetailsList: items prop is not an array:', props.items);
99
- return [];
100
- }
101
-
102
- const result: (TableItem | GroupedLink)[] = [];
103
- const linkGroups = new Map<string, GroupedLink>();
104
-
105
- // Process all elements
106
- props.items.forEach(item => {
107
- // If it's a link (blog, youtube, vimeo)
108
- if (isLink(item.id)) {
109
- // Add a link to the relevant group
110
- if (!linkGroups.has(item.id)) {
111
- linkGroups.set(item.id, {
112
- id: item.id,
113
- links: []
114
- });
115
- }
116
-
117
- // Add link to the relevant group
118
- linkGroups.get(item.id)?.links.push({
119
- name: item.name,
120
- value: item.value as string
121
- });
122
- } else {
123
- // If it's not a link, add it normally to the results
124
- result.push(item);
125
- }
126
- });
127
-
128
- // Add all link groups at the end
129
- linkGroups.forEach(group => {
130
- result.push(group);
131
- });
132
-
133
- return result;
134
- });
135
- </script>
136
-
137
- <template>
138
- <table class="details-table">
139
- <caption v-if="!!$slots.caption || caption">
140
- <slot name="caption">{{ caption }}</slot>
141
- </caption>
142
- <colgroup>
143
- <col class="details-table-col">
144
- <col class="details-table-col">
145
- </colgroup>
146
- <tbody>
147
- <tr v-for="row, index in groupedItems" :key="index" class="details-table-row">
148
- <ProductDetailName as="th" :text="getHeaderText(row)" class="details-table-header" />
149
-
150
- <td v-if="'links' in row" class="details-table-cell">
151
- <ul class="list-none p-0 m-0">
152
- <li v-for="(link, linkIndex) in row.links" :key="linkIndex" class="mb-2 last:mb-0 flex items-center">
153
- <span
154
- :class="[getLinkIconClass(row.id), 'leading-none inline-block mr-2 w-4 min-w-4 h-4 text-gray-400']" />
155
- <a :href="link.value" target="_blank" rel="noopener noreferrer" class="link-primary">
156
- {{ link.name }}
157
- </a>
158
- </li>
159
- </ul>
160
- </td>
161
-
162
- <td v-else-if="'id' in row && isColorArray(row)" class="details-table-cell">
163
- <ul class="list-none p-0 m-0">
164
- <li v-for="(colorItem, colorIndex) in (row.value as ColorCode[])" :key="colorIndex"
165
- class="flex items-center gap-1 mb-1 last:mb-0">
166
-
167
- <template v-if="colorItem.code">
168
- <code class="font-mono text-sm">
169
- {{ colorItem.code }}
170
- </code>
171
- <span class="text-gray-400">-</span>
172
- </template>
173
- <span class="text-gray-700 dark:text-gray-300">{{ colorItem.name }}</span>
174
- </li>
175
- </ul>
176
- </td>
177
-
178
- <td v-else-if="'id' in row && isPaintMarks(row)" class="details-table-cell">
179
- <span class="text-gray-700 dark:text-gray-300">{{ row.value }}</span>
180
- </td>
181
-
182
- <td v-else-if="'id' in row && isForExteriorColour(row)" class="details-table-cell">
183
- <ul class="list-none p-0 m-0">
184
- <li v-for="(colorEntry, colourIndex) in (row.value as ColorCode[])" :key="colourIndex"
185
- class="flex items-center gap-1 mb-1 last:mb-0">
186
-
187
- <template v-if="colorEntry.code">
188
- <code class="font-mono text-sm">
189
- {{ colorEntry.code }}
190
- </code>
191
- <span class="text-gray-400">-</span>
192
- </template>
193
- <span class="text-gray-700 dark:text-gray-300">{{ colorEntry.name }}</span>
194
- </li>
195
- </ul>
196
- </td>
197
-
198
- <td v-else-if="'id' in row && isGenericArray(row)" class="details-table-cell">
199
- <ul class="list-none p-0 m-0">
200
- <li v-for="(item, itemIndex) in (row.value as string[])" :key="itemIndex"
201
- class="flex items-start gap-2 mb-1 last:mb-0 leading-relaxed">
202
- <span class="text-gray-500 font-bold flex-shrink-0 mt-0.5">·</span>
203
- <span class="text-gray-700 dark:text-gray-300 text-sm">{{ item }}</span>
204
- </li>
205
- </ul>
206
- </td>
207
-
208
- <td v-else-if="'id' in row && isHtmlValue(row.value)" class="details-table-cell" v-html="row.value"></td>
209
-
210
- <slot v-else-if="'id' in row" :name="row.id">
211
- <td class="details-table-cell">{{ row.value }}</td>
212
- </slot>
213
- </tr>
214
- </tbody>
215
- </table>
216
- </template>
1
+ <script setup lang="ts">
2
+ import { PropType, computed } from "vue";
3
+ import ProductDetailName from "./ProductDetailName.vue";
4
+
5
+ interface ColorCode {
6
+ code: string;
7
+ name: string;
8
+ }
9
+
10
+ interface TableItem {
11
+ id: string;
12
+ name: string;
13
+ value: unknown; // Może być string, number, boolean, array (ColorCode[] lub string[])
14
+ translated?: boolean;
15
+ icon?: boolean;
16
+ isArrayValue?: boolean;
17
+ isColorArray?: boolean; // dla product.colors (color_ids)
18
+ isPaintMarks?: boolean; // dla product.paint_marks_text
19
+ isGenericArray?: boolean; // dla ogólnych tablic stringów (np. position)
20
+ isForExteriorColour?: boolean; // Ta flaga będzie ustawiana przez getProductDetails na true, jeśli 'value' jest tablicą
21
+ }
22
+
23
+ interface GroupedLink {
24
+ id: string;
25
+ links: {
26
+ name: string;
27
+ value: string;
28
+ }[];
29
+ }
30
+
31
+ const props = defineProps({
32
+ items: { type: Array as PropType<TableItem[]>, default: () => [] },
33
+ caption: { type: String, default: null }
34
+ });
35
+
36
+ // Function to check if a value is a link
37
+ const isLink = (id: string) => {
38
+ return ['blog', 'youtube', 'vimeo'].includes(id);
39
+ };
40
+
41
+ // Function to check if it's a color array (for 'color_ids' field from product.colors)
42
+ // This will still apply to the 'color' detail if its value is an array of ColorCode objects
43
+ const isColorArray = (item: TableItem) => {
44
+ const colorIds = ['color', 'thread-color']; // lista ID które są kolorami
45
+ return (item.isColorArray || colorIds.includes(item.id)) && Array.isArray(item.value);
46
+ };
47
+
48
+ // Function to check if it's paint marks (value is now a string from API)
49
+ const isPaintMarks = (item: TableItem) => {
50
+ return item.isPaintMarks && typeof item.value === 'string';
51
+ };
52
+
53
+ // Function to check if it's a generic array (e.g., for position)
54
+ const isGenericArray = (item: TableItem) => {
55
+ return item.isGenericArray && Array.isArray(item.value);
56
+ };
57
+
58
+ // ✅ Zaktualizowana funkcja: Sprawdzamy ID i czy value jest faktycznie tablicą ColorCode[]
59
+ const isForExteriorColour = (item: TableItem) => {
60
+ return item.id === 'for-exterior-colour' && Array.isArray(item.value);
61
+ };
62
+
63
+
64
+ // Function to check if value is HTML string (fallback)
65
+ const isHtmlValue = (value: unknown): boolean => {
66
+ return typeof value === 'string' && (value.includes('<span') || value.includes('<br>'));
67
+ };
68
+
69
+ // Function for specifying header text
70
+ const getHeaderText = (row: TableItem | GroupedLink) => {
71
+ if (row.id === 'blog') {
72
+ return row.id.charAt(0).toUpperCase() + row.id.slice(1);
73
+ }
74
+ // Użyj `name` z obiektu `TableItem`, jeśli istnieje, w przeciwnym razie sformatuj `id`.
75
+ return 'name' in row ? row.name : row.id.split('-')
76
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
77
+ .join(' ');
78
+ };
79
+
80
+ // Function to determine the icon class for a link type
81
+ const getLinkIconClass = (linkId: string) => {
82
+ switch (linkId) {
83
+ case 'blog':
84
+ return 'i-lucide-book-text';
85
+ case 'youtube':
86
+ return 'i-simple-icons-youtube';
87
+ case 'vimeo':
88
+ return 'i-simple-icons-vimeo';
89
+ default:
90
+ return 'i-lucide-link';
91
+ }
92
+ };
93
+
94
+ // Grouping of elements by id
95
+ const groupedItems = computed(() => {
96
+ // ✅ Add validation to ensure props.items is an array
97
+ if (!Array.isArray(props.items)) {
98
+ console.warn('ProductDetailsList: items prop is not an array:', props.items);
99
+ return [];
100
+ }
101
+
102
+ const result: (TableItem | GroupedLink)[] = [];
103
+ const linkGroups = new Map<string, GroupedLink>();
104
+
105
+ // Process all elements
106
+ props.items.forEach(item => {
107
+ // If it's a link (blog, youtube, vimeo)
108
+ if (isLink(item.id)) {
109
+ // Add a link to the relevant group
110
+ if (!linkGroups.has(item.id)) {
111
+ linkGroups.set(item.id, {
112
+ id: item.id,
113
+ links: []
114
+ });
115
+ }
116
+
117
+ // Add link to the relevant group
118
+ linkGroups.get(item.id)?.links.push({
119
+ name: item.name,
120
+ value: item.value as string
121
+ });
122
+ } else {
123
+ // If it's not a link, add it normally to the results
124
+ result.push(item);
125
+ }
126
+ });
127
+
128
+ // Add all link groups at the end
129
+ linkGroups.forEach(group => {
130
+ result.push(group);
131
+ });
132
+
133
+ return result;
134
+ });
135
+ </script>
136
+
137
+ <template>
138
+ <table class="details-table">
139
+ <caption v-if="!!$slots.caption || caption">
140
+ <slot name="caption">{{ caption }}</slot>
141
+ </caption>
142
+ <colgroup>
143
+ <col class="details-table-col">
144
+ <col class="details-table-col">
145
+ </colgroup>
146
+ <tbody>
147
+ <tr v-for="row, index in groupedItems" :key="index" class="details-table-row">
148
+ <ProductDetailName as="th" :text="getHeaderText(row)" class="details-table-header" />
149
+
150
+ <td v-if="'links' in row" class="details-table-cell">
151
+ <ul class="list-none p-0 m-0">
152
+ <li v-for="(link, linkIndex) in row.links" :key="linkIndex" class="mb-2 last:mb-0 flex items-center">
153
+ <span
154
+ :class="[getLinkIconClass(row.id), 'leading-none inline-block mr-2 w-4 min-w-4 h-4 text-gray-400']" />
155
+ <a :href="link.value" target="_blank" rel="noopener noreferrer" class="link-primary">
156
+ {{ link.name }}
157
+ </a>
158
+ </li>
159
+ </ul>
160
+ </td>
161
+
162
+ <td v-else-if="'id' in row && isColorArray(row)" class="details-table-cell">
163
+ <ul class="list-none p-0 m-0">
164
+ <li v-for="(colorItem, colorIndex) in (row.value as ColorCode[])" :key="colorIndex"
165
+ class="flex items-center gap-1 mb-1 last:mb-0">
166
+
167
+ <template v-if="colorItem.code">
168
+ <code class="font-mono text-sm">
169
+ {{ colorItem.code }}
170
+ </code>
171
+ <span class="text-gray-400">-</span>
172
+ </template>
173
+ <span class="text-gray-700 dark:text-gray-300">{{ colorItem.name }}</span>
174
+ </li>
175
+ </ul>
176
+ </td>
177
+
178
+ <td v-else-if="'id' in row && isPaintMarks(row)" class="details-table-cell">
179
+ <span class="text-gray-700 dark:text-gray-300">{{ row.value }}</span>
180
+ </td>
181
+
182
+ <td v-else-if="'id' in row && isForExteriorColour(row)" class="details-table-cell">
183
+ <ul class="list-none p-0 m-0">
184
+ <li v-for="(colorEntry, colourIndex) in (row.value as ColorCode[])" :key="colourIndex"
185
+ class="flex items-center gap-1 mb-1 last:mb-0">
186
+
187
+ <template v-if="colorEntry.code">
188
+ <code class="font-mono text-sm">
189
+ {{ colorEntry.code }}
190
+ </code>
191
+ <span class="text-gray-400">-</span>
192
+ </template>
193
+ <span class="text-gray-700 dark:text-gray-300">{{ colorEntry.name }}</span>
194
+ </li>
195
+ </ul>
196
+ </td>
197
+
198
+ <td v-else-if="'id' in row && isGenericArray(row)" class="details-table-cell">
199
+ <ul class="list-none p-0 m-0">
200
+ <li v-for="(item, itemIndex) in (row.value as string[])" :key="itemIndex"
201
+ class="flex items-start gap-2 mb-1 last:mb-0 leading-relaxed">
202
+ <span class="text-gray-500 font-bold flex-shrink-0 mt-0.5">·</span>
203
+ <span class="text-gray-700 dark:text-gray-300 text-sm">{{ item }}</span>
204
+ </li>
205
+ </ul>
206
+ </td>
207
+
208
+ <td v-else-if="'id' in row && isHtmlValue(row.value)" class="details-table-cell" v-html="row.value"></td>
209
+
210
+ <slot v-else-if="'id' in row" :name="row.id">
211
+ <td class="details-table-cell">{{ row.value }}</td>
212
+ </slot>
213
+ </tr>
214
+ </tbody>
215
+ </table>
216
+ </template>